mirror of
https://github.com/jesseduffield/lazygit.git
synced 2026-09-28 18:26:03 -04:00
Compare commits
148
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
76ed204795 | ||
|
|
b07c4fc001 | ||
|
|
2304ffc804 | ||
|
|
401610c0ef | ||
|
|
64b2685c2d | ||
|
|
7d4bfb6621 | ||
|
|
a2778f01c6 | ||
|
|
d161afe37f | ||
|
|
3dd96a8010 | ||
|
|
681a9bf20d | ||
|
|
0606b7a43b | ||
|
|
33da56eeb4 | ||
|
|
46b93bba0e | ||
|
|
d2d50aedd0 | ||
|
|
ea5fb6a364 | ||
|
|
a82134f41c | ||
|
|
00b03079d8 | ||
|
|
9592686629 | ||
|
|
114ad52ff6 | ||
|
|
9514284f8e | ||
|
|
ece14844bb | ||
|
|
9910a7c308 | ||
|
|
910a61dc46 | ||
|
|
e5dd4d3110 | ||
|
|
595c7ee73e | ||
|
|
051af6a066 | ||
|
|
e2d7e461a8 | ||
|
|
7c66ca83c1 | ||
|
|
007b406b14 | ||
|
|
2b30085dba | ||
|
|
5c95d23169 | ||
|
|
e156e090cc | ||
|
|
0accb07dcc | ||
|
|
3cac14c76e | ||
|
|
1636931c2b | ||
|
|
6e027f42da | ||
|
|
5149b24ab3 | ||
|
|
c520c5cfc3 | ||
|
|
c88ecdf87c | ||
|
|
d607b366cb | ||
|
|
c8f26aca68 | ||
|
|
30656b5ac6 | ||
|
|
5dacbb6293 | ||
|
|
88d4313970 | ||
|
|
4160fa518c | ||
|
|
fba1a2b5ac | ||
|
|
c6c4346d48 | ||
|
|
ee9ae8f07f | ||
|
|
8d68ab41b6 | ||
|
|
af97bf484c | ||
|
|
a57310df24 | ||
|
|
9d68b287db | ||
|
|
a5c72d056d | ||
|
|
8a86de85c8 | ||
|
|
dd31f8ecea | ||
|
|
5a7b2ab6d0 | ||
|
|
68a9d7fd77 | ||
|
|
19cbafcdfc | ||
|
|
f2c85c5b19 | ||
|
|
0faa41e6f8 | ||
|
|
2e32e55957 | ||
|
|
037cd99138 | ||
|
|
6388885699 | ||
|
|
d3e9bc2185 | ||
|
|
ea4587a3b8 | ||
|
|
2da300f2fb | ||
|
|
7848958326 | ||
|
|
2cba98e3fe | ||
|
|
f8c9ce33c2 | ||
|
|
71753770ad | ||
|
|
820b1e811d | ||
|
|
4a33fede7b | ||
|
|
db12853bbe | ||
|
|
711674f6cd | ||
|
|
fc91ef6a59 | ||
|
|
43251e7275 | ||
|
|
f081358943 | ||
|
|
0c6ab4b43e | ||
|
|
1b2fb34ffd | ||
|
|
0e5a4c7a36 | ||
|
|
47b91f1ef5 | ||
|
|
e2db6a1732 | ||
|
|
509e3efa70 | ||
|
|
8edad826ca | ||
|
|
3ec416047f | ||
|
|
8ed29f2b7d | ||
|
|
9adbef40de | ||
|
|
0cd5257523 | ||
|
|
49da7b482d | ||
|
|
d675117289 | ||
|
|
e63858215e | ||
|
|
3fe4db9316 | ||
|
|
185bbf0c75 | ||
|
|
a8586ba57e | ||
|
|
dad7a70bf8 | ||
|
|
3791f0b2fa | ||
|
|
b8fbe9756e | ||
|
|
ab25600ccb | ||
|
|
d50c58b4c6 | ||
|
|
5645a662de | ||
|
|
a41218551d | ||
|
|
d210107caa | ||
|
|
826128a8e0 | ||
|
|
aec46942a8 | ||
|
|
aa70723e3a | ||
|
|
7db54a948a | ||
|
|
32556480da | ||
|
|
2553015029 | ||
|
|
6c010a788c | ||
|
|
b17c38befd | ||
|
|
79dc1d9052 | ||
|
|
28d2b1432b | ||
|
|
ed98e11fa0 | ||
|
|
07a22e69e7 | ||
|
|
bf3dd79b7a | ||
|
|
21072226d2 | ||
|
|
b09000194a | ||
|
|
3546ab8f21 | ||
|
|
9a13447b97 | ||
|
|
1efb565b22 | ||
|
|
08a445eb9d | ||
|
|
28501fbf77 | ||
|
|
8f1f712841 | ||
|
|
b82b6a2992 | ||
|
|
ddcd6be245 | ||
|
|
711be78811 | ||
|
|
3422b1e218 | ||
|
|
a3fdf91714 | ||
|
|
368f9c8cb3 | ||
|
|
e18b4a4cc3 | ||
|
|
46718e25ca | ||
|
|
d675eb6507 | ||
|
|
dc4e88f8a4 | ||
|
|
120dd1530a | ||
|
|
860a8d102b | ||
|
|
a304fed68c | ||
|
|
227b0b781c | ||
|
|
740474c10c | ||
|
|
a0d179b6dc | ||
|
|
c53c5e47ef | ||
|
|
188773511e | ||
|
|
62c5c32fbb | ||
|
|
981ba4c66c | ||
|
|
7f5465a27b | ||
|
|
275ed12486 | ||
|
|
6a340ec840 | ||
|
|
3535cd0f94 | ||
|
|
b1a56249f5 |
@@ -135,10 +135,13 @@ 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 Tests List
|
||||
# ensure our tests list matches up with our generator's output
|
||||
- name: Check Integration Test List
|
||||
# ensure our integration test list is up to date
|
||||
run: |
|
||||
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)
|
||||
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
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
|
||||
+11
-8
@@ -57,6 +57,7 @@ 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
|
||||
@@ -87,6 +88,9 @@ git:
|
||||
# displays the whole git graph by default in the commits panel (equivalent to passing the `--all` argument to `git log`)
|
||||
showWholeGraph: false
|
||||
skipHookPrefix: WIP
|
||||
# The main branches. We colour commits green if they belong to one of these branches,
|
||||
# so that you can easily see which commits are unique to your branch (coloured in yellow)
|
||||
mainBranches: [master, main]
|
||||
autoFetch: true
|
||||
autoRefresh: true
|
||||
branchLogCmd: 'git log --graph --color=always --abbrev-commit --decorate --date=relative --pretty=medium {{branchName}} --'
|
||||
@@ -171,7 +175,6 @@ keybinding:
|
||||
diffingMenu-alt: '<c-e>' # deprecated
|
||||
copyToClipboard: '<c-o>'
|
||||
submitEditorText: '<enter>'
|
||||
appendNewline: '<a-enter>'
|
||||
extrasMenu: '@'
|
||||
toggleWhitespaceInDiffView: '<c-w>'
|
||||
increaseContextInDiffView: '}'
|
||||
@@ -252,21 +255,21 @@ keybinding:
|
||||
|
||||
```yaml
|
||||
os:
|
||||
openCommand: 'start "" {{filename}}'
|
||||
open: 'start "" {{filename}}'
|
||||
```
|
||||
|
||||
### Linux
|
||||
|
||||
```yaml
|
||||
os:
|
||||
openCommand: 'xdg-open {{filename}} >/dev/null'
|
||||
open: 'xdg-open {{filename}} >/dev/null'
|
||||
```
|
||||
|
||||
### OSX
|
||||
|
||||
```yaml
|
||||
os:
|
||||
openCommand: 'open {{filename}}'
|
||||
open: 'open {{filename}}'
|
||||
```
|
||||
|
||||
### Configuring File Editing
|
||||
@@ -285,9 +288,9 @@ os:
|
||||
editPreset: 'vscode'
|
||||
```
|
||||
|
||||
Supported presets are `vim`, `emacs`, `nano`, `vscode`, `sublime`, `bbedit`, and
|
||||
`xcode`. In many cases lazygit will be able to guess the right preset from your
|
||||
$(git config core.editor), or an environment variable such as $VISUAL or $EDITOR.
|
||||
Supported presets are `vim`, `nvim`, `emacs`, `nano`, `vscode`, `sublime`, `bbedit`,
|
||||
`kakoune` and `xcode`. In many cases lazygit will be able to guess the right preset
|
||||
from your $(git config core.editor), or an environment variable such as $VISUAL or $EDITOR.
|
||||
|
||||
If for some reason you are not happy with the default commands from a preset, or
|
||||
there simply is no preset for your editor, you can customize the commands by
|
||||
@@ -474,7 +477,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` or `gitlab`
|
||||
- `provider` is one of `github`, `bitbucket`, `bitbucketServer`, `azuredevops`, `gitlab` or `gitea`
|
||||
- `webDomain` is the URL where your git service exposes a web interface and APIs, e.g. `gitservice.work.com`
|
||||
|
||||
## Predefined commit message prefix
|
||||
|
||||
@@ -8,20 +8,20 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
<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>ctrl+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>ctrl+s</kbd>: view filter-by-path options
|
||||
<kbd>W</kbd>: open diff menu
|
||||
<kbd>ctrl+e</kbd>: open diff menu
|
||||
<kbd>ctrl+w</kbd>: Toggle whether or not whitespace changes are shown in the diff view
|
||||
<kbd>z</kbd>: undo (via reflog) (experimental)
|
||||
<kbd>ctrl+z</kbd>: redo (via reflog) (experimental)
|
||||
<kbd>P</kbd>: push
|
||||
@@ -56,6 +56,13 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
<kbd>`</kbd>: toggle file tree view
|
||||
</pre>
|
||||
|
||||
## Commit Summary
|
||||
|
||||
<pre>
|
||||
<kbd>enter</kbd>: confirm
|
||||
<kbd>esc</kbd>: close
|
||||
</pre>
|
||||
|
||||
## Commits
|
||||
|
||||
<pre>
|
||||
@@ -89,6 +96,13 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
<kbd>enter</kbd>: view selected item's files
|
||||
</pre>
|
||||
|
||||
## Confirmation Panel
|
||||
|
||||
<pre>
|
||||
<kbd>enter</kbd>: confirm
|
||||
<kbd>esc</kbd>: close/cancel
|
||||
</pre>
|
||||
|
||||
## Files
|
||||
|
||||
<pre>
|
||||
@@ -197,6 +211,13 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
<kbd>C</kbd>: commit changes using git editor
|
||||
</pre>
|
||||
|
||||
## Menu
|
||||
|
||||
<pre>
|
||||
<kbd>enter</kbd>: execute
|
||||
<kbd>esc</kbd>: close
|
||||
</pre>
|
||||
|
||||
## Reflog
|
||||
|
||||
<pre>
|
||||
@@ -250,8 +271,8 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
## Status
|
||||
|
||||
<pre>
|
||||
<kbd>e</kbd>: edit config file
|
||||
<kbd>o</kbd>: open config file
|
||||
<kbd>e</kbd>: edit config file
|
||||
<kbd>u</kbd>: check for update
|
||||
<kbd>enter</kbd>: switch to a recent repo
|
||||
<kbd>a</kbd>: show all branch logs
|
||||
|
||||
@@ -8,20 +8,20 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
<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>ctrl+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>ctrl+s</kbd>: view filter-by-path options
|
||||
<kbd>W</kbd>: 差分メニューを開く
|
||||
<kbd>ctrl+e</kbd>: 差分メニューを開く
|
||||
<kbd>ctrl+w</kbd>: 空白文字の差分の表示有無を切り替え
|
||||
<kbd>z</kbd>: アンドゥ (via reflog) (experimental)
|
||||
<kbd>ctrl+z</kbd>: リドゥ (via reflog) (experimental)
|
||||
<kbd>P</kbd>: push
|
||||
@@ -115,6 +115,13 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
<kbd>`</kbd>: ファイルツリーの表示を切り替え
|
||||
</pre>
|
||||
|
||||
## コミットメッセージ
|
||||
|
||||
<pre>
|
||||
<kbd>enter</kbd>: 確認
|
||||
<kbd>esc</kbd>: 閉じる
|
||||
</pre>
|
||||
|
||||
## サブモジュール
|
||||
|
||||
<pre>
|
||||
@@ -131,8 +138,8 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
## ステータス
|
||||
|
||||
<pre>
|
||||
<kbd>e</kbd>: 設定ファイルを編集
|
||||
<kbd>o</kbd>: 設定ファイルを開く
|
||||
<kbd>e</kbd>: 設定ファイルを編集
|
||||
<kbd>u</kbd>: 更新を確認
|
||||
<kbd>enter</kbd>: 最近使用したリポジトリに切り替え
|
||||
<kbd>a</kbd>: すべてのブランチログを表示
|
||||
@@ -257,6 +264,13 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
<kbd>C</kbd>: gitエディタを使用して変更をコミット
|
||||
</pre>
|
||||
|
||||
## メニュー
|
||||
|
||||
<pre>
|
||||
<kbd>enter</kbd>: 実行
|
||||
<kbd>esc</kbd>: 閉じる
|
||||
</pre>
|
||||
|
||||
## リモート
|
||||
|
||||
<pre>
|
||||
@@ -295,3 +309,10 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
<kbd>ctrl+r</kbd>: reset cherry-picked (copied) commits selection
|
||||
<kbd>enter</kbd>: コミットを閲覧
|
||||
</pre>
|
||||
|
||||
## 確認パネル
|
||||
|
||||
<pre>
|
||||
<kbd>enter</kbd>: 確認
|
||||
<kbd>esc</kbd>: 閉じる/キャンセル
|
||||
</pre>
|
||||
|
||||
@@ -8,20 +8,20 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
<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>ctrl+p</kbd>: 커스텀 Patch 옵션 보기
|
||||
<kbd>m</kbd>: view merge/rebase options
|
||||
<kbd>R</kbd>: 새로고침
|
||||
<kbd>+</kbd>: 다음 스크린 모드 (normal/half/fullscreen)
|
||||
<kbd>_</kbd>: 이전 스크린 모드
|
||||
<kbd>?</kbd>: 매뉴 열기
|
||||
<kbd>ctrl+s</kbd>: view filter-by-path options
|
||||
<kbd>W</kbd>: Diff 메뉴 열기
|
||||
<kbd>ctrl+e</kbd>: Diff 메뉴 열기
|
||||
<kbd>ctrl+w</kbd>: 공백문자를 Diff 뷰에서 표시 여부 전환
|
||||
<kbd>z</kbd>: 되돌리기 (reflog) (실험적)
|
||||
<kbd>ctrl+z</kbd>: 다시 실행 (reflog) (실험적)
|
||||
<kbd>P</kbd>: 푸시
|
||||
@@ -83,6 +83,13 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
<kbd>enter</kbd>: view selected item's files
|
||||
</pre>
|
||||
|
||||
## 메뉴
|
||||
|
||||
<pre>
|
||||
<kbd>enter</kbd>: 실행
|
||||
<kbd>esc</kbd>: 닫기
|
||||
</pre>
|
||||
|
||||
## 메인 패널 (Merging)
|
||||
|
||||
<pre>
|
||||
@@ -168,8 +175,8 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
## 상태
|
||||
|
||||
<pre>
|
||||
<kbd>e</kbd>: 설정 파일 수정
|
||||
<kbd>o</kbd>: 설정 파일 열기
|
||||
<kbd>e</kbd>: 설정 파일 수정
|
||||
<kbd>u</kbd>: 업데이트 확인
|
||||
<kbd>enter</kbd>: 최근에 사용한 저장소로 전환
|
||||
<kbd>a</kbd>: 모든 브랜치 로그 표시
|
||||
@@ -259,6 +266,13 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
<kbd>`</kbd>: 파일 트리뷰로 전환
|
||||
</pre>
|
||||
|
||||
## 커밋메시지
|
||||
|
||||
<pre>
|
||||
<kbd>enter</kbd>: 확인
|
||||
<kbd>esc</kbd>: 닫기
|
||||
</pre>
|
||||
|
||||
## 태그
|
||||
|
||||
<pre>
|
||||
@@ -295,3 +309,10 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
<kbd>M</kbd>: git mergetool를 열기
|
||||
<kbd>f</kbd>: fetch
|
||||
</pre>
|
||||
|
||||
## 확인 패널
|
||||
|
||||
<pre>
|
||||
<kbd>enter</kbd>: 확인
|
||||
<kbd>esc</kbd>: 닫기/취소
|
||||
</pre>
|
||||
|
||||
@@ -8,20 +8,20 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
<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>ctrl+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>ctrl+s</kbd>: bekijk scoping opties
|
||||
<kbd>W</kbd>: open diff menu
|
||||
<kbd>ctrl+e</kbd>: open diff menu
|
||||
<kbd>ctrl+w</kbd>: Toggle whether or not whitespace changes are shown in the diff view
|
||||
<kbd>z</kbd>: ongedaan maken (via reflog) (experimenteel)
|
||||
<kbd>ctrl+z</kbd>: redo (via reflog) (experimenteel)
|
||||
<kbd>P</kbd>: push
|
||||
@@ -68,6 +68,13 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
<kbd>f</kbd>: fetch
|
||||
</pre>
|
||||
|
||||
## Bevestigingspaneel
|
||||
|
||||
<pre>
|
||||
<kbd>enter</kbd>: bevestig
|
||||
<kbd>esc</kbd>: sluiten
|
||||
</pre>
|
||||
|
||||
## Branches
|
||||
|
||||
<pre>
|
||||
@@ -91,6 +98,13 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
<kbd>enter</kbd>: bekijk commits
|
||||
</pre>
|
||||
|
||||
## Commit Bericht
|
||||
|
||||
<pre>
|
||||
<kbd>enter</kbd>: bevestig
|
||||
<kbd>esc</kbd>: sluiten
|
||||
</pre>
|
||||
|
||||
## Commit bestanden
|
||||
|
||||
<pre>
|
||||
@@ -138,6 +152,13 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
<kbd>enter</kbd>: bekijk gecommite bestanden
|
||||
</pre>
|
||||
|
||||
## Menu
|
||||
|
||||
<pre>
|
||||
<kbd>enter</kbd>: uitvoeren
|
||||
<kbd>esc</kbd>: sluiten
|
||||
</pre>
|
||||
|
||||
## Mergen
|
||||
|
||||
<pre>
|
||||
@@ -250,8 +271,8 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
## Status
|
||||
|
||||
<pre>
|
||||
<kbd>e</kbd>: verander config bestand
|
||||
<kbd>o</kbd>: open config bestand
|
||||
<kbd>e</kbd>: verander config bestand
|
||||
<kbd>u</kbd>: check voor updates
|
||||
<kbd>enter</kbd>: wissel naar een recente repo
|
||||
<kbd>a</kbd>: alle logs van de branch laten zien
|
||||
|
||||
@@ -8,20 +8,20 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
<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>ctrl+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>ctrl+s</kbd>: view filter-by-path options
|
||||
<kbd>W</kbd>: open diff menu
|
||||
<kbd>ctrl+e</kbd>: open diff menu
|
||||
<kbd>ctrl+w</kbd>: Toggle whether or not whitespace changes are shown in the diff view
|
||||
<kbd>z</kbd>: undo (via reflog) (experimental)
|
||||
<kbd>ctrl+z</kbd>: redo (via reflog) (experimental)
|
||||
<kbd>P</kbd>: push
|
||||
@@ -42,6 +42,13 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
<kbd>[</kbd>: previous tab
|
||||
</pre>
|
||||
|
||||
## Commit Summary
|
||||
|
||||
<pre>
|
||||
<kbd>enter</kbd>: potwierdź
|
||||
<kbd>esc</kbd>: zamknij
|
||||
</pre>
|
||||
|
||||
## Commity
|
||||
|
||||
<pre>
|
||||
@@ -75,6 +82,13 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
<kbd>enter</kbd>: przeglądaj pliki commita
|
||||
</pre>
|
||||
|
||||
## Confirmation Panel
|
||||
|
||||
<pre>
|
||||
<kbd>enter</kbd>: potwierdź
|
||||
<kbd>esc</kbd>: zamknij
|
||||
</pre>
|
||||
|
||||
## Local Branches
|
||||
|
||||
<pre>
|
||||
@@ -113,6 +127,13 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
<kbd>esc</kbd>: wyście z trybu "linia po linii"
|
||||
</pre>
|
||||
|
||||
## Menu
|
||||
|
||||
<pre>
|
||||
<kbd>enter</kbd>: wykonaj
|
||||
<kbd>esc</kbd>: zamknij
|
||||
</pre>
|
||||
|
||||
## Pliki
|
||||
|
||||
<pre>
|
||||
@@ -243,8 +264,8 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
## Status
|
||||
|
||||
<pre>
|
||||
<kbd>e</kbd>: edytuj konfigurację
|
||||
<kbd>o</kbd>: otwórz konfigurację
|
||||
<kbd>e</kbd>: edytuj konfigurację
|
||||
<kbd>u</kbd>: sprawdź aktualizacje
|
||||
<kbd>enter</kbd>: switch to a recent repo
|
||||
<kbd>a</kbd>: pokaż wszystkie logi gałęzi
|
||||
|
||||
@@ -8,20 +8,20 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
<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>ctrl+p</kbd>: 查看自定义补丁选项
|
||||
<kbd>m</kbd>: 查看 合并/变基 选项
|
||||
<kbd>R</kbd>: 刷新
|
||||
<kbd>+</kbd>: 下一屏模式(正常/半屏/全屏)
|
||||
<kbd>_</kbd>: 上一屏模式
|
||||
<kbd>?</kbd>: 打开菜单
|
||||
<kbd>ctrl+s</kbd>: 查看按路径过滤选项
|
||||
<kbd>W</kbd>: 打开 diff 菜单
|
||||
<kbd>ctrl+e</kbd>: 打开 diff 菜单
|
||||
<kbd>ctrl+w</kbd>: 切换是否在差异视图中显示空白字符差异
|
||||
<kbd>z</kbd>: (通过 reflog)撤销「实验功能」
|
||||
<kbd>ctrl+z</kbd>: (通过 reflog)重做「实验功能」
|
||||
<kbd>P</kbd>: 推送
|
||||
@@ -155,6 +155,13 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
<kbd>`</kbd>: 切换文件树视图
|
||||
</pre>
|
||||
|
||||
## 提交讯息
|
||||
|
||||
<pre>
|
||||
<kbd>enter</kbd>: 确认
|
||||
<kbd>esc</kbd>: 关闭
|
||||
</pre>
|
||||
|
||||
## 文件
|
||||
|
||||
<pre>
|
||||
@@ -254,13 +261,27 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
## 状态
|
||||
|
||||
<pre>
|
||||
<kbd>e</kbd>: 编辑配置文件
|
||||
<kbd>o</kbd>: 打开配置文件
|
||||
<kbd>e</kbd>: 编辑配置文件
|
||||
<kbd>u</kbd>: 检查更新
|
||||
<kbd>enter</kbd>: 切换到最近的仓库
|
||||
<kbd>a</kbd>: 显示所有分支的日志
|
||||
</pre>
|
||||
|
||||
## 确认面板
|
||||
|
||||
<pre>
|
||||
<kbd>enter</kbd>: 确认
|
||||
<kbd>esc</kbd>: 关闭
|
||||
</pre>
|
||||
|
||||
## 菜单
|
||||
|
||||
<pre>
|
||||
<kbd>enter</kbd>: 执行
|
||||
<kbd>esc</kbd>: 关闭
|
||||
</pre>
|
||||
|
||||
## 贮藏
|
||||
|
||||
<pre>
|
||||
|
||||
@@ -4,12 +4,12 @@ go 1.18
|
||||
|
||||
require (
|
||||
github.com/OpenPeeDeeP/xdg v1.0.0
|
||||
github.com/atotto/clipboard v0.1.2
|
||||
github.com/atotto/clipboard v0.1.4
|
||||
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.2
|
||||
github.com/fsmiamoto/git-todo-parser v0.0.4
|
||||
github.com/fsnotify/fsnotify v1.4.7
|
||||
github.com/gdamore/tcell/v2 v2.6.0
|
||||
github.com/go-errors/errors v1.4.2
|
||||
|
||||
@@ -8,6 +8,8 @@ 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=
|
||||
@@ -28,8 +30,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.2 h1:l6Y+9q7jbM+yK/w6kASpHO7ejL9ARCErm3tCEqOT278=
|
||||
github.com/fsmiamoto/git-todo-parser v0.0.2/go.mod h1:B+AgTbNE2BARvJqzXygThzqxLIaEWvwr2sxKYYb0Fas=
|
||||
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/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=
|
||||
|
||||
+2
-4
@@ -33,7 +33,6 @@ type App struct {
|
||||
Config config.AppConfigurer
|
||||
OSCommand *oscommands.OSCommand
|
||||
Gui *gui.Gui
|
||||
Updater *updates.Updater // may only need this on the Gui
|
||||
}
|
||||
|
||||
func Run(
|
||||
@@ -87,8 +86,7 @@ func NewApp(config config.AppConfigurer, common *common.Common) (*App, error) {
|
||||
|
||||
app.OSCommand = oscommands.NewOSCommand(common, config, oscommands.GetPlatform(), oscommands.NewNullGuiIO(app.Log))
|
||||
|
||||
var err error
|
||||
app.Updater, err = updates.NewUpdater(common, config, app.OSCommand)
|
||||
updater, err := updates.NewUpdater(common, config, app.OSCommand)
|
||||
if err != nil {
|
||||
return app, err
|
||||
}
|
||||
@@ -108,7 +106,7 @@ func NewApp(config config.AppConfigurer, common *common.Common) (*App, error) {
|
||||
return app, err
|
||||
}
|
||||
|
||||
app.Gui, err = gui.NewGui(common, config, gitVersion, app.Updater, showRecentRepos, dirName)
|
||||
app.Gui, err = gui.NewGui(common, config, gitVersion, updater, showRecentRepos, dirName)
|
||||
if err != nil {
|
||||
return app, err
|
||||
}
|
||||
|
||||
+252
-52
@@ -1,13 +1,17 @@
|
||||
package daemon
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"strconv"
|
||||
|
||||
"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/env"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
// Sometimes lazygit will be invoked in daemon mode from a parent lazygit process.
|
||||
@@ -15,33 +19,58 @@ 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=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.
|
||||
// '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.
|
||||
|
||||
type DaemonKind string
|
||||
type DaemonKind int
|
||||
|
||||
const (
|
||||
InteractiveRebase DaemonKind = "INTERACTIVE_REBASE"
|
||||
ExitImmediately DaemonKind = "EXIT_IMMEDIATELY"
|
||||
// for when we fail to parse the daemon kind
|
||||
DaemonKindUnknown DaemonKind = iota
|
||||
|
||||
DaemonKindExitImmediately
|
||||
DaemonKindCherryPick
|
||||
DaemonKindMoveTodoUp
|
||||
DaemonKindMoveTodoDown
|
||||
DaemonKindInsertBreak
|
||||
DaemonKindChangeTodoActions
|
||||
DaemonKindMoveFixupCommitDown
|
||||
)
|
||||
|
||||
const (
|
||||
DaemonKindEnvKey string = "LAZYGIT_DAEMON_KIND"
|
||||
RebaseTODOEnvKey string = "LAZYGIT_REBASE_TODO"
|
||||
|
||||
// Contains json-encoded arguments to the daemon
|
||||
DaemonInstructionEnvKey string = "LAZYGIT_DAEMON_INSTRUCTION"
|
||||
)
|
||||
|
||||
type Daemon interface {
|
||||
Run() error
|
||||
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)
|
||||
}
|
||||
|
||||
func Handle(common *common.Common) {
|
||||
d := getDaemon(common)
|
||||
if d == nil {
|
||||
if !InDaemonMode() {
|
||||
return
|
||||
}
|
||||
|
||||
if err := d.Run(); err != nil {
|
||||
instruction := getInstruction()
|
||||
|
||||
if err := instruction.run(common); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -49,58 +78,229 @@ func Handle(common *common.Common) {
|
||||
}
|
||||
|
||||
func InDaemonMode() bool {
|
||||
return getDaemonKind() != ""
|
||||
}
|
||||
|
||||
func getDaemon(common *common.Common) Daemon {
|
||||
switch getDaemonKind() {
|
||||
case InteractiveRebase:
|
||||
return &rebaseDaemon{c: common}
|
||||
case ExitImmediately:
|
||||
return &exitImmediatelyDaemon{c: common}
|
||||
}
|
||||
|
||||
return nil
|
||||
return getDaemonKind() != DaemonKindUnknown
|
||||
}
|
||||
|
||||
func getDaemonKind() DaemonKind {
|
||||
return DaemonKind(os.Getenv(DaemonKindEnvKey))
|
||||
intValue, err := strconv.Atoi(os.Getenv(DaemonKindEnvKey))
|
||||
if err != nil {
|
||||
return DaemonKindUnknown
|
||||
}
|
||||
|
||||
return DaemonKind(intValue)
|
||||
}
|
||||
|
||||
type rebaseDaemon struct {
|
||||
c *common.Common
|
||||
// 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 (self *rebaseDaemon) Run() error {
|
||||
self.c.Log.Info("Lazygit invoked as interactive rebase demon")
|
||||
self.c.Log.Info("args: ", os.Args)
|
||||
func serializeInstruction[T any](instruction T) string {
|
||||
jsonData, err := json.Marshal(instruction)
|
||||
if err != nil {
|
||||
// this should never happen
|
||||
panic(err)
|
||||
}
|
||||
|
||||
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
|
||||
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{}
|
||||
}
|
||||
|
||||
type CherryPickCommitsInstruction struct {
|
||||
Todo string
|
||||
}
|
||||
|
||||
func NewCherryPickCommitsInstruction(commits []*models.Commit) Instruction {
|
||||
todoLines := lo.Map(commits, func(commit *models.Commit, _ int) TodoLine {
|
||||
return TodoLine{
|
||||
Action: "pick",
|
||||
Commit: commit,
|
||||
}
|
||||
} 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")
|
||||
})
|
||||
|
||||
todo := TodoLinesToString(todoLines)
|
||||
|
||||
return &CherryPickCommitsInstruction{
|
||||
Todo: todo,
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func gitDir() string {
|
||||
dir := env.GetGitDirEnv()
|
||||
if dir == "" {
|
||||
return ".git"
|
||||
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,
|
||||
}
|
||||
return dir
|
||||
}
|
||||
|
||||
type exitImmediatelyDaemon struct {
|
||||
c *common.Common
|
||||
func (self *ChangeTodoActionsInstruction) Kind() DaemonKind {
|
||||
return DaemonKindChangeTodoActions
|
||||
}
|
||||
|
||||
func (self *exitImmediatelyDaemon) Run() error {
|
||||
return nil
|
||||
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"))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
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
|
||||
}
|
||||
+28
-27
@@ -87,33 +87,34 @@ 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,
|
||||
"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,
|
||||
"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,
|
||||
}
|
||||
|
||||
title, ok := contextTitleMap[str]
|
||||
|
||||
+1
-1
@@ -129,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, branchCommands.CurrentBranchInfo, statusCommands.RebaseMode)
|
||||
commitLoader := git_commands.NewCommitLoader(cmn, cmd, dotGitDir, statusCommands.RebaseMode)
|
||||
reflogCommitLoader := git_commands.NewReflogCommitLoader(cmn, cmd)
|
||||
remoteLoader := git_commands.NewRemoteLoader(cmn, cmd, repo.Remotes)
|
||||
stashLoader := git_commands.NewStashLoader(cmn, cmd)
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
|
||||
"github.com/go-errors/errors"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
|
||||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
@@ -99,12 +100,53 @@ func TestBranchDeleteBranch(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestBranchMerge(t *testing.T) {
|
||||
runner := oscommands.NewFakeRunner(t).
|
||||
Expect(`git merge --no-edit "test"`, "", nil)
|
||||
instance := buildBranchCommands(commonDeps{runner: runner})
|
||||
scenarios := []struct {
|
||||
testName string
|
||||
userConfig *config.UserConfig
|
||||
opts MergeOpts
|
||||
branchName string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
testName: "basic",
|
||||
userConfig: &config.UserConfig{},
|
||||
opts: MergeOpts{},
|
||||
branchName: "mybranch",
|
||||
expected: `git merge --no-edit "mybranch"`,
|
||||
},
|
||||
{
|
||||
testName: "merging args",
|
||||
userConfig: &config.UserConfig{
|
||||
Git: config.GitConfig{
|
||||
Merging: config.MergingConfig{
|
||||
Args: "--merging-args", // it's up to the user what they put here
|
||||
},
|
||||
},
|
||||
},
|
||||
opts: MergeOpts{},
|
||||
branchName: "mybranch",
|
||||
expected: `git merge --no-edit --merging-args "mybranch"`,
|
||||
},
|
||||
{
|
||||
testName: "fast forward only",
|
||||
userConfig: &config.UserConfig{},
|
||||
opts: MergeOpts{FastForwardOnly: true},
|
||||
branchName: "mybranch",
|
||||
expected: `git merge --no-edit --ff-only "mybranch"`,
|
||||
},
|
||||
}
|
||||
|
||||
assert.NoError(t, instance.Merge("test", MergeOpts{}))
|
||||
runner.CheckForMissingCalls()
|
||||
for _, s := range scenarios {
|
||||
s := s
|
||||
t.Run(s.testName, func(t *testing.T) {
|
||||
runner := oscommands.NewFakeRunner(t).
|
||||
Expect(s.expected, "", nil)
|
||||
instance := buildBranchCommands(commonDeps{runner: runner, userConfig: s.userConfig})
|
||||
|
||||
assert.NoError(t, instance.Merge(s.branchName, s.opts))
|
||||
runner.CheckForMissingCalls()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestBranchCheckout(t *testing.T) {
|
||||
|
||||
@@ -8,6 +8,8 @@ import (
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
|
||||
)
|
||||
|
||||
var ErrInvalidCommitIndex = errors.New("invalid commit index")
|
||||
|
||||
type CommitCommands struct {
|
||||
*GitCommon
|
||||
}
|
||||
@@ -18,11 +20,6 @@ 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 {
|
||||
return self.cmd.New("git commit --allow-empty --only --no-edit --amend --reset-author").Run()
|
||||
@@ -45,11 +42,7 @@ func (self *CommitCommands) ResetToCommit(sha string, strength string, envVars [
|
||||
}
|
||||
|
||||
func (self *CommitCommands) CommitCmdObj(message string) oscommands.ICmdObj {
|
||||
splitMessage := strings.Split(message, "\n")
|
||||
lineArgs := ""
|
||||
for _, line := range splitMessage {
|
||||
lineArgs += fmt.Sprintf(" -m %s", self.cmd.Quote(line))
|
||||
}
|
||||
messageArgs := self.commitMessageArgs(message)
|
||||
|
||||
skipHookPrefix := self.UserConfig.Git.SkipHookPrefix
|
||||
noVerifyFlag := ""
|
||||
@@ -57,7 +50,23 @@ func (self *CommitCommands) CommitCmdObj(message string) oscommands.ICmdObj {
|
||||
noVerifyFlag = " --no-verify"
|
||||
}
|
||||
|
||||
return self.cmd.New(fmt.Sprintf("git commit%s%s%s", noVerifyFlag, self.signoffFlag(), lineArgs))
|
||||
return self.cmd.New(fmt.Sprintf("git commit%s%s%s", noVerifyFlag, self.signoffFlag(), messageArgs))
|
||||
}
|
||||
|
||||
// RewordLastCommit rewords the topmost commit with the given message
|
||||
func (self *CommitCommands) RewordLastCommit(message string) error {
|
||||
messageArgs := self.commitMessageArgs(message)
|
||||
return self.cmd.New(fmt.Sprintf("git commit --allow-empty --amend --only%s", messageArgs)).Run()
|
||||
}
|
||||
|
||||
func (self *CommitCommands) commitMessageArgs(message string) string {
|
||||
msg, description, _ := strings.Cut(message, "\n")
|
||||
descriptionArgs := ""
|
||||
if description != "" {
|
||||
descriptionArgs = fmt.Sprintf(" -m %s", self.cmd.Quote(description))
|
||||
}
|
||||
|
||||
return fmt.Sprintf(" -m %s%s", self.cmd.Quote(msg), descriptionArgs)
|
||||
}
|
||||
|
||||
// runs git commit without the -m argument meaning it will invoke the user's editor
|
||||
@@ -178,3 +187,13 @@ func (self *CommitCommands) RevertMerge(sha string, parentNumber int) error {
|
||||
func (self *CommitCommands) CreateFixupCommit(sha string) error {
|
||||
return self.cmd.New(fmt.Sprintf("git commit --fixup=%s", sha)).Run()
|
||||
}
|
||||
|
||||
// a value of 0 means the head commit, 1 is the parent commit, etc
|
||||
func (self *CommitCommands) GetCommitMessageFromHistory(value int) (string, error) {
|
||||
hash, _ := self.cmd.New(fmt.Sprintf("git log -1 --skip=%d --pretty=%%H", value)).DontLog().RunWithOutput()
|
||||
formattedHash := strings.TrimSpace(hash)
|
||||
if len(formattedHash) == 0 {
|
||||
return "", ErrInvalidCommitIndex
|
||||
}
|
||||
return self.GetCommitMessage(formattedHash)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package git_commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/jesseduffield/generics/slices"
|
||||
@@ -25,12 +24,18 @@ func NewCommitFileLoader(common *common.Common, cmd oscommands.ICmdObjBuilder) *
|
||||
|
||||
// GetFilesInDiff get the specified commit files
|
||||
func (self *CommitFileLoader) GetFilesInDiff(from string, to string, reverse bool) ([]*models.CommitFile, error) {
|
||||
reverseFlag := ""
|
||||
if reverse {
|
||||
reverseFlag = " -R "
|
||||
}
|
||||
cmdStr := NewGitCmd("diff").
|
||||
Arg("--submodule").
|
||||
Arg("--no-ext-diff").
|
||||
Arg("--name-status").
|
||||
Arg("-z").
|
||||
Arg("--no-renames").
|
||||
ArgIf(reverse, "-R").
|
||||
Arg(from).
|
||||
Arg(to).
|
||||
ToString()
|
||||
|
||||
filenames, err := self.cmd.New(fmt.Sprintf("git diff --submodule --no-ext-diff --name-status -z --no-renames %s %s %s", reverseFlag, from, to)).DontLog().RunWithOutput()
|
||||
filenames, err := self.cmd.New(cmdStr).DontLog().RunWithOutput()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/types/enums"
|
||||
"github.com/jesseduffield/lazygit/pkg/common"
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
// context:
|
||||
@@ -28,11 +29,14 @@ type CommitLoader struct {
|
||||
*common.Common
|
||||
cmd oscommands.ICmdObjBuilder
|
||||
|
||||
getCurrentBranchInfo func() (BranchInfo, error)
|
||||
getRebaseMode func() (enums.RebaseMode, error)
|
||||
readFile func(filename string) ([]byte, error)
|
||||
walkFiles func(root string, fn filepath.WalkFunc) error
|
||||
dotGitDir string
|
||||
getRebaseMode func() (enums.RebaseMode, error)
|
||||
readFile func(filename string) ([]byte, error)
|
||||
walkFiles func(root string, fn filepath.WalkFunc) error
|
||||
dotGitDir string
|
||||
// List of main branches that exist in the repo, quoted for direct use in a git command.
|
||||
// We use these to obtain the merge base of the branch.
|
||||
// When nil, we're yet to obtain the list of main branches.
|
||||
quotedMainBranches *string
|
||||
}
|
||||
|
||||
// making our dependencies explicit for the sake of easier testing
|
||||
@@ -40,17 +44,16 @@ func NewCommitLoader(
|
||||
cmn *common.Common,
|
||||
cmd oscommands.ICmdObjBuilder,
|
||||
dotGitDir string,
|
||||
getCurrentBranchInfo func() (BranchInfo, error),
|
||||
getRebaseMode func() (enums.RebaseMode, error),
|
||||
) *CommitLoader {
|
||||
return &CommitLoader{
|
||||
Common: cmn,
|
||||
cmd: cmd,
|
||||
getCurrentBranchInfo: getCurrentBranchInfo,
|
||||
getRebaseMode: getRebaseMode,
|
||||
readFile: os.ReadFile,
|
||||
walkFiles: filepath.Walk,
|
||||
dotGitDir: dotGitDir,
|
||||
Common: cmn,
|
||||
cmd: cmd,
|
||||
getRebaseMode: getRebaseMode,
|
||||
readFile: os.ReadFile,
|
||||
walkFiles: filepath.Walk,
|
||||
dotGitDir: dotGitDir,
|
||||
quotedMainBranches: nil,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,7 +92,7 @@ func (self *CommitLoader) GetCommits(opts GetCommitsOptions) ([]*models.Commit,
|
||||
if commit.Sha == firstPushedCommit {
|
||||
passedFirstPushedCommit = true
|
||||
}
|
||||
commit.Status = map[bool]string{true: "unpushed", false: "pushed"}[!passedFirstPushedCommit]
|
||||
commit.Status = map[bool]models.CommitStatus{true: models.StatusUnpushed, false: models.StatusPushed}[!passedFirstPushedCommit]
|
||||
commits = append(commits, commit)
|
||||
return false, nil
|
||||
})
|
||||
@@ -101,10 +104,7 @@ func (self *CommitLoader) GetCommits(opts GetCommitsOptions) ([]*models.Commit,
|
||||
return commits, nil
|
||||
}
|
||||
|
||||
commits, err = self.setCommitMergedStatuses(opts.RefName, commits)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
commits = self.setCommitMergedStatuses(opts.RefName, commits)
|
||||
|
||||
return commits, nil
|
||||
}
|
||||
@@ -194,35 +194,43 @@ func (self *CommitLoader) getHydratedRebasingCommits(rebaseMode enums.RebaseMode
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
commitShas := slices.Map(commits, func(commit *models.Commit) string {
|
||||
return commit.Sha
|
||||
commitShas := slices.FilterMap(commits, func(commit *models.Commit) (string, bool) {
|
||||
return commit.Sha, 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(
|
||||
fmt.Sprintf(
|
||||
"git -c log.showSignature=false show %s --no-patch --oneline %s --abbrev=%d",
|
||||
strings.Join(commitShas, " "),
|
||||
prettyFormat,
|
||||
20,
|
||||
),
|
||||
NewGitCmd("show").
|
||||
Config("log.showSignature=false").
|
||||
Arg("--no-patch").
|
||||
Arg("--oneline").
|
||||
Arg(strings.Join(commitShas, " ")).
|
||||
Arg(prettyFormat).
|
||||
Arg("--abbrev=20").
|
||||
ToString(),
|
||||
).DontLog()
|
||||
|
||||
hydratedCommits := make([]*models.Commit, 0, len(commits))
|
||||
i := 0
|
||||
fullCommits := map[string]*models.Commit{}
|
||||
err = cmdObj.RunAndProcessLines(func(line string) (bool, error) {
|
||||
commit := self.extractCommitFromLine(line)
|
||||
matchingCommit := commits[i]
|
||||
commit.Action = matchingCommit.Action
|
||||
commit.Status = matchingCommit.Status
|
||||
hydratedCommits = append(hydratedCommits, commit)
|
||||
i++
|
||||
fullCommits[commit.Sha] = commit
|
||||
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
|
||||
}
|
||||
|
||||
@@ -305,15 +313,17 @@ func (self *CommitLoader) getInteractiveRebasingCommits() ([]*models.Commit, err
|
||||
}
|
||||
|
||||
for _, t := range todos {
|
||||
if t.Commit == "" {
|
||||
if t.Command == todo.UpdateRef {
|
||||
t.Msg = strings.TrimPrefix(t.Ref, "refs/heads/")
|
||||
} else 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: "rebasing",
|
||||
Action: t.Command.String(),
|
||||
Status: models.StatusRebasing,
|
||||
Action: t.Command,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -332,47 +342,69 @@ func (self *CommitLoader) commitFromPatch(content string) *models.Commit {
|
||||
return &models.Commit{
|
||||
Sha: sha,
|
||||
Name: name,
|
||||
Status: "rebasing",
|
||||
Status: models.StatusRebasing,
|
||||
}
|
||||
}
|
||||
|
||||
func (self *CommitLoader) setCommitMergedStatuses(refName string, commits []*models.Commit) ([]*models.Commit, error) {
|
||||
ancestor, err := self.getMergeBase(refName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
func (self *CommitLoader) setCommitMergedStatuses(refName string, commits []*models.Commit) []*models.Commit {
|
||||
ancestor := self.getMergeBase(refName)
|
||||
if ancestor == "" {
|
||||
return commits, nil
|
||||
return commits
|
||||
}
|
||||
passedAncestor := false
|
||||
for i, commit := range commits {
|
||||
if strings.HasPrefix(ancestor, commit.Sha) {
|
||||
passedAncestor = true
|
||||
}
|
||||
if commit.Status != "pushed" {
|
||||
if commit.Status != models.StatusPushed && commit.Status != models.StatusUnpushed {
|
||||
continue
|
||||
}
|
||||
if passedAncestor {
|
||||
commits[i].Status = "merged"
|
||||
commits[i].Status = models.StatusMerged
|
||||
}
|
||||
}
|
||||
return commits, nil
|
||||
return commits
|
||||
}
|
||||
|
||||
func (self *CommitLoader) getMergeBase(refName string) (string, error) {
|
||||
info, err := self.getCurrentBranchInfo()
|
||||
func (self *CommitLoader) getMergeBase(refName string) string {
|
||||
if self.quotedMainBranches == nil {
|
||||
self.quotedMainBranches = lo.ToPtr(self.getExistingMainBranches())
|
||||
}
|
||||
|
||||
if *self.quotedMainBranches == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
// We pass all configured main branches to the merge-base call; git will
|
||||
// return the base commit for the closest one.
|
||||
|
||||
output, err := self.cmd.New(
|
||||
NewGitCmd("merge-base").
|
||||
Arg(self.cmd.Quote(refName)).
|
||||
Arg(*self.quotedMainBranches).
|
||||
ToString(),
|
||||
).DontLog().RunWithOutput()
|
||||
if err != nil {
|
||||
return "", err
|
||||
// If there's an error, it must be because one of the main branches that
|
||||
// used to exist when we called getExistingMainBranches() was deleted
|
||||
// meanwhile. To fix this for next time, throw away our cache.
|
||||
self.quotedMainBranches = nil
|
||||
}
|
||||
return ignoringWarnings(output)
|
||||
}
|
||||
|
||||
baseBranch := "master"
|
||||
if strings.HasPrefix(info.RefName, "feature/") {
|
||||
baseBranch = "develop"
|
||||
}
|
||||
|
||||
// swallowing error because it's not a big deal; probably because there are no commits yet
|
||||
output, _ := self.cmd.New(fmt.Sprintf("git merge-base %s %s", self.cmd.Quote(refName), self.cmd.Quote(baseBranch))).DontLog().RunWithOutput()
|
||||
return ignoringWarnings(output), nil
|
||||
func (self *CommitLoader) getExistingMainBranches() string {
|
||||
return strings.Join(
|
||||
lo.FilterMap(self.UserConfig.Git.MainBranches,
|
||||
func(branchName string, _ int) (string, bool) {
|
||||
quotedRef := self.cmd.Quote("refs/heads/" + branchName)
|
||||
if err := self.cmd.New(
|
||||
NewGitCmd("rev-parse").Arg("--verify").Arg("--quiet").Arg(quotedRef).ToString(),
|
||||
).DontLog().Run(); err != nil {
|
||||
return "", false
|
||||
}
|
||||
return quotedRef, true
|
||||
}), " ")
|
||||
}
|
||||
|
||||
func ignoringWarnings(commandOutput string) string {
|
||||
@@ -390,9 +422,10 @@ func ignoringWarnings(commandOutput string) string {
|
||||
func (self *CommitLoader) getFirstPushedCommit(refName string) (string, error) {
|
||||
output, err := self.cmd.
|
||||
New(
|
||||
fmt.Sprintf("git merge-base %s %s@{u}",
|
||||
self.cmd.Quote(refName),
|
||||
self.cmd.Quote(strings.TrimPrefix(refName, "refs/heads/"))),
|
||||
NewGitCmd("merge-base").
|
||||
Arg(self.cmd.Quote(refName)).
|
||||
Arg(self.cmd.Quote(strings.TrimPrefix(refName, "refs/heads/")) + "@{u}").
|
||||
ToString(),
|
||||
).
|
||||
DontLog().
|
||||
RunWithOutput()
|
||||
@@ -405,42 +438,22 @@ func (self *CommitLoader) getFirstPushedCommit(refName string) (string, error) {
|
||||
|
||||
// getLog gets the git log.
|
||||
func (self *CommitLoader) getLogCmd(opts GetCommitsOptions) oscommands.ICmdObj {
|
||||
limitFlag := ""
|
||||
if opts.Limit {
|
||||
limitFlag = " -300"
|
||||
}
|
||||
|
||||
followFlag := ""
|
||||
filterFlag := ""
|
||||
if opts.FilterPath != "" {
|
||||
followFlag = " --follow"
|
||||
filterFlag = fmt.Sprintf(" %s", self.cmd.Quote(opts.FilterPath))
|
||||
}
|
||||
|
||||
config := self.UserConfig.Git.Log
|
||||
|
||||
orderFlag := ""
|
||||
if config.Order != "default" {
|
||||
orderFlag = " --" + config.Order
|
||||
}
|
||||
allFlag := ""
|
||||
if opts.All {
|
||||
allFlag = " --all"
|
||||
}
|
||||
cmdStr := NewGitCmd("log").
|
||||
Arg(self.cmd.Quote(opts.RefName)).
|
||||
ArgIf(config.Order != "default", "--"+config.Order).
|
||||
ArgIf(opts.All, "--all").
|
||||
Arg("--oneline").
|
||||
Arg(prettyFormat).
|
||||
Arg("--abbrev=40").
|
||||
ArgIf(opts.Limit, "-300").
|
||||
ArgIf(opts.FilterPath != "", "--follow").
|
||||
ArgIf(opts.FilterPath != "", self.cmd.Quote(opts.FilterPath)).
|
||||
Arg("--no-show-signature").
|
||||
ToString()
|
||||
|
||||
return self.cmd.New(
|
||||
fmt.Sprintf(
|
||||
"git log %s%s%s --oneline %s%s --abbrev=%d%s --no-show-signature --%s",
|
||||
self.cmd.Quote(opts.RefName),
|
||||
orderFlag,
|
||||
allFlag,
|
||||
prettyFormat,
|
||||
limitFlag,
|
||||
40,
|
||||
followFlag,
|
||||
filterFlag,
|
||||
),
|
||||
).DontLog()
|
||||
return self.cmd.New(cmdStr)
|
||||
}
|
||||
|
||||
const prettyFormat = `--pretty=format:"%H%x00%at%x00%aN%x00%ae%x00%d%x00%p%x00%s"`
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package git_commands
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
@@ -21,25 +22,26 @@ d8084cd558925eb7c9c38afeed5725c21653ab90|1640821426|Jesse Duffield|jessedduffiel
|
||||
3d4470a6c072208722e5ae9a54bcb9634959a1c5|1640748818|Jesse Duffield|jessedduffield@gmail.com||053a66a7be3da43aacdc|WIP
|
||||
053a66a7be3da43aacdc7aa78e1fe757b82c4dd2|1640739815|Jesse Duffield|jessedduffield@gmail.com||985fe482e806b172aea4|refactoring the config struct`, "|", "\x00", -1)
|
||||
|
||||
var singleCommitOutput = strings.Replace(`0eea75e8c631fba6b58135697835d58ba4c18dbc|1640826609|Jesse Duffield|jessedduffield@gmail.com| (HEAD -> better-tests)|b21997d6b4cbdf84b149|better typing for rebase mode`, "|", "\x00", -1)
|
||||
|
||||
func TestGetCommits(t *testing.T) {
|
||||
type scenario struct {
|
||||
testName string
|
||||
runner *oscommands.FakeCmdObjRunner
|
||||
expectedCommits []*models.Commit
|
||||
expectedError error
|
||||
logOrder string
|
||||
rebaseMode enums.RebaseMode
|
||||
currentBranchName string
|
||||
opts GetCommitsOptions
|
||||
testName string
|
||||
runner *oscommands.FakeCmdObjRunner
|
||||
expectedCommits []*models.Commit
|
||||
expectedError error
|
||||
logOrder string
|
||||
rebaseMode enums.RebaseMode
|
||||
opts GetCommitsOptions
|
||||
mainBranches []string
|
||||
}
|
||||
|
||||
scenarios := []scenario{
|
||||
{
|
||||
testName: "should return no commits if there are none",
|
||||
logOrder: "topo-order",
|
||||
rebaseMode: enums.REBASE_MODE_NONE,
|
||||
currentBranchName: "master",
|
||||
opts: GetCommitsOptions{RefName: "HEAD", IncludeRebaseCommits: false},
|
||||
testName: "should return no commits if there are none",
|
||||
logOrder: "topo-order",
|
||||
rebaseMode: enums.REBASE_MODE_NONE,
|
||||
opts: GetCommitsOptions{RefName: "HEAD", IncludeRebaseCommits: false},
|
||||
runner: oscommands.NewFakeRunner(t).
|
||||
Expect(`git merge-base "HEAD" "HEAD"@{u}`, "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", nil).
|
||||
Expect(`git log "HEAD" --topo-order --oneline --pretty=format:"%H%x00%at%x00%aN%x00%ae%x00%d%x00%p%x00%s" --abbrev=40 --no-show-signature --`, "", nil),
|
||||
@@ -48,11 +50,10 @@ func TestGetCommits(t *testing.T) {
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
testName: "should use proper upstream name for branch",
|
||||
logOrder: "topo-order",
|
||||
rebaseMode: enums.REBASE_MODE_NONE,
|
||||
currentBranchName: "mybranch",
|
||||
opts: GetCommitsOptions{RefName: "refs/heads/mybranch", IncludeRebaseCommits: false},
|
||||
testName: "should use proper upstream name for branch",
|
||||
logOrder: "topo-order",
|
||||
rebaseMode: enums.REBASE_MODE_NONE,
|
||||
opts: GetCommitsOptions{RefName: "refs/heads/mybranch", IncludeRebaseCommits: false},
|
||||
runner: oscommands.NewFakeRunner(t).
|
||||
Expect(`git merge-base "refs/heads/mybranch" "mybranch"@{u}`, "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", nil).
|
||||
Expect(`git log "refs/heads/mybranch" --topo-order --oneline --pretty=format:"%H%x00%at%x00%aN%x00%ae%x00%d%x00%p%x00%s" --abbrev=40 --no-show-signature --`, "", nil),
|
||||
@@ -61,25 +62,28 @@ func TestGetCommits(t *testing.T) {
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
testName: "should return commits if they are present",
|
||||
logOrder: "topo-order",
|
||||
rebaseMode: enums.REBASE_MODE_NONE,
|
||||
currentBranchName: "master",
|
||||
opts: GetCommitsOptions{RefName: "HEAD", IncludeRebaseCommits: false},
|
||||
testName: "should return commits if they are present",
|
||||
logOrder: "topo-order",
|
||||
rebaseMode: enums.REBASE_MODE_NONE,
|
||||
opts: GetCommitsOptions{RefName: "HEAD", IncludeRebaseCommits: false},
|
||||
mainBranches: []string{"master", "main"},
|
||||
runner: oscommands.NewFakeRunner(t).
|
||||
// here it's seeing which commits are yet to be pushed
|
||||
Expect(`git merge-base "HEAD" "HEAD"@{u}`, "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", nil).
|
||||
// here it's actually getting all the commits in a formatted form, one per line
|
||||
Expect(`git log "HEAD" --topo-order --oneline --pretty=format:"%H%x00%at%x00%aN%x00%ae%x00%d%x00%p%x00%s" --abbrev=40 --no-show-signature --`, commitsOutput, nil).
|
||||
// here it's testing which of the configured main branches exist
|
||||
Expect(`git rev-parse --verify --quiet "refs/heads/master"`, "", nil). // this one does
|
||||
Expect(`git rev-parse --verify --quiet "refs/heads/main"`, "", errors.New("error")). // this one doesn't
|
||||
// here it's seeing where our branch diverged from the master branch so that we can mark that commit and parent commits as 'merged'
|
||||
Expect(`git merge-base "HEAD" "master"`, "26c07b1ab33860a1a7591a0638f9925ccf497ffa", nil),
|
||||
Expect(`git merge-base "HEAD" "refs/heads/master"`, "26c07b1ab33860a1a7591a0638f9925ccf497ffa", nil),
|
||||
|
||||
expectedCommits: []*models.Commit{
|
||||
{
|
||||
Sha: "0eea75e8c631fba6b58135697835d58ba4c18dbc",
|
||||
Name: "better typing for rebase mode",
|
||||
Status: "unpushed",
|
||||
Action: "",
|
||||
Status: models.StatusUnpushed,
|
||||
Action: models.ActionNone,
|
||||
Tags: []string{},
|
||||
ExtraInfo: "(HEAD -> better-tests)",
|
||||
AuthorName: "Jesse Duffield",
|
||||
@@ -92,8 +96,8 @@ func TestGetCommits(t *testing.T) {
|
||||
{
|
||||
Sha: "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164",
|
||||
Name: "fix logging",
|
||||
Status: "pushed",
|
||||
Action: "",
|
||||
Status: models.StatusPushed,
|
||||
Action: models.ActionNone,
|
||||
Tags: []string{},
|
||||
ExtraInfo: "(origin/better-tests)",
|
||||
AuthorName: "Jesse Duffield",
|
||||
@@ -106,8 +110,8 @@ func TestGetCommits(t *testing.T) {
|
||||
{
|
||||
Sha: "e94e8fc5b6fab4cb755f29f1bdb3ee5e001df35c",
|
||||
Name: "refactor",
|
||||
Status: "pushed",
|
||||
Action: "",
|
||||
Status: models.StatusPushed,
|
||||
Action: models.ActionNone,
|
||||
Tags: []string{},
|
||||
ExtraInfo: "",
|
||||
AuthorName: "Jesse Duffield",
|
||||
@@ -120,8 +124,8 @@ func TestGetCommits(t *testing.T) {
|
||||
{
|
||||
Sha: "d8084cd558925eb7c9c38afeed5725c21653ab90",
|
||||
Name: "WIP",
|
||||
Status: "pushed",
|
||||
Action: "",
|
||||
Status: models.StatusPushed,
|
||||
Action: models.ActionNone,
|
||||
Tags: []string{},
|
||||
ExtraInfo: "",
|
||||
AuthorName: "Jesse Duffield",
|
||||
@@ -134,8 +138,8 @@ func TestGetCommits(t *testing.T) {
|
||||
{
|
||||
Sha: "65f910ebd85283b5cce9bf67d03d3f1a9ea3813a",
|
||||
Name: "WIP",
|
||||
Status: "pushed",
|
||||
Action: "",
|
||||
Status: models.StatusPushed,
|
||||
Action: models.ActionNone,
|
||||
Tags: []string{},
|
||||
ExtraInfo: "",
|
||||
AuthorName: "Jesse Duffield",
|
||||
@@ -148,8 +152,8 @@ func TestGetCommits(t *testing.T) {
|
||||
{
|
||||
Sha: "26c07b1ab33860a1a7591a0638f9925ccf497ffa",
|
||||
Name: "WIP",
|
||||
Status: "merged",
|
||||
Action: "",
|
||||
Status: models.StatusMerged,
|
||||
Action: models.ActionNone,
|
||||
Tags: []string{},
|
||||
ExtraInfo: "",
|
||||
AuthorName: "Jesse Duffield",
|
||||
@@ -162,8 +166,8 @@ func TestGetCommits(t *testing.T) {
|
||||
{
|
||||
Sha: "3d4470a6c072208722e5ae9a54bcb9634959a1c5",
|
||||
Name: "WIP",
|
||||
Status: "merged",
|
||||
Action: "",
|
||||
Status: models.StatusMerged,
|
||||
Action: models.ActionNone,
|
||||
Tags: []string{},
|
||||
ExtraInfo: "",
|
||||
AuthorName: "Jesse Duffield",
|
||||
@@ -176,8 +180,8 @@ func TestGetCommits(t *testing.T) {
|
||||
{
|
||||
Sha: "053a66a7be3da43aacdc7aa78e1fe757b82c4dd2",
|
||||
Name: "refactoring the config struct",
|
||||
Status: "merged",
|
||||
Action: "",
|
||||
Status: models.StatusMerged,
|
||||
Action: models.ActionNone,
|
||||
Tags: []string{},
|
||||
ExtraInfo: "",
|
||||
AuthorName: "Jesse Duffield",
|
||||
@@ -191,11 +195,80 @@ func TestGetCommits(t *testing.T) {
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
testName: "should not specify order if `log.order` is `default`",
|
||||
logOrder: "default",
|
||||
rebaseMode: enums.REBASE_MODE_NONE,
|
||||
currentBranchName: "master",
|
||||
opts: GetCommitsOptions{RefName: "HEAD", IncludeRebaseCommits: false},
|
||||
testName: "should not call merge-base for mainBranches if none exist",
|
||||
logOrder: "topo-order",
|
||||
rebaseMode: enums.REBASE_MODE_NONE,
|
||||
opts: GetCommitsOptions{RefName: "HEAD", IncludeRebaseCommits: false},
|
||||
mainBranches: []string{"master", "main"},
|
||||
runner: oscommands.NewFakeRunner(t).
|
||||
// here it's seeing which commits are yet to be pushed
|
||||
Expect(`git merge-base "HEAD" "HEAD"@{u}`, "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", nil).
|
||||
// here it's actually getting all the commits in a formatted form, one per line
|
||||
Expect(`git log "HEAD" --topo-order --oneline --pretty=format:"%H%x00%at%x00%aN%x00%ae%x00%d%x00%p%x00%s" --abbrev=40 --no-show-signature --`, singleCommitOutput, nil).
|
||||
// here it's testing which of the configured main branches exist; neither does
|
||||
Expect(`git rev-parse --verify --quiet "refs/heads/master"`, "", errors.New("error")).
|
||||
Expect(`git rev-parse --verify --quiet "refs/heads/main"`, "", errors.New("error")),
|
||||
|
||||
expectedCommits: []*models.Commit{
|
||||
{
|
||||
Sha: "0eea75e8c631fba6b58135697835d58ba4c18dbc",
|
||||
Name: "better typing for rebase mode",
|
||||
Status: models.StatusUnpushed,
|
||||
Action: models.ActionNone,
|
||||
Tags: []string{},
|
||||
ExtraInfo: "(HEAD -> better-tests)",
|
||||
AuthorName: "Jesse Duffield",
|
||||
AuthorEmail: "jessedduffield@gmail.com",
|
||||
UnixTimestamp: 1640826609,
|
||||
Parents: []string{
|
||||
"b21997d6b4cbdf84b149",
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
testName: "should call merge-base for all main branches that exist",
|
||||
logOrder: "topo-order",
|
||||
rebaseMode: enums.REBASE_MODE_NONE,
|
||||
opts: GetCommitsOptions{RefName: "HEAD", IncludeRebaseCommits: false},
|
||||
mainBranches: []string{"master", "main", "develop", "1.0-hotfixes"},
|
||||
runner: oscommands.NewFakeRunner(t).
|
||||
// here it's seeing which commits are yet to be pushed
|
||||
Expect(`git merge-base "HEAD" "HEAD"@{u}`, "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", nil).
|
||||
// here it's actually getting all the commits in a formatted form, one per line
|
||||
Expect(`git log "HEAD" --topo-order --oneline --pretty=format:"%H%x00%at%x00%aN%x00%ae%x00%d%x00%p%x00%s" --abbrev=40 --no-show-signature --`, singleCommitOutput, nil).
|
||||
// here it's testing which of the configured main branches exist
|
||||
Expect(`git rev-parse --verify --quiet "refs/heads/master"`, "", nil).
|
||||
Expect(`git rev-parse --verify --quiet "refs/heads/main"`, "", errors.New("error")).
|
||||
Expect(`git rev-parse --verify --quiet "refs/heads/develop"`, "", nil).
|
||||
Expect(`git rev-parse --verify --quiet "refs/heads/1.0-hotfixes"`, "", nil).
|
||||
// here it's seeing where our branch diverged from the master branch so that we can mark that commit and parent commits as 'merged'
|
||||
Expect(`git merge-base "HEAD" "refs/heads/master" "refs/heads/develop" "refs/heads/1.0-hotfixes"`, "26c07b1ab33860a1a7591a0638f9925ccf497ffa", nil),
|
||||
|
||||
expectedCommits: []*models.Commit{
|
||||
{
|
||||
Sha: "0eea75e8c631fba6b58135697835d58ba4c18dbc",
|
||||
Name: "better typing for rebase mode",
|
||||
Status: models.StatusUnpushed,
|
||||
Action: models.ActionNone,
|
||||
Tags: []string{},
|
||||
ExtraInfo: "(HEAD -> better-tests)",
|
||||
AuthorName: "Jesse Duffield",
|
||||
AuthorEmail: "jessedduffield@gmail.com",
|
||||
UnixTimestamp: 1640826609,
|
||||
Parents: []string{
|
||||
"b21997d6b4cbdf84b149",
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
testName: "should not specify order if `log.order` is `default`",
|
||||
logOrder: "default",
|
||||
rebaseMode: enums.REBASE_MODE_NONE,
|
||||
opts: GetCommitsOptions{RefName: "HEAD", IncludeRebaseCommits: false},
|
||||
runner: oscommands.NewFakeRunner(t).
|
||||
Expect(`git merge-base "HEAD" "HEAD"@{u}`, "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", nil).
|
||||
Expect(`git log "HEAD" --oneline --pretty=format:"%H%x00%at%x00%aN%x00%ae%x00%d%x00%p%x00%s" --abbrev=40 --no-show-signature --`, "", nil),
|
||||
@@ -204,11 +277,10 @@ func TestGetCommits(t *testing.T) {
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
testName: "should set filter path",
|
||||
logOrder: "default",
|
||||
rebaseMode: enums.REBASE_MODE_NONE,
|
||||
currentBranchName: "master",
|
||||
opts: GetCommitsOptions{RefName: "HEAD", FilterPath: "src"},
|
||||
testName: "should set filter path",
|
||||
logOrder: "default",
|
||||
rebaseMode: enums.REBASE_MODE_NONE,
|
||||
opts: GetCommitsOptions{RefName: "HEAD", FilterPath: "src"},
|
||||
runner: oscommands.NewFakeRunner(t).
|
||||
Expect(`git merge-base "HEAD" "HEAD"@{u}`, "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", nil).
|
||||
Expect(`git log "HEAD" --oneline --pretty=format:"%H%x00%at%x00%aN%x00%ae%x00%d%x00%p%x00%s" --abbrev=40 --follow --no-show-signature -- "src"`, "", nil),
|
||||
@@ -225,11 +297,8 @@ func TestGetCommits(t *testing.T) {
|
||||
common.UserConfig.Git.Log.Order = scenario.logOrder
|
||||
|
||||
builder := &CommitLoader{
|
||||
Common: common,
|
||||
cmd: oscommands.NewDummyCmdObjBuilder(scenario.runner),
|
||||
getCurrentBranchInfo: func() (BranchInfo, error) {
|
||||
return BranchInfo{RefName: scenario.currentBranchName, DisplayName: scenario.currentBranchName, DetachedHead: false}, nil
|
||||
},
|
||||
Common: common,
|
||||
cmd: oscommands.NewDummyCmdObjBuilder(scenario.runner),
|
||||
getRebaseMode: func() (enums.RebaseMode, error) { return scenario.rebaseMode, nil },
|
||||
dotGitDir: ".git",
|
||||
readFile: func(filename string) ([]byte, error) {
|
||||
@@ -240,6 +309,7 @@ func TestGetCommits(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
common.UserConfig.Git.MainBranches = scenario.mainBranches
|
||||
commits, err := builder.GetCommits(scenario.opts)
|
||||
|
||||
assert.Equal(t, scenario.expectedCommits, commits)
|
||||
|
||||
@@ -9,12 +9,32 @@ import (
|
||||
)
|
||||
|
||||
func TestCommitRewordCommit(t *testing.T) {
|
||||
runner := oscommands.NewFakeRunner(t).
|
||||
ExpectGitArgs([]string{"commit", "--allow-empty", "--amend", "--only", "-m", "test"}, "", nil)
|
||||
instance := buildCommitCommands(commonDeps{runner: runner})
|
||||
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})
|
||||
|
||||
assert.NoError(t, instance.RewordLastCommit("test"))
|
||||
runner.CheckForMissingCalls()
|
||||
assert.NoError(t, instance.RewordLastCommit(s.input))
|
||||
s.runner.CheckForMissingCalls()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommitResetToCommit(t *testing.T) {
|
||||
@@ -274,3 +294,40 @@ 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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,3 +150,9 @@ func buildBranchCommands(deps commonDeps) *BranchCommands {
|
||||
|
||||
return NewBranchCommands(gitCommon)
|
||||
}
|
||||
|
||||
func buildFlowCommands(deps commonDeps) *FlowCommands {
|
||||
gitCommon := buildGitCommon(deps)
|
||||
|
||||
return NewFlowCommands(gitCommon)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
package git_commands
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/git_config"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestStartCmdObj(t *testing.T) {
|
||||
scenarios := []struct {
|
||||
testName string
|
||||
branchType string
|
||||
name string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
testName: "basic",
|
||||
branchType: "feature",
|
||||
name: "test",
|
||||
expected: "git flow feature start test",
|
||||
},
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
s := s
|
||||
t.Run(s.testName, func(t *testing.T) {
|
||||
instance := buildFlowCommands(commonDeps{})
|
||||
|
||||
assert.Equal(t,
|
||||
instance.StartCmdObj(s.branchType, s.name).ToString(),
|
||||
s.expected,
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFinishCmdObj(t *testing.T) {
|
||||
scenarios := []struct {
|
||||
testName string
|
||||
branchName string
|
||||
expected string
|
||||
expectedError string
|
||||
gitConfigMockResponses map[string]string
|
||||
}{
|
||||
{
|
||||
testName: "not a git flow branch",
|
||||
branchName: "mybranch",
|
||||
expected: "",
|
||||
expectedError: "This does not seem to be a git flow branch",
|
||||
gitConfigMockResponses: nil,
|
||||
},
|
||||
{
|
||||
testName: "feature branch without config",
|
||||
branchName: "feature/mybranch",
|
||||
expected: "",
|
||||
expectedError: "This does not seem to be a git flow branch",
|
||||
gitConfigMockResponses: nil,
|
||||
},
|
||||
{
|
||||
testName: "feature branch with config",
|
||||
branchName: "feature/mybranch",
|
||||
expected: "git flow feature finish mybranch",
|
||||
expectedError: "",
|
||||
gitConfigMockResponses: map[string]string{
|
||||
"--local --get-regexp gitflow.prefix": "gitflow.prefix.feature feature/",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
s := s
|
||||
t.Run(s.testName, func(t *testing.T) {
|
||||
instance := buildFlowCommands(commonDeps{
|
||||
gitConfig: git_config.NewFakeGitConfig(s.gitConfigMockResponses),
|
||||
})
|
||||
|
||||
cmd, err := instance.FinishCmdObj(s.branchName)
|
||||
|
||||
if s.expectedError != "" {
|
||||
if err == nil {
|
||||
t.Errorf("Expected error, got nil")
|
||||
} else {
|
||||
assert.Equal(t, err.Error(), s.expectedError)
|
||||
}
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, cmd.ToString(), s.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package git_commands
|
||||
|
||||
import "fmt"
|
||||
|
||||
// convenience struct for building git commands. Especially useful when
|
||||
// including conditional args
|
||||
type GitCommandBuilder struct {
|
||||
command string
|
||||
}
|
||||
|
||||
func NewGitCmd(command string) *GitCommandBuilder {
|
||||
return &GitCommandBuilder{command: command}
|
||||
}
|
||||
|
||||
func (self *GitCommandBuilder) Arg(flag string) *GitCommandBuilder {
|
||||
if flag == "" {
|
||||
return self
|
||||
}
|
||||
|
||||
self.command += " " + flag
|
||||
|
||||
return self
|
||||
}
|
||||
|
||||
func (self *GitCommandBuilder) ArgIf(include bool, flag string) *GitCommandBuilder {
|
||||
if include {
|
||||
return self.Arg(flag)
|
||||
}
|
||||
|
||||
return self
|
||||
}
|
||||
|
||||
func (self *GitCommandBuilder) ArgIfElse(isTrue bool, onTrue string, onFalse string) *GitCommandBuilder {
|
||||
if isTrue {
|
||||
return self.Arg(onTrue)
|
||||
} else {
|
||||
return self.Arg(onFalse)
|
||||
}
|
||||
}
|
||||
|
||||
func (self *GitCommandBuilder) Args(args []string) *GitCommandBuilder {
|
||||
for _, arg := range args {
|
||||
self.Arg(arg)
|
||||
}
|
||||
|
||||
return self
|
||||
}
|
||||
|
||||
func (self *GitCommandBuilder) Config(value string) *GitCommandBuilder {
|
||||
// config settings come before the command
|
||||
self.command = fmt.Sprintf("-c %s %s", value, self.command)
|
||||
|
||||
return self
|
||||
}
|
||||
|
||||
func (self *GitCommandBuilder) RepoPath(value string) *GitCommandBuilder {
|
||||
// repo path comes before the command
|
||||
self.command = fmt.Sprintf("-C %s %s", value, self.command)
|
||||
|
||||
return self
|
||||
}
|
||||
|
||||
func (self *GitCommandBuilder) ToString() string {
|
||||
return "git " + self.command
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package git_commands
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestGitCommandBuilder(t *testing.T) {
|
||||
scenarios := []struct {
|
||||
input string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
input: NewGitCmd("push").
|
||||
Arg("--force-with-lease").
|
||||
Arg("--set-upstream").
|
||||
Arg("origin").
|
||||
Arg("master").
|
||||
ToString(),
|
||||
expected: "git push --force-with-lease --set-upstream origin master",
|
||||
},
|
||||
{
|
||||
input: NewGitCmd("push").ArgIf(true, "--test").ToString(),
|
||||
expected: "git push --test",
|
||||
},
|
||||
{
|
||||
input: NewGitCmd("push").ArgIf(false, "--test").ToString(),
|
||||
expected: "git push",
|
||||
},
|
||||
{
|
||||
input: NewGitCmd("push").ArgIfElse(true, "-b", "-a").ToString(),
|
||||
expected: "git push -b",
|
||||
},
|
||||
{
|
||||
input: NewGitCmd("push").ArgIfElse(false, "-a", "-b").ToString(),
|
||||
expected: "git push -b",
|
||||
},
|
||||
{
|
||||
input: NewGitCmd("push").Args([]string{"-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)
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,9 @@ package git_commands
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"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"
|
||||
@@ -103,15 +105,17 @@ func (self *PatchCommands) MovePatchToSelectedCommit(commits []*models.Commit, s
|
||||
|
||||
baseIndex := sourceCommitIdx + 1
|
||||
|
||||
todoLines := self.rebase.BuildTodoLines(commits[0:baseIndex], func(commit *models.Commit, i int) string {
|
||||
if i == sourceCommitIdx || i == destinationCommitIdx {
|
||||
return "edit"
|
||||
} else {
|
||||
return "pick"
|
||||
}
|
||||
})
|
||||
changes := []daemon.ChangeTodoAction{
|
||||
{Sha: commits[sourceCommitIdx].Sha, NewAction: todo.Edit},
|
||||
{Sha: commits[destinationCommitIdx].Sha, NewAction: todo.Edit},
|
||||
}
|
||||
self.os.LogCommand(logTodoChanges(changes), false)
|
||||
|
||||
err := self.rebase.PrepareInteractiveRebaseCommand(commits[baseIndex].Sha, todoLines, true).Run()
|
||||
err := self.rebase.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{
|
||||
baseShaOrRoot: commits[baseIndex].Sha,
|
||||
overrideEditor: true,
|
||||
instruction: daemon.NewChangeTodoActionsInstruction(changes),
|
||||
}).Run()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
+130
-145
@@ -2,15 +2,17 @@ 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 {
|
||||
@@ -54,12 +56,16 @@ func (self *RebaseCommands) RewordCommit(commits []*models.Commit, index int, me
|
||||
}
|
||||
|
||||
func (self *RebaseCommands) RewordCommitInEditor(commits []*models.Commit, index int) (oscommands.ICmdObj, error) {
|
||||
todo, sha, err := self.BuildSingleActionTodo(commits, index, "reword")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
changes := []daemon.ChangeTodoAction{{
|
||||
Sha: commits[index].Sha,
|
||||
NewAction: todo.Reword,
|
||||
}}
|
||||
self.os.LogCommand(logTodoChanges(changes), false)
|
||||
|
||||
return self.PrepareInteractiveRebaseCommand(sha, todo, false), nil
|
||||
return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{
|
||||
baseShaOrRoot: getBaseShaOrRoot(commits, index+1),
|
||||
instruction: daemon.NewChangeTodoActionsInstruction(changes),
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (self *RebaseCommands) ResetCommitAuthor(commits []*models.Commit, index int) error {
|
||||
@@ -75,7 +81,7 @@ func (self *RebaseCommands) SetCommitAuthor(commits []*models.Commit, index int,
|
||||
}
|
||||
|
||||
func (self *RebaseCommands) GenericAmend(commits []*models.Commit, index int, f func() error) error {
|
||||
if index == 0 {
|
||||
if models.IsHeadCommit(commits, index) {
|
||||
// we've selected the top commit so no rebase is required
|
||||
return f()
|
||||
}
|
||||
@@ -95,41 +101,79 @@ 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)
|
||||
|
||||
return self.PrepareInteractiveRebaseCommand(baseShaOrRoot, todoLines, true).Run()
|
||||
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()
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
func (self *RebaseCommands) MoveCommitUp(commits []*models.Commit, index int) error {
|
||||
baseShaOrRoot := getBaseShaOrRoot(commits, index+1)
|
||||
|
||||
return self.PrepareInteractiveRebaseCommand(sha, todo, true).Run()
|
||||
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) InteractiveRebaseBreakAfter(commits []*models.Commit, index int) error {
|
||||
todo, sha, err := self.BuildSingleActionTodo(commits, index-1, "pick")
|
||||
if err != nil {
|
||||
return err
|
||||
func (self *RebaseCommands) InteractiveRebase(commits []*models.Commit, index int, action todo.TodoCommand) error {
|
||||
baseIndex := index + 1
|
||||
if action == todo.Squash || action == todo.Fixup {
|
||||
baseIndex++
|
||||
}
|
||||
|
||||
todo = append(todo, TodoLine{Action: "break", Commit: nil})
|
||||
return self.PrepareInteractiveRebaseCommand(sha, todo, true).Run()
|
||||
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()
|
||||
}
|
||||
|
||||
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 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
|
||||
}
|
||||
|
||||
// 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(baseShaOrRoot string, todoLines []TodoLine, overrideEditor bool) oscommands.ICmdObj {
|
||||
todo := self.buildTodo(todoLines)
|
||||
func (self *RebaseCommands) PrepareInteractiveRebaseCommand(opts PrepareInteractiveRebaseCommandOpts) oscommands.ICmdObj {
|
||||
ex := oscommands.GetLazygitPath()
|
||||
|
||||
debug := "FALSE"
|
||||
@@ -137,119 +181,81 @@ func (self *RebaseCommands) PrepareInteractiveRebaseCommand(baseShaOrRoot string
|
||||
debug = "TRUE"
|
||||
}
|
||||
|
||||
cmdStr := fmt.Sprintf("git rebase --interactive --autostash --keep-empty --empty=keep --no-autosquash %s", baseShaOrRoot)
|
||||
emptyArg := " --empty=keep"
|
||||
if self.version.IsOlderThan(2, 26, 0) {
|
||||
emptyArg = ""
|
||||
}
|
||||
|
||||
rebaseMergesArg := " --rebase-merges"
|
||||
if self.version.IsOlderThan(2, 22, 0) {
|
||||
rebaseMergesArg = ""
|
||||
}
|
||||
|
||||
cmdStr := fmt.Sprintf("git rebase --interactive --autostash --keep-empty%s --no-autosquash%s %s",
|
||||
emptyArg, rebaseMergesArg, opts.baseShaOrRoot)
|
||||
self.Log.WithField("command", cmdStr).Debug("RunCommand")
|
||||
|
||||
cmdObj := self.cmd.New(cmdStr)
|
||||
|
||||
gitSequenceEditor := ex
|
||||
if todo == "" {
|
||||
gitSequenceEditor = "true"
|
||||
|
||||
if opts.instruction != nil {
|
||||
cmdObj.AddEnvVars(daemon.ToEnvVars(opts.instruction)...)
|
||||
} else {
|
||||
self.os.LogCommand(fmt.Sprintf("Creating TODO file for interactive rebase: \n\n%s", todo), false)
|
||||
gitSequenceEditor = "true"
|
||||
}
|
||||
|
||||
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 overrideEditor {
|
||||
if opts.overrideEditor {
|
||||
cmdObj.AddEnvVars("GIT_EDITOR=" + ex)
|
||||
}
|
||||
|
||||
return cmdObj
|
||||
}
|
||||
|
||||
// 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 {
|
||||
func (self *RebaseCommands) AmendTo(commits []*models.Commit, commitIndex int) error {
|
||||
commit := commits[commitIndex]
|
||||
|
||||
if err := self.commit.CreateFixupCommit(commit.Sha); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
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)
|
||||
// Get the sha of the commit we just created
|
||||
fixupSha, err := self.cmd.New("git rev-parse --verify HEAD").RunWithOutput()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
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)
|
||||
return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{
|
||||
baseShaOrRoot: getBaseShaOrRoot(commits, commitIndex+1),
|
||||
overrideEditor: true,
|
||||
instruction: daemon.NewMoveFixupCommitDownInstruction(commit.Sha, fixupSha),
|
||||
}).Run()
|
||||
}
|
||||
|
||||
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
|
||||
// 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)
|
||||
}
|
||||
|
||||
// MoveTodoDown moves a rebase todo item down by one position
|
||||
func (self *RebaseCommands) MoveTodoDown(index int) error {
|
||||
func (self *RebaseCommands) MoveTodoDown(commit *models.Commit) error {
|
||||
fileName := filepath.Join(self.dotGitDir, "rebase-merge/git-rebase-todo")
|
||||
bytes, err := os.ReadFile(fileName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return utils.MoveTodoDown(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)
|
||||
// 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)
|
||||
}
|
||||
|
||||
// SquashAllAboveFixupCommits squashes all fixup! commits above the given one
|
||||
@@ -283,17 +289,22 @@ func (self *RebaseCommands) BeginInteractiveRebaseForCommit(commits []*models.Co
|
||||
return errors.New(self.Tr.DisabledForGPG)
|
||||
}
|
||||
|
||||
todo, sha, err := self.BuildSingleActionTodo(commits, commitIndex, "edit")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
changes := []daemon.ChangeTodoAction{{
|
||||
Sha: commits[commitIndex].Sha,
|
||||
NewAction: todo.Edit,
|
||||
}}
|
||||
self.os.LogCommand(logTodoChanges(changes), false)
|
||||
|
||||
return self.PrepareInteractiveRebaseCommand(sha, todo, true).Run()
|
||||
return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{
|
||||
baseShaOrRoot: getBaseShaOrRoot(commits, commitIndex+1),
|
||||
overrideEditor: true,
|
||||
instruction: daemon.NewChangeTodoActionsInstruction(changes),
|
||||
}).Run()
|
||||
}
|
||||
|
||||
// RebaseBranch interactive rebases onto a branch
|
||||
func (self *RebaseCommands) RebaseBranch(branchName string) error {
|
||||
return self.PrepareInteractiveRebaseCommand(branchName, nil, false).Run()
|
||||
return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{baseShaOrRoot: branchName}).Run()
|
||||
}
|
||||
|
||||
func (self *RebaseCommands) GenericMergeOrRebaseActionCmdObj(commandType string, command string) oscommands.ICmdObj {
|
||||
@@ -334,15 +345,16 @@ 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()
|
||||
}
|
||||
|
||||
@@ -376,42 +388,15 @@ 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 {
|
||||
todoLines := self.BuildTodoLinesSingleAction(commits, "pick")
|
||||
|
||||
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()
|
||||
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)
|
||||
|
||||
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"
|
||||
}
|
||||
return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{
|
||||
baseShaOrRoot: "HEAD",
|
||||
instruction: daemon.NewCherryPickCommitsInstruction(commits),
|
||||
}).Run()
|
||||
}
|
||||
|
||||
// we can't start an interactive rebase from the first commit without passing the
|
||||
|
||||
@@ -2,6 +2,7 @@ package git_commands
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/go-errors/errors"
|
||||
@@ -15,37 +16,60 @@ import (
|
||||
|
||||
func TestRebaseRebaseBranch(t *testing.T) {
|
||||
type scenario struct {
|
||||
testName string
|
||||
arg string
|
||||
runner *oscommands.FakeCmdObjRunner
|
||||
test func(error)
|
||||
testName string
|
||||
arg string
|
||||
gitVersion *GitVersion
|
||||
runner *oscommands.FakeCmdObjRunner
|
||||
test func(error)
|
||||
}
|
||||
|
||||
scenarios := []scenario{
|
||||
{
|
||||
testName: "successful rebase",
|
||||
arg: "master",
|
||||
testName: "successful rebase",
|
||||
arg: "master",
|
||||
gitVersion: &GitVersion{2, 26, 0, ""},
|
||||
runner: oscommands.NewFakeRunner(t).
|
||||
Expect(`git rebase --interactive --autostash --keep-empty --empty=keep --no-autosquash master`, "", nil),
|
||||
Expect(`git rebase --interactive --autostash --keep-empty --empty=keep --no-autosquash --rebase-merges master`, "", nil),
|
||||
test: func(err error) {
|
||||
assert.NoError(t, err)
|
||||
},
|
||||
},
|
||||
{
|
||||
testName: "unsuccessful rebase",
|
||||
arg: "master",
|
||||
testName: "unsuccessful rebase",
|
||||
arg: "master",
|
||||
gitVersion: &GitVersion{2, 26, 0, ""},
|
||||
runner: oscommands.NewFakeRunner(t).
|
||||
Expect(`git rebase --interactive --autostash --keep-empty --empty=keep --no-autosquash master`, "", errors.New("error")),
|
||||
Expect(`git rebase --interactive --autostash --keep-empty --empty=keep --no-autosquash --rebase-merges master`, "", errors.New("error")),
|
||||
test: func(err error) {
|
||||
assert.Error(t, err)
|
||||
},
|
||||
},
|
||||
{
|
||||
testName: "successful rebase (< 2.26.0)",
|
||||
arg: "master",
|
||||
gitVersion: &GitVersion{2, 25, 5, ""},
|
||||
runner: oscommands.NewFakeRunner(t).
|
||||
Expect(`git rebase --interactive --autostash --keep-empty --no-autosquash --rebase-merges master`, "", nil),
|
||||
test: func(err error) {
|
||||
assert.NoError(t, err)
|
||||
},
|
||||
},
|
||||
{
|
||||
testName: "successful rebase (< 2.22.0)",
|
||||
arg: "master",
|
||||
gitVersion: &GitVersion{2, 21, 9, ""},
|
||||
runner: oscommands.NewFakeRunner(t).
|
||||
Expect(`git rebase --interactive --autostash --keep-empty --no-autosquash master`, "", nil),
|
||||
test: func(err error) {
|
||||
assert.NoError(t, err)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
s := s
|
||||
t.Run(s.testName, func(t *testing.T) {
|
||||
instance := buildRebaseCommands(commonDeps{runner: s.runner})
|
||||
instance := buildRebaseCommands(commonDeps{runner: s.runner, gitVersion: s.gitVersion})
|
||||
s.test(instance.RebaseBranch(s.arg))
|
||||
})
|
||||
}
|
||||
@@ -63,7 +87,7 @@ func TestRebaseSkipEditorCommand(t *testing.T) {
|
||||
`^EDITOR=.*$`,
|
||||
`^GIT_EDITOR=.*$`,
|
||||
`^GIT_SEQUENCE_EDITOR=.*$`,
|
||||
"^" + daemon.DaemonKindEnvKey + "=" + string(daemon.ExitImmediately) + "$",
|
||||
"^" + daemon.DaemonKindEnvKey + "=" + strconv.Itoa(int(daemon.DaemonKindExitImmediately)) + "$",
|
||||
} {
|
||||
regexStr := regexStr
|
||||
foundMatch := lo.ContainsBy(envVars, func(envVar string) bool {
|
||||
@@ -125,7 +149,7 @@ func TestRebaseDiscardOldFileChanges(t *testing.T) {
|
||||
commitIndex: 0,
|
||||
fileName: "test999.txt",
|
||||
runner: oscommands.NewFakeRunner(t).
|
||||
Expect(`git rebase --interactive --autostash --keep-empty --empty=keep --no-autosquash abcdef`, "", nil).
|
||||
Expect(`git rebase --interactive --autostash --keep-empty --empty=keep --no-autosquash --rebase-merges abcdef`, "", nil).
|
||||
Expect(`git cat-file -e HEAD^:"test999.txt"`, "", nil).
|
||||
Expect(`git checkout HEAD^ -- "test999.txt"`, "", nil).
|
||||
Expect(`git commit --amend --no-edit --allow-empty`, "", nil).
|
||||
@@ -142,8 +166,9 @@ func TestRebaseDiscardOldFileChanges(t *testing.T) {
|
||||
s := s
|
||||
t.Run(s.testName, func(t *testing.T) {
|
||||
instance := buildRebaseCommands(commonDeps{
|
||||
runner: s.runner,
|
||||
gitConfig: git_config.NewFakeGitConfig(s.gitConfigMockResponses),
|
||||
runner: s.runner,
|
||||
gitVersion: &GitVersion{2, 26, 0, ""},
|
||||
gitConfig: git_config.NewFakeGitConfig(s.gitConfigMockResponses),
|
||||
})
|
||||
|
||||
s.test(instance.DiscardOldFileChanges(s.commits, s.commitIndex, s.fileName))
|
||||
|
||||
@@ -52,7 +52,7 @@ func (self *ReflogCommitLoader) GetReflogCommits(lastReflogCommit *models.Commit
|
||||
Sha: fields[0],
|
||||
Name: fields[2],
|
||||
UnixTimestamp: int64(unixTimestamp),
|
||||
Status: "reflog",
|
||||
Status: models.StatusReflog,
|
||||
Parents: parents,
|
||||
}
|
||||
|
||||
|
||||
@@ -51,35 +51,35 @@ func TestGetReflogCommits(t *testing.T) {
|
||||
{
|
||||
Sha: "c3c4b66b64c97ffeecde",
|
||||
Name: "checkout: moving from A to B",
|
||||
Status: "reflog",
|
||||
Status: models.StatusReflog,
|
||||
UnixTimestamp: 1643150483,
|
||||
Parents: []string{"51baa8c1"},
|
||||
},
|
||||
{
|
||||
Sha: "c3c4b66b64c97ffeecde",
|
||||
Name: "checkout: moving from B to A",
|
||||
Status: "reflog",
|
||||
Status: models.StatusReflog,
|
||||
UnixTimestamp: 1643150483,
|
||||
Parents: []string{"51baa8c1"},
|
||||
},
|
||||
{
|
||||
Sha: "c3c4b66b64c97ffeecde",
|
||||
Name: "checkout: moving from A to B",
|
||||
Status: "reflog",
|
||||
Status: models.StatusReflog,
|
||||
UnixTimestamp: 1643150483,
|
||||
Parents: []string{"51baa8c1"},
|
||||
},
|
||||
{
|
||||
Sha: "c3c4b66b64c97ffeecde",
|
||||
Name: "checkout: moving from master to A",
|
||||
Status: "reflog",
|
||||
Status: models.StatusReflog,
|
||||
UnixTimestamp: 1643150483,
|
||||
Parents: []string{"51baa8c1"},
|
||||
},
|
||||
{
|
||||
Sha: "f4ddf2f0d4be4ccc7efa",
|
||||
Name: "checkout: moving from A to master",
|
||||
Status: "reflog",
|
||||
Status: models.StatusReflog,
|
||||
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: "reflog",
|
||||
Status: models.StatusReflog,
|
||||
UnixTimestamp: 1643150483,
|
||||
Parents: []string{"51baa8c1"},
|
||||
},
|
||||
@@ -103,7 +103,7 @@ func TestGetReflogCommits(t *testing.T) {
|
||||
{
|
||||
Sha: "c3c4b66b64c97ffeecde",
|
||||
Name: "checkout: moving from A to B",
|
||||
Status: "reflog",
|
||||
Status: models.StatusReflog,
|
||||
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: "reflog",
|
||||
Status: models.StatusReflog,
|
||||
UnixTimestamp: 1643150483,
|
||||
Parents: []string{"51baa8c1"},
|
||||
},
|
||||
@@ -128,7 +128,7 @@ func TestGetReflogCommits(t *testing.T) {
|
||||
{
|
||||
Sha: "c3c4b66b64c97ffeecde",
|
||||
Name: "checkout: moving from A to B",
|
||||
Status: "reflog",
|
||||
Status: models.StatusReflog,
|
||||
UnixTimestamp: 1643150483,
|
||||
Parents: []string{"51baa8c1"},
|
||||
},
|
||||
|
||||
@@ -59,8 +59,19 @@ func (self *StashCommands) Sha(index int) (string, error) {
|
||||
return strings.Trim(sha, "\r\n"), err
|
||||
}
|
||||
|
||||
func (self *StashCommands) ShowStashEntryCmdObj(index int) oscommands.ICmdObj {
|
||||
cmdStr := fmt.Sprintf("git stash show -p --stat --color=%s --unified=%d stash@{%d}", self.UserConfig.Git.Paging.ColorArg, self.UserConfig.Git.DiffContextSize, index)
|
||||
func (self *StashCommands) ShowStashEntryCmdObj(index int, ignoreWhitespace bool) oscommands.ICmdObj {
|
||||
ignoreWhitespaceFlag := ""
|
||||
if ignoreWhitespace {
|
||||
ignoreWhitespaceFlag = " --ignore-all-space"
|
||||
}
|
||||
|
||||
cmdStr := fmt.Sprintf(
|
||||
"git stash show -p --stat --color=%s --unified=%d%s stash@{%d}",
|
||||
self.UserConfig.Git.Paging.ColorArg,
|
||||
self.UserConfig.Git.DiffContextSize,
|
||||
ignoreWhitespaceFlag,
|
||||
index,
|
||||
)
|
||||
|
||||
return self.cmd.New(cmdStr).DontLog()
|
||||
}
|
||||
|
||||
@@ -99,24 +99,34 @@ func TestStashSha(t *testing.T) {
|
||||
|
||||
func TestStashStashEntryCmdObj(t *testing.T) {
|
||||
type scenario struct {
|
||||
testName string
|
||||
index int
|
||||
contextSize int
|
||||
expected string
|
||||
testName string
|
||||
index int
|
||||
contextSize int
|
||||
ignoreWhitespace bool
|
||||
expected string
|
||||
}
|
||||
|
||||
scenarios := []scenario{
|
||||
{
|
||||
testName: "Default case",
|
||||
index: 5,
|
||||
contextSize: 3,
|
||||
expected: "git stash show -p --stat --color=always --unified=3 stash@{5}",
|
||||
testName: "Default case",
|
||||
index: 5,
|
||||
contextSize: 3,
|
||||
ignoreWhitespace: false,
|
||||
expected: "git stash show -p --stat --color=always --unified=3 stash@{5}",
|
||||
},
|
||||
{
|
||||
testName: "Show diff with custom context size",
|
||||
index: 5,
|
||||
contextSize: 77,
|
||||
expected: "git stash show -p --stat --color=always --unified=77 stash@{5}",
|
||||
testName: "Show diff with custom context size",
|
||||
index: 5,
|
||||
contextSize: 77,
|
||||
ignoreWhitespace: false,
|
||||
expected: "git stash show -p --stat --color=always --unified=77 stash@{5}",
|
||||
},
|
||||
{
|
||||
testName: "Default case",
|
||||
index: 5,
|
||||
contextSize: 3,
|
||||
ignoreWhitespace: true,
|
||||
expected: "git stash show -p --stat --color=always --unified=3 --ignore-all-space stash@{5}",
|
||||
},
|
||||
}
|
||||
|
||||
@@ -127,7 +137,7 @@ func TestStashStashEntryCmdObj(t *testing.T) {
|
||||
userConfig.Git.DiffContextSize = s.contextSize
|
||||
instance := buildStashCommands(commonDeps{userConfig: userConfig})
|
||||
|
||||
cmdStr := instance.ShowStashEntryCmdObj(s.index).ToString()
|
||||
cmdStr := instance.ShowStashEntryCmdObj(s.index, s.ignoreWhitespace).ToString()
|
||||
assert.Equal(t, s.expected, cmdStr)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -29,11 +29,7 @@ func (self *SyncCommands) PushCmdObj(opts PushOpts) (oscommands.ICmdObj, error)
|
||||
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 += " --force-with-lease"
|
||||
}
|
||||
|
||||
if opts.SetUpstream {
|
||||
|
||||
@@ -10,7 +10,6 @@ import (
|
||||
func TestSyncPush(t *testing.T) {
|
||||
type scenario struct {
|
||||
testName string
|
||||
version *GitVersion
|
||||
opts PushOpts
|
||||
test func(oscommands.ICmdObj, error)
|
||||
}
|
||||
@@ -18,7 +17,6 @@ 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")
|
||||
@@ -27,25 +25,14 @@ 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",
|
||||
@@ -58,7 +45,6 @@ func TestSyncPush(t *testing.T) {
|
||||
},
|
||||
{
|
||||
testName: "Push with force disabled, setting upstream",
|
||||
version: &GitVersion{2, 29, 3, ""},
|
||||
opts: PushOpts{
|
||||
Force: false,
|
||||
UpstreamRemote: "origin",
|
||||
@@ -72,7 +58,6 @@ func TestSyncPush(t *testing.T) {
|
||||
},
|
||||
{
|
||||
testName: "Push with force enabled, setting upstream",
|
||||
version: &GitVersion{2, 29, 3, ""},
|
||||
opts: PushOpts{
|
||||
Force: true,
|
||||
UpstreamRemote: "origin",
|
||||
@@ -84,23 +69,8 @@ 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: "",
|
||||
@@ -117,7 +87,7 @@ func TestSyncPush(t *testing.T) {
|
||||
for _, s := range scenarios {
|
||||
s := s
|
||||
t.Run(s.testName, func(t *testing.T) {
|
||||
instance := buildSyncCommands(commonDeps{gitVersion: s.version})
|
||||
instance := buildSyncCommands(commonDeps{})
|
||||
s.test(instance.PushCmdObj(s.opts))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -35,5 +35,5 @@ func (self *TagCommands) Delete(tagName string) error {
|
||||
}
|
||||
|
||||
func (self *TagCommands) Push(remoteName string, tagName string) error {
|
||||
return self.cmd.New(fmt.Sprintf("git push %s %s", self.cmd.Quote(remoteName), self.cmd.Quote(tagName))).PromptOnCredentialRequest().WithMutex(self.syncMutex).Run()
|
||||
return self.cmd.New(fmt.Sprintf("git push %s tag %s", self.cmd.Quote(remoteName), self.cmd.Quote(tagName))).PromptOnCredentialRequest().WithMutex(self.syncMutex).Run()
|
||||
}
|
||||
|
||||
@@ -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,3 +65,7 @@ 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)
|
||||
}
|
||||
|
||||
@@ -64,12 +64,22 @@ 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{
|
||||
@@ -93,4 +103,9 @@ 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, etc.
|
||||
// This package is for handling logic specific to a git hosting service like github, gitlab, bitbucket, gitea, 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,6 +264,60 @@ 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",
|
||||
@@ -310,7 +364,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"},
|
||||
expectedLoggedErrors: []string{"Unknown git service type: 'noservice'. Expected one of github, bitbucket, gitlab, azuredevops, bitbucketServer, gitea"},
|
||||
},
|
||||
{
|
||||
testName: "Escapes reserved URL characters in from branch name",
|
||||
|
||||
@@ -3,18 +3,37 @@ 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 string // one of "unpushed", "pushed", "merged", "rebasing" or "selected"
|
||||
Action string // one of "", "pick", "edit", "squash", "reword", "drop", "fixup"
|
||||
Status CommitStatus
|
||||
Action todo.TodoCommand
|
||||
Tags []string
|
||||
ExtraInfo string // something like 'HEAD -> master, tag: v0.15.2'
|
||||
AuthorName string // something like 'Jesse Duffield'
|
||||
@@ -63,7 +82,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 != ""
|
||||
return c.Action != ActionNone
|
||||
}
|
||||
|
||||
func IsHeadCommit(commits []*Commit, index int) bool {
|
||||
|
||||
@@ -35,13 +35,15 @@ 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"),
|
||||
"vi": standardTerminalEditorPreset("vi"),
|
||||
"vim": standardTerminalEditorPreset("vim"),
|
||||
"nvim": standardTerminalEditorPreset("nvim"),
|
||||
"emacs": standardTerminalEditorPreset("emacs"),
|
||||
"nano": standardTerminalEditorPreset("nano"),
|
||||
"kakoune": standardTerminalEditorPreset("kakoune"),
|
||||
"vscode": {
|
||||
editTemplate: "code --reuse-window -- {{filename}}",
|
||||
editAtLineTemplate: "code --reuse-window --goto -- {{filename}}:{{line}}",
|
||||
@@ -70,6 +72,7 @@ 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",
|
||||
|
||||
+44
-40
@@ -27,32 +27,33 @@ 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"`
|
||||
CommandLogSize int `yaml:"commandLogSize"`
|
||||
SplitDiff string `yaml:"splitDiff"`
|
||||
SkipRewordInEditorWarning bool `yaml:"skipRewordInEditorWarning"`
|
||||
WindowSize string `yaml:"windowSize"`
|
||||
Border string `yaml:"border"`
|
||||
AuthorColors map[string]string `yaml:"authorColors"`
|
||||
BranchColors map[string]string `yaml:"branchColors"`
|
||||
ScrollHeight int `yaml:"scrollHeight"`
|
||||
ScrollPastBottom bool `yaml:"scrollPastBottom"`
|
||||
MouseEvents bool `yaml:"mouseEvents"`
|
||||
SkipUnstageLineWarning bool `yaml:"skipUnstageLineWarning"`
|
||||
SkipStashWarning bool `yaml:"skipStashWarning"`
|
||||
SidePanelWidth float64 `yaml:"sidePanelWidth"`
|
||||
ExpandFocusedSidePanel bool `yaml:"expandFocusedSidePanel"`
|
||||
MainPanelSplitMode string `yaml:"mainPanelSplitMode"`
|
||||
Language string `yaml:"language"`
|
||||
TimeFormat string `yaml:"timeFormat"`
|
||||
Theme ThemeConfig `yaml:"theme"`
|
||||
CommitLength CommitLengthConfig `yaml:"commitLength"`
|
||||
SkipNoStagedFilesWarning bool `yaml:"skipNoStagedFilesWarning"`
|
||||
ShowListFooter bool `yaml:"showListFooter"`
|
||||
ShowFileTree bool `yaml:"showFileTree"`
|
||||
ShowRandomTip bool `yaml:"showRandomTip"`
|
||||
ShowCommandLog bool `yaml:"showCommandLog"`
|
||||
ShowBottomLine bool `yaml:"showBottomLine"`
|
||||
ShowIcons bool `yaml:"showIcons"`
|
||||
ExperimentalShowBranchHeads bool `yaml:"experimentalShowBranchHeads"`
|
||||
CommandLogSize int `yaml:"commandLogSize"`
|
||||
SplitDiff string `yaml:"splitDiff"`
|
||||
SkipRewordInEditorWarning bool `yaml:"skipRewordInEditorWarning"`
|
||||
WindowSize string `yaml:"windowSize"`
|
||||
Border string `yaml:"border"`
|
||||
}
|
||||
|
||||
type ThemeConfig struct {
|
||||
@@ -75,6 +76,7 @@ type GitConfig struct {
|
||||
Paging PagingConfig `yaml:"paging"`
|
||||
Commit CommitConfig `yaml:"commit"`
|
||||
Merging MergingConfig `yaml:"merging"`
|
||||
MainBranches []string `yaml:"mainBranches"`
|
||||
SkipHookPrefix string `yaml:"skipHookPrefix"`
|
||||
AutoFetch bool `yaml:"autoFetch"`
|
||||
AutoRefresh bool `yaml:"autoRefresh"`
|
||||
@@ -165,6 +167,7 @@ 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"`
|
||||
@@ -193,7 +196,6 @@ 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"`
|
||||
@@ -408,18 +410,19 @@ 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,
|
||||
CommandLogSize: 8,
|
||||
SplitDiff: "auto",
|
||||
SkipRewordInEditorWarning: false,
|
||||
Border: "single",
|
||||
CommitLength: CommitLengthConfig{Show: true},
|
||||
SkipNoStagedFilesWarning: false,
|
||||
ShowListFooter: true,
|
||||
ShowCommandLog: true,
|
||||
ShowBottomLine: true,
|
||||
ShowFileTree: true,
|
||||
ShowRandomTip: true,
|
||||
ShowIcons: false,
|
||||
ExperimentalShowBranchHeads: false,
|
||||
CommandLogSize: 8,
|
||||
SplitDiff: "auto",
|
||||
SkipRewordInEditorWarning: false,
|
||||
Border: "single",
|
||||
},
|
||||
Git: GitConfig{
|
||||
Paging: PagingConfig{
|
||||
@@ -441,6 +444,7 @@ func GetDefaultConfig() *UserConfig {
|
||||
ShowWholeGraph: false,
|
||||
},
|
||||
SkipHookPrefix: "WIP",
|
||||
MainBranches: []string{"master", "main"},
|
||||
AutoFetch: true,
|
||||
AutoRefresh: true,
|
||||
BranchLogCmd: "git log --graph --color=always --abbrev-commit --decorate --date=relative --pretty=medium {{branchName}} --",
|
||||
@@ -492,6 +496,7 @@ func GetDefaultConfig() *UserConfig {
|
||||
Select: "<space>",
|
||||
GoInto: "<enter>",
|
||||
Confirm: "<enter>",
|
||||
ConfirmInEditor: "<a-enter>",
|
||||
Remove: "d",
|
||||
New: "n",
|
||||
Edit: "e",
|
||||
@@ -520,7 +525,6 @@ func GetDefaultConfig() *UserConfig {
|
||||
DiffingMenuAlt: "<c-e>",
|
||||
CopyToClipboard: "<c-o>",
|
||||
SubmitEditorText: "<enter>",
|
||||
AppendNewline: "<a-enter>",
|
||||
ExtrasMenu: "@",
|
||||
ToggleWhitespaceInDiffView: "<c-w>",
|
||||
IncreaseContextInDiffView: "}",
|
||||
|
||||
@@ -1,131 +0,0 @@
|
||||
package gui
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/jesseduffield/generics/slices"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
"github.com/sasha-s/go-deadlock"
|
||||
)
|
||||
|
||||
// statusManager's job is to handle rendering of loading states and toast notifications
|
||||
// that you see at the bottom left of the screen.
|
||||
type statusManager struct {
|
||||
statuses []appStatus
|
||||
nextId int
|
||||
mutex deadlock.Mutex
|
||||
}
|
||||
|
||||
type appStatus struct {
|
||||
message string
|
||||
statusType string
|
||||
id int
|
||||
}
|
||||
|
||||
func (m *statusManager) removeStatus(id int) {
|
||||
m.mutex.Lock()
|
||||
defer m.mutex.Unlock()
|
||||
|
||||
m.statuses = slices.Filter(m.statuses, func(status appStatus) bool {
|
||||
return status.id != id
|
||||
})
|
||||
}
|
||||
|
||||
func (m *statusManager) addWaitingStatus(message string) int {
|
||||
m.mutex.Lock()
|
||||
defer m.mutex.Unlock()
|
||||
|
||||
m.nextId += 1
|
||||
id := m.nextId
|
||||
|
||||
newStatus := appStatus{
|
||||
message: message,
|
||||
statusType: "waiting",
|
||||
id: id,
|
||||
}
|
||||
m.statuses = append([]appStatus{newStatus}, m.statuses...)
|
||||
|
||||
return id
|
||||
}
|
||||
|
||||
func (m *statusManager) addToastStatus(message string) int {
|
||||
m.mutex.Lock()
|
||||
defer m.mutex.Unlock()
|
||||
|
||||
m.nextId++
|
||||
id := m.nextId
|
||||
|
||||
newStatus := appStatus{
|
||||
message: message,
|
||||
statusType: "toast",
|
||||
id: id,
|
||||
}
|
||||
m.statuses = append([]appStatus{newStatus}, m.statuses...)
|
||||
|
||||
go func() {
|
||||
time.Sleep(time.Second * 2)
|
||||
|
||||
m.removeStatus(id)
|
||||
}()
|
||||
|
||||
return id
|
||||
}
|
||||
|
||||
func (m *statusManager) getStatusString() string {
|
||||
if len(m.statuses) == 0 {
|
||||
return ""
|
||||
}
|
||||
topStatus := m.statuses[0]
|
||||
if topStatus.statusType == "waiting" {
|
||||
return topStatus.message + " " + utils.Loader()
|
||||
}
|
||||
return topStatus.message
|
||||
}
|
||||
|
||||
func (m *statusManager) showStatus() bool {
|
||||
return len(m.statuses) > 0
|
||||
}
|
||||
|
||||
func (gui *Gui) toast(message string) {
|
||||
gui.statusManager.addToastStatus(message)
|
||||
|
||||
gui.renderAppStatus()
|
||||
}
|
||||
|
||||
func (gui *Gui) renderAppStatus() {
|
||||
go utils.Safe(func() {
|
||||
ticker := time.NewTicker(time.Millisecond * 50)
|
||||
defer ticker.Stop()
|
||||
for range ticker.C {
|
||||
appStatus := gui.statusManager.getStatusString()
|
||||
gui.c.OnUIThread(func() error {
|
||||
return gui.renderString(gui.Views.AppStatus, appStatus)
|
||||
})
|
||||
|
||||
if appStatus == "" {
|
||||
return
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// withWaitingStatus wraps a function and shows a waiting status while the function is still executing
|
||||
func (gui *Gui) withWaitingStatus(message string, f func() error) error {
|
||||
go utils.Safe(func() {
|
||||
id := gui.statusManager.addWaitingStatus(message)
|
||||
|
||||
defer func() {
|
||||
gui.statusManager.removeStatus(id)
|
||||
}()
|
||||
|
||||
gui.renderAppStatus()
|
||||
|
||||
if err := f(); err != nil {
|
||||
gui.c.OnUIThread(func() error {
|
||||
return gui.c.Error(err)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
+42
-17
@@ -4,18 +4,33 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
)
|
||||
|
||||
func (gui *Gui) startBackgroundRoutines() {
|
||||
userConfig := gui.UserConfig
|
||||
type BackgroundRoutineMgr struct {
|
||||
gui *Gui
|
||||
|
||||
// if we've suspended the gui (e.g. because we've switched to a subprocess)
|
||||
// we typically want to pause some things that are running like background
|
||||
// file refreshes
|
||||
pauseBackgroundThreads bool
|
||||
}
|
||||
|
||||
func (self *BackgroundRoutineMgr) PauseBackgroundThreads(pause bool) {
|
||||
self.pauseBackgroundThreads = pause
|
||||
}
|
||||
|
||||
func (self *BackgroundRoutineMgr) startBackgroundRoutines() {
|
||||
userConfig := self.gui.UserConfig
|
||||
|
||||
if userConfig.Git.AutoFetch {
|
||||
fetchInterval := userConfig.Refresher.FetchInterval
|
||||
if fetchInterval > 0 {
|
||||
go utils.Safe(gui.startBackgroundFetch)
|
||||
go utils.Safe(self.startBackgroundFetch)
|
||||
} else {
|
||||
gui.c.Log.Errorf(
|
||||
self.gui.c.Log.Errorf(
|
||||
"Value of config option 'refresher.fetchInterval' (%d) is invalid, disabling auto-fetch",
|
||||
fetchInterval)
|
||||
}
|
||||
@@ -24,42 +39,44 @@ func (gui *Gui) startBackgroundRoutines() {
|
||||
if userConfig.Git.AutoRefresh {
|
||||
refreshInterval := userConfig.Refresher.RefreshInterval
|
||||
if refreshInterval > 0 {
|
||||
gui.goEvery(time.Second*time.Duration(refreshInterval), gui.stopChan, gui.refreshFilesAndSubmodules)
|
||||
self.goEvery(time.Second*time.Duration(refreshInterval), self.gui.stopChan, func() error {
|
||||
return self.gui.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.FILES}})
|
||||
})
|
||||
} else {
|
||||
gui.c.Log.Errorf(
|
||||
self.gui.c.Log.Errorf(
|
||||
"Value of config option 'refresher.refreshInterval' (%d) is invalid, disabling auto-refresh",
|
||||
refreshInterval)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (gui *Gui) startBackgroundFetch() {
|
||||
gui.waitForIntro.Wait()
|
||||
isNew := gui.IsNewRepo
|
||||
userConfig := gui.UserConfig
|
||||
func (self *BackgroundRoutineMgr) startBackgroundFetch() {
|
||||
self.gui.waitForIntro.Wait()
|
||||
isNew := self.gui.IsNewRepo
|
||||
userConfig := self.gui.UserConfig
|
||||
if !isNew {
|
||||
time.After(time.Duration(userConfig.Refresher.FetchInterval) * time.Second)
|
||||
}
|
||||
err := gui.backgroundFetch()
|
||||
err := self.backgroundFetch()
|
||||
if err != nil && strings.Contains(err.Error(), "exit status 128") && isNew {
|
||||
_ = gui.c.Alert(gui.c.Tr.NoAutomaticGitFetchTitle, gui.c.Tr.NoAutomaticGitFetchBody)
|
||||
_ = self.gui.c.Alert(self.gui.c.Tr.NoAutomaticGitFetchTitle, self.gui.c.Tr.NoAutomaticGitFetchBody)
|
||||
} else {
|
||||
gui.goEvery(time.Second*time.Duration(userConfig.Refresher.FetchInterval), gui.stopChan, func() error {
|
||||
err := gui.backgroundFetch()
|
||||
gui.render()
|
||||
self.goEvery(time.Second*time.Duration(userConfig.Refresher.FetchInterval), self.gui.stopChan, func() error {
|
||||
err := self.backgroundFetch()
|
||||
self.gui.c.Render()
|
||||
return err
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (gui *Gui) goEvery(interval time.Duration, stop chan struct{}, function func() error) {
|
||||
func (self *BackgroundRoutineMgr) goEvery(interval time.Duration, stop chan struct{}, function func() error) {
|
||||
go utils.Safe(func() {
|
||||
ticker := time.NewTicker(interval)
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
if gui.PauseBackgroundThreads {
|
||||
if self.pauseBackgroundThreads {
|
||||
continue
|
||||
}
|
||||
_ = function()
|
||||
@@ -69,3 +86,11 @@ func (gui *Gui) goEvery(interval time.Duration, stop chan struct{}, function fun
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (self *BackgroundRoutineMgr) backgroundFetch() (err error) {
|
||||
err = self.gui.git.Sync.Fetch(git_commands.FetchOptions{Background: true})
|
||||
|
||||
_ = self.gui.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.BRANCHES, types.COMMITS, types.REMOTES, types.TAGS}, Mode: types.ASYNC})
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
package gui
|
||||
|
||||
import "github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
|
||||
func (gui *Gui) branchesRenderToMain() error {
|
||||
var task types.UpdateTask
|
||||
branch := gui.State.Contexts.Branches.GetSelected()
|
||||
if branch == nil {
|
||||
task = types.NewRenderStringTask(gui.c.Tr.NoBranchesThisRepo)
|
||||
} else {
|
||||
cmdObj := gui.git.Branch.GetGraphCmdObj(branch.FullRefName())
|
||||
|
||||
task = types.NewRunPtyTask(cmdObj.GetCmd())
|
||||
}
|
||||
|
||||
return gui.c.RenderToMainViews(types.RefreshMainOpts{
|
||||
Pair: gui.c.MainViewPairs().Normal,
|
||||
Main: &types.ViewUpdateOpts{
|
||||
Title: gui.c.Tr.LogTitle,
|
||||
Task: task,
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -30,6 +30,7 @@ func (gui *Gui) LogAction(action string) {
|
||||
|
||||
gui.Views.Extras.Autoscroll = true
|
||||
|
||||
gui.GuiLog = append(gui.GuiLog, action)
|
||||
fmt.Fprint(gui.Views.Extras, "\n"+style.FgYellow.Sprint(action))
|
||||
}
|
||||
|
||||
@@ -46,7 +47,7 @@ func (gui *Gui) LogCommand(cmdStr string, commandLine bool) {
|
||||
// we style it differently to communicate that
|
||||
textStyle = style.FgMagenta
|
||||
}
|
||||
gui.CmdLog = append(gui.CmdLog, cmdStr)
|
||||
gui.GuiLog = append(gui.GuiLog, cmdStr)
|
||||
indentedCmdStr := " " + strings.Replace(cmdStr, "\n", "\n ", -1)
|
||||
fmt.Fprint(gui.Views.Extras, "\n"+textStyle.Sprint(indentedCmdStr))
|
||||
}
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
package gui
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/controllers"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
)
|
||||
|
||||
func (gui *Gui) commitFilesRenderToMain() error {
|
||||
node := gui.State.Contexts.CommitFiles.GetSelected()
|
||||
if node == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
ref := gui.State.Contexts.CommitFiles.GetRef()
|
||||
to := ref.RefName()
|
||||
from, reverse := gui.State.Modes.Diffing.GetFromAndReverseArgsForDiff(ref.ParentRefName())
|
||||
|
||||
cmdObj := gui.git.WorkingTree.ShowFileDiffCmdObj(from, to, reverse, node.GetPath(), false,
|
||||
gui.IgnoreWhitespaceInDiffView)
|
||||
task := types.NewRunPtyTask(cmdObj.GetCmd())
|
||||
|
||||
pair := gui.c.MainViewPairs().Normal
|
||||
if node.File != nil {
|
||||
pair = gui.c.MainViewPairs().PatchBuilding
|
||||
}
|
||||
|
||||
return gui.c.RenderToMainViews(types.RefreshMainOpts{
|
||||
Pair: pair,
|
||||
Main: &types.ViewUpdateOpts{
|
||||
Title: gui.Tr.Patch,
|
||||
Task: task,
|
||||
},
|
||||
Secondary: gui.secondaryPatchPanelUpdateOpts(),
|
||||
})
|
||||
}
|
||||
|
||||
func (gui *Gui) SwitchToCommitFilesContext(opts controllers.SwitchToCommitFilesContextOpts) error {
|
||||
gui.State.Contexts.CommitFiles.SetSelectedLineIdx(0)
|
||||
gui.State.Contexts.CommitFiles.SetRef(opts.Ref)
|
||||
gui.State.Contexts.CommitFiles.SetTitleRef(opts.Ref.Description())
|
||||
gui.State.Contexts.CommitFiles.SetCanRebase(opts.CanRebase)
|
||||
gui.State.Contexts.CommitFiles.SetParentContext(opts.Context)
|
||||
gui.State.Contexts.CommitFiles.SetWindowName(opts.Context.GetWindowName())
|
||||
|
||||
if err := gui.c.Refresh(types.RefreshOptions{
|
||||
Scope: []types.RefreshableView{types.COMMIT_FILES},
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return gui.c.PushContext(gui.State.Contexts.CommitFiles)
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
package gui
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/keybindings"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
)
|
||||
|
||||
func (gui *Gui) handleCommitMessageFocused() error {
|
||||
message := utils.ResolvePlaceholderString(
|
||||
gui.c.Tr.CommitMessageConfirm,
|
||||
map[string]string{
|
||||
"keyBindClose": keybindings.Label(gui.c.UserConfig.Keybinding.Universal.Return),
|
||||
"keyBindConfirm": keybindings.Label(gui.c.UserConfig.Keybinding.Universal.Confirm),
|
||||
"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) + " "
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
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()
|
||||
}
|
||||
@@ -1,317 +0,0 @@
|
||||
package gui
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/keybindings"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/style"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/jesseduffield/lazygit/pkg/theme"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
"github.com/mattn/go-runewidth"
|
||||
)
|
||||
|
||||
// This file is for the rendering of confirmation panels along with setting and handling associated
|
||||
// keybindings.
|
||||
|
||||
func (gui *Gui) wrappedConfirmationFunction(cancel context.CancelFunc, function func() error) func() error {
|
||||
return func() error {
|
||||
cancel()
|
||||
|
||||
if err := gui.c.PopContext(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if function != nil {
|
||||
if err := function(); err != nil {
|
||||
return gui.c.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (gui *Gui) wrappedPromptConfirmationFunction(cancel context.CancelFunc, function func(string) error, getResponse func() string) func() error {
|
||||
return func() error {
|
||||
cancel()
|
||||
|
||||
if err := gui.c.PopContext(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if function != nil {
|
||||
if err := function(getResponse()); err != nil {
|
||||
return gui.c.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (gui *Gui) deactivateConfirmationPrompt() {
|
||||
gui.Mutexes.PopupMutex.Lock()
|
||||
gui.State.CurrentPopupOpts = nil
|
||||
gui.Mutexes.PopupMutex.Unlock()
|
||||
|
||||
gui.Views.Confirmation.Visible = false
|
||||
gui.Views.Suggestions.Visible = false
|
||||
|
||||
gui.clearConfirmationViewKeyBindings()
|
||||
}
|
||||
|
||||
func (gui *Gui) getMessageHeight(wrap bool, message string, width int) int {
|
||||
lines := strings.Split(message, "\n")
|
||||
lineCount := 0
|
||||
// if we need to wrap, calculate height to fit content within view's width
|
||||
if wrap {
|
||||
for _, line := range lines {
|
||||
lineCount += runewidth.StringWidth(line)/width + 1
|
||||
}
|
||||
} else {
|
||||
lineCount = len(lines)
|
||||
}
|
||||
return lineCount
|
||||
}
|
||||
|
||||
func (gui *Gui) 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)
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
package constants
|
||||
|
||||
const SEARCH_PREFIX = "search: "
|
||||
+154
-206
@@ -1,14 +1,13 @@
|
||||
package gui
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"strings"
|
||||
"errors"
|
||||
"sync"
|
||||
|
||||
"github.com/jesseduffield/generics/maps"
|
||||
"github.com/jesseduffield/generics/slices"
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/context"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
// This file is for the management of contexts. There is a context stack such that
|
||||
@@ -16,46 +15,66 @@ import (
|
||||
// you in the menu context. When contexts are activated/deactivated certain things need
|
||||
// to happen like showing/hiding views and rendering content.
|
||||
|
||||
func (gui *Gui) popupViewNames() []string {
|
||||
popups := slices.Filter(gui.State.Contexts.Flatten(), func(c types.Context) bool {
|
||||
return c.GetKind() == types.PERSISTENT_POPUP || c.GetKind() == types.TEMPORARY_POPUP
|
||||
})
|
||||
type ContextMgr struct {
|
||||
ContextStack []types.Context
|
||||
sync.RWMutex
|
||||
gui *Gui
|
||||
|
||||
return slices.Map(popups, func(c types.Context) string {
|
||||
return c.GetViewName()
|
||||
})
|
||||
allContexts *context.ContextTree
|
||||
}
|
||||
|
||||
// use replaceContext when you don't want to return to the original context upon
|
||||
func NewContextMgr(
|
||||
gui *Gui,
|
||||
allContexts *context.ContextTree,
|
||||
) *ContextMgr {
|
||||
return &ContextMgr{
|
||||
ContextStack: []types.Context{},
|
||||
RWMutex: sync.RWMutex{},
|
||||
gui: gui,
|
||||
allContexts: allContexts,
|
||||
}
|
||||
}
|
||||
|
||||
// use when you don't want to return to the original context upon
|
||||
// hitting escape: you want to go that context's parent instead.
|
||||
func (gui *Gui) replaceContext(c types.Context) error {
|
||||
func (self *ContextMgr) Replace(c types.Context) error {
|
||||
if !c.IsFocusable() {
|
||||
return nil
|
||||
}
|
||||
|
||||
gui.State.ContextManager.Lock()
|
||||
self.Lock()
|
||||
|
||||
if len(gui.State.ContextManager.ContextStack) == 0 {
|
||||
gui.State.ContextManager.ContextStack = []types.Context{c}
|
||||
if len(self.ContextStack) == 0 {
|
||||
self.ContextStack = []types.Context{c}
|
||||
} else {
|
||||
// replace the last item with the given item
|
||||
gui.State.ContextManager.ContextStack = append(gui.State.ContextManager.ContextStack[0:len(gui.State.ContextManager.ContextStack)-1], c)
|
||||
self.ContextStack = append(self.ContextStack[0:len(self.ContextStack)-1], c)
|
||||
}
|
||||
|
||||
defer gui.State.ContextManager.Unlock()
|
||||
defer self.Unlock()
|
||||
|
||||
return gui.activateContext(c, types.OnFocusOpts{})
|
||||
return self.ActivateContext(c, types.OnFocusOpts{})
|
||||
}
|
||||
|
||||
func (gui *Gui) pushContext(c types.Context, opts types.OnFocusOpts) error {
|
||||
func (self *ContextMgr) Push(c types.Context, opts ...types.OnFocusOpts) error {
|
||||
if len(opts) > 1 {
|
||||
return errors.New("cannot pass multiple opts to Push")
|
||||
}
|
||||
|
||||
singleOpts := types.OnFocusOpts{}
|
||||
if len(opts) > 0 {
|
||||
// using triple dot but you should only ever pass one of these opt structs
|
||||
singleOpts = opts[0]
|
||||
}
|
||||
|
||||
if !c.IsFocusable() {
|
||||
return nil
|
||||
}
|
||||
|
||||
contextsToDeactivate, contextToActivate := gui.pushToContextStack(c)
|
||||
contextsToDeactivate, contextToActivate := self.pushToContextStack(c)
|
||||
|
||||
for _, contextToDeactivate := range contextsToDeactivate {
|
||||
if err := gui.deactivateContext(contextToDeactivate, types.OnFocusLostOpts{NewContextKey: c.GetKey()}); err != nil {
|
||||
if err := self.deactivateContext(contextToDeactivate, types.OnFocusLostOpts{NewContextKey: c.GetKey()}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -64,43 +83,45 @@ func (gui *Gui) pushContext(c types.Context, opts types.OnFocusOpts) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
return gui.activateContext(contextToActivate, opts)
|
||||
return self.ActivateContext(contextToActivate, singleOpts)
|
||||
}
|
||||
|
||||
// Adjusts the context stack based on the context that's being pushed and
|
||||
// returns (contexts to deactivate, context to activate)
|
||||
func (gui *Gui) pushToContextStack(c types.Context) ([]types.Context, types.Context) {
|
||||
func (self *ContextMgr) pushToContextStack(c types.Context) ([]types.Context, types.Context) {
|
||||
contextsToDeactivate := []types.Context{}
|
||||
|
||||
gui.State.ContextManager.Lock()
|
||||
defer gui.State.ContextManager.Unlock()
|
||||
self.Lock()
|
||||
defer self.Unlock()
|
||||
|
||||
if len(gui.State.ContextManager.ContextStack) > 0 &&
|
||||
c == gui.State.ContextManager.ContextStack[len(gui.State.ContextManager.ContextStack)-1] {
|
||||
if len(self.ContextStack) > 0 &&
|
||||
c.GetKey() == self.ContextStack[len(self.ContextStack)-1].GetKey() {
|
||||
// Context being pushed is already on top of the stack: nothing to
|
||||
// deactivate or activate
|
||||
return contextsToDeactivate, nil
|
||||
}
|
||||
|
||||
if len(gui.State.ContextManager.ContextStack) == 0 {
|
||||
gui.State.ContextManager.ContextStack = append(gui.State.ContextManager.ContextStack, c)
|
||||
if len(self.ContextStack) == 0 {
|
||||
self.ContextStack = append(self.ContextStack, c)
|
||||
} else if c.GetKind() == types.SIDE_CONTEXT {
|
||||
// if we are switching to a side context, remove all other contexts in the stack
|
||||
contextsToDeactivate = gui.State.ContextManager.ContextStack
|
||||
gui.State.ContextManager.ContextStack = []types.Context{c}
|
||||
contextsToDeactivate = lo.Filter(self.ContextStack, func(context types.Context, _ int) bool {
|
||||
return context.GetKey() != c.GetKey()
|
||||
})
|
||||
self.ContextStack = []types.Context{c}
|
||||
} else if c.GetKind() == types.MAIN_CONTEXT {
|
||||
// if we're switching to a main context, remove all other main contexts in the stack
|
||||
contextsToKeep := []types.Context{}
|
||||
for _, stackContext := range gui.State.ContextManager.ContextStack {
|
||||
for _, stackContext := range self.ContextStack {
|
||||
if stackContext.GetKind() == types.MAIN_CONTEXT {
|
||||
contextsToDeactivate = append(contextsToDeactivate, stackContext)
|
||||
} else {
|
||||
contextsToKeep = append(contextsToKeep, stackContext)
|
||||
}
|
||||
}
|
||||
gui.State.ContextManager.ContextStack = append(contextsToKeep, c)
|
||||
self.ContextStack = append(contextsToKeep, c)
|
||||
} else {
|
||||
topContext := gui.currentContextWithoutLock()
|
||||
topContext := self.currentContextWithoutLock()
|
||||
|
||||
// if we're pushing the same context on, we do nothing.
|
||||
if topContext.GetKey() != c.GetKey() {
|
||||
@@ -113,44 +134,74 @@ func (gui *Gui) pushToContextStack(c types.Context) ([]types.Context, types.Cont
|
||||
(topContext.GetKind() == types.MAIN_CONTEXT && c.GetKind() == types.MAIN_CONTEXT) {
|
||||
|
||||
contextsToDeactivate = append(contextsToDeactivate, topContext)
|
||||
_, gui.State.ContextManager.ContextStack = slices.Pop(gui.State.ContextManager.ContextStack)
|
||||
_, self.ContextStack = slices.Pop(self.ContextStack)
|
||||
}
|
||||
|
||||
gui.State.ContextManager.ContextStack = append(gui.State.ContextManager.ContextStack, c)
|
||||
self.ContextStack = append(self.ContextStack, c)
|
||||
}
|
||||
}
|
||||
|
||||
return contextsToDeactivate, c
|
||||
}
|
||||
|
||||
func (gui *Gui) popContext() error {
|
||||
gui.State.ContextManager.Lock()
|
||||
func (self *ContextMgr) Pop() error {
|
||||
self.Lock()
|
||||
|
||||
if len(gui.State.ContextManager.ContextStack) == 1 {
|
||||
if len(self.ContextStack) == 1 {
|
||||
// cannot escape from bottommost context
|
||||
gui.State.ContextManager.Unlock()
|
||||
self.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
var currentContext types.Context
|
||||
currentContext, gui.State.ContextManager.ContextStack = slices.Pop(gui.State.ContextManager.ContextStack)
|
||||
currentContext, self.ContextStack = slices.Pop(self.ContextStack)
|
||||
|
||||
newContext := gui.State.ContextManager.ContextStack[len(gui.State.ContextManager.ContextStack)-1]
|
||||
newContext := self.ContextStack[len(self.ContextStack)-1]
|
||||
|
||||
gui.State.ContextManager.Unlock()
|
||||
self.Unlock()
|
||||
|
||||
if err := gui.deactivateContext(currentContext, types.OnFocusLostOpts{NewContextKey: newContext.GetKey()}); err != nil {
|
||||
if err := self.deactivateContext(currentContext, types.OnFocusLostOpts{NewContextKey: newContext.GetKey()}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return gui.activateContext(newContext, types.OnFocusOpts{})
|
||||
return self.ActivateContext(newContext, types.OnFocusOpts{})
|
||||
}
|
||||
|
||||
func (gui *Gui) deactivateContext(c types.Context, opts types.OnFocusLostOpts) error {
|
||||
view, _ := gui.g.View(c.GetViewName())
|
||||
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())
|
||||
|
||||
if view != nil && view.IsSearching() {
|
||||
if err := gui.onSearchEscape(); err != nil {
|
||||
if err := self.gui.onSearchEscape(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -169,34 +220,17 @@ func (gui *Gui) deactivateContext(c types.Context, opts types.OnFocusLostOpts) e
|
||||
return nil
|
||||
}
|
||||
|
||||
// postRefreshUpdate is to be called on a context after the state that it depends on has been refreshed
|
||||
// if the context's view is set to another context we do nothing.
|
||||
// if the context's view is the current view we trigger a focus; re-selecting the current item.
|
||||
func (gui *Gui) postRefreshUpdate(c types.Context) error {
|
||||
if err := c.HandleRender(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if gui.currentViewName() == c.GetViewName() {
|
||||
if err := c.HandleFocus(types.OnFocusOpts{}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) activateContext(c types.Context, opts types.OnFocusOpts) error {
|
||||
func (self *ContextMgr) ActivateContext(c types.Context, opts types.OnFocusOpts) error {
|
||||
viewName := c.GetViewName()
|
||||
v, err := gui.g.View(viewName)
|
||||
v, err := self.gui.c.GocuiGui().View(viewName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
gui.setWindowContext(c)
|
||||
self.gui.helpers.Window.SetWindowContext(c)
|
||||
|
||||
gui.moveToTopOfWindow(c)
|
||||
if _, err := gui.g.SetCurrentView(viewName); err != nil {
|
||||
self.gui.helpers.Window.MoveToTopOfWindow(c)
|
||||
if _, err := self.gui.c.GocuiGui().SetCurrentView(viewName); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -207,14 +241,9 @@ func (gui *Gui) activateContext(c types.Context, opts types.OnFocusOpts) error {
|
||||
|
||||
v.Visible = true
|
||||
|
||||
gui.g.Cursor = v.Editable
|
||||
self.gui.c.GocuiGui().Cursor = v.Editable
|
||||
|
||||
// render the options available for the current context at the bottom of the screen
|
||||
optionsMap := c.GetOptionsMap()
|
||||
if optionsMap == nil {
|
||||
optionsMap = gui.globalOptionsMap()
|
||||
}
|
||||
gui.renderOptionsMap(optionsMap)
|
||||
self.gui.renderContextOptionsMap(c)
|
||||
|
||||
if err := c.HandleFocus(opts); err != nil {
|
||||
return err
|
||||
@@ -223,63 +252,27 @@ func (gui *Gui) activateContext(c types.Context, opts types.OnFocusOpts) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) optionsMapToString(optionsMap map[string]string) string {
|
||||
options := maps.MapToSlice(optionsMap, func(key string, description string) string {
|
||||
return key + ": " + description
|
||||
})
|
||||
sort.Strings(options)
|
||||
return strings.Join(options, ", ")
|
||||
func (self *ContextMgr) Current() types.Context {
|
||||
self.RLock()
|
||||
defer self.RUnlock()
|
||||
|
||||
return self.currentContextWithoutLock()
|
||||
}
|
||||
|
||||
func (gui *Gui) renderOptionsMap(optionsMap map[string]string) {
|
||||
_ = gui.renderString(gui.Views.Options, gui.optionsMapToString(optionsMap))
|
||||
}
|
||||
|
||||
// // currently unused
|
||||
// func (gui *Gui) renderContextStack() string {
|
||||
// result := ""
|
||||
// for _, context := range gui.State.ContextManager.ContextStack {
|
||||
// result += string(context.GetKey()) + "\n"
|
||||
// }
|
||||
// return result
|
||||
// }
|
||||
|
||||
func (gui *Gui) currentContext() types.Context {
|
||||
gui.State.ContextManager.RLock()
|
||||
defer gui.State.ContextManager.RUnlock()
|
||||
|
||||
return gui.currentContextWithoutLock()
|
||||
}
|
||||
|
||||
func (gui *Gui) currentContextWithoutLock() types.Context {
|
||||
if len(gui.State.ContextManager.ContextStack) == 0 {
|
||||
return gui.defaultSideContext()
|
||||
func (self *ContextMgr) currentContextWithoutLock() types.Context {
|
||||
if len(self.ContextStack) == 0 {
|
||||
return self.gui.defaultSideContext()
|
||||
}
|
||||
|
||||
return gui.State.ContextManager.ContextStack[len(gui.State.ContextManager.ContextStack)-1]
|
||||
return self.ContextStack[len(self.ContextStack)-1]
|
||||
}
|
||||
|
||||
// the status panel is not yet a list context (and may never be), so this method is not
|
||||
// quite the same as currentSideContext()
|
||||
func (gui *Gui) currentSideListContext() types.IListContext {
|
||||
context := gui.currentSideContext()
|
||||
listContext, ok := context.(types.IListContext)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return listContext
|
||||
}
|
||||
// Note that this could return the 'status' context which is not itself a list context.
|
||||
func (self *ContextMgr) CurrentSide() types.Context {
|
||||
self.RLock()
|
||||
defer self.RUnlock()
|
||||
|
||||
func (gui *Gui) currentSideContext() types.Context {
|
||||
gui.State.ContextManager.RLock()
|
||||
defer gui.State.ContextManager.RUnlock()
|
||||
|
||||
stack := gui.State.ContextManager.ContextStack
|
||||
|
||||
// on startup the stack can be empty so we'll return an empty string in that case
|
||||
if len(stack) == 0 {
|
||||
return gui.defaultSideContext()
|
||||
}
|
||||
stack := self.ContextStack
|
||||
|
||||
// find the first context in the stack with the type of types.SIDE_CONTEXT
|
||||
for i := range stack {
|
||||
@@ -290,22 +283,22 @@ func (gui *Gui) currentSideContext() types.Context {
|
||||
}
|
||||
}
|
||||
|
||||
return gui.defaultSideContext()
|
||||
return self.gui.defaultSideContext()
|
||||
}
|
||||
|
||||
// static as opposed to popup
|
||||
func (gui *Gui) currentStaticContext() types.Context {
|
||||
gui.State.ContextManager.RLock()
|
||||
defer gui.State.ContextManager.RUnlock()
|
||||
func (self *ContextMgr) CurrentStatic() types.Context {
|
||||
self.RLock()
|
||||
defer self.RUnlock()
|
||||
|
||||
return gui.currentStaticContextWithoutLock()
|
||||
return self.currentStaticContextWithoutLock()
|
||||
}
|
||||
|
||||
func (gui *Gui) currentStaticContextWithoutLock() types.Context {
|
||||
stack := gui.State.ContextManager.ContextStack
|
||||
func (self *ContextMgr) currentStaticContextWithoutLock() types.Context {
|
||||
stack := self.ContextStack
|
||||
|
||||
if len(stack) == 0 {
|
||||
return gui.defaultSideContext()
|
||||
return self.gui.defaultSideContext()
|
||||
}
|
||||
|
||||
// find the first context in the stack without a popup type
|
||||
@@ -317,88 +310,43 @@ func (gui *Gui) currentStaticContextWithoutLock() types.Context {
|
||||
}
|
||||
}
|
||||
|
||||
return gui.defaultSideContext()
|
||||
return self.gui.defaultSideContext()
|
||||
}
|
||||
|
||||
func (gui *Gui) defaultSideContext() types.Context {
|
||||
if gui.State.Modes.Filtering.Active() {
|
||||
return gui.State.Contexts.LocalCommits
|
||||
} else {
|
||||
return gui.State.Contexts.Files
|
||||
func (self *ContextMgr) ForEach(f func(types.Context)) {
|
||||
self.RLock()
|
||||
defer self.RUnlock()
|
||||
|
||||
for _, context := range self.gui.State.ContextMgr.ContextStack {
|
||||
f(context)
|
||||
}
|
||||
}
|
||||
|
||||
// getFocusLayout returns a manager function for when view gain and lose focus
|
||||
func (gui *Gui) getFocusLayout() func(g *gocui.Gui) error {
|
||||
var previousView *gocui.View
|
||||
return func(g *gocui.Gui) error {
|
||||
newView := gui.g.CurrentView()
|
||||
// for now we don't consider losing focus to a popup panel as actually losing focus
|
||||
if newView != previousView && !gui.isPopupPanel(newView.Name()) {
|
||||
if err := gui.onViewFocusLost(previousView); err != nil {
|
||||
return err
|
||||
}
|
||||
func (self *ContextMgr) IsCurrent(c types.Context) bool {
|
||||
return self.Current().GetKey() == c.GetKey()
|
||||
}
|
||||
|
||||
previousView = newView
|
||||
// all list contexts
|
||||
func (self *ContextMgr) AllList() []types.IListContext {
|
||||
var listContexts []types.IListContext
|
||||
|
||||
for _, context := range self.allContexts.Flatten() {
|
||||
if listContext, ok := context.(types.IListContext); ok {
|
||||
listContexts = append(listContexts, listContext)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (gui *Gui) onViewFocusLost(oldView *gocui.View) error {
|
||||
if oldView == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
oldView.Highlight = false
|
||||
|
||||
_ = oldView.SetOriginX(0)
|
||||
|
||||
return nil
|
||||
return listContexts
|
||||
}
|
||||
|
||||
func (gui *Gui) TransientContexts() []types.Context {
|
||||
return slices.Filter(gui.State.Contexts.Flatten(), func(context types.Context) bool {
|
||||
return context.IsTransient()
|
||||
})
|
||||
}
|
||||
func (self *ContextMgr) AllPatchExplorer() []types.IPatchExplorerContext {
|
||||
var listContexts []types.IPatchExplorerContext
|
||||
|
||||
func (gui *Gui) rerenderView(view *gocui.View) error {
|
||||
context, ok := gui.contextForView(view.Name())
|
||||
if !ok {
|
||||
gui.Log.Errorf("no context found for view %s", view.Name())
|
||||
return nil
|
||||
for _, context := range self.allContexts.Flatten() {
|
||||
if listContext, ok := context.(types.IPatchExplorerContext); ok {
|
||||
listContexts = append(listContexts, listContext)
|
||||
}
|
||||
}
|
||||
|
||||
return context.HandleRender()
|
||||
return listContexts
|
||||
}
|
||||
|
||||
func (gui *Gui) getSideContextSelectedItemId() string {
|
||||
currentSideContext := gui.currentSideListContext()
|
||||
if currentSideContext == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return currentSideContext.GetSelectedItemId()
|
||||
}
|
||||
|
||||
// currently unused
|
||||
// func (gui *Gui) getCurrentSideView() *gocui.View {
|
||||
// currentSideContext := gui.currentSideContext()
|
||||
// if currentSideContext == nil {
|
||||
// return nil
|
||||
// }
|
||||
|
||||
// view, _ := gui.g.View(currentSideContext.GetViewName())
|
||||
|
||||
// return view
|
||||
// }
|
||||
|
||||
// currently unused
|
||||
// func (gui *Gui) renderContextStack() string {
|
||||
// result := ""
|
||||
// for _, context := range gui.State.ContextManager.ContextStack {
|
||||
// result += context.GetViewName() + "\n"
|
||||
// }
|
||||
// return result
|
||||
// }
|
||||
|
||||
@@ -16,6 +16,9 @@ type BaseContext struct {
|
||||
keybindingsFns []types.KeybindingsFn
|
||||
mouseKeybindingsFns []types.MouseKeybindingsFn
|
||||
onClickFn func() error
|
||||
onRenderToMainFn func() error
|
||||
onFocusFn onFocusFn
|
||||
onFocusLostFn onFocusLostFn
|
||||
|
||||
focusable bool
|
||||
transient bool
|
||||
@@ -25,6 +28,11 @@ type BaseContext struct {
|
||||
*ParentContextMgr
|
||||
}
|
||||
|
||||
type (
|
||||
onFocusFn = func(types.OnFocusOpts) error
|
||||
onFocusLostFn = func(types.OnFocusLostOpts) error
|
||||
)
|
||||
|
||||
var _ types.IBaseContext = &BaseContext{}
|
||||
|
||||
type NewBaseContextOpts struct {
|
||||
@@ -129,6 +137,36 @@ func (self *BaseContext) GetOnClick() func() error {
|
||||
return self.onClickFn
|
||||
}
|
||||
|
||||
func (self *BaseContext) AddOnRenderToMainFn(fn func() error) {
|
||||
if fn != nil {
|
||||
self.onRenderToMainFn = fn
|
||||
}
|
||||
}
|
||||
|
||||
func (self *BaseContext) GetOnRenderToMain() func() error {
|
||||
return self.onRenderToMainFn
|
||||
}
|
||||
|
||||
func (self *BaseContext) AddOnFocusFn(fn onFocusFn) {
|
||||
if fn != nil {
|
||||
self.onFocusFn = fn
|
||||
}
|
||||
}
|
||||
|
||||
func (self *BaseContext) GetOnFocus() onFocusFn {
|
||||
return self.onFocusFn
|
||||
}
|
||||
|
||||
func (self *BaseContext) AddOnFocusLostFn(fn onFocusLostFn) {
|
||||
if fn != nil {
|
||||
self.onFocusLostFn = fn
|
||||
}
|
||||
}
|
||||
|
||||
func (self *BaseContext) GetOnFocusLost() onFocusLostFn {
|
||||
return self.onFocusLostFn
|
||||
}
|
||||
|
||||
func (self *BaseContext) GetMouseKeybindings(opts types.KeybindingsOpts) []*gocui.ViewMouseBinding {
|
||||
bindings := []*gocui.ViewMouseBinding{}
|
||||
for i := range self.mouseKeybindingsFns {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package context
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
)
|
||||
|
||||
@@ -11,40 +11,40 @@ type BranchesContext struct {
|
||||
*ListContextTrait
|
||||
}
|
||||
|
||||
var _ types.IListContext = (*BranchesContext)(nil)
|
||||
var (
|
||||
_ types.IListContext = (*BranchesContext)(nil)
|
||||
_ types.DiffableContext = (*BranchesContext)(nil)
|
||||
)
|
||||
|
||||
func NewBranchesContext(
|
||||
getModel func() []*models.Branch,
|
||||
view *gocui.View,
|
||||
getDisplayStrings func(startIdx int, length int) [][]string,
|
||||
func NewBranchesContext(c *ContextCommon) *BranchesContext {
|
||||
viewModel := NewBasicViewModel(func() []*models.Branch { return c.Model().Branches })
|
||||
|
||||
onFocus func(types.OnFocusOpts) error,
|
||||
onRenderToMain func() error,
|
||||
onFocusLost func(opts types.OnFocusLostOpts) error,
|
||||
getDisplayStrings := func(startIdx int, length int) [][]string {
|
||||
return presentation.GetBranchListDisplayStrings(
|
||||
c.Model().Branches,
|
||||
c.State().GetRepoState().GetScreenMode() != types.SCREEN_NORMAL,
|
||||
c.Modes().Diffing.Ref,
|
||||
c.Tr,
|
||||
)
|
||||
}
|
||||
|
||||
c *types.HelperCommon,
|
||||
) *BranchesContext {
|
||||
viewModel := NewBasicViewModel(getModel)
|
||||
|
||||
return &BranchesContext{
|
||||
self := &BranchesContext{
|
||||
BasicViewModel: viewModel,
|
||||
ListContextTrait: &ListContextTrait{
|
||||
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
|
||||
View: view,
|
||||
View: c.Views().Branches,
|
||||
WindowName: "branches",
|
||||
Key: LOCAL_BRANCHES_CONTEXT_KEY,
|
||||
Kind: types.SIDE_CONTEXT,
|
||||
Focusable: true,
|
||||
}), ContextCallbackOpts{
|
||||
OnFocus: onFocus,
|
||||
OnFocusLost: onFocusLost,
|
||||
OnRenderToMain: onRenderToMain,
|
||||
}),
|
||||
})),
|
||||
list: viewModel,
|
||||
getDisplayStrings: getDisplayStrings,
|
||||
c: c,
|
||||
},
|
||||
}
|
||||
|
||||
return self
|
||||
}
|
||||
|
||||
func (self *BranchesContext) GetSelectedItemId() string {
|
||||
@@ -63,3 +63,16 @@ func (self *BranchesContext) GetSelectedRef() types.Ref {
|
||||
}
|
||||
return branch
|
||||
}
|
||||
|
||||
func (self *BranchesContext) GetDiffTerminals() []string {
|
||||
// for our local branches we want to include both the branch and its upstream
|
||||
branch := self.GetSelected()
|
||||
if branch != nil {
|
||||
names := []string{branch.ID()}
|
||||
if branch.IsTrackingRemote() {
|
||||
names = append(names, branch.ID()+"@{u}")
|
||||
}
|
||||
return names
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
package context
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/generics/slices"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/filetree"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/style"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
)
|
||||
|
||||
@@ -13,20 +15,28 @@ type CommitFilesContext struct {
|
||||
*DynamicTitleBuilder
|
||||
}
|
||||
|
||||
var _ types.IListContext = (*CommitFilesContext)(nil)
|
||||
var (
|
||||
_ types.IListContext = (*CommitFilesContext)(nil)
|
||||
_ types.DiffableContext = (*CommitFilesContext)(nil)
|
||||
)
|
||||
|
||||
func NewCommitFilesContext(
|
||||
getModel func() []*models.CommitFile,
|
||||
view *gocui.View,
|
||||
getDisplayStrings func(startIdx int, length int) [][]string,
|
||||
func NewCommitFilesContext(c *ContextCommon) *CommitFilesContext {
|
||||
viewModel := filetree.NewCommitFileTreeViewModel(
|
||||
func() []*models.CommitFile { return c.Model().CommitFiles },
|
||||
c.Log,
|
||||
c.UserConfig.Gui.ShowFileTree,
|
||||
)
|
||||
|
||||
onFocus func(types.OnFocusOpts) error,
|
||||
onRenderToMain func() error,
|
||||
onFocusLost func(opts types.OnFocusLostOpts) error,
|
||||
getDisplayStrings := func(startIdx int, length int) [][]string {
|
||||
if viewModel.Len() == 0 {
|
||||
return [][]string{{style.FgRed.Sprint("(none)")}}
|
||||
}
|
||||
|
||||
c *types.HelperCommon,
|
||||
) *CommitFilesContext {
|
||||
viewModel := filetree.NewCommitFileTreeViewModel(getModel, c.Log, c.UserConfig.Gui.ShowFileTree)
|
||||
lines := presentation.RenderCommitFileTree(viewModel, c.Modes().Diffing.Ref, c.Git().Patch.PatchBuilder)
|
||||
return slices.Map(lines, func(line string) []string {
|
||||
return []string{line}
|
||||
})
|
||||
}
|
||||
|
||||
return &CommitFilesContext{
|
||||
CommitFileTreeViewModel: viewModel,
|
||||
@@ -34,18 +44,14 @@ func NewCommitFilesContext(
|
||||
ListContextTrait: &ListContextTrait{
|
||||
Context: NewSimpleContext(
|
||||
NewBaseContext(NewBaseContextOpts{
|
||||
View: view,
|
||||
View: c.Views().CommitFiles,
|
||||
WindowName: "commits",
|
||||
Key: COMMIT_FILES_CONTEXT_KEY,
|
||||
Kind: types.SIDE_CONTEXT,
|
||||
Focusable: true,
|
||||
Transient: true,
|
||||
}),
|
||||
ContextCallbackOpts{
|
||||
OnFocus: onFocus,
|
||||
OnFocusLost: onFocusLost,
|
||||
OnRenderToMain: onRenderToMain,
|
||||
}),
|
||||
),
|
||||
list: viewModel,
|
||||
getDisplayStrings: getDisplayStrings,
|
||||
c: c,
|
||||
@@ -61,3 +67,7 @@ func (self *CommitFilesContext) GetSelectedItemId() string {
|
||||
|
||||
return item.ID()
|
||||
}
|
||||
|
||||
func (self *CommitFilesContext) GetDiffTerminals() []string {
|
||||
return []string{self.GetRef().RefName()}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
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) + " "
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package context
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
)
|
||||
|
||||
type ConfirmationContext struct {
|
||||
*SimpleContext
|
||||
c *ContextCommon
|
||||
|
||||
State ConfirmationContextState
|
||||
}
|
||||
|
||||
type ConfirmationContextState struct {
|
||||
OnConfirm func() error
|
||||
OnClose func() error
|
||||
}
|
||||
|
||||
var _ types.Context = (*ConfirmationContext)(nil)
|
||||
|
||||
func NewConfirmationContext(
|
||||
c *ContextCommon,
|
||||
) *ConfirmationContext {
|
||||
return &ConfirmationContext{
|
||||
c: c,
|
||||
SimpleContext: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
|
||||
View: c.Views().Confirmation,
|
||||
WindowName: "confirmation",
|
||||
Key: CONFIRMATION_CONTEXT_KEY,
|
||||
Kind: types.TEMPORARY_POPUP,
|
||||
Focusable: true,
|
||||
HasUncontrolledBounds: true,
|
||||
})),
|
||||
}
|
||||
}
|
||||
@@ -33,13 +33,14 @@ 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"
|
||||
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"
|
||||
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"
|
||||
)
|
||||
|
||||
var AllContextKeys = []types.ContextKey{
|
||||
@@ -96,8 +97,9 @@ type ContextTree struct {
|
||||
CustomPatchBuilder *PatchExplorerContext
|
||||
CustomPatchBuilderSecondary types.Context
|
||||
MergeConflicts *MergeConflictsContext
|
||||
Confirmation types.Context
|
||||
CommitMessage types.Context
|
||||
Confirmation *ConfirmationContext
|
||||
CommitMessage *CommitMessageContext
|
||||
CommitDescription types.Context
|
||||
CommandLog types.Context
|
||||
|
||||
// display contexts
|
||||
@@ -129,6 +131,7 @@ func (self *ContextTree) Flatten() []types.Context {
|
||||
self.Menu,
|
||||
self.Confirmation,
|
||||
self.CommitMessage,
|
||||
self.CommitDescription,
|
||||
|
||||
self.MergeConflicts,
|
||||
self.StagingSecondary,
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
package context
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/common"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
)
|
||||
|
||||
type ContextCommon struct {
|
||||
*common.Common
|
||||
types.IGuiCommon
|
||||
}
|
||||
@@ -10,11 +10,13 @@ import (
|
||||
type ListContextTrait struct {
|
||||
types.Context
|
||||
|
||||
c *types.HelperCommon
|
||||
c *ContextCommon
|
||||
list types.IList
|
||||
getDisplayStrings func(startIdx int, length int) [][]string
|
||||
}
|
||||
|
||||
func (self *ListContextTrait) IsListContext() {}
|
||||
|
||||
func (self *ListContextTrait) GetList() types.IList {
|
||||
return self.list
|
||||
}
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
package context
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/gocui"
|
||||
"log"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/types/enums"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
)
|
||||
|
||||
@@ -11,36 +14,57 @@ type LocalCommitsContext struct {
|
||||
*ViewportListContextTrait
|
||||
}
|
||||
|
||||
var _ types.IListContext = (*LocalCommitsContext)(nil)
|
||||
var (
|
||||
_ types.IListContext = (*LocalCommitsContext)(nil)
|
||||
_ types.DiffableContext = (*LocalCommitsContext)(nil)
|
||||
)
|
||||
|
||||
func NewLocalCommitsContext(
|
||||
getModel func() []*models.Commit,
|
||||
view *gocui.View,
|
||||
getDisplayStrings func(startIdx int, length int) [][]string,
|
||||
func NewLocalCommitsContext(c *ContextCommon) *LocalCommitsContext {
|
||||
viewModel := NewLocalCommitsViewModel(
|
||||
func() []*models.Commit { return c.Model().Commits },
|
||||
c,
|
||||
)
|
||||
|
||||
onFocus func(types.OnFocusOpts) error,
|
||||
onRenderToMain func() error,
|
||||
onFocusLost func(opts types.OnFocusLostOpts) error,
|
||||
getDisplayStrings := func(startIdx int, length int) [][]string {
|
||||
selectedCommitSha := ""
|
||||
|
||||
c *types.HelperCommon,
|
||||
) *LocalCommitsContext {
|
||||
viewModel := NewLocalCommitsViewModel(getModel, c)
|
||||
if c.CurrentContext().GetKey() == LOCAL_COMMITS_CONTEXT_KEY {
|
||||
selectedCommit := viewModel.GetSelected()
|
||||
if selectedCommit != nil {
|
||||
selectedCommitSha = selectedCommit.Sha
|
||||
}
|
||||
}
|
||||
|
||||
showYouAreHereLabel := c.Model().WorkingTreeStateAtLastCommitRefresh == enums.REBASE_MODE_REBASING
|
||||
|
||||
return presentation.GetCommitListDisplayStrings(
|
||||
c.Common,
|
||||
c.Model().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),
|
||||
c.Model().BisectInfo,
|
||||
showYouAreHereLabel,
|
||||
)
|
||||
}
|
||||
|
||||
return &LocalCommitsContext{
|
||||
LocalCommitsViewModel: viewModel,
|
||||
ViewportListContextTrait: &ViewportListContextTrait{
|
||||
ListContextTrait: &ListContextTrait{
|
||||
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
|
||||
View: view,
|
||||
View: c.Views().Commits,
|
||||
WindowName: "commits",
|
||||
Key: LOCAL_COMMITS_CONTEXT_KEY,
|
||||
Kind: types.SIDE_CONTEXT,
|
||||
Focusable: true,
|
||||
}), ContextCallbackOpts{
|
||||
OnFocus: onFocus,
|
||||
OnFocusLost: onFocusLost,
|
||||
OnRenderToMain: onRenderToMain,
|
||||
}),
|
||||
})),
|
||||
list: viewModel,
|
||||
getDisplayStrings: getDisplayStrings,
|
||||
c: c,
|
||||
@@ -69,7 +93,7 @@ type LocalCommitsViewModel struct {
|
||||
showWholeGitGraph bool
|
||||
}
|
||||
|
||||
func NewLocalCommitsViewModel(getModel func() []*models.Commit, c *types.HelperCommon) *LocalCommitsViewModel {
|
||||
func NewLocalCommitsViewModel(getModel func() []*models.Commit, c *ContextCommon) *LocalCommitsViewModel {
|
||||
self := &LocalCommitsViewModel{
|
||||
BasicViewModel: NewBasicViewModel(getModel),
|
||||
limitCommits: true,
|
||||
@@ -91,6 +115,12 @@ func (self *LocalCommitsContext) GetSelectedRef() types.Ref {
|
||||
return commit
|
||||
}
|
||||
|
||||
func (self *LocalCommitsContext) GetDiffTerminals() []string {
|
||||
itemId := self.GetSelectedItemId()
|
||||
|
||||
return []string{itemId}
|
||||
}
|
||||
|
||||
func (self *LocalCommitsViewModel) SetLimitCommits(value bool) {
|
||||
self.limitCommits = value
|
||||
}
|
||||
@@ -110,3 +140,22 @@ 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
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ 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"
|
||||
@@ -16,34 +15,21 @@ type MenuContext struct {
|
||||
var _ types.IListContext = (*MenuContext)(nil)
|
||||
|
||||
func NewMenuContext(
|
||||
view *gocui.View,
|
||||
|
||||
c *types.HelperCommon,
|
||||
getOptionsMap func() map[string]string,
|
||||
renderToDescriptionView func(string),
|
||||
c *ContextCommon,
|
||||
) *MenuContext {
|
||||
viewModel := NewMenuViewModel()
|
||||
|
||||
onFocus := func(types.OnFocusOpts) error {
|
||||
selectedMenuItem := viewModel.GetSelected()
|
||||
renderToDescriptionView(selectedMenuItem.Tooltip)
|
||||
return nil
|
||||
}
|
||||
|
||||
return &MenuContext{
|
||||
MenuViewModel: viewModel,
|
||||
ListContextTrait: &ListContextTrait{
|
||||
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
|
||||
View: view,
|
||||
View: c.Views().Menu,
|
||||
WindowName: "menu",
|
||||
Key: "menu",
|
||||
Kind: types.TEMPORARY_POPUP,
|
||||
OnGetOptionsMap: getOptionsMap,
|
||||
Focusable: true,
|
||||
HasUncontrolledBounds: true,
|
||||
}), ContextCallbackOpts{
|
||||
OnFocus: onFocus,
|
||||
}),
|
||||
})),
|
||||
getDisplayStrings: viewModel.GetDisplayStrings,
|
||||
list: viewModel,
|
||||
c: c,
|
||||
|
||||
@@ -3,7 +3,6 @@ package context
|
||||
import (
|
||||
"math"
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/mergeconflicts"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/sasha-s/go-deadlock"
|
||||
@@ -12,7 +11,7 @@ import (
|
||||
type MergeConflictsContext struct {
|
||||
types.Context
|
||||
viewModel *ConflictsViewModel
|
||||
c *types.HelperCommon
|
||||
c *ContextCommon
|
||||
mutex *deadlock.Mutex
|
||||
}
|
||||
|
||||
@@ -25,12 +24,7 @@ type ConflictsViewModel struct {
|
||||
}
|
||||
|
||||
func NewMergeConflictsContext(
|
||||
view *gocui.View,
|
||||
|
||||
opts ContextCallbackOpts,
|
||||
|
||||
c *types.HelperCommon,
|
||||
getOptionsMap func() map[string]string,
|
||||
c *ContextCommon,
|
||||
) *MergeConflictsContext {
|
||||
viewModel := &ConflictsViewModel{
|
||||
state: mergeconflicts.NewState(),
|
||||
@@ -43,14 +37,12 @@ func NewMergeConflictsContext(
|
||||
Context: NewSimpleContext(
|
||||
NewBaseContext(NewBaseContextOpts{
|
||||
Kind: types.MAIN_CONTEXT,
|
||||
View: view,
|
||||
View: c.Views().MergeConflicts,
|
||||
WindowName: "main",
|
||||
Key: MERGE_CONFLICTS_CONTEXT_KEY,
|
||||
OnGetOptionsMap: getOptionsMap,
|
||||
Focusable: true,
|
||||
HighlightOnFocus: true,
|
||||
}),
|
||||
opts,
|
||||
),
|
||||
c: c,
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ type PatchExplorerContext struct {
|
||||
state *patch_exploring.State
|
||||
viewTrait *ViewTrait
|
||||
getIncludedLineIndices func() []int
|
||||
c *types.HelperCommon
|
||||
c *ContextCommon
|
||||
mutex *deadlock.Mutex
|
||||
}
|
||||
|
||||
@@ -24,11 +24,9 @@ func NewPatchExplorerContext(
|
||||
windowName string,
|
||||
key types.ContextKey,
|
||||
|
||||
onFocus func(types.OnFocusOpts) error,
|
||||
onFocusLost func(opts types.OnFocusLostOpts) error,
|
||||
getIncludedLineIndices func() []int,
|
||||
|
||||
c *types.HelperCommon,
|
||||
c *ContextCommon,
|
||||
) *PatchExplorerContext {
|
||||
return &PatchExplorerContext{
|
||||
state: nil,
|
||||
@@ -43,13 +41,12 @@ func NewPatchExplorerContext(
|
||||
Kind: types.MAIN_CONTEXT,
|
||||
Focusable: true,
|
||||
HighlightOnFocus: true,
|
||||
}), ContextCallbackOpts{
|
||||
OnFocus: onFocus,
|
||||
OnFocusLost: onFocusLost,
|
||||
}),
|
||||
})),
|
||||
}
|
||||
}
|
||||
|
||||
func (self *PatchExplorerContext) IsPatchExplorerContext() {}
|
||||
|
||||
func (self *PatchExplorerContext) GetState() *patch_exploring.State {
|
||||
return self.state
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package context
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
)
|
||||
|
||||
@@ -11,35 +11,35 @@ type ReflogCommitsContext struct {
|
||||
*ListContextTrait
|
||||
}
|
||||
|
||||
var _ types.IListContext = (*ReflogCommitsContext)(nil)
|
||||
var (
|
||||
_ types.IListContext = (*ReflogCommitsContext)(nil)
|
||||
_ types.DiffableContext = (*ReflogCommitsContext)(nil)
|
||||
)
|
||||
|
||||
func NewReflogCommitsContext(
|
||||
getModel func() []*models.Commit,
|
||||
view *gocui.View,
|
||||
getDisplayStrings func(startIdx int, length int) [][]string,
|
||||
func NewReflogCommitsContext(c *ContextCommon) *ReflogCommitsContext {
|
||||
viewModel := NewBasicViewModel(func() []*models.Commit { return c.Model().FilteredReflogCommits })
|
||||
|
||||
onFocus func(types.OnFocusOpts) error,
|
||||
onRenderToMain func() error,
|
||||
onFocusLost func(opts types.OnFocusLostOpts) error,
|
||||
|
||||
c *types.HelperCommon,
|
||||
) *ReflogCommitsContext {
|
||||
viewModel := NewBasicViewModel(getModel)
|
||||
getDisplayStrings := func(startIdx int, length int) [][]string {
|
||||
return presentation.GetReflogCommitListDisplayStrings(
|
||||
c.Model().FilteredReflogCommits,
|
||||
c.State().GetRepoState().GetScreenMode() != types.SCREEN_NORMAL,
|
||||
c.Modes().CherryPicking.SelectedShaSet(),
|
||||
c.Modes().Diffing.Ref,
|
||||
c.UserConfig.Gui.TimeFormat,
|
||||
c.UserConfig.Git.ParseEmoji,
|
||||
)
|
||||
}
|
||||
|
||||
return &ReflogCommitsContext{
|
||||
BasicViewModel: viewModel,
|
||||
ListContextTrait: &ListContextTrait{
|
||||
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
|
||||
View: view,
|
||||
View: c.Views().ReflogCommits,
|
||||
WindowName: "commits",
|
||||
Key: REFLOG_COMMITS_CONTEXT_KEY,
|
||||
Kind: types.SIDE_CONTEXT,
|
||||
Focusable: true,
|
||||
}), ContextCallbackOpts{
|
||||
OnFocus: onFocus,
|
||||
OnFocusLost: onFocusLost,
|
||||
OnRenderToMain: onRenderToMain,
|
||||
}),
|
||||
})),
|
||||
list: viewModel,
|
||||
getDisplayStrings: getDisplayStrings,
|
||||
c: c,
|
||||
@@ -71,3 +71,9 @@ func (self *ReflogCommitsContext) GetSelectedRef() types.Ref {
|
||||
func (self *ReflogCommitsContext) GetCommits() []*models.Commit {
|
||||
return self.getModel()
|
||||
}
|
||||
|
||||
func (self *ReflogCommitsContext) GetDiffTerminals() []string {
|
||||
itemId := self.GetSelectedItemId()
|
||||
|
||||
return []string{itemId}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package context
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
)
|
||||
|
||||
@@ -12,37 +12,32 @@ type RemoteBranchesContext struct {
|
||||
*DynamicTitleBuilder
|
||||
}
|
||||
|
||||
var _ types.IListContext = (*RemoteBranchesContext)(nil)
|
||||
var (
|
||||
_ types.IListContext = (*RemoteBranchesContext)(nil)
|
||||
_ types.DiffableContext = (*RemoteBranchesContext)(nil)
|
||||
)
|
||||
|
||||
func NewRemoteBranchesContext(
|
||||
getModel func() []*models.RemoteBranch,
|
||||
view *gocui.View,
|
||||
getDisplayStrings func(startIdx int, length int) [][]string,
|
||||
|
||||
onFocus func(types.OnFocusOpts) error,
|
||||
onRenderToMain func() error,
|
||||
onFocusLost func(opts types.OnFocusLostOpts) error,
|
||||
|
||||
c *types.HelperCommon,
|
||||
c *ContextCommon,
|
||||
) *RemoteBranchesContext {
|
||||
viewModel := NewBasicViewModel(getModel)
|
||||
viewModel := NewBasicViewModel(func() []*models.RemoteBranch { return c.Model().RemoteBranches })
|
||||
|
||||
getDisplayStrings := func(startIdx int, length int) [][]string {
|
||||
return presentation.GetRemoteBranchListDisplayStrings(c.Model().RemoteBranches, c.Modes().Diffing.Ref)
|
||||
}
|
||||
|
||||
return &RemoteBranchesContext{
|
||||
BasicViewModel: viewModel,
|
||||
DynamicTitleBuilder: NewDynamicTitleBuilder(c.Tr.RemoteBranchesDynamicTitle),
|
||||
ListContextTrait: &ListContextTrait{
|
||||
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
|
||||
View: view,
|
||||
View: c.Views().RemoteBranches,
|
||||
WindowName: "branches",
|
||||
Key: REMOTE_BRANCHES_CONTEXT_KEY,
|
||||
Kind: types.SIDE_CONTEXT,
|
||||
Focusable: true,
|
||||
Transient: true,
|
||||
}), ContextCallbackOpts{
|
||||
OnFocus: onFocus,
|
||||
OnFocusLost: onFocusLost,
|
||||
OnRenderToMain: onRenderToMain,
|
||||
}),
|
||||
})),
|
||||
list: viewModel,
|
||||
getDisplayStrings: getDisplayStrings,
|
||||
c: c,
|
||||
@@ -66,3 +61,9 @@ func (self *RemoteBranchesContext) GetSelectedRef() types.Ref {
|
||||
}
|
||||
return remoteBranch
|
||||
}
|
||||
|
||||
func (self *RemoteBranchesContext) GetDiffTerminals() []string {
|
||||
itemId := self.GetSelectedItemId()
|
||||
|
||||
return []string{itemId}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package context
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
)
|
||||
|
||||
@@ -11,35 +11,28 @@ type RemotesContext struct {
|
||||
*ListContextTrait
|
||||
}
|
||||
|
||||
var _ types.IListContext = (*RemotesContext)(nil)
|
||||
var (
|
||||
_ types.IListContext = (*RemotesContext)(nil)
|
||||
_ types.DiffableContext = (*RemotesContext)(nil)
|
||||
)
|
||||
|
||||
func NewRemotesContext(
|
||||
getModel func() []*models.Remote,
|
||||
view *gocui.View,
|
||||
getDisplayStrings func(startIdx int, length int) [][]string,
|
||||
func NewRemotesContext(c *ContextCommon) *RemotesContext {
|
||||
viewModel := NewBasicViewModel(func() []*models.Remote { return c.Model().Remotes })
|
||||
|
||||
onFocus func(types.OnFocusOpts) error,
|
||||
onRenderToMain func() error,
|
||||
onFocusLost func(opts types.OnFocusLostOpts) error,
|
||||
|
||||
c *types.HelperCommon,
|
||||
) *RemotesContext {
|
||||
viewModel := NewBasicViewModel(getModel)
|
||||
getDisplayStrings := func(startIdx int, length int) [][]string {
|
||||
return presentation.GetRemoteListDisplayStrings(c.Model().Remotes, c.Modes().Diffing.Ref)
|
||||
}
|
||||
|
||||
return &RemotesContext{
|
||||
BasicViewModel: viewModel,
|
||||
ListContextTrait: &ListContextTrait{
|
||||
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
|
||||
View: view,
|
||||
View: c.Views().Remotes,
|
||||
WindowName: "branches",
|
||||
Key: REMOTES_CONTEXT_KEY,
|
||||
Kind: types.SIDE_CONTEXT,
|
||||
Focusable: true,
|
||||
}), ContextCallbackOpts{
|
||||
OnFocus: onFocus,
|
||||
OnFocusLost: onFocusLost,
|
||||
OnRenderToMain: onRenderToMain,
|
||||
}),
|
||||
})),
|
||||
list: viewModel,
|
||||
getDisplayStrings: getDisplayStrings,
|
||||
c: c,
|
||||
@@ -55,3 +48,9 @@ func (self *RemotesContext) GetSelectedItemId() string {
|
||||
|
||||
return item.ID()
|
||||
}
|
||||
|
||||
func (self *RemotesContext) GetDiffTerminals() []string {
|
||||
itemId := self.GetSelectedItemId()
|
||||
|
||||
return []string{itemId}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,146 @@
|
||||
package context
|
||||
|
||||
import "github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
|
||||
func NewContextTree(c *ContextCommon) *ContextTree {
|
||||
commitFilesContext := NewCommitFilesContext(c)
|
||||
|
||||
return &ContextTree{
|
||||
Global: NewSimpleContext(
|
||||
NewBaseContext(NewBaseContextOpts{
|
||||
Kind: types.GLOBAL_CONTEXT,
|
||||
View: nil, // TODO: see if this breaks anything
|
||||
WindowName: "",
|
||||
Key: GLOBAL_CONTEXT_KEY,
|
||||
Focusable: false,
|
||||
HasUncontrolledBounds: true, // setting to true because the global context doesn't even have a view
|
||||
}),
|
||||
),
|
||||
Status: NewSimpleContext(
|
||||
NewBaseContext(NewBaseContextOpts{
|
||||
Kind: types.SIDE_CONTEXT,
|
||||
View: c.Views().Status,
|
||||
WindowName: "status",
|
||||
Key: STATUS_CONTEXT_KEY,
|
||||
Focusable: true,
|
||||
}),
|
||||
),
|
||||
Files: NewWorkingTreeContext(c),
|
||||
Submodules: NewSubmodulesContext(c),
|
||||
Menu: NewMenuContext(c),
|
||||
Remotes: NewRemotesContext(c),
|
||||
RemoteBranches: NewRemoteBranchesContext(c),
|
||||
LocalCommits: NewLocalCommitsContext(c),
|
||||
CommitFiles: commitFilesContext,
|
||||
ReflogCommits: NewReflogCommitsContext(c),
|
||||
SubCommits: NewSubCommitsContext(c),
|
||||
Branches: NewBranchesContext(c),
|
||||
Tags: NewTagsContext(c),
|
||||
Stash: NewStashContext(c),
|
||||
Suggestions: NewSuggestionsContext(c),
|
||||
Normal: NewSimpleContext(
|
||||
NewBaseContext(NewBaseContextOpts{
|
||||
Kind: types.MAIN_CONTEXT,
|
||||
View: c.Views().Main,
|
||||
WindowName: "main",
|
||||
Key: NORMAL_MAIN_CONTEXT_KEY,
|
||||
Focusable: false,
|
||||
}),
|
||||
),
|
||||
NormalSecondary: NewSimpleContext(
|
||||
NewBaseContext(NewBaseContextOpts{
|
||||
Kind: types.MAIN_CONTEXT,
|
||||
View: c.Views().Secondary,
|
||||
WindowName: "secondary",
|
||||
Key: NORMAL_SECONDARY_CONTEXT_KEY,
|
||||
Focusable: false,
|
||||
}),
|
||||
),
|
||||
Staging: NewPatchExplorerContext(
|
||||
c.Views().Staging,
|
||||
"main",
|
||||
STAGING_MAIN_CONTEXT_KEY,
|
||||
func() []int { return nil },
|
||||
c,
|
||||
),
|
||||
StagingSecondary: NewPatchExplorerContext(
|
||||
c.Views().StagingSecondary,
|
||||
"secondary",
|
||||
STAGING_SECONDARY_CONTEXT_KEY,
|
||||
func() []int { return nil },
|
||||
c,
|
||||
),
|
||||
CustomPatchBuilder: NewPatchExplorerContext(
|
||||
c.Views().PatchBuilding,
|
||||
"main",
|
||||
PATCH_BUILDING_MAIN_CONTEXT_KEY,
|
||||
func() []int {
|
||||
filename := commitFilesContext.GetSelectedPath()
|
||||
includedLineIndices, err := c.Git().Patch.PatchBuilder.GetFileIncLineIndices(filename)
|
||||
if err != nil {
|
||||
c.Log.Error(err)
|
||||
return nil
|
||||
}
|
||||
|
||||
return includedLineIndices
|
||||
},
|
||||
c,
|
||||
),
|
||||
CustomPatchBuilderSecondary: NewSimpleContext(
|
||||
NewBaseContext(NewBaseContextOpts{
|
||||
Kind: types.MAIN_CONTEXT,
|
||||
View: c.Views().PatchBuildingSecondary,
|
||||
WindowName: "secondary",
|
||||
Key: PATCH_BUILDING_SECONDARY_CONTEXT_KEY,
|
||||
Focusable: false,
|
||||
}),
|
||||
),
|
||||
MergeConflicts: NewMergeConflictsContext(
|
||||
c,
|
||||
),
|
||||
Confirmation: NewConfirmationContext(c),
|
||||
CommitMessage: NewCommitMessageContext(c),
|
||||
CommitDescription: NewSimpleContext(
|
||||
NewBaseContext(NewBaseContextOpts{
|
||||
Kind: types.PERSISTENT_POPUP,
|
||||
View: c.Views().CommitDescription,
|
||||
WindowName: "commitDescription",
|
||||
Key: COMMIT_DESCRIPTION_CONTEXT_KEY,
|
||||
Focusable: true,
|
||||
HasUncontrolledBounds: true,
|
||||
}),
|
||||
),
|
||||
Search: NewSimpleContext(
|
||||
NewBaseContext(NewBaseContextOpts{
|
||||
Kind: types.PERSISTENT_POPUP,
|
||||
View: c.Views().Search,
|
||||
WindowName: "search",
|
||||
Key: SEARCH_CONTEXT_KEY,
|
||||
Focusable: true,
|
||||
}),
|
||||
),
|
||||
CommandLog: NewSimpleContext(
|
||||
NewBaseContext(NewBaseContextOpts{
|
||||
Kind: types.EXTRAS_CONTEXT,
|
||||
View: c.Views().Extras,
|
||||
WindowName: "extras",
|
||||
Key: COMMAND_LOG_CONTEXT_KEY,
|
||||
Focusable: true,
|
||||
}),
|
||||
),
|
||||
Snake: NewSimpleContext(
|
||||
NewBaseContext(NewBaseContextOpts{
|
||||
Kind: types.SIDE_CONTEXT,
|
||||
View: c.Views().Snake,
|
||||
WindowName: "files",
|
||||
Key: SNAKE_CONTEXT_KEY,
|
||||
Focusable: true,
|
||||
}),
|
||||
),
|
||||
Options: NewDisplayContext(OPTIONS_CONTEXT_KEY, c.Views().Options, "options"),
|
||||
AppStatus: NewDisplayContext(APP_STATUS_CONTEXT_KEY, c.Views().AppStatus, "appStatus"),
|
||||
SearchPrefix: NewDisplayContext(SEARCH_PREFIX_CONTEXT_KEY, c.Views().SearchPrefix, "searchPrefix"),
|
||||
Information: NewDisplayContext(INFORMATION_CONTEXT_KEY, c.Views().Information, "information"),
|
||||
Limit: NewDisplayContext(LIMIT_CONTEXT_KEY, c.Views().Limit, "limit"),
|
||||
}
|
||||
}
|
||||
@@ -6,29 +6,12 @@ import (
|
||||
)
|
||||
|
||||
type SimpleContext struct {
|
||||
OnFocus func(opts types.OnFocusOpts) error
|
||||
OnFocusLost func(opts types.OnFocusLostOpts) error
|
||||
OnRender func() error
|
||||
// this is for pushing some content to the main view
|
||||
OnRenderToMain func() error
|
||||
|
||||
*BaseContext
|
||||
}
|
||||
|
||||
type ContextCallbackOpts struct {
|
||||
OnFocus func(opts types.OnFocusOpts) error
|
||||
OnFocusLost func(opts types.OnFocusLostOpts) error
|
||||
OnRender func() error
|
||||
OnRenderToMain func() error
|
||||
}
|
||||
|
||||
func NewSimpleContext(baseContext *BaseContext, opts ContextCallbackOpts) *SimpleContext {
|
||||
func NewSimpleContext(baseContext *BaseContext) *SimpleContext {
|
||||
return &SimpleContext{
|
||||
OnFocus: opts.OnFocus,
|
||||
OnFocusLost: opts.OnFocusLost,
|
||||
OnRender: opts.OnRender,
|
||||
OnRenderToMain: opts.OnRenderToMain,
|
||||
BaseContext: baseContext,
|
||||
BaseContext: baseContext,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,7 +28,6 @@ func NewDisplayContext(key types.ContextKey, view *gocui.View, windowName string
|
||||
Focusable: false,
|
||||
Transient: false,
|
||||
}),
|
||||
ContextCallbackOpts{},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -54,14 +36,14 @@ func (self *SimpleContext) HandleFocus(opts types.OnFocusOpts) error {
|
||||
self.GetViewTrait().SetHighlight(true)
|
||||
}
|
||||
|
||||
if self.OnFocus != nil {
|
||||
if err := self.OnFocus(opts); err != nil {
|
||||
if self.onFocusFn != nil {
|
||||
if err := self.onFocusFn(opts); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if self.OnRenderToMain != nil {
|
||||
if err := self.OnRenderToMain(); err != nil {
|
||||
if self.onRenderToMainFn != nil {
|
||||
if err := self.onRenderToMainFn(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -70,22 +52,19 @@ func (self *SimpleContext) HandleFocus(opts types.OnFocusOpts) error {
|
||||
}
|
||||
|
||||
func (self *SimpleContext) HandleFocusLost(opts types.OnFocusLostOpts) error {
|
||||
if self.OnFocusLost != nil {
|
||||
return self.OnFocusLost(opts)
|
||||
if self.onFocusLostFn != nil {
|
||||
return self.onFocusLostFn(opts)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *SimpleContext) HandleRender() error {
|
||||
if self.OnRender != nil {
|
||||
return self.OnRender()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *SimpleContext) HandleRenderToMain() error {
|
||||
if self.OnRenderToMain != nil {
|
||||
return self.OnRenderToMain()
|
||||
if self.onRenderToMainFn != nil {
|
||||
return self.onRenderToMainFn()
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package context
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
)
|
||||
|
||||
@@ -11,35 +11,30 @@ type StashContext struct {
|
||||
*ListContextTrait
|
||||
}
|
||||
|
||||
var _ types.IListContext = (*StashContext)(nil)
|
||||
var (
|
||||
_ types.IListContext = (*StashContext)(nil)
|
||||
_ types.DiffableContext = (*StashContext)(nil)
|
||||
)
|
||||
|
||||
func NewStashContext(
|
||||
getModel func() []*models.StashEntry,
|
||||
view *gocui.View,
|
||||
getDisplayStrings func(startIdx int, length int) [][]string,
|
||||
|
||||
onFocus func(types.OnFocusOpts) error,
|
||||
onRenderToMain func() error,
|
||||
onFocusLost func(opts types.OnFocusLostOpts) error,
|
||||
|
||||
c *types.HelperCommon,
|
||||
c *ContextCommon,
|
||||
) *StashContext {
|
||||
viewModel := NewBasicViewModel(getModel)
|
||||
viewModel := NewBasicViewModel(func() []*models.StashEntry { return c.Model().StashEntries })
|
||||
|
||||
getDisplayStrings := func(startIdx int, length int) [][]string {
|
||||
return presentation.GetStashEntryListDisplayStrings(c.Model().StashEntries, c.Modes().Diffing.Ref)
|
||||
}
|
||||
|
||||
return &StashContext{
|
||||
BasicViewModel: viewModel,
|
||||
ListContextTrait: &ListContextTrait{
|
||||
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
|
||||
View: view,
|
||||
View: c.Views().Stash,
|
||||
WindowName: "stash",
|
||||
Key: STASH_CONTEXT_KEY,
|
||||
Kind: types.SIDE_CONTEXT,
|
||||
Focusable: true,
|
||||
}), ContextCallbackOpts{
|
||||
OnFocus: onFocus,
|
||||
OnFocusLost: onFocusLost,
|
||||
OnRenderToMain: onRenderToMain,
|
||||
}),
|
||||
})),
|
||||
list: viewModel,
|
||||
getDisplayStrings: getDisplayStrings,
|
||||
c: c,
|
||||
@@ -67,3 +62,9 @@ func (self *StashContext) GetSelectedRef() types.Ref {
|
||||
}
|
||||
return stash
|
||||
}
|
||||
|
||||
func (self *StashContext) GetDiffTerminals() []string {
|
||||
itemId := self.GetSelectedItemId()
|
||||
|
||||
return []string{itemId}
|
||||
}
|
||||
|
||||
@@ -3,8 +3,9 @@ package context
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
)
|
||||
@@ -15,23 +16,45 @@ type SubCommitsContext struct {
|
||||
*DynamicTitleBuilder
|
||||
}
|
||||
|
||||
var _ types.IListContext = (*SubCommitsContext)(nil)
|
||||
var (
|
||||
_ types.IListContext = (*SubCommitsContext)(nil)
|
||||
_ types.DiffableContext = (*SubCommitsContext)(nil)
|
||||
)
|
||||
|
||||
func NewSubCommitsContext(
|
||||
getModel func() []*models.Commit,
|
||||
view *gocui.View,
|
||||
getDisplayStrings func(startIdx int, length int) [][]string,
|
||||
|
||||
onFocus func(types.OnFocusOpts) error,
|
||||
onRenderToMain func() error,
|
||||
onFocusLost func(opts types.OnFocusLostOpts) error,
|
||||
|
||||
c *types.HelperCommon,
|
||||
c *ContextCommon,
|
||||
) *SubCommitsContext {
|
||||
viewModel := &SubCommitsViewModel{
|
||||
BasicViewModel: NewBasicViewModel(getModel),
|
||||
ref: nil,
|
||||
limitCommits: true,
|
||||
BasicViewModel: NewBasicViewModel(
|
||||
func() []*models.Commit { return c.Model().SubCommits },
|
||||
),
|
||||
ref: nil,
|
||||
limitCommits: true,
|
||||
}
|
||||
|
||||
getDisplayStrings := func(startIdx int, length int) [][]string {
|
||||
selectedCommitSha := ""
|
||||
if c.CurrentContext().GetKey() == SUB_COMMITS_CONTEXT_KEY {
|
||||
selectedCommit := viewModel.GetSelected()
|
||||
if selectedCommit != nil {
|
||||
selectedCommitSha = selectedCommit.Sha
|
||||
}
|
||||
}
|
||||
return presentation.GetCommitListDisplayStrings(
|
||||
c.Common,
|
||||
c.Model().SubCommits,
|
||||
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),
|
||||
git_commands.NewNullBisectInfo(),
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
||||
return &SubCommitsContext{
|
||||
@@ -40,17 +63,13 @@ func NewSubCommitsContext(
|
||||
ViewportListContextTrait: &ViewportListContextTrait{
|
||||
ListContextTrait: &ListContextTrait{
|
||||
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
|
||||
View: view,
|
||||
View: c.Views().SubCommits,
|
||||
WindowName: "branches",
|
||||
Key: SUB_COMMITS_CONTEXT_KEY,
|
||||
Kind: types.SIDE_CONTEXT,
|
||||
Focusable: true,
|
||||
Transient: true,
|
||||
}), ContextCallbackOpts{
|
||||
OnFocus: onFocus,
|
||||
OnFocusLost: onFocusLost,
|
||||
OnRenderToMain: onRenderToMain,
|
||||
}),
|
||||
})),
|
||||
list: viewModel,
|
||||
getDisplayStrings: getDisplayStrings,
|
||||
c: c,
|
||||
@@ -111,3 +130,9 @@ func (self *SubCommitsContext) SetLimitCommits(value bool) {
|
||||
func (self *SubCommitsContext) GetLimitCommits() bool {
|
||||
return self.limitCommits
|
||||
}
|
||||
|
||||
func (self *SubCommitsContext) GetDiffTerminals() []string {
|
||||
itemId := self.GetSelectedItemId()
|
||||
|
||||
return []string{itemId}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package context
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
)
|
||||
|
||||
@@ -13,33 +13,23 @@ type SubmodulesContext struct {
|
||||
|
||||
var _ types.IListContext = (*SubmodulesContext)(nil)
|
||||
|
||||
func NewSubmodulesContext(
|
||||
getModel func() []*models.SubmoduleConfig,
|
||||
view *gocui.View,
|
||||
getDisplayStrings func(startIdx int, length int) [][]string,
|
||||
func NewSubmodulesContext(c *ContextCommon) *SubmodulesContext {
|
||||
viewModel := NewBasicViewModel(func() []*models.SubmoduleConfig { return c.Model().Submodules })
|
||||
|
||||
onFocus func(types.OnFocusOpts) error,
|
||||
onRenderToMain func() error,
|
||||
onFocusLost func(opts types.OnFocusLostOpts) error,
|
||||
|
||||
c *types.HelperCommon,
|
||||
) *SubmodulesContext {
|
||||
viewModel := NewBasicViewModel(getModel)
|
||||
getDisplayStrings := func(startIdx int, length int) [][]string {
|
||||
return presentation.GetSubmoduleListDisplayStrings(c.Model().Submodules)
|
||||
}
|
||||
|
||||
return &SubmodulesContext{
|
||||
BasicViewModel: viewModel,
|
||||
ListContextTrait: &ListContextTrait{
|
||||
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
|
||||
View: view,
|
||||
View: c.Views().Submodules,
|
||||
WindowName: "files",
|
||||
Key: SUBMODULES_CONTEXT_KEY,
|
||||
Kind: types.SIDE_CONTEXT,
|
||||
Focusable: true,
|
||||
}), ContextCallbackOpts{
|
||||
OnFocus: onFocus,
|
||||
OnFocusLost: onFocusLost,
|
||||
OnRenderToMain: onRenderToMain,
|
||||
}),
|
||||
})),
|
||||
list: viewModel,
|
||||
getDisplayStrings: getDisplayStrings,
|
||||
c: c,
|
||||
|
||||
@@ -1,45 +1,59 @@
|
||||
package context
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/jesseduffield/lazygit/pkg/tasks"
|
||||
)
|
||||
|
||||
type SuggestionsContext struct {
|
||||
*BasicViewModel[*types.Suggestion]
|
||||
*ListContextTrait
|
||||
|
||||
State *SuggestionsContextState
|
||||
}
|
||||
|
||||
type SuggestionsContextState struct {
|
||||
Suggestions []*types.Suggestion
|
||||
OnConfirm func() error
|
||||
OnClose func() error
|
||||
AsyncHandler *tasks.AsyncHandler
|
||||
|
||||
// FindSuggestions will take a string that the user has typed into a prompt
|
||||
// and return a slice of suggestions which match that string.
|
||||
FindSuggestions func(string) []*types.Suggestion
|
||||
}
|
||||
|
||||
var _ types.IListContext = (*SuggestionsContext)(nil)
|
||||
|
||||
func NewSuggestionsContext(
|
||||
getModel func() []*types.Suggestion,
|
||||
view *gocui.View,
|
||||
getDisplayStrings func(startIdx int, length int) [][]string,
|
||||
|
||||
onFocus func(types.OnFocusOpts) error,
|
||||
onRenderToMain func() error,
|
||||
onFocusLost func(opts types.OnFocusLostOpts) error,
|
||||
|
||||
c *types.HelperCommon,
|
||||
c *ContextCommon,
|
||||
) *SuggestionsContext {
|
||||
state := &SuggestionsContextState{
|
||||
AsyncHandler: tasks.NewAsyncHandler(),
|
||||
}
|
||||
getModel := func() []*types.Suggestion {
|
||||
return state.Suggestions
|
||||
}
|
||||
|
||||
getDisplayStrings := func(startIdx int, length int) [][]string {
|
||||
return presentation.GetSuggestionListDisplayStrings(state.Suggestions)
|
||||
}
|
||||
|
||||
viewModel := NewBasicViewModel(getModel)
|
||||
|
||||
return &SuggestionsContext{
|
||||
State: state,
|
||||
BasicViewModel: viewModel,
|
||||
ListContextTrait: &ListContextTrait{
|
||||
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
|
||||
View: view,
|
||||
View: c.Views().Suggestions,
|
||||
WindowName: "suggestions",
|
||||
Key: SUGGESTIONS_CONTEXT_KEY,
|
||||
Kind: types.PERSISTENT_POPUP,
|
||||
Focusable: true,
|
||||
HasUncontrolledBounds: true,
|
||||
}), ContextCallbackOpts{
|
||||
OnFocus: onFocus,
|
||||
OnFocusLost: onFocusLost,
|
||||
OnRenderToMain: onRenderToMain,
|
||||
}),
|
||||
})),
|
||||
list: viewModel,
|
||||
getDisplayStrings: getDisplayStrings,
|
||||
c: c,
|
||||
@@ -55,3 +69,22 @@ func (self *SuggestionsContext) GetSelectedItemId() string {
|
||||
|
||||
return item.Value
|
||||
}
|
||||
|
||||
func (self *SuggestionsContext) SetSuggestions(suggestions []*types.Suggestion) {
|
||||
self.State.Suggestions = suggestions
|
||||
self.SetSelectedLineIdx(0)
|
||||
self.c.ResetViewOrigin(self.GetView())
|
||||
_ = self.HandleRender()
|
||||
}
|
||||
|
||||
func (self *SuggestionsContext) RefreshSuggestions() {
|
||||
self.State.AsyncHandler.Do(func() func() {
|
||||
findSuggestionsFn := self.State.FindSuggestions
|
||||
if findSuggestionsFn != nil {
|
||||
suggestions := findSuggestionsFn(self.c.GetPromptInput())
|
||||
return func() { self.SetSuggestions(suggestions) }
|
||||
} else {
|
||||
return func() {}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package context
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
)
|
||||
|
||||
@@ -11,35 +11,30 @@ type TagsContext struct {
|
||||
*ListContextTrait
|
||||
}
|
||||
|
||||
var _ types.IListContext = (*TagsContext)(nil)
|
||||
var (
|
||||
_ types.IListContext = (*TagsContext)(nil)
|
||||
_ types.DiffableContext = (*TagsContext)(nil)
|
||||
)
|
||||
|
||||
func NewTagsContext(
|
||||
getModel func() []*models.Tag,
|
||||
view *gocui.View,
|
||||
getDisplayStrings func(startIdx int, length int) [][]string,
|
||||
|
||||
onFocus func(types.OnFocusOpts) error,
|
||||
onRenderToMain func() error,
|
||||
onFocusLost func(opts types.OnFocusLostOpts) error,
|
||||
|
||||
c *types.HelperCommon,
|
||||
c *ContextCommon,
|
||||
) *TagsContext {
|
||||
viewModel := NewBasicViewModel(getModel)
|
||||
viewModel := NewBasicViewModel(func() []*models.Tag { return c.Model().Tags })
|
||||
|
||||
getDisplayStrings := func(startIdx int, length int) [][]string {
|
||||
return presentation.GetTagListDisplayStrings(c.Model().Tags, c.Modes().Diffing.Ref)
|
||||
}
|
||||
|
||||
return &TagsContext{
|
||||
BasicViewModel: viewModel,
|
||||
ListContextTrait: &ListContextTrait{
|
||||
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
|
||||
View: view,
|
||||
View: c.Views().Tags,
|
||||
WindowName: "branches",
|
||||
Key: TAGS_CONTEXT_KEY,
|
||||
Kind: types.SIDE_CONTEXT,
|
||||
Focusable: true,
|
||||
}), ContextCallbackOpts{
|
||||
OnFocus: onFocus,
|
||||
OnFocusLost: onFocusLost,
|
||||
OnRenderToMain: onRenderToMain,
|
||||
}),
|
||||
})),
|
||||
list: viewModel,
|
||||
getDisplayStrings: getDisplayStrings,
|
||||
c: c,
|
||||
@@ -63,3 +58,9 @@ func (self *TagsContext) GetSelectedRef() types.Ref {
|
||||
}
|
||||
return tag
|
||||
}
|
||||
|
||||
func (self *TagsContext) GetDiffTerminals() []string {
|
||||
itemId := self.GetSelectedItemId()
|
||||
|
||||
return []string{itemId}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
package context
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/generics/slices"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/filetree"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
)
|
||||
|
||||
@@ -14,33 +15,30 @@ type WorkingTreeContext struct {
|
||||
|
||||
var _ types.IListContext = (*WorkingTreeContext)(nil)
|
||||
|
||||
func NewWorkingTreeContext(
|
||||
getModel func() []*models.File,
|
||||
view *gocui.View,
|
||||
getDisplayStrings func(startIdx int, length int) [][]string,
|
||||
func NewWorkingTreeContext(c *ContextCommon) *WorkingTreeContext {
|
||||
viewModel := filetree.NewFileTreeViewModel(
|
||||
func() []*models.File { return c.Model().Files },
|
||||
c.Log,
|
||||
c.UserConfig.Gui.ShowFileTree,
|
||||
)
|
||||
|
||||
onFocus func(types.OnFocusOpts) error,
|
||||
onRenderToMain func() error,
|
||||
onFocusLost func(opts types.OnFocusLostOpts) error,
|
||||
|
||||
c *types.HelperCommon,
|
||||
) *WorkingTreeContext {
|
||||
viewModel := filetree.NewFileTreeViewModel(getModel, c.Log, c.UserConfig.Gui.ShowFileTree)
|
||||
getDisplayStrings := func(startIdx int, length int) [][]string {
|
||||
lines := presentation.RenderFileTree(viewModel, c.Modes().Diffing.Ref, c.Model().Submodules)
|
||||
return slices.Map(lines, func(line string) []string {
|
||||
return []string{line}
|
||||
})
|
||||
}
|
||||
|
||||
return &WorkingTreeContext{
|
||||
FileTreeViewModel: viewModel,
|
||||
ListContextTrait: &ListContextTrait{
|
||||
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
|
||||
View: view,
|
||||
View: c.Views().Files,
|
||||
WindowName: "files",
|
||||
Key: FILES_CONTEXT_KEY,
|
||||
Kind: types.SIDE_CONTEXT,
|
||||
Focusable: true,
|
||||
}), ContextCallbackOpts{
|
||||
OnFocus: onFocus,
|
||||
OnFocusLost: onFocusLost,
|
||||
OnRenderToMain: onRenderToMain,
|
||||
}),
|
||||
})),
|
||||
list: viewModel,
|
||||
getDisplayStrings: getDisplayStrings,
|
||||
c: c,
|
||||
|
||||
+9
-259
@@ -6,261 +6,11 @@ import (
|
||||
)
|
||||
|
||||
func (gui *Gui) contextTree() *context.ContextTree {
|
||||
return &context.ContextTree{
|
||||
Global: context.NewSimpleContext(
|
||||
context.NewBaseContext(context.NewBaseContextOpts{
|
||||
Kind: types.GLOBAL_CONTEXT,
|
||||
View: nil, // TODO: see if this breaks anything
|
||||
WindowName: "",
|
||||
Key: context.GLOBAL_CONTEXT_KEY,
|
||||
Focusable: false,
|
||||
HasUncontrolledBounds: true, // setting to true because the global context doesn't even have a view
|
||||
}),
|
||||
context.ContextCallbackOpts{
|
||||
OnRenderToMain: gui.statusRenderToMain,
|
||||
},
|
||||
),
|
||||
Status: context.NewSimpleContext(
|
||||
context.NewBaseContext(context.NewBaseContextOpts{
|
||||
Kind: types.SIDE_CONTEXT,
|
||||
View: gui.Views.Status,
|
||||
WindowName: "status",
|
||||
Key: context.STATUS_CONTEXT_KEY,
|
||||
Focusable: true,
|
||||
}),
|
||||
context.ContextCallbackOpts{
|
||||
OnRenderToMain: gui.statusRenderToMain,
|
||||
},
|
||||
),
|
||||
Snake: context.NewSimpleContext(
|
||||
context.NewBaseContext(context.NewBaseContextOpts{
|
||||
Kind: types.SIDE_CONTEXT,
|
||||
View: gui.Views.Snake,
|
||||
WindowName: "files",
|
||||
Key: context.SNAKE_CONTEXT_KEY,
|
||||
Focusable: true,
|
||||
}),
|
||||
context.ContextCallbackOpts{
|
||||
OnFocus: func(opts types.OnFocusOpts) error {
|
||||
gui.startSnake()
|
||||
return nil
|
||||
},
|
||||
OnFocusLost: func(opts types.OnFocusLostOpts) error {
|
||||
gui.snakeGame.Exit()
|
||||
gui.moveToTopOfWindow(gui.State.Contexts.Submodules)
|
||||
return nil
|
||||
},
|
||||
},
|
||||
),
|
||||
Files: gui.filesListContext(),
|
||||
Submodules: gui.submodulesListContext(),
|
||||
Menu: gui.menuListContext(),
|
||||
Remotes: gui.remotesListContext(),
|
||||
RemoteBranches: gui.remoteBranchesListContext(),
|
||||
LocalCommits: gui.branchCommitsListContext(),
|
||||
CommitFiles: gui.commitFilesListContext(),
|
||||
ReflogCommits: gui.reflogCommitsListContext(),
|
||||
SubCommits: gui.subCommitsListContext(),
|
||||
Branches: gui.branchesListContext(),
|
||||
Tags: gui.tagsListContext(),
|
||||
Stash: gui.stashListContext(),
|
||||
Suggestions: gui.suggestionsListContext(),
|
||||
Normal: context.NewSimpleContext(
|
||||
context.NewBaseContext(context.NewBaseContextOpts{
|
||||
Kind: types.MAIN_CONTEXT,
|
||||
View: gui.Views.Main,
|
||||
WindowName: "main",
|
||||
Key: context.NORMAL_MAIN_CONTEXT_KEY,
|
||||
Focusable: false,
|
||||
}),
|
||||
context.ContextCallbackOpts{
|
||||
OnFocus: func(opts types.OnFocusOpts) error {
|
||||
return nil // TODO: should we do something here? We should allow for scrolling the panel
|
||||
},
|
||||
},
|
||||
),
|
||||
NormalSecondary: context.NewSimpleContext(
|
||||
context.NewBaseContext(context.NewBaseContextOpts{
|
||||
Kind: types.MAIN_CONTEXT,
|
||||
View: gui.Views.Secondary,
|
||||
WindowName: "secondary",
|
||||
Key: context.NORMAL_SECONDARY_CONTEXT_KEY,
|
||||
Focusable: false,
|
||||
}),
|
||||
context.ContextCallbackOpts{},
|
||||
),
|
||||
Staging: context.NewPatchExplorerContext(
|
||||
gui.Views.Staging,
|
||||
"main",
|
||||
context.STAGING_MAIN_CONTEXT_KEY,
|
||||
func(opts types.OnFocusOpts) error {
|
||||
gui.Views.Staging.Wrap = false
|
||||
gui.Views.StagingSecondary.Wrap = false
|
||||
|
||||
return gui.refreshStagingPanel(opts)
|
||||
},
|
||||
func(opts types.OnFocusLostOpts) error {
|
||||
gui.State.Contexts.Staging.SetState(nil)
|
||||
|
||||
if opts.NewContextKey != context.STAGING_SECONDARY_CONTEXT_KEY {
|
||||
gui.Views.Staging.Wrap = true
|
||||
gui.Views.StagingSecondary.Wrap = true
|
||||
_ = gui.State.Contexts.Staging.Render(false)
|
||||
_ = gui.State.Contexts.StagingSecondary.Render(false)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
func() []int { return nil },
|
||||
gui.c,
|
||||
),
|
||||
StagingSecondary: context.NewPatchExplorerContext(
|
||||
gui.Views.StagingSecondary,
|
||||
"secondary",
|
||||
context.STAGING_SECONDARY_CONTEXT_KEY,
|
||||
func(opts types.OnFocusOpts) error {
|
||||
gui.Views.Staging.Wrap = false
|
||||
gui.Views.StagingSecondary.Wrap = false
|
||||
|
||||
return gui.refreshStagingPanel(opts)
|
||||
},
|
||||
func(opts types.OnFocusLostOpts) error {
|
||||
gui.State.Contexts.StagingSecondary.SetState(nil)
|
||||
|
||||
if opts.NewContextKey != context.STAGING_MAIN_CONTEXT_KEY {
|
||||
gui.Views.Staging.Wrap = true
|
||||
gui.Views.StagingSecondary.Wrap = true
|
||||
_ = gui.State.Contexts.Staging.Render(false)
|
||||
_ = gui.State.Contexts.StagingSecondary.Render(false)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
func() []int { return nil },
|
||||
gui.c,
|
||||
),
|
||||
CustomPatchBuilder: context.NewPatchExplorerContext(
|
||||
gui.Views.PatchBuilding,
|
||||
"main",
|
||||
context.PATCH_BUILDING_MAIN_CONTEXT_KEY,
|
||||
func(opts types.OnFocusOpts) error {
|
||||
// no need to change wrap on the secondary view because it can't be interacted with
|
||||
gui.Views.PatchBuilding.Wrap = false
|
||||
|
||||
return gui.refreshPatchBuildingPanel(opts)
|
||||
},
|
||||
func(opts types.OnFocusLostOpts) error {
|
||||
gui.Views.PatchBuilding.Wrap = true
|
||||
|
||||
if gui.git.Patch.PatchBuilder.IsEmpty() {
|
||||
gui.git.Patch.PatchBuilder.Reset()
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
func() []int {
|
||||
filename := gui.State.Contexts.CommitFiles.GetSelectedPath()
|
||||
includedLineIndices, err := gui.git.Patch.PatchBuilder.GetFileIncLineIndices(filename)
|
||||
if err != nil {
|
||||
gui.Log.Error(err)
|
||||
return nil
|
||||
}
|
||||
|
||||
return includedLineIndices
|
||||
},
|
||||
gui.c,
|
||||
),
|
||||
CustomPatchBuilderSecondary: context.NewSimpleContext(
|
||||
context.NewBaseContext(context.NewBaseContextOpts{
|
||||
Kind: types.MAIN_CONTEXT,
|
||||
View: gui.Views.PatchBuildingSecondary,
|
||||
WindowName: "secondary",
|
||||
Key: context.PATCH_BUILDING_SECONDARY_CONTEXT_KEY,
|
||||
Focusable: false,
|
||||
}),
|
||||
context.ContextCallbackOpts{},
|
||||
),
|
||||
MergeConflicts: context.NewMergeConflictsContext(
|
||||
gui.Views.MergeConflicts,
|
||||
context.ContextCallbackOpts{
|
||||
OnFocus: OnFocusWrapper(func() error {
|
||||
gui.Views.MergeConflicts.Wrap = false
|
||||
|
||||
return gui.refreshMergePanel(true)
|
||||
}),
|
||||
OnFocusLost: func(opts types.OnFocusLostOpts) error {
|
||||
gui.State.Contexts.MergeConflicts.SetUserScrolling(false)
|
||||
gui.State.Contexts.MergeConflicts.GetState().ResetConflictSelection()
|
||||
gui.Views.MergeConflicts.Wrap = true
|
||||
|
||||
return nil
|
||||
},
|
||||
},
|
||||
gui.c,
|
||||
func() map[string]string {
|
||||
// wrapping in a function because contexts are initialized before helpers
|
||||
return gui.helpers.MergeConflicts.GetMergingOptions()
|
||||
},
|
||||
),
|
||||
Confirmation: context.NewSimpleContext(
|
||||
context.NewBaseContext(context.NewBaseContextOpts{
|
||||
Kind: types.TEMPORARY_POPUP,
|
||||
View: gui.Views.Confirmation,
|
||||
WindowName: "confirmation",
|
||||
Key: context.CONFIRMATION_CONTEXT_KEY,
|
||||
Focusable: true,
|
||||
HasUncontrolledBounds: true,
|
||||
}),
|
||||
context.ContextCallbackOpts{
|
||||
OnFocus: OnFocusWrapper(gui.handleAskFocused),
|
||||
OnFocusLost: func(types.OnFocusLostOpts) error {
|
||||
gui.deactivateConfirmationPrompt()
|
||||
return nil
|
||||
},
|
||||
},
|
||||
),
|
||||
CommitMessage: context.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"),
|
||||
contextCommon := &context.ContextCommon{
|
||||
IGuiCommon: gui.c.IGuiCommon,
|
||||
Common: gui.c.Common,
|
||||
}
|
||||
return context.NewContextTree(contextCommon)
|
||||
}
|
||||
|
||||
// using this wrapper for when an onFocus function doesn't care about any potential
|
||||
@@ -271,10 +21,10 @@ func OnFocusWrapper(f func() error) func(opts types.OnFocusOpts) error {
|
||||
}
|
||||
}
|
||||
|
||||
func (gui *Gui) getPatchExplorerContexts() []types.IPatchExplorerContext {
|
||||
return []types.IPatchExplorerContext{
|
||||
gui.State.Contexts.Staging,
|
||||
gui.State.Contexts.StagingSecondary,
|
||||
gui.State.Contexts.CustomPatchBuilder,
|
||||
func (gui *Gui) defaultSideContext() types.Context {
|
||||
if gui.State.Modes.Filtering.Active() {
|
||||
return gui.State.Contexts.LocalCommits
|
||||
} else {
|
||||
return gui.State.Contexts.Files
|
||||
}
|
||||
}
|
||||
|
||||
+160
-79
@@ -7,104 +7,121 @@ 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) resetControllers() {
|
||||
func (gui *Gui) Helpers() *helpers.Helpers {
|
||||
return gui.helpers
|
||||
}
|
||||
|
||||
func (gui *Gui) resetHelpersAndControllers() {
|
||||
helperCommon := gui.c
|
||||
osCommand := gui.os
|
||||
model := gui.State.Model
|
||||
refsHelper := helpers.NewRefsHelper(
|
||||
helperCommon,
|
||||
gui.git,
|
||||
gui.State.Contexts,
|
||||
model,
|
||||
refsHelper := helpers.NewRefsHelper(helperCommon)
|
||||
|
||||
rebaseHelper := helpers.NewMergeAndRebaseHelper(helperCommon, refsHelper)
|
||||
suggestionsHelper := helpers.NewSuggestionsHelper(helperCommon)
|
||||
|
||||
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())
|
||||
}
|
||||
|
||||
getCommitDescription := func() string {
|
||||
return strings.TrimSpace(gui.Views.CommitDescription.TextArea.GetContent())
|
||||
}
|
||||
commitsHelper := helpers.NewCommitsHelper(helperCommon,
|
||||
getCommitSummary,
|
||||
setCommitSummary,
|
||||
getCommitDescription,
|
||||
setCommitDescription,
|
||||
)
|
||||
|
||||
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
|
||||
}
|
||||
gpgHelper := helpers.NewGpgHelper(helperCommon, gui.os, gui.git)
|
||||
gpgHelper := helpers.NewGpgHelper(helperCommon)
|
||||
viewHelper := helpers.NewViewHelper(helperCommon, gui.State.Contexts)
|
||||
recordDirectoryHelper := helpers.NewRecordDirectoryHelper(helperCommon)
|
||||
patchBuildingHelper := helpers.NewPatchBuildingHelper(helperCommon)
|
||||
stagingHelper := helpers.NewStagingHelper(helperCommon)
|
||||
mergeConflictsHelper := helpers.NewMergeConflictsHelper(helperCommon)
|
||||
refreshHelper := helpers.NewRefreshHelper(helperCommon, refsHelper, rebaseHelper, patchBuildingHelper, stagingHelper, mergeConflictsHelper, gui.fileWatcher)
|
||||
diffHelper := helpers.NewDiffHelper(helperCommon)
|
||||
cherryPickHelper := helpers.NewCherryPickHelper(
|
||||
helperCommon,
|
||||
rebaseHelper,
|
||||
)
|
||||
bisectHelper := helpers.NewBisectHelper(helperCommon)
|
||||
windowHelper := helpers.NewWindowHelper(helperCommon, viewHelper)
|
||||
modeHelper := helpers.NewModeHelper(
|
||||
helperCommon,
|
||||
diffHelper,
|
||||
patchBuildingHelper,
|
||||
cherryPickHelper,
|
||||
rebaseHelper,
|
||||
bisectHelper,
|
||||
)
|
||||
appStatusHelper := helpers.NewAppStatusHelper(
|
||||
helperCommon,
|
||||
func() *status.StatusManager { return gui.statusManager },
|
||||
)
|
||||
gui.helpers = &helpers.Helpers{
|
||||
Refs: refsHelper,
|
||||
Host: helpers.NewHostHelper(helperCommon, gui.git),
|
||||
PatchBuilding: helpers.NewPatchBuildingHelper(helperCommon, gui.git, gui.State.Contexts),
|
||||
Bisect: helpers.NewBisectHelper(helperCommon, gui.git),
|
||||
Suggestions: suggestionsHelper,
|
||||
Files: helpers.NewFilesHelper(helperCommon, gui.git, osCommand),
|
||||
WorkingTree: helpers.NewWorkingTreeHelper(helperCommon, gui.git, gui.State.Contexts, refsHelper, model, 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,
|
||||
Refs: refsHelper,
|
||||
Host: helpers.NewHostHelper(helperCommon),
|
||||
PatchBuilding: patchBuildingHelper,
|
||||
Staging: stagingHelper,
|
||||
Bisect: bisectHelper,
|
||||
Suggestions: suggestionsHelper,
|
||||
Files: helpers.NewFilesHelper(helperCommon),
|
||||
WorkingTree: helpers.NewWorkingTreeHelper(helperCommon, refsHelper, commitsHelper, gpgHelper),
|
||||
Tags: helpers.NewTagsHelper(helperCommon),
|
||||
GPG: helpers.NewGpgHelper(helperCommon),
|
||||
MergeAndRebase: rebaseHelper,
|
||||
MergeConflicts: mergeConflictsHelper,
|
||||
CherryPick: cherryPickHelper,
|
||||
Upstream: helpers.NewUpstreamHelper(helperCommon, suggestionsHelper.GetRemoteBranchesSuggestionsFunc),
|
||||
AmendHelper: helpers.NewAmendHelper(helperCommon, gpgHelper),
|
||||
Commits: commitsHelper,
|
||||
Snake: helpers.NewSnakeHelper(helperCommon),
|
||||
Diff: diffHelper,
|
||||
Repos: helpers.NewRecentReposHelper(helperCommon, recordDirectoryHelper, gui.onNewRepo),
|
||||
RecordDirectory: recordDirectoryHelper,
|
||||
Update: helpers.NewUpdateHelper(helperCommon, gui.Updater),
|
||||
Window: windowHelper,
|
||||
View: viewHelper,
|
||||
Refresh: refreshHelper,
|
||||
Confirmation: helpers.NewConfirmationHelper(helperCommon),
|
||||
Mode: modeHelper,
|
||||
AppStatus: appStatusHelper,
|
||||
WindowArrangement: helpers.NewWindowArrangementHelper(
|
||||
gui.c,
|
||||
windowHelper,
|
||||
modeHelper,
|
||||
appStatusHelper,
|
||||
),
|
||||
Upstream: helpers.NewUpstreamHelper(helperCommon, model, suggestionsHelper.GetRemoteBranchesSuggestionsFunc),
|
||||
AmendHelper: helpers.NewAmendHelper(helperCommon, gui.git, gpgHelper),
|
||||
}
|
||||
|
||||
gui.CustomCommandsClient = custom_commands.NewClient(
|
||||
helperCommon,
|
||||
gui.os,
|
||||
gui.git,
|
||||
gui.State.Contexts,
|
||||
gui.helpers,
|
||||
)
|
||||
|
||||
common := controllers.NewControllerCommon(
|
||||
helperCommon,
|
||||
osCommand,
|
||||
gui.git,
|
||||
gui.helpers,
|
||||
model,
|
||||
gui.State.Contexts,
|
||||
gui.State.Modes,
|
||||
&gui.Mutexes,
|
||||
)
|
||||
common := controllers.NewControllerCommon(helperCommon, gui)
|
||||
|
||||
syncController := controllers.NewSyncController(
|
||||
common,
|
||||
)
|
||||
|
||||
submodulesController := controllers.NewSubmodulesController(
|
||||
common,
|
||||
gui.enterSubmodule,
|
||||
)
|
||||
submodulesController := controllers.NewSubmodulesController(common)
|
||||
|
||||
bisectController := controllers.NewBisectController(common)
|
||||
|
||||
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,
|
||||
getCommitMessage,
|
||||
onCommitAttempt,
|
||||
onCommitSuccess,
|
||||
)
|
||||
|
||||
commitDescriptionController := controllers.NewCommitDescriptionController(
|
||||
common,
|
||||
)
|
||||
|
||||
remoteBranchesController := controllers.NewRemoteBranchesController(common)
|
||||
@@ -114,9 +131,6 @@ func (gui *Gui) resetControllers() {
|
||||
tagsController := controllers.NewTagsController(common)
|
||||
filesController := controllers.NewFilesController(
|
||||
common,
|
||||
gui.enterSubmodule,
|
||||
setCommitMessage,
|
||||
getSavedCommitMessage,
|
||||
)
|
||||
mergeConflictsController := controllers.NewMergeConflictsController(common)
|
||||
remotesController := controllers.NewRemotesController(
|
||||
@@ -137,7 +151,34 @@ func (gui *Gui) resetControllers() {
|
||||
stagingController := controllers.NewStagingController(common, gui.State.Contexts.Staging, gui.State.Contexts.StagingSecondary, false)
|
||||
stagingSecondaryController := controllers.NewStagingController(common, gui.State.Contexts.StagingSecondary, gui.State.Contexts.Staging, true)
|
||||
patchBuildingController := controllers.NewPatchBuildingController(common)
|
||||
snakeController := controllers.NewSnakeController(common, func() *snake.Game { return gui.snakeGame })
|
||||
snakeController := controllers.NewSnakeController(common)
|
||||
reflogCommitsController := controllers.NewReflogCommitsController(common)
|
||||
subCommitsController := controllers.NewSubCommitsController(common)
|
||||
statusController := controllers.NewStatusController(common)
|
||||
commandLogController := controllers.NewCommandLogController(common)
|
||||
confirmationController := controllers.NewConfirmationController(common)
|
||||
suggestionsController := controllers.NewSuggestionsController(common)
|
||||
jumpToSideWindowController := controllers.NewJumpToSideWindowController(common)
|
||||
|
||||
sideWindowControllerFactory := controllers.NewSideWindowControllerFactory(common)
|
||||
|
||||
// allow for navigating between side window contexts
|
||||
for _, context := range []types.Context{
|
||||
gui.State.Contexts.Status,
|
||||
gui.State.Contexts.Remotes,
|
||||
gui.State.Contexts.Tags,
|
||||
gui.State.Contexts.Branches,
|
||||
gui.State.Contexts.RemoteBranches,
|
||||
gui.State.Contexts.Files,
|
||||
gui.State.Contexts.Submodules,
|
||||
gui.State.Contexts.ReflogCommits,
|
||||
gui.State.Contexts.LocalCommits,
|
||||
gui.State.Contexts.CommitFiles,
|
||||
gui.State.Contexts.SubCommits,
|
||||
gui.State.Contexts.Stash,
|
||||
} {
|
||||
controllers.AttachControllers(context, sideWindowControllerFactory.Create(context))
|
||||
}
|
||||
|
||||
setSubCommits := func(commits []*models.Commit) {
|
||||
gui.Mutexes.SubCommitsMutex.Lock()
|
||||
@@ -163,7 +204,7 @@ func (gui *Gui) resetControllers() {
|
||||
gui.State.Contexts.Stash,
|
||||
} {
|
||||
controllers.AttachControllers(context, controllers.NewSwitchToDiffFilesController(
|
||||
common, gui.SwitchToCommitFilesContext, context,
|
||||
common, context, gui.State.Contexts.CommitFiles,
|
||||
))
|
||||
}
|
||||
|
||||
@@ -175,6 +216,14 @@ func (gui *Gui) resetControllers() {
|
||||
controllers.AttachControllers(context, controllers.NewBasicCommitsController(common, context))
|
||||
}
|
||||
|
||||
controllers.AttachControllers(gui.State.Contexts.ReflogCommits,
|
||||
reflogCommitsController,
|
||||
)
|
||||
|
||||
controllers.AttachControllers(gui.State.Contexts.SubCommits,
|
||||
subCommitsController,
|
||||
)
|
||||
|
||||
// TODO: add scroll controllers for main panels (need to bring some more functionality across for that e.g. reading more from the currently displayed git command)
|
||||
controllers.AttachControllers(gui.State.Contexts.Staging,
|
||||
stagingController,
|
||||
@@ -250,15 +299,36 @@ func (gui *Gui) resetControllers() {
|
||||
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,
|
||||
@@ -266,8 +336,19 @@ func (gui *Gui) resetControllers() {
|
||||
)
|
||||
|
||||
// this must come last so that we've got our click handlers defined against the context
|
||||
listControllerFactory := controllers.NewListControllerFactory(gui.c)
|
||||
for _, context := range gui.getListContexts() {
|
||||
listControllerFactory := controllers.NewListControllerFactory(common)
|
||||
for _, context := range gui.c.Context().AllList() {
|
||||
controllers.AttachControllers(context, listControllerFactory.Create(context))
|
||||
}
|
||||
}
|
||||
|
||||
func (gui *Gui) getCommitMessageSetTextareaTextFn(getView func() *gocui.View) func(string) {
|
||||
return func(text string) {
|
||||
// using a getView function so that we don't need to worry about when the view is created
|
||||
view := getView()
|
||||
view.ClearTextArea()
|
||||
view.TextArea.TypeString(text)
|
||||
gui.helpers.Confirmation.ResizeCommitMessagePanels()
|
||||
view.RenderTextArea()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,5 +7,8 @@ func AttachControllers(context types.Context, controllers ...types.IController)
|
||||
context.AddKeybindingsFn(controller.GetKeybindings)
|
||||
context.AddMouseKeybindingsFn(controller.GetMouseKeybindings)
|
||||
context.AddOnClickFn(controller.GetOnClick())
|
||||
context.AddOnRenderToMainFn(controller.GetOnRenderToMain())
|
||||
context.AddOnFocusFn(controller.GetOnFocus())
|
||||
context.AddOnFocusLostFn(controller.GetOnFocusLost())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,3 +18,15 @@ func (self *baseController) GetMouseKeybindings(opts types.KeybindingsOpts) []*g
|
||||
func (self *baseController) GetOnClick() func() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *baseController) GetOnRenderToMain() func() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *baseController) GetOnFocus() func(types.OnFocusOpts) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *baseController) GetOnFocusLost() func(types.OnFocusLostOpts) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -20,15 +20,15 @@ type ContainsCommits interface {
|
||||
|
||||
type BasicCommitsController struct {
|
||||
baseController
|
||||
*controllerCommon
|
||||
c *ControllerCommon
|
||||
context ContainsCommits
|
||||
}
|
||||
|
||||
func NewBasicCommitsController(controllerCommon *controllerCommon, context ContainsCommits) *BasicCommitsController {
|
||||
func NewBasicCommitsController(controllerCommon *ControllerCommon, context ContainsCommits) *BasicCommitsController {
|
||||
return &BasicCommitsController{
|
||||
baseController: baseController{},
|
||||
controllerCommon: controllerCommon,
|
||||
context: context,
|
||||
baseController: baseController{},
|
||||
c: controllerCommon,
|
||||
context: context,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,7 +73,7 @@ func (self *BasicCommitsController) GetKeybindings(opts types.KeybindingsOpts) [
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Commits.ResetCherryPick),
|
||||
Handler: self.helpers.CherryPick.Reset,
|
||||
Handler: self.c.Helpers().CherryPick.Reset,
|
||||
Description: self.c.Tr.LcResetCherryPick,
|
||||
},
|
||||
}
|
||||
@@ -141,7 +141,7 @@ func (self *BasicCommitsController) copyCommitAttribute(commit *models.Commit) e
|
||||
|
||||
func (self *BasicCommitsController) copyCommitSHAToClipboard(commit *models.Commit) error {
|
||||
self.c.LogAction(self.c.Tr.Actions.CopyCommitSHAToClipboard)
|
||||
if err := self.os.CopyToClipboard(commit.Sha); err != nil {
|
||||
if err := self.c.OS().CopyToClipboard(commit.Sha); err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
|
||||
@@ -150,13 +150,13 @@ func (self *BasicCommitsController) copyCommitSHAToClipboard(commit *models.Comm
|
||||
}
|
||||
|
||||
func (self *BasicCommitsController) copyCommitURLToClipboard(commit *models.Commit) error {
|
||||
url, err := self.helpers.Host.GetCommitURL(commit.Sha)
|
||||
url, err := self.c.Helpers().Host.GetCommitURL(commit.Sha)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
self.c.LogAction(self.c.Tr.Actions.CopyCommitURLToClipboard)
|
||||
if err := self.os.CopyToClipboard(url); err != nil {
|
||||
if err := self.c.OS().CopyToClipboard(url); err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
|
||||
@@ -165,13 +165,13 @@ func (self *BasicCommitsController) copyCommitURLToClipboard(commit *models.Comm
|
||||
}
|
||||
|
||||
func (self *BasicCommitsController) copyCommitDiffToClipboard(commit *models.Commit) error {
|
||||
diff, err := self.git.Commit.GetCommitDiff(commit.Sha)
|
||||
diff, err := self.c.Git().Commit.GetCommitDiff(commit.Sha)
|
||||
if err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
|
||||
self.c.LogAction(self.c.Tr.Actions.CopyCommitDiffToClipboard)
|
||||
if err := self.os.CopyToClipboard(diff); err != nil {
|
||||
if err := self.c.OS().CopyToClipboard(diff); err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
|
||||
@@ -180,7 +180,7 @@ func (self *BasicCommitsController) copyCommitDiffToClipboard(commit *models.Com
|
||||
}
|
||||
|
||||
func (self *BasicCommitsController) copyAuthorToClipboard(commit *models.Commit) error {
|
||||
author, err := self.git.Commit.GetCommitAuthor(commit.Sha)
|
||||
author, err := self.c.Git().Commit.GetCommitAuthor(commit.Sha)
|
||||
if err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
@@ -188,7 +188,7 @@ func (self *BasicCommitsController) copyAuthorToClipboard(commit *models.Commit)
|
||||
formattedAuthor := fmt.Sprintf("%s <%s>", author.Name, author.Email)
|
||||
|
||||
self.c.LogAction(self.c.Tr.Actions.CopyCommitAuthorToClipboard)
|
||||
if err := self.os.CopyToClipboard(formattedAuthor); err != nil {
|
||||
if err := self.c.OS().CopyToClipboard(formattedAuthor); err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
|
||||
@@ -197,13 +197,13 @@ func (self *BasicCommitsController) copyAuthorToClipboard(commit *models.Commit)
|
||||
}
|
||||
|
||||
func (self *BasicCommitsController) copyCommitMessageToClipboard(commit *models.Commit) error {
|
||||
message, err := self.git.Commit.GetCommitMessage(commit.Sha)
|
||||
message, err := self.c.Git().Commit.GetCommitMessage(commit.Sha)
|
||||
if err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
|
||||
self.c.LogAction(self.c.Tr.Actions.CopyCommitMessageToClipboard)
|
||||
if err := self.os.CopyToClipboard(message); err != nil {
|
||||
if err := self.c.OS().CopyToClipboard(message); err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
|
||||
@@ -212,13 +212,13 @@ func (self *BasicCommitsController) copyCommitMessageToClipboard(commit *models.
|
||||
}
|
||||
|
||||
func (self *BasicCommitsController) openInBrowser(commit *models.Commit) error {
|
||||
url, err := self.helpers.Host.GetCommitURL(commit.Sha)
|
||||
url, err := self.c.Helpers().Host.GetCommitURL(commit.Sha)
|
||||
if err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
|
||||
self.c.LogAction(self.c.Tr.Actions.OpenCommitInBrowser)
|
||||
if err := self.os.OpenLink(url); err != nil {
|
||||
if err := self.c.OS().OpenLink(url); err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
|
||||
@@ -226,11 +226,11 @@ func (self *BasicCommitsController) openInBrowser(commit *models.Commit) error {
|
||||
}
|
||||
|
||||
func (self *BasicCommitsController) newBranch(commit *models.Commit) error {
|
||||
return self.helpers.Refs.NewBranch(commit.RefName(), commit.Description(), "")
|
||||
return self.c.Helpers().Refs.NewBranch(commit.RefName(), commit.Description(), "")
|
||||
}
|
||||
|
||||
func (self *BasicCommitsController) createResetMenu(commit *models.Commit) error {
|
||||
return self.helpers.Refs.CreateGitResetMenu(commit.Sha)
|
||||
return self.c.Helpers().Refs.CreateGitResetMenu(commit.Sha)
|
||||
}
|
||||
|
||||
func (self *BasicCommitsController) checkout(commit *models.Commit) error {
|
||||
@@ -239,15 +239,15 @@ func (self *BasicCommitsController) checkout(commit *models.Commit) error {
|
||||
Prompt: self.c.Tr.SureCheckoutThisCommit,
|
||||
HandleConfirm: func() error {
|
||||
self.c.LogAction(self.c.Tr.Actions.CheckoutCommit)
|
||||
return self.helpers.Refs.CheckoutRef(commit.Sha, types.CheckoutRefOptions{})
|
||||
return self.c.Helpers().Refs.CheckoutRef(commit.Sha, types.CheckoutRefOptions{})
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func (self *BasicCommitsController) copy(commit *models.Commit) error {
|
||||
return self.helpers.CherryPick.Copy(commit, self.context.GetCommits(), self.context)
|
||||
return self.c.Helpers().CherryPick.Copy(commit, self.context.GetCommits(), self.context)
|
||||
}
|
||||
|
||||
func (self *BasicCommitsController) copyRange(*models.Commit) error {
|
||||
return self.helpers.CherryPick.CopyRange(self.context.GetSelectedLineIdx(), self.context.GetCommits(), self.context)
|
||||
return self.c.Helpers().CherryPick.CopyRange(self.context.GetSelectedLineIdx(), self.context.GetCommits(), self.context)
|
||||
}
|
||||
|
||||
@@ -12,17 +12,17 @@ import (
|
||||
|
||||
type BisectController struct {
|
||||
baseController
|
||||
*controllerCommon
|
||||
c *ControllerCommon
|
||||
}
|
||||
|
||||
var _ types.IController = &BisectController{}
|
||||
|
||||
func NewBisectController(
|
||||
common *controllerCommon,
|
||||
common *ControllerCommon,
|
||||
) *BisectController {
|
||||
return &BisectController{
|
||||
baseController: baseController{},
|
||||
controllerCommon: common,
|
||||
baseController: baseController{},
|
||||
c: common,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ func (self *BisectController) GetKeybindings(opts types.KeybindingsOpts) []*type
|
||||
func (self *BisectController) openMenu(commit *models.Commit) error {
|
||||
// no shame in getting this directly rather than using the cached value
|
||||
// given how cheap it is to obtain
|
||||
info := self.git.Bisect.GetInfo()
|
||||
info := self.c.Git().Bisect.GetInfo()
|
||||
if info.Started() {
|
||||
return self.openMidBisectMenu(info, commit)
|
||||
} else {
|
||||
@@ -63,14 +63,14 @@ func (self *BisectController) openMidBisectMenu(info *git_commands.BisectInfo, c
|
||||
selectCurrentAfter := info.GetCurrentSha() == "" || info.GetCurrentSha() == commit.Sha
|
||||
// we need to wait to reselect if our bisect commits aren't ancestors of our 'start'
|
||||
// ref, because we'll be reloading our commits in that case.
|
||||
waitToReselect := selectCurrentAfter && !self.git.Bisect.ReachableFromStart(info)
|
||||
waitToReselect := selectCurrentAfter && !self.c.Git().Bisect.ReachableFromStart(info)
|
||||
|
||||
menuItems := []*types.MenuItem{
|
||||
{
|
||||
Label: fmt.Sprintf(self.c.Tr.Bisect.Mark, commit.ShortSha(), info.NewTerm()),
|
||||
OnPress: func() error {
|
||||
self.c.LogAction(self.c.Tr.Actions.BisectMark)
|
||||
if err := self.git.Bisect.Mark(commit.Sha, info.NewTerm()); err != nil {
|
||||
if err := self.c.Git().Bisect.Mark(commit.Sha, info.NewTerm()); err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
|
||||
@@ -82,7 +82,7 @@ func (self *BisectController) openMidBisectMenu(info *git_commands.BisectInfo, c
|
||||
Label: fmt.Sprintf(self.c.Tr.Bisect.Mark, commit.ShortSha(), info.OldTerm()),
|
||||
OnPress: func() error {
|
||||
self.c.LogAction(self.c.Tr.Actions.BisectMark)
|
||||
if err := self.git.Bisect.Mark(commit.Sha, info.OldTerm()); err != nil {
|
||||
if err := self.c.Git().Bisect.Mark(commit.Sha, info.OldTerm()); err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
|
||||
@@ -94,7 +94,7 @@ func (self *BisectController) openMidBisectMenu(info *git_commands.BisectInfo, c
|
||||
Label: fmt.Sprintf(self.c.Tr.Bisect.Skip, commit.ShortSha()),
|
||||
OnPress: func() error {
|
||||
self.c.LogAction(self.c.Tr.Actions.BisectSkip)
|
||||
if err := self.git.Bisect.Skip(commit.Sha); err != nil {
|
||||
if err := self.c.Git().Bisect.Skip(commit.Sha); err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
|
||||
@@ -105,7 +105,7 @@ func (self *BisectController) openMidBisectMenu(info *git_commands.BisectInfo, c
|
||||
{
|
||||
Label: self.c.Tr.Bisect.ResetOption,
|
||||
OnPress: func() error {
|
||||
return self.helpers.Bisect.Reset()
|
||||
return self.c.Helpers().Bisect.Reset()
|
||||
},
|
||||
Key: 'r',
|
||||
},
|
||||
@@ -125,15 +125,15 @@ func (self *BisectController) openStartBisectMenu(info *git_commands.BisectInfo,
|
||||
Label: fmt.Sprintf(self.c.Tr.Bisect.MarkStart, commit.ShortSha(), info.NewTerm()),
|
||||
OnPress: func() error {
|
||||
self.c.LogAction(self.c.Tr.Actions.StartBisect)
|
||||
if err := self.git.Bisect.Start(); err != nil {
|
||||
if err := self.c.Git().Bisect.Start(); err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
|
||||
if err := self.git.Bisect.Mark(commit.Sha, info.NewTerm()); err != nil {
|
||||
if err := self.c.Git().Bisect.Mark(commit.Sha, info.NewTerm()); err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
|
||||
return self.helpers.Bisect.PostBisectCommandRefresh()
|
||||
return self.c.Helpers().Bisect.PostBisectCommandRefresh()
|
||||
},
|
||||
Key: 'b',
|
||||
},
|
||||
@@ -141,15 +141,15 @@ func (self *BisectController) openStartBisectMenu(info *git_commands.BisectInfo,
|
||||
Label: fmt.Sprintf(self.c.Tr.Bisect.MarkStart, commit.ShortSha(), info.OldTerm()),
|
||||
OnPress: func() error {
|
||||
self.c.LogAction(self.c.Tr.Actions.StartBisect)
|
||||
if err := self.git.Bisect.Start(); err != nil {
|
||||
if err := self.c.Git().Bisect.Start(); err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
|
||||
if err := self.git.Bisect.Mark(commit.Sha, info.OldTerm()); err != nil {
|
||||
if err := self.c.Git().Bisect.Mark(commit.Sha, info.OldTerm()); err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
|
||||
return self.helpers.Bisect.PostBisectCommandRefresh()
|
||||
return self.c.Helpers().Bisect.PostBisectCommandRefresh()
|
||||
},
|
||||
Key: 'g',
|
||||
},
|
||||
@@ -163,7 +163,7 @@ func (self *BisectController) showBisectCompleteMessage(candidateShas []string)
|
||||
prompt = self.c.Tr.Bisect.CompletePromptIndeterminate
|
||||
}
|
||||
|
||||
formattedCommits, err := self.git.Commit.GetCommitsOneline(candidateShas)
|
||||
formattedCommits, err := self.c.Git().Commit.GetCommitsOneline(candidateShas)
|
||||
if err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
@@ -173,17 +173,17 @@ func (self *BisectController) showBisectCompleteMessage(candidateShas []string)
|
||||
Prompt: fmt.Sprintf(prompt, strings.TrimSpace(formattedCommits)),
|
||||
HandleConfirm: func() error {
|
||||
self.c.LogAction(self.c.Tr.Actions.ResetBisect)
|
||||
if err := self.git.Bisect.Reset(); err != nil {
|
||||
if err := self.c.Git().Bisect.Reset(); err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
|
||||
return self.helpers.Bisect.PostBisectCommandRefresh()
|
||||
return self.c.Helpers().Bisect.PostBisectCommandRefresh()
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func (self *BisectController) afterMark(selectCurrent bool, waitToReselect bool) error {
|
||||
done, candidateShas, err := self.git.Bisect.IsDone()
|
||||
done, candidateShas, err := self.c.Git().Bisect.IsDone()
|
||||
if err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
@@ -211,15 +211,15 @@ func (self *BisectController) afterBisectMarkRefresh(selectCurrent bool, waitToR
|
||||
} else {
|
||||
selectFn()
|
||||
|
||||
return self.helpers.Bisect.PostBisectCommandRefresh()
|
||||
return self.c.Helpers().Bisect.PostBisectCommandRefresh()
|
||||
}
|
||||
}
|
||||
|
||||
func (self *BisectController) selectCurrentBisectCommit() {
|
||||
info := self.git.Bisect.GetInfo()
|
||||
info := self.c.Git().Bisect.GetInfo()
|
||||
if info.GetCurrentSha() != "" {
|
||||
// find index of commit with that sha, move cursor to that.
|
||||
for i, commit := range self.model.Commits {
|
||||
for i, commit := range self.c.Model().Commits {
|
||||
if commit.Sha == info.GetCurrentSha() {
|
||||
self.context().SetSelectedLineIdx(i)
|
||||
_ = self.context().HandleFocus(types.OnFocusOpts{})
|
||||
@@ -245,5 +245,5 @@ func (self *BisectController) Context() types.Context {
|
||||
}
|
||||
|
||||
func (self *BisectController) context() *context.LocalCommitsContext {
|
||||
return self.contexts.LocalCommits
|
||||
return self.c.Contexts().LocalCommits
|
||||
}
|
||||
|
||||
@@ -14,17 +14,17 @@ import (
|
||||
|
||||
type BranchesController struct {
|
||||
baseController
|
||||
*controllerCommon
|
||||
c *ControllerCommon
|
||||
}
|
||||
|
||||
var _ types.IController = &BranchesController{}
|
||||
|
||||
func NewBranchesController(
|
||||
common *controllerCommon,
|
||||
common *ControllerCommon,
|
||||
) *BranchesController {
|
||||
return &BranchesController{
|
||||
baseController: baseController{},
|
||||
controllerCommon: common,
|
||||
baseController: baseController{},
|
||||
c: common,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,6 +111,30 @@ func (self *BranchesController) GetKeybindings(opts types.KeybindingsOpts) []*ty
|
||||
}
|
||||
}
|
||||
|
||||
func (self *BranchesController) GetOnRenderToMain() func() error {
|
||||
return func() error {
|
||||
return self.c.Helpers().Diff.WithDiffModeCheck(func() error {
|
||||
var task types.UpdateTask
|
||||
branch := self.context().GetSelected()
|
||||
if branch == nil {
|
||||
task = types.NewRenderStringTask(self.c.Tr.NoBranchesThisRepo)
|
||||
} else {
|
||||
cmdObj := self.c.Git().Branch.GetGraphCmdObj(branch.FullRefName())
|
||||
|
||||
task = types.NewRunPtyTask(cmdObj.GetCmd())
|
||||
}
|
||||
|
||||
return self.c.RenderToMainViews(types.RefreshMainOpts{
|
||||
Pair: self.c.MainViewPairs().Normal,
|
||||
Main: &types.ViewUpdateOpts{
|
||||
Title: self.c.Tr.LogTitle,
|
||||
Task: task,
|
||||
},
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (self *BranchesController) setUpstream(selectedBranch *models.Branch) error {
|
||||
return self.c.Menu(types.CreateMenuOptions{
|
||||
Title: self.c.Tr.Actions.SetUnsetUpstream,
|
||||
@@ -118,7 +142,7 @@ func (self *BranchesController) setUpstream(selectedBranch *models.Branch) error
|
||||
{
|
||||
LabelColumns: []string{self.c.Tr.LcUnsetUpstream},
|
||||
OnPress: func() error {
|
||||
if err := self.git.Branch.UnsetUpstream(selectedBranch.Name); err != nil {
|
||||
if err := self.c.Git().Branch.UnsetUpstream(selectedBranch.Name); err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
if err := self.c.Refresh(types.RefreshOptions{
|
||||
@@ -137,13 +161,13 @@ func (self *BranchesController) setUpstream(selectedBranch *models.Branch) error
|
||||
{
|
||||
LabelColumns: []string{self.c.Tr.LcSetUpstream},
|
||||
OnPress: func() error {
|
||||
return self.helpers.Upstream.PromptForUpstreamWithoutInitialContent(selectedBranch, func(upstream string) error {
|
||||
upstreamRemote, upstreamBranch, err := self.helpers.Upstream.ParseUpstream(upstream)
|
||||
return self.c.Helpers().Upstream.PromptForUpstreamWithoutInitialContent(selectedBranch, func(upstream string) error {
|
||||
upstreamRemote, upstreamBranch, err := self.c.Helpers().Upstream.ParseUpstream(upstream)
|
||||
if err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
|
||||
if err := self.git.Branch.SetUpstream(upstreamRemote, upstreamBranch, selectedBranch.Name); err != nil {
|
||||
if err := self.c.Git().Branch.SetUpstream(upstreamRemote, upstreamBranch, selectedBranch.Name); err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
if err := self.c.Refresh(types.RefreshOptions{
|
||||
@@ -169,16 +193,16 @@ func (self *BranchesController) Context() types.Context {
|
||||
}
|
||||
|
||||
func (self *BranchesController) context() *context.BranchesContext {
|
||||
return self.contexts.Branches
|
||||
return self.c.Contexts().Branches
|
||||
}
|
||||
|
||||
func (self *BranchesController) press(selectedBranch *models.Branch) error {
|
||||
if selectedBranch == self.helpers.Refs.GetCheckedOutRef() {
|
||||
if selectedBranch == self.c.Helpers().Refs.GetCheckedOutRef() {
|
||||
return self.c.ErrorMsg(self.c.Tr.AlreadyCheckedOutBranch)
|
||||
}
|
||||
|
||||
self.c.LogAction(self.c.Tr.Actions.CheckoutBranch)
|
||||
return self.helpers.Refs.CheckoutRef(selectedBranch.Name, types.CheckoutRefOptions{})
|
||||
return self.c.Helpers().Refs.CheckoutRef(selectedBranch.Name, types.CheckoutRefOptions{})
|
||||
}
|
||||
|
||||
func (self *BranchesController) handleCreatePullRequest(selectedBranch *models.Branch) error {
|
||||
@@ -186,7 +210,7 @@ func (self *BranchesController) handleCreatePullRequest(selectedBranch *models.B
|
||||
}
|
||||
|
||||
func (self *BranchesController) handleCreatePullRequestMenu(selectedBranch *models.Branch) error {
|
||||
checkedOutBranch := self.helpers.Refs.GetCheckedOutRef()
|
||||
checkedOutBranch := self.c.Helpers().Refs.GetCheckedOutRef()
|
||||
|
||||
return self.createPullRequestMenu(selectedBranch, checkedOutBranch)
|
||||
}
|
||||
@@ -194,18 +218,18 @@ func (self *BranchesController) handleCreatePullRequestMenu(selectedBranch *mode
|
||||
func (self *BranchesController) copyPullRequestURL() error {
|
||||
branch := self.context().GetSelected()
|
||||
|
||||
branchExistsOnRemote := self.git.Remote.CheckRemoteBranchExists(branch.Name)
|
||||
branchExistsOnRemote := self.c.Git().Remote.CheckRemoteBranchExists(branch.Name)
|
||||
|
||||
if !branchExistsOnRemote {
|
||||
return self.c.Error(errors.New(self.c.Tr.NoBranchOnRemote))
|
||||
}
|
||||
|
||||
url, err := self.helpers.Host.GetPullRequestURL(branch.Name, "")
|
||||
url, err := self.c.Helpers().Host.GetPullRequestURL(branch.Name, "")
|
||||
if err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
self.c.LogAction(self.c.Tr.Actions.CopyPullRequestURL)
|
||||
if err := self.os.CopyToClipboard(url); err != nil {
|
||||
if err := self.c.OS().CopyToClipboard(url); err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
|
||||
@@ -224,7 +248,7 @@ func (self *BranchesController) forceCheckout() error {
|
||||
Prompt: message,
|
||||
HandleConfirm: func() error {
|
||||
self.c.LogAction(self.c.Tr.Actions.ForceCheckoutBranch)
|
||||
if err := self.git.Branch.Checkout(branch.Name, git_commands.CheckoutOptions{Force: true}); err != nil {
|
||||
if err := self.c.Git().Branch.Checkout(branch.Name, git_commands.CheckoutOptions{Force: true}); err != nil {
|
||||
_ = self.c.Error(err)
|
||||
}
|
||||
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
|
||||
@@ -235,10 +259,10 @@ func (self *BranchesController) forceCheckout() error {
|
||||
func (self *BranchesController) checkoutByName() error {
|
||||
return self.c.Prompt(types.PromptOpts{
|
||||
Title: self.c.Tr.BranchName + ":",
|
||||
FindSuggestionsFunc: self.helpers.Suggestions.GetRefsSuggestionsFunc(),
|
||||
FindSuggestionsFunc: self.c.Helpers().Suggestions.GetRefsSuggestionsFunc(),
|
||||
HandleConfirm: func(response string) error {
|
||||
self.c.LogAction("Checkout branch")
|
||||
return self.helpers.Refs.CheckoutRef(response, types.CheckoutRefOptions{
|
||||
return self.c.Helpers().Refs.CheckoutRef(response, types.CheckoutRefOptions{
|
||||
OnRefNotFound: func(ref string) error {
|
||||
return self.c.Confirm(types.ConfirmOpts{
|
||||
Title: self.c.Tr.BranchNotFoundTitle,
|
||||
@@ -260,7 +284,7 @@ func (self *BranchesController) createNewBranchWithName(newBranchName string) er
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := self.git.Branch.New(newBranchName, branch.FullRefName()); err != nil {
|
||||
if err := self.c.Git().Branch.New(newBranchName, branch.FullRefName()); err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
|
||||
@@ -269,7 +293,7 @@ func (self *BranchesController) createNewBranchWithName(newBranchName string) er
|
||||
}
|
||||
|
||||
func (self *BranchesController) delete(branch *models.Branch) error {
|
||||
checkedOutBranch := self.helpers.Refs.GetCheckedOutRef()
|
||||
checkedOutBranch := self.c.Helpers().Refs.GetCheckedOutRef()
|
||||
if checkedOutBranch.Name == branch.Name {
|
||||
return self.c.ErrorMsg(self.c.Tr.CantDeleteCheckOutBranch)
|
||||
}
|
||||
@@ -296,7 +320,7 @@ func (self *BranchesController) deleteWithForce(selectedBranch *models.Branch, f
|
||||
Prompt: message,
|
||||
HandleConfirm: func() error {
|
||||
self.c.LogAction(self.c.Tr.Actions.DeleteBranch)
|
||||
if err := self.git.Branch.Delete(selectedBranch.Name, force); err != nil {
|
||||
if err := self.c.Git().Branch.Delete(selectedBranch.Name, force); err != nil {
|
||||
errMessage := err.Error()
|
||||
if !force && strings.Contains(errMessage, "git branch -D ") {
|
||||
return self.deleteWithForce(selectedBranch, true)
|
||||
@@ -310,12 +334,12 @@ func (self *BranchesController) deleteWithForce(selectedBranch *models.Branch, f
|
||||
|
||||
func (self *BranchesController) merge() error {
|
||||
selectedBranchName := self.context().GetSelected().Name
|
||||
return self.helpers.MergeAndRebase.MergeRefIntoCheckedOutBranch(selectedBranchName)
|
||||
return self.c.Helpers().MergeAndRebase.MergeRefIntoCheckedOutBranch(selectedBranchName)
|
||||
}
|
||||
|
||||
func (self *BranchesController) rebase() error {
|
||||
selectedBranchName := self.context().GetSelected().Name
|
||||
return self.helpers.MergeAndRebase.RebaseOntoRef(selectedBranchName)
|
||||
return self.c.Helpers().MergeAndRebase.RebaseOntoRef(selectedBranchName)
|
||||
}
|
||||
|
||||
func (self *BranchesController) fastForward(branch *models.Branch) error {
|
||||
@@ -340,10 +364,10 @@ func (self *BranchesController) fastForward(branch *models.Branch) error {
|
||||
)
|
||||
|
||||
return self.c.WithLoaderPanel(message, func() error {
|
||||
if branch == self.helpers.Refs.GetCheckedOutRef() {
|
||||
if branch == self.c.Helpers().Refs.GetCheckedOutRef() {
|
||||
self.c.LogAction(action)
|
||||
|
||||
err := self.git.Sync.Pull(
|
||||
err := self.c.Git().Sync.Pull(
|
||||
git_commands.PullOptions{
|
||||
RemoteName: branch.UpstreamRemote,
|
||||
BranchName: branch.UpstreamBranch,
|
||||
@@ -357,7 +381,7 @@ func (self *BranchesController) fastForward(branch *models.Branch) error {
|
||||
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
|
||||
} else {
|
||||
self.c.LogAction(action)
|
||||
err := self.git.Sync.FastForward(branch.Name, branch.UpstreamRemote, branch.UpstreamBranch)
|
||||
err := self.c.Git().Sync.FastForward(branch.Name, branch.UpstreamRemote, branch.UpstreamBranch)
|
||||
if err != nil {
|
||||
_ = self.c.Error(err)
|
||||
}
|
||||
@@ -369,11 +393,11 @@ func (self *BranchesController) fastForward(branch *models.Branch) error {
|
||||
}
|
||||
|
||||
func (self *BranchesController) createTag(branch *models.Branch) error {
|
||||
return self.helpers.Tags.CreateTagMenu(branch.FullRefName(), func() {})
|
||||
return self.c.Helpers().Tags.CreateTagMenu(branch.FullRefName(), func() {})
|
||||
}
|
||||
|
||||
func (self *BranchesController) createResetMenu(selectedBranch *models.Branch) error {
|
||||
return self.helpers.Refs.CreateGitResetMenu(selectedBranch.Name)
|
||||
return self.c.Helpers().Refs.CreateGitResetMenu(selectedBranch.Name)
|
||||
}
|
||||
|
||||
func (self *BranchesController) rename(branch *models.Branch) error {
|
||||
@@ -383,7 +407,7 @@ func (self *BranchesController) rename(branch *models.Branch) error {
|
||||
InitialContent: branch.Name,
|
||||
HandleConfirm: func(newBranchName string) error {
|
||||
self.c.LogAction(self.c.Tr.Actions.RenameBranch)
|
||||
if err := self.git.Branch.Rename(branch.Name, newBranchName); err != nil {
|
||||
if err := self.c.Git().Branch.Rename(branch.Name, newBranchName); err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
|
||||
@@ -391,7 +415,7 @@ func (self *BranchesController) rename(branch *models.Branch) error {
|
||||
_ = self.c.Refresh(types.RefreshOptions{Mode: types.SYNC, Scope: []types.RefreshableView{types.BRANCHES}})
|
||||
|
||||
// now that we've got our stuff again we need to find that branch and reselect it.
|
||||
for i, newBranch := range self.model.Branches {
|
||||
for i, newBranch := range self.c.Model().Branches {
|
||||
if newBranch.Name == newBranchName {
|
||||
self.context().SetSelectedLineIdx(i)
|
||||
if err := self.context().HandleRender(); err != nil {
|
||||
@@ -420,7 +444,7 @@ func (self *BranchesController) rename(branch *models.Branch) error {
|
||||
}
|
||||
|
||||
func (self *BranchesController) newBranch(selectedBranch *models.Branch) error {
|
||||
return self.helpers.Refs.NewBranch(selectedBranch.FullRefName(), selectedBranch.RefName(), "")
|
||||
return self.c.Helpers().Refs.NewBranch(selectedBranch.FullRefName(), selectedBranch.RefName(), "")
|
||||
}
|
||||
|
||||
func (self *BranchesController) createPullRequestMenu(selectedBranch *models.Branch, checkedOutBranch *models.Branch) error {
|
||||
@@ -443,7 +467,7 @@ func (self *BranchesController) createPullRequestMenu(selectedBranch *models.Bra
|
||||
OnPress: func() error {
|
||||
return self.c.Prompt(types.PromptOpts{
|
||||
Title: branch.Name + " →",
|
||||
FindSuggestionsFunc: self.helpers.Suggestions.GetBranchNameSuggestionsFunc(),
|
||||
FindSuggestionsFunc: self.c.Helpers().Suggestions.GetBranchNameSuggestionsFunc(),
|
||||
HandleConfirm: func(targetBranchName string) error {
|
||||
return self.createPullRequest(branch.Name, targetBranchName)
|
||||
},
|
||||
@@ -471,14 +495,14 @@ func (self *BranchesController) createPullRequestMenu(selectedBranch *models.Bra
|
||||
}
|
||||
|
||||
func (self *BranchesController) createPullRequest(from string, to string) error {
|
||||
url, err := self.helpers.Host.GetPullRequestURL(from, to)
|
||||
url, err := self.c.Helpers().Host.GetPullRequestURL(from, to)
|
||||
if err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
|
||||
self.c.LogAction(self.c.Tr.Actions.OpenPullRequest)
|
||||
|
||||
if err := self.os.OpenLink(url); err != nil {
|
||||
if err := self.c.OS().OpenLink(url); err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
)
|
||||
|
||||
type CommandLogController struct {
|
||||
baseController
|
||||
c *ControllerCommon
|
||||
}
|
||||
|
||||
var _ types.IController = &CommandLogController{}
|
||||
|
||||
func NewCommandLogController(
|
||||
common *ControllerCommon,
|
||||
) *CommandLogController {
|
||||
return &CommandLogController{
|
||||
baseController: baseController{},
|
||||
c: common,
|
||||
}
|
||||
}
|
||||
|
||||
func (self *CommandLogController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding {
|
||||
bindings := []*types.Binding{}
|
||||
|
||||
return bindings
|
||||
}
|
||||
|
||||
func (self *CommandLogController) GetOnFocusLost() func(types.OnFocusLostOpts) error {
|
||||
return func(types.OnFocusLostOpts) error {
|
||||
self.c.Views().Extras.Autoscroll = true
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (self *CommandLogController) Context() types.Context {
|
||||
return self.context()
|
||||
}
|
||||
|
||||
func (self *CommandLogController) context() types.Context {
|
||||
return self.c.Contexts().CommandLog
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
)
|
||||
|
||||
type CommitDescriptionController struct {
|
||||
baseController
|
||||
c *ControllerCommon
|
||||
}
|
||||
|
||||
var _ types.IController = &CommitMessageController{}
|
||||
|
||||
func NewCommitDescriptionController(
|
||||
common *ControllerCommon,
|
||||
) *CommitDescriptionController {
|
||||
return &CommitDescriptionController{
|
||||
baseController: baseController{},
|
||||
c: common,
|
||||
}
|
||||
}
|
||||
|
||||
func (self *CommitDescriptionController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding {
|
||||
bindings := []*types.Binding{
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Universal.TogglePanel),
|
||||
Handler: self.switchToCommitMessage,
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Universal.Return),
|
||||
Handler: self.close,
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Universal.ConfirmInEditor),
|
||||
Handler: self.confirm,
|
||||
},
|
||||
}
|
||||
|
||||
return bindings
|
||||
}
|
||||
|
||||
func (self *CommitDescriptionController) Context() types.Context {
|
||||
return self.context()
|
||||
}
|
||||
|
||||
func (self *CommitDescriptionController) context() types.Context {
|
||||
return self.c.Contexts().CommitMessage
|
||||
}
|
||||
|
||||
func (self *CommitDescriptionController) switchToCommitMessage() error {
|
||||
return self.c.PushContext(self.c.Contexts().CommitMessage)
|
||||
}
|
||||
|
||||
func (self *CommitDescriptionController) close() error {
|
||||
return self.c.Helpers().Commits.CloseCommitMessagePanel()
|
||||
}
|
||||
|
||||
func (self *CommitDescriptionController) confirm() error {
|
||||
return self.c.Helpers().Commits.HandleCommitConfirm()
|
||||
}
|
||||
@@ -1,79 +1,126 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/context"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
)
|
||||
|
||||
type CommitMessageController struct {
|
||||
baseController
|
||||
*controllerCommon
|
||||
|
||||
getCommitMessage func() string
|
||||
onCommitAttempt func(message string)
|
||||
onCommitSuccess func()
|
||||
c *ControllerCommon
|
||||
}
|
||||
|
||||
var _ types.IController = &CommitMessageController{}
|
||||
|
||||
func NewCommitMessageController(
|
||||
common *controllerCommon,
|
||||
getCommitMessage func() string,
|
||||
onCommitAttempt func(message string),
|
||||
onCommitSuccess func(),
|
||||
common *ControllerCommon,
|
||||
) *CommitMessageController {
|
||||
return &CommitMessageController{
|
||||
baseController: baseController{},
|
||||
controllerCommon: common,
|
||||
|
||||
getCommitMessage: getCommitMessage,
|
||||
onCommitAttempt: onCommitAttempt,
|
||||
onCommitSuccess: onCommitSuccess,
|
||||
baseController: baseController{},
|
||||
c: common,
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: merge that commit panel PR because we're not currently showing how to add a newline as it's
|
||||
// handled by the editor func rather than by the controller here.
|
||||
func (self *CommitMessageController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding {
|
||||
bindings := []*types.Binding{
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Universal.SubmitEditorText),
|
||||
Handler: self.confirm,
|
||||
Key: opts.GetKey(opts.Config.Universal.SubmitEditorText),
|
||||
Handler: self.confirm,
|
||||
Description: self.c.Tr.LcConfirm,
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Universal.Return),
|
||||
Handler: self.close,
|
||||
Key: opts.GetKey(opts.Config.Universal.Return),
|
||||
Handler: self.close,
|
||||
Description: self.c.Tr.LcClose,
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Universal.PrevItem),
|
||||
Handler: self.handlePreviousCommit,
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Universal.NextItem),
|
||||
Handler: self.handleNextCommit,
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Universal.TogglePanel),
|
||||
Handler: self.switchToCommitDescription,
|
||||
},
|
||||
}
|
||||
|
||||
return bindings
|
||||
}
|
||||
|
||||
func (self *CommitMessageController) GetOnFocusLost() func(types.OnFocusLostOpts) error {
|
||||
return func(types.OnFocusLostOpts) error {
|
||||
self.context().RenderCommitLength()
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (self *CommitMessageController) Context() types.Context {
|
||||
return self.context()
|
||||
}
|
||||
|
||||
// this method is pointless in this context but I'm keeping it consistent
|
||||
// with other contexts so that when generics arrive it's easier to refactor
|
||||
func (self *CommitMessageController) context() types.Context {
|
||||
return self.contexts.CommitMessage
|
||||
func (self *CommitMessageController) context() *context.CommitMessageContext {
|
||||
return self.c.Contexts().CommitMessage
|
||||
}
|
||||
|
||||
func (self *CommitMessageController) handlePreviousCommit() error {
|
||||
return self.handleCommitIndexChange(1)
|
||||
}
|
||||
|
||||
func (self *CommitMessageController) handleNextCommit() error {
|
||||
if self.context().GetSelectedIndex() == context.NoCommitIndex {
|
||||
return nil
|
||||
}
|
||||
return self.handleCommitIndexChange(-1)
|
||||
}
|
||||
|
||||
func (self *CommitMessageController) switchToCommitDescription() error {
|
||||
if err := self.c.PushContext(self.c.Contexts().CommitDescription); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *CommitMessageController) handleCommitIndexChange(value int) error {
|
||||
currentIndex := self.context().GetSelectedIndex()
|
||||
newIndex := currentIndex + value
|
||||
if newIndex == context.NoCommitIndex {
|
||||
self.context().SetSelectedIndex(newIndex)
|
||||
self.c.Helpers().Commits.SetMessageAndDescriptionInView(self.context().GetHistoryMessage())
|
||||
return nil
|
||||
} else if currentIndex == context.NoCommitIndex {
|
||||
self.context().SetHistoryMessage(self.c.Helpers().Commits.JoinCommitMessageAndDescription())
|
||||
}
|
||||
|
||||
validCommit, err := self.setCommitMessageAtIndex(newIndex)
|
||||
if validCommit {
|
||||
self.context().SetSelectedIndex(newIndex)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// returns true if the given index is for a valid commit
|
||||
func (self *CommitMessageController) setCommitMessageAtIndex(index int) (bool, error) {
|
||||
commitMessage, err := self.c.Git().Commit.GetCommitMessageFromHistory(index)
|
||||
if err != nil {
|
||||
if err == git_commands.ErrInvalidCommitIndex {
|
||||
return false, nil
|
||||
}
|
||||
return false, self.c.ErrorMsg(self.c.Tr.CommitWithoutMessageErr)
|
||||
}
|
||||
self.c.Helpers().Commits.UpdateCommitPanelView(commitMessage)
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (self *CommitMessageController) confirm() error {
|
||||
message := self.getCommitMessage()
|
||||
self.onCommitAttempt(message)
|
||||
|
||||
if message == "" {
|
||||
return self.c.ErrorMsg(self.c.Tr.CommitWithoutMessageErr)
|
||||
}
|
||||
|
||||
cmdObj := self.git.Commit.CommitCmdObj(message)
|
||||
self.c.LogAction(self.c.Tr.Actions.Commit)
|
||||
|
||||
_ = self.c.PopContext()
|
||||
return self.helpers.GPG.WithGpgHandling(cmdObj, self.c.Tr.CommittingStatus, func() error {
|
||||
self.onCommitSuccess()
|
||||
return nil
|
||||
})
|
||||
return self.c.Helpers().Commits.HandleCommitConfirm()
|
||||
}
|
||||
|
||||
func (self *CommitMessageController) close() error {
|
||||
return self.c.PopContext()
|
||||
return self.c.Helpers().Commits.CloseCommitMessagePanel()
|
||||
}
|
||||
|
||||
@@ -11,17 +11,17 @@ import (
|
||||
|
||||
type CommitFilesController struct {
|
||||
baseController
|
||||
*controllerCommon
|
||||
c *ControllerCommon
|
||||
}
|
||||
|
||||
var _ types.IController = &CommitFilesController{}
|
||||
|
||||
func NewCommitFilesController(
|
||||
common *controllerCommon,
|
||||
common *ControllerCommon,
|
||||
) *CommitFilesController {
|
||||
return &CommitFilesController{
|
||||
baseController: baseController{},
|
||||
controllerCommon: common,
|
||||
baseController: baseController{},
|
||||
c: common,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,7 +99,40 @@ func (self *CommitFilesController) Context() types.Context {
|
||||
}
|
||||
|
||||
func (self *CommitFilesController) context() *context.CommitFilesContext {
|
||||
return self.contexts.CommitFiles
|
||||
return self.c.Contexts().CommitFiles
|
||||
}
|
||||
|
||||
func (self *CommitFilesController) GetOnRenderToMain() func() error {
|
||||
return func() error {
|
||||
node := self.context().GetSelected()
|
||||
if node == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
ref := self.context().GetRef()
|
||||
to := ref.RefName()
|
||||
from, reverse := self.c.Modes().Diffing.GetFromAndReverseArgsForDiff(ref.ParentRefName())
|
||||
|
||||
cmdObj := self.c.Git().WorkingTree.ShowFileDiffCmdObj(
|
||||
from, to, reverse, node.GetPath(), false, self.c.State().GetIgnoreWhitespaceInDiffView(),
|
||||
)
|
||||
task := types.NewRunPtyTask(cmdObj.GetCmd())
|
||||
|
||||
pair := self.c.MainViewPairs().Normal
|
||||
if node.File != nil {
|
||||
pair = self.c.MainViewPairs().PatchBuilding
|
||||
}
|
||||
|
||||
return self.c.RenderToMainViews(types.RefreshMainOpts{
|
||||
Pair: pair,
|
||||
Main: &types.ViewUpdateOpts{
|
||||
Title: self.c.Tr.Patch,
|
||||
SubTitle: self.c.Helpers().Diff.IgnoringWhitespaceSubTitle(),
|
||||
Task: task,
|
||||
},
|
||||
Secondary: secondaryPatchPanelUpdateOpts(self.c),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (self *CommitFilesController) onClickMain(opts gocui.ViewMouseBindingOpts) error {
|
||||
@@ -112,7 +145,7 @@ func (self *CommitFilesController) onClickMain(opts gocui.ViewMouseBindingOpts)
|
||||
|
||||
func (self *CommitFilesController) checkout(node *filetree.CommitFileNode) error {
|
||||
self.c.LogAction(self.c.Tr.Actions.CheckoutFile)
|
||||
if err := self.git.WorkingTree.CheckoutFile(self.context().GetRef().RefName(), node.GetPath()); err != nil {
|
||||
if err := self.c.Git().WorkingTree.CheckoutFile(self.context().GetRef().RefName(), node.GetPath()); err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
|
||||
@@ -120,7 +153,7 @@ func (self *CommitFilesController) checkout(node *filetree.CommitFileNode) error
|
||||
}
|
||||
|
||||
func (self *CommitFilesController) discard(node *filetree.CommitFileNode) error {
|
||||
if ok, err := self.helpers.PatchBuilding.ValidateNormalWorkingTreeState(); !ok {
|
||||
if ok, err := self.c.Helpers().PatchBuilding.ValidateNormalWorkingTreeState(); !ok {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -130,8 +163,8 @@ func (self *CommitFilesController) discard(node *filetree.CommitFileNode) error
|
||||
HandleConfirm: func() error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.RebasingStatus, func() error {
|
||||
self.c.LogAction(self.c.Tr.Actions.DiscardOldFileChange)
|
||||
if err := self.git.Rebase.DiscardOldFileChanges(self.model.Commits, self.contexts.LocalCommits.GetSelectedLineIdx(), node.GetPath()); err != nil {
|
||||
if err := self.helpers.MergeAndRebase.CheckMergeOrRebase(err); err != nil {
|
||||
if err := self.c.Git().Rebase.DiscardOldFileChanges(self.c.Model().Commits, self.c.Contexts().LocalCommits.GetSelectedLineIdx(), node.GetPath()); err != nil {
|
||||
if err := self.c.Helpers().MergeAndRebase.CheckMergeOrRebase(err); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -143,7 +176,7 @@ func (self *CommitFilesController) discard(node *filetree.CommitFileNode) error
|
||||
}
|
||||
|
||||
func (self *CommitFilesController) open(node *filetree.CommitFileNode) error {
|
||||
return self.helpers.Files.OpenFile(node.GetPath())
|
||||
return self.c.Helpers().Files.OpenFile(node.GetPath())
|
||||
}
|
||||
|
||||
func (self *CommitFilesController) edit(node *filetree.CommitFileNode) error {
|
||||
@@ -151,13 +184,13 @@ func (self *CommitFilesController) edit(node *filetree.CommitFileNode) error {
|
||||
return self.c.ErrorMsg(self.c.Tr.ErrCannotEditDirectory)
|
||||
}
|
||||
|
||||
return self.helpers.Files.EditFile(node.GetPath())
|
||||
return self.c.Helpers().Files.EditFile(node.GetPath())
|
||||
}
|
||||
|
||||
func (self *CommitFilesController) toggleForPatch(node *filetree.CommitFileNode) error {
|
||||
toggle := func() error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.LcUpdatingPatch, func() error {
|
||||
if !self.git.Patch.PatchBuilder.Active() {
|
||||
if !self.c.Git().Patch.PatchBuilder.Active() {
|
||||
if err := self.startPatchBuilder(); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -166,34 +199,34 @@ func (self *CommitFilesController) toggleForPatch(node *filetree.CommitFileNode)
|
||||
// if there is any file that hasn't been fully added we'll fully add everything,
|
||||
// otherwise we'll remove everything
|
||||
adding := node.SomeFile(func(file *models.CommitFile) bool {
|
||||
return self.git.Patch.PatchBuilder.GetFileStatus(file.Name, self.context().GetRef().RefName()) != patch.WHOLE
|
||||
return self.c.Git().Patch.PatchBuilder.GetFileStatus(file.Name, self.context().GetRef().RefName()) != patch.WHOLE
|
||||
})
|
||||
|
||||
err := node.ForEachFile(func(file *models.CommitFile) error {
|
||||
if adding {
|
||||
return self.git.Patch.PatchBuilder.AddFileWhole(file.Name)
|
||||
return self.c.Git().Patch.PatchBuilder.AddFileWhole(file.Name)
|
||||
} else {
|
||||
return self.git.Patch.PatchBuilder.RemoveFile(file.Name)
|
||||
return self.c.Git().Patch.PatchBuilder.RemoveFile(file.Name)
|
||||
}
|
||||
})
|
||||
if err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
|
||||
if self.git.Patch.PatchBuilder.IsEmpty() {
|
||||
self.git.Patch.PatchBuilder.Reset()
|
||||
if self.c.Git().Patch.PatchBuilder.IsEmpty() {
|
||||
self.c.Git().Patch.PatchBuilder.Reset()
|
||||
}
|
||||
|
||||
return self.c.PostRefreshUpdate(self.context())
|
||||
})
|
||||
}
|
||||
|
||||
if self.git.Patch.PatchBuilder.Active() && self.git.Patch.PatchBuilder.To != self.context().GetRef().RefName() {
|
||||
if self.c.Git().Patch.PatchBuilder.Active() && self.c.Git().Patch.PatchBuilder.To != self.context().GetRef().RefName() {
|
||||
return self.c.Confirm(types.ConfirmOpts{
|
||||
Title: self.c.Tr.DiscardPatch,
|
||||
Prompt: self.c.Tr.DiscardPatchConfirm,
|
||||
HandleConfirm: func() error {
|
||||
self.git.Patch.PatchBuilder.Reset()
|
||||
self.c.Git().Patch.PatchBuilder.Reset()
|
||||
return toggle()
|
||||
},
|
||||
})
|
||||
@@ -213,9 +246,9 @@ func (self *CommitFilesController) startPatchBuilder() error {
|
||||
canRebase := commitFilesContext.GetCanRebase()
|
||||
ref := commitFilesContext.GetRef()
|
||||
to := ref.RefName()
|
||||
from, reverse := self.modes.Diffing.GetFromAndReverseArgsForDiff(ref.ParentRefName())
|
||||
from, reverse := self.c.Modes().Diffing.GetFromAndReverseArgsForDiff(ref.ParentRefName())
|
||||
|
||||
self.git.Patch.PatchBuilder.Start(from, to, reverse, canRebase)
|
||||
self.c.Git().Patch.PatchBuilder.Start(from, to, reverse, canRebase)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -229,21 +262,21 @@ func (self *CommitFilesController) enterCommitFile(node *filetree.CommitFileNode
|
||||
}
|
||||
|
||||
enterTheFile := func() error {
|
||||
if !self.git.Patch.PatchBuilder.Active() {
|
||||
if !self.c.Git().Patch.PatchBuilder.Active() {
|
||||
if err := self.startPatchBuilder(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return self.c.PushContext(self.contexts.CustomPatchBuilder, opts)
|
||||
return self.c.PushContext(self.c.Contexts().CustomPatchBuilder, opts)
|
||||
}
|
||||
|
||||
if self.git.Patch.PatchBuilder.Active() && self.git.Patch.PatchBuilder.To != self.context().GetRef().RefName() {
|
||||
if self.c.Git().Patch.PatchBuilder.Active() && self.c.Git().Patch.PatchBuilder.To != self.context().GetRef().RefName() {
|
||||
return self.c.Confirm(types.ConfirmOpts{
|
||||
Title: self.c.Tr.DiscardPatch,
|
||||
Prompt: self.c.Tr.DiscardPatchConfirm,
|
||||
HandleConfirm: func() error {
|
||||
self.git.Patch.PatchBuilder.Reset()
|
||||
self.c.Git().Patch.PatchBuilder.Reset()
|
||||
return enterTheFile()
|
||||
},
|
||||
})
|
||||
|
||||
@@ -1,42 +1,24 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/commands"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/context"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/controllers/helpers"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
)
|
||||
|
||||
type controllerCommon struct {
|
||||
c *types.HelperCommon
|
||||
os *oscommands.OSCommand
|
||||
git *commands.GitCommand
|
||||
helpers *helpers.Helpers
|
||||
model *types.Model
|
||||
contexts *context.ContextTree
|
||||
modes *types.Modes
|
||||
mutexes *types.Mutexes
|
||||
type ControllerCommon struct {
|
||||
*helpers.HelperCommon
|
||||
IGetHelpers
|
||||
}
|
||||
|
||||
type IGetHelpers interface {
|
||||
Helpers() *helpers.Helpers
|
||||
}
|
||||
|
||||
func NewControllerCommon(
|
||||
c *types.HelperCommon,
|
||||
os *oscommands.OSCommand,
|
||||
git *commands.GitCommand,
|
||||
helpers *helpers.Helpers,
|
||||
model *types.Model,
|
||||
contexts *context.ContextTree,
|
||||
modes *types.Modes,
|
||||
mutexes *types.Mutexes,
|
||||
) *controllerCommon {
|
||||
return &controllerCommon{
|
||||
c: c,
|
||||
os: os,
|
||||
git: git,
|
||||
helpers: helpers,
|
||||
model: model,
|
||||
contexts: contexts,
|
||||
modes: modes,
|
||||
mutexes: mutexes,
|
||||
c *helpers.HelperCommon,
|
||||
IGetHelpers IGetHelpers,
|
||||
) *ControllerCommon {
|
||||
return &ControllerCommon{
|
||||
HelperCommon: c,
|
||||
IGetHelpers: IGetHelpers,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/context"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
)
|
||||
|
||||
type ConfirmationController struct {
|
||||
baseController
|
||||
c *ControllerCommon
|
||||
}
|
||||
|
||||
var _ types.IController = &ConfirmationController{}
|
||||
|
||||
func NewConfirmationController(
|
||||
common *ControllerCommon,
|
||||
) *ConfirmationController {
|
||||
return &ConfirmationController{
|
||||
baseController: baseController{},
|
||||
c: common,
|
||||
}
|
||||
}
|
||||
|
||||
func (self *ConfirmationController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding {
|
||||
bindings := []*types.Binding{
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Universal.Confirm),
|
||||
Handler: func() error { return self.context().State.OnConfirm() },
|
||||
Description: self.c.Tr.LcConfirm,
|
||||
Display: true,
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Universal.Return),
|
||||
Handler: func() error { return self.context().State.OnClose() },
|
||||
Description: self.c.Tr.LcCloseCancel,
|
||||
Display: true,
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Universal.TogglePanel),
|
||||
Handler: func() error {
|
||||
if len(self.c.Contexts().Suggestions.State.Suggestions) > 0 {
|
||||
return self.c.ReplaceContext(self.c.Contexts().Suggestions)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return bindings
|
||||
}
|
||||
|
||||
func (self *ConfirmationController) GetOnFocusLost() func(types.OnFocusLostOpts) error {
|
||||
return func(types.OnFocusLostOpts) error {
|
||||
self.c.Helpers().Confirmation.DeactivateConfirmationPrompt()
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (self *ConfirmationController) Context() types.Context {
|
||||
return self.context()
|
||||
}
|
||||
|
||||
func (self *ConfirmationController) context() *context.ConfirmationContext {
|
||||
return self.c.Contexts().Confirmation
|
||||
}
|
||||
@@ -24,17 +24,17 @@ var CONTEXT_KEYS_SHOWING_DIFFS = []types.ContextKey{
|
||||
|
||||
type ContextLinesController struct {
|
||||
baseController
|
||||
*controllerCommon
|
||||
c *ControllerCommon
|
||||
}
|
||||
|
||||
var _ types.IController = &ContextLinesController{}
|
||||
|
||||
func NewContextLinesController(
|
||||
common *controllerCommon,
|
||||
common *ControllerCommon,
|
||||
) *ContextLinesController {
|
||||
return &ContextLinesController{
|
||||
baseController: baseController{},
|
||||
controllerCommon: common,
|
||||
baseController: baseController{},
|
||||
c: common,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,7 +101,7 @@ func (self *ContextLinesController) applyChange() error {
|
||||
}
|
||||
|
||||
func (self *ContextLinesController) checkCanChangeContext() error {
|
||||
if self.git.Patch.PatchBuilder.Active() {
|
||||
if self.c.Git().Patch.PatchBuilder.Active() {
|
||||
return errors.New(self.c.Tr.CantChangeContextSizeError)
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/jesseduffield/generics/slices"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/controllers/helpers"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
type CustomCommandAction struct {
|
||||
c *ControllerCommon
|
||||
}
|
||||
|
||||
func (self *CustomCommandAction) Call() error {
|
||||
return self.c.Prompt(types.PromptOpts{
|
||||
Title: self.c.Tr.CustomCommand,
|
||||
FindSuggestionsFunc: self.GetCustomCommandsHistorySuggestionsFunc(),
|
||||
HandleConfirm: func(command string) error {
|
||||
if self.shouldSaveCommand(command) {
|
||||
self.c.GetAppState().CustomCommandsHistory = utils.Limit(
|
||||
lo.Uniq(append(self.c.GetAppState().CustomCommandsHistory, command)),
|
||||
1000,
|
||||
)
|
||||
}
|
||||
|
||||
err := self.c.SaveAppState()
|
||||
if err != nil {
|
||||
self.c.Log.Error(err)
|
||||
}
|
||||
|
||||
self.c.LogAction(self.c.Tr.Actions.CustomCommand)
|
||||
return self.c.RunSubprocessAndRefresh(
|
||||
self.c.OS().Cmd.NewShell(command),
|
||||
)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func (self *CustomCommandAction) GetCustomCommandsHistorySuggestionsFunc() func(string) []*types.Suggestion {
|
||||
// reversing so that we display the latest command first
|
||||
history := slices.Reverse(self.c.GetAppState().CustomCommandsHistory)
|
||||
|
||||
return helpers.FuzzySearchFunc(history)
|
||||
}
|
||||
|
||||
// this mimics the shell functionality `ignorespace`
|
||||
// which doesn't save a command to history if it starts with a space
|
||||
func (self *CustomCommandAction) shouldSaveCommand(command string) bool {
|
||||
return !strings.HasPrefix(command, " ")
|
||||
}
|
||||
@@ -0,0 +1,219 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/types/enums"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
)
|
||||
|
||||
type CustomPatchOptionsMenuAction struct {
|
||||
c *ControllerCommon
|
||||
}
|
||||
|
||||
func (self *CustomPatchOptionsMenuAction) Call() error {
|
||||
if !self.c.Git().Patch.PatchBuilder.Active() {
|
||||
return self.c.ErrorMsg(self.c.Tr.NoPatchError)
|
||||
}
|
||||
|
||||
menuItems := []*types.MenuItem{
|
||||
{
|
||||
Label: "reset patch",
|
||||
OnPress: self.c.Helpers().PatchBuilding.Reset,
|
||||
Key: 'c',
|
||||
},
|
||||
{
|
||||
Label: "apply patch",
|
||||
OnPress: func() error { return self.handleApplyPatch(false) },
|
||||
Key: 'a',
|
||||
},
|
||||
{
|
||||
Label: "apply patch in reverse",
|
||||
OnPress: func() error { return self.handleApplyPatch(true) },
|
||||
Key: 'r',
|
||||
},
|
||||
}
|
||||
|
||||
if self.c.Git().Patch.PatchBuilder.CanRebase && self.c.Git().Status.WorkingTreeState() == enums.REBASE_MODE_NONE {
|
||||
menuItems = append(menuItems, []*types.MenuItem{
|
||||
{
|
||||
Label: fmt.Sprintf("remove patch from original commit (%s)", self.c.Git().Patch.PatchBuilder.To),
|
||||
OnPress: self.handleDeletePatchFromCommit,
|
||||
Key: 'd',
|
||||
},
|
||||
{
|
||||
Label: "move patch out into index",
|
||||
OnPress: self.handleMovePatchIntoWorkingTree,
|
||||
Key: 'i',
|
||||
},
|
||||
{
|
||||
Label: "move patch into new commit",
|
||||
OnPress: self.handlePullPatchIntoNewCommit,
|
||||
Key: 'n',
|
||||
},
|
||||
}...)
|
||||
|
||||
if self.c.CurrentContext().GetKey() == self.c.Contexts().LocalCommits.GetKey() {
|
||||
selectedCommit := self.c.Contexts().LocalCommits.GetSelected()
|
||||
if selectedCommit != nil && self.c.Git().Patch.PatchBuilder.To != selectedCommit.Sha {
|
||||
// adding this option to index 1
|
||||
menuItems = append(
|
||||
menuItems[:1],
|
||||
append(
|
||||
[]*types.MenuItem{
|
||||
{
|
||||
Label: fmt.Sprintf("move patch to selected commit (%s)", selectedCommit.Sha),
|
||||
OnPress: self.handleMovePatchToSelectedCommit,
|
||||
Key: 'm',
|
||||
},
|
||||
}, menuItems[1:]...,
|
||||
)...,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
menuItems = append(menuItems, []*types.MenuItem{
|
||||
{
|
||||
Label: "copy patch to clipboard",
|
||||
OnPress: func() error { return self.copyPatchToClipboard() },
|
||||
Key: 'y',
|
||||
},
|
||||
}...)
|
||||
|
||||
return self.c.Menu(types.CreateMenuOptions{Title: self.c.Tr.PatchOptionsTitle, Items: menuItems})
|
||||
}
|
||||
|
||||
func (self *CustomPatchOptionsMenuAction) getPatchCommitIndex() int {
|
||||
for index, commit := range self.c.Model().Commits {
|
||||
if commit.Sha == self.c.Git().Patch.PatchBuilder.To {
|
||||
return index
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
func (self *CustomPatchOptionsMenuAction) validateNormalWorkingTreeState() (bool, error) {
|
||||
if self.c.Git().Status.WorkingTreeState() != enums.REBASE_MODE_NONE {
|
||||
return false, self.c.ErrorMsg(self.c.Tr.CantPatchWhileRebasingError)
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (self *CustomPatchOptionsMenuAction) returnFocusFromPatchExplorerIfNecessary() error {
|
||||
if self.c.CurrentContext().GetKey() == self.c.Contexts().CustomPatchBuilder.GetKey() {
|
||||
return self.c.Helpers().PatchBuilding.Escape()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *CustomPatchOptionsMenuAction) handleDeletePatchFromCommit() error {
|
||||
if ok, err := self.validateNormalWorkingTreeState(); !ok {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := self.returnFocusFromPatchExplorerIfNecessary(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return self.c.WithWaitingStatus(self.c.Tr.RebasingStatus, func() error {
|
||||
commitIndex := self.getPatchCommitIndex()
|
||||
self.c.LogAction(self.c.Tr.Actions.RemovePatchFromCommit)
|
||||
err := self.c.Git().Patch.DeletePatchesFromCommit(self.c.Model().Commits, commitIndex)
|
||||
return self.c.Helpers().MergeAndRebase.CheckMergeOrRebase(err)
|
||||
})
|
||||
}
|
||||
|
||||
func (self *CustomPatchOptionsMenuAction) handleMovePatchToSelectedCommit() error {
|
||||
if ok, err := self.validateNormalWorkingTreeState(); !ok {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := self.returnFocusFromPatchExplorerIfNecessary(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return self.c.WithWaitingStatus(self.c.Tr.RebasingStatus, func() error {
|
||||
commitIndex := self.getPatchCommitIndex()
|
||||
self.c.LogAction(self.c.Tr.Actions.MovePatchToSelectedCommit)
|
||||
err := self.c.Git().Patch.MovePatchToSelectedCommit(self.c.Model().Commits, commitIndex, self.c.Contexts().LocalCommits.GetSelectedLineIdx())
|
||||
return self.c.Helpers().MergeAndRebase.CheckMergeOrRebase(err)
|
||||
})
|
||||
}
|
||||
|
||||
func (self *CustomPatchOptionsMenuAction) handleMovePatchIntoWorkingTree() error {
|
||||
if ok, err := self.validateNormalWorkingTreeState(); !ok {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := self.returnFocusFromPatchExplorerIfNecessary(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pull := func(stash bool) error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.RebasingStatus, func() error {
|
||||
commitIndex := self.getPatchCommitIndex()
|
||||
self.c.LogAction(self.c.Tr.Actions.MovePatchIntoIndex)
|
||||
err := self.c.Git().Patch.MovePatchIntoIndex(self.c.Model().Commits, commitIndex, stash)
|
||||
return self.c.Helpers().MergeAndRebase.CheckMergeOrRebase(err)
|
||||
})
|
||||
}
|
||||
|
||||
if self.c.Helpers().WorkingTree.IsWorkingTreeDirty() {
|
||||
return self.c.Confirm(types.ConfirmOpts{
|
||||
Title: self.c.Tr.MustStashTitle,
|
||||
Prompt: self.c.Tr.MustStashWarning,
|
||||
HandleConfirm: func() error {
|
||||
return pull(true)
|
||||
},
|
||||
})
|
||||
} else {
|
||||
return pull(false)
|
||||
}
|
||||
}
|
||||
|
||||
func (self *CustomPatchOptionsMenuAction) handlePullPatchIntoNewCommit() error {
|
||||
if ok, err := self.validateNormalWorkingTreeState(); !ok {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := self.returnFocusFromPatchExplorerIfNecessary(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return self.c.WithWaitingStatus(self.c.Tr.RebasingStatus, func() error {
|
||||
commitIndex := self.getPatchCommitIndex()
|
||||
self.c.LogAction(self.c.Tr.Actions.MovePatchIntoNewCommit)
|
||||
err := self.c.Git().Patch.PullPatchIntoNewCommit(self.c.Model().Commits, commitIndex)
|
||||
return self.c.Helpers().MergeAndRebase.CheckMergeOrRebase(err)
|
||||
})
|
||||
}
|
||||
|
||||
func (self *CustomPatchOptionsMenuAction) handleApplyPatch(reverse bool) error {
|
||||
if err := self.returnFocusFromPatchExplorerIfNecessary(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
action := self.c.Tr.Actions.ApplyPatch
|
||||
if reverse {
|
||||
action = "Apply patch in reverse"
|
||||
}
|
||||
self.c.LogAction(action)
|
||||
if err := self.c.Git().Patch.PatchBuilder.ApplyPatches(reverse); err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
|
||||
}
|
||||
|
||||
func (self *CustomPatchOptionsMenuAction) copyPatchToClipboard() error {
|
||||
patch := self.c.Git().Patch.PatchBuilder.RenderAggregatedPatch(true)
|
||||
|
||||
self.c.LogAction(self.c.Tr.Actions.CopyPatchToClipboard)
|
||||
if err := self.c.OS().CopyToClipboard(patch); err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
|
||||
self.c.Toast(self.c.Tr.PatchCopiedToClipboard)
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/modes/diffing"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
)
|
||||
|
||||
type DiffingMenuAction struct {
|
||||
c *ControllerCommon
|
||||
}
|
||||
|
||||
func (self *DiffingMenuAction) Call() error {
|
||||
names := self.c.Helpers().Diff.CurrentDiffTerminals()
|
||||
|
||||
menuItems := []*types.MenuItem{}
|
||||
for _, name := range names {
|
||||
name := name
|
||||
menuItems = append(menuItems, []*types.MenuItem{
|
||||
{
|
||||
Label: fmt.Sprintf("%s %s", self.c.Tr.LcDiff, name),
|
||||
OnPress: func() error {
|
||||
self.c.Modes().Diffing.Ref = name
|
||||
// can scope this down based on current view but too lazy right now
|
||||
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
|
||||
},
|
||||
},
|
||||
}...)
|
||||
}
|
||||
|
||||
menuItems = append(menuItems, []*types.MenuItem{
|
||||
{
|
||||
Label: self.c.Tr.LcEnterRefToDiff,
|
||||
OnPress: func() error {
|
||||
return self.c.Prompt(types.PromptOpts{
|
||||
Title: self.c.Tr.LcEnteRefName,
|
||||
FindSuggestionsFunc: self.c.Helpers().Suggestions.GetRefsSuggestionsFunc(),
|
||||
HandleConfirm: func(response string) error {
|
||||
self.c.Modes().Diffing.Ref = strings.TrimSpace(response)
|
||||
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
|
||||
},
|
||||
})
|
||||
},
|
||||
},
|
||||
}...)
|
||||
|
||||
if self.c.Modes().Diffing.Active() {
|
||||
menuItems = append(menuItems, []*types.MenuItem{
|
||||
{
|
||||
Label: self.c.Tr.LcSwapDiff,
|
||||
OnPress: func() error {
|
||||
self.c.Modes().Diffing.Reverse = !self.c.Modes().Diffing.Reverse
|
||||
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
|
||||
},
|
||||
},
|
||||
{
|
||||
Label: self.c.Tr.LcExitDiffMode,
|
||||
OnPress: func() error {
|
||||
self.c.Modes().Diffing = diffing.New()
|
||||
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
|
||||
},
|
||||
},
|
||||
}...)
|
||||
}
|
||||
|
||||
return self.c.Menu(types.CreateMenuOptions{Title: self.c.Tr.DiffingMenuTitle, Items: menuItems})
|
||||
}
|
||||
@@ -13,26 +13,16 @@ import (
|
||||
|
||||
type FilesController struct {
|
||||
baseController // nolint: unused
|
||||
*controllerCommon
|
||||
|
||||
enterSubmodule func(submodule *models.SubmoduleConfig) error
|
||||
setCommitMessage func(message string)
|
||||
getSavedCommitMessage func() string
|
||||
c *ControllerCommon
|
||||
}
|
||||
|
||||
var _ types.IController = &FilesController{}
|
||||
|
||||
func NewFilesController(
|
||||
common *controllerCommon,
|
||||
enterSubmodule func(submodule *models.SubmoduleConfig) error,
|
||||
setCommitMessage func(message string),
|
||||
getSavedCommitMessage func() string,
|
||||
common *ControllerCommon,
|
||||
) *FilesController {
|
||||
return &FilesController{
|
||||
controllerCommon: common,
|
||||
enterSubmodule: enterSubmodule,
|
||||
setCommitMessage: setCommitMessage,
|
||||
getSavedCommitMessage: getSavedCommitMessage,
|
||||
c: common,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,12 +40,12 @@ func (self *FilesController) GetKeybindings(opts types.KeybindingsOpts) []*types
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Files.CommitChanges),
|
||||
Handler: self.helpers.WorkingTree.HandleCommitPress,
|
||||
Handler: self.c.Helpers().WorkingTree.HandleCommitPress,
|
||||
Description: self.c.Tr.CommitChanges,
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Files.CommitChangesWithoutHook),
|
||||
Handler: self.helpers.WorkingTree.HandleWIPCommitPress,
|
||||
Handler: self.c.Helpers().WorkingTree.HandleWIPCommitPress,
|
||||
Description: self.c.Tr.LcCommitChangesWithoutHook,
|
||||
},
|
||||
{
|
||||
@@ -65,7 +55,7 @@ func (self *FilesController) GetKeybindings(opts types.KeybindingsOpts) []*types
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Files.CommitChangesWithEditor),
|
||||
Handler: self.helpers.WorkingTree.HandleCommitEditorPress,
|
||||
Handler: self.c.Helpers().WorkingTree.HandleCommitEditorPress,
|
||||
Description: self.c.Tr.CommitChangesWithEditor,
|
||||
},
|
||||
{
|
||||
@@ -129,7 +119,7 @@ func (self *FilesController) GetKeybindings(opts types.KeybindingsOpts) []*types
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Files.OpenMergeTool),
|
||||
Handler: self.helpers.WorkingTree.OpenMergeTool,
|
||||
Handler: self.c.Helpers().WorkingTree.OpenMergeTool,
|
||||
Description: self.c.Tr.LcOpenMergeTool,
|
||||
},
|
||||
{
|
||||
@@ -175,6 +165,77 @@ func (self *FilesController) GetMouseKeybindings(opts types.KeybindingsOpts) []*
|
||||
}
|
||||
}
|
||||
|
||||
func (self *FilesController) GetOnRenderToMain() func() error {
|
||||
return func() error {
|
||||
return self.c.Helpers().Diff.WithDiffModeCheck(func() error {
|
||||
node := self.context().GetSelected()
|
||||
|
||||
if node == nil {
|
||||
return self.c.RenderToMainViews(types.RefreshMainOpts{
|
||||
Pair: self.c.MainViewPairs().Normal,
|
||||
Main: &types.ViewUpdateOpts{
|
||||
Title: self.c.Tr.DiffTitle,
|
||||
SubTitle: self.c.Helpers().Diff.IgnoringWhitespaceSubTitle(),
|
||||
Task: types.NewRenderStringTask(self.c.Tr.NoChangedFiles),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
if node.File != nil && node.File.HasInlineMergeConflicts {
|
||||
hasConflicts, err := self.c.Helpers().MergeConflicts.SetMergeState(node.GetPath())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if hasConflicts {
|
||||
return self.c.Helpers().MergeConflicts.Render(false)
|
||||
}
|
||||
}
|
||||
|
||||
self.c.Helpers().MergeConflicts.ResetMergeState()
|
||||
|
||||
pair := self.c.MainViewPairs().Normal
|
||||
if node.File != nil {
|
||||
pair = self.c.MainViewPairs().Staging
|
||||
}
|
||||
|
||||
split := self.c.UserConfig.Gui.SplitDiff == "always" || (node.GetHasUnstagedChanges() && node.GetHasStagedChanges())
|
||||
mainShowsStaged := !split && node.GetHasStagedChanges()
|
||||
|
||||
cmdObj := self.c.Git().WorkingTree.WorktreeFileDiffCmdObj(node, false, mainShowsStaged, self.c.State().GetIgnoreWhitespaceInDiffView())
|
||||
title := self.c.Tr.UnstagedChanges
|
||||
if mainShowsStaged {
|
||||
title = self.c.Tr.StagedChanges
|
||||
}
|
||||
refreshOpts := types.RefreshMainOpts{
|
||||
Pair: pair,
|
||||
Main: &types.ViewUpdateOpts{
|
||||
Task: types.NewRunPtyTask(cmdObj.GetCmd()),
|
||||
SubTitle: self.c.Helpers().Diff.IgnoringWhitespaceSubTitle(),
|
||||
Title: title,
|
||||
},
|
||||
}
|
||||
|
||||
if split {
|
||||
cmdObj := self.c.Git().WorkingTree.WorktreeFileDiffCmdObj(node, false, true, self.c.State().GetIgnoreWhitespaceInDiffView())
|
||||
|
||||
title := self.c.Tr.StagedChanges
|
||||
if mainShowsStaged {
|
||||
title = self.c.Tr.UnstagedChanges
|
||||
}
|
||||
|
||||
refreshOpts.Secondary = &types.ViewUpdateOpts{
|
||||
Title: title,
|
||||
SubTitle: self.c.Helpers().Diff.IgnoringWhitespaceSubTitle(),
|
||||
Task: types.NewRunPtyTask(cmdObj.GetCmd()),
|
||||
}
|
||||
}
|
||||
|
||||
return self.c.RenderToMainViews(refreshOpts)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (self *FilesController) GetOnClick() func() error {
|
||||
return self.checkSelectedFileNode(self.press)
|
||||
}
|
||||
@@ -230,7 +291,7 @@ func (self *FilesController) optimisticChange(node *filetree.FileNode, optimisti
|
||||
rerender := false
|
||||
err := node.ForEachFile(func(f *models.File) error {
|
||||
// can't act on the file itself: we need to update the original model file
|
||||
for _, modelFile := range self.model.Files {
|
||||
for _, modelFile := range self.c.Model().Files {
|
||||
if modelFile.Name == f.Name {
|
||||
if optimisticChangeFn(modelFile) {
|
||||
rerender = true
|
||||
@@ -245,7 +306,7 @@ func (self *FilesController) optimisticChange(node *filetree.FileNode, optimisti
|
||||
return err
|
||||
}
|
||||
if rerender {
|
||||
if err := self.c.PostRefreshUpdate(self.contexts.Files); err != nil {
|
||||
if err := self.c.PostRefreshUpdate(self.c.Contexts().Files); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -256,8 +317,8 @@ func (self *FilesController) optimisticChange(node *filetree.FileNode, optimisti
|
||||
func (self *FilesController) pressWithLock(node *filetree.FileNode) error {
|
||||
// Obtaining this lock because optimistic rendering requires us to mutate
|
||||
// the files in our model.
|
||||
self.mutexes.RefreshingFilesMutex.Lock()
|
||||
defer self.mutexes.RefreshingFilesMutex.Unlock()
|
||||
self.c.Mutexes().RefreshingFilesMutex.Lock()
|
||||
defer self.c.Mutexes().RefreshingFilesMutex.Unlock()
|
||||
|
||||
if node.IsFile() {
|
||||
file := node.File
|
||||
@@ -269,7 +330,7 @@ func (self *FilesController) pressWithLock(node *filetree.FileNode) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := self.git.WorkingTree.StageFile(file.Name); err != nil {
|
||||
if err := self.c.Git().WorkingTree.StageFile(file.Name); err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
} else {
|
||||
@@ -279,7 +340,7 @@ func (self *FilesController) pressWithLock(node *filetree.FileNode) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := self.git.WorkingTree.UnStageFile(file.Names(), file.Tracked); err != nil {
|
||||
if err := self.c.Git().WorkingTree.UnStageFile(file.Names(), file.Tracked); err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
}
|
||||
@@ -297,7 +358,7 @@ func (self *FilesController) pressWithLock(node *filetree.FileNode) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := self.git.WorkingTree.StageFile(node.Path); err != nil {
|
||||
if err := self.c.Git().WorkingTree.StageFile(node.Path); err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
} else {
|
||||
@@ -308,7 +369,7 @@ func (self *FilesController) pressWithLock(node *filetree.FileNode) error {
|
||||
}
|
||||
|
||||
// pretty sure it doesn't matter that we're always passing true here
|
||||
if err := self.git.WorkingTree.UnStageFile([]string{node.Path}, true); err != nil {
|
||||
if err := self.c.Git().WorkingTree.UnStageFile([]string{node.Path}, true); err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
}
|
||||
@@ -349,7 +410,7 @@ func (self *FilesController) Context() types.Context {
|
||||
}
|
||||
|
||||
func (self *FilesController) context() *context.WorkingTreeContext {
|
||||
return self.contexts.Files
|
||||
return self.c.Contexts().Files
|
||||
}
|
||||
|
||||
func (self *FilesController) getSelectedFile() *models.File {
|
||||
@@ -376,10 +437,10 @@ func (self *FilesController) EnterFile(opts types.OnFocusOpts) error {
|
||||
|
||||
file := node.File
|
||||
|
||||
submoduleConfigs := self.model.Submodules
|
||||
submoduleConfigs := self.c.Model().Submodules
|
||||
if file.IsSubmodule(submoduleConfigs) {
|
||||
submoduleConfig := file.SubmoduleConfig(submoduleConfigs)
|
||||
return self.enterSubmodule(submoduleConfig)
|
||||
return self.c.Helpers().Repos.EnterSubmodule(submoduleConfig)
|
||||
}
|
||||
|
||||
if file.HasInlineMergeConflicts {
|
||||
@@ -389,7 +450,7 @@ func (self *FilesController) EnterFile(opts types.OnFocusOpts) error {
|
||||
return self.c.ErrorMsg(self.c.Tr.FileStagingRequirements)
|
||||
}
|
||||
|
||||
return self.c.PushContext(self.contexts.Staging, opts)
|
||||
return self.c.PushContext(self.c.Contexts().Staging, opts)
|
||||
}
|
||||
|
||||
func (self *FilesController) toggleStagedAll() error {
|
||||
@@ -405,8 +466,8 @@ func (self *FilesController) toggleStagedAll() error {
|
||||
}
|
||||
|
||||
func (self *FilesController) toggleStagedAllWithLock() error {
|
||||
self.mutexes.RefreshingFilesMutex.Lock()
|
||||
defer self.mutexes.RefreshingFilesMutex.Unlock()
|
||||
self.c.Mutexes().RefreshingFilesMutex.Lock()
|
||||
defer self.c.Mutexes().RefreshingFilesMutex.Unlock()
|
||||
|
||||
root := self.context().FileTreeViewModel.GetRoot()
|
||||
|
||||
@@ -423,7 +484,7 @@ func (self *FilesController) toggleStagedAllWithLock() error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := self.git.WorkingTree.StageAll(); err != nil {
|
||||
if err := self.c.Git().WorkingTree.StageAll(); err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
} else {
|
||||
@@ -433,7 +494,7 @@ func (self *FilesController) toggleStagedAllWithLock() error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := self.git.WorkingTree.UnstageAll(); err != nil {
|
||||
if err := self.c.Git().WorkingTree.UnstageAll(); err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
}
|
||||
@@ -444,7 +505,7 @@ func (self *FilesController) toggleStagedAllWithLock() error {
|
||||
func (self *FilesController) unstageFiles(node *filetree.FileNode) error {
|
||||
return node.ForEachFile(func(file *models.File) error {
|
||||
if file.HasStagedChanges {
|
||||
if err := self.git.WorkingTree.UnStageFile(file.Names(), file.Tracked); err != nil {
|
||||
if err := self.c.Git().WorkingTree.UnStageFile(file.Names(), file.Tracked); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -460,7 +521,7 @@ func (self *FilesController) ignoreOrExcludeTracked(node *filetree.FileNode, trA
|
||||
return err
|
||||
}
|
||||
|
||||
if err := self.git.WorkingTree.RemoveTrackedFiles(node.GetPath()); err != nil {
|
||||
if err := self.c.Git().WorkingTree.RemoveTrackedFiles(node.GetPath()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -498,7 +559,7 @@ func (self *FilesController) ignore(node *filetree.FileNode) error {
|
||||
if node.GetPath() == ".gitignore" {
|
||||
return self.c.ErrorMsg(self.c.Tr.Actions.IgnoreFileErr)
|
||||
}
|
||||
err := self.ignoreOrExcludeFile(node, self.c.Tr.IgnoreTracked, self.c.Tr.IgnoreTrackedPrompt, self.c.Tr.Actions.LcIgnoreExcludeFile, self.git.WorkingTree.Ignore)
|
||||
err := self.ignoreOrExcludeFile(node, self.c.Tr.IgnoreTracked, self.c.Tr.IgnoreTrackedPrompt, self.c.Tr.Actions.LcIgnoreExcludeFile, self.c.Git().WorkingTree.Ignore)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -515,7 +576,7 @@ func (self *FilesController) exclude(node *filetree.FileNode) error {
|
||||
return self.c.ErrorMsg(self.c.Tr.Actions.ExcludeGitIgnoreErr)
|
||||
}
|
||||
|
||||
err := self.ignoreOrExcludeFile(node, self.c.Tr.ExcludeTracked, self.c.Tr.ExcludeTrackedPrompt, self.c.Tr.Actions.ExcludeFile, self.git.WorkingTree.Exclude)
|
||||
err := self.ignoreOrExcludeFile(node, self.c.Tr.ExcludeTracked, self.c.Tr.ExcludeTrackedPrompt, self.c.Tr.Actions.ExcludeFile, self.c.Git().WorkingTree.Exclude)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -555,19 +616,19 @@ func (self *FilesController) refresh() error {
|
||||
}
|
||||
|
||||
func (self *FilesController) handleAmendCommitPress() error {
|
||||
if len(self.model.Files) == 0 {
|
||||
if len(self.c.Model().Files) == 0 {
|
||||
return self.c.ErrorMsg(self.c.Tr.NoFilesStagedTitle)
|
||||
}
|
||||
|
||||
if !self.helpers.WorkingTree.AnyStagedFiles() {
|
||||
return self.helpers.WorkingTree.PromptToStageAllAndRetry(self.handleAmendCommitPress)
|
||||
if !self.c.Helpers().WorkingTree.AnyStagedFiles() {
|
||||
return self.c.Helpers().WorkingTree.PromptToStageAllAndRetry(self.handleAmendCommitPress)
|
||||
}
|
||||
|
||||
if len(self.model.Commits) == 0 {
|
||||
if len(self.c.Model().Commits) == 0 {
|
||||
return self.c.ErrorMsg(self.c.Tr.NoCommitToAmend)
|
||||
}
|
||||
|
||||
return self.helpers.AmendHelper.AmendHead()
|
||||
return self.c.Helpers().AmendHelper.AmendHead()
|
||||
}
|
||||
|
||||
func (self *FilesController) handleStatusFilterPressed() error {
|
||||
@@ -606,7 +667,7 @@ func (self *FilesController) edit(node *filetree.FileNode) error {
|
||||
return self.c.ErrorMsg(self.c.Tr.ErrCannotEditDirectory)
|
||||
}
|
||||
|
||||
return self.helpers.Files.EditFile(node.GetPath())
|
||||
return self.c.Helpers().Files.EditFile(node.GetPath())
|
||||
}
|
||||
|
||||
func (self *FilesController) Open() error {
|
||||
@@ -615,7 +676,7 @@ func (self *FilesController) Open() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
return self.helpers.Files.OpenFile(node.GetPath())
|
||||
return self.c.Helpers().Files.OpenFile(node.GetPath())
|
||||
}
|
||||
|
||||
func (self *FilesController) switchToMerge() error {
|
||||
@@ -624,7 +685,7 @@ func (self *FilesController) switchToMerge() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
return self.helpers.MergeConflicts.SwitchToMerge(file.Name)
|
||||
return self.c.Helpers().MergeConflicts.SwitchToMerge(file.Name)
|
||||
}
|
||||
|
||||
func (self *FilesController) createStashMenu() error {
|
||||
@@ -634,28 +695,28 @@ func (self *FilesController) createStashMenu() error {
|
||||
{
|
||||
Label: self.c.Tr.LcStashAllChanges,
|
||||
OnPress: func() error {
|
||||
if !self.helpers.WorkingTree.IsWorkingTreeDirty() {
|
||||
if !self.c.Helpers().WorkingTree.IsWorkingTreeDirty() {
|
||||
return self.c.ErrorMsg(self.c.Tr.NoFilesToStash)
|
||||
}
|
||||
return self.handleStashSave(self.git.Stash.Save, self.c.Tr.Actions.StashAllChanges)
|
||||
return self.handleStashSave(self.c.Git().Stash.Save, self.c.Tr.Actions.StashAllChanges)
|
||||
},
|
||||
Key: 'a',
|
||||
},
|
||||
{
|
||||
Label: self.c.Tr.LcStashAllChangesKeepIndex,
|
||||
OnPress: func() error {
|
||||
if !self.helpers.WorkingTree.IsWorkingTreeDirty() {
|
||||
if !self.c.Helpers().WorkingTree.IsWorkingTreeDirty() {
|
||||
return self.c.ErrorMsg(self.c.Tr.NoFilesToStash)
|
||||
}
|
||||
// if there are no staged files it behaves the same as Stash.Save
|
||||
return self.handleStashSave(self.git.Stash.StashAndKeepIndex, self.c.Tr.Actions.StashAllChangesKeepIndex)
|
||||
return self.handleStashSave(self.c.Git().Stash.StashAndKeepIndex, self.c.Tr.Actions.StashAllChangesKeepIndex)
|
||||
},
|
||||
Key: 'i',
|
||||
},
|
||||
{
|
||||
Label: self.c.Tr.LcStashIncludeUntrackedChanges,
|
||||
OnPress: func() error {
|
||||
return self.handleStashSave(self.git.Stash.StashIncludeUntrackedChanges, self.c.Tr.Actions.StashIncludeUntrackedChanges)
|
||||
return self.handleStashSave(self.c.Git().Stash.StashIncludeUntrackedChanges, self.c.Tr.Actions.StashIncludeUntrackedChanges)
|
||||
},
|
||||
Key: 'U',
|
||||
},
|
||||
@@ -663,24 +724,24 @@ func (self *FilesController) createStashMenu() error {
|
||||
Label: self.c.Tr.LcStashStagedChanges,
|
||||
OnPress: func() error {
|
||||
// there must be something in staging otherwise the current implementation mucks the stash up
|
||||
if !self.helpers.WorkingTree.AnyStagedFiles() {
|
||||
if !self.c.Helpers().WorkingTree.AnyStagedFiles() {
|
||||
return self.c.ErrorMsg(self.c.Tr.NoTrackedStagedFilesStash)
|
||||
}
|
||||
return self.handleStashSave(self.git.Stash.SaveStagedChanges, self.c.Tr.Actions.StashStagedChanges)
|
||||
return self.handleStashSave(self.c.Git().Stash.SaveStagedChanges, self.c.Tr.Actions.StashStagedChanges)
|
||||
},
|
||||
Key: 's',
|
||||
},
|
||||
{
|
||||
Label: self.c.Tr.LcStashUnstagedChanges,
|
||||
OnPress: func() error {
|
||||
if !self.helpers.WorkingTree.IsWorkingTreeDirty() {
|
||||
if !self.c.Helpers().WorkingTree.IsWorkingTreeDirty() {
|
||||
return self.c.ErrorMsg(self.c.Tr.NoFilesToStash)
|
||||
}
|
||||
if self.helpers.WorkingTree.AnyStagedFiles() {
|
||||
return self.handleStashSave(self.git.Stash.StashUnstagedChanges, self.c.Tr.Actions.StashUnstagedChanges)
|
||||
if self.c.Helpers().WorkingTree.AnyStagedFiles() {
|
||||
return self.handleStashSave(self.c.Git().Stash.StashUnstagedChanges, self.c.Tr.Actions.StashUnstagedChanges)
|
||||
}
|
||||
// ordinary stash
|
||||
return self.handleStashSave(self.git.Stash.Save, self.c.Tr.Actions.StashUnstagedChanges)
|
||||
return self.handleStashSave(self.c.Git().Stash.Save, self.c.Tr.Actions.StashUnstagedChanges)
|
||||
},
|
||||
Key: 'u',
|
||||
},
|
||||
@@ -689,11 +750,11 @@ func (self *FilesController) createStashMenu() error {
|
||||
}
|
||||
|
||||
func (self *FilesController) stash() error {
|
||||
return self.handleStashSave(self.git.Stash.Save, self.c.Tr.Actions.StashAllChanges)
|
||||
return self.handleStashSave(self.c.Git().Stash.Save, self.c.Tr.Actions.StashAllChanges)
|
||||
}
|
||||
|
||||
func (self *FilesController) createResetToUpstreamMenu() error {
|
||||
return self.helpers.Refs.CreateGitResetMenu("@{upstream}")
|
||||
return self.c.Helpers().Refs.CreateGitResetMenu("@{upstream}")
|
||||
}
|
||||
|
||||
func (self *FilesController) handleToggleDirCollapsed() error {
|
||||
@@ -704,7 +765,7 @@ func (self *FilesController) handleToggleDirCollapsed() error {
|
||||
|
||||
self.context().FileTreeViewModel.ToggleCollapsed(node.GetPath())
|
||||
|
||||
if err := self.c.PostRefreshUpdate(self.contexts.Files); err != nil {
|
||||
if err := self.c.PostRefreshUpdate(self.c.Contexts().Files); err != nil {
|
||||
self.c.Log.Error(err)
|
||||
}
|
||||
|
||||
@@ -750,7 +811,7 @@ func (self *FilesController) fetch() error {
|
||||
|
||||
func (self *FilesController) fetchAux() (err error) {
|
||||
self.c.LogAction("Fetch")
|
||||
err = self.git.Sync.Fetch(git_commands.FetchOptions{})
|
||||
err = self.c.Git().Sync.Fetch(git_commands.FetchOptions{})
|
||||
|
||||
if err != nil && strings.Contains(err.Error(), "exit status 128") {
|
||||
_ = self.c.ErrorMsg(self.c.Tr.PassUnameWrong)
|
||||
|
||||
@@ -12,17 +12,17 @@ import (
|
||||
|
||||
type FilesRemoveController struct {
|
||||
baseController
|
||||
*controllerCommon
|
||||
c *ControllerCommon
|
||||
}
|
||||
|
||||
var _ types.IController = &FilesRemoveController{}
|
||||
|
||||
func NewFilesRemoveController(
|
||||
common *controllerCommon,
|
||||
common *ControllerCommon,
|
||||
) *FilesRemoveController {
|
||||
return &FilesRemoveController{
|
||||
baseController: baseController{},
|
||||
controllerCommon: common,
|
||||
baseController: baseController{},
|
||||
c: common,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ func (self *FilesRemoveController) remove(node *filetree.FileNode) error {
|
||||
Label: self.c.Tr.LcDiscardAllChanges,
|
||||
OnPress: func() error {
|
||||
self.c.LogAction(self.c.Tr.Actions.DiscardAllChangesInDirectory)
|
||||
if err := self.git.WorkingTree.DiscardAllDirChanges(node); err != nil {
|
||||
if err := self.c.Git().WorkingTree.DiscardAllDirChanges(node); err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.FILES}})
|
||||
@@ -67,7 +67,7 @@ func (self *FilesRemoveController) remove(node *filetree.FileNode) error {
|
||||
Label: self.c.Tr.LcDiscardUnstagedChanges,
|
||||
OnPress: func() error {
|
||||
self.c.LogAction(self.c.Tr.Actions.DiscardUnstagedChangesInDirectory)
|
||||
if err := self.git.WorkingTree.DiscardUnstagedDirChanges(node); err != nil {
|
||||
if err := self.c.Git().WorkingTree.DiscardUnstagedDirChanges(node); err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
|
||||
@@ -85,7 +85,7 @@ func (self *FilesRemoveController) remove(node *filetree.FileNode) error {
|
||||
} else {
|
||||
file := node.File
|
||||
|
||||
submodules := self.model.Submodules
|
||||
submodules := self.c.Model().Submodules
|
||||
if file.IsSubmodule(submodules) {
|
||||
submodule := file.SubmoduleConfig(submodules)
|
||||
|
||||
@@ -103,7 +103,7 @@ func (self *FilesRemoveController) remove(node *filetree.FileNode) error {
|
||||
Label: self.c.Tr.LcDiscardAllChanges,
|
||||
OnPress: func() error {
|
||||
self.c.LogAction(self.c.Tr.Actions.DiscardAllChangesInFile)
|
||||
if err := self.git.WorkingTree.DiscardAllFileChanges(file); err != nil {
|
||||
if err := self.c.Git().WorkingTree.DiscardAllFileChanges(file); err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.FILES}})
|
||||
@@ -123,7 +123,7 @@ func (self *FilesRemoveController) remove(node *filetree.FileNode) error {
|
||||
Label: self.c.Tr.LcDiscardUnstagedChanges,
|
||||
OnPress: func() error {
|
||||
self.c.LogAction(self.c.Tr.Actions.DiscardAllUnstagedChangesInFile)
|
||||
if err := self.git.WorkingTree.DiscardUnstagedFileChanges(file); err != nil {
|
||||
if err := self.c.Git().WorkingTree.DiscardUnstagedFileChanges(file); err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
|
||||
@@ -148,17 +148,17 @@ func (self *FilesRemoveController) ResetSubmodule(submodule *models.SubmoduleCon
|
||||
return self.c.WithWaitingStatus(self.c.Tr.LcResettingSubmoduleStatus, func() error {
|
||||
self.c.LogAction(self.c.Tr.Actions.ResetSubmodule)
|
||||
|
||||
file := self.helpers.WorkingTree.FileForSubmodule(submodule)
|
||||
file := self.c.Helpers().WorkingTree.FileForSubmodule(submodule)
|
||||
if file != nil {
|
||||
if err := self.git.WorkingTree.UnStageFile(file.Names(), file.Tracked); err != nil {
|
||||
if err := self.c.Git().WorkingTree.UnStageFile(file.Names(), file.Tracked); err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := self.git.Submodule.Stash(submodule); err != nil {
|
||||
if err := self.c.Git().Submodule.Stash(submodule); err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
if err := self.git.Submodule.Reset(submodule); err != nil {
|
||||
if err := self.c.Git().Submodule.Reset(submodule); err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
|
||||
@@ -182,5 +182,5 @@ func (self *FilesRemoveController) Context() types.Context {
|
||||
}
|
||||
|
||||
func (self *FilesRemoveController) context() *context.WorkingTreeContext {
|
||||
return self.contexts.Files
|
||||
return self.c.Contexts().Files
|
||||
}
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
)
|
||||
|
||||
type FilteringMenuAction struct {
|
||||
c *ControllerCommon
|
||||
}
|
||||
|
||||
func (self *FilteringMenuAction) Call() error {
|
||||
fileName := ""
|
||||
switch self.c.CurrentSideContext() {
|
||||
case self.c.Contexts().Files:
|
||||
node := self.c.Contexts().Files.GetSelected()
|
||||
if node != nil {
|
||||
fileName = node.GetPath()
|
||||
}
|
||||
case self.c.Contexts().CommitFiles:
|
||||
node := self.c.Contexts().CommitFiles.GetSelected()
|
||||
if node != nil {
|
||||
fileName = node.GetPath()
|
||||
}
|
||||
}
|
||||
|
||||
menuItems := []*types.MenuItem{}
|
||||
|
||||
if fileName != "" {
|
||||
menuItems = append(menuItems, &types.MenuItem{
|
||||
Label: fmt.Sprintf("%s '%s'", self.c.Tr.LcFilterBy, fileName),
|
||||
OnPress: func() error {
|
||||
return self.setFiltering(fileName)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
menuItems = append(menuItems, &types.MenuItem{
|
||||
Label: self.c.Tr.LcFilterPathOption,
|
||||
OnPress: func() error {
|
||||
return self.c.Prompt(types.PromptOpts{
|
||||
FindSuggestionsFunc: self.c.Helpers().Suggestions.GetFilePathSuggestionsFunc(),
|
||||
Title: self.c.Tr.EnterFileName,
|
||||
HandleConfirm: func(response string) error {
|
||||
return self.setFiltering(strings.TrimSpace(response))
|
||||
},
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
if self.c.Modes().Filtering.Active() {
|
||||
menuItems = append(menuItems, &types.MenuItem{
|
||||
Label: self.c.Tr.LcExitFilterMode,
|
||||
OnPress: self.c.Helpers().Mode.ClearFiltering,
|
||||
})
|
||||
}
|
||||
|
||||
return self.c.Menu(types.CreateMenuOptions{Title: self.c.Tr.FilteringMenuTitle, Items: menuItems})
|
||||
}
|
||||
|
||||
func (self *FilteringMenuAction) setFiltering(path string) error {
|
||||
self.c.Modes().Filtering.SetPath(path)
|
||||
|
||||
repoState := self.c.State().GetRepoState()
|
||||
if repoState.GetScreenMode() == types.SCREEN_NORMAL {
|
||||
repoState.SetScreenMode(types.SCREEN_HALF)
|
||||
}
|
||||
|
||||
if err := self.c.PushContext(self.c.Contexts().LocalCommits); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.COMMITS}, Then: func() {
|
||||
self.c.Contexts().LocalCommits.SetSelectedLineIdx(0)
|
||||
self.c.Contexts().LocalCommits.FocusLine()
|
||||
}})
|
||||
}
|
||||
@@ -11,17 +11,17 @@ import (
|
||||
|
||||
type GitFlowController struct {
|
||||
baseController
|
||||
*controllerCommon
|
||||
c *ControllerCommon
|
||||
}
|
||||
|
||||
var _ types.IController = &GitFlowController{}
|
||||
|
||||
func NewGitFlowController(
|
||||
common *controllerCommon,
|
||||
common *ControllerCommon,
|
||||
) *GitFlowController {
|
||||
return &GitFlowController{
|
||||
baseController: baseController{},
|
||||
controllerCommon: common,
|
||||
baseController: baseController{},
|
||||
c: common,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ func (self *GitFlowController) GetKeybindings(opts types.KeybindingsOpts) []*typ
|
||||
}
|
||||
|
||||
func (self *GitFlowController) handleCreateGitFlowMenu(branch *models.Branch) error {
|
||||
if !self.git.Flow.GitFlowEnabled() {
|
||||
if !self.c.Git().Flow.GitFlowEnabled() {
|
||||
return self.c.ErrorMsg("You need to install git-flow and enable it in this repo to use git-flow features")
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ func (self *GitFlowController) handleCreateGitFlowMenu(branch *models.Branch) er
|
||||
HandleConfirm: func(name string) error {
|
||||
self.c.LogAction(self.c.Tr.Actions.GitFlowStart)
|
||||
return self.c.RunSubprocessAndRefresh(
|
||||
self.git.Flow.StartCmdObj(branchType, name),
|
||||
self.c.Git().Flow.StartCmdObj(branchType, name),
|
||||
)
|
||||
},
|
||||
})
|
||||
@@ -94,7 +94,7 @@ func (self *GitFlowController) handleCreateGitFlowMenu(branch *models.Branch) er
|
||||
}
|
||||
|
||||
func (self *GitFlowController) gitFlowFinishBranch(branchName string) error {
|
||||
cmdObj, err := self.git.Flow.FinishCmdObj(branchName)
|
||||
cmdObj, err := self.c.Git().Flow.FinishCmdObj(branchName)
|
||||
if err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
@@ -119,5 +119,5 @@ func (self *GitFlowController) Context() types.Context {
|
||||
}
|
||||
|
||||
func (self *GitFlowController) context() *context.BranchesContext {
|
||||
return self.contexts.Branches
|
||||
return self.c.Contexts().Branches
|
||||
}
|
||||
|
||||
@@ -1,26 +1,21 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/jesseduffield/generics/slices"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/controllers/helpers"
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
type GlobalController struct {
|
||||
baseController
|
||||
*controllerCommon
|
||||
c *ControllerCommon
|
||||
}
|
||||
|
||||
func NewGlobalController(
|
||||
common *controllerCommon,
|
||||
common *ControllerCommon,
|
||||
) *GlobalController {
|
||||
return &GlobalController{
|
||||
baseController: baseController{},
|
||||
controllerCommon: common,
|
||||
baseController: baseController{},
|
||||
c: common,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,47 +26,143 @@ func (self *GlobalController) GetKeybindings(opts types.KeybindingsOpts) []*type
|
||||
Handler: self.customCommand,
|
||||
Description: self.c.Tr.LcExecuteCustomCommand,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (self *GlobalController) customCommand() error {
|
||||
return self.c.Prompt(types.PromptOpts{
|
||||
Title: self.c.Tr.CustomCommand,
|
||||
FindSuggestionsFunc: self.GetCustomCommandsHistorySuggestionsFunc(),
|
||||
HandleConfirm: func(command string) error {
|
||||
if self.shouldSaveCommand(command) {
|
||||
self.c.GetAppState().CustomCommandsHistory = utils.Limit(
|
||||
lo.Uniq(append(self.c.GetAppState().CustomCommandsHistory, command)),
|
||||
1000,
|
||||
)
|
||||
}
|
||||
|
||||
err := self.c.SaveAppState()
|
||||
if err != nil {
|
||||
self.c.Log.Error(err)
|
||||
}
|
||||
|
||||
self.c.LogAction(self.c.Tr.Actions.CustomCommand)
|
||||
return self.c.RunSubprocessAndRefresh(
|
||||
self.os.Cmd.NewShell(command),
|
||||
)
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Universal.CreatePatchOptionsMenu),
|
||||
Handler: self.createCustomPatchOptionsMenu,
|
||||
Description: self.c.Tr.ViewPatchOptions,
|
||||
OpensMenu: true,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// this mimics the shell functionality `ignorespace`
|
||||
// which doesn't save a command to history if it starts with a space
|
||||
func (self *GlobalController) shouldSaveCommand(command string) bool {
|
||||
return !strings.HasPrefix(command, " ")
|
||||
}
|
||||
|
||||
func (self *GlobalController) GetCustomCommandsHistorySuggestionsFunc() func(string) []*types.Suggestion {
|
||||
// reversing so that we display the latest command first
|
||||
history := slices.Reverse(self.c.GetAppState().CustomCommandsHistory)
|
||||
|
||||
return helpers.FuzzySearchFunc(history)
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Universal.CreateRebaseOptionsMenu),
|
||||
Handler: self.c.Helpers().MergeAndRebase.CreateRebaseOptionsMenu,
|
||||
Description: self.c.Tr.ViewMergeRebaseOptions,
|
||||
OpensMenu: true,
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Universal.Refresh),
|
||||
Handler: self.refresh,
|
||||
Description: self.c.Tr.LcRefresh,
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Universal.NextScreenMode),
|
||||
Handler: self.nextScreenMode,
|
||||
Description: self.c.Tr.LcNextScreenMode,
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Universal.PrevScreenMode),
|
||||
Handler: self.prevScreenMode,
|
||||
Description: self.c.Tr.LcPrevScreenMode,
|
||||
},
|
||||
{
|
||||
ViewName: "",
|
||||
Key: opts.GetKey(opts.Config.Universal.OptionMenu),
|
||||
Handler: self.createOptionsMenu,
|
||||
OpensMenu: true,
|
||||
},
|
||||
{
|
||||
ViewName: "",
|
||||
Key: opts.GetKey(opts.Config.Universal.OptionMenuAlt1),
|
||||
Modifier: gocui.ModNone,
|
||||
// we have the description on the alt key and not the main key for legacy reasons
|
||||
// (the original main key was 'x' but we've reassigned that to other purposes)
|
||||
Description: self.c.Tr.LcOpenMenu,
|
||||
Handler: self.createOptionsMenu,
|
||||
},
|
||||
{
|
||||
ViewName: "",
|
||||
Key: opts.GetKey(opts.Config.Universal.FilteringMenu),
|
||||
Handler: self.createFilteringMenu,
|
||||
Description: self.c.Tr.LcOpenFilteringMenu,
|
||||
OpensMenu: true,
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Universal.DiffingMenu),
|
||||
Handler: self.createDiffingMenu,
|
||||
Description: self.c.Tr.LcOpenDiffingMenu,
|
||||
OpensMenu: true,
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Universal.DiffingMenuAlt),
|
||||
Handler: self.createDiffingMenu,
|
||||
Description: self.c.Tr.LcOpenDiffingMenu,
|
||||
OpensMenu: true,
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Universal.Quit),
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: self.quit,
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Universal.QuitAlt1),
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: self.quit,
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Universal.QuitWithoutChangingDirectory),
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: self.quitWithoutChangingDirectory,
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Universal.Return),
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: self.escape,
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Universal.ToggleWhitespaceInDiffView),
|
||||
Handler: self.toggleWhitespace,
|
||||
Description: self.c.Tr.ToggleWhitespaceInDiffView,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (self *GlobalController) Context() types.Context {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *GlobalController) customCommand() error {
|
||||
return (&CustomCommandAction{c: self.c}).Call()
|
||||
}
|
||||
|
||||
func (self *GlobalController) createCustomPatchOptionsMenu() error {
|
||||
return (&CustomPatchOptionsMenuAction{c: self.c}).Call()
|
||||
}
|
||||
|
||||
func (self *GlobalController) refresh() error {
|
||||
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
|
||||
}
|
||||
|
||||
func (self *GlobalController) nextScreenMode() error {
|
||||
return (&ScreenModeActions{c: self.c}).Next()
|
||||
}
|
||||
|
||||
func (self *GlobalController) prevScreenMode() error {
|
||||
return (&ScreenModeActions{c: self.c}).Prev()
|
||||
}
|
||||
|
||||
func (self *GlobalController) createOptionsMenu() error {
|
||||
return (&OptionsMenuAction{c: self.c}).Call()
|
||||
}
|
||||
|
||||
func (self *GlobalController) createFilteringMenu() error {
|
||||
return (&FilteringMenuAction{c: self.c}).Call()
|
||||
}
|
||||
|
||||
func (self *GlobalController) createDiffingMenu() error {
|
||||
return (&DiffingMenuAction{c: self.c}).Call()
|
||||
}
|
||||
|
||||
func (self *GlobalController) quit() error {
|
||||
return (&QuitActions{c: self.c}).Quit()
|
||||
}
|
||||
|
||||
func (self *GlobalController) quitWithoutChangingDirectory() error {
|
||||
return (&QuitActions{c: self.c}).QuitWithoutChangingDirectory()
|
||||
}
|
||||
|
||||
func (self *GlobalController) escape() error {
|
||||
return (&QuitActions{c: self.c}).Escape()
|
||||
}
|
||||
|
||||
func (self *GlobalController) toggleWhitespace() error {
|
||||
return (&ToggleWhitespaceAction{c: self.c}).Call()
|
||||
}
|
||||
|
||||
@@ -1,24 +1,20 @@
|
||||
package helpers
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/commands"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
)
|
||||
|
||||
type AmendHelper struct {
|
||||
c *types.HelperCommon
|
||||
git *commands.GitCommand
|
||||
c *HelperCommon
|
||||
gpg *GpgHelper
|
||||
}
|
||||
|
||||
func NewAmendHelper(
|
||||
c *types.HelperCommon,
|
||||
git *commands.GitCommand,
|
||||
c *HelperCommon,
|
||||
gpg *GpgHelper,
|
||||
) *AmendHelper {
|
||||
return &AmendHelper{
|
||||
c: c,
|
||||
git: git,
|
||||
gpg: gpg,
|
||||
}
|
||||
}
|
||||
@@ -28,7 +24,7 @@ func (self *AmendHelper) AmendHead() error {
|
||||
Title: self.c.Tr.AmendLastCommitTitle,
|
||||
Prompt: self.c.Tr.SureToAmend,
|
||||
HandleConfirm: func() error {
|
||||
cmdObj := self.git.Commit.AmendHeadCmdObj()
|
||||
cmdObj := self.c.Git().Commit.AmendHeadCmdObj()
|
||||
self.c.LogAction(self.c.Tr.Actions.AmendCommit)
|
||||
return self.gpg.WithGpgHandling(cmdObj, self.c.Tr.AmendingStatus, nil)
|
||||
},
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
package helpers
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/status"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
)
|
||||
|
||||
type AppStatusHelper struct {
|
||||
c *HelperCommon
|
||||
|
||||
statusMgr func() *status.StatusManager
|
||||
}
|
||||
|
||||
func NewAppStatusHelper(c *HelperCommon, statusMgr func() *status.StatusManager) *AppStatusHelper {
|
||||
return &AppStatusHelper{
|
||||
c: c,
|
||||
statusMgr: statusMgr,
|
||||
}
|
||||
}
|
||||
|
||||
func (self *AppStatusHelper) Toast(message string) {
|
||||
self.statusMgr().AddToastStatus(message)
|
||||
|
||||
self.renderAppStatus()
|
||||
}
|
||||
|
||||
// withWaitingStatus wraps a function and shows a waiting status while the function is still executing
|
||||
func (self *AppStatusHelper) WithWaitingStatus(message string, f func() error) {
|
||||
go utils.Safe(func() {
|
||||
self.statusMgr().WithWaitingStatus(message, func() {
|
||||
self.renderAppStatus()
|
||||
|
||||
if err := f(); err != nil {
|
||||
self.c.OnUIThread(func() error {
|
||||
return self.c.Error(err)
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func (self *AppStatusHelper) HasStatus() bool {
|
||||
return self.statusMgr().HasStatus()
|
||||
}
|
||||
|
||||
func (self *AppStatusHelper) GetStatusString() string {
|
||||
return self.statusMgr().GetStatusString()
|
||||
}
|
||||
|
||||
func (self *AppStatusHelper) renderAppStatus() {
|
||||
go utils.Safe(func() {
|
||||
ticker := time.NewTicker(time.Millisecond * 50)
|
||||
defer ticker.Stop()
|
||||
for range ticker.C {
|
||||
appStatus := self.statusMgr().GetStatusString()
|
||||
self.c.OnUIThread(func() error {
|
||||
self.c.SetViewContent(self.c.Views().AppStatus, appStatus)
|
||||
return nil
|
||||
})
|
||||
|
||||
if appStatus == "" {
|
||||
return
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user