mirror of
https://github.com/jesseduffield/lazygit.git
synced 2026-10-04 21:16:48 -04:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e5e3cc200e | ||
|
|
8e4efddbf7 |
@@ -135,13 +135,10 @@ jobs:
|
||||
# ensure our vendor directory matches up with our go modules
|
||||
run: |
|
||||
go mod vendor && git diff --exit-code || (echo "Unexpected change to vendor directory. Run 'go mod vendor' locally and commit the changes" && exit 1)
|
||||
- name: Check Integration Test List
|
||||
# ensure our integration test list is up to date
|
||||
- name: Check Integration Tests List
|
||||
# ensure our tests list matches up with our generator's output
|
||||
run: |
|
||||
go generate pkg/integration/tests/tests.go && git diff --exit-code || (echo "Integration test list not up to date. Run 'go generate pkg/integration/tests/tests.go' locally and commit the changes" && exit 1)
|
||||
shell: bash # needed so that we get "-o pipefail"
|
||||
- name: Check Filenames
|
||||
run: scripts/check_filenames.sh
|
||||
go generate pkg/integration/tests/tests.go && git diff --exit-code || (echo "Unexpected change to tests list. Run 'go generate pkg/integration/tests/tests.go' locally and commit the changes" && exit 1)
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
|
||||
Vendored
+2
-2
@@ -5,8 +5,8 @@
|
||||
"tasks": [
|
||||
{
|
||||
"label": "Generate cheatsheet",
|
||||
"type": "shell",
|
||||
"command": "go run scripts/cheatsheet/main.go generate",
|
||||
"type": "process",
|
||||
"command": "go run scripts/cheatsheet/main.go ",
|
||||
"problemMatcher": [],
|
||||
},
|
||||
{
|
||||
|
||||
+8
-14
@@ -57,7 +57,6 @@ gui:
|
||||
showFileTree: true # for rendering changes files in a tree format
|
||||
showListFooter: true # for seeing the '5 of 20' message in list panels
|
||||
showRandomTip: true
|
||||
experimentalShowBranchHeads: false # visualize branch heads with (*) in commits list
|
||||
showBottomLine: true # for hiding the bottom information line (unless it has important information to tell you)
|
||||
showCommandLog: true
|
||||
showIcons: false
|
||||
@@ -65,8 +64,6 @@ 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
|
||||
@@ -90,9 +87,6 @@ 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}} --'
|
||||
@@ -177,6 +171,7 @@ keybinding:
|
||||
diffingMenu-alt: '<c-e>' # deprecated
|
||||
copyToClipboard: '<c-o>'
|
||||
submitEditorText: '<enter>'
|
||||
appendNewline: '<a-enter>'
|
||||
extrasMenu: '@'
|
||||
toggleWhitespaceInDiffView: '<c-w>'
|
||||
increaseContextInDiffView: '}'
|
||||
@@ -257,21 +252,21 @@ keybinding:
|
||||
|
||||
```yaml
|
||||
os:
|
||||
open: 'start "" {{filename}}'
|
||||
openCommand: 'start "" {{filename}}'
|
||||
```
|
||||
|
||||
### Linux
|
||||
|
||||
```yaml
|
||||
os:
|
||||
open: 'xdg-open {{filename}} >/dev/null'
|
||||
openCommand: 'xdg-open {{filename}} >/dev/null'
|
||||
```
|
||||
|
||||
### OSX
|
||||
|
||||
```yaml
|
||||
os:
|
||||
open: 'open {{filename}}'
|
||||
openCommand: 'open {{filename}}'
|
||||
```
|
||||
|
||||
### Configuring File Editing
|
||||
@@ -290,9 +285,9 @@ os:
|
||||
editPreset: 'vscode'
|
||||
```
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
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
|
||||
@@ -349,7 +344,6 @@ The available attributes are:
|
||||
- default
|
||||
- reverse # useful for high-contrast
|
||||
- underline
|
||||
- strikethrough
|
||||
|
||||
## Highlighting the selected line
|
||||
|
||||
@@ -480,7 +474,7 @@ services:
|
||||
Where:
|
||||
|
||||
- `gitDomain` stands for the domain used by git itself (i.e. the one present on clone URLs), e.g. `git.work.com`
|
||||
- `provider` is one of `github`, `bitbucket`, `bitbucketServer`, `azuredevops`, `gitlab` or `gitea`
|
||||
- `provider` is one of `github`, `bitbucket`, `bitbucketServer`, `azuredevops` or `gitlab`
|
||||
- `webDomain` is the URL where your git service exposes a web interface and APIs, e.g. `gitservice.work.com`
|
||||
|
||||
## Predefined commit message prefix
|
||||
|
||||
@@ -2,30 +2,28 @@ _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><c-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>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>@</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><c-p></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><c-s></kbd>: view filter-by-path options
|
||||
<kbd>W</kbd>: open diff menu
|
||||
<kbd><c-e></kbd>: open diff menu
|
||||
<kbd><c-w></kbd>: Toggle whether or not whitespace changes are shown in the diff view
|
||||
<kbd>z</kbd>: undo (via reflog) (experimental)
|
||||
<kbd><c-z></kbd>: redo (via reflog) (experimental)
|
||||
<kbd>ctrl+z</kbd>: redo (via reflog) (experimental)
|
||||
<kbd>P</kbd>: push
|
||||
<kbd>p</kbd>: pull
|
||||
</pre>
|
||||
@@ -35,9 +33,9 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<pre>
|
||||
<kbd>,</kbd>: previous page
|
||||
<kbd>.</kbd>: next page
|
||||
<kbd><</kbd>: scroll to top
|
||||
<kbd><</kbd>: scroll to top
|
||||
<kbd>/</kbd>: start search
|
||||
<kbd>></kbd>: scroll to bottom
|
||||
<kbd>></kbd>: scroll to bottom
|
||||
<kbd>H</kbd>: scroll left
|
||||
<kbd>L</kbd>: scroll right
|
||||
<kbd>]</kbd>: next tab
|
||||
@@ -47,29 +45,22 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
## Commit Files
|
||||
|
||||
<pre>
|
||||
<kbd><c-o></kbd>: copy the committed file name to the clipboard
|
||||
<kbd>ctrl+o</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>space</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>enter</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><enter></kbd>: confirm
|
||||
<kbd><esc></kbd>: close
|
||||
</pre>
|
||||
|
||||
## Commits
|
||||
|
||||
<pre>
|
||||
<kbd><c-o></kbd>: copy commit SHA to clipboard
|
||||
<kbd><c-r></kbd>: reset cherry-picked (copied) commits selection
|
||||
<kbd>ctrl+o</kbd>: copy commit SHA to clipboard
|
||||
<kbd>ctrl+r</kbd>: reset cherry-picked (copied) commits selection
|
||||
<kbd>b</kbd>: view bisect options
|
||||
<kbd>s</kbd>: squash down
|
||||
<kbd>f</kbd>: fixup commit
|
||||
@@ -80,38 +71,31 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<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><c-j></kbd>: move commit down one
|
||||
<kbd><c-k></kbd>: move commit up one
|
||||
<kbd>ctrl+j</kbd>: move commit down one
|
||||
<kbd>ctrl+k</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><c-l></kbd>: open log menu
|
||||
<kbd><space></kbd>: checkout commit
|
||||
<kbd>ctrl+l</kbd>: open log menu
|
||||
<kbd>space</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
|
||||
</pre>
|
||||
|
||||
## Confirmation Panel
|
||||
|
||||
<pre>
|
||||
<kbd><enter></kbd>: confirm
|
||||
<kbd><esc></kbd>: close/cancel
|
||||
<kbd>enter</kbd>: view selected item's files
|
||||
</pre>
|
||||
|
||||
## Files
|
||||
|
||||
<pre>
|
||||
<kbd><c-o></kbd>: copy the file name to the clipboard
|
||||
<kbd>ctrl+o</kbd>: copy the file name to the clipboard
|
||||
<kbd>d</kbd>: view 'discard changes' options
|
||||
<kbd><space></kbd>: toggle staged
|
||||
<kbd><c-b></kbd>: Filter files (staged/unstaged)
|
||||
<kbd>space</kbd>: toggle staged
|
||||
<kbd>ctrl+b</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
|
||||
@@ -123,7 +107,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<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>enter</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
|
||||
@@ -134,13 +118,13 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
## Local Branches
|
||||
|
||||
<pre>
|
||||
<kbd><c-o></kbd>: copy branch name to clipboard
|
||||
<kbd>ctrl+o</kbd>: copy branch name to clipboard
|
||||
<kbd>i</kbd>: show git-flow options
|
||||
<kbd><space></kbd>: checkout
|
||||
<kbd>space</kbd>: checkout
|
||||
<kbd>n</kbd>: new branch
|
||||
<kbd>o</kbd>: create pull request
|
||||
<kbd>O</kbd>: create pull request options
|
||||
<kbd><c-y></kbd>: copy pull request URL to clipboard
|
||||
<kbd>ctrl+y</kbd>: copy pull request URL to clipboard
|
||||
<kbd>c</kbd>: checkout by name
|
||||
<kbd>F</kbd>: force checkout
|
||||
<kbd>d</kbd>: delete branch
|
||||
@@ -151,7 +135,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>g</kbd>: view reset options
|
||||
<kbd>R</kbd>: rename branch
|
||||
<kbd>u</kbd>: set/unset upstream
|
||||
<kbd><enter></kbd>: view commits
|
||||
<kbd>enter</kbd>: view commits
|
||||
</pre>
|
||||
|
||||
## Main Panel (Merging)
|
||||
@@ -159,53 +143,53 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<pre>
|
||||
<kbd>e</kbd>: edit file
|
||||
<kbd>o</kbd>: open file
|
||||
<kbd><left></kbd>: select previous conflict
|
||||
<kbd><right></kbd>: select next conflict
|
||||
<kbd><up></kbd>: select previous hunk
|
||||
<kbd><down></kbd>: select next hunk
|
||||
<kbd>◀</kbd>: select previous conflict
|
||||
<kbd>▶</kbd>: select next conflict
|
||||
<kbd>▲</kbd>: select previous hunk
|
||||
<kbd>▼</kbd>: select next hunk
|
||||
<kbd>z</kbd>: undo
|
||||
<kbd>M</kbd>: open external merge tool (git mergetool)
|
||||
<kbd><space></kbd>: pick hunk
|
||||
<kbd>space</kbd>: pick hunk
|
||||
<kbd>b</kbd>: pick all hunks
|
||||
<kbd><esc></kbd>: return to files panel
|
||||
<kbd>esc</kbd>: return to files panel
|
||||
</pre>
|
||||
|
||||
## Main Panel (Normal)
|
||||
|
||||
<pre>
|
||||
<kbd>mouse wheel down</kbd>: scroll down (fn+up)
|
||||
<kbd>mouse wheel up</kbd>: scroll up (fn+down)
|
||||
<kbd>mouse wheel ▼</kbd>: scroll down (fn+up)
|
||||
<kbd>mouse wheel ▲</kbd>: scroll up (fn+down)
|
||||
</pre>
|
||||
|
||||
## Main Panel (Patch Building)
|
||||
|
||||
<pre>
|
||||
<kbd><left></kbd>: select previous hunk
|
||||
<kbd><right></kbd>: select next hunk
|
||||
<kbd>◀</kbd>: select previous hunk
|
||||
<kbd>▶</kbd>: select next hunk
|
||||
<kbd>v</kbd>: toggle drag select
|
||||
<kbd>V</kbd>: toggle drag select
|
||||
<kbd>a</kbd>: toggle select hunk
|
||||
<kbd><c-o></kbd>: copy the selected text to the clipboard
|
||||
<kbd>ctrl+o</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>space</kbd>: add/remove line(s) to patch
|
||||
<kbd>esc</kbd>: exit custom patch builder
|
||||
</pre>
|
||||
|
||||
## Main Panel (Staging)
|
||||
|
||||
<pre>
|
||||
<kbd><left></kbd>: select previous hunk
|
||||
<kbd><right></kbd>: select next hunk
|
||||
<kbd>◀</kbd>: select previous hunk
|
||||
<kbd>▶</kbd>: select next hunk
|
||||
<kbd>v</kbd>: toggle drag select
|
||||
<kbd>V</kbd>: toggle drag select
|
||||
<kbd>a</kbd>: toggle select hunk
|
||||
<kbd><c-o></kbd>: copy the selected text to the clipboard
|
||||
<kbd>ctrl+o</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>esc</kbd>: return to files panel
|
||||
<kbd>tab</kbd>: switch to other panel (staged/unstaged changes)
|
||||
<kbd>space</kbd>: toggle line staged / unstaged
|
||||
<kbd>d</kbd>: delete change (git reset)
|
||||
<kbd>E</kbd>: edit hunk
|
||||
<kbd>c</kbd>: commit changes
|
||||
@@ -213,41 +197,34 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>C</kbd>: commit changes using git editor
|
||||
</pre>
|
||||
|
||||
## Menu
|
||||
|
||||
<pre>
|
||||
<kbd><enter></kbd>: execute
|
||||
<kbd><esc></kbd>: close
|
||||
</pre>
|
||||
|
||||
## Reflog
|
||||
|
||||
<pre>
|
||||
<kbd><c-o></kbd>: copy commit SHA to clipboard
|
||||
<kbd><space></kbd>: checkout commit
|
||||
<kbd>ctrl+o</kbd>: copy commit SHA to clipboard
|
||||
<kbd>space</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><c-r></kbd>: reset cherry-picked (copied) commits selection
|
||||
<kbd><enter></kbd>: view commits
|
||||
<kbd>ctrl+r</kbd>: reset cherry-picked (copied) commits selection
|
||||
<kbd>enter</kbd>: view commits
|
||||
</pre>
|
||||
|
||||
## Remote Branches
|
||||
|
||||
<pre>
|
||||
<kbd><c-o></kbd>: copy branch name to clipboard
|
||||
<kbd><space></kbd>: checkout
|
||||
<kbd>ctrl+o</kbd>: copy branch name to clipboard
|
||||
<kbd>space</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>esc</kbd>: Return to remotes list
|
||||
<kbd>g</kbd>: view reset options
|
||||
<kbd><enter></kbd>: view commits
|
||||
<kbd>enter</kbd>: view commits
|
||||
</pre>
|
||||
|
||||
## Remotes
|
||||
@@ -262,44 +239,44 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
## Stash
|
||||
|
||||
<pre>
|
||||
<kbd><space></kbd>: apply
|
||||
<kbd>space</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>enter</kbd>: view selected item's files
|
||||
</pre>
|
||||
|
||||
## Status
|
||||
|
||||
<pre>
|
||||
<kbd>o</kbd>: open config file
|
||||
<kbd>e</kbd>: edit config file
|
||||
<kbd>o</kbd>: open config file
|
||||
<kbd>u</kbd>: check for update
|
||||
<kbd><enter></kbd>: switch to a recent repo
|
||||
<kbd>enter</kbd>: switch to a recent repo
|
||||
<kbd>a</kbd>: show all branch logs
|
||||
</pre>
|
||||
|
||||
## Sub-commits
|
||||
|
||||
<pre>
|
||||
<kbd><c-o></kbd>: copy commit SHA to clipboard
|
||||
<kbd><space></kbd>: checkout commit
|
||||
<kbd>ctrl+o</kbd>: copy commit SHA to clipboard
|
||||
<kbd>space</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><c-r></kbd>: reset cherry-picked (copied) commits selection
|
||||
<kbd><enter></kbd>: view selected item's files
|
||||
<kbd>ctrl+r</kbd>: reset cherry-picked (copied) commits selection
|
||||
<kbd>enter</kbd>: view selected item's files
|
||||
</pre>
|
||||
|
||||
## Submodules
|
||||
|
||||
<pre>
|
||||
<kbd><c-o></kbd>: copy submodule name to clipboard
|
||||
<kbd><enter></kbd>: enter submodule
|
||||
<kbd>ctrl+o</kbd>: copy submodule name to clipboard
|
||||
<kbd>enter</kbd>: enter submodule
|
||||
<kbd>d</kbd>: remove submodule
|
||||
<kbd>u</kbd>: update submodule
|
||||
<kbd>n</kbd>: add new submodule
|
||||
@@ -311,10 +288,10 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
## Tags
|
||||
|
||||
<pre>
|
||||
<kbd><space></kbd>: checkout
|
||||
<kbd>space</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>enter</kbd>: view commits
|
||||
</pre>
|
||||
|
||||
@@ -2,30 +2,28 @@ _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><c-r></kbd>: 最近使用したリポジトリに切り替え
|
||||
<kbd><pgup></kbd>: メインパネルを上にスクロール (fn+up/shift+k)
|
||||
<kbd><pgdown></kbd>: メインパネルを下にスクロール (fn+down/shift+j)
|
||||
<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>@</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><c-p></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><c-s></kbd>: view filter-by-path options
|
||||
<kbd>W</kbd>: 差分メニューを開く
|
||||
<kbd><c-e></kbd>: 差分メニューを開く
|
||||
<kbd><c-w></kbd>: 空白文字の差分の表示有無を切り替え
|
||||
<kbd>z</kbd>: アンドゥ (via reflog) (experimental)
|
||||
<kbd><c-z></kbd>: リドゥ (via reflog) (experimental)
|
||||
<kbd>ctrl+z</kbd>: リドゥ (via reflog) (experimental)
|
||||
<kbd>P</kbd>: push
|
||||
<kbd>p</kbd>: pull
|
||||
</pre>
|
||||
@@ -35,9 +33,9 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<pre>
|
||||
<kbd>,</kbd>: 前のページ
|
||||
<kbd>.</kbd>: 次のページ
|
||||
<kbd><</kbd>: 最上部までスクロール
|
||||
<kbd><</kbd>: 最上部までスクロール
|
||||
<kbd>/</kbd>: 検索を開始
|
||||
<kbd>></kbd>: 最下部までスクロール
|
||||
<kbd>></kbd>: 最下部までスクロール
|
||||
<kbd>H</kbd>: 左スクロール
|
||||
<kbd>L</kbd>: 右スクロール
|
||||
<kbd>]</kbd>: 次のタブ
|
||||
@@ -47,34 +45,34 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
## Stash
|
||||
|
||||
<pre>
|
||||
<kbd><space></kbd>: 適用
|
||||
<kbd>space</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>enter</kbd>: view selected item's files
|
||||
</pre>
|
||||
|
||||
## Sub-commits
|
||||
|
||||
<pre>
|
||||
<kbd><c-o></kbd>: コミットのSHAをクリップボードにコピー
|
||||
<kbd><space></kbd>: コミットをチェックアウト
|
||||
<kbd>ctrl+o</kbd>: コミットのSHAをクリップボードにコピー
|
||||
<kbd>space</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><c-r></kbd>: reset cherry-picked (copied) commits selection
|
||||
<kbd><enter></kbd>: view selected item's files
|
||||
<kbd>ctrl+r</kbd>: reset cherry-picked (copied) commits selection
|
||||
<kbd>enter</kbd>: view selected item's files
|
||||
</pre>
|
||||
|
||||
## コミット
|
||||
|
||||
<pre>
|
||||
<kbd><c-o></kbd>: コミットのSHAをクリップボードにコピー
|
||||
<kbd><c-r></kbd>: reset cherry-picked (copied) commits selection
|
||||
<kbd>ctrl+o</kbd>: コミットのSHAをクリップボードにコピー
|
||||
<kbd>ctrl+r</kbd>: reset cherry-picked (copied) commits selection
|
||||
<kbd>b</kbd>: view bisect options
|
||||
<kbd>s</kbd>: squash down
|
||||
<kbd>f</kbd>: fixup commit
|
||||
@@ -85,50 +83,43 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>p</kbd>: pick commit (when mid-rebase)
|
||||
<kbd>F</kbd>: このコミットに対するfixupコミットを作成
|
||||
<kbd>S</kbd>: squash all 'fixup!' commits above selected commit (autosquash)
|
||||
<kbd><c-j></kbd>: コミットを1つ下に移動
|
||||
<kbd><c-k></kbd>: コミットを1つ上に移動
|
||||
<kbd>ctrl+j</kbd>: コミットを1つ下に移動
|
||||
<kbd>ctrl+k</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><c-l></kbd>: ログメニューを開く
|
||||
<kbd><space></kbd>: コミットをチェックアウト
|
||||
<kbd>ctrl+l</kbd>: ログメニューを開く
|
||||
<kbd>space</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>enter</kbd>: view selected item's files
|
||||
</pre>
|
||||
|
||||
## コミットファイル
|
||||
|
||||
<pre>
|
||||
<kbd><c-o></kbd>: コミットされたファイル名をクリップボードにコピー
|
||||
<kbd>ctrl+o</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>space</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>enter</kbd>: enter file to add selected lines to the patch (or toggle directory collapsed)
|
||||
<kbd>`</kbd>: ファイルツリーの表示を切り替え
|
||||
</pre>
|
||||
|
||||
## コミットメッセージ
|
||||
|
||||
<pre>
|
||||
<kbd><enter></kbd>: 確認
|
||||
<kbd><esc></kbd>: 閉じる
|
||||
</pre>
|
||||
|
||||
## サブモジュール
|
||||
|
||||
<pre>
|
||||
<kbd><c-o></kbd>: サブモジュール名をクリップボードにコピー
|
||||
<kbd><enter></kbd>: サブモジュールを開く
|
||||
<kbd>ctrl+o</kbd>: サブモジュール名をクリップボードにコピー
|
||||
<kbd>enter</kbd>: サブモジュールを開く
|
||||
<kbd>d</kbd>: サブモジュールを削除
|
||||
<kbd>u</kbd>: サブモジュールを更新
|
||||
<kbd>n</kbd>: サブモジュールを新規追加
|
||||
@@ -140,31 +131,31 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
## ステータス
|
||||
|
||||
<pre>
|
||||
<kbd>o</kbd>: 設定ファイルを開く
|
||||
<kbd>e</kbd>: 設定ファイルを編集
|
||||
<kbd>o</kbd>: 設定ファイルを開く
|
||||
<kbd>u</kbd>: 更新を確認
|
||||
<kbd><enter></kbd>: 最近使用したリポジトリに切り替え
|
||||
<kbd>enter</kbd>: 最近使用したリポジトリに切り替え
|
||||
<kbd>a</kbd>: すべてのブランチログを表示
|
||||
</pre>
|
||||
|
||||
## タグ
|
||||
|
||||
<pre>
|
||||
<kbd><space></kbd>: チェックアウト
|
||||
<kbd>space</kbd>: チェックアウト
|
||||
<kbd>d</kbd>: タグを削除
|
||||
<kbd>P</kbd>: タグをpush
|
||||
<kbd>n</kbd>: タグを作成
|
||||
<kbd>g</kbd>: view reset options
|
||||
<kbd><enter></kbd>: コミットを閲覧
|
||||
<kbd>enter</kbd>: コミットを閲覧
|
||||
</pre>
|
||||
|
||||
## ファイル
|
||||
|
||||
<pre>
|
||||
<kbd><c-o></kbd>: ファイル名をクリップボードにコピー
|
||||
<kbd>ctrl+o</kbd>: ファイル名をクリップボードにコピー
|
||||
<kbd>d</kbd>: view 'discard changes' options
|
||||
<kbd><space></kbd>: ステージ/アンステージ
|
||||
<kbd><c-b></kbd>: ファイルをフィルタ (ステージ/アンステージ)
|
||||
<kbd>space</kbd>: ステージ/アンステージ
|
||||
<kbd>ctrl+b</kbd>: ファイルをフィルタ (ステージ/アンステージ)
|
||||
<kbd>c</kbd>: 変更をコミット
|
||||
<kbd>w</kbd>: pre-commitフックを実行せずに変更をコミット
|
||||
<kbd>A</kbd>: 最新のコミットにamend
|
||||
@@ -176,7 +167,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<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>enter</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>: ファイルツリーの表示を切り替え
|
||||
@@ -187,13 +178,13 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
## ブランチ
|
||||
|
||||
<pre>
|
||||
<kbd><c-o></kbd>: ブランチ名をクリップボードにコピー
|
||||
<kbd>ctrl+o</kbd>: ブランチ名をクリップボードにコピー
|
||||
<kbd>i</kbd>: show git-flow options
|
||||
<kbd><space></kbd>: チェックアウト
|
||||
<kbd>space</kbd>: チェックアウト
|
||||
<kbd>n</kbd>: 新しいブランチを作成
|
||||
<kbd>o</kbd>: Pull Requestを作成
|
||||
<kbd>O</kbd>: create pull request options
|
||||
<kbd><c-y></kbd>: Pull RequestのURLをクリップボードにコピー
|
||||
<kbd>ctrl+y</kbd>: Pull RequestのURLをクリップボードにコピー
|
||||
<kbd>c</kbd>: checkout by name
|
||||
<kbd>F</kbd>: force checkout
|
||||
<kbd>d</kbd>: ブランチを削除
|
||||
@@ -204,7 +195,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>g</kbd>: view reset options
|
||||
<kbd>R</kbd>: ブランチ名を変更
|
||||
<kbd>u</kbd>: set/unset upstream
|
||||
<kbd><enter></kbd>: コミットを閲覧
|
||||
<kbd>enter</kbd>: コミットを閲覧
|
||||
</pre>
|
||||
|
||||
## メインパネル (Merging)
|
||||
@@ -212,53 +203,53 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<pre>
|
||||
<kbd>e</kbd>: ファイルを編集
|
||||
<kbd>o</kbd>: ファイルを開く
|
||||
<kbd><left></kbd>: 前のコンフリクトを選択
|
||||
<kbd><right></kbd>: 次のコンフリクトを選択
|
||||
<kbd><up></kbd>: 前のhunkを選択
|
||||
<kbd><down></kbd>: 次のhunkを選択
|
||||
<kbd>◀</kbd>: 前のコンフリクトを選択
|
||||
<kbd>▶</kbd>: 次のコンフリクトを選択
|
||||
<kbd>▲</kbd>: 前のhunkを選択
|
||||
<kbd>▼</kbd>: 次のhunkを選択
|
||||
<kbd>z</kbd>: アンドゥ
|
||||
<kbd>M</kbd>: git mergetoolを開く
|
||||
<kbd><space></kbd>: pick hunk
|
||||
<kbd>space</kbd>: pick hunk
|
||||
<kbd>b</kbd>: pick all hunks
|
||||
<kbd><esc></kbd>: ファイル一覧に戻る
|
||||
<kbd>esc</kbd>: ファイル一覧に戻る
|
||||
</pre>
|
||||
|
||||
## メインパネル (Normal)
|
||||
|
||||
<pre>
|
||||
<kbd>mouse wheel down</kbd>: 下にスクロール (fn+up)
|
||||
<kbd>mouse wheel up</kbd>: 上にスクロール (fn+down)
|
||||
<kbd>mouse wheel ▼</kbd>: 下にスクロール (fn+up)
|
||||
<kbd>mouse wheel ▲</kbd>: 上にスクロール (fn+down)
|
||||
</pre>
|
||||
|
||||
## メインパネル (Patch Building)
|
||||
|
||||
<pre>
|
||||
<kbd><left></kbd>: 前のhunkを選択
|
||||
<kbd><right></kbd>: 次のhunkを選択
|
||||
<kbd>◀</kbd>: 前のhunkを選択
|
||||
<kbd>▶</kbd>: 次のhunkを選択
|
||||
<kbd>v</kbd>: 範囲選択を切り替え
|
||||
<kbd>V</kbd>: 範囲選択を切り替え
|
||||
<kbd>a</kbd>: hunk選択を切り替え
|
||||
<kbd><c-o></kbd>: 選択されたテキストをクリップボードにコピー
|
||||
<kbd>ctrl+o</kbd>: 選択されたテキストをクリップボードにコピー
|
||||
<kbd>o</kbd>: ファイルを開く
|
||||
<kbd>e</kbd>: ファイルを編集
|
||||
<kbd><space></kbd>: 行をパッチに追加/削除
|
||||
<kbd><esc></kbd>: exit custom patch builder
|
||||
<kbd>space</kbd>: 行をパッチに追加/削除
|
||||
<kbd>esc</kbd>: exit custom patch builder
|
||||
</pre>
|
||||
|
||||
## メインパネル (Staging)
|
||||
|
||||
<pre>
|
||||
<kbd><left></kbd>: 前のhunkを選択
|
||||
<kbd><right></kbd>: 次のhunkを選択
|
||||
<kbd>◀</kbd>: 前のhunkを選択
|
||||
<kbd>▶</kbd>: 次のhunkを選択
|
||||
<kbd>v</kbd>: 範囲選択を切り替え
|
||||
<kbd>V</kbd>: 範囲選択を切り替え
|
||||
<kbd>a</kbd>: hunk選択を切り替え
|
||||
<kbd><c-o></kbd>: 選択されたテキストをクリップボードにコピー
|
||||
<kbd>ctrl+o</kbd>: 選択されたテキストをクリップボードにコピー
|
||||
<kbd>o</kbd>: ファイルを開く
|
||||
<kbd>e</kbd>: ファイルを編集
|
||||
<kbd><esc></kbd>: ファイル一覧に戻る
|
||||
<kbd><tab></kbd>: パネルを切り替え
|
||||
<kbd><space></kbd>: 選択行をステージ/アンステージ
|
||||
<kbd>esc</kbd>: ファイル一覧に戻る
|
||||
<kbd>tab</kbd>: パネルを切り替え
|
||||
<kbd>space</kbd>: 選択行をステージ/アンステージ
|
||||
<kbd>d</kbd>: 変更を削除 (git reset)
|
||||
<kbd>E</kbd>: edit hunk
|
||||
<kbd>c</kbd>: 変更をコミット
|
||||
@@ -266,13 +257,6 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>C</kbd>: gitエディタを使用して変更をコミット
|
||||
</pre>
|
||||
|
||||
## メニュー
|
||||
|
||||
<pre>
|
||||
<kbd><enter></kbd>: 実行
|
||||
<kbd><esc></kbd>: 閉じる
|
||||
</pre>
|
||||
|
||||
## リモート
|
||||
|
||||
<pre>
|
||||
@@ -285,36 +269,29 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
## リモートブランチ
|
||||
|
||||
<pre>
|
||||
<kbd><c-o></kbd>: ブランチ名をクリップボードにコピー
|
||||
<kbd><space></kbd>: チェックアウト
|
||||
<kbd>ctrl+o</kbd>: ブランチ名をクリップボードにコピー
|
||||
<kbd>space</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>esc</kbd>: リモート一覧に戻る
|
||||
<kbd>g</kbd>: view reset options
|
||||
<kbd><enter></kbd>: コミットを閲覧
|
||||
<kbd>enter</kbd>: コミットを閲覧
|
||||
</pre>
|
||||
|
||||
## 参照ログ
|
||||
|
||||
<pre>
|
||||
<kbd><c-o></kbd>: コミットのSHAをクリップボードにコピー
|
||||
<kbd><space></kbd>: コミットをチェックアウト
|
||||
<kbd>ctrl+o</kbd>: コミットのSHAをクリップボードにコピー
|
||||
<kbd>space</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><c-r></kbd>: reset cherry-picked (copied) commits selection
|
||||
<kbd><enter></kbd>: コミットを閲覧
|
||||
</pre>
|
||||
|
||||
## 確認パネル
|
||||
|
||||
<pre>
|
||||
<kbd><enter></kbd>: 確認
|
||||
<kbd><esc></kbd>: 閉じる/キャンセル
|
||||
<kbd>ctrl+r</kbd>: reset cherry-picked (copied) commits selection
|
||||
<kbd>enter</kbd>: コミットを閲覧
|
||||
</pre>
|
||||
|
||||
@@ -2,30 +2,28 @@ _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><c-r></kbd>: 최근에 사용한 저장소로 전환
|
||||
<kbd><pgup></kbd>: 메인 패널을 위로 스크롤 (fn+up/shift+k)
|
||||
<kbd><pgdown></kbd>: 메인 패널을 아래로로 스크롤 (fn+down/shift+j)
|
||||
<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>@</kbd>: 명령어 로그 메뉴 열기
|
||||
<kbd>ctrl+w</kbd>: 공백문자를 Diff 뷰에서 표시 여부 전환
|
||||
<kbd>}</kbd>: diff 보기의 변경 사항 주위에 표시되는 컨텍스트의 크기를 늘리기
|
||||
<kbd>{</kbd>: diff 보기의 변경 사항 주위에 표시되는 컨텍스트 크기 줄이기
|
||||
<kbd>:</kbd>: execute custom command
|
||||
<kbd><c-p></kbd>: 커스텀 Patch 옵션 보기
|
||||
<kbd>m</kbd>: view merge/rebase options
|
||||
<kbd>R</kbd>: 새로고침
|
||||
<kbd>+</kbd>: 다음 스크린 모드 (normal/half/fullscreen)
|
||||
<kbd>_</kbd>: 이전 스크린 모드
|
||||
<kbd>?</kbd>: 매뉴 열기
|
||||
<kbd><c-s></kbd>: view filter-by-path options
|
||||
<kbd>W</kbd>: Diff 메뉴 열기
|
||||
<kbd><c-e></kbd>: Diff 메뉴 열기
|
||||
<kbd><c-w></kbd>: 공백문자를 Diff 뷰에서 표시 여부 전환
|
||||
<kbd>z</kbd>: 되돌리기 (reflog) (실험적)
|
||||
<kbd><c-z></kbd>: 다시 실행 (reflog) (실험적)
|
||||
<kbd>ctrl+z</kbd>: 다시 실행 (reflog) (실험적)
|
||||
<kbd>P</kbd>: 푸시
|
||||
<kbd>p</kbd>: 업데이트
|
||||
</pre>
|
||||
@@ -35,9 +33,9 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<pre>
|
||||
<kbd>,</kbd>: 이전 페이지
|
||||
<kbd>.</kbd>: 다음 페이지
|
||||
<kbd><</kbd>: 맨 위로 스크롤
|
||||
<kbd><</kbd>: 맨 위로 스크롤
|
||||
<kbd>/</kbd>: 검색 시작
|
||||
<kbd>></kbd>: 맨 아래로 스크롤
|
||||
<kbd>></kbd>: 맨 아래로 스크롤
|
||||
<kbd>H</kbd>: 우 스크롤
|
||||
<kbd>L</kbd>: 좌 스크롤
|
||||
<kbd>]</kbd>: 이전 탭
|
||||
@@ -47,49 +45,42 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
## Reflog
|
||||
|
||||
<pre>
|
||||
<kbd><c-o></kbd>: 커밋 SHA를 클립보드에 복사
|
||||
<kbd><space></kbd>: 커밋을 체크아웃
|
||||
<kbd>ctrl+o</kbd>: 커밋 SHA를 클립보드에 복사
|
||||
<kbd>space</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><c-r></kbd>: reset cherry-picked (copied) commits selection
|
||||
<kbd><enter></kbd>: 커밋 보기
|
||||
<kbd>ctrl+r</kbd>: reset cherry-picked (copied) commits selection
|
||||
<kbd>enter</kbd>: 커밋 보기
|
||||
</pre>
|
||||
|
||||
## Stash
|
||||
|
||||
<pre>
|
||||
<kbd><space></kbd>: 적용
|
||||
<kbd>space</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>enter</kbd>: view selected item's files
|
||||
</pre>
|
||||
|
||||
## Sub-commits
|
||||
|
||||
<pre>
|
||||
<kbd><c-o></kbd>: 커밋 SHA를 클립보드에 복사
|
||||
<kbd><space></kbd>: 커밋을 체크아웃
|
||||
<kbd>ctrl+o</kbd>: 커밋 SHA를 클립보드에 복사
|
||||
<kbd>space</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><c-r></kbd>: reset cherry-picked (copied) commits selection
|
||||
<kbd><enter></kbd>: view selected item's files
|
||||
</pre>
|
||||
|
||||
## 메뉴
|
||||
|
||||
<pre>
|
||||
<kbd><enter></kbd>: 실행
|
||||
<kbd><esc></kbd>: 닫기
|
||||
<kbd>ctrl+r</kbd>: reset cherry-picked (copied) commits selection
|
||||
<kbd>enter</kbd>: view selected item's files
|
||||
</pre>
|
||||
|
||||
## 메인 패널 (Merging)
|
||||
@@ -97,53 +88,53 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<pre>
|
||||
<kbd>e</kbd>: 파일 편집
|
||||
<kbd>o</kbd>: 파일 닫기
|
||||
<kbd><left></kbd>: 이전 충돌을 선택
|
||||
<kbd><right></kbd>: 다음 충돌을 선택
|
||||
<kbd><up></kbd>: 이전 hunk를 선택
|
||||
<kbd><down></kbd>: 다음 hunk를 선택
|
||||
<kbd>◀</kbd>: 이전 충돌을 선택
|
||||
<kbd>▶</kbd>: 다음 충돌을 선택
|
||||
<kbd>▲</kbd>: 이전 hunk를 선택
|
||||
<kbd>▼</kbd>: 다음 hunk를 선택
|
||||
<kbd>z</kbd>: 되돌리기
|
||||
<kbd>M</kbd>: git mergetool를 열기
|
||||
<kbd><space></kbd>: pick hunk
|
||||
<kbd>space</kbd>: pick hunk
|
||||
<kbd>b</kbd>: pick all hunks
|
||||
<kbd><esc></kbd>: 파일 목록으로 돌아가기
|
||||
<kbd>esc</kbd>: 파일 목록으로 돌아가기
|
||||
</pre>
|
||||
|
||||
## 메인 패널 (Normal)
|
||||
|
||||
<pre>
|
||||
<kbd>mouse wheel down</kbd>: 아래로 스크롤 (fn+up)
|
||||
<kbd>mouse wheel up</kbd>: 위로 스크롤 (fn+down)
|
||||
<kbd>mouse wheel ▼</kbd>: 아래로 스크롤 (fn+up)
|
||||
<kbd>mouse wheel ▲</kbd>: 위로 스크롤 (fn+down)
|
||||
</pre>
|
||||
|
||||
## 메인 패널 (Patch Building)
|
||||
|
||||
<pre>
|
||||
<kbd><left></kbd>: 이전 hunk를 선택
|
||||
<kbd><right></kbd>: 다음 hunk를 선택
|
||||
<kbd>◀</kbd>: 이전 hunk를 선택
|
||||
<kbd>▶</kbd>: 다음 hunk를 선택
|
||||
<kbd>v</kbd>: 드래그 선택 전환
|
||||
<kbd>V</kbd>: 드래그 선택 전환
|
||||
<kbd>a</kbd>: toggle select hunk
|
||||
<kbd><c-o></kbd>: 선택한 텍스트를 클립보드에 복사
|
||||
<kbd>ctrl+o</kbd>: 선택한 텍스트를 클립보드에 복사
|
||||
<kbd>o</kbd>: 파일 닫기
|
||||
<kbd>e</kbd>: 파일 편집
|
||||
<kbd><space></kbd>: line(s)을 패치에 추가/삭제
|
||||
<kbd><esc></kbd>: exit custom patch builder
|
||||
<kbd>space</kbd>: line(s)을 패치에 추가/삭제
|
||||
<kbd>esc</kbd>: exit custom patch builder
|
||||
</pre>
|
||||
|
||||
## 메인 패널 (Staging)
|
||||
|
||||
<pre>
|
||||
<kbd><left></kbd>: 이전 hunk를 선택
|
||||
<kbd><right></kbd>: 다음 hunk를 선택
|
||||
<kbd>◀</kbd>: 이전 hunk를 선택
|
||||
<kbd>▶</kbd>: 다음 hunk를 선택
|
||||
<kbd>v</kbd>: 드래그 선택 전환
|
||||
<kbd>V</kbd>: 드래그 선택 전환
|
||||
<kbd>a</kbd>: toggle select hunk
|
||||
<kbd><c-o></kbd>: 선택한 텍스트를 클립보드에 복사
|
||||
<kbd>ctrl+o</kbd>: 선택한 텍스트를 클립보드에 복사
|
||||
<kbd>o</kbd>: 파일 닫기
|
||||
<kbd>e</kbd>: 파일 편집
|
||||
<kbd><esc></kbd>: 파일 목록으로 돌아가기
|
||||
<kbd><tab></kbd>: 패널 전환
|
||||
<kbd><space></kbd>: 선택한 행을 staged / unstaged
|
||||
<kbd>esc</kbd>: 파일 목록으로 돌아가기
|
||||
<kbd>tab</kbd>: 패널 전환
|
||||
<kbd>space</kbd>: 선택한 행을 staged / unstaged
|
||||
<kbd>d</kbd>: 변경을 삭제 (git reset)
|
||||
<kbd>E</kbd>: edit hunk
|
||||
<kbd>c</kbd>: 커밋 변경내용
|
||||
@@ -154,13 +145,13 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
## 브랜치
|
||||
|
||||
<pre>
|
||||
<kbd><c-o></kbd>: 브랜치명을 클립보드에 복사
|
||||
<kbd>ctrl+o</kbd>: 브랜치명을 클립보드에 복사
|
||||
<kbd>i</kbd>: git-flow 옵션 보기
|
||||
<kbd><space></kbd>: 체크아웃
|
||||
<kbd>space</kbd>: 체크아웃
|
||||
<kbd>n</kbd>: 새 브랜치 생성
|
||||
<kbd>o</kbd>: 풀 리퀘스트 생성
|
||||
<kbd>O</kbd>: 풀 리퀘스트 생성 옵션
|
||||
<kbd><c-y></kbd>: 풀 리퀘스트 URL을 클립보드에 복사
|
||||
<kbd>ctrl+y</kbd>: 풀 리퀘스트 URL을 클립보드에 복사
|
||||
<kbd>c</kbd>: 이름으로 체크아웃
|
||||
<kbd>F</kbd>: 강제 체크아웃
|
||||
<kbd>d</kbd>: 브랜치 삭제
|
||||
@@ -171,24 +162,24 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>g</kbd>: view reset options
|
||||
<kbd>R</kbd>: 브랜치 이름 변경
|
||||
<kbd>u</kbd>: set/unset upstream
|
||||
<kbd><enter></kbd>: 커밋 보기
|
||||
<kbd>enter</kbd>: 커밋 보기
|
||||
</pre>
|
||||
|
||||
## 상태
|
||||
|
||||
<pre>
|
||||
<kbd>o</kbd>: 설정 파일 열기
|
||||
<kbd>e</kbd>: 설정 파일 수정
|
||||
<kbd>o</kbd>: 설정 파일 열기
|
||||
<kbd>u</kbd>: 업데이트 확인
|
||||
<kbd><enter></kbd>: 최근에 사용한 저장소로 전환
|
||||
<kbd>enter</kbd>: 최근에 사용한 저장소로 전환
|
||||
<kbd>a</kbd>: 모든 브랜치 로그 표시
|
||||
</pre>
|
||||
|
||||
## 서브모듈
|
||||
|
||||
<pre>
|
||||
<kbd><c-o></kbd>: 서브모듈 이름을 클립보드에 복사
|
||||
<kbd><enter></kbd>: 서브모듈 열기
|
||||
<kbd>ctrl+o</kbd>: 서브모듈 이름을 클립보드에 복사
|
||||
<kbd>enter</kbd>: 서브모듈 열기
|
||||
<kbd>d</kbd>: 서브모듈 삭제
|
||||
<kbd>u</kbd>: 서브모듈 업데이트
|
||||
<kbd>n</kbd>: 새로운 서브모듈 추가
|
||||
@@ -209,23 +200,23 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
## 원격 브랜치
|
||||
|
||||
<pre>
|
||||
<kbd><c-o></kbd>: 브랜치명을 클립보드에 복사
|
||||
<kbd><space></kbd>: 체크아웃
|
||||
<kbd>ctrl+o</kbd>: 브랜치명을 클립보드에 복사
|
||||
<kbd>space</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>esc</kbd>: 원격목록으로 돌아가기
|
||||
<kbd>g</kbd>: view reset options
|
||||
<kbd><enter></kbd>: 커밋 보기
|
||||
<kbd>enter</kbd>: 커밋 보기
|
||||
</pre>
|
||||
|
||||
## 커밋
|
||||
|
||||
<pre>
|
||||
<kbd><c-o></kbd>: 커밋 SHA를 클립보드에 복사
|
||||
<kbd><c-r></kbd>: reset cherry-picked (copied) commits selection
|
||||
<kbd>ctrl+o</kbd>: 커밋 SHA를 클립보드에 복사
|
||||
<kbd>ctrl+r</kbd>: reset cherry-picked (copied) commits selection
|
||||
<kbd>b</kbd>: bisect 옵션 보기
|
||||
<kbd>s</kbd>: squash down
|
||||
<kbd>f</kbd>: fixup commit
|
||||
@@ -236,63 +227,56 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<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><c-j></kbd>: 커밋을 1개 아래로 이동
|
||||
<kbd><c-k></kbd>: 커밋을 1개 위로 이동
|
||||
<kbd>ctrl+j</kbd>: 커밋을 1개 아래로 이동
|
||||
<kbd>ctrl+k</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><c-l></kbd>: 로그 메뉴 열기
|
||||
<kbd><space></kbd>: 커밋을 체크아웃
|
||||
<kbd>ctrl+l</kbd>: 로그 메뉴 열기
|
||||
<kbd>space</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>enter</kbd>: view selected item's files
|
||||
</pre>
|
||||
|
||||
## 커밋 파일
|
||||
|
||||
<pre>
|
||||
<kbd><c-o></kbd>: 커밋한 파일명을 클립보드에 복사
|
||||
<kbd>ctrl+o</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>space</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>enter</kbd>: enter file to add selected lines to the patch (or toggle directory collapsed)
|
||||
<kbd>`</kbd>: 파일 트리뷰로 전환
|
||||
</pre>
|
||||
|
||||
## 커밋메시지
|
||||
|
||||
<pre>
|
||||
<kbd><enter></kbd>: 확인
|
||||
<kbd><esc></kbd>: 닫기
|
||||
</pre>
|
||||
|
||||
## 태그
|
||||
|
||||
<pre>
|
||||
<kbd><space></kbd>: 체크아웃
|
||||
<kbd>space</kbd>: 체크아웃
|
||||
<kbd>d</kbd>: 태그 삭제
|
||||
<kbd>P</kbd>: 태그를 push
|
||||
<kbd>n</kbd>: 태그를 생성
|
||||
<kbd>g</kbd>: view reset options
|
||||
<kbd><enter></kbd>: 커밋 보기
|
||||
<kbd>enter</kbd>: 커밋 보기
|
||||
</pre>
|
||||
|
||||
## 파일
|
||||
|
||||
<pre>
|
||||
<kbd><c-o></kbd>: 파일명을 클립보드에 복사
|
||||
<kbd>ctrl+o</kbd>: 파일명을 클립보드에 복사
|
||||
<kbd>d</kbd>: view 'discard changes' options
|
||||
<kbd><space></kbd>: Staged 전환
|
||||
<kbd><c-b></kbd>: 파일을 필터하기 (Staged/unstaged)
|
||||
<kbd>space</kbd>: Staged 전환
|
||||
<kbd>ctrl+b</kbd>: 파일을 필터하기 (Staged/unstaged)
|
||||
<kbd>c</kbd>: 커밋 변경내용
|
||||
<kbd>w</kbd>: commit changes without pre-commit hook
|
||||
<kbd>A</kbd>: 마지맛 커밋 수정
|
||||
@@ -304,17 +288,10 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<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>enter</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><enter></kbd>: 확인
|
||||
<kbd><esc></kbd>: 닫기/취소
|
||||
</pre>
|
||||
|
||||
@@ -2,30 +2,28 @@ _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><c-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>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>@</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><c-p></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><c-s></kbd>: bekijk scoping opties
|
||||
<kbd>W</kbd>: open diff menu
|
||||
<kbd><c-e></kbd>: open diff menu
|
||||
<kbd><c-w></kbd>: Toggle whether or not whitespace changes are shown in the diff view
|
||||
<kbd>z</kbd>: ongedaan maken (via reflog) (experimenteel)
|
||||
<kbd><c-z></kbd>: redo (via reflog) (experimenteel)
|
||||
<kbd>ctrl+z</kbd>: redo (via reflog) (experimenteel)
|
||||
<kbd>P</kbd>: push
|
||||
<kbd>p</kbd>: pull
|
||||
</pre>
|
||||
@@ -35,9 +33,9 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<pre>
|
||||
<kbd>,</kbd>: vorige pagina
|
||||
<kbd>.</kbd>: volgende pagina
|
||||
<kbd><</kbd>: scroll naar boven
|
||||
<kbd><</kbd>: scroll naar boven
|
||||
<kbd>/</kbd>: start met zoeken
|
||||
<kbd>></kbd>: scroll naar beneden
|
||||
<kbd>></kbd>: scroll naar beneden
|
||||
<kbd>H</kbd>: scroll left
|
||||
<kbd>L</kbd>: scroll right
|
||||
<kbd>]</kbd>: volgende tabblad
|
||||
@@ -47,10 +45,10 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
## Bestanden
|
||||
|
||||
<pre>
|
||||
<kbd><c-o></kbd>: kopieer de bestandsnaam naar het klembord
|
||||
<kbd>ctrl+o</kbd>: kopieer de bestandsnaam naar het klembord
|
||||
<kbd>d</kbd>: bekijk 'veranderingen ongedaan maken' opties
|
||||
<kbd><space></kbd>: toggle staged
|
||||
<kbd><c-b></kbd>: Filter files (staged/unstaged)
|
||||
<kbd>space</kbd>: toggle staged
|
||||
<kbd>ctrl+b</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
|
||||
@@ -62,7 +60,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<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>enter</kbd>: stage individuele hunks/lijnen
|
||||
<kbd>g</kbd>: bekijk upstream reset opties
|
||||
<kbd>D</kbd>: bekijk reset opties
|
||||
<kbd>`</kbd>: toggle bestandsboom weergave
|
||||
@@ -70,23 +68,16 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>f</kbd>: fetch
|
||||
</pre>
|
||||
|
||||
## Bevestigingspaneel
|
||||
|
||||
<pre>
|
||||
<kbd><enter></kbd>: bevestig
|
||||
<kbd><esc></kbd>: sluiten
|
||||
</pre>
|
||||
|
||||
## Branches
|
||||
|
||||
<pre>
|
||||
<kbd><c-o></kbd>: kopieer branch name naar klembord
|
||||
<kbd>ctrl+o</kbd>: kopieer branch name naar klembord
|
||||
<kbd>i</kbd>: laat git-flow opties zien
|
||||
<kbd><space></kbd>: uitchecken
|
||||
<kbd>space</kbd>: uitchecken
|
||||
<kbd>n</kbd>: nieuwe branch
|
||||
<kbd>o</kbd>: maak een pull-request
|
||||
<kbd>O</kbd>: bekijk opties voor pull-aanvraag
|
||||
<kbd><c-y></kbd>: kopieer de URL van het pull-verzoek naar het klembord
|
||||
<kbd>ctrl+y</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
|
||||
@@ -97,35 +88,28 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>g</kbd>: bekijk reset opties
|
||||
<kbd>R</kbd>: hernoem branch
|
||||
<kbd>u</kbd>: set/unset upstream
|
||||
<kbd><enter></kbd>: bekijk commits
|
||||
</pre>
|
||||
|
||||
## Commit Bericht
|
||||
|
||||
<pre>
|
||||
<kbd><enter></kbd>: bevestig
|
||||
<kbd><esc></kbd>: sluiten
|
||||
<kbd>enter</kbd>: bekijk commits
|
||||
</pre>
|
||||
|
||||
## Commit bestanden
|
||||
|
||||
<pre>
|
||||
<kbd><c-o></kbd>: kopieer de vastgelegde bestandsnaam naar het klembord
|
||||
<kbd>ctrl+o</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>space</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>enter</kbd>: enter bestand om geselecteerde regels toe te voegen aan de patch
|
||||
<kbd>`</kbd>: toggle bestandsboom weergave
|
||||
</pre>
|
||||
|
||||
## Commits
|
||||
|
||||
<pre>
|
||||
<kbd><c-o></kbd>: kopieer commit SHA naar klembord
|
||||
<kbd><c-r></kbd>: reset cherry-picked (gekopieerde) commits selectie
|
||||
<kbd>ctrl+o</kbd>: kopieer commit SHA naar klembord
|
||||
<kbd>ctrl+r</kbd>: reset cherry-picked (gekopieerde) commits selectie
|
||||
<kbd>b</kbd>: view bisect options
|
||||
<kbd>s</kbd>: squash beneden
|
||||
<kbd>f</kbd>: Fixup commit
|
||||
@@ -136,29 +120,22 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<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><c-j></kbd>: verplaats commit 1 naar beneden
|
||||
<kbd><c-k></kbd>: verplaats commit 1 naar boven
|
||||
<kbd>ctrl+j</kbd>: verplaats commit 1 naar beneden
|
||||
<kbd>ctrl+k</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><c-l></kbd>: open log menu
|
||||
<kbd><space></kbd>: checkout commit
|
||||
<kbd>ctrl+l</kbd>: open log menu
|
||||
<kbd>space</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
|
||||
</pre>
|
||||
|
||||
## Menu
|
||||
|
||||
<pre>
|
||||
<kbd><enter></kbd>: uitvoeren
|
||||
<kbd><esc></kbd>: sluiten
|
||||
<kbd>enter</kbd>: bekijk gecommite bestanden
|
||||
</pre>
|
||||
|
||||
## Mergen
|
||||
@@ -166,67 +143,67 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<pre>
|
||||
<kbd>e</kbd>: verander bestand
|
||||
<kbd>o</kbd>: open bestand
|
||||
<kbd><left></kbd>: selecteer voorgaand conflict
|
||||
<kbd><right></kbd>: selecteer volgende conflict
|
||||
<kbd><up></kbd>: selecteer bovenste hunk
|
||||
<kbd><down></kbd>: selecteer onderste hunk
|
||||
<kbd>◀</kbd>: selecteer voorgaand conflict
|
||||
<kbd>▶</kbd>: selecteer volgende conflict
|
||||
<kbd>▲</kbd>: selecteer bovenste hunk
|
||||
<kbd>▼</kbd>: selecteer onderste hunk
|
||||
<kbd>z</kbd>: ongedaan maken
|
||||
<kbd>M</kbd>: open external merge tool (git mergetool)
|
||||
<kbd><space></kbd>: kies hunk
|
||||
<kbd>space</kbd>: kies hunk
|
||||
<kbd>b</kbd>: kies bijde hunks
|
||||
<kbd><esc></kbd>: ga terug naar het bestanden paneel
|
||||
<kbd>esc</kbd>: ga terug naar het bestanden paneel
|
||||
</pre>
|
||||
|
||||
## Normaal
|
||||
|
||||
<pre>
|
||||
<kbd>mouse wheel down</kbd>: scroll omlaag (fn+up)
|
||||
<kbd>mouse wheel up</kbd>: scroll omhoog (fn+down)
|
||||
<kbd>mouse wheel ▼</kbd>: scroll omlaag (fn+up)
|
||||
<kbd>mouse wheel ▲</kbd>: scroll omhoog (fn+down)
|
||||
</pre>
|
||||
|
||||
## Patch Bouwen
|
||||
|
||||
<pre>
|
||||
<kbd><left></kbd>: selecteer de vorige hunk
|
||||
<kbd><right></kbd>: selecteer de volgende hunk
|
||||
<kbd>◀</kbd>: selecteer de vorige hunk
|
||||
<kbd>▶</kbd>: selecteer de volgende hunk
|
||||
<kbd>v</kbd>: toggle drag selecteer
|
||||
<kbd>V</kbd>: toggle drag selecteer
|
||||
<kbd>a</kbd>: toggle selecteer hunk
|
||||
<kbd><c-o></kbd>: copy the selected text to the clipboard
|
||||
<kbd>ctrl+o</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>space</kbd>: voeg toe/verwijder lijn(en) in patch
|
||||
<kbd>esc</kbd>: sluit lijn-bij-lijn modus
|
||||
</pre>
|
||||
|
||||
## Reflog
|
||||
|
||||
<pre>
|
||||
<kbd><c-o></kbd>: kopieer commit SHA naar klembord
|
||||
<kbd><space></kbd>: checkout commit
|
||||
<kbd>ctrl+o</kbd>: kopieer commit SHA naar klembord
|
||||
<kbd>space</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><c-r></kbd>: reset cherry-picked (gekopieerde) commits selectie
|
||||
<kbd><enter></kbd>: bekijk commits
|
||||
<kbd>ctrl+r</kbd>: reset cherry-picked (gekopieerde) commits selectie
|
||||
<kbd>enter</kbd>: bekijk commits
|
||||
</pre>
|
||||
|
||||
## Remote Branches
|
||||
|
||||
<pre>
|
||||
<kbd><c-o></kbd>: kopieer branch name naar klembord
|
||||
<kbd><space></kbd>: uitchecken
|
||||
<kbd>ctrl+o</kbd>: kopieer branch name naar klembord
|
||||
<kbd>space</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>esc</kbd>: ga terug naar remotes lijst
|
||||
<kbd>g</kbd>: bekijk reset opties
|
||||
<kbd><enter></kbd>: bekijk commits
|
||||
<kbd>enter</kbd>: bekijk commits
|
||||
</pre>
|
||||
|
||||
## Remotes
|
||||
@@ -241,17 +218,17 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
## Staging
|
||||
|
||||
<pre>
|
||||
<kbd><left></kbd>: selecteer de vorige hunk
|
||||
<kbd><right></kbd>: selecteer de volgende hunk
|
||||
<kbd>◀</kbd>: selecteer de vorige hunk
|
||||
<kbd>▶</kbd>: selecteer de volgende hunk
|
||||
<kbd>v</kbd>: toggle drag selecteer
|
||||
<kbd>V</kbd>: toggle drag selecteer
|
||||
<kbd>a</kbd>: toggle selecteer hunk
|
||||
<kbd><c-o></kbd>: copy the selected text to the clipboard
|
||||
<kbd>ctrl+o</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>esc</kbd>: ga terug naar het bestanden paneel
|
||||
<kbd>tab</kbd>: ga naar een ander paneel
|
||||
<kbd>space</kbd>: toggle lijnen staged / unstaged
|
||||
<kbd>d</kbd>: verwijdert change (git reset)
|
||||
<kbd>E</kbd>: edit hunk
|
||||
<kbd>c</kbd>: commit veranderingen
|
||||
@@ -262,44 +239,44 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
## Stash
|
||||
|
||||
<pre>
|
||||
<kbd><space></kbd>: toepassen
|
||||
<kbd>space</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>enter</kbd>: bekijk gecommite bestanden
|
||||
</pre>
|
||||
|
||||
## Status
|
||||
|
||||
<pre>
|
||||
<kbd>o</kbd>: open config bestand
|
||||
<kbd>e</kbd>: verander config bestand
|
||||
<kbd>o</kbd>: open config bestand
|
||||
<kbd>u</kbd>: check voor updates
|
||||
<kbd><enter></kbd>: wissel naar een recente repo
|
||||
<kbd>enter</kbd>: wissel naar een recente repo
|
||||
<kbd>a</kbd>: alle logs van de branch laten zien
|
||||
</pre>
|
||||
|
||||
## Sub-commits
|
||||
|
||||
<pre>
|
||||
<kbd><c-o></kbd>: kopieer commit SHA naar klembord
|
||||
<kbd><space></kbd>: checkout commit
|
||||
<kbd>ctrl+o</kbd>: kopieer commit SHA naar klembord
|
||||
<kbd>space</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><c-r></kbd>: reset cherry-picked (gekopieerde) commits selectie
|
||||
<kbd><enter></kbd>: bekijk gecommite bestanden
|
||||
<kbd>ctrl+r</kbd>: reset cherry-picked (gekopieerde) commits selectie
|
||||
<kbd>enter</kbd>: bekijk gecommite bestanden
|
||||
</pre>
|
||||
|
||||
## Submodules
|
||||
|
||||
<pre>
|
||||
<kbd><c-o></kbd>: kopieer submodule naam naar klembord
|
||||
<kbd><enter></kbd>: enter submodule
|
||||
<kbd>ctrl+o</kbd>: kopieer submodule naam naar klembord
|
||||
<kbd>enter</kbd>: enter submodule
|
||||
<kbd>d</kbd>: remove submodule
|
||||
<kbd>u</kbd>: update submodule
|
||||
<kbd>n</kbd>: voeg nieuwe submodule toe
|
||||
@@ -311,10 +288,10 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
## Tags
|
||||
|
||||
<pre>
|
||||
<kbd><space></kbd>: uitchecken
|
||||
<kbd>space</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>enter</kbd>: bekijk commits
|
||||
</pre>
|
||||
|
||||
@@ -2,30 +2,28 @@ _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><c-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>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>@</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><c-p></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><c-s></kbd>: view filter-by-path options
|
||||
<kbd>W</kbd>: open diff menu
|
||||
<kbd><c-e></kbd>: open diff menu
|
||||
<kbd><c-w></kbd>: Toggle whether or not whitespace changes are shown in the diff view
|
||||
<kbd>z</kbd>: undo (via reflog) (experimental)
|
||||
<kbd><c-z></kbd>: redo (via reflog) (experimental)
|
||||
<kbd>ctrl+z</kbd>: redo (via reflog) (experimental)
|
||||
<kbd>P</kbd>: push
|
||||
<kbd>p</kbd>: pull
|
||||
</pre>
|
||||
@@ -35,27 +33,20 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<pre>
|
||||
<kbd>,</kbd>: previous page
|
||||
<kbd>.</kbd>: next page
|
||||
<kbd><</kbd>: scroll to top
|
||||
<kbd><</kbd>: scroll to top
|
||||
<kbd>/</kbd>: start search
|
||||
<kbd>></kbd>: scroll to bottom
|
||||
<kbd>></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><enter></kbd>: potwierdź
|
||||
<kbd><esc></kbd>: zamknij
|
||||
</pre>
|
||||
|
||||
## Commity
|
||||
|
||||
<pre>
|
||||
<kbd><c-o></kbd>: copy commit SHA to clipboard
|
||||
<kbd><c-r></kbd>: reset cherry-picked (copied) commits selection
|
||||
<kbd>ctrl+o</kbd>: copy commit SHA to clipboard
|
||||
<kbd>ctrl+r</kbd>: reset cherry-picked (copied) commits selection
|
||||
<kbd>b</kbd>: view bisect options
|
||||
<kbd>s</kbd>: ściśnij
|
||||
<kbd>f</kbd>: napraw commit
|
||||
@@ -66,41 +57,34 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<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><c-j></kbd>: przenieś commit 1 w dół
|
||||
<kbd><c-k></kbd>: przenieś commit 1 w górę
|
||||
<kbd>ctrl+j</kbd>: przenieś commit 1 w dół
|
||||
<kbd>ctrl+k</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><c-l></kbd>: open log menu
|
||||
<kbd><space></kbd>: checkout commit
|
||||
<kbd>ctrl+l</kbd>: open log menu
|
||||
<kbd>space</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
|
||||
</pre>
|
||||
|
||||
## Confirmation Panel
|
||||
|
||||
<pre>
|
||||
<kbd><enter></kbd>: potwierdź
|
||||
<kbd><esc></kbd>: zamknij
|
||||
<kbd>enter</kbd>: przeglądaj pliki commita
|
||||
</pre>
|
||||
|
||||
## Local Branches
|
||||
|
||||
<pre>
|
||||
<kbd><c-o></kbd>: copy branch name to clipboard
|
||||
<kbd>ctrl+o</kbd>: copy branch name to clipboard
|
||||
<kbd>i</kbd>: show git-flow options
|
||||
<kbd><space></kbd>: przełącz
|
||||
<kbd>space</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><c-y></kbd>: skopiuj adres URL żądania pobrania do schowka
|
||||
<kbd>ctrl+y</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łąź
|
||||
@@ -111,38 +95,31 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>g</kbd>: wyświetl opcje resetu
|
||||
<kbd>R</kbd>: rename branch
|
||||
<kbd>u</kbd>: set/unset upstream
|
||||
<kbd><enter></kbd>: view commits
|
||||
<kbd>enter</kbd>: view commits
|
||||
</pre>
|
||||
|
||||
## Main Panel (Patch Building)
|
||||
|
||||
<pre>
|
||||
<kbd><left></kbd>: poprzedni kawałek
|
||||
<kbd><right></kbd>: następny kawałek
|
||||
<kbd>◀</kbd>: poprzedni kawałek
|
||||
<kbd>▶</kbd>: następny kawałek
|
||||
<kbd>v</kbd>: toggle drag select
|
||||
<kbd>V</kbd>: toggle drag select
|
||||
<kbd>a</kbd>: toggle select hunk
|
||||
<kbd><c-o></kbd>: copy the selected text to the clipboard
|
||||
<kbd>ctrl+o</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"
|
||||
</pre>
|
||||
|
||||
## Menu
|
||||
|
||||
<pre>
|
||||
<kbd><enter></kbd>: wykonaj
|
||||
<kbd><esc></kbd>: zamknij
|
||||
<kbd>space</kbd>: add/remove line(s) to patch
|
||||
<kbd>esc</kbd>: wyście z trybu "linia po linii"
|
||||
</pre>
|
||||
|
||||
## Pliki
|
||||
|
||||
<pre>
|
||||
<kbd><c-o></kbd>: copy the file name to the clipboard
|
||||
<kbd>ctrl+o</kbd>: copy the file name to the clipboard
|
||||
<kbd>d</kbd>: pokaż opcje porzucania zmian
|
||||
<kbd><space></kbd>: przełącz stan poczekalni
|
||||
<kbd><c-b></kbd>: Filter files (staged/unstaged)
|
||||
<kbd>space</kbd>: przełącz stan poczekalni
|
||||
<kbd>ctrl+b</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
|
||||
@@ -154,7 +131,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<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>enter</kbd>: zatwierdź pojedyncze linie
|
||||
<kbd>g</kbd>: view upstream reset options
|
||||
<kbd>D</kbd>: wyświetl opcje resetu
|
||||
<kbd>`</kbd>: toggle file tree view
|
||||
@@ -165,31 +142,31 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
## Pliki commita
|
||||
|
||||
<pre>
|
||||
<kbd><c-o></kbd>: copy the committed file name to the clipboard
|
||||
<kbd>ctrl+o</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>space</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>enter</kbd>: enter file to add selected lines to the patch (or toggle directory collapsed)
|
||||
<kbd>`</kbd>: toggle file tree view
|
||||
</pre>
|
||||
|
||||
## Poczekalnia
|
||||
|
||||
<pre>
|
||||
<kbd><left></kbd>: poprzedni kawałek
|
||||
<kbd><right></kbd>: następny kawałek
|
||||
<kbd>◀</kbd>: poprzedni kawałek
|
||||
<kbd>▶</kbd>: następny kawałek
|
||||
<kbd>v</kbd>: toggle drag select
|
||||
<kbd>V</kbd>: toggle drag select
|
||||
<kbd>a</kbd>: toggle select hunk
|
||||
<kbd><c-o></kbd>: copy the selected text to the clipboard
|
||||
<kbd>ctrl+o</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>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>d</kbd>: delete change (git reset)
|
||||
<kbd>E</kbd>: edit hunk
|
||||
<kbd>c</kbd>: Zatwierdź zmiany
|
||||
@@ -200,31 +177,31 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
## Reflog
|
||||
|
||||
<pre>
|
||||
<kbd><c-o></kbd>: copy commit SHA to clipboard
|
||||
<kbd><space></kbd>: checkout commit
|
||||
<kbd>ctrl+o</kbd>: copy commit SHA to clipboard
|
||||
<kbd>space</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><c-r></kbd>: reset cherry-picked (copied) commits selection
|
||||
<kbd><enter></kbd>: view commits
|
||||
<kbd>ctrl+r</kbd>: reset cherry-picked (copied) commits selection
|
||||
<kbd>enter</kbd>: view commits
|
||||
</pre>
|
||||
|
||||
## Remote Branches
|
||||
|
||||
<pre>
|
||||
<kbd><c-o></kbd>: copy branch name to clipboard
|
||||
<kbd><space></kbd>: przełącz
|
||||
<kbd>ctrl+o</kbd>: copy branch name to clipboard
|
||||
<kbd>space</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>esc</kbd>: wróć do listy repozytoriów zdalnych
|
||||
<kbd>g</kbd>: wyświetl opcje resetu
|
||||
<kbd><enter></kbd>: view commits
|
||||
<kbd>enter</kbd>: view commits
|
||||
</pre>
|
||||
|
||||
## Remotes
|
||||
@@ -241,58 +218,58 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<pre>
|
||||
<kbd>e</kbd>: edytuj plik
|
||||
<kbd>o</kbd>: otwórz plik
|
||||
<kbd><left></kbd>: poprzedni konflikt
|
||||
<kbd><right></kbd>: następny konflikt
|
||||
<kbd><up></kbd>: wybierz poprzedni kawałek
|
||||
<kbd><down></kbd>: wybierz następny kawałek
|
||||
<kbd>◀</kbd>: poprzedni konflikt
|
||||
<kbd>▶</kbd>: następny konflikt
|
||||
<kbd>▲</kbd>: wybierz poprzedni kawałek
|
||||
<kbd>▼</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>space</kbd>: wybierz kawałek
|
||||
<kbd>b</kbd>: wybierz wszystkie kawałki
|
||||
<kbd><esc></kbd>: wróć do panelu plików
|
||||
<kbd>esc</kbd>: wróć do panelu plików
|
||||
</pre>
|
||||
|
||||
## Schowek
|
||||
|
||||
<pre>
|
||||
<kbd><space></kbd>: zastosuj
|
||||
<kbd>space</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>enter</kbd>: przeglądaj pliki commita
|
||||
</pre>
|
||||
|
||||
## Status
|
||||
|
||||
<pre>
|
||||
<kbd>o</kbd>: otwórz konfigurację
|
||||
<kbd>e</kbd>: edytuj konfigurację
|
||||
<kbd>o</kbd>: otwórz konfigurację
|
||||
<kbd>u</kbd>: sprawdź aktualizacje
|
||||
<kbd><enter></kbd>: switch to a recent repo
|
||||
<kbd>enter</kbd>: switch to a recent repo
|
||||
<kbd>a</kbd>: pokaż wszystkie logi gałęzi
|
||||
</pre>
|
||||
|
||||
## Sub-commits
|
||||
|
||||
<pre>
|
||||
<kbd><c-o></kbd>: copy commit SHA to clipboard
|
||||
<kbd><space></kbd>: checkout commit
|
||||
<kbd>ctrl+o</kbd>: copy commit SHA to clipboard
|
||||
<kbd>space</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><c-r></kbd>: reset cherry-picked (copied) commits selection
|
||||
<kbd><enter></kbd>: przeglądaj pliki commita
|
||||
<kbd>ctrl+r</kbd>: reset cherry-picked (copied) commits selection
|
||||
<kbd>enter</kbd>: przeglądaj pliki commita
|
||||
</pre>
|
||||
|
||||
## Submodules
|
||||
|
||||
<pre>
|
||||
<kbd><c-o></kbd>: copy submodule name to clipboard
|
||||
<kbd><enter></kbd>: enter submodule
|
||||
<kbd>ctrl+o</kbd>: copy submodule name to clipboard
|
||||
<kbd>enter</kbd>: enter submodule
|
||||
<kbd>d</kbd>: remove submodule
|
||||
<kbd>u</kbd>: update submodule
|
||||
<kbd>n</kbd>: add new submodule
|
||||
@@ -304,17 +281,17 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
## Tags
|
||||
|
||||
<pre>
|
||||
<kbd><space></kbd>: przełącz
|
||||
<kbd>space</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>enter</kbd>: view commits
|
||||
</pre>
|
||||
|
||||
## Zwykłe
|
||||
|
||||
<pre>
|
||||
<kbd>mouse wheel down</kbd>: przewiń w dół (fn+up)
|
||||
<kbd>mouse wheel up</kbd>: przewiń w górę (fn+down)
|
||||
<kbd>mouse wheel ▼</kbd>: przewiń w dół (fn+up)
|
||||
<kbd>mouse wheel ▲</kbd>: przewiń w górę (fn+down)
|
||||
</pre>
|
||||
|
||||
@@ -2,30 +2,28 @@ _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><c-r></kbd>: 切换到最近的仓库
|
||||
<kbd><pgup></kbd>: 向上滚动主面板 (fn+up/shift+k)
|
||||
<kbd><pgdown></kbd>: 向下滚动主面板 (fn+down/shift+j)
|
||||
<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>@</kbd>: 打开命令日志菜单
|
||||
<kbd>ctrl+w</kbd>: 切换是否在差异视图中显示空白字符差异
|
||||
<kbd>}</kbd>: 扩大差异视图中显示的上下文范围
|
||||
<kbd>{</kbd>: 缩小差异视图中显示的上下文范围
|
||||
<kbd>:</kbd>: 执行自定义命令
|
||||
<kbd><c-p></kbd>: 查看自定义补丁选项
|
||||
<kbd>m</kbd>: 查看 合并/变基 选项
|
||||
<kbd>R</kbd>: 刷新
|
||||
<kbd>+</kbd>: 下一屏模式(正常/半屏/全屏)
|
||||
<kbd>_</kbd>: 上一屏模式
|
||||
<kbd>?</kbd>: 打开菜单
|
||||
<kbd><c-s></kbd>: 查看按路径过滤选项
|
||||
<kbd>W</kbd>: 打开 diff 菜单
|
||||
<kbd><c-e></kbd>: 打开 diff 菜单
|
||||
<kbd><c-w></kbd>: 切换是否在差异视图中显示空白字符差异
|
||||
<kbd>z</kbd>: (通过 reflog)撤销「实验功能」
|
||||
<kbd><c-z></kbd>: (通过 reflog)重做「实验功能」
|
||||
<kbd>ctrl+z</kbd>: (通过 reflog)重做「实验功能」
|
||||
<kbd>P</kbd>: 推送
|
||||
<kbd>p</kbd>: 拉取
|
||||
</pre>
|
||||
@@ -35,9 +33,9 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<pre>
|
||||
<kbd>,</kbd>: 上一页
|
||||
<kbd>.</kbd>: 下一页
|
||||
<kbd><</kbd>: 滚动到顶部
|
||||
<kbd><</kbd>: 滚动到顶部
|
||||
<kbd>/</kbd>: 开始搜索
|
||||
<kbd>></kbd>: 滚动到底部
|
||||
<kbd>></kbd>: 滚动到底部
|
||||
<kbd>H</kbd>: 向左滚动
|
||||
<kbd>L</kbd>: 向右滚动
|
||||
<kbd>]</kbd>: 下一个标签
|
||||
@@ -47,28 +45,28 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
## Reflog 页面
|
||||
|
||||
<pre>
|
||||
<kbd><c-o></kbd>: 将提交的 SHA 复制到剪贴板
|
||||
<kbd><space></kbd>: 检出提交
|
||||
<kbd>ctrl+o</kbd>: 将提交的 SHA 复制到剪贴板
|
||||
<kbd>space</kbd>: 检出提交
|
||||
<kbd>y</kbd>: copy commit attribute
|
||||
<kbd>o</kbd>: 在浏览器中打开提交
|
||||
<kbd>n</kbd>: 从提交创建新分支
|
||||
<kbd>g</kbd>: 查看重置选项
|
||||
<kbd>c</kbd>: 复制提交(拣选)
|
||||
<kbd>C</kbd>: 复制提交范围(拣选)
|
||||
<kbd><c-r></kbd>: 重置已拣选(复制)的提交
|
||||
<kbd><enter></kbd>: 查看提交
|
||||
<kbd>ctrl+r</kbd>: 重置已拣选(复制)的提交
|
||||
<kbd>enter</kbd>: 查看提交
|
||||
</pre>
|
||||
|
||||
## 分支页面
|
||||
|
||||
<pre>
|
||||
<kbd><c-o></kbd>: 将分支名称复制到剪贴板
|
||||
<kbd>ctrl+o</kbd>: 将分支名称复制到剪贴板
|
||||
<kbd>i</kbd>: 显示 git-flow 选项
|
||||
<kbd><space></kbd>: 检出
|
||||
<kbd>space</kbd>: 检出
|
||||
<kbd>n</kbd>: 新分支
|
||||
<kbd>o</kbd>: 创建抓取请求
|
||||
<kbd>O</kbd>: 创建抓取请求选项
|
||||
<kbd><c-y></kbd>: 将抓取请求 URL 复制到剪贴板
|
||||
<kbd>ctrl+y</kbd>: 将抓取请求 URL 复制到剪贴板
|
||||
<kbd>c</kbd>: 按名称检出
|
||||
<kbd>F</kbd>: 强制检出
|
||||
<kbd>d</kbd>: 删除分支
|
||||
@@ -79,29 +77,29 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>g</kbd>: 查看重置选项
|
||||
<kbd>R</kbd>: 重命名分支
|
||||
<kbd>u</kbd>: set/unset upstream
|
||||
<kbd><enter></kbd>: 查看提交
|
||||
<kbd>enter</kbd>: 查看提交
|
||||
</pre>
|
||||
|
||||
## 子提交
|
||||
|
||||
<pre>
|
||||
<kbd><c-o></kbd>: 将提交的 SHA 复制到剪贴板
|
||||
<kbd><space></kbd>: 检出提交
|
||||
<kbd>ctrl+o</kbd>: 将提交的 SHA 复制到剪贴板
|
||||
<kbd>space</kbd>: 检出提交
|
||||
<kbd>y</kbd>: copy commit attribute
|
||||
<kbd>o</kbd>: 在浏览器中打开提交
|
||||
<kbd>n</kbd>: 从提交创建新分支
|
||||
<kbd>g</kbd>: 查看重置选项
|
||||
<kbd>c</kbd>: 复制提交(拣选)
|
||||
<kbd>C</kbd>: 复制提交范围(拣选)
|
||||
<kbd><c-r></kbd>: 重置已拣选(复制)的提交
|
||||
<kbd><enter></kbd>: 查看提交的文件
|
||||
<kbd>ctrl+r</kbd>: 重置已拣选(复制)的提交
|
||||
<kbd>enter</kbd>: 查看提交的文件
|
||||
</pre>
|
||||
|
||||
## 子模块
|
||||
|
||||
<pre>
|
||||
<kbd><c-o></kbd>: 将子模块名称复制到剪贴板
|
||||
<kbd><enter></kbd>: 输入子模块
|
||||
<kbd>ctrl+o</kbd>: 将子模块名称复制到剪贴板
|
||||
<kbd>enter</kbd>: 输入子模块
|
||||
<kbd>d</kbd>: 删除子模块
|
||||
<kbd>u</kbd>: 更新子模块
|
||||
<kbd>n</kbd>: 添加新的子模块
|
||||
@@ -113,8 +111,8 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
## 提交
|
||||
|
||||
<pre>
|
||||
<kbd><c-o></kbd>: 将提交的 SHA 复制到剪贴板
|
||||
<kbd><c-r></kbd>: 重置已拣选(复制)的提交
|
||||
<kbd>ctrl+o</kbd>: 将提交的 SHA 复制到剪贴板
|
||||
<kbd>ctrl+r</kbd>: 重置已拣选(复制)的提交
|
||||
<kbd>b</kbd>: 查看二分查找选项
|
||||
<kbd>s</kbd>: 向下压缩
|
||||
<kbd>f</kbd>: 修正提交(fixup)
|
||||
@@ -125,52 +123,45 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>p</kbd>: 选择提交(变基过程中)
|
||||
<kbd>F</kbd>: 为此提交创建修正
|
||||
<kbd>S</kbd>: 压缩在所选提交之上的所有“fixup!”提交(自动压缩)
|
||||
<kbd><c-j></kbd>: 下移提交
|
||||
<kbd><c-k></kbd>: 上移提交
|
||||
<kbd>ctrl+j</kbd>: 下移提交
|
||||
<kbd>ctrl+k</kbd>: 上移提交
|
||||
<kbd>v</kbd>: 粘贴提交(拣选)
|
||||
<kbd>A</kbd>: 用已暂存的更改来修补提交
|
||||
<kbd>a</kbd>: reset commit author
|
||||
<kbd>t</kbd>: 还原提交
|
||||
<kbd>T</kbd>: 标签提交
|
||||
<kbd><c-l></kbd>: 打开日志菜单
|
||||
<kbd><space></kbd>: 检出提交
|
||||
<kbd>ctrl+l</kbd>: 打开日志菜单
|
||||
<kbd>space</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>enter</kbd>: 查看提交的文件
|
||||
</pre>
|
||||
|
||||
## 提交文件
|
||||
|
||||
<pre>
|
||||
<kbd><c-o></kbd>: 将提交的文件名复制到剪贴板
|
||||
<kbd>ctrl+o</kbd>: 将提交的文件名复制到剪贴板
|
||||
<kbd>c</kbd>: 检出文件
|
||||
<kbd>d</kbd>: 放弃对此文件的提交更改
|
||||
<kbd>o</kbd>: 打开文件
|
||||
<kbd>e</kbd>: 编辑文件
|
||||
<kbd><space></kbd>: 补丁中包含的切换文件
|
||||
<kbd>space</kbd>: 补丁中包含的切换文件
|
||||
<kbd>a</kbd>: toggle all files included in patch
|
||||
<kbd><enter></kbd>: 输入文件以将所选行添加到补丁中(或切换目录折叠)
|
||||
<kbd>enter</kbd>: 输入文件以将所选行添加到补丁中(或切换目录折叠)
|
||||
<kbd>`</kbd>: 切换文件树视图
|
||||
</pre>
|
||||
|
||||
## 提交讯息
|
||||
|
||||
<pre>
|
||||
<kbd><enter></kbd>: 确认
|
||||
<kbd><esc></kbd>: 关闭
|
||||
</pre>
|
||||
|
||||
## 文件
|
||||
|
||||
<pre>
|
||||
<kbd><c-o></kbd>: 将文件名复制到剪贴板
|
||||
<kbd>ctrl+o</kbd>: 将文件名复制到剪贴板
|
||||
<kbd>d</kbd>: 查看'放弃更改'选项
|
||||
<kbd><space></kbd>: 切换暂存状态
|
||||
<kbd><c-b></kbd>: Filter files (staged/unstaged)
|
||||
<kbd>space</kbd>: 切换暂存状态
|
||||
<kbd>ctrl+b</kbd>: Filter files (staged/unstaged)
|
||||
<kbd>c</kbd>: 提交更改
|
||||
<kbd>w</kbd>: 提交更改而无需预先提交钩子
|
||||
<kbd>A</kbd>: 修补最后一次提交
|
||||
@@ -182,7 +173,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>s</kbd>: 将所有更改加入贮藏
|
||||
<kbd>S</kbd>: 查看贮藏选项
|
||||
<kbd>a</kbd>: 切换所有文件的暂存状态
|
||||
<kbd><enter></kbd>: 暂存单个 块/行 用于文件, 或 折叠/展开 目录
|
||||
<kbd>enter</kbd>: 暂存单个 块/行 用于文件, 或 折叠/展开 目录
|
||||
<kbd>g</kbd>: 查看上游重置选项
|
||||
<kbd>D</kbd>: 查看重置选项
|
||||
<kbd>`</kbd>: 切换文件树视图
|
||||
@@ -193,27 +184,27 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
## 构建补丁中
|
||||
|
||||
<pre>
|
||||
<kbd><left></kbd>: 选择上一个区块
|
||||
<kbd><right></kbd>: 选择下一个区块
|
||||
<kbd>◀</kbd>: 选择上一个区块
|
||||
<kbd>▶</kbd>: 选择下一个区块
|
||||
<kbd>v</kbd>: 切换拖动选择
|
||||
<kbd>V</kbd>: 切换拖动选择
|
||||
<kbd>a</kbd>: 切换选择区块
|
||||
<kbd><c-o></kbd>: 将选中文本复制到剪贴板
|
||||
<kbd>ctrl+o</kbd>: 将选中文本复制到剪贴板
|
||||
<kbd>o</kbd>: 打开文件
|
||||
<kbd>e</kbd>: 编辑文件
|
||||
<kbd><space></kbd>: 添加/移除 行到补丁
|
||||
<kbd><esc></kbd>: 退出逐行模式
|
||||
<kbd>space</kbd>: 添加/移除 行到补丁
|
||||
<kbd>esc</kbd>: 退出逐行模式
|
||||
</pre>
|
||||
|
||||
## 标签页面
|
||||
|
||||
<pre>
|
||||
<kbd><space></kbd>: 检出
|
||||
<kbd>space</kbd>: 检出
|
||||
<kbd>d</kbd>: 删除标签
|
||||
<kbd>P</kbd>: 推送标签
|
||||
<kbd>n</kbd>: 创建标签
|
||||
<kbd>g</kbd>: 查看重置选项
|
||||
<kbd><enter></kbd>: 查看提交
|
||||
<kbd>enter</kbd>: 查看提交
|
||||
</pre>
|
||||
|
||||
## 正在合并
|
||||
@@ -221,31 +212,31 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<pre>
|
||||
<kbd>e</kbd>: 编辑文件
|
||||
<kbd>o</kbd>: 打开文件
|
||||
<kbd><left></kbd>: 选择上一个冲突
|
||||
<kbd><right></kbd>: 选择下一个冲突
|
||||
<kbd><up></kbd>: 选择顶部块
|
||||
<kbd><down></kbd>: 选择底部块
|
||||
<kbd>◀</kbd>: 选择上一个冲突
|
||||
<kbd>▶</kbd>: 选择下一个冲突
|
||||
<kbd>▲</kbd>: 选择顶部块
|
||||
<kbd>▼</kbd>: 选择底部块
|
||||
<kbd>z</kbd>: 撤销
|
||||
<kbd>M</kbd>: 打开外部合并工具 (git mergetool)
|
||||
<kbd><space></kbd>: 选中区块
|
||||
<kbd>space</kbd>: 选中区块
|
||||
<kbd>b</kbd>: 选中所有区块
|
||||
<kbd><esc></kbd>: 返回文件面板
|
||||
<kbd>esc</kbd>: 返回文件面板
|
||||
</pre>
|
||||
|
||||
## 正在暂存
|
||||
|
||||
<pre>
|
||||
<kbd><left></kbd>: 选择上一个区块
|
||||
<kbd><right></kbd>: 选择下一个区块
|
||||
<kbd>◀</kbd>: 选择上一个区块
|
||||
<kbd>▶</kbd>: 选择下一个区块
|
||||
<kbd>v</kbd>: 切换拖动选择
|
||||
<kbd>V</kbd>: 切换拖动选择
|
||||
<kbd>a</kbd>: 切换选择区块
|
||||
<kbd><c-o></kbd>: 将选中文本复制到剪贴板
|
||||
<kbd>ctrl+o</kbd>: 将选中文本复制到剪贴板
|
||||
<kbd>o</kbd>: 打开文件
|
||||
<kbd>e</kbd>: 编辑文件
|
||||
<kbd><esc></kbd>: 返回文件面板
|
||||
<kbd><tab></kbd>: 切换到其他面板
|
||||
<kbd><space></kbd>: 切换行暂存状态
|
||||
<kbd>esc</kbd>: 返回文件面板
|
||||
<kbd>tab</kbd>: 切换到其他面板
|
||||
<kbd>space</kbd>: 切换行暂存状态
|
||||
<kbd>d</kbd>: 取消变更 (git reset)
|
||||
<kbd>E</kbd>: edit hunk
|
||||
<kbd>c</kbd>: 提交更改
|
||||
@@ -256,58 +247,44 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
## 正常
|
||||
|
||||
<pre>
|
||||
<kbd>mouse wheel down</kbd>: 向下滚动 (fn+up)
|
||||
<kbd>mouse wheel up</kbd>: 向上滚动 (fn+down)
|
||||
<kbd>mouse wheel ▼</kbd>: 向下滚动 (fn+up)
|
||||
<kbd>mouse wheel ▲</kbd>: 向上滚动 (fn+down)
|
||||
</pre>
|
||||
|
||||
## 状态
|
||||
|
||||
<pre>
|
||||
<kbd>o</kbd>: 打开配置文件
|
||||
<kbd>e</kbd>: 编辑配置文件
|
||||
<kbd>o</kbd>: 打开配置文件
|
||||
<kbd>u</kbd>: 检查更新
|
||||
<kbd><enter></kbd>: 切换到最近的仓库
|
||||
<kbd>enter</kbd>: 切换到最近的仓库
|
||||
<kbd>a</kbd>: 显示所有分支的日志
|
||||
</pre>
|
||||
|
||||
## 确认面板
|
||||
|
||||
<pre>
|
||||
<kbd><enter></kbd>: 确认
|
||||
<kbd><esc></kbd>: 关闭
|
||||
</pre>
|
||||
|
||||
## 菜单
|
||||
|
||||
<pre>
|
||||
<kbd><enter></kbd>: 执行
|
||||
<kbd><esc></kbd>: 关闭
|
||||
</pre>
|
||||
|
||||
## 贮藏
|
||||
|
||||
<pre>
|
||||
<kbd><space></kbd>: 应用
|
||||
<kbd>space</kbd>: 应用
|
||||
<kbd>g</kbd>: 应用并删除
|
||||
<kbd>d</kbd>: 删除
|
||||
<kbd>n</kbd>: 新分支
|
||||
<kbd>r</kbd>: rename stash
|
||||
<kbd><enter></kbd>: 查看提交的文件
|
||||
<kbd>enter</kbd>: 查看提交的文件
|
||||
</pre>
|
||||
|
||||
## 远程分支
|
||||
|
||||
<pre>
|
||||
<kbd><c-o></kbd>: 将分支名称复制到剪贴板
|
||||
<kbd><space></kbd>: 检出
|
||||
<kbd>ctrl+o</kbd>: 将分支名称复制到剪贴板
|
||||
<kbd>space</kbd>: 检出
|
||||
<kbd>n</kbd>: 新分支
|
||||
<kbd>M</kbd>: 合并到当前检出的分支
|
||||
<kbd>r</kbd>: 将已检出的分支变基到该分支
|
||||
<kbd>d</kbd>: 删除分支
|
||||
<kbd>u</kbd>: 设置为检出分支的上游
|
||||
<kbd><esc></kbd>: 返回远程仓库列表
|
||||
<kbd>esc</kbd>: 返回远程仓库列表
|
||||
<kbd>g</kbd>: 查看重置选项
|
||||
<kbd><enter></kbd>: 查看提交
|
||||
<kbd>enter</kbd>: 查看提交
|
||||
</pre>
|
||||
|
||||
## 远程页面
|
||||
|
||||
@@ -4,12 +4,12 @@ go 1.18
|
||||
|
||||
require (
|
||||
github.com/OpenPeeDeeP/xdg v1.0.0
|
||||
github.com/atotto/clipboard v0.1.4
|
||||
github.com/atotto/clipboard v0.1.2
|
||||
github.com/aybabtme/humanlog v0.4.1
|
||||
github.com/cli/safeexec v1.0.0
|
||||
github.com/cloudfoundry/jibber_jabber v0.0.0-20151120183258-bcc4c8345a21
|
||||
github.com/creack/pty v1.1.11
|
||||
github.com/fsmiamoto/git-todo-parser v0.0.4
|
||||
github.com/fsmiamoto/git-todo-parser v0.0.2
|
||||
github.com/fsnotify/fsnotify v1.4.7
|
||||
github.com/gdamore/tcell/v2 v2.6.0
|
||||
github.com/go-errors/errors v1.4.2
|
||||
|
||||
@@ -8,8 +8,6 @@ github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPd
|
||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
|
||||
github.com/atotto/clipboard v0.1.2 h1:YZCtFu5Ie8qX2VmVTBnrqLSiU9XOWwqNRmdT3gIQzbY=
|
||||
github.com/atotto/clipboard v0.1.2/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
|
||||
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
|
||||
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
|
||||
github.com/aybabtme/humanlog v0.4.1 h1:D8d9um55rrthJsP8IGSHBcti9lTb/XknmDAX6Zy8tek=
|
||||
github.com/aybabtme/humanlog v0.4.1/go.mod h1:B0bnQX4FTSU3oftPMTTPvENCy8LqixLDvYJA9TUCAGo=
|
||||
github.com/aybabtme/rgbterm v0.0.0-20170906152045-cc83f3b3ce59/go.mod h1:q/89r3U2H7sSsE2t6Kca0lfwTK8JdoNGS/yzM/4iH5I=
|
||||
@@ -30,8 +28,8 @@ github.com/fatih/color v1.7.1-0.20180516100307-2d684516a886/go.mod h1:Zm6kSWBoL9
|
||||
github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s=
|
||||
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
|
||||
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
|
||||
github.com/fsmiamoto/git-todo-parser v0.0.4 h1:fzcGaoAFDHWzJRKw//CSZFrXucsLKplIvOSab3FtWWM=
|
||||
github.com/fsmiamoto/git-todo-parser v0.0.4/go.mod h1:B+AgTbNE2BARvJqzXygThzqxLIaEWvwr2sxKYYb0Fas=
|
||||
github.com/fsmiamoto/git-todo-parser v0.0.2 h1:l6Y+9q7jbM+yK/w6kASpHO7ejL9ARCErm3tCEqOT278=
|
||||
github.com/fsmiamoto/git-todo-parser v0.0.2/go.mod h1:B+AgTbNE2BARvJqzXygThzqxLIaEWvwr2sxKYYb0Fas=
|
||||
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko=
|
||||
|
||||
+4
-2
@@ -33,6 +33,7 @@ type App struct {
|
||||
Config config.AppConfigurer
|
||||
OSCommand *oscommands.OSCommand
|
||||
Gui *gui.Gui
|
||||
Updater *updates.Updater // may only need this on the Gui
|
||||
}
|
||||
|
||||
func Run(
|
||||
@@ -86,7 +87,8 @@ func NewApp(config config.AppConfigurer, common *common.Common) (*App, error) {
|
||||
|
||||
app.OSCommand = oscommands.NewOSCommand(common, config, oscommands.GetPlatform(), oscommands.NewNullGuiIO(app.Log))
|
||||
|
||||
updater, err := updates.NewUpdater(common, config, app.OSCommand)
|
||||
var err error
|
||||
app.Updater, err = updates.NewUpdater(common, config, app.OSCommand)
|
||||
if err != nil {
|
||||
return app, err
|
||||
}
|
||||
@@ -106,7 +108,7 @@ func NewApp(config config.AppConfigurer, common *common.Common) (*App, error) {
|
||||
return app, err
|
||||
}
|
||||
|
||||
app.Gui, err = gui.NewGui(common, config, gitVersion, updater, showRecentRepos, dirName)
|
||||
app.Gui, err = gui.NewGui(common, config, gitVersion, app.Updater, showRecentRepos, dirName)
|
||||
if err != nil {
|
||||
return app, err
|
||||
}
|
||||
|
||||
+49
-249
@@ -1,17 +1,13 @@
|
||||
package daemon
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"strconv"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/fsmiamoto/git-todo-parser/todo"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/common"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
"github.com/samber/lo"
|
||||
"github.com/jesseduffield/lazygit/pkg/env"
|
||||
)
|
||||
|
||||
// Sometimes lazygit will be invoked in daemon mode from a parent lazygit process.
|
||||
@@ -19,58 +15,33 @@ import (
|
||||
// For example, if we want to ensure that a git command doesn't hang due to
|
||||
// waiting for an editor to save a commit message, we can tell git to invoke lazygit
|
||||
// as the editor via 'GIT_EDITOR=lazygit', and use the env var
|
||||
// 'LAZYGIT_DAEMON_KIND=1' (exit immediately) to specify that we want to run lazygit
|
||||
// as a daemon which simply exits immediately.
|
||||
//
|
||||
// 'Daemon' is not the best name for this, because it's not a persistent background
|
||||
// process, but it's close enough.
|
||||
// 'LAZYGIT_DAEMON_KIND=EXIT_IMMEDIATELY' to specify that we want to run lazygit
|
||||
// as a daemon which simply exits immediately. Any additional arguments we want
|
||||
// to pass to a daemon can be done via other env vars.
|
||||
|
||||
type DaemonKind int
|
||||
type DaemonKind string
|
||||
|
||||
const (
|
||||
// for when we fail to parse the daemon kind
|
||||
DaemonKindUnknown DaemonKind = iota
|
||||
|
||||
DaemonKindExitImmediately
|
||||
DaemonKindCherryPick
|
||||
DaemonKindMoveTodoUp
|
||||
DaemonKindMoveTodoDown
|
||||
DaemonKindInsertBreak
|
||||
DaemonKindChangeTodoActions
|
||||
DaemonKindMoveFixupCommitDown
|
||||
InteractiveRebase DaemonKind = "INTERACTIVE_REBASE"
|
||||
ExitImmediately DaemonKind = "EXIT_IMMEDIATELY"
|
||||
)
|
||||
|
||||
const (
|
||||
DaemonKindEnvKey string = "LAZYGIT_DAEMON_KIND"
|
||||
|
||||
// Contains json-encoded arguments to the daemon
|
||||
DaemonInstructionEnvKey string = "LAZYGIT_DAEMON_INSTRUCTION"
|
||||
RebaseTODOEnvKey string = "LAZYGIT_REBASE_TODO"
|
||||
)
|
||||
|
||||
func getInstruction() Instruction {
|
||||
jsonData := os.Getenv(DaemonInstructionEnvKey)
|
||||
|
||||
mapping := map[DaemonKind]func(string) Instruction{
|
||||
DaemonKindExitImmediately: deserializeInstruction[*ExitImmediatelyInstruction],
|
||||
DaemonKindCherryPick: deserializeInstruction[*CherryPickCommitsInstruction],
|
||||
DaemonKindChangeTodoActions: deserializeInstruction[*ChangeTodoActionsInstruction],
|
||||
DaemonKindMoveFixupCommitDown: deserializeInstruction[*MoveFixupCommitDownInstruction],
|
||||
DaemonKindMoveTodoUp: deserializeInstruction[*MoveTodoUpInstruction],
|
||||
DaemonKindMoveTodoDown: deserializeInstruction[*MoveTodoDownInstruction],
|
||||
DaemonKindInsertBreak: deserializeInstruction[*InsertBreakInstruction],
|
||||
}
|
||||
|
||||
return mapping[getDaemonKind()](jsonData)
|
||||
type Daemon interface {
|
||||
Run() error
|
||||
}
|
||||
|
||||
func Handle(common *common.Common) {
|
||||
if !InDaemonMode() {
|
||||
d := getDaemon(common)
|
||||
if d == nil {
|
||||
return
|
||||
}
|
||||
|
||||
instruction := getInstruction()
|
||||
|
||||
if err := instruction.run(common); err != nil {
|
||||
if err := d.Run(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -78,229 +49,58 @@ func Handle(common *common.Common) {
|
||||
}
|
||||
|
||||
func InDaemonMode() bool {
|
||||
return getDaemonKind() != DaemonKindUnknown
|
||||
return getDaemonKind() != ""
|
||||
}
|
||||
|
||||
func getDaemonKind() DaemonKind {
|
||||
intValue, err := strconv.Atoi(os.Getenv(DaemonKindEnvKey))
|
||||
if err != nil {
|
||||
return DaemonKindUnknown
|
||||
func getDaemon(common *common.Common) Daemon {
|
||||
switch getDaemonKind() {
|
||||
case InteractiveRebase:
|
||||
return &rebaseDaemon{c: common}
|
||||
case ExitImmediately:
|
||||
return &exitImmediatelyDaemon{c: common}
|
||||
}
|
||||
|
||||
return DaemonKind(intValue)
|
||||
}
|
||||
|
||||
// An Instruction is a command to be run by lazygit in daemon mode.
|
||||
// It is serialized to json and passed to lazygit via environment variables
|
||||
type Instruction interface {
|
||||
Kind() DaemonKind
|
||||
SerializedInstructions() string
|
||||
|
||||
// runs the instruction
|
||||
run(common *common.Common) error
|
||||
}
|
||||
|
||||
func serializeInstruction[T any](instruction T) string {
|
||||
jsonData, err := json.Marshal(instruction)
|
||||
if err != nil {
|
||||
// this should never happen
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return string(jsonData)
|
||||
}
|
||||
|
||||
func deserializeInstruction[T Instruction](jsonData string) Instruction {
|
||||
var instruction T
|
||||
err := json.Unmarshal([]byte(jsonData), &instruction)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return instruction
|
||||
}
|
||||
|
||||
func ToEnvVars(instruction Instruction) []string {
|
||||
return []string{
|
||||
fmt.Sprintf("%s=%d", DaemonKindEnvKey, instruction.Kind()),
|
||||
fmt.Sprintf("%s=%s", DaemonInstructionEnvKey, instruction.SerializedInstructions()),
|
||||
}
|
||||
}
|
||||
|
||||
type ExitImmediatelyInstruction struct{}
|
||||
|
||||
func (self *ExitImmediatelyInstruction) Kind() DaemonKind {
|
||||
return DaemonKindExitImmediately
|
||||
}
|
||||
|
||||
func (self *ExitImmediatelyInstruction) SerializedInstructions() string {
|
||||
return serializeInstruction(self)
|
||||
}
|
||||
|
||||
func (self *ExitImmediatelyInstruction) run(common *common.Common) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewExitImmediatelyInstruction() Instruction {
|
||||
return &ExitImmediatelyInstruction{}
|
||||
func getDaemonKind() DaemonKind {
|
||||
return DaemonKind(os.Getenv(DaemonKindEnvKey))
|
||||
}
|
||||
|
||||
type CherryPickCommitsInstruction struct {
|
||||
Todo string
|
||||
type rebaseDaemon struct {
|
||||
c *common.Common
|
||||
}
|
||||
|
||||
func NewCherryPickCommitsInstruction(commits []*models.Commit) Instruction {
|
||||
todoLines := lo.Map(commits, func(commit *models.Commit, _ int) TodoLine {
|
||||
return TodoLine{
|
||||
Action: "pick",
|
||||
Commit: commit,
|
||||
func (self *rebaseDaemon) Run() error {
|
||||
self.c.Log.Info("Lazygit invoked as interactive rebase demon")
|
||||
self.c.Log.Info("args: ", os.Args)
|
||||
|
||||
if strings.HasSuffix(os.Args[1], "git-rebase-todo") {
|
||||
if err := os.WriteFile(os.Args[1], []byte(os.Getenv(RebaseTODOEnvKey)), 0o644); err != nil {
|
||||
return err
|
||||
}
|
||||
})
|
||||
|
||||
todo := TodoLinesToString(todoLines)
|
||||
|
||||
return &CherryPickCommitsInstruction{
|
||||
Todo: todo,
|
||||
} else if strings.HasSuffix(os.Args[1], filepath.Join(gitDir(), "COMMIT_EDITMSG")) { // TODO: test
|
||||
// if we are rebasing and squashing, we'll see a COMMIT_EDITMSG
|
||||
// but in this case we don't need to edit it, so we'll just return
|
||||
} else {
|
||||
self.c.Log.Info("Lazygit demon did not match on any use cases")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *CherryPickCommitsInstruction) Kind() DaemonKind {
|
||||
return DaemonKindCherryPick
|
||||
}
|
||||
|
||||
func (self *CherryPickCommitsInstruction) SerializedInstructions() string {
|
||||
return serializeInstruction(self)
|
||||
}
|
||||
|
||||
func (self *CherryPickCommitsInstruction) run(common *common.Common) error {
|
||||
return handleInteractiveRebase(common, func(path string) error {
|
||||
return utils.PrependStrToTodoFile(path, []byte(self.Todo))
|
||||
})
|
||||
}
|
||||
|
||||
type ChangeTodoActionsInstruction struct {
|
||||
Changes []ChangeTodoAction
|
||||
}
|
||||
|
||||
func NewChangeTodoActionsInstruction(changes []ChangeTodoAction) Instruction {
|
||||
return &ChangeTodoActionsInstruction{
|
||||
Changes: changes,
|
||||
func gitDir() string {
|
||||
dir := env.GetGitDirEnv()
|
||||
if dir == "" {
|
||||
return ".git"
|
||||
}
|
||||
return dir
|
||||
}
|
||||
|
||||
func (self *ChangeTodoActionsInstruction) Kind() DaemonKind {
|
||||
return DaemonKindChangeTodoActions
|
||||
type exitImmediatelyDaemon struct {
|
||||
c *common.Common
|
||||
}
|
||||
|
||||
func (self *ChangeTodoActionsInstruction) SerializedInstructions() string {
|
||||
return serializeInstruction(self)
|
||||
}
|
||||
|
||||
func (self *ChangeTodoActionsInstruction) run(common *common.Common) error {
|
||||
return handleInteractiveRebase(common, func(path string) error {
|
||||
for _, c := range self.Changes {
|
||||
if err := utils.EditRebaseTodo(path, c.Sha, todo.Pick, c.NewAction); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// Takes the sha of some commit, and the sha of a fixup commit that was created
|
||||
// at the end of the branch, then moves the fixup commit down to right after the
|
||||
// original commit, changing its type to "fixup"
|
||||
type MoveFixupCommitDownInstruction struct {
|
||||
OriginalSha string
|
||||
FixupSha string
|
||||
}
|
||||
|
||||
func NewMoveFixupCommitDownInstruction(originalSha string, fixupSha string) Instruction {
|
||||
return &MoveFixupCommitDownInstruction{
|
||||
OriginalSha: originalSha,
|
||||
FixupSha: fixupSha,
|
||||
}
|
||||
}
|
||||
|
||||
func (self *MoveFixupCommitDownInstruction) Kind() DaemonKind {
|
||||
return DaemonKindMoveFixupCommitDown
|
||||
}
|
||||
|
||||
func (self *MoveFixupCommitDownInstruction) SerializedInstructions() string {
|
||||
return serializeInstruction(self)
|
||||
}
|
||||
|
||||
func (self *MoveFixupCommitDownInstruction) run(common *common.Common) error {
|
||||
return handleInteractiveRebase(common, func(path string) error {
|
||||
return utils.MoveFixupCommitDown(path, self.OriginalSha, self.FixupSha)
|
||||
})
|
||||
}
|
||||
|
||||
type MoveTodoUpInstruction struct {
|
||||
Sha string
|
||||
}
|
||||
|
||||
func NewMoveTodoUpInstruction(sha string) Instruction {
|
||||
return &MoveTodoUpInstruction{
|
||||
Sha: sha,
|
||||
}
|
||||
}
|
||||
|
||||
func (self *MoveTodoUpInstruction) Kind() DaemonKind {
|
||||
return DaemonKindMoveTodoUp
|
||||
}
|
||||
|
||||
func (self *MoveTodoUpInstruction) SerializedInstructions() string {
|
||||
return serializeInstruction(self)
|
||||
}
|
||||
|
||||
func (self *MoveTodoUpInstruction) run(common *common.Common) error {
|
||||
return handleInteractiveRebase(common, func(path string) error {
|
||||
return utils.MoveTodoUp(path, self.Sha, todo.Pick)
|
||||
})
|
||||
}
|
||||
|
||||
type MoveTodoDownInstruction struct {
|
||||
Sha string
|
||||
}
|
||||
|
||||
func NewMoveTodoDownInstruction(sha string) Instruction {
|
||||
return &MoveTodoDownInstruction{
|
||||
Sha: sha,
|
||||
}
|
||||
}
|
||||
|
||||
func (self *MoveTodoDownInstruction) Kind() DaemonKind {
|
||||
return DaemonKindMoveTodoDown
|
||||
}
|
||||
|
||||
func (self *MoveTodoDownInstruction) SerializedInstructions() string {
|
||||
return serializeInstruction(self)
|
||||
}
|
||||
|
||||
func (self *MoveTodoDownInstruction) run(common *common.Common) error {
|
||||
return handleInteractiveRebase(common, func(path string) error {
|
||||
return utils.MoveTodoDown(path, self.Sha, todo.Pick)
|
||||
})
|
||||
}
|
||||
|
||||
type InsertBreakInstruction struct{}
|
||||
|
||||
func NewInsertBreakInstruction() Instruction {
|
||||
return &InsertBreakInstruction{}
|
||||
}
|
||||
|
||||
func (self *InsertBreakInstruction) Kind() DaemonKind {
|
||||
return DaemonKindInsertBreak
|
||||
}
|
||||
|
||||
func (self *InsertBreakInstruction) SerializedInstructions() string {
|
||||
return serializeInstruction(self)
|
||||
}
|
||||
|
||||
func (self *InsertBreakInstruction) run(common *common.Common) error {
|
||||
return handleInteractiveRebase(common, func(path string) error {
|
||||
return utils.PrependStrToTodoFile(path, []byte("break\n"))
|
||||
})
|
||||
func (self *exitImmediatelyDaemon) Run() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
package daemon
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/fsmiamoto/git-todo-parser/todo"
|
||||
"github.com/jesseduffield/generics/slices"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/common"
|
||||
"github.com/jesseduffield/lazygit/pkg/env"
|
||||
)
|
||||
|
||||
type TodoLine struct {
|
||||
Action string
|
||||
Commit *models.Commit
|
||||
}
|
||||
|
||||
func (self *TodoLine) ToString() string {
|
||||
if self.Action == "break" {
|
||||
return self.Action + "\n"
|
||||
} else {
|
||||
return self.Action + " " + self.Commit.Sha + " " + self.Commit.Name + "\n"
|
||||
}
|
||||
}
|
||||
|
||||
func TodoLinesToString(todoLines []TodoLine) string {
|
||||
lines := slices.Map(todoLines, func(todoLine TodoLine) string {
|
||||
return todoLine.ToString()
|
||||
})
|
||||
|
||||
return strings.Join(slices.Reverse(lines), "")
|
||||
}
|
||||
|
||||
type ChangeTodoAction struct {
|
||||
Sha string
|
||||
NewAction todo.TodoCommand
|
||||
}
|
||||
|
||||
func handleInteractiveRebase(common *common.Common, f func(path string) error) error {
|
||||
common.Log.Info("Lazygit invoked as interactive rebase demon")
|
||||
common.Log.Info("args: ", os.Args)
|
||||
path := os.Args[1]
|
||||
|
||||
if strings.HasSuffix(path, "git-rebase-todo") {
|
||||
return f(path)
|
||||
} else if strings.HasSuffix(path, filepath.Join(gitDir(), "COMMIT_EDITMSG")) { // TODO: test
|
||||
// if we are rebasing and squashing, we'll see a COMMIT_EDITMSG
|
||||
// but in this case we don't need to edit it, so we'll just return
|
||||
} else {
|
||||
common.Log.Info("Lazygit demon did not match on any use cases")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func gitDir() string {
|
||||
dir := env.GetGitDirEnv()
|
||||
if dir == "" {
|
||||
return ".git"
|
||||
}
|
||||
return dir
|
||||
}
|
||||
+34
-46
@@ -12,7 +12,6 @@ import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/jesseduffield/generics/maps"
|
||||
"github.com/jesseduffield/generics/slices"
|
||||
@@ -88,34 +87,33 @@ func writeString(file *os.File, str string) {
|
||||
|
||||
func localisedTitle(tr *i18n.TranslationSet, str string) string {
|
||||
contextTitleMap := map[string]string{
|
||||
"global": tr.GlobalTitle,
|
||||
"navigation": tr.NavigationTitle,
|
||||
"branches": tr.BranchesTitle,
|
||||
"localBranches": tr.LocalBranchesTitle,
|
||||
"files": tr.FilesTitle,
|
||||
"status": tr.StatusTitle,
|
||||
"submodules": tr.SubmodulesTitle,
|
||||
"subCommits": tr.SubCommitsTitle,
|
||||
"remoteBranches": tr.RemoteBranchesTitle,
|
||||
"remotes": tr.RemotesTitle,
|
||||
"reflogCommits": tr.ReflogCommitsTitle,
|
||||
"tags": tr.TagsTitle,
|
||||
"commitFiles": tr.CommitFilesTitle,
|
||||
"commitMessage": tr.CommitMessageTitle,
|
||||
"commitDescription": tr.CommitDescriptionTitle,
|
||||
"commits": tr.CommitsTitle,
|
||||
"confirmation": tr.ConfirmationTitle,
|
||||
"information": tr.InformationTitle,
|
||||
"main": tr.NormalTitle,
|
||||
"patchBuilding": tr.PatchBuildingTitle,
|
||||
"mergeConflicts": tr.MergingTitle,
|
||||
"staging": tr.StagingTitle,
|
||||
"menu": tr.MenuTitle,
|
||||
"search": tr.SearchTitle,
|
||||
"secondary": tr.SecondaryTitle,
|
||||
"stash": tr.StashTitle,
|
||||
"suggestions": tr.SuggestionsCheatsheetTitle,
|
||||
"extras": tr.ExtrasTitle,
|
||||
"global": tr.GlobalTitle,
|
||||
"navigation": tr.NavigationTitle,
|
||||
"branches": tr.BranchesTitle,
|
||||
"localBranches": tr.LocalBranchesTitle,
|
||||
"files": tr.FilesTitle,
|
||||
"status": tr.StatusTitle,
|
||||
"submodules": tr.SubmodulesTitle,
|
||||
"subCommits": tr.SubCommitsTitle,
|
||||
"remoteBranches": tr.RemoteBranchesTitle,
|
||||
"remotes": tr.RemotesTitle,
|
||||
"reflogCommits": tr.ReflogCommitsTitle,
|
||||
"tags": tr.TagsTitle,
|
||||
"commitFiles": tr.CommitFilesTitle,
|
||||
"commitMessage": tr.CommitMessageTitle,
|
||||
"commits": tr.CommitsTitle,
|
||||
"confirmation": tr.ConfirmationTitle,
|
||||
"information": tr.InformationTitle,
|
||||
"main": tr.NormalTitle,
|
||||
"patchBuilding": tr.PatchBuildingTitle,
|
||||
"mergeConflicts": tr.MergingTitle,
|
||||
"staging": tr.StagingTitle,
|
||||
"menu": tr.MenuTitle,
|
||||
"search": tr.SearchTitle,
|
||||
"secondary": tr.SecondaryTitle,
|
||||
"stash": tr.StashTitle,
|
||||
"suggestions": tr.SuggestionsCheatsheetTitle,
|
||||
"extras": tr.ExtrasTitle,
|
||||
}
|
||||
|
||||
title, ok := contextTitleMap[str]
|
||||
@@ -184,8 +182,6 @@ 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"
|
||||
@@ -203,21 +199,13 @@ 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 != "" {
|
||||
result += fmt.Sprintf(" (%s)", binding.Alternative)
|
||||
return fmt.Sprintf(
|
||||
" <kbd>%s</kbd>: %s (%s)\n",
|
||||
keybindings.LabelFromKey(binding.Key),
|
||||
binding.Description,
|
||||
binding.Alternative,
|
||||
)
|
||||
}
|
||||
result += "\n"
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func escapeAngleBrackets(str string) string {
|
||||
result := strings.ReplaceAll(str, ">", ">")
|
||||
result = strings.ReplaceAll(result, "<", "<")
|
||||
return result
|
||||
}
|
||||
|
||||
func italicize(str string) string {
|
||||
return fmt.Sprintf("_%s_", str)
|
||||
return fmt.Sprintf(" <kbd>%s</kbd>: %s\n", keybindings.LabelFromKey(binding.Key), binding.Description)
|
||||
}
|
||||
|
||||
+3
-2
@@ -117,7 +117,8 @@ func NewGitCommandAux(
|
||||
workingTreeCommands := git_commands.NewWorkingTreeCommands(gitCommon, submoduleCommands, fileLoader)
|
||||
rebaseCommands := git_commands.NewRebaseCommands(gitCommon, commitCommands, workingTreeCommands)
|
||||
stashCommands := git_commands.NewStashCommands(gitCommon, fileLoader, workingTreeCommands)
|
||||
patchBuilder := patch.NewPatchBuilder(cmn.Log,
|
||||
// TODO: have patch builder take workingTreeCommands in its entirety
|
||||
patchBuilder := patch.NewPatchBuilder(cmn.Log, workingTreeCommands.ApplyPatch,
|
||||
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.
|
||||
@@ -128,7 +129,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, statusCommands.RebaseMode)
|
||||
commitLoader := git_commands.NewCommitLoader(cmn, cmd, dotGitDir, branchCommands.CurrentBranchInfo, statusCommands.RebaseMode)
|
||||
reflogCommitLoader := git_commands.NewReflogCommitLoader(cmn, cmd)
|
||||
remoteLoader := git_commands.NewRemoteLoader(cmn, cmd, repo.Remotes)
|
||||
stashLoader := git_commands.NewStashLoader(cmn, cmd)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package git_commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
@@ -97,15 +98,13 @@ func (self *BisectCommands) GetInfo() *BisectInfo {
|
||||
}
|
||||
|
||||
func (self *BisectCommands) Reset() error {
|
||||
cmdStr := NewGitCmd("bisect").Arg("reset").ToString()
|
||||
|
||||
return self.cmd.New(cmdStr).StreamOutput().Run()
|
||||
return self.cmd.New("git bisect reset").StreamOutput().Run()
|
||||
}
|
||||
|
||||
func (self *BisectCommands) Mark(ref string, term string) error {
|
||||
cmdStr := NewGitCmd("bisect").Arg(term, ref).ToString()
|
||||
|
||||
return self.cmd.New(cmdStr).
|
||||
return self.cmd.New(
|
||||
fmt.Sprintf("git bisect %s %s", term, ref),
|
||||
).
|
||||
IgnoreEmptyError().
|
||||
StreamOutput().
|
||||
Run()
|
||||
@@ -116,9 +115,7 @@ func (self *BisectCommands) Skip(ref string) error {
|
||||
}
|
||||
|
||||
func (self *BisectCommands) Start() error {
|
||||
cmdStr := NewGitCmd("bisect").Arg("start").ToString()
|
||||
|
||||
return self.cmd.New(cmdStr).StreamOutput().Run()
|
||||
return self.cmd.New("git bisect start").StreamOutput().Run()
|
||||
}
|
||||
|
||||
// tells us whether we've found our problem commit(s). We return a string slice of
|
||||
@@ -140,8 +137,7 @@ func (self *BisectCommands) IsDone() (bool, []string, error) {
|
||||
done := false
|
||||
candidates := []string{}
|
||||
|
||||
cmdStr := NewGitCmd("rev-list").Arg(newSha).ToString()
|
||||
err := self.cmd.New(cmdStr).RunAndProcessLines(func(line string) (bool, error) {
|
||||
err := self.cmd.New(fmt.Sprintf("git rev-list %s", newSha)).RunAndProcessLines(func(line string) (bool, error) {
|
||||
sha := strings.TrimSpace(line)
|
||||
|
||||
if status, ok := info.statusMap[sha]; ok {
|
||||
@@ -171,11 +167,9 @@ 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 {
|
||||
cmdStr := NewGitCmd("merge-base").
|
||||
Arg("--is-ancestor", bisectInfo.GetNewSha(), bisectInfo.GetStartSha()).
|
||||
ToString()
|
||||
|
||||
err := self.cmd.New(cmdStr).DontLog().Run()
|
||||
err := self.cmd.New(
|
||||
fmt.Sprintf("git merge-base --is-ancestor %s %s", bisectInfo.GetNewSha(), bisectInfo.GetStartSha()),
|
||||
).DontLog().Run()
|
||||
|
||||
return err == nil
|
||||
}
|
||||
|
||||
@@ -20,20 +20,12 @@ func NewBranchCommands(gitCommon *GitCommon) *BranchCommands {
|
||||
|
||||
// New creates a new branch
|
||||
func (self *BranchCommands) New(name string, base string) error {
|
||||
cmdStr := NewGitCmd("checkout").
|
||||
Arg("-b", self.cmd.Quote(name), self.cmd.Quote(base)).
|
||||
ToString()
|
||||
|
||||
return self.cmd.New(cmdStr).Run()
|
||||
return self.cmd.New(fmt.Sprintf("git checkout -b %s %s", self.cmd.Quote(name), self.cmd.Quote(base))).Run()
|
||||
}
|
||||
|
||||
// CurrentBranchInfo get the current branch information.
|
||||
func (self *BranchCommands) CurrentBranchInfo() (BranchInfo, error) {
|
||||
branchName, err := self.cmd.New(
|
||||
NewGitCmd("symbolic-ref").
|
||||
Arg("--short", "HEAD").
|
||||
ToString(),
|
||||
).DontLog().RunWithOutput()
|
||||
branchName, err := self.cmd.New("git symbolic-ref --short HEAD").DontLog().RunWithOutput()
|
||||
if err == nil && branchName != "HEAD\n" {
|
||||
trimmedBranchName := strings.TrimSpace(branchName)
|
||||
return BranchInfo{
|
||||
@@ -42,11 +34,7 @@ func (self *BranchCommands) CurrentBranchInfo() (BranchInfo, error) {
|
||||
DetachedHead: false,
|
||||
}, nil
|
||||
}
|
||||
output, err := self.cmd.New(
|
||||
NewGitCmd("branch").
|
||||
Arg("--points-at=HEAD", "--format=\"%(HEAD)%00%(objectname)%00%(refname)\"").
|
||||
ToString(),
|
||||
).DontLog().RunWithOutput()
|
||||
output, err := self.cmd.New(`git branch --points-at=HEAD --format="%(HEAD)%00%(objectname)%00%(refname)"`).DontLog().RunWithOutput()
|
||||
if err != nil {
|
||||
return BranchInfo{}, err
|
||||
}
|
||||
@@ -71,12 +59,13 @@ func (self *BranchCommands) CurrentBranchInfo() (BranchInfo, error) {
|
||||
|
||||
// Delete delete branch
|
||||
func (self *BranchCommands) Delete(branch string, force bool) error {
|
||||
cmdStr := NewGitCmd("branch").
|
||||
ArgIfElse(force, "-D", "-d").
|
||||
Arg(self.cmd.Quote(branch)).
|
||||
ToString()
|
||||
command := "git branch -d"
|
||||
|
||||
return self.cmd.New(cmdStr).Run()
|
||||
if force {
|
||||
command = "git branch -D"
|
||||
}
|
||||
|
||||
return self.cmd.New(fmt.Sprintf("%s %s", command, self.cmd.Quote(branch))).Run()
|
||||
}
|
||||
|
||||
// Checkout checks out a branch (or commit), with --force if you set the force arg to true
|
||||
@@ -86,12 +75,12 @@ type CheckoutOptions struct {
|
||||
}
|
||||
|
||||
func (self *BranchCommands) Checkout(branch string, options CheckoutOptions) error {
|
||||
cmdStr := NewGitCmd("checkout").
|
||||
ArgIf(options.Force, "--force").
|
||||
Arg(self.cmd.Quote(branch)).
|
||||
ToString()
|
||||
forceArg := ""
|
||||
if options.Force {
|
||||
forceArg = " --force"
|
||||
}
|
||||
|
||||
return self.cmd.New(cmdStr).
|
||||
return self.cmd.New(fmt.Sprintf("git checkout%s %s", forceArg, self.cmd.Quote(branch))).
|
||||
// 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").
|
||||
@@ -115,27 +104,15 @@ func (self *BranchCommands) GetGraphCmdObj(branchName string) oscommands.ICmdObj
|
||||
}
|
||||
|
||||
func (self *BranchCommands) SetCurrentBranchUpstream(remoteName string, remoteBranchName string) error {
|
||||
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()
|
||||
return self.cmd.New(fmt.Sprintf("git branch --set-upstream-to=%s/%s", self.cmd.Quote(remoteName), self.cmd.Quote(remoteBranchName))).Run()
|
||||
}
|
||||
|
||||
func (self *BranchCommands) SetUpstream(remoteName string, remoteBranchName string, branchName string) error {
|
||||
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()
|
||||
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()
|
||||
}
|
||||
|
||||
func (self *BranchCommands) UnsetUpstream(branchName string) error {
|
||||
cmdStr := NewGitCmd("branch").Arg("--unset-upstream", self.cmd.Quote(branchName)).
|
||||
ToString()
|
||||
|
||||
return self.cmd.New(cmdStr).Run()
|
||||
return self.cmd.New(fmt.Sprintf("git branch --unset-upstream %s", self.cmd.Quote(branchName))).Run()
|
||||
}
|
||||
|
||||
func (self *BranchCommands) GetCurrentBranchUpstreamDifferenceCount() (string, string) {
|
||||
@@ -149,49 +126,29 @@ 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) {
|
||||
pushableCount, err := self.countDifferences(to, from)
|
||||
command := "git rev-list %s..%s --count"
|
||||
pushableCount, err := self.cmd.New(fmt.Sprintf(command, to, from)).DontLog().RunWithOutput()
|
||||
if err != nil {
|
||||
return "?", "?"
|
||||
}
|
||||
pullableCount, err := self.countDifferences(from, to)
|
||||
pullableCount, err := self.cmd.New(fmt.Sprintf(command, from, to)).DontLog().RunWithOutput()
|
||||
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 {
|
||||
cmdStr := NewGitCmd("symbolic-ref").Arg("-q", "HEAD").ToString()
|
||||
|
||||
err := self.cmd.New(cmdStr).DontLog().Run()
|
||||
err := self.cmd.New("git symbolic-ref -q HEAD").DontLog().Run()
|
||||
return err != nil
|
||||
}
|
||||
|
||||
func (self *BranchCommands) Rename(oldName string, newName string) error {
|
||||
cmdStr := NewGitCmd("branch").
|
||||
Arg("--move", self.cmd.Quote(oldName), self.cmd.Quote(newName)).
|
||||
ToString()
|
||||
|
||||
return self.cmd.New(cmdStr).Run()
|
||||
return self.cmd.New(fmt.Sprintf("git branch --move %s %s", self.cmd.Quote(oldName), self.cmd.Quote(newName))).Run()
|
||||
}
|
||||
|
||||
func (self *BranchCommands) GetRawBranches() (string, error) {
|
||||
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()
|
||||
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()
|
||||
}
|
||||
|
||||
type MergeOpts struct {
|
||||
@@ -199,12 +156,15 @@ type MergeOpts struct {
|
||||
}
|
||||
|
||||
func (self *BranchCommands) Merge(branchName string, opts MergeOpts) error {
|
||||
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()
|
||||
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)
|
||||
}
|
||||
|
||||
return self.cmd.New(command).Run()
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
|
||||
"github.com/go-errors/errors"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
|
||||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
@@ -100,53 +99,12 @@ func TestBranchDeleteBranch(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestBranchMerge(t *testing.T) {
|
||||
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"`,
|
||||
},
|
||||
}
|
||||
runner := oscommands.NewFakeRunner(t).
|
||||
Expect(`git merge --no-edit "test"`, "", nil)
|
||||
instance := buildBranchCommands(commonDeps{runner: runner})
|
||||
|
||||
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()
|
||||
})
|
||||
}
|
||||
assert.NoError(t, instance.Merge("test", MergeOpts{}))
|
||||
runner.CheckForMissingCalls()
|
||||
}
|
||||
|
||||
func TestBranchCheckout(t *testing.T) {
|
||||
|
||||
@@ -3,14 +3,11 @@ package git_commands
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-errors/errors"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
|
||||
)
|
||||
|
||||
var ErrInvalidCommitIndex = errors.New("invalid commit index")
|
||||
|
||||
type CommitCommands struct {
|
||||
*GitCommon
|
||||
}
|
||||
@@ -21,29 +18,25 @@ func NewCommitCommands(gitCommon *GitCommon) *CommitCommands {
|
||||
}
|
||||
}
|
||||
|
||||
// RewordLastCommit rewords the topmost commit with the given message
|
||||
func (self *CommitCommands) RewordLastCommit(message string) error {
|
||||
return self.cmd.New("git commit --allow-empty --amend --only -m " + self.cmd.Quote(message)).Run()
|
||||
}
|
||||
|
||||
// ResetAuthor resets the author of the topmost commit
|
||||
func (self *CommitCommands) ResetAuthor() error {
|
||||
cmdStr := NewGitCmd("commit").
|
||||
Arg("--allow-empty", "--only", "--no-edit", "--amend", "--reset-author").
|
||||
ToString()
|
||||
|
||||
return self.cmd.New(cmdStr).Run()
|
||||
return self.cmd.New("git commit --allow-empty --only --no-edit --amend --reset-author").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 {
|
||||
cmdStr := NewGitCmd("commit").
|
||||
Arg("--allow-empty", "--only", "--no-edit", "--amend", "--author="+self.cmd.Quote(value)).
|
||||
ToString()
|
||||
|
||||
return self.cmd.New(cmdStr).Run()
|
||||
commandStr := fmt.Sprintf("git commit --allow-empty --only --no-edit --amend --author=%s", self.cmd.Quote(value))
|
||||
return self.cmd.New(commandStr).Run()
|
||||
}
|
||||
|
||||
// ResetToCommit reset to commit
|
||||
func (self *CommitCommands) ResetToCommit(sha string, strength string, envVars []string) error {
|
||||
cmdStr := NewGitCmd("reset").Arg("--"+strength, sha).ToString()
|
||||
|
||||
return self.cmd.New(cmdStr).
|
||||
return self.cmd.New(fmt.Sprintf("git reset --%s %s", strength, sha)).
|
||||
// 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").
|
||||
@@ -52,55 +45,29 @@ func (self *CommitCommands) ResetToCommit(sha string, strength string, envVars [
|
||||
}
|
||||
|
||||
func (self *CommitCommands) CommitCmdObj(message string) oscommands.ICmdObj {
|
||||
messageArgs := self.commitMessageArgs(message)
|
||||
|
||||
skipHookPrefix := self.UserConfig.Git.SkipHookPrefix
|
||||
|
||||
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)
|
||||
|
||||
cmdStr := NewGitCmd("commit").
|
||||
Arg("--allow-empty", "--amend", "--only").
|
||||
Arg(messageArgs...).
|
||||
ToString()
|
||||
|
||||
return self.cmd.New(cmdStr).Run()
|
||||
}
|
||||
|
||||
func (self *CommitCommands) commitMessageArgs(message string) []string {
|
||||
msg, description, _ := strings.Cut(message, "\n")
|
||||
args := []string{"-m", self.cmd.Quote(msg)}
|
||||
|
||||
if description != "" {
|
||||
args = append(args, "-m", self.cmd.Quote(description))
|
||||
splitMessage := strings.Split(message, "\n")
|
||||
lineArgs := ""
|
||||
for _, line := range splitMessage {
|
||||
lineArgs += fmt.Sprintf(" -m %s", self.cmd.Quote(line))
|
||||
}
|
||||
|
||||
return args
|
||||
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(), lineArgs))
|
||||
}
|
||||
|
||||
// runs git commit without the -m argument meaning it will invoke the user's editor
|
||||
func (self *CommitCommands) CommitEditorCmdObj() oscommands.ICmdObj {
|
||||
cmdStr := NewGitCmd("commit").
|
||||
ArgIf(self.signoffFlag() != "", self.signoffFlag()).
|
||||
ArgIf(self.verboseFlag() != "", self.verboseFlag()).
|
||||
ToString()
|
||||
|
||||
return self.cmd.New(cmdStr)
|
||||
return self.cmd.New(fmt.Sprintf("git commit%s%s", self.signoffFlag(), self.verboseFlag()))
|
||||
}
|
||||
|
||||
func (self *CommitCommands) signoffFlag() string {
|
||||
if self.UserConfig.Git.Commit.SignOff {
|
||||
return "--signoff"
|
||||
return " --signoff"
|
||||
} else {
|
||||
return ""
|
||||
}
|
||||
@@ -109,9 +76,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 ""
|
||||
}
|
||||
@@ -119,25 +86,19 @@ func (self *CommitCommands) verboseFlag() string {
|
||||
|
||||
// Get the subject of the HEAD commit
|
||||
func (self *CommitCommands) GetHeadCommitMessage() (string, error) {
|
||||
cmdStr := NewGitCmd("log").Arg("-1", "--pretty=%s").ToString()
|
||||
|
||||
message, err := self.cmd.New(cmdStr).DontLog().RunWithOutput()
|
||||
message, err := self.cmd.New("git log -1 --pretty=%s").DontLog().RunWithOutput()
|
||||
return strings.TrimSpace(message), err
|
||||
}
|
||||
|
||||
func (self *CommitCommands) GetCommitMessage(commitSha string) (string, error) {
|
||||
cmdStr := NewGitCmd("rev-list").
|
||||
Arg("--format=%B", "--max-count=1", commitSha).
|
||||
ToString()
|
||||
|
||||
cmdStr := "git rev-list --format=%B --max-count=1 " + commitSha
|
||||
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 := NewGitCmd("show").Arg("--no-color", commitSha).ToString()
|
||||
|
||||
cmdStr := "git show --no-color " + commitSha
|
||||
diff, err := self.cmd.New(cmdStr).DontLog().RunWithOutput()
|
||||
return diff, err
|
||||
}
|
||||
@@ -148,10 +109,7 @@ type Author struct {
|
||||
}
|
||||
|
||||
func (self *CommitCommands) GetCommitAuthor(commitSha string) (Author, error) {
|
||||
cmdStr := NewGitCmd("show").
|
||||
Arg("--no-patch", "--pretty=format:'%an%x00%ae'", commitSha).
|
||||
ToString()
|
||||
|
||||
cmdStr := "git show --no-patch --pretty=format:'%an%x00%ae' " + commitSha
|
||||
output, err := self.cmd.New(cmdStr).DontLog().RunWithOutput()
|
||||
if err != nil {
|
||||
return Author{}, err
|
||||
@@ -171,21 +129,15 @@ func (self *CommitCommands) GetCommitMessageFirstLine(sha string) (string, error
|
||||
}
|
||||
|
||||
func (self *CommitCommands) GetCommitMessagesFirstLine(shas []string) (string, error) {
|
||||
cmdStr := NewGitCmd("show").
|
||||
Arg("--no-patch", "--pretty=format:%s").
|
||||
Arg(shas...).
|
||||
ToString()
|
||||
|
||||
return self.cmd.New(cmdStr).DontLog().RunWithOutput()
|
||||
return self.cmd.New(
|
||||
fmt.Sprintf("git show --no-patch --pretty=format:%%s %s", strings.Join(shas, " ")),
|
||||
).DontLog().RunWithOutput()
|
||||
}
|
||||
|
||||
func (self *CommitCommands) GetCommitsOneline(shas []string) (string, error) {
|
||||
cmdStr := NewGitCmd("show").
|
||||
Arg("--no-patch", "--oneline").
|
||||
Arg(shas...).
|
||||
ToString()
|
||||
|
||||
return self.cmd.New(cmdStr).DontLog().RunWithOutput()
|
||||
return self.cmd.New(
|
||||
fmt.Sprintf("git show --no-patch --oneline %s", strings.Join(shas, " ")),
|
||||
).DontLog().RunWithOutput()
|
||||
}
|
||||
|
||||
// AmendHead amends HEAD with whatever is staged in your working tree
|
||||
@@ -194,76 +146,35 @@ func (self *CommitCommands) AmendHead() error {
|
||||
}
|
||||
|
||||
func (self *CommitCommands) AmendHeadCmdObj() oscommands.ICmdObj {
|
||||
cmdStr := NewGitCmd("commit").
|
||||
Arg("--amend", "--no-edit", "--allow-empty").
|
||||
ToString()
|
||||
|
||||
return self.cmd.New(cmdStr)
|
||||
return self.cmd.New("git commit --amend --no-edit --allow-empty")
|
||||
}
|
||||
|
||||
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 := 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()
|
||||
|
||||
cmdStr := fmt.Sprintf("git show --submodule --color=%s --unified=%d --stat -p %s%s%s",
|
||||
self.UserConfig.Git.Paging.ColorArg, contextSize, sha, ignoreWhitespaceArg, filterPathArg)
|
||||
return self.cmd.New(cmdStr).DontLog()
|
||||
}
|
||||
|
||||
// Revert reverts the selected commit by sha
|
||||
func (self *CommitCommands) Revert(sha string) error {
|
||||
cmdStr := NewGitCmd("revert").Arg(sha).ToString()
|
||||
|
||||
return self.cmd.New(cmdStr).Run()
|
||||
return self.cmd.New(fmt.Sprintf("git revert %s", sha)).Run()
|
||||
}
|
||||
|
||||
func (self *CommitCommands) RevertMerge(sha string, parentNumber int) error {
|
||||
cmdStr := NewGitCmd("revert").Arg(sha, "-m", fmt.Sprintf("%d", parentNumber)).
|
||||
ToString()
|
||||
|
||||
return self.cmd.New(cmdStr).Run()
|
||||
return self.cmd.New(fmt.Sprintf("git revert %s -m %d", sha, parentNumber)).Run()
|
||||
}
|
||||
|
||||
// CreateFixupCommit creates a commit that fixes up a previous commit
|
||||
func (self *CommitCommands) CreateFixupCommit(sha string) error {
|
||||
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) {
|
||||
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")
|
||||
return self.cmd.New(fmt.Sprintf("git commit --fixup=%s", sha)).Run()
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package git_commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/jesseduffield/generics/slices"
|
||||
@@ -24,18 +25,12 @@ 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) {
|
||||
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()
|
||||
reverseFlag := ""
|
||||
if reverse {
|
||||
reverseFlag = " -R "
|
||||
}
|
||||
|
||||
filenames, err := self.cmd.New(cmdStr).DontLog().RunWithOutput()
|
||||
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()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -15,7 +15,6 @@ 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:
|
||||
@@ -29,14 +28,11 @@ type CommitLoader struct {
|
||||
*common.Common
|
||||
cmd oscommands.ICmdObjBuilder
|
||||
|
||||
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
|
||||
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
|
||||
}
|
||||
|
||||
// making our dependencies explicit for the sake of easier testing
|
||||
@@ -44,16 +40,17 @@ 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,
|
||||
getRebaseMode: getRebaseMode,
|
||||
readFile: os.ReadFile,
|
||||
walkFiles: filepath.Walk,
|
||||
dotGitDir: dotGitDir,
|
||||
quotedMainBranches: nil,
|
||||
Common: cmn,
|
||||
cmd: cmd,
|
||||
getCurrentBranchInfo: getCurrentBranchInfo,
|
||||
getRebaseMode: getRebaseMode,
|
||||
readFile: os.ReadFile,
|
||||
walkFiles: filepath.Walk,
|
||||
dotGitDir: dotGitDir,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,7 +89,7 @@ func (self *CommitLoader) GetCommits(opts GetCommitsOptions) ([]*models.Commit,
|
||||
if commit.Sha == firstPushedCommit {
|
||||
passedFirstPushedCommit = true
|
||||
}
|
||||
commit.Status = map[bool]models.CommitStatus{true: models.StatusUnpushed, false: models.StatusPushed}[!passedFirstPushedCommit]
|
||||
commit.Status = map[bool]string{true: "unpushed", false: "pushed"}[!passedFirstPushedCommit]
|
||||
commits = append(commits, commit)
|
||||
return false, nil
|
||||
})
|
||||
@@ -104,7 +101,10 @@ func (self *CommitLoader) GetCommits(opts GetCommitsOptions) ([]*models.Commit,
|
||||
return commits, nil
|
||||
}
|
||||
|
||||
commits = self.setCommitMergedStatuses(opts.RefName, commits)
|
||||
commits, err = self.setCommitMergedStatuses(opts.RefName, commits)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return commits, nil
|
||||
}
|
||||
@@ -194,40 +194,35 @@ func (self *CommitLoader) getHydratedRebasingCommits(rebaseMode enums.RebaseMode
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
commitShas := slices.FilterMap(commits, func(commit *models.Commit) (string, bool) {
|
||||
return commit.Sha, commit.Sha != ""
|
||||
commitShas := slices.Map(commits, func(commit *models.Commit) string {
|
||||
return commit.Sha
|
||||
})
|
||||
|
||||
// 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(
|
||||
NewGitCmd("show").
|
||||
Config("log.showSignature=false").
|
||||
Arg("--no-patch", "--oneline", "--abbrev=20", prettyFormat).
|
||||
Arg(commitShas...).
|
||||
ToString(),
|
||||
fmt.Sprintf(
|
||||
"git -c log.showSignature=false show %s --no-patch --oneline %s --abbrev=%d",
|
||||
strings.Join(commitShas, " "),
|
||||
prettyFormat,
|
||||
20,
|
||||
),
|
||||
).DontLog()
|
||||
|
||||
fullCommits := map[string]*models.Commit{}
|
||||
hydratedCommits := make([]*models.Commit, 0, len(commits))
|
||||
i := 0
|
||||
err = cmdObj.RunAndProcessLines(func(line string) (bool, error) {
|
||||
commit := self.extractCommitFromLine(line)
|
||||
fullCommits[commit.Sha] = commit
|
||||
matchingCommit := commits[i]
|
||||
commit.Action = matchingCommit.Action
|
||||
commit.Status = matchingCommit.Status
|
||||
hydratedCommits = append(hydratedCommits, commit)
|
||||
i++
|
||||
return false, nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
hydratedCommits := make([]*models.Commit, 0, len(commits))
|
||||
for _, rebasingCommit := range commits {
|
||||
if rebasingCommit.Sha == "" {
|
||||
hydratedCommits = append(hydratedCommits, rebasingCommit)
|
||||
} else if commit := fullCommits[rebasingCommit.Sha]; commit != nil {
|
||||
commit.Action = rebasingCommit.Action
|
||||
commit.Status = rebasingCommit.Status
|
||||
hydratedCommits = append(hydratedCommits, commit)
|
||||
}
|
||||
}
|
||||
return hydratedCommits, nil
|
||||
}
|
||||
|
||||
@@ -310,17 +305,15 @@ func (self *CommitLoader) getInteractiveRebasingCommits() ([]*models.Commit, err
|
||||
}
|
||||
|
||||
for _, t := range todos {
|
||||
if t.Command == todo.UpdateRef {
|
||||
t.Msg = strings.TrimPrefix(t.Ref, "refs/heads/")
|
||||
} else if t.Commit == "" {
|
||||
if t.Commit == "" {
|
||||
// Command does not have a commit associated, skip
|
||||
continue
|
||||
}
|
||||
commits = slices.Prepend(commits, &models.Commit{
|
||||
Sha: t.Commit,
|
||||
Name: t.Msg,
|
||||
Status: models.StatusRebasing,
|
||||
Action: t.Command,
|
||||
Status: "rebasing",
|
||||
Action: t.Command.String(),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -339,67 +332,47 @@ func (self *CommitLoader) commitFromPatch(content string) *models.Commit {
|
||||
return &models.Commit{
|
||||
Sha: sha,
|
||||
Name: name,
|
||||
Status: models.StatusRebasing,
|
||||
Status: "rebasing",
|
||||
}
|
||||
}
|
||||
|
||||
func (self *CommitLoader) setCommitMergedStatuses(refName string, commits []*models.Commit) []*models.Commit {
|
||||
ancestor := self.getMergeBase(refName)
|
||||
func (self *CommitLoader) setCommitMergedStatuses(refName string, commits []*models.Commit) ([]*models.Commit, error) {
|
||||
ancestor, err := self.getMergeBase(refName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if ancestor == "" {
|
||||
return commits
|
||||
return commits, nil
|
||||
}
|
||||
passedAncestor := false
|
||||
for i, commit := range commits {
|
||||
if strings.HasPrefix(ancestor, commit.Sha) {
|
||||
passedAncestor = true
|
||||
}
|
||||
if commit.Status != models.StatusPushed && commit.Status != models.StatusUnpushed {
|
||||
if commit.Status != "pushed" {
|
||||
continue
|
||||
}
|
||||
if passedAncestor {
|
||||
commits[i].Status = models.StatusMerged
|
||||
commits[i].Status = "merged"
|
||||
}
|
||||
}
|
||||
return commits
|
||||
return commits, nil
|
||||
}
|
||||
|
||||
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()
|
||||
func (self *CommitLoader) getMergeBase(refName string) (string, error) {
|
||||
info, err := self.getCurrentBranchInfo()
|
||||
if err != nil {
|
||||
// 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 "", err
|
||||
}
|
||||
return ignoringWarnings(output)
|
||||
}
|
||||
|
||||
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
|
||||
}), " ")
|
||||
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 ignoringWarnings(commandOutput string) string {
|
||||
@@ -417,10 +390,9 @@ func ignoringWarnings(commandOutput string) string {
|
||||
func (self *CommitLoader) getFirstPushedCommit(refName string) (string, error) {
|
||||
output, err := self.cmd.
|
||||
New(
|
||||
NewGitCmd("merge-base").
|
||||
Arg(self.cmd.Quote(refName)).
|
||||
Arg(self.cmd.Quote(strings.TrimPrefix(refName, "refs/heads/")) + "@{u}").
|
||||
ToString(),
|
||||
fmt.Sprintf("git merge-base %s %s@{u}",
|
||||
self.cmd.Quote(refName),
|
||||
self.cmd.Quote(strings.TrimPrefix(refName, "refs/heads/"))),
|
||||
).
|
||||
DontLog().
|
||||
RunWithOutput()
|
||||
@@ -433,23 +405,42 @@ 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
|
||||
|
||||
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()
|
||||
orderFlag := ""
|
||||
if config.Order != "default" {
|
||||
orderFlag = " --" + config.Order
|
||||
}
|
||||
allFlag := ""
|
||||
if opts.All {
|
||||
allFlag = " --all"
|
||||
}
|
||||
|
||||
return self.cmd.New(cmdStr).DontLog()
|
||||
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()
|
||||
}
|
||||
|
||||
const prettyFormat = `--pretty=format:"%H%x00%at%x00%aN%x00%ae%x00%d%x00%p%x00%s"`
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package git_commands
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
@@ -22,26 +21,25 @@ 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
|
||||
opts GetCommitsOptions
|
||||
mainBranches []string
|
||||
testName string
|
||||
runner *oscommands.FakeCmdObjRunner
|
||||
expectedCommits []*models.Commit
|
||||
expectedError error
|
||||
logOrder string
|
||||
rebaseMode enums.RebaseMode
|
||||
currentBranchName string
|
||||
opts GetCommitsOptions
|
||||
}
|
||||
|
||||
scenarios := []scenario{
|
||||
{
|
||||
testName: "should return no commits if there are none",
|
||||
logOrder: "topo-order",
|
||||
rebaseMode: enums.REBASE_MODE_NONE,
|
||||
opts: GetCommitsOptions{RefName: "HEAD", IncludeRebaseCommits: false},
|
||||
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},
|
||||
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),
|
||||
@@ -50,10 +48,11 @@ func TestGetCommits(t *testing.T) {
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
testName: "should use proper upstream name for branch",
|
||||
logOrder: "topo-order",
|
||||
rebaseMode: enums.REBASE_MODE_NONE,
|
||||
opts: GetCommitsOptions{RefName: "refs/heads/mybranch", IncludeRebaseCommits: false},
|
||||
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},
|
||||
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),
|
||||
@@ -62,28 +61,25 @@ func TestGetCommits(t *testing.T) {
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
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"},
|
||||
testName: "should return commits if they are present",
|
||||
logOrder: "topo-order",
|
||||
rebaseMode: enums.REBASE_MODE_NONE,
|
||||
currentBranchName: "master",
|
||||
opts: GetCommitsOptions{RefName: "HEAD", IncludeRebaseCommits: false},
|
||||
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" "refs/heads/master"`, "26c07b1ab33860a1a7591a0638f9925ccf497ffa", nil),
|
||||
Expect(`git merge-base "HEAD" "master"`, "26c07b1ab33860a1a7591a0638f9925ccf497ffa", nil),
|
||||
|
||||
expectedCommits: []*models.Commit{
|
||||
{
|
||||
Sha: "0eea75e8c631fba6b58135697835d58ba4c18dbc",
|
||||
Name: "better typing for rebase mode",
|
||||
Status: models.StatusUnpushed,
|
||||
Action: models.ActionNone,
|
||||
Status: "unpushed",
|
||||
Action: "",
|
||||
Tags: []string{},
|
||||
ExtraInfo: "(HEAD -> better-tests)",
|
||||
AuthorName: "Jesse Duffield",
|
||||
@@ -96,8 +92,8 @@ func TestGetCommits(t *testing.T) {
|
||||
{
|
||||
Sha: "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164",
|
||||
Name: "fix logging",
|
||||
Status: models.StatusPushed,
|
||||
Action: models.ActionNone,
|
||||
Status: "pushed",
|
||||
Action: "",
|
||||
Tags: []string{},
|
||||
ExtraInfo: "(origin/better-tests)",
|
||||
AuthorName: "Jesse Duffield",
|
||||
@@ -110,8 +106,8 @@ func TestGetCommits(t *testing.T) {
|
||||
{
|
||||
Sha: "e94e8fc5b6fab4cb755f29f1bdb3ee5e001df35c",
|
||||
Name: "refactor",
|
||||
Status: models.StatusPushed,
|
||||
Action: models.ActionNone,
|
||||
Status: "pushed",
|
||||
Action: "",
|
||||
Tags: []string{},
|
||||
ExtraInfo: "",
|
||||
AuthorName: "Jesse Duffield",
|
||||
@@ -124,8 +120,8 @@ func TestGetCommits(t *testing.T) {
|
||||
{
|
||||
Sha: "d8084cd558925eb7c9c38afeed5725c21653ab90",
|
||||
Name: "WIP",
|
||||
Status: models.StatusPushed,
|
||||
Action: models.ActionNone,
|
||||
Status: "pushed",
|
||||
Action: "",
|
||||
Tags: []string{},
|
||||
ExtraInfo: "",
|
||||
AuthorName: "Jesse Duffield",
|
||||
@@ -138,8 +134,8 @@ func TestGetCommits(t *testing.T) {
|
||||
{
|
||||
Sha: "65f910ebd85283b5cce9bf67d03d3f1a9ea3813a",
|
||||
Name: "WIP",
|
||||
Status: models.StatusPushed,
|
||||
Action: models.ActionNone,
|
||||
Status: "pushed",
|
||||
Action: "",
|
||||
Tags: []string{},
|
||||
ExtraInfo: "",
|
||||
AuthorName: "Jesse Duffield",
|
||||
@@ -152,8 +148,8 @@ func TestGetCommits(t *testing.T) {
|
||||
{
|
||||
Sha: "26c07b1ab33860a1a7591a0638f9925ccf497ffa",
|
||||
Name: "WIP",
|
||||
Status: models.StatusMerged,
|
||||
Action: models.ActionNone,
|
||||
Status: "merged",
|
||||
Action: "",
|
||||
Tags: []string{},
|
||||
ExtraInfo: "",
|
||||
AuthorName: "Jesse Duffield",
|
||||
@@ -166,8 +162,8 @@ func TestGetCommits(t *testing.T) {
|
||||
{
|
||||
Sha: "3d4470a6c072208722e5ae9a54bcb9634959a1c5",
|
||||
Name: "WIP",
|
||||
Status: models.StatusMerged,
|
||||
Action: models.ActionNone,
|
||||
Status: "merged",
|
||||
Action: "",
|
||||
Tags: []string{},
|
||||
ExtraInfo: "",
|
||||
AuthorName: "Jesse Duffield",
|
||||
@@ -180,8 +176,8 @@ func TestGetCommits(t *testing.T) {
|
||||
{
|
||||
Sha: "053a66a7be3da43aacdc7aa78e1fe757b82c4dd2",
|
||||
Name: "refactoring the config struct",
|
||||
Status: models.StatusMerged,
|
||||
Action: models.ActionNone,
|
||||
Status: "merged",
|
||||
Action: "",
|
||||
Tags: []string{},
|
||||
ExtraInfo: "",
|
||||
AuthorName: "Jesse Duffield",
|
||||
@@ -195,80 +191,11 @@ func TestGetCommits(t *testing.T) {
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
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},
|
||||
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},
|
||||
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),
|
||||
@@ -277,10 +204,11 @@ func TestGetCommits(t *testing.T) {
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
testName: "should set filter path",
|
||||
logOrder: "default",
|
||||
rebaseMode: enums.REBASE_MODE_NONE,
|
||||
opts: GetCommitsOptions{RefName: "HEAD", FilterPath: "src"},
|
||||
testName: "should set filter path",
|
||||
logOrder: "default",
|
||||
rebaseMode: enums.REBASE_MODE_NONE,
|
||||
currentBranchName: "master",
|
||||
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),
|
||||
@@ -297,8 +225,11 @@ func TestGetCommits(t *testing.T) {
|
||||
common.UserConfig.Git.Log.Order = scenario.logOrder
|
||||
|
||||
builder := &CommitLoader{
|
||||
Common: common,
|
||||
cmd: oscommands.NewDummyCmdObjBuilder(scenario.runner),
|
||||
Common: common,
|
||||
cmd: oscommands.NewDummyCmdObjBuilder(scenario.runner),
|
||||
getCurrentBranchInfo: func() (BranchInfo, error) {
|
||||
return BranchInfo{RefName: scenario.currentBranchName, DisplayName: scenario.currentBranchName, DetachedHead: false}, nil
|
||||
},
|
||||
getRebaseMode: func() (enums.RebaseMode, error) { return scenario.rebaseMode, nil },
|
||||
dotGitDir: ".git",
|
||||
readFile: func(filename string) ([]byte, error) {
|
||||
@@ -309,7 +240,6 @@ func TestGetCommits(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
common.UserConfig.Git.MainBranches = scenario.mainBranches
|
||||
commits, err := builder.GetCommits(scenario.opts)
|
||||
|
||||
assert.Equal(t, scenario.expectedCommits, commits)
|
||||
|
||||
@@ -9,32 +9,12 @@ import (
|
||||
)
|
||||
|
||||
func TestCommitRewordCommit(t *testing.T) {
|
||||
type scenario struct {
|
||||
testName string
|
||||
runner *oscommands.FakeCmdObjRunner
|
||||
input string
|
||||
}
|
||||
scenarios := []scenario{
|
||||
{
|
||||
"Single line reword",
|
||||
oscommands.NewFakeRunner(t).ExpectGitArgs([]string{"commit", "--allow-empty", "--amend", "--only", "-m", "test"}, "", nil),
|
||||
"test",
|
||||
},
|
||||
{
|
||||
"Multi line reword",
|
||||
oscommands.NewFakeRunner(t).ExpectGitArgs([]string{"commit", "--allow-empty", "--amend", "--only", "-m", "test", "-m", "line 2\nline 3"}, "", nil),
|
||||
"test\nline 2\nline 3",
|
||||
},
|
||||
}
|
||||
for _, s := range scenarios {
|
||||
s := s
|
||||
t.Run(s.testName, func(t *testing.T) {
|
||||
instance := buildCommitCommands(commonDeps{runner: s.runner})
|
||||
runner := oscommands.NewFakeRunner(t).
|
||||
ExpectGitArgs([]string{"commit", "--allow-empty", "--amend", "--only", "-m", "test"}, "", nil)
|
||||
instance := buildCommitCommands(commonDeps{runner: runner})
|
||||
|
||||
assert.NoError(t, instance.RewordLastCommit(s.input))
|
||||
s.runner.CheckForMissingCalls()
|
||||
})
|
||||
}
|
||||
assert.NoError(t, instance.RewordLastCommit("test"))
|
||||
runner.CheckForMissingCalls()
|
||||
}
|
||||
|
||||
func TestCommitResetToCommit(t *testing.T) {
|
||||
@@ -294,40 +274,3 @@ Merge pull request #1750 from mark2185/fix-issue-template
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetCommitMessageFromHistory(t *testing.T) {
|
||||
type scenario struct {
|
||||
testName string
|
||||
runner *oscommands.FakeCmdObjRunner
|
||||
test func(string, error)
|
||||
}
|
||||
scenarios := []scenario{
|
||||
{
|
||||
"Empty message",
|
||||
oscommands.NewFakeRunner(t).Expect("git log -1 --skip=2 --pretty=%H", "", nil).Expect("git rev-list --format=%B --max-count=1 ", "", nil),
|
||||
func(output string, err error) {
|
||||
assert.Error(t, err)
|
||||
},
|
||||
},
|
||||
{
|
||||
"Default case to retrieve a commit in history",
|
||||
oscommands.NewFakeRunner(t).Expect("git log -1 --skip=2 --pretty=%H", "sha3 \n", nil).Expect("git rev-list --format=%B --max-count=1 sha3", `commit sha3
|
||||
use generics to DRY up context code`, nil),
|
||||
func(output string, err error) {
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "use generics to DRY up context code", output)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
s := s
|
||||
t.Run(s.testName, func(t *testing.T) {
|
||||
instance := buildCommitCommands(commonDeps{runner: s.runner})
|
||||
|
||||
output, err := instance.GetCommitMessageFromHistory(2)
|
||||
|
||||
s.test(output, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ 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"
|
||||
@@ -118,26 +117,6 @@ 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)
|
||||
@@ -171,9 +150,3 @@ func buildBranchCommands(deps commonDeps) *BranchCommands {
|
||||
|
||||
return NewBranchCommands(gitCommon)
|
||||
}
|
||||
|
||||
func buildFlowCommands(deps commonDeps) *FlowCommands {
|
||||
gitCommon := buildGitCommon(deps)
|
||||
|
||||
return NewFlowCommands(gitCommon)
|
||||
}
|
||||
|
||||
@@ -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,15 +81,13 @@ type FileStatus struct {
|
||||
PreviousName string
|
||||
}
|
||||
|
||||
func (c *FileLoader) gitStatus(opts GitStatusOptions) ([]FileStatus, error) {
|
||||
cmdStr := NewGitCmd("status").
|
||||
Arg(opts.UntrackedFilesArg).
|
||||
Arg("--porcelain").
|
||||
Arg("-z").
|
||||
ArgIf(opts.NoRenames, "--no-renames").
|
||||
ToString()
|
||||
func (c *FileLoader) GitStatus(opts GitStatusOptions) ([]FileStatus, error) {
|
||||
noRenamesFlag := ""
|
||||
if opts.NoRenames {
|
||||
noRenamesFlag = " --no-renames"
|
||||
}
|
||||
|
||||
statusLines, _, err := c.cmd.New(cmdStr).DontLog().RunWithOutputs()
|
||||
statusLines, _, err := c.cmd.New(fmt.Sprintf("git status %s --porcelain -z%s", opts.UntrackedFilesArg, noRenamesFlag)).DontLog().RunWithOutputs()
|
||||
if err != nil {
|
||||
return []FileStatus{}, err
|
||||
}
|
||||
|
||||
@@ -34,7 +34,6 @@ 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)
|
||||
|
||||
@@ -49,13 +48,9 @@ func (self *FlowCommands) FinishCmdObj(branchName string) (oscommands.ICmdObj, e
|
||||
return nil, errors.New(self.Tr.NotAGitFlowBranch)
|
||||
}
|
||||
|
||||
cmdStr := NewGitCmd("flow").Arg(branchType, "finish", suffix).ToString()
|
||||
|
||||
return self.cmd.New(cmdStr), nil
|
||||
return self.cmd.New("git flow " + branchType + " finish " + suffix), nil
|
||||
}
|
||||
|
||||
func (self *FlowCommands) StartCmdObj(branchType string, name string) oscommands.ICmdObj {
|
||||
cmdStr := NewGitCmd("flow").Arg(branchType, "start", name).ToString()
|
||||
|
||||
return self.cmd.New(cmdStr)
|
||||
return self.cmd.New("git flow " + branchType + " start " + name)
|
||||
}
|
||||
|
||||
@@ -1,92 +0,0 @@
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
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, " ")
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -2,16 +2,11 @@ package git_commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/fsmiamoto/git-todo-parser/todo"
|
||||
"github.com/go-errors/errors"
|
||||
"github.com/jesseduffield/lazygit/pkg/app/daemon"
|
||||
"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 {
|
||||
@@ -42,53 +37,6 @@ 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 {
|
||||
@@ -96,7 +44,7 @@ func (self *PatchCommands) DeletePatchesFromCommit(commits []*models.Commit, com
|
||||
}
|
||||
|
||||
// apply each patch in reverse
|
||||
if err := self.ApplyCustomPatch(true); err != nil {
|
||||
if err := self.PatchBuilder.ApplyPatches(true); err != nil {
|
||||
_ = self.rebase.AbortRebase()
|
||||
return err
|
||||
}
|
||||
@@ -122,7 +70,7 @@ func (self *PatchCommands) MovePatchToSelectedCommit(commits []*models.Commit, s
|
||||
}
|
||||
|
||||
// apply each patch forward
|
||||
if err := self.ApplyCustomPatch(false); err != nil {
|
||||
if err := self.PatchBuilder.ApplyPatches(false); err != nil {
|
||||
// Don't abort the rebase here; this might cause conflicts, so give
|
||||
// the user a chance to resolve them
|
||||
return err
|
||||
@@ -155,23 +103,21 @@ func (self *PatchCommands) MovePatchToSelectedCommit(commits []*models.Commit, s
|
||||
|
||||
baseIndex := sourceCommitIdx + 1
|
||||
|
||||
changes := []daemon.ChangeTodoAction{
|
||||
{Sha: commits[sourceCommitIdx].Sha, NewAction: todo.Edit},
|
||||
{Sha: commits[destinationCommitIdx].Sha, NewAction: todo.Edit},
|
||||
}
|
||||
self.os.LogCommand(logTodoChanges(changes), false)
|
||||
todoLines := self.rebase.BuildTodoLines(commits[0:baseIndex], func(commit *models.Commit, i int) string {
|
||||
if i == sourceCommitIdx || i == destinationCommitIdx {
|
||||
return "edit"
|
||||
} else {
|
||||
return "pick"
|
||||
}
|
||||
})
|
||||
|
||||
err := self.rebase.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{
|
||||
baseShaOrRoot: commits[baseIndex].Sha,
|
||||
overrideEditor: true,
|
||||
instruction: daemon.NewChangeTodoActionsInstruction(changes),
|
||||
}).Run()
|
||||
err := self.rebase.PrepareInteractiveRebaseCommand(commits[baseIndex].Sha, todoLines, true).Run()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// apply each patch in reverse
|
||||
if err := self.ApplyCustomPatch(true); err != nil {
|
||||
if err := self.PatchBuilder.ApplyPatches(true); err != nil {
|
||||
_ = self.rebase.AbortRebase()
|
||||
return err
|
||||
}
|
||||
@@ -194,7 +140,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.ApplyPatch(patch, ApplyPatchOpts{Index: true, ThreeWay: true}); err != nil {
|
||||
if err := self.rebase.workingTree.ApplyPatch(patch, "index", "3way"); err != nil {
|
||||
// Don't abort the rebase here; this might cause conflicts, so give
|
||||
// the user a chance to resolve them
|
||||
return err
|
||||
@@ -227,7 +173,7 @@ func (self *PatchCommands) MovePatchIntoIndex(commits []*models.Commit, commitId
|
||||
return err
|
||||
}
|
||||
|
||||
if err := self.ApplyCustomPatch(true); err != nil {
|
||||
if err := self.PatchBuilder.ApplyPatches(true); err != nil {
|
||||
if self.status.WorkingTreeState() == enums.REBASE_MODE_REBASING {
|
||||
_ = self.rebase.AbortRebase()
|
||||
}
|
||||
@@ -251,7 +197,7 @@ func (self *PatchCommands) MovePatchIntoIndex(commits []*models.Commit, commitId
|
||||
|
||||
self.rebase.onSuccessfulContinue = func() error {
|
||||
// add patches to index
|
||||
if err := self.ApplyPatch(patch, ApplyPatchOpts{Index: true, ThreeWay: true}); err != nil {
|
||||
if err := self.rebase.workingTree.ApplyPatch(patch, "index", "3way"); err != nil {
|
||||
if self.status.WorkingTreeState() == enums.REBASE_MODE_REBASING {
|
||||
_ = self.rebase.AbortRebase()
|
||||
}
|
||||
@@ -276,7 +222,7 @@ func (self *PatchCommands) PullPatchIntoNewCommit(commits []*models.Commit, comm
|
||||
return err
|
||||
}
|
||||
|
||||
if err := self.ApplyCustomPatch(true); err != nil {
|
||||
if err := self.PatchBuilder.ApplyPatches(true); err != nil {
|
||||
_ = self.rebase.AbortRebase()
|
||||
return err
|
||||
}
|
||||
@@ -292,7 +238,7 @@ func (self *PatchCommands) PullPatchIntoNewCommit(commits []*models.Commit, comm
|
||||
return err
|
||||
}
|
||||
|
||||
if err := self.ApplyPatch(patch, ApplyPatchOpts{Index: true, ThreeWay: true}); err != nil {
|
||||
if err := self.rebase.workingTree.ApplyPatch(patch, "index", "3way"); err != nil {
|
||||
_ = self.rebase.AbortRebase()
|
||||
return err
|
||||
}
|
||||
@@ -317,7 +263,5 @@ 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) {
|
||||
cmdStr := NewGitCmd("diff").Arg("HEAD.." + commit.Sha).ToString()
|
||||
|
||||
return self.cmd.New(cmdStr).RunWithOutput()
|
||||
return self.cmd.New(fmt.Sprintf("git diff HEAD..%s", commit.Sha)).RunWithOutput()
|
||||
}
|
||||
|
||||
@@ -1,66 +0,0 @@
|
||||
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()
|
||||
})
|
||||
}
|
||||
}
|
||||
+156
-141
@@ -2,17 +2,15 @@ package git_commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/fsmiamoto/git-todo-parser/todo"
|
||||
"github.com/go-errors/errors"
|
||||
"github.com/jesseduffield/generics/slices"
|
||||
"github.com/jesseduffield/lazygit/pkg/app/daemon"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
type RebaseCommands struct {
|
||||
@@ -56,16 +54,12 @@ func (self *RebaseCommands) RewordCommit(commits []*models.Commit, index int, me
|
||||
}
|
||||
|
||||
func (self *RebaseCommands) RewordCommitInEditor(commits []*models.Commit, index int) (oscommands.ICmdObj, error) {
|
||||
changes := []daemon.ChangeTodoAction{{
|
||||
Sha: commits[index].Sha,
|
||||
NewAction: todo.Reword,
|
||||
}}
|
||||
self.os.LogCommand(logTodoChanges(changes), false)
|
||||
todo, sha, err := self.BuildSingleActionTodo(commits, index, "reword")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{
|
||||
baseShaOrRoot: getBaseShaOrRoot(commits, index+1),
|
||||
instruction: daemon.NewChangeTodoActionsInstruction(changes),
|
||||
}), nil
|
||||
return self.PrepareInteractiveRebaseCommand(sha, todo, false), nil
|
||||
}
|
||||
|
||||
func (self *RebaseCommands) ResetCommitAuthor(commits []*models.Commit, index int) error {
|
||||
@@ -81,7 +75,7 @@ func (self *RebaseCommands) SetCommitAuthor(commits []*models.Commit, index int,
|
||||
}
|
||||
|
||||
func (self *RebaseCommands) GenericAmend(commits []*models.Commit, index int, f func() error) error {
|
||||
if models.IsHeadCommit(commits, index) {
|
||||
if index == 0 {
|
||||
// we've selected the top commit so no rebase is required
|
||||
return f()
|
||||
}
|
||||
@@ -101,160 +95,161 @@ func (self *RebaseCommands) GenericAmend(commits []*models.Commit, index int, f
|
||||
}
|
||||
|
||||
func (self *RebaseCommands) MoveCommitDown(commits []*models.Commit, index int) error {
|
||||
// not appending to original slice so that we don't mutate it
|
||||
orderedCommits := append([]*models.Commit{}, commits[0:index]...)
|
||||
orderedCommits = append(orderedCommits, commits[index+1], commits[index])
|
||||
|
||||
todoLines := self.BuildTodoLinesSingleAction(orderedCommits, "pick")
|
||||
|
||||
baseShaOrRoot := getBaseShaOrRoot(commits, index+2)
|
||||
|
||||
sha := commits[index].Sha
|
||||
|
||||
self.os.LogCommand(fmt.Sprintf("Moving TODO down: %s", utils.ShortSha(sha)), false)
|
||||
|
||||
return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{
|
||||
baseShaOrRoot: baseShaOrRoot,
|
||||
instruction: daemon.NewMoveTodoDownInstruction(sha),
|
||||
overrideEditor: true,
|
||||
}).Run()
|
||||
return self.PrepareInteractiveRebaseCommand(baseShaOrRoot, todoLines, true).Run()
|
||||
}
|
||||
|
||||
func (self *RebaseCommands) MoveCommitUp(commits []*models.Commit, index int) error {
|
||||
baseShaOrRoot := getBaseShaOrRoot(commits, index+1)
|
||||
|
||||
sha := commits[index].Sha
|
||||
|
||||
self.os.LogCommand(fmt.Sprintf("Moving TODO up: %s", utils.ShortSha(sha)), false)
|
||||
|
||||
return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{
|
||||
baseShaOrRoot: baseShaOrRoot,
|
||||
instruction: daemon.NewMoveTodoUpInstruction(sha),
|
||||
overrideEditor: true,
|
||||
}).Run()
|
||||
}
|
||||
|
||||
func (self *RebaseCommands) InteractiveRebase(commits []*models.Commit, index int, action todo.TodoCommand) error {
|
||||
baseIndex := index + 1
|
||||
if action == todo.Squash || action == todo.Fixup {
|
||||
baseIndex++
|
||||
func (self *RebaseCommands) InteractiveRebase(commits []*models.Commit, index int, action string) error {
|
||||
todo, sha, err := self.BuildSingleActionTodo(commits, index, action)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
baseShaOrRoot := getBaseShaOrRoot(commits, baseIndex)
|
||||
|
||||
changes := []daemon.ChangeTodoAction{{
|
||||
Sha: commits[index].Sha,
|
||||
NewAction: action,
|
||||
}}
|
||||
self.os.LogCommand(logTodoChanges(changes), false)
|
||||
|
||||
return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{
|
||||
baseShaOrRoot: baseShaOrRoot,
|
||||
overrideEditor: true,
|
||||
instruction: daemon.NewChangeTodoActionsInstruction(changes),
|
||||
}).Run()
|
||||
return self.PrepareInteractiveRebaseCommand(sha, todo, true).Run()
|
||||
}
|
||||
|
||||
func (self *RebaseCommands) EditRebase(branchRef string) error {
|
||||
self.os.LogCommand(fmt.Sprintf("Beginning interactive rebase at '%s'", branchRef), false)
|
||||
return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{
|
||||
baseShaOrRoot: branchRef,
|
||||
instruction: daemon.NewInsertBreakInstruction(),
|
||||
}).Run()
|
||||
}
|
||||
func (self *RebaseCommands) InteractiveRebaseBreakAfter(commits []*models.Commit, index int) error {
|
||||
todo, sha, err := self.BuildSingleActionTodo(commits, index-1, "pick")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
func logTodoChanges(changes []daemon.ChangeTodoAction) string {
|
||||
changeTodoStr := strings.Join(slices.Map(changes, func(c daemon.ChangeTodoAction) string {
|
||||
return fmt.Sprintf("%s:%s", c.Sha, c.NewAction)
|
||||
}), "\n")
|
||||
return fmt.Sprintf("Changing TODO actions: %s", changeTodoStr)
|
||||
}
|
||||
|
||||
type PrepareInteractiveRebaseCommandOpts struct {
|
||||
baseShaOrRoot string
|
||||
instruction daemon.Instruction
|
||||
overrideEditor bool
|
||||
todo = append(todo, TodoLine{Action: "break", Commit: nil})
|
||||
return self.PrepareInteractiveRebaseCommand(sha, todo, true).Run()
|
||||
}
|
||||
|
||||
// PrepareInteractiveRebaseCommand returns the cmd for an interactive rebase
|
||||
// we tell git to run lazygit to edit the todo list, and we pass the client
|
||||
// lazygit a todo string to write to the todo file
|
||||
func (self *RebaseCommands) PrepareInteractiveRebaseCommand(opts PrepareInteractiveRebaseCommandOpts) oscommands.ICmdObj {
|
||||
func (self *RebaseCommands) PrepareInteractiveRebaseCommand(baseShaOrRoot string, todoLines []TodoLine, overrideEditor bool) oscommands.ICmdObj {
|
||||
todo := self.buildTodo(todoLines)
|
||||
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"
|
||||
}
|
||||
|
||||
cmdStr := fmt.Sprintf("git rebase --interactive --autostash --keep-empty --empty=keep --no-autosquash %s", baseShaOrRoot)
|
||||
self.Log.WithField("command", cmdStr).Debug("RunCommand")
|
||||
|
||||
cmdObj := self.cmd.New(cmdStr)
|
||||
|
||||
gitSequenceEditor := ex
|
||||
|
||||
if opts.instruction != nil {
|
||||
cmdObj.AddEnvVars(daemon.ToEnvVars(opts.instruction)...)
|
||||
} else {
|
||||
if todo == "" {
|
||||
gitSequenceEditor = "true"
|
||||
} else {
|
||||
self.os.LogCommand(fmt.Sprintf("Creating TODO file for interactive rebase: \n\n%s", todo), false)
|
||||
}
|
||||
|
||||
cmdObj.AddEnvVars(
|
||||
daemon.DaemonKindEnvKey+"="+string(daemon.InteractiveRebase),
|
||||
daemon.RebaseTODOEnvKey+"="+todo,
|
||||
"DEBUG="+debug,
|
||||
"LANG=en_US.UTF-8", // Force using EN as language
|
||||
"LC_ALL=en_US.UTF-8", // Force using EN as language
|
||||
"GIT_SEQUENCE_EDITOR="+gitSequenceEditor,
|
||||
)
|
||||
|
||||
if opts.overrideEditor {
|
||||
if overrideEditor {
|
||||
cmdObj.AddEnvVars("GIT_EDITOR=" + ex)
|
||||
}
|
||||
|
||||
return cmdObj
|
||||
}
|
||||
|
||||
// AmendTo amends the given commit with whatever files are staged
|
||||
func (self *RebaseCommands) AmendTo(commits []*models.Commit, commitIndex int) error {
|
||||
commit := commits[commitIndex]
|
||||
// produces TodoLines where every commit is picked (or dropped for merge commits) except for the commit at the given index, which
|
||||
// will have the given action applied to it.
|
||||
func (self *RebaseCommands) BuildSingleActionTodo(commits []*models.Commit, actionIndex int, action string) ([]TodoLine, string, error) {
|
||||
baseIndex := actionIndex + 1
|
||||
|
||||
if action == "squash" || action == "fixup" {
|
||||
baseIndex++
|
||||
}
|
||||
|
||||
todoLines := self.BuildTodoLines(commits[0:baseIndex], func(commit *models.Commit, i int) string {
|
||||
if i == actionIndex {
|
||||
return action
|
||||
} else if commit.IsMerge() {
|
||||
// your typical interactive rebase will actually drop merge commits by default. Damn git CLI, you scary!
|
||||
// doing this means we don't need to worry about rebasing over merges which always causes problems.
|
||||
// you typically shouldn't be doing rebases that pass over merge commits anyway.
|
||||
return "drop"
|
||||
} else {
|
||||
return "pick"
|
||||
}
|
||||
})
|
||||
|
||||
baseShaOrRoot := getBaseShaOrRoot(commits, baseIndex)
|
||||
|
||||
return todoLines, baseShaOrRoot, nil
|
||||
}
|
||||
|
||||
// AmendTo amends the given commit with whatever files are staged
|
||||
func (self *RebaseCommands) AmendTo(commit *models.Commit) error {
|
||||
if err := self.commit.CreateFixupCommit(commit.Sha); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Get the sha of the commit we just created
|
||||
cmdStr := NewGitCmd("rev-parse").Arg("--verify", "HEAD").ToString()
|
||||
fixupSha, err := self.cmd.New(cmdStr).RunWithOutput()
|
||||
return self.SquashAllAboveFixupCommits(commit)
|
||||
}
|
||||
|
||||
// EditRebaseTodo sets the action at a given index in the git-rebase-todo file
|
||||
func (self *RebaseCommands) EditRebaseTodo(index int, action string) error {
|
||||
fileName := filepath.Join(self.dotGitDir, "rebase-merge/git-rebase-todo")
|
||||
bytes, err := os.ReadFile(fileName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{
|
||||
baseShaOrRoot: getBaseShaOrRoot(commits, commitIndex+1),
|
||||
overrideEditor: true,
|
||||
instruction: daemon.NewMoveFixupCommitDownInstruction(commit.Sha, fixupSha),
|
||||
}).Run()
|
||||
content := strings.Split(string(bytes), "\n")
|
||||
commitCount := self.getTodoCommitCount(content)
|
||||
|
||||
// we have the most recent commit at the bottom whereas the todo file has
|
||||
// it at the bottom, so we need to subtract our index from the commit count
|
||||
contentIndex := commitCount - 1 - index
|
||||
splitLine := strings.Split(content[contentIndex], " ")
|
||||
content[contentIndex] = action + " " + strings.Join(splitLine[1:], " ")
|
||||
result := strings.Join(content, "\n")
|
||||
|
||||
return os.WriteFile(fileName, []byte(result), 0o644)
|
||||
}
|
||||
|
||||
// EditRebaseTodo sets the action for a given rebase commit in the git-rebase-todo file
|
||||
func (self *RebaseCommands) EditRebaseTodo(commit *models.Commit, action todo.TodoCommand) error {
|
||||
return utils.EditRebaseTodo(
|
||||
filepath.Join(self.dotGitDir, "rebase-merge/git-rebase-todo"), commit.Sha, commit.Action, action)
|
||||
func (self *RebaseCommands) getTodoCommitCount(content []string) int {
|
||||
// count lines that are not blank and are not comments
|
||||
commitCount := 0
|
||||
for _, line := range content {
|
||||
if line != "" && !strings.HasPrefix(line, "#") {
|
||||
commitCount++
|
||||
}
|
||||
}
|
||||
return commitCount
|
||||
}
|
||||
|
||||
// MoveTodoDown moves a rebase todo item down by one position
|
||||
func (self *RebaseCommands) MoveTodoDown(commit *models.Commit) error {
|
||||
func (self *RebaseCommands) MoveTodoDown(index int) error {
|
||||
fileName := filepath.Join(self.dotGitDir, "rebase-merge/git-rebase-todo")
|
||||
return utils.MoveTodoDown(fileName, commit.Sha, commit.Action)
|
||||
}
|
||||
bytes, err := os.ReadFile(fileName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// MoveTodoDown moves a rebase todo item down by one position
|
||||
func (self *RebaseCommands) MoveTodoUp(commit *models.Commit) error {
|
||||
fileName := filepath.Join(self.dotGitDir, "rebase-merge/git-rebase-todo")
|
||||
return utils.MoveTodoUp(fileName, commit.Sha, commit.Action)
|
||||
content := strings.Split(string(bytes), "\n")
|
||||
commitCount := self.getTodoCommitCount(content)
|
||||
contentIndex := commitCount - 1 - index
|
||||
|
||||
rearrangedContent := append(content[0:contentIndex-1], content[contentIndex], content[contentIndex-1])
|
||||
rearrangedContent = append(rearrangedContent, content[contentIndex+1:]...)
|
||||
result := strings.Join(rearrangedContent, "\n")
|
||||
|
||||
return os.WriteFile(fileName, []byte(result), 0o644)
|
||||
}
|
||||
|
||||
// SquashAllAboveFixupCommits squashes all fixup! commits above the given one
|
||||
@@ -264,11 +259,14 @@ func (self *RebaseCommands) SquashAllAboveFixupCommits(commit *models.Commit) er
|
||||
shaOrRoot = "--root"
|
||||
}
|
||||
|
||||
cmdStr := NewGitCmd("rebase").
|
||||
Arg("--interactive", "--rebase-merges", "--autostash", "--autosquash", shaOrRoot).
|
||||
ToString()
|
||||
|
||||
return self.runSkipEditorCommand(self.cmd.New(cmdStr))
|
||||
return self.runSkipEditorCommand(
|
||||
self.cmd.New(
|
||||
fmt.Sprintf(
|
||||
"git rebase --interactive --rebase-merges --autostash --autosquash %s",
|
||||
shaOrRoot,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
// BeginInteractiveRebaseForCommit starts an interactive rebase to edit the current
|
||||
@@ -285,28 +283,21 @@ func (self *RebaseCommands) BeginInteractiveRebaseForCommit(commits []*models.Co
|
||||
return errors.New(self.Tr.DisabledForGPG)
|
||||
}
|
||||
|
||||
changes := []daemon.ChangeTodoAction{{
|
||||
Sha: commits[commitIndex].Sha,
|
||||
NewAction: todo.Edit,
|
||||
}}
|
||||
self.os.LogCommand(logTodoChanges(changes), false)
|
||||
todo, sha, err := self.BuildSingleActionTodo(commits, commitIndex, "edit")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{
|
||||
baseShaOrRoot: getBaseShaOrRoot(commits, commitIndex+1),
|
||||
overrideEditor: true,
|
||||
instruction: daemon.NewChangeTodoActionsInstruction(changes),
|
||||
}).Run()
|
||||
return self.PrepareInteractiveRebaseCommand(sha, todo, true).Run()
|
||||
}
|
||||
|
||||
// RebaseBranch interactive rebases onto a branch
|
||||
func (self *RebaseCommands) RebaseBranch(branchName string) error {
|
||||
return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{baseShaOrRoot: branchName}).Run()
|
||||
return self.PrepareInteractiveRebaseCommand(branchName, nil, false).Run()
|
||||
}
|
||||
|
||||
func (self *RebaseCommands) GenericMergeOrRebaseActionCmdObj(commandType string, command string) oscommands.ICmdObj {
|
||||
cmdStr := NewGitCmd(commandType).Arg("--" + command).ToString()
|
||||
|
||||
return self.cmd.New(cmdStr)
|
||||
return self.cmd.New("git " + commandType + " --" + command)
|
||||
}
|
||||
|
||||
func (self *RebaseCommands) ContinueRebase() error {
|
||||
@@ -343,16 +334,15 @@ func (self *RebaseCommands) GenericMergeOrRebaseAction(commandType string, comma
|
||||
}
|
||||
|
||||
func (self *RebaseCommands) runSkipEditorCommand(cmdObj oscommands.ICmdObj) error {
|
||||
instruction := daemon.NewExitImmediatelyInstruction()
|
||||
lazyGitPath := oscommands.GetLazygitPath()
|
||||
return cmdObj.
|
||||
AddEnvVars(
|
||||
daemon.DaemonKindEnvKey+"="+string(daemon.ExitImmediately),
|
||||
"GIT_EDITOR="+lazyGitPath,
|
||||
"GIT_SEQUENCE_EDITOR="+lazyGitPath,
|
||||
"EDITOR="+lazyGitPath,
|
||||
"VISUAL="+lazyGitPath,
|
||||
).
|
||||
AddEnvVars(daemon.ToEnvVars(instruction)...).
|
||||
Run()
|
||||
}
|
||||
|
||||
@@ -363,9 +353,7 @@ 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)
|
||||
cmdStr := NewGitCmd("cat-file").Arg("-e", "HEAD^:"+self.cmd.Quote(fileName)).ToString()
|
||||
|
||||
if err := self.cmd.New(cmdStr).Run(); err != nil {
|
||||
if err := self.cmd.New("git cat-file -e HEAD^:" + self.cmd.Quote(fileName)).Run(); err != nil {
|
||||
if err := self.os.Remove(fileName); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -388,15 +376,42 @@ func (self *RebaseCommands) DiscardOldFileChanges(commits []*models.Commit, comm
|
||||
|
||||
// CherryPickCommits begins an interactive rebase with the given shas being cherry picked onto HEAD
|
||||
func (self *RebaseCommands) CherryPickCommits(commits []*models.Commit) error {
|
||||
commitLines := lo.Map(commits, func(commit *models.Commit, _ int) string {
|
||||
return fmt.Sprintf("%s %s", utils.ShortSha(commit.Sha), commit.Name)
|
||||
})
|
||||
self.os.LogCommand(fmt.Sprintf("Cherry-picking commits:\n%s", strings.Join(commitLines, "\n")), false)
|
||||
todoLines := self.BuildTodoLinesSingleAction(commits, "pick")
|
||||
|
||||
return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{
|
||||
baseShaOrRoot: "HEAD",
|
||||
instruction: daemon.NewCherryPickCommitsInstruction(commits),
|
||||
}).Run()
|
||||
return self.PrepareInteractiveRebaseCommand("HEAD", todoLines, false).Run()
|
||||
}
|
||||
|
||||
func (self *RebaseCommands) buildTodo(todoLines []TodoLine) string {
|
||||
lines := slices.Map(todoLines, func(todoLine TodoLine) string {
|
||||
return todoLine.ToString()
|
||||
})
|
||||
|
||||
return strings.Join(slices.Reverse(lines), "")
|
||||
}
|
||||
|
||||
func (self *RebaseCommands) BuildTodoLines(commits []*models.Commit, f func(*models.Commit, int) string) []TodoLine {
|
||||
return slices.MapWithIndex(commits, func(commit *models.Commit, i int) TodoLine {
|
||||
return TodoLine{Action: f(commit, i), Commit: commit}
|
||||
})
|
||||
}
|
||||
|
||||
func (self *RebaseCommands) BuildTodoLinesSingleAction(commits []*models.Commit, action string) []TodoLine {
|
||||
return self.BuildTodoLines(commits, func(commit *models.Commit, i int) string {
|
||||
return action
|
||||
})
|
||||
}
|
||||
|
||||
type TodoLine struct {
|
||||
Action string
|
||||
Commit *models.Commit
|
||||
}
|
||||
|
||||
func (self *TodoLine) ToString() string {
|
||||
if self.Action == "break" {
|
||||
return self.Action + "\n"
|
||||
} else {
|
||||
return self.Action + " " + self.Commit.Sha + " " + self.Commit.Name + "\n"
|
||||
}
|
||||
}
|
||||
|
||||
// we can't start an interactive rebase from the first commit without passing the
|
||||
|
||||
@@ -2,7 +2,6 @@ package git_commands
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/go-errors/errors"
|
||||
@@ -16,60 +15,37 @@ import (
|
||||
|
||||
func TestRebaseRebaseBranch(t *testing.T) {
|
||||
type scenario struct {
|
||||
testName string
|
||||
arg string
|
||||
gitVersion *GitVersion
|
||||
runner *oscommands.FakeCmdObjRunner
|
||||
test func(error)
|
||||
testName string
|
||||
arg string
|
||||
runner *oscommands.FakeCmdObjRunner
|
||||
test func(error)
|
||||
}
|
||||
|
||||
scenarios := []scenario{
|
||||
{
|
||||
testName: "successful rebase",
|
||||
arg: "master",
|
||||
gitVersion: &GitVersion{2, 26, 0, ""},
|
||||
testName: "successful rebase",
|
||||
arg: "master",
|
||||
runner: oscommands.NewFakeRunner(t).
|
||||
Expect(`git rebase --interactive --autostash --keep-empty --empty=keep --no-autosquash --rebase-merges master`, "", nil),
|
||||
Expect(`git rebase --interactive --autostash --keep-empty --empty=keep --no-autosquash master`, "", nil),
|
||||
test: func(err error) {
|
||||
assert.NoError(t, err)
|
||||
},
|
||||
},
|
||||
{
|
||||
testName: "unsuccessful rebase",
|
||||
arg: "master",
|
||||
gitVersion: &GitVersion{2, 26, 0, ""},
|
||||
testName: "unsuccessful rebase",
|
||||
arg: "master",
|
||||
runner: oscommands.NewFakeRunner(t).
|
||||
Expect(`git rebase --interactive --autostash --keep-empty --empty=keep --no-autosquash --rebase-merges master`, "", errors.New("error")),
|
||||
Expect(`git rebase --interactive --autostash --keep-empty --empty=keep --no-autosquash 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, gitVersion: s.gitVersion})
|
||||
instance := buildRebaseCommands(commonDeps{runner: s.runner})
|
||||
s.test(instance.RebaseBranch(s.arg))
|
||||
})
|
||||
}
|
||||
@@ -87,7 +63,7 @@ func TestRebaseSkipEditorCommand(t *testing.T) {
|
||||
`^EDITOR=.*$`,
|
||||
`^GIT_EDITOR=.*$`,
|
||||
`^GIT_SEQUENCE_EDITOR=.*$`,
|
||||
"^" + daemon.DaemonKindEnvKey + "=" + strconv.Itoa(int(daemon.DaemonKindExitImmediately)) + "$",
|
||||
"^" + daemon.DaemonKindEnvKey + "=" + string(daemon.ExitImmediately) + "$",
|
||||
} {
|
||||
regexStr := regexStr
|
||||
foundMatch := lo.ContainsBy(envVars, func(envVar string) bool {
|
||||
@@ -149,7 +125,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 --rebase-merges abcdef`, "", nil).
|
||||
Expect(`git rebase --interactive --autostash --keep-empty --empty=keep --no-autosquash 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).
|
||||
@@ -166,9 +142,8 @@ func TestRebaseDiscardOldFileChanges(t *testing.T) {
|
||||
s := s
|
||||
t.Run(s.testName, func(t *testing.T) {
|
||||
instance := buildRebaseCommands(commonDeps{
|
||||
runner: s.runner,
|
||||
gitVersion: &GitVersion{2, 26, 0, ""},
|
||||
gitConfig: git_config.NewFakeGitConfig(s.gitConfigMockResponses),
|
||||
runner: s.runner,
|
||||
gitConfig: git_config.NewFakeGitConfig(s.gitConfigMockResponses),
|
||||
})
|
||||
|
||||
s.test(instance.DiscardOldFileChanges(s.commits, s.commitIndex, s.fileName))
|
||||
|
||||
@@ -52,7 +52,7 @@ func (self *ReflogCommitLoader) GetReflogCommits(lastReflogCommit *models.Commit
|
||||
Sha: fields[0],
|
||||
Name: fields[2],
|
||||
UnixTimestamp: int64(unixTimestamp),
|
||||
Status: models.StatusReflog,
|
||||
Status: "reflog",
|
||||
Parents: parents,
|
||||
}
|
||||
|
||||
|
||||
@@ -51,35 +51,35 @@ func TestGetReflogCommits(t *testing.T) {
|
||||
{
|
||||
Sha: "c3c4b66b64c97ffeecde",
|
||||
Name: "checkout: moving from A to B",
|
||||
Status: models.StatusReflog,
|
||||
Status: "reflog",
|
||||
UnixTimestamp: 1643150483,
|
||||
Parents: []string{"51baa8c1"},
|
||||
},
|
||||
{
|
||||
Sha: "c3c4b66b64c97ffeecde",
|
||||
Name: "checkout: moving from B to A",
|
||||
Status: models.StatusReflog,
|
||||
Status: "reflog",
|
||||
UnixTimestamp: 1643150483,
|
||||
Parents: []string{"51baa8c1"},
|
||||
},
|
||||
{
|
||||
Sha: "c3c4b66b64c97ffeecde",
|
||||
Name: "checkout: moving from A to B",
|
||||
Status: models.StatusReflog,
|
||||
Status: "reflog",
|
||||
UnixTimestamp: 1643150483,
|
||||
Parents: []string{"51baa8c1"},
|
||||
},
|
||||
{
|
||||
Sha: "c3c4b66b64c97ffeecde",
|
||||
Name: "checkout: moving from master to A",
|
||||
Status: models.StatusReflog,
|
||||
Status: "reflog",
|
||||
UnixTimestamp: 1643150483,
|
||||
Parents: []string{"51baa8c1"},
|
||||
},
|
||||
{
|
||||
Sha: "f4ddf2f0d4be4ccc7efa",
|
||||
Name: "checkout: moving from A to master",
|
||||
Status: models.StatusReflog,
|
||||
Status: "reflog",
|
||||
UnixTimestamp: 1643149435,
|
||||
Parents: []string{"51baa8c1"},
|
||||
},
|
||||
@@ -95,7 +95,7 @@ func TestGetReflogCommits(t *testing.T) {
|
||||
lastReflogCommit: &models.Commit{
|
||||
Sha: "c3c4b66b64c97ffeecde",
|
||||
Name: "checkout: moving from B to A",
|
||||
Status: models.StatusReflog,
|
||||
Status: "reflog",
|
||||
UnixTimestamp: 1643150483,
|
||||
Parents: []string{"51baa8c1"},
|
||||
},
|
||||
@@ -103,7 +103,7 @@ func TestGetReflogCommits(t *testing.T) {
|
||||
{
|
||||
Sha: "c3c4b66b64c97ffeecde",
|
||||
Name: "checkout: moving from A to B",
|
||||
Status: models.StatusReflog,
|
||||
Status: "reflog",
|
||||
UnixTimestamp: 1643150483,
|
||||
Parents: []string{"51baa8c1"},
|
||||
},
|
||||
@@ -119,7 +119,7 @@ func TestGetReflogCommits(t *testing.T) {
|
||||
lastReflogCommit: &models.Commit{
|
||||
Sha: "c3c4b66b64c97ffeecde",
|
||||
Name: "checkout: moving from B to A",
|
||||
Status: models.StatusReflog,
|
||||
Status: "reflog",
|
||||
UnixTimestamp: 1643150483,
|
||||
Parents: []string{"51baa8c1"},
|
||||
},
|
||||
@@ -128,7 +128,7 @@ func TestGetReflogCommits(t *testing.T) {
|
||||
{
|
||||
Sha: "c3c4b66b64c97ffeecde",
|
||||
Name: "checkout: moving from A to B",
|
||||
Status: models.StatusReflog,
|
||||
Status: "reflog",
|
||||
UnixTimestamp: 1643150483,
|
||||
Parents: []string{"51baa8c1"},
|
||||
},
|
||||
|
||||
@@ -15,52 +15,44 @@ func NewRemoteCommands(gitCommon *GitCommon) *RemoteCommands {
|
||||
}
|
||||
|
||||
func (self *RemoteCommands) AddRemote(name string, url string) error {
|
||||
cmdStr := NewGitCmd("remote").
|
||||
Arg("add", self.cmd.Quote(name), self.cmd.Quote(url)).
|
||||
ToString()
|
||||
|
||||
return self.cmd.New(cmdStr).Run()
|
||||
return self.cmd.
|
||||
New(fmt.Sprintf("git remote add %s %s", self.cmd.Quote(name), self.cmd.Quote(url))).
|
||||
Run()
|
||||
}
|
||||
|
||||
func (self *RemoteCommands) RemoveRemote(name string) error {
|
||||
cmdStr := NewGitCmd("remote").
|
||||
Arg("remove", self.cmd.Quote(name)).
|
||||
ToString()
|
||||
|
||||
return self.cmd.New(cmdStr).Run()
|
||||
return self.cmd.
|
||||
New(fmt.Sprintf("git remote remove %s", self.cmd.Quote(name))).
|
||||
Run()
|
||||
}
|
||||
|
||||
func (self *RemoteCommands) RenameRemote(oldRemoteName string, newRemoteName string) error {
|
||||
cmdStr := NewGitCmd("remote").
|
||||
Arg("rename", self.cmd.Quote(oldRemoteName), self.cmd.Quote(newRemoteName)).
|
||||
ToString()
|
||||
|
||||
return self.cmd.New(cmdStr).Run()
|
||||
return self.cmd.
|
||||
New(fmt.Sprintf("git remote rename %s %s", self.cmd.Quote(oldRemoteName), self.cmd.Quote(newRemoteName))).
|
||||
Run()
|
||||
}
|
||||
|
||||
func (self *RemoteCommands) UpdateRemoteUrl(remoteName string, updatedUrl string) error {
|
||||
cmdStr := NewGitCmd("remote").
|
||||
Arg("set-url", self.cmd.Quote(remoteName), self.cmd.Quote(updatedUrl)).
|
||||
ToString()
|
||||
|
||||
return self.cmd.New(cmdStr).Run()
|
||||
return self.cmd.
|
||||
New(fmt.Sprintf("git remote set-url %s %s", self.cmd.Quote(remoteName), self.cmd.Quote(updatedUrl))).
|
||||
Run()
|
||||
}
|
||||
|
||||
func (self *RemoteCommands) DeleteRemoteBranch(remoteName string, branchName string) error {
|
||||
cmdStr := NewGitCmd("push").
|
||||
Arg(self.cmd.Quote(remoteName), "--delete", self.cmd.Quote(branchName)).
|
||||
ToString()
|
||||
|
||||
return self.cmd.New(cmdStr).PromptOnCredentialRequest().WithMutex(self.syncMutex).Run()
|
||||
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()
|
||||
}
|
||||
|
||||
// CheckRemoteBranchExists Returns remote branch
|
||||
func (self *RemoteCommands) CheckRemoteBranchExists(branchName string) bool {
|
||||
cmdStr := NewGitCmd("show-ref").
|
||||
Arg("--verify", "--", fmt.Sprintf("refs/remotes/origin/%s", self.cmd.Quote(branchName))).
|
||||
ToString()
|
||||
|
||||
_, err := self.cmd.New(cmdStr).DontLog().RunWithOutput()
|
||||
_, err := self.cmd.
|
||||
New(
|
||||
fmt.Sprintf("git show-ref --verify -- refs/remotes/origin/%s",
|
||||
self.cmd.Quote(branchName),
|
||||
),
|
||||
).
|
||||
DontLog().
|
||||
RunWithOutput()
|
||||
|
||||
return err == nil
|
||||
}
|
||||
|
||||
@@ -31,8 +31,7 @@ func NewRemoteLoader(
|
||||
}
|
||||
|
||||
func (self *RemoteLoader) GetRemotes() ([]*models.Remote, error) {
|
||||
cmdStr := NewGitCmd("branch").Arg("-r").ToString()
|
||||
remoteBranchesStr, err := self.cmd.New(cmdStr).DontLog().RunWithOutput()
|
||||
remoteBranchesStr, err := self.cmd.New("git branch -r").DontLog().RunWithOutput()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -26,94 +26,57 @@ func NewStashCommands(
|
||||
}
|
||||
|
||||
func (self *StashCommands) DropNewest() error {
|
||||
cmdStr := NewGitCmd("stash").Arg("drop").ToString()
|
||||
|
||||
return self.cmd.New(cmdStr).Run()
|
||||
return self.cmd.New("git stash drop").Run()
|
||||
}
|
||||
|
||||
func (self *StashCommands) Drop(index int) error {
|
||||
cmdStr := NewGitCmd("stash").Arg("drop", fmt.Sprintf("stash@{%d}", index)).
|
||||
ToString()
|
||||
|
||||
return self.cmd.New(cmdStr).Run()
|
||||
return self.cmd.New(fmt.Sprintf("git stash drop stash@{%d}", index)).Run()
|
||||
}
|
||||
|
||||
func (self *StashCommands) Pop(index int) error {
|
||||
cmdStr := NewGitCmd("stash").Arg("pop", fmt.Sprintf("stash@{%d}", index)).
|
||||
ToString()
|
||||
|
||||
return self.cmd.New(cmdStr).Run()
|
||||
return self.cmd.New(fmt.Sprintf("git stash pop stash@{%d}", index)).Run()
|
||||
}
|
||||
|
||||
func (self *StashCommands) Apply(index int) error {
|
||||
cmdStr := NewGitCmd("stash").Arg("apply", fmt.Sprintf("stash@{%d}", index)).
|
||||
ToString()
|
||||
|
||||
return self.cmd.New(cmdStr).Run()
|
||||
return self.cmd.New(fmt.Sprintf("git stash apply stash@{%d}", index)).Run()
|
||||
}
|
||||
|
||||
// Save save stash
|
||||
func (self *StashCommands) Save(message string) error {
|
||||
cmdStr := NewGitCmd("stash").Arg("save", self.cmd.Quote(message)).
|
||||
ToString()
|
||||
|
||||
return self.cmd.New(cmdStr).Run()
|
||||
return self.cmd.New("git stash save " + self.cmd.Quote(message)).Run()
|
||||
}
|
||||
|
||||
func (self *StashCommands) Store(sha string, message string) error {
|
||||
trimmedMessage := strings.Trim(message, " \t")
|
||||
|
||||
cmdStr := NewGitCmd("stash").Arg("store", self.cmd.Quote(sha)).
|
||||
ArgIf(trimmedMessage != "", "-m", self.cmd.Quote(trimmedMessage)).
|
||||
ToString()
|
||||
|
||||
return self.cmd.New(cmdStr).Run()
|
||||
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()
|
||||
}
|
||||
|
||||
func (self *StashCommands) Sha(index int) (string, error) {
|
||||
cmdStr := NewGitCmd("rev-parse").
|
||||
Arg(fmt.Sprintf("refs/stash@{%d}", index)).
|
||||
ToString()
|
||||
|
||||
sha, _, err := self.cmd.New(cmdStr).DontLog().RunWithOutputs()
|
||||
sha, _, err := self.cmd.New(fmt.Sprintf("git rev-parse refs/stash@{%d}", index)).DontLog().RunWithOutputs()
|
||||
return strings.Trim(sha, "\r\n"), err
|
||||
}
|
||||
|
||||
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()
|
||||
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)
|
||||
|
||||
return self.cmd.New(cmdStr).DontLog()
|
||||
}
|
||||
|
||||
func (self *StashCommands) StashAndKeepIndex(message string) error {
|
||||
cmdStr := NewGitCmd("stash").Arg("save", self.cmd.Quote(message), "--keep-index").
|
||||
ToString()
|
||||
|
||||
return self.cmd.New(cmdStr).Run()
|
||||
return self.cmd.New(fmt.Sprintf("git stash save %s --keep-index", self.cmd.Quote(message))).Run()
|
||||
}
|
||||
|
||||
func (self *StashCommands) StashUnstagedChanges(message string) error {
|
||||
if err := self.cmd.New(
|
||||
NewGitCmd("commit").
|
||||
Arg("--no-verify", "-m", self.cmd.Quote("[lazygit] stashing unstaged changes")).
|
||||
ToString(),
|
||||
).Run(); err != nil {
|
||||
if err := self.cmd.New("git commit --no-verify -m \"[lazygit] stashing unstaged changes\"").Run(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := self.Save(message); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := self.cmd.New(
|
||||
NewGitCmd("reset").Arg("--soft", "HEAD^").ToString(),
|
||||
).Run(); err != nil {
|
||||
if err := self.cmd.New("git reset --soft HEAD^").Run(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
@@ -123,9 +86,7 @@ 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(
|
||||
NewGitCmd("stash").Arg("--keep-index").ToString(),
|
||||
).Run(); err != nil {
|
||||
if err := self.cmd.New("git stash --keep-index").Run(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -133,22 +94,15 @@ func (self *StashCommands) SaveStagedChanges(message string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := self.cmd.New(
|
||||
NewGitCmd("stash").Arg("apply", "stash@{1}").ToString(),
|
||||
).Run(); err != nil {
|
||||
if err := self.cmd.New("git stash apply stash@{1}").Run(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := self.os.PipeCommands(
|
||||
NewGitCmd("stash").Arg("show", "-p").ToString(),
|
||||
NewGitCmd("apply").Arg("-R").ToString(),
|
||||
); err != nil {
|
||||
if err := self.os.PipeCommands("git stash show -p", "git apply -R"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := self.cmd.New(
|
||||
NewGitCmd("stash").Arg("drop", "stash@{1}").ToString(),
|
||||
).Run(); err != nil {
|
||||
if err := self.cmd.New("git stash drop stash@{1}").Run(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -170,10 +124,7 @@ func (self *StashCommands) SaveStagedChanges(message string) error {
|
||||
}
|
||||
|
||||
func (self *StashCommands) StashIncludeUntrackedChanges(message string) error {
|
||||
return self.cmd.New(
|
||||
NewGitCmd("stash").Arg("save", self.cmd.Quote(message), "--include-untracked").
|
||||
ToString(),
|
||||
).Run()
|
||||
return self.cmd.New(fmt.Sprintf("git stash save %s --include-untracked", self.cmd.Quote(message))).Run()
|
||||
}
|
||||
|
||||
func (self *StashCommands) Rename(index int, message string) error {
|
||||
|
||||
@@ -32,8 +32,7 @@ func (self *StashLoader) GetStashEntries(filterPath string) []*models.StashEntry
|
||||
return self.getUnfilteredStashEntries()
|
||||
}
|
||||
|
||||
cmdStr := NewGitCmd("stash").Arg("list", "--name-only").ToString()
|
||||
rawString, err := self.cmd.New(cmdStr).DontLog().RunWithOutput()
|
||||
rawString, err := self.cmd.New("git stash list --name-only").DontLog().RunWithOutput()
|
||||
if err != nil {
|
||||
return self.getUnfilteredStashEntries()
|
||||
}
|
||||
@@ -66,9 +65,7 @@ outer:
|
||||
}
|
||||
|
||||
func (self *StashLoader) getUnfilteredStashEntries() []*models.StashEntry {
|
||||
cmdStr := NewGitCmd("stash").Arg("list", "-z", "--pretty='%gs'").ToString()
|
||||
|
||||
rawString, _ := self.cmd.New(cmdStr).DontLog().RunWithOutput()
|
||||
rawString, _ := self.cmd.New("git stash list -z --pretty='%gs'").DontLog().RunWithOutput()
|
||||
return slices.MapWithIndex(utils.SplitNul(rawString), func(line string, index int) *models.StashEntry {
|
||||
return self.stashEntryFromLine(line, index)
|
||||
})
|
||||
|
||||
@@ -99,34 +99,24 @@ func TestStashSha(t *testing.T) {
|
||||
|
||||
func TestStashStashEntryCmdObj(t *testing.T) {
|
||||
type scenario struct {
|
||||
testName string
|
||||
index int
|
||||
contextSize int
|
||||
ignoreWhitespace bool
|
||||
expected string
|
||||
testName string
|
||||
index int
|
||||
contextSize int
|
||||
expected string
|
||||
}
|
||||
|
||||
scenarios := []scenario{
|
||||
{
|
||||
testName: "Default case",
|
||||
index: 5,
|
||||
contextSize: 3,
|
||||
ignoreWhitespace: false,
|
||||
expected: "git stash show -p --stat --color=always --unified=3 stash@{5}",
|
||||
testName: "Default case",
|
||||
index: 5,
|
||||
contextSize: 3,
|
||||
expected: "git stash show -p --stat --color=always --unified=3 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}",
|
||||
testName: "Show diff with custom context size",
|
||||
index: 5,
|
||||
contextSize: 77,
|
||||
expected: "git stash show -p --stat --color=always --unified=77 stash@{5}",
|
||||
},
|
||||
}
|
||||
|
||||
@@ -137,7 +127,7 @@ func TestStashStashEntryCmdObj(t *testing.T) {
|
||||
userConfig.Git.DiffContextSize = s.contextSize
|
||||
instance := buildStashCommands(commonDeps{userConfig: userConfig})
|
||||
|
||||
cmdStr := instance.ShowStashEntryCmdObj(s.index, s.ignoreWhitespace).ToString()
|
||||
cmdStr := instance.ShowStashEntryCmdObj(s.index).ToString()
|
||||
assert.Equal(t, s.expected, cmdStr)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -56,9 +56,7 @@ func (self *StatusCommands) IsBareRepo() (bool, error) {
|
||||
}
|
||||
|
||||
func IsBareRepo(osCommand *oscommands.OSCommand) (bool, error) {
|
||||
res, err := osCommand.Cmd.New(
|
||||
NewGitCmd("rev-parse").Arg("--is-bare-repository").ToString(),
|
||||
).DontLog().RunWithOutput()
|
||||
res, err := osCommand.Cmd.New("git rev-parse --is-bare-repository").DontLog().RunWithOutput()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package git_commands
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
@@ -81,60 +82,38 @@ func (self *SubmoduleCommands) Stash(submodule *models.SubmoduleConfig) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
cmdStr := NewGitCmd("stash").
|
||||
RepoPath(self.cmd.Quote(submodule.Path)).
|
||||
Arg("--include-untracked").
|
||||
ToString()
|
||||
|
||||
return self.cmd.New(cmdStr).Run()
|
||||
return self.cmd.New("git -C " + self.cmd.Quote(submodule.Path) + " stash --include-untracked").Run()
|
||||
}
|
||||
|
||||
func (self *SubmoduleCommands) Reset(submodule *models.SubmoduleConfig) error {
|
||||
cmdStr := NewGitCmd("submodule").
|
||||
Arg("update", "--init", "--force", "--", self.cmd.Quote(submodule.Path)).
|
||||
ToString()
|
||||
|
||||
return self.cmd.New(cmdStr).Run()
|
||||
return self.cmd.New("git submodule update --init --force -- " + self.cmd.Quote(submodule.Path)).Run()
|
||||
}
|
||||
|
||||
func (self *SubmoduleCommands) UpdateAll() error {
|
||||
// not doing an --init here because the user probably doesn't want that
|
||||
cmdStr := NewGitCmd("submodule").Arg("update", "--force").ToString()
|
||||
|
||||
return self.cmd.New(cmdStr).Run()
|
||||
return self.cmd.New("git submodule update --force").Run()
|
||||
}
|
||||
|
||||
func (self *SubmoduleCommands) Delete(submodule *models.SubmoduleConfig) error {
|
||||
// based on https://gist.github.com/myusuf3/7f645819ded92bda6677
|
||||
|
||||
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 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("config").
|
||||
Arg("--file", ".gitmodules", "--remove-section", "submodule."+self.cmd.Quote(submodule.Path)).
|
||||
ToString(),
|
||||
).Run(); err != nil {
|
||||
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("--remove-section", "submodule."+self.cmd.Quote(submodule.Path)).
|
||||
ToString(),
|
||||
).Run(); err != nil {
|
||||
// if there's an error here about it not existing then we'll just continue to do `git rm`
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := self.cmd.New(
|
||||
NewGitCmd("rm").Arg("--force", "-r", submodule.Path).ToString(),
|
||||
).Run(); err != nil {
|
||||
if err := self.cmd.New("git rm --force -r " + submodule.Path).Run(); err != nil {
|
||||
// if the directory isn't there then that's fine
|
||||
self.Log.Error(err)
|
||||
}
|
||||
@@ -143,35 +122,24 @@ func (self *SubmoduleCommands) Delete(submodule *models.SubmoduleConfig) error {
|
||||
}
|
||||
|
||||
func (self *SubmoduleCommands) Add(name string, path string, url string) error {
|
||||
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()
|
||||
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()
|
||||
}
|
||||
|
||||
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(setUrlCmdStr).Run(); err != nil {
|
||||
if err := self.cmd.New("git config --file .gitmodules submodule." + self.cmd.Quote(name) + ".url " + self.cmd.Quote(newUrl)).Run(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
syncCmdStr := NewGitCmd("submodule").Arg("sync", "--", self.cmd.Quote(path)).
|
||||
ToString()
|
||||
|
||||
if err := self.cmd.New(syncCmdStr).Run(); err != nil {
|
||||
if err := self.cmd.New("git submodule sync -- " + self.cmd.Quote(path)).Run(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -179,45 +147,27 @@ func (self *SubmoduleCommands) UpdateUrl(name string, path string, newUrl string
|
||||
}
|
||||
|
||||
func (self *SubmoduleCommands) Init(path string) error {
|
||||
cmdStr := NewGitCmd("submodule").Arg("init", "--", self.cmd.Quote(path)).
|
||||
ToString()
|
||||
|
||||
return self.cmd.New(cmdStr).Run()
|
||||
return self.cmd.New("git submodule init -- " + self.cmd.Quote(path)).Run()
|
||||
}
|
||||
|
||||
func (self *SubmoduleCommands) Update(path string) error {
|
||||
cmdStr := NewGitCmd("submodule").Arg("update", "--init", "--", self.cmd.Quote(path)).
|
||||
ToString()
|
||||
|
||||
return self.cmd.New(cmdStr).Run()
|
||||
return self.cmd.New("git submodule update --init -- " + self.cmd.Quote(path)).Run()
|
||||
}
|
||||
|
||||
func (self *SubmoduleCommands) BulkInitCmdObj() oscommands.ICmdObj {
|
||||
cmdStr := NewGitCmd("submodule").Arg("init").
|
||||
ToString()
|
||||
|
||||
return self.cmd.New(cmdStr)
|
||||
return self.cmd.New("git submodule init")
|
||||
}
|
||||
|
||||
func (self *SubmoduleCommands) BulkUpdateCmdObj() oscommands.ICmdObj {
|
||||
cmdStr := NewGitCmd("submodule").Arg("update").
|
||||
ToString()
|
||||
|
||||
return self.cmd.New(cmdStr)
|
||||
return self.cmd.New("git submodule update")
|
||||
}
|
||||
|
||||
func (self *SubmoduleCommands) ForceBulkUpdateCmdObj() oscommands.ICmdObj {
|
||||
cmdStr := NewGitCmd("submodule").Arg("update", "--force").
|
||||
ToString()
|
||||
|
||||
return self.cmd.New(cmdStr)
|
||||
return self.cmd.New("git submodule update --force")
|
||||
}
|
||||
|
||||
func (self *SubmoduleCommands) BulkDeinitCmdObj() oscommands.ICmdObj {
|
||||
cmdStr := NewGitCmd("submodule").Arg("deinit", "--all", "--force").
|
||||
ToString()
|
||||
|
||||
return self.cmd.New(cmdStr)
|
||||
return self.cmd.New("git submodule deinit --all --force")
|
||||
}
|
||||
|
||||
func (self *SubmoduleCommands) ResetSubmodules(submodules []*models.SubmoduleConfig) error {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package git_commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/go-errors/errors"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
|
||||
)
|
||||
@@ -24,16 +26,30 @@ type PushOpts struct {
|
||||
}
|
||||
|
||||
func (self *SyncCommands) PushCmdObj(opts PushOpts) (oscommands.ICmdObj, error) {
|
||||
if opts.UpstreamBranch != "" && opts.UpstreamRemote == "" {
|
||||
return nil, errors.New(self.Tr.MustSpecifyOriginError)
|
||||
cmdStr := "git push"
|
||||
|
||||
if opts.Force {
|
||||
if self.version.IsOlderThan(2, 30, 0) {
|
||||
cmdStr += " --force-with-lease"
|
||||
} else {
|
||||
cmdStr += " --force-with-lease --force-if-includes"
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
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)
|
||||
}
|
||||
|
||||
cmdObj := self.cmd.New(cmdStr).PromptOnCredentialRequest().WithMutex(self.syncMutex)
|
||||
return cmdObj, nil
|
||||
@@ -56,10 +72,14 @@ type FetchOptions struct {
|
||||
|
||||
// Fetch fetch git repo
|
||||
func (self *SyncCommands) Fetch(opts FetchOptions) error {
|
||||
cmdStr := NewGitCmd("fetch").
|
||||
ArgIf(opts.RemoteName != "", self.cmd.Quote(opts.RemoteName)).
|
||||
ArgIf(opts.BranchName != "", self.cmd.Quote(opts.BranchName)).
|
||||
ToString()
|
||||
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))
|
||||
}
|
||||
|
||||
cmdObj := self.cmd.New(cmdStr)
|
||||
if opts.Background {
|
||||
@@ -77,12 +97,18 @@ type PullOptions struct {
|
||||
}
|
||||
|
||||
func (self *SyncCommands) Pull(opts PullOptions) error {
|
||||
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()
|
||||
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))
|
||||
}
|
||||
|
||||
// setting GIT_SEQUENCE_EDITOR to ':' as a way of skipping it, in case the user
|
||||
// has 'pull.rebase = interactive' configured.
|
||||
@@ -90,18 +116,11 @@ func (self *SyncCommands) Pull(opts PullOptions) error {
|
||||
}
|
||||
|
||||
func (self *SyncCommands) FastForward(branchName string, remoteName string, remoteBranchName string) error {
|
||||
cmdStr := NewGitCmd("fetch").
|
||||
Arg(self.cmd.Quote(remoteName)).
|
||||
Arg(self.cmd.Quote(remoteBranchName) + ":" + self.cmd.Quote(branchName)).
|
||||
ToString()
|
||||
|
||||
cmdStr := fmt.Sprintf("git fetch %s %s:%s", self.cmd.Quote(remoteName), self.cmd.Quote(remoteBranchName), self.cmd.Quote(branchName))
|
||||
return self.cmd.New(cmdStr).PromptOnCredentialRequest().WithMutex(self.syncMutex).Run()
|
||||
}
|
||||
|
||||
func (self *SyncCommands) FetchRemote(remoteName string) error {
|
||||
cmdStr := NewGitCmd("fetch").
|
||||
Arg(self.cmd.Quote(remoteName)).
|
||||
ToString()
|
||||
|
||||
cmdStr := fmt.Sprintf("git fetch %s", self.cmd.Quote(remoteName))
|
||||
return self.cmd.New(cmdStr).PromptOnCredentialRequest().WithMutex(self.syncMutex).Run()
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
func TestSyncPush(t *testing.T) {
|
||||
type scenario struct {
|
||||
testName string
|
||||
version *GitVersion
|
||||
opts PushOpts
|
||||
test func(oscommands.ICmdObj, error)
|
||||
}
|
||||
@@ -17,6 +18,7 @@ func TestSyncPush(t *testing.T) {
|
||||
scenarios := []scenario{
|
||||
{
|
||||
testName: "Push with force disabled",
|
||||
version: &GitVersion{2, 29, 3, ""},
|
||||
opts: PushOpts{Force: false},
|
||||
test: func(cmdObj oscommands.ICmdObj, err error) {
|
||||
assert.Equal(t, cmdObj.ToString(), "git push")
|
||||
@@ -25,14 +27,25 @@ func TestSyncPush(t *testing.T) {
|
||||
},
|
||||
{
|
||||
testName: "Push with force enabled",
|
||||
version: &GitVersion{2, 29, 3, ""},
|
||||
opts: PushOpts{Force: true},
|
||||
test: func(cmdObj oscommands.ICmdObj, err error) {
|
||||
assert.Equal(t, cmdObj.ToString(), "git push --force-with-lease")
|
||||
assert.NoError(t, err)
|
||||
},
|
||||
},
|
||||
{
|
||||
testName: "Push with force enabled (>= 2.30.0)",
|
||||
version: &GitVersion{2, 30, 0, ""},
|
||||
opts: PushOpts{Force: true},
|
||||
test: func(cmdObj oscommands.ICmdObj, err error) {
|
||||
assert.Equal(t, cmdObj.ToString(), "git push --force-with-lease --force-if-includes")
|
||||
assert.NoError(t, err)
|
||||
},
|
||||
},
|
||||
{
|
||||
testName: "Push with force disabled, upstream supplied",
|
||||
version: &GitVersion{2, 29, 3, ""},
|
||||
opts: PushOpts{
|
||||
Force: false,
|
||||
UpstreamRemote: "origin",
|
||||
@@ -45,6 +58,7 @@ func TestSyncPush(t *testing.T) {
|
||||
},
|
||||
{
|
||||
testName: "Push with force disabled, setting upstream",
|
||||
version: &GitVersion{2, 29, 3, ""},
|
||||
opts: PushOpts{
|
||||
Force: false,
|
||||
UpstreamRemote: "origin",
|
||||
@@ -58,6 +72,7 @@ func TestSyncPush(t *testing.T) {
|
||||
},
|
||||
{
|
||||
testName: "Push with force enabled, setting upstream",
|
||||
version: &GitVersion{2, 29, 3, ""},
|
||||
opts: PushOpts{
|
||||
Force: true,
|
||||
UpstreamRemote: "origin",
|
||||
@@ -69,8 +84,23 @@ func TestSyncPush(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
},
|
||||
},
|
||||
{
|
||||
testName: "Push with force enabled, setting upstream (>= 2.30.0)",
|
||||
version: &GitVersion{2, 30, 0, ""},
|
||||
opts: PushOpts{
|
||||
Force: true,
|
||||
UpstreamRemote: "origin",
|
||||
UpstreamBranch: "master",
|
||||
SetUpstream: true,
|
||||
},
|
||||
test: func(cmdObj oscommands.ICmdObj, err error) {
|
||||
assert.Equal(t, cmdObj.ToString(), `git push --force-with-lease --force-if-includes --set-upstream "origin" "master"`)
|
||||
assert.NoError(t, err)
|
||||
},
|
||||
},
|
||||
{
|
||||
testName: "Push with remote branch but no origin",
|
||||
version: &GitVersion{2, 29, 3, ""},
|
||||
opts: PushOpts{
|
||||
Force: true,
|
||||
UpstreamRemote: "",
|
||||
@@ -87,7 +117,7 @@ func TestSyncPush(t *testing.T) {
|
||||
for _, s := range scenarios {
|
||||
s := s
|
||||
t.Run(s.testName, func(t *testing.T) {
|
||||
instance := buildSyncCommands(commonDeps{})
|
||||
instance := buildSyncCommands(commonDeps{gitVersion: s.version})
|
||||
s.test(instance.PushCmdObj(s.opts))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
package git_commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type TagCommands struct {
|
||||
*GitCommon
|
||||
}
|
||||
@@ -11,32 +15,25 @@ func NewTagCommands(gitCommon *GitCommon) *TagCommands {
|
||||
}
|
||||
|
||||
func (self *TagCommands) CreateLightweight(tagName string, ref string) error {
|
||||
cmdStr := NewGitCmd("tag").Arg("--", self.cmd.Quote(tagName)).
|
||||
ArgIf(len(ref) > 0, self.cmd.Quote(ref)).
|
||||
ToString()
|
||||
|
||||
return self.cmd.New(cmdStr).Run()
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
func (self *TagCommands) CreateAnnotated(tagName, ref, msg string) error {
|
||||
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()
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
func (self *TagCommands) Delete(tagName string) error {
|
||||
cmdStr := NewGitCmd("tag").Arg("-d", self.cmd.Quote(tagName)).
|
||||
ToString()
|
||||
|
||||
return self.cmd.New(cmdStr).Run()
|
||||
return self.cmd.New(fmt.Sprintf("git tag -d %s", self.cmd.Quote(tagName))).Run()
|
||||
}
|
||||
|
||||
func (self *TagCommands) Push(remoteName string, tagName string) error {
|
||||
cmdStr := NewGitCmd("push").Arg(self.cmd.Quote(remoteName), "tag", self.cmd.Quote(tagName)).
|
||||
ToString()
|
||||
|
||||
return self.cmd.New(cmdStr).PromptOnCredentialRequest().WithMutex(self.syncMutex).Run()
|
||||
return self.cmd.New(fmt.Sprintf("git push %s %s", self.cmd.Quote(remoteName), self.cmd.Quote(tagName))).PromptOnCredentialRequest().WithMutex(self.syncMutex).Run()
|
||||
}
|
||||
|
||||
@@ -28,8 +28,7 @@ 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
|
||||
cmdStr := NewGitCmd("tag").Arg("--list", "-n", "--sort=-creatordate").ToString()
|
||||
tagsOutput, err := self.cmd.New(cmdStr).DontLog().RunWithOutput()
|
||||
tagsOutput, err := self.cmd.New(`git tag --list -n --sort=-creatordate`).DontLog().RunWithOutput()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ type GitVersion struct {
|
||||
}
|
||||
|
||||
func GetGitVersion(osCommand *oscommands.OSCommand) (*GitVersion, error) {
|
||||
versionStr, _, err := osCommand.Cmd.New(NewGitCmd("--version").ToString()).RunWithOutputs()
|
||||
versionStr, _, err := osCommand.Cmd.New("git --version").RunWithOutputs()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -32,7 +32,7 @@ func ParseGitVersion(versionStr string) (*GitVersion, error) {
|
||||
// versionStr should be something like:
|
||||
// git version 2.39.0
|
||||
// git version 2.37.1 (Apple Git-137.1)
|
||||
re := regexp.MustCompile(`[^\d]*(\d+)(\.\d+)?(\.\d+)?(.*)`)
|
||||
re := regexp.MustCompile(`[^\d]+(\d+)(\.\d+)?(\.\d+)?(.*)`)
|
||||
matches := re.FindStringSubmatch(versionStr)
|
||||
|
||||
if len(matches) < 5 {
|
||||
@@ -65,7 +65,3 @@ func (v *GitVersion) IsOlderThan(major, minor, patch int) bool {
|
||||
required := major*1000*1000 + minor*1000 + patch
|
||||
return actual < required
|
||||
}
|
||||
|
||||
func (v *GitVersion) IsOlderThanVersion(version *GitVersion) bool {
|
||||
return v.IsOlderThan(version.Major, version.Minor, version.Patch)
|
||||
}
|
||||
|
||||
@@ -3,11 +3,15 @@ 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 {
|
||||
@@ -29,7 +33,7 @@ func NewWorkingTreeCommands(
|
||||
}
|
||||
|
||||
func (self *WorkingTreeCommands) OpenMergeToolCmdObj() oscommands.ICmdObj {
|
||||
return self.cmd.New(NewGitCmd("mergetool").ToString())
|
||||
return self.cmd.New("git mergetool")
|
||||
}
|
||||
|
||||
func (self *WorkingTreeCommands) OpenMergeTool() error {
|
||||
@@ -45,37 +49,30 @@ func (self *WorkingTreeCommands) StageFiles(paths []string) error {
|
||||
quotedPaths := slices.Map(paths, func(path string) string {
|
||||
return self.cmd.Quote(path)
|
||||
})
|
||||
|
||||
cmdStr := NewGitCmd("add").Arg("--").Arg(quotedPaths...).ToString()
|
||||
|
||||
return self.cmd.New(cmdStr).Run()
|
||||
return self.cmd.New(fmt.Sprintf("git add -- %s", strings.Join(quotedPaths, " "))).Run()
|
||||
}
|
||||
|
||||
// StageAll stages all files
|
||||
func (self *WorkingTreeCommands) StageAll() error {
|
||||
cmdStr := NewGitCmd("add").Arg("-A").ToString()
|
||||
|
||||
return self.cmd.New(cmdStr).Run()
|
||||
return self.cmd.New("git add -A").Run()
|
||||
}
|
||||
|
||||
// UnstageAll unstages all files
|
||||
func (self *WorkingTreeCommands) UnstageAll() error {
|
||||
return self.cmd.New(NewGitCmd("reset").ToString()).Run()
|
||||
return self.cmd.New("git reset").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 {
|
||||
for _, name := range fileNames {
|
||||
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()
|
||||
}
|
||||
command := "git rm --cached --force -- %s"
|
||||
if reset {
|
||||
command = "git reset HEAD -- %s"
|
||||
}
|
||||
|
||||
err := self.cmd.New(cmdStr).Run()
|
||||
for _, name := range fileNames {
|
||||
err := self.cmd.New(fmt.Sprintf(command, self.cmd.Quote(name))).Run()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -140,31 +137,22 @@ func (self *WorkingTreeCommands) DiscardAllFileChanges(file *models.File) error
|
||||
quotedFileName := self.cmd.Quote(file.Name)
|
||||
|
||||
if file.ShortStatus == "AA" {
|
||||
if err := self.cmd.New(
|
||||
NewGitCmd("checkout").Arg("--ours", "--", quotedFileName).ToString(),
|
||||
).Run(); err != nil {
|
||||
if err := self.cmd.New("git checkout --ours -- " + quotedFileName).Run(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := self.cmd.New(
|
||||
NewGitCmd("add").Arg("--", quotedFileName).ToString(),
|
||||
).Run(); err != nil {
|
||||
if err := self.cmd.New("git add -- " + quotedFileName).Run(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if file.ShortStatus == "DU" {
|
||||
return self.cmd.New(
|
||||
NewGitCmd("rm").Arg("rm", "--", quotedFileName).ToString(),
|
||||
).Run()
|
||||
return self.cmd.New("git rm -- " + quotedFileName).Run()
|
||||
}
|
||||
|
||||
// if the file isn't tracked, we assume you want to delete it
|
||||
if file.HasStagedChanges || file.HasMergeConflicts {
|
||||
if err := self.cmd.New(
|
||||
NewGitCmd("reset").Arg("--", quotedFileName).ToString(),
|
||||
).Run(); err != nil {
|
||||
if err := self.cmd.New("git reset -- " + quotedFileName).Run(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -196,8 +184,7 @@ func (self *WorkingTreeCommands) DiscardUnstagedDirChanges(node IFileNode) error
|
||||
}
|
||||
|
||||
quotedPath := self.cmd.Quote(node.GetPath())
|
||||
cmdStr := NewGitCmd("checkout").Arg("--", quotedPath).ToString()
|
||||
if err := self.cmd.New(cmdStr).Run(); err != nil {
|
||||
if err := self.cmd.New("git checkout -- " + quotedPath).Run(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -222,8 +209,7 @@ func (self *WorkingTreeCommands) RemoveUntrackedDirFiles(node IFileNode) error {
|
||||
// DiscardUnstagedFileChanges directly
|
||||
func (self *WorkingTreeCommands) DiscardUnstagedFileChanges(file *models.File) error {
|
||||
quotedFileName := self.cmd.Quote(file.Name)
|
||||
cmdStr := NewGitCmd("checkout").Arg("--", quotedFileName).ToString()
|
||||
return self.cmd.New(cmdStr).Run()
|
||||
return self.cmd.New("git checkout -- " + quotedFileName).Run()
|
||||
}
|
||||
|
||||
// Ignore adds a file to the gitignore for the repo
|
||||
@@ -244,32 +230,61 @@ 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)
|
||||
}
|
||||
|
||||
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()
|
||||
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)
|
||||
|
||||
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,
|
||||
@@ -281,59 +296,49 @@ 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 {
|
||||
contextSize := self.UserConfig.Git.DiffContextSize
|
||||
|
||||
colorArg := self.UserConfig.Git.Paging.ColorArg
|
||||
contextSize := self.UserConfig.Git.DiffContextSize
|
||||
if plain {
|
||||
colorArg = "never"
|
||||
}
|
||||
|
||||
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()
|
||||
reverseFlag := ""
|
||||
if reverse {
|
||||
reverseFlag = " -R"
|
||||
}
|
||||
|
||||
return self.cmd.New(cmdStr).DontLog()
|
||||
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()
|
||||
}
|
||||
|
||||
// CheckoutFile checks out the file for the given commit
|
||||
func (self *WorkingTreeCommands) CheckoutFile(commitSha, fileName string) error {
|
||||
cmdStr := NewGitCmd("checkout").Arg(commitSha, "--", self.cmd.Quote(fileName)).
|
||||
ToString()
|
||||
|
||||
return self.cmd.New(cmdStr).Run()
|
||||
return self.cmd.New(fmt.Sprintf("git checkout %s -- %s", commitSha, self.cmd.Quote(fileName))).Run()
|
||||
}
|
||||
|
||||
// DiscardAnyUnstagedFileChanges discards any unstaged file changes via `git checkout -- .`
|
||||
// DiscardAnyUnstagedFileChanges discards any unstages file changes via `git checkout -- .`
|
||||
func (self *WorkingTreeCommands) DiscardAnyUnstagedFileChanges() error {
|
||||
cmdStr := NewGitCmd("checkout").Arg("--", ".").
|
||||
ToString()
|
||||
|
||||
return self.cmd.New(cmdStr).Run()
|
||||
return self.cmd.New("git checkout -- .").Run()
|
||||
}
|
||||
|
||||
// RemoveTrackedFiles will delete the given file(s) even if they are currently tracked
|
||||
func (self *WorkingTreeCommands) RemoveTrackedFiles(name string) error {
|
||||
cmdStr := NewGitCmd("rm").Arg("-r", "--cached", "--", self.cmd.Quote(name)).
|
||||
ToString()
|
||||
|
||||
return self.cmd.New(cmdStr).Run()
|
||||
return self.cmd.New("git rm -r --cached -- " + self.cmd.Quote(name)).Run()
|
||||
}
|
||||
|
||||
// RemoveUntrackedFiles runs `git clean -fd`
|
||||
func (self *WorkingTreeCommands) RemoveUntrackedFiles() error {
|
||||
cmdStr := NewGitCmd("clean").Arg("-fd").ToString()
|
||||
|
||||
return self.cmd.New(cmdStr).Run()
|
||||
return self.cmd.New("git clean -fd").Run()
|
||||
}
|
||||
|
||||
// ResetAndClean removes all unstaged changes and removes all untracked files
|
||||
@@ -358,23 +363,23 @@ func (self *WorkingTreeCommands) ResetAndClean() error {
|
||||
|
||||
// ResetHardHead runs `git reset --hard`
|
||||
func (self *WorkingTreeCommands) ResetHard(ref string) error {
|
||||
cmdStr := NewGitCmd("reset").Arg("--hard", self.cmd.Quote(ref)).
|
||||
ToString()
|
||||
|
||||
return self.cmd.New(cmdStr).Run()
|
||||
return self.cmd.New("git reset --hard " + self.cmd.Quote(ref)).Run()
|
||||
}
|
||||
|
||||
// ResetSoft runs `git reset --soft HEAD`
|
||||
func (self *WorkingTreeCommands) ResetSoft(ref string) error {
|
||||
cmdStr := NewGitCmd("reset").Arg("--soft", self.cmd.Quote(ref)).
|
||||
ToString()
|
||||
|
||||
return self.cmd.New(cmdStr).Run()
|
||||
return self.cmd.New("git reset --soft " + self.cmd.Quote(ref)).Run()
|
||||
}
|
||||
|
||||
func (self *WorkingTreeCommands) ResetMixed(ref string) error {
|
||||
cmdStr := NewGitCmd("reset").Arg("--mixed", self.cmd.Quote(ref)).
|
||||
ToString()
|
||||
return self.cmd.New("git reset --mixed " + self.cmd.Quote(ref)).Run()
|
||||
}
|
||||
|
||||
return self.cmd.New(cmdStr).Run()
|
||||
// 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
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@ package git_commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"regexp"
|
||||
"testing"
|
||||
|
||||
"github.com/go-errors/errors"
|
||||
@@ -428,6 +430,60 @@ 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
|
||||
|
||||
@@ -64,22 +64,12 @@ var bitbucketServerServiceDef = ServiceDefinition{
|
||||
repoURLTemplate: "https://{{.webDomain}}/projects/{{.project}}/repos/{{.repo}}",
|
||||
}
|
||||
|
||||
var giteaServiceDef = ServiceDefinition{
|
||||
provider: "gitea",
|
||||
pullRequestURLIntoDefaultBranch: "/compare/{{.From}}",
|
||||
pullRequestURLIntoTargetBranch: "/compare/{{.To}}...{{.From}}",
|
||||
commitURL: "/commit/{{.CommitSha}}",
|
||||
regexStrings: defaultUrlRegexStrings,
|
||||
repoURLTemplate: defaultRepoURLTemplate,
|
||||
}
|
||||
|
||||
var serviceDefinitions = []ServiceDefinition{
|
||||
githubServiceDef,
|
||||
bitbucketServiceDef,
|
||||
gitLabServiceDef,
|
||||
azdoServiceDef,
|
||||
bitbucketServerServiceDef,
|
||||
giteaServiceDef,
|
||||
}
|
||||
|
||||
var defaultServiceDomains = []ServiceDomain{
|
||||
@@ -103,9 +93,4 @@ var defaultServiceDomains = []ServiceDomain{
|
||||
gitDomain: "dev.azure.com",
|
||||
webDomain: "dev.azure.com",
|
||||
},
|
||||
{
|
||||
serviceDefinition: giteaServiceDef,
|
||||
gitDomain: "try.gitea.io",
|
||||
webDomain: "try.gitea.io",
|
||||
},
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
"github.com/jesseduffield/generics/slices"
|
||||
)
|
||||
|
||||
// This package is for handling logic specific to a git hosting service like github, gitlab, bitbucket, gitea, etc.
|
||||
// This package is for handling logic specific to a git hosting service like github, gitlab, bitbucket, etc.
|
||||
// Different git hosting services have different URL formats for when you want to open a PR or view a commit,
|
||||
// and this package's responsibility is to determine which service you're using based on the remote URL,
|
||||
// and then which URL you need for whatever use case you have.
|
||||
|
||||
@@ -264,60 +264,6 @@ func TestGetPullRequestURL(t *testing.T) {
|
||||
assert.Equal(t, "https://mycompany.bitbucket.com/projects/myproject/repos/myrepo/pull-requests?create&targetBranch=dev&sourceBranch=feature%2Fnew", url)
|
||||
},
|
||||
},
|
||||
{
|
||||
testName: "Opens a link to new pull request on Gitea Server (SSH)",
|
||||
from: "feature/new",
|
||||
remoteUrl: "ssh://git@mycompany.gitea.io/myproject/myrepo.git",
|
||||
configServiceDomains: map[string]string{
|
||||
// valid configuration for a gitea server URL
|
||||
"mycompany.gitea.io": "gitea:mycompany.gitea.io",
|
||||
},
|
||||
test: func(url string, err error) {
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "https://mycompany.gitea.io/myproject/myrepo/compare/feature%2Fnew", url)
|
||||
},
|
||||
},
|
||||
{
|
||||
testName: "Opens a link to new pull request on Gitea Server (SSH) with specific target",
|
||||
from: "feature/new",
|
||||
to: "dev",
|
||||
remoteUrl: "ssh://git@mycompany.gitea.io/myproject/myrepo.git",
|
||||
configServiceDomains: map[string]string{
|
||||
// valid configuration for a gitea server URL
|
||||
"mycompany.gitea.io": "gitea:mycompany.gitea.io",
|
||||
},
|
||||
test: func(url string, err error) {
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "https://mycompany.gitea.io/myproject/myrepo/compare/dev...feature%2Fnew", url)
|
||||
},
|
||||
},
|
||||
{
|
||||
testName: "Opens a link to new pull request on Gitea Server (HTTP)",
|
||||
from: "feature/new",
|
||||
remoteUrl: "https://mycompany.gitea.io/myproject/myrepo.git",
|
||||
configServiceDomains: map[string]string{
|
||||
// valid configuration for a gitea server URL
|
||||
"mycompany.gitea.io": "gitea:mycompany.gitea.io",
|
||||
},
|
||||
test: func(url string, err error) {
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "https://mycompany.gitea.io/myproject/myrepo/compare/feature%2Fnew", url)
|
||||
},
|
||||
},
|
||||
{
|
||||
testName: "Opens a link to new pull request on Gitea Server (HTTP) with specific target",
|
||||
from: "feature/new",
|
||||
to: "dev",
|
||||
remoteUrl: "https://mycompany.gitea.io/myproject/myrepo.git",
|
||||
configServiceDomains: map[string]string{
|
||||
// valid configuration for a gitea server URL
|
||||
"mycompany.gitea.io": "gitea:mycompany.gitea.io",
|
||||
},
|
||||
test: func(url string, err error) {
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "https://mycompany.gitea.io/myproject/myrepo/compare/dev...feature%2Fnew", url)
|
||||
},
|
||||
},
|
||||
{
|
||||
testName: "Throws an error if git service is unsupported",
|
||||
from: "feature/divide-operation",
|
||||
@@ -364,7 +310,7 @@ func TestGetPullRequestURL(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "https://bitbucket.org/johndoe/social_network/pull-requests/new?source=feature%2Fprofile-page&t=1", url)
|
||||
},
|
||||
expectedLoggedErrors: []string{"Unknown git service type: 'noservice'. Expected one of github, bitbucket, gitlab, azuredevops, bitbucketServer, gitea"},
|
||||
expectedLoggedErrors: []string{"Unknown git service type: 'noservice'. Expected one of github, bitbucket, gitlab, azuredevops, bitbucketServer"},
|
||||
},
|
||||
{
|
||||
testName: "Escapes reserved URL characters in from branch name",
|
||||
|
||||
@@ -3,37 +3,18 @@ package models
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/fsmiamoto/git-todo-parser/todo"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
)
|
||||
|
||||
// Special commit hash for empty tree object
|
||||
const EmptyTreeCommitHash = "4b825dc642cb6eb9a060e54bf8d69288fbee4904"
|
||||
|
||||
type CommitStatus int
|
||||
|
||||
const (
|
||||
StatusNone CommitStatus = iota
|
||||
StatusUnpushed
|
||||
StatusPushed
|
||||
StatusMerged
|
||||
StatusRebasing
|
||||
StatusSelected
|
||||
StatusReflog
|
||||
)
|
||||
|
||||
const (
|
||||
// Conveniently for us, the todo package starts the enum at 1, and given
|
||||
// that it doesn't have a "none" value, we're setting ours to 0
|
||||
ActionNone todo.TodoCommand = 0
|
||||
)
|
||||
|
||||
// Commit : A git commit
|
||||
type Commit struct {
|
||||
Sha string
|
||||
Name string
|
||||
Status CommitStatus
|
||||
Action todo.TodoCommand
|
||||
Status string // one of "unpushed", "pushed", "merged", "rebasing" or "selected"
|
||||
Action string // one of "", "pick", "edit", "squash", "reword", "drop", "fixup"
|
||||
Tags []string
|
||||
ExtraInfo string // something like 'HEAD -> master, tag: v0.15.2'
|
||||
AuthorName string // something like 'Jesse Duffield'
|
||||
@@ -82,7 +63,7 @@ func (c *Commit) IsMerge() bool {
|
||||
// returns true if this commit is not actually in the git log but instead
|
||||
// is from a TODO file for an interactive rebase.
|
||||
func (c *Commit) IsTODO() bool {
|
||||
return c.Action != ActionNone
|
||||
return c.Action != ""
|
||||
}
|
||||
|
||||
func IsHeadCommit(commits []*Commit, index int) bool {
|
||||
|
||||
@@ -29,6 +29,7 @@ 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)
|
||||
)
|
||||
|
||||
@@ -46,14 +47,17 @@ 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
|
||||
}
|
||||
|
||||
func NewPatchBuilder(log *logrus.Entry, loadFileDiff loadFileDiffFunc) *PatchBuilder {
|
||||
// NewPatchBuilder returns a new PatchBuilder
|
||||
func NewPatchBuilder(log *logrus.Entry, applyPatch applyPatchFunc, loadFileDiff loadFileDiffFunc) *PatchBuilder {
|
||||
return &PatchBuilder{
|
||||
Log: log,
|
||||
applyPatch: applyPatch,
|
||||
loadFileDiff: loadFileDiff,
|
||||
}
|
||||
}
|
||||
@@ -66,20 +70,6 @@ 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"))
|
||||
@@ -244,6 +234,25 @@ 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 = ""
|
||||
|
||||
@@ -35,15 +35,13 @@ 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"),
|
||||
"vim": standardTerminalEditorPreset("vim"),
|
||||
"nvim": standardTerminalEditorPreset("nvim"),
|
||||
"emacs": standardTerminalEditorPreset("emacs"),
|
||||
"nano": standardTerminalEditorPreset("nano"),
|
||||
"kakoune": standardTerminalEditorPreset("kakoune"),
|
||||
"vi": standardTerminalEditorPreset("vi"),
|
||||
"vim": standardTerminalEditorPreset("vim"),
|
||||
"nvim": standardTerminalEditorPreset("nvim"),
|
||||
"emacs": standardTerminalEditorPreset("emacs"),
|
||||
"nano": standardTerminalEditorPreset("nano"),
|
||||
"vscode": {
|
||||
editTemplate: "code --reuse-window -- {{filename}}",
|
||||
editAtLineTemplate: "code --reuse-window --goto -- {{filename}}:{{line}}",
|
||||
@@ -72,7 +70,6 @@ func getPreset(osConfig *OSConfig, guessDefaultEditor func() string) *editPreset
|
||||
|
||||
// Some of our presets have a different name than the editor they are using.
|
||||
editorToPreset := map[string]string{
|
||||
"kak": "kakoune",
|
||||
"code": "vscode",
|
||||
"subl": "sublime",
|
||||
"xed": "xcode",
|
||||
|
||||
+40
-46
@@ -27,34 +27,32 @@ 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"`
|
||||
ExperimentalMarkCommitsWhichChangedFile bool `yaml:"experimentalMarkCommitsWhichChangedFile"`
|
||||
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"`
|
||||
CommandLogSize int `yaml:"commandLogSize"`
|
||||
SplitDiff string `yaml:"splitDiff"`
|
||||
SkipRewordInEditorWarning bool `yaml:"skipRewordInEditorWarning"`
|
||||
WindowSize string `yaml:"windowSize"`
|
||||
Border string `yaml:"border"`
|
||||
}
|
||||
|
||||
type ThemeConfig struct {
|
||||
@@ -77,7 +75,6 @@ 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"`
|
||||
@@ -168,7 +165,6 @@ type KeybindingUniversalConfig struct {
|
||||
Select string `yaml:"select"`
|
||||
GoInto string `yaml:"goInto"`
|
||||
Confirm string `yaml:"confirm"`
|
||||
ConfirmInEditor string `yaml:"confirmInEditor"`
|
||||
Remove string `yaml:"remove"`
|
||||
New string `yaml:"new"`
|
||||
Edit string `yaml:"edit"`
|
||||
@@ -197,6 +193,7 @@ type KeybindingUniversalConfig struct {
|
||||
CopyToClipboard string `yaml:"copyToClipboard"`
|
||||
OpenRecentRepos string `yaml:"openRecentRepos"`
|
||||
SubmitEditorText string `yaml:"submitEditorText"`
|
||||
AppendNewline string `yaml:"appendNewline"`
|
||||
ExtrasMenu string `yaml:"extrasMenu"`
|
||||
ToggleWhitespaceInDiffView string `yaml:"toggleWhitespaceInDiffView"`
|
||||
IncreaseContextInDiffView string `yaml:"increaseContextInDiffView"`
|
||||
@@ -411,20 +408,18 @@ 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",
|
||||
ExperimentalMarkCommitsWhichChangedFile: false,
|
||||
CommitLength: CommitLengthConfig{Show: true},
|
||||
SkipNoStagedFilesWarning: false,
|
||||
ShowListFooter: true,
|
||||
ShowCommandLog: true,
|
||||
ShowBottomLine: true,
|
||||
ShowFileTree: true,
|
||||
ShowRandomTip: true,
|
||||
ShowIcons: false,
|
||||
CommandLogSize: 8,
|
||||
SplitDiff: "auto",
|
||||
SkipRewordInEditorWarning: false,
|
||||
Border: "single",
|
||||
},
|
||||
Git: GitConfig{
|
||||
Paging: PagingConfig{
|
||||
@@ -446,7 +441,6 @@ 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}} --",
|
||||
@@ -498,7 +492,6 @@ func GetDefaultConfig() *UserConfig {
|
||||
Select: "<space>",
|
||||
GoInto: "<enter>",
|
||||
Confirm: "<enter>",
|
||||
ConfirmInEditor: "<a-enter>",
|
||||
Remove: "d",
|
||||
New: "n",
|
||||
Edit: "e",
|
||||
@@ -527,6 +520,7 @@ func GetDefaultConfig() *UserConfig {
|
||||
DiffingMenuAlt: "<c-e>",
|
||||
CopyToClipboard: "<c-o>",
|
||||
SubmitEditorText: "<enter>",
|
||||
AppendNewline: "<a-enter>",
|
||||
ExtrasMenu: "@",
|
||||
ToggleWhitespaceInDiffView: "<c-w>",
|
||||
IncreaseContextInDiffView: "}",
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
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
|
||||
}
|
||||
@@ -1,8 +1,7 @@
|
||||
package helpers
|
||||
package gui
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazycore/pkg/boxlayout"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/constants"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/context"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
@@ -12,33 +11,12 @@ import (
|
||||
// In this file we use the boxlayout package, along with knowledge about the app's state,
|
||||
// to arrange the windows (i.e. panels) on the screen.
|
||||
|
||||
type WindowArrangementHelper struct {
|
||||
c *HelperCommon
|
||||
windowHelper *WindowHelper
|
||||
modeHelper *ModeHelper
|
||||
appStatusHelper *AppStatusHelper
|
||||
}
|
||||
|
||||
func NewWindowArrangementHelper(
|
||||
c *HelperCommon,
|
||||
windowHelper *WindowHelper,
|
||||
modeHelper *ModeHelper,
|
||||
appStatusHelper *AppStatusHelper,
|
||||
) *WindowArrangementHelper {
|
||||
return &WindowArrangementHelper{
|
||||
c: c,
|
||||
windowHelper: windowHelper,
|
||||
modeHelper: modeHelper,
|
||||
appStatusHelper: appStatusHelper,
|
||||
}
|
||||
}
|
||||
|
||||
const INFO_SECTION_PADDING = " "
|
||||
|
||||
func (self *WindowArrangementHelper) GetWindowDimensions(informationStr string, appStatus string) map[string]boxlayout.Dimensions {
|
||||
width, height := self.c.GocuiGui().Size()
|
||||
func (gui *Gui) getWindowDimensions(informationStr string, appStatus string) map[string]boxlayout.Dimensions {
|
||||
width, height := gui.g.Size()
|
||||
|
||||
sideSectionWeight, mainSectionWeight := self.getMidSectionWeights()
|
||||
sideSectionWeight, mainSectionWeight := gui.getMidSectionWeights()
|
||||
|
||||
sidePanelsDirection := boxlayout.COLUMN
|
||||
portraitMode := width <= 84 && height > 45
|
||||
@@ -47,18 +25,13 @@ func (self *WindowArrangementHelper) GetWindowDimensions(informationStr string,
|
||||
}
|
||||
|
||||
mainPanelsDirection := boxlayout.ROW
|
||||
if self.splitMainPanelSideBySide() {
|
||||
if gui.splitMainPanelSideBySide() {
|
||||
mainPanelsDirection = boxlayout.COLUMN
|
||||
}
|
||||
|
||||
extrasWindowSize := self.getExtrasWindowSize(height)
|
||||
extrasWindowSize := gui.getExtrasWindowSize(height)
|
||||
|
||||
self.c.Modes().Filtering.Active()
|
||||
|
||||
showInfoSection := self.c.UserConfig.Gui.ShowBottomLine ||
|
||||
self.c.State().GetRepoState().IsSearching() ||
|
||||
self.modeHelper.IsAnyModeActive() ||
|
||||
self.appStatusHelper.HasStatus()
|
||||
showInfoSection := gui.c.UserConfig.Gui.ShowBottomLine || gui.State.Searching.isSearching || gui.isAnyModeActive() || gui.statusManager.showStatus()
|
||||
infoSectionSize := 0
|
||||
if showInfoSection {
|
||||
infoSectionSize = 1
|
||||
@@ -74,7 +47,7 @@ func (self *WindowArrangementHelper) GetWindowDimensions(informationStr string,
|
||||
{
|
||||
Direction: boxlayout.ROW,
|
||||
Weight: sideSectionWeight,
|
||||
ConditionalChildren: self.sidePanelChildren,
|
||||
ConditionalChildren: gui.sidePanelChildren,
|
||||
},
|
||||
{
|
||||
Direction: boxlayout.ROW,
|
||||
@@ -82,7 +55,7 @@ func (self *WindowArrangementHelper) GetWindowDimensions(informationStr string,
|
||||
Children: []*boxlayout.Box{
|
||||
{
|
||||
Direction: mainPanelsDirection,
|
||||
Children: self.mainSectionChildren(),
|
||||
Children: gui.mainSectionChildren(),
|
||||
Weight: 1,
|
||||
},
|
||||
{
|
||||
@@ -96,7 +69,7 @@ func (self *WindowArrangementHelper) GetWindowDimensions(informationStr string,
|
||||
{
|
||||
Direction: boxlayout.COLUMN,
|
||||
Size: infoSectionSize,
|
||||
Children: self.infoSectionChildren(informationStr, appStatus),
|
||||
Children: gui.infoSectionChildren(informationStr, appStatus),
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -118,12 +91,12 @@ func MergeMaps[K comparable, V any](maps ...map[K]V) map[K]V {
|
||||
return result
|
||||
}
|
||||
|
||||
func (self *WindowArrangementHelper) mainSectionChildren() []*boxlayout.Box {
|
||||
currentWindow := self.windowHelper.CurrentWindow()
|
||||
func (gui *Gui) mainSectionChildren() []*boxlayout.Box {
|
||||
currentWindow := gui.currentWindow()
|
||||
|
||||
// if we're not in split mode we can just show the one main panel. Likewise if
|
||||
// the main panel is focused and we're in full-screen mode
|
||||
if !self.c.State().GetRepoState().GetSplitMainPanel() || (self.c.State().GetRepoState().GetScreenMode() == types.SCREEN_FULL && currentWindow == "main") {
|
||||
if !gui.isMainPanelSplit() || (gui.State.ScreenMode == SCREEN_FULL && currentWindow == "main") {
|
||||
return []*boxlayout.Box{
|
||||
{
|
||||
Window: "main",
|
||||
@@ -144,29 +117,27 @@ func (self *WindowArrangementHelper) mainSectionChildren() []*boxlayout.Box {
|
||||
}
|
||||
}
|
||||
|
||||
func (self *WindowArrangementHelper) getMidSectionWeights() (int, int) {
|
||||
currentWindow := self.windowHelper.CurrentWindow()
|
||||
func (gui *Gui) getMidSectionWeights() (int, int) {
|
||||
currentWindow := gui.currentWindow()
|
||||
|
||||
// we originally specified this as a ratio i.e. .20 would correspond to a weight of 1 against 4
|
||||
sidePanelWidthRatio := self.c.UserConfig.Gui.SidePanelWidth
|
||||
sidePanelWidthRatio := gui.c.UserConfig.Gui.SidePanelWidth
|
||||
// we could make this better by creating ratios like 2:3 rather than always 1:something
|
||||
mainSectionWeight := int(1/sidePanelWidthRatio) - 1
|
||||
sideSectionWeight := 1
|
||||
|
||||
if self.splitMainPanelSideBySide() {
|
||||
if gui.splitMainPanelSideBySide() {
|
||||
mainSectionWeight = 5 // need to shrink side panel to make way for main panels if side-by-side
|
||||
}
|
||||
|
||||
screenMode := self.c.State().GetRepoState().GetScreenMode()
|
||||
|
||||
if currentWindow == "main" {
|
||||
if screenMode == types.SCREEN_HALF || screenMode == types.SCREEN_FULL {
|
||||
if gui.State.ScreenMode == SCREEN_HALF || gui.State.ScreenMode == SCREEN_FULL {
|
||||
sideSectionWeight = 0
|
||||
}
|
||||
} else {
|
||||
if screenMode == types.SCREEN_HALF {
|
||||
if gui.State.ScreenMode == SCREEN_HALF {
|
||||
mainSectionWeight = 1
|
||||
} else if screenMode == types.SCREEN_FULL {
|
||||
} else if gui.State.ScreenMode == SCREEN_FULL {
|
||||
mainSectionWeight = 0
|
||||
}
|
||||
}
|
||||
@@ -174,12 +145,12 @@ func (self *WindowArrangementHelper) getMidSectionWeights() (int, int) {
|
||||
return sideSectionWeight, mainSectionWeight
|
||||
}
|
||||
|
||||
func (self *WindowArrangementHelper) infoSectionChildren(informationStr string, appStatus string) []*boxlayout.Box {
|
||||
if self.c.State().GetRepoState().IsSearching() {
|
||||
func (gui *Gui) infoSectionChildren(informationStr string, appStatus string) []*boxlayout.Box {
|
||||
if gui.State.Searching.isSearching {
|
||||
return []*boxlayout.Box{
|
||||
{
|
||||
Window: "searchPrefix",
|
||||
Size: runewidth.StringWidth(constants.SEARCH_PREFIX),
|
||||
Size: runewidth.StringWidth(SEARCH_PREFIX),
|
||||
},
|
||||
{
|
||||
Window: "search",
|
||||
@@ -191,7 +162,7 @@ func (self *WindowArrangementHelper) infoSectionChildren(informationStr string,
|
||||
appStatusBox := &boxlayout.Box{Window: "appStatus"}
|
||||
optionsBox := &boxlayout.Box{Window: "options"}
|
||||
|
||||
if !self.c.UserConfig.Gui.ShowBottomLine {
|
||||
if !gui.c.UserConfig.Gui.ShowBottomLine {
|
||||
optionsBox.Weight = 0
|
||||
appStatusBox.Weight = 1
|
||||
} else {
|
||||
@@ -201,7 +172,7 @@ func (self *WindowArrangementHelper) infoSectionChildren(informationStr string,
|
||||
|
||||
result := []*boxlayout.Box{appStatusBox, optionsBox}
|
||||
|
||||
if self.c.UserConfig.Gui.ShowBottomLine || self.modeHelper.IsAnyModeActive() {
|
||||
if gui.c.UserConfig.Gui.ShowBottomLine || gui.isAnyModeActive() {
|
||||
result = append(result, &boxlayout.Box{
|
||||
Window: "information",
|
||||
// unlike appStatus, informationStr has various colors so we need to decolorise before taking the length
|
||||
@@ -212,13 +183,13 @@ func (self *WindowArrangementHelper) infoSectionChildren(informationStr string,
|
||||
return result
|
||||
}
|
||||
|
||||
func (self *WindowArrangementHelper) splitMainPanelSideBySide() bool {
|
||||
if !self.c.State().GetRepoState().GetSplitMainPanel() {
|
||||
func (gui *Gui) splitMainPanelSideBySide() bool {
|
||||
if !gui.isMainPanelSplit() {
|
||||
return false
|
||||
}
|
||||
|
||||
mainPanelSplitMode := self.c.UserConfig.Gui.MainPanelSplitMode
|
||||
width, height := self.c.GocuiGui().Size()
|
||||
mainPanelSplitMode := gui.c.UserConfig.Gui.MainPanelSplitMode
|
||||
width, height := gui.g.Size()
|
||||
|
||||
switch mainPanelSplitMode {
|
||||
case "vertical":
|
||||
@@ -234,18 +205,18 @@ func (self *WindowArrangementHelper) splitMainPanelSideBySide() bool {
|
||||
}
|
||||
}
|
||||
|
||||
func (self *WindowArrangementHelper) getExtrasWindowSize(screenHeight int) int {
|
||||
if !self.c.State().GetShowExtrasWindow() {
|
||||
func (gui *Gui) getExtrasWindowSize(screenHeight int) int {
|
||||
if !gui.ShowExtrasWindow {
|
||||
return 0
|
||||
}
|
||||
|
||||
var baseSize int
|
||||
if self.c.CurrentStaticContext().GetKey() == context.COMMAND_LOG_CONTEXT_KEY {
|
||||
if gui.currentStaticContext().GetKey() == context.COMMAND_LOG_CONTEXT_KEY {
|
||||
baseSize = 1000 // my way of saying 'fill the available space'
|
||||
} else if screenHeight < 40 {
|
||||
baseSize = 1
|
||||
} else {
|
||||
baseSize = self.c.UserConfig.Gui.CommandLogSize
|
||||
baseSize = gui.c.UserConfig.Gui.CommandLogSize
|
||||
}
|
||||
|
||||
frameSize := 2
|
||||
@@ -256,15 +227,17 @@ func (self *WindowArrangementHelper) getExtrasWindowSize(screenHeight int) int {
|
||||
// too much space, but if you access it it should take up some space. This is
|
||||
// the default behaviour when accordion mode is NOT in effect. If it is in effect
|
||||
// then when it's accessed it will have weight 2, not 1.
|
||||
func (self *WindowArrangementHelper) getDefaultStashWindowBox() *boxlayout.Box {
|
||||
func (gui *Gui) getDefaultStashWindowBox() *boxlayout.Box {
|
||||
gui.State.ContextManager.RLock()
|
||||
defer gui.State.ContextManager.RUnlock()
|
||||
|
||||
box := &boxlayout.Box{Window: "stash"}
|
||||
stashWindowAccessed := false
|
||||
self.c.Context().ForEach(func(context types.Context) {
|
||||
for _, context := range gui.State.ContextManager.ContextStack {
|
||||
if context.GetWindowName() == "stash" {
|
||||
stashWindowAccessed = true
|
||||
}
|
||||
})
|
||||
|
||||
box := &boxlayout.Box{Window: "stash"}
|
||||
}
|
||||
// if the stash window is anywhere in our stack we should enlargen it
|
||||
if stashWindowAccessed {
|
||||
box.Weight = 1
|
||||
@@ -275,11 +248,10 @@ func (self *WindowArrangementHelper) getDefaultStashWindowBox() *boxlayout.Box {
|
||||
return box
|
||||
}
|
||||
|
||||
func (self *WindowArrangementHelper) sidePanelChildren(width int, height int) []*boxlayout.Box {
|
||||
currentWindow := self.c.CurrentSideContext().GetWindowName()
|
||||
func (gui *Gui) sidePanelChildren(width int, height int) []*boxlayout.Box {
|
||||
currentWindow := gui.currentSideWindowName()
|
||||
|
||||
screenMode := self.c.State().GetRepoState().GetScreenMode()
|
||||
if screenMode == types.SCREEN_FULL || screenMode == types.SCREEN_HALF {
|
||||
if gui.State.ScreenMode == SCREEN_FULL || gui.State.ScreenMode == SCREEN_HALF {
|
||||
fullHeightBox := func(window string) *boxlayout.Box {
|
||||
if window == currentWindow {
|
||||
return &boxlayout.Box{
|
||||
@@ -302,7 +274,7 @@ func (self *WindowArrangementHelper) sidePanelChildren(width int, height int) []
|
||||
fullHeightBox("stash"),
|
||||
}
|
||||
} else if height >= 28 {
|
||||
accordionMode := self.c.UserConfig.Gui.ExpandFocusedSidePanel
|
||||
accordionMode := gui.c.UserConfig.Gui.ExpandFocusedSidePanel
|
||||
accordionBox := func(defaultBox *boxlayout.Box) *boxlayout.Box {
|
||||
if accordionMode && defaultBox.Window == currentWindow {
|
||||
return &boxlayout.Box{
|
||||
@@ -322,7 +294,7 @@ func (self *WindowArrangementHelper) sidePanelChildren(width int, height int) []
|
||||
accordionBox(&boxlayout.Box{Window: "files", Weight: 1}),
|
||||
accordionBox(&boxlayout.Box{Window: "branches", Weight: 1}),
|
||||
accordionBox(&boxlayout.Box{Window: "commits", Weight: 1}),
|
||||
accordionBox(self.getDefaultStashWindowBox()),
|
||||
accordionBox(gui.getDefaultStashWindowBox()),
|
||||
}
|
||||
} else {
|
||||
squashedHeight := 1
|
||||
@@ -353,3 +325,24 @@ func (self *WindowArrangementHelper) sidePanelChildren(width int, height int) []
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (gui *Gui) getCyclableWindows() []string {
|
||||
return []string{"status", "files", "branches", "commits", "stash"}
|
||||
}
|
||||
|
||||
func (gui *Gui) currentSideWindowName() string {
|
||||
// there is always one and only one cyclable context in the context stack. We'll look from top to bottom
|
||||
gui.State.ContextManager.RLock()
|
||||
defer gui.State.ContextManager.RUnlock()
|
||||
|
||||
for idx := range gui.State.ContextManager.ContextStack {
|
||||
reversedIdx := len(gui.State.ContextManager.ContextStack) - 1 - idx
|
||||
context := gui.State.ContextManager.ContextStack[reversedIdx]
|
||||
|
||||
if context.GetKind() == types.SIDE_CONTEXT {
|
||||
return context.GetWindowName()
|
||||
}
|
||||
}
|
||||
|
||||
return "files" // default
|
||||
}
|
||||
+17
-42
@@ -4,33 +4,18 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
)
|
||||
|
||||
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
|
||||
func (gui *Gui) startBackgroundRoutines() {
|
||||
userConfig := gui.UserConfig
|
||||
|
||||
if userConfig.Git.AutoFetch {
|
||||
fetchInterval := userConfig.Refresher.FetchInterval
|
||||
if fetchInterval > 0 {
|
||||
go utils.Safe(self.startBackgroundFetch)
|
||||
go utils.Safe(gui.startBackgroundFetch)
|
||||
} else {
|
||||
self.gui.c.Log.Errorf(
|
||||
gui.c.Log.Errorf(
|
||||
"Value of config option 'refresher.fetchInterval' (%d) is invalid, disabling auto-fetch",
|
||||
fetchInterval)
|
||||
}
|
||||
@@ -39,44 +24,42 @@ func (self *BackgroundRoutineMgr) startBackgroundRoutines() {
|
||||
if userConfig.Git.AutoRefresh {
|
||||
refreshInterval := userConfig.Refresher.RefreshInterval
|
||||
if refreshInterval > 0 {
|
||||
self.goEvery(time.Second*time.Duration(refreshInterval), self.gui.stopChan, func() error {
|
||||
return self.gui.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.FILES}})
|
||||
})
|
||||
gui.goEvery(time.Second*time.Duration(refreshInterval), gui.stopChan, gui.refreshFilesAndSubmodules)
|
||||
} else {
|
||||
self.gui.c.Log.Errorf(
|
||||
gui.c.Log.Errorf(
|
||||
"Value of config option 'refresher.refreshInterval' (%d) is invalid, disabling auto-refresh",
|
||||
refreshInterval)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (self *BackgroundRoutineMgr) startBackgroundFetch() {
|
||||
self.gui.waitForIntro.Wait()
|
||||
isNew := self.gui.IsNewRepo
|
||||
userConfig := self.gui.UserConfig
|
||||
func (gui *Gui) startBackgroundFetch() {
|
||||
gui.waitForIntro.Wait()
|
||||
isNew := gui.IsNewRepo
|
||||
userConfig := gui.UserConfig
|
||||
if !isNew {
|
||||
time.After(time.Duration(userConfig.Refresher.FetchInterval) * time.Second)
|
||||
}
|
||||
err := self.backgroundFetch()
|
||||
err := gui.backgroundFetch()
|
||||
if err != nil && strings.Contains(err.Error(), "exit status 128") && isNew {
|
||||
_ = self.gui.c.Alert(self.gui.c.Tr.NoAutomaticGitFetchTitle, self.gui.c.Tr.NoAutomaticGitFetchBody)
|
||||
_ = gui.c.Alert(gui.c.Tr.NoAutomaticGitFetchTitle, gui.c.Tr.NoAutomaticGitFetchBody)
|
||||
} else {
|
||||
self.goEvery(time.Second*time.Duration(userConfig.Refresher.FetchInterval), self.gui.stopChan, func() error {
|
||||
err := self.backgroundFetch()
|
||||
self.gui.c.Render()
|
||||
gui.goEvery(time.Second*time.Duration(userConfig.Refresher.FetchInterval), gui.stopChan, func() error {
|
||||
err := gui.backgroundFetch()
|
||||
gui.render()
|
||||
return err
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (self *BackgroundRoutineMgr) goEvery(interval time.Duration, stop chan struct{}, function func() error) {
|
||||
func (gui *Gui) 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 self.pauseBackgroundThreads {
|
||||
if gui.PauseBackgroundThreads {
|
||||
continue
|
||||
}
|
||||
_ = function()
|
||||
@@ -86,11 +69,3 @@ func (self *BackgroundRoutineMgr) goEvery(interval time.Duration, stop chan stru
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
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,
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -30,7 +30,6 @@ 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))
|
||||
}
|
||||
|
||||
@@ -47,7 +46,7 @@ func (gui *Gui) LogCommand(cmdStr string, commandLine bool) {
|
||||
// we style it differently to communicate that
|
||||
textStyle = style.FgMagenta
|
||||
}
|
||||
gui.GuiLog = append(gui.GuiLog, cmdStr)
|
||||
gui.CmdLog = append(gui.CmdLog, cmdStr)
|
||||
indentedCmdStr := " " + strings.Replace(cmdStr, "\n", "\n ", -1)
|
||||
fmt.Fprint(gui.Views.Extras, "\n"+textStyle.Sprint(indentedCmdStr))
|
||||
}
|
||||
@@ -104,7 +103,7 @@ func (gui *Gui) getRandomTip() string {
|
||||
),
|
||||
fmt.Sprintf(
|
||||
"to hard reset onto your current upstream branch, press '%s' in the files panel",
|
||||
formattedKey(config.Commits.ViewResetOptions),
|
||||
formattedKey(config.Files.ViewResetOptions),
|
||||
),
|
||||
fmt.Sprintf(
|
||||
"To push a tag, navigate to the tag in the tags tab and press '%s'",
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
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)
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
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),
|
||||
"keyBindNewLine": keybindings.Label(gui.c.UserConfig.Keybinding.Universal.AppendNewline),
|
||||
},
|
||||
)
|
||||
|
||||
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) + " "
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
package gui
|
||||
|
||||
import (
|
||||
"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 {
|
||||
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()
|
||||
}
|
||||
@@ -0,0 +1,317 @@
|
||||
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) getConfirmationPanelDimensions(wrap bool, prompt string) (int, int, int, int) {
|
||||
panelWidth := gui.getConfirmationPanelWidth()
|
||||
panelHeight := gui.getMessageHeight(wrap, prompt, panelWidth)
|
||||
return gui.getConfirmationPanelDimensionsAux(panelWidth, panelHeight)
|
||||
}
|
||||
|
||||
func (gui *Gui) getConfirmationPanelDimensionsForContentHeight(panelWidth, contentHeight int) (int, int, int, int) {
|
||||
return gui.getConfirmationPanelDimensionsAux(panelWidth, contentHeight)
|
||||
}
|
||||
|
||||
func (gui *Gui) getConfirmationPanelDimensionsAux(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.defaultEditor)
|
||||
|
||||
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)
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
package constants
|
||||
|
||||
const SEARCH_PREFIX = "search: "
|
||||
+208
-156
@@ -1,13 +1,14 @@
|
||||
package gui
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sync"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
// This file is for the management of contexts. There is a context stack such that
|
||||
@@ -15,66 +16,46 @@ import (
|
||||
// you in the menu context. When contexts are activated/deactivated certain things need
|
||||
// to happen like showing/hiding views and rendering content.
|
||||
|
||||
type ContextMgr struct {
|
||||
ContextStack []types.Context
|
||||
sync.RWMutex
|
||||
gui *Gui
|
||||
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
|
||||
})
|
||||
|
||||
allContexts *context.ContextTree
|
||||
return slices.Map(popups, func(c types.Context) string {
|
||||
return c.GetViewName()
|
||||
})
|
||||
}
|
||||
|
||||
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
|
||||
// use replaceContext when you don't want to return to the original context upon
|
||||
// hitting escape: you want to go that context's parent instead.
|
||||
func (self *ContextMgr) Replace(c types.Context) error {
|
||||
func (gui *Gui) replaceContext(c types.Context) error {
|
||||
if !c.IsFocusable() {
|
||||
return nil
|
||||
}
|
||||
|
||||
self.Lock()
|
||||
gui.State.ContextManager.Lock()
|
||||
|
||||
if len(self.ContextStack) == 0 {
|
||||
self.ContextStack = []types.Context{c}
|
||||
if len(gui.State.ContextManager.ContextStack) == 0 {
|
||||
gui.State.ContextManager.ContextStack = []types.Context{c}
|
||||
} else {
|
||||
// replace the last item with the given item
|
||||
self.ContextStack = append(self.ContextStack[0:len(self.ContextStack)-1], c)
|
||||
gui.State.ContextManager.ContextStack = append(gui.State.ContextManager.ContextStack[0:len(gui.State.ContextManager.ContextStack)-1], c)
|
||||
}
|
||||
|
||||
defer self.Unlock()
|
||||
defer gui.State.ContextManager.Unlock()
|
||||
|
||||
return self.ActivateContext(c, types.OnFocusOpts{})
|
||||
return gui.activateContext(c, types.OnFocusOpts{})
|
||||
}
|
||||
|
||||
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]
|
||||
}
|
||||
|
||||
func (gui *Gui) pushContext(c types.Context, opts types.OnFocusOpts) error {
|
||||
if !c.IsFocusable() {
|
||||
return nil
|
||||
}
|
||||
|
||||
contextsToDeactivate, contextToActivate := self.pushToContextStack(c)
|
||||
contextsToDeactivate, contextToActivate := gui.pushToContextStack(c)
|
||||
|
||||
for _, contextToDeactivate := range contextsToDeactivate {
|
||||
if err := self.deactivateContext(contextToDeactivate, types.OnFocusLostOpts{NewContextKey: c.GetKey()}); err != nil {
|
||||
if err := gui.deactivateContext(contextToDeactivate, types.OnFocusLostOpts{NewContextKey: c.GetKey()}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -83,45 +64,43 @@ func (self *ContextMgr) Push(c types.Context, opts ...types.OnFocusOpts) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
return self.ActivateContext(contextToActivate, singleOpts)
|
||||
return gui.activateContext(contextToActivate, opts)
|
||||
}
|
||||
|
||||
// Adjusts the context stack based on the context that's being pushed and
|
||||
// returns (contexts to deactivate, context to activate)
|
||||
func (self *ContextMgr) pushToContextStack(c types.Context) ([]types.Context, types.Context) {
|
||||
func (gui *Gui) pushToContextStack(c types.Context) ([]types.Context, types.Context) {
|
||||
contextsToDeactivate := []types.Context{}
|
||||
|
||||
self.Lock()
|
||||
defer self.Unlock()
|
||||
gui.State.ContextManager.Lock()
|
||||
defer gui.State.ContextManager.Unlock()
|
||||
|
||||
if len(self.ContextStack) > 0 &&
|
||||
c.GetKey() == self.ContextStack[len(self.ContextStack)-1].GetKey() {
|
||||
if len(gui.State.ContextManager.ContextStack) > 0 &&
|
||||
c == gui.State.ContextManager.ContextStack[len(gui.State.ContextManager.ContextStack)-1] {
|
||||
// Context being pushed is already on top of the stack: nothing to
|
||||
// deactivate or activate
|
||||
return contextsToDeactivate, nil
|
||||
}
|
||||
|
||||
if len(self.ContextStack) == 0 {
|
||||
self.ContextStack = append(self.ContextStack, c)
|
||||
if len(gui.State.ContextManager.ContextStack) == 0 {
|
||||
gui.State.ContextManager.ContextStack = append(gui.State.ContextManager.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 = lo.Filter(self.ContextStack, func(context types.Context, _ int) bool {
|
||||
return context.GetKey() != c.GetKey()
|
||||
})
|
||||
self.ContextStack = []types.Context{c}
|
||||
contextsToDeactivate = gui.State.ContextManager.ContextStack
|
||||
gui.State.ContextManager.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 self.ContextStack {
|
||||
for _, stackContext := range gui.State.ContextManager.ContextStack {
|
||||
if stackContext.GetKind() == types.MAIN_CONTEXT {
|
||||
contextsToDeactivate = append(contextsToDeactivate, stackContext)
|
||||
} else {
|
||||
contextsToKeep = append(contextsToKeep, stackContext)
|
||||
}
|
||||
}
|
||||
self.ContextStack = append(contextsToKeep, c)
|
||||
gui.State.ContextManager.ContextStack = append(contextsToKeep, c)
|
||||
} else {
|
||||
topContext := self.currentContextWithoutLock()
|
||||
topContext := gui.currentContextWithoutLock()
|
||||
|
||||
// if we're pushing the same context on, we do nothing.
|
||||
if topContext.GetKey() != c.GetKey() {
|
||||
@@ -134,74 +113,44 @@ func (self *ContextMgr) pushToContextStack(c types.Context) ([]types.Context, ty
|
||||
(topContext.GetKind() == types.MAIN_CONTEXT && c.GetKind() == types.MAIN_CONTEXT) {
|
||||
|
||||
contextsToDeactivate = append(contextsToDeactivate, topContext)
|
||||
_, self.ContextStack = slices.Pop(self.ContextStack)
|
||||
_, gui.State.ContextManager.ContextStack = slices.Pop(gui.State.ContextManager.ContextStack)
|
||||
}
|
||||
|
||||
self.ContextStack = append(self.ContextStack, c)
|
||||
gui.State.ContextManager.ContextStack = append(gui.State.ContextManager.ContextStack, c)
|
||||
}
|
||||
}
|
||||
|
||||
return contextsToDeactivate, c
|
||||
}
|
||||
|
||||
func (self *ContextMgr) Pop() error {
|
||||
self.Lock()
|
||||
func (gui *Gui) popContext() error {
|
||||
gui.State.ContextManager.Lock()
|
||||
|
||||
if len(self.ContextStack) == 1 {
|
||||
if len(gui.State.ContextManager.ContextStack) == 1 {
|
||||
// cannot escape from bottommost context
|
||||
self.Unlock()
|
||||
gui.State.ContextManager.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
var currentContext types.Context
|
||||
currentContext, self.ContextStack = slices.Pop(self.ContextStack)
|
||||
currentContext, gui.State.ContextManager.ContextStack = slices.Pop(gui.State.ContextManager.ContextStack)
|
||||
|
||||
newContext := self.ContextStack[len(self.ContextStack)-1]
|
||||
newContext := gui.State.ContextManager.ContextStack[len(gui.State.ContextManager.ContextStack)-1]
|
||||
|
||||
self.Unlock()
|
||||
gui.State.ContextManager.Unlock()
|
||||
|
||||
if err := self.deactivateContext(currentContext, types.OnFocusLostOpts{NewContextKey: newContext.GetKey()}); err != nil {
|
||||
if err := gui.deactivateContext(currentContext, types.OnFocusLostOpts{NewContextKey: newContext.GetKey()}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return self.ActivateContext(newContext, types.OnFocusOpts{})
|
||||
return gui.activateContext(newContext, types.OnFocusOpts{})
|
||||
}
|
||||
|
||||
func (self *ContextMgr) RemoveContexts(contextsToRemove []types.Context) error {
|
||||
self.Lock()
|
||||
|
||||
if len(self.ContextStack) == 1 {
|
||||
self.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
rest := lo.Filter(self.ContextStack, func(context types.Context, _ int) bool {
|
||||
for _, contextToRemove := range contextsToRemove {
|
||||
if context.GetKey() == contextToRemove.GetKey() {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
self.ContextStack = rest
|
||||
contextToActivate := rest[len(rest)-1]
|
||||
self.Unlock()
|
||||
|
||||
for _, context := range contextsToRemove {
|
||||
if err := self.deactivateContext(context, types.OnFocusLostOpts{NewContextKey: contextToActivate.GetKey()}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// activate the item at the top of the stack
|
||||
return self.ActivateContext(contextToActivate, types.OnFocusOpts{})
|
||||
}
|
||||
|
||||
func (self *ContextMgr) deactivateContext(c types.Context, opts types.OnFocusLostOpts) error {
|
||||
view, _ := self.gui.c.GocuiGui().View(c.GetViewName())
|
||||
func (gui *Gui) deactivateContext(c types.Context, opts types.OnFocusLostOpts) error {
|
||||
view, _ := gui.g.View(c.GetViewName())
|
||||
|
||||
if view != nil && view.IsSearching() {
|
||||
if err := self.gui.onSearchEscape(); err != nil {
|
||||
if err := gui.onSearchEscape(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -220,17 +169,34 @@ func (self *ContextMgr) deactivateContext(c types.Context, opts types.OnFocusLos
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *ContextMgr) ActivateContext(c types.Context, opts types.OnFocusOpts) error {
|
||||
// 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 {
|
||||
viewName := c.GetViewName()
|
||||
v, err := self.gui.c.GocuiGui().View(viewName)
|
||||
v, err := gui.g.View(viewName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
self.gui.helpers.Window.SetWindowContext(c)
|
||||
gui.setWindowContext(c)
|
||||
|
||||
self.gui.helpers.Window.MoveToTopOfWindow(c)
|
||||
if _, err := self.gui.c.GocuiGui().SetCurrentView(viewName); err != nil {
|
||||
gui.moveToTopOfWindow(c)
|
||||
if _, err := gui.g.SetCurrentView(viewName); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -241,9 +207,14 @@ func (self *ContextMgr) ActivateContext(c types.Context, opts types.OnFocusOpts)
|
||||
|
||||
v.Visible = true
|
||||
|
||||
self.gui.c.GocuiGui().Cursor = v.Editable
|
||||
gui.g.Cursor = v.Editable
|
||||
|
||||
self.gui.renderContextOptionsMap(c)
|
||||
// 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)
|
||||
|
||||
if err := c.HandleFocus(opts); err != nil {
|
||||
return err
|
||||
@@ -252,27 +223,63 @@ func (self *ContextMgr) ActivateContext(c types.Context, opts types.OnFocusOpts)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *ContextMgr) Current() types.Context {
|
||||
self.RLock()
|
||||
defer self.RUnlock()
|
||||
|
||||
return self.currentContextWithoutLock()
|
||||
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) currentContextWithoutLock() types.Context {
|
||||
if len(self.ContextStack) == 0 {
|
||||
return self.gui.defaultSideContext()
|
||||
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()
|
||||
}
|
||||
|
||||
return self.ContextStack[len(self.ContextStack)-1]
|
||||
return gui.State.ContextManager.ContextStack[len(gui.State.ContextManager.ContextStack)-1]
|
||||
}
|
||||
|
||||
// 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()
|
||||
// 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
|
||||
}
|
||||
|
||||
stack := self.ContextStack
|
||||
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()
|
||||
}
|
||||
|
||||
// find the first context in the stack with the type of types.SIDE_CONTEXT
|
||||
for i := range stack {
|
||||
@@ -283,22 +290,22 @@ func (self *ContextMgr) CurrentSide() types.Context {
|
||||
}
|
||||
}
|
||||
|
||||
return self.gui.defaultSideContext()
|
||||
return gui.defaultSideContext()
|
||||
}
|
||||
|
||||
// static as opposed to popup
|
||||
func (self *ContextMgr) CurrentStatic() types.Context {
|
||||
self.RLock()
|
||||
defer self.RUnlock()
|
||||
func (gui *Gui) currentStaticContext() types.Context {
|
||||
gui.State.ContextManager.RLock()
|
||||
defer gui.State.ContextManager.RUnlock()
|
||||
|
||||
return self.currentStaticContextWithoutLock()
|
||||
return gui.currentStaticContextWithoutLock()
|
||||
}
|
||||
|
||||
func (self *ContextMgr) currentStaticContextWithoutLock() types.Context {
|
||||
stack := self.ContextStack
|
||||
func (gui *Gui) currentStaticContextWithoutLock() types.Context {
|
||||
stack := gui.State.ContextManager.ContextStack
|
||||
|
||||
if len(stack) == 0 {
|
||||
return self.gui.defaultSideContext()
|
||||
return gui.defaultSideContext()
|
||||
}
|
||||
|
||||
// find the first context in the stack without a popup type
|
||||
@@ -310,43 +317,88 @@ func (self *ContextMgr) currentStaticContextWithoutLock() types.Context {
|
||||
}
|
||||
}
|
||||
|
||||
return self.gui.defaultSideContext()
|
||||
return gui.defaultSideContext()
|
||||
}
|
||||
|
||||
func (self *ContextMgr) ForEach(f func(types.Context)) {
|
||||
self.RLock()
|
||||
defer self.RUnlock()
|
||||
|
||||
for _, context := range self.gui.State.ContextMgr.ContextStack {
|
||||
f(context)
|
||||
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) IsCurrent(c types.Context) bool {
|
||||
return self.Current().GetKey() == c.GetKey()
|
||||
}
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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)
|
||||
previousView = newView
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
return listContexts
|
||||
}
|
||||
|
||||
func (self *ContextMgr) AllPatchExplorer() []types.IPatchExplorerContext {
|
||||
var listContexts []types.IPatchExplorerContext
|
||||
|
||||
for _, context := range self.allContexts.Flatten() {
|
||||
if listContext, ok := context.(types.IPatchExplorerContext); ok {
|
||||
listContexts = append(listContexts, listContext)
|
||||
}
|
||||
func (gui *Gui) onViewFocusLost(oldView *gocui.View) error {
|
||||
if oldView == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return listContexts
|
||||
oldView.Highlight = false
|
||||
|
||||
_ = oldView.SetOriginX(0)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) TransientContexts() []types.Context {
|
||||
return slices.Filter(gui.State.Contexts.Flatten(), func(context types.Context) bool {
|
||||
return context.IsTransient()
|
||||
})
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
return context.HandleRender()
|
||||
}
|
||||
|
||||
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
|
||||
// }
|
||||
|
||||
@@ -16,9 +16,6 @@ type BaseContext struct {
|
||||
keybindingsFns []types.KeybindingsFn
|
||||
mouseKeybindingsFns []types.MouseKeybindingsFn
|
||||
onClickFn func() error
|
||||
onRenderToMainFn func() error
|
||||
onFocusFn onFocusFn
|
||||
onFocusLostFn onFocusLostFn
|
||||
|
||||
focusable bool
|
||||
transient bool
|
||||
@@ -28,11 +25,6 @@ type BaseContext struct {
|
||||
*ParentContextMgr
|
||||
}
|
||||
|
||||
type (
|
||||
onFocusFn = func(types.OnFocusOpts) error
|
||||
onFocusLostFn = func(types.OnFocusLostOpts) error
|
||||
)
|
||||
|
||||
var _ types.IBaseContext = &BaseContext{}
|
||||
|
||||
type NewBaseContextOpts struct {
|
||||
@@ -137,36 +129,6 @@ 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 {
|
||||
|
||||
@@ -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)
|
||||
_ types.DiffableContext = (*BranchesContext)(nil)
|
||||
)
|
||||
var _ types.IListContext = (*BranchesContext)(nil)
|
||||
|
||||
func NewBranchesContext(c *ContextCommon) *BranchesContext {
|
||||
viewModel := NewBasicViewModel(func() []*models.Branch { return c.Model().Branches })
|
||||
func NewBranchesContext(
|
||||
getModel func() []*models.Branch,
|
||||
view *gocui.View,
|
||||
getDisplayStrings func(startIdx int, length int) [][]string,
|
||||
|
||||
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,
|
||||
)
|
||||
}
|
||||
onFocus func(types.OnFocusOpts) error,
|
||||
onRenderToMain func() error,
|
||||
onFocusLost func(opts types.OnFocusLostOpts) error,
|
||||
|
||||
self := &BranchesContext{
|
||||
c *types.HelperCommon,
|
||||
) *BranchesContext {
|
||||
viewModel := NewBasicViewModel(getModel)
|
||||
|
||||
return &BranchesContext{
|
||||
BasicViewModel: viewModel,
|
||||
ListContextTrait: &ListContextTrait{
|
||||
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
|
||||
View: c.Views().Branches,
|
||||
View: view,
|
||||
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,16 +63,3 @@ 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
|
||||
}
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
package context
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/generics/slices"
|
||||
"github.com/jesseduffield/gocui"
|
||||
"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"
|
||||
)
|
||||
|
||||
@@ -15,28 +13,20 @@ type CommitFilesContext struct {
|
||||
*DynamicTitleBuilder
|
||||
}
|
||||
|
||||
var (
|
||||
_ types.IListContext = (*CommitFilesContext)(nil)
|
||||
_ types.DiffableContext = (*CommitFilesContext)(nil)
|
||||
)
|
||||
var _ types.IListContext = (*CommitFilesContext)(nil)
|
||||
|
||||
func NewCommitFilesContext(c *ContextCommon) *CommitFilesContext {
|
||||
viewModel := filetree.NewCommitFileTreeViewModel(
|
||||
func() []*models.CommitFile { return c.Model().CommitFiles },
|
||||
c.Log,
|
||||
c.UserConfig.Gui.ShowFileTree,
|
||||
)
|
||||
func NewCommitFilesContext(
|
||||
getModel func() []*models.CommitFile,
|
||||
view *gocui.View,
|
||||
getDisplayStrings func(startIdx int, length int) [][]string,
|
||||
|
||||
getDisplayStrings := func(startIdx int, length int) [][]string {
|
||||
if viewModel.Len() == 0 {
|
||||
return [][]string{{style.FgRed.Sprint("(none)")}}
|
||||
}
|
||||
onFocus func(types.OnFocusOpts) error,
|
||||
onRenderToMain func() error,
|
||||
onFocusLost func(opts types.OnFocusLostOpts) error,
|
||||
|
||||
lines := presentation.RenderCommitFileTree(viewModel, c.Modes().Diffing.Ref, c.Git().Patch.PatchBuilder)
|
||||
return slices.Map(lines, func(line string) []string {
|
||||
return []string{line}
|
||||
})
|
||||
}
|
||||
c *types.HelperCommon,
|
||||
) *CommitFilesContext {
|
||||
viewModel := filetree.NewCommitFileTreeViewModel(getModel, c.Log, c.UserConfig.Gui.ShowFileTree)
|
||||
|
||||
return &CommitFilesContext{
|
||||
CommitFileTreeViewModel: viewModel,
|
||||
@@ -44,14 +34,18 @@ func NewCommitFilesContext(c *ContextCommon) *CommitFilesContext {
|
||||
ListContextTrait: &ListContextTrait{
|
||||
Context: NewSimpleContext(
|
||||
NewBaseContext(NewBaseContextOpts{
|
||||
View: c.Views().CommitFiles,
|
||||
View: view,
|
||||
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,
|
||||
@@ -67,7 +61,3 @@ func (self *CommitFilesContext) GetSelectedItemId() string {
|
||||
|
||||
return item.ID()
|
||||
}
|
||||
|
||||
func (self *CommitFilesContext) GetDiffTerminals() []string {
|
||||
return []string{self.GetRef().RefName()}
|
||||
}
|
||||
|
||||
@@ -1,112 +0,0 @@
|
||||
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
|
||||
const NoCommitIndex = -1
|
||||
|
||||
type CommitMessageViewModel struct {
|
||||
// index of the commit message, where -1 is 'no commit', 0 is the HEAD commit, 1
|
||||
// is the prior commit, and so on
|
||||
selectedindex int
|
||||
// if true, then upon escaping from the commit message panel, we will preserve
|
||||
// the message so that it's still shown next time we open the panel
|
||||
preserveMessage bool
|
||||
// the full preserved message (combined summary and description)
|
||||
preservedMessage string
|
||||
// invoked when pressing enter in the commit message panel
|
||||
onConfirm func(string) error
|
||||
|
||||
// The message typed in before cycling through history
|
||||
// We store this separately to 'preservedMessage' because 'preservedMessage'
|
||||
// is specifically for committing staged files and we don't want this affected
|
||||
// by cycling through history in the context of rewording an old commit.
|
||||
historyMessage string
|
||||
}
|
||||
|
||||
func NewCommitMessageContext(
|
||||
c *ContextCommon,
|
||||
) *CommitMessageContext {
|
||||
viewModel := &CommitMessageViewModel{}
|
||||
return &CommitMessageContext{
|
||||
c: c,
|
||||
viewModel: viewModel,
|
||||
Context: NewSimpleContext(
|
||||
NewBaseContext(NewBaseContextOpts{
|
||||
Kind: types.PERSISTENT_POPUP,
|
||||
View: c.Views().CommitMessage,
|
||||
WindowName: "commitMessage",
|
||||
Key: COMMIT_MESSAGE_CONTEXT_KEY,
|
||||
Focusable: true,
|
||||
HasUncontrolledBounds: true,
|
||||
}),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
func (self *CommitMessageContext) SetSelectedIndex(value int) {
|
||||
self.viewModel.selectedindex = value
|
||||
}
|
||||
|
||||
func (self *CommitMessageContext) GetSelectedIndex() int {
|
||||
return self.viewModel.selectedindex
|
||||
}
|
||||
|
||||
func (self *CommitMessageContext) GetPreserveMessage() bool {
|
||||
return self.viewModel.preserveMessage
|
||||
}
|
||||
|
||||
func (self *CommitMessageContext) GetPreservedMessage() string {
|
||||
return self.viewModel.preservedMessage
|
||||
}
|
||||
|
||||
func (self *CommitMessageContext) SetPreservedMessage(message string) {
|
||||
self.viewModel.preservedMessage = message
|
||||
}
|
||||
|
||||
func (self *CommitMessageContext) GetHistoryMessage() string {
|
||||
return self.viewModel.historyMessage
|
||||
}
|
||||
|
||||
func (self *CommitMessageContext) SetHistoryMessage(message string) {
|
||||
self.viewModel.historyMessage = message
|
||||
}
|
||||
|
||||
func (self *CommitMessageContext) OnConfirm(message string) error {
|
||||
return self.viewModel.onConfirm(message)
|
||||
}
|
||||
|
||||
func (self *CommitMessageContext) SetPanelState(index int, title string, preserveMessage bool, onConfirm func(string) error) {
|
||||
self.viewModel.selectedindex = index
|
||||
self.viewModel.preserveMessage = preserveMessage
|
||||
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) + " "
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
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,
|
||||
})),
|
||||
}
|
||||
}
|
||||
@@ -33,14 +33,13 @@ const (
|
||||
INFORMATION_CONTEXT_KEY types.ContextKey = "information"
|
||||
LIMIT_CONTEXT_KEY types.ContextKey = "limit"
|
||||
|
||||
MENU_CONTEXT_KEY types.ContextKey = "menu"
|
||||
CONFIRMATION_CONTEXT_KEY types.ContextKey = "confirmation"
|
||||
SEARCH_CONTEXT_KEY types.ContextKey = "search"
|
||||
COMMIT_MESSAGE_CONTEXT_KEY types.ContextKey = "commitMessage"
|
||||
COMMIT_DESCRIPTION_CONTEXT_KEY types.ContextKey = "commitDescription"
|
||||
SUBMODULES_CONTEXT_KEY types.ContextKey = "submodules"
|
||||
SUGGESTIONS_CONTEXT_KEY types.ContextKey = "suggestions"
|
||||
COMMAND_LOG_CONTEXT_KEY types.ContextKey = "cmdLog"
|
||||
MENU_CONTEXT_KEY types.ContextKey = "menu"
|
||||
CONFIRMATION_CONTEXT_KEY types.ContextKey = "confirmation"
|
||||
SEARCH_CONTEXT_KEY types.ContextKey = "search"
|
||||
COMMIT_MESSAGE_CONTEXT_KEY types.ContextKey = "commitMessage"
|
||||
SUBMODULES_CONTEXT_KEY types.ContextKey = "submodules"
|
||||
SUGGESTIONS_CONTEXT_KEY types.ContextKey = "suggestions"
|
||||
COMMAND_LOG_CONTEXT_KEY types.ContextKey = "cmdLog"
|
||||
)
|
||||
|
||||
var AllContextKeys = []types.ContextKey{
|
||||
@@ -97,9 +96,8 @@ type ContextTree struct {
|
||||
CustomPatchBuilder *PatchExplorerContext
|
||||
CustomPatchBuilderSecondary types.Context
|
||||
MergeConflicts *MergeConflictsContext
|
||||
Confirmation *ConfirmationContext
|
||||
CommitMessage *CommitMessageContext
|
||||
CommitDescription types.Context
|
||||
Confirmation types.Context
|
||||
CommitMessage types.Context
|
||||
CommandLog types.Context
|
||||
|
||||
// display contexts
|
||||
@@ -131,7 +129,6 @@ func (self *ContextTree) Flatten() []types.Context {
|
||||
self.Menu,
|
||||
self.Confirmation,
|
||||
self.CommitMessage,
|
||||
self.CommitDescription,
|
||||
|
||||
self.MergeConflicts,
|
||||
self.StagingSecondary,
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
package context
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/common"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
)
|
||||
|
||||
type ContextCommon struct {
|
||||
*common.Common
|
||||
types.IGuiCommon
|
||||
}
|
||||
@@ -10,15 +10,11 @@ import (
|
||||
type ListContextTrait struct {
|
||||
types.Context
|
||||
|
||||
c *ContextCommon
|
||||
c *types.HelperCommon
|
||||
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
|
||||
}
|
||||
@@ -54,10 +50,7 @@ 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()),
|
||||
self.columnAlignments,
|
||||
)
|
||||
content := utils.RenderDisplayStrings(self.getDisplayStrings(0, self.list.Len()))
|
||||
self.GetViewTrait().SetContent(content)
|
||||
self.c.Render()
|
||||
self.setFooter()
|
||||
|
||||
@@ -1,12 +1,8 @@
|
||||
package context
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
|
||||
"github.com/jesseduffield/gocui"
|
||||
"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"
|
||||
)
|
||||
|
||||
@@ -15,51 +11,36 @@ type LocalCommitsContext struct {
|
||||
*ViewportListContextTrait
|
||||
}
|
||||
|
||||
var (
|
||||
_ types.IListContext = (*LocalCommitsContext)(nil)
|
||||
_ types.DiffableContext = (*LocalCommitsContext)(nil)
|
||||
)
|
||||
var _ types.IListContext = (*LocalCommitsContext)(nil)
|
||||
|
||||
func NewLocalCommitsContext(c *ContextCommon) *LocalCommitsContext {
|
||||
viewModel := NewLocalCommitsViewModel(
|
||||
func() []*models.Commit { return c.Model().Commits },
|
||||
c,
|
||||
)
|
||||
func NewLocalCommitsContext(
|
||||
getModel func() []*models.Commit,
|
||||
view *gocui.View,
|
||||
getDisplayStrings func(startIdx int, length int) [][]string,
|
||||
|
||||
getDisplayStrings := func(startIdx int, length int) [][]string {
|
||||
selectedCommitSha := ""
|
||||
onFocus func(types.OnFocusOpts) error,
|
||||
onRenderToMain func() error,
|
||||
onFocusLost func(opts types.OnFocusLostOpts) error,
|
||||
|
||||
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,
|
||||
)
|
||||
}
|
||||
c *types.HelperCommon,
|
||||
) *LocalCommitsContext {
|
||||
viewModel := NewLocalCommitsViewModel(getModel, c)
|
||||
|
||||
return &LocalCommitsContext{
|
||||
LocalCommitsViewModel: viewModel,
|
||||
ViewportListContextTrait: &ViewportListContextTrait{
|
||||
ListContextTrait: &ListContextTrait{
|
||||
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
|
||||
View: c.Views().Commits,
|
||||
View: view,
|
||||
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,
|
||||
@@ -88,7 +69,7 @@ type LocalCommitsViewModel struct {
|
||||
showWholeGitGraph bool
|
||||
}
|
||||
|
||||
func NewLocalCommitsViewModel(getModel func() []*models.Commit, c *ContextCommon) *LocalCommitsViewModel {
|
||||
func NewLocalCommitsViewModel(getModel func() []*models.Commit, c *types.HelperCommon) *LocalCommitsViewModel {
|
||||
self := &LocalCommitsViewModel{
|
||||
BasicViewModel: NewBasicViewModel(getModel),
|
||||
limitCommits: true,
|
||||
@@ -110,12 +91,6 @@ 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
|
||||
}
|
||||
@@ -135,50 +110,3 @@ 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,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -2,16 +2,13 @@ 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
|
||||
}
|
||||
@@ -19,26 +16,37 @@ type MenuContext struct {
|
||||
var _ types.IListContext = (*MenuContext)(nil)
|
||||
|
||||
func NewMenuContext(
|
||||
c *ContextCommon,
|
||||
view *gocui.View,
|
||||
|
||||
c *types.HelperCommon,
|
||||
getOptionsMap func() map[string]string,
|
||||
renderToDescriptionView func(string),
|
||||
) *MenuContext {
|
||||
viewModel := NewMenuViewModel(c)
|
||||
viewModel := NewMenuViewModel()
|
||||
|
||||
onFocus := func(types.OnFocusOpts) error {
|
||||
selectedMenuItem := viewModel.GetSelected()
|
||||
renderToDescriptionView(selectedMenuItem.Tooltip)
|
||||
return nil
|
||||
}
|
||||
|
||||
return &MenuContext{
|
||||
c: c,
|
||||
MenuViewModel: viewModel,
|
||||
ListContextTrait: &ListContextTrait{
|
||||
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
|
||||
View: c.Views().Menu,
|
||||
View: view,
|
||||
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},
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -54,15 +62,13 @@ func (self *MenuContext) GetSelectedItemId() string {
|
||||
}
|
||||
|
||||
type MenuViewModel struct {
|
||||
c *ContextCommon
|
||||
menuItems []*types.MenuItem
|
||||
*BasicViewModel[*types.MenuItem]
|
||||
}
|
||||
|
||||
func NewMenuViewModel(c *ContextCommon) *MenuViewModel {
|
||||
func NewMenuViewModel() *MenuViewModel {
|
||||
self := &MenuViewModel{
|
||||
menuItems: nil,
|
||||
c: c,
|
||||
}
|
||||
|
||||
self.BasicViewModel = NewBasicViewModel(func() []*types.MenuItem { return self.menuItems })
|
||||
@@ -82,25 +88,9 @@ func (self *MenuViewModel) GetDisplayStrings(_startIdx int, _length int) [][]str
|
||||
|
||||
return slices.Map(self.menuItems, func(item *types.MenuItem) []string {
|
||||
displayStrings := item.LabelColumns
|
||||
|
||||
if !showKeys {
|
||||
return displayStrings
|
||||
if showKeys {
|
||||
displayStrings = slices.Prepend(displayStrings, style.FgCyan.Sprint(keybindings.LabelFromKey(item.Key)))
|
||||
}
|
||||
|
||||
// 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,6 +3,7 @@ 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"
|
||||
@@ -11,7 +12,7 @@ import (
|
||||
type MergeConflictsContext struct {
|
||||
types.Context
|
||||
viewModel *ConflictsViewModel
|
||||
c *ContextCommon
|
||||
c *types.HelperCommon
|
||||
mutex *deadlock.Mutex
|
||||
}
|
||||
|
||||
@@ -24,7 +25,12 @@ type ConflictsViewModel struct {
|
||||
}
|
||||
|
||||
func NewMergeConflictsContext(
|
||||
c *ContextCommon,
|
||||
view *gocui.View,
|
||||
|
||||
opts ContextCallbackOpts,
|
||||
|
||||
c *types.HelperCommon,
|
||||
getOptionsMap func() map[string]string,
|
||||
) *MergeConflictsContext {
|
||||
viewModel := &ConflictsViewModel{
|
||||
state: mergeconflicts.NewState(),
|
||||
@@ -37,12 +43,14 @@ func NewMergeConflictsContext(
|
||||
Context: NewSimpleContext(
|
||||
NewBaseContext(NewBaseContextOpts{
|
||||
Kind: types.MAIN_CONTEXT,
|
||||
View: c.Views().MergeConflicts,
|
||||
View: view,
|
||||
WindowName: "main",
|
||||
Key: MERGE_CONFLICTS_CONTEXT_KEY,
|
||||
OnGetOptionsMap: getOptionsMap,
|
||||
Focusable: true,
|
||||
HighlightOnFocus: true,
|
||||
}),
|
||||
opts,
|
||||
),
|
||||
c: c,
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ type PatchExplorerContext struct {
|
||||
state *patch_exploring.State
|
||||
viewTrait *ViewTrait
|
||||
getIncludedLineIndices func() []int
|
||||
c *ContextCommon
|
||||
c *types.HelperCommon
|
||||
mutex *deadlock.Mutex
|
||||
}
|
||||
|
||||
@@ -24,9 +24,11 @@ func NewPatchExplorerContext(
|
||||
windowName string,
|
||||
key types.ContextKey,
|
||||
|
||||
onFocus func(types.OnFocusOpts) error,
|
||||
onFocusLost func(opts types.OnFocusLostOpts) error,
|
||||
getIncludedLineIndices func() []int,
|
||||
|
||||
c *ContextCommon,
|
||||
c *types.HelperCommon,
|
||||
) *PatchExplorerContext {
|
||||
return &PatchExplorerContext{
|
||||
state: nil,
|
||||
@@ -41,12 +43,13 @@ 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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
_ types.DiffableContext = (*ReflogCommitsContext)(nil)
|
||||
)
|
||||
var _ types.IListContext = (*ReflogCommitsContext)(nil)
|
||||
|
||||
func NewReflogCommitsContext(c *ContextCommon) *ReflogCommitsContext {
|
||||
viewModel := NewBasicViewModel(func() []*models.Commit { return c.Model().FilteredReflogCommits })
|
||||
func NewReflogCommitsContext(
|
||||
getModel func() []*models.Commit,
|
||||
view *gocui.View,
|
||||
getDisplayStrings func(startIdx int, length int) [][]string,
|
||||
|
||||
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,
|
||||
)
|
||||
}
|
||||
onFocus func(types.OnFocusOpts) error,
|
||||
onRenderToMain func() error,
|
||||
onFocusLost func(opts types.OnFocusLostOpts) error,
|
||||
|
||||
c *types.HelperCommon,
|
||||
) *ReflogCommitsContext {
|
||||
viewModel := NewBasicViewModel(getModel)
|
||||
|
||||
return &ReflogCommitsContext{
|
||||
BasicViewModel: viewModel,
|
||||
ListContextTrait: &ListContextTrait{
|
||||
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
|
||||
View: c.Views().ReflogCommits,
|
||||
View: view,
|
||||
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,9 +71,3 @@ 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}
|
||||
}
|
||||
|
||||
@@ -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,32 +12,37 @@ type RemoteBranchesContext struct {
|
||||
*DynamicTitleBuilder
|
||||
}
|
||||
|
||||
var (
|
||||
_ types.IListContext = (*RemoteBranchesContext)(nil)
|
||||
_ types.DiffableContext = (*RemoteBranchesContext)(nil)
|
||||
)
|
||||
var _ types.IListContext = (*RemoteBranchesContext)(nil)
|
||||
|
||||
func NewRemoteBranchesContext(
|
||||
c *ContextCommon,
|
||||
) *RemoteBranchesContext {
|
||||
viewModel := NewBasicViewModel(func() []*models.RemoteBranch { return c.Model().RemoteBranches })
|
||||
getModel func() []*models.RemoteBranch,
|
||||
view *gocui.View,
|
||||
getDisplayStrings func(startIdx int, length int) [][]string,
|
||||
|
||||
getDisplayStrings := func(startIdx int, length int) [][]string {
|
||||
return presentation.GetRemoteBranchListDisplayStrings(c.Model().RemoteBranches, c.Modes().Diffing.Ref)
|
||||
}
|
||||
onFocus func(types.OnFocusOpts) error,
|
||||
onRenderToMain func() error,
|
||||
onFocusLost func(opts types.OnFocusLostOpts) error,
|
||||
|
||||
c *types.HelperCommon,
|
||||
) *RemoteBranchesContext {
|
||||
viewModel := NewBasicViewModel(getModel)
|
||||
|
||||
return &RemoteBranchesContext{
|
||||
BasicViewModel: viewModel,
|
||||
DynamicTitleBuilder: NewDynamicTitleBuilder(c.Tr.RemoteBranchesDynamicTitle),
|
||||
ListContextTrait: &ListContextTrait{
|
||||
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
|
||||
View: c.Views().RemoteBranches,
|
||||
View: view,
|
||||
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,
|
||||
@@ -61,9 +66,3 @@ func (self *RemoteBranchesContext) GetSelectedRef() types.Ref {
|
||||
}
|
||||
return remoteBranch
|
||||
}
|
||||
|
||||
func (self *RemoteBranchesContext) GetDiffTerminals() []string {
|
||||
itemId := self.GetSelectedItemId()
|
||||
|
||||
return []string{itemId}
|
||||
}
|
||||
|
||||
@@ -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,28 +11,35 @@ type RemotesContext struct {
|
||||
*ListContextTrait
|
||||
}
|
||||
|
||||
var (
|
||||
_ types.IListContext = (*RemotesContext)(nil)
|
||||
_ types.DiffableContext = (*RemotesContext)(nil)
|
||||
)
|
||||
var _ types.IListContext = (*RemotesContext)(nil)
|
||||
|
||||
func NewRemotesContext(c *ContextCommon) *RemotesContext {
|
||||
viewModel := NewBasicViewModel(func() []*models.Remote { return c.Model().Remotes })
|
||||
func NewRemotesContext(
|
||||
getModel func() []*models.Remote,
|
||||
view *gocui.View,
|
||||
getDisplayStrings func(startIdx int, length int) [][]string,
|
||||
|
||||
getDisplayStrings := func(startIdx int, length int) [][]string {
|
||||
return presentation.GetRemoteListDisplayStrings(c.Model().Remotes, c.Modes().Diffing.Ref)
|
||||
}
|
||||
onFocus func(types.OnFocusOpts) error,
|
||||
onRenderToMain func() error,
|
||||
onFocusLost func(opts types.OnFocusLostOpts) error,
|
||||
|
||||
c *types.HelperCommon,
|
||||
) *RemotesContext {
|
||||
viewModel := NewBasicViewModel(getModel)
|
||||
|
||||
return &RemotesContext{
|
||||
BasicViewModel: viewModel,
|
||||
ListContextTrait: &ListContextTrait{
|
||||
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
|
||||
View: c.Views().Remotes,
|
||||
View: view,
|
||||
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,
|
||||
@@ -48,9 +55,3 @@ func (self *RemotesContext) GetSelectedItemId() string {
|
||||
|
||||
return item.ID()
|
||||
}
|
||||
|
||||
func (self *RemotesContext) GetDiffTerminals() []string {
|
||||
itemId := self.GetSelectedItemId()
|
||||
|
||||
return []string{itemId}
|
||||
}
|
||||
|
||||
@@ -1,146 +0,0 @@
|
||||
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"),
|
||||
}
|
||||
}
|
||||
@@ -6,12 +6,29 @@ 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
|
||||
}
|
||||
|
||||
func NewSimpleContext(baseContext *BaseContext) *SimpleContext {
|
||||
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 {
|
||||
return &SimpleContext{
|
||||
BaseContext: baseContext,
|
||||
OnFocus: opts.OnFocus,
|
||||
OnFocusLost: opts.OnFocusLost,
|
||||
OnRender: opts.OnRender,
|
||||
OnRenderToMain: opts.OnRenderToMain,
|
||||
BaseContext: baseContext,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,6 +45,7 @@ func NewDisplayContext(key types.ContextKey, view *gocui.View, windowName string
|
||||
Focusable: false,
|
||||
Transient: false,
|
||||
}),
|
||||
ContextCallbackOpts{},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -36,14 +54,14 @@ func (self *SimpleContext) HandleFocus(opts types.OnFocusOpts) error {
|
||||
self.GetViewTrait().SetHighlight(true)
|
||||
}
|
||||
|
||||
if self.onFocusFn != nil {
|
||||
if err := self.onFocusFn(opts); err != nil {
|
||||
if self.OnFocus != nil {
|
||||
if err := self.OnFocus(opts); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if self.onRenderToMainFn != nil {
|
||||
if err := self.onRenderToMainFn(); err != nil {
|
||||
if self.OnRenderToMain != nil {
|
||||
if err := self.OnRenderToMain(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -52,19 +70,22 @@ func (self *SimpleContext) HandleFocus(opts types.OnFocusOpts) error {
|
||||
}
|
||||
|
||||
func (self *SimpleContext) HandleFocusLost(opts types.OnFocusLostOpts) error {
|
||||
if self.onFocusLostFn != nil {
|
||||
return self.onFocusLostFn(opts)
|
||||
if self.OnFocusLost != nil {
|
||||
return self.OnFocusLost(opts)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *SimpleContext) HandleRender() error {
|
||||
if self.OnRender != nil {
|
||||
return self.OnRender()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *SimpleContext) HandleRenderToMain() error {
|
||||
if self.onRenderToMainFn != nil {
|
||||
return self.onRenderToMainFn()
|
||||
if self.OnRenderToMain != nil {
|
||||
return self.OnRenderToMain()
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -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,30 +11,35 @@ type StashContext struct {
|
||||
*ListContextTrait
|
||||
}
|
||||
|
||||
var (
|
||||
_ types.IListContext = (*StashContext)(nil)
|
||||
_ types.DiffableContext = (*StashContext)(nil)
|
||||
)
|
||||
var _ types.IListContext = (*StashContext)(nil)
|
||||
|
||||
func NewStashContext(
|
||||
c *ContextCommon,
|
||||
) *StashContext {
|
||||
viewModel := NewBasicViewModel(func() []*models.StashEntry { return c.Model().StashEntries })
|
||||
getModel func() []*models.StashEntry,
|
||||
view *gocui.View,
|
||||
getDisplayStrings func(startIdx int, length int) [][]string,
|
||||
|
||||
getDisplayStrings := func(startIdx int, length int) [][]string {
|
||||
return presentation.GetStashEntryListDisplayStrings(c.Model().StashEntries, c.Modes().Diffing.Ref)
|
||||
}
|
||||
onFocus func(types.OnFocusOpts) error,
|
||||
onRenderToMain func() error,
|
||||
onFocusLost func(opts types.OnFocusLostOpts) error,
|
||||
|
||||
c *types.HelperCommon,
|
||||
) *StashContext {
|
||||
viewModel := NewBasicViewModel(getModel)
|
||||
|
||||
return &StashContext{
|
||||
BasicViewModel: viewModel,
|
||||
ListContextTrait: &ListContextTrait{
|
||||
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
|
||||
View: c.Views().Stash,
|
||||
View: view,
|
||||
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,
|
||||
@@ -62,9 +67,3 @@ func (self *StashContext) GetSelectedRef() types.Ref {
|
||||
}
|
||||
return stash
|
||||
}
|
||||
|
||||
func (self *StashContext) GetDiffTerminals() []string {
|
||||
itemId := self.GetSelectedItemId()
|
||||
|
||||
return []string{itemId}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ package context
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
@@ -15,39 +15,23 @@ type SubCommitsContext struct {
|
||||
*DynamicTitleBuilder
|
||||
}
|
||||
|
||||
var (
|
||||
_ types.IListContext = (*SubCommitsContext)(nil)
|
||||
_ types.DiffableContext = (*SubCommitsContext)(nil)
|
||||
)
|
||||
var _ types.IListContext = (*SubCommitsContext)(nil)
|
||||
|
||||
func NewSubCommitsContext(
|
||||
c *ContextCommon,
|
||||
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,
|
||||
) *SubCommitsContext {
|
||||
viewModel := &SubCommitsViewModel{
|
||||
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(),
|
||||
)
|
||||
BasicViewModel: NewBasicViewModel(getModel),
|
||||
ref: nil,
|
||||
limitCommits: true,
|
||||
}
|
||||
|
||||
return &SubCommitsContext{
|
||||
@@ -56,13 +40,17 @@ func NewSubCommitsContext(
|
||||
ViewportListContextTrait: &ViewportListContextTrait{
|
||||
ListContextTrait: &ListContextTrait{
|
||||
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
|
||||
View: c.Views().SubCommits,
|
||||
View: view,
|
||||
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,
|
||||
@@ -123,9 +111,3 @@ 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}
|
||||
}
|
||||
|
||||
@@ -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,23 +13,33 @@ type SubmodulesContext struct {
|
||||
|
||||
var _ types.IListContext = (*SubmodulesContext)(nil)
|
||||
|
||||
func NewSubmodulesContext(c *ContextCommon) *SubmodulesContext {
|
||||
viewModel := NewBasicViewModel(func() []*models.SubmoduleConfig { return c.Model().Submodules })
|
||||
func NewSubmodulesContext(
|
||||
getModel func() []*models.SubmoduleConfig,
|
||||
view *gocui.View,
|
||||
getDisplayStrings func(startIdx int, length int) [][]string,
|
||||
|
||||
getDisplayStrings := func(startIdx int, length int) [][]string {
|
||||
return presentation.GetSubmoduleListDisplayStrings(c.Model().Submodules)
|
||||
}
|
||||
onFocus func(types.OnFocusOpts) error,
|
||||
onRenderToMain func() error,
|
||||
onFocusLost func(opts types.OnFocusLostOpts) error,
|
||||
|
||||
c *types.HelperCommon,
|
||||
) *SubmodulesContext {
|
||||
viewModel := NewBasicViewModel(getModel)
|
||||
|
||||
return &SubmodulesContext{
|
||||
BasicViewModel: viewModel,
|
||||
ListContextTrait: &ListContextTrait{
|
||||
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
|
||||
View: c.Views().Submodules,
|
||||
View: view,
|
||||
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,
|
||||
|
||||
@@ -1,59 +1,45 @@
|
||||
package context
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
|
||||
"github.com/jesseduffield/gocui"
|
||||
"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(
|
||||
c *ContextCommon,
|
||||
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,
|
||||
) *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: c.Views().Suggestions,
|
||||
View: view,
|
||||
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,
|
||||
@@ -69,22 +55,3 @@ 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() {}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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,30 +11,35 @@ type TagsContext struct {
|
||||
*ListContextTrait
|
||||
}
|
||||
|
||||
var (
|
||||
_ types.IListContext = (*TagsContext)(nil)
|
||||
_ types.DiffableContext = (*TagsContext)(nil)
|
||||
)
|
||||
var _ types.IListContext = (*TagsContext)(nil)
|
||||
|
||||
func NewTagsContext(
|
||||
c *ContextCommon,
|
||||
) *TagsContext {
|
||||
viewModel := NewBasicViewModel(func() []*models.Tag { return c.Model().Tags })
|
||||
getModel func() []*models.Tag,
|
||||
view *gocui.View,
|
||||
getDisplayStrings func(startIdx int, length int) [][]string,
|
||||
|
||||
getDisplayStrings := func(startIdx int, length int) [][]string {
|
||||
return presentation.GetTagListDisplayStrings(c.Model().Tags, c.Modes().Diffing.Ref)
|
||||
}
|
||||
onFocus func(types.OnFocusOpts) error,
|
||||
onRenderToMain func() error,
|
||||
onFocusLost func(opts types.OnFocusLostOpts) error,
|
||||
|
||||
c *types.HelperCommon,
|
||||
) *TagsContext {
|
||||
viewModel := NewBasicViewModel(getModel)
|
||||
|
||||
return &TagsContext{
|
||||
BasicViewModel: viewModel,
|
||||
ListContextTrait: &ListContextTrait{
|
||||
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
|
||||
View: c.Views().Tags,
|
||||
View: view,
|
||||
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,
|
||||
@@ -58,9 +63,3 @@ 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, nil)
|
||||
content := utils.RenderDisplayStrings(displayStrings)
|
||||
self.GetViewTrait().SetViewPortContent(content)
|
||||
}
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
package context
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/generics/slices"
|
||||
"github.com/jesseduffield/gocui"
|
||||
"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"
|
||||
)
|
||||
|
||||
@@ -15,30 +14,33 @@ type WorkingTreeContext struct {
|
||||
|
||||
var _ types.IListContext = (*WorkingTreeContext)(nil)
|
||||
|
||||
func NewWorkingTreeContext(c *ContextCommon) *WorkingTreeContext {
|
||||
viewModel := filetree.NewFileTreeViewModel(
|
||||
func() []*models.File { return c.Model().Files },
|
||||
c.Log,
|
||||
c.UserConfig.Gui.ShowFileTree,
|
||||
)
|
||||
func NewWorkingTreeContext(
|
||||
getModel func() []*models.File,
|
||||
view *gocui.View,
|
||||
getDisplayStrings func(startIdx int, length int) [][]string,
|
||||
|
||||
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}
|
||||
})
|
||||
}
|
||||
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)
|
||||
|
||||
return &WorkingTreeContext{
|
||||
FileTreeViewModel: viewModel,
|
||||
ListContextTrait: &ListContextTrait{
|
||||
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
|
||||
View: c.Views().Files,
|
||||
View: view,
|
||||
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,
|
||||
|
||||
+259
-9
@@ -6,11 +6,261 @@ import (
|
||||
)
|
||||
|
||||
func (gui *Gui) contextTree() *context.ContextTree {
|
||||
contextCommon := &context.ContextCommon{
|
||||
IGuiCommon: gui.c.IGuiCommon,
|
||||
Common: gui.c.Common,
|
||||
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.NewSimpleContext(
|
||||
context.NewBaseContext(context.NewBaseContextOpts{
|
||||
Kind: types.PERSISTENT_POPUP,
|
||||
View: gui.Views.CommitMessage,
|
||||
WindowName: "commitMessage",
|
||||
Key: context.COMMIT_MESSAGE_CONTEXT_KEY,
|
||||
Focusable: true,
|
||||
HasUncontrolledBounds: true,
|
||||
}),
|
||||
context.ContextCallbackOpts{
|
||||
OnFocus: OnFocusWrapper(gui.handleCommitMessageFocused),
|
||||
},
|
||||
),
|
||||
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"),
|
||||
}
|
||||
return context.NewContextTree(contextCommon)
|
||||
}
|
||||
|
||||
// using this wrapper for when an onFocus function doesn't care about any potential
|
||||
@@ -21,10 +271,10 @@ func OnFocusWrapper(f func() error) func(opts types.OnFocusOpts) error {
|
||||
}
|
||||
}
|
||||
|
||||
func (gui *Gui) defaultSideContext() types.Context {
|
||||
if gui.State.Modes.Filtering.Active() {
|
||||
return gui.State.Contexts.LocalCommits
|
||||
} else {
|
||||
return gui.State.Contexts.Files
|
||||
func (gui *Gui) getPatchExplorerContexts() []types.IPatchExplorerContext {
|
||||
return []types.IPatchExplorerContext{
|
||||
gui.State.Contexts.Staging,
|
||||
gui.State.Contexts.StagingSecondary,
|
||||
gui.State.Contexts.CustomPatchBuilder,
|
||||
}
|
||||
}
|
||||
|
||||
+79
-160
@@ -7,121 +7,104 @@ 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/gui/status"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/jesseduffield/lazygit/pkg/snake"
|
||||
)
|
||||
|
||||
func (gui *Gui) Helpers() *helpers.Helpers {
|
||||
return gui.helpers
|
||||
}
|
||||
|
||||
func (gui *Gui) resetHelpersAndControllers() {
|
||||
func (gui *Gui) resetControllers() {
|
||||
helperCommon := gui.c
|
||||
refsHelper := helpers.NewRefsHelper(helperCommon)
|
||||
osCommand := gui.os
|
||||
model := gui.State.Model
|
||||
refsHelper := helpers.NewRefsHelper(
|
||||
helperCommon,
|
||||
gui.git,
|
||||
gui.State.Contexts,
|
||||
model,
|
||||
)
|
||||
|
||||
rebaseHelper := helpers.NewMergeAndRebaseHelper(helperCommon, refsHelper)
|
||||
suggestionsHelper := helpers.NewSuggestionsHelper(helperCommon)
|
||||
|
||||
setCommitSummary := gui.getCommitMessageSetTextareaTextFn(func() *gocui.View { return gui.Views.CommitMessage })
|
||||
setCommitDescription := gui.getCommitMessageSetTextareaTextFn(func() *gocui.View { return gui.Views.CommitDescription })
|
||||
getCommitSummary := func() string {
|
||||
return strings.TrimSpace(gui.Views.CommitMessage.TextArea.GetContent())
|
||||
rebaseHelper := helpers.NewMergeAndRebaseHelper(helperCommon, gui.State.Contexts, gui.git, refsHelper)
|
||||
suggestionsHelper := helpers.NewSuggestionsHelper(helperCommon, model, gui.refreshSuggestions)
|
||||
setCommitMessage := gui.getSetTextareaTextFn(func() *gocui.View { return gui.Views.CommitMessage })
|
||||
getSavedCommitMessage := func() string {
|
||||
return gui.State.savedCommitMessage
|
||||
}
|
||||
|
||||
getCommitDescription := func() string {
|
||||
return strings.TrimSpace(gui.Views.CommitDescription.TextArea.GetContent())
|
||||
}
|
||||
commitsHelper := helpers.NewCommitsHelper(helperCommon,
|
||||
getCommitSummary,
|
||||
setCommitSummary,
|
||||
getCommitDescription,
|
||||
setCommitDescription,
|
||||
)
|
||||
|
||||
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 },
|
||||
)
|
||||
gpgHelper := helpers.NewGpgHelper(helperCommon, gui.os, gui.git)
|
||||
gui.helpers = &helpers.Helpers{
|
||||
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,
|
||||
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, setCommitMessage, getSavedCommitMessage),
|
||||
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,
|
||||
),
|
||||
Upstream: helpers.NewUpstreamHelper(helperCommon, model, suggestionsHelper.GetRemoteBranchesSuggestionsFunc),
|
||||
AmendHelper: helpers.NewAmendHelper(helperCommon, gui.git, gpgHelper),
|
||||
}
|
||||
|
||||
gui.CustomCommandsClient = custom_commands.NewClient(
|
||||
helperCommon,
|
||||
gui.os,
|
||||
gui.git,
|
||||
gui.State.Contexts,
|
||||
gui.helpers,
|
||||
)
|
||||
|
||||
common := controllers.NewControllerCommon(helperCommon, gui)
|
||||
common := controllers.NewControllerCommon(
|
||||
helperCommon,
|
||||
osCommand,
|
||||
gui.git,
|
||||
gui.helpers,
|
||||
model,
|
||||
gui.State.Contexts,
|
||||
gui.State.Modes,
|
||||
&gui.Mutexes,
|
||||
)
|
||||
|
||||
syncController := controllers.NewSyncController(
|
||||
common,
|
||||
)
|
||||
|
||||
submodulesController := controllers.NewSubmodulesController(common)
|
||||
submodulesController := controllers.NewSubmodulesController(
|
||||
common,
|
||||
gui.enterSubmodule,
|
||||
)
|
||||
|
||||
bisectController := controllers.NewBisectController(common)
|
||||
|
||||
getCommitMessage := func() string {
|
||||
return strings.TrimSpace(gui.Views.CommitMessage.TextArea.GetContent())
|
||||
}
|
||||
|
||||
onCommitAttempt := func(message string) {
|
||||
gui.State.savedCommitMessage = message
|
||||
gui.Views.CommitMessage.ClearTextArea()
|
||||
}
|
||||
|
||||
onCommitSuccess := func() {
|
||||
gui.State.savedCommitMessage = ""
|
||||
_ = gui.c.Refresh(types.RefreshOptions{
|
||||
Scope: []types.RefreshableView{types.STAGING},
|
||||
})
|
||||
}
|
||||
|
||||
commitMessageController := controllers.NewCommitMessageController(
|
||||
common,
|
||||
)
|
||||
|
||||
commitDescriptionController := controllers.NewCommitDescriptionController(
|
||||
common,
|
||||
getCommitMessage,
|
||||
onCommitAttempt,
|
||||
onCommitSuccess,
|
||||
)
|
||||
|
||||
remoteBranchesController := controllers.NewRemoteBranchesController(common)
|
||||
@@ -131,6 +114,9 @@ func (gui *Gui) resetHelpersAndControllers() {
|
||||
tagsController := controllers.NewTagsController(common)
|
||||
filesController := controllers.NewFilesController(
|
||||
common,
|
||||
gui.enterSubmodule,
|
||||
setCommitMessage,
|
||||
getSavedCommitMessage,
|
||||
)
|
||||
mergeConflictsController := controllers.NewMergeConflictsController(common)
|
||||
remotesController := controllers.NewRemotesController(
|
||||
@@ -151,34 +137,7 @@ func (gui *Gui) resetHelpersAndControllers() {
|
||||
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)
|
||||
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))
|
||||
}
|
||||
snakeController := controllers.NewSnakeController(common, func() *snake.Game { return gui.snakeGame })
|
||||
|
||||
setSubCommits := func(commits []*models.Commit) {
|
||||
gui.Mutexes.SubCommitsMutex.Lock()
|
||||
@@ -204,7 +163,7 @@ func (gui *Gui) resetHelpersAndControllers() {
|
||||
gui.State.Contexts.Stash,
|
||||
} {
|
||||
controllers.AttachControllers(context, controllers.NewSwitchToDiffFilesController(
|
||||
common, context, gui.State.Contexts.CommitFiles,
|
||||
common, gui.SwitchToCommitFilesContext, context,
|
||||
))
|
||||
}
|
||||
|
||||
@@ -216,14 +175,6 @@ func (gui *Gui) resetHelpersAndControllers() {
|
||||
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,
|
||||
@@ -299,36 +250,15 @@ func (gui *Gui) resetHelpersAndControllers() {
|
||||
commitMessageController,
|
||||
)
|
||||
|
||||
controllers.AttachControllers(gui.State.Contexts.CommitDescription,
|
||||
commitDescriptionController,
|
||||
)
|
||||
|
||||
controllers.AttachControllers(gui.State.Contexts.RemoteBranches,
|
||||
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,
|
||||
@@ -336,19 +266,8 @@ func (gui *Gui) resetHelpersAndControllers() {
|
||||
)
|
||||
|
||||
// this must come last so that we've got our click handlers defined against the context
|
||||
listControllerFactory := controllers.NewListControllerFactory(common)
|
||||
for _, context := range gui.c.Context().AllList() {
|
||||
listControllerFactory := controllers.NewListControllerFactory(gui.c)
|
||||
for _, context := range gui.getListContexts() {
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,8 +7,5 @@ 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())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,15 +18,3 @@ 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
|
||||
}
|
||||
|
||||
@@ -20,15 +20,15 @@ type ContainsCommits interface {
|
||||
|
||||
type BasicCommitsController struct {
|
||||
baseController
|
||||
c *ControllerCommon
|
||||
*controllerCommon
|
||||
context ContainsCommits
|
||||
}
|
||||
|
||||
func NewBasicCommitsController(controllerCommon *ControllerCommon, context ContainsCommits) *BasicCommitsController {
|
||||
func NewBasicCommitsController(controllerCommon *controllerCommon, context ContainsCommits) *BasicCommitsController {
|
||||
return &BasicCommitsController{
|
||||
baseController: baseController{},
|
||||
c: controllerCommon,
|
||||
context: context,
|
||||
baseController: baseController{},
|
||||
controllerCommon: controllerCommon,
|
||||
context: context,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,7 +73,7 @@ func (self *BasicCommitsController) GetKeybindings(opts types.KeybindingsOpts) [
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Commits.ResetCherryPick),
|
||||
Handler: self.c.Helpers().CherryPick.Reset,
|
||||
Handler: self.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.c.OS().CopyToClipboard(commit.Sha); err != nil {
|
||||
if err := self.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.c.Helpers().Host.GetCommitURL(commit.Sha)
|
||||
url, err := self.helpers.Host.GetCommitURL(commit.Sha)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
self.c.LogAction(self.c.Tr.Actions.CopyCommitURLToClipboard)
|
||||
if err := self.c.OS().CopyToClipboard(url); err != nil {
|
||||
if err := self.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.c.Git().Commit.GetCommitDiff(commit.Sha)
|
||||
diff, err := self.git.Commit.GetCommitDiff(commit.Sha)
|
||||
if err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
|
||||
self.c.LogAction(self.c.Tr.Actions.CopyCommitDiffToClipboard)
|
||||
if err := self.c.OS().CopyToClipboard(diff); err != nil {
|
||||
if err := self.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.c.Git().Commit.GetCommitAuthor(commit.Sha)
|
||||
author, err := self.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.c.OS().CopyToClipboard(formattedAuthor); err != nil {
|
||||
if err := self.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.c.Git().Commit.GetCommitMessage(commit.Sha)
|
||||
message, err := self.git.Commit.GetCommitMessage(commit.Sha)
|
||||
if err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
|
||||
self.c.LogAction(self.c.Tr.Actions.CopyCommitMessageToClipboard)
|
||||
if err := self.c.OS().CopyToClipboard(message); err != nil {
|
||||
if err := self.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.c.Helpers().Host.GetCommitURL(commit.Sha)
|
||||
url, err := self.helpers.Host.GetCommitURL(commit.Sha)
|
||||
if err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
|
||||
self.c.LogAction(self.c.Tr.Actions.OpenCommitInBrowser)
|
||||
if err := self.c.OS().OpenLink(url); err != nil {
|
||||
if err := self.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.c.Helpers().Refs.NewBranch(commit.RefName(), commit.Description(), "")
|
||||
return self.helpers.Refs.NewBranch(commit.RefName(), commit.Description(), "")
|
||||
}
|
||||
|
||||
func (self *BasicCommitsController) createResetMenu(commit *models.Commit) error {
|
||||
return self.c.Helpers().Refs.CreateGitResetMenu(commit.Sha)
|
||||
return self.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.c.Helpers().Refs.CheckoutRef(commit.Sha, types.CheckoutRefOptions{})
|
||||
return self.helpers.Refs.CheckoutRef(commit.Sha, types.CheckoutRefOptions{})
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func (self *BasicCommitsController) copy(commit *models.Commit) error {
|
||||
return self.c.Helpers().CherryPick.Copy(commit, self.context.GetCommits(), self.context)
|
||||
return self.helpers.CherryPick.Copy(commit, self.context.GetCommits(), self.context)
|
||||
}
|
||||
|
||||
func (self *BasicCommitsController) copyRange(*models.Commit) error {
|
||||
return self.c.Helpers().CherryPick.CopyRange(self.context.GetSelectedLineIdx(), self.context.GetCommits(), self.context)
|
||||
return self.helpers.CherryPick.CopyRange(self.context.GetSelectedLineIdx(), self.context.GetCommits(), self.context)
|
||||
}
|
||||
|
||||
@@ -12,17 +12,17 @@ import (
|
||||
|
||||
type BisectController struct {
|
||||
baseController
|
||||
c *ControllerCommon
|
||||
*controllerCommon
|
||||
}
|
||||
|
||||
var _ types.IController = &BisectController{}
|
||||
|
||||
func NewBisectController(
|
||||
common *ControllerCommon,
|
||||
common *controllerCommon,
|
||||
) *BisectController {
|
||||
return &BisectController{
|
||||
baseController: baseController{},
|
||||
c: common,
|
||||
baseController: baseController{},
|
||||
controllerCommon: 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.c.Git().Bisect.GetInfo()
|
||||
info := self.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.c.Git().Bisect.ReachableFromStart(info)
|
||||
waitToReselect := selectCurrentAfter && !self.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.c.Git().Bisect.Mark(commit.Sha, info.NewTerm()); err != nil {
|
||||
if err := self.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.c.Git().Bisect.Mark(commit.Sha, info.OldTerm()); err != nil {
|
||||
if err := self.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.c.Git().Bisect.Skip(commit.Sha); err != nil {
|
||||
if err := self.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.c.Helpers().Bisect.Reset()
|
||||
return self.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.c.Git().Bisect.Start(); err != nil {
|
||||
if err := self.git.Bisect.Start(); err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
|
||||
if err := self.c.Git().Bisect.Mark(commit.Sha, info.NewTerm()); err != nil {
|
||||
if err := self.git.Bisect.Mark(commit.Sha, info.NewTerm()); err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
|
||||
return self.c.Helpers().Bisect.PostBisectCommandRefresh()
|
||||
return self.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.c.Git().Bisect.Start(); err != nil {
|
||||
if err := self.git.Bisect.Start(); err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
|
||||
if err := self.c.Git().Bisect.Mark(commit.Sha, info.OldTerm()); err != nil {
|
||||
if err := self.git.Bisect.Mark(commit.Sha, info.OldTerm()); err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
|
||||
return self.c.Helpers().Bisect.PostBisectCommandRefresh()
|
||||
return self.helpers.Bisect.PostBisectCommandRefresh()
|
||||
},
|
||||
Key: 'g',
|
||||
},
|
||||
@@ -163,7 +163,7 @@ func (self *BisectController) showBisectCompleteMessage(candidateShas []string)
|
||||
prompt = self.c.Tr.Bisect.CompletePromptIndeterminate
|
||||
}
|
||||
|
||||
formattedCommits, err := self.c.Git().Commit.GetCommitsOneline(candidateShas)
|
||||
formattedCommits, err := self.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.c.Git().Bisect.Reset(); err != nil {
|
||||
if err := self.git.Bisect.Reset(); err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
|
||||
return self.c.Helpers().Bisect.PostBisectCommandRefresh()
|
||||
return self.helpers.Bisect.PostBisectCommandRefresh()
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func (self *BisectController) afterMark(selectCurrent bool, waitToReselect bool) error {
|
||||
done, candidateShas, err := self.c.Git().Bisect.IsDone()
|
||||
done, candidateShas, err := self.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.c.Helpers().Bisect.PostBisectCommandRefresh()
|
||||
return self.helpers.Bisect.PostBisectCommandRefresh()
|
||||
}
|
||||
}
|
||||
|
||||
func (self *BisectController) selectCurrentBisectCommit() {
|
||||
info := self.c.Git().Bisect.GetInfo()
|
||||
info := self.git.Bisect.GetInfo()
|
||||
if info.GetCurrentSha() != "" {
|
||||
// find index of commit with that sha, move cursor to that.
|
||||
for i, commit := range self.c.Model().Commits {
|
||||
for i, commit := range self.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.c.Contexts().LocalCommits
|
||||
return self.contexts.LocalCommits
|
||||
}
|
||||
|
||||
@@ -14,17 +14,17 @@ import (
|
||||
|
||||
type BranchesController struct {
|
||||
baseController
|
||||
c *ControllerCommon
|
||||
*controllerCommon
|
||||
}
|
||||
|
||||
var _ types.IController = &BranchesController{}
|
||||
|
||||
func NewBranchesController(
|
||||
common *ControllerCommon,
|
||||
common *controllerCommon,
|
||||
) *BranchesController {
|
||||
return &BranchesController{
|
||||
baseController: baseController{},
|
||||
c: common,
|
||||
baseController: baseController{},
|
||||
controllerCommon: common,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,30 +111,6 @@ 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,
|
||||
@@ -142,7 +118,7 @@ func (self *BranchesController) setUpstream(selectedBranch *models.Branch) error
|
||||
{
|
||||
LabelColumns: []string{self.c.Tr.LcUnsetUpstream},
|
||||
OnPress: func() error {
|
||||
if err := self.c.Git().Branch.UnsetUpstream(selectedBranch.Name); err != nil {
|
||||
if err := self.git.Branch.UnsetUpstream(selectedBranch.Name); err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
if err := self.c.Refresh(types.RefreshOptions{
|
||||
@@ -161,13 +137,13 @@ func (self *BranchesController) setUpstream(selectedBranch *models.Branch) error
|
||||
{
|
||||
LabelColumns: []string{self.c.Tr.LcSetUpstream},
|
||||
OnPress: func() error {
|
||||
return self.c.Helpers().Upstream.PromptForUpstreamWithoutInitialContent(selectedBranch, func(upstream string) error {
|
||||
upstreamRemote, upstreamBranch, err := self.c.Helpers().Upstream.ParseUpstream(upstream)
|
||||
return self.helpers.Upstream.PromptForUpstreamWithoutInitialContent(selectedBranch, func(upstream string) error {
|
||||
upstreamRemote, upstreamBranch, err := self.helpers.Upstream.ParseUpstream(upstream)
|
||||
if err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
|
||||
if err := self.c.Git().Branch.SetUpstream(upstreamRemote, upstreamBranch, selectedBranch.Name); err != nil {
|
||||
if err := self.git.Branch.SetUpstream(upstreamRemote, upstreamBranch, selectedBranch.Name); err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
if err := self.c.Refresh(types.RefreshOptions{
|
||||
@@ -193,16 +169,16 @@ func (self *BranchesController) Context() types.Context {
|
||||
}
|
||||
|
||||
func (self *BranchesController) context() *context.BranchesContext {
|
||||
return self.c.Contexts().Branches
|
||||
return self.contexts.Branches
|
||||
}
|
||||
|
||||
func (self *BranchesController) press(selectedBranch *models.Branch) error {
|
||||
if selectedBranch == self.c.Helpers().Refs.GetCheckedOutRef() {
|
||||
if selectedBranch == self.helpers.Refs.GetCheckedOutRef() {
|
||||
return self.c.ErrorMsg(self.c.Tr.AlreadyCheckedOutBranch)
|
||||
}
|
||||
|
||||
self.c.LogAction(self.c.Tr.Actions.CheckoutBranch)
|
||||
return self.c.Helpers().Refs.CheckoutRef(selectedBranch.Name, types.CheckoutRefOptions{})
|
||||
return self.helpers.Refs.CheckoutRef(selectedBranch.Name, types.CheckoutRefOptions{})
|
||||
}
|
||||
|
||||
func (self *BranchesController) handleCreatePullRequest(selectedBranch *models.Branch) error {
|
||||
@@ -210,7 +186,7 @@ func (self *BranchesController) handleCreatePullRequest(selectedBranch *models.B
|
||||
}
|
||||
|
||||
func (self *BranchesController) handleCreatePullRequestMenu(selectedBranch *models.Branch) error {
|
||||
checkedOutBranch := self.c.Helpers().Refs.GetCheckedOutRef()
|
||||
checkedOutBranch := self.helpers.Refs.GetCheckedOutRef()
|
||||
|
||||
return self.createPullRequestMenu(selectedBranch, checkedOutBranch)
|
||||
}
|
||||
@@ -218,18 +194,18 @@ func (self *BranchesController) handleCreatePullRequestMenu(selectedBranch *mode
|
||||
func (self *BranchesController) copyPullRequestURL() error {
|
||||
branch := self.context().GetSelected()
|
||||
|
||||
branchExistsOnRemote := self.c.Git().Remote.CheckRemoteBranchExists(branch.Name)
|
||||
branchExistsOnRemote := self.git.Remote.CheckRemoteBranchExists(branch.Name)
|
||||
|
||||
if !branchExistsOnRemote {
|
||||
return self.c.Error(errors.New(self.c.Tr.NoBranchOnRemote))
|
||||
}
|
||||
|
||||
url, err := self.c.Helpers().Host.GetPullRequestURL(branch.Name, "")
|
||||
url, err := self.helpers.Host.GetPullRequestURL(branch.Name, "")
|
||||
if err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
self.c.LogAction(self.c.Tr.Actions.CopyPullRequestURL)
|
||||
if err := self.c.OS().CopyToClipboard(url); err != nil {
|
||||
if err := self.os.CopyToClipboard(url); err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
|
||||
@@ -248,7 +224,7 @@ func (self *BranchesController) forceCheckout() error {
|
||||
Prompt: message,
|
||||
HandleConfirm: func() error {
|
||||
self.c.LogAction(self.c.Tr.Actions.ForceCheckoutBranch)
|
||||
if err := self.c.Git().Branch.Checkout(branch.Name, git_commands.CheckoutOptions{Force: true}); err != nil {
|
||||
if err := self.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})
|
||||
@@ -259,10 +235,10 @@ func (self *BranchesController) forceCheckout() error {
|
||||
func (self *BranchesController) checkoutByName() error {
|
||||
return self.c.Prompt(types.PromptOpts{
|
||||
Title: self.c.Tr.BranchName + ":",
|
||||
FindSuggestionsFunc: self.c.Helpers().Suggestions.GetRefsSuggestionsFunc(),
|
||||
FindSuggestionsFunc: self.helpers.Suggestions.GetRefsSuggestionsFunc(),
|
||||
HandleConfirm: func(response string) error {
|
||||
self.c.LogAction("Checkout branch")
|
||||
return self.c.Helpers().Refs.CheckoutRef(response, types.CheckoutRefOptions{
|
||||
return self.helpers.Refs.CheckoutRef(response, types.CheckoutRefOptions{
|
||||
OnRefNotFound: func(ref string) error {
|
||||
return self.c.Confirm(types.ConfirmOpts{
|
||||
Title: self.c.Tr.BranchNotFoundTitle,
|
||||
@@ -284,7 +260,7 @@ func (self *BranchesController) createNewBranchWithName(newBranchName string) er
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := self.c.Git().Branch.New(newBranchName, branch.FullRefName()); err != nil {
|
||||
if err := self.git.Branch.New(newBranchName, branch.FullRefName()); err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
|
||||
@@ -293,7 +269,7 @@ func (self *BranchesController) createNewBranchWithName(newBranchName string) er
|
||||
}
|
||||
|
||||
func (self *BranchesController) delete(branch *models.Branch) error {
|
||||
checkedOutBranch := self.c.Helpers().Refs.GetCheckedOutRef()
|
||||
checkedOutBranch := self.helpers.Refs.GetCheckedOutRef()
|
||||
if checkedOutBranch.Name == branch.Name {
|
||||
return self.c.ErrorMsg(self.c.Tr.CantDeleteCheckOutBranch)
|
||||
}
|
||||
@@ -320,7 +296,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.c.Git().Branch.Delete(selectedBranch.Name, force); err != nil {
|
||||
if err := self.git.Branch.Delete(selectedBranch.Name, force); err != nil {
|
||||
errMessage := err.Error()
|
||||
if !force && strings.Contains(errMessage, "git branch -D ") {
|
||||
return self.deleteWithForce(selectedBranch, true)
|
||||
@@ -334,12 +310,12 @@ func (self *BranchesController) deleteWithForce(selectedBranch *models.Branch, f
|
||||
|
||||
func (self *BranchesController) merge() error {
|
||||
selectedBranchName := self.context().GetSelected().Name
|
||||
return self.c.Helpers().MergeAndRebase.MergeRefIntoCheckedOutBranch(selectedBranchName)
|
||||
return self.helpers.MergeAndRebase.MergeRefIntoCheckedOutBranch(selectedBranchName)
|
||||
}
|
||||
|
||||
func (self *BranchesController) rebase() error {
|
||||
selectedBranchName := self.context().GetSelected().Name
|
||||
return self.c.Helpers().MergeAndRebase.RebaseOntoRef(selectedBranchName)
|
||||
return self.helpers.MergeAndRebase.RebaseOntoRef(selectedBranchName)
|
||||
}
|
||||
|
||||
func (self *BranchesController) fastForward(branch *models.Branch) error {
|
||||
@@ -364,10 +340,10 @@ func (self *BranchesController) fastForward(branch *models.Branch) error {
|
||||
)
|
||||
|
||||
return self.c.WithLoaderPanel(message, func() error {
|
||||
if branch == self.c.Helpers().Refs.GetCheckedOutRef() {
|
||||
if branch == self.helpers.Refs.GetCheckedOutRef() {
|
||||
self.c.LogAction(action)
|
||||
|
||||
err := self.c.Git().Sync.Pull(
|
||||
err := self.git.Sync.Pull(
|
||||
git_commands.PullOptions{
|
||||
RemoteName: branch.UpstreamRemote,
|
||||
BranchName: branch.UpstreamBranch,
|
||||
@@ -381,7 +357,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.c.Git().Sync.FastForward(branch.Name, branch.UpstreamRemote, branch.UpstreamBranch)
|
||||
err := self.git.Sync.FastForward(branch.Name, branch.UpstreamRemote, branch.UpstreamBranch)
|
||||
if err != nil {
|
||||
_ = self.c.Error(err)
|
||||
}
|
||||
@@ -393,11 +369,11 @@ func (self *BranchesController) fastForward(branch *models.Branch) error {
|
||||
}
|
||||
|
||||
func (self *BranchesController) createTag(branch *models.Branch) error {
|
||||
return self.c.Helpers().Tags.CreateTagMenu(branch.FullRefName(), func() {})
|
||||
return self.helpers.Tags.CreateTagMenu(branch.FullRefName(), func() {})
|
||||
}
|
||||
|
||||
func (self *BranchesController) createResetMenu(selectedBranch *models.Branch) error {
|
||||
return self.c.Helpers().Refs.CreateGitResetMenu(selectedBranch.Name)
|
||||
return self.helpers.Refs.CreateGitResetMenu(selectedBranch.Name)
|
||||
}
|
||||
|
||||
func (self *BranchesController) rename(branch *models.Branch) error {
|
||||
@@ -407,7 +383,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.c.Git().Branch.Rename(branch.Name, newBranchName); err != nil {
|
||||
if err := self.git.Branch.Rename(branch.Name, newBranchName); err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
|
||||
@@ -415,7 +391,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.c.Model().Branches {
|
||||
for i, newBranch := range self.model.Branches {
|
||||
if newBranch.Name == newBranchName {
|
||||
self.context().SetSelectedLineIdx(i)
|
||||
if err := self.context().HandleRender(); err != nil {
|
||||
@@ -444,7 +420,7 @@ func (self *BranchesController) rename(branch *models.Branch) error {
|
||||
}
|
||||
|
||||
func (self *BranchesController) newBranch(selectedBranch *models.Branch) error {
|
||||
return self.c.Helpers().Refs.NewBranch(selectedBranch.FullRefName(), selectedBranch.RefName(), "")
|
||||
return self.helpers.Refs.NewBranch(selectedBranch.FullRefName(), selectedBranch.RefName(), "")
|
||||
}
|
||||
|
||||
func (self *BranchesController) createPullRequestMenu(selectedBranch *models.Branch, checkedOutBranch *models.Branch) error {
|
||||
@@ -467,7 +443,7 @@ func (self *BranchesController) createPullRequestMenu(selectedBranch *models.Bra
|
||||
OnPress: func() error {
|
||||
return self.c.Prompt(types.PromptOpts{
|
||||
Title: branch.Name + " →",
|
||||
FindSuggestionsFunc: self.c.Helpers().Suggestions.GetBranchNameSuggestionsFunc(),
|
||||
FindSuggestionsFunc: self.helpers.Suggestions.GetBranchNameSuggestionsFunc(),
|
||||
HandleConfirm: func(targetBranchName string) error {
|
||||
return self.createPullRequest(branch.Name, targetBranchName)
|
||||
},
|
||||
@@ -495,14 +471,14 @@ func (self *BranchesController) createPullRequestMenu(selectedBranch *models.Bra
|
||||
}
|
||||
|
||||
func (self *BranchesController) createPullRequest(from string, to string) error {
|
||||
url, err := self.c.Helpers().Host.GetPullRequestURL(from, to)
|
||||
url, err := self.helpers.Host.GetPullRequestURL(from, to)
|
||||
if err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
|
||||
self.c.LogAction(self.c.Tr.Actions.OpenPullRequest)
|
||||
|
||||
if err := self.c.OS().OpenLink(url); err != nil {
|
||||
if err := self.os.OpenLink(url); err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user