Compare commits

..
Author SHA1 Message Date
Jesse Duffield 7efe433f1c Mark commits which changed a file among the last X commits
See https://github.com/jesseduffield/lazygit/issues/2653
2023-05-21 13:54:06 +10:00
Jesse Duffield 6f535d71c9 Merge pull request #2652 from jesseduffield/right-align-columns 2023-05-21 12:30:53 +10:00
Jesse Duffield ec3a28df43 Right-align key labels in menu
I find this makes it look a little nicer
2023-05-21 12:09:43 +10:00
Jesse Duffield 5b933762c2 Merge pull request #2651 from jesseduffield/strikethrough-menu 2023-05-21 11:34:19 +10:00
Jesse Duffield 3eed997161 Update cheatsheet
Now that we're using the angle-bracket syntax everywhere for consistency, we need to escape
the angle brackets in the markdown of the cheatsheets.
2023-05-21 11:31:29 +10:00
Jesse Duffield 526d8a8a76 Fix cheatsheet generate VSCode task
This was previously not working because we tried to run the whole string as its own process
2023-05-21 11:31:29 +10:00
Jesse Duffield e1fc90615d Apply strikethrough style to reserved keybindings in menus
If a given menu item has an associated keybinding of 'enter', hitting enter won't actually execute
that item unless your cursor is on it. This creates confusion, and so we're going to use a strikethrough
style to communicate that the keybinding is reserved for something else.
2023-05-21 11:31:29 +10:00
Jesse Duffield 460a166e16 Stop displaying navigation keybinding at bottom of screen
The reason for this is that now our labels for navigation keybindings are larger so they
take up more realestate. It's not the kind of thing a user needs to be told anyway,
anybody is going to try out hjkl and the arrow keys when a TUI opens up.

We could map from <up> to the single character up unicode rune but given you can rebind this stuff
I'd rather keep it simple
2023-05-21 11:01:15 +10:00
Jesse Duffield 2e66d87b94 Use same labels for keys that we use in the config
Previously we were displaying keys in a different format than we expected them in the config.
This was certain to cause confusion.
2023-05-21 10:59:16 +10:00
Jesse Duffield 820f7b9404 Support strikethrough text style 2023-05-21 10:46:13 +10:00
Jesse Duffield 2e0d0a92ee Merge pull request #2647 from jesseduffield/fix-tip 2023-05-20 23:40:31 +10:00
Jesse Duffield ed857d1e07 Show correct keybinding in tip 2023-05-20 23:36:34 +10:00
Jesse Duffield b30ec538fb Merge pull request #2645 from jesseduffield/convenient-git-command-building 2023-05-20 20:58:18 +10:00
Jesse Duffield ee11046d35 Refactor interface for ApplyPatch 2023-05-20 20:54:39 +10:00
Jesse Duffield 25f8b0337e Add convenience builder for git commands 2023-05-20 20:54:24 +10:00
Jesse Duffield 63ddc52a6b Increase test coverage 2023-05-20 16:55:15 +10:00
README-bot b07c4fc001 Updated README.md 2023-05-20 03:01:38 +00:00
Jesse Duffield 2304ffc804 Merge pull request #2641 from stefanhaller/visualize-ignore-whitespace-config 2023-05-20 13:01:22 +10:00
Stefan Haller 401610c0ef Remove the toast when toggling "ignore whitespace"
Now that we visualize the state, the toast is no longer needed.
2023-05-20 12:58:32 +10:00
Stefan Haller 64b2685c2d Visualize the "ignore whitespace" state in the subtitle of the diff view 2023-05-20 12:58:32 +10:00
Stefan Haller 7d4bfb6621 Don't toggle "ignore whitespace" in the staging and patch building panels
The option doesn't have any affect in these views, so we don't need to toggle it
here. But the problem was the HandleFocus call at the end: this would activate
the wrong view, so we need to avoid it here.

Show an error if the user tries to turn the option on, to let them know that it
doesn't work here.
2023-05-20 12:58:32 +10:00
Stefan Haller a2778f01c6 Disregard the "ignore whitespace" option in the patch building panel
It's not possible to reliably stage things into a custom patch when "ignore
whitespace" is on, so always treat it as off here (like we do in the staging
panel).

It looks like this is a regression that was introduced in 8edad826ca.
2023-05-19 18:22:28 +02:00
Jesse Duffield d161afe37f Support ignoring whitespace on stash 2023-05-19 17:49:22 +02:00
README-bot 3dd96a8010 Updated README.md 2023-05-17 07:12:19 +00:00
Jesse Duffield 681a9bf20d Merge pull request #2612 from longlhh90/fix-commit-prefixes-with-empty-commit-message 2023-05-17 17:12:05 +10:00
Lukas 0606b7a43b remove empty message check as message of commit can be empty 2023-05-17 17:07:50 +10:00
Stefan Haller 33da56eeb4 Merge pull request #2619 from stefanhaller/config-for-base-branches
Add config for main branches
2023-05-16 13:23:56 +02:00
Stefan Haller 46b93bba0e Add config git.mainBranches
It defaults to {"master", "main"}, but can be set to whatever branch names
are used as base branches, e.g. {"master", "devel", "v1.0-hotfixes"}. It is
used for color-coding the shas in the commit list, i.e. to decide whether
commits are green or yellow.
2023-05-16 13:20:03 +02:00
README-bot d2d50aedd0 Updated README.md 2023-05-16 11:13:36 +00:00
Jesse Duffield ea5fb6a364 Merge pull request #2629 from jesseduffield/try-fix-conflict-continue-prompt 2023-05-16 21:13:17 +10:00
Jesse Duffield a82134f41c Fix race condition
Our refresh code may try to push a context. It does this in two places:
1) when all merge conflicts are resolved, we push a 'continue merge?' confirmation context
2) when all conflicts of a given file are resolved and we're in the merge conflicts context,
   we push the files context.

Sometimes we push the confirmation context and then push the files context over it, so the user
never sees the confirmation context.

This commit fixes the race condition by adding a check to ensure that we're still in the
merge conflicts panel before we try escaping from it
2023-05-16 21:01:38 +10:00
Jesse Duffield 00b03079d8 Don't deactivate context that you're about to activate 2023-05-16 21:01:38 +10:00
Jesse Duffield 9592686629 Compare contexts with keys
We don't want to compare contexts directly given they are interfaces and not
pointers to structs
2023-05-16 21:01:38 +10:00
Jesse Duffield 114ad52ff6 Rename CmdLog -> GuiLog
We want to log both actions and commands for the sake of integration tests
2023-05-16 21:01:38 +10:00
Stefan Haller 9514284f8e Merge pull request #2608 from stefanhaller/allow-selected-line-outside-view
Allow the selected line of a list view to be outside the visible area
2023-05-13 12:29:17 +02:00
README-bot ece14844bb Updated README.md 2023-05-13 02:33:48 +00:00
Jesse Duffield 9910a7c308 Merge pull request #2628 from stefanhaller/make-merged-take-precedence-over-unpushed 2023-05-13 12:33:34 +10:00
Stefan Haller 910a61dc46 Make "merged" take precedence over "unpushed"
Previously, when rebasing a branch onto a newer master, all commits from the
previous fork point up to its head were marked red (unpushed), including the
commits that are on master already. While this is technically correct from the
perspective of the current branch's upstream, it's not what most people expect,
intuitively; they want to see where the current branch starts, relative to
master. So all commits of master should be green, and then the commits of the
current branch in red.
2023-05-12 22:56:58 +02:00
Stefan Haller e5dd4d3110 Allow the selected line of a list view to be outside the visible area
I don't see a reason why this restriction to have the selection be always
visible was necessary. Removing it has two benefits:

1. Scrolling a list view doesn't change the selection. A common scenario: you
   look at one of the commits of your current branch; you want to see the how
   many'th commit this is, but the beginning of the branch is scrolled off the
   bottom of the commits panel. You scroll down to find the beginning of your
   branch, but this changes the selection and shows a different commit now - not
   what you want.

2. It is possible to scroll a panel that is not the current one without changing
   the focus to it. That's how windows in other GUIs usually behave.
2023-05-11 13:23:58 +02:00
Stefan Haller 595c7ee73e Extend one of the filtering tests to start on a commit other than the first
Enabling the filter selects the first entry in the filtered commits view. It's
useful to have a test that checks this, as I almost broke it in the following
commit (it needs an added FocusLine call in the setFiltering function in
filtering_menu_action.go).
2023-05-11 13:23:58 +02:00
README-bot 051af6a066 Updated README.md 2023-05-11 09:09:15 +00:00
Jesse Duffield e2d7e461a8 Merge pull request #2519 from jesseduffield/refactor-better-encapsulation 2023-05-11 19:09:01 +10:00
Jesse Duffield 7c66ca83c1 update cheatsheets 2023-05-11 19:02:25 +10:00
Jesse Duffield 007b406b14 remove duplicate method 2023-05-11 19:00:41 +10:00
Jesse Duffield 2b30085dba Merge branch 'master' into refactor-better-encapsulation 2023-05-11 19:00:01 +10:00
Jesse Duffield 5c95d23169 Merge pull request #2620 from jesseduffield/yaml 2023-05-11 09:14:57 +10:00
Jesse Duffield e156e090cc add ability to update yaml path while preserving comments 2023-05-10 22:31:27 +10:00
Jesse Duffield 0accb07dcc Merge pull request #2591 from screendriver/nvim 2023-05-04 16:55:22 +10:00
Jesse Duffield 3cac14c76e add comment to encourage keeping code and docs in sync 2023-05-04 16:53:13 +10:00
Christian Rackerseder 1636931c2b Include "kakoune" in supported edit presets 2023-05-04 08:28:58 +02:00
Christian Rackerseder 6e027f42da Include "nvim" in supported edit presets 2023-05-03 13:30:51 +02:00
Jesse Duffield 5149b24ab3 Merge pull request #2585 from stefanhaller/only-use-empty-arg-when-available 2023-05-03 17:59:07 +10:00
Jesse Duffield c520c5cfc3 Merge pull request #2588 from jesseduffield/update-open-docs 2023-05-03 13:48:50 +10:00
Jesse Duffield c88ecdf87c update open docs 2023-05-03 13:48:04 +10:00
Stefan Haller d607b366cb Add own version for test move_to_earlier_commit for older git versions 2023-05-02 16:27:32 +02:00
Stefan Haller c8f26aca68 Rename From to AtLeast 2023-05-02 16:27:32 +02:00
Ryooooooga 30656b5ac6 chore(git_commands): support old git version (git rebase --empty=keep) 2023-05-02 16:27:32 +02:00
Jesse Duffield 5dacbb6293 merge master into refactor-better-encapsulation 2023-05-02 19:05:42 +10:00
Jesse Duffield 8d68ab41b6 Merge branch 'refactor-better-encapsulation' into test-refactor 2023-04-30 14:02:16 +10:00
Jesse Duffield af97bf484c Refresh staging panel when committing
We now refresh the staging panel when doing an unscoped refresh, so that if we commit from the staging panel we escape
back to the files panel if need be. But that causes flickering when doing an unscoped refresh from other contexts,
because the refreshStagingPanel function assumes that the staging panel has focus. So we're adding a guard at the top
of that function to early exit if we don't have focus.
2023-04-30 13:19:54 +10:00
Jesse Duffield a57310df24 Retain commit message when cycling history
When cycling history, we want to make it so that upon returning to the original prompt, you get your text back.
Importantly, we don't want to use the existing preservedMessage field for that because that's only for preserving
a NEW commit message, and we don't want the history stuff of the commit reword flow to overwrite that.
2023-04-30 13:19:54 +10:00
Sean 9d68b287db Split commit message panel into commit summary and commit description panel
When we use the one panel for the entire commit message, its tricky to have a keybinding both for adding a newline and submitting.
By having two panels: one for the summary line and one for the description, we allow for 'enter' to submit the message when done from the summary panel,
and 'enter' to add a newline when done from the description panel. Alt-enter, for those who can use that key combo, also works for submitting the message
from the description panel. For those who can't use that key combo, and don't want to remap the keybinding, they can hit tab to go back to the summary panel
and then 'enter' to submit the message.

We have some awkwardness in that both contexts (i.e. panels) need to appear and disappear in tandem and we don't have a great way of handling that concept,
so we just push both contexts one after the other, and likewise remove both contexts when we escape.
2023-04-30 13:19:53 +10:00
Jesse Duffield a5c72d056d ensure initial context is set when entering submodule 2023-04-30 13:19:53 +10:00
Jesse Duffield 8a86de85c8 remove log call because it clutters test output 2023-04-30 13:19:53 +10:00
Jesse Duffield dd31f8ecea update cheatsheets 2023-04-30 13:19:53 +10:00
Jesse Duffield 5a7b2ab6d0 fix rendering of commit files view 2023-04-30 13:19:53 +10:00
Jesse Duffield 68a9d7fd77 appease linter 2023-04-30 13:19:53 +10:00
Jesse Duffield 19cbafcdfc remove unused file 2023-04-30 13:19:53 +10:00
Jesse Duffield f2c85c5b19 move side window actions to controllers package 2023-04-30 13:19:53 +10:00
Jesse Duffield 0faa41e6f8 move toggle whitespace action to controllers package 2023-04-30 13:19:53 +10:00
Jesse Duffield 2e32e55957 update integration test for toggling whitespace 2023-04-30 13:19:53 +10:00
Jesse Duffield 037cd99138 move quit actions to controller 2023-04-30 13:19:53 +10:00
Jesse Duffield 6388885699 fix reflog text colour by defaulting every view to the same foreground colour 2023-04-30 13:19:53 +10:00
Jesse Duffield d3e9bc2185 remove unused file 2023-04-30 13:19:53 +10:00
Jesse Duffield ea4587a3b8 move some methods 2023-04-30 13:19:53 +10:00
Jesse Duffield 2da300f2fb move diffing menu action to controller 2023-04-30 13:19:53 +10:00
Jesse Duffield 7848958326 move filtering menu action to controller 2023-04-30 13:19:53 +10:00
Jesse Duffield 2cba98e3fe move another action into controller 2023-04-30 13:19:53 +10:00
Jesse Duffield f8c9ce33c2 move more actions into controller 2023-04-30 13:19:53 +10:00
Jesse Duffield 71753770ad move custom patch options menu action to controllers package 2023-04-30 13:19:53 +10:00
Jesse Duffield 820b1e811d move custom command action into its own file 2023-04-30 13:19:53 +10:00
Jesse Duffield 4a33fede7b move window arrangement helper 2023-04-30 13:19:53 +10:00
Jesse Duffield db12853bbe lots of changes 2023-04-30 13:19:53 +10:00
Jesse Duffield 711674f6cd standardise controller helper methods 2023-04-30 13:19:53 +10:00
Jesse Duffield fc91ef6a59 standardise helper args 2023-04-30 13:19:53 +10:00
Jesse Duffield 43251e7275 split context common from helper common 2023-04-30 13:19:53 +10:00
Jesse Duffield f081358943 move getDisplayStrings funcs into contexts 2023-04-30 13:19:53 +10:00
Jesse Duffield 0c6ab4b43e refactor cherry pick code to move state access out of helper 2023-04-30 13:19:53 +10:00
Jesse Duffield 1b2fb34ffd start moving getDisplayStrings funcs into contexts 2023-04-30 13:19:53 +10:00
Jesse Duffield 0e5a4c7a36 move getModel functions into contexts 2023-04-30 13:19:53 +10:00
Jesse Duffield 47b91f1ef5 move views into contexts 2023-04-30 13:19:53 +10:00
Jesse Duffield e2db6a1732 remove context callback opts 2023-04-30 13:19:53 +10:00
Jesse Duffield 509e3efa70 lots more refactoring 2023-04-30 13:19:53 +10:00
Jesse Duffield 8edad826ca Begin refactoring gui
This begins a big refactor of moving more code out of the Gui struct into contexts, controllers, and helpers. We also move some code into structs in the
gui package purely for the sake of better encapsulation
2023-04-30 13:19:52 +10:00
249 changed files with 9195 additions and 7426 deletions
+2 -2
View File
@@ -5,8 +5,8 @@
"tasks": [
{
"label": "Generate cheatsheet",
"type": "process",
"command": "go run scripts/cheatsheet/main.go ",
"type": "shell",
"command": "go run scripts/cheatsheet/main.go generate",
"problemMatcher": [],
},
{
+1 -1
View File
File diff suppressed because one or more lines are too long
+12 -6
View File
@@ -65,6 +65,8 @@ gui:
splitDiff: 'auto' # one of 'auto' | 'always'
skipRewordInEditorWarning: false # for skipping the confirmation before launching the reword editor
border: 'single' # one of 'single' | 'double' | 'rounded' | 'hidden'
# For marking recent commits which changed the selected file
experimentalMarkCommitsWhichChangedFile: false
git:
paging:
colorArg: always
@@ -88,6 +90,9 @@ git:
# displays the whole git graph by default in the commits panel (equivalent to passing the `--all` argument to `git log`)
showWholeGraph: false
skipHookPrefix: WIP
# The main branches. We colour commits green if they belong to one of these branches,
# so that you can easily see which commits are unique to your branch (coloured in yellow)
mainBranches: [master, main]
autoFetch: true
autoRefresh: true
branchLogCmd: 'git log --graph --color=always --abbrev-commit --decorate --date=relative --pretty=medium {{branchName}} --'
@@ -252,21 +257,21 @@ keybinding:
```yaml
os:
openCommand: 'start "" {{filename}}'
open: 'start "" {{filename}}'
```
### Linux
```yaml
os:
openCommand: 'xdg-open {{filename}} >/dev/null'
open: 'xdg-open {{filename}} >/dev/null'
```
### OSX
```yaml
os:
openCommand: 'open {{filename}}'
open: 'open {{filename}}'
```
### Configuring File Editing
@@ -285,9 +290,9 @@ os:
editPreset: 'vscode'
```
Supported presets are `vim`, `emacs`, `nano`, `vscode`, `sublime`, `bbedit`, and
`xcode`. In many cases lazygit will be able to guess the right preset from your
$(git config core.editor), or an environment variable such as $VISUAL or $EDITOR.
Supported presets are `vim`, `nvim`, `emacs`, `nano`, `vscode`, `sublime`, `bbedit`,
`kakoune` and `xcode`. In many cases lazygit will be able to guess the right preset
from your $(git config core.editor), or an environment variable such as $VISUAL or $EDITOR.
If for some reason you are not happy with the default commands from a preset, or
there simply is no preset for your editor, you can customize the commands by
@@ -344,6 +349,7 @@ The available attributes are:
- default
- reverse # useful for high-contrast
- underline
- strikethrough
## Highlighting the selected line
+96 -73
View File
@@ -2,28 +2,30 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
# Lazygit Keybindings
_Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
## Global Keybindings
<pre>
<kbd>ctrl+r</kbd>: switch to a recent repo
<kbd>pgup</kbd>: scroll up main panel (fn+up/shift+k)
<kbd>pgdown</kbd>: scroll down main panel (fn+down/shift+j)
<kbd>m</kbd>: view merge/rebase options
<kbd>ctrl+p</kbd>: view custom patch options
<kbd>R</kbd>: refresh
<kbd>?</kbd>: open menu
<kbd>+</kbd>: next screen mode (normal/half/fullscreen)
<kbd>_</kbd>: prev screen mode
<kbd>ctrl+s</kbd>: view filter-by-path options
<kbd>W</kbd>: open diff menu
<kbd>ctrl+e</kbd>: open diff menu
<kbd>&lt;c-r&gt;</kbd>: switch to a recent repo
<kbd>&lt;pgup&gt;</kbd>: scroll up main panel (fn+up/shift+k)
<kbd>&lt;pgdown&gt;</kbd>: scroll down main panel (fn+down/shift+j)
<kbd>@</kbd>: open command log menu
<kbd>ctrl+w</kbd>: Toggle whether or not whitespace changes are shown in the diff view
<kbd>}</kbd>: Increase the size of the context shown around changes in the diff view
<kbd>{</kbd>: Decrease the size of the context shown around changes in the diff view
<kbd>:</kbd>: execute custom command
<kbd>&lt;c-p&gt;</kbd>: view custom patch options
<kbd>m</kbd>: view merge/rebase options
<kbd>R</kbd>: refresh
<kbd>+</kbd>: next screen mode (normal/half/fullscreen)
<kbd>_</kbd>: prev screen mode
<kbd>?</kbd>: open menu
<kbd>&lt;c-s&gt;</kbd>: view filter-by-path options
<kbd>W</kbd>: open diff menu
<kbd>&lt;c-e&gt;</kbd>: open diff menu
<kbd>&lt;c-w&gt;</kbd>: Toggle whether or not whitespace changes are shown in the diff view
<kbd>z</kbd>: undo (via reflog) (experimental)
<kbd>ctrl+z</kbd>: redo (via reflog) (experimental)
<kbd>&lt;c-z&gt;</kbd>: redo (via reflog) (experimental)
<kbd>P</kbd>: push
<kbd>p</kbd>: pull
</pre>
@@ -33,9 +35,9 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
<pre>
<kbd>,</kbd>: previous page
<kbd>.</kbd>: next page
<kbd><</kbd>: scroll to top
<kbd>&lt;</kbd>: scroll to top
<kbd>/</kbd>: start search
<kbd>></kbd>: scroll to bottom
<kbd>&gt;</kbd>: scroll to bottom
<kbd>H</kbd>: scroll left
<kbd>L</kbd>: scroll right
<kbd>]</kbd>: next tab
@@ -45,22 +47,29 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
## Commit Files
<pre>
<kbd>ctrl+o</kbd>: copy the committed file name to the clipboard
<kbd>&lt;c-o&gt;</kbd>: copy the committed file name to the clipboard
<kbd>c</kbd>: checkout file
<kbd>d</kbd>: discard this commit's changes to this file
<kbd>o</kbd>: open file
<kbd>e</kbd>: edit file
<kbd>space</kbd>: toggle file included in patch
<kbd>&lt;space&gt;</kbd>: toggle file included in patch
<kbd>a</kbd>: toggle all files included in patch
<kbd>enter</kbd>: enter file to add selected lines to the patch (or toggle directory collapsed)
<kbd>&lt;enter&gt;</kbd>: enter file to add selected lines to the patch (or toggle directory collapsed)
<kbd>`</kbd>: toggle file tree view
</pre>
## Commit Summary
<pre>
<kbd>&lt;enter&gt;</kbd>: confirm
<kbd>&lt;esc&gt;</kbd>: close
</pre>
## Commits
<pre>
<kbd>ctrl+o</kbd>: copy commit SHA to clipboard
<kbd>ctrl+r</kbd>: reset cherry-picked (copied) commits selection
<kbd>&lt;c-o&gt;</kbd>: copy commit SHA to clipboard
<kbd>&lt;c-r&gt;</kbd>: reset cherry-picked (copied) commits selection
<kbd>b</kbd>: view bisect options
<kbd>s</kbd>: squash down
<kbd>f</kbd>: fixup commit
@@ -71,31 +80,38 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
<kbd>p</kbd>: pick commit (when mid-rebase)
<kbd>F</kbd>: create fixup commit for this commit
<kbd>S</kbd>: squash all 'fixup!' commits above selected commit (autosquash)
<kbd>ctrl+j</kbd>: move commit down one
<kbd>ctrl+k</kbd>: move commit up one
<kbd>&lt;c-j&gt;</kbd>: move commit down one
<kbd>&lt;c-k&gt;</kbd>: move commit up one
<kbd>v</kbd>: paste commits (cherry-pick)
<kbd>A</kbd>: amend commit with staged changes
<kbd>a</kbd>: reset commit author
<kbd>t</kbd>: revert commit
<kbd>T</kbd>: tag commit
<kbd>ctrl+l</kbd>: open log menu
<kbd>space</kbd>: checkout commit
<kbd>&lt;c-l&gt;</kbd>: open log menu
<kbd>&lt;space&gt;</kbd>: checkout commit
<kbd>y</kbd>: copy commit attribute
<kbd>o</kbd>: open commit in browser
<kbd>n</kbd>: create new branch off of commit
<kbd>g</kbd>: view reset options
<kbd>c</kbd>: copy commit (cherry-pick)
<kbd>C</kbd>: copy commit range (cherry-pick)
<kbd>enter</kbd>: view selected item's files
<kbd>&lt;enter&gt;</kbd>: view selected item's files
</pre>
## Confirmation Panel
<pre>
<kbd>&lt;enter&gt;</kbd>: confirm
<kbd>&lt;esc&gt;</kbd>: close/cancel
</pre>
## Files
<pre>
<kbd>ctrl+o</kbd>: copy the file name to the clipboard
<kbd>&lt;c-o&gt;</kbd>: copy the file name to the clipboard
<kbd>d</kbd>: view 'discard changes' options
<kbd>space</kbd>: toggle staged
<kbd>ctrl+b</kbd>: Filter files (staged/unstaged)
<kbd>&lt;space&gt;</kbd>: toggle staged
<kbd>&lt;c-b&gt;</kbd>: Filter files (staged/unstaged)
<kbd>c</kbd>: commit changes
<kbd>w</kbd>: commit changes without pre-commit hook
<kbd>A</kbd>: amend last commit
@@ -107,7 +123,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
<kbd>s</kbd>: stash all changes
<kbd>S</kbd>: view stash options
<kbd>a</kbd>: stage/unstage all
<kbd>enter</kbd>: stage individual hunks/lines for file, or collapse/expand for directory
<kbd>&lt;enter&gt;</kbd>: stage individual hunks/lines for file, or collapse/expand for directory
<kbd>g</kbd>: view upstream reset options
<kbd>D</kbd>: view reset options
<kbd>`</kbd>: toggle file tree view
@@ -118,13 +134,13 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
## Local Branches
<pre>
<kbd>ctrl+o</kbd>: copy branch name to clipboard
<kbd>&lt;c-o&gt;</kbd>: copy branch name to clipboard
<kbd>i</kbd>: show git-flow options
<kbd>space</kbd>: checkout
<kbd>&lt;space&gt;</kbd>: checkout
<kbd>n</kbd>: new branch
<kbd>o</kbd>: create pull request
<kbd>O</kbd>: create pull request options
<kbd>ctrl+y</kbd>: copy pull request URL to clipboard
<kbd>&lt;c-y&gt;</kbd>: copy pull request URL to clipboard
<kbd>c</kbd>: checkout by name
<kbd>F</kbd>: force checkout
<kbd>d</kbd>: delete branch
@@ -135,7 +151,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
<kbd>g</kbd>: view reset options
<kbd>R</kbd>: rename branch
<kbd>u</kbd>: set/unset upstream
<kbd>enter</kbd>: view commits
<kbd>&lt;enter&gt;</kbd>: view commits
</pre>
## Main Panel (Merging)
@@ -143,53 +159,53 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
<pre>
<kbd>e</kbd>: edit file
<kbd>o</kbd>: open file
<kbd>◀</kbd>: select previous conflict
<kbd>▶</kbd>: select next conflict
<kbd>▲</kbd>: select previous hunk
<kbd>▼</kbd>: select next hunk
<kbd>&lt;left&gt;</kbd>: select previous conflict
<kbd>&lt;right&gt;</kbd>: select next conflict
<kbd>&lt;up&gt;</kbd>: select previous hunk
<kbd>&lt;down&gt;</kbd>: select next hunk
<kbd>z</kbd>: undo
<kbd>M</kbd>: open external merge tool (git mergetool)
<kbd>space</kbd>: pick hunk
<kbd>&lt;space&gt;</kbd>: pick hunk
<kbd>b</kbd>: pick all hunks
<kbd>esc</kbd>: return to files panel
<kbd>&lt;esc&gt;</kbd>: return to files panel
</pre>
## Main Panel (Normal)
<pre>
<kbd>mouse wheel ▼</kbd>: scroll down (fn+up)
<kbd>mouse wheel ▲</kbd>: scroll up (fn+down)
<kbd>mouse wheel down</kbd>: scroll down (fn+up)
<kbd>mouse wheel up</kbd>: scroll up (fn+down)
</pre>
## Main Panel (Patch Building)
<pre>
<kbd>◀</kbd>: select previous hunk
<kbd>▶</kbd>: select next hunk
<kbd>&lt;left&gt;</kbd>: select previous hunk
<kbd>&lt;right&gt;</kbd>: select next hunk
<kbd>v</kbd>: toggle drag select
<kbd>V</kbd>: toggle drag select
<kbd>a</kbd>: toggle select hunk
<kbd>ctrl+o</kbd>: copy the selected text to the clipboard
<kbd>&lt;c-o&gt;</kbd>: copy the selected text to the clipboard
<kbd>o</kbd>: open file
<kbd>e</kbd>: edit file
<kbd>space</kbd>: add/remove line(s) to patch
<kbd>esc</kbd>: exit custom patch builder
<kbd>&lt;space&gt;</kbd>: add/remove line(s) to patch
<kbd>&lt;esc&gt;</kbd>: exit custom patch builder
</pre>
## Main Panel (Staging)
<pre>
<kbd>◀</kbd>: select previous hunk
<kbd>▶</kbd>: select next hunk
<kbd>&lt;left&gt;</kbd>: select previous hunk
<kbd>&lt;right&gt;</kbd>: select next hunk
<kbd>v</kbd>: toggle drag select
<kbd>V</kbd>: toggle drag select
<kbd>a</kbd>: toggle select hunk
<kbd>ctrl+o</kbd>: copy the selected text to the clipboard
<kbd>&lt;c-o&gt;</kbd>: copy the selected text to the clipboard
<kbd>o</kbd>: open file
<kbd>e</kbd>: edit file
<kbd>esc</kbd>: return to files panel
<kbd>tab</kbd>: switch to other panel (staged/unstaged changes)
<kbd>space</kbd>: toggle line staged / unstaged
<kbd>&lt;esc&gt;</kbd>: return to files panel
<kbd>&lt;tab&gt;</kbd>: switch to other panel (staged/unstaged changes)
<kbd>&lt;space&gt;</kbd>: toggle line staged / unstaged
<kbd>d</kbd>: delete change (git reset)
<kbd>E</kbd>: edit hunk
<kbd>c</kbd>: commit changes
@@ -197,34 +213,41 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
<kbd>C</kbd>: commit changes using git editor
</pre>
## Menu
<pre>
<kbd>&lt;enter&gt;</kbd>: execute
<kbd>&lt;esc&gt;</kbd>: close
</pre>
## Reflog
<pre>
<kbd>ctrl+o</kbd>: copy commit SHA to clipboard
<kbd>space</kbd>: checkout commit
<kbd>&lt;c-o&gt;</kbd>: copy commit SHA to clipboard
<kbd>&lt;space&gt;</kbd>: checkout commit
<kbd>y</kbd>: copy commit attribute
<kbd>o</kbd>: open commit in browser
<kbd>n</kbd>: create new branch off of commit
<kbd>g</kbd>: view reset options
<kbd>c</kbd>: copy commit (cherry-pick)
<kbd>C</kbd>: copy commit range (cherry-pick)
<kbd>ctrl+r</kbd>: reset cherry-picked (copied) commits selection
<kbd>enter</kbd>: view commits
<kbd>&lt;c-r&gt;</kbd>: reset cherry-picked (copied) commits selection
<kbd>&lt;enter&gt;</kbd>: view commits
</pre>
## Remote Branches
<pre>
<kbd>ctrl+o</kbd>: copy branch name to clipboard
<kbd>space</kbd>: checkout
<kbd>&lt;c-o&gt;</kbd>: copy branch name to clipboard
<kbd>&lt;space&gt;</kbd>: checkout
<kbd>n</kbd>: new branch
<kbd>M</kbd>: merge into currently checked out branch
<kbd>r</kbd>: rebase checked-out branch onto this branch
<kbd>d</kbd>: delete branch
<kbd>u</kbd>: set as upstream of checked-out branch
<kbd>esc</kbd>: Return to remotes list
<kbd>&lt;esc&gt;</kbd>: Return to remotes list
<kbd>g</kbd>: view reset options
<kbd>enter</kbd>: view commits
<kbd>&lt;enter&gt;</kbd>: view commits
</pre>
## Remotes
@@ -239,44 +262,44 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
## Stash
<pre>
<kbd>space</kbd>: apply
<kbd>&lt;space&gt;</kbd>: apply
<kbd>g</kbd>: pop
<kbd>d</kbd>: drop
<kbd>n</kbd>: new branch
<kbd>r</kbd>: rename stash
<kbd>enter</kbd>: view selected item's files
<kbd>&lt;enter&gt;</kbd>: view selected item's files
</pre>
## Status
<pre>
<kbd>e</kbd>: edit config file
<kbd>o</kbd>: open config file
<kbd>e</kbd>: edit config file
<kbd>u</kbd>: check for update
<kbd>enter</kbd>: switch to a recent repo
<kbd>&lt;enter&gt;</kbd>: switch to a recent repo
<kbd>a</kbd>: show all branch logs
</pre>
## Sub-commits
<pre>
<kbd>ctrl+o</kbd>: copy commit SHA to clipboard
<kbd>space</kbd>: checkout commit
<kbd>&lt;c-o&gt;</kbd>: copy commit SHA to clipboard
<kbd>&lt;space&gt;</kbd>: checkout commit
<kbd>y</kbd>: copy commit attribute
<kbd>o</kbd>: open commit in browser
<kbd>n</kbd>: create new branch off of commit
<kbd>g</kbd>: view reset options
<kbd>c</kbd>: copy commit (cherry-pick)
<kbd>C</kbd>: copy commit range (cherry-pick)
<kbd>ctrl+r</kbd>: reset cherry-picked (copied) commits selection
<kbd>enter</kbd>: view selected item's files
<kbd>&lt;c-r&gt;</kbd>: reset cherry-picked (copied) commits selection
<kbd>&lt;enter&gt;</kbd>: view selected item's files
</pre>
## Submodules
<pre>
<kbd>ctrl+o</kbd>: copy submodule name to clipboard
<kbd>enter</kbd>: enter submodule
<kbd>&lt;c-o&gt;</kbd>: copy submodule name to clipboard
<kbd>&lt;enter&gt;</kbd>: enter submodule
<kbd>d</kbd>: remove submodule
<kbd>u</kbd>: update submodule
<kbd>n</kbd>: add new submodule
@@ -288,10 +311,10 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
## Tags
<pre>
<kbd>space</kbd>: checkout
<kbd>&lt;space&gt;</kbd>: checkout
<kbd>d</kbd>: delete tag
<kbd>P</kbd>: push tag
<kbd>n</kbd>: create tag
<kbd>g</kbd>: view reset options
<kbd>enter</kbd>: view commits
<kbd>&lt;enter&gt;</kbd>: view commits
</pre>
+96 -73
View File
@@ -2,28 +2,30 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
# Lazygit キーバインド
_Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
## グローバルキーバインド
<pre>
<kbd>ctrl+r</kbd>: 最近使用したリポジトリに切り替え
<kbd>pgup</kbd>: メインパネルを上にスクロール (fn+up/shift+k)
<kbd>pgdown</kbd>: メインパネルを下にスクロール (fn+down/shift+j)
<kbd>m</kbd>: view merge/rebase options
<kbd>ctrl+p</kbd>: view custom patch options
<kbd>R</kbd>: リフレッシュ
<kbd>?</kbd>: メニューを開く
<kbd>+</kbd>: 次のスクリーンモード (normal/half/fullscreen)
<kbd>_</kbd>: 前のスクリーンモード
<kbd>ctrl+s</kbd>: view filter-by-path options
<kbd>W</kbd>: 差分メニューを開く
<kbd>ctrl+e</kbd>: 差分メニューを開く
<kbd>&lt;c-r&gt;</kbd>: 最近使用したリポジトリに切り替え
<kbd>&lt;pgup&gt;</kbd>: メインパネルを上にスクロール (fn+up/shift+k)
<kbd>&lt;pgdown&gt;</kbd>: メインパネルを下にスクロール (fn+down/shift+j)
<kbd>@</kbd>: コマンドログメニューを開く
<kbd>ctrl+w</kbd>: 空白文字の差分の表示有無を切り替え
<kbd>}</kbd>: Increase the size of the context shown around changes in the diff view
<kbd>{</kbd>: Decrease the size of the context shown around changes in the diff view
<kbd>:</kbd>: カスタムコマンドを実行
<kbd>&lt;c-p&gt;</kbd>: view custom patch options
<kbd>m</kbd>: view merge/rebase options
<kbd>R</kbd>: リフレッシュ
<kbd>+</kbd>: 次のスクリーンモード (normal/half/fullscreen)
<kbd>_</kbd>: 前のスクリーンモード
<kbd>?</kbd>: メニューを開く
<kbd>&lt;c-s&gt;</kbd>: view filter-by-path options
<kbd>W</kbd>: 差分メニューを開く
<kbd>&lt;c-e&gt;</kbd>: 差分メニューを開く
<kbd>&lt;c-w&gt;</kbd>: 空白文字の差分の表示有無を切り替え
<kbd>z</kbd>: アンドゥ (via reflog) (experimental)
<kbd>ctrl+z</kbd>: リドゥ (via reflog) (experimental)
<kbd>&lt;c-z&gt;</kbd>: リドゥ (via reflog) (experimental)
<kbd>P</kbd>: push
<kbd>p</kbd>: pull
</pre>
@@ -33,9 +35,9 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
<pre>
<kbd>,</kbd>: 前のページ
<kbd>.</kbd>: 次のページ
<kbd><</kbd>: 最上部までスクロール
<kbd>&lt;</kbd>: 最上部までスクロール
<kbd>/</kbd>: 検索を開始
<kbd>></kbd>: 最下部までスクロール
<kbd>&gt;</kbd>: 最下部までスクロール
<kbd>H</kbd>: 左スクロール
<kbd>L</kbd>: 右スクロール
<kbd>]</kbd>: 次のタブ
@@ -45,34 +47,34 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
## Stash
<pre>
<kbd>space</kbd>: 適用
<kbd>&lt;space&gt;</kbd>: 適用
<kbd>g</kbd>: pop
<kbd>d</kbd>: drop
<kbd>n</kbd>: 新しいブランチを作成
<kbd>r</kbd>: Stashを変更
<kbd>enter</kbd>: view selected item's files
<kbd>&lt;enter&gt;</kbd>: view selected item's files
</pre>
## Sub-commits
<pre>
<kbd>ctrl+o</kbd>: コミットのSHAをクリップボードにコピー
<kbd>space</kbd>: コミットをチェックアウト
<kbd>&lt;c-o&gt;</kbd>: コミットのSHAをクリップボードにコピー
<kbd>&lt;space&gt;</kbd>: コミットをチェックアウト
<kbd>y</kbd>: コミットの情報をコピー
<kbd>o</kbd>: ブラウザでコミットを開く
<kbd>n</kbd>: コミットにブランチを作成
<kbd>g</kbd>: view reset options
<kbd>c</kbd>: コミットをコピー (cherry-pick)
<kbd>C</kbd>: コミットを範囲コピー (cherry-pick)
<kbd>ctrl+r</kbd>: reset cherry-picked (copied) commits selection
<kbd>enter</kbd>: view selected item's files
<kbd>&lt;c-r&gt;</kbd>: reset cherry-picked (copied) commits selection
<kbd>&lt;enter&gt;</kbd>: view selected item's files
</pre>
## コミット
<pre>
<kbd>ctrl+o</kbd>: コミットのSHAをクリップボードにコピー
<kbd>ctrl+r</kbd>: reset cherry-picked (copied) commits selection
<kbd>&lt;c-o&gt;</kbd>: コミットのSHAをクリップボードにコピー
<kbd>&lt;c-r&gt;</kbd>: reset cherry-picked (copied) commits selection
<kbd>b</kbd>: view bisect options
<kbd>s</kbd>: squash down
<kbd>f</kbd>: fixup commit
@@ -83,43 +85,50 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
<kbd>p</kbd>: pick commit (when mid-rebase)
<kbd>F</kbd>: このコミットに対するfixupコミットを作成
<kbd>S</kbd>: squash all 'fixup!' commits above selected commit (autosquash)
<kbd>ctrl+j</kbd>: コミットを1つ下に移動
<kbd>ctrl+k</kbd>: コミットを1つ上に移動
<kbd>&lt;c-j&gt;</kbd>: コミットを1つ下に移動
<kbd>&lt;c-k&gt;</kbd>: コミットを1つ上に移動
<kbd>v</kbd>: コミットを貼り付け (cherry-pick)
<kbd>A</kbd>: ステージされた変更でamendコミット
<kbd>a</kbd>: reset commit author
<kbd>t</kbd>: コミットをrevert
<kbd>T</kbd>: タグを作成
<kbd>ctrl+l</kbd>: ログメニューを開く
<kbd>space</kbd>: コミットをチェックアウト
<kbd>&lt;c-l&gt;</kbd>: ログメニューを開く
<kbd>&lt;space&gt;</kbd>: コミットをチェックアウト
<kbd>y</kbd>: コミットの情報をコピー
<kbd>o</kbd>: ブラウザでコミットを開く
<kbd>n</kbd>: コミットにブランチを作成
<kbd>g</kbd>: view reset options
<kbd>c</kbd>: コミットをコピー (cherry-pick)
<kbd>C</kbd>: コミットを範囲コピー (cherry-pick)
<kbd>enter</kbd>: view selected item's files
<kbd>&lt;enter&gt;</kbd>: view selected item's files
</pre>
## コミットファイル
<pre>
<kbd>ctrl+o</kbd>: コミットされたファイル名をクリップボードにコピー
<kbd>&lt;c-o&gt;</kbd>: コミットされたファイル名をクリップボードにコピー
<kbd>c</kbd>: checkout file
<kbd>d</kbd>: discard this commit's changes to this file
<kbd>o</kbd>: ファイルを開く
<kbd>e</kbd>: ファイルを編集
<kbd>space</kbd>: toggle file included in patch
<kbd>&lt;space&gt;</kbd>: toggle file included in patch
<kbd>a</kbd>: toggle all files included in patch
<kbd>enter</kbd>: enter file to add selected lines to the patch (or toggle directory collapsed)
<kbd>&lt;enter&gt;</kbd>: enter file to add selected lines to the patch (or toggle directory collapsed)
<kbd>`</kbd>: ファイルツリーの表示を切り替え
</pre>
## コミットメッセージ
<pre>
<kbd>&lt;enter&gt;</kbd>: 確認
<kbd>&lt;esc&gt;</kbd>: 閉じる
</pre>
## サブモジュール
<pre>
<kbd>ctrl+o</kbd>: サブモジュール名をクリップボードにコピー
<kbd>enter</kbd>: サブモジュールを開く
<kbd>&lt;c-o&gt;</kbd>: サブモジュール名をクリップボードにコピー
<kbd>&lt;enter&gt;</kbd>: サブモジュールを開く
<kbd>d</kbd>: サブモジュールを削除
<kbd>u</kbd>: サブモジュールを更新
<kbd>n</kbd>: サブモジュールを新規追加
@@ -131,31 +140,31 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
## ステータス
<pre>
<kbd>e</kbd>: 設定ファイルを編集
<kbd>o</kbd>: 設定ファイルを開く
<kbd>e</kbd>: 設定ファイルを編集
<kbd>u</kbd>: 更新を確認
<kbd>enter</kbd>: 最近使用したリポジトリに切り替え
<kbd>&lt;enter&gt;</kbd>: 最近使用したリポジトリに切り替え
<kbd>a</kbd>: すべてのブランチログを表示
</pre>
## タグ
<pre>
<kbd>space</kbd>: チェックアウト
<kbd>&lt;space&gt;</kbd>: チェックアウト
<kbd>d</kbd>: タグを削除
<kbd>P</kbd>: タグをpush
<kbd>n</kbd>: タグを作成
<kbd>g</kbd>: view reset options
<kbd>enter</kbd>: コミットを閲覧
<kbd>&lt;enter&gt;</kbd>: コミットを閲覧
</pre>
## ファイル
<pre>
<kbd>ctrl+o</kbd>: ファイル名をクリップボードにコピー
<kbd>&lt;c-o&gt;</kbd>: ファイル名をクリップボードにコピー
<kbd>d</kbd>: view 'discard changes' options
<kbd>space</kbd>: ステージ/アンステージ
<kbd>ctrl+b</kbd>: ファイルをフィルタ (ステージ/アンステージ)
<kbd>&lt;space&gt;</kbd>: ステージ/アンステージ
<kbd>&lt;c-b&gt;</kbd>: ファイルをフィルタ (ステージ/アンステージ)
<kbd>c</kbd>: 変更をコミット
<kbd>w</kbd>: pre-commitフックを実行せずに変更をコミット
<kbd>A</kbd>: 最新のコミットにamend
@@ -167,7 +176,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
<kbd>s</kbd>: 変更をstash
<kbd>S</kbd>: view stash options
<kbd>a</kbd>: すべての変更をステージ/アンステージ
<kbd>enter</kbd>: stage individual hunks/lines for file, or collapse/expand for directory
<kbd>&lt;enter&gt;</kbd>: stage individual hunks/lines for file, or collapse/expand for directory
<kbd>g</kbd>: view upstream reset options
<kbd>D</kbd>: view reset options
<kbd>`</kbd>: ファイルツリーの表示を切り替え
@@ -178,13 +187,13 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
## ブランチ
<pre>
<kbd>ctrl+o</kbd>: ブランチ名をクリップボードにコピー
<kbd>&lt;c-o&gt;</kbd>: ブランチ名をクリップボードにコピー
<kbd>i</kbd>: show git-flow options
<kbd>space</kbd>: チェックアウト
<kbd>&lt;space&gt;</kbd>: チェックアウト
<kbd>n</kbd>: 新しいブランチを作成
<kbd>o</kbd>: Pull Requestを作成
<kbd>O</kbd>: create pull request options
<kbd>ctrl+y</kbd>: Pull RequestのURLをクリップボードにコピー
<kbd>&lt;c-y&gt;</kbd>: Pull RequestのURLをクリップボードにコピー
<kbd>c</kbd>: checkout by name
<kbd>F</kbd>: force checkout
<kbd>d</kbd>: ブランチを削除
@@ -195,7 +204,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
<kbd>g</kbd>: view reset options
<kbd>R</kbd>: ブランチ名を変更
<kbd>u</kbd>: set/unset upstream
<kbd>enter</kbd>: コミットを閲覧
<kbd>&lt;enter&gt;</kbd>: コミットを閲覧
</pre>
## メインパネル (Merging)
@@ -203,53 +212,53 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
<pre>
<kbd>e</kbd>: ファイルを編集
<kbd>o</kbd>: ファイルを開く
<kbd>◀</kbd>: 前のコンフリクトを選択
<kbd>▶</kbd>: 次のコンフリクトを選択
<kbd>▲</kbd>: 前のhunkを選択
<kbd>▼</kbd>: 次のhunkを選択
<kbd>&lt;left&gt;</kbd>: 前のコンフリクトを選択
<kbd>&lt;right&gt;</kbd>: 次のコンフリクトを選択
<kbd>&lt;up&gt;</kbd>: 前のhunkを選択
<kbd>&lt;down&gt;</kbd>: 次のhunkを選択
<kbd>z</kbd>: アンドゥ
<kbd>M</kbd>: git mergetoolを開く
<kbd>space</kbd>: pick hunk
<kbd>&lt;space&gt;</kbd>: pick hunk
<kbd>b</kbd>: pick all hunks
<kbd>esc</kbd>: ファイル一覧に戻る
<kbd>&lt;esc&gt;</kbd>: ファイル一覧に戻る
</pre>
## メインパネル (Normal)
<pre>
<kbd>mouse wheel ▼</kbd>: 下にスクロール (fn+up)
<kbd>mouse wheel ▲</kbd>: 上にスクロール (fn+down)
<kbd>mouse wheel down</kbd>: 下にスクロール (fn+up)
<kbd>mouse wheel up</kbd>: 上にスクロール (fn+down)
</pre>
## メインパネル (Patch Building)
<pre>
<kbd>◀</kbd>: 前のhunkを選択
<kbd>▶</kbd>: 次のhunkを選択
<kbd>&lt;left&gt;</kbd>: 前のhunkを選択
<kbd>&lt;right&gt;</kbd>: 次のhunkを選択
<kbd>v</kbd>: 範囲選択を切り替え
<kbd>V</kbd>: 範囲選択を切り替え
<kbd>a</kbd>: hunk選択を切り替え
<kbd>ctrl+o</kbd>: 選択されたテキストをクリップボードにコピー
<kbd>&lt;c-o&gt;</kbd>: 選択されたテキストをクリップボードにコピー
<kbd>o</kbd>: ファイルを開く
<kbd>e</kbd>: ファイルを編集
<kbd>space</kbd>: 行をパッチに追加/削除
<kbd>esc</kbd>: exit custom patch builder
<kbd>&lt;space&gt;</kbd>: 行をパッチに追加/削除
<kbd>&lt;esc&gt;</kbd>: exit custom patch builder
</pre>
## メインパネル (Staging)
<pre>
<kbd>◀</kbd>: 前のhunkを選択
<kbd>▶</kbd>: 次のhunkを選択
<kbd>&lt;left&gt;</kbd>: 前のhunkを選択
<kbd>&lt;right&gt;</kbd>: 次のhunkを選択
<kbd>v</kbd>: 範囲選択を切り替え
<kbd>V</kbd>: 範囲選択を切り替え
<kbd>a</kbd>: hunk選択を切り替え
<kbd>ctrl+o</kbd>: 選択されたテキストをクリップボードにコピー
<kbd>&lt;c-o&gt;</kbd>: 選択されたテキストをクリップボードにコピー
<kbd>o</kbd>: ファイルを開く
<kbd>e</kbd>: ファイルを編集
<kbd>esc</kbd>: ファイル一覧に戻る
<kbd>tab</kbd>: パネルを切り替え
<kbd>space</kbd>: 選択行をステージ/アンステージ
<kbd>&lt;esc&gt;</kbd>: ファイル一覧に戻る
<kbd>&lt;tab&gt;</kbd>: パネルを切り替え
<kbd>&lt;space&gt;</kbd>: 選択行をステージ/アンステージ
<kbd>d</kbd>: 変更を削除 (git reset)
<kbd>E</kbd>: edit hunk
<kbd>c</kbd>: 変更をコミット
@@ -257,6 +266,13 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
<kbd>C</kbd>: gitエディタを使用して変更をコミット
</pre>
## メニュー
<pre>
<kbd>&lt;enter&gt;</kbd>: 実行
<kbd>&lt;esc&gt;</kbd>: 閉じる
</pre>
## リモート
<pre>
@@ -269,29 +285,36 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
## リモートブランチ
<pre>
<kbd>ctrl+o</kbd>: ブランチ名をクリップボードにコピー
<kbd>space</kbd>: チェックアウト
<kbd>&lt;c-o&gt;</kbd>: ブランチ名をクリップボードにコピー
<kbd>&lt;space&gt;</kbd>: チェックアウト
<kbd>n</kbd>: 新しいブランチを作成
<kbd>M</kbd>: 現在のブランチにマージ
<kbd>r</kbd>: rebase checked-out branch onto this branch
<kbd>d</kbd>: ブランチを削除
<kbd>u</kbd>: set as upstream of checked-out branch
<kbd>esc</kbd>: リモート一覧に戻る
<kbd>&lt;esc&gt;</kbd>: リモート一覧に戻る
<kbd>g</kbd>: view reset options
<kbd>enter</kbd>: コミットを閲覧
<kbd>&lt;enter&gt;</kbd>: コミットを閲覧
</pre>
## 参照ログ
<pre>
<kbd>ctrl+o</kbd>: コミットのSHAをクリップボードにコピー
<kbd>space</kbd>: コミットをチェックアウト
<kbd>&lt;c-o&gt;</kbd>: コミットのSHAをクリップボードにコピー
<kbd>&lt;space&gt;</kbd>: コミットをチェックアウト
<kbd>y</kbd>: コミットの情報をコピー
<kbd>o</kbd>: ブラウザでコミットを開く
<kbd>n</kbd>: コミットにブランチを作成
<kbd>g</kbd>: view reset options
<kbd>c</kbd>: コミットをコピー (cherry-pick)
<kbd>C</kbd>: コミットを範囲コピー (cherry-pick)
<kbd>ctrl+r</kbd>: reset cherry-picked (copied) commits selection
<kbd>enter</kbd>: コミットを閲覧
<kbd>&lt;c-r&gt;</kbd>: reset cherry-picked (copied) commits selection
<kbd>&lt;enter&gt;</kbd>: コミットを閲覧
</pre>
## 確認パネル
<pre>
<kbd>&lt;enter&gt;</kbd>: 確認
<kbd>&lt;esc&gt;</kbd>: 閉じる/キャンセル
</pre>
+96 -73
View File
@@ -2,28 +2,30 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
# Lazygit 키 바인딩
_Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
## 글로벌 키 바인딩
<pre>
<kbd>ctrl+r</kbd>: 최근에 사용한 저장소로 전환
<kbd>pgup</kbd>: 메인 패널을 위로 스크롤 (fn+up/shift+k)
<kbd>pgdown</kbd>: 메인 패널을 아래로로 스크롤 (fn+down/shift+j)
<kbd>m</kbd>: view merge/rebase options
<kbd>ctrl+p</kbd>: 커스텀 Patch 옵션 보기
<kbd>R</kbd>: 새로고침
<kbd>?</kbd>: 매뉴 열기
<kbd>+</kbd>: 다음 스크린 모드 (normal/half/fullscreen)
<kbd>_</kbd>: 이전 스크린 모드
<kbd>ctrl+s</kbd>: view filter-by-path options
<kbd>W</kbd>: Diff 메뉴 열기
<kbd>ctrl+e</kbd>: Diff 메뉴 열기
<kbd>&lt;c-r&gt;</kbd>: 최근에 사용한 저장소로 전환
<kbd>&lt;pgup&gt;</kbd>: 메인 패널을 위로 스크롤 (fn+up/shift+k)
<kbd>&lt;pgdown&gt;</kbd>: 메인 패널을 아래로로 스크롤 (fn+down/shift+j)
<kbd>@</kbd>: 명령어 로그 메뉴 열기
<kbd>ctrl+w</kbd>: 공백문자를 Diff 뷰에서 표시 여부 전환
<kbd>}</kbd>: diff 보기의 변경 사항 주위에 표시되는 컨텍스트의 크기를 늘리기
<kbd>{</kbd>: diff 보기의 변경 사항 주위에 표시되는 컨텍스트 크기 줄이기
<kbd>:</kbd>: execute custom command
<kbd>&lt;c-p&gt;</kbd>: 커스텀 Patch 옵션 보기
<kbd>m</kbd>: view merge/rebase options
<kbd>R</kbd>: 새로고침
<kbd>+</kbd>: 다음 스크린 모드 (normal/half/fullscreen)
<kbd>_</kbd>: 이전 스크린 모드
<kbd>?</kbd>: 매뉴 열기
<kbd>&lt;c-s&gt;</kbd>: view filter-by-path options
<kbd>W</kbd>: Diff 메뉴 열기
<kbd>&lt;c-e&gt;</kbd>: Diff 메뉴 열기
<kbd>&lt;c-w&gt;</kbd>: 공백문자를 Diff 뷰에서 표시 여부 전환
<kbd>z</kbd>: 되돌리기 (reflog) (실험적)
<kbd>ctrl+z</kbd>: 다시 실행 (reflog) (실험적)
<kbd>&lt;c-z&gt;</kbd>: 다시 실행 (reflog) (실험적)
<kbd>P</kbd>: 푸시
<kbd>p</kbd>: 업데이트
</pre>
@@ -33,9 +35,9 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
<pre>
<kbd>,</kbd>: 이전 페이지
<kbd>.</kbd>: 다음 페이지
<kbd><</kbd>: 맨 위로 스크롤
<kbd>&lt;</kbd>: 맨 위로 스크롤
<kbd>/</kbd>: 검색 시작
<kbd>></kbd>: 맨 아래로 스크롤
<kbd>&gt;</kbd>: 맨 아래로 스크롤
<kbd>H</kbd>: 우 스크롤
<kbd>L</kbd>: 좌 스크롤
<kbd>]</kbd>: 이전 탭
@@ -45,42 +47,49 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
## Reflog
<pre>
<kbd>ctrl+o</kbd>: 커밋 SHA를 클립보드에 복사
<kbd>space</kbd>: 커밋을 체크아웃
<kbd>&lt;c-o&gt;</kbd>: 커밋 SHA를 클립보드에 복사
<kbd>&lt;space&gt;</kbd>: 커밋을 체크아웃
<kbd>y</kbd>: 커밋 attribute 복사
<kbd>o</kbd>: 브라우저에서 커밋 열기
<kbd>n</kbd>: 커밋에서 새 브랜치를 만듭니다.
<kbd>g</kbd>: view reset options
<kbd>c</kbd>: 커밋을 복사 (cherry-pick)
<kbd>C</kbd>: 커밋을 범위로 복사 (cherry-pick)
<kbd>ctrl+r</kbd>: reset cherry-picked (copied) commits selection
<kbd>enter</kbd>: 커밋 보기
<kbd>&lt;c-r&gt;</kbd>: reset cherry-picked (copied) commits selection
<kbd>&lt;enter&gt;</kbd>: 커밋 보기
</pre>
## Stash
<pre>
<kbd>space</kbd>: 적용
<kbd>&lt;space&gt;</kbd>: 적용
<kbd>g</kbd>: pop
<kbd>d</kbd>: drop
<kbd>n</kbd>: 새 브랜치 생성
<kbd>r</kbd>: rename stash
<kbd>enter</kbd>: view selected item's files
<kbd>&lt;enter&gt;</kbd>: view selected item's files
</pre>
## Sub-commits
<pre>
<kbd>ctrl+o</kbd>: 커밋 SHA를 클립보드에 복사
<kbd>space</kbd>: 커밋을 체크아웃
<kbd>&lt;c-o&gt;</kbd>: 커밋 SHA를 클립보드에 복사
<kbd>&lt;space&gt;</kbd>: 커밋을 체크아웃
<kbd>y</kbd>: 커밋 attribute 복사
<kbd>o</kbd>: 브라우저에서 커밋 열기
<kbd>n</kbd>: 커밋에서 새 브랜치를 만듭니다.
<kbd>g</kbd>: view reset options
<kbd>c</kbd>: 커밋을 복사 (cherry-pick)
<kbd>C</kbd>: 커밋을 범위로 복사 (cherry-pick)
<kbd>ctrl+r</kbd>: reset cherry-picked (copied) commits selection
<kbd>enter</kbd>: view selected item's files
<kbd>&lt;c-r&gt;</kbd>: reset cherry-picked (copied) commits selection
<kbd>&lt;enter&gt;</kbd>: view selected item's files
</pre>
## 메뉴
<pre>
<kbd>&lt;enter&gt;</kbd>: 실행
<kbd>&lt;esc&gt;</kbd>: 닫기
</pre>
## 메인 패널 (Merging)
@@ -88,53 +97,53 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
<pre>
<kbd>e</kbd>: 파일 편집
<kbd>o</kbd>: 파일 닫기
<kbd>◀</kbd>: 이전 충돌을 선택
<kbd>▶</kbd>: 다음 충돌을 선택
<kbd>▲</kbd>: 이전 hunk를 선택
<kbd>▼</kbd>: 다음 hunk를 선택
<kbd>&lt;left&gt;</kbd>: 이전 충돌을 선택
<kbd>&lt;right&gt;</kbd>: 다음 충돌을 선택
<kbd>&lt;up&gt;</kbd>: 이전 hunk를 선택
<kbd>&lt;down&gt;</kbd>: 다음 hunk를 선택
<kbd>z</kbd>: 되돌리기
<kbd>M</kbd>: git mergetool를 열기
<kbd>space</kbd>: pick hunk
<kbd>&lt;space&gt;</kbd>: pick hunk
<kbd>b</kbd>: pick all hunks
<kbd>esc</kbd>: 파일 목록으로 돌아가기
<kbd>&lt;esc&gt;</kbd>: 파일 목록으로 돌아가기
</pre>
## 메인 패널 (Normal)
<pre>
<kbd>mouse wheel ▼</kbd>: 아래로 스크롤 (fn+up)
<kbd>mouse wheel ▲</kbd>: 위로 스크롤 (fn+down)
<kbd>mouse wheel down</kbd>: 아래로 스크롤 (fn+up)
<kbd>mouse wheel up</kbd>: 위로 스크롤 (fn+down)
</pre>
## 메인 패널 (Patch Building)
<pre>
<kbd>◀</kbd>: 이전 hunk를 선택
<kbd>▶</kbd>: 다음 hunk를 선택
<kbd>&lt;left&gt;</kbd>: 이전 hunk를 선택
<kbd>&lt;right&gt;</kbd>: 다음 hunk를 선택
<kbd>v</kbd>: 드래그 선택 전환
<kbd>V</kbd>: 드래그 선택 전환
<kbd>a</kbd>: toggle select hunk
<kbd>ctrl+o</kbd>: 선택한 텍스트를 클립보드에 복사
<kbd>&lt;c-o&gt;</kbd>: 선택한 텍스트를 클립보드에 복사
<kbd>o</kbd>: 파일 닫기
<kbd>e</kbd>: 파일 편집
<kbd>space</kbd>: line(s)을 패치에 추가/삭제
<kbd>esc</kbd>: exit custom patch builder
<kbd>&lt;space&gt;</kbd>: line(s)을 패치에 추가/삭제
<kbd>&lt;esc&gt;</kbd>: exit custom patch builder
</pre>
## 메인 패널 (Staging)
<pre>
<kbd>◀</kbd>: 이전 hunk를 선택
<kbd>▶</kbd>: 다음 hunk를 선택
<kbd>&lt;left&gt;</kbd>: 이전 hunk를 선택
<kbd>&lt;right&gt;</kbd>: 다음 hunk를 선택
<kbd>v</kbd>: 드래그 선택 전환
<kbd>V</kbd>: 드래그 선택 전환
<kbd>a</kbd>: toggle select hunk
<kbd>ctrl+o</kbd>: 선택한 텍스트를 클립보드에 복사
<kbd>&lt;c-o&gt;</kbd>: 선택한 텍스트를 클립보드에 복사
<kbd>o</kbd>: 파일 닫기
<kbd>e</kbd>: 파일 편집
<kbd>esc</kbd>: 파일 목록으로 돌아가기
<kbd>tab</kbd>: 패널 전환
<kbd>space</kbd>: 선택한 행을 staged / unstaged
<kbd>&lt;esc&gt;</kbd>: 파일 목록으로 돌아가기
<kbd>&lt;tab&gt;</kbd>: 패널 전환
<kbd>&lt;space&gt;</kbd>: 선택한 행을 staged / unstaged
<kbd>d</kbd>: 변경을 삭제 (git reset)
<kbd>E</kbd>: edit hunk
<kbd>c</kbd>: 커밋 변경내용
@@ -145,13 +154,13 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
## 브랜치
<pre>
<kbd>ctrl+o</kbd>: 브랜치명을 클립보드에 복사
<kbd>&lt;c-o&gt;</kbd>: 브랜치명을 클립보드에 복사
<kbd>i</kbd>: git-flow 옵션 보기
<kbd>space</kbd>: 체크아웃
<kbd>&lt;space&gt;</kbd>: 체크아웃
<kbd>n</kbd>: 새 브랜치 생성
<kbd>o</kbd>: 풀 리퀘스트 생성
<kbd>O</kbd>: 풀 리퀘스트 생성 옵션
<kbd>ctrl+y</kbd>: 풀 리퀘스트 URL을 클립보드에 복사
<kbd>&lt;c-y&gt;</kbd>: 풀 리퀘스트 URL을 클립보드에 복사
<kbd>c</kbd>: 이름으로 체크아웃
<kbd>F</kbd>: 강제 체크아웃
<kbd>d</kbd>: 브랜치 삭제
@@ -162,24 +171,24 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
<kbd>g</kbd>: view reset options
<kbd>R</kbd>: 브랜치 이름 변경
<kbd>u</kbd>: set/unset upstream
<kbd>enter</kbd>: 커밋 보기
<kbd>&lt;enter&gt;</kbd>: 커밋 보기
</pre>
## 상태
<pre>
<kbd>e</kbd>: 설정 파일 수정
<kbd>o</kbd>: 설정 파일 열기
<kbd>e</kbd>: 설정 파일 수정
<kbd>u</kbd>: 업데이트 확인
<kbd>enter</kbd>: 최근에 사용한 저장소로 전환
<kbd>&lt;enter&gt;</kbd>: 최근에 사용한 저장소로 전환
<kbd>a</kbd>: 모든 브랜치 로그 표시
</pre>
## 서브모듈
<pre>
<kbd>ctrl+o</kbd>: 서브모듈 이름을 클립보드에 복사
<kbd>enter</kbd>: 서브모듈 열기
<kbd>&lt;c-o&gt;</kbd>: 서브모듈 이름을 클립보드에 복사
<kbd>&lt;enter&gt;</kbd>: 서브모듈 열기
<kbd>d</kbd>: 서브모듈 삭제
<kbd>u</kbd>: 서브모듈 업데이트
<kbd>n</kbd>: 새로운 서브모듈 추가
@@ -200,23 +209,23 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
## 원격 브랜치
<pre>
<kbd>ctrl+o</kbd>: 브랜치명을 클립보드에 복사
<kbd>space</kbd>: 체크아웃
<kbd>&lt;c-o&gt;</kbd>: 브랜치명을 클립보드에 복사
<kbd>&lt;space&gt;</kbd>: 체크아웃
<kbd>n</kbd>: 새 브랜치 생성
<kbd>M</kbd>: 현재 브랜치에 병합
<kbd>r</kbd>: 체크아웃된 브랜치를 이 브랜치에 리베이스
<kbd>d</kbd>: 브랜치 삭제
<kbd>u</kbd>: set as upstream of checked-out branch
<kbd>esc</kbd>: 원격목록으로 돌아가기
<kbd>&lt;esc&gt;</kbd>: 원격목록으로 돌아가기
<kbd>g</kbd>: view reset options
<kbd>enter</kbd>: 커밋 보기
<kbd>&lt;enter&gt;</kbd>: 커밋 보기
</pre>
## 커밋
<pre>
<kbd>ctrl+o</kbd>: 커밋 SHA를 클립보드에 복사
<kbd>ctrl+r</kbd>: reset cherry-picked (copied) commits selection
<kbd>&lt;c-o&gt;</kbd>: 커밋 SHA를 클립보드에 복사
<kbd>&lt;c-r&gt;</kbd>: reset cherry-picked (copied) commits selection
<kbd>b</kbd>: bisect 옵션 보기
<kbd>s</kbd>: squash down
<kbd>f</kbd>: fixup commit
@@ -227,56 +236,63 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
<kbd>p</kbd>: pick commit (when mid-rebase)
<kbd>F</kbd>: create fixup commit for this commit
<kbd>S</kbd>: squash all 'fixup!' commits above selected commit (autosquash)
<kbd>ctrl+j</kbd>: 커밋을 1개 아래로 이동
<kbd>ctrl+k</kbd>: 커밋을 1개 위로 이동
<kbd>&lt;c-j&gt;</kbd>: 커밋을 1개 아래로 이동
<kbd>&lt;c-k&gt;</kbd>: 커밋을 1개 위로 이동
<kbd>v</kbd>: 커밋을 붙여넣기 (cherry-pick)
<kbd>A</kbd>: amend commit with staged changes
<kbd>a</kbd>: reset commit author
<kbd>t</kbd>: 커밋 되돌리기
<kbd>T</kbd>: tag commit
<kbd>ctrl+l</kbd>: 로그 메뉴 열기
<kbd>space</kbd>: 커밋을 체크아웃
<kbd>&lt;c-l&gt;</kbd>: 로그 메뉴 열기
<kbd>&lt;space&gt;</kbd>: 커밋을 체크아웃
<kbd>y</kbd>: 커밋 attribute 복사
<kbd>o</kbd>: 브라우저에서 커밋 열기
<kbd>n</kbd>: 커밋에서 새 브랜치를 만듭니다.
<kbd>g</kbd>: view reset options
<kbd>c</kbd>: 커밋을 복사 (cherry-pick)
<kbd>C</kbd>: 커밋을 범위로 복사 (cherry-pick)
<kbd>enter</kbd>: view selected item's files
<kbd>&lt;enter&gt;</kbd>: view selected item's files
</pre>
## 커밋 파일
<pre>
<kbd>ctrl+o</kbd>: 커밋한 파일명을 클립보드에 복사
<kbd>&lt;c-o&gt;</kbd>: 커밋한 파일명을 클립보드에 복사
<kbd>c</kbd>: checkout file
<kbd>d</kbd>: discard this commit's changes to this file
<kbd>o</kbd>: 파일 닫기
<kbd>e</kbd>: 파일 편집
<kbd>space</kbd>: toggle file included in patch
<kbd>&lt;space&gt;</kbd>: toggle file included in patch
<kbd>a</kbd>: toggle all files included in patch
<kbd>enter</kbd>: enter file to add selected lines to the patch (or toggle directory collapsed)
<kbd>&lt;enter&gt;</kbd>: enter file to add selected lines to the patch (or toggle directory collapsed)
<kbd>`</kbd>: 파일 트리뷰로 전환
</pre>
## 커밋메시지
<pre>
<kbd>&lt;enter&gt;</kbd>: 확인
<kbd>&lt;esc&gt;</kbd>: 닫기
</pre>
## 태그
<pre>
<kbd>space</kbd>: 체크아웃
<kbd>&lt;space&gt;</kbd>: 체크아웃
<kbd>d</kbd>: 태그 삭제
<kbd>P</kbd>: 태그를 push
<kbd>n</kbd>: 태그를 생성
<kbd>g</kbd>: view reset options
<kbd>enter</kbd>: 커밋 보기
<kbd>&lt;enter&gt;</kbd>: 커밋 보기
</pre>
## 파일
<pre>
<kbd>ctrl+o</kbd>: 파일명을 클립보드에 복사
<kbd>&lt;c-o&gt;</kbd>: 파일명을 클립보드에 복사
<kbd>d</kbd>: view 'discard changes' options
<kbd>space</kbd>: Staged 전환
<kbd>ctrl+b</kbd>: 파일을 필터하기 (Staged/unstaged)
<kbd>&lt;space&gt;</kbd>: Staged 전환
<kbd>&lt;c-b&gt;</kbd>: 파일을 필터하기 (Staged/unstaged)
<kbd>c</kbd>: 커밋 변경내용
<kbd>w</kbd>: commit changes without pre-commit hook
<kbd>A</kbd>: 마지맛 커밋 수정
@@ -288,10 +304,17 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
<kbd>s</kbd>: 변경사항을 Stash
<kbd>S</kbd>: Stash 옵션 보기
<kbd>a</kbd>: 모든 변경을 Staged/unstaged으로 전환
<kbd>enter</kbd>: stage individual hunks/lines for file, or collapse/expand for directory
<kbd>&lt;enter&gt;</kbd>: stage individual hunks/lines for file, or collapse/expand for directory
<kbd>g</kbd>: view upstream reset options
<kbd>D</kbd>: view reset options
<kbd>`</kbd>: 파일 트리뷰로 전환
<kbd>M</kbd>: git mergetool를 열기
<kbd>f</kbd>: fetch
</pre>
## 확인 패널
<pre>
<kbd>&lt;enter&gt;</kbd>: 확인
<kbd>&lt;esc&gt;</kbd>: 닫기/취소
</pre>
+96 -73
View File
@@ -2,28 +2,30 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
# Lazygit Sneltoetsen
_Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
## Globale Sneltoetsen
<pre>
<kbd>ctrl+r</kbd>: wissel naar een recente repo
<kbd>pgup</kbd>: scroll naar beneden vanaf hoofdpaneel (fn+up/shift+k)
<kbd>pgdown</kbd>: scroll naar beneden vanaf hoofdpaneel (fn+down/shift+j)
<kbd>m</kbd>: bekijk merge/rebase opties
<kbd>ctrl+p</kbd>: bekijk aangepaste patch opties
<kbd>R</kbd>: verversen
<kbd>?</kbd>: open menu
<kbd>+</kbd>: volgende scherm modus (normaal/half/groot)
<kbd>_</kbd>: vorige scherm modus
<kbd>ctrl+s</kbd>: bekijk scoping opties
<kbd>W</kbd>: open diff menu
<kbd>ctrl+e</kbd>: open diff menu
<kbd>&lt;c-r&gt;</kbd>: wissel naar een recente repo
<kbd>&lt;pgup&gt;</kbd>: scroll naar beneden vanaf hoofdpaneel (fn+up/shift+k)
<kbd>&lt;pgdown&gt;</kbd>: scroll naar beneden vanaf hoofdpaneel (fn+down/shift+j)
<kbd>@</kbd>: open command log menu
<kbd>ctrl+w</kbd>: Toggle whether or not whitespace changes are shown in the diff view
<kbd>}</kbd>: Increase the size of the context shown around changes in the diff view
<kbd>{</kbd>: Decrease the size of the context shown around changes in the diff view
<kbd>:</kbd>: voer aangepaste commando uit
<kbd>&lt;c-p&gt;</kbd>: bekijk aangepaste patch opties
<kbd>m</kbd>: bekijk merge/rebase opties
<kbd>R</kbd>: verversen
<kbd>+</kbd>: volgende scherm modus (normaal/half/groot)
<kbd>_</kbd>: vorige scherm modus
<kbd>?</kbd>: open menu
<kbd>&lt;c-s&gt;</kbd>: bekijk scoping opties
<kbd>W</kbd>: open diff menu
<kbd>&lt;c-e&gt;</kbd>: open diff menu
<kbd>&lt;c-w&gt;</kbd>: Toggle whether or not whitespace changes are shown in the diff view
<kbd>z</kbd>: ongedaan maken (via reflog) (experimenteel)
<kbd>ctrl+z</kbd>: redo (via reflog) (experimenteel)
<kbd>&lt;c-z&gt;</kbd>: redo (via reflog) (experimenteel)
<kbd>P</kbd>: push
<kbd>p</kbd>: pull
</pre>
@@ -33,9 +35,9 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
<pre>
<kbd>,</kbd>: vorige pagina
<kbd>.</kbd>: volgende pagina
<kbd><</kbd>: scroll naar boven
<kbd>&lt;</kbd>: scroll naar boven
<kbd>/</kbd>: start met zoeken
<kbd>></kbd>: scroll naar beneden
<kbd>&gt;</kbd>: scroll naar beneden
<kbd>H</kbd>: scroll left
<kbd>L</kbd>: scroll right
<kbd>]</kbd>: volgende tabblad
@@ -45,10 +47,10 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
## Bestanden
<pre>
<kbd>ctrl+o</kbd>: kopieer de bestandsnaam naar het klembord
<kbd>&lt;c-o&gt;</kbd>: kopieer de bestandsnaam naar het klembord
<kbd>d</kbd>: bekijk 'veranderingen ongedaan maken' opties
<kbd>space</kbd>: toggle staged
<kbd>ctrl+b</kbd>: Filter files (staged/unstaged)
<kbd>&lt;space&gt;</kbd>: toggle staged
<kbd>&lt;c-b&gt;</kbd>: Filter files (staged/unstaged)
<kbd>c</kbd>: commit veranderingen
<kbd>w</kbd>: commit veranderingen zonder pre-commit hook
<kbd>A</kbd>: wijzig laatste commit
@@ -60,7 +62,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
<kbd>s</kbd>: stash-bestanden
<kbd>S</kbd>: bekijk stash opties
<kbd>a</kbd>: toggle staged alle
<kbd>enter</kbd>: stage individuele hunks/lijnen
<kbd>&lt;enter&gt;</kbd>: stage individuele hunks/lijnen
<kbd>g</kbd>: bekijk upstream reset opties
<kbd>D</kbd>: bekijk reset opties
<kbd>`</kbd>: toggle bestandsboom weergave
@@ -68,16 +70,23 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
<kbd>f</kbd>: fetch
</pre>
## Bevestigingspaneel
<pre>
<kbd>&lt;enter&gt;</kbd>: bevestig
<kbd>&lt;esc&gt;</kbd>: sluiten
</pre>
## Branches
<pre>
<kbd>ctrl+o</kbd>: kopieer branch name naar klembord
<kbd>&lt;c-o&gt;</kbd>: kopieer branch name naar klembord
<kbd>i</kbd>: laat git-flow opties zien
<kbd>space</kbd>: uitchecken
<kbd>&lt;space&gt;</kbd>: uitchecken
<kbd>n</kbd>: nieuwe branch
<kbd>o</kbd>: maak een pull-request
<kbd>O</kbd>: bekijk opties voor pull-aanvraag
<kbd>ctrl+y</kbd>: kopieer de URL van het pull-verzoek naar het klembord
<kbd>&lt;c-y&gt;</kbd>: kopieer de URL van het pull-verzoek naar het klembord
<kbd>c</kbd>: uitchecken bij naam
<kbd>F</kbd>: forceer checkout
<kbd>d</kbd>: verwijder branch
@@ -88,28 +97,35 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
<kbd>g</kbd>: bekijk reset opties
<kbd>R</kbd>: hernoem branch
<kbd>u</kbd>: set/unset upstream
<kbd>enter</kbd>: bekijk commits
<kbd>&lt;enter&gt;</kbd>: bekijk commits
</pre>
## Commit Bericht
<pre>
<kbd>&lt;enter&gt;</kbd>: bevestig
<kbd>&lt;esc&gt;</kbd>: sluiten
</pre>
## Commit bestanden
<pre>
<kbd>ctrl+o</kbd>: kopieer de vastgelegde bestandsnaam naar het klembord
<kbd>&lt;c-o&gt;</kbd>: kopieer de vastgelegde bestandsnaam naar het klembord
<kbd>c</kbd>: bestand uitchecken
<kbd>d</kbd>: uitsluit deze commit zijn veranderingen aan dit bestand
<kbd>o</kbd>: open bestand
<kbd>e</kbd>: verander bestand
<kbd>space</kbd>: toggle bestand inbegrepen in patch
<kbd>&lt;space&gt;</kbd>: toggle bestand inbegrepen in patch
<kbd>a</kbd>: toggle all files included in patch
<kbd>enter</kbd>: enter bestand om geselecteerde regels toe te voegen aan de patch
<kbd>&lt;enter&gt;</kbd>: enter bestand om geselecteerde regels toe te voegen aan de patch
<kbd>`</kbd>: toggle bestandsboom weergave
</pre>
## Commits
<pre>
<kbd>ctrl+o</kbd>: kopieer commit SHA naar klembord
<kbd>ctrl+r</kbd>: reset cherry-picked (gekopieerde) commits selectie
<kbd>&lt;c-o&gt;</kbd>: kopieer commit SHA naar klembord
<kbd>&lt;c-r&gt;</kbd>: reset cherry-picked (gekopieerde) commits selectie
<kbd>b</kbd>: view bisect options
<kbd>s</kbd>: squash beneden
<kbd>f</kbd>: Fixup commit
@@ -120,22 +136,29 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
<kbd>p</kbd>: kies commit (wanneer midden in rebase)
<kbd>F</kbd>: creëer fixup commit voor deze commit
<kbd>S</kbd>: squash bovenstaande commits
<kbd>ctrl+j</kbd>: verplaats commit 1 naar beneden
<kbd>ctrl+k</kbd>: verplaats commit 1 naar boven
<kbd>&lt;c-j&gt;</kbd>: verplaats commit 1 naar beneden
<kbd>&lt;c-k&gt;</kbd>: verplaats commit 1 naar boven
<kbd>v</kbd>: plak commits (cherry-pick)
<kbd>A</kbd>: wijzig commit met staged veranderingen
<kbd>a</kbd>: reset commit author
<kbd>t</kbd>: commit ongedaan maken
<kbd>T</kbd>: tag commit
<kbd>ctrl+l</kbd>: open log menu
<kbd>space</kbd>: checkout commit
<kbd>&lt;c-l&gt;</kbd>: open log menu
<kbd>&lt;space&gt;</kbd>: checkout commit
<kbd>y</kbd>: copy commit attribute
<kbd>o</kbd>: open commit in browser
<kbd>n</kbd>: creëer nieuwe branch van commit
<kbd>g</kbd>: bekijk reset opties
<kbd>c</kbd>: kopieer commit (cherry-pick)
<kbd>C</kbd>: kopieer commit reeks (cherry-pick)
<kbd>enter</kbd>: bekijk gecommite bestanden
<kbd>&lt;enter&gt;</kbd>: bekijk gecommite bestanden
</pre>
## Menu
<pre>
<kbd>&lt;enter&gt;</kbd>: uitvoeren
<kbd>&lt;esc&gt;</kbd>: sluiten
</pre>
## Mergen
@@ -143,67 +166,67 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
<pre>
<kbd>e</kbd>: verander bestand
<kbd>o</kbd>: open bestand
<kbd>◀</kbd>: selecteer voorgaand conflict
<kbd>▶</kbd>: selecteer volgende conflict
<kbd>▲</kbd>: selecteer bovenste hunk
<kbd>▼</kbd>: selecteer onderste hunk
<kbd>&lt;left&gt;</kbd>: selecteer voorgaand conflict
<kbd>&lt;right&gt;</kbd>: selecteer volgende conflict
<kbd>&lt;up&gt;</kbd>: selecteer bovenste hunk
<kbd>&lt;down&gt;</kbd>: selecteer onderste hunk
<kbd>z</kbd>: ongedaan maken
<kbd>M</kbd>: open external merge tool (git mergetool)
<kbd>space</kbd>: kies hunk
<kbd>&lt;space&gt;</kbd>: kies hunk
<kbd>b</kbd>: kies bijde hunks
<kbd>esc</kbd>: ga terug naar het bestanden paneel
<kbd>&lt;esc&gt;</kbd>: ga terug naar het bestanden paneel
</pre>
## Normaal
<pre>
<kbd>mouse wheel ▼</kbd>: scroll omlaag (fn+up)
<kbd>mouse wheel ▲</kbd>: scroll omhoog (fn+down)
<kbd>mouse wheel down</kbd>: scroll omlaag (fn+up)
<kbd>mouse wheel up</kbd>: scroll omhoog (fn+down)
</pre>
## Patch Bouwen
<pre>
<kbd>◀</kbd>: selecteer de vorige hunk
<kbd>▶</kbd>: selecteer de volgende hunk
<kbd>&lt;left&gt;</kbd>: selecteer de vorige hunk
<kbd>&lt;right&gt;</kbd>: selecteer de volgende hunk
<kbd>v</kbd>: toggle drag selecteer
<kbd>V</kbd>: toggle drag selecteer
<kbd>a</kbd>: toggle selecteer hunk
<kbd>ctrl+o</kbd>: copy the selected text to the clipboard
<kbd>&lt;c-o&gt;</kbd>: copy the selected text to the clipboard
<kbd>o</kbd>: open bestand
<kbd>e</kbd>: verander bestand
<kbd>space</kbd>: voeg toe/verwijder lijn(en) in patch
<kbd>esc</kbd>: sluit lijn-bij-lijn modus
<kbd>&lt;space&gt;</kbd>: voeg toe/verwijder lijn(en) in patch
<kbd>&lt;esc&gt;</kbd>: sluit lijn-bij-lijn modus
</pre>
## Reflog
<pre>
<kbd>ctrl+o</kbd>: kopieer commit SHA naar klembord
<kbd>space</kbd>: checkout commit
<kbd>&lt;c-o&gt;</kbd>: kopieer commit SHA naar klembord
<kbd>&lt;space&gt;</kbd>: checkout commit
<kbd>y</kbd>: copy commit attribute
<kbd>o</kbd>: open commit in browser
<kbd>n</kbd>: creëer nieuwe branch van commit
<kbd>g</kbd>: bekijk reset opties
<kbd>c</kbd>: kopieer commit (cherry-pick)
<kbd>C</kbd>: kopieer commit reeks (cherry-pick)
<kbd>ctrl+r</kbd>: reset cherry-picked (gekopieerde) commits selectie
<kbd>enter</kbd>: bekijk commits
<kbd>&lt;c-r&gt;</kbd>: reset cherry-picked (gekopieerde) commits selectie
<kbd>&lt;enter&gt;</kbd>: bekijk commits
</pre>
## Remote Branches
<pre>
<kbd>ctrl+o</kbd>: kopieer branch name naar klembord
<kbd>space</kbd>: uitchecken
<kbd>&lt;c-o&gt;</kbd>: kopieer branch name naar klembord
<kbd>&lt;space&gt;</kbd>: uitchecken
<kbd>n</kbd>: nieuwe branch
<kbd>M</kbd>: merge in met huidige checked out branch
<kbd>r</kbd>: rebase branch
<kbd>d</kbd>: verwijder branch
<kbd>u</kbd>: stel in als upstream van uitgecheckte branch
<kbd>esc</kbd>: ga terug naar remotes lijst
<kbd>&lt;esc&gt;</kbd>: ga terug naar remotes lijst
<kbd>g</kbd>: bekijk reset opties
<kbd>enter</kbd>: bekijk commits
<kbd>&lt;enter&gt;</kbd>: bekijk commits
</pre>
## Remotes
@@ -218,17 +241,17 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
## Staging
<pre>
<kbd>◀</kbd>: selecteer de vorige hunk
<kbd>▶</kbd>: selecteer de volgende hunk
<kbd>&lt;left&gt;</kbd>: selecteer de vorige hunk
<kbd>&lt;right&gt;</kbd>: selecteer de volgende hunk
<kbd>v</kbd>: toggle drag selecteer
<kbd>V</kbd>: toggle drag selecteer
<kbd>a</kbd>: toggle selecteer hunk
<kbd>ctrl+o</kbd>: copy the selected text to the clipboard
<kbd>&lt;c-o&gt;</kbd>: copy the selected text to the clipboard
<kbd>o</kbd>: open bestand
<kbd>e</kbd>: verander bestand
<kbd>esc</kbd>: ga terug naar het bestanden paneel
<kbd>tab</kbd>: ga naar een ander paneel
<kbd>space</kbd>: toggle lijnen staged / unstaged
<kbd>&lt;esc&gt;</kbd>: ga terug naar het bestanden paneel
<kbd>&lt;tab&gt;</kbd>: ga naar een ander paneel
<kbd>&lt;space&gt;</kbd>: toggle lijnen staged / unstaged
<kbd>d</kbd>: verwijdert change (git reset)
<kbd>E</kbd>: edit hunk
<kbd>c</kbd>: commit veranderingen
@@ -239,44 +262,44 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
## Stash
<pre>
<kbd>space</kbd>: toepassen
<kbd>&lt;space&gt;</kbd>: toepassen
<kbd>g</kbd>: pop
<kbd>d</kbd>: laten vallen
<kbd>n</kbd>: nieuwe branch
<kbd>r</kbd>: rename stash
<kbd>enter</kbd>: bekijk gecommite bestanden
<kbd>&lt;enter&gt;</kbd>: bekijk gecommite bestanden
</pre>
## Status
<pre>
<kbd>e</kbd>: verander config bestand
<kbd>o</kbd>: open config bestand
<kbd>e</kbd>: verander config bestand
<kbd>u</kbd>: check voor updates
<kbd>enter</kbd>: wissel naar een recente repo
<kbd>&lt;enter&gt;</kbd>: wissel naar een recente repo
<kbd>a</kbd>: alle logs van de branch laten zien
</pre>
## Sub-commits
<pre>
<kbd>ctrl+o</kbd>: kopieer commit SHA naar klembord
<kbd>space</kbd>: checkout commit
<kbd>&lt;c-o&gt;</kbd>: kopieer commit SHA naar klembord
<kbd>&lt;space&gt;</kbd>: checkout commit
<kbd>y</kbd>: copy commit attribute
<kbd>o</kbd>: open commit in browser
<kbd>n</kbd>: creëer nieuwe branch van commit
<kbd>g</kbd>: bekijk reset opties
<kbd>c</kbd>: kopieer commit (cherry-pick)
<kbd>C</kbd>: kopieer commit reeks (cherry-pick)
<kbd>ctrl+r</kbd>: reset cherry-picked (gekopieerde) commits selectie
<kbd>enter</kbd>: bekijk gecommite bestanden
<kbd>&lt;c-r&gt;</kbd>: reset cherry-picked (gekopieerde) commits selectie
<kbd>&lt;enter&gt;</kbd>: bekijk gecommite bestanden
</pre>
## Submodules
<pre>
<kbd>ctrl+o</kbd>: kopieer submodule naam naar klembord
<kbd>enter</kbd>: enter submodule
<kbd>&lt;c-o&gt;</kbd>: kopieer submodule naam naar klembord
<kbd>&lt;enter&gt;</kbd>: enter submodule
<kbd>d</kbd>: remove submodule
<kbd>u</kbd>: update submodule
<kbd>n</kbd>: voeg nieuwe submodule toe
@@ -288,10 +311,10 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
## Tags
<pre>
<kbd>space</kbd>: uitchecken
<kbd>&lt;space&gt;</kbd>: uitchecken
<kbd>d</kbd>: verwijder tag
<kbd>P</kbd>: push tag
<kbd>n</kbd>: creëer tag
<kbd>g</kbd>: bekijk reset opties
<kbd>enter</kbd>: bekijk commits
<kbd>&lt;enter&gt;</kbd>: bekijk commits
</pre>
+96 -73
View File
@@ -2,28 +2,30 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
# Lazygit Keybindings
_Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
## Globalne
<pre>
<kbd>ctrl+r</kbd>: switch to a recent repo
<kbd>pgup</kbd>: scroll up main panel (fn+up/shift+k)
<kbd>pgdown</kbd>: scroll down main panel (fn+down/shift+j)
<kbd>m</kbd>: widok scalenia/opcje zmiany bazy
<kbd>ctrl+p</kbd>: view custom patch options
<kbd>R</kbd>: odśwież
<kbd>?</kbd>: open menu
<kbd>+</kbd>: next screen mode (normal/half/fullscreen)
<kbd>_</kbd>: prev screen mode
<kbd>ctrl+s</kbd>: view filter-by-path options
<kbd>W</kbd>: open diff menu
<kbd>ctrl+e</kbd>: open diff menu
<kbd>&lt;c-r&gt;</kbd>: switch to a recent repo
<kbd>&lt;pgup&gt;</kbd>: scroll up main panel (fn+up/shift+k)
<kbd>&lt;pgdown&gt;</kbd>: scroll down main panel (fn+down/shift+j)
<kbd>@</kbd>: open command log menu
<kbd>ctrl+w</kbd>: Toggle whether or not whitespace changes are shown in the diff view
<kbd>}</kbd>: Increase the size of the context shown around changes in the diff view
<kbd>{</kbd>: Decrease the size of the context shown around changes in the diff view
<kbd>:</kbd>: wykonaj własną komendę
<kbd>&lt;c-p&gt;</kbd>: view custom patch options
<kbd>m</kbd>: widok scalenia/opcje zmiany bazy
<kbd>R</kbd>: odśwież
<kbd>+</kbd>: next screen mode (normal/half/fullscreen)
<kbd>_</kbd>: prev screen mode
<kbd>?</kbd>: open menu
<kbd>&lt;c-s&gt;</kbd>: view filter-by-path options
<kbd>W</kbd>: open diff menu
<kbd>&lt;c-e&gt;</kbd>: open diff menu
<kbd>&lt;c-w&gt;</kbd>: Toggle whether or not whitespace changes are shown in the diff view
<kbd>z</kbd>: undo (via reflog) (experimental)
<kbd>ctrl+z</kbd>: redo (via reflog) (experimental)
<kbd>&lt;c-z&gt;</kbd>: redo (via reflog) (experimental)
<kbd>P</kbd>: push
<kbd>p</kbd>: pull
</pre>
@@ -33,20 +35,27 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
<pre>
<kbd>,</kbd>: previous page
<kbd>.</kbd>: next page
<kbd><</kbd>: scroll to top
<kbd>&lt;</kbd>: scroll to top
<kbd>/</kbd>: start search
<kbd>></kbd>: scroll to bottom
<kbd>&gt;</kbd>: scroll to bottom
<kbd>H</kbd>: scroll left
<kbd>L</kbd>: scroll right
<kbd>]</kbd>: next tab
<kbd>[</kbd>: previous tab
</pre>
## Commit Summary
<pre>
<kbd>&lt;enter&gt;</kbd>: potwierdź
<kbd>&lt;esc&gt;</kbd>: zamknij
</pre>
## Commity
<pre>
<kbd>ctrl+o</kbd>: copy commit SHA to clipboard
<kbd>ctrl+r</kbd>: reset cherry-picked (copied) commits selection
<kbd>&lt;c-o&gt;</kbd>: copy commit SHA to clipboard
<kbd>&lt;c-r&gt;</kbd>: reset cherry-picked (copied) commits selection
<kbd>b</kbd>: view bisect options
<kbd>s</kbd>: ściśnij
<kbd>f</kbd>: napraw commit
@@ -57,34 +66,41 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
<kbd>p</kbd>: wybierz commit (podczas zmiany bazy)
<kbd>F</kbd>: utwórz commit naprawczy dla tego commita
<kbd>S</kbd>: spłaszcz wszystkie commity naprawcze powyżej zaznaczonych commitów (autosquash)
<kbd>ctrl+j</kbd>: przenieś commit 1 w dół
<kbd>ctrl+k</kbd>: przenieś commit 1 w górę
<kbd>&lt;c-j&gt;</kbd>: przenieś commit 1 w dół
<kbd>&lt;c-k&gt;</kbd>: przenieś commit 1 w górę
<kbd>v</kbd>: wklej commity (przebieranie)
<kbd>A</kbd>: popraw commit zmianami z poczekalni
<kbd>a</kbd>: reset commit author
<kbd>t</kbd>: odwróć commit
<kbd>T</kbd>: tag commit
<kbd>ctrl+l</kbd>: open log menu
<kbd>space</kbd>: checkout commit
<kbd>&lt;c-l&gt;</kbd>: open log menu
<kbd>&lt;space&gt;</kbd>: checkout commit
<kbd>y</kbd>: copy commit attribute
<kbd>o</kbd>: open commit in browser
<kbd>n</kbd>: create new branch off of commit
<kbd>g</kbd>: wyświetl opcje resetu
<kbd>c</kbd>: kopiuj commit (przebieranie)
<kbd>C</kbd>: kopiuj zakres commitów (przebieranie)
<kbd>enter</kbd>: przeglądaj pliki commita
<kbd>&lt;enter&gt;</kbd>: przeglądaj pliki commita
</pre>
## Confirmation Panel
<pre>
<kbd>&lt;enter&gt;</kbd>: potwierdź
<kbd>&lt;esc&gt;</kbd>: zamknij
</pre>
## Local Branches
<pre>
<kbd>ctrl+o</kbd>: copy branch name to clipboard
<kbd>&lt;c-o&gt;</kbd>: copy branch name to clipboard
<kbd>i</kbd>: show git-flow options
<kbd>space</kbd>: przełącz
<kbd>&lt;space&gt;</kbd>: przełącz
<kbd>n</kbd>: nowa gałąź
<kbd>o</kbd>: utwórz żądanie pobrania
<kbd>O</kbd>: utwórz opcje żądania ściągnięcia
<kbd>ctrl+y</kbd>: skopiuj adres URL żądania pobrania do schowka
<kbd>&lt;c-y&gt;</kbd>: skopiuj adres URL żądania pobrania do schowka
<kbd>c</kbd>: przełącz używając nazwy
<kbd>F</kbd>: wymuś przełączenie
<kbd>d</kbd>: usuń gałąź
@@ -95,31 +111,38 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
<kbd>g</kbd>: wyświetl opcje resetu
<kbd>R</kbd>: rename branch
<kbd>u</kbd>: set/unset upstream
<kbd>enter</kbd>: view commits
<kbd>&lt;enter&gt;</kbd>: view commits
</pre>
## Main Panel (Patch Building)
<pre>
<kbd>◀</kbd>: poprzedni kawałek
<kbd>▶</kbd>: następny kawałek
<kbd>&lt;left&gt;</kbd>: poprzedni kawałek
<kbd>&lt;right&gt;</kbd>: następny kawałek
<kbd>v</kbd>: toggle drag select
<kbd>V</kbd>: toggle drag select
<kbd>a</kbd>: toggle select hunk
<kbd>ctrl+o</kbd>: copy the selected text to the clipboard
<kbd>&lt;c-o&gt;</kbd>: copy the selected text to the clipboard
<kbd>o</kbd>: otwórz plik
<kbd>e</kbd>: edytuj plik
<kbd>space</kbd>: add/remove line(s) to patch
<kbd>esc</kbd>: wyście z trybu "linia po linii"
<kbd>&lt;space&gt;</kbd>: add/remove line(s) to patch
<kbd>&lt;esc&gt;</kbd>: wyście z trybu "linia po linii"
</pre>
## Menu
<pre>
<kbd>&lt;enter&gt;</kbd>: wykonaj
<kbd>&lt;esc&gt;</kbd>: zamknij
</pre>
## Pliki
<pre>
<kbd>ctrl+o</kbd>: copy the file name to the clipboard
<kbd>&lt;c-o&gt;</kbd>: copy the file name to the clipboard
<kbd>d</kbd>: pokaż opcje porzucania zmian
<kbd>space</kbd>: przełącz stan poczekalni
<kbd>ctrl+b</kbd>: Filter files (staged/unstaged)
<kbd>&lt;space&gt;</kbd>: przełącz stan poczekalni
<kbd>&lt;c-b&gt;</kbd>: Filter files (staged/unstaged)
<kbd>c</kbd>: Zatwierdź zmiany
<kbd>w</kbd>: zatwierdź zmiany bez skryptu pre-commit
<kbd>A</kbd>: Zmień ostatni commit
@@ -131,7 +154,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
<kbd>s</kbd>: przechowaj zmiany
<kbd>S</kbd>: wyświetl opcje schowka
<kbd>a</kbd>: przełącz stan poczekalni wszystkich
<kbd>enter</kbd>: zatwierdź pojedyncze linie
<kbd>&lt;enter&gt;</kbd>: zatwierdź pojedyncze linie
<kbd>g</kbd>: view upstream reset options
<kbd>D</kbd>: wyświetl opcje resetu
<kbd>`</kbd>: toggle file tree view
@@ -142,31 +165,31 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
## Pliki commita
<pre>
<kbd>ctrl+o</kbd>: copy the committed file name to the clipboard
<kbd>&lt;c-o&gt;</kbd>: copy the committed file name to the clipboard
<kbd>c</kbd>: plik wybierania
<kbd>d</kbd>: porzuć zmiany commita dla tego pliku
<kbd>o</kbd>: otwórz plik
<kbd>e</kbd>: edytuj plik
<kbd>space</kbd>: toggle file included in patch
<kbd>&lt;space&gt;</kbd>: toggle file included in patch
<kbd>a</kbd>: toggle all files included in patch
<kbd>enter</kbd>: enter file to add selected lines to the patch (or toggle directory collapsed)
<kbd>&lt;enter&gt;</kbd>: enter file to add selected lines to the patch (or toggle directory collapsed)
<kbd>`</kbd>: toggle file tree view
</pre>
## Poczekalnia
<pre>
<kbd>◀</kbd>: poprzedni kawałek
<kbd>▶</kbd>: następny kawałek
<kbd>&lt;left&gt;</kbd>: poprzedni kawałek
<kbd>&lt;right&gt;</kbd>: następny kawałek
<kbd>v</kbd>: toggle drag select
<kbd>V</kbd>: toggle drag select
<kbd>a</kbd>: toggle select hunk
<kbd>ctrl+o</kbd>: copy the selected text to the clipboard
<kbd>&lt;c-o&gt;</kbd>: copy the selected text to the clipboard
<kbd>o</kbd>: otwórz plik
<kbd>e</kbd>: edytuj plik
<kbd>esc</kbd>: wróć do panelu plików
<kbd>tab</kbd>: switch to other panel (staged/unstaged changes)
<kbd>space</kbd>: toggle line staged / unstaged
<kbd>&lt;esc&gt;</kbd>: wróć do panelu plików
<kbd>&lt;tab&gt;</kbd>: switch to other panel (staged/unstaged changes)
<kbd>&lt;space&gt;</kbd>: toggle line staged / unstaged
<kbd>d</kbd>: delete change (git reset)
<kbd>E</kbd>: edit hunk
<kbd>c</kbd>: Zatwierdź zmiany
@@ -177,31 +200,31 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
## Reflog
<pre>
<kbd>ctrl+o</kbd>: copy commit SHA to clipboard
<kbd>space</kbd>: checkout commit
<kbd>&lt;c-o&gt;</kbd>: copy commit SHA to clipboard
<kbd>&lt;space&gt;</kbd>: checkout commit
<kbd>y</kbd>: copy commit attribute
<kbd>o</kbd>: open commit in browser
<kbd>n</kbd>: create new branch off of commit
<kbd>g</kbd>: wyświetl opcje resetu
<kbd>c</kbd>: kopiuj commit (przebieranie)
<kbd>C</kbd>: kopiuj zakres commitów (przebieranie)
<kbd>ctrl+r</kbd>: reset cherry-picked (copied) commits selection
<kbd>enter</kbd>: view commits
<kbd>&lt;c-r&gt;</kbd>: reset cherry-picked (copied) commits selection
<kbd>&lt;enter&gt;</kbd>: view commits
</pre>
## Remote Branches
<pre>
<kbd>ctrl+o</kbd>: copy branch name to clipboard
<kbd>space</kbd>: przełącz
<kbd>&lt;c-o&gt;</kbd>: copy branch name to clipboard
<kbd>&lt;space&gt;</kbd>: przełącz
<kbd>n</kbd>: nowa gałąź
<kbd>M</kbd>: scal do obecnej gałęzi
<kbd>r</kbd>: zmiana bazy gałęzi
<kbd>d</kbd>: usuń gałąź
<kbd>u</kbd>: set as upstream of checked-out branch
<kbd>esc</kbd>: wróć do listy repozytoriów zdalnych
<kbd>&lt;esc&gt;</kbd>: wróć do listy repozytoriów zdalnych
<kbd>g</kbd>: wyświetl opcje resetu
<kbd>enter</kbd>: view commits
<kbd>&lt;enter&gt;</kbd>: view commits
</pre>
## Remotes
@@ -218,58 +241,58 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
<pre>
<kbd>e</kbd>: edytuj plik
<kbd>o</kbd>: otwórz plik
<kbd>◀</kbd>: poprzedni konflikt
<kbd>▶</kbd>: następny konflikt
<kbd>▲</kbd>: wybierz poprzedni kawałek
<kbd>▼</kbd>: wybierz następny kawałek
<kbd>&lt;left&gt;</kbd>: poprzedni konflikt
<kbd>&lt;right&gt;</kbd>: następny konflikt
<kbd>&lt;up&gt;</kbd>: wybierz poprzedni kawałek
<kbd>&lt;down&gt;</kbd>: wybierz następny kawałek
<kbd>z</kbd>: cofnij
<kbd>M</kbd>: open external merge tool (git mergetool)
<kbd>space</kbd>: wybierz kawałek
<kbd>&lt;space&gt;</kbd>: wybierz kawałek
<kbd>b</kbd>: wybierz wszystkie kawałki
<kbd>esc</kbd>: wróć do panelu plików
<kbd>&lt;esc&gt;</kbd>: wróć do panelu plików
</pre>
## Schowek
<pre>
<kbd>space</kbd>: zastosuj
<kbd>&lt;space&gt;</kbd>: zastosuj
<kbd>g</kbd>: wyciągnij
<kbd>d</kbd>: porzuć
<kbd>n</kbd>: nowa gałąź
<kbd>r</kbd>: rename stash
<kbd>enter</kbd>: przeglądaj pliki commita
<kbd>&lt;enter&gt;</kbd>: przeglądaj pliki commita
</pre>
## Status
<pre>
<kbd>e</kbd>: edytuj konfigurację
<kbd>o</kbd>: otwórz konfigurację
<kbd>e</kbd>: edytuj konfigurację
<kbd>u</kbd>: sprawdź aktualizacje
<kbd>enter</kbd>: switch to a recent repo
<kbd>&lt;enter&gt;</kbd>: switch to a recent repo
<kbd>a</kbd>: pokaż wszystkie logi gałęzi
</pre>
## Sub-commits
<pre>
<kbd>ctrl+o</kbd>: copy commit SHA to clipboard
<kbd>space</kbd>: checkout commit
<kbd>&lt;c-o&gt;</kbd>: copy commit SHA to clipboard
<kbd>&lt;space&gt;</kbd>: checkout commit
<kbd>y</kbd>: copy commit attribute
<kbd>o</kbd>: open commit in browser
<kbd>n</kbd>: create new branch off of commit
<kbd>g</kbd>: wyświetl opcje resetu
<kbd>c</kbd>: kopiuj commit (przebieranie)
<kbd>C</kbd>: kopiuj zakres commitów (przebieranie)
<kbd>ctrl+r</kbd>: reset cherry-picked (copied) commits selection
<kbd>enter</kbd>: przeglądaj pliki commita
<kbd>&lt;c-r&gt;</kbd>: reset cherry-picked (copied) commits selection
<kbd>&lt;enter&gt;</kbd>: przeglądaj pliki commita
</pre>
## Submodules
<pre>
<kbd>ctrl+o</kbd>: copy submodule name to clipboard
<kbd>enter</kbd>: enter submodule
<kbd>&lt;c-o&gt;</kbd>: copy submodule name to clipboard
<kbd>&lt;enter&gt;</kbd>: enter submodule
<kbd>d</kbd>: remove submodule
<kbd>u</kbd>: update submodule
<kbd>n</kbd>: add new submodule
@@ -281,17 +304,17 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
## Tags
<pre>
<kbd>space</kbd>: przełącz
<kbd>&lt;space&gt;</kbd>: przełącz
<kbd>d</kbd>: delete tag
<kbd>P</kbd>: push tag
<kbd>n</kbd>: create tag
<kbd>g</kbd>: wyświetl opcje resetu
<kbd>enter</kbd>: view commits
<kbd>&lt;enter&gt;</kbd>: view commits
</pre>
## Zwykłe
<pre>
<kbd>mouse wheel ▼</kbd>: przewiń w dół (fn+up)
<kbd>mouse wheel ▲</kbd>: przewiń w górę (fn+down)
<kbd>mouse wheel down</kbd>: przewiń w dół (fn+up)
<kbd>mouse wheel up</kbd>: przewiń w górę (fn+down)
</pre>
+96 -73
View File
@@ -2,28 +2,30 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
# Lazygit 按键绑定
_Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
## 全局键绑定
<pre>
<kbd>ctrl+r</kbd>: 切换到最近的仓库
<kbd>pgup</kbd>: 向上滚动主面板 (fn+up/shift+k)
<kbd>pgdown</kbd>: 向下滚动主面板 (fn+down/shift+j)
<kbd>m</kbd>: 查看 合并/变基 选项
<kbd>ctrl+p</kbd>: 查看自定义补丁选项
<kbd>R</kbd>: 刷新
<kbd>?</kbd>: 打开菜单
<kbd>+</kbd>: 下一屏模式(正常/半屏/全屏)
<kbd>_</kbd>: 上一屏模式
<kbd>ctrl+s</kbd>: 查看按路径过滤选项
<kbd>W</kbd>: 打开 diff 菜单
<kbd>ctrl+e</kbd>: 打开 diff 菜单
<kbd>&lt;c-r&gt;</kbd>: 切换到最近的仓库
<kbd>&lt;pgup&gt;</kbd>: 向上滚动主面板 (fn+up/shift+k)
<kbd>&lt;pgdown&gt;</kbd>: 向下滚动主面板 (fn+down/shift+j)
<kbd>@</kbd>: 打开命令日志菜单
<kbd>ctrl+w</kbd>: 切换是否在差异视图中显示空白字符差异
<kbd>}</kbd>: 扩大差异视图中显示的上下文范围
<kbd>{</kbd>: 缩小差异视图中显示的上下文范围
<kbd>:</kbd>: 执行自定义命令
<kbd>&lt;c-p&gt;</kbd>: 查看自定义补丁选项
<kbd>m</kbd>: 查看 合并/变基 选项
<kbd>R</kbd>: 刷新
<kbd>+</kbd>: 下一屏模式(正常/半屏/全屏)
<kbd>_</kbd>: 上一屏模式
<kbd>?</kbd>: 打开菜单
<kbd>&lt;c-s&gt;</kbd>: 查看按路径过滤选项
<kbd>W</kbd>: 打开 diff 菜单
<kbd>&lt;c-e&gt;</kbd>: 打开 diff 菜单
<kbd>&lt;c-w&gt;</kbd>: 切换是否在差异视图中显示空白字符差异
<kbd>z</kbd>: (通过 reflog)撤销「实验功能」
<kbd>ctrl+z</kbd>: (通过 reflog)重做「实验功能」
<kbd>&lt;c-z&gt;</kbd>: (通过 reflog)重做「实验功能」
<kbd>P</kbd>: 推送
<kbd>p</kbd>: 拉取
</pre>
@@ -33,9 +35,9 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
<pre>
<kbd>,</kbd>: 上一页
<kbd>.</kbd>: 下一页
<kbd><</kbd>: 滚动到顶部
<kbd>&lt;</kbd>: 滚动到顶部
<kbd>/</kbd>: 开始搜索
<kbd>></kbd>: 滚动到底部
<kbd>&gt;</kbd>: 滚动到底部
<kbd>H</kbd>: 向左滚动
<kbd>L</kbd>: 向右滚动
<kbd>]</kbd>: 下一个标签
@@ -45,28 +47,28 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
## Reflog 页面
<pre>
<kbd>ctrl+o</kbd>: 将提交的 SHA 复制到剪贴板
<kbd>space</kbd>: 检出提交
<kbd>&lt;c-o&gt;</kbd>: 将提交的 SHA 复制到剪贴板
<kbd>&lt;space&gt;</kbd>: 检出提交
<kbd>y</kbd>: copy commit attribute
<kbd>o</kbd>: 在浏览器中打开提交
<kbd>n</kbd>: 从提交创建新分支
<kbd>g</kbd>: 查看重置选项
<kbd>c</kbd>: 复制提交(拣选)
<kbd>C</kbd>: 复制提交范围(拣选)
<kbd>ctrl+r</kbd>: 重置已拣选(复制)的提交
<kbd>enter</kbd>: 查看提交
<kbd>&lt;c-r&gt;</kbd>: 重置已拣选(复制)的提交
<kbd>&lt;enter&gt;</kbd>: 查看提交
</pre>
## 分支页面
<pre>
<kbd>ctrl+o</kbd>: 将分支名称复制到剪贴板
<kbd>&lt;c-o&gt;</kbd>: 将分支名称复制到剪贴板
<kbd>i</kbd>: 显示 git-flow 选项
<kbd>space</kbd>: 检出
<kbd>&lt;space&gt;</kbd>: 检出
<kbd>n</kbd>: 新分支
<kbd>o</kbd>: 创建抓取请求
<kbd>O</kbd>: 创建抓取请求选项
<kbd>ctrl+y</kbd>: 将抓取请求 URL 复制到剪贴板
<kbd>&lt;c-y&gt;</kbd>: 将抓取请求 URL 复制到剪贴板
<kbd>c</kbd>: 按名称检出
<kbd>F</kbd>: 强制检出
<kbd>d</kbd>: 删除分支
@@ -77,29 +79,29 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
<kbd>g</kbd>: 查看重置选项
<kbd>R</kbd>: 重命名分支
<kbd>u</kbd>: set/unset upstream
<kbd>enter</kbd>: 查看提交
<kbd>&lt;enter&gt;</kbd>: 查看提交
</pre>
## 子提交
<pre>
<kbd>ctrl+o</kbd>: 将提交的 SHA 复制到剪贴板
<kbd>space</kbd>: 检出提交
<kbd>&lt;c-o&gt;</kbd>: 将提交的 SHA 复制到剪贴板
<kbd>&lt;space&gt;</kbd>: 检出提交
<kbd>y</kbd>: copy commit attribute
<kbd>o</kbd>: 在浏览器中打开提交
<kbd>n</kbd>: 从提交创建新分支
<kbd>g</kbd>: 查看重置选项
<kbd>c</kbd>: 复制提交(拣选)
<kbd>C</kbd>: 复制提交范围(拣选)
<kbd>ctrl+r</kbd>: 重置已拣选(复制)的提交
<kbd>enter</kbd>: 查看提交的文件
<kbd>&lt;c-r&gt;</kbd>: 重置已拣选(复制)的提交
<kbd>&lt;enter&gt;</kbd>: 查看提交的文件
</pre>
## 子模块
<pre>
<kbd>ctrl+o</kbd>: 将子模块名称复制到剪贴板
<kbd>enter</kbd>: 输入子模块
<kbd>&lt;c-o&gt;</kbd>: 将子模块名称复制到剪贴板
<kbd>&lt;enter&gt;</kbd>: 输入子模块
<kbd>d</kbd>: 删除子模块
<kbd>u</kbd>: 更新子模块
<kbd>n</kbd>: 添加新的子模块
@@ -111,8 +113,8 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
## 提交
<pre>
<kbd>ctrl+o</kbd>: 将提交的 SHA 复制到剪贴板
<kbd>ctrl+r</kbd>: 重置已拣选(复制)的提交
<kbd>&lt;c-o&gt;</kbd>: 将提交的 SHA 复制到剪贴板
<kbd>&lt;c-r&gt;</kbd>: 重置已拣选(复制)的提交
<kbd>b</kbd>: 查看二分查找选项
<kbd>s</kbd>: 向下压缩
<kbd>f</kbd>: 修正提交(fixup)
@@ -123,45 +125,52 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
<kbd>p</kbd>: 选择提交(变基过程中)
<kbd>F</kbd>: 为此提交创建修正
<kbd>S</kbd>: 压缩在所选提交之上的所有“fixup!”提交(自动压缩)
<kbd>ctrl+j</kbd>: 下移提交
<kbd>ctrl+k</kbd>: 上移提交
<kbd>&lt;c-j&gt;</kbd>: 下移提交
<kbd>&lt;c-k&gt;</kbd>: 上移提交
<kbd>v</kbd>: 粘贴提交(拣选)
<kbd>A</kbd>: 用已暂存的更改来修补提交
<kbd>a</kbd>: reset commit author
<kbd>t</kbd>: 还原提交
<kbd>T</kbd>: 标签提交
<kbd>ctrl+l</kbd>: 打开日志菜单
<kbd>space</kbd>: 检出提交
<kbd>&lt;c-l&gt;</kbd>: 打开日志菜单
<kbd>&lt;space&gt;</kbd>: 检出提交
<kbd>y</kbd>: copy commit attribute
<kbd>o</kbd>: 在浏览器中打开提交
<kbd>n</kbd>: 从提交创建新分支
<kbd>g</kbd>: 查看重置选项
<kbd>c</kbd>: 复制提交(拣选)
<kbd>C</kbd>: 复制提交范围(拣选)
<kbd>enter</kbd>: 查看提交的文件
<kbd>&lt;enter&gt;</kbd>: 查看提交的文件
</pre>
## 提交文件
<pre>
<kbd>ctrl+o</kbd>: 将提交的文件名复制到剪贴板
<kbd>&lt;c-o&gt;</kbd>: 将提交的文件名复制到剪贴板
<kbd>c</kbd>: 检出文件
<kbd>d</kbd>: 放弃对此文件的提交更改
<kbd>o</kbd>: 打开文件
<kbd>e</kbd>: 编辑文件
<kbd>space</kbd>: 补丁中包含的切换文件
<kbd>&lt;space&gt;</kbd>: 补丁中包含的切换文件
<kbd>a</kbd>: toggle all files included in patch
<kbd>enter</kbd>: 输入文件以将所选行添加到补丁中(或切换目录折叠)
<kbd>&lt;enter&gt;</kbd>: 输入文件以将所选行添加到补丁中(或切换目录折叠)
<kbd>`</kbd>: 切换文件树视图
</pre>
## 提交讯息
<pre>
<kbd>&lt;enter&gt;</kbd>: 确认
<kbd>&lt;esc&gt;</kbd>: 关闭
</pre>
## 文件
<pre>
<kbd>ctrl+o</kbd>: 将文件名复制到剪贴板
<kbd>&lt;c-o&gt;</kbd>: 将文件名复制到剪贴板
<kbd>d</kbd>: 查看'放弃更改'选项
<kbd>space</kbd>: 切换暂存状态
<kbd>ctrl+b</kbd>: Filter files (staged/unstaged)
<kbd>&lt;space&gt;</kbd>: 切换暂存状态
<kbd>&lt;c-b&gt;</kbd>: Filter files (staged/unstaged)
<kbd>c</kbd>: 提交更改
<kbd>w</kbd>: 提交更改而无需预先提交钩子
<kbd>A</kbd>: 修补最后一次提交
@@ -173,7 +182,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
<kbd>s</kbd>: 将所有更改加入贮藏
<kbd>S</kbd>: 查看贮藏选项
<kbd>a</kbd>: 切换所有文件的暂存状态
<kbd>enter</kbd>: 暂存单个 块/行 用于文件, 或 折叠/展开 目录
<kbd>&lt;enter&gt;</kbd>: 暂存单个 块/行 用于文件, 或 折叠/展开 目录
<kbd>g</kbd>: 查看上游重置选项
<kbd>D</kbd>: 查看重置选项
<kbd>`</kbd>: 切换文件树视图
@@ -184,27 +193,27 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
## 构建补丁中
<pre>
<kbd>◀</kbd>: 选择上一个区块
<kbd>▶</kbd>: 选择下一个区块
<kbd>&lt;left&gt;</kbd>: 选择上一个区块
<kbd>&lt;right&gt;</kbd>: 选择下一个区块
<kbd>v</kbd>: 切换拖动选择
<kbd>V</kbd>: 切换拖动选择
<kbd>a</kbd>: 切换选择区块
<kbd>ctrl+o</kbd>: 将选中文本复制到剪贴板
<kbd>&lt;c-o&gt;</kbd>: 将选中文本复制到剪贴板
<kbd>o</kbd>: 打开文件
<kbd>e</kbd>: 编辑文件
<kbd>space</kbd>: 添加/移除 行到补丁
<kbd>esc</kbd>: 退出逐行模式
<kbd>&lt;space&gt;</kbd>: 添加/移除 行到补丁
<kbd>&lt;esc&gt;</kbd>: 退出逐行模式
</pre>
## 标签页面
<pre>
<kbd>space</kbd>: 检出
<kbd>&lt;space&gt;</kbd>: 检出
<kbd>d</kbd>: 删除标签
<kbd>P</kbd>: 推送标签
<kbd>n</kbd>: 创建标签
<kbd>g</kbd>: 查看重置选项
<kbd>enter</kbd>: 查看提交
<kbd>&lt;enter&gt;</kbd>: 查看提交
</pre>
## 正在合并
@@ -212,31 +221,31 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
<pre>
<kbd>e</kbd>: 编辑文件
<kbd>o</kbd>: 打开文件
<kbd>◀</kbd>: 选择上一个冲突
<kbd>▶</kbd>: 选择下一个冲突
<kbd>▲</kbd>: 选择顶部块
<kbd>▼</kbd>: 选择底部块
<kbd>&lt;left&gt;</kbd>: 选择上一个冲突
<kbd>&lt;right&gt;</kbd>: 选择下一个冲突
<kbd>&lt;up&gt;</kbd>: 选择顶部块
<kbd>&lt;down&gt;</kbd>: 选择底部块
<kbd>z</kbd>: 撤销
<kbd>M</kbd>: 打开外部合并工具 (git mergetool)
<kbd>space</kbd>: 选中区块
<kbd>&lt;space&gt;</kbd>: 选中区块
<kbd>b</kbd>: 选中所有区块
<kbd>esc</kbd>: 返回文件面板
<kbd>&lt;esc&gt;</kbd>: 返回文件面板
</pre>
## 正在暂存
<pre>
<kbd>◀</kbd>: 选择上一个区块
<kbd>▶</kbd>: 选择下一个区块
<kbd>&lt;left&gt;</kbd>: 选择上一个区块
<kbd>&lt;right&gt;</kbd>: 选择下一个区块
<kbd>v</kbd>: 切换拖动选择
<kbd>V</kbd>: 切换拖动选择
<kbd>a</kbd>: 切换选择区块
<kbd>ctrl+o</kbd>: 将选中文本复制到剪贴板
<kbd>&lt;c-o&gt;</kbd>: 将选中文本复制到剪贴板
<kbd>o</kbd>: 打开文件
<kbd>e</kbd>: 编辑文件
<kbd>esc</kbd>: 返回文件面板
<kbd>tab</kbd>: 切换到其他面板
<kbd>space</kbd>: 切换行暂存状态
<kbd>&lt;esc&gt;</kbd>: 返回文件面板
<kbd>&lt;tab&gt;</kbd>: 切换到其他面板
<kbd>&lt;space&gt;</kbd>: 切换行暂存状态
<kbd>d</kbd>: 取消变更 (git reset)
<kbd>E</kbd>: edit hunk
<kbd>c</kbd>: 提交更改
@@ -247,44 +256,58 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
## 正常
<pre>
<kbd>mouse wheel ▼</kbd>: 向下滚动 (fn+up)
<kbd>mouse wheel ▲</kbd>: 向上滚动 (fn+down)
<kbd>mouse wheel down</kbd>: 向下滚动 (fn+up)
<kbd>mouse wheel up</kbd>: 向上滚动 (fn+down)
</pre>
## 状态
<pre>
<kbd>e</kbd>: 编辑配置文件
<kbd>o</kbd>: 打开配置文件
<kbd>e</kbd>: 编辑配置文件
<kbd>u</kbd>: 检查更新
<kbd>enter</kbd>: 切换到最近的仓库
<kbd>&lt;enter&gt;</kbd>: 切换到最近的仓库
<kbd>a</kbd>: 显示所有分支的日志
</pre>
## 确认面板
<pre>
<kbd>&lt;enter&gt;</kbd>: 确认
<kbd>&lt;esc&gt;</kbd>: 关闭
</pre>
## 菜单
<pre>
<kbd>&lt;enter&gt;</kbd>: 执行
<kbd>&lt;esc&gt;</kbd>: 关闭
</pre>
## 贮藏
<pre>
<kbd>space</kbd>: 应用
<kbd>&lt;space&gt;</kbd>: 应用
<kbd>g</kbd>: 应用并删除
<kbd>d</kbd>: 删除
<kbd>n</kbd>: 新分支
<kbd>r</kbd>: rename stash
<kbd>enter</kbd>: 查看提交的文件
<kbd>&lt;enter&gt;</kbd>: 查看提交的文件
</pre>
## 远程分支
<pre>
<kbd>ctrl+o</kbd>: 将分支名称复制到剪贴板
<kbd>space</kbd>: 检出
<kbd>&lt;c-o&gt;</kbd>: 将分支名称复制到剪贴板
<kbd>&lt;space&gt;</kbd>: 检出
<kbd>n</kbd>: 新分支
<kbd>M</kbd>: 合并到当前检出的分支
<kbd>r</kbd>: 将已检出的分支变基到该分支
<kbd>d</kbd>: 删除分支
<kbd>u</kbd>: 设置为检出分支的上游
<kbd>esc</kbd>: 返回远程仓库列表
<kbd>&lt;esc&gt;</kbd>: 返回远程仓库列表
<kbd>g</kbd>: 查看重置选项
<kbd>enter</kbd>: 查看提交
<kbd>&lt;enter&gt;</kbd>: 查看提交
</pre>
## 远程页面
+2 -4
View File
@@ -33,7 +33,6 @@ type App struct {
Config config.AppConfigurer
OSCommand *oscommands.OSCommand
Gui *gui.Gui
Updater *updates.Updater // may only need this on the Gui
}
func Run(
@@ -87,8 +86,7 @@ func NewApp(config config.AppConfigurer, common *common.Common) (*App, error) {
app.OSCommand = oscommands.NewOSCommand(common, config, oscommands.GetPlatform(), oscommands.NewNullGuiIO(app.Log))
var err error
app.Updater, err = updates.NewUpdater(common, config, app.OSCommand)
updater, err := updates.NewUpdater(common, config, app.OSCommand)
if err != nil {
return app, err
}
@@ -108,7 +106,7 @@ func NewApp(config config.AppConfigurer, common *common.Common) (*App, error) {
return app, err
}
app.Gui, err = gui.NewGui(common, config, gitVersion, app.Updater, showRecentRepos, dirName)
app.Gui, err = gui.NewGui(common, config, gitVersion, updater, showRecentRepos, dirName)
if err != nil {
return app, err
}
+18 -7
View File
@@ -12,6 +12,7 @@ import (
"fmt"
"log"
"os"
"strings"
"github.com/jesseduffield/generics/maps"
"github.com/jesseduffield/generics/slices"
@@ -183,6 +184,8 @@ func getHeader(binding *types.Binding, tr *i18n.TranslationSet) header {
func formatSections(tr *i18n.TranslationSet, bindingSections []*bindingSection) string {
content := fmt.Sprintf("# Lazygit %s\n", tr.Keybindings)
content += fmt.Sprintf("\n%s\n", italicize(tr.KeybindingsLegend))
for _, section := range bindingSections {
content += formatTitle(section.title)
content += "<pre>\n"
@@ -200,13 +203,21 @@ func formatTitle(title string) string {
}
func formatBinding(binding *types.Binding) string {
result := fmt.Sprintf(" <kbd>%s</kbd>: %s", escapeAngleBrackets(keybindings.LabelFromKey(binding.Key)), binding.Description)
if binding.Alternative != "" {
return fmt.Sprintf(
" <kbd>%s</kbd>: %s (%s)\n",
keybindings.LabelFromKey(binding.Key),
binding.Description,
binding.Alternative,
)
result += fmt.Sprintf(" (%s)", binding.Alternative)
}
return fmt.Sprintf(" <kbd>%s</kbd>: %s\n", keybindings.LabelFromKey(binding.Key), binding.Description)
result += "\n"
return result
}
func escapeAngleBrackets(str string) string {
result := strings.ReplaceAll(str, ">", "&gt;")
result = strings.ReplaceAll(result, "<", "&lt;")
return result
}
func italicize(str string) string {
return fmt.Sprintf("_%s_", str)
}
+2 -3
View File
@@ -117,8 +117,7 @@ func NewGitCommandAux(
workingTreeCommands := git_commands.NewWorkingTreeCommands(gitCommon, submoduleCommands, fileLoader)
rebaseCommands := git_commands.NewRebaseCommands(gitCommon, commitCommands, workingTreeCommands)
stashCommands := git_commands.NewStashCommands(gitCommon, fileLoader, workingTreeCommands)
// TODO: have patch builder take workingTreeCommands in its entirety
patchBuilder := patch.NewPatchBuilder(cmn.Log, workingTreeCommands.ApplyPatch,
patchBuilder := patch.NewPatchBuilder(cmn.Log,
func(from string, to string, reverse bool, filename string, plain bool) (string, error) {
// TODO: make patch builder take Gui.IgnoreWhitespaceInDiffView into
// account. For now we just pass false.
@@ -129,7 +128,7 @@ func NewGitCommandAux(
branchLoader := git_commands.NewBranchLoader(cmn, branchCommands.GetRawBranches, branchCommands.CurrentBranchInfo, configCommands)
commitFileLoader := git_commands.NewCommitFileLoader(cmn, cmd)
commitLoader := git_commands.NewCommitLoader(cmn, cmd, dotGitDir, branchCommands.CurrentBranchInfo, statusCommands.RebaseMode)
commitLoader := git_commands.NewCommitLoader(cmn, cmd, dotGitDir, statusCommands.RebaseMode)
reflogCommitLoader := git_commands.NewReflogCommitLoader(cmn, cmd)
remoteLoader := git_commands.NewRemoteLoader(cmn, cmd, repo.Remotes)
stashLoader := git_commands.NewStashLoader(cmn, cmd)
+16 -10
View File
@@ -1,7 +1,6 @@
package git_commands
import (
"fmt"
"os"
"path/filepath"
"strings"
@@ -98,13 +97,15 @@ func (self *BisectCommands) GetInfo() *BisectInfo {
}
func (self *BisectCommands) Reset() error {
return self.cmd.New("git bisect reset").StreamOutput().Run()
cmdStr := NewGitCmd("bisect").Arg("reset").ToString()
return self.cmd.New(cmdStr).StreamOutput().Run()
}
func (self *BisectCommands) Mark(ref string, term string) error {
return self.cmd.New(
fmt.Sprintf("git bisect %s %s", term, ref),
).
cmdStr := NewGitCmd("bisect").Arg(term, ref).ToString()
return self.cmd.New(cmdStr).
IgnoreEmptyError().
StreamOutput().
Run()
@@ -115,7 +116,9 @@ func (self *BisectCommands) Skip(ref string) error {
}
func (self *BisectCommands) Start() error {
return self.cmd.New("git bisect start").StreamOutput().Run()
cmdStr := NewGitCmd("bisect").Arg("start").ToString()
return self.cmd.New(cmdStr).StreamOutput().Run()
}
// tells us whether we've found our problem commit(s). We return a string slice of
@@ -137,7 +140,8 @@ func (self *BisectCommands) IsDone() (bool, []string, error) {
done := false
candidates := []string{}
err := self.cmd.New(fmt.Sprintf("git rev-list %s", newSha)).RunAndProcessLines(func(line string) (bool, error) {
cmdStr := NewGitCmd("rev-list").Arg(newSha).ToString()
err := self.cmd.New(cmdStr).RunAndProcessLines(func(line string) (bool, error) {
sha := strings.TrimSpace(line)
if status, ok := info.statusMap[sha]; ok {
@@ -167,9 +171,11 @@ func (self *BisectCommands) IsDone() (bool, []string, error) {
// bisecting is actually a descendant of our current bisect commit. If it's not, we need to
// render the commits from the bad commit.
func (self *BisectCommands) ReachableFromStart(bisectInfo *BisectInfo) bool {
err := self.cmd.New(
fmt.Sprintf("git merge-base --is-ancestor %s %s", bisectInfo.GetNewSha(), bisectInfo.GetStartSha()),
).DontLog().Run()
cmdStr := NewGitCmd("merge-base").
Arg("--is-ancestor", bisectInfo.GetNewSha(), bisectInfo.GetStartSha()).
ToString()
err := self.cmd.New(cmdStr).DontLog().Run()
return err == nil
}
+72 -32
View File
@@ -20,12 +20,20 @@ func NewBranchCommands(gitCommon *GitCommon) *BranchCommands {
// New creates a new branch
func (self *BranchCommands) New(name string, base string) error {
return self.cmd.New(fmt.Sprintf("git checkout -b %s %s", self.cmd.Quote(name), self.cmd.Quote(base))).Run()
cmdStr := NewGitCmd("checkout").
Arg("-b", self.cmd.Quote(name), self.cmd.Quote(base)).
ToString()
return self.cmd.New(cmdStr).Run()
}
// CurrentBranchInfo get the current branch information.
func (self *BranchCommands) CurrentBranchInfo() (BranchInfo, error) {
branchName, err := self.cmd.New("git symbolic-ref --short HEAD").DontLog().RunWithOutput()
branchName, err := self.cmd.New(
NewGitCmd("symbolic-ref").
Arg("--short", "HEAD").
ToString(),
).DontLog().RunWithOutput()
if err == nil && branchName != "HEAD\n" {
trimmedBranchName := strings.TrimSpace(branchName)
return BranchInfo{
@@ -34,7 +42,11 @@ func (self *BranchCommands) CurrentBranchInfo() (BranchInfo, error) {
DetachedHead: false,
}, nil
}
output, err := self.cmd.New(`git branch --points-at=HEAD --format="%(HEAD)%00%(objectname)%00%(refname)"`).DontLog().RunWithOutput()
output, err := self.cmd.New(
NewGitCmd("branch").
Arg("--points-at=HEAD", "--format=\"%(HEAD)%00%(objectname)%00%(refname)\"").
ToString(),
).DontLog().RunWithOutput()
if err != nil {
return BranchInfo{}, err
}
@@ -59,13 +71,12 @@ func (self *BranchCommands) CurrentBranchInfo() (BranchInfo, error) {
// Delete delete branch
func (self *BranchCommands) Delete(branch string, force bool) error {
command := "git branch -d"
cmdStr := NewGitCmd("branch").
ArgIfElse(force, "-D", "-d").
Arg(self.cmd.Quote(branch)).
ToString()
if force {
command = "git branch -D"
}
return self.cmd.New(fmt.Sprintf("%s %s", command, self.cmd.Quote(branch))).Run()
return self.cmd.New(cmdStr).Run()
}
// Checkout checks out a branch (or commit), with --force if you set the force arg to true
@@ -75,12 +86,12 @@ type CheckoutOptions struct {
}
func (self *BranchCommands) Checkout(branch string, options CheckoutOptions) error {
forceArg := ""
if options.Force {
forceArg = " --force"
}
cmdStr := NewGitCmd("checkout").
ArgIf(options.Force, "--force").
Arg(self.cmd.Quote(branch)).
ToString()
return self.cmd.New(fmt.Sprintf("git checkout%s %s", forceArg, self.cmd.Quote(branch))).
return self.cmd.New(cmdStr).
// prevents git from prompting us for input which would freeze the program
// TODO: see if this is actually needed here
AddEnvVars("GIT_TERMINAL_PROMPT=0").
@@ -104,15 +115,27 @@ func (self *BranchCommands) GetGraphCmdObj(branchName string) oscommands.ICmdObj
}
func (self *BranchCommands) SetCurrentBranchUpstream(remoteName string, remoteBranchName string) error {
return self.cmd.New(fmt.Sprintf("git branch --set-upstream-to=%s/%s", self.cmd.Quote(remoteName), self.cmd.Quote(remoteBranchName))).Run()
cmdStr := NewGitCmd("branch").
Arg(fmt.Sprintf("--set-upstream-to=%s/%s", self.cmd.Quote(remoteName), self.cmd.Quote(remoteBranchName))).
ToString()
return self.cmd.New(cmdStr).Run()
}
func (self *BranchCommands) SetUpstream(remoteName string, remoteBranchName string, branchName string) error {
return self.cmd.New(fmt.Sprintf("git branch --set-upstream-to=%s/%s %s", self.cmd.Quote(remoteName), self.cmd.Quote(remoteBranchName), self.cmd.Quote(branchName))).Run()
cmdStr := NewGitCmd("branch").
Arg(fmt.Sprintf("--set-upstream-to=%s/%s", self.cmd.Quote(remoteName), self.cmd.Quote(remoteBranchName))).
Arg(self.cmd.Quote(branchName)).
ToString()
return self.cmd.New(cmdStr).Run()
}
func (self *BranchCommands) UnsetUpstream(branchName string) error {
return self.cmd.New(fmt.Sprintf("git branch --unset-upstream %s", self.cmd.Quote(branchName))).Run()
cmdStr := NewGitCmd("branch").Arg("--unset-upstream", self.cmd.Quote(branchName)).
ToString()
return self.cmd.New(cmdStr).Run()
}
func (self *BranchCommands) GetCurrentBranchUpstreamDifferenceCount() (string, string) {
@@ -126,29 +149,49 @@ func (self *BranchCommands) GetUpstreamDifferenceCount(branchName string) (strin
// GetCommitDifferences checks how many pushables/pullables there are for the
// current branch
func (self *BranchCommands) GetCommitDifferences(from, to string) (string, string) {
command := "git rev-list %s..%s --count"
pushableCount, err := self.cmd.New(fmt.Sprintf(command, to, from)).DontLog().RunWithOutput()
pushableCount, err := self.countDifferences(to, from)
if err != nil {
return "?", "?"
}
pullableCount, err := self.cmd.New(fmt.Sprintf(command, from, to)).DontLog().RunWithOutput()
pullableCount, err := self.countDifferences(from, to)
if err != nil {
return "?", "?"
}
return strings.TrimSpace(pushableCount), strings.TrimSpace(pullableCount)
}
func (self *BranchCommands) countDifferences(from, to string) (string, error) {
cmdStr := NewGitCmd("rev-list").
Arg(fmt.Sprintf("%s..%s", from, to)).
Arg("--count").
ToString()
return self.cmd.New(cmdStr).DontLog().RunWithOutput()
}
func (self *BranchCommands) IsHeadDetached() bool {
err := self.cmd.New("git symbolic-ref -q HEAD").DontLog().Run()
cmdStr := NewGitCmd("symbolic-ref").Arg("-q", "HEAD").ToString()
err := self.cmd.New(cmdStr).DontLog().Run()
return err != nil
}
func (self *BranchCommands) Rename(oldName string, newName string) error {
return self.cmd.New(fmt.Sprintf("git branch --move %s %s", self.cmd.Quote(oldName), self.cmd.Quote(newName))).Run()
cmdStr := NewGitCmd("branch").
Arg("--move", self.cmd.Quote(oldName), self.cmd.Quote(newName)).
ToString()
return self.cmd.New(cmdStr).Run()
}
func (self *BranchCommands) GetRawBranches() (string, error) {
return self.cmd.New(`git for-each-ref --sort=-committerdate --format="%(HEAD)%00%(refname:short)%00%(upstream:short)%00%(upstream:track)" refs/heads`).DontLog().RunWithOutput()
cmdStr := NewGitCmd("for-each-ref").
Arg("--sort=-committerdate").
Arg(`--format="%(HEAD)%00%(refname:short)%00%(upstream:short)%00%(upstream:track)"`).
Arg("refs/heads").
ToString()
return self.cmd.New(cmdStr).DontLog().RunWithOutput()
}
type MergeOpts struct {
@@ -156,15 +199,12 @@ type MergeOpts struct {
}
func (self *BranchCommands) Merge(branchName string, opts MergeOpts) error {
mergeArg := ""
if self.UserConfig.Git.Merging.Args != "" {
mergeArg = " " + self.UserConfig.Git.Merging.Args
}
command := fmt.Sprintf("git merge --no-edit%s %s", mergeArg, self.cmd.Quote(branchName))
if opts.FastForwardOnly {
command = fmt.Sprintf("%s --ff-only", command)
}
command := NewGitCmd("merge").
Arg("--no-edit").
ArgIf(self.UserConfig.Git.Merging.Args != "", self.UserConfig.Git.Merging.Args).
ArgIf(opts.FastForwardOnly, "--ff-only").
Arg(self.cmd.Quote(branchName)).
ToString()
return self.cmd.New(command).Run()
}
+47 -5
View File
@@ -5,6 +5,7 @@ import (
"github.com/go-errors/errors"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/config"
"github.com/stretchr/testify/assert"
)
@@ -99,12 +100,53 @@ func TestBranchDeleteBranch(t *testing.T) {
}
func TestBranchMerge(t *testing.T) {
runner := oscommands.NewFakeRunner(t).
Expect(`git merge --no-edit "test"`, "", nil)
instance := buildBranchCommands(commonDeps{runner: runner})
scenarios := []struct {
testName string
userConfig *config.UserConfig
opts MergeOpts
branchName string
expected string
}{
{
testName: "basic",
userConfig: &config.UserConfig{},
opts: MergeOpts{},
branchName: "mybranch",
expected: `git merge --no-edit "mybranch"`,
},
{
testName: "merging args",
userConfig: &config.UserConfig{
Git: config.GitConfig{
Merging: config.MergingConfig{
Args: "--merging-args", // it's up to the user what they put here
},
},
},
opts: MergeOpts{},
branchName: "mybranch",
expected: `git merge --no-edit --merging-args "mybranch"`,
},
{
testName: "fast forward only",
userConfig: &config.UserConfig{},
opts: MergeOpts{FastForwardOnly: true},
branchName: "mybranch",
expected: `git merge --no-edit --ff-only "mybranch"`,
},
}
assert.NoError(t, instance.Merge("test", MergeOpts{}))
runner.CheckForMissingCalls()
for _, s := range scenarios {
s := s
t.Run(s.testName, func(t *testing.T) {
runner := oscommands.NewFakeRunner(t).
Expect(s.expected, "", nil)
instance := buildBranchCommands(commonDeps{runner: runner, userConfig: s.userConfig})
assert.NoError(t, instance.Merge(s.branchName, s.opts))
runner.CheckForMissingCalls()
})
}
}
func TestBranchCheckout(t *testing.T) {
+113 -43
View File
@@ -3,6 +3,7 @@ package git_commands
import (
"fmt"
"strings"
"time"
"github.com/go-errors/errors"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
@@ -22,18 +23,27 @@ func NewCommitCommands(gitCommon *GitCommon) *CommitCommands {
// ResetAuthor resets the author of the topmost commit
func (self *CommitCommands) ResetAuthor() error {
return self.cmd.New("git commit --allow-empty --only --no-edit --amend --reset-author").Run()
cmdStr := NewGitCmd("commit").
Arg("--allow-empty", "--only", "--no-edit", "--amend", "--reset-author").
ToString()
return self.cmd.New(cmdStr).Run()
}
// Sets the commit's author to the supplied value. Value is expected to be of the form 'Name <Email>'
func (self *CommitCommands) SetAuthor(value string) error {
commandStr := fmt.Sprintf("git commit --allow-empty --only --no-edit --amend --author=%s", self.cmd.Quote(value))
return self.cmd.New(commandStr).Run()
cmdStr := NewGitCmd("commit").
Arg("--allow-empty", "--only", "--no-edit", "--amend", "--author="+self.cmd.Quote(value)).
ToString()
return self.cmd.New(cmdStr).Run()
}
// ResetToCommit reset to commit
func (self *CommitCommands) ResetToCommit(sha string, strength string, envVars []string) error {
return self.cmd.New(fmt.Sprintf("git reset --%s %s", strength, sha)).
cmdStr := NewGitCmd("reset").Arg("--"+strength, sha).ToString()
return self.cmd.New(cmdStr).
// prevents git from prompting us for input which would freeze the program
// TODO: see if this is actually needed here
AddEnvVars("GIT_TERMINAL_PROMPT=0").
@@ -45,38 +55,52 @@ func (self *CommitCommands) CommitCmdObj(message string) oscommands.ICmdObj {
messageArgs := self.commitMessageArgs(message)
skipHookPrefix := self.UserConfig.Git.SkipHookPrefix
noVerifyFlag := ""
if skipHookPrefix != "" && strings.HasPrefix(message, skipHookPrefix) {
noVerifyFlag = " --no-verify"
}
return self.cmd.New(fmt.Sprintf("git commit%s%s%s", noVerifyFlag, self.signoffFlag(), messageArgs))
cmdStr := NewGitCmd("commit").
ArgIf(skipHookPrefix != "" && strings.HasPrefix(message, skipHookPrefix), "--no-verify").
ArgIf(self.signoffFlag() != "", self.signoffFlag()).
Arg(messageArgs...).
ToString()
return self.cmd.New(cmdStr)
}
// RewordLastCommit rewords the topmost commit with the given message
func (self *CommitCommands) RewordLastCommit(message string) error {
messageArgs := self.commitMessageArgs(message)
return self.cmd.New(fmt.Sprintf("git commit --allow-empty --amend --only%s", messageArgs)).Run()
cmdStr := NewGitCmd("commit").
Arg("--allow-empty", "--amend", "--only").
Arg(messageArgs...).
ToString()
return self.cmd.New(cmdStr).Run()
}
func (self *CommitCommands) commitMessageArgs(message string) string {
func (self *CommitCommands) commitMessageArgs(message string) []string {
msg, description, _ := strings.Cut(message, "\n")
descriptionArgs := ""
args := []string{"-m", self.cmd.Quote(msg)}
if description != "" {
descriptionArgs = fmt.Sprintf(" -m %s", self.cmd.Quote(description))
args = append(args, "-m", self.cmd.Quote(description))
}
return fmt.Sprintf(" -m %s%s", self.cmd.Quote(msg), descriptionArgs)
return args
}
// runs git commit without the -m argument meaning it will invoke the user's editor
func (self *CommitCommands) CommitEditorCmdObj() oscommands.ICmdObj {
return self.cmd.New(fmt.Sprintf("git commit%s%s", self.signoffFlag(), self.verboseFlag()))
cmdStr := NewGitCmd("commit").
ArgIf(self.signoffFlag() != "", self.signoffFlag()).
ArgIf(self.verboseFlag() != "", self.verboseFlag()).
ToString()
return self.cmd.New(cmdStr)
}
func (self *CommitCommands) signoffFlag() string {
if self.UserConfig.Git.Commit.SignOff {
return " --signoff"
return "--signoff"
} else {
return ""
}
@@ -85,9 +109,9 @@ func (self *CommitCommands) signoffFlag() string {
func (self *CommitCommands) verboseFlag() string {
switch self.config.UserConfig.Git.Commit.Verbose {
case "always":
return " --verbose"
return "--verbose"
case "never":
return " --no-verbose"
return "--no-verbose"
default:
return ""
}
@@ -95,19 +119,25 @@ func (self *CommitCommands) verboseFlag() string {
// Get the subject of the HEAD commit
func (self *CommitCommands) GetHeadCommitMessage() (string, error) {
message, err := self.cmd.New("git log -1 --pretty=%s").DontLog().RunWithOutput()
cmdStr := NewGitCmd("log").Arg("-1", "--pretty=%s").ToString()
message, err := self.cmd.New(cmdStr).DontLog().RunWithOutput()
return strings.TrimSpace(message), err
}
func (self *CommitCommands) GetCommitMessage(commitSha string) (string, error) {
cmdStr := "git rev-list --format=%B --max-count=1 " + commitSha
cmdStr := NewGitCmd("rev-list").
Arg("--format=%B", "--max-count=1", commitSha).
ToString()
messageWithHeader, err := self.cmd.New(cmdStr).DontLog().RunWithOutput()
message := strings.Join(strings.SplitAfter(messageWithHeader, "\n")[1:], "")
return strings.TrimSpace(message), err
}
func (self *CommitCommands) GetCommitDiff(commitSha string) (string, error) {
cmdStr := "git show --no-color " + commitSha
cmdStr := NewGitCmd("show").Arg("--no-color", commitSha).ToString()
diff, err := self.cmd.New(cmdStr).DontLog().RunWithOutput()
return diff, err
}
@@ -118,7 +148,10 @@ type Author struct {
}
func (self *CommitCommands) GetCommitAuthor(commitSha string) (Author, error) {
cmdStr := "git show --no-patch --pretty=format:'%an%x00%ae' " + commitSha
cmdStr := NewGitCmd("show").
Arg("--no-patch", "--pretty=format:'%an%x00%ae'", commitSha).
ToString()
output, err := self.cmd.New(cmdStr).DontLog().RunWithOutput()
if err != nil {
return Author{}, err
@@ -138,15 +171,21 @@ func (self *CommitCommands) GetCommitMessageFirstLine(sha string) (string, error
}
func (self *CommitCommands) GetCommitMessagesFirstLine(shas []string) (string, error) {
return self.cmd.New(
fmt.Sprintf("git show --no-patch --pretty=format:%%s %s", strings.Join(shas, " ")),
).DontLog().RunWithOutput()
cmdStr := NewGitCmd("show").
Arg("--no-patch", "--pretty=format:%s").
Arg(shas...).
ToString()
return self.cmd.New(cmdStr).DontLog().RunWithOutput()
}
func (self *CommitCommands) GetCommitsOneline(shas []string) (string, error) {
return self.cmd.New(
fmt.Sprintf("git show --no-patch --oneline %s", strings.Join(shas, " ")),
).DontLog().RunWithOutput()
cmdStr := NewGitCmd("show").
Arg("--no-patch", "--oneline").
Arg(shas...).
ToString()
return self.cmd.New(cmdStr).DontLog().RunWithOutput()
}
// AmendHead amends HEAD with whatever is staged in your working tree
@@ -155,45 +194,76 @@ func (self *CommitCommands) AmendHead() error {
}
func (self *CommitCommands) AmendHeadCmdObj() oscommands.ICmdObj {
return self.cmd.New("git commit --amend --no-edit --allow-empty")
cmdStr := NewGitCmd("commit").
Arg("--amend", "--no-edit", "--allow-empty").
ToString()
return self.cmd.New(cmdStr)
}
func (self *CommitCommands) ShowCmdObj(sha string, filterPath string, ignoreWhitespace bool) oscommands.ICmdObj {
contextSize := self.UserConfig.Git.DiffContextSize
filterPathArg := ""
if filterPath != "" {
filterPathArg = fmt.Sprintf(" -- %s", self.cmd.Quote(filterPath))
}
ignoreWhitespaceArg := ""
if ignoreWhitespace {
ignoreWhitespaceArg = " --ignore-all-space"
}
cmdStr := fmt.Sprintf("git show --submodule --color=%s --unified=%d --stat -p %s%s%s",
self.UserConfig.Git.Paging.ColorArg, contextSize, sha, ignoreWhitespaceArg, filterPathArg)
cmdStr := NewGitCmd("show").
Arg("--submodule").
Arg("--color="+self.UserConfig.Git.Paging.ColorArg).
Arg(fmt.Sprintf("--unified=%d", contextSize)).
Arg("--stat").
Arg("-p").
Arg(sha).
ArgIf(ignoreWhitespace, "--ignore-all-space").
ArgIf(filterPath != "", "--", self.cmd.Quote(filterPath)).
ToString()
return self.cmd.New(cmdStr).DontLog()
}
// Revert reverts the selected commit by sha
func (self *CommitCommands) Revert(sha string) error {
return self.cmd.New(fmt.Sprintf("git revert %s", sha)).Run()
cmdStr := NewGitCmd("revert").Arg(sha).ToString()
return self.cmd.New(cmdStr).Run()
}
func (self *CommitCommands) RevertMerge(sha string, parentNumber int) error {
return self.cmd.New(fmt.Sprintf("git revert %s -m %d", sha, parentNumber)).Run()
cmdStr := NewGitCmd("revert").Arg(sha, "-m", fmt.Sprintf("%d", parentNumber)).
ToString()
return self.cmd.New(cmdStr).Run()
}
// CreateFixupCommit creates a commit that fixes up a previous commit
func (self *CommitCommands) CreateFixupCommit(sha string) error {
return self.cmd.New(fmt.Sprintf("git commit --fixup=%s", sha)).Run()
cmdStr := NewGitCmd("commit").Arg("--fixup=" + sha).ToString()
return self.cmd.New(cmdStr).Run()
}
// a value of 0 means the head commit, 1 is the parent commit, etc
func (self *CommitCommands) GetCommitMessageFromHistory(value int) (string, error) {
hash, _ := self.cmd.New(fmt.Sprintf("git log -1 --skip=%d --pretty=%%H", value)).DontLog().RunWithOutput()
cmdStr := NewGitCmd("log").Arg("-1", fmt.Sprintf("--skip=%d", value), "--pretty=%H").
ToString()
hash, _ := self.cmd.New(cmdStr).DontLog().RunWithOutput()
formattedHash := strings.TrimSpace(hash)
if len(formattedHash) == 0 {
return "", ErrInvalidCommitIndex
}
return self.GetCommitMessage(formattedHash)
}
// Returns hashes of recent commits which changed the given file
// Note: This does not look for the last X commits to change a file, instead
// it looks among the last X commits and see which of them happened to have changed the file.
// This is more efficient.
func (self *CommitCommands) GetRecentCommitsWhichChangedFile(path string) []string {
t := time.Now()
// Checking last X commits. Funnily this seems to actually consider more than the last
// X, perhaps because of topological sorting.
cmdStr := NewGitCmd("log").Arg("HEAD~50..HEAD", "--pretty=%H", "--", self.cmd.Quote(path)).
ToString()
hashes, _ := self.cmd.New(cmdStr).DontLog().RunWithOutput()
self.Log.Warn(fmt.Sprintf("GetRecentCommitsWhichChangedFile took %s", time.Since(t)))
return strings.Split(strings.TrimSpace(hashes), "\n")
}
@@ -1,7 +1,6 @@
package git_commands
import (
"fmt"
"strings"
"github.com/jesseduffield/generics/slices"
@@ -25,12 +24,18 @@ func NewCommitFileLoader(common *common.Common, cmd oscommands.ICmdObjBuilder) *
// GetFilesInDiff get the specified commit files
func (self *CommitFileLoader) GetFilesInDiff(from string, to string, reverse bool) ([]*models.CommitFile, error) {
reverseFlag := ""
if reverse {
reverseFlag = " -R "
}
cmdStr := NewGitCmd("diff").
Arg("--submodule").
Arg("--no-ext-diff").
Arg("--name-status").
Arg("-z").
Arg("--no-renames").
ArgIf(reverse, "-R").
Arg(from).
Arg(to).
ToString()
filenames, err := self.cmd.New(fmt.Sprintf("git diff --submodule --no-ext-diff --name-status -z --no-renames %s %s %s", reverseFlag, from, to)).DontLog().RunWithOutput()
filenames, err := self.cmd.New(cmdStr).DontLog().RunWithOutput()
if err != nil {
return nil, err
}
+79 -78
View File
@@ -15,6 +15,7 @@ import (
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/commands/types/enums"
"github.com/jesseduffield/lazygit/pkg/common"
"github.com/samber/lo"
)
// context:
@@ -28,11 +29,14 @@ type CommitLoader struct {
*common.Common
cmd oscommands.ICmdObjBuilder
getCurrentBranchInfo func() (BranchInfo, error)
getRebaseMode func() (enums.RebaseMode, error)
readFile func(filename string) ([]byte, error)
walkFiles func(root string, fn filepath.WalkFunc) error
dotGitDir string
getRebaseMode func() (enums.RebaseMode, error)
readFile func(filename string) ([]byte, error)
walkFiles func(root string, fn filepath.WalkFunc) error
dotGitDir string
// List of main branches that exist in the repo, quoted for direct use in a git command.
// We use these to obtain the merge base of the branch.
// When nil, we're yet to obtain the list of main branches.
quotedMainBranches *string
}
// making our dependencies explicit for the sake of easier testing
@@ -40,17 +44,16 @@ func NewCommitLoader(
cmn *common.Common,
cmd oscommands.ICmdObjBuilder,
dotGitDir string,
getCurrentBranchInfo func() (BranchInfo, error),
getRebaseMode func() (enums.RebaseMode, error),
) *CommitLoader {
return &CommitLoader{
Common: cmn,
cmd: cmd,
getCurrentBranchInfo: getCurrentBranchInfo,
getRebaseMode: getRebaseMode,
readFile: os.ReadFile,
walkFiles: filepath.Walk,
dotGitDir: dotGitDir,
Common: cmn,
cmd: cmd,
getRebaseMode: getRebaseMode,
readFile: os.ReadFile,
walkFiles: filepath.Walk,
dotGitDir: dotGitDir,
quotedMainBranches: nil,
}
}
@@ -101,10 +104,7 @@ func (self *CommitLoader) GetCommits(opts GetCommitsOptions) ([]*models.Commit,
return commits, nil
}
commits, err = self.setCommitMergedStatuses(opts.RefName, commits)
if err != nil {
return nil, err
}
commits = self.setCommitMergedStatuses(opts.RefName, commits)
return commits, nil
}
@@ -201,12 +201,11 @@ func (self *CommitLoader) getHydratedRebasingCommits(rebaseMode enums.RebaseMode
// note that we're not filtering these as we do non-rebasing commits just because
// I suspect that will cause some damage
cmdObj := self.cmd.New(
fmt.Sprintf(
"git -c log.showSignature=false show %s --no-patch --oneline %s --abbrev=%d",
strings.Join(commitShas, " "),
prettyFormat,
20,
),
NewGitCmd("show").
Config("log.showSignature=false").
Arg("--no-patch", "--oneline", "--abbrev=20", prettyFormat).
Arg(commitShas...).
ToString(),
).DontLog()
fullCommits := map[string]*models.Commit{}
@@ -344,43 +343,63 @@ func (self *CommitLoader) commitFromPatch(content string) *models.Commit {
}
}
func (self *CommitLoader) setCommitMergedStatuses(refName string, commits []*models.Commit) ([]*models.Commit, error) {
ancestor, err := self.getMergeBase(refName)
if err != nil {
return nil, err
}
func (self *CommitLoader) setCommitMergedStatuses(refName string, commits []*models.Commit) []*models.Commit {
ancestor := self.getMergeBase(refName)
if ancestor == "" {
return commits, nil
return commits
}
passedAncestor := false
for i, commit := range commits {
if strings.HasPrefix(ancestor, commit.Sha) {
passedAncestor = true
}
if commit.Status != models.StatusPushed {
if commit.Status != models.StatusPushed && commit.Status != models.StatusUnpushed {
continue
}
if passedAncestor {
commits[i].Status = models.StatusMerged
}
}
return commits, nil
return commits
}
func (self *CommitLoader) getMergeBase(refName string) (string, error) {
info, err := self.getCurrentBranchInfo()
func (self *CommitLoader) getMergeBase(refName string) string {
if self.quotedMainBranches == nil {
self.quotedMainBranches = lo.ToPtr(self.getExistingMainBranches())
}
if *self.quotedMainBranches == "" {
return ""
}
// We pass all configured main branches to the merge-base call; git will
// return the base commit for the closest one.
output, err := self.cmd.New(
NewGitCmd("merge-base").Arg(self.cmd.Quote(refName), *self.quotedMainBranches).
ToString(),
).DontLog().RunWithOutput()
if err != nil {
return "", err
// If there's an error, it must be because one of the main branches that
// used to exist when we called getExistingMainBranches() was deleted
// meanwhile. To fix this for next time, throw away our cache.
self.quotedMainBranches = nil
}
return ignoringWarnings(output)
}
baseBranch := "master"
if strings.HasPrefix(info.RefName, "feature/") {
baseBranch = "develop"
}
// swallowing error because it's not a big deal; probably because there are no commits yet
output, _ := self.cmd.New(fmt.Sprintf("git merge-base %s %s", self.cmd.Quote(refName), self.cmd.Quote(baseBranch))).DontLog().RunWithOutput()
return ignoringWarnings(output), nil
func (self *CommitLoader) getExistingMainBranches() string {
return strings.Join(
lo.FilterMap(self.UserConfig.Git.MainBranches,
func(branchName string, _ int) (string, bool) {
quotedRef := self.cmd.Quote("refs/heads/" + branchName)
if err := self.cmd.New(
NewGitCmd("rev-parse").Arg("--verify", "--quiet", quotedRef).ToString(),
).DontLog().Run(); err != nil {
return "", false
}
return quotedRef, true
}), " ")
}
func ignoringWarnings(commandOutput string) string {
@@ -398,9 +417,10 @@ func ignoringWarnings(commandOutput string) string {
func (self *CommitLoader) getFirstPushedCommit(refName string) (string, error) {
output, err := self.cmd.
New(
fmt.Sprintf("git merge-base %s %s@{u}",
self.cmd.Quote(refName),
self.cmd.Quote(strings.TrimPrefix(refName, "refs/heads/"))),
NewGitCmd("merge-base").
Arg(self.cmd.Quote(refName)).
Arg(self.cmd.Quote(strings.TrimPrefix(refName, "refs/heads/")) + "@{u}").
ToString(),
).
DontLog().
RunWithOutput()
@@ -413,42 +433,23 @@ func (self *CommitLoader) getFirstPushedCommit(refName string) (string, error) {
// getLog gets the git log.
func (self *CommitLoader) getLogCmd(opts GetCommitsOptions) oscommands.ICmdObj {
limitFlag := ""
if opts.Limit {
limitFlag = " -300"
}
followFlag := ""
filterFlag := ""
if opts.FilterPath != "" {
followFlag = " --follow"
filterFlag = fmt.Sprintf(" %s", self.cmd.Quote(opts.FilterPath))
}
config := self.UserConfig.Git.Log
orderFlag := ""
if config.Order != "default" {
orderFlag = " --" + config.Order
}
allFlag := ""
if opts.All {
allFlag = " --all"
}
cmdStr := NewGitCmd("log").
Arg(self.cmd.Quote(opts.RefName)).
ArgIf(config.Order != "default", "--"+config.Order).
ArgIf(opts.All, "--all").
Arg("--oneline").
Arg(prettyFormat).
Arg("--abbrev=40").
ArgIf(opts.Limit, "-300").
ArgIf(opts.FilterPath != "", "--follow").
Arg("--no-show-signature").
Arg("--").
ArgIf(opts.FilterPath != "", self.cmd.Quote(opts.FilterPath)).
ToString()
return self.cmd.New(
fmt.Sprintf(
"git log %s%s%s --oneline %s%s --abbrev=%d%s --no-show-signature --%s",
self.cmd.Quote(opts.RefName),
orderFlag,
allFlag,
prettyFormat,
limitFlag,
40,
followFlag,
filterFlag,
),
).DontLog()
return self.cmd.New(cmdStr).DontLog()
}
const prettyFormat = `--pretty=format:"%H%x00%at%x00%aN%x00%ae%x00%d%x00%p%x00%s"`
+109 -39
View File
@@ -1,6 +1,7 @@
package git_commands
import (
"errors"
"path/filepath"
"strings"
"testing"
@@ -21,25 +22,26 @@ d8084cd558925eb7c9c38afeed5725c21653ab90|1640821426|Jesse Duffield|jessedduffiel
3d4470a6c072208722e5ae9a54bcb9634959a1c5|1640748818|Jesse Duffield|jessedduffield@gmail.com||053a66a7be3da43aacdc|WIP
053a66a7be3da43aacdc7aa78e1fe757b82c4dd2|1640739815|Jesse Duffield|jessedduffield@gmail.com||985fe482e806b172aea4|refactoring the config struct`, "|", "\x00", -1)
var singleCommitOutput = strings.Replace(`0eea75e8c631fba6b58135697835d58ba4c18dbc|1640826609|Jesse Duffield|jessedduffield@gmail.com| (HEAD -> better-tests)|b21997d6b4cbdf84b149|better typing for rebase mode`, "|", "\x00", -1)
func TestGetCommits(t *testing.T) {
type scenario struct {
testName string
runner *oscommands.FakeCmdObjRunner
expectedCommits []*models.Commit
expectedError error
logOrder string
rebaseMode enums.RebaseMode
currentBranchName string
opts GetCommitsOptions
testName string
runner *oscommands.FakeCmdObjRunner
expectedCommits []*models.Commit
expectedError error
logOrder string
rebaseMode enums.RebaseMode
opts GetCommitsOptions
mainBranches []string
}
scenarios := []scenario{
{
testName: "should return no commits if there are none",
logOrder: "topo-order",
rebaseMode: enums.REBASE_MODE_NONE,
currentBranchName: "master",
opts: GetCommitsOptions{RefName: "HEAD", IncludeRebaseCommits: false},
testName: "should return no commits if there are none",
logOrder: "topo-order",
rebaseMode: enums.REBASE_MODE_NONE,
opts: GetCommitsOptions{RefName: "HEAD", IncludeRebaseCommits: false},
runner: oscommands.NewFakeRunner(t).
Expect(`git merge-base "HEAD" "HEAD"@{u}`, "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", nil).
Expect(`git log "HEAD" --topo-order --oneline --pretty=format:"%H%x00%at%x00%aN%x00%ae%x00%d%x00%p%x00%s" --abbrev=40 --no-show-signature --`, "", nil),
@@ -48,11 +50,10 @@ func TestGetCommits(t *testing.T) {
expectedError: nil,
},
{
testName: "should use proper upstream name for branch",
logOrder: "topo-order",
rebaseMode: enums.REBASE_MODE_NONE,
currentBranchName: "mybranch",
opts: GetCommitsOptions{RefName: "refs/heads/mybranch", IncludeRebaseCommits: false},
testName: "should use proper upstream name for branch",
logOrder: "topo-order",
rebaseMode: enums.REBASE_MODE_NONE,
opts: GetCommitsOptions{RefName: "refs/heads/mybranch", IncludeRebaseCommits: false},
runner: oscommands.NewFakeRunner(t).
Expect(`git merge-base "refs/heads/mybranch" "mybranch"@{u}`, "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", nil).
Expect(`git log "refs/heads/mybranch" --topo-order --oneline --pretty=format:"%H%x00%at%x00%aN%x00%ae%x00%d%x00%p%x00%s" --abbrev=40 --no-show-signature --`, "", nil),
@@ -61,18 +62,21 @@ func TestGetCommits(t *testing.T) {
expectedError: nil,
},
{
testName: "should return commits if they are present",
logOrder: "topo-order",
rebaseMode: enums.REBASE_MODE_NONE,
currentBranchName: "master",
opts: GetCommitsOptions{RefName: "HEAD", IncludeRebaseCommits: false},
testName: "should return commits if they are present",
logOrder: "topo-order",
rebaseMode: enums.REBASE_MODE_NONE,
opts: GetCommitsOptions{RefName: "HEAD", IncludeRebaseCommits: false},
mainBranches: []string{"master", "main"},
runner: oscommands.NewFakeRunner(t).
// here it's seeing which commits are yet to be pushed
Expect(`git merge-base "HEAD" "HEAD"@{u}`, "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", nil).
// here it's actually getting all the commits in a formatted form, one per line
Expect(`git log "HEAD" --topo-order --oneline --pretty=format:"%H%x00%at%x00%aN%x00%ae%x00%d%x00%p%x00%s" --abbrev=40 --no-show-signature --`, commitsOutput, nil).
// here it's testing which of the configured main branches exist
Expect(`git rev-parse --verify --quiet "refs/heads/master"`, "", nil). // this one does
Expect(`git rev-parse --verify --quiet "refs/heads/main"`, "", errors.New("error")). // this one doesn't
// here it's seeing where our branch diverged from the master branch so that we can mark that commit and parent commits as 'merged'
Expect(`git merge-base "HEAD" "master"`, "26c07b1ab33860a1a7591a0638f9925ccf497ffa", nil),
Expect(`git merge-base "HEAD" "refs/heads/master"`, "26c07b1ab33860a1a7591a0638f9925ccf497ffa", nil),
expectedCommits: []*models.Commit{
{
@@ -191,11 +195,80 @@ func TestGetCommits(t *testing.T) {
expectedError: nil,
},
{
testName: "should not specify order if `log.order` is `default`",
logOrder: "default",
rebaseMode: enums.REBASE_MODE_NONE,
currentBranchName: "master",
opts: GetCommitsOptions{RefName: "HEAD", IncludeRebaseCommits: false},
testName: "should not call merge-base for mainBranches if none exist",
logOrder: "topo-order",
rebaseMode: enums.REBASE_MODE_NONE,
opts: GetCommitsOptions{RefName: "HEAD", IncludeRebaseCommits: false},
mainBranches: []string{"master", "main"},
runner: oscommands.NewFakeRunner(t).
// here it's seeing which commits are yet to be pushed
Expect(`git merge-base "HEAD" "HEAD"@{u}`, "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", nil).
// here it's actually getting all the commits in a formatted form, one per line
Expect(`git log "HEAD" --topo-order --oneline --pretty=format:"%H%x00%at%x00%aN%x00%ae%x00%d%x00%p%x00%s" --abbrev=40 --no-show-signature --`, singleCommitOutput, nil).
// here it's testing which of the configured main branches exist; neither does
Expect(`git rev-parse --verify --quiet "refs/heads/master"`, "", errors.New("error")).
Expect(`git rev-parse --verify --quiet "refs/heads/main"`, "", errors.New("error")),
expectedCommits: []*models.Commit{
{
Sha: "0eea75e8c631fba6b58135697835d58ba4c18dbc",
Name: "better typing for rebase mode",
Status: models.StatusUnpushed,
Action: models.ActionNone,
Tags: []string{},
ExtraInfo: "(HEAD -> better-tests)",
AuthorName: "Jesse Duffield",
AuthorEmail: "jessedduffield@gmail.com",
UnixTimestamp: 1640826609,
Parents: []string{
"b21997d6b4cbdf84b149",
},
},
},
expectedError: nil,
},
{
testName: "should call merge-base for all main branches that exist",
logOrder: "topo-order",
rebaseMode: enums.REBASE_MODE_NONE,
opts: GetCommitsOptions{RefName: "HEAD", IncludeRebaseCommits: false},
mainBranches: []string{"master", "main", "develop", "1.0-hotfixes"},
runner: oscommands.NewFakeRunner(t).
// here it's seeing which commits are yet to be pushed
Expect(`git merge-base "HEAD" "HEAD"@{u}`, "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", nil).
// here it's actually getting all the commits in a formatted form, one per line
Expect(`git log "HEAD" --topo-order --oneline --pretty=format:"%H%x00%at%x00%aN%x00%ae%x00%d%x00%p%x00%s" --abbrev=40 --no-show-signature --`, singleCommitOutput, nil).
// here it's testing which of the configured main branches exist
Expect(`git rev-parse --verify --quiet "refs/heads/master"`, "", nil).
Expect(`git rev-parse --verify --quiet "refs/heads/main"`, "", errors.New("error")).
Expect(`git rev-parse --verify --quiet "refs/heads/develop"`, "", nil).
Expect(`git rev-parse --verify --quiet "refs/heads/1.0-hotfixes"`, "", nil).
// here it's seeing where our branch diverged from the master branch so that we can mark that commit and parent commits as 'merged'
Expect(`git merge-base "HEAD" "refs/heads/master" "refs/heads/develop" "refs/heads/1.0-hotfixes"`, "26c07b1ab33860a1a7591a0638f9925ccf497ffa", nil),
expectedCommits: []*models.Commit{
{
Sha: "0eea75e8c631fba6b58135697835d58ba4c18dbc",
Name: "better typing for rebase mode",
Status: models.StatusUnpushed,
Action: models.ActionNone,
Tags: []string{},
ExtraInfo: "(HEAD -> better-tests)",
AuthorName: "Jesse Duffield",
AuthorEmail: "jessedduffield@gmail.com",
UnixTimestamp: 1640826609,
Parents: []string{
"b21997d6b4cbdf84b149",
},
},
},
expectedError: nil,
},
{
testName: "should not specify order if `log.order` is `default`",
logOrder: "default",
rebaseMode: enums.REBASE_MODE_NONE,
opts: GetCommitsOptions{RefName: "HEAD", IncludeRebaseCommits: false},
runner: oscommands.NewFakeRunner(t).
Expect(`git merge-base "HEAD" "HEAD"@{u}`, "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", nil).
Expect(`git log "HEAD" --oneline --pretty=format:"%H%x00%at%x00%aN%x00%ae%x00%d%x00%p%x00%s" --abbrev=40 --no-show-signature --`, "", nil),
@@ -204,11 +277,10 @@ func TestGetCommits(t *testing.T) {
expectedError: nil,
},
{
testName: "should set filter path",
logOrder: "default",
rebaseMode: enums.REBASE_MODE_NONE,
currentBranchName: "master",
opts: GetCommitsOptions{RefName: "HEAD", FilterPath: "src"},
testName: "should set filter path",
logOrder: "default",
rebaseMode: enums.REBASE_MODE_NONE,
opts: GetCommitsOptions{RefName: "HEAD", FilterPath: "src"},
runner: oscommands.NewFakeRunner(t).
Expect(`git merge-base "HEAD" "HEAD"@{u}`, "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", nil).
Expect(`git log "HEAD" --oneline --pretty=format:"%H%x00%at%x00%aN%x00%ae%x00%d%x00%p%x00%s" --abbrev=40 --follow --no-show-signature -- "src"`, "", nil),
@@ -225,11 +297,8 @@ func TestGetCommits(t *testing.T) {
common.UserConfig.Git.Log.Order = scenario.logOrder
builder := &CommitLoader{
Common: common,
cmd: oscommands.NewDummyCmdObjBuilder(scenario.runner),
getCurrentBranchInfo: func() (BranchInfo, error) {
return BranchInfo{RefName: scenario.currentBranchName, DisplayName: scenario.currentBranchName, DetachedHead: false}, nil
},
Common: common,
cmd: oscommands.NewDummyCmdObjBuilder(scenario.runner),
getRebaseMode: func() (enums.RebaseMode, error) { return scenario.rebaseMode, nil },
dotGitDir: ".git",
readFile: func(filename string) ([]byte, error) {
@@ -240,6 +309,7 @@ func TestGetCommits(t *testing.T) {
},
}
common.UserConfig.Git.MainBranches = scenario.mainBranches
commits, err := builder.GetCommits(scenario.opts)
assert.Equal(t, scenario.expectedCommits, commits)
+27
View File
@@ -7,6 +7,7 @@ import (
gogit "github.com/jesseduffield/go-git/v5"
"github.com/jesseduffield/lazygit/pkg/commands/git_config"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/commands/patch"
"github.com/jesseduffield/lazygit/pkg/common"
"github.com/jesseduffield/lazygit/pkg/config"
"github.com/jesseduffield/lazygit/pkg/utils"
@@ -117,6 +118,26 @@ func buildWorkingTreeCommands(deps commonDeps) *WorkingTreeCommands {
return NewWorkingTreeCommands(gitCommon, submoduleCommands, fileLoader)
}
func buildPatchCommands(deps commonDeps) *PatchCommands {
gitCommon := buildGitCommon(deps)
rebaseCommands := buildRebaseCommands(deps)
commitCommands := buildCommitCommands(deps)
statusCommands := buildStatusCommands(deps)
stashCommands := buildStashCommands(deps)
loadFileFn := func(from string, to string, reverse bool, filename string, plain bool) (string, error) {
return "", nil
}
patchBuilder := patch.NewPatchBuilder(gitCommon.Log, loadFileFn)
return NewPatchCommands(gitCommon, rebaseCommands, commitCommands, statusCommands, stashCommands, patchBuilder)
}
func buildStatusCommands(deps commonDeps) *StatusCommands {
gitCommon := buildGitCommon(deps)
return NewStatusCommands(gitCommon)
}
func buildStashCommands(deps commonDeps) *StashCommands {
gitCommon := buildGitCommon(deps)
fileLoader := buildFileLoader(gitCommon)
@@ -150,3 +171,9 @@ func buildBranchCommands(deps commonDeps) *BranchCommands {
return NewBranchCommands(gitCommon)
}
func buildFlowCommands(deps commonDeps) *FlowCommands {
gitCommon := buildGitCommon(deps)
return NewFlowCommands(gitCommon)
}
+9 -7
View File
@@ -42,7 +42,7 @@ func (self *FileLoader) GetStatusFiles(opts GetStatusFileOptions) []*models.File
}
untrackedFilesArg := fmt.Sprintf("--untracked-files=%s", untrackedFilesSetting)
statuses, err := self.GitStatus(GitStatusOptions{NoRenames: opts.NoRenames, UntrackedFilesArg: untrackedFilesArg})
statuses, err := self.gitStatus(GitStatusOptions{NoRenames: opts.NoRenames, UntrackedFilesArg: untrackedFilesArg})
if err != nil {
self.Log.Error(err)
}
@@ -81,13 +81,15 @@ type FileStatus struct {
PreviousName string
}
func (c *FileLoader) GitStatus(opts GitStatusOptions) ([]FileStatus, error) {
noRenamesFlag := ""
if opts.NoRenames {
noRenamesFlag = " --no-renames"
}
func (c *FileLoader) gitStatus(opts GitStatusOptions) ([]FileStatus, error) {
cmdStr := NewGitCmd("status").
Arg(opts.UntrackedFilesArg).
Arg("--porcelain").
Arg("-z").
ArgIf(opts.NoRenames, "--no-renames").
ToString()
statusLines, _, err := c.cmd.New(fmt.Sprintf("git status %s --porcelain -z%s", opts.UntrackedFilesArg, noRenamesFlag)).DontLog().RunWithOutputs()
statusLines, _, err := c.cmd.New(cmdStr).DontLog().RunWithOutputs()
if err != nil {
return []FileStatus{}, err
}
+7 -2
View File
@@ -34,6 +34,7 @@ func (self *FlowCommands) FinishCmdObj(branchName string) (oscommands.ICmdObj, e
branchType := ""
for _, line := range strings.Split(strings.TrimSpace(prefixes), "\n") {
if strings.HasPrefix(line, "gitflow.prefix.") && strings.HasSuffix(line, prefix) {
regex := regexp.MustCompile("gitflow.prefix.([^ ]*) .*")
matches := regex.FindAllStringSubmatch(line, 1)
@@ -48,9 +49,13 @@ func (self *FlowCommands) FinishCmdObj(branchName string) (oscommands.ICmdObj, e
return nil, errors.New(self.Tr.NotAGitFlowBranch)
}
return self.cmd.New("git flow " + branchType + " finish " + suffix), nil
cmdStr := NewGitCmd("flow").Arg(branchType, "finish", suffix).ToString()
return self.cmd.New(cmdStr), nil
}
func (self *FlowCommands) StartCmdObj(branchType string, name string) oscommands.ICmdObj {
return self.cmd.New("git flow " + branchType + " start " + name)
cmdStr := NewGitCmd("flow").Arg(branchType, "start", name).ToString()
return self.cmd.New(cmdStr)
}
+92
View File
@@ -0,0 +1,92 @@
package git_commands
import (
"testing"
"github.com/jesseduffield/lazygit/pkg/commands/git_config"
"github.com/stretchr/testify/assert"
)
func TestStartCmdObj(t *testing.T) {
scenarios := []struct {
testName string
branchType string
name string
expected string
}{
{
testName: "basic",
branchType: "feature",
name: "test",
expected: "git flow feature start test",
},
}
for _, s := range scenarios {
s := s
t.Run(s.testName, func(t *testing.T) {
instance := buildFlowCommands(commonDeps{})
assert.Equal(t,
instance.StartCmdObj(s.branchType, s.name).ToString(),
s.expected,
)
})
}
}
func TestFinishCmdObj(t *testing.T) {
scenarios := []struct {
testName string
branchName string
expected string
expectedError string
gitConfigMockResponses map[string]string
}{
{
testName: "not a git flow branch",
branchName: "mybranch",
expected: "",
expectedError: "This does not seem to be a git flow branch",
gitConfigMockResponses: nil,
},
{
testName: "feature branch without config",
branchName: "feature/mybranch",
expected: "",
expectedError: "This does not seem to be a git flow branch",
gitConfigMockResponses: nil,
},
{
testName: "feature branch with config",
branchName: "feature/mybranch",
expected: "git flow feature finish mybranch",
expectedError: "",
gitConfigMockResponses: map[string]string{
"--local --get-regexp gitflow.prefix": "gitflow.prefix.feature feature/",
},
},
}
for _, s := range scenarios {
s := s
t.Run(s.testName, func(t *testing.T) {
instance := buildFlowCommands(commonDeps{
gitConfig: git_config.NewFakeGitConfig(s.gitConfigMockResponses),
})
cmd, err := instance.FinishCmdObj(s.branchName)
if s.expectedError != "" {
if err == nil {
t.Errorf("Expected error, got nil")
} else {
assert.Equal(t, err.Error(), s.expectedError)
}
} else {
assert.NoError(t, err)
assert.Equal(t, cmd.ToString(), s.expected)
}
})
}
}
@@ -0,0 +1,54 @@
package git_commands
import "strings"
// convenience struct for building git commands. Especially useful when
// including conditional args
type GitCommandBuilder struct {
// command string
args []string
}
func NewGitCmd(command string) *GitCommandBuilder {
return &GitCommandBuilder{args: []string{command}}
}
func (self *GitCommandBuilder) Arg(args ...string) *GitCommandBuilder {
self.args = append(self.args, args...)
return self
}
func (self *GitCommandBuilder) ArgIf(condition bool, ifTrue ...string) *GitCommandBuilder {
if condition {
self.Arg(ifTrue...)
}
return self
}
func (self *GitCommandBuilder) ArgIfElse(condition bool, ifTrue string, ifFalse string) *GitCommandBuilder {
if condition {
return self.Arg(ifTrue)
} else {
return self.Arg(ifFalse)
}
}
func (self *GitCommandBuilder) Config(value string) *GitCommandBuilder {
// config settings come before the command
self.args = append([]string{"-c", value}, self.args...)
return self
}
func (self *GitCommandBuilder) RepoPath(value string) *GitCommandBuilder {
// repo path comes before the command
self.args = append([]string{"-C", value}, self.args...)
return self
}
func (self *GitCommandBuilder) ToString() string {
return "git " + strings.Join(self.args, " ")
}
@@ -0,0 +1,56 @@
package git_commands
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestGitCommandBuilder(t *testing.T) {
scenarios := []struct {
input string
expected string
}{
{
input: NewGitCmd("push").
Arg("--force-with-lease").
Arg("--set-upstream").
Arg("origin").
Arg("master").
ToString(),
expected: "git push --force-with-lease --set-upstream origin master",
},
{
input: NewGitCmd("push").ArgIf(true, "--test").ToString(),
expected: "git push --test",
},
{
input: NewGitCmd("push").ArgIf(false, "--test").ToString(),
expected: "git push",
},
{
input: NewGitCmd("push").ArgIfElse(true, "-b", "-a").ToString(),
expected: "git push -b",
},
{
input: NewGitCmd("push").ArgIfElse(false, "-a", "-b").ToString(),
expected: "git push -b",
},
{
input: NewGitCmd("push").Arg("-a", "-b").ToString(),
expected: "git push -a -b",
},
{
input: NewGitCmd("push").Config("user.name=foo").Config("user.email=bar").ToString(),
expected: "git -c user.email=bar -c user.name=foo push",
},
{
input: NewGitCmd("push").RepoPath("a/b/c").ToString(),
expected: "git -C a/b/c push",
},
}
for _, s := range scenarios {
assert.Equal(t, s.input, s.expected)
}
}
+61 -9
View File
@@ -2,6 +2,8 @@ package git_commands
import (
"fmt"
"path/filepath"
"time"
"github.com/fsmiamoto/git-todo-parser/todo"
"github.com/go-errors/errors"
@@ -9,6 +11,7 @@ import (
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/commands/patch"
"github.com/jesseduffield/lazygit/pkg/commands/types/enums"
"github.com/jesseduffield/lazygit/pkg/utils"
)
type PatchCommands struct {
@@ -39,6 +42,53 @@ func NewPatchCommands(
}
}
type ApplyPatchOpts struct {
ThreeWay bool
Cached bool
Index bool
Reverse bool
}
func (self *PatchCommands) ApplyCustomPatch(reverse bool) error {
patch := self.PatchBuilder.PatchToApply(reverse)
return self.ApplyPatch(patch, ApplyPatchOpts{
Index: true,
ThreeWay: true,
Reverse: reverse,
})
}
func (self *PatchCommands) ApplyPatch(patch string, opts ApplyPatchOpts) error {
filepath, err := self.SaveTemporaryPatch(patch)
if err != nil {
return err
}
return self.applyPatchFile(filepath, opts)
}
func (self *PatchCommands) applyPatchFile(filepath string, opts ApplyPatchOpts) error {
cmdStr := NewGitCmd("apply").
ArgIf(opts.ThreeWay, "--3way").
ArgIf(opts.Cached, "--cached").
ArgIf(opts.Index, "--index").
ArgIf(opts.Reverse, "--reverse").
Arg(self.cmd.Quote(filepath)).
ToString()
return self.cmd.New(cmdStr).Run()
}
func (self *PatchCommands) SaveTemporaryPatch(patch string) (string, error) {
filepath := filepath.Join(self.os.GetTempDir(), utils.GetCurrentRepoName(), time.Now().Format("Jan _2 15.04.05.000000000")+".patch")
self.Log.Infof("saving temporary patch to %s", filepath)
if err := self.os.CreateFileWithContent(filepath, patch); err != nil {
return "", err
}
return filepath, nil
}
// DeletePatchesFromCommit applies a patch in reverse for a commit
func (self *PatchCommands) DeletePatchesFromCommit(commits []*models.Commit, commitIndex int) error {
if err := self.rebase.BeginInteractiveRebaseForCommit(commits, commitIndex); err != nil {
@@ -46,7 +96,7 @@ func (self *PatchCommands) DeletePatchesFromCommit(commits []*models.Commit, com
}
// apply each patch in reverse
if err := self.PatchBuilder.ApplyPatches(true); err != nil {
if err := self.ApplyCustomPatch(true); err != nil {
_ = self.rebase.AbortRebase()
return err
}
@@ -72,7 +122,7 @@ func (self *PatchCommands) MovePatchToSelectedCommit(commits []*models.Commit, s
}
// apply each patch forward
if err := self.PatchBuilder.ApplyPatches(false); err != nil {
if err := self.ApplyCustomPatch(false); err != nil {
// Don't abort the rebase here; this might cause conflicts, so give
// the user a chance to resolve them
return err
@@ -121,7 +171,7 @@ func (self *PatchCommands) MovePatchToSelectedCommit(commits []*models.Commit, s
}
// apply each patch in reverse
if err := self.PatchBuilder.ApplyPatches(true); err != nil {
if err := self.ApplyCustomPatch(true); err != nil {
_ = self.rebase.AbortRebase()
return err
}
@@ -144,7 +194,7 @@ func (self *PatchCommands) MovePatchToSelectedCommit(commits []*models.Commit, s
self.rebase.onSuccessfulContinue = func() error {
// now we should be up to the destination, so let's apply forward these patches to that.
// ideally we would ensure we're on the right commit but I'm not sure if that check is necessary
if err := self.rebase.workingTree.ApplyPatch(patch, "index", "3way"); err != nil {
if err := self.ApplyPatch(patch, ApplyPatchOpts{Index: true, ThreeWay: true}); err != nil {
// Don't abort the rebase here; this might cause conflicts, so give
// the user a chance to resolve them
return err
@@ -177,7 +227,7 @@ func (self *PatchCommands) MovePatchIntoIndex(commits []*models.Commit, commitId
return err
}
if err := self.PatchBuilder.ApplyPatches(true); err != nil {
if err := self.ApplyCustomPatch(true); err != nil {
if self.status.WorkingTreeState() == enums.REBASE_MODE_REBASING {
_ = self.rebase.AbortRebase()
}
@@ -201,7 +251,7 @@ func (self *PatchCommands) MovePatchIntoIndex(commits []*models.Commit, commitId
self.rebase.onSuccessfulContinue = func() error {
// add patches to index
if err := self.rebase.workingTree.ApplyPatch(patch, "index", "3way"); err != nil {
if err := self.ApplyPatch(patch, ApplyPatchOpts{Index: true, ThreeWay: true}); err != nil {
if self.status.WorkingTreeState() == enums.REBASE_MODE_REBASING {
_ = self.rebase.AbortRebase()
}
@@ -226,7 +276,7 @@ func (self *PatchCommands) PullPatchIntoNewCommit(commits []*models.Commit, comm
return err
}
if err := self.PatchBuilder.ApplyPatches(true); err != nil {
if err := self.ApplyCustomPatch(true); err != nil {
_ = self.rebase.AbortRebase()
return err
}
@@ -242,7 +292,7 @@ func (self *PatchCommands) PullPatchIntoNewCommit(commits []*models.Commit, comm
return err
}
if err := self.rebase.workingTree.ApplyPatch(patch, "index", "3way"); err != nil {
if err := self.ApplyPatch(patch, ApplyPatchOpts{Index: true, ThreeWay: true}); err != nil {
_ = self.rebase.AbortRebase()
return err
}
@@ -267,5 +317,7 @@ func (self *PatchCommands) PullPatchIntoNewCommit(commits []*models.Commit, comm
// only some lines of a range of adjacent added lines. To solve this, we
// get the diff of HEAD and the original commit and then apply that.
func (self *PatchCommands) diffHeadAgainstCommit(commit *models.Commit) (string, error) {
return self.cmd.New(fmt.Sprintf("git diff HEAD..%s", commit.Sha)).RunWithOutput()
cmdStr := NewGitCmd("diff").Arg("HEAD.." + commit.Sha).ToString()
return self.cmd.New(cmdStr).RunWithOutput()
}
+66
View File
@@ -0,0 +1,66 @@
package git_commands
import (
"fmt"
"os"
"regexp"
"testing"
"github.com/go-errors/errors"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/stretchr/testify/assert"
)
func TestWorkingTreeApplyPatch(t *testing.T) {
type scenario struct {
testName string
runner *oscommands.FakeCmdObjRunner
test func(error)
}
expectFn := func(regexStr string, errToReturn error) func(cmdObj oscommands.ICmdObj) (string, error) {
return func(cmdObj oscommands.ICmdObj) (string, error) {
re := regexp.MustCompile(regexStr)
cmdStr := cmdObj.ToString()
matches := re.FindStringSubmatch(cmdStr)
assert.Equal(t, 2, len(matches), fmt.Sprintf("unexpected command: %s", cmdStr))
filename := matches[1]
content, err := os.ReadFile(filename)
assert.NoError(t, err)
assert.Equal(t, "test", string(content))
return "", errToReturn
}
}
scenarios := []scenario{
{
testName: "valid case",
runner: oscommands.NewFakeRunner(t).
ExpectFunc(expectFn(`git apply --cached "(.*)"`, nil)),
test: func(err error) {
assert.NoError(t, err)
},
},
{
testName: "command returns error",
runner: oscommands.NewFakeRunner(t).
ExpectFunc(expectFn(`git apply --cached "(.*)"`, errors.New("error"))),
test: func(err error) {
assert.Error(t, err)
},
},
}
for _, s := range scenarios {
s := s
t.Run(s.testName, func(t *testing.T) {
instance := buildPatchCommands(commonDeps{runner: s.runner})
s.test(instance.ApplyPatch("test", ApplyPatchOpts{Cached: true}))
s.runner.CheckForMissingCalls()
})
}
}
+23 -17
View File
@@ -176,17 +176,21 @@ type PrepareInteractiveRebaseCommandOpts struct {
func (self *RebaseCommands) PrepareInteractiveRebaseCommand(opts PrepareInteractiveRebaseCommandOpts) oscommands.ICmdObj {
ex := oscommands.GetLazygitPath()
cmdStr := NewGitCmd("rebase").
Arg("--interactive").
Arg("--autostash").
Arg("--keep-empty").
ArgIf(!self.version.IsOlderThan(2, 26, 0), "--empty=keep").
Arg("--no-autosquash").
ArgIf(!self.version.IsOlderThan(2, 22, 0), "--rebase-merges").
Arg(opts.baseShaOrRoot).
ToString()
debug := "FALSE"
if self.Debug {
debug = "TRUE"
}
rebaseMergesArg := " --rebase-merges"
if self.version.IsOlderThan(2, 22, 0) {
rebaseMergesArg = ""
}
cmdStr := fmt.Sprintf("git rebase --interactive --autostash --keep-empty --empty=keep --no-autosquash%s %s",
rebaseMergesArg, opts.baseShaOrRoot)
self.Log.WithField("command", cmdStr).Debug("RunCommand")
cmdObj := self.cmd.New(cmdStr)
@@ -222,7 +226,8 @@ func (self *RebaseCommands) AmendTo(commits []*models.Commit, commitIndex int) e
}
// Get the sha of the commit we just created
fixupSha, err := self.cmd.New("git rev-parse --verify HEAD").RunWithOutput()
cmdStr := NewGitCmd("rev-parse").Arg("--verify", "HEAD").ToString()
fixupSha, err := self.cmd.New(cmdStr).RunWithOutput()
if err != nil {
return err
}
@@ -259,14 +264,11 @@ func (self *RebaseCommands) SquashAllAboveFixupCommits(commit *models.Commit) er
shaOrRoot = "--root"
}
return self.runSkipEditorCommand(
self.cmd.New(
fmt.Sprintf(
"git rebase --interactive --rebase-merges --autostash --autosquash %s",
shaOrRoot,
),
),
)
cmdStr := NewGitCmd("rebase").
Arg("--interactive", "--rebase-merges", "--autostash", "--autosquash", shaOrRoot).
ToString()
return self.runSkipEditorCommand(self.cmd.New(cmdStr))
}
// BeginInteractiveRebaseForCommit starts an interactive rebase to edit the current
@@ -302,7 +304,9 @@ func (self *RebaseCommands) RebaseBranch(branchName string) error {
}
func (self *RebaseCommands) GenericMergeOrRebaseActionCmdObj(commandType string, command string) oscommands.ICmdObj {
return self.cmd.New("git " + commandType + " --" + command)
cmdStr := NewGitCmd(commandType).Arg("--" + command).ToString()
return self.cmd.New(cmdStr)
}
func (self *RebaseCommands) ContinueRebase() error {
@@ -359,7 +363,9 @@ func (self *RebaseCommands) DiscardOldFileChanges(commits []*models.Commit, comm
}
// check if file exists in previous commit (this command returns an error if the file doesn't exist)
if err := self.cmd.New("git cat-file -e HEAD^:" + self.cmd.Quote(fileName)).Run(); err != nil {
cmdStr := NewGitCmd("cat-file").Arg("-e", "HEAD^:"+self.cmd.Quote(fileName)).ToString()
if err := self.cmd.New(cmdStr).Run(); err != nil {
if err := self.os.Remove(fileName); err != nil {
return err
}
+38 -14
View File
@@ -16,37 +16,60 @@ import (
func TestRebaseRebaseBranch(t *testing.T) {
type scenario struct {
testName string
arg string
runner *oscommands.FakeCmdObjRunner
test func(error)
testName string
arg string
gitVersion *GitVersion
runner *oscommands.FakeCmdObjRunner
test func(error)
}
scenarios := []scenario{
{
testName: "successful rebase",
arg: "master",
testName: "successful rebase",
arg: "master",
gitVersion: &GitVersion{2, 26, 0, ""},
runner: oscommands.NewFakeRunner(t).
Expect(`git rebase --interactive --autostash --keep-empty --empty=keep --no-autosquash master`, "", nil),
Expect(`git rebase --interactive --autostash --keep-empty --empty=keep --no-autosquash --rebase-merges master`, "", nil),
test: func(err error) {
assert.NoError(t, err)
},
},
{
testName: "unsuccessful rebase",
arg: "master",
testName: "unsuccessful rebase",
arg: "master",
gitVersion: &GitVersion{2, 26, 0, ""},
runner: oscommands.NewFakeRunner(t).
Expect(`git rebase --interactive --autostash --keep-empty --empty=keep --no-autosquash master`, "", errors.New("error")),
Expect(`git rebase --interactive --autostash --keep-empty --empty=keep --no-autosquash --rebase-merges master`, "", errors.New("error")),
test: func(err error) {
assert.Error(t, err)
},
},
{
testName: "successful rebase (< 2.26.0)",
arg: "master",
gitVersion: &GitVersion{2, 25, 5, ""},
runner: oscommands.NewFakeRunner(t).
Expect(`git rebase --interactive --autostash --keep-empty --no-autosquash --rebase-merges master`, "", nil),
test: func(err error) {
assert.NoError(t, err)
},
},
{
testName: "successful rebase (< 2.22.0)",
arg: "master",
gitVersion: &GitVersion{2, 21, 9, ""},
runner: oscommands.NewFakeRunner(t).
Expect(`git rebase --interactive --autostash --keep-empty --no-autosquash master`, "", nil),
test: func(err error) {
assert.NoError(t, err)
},
},
}
for _, s := range scenarios {
s := s
t.Run(s.testName, func(t *testing.T) {
instance := buildRebaseCommands(commonDeps{runner: s.runner})
instance := buildRebaseCommands(commonDeps{runner: s.runner, gitVersion: s.gitVersion})
s.test(instance.RebaseBranch(s.arg))
})
}
@@ -126,7 +149,7 @@ func TestRebaseDiscardOldFileChanges(t *testing.T) {
commitIndex: 0,
fileName: "test999.txt",
runner: oscommands.NewFakeRunner(t).
Expect(`git rebase --interactive --autostash --keep-empty --empty=keep --no-autosquash abcdef`, "", nil).
Expect(`git rebase --interactive --autostash --keep-empty --empty=keep --no-autosquash --rebase-merges abcdef`, "", nil).
Expect(`git cat-file -e HEAD^:"test999.txt"`, "", nil).
Expect(`git checkout HEAD^ -- "test999.txt"`, "", nil).
Expect(`git commit --amend --no-edit --allow-empty`, "", nil).
@@ -143,8 +166,9 @@ func TestRebaseDiscardOldFileChanges(t *testing.T) {
s := s
t.Run(s.testName, func(t *testing.T) {
instance := buildRebaseCommands(commonDeps{
runner: s.runner,
gitConfig: git_config.NewFakeGitConfig(s.gitConfigMockResponses),
runner: s.runner,
gitVersion: &GitVersion{2, 26, 0, ""},
gitConfig: git_config.NewFakeGitConfig(s.gitConfigMockResponses),
})
s.test(instance.DiscardOldFileChanges(s.commits, s.commitIndex, s.fileName))
+30 -22
View File
@@ -15,44 +15,52 @@ func NewRemoteCommands(gitCommon *GitCommon) *RemoteCommands {
}
func (self *RemoteCommands) AddRemote(name string, url string) error {
return self.cmd.
New(fmt.Sprintf("git remote add %s %s", self.cmd.Quote(name), self.cmd.Quote(url))).
Run()
cmdStr := NewGitCmd("remote").
Arg("add", self.cmd.Quote(name), self.cmd.Quote(url)).
ToString()
return self.cmd.New(cmdStr).Run()
}
func (self *RemoteCommands) RemoveRemote(name string) error {
return self.cmd.
New(fmt.Sprintf("git remote remove %s", self.cmd.Quote(name))).
Run()
cmdStr := NewGitCmd("remote").
Arg("remove", self.cmd.Quote(name)).
ToString()
return self.cmd.New(cmdStr).Run()
}
func (self *RemoteCommands) RenameRemote(oldRemoteName string, newRemoteName string) error {
return self.cmd.
New(fmt.Sprintf("git remote rename %s %s", self.cmd.Quote(oldRemoteName), self.cmd.Quote(newRemoteName))).
Run()
cmdStr := NewGitCmd("remote").
Arg("rename", self.cmd.Quote(oldRemoteName), self.cmd.Quote(newRemoteName)).
ToString()
return self.cmd.New(cmdStr).Run()
}
func (self *RemoteCommands) UpdateRemoteUrl(remoteName string, updatedUrl string) error {
return self.cmd.
New(fmt.Sprintf("git remote set-url %s %s", self.cmd.Quote(remoteName), self.cmd.Quote(updatedUrl))).
Run()
cmdStr := NewGitCmd("remote").
Arg("set-url", self.cmd.Quote(remoteName), self.cmd.Quote(updatedUrl)).
ToString()
return self.cmd.New(cmdStr).Run()
}
func (self *RemoteCommands) DeleteRemoteBranch(remoteName string, branchName string) error {
command := fmt.Sprintf("git push %s --delete %s", self.cmd.Quote(remoteName), self.cmd.Quote(branchName))
return self.cmd.New(command).PromptOnCredentialRequest().WithMutex(self.syncMutex).Run()
cmdStr := NewGitCmd("push").
Arg(self.cmd.Quote(remoteName), "--delete", self.cmd.Quote(branchName)).
ToString()
return self.cmd.New(cmdStr).PromptOnCredentialRequest().WithMutex(self.syncMutex).Run()
}
// CheckRemoteBranchExists Returns remote branch
func (self *RemoteCommands) CheckRemoteBranchExists(branchName string) bool {
_, err := self.cmd.
New(
fmt.Sprintf("git show-ref --verify -- refs/remotes/origin/%s",
self.cmd.Quote(branchName),
),
).
DontLog().
RunWithOutput()
cmdStr := NewGitCmd("show-ref").
Arg("--verify", "--", fmt.Sprintf("refs/remotes/origin/%s", self.cmd.Quote(branchName))).
ToString()
_, err := self.cmd.New(cmdStr).DontLog().RunWithOutput()
return err == nil
}
+2 -1
View File
@@ -31,7 +31,8 @@ func NewRemoteLoader(
}
func (self *RemoteLoader) GetRemotes() ([]*models.Remote, error) {
remoteBranchesStr, err := self.cmd.New("git branch -r").DontLog().RunWithOutput()
cmdStr := NewGitCmd("branch").Arg("-r").ToString()
remoteBranchesStr, err := self.cmd.New(cmdStr).DontLog().RunWithOutput()
if err != nil {
return nil, err
}
+69 -20
View File
@@ -26,57 +26,94 @@ func NewStashCommands(
}
func (self *StashCommands) DropNewest() error {
return self.cmd.New("git stash drop").Run()
cmdStr := NewGitCmd("stash").Arg("drop").ToString()
return self.cmd.New(cmdStr).Run()
}
func (self *StashCommands) Drop(index int) error {
return self.cmd.New(fmt.Sprintf("git stash drop stash@{%d}", index)).Run()
cmdStr := NewGitCmd("stash").Arg("drop", fmt.Sprintf("stash@{%d}", index)).
ToString()
return self.cmd.New(cmdStr).Run()
}
func (self *StashCommands) Pop(index int) error {
return self.cmd.New(fmt.Sprintf("git stash pop stash@{%d}", index)).Run()
cmdStr := NewGitCmd("stash").Arg("pop", fmt.Sprintf("stash@{%d}", index)).
ToString()
return self.cmd.New(cmdStr).Run()
}
func (self *StashCommands) Apply(index int) error {
return self.cmd.New(fmt.Sprintf("git stash apply stash@{%d}", index)).Run()
cmdStr := NewGitCmd("stash").Arg("apply", fmt.Sprintf("stash@{%d}", index)).
ToString()
return self.cmd.New(cmdStr).Run()
}
// Save save stash
func (self *StashCommands) Save(message string) error {
return self.cmd.New("git stash save " + self.cmd.Quote(message)).Run()
cmdStr := NewGitCmd("stash").Arg("save", self.cmd.Quote(message)).
ToString()
return self.cmd.New(cmdStr).Run()
}
func (self *StashCommands) Store(sha string, message string) error {
trimmedMessage := strings.Trim(message, " \t")
if len(trimmedMessage) > 0 {
return self.cmd.New(fmt.Sprintf("git stash store %s -m %s", self.cmd.Quote(sha), self.cmd.Quote(trimmedMessage))).Run()
}
return self.cmd.New(fmt.Sprintf("git stash store %s", self.cmd.Quote(sha))).Run()
cmdStr := NewGitCmd("stash").Arg("store", self.cmd.Quote(sha)).
ArgIf(trimmedMessage != "", "-m", self.cmd.Quote(trimmedMessage)).
ToString()
return self.cmd.New(cmdStr).Run()
}
func (self *StashCommands) Sha(index int) (string, error) {
sha, _, err := self.cmd.New(fmt.Sprintf("git rev-parse refs/stash@{%d}", index)).DontLog().RunWithOutputs()
cmdStr := NewGitCmd("rev-parse").
Arg(fmt.Sprintf("refs/stash@{%d}", index)).
ToString()
sha, _, err := self.cmd.New(cmdStr).DontLog().RunWithOutputs()
return strings.Trim(sha, "\r\n"), err
}
func (self *StashCommands) ShowStashEntryCmdObj(index int) oscommands.ICmdObj {
cmdStr := fmt.Sprintf("git stash show -p --stat --color=%s --unified=%d stash@{%d}", self.UserConfig.Git.Paging.ColorArg, self.UserConfig.Git.DiffContextSize, index)
func (self *StashCommands) ShowStashEntryCmdObj(index int, ignoreWhitespace bool) oscommands.ICmdObj {
cmdStr := NewGitCmd("stash").Arg("show").
Arg("-p").
Arg("--stat").
Arg(fmt.Sprintf("--color=%s", self.UserConfig.Git.Paging.ColorArg)).
Arg(fmt.Sprintf("--unified=%d", self.UserConfig.Git.DiffContextSize)).
ArgIf(ignoreWhitespace, "--ignore-all-space").
Arg(fmt.Sprintf("stash@{%d}", index)).
ToString()
return self.cmd.New(cmdStr).DontLog()
}
func (self *StashCommands) StashAndKeepIndex(message string) error {
return self.cmd.New(fmt.Sprintf("git stash save %s --keep-index", self.cmd.Quote(message))).Run()
cmdStr := NewGitCmd("stash").Arg("save", self.cmd.Quote(message), "--keep-index").
ToString()
return self.cmd.New(cmdStr).Run()
}
func (self *StashCommands) StashUnstagedChanges(message string) error {
if err := self.cmd.New("git commit --no-verify -m \"[lazygit] stashing unstaged changes\"").Run(); err != nil {
if err := self.cmd.New(
NewGitCmd("commit").
Arg("--no-verify", "-m", self.cmd.Quote("[lazygit] stashing unstaged changes")).
ToString(),
).Run(); err != nil {
return err
}
if err := self.Save(message); err != nil {
return err
}
if err := self.cmd.New("git reset --soft HEAD^").Run(); err != nil {
if err := self.cmd.New(
NewGitCmd("reset").Arg("--soft", "HEAD^").ToString(),
).Run(); err != nil {
return err
}
return nil
@@ -86,7 +123,9 @@ func (self *StashCommands) StashUnstagedChanges(message string) error {
// shoutouts to Joe on https://stackoverflow.com/questions/14759748/stashing-only-staged-changes-in-git-is-it-possible
func (self *StashCommands) SaveStagedChanges(message string) error {
// wrap in 'writing', which uses a mutex
if err := self.cmd.New("git stash --keep-index").Run(); err != nil {
if err := self.cmd.New(
NewGitCmd("stash").Arg("--keep-index").ToString(),
).Run(); err != nil {
return err
}
@@ -94,15 +133,22 @@ func (self *StashCommands) SaveStagedChanges(message string) error {
return err
}
if err := self.cmd.New("git stash apply stash@{1}").Run(); err != nil {
if err := self.cmd.New(
NewGitCmd("stash").Arg("apply", "stash@{1}").ToString(),
).Run(); err != nil {
return err
}
if err := self.os.PipeCommands("git stash show -p", "git apply -R"); err != nil {
if err := self.os.PipeCommands(
NewGitCmd("stash").Arg("show", "-p").ToString(),
NewGitCmd("apply").Arg("-R").ToString(),
); err != nil {
return err
}
if err := self.cmd.New("git stash drop stash@{1}").Run(); err != nil {
if err := self.cmd.New(
NewGitCmd("stash").Arg("drop", "stash@{1}").ToString(),
).Run(); err != nil {
return err
}
@@ -124,7 +170,10 @@ func (self *StashCommands) SaveStagedChanges(message string) error {
}
func (self *StashCommands) StashIncludeUntrackedChanges(message string) error {
return self.cmd.New(fmt.Sprintf("git stash save %s --include-untracked", self.cmd.Quote(message))).Run()
return self.cmd.New(
NewGitCmd("stash").Arg("save", self.cmd.Quote(message), "--include-untracked").
ToString(),
).Run()
}
func (self *StashCommands) Rename(index int, message string) error {
+5 -2
View File
@@ -32,7 +32,8 @@ func (self *StashLoader) GetStashEntries(filterPath string) []*models.StashEntry
return self.getUnfilteredStashEntries()
}
rawString, err := self.cmd.New("git stash list --name-only").DontLog().RunWithOutput()
cmdStr := NewGitCmd("stash").Arg("list", "--name-only").ToString()
rawString, err := self.cmd.New(cmdStr).DontLog().RunWithOutput()
if err != nil {
return self.getUnfilteredStashEntries()
}
@@ -65,7 +66,9 @@ outer:
}
func (self *StashLoader) getUnfilteredStashEntries() []*models.StashEntry {
rawString, _ := self.cmd.New("git stash list -z --pretty='%gs'").DontLog().RunWithOutput()
cmdStr := NewGitCmd("stash").Arg("list", "-z", "--pretty='%gs'").ToString()
rawString, _ := self.cmd.New(cmdStr).DontLog().RunWithOutput()
return slices.MapWithIndex(utils.SplitNul(rawString), func(line string, index int) *models.StashEntry {
return self.stashEntryFromLine(line, index)
})
+23 -13
View File
@@ -99,24 +99,34 @@ func TestStashSha(t *testing.T) {
func TestStashStashEntryCmdObj(t *testing.T) {
type scenario struct {
testName string
index int
contextSize int
expected string
testName string
index int
contextSize int
ignoreWhitespace bool
expected string
}
scenarios := []scenario{
{
testName: "Default case",
index: 5,
contextSize: 3,
expected: "git stash show -p --stat --color=always --unified=3 stash@{5}",
testName: "Default case",
index: 5,
contextSize: 3,
ignoreWhitespace: false,
expected: "git stash show -p --stat --color=always --unified=3 stash@{5}",
},
{
testName: "Show diff with custom context size",
index: 5,
contextSize: 77,
expected: "git stash show -p --stat --color=always --unified=77 stash@{5}",
testName: "Show diff with custom context size",
index: 5,
contextSize: 77,
ignoreWhitespace: false,
expected: "git stash show -p --stat --color=always --unified=77 stash@{5}",
},
{
testName: "Default case",
index: 5,
contextSize: 3,
ignoreWhitespace: true,
expected: "git stash show -p --stat --color=always --unified=3 --ignore-all-space stash@{5}",
},
}
@@ -127,7 +137,7 @@ func TestStashStashEntryCmdObj(t *testing.T) {
userConfig.Git.DiffContextSize = s.contextSize
instance := buildStashCommands(commonDeps{userConfig: userConfig})
cmdStr := instance.ShowStashEntryCmdObj(s.index).ToString()
cmdStr := instance.ShowStashEntryCmdObj(s.index, s.ignoreWhitespace).ToString()
assert.Equal(t, s.expected, cmdStr)
})
}
+3 -1
View File
@@ -56,7 +56,9 @@ func (self *StatusCommands) IsBareRepo() (bool, error) {
}
func IsBareRepo(osCommand *oscommands.OSCommand) (bool, error) {
res, err := osCommand.Cmd.New("git rev-parse --is-bare-repository").DontLog().RunWithOutput()
res, err := osCommand.Cmd.New(
NewGitCmd("rev-parse").Arg("--is-bare-repository").ToString(),
).DontLog().RunWithOutput()
if err != nil {
return false, err
}
+82 -32
View File
@@ -2,7 +2,6 @@ package git_commands
import (
"bufio"
"fmt"
"os"
"path/filepath"
"regexp"
@@ -82,38 +81,60 @@ func (self *SubmoduleCommands) Stash(submodule *models.SubmoduleConfig) error {
return nil
}
return self.cmd.New("git -C " + self.cmd.Quote(submodule.Path) + " stash --include-untracked").Run()
cmdStr := NewGitCmd("stash").
RepoPath(self.cmd.Quote(submodule.Path)).
Arg("--include-untracked").
ToString()
return self.cmd.New(cmdStr).Run()
}
func (self *SubmoduleCommands) Reset(submodule *models.SubmoduleConfig) error {
return self.cmd.New("git submodule update --init --force -- " + self.cmd.Quote(submodule.Path)).Run()
cmdStr := NewGitCmd("submodule").
Arg("update", "--init", "--force", "--", self.cmd.Quote(submodule.Path)).
ToString()
return self.cmd.New(cmdStr).Run()
}
func (self *SubmoduleCommands) UpdateAll() error {
// not doing an --init here because the user probably doesn't want that
return self.cmd.New("git submodule update --force").Run()
cmdStr := NewGitCmd("submodule").Arg("update", "--force").ToString()
return self.cmd.New(cmdStr).Run()
}
func (self *SubmoduleCommands) Delete(submodule *models.SubmoduleConfig) error {
// based on https://gist.github.com/myusuf3/7f645819ded92bda6677
if err := self.cmd.New("git submodule deinit --force -- " + self.cmd.Quote(submodule.Path)).Run(); err != nil {
if strings.Contains(err.Error(), "did not match any file(s) known to git") {
if err := self.cmd.New("git config --file .gitmodules --remove-section submodule." + self.cmd.Quote(submodule.Name)).Run(); err != nil {
return err
}
if err := self.cmd.New(
NewGitCmd("submodule").
Arg("deinit", "--force", "--", self.cmd.Quote(submodule.Path)).ToString(),
).Run(); err != nil {
if !strings.Contains(err.Error(), "did not match any file(s) known to git") {
return err
}
if err := self.cmd.New("git config --remove-section submodule." + self.cmd.Quote(submodule.Name)).Run(); err != nil {
return err
}
if err := self.cmd.New(
NewGitCmd("config").
Arg("--file", ".gitmodules", "--remove-section", "submodule."+self.cmd.Quote(submodule.Path)).
ToString(),
).Run(); err != nil {
return err
}
// if there's an error here about it not existing then we'll just continue to do `git rm`
} else {
if err := self.cmd.New(
NewGitCmd("config").
Arg("--remove-section", "submodule."+self.cmd.Quote(submodule.Path)).
ToString(),
).Run(); err != nil {
return err
}
}
if err := self.cmd.New("git rm --force -r " + submodule.Path).Run(); err != nil {
if err := self.cmd.New(
NewGitCmd("rm").Arg("--force", "-r", submodule.Path).ToString(),
).Run(); err != nil {
// if the directory isn't there then that's fine
self.Log.Error(err)
}
@@ -122,24 +143,35 @@ func (self *SubmoduleCommands) Delete(submodule *models.SubmoduleConfig) error {
}
func (self *SubmoduleCommands) Add(name string, path string, url string) error {
return self.cmd.
New(
fmt.Sprintf(
"git submodule add --force --name %s -- %s %s ",
self.cmd.Quote(name),
self.cmd.Quote(url),
self.cmd.Quote(path),
)).
Run()
cmdStr := NewGitCmd("submodule").
Arg("add").
Arg("--force").
Arg("--name").
Arg(self.cmd.Quote(name)).
Arg("--").
Arg(self.cmd.Quote(url)).
Arg(self.cmd.Quote(path)).
ToString()
return self.cmd.New(cmdStr).Run()
}
func (self *SubmoduleCommands) UpdateUrl(name string, path string, newUrl string) error {
setUrlCmdStr := NewGitCmd("config").
Arg(
"--file", ".gitmodules", "submodule."+self.cmd.Quote(name)+".url", self.cmd.Quote(newUrl),
).
ToString()
// the set-url command is only for later git versions so we're doing it manually here
if err := self.cmd.New("git config --file .gitmodules submodule." + self.cmd.Quote(name) + ".url " + self.cmd.Quote(newUrl)).Run(); err != nil {
if err := self.cmd.New(setUrlCmdStr).Run(); err != nil {
return err
}
if err := self.cmd.New("git submodule sync -- " + self.cmd.Quote(path)).Run(); err != nil {
syncCmdStr := NewGitCmd("submodule").Arg("sync", "--", self.cmd.Quote(path)).
ToString()
if err := self.cmd.New(syncCmdStr).Run(); err != nil {
return err
}
@@ -147,27 +179,45 @@ func (self *SubmoduleCommands) UpdateUrl(name string, path string, newUrl string
}
func (self *SubmoduleCommands) Init(path string) error {
return self.cmd.New("git submodule init -- " + self.cmd.Quote(path)).Run()
cmdStr := NewGitCmd("submodule").Arg("init", "--", self.cmd.Quote(path)).
ToString()
return self.cmd.New(cmdStr).Run()
}
func (self *SubmoduleCommands) Update(path string) error {
return self.cmd.New("git submodule update --init -- " + self.cmd.Quote(path)).Run()
cmdStr := NewGitCmd("submodule").Arg("update", "--init", "--", self.cmd.Quote(path)).
ToString()
return self.cmd.New(cmdStr).Run()
}
func (self *SubmoduleCommands) BulkInitCmdObj() oscommands.ICmdObj {
return self.cmd.New("git submodule init")
cmdStr := NewGitCmd("submodule").Arg("init").
ToString()
return self.cmd.New(cmdStr)
}
func (self *SubmoduleCommands) BulkUpdateCmdObj() oscommands.ICmdObj {
return self.cmd.New("git submodule update")
cmdStr := NewGitCmd("submodule").Arg("update").
ToString()
return self.cmd.New(cmdStr)
}
func (self *SubmoduleCommands) ForceBulkUpdateCmdObj() oscommands.ICmdObj {
return self.cmd.New("git submodule update --force")
cmdStr := NewGitCmd("submodule").Arg("update", "--force").
ToString()
return self.cmd.New(cmdStr)
}
func (self *SubmoduleCommands) BulkDeinitCmdObj() oscommands.ICmdObj {
return self.cmd.New("git submodule deinit --all --force")
cmdStr := NewGitCmd("submodule").Arg("deinit", "--all", "--force").
ToString()
return self.cmd.New(cmdStr)
}
func (self *SubmoduleCommands) ResetSubmodules(submodules []*models.SubmoduleConfig) error {
+27 -42
View File
@@ -1,8 +1,6 @@
package git_commands
import (
"fmt"
"github.com/go-errors/errors"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
)
@@ -26,26 +24,16 @@ type PushOpts struct {
}
func (self *SyncCommands) PushCmdObj(opts PushOpts) (oscommands.ICmdObj, error) {
cmdStr := "git push"
if opts.Force {
cmdStr += " --force-with-lease"
if opts.UpstreamBranch != "" && opts.UpstreamRemote == "" {
return nil, errors.New(self.Tr.MustSpecifyOriginError)
}
if opts.SetUpstream {
cmdStr += " --set-upstream"
}
if opts.UpstreamRemote != "" {
cmdStr += " " + self.cmd.Quote(opts.UpstreamRemote)
}
if opts.UpstreamBranch != "" {
if opts.UpstreamRemote == "" {
return nil, errors.New(self.Tr.MustSpecifyOriginError)
}
cmdStr += " " + self.cmd.Quote(opts.UpstreamBranch)
}
cmdStr := NewGitCmd("push").
ArgIf(opts.Force, "--force-with-lease").
ArgIf(opts.SetUpstream, "--set-upstream").
ArgIf(opts.UpstreamRemote != "", self.cmd.Quote(opts.UpstreamRemote)).
ArgIf(opts.UpstreamBranch != "", self.cmd.Quote(opts.UpstreamBranch)).
ToString()
cmdObj := self.cmd.New(cmdStr).PromptOnCredentialRequest().WithMutex(self.syncMutex)
return cmdObj, nil
@@ -68,14 +56,10 @@ type FetchOptions struct {
// Fetch fetch git repo
func (self *SyncCommands) Fetch(opts FetchOptions) error {
cmdStr := "git fetch"
if opts.RemoteName != "" {
cmdStr = fmt.Sprintf("%s %s", cmdStr, self.cmd.Quote(opts.RemoteName))
}
if opts.BranchName != "" {
cmdStr = fmt.Sprintf("%s %s", cmdStr, self.cmd.Quote(opts.BranchName))
}
cmdStr := NewGitCmd("fetch").
ArgIf(opts.RemoteName != "", self.cmd.Quote(opts.RemoteName)).
ArgIf(opts.BranchName != "", self.cmd.Quote(opts.BranchName)).
ToString()
cmdObj := self.cmd.New(cmdStr)
if opts.Background {
@@ -93,18 +77,12 @@ type PullOptions struct {
}
func (self *SyncCommands) Pull(opts PullOptions) error {
cmdStr := "git pull --no-edit"
if opts.FastForwardOnly {
cmdStr += " --ff-only"
}
if opts.RemoteName != "" {
cmdStr = fmt.Sprintf("%s %s", cmdStr, self.cmd.Quote(opts.RemoteName))
}
if opts.BranchName != "" {
cmdStr = fmt.Sprintf("%s %s", cmdStr, self.cmd.Quote(opts.BranchName))
}
cmdStr := NewGitCmd("pull").
Arg("--no-edit").
ArgIf(opts.FastForwardOnly, "--ff-only").
ArgIf(opts.RemoteName != "", self.cmd.Quote(opts.RemoteName)).
ArgIf(opts.BranchName != "", self.cmd.Quote(opts.BranchName)).
ToString()
// setting GIT_SEQUENCE_EDITOR to ':' as a way of skipping it, in case the user
// has 'pull.rebase = interactive' configured.
@@ -112,11 +90,18 @@ func (self *SyncCommands) Pull(opts PullOptions) error {
}
func (self *SyncCommands) FastForward(branchName string, remoteName string, remoteBranchName string) error {
cmdStr := fmt.Sprintf("git fetch %s %s:%s", self.cmd.Quote(remoteName), self.cmd.Quote(remoteBranchName), self.cmd.Quote(branchName))
cmdStr := NewGitCmd("fetch").
Arg(self.cmd.Quote(remoteName)).
Arg(self.cmd.Quote(remoteBranchName) + ":" + self.cmd.Quote(branchName)).
ToString()
return self.cmd.New(cmdStr).PromptOnCredentialRequest().WithMutex(self.syncMutex).Run()
}
func (self *SyncCommands) FetchRemote(remoteName string) error {
cmdStr := fmt.Sprintf("git fetch %s", self.cmd.Quote(remoteName))
cmdStr := NewGitCmd("fetch").
Arg(self.cmd.Quote(remoteName)).
ToString()
return self.cmd.New(cmdStr).PromptOnCredentialRequest().WithMutex(self.syncMutex).Run()
}
+19 -16
View File
@@ -1,9 +1,5 @@
package git_commands
import (
"fmt"
)
type TagCommands struct {
*GitCommon
}
@@ -15,25 +11,32 @@ func NewTagCommands(gitCommon *GitCommon) *TagCommands {
}
func (self *TagCommands) CreateLightweight(tagName string, ref string) error {
if len(ref) > 0 {
return self.cmd.New(fmt.Sprintf("git tag -- %s %s", self.cmd.Quote(tagName), self.cmd.Quote(ref))).Run()
} else {
return self.cmd.New(fmt.Sprintf("git tag -- %s", self.cmd.Quote(tagName))).Run()
}
cmdStr := NewGitCmd("tag").Arg("--", self.cmd.Quote(tagName)).
ArgIf(len(ref) > 0, self.cmd.Quote(ref)).
ToString()
return self.cmd.New(cmdStr).Run()
}
func (self *TagCommands) CreateAnnotated(tagName, ref, msg string) error {
if len(ref) > 0 {
return self.cmd.New(fmt.Sprintf("git tag %s %s -m %s", self.cmd.Quote(tagName), self.cmd.Quote(ref), self.cmd.Quote(msg))).Run()
} else {
return self.cmd.New(fmt.Sprintf("git tag %s -m %s", self.cmd.Quote(tagName), self.cmd.Quote(msg))).Run()
}
cmdStr := NewGitCmd("tag").Arg(self.cmd.Quote(tagName)).
ArgIf(len(ref) > 0, self.cmd.Quote(ref)).
Arg("-m", self.cmd.Quote(msg)).
ToString()
return self.cmd.New(cmdStr).Run()
}
func (self *TagCommands) Delete(tagName string) error {
return self.cmd.New(fmt.Sprintf("git tag -d %s", self.cmd.Quote(tagName))).Run()
cmdStr := NewGitCmd("tag").Arg("-d", self.cmd.Quote(tagName)).
ToString()
return self.cmd.New(cmdStr).Run()
}
func (self *TagCommands) Push(remoteName string, tagName string) error {
return self.cmd.New(fmt.Sprintf("git push %s tag %s", self.cmd.Quote(remoteName), self.cmd.Quote(tagName))).PromptOnCredentialRequest().WithMutex(self.syncMutex).Run()
cmdStr := NewGitCmd("push").Arg(self.cmd.Quote(remoteName), "tag", self.cmd.Quote(tagName)).
ToString()
return self.cmd.New(cmdStr).PromptOnCredentialRequest().WithMutex(self.syncMutex).Run()
}
+2 -1
View File
@@ -28,7 +28,8 @@ func NewTagLoader(
func (self *TagLoader) GetTags() ([]*models.Tag, error) {
// get remote branches, sorted by creation date (descending)
// see: https://git-scm.com/docs/git-tag#Documentation/git-tag.txt---sortltkeygt
tagsOutput, err := self.cmd.New(`git tag --list -n --sort=-creatordate`).DontLog().RunWithOutput()
cmdStr := NewGitCmd("tag").Arg("--list", "-n", "--sort=-creatordate").ToString()
tagsOutput, err := self.cmd.New(cmdStr).DontLog().RunWithOutput()
if err != nil {
return nil, err
}
+1 -1
View File
@@ -15,7 +15,7 @@ type GitVersion struct {
}
func GetGitVersion(osCommand *oscommands.OSCommand) (*GitVersion, error) {
versionStr, _, err := osCommand.Cmd.New("git --version").RunWithOutputs()
versionStr, _, err := osCommand.Cmd.New(NewGitCmd("--version").ToString()).RunWithOutputs()
if err != nil {
return nil, err
}
+94 -99
View File
@@ -3,15 +3,11 @@ package git_commands
import (
"fmt"
"os"
"path/filepath"
"strings"
"time"
"github.com/go-errors/errors"
"github.com/jesseduffield/generics/slices"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/utils"
)
type WorkingTreeCommands struct {
@@ -33,7 +29,7 @@ func NewWorkingTreeCommands(
}
func (self *WorkingTreeCommands) OpenMergeToolCmdObj() oscommands.ICmdObj {
return self.cmd.New("git mergetool")
return self.cmd.New(NewGitCmd("mergetool").ToString())
}
func (self *WorkingTreeCommands) OpenMergeTool() error {
@@ -49,30 +45,37 @@ func (self *WorkingTreeCommands) StageFiles(paths []string) error {
quotedPaths := slices.Map(paths, func(path string) string {
return self.cmd.Quote(path)
})
return self.cmd.New(fmt.Sprintf("git add -- %s", strings.Join(quotedPaths, " "))).Run()
cmdStr := NewGitCmd("add").Arg("--").Arg(quotedPaths...).ToString()
return self.cmd.New(cmdStr).Run()
}
// StageAll stages all files
func (self *WorkingTreeCommands) StageAll() error {
return self.cmd.New("git add -A").Run()
cmdStr := NewGitCmd("add").Arg("-A").ToString()
return self.cmd.New(cmdStr).Run()
}
// UnstageAll unstages all files
func (self *WorkingTreeCommands) UnstageAll() error {
return self.cmd.New("git reset").Run()
return self.cmd.New(NewGitCmd("reset").ToString()).Run()
}
// UnStageFile unstages a file
// we accept an array of filenames for the cases where a file has been renamed i.e.
// we accept the current name and the previous name
func (self *WorkingTreeCommands) UnStageFile(fileNames []string, reset bool) error {
command := "git rm --cached --force -- %s"
if reset {
command = "git reset HEAD -- %s"
}
for _, name := range fileNames {
err := self.cmd.New(fmt.Sprintf(command, self.cmd.Quote(name))).Run()
var cmdStr string
if reset {
cmdStr = NewGitCmd("reset").Arg("HEAD", "--", self.cmd.Quote(name)).ToString()
} else {
cmdStr = NewGitCmd("rm").Arg("--cached", "--force", "--", self.cmd.Quote(name)).ToString()
}
err := self.cmd.New(cmdStr).Run()
if err != nil {
return err
}
@@ -137,22 +140,31 @@ func (self *WorkingTreeCommands) DiscardAllFileChanges(file *models.File) error
quotedFileName := self.cmd.Quote(file.Name)
if file.ShortStatus == "AA" {
if err := self.cmd.New("git checkout --ours -- " + quotedFileName).Run(); err != nil {
if err := self.cmd.New(
NewGitCmd("checkout").Arg("--ours", "--", quotedFileName).ToString(),
).Run(); err != nil {
return err
}
if err := self.cmd.New("git add -- " + quotedFileName).Run(); err != nil {
if err := self.cmd.New(
NewGitCmd("add").Arg("--", quotedFileName).ToString(),
).Run(); err != nil {
return err
}
return nil
}
if file.ShortStatus == "DU" {
return self.cmd.New("git rm -- " + quotedFileName).Run()
return self.cmd.New(
NewGitCmd("rm").Arg("rm", "--", quotedFileName).ToString(),
).Run()
}
// if the file isn't tracked, we assume you want to delete it
if file.HasStagedChanges || file.HasMergeConflicts {
if err := self.cmd.New("git reset -- " + quotedFileName).Run(); err != nil {
if err := self.cmd.New(
NewGitCmd("reset").Arg("--", quotedFileName).ToString(),
).Run(); err != nil {
return err
}
}
@@ -184,7 +196,8 @@ func (self *WorkingTreeCommands) DiscardUnstagedDirChanges(node IFileNode) error
}
quotedPath := self.cmd.Quote(node.GetPath())
if err := self.cmd.New("git checkout -- " + quotedPath).Run(); err != nil {
cmdStr := NewGitCmd("checkout").Arg("--", quotedPath).ToString()
if err := self.cmd.New(cmdStr).Run(); err != nil {
return err
}
@@ -209,7 +222,8 @@ func (self *WorkingTreeCommands) RemoveUntrackedDirFiles(node IFileNode) error {
// DiscardUnstagedFileChanges directly
func (self *WorkingTreeCommands) DiscardUnstagedFileChanges(file *models.File) error {
quotedFileName := self.cmd.Quote(file.Name)
return self.cmd.New("git checkout -- " + quotedFileName).Run()
cmdStr := NewGitCmd("checkout").Arg("--", quotedFileName).ToString()
return self.cmd.New(cmdStr).Run()
}
// Ignore adds a file to the gitignore for the repo
@@ -230,61 +244,32 @@ func (self *WorkingTreeCommands) WorktreeFileDiff(file *models.File, plain bool,
}
func (self *WorkingTreeCommands) WorktreeFileDiffCmdObj(node models.IFile, plain bool, cached bool, ignoreWhitespace bool) oscommands.ICmdObj {
cachedArg := ""
trackedArg := "--"
colorArg := self.UserConfig.Git.Paging.ColorArg
quotedPath := self.cmd.Quote(node.GetPath())
quotedPrevPath := ""
ignoreWhitespaceArg := ""
contextSize := self.UserConfig.Git.DiffContextSize
if cached {
cachedArg = " --cached"
}
if !node.GetIsTracked() && !node.GetHasStagedChanges() && !cached && node.GetIsFile() {
trackedArg = "--no-index -- /dev/null"
}
if plain {
colorArg = "never"
}
if ignoreWhitespace {
ignoreWhitespaceArg = " --ignore-all-space"
}
if prevPath := node.GetPreviousPath(); prevPath != "" {
quotedPrevPath = " " + self.cmd.Quote(prevPath)
}
cmdStr := fmt.Sprintf("git diff --submodule --no-ext-diff --unified=%d --color=%s%s%s %s %s%s", contextSize, colorArg, ignoreWhitespaceArg, cachedArg, trackedArg, quotedPath, quotedPrevPath)
contextSize := self.UserConfig.Git.DiffContextSize
prevPath := node.GetPreviousPath()
noIndex := !node.GetIsTracked() && !node.GetHasStagedChanges() && !cached && node.GetIsFile()
cmdStr := NewGitCmd("diff").
Arg("--submodule").
Arg("--no-ext-diff").
Arg(fmt.Sprintf("--unified=%d", contextSize)).
Arg(fmt.Sprintf("--color=%s", colorArg)).
ArgIf(ignoreWhitespace, "--ignore-all-space").
ArgIf(cached, "--cached").
ArgIf(noIndex, "--no-index").
Arg("--").
ArgIf(noIndex, "/dev/null").
Arg(self.cmd.Quote(node.GetPath())).
ArgIf(prevPath != "", self.cmd.Quote(prevPath)).
ToString()
return self.cmd.New(cmdStr).DontLog()
}
func (self *WorkingTreeCommands) ApplyPatch(patch string, flags ...string) error {
filepath, err := self.SaveTemporaryPatch(patch)
if err != nil {
return err
}
return self.ApplyPatchFile(filepath, flags...)
}
func (self *WorkingTreeCommands) ApplyPatchFile(filepath string, flags ...string) error {
flagStr := ""
for _, flag := range flags {
flagStr += " --" + flag
}
return self.cmd.New(fmt.Sprintf("git apply%s %s", flagStr, self.cmd.Quote(filepath))).Run()
}
func (self *WorkingTreeCommands) SaveTemporaryPatch(patch string) (string, error) {
filepath := filepath.Join(self.os.GetTempDir(), utils.GetCurrentRepoName(), time.Now().Format("Jan _2 15.04.05.000000000")+".patch")
self.Log.Infof("saving temporary patch to %s", filepath)
if err := self.os.CreateFileWithContent(filepath, patch); err != nil {
return "", err
}
return filepath, nil
}
// 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,
@@ -296,49 +281,59 @@ func (self *WorkingTreeCommands) ShowFileDiff(from string, to string, reverse bo
func (self *WorkingTreeCommands) ShowFileDiffCmdObj(from string, to string, reverse bool, fileName string, plain bool,
ignoreWhitespace bool,
) oscommands.ICmdObj {
colorArg := self.UserConfig.Git.Paging.ColorArg
contextSize := self.UserConfig.Git.DiffContextSize
colorArg := self.UserConfig.Git.Paging.ColorArg
if plain {
colorArg = "never"
}
reverseFlag := ""
if reverse {
reverseFlag = " -R"
}
cmdStr := NewGitCmd("diff").
Arg("--submodule").
Arg("--no-ext-diff").
Arg(fmt.Sprintf("--unified=%d", contextSize)).
Arg("--no-renames").
Arg(fmt.Sprintf("--color=%s", colorArg)).
Arg(from).
Arg(to).
ArgIf(reverse, "-R").
ArgIf(ignoreWhitespace, "--ignore-all-space").
Arg("--").
Arg(self.cmd.Quote(fileName)).
ToString()
ignoreWhitespaceFlag := ""
if ignoreWhitespace {
ignoreWhitespaceFlag = " --ignore-all-space"
}
return self.cmd.
New(
fmt.Sprintf(
"git diff --submodule --no-ext-diff --unified=%d --no-renames --color=%s%s%s%s%s -- %s",
contextSize, colorArg, pad(from), pad(to), reverseFlag, ignoreWhitespaceFlag, self.cmd.Quote(fileName)),
).
DontLog()
return self.cmd.New(cmdStr).DontLog()
}
// CheckoutFile checks out the file for the given commit
func (self *WorkingTreeCommands) CheckoutFile(commitSha, fileName string) error {
return self.cmd.New(fmt.Sprintf("git checkout %s -- %s", commitSha, self.cmd.Quote(fileName))).Run()
cmdStr := NewGitCmd("checkout").Arg(commitSha, "--", self.cmd.Quote(fileName)).
ToString()
return self.cmd.New(cmdStr).Run()
}
// DiscardAnyUnstagedFileChanges discards any unstages file changes via `git checkout -- .`
// DiscardAnyUnstagedFileChanges discards any unstaged file changes via `git checkout -- .`
func (self *WorkingTreeCommands) DiscardAnyUnstagedFileChanges() error {
return self.cmd.New("git checkout -- .").Run()
cmdStr := NewGitCmd("checkout").Arg("--", ".").
ToString()
return self.cmd.New(cmdStr).Run()
}
// RemoveTrackedFiles will delete the given file(s) even if they are currently tracked
func (self *WorkingTreeCommands) RemoveTrackedFiles(name string) error {
return self.cmd.New("git rm -r --cached -- " + self.cmd.Quote(name)).Run()
cmdStr := NewGitCmd("rm").Arg("-r", "--cached", "--", self.cmd.Quote(name)).
ToString()
return self.cmd.New(cmdStr).Run()
}
// RemoveUntrackedFiles runs `git clean -fd`
func (self *WorkingTreeCommands) RemoveUntrackedFiles() error {
return self.cmd.New("git clean -fd").Run()
cmdStr := NewGitCmd("clean").Arg("-fd").ToString()
return self.cmd.New(cmdStr).Run()
}
// ResetAndClean removes all unstaged changes and removes all untracked files
@@ -363,23 +358,23 @@ func (self *WorkingTreeCommands) ResetAndClean() error {
// ResetHardHead runs `git reset --hard`
func (self *WorkingTreeCommands) ResetHard(ref string) error {
return self.cmd.New("git reset --hard " + self.cmd.Quote(ref)).Run()
cmdStr := NewGitCmd("reset").Arg("--hard", self.cmd.Quote(ref)).
ToString()
return self.cmd.New(cmdStr).Run()
}
// ResetSoft runs `git reset --soft HEAD`
func (self *WorkingTreeCommands) ResetSoft(ref string) error {
return self.cmd.New("git reset --soft " + self.cmd.Quote(ref)).Run()
cmdStr := NewGitCmd("reset").Arg("--soft", self.cmd.Quote(ref)).
ToString()
return self.cmd.New(cmdStr).Run()
}
func (self *WorkingTreeCommands) ResetMixed(ref string) error {
return self.cmd.New("git reset --mixed " + self.cmd.Quote(ref)).Run()
}
cmdStr := NewGitCmd("reset").Arg("--mixed", self.cmd.Quote(ref)).
ToString()
// so that we don't have unnecessary space in our commands we use this helper function to prepend spaces to args so that in the format string we can go '%s%s%s' and if any args are missing we won't have gaps.
func pad(str string) string {
if str == "" {
return ""
}
return " " + str
return self.cmd.New(cmdStr).Run()
}
@@ -2,8 +2,6 @@ package git_commands
import (
"fmt"
"os"
"regexp"
"testing"
"github.com/go-errors/errors"
@@ -430,60 +428,6 @@ func TestWorkingTreeCheckoutFile(t *testing.T) {
}
}
func TestWorkingTreeApplyPatch(t *testing.T) {
type scenario struct {
testName string
runner *oscommands.FakeCmdObjRunner
test func(error)
}
expectFn := func(regexStr string, errToReturn error) func(cmdObj oscommands.ICmdObj) (string, error) {
return func(cmdObj oscommands.ICmdObj) (string, error) {
re := regexp.MustCompile(regexStr)
cmdStr := cmdObj.ToString()
matches := re.FindStringSubmatch(cmdStr)
assert.Equal(t, 2, len(matches), fmt.Sprintf("unexpected command: %s", cmdStr))
filename := matches[1]
content, err := os.ReadFile(filename)
assert.NoError(t, err)
assert.Equal(t, "test", string(content))
return "", errToReturn
}
}
scenarios := []scenario{
{
testName: "valid case",
runner: oscommands.NewFakeRunner(t).
ExpectFunc(expectFn(`git apply --cached "(.*)"`, nil)),
test: func(err error) {
assert.NoError(t, err)
},
},
{
testName: "command returns error",
runner: oscommands.NewFakeRunner(t).
ExpectFunc(expectFn(`git apply --cached "(.*)"`, errors.New("error"))),
test: func(err error) {
assert.Error(t, err)
},
},
}
for _, s := range scenarios {
s := s
t.Run(s.testName, func(t *testing.T) {
instance := buildWorkingTreeCommands(commonDeps{runner: s.runner})
s.test(instance.ApplyPatch("test", "cached"))
s.runner.CheckForMissingCalls()
})
}
}
func TestWorkingTreeDiscardUnstagedFileChanges(t *testing.T) {
type scenario struct {
testName string
+15 -24
View File
@@ -29,7 +29,6 @@ type fileInfo struct {
}
type (
applyPatchFunc func(patch string, flags ...string) error
loadFileDiffFunc func(from string, to string, reverse bool, filename string, plain bool) (string, error)
)
@@ -47,17 +46,14 @@ type PatchBuilder struct {
// fileInfoMap starts empty but you add files to it as you go along
fileInfoMap map[string]*fileInfo
Log *logrus.Entry
applyPatch applyPatchFunc
// loadFileDiff loads the diff of a file, for a given to (typically a commit SHA)
loadFileDiff loadFileDiffFunc
}
// NewPatchBuilder returns a new PatchBuilder
func NewPatchBuilder(log *logrus.Entry, applyPatch applyPatchFunc, loadFileDiff loadFileDiffFunc) *PatchBuilder {
func NewPatchBuilder(log *logrus.Entry, loadFileDiff loadFileDiffFunc) *PatchBuilder {
return &PatchBuilder{
Log: log,
applyPatch: applyPatch,
loadFileDiff: loadFileDiff,
}
}
@@ -70,6 +66,20 @@ func (p *PatchBuilder) Start(from, to string, reverse bool, canRebase bool) {
p.fileInfoMap = map[string]*fileInfo{}
}
func (p *PatchBuilder) PatchToApply(reverse bool) string {
patch := ""
for filename, info := range p.fileInfoMap {
if info.mode == UNSELECTED {
continue
}
patch += p.RenderPatchForFile(filename, true, reverse)
}
return patch
}
func (p *PatchBuilder) addFileWhole(info *fileInfo) {
info.mode = WHOLE
lineCount := len(strings.Split(info.diff, "\n"))
@@ -234,25 +244,6 @@ func (p *PatchBuilder) GetFileIncLineIndices(filename string) ([]int, error) {
return info.includedLineIndices, nil
}
func (p *PatchBuilder) ApplyPatches(reverse bool) error {
patch := ""
applyFlags := []string{"index", "3way"}
if reverse {
applyFlags = append(applyFlags, "reverse")
}
for filename, info := range p.fileInfoMap {
if info.mode == UNSELECTED {
continue
}
patch += p.RenderPatchForFile(filename, true, reverse)
}
return p.applyPatch(patch, applyFlags...)
}
// clears the patch
func (p *PatchBuilder) Reset() {
p.To = ""
+1
View File
@@ -35,6 +35,7 @@ type editPreset struct {
editInTerminal bool
}
// IF YOU ADD A PRESET TO THIS FUNCTION YOU MUST UPDATE THE `Supported presets` SECTION OF docs/Config.md
func getPreset(osConfig *OSConfig, guessDefaultEditor func() string) *editPreset {
presets := map[string]*editPreset{
"vi": standardTerminalEditorPreset("vi"),
+44 -40
View File
@@ -27,33 +27,34 @@ type RefresherConfig struct {
}
type GuiConfig struct {
AuthorColors map[string]string `yaml:"authorColors"`
BranchColors map[string]string `yaml:"branchColors"`
ScrollHeight int `yaml:"scrollHeight"`
ScrollPastBottom bool `yaml:"scrollPastBottom"`
MouseEvents bool `yaml:"mouseEvents"`
SkipUnstageLineWarning bool `yaml:"skipUnstageLineWarning"`
SkipStashWarning bool `yaml:"skipStashWarning"`
SidePanelWidth float64 `yaml:"sidePanelWidth"`
ExpandFocusedSidePanel bool `yaml:"expandFocusedSidePanel"`
MainPanelSplitMode string `yaml:"mainPanelSplitMode"`
Language string `yaml:"language"`
TimeFormat string `yaml:"timeFormat"`
Theme ThemeConfig `yaml:"theme"`
CommitLength CommitLengthConfig `yaml:"commitLength"`
SkipNoStagedFilesWarning bool `yaml:"skipNoStagedFilesWarning"`
ShowListFooter bool `yaml:"showListFooter"`
ShowFileTree bool `yaml:"showFileTree"`
ShowRandomTip bool `yaml:"showRandomTip"`
ShowCommandLog bool `yaml:"showCommandLog"`
ShowBottomLine bool `yaml:"showBottomLine"`
ShowIcons bool `yaml:"showIcons"`
ExperimentalShowBranchHeads bool `yaml:"experimentalShowBranchHeads"`
CommandLogSize int `yaml:"commandLogSize"`
SplitDiff string `yaml:"splitDiff"`
SkipRewordInEditorWarning bool `yaml:"skipRewordInEditorWarning"`
WindowSize string `yaml:"windowSize"`
Border string `yaml:"border"`
AuthorColors map[string]string `yaml:"authorColors"`
BranchColors map[string]string `yaml:"branchColors"`
ScrollHeight int `yaml:"scrollHeight"`
ScrollPastBottom bool `yaml:"scrollPastBottom"`
MouseEvents bool `yaml:"mouseEvents"`
SkipUnstageLineWarning bool `yaml:"skipUnstageLineWarning"`
SkipStashWarning bool `yaml:"skipStashWarning"`
SidePanelWidth float64 `yaml:"sidePanelWidth"`
ExpandFocusedSidePanel bool `yaml:"expandFocusedSidePanel"`
MainPanelSplitMode string `yaml:"mainPanelSplitMode"`
Language string `yaml:"language"`
TimeFormat string `yaml:"timeFormat"`
Theme ThemeConfig `yaml:"theme"`
CommitLength CommitLengthConfig `yaml:"commitLength"`
SkipNoStagedFilesWarning bool `yaml:"skipNoStagedFilesWarning"`
ShowListFooter bool `yaml:"showListFooter"`
ShowFileTree bool `yaml:"showFileTree"`
ShowRandomTip bool `yaml:"showRandomTip"`
ShowCommandLog bool `yaml:"showCommandLog"`
ShowBottomLine bool `yaml:"showBottomLine"`
ShowIcons bool `yaml:"showIcons"`
ExperimentalShowBranchHeads bool `yaml:"experimentalShowBranchHeads"`
CommandLogSize int `yaml:"commandLogSize"`
SplitDiff string `yaml:"splitDiff"`
SkipRewordInEditorWarning bool `yaml:"skipRewordInEditorWarning"`
WindowSize string `yaml:"windowSize"`
Border string `yaml:"border"`
ExperimentalMarkCommitsWhichChangedFile bool `yaml:"experimentalMarkCommitsWhichChangedFile"`
}
type ThemeConfig struct {
@@ -76,6 +77,7 @@ type GitConfig struct {
Paging PagingConfig `yaml:"paging"`
Commit CommitConfig `yaml:"commit"`
Merging MergingConfig `yaml:"merging"`
MainBranches []string `yaml:"mainBranches"`
SkipHookPrefix string `yaml:"skipHookPrefix"`
AutoFetch bool `yaml:"autoFetch"`
AutoRefresh bool `yaml:"autoRefresh"`
@@ -409,19 +411,20 @@ func GetDefaultConfig() *UserConfig {
UnstagedChangesColor: []string{"red"},
DefaultFgColor: []string{"default"},
},
CommitLength: CommitLengthConfig{Show: true},
SkipNoStagedFilesWarning: false,
ShowListFooter: true,
ShowCommandLog: true,
ShowBottomLine: true,
ShowFileTree: true,
ShowRandomTip: true,
ShowIcons: false,
ExperimentalShowBranchHeads: false,
CommandLogSize: 8,
SplitDiff: "auto",
SkipRewordInEditorWarning: false,
Border: "single",
CommitLength: CommitLengthConfig{Show: true},
SkipNoStagedFilesWarning: false,
ShowListFooter: true,
ShowCommandLog: true,
ShowBottomLine: true,
ShowFileTree: true,
ShowRandomTip: true,
ShowIcons: false,
ExperimentalShowBranchHeads: false,
CommandLogSize: 8,
SplitDiff: "auto",
SkipRewordInEditorWarning: false,
Border: "single",
ExperimentalMarkCommitsWhichChangedFile: false,
},
Git: GitConfig{
Paging: PagingConfig{
@@ -443,6 +446,7 @@ func GetDefaultConfig() *UserConfig {
ShowWholeGraph: false,
},
SkipHookPrefix: "WIP",
MainBranches: []string{"master", "main"},
AutoFetch: true,
AutoRefresh: true,
BranchLogCmd: "git log --graph --color=always --abbrev-commit --decorate --date=relative --pretty=medium {{branchName}} --",
-131
View File
@@ -1,131 +0,0 @@
package gui
import (
"time"
"github.com/jesseduffield/generics/slices"
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/sasha-s/go-deadlock"
)
// statusManager's job is to handle rendering of loading states and toast notifications
// that you see at the bottom left of the screen.
type statusManager struct {
statuses []appStatus
nextId int
mutex deadlock.Mutex
}
type appStatus struct {
message string
statusType string
id int
}
func (m *statusManager) removeStatus(id int) {
m.mutex.Lock()
defer m.mutex.Unlock()
m.statuses = slices.Filter(m.statuses, func(status appStatus) bool {
return status.id != id
})
}
func (m *statusManager) addWaitingStatus(message string) int {
m.mutex.Lock()
defer m.mutex.Unlock()
m.nextId += 1
id := m.nextId
newStatus := appStatus{
message: message,
statusType: "waiting",
id: id,
}
m.statuses = append([]appStatus{newStatus}, m.statuses...)
return id
}
func (m *statusManager) addToastStatus(message string) int {
m.mutex.Lock()
defer m.mutex.Unlock()
m.nextId++
id := m.nextId
newStatus := appStatus{
message: message,
statusType: "toast",
id: id,
}
m.statuses = append([]appStatus{newStatus}, m.statuses...)
go func() {
time.Sleep(time.Second * 2)
m.removeStatus(id)
}()
return id
}
func (m *statusManager) getStatusString() string {
if len(m.statuses) == 0 {
return ""
}
topStatus := m.statuses[0]
if topStatus.statusType == "waiting" {
return topStatus.message + " " + utils.Loader()
}
return topStatus.message
}
func (m *statusManager) showStatus() bool {
return len(m.statuses) > 0
}
func (gui *Gui) toast(message string) {
gui.statusManager.addToastStatus(message)
gui.renderAppStatus()
}
func (gui *Gui) renderAppStatus() {
go utils.Safe(func() {
ticker := time.NewTicker(time.Millisecond * 50)
defer ticker.Stop()
for range ticker.C {
appStatus := gui.statusManager.getStatusString()
gui.c.OnUIThread(func() error {
return gui.renderString(gui.Views.AppStatus, appStatus)
})
if appStatus == "" {
return
}
}
})
}
// withWaitingStatus wraps a function and shows a waiting status while the function is still executing
func (gui *Gui) withWaitingStatus(message string, f func() error) error {
go utils.Safe(func() {
id := gui.statusManager.addWaitingStatus(message)
defer func() {
gui.statusManager.removeStatus(id)
}()
gui.renderAppStatus()
if err := f(); err != nil {
gui.c.OnUIThread(func() error {
return gui.c.Error(err)
})
}
})
return nil
}
+42 -17
View File
@@ -4,18 +4,33 @@ import (
"strings"
"time"
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/utils"
)
func (gui *Gui) startBackgroundRoutines() {
userConfig := gui.UserConfig
type BackgroundRoutineMgr struct {
gui *Gui
// if we've suspended the gui (e.g. because we've switched to a subprocess)
// we typically want to pause some things that are running like background
// file refreshes
pauseBackgroundThreads bool
}
func (self *BackgroundRoutineMgr) PauseBackgroundThreads(pause bool) {
self.pauseBackgroundThreads = pause
}
func (self *BackgroundRoutineMgr) startBackgroundRoutines() {
userConfig := self.gui.UserConfig
if userConfig.Git.AutoFetch {
fetchInterval := userConfig.Refresher.FetchInterval
if fetchInterval > 0 {
go utils.Safe(gui.startBackgroundFetch)
go utils.Safe(self.startBackgroundFetch)
} else {
gui.c.Log.Errorf(
self.gui.c.Log.Errorf(
"Value of config option 'refresher.fetchInterval' (%d) is invalid, disabling auto-fetch",
fetchInterval)
}
@@ -24,42 +39,44 @@ func (gui *Gui) startBackgroundRoutines() {
if userConfig.Git.AutoRefresh {
refreshInterval := userConfig.Refresher.RefreshInterval
if refreshInterval > 0 {
gui.goEvery(time.Second*time.Duration(refreshInterval), gui.stopChan, gui.refreshFilesAndSubmodules)
self.goEvery(time.Second*time.Duration(refreshInterval), self.gui.stopChan, func() error {
return self.gui.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.FILES}})
})
} else {
gui.c.Log.Errorf(
self.gui.c.Log.Errorf(
"Value of config option 'refresher.refreshInterval' (%d) is invalid, disabling auto-refresh",
refreshInterval)
}
}
}
func (gui *Gui) startBackgroundFetch() {
gui.waitForIntro.Wait()
isNew := gui.IsNewRepo
userConfig := gui.UserConfig
func (self *BackgroundRoutineMgr) startBackgroundFetch() {
self.gui.waitForIntro.Wait()
isNew := self.gui.IsNewRepo
userConfig := self.gui.UserConfig
if !isNew {
time.After(time.Duration(userConfig.Refresher.FetchInterval) * time.Second)
}
err := gui.backgroundFetch()
err := self.backgroundFetch()
if err != nil && strings.Contains(err.Error(), "exit status 128") && isNew {
_ = gui.c.Alert(gui.c.Tr.NoAutomaticGitFetchTitle, gui.c.Tr.NoAutomaticGitFetchBody)
_ = self.gui.c.Alert(self.gui.c.Tr.NoAutomaticGitFetchTitle, self.gui.c.Tr.NoAutomaticGitFetchBody)
} else {
gui.goEvery(time.Second*time.Duration(userConfig.Refresher.FetchInterval), gui.stopChan, func() error {
err := gui.backgroundFetch()
gui.render()
self.goEvery(time.Second*time.Duration(userConfig.Refresher.FetchInterval), self.gui.stopChan, func() error {
err := self.backgroundFetch()
self.gui.c.Render()
return err
})
}
}
func (gui *Gui) goEvery(interval time.Duration, stop chan struct{}, function func() error) {
func (self *BackgroundRoutineMgr) goEvery(interval time.Duration, stop chan struct{}, function func() error) {
go utils.Safe(func() {
ticker := time.NewTicker(interval)
defer ticker.Stop()
for {
select {
case <-ticker.C:
if gui.PauseBackgroundThreads {
if self.pauseBackgroundThreads {
continue
}
_ = function()
@@ -69,3 +86,11 @@ func (gui *Gui) goEvery(interval time.Duration, stop chan struct{}, function fun
}
})
}
func (self *BackgroundRoutineMgr) backgroundFetch() (err error) {
err = self.gui.git.Sync.Fetch(git_commands.FetchOptions{Background: true})
_ = self.gui.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.BRANCHES, types.COMMITS, types.REMOTES, types.TAGS}, Mode: types.ASYNC})
return err
}
-23
View File
@@ -1,23 +0,0 @@
package gui
import "github.com/jesseduffield/lazygit/pkg/gui/types"
func (gui *Gui) branchesRenderToMain() error {
var task types.UpdateTask
branch := gui.State.Contexts.Branches.GetSelected()
if branch == nil {
task = types.NewRenderStringTask(gui.c.Tr.NoBranchesThisRepo)
} else {
cmdObj := gui.git.Branch.GetGraphCmdObj(branch.FullRefName())
task = types.NewRunPtyTask(cmdObj.GetCmd())
}
return gui.c.RenderToMainViews(types.RefreshMainOpts{
Pair: gui.c.MainViewPairs().Normal,
Main: &types.ViewUpdateOpts{
Title: gui.c.Tr.LogTitle,
Task: task,
},
})
}
+3 -2
View File
@@ -30,6 +30,7 @@ func (gui *Gui) LogAction(action string) {
gui.Views.Extras.Autoscroll = true
gui.GuiLog = append(gui.GuiLog, action)
fmt.Fprint(gui.Views.Extras, "\n"+style.FgYellow.Sprint(action))
}
@@ -46,7 +47,7 @@ func (gui *Gui) LogCommand(cmdStr string, commandLine bool) {
// we style it differently to communicate that
textStyle = style.FgMagenta
}
gui.CmdLog = append(gui.CmdLog, cmdStr)
gui.GuiLog = append(gui.GuiLog, cmdStr)
indentedCmdStr := " " + strings.Replace(cmdStr, "\n", "\n ", -1)
fmt.Fprint(gui.Views.Extras, "\n"+textStyle.Sprint(indentedCmdStr))
}
@@ -103,7 +104,7 @@ func (gui *Gui) getRandomTip() string {
),
fmt.Sprintf(
"to hard reset onto your current upstream branch, press '%s' in the files panel",
formattedKey(config.Files.ViewResetOptions),
formattedKey(config.Commits.ViewResetOptions),
),
fmt.Sprintf(
"To push a tag, navigate to the tag in the tags tab and press '%s'",
-52
View File
@@ -1,52 +0,0 @@
package gui
import (
"github.com/jesseduffield/lazygit/pkg/gui/controllers"
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
func (gui *Gui) commitFilesRenderToMain() error {
node := gui.State.Contexts.CommitFiles.GetSelected()
if node == nil {
return nil
}
ref := gui.State.Contexts.CommitFiles.GetRef()
to := ref.RefName()
from, reverse := gui.State.Modes.Diffing.GetFromAndReverseArgsForDiff(ref.ParentRefName())
cmdObj := gui.git.WorkingTree.ShowFileDiffCmdObj(from, to, reverse, node.GetPath(), false,
gui.IgnoreWhitespaceInDiffView)
task := types.NewRunPtyTask(cmdObj.GetCmd())
pair := gui.c.MainViewPairs().Normal
if node.File != nil {
pair = gui.c.MainViewPairs().PatchBuilding
}
return gui.c.RenderToMainViews(types.RefreshMainOpts{
Pair: pair,
Main: &types.ViewUpdateOpts{
Title: gui.Tr.Patch,
Task: task,
},
Secondary: gui.secondaryPatchPanelUpdateOpts(),
})
}
func (gui *Gui) SwitchToCommitFilesContext(opts controllers.SwitchToCommitFilesContextOpts) error {
gui.State.Contexts.CommitFiles.SetSelectedLineIdx(0)
gui.State.Contexts.CommitFiles.SetRef(opts.Ref)
gui.State.Contexts.CommitFiles.SetTitleRef(opts.Ref.Description())
gui.State.Contexts.CommitFiles.SetCanRebase(opts.CanRebase)
gui.State.Contexts.CommitFiles.SetParentContext(opts.Context)
gui.State.Contexts.CommitFiles.SetWindowName(opts.Context.GetWindowName())
if err := gui.c.Refresh(types.RefreshOptions{
Scope: []types.RefreshableView{types.COMMIT_FILES},
}); err != nil {
return err
}
return gui.c.PushContext(gui.State.Contexts.CommitFiles)
}
-36
View File
@@ -1,36 +0,0 @@
package gui
import (
"strconv"
"strings"
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/gui/keybindings"
"github.com/jesseduffield/lazygit/pkg/utils"
)
func (gui *Gui) handleCommitMessageFocused() error {
message := utils.ResolvePlaceholderString(
gui.c.Tr.CommitMessageConfirm,
map[string]string{
"keyBindClose": keybindings.Label(gui.c.UserConfig.Keybinding.Universal.Return),
"keyBindConfirm": keybindings.Label(gui.c.UserConfig.Keybinding.Universal.Confirm),
},
)
gui.RenderCommitLength()
return gui.renderString(gui.Views.Options, message)
}
func (gui *Gui) RenderCommitLength() {
if !gui.c.UserConfig.Gui.CommitLength.Show {
return
}
gui.Views.CommitMessage.Subtitle = getBufferLength(gui.Views.CommitMessage)
}
func getBufferLength(view *gocui.View) string {
return " " + strconv.Itoa(strings.Count(view.TextArea.GetContent(), "")-1) + " "
}
-88
View File
@@ -1,88 +0,0 @@
package gui
import (
"github.com/fsmiamoto/git-todo-parser/todo"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/utils"
)
// after selecting the 200th commit, we'll load in all the rest
const COMMIT_THRESHOLD = 200
// list panel functions
func (gui *Gui) getSelectedLocalCommit() *models.Commit {
return gui.State.Contexts.LocalCommits.GetSelected()
}
func (gui *Gui) onCommitFocus() error {
context := gui.State.Contexts.LocalCommits
if context.GetSelectedLineIdx() > COMMIT_THRESHOLD && context.GetLimitCommits() {
context.SetLimitCommits(false)
go utils.Safe(func() {
if err := gui.refreshCommitsWithLimit(); err != nil {
_ = gui.c.Error(err)
}
})
}
return nil
}
func (gui *Gui) branchCommitsRenderToMain() error {
var task types.UpdateTask
commit := gui.State.Contexts.LocalCommits.GetSelected()
if commit == nil {
task = types.NewRenderStringTask(gui.c.Tr.NoCommitsThisBranch)
} else if commit.Action == todo.UpdateRef {
task = types.NewRenderStringTask(
utils.ResolvePlaceholderString(
gui.c.Tr.UpdateRefHere,
map[string]string{
"ref": commit.Name,
}))
} else {
cmdObj := gui.git.Commit.ShowCmdObj(commit.Sha, gui.State.Modes.Filtering.GetPath(),
gui.IgnoreWhitespaceInDiffView)
task = types.NewRunPtyTask(cmdObj.GetCmd())
}
return gui.c.RenderToMainViews(types.RefreshMainOpts{
Pair: gui.c.MainViewPairs().Normal,
Main: &types.ViewUpdateOpts{
Title: "Patch",
Task: task,
},
Secondary: gui.secondaryPatchPanelUpdateOpts(),
})
}
func (gui *Gui) secondaryPatchPanelUpdateOpts() *types.ViewUpdateOpts {
if gui.git.Patch.PatchBuilder.Active() {
patch := gui.git.Patch.PatchBuilder.RenderAggregatedPatch(false)
return &types.ViewUpdateOpts{
Task: types.NewRenderStringWithoutScrollTask(patch),
Title: gui.Tr.CustomPatch,
}
}
return nil
}
func (gui *Gui) refForLog() string {
bisectInfo := gui.git.Bisect.GetInfo()
gui.State.Model.BisectInfo = bisectInfo
if !bisectInfo.Started() {
return "HEAD"
}
// need to see if our bisect's current commit is reachable from our 'new' ref.
if bisectInfo.Bisecting() && !gui.git.Bisect.ReachableFromStart(bisectInfo) {
return bisectInfo.GetNewSha()
}
return bisectInfo.GetStartSha()
}
-311
View File
@@ -1,311 +0,0 @@
package gui
import (
"context"
"fmt"
"strings"
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/gui/keybindings"
"github.com/jesseduffield/lazygit/pkg/gui/style"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/theme"
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/mattn/go-runewidth"
)
// This file is for the rendering of confirmation panels along with setting and handling associated
// keybindings.
func (gui *Gui) wrappedConfirmationFunction(cancel context.CancelFunc, function func() error) func() error {
return func() error {
cancel()
if err := gui.c.PopContext(); err != nil {
return err
}
if function != nil {
if err := function(); err != nil {
return gui.c.Error(err)
}
}
return nil
}
}
func (gui *Gui) wrappedPromptConfirmationFunction(cancel context.CancelFunc, function func(string) error, getResponse func() string) func() error {
return func() error {
cancel()
if err := gui.c.PopContext(); err != nil {
return err
}
if function != nil {
if err := function(getResponse()); err != nil {
return gui.c.Error(err)
}
}
return nil
}
}
func (gui *Gui) deactivateConfirmationPrompt() {
gui.Mutexes.PopupMutex.Lock()
gui.State.CurrentPopupOpts = nil
gui.Mutexes.PopupMutex.Unlock()
gui.Views.Confirmation.Visible = false
gui.Views.Suggestions.Visible = false
gui.clearConfirmationViewKeyBindings()
}
func (gui *Gui) getMessageHeight(wrap bool, message string, width int) int {
lines := strings.Split(message, "\n")
lineCount := 0
// if we need to wrap, calculate height to fit content within view's width
if wrap {
for _, line := range lines {
lineCount += runewidth.StringWidth(line)/width + 1
}
} else {
lineCount = len(lines)
}
return lineCount
}
func (gui *Gui) getPopupPanelDimensionsForContentHeight(panelWidth, contentHeight int) (int, int, int, int) {
return gui.getPopupPanelDimensionsAux(panelWidth, contentHeight)
}
func (gui *Gui) getPopupPanelDimensionsAux(panelWidth int, panelHeight int) (int, int, int, int) {
width, height := gui.g.Size()
if panelHeight > height*3/4 {
panelHeight = height * 3 / 4
}
return width/2 - panelWidth/2,
height/2 - panelHeight/2 - panelHeight%2 - 1,
width/2 + panelWidth/2,
height/2 + panelHeight/2
}
func (gui *Gui) getConfirmationPanelWidth() int {
width, _ := gui.g.Size()
// we want a minimum width up to a point, then we do it based on ratio.
panelWidth := 4 * width / 7
minWidth := 80
if panelWidth < minWidth {
if width-2 < minWidth {
panelWidth = width - 2
} else {
panelWidth = minWidth
}
}
return panelWidth
}
func (gui *Gui) prepareConfirmationPanel(
ctx context.Context,
opts types.ConfirmOpts,
) error {
gui.Views.Confirmation.HasLoader = opts.HasLoader
if opts.HasLoader {
gui.g.StartTicking(ctx)
}
gui.Views.Confirmation.Title = opts.Title
// for now we do not support wrapping in our editor
gui.Views.Confirmation.Wrap = !opts.Editable
gui.Views.Confirmation.FgColor = theme.GocuiDefaultTextColor
gui.Views.Confirmation.Mask = runeForMask(opts.Mask)
_ = gui.Views.Confirmation.SetOrigin(0, 0)
gui.findSuggestions = opts.FindSuggestionsFunc
if opts.FindSuggestionsFunc != nil {
suggestionsView := gui.Views.Suggestions
suggestionsView.Wrap = false
suggestionsView.FgColor = theme.GocuiDefaultTextColor
gui.setSuggestions(opts.FindSuggestionsFunc(""))
suggestionsView.Visible = true
suggestionsView.Title = fmt.Sprintf(gui.c.Tr.SuggestionsTitle, gui.c.UserConfig.Keybinding.Universal.TogglePanel)
}
gui.resizeConfirmationPanel()
return nil
}
func runeForMask(mask bool) rune {
if mask {
return '*'
}
return 0
}
func (gui *Gui) createPopupPanel(ctx context.Context, opts types.CreatePopupPanelOpts) error {
gui.Mutexes.PopupMutex.Lock()
defer gui.Mutexes.PopupMutex.Unlock()
ctx, cancel := context.WithCancel(ctx)
// we don't allow interruptions of non-loader popups in case we get stuck somehow
// e.g. a credentials popup never gets its required user input so a process hangs
// forever.
// The proper solution is to have a queue of popup options
if gui.State.CurrentPopupOpts != nil && !gui.State.CurrentPopupOpts.HasLoader {
gui.Log.Error("ignoring create popup panel because a popup panel is already open")
cancel()
return nil
}
// remove any previous keybindings
gui.clearConfirmationViewKeyBindings()
err := gui.prepareConfirmationPanel(
ctx,
types.ConfirmOpts{
Title: opts.Title,
Prompt: opts.Prompt,
HasLoader: opts.HasLoader,
FindSuggestionsFunc: opts.FindSuggestionsFunc,
Editable: opts.Editable,
Mask: opts.Mask,
})
if err != nil {
cancel()
return err
}
confirmationView := gui.Views.Confirmation
confirmationView.Editable = opts.Editable
confirmationView.Editor = gocui.EditorFunc(gui.promptEditor)
if opts.Editable {
textArea := confirmationView.TextArea
textArea.Clear()
textArea.TypeString(opts.Prompt)
gui.resizeConfirmationPanel()
confirmationView.RenderTextArea()
} else {
if err := gui.renderString(confirmationView, style.AttrBold.Sprint(opts.Prompt)); err != nil {
cancel()
return err
}
}
if err := gui.setKeyBindings(cancel, opts); err != nil {
cancel()
return err
}
gui.State.CurrentPopupOpts = &opts
return gui.c.PushContext(gui.State.Contexts.Confirmation)
}
func (gui *Gui) setKeyBindings(cancel context.CancelFunc, opts types.CreatePopupPanelOpts) error {
actions := utils.ResolvePlaceholderString(
gui.c.Tr.CloseConfirm,
map[string]string{
"keyBindClose": "esc",
"keyBindConfirm": "enter",
},
)
_ = gui.renderString(gui.Views.Options, actions)
var onConfirm func() error
if opts.HandleConfirmPrompt != nil {
onConfirm = gui.wrappedPromptConfirmationFunction(cancel, opts.HandleConfirmPrompt, func() string { return gui.Views.Confirmation.TextArea.GetContent() })
} else {
onConfirm = gui.wrappedConfirmationFunction(cancel, opts.HandleConfirm)
}
keybindingConfig := gui.c.UserConfig.Keybinding
onSuggestionConfirm := gui.wrappedPromptConfirmationFunction(
cancel,
opts.HandleConfirmPrompt,
gui.getSelectedSuggestionValue,
)
bindings := []*types.Binding{
{
ViewName: "confirmation",
Key: keybindings.GetKey(keybindingConfig.Universal.Confirm),
Handler: onConfirm,
},
{
ViewName: "confirmation",
Key: keybindings.GetKey(keybindingConfig.Universal.Return),
Handler: gui.wrappedConfirmationFunction(cancel, opts.HandleClose),
},
{
ViewName: "confirmation",
Key: keybindings.GetKey(keybindingConfig.Universal.TogglePanel),
Handler: func() error {
if len(gui.State.Suggestions) > 0 {
return gui.replaceContext(gui.State.Contexts.Suggestions)
}
return nil
},
},
{
ViewName: "suggestions",
Key: keybindings.GetKey(keybindingConfig.Universal.Confirm),
Handler: onSuggestionConfirm,
},
{
ViewName: "suggestions",
Key: keybindings.GetKey(keybindingConfig.Universal.Return),
Handler: gui.wrappedConfirmationFunction(cancel, opts.HandleClose),
},
{
ViewName: "suggestions",
Key: keybindings.GetKey(keybindingConfig.Universal.TogglePanel),
Handler: func() error { return gui.replaceContext(gui.State.Contexts.Confirmation) },
},
}
for _, binding := range bindings {
if err := gui.SetKeybinding(binding); err != nil {
return err
}
}
return nil
}
func (gui *Gui) clearConfirmationViewKeyBindings() {
keybindingConfig := gui.c.UserConfig.Keybinding
_ = gui.g.DeleteKeybinding("confirmation", keybindings.GetKey(keybindingConfig.Universal.Confirm), gocui.ModNone)
_ = gui.g.DeleteKeybinding("confirmation", keybindings.GetKey(keybindingConfig.Universal.Return), gocui.ModNone)
_ = gui.g.DeleteKeybinding("suggestions", keybindings.GetKey(keybindingConfig.Universal.Confirm), gocui.ModNone)
_ = gui.g.DeleteKeybinding("suggestions", keybindings.GetKey(keybindingConfig.Universal.Return), gocui.ModNone)
}
func (gui *Gui) refreshSuggestions() {
gui.suggestionsAsyncHandler.Do(func() func() {
findSuggestionsFn := gui.findSuggestions
if findSuggestionsFn != nil {
suggestions := gui.findSuggestions(gui.c.GetPromptInput())
return func() { gui.setSuggestions(suggestions) }
} else {
return func() {}
}
})
}
func (gui *Gui) handleAskFocused() error {
keybindingConfig := gui.c.UserConfig.Keybinding
message := utils.ResolvePlaceholderString(
gui.c.Tr.CloseConfirm,
map[string]string{
"keyBindClose": keybindings.Label(keybindingConfig.Universal.Return),
"keyBindConfirm": keybindings.Label(keybindingConfig.Universal.Confirm),
},
)
return gui.renderString(gui.Views.Options, message)
}
+3
View File
@@ -0,0 +1,3 @@
package constants
const SEARCH_PREFIX = "search: "
+132 -215
View File
@@ -1,12 +1,10 @@
package gui
import (
"sort"
"strings"
"errors"
"sync"
"github.com/jesseduffield/generics/maps"
"github.com/jesseduffield/generics/slices"
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/gui/context"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/samber/lo"
@@ -17,46 +15,66 @@ import (
// you in the menu context. When contexts are activated/deactivated certain things need
// to happen like showing/hiding views and rendering content.
func (gui *Gui) popupViewNames() []string {
popups := slices.Filter(gui.State.Contexts.Flatten(), func(c types.Context) bool {
return c.GetKind() == types.PERSISTENT_POPUP || c.GetKind() == types.TEMPORARY_POPUP
})
type ContextMgr struct {
ContextStack []types.Context
sync.RWMutex
gui *Gui
return slices.Map(popups, func(c types.Context) string {
return c.GetViewName()
})
allContexts *context.ContextTree
}
// use replaceContext when you don't want to return to the original context upon
func NewContextMgr(
gui *Gui,
allContexts *context.ContextTree,
) *ContextMgr {
return &ContextMgr{
ContextStack: []types.Context{},
RWMutex: sync.RWMutex{},
gui: gui,
allContexts: allContexts,
}
}
// use when you don't want to return to the original context upon
// hitting escape: you want to go that context's parent instead.
func (gui *Gui) replaceContext(c types.Context) error {
func (self *ContextMgr) Replace(c types.Context) error {
if !c.IsFocusable() {
return nil
}
gui.State.ContextManager.Lock()
self.Lock()
if len(gui.State.ContextManager.ContextStack) == 0 {
gui.State.ContextManager.ContextStack = []types.Context{c}
if len(self.ContextStack) == 0 {
self.ContextStack = []types.Context{c}
} else {
// replace the last item with the given item
gui.State.ContextManager.ContextStack = append(gui.State.ContextManager.ContextStack[0:len(gui.State.ContextManager.ContextStack)-1], c)
self.ContextStack = append(self.ContextStack[0:len(self.ContextStack)-1], c)
}
defer gui.State.ContextManager.Unlock()
defer self.Unlock()
return gui.activateContext(c, types.OnFocusOpts{})
return self.ActivateContext(c, types.OnFocusOpts{})
}
func (gui *Gui) pushContext(c types.Context, opts types.OnFocusOpts) error {
func (self *ContextMgr) Push(c types.Context, opts ...types.OnFocusOpts) error {
if len(opts) > 1 {
return errors.New("cannot pass multiple opts to Push")
}
singleOpts := types.OnFocusOpts{}
if len(opts) > 0 {
// using triple dot but you should only ever pass one of these opt structs
singleOpts = opts[0]
}
if !c.IsFocusable() {
return nil
}
contextsToDeactivate, contextToActivate := gui.pushToContextStack(c)
contextsToDeactivate, contextToActivate := self.pushToContextStack(c)
for _, contextToDeactivate := range contextsToDeactivate {
if err := gui.deactivateContext(contextToDeactivate, types.OnFocusLostOpts{NewContextKey: c.GetKey()}); err != nil {
if err := self.deactivateContext(contextToDeactivate, types.OnFocusLostOpts{NewContextKey: c.GetKey()}); err != nil {
return err
}
}
@@ -65,43 +83,45 @@ func (gui *Gui) pushContext(c types.Context, opts types.OnFocusOpts) error {
return nil
}
return gui.activateContext(contextToActivate, opts)
return self.ActivateContext(contextToActivate, singleOpts)
}
// Adjusts the context stack based on the context that's being pushed and
// returns (contexts to deactivate, context to activate)
func (gui *Gui) pushToContextStack(c types.Context) ([]types.Context, types.Context) {
func (self *ContextMgr) pushToContextStack(c types.Context) ([]types.Context, types.Context) {
contextsToDeactivate := []types.Context{}
gui.State.ContextManager.Lock()
defer gui.State.ContextManager.Unlock()
self.Lock()
defer self.Unlock()
if len(gui.State.ContextManager.ContextStack) > 0 &&
c == gui.State.ContextManager.ContextStack[len(gui.State.ContextManager.ContextStack)-1] {
if len(self.ContextStack) > 0 &&
c.GetKey() == self.ContextStack[len(self.ContextStack)-1].GetKey() {
// Context being pushed is already on top of the stack: nothing to
// deactivate or activate
return contextsToDeactivate, nil
}
if len(gui.State.ContextManager.ContextStack) == 0 {
gui.State.ContextManager.ContextStack = append(gui.State.ContextManager.ContextStack, c)
if len(self.ContextStack) == 0 {
self.ContextStack = append(self.ContextStack, c)
} else if c.GetKind() == types.SIDE_CONTEXT {
// if we are switching to a side context, remove all other contexts in the stack
contextsToDeactivate = gui.State.ContextManager.ContextStack
gui.State.ContextManager.ContextStack = []types.Context{c}
contextsToDeactivate = lo.Filter(self.ContextStack, func(context types.Context, _ int) bool {
return context.GetKey() != c.GetKey()
})
self.ContextStack = []types.Context{c}
} else if c.GetKind() == types.MAIN_CONTEXT {
// if we're switching to a main context, remove all other main contexts in the stack
contextsToKeep := []types.Context{}
for _, stackContext := range gui.State.ContextManager.ContextStack {
for _, stackContext := range self.ContextStack {
if stackContext.GetKind() == types.MAIN_CONTEXT {
contextsToDeactivate = append(contextsToDeactivate, stackContext)
} else {
contextsToKeep = append(contextsToKeep, stackContext)
}
}
gui.State.ContextManager.ContextStack = append(contextsToKeep, c)
self.ContextStack = append(contextsToKeep, c)
} else {
topContext := gui.currentContextWithoutLock()
topContext := self.currentContextWithoutLock()
// if we're pushing the same context on, we do nothing.
if topContext.GetKey() != c.GetKey() {
@@ -114,48 +134,48 @@ func (gui *Gui) pushToContextStack(c types.Context) ([]types.Context, types.Cont
(topContext.GetKind() == types.MAIN_CONTEXT && c.GetKind() == types.MAIN_CONTEXT) {
contextsToDeactivate = append(contextsToDeactivate, topContext)
_, gui.State.ContextManager.ContextStack = slices.Pop(gui.State.ContextManager.ContextStack)
_, self.ContextStack = slices.Pop(self.ContextStack)
}
gui.State.ContextManager.ContextStack = append(gui.State.ContextManager.ContextStack, c)
self.ContextStack = append(self.ContextStack, c)
}
}
return contextsToDeactivate, c
}
func (gui *Gui) popContext() error {
gui.State.ContextManager.Lock()
func (self *ContextMgr) Pop() error {
self.Lock()
if len(gui.State.ContextManager.ContextStack) == 1 {
if len(self.ContextStack) == 1 {
// cannot escape from bottommost context
gui.State.ContextManager.Unlock()
self.Unlock()
return nil
}
var currentContext types.Context
currentContext, gui.State.ContextManager.ContextStack = slices.Pop(gui.State.ContextManager.ContextStack)
currentContext, self.ContextStack = slices.Pop(self.ContextStack)
newContext := gui.State.ContextManager.ContextStack[len(gui.State.ContextManager.ContextStack)-1]
newContext := self.ContextStack[len(self.ContextStack)-1]
gui.State.ContextManager.Unlock()
self.Unlock()
if err := gui.deactivateContext(currentContext, types.OnFocusLostOpts{NewContextKey: newContext.GetKey()}); err != nil {
if err := self.deactivateContext(currentContext, types.OnFocusLostOpts{NewContextKey: newContext.GetKey()}); err != nil {
return err
}
return gui.activateContext(newContext, types.OnFocusOpts{})
return self.ActivateContext(newContext, types.OnFocusOpts{})
}
func (gui *Gui) removeContexts(contextsToRemove []types.Context) error {
gui.State.ContextManager.Lock()
func (self *ContextMgr) RemoveContexts(contextsToRemove []types.Context) error {
self.Lock()
if len(gui.State.ContextManager.ContextStack) == 1 {
gui.State.ContextManager.Unlock()
if len(self.ContextStack) == 1 {
self.Unlock()
return nil
}
rest := lo.Filter(gui.State.ContextManager.ContextStack, func(context types.Context, _ int) bool {
rest := lo.Filter(self.ContextStack, func(context types.Context, _ int) bool {
for _, contextToRemove := range contextsToRemove {
if context.GetKey() == contextToRemove.GetKey() {
return false
@@ -163,25 +183,25 @@ func (gui *Gui) removeContexts(contextsToRemove []types.Context) error {
}
return true
})
gui.State.ContextManager.ContextStack = rest
self.ContextStack = rest
contextToActivate := rest[len(rest)-1]
gui.State.ContextManager.Unlock()
self.Unlock()
for _, context := range contextsToRemove {
if err := gui.deactivateContext(context, types.OnFocusLostOpts{NewContextKey: contextToActivate.GetKey()}); err != nil {
if err := self.deactivateContext(context, types.OnFocusLostOpts{NewContextKey: contextToActivate.GetKey()}); err != nil {
return err
}
}
// activate the item at the top of the stack
return gui.activateContext(contextToActivate, types.OnFocusOpts{})
return self.ActivateContext(contextToActivate, types.OnFocusOpts{})
}
func (gui *Gui) deactivateContext(c types.Context, opts types.OnFocusLostOpts) error {
view, _ := gui.g.View(c.GetViewName())
func (self *ContextMgr) deactivateContext(c types.Context, opts types.OnFocusLostOpts) error {
view, _ := self.gui.c.GocuiGui().View(c.GetViewName())
if view != nil && view.IsSearching() {
if err := gui.onSearchEscape(); err != nil {
if err := self.gui.onSearchEscape(); err != nil {
return err
}
}
@@ -200,34 +220,17 @@ func (gui *Gui) deactivateContext(c types.Context, opts types.OnFocusLostOpts) e
return nil
}
// postRefreshUpdate is to be called on a context after the state that it depends on has been refreshed
// if the context's view is set to another context we do nothing.
// if the context's view is the current view we trigger a focus; re-selecting the current item.
func (gui *Gui) postRefreshUpdate(c types.Context) error {
if err := c.HandleRender(); err != nil {
return err
}
if gui.currentViewName() == c.GetViewName() {
if err := c.HandleFocus(types.OnFocusOpts{}); err != nil {
return err
}
}
return nil
}
func (gui *Gui) activateContext(c types.Context, opts types.OnFocusOpts) error {
func (self *ContextMgr) ActivateContext(c types.Context, opts types.OnFocusOpts) error {
viewName := c.GetViewName()
v, err := gui.g.View(viewName)
v, err := self.gui.c.GocuiGui().View(viewName)
if err != nil {
return err
}
gui.setWindowContext(c)
self.gui.helpers.Window.SetWindowContext(c)
gui.moveToTopOfWindow(c)
if _, err := gui.g.SetCurrentView(viewName); err != nil {
self.gui.helpers.Window.MoveToTopOfWindow(c)
if _, err := self.gui.c.GocuiGui().SetCurrentView(viewName); err != nil {
return err
}
@@ -238,14 +241,9 @@ func (gui *Gui) activateContext(c types.Context, opts types.OnFocusOpts) error {
v.Visible = true
gui.g.Cursor = v.Editable
self.gui.c.GocuiGui().Cursor = v.Editable
// render the options available for the current context at the bottom of the screen
optionsMap := c.GetOptionsMap()
if optionsMap == nil {
optionsMap = gui.globalOptionsMap()
}
gui.renderOptionsMap(optionsMap)
self.gui.renderContextOptionsMap(c)
if err := c.HandleFocus(opts); err != nil {
return err
@@ -254,63 +252,27 @@ func (gui *Gui) activateContext(c types.Context, opts types.OnFocusOpts) error {
return nil
}
func (gui *Gui) optionsMapToString(optionsMap map[string]string) string {
options := maps.MapToSlice(optionsMap, func(key string, description string) string {
return key + ": " + description
})
sort.Strings(options)
return strings.Join(options, ", ")
func (self *ContextMgr) Current() types.Context {
self.RLock()
defer self.RUnlock()
return self.currentContextWithoutLock()
}
func (gui *Gui) renderOptionsMap(optionsMap map[string]string) {
_ = gui.renderString(gui.Views.Options, gui.optionsMapToString(optionsMap))
}
// // currently unused
// func (gui *Gui) renderContextStack() string {
// result := ""
// for _, context := range gui.State.ContextManager.ContextStack {
// result += string(context.GetKey()) + "\n"
// }
// return result
// }
func (gui *Gui) currentContext() types.Context {
gui.State.ContextManager.RLock()
defer gui.State.ContextManager.RUnlock()
return gui.currentContextWithoutLock()
}
func (gui *Gui) currentContextWithoutLock() types.Context {
if len(gui.State.ContextManager.ContextStack) == 0 {
return gui.defaultSideContext()
func (self *ContextMgr) currentContextWithoutLock() types.Context {
if len(self.ContextStack) == 0 {
return self.gui.defaultSideContext()
}
return gui.State.ContextManager.ContextStack[len(gui.State.ContextManager.ContextStack)-1]
return self.ContextStack[len(self.ContextStack)-1]
}
// the status panel is not yet a list context (and may never be), so this method is not
// quite the same as currentSideContext()
func (gui *Gui) currentSideListContext() types.IListContext {
context := gui.currentSideContext()
listContext, ok := context.(types.IListContext)
if !ok {
return nil
}
return listContext
}
// Note that this could return the 'status' context which is not itself a list context.
func (self *ContextMgr) CurrentSide() types.Context {
self.RLock()
defer self.RUnlock()
func (gui *Gui) currentSideContext() types.Context {
gui.State.ContextManager.RLock()
defer gui.State.ContextManager.RUnlock()
stack := gui.State.ContextManager.ContextStack
// on startup the stack can be empty so we'll return an empty string in that case
if len(stack) == 0 {
return gui.defaultSideContext()
}
stack := self.ContextStack
// find the first context in the stack with the type of types.SIDE_CONTEXT
for i := range stack {
@@ -321,22 +283,22 @@ func (gui *Gui) currentSideContext() types.Context {
}
}
return gui.defaultSideContext()
return self.gui.defaultSideContext()
}
// static as opposed to popup
func (gui *Gui) currentStaticContext() types.Context {
gui.State.ContextManager.RLock()
defer gui.State.ContextManager.RUnlock()
func (self *ContextMgr) CurrentStatic() types.Context {
self.RLock()
defer self.RUnlock()
return gui.currentStaticContextWithoutLock()
return self.currentStaticContextWithoutLock()
}
func (gui *Gui) currentStaticContextWithoutLock() types.Context {
stack := gui.State.ContextManager.ContextStack
func (self *ContextMgr) currentStaticContextWithoutLock() types.Context {
stack := self.ContextStack
if len(stack) == 0 {
return gui.defaultSideContext()
return self.gui.defaultSideContext()
}
// find the first context in the stack without a popup type
@@ -348,88 +310,43 @@ func (gui *Gui) currentStaticContextWithoutLock() types.Context {
}
}
return gui.defaultSideContext()
return self.gui.defaultSideContext()
}
func (gui *Gui) defaultSideContext() types.Context {
if gui.State.Modes.Filtering.Active() {
return gui.State.Contexts.LocalCommits
} else {
return gui.State.Contexts.Files
func (self *ContextMgr) ForEach(f func(types.Context)) {
self.RLock()
defer self.RUnlock()
for _, context := range self.gui.State.ContextMgr.ContextStack {
f(context)
}
}
// getFocusLayout returns a manager function for when view gain and lose focus
func (gui *Gui) getFocusLayout() func(g *gocui.Gui) error {
var previousView *gocui.View
return func(g *gocui.Gui) error {
newView := gui.g.CurrentView()
// for now we don't consider losing focus to a popup panel as actually losing focus
if newView != previousView && !gui.isPopupPanel(newView.Name()) {
if err := gui.onViewFocusLost(previousView); err != nil {
return err
}
func (self *ContextMgr) IsCurrent(c types.Context) bool {
return self.Current().GetKey() == c.GetKey()
}
previousView = newView
// all list contexts
func (self *ContextMgr) AllList() []types.IListContext {
var listContexts []types.IListContext
for _, context := range self.allContexts.Flatten() {
if listContext, ok := context.(types.IListContext); ok {
listContexts = append(listContexts, listContext)
}
return nil
}
}
func (gui *Gui) onViewFocusLost(oldView *gocui.View) error {
if oldView == nil {
return nil
}
oldView.Highlight = false
_ = oldView.SetOriginX(0)
return nil
return listContexts
}
func (gui *Gui) TransientContexts() []types.Context {
return slices.Filter(gui.State.Contexts.Flatten(), func(context types.Context) bool {
return context.IsTransient()
})
}
func (self *ContextMgr) AllPatchExplorer() []types.IPatchExplorerContext {
var listContexts []types.IPatchExplorerContext
func (gui *Gui) rerenderView(view *gocui.View) error {
context, ok := gui.contextForView(view.Name())
if !ok {
gui.Log.Errorf("no context found for view %s", view.Name())
return nil
for _, context := range self.allContexts.Flatten() {
if listContext, ok := context.(types.IPatchExplorerContext); ok {
listContexts = append(listContexts, listContext)
}
}
return context.HandleRender()
return listContexts
}
func (gui *Gui) getSideContextSelectedItemId() string {
currentSideContext := gui.currentSideListContext()
if currentSideContext == nil {
return ""
}
return currentSideContext.GetSelectedItemId()
}
// currently unused
// func (gui *Gui) getCurrentSideView() *gocui.View {
// currentSideContext := gui.currentSideContext()
// if currentSideContext == nil {
// return nil
// }
// view, _ := gui.g.View(currentSideContext.GetViewName())
// return view
// }
// currently unused
// func (gui *Gui) renderContextStack() string {
// result := ""
// for _, context := range gui.State.ContextManager.ContextStack {
// result += context.GetViewName() + "\n"
// }
// return result
// }
+38
View File
@@ -16,6 +16,9 @@ type BaseContext struct {
keybindingsFns []types.KeybindingsFn
mouseKeybindingsFns []types.MouseKeybindingsFn
onClickFn func() error
onRenderToMainFn func() error
onFocusFn onFocusFn
onFocusLostFn onFocusLostFn
focusable bool
transient bool
@@ -25,6 +28,11 @@ type BaseContext struct {
*ParentContextMgr
}
type (
onFocusFn = func(types.OnFocusOpts) error
onFocusLostFn = func(types.OnFocusLostOpts) error
)
var _ types.IBaseContext = &BaseContext{}
type NewBaseContextOpts struct {
@@ -129,6 +137,36 @@ func (self *BaseContext) GetOnClick() func() error {
return self.onClickFn
}
func (self *BaseContext) AddOnRenderToMainFn(fn func() error) {
if fn != nil {
self.onRenderToMainFn = fn
}
}
func (self *BaseContext) GetOnRenderToMain() func() error {
return self.onRenderToMainFn
}
func (self *BaseContext) AddOnFocusFn(fn onFocusFn) {
if fn != nil {
self.onFocusFn = fn
}
}
func (self *BaseContext) GetOnFocus() onFocusFn {
return self.onFocusFn
}
func (self *BaseContext) AddOnFocusLostFn(fn onFocusLostFn) {
if fn != nil {
self.onFocusLostFn = fn
}
}
func (self *BaseContext) GetOnFocusLost() onFocusLostFn {
return self.onFocusLostFn
}
func (self *BaseContext) GetMouseKeybindings(opts types.KeybindingsOpts) []*gocui.ViewMouseBinding {
bindings := []*gocui.ViewMouseBinding{}
for i := range self.mouseKeybindingsFns {
+33 -20
View File
@@ -1,8 +1,8 @@
package context
import (
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
@@ -11,40 +11,40 @@ type BranchesContext struct {
*ListContextTrait
}
var _ types.IListContext = (*BranchesContext)(nil)
var (
_ types.IListContext = (*BranchesContext)(nil)
_ types.DiffableContext = (*BranchesContext)(nil)
)
func NewBranchesContext(
getModel func() []*models.Branch,
view *gocui.View,
getDisplayStrings func(startIdx int, length int) [][]string,
func NewBranchesContext(c *ContextCommon) *BranchesContext {
viewModel := NewBasicViewModel(func() []*models.Branch { return c.Model().Branches })
onFocus func(types.OnFocusOpts) error,
onRenderToMain func() error,
onFocusLost func(opts types.OnFocusLostOpts) error,
getDisplayStrings := func(startIdx int, length int) [][]string {
return presentation.GetBranchListDisplayStrings(
c.Model().Branches,
c.State().GetRepoState().GetScreenMode() != types.SCREEN_NORMAL,
c.Modes().Diffing.Ref,
c.Tr,
)
}
c *types.HelperCommon,
) *BranchesContext {
viewModel := NewBasicViewModel(getModel)
return &BranchesContext{
self := &BranchesContext{
BasicViewModel: viewModel,
ListContextTrait: &ListContextTrait{
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
View: view,
View: c.Views().Branches,
WindowName: "branches",
Key: LOCAL_BRANCHES_CONTEXT_KEY,
Kind: types.SIDE_CONTEXT,
Focusable: true,
}), ContextCallbackOpts{
OnFocus: onFocus,
OnFocusLost: onFocusLost,
OnRenderToMain: onRenderToMain,
}),
})),
list: viewModel,
getDisplayStrings: getDisplayStrings,
c: c,
},
}
return self
}
func (self *BranchesContext) GetSelectedItemId() string {
@@ -63,3 +63,16 @@ func (self *BranchesContext) GetSelectedRef() types.Ref {
}
return branch
}
func (self *BranchesContext) GetDiffTerminals() []string {
// for our local branches we want to include both the branch and its upstream
branch := self.GetSelected()
if branch != nil {
names := []string{branch.ID()}
if branch.IsTrackingRemote() {
names = append(names, branch.ID()+"@{u}")
}
return names
}
return nil
}
+28 -18
View File
@@ -1,9 +1,11 @@
package context
import (
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/generics/slices"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/gui/filetree"
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
"github.com/jesseduffield/lazygit/pkg/gui/style"
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
@@ -13,20 +15,28 @@ type CommitFilesContext struct {
*DynamicTitleBuilder
}
var _ types.IListContext = (*CommitFilesContext)(nil)
var (
_ types.IListContext = (*CommitFilesContext)(nil)
_ types.DiffableContext = (*CommitFilesContext)(nil)
)
func NewCommitFilesContext(
getModel func() []*models.CommitFile,
view *gocui.View,
getDisplayStrings func(startIdx int, length int) [][]string,
func NewCommitFilesContext(c *ContextCommon) *CommitFilesContext {
viewModel := filetree.NewCommitFileTreeViewModel(
func() []*models.CommitFile { return c.Model().CommitFiles },
c.Log,
c.UserConfig.Gui.ShowFileTree,
)
onFocus func(types.OnFocusOpts) error,
onRenderToMain func() error,
onFocusLost func(opts types.OnFocusLostOpts) error,
getDisplayStrings := func(startIdx int, length int) [][]string {
if viewModel.Len() == 0 {
return [][]string{{style.FgRed.Sprint("(none)")}}
}
c *types.HelperCommon,
) *CommitFilesContext {
viewModel := filetree.NewCommitFileTreeViewModel(getModel, c.Log, c.UserConfig.Gui.ShowFileTree)
lines := presentation.RenderCommitFileTree(viewModel, c.Modes().Diffing.Ref, c.Git().Patch.PatchBuilder)
return slices.Map(lines, func(line string) []string {
return []string{line}
})
}
return &CommitFilesContext{
CommitFileTreeViewModel: viewModel,
@@ -34,18 +44,14 @@ func NewCommitFilesContext(
ListContextTrait: &ListContextTrait{
Context: NewSimpleContext(
NewBaseContext(NewBaseContextOpts{
View: view,
View: c.Views().CommitFiles,
WindowName: "commits",
Key: COMMIT_FILES_CONTEXT_KEY,
Kind: types.SIDE_CONTEXT,
Focusable: true,
Transient: true,
}),
ContextCallbackOpts{
OnFocus: onFocus,
OnFocusLost: onFocusLost,
OnRenderToMain: onRenderToMain,
}),
),
list: viewModel,
getDisplayStrings: getDisplayStrings,
c: c,
@@ -61,3 +67,7 @@ func (self *CommitFilesContext) GetSelectedItemId() string {
return item.ID()
}
func (self *CommitFilesContext) GetDiffTerminals() []string {
return []string{self.GetRef().RefName()}
}
+21 -4
View File
@@ -1,15 +1,21 @@
package context
import (
"strconv"
"strings"
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
type CommitMessageContext struct {
c *ContextCommon
types.Context
viewModel *CommitMessageViewModel
}
var _ types.Context = (*CommitMessageContext)(nil)
// when selectedIndex (see below) is set to this value, it means that we're not
// currently viewing a commit message of an existing commit: instead we're making our own
// new commit message
@@ -35,22 +41,21 @@ type CommitMessageViewModel struct {
}
func NewCommitMessageContext(
view *gocui.View,
opts ContextCallbackOpts,
c *ContextCommon,
) *CommitMessageContext {
viewModel := &CommitMessageViewModel{}
return &CommitMessageContext{
c: c,
viewModel: viewModel,
Context: NewSimpleContext(
NewBaseContext(NewBaseContextOpts{
Kind: types.PERSISTENT_POPUP,
View: view,
View: c.Views().CommitMessage,
WindowName: "commitMessage",
Key: COMMIT_MESSAGE_CONTEXT_KEY,
Focusable: true,
HasUncontrolledBounds: true,
}),
opts,
),
}
}
@@ -93,3 +98,15 @@ func (self *CommitMessageContext) SetPanelState(index int, title string, preserv
self.viewModel.onConfirm = onConfirm
self.GetView().Title = title
}
func (self *CommitMessageContext) RenderCommitLength() {
if !self.c.UserConfig.Gui.CommitLength.Show {
return
}
self.c.Views().CommitMessage.Subtitle = getBufferLength(self.c.Views().CommitMessage)
}
func getBufferLength(view *gocui.View) string {
return " " + strconv.Itoa(strings.Count(view.TextArea.GetContent(), "")-1) + " "
}
+35
View File
@@ -0,0 +1,35 @@
package context
import (
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
type ConfirmationContext struct {
*SimpleContext
c *ContextCommon
State ConfirmationContextState
}
type ConfirmationContextState struct {
OnConfirm func() error
OnClose func() error
}
var _ types.Context = (*ConfirmationContext)(nil)
func NewConfirmationContext(
c *ContextCommon,
) *ConfirmationContext {
return &ConfirmationContext{
c: c,
SimpleContext: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
View: c.Views().Confirmation,
WindowName: "confirmation",
Key: CONFIRMATION_CONTEXT_KEY,
Kind: types.TEMPORARY_POPUP,
Focusable: true,
HasUncontrolledBounds: true,
})),
}
}
+1 -1
View File
@@ -97,7 +97,7 @@ type ContextTree struct {
CustomPatchBuilder *PatchExplorerContext
CustomPatchBuilderSecondary types.Context
MergeConflicts *MergeConflictsContext
Confirmation types.Context
Confirmation *ConfirmationContext
CommitMessage *CommitMessageContext
CommitDescription types.Context
CommandLog types.Context
+11
View File
@@ -0,0 +1,11 @@
package context
import (
"github.com/jesseduffield/lazygit/pkg/common"
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
type ContextCommon struct {
*common.Common
types.IGuiCommon
}
+9 -2
View File
@@ -10,11 +10,15 @@ import (
type ListContextTrait struct {
types.Context
c *types.HelperCommon
c *ContextCommon
list types.IList
getDisplayStrings func(startIdx int, length int) [][]string
// alignment for each column. If nil, the default is left alignment
columnAlignments []utils.Alignment
}
func (self *ListContextTrait) IsListContext() {}
func (self *ListContextTrait) GetList() types.IList {
return self.list
}
@@ -50,7 +54,10 @@ func (self *ListContextTrait) HandleFocusLost(opts types.OnFocusLostOpts) error
// OnFocus assumes that the content of the context has already been rendered to the view. OnRender is the function which actually renders the content to the view
func (self *ListContextTrait) HandleRender() error {
self.list.RefreshSelectedIdx()
content := utils.RenderDisplayStrings(self.getDisplayStrings(0, self.list.Len()))
content := utils.RenderDisplayStrings(
self.getDisplayStrings(0, self.list.Len()),
self.columnAlignments,
)
self.GetViewTrait().SetContent(content)
self.c.Render()
self.setFooter()
+91 -19
View File
@@ -1,8 +1,12 @@
package context
import (
"github.com/jesseduffield/gocui"
"log"
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/commands/types/enums"
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
@@ -11,36 +15,51 @@ type LocalCommitsContext struct {
*ViewportListContextTrait
}
var _ types.IListContext = (*LocalCommitsContext)(nil)
var (
_ types.IListContext = (*LocalCommitsContext)(nil)
_ types.DiffableContext = (*LocalCommitsContext)(nil)
)
func NewLocalCommitsContext(
getModel func() []*models.Commit,
view *gocui.View,
getDisplayStrings func(startIdx int, length int) [][]string,
func NewLocalCommitsContext(c *ContextCommon) *LocalCommitsContext {
viewModel := NewLocalCommitsViewModel(
func() []*models.Commit { return c.Model().Commits },
c,
)
onFocus func(types.OnFocusOpts) error,
onRenderToMain func() error,
onFocusLost func(opts types.OnFocusLostOpts) error,
getDisplayStrings := func(startIdx int, length int) [][]string {
selectedCommitSha := ""
c *types.HelperCommon,
) *LocalCommitsContext {
viewModel := NewLocalCommitsViewModel(getModel, c)
if c.CurrentContext().GetKey() == LOCAL_COMMITS_CONTEXT_KEY {
selectedCommit := viewModel.GetSelected()
if selectedCommit != nil {
selectedCommitSha = selectedCommit.Sha
}
}
showYouAreHereLabel := c.Model().WorkingTreeStateAtLastCommitRefresh == enums.REBASE_MODE_REBASING
return getCommitsDisplayStrings(
c,
c.Model().Commits,
startIdx,
length,
selectedCommitSha,
showYouAreHereLabel,
c.Model().BisectInfo,
)
}
return &LocalCommitsContext{
LocalCommitsViewModel: viewModel,
ViewportListContextTrait: &ViewportListContextTrait{
ListContextTrait: &ListContextTrait{
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
View: view,
View: c.Views().Commits,
WindowName: "commits",
Key: LOCAL_COMMITS_CONTEXT_KEY,
Kind: types.SIDE_CONTEXT,
Focusable: true,
}), ContextCallbackOpts{
OnFocus: onFocus,
OnFocusLost: onFocusLost,
OnRenderToMain: onRenderToMain,
}),
})),
list: viewModel,
getDisplayStrings: getDisplayStrings,
c: c,
@@ -69,7 +88,7 @@ type LocalCommitsViewModel struct {
showWholeGitGraph bool
}
func NewLocalCommitsViewModel(getModel func() []*models.Commit, c *types.HelperCommon) *LocalCommitsViewModel {
func NewLocalCommitsViewModel(getModel func() []*models.Commit, c *ContextCommon) *LocalCommitsViewModel {
self := &LocalCommitsViewModel{
BasicViewModel: NewBasicViewModel(getModel),
limitCommits: true,
@@ -91,6 +110,12 @@ func (self *LocalCommitsContext) GetSelectedRef() types.Ref {
return commit
}
func (self *LocalCommitsContext) GetDiffTerminals() []string {
itemId := self.GetSelectedItemId()
return []string{itemId}
}
func (self *LocalCommitsViewModel) SetLimitCommits(value bool) {
self.limitCommits = value
}
@@ -110,3 +135,50 @@ func (self *LocalCommitsViewModel) GetShowWholeGitGraph() bool {
func (self *LocalCommitsViewModel) GetCommits() []*models.Commit {
return self.getModel()
}
func shouldShowGraph(c *ContextCommon) bool {
if c.Modes().Filtering.Active() {
return false
}
value := c.UserConfig.Git.Log.ShowGraph
switch value {
case "always":
return true
case "never":
return false
case "when-maximised":
return c.State().GetRepoState().GetScreenMode() != types.SCREEN_NORMAL
}
log.Fatalf("Unknown value for git.log.showGraph: %s. Expected one of: 'always', 'never', 'when-maximised'", value)
return false
}
func getCommitsDisplayStrings(
c *ContextCommon,
commits []*models.Commit,
startIdx int,
length int,
selectedCommitSha string,
showYouAreHereLabel bool,
bisectInfo *git_commands.BisectInfo,
) [][]string {
return presentation.GetCommitListDisplayStrings(
c.Common,
commits,
c.State().GetRepoState().GetScreenMode() != types.SCREEN_NORMAL,
c.Modes().CherryPicking.SelectedShaSet(),
c.Modes().Diffing.Ref,
c.UserConfig.Gui.TimeFormat,
c.UserConfig.Git.ParseEmoji,
selectedCommitSha,
startIdx,
length,
shouldShowGraph(c),
bisectInfo,
showYouAreHereLabel,
c.State().GetRepoState().GetRecentCommitsWhichChangedFile(),
c.UserConfig.Gui.ExperimentalMarkCommitsWhichChangedFile,
)
}
+31 -21
View File
@@ -2,13 +2,16 @@ package context
import (
"github.com/jesseduffield/generics/slices"
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/gui/keybindings"
"github.com/jesseduffield/lazygit/pkg/gui/style"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/samber/lo"
)
type MenuContext struct {
c *ContextCommon
*MenuViewModel
*ListContextTrait
}
@@ -16,37 +19,26 @@ type MenuContext struct {
var _ types.IListContext = (*MenuContext)(nil)
func NewMenuContext(
view *gocui.View,
c *types.HelperCommon,
getOptionsMap func() map[string]string,
renderToDescriptionView func(string),
c *ContextCommon,
) *MenuContext {
viewModel := NewMenuViewModel()
onFocus := func(types.OnFocusOpts) error {
selectedMenuItem := viewModel.GetSelected()
renderToDescriptionView(selectedMenuItem.Tooltip)
return nil
}
viewModel := NewMenuViewModel(c)
return &MenuContext{
c: c,
MenuViewModel: viewModel,
ListContextTrait: &ListContextTrait{
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
View: view,
View: c.Views().Menu,
WindowName: "menu",
Key: "menu",
Kind: types.TEMPORARY_POPUP,
OnGetOptionsMap: getOptionsMap,
Focusable: true,
HasUncontrolledBounds: true,
}), ContextCallbackOpts{
OnFocus: onFocus,
}),
})),
getDisplayStrings: viewModel.GetDisplayStrings,
list: viewModel,
c: c,
columnAlignments: []utils.Alignment{utils.AlignRight, utils.AlignLeft},
},
}
}
@@ -62,13 +54,15 @@ func (self *MenuContext) GetSelectedItemId() string {
}
type MenuViewModel struct {
c *ContextCommon
menuItems []*types.MenuItem
*BasicViewModel[*types.MenuItem]
}
func NewMenuViewModel() *MenuViewModel {
func NewMenuViewModel(c *ContextCommon) *MenuViewModel {
self := &MenuViewModel{
menuItems: nil,
c: c,
}
self.BasicViewModel = NewBasicViewModel(func() []*types.MenuItem { return self.menuItems })
@@ -88,9 +82,25 @@ func (self *MenuViewModel) GetDisplayStrings(_startIdx int, _length int) [][]str
return slices.Map(self.menuItems, func(item *types.MenuItem) []string {
displayStrings := item.LabelColumns
if showKeys {
displayStrings = slices.Prepend(displayStrings, style.FgCyan.Sprint(keybindings.LabelFromKey(item.Key)))
if !showKeys {
return displayStrings
}
// These keys are used for general navigation so we'll strike them out to
// avoid confusion
reservedKeys := []string{
self.c.UserConfig.Keybinding.Universal.Confirm,
self.c.UserConfig.Keybinding.Universal.Select,
self.c.UserConfig.Keybinding.Universal.Return,
}
keyLabel := keybindings.LabelFromKey(item.Key)
keyStyle := style.FgCyan
if lo.Contains(reservedKeys, keyLabel) {
keyStyle = style.FgDefault.SetStrikethrough()
}
displayStrings = slices.Prepend(displayStrings, keyStyle.Sprint(keyLabel))
return displayStrings
})
}
+3 -11
View File
@@ -3,7 +3,6 @@ package context
import (
"math"
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/gui/mergeconflicts"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/sasha-s/go-deadlock"
@@ -12,7 +11,7 @@ import (
type MergeConflictsContext struct {
types.Context
viewModel *ConflictsViewModel
c *types.HelperCommon
c *ContextCommon
mutex *deadlock.Mutex
}
@@ -25,12 +24,7 @@ type ConflictsViewModel struct {
}
func NewMergeConflictsContext(
view *gocui.View,
opts ContextCallbackOpts,
c *types.HelperCommon,
getOptionsMap func() map[string]string,
c *ContextCommon,
) *MergeConflictsContext {
viewModel := &ConflictsViewModel{
state: mergeconflicts.NewState(),
@@ -43,14 +37,12 @@ func NewMergeConflictsContext(
Context: NewSimpleContext(
NewBaseContext(NewBaseContextOpts{
Kind: types.MAIN_CONTEXT,
View: view,
View: c.Views().MergeConflicts,
WindowName: "main",
Key: MERGE_CONFLICTS_CONTEXT_KEY,
OnGetOptionsMap: getOptionsMap,
Focusable: true,
HighlightOnFocus: true,
}),
opts,
),
c: c,
}
+5 -8
View File
@@ -13,7 +13,7 @@ type PatchExplorerContext struct {
state *patch_exploring.State
viewTrait *ViewTrait
getIncludedLineIndices func() []int
c *types.HelperCommon
c *ContextCommon
mutex *deadlock.Mutex
}
@@ -24,11 +24,9 @@ func NewPatchExplorerContext(
windowName string,
key types.ContextKey,
onFocus func(types.OnFocusOpts) error,
onFocusLost func(opts types.OnFocusLostOpts) error,
getIncludedLineIndices func() []int,
c *types.HelperCommon,
c *ContextCommon,
) *PatchExplorerContext {
return &PatchExplorerContext{
state: nil,
@@ -43,13 +41,12 @@ func NewPatchExplorerContext(
Kind: types.MAIN_CONTEXT,
Focusable: true,
HighlightOnFocus: true,
}), ContextCallbackOpts{
OnFocus: onFocus,
OnFocusLost: onFocusLost,
}),
})),
}
}
func (self *PatchExplorerContext) IsPatchExplorerContext() {}
func (self *PatchExplorerContext) GetState() *patch_exploring.State {
return self.state
}
+25 -19
View File
@@ -1,8 +1,8 @@
package context
import (
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
@@ -11,35 +11,35 @@ type ReflogCommitsContext struct {
*ListContextTrait
}
var _ types.IListContext = (*ReflogCommitsContext)(nil)
var (
_ types.IListContext = (*ReflogCommitsContext)(nil)
_ types.DiffableContext = (*ReflogCommitsContext)(nil)
)
func NewReflogCommitsContext(
getModel func() []*models.Commit,
view *gocui.View,
getDisplayStrings func(startIdx int, length int) [][]string,
func NewReflogCommitsContext(c *ContextCommon) *ReflogCommitsContext {
viewModel := NewBasicViewModel(func() []*models.Commit { return c.Model().FilteredReflogCommits })
onFocus func(types.OnFocusOpts) error,
onRenderToMain func() error,
onFocusLost func(opts types.OnFocusLostOpts) error,
c *types.HelperCommon,
) *ReflogCommitsContext {
viewModel := NewBasicViewModel(getModel)
getDisplayStrings := func(startIdx int, length int) [][]string {
return presentation.GetReflogCommitListDisplayStrings(
c.Model().FilteredReflogCommits,
c.State().GetRepoState().GetScreenMode() != types.SCREEN_NORMAL,
c.Modes().CherryPicking.SelectedShaSet(),
c.Modes().Diffing.Ref,
c.UserConfig.Gui.TimeFormat,
c.UserConfig.Git.ParseEmoji,
)
}
return &ReflogCommitsContext{
BasicViewModel: viewModel,
ListContextTrait: &ListContextTrait{
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
View: view,
View: c.Views().ReflogCommits,
WindowName: "commits",
Key: REFLOG_COMMITS_CONTEXT_KEY,
Kind: types.SIDE_CONTEXT,
Focusable: true,
}), ContextCallbackOpts{
OnFocus: onFocus,
OnFocusLost: onFocusLost,
OnRenderToMain: onRenderToMain,
}),
})),
list: viewModel,
getDisplayStrings: getDisplayStrings,
c: c,
@@ -71,3 +71,9 @@ func (self *ReflogCommitsContext) GetSelectedRef() types.Ref {
func (self *ReflogCommitsContext) GetCommits() []*models.Commit {
return self.getModel()
}
func (self *ReflogCommitsContext) GetDiffTerminals() []string {
itemId := self.GetSelectedItemId()
return []string{itemId}
}
+19 -18
View File
@@ -1,8 +1,8 @@
package context
import (
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
@@ -12,37 +12,32 @@ type RemoteBranchesContext struct {
*DynamicTitleBuilder
}
var _ types.IListContext = (*RemoteBranchesContext)(nil)
var (
_ types.IListContext = (*RemoteBranchesContext)(nil)
_ types.DiffableContext = (*RemoteBranchesContext)(nil)
)
func NewRemoteBranchesContext(
getModel func() []*models.RemoteBranch,
view *gocui.View,
getDisplayStrings func(startIdx int, length int) [][]string,
onFocus func(types.OnFocusOpts) error,
onRenderToMain func() error,
onFocusLost func(opts types.OnFocusLostOpts) error,
c *types.HelperCommon,
c *ContextCommon,
) *RemoteBranchesContext {
viewModel := NewBasicViewModel(getModel)
viewModel := NewBasicViewModel(func() []*models.RemoteBranch { return c.Model().RemoteBranches })
getDisplayStrings := func(startIdx int, length int) [][]string {
return presentation.GetRemoteBranchListDisplayStrings(c.Model().RemoteBranches, c.Modes().Diffing.Ref)
}
return &RemoteBranchesContext{
BasicViewModel: viewModel,
DynamicTitleBuilder: NewDynamicTitleBuilder(c.Tr.RemoteBranchesDynamicTitle),
ListContextTrait: &ListContextTrait{
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
View: view,
View: c.Views().RemoteBranches,
WindowName: "branches",
Key: REMOTE_BRANCHES_CONTEXT_KEY,
Kind: types.SIDE_CONTEXT,
Focusable: true,
Transient: true,
}), ContextCallbackOpts{
OnFocus: onFocus,
OnFocusLost: onFocusLost,
OnRenderToMain: onRenderToMain,
}),
})),
list: viewModel,
getDisplayStrings: getDisplayStrings,
c: c,
@@ -66,3 +61,9 @@ func (self *RemoteBranchesContext) GetSelectedRef() types.Ref {
}
return remoteBranch
}
func (self *RemoteBranchesContext) GetDiffTerminals() []string {
itemId := self.GetSelectedItemId()
return []string{itemId}
}
+18 -19
View File
@@ -1,8 +1,8 @@
package context
import (
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
@@ -11,35 +11,28 @@ type RemotesContext struct {
*ListContextTrait
}
var _ types.IListContext = (*RemotesContext)(nil)
var (
_ types.IListContext = (*RemotesContext)(nil)
_ types.DiffableContext = (*RemotesContext)(nil)
)
func NewRemotesContext(
getModel func() []*models.Remote,
view *gocui.View,
getDisplayStrings func(startIdx int, length int) [][]string,
func NewRemotesContext(c *ContextCommon) *RemotesContext {
viewModel := NewBasicViewModel(func() []*models.Remote { return c.Model().Remotes })
onFocus func(types.OnFocusOpts) error,
onRenderToMain func() error,
onFocusLost func(opts types.OnFocusLostOpts) error,
c *types.HelperCommon,
) *RemotesContext {
viewModel := NewBasicViewModel(getModel)
getDisplayStrings := func(startIdx int, length int) [][]string {
return presentation.GetRemoteListDisplayStrings(c.Model().Remotes, c.Modes().Diffing.Ref)
}
return &RemotesContext{
BasicViewModel: viewModel,
ListContextTrait: &ListContextTrait{
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
View: view,
View: c.Views().Remotes,
WindowName: "branches",
Key: REMOTES_CONTEXT_KEY,
Kind: types.SIDE_CONTEXT,
Focusable: true,
}), ContextCallbackOpts{
OnFocus: onFocus,
OnFocusLost: onFocusLost,
OnRenderToMain: onRenderToMain,
}),
})),
list: viewModel,
getDisplayStrings: getDisplayStrings,
c: c,
@@ -55,3 +48,9 @@ func (self *RemotesContext) GetSelectedItemId() string {
return item.ID()
}
func (self *RemotesContext) GetDiffTerminals() []string {
itemId := self.GetSelectedItemId()
return []string{itemId}
}
+146
View File
@@ -0,0 +1,146 @@
package context
import "github.com/jesseduffield/lazygit/pkg/gui/types"
func NewContextTree(c *ContextCommon) *ContextTree {
commitFilesContext := NewCommitFilesContext(c)
return &ContextTree{
Global: NewSimpleContext(
NewBaseContext(NewBaseContextOpts{
Kind: types.GLOBAL_CONTEXT,
View: nil, // TODO: see if this breaks anything
WindowName: "",
Key: GLOBAL_CONTEXT_KEY,
Focusable: false,
HasUncontrolledBounds: true, // setting to true because the global context doesn't even have a view
}),
),
Status: NewSimpleContext(
NewBaseContext(NewBaseContextOpts{
Kind: types.SIDE_CONTEXT,
View: c.Views().Status,
WindowName: "status",
Key: STATUS_CONTEXT_KEY,
Focusable: true,
}),
),
Files: NewWorkingTreeContext(c),
Submodules: NewSubmodulesContext(c),
Menu: NewMenuContext(c),
Remotes: NewRemotesContext(c),
RemoteBranches: NewRemoteBranchesContext(c),
LocalCommits: NewLocalCommitsContext(c),
CommitFiles: commitFilesContext,
ReflogCommits: NewReflogCommitsContext(c),
SubCommits: NewSubCommitsContext(c),
Branches: NewBranchesContext(c),
Tags: NewTagsContext(c),
Stash: NewStashContext(c),
Suggestions: NewSuggestionsContext(c),
Normal: NewSimpleContext(
NewBaseContext(NewBaseContextOpts{
Kind: types.MAIN_CONTEXT,
View: c.Views().Main,
WindowName: "main",
Key: NORMAL_MAIN_CONTEXT_KEY,
Focusable: false,
}),
),
NormalSecondary: NewSimpleContext(
NewBaseContext(NewBaseContextOpts{
Kind: types.MAIN_CONTEXT,
View: c.Views().Secondary,
WindowName: "secondary",
Key: NORMAL_SECONDARY_CONTEXT_KEY,
Focusable: false,
}),
),
Staging: NewPatchExplorerContext(
c.Views().Staging,
"main",
STAGING_MAIN_CONTEXT_KEY,
func() []int { return nil },
c,
),
StagingSecondary: NewPatchExplorerContext(
c.Views().StagingSecondary,
"secondary",
STAGING_SECONDARY_CONTEXT_KEY,
func() []int { return nil },
c,
),
CustomPatchBuilder: NewPatchExplorerContext(
c.Views().PatchBuilding,
"main",
PATCH_BUILDING_MAIN_CONTEXT_KEY,
func() []int {
filename := commitFilesContext.GetSelectedPath()
includedLineIndices, err := c.Git().Patch.PatchBuilder.GetFileIncLineIndices(filename)
if err != nil {
c.Log.Error(err)
return nil
}
return includedLineIndices
},
c,
),
CustomPatchBuilderSecondary: NewSimpleContext(
NewBaseContext(NewBaseContextOpts{
Kind: types.MAIN_CONTEXT,
View: c.Views().PatchBuildingSecondary,
WindowName: "secondary",
Key: PATCH_BUILDING_SECONDARY_CONTEXT_KEY,
Focusable: false,
}),
),
MergeConflicts: NewMergeConflictsContext(
c,
),
Confirmation: NewConfirmationContext(c),
CommitMessage: NewCommitMessageContext(c),
CommitDescription: NewSimpleContext(
NewBaseContext(NewBaseContextOpts{
Kind: types.PERSISTENT_POPUP,
View: c.Views().CommitDescription,
WindowName: "commitDescription",
Key: COMMIT_DESCRIPTION_CONTEXT_KEY,
Focusable: true,
HasUncontrolledBounds: true,
}),
),
Search: NewSimpleContext(
NewBaseContext(NewBaseContextOpts{
Kind: types.PERSISTENT_POPUP,
View: c.Views().Search,
WindowName: "search",
Key: SEARCH_CONTEXT_KEY,
Focusable: true,
}),
),
CommandLog: NewSimpleContext(
NewBaseContext(NewBaseContextOpts{
Kind: types.EXTRAS_CONTEXT,
View: c.Views().Extras,
WindowName: "extras",
Key: COMMAND_LOG_CONTEXT_KEY,
Focusable: true,
}),
),
Snake: NewSimpleContext(
NewBaseContext(NewBaseContextOpts{
Kind: types.SIDE_CONTEXT,
View: c.Views().Snake,
WindowName: "files",
Key: SNAKE_CONTEXT_KEY,
Focusable: true,
}),
),
Options: NewDisplayContext(OPTIONS_CONTEXT_KEY, c.Views().Options, "options"),
AppStatus: NewDisplayContext(APP_STATUS_CONTEXT_KEY, c.Views().AppStatus, "appStatus"),
SearchPrefix: NewDisplayContext(SEARCH_PREFIX_CONTEXT_KEY, c.Views().SearchPrefix, "searchPrefix"),
Information: NewDisplayContext(INFORMATION_CONTEXT_KEY, c.Views().Information, "information"),
Limit: NewDisplayContext(LIMIT_CONTEXT_KEY, c.Views().Limit, "limit"),
}
}
+10 -31
View File
@@ -6,29 +6,12 @@ import (
)
type SimpleContext struct {
OnFocus func(opts types.OnFocusOpts) error
OnFocusLost func(opts types.OnFocusLostOpts) error
OnRender func() error
// this is for pushing some content to the main view
OnRenderToMain func() error
*BaseContext
}
type ContextCallbackOpts struct {
OnFocus func(opts types.OnFocusOpts) error
OnFocusLost func(opts types.OnFocusLostOpts) error
OnRender func() error
OnRenderToMain func() error
}
func NewSimpleContext(baseContext *BaseContext, opts ContextCallbackOpts) *SimpleContext {
func NewSimpleContext(baseContext *BaseContext) *SimpleContext {
return &SimpleContext{
OnFocus: opts.OnFocus,
OnFocusLost: opts.OnFocusLost,
OnRender: opts.OnRender,
OnRenderToMain: opts.OnRenderToMain,
BaseContext: baseContext,
BaseContext: baseContext,
}
}
@@ -45,7 +28,6 @@ func NewDisplayContext(key types.ContextKey, view *gocui.View, windowName string
Focusable: false,
Transient: false,
}),
ContextCallbackOpts{},
)
}
@@ -54,14 +36,14 @@ func (self *SimpleContext) HandleFocus(opts types.OnFocusOpts) error {
self.GetViewTrait().SetHighlight(true)
}
if self.OnFocus != nil {
if err := self.OnFocus(opts); err != nil {
if self.onFocusFn != nil {
if err := self.onFocusFn(opts); err != nil {
return err
}
}
if self.OnRenderToMain != nil {
if err := self.OnRenderToMain(); err != nil {
if self.onRenderToMainFn != nil {
if err := self.onRenderToMainFn(); err != nil {
return err
}
}
@@ -70,22 +52,19 @@ func (self *SimpleContext) HandleFocus(opts types.OnFocusOpts) error {
}
func (self *SimpleContext) HandleFocusLost(opts types.OnFocusLostOpts) error {
if self.OnFocusLost != nil {
return self.OnFocusLost(opts)
if self.onFocusLostFn != nil {
return self.onFocusLostFn(opts)
}
return nil
}
func (self *SimpleContext) HandleRender() error {
if self.OnRender != nil {
return self.OnRender()
}
return nil
}
func (self *SimpleContext) HandleRenderToMain() error {
if self.OnRenderToMain != nil {
return self.OnRenderToMain()
if self.onRenderToMainFn != nil {
return self.onRenderToMainFn()
}
return nil
+19 -18
View File
@@ -1,8 +1,8 @@
package context
import (
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
@@ -11,35 +11,30 @@ type StashContext struct {
*ListContextTrait
}
var _ types.IListContext = (*StashContext)(nil)
var (
_ types.IListContext = (*StashContext)(nil)
_ types.DiffableContext = (*StashContext)(nil)
)
func NewStashContext(
getModel func() []*models.StashEntry,
view *gocui.View,
getDisplayStrings func(startIdx int, length int) [][]string,
onFocus func(types.OnFocusOpts) error,
onRenderToMain func() error,
onFocusLost func(opts types.OnFocusLostOpts) error,
c *types.HelperCommon,
c *ContextCommon,
) *StashContext {
viewModel := NewBasicViewModel(getModel)
viewModel := NewBasicViewModel(func() []*models.StashEntry { return c.Model().StashEntries })
getDisplayStrings := func(startIdx int, length int) [][]string {
return presentation.GetStashEntryListDisplayStrings(c.Model().StashEntries, c.Modes().Diffing.Ref)
}
return &StashContext{
BasicViewModel: viewModel,
ListContextTrait: &ListContextTrait{
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
View: view,
View: c.Views().Stash,
WindowName: "stash",
Key: STASH_CONTEXT_KEY,
Kind: types.SIDE_CONTEXT,
Focusable: true,
}), ContextCallbackOpts{
OnFocus: onFocus,
OnFocusLost: onFocusLost,
OnRenderToMain: onRenderToMain,
}),
})),
list: viewModel,
getDisplayStrings: getDisplayStrings,
c: c,
@@ -67,3 +62,9 @@ func (self *StashContext) GetSelectedRef() types.Ref {
}
return stash
}
func (self *StashContext) GetDiffTerminals() []string {
itemId := self.GetSelectedItemId()
return []string{itemId}
}
+38 -20
View File
@@ -3,7 +3,7 @@ package context
import (
"fmt"
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/utils"
@@ -15,23 +15,39 @@ type SubCommitsContext struct {
*DynamicTitleBuilder
}
var _ types.IListContext = (*SubCommitsContext)(nil)
var (
_ types.IListContext = (*SubCommitsContext)(nil)
_ types.DiffableContext = (*SubCommitsContext)(nil)
)
func NewSubCommitsContext(
getModel func() []*models.Commit,
view *gocui.View,
getDisplayStrings func(startIdx int, length int) [][]string,
onFocus func(types.OnFocusOpts) error,
onRenderToMain func() error,
onFocusLost func(opts types.OnFocusLostOpts) error,
c *types.HelperCommon,
c *ContextCommon,
) *SubCommitsContext {
viewModel := &SubCommitsViewModel{
BasicViewModel: NewBasicViewModel(getModel),
ref: nil,
limitCommits: true,
BasicViewModel: NewBasicViewModel(
func() []*models.Commit { return c.Model().SubCommits },
),
ref: nil,
limitCommits: true,
}
getDisplayStrings := func(startIdx int, length int) [][]string {
selectedCommitSha := ""
if c.CurrentContext().GetKey() == SUB_COMMITS_CONTEXT_KEY {
selectedCommit := viewModel.GetSelected()
if selectedCommit != nil {
selectedCommitSha = selectedCommit.Sha
}
}
return getCommitsDisplayStrings(
c,
c.Model().SubCommits,
startIdx,
length,
selectedCommitSha,
false,
git_commands.NewNullBisectInfo(),
)
}
return &SubCommitsContext{
@@ -40,17 +56,13 @@ func NewSubCommitsContext(
ViewportListContextTrait: &ViewportListContextTrait{
ListContextTrait: &ListContextTrait{
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
View: view,
View: c.Views().SubCommits,
WindowName: "branches",
Key: SUB_COMMITS_CONTEXT_KEY,
Kind: types.SIDE_CONTEXT,
Focusable: true,
Transient: true,
}), ContextCallbackOpts{
OnFocus: onFocus,
OnFocusLost: onFocusLost,
OnRenderToMain: onRenderToMain,
}),
})),
list: viewModel,
getDisplayStrings: getDisplayStrings,
c: c,
@@ -111,3 +123,9 @@ func (self *SubCommitsContext) SetLimitCommits(value bool) {
func (self *SubCommitsContext) GetLimitCommits() bool {
return self.limitCommits
}
func (self *SubCommitsContext) GetDiffTerminals() []string {
itemId := self.GetSelectedItemId()
return []string{itemId}
}
+8 -18
View File
@@ -1,8 +1,8 @@
package context
import (
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
@@ -13,33 +13,23 @@ type SubmodulesContext struct {
var _ types.IListContext = (*SubmodulesContext)(nil)
func NewSubmodulesContext(
getModel func() []*models.SubmoduleConfig,
view *gocui.View,
getDisplayStrings func(startIdx int, length int) [][]string,
func NewSubmodulesContext(c *ContextCommon) *SubmodulesContext {
viewModel := NewBasicViewModel(func() []*models.SubmoduleConfig { return c.Model().Submodules })
onFocus func(types.OnFocusOpts) error,
onRenderToMain func() error,
onFocusLost func(opts types.OnFocusLostOpts) error,
c *types.HelperCommon,
) *SubmodulesContext {
viewModel := NewBasicViewModel(getModel)
getDisplayStrings := func(startIdx int, length int) [][]string {
return presentation.GetSubmoduleListDisplayStrings(c.Model().Submodules)
}
return &SubmodulesContext{
BasicViewModel: viewModel,
ListContextTrait: &ListContextTrait{
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
View: view,
View: c.Views().Submodules,
WindowName: "files",
Key: SUBMODULES_CONTEXT_KEY,
Kind: types.SIDE_CONTEXT,
Focusable: true,
}), ContextCallbackOpts{
OnFocus: onFocus,
OnFocusLost: onFocusLost,
OnRenderToMain: onRenderToMain,
}),
})),
list: viewModel,
getDisplayStrings: getDisplayStrings,
c: c,
+49 -16
View File
@@ -1,45 +1,59 @@
package context
import (
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/tasks"
)
type SuggestionsContext struct {
*BasicViewModel[*types.Suggestion]
*ListContextTrait
State *SuggestionsContextState
}
type SuggestionsContextState struct {
Suggestions []*types.Suggestion
OnConfirm func() error
OnClose func() error
AsyncHandler *tasks.AsyncHandler
// FindSuggestions will take a string that the user has typed into a prompt
// and return a slice of suggestions which match that string.
FindSuggestions func(string) []*types.Suggestion
}
var _ types.IListContext = (*SuggestionsContext)(nil)
func NewSuggestionsContext(
getModel func() []*types.Suggestion,
view *gocui.View,
getDisplayStrings func(startIdx int, length int) [][]string,
onFocus func(types.OnFocusOpts) error,
onRenderToMain func() error,
onFocusLost func(opts types.OnFocusLostOpts) error,
c *types.HelperCommon,
c *ContextCommon,
) *SuggestionsContext {
state := &SuggestionsContextState{
AsyncHandler: tasks.NewAsyncHandler(),
}
getModel := func() []*types.Suggestion {
return state.Suggestions
}
getDisplayStrings := func(startIdx int, length int) [][]string {
return presentation.GetSuggestionListDisplayStrings(state.Suggestions)
}
viewModel := NewBasicViewModel(getModel)
return &SuggestionsContext{
State: state,
BasicViewModel: viewModel,
ListContextTrait: &ListContextTrait{
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
View: view,
View: c.Views().Suggestions,
WindowName: "suggestions",
Key: SUGGESTIONS_CONTEXT_KEY,
Kind: types.PERSISTENT_POPUP,
Focusable: true,
HasUncontrolledBounds: true,
}), ContextCallbackOpts{
OnFocus: onFocus,
OnFocusLost: onFocusLost,
OnRenderToMain: onRenderToMain,
}),
})),
list: viewModel,
getDisplayStrings: getDisplayStrings,
c: c,
@@ -55,3 +69,22 @@ func (self *SuggestionsContext) GetSelectedItemId() string {
return item.Value
}
func (self *SuggestionsContext) SetSuggestions(suggestions []*types.Suggestion) {
self.State.Suggestions = suggestions
self.SetSelectedLineIdx(0)
self.c.ResetViewOrigin(self.GetView())
_ = self.HandleRender()
}
func (self *SuggestionsContext) RefreshSuggestions() {
self.State.AsyncHandler.Do(func() func() {
findSuggestionsFn := self.State.FindSuggestions
if findSuggestionsFn != nil {
suggestions := findSuggestionsFn(self.c.GetPromptInput())
return func() { self.SetSuggestions(suggestions) }
} else {
return func() {}
}
})
}
+19 -18
View File
@@ -1,8 +1,8 @@
package context
import (
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
@@ -11,35 +11,30 @@ type TagsContext struct {
*ListContextTrait
}
var _ types.IListContext = (*TagsContext)(nil)
var (
_ types.IListContext = (*TagsContext)(nil)
_ types.DiffableContext = (*TagsContext)(nil)
)
func NewTagsContext(
getModel func() []*models.Tag,
view *gocui.View,
getDisplayStrings func(startIdx int, length int) [][]string,
onFocus func(types.OnFocusOpts) error,
onRenderToMain func() error,
onFocusLost func(opts types.OnFocusLostOpts) error,
c *types.HelperCommon,
c *ContextCommon,
) *TagsContext {
viewModel := NewBasicViewModel(getModel)
viewModel := NewBasicViewModel(func() []*models.Tag { return c.Model().Tags })
getDisplayStrings := func(startIdx int, length int) [][]string {
return presentation.GetTagListDisplayStrings(c.Model().Tags, c.Modes().Diffing.Ref)
}
return &TagsContext{
BasicViewModel: viewModel,
ListContextTrait: &ListContextTrait{
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
View: view,
View: c.Views().Tags,
WindowName: "branches",
Key: TAGS_CONTEXT_KEY,
Kind: types.SIDE_CONTEXT,
Focusable: true,
}), ContextCallbackOpts{
OnFocus: onFocus,
OnFocusLost: onFocusLost,
OnRenderToMain: onRenderToMain,
}),
})),
list: viewModel,
getDisplayStrings: getDisplayStrings,
c: c,
@@ -63,3 +58,9 @@ func (self *TagsContext) GetSelectedRef() types.Ref {
}
return tag
}
func (self *TagsContext) GetDiffTerminals() []string {
itemId := self.GetSelectedItemId()
return []string{itemId}
}
@@ -17,6 +17,6 @@ func (self *ViewportListContextTrait) FocusLine() {
startIdx, length := self.GetViewTrait().ViewPortYBounds()
displayStrings := self.ListContextTrait.getDisplayStrings(startIdx, length)
content := utils.RenderDisplayStrings(displayStrings)
content := utils.RenderDisplayStrings(displayStrings, nil)
self.GetViewTrait().SetViewPortContent(content)
}
+16 -18
View File
@@ -1,9 +1,10 @@
package context
import (
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/generics/slices"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/gui/filetree"
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
@@ -14,33 +15,30 @@ type WorkingTreeContext struct {
var _ types.IListContext = (*WorkingTreeContext)(nil)
func NewWorkingTreeContext(
getModel func() []*models.File,
view *gocui.View,
getDisplayStrings func(startIdx int, length int) [][]string,
func NewWorkingTreeContext(c *ContextCommon) *WorkingTreeContext {
viewModel := filetree.NewFileTreeViewModel(
func() []*models.File { return c.Model().Files },
c.Log,
c.UserConfig.Gui.ShowFileTree,
)
onFocus func(types.OnFocusOpts) error,
onRenderToMain func() error,
onFocusLost func(opts types.OnFocusLostOpts) error,
c *types.HelperCommon,
) *WorkingTreeContext {
viewModel := filetree.NewFileTreeViewModel(getModel, c.Log, c.UserConfig.Gui.ShowFileTree)
getDisplayStrings := func(startIdx int, length int) [][]string {
lines := presentation.RenderFileTree(viewModel, c.Modes().Diffing.Ref, c.Model().Submodules)
return slices.Map(lines, func(line string) []string {
return []string{line}
})
}
return &WorkingTreeContext{
FileTreeViewModel: viewModel,
ListContextTrait: &ListContextTrait{
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
View: view,
View: c.Views().Files,
WindowName: "files",
Key: FILES_CONTEXT_KEY,
Kind: types.SIDE_CONTEXT,
Focusable: true,
}), ContextCallbackOpts{
OnFocus: onFocus,
OnFocusLost: onFocusLost,
OnRenderToMain: onRenderToMain,
}),
})),
list: viewModel,
getDisplayStrings: getDisplayStrings,
c: c,
+9 -271
View File
@@ -6,273 +6,11 @@ import (
)
func (gui *Gui) contextTree() *context.ContextTree {
return &context.ContextTree{
Global: context.NewSimpleContext(
context.NewBaseContext(context.NewBaseContextOpts{
Kind: types.GLOBAL_CONTEXT,
View: nil, // TODO: see if this breaks anything
WindowName: "",
Key: context.GLOBAL_CONTEXT_KEY,
Focusable: false,
HasUncontrolledBounds: true, // setting to true because the global context doesn't even have a view
}),
context.ContextCallbackOpts{
OnRenderToMain: gui.statusRenderToMain,
},
),
Status: context.NewSimpleContext(
context.NewBaseContext(context.NewBaseContextOpts{
Kind: types.SIDE_CONTEXT,
View: gui.Views.Status,
WindowName: "status",
Key: context.STATUS_CONTEXT_KEY,
Focusable: true,
}),
context.ContextCallbackOpts{
OnRenderToMain: gui.statusRenderToMain,
},
),
Snake: context.NewSimpleContext(
context.NewBaseContext(context.NewBaseContextOpts{
Kind: types.SIDE_CONTEXT,
View: gui.Views.Snake,
WindowName: "files",
Key: context.SNAKE_CONTEXT_KEY,
Focusable: true,
}),
context.ContextCallbackOpts{
OnFocus: func(opts types.OnFocusOpts) error {
gui.startSnake()
return nil
},
OnFocusLost: func(opts types.OnFocusLostOpts) error {
gui.snakeGame.Exit()
gui.moveToTopOfWindow(gui.State.Contexts.Submodules)
return nil
},
},
),
Files: gui.filesListContext(),
Submodules: gui.submodulesListContext(),
Menu: gui.menuListContext(),
Remotes: gui.remotesListContext(),
RemoteBranches: gui.remoteBranchesListContext(),
LocalCommits: gui.branchCommitsListContext(),
CommitFiles: gui.commitFilesListContext(),
ReflogCommits: gui.reflogCommitsListContext(),
SubCommits: gui.subCommitsListContext(),
Branches: gui.branchesListContext(),
Tags: gui.tagsListContext(),
Stash: gui.stashListContext(),
Suggestions: gui.suggestionsListContext(),
Normal: context.NewSimpleContext(
context.NewBaseContext(context.NewBaseContextOpts{
Kind: types.MAIN_CONTEXT,
View: gui.Views.Main,
WindowName: "main",
Key: context.NORMAL_MAIN_CONTEXT_KEY,
Focusable: false,
}),
context.ContextCallbackOpts{
OnFocus: func(opts types.OnFocusOpts) error {
return nil // TODO: should we do something here? We should allow for scrolling the panel
},
},
),
NormalSecondary: context.NewSimpleContext(
context.NewBaseContext(context.NewBaseContextOpts{
Kind: types.MAIN_CONTEXT,
View: gui.Views.Secondary,
WindowName: "secondary",
Key: context.NORMAL_SECONDARY_CONTEXT_KEY,
Focusable: false,
}),
context.ContextCallbackOpts{},
),
Staging: context.NewPatchExplorerContext(
gui.Views.Staging,
"main",
context.STAGING_MAIN_CONTEXT_KEY,
func(opts types.OnFocusOpts) error {
gui.Views.Staging.Wrap = false
gui.Views.StagingSecondary.Wrap = false
return gui.refreshStagingPanel(opts)
},
func(opts types.OnFocusLostOpts) error {
gui.State.Contexts.Staging.SetState(nil)
if opts.NewContextKey != context.STAGING_SECONDARY_CONTEXT_KEY {
gui.Views.Staging.Wrap = true
gui.Views.StagingSecondary.Wrap = true
_ = gui.State.Contexts.Staging.Render(false)
_ = gui.State.Contexts.StagingSecondary.Render(false)
}
return nil
},
func() []int { return nil },
gui.c,
),
StagingSecondary: context.NewPatchExplorerContext(
gui.Views.StagingSecondary,
"secondary",
context.STAGING_SECONDARY_CONTEXT_KEY,
func(opts types.OnFocusOpts) error {
gui.Views.Staging.Wrap = false
gui.Views.StagingSecondary.Wrap = false
return gui.refreshStagingPanel(opts)
},
func(opts types.OnFocusLostOpts) error {
gui.State.Contexts.StagingSecondary.SetState(nil)
if opts.NewContextKey != context.STAGING_MAIN_CONTEXT_KEY {
gui.Views.Staging.Wrap = true
gui.Views.StagingSecondary.Wrap = true
_ = gui.State.Contexts.Staging.Render(false)
_ = gui.State.Contexts.StagingSecondary.Render(false)
}
return nil
},
func() []int { return nil },
gui.c,
),
CustomPatchBuilder: context.NewPatchExplorerContext(
gui.Views.PatchBuilding,
"main",
context.PATCH_BUILDING_MAIN_CONTEXT_KEY,
func(opts types.OnFocusOpts) error {
// no need to change wrap on the secondary view because it can't be interacted with
gui.Views.PatchBuilding.Wrap = false
return gui.refreshPatchBuildingPanel(opts)
},
func(opts types.OnFocusLostOpts) error {
gui.Views.PatchBuilding.Wrap = true
if gui.git.Patch.PatchBuilder.IsEmpty() {
gui.git.Patch.PatchBuilder.Reset()
}
return nil
},
func() []int {
filename := gui.State.Contexts.CommitFiles.GetSelectedPath()
includedLineIndices, err := gui.git.Patch.PatchBuilder.GetFileIncLineIndices(filename)
if err != nil {
gui.Log.Error(err)
return nil
}
return includedLineIndices
},
gui.c,
),
CustomPatchBuilderSecondary: context.NewSimpleContext(
context.NewBaseContext(context.NewBaseContextOpts{
Kind: types.MAIN_CONTEXT,
View: gui.Views.PatchBuildingSecondary,
WindowName: "secondary",
Key: context.PATCH_BUILDING_SECONDARY_CONTEXT_KEY,
Focusable: false,
}),
context.ContextCallbackOpts{},
),
MergeConflicts: context.NewMergeConflictsContext(
gui.Views.MergeConflicts,
context.ContextCallbackOpts{
OnFocus: OnFocusWrapper(func() error {
gui.Views.MergeConflicts.Wrap = false
return gui.refreshMergePanel(true)
}),
OnFocusLost: func(opts types.OnFocusLostOpts) error {
gui.State.Contexts.MergeConflicts.SetUserScrolling(false)
gui.State.Contexts.MergeConflicts.GetState().ResetConflictSelection()
gui.Views.MergeConflicts.Wrap = true
return nil
},
},
gui.c,
func() map[string]string {
// wrapping in a function because contexts are initialized before helpers
return gui.helpers.MergeConflicts.GetMergingOptions()
},
),
Confirmation: context.NewSimpleContext(
context.NewBaseContext(context.NewBaseContextOpts{
Kind: types.TEMPORARY_POPUP,
View: gui.Views.Confirmation,
WindowName: "confirmation",
Key: context.CONFIRMATION_CONTEXT_KEY,
Focusable: true,
HasUncontrolledBounds: true,
}),
context.ContextCallbackOpts{
OnFocus: OnFocusWrapper(gui.handleAskFocused),
OnFocusLost: func(types.OnFocusLostOpts) error {
gui.deactivateConfirmationPrompt()
return nil
},
},
),
CommitMessage: context.NewCommitMessageContext(
gui.Views.CommitMessage,
context.ContextCallbackOpts{
OnFocus: OnFocusWrapper(gui.handleCommitMessageFocused),
},
),
CommitDescription: context.NewSimpleContext(
context.NewBaseContext(context.NewBaseContextOpts{
Kind: types.PERSISTENT_POPUP,
View: gui.Views.CommitDescription,
WindowName: "commitDescription",
Key: context.COMMIT_DESCRIPTION_CONTEXT_KEY,
Focusable: true,
HasUncontrolledBounds: true,
}),
context.ContextCallbackOpts{
OnFocus: func(opts types.OnFocusOpts) error {
_, err := gui.g.SetViewBeneath("commitDescription", "commitMessage", 10)
if err != nil {
return err
}
return nil
},
},
),
Search: context.NewSimpleContext(
context.NewBaseContext(context.NewBaseContextOpts{
Kind: types.PERSISTENT_POPUP,
View: gui.Views.Search,
WindowName: "search",
Key: context.SEARCH_CONTEXT_KEY,
Focusable: true,
}),
context.ContextCallbackOpts{},
),
CommandLog: context.NewSimpleContext(
context.NewBaseContext(context.NewBaseContextOpts{
Kind: types.EXTRAS_CONTEXT,
View: gui.Views.Extras,
WindowName: "extras",
Key: context.COMMAND_LOG_CONTEXT_KEY,
Focusable: true,
}),
context.ContextCallbackOpts{
OnFocusLost: func(opts types.OnFocusLostOpts) error {
gui.Views.Extras.Autoscroll = true
return nil
},
},
),
Options: context.NewDisplayContext(context.OPTIONS_CONTEXT_KEY, gui.Views.Options, "options"),
AppStatus: context.NewDisplayContext(context.APP_STATUS_CONTEXT_KEY, gui.Views.AppStatus, "appStatus"),
SearchPrefix: context.NewDisplayContext(context.SEARCH_PREFIX_CONTEXT_KEY, gui.Views.SearchPrefix, "searchPrefix"),
Information: context.NewDisplayContext(context.INFORMATION_CONTEXT_KEY, gui.Views.Information, "information"),
Limit: context.NewDisplayContext(context.LIMIT_CONTEXT_KEY, gui.Views.Limit, "limit"),
contextCommon := &context.ContextCommon{
IGuiCommon: gui.c.IGuiCommon,
Common: gui.c.Common,
}
return context.NewContextTree(contextCommon)
}
// using this wrapper for when an onFocus function doesn't care about any potential
@@ -283,10 +21,10 @@ func OnFocusWrapper(f func() error) func(opts types.OnFocusOpts) error {
}
}
func (gui *Gui) getPatchExplorerContexts() []types.IPatchExplorerContext {
return []types.IPatchExplorerContext{
gui.State.Contexts.Staging,
gui.State.Contexts.StagingSecondary,
gui.State.Contexts.CustomPatchBuilder,
func (gui *Gui) defaultSideContext() types.Context {
if gui.State.Modes.Filtering.Active() {
return gui.State.Contexts.LocalCommits
} else {
return gui.State.Contexts.Files
}
}
+139 -60
View File
@@ -7,24 +7,22 @@ import (
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/gui/controllers"
"github.com/jesseduffield/lazygit/pkg/gui/controllers/helpers"
"github.com/jesseduffield/lazygit/pkg/gui/modes/cherrypicking"
"github.com/jesseduffield/lazygit/pkg/gui/services/custom_commands"
"github.com/jesseduffield/lazygit/pkg/snake"
"github.com/jesseduffield/lazygit/pkg/gui/status"
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
func (gui *Gui) resetControllers() {
func (gui *Gui) Helpers() *helpers.Helpers {
return gui.helpers
}
func (gui *Gui) resetHelpersAndControllers() {
helperCommon := gui.c
osCommand := gui.os
model := gui.State.Model
refsHelper := helpers.NewRefsHelper(
helperCommon,
gui.git,
gui.State.Contexts,
model,
)
refsHelper := helpers.NewRefsHelper(helperCommon)
rebaseHelper := helpers.NewMergeAndRebaseHelper(helperCommon, refsHelper)
suggestionsHelper := helpers.NewSuggestionsHelper(helperCommon)
rebaseHelper := helpers.NewMergeAndRebaseHelper(helperCommon, gui.State.Contexts, gui.git, refsHelper)
suggestionsHelper := helpers.NewSuggestionsHelper(helperCommon, model, gui.refreshSuggestions)
setCommitSummary := gui.getCommitMessageSetTextareaTextFn(func() *gocui.View { return gui.Views.CommitMessage })
setCommitDescription := gui.getCommitMessageSetTextareaTextFn(func() *gocui.View { return gui.Views.CommitDescription })
getCommitSummary := func() string {
@@ -35,66 +33,86 @@ func (gui *Gui) resetControllers() {
return strings.TrimSpace(gui.Views.CommitDescription.TextArea.GetContent())
}
commitsHelper := helpers.NewCommitsHelper(helperCommon,
gui.State.Model,
gui.State.Contexts,
getCommitSummary,
setCommitSummary,
getCommitDescription,
setCommitDescription,
gui.RenderCommitLength,
)
gpgHelper := helpers.NewGpgHelper(helperCommon, gui.os, gui.git)
gpgHelper := helpers.NewGpgHelper(helperCommon)
viewHelper := helpers.NewViewHelper(helperCommon, gui.State.Contexts)
recordDirectoryHelper := helpers.NewRecordDirectoryHelper(helperCommon)
patchBuildingHelper := helpers.NewPatchBuildingHelper(helperCommon)
stagingHelper := helpers.NewStagingHelper(helperCommon)
mergeConflictsHelper := helpers.NewMergeConflictsHelper(helperCommon)
refreshHelper := helpers.NewRefreshHelper(helperCommon, refsHelper, rebaseHelper, patchBuildingHelper, stagingHelper, mergeConflictsHelper, gui.fileWatcher)
diffHelper := helpers.NewDiffHelper(helperCommon)
cherryPickHelper := helpers.NewCherryPickHelper(
helperCommon,
rebaseHelper,
)
bisectHelper := helpers.NewBisectHelper(helperCommon)
windowHelper := helpers.NewWindowHelper(helperCommon, viewHelper)
modeHelper := helpers.NewModeHelper(
helperCommon,
diffHelper,
patchBuildingHelper,
cherryPickHelper,
rebaseHelper,
bisectHelper,
)
appStatusHelper := helpers.NewAppStatusHelper(
helperCommon,
func() *status.StatusManager { return gui.statusManager },
)
gui.helpers = &helpers.Helpers{
Refs: refsHelper,
Host: helpers.NewHostHelper(helperCommon, gui.git),
PatchBuilding: helpers.NewPatchBuildingHelper(helperCommon, gui.git, gui.State.Contexts),
Bisect: helpers.NewBisectHelper(helperCommon, gui.git),
Suggestions: suggestionsHelper,
Files: helpers.NewFilesHelper(helperCommon, gui.git, osCommand),
WorkingTree: helpers.NewWorkingTreeHelper(helperCommon, gui.git, gui.State.Contexts, refsHelper, model, setCommitSummary, commitsHelper, gpgHelper),
Tags: helpers.NewTagsHelper(helperCommon, gui.git),
GPG: gpgHelper,
MergeAndRebase: rebaseHelper,
MergeConflicts: helpers.NewMergeConflictsHelper(helperCommon, gui.State.Contexts, gui.git),
CherryPick: helpers.NewCherryPickHelper(
helperCommon,
gui.git,
gui.State.Contexts,
func() *cherrypicking.CherryPicking { return gui.State.Modes.CherryPicking },
rebaseHelper,
Refs: refsHelper,
Host: helpers.NewHostHelper(helperCommon),
PatchBuilding: patchBuildingHelper,
Staging: stagingHelper,
Bisect: bisectHelper,
Suggestions: suggestionsHelper,
Files: helpers.NewFilesHelper(helperCommon),
WorkingTree: helpers.NewWorkingTreeHelper(helperCommon, refsHelper, commitsHelper, gpgHelper),
Tags: helpers.NewTagsHelper(helperCommon),
GPG: helpers.NewGpgHelper(helperCommon),
MergeAndRebase: rebaseHelper,
MergeConflicts: mergeConflictsHelper,
CherryPick: cherryPickHelper,
Upstream: helpers.NewUpstreamHelper(helperCommon, suggestionsHelper.GetRemoteBranchesSuggestionsFunc),
AmendHelper: helpers.NewAmendHelper(helperCommon, gpgHelper),
Commits: commitsHelper,
Snake: helpers.NewSnakeHelper(helperCommon),
Diff: diffHelper,
Repos: helpers.NewRecentReposHelper(helperCommon, recordDirectoryHelper, gui.onNewRepo),
RecordDirectory: recordDirectoryHelper,
Update: helpers.NewUpdateHelper(helperCommon, gui.Updater),
Window: windowHelper,
View: viewHelper,
Refresh: refreshHelper,
Confirmation: helpers.NewConfirmationHelper(helperCommon),
Mode: modeHelper,
AppStatus: appStatusHelper,
WindowArrangement: helpers.NewWindowArrangementHelper(
gui.c,
windowHelper,
modeHelper,
appStatusHelper,
),
Upstream: helpers.NewUpstreamHelper(helperCommon, model, suggestionsHelper.GetRemoteBranchesSuggestionsFunc),
AmendHelper: helpers.NewAmendHelper(helperCommon, gui.git, gpgHelper),
Commits: commitsHelper,
}
gui.CustomCommandsClient = custom_commands.NewClient(
helperCommon,
gui.os,
gui.git,
gui.State.Contexts,
gui.helpers,
)
common := controllers.NewControllerCommon(
helperCommon,
osCommand,
gui.git,
gui.helpers,
model,
gui.State.Contexts,
gui.State.Modes,
&gui.Mutexes,
)
common := controllers.NewControllerCommon(helperCommon, gui)
syncController := controllers.NewSyncController(
common,
)
submodulesController := controllers.NewSubmodulesController(
common,
gui.enterSubmodule,
)
submodulesController := controllers.NewSubmodulesController(common)
bisectController := controllers.NewBisectController(common)
@@ -113,8 +131,6 @@ func (gui *Gui) resetControllers() {
tagsController := controllers.NewTagsController(common)
filesController := controllers.NewFilesController(
common,
gui.enterSubmodule,
setCommitSummary,
)
mergeConflictsController := controllers.NewMergeConflictsController(common)
remotesController := controllers.NewRemotesController(
@@ -135,7 +151,34 @@ func (gui *Gui) resetControllers() {
stagingController := controllers.NewStagingController(common, gui.State.Contexts.Staging, gui.State.Contexts.StagingSecondary, false)
stagingSecondaryController := controllers.NewStagingController(common, gui.State.Contexts.StagingSecondary, gui.State.Contexts.Staging, true)
patchBuildingController := controllers.NewPatchBuildingController(common)
snakeController := controllers.NewSnakeController(common, func() *snake.Game { return gui.snakeGame })
snakeController := controllers.NewSnakeController(common)
reflogCommitsController := controllers.NewReflogCommitsController(common)
subCommitsController := controllers.NewSubCommitsController(common)
statusController := controllers.NewStatusController(common)
commandLogController := controllers.NewCommandLogController(common)
confirmationController := controllers.NewConfirmationController(common)
suggestionsController := controllers.NewSuggestionsController(common)
jumpToSideWindowController := controllers.NewJumpToSideWindowController(common)
sideWindowControllerFactory := controllers.NewSideWindowControllerFactory(common)
// allow for navigating between side window contexts
for _, context := range []types.Context{
gui.State.Contexts.Status,
gui.State.Contexts.Remotes,
gui.State.Contexts.Tags,
gui.State.Contexts.Branches,
gui.State.Contexts.RemoteBranches,
gui.State.Contexts.Files,
gui.State.Contexts.Submodules,
gui.State.Contexts.ReflogCommits,
gui.State.Contexts.LocalCommits,
gui.State.Contexts.CommitFiles,
gui.State.Contexts.SubCommits,
gui.State.Contexts.Stash,
} {
controllers.AttachControllers(context, sideWindowControllerFactory.Create(context))
}
setSubCommits := func(commits []*models.Commit) {
gui.Mutexes.SubCommitsMutex.Lock()
@@ -161,7 +204,7 @@ func (gui *Gui) resetControllers() {
gui.State.Contexts.Stash,
} {
controllers.AttachControllers(context, controllers.NewSwitchToDiffFilesController(
common, gui.SwitchToCommitFilesContext, context,
common, context, gui.State.Contexts.CommitFiles,
))
}
@@ -173,6 +216,14 @@ func (gui *Gui) resetControllers() {
controllers.AttachControllers(context, controllers.NewBasicCommitsController(common, context))
}
controllers.AttachControllers(gui.State.Contexts.ReflogCommits,
reflogCommitsController,
)
controllers.AttachControllers(gui.State.Contexts.SubCommits,
subCommitsController,
)
// TODO: add scroll controllers for main panels (need to bring some more functionality across for that e.g. reading more from the currently displayed git command)
controllers.AttachControllers(gui.State.Contexts.Staging,
stagingController,
@@ -256,11 +307,28 @@ func (gui *Gui) resetControllers() {
remoteBranchesController,
)
controllers.AttachControllers(gui.State.Contexts.Status,
statusController,
)
controllers.AttachControllers(gui.State.Contexts.CommandLog,
commandLogController,
)
controllers.AttachControllers(gui.State.Contexts.Confirmation,
confirmationController,
)
controllers.AttachControllers(gui.State.Contexts.Suggestions,
suggestionsController,
)
controllers.AttachControllers(gui.State.Contexts.Global,
syncController,
undoController,
globalController,
contextLinesController,
jumpToSideWindowController,
)
controllers.AttachControllers(gui.State.Contexts.Snake,
@@ -268,8 +336,19 @@ func (gui *Gui) resetControllers() {
)
// this must come last so that we've got our click handlers defined against the context
listControllerFactory := controllers.NewListControllerFactory(gui.c)
for _, context := range gui.getListContexts() {
listControllerFactory := controllers.NewListControllerFactory(common)
for _, context := range gui.c.Context().AllList() {
controllers.AttachControllers(context, listControllerFactory.Create(context))
}
}
func (gui *Gui) getCommitMessageSetTextareaTextFn(getView func() *gocui.View) func(string) {
return func(text string) {
// using a getView function so that we don't need to worry about when the view is created
view := getView()
view.ClearTextArea()
view.TextArea.TypeString(text)
gui.helpers.Confirmation.ResizeCommitMessagePanels()
view.RenderTextArea()
}
}
+3
View File
@@ -7,5 +7,8 @@ func AttachControllers(context types.Context, controllers ...types.IController)
context.AddKeybindingsFn(controller.GetKeybindings)
context.AddMouseKeybindingsFn(controller.GetMouseKeybindings)
context.AddOnClickFn(controller.GetOnClick())
context.AddOnRenderToMainFn(controller.GetOnRenderToMain())
context.AddOnFocusFn(controller.GetOnFocus())
context.AddOnFocusLostFn(controller.GetOnFocusLost())
}
}
+12
View File
@@ -18,3 +18,15 @@ func (self *baseController) GetMouseKeybindings(opts types.KeybindingsOpts) []*g
func (self *baseController) GetOnClick() func() error {
return nil
}
func (self *baseController) GetOnRenderToMain() func() error {
return nil
}
func (self *baseController) GetOnFocus() func(types.OnFocusOpts) error {
return nil
}
func (self *baseController) GetOnFocusLost() func(types.OnFocusLostOpts) error {
return nil
}
+22 -22
View File
@@ -20,15 +20,15 @@ type ContainsCommits interface {
type BasicCommitsController struct {
baseController
*controllerCommon
c *ControllerCommon
context ContainsCommits
}
func NewBasicCommitsController(controllerCommon *controllerCommon, context ContainsCommits) *BasicCommitsController {
func NewBasicCommitsController(controllerCommon *ControllerCommon, context ContainsCommits) *BasicCommitsController {
return &BasicCommitsController{
baseController: baseController{},
controllerCommon: controllerCommon,
context: context,
baseController: baseController{},
c: controllerCommon,
context: context,
}
}
@@ -73,7 +73,7 @@ func (self *BasicCommitsController) GetKeybindings(opts types.KeybindingsOpts) [
},
{
Key: opts.GetKey(opts.Config.Commits.ResetCherryPick),
Handler: self.helpers.CherryPick.Reset,
Handler: self.c.Helpers().CherryPick.Reset,
Description: self.c.Tr.LcResetCherryPick,
},
}
@@ -141,7 +141,7 @@ func (self *BasicCommitsController) copyCommitAttribute(commit *models.Commit) e
func (self *BasicCommitsController) copyCommitSHAToClipboard(commit *models.Commit) error {
self.c.LogAction(self.c.Tr.Actions.CopyCommitSHAToClipboard)
if err := self.os.CopyToClipboard(commit.Sha); err != nil {
if err := self.c.OS().CopyToClipboard(commit.Sha); err != nil {
return self.c.Error(err)
}
@@ -150,13 +150,13 @@ func (self *BasicCommitsController) copyCommitSHAToClipboard(commit *models.Comm
}
func (self *BasicCommitsController) copyCommitURLToClipboard(commit *models.Commit) error {
url, err := self.helpers.Host.GetCommitURL(commit.Sha)
url, err := self.c.Helpers().Host.GetCommitURL(commit.Sha)
if err != nil {
return err
}
self.c.LogAction(self.c.Tr.Actions.CopyCommitURLToClipboard)
if err := self.os.CopyToClipboard(url); err != nil {
if err := self.c.OS().CopyToClipboard(url); err != nil {
return self.c.Error(err)
}
@@ -165,13 +165,13 @@ func (self *BasicCommitsController) copyCommitURLToClipboard(commit *models.Comm
}
func (self *BasicCommitsController) copyCommitDiffToClipboard(commit *models.Commit) error {
diff, err := self.git.Commit.GetCommitDiff(commit.Sha)
diff, err := self.c.Git().Commit.GetCommitDiff(commit.Sha)
if err != nil {
return self.c.Error(err)
}
self.c.LogAction(self.c.Tr.Actions.CopyCommitDiffToClipboard)
if err := self.os.CopyToClipboard(diff); err != nil {
if err := self.c.OS().CopyToClipboard(diff); err != nil {
return self.c.Error(err)
}
@@ -180,7 +180,7 @@ func (self *BasicCommitsController) copyCommitDiffToClipboard(commit *models.Com
}
func (self *BasicCommitsController) copyAuthorToClipboard(commit *models.Commit) error {
author, err := self.git.Commit.GetCommitAuthor(commit.Sha)
author, err := self.c.Git().Commit.GetCommitAuthor(commit.Sha)
if err != nil {
return self.c.Error(err)
}
@@ -188,7 +188,7 @@ func (self *BasicCommitsController) copyAuthorToClipboard(commit *models.Commit)
formattedAuthor := fmt.Sprintf("%s <%s>", author.Name, author.Email)
self.c.LogAction(self.c.Tr.Actions.CopyCommitAuthorToClipboard)
if err := self.os.CopyToClipboard(formattedAuthor); err != nil {
if err := self.c.OS().CopyToClipboard(formattedAuthor); err != nil {
return self.c.Error(err)
}
@@ -197,13 +197,13 @@ func (self *BasicCommitsController) copyAuthorToClipboard(commit *models.Commit)
}
func (self *BasicCommitsController) copyCommitMessageToClipboard(commit *models.Commit) error {
message, err := self.git.Commit.GetCommitMessage(commit.Sha)
message, err := self.c.Git().Commit.GetCommitMessage(commit.Sha)
if err != nil {
return self.c.Error(err)
}
self.c.LogAction(self.c.Tr.Actions.CopyCommitMessageToClipboard)
if err := self.os.CopyToClipboard(message); err != nil {
if err := self.c.OS().CopyToClipboard(message); err != nil {
return self.c.Error(err)
}
@@ -212,13 +212,13 @@ func (self *BasicCommitsController) copyCommitMessageToClipboard(commit *models.
}
func (self *BasicCommitsController) openInBrowser(commit *models.Commit) error {
url, err := self.helpers.Host.GetCommitURL(commit.Sha)
url, err := self.c.Helpers().Host.GetCommitURL(commit.Sha)
if err != nil {
return self.c.Error(err)
}
self.c.LogAction(self.c.Tr.Actions.OpenCommitInBrowser)
if err := self.os.OpenLink(url); err != nil {
if err := self.c.OS().OpenLink(url); err != nil {
return self.c.Error(err)
}
@@ -226,11 +226,11 @@ func (self *BasicCommitsController) openInBrowser(commit *models.Commit) error {
}
func (self *BasicCommitsController) newBranch(commit *models.Commit) error {
return self.helpers.Refs.NewBranch(commit.RefName(), commit.Description(), "")
return self.c.Helpers().Refs.NewBranch(commit.RefName(), commit.Description(), "")
}
func (self *BasicCommitsController) createResetMenu(commit *models.Commit) error {
return self.helpers.Refs.CreateGitResetMenu(commit.Sha)
return self.c.Helpers().Refs.CreateGitResetMenu(commit.Sha)
}
func (self *BasicCommitsController) checkout(commit *models.Commit) error {
@@ -239,15 +239,15 @@ func (self *BasicCommitsController) checkout(commit *models.Commit) error {
Prompt: self.c.Tr.SureCheckoutThisCommit,
HandleConfirm: func() error {
self.c.LogAction(self.c.Tr.Actions.CheckoutCommit)
return self.helpers.Refs.CheckoutRef(commit.Sha, types.CheckoutRefOptions{})
return self.c.Helpers().Refs.CheckoutRef(commit.Sha, types.CheckoutRefOptions{})
},
})
}
func (self *BasicCommitsController) copy(commit *models.Commit) error {
return self.helpers.CherryPick.Copy(commit, self.context.GetCommits(), self.context)
return self.c.Helpers().CherryPick.Copy(commit, self.context.GetCommits(), self.context)
}
func (self *BasicCommitsController) copyRange(*models.Commit) error {
return self.helpers.CherryPick.CopyRange(self.context.GetSelectedLineIdx(), self.context.GetCommits(), self.context)
return self.c.Helpers().CherryPick.CopyRange(self.context.GetSelectedLineIdx(), self.context.GetCommits(), self.context)
}
+24 -24
View File
@@ -12,17 +12,17 @@ import (
type BisectController struct {
baseController
*controllerCommon
c *ControllerCommon
}
var _ types.IController = &BisectController{}
func NewBisectController(
common *controllerCommon,
common *ControllerCommon,
) *BisectController {
return &BisectController{
baseController: baseController{},
controllerCommon: common,
baseController: baseController{},
c: common,
}
}
@@ -42,7 +42,7 @@ func (self *BisectController) GetKeybindings(opts types.KeybindingsOpts) []*type
func (self *BisectController) openMenu(commit *models.Commit) error {
// no shame in getting this directly rather than using the cached value
// given how cheap it is to obtain
info := self.git.Bisect.GetInfo()
info := self.c.Git().Bisect.GetInfo()
if info.Started() {
return self.openMidBisectMenu(info, commit)
} else {
@@ -63,14 +63,14 @@ func (self *BisectController) openMidBisectMenu(info *git_commands.BisectInfo, c
selectCurrentAfter := info.GetCurrentSha() == "" || info.GetCurrentSha() == commit.Sha
// we need to wait to reselect if our bisect commits aren't ancestors of our 'start'
// ref, because we'll be reloading our commits in that case.
waitToReselect := selectCurrentAfter && !self.git.Bisect.ReachableFromStart(info)
waitToReselect := selectCurrentAfter && !self.c.Git().Bisect.ReachableFromStart(info)
menuItems := []*types.MenuItem{
{
Label: fmt.Sprintf(self.c.Tr.Bisect.Mark, commit.ShortSha(), info.NewTerm()),
OnPress: func() error {
self.c.LogAction(self.c.Tr.Actions.BisectMark)
if err := self.git.Bisect.Mark(commit.Sha, info.NewTerm()); err != nil {
if err := self.c.Git().Bisect.Mark(commit.Sha, info.NewTerm()); err != nil {
return self.c.Error(err)
}
@@ -82,7 +82,7 @@ func (self *BisectController) openMidBisectMenu(info *git_commands.BisectInfo, c
Label: fmt.Sprintf(self.c.Tr.Bisect.Mark, commit.ShortSha(), info.OldTerm()),
OnPress: func() error {
self.c.LogAction(self.c.Tr.Actions.BisectMark)
if err := self.git.Bisect.Mark(commit.Sha, info.OldTerm()); err != nil {
if err := self.c.Git().Bisect.Mark(commit.Sha, info.OldTerm()); err != nil {
return self.c.Error(err)
}
@@ -94,7 +94,7 @@ func (self *BisectController) openMidBisectMenu(info *git_commands.BisectInfo, c
Label: fmt.Sprintf(self.c.Tr.Bisect.Skip, commit.ShortSha()),
OnPress: func() error {
self.c.LogAction(self.c.Tr.Actions.BisectSkip)
if err := self.git.Bisect.Skip(commit.Sha); err != nil {
if err := self.c.Git().Bisect.Skip(commit.Sha); err != nil {
return self.c.Error(err)
}
@@ -105,7 +105,7 @@ func (self *BisectController) openMidBisectMenu(info *git_commands.BisectInfo, c
{
Label: self.c.Tr.Bisect.ResetOption,
OnPress: func() error {
return self.helpers.Bisect.Reset()
return self.c.Helpers().Bisect.Reset()
},
Key: 'r',
},
@@ -125,15 +125,15 @@ func (self *BisectController) openStartBisectMenu(info *git_commands.BisectInfo,
Label: fmt.Sprintf(self.c.Tr.Bisect.MarkStart, commit.ShortSha(), info.NewTerm()),
OnPress: func() error {
self.c.LogAction(self.c.Tr.Actions.StartBisect)
if err := self.git.Bisect.Start(); err != nil {
if err := self.c.Git().Bisect.Start(); err != nil {
return self.c.Error(err)
}
if err := self.git.Bisect.Mark(commit.Sha, info.NewTerm()); err != nil {
if err := self.c.Git().Bisect.Mark(commit.Sha, info.NewTerm()); err != nil {
return self.c.Error(err)
}
return self.helpers.Bisect.PostBisectCommandRefresh()
return self.c.Helpers().Bisect.PostBisectCommandRefresh()
},
Key: 'b',
},
@@ -141,15 +141,15 @@ func (self *BisectController) openStartBisectMenu(info *git_commands.BisectInfo,
Label: fmt.Sprintf(self.c.Tr.Bisect.MarkStart, commit.ShortSha(), info.OldTerm()),
OnPress: func() error {
self.c.LogAction(self.c.Tr.Actions.StartBisect)
if err := self.git.Bisect.Start(); err != nil {
if err := self.c.Git().Bisect.Start(); err != nil {
return self.c.Error(err)
}
if err := self.git.Bisect.Mark(commit.Sha, info.OldTerm()); err != nil {
if err := self.c.Git().Bisect.Mark(commit.Sha, info.OldTerm()); err != nil {
return self.c.Error(err)
}
return self.helpers.Bisect.PostBisectCommandRefresh()
return self.c.Helpers().Bisect.PostBisectCommandRefresh()
},
Key: 'g',
},
@@ -163,7 +163,7 @@ func (self *BisectController) showBisectCompleteMessage(candidateShas []string)
prompt = self.c.Tr.Bisect.CompletePromptIndeterminate
}
formattedCommits, err := self.git.Commit.GetCommitsOneline(candidateShas)
formattedCommits, err := self.c.Git().Commit.GetCommitsOneline(candidateShas)
if err != nil {
return self.c.Error(err)
}
@@ -173,17 +173,17 @@ func (self *BisectController) showBisectCompleteMessage(candidateShas []string)
Prompt: fmt.Sprintf(prompt, strings.TrimSpace(formattedCommits)),
HandleConfirm: func() error {
self.c.LogAction(self.c.Tr.Actions.ResetBisect)
if err := self.git.Bisect.Reset(); err != nil {
if err := self.c.Git().Bisect.Reset(); err != nil {
return self.c.Error(err)
}
return self.helpers.Bisect.PostBisectCommandRefresh()
return self.c.Helpers().Bisect.PostBisectCommandRefresh()
},
})
}
func (self *BisectController) afterMark(selectCurrent bool, waitToReselect bool) error {
done, candidateShas, err := self.git.Bisect.IsDone()
done, candidateShas, err := self.c.Git().Bisect.IsDone()
if err != nil {
return self.c.Error(err)
}
@@ -211,15 +211,15 @@ func (self *BisectController) afterBisectMarkRefresh(selectCurrent bool, waitToR
} else {
selectFn()
return self.helpers.Bisect.PostBisectCommandRefresh()
return self.c.Helpers().Bisect.PostBisectCommandRefresh()
}
}
func (self *BisectController) selectCurrentBisectCommit() {
info := self.git.Bisect.GetInfo()
info := self.c.Git().Bisect.GetInfo()
if info.GetCurrentSha() != "" {
// find index of commit with that sha, move cursor to that.
for i, commit := range self.model.Commits {
for i, commit := range self.c.Model().Commits {
if commit.Sha == info.GetCurrentSha() {
self.context().SetSelectedLineIdx(i)
_ = self.context().HandleFocus(types.OnFocusOpts{})
@@ -245,5 +245,5 @@ func (self *BisectController) Context() types.Context {
}
func (self *BisectController) context() *context.LocalCommitsContext {
return self.contexts.LocalCommits
return self.c.Contexts().LocalCommits
}
+58 -34
View File
@@ -14,17 +14,17 @@ import (
type BranchesController struct {
baseController
*controllerCommon
c *ControllerCommon
}
var _ types.IController = &BranchesController{}
func NewBranchesController(
common *controllerCommon,
common *ControllerCommon,
) *BranchesController {
return &BranchesController{
baseController: baseController{},
controllerCommon: common,
baseController: baseController{},
c: common,
}
}
@@ -111,6 +111,30 @@ func (self *BranchesController) GetKeybindings(opts types.KeybindingsOpts) []*ty
}
}
func (self *BranchesController) GetOnRenderToMain() func() error {
return func() error {
return self.c.Helpers().Diff.WithDiffModeCheck(func() error {
var task types.UpdateTask
branch := self.context().GetSelected()
if branch == nil {
task = types.NewRenderStringTask(self.c.Tr.NoBranchesThisRepo)
} else {
cmdObj := self.c.Git().Branch.GetGraphCmdObj(branch.FullRefName())
task = types.NewRunPtyTask(cmdObj.GetCmd())
}
return self.c.RenderToMainViews(types.RefreshMainOpts{
Pair: self.c.MainViewPairs().Normal,
Main: &types.ViewUpdateOpts{
Title: self.c.Tr.LogTitle,
Task: task,
},
})
})
}
}
func (self *BranchesController) setUpstream(selectedBranch *models.Branch) error {
return self.c.Menu(types.CreateMenuOptions{
Title: self.c.Tr.Actions.SetUnsetUpstream,
@@ -118,7 +142,7 @@ func (self *BranchesController) setUpstream(selectedBranch *models.Branch) error
{
LabelColumns: []string{self.c.Tr.LcUnsetUpstream},
OnPress: func() error {
if err := self.git.Branch.UnsetUpstream(selectedBranch.Name); err != nil {
if err := self.c.Git().Branch.UnsetUpstream(selectedBranch.Name); err != nil {
return self.c.Error(err)
}
if err := self.c.Refresh(types.RefreshOptions{
@@ -137,13 +161,13 @@ func (self *BranchesController) setUpstream(selectedBranch *models.Branch) error
{
LabelColumns: []string{self.c.Tr.LcSetUpstream},
OnPress: func() error {
return self.helpers.Upstream.PromptForUpstreamWithoutInitialContent(selectedBranch, func(upstream string) error {
upstreamRemote, upstreamBranch, err := self.helpers.Upstream.ParseUpstream(upstream)
return self.c.Helpers().Upstream.PromptForUpstreamWithoutInitialContent(selectedBranch, func(upstream string) error {
upstreamRemote, upstreamBranch, err := self.c.Helpers().Upstream.ParseUpstream(upstream)
if err != nil {
return self.c.Error(err)
}
if err := self.git.Branch.SetUpstream(upstreamRemote, upstreamBranch, selectedBranch.Name); err != nil {
if err := self.c.Git().Branch.SetUpstream(upstreamRemote, upstreamBranch, selectedBranch.Name); err != nil {
return self.c.Error(err)
}
if err := self.c.Refresh(types.RefreshOptions{
@@ -169,16 +193,16 @@ func (self *BranchesController) Context() types.Context {
}
func (self *BranchesController) context() *context.BranchesContext {
return self.contexts.Branches
return self.c.Contexts().Branches
}
func (self *BranchesController) press(selectedBranch *models.Branch) error {
if selectedBranch == self.helpers.Refs.GetCheckedOutRef() {
if selectedBranch == self.c.Helpers().Refs.GetCheckedOutRef() {
return self.c.ErrorMsg(self.c.Tr.AlreadyCheckedOutBranch)
}
self.c.LogAction(self.c.Tr.Actions.CheckoutBranch)
return self.helpers.Refs.CheckoutRef(selectedBranch.Name, types.CheckoutRefOptions{})
return self.c.Helpers().Refs.CheckoutRef(selectedBranch.Name, types.CheckoutRefOptions{})
}
func (self *BranchesController) handleCreatePullRequest(selectedBranch *models.Branch) error {
@@ -186,7 +210,7 @@ func (self *BranchesController) handleCreatePullRequest(selectedBranch *models.B
}
func (self *BranchesController) handleCreatePullRequestMenu(selectedBranch *models.Branch) error {
checkedOutBranch := self.helpers.Refs.GetCheckedOutRef()
checkedOutBranch := self.c.Helpers().Refs.GetCheckedOutRef()
return self.createPullRequestMenu(selectedBranch, checkedOutBranch)
}
@@ -194,18 +218,18 @@ func (self *BranchesController) handleCreatePullRequestMenu(selectedBranch *mode
func (self *BranchesController) copyPullRequestURL() error {
branch := self.context().GetSelected()
branchExistsOnRemote := self.git.Remote.CheckRemoteBranchExists(branch.Name)
branchExistsOnRemote := self.c.Git().Remote.CheckRemoteBranchExists(branch.Name)
if !branchExistsOnRemote {
return self.c.Error(errors.New(self.c.Tr.NoBranchOnRemote))
}
url, err := self.helpers.Host.GetPullRequestURL(branch.Name, "")
url, err := self.c.Helpers().Host.GetPullRequestURL(branch.Name, "")
if err != nil {
return self.c.Error(err)
}
self.c.LogAction(self.c.Tr.Actions.CopyPullRequestURL)
if err := self.os.CopyToClipboard(url); err != nil {
if err := self.c.OS().CopyToClipboard(url); err != nil {
return self.c.Error(err)
}
@@ -224,7 +248,7 @@ func (self *BranchesController) forceCheckout() error {
Prompt: message,
HandleConfirm: func() error {
self.c.LogAction(self.c.Tr.Actions.ForceCheckoutBranch)
if err := self.git.Branch.Checkout(branch.Name, git_commands.CheckoutOptions{Force: true}); err != nil {
if err := self.c.Git().Branch.Checkout(branch.Name, git_commands.CheckoutOptions{Force: true}); err != nil {
_ = self.c.Error(err)
}
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
@@ -235,10 +259,10 @@ func (self *BranchesController) forceCheckout() error {
func (self *BranchesController) checkoutByName() error {
return self.c.Prompt(types.PromptOpts{
Title: self.c.Tr.BranchName + ":",
FindSuggestionsFunc: self.helpers.Suggestions.GetRefsSuggestionsFunc(),
FindSuggestionsFunc: self.c.Helpers().Suggestions.GetRefsSuggestionsFunc(),
HandleConfirm: func(response string) error {
self.c.LogAction("Checkout branch")
return self.helpers.Refs.CheckoutRef(response, types.CheckoutRefOptions{
return self.c.Helpers().Refs.CheckoutRef(response, types.CheckoutRefOptions{
OnRefNotFound: func(ref string) error {
return self.c.Confirm(types.ConfirmOpts{
Title: self.c.Tr.BranchNotFoundTitle,
@@ -260,7 +284,7 @@ func (self *BranchesController) createNewBranchWithName(newBranchName string) er
return nil
}
if err := self.git.Branch.New(newBranchName, branch.FullRefName()); err != nil {
if err := self.c.Git().Branch.New(newBranchName, branch.FullRefName()); err != nil {
return self.c.Error(err)
}
@@ -269,7 +293,7 @@ func (self *BranchesController) createNewBranchWithName(newBranchName string) er
}
func (self *BranchesController) delete(branch *models.Branch) error {
checkedOutBranch := self.helpers.Refs.GetCheckedOutRef()
checkedOutBranch := self.c.Helpers().Refs.GetCheckedOutRef()
if checkedOutBranch.Name == branch.Name {
return self.c.ErrorMsg(self.c.Tr.CantDeleteCheckOutBranch)
}
@@ -296,7 +320,7 @@ func (self *BranchesController) deleteWithForce(selectedBranch *models.Branch, f
Prompt: message,
HandleConfirm: func() error {
self.c.LogAction(self.c.Tr.Actions.DeleteBranch)
if err := self.git.Branch.Delete(selectedBranch.Name, force); err != nil {
if err := self.c.Git().Branch.Delete(selectedBranch.Name, force); err != nil {
errMessage := err.Error()
if !force && strings.Contains(errMessage, "git branch -D ") {
return self.deleteWithForce(selectedBranch, true)
@@ -310,12 +334,12 @@ func (self *BranchesController) deleteWithForce(selectedBranch *models.Branch, f
func (self *BranchesController) merge() error {
selectedBranchName := self.context().GetSelected().Name
return self.helpers.MergeAndRebase.MergeRefIntoCheckedOutBranch(selectedBranchName)
return self.c.Helpers().MergeAndRebase.MergeRefIntoCheckedOutBranch(selectedBranchName)
}
func (self *BranchesController) rebase() error {
selectedBranchName := self.context().GetSelected().Name
return self.helpers.MergeAndRebase.RebaseOntoRef(selectedBranchName)
return self.c.Helpers().MergeAndRebase.RebaseOntoRef(selectedBranchName)
}
func (self *BranchesController) fastForward(branch *models.Branch) error {
@@ -340,10 +364,10 @@ func (self *BranchesController) fastForward(branch *models.Branch) error {
)
return self.c.WithLoaderPanel(message, func() error {
if branch == self.helpers.Refs.GetCheckedOutRef() {
if branch == self.c.Helpers().Refs.GetCheckedOutRef() {
self.c.LogAction(action)
err := self.git.Sync.Pull(
err := self.c.Git().Sync.Pull(
git_commands.PullOptions{
RemoteName: branch.UpstreamRemote,
BranchName: branch.UpstreamBranch,
@@ -357,7 +381,7 @@ func (self *BranchesController) fastForward(branch *models.Branch) error {
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
} else {
self.c.LogAction(action)
err := self.git.Sync.FastForward(branch.Name, branch.UpstreamRemote, branch.UpstreamBranch)
err := self.c.Git().Sync.FastForward(branch.Name, branch.UpstreamRemote, branch.UpstreamBranch)
if err != nil {
_ = self.c.Error(err)
}
@@ -369,11 +393,11 @@ func (self *BranchesController) fastForward(branch *models.Branch) error {
}
func (self *BranchesController) createTag(branch *models.Branch) error {
return self.helpers.Tags.CreateTagMenu(branch.FullRefName(), func() {})
return self.c.Helpers().Tags.CreateTagMenu(branch.FullRefName(), func() {})
}
func (self *BranchesController) createResetMenu(selectedBranch *models.Branch) error {
return self.helpers.Refs.CreateGitResetMenu(selectedBranch.Name)
return self.c.Helpers().Refs.CreateGitResetMenu(selectedBranch.Name)
}
func (self *BranchesController) rename(branch *models.Branch) error {
@@ -383,7 +407,7 @@ func (self *BranchesController) rename(branch *models.Branch) error {
InitialContent: branch.Name,
HandleConfirm: func(newBranchName string) error {
self.c.LogAction(self.c.Tr.Actions.RenameBranch)
if err := self.git.Branch.Rename(branch.Name, newBranchName); err != nil {
if err := self.c.Git().Branch.Rename(branch.Name, newBranchName); err != nil {
return self.c.Error(err)
}
@@ -391,7 +415,7 @@ func (self *BranchesController) rename(branch *models.Branch) error {
_ = self.c.Refresh(types.RefreshOptions{Mode: types.SYNC, Scope: []types.RefreshableView{types.BRANCHES}})
// now that we've got our stuff again we need to find that branch and reselect it.
for i, newBranch := range self.model.Branches {
for i, newBranch := range self.c.Model().Branches {
if newBranch.Name == newBranchName {
self.context().SetSelectedLineIdx(i)
if err := self.context().HandleRender(); err != nil {
@@ -420,7 +444,7 @@ func (self *BranchesController) rename(branch *models.Branch) error {
}
func (self *BranchesController) newBranch(selectedBranch *models.Branch) error {
return self.helpers.Refs.NewBranch(selectedBranch.FullRefName(), selectedBranch.RefName(), "")
return self.c.Helpers().Refs.NewBranch(selectedBranch.FullRefName(), selectedBranch.RefName(), "")
}
func (self *BranchesController) createPullRequestMenu(selectedBranch *models.Branch, checkedOutBranch *models.Branch) error {
@@ -443,7 +467,7 @@ func (self *BranchesController) createPullRequestMenu(selectedBranch *models.Bra
OnPress: func() error {
return self.c.Prompt(types.PromptOpts{
Title: branch.Name + " →",
FindSuggestionsFunc: self.helpers.Suggestions.GetBranchNameSuggestionsFunc(),
FindSuggestionsFunc: self.c.Helpers().Suggestions.GetBranchNameSuggestionsFunc(),
HandleConfirm: func(targetBranchName string) error {
return self.createPullRequest(branch.Name, targetBranchName)
},
@@ -471,14 +495,14 @@ func (self *BranchesController) createPullRequestMenu(selectedBranch *models.Bra
}
func (self *BranchesController) createPullRequest(from string, to string) error {
url, err := self.helpers.Host.GetPullRequestURL(from, to)
url, err := self.c.Helpers().Host.GetPullRequestURL(from, to)
if err != nil {
return self.c.Error(err)
}
self.c.LogAction(self.c.Tr.Actions.OpenPullRequest)
if err := self.os.OpenLink(url); err != nil {
if err := self.c.OS().OpenLink(url); err != nil {
return self.c.Error(err)
}
@@ -0,0 +1,42 @@
package controllers
import (
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
type CommandLogController struct {
baseController
c *ControllerCommon
}
var _ types.IController = &CommandLogController{}
func NewCommandLogController(
common *ControllerCommon,
) *CommandLogController {
return &CommandLogController{
baseController: baseController{},
c: common,
}
}
func (self *CommandLogController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding {
bindings := []*types.Binding{}
return bindings
}
func (self *CommandLogController) GetOnFocusLost() func(types.OnFocusLostOpts) error {
return func(types.OnFocusLostOpts) error {
self.c.Views().Extras.Autoscroll = true
return nil
}
}
func (self *CommandLogController) Context() types.Context {
return self.context()
}
func (self *CommandLogController) context() types.Context {
return self.c.Contexts().CommandLog
}
@@ -6,17 +6,17 @@ import (
type CommitDescriptionController struct {
baseController
*controllerCommon
c *ControllerCommon
}
var _ types.IController = &CommitMessageController{}
func NewCommitDescriptionController(
common *controllerCommon,
common *ControllerCommon,
) *CommitDescriptionController {
return &CommitDescriptionController{
baseController: baseController{},
controllerCommon: common,
baseController: baseController{},
c: common,
}
}
@@ -44,17 +44,17 @@ func (self *CommitDescriptionController) Context() types.Context {
}
func (self *CommitDescriptionController) context() types.Context {
return self.contexts.CommitMessage
return self.c.Contexts().CommitMessage
}
func (self *CommitDescriptionController) switchToCommitMessage() error {
return self.c.PushContext(self.contexts.CommitMessage)
return self.c.PushContext(self.c.Contexts().CommitMessage)
}
func (self *CommitDescriptionController) close() error {
return self.helpers.Commits.CloseCommitMessagePanel()
return self.c.Helpers().Commits.CloseCommitMessagePanel()
}
func (self *CommitDescriptionController) confirm() error {
return self.helpers.Commits.HandleCommitConfirm()
return self.c.Helpers().Commits.HandleCommitConfirm()
}
@@ -8,29 +8,33 @@ import (
type CommitMessageController struct {
baseController
*controllerCommon
c *ControllerCommon
}
var _ types.IController = &CommitMessageController{}
func NewCommitMessageController(
common *controllerCommon,
common *ControllerCommon,
) *CommitMessageController {
return &CommitMessageController{
baseController: baseController{},
controllerCommon: common,
baseController: baseController{},
c: common,
}
}
// TODO: merge that commit panel PR because we're not currently showing how to add a newline as it's
// handled by the editor func rather than by the controller here.
func (self *CommitMessageController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding {
bindings := []*types.Binding{
{
Key: opts.GetKey(opts.Config.Universal.SubmitEditorText),
Handler: self.confirm,
Key: opts.GetKey(opts.Config.Universal.SubmitEditorText),
Handler: self.confirm,
Description: self.c.Tr.LcConfirm,
},
{
Key: opts.GetKey(opts.Config.Universal.Return),
Handler: self.close,
Key: opts.GetKey(opts.Config.Universal.Return),
Handler: self.close,
Description: self.c.Tr.LcClose,
},
{
Key: opts.GetKey(opts.Config.Universal.PrevItem),
@@ -49,12 +53,19 @@ func (self *CommitMessageController) GetKeybindings(opts types.KeybindingsOpts)
return bindings
}
func (self *CommitMessageController) GetOnFocusLost() func(types.OnFocusLostOpts) error {
return func(types.OnFocusLostOpts) error {
self.context().RenderCommitLength()
return nil
}
}
func (self *CommitMessageController) Context() types.Context {
return self.context()
}
func (self *CommitMessageController) context() *context.CommitMessageContext {
return self.contexts.CommitMessage
return self.c.Contexts().CommitMessage
}
func (self *CommitMessageController) handlePreviousCommit() error {
@@ -69,7 +80,7 @@ func (self *CommitMessageController) handleNextCommit() error {
}
func (self *CommitMessageController) switchToCommitDescription() error {
if err := self.c.PushContext(self.contexts.CommitDescription); err != nil {
if err := self.c.PushContext(self.c.Contexts().CommitDescription); err != nil {
return err
}
return nil
@@ -80,10 +91,10 @@ func (self *CommitMessageController) handleCommitIndexChange(value int) error {
newIndex := currentIndex + value
if newIndex == context.NoCommitIndex {
self.context().SetSelectedIndex(newIndex)
self.helpers.Commits.SetMessageAndDescriptionInView(self.context().GetHistoryMessage())
self.c.Helpers().Commits.SetMessageAndDescriptionInView(self.context().GetHistoryMessage())
return nil
} else if currentIndex == context.NoCommitIndex {
self.context().SetHistoryMessage(self.helpers.Commits.JoinCommitMessageAndDescription())
self.context().SetHistoryMessage(self.c.Helpers().Commits.JoinCommitMessageAndDescription())
}
validCommit, err := self.setCommitMessageAtIndex(newIndex)
@@ -95,21 +106,21 @@ func (self *CommitMessageController) handleCommitIndexChange(value int) error {
// returns true if the given index is for a valid commit
func (self *CommitMessageController) setCommitMessageAtIndex(index int) (bool, error) {
commitMessage, err := self.git.Commit.GetCommitMessageFromHistory(index)
commitMessage, err := self.c.Git().Commit.GetCommitMessageFromHistory(index)
if err != nil {
if err == git_commands.ErrInvalidCommitIndex {
return false, nil
}
return false, self.c.ErrorMsg(self.c.Tr.CommitWithoutMessageErr)
}
self.helpers.Commits.UpdateCommitPanelView(commitMessage)
self.c.Helpers().Commits.UpdateCommitPanelView(commitMessage)
return true, nil
}
func (self *CommitMessageController) confirm() error {
return self.helpers.Commits.HandleCommitConfirm()
return self.c.Helpers().Commits.HandleCommitConfirm()
}
func (self *CommitMessageController) close() error {
return self.helpers.Commits.CloseCommitMessagePanel()
return self.c.Helpers().Commits.CloseCommitMessagePanel()
}
+58 -25
View File
@@ -11,17 +11,17 @@ import (
type CommitFilesController struct {
baseController
*controllerCommon
c *ControllerCommon
}
var _ types.IController = &CommitFilesController{}
func NewCommitFilesController(
common *controllerCommon,
common *ControllerCommon,
) *CommitFilesController {
return &CommitFilesController{
baseController: baseController{},
controllerCommon: common,
baseController: baseController{},
c: common,
}
}
@@ -99,7 +99,40 @@ func (self *CommitFilesController) Context() types.Context {
}
func (self *CommitFilesController) context() *context.CommitFilesContext {
return self.contexts.CommitFiles
return self.c.Contexts().CommitFiles
}
func (self *CommitFilesController) GetOnRenderToMain() func() error {
return func() error {
node := self.context().GetSelected()
if node == nil {
return nil
}
ref := self.context().GetRef()
to := ref.RefName()
from, reverse := self.c.Modes().Diffing.GetFromAndReverseArgsForDiff(ref.ParentRefName())
cmdObj := self.c.Git().WorkingTree.ShowFileDiffCmdObj(
from, to, reverse, node.GetPath(), false, self.c.State().GetIgnoreWhitespaceInDiffView(),
)
task := types.NewRunPtyTask(cmdObj.GetCmd())
pair := self.c.MainViewPairs().Normal
if node.File != nil {
pair = self.c.MainViewPairs().PatchBuilding
}
return self.c.RenderToMainViews(types.RefreshMainOpts{
Pair: pair,
Main: &types.ViewUpdateOpts{
Title: self.c.Tr.Patch,
SubTitle: self.c.Helpers().Diff.IgnoringWhitespaceSubTitle(),
Task: task,
},
Secondary: secondaryPatchPanelUpdateOpts(self.c),
})
}
}
func (self *CommitFilesController) onClickMain(opts gocui.ViewMouseBindingOpts) error {
@@ -112,7 +145,7 @@ func (self *CommitFilesController) onClickMain(opts gocui.ViewMouseBindingOpts)
func (self *CommitFilesController) checkout(node *filetree.CommitFileNode) error {
self.c.LogAction(self.c.Tr.Actions.CheckoutFile)
if err := self.git.WorkingTree.CheckoutFile(self.context().GetRef().RefName(), node.GetPath()); err != nil {
if err := self.c.Git().WorkingTree.CheckoutFile(self.context().GetRef().RefName(), node.GetPath()); err != nil {
return self.c.Error(err)
}
@@ -120,7 +153,7 @@ func (self *CommitFilesController) checkout(node *filetree.CommitFileNode) error
}
func (self *CommitFilesController) discard(node *filetree.CommitFileNode) error {
if ok, err := self.helpers.PatchBuilding.ValidateNormalWorkingTreeState(); !ok {
if ok, err := self.c.Helpers().PatchBuilding.ValidateNormalWorkingTreeState(); !ok {
return err
}
@@ -130,8 +163,8 @@ func (self *CommitFilesController) discard(node *filetree.CommitFileNode) error
HandleConfirm: func() error {
return self.c.WithWaitingStatus(self.c.Tr.RebasingStatus, func() error {
self.c.LogAction(self.c.Tr.Actions.DiscardOldFileChange)
if err := self.git.Rebase.DiscardOldFileChanges(self.model.Commits, self.contexts.LocalCommits.GetSelectedLineIdx(), node.GetPath()); err != nil {
if err := self.helpers.MergeAndRebase.CheckMergeOrRebase(err); err != nil {
if err := self.c.Git().Rebase.DiscardOldFileChanges(self.c.Model().Commits, self.c.Contexts().LocalCommits.GetSelectedLineIdx(), node.GetPath()); err != nil {
if err := self.c.Helpers().MergeAndRebase.CheckMergeOrRebase(err); err != nil {
return err
}
}
@@ -143,7 +176,7 @@ func (self *CommitFilesController) discard(node *filetree.CommitFileNode) error
}
func (self *CommitFilesController) open(node *filetree.CommitFileNode) error {
return self.helpers.Files.OpenFile(node.GetPath())
return self.c.Helpers().Files.OpenFile(node.GetPath())
}
func (self *CommitFilesController) edit(node *filetree.CommitFileNode) error {
@@ -151,13 +184,13 @@ func (self *CommitFilesController) edit(node *filetree.CommitFileNode) error {
return self.c.ErrorMsg(self.c.Tr.ErrCannotEditDirectory)
}
return self.helpers.Files.EditFile(node.GetPath())
return self.c.Helpers().Files.EditFile(node.GetPath())
}
func (self *CommitFilesController) toggleForPatch(node *filetree.CommitFileNode) error {
toggle := func() error {
return self.c.WithWaitingStatus(self.c.Tr.LcUpdatingPatch, func() error {
if !self.git.Patch.PatchBuilder.Active() {
if !self.c.Git().Patch.PatchBuilder.Active() {
if err := self.startPatchBuilder(); err != nil {
return err
}
@@ -166,34 +199,34 @@ func (self *CommitFilesController) toggleForPatch(node *filetree.CommitFileNode)
// if there is any file that hasn't been fully added we'll fully add everything,
// otherwise we'll remove everything
adding := node.SomeFile(func(file *models.CommitFile) bool {
return self.git.Patch.PatchBuilder.GetFileStatus(file.Name, self.context().GetRef().RefName()) != patch.WHOLE
return self.c.Git().Patch.PatchBuilder.GetFileStatus(file.Name, self.context().GetRef().RefName()) != patch.WHOLE
})
err := node.ForEachFile(func(file *models.CommitFile) error {
if adding {
return self.git.Patch.PatchBuilder.AddFileWhole(file.Name)
return self.c.Git().Patch.PatchBuilder.AddFileWhole(file.Name)
} else {
return self.git.Patch.PatchBuilder.RemoveFile(file.Name)
return self.c.Git().Patch.PatchBuilder.RemoveFile(file.Name)
}
})
if err != nil {
return self.c.Error(err)
}
if self.git.Patch.PatchBuilder.IsEmpty() {
self.git.Patch.PatchBuilder.Reset()
if self.c.Git().Patch.PatchBuilder.IsEmpty() {
self.c.Git().Patch.PatchBuilder.Reset()
}
return self.c.PostRefreshUpdate(self.context())
})
}
if self.git.Patch.PatchBuilder.Active() && self.git.Patch.PatchBuilder.To != self.context().GetRef().RefName() {
if self.c.Git().Patch.PatchBuilder.Active() && self.c.Git().Patch.PatchBuilder.To != self.context().GetRef().RefName() {
return self.c.Confirm(types.ConfirmOpts{
Title: self.c.Tr.DiscardPatch,
Prompt: self.c.Tr.DiscardPatchConfirm,
HandleConfirm: func() error {
self.git.Patch.PatchBuilder.Reset()
self.c.Git().Patch.PatchBuilder.Reset()
return toggle()
},
})
@@ -213,9 +246,9 @@ func (self *CommitFilesController) startPatchBuilder() error {
canRebase := commitFilesContext.GetCanRebase()
ref := commitFilesContext.GetRef()
to := ref.RefName()
from, reverse := self.modes.Diffing.GetFromAndReverseArgsForDiff(ref.ParentRefName())
from, reverse := self.c.Modes().Diffing.GetFromAndReverseArgsForDiff(ref.ParentRefName())
self.git.Patch.PatchBuilder.Start(from, to, reverse, canRebase)
self.c.Git().Patch.PatchBuilder.Start(from, to, reverse, canRebase)
return nil
}
@@ -229,21 +262,21 @@ func (self *CommitFilesController) enterCommitFile(node *filetree.CommitFileNode
}
enterTheFile := func() error {
if !self.git.Patch.PatchBuilder.Active() {
if !self.c.Git().Patch.PatchBuilder.Active() {
if err := self.startPatchBuilder(); err != nil {
return err
}
}
return self.c.PushContext(self.contexts.CustomPatchBuilder, opts)
return self.c.PushContext(self.c.Contexts().CustomPatchBuilder, opts)
}
if self.git.Patch.PatchBuilder.Active() && self.git.Patch.PatchBuilder.To != self.context().GetRef().RefName() {
if self.c.Git().Patch.PatchBuilder.Active() && self.c.Git().Patch.PatchBuilder.To != self.context().GetRef().RefName() {
return self.c.Confirm(types.ConfirmOpts{
Title: self.c.Tr.DiscardPatch,
Prompt: self.c.Tr.DiscardPatchConfirm,
HandleConfirm: func() error {
self.git.Patch.PatchBuilder.Reset()
self.c.Git().Patch.PatchBuilder.Reset()
return enterTheFile()
},
})
+13 -31
View File
@@ -1,42 +1,24 @@
package controllers
import (
"github.com/jesseduffield/lazygit/pkg/commands"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/gui/context"
"github.com/jesseduffield/lazygit/pkg/gui/controllers/helpers"
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
type controllerCommon struct {
c *types.HelperCommon
os *oscommands.OSCommand
git *commands.GitCommand
helpers *helpers.Helpers
model *types.Model
contexts *context.ContextTree
modes *types.Modes
mutexes *types.Mutexes
type ControllerCommon struct {
*helpers.HelperCommon
IGetHelpers
}
type IGetHelpers interface {
Helpers() *helpers.Helpers
}
func NewControllerCommon(
c *types.HelperCommon,
os *oscommands.OSCommand,
git *commands.GitCommand,
helpers *helpers.Helpers,
model *types.Model,
contexts *context.ContextTree,
modes *types.Modes,
mutexes *types.Mutexes,
) *controllerCommon {
return &controllerCommon{
c: c,
os: os,
git: git,
helpers: helpers,
model: model,
contexts: contexts,
modes: modes,
mutexes: mutexes,
c *helpers.HelperCommon,
IGetHelpers IGetHelpers,
) *ControllerCommon {
return &ControllerCommon{
HelperCommon: c,
IGetHelpers: IGetHelpers,
}
}
@@ -0,0 +1,65 @@
package controllers
import (
"github.com/jesseduffield/lazygit/pkg/gui/context"
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
type ConfirmationController struct {
baseController
c *ControllerCommon
}
var _ types.IController = &ConfirmationController{}
func NewConfirmationController(
common *ControllerCommon,
) *ConfirmationController {
return &ConfirmationController{
baseController: baseController{},
c: common,
}
}
func (self *ConfirmationController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding {
bindings := []*types.Binding{
{
Key: opts.GetKey(opts.Config.Universal.Confirm),
Handler: func() error { return self.context().State.OnConfirm() },
Description: self.c.Tr.LcConfirm,
Display: true,
},
{
Key: opts.GetKey(opts.Config.Universal.Return),
Handler: func() error { return self.context().State.OnClose() },
Description: self.c.Tr.LcCloseCancel,
Display: true,
},
{
Key: opts.GetKey(opts.Config.Universal.TogglePanel),
Handler: func() error {
if len(self.c.Contexts().Suggestions.State.Suggestions) > 0 {
return self.c.ReplaceContext(self.c.Contexts().Suggestions)
}
return nil
},
},
}
return bindings
}
func (self *ConfirmationController) GetOnFocusLost() func(types.OnFocusLostOpts) error {
return func(types.OnFocusLostOpts) error {
self.c.Helpers().Confirmation.DeactivateConfirmationPrompt()
return nil
}
}
func (self *ConfirmationController) Context() types.Context {
return self.context()
}
func (self *ConfirmationController) context() *context.ConfirmationContext {
return self.c.Contexts().Confirmation
}
@@ -24,17 +24,17 @@ var CONTEXT_KEYS_SHOWING_DIFFS = []types.ContextKey{
type ContextLinesController struct {
baseController
*controllerCommon
c *ControllerCommon
}
var _ types.IController = &ContextLinesController{}
func NewContextLinesController(
common *controllerCommon,
common *ControllerCommon,
) *ContextLinesController {
return &ContextLinesController{
baseController: baseController{},
controllerCommon: common,
baseController: baseController{},
c: common,
}
}
@@ -101,7 +101,7 @@ func (self *ContextLinesController) applyChange() error {
}
func (self *ContextLinesController) checkCanChangeContext() error {
if self.git.Patch.PatchBuilder.Active() {
if self.c.Git().Patch.PatchBuilder.Active() {
return errors.New(self.c.Tr.CantChangeContextSizeError)
}
@@ -0,0 +1,53 @@
package controllers
import (
"strings"
"github.com/jesseduffield/generics/slices"
"github.com/jesseduffield/lazygit/pkg/gui/controllers/helpers"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/samber/lo"
)
type CustomCommandAction struct {
c *ControllerCommon
}
func (self *CustomCommandAction) Call() error {
return self.c.Prompt(types.PromptOpts{
Title: self.c.Tr.CustomCommand,
FindSuggestionsFunc: self.GetCustomCommandsHistorySuggestionsFunc(),
HandleConfirm: func(command string) error {
if self.shouldSaveCommand(command) {
self.c.GetAppState().CustomCommandsHistory = utils.Limit(
lo.Uniq(append(self.c.GetAppState().CustomCommandsHistory, command)),
1000,
)
}
err := self.c.SaveAppState()
if err != nil {
self.c.Log.Error(err)
}
self.c.LogAction(self.c.Tr.Actions.CustomCommand)
return self.c.RunSubprocessAndRefresh(
self.c.OS().Cmd.NewShell(command),
)
},
})
}
func (self *CustomCommandAction) GetCustomCommandsHistorySuggestionsFunc() func(string) []*types.Suggestion {
// reversing so that we display the latest command first
history := slices.Reverse(self.c.GetAppState().CustomCommandsHistory)
return helpers.FuzzySearchFunc(history)
}
// this mimics the shell functionality `ignorespace`
// which doesn't save a command to history if it starts with a space
func (self *CustomCommandAction) shouldSaveCommand(command string) bool {
return !strings.HasPrefix(command, " ")
}
@@ -0,0 +1,219 @@
package controllers
import (
"fmt"
"github.com/jesseduffield/lazygit/pkg/commands/types/enums"
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
type CustomPatchOptionsMenuAction struct {
c *ControllerCommon
}
func (self *CustomPatchOptionsMenuAction) Call() error {
if !self.c.Git().Patch.PatchBuilder.Active() {
return self.c.ErrorMsg(self.c.Tr.NoPatchError)
}
menuItems := []*types.MenuItem{
{
Label: "reset patch",
OnPress: self.c.Helpers().PatchBuilding.Reset,
Key: 'c',
},
{
Label: "apply patch",
OnPress: func() error { return self.handleApplyPatch(false) },
Key: 'a',
},
{
Label: "apply patch in reverse",
OnPress: func() error { return self.handleApplyPatch(true) },
Key: 'r',
},
}
if self.c.Git().Patch.PatchBuilder.CanRebase && self.c.Git().Status.WorkingTreeState() == enums.REBASE_MODE_NONE {
menuItems = append(menuItems, []*types.MenuItem{
{
Label: fmt.Sprintf("remove patch from original commit (%s)", self.c.Git().Patch.PatchBuilder.To),
OnPress: self.handleDeletePatchFromCommit,
Key: 'd',
},
{
Label: "move patch out into index",
OnPress: self.handleMovePatchIntoWorkingTree,
Key: 'i',
},
{
Label: "move patch into new commit",
OnPress: self.handlePullPatchIntoNewCommit,
Key: 'n',
},
}...)
if self.c.CurrentContext().GetKey() == self.c.Contexts().LocalCommits.GetKey() {
selectedCommit := self.c.Contexts().LocalCommits.GetSelected()
if selectedCommit != nil && self.c.Git().Patch.PatchBuilder.To != selectedCommit.Sha {
// adding this option to index 1
menuItems = append(
menuItems[:1],
append(
[]*types.MenuItem{
{
Label: fmt.Sprintf("move patch to selected commit (%s)", selectedCommit.Sha),
OnPress: self.handleMovePatchToSelectedCommit,
Key: 'm',
},
}, menuItems[1:]...,
)...,
)
}
}
}
menuItems = append(menuItems, []*types.MenuItem{
{
Label: "copy patch to clipboard",
OnPress: func() error { return self.copyPatchToClipboard() },
Key: 'y',
},
}...)
return self.c.Menu(types.CreateMenuOptions{Title: self.c.Tr.PatchOptionsTitle, Items: menuItems})
}
func (self *CustomPatchOptionsMenuAction) getPatchCommitIndex() int {
for index, commit := range self.c.Model().Commits {
if commit.Sha == self.c.Git().Patch.PatchBuilder.To {
return index
}
}
return -1
}
func (self *CustomPatchOptionsMenuAction) validateNormalWorkingTreeState() (bool, error) {
if self.c.Git().Status.WorkingTreeState() != enums.REBASE_MODE_NONE {
return false, self.c.ErrorMsg(self.c.Tr.CantPatchWhileRebasingError)
}
return true, nil
}
func (self *CustomPatchOptionsMenuAction) returnFocusFromPatchExplorerIfNecessary() error {
if self.c.CurrentContext().GetKey() == self.c.Contexts().CustomPatchBuilder.GetKey() {
return self.c.Helpers().PatchBuilding.Escape()
}
return nil
}
func (self *CustomPatchOptionsMenuAction) handleDeletePatchFromCommit() error {
if ok, err := self.validateNormalWorkingTreeState(); !ok {
return err
}
if err := self.returnFocusFromPatchExplorerIfNecessary(); err != nil {
return err
}
return self.c.WithWaitingStatus(self.c.Tr.RebasingStatus, func() error {
commitIndex := self.getPatchCommitIndex()
self.c.LogAction(self.c.Tr.Actions.RemovePatchFromCommit)
err := self.c.Git().Patch.DeletePatchesFromCommit(self.c.Model().Commits, commitIndex)
return self.c.Helpers().MergeAndRebase.CheckMergeOrRebase(err)
})
}
func (self *CustomPatchOptionsMenuAction) handleMovePatchToSelectedCommit() error {
if ok, err := self.validateNormalWorkingTreeState(); !ok {
return err
}
if err := self.returnFocusFromPatchExplorerIfNecessary(); err != nil {
return err
}
return self.c.WithWaitingStatus(self.c.Tr.RebasingStatus, func() 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())
return self.c.Helpers().MergeAndRebase.CheckMergeOrRebase(err)
})
}
func (self *CustomPatchOptionsMenuAction) handleMovePatchIntoWorkingTree() error {
if ok, err := self.validateNormalWorkingTreeState(); !ok {
return err
}
if err := self.returnFocusFromPatchExplorerIfNecessary(); err != nil {
return err
}
pull := func(stash bool) error {
return self.c.WithWaitingStatus(self.c.Tr.RebasingStatus, func() error {
commitIndex := self.getPatchCommitIndex()
self.c.LogAction(self.c.Tr.Actions.MovePatchIntoIndex)
err := self.c.Git().Patch.MovePatchIntoIndex(self.c.Model().Commits, commitIndex, stash)
return self.c.Helpers().MergeAndRebase.CheckMergeOrRebase(err)
})
}
if self.c.Helpers().WorkingTree.IsWorkingTreeDirty() {
return self.c.Confirm(types.ConfirmOpts{
Title: self.c.Tr.MustStashTitle,
Prompt: self.c.Tr.MustStashWarning,
HandleConfirm: func() error {
return pull(true)
},
})
} else {
return pull(false)
}
}
func (self *CustomPatchOptionsMenuAction) handlePullPatchIntoNewCommit() error {
if ok, err := self.validateNormalWorkingTreeState(); !ok {
return err
}
if err := self.returnFocusFromPatchExplorerIfNecessary(); err != nil {
return err
}
return self.c.WithWaitingStatus(self.c.Tr.RebasingStatus, func() error {
commitIndex := self.getPatchCommitIndex()
self.c.LogAction(self.c.Tr.Actions.MovePatchIntoNewCommit)
err := self.c.Git().Patch.PullPatchIntoNewCommit(self.c.Model().Commits, commitIndex)
return self.c.Helpers().MergeAndRebase.CheckMergeOrRebase(err)
})
}
func (self *CustomPatchOptionsMenuAction) handleApplyPatch(reverse bool) error {
if err := self.returnFocusFromPatchExplorerIfNecessary(); err != nil {
return err
}
action := self.c.Tr.Actions.ApplyPatch
if reverse {
action = "Apply patch in reverse"
}
self.c.LogAction(action)
if err := self.c.Git().Patch.ApplyCustomPatch(reverse); err != nil {
return self.c.Error(err)
}
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
}
func (self *CustomPatchOptionsMenuAction) copyPatchToClipboard() error {
patch := self.c.Git().Patch.PatchBuilder.RenderAggregatedPatch(true)
self.c.LogAction(self.c.Tr.Actions.CopyPatchToClipboard)
if err := self.c.OS().CopyToClipboard(patch); err != nil {
return self.c.Error(err)
}
self.c.Toast(self.c.Tr.PatchCopiedToClipboard)
return nil
}
@@ -0,0 +1,69 @@
package controllers
import (
"fmt"
"strings"
"github.com/jesseduffield/lazygit/pkg/gui/modes/diffing"
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
type DiffingMenuAction struct {
c *ControllerCommon
}
func (self *DiffingMenuAction) Call() error {
names := self.c.Helpers().Diff.CurrentDiffTerminals()
menuItems := []*types.MenuItem{}
for _, name := range names {
name := name
menuItems = append(menuItems, []*types.MenuItem{
{
Label: fmt.Sprintf("%s %s", self.c.Tr.LcDiff, name),
OnPress: func() error {
self.c.Modes().Diffing.Ref = name
// can scope this down based on current view but too lazy right now
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
},
},
}...)
}
menuItems = append(menuItems, []*types.MenuItem{
{
Label: self.c.Tr.LcEnterRefToDiff,
OnPress: func() error {
return self.c.Prompt(types.PromptOpts{
Title: self.c.Tr.LcEnteRefName,
FindSuggestionsFunc: self.c.Helpers().Suggestions.GetRefsSuggestionsFunc(),
HandleConfirm: func(response string) error {
self.c.Modes().Diffing.Ref = strings.TrimSpace(response)
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
},
})
},
},
}...)
if self.c.Modes().Diffing.Active() {
menuItems = append(menuItems, []*types.MenuItem{
{
Label: self.c.Tr.LcSwapDiff,
OnPress: func() error {
self.c.Modes().Diffing.Reverse = !self.c.Modes().Diffing.Reverse
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
},
},
{
Label: self.c.Tr.LcExitDiffMode,
OnPress: func() error {
self.c.Modes().Diffing = diffing.New()
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
},
},
}...)
}
return self.c.Menu(types.CreateMenuOptions{Title: self.c.Tr.DiffingMenuTitle, Items: menuItems})
}
+132 -57
View File
@@ -9,27 +9,21 @@ import (
"github.com/jesseduffield/lazygit/pkg/gui/context"
"github.com/jesseduffield/lazygit/pkg/gui/filetree"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/utils"
)
type FilesController struct {
baseController // nolint: unused
*controllerCommon
enterSubmodule func(submodule *models.SubmoduleConfig) error
setCommitMessage func(message string)
c *ControllerCommon
}
var _ types.IController = &FilesController{}
func NewFilesController(
common *controllerCommon,
enterSubmodule func(submodule *models.SubmoduleConfig) error,
setCommitMessage func(message string),
common *ControllerCommon,
) *FilesController {
return &FilesController{
controllerCommon: common,
enterSubmodule: enterSubmodule,
setCommitMessage: setCommitMessage,
c: common,
}
}
@@ -47,12 +41,12 @@ func (self *FilesController) GetKeybindings(opts types.KeybindingsOpts) []*types
},
{
Key: opts.GetKey(opts.Config.Files.CommitChanges),
Handler: self.helpers.WorkingTree.HandleCommitPress,
Handler: self.c.Helpers().WorkingTree.HandleCommitPress,
Description: self.c.Tr.CommitChanges,
},
{
Key: opts.GetKey(opts.Config.Files.CommitChangesWithoutHook),
Handler: self.helpers.WorkingTree.HandleWIPCommitPress,
Handler: self.c.Helpers().WorkingTree.HandleWIPCommitPress,
Description: self.c.Tr.LcCommitChangesWithoutHook,
},
{
@@ -62,7 +56,7 @@ func (self *FilesController) GetKeybindings(opts types.KeybindingsOpts) []*types
},
{
Key: opts.GetKey(opts.Config.Files.CommitChangesWithEditor),
Handler: self.helpers.WorkingTree.HandleCommitEditorPress,
Handler: self.c.Helpers().WorkingTree.HandleCommitEditorPress,
Description: self.c.Tr.CommitChangesWithEditor,
},
{
@@ -126,7 +120,7 @@ func (self *FilesController) GetKeybindings(opts types.KeybindingsOpts) []*types
},
{
Key: opts.GetKey(opts.Config.Files.OpenMergeTool),
Handler: self.helpers.WorkingTree.OpenMergeTool,
Handler: self.c.Helpers().WorkingTree.OpenMergeTool,
Description: self.c.Tr.LcOpenMergeTool,
},
{
@@ -172,6 +166,87 @@ func (self *FilesController) GetMouseKeybindings(opts types.KeybindingsOpts) []*
}
}
func (self *FilesController) GetOnRenderToMain() func() error {
return func() error {
return self.c.Helpers().Diff.WithDiffModeCheck(func() error {
node := self.context().GetSelected()
if node == nil {
return self.c.RenderToMainViews(types.RefreshMainOpts{
Pair: self.c.MainViewPairs().Normal,
Main: &types.ViewUpdateOpts{
Title: self.c.Tr.DiffTitle,
SubTitle: self.c.Helpers().Diff.IgnoringWhitespaceSubTitle(),
Task: types.NewRenderStringTask(self.c.Tr.NoChangedFiles),
},
})
}
if node.File != nil && node.File.HasInlineMergeConflicts {
hasConflicts, err := self.c.Helpers().MergeConflicts.SetMergeState(node.GetPath())
if err != nil {
return err
}
if hasConflicts {
return self.c.Helpers().MergeConflicts.Render(false)
}
}
if self.c.UserConfig.Gui.ExperimentalMarkCommitsWhichChangedFile {
go utils.Safe(func() {
recentCommitsWhichChangedFile := self.c.Git().Commit.GetRecentCommitsWhichChangedFile(node.GetPath())
self.c.OnUIThread(func() error {
self.c.State().GetRepoState().SetRecentCommitsWhichChangedFile(recentCommitsWhichChangedFile)
return self.c.PostRefreshUpdate(self.c.Contexts().LocalCommits)
})
})
}
self.c.Helpers().MergeConflicts.ResetMergeState()
pair := self.c.MainViewPairs().Normal
if node.File != nil {
pair = self.c.MainViewPairs().Staging
}
split := self.c.UserConfig.Gui.SplitDiff == "always" || (node.GetHasUnstagedChanges() && node.GetHasStagedChanges())
mainShowsStaged := !split && node.GetHasStagedChanges()
cmdObj := self.c.Git().WorkingTree.WorktreeFileDiffCmdObj(node, false, mainShowsStaged, self.c.State().GetIgnoreWhitespaceInDiffView())
title := self.c.Tr.UnstagedChanges
if mainShowsStaged {
title = self.c.Tr.StagedChanges
}
refreshOpts := types.RefreshMainOpts{
Pair: pair,
Main: &types.ViewUpdateOpts{
Task: types.NewRunPtyTask(cmdObj.GetCmd()),
SubTitle: self.c.Helpers().Diff.IgnoringWhitespaceSubTitle(),
Title: title,
},
}
if split {
cmdObj := self.c.Git().WorkingTree.WorktreeFileDiffCmdObj(node, false, true, self.c.State().GetIgnoreWhitespaceInDiffView())
title := self.c.Tr.StagedChanges
if mainShowsStaged {
title = self.c.Tr.UnstagedChanges
}
refreshOpts.Secondary = &types.ViewUpdateOpts{
Title: title,
SubTitle: self.c.Helpers().Diff.IgnoringWhitespaceSubTitle(),
Task: types.NewRunPtyTask(cmdObj.GetCmd()),
}
}
return self.c.RenderToMainViews(refreshOpts)
})
}
}
func (self *FilesController) GetOnClick() func() error {
return self.checkSelectedFileNode(self.press)
}
@@ -227,7 +302,7 @@ func (self *FilesController) optimisticChange(node *filetree.FileNode, optimisti
rerender := false
err := node.ForEachFile(func(f *models.File) error {
// can't act on the file itself: we need to update the original model file
for _, modelFile := range self.model.Files {
for _, modelFile := range self.c.Model().Files {
if modelFile.Name == f.Name {
if optimisticChangeFn(modelFile) {
rerender = true
@@ -242,7 +317,7 @@ func (self *FilesController) optimisticChange(node *filetree.FileNode, optimisti
return err
}
if rerender {
if err := self.c.PostRefreshUpdate(self.contexts.Files); err != nil {
if err := self.c.PostRefreshUpdate(self.c.Contexts().Files); err != nil {
return err
}
}
@@ -253,8 +328,8 @@ func (self *FilesController) optimisticChange(node *filetree.FileNode, optimisti
func (self *FilesController) pressWithLock(node *filetree.FileNode) error {
// Obtaining this lock because optimistic rendering requires us to mutate
// the files in our model.
self.mutexes.RefreshingFilesMutex.Lock()
defer self.mutexes.RefreshingFilesMutex.Unlock()
self.c.Mutexes().RefreshingFilesMutex.Lock()
defer self.c.Mutexes().RefreshingFilesMutex.Unlock()
if node.IsFile() {
file := node.File
@@ -266,7 +341,7 @@ func (self *FilesController) pressWithLock(node *filetree.FileNode) error {
return err
}
if err := self.git.WorkingTree.StageFile(file.Name); err != nil {
if err := self.c.Git().WorkingTree.StageFile(file.Name); err != nil {
return self.c.Error(err)
}
} else {
@@ -276,7 +351,7 @@ func (self *FilesController) pressWithLock(node *filetree.FileNode) error {
return err
}
if err := self.git.WorkingTree.UnStageFile(file.Names(), file.Tracked); err != nil {
if err := self.c.Git().WorkingTree.UnStageFile(file.Names(), file.Tracked); err != nil {
return self.c.Error(err)
}
}
@@ -294,7 +369,7 @@ func (self *FilesController) pressWithLock(node *filetree.FileNode) error {
return err
}
if err := self.git.WorkingTree.StageFile(node.Path); err != nil {
if err := self.c.Git().WorkingTree.StageFile(node.Path); err != nil {
return self.c.Error(err)
}
} else {
@@ -305,7 +380,7 @@ func (self *FilesController) pressWithLock(node *filetree.FileNode) error {
}
// pretty sure it doesn't matter that we're always passing true here
if err := self.git.WorkingTree.UnStageFile([]string{node.Path}, true); err != nil {
if err := self.c.Git().WorkingTree.UnStageFile([]string{node.Path}, true); err != nil {
return self.c.Error(err)
}
}
@@ -346,7 +421,7 @@ func (self *FilesController) Context() types.Context {
}
func (self *FilesController) context() *context.WorkingTreeContext {
return self.contexts.Files
return self.c.Contexts().Files
}
func (self *FilesController) getSelectedFile() *models.File {
@@ -373,10 +448,10 @@ func (self *FilesController) EnterFile(opts types.OnFocusOpts) error {
file := node.File
submoduleConfigs := self.model.Submodules
submoduleConfigs := self.c.Model().Submodules
if file.IsSubmodule(submoduleConfigs) {
submoduleConfig := file.SubmoduleConfig(submoduleConfigs)
return self.enterSubmodule(submoduleConfig)
return self.c.Helpers().Repos.EnterSubmodule(submoduleConfig)
}
if file.HasInlineMergeConflicts {
@@ -386,7 +461,7 @@ func (self *FilesController) EnterFile(opts types.OnFocusOpts) error {
return self.c.ErrorMsg(self.c.Tr.FileStagingRequirements)
}
return self.c.PushContext(self.contexts.Staging, opts)
return self.c.PushContext(self.c.Contexts().Staging, opts)
}
func (self *FilesController) toggleStagedAll() error {
@@ -402,8 +477,8 @@ func (self *FilesController) toggleStagedAll() error {
}
func (self *FilesController) toggleStagedAllWithLock() error {
self.mutexes.RefreshingFilesMutex.Lock()
defer self.mutexes.RefreshingFilesMutex.Unlock()
self.c.Mutexes().RefreshingFilesMutex.Lock()
defer self.c.Mutexes().RefreshingFilesMutex.Unlock()
root := self.context().FileTreeViewModel.GetRoot()
@@ -420,7 +495,7 @@ func (self *FilesController) toggleStagedAllWithLock() error {
return err
}
if err := self.git.WorkingTree.StageAll(); err != nil {
if err := self.c.Git().WorkingTree.StageAll(); err != nil {
return self.c.Error(err)
}
} else {
@@ -430,7 +505,7 @@ func (self *FilesController) toggleStagedAllWithLock() error {
return err
}
if err := self.git.WorkingTree.UnstageAll(); err != nil {
if err := self.c.Git().WorkingTree.UnstageAll(); err != nil {
return self.c.Error(err)
}
}
@@ -441,7 +516,7 @@ func (self *FilesController) toggleStagedAllWithLock() error {
func (self *FilesController) unstageFiles(node *filetree.FileNode) error {
return node.ForEachFile(func(file *models.File) error {
if file.HasStagedChanges {
if err := self.git.WorkingTree.UnStageFile(file.Names(), file.Tracked); err != nil {
if err := self.c.Git().WorkingTree.UnStageFile(file.Names(), file.Tracked); err != nil {
return err
}
}
@@ -457,7 +532,7 @@ func (self *FilesController) ignoreOrExcludeTracked(node *filetree.FileNode, trA
return err
}
if err := self.git.WorkingTree.RemoveTrackedFiles(node.GetPath()); err != nil {
if err := self.c.Git().WorkingTree.RemoveTrackedFiles(node.GetPath()); err != nil {
return err
}
@@ -495,7 +570,7 @@ func (self *FilesController) ignore(node *filetree.FileNode) error {
if node.GetPath() == ".gitignore" {
return self.c.ErrorMsg(self.c.Tr.Actions.IgnoreFileErr)
}
err := self.ignoreOrExcludeFile(node, self.c.Tr.IgnoreTracked, self.c.Tr.IgnoreTrackedPrompt, self.c.Tr.Actions.LcIgnoreExcludeFile, self.git.WorkingTree.Ignore)
err := self.ignoreOrExcludeFile(node, self.c.Tr.IgnoreTracked, self.c.Tr.IgnoreTrackedPrompt, self.c.Tr.Actions.LcIgnoreExcludeFile, self.c.Git().WorkingTree.Ignore)
if err != nil {
return err
}
@@ -512,7 +587,7 @@ func (self *FilesController) exclude(node *filetree.FileNode) error {
return self.c.ErrorMsg(self.c.Tr.Actions.ExcludeGitIgnoreErr)
}
err := self.ignoreOrExcludeFile(node, self.c.Tr.ExcludeTracked, self.c.Tr.ExcludeTrackedPrompt, self.c.Tr.Actions.ExcludeFile, self.git.WorkingTree.Exclude)
err := self.ignoreOrExcludeFile(node, self.c.Tr.ExcludeTracked, self.c.Tr.ExcludeTrackedPrompt, self.c.Tr.Actions.ExcludeFile, self.c.Git().WorkingTree.Exclude)
if err != nil {
return err
}
@@ -552,19 +627,19 @@ func (self *FilesController) refresh() error {
}
func (self *FilesController) handleAmendCommitPress() error {
if len(self.model.Files) == 0 {
if len(self.c.Model().Files) == 0 {
return self.c.ErrorMsg(self.c.Tr.NoFilesStagedTitle)
}
if !self.helpers.WorkingTree.AnyStagedFiles() {
return self.helpers.WorkingTree.PromptToStageAllAndRetry(self.handleAmendCommitPress)
if !self.c.Helpers().WorkingTree.AnyStagedFiles() {
return self.c.Helpers().WorkingTree.PromptToStageAllAndRetry(self.handleAmendCommitPress)
}
if len(self.model.Commits) == 0 {
if len(self.c.Model().Commits) == 0 {
return self.c.ErrorMsg(self.c.Tr.NoCommitToAmend)
}
return self.helpers.AmendHelper.AmendHead()
return self.c.Helpers().AmendHelper.AmendHead()
}
func (self *FilesController) handleStatusFilterPressed() error {
@@ -603,7 +678,7 @@ func (self *FilesController) edit(node *filetree.FileNode) error {
return self.c.ErrorMsg(self.c.Tr.ErrCannotEditDirectory)
}
return self.helpers.Files.EditFile(node.GetPath())
return self.c.Helpers().Files.EditFile(node.GetPath())
}
func (self *FilesController) Open() error {
@@ -612,7 +687,7 @@ func (self *FilesController) Open() error {
return nil
}
return self.helpers.Files.OpenFile(node.GetPath())
return self.c.Helpers().Files.OpenFile(node.GetPath())
}
func (self *FilesController) switchToMerge() error {
@@ -621,7 +696,7 @@ func (self *FilesController) switchToMerge() error {
return nil
}
return self.helpers.MergeConflicts.SwitchToMerge(file.Name)
return self.c.Helpers().MergeConflicts.SwitchToMerge(file.Name)
}
func (self *FilesController) createStashMenu() error {
@@ -631,28 +706,28 @@ func (self *FilesController) createStashMenu() error {
{
Label: self.c.Tr.LcStashAllChanges,
OnPress: func() error {
if !self.helpers.WorkingTree.IsWorkingTreeDirty() {
if !self.c.Helpers().WorkingTree.IsWorkingTreeDirty() {
return self.c.ErrorMsg(self.c.Tr.NoFilesToStash)
}
return self.handleStashSave(self.git.Stash.Save, self.c.Tr.Actions.StashAllChanges)
return self.handleStashSave(self.c.Git().Stash.Save, self.c.Tr.Actions.StashAllChanges)
},
Key: 'a',
},
{
Label: self.c.Tr.LcStashAllChangesKeepIndex,
OnPress: func() error {
if !self.helpers.WorkingTree.IsWorkingTreeDirty() {
if !self.c.Helpers().WorkingTree.IsWorkingTreeDirty() {
return self.c.ErrorMsg(self.c.Tr.NoFilesToStash)
}
// if there are no staged files it behaves the same as Stash.Save
return self.handleStashSave(self.git.Stash.StashAndKeepIndex, self.c.Tr.Actions.StashAllChangesKeepIndex)
return self.handleStashSave(self.c.Git().Stash.StashAndKeepIndex, self.c.Tr.Actions.StashAllChangesKeepIndex)
},
Key: 'i',
},
{
Label: self.c.Tr.LcStashIncludeUntrackedChanges,
OnPress: func() error {
return self.handleStashSave(self.git.Stash.StashIncludeUntrackedChanges, self.c.Tr.Actions.StashIncludeUntrackedChanges)
return self.handleStashSave(self.c.Git().Stash.StashIncludeUntrackedChanges, self.c.Tr.Actions.StashIncludeUntrackedChanges)
},
Key: 'U',
},
@@ -660,24 +735,24 @@ func (self *FilesController) createStashMenu() error {
Label: self.c.Tr.LcStashStagedChanges,
OnPress: func() error {
// there must be something in staging otherwise the current implementation mucks the stash up
if !self.helpers.WorkingTree.AnyStagedFiles() {
if !self.c.Helpers().WorkingTree.AnyStagedFiles() {
return self.c.ErrorMsg(self.c.Tr.NoTrackedStagedFilesStash)
}
return self.handleStashSave(self.git.Stash.SaveStagedChanges, self.c.Tr.Actions.StashStagedChanges)
return self.handleStashSave(self.c.Git().Stash.SaveStagedChanges, self.c.Tr.Actions.StashStagedChanges)
},
Key: 's',
},
{
Label: self.c.Tr.LcStashUnstagedChanges,
OnPress: func() error {
if !self.helpers.WorkingTree.IsWorkingTreeDirty() {
if !self.c.Helpers().WorkingTree.IsWorkingTreeDirty() {
return self.c.ErrorMsg(self.c.Tr.NoFilesToStash)
}
if self.helpers.WorkingTree.AnyStagedFiles() {
return self.handleStashSave(self.git.Stash.StashUnstagedChanges, self.c.Tr.Actions.StashUnstagedChanges)
if self.c.Helpers().WorkingTree.AnyStagedFiles() {
return self.handleStashSave(self.c.Git().Stash.StashUnstagedChanges, self.c.Tr.Actions.StashUnstagedChanges)
}
// ordinary stash
return self.handleStashSave(self.git.Stash.Save, self.c.Tr.Actions.StashUnstagedChanges)
return self.handleStashSave(self.c.Git().Stash.Save, self.c.Tr.Actions.StashUnstagedChanges)
},
Key: 'u',
},
@@ -686,11 +761,11 @@ func (self *FilesController) createStashMenu() error {
}
func (self *FilesController) stash() error {
return self.handleStashSave(self.git.Stash.Save, self.c.Tr.Actions.StashAllChanges)
return self.handleStashSave(self.c.Git().Stash.Save, self.c.Tr.Actions.StashAllChanges)
}
func (self *FilesController) createResetToUpstreamMenu() error {
return self.helpers.Refs.CreateGitResetMenu("@{upstream}")
return self.c.Helpers().Refs.CreateGitResetMenu("@{upstream}")
}
func (self *FilesController) handleToggleDirCollapsed() error {
@@ -701,7 +776,7 @@ func (self *FilesController) handleToggleDirCollapsed() error {
self.context().FileTreeViewModel.ToggleCollapsed(node.GetPath())
if err := self.c.PostRefreshUpdate(self.contexts.Files); err != nil {
if err := self.c.PostRefreshUpdate(self.c.Contexts().Files); err != nil {
self.c.Log.Error(err)
}
@@ -747,7 +822,7 @@ func (self *FilesController) fetch() error {
func (self *FilesController) fetchAux() (err error) {
self.c.LogAction("Fetch")
err = self.git.Sync.Fetch(git_commands.FetchOptions{})
err = self.c.Git().Sync.Fetch(git_commands.FetchOptions{})
if err != nil && strings.Contains(err.Error(), "exit status 128") {
_ = self.c.ErrorMsg(self.c.Tr.PassUnameWrong)
+14 -14
View File
@@ -12,17 +12,17 @@ import (
type FilesRemoveController struct {
baseController
*controllerCommon
c *ControllerCommon
}
var _ types.IController = &FilesRemoveController{}
func NewFilesRemoveController(
common *controllerCommon,
common *ControllerCommon,
) *FilesRemoveController {
return &FilesRemoveController{
baseController: baseController{},
controllerCommon: common,
baseController: baseController{},
c: common,
}
}
@@ -47,7 +47,7 @@ func (self *FilesRemoveController) remove(node *filetree.FileNode) error {
Label: self.c.Tr.LcDiscardAllChanges,
OnPress: func() error {
self.c.LogAction(self.c.Tr.Actions.DiscardAllChangesInDirectory)
if err := self.git.WorkingTree.DiscardAllDirChanges(node); err != nil {
if err := self.c.Git().WorkingTree.DiscardAllDirChanges(node); err != nil {
return self.c.Error(err)
}
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.FILES}})
@@ -67,7 +67,7 @@ func (self *FilesRemoveController) remove(node *filetree.FileNode) error {
Label: self.c.Tr.LcDiscardUnstagedChanges,
OnPress: func() error {
self.c.LogAction(self.c.Tr.Actions.DiscardUnstagedChangesInDirectory)
if err := self.git.WorkingTree.DiscardUnstagedDirChanges(node); err != nil {
if err := self.c.Git().WorkingTree.DiscardUnstagedDirChanges(node); err != nil {
return self.c.Error(err)
}
@@ -85,7 +85,7 @@ func (self *FilesRemoveController) remove(node *filetree.FileNode) error {
} else {
file := node.File
submodules := self.model.Submodules
submodules := self.c.Model().Submodules
if file.IsSubmodule(submodules) {
submodule := file.SubmoduleConfig(submodules)
@@ -103,7 +103,7 @@ func (self *FilesRemoveController) remove(node *filetree.FileNode) error {
Label: self.c.Tr.LcDiscardAllChanges,
OnPress: func() error {
self.c.LogAction(self.c.Tr.Actions.DiscardAllChangesInFile)
if err := self.git.WorkingTree.DiscardAllFileChanges(file); err != nil {
if err := self.c.Git().WorkingTree.DiscardAllFileChanges(file); err != nil {
return self.c.Error(err)
}
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.FILES}})
@@ -123,7 +123,7 @@ func (self *FilesRemoveController) remove(node *filetree.FileNode) error {
Label: self.c.Tr.LcDiscardUnstagedChanges,
OnPress: func() error {
self.c.LogAction(self.c.Tr.Actions.DiscardAllUnstagedChangesInFile)
if err := self.git.WorkingTree.DiscardUnstagedFileChanges(file); err != nil {
if err := self.c.Git().WorkingTree.DiscardUnstagedFileChanges(file); err != nil {
return self.c.Error(err)
}
@@ -148,17 +148,17 @@ func (self *FilesRemoveController) ResetSubmodule(submodule *models.SubmoduleCon
return self.c.WithWaitingStatus(self.c.Tr.LcResettingSubmoduleStatus, func() error {
self.c.LogAction(self.c.Tr.Actions.ResetSubmodule)
file := self.helpers.WorkingTree.FileForSubmodule(submodule)
file := self.c.Helpers().WorkingTree.FileForSubmodule(submodule)
if file != nil {
if err := self.git.WorkingTree.UnStageFile(file.Names(), file.Tracked); err != nil {
if err := self.c.Git().WorkingTree.UnStageFile(file.Names(), file.Tracked); err != nil {
return self.c.Error(err)
}
}
if err := self.git.Submodule.Stash(submodule); err != nil {
if err := self.c.Git().Submodule.Stash(submodule); err != nil {
return self.c.Error(err)
}
if err := self.git.Submodule.Reset(submodule); err != nil {
if err := self.c.Git().Submodule.Reset(submodule); err != nil {
return self.c.Error(err)
}
@@ -182,5 +182,5 @@ func (self *FilesRemoveController) Context() types.Context {
}
func (self *FilesRemoveController) context() *context.WorkingTreeContext {
return self.contexts.Files
return self.c.Contexts().Files
}
@@ -0,0 +1,79 @@
package controllers
import (
"fmt"
"strings"
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
type FilteringMenuAction struct {
c *ControllerCommon
}
func (self *FilteringMenuAction) Call() error {
fileName := ""
switch self.c.CurrentSideContext() {
case self.c.Contexts().Files:
node := self.c.Contexts().Files.GetSelected()
if node != nil {
fileName = node.GetPath()
}
case self.c.Contexts().CommitFiles:
node := self.c.Contexts().CommitFiles.GetSelected()
if node != nil {
fileName = node.GetPath()
}
}
menuItems := []*types.MenuItem{}
if fileName != "" {
menuItems = append(menuItems, &types.MenuItem{
Label: fmt.Sprintf("%s '%s'", self.c.Tr.LcFilterBy, fileName),
OnPress: func() error {
return self.setFiltering(fileName)
},
})
}
menuItems = append(menuItems, &types.MenuItem{
Label: self.c.Tr.LcFilterPathOption,
OnPress: func() error {
return self.c.Prompt(types.PromptOpts{
FindSuggestionsFunc: self.c.Helpers().Suggestions.GetFilePathSuggestionsFunc(),
Title: self.c.Tr.EnterFileName,
HandleConfirm: func(response string) error {
return self.setFiltering(strings.TrimSpace(response))
},
})
},
})
if self.c.Modes().Filtering.Active() {
menuItems = append(menuItems, &types.MenuItem{
Label: self.c.Tr.LcExitFilterMode,
OnPress: self.c.Helpers().Mode.ClearFiltering,
})
}
return self.c.Menu(types.CreateMenuOptions{Title: self.c.Tr.FilteringMenuTitle, Items: menuItems})
}
func (self *FilteringMenuAction) setFiltering(path string) error {
self.c.Modes().Filtering.SetPath(path)
repoState := self.c.State().GetRepoState()
if repoState.GetScreenMode() == types.SCREEN_NORMAL {
repoState.SetScreenMode(types.SCREEN_HALF)
}
if err := self.c.PushContext(self.c.Contexts().LocalCommits); err != nil {
return err
}
return self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.COMMITS}, Then: func() {
self.c.Contexts().LocalCommits.SetSelectedLineIdx(0)
self.c.Contexts().LocalCommits.FocusLine()
}})
}
+8 -8
View File
@@ -11,17 +11,17 @@ import (
type GitFlowController struct {
baseController
*controllerCommon
c *ControllerCommon
}
var _ types.IController = &GitFlowController{}
func NewGitFlowController(
common *controllerCommon,
common *ControllerCommon,
) *GitFlowController {
return &GitFlowController{
baseController: baseController{},
controllerCommon: common,
baseController: baseController{},
c: common,
}
}
@@ -39,7 +39,7 @@ func (self *GitFlowController) GetKeybindings(opts types.KeybindingsOpts) []*typ
}
func (self *GitFlowController) handleCreateGitFlowMenu(branch *models.Branch) error {
if !self.git.Flow.GitFlowEnabled() {
if !self.c.Git().Flow.GitFlowEnabled() {
return self.c.ErrorMsg("You need to install git-flow and enable it in this repo to use git-flow features")
}
@@ -52,7 +52,7 @@ func (self *GitFlowController) handleCreateGitFlowMenu(branch *models.Branch) er
HandleConfirm: func(name string) error {
self.c.LogAction(self.c.Tr.Actions.GitFlowStart)
return self.c.RunSubprocessAndRefresh(
self.git.Flow.StartCmdObj(branchType, name),
self.c.Git().Flow.StartCmdObj(branchType, name),
)
},
})
@@ -94,7 +94,7 @@ func (self *GitFlowController) handleCreateGitFlowMenu(branch *models.Branch) er
}
func (self *GitFlowController) gitFlowFinishBranch(branchName string) error {
cmdObj, err := self.git.Flow.FinishCmdObj(branchName)
cmdObj, err := self.c.Git().Flow.FinishCmdObj(branchName)
if err != nil {
return self.c.Error(err)
}
@@ -119,5 +119,5 @@ func (self *GitFlowController) Context() types.Context {
}
func (self *GitFlowController) context() *context.BranchesContext {
return self.contexts.Branches
return self.c.Contexts().Branches
}

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