Compare commits

..
Author SHA1 Message Date
Jesse Duffield e5e3cc200e fix test names 2023-04-14 20:24:35 +10:00
Jesse Duffield 8e4efddbf7 ensure test list is correct in CI 2023-04-14 20:21:55 +10:00
273 changed files with 6888 additions and 10672 deletions
+3 -6
View File
@@ -135,13 +135,10 @@ jobs:
# ensure our vendor directory matches up with our go modules
run: |
go mod vendor && git diff --exit-code || (echo "Unexpected change to vendor directory. Run 'go mod vendor' locally and commit the changes" && exit 1)
- name: Check Integration Test List
# ensure our integration test list is up to date
- name: Check Integration Tests List
# ensure our tests list matches up with our generator's output
run: |
go generate pkg/integration/tests/tests.go && git diff --exit-code || (echo "Integration test list not up to date. Run 'go generate pkg/integration/tests/tests.go' locally and commit the changes" && exit 1)
shell: bash # needed so that we get "-o pipefail"
- name: Check Filenames
run: scripts/check_filenames.sh
go generate pkg/integration/tests/tests.go && git diff --exit-code || (echo "Unexpected change to tests list. Run 'go generate pkg/integration/tests/tests.go' locally and commit the changes" && exit 1)
lint:
runs-on: ubuntu-latest
env:
+1 -1
View File
File diff suppressed because one or more lines are too long
+8 -11
View File
@@ -57,7 +57,6 @@ gui:
showFileTree: true # for rendering changes files in a tree format
showListFooter: true # for seeing the '5 of 20' message in list panels
showRandomTip: true
experimentalShowBranchHeads: false # visualize branch heads with (*) in commits list
showBottomLine: true # for hiding the bottom information line (unless it has important information to tell you)
showCommandLog: true
showIcons: false
@@ -88,9 +87,6 @@ git:
# displays the whole git graph by default in the commits panel (equivalent to passing the `--all` argument to `git log`)
showWholeGraph: false
skipHookPrefix: WIP
# The main branches. We colour commits green if they belong to one of these branches,
# so that you can easily see which commits are unique to your branch (coloured in yellow)
mainBranches: [master, main]
autoFetch: true
autoRefresh: true
branchLogCmd: 'git log --graph --color=always --abbrev-commit --decorate --date=relative --pretty=medium {{branchName}} --'
@@ -175,6 +171,7 @@ keybinding:
diffingMenu-alt: '<c-e>' # deprecated
copyToClipboard: '<c-o>'
submitEditorText: '<enter>'
appendNewline: '<a-enter>'
extrasMenu: '@'
toggleWhitespaceInDiffView: '<c-w>'
increaseContextInDiffView: '}'
@@ -255,21 +252,21 @@ keybinding:
```yaml
os:
open: 'start "" {{filename}}'
openCommand: 'start "" {{filename}}'
```
### Linux
```yaml
os:
open: 'xdg-open {{filename}} >/dev/null'
openCommand: 'xdg-open {{filename}} >/dev/null'
```
### OSX
```yaml
os:
open: 'open {{filename}}'
openCommand: 'open {{filename}}'
```
### Configuring File Editing
@@ -288,9 +285,9 @@ os:
editPreset: 'vscode'
```
Supported presets are `vim`, `nvim`, `emacs`, `nano`, `vscode`, `sublime`, `bbedit`,
`kakoune` and `xcode`. In many cases lazygit will be able to guess the right preset
from your $(git config core.editor), or an environment variable such as $VISUAL or $EDITOR.
Supported presets are `vim`, `emacs`, `nano`, `vscode`, `sublime`, `bbedit`, and
`xcode`. In many cases lazygit will be able to guess the right preset from your
$(git config core.editor), or an environment variable such as $VISUAL or $EDITOR.
If for some reason you are not happy with the default commands from a preset, or
there simply is no preset for your editor, you can customize the commands by
@@ -477,7 +474,7 @@ services:
Where:
- `gitDomain` stands for the domain used by git itself (i.e. the one present on clone URLs), e.g. `git.work.com`
- `provider` is one of `github`, `bitbucket`, `bitbucketServer`, `azuredevops`, `gitlab` or `gitea`
- `provider` is one of `github`, `bitbucket`, `bitbucketServer`, `azuredevops` or `gitlab`
- `webDomain` is the URL where your git service exposes a web interface and APIs, e.g. `gitservice.work.com`
## Predefined commit message prefix
+7 -28
View File
@@ -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>@</kbd>: open command log menu
<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>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>?</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>@</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>z</kbd>: undo (via reflog) (experimental)
<kbd>ctrl+z</kbd>: redo (via reflog) (experimental)
<kbd>P</kbd>: push
@@ -56,13 +56,6 @@ _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>
@@ -96,13 +89,6 @@ _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>
@@ -211,13 +197,6 @@ _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>
@@ -271,8 +250,8 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
## Status
<pre>
<kbd>o</kbd>: open config file
<kbd>e</kbd>: edit config file
<kbd>o</kbd>: open config file
<kbd>u</kbd>: check for update
<kbd>enter</kbd>: switch to a recent repo
<kbd>a</kbd>: show all branch logs
+7 -28
View File
@@ -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>@</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>ctrl+p</kbd>: view custom patch options
<kbd>R</kbd>: リフレッシュ
<kbd>?</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>@</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>z</kbd>: アンドゥ (via reflog) (experimental)
<kbd>ctrl+z</kbd>: リドゥ (via reflog) (experimental)
<kbd>P</kbd>: push
@@ -115,13 +115,6 @@ _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>
@@ -138,8 +131,8 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
## ステータス
<pre>
<kbd>o</kbd>: 設定ファイルを開く
<kbd>e</kbd>: 設定ファイルを編集
<kbd>o</kbd>: 設定ファイルを開く
<kbd>u</kbd>: 更新を確認
<kbd>enter</kbd>: 最近使用したリポジトリに切り替え
<kbd>a</kbd>: すべてのブランチログを表示
@@ -264,13 +257,6 @@ _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>
@@ -309,10 +295,3 @@ _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>
+7 -28
View File
@@ -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>@</kbd>: 명령어 로그 메뉴 열기
<kbd>}</kbd>: diff 보기의 변경 사항 주위에 표시되는 컨텍스트의 크기를 늘리기
<kbd>{</kbd>: diff 보기의 변경 사항 주위에 표시되는 컨텍스트 크기 줄이기
<kbd>:</kbd>: execute custom command
<kbd>ctrl+p</kbd>: 커스텀 Patch 옵션 보기
<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>?</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>z</kbd>: 되돌리기 (reflog) (실험적)
<kbd>ctrl+z</kbd>: 다시 실행 (reflog) (실험적)
<kbd>P</kbd>: 푸시
@@ -83,13 +83,6 @@ _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>
@@ -175,8 +168,8 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
## 상태
<pre>
<kbd>o</kbd>: 설정 파일 열기
<kbd>e</kbd>: 설정 파일 수정
<kbd>o</kbd>: 설정 파일 열기
<kbd>u</kbd>: 업데이트 확인
<kbd>enter</kbd>: 최근에 사용한 저장소로 전환
<kbd>a</kbd>: 모든 브랜치 로그 표시
@@ -266,13 +259,6 @@ _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>
@@ -309,10 +295,3 @@ _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>
+7 -28
View File
@@ -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>@</kbd>: open command log menu
<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>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>?</kbd>: open menu
<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>z</kbd>: ongedaan maken (via reflog) (experimenteel)
<kbd>ctrl+z</kbd>: redo (via reflog) (experimenteel)
<kbd>P</kbd>: push
@@ -68,13 +68,6 @@ _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>
@@ -98,13 +91,6 @@ _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>
@@ -152,13 +138,6 @@ _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>
@@ -271,8 +250,8 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
## Status
<pre>
<kbd>o</kbd>: open config bestand
<kbd>e</kbd>: verander config bestand
<kbd>o</kbd>: open config bestand
<kbd>u</kbd>: check voor updates
<kbd>enter</kbd>: wissel naar een recente repo
<kbd>a</kbd>: alle logs van de branch laten zien
+7 -28
View File
@@ -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>@</kbd>: open command log menu
<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>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>?</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>@</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>z</kbd>: undo (via reflog) (experimental)
<kbd>ctrl+z</kbd>: redo (via reflog) (experimental)
<kbd>P</kbd>: push
@@ -42,13 +42,6 @@ _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>
@@ -82,13 +75,6 @@ _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>
@@ -127,13 +113,6 @@ _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>
@@ -264,8 +243,8 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
## Status
<pre>
<kbd>o</kbd>: otwórz konfigurację
<kbd>e</kbd>: edytuj konfigurację
<kbd>o</kbd>: otwórz konfigurację
<kbd>u</kbd>: sprawdź aktualizacje
<kbd>enter</kbd>: switch to a recent repo
<kbd>a</kbd>: pokaż wszystkie logi gałęzi
+7 -28
View File
@@ -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>@</kbd>: 打开命令日志菜单
<kbd>}</kbd>: 扩大差异视图中显示的上下文范围
<kbd>{</kbd>: 缩小差异视图中显示的上下文范围
<kbd>:</kbd>: 执行自定义命令
<kbd>ctrl+p</kbd>: 查看自定义补丁选项
<kbd>m</kbd>: 查看 合并/变基 选项
<kbd>ctrl+p</kbd>: 查看自定义补丁选项
<kbd>R</kbd>: 刷新
<kbd>?</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>z</kbd>: (通过 reflog)撤销「实验功能」
<kbd>ctrl+z</kbd>: (通过 reflog)重做「实验功能」
<kbd>P</kbd>: 推送
@@ -155,13 +155,6 @@ _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>
@@ -261,27 +254,13 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
## 状态
<pre>
<kbd>o</kbd>: 打开配置文件
<kbd>e</kbd>: 编辑配置文件
<kbd>o</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>
+2 -2
View File
@@ -4,12 +4,12 @@ go 1.18
require (
github.com/OpenPeeDeeP/xdg v1.0.0
github.com/atotto/clipboard v0.1.4
github.com/atotto/clipboard v0.1.2
github.com/aybabtme/humanlog v0.4.1
github.com/cli/safeexec v1.0.0
github.com/cloudfoundry/jibber_jabber v0.0.0-20151120183258-bcc4c8345a21
github.com/creack/pty v1.1.11
github.com/fsmiamoto/git-todo-parser v0.0.4
github.com/fsmiamoto/git-todo-parser v0.0.2
github.com/fsnotify/fsnotify v1.4.7
github.com/gdamore/tcell/v2 v2.6.0
github.com/go-errors/errors v1.4.2
+2 -4
View File
@@ -8,8 +8,6 @@ github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPd
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
github.com/atotto/clipboard v0.1.2 h1:YZCtFu5Ie8qX2VmVTBnrqLSiU9XOWwqNRmdT3gIQzbY=
github.com/atotto/clipboard v0.1.2/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
github.com/aybabtme/humanlog v0.4.1 h1:D8d9um55rrthJsP8IGSHBcti9lTb/XknmDAX6Zy8tek=
github.com/aybabtme/humanlog v0.4.1/go.mod h1:B0bnQX4FTSU3oftPMTTPvENCy8LqixLDvYJA9TUCAGo=
github.com/aybabtme/rgbterm v0.0.0-20170906152045-cc83f3b3ce59/go.mod h1:q/89r3U2H7sSsE2t6Kca0lfwTK8JdoNGS/yzM/4iH5I=
@@ -30,8 +28,8 @@ github.com/fatih/color v1.7.1-0.20180516100307-2d684516a886/go.mod h1:Zm6kSWBoL9
github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s=
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
github.com/fsmiamoto/git-todo-parser v0.0.4 h1:fzcGaoAFDHWzJRKw//CSZFrXucsLKplIvOSab3FtWWM=
github.com/fsmiamoto/git-todo-parser v0.0.4/go.mod h1:B+AgTbNE2BARvJqzXygThzqxLIaEWvwr2sxKYYb0Fas=
github.com/fsmiamoto/git-todo-parser v0.0.2 h1:l6Y+9q7jbM+yK/w6kASpHO7ejL9ARCErm3tCEqOT278=
github.com/fsmiamoto/git-todo-parser v0.0.2/go.mod h1:B+AgTbNE2BARvJqzXygThzqxLIaEWvwr2sxKYYb0Fas=
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko=
+4 -2
View File
@@ -33,6 +33,7 @@ type App struct {
Config config.AppConfigurer
OSCommand *oscommands.OSCommand
Gui *gui.Gui
Updater *updates.Updater // may only need this on the Gui
}
func Run(
@@ -86,7 +87,8 @@ func NewApp(config config.AppConfigurer, common *common.Common) (*App, error) {
app.OSCommand = oscommands.NewOSCommand(common, config, oscommands.GetPlatform(), oscommands.NewNullGuiIO(app.Log))
updater, err := updates.NewUpdater(common, config, app.OSCommand)
var err error
app.Updater, err = updates.NewUpdater(common, config, app.OSCommand)
if err != nil {
return app, err
}
@@ -106,7 +108,7 @@ func NewApp(config config.AppConfigurer, common *common.Common) (*App, error) {
return app, err
}
app.Gui, err = gui.NewGui(common, config, gitVersion, updater, showRecentRepos, dirName)
app.Gui, err = gui.NewGui(common, config, gitVersion, app.Updater, showRecentRepos, dirName)
if err != nil {
return app, err
}
+49 -249
View File
@@ -1,17 +1,13 @@
package daemon
import (
"encoding/json"
"fmt"
"log"
"os"
"strconv"
"path/filepath"
"strings"
"github.com/fsmiamoto/git-todo-parser/todo"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/common"
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/samber/lo"
"github.com/jesseduffield/lazygit/pkg/env"
)
// Sometimes lazygit will be invoked in daemon mode from a parent lazygit process.
@@ -19,58 +15,33 @@ import (
// For example, if we want to ensure that a git command doesn't hang due to
// waiting for an editor to save a commit message, we can tell git to invoke lazygit
// as the editor via 'GIT_EDITOR=lazygit', and use the env var
// 'LAZYGIT_DAEMON_KIND=1' (exit immediately) to specify that we want to run lazygit
// as a daemon which simply exits immediately.
//
// 'Daemon' is not the best name for this, because it's not a persistent background
// process, but it's close enough.
// 'LAZYGIT_DAEMON_KIND=EXIT_IMMEDIATELY' to specify that we want to run lazygit
// as a daemon which simply exits immediately. Any additional arguments we want
// to pass to a daemon can be done via other env vars.
type DaemonKind int
type DaemonKind string
const (
// for when we fail to parse the daemon kind
DaemonKindUnknown DaemonKind = iota
DaemonKindExitImmediately
DaemonKindCherryPick
DaemonKindMoveTodoUp
DaemonKindMoveTodoDown
DaemonKindInsertBreak
DaemonKindChangeTodoActions
DaemonKindMoveFixupCommitDown
InteractiveRebase DaemonKind = "INTERACTIVE_REBASE"
ExitImmediately DaemonKind = "EXIT_IMMEDIATELY"
)
const (
DaemonKindEnvKey string = "LAZYGIT_DAEMON_KIND"
// Contains json-encoded arguments to the daemon
DaemonInstructionEnvKey string = "LAZYGIT_DAEMON_INSTRUCTION"
RebaseTODOEnvKey string = "LAZYGIT_REBASE_TODO"
)
func getInstruction() Instruction {
jsonData := os.Getenv(DaemonInstructionEnvKey)
mapping := map[DaemonKind]func(string) Instruction{
DaemonKindExitImmediately: deserializeInstruction[*ExitImmediatelyInstruction],
DaemonKindCherryPick: deserializeInstruction[*CherryPickCommitsInstruction],
DaemonKindChangeTodoActions: deserializeInstruction[*ChangeTodoActionsInstruction],
DaemonKindMoveFixupCommitDown: deserializeInstruction[*MoveFixupCommitDownInstruction],
DaemonKindMoveTodoUp: deserializeInstruction[*MoveTodoUpInstruction],
DaemonKindMoveTodoDown: deserializeInstruction[*MoveTodoDownInstruction],
DaemonKindInsertBreak: deserializeInstruction[*InsertBreakInstruction],
}
return mapping[getDaemonKind()](jsonData)
type Daemon interface {
Run() error
}
func Handle(common *common.Common) {
if !InDaemonMode() {
d := getDaemon(common)
if d == nil {
return
}
instruction := getInstruction()
if err := instruction.run(common); err != nil {
if err := d.Run(); err != nil {
log.Fatal(err)
}
@@ -78,229 +49,58 @@ func Handle(common *common.Common) {
}
func InDaemonMode() bool {
return getDaemonKind() != DaemonKindUnknown
return getDaemonKind() != ""
}
func getDaemonKind() DaemonKind {
intValue, err := strconv.Atoi(os.Getenv(DaemonKindEnvKey))
if err != nil {
return DaemonKindUnknown
func getDaemon(common *common.Common) Daemon {
switch getDaemonKind() {
case InteractiveRebase:
return &rebaseDaemon{c: common}
case ExitImmediately:
return &exitImmediatelyDaemon{c: common}
}
return DaemonKind(intValue)
}
// An Instruction is a command to be run by lazygit in daemon mode.
// It is serialized to json and passed to lazygit via environment variables
type Instruction interface {
Kind() DaemonKind
SerializedInstructions() string
// runs the instruction
run(common *common.Common) error
}
func serializeInstruction[T any](instruction T) string {
jsonData, err := json.Marshal(instruction)
if err != nil {
// this should never happen
panic(err)
}
return string(jsonData)
}
func deserializeInstruction[T Instruction](jsonData string) Instruction {
var instruction T
err := json.Unmarshal([]byte(jsonData), &instruction)
if err != nil {
panic(err)
}
return instruction
}
func ToEnvVars(instruction Instruction) []string {
return []string{
fmt.Sprintf("%s=%d", DaemonKindEnvKey, instruction.Kind()),
fmt.Sprintf("%s=%s", DaemonInstructionEnvKey, instruction.SerializedInstructions()),
}
}
type ExitImmediatelyInstruction struct{}
func (self *ExitImmediatelyInstruction) Kind() DaemonKind {
return DaemonKindExitImmediately
}
func (self *ExitImmediatelyInstruction) SerializedInstructions() string {
return serializeInstruction(self)
}
func (self *ExitImmediatelyInstruction) run(common *common.Common) error {
return nil
}
func NewExitImmediatelyInstruction() Instruction {
return &ExitImmediatelyInstruction{}
func getDaemonKind() DaemonKind {
return DaemonKind(os.Getenv(DaemonKindEnvKey))
}
type CherryPickCommitsInstruction struct {
Todo string
type rebaseDaemon struct {
c *common.Common
}
func NewCherryPickCommitsInstruction(commits []*models.Commit) Instruction {
todoLines := lo.Map(commits, func(commit *models.Commit, _ int) TodoLine {
return TodoLine{
Action: "pick",
Commit: commit,
func (self *rebaseDaemon) Run() error {
self.c.Log.Info("Lazygit invoked as interactive rebase demon")
self.c.Log.Info("args: ", os.Args)
if strings.HasSuffix(os.Args[1], "git-rebase-todo") {
if err := os.WriteFile(os.Args[1], []byte(os.Getenv(RebaseTODOEnvKey)), 0o644); err != nil {
return err
}
})
todo := TodoLinesToString(todoLines)
return &CherryPickCommitsInstruction{
Todo: todo,
} else if strings.HasSuffix(os.Args[1], filepath.Join(gitDir(), "COMMIT_EDITMSG")) { // TODO: test
// if we are rebasing and squashing, we'll see a COMMIT_EDITMSG
// but in this case we don't need to edit it, so we'll just return
} else {
self.c.Log.Info("Lazygit demon did not match on any use cases")
}
return nil
}
func (self *CherryPickCommitsInstruction) Kind() DaemonKind {
return DaemonKindCherryPick
}
func (self *CherryPickCommitsInstruction) SerializedInstructions() string {
return serializeInstruction(self)
}
func (self *CherryPickCommitsInstruction) run(common *common.Common) error {
return handleInteractiveRebase(common, func(path string) error {
return utils.PrependStrToTodoFile(path, []byte(self.Todo))
})
}
type ChangeTodoActionsInstruction struct {
Changes []ChangeTodoAction
}
func NewChangeTodoActionsInstruction(changes []ChangeTodoAction) Instruction {
return &ChangeTodoActionsInstruction{
Changes: changes,
func gitDir() string {
dir := env.GetGitDirEnv()
if dir == "" {
return ".git"
}
return dir
}
func (self *ChangeTodoActionsInstruction) Kind() DaemonKind {
return DaemonKindChangeTodoActions
type exitImmediatelyDaemon struct {
c *common.Common
}
func (self *ChangeTodoActionsInstruction) SerializedInstructions() string {
return serializeInstruction(self)
}
func (self *ChangeTodoActionsInstruction) run(common *common.Common) error {
return handleInteractiveRebase(common, func(path string) error {
for _, c := range self.Changes {
if err := utils.EditRebaseTodo(path, c.Sha, todo.Pick, c.NewAction); err != nil {
return err
}
}
return nil
})
}
// Takes the sha of some commit, and the sha of a fixup commit that was created
// at the end of the branch, then moves the fixup commit down to right after the
// original commit, changing its type to "fixup"
type MoveFixupCommitDownInstruction struct {
OriginalSha string
FixupSha string
}
func NewMoveFixupCommitDownInstruction(originalSha string, fixupSha string) Instruction {
return &MoveFixupCommitDownInstruction{
OriginalSha: originalSha,
FixupSha: fixupSha,
}
}
func (self *MoveFixupCommitDownInstruction) Kind() DaemonKind {
return DaemonKindMoveFixupCommitDown
}
func (self *MoveFixupCommitDownInstruction) SerializedInstructions() string {
return serializeInstruction(self)
}
func (self *MoveFixupCommitDownInstruction) run(common *common.Common) error {
return handleInteractiveRebase(common, func(path string) error {
return utils.MoveFixupCommitDown(path, self.OriginalSha, self.FixupSha)
})
}
type MoveTodoUpInstruction struct {
Sha string
}
func NewMoveTodoUpInstruction(sha string) Instruction {
return &MoveTodoUpInstruction{
Sha: sha,
}
}
func (self *MoveTodoUpInstruction) Kind() DaemonKind {
return DaemonKindMoveTodoUp
}
func (self *MoveTodoUpInstruction) SerializedInstructions() string {
return serializeInstruction(self)
}
func (self *MoveTodoUpInstruction) run(common *common.Common) error {
return handleInteractiveRebase(common, func(path string) error {
return utils.MoveTodoUp(path, self.Sha, todo.Pick)
})
}
type MoveTodoDownInstruction struct {
Sha string
}
func NewMoveTodoDownInstruction(sha string) Instruction {
return &MoveTodoDownInstruction{
Sha: sha,
}
}
func (self *MoveTodoDownInstruction) Kind() DaemonKind {
return DaemonKindMoveTodoDown
}
func (self *MoveTodoDownInstruction) SerializedInstructions() string {
return serializeInstruction(self)
}
func (self *MoveTodoDownInstruction) run(common *common.Common) error {
return handleInteractiveRebase(common, func(path string) error {
return utils.MoveTodoDown(path, self.Sha, todo.Pick)
})
}
type InsertBreakInstruction struct{}
func NewInsertBreakInstruction() Instruction {
return &InsertBreakInstruction{}
}
func (self *InsertBreakInstruction) Kind() DaemonKind {
return DaemonKindInsertBreak
}
func (self *InsertBreakInstruction) SerializedInstructions() string {
return serializeInstruction(self)
}
func (self *InsertBreakInstruction) run(common *common.Common) error {
return handleInteractiveRebase(common, func(path string) error {
return utils.PrependStrToTodoFile(path, []byte("break\n"))
})
func (self *exitImmediatelyDaemon) Run() error {
return nil
}
-64
View File
@@ -1,64 +0,0 @@
package daemon
import (
"os"
"path/filepath"
"strings"
"github.com/fsmiamoto/git-todo-parser/todo"
"github.com/jesseduffield/generics/slices"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/common"
"github.com/jesseduffield/lazygit/pkg/env"
)
type TodoLine struct {
Action string
Commit *models.Commit
}
func (self *TodoLine) ToString() string {
if self.Action == "break" {
return self.Action + "\n"
} else {
return self.Action + " " + self.Commit.Sha + " " + self.Commit.Name + "\n"
}
}
func TodoLinesToString(todoLines []TodoLine) string {
lines := slices.Map(todoLines, func(todoLine TodoLine) string {
return todoLine.ToString()
})
return strings.Join(slices.Reverse(lines), "")
}
type ChangeTodoAction struct {
Sha string
NewAction todo.TodoCommand
}
func handleInteractiveRebase(common *common.Common, f func(path string) error) error {
common.Log.Info("Lazygit invoked as interactive rebase demon")
common.Log.Info("args: ", os.Args)
path := os.Args[1]
if strings.HasSuffix(path, "git-rebase-todo") {
return f(path)
} else if strings.HasSuffix(path, filepath.Join(gitDir(), "COMMIT_EDITMSG")) { // TODO: test
// if we are rebasing and squashing, we'll see a COMMIT_EDITMSG
// but in this case we don't need to edit it, so we'll just return
} else {
common.Log.Info("Lazygit demon did not match on any use cases")
}
return nil
}
func gitDir() string {
dir := env.GetGitDirEnv()
if dir == "" {
return ".git"
}
return dir
}
+27 -28
View File
@@ -87,34 +87,33 @@ func writeString(file *os.File, str string) {
func localisedTitle(tr *i18n.TranslationSet, str string) string {
contextTitleMap := map[string]string{
"global": tr.GlobalTitle,
"navigation": tr.NavigationTitle,
"branches": tr.BranchesTitle,
"localBranches": tr.LocalBranchesTitle,
"files": tr.FilesTitle,
"status": tr.StatusTitle,
"submodules": tr.SubmodulesTitle,
"subCommits": tr.SubCommitsTitle,
"remoteBranches": tr.RemoteBranchesTitle,
"remotes": tr.RemotesTitle,
"reflogCommits": tr.ReflogCommitsTitle,
"tags": tr.TagsTitle,
"commitFiles": tr.CommitFilesTitle,
"commitMessage": tr.CommitMessageTitle,
"commitDescription": tr.CommitDescriptionTitle,
"commits": tr.CommitsTitle,
"confirmation": tr.ConfirmationTitle,
"information": tr.InformationTitle,
"main": tr.NormalTitle,
"patchBuilding": tr.PatchBuildingTitle,
"mergeConflicts": tr.MergingTitle,
"staging": tr.StagingTitle,
"menu": tr.MenuTitle,
"search": tr.SearchTitle,
"secondary": tr.SecondaryTitle,
"stash": tr.StashTitle,
"suggestions": tr.SuggestionsCheatsheetTitle,
"extras": tr.ExtrasTitle,
"global": tr.GlobalTitle,
"navigation": tr.NavigationTitle,
"branches": tr.BranchesTitle,
"localBranches": tr.LocalBranchesTitle,
"files": tr.FilesTitle,
"status": tr.StatusTitle,
"submodules": tr.SubmodulesTitle,
"subCommits": tr.SubCommitsTitle,
"remoteBranches": tr.RemoteBranchesTitle,
"remotes": tr.RemotesTitle,
"reflogCommits": tr.ReflogCommitsTitle,
"tags": tr.TagsTitle,
"commitFiles": tr.CommitFilesTitle,
"commitMessage": tr.CommitMessageTitle,
"commits": tr.CommitsTitle,
"confirmation": tr.ConfirmationTitle,
"information": tr.InformationTitle,
"main": tr.NormalTitle,
"patchBuilding": tr.PatchBuildingTitle,
"mergeConflicts": tr.MergingTitle,
"staging": tr.StagingTitle,
"menu": tr.MenuTitle,
"search": tr.SearchTitle,
"secondary": tr.SecondaryTitle,
"stash": tr.StashTitle,
"suggestions": tr.SuggestionsCheatsheetTitle,
"extras": tr.ExtrasTitle,
}
title, ok := contextTitleMap[str]
+1 -1
View File
@@ -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, statusCommands.RebaseMode)
commitLoader := git_commands.NewCommitLoader(cmn, cmd, dotGitDir, branchCommands.CurrentBranchInfo, statusCommands.RebaseMode)
reflogCommitLoader := git_commands.NewReflogCommitLoader(cmn, cmd)
remoteLoader := git_commands.NewRemoteLoader(cmn, cmd, repo.Remotes)
stashLoader := git_commands.NewStashLoader(cmn, cmd)
+5 -47
View File
@@ -5,7 +5,6 @@ import (
"github.com/go-errors/errors"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/config"
"github.com/stretchr/testify/assert"
)
@@ -100,53 +99,12 @@ func TestBranchDeleteBranch(t *testing.T) {
}
func TestBranchMerge(t *testing.T) {
scenarios := []struct {
testName string
userConfig *config.UserConfig
opts MergeOpts
branchName string
expected string
}{
{
testName: "basic",
userConfig: &config.UserConfig{},
opts: MergeOpts{},
branchName: "mybranch",
expected: `git merge --no-edit "mybranch"`,
},
{
testName: "merging args",
userConfig: &config.UserConfig{
Git: config.GitConfig{
Merging: config.MergingConfig{
Args: "--merging-args", // it's up to the user what they put here
},
},
},
opts: MergeOpts{},
branchName: "mybranch",
expected: `git merge --no-edit --merging-args "mybranch"`,
},
{
testName: "fast forward only",
userConfig: &config.UserConfig{},
opts: MergeOpts{FastForwardOnly: true},
branchName: "mybranch",
expected: `git merge --no-edit --ff-only "mybranch"`,
},
}
runner := oscommands.NewFakeRunner(t).
Expect(`git merge --no-edit "test"`, "", nil)
instance := buildBranchCommands(commonDeps{runner: runner})
for _, s := range scenarios {
s := s
t.Run(s.testName, func(t *testing.T) {
runner := oscommands.NewFakeRunner(t).
Expect(s.expected, "", nil)
instance := buildBranchCommands(commonDeps{runner: runner, userConfig: s.userConfig})
assert.NoError(t, instance.Merge(s.branchName, s.opts))
runner.CheckForMissingCalls()
})
}
assert.NoError(t, instance.Merge("test", MergeOpts{}))
runner.CheckForMissingCalls()
}
func TestBranchCheckout(t *testing.T) {
+11 -30
View File
@@ -8,8 +8,6 @@ import (
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
)
var ErrInvalidCommitIndex = errors.New("invalid commit index")
type CommitCommands struct {
*GitCommon
}
@@ -20,6 +18,11 @@ 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()
@@ -42,7 +45,11 @@ func (self *CommitCommands) ResetToCommit(sha string, strength string, envVars [
}
func (self *CommitCommands) CommitCmdObj(message string) oscommands.ICmdObj {
messageArgs := self.commitMessageArgs(message)
splitMessage := strings.Split(message, "\n")
lineArgs := ""
for _, line := range splitMessage {
lineArgs += fmt.Sprintf(" -m %s", self.cmd.Quote(line))
}
skipHookPrefix := self.UserConfig.Git.SkipHookPrefix
noVerifyFlag := ""
@@ -50,23 +57,7 @@ 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(), 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)
return self.cmd.New(fmt.Sprintf("git commit%s%s%s", noVerifyFlag, self.signoffFlag(), lineArgs))
}
// runs git commit without the -m argument meaning it will invoke the user's editor
@@ -187,13 +178,3 @@ 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,6 +1,7 @@
package git_commands
import (
"fmt"
"strings"
"github.com/jesseduffield/generics/slices"
@@ -24,18 +25,12 @@ func NewCommitFileLoader(common *common.Common, cmd oscommands.ICmdObjBuilder) *
// GetFilesInDiff get the specified commit files
func (self *CommitFileLoader) GetFilesInDiff(from string, to string, reverse bool) ([]*models.CommitFile, error) {
cmdStr := NewGitCmd("diff").
Arg("--submodule").
Arg("--no-ext-diff").
Arg("--name-status").
Arg("-z").
Arg("--no-renames").
ArgIf(reverse, "-R").
Arg(from).
Arg(to).
ToString()
reverseFlag := ""
if reverse {
reverseFlag = " -R "
}
filenames, err := self.cmd.New(cmdStr).DontLog().RunWithOutput()
filenames, err := self.cmd.New(fmt.Sprintf("git diff --submodule --no-ext-diff --name-status -z --no-renames %s %s %s", reverseFlag, from, to)).DontLog().RunWithOutput()
if err != nil {
return nil, err
}
+93 -106
View File
@@ -15,7 +15,6 @@ import (
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/commands/types/enums"
"github.com/jesseduffield/lazygit/pkg/common"
"github.com/samber/lo"
)
// context:
@@ -29,14 +28,11 @@ type CommitLoader struct {
*common.Common
cmd oscommands.ICmdObjBuilder
getRebaseMode func() (enums.RebaseMode, error)
readFile func(filename string) ([]byte, error)
walkFiles func(root string, fn filepath.WalkFunc) error
dotGitDir string
// List of main branches that exist in the repo, quoted for direct use in a git command.
// We use these to obtain the merge base of the branch.
// When nil, we're yet to obtain the list of main branches.
quotedMainBranches *string
getCurrentBranchInfo func() (BranchInfo, error)
getRebaseMode func() (enums.RebaseMode, error)
readFile func(filename string) ([]byte, error)
walkFiles func(root string, fn filepath.WalkFunc) error
dotGitDir string
}
// making our dependencies explicit for the sake of easier testing
@@ -44,16 +40,17 @@ func NewCommitLoader(
cmn *common.Common,
cmd oscommands.ICmdObjBuilder,
dotGitDir string,
getCurrentBranchInfo func() (BranchInfo, error),
getRebaseMode func() (enums.RebaseMode, error),
) *CommitLoader {
return &CommitLoader{
Common: cmn,
cmd: cmd,
getRebaseMode: getRebaseMode,
readFile: os.ReadFile,
walkFiles: filepath.Walk,
dotGitDir: dotGitDir,
quotedMainBranches: nil,
Common: cmn,
cmd: cmd,
getCurrentBranchInfo: getCurrentBranchInfo,
getRebaseMode: getRebaseMode,
readFile: os.ReadFile,
walkFiles: filepath.Walk,
dotGitDir: dotGitDir,
}
}
@@ -92,7 +89,7 @@ func (self *CommitLoader) GetCommits(opts GetCommitsOptions) ([]*models.Commit,
if commit.Sha == firstPushedCommit {
passedFirstPushedCommit = true
}
commit.Status = map[bool]models.CommitStatus{true: models.StatusUnpushed, false: models.StatusPushed}[!passedFirstPushedCommit]
commit.Status = map[bool]string{true: "unpushed", false: "pushed"}[!passedFirstPushedCommit]
commits = append(commits, commit)
return false, nil
})
@@ -104,7 +101,10 @@ func (self *CommitLoader) GetCommits(opts GetCommitsOptions) ([]*models.Commit,
return commits, nil
}
commits = self.setCommitMergedStatuses(opts.RefName, commits)
commits, err = self.setCommitMergedStatuses(opts.RefName, commits)
if err != nil {
return nil, err
}
return commits, nil
}
@@ -194,43 +194,35 @@ func (self *CommitLoader) getHydratedRebasingCommits(rebaseMode enums.RebaseMode
return nil, nil
}
commitShas := slices.FilterMap(commits, func(commit *models.Commit) (string, bool) {
return commit.Sha, commit.Sha != ""
commitShas := slices.Map(commits, func(commit *models.Commit) string {
return commit.Sha
})
// note that we're not filtering these as we do non-rebasing commits just because
// I suspect that will cause some damage
cmdObj := self.cmd.New(
NewGitCmd("show").
Config("log.showSignature=false").
Arg("--no-patch").
Arg("--oneline").
Arg(strings.Join(commitShas, " ")).
Arg(prettyFormat).
Arg("--abbrev=20").
ToString(),
fmt.Sprintf(
"git -c log.showSignature=false show %s --no-patch --oneline %s --abbrev=%d",
strings.Join(commitShas, " "),
prettyFormat,
20,
),
).DontLog()
fullCommits := map[string]*models.Commit{}
hydratedCommits := make([]*models.Commit, 0, len(commits))
i := 0
err = cmdObj.RunAndProcessLines(func(line string) (bool, error) {
commit := self.extractCommitFromLine(line)
fullCommits[commit.Sha] = commit
matchingCommit := commits[i]
commit.Action = matchingCommit.Action
commit.Status = matchingCommit.Status
hydratedCommits = append(hydratedCommits, commit)
i++
return false, nil
})
if err != nil {
return nil, err
}
hydratedCommits := make([]*models.Commit, 0, len(commits))
for _, rebasingCommit := range commits {
if rebasingCommit.Sha == "" {
hydratedCommits = append(hydratedCommits, rebasingCommit)
} else if commit := fullCommits[rebasingCommit.Sha]; commit != nil {
commit.Action = rebasingCommit.Action
commit.Status = rebasingCommit.Status
hydratedCommits = append(hydratedCommits, commit)
}
}
return hydratedCommits, nil
}
@@ -313,17 +305,15 @@ func (self *CommitLoader) getInteractiveRebasingCommits() ([]*models.Commit, err
}
for _, t := range todos {
if t.Command == todo.UpdateRef {
t.Msg = strings.TrimPrefix(t.Ref, "refs/heads/")
} else if t.Commit == "" {
if t.Commit == "" {
// Command does not have a commit associated, skip
continue
}
commits = slices.Prepend(commits, &models.Commit{
Sha: t.Commit,
Name: t.Msg,
Status: models.StatusRebasing,
Action: t.Command,
Status: "rebasing",
Action: t.Command.String(),
})
}
@@ -342,69 +332,47 @@ func (self *CommitLoader) commitFromPatch(content string) *models.Commit {
return &models.Commit{
Sha: sha,
Name: name,
Status: models.StatusRebasing,
Status: "rebasing",
}
}
func (self *CommitLoader) setCommitMergedStatuses(refName string, commits []*models.Commit) []*models.Commit {
ancestor := self.getMergeBase(refName)
func (self *CommitLoader) setCommitMergedStatuses(refName string, commits []*models.Commit) ([]*models.Commit, error) {
ancestor, err := self.getMergeBase(refName)
if err != nil {
return nil, err
}
if ancestor == "" {
return commits
return commits, nil
}
passedAncestor := false
for i, commit := range commits {
if strings.HasPrefix(ancestor, commit.Sha) {
passedAncestor = true
}
if commit.Status != models.StatusPushed && commit.Status != models.StatusUnpushed {
if commit.Status != "pushed" {
continue
}
if passedAncestor {
commits[i].Status = models.StatusMerged
commits[i].Status = "merged"
}
}
return commits
return commits, nil
}
func (self *CommitLoader) getMergeBase(refName string) string {
if self.quotedMainBranches == nil {
self.quotedMainBranches = lo.ToPtr(self.getExistingMainBranches())
}
if *self.quotedMainBranches == "" {
return ""
}
// We pass all configured main branches to the merge-base call; git will
// return the base commit for the closest one.
output, err := self.cmd.New(
NewGitCmd("merge-base").
Arg(self.cmd.Quote(refName)).
Arg(*self.quotedMainBranches).
ToString(),
).DontLog().RunWithOutput()
func (self *CommitLoader) getMergeBase(refName string) (string, error) {
info, err := self.getCurrentBranchInfo()
if err != nil {
// If there's an error, it must be because one of the main branches that
// used to exist when we called getExistingMainBranches() was deleted
// meanwhile. To fix this for next time, throw away our cache.
self.quotedMainBranches = nil
return "", err
}
return ignoringWarnings(output)
}
func (self *CommitLoader) getExistingMainBranches() string {
return strings.Join(
lo.FilterMap(self.UserConfig.Git.MainBranches,
func(branchName string, _ int) (string, bool) {
quotedRef := self.cmd.Quote("refs/heads/" + branchName)
if err := self.cmd.New(
NewGitCmd("rev-parse").Arg("--verify").Arg("--quiet").Arg(quotedRef).ToString(),
).DontLog().Run(); err != nil {
return "", false
}
return quotedRef, true
}), " ")
baseBranch := "master"
if strings.HasPrefix(info.RefName, "feature/") {
baseBranch = "develop"
}
// swallowing error because it's not a big deal; probably because there are no commits yet
output, _ := self.cmd.New(fmt.Sprintf("git merge-base %s %s", self.cmd.Quote(refName), self.cmd.Quote(baseBranch))).DontLog().RunWithOutput()
return ignoringWarnings(output), nil
}
func ignoringWarnings(commandOutput string) string {
@@ -422,10 +390,9 @@ func ignoringWarnings(commandOutput string) string {
func (self *CommitLoader) getFirstPushedCommit(refName string) (string, error) {
output, err := self.cmd.
New(
NewGitCmd("merge-base").
Arg(self.cmd.Quote(refName)).
Arg(self.cmd.Quote(strings.TrimPrefix(refName, "refs/heads/")) + "@{u}").
ToString(),
fmt.Sprintf("git merge-base %s %s@{u}",
self.cmd.Quote(refName),
self.cmd.Quote(strings.TrimPrefix(refName, "refs/heads/"))),
).
DontLog().
RunWithOutput()
@@ -438,22 +405,42 @@ func (self *CommitLoader) getFirstPushedCommit(refName string) (string, error) {
// getLog gets the git log.
func (self *CommitLoader) getLogCmd(opts GetCommitsOptions) oscommands.ICmdObj {
limitFlag := ""
if opts.Limit {
limitFlag = " -300"
}
followFlag := ""
filterFlag := ""
if opts.FilterPath != "" {
followFlag = " --follow"
filterFlag = fmt.Sprintf(" %s", self.cmd.Quote(opts.FilterPath))
}
config := self.UserConfig.Git.Log
cmdStr := NewGitCmd("log").
Arg(self.cmd.Quote(opts.RefName)).
ArgIf(config.Order != "default", "--"+config.Order).
ArgIf(opts.All, "--all").
Arg("--oneline").
Arg(prettyFormat).
Arg("--abbrev=40").
ArgIf(opts.Limit, "-300").
ArgIf(opts.FilterPath != "", "--follow").
ArgIf(opts.FilterPath != "", self.cmd.Quote(opts.FilterPath)).
Arg("--no-show-signature").
ToString()
orderFlag := ""
if config.Order != "default" {
orderFlag = " --" + config.Order
}
allFlag := ""
if opts.All {
allFlag = " --all"
}
return self.cmd.New(cmdStr)
return self.cmd.New(
fmt.Sprintf(
"git log %s%s%s --oneline %s%s --abbrev=%d%s --no-show-signature --%s",
self.cmd.Quote(opts.RefName),
orderFlag,
allFlag,
prettyFormat,
limitFlag,
40,
followFlag,
filterFlag,
),
).DontLog()
}
const prettyFormat = `--pretty=format:"%H%x00%at%x00%aN%x00%ae%x00%d%x00%p%x00%s"`
+55 -125
View File
@@ -1,7 +1,6 @@
package git_commands
import (
"errors"
"path/filepath"
"strings"
"testing"
@@ -22,26 +21,25 @@ d8084cd558925eb7c9c38afeed5725c21653ab90|1640821426|Jesse Duffield|jessedduffiel
3d4470a6c072208722e5ae9a54bcb9634959a1c5|1640748818|Jesse Duffield|jessedduffield@gmail.com||053a66a7be3da43aacdc|WIP
053a66a7be3da43aacdc7aa78e1fe757b82c4dd2|1640739815|Jesse Duffield|jessedduffield@gmail.com||985fe482e806b172aea4|refactoring the config struct`, "|", "\x00", -1)
var singleCommitOutput = strings.Replace(`0eea75e8c631fba6b58135697835d58ba4c18dbc|1640826609|Jesse Duffield|jessedduffield@gmail.com| (HEAD -> better-tests)|b21997d6b4cbdf84b149|better typing for rebase mode`, "|", "\x00", -1)
func TestGetCommits(t *testing.T) {
type scenario struct {
testName string
runner *oscommands.FakeCmdObjRunner
expectedCommits []*models.Commit
expectedError error
logOrder string
rebaseMode enums.RebaseMode
opts GetCommitsOptions
mainBranches []string
testName string
runner *oscommands.FakeCmdObjRunner
expectedCommits []*models.Commit
expectedError error
logOrder string
rebaseMode enums.RebaseMode
currentBranchName string
opts GetCommitsOptions
}
scenarios := []scenario{
{
testName: "should return no commits if there are none",
logOrder: "topo-order",
rebaseMode: enums.REBASE_MODE_NONE,
opts: GetCommitsOptions{RefName: "HEAD", IncludeRebaseCommits: false},
testName: "should return no commits if there are none",
logOrder: "topo-order",
rebaseMode: enums.REBASE_MODE_NONE,
currentBranchName: "master",
opts: GetCommitsOptions{RefName: "HEAD", IncludeRebaseCommits: false},
runner: oscommands.NewFakeRunner(t).
Expect(`git merge-base "HEAD" "HEAD"@{u}`, "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", nil).
Expect(`git log "HEAD" --topo-order --oneline --pretty=format:"%H%x00%at%x00%aN%x00%ae%x00%d%x00%p%x00%s" --abbrev=40 --no-show-signature --`, "", nil),
@@ -50,10 +48,11 @@ func TestGetCommits(t *testing.T) {
expectedError: nil,
},
{
testName: "should use proper upstream name for branch",
logOrder: "topo-order",
rebaseMode: enums.REBASE_MODE_NONE,
opts: GetCommitsOptions{RefName: "refs/heads/mybranch", IncludeRebaseCommits: false},
testName: "should use proper upstream name for branch",
logOrder: "topo-order",
rebaseMode: enums.REBASE_MODE_NONE,
currentBranchName: "mybranch",
opts: GetCommitsOptions{RefName: "refs/heads/mybranch", IncludeRebaseCommits: false},
runner: oscommands.NewFakeRunner(t).
Expect(`git merge-base "refs/heads/mybranch" "mybranch"@{u}`, "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", nil).
Expect(`git log "refs/heads/mybranch" --topo-order --oneline --pretty=format:"%H%x00%at%x00%aN%x00%ae%x00%d%x00%p%x00%s" --abbrev=40 --no-show-signature --`, "", nil),
@@ -62,28 +61,25 @@ func TestGetCommits(t *testing.T) {
expectedError: nil,
},
{
testName: "should return commits if they are present",
logOrder: "topo-order",
rebaseMode: enums.REBASE_MODE_NONE,
opts: GetCommitsOptions{RefName: "HEAD", IncludeRebaseCommits: false},
mainBranches: []string{"master", "main"},
testName: "should return commits if they are present",
logOrder: "topo-order",
rebaseMode: enums.REBASE_MODE_NONE,
currentBranchName: "master",
opts: GetCommitsOptions{RefName: "HEAD", IncludeRebaseCommits: false},
runner: oscommands.NewFakeRunner(t).
// here it's seeing which commits are yet to be pushed
Expect(`git merge-base "HEAD" "HEAD"@{u}`, "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", nil).
// here it's actually getting all the commits in a formatted form, one per line
Expect(`git log "HEAD" --topo-order --oneline --pretty=format:"%H%x00%at%x00%aN%x00%ae%x00%d%x00%p%x00%s" --abbrev=40 --no-show-signature --`, commitsOutput, nil).
// here it's testing which of the configured main branches exist
Expect(`git rev-parse --verify --quiet "refs/heads/master"`, "", nil). // this one does
Expect(`git rev-parse --verify --quiet "refs/heads/main"`, "", errors.New("error")). // this one doesn't
// here it's seeing where our branch diverged from the master branch so that we can mark that commit and parent commits as 'merged'
Expect(`git merge-base "HEAD" "refs/heads/master"`, "26c07b1ab33860a1a7591a0638f9925ccf497ffa", nil),
Expect(`git merge-base "HEAD" "master"`, "26c07b1ab33860a1a7591a0638f9925ccf497ffa", nil),
expectedCommits: []*models.Commit{
{
Sha: "0eea75e8c631fba6b58135697835d58ba4c18dbc",
Name: "better typing for rebase mode",
Status: models.StatusUnpushed,
Action: models.ActionNone,
Status: "unpushed",
Action: "",
Tags: []string{},
ExtraInfo: "(HEAD -> better-tests)",
AuthorName: "Jesse Duffield",
@@ -96,8 +92,8 @@ func TestGetCommits(t *testing.T) {
{
Sha: "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164",
Name: "fix logging",
Status: models.StatusPushed,
Action: models.ActionNone,
Status: "pushed",
Action: "",
Tags: []string{},
ExtraInfo: "(origin/better-tests)",
AuthorName: "Jesse Duffield",
@@ -110,8 +106,8 @@ func TestGetCommits(t *testing.T) {
{
Sha: "e94e8fc5b6fab4cb755f29f1bdb3ee5e001df35c",
Name: "refactor",
Status: models.StatusPushed,
Action: models.ActionNone,
Status: "pushed",
Action: "",
Tags: []string{},
ExtraInfo: "",
AuthorName: "Jesse Duffield",
@@ -124,8 +120,8 @@ func TestGetCommits(t *testing.T) {
{
Sha: "d8084cd558925eb7c9c38afeed5725c21653ab90",
Name: "WIP",
Status: models.StatusPushed,
Action: models.ActionNone,
Status: "pushed",
Action: "",
Tags: []string{},
ExtraInfo: "",
AuthorName: "Jesse Duffield",
@@ -138,8 +134,8 @@ func TestGetCommits(t *testing.T) {
{
Sha: "65f910ebd85283b5cce9bf67d03d3f1a9ea3813a",
Name: "WIP",
Status: models.StatusPushed,
Action: models.ActionNone,
Status: "pushed",
Action: "",
Tags: []string{},
ExtraInfo: "",
AuthorName: "Jesse Duffield",
@@ -152,8 +148,8 @@ func TestGetCommits(t *testing.T) {
{
Sha: "26c07b1ab33860a1a7591a0638f9925ccf497ffa",
Name: "WIP",
Status: models.StatusMerged,
Action: models.ActionNone,
Status: "merged",
Action: "",
Tags: []string{},
ExtraInfo: "",
AuthorName: "Jesse Duffield",
@@ -166,8 +162,8 @@ func TestGetCommits(t *testing.T) {
{
Sha: "3d4470a6c072208722e5ae9a54bcb9634959a1c5",
Name: "WIP",
Status: models.StatusMerged,
Action: models.ActionNone,
Status: "merged",
Action: "",
Tags: []string{},
ExtraInfo: "",
AuthorName: "Jesse Duffield",
@@ -180,8 +176,8 @@ func TestGetCommits(t *testing.T) {
{
Sha: "053a66a7be3da43aacdc7aa78e1fe757b82c4dd2",
Name: "refactoring the config struct",
Status: models.StatusMerged,
Action: models.ActionNone,
Status: "merged",
Action: "",
Tags: []string{},
ExtraInfo: "",
AuthorName: "Jesse Duffield",
@@ -195,80 +191,11 @@ func TestGetCommits(t *testing.T) {
expectedError: nil,
},
{
testName: "should not call merge-base for mainBranches if none exist",
logOrder: "topo-order",
rebaseMode: enums.REBASE_MODE_NONE,
opts: GetCommitsOptions{RefName: "HEAD", IncludeRebaseCommits: false},
mainBranches: []string{"master", "main"},
runner: oscommands.NewFakeRunner(t).
// here it's seeing which commits are yet to be pushed
Expect(`git merge-base "HEAD" "HEAD"@{u}`, "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", nil).
// here it's actually getting all the commits in a formatted form, one per line
Expect(`git log "HEAD" --topo-order --oneline --pretty=format:"%H%x00%at%x00%aN%x00%ae%x00%d%x00%p%x00%s" --abbrev=40 --no-show-signature --`, singleCommitOutput, nil).
// here it's testing which of the configured main branches exist; neither does
Expect(`git rev-parse --verify --quiet "refs/heads/master"`, "", errors.New("error")).
Expect(`git rev-parse --verify --quiet "refs/heads/main"`, "", errors.New("error")),
expectedCommits: []*models.Commit{
{
Sha: "0eea75e8c631fba6b58135697835d58ba4c18dbc",
Name: "better typing for rebase mode",
Status: models.StatusUnpushed,
Action: models.ActionNone,
Tags: []string{},
ExtraInfo: "(HEAD -> better-tests)",
AuthorName: "Jesse Duffield",
AuthorEmail: "jessedduffield@gmail.com",
UnixTimestamp: 1640826609,
Parents: []string{
"b21997d6b4cbdf84b149",
},
},
},
expectedError: nil,
},
{
testName: "should call merge-base for all main branches that exist",
logOrder: "topo-order",
rebaseMode: enums.REBASE_MODE_NONE,
opts: GetCommitsOptions{RefName: "HEAD", IncludeRebaseCommits: false},
mainBranches: []string{"master", "main", "develop", "1.0-hotfixes"},
runner: oscommands.NewFakeRunner(t).
// here it's seeing which commits are yet to be pushed
Expect(`git merge-base "HEAD" "HEAD"@{u}`, "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", nil).
// here it's actually getting all the commits in a formatted form, one per line
Expect(`git log "HEAD" --topo-order --oneline --pretty=format:"%H%x00%at%x00%aN%x00%ae%x00%d%x00%p%x00%s" --abbrev=40 --no-show-signature --`, singleCommitOutput, nil).
// here it's testing which of the configured main branches exist
Expect(`git rev-parse --verify --quiet "refs/heads/master"`, "", nil).
Expect(`git rev-parse --verify --quiet "refs/heads/main"`, "", errors.New("error")).
Expect(`git rev-parse --verify --quiet "refs/heads/develop"`, "", nil).
Expect(`git rev-parse --verify --quiet "refs/heads/1.0-hotfixes"`, "", nil).
// here it's seeing where our branch diverged from the master branch so that we can mark that commit and parent commits as 'merged'
Expect(`git merge-base "HEAD" "refs/heads/master" "refs/heads/develop" "refs/heads/1.0-hotfixes"`, "26c07b1ab33860a1a7591a0638f9925ccf497ffa", nil),
expectedCommits: []*models.Commit{
{
Sha: "0eea75e8c631fba6b58135697835d58ba4c18dbc",
Name: "better typing for rebase mode",
Status: models.StatusUnpushed,
Action: models.ActionNone,
Tags: []string{},
ExtraInfo: "(HEAD -> better-tests)",
AuthorName: "Jesse Duffield",
AuthorEmail: "jessedduffield@gmail.com",
UnixTimestamp: 1640826609,
Parents: []string{
"b21997d6b4cbdf84b149",
},
},
},
expectedError: nil,
},
{
testName: "should not specify order if `log.order` is `default`",
logOrder: "default",
rebaseMode: enums.REBASE_MODE_NONE,
opts: GetCommitsOptions{RefName: "HEAD", IncludeRebaseCommits: false},
testName: "should not specify order if `log.order` is `default`",
logOrder: "default",
rebaseMode: enums.REBASE_MODE_NONE,
currentBranchName: "master",
opts: GetCommitsOptions{RefName: "HEAD", IncludeRebaseCommits: false},
runner: oscommands.NewFakeRunner(t).
Expect(`git merge-base "HEAD" "HEAD"@{u}`, "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", nil).
Expect(`git log "HEAD" --oneline --pretty=format:"%H%x00%at%x00%aN%x00%ae%x00%d%x00%p%x00%s" --abbrev=40 --no-show-signature --`, "", nil),
@@ -277,10 +204,11 @@ func TestGetCommits(t *testing.T) {
expectedError: nil,
},
{
testName: "should set filter path",
logOrder: "default",
rebaseMode: enums.REBASE_MODE_NONE,
opts: GetCommitsOptions{RefName: "HEAD", FilterPath: "src"},
testName: "should set filter path",
logOrder: "default",
rebaseMode: enums.REBASE_MODE_NONE,
currentBranchName: "master",
opts: GetCommitsOptions{RefName: "HEAD", FilterPath: "src"},
runner: oscommands.NewFakeRunner(t).
Expect(`git merge-base "HEAD" "HEAD"@{u}`, "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", nil).
Expect(`git log "HEAD" --oneline --pretty=format:"%H%x00%at%x00%aN%x00%ae%x00%d%x00%p%x00%s" --abbrev=40 --follow --no-show-signature -- "src"`, "", nil),
@@ -297,8 +225,11 @@ func TestGetCommits(t *testing.T) {
common.UserConfig.Git.Log.Order = scenario.logOrder
builder := &CommitLoader{
Common: common,
cmd: oscommands.NewDummyCmdObjBuilder(scenario.runner),
Common: common,
cmd: oscommands.NewDummyCmdObjBuilder(scenario.runner),
getCurrentBranchInfo: func() (BranchInfo, error) {
return BranchInfo{RefName: scenario.currentBranchName, DisplayName: scenario.currentBranchName, DetachedHead: false}, nil
},
getRebaseMode: func() (enums.RebaseMode, error) { return scenario.rebaseMode, nil },
dotGitDir: ".git",
readFile: func(filename string) ([]byte, error) {
@@ -309,7 +240,6 @@ func TestGetCommits(t *testing.T) {
},
}
common.UserConfig.Git.MainBranches = scenario.mainBranches
commits, err := builder.GetCommits(scenario.opts)
assert.Equal(t, scenario.expectedCommits, commits)
+5 -62
View File
@@ -9,32 +9,12 @@ import (
)
func TestCommitRewordCommit(t *testing.T) {
type scenario struct {
testName string
runner *oscommands.FakeCmdObjRunner
input string
}
scenarios := []scenario{
{
"Single line reword",
oscommands.NewFakeRunner(t).ExpectGitArgs([]string{"commit", "--allow-empty", "--amend", "--only", "-m", "test"}, "", nil),
"test",
},
{
"Multi line reword",
oscommands.NewFakeRunner(t).ExpectGitArgs([]string{"commit", "--allow-empty", "--amend", "--only", "-m", "test", "-m", "line 2\nline 3"}, "", nil),
"test\nline 2\nline 3",
},
}
for _, s := range scenarios {
s := s
t.Run(s.testName, func(t *testing.T) {
instance := buildCommitCommands(commonDeps{runner: s.runner})
runner := oscommands.NewFakeRunner(t).
ExpectGitArgs([]string{"commit", "--allow-empty", "--amend", "--only", "-m", "test"}, "", nil)
instance := buildCommitCommands(commonDeps{runner: runner})
assert.NoError(t, instance.RewordLastCommit(s.input))
s.runner.CheckForMissingCalls()
})
}
assert.NoError(t, instance.RewordLastCommit("test"))
runner.CheckForMissingCalls()
}
func TestCommitResetToCommit(t *testing.T) {
@@ -294,40 +274,3 @@ Merge pull request #1750 from mark2185/fix-issue-template
})
}
}
func TestGetCommitMessageFromHistory(t *testing.T) {
type scenario struct {
testName string
runner *oscommands.FakeCmdObjRunner
test func(string, error)
}
scenarios := []scenario{
{
"Empty message",
oscommands.NewFakeRunner(t).Expect("git log -1 --skip=2 --pretty=%H", "", nil).Expect("git rev-list --format=%B --max-count=1 ", "", nil),
func(output string, err error) {
assert.Error(t, err)
},
},
{
"Default case to retrieve a commit in history",
oscommands.NewFakeRunner(t).Expect("git log -1 --skip=2 --pretty=%H", "sha3 \n", nil).Expect("git rev-list --format=%B --max-count=1 sha3", `commit sha3
use generics to DRY up context code`, nil),
func(output string, err error) {
assert.NoError(t, err)
assert.Equal(t, "use generics to DRY up context code", output)
},
},
}
for _, s := range scenarios {
s := s
t.Run(s.testName, func(t *testing.T) {
instance := buildCommitCommands(commonDeps{runner: s.runner})
output, err := instance.GetCommitMessageFromHistory(2)
s.test(output, err)
})
}
}
-6
View File
@@ -150,9 +150,3 @@ func buildBranchCommands(deps commonDeps) *BranchCommands {
return NewBranchCommands(gitCommon)
}
func buildFlowCommands(deps commonDeps) *FlowCommands {
gitCommon := buildGitCommon(deps)
return NewFlowCommands(gitCommon)
}
-92
View File
@@ -1,92 +0,0 @@
package git_commands
import (
"testing"
"github.com/jesseduffield/lazygit/pkg/commands/git_config"
"github.com/stretchr/testify/assert"
)
func TestStartCmdObj(t *testing.T) {
scenarios := []struct {
testName string
branchType string
name string
expected string
}{
{
testName: "basic",
branchType: "feature",
name: "test",
expected: "git flow feature start test",
},
}
for _, s := range scenarios {
s := s
t.Run(s.testName, func(t *testing.T) {
instance := buildFlowCommands(commonDeps{})
assert.Equal(t,
instance.StartCmdObj(s.branchType, s.name).ToString(),
s.expected,
)
})
}
}
func TestFinishCmdObj(t *testing.T) {
scenarios := []struct {
testName string
branchName string
expected string
expectedError string
gitConfigMockResponses map[string]string
}{
{
testName: "not a git flow branch",
branchName: "mybranch",
expected: "",
expectedError: "This does not seem to be a git flow branch",
gitConfigMockResponses: nil,
},
{
testName: "feature branch without config",
branchName: "feature/mybranch",
expected: "",
expectedError: "This does not seem to be a git flow branch",
gitConfigMockResponses: nil,
},
{
testName: "feature branch with config",
branchName: "feature/mybranch",
expected: "git flow feature finish mybranch",
expectedError: "",
gitConfigMockResponses: map[string]string{
"--local --get-regexp gitflow.prefix": "gitflow.prefix.feature feature/",
},
},
}
for _, s := range scenarios {
s := s
t.Run(s.testName, func(t *testing.T) {
instance := buildFlowCommands(commonDeps{
gitConfig: git_config.NewFakeGitConfig(s.gitConfigMockResponses),
})
cmd, err := instance.FinishCmdObj(s.branchName)
if s.expectedError != "" {
if err == nil {
t.Errorf("Expected error, got nil")
} else {
assert.Equal(t, err.Error(), s.expectedError)
}
} else {
assert.NoError(t, err)
assert.Equal(t, cmd.ToString(), s.expected)
}
})
}
}
@@ -1,65 +0,0 @@
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
}
@@ -1,56 +0,0 @@
package git_commands
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestGitCommandBuilder(t *testing.T) {
scenarios := []struct {
input string
expected string
}{
{
input: NewGitCmd("push").
Arg("--force-with-lease").
Arg("--set-upstream").
Arg("origin").
Arg("master").
ToString(),
expected: "git push --force-with-lease --set-upstream origin master",
},
{
input: NewGitCmd("push").ArgIf(true, "--test").ToString(),
expected: "git push --test",
},
{
input: NewGitCmd("push").ArgIf(false, "--test").ToString(),
expected: "git push",
},
{
input: NewGitCmd("push").ArgIfElse(true, "-b", "-a").ToString(),
expected: "git push -b",
},
{
input: NewGitCmd("push").ArgIfElse(false, "-a", "-b").ToString(),
expected: "git push -b",
},
{
input: NewGitCmd("push").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)
}
}
+8 -12
View File
@@ -3,9 +3,7 @@ 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"
@@ -105,17 +103,15 @@ func (self *PatchCommands) MovePatchToSelectedCommit(commits []*models.Commit, s
baseIndex := sourceCommitIdx + 1
changes := []daemon.ChangeTodoAction{
{Sha: commits[sourceCommitIdx].Sha, NewAction: todo.Edit},
{Sha: commits[destinationCommitIdx].Sha, NewAction: todo.Edit},
}
self.os.LogCommand(logTodoChanges(changes), false)
todoLines := self.rebase.BuildTodoLines(commits[0:baseIndex], func(commit *models.Commit, i int) string {
if i == sourceCommitIdx || i == destinationCommitIdx {
return "edit"
} else {
return "pick"
}
})
err := self.rebase.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{
baseShaOrRoot: commits[baseIndex].Sha,
overrideEditor: true,
instruction: daemon.NewChangeTodoActionsInstruction(changes),
}).Run()
err := self.rebase.PrepareInteractiveRebaseCommand(commits[baseIndex].Sha, todoLines, true).Run()
if err != nil {
return err
}
+146 -131
View File
@@ -2,17 +2,15 @@ package git_commands
import (
"fmt"
"os"
"path/filepath"
"strings"
"github.com/fsmiamoto/git-todo-parser/todo"
"github.com/go-errors/errors"
"github.com/jesseduffield/generics/slices"
"github.com/jesseduffield/lazygit/pkg/app/daemon"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/samber/lo"
)
type RebaseCommands struct {
@@ -56,16 +54,12 @@ func (self *RebaseCommands) RewordCommit(commits []*models.Commit, index int, me
}
func (self *RebaseCommands) RewordCommitInEditor(commits []*models.Commit, index int) (oscommands.ICmdObj, error) {
changes := []daemon.ChangeTodoAction{{
Sha: commits[index].Sha,
NewAction: todo.Reword,
}}
self.os.LogCommand(logTodoChanges(changes), false)
todo, sha, err := self.BuildSingleActionTodo(commits, index, "reword")
if err != nil {
return nil, err
}
return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{
baseShaOrRoot: getBaseShaOrRoot(commits, index+1),
instruction: daemon.NewChangeTodoActionsInstruction(changes),
}), nil
return self.PrepareInteractiveRebaseCommand(sha, todo, false), nil
}
func (self *RebaseCommands) ResetCommitAuthor(commits []*models.Commit, index int) error {
@@ -81,7 +75,7 @@ func (self *RebaseCommands) SetCommitAuthor(commits []*models.Commit, index int,
}
func (self *RebaseCommands) GenericAmend(commits []*models.Commit, index int, f func() error) error {
if models.IsHeadCommit(commits, index) {
if index == 0 {
// we've selected the top commit so no rebase is required
return f()
}
@@ -101,79 +95,41 @@ func (self *RebaseCommands) GenericAmend(commits []*models.Commit, index int, f
}
func (self *RebaseCommands) MoveCommitDown(commits []*models.Commit, index int) error {
// not appending to original slice so that we don't mutate it
orderedCommits := append([]*models.Commit{}, commits[0:index]...)
orderedCommits = append(orderedCommits, commits[index+1], commits[index])
todoLines := self.BuildTodoLinesSingleAction(orderedCommits, "pick")
baseShaOrRoot := getBaseShaOrRoot(commits, index+2)
sha := commits[index].Sha
self.os.LogCommand(fmt.Sprintf("Moving TODO down: %s", utils.ShortSha(sha)), false)
return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{
baseShaOrRoot: baseShaOrRoot,
instruction: daemon.NewMoveTodoDownInstruction(sha),
overrideEditor: true,
}).Run()
return self.PrepareInteractiveRebaseCommand(baseShaOrRoot, todoLines, true).Run()
}
func (self *RebaseCommands) MoveCommitUp(commits []*models.Commit, index int) error {
baseShaOrRoot := getBaseShaOrRoot(commits, index+1)
sha := commits[index].Sha
self.os.LogCommand(fmt.Sprintf("Moving TODO up: %s", utils.ShortSha(sha)), false)
return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{
baseShaOrRoot: baseShaOrRoot,
instruction: daemon.NewMoveTodoUpInstruction(sha),
overrideEditor: true,
}).Run()
}
func (self *RebaseCommands) InteractiveRebase(commits []*models.Commit, index int, action todo.TodoCommand) error {
baseIndex := index + 1
if action == todo.Squash || action == todo.Fixup {
baseIndex++
func (self *RebaseCommands) InteractiveRebase(commits []*models.Commit, index int, action string) error {
todo, sha, err := self.BuildSingleActionTodo(commits, index, action)
if err != nil {
return err
}
baseShaOrRoot := getBaseShaOrRoot(commits, baseIndex)
changes := []daemon.ChangeTodoAction{{
Sha: commits[index].Sha,
NewAction: action,
}}
self.os.LogCommand(logTodoChanges(changes), false)
return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{
baseShaOrRoot: baseShaOrRoot,
overrideEditor: true,
instruction: daemon.NewChangeTodoActionsInstruction(changes),
}).Run()
return self.PrepareInteractiveRebaseCommand(sha, todo, true).Run()
}
func (self *RebaseCommands) EditRebase(branchRef string) error {
self.os.LogCommand(fmt.Sprintf("Beginning interactive rebase at '%s'", branchRef), false)
return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{
baseShaOrRoot: branchRef,
instruction: daemon.NewInsertBreakInstruction(),
}).Run()
}
func (self *RebaseCommands) InteractiveRebaseBreakAfter(commits []*models.Commit, index int) error {
todo, sha, err := self.BuildSingleActionTodo(commits, index-1, "pick")
if err != nil {
return err
}
func logTodoChanges(changes []daemon.ChangeTodoAction) string {
changeTodoStr := strings.Join(slices.Map(changes, func(c daemon.ChangeTodoAction) string {
return fmt.Sprintf("%s:%s", c.Sha, c.NewAction)
}), "\n")
return fmt.Sprintf("Changing TODO actions: %s", changeTodoStr)
}
type PrepareInteractiveRebaseCommandOpts struct {
baseShaOrRoot string
instruction daemon.Instruction
overrideEditor bool
todo = append(todo, TodoLine{Action: "break", Commit: nil})
return self.PrepareInteractiveRebaseCommand(sha, todo, true).Run()
}
// PrepareInteractiveRebaseCommand returns the cmd for an interactive rebase
// we tell git to run lazygit to edit the todo list, and we pass the client
// lazygit a todo string to write to the todo file
func (self *RebaseCommands) PrepareInteractiveRebaseCommand(opts PrepareInteractiveRebaseCommandOpts) oscommands.ICmdObj {
func (self *RebaseCommands) PrepareInteractiveRebaseCommand(baseShaOrRoot string, todoLines []TodoLine, overrideEditor bool) oscommands.ICmdObj {
todo := self.buildTodo(todoLines)
ex := oscommands.GetLazygitPath()
debug := "FALSE"
@@ -181,81 +137,119 @@ func (self *RebaseCommands) PrepareInteractiveRebaseCommand(opts PrepareInteract
debug = "TRUE"
}
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)
cmdStr := fmt.Sprintf("git rebase --interactive --autostash --keep-empty --empty=keep --no-autosquash %s", baseShaOrRoot)
self.Log.WithField("command", cmdStr).Debug("RunCommand")
cmdObj := self.cmd.New(cmdStr)
gitSequenceEditor := ex
if opts.instruction != nil {
cmdObj.AddEnvVars(daemon.ToEnvVars(opts.instruction)...)
} else {
if todo == "" {
gitSequenceEditor = "true"
} else {
self.os.LogCommand(fmt.Sprintf("Creating TODO file for interactive rebase: \n\n%s", todo), false)
}
cmdObj.AddEnvVars(
daemon.DaemonKindEnvKey+"="+string(daemon.InteractiveRebase),
daemon.RebaseTODOEnvKey+"="+todo,
"DEBUG="+debug,
"LANG=en_US.UTF-8", // Force using EN as language
"LC_ALL=en_US.UTF-8", // Force using EN as language
"GIT_SEQUENCE_EDITOR="+gitSequenceEditor,
)
if opts.overrideEditor {
if overrideEditor {
cmdObj.AddEnvVars("GIT_EDITOR=" + ex)
}
return cmdObj
}
// AmendTo amends the given commit with whatever files are staged
func (self *RebaseCommands) AmendTo(commits []*models.Commit, commitIndex int) error {
commit := commits[commitIndex]
// produces TodoLines where every commit is picked (or dropped for merge commits) except for the commit at the given index, which
// will have the given action applied to it.
func (self *RebaseCommands) BuildSingleActionTodo(commits []*models.Commit, actionIndex int, action string) ([]TodoLine, string, error) {
baseIndex := actionIndex + 1
if action == "squash" || action == "fixup" {
baseIndex++
}
todoLines := self.BuildTodoLines(commits[0:baseIndex], func(commit *models.Commit, i int) string {
if i == actionIndex {
return action
} else if commit.IsMerge() {
// your typical interactive rebase will actually drop merge commits by default. Damn git CLI, you scary!
// doing this means we don't need to worry about rebasing over merges which always causes problems.
// you typically shouldn't be doing rebases that pass over merge commits anyway.
return "drop"
} else {
return "pick"
}
})
baseShaOrRoot := getBaseShaOrRoot(commits, baseIndex)
return todoLines, baseShaOrRoot, nil
}
// AmendTo amends the given commit with whatever files are staged
func (self *RebaseCommands) AmendTo(commit *models.Commit) error {
if err := self.commit.CreateFixupCommit(commit.Sha); err != nil {
return err
}
// Get the sha of the commit we just created
fixupSha, err := self.cmd.New("git rev-parse --verify HEAD").RunWithOutput()
return self.SquashAllAboveFixupCommits(commit)
}
// EditRebaseTodo sets the action at a given index in the git-rebase-todo file
func (self *RebaseCommands) EditRebaseTodo(index int, action string) error {
fileName := filepath.Join(self.dotGitDir, "rebase-merge/git-rebase-todo")
bytes, err := os.ReadFile(fileName)
if err != nil {
return err
}
return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{
baseShaOrRoot: getBaseShaOrRoot(commits, commitIndex+1),
overrideEditor: true,
instruction: daemon.NewMoveFixupCommitDownInstruction(commit.Sha, fixupSha),
}).Run()
content := strings.Split(string(bytes), "\n")
commitCount := self.getTodoCommitCount(content)
// we have the most recent commit at the bottom whereas the todo file has
// it at the bottom, so we need to subtract our index from the commit count
contentIndex := commitCount - 1 - index
splitLine := strings.Split(content[contentIndex], " ")
content[contentIndex] = action + " " + strings.Join(splitLine[1:], " ")
result := strings.Join(content, "\n")
return os.WriteFile(fileName, []byte(result), 0o644)
}
// EditRebaseTodo sets the action for a given rebase commit in the git-rebase-todo file
func (self *RebaseCommands) EditRebaseTodo(commit *models.Commit, action todo.TodoCommand) error {
return utils.EditRebaseTodo(
filepath.Join(self.dotGitDir, "rebase-merge/git-rebase-todo"), commit.Sha, commit.Action, action)
func (self *RebaseCommands) getTodoCommitCount(content []string) int {
// count lines that are not blank and are not comments
commitCount := 0
for _, line := range content {
if line != "" && !strings.HasPrefix(line, "#") {
commitCount++
}
}
return commitCount
}
// MoveTodoDown moves a rebase todo item down by one position
func (self *RebaseCommands) MoveTodoDown(commit *models.Commit) error {
func (self *RebaseCommands) MoveTodoDown(index int) error {
fileName := filepath.Join(self.dotGitDir, "rebase-merge/git-rebase-todo")
return utils.MoveTodoDown(fileName, commit.Sha, commit.Action)
}
bytes, err := os.ReadFile(fileName)
if err != nil {
return err
}
// MoveTodoDown moves a rebase todo item down by one position
func (self *RebaseCommands) MoveTodoUp(commit *models.Commit) error {
fileName := filepath.Join(self.dotGitDir, "rebase-merge/git-rebase-todo")
return utils.MoveTodoUp(fileName, commit.Sha, commit.Action)
content := strings.Split(string(bytes), "\n")
commitCount := self.getTodoCommitCount(content)
contentIndex := commitCount - 1 - index
rearrangedContent := append(content[0:contentIndex-1], content[contentIndex], content[contentIndex-1])
rearrangedContent = append(rearrangedContent, content[contentIndex+1:]...)
result := strings.Join(rearrangedContent, "\n")
return os.WriteFile(fileName, []byte(result), 0o644)
}
// SquashAllAboveFixupCommits squashes all fixup! commits above the given one
@@ -289,22 +283,17 @@ func (self *RebaseCommands) BeginInteractiveRebaseForCommit(commits []*models.Co
return errors.New(self.Tr.DisabledForGPG)
}
changes := []daemon.ChangeTodoAction{{
Sha: commits[commitIndex].Sha,
NewAction: todo.Edit,
}}
self.os.LogCommand(logTodoChanges(changes), false)
todo, sha, err := self.BuildSingleActionTodo(commits, commitIndex, "edit")
if err != nil {
return err
}
return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{
baseShaOrRoot: getBaseShaOrRoot(commits, commitIndex+1),
overrideEditor: true,
instruction: daemon.NewChangeTodoActionsInstruction(changes),
}).Run()
return self.PrepareInteractiveRebaseCommand(sha, todo, true).Run()
}
// RebaseBranch interactive rebases onto a branch
func (self *RebaseCommands) RebaseBranch(branchName string) error {
return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{baseShaOrRoot: branchName}).Run()
return self.PrepareInteractiveRebaseCommand(branchName, nil, false).Run()
}
func (self *RebaseCommands) GenericMergeOrRebaseActionCmdObj(commandType string, command string) oscommands.ICmdObj {
@@ -345,16 +334,15 @@ func (self *RebaseCommands) GenericMergeOrRebaseAction(commandType string, comma
}
func (self *RebaseCommands) runSkipEditorCommand(cmdObj oscommands.ICmdObj) error {
instruction := daemon.NewExitImmediatelyInstruction()
lazyGitPath := oscommands.GetLazygitPath()
return cmdObj.
AddEnvVars(
daemon.DaemonKindEnvKey+"="+string(daemon.ExitImmediately),
"GIT_EDITOR="+lazyGitPath,
"GIT_SEQUENCE_EDITOR="+lazyGitPath,
"EDITOR="+lazyGitPath,
"VISUAL="+lazyGitPath,
).
AddEnvVars(daemon.ToEnvVars(instruction)...).
Run()
}
@@ -388,15 +376,42 @@ func (self *RebaseCommands) DiscardOldFileChanges(commits []*models.Commit, comm
// CherryPickCommits begins an interactive rebase with the given shas being cherry picked onto HEAD
func (self *RebaseCommands) CherryPickCommits(commits []*models.Commit) error {
commitLines := lo.Map(commits, func(commit *models.Commit, _ int) string {
return fmt.Sprintf("%s %s", utils.ShortSha(commit.Sha), commit.Name)
})
self.os.LogCommand(fmt.Sprintf("Cherry-picking commits:\n%s", strings.Join(commitLines, "\n")), false)
todoLines := self.BuildTodoLinesSingleAction(commits, "pick")
return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{
baseShaOrRoot: "HEAD",
instruction: daemon.NewCherryPickCommitsInstruction(commits),
}).Run()
return self.PrepareInteractiveRebaseCommand("HEAD", todoLines, false).Run()
}
func (self *RebaseCommands) buildTodo(todoLines []TodoLine) string {
lines := slices.Map(todoLines, func(todoLine TodoLine) string {
return todoLine.ToString()
})
return strings.Join(slices.Reverse(lines), "")
}
func (self *RebaseCommands) BuildTodoLines(commits []*models.Commit, f func(*models.Commit, int) string) []TodoLine {
return slices.MapWithIndex(commits, func(commit *models.Commit, i int) TodoLine {
return TodoLine{Action: f(commit, i), Commit: commit}
})
}
func (self *RebaseCommands) BuildTodoLinesSingleAction(commits []*models.Commit, action string) []TodoLine {
return self.BuildTodoLines(commits, func(commit *models.Commit, i int) string {
return action
})
}
type TodoLine struct {
Action string
Commit *models.Commit
}
func (self *TodoLine) ToString() string {
if self.Action == "break" {
return self.Action + "\n"
} else {
return self.Action + " " + self.Commit.Sha + " " + self.Commit.Name + "\n"
}
}
// we can't start an interactive rebase from the first commit without passing the
+15 -40
View File
@@ -2,7 +2,6 @@ package git_commands
import (
"regexp"
"strconv"
"testing"
"github.com/go-errors/errors"
@@ -16,60 +15,37 @@ import (
func TestRebaseRebaseBranch(t *testing.T) {
type scenario struct {
testName string
arg string
gitVersion *GitVersion
runner *oscommands.FakeCmdObjRunner
test func(error)
testName string
arg string
runner *oscommands.FakeCmdObjRunner
test func(error)
}
scenarios := []scenario{
{
testName: "successful rebase",
arg: "master",
gitVersion: &GitVersion{2, 26, 0, ""},
testName: "successful rebase",
arg: "master",
runner: oscommands.NewFakeRunner(t).
Expect(`git rebase --interactive --autostash --keep-empty --empty=keep --no-autosquash --rebase-merges master`, "", nil),
Expect(`git rebase --interactive --autostash --keep-empty --empty=keep --no-autosquash master`, "", nil),
test: func(err error) {
assert.NoError(t, err)
},
},
{
testName: "unsuccessful rebase",
arg: "master",
gitVersion: &GitVersion{2, 26, 0, ""},
testName: "unsuccessful rebase",
arg: "master",
runner: oscommands.NewFakeRunner(t).
Expect(`git rebase --interactive --autostash --keep-empty --empty=keep --no-autosquash --rebase-merges master`, "", errors.New("error")),
Expect(`git rebase --interactive --autostash --keep-empty --empty=keep --no-autosquash master`, "", errors.New("error")),
test: func(err error) {
assert.Error(t, err)
},
},
{
testName: "successful rebase (< 2.26.0)",
arg: "master",
gitVersion: &GitVersion{2, 25, 5, ""},
runner: oscommands.NewFakeRunner(t).
Expect(`git rebase --interactive --autostash --keep-empty --no-autosquash --rebase-merges master`, "", nil),
test: func(err error) {
assert.NoError(t, err)
},
},
{
testName: "successful rebase (< 2.22.0)",
arg: "master",
gitVersion: &GitVersion{2, 21, 9, ""},
runner: oscommands.NewFakeRunner(t).
Expect(`git rebase --interactive --autostash --keep-empty --no-autosquash master`, "", nil),
test: func(err error) {
assert.NoError(t, err)
},
},
}
for _, s := range scenarios {
s := s
t.Run(s.testName, func(t *testing.T) {
instance := buildRebaseCommands(commonDeps{runner: s.runner, gitVersion: s.gitVersion})
instance := buildRebaseCommands(commonDeps{runner: s.runner})
s.test(instance.RebaseBranch(s.arg))
})
}
@@ -87,7 +63,7 @@ func TestRebaseSkipEditorCommand(t *testing.T) {
`^EDITOR=.*$`,
`^GIT_EDITOR=.*$`,
`^GIT_SEQUENCE_EDITOR=.*$`,
"^" + daemon.DaemonKindEnvKey + "=" + strconv.Itoa(int(daemon.DaemonKindExitImmediately)) + "$",
"^" + daemon.DaemonKindEnvKey + "=" + string(daemon.ExitImmediately) + "$",
} {
regexStr := regexStr
foundMatch := lo.ContainsBy(envVars, func(envVar string) bool {
@@ -149,7 +125,7 @@ func TestRebaseDiscardOldFileChanges(t *testing.T) {
commitIndex: 0,
fileName: "test999.txt",
runner: oscommands.NewFakeRunner(t).
Expect(`git rebase --interactive --autostash --keep-empty --empty=keep --no-autosquash --rebase-merges abcdef`, "", nil).
Expect(`git rebase --interactive --autostash --keep-empty --empty=keep --no-autosquash abcdef`, "", nil).
Expect(`git cat-file -e HEAD^:"test999.txt"`, "", nil).
Expect(`git checkout HEAD^ -- "test999.txt"`, "", nil).
Expect(`git commit --amend --no-edit --allow-empty`, "", nil).
@@ -166,9 +142,8 @@ func TestRebaseDiscardOldFileChanges(t *testing.T) {
s := s
t.Run(s.testName, func(t *testing.T) {
instance := buildRebaseCommands(commonDeps{
runner: s.runner,
gitVersion: &GitVersion{2, 26, 0, ""},
gitConfig: git_config.NewFakeGitConfig(s.gitConfigMockResponses),
runner: s.runner,
gitConfig: git_config.NewFakeGitConfig(s.gitConfigMockResponses),
})
s.test(instance.DiscardOldFileChanges(s.commits, s.commitIndex, s.fileName))
@@ -52,7 +52,7 @@ func (self *ReflogCommitLoader) GetReflogCommits(lastReflogCommit *models.Commit
Sha: fields[0],
Name: fields[2],
UnixTimestamp: int64(unixTimestamp),
Status: models.StatusReflog,
Status: "reflog",
Parents: parents,
}
@@ -51,35 +51,35 @@ func TestGetReflogCommits(t *testing.T) {
{
Sha: "c3c4b66b64c97ffeecde",
Name: "checkout: moving from A to B",
Status: models.StatusReflog,
Status: "reflog",
UnixTimestamp: 1643150483,
Parents: []string{"51baa8c1"},
},
{
Sha: "c3c4b66b64c97ffeecde",
Name: "checkout: moving from B to A",
Status: models.StatusReflog,
Status: "reflog",
UnixTimestamp: 1643150483,
Parents: []string{"51baa8c1"},
},
{
Sha: "c3c4b66b64c97ffeecde",
Name: "checkout: moving from A to B",
Status: models.StatusReflog,
Status: "reflog",
UnixTimestamp: 1643150483,
Parents: []string{"51baa8c1"},
},
{
Sha: "c3c4b66b64c97ffeecde",
Name: "checkout: moving from master to A",
Status: models.StatusReflog,
Status: "reflog",
UnixTimestamp: 1643150483,
Parents: []string{"51baa8c1"},
},
{
Sha: "f4ddf2f0d4be4ccc7efa",
Name: "checkout: moving from A to master",
Status: models.StatusReflog,
Status: "reflog",
UnixTimestamp: 1643149435,
Parents: []string{"51baa8c1"},
},
@@ -95,7 +95,7 @@ func TestGetReflogCommits(t *testing.T) {
lastReflogCommit: &models.Commit{
Sha: "c3c4b66b64c97ffeecde",
Name: "checkout: moving from B to A",
Status: models.StatusReflog,
Status: "reflog",
UnixTimestamp: 1643150483,
Parents: []string{"51baa8c1"},
},
@@ -103,7 +103,7 @@ func TestGetReflogCommits(t *testing.T) {
{
Sha: "c3c4b66b64c97ffeecde",
Name: "checkout: moving from A to B",
Status: models.StatusReflog,
Status: "reflog",
UnixTimestamp: 1643150483,
Parents: []string{"51baa8c1"},
},
@@ -119,7 +119,7 @@ func TestGetReflogCommits(t *testing.T) {
lastReflogCommit: &models.Commit{
Sha: "c3c4b66b64c97ffeecde",
Name: "checkout: moving from B to A",
Status: models.StatusReflog,
Status: "reflog",
UnixTimestamp: 1643150483,
Parents: []string{"51baa8c1"},
},
@@ -128,7 +128,7 @@ func TestGetReflogCommits(t *testing.T) {
{
Sha: "c3c4b66b64c97ffeecde",
Name: "checkout: moving from A to B",
Status: models.StatusReflog,
Status: "reflog",
UnixTimestamp: 1643150483,
Parents: []string{"51baa8c1"},
},
+2 -13
View File
@@ -59,19 +59,8 @@ func (self *StashCommands) Sha(index int) (string, error) {
return strings.Trim(sha, "\r\n"), err
}
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,
)
func (self *StashCommands) ShowStashEntryCmdObj(index int) oscommands.ICmdObj {
cmdStr := fmt.Sprintf("git stash show -p --stat --color=%s --unified=%d stash@{%d}", self.UserConfig.Git.Paging.ColorArg, self.UserConfig.Git.DiffContextSize, index)
return self.cmd.New(cmdStr).DontLog()
}
+13 -23
View File
@@ -99,34 +99,24 @@ func TestStashSha(t *testing.T) {
func TestStashStashEntryCmdObj(t *testing.T) {
type scenario struct {
testName string
index int
contextSize int
ignoreWhitespace bool
expected string
testName string
index int
contextSize int
expected string
}
scenarios := []scenario{
{
testName: "Default case",
index: 5,
contextSize: 3,
ignoreWhitespace: false,
expected: "git stash show -p --stat --color=always --unified=3 stash@{5}",
testName: "Default case",
index: 5,
contextSize: 3,
expected: "git stash show -p --stat --color=always --unified=3 stash@{5}",
},
{
testName: "Show diff with custom context size",
index: 5,
contextSize: 77,
ignoreWhitespace: false,
expected: "git stash show -p --stat --color=always --unified=77 stash@{5}",
},
{
testName: "Default case",
index: 5,
contextSize: 3,
ignoreWhitespace: true,
expected: "git stash show -p --stat --color=always --unified=3 --ignore-all-space stash@{5}",
testName: "Show diff with custom context size",
index: 5,
contextSize: 77,
expected: "git stash show -p --stat --color=always --unified=77 stash@{5}",
},
}
@@ -137,7 +127,7 @@ func TestStashStashEntryCmdObj(t *testing.T) {
userConfig.Git.DiffContextSize = s.contextSize
instance := buildStashCommands(commonDeps{userConfig: userConfig})
cmdStr := instance.ShowStashEntryCmdObj(s.index, s.ignoreWhitespace).ToString()
cmdStr := instance.ShowStashEntryCmdObj(s.index).ToString()
assert.Equal(t, s.expected, cmdStr)
})
}
+5 -1
View File
@@ -29,7 +29,11 @@ func (self *SyncCommands) PushCmdObj(opts PushOpts) (oscommands.ICmdObj, error)
cmdStr := "git push"
if opts.Force {
cmdStr += " --force-with-lease"
if self.version.IsOlderThan(2, 30, 0) {
cmdStr += " --force-with-lease"
} else {
cmdStr += " --force-with-lease --force-if-includes"
}
}
if opts.SetUpstream {
+31 -1
View File
@@ -10,6 +10,7 @@ import (
func TestSyncPush(t *testing.T) {
type scenario struct {
testName string
version *GitVersion
opts PushOpts
test func(oscommands.ICmdObj, error)
}
@@ -17,6 +18,7 @@ func TestSyncPush(t *testing.T) {
scenarios := []scenario{
{
testName: "Push with force disabled",
version: &GitVersion{2, 29, 3, ""},
opts: PushOpts{Force: false},
test: func(cmdObj oscommands.ICmdObj, err error) {
assert.Equal(t, cmdObj.ToString(), "git push")
@@ -25,14 +27,25 @@ func TestSyncPush(t *testing.T) {
},
{
testName: "Push with force enabled",
version: &GitVersion{2, 29, 3, ""},
opts: PushOpts{Force: true},
test: func(cmdObj oscommands.ICmdObj, err error) {
assert.Equal(t, cmdObj.ToString(), "git push --force-with-lease")
assert.NoError(t, err)
},
},
{
testName: "Push with force enabled (>= 2.30.0)",
version: &GitVersion{2, 30, 0, ""},
opts: PushOpts{Force: true},
test: func(cmdObj oscommands.ICmdObj, err error) {
assert.Equal(t, cmdObj.ToString(), "git push --force-with-lease --force-if-includes")
assert.NoError(t, err)
},
},
{
testName: "Push with force disabled, upstream supplied",
version: &GitVersion{2, 29, 3, ""},
opts: PushOpts{
Force: false,
UpstreamRemote: "origin",
@@ -45,6 +58,7 @@ func TestSyncPush(t *testing.T) {
},
{
testName: "Push with force disabled, setting upstream",
version: &GitVersion{2, 29, 3, ""},
opts: PushOpts{
Force: false,
UpstreamRemote: "origin",
@@ -58,6 +72,7 @@ func TestSyncPush(t *testing.T) {
},
{
testName: "Push with force enabled, setting upstream",
version: &GitVersion{2, 29, 3, ""},
opts: PushOpts{
Force: true,
UpstreamRemote: "origin",
@@ -69,8 +84,23 @@ func TestSyncPush(t *testing.T) {
assert.NoError(t, err)
},
},
{
testName: "Push with force enabled, setting upstream (>= 2.30.0)",
version: &GitVersion{2, 30, 0, ""},
opts: PushOpts{
Force: true,
UpstreamRemote: "origin",
UpstreamBranch: "master",
SetUpstream: true,
},
test: func(cmdObj oscommands.ICmdObj, err error) {
assert.Equal(t, cmdObj.ToString(), `git push --force-with-lease --force-if-includes --set-upstream "origin" "master"`)
assert.NoError(t, err)
},
},
{
testName: "Push with remote branch but no origin",
version: &GitVersion{2, 29, 3, ""},
opts: PushOpts{
Force: true,
UpstreamRemote: "",
@@ -87,7 +117,7 @@ func TestSyncPush(t *testing.T) {
for _, s := range scenarios {
s := s
t.Run(s.testName, func(t *testing.T) {
instance := buildSyncCommands(commonDeps{})
instance := buildSyncCommands(commonDeps{gitVersion: s.version})
s.test(instance.PushCmdObj(s.opts))
})
}
+1 -1
View File
@@ -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 tag %s", self.cmd.Quote(remoteName), self.cmd.Quote(tagName))).PromptOnCredentialRequest().WithMutex(self.syncMutex).Run()
return self.cmd.New(fmt.Sprintf("git push %s %s", self.cmd.Quote(remoteName), self.cmd.Quote(tagName))).PromptOnCredentialRequest().WithMutex(self.syncMutex).Run()
}
+1 -5
View File
@@ -32,7 +32,7 @@ func ParseGitVersion(versionStr string) (*GitVersion, error) {
// versionStr should be something like:
// git version 2.39.0
// git version 2.37.1 (Apple Git-137.1)
re := regexp.MustCompile(`[^\d]*(\d+)(\.\d+)?(\.\d+)?(.*)`)
re := regexp.MustCompile(`[^\d]+(\d+)(\.\d+)?(\.\d+)?(.*)`)
matches := re.FindStringSubmatch(versionStr)
if len(matches) < 5 {
@@ -65,7 +65,3 @@ func (v *GitVersion) IsOlderThan(major, minor, patch int) bool {
required := major*1000*1000 + minor*1000 + patch
return actual < required
}
func (v *GitVersion) IsOlderThanVersion(version *GitVersion) bool {
return v.IsOlderThan(version.Major, version.Minor, version.Patch)
}
@@ -64,22 +64,12 @@ var bitbucketServerServiceDef = ServiceDefinition{
repoURLTemplate: "https://{{.webDomain}}/projects/{{.project}}/repos/{{.repo}}",
}
var giteaServiceDef = ServiceDefinition{
provider: "gitea",
pullRequestURLIntoDefaultBranch: "/compare/{{.From}}",
pullRequestURLIntoTargetBranch: "/compare/{{.To}}...{{.From}}",
commitURL: "/commit/{{.CommitSha}}",
regexStrings: defaultUrlRegexStrings,
repoURLTemplate: defaultRepoURLTemplate,
}
var serviceDefinitions = []ServiceDefinition{
githubServiceDef,
bitbucketServiceDef,
gitLabServiceDef,
azdoServiceDef,
bitbucketServerServiceDef,
giteaServiceDef,
}
var defaultServiceDomains = []ServiceDomain{
@@ -103,9 +93,4 @@ var defaultServiceDomains = []ServiceDomain{
gitDomain: "dev.azure.com",
webDomain: "dev.azure.com",
},
{
serviceDefinition: giteaServiceDef,
gitDomain: "try.gitea.io",
webDomain: "try.gitea.io",
},
}
@@ -13,7 +13,7 @@ import (
"github.com/jesseduffield/generics/slices"
)
// This package is for handling logic specific to a git hosting service like github, gitlab, bitbucket, gitea, etc.
// This package is for handling logic specific to a git hosting service like github, gitlab, bitbucket, etc.
// Different git hosting services have different URL formats for when you want to open a PR or view a commit,
// and this package's responsibility is to determine which service you're using based on the remote URL,
// and then which URL you need for whatever use case you have.
@@ -264,60 +264,6 @@ func TestGetPullRequestURL(t *testing.T) {
assert.Equal(t, "https://mycompany.bitbucket.com/projects/myproject/repos/myrepo/pull-requests?create&targetBranch=dev&sourceBranch=feature%2Fnew", url)
},
},
{
testName: "Opens a link to new pull request on Gitea Server (SSH)",
from: "feature/new",
remoteUrl: "ssh://git@mycompany.gitea.io/myproject/myrepo.git",
configServiceDomains: map[string]string{
// valid configuration for a gitea server URL
"mycompany.gitea.io": "gitea:mycompany.gitea.io",
},
test: func(url string, err error) {
assert.NoError(t, err)
assert.Equal(t, "https://mycompany.gitea.io/myproject/myrepo/compare/feature%2Fnew", url)
},
},
{
testName: "Opens a link to new pull request on Gitea Server (SSH) with specific target",
from: "feature/new",
to: "dev",
remoteUrl: "ssh://git@mycompany.gitea.io/myproject/myrepo.git",
configServiceDomains: map[string]string{
// valid configuration for a gitea server URL
"mycompany.gitea.io": "gitea:mycompany.gitea.io",
},
test: func(url string, err error) {
assert.NoError(t, err)
assert.Equal(t, "https://mycompany.gitea.io/myproject/myrepo/compare/dev...feature%2Fnew", url)
},
},
{
testName: "Opens a link to new pull request on Gitea Server (HTTP)",
from: "feature/new",
remoteUrl: "https://mycompany.gitea.io/myproject/myrepo.git",
configServiceDomains: map[string]string{
// valid configuration for a gitea server URL
"mycompany.gitea.io": "gitea:mycompany.gitea.io",
},
test: func(url string, err error) {
assert.NoError(t, err)
assert.Equal(t, "https://mycompany.gitea.io/myproject/myrepo/compare/feature%2Fnew", url)
},
},
{
testName: "Opens a link to new pull request on Gitea Server (HTTP) with specific target",
from: "feature/new",
to: "dev",
remoteUrl: "https://mycompany.gitea.io/myproject/myrepo.git",
configServiceDomains: map[string]string{
// valid configuration for a gitea server URL
"mycompany.gitea.io": "gitea:mycompany.gitea.io",
},
test: func(url string, err error) {
assert.NoError(t, err)
assert.Equal(t, "https://mycompany.gitea.io/myproject/myrepo/compare/dev...feature%2Fnew", url)
},
},
{
testName: "Throws an error if git service is unsupported",
from: "feature/divide-operation",
@@ -364,7 +310,7 @@ func TestGetPullRequestURL(t *testing.T) {
assert.NoError(t, err)
assert.Equal(t, "https://bitbucket.org/johndoe/social_network/pull-requests/new?source=feature%2Fprofile-page&t=1", url)
},
expectedLoggedErrors: []string{"Unknown git service type: 'noservice'. Expected one of github, bitbucket, gitlab, azuredevops, bitbucketServer, gitea"},
expectedLoggedErrors: []string{"Unknown git service type: 'noservice'. Expected one of github, bitbucket, gitlab, azuredevops, bitbucketServer"},
},
{
testName: "Escapes reserved URL characters in from branch name",
+3 -22
View File
@@ -3,37 +3,18 @@ package models
import (
"fmt"
"github.com/fsmiamoto/git-todo-parser/todo"
"github.com/jesseduffield/lazygit/pkg/utils"
)
// Special commit hash for empty tree object
const EmptyTreeCommitHash = "4b825dc642cb6eb9a060e54bf8d69288fbee4904"
type CommitStatus int
const (
StatusNone CommitStatus = iota
StatusUnpushed
StatusPushed
StatusMerged
StatusRebasing
StatusSelected
StatusReflog
)
const (
// Conveniently for us, the todo package starts the enum at 1, and given
// that it doesn't have a "none" value, we're setting ours to 0
ActionNone todo.TodoCommand = 0
)
// Commit : A git commit
type Commit struct {
Sha string
Name string
Status CommitStatus
Action todo.TodoCommand
Status string // one of "unpushed", "pushed", "merged", "rebasing" or "selected"
Action string // one of "", "pick", "edit", "squash", "reword", "drop", "fixup"
Tags []string
ExtraInfo string // something like 'HEAD -> master, tag: v0.15.2'
AuthorName string // something like 'Jesse Duffield'
@@ -82,7 +63,7 @@ func (c *Commit) IsMerge() bool {
// returns true if this commit is not actually in the git log but instead
// is from a TODO file for an interactive rebase.
func (c *Commit) IsTODO() bool {
return c.Action != ActionNone
return c.Action != ""
}
func IsHeadCommit(commits []*Commit, index int) bool {
+5 -8
View File
@@ -35,15 +35,13 @@ type editPreset struct {
editInTerminal bool
}
// IF YOU ADD A PRESET TO THIS FUNCTION YOU MUST UPDATE THE `Supported presets` SECTION OF docs/Config.md
func getPreset(osConfig *OSConfig, guessDefaultEditor func() string) *editPreset {
presets := map[string]*editPreset{
"vi": standardTerminalEditorPreset("vi"),
"vim": standardTerminalEditorPreset("vim"),
"nvim": standardTerminalEditorPreset("nvim"),
"emacs": standardTerminalEditorPreset("emacs"),
"nano": standardTerminalEditorPreset("nano"),
"kakoune": standardTerminalEditorPreset("kakoune"),
"vi": standardTerminalEditorPreset("vi"),
"vim": standardTerminalEditorPreset("vim"),
"nvim": standardTerminalEditorPreset("nvim"),
"emacs": standardTerminalEditorPreset("emacs"),
"nano": standardTerminalEditorPreset("nano"),
"vscode": {
editTemplate: "code --reuse-window -- {{filename}}",
editAtLineTemplate: "code --reuse-window --goto -- {{filename}}:{{line}}",
@@ -72,7 +70,6 @@ func getPreset(osConfig *OSConfig, guessDefaultEditor func() string) *editPreset
// Some of our presets have a different name than the editor they are using.
editorToPreset := map[string]string{
"kak": "kakoune",
"code": "vscode",
"subl": "sublime",
"xed": "xcode",
+40 -44
View File
@@ -27,33 +27,32 @@ type RefresherConfig struct {
}
type GuiConfig struct {
AuthorColors map[string]string `yaml:"authorColors"`
BranchColors map[string]string `yaml:"branchColors"`
ScrollHeight int `yaml:"scrollHeight"`
ScrollPastBottom bool `yaml:"scrollPastBottom"`
MouseEvents bool `yaml:"mouseEvents"`
SkipUnstageLineWarning bool `yaml:"skipUnstageLineWarning"`
SkipStashWarning bool `yaml:"skipStashWarning"`
SidePanelWidth float64 `yaml:"sidePanelWidth"`
ExpandFocusedSidePanel bool `yaml:"expandFocusedSidePanel"`
MainPanelSplitMode string `yaml:"mainPanelSplitMode"`
Language string `yaml:"language"`
TimeFormat string `yaml:"timeFormat"`
Theme ThemeConfig `yaml:"theme"`
CommitLength CommitLengthConfig `yaml:"commitLength"`
SkipNoStagedFilesWarning bool `yaml:"skipNoStagedFilesWarning"`
ShowListFooter bool `yaml:"showListFooter"`
ShowFileTree bool `yaml:"showFileTree"`
ShowRandomTip bool `yaml:"showRandomTip"`
ShowCommandLog bool `yaml:"showCommandLog"`
ShowBottomLine bool `yaml:"showBottomLine"`
ShowIcons bool `yaml:"showIcons"`
ExperimentalShowBranchHeads bool `yaml:"experimentalShowBranchHeads"`
CommandLogSize int `yaml:"commandLogSize"`
SplitDiff string `yaml:"splitDiff"`
SkipRewordInEditorWarning bool `yaml:"skipRewordInEditorWarning"`
WindowSize string `yaml:"windowSize"`
Border string `yaml:"border"`
AuthorColors map[string]string `yaml:"authorColors"`
BranchColors map[string]string `yaml:"branchColors"`
ScrollHeight int `yaml:"scrollHeight"`
ScrollPastBottom bool `yaml:"scrollPastBottom"`
MouseEvents bool `yaml:"mouseEvents"`
SkipUnstageLineWarning bool `yaml:"skipUnstageLineWarning"`
SkipStashWarning bool `yaml:"skipStashWarning"`
SidePanelWidth float64 `yaml:"sidePanelWidth"`
ExpandFocusedSidePanel bool `yaml:"expandFocusedSidePanel"`
MainPanelSplitMode string `yaml:"mainPanelSplitMode"`
Language string `yaml:"language"`
TimeFormat string `yaml:"timeFormat"`
Theme ThemeConfig `yaml:"theme"`
CommitLength CommitLengthConfig `yaml:"commitLength"`
SkipNoStagedFilesWarning bool `yaml:"skipNoStagedFilesWarning"`
ShowListFooter bool `yaml:"showListFooter"`
ShowFileTree bool `yaml:"showFileTree"`
ShowRandomTip bool `yaml:"showRandomTip"`
ShowCommandLog bool `yaml:"showCommandLog"`
ShowBottomLine bool `yaml:"showBottomLine"`
ShowIcons bool `yaml:"showIcons"`
CommandLogSize int `yaml:"commandLogSize"`
SplitDiff string `yaml:"splitDiff"`
SkipRewordInEditorWarning bool `yaml:"skipRewordInEditorWarning"`
WindowSize string `yaml:"windowSize"`
Border string `yaml:"border"`
}
type ThemeConfig struct {
@@ -76,7 +75,6 @@ type GitConfig struct {
Paging PagingConfig `yaml:"paging"`
Commit CommitConfig `yaml:"commit"`
Merging MergingConfig `yaml:"merging"`
MainBranches []string `yaml:"mainBranches"`
SkipHookPrefix string `yaml:"skipHookPrefix"`
AutoFetch bool `yaml:"autoFetch"`
AutoRefresh bool `yaml:"autoRefresh"`
@@ -167,7 +165,6 @@ type KeybindingUniversalConfig struct {
Select string `yaml:"select"`
GoInto string `yaml:"goInto"`
Confirm string `yaml:"confirm"`
ConfirmInEditor string `yaml:"confirmInEditor"`
Remove string `yaml:"remove"`
New string `yaml:"new"`
Edit string `yaml:"edit"`
@@ -196,6 +193,7 @@ type KeybindingUniversalConfig struct {
CopyToClipboard string `yaml:"copyToClipboard"`
OpenRecentRepos string `yaml:"openRecentRepos"`
SubmitEditorText string `yaml:"submitEditorText"`
AppendNewline string `yaml:"appendNewline"`
ExtrasMenu string `yaml:"extrasMenu"`
ToggleWhitespaceInDiffView string `yaml:"toggleWhitespaceInDiffView"`
IncreaseContextInDiffView string `yaml:"increaseContextInDiffView"`
@@ -410,19 +408,18 @@ func GetDefaultConfig() *UserConfig {
UnstagedChangesColor: []string{"red"},
DefaultFgColor: []string{"default"},
},
CommitLength: CommitLengthConfig{Show: true},
SkipNoStagedFilesWarning: false,
ShowListFooter: true,
ShowCommandLog: true,
ShowBottomLine: true,
ShowFileTree: true,
ShowRandomTip: true,
ShowIcons: false,
ExperimentalShowBranchHeads: false,
CommandLogSize: 8,
SplitDiff: "auto",
SkipRewordInEditorWarning: false,
Border: "single",
CommitLength: CommitLengthConfig{Show: true},
SkipNoStagedFilesWarning: false,
ShowListFooter: true,
ShowCommandLog: true,
ShowBottomLine: true,
ShowFileTree: true,
ShowRandomTip: true,
ShowIcons: false,
CommandLogSize: 8,
SplitDiff: "auto",
SkipRewordInEditorWarning: false,
Border: "single",
},
Git: GitConfig{
Paging: PagingConfig{
@@ -444,7 +441,6 @@ func GetDefaultConfig() *UserConfig {
ShowWholeGraph: false,
},
SkipHookPrefix: "WIP",
MainBranches: []string{"master", "main"},
AutoFetch: true,
AutoRefresh: true,
BranchLogCmd: "git log --graph --color=always --abbrev-commit --decorate --date=relative --pretty=medium {{branchName}} --",
@@ -496,7 +492,6 @@ func GetDefaultConfig() *UserConfig {
Select: "<space>",
GoInto: "<enter>",
Confirm: "<enter>",
ConfirmInEditor: "<a-enter>",
Remove: "d",
New: "n",
Edit: "e",
@@ -525,6 +520,7 @@ func GetDefaultConfig() *UserConfig {
DiffingMenuAlt: "<c-e>",
CopyToClipboard: "<c-o>",
SubmitEditorText: "<enter>",
AppendNewline: "<a-enter>",
ExtrasMenu: "@",
ToggleWhitespaceInDiffView: "<c-w>",
IncreaseContextInDiffView: "}",
+131
View File
@@ -0,0 +1,131 @@
package gui
import (
"time"
"github.com/jesseduffield/generics/slices"
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/sasha-s/go-deadlock"
)
// statusManager's job is to handle rendering of loading states and toast notifications
// that you see at the bottom left of the screen.
type statusManager struct {
statuses []appStatus
nextId int
mutex deadlock.Mutex
}
type appStatus struct {
message string
statusType string
id int
}
func (m *statusManager) removeStatus(id int) {
m.mutex.Lock()
defer m.mutex.Unlock()
m.statuses = slices.Filter(m.statuses, func(status appStatus) bool {
return status.id != id
})
}
func (m *statusManager) addWaitingStatus(message string) int {
m.mutex.Lock()
defer m.mutex.Unlock()
m.nextId += 1
id := m.nextId
newStatus := appStatus{
message: message,
statusType: "waiting",
id: id,
}
m.statuses = append([]appStatus{newStatus}, m.statuses...)
return id
}
func (m *statusManager) addToastStatus(message string) int {
m.mutex.Lock()
defer m.mutex.Unlock()
m.nextId++
id := m.nextId
newStatus := appStatus{
message: message,
statusType: "toast",
id: id,
}
m.statuses = append([]appStatus{newStatus}, m.statuses...)
go func() {
time.Sleep(time.Second * 2)
m.removeStatus(id)
}()
return id
}
func (m *statusManager) getStatusString() string {
if len(m.statuses) == 0 {
return ""
}
topStatus := m.statuses[0]
if topStatus.statusType == "waiting" {
return topStatus.message + " " + utils.Loader()
}
return topStatus.message
}
func (m *statusManager) showStatus() bool {
return len(m.statuses) > 0
}
func (gui *Gui) toast(message string) {
gui.statusManager.addToastStatus(message)
gui.renderAppStatus()
}
func (gui *Gui) renderAppStatus() {
go utils.Safe(func() {
ticker := time.NewTicker(time.Millisecond * 50)
defer ticker.Stop()
for range ticker.C {
appStatus := gui.statusManager.getStatusString()
gui.c.OnUIThread(func() error {
return gui.renderString(gui.Views.AppStatus, appStatus)
})
if appStatus == "" {
return
}
}
})
}
// withWaitingStatus wraps a function and shows a waiting status while the function is still executing
func (gui *Gui) withWaitingStatus(message string, f func() error) error {
go utils.Safe(func() {
id := gui.statusManager.addWaitingStatus(message)
defer func() {
gui.statusManager.removeStatus(id)
}()
gui.renderAppStatus()
if err := f(); err != nil {
gui.c.OnUIThread(func() error {
return gui.c.Error(err)
})
}
})
return nil
}
@@ -1,8 +1,7 @@
package helpers
package gui
import (
"github.com/jesseduffield/lazycore/pkg/boxlayout"
"github.com/jesseduffield/lazygit/pkg/gui/constants"
"github.com/jesseduffield/lazygit/pkg/gui/context"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/utils"
@@ -12,33 +11,12 @@ import (
// In this file we use the boxlayout package, along with knowledge about the app's state,
// to arrange the windows (i.e. panels) on the screen.
type WindowArrangementHelper struct {
c *HelperCommon
windowHelper *WindowHelper
modeHelper *ModeHelper
appStatusHelper *AppStatusHelper
}
func NewWindowArrangementHelper(
c *HelperCommon,
windowHelper *WindowHelper,
modeHelper *ModeHelper,
appStatusHelper *AppStatusHelper,
) *WindowArrangementHelper {
return &WindowArrangementHelper{
c: c,
windowHelper: windowHelper,
modeHelper: modeHelper,
appStatusHelper: appStatusHelper,
}
}
const INFO_SECTION_PADDING = " "
func (self *WindowArrangementHelper) GetWindowDimensions(informationStr string, appStatus string) map[string]boxlayout.Dimensions {
width, height := self.c.GocuiGui().Size()
func (gui *Gui) getWindowDimensions(informationStr string, appStatus string) map[string]boxlayout.Dimensions {
width, height := gui.g.Size()
sideSectionWeight, mainSectionWeight := self.getMidSectionWeights()
sideSectionWeight, mainSectionWeight := gui.getMidSectionWeights()
sidePanelsDirection := boxlayout.COLUMN
portraitMode := width <= 84 && height > 45
@@ -47,18 +25,13 @@ func (self *WindowArrangementHelper) GetWindowDimensions(informationStr string,
}
mainPanelsDirection := boxlayout.ROW
if self.splitMainPanelSideBySide() {
if gui.splitMainPanelSideBySide() {
mainPanelsDirection = boxlayout.COLUMN
}
extrasWindowSize := self.getExtrasWindowSize(height)
extrasWindowSize := gui.getExtrasWindowSize(height)
self.c.Modes().Filtering.Active()
showInfoSection := self.c.UserConfig.Gui.ShowBottomLine ||
self.c.State().GetRepoState().IsSearching() ||
self.modeHelper.IsAnyModeActive() ||
self.appStatusHelper.HasStatus()
showInfoSection := gui.c.UserConfig.Gui.ShowBottomLine || gui.State.Searching.isSearching || gui.isAnyModeActive() || gui.statusManager.showStatus()
infoSectionSize := 0
if showInfoSection {
infoSectionSize = 1
@@ -74,7 +47,7 @@ func (self *WindowArrangementHelper) GetWindowDimensions(informationStr string,
{
Direction: boxlayout.ROW,
Weight: sideSectionWeight,
ConditionalChildren: self.sidePanelChildren,
ConditionalChildren: gui.sidePanelChildren,
},
{
Direction: boxlayout.ROW,
@@ -82,7 +55,7 @@ func (self *WindowArrangementHelper) GetWindowDimensions(informationStr string,
Children: []*boxlayout.Box{
{
Direction: mainPanelsDirection,
Children: self.mainSectionChildren(),
Children: gui.mainSectionChildren(),
Weight: 1,
},
{
@@ -96,7 +69,7 @@ func (self *WindowArrangementHelper) GetWindowDimensions(informationStr string,
{
Direction: boxlayout.COLUMN,
Size: infoSectionSize,
Children: self.infoSectionChildren(informationStr, appStatus),
Children: gui.infoSectionChildren(informationStr, appStatus),
},
},
}
@@ -118,12 +91,12 @@ func MergeMaps[K comparable, V any](maps ...map[K]V) map[K]V {
return result
}
func (self *WindowArrangementHelper) mainSectionChildren() []*boxlayout.Box {
currentWindow := self.windowHelper.CurrentWindow()
func (gui *Gui) mainSectionChildren() []*boxlayout.Box {
currentWindow := gui.currentWindow()
// if we're not in split mode we can just show the one main panel. Likewise if
// the main panel is focused and we're in full-screen mode
if !self.c.State().GetRepoState().GetSplitMainPanel() || (self.c.State().GetRepoState().GetScreenMode() == types.SCREEN_FULL && currentWindow == "main") {
if !gui.isMainPanelSplit() || (gui.State.ScreenMode == SCREEN_FULL && currentWindow == "main") {
return []*boxlayout.Box{
{
Window: "main",
@@ -144,29 +117,27 @@ func (self *WindowArrangementHelper) mainSectionChildren() []*boxlayout.Box {
}
}
func (self *WindowArrangementHelper) getMidSectionWeights() (int, int) {
currentWindow := self.windowHelper.CurrentWindow()
func (gui *Gui) getMidSectionWeights() (int, int) {
currentWindow := gui.currentWindow()
// we originally specified this as a ratio i.e. .20 would correspond to a weight of 1 against 4
sidePanelWidthRatio := self.c.UserConfig.Gui.SidePanelWidth
sidePanelWidthRatio := gui.c.UserConfig.Gui.SidePanelWidth
// we could make this better by creating ratios like 2:3 rather than always 1:something
mainSectionWeight := int(1/sidePanelWidthRatio) - 1
sideSectionWeight := 1
if self.splitMainPanelSideBySide() {
if gui.splitMainPanelSideBySide() {
mainSectionWeight = 5 // need to shrink side panel to make way for main panels if side-by-side
}
screenMode := self.c.State().GetRepoState().GetScreenMode()
if currentWindow == "main" {
if screenMode == types.SCREEN_HALF || screenMode == types.SCREEN_FULL {
if gui.State.ScreenMode == SCREEN_HALF || gui.State.ScreenMode == SCREEN_FULL {
sideSectionWeight = 0
}
} else {
if screenMode == types.SCREEN_HALF {
if gui.State.ScreenMode == SCREEN_HALF {
mainSectionWeight = 1
} else if screenMode == types.SCREEN_FULL {
} else if gui.State.ScreenMode == SCREEN_FULL {
mainSectionWeight = 0
}
}
@@ -174,12 +145,12 @@ func (self *WindowArrangementHelper) getMidSectionWeights() (int, int) {
return sideSectionWeight, mainSectionWeight
}
func (self *WindowArrangementHelper) infoSectionChildren(informationStr string, appStatus string) []*boxlayout.Box {
if self.c.State().GetRepoState().IsSearching() {
func (gui *Gui) infoSectionChildren(informationStr string, appStatus string) []*boxlayout.Box {
if gui.State.Searching.isSearching {
return []*boxlayout.Box{
{
Window: "searchPrefix",
Size: runewidth.StringWidth(constants.SEARCH_PREFIX),
Size: runewidth.StringWidth(SEARCH_PREFIX),
},
{
Window: "search",
@@ -191,7 +162,7 @@ func (self *WindowArrangementHelper) infoSectionChildren(informationStr string,
appStatusBox := &boxlayout.Box{Window: "appStatus"}
optionsBox := &boxlayout.Box{Window: "options"}
if !self.c.UserConfig.Gui.ShowBottomLine {
if !gui.c.UserConfig.Gui.ShowBottomLine {
optionsBox.Weight = 0
appStatusBox.Weight = 1
} else {
@@ -201,7 +172,7 @@ func (self *WindowArrangementHelper) infoSectionChildren(informationStr string,
result := []*boxlayout.Box{appStatusBox, optionsBox}
if self.c.UserConfig.Gui.ShowBottomLine || self.modeHelper.IsAnyModeActive() {
if gui.c.UserConfig.Gui.ShowBottomLine || gui.isAnyModeActive() {
result = append(result, &boxlayout.Box{
Window: "information",
// unlike appStatus, informationStr has various colors so we need to decolorise before taking the length
@@ -212,13 +183,13 @@ func (self *WindowArrangementHelper) infoSectionChildren(informationStr string,
return result
}
func (self *WindowArrangementHelper) splitMainPanelSideBySide() bool {
if !self.c.State().GetRepoState().GetSplitMainPanel() {
func (gui *Gui) splitMainPanelSideBySide() bool {
if !gui.isMainPanelSplit() {
return false
}
mainPanelSplitMode := self.c.UserConfig.Gui.MainPanelSplitMode
width, height := self.c.GocuiGui().Size()
mainPanelSplitMode := gui.c.UserConfig.Gui.MainPanelSplitMode
width, height := gui.g.Size()
switch mainPanelSplitMode {
case "vertical":
@@ -234,18 +205,18 @@ func (self *WindowArrangementHelper) splitMainPanelSideBySide() bool {
}
}
func (self *WindowArrangementHelper) getExtrasWindowSize(screenHeight int) int {
if !self.c.State().GetShowExtrasWindow() {
func (gui *Gui) getExtrasWindowSize(screenHeight int) int {
if !gui.ShowExtrasWindow {
return 0
}
var baseSize int
if self.c.CurrentStaticContext().GetKey() == context.COMMAND_LOG_CONTEXT_KEY {
if gui.currentStaticContext().GetKey() == context.COMMAND_LOG_CONTEXT_KEY {
baseSize = 1000 // my way of saying 'fill the available space'
} else if screenHeight < 40 {
baseSize = 1
} else {
baseSize = self.c.UserConfig.Gui.CommandLogSize
baseSize = gui.c.UserConfig.Gui.CommandLogSize
}
frameSize := 2
@@ -256,15 +227,17 @@ func (self *WindowArrangementHelper) getExtrasWindowSize(screenHeight int) int {
// too much space, but if you access it it should take up some space. This is
// the default behaviour when accordion mode is NOT in effect. If it is in effect
// then when it's accessed it will have weight 2, not 1.
func (self *WindowArrangementHelper) getDefaultStashWindowBox() *boxlayout.Box {
func (gui *Gui) getDefaultStashWindowBox() *boxlayout.Box {
gui.State.ContextManager.RLock()
defer gui.State.ContextManager.RUnlock()
box := &boxlayout.Box{Window: "stash"}
stashWindowAccessed := false
self.c.Context().ForEach(func(context types.Context) {
for _, context := range gui.State.ContextManager.ContextStack {
if context.GetWindowName() == "stash" {
stashWindowAccessed = true
}
})
box := &boxlayout.Box{Window: "stash"}
}
// if the stash window is anywhere in our stack we should enlargen it
if stashWindowAccessed {
box.Weight = 1
@@ -275,11 +248,10 @@ func (self *WindowArrangementHelper) getDefaultStashWindowBox() *boxlayout.Box {
return box
}
func (self *WindowArrangementHelper) sidePanelChildren(width int, height int) []*boxlayout.Box {
currentWindow := self.c.CurrentSideContext().GetWindowName()
func (gui *Gui) sidePanelChildren(width int, height int) []*boxlayout.Box {
currentWindow := gui.currentSideWindowName()
screenMode := self.c.State().GetRepoState().GetScreenMode()
if screenMode == types.SCREEN_FULL || screenMode == types.SCREEN_HALF {
if gui.State.ScreenMode == SCREEN_FULL || gui.State.ScreenMode == SCREEN_HALF {
fullHeightBox := func(window string) *boxlayout.Box {
if window == currentWindow {
return &boxlayout.Box{
@@ -302,7 +274,7 @@ func (self *WindowArrangementHelper) sidePanelChildren(width int, height int) []
fullHeightBox("stash"),
}
} else if height >= 28 {
accordionMode := self.c.UserConfig.Gui.ExpandFocusedSidePanel
accordionMode := gui.c.UserConfig.Gui.ExpandFocusedSidePanel
accordionBox := func(defaultBox *boxlayout.Box) *boxlayout.Box {
if accordionMode && defaultBox.Window == currentWindow {
return &boxlayout.Box{
@@ -322,7 +294,7 @@ func (self *WindowArrangementHelper) sidePanelChildren(width int, height int) []
accordionBox(&boxlayout.Box{Window: "files", Weight: 1}),
accordionBox(&boxlayout.Box{Window: "branches", Weight: 1}),
accordionBox(&boxlayout.Box{Window: "commits", Weight: 1}),
accordionBox(self.getDefaultStashWindowBox()),
accordionBox(gui.getDefaultStashWindowBox()),
}
} else {
squashedHeight := 1
@@ -353,3 +325,24 @@ func (self *WindowArrangementHelper) sidePanelChildren(width int, height int) []
}
}
}
func (gui *Gui) getCyclableWindows() []string {
return []string{"status", "files", "branches", "commits", "stash"}
}
func (gui *Gui) currentSideWindowName() string {
// there is always one and only one cyclable context in the context stack. We'll look from top to bottom
gui.State.ContextManager.RLock()
defer gui.State.ContextManager.RUnlock()
for idx := range gui.State.ContextManager.ContextStack {
reversedIdx := len(gui.State.ContextManager.ContextStack) - 1 - idx
context := gui.State.ContextManager.ContextStack[reversedIdx]
if context.GetKind() == types.SIDE_CONTEXT {
return context.GetWindowName()
}
}
return "files" // default
}
+17 -42
View File
@@ -4,33 +4,18 @@ import (
"strings"
"time"
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/utils"
)
type BackgroundRoutineMgr struct {
gui *Gui
// if we've suspended the gui (e.g. because we've switched to a subprocess)
// we typically want to pause some things that are running like background
// file refreshes
pauseBackgroundThreads bool
}
func (self *BackgroundRoutineMgr) PauseBackgroundThreads(pause bool) {
self.pauseBackgroundThreads = pause
}
func (self *BackgroundRoutineMgr) startBackgroundRoutines() {
userConfig := self.gui.UserConfig
func (gui *Gui) startBackgroundRoutines() {
userConfig := gui.UserConfig
if userConfig.Git.AutoFetch {
fetchInterval := userConfig.Refresher.FetchInterval
if fetchInterval > 0 {
go utils.Safe(self.startBackgroundFetch)
go utils.Safe(gui.startBackgroundFetch)
} else {
self.gui.c.Log.Errorf(
gui.c.Log.Errorf(
"Value of config option 'refresher.fetchInterval' (%d) is invalid, disabling auto-fetch",
fetchInterval)
}
@@ -39,44 +24,42 @@ func (self *BackgroundRoutineMgr) startBackgroundRoutines() {
if userConfig.Git.AutoRefresh {
refreshInterval := userConfig.Refresher.RefreshInterval
if refreshInterval > 0 {
self.goEvery(time.Second*time.Duration(refreshInterval), self.gui.stopChan, func() error {
return self.gui.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.FILES}})
})
gui.goEvery(time.Second*time.Duration(refreshInterval), gui.stopChan, gui.refreshFilesAndSubmodules)
} else {
self.gui.c.Log.Errorf(
gui.c.Log.Errorf(
"Value of config option 'refresher.refreshInterval' (%d) is invalid, disabling auto-refresh",
refreshInterval)
}
}
}
func (self *BackgroundRoutineMgr) startBackgroundFetch() {
self.gui.waitForIntro.Wait()
isNew := self.gui.IsNewRepo
userConfig := self.gui.UserConfig
func (gui *Gui) startBackgroundFetch() {
gui.waitForIntro.Wait()
isNew := gui.IsNewRepo
userConfig := gui.UserConfig
if !isNew {
time.After(time.Duration(userConfig.Refresher.FetchInterval) * time.Second)
}
err := self.backgroundFetch()
err := gui.backgroundFetch()
if err != nil && strings.Contains(err.Error(), "exit status 128") && isNew {
_ = self.gui.c.Alert(self.gui.c.Tr.NoAutomaticGitFetchTitle, self.gui.c.Tr.NoAutomaticGitFetchBody)
_ = gui.c.Alert(gui.c.Tr.NoAutomaticGitFetchTitle, gui.c.Tr.NoAutomaticGitFetchBody)
} else {
self.goEvery(time.Second*time.Duration(userConfig.Refresher.FetchInterval), self.gui.stopChan, func() error {
err := self.backgroundFetch()
self.gui.c.Render()
gui.goEvery(time.Second*time.Duration(userConfig.Refresher.FetchInterval), gui.stopChan, func() error {
err := gui.backgroundFetch()
gui.render()
return err
})
}
}
func (self *BackgroundRoutineMgr) goEvery(interval time.Duration, stop chan struct{}, function func() error) {
func (gui *Gui) goEvery(interval time.Duration, stop chan struct{}, function func() error) {
go utils.Safe(func() {
ticker := time.NewTicker(interval)
defer ticker.Stop()
for {
select {
case <-ticker.C:
if self.pauseBackgroundThreads {
if gui.PauseBackgroundThreads {
continue
}
_ = function()
@@ -86,11 +69,3 @@ func (self *BackgroundRoutineMgr) goEvery(interval time.Duration, stop chan stru
}
})
}
func (self *BackgroundRoutineMgr) backgroundFetch() (err error) {
err = self.gui.git.Sync.Fetch(git_commands.FetchOptions{Background: true})
_ = self.gui.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.BRANCHES, types.COMMITS, types.REMOTES, types.TAGS}, Mode: types.ASYNC})
return err
}
+23
View File
@@ -0,0 +1,23 @@
package gui
import "github.com/jesseduffield/lazygit/pkg/gui/types"
func (gui *Gui) branchesRenderToMain() error {
var task types.UpdateTask
branch := gui.State.Contexts.Branches.GetSelected()
if branch == nil {
task = types.NewRenderStringTask(gui.c.Tr.NoBranchesThisRepo)
} else {
cmdObj := gui.git.Branch.GetGraphCmdObj(branch.FullRefName())
task = types.NewRunPtyTask(cmdObj.GetCmd())
}
return gui.c.RenderToMainViews(types.RefreshMainOpts{
Pair: gui.c.MainViewPairs().Normal,
Main: &types.ViewUpdateOpts{
Title: gui.c.Tr.LogTitle,
Task: task,
},
})
}
+1 -2
View File
@@ -30,7 +30,6 @@ func (gui *Gui) LogAction(action string) {
gui.Views.Extras.Autoscroll = true
gui.GuiLog = append(gui.GuiLog, action)
fmt.Fprint(gui.Views.Extras, "\n"+style.FgYellow.Sprint(action))
}
@@ -47,7 +46,7 @@ func (gui *Gui) LogCommand(cmdStr string, commandLine bool) {
// we style it differently to communicate that
textStyle = style.FgMagenta
}
gui.GuiLog = append(gui.GuiLog, cmdStr)
gui.CmdLog = append(gui.CmdLog, cmdStr)
indentedCmdStr := " " + strings.Replace(cmdStr, "\n", "\n ", -1)
fmt.Fprint(gui.Views.Extras, "\n"+textStyle.Sprint(indentedCmdStr))
}
+52
View File
@@ -0,0 +1,52 @@
package gui
import (
"github.com/jesseduffield/lazygit/pkg/gui/controllers"
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
func (gui *Gui) commitFilesRenderToMain() error {
node := gui.State.Contexts.CommitFiles.GetSelected()
if node == nil {
return nil
}
ref := gui.State.Contexts.CommitFiles.GetRef()
to := ref.RefName()
from, reverse := gui.State.Modes.Diffing.GetFromAndReverseArgsForDiff(ref.ParentRefName())
cmdObj := gui.git.WorkingTree.ShowFileDiffCmdObj(from, to, reverse, node.GetPath(), false,
gui.IgnoreWhitespaceInDiffView)
task := types.NewRunPtyTask(cmdObj.GetCmd())
pair := gui.c.MainViewPairs().Normal
if node.File != nil {
pair = gui.c.MainViewPairs().PatchBuilding
}
return gui.c.RenderToMainViews(types.RefreshMainOpts{
Pair: pair,
Main: &types.ViewUpdateOpts{
Title: gui.Tr.Patch,
Task: task,
},
Secondary: gui.secondaryPatchPanelUpdateOpts(),
})
}
func (gui *Gui) SwitchToCommitFilesContext(opts controllers.SwitchToCommitFilesContextOpts) error {
gui.State.Contexts.CommitFiles.SetSelectedLineIdx(0)
gui.State.Contexts.CommitFiles.SetRef(opts.Ref)
gui.State.Contexts.CommitFiles.SetTitleRef(opts.Ref.Description())
gui.State.Contexts.CommitFiles.SetCanRebase(opts.CanRebase)
gui.State.Contexts.CommitFiles.SetParentContext(opts.Context)
gui.State.Contexts.CommitFiles.SetWindowName(opts.Context.GetWindowName())
if err := gui.c.Refresh(types.RefreshOptions{
Scope: []types.RefreshableView{types.COMMIT_FILES},
}); err != nil {
return err
}
return gui.c.PushContext(gui.State.Contexts.CommitFiles)
}
+37
View File
@@ -0,0 +1,37 @@
package gui
import (
"strconv"
"strings"
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/gui/keybindings"
"github.com/jesseduffield/lazygit/pkg/utils"
)
func (gui *Gui) handleCommitMessageFocused() error {
message := utils.ResolvePlaceholderString(
gui.c.Tr.CommitMessageConfirm,
map[string]string{
"keyBindClose": keybindings.Label(gui.c.UserConfig.Keybinding.Universal.Return),
"keyBindConfirm": keybindings.Label(gui.c.UserConfig.Keybinding.Universal.Confirm),
"keyBindNewLine": keybindings.Label(gui.c.UserConfig.Keybinding.Universal.AppendNewline),
},
)
gui.RenderCommitLength()
return gui.renderString(gui.Views.Options, message)
}
func (gui *Gui) RenderCommitLength() {
if !gui.c.UserConfig.Gui.CommitLength.Show {
return
}
gui.Views.CommitMessage.Subtitle = getBufferLength(gui.Views.CommitMessage)
}
func getBufferLength(view *gocui.View) string {
return " " + strconv.Itoa(strings.Count(view.TextArea.GetContent(), "")-1) + " "
}
+80
View File
@@ -0,0 +1,80 @@
package gui
import (
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/utils"
)
// after selecting the 200th commit, we'll load in all the rest
const COMMIT_THRESHOLD = 200
// list panel functions
func (gui *Gui) getSelectedLocalCommit() *models.Commit {
return gui.State.Contexts.LocalCommits.GetSelected()
}
func (gui *Gui) onCommitFocus() error {
context := gui.State.Contexts.LocalCommits
if context.GetSelectedLineIdx() > COMMIT_THRESHOLD && context.GetLimitCommits() {
context.SetLimitCommits(false)
go utils.Safe(func() {
if err := gui.refreshCommitsWithLimit(); err != nil {
_ = gui.c.Error(err)
}
})
}
return nil
}
func (gui *Gui) branchCommitsRenderToMain() error {
var task types.UpdateTask
commit := gui.State.Contexts.LocalCommits.GetSelected()
if commit == nil {
task = types.NewRenderStringTask(gui.c.Tr.NoCommitsThisBranch)
} else {
cmdObj := gui.git.Commit.ShowCmdObj(commit.Sha, gui.State.Modes.Filtering.GetPath(),
gui.IgnoreWhitespaceInDiffView)
task = types.NewRunPtyTask(cmdObj.GetCmd())
}
return gui.c.RenderToMainViews(types.RefreshMainOpts{
Pair: gui.c.MainViewPairs().Normal,
Main: &types.ViewUpdateOpts{
Title: "Patch",
Task: task,
},
Secondary: gui.secondaryPatchPanelUpdateOpts(),
})
}
func (gui *Gui) secondaryPatchPanelUpdateOpts() *types.ViewUpdateOpts {
if gui.git.Patch.PatchBuilder.Active() {
patch := gui.git.Patch.PatchBuilder.RenderAggregatedPatch(false)
return &types.ViewUpdateOpts{
Task: types.NewRenderStringWithoutScrollTask(patch),
Title: gui.Tr.CustomPatch,
}
}
return nil
}
func (gui *Gui) refForLog() string {
bisectInfo := gui.git.Bisect.GetInfo()
gui.State.Model.BisectInfo = bisectInfo
if !bisectInfo.Started() {
return "HEAD"
}
// need to see if our bisect's current commit is reachable from our 'new' ref.
if bisectInfo.Bisecting() && !gui.git.Bisect.ReachableFromStart(bisectInfo) {
return bisectInfo.GetNewSha()
}
return bisectInfo.GetStartSha()
}
+317
View File
@@ -0,0 +1,317 @@
package gui
import (
"context"
"fmt"
"strings"
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/gui/keybindings"
"github.com/jesseduffield/lazygit/pkg/gui/style"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/theme"
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/mattn/go-runewidth"
)
// This file is for the rendering of confirmation panels along with setting and handling associated
// keybindings.
func (gui *Gui) wrappedConfirmationFunction(cancel context.CancelFunc, function func() error) func() error {
return func() error {
cancel()
if err := gui.c.PopContext(); err != nil {
return err
}
if function != nil {
if err := function(); err != nil {
return gui.c.Error(err)
}
}
return nil
}
}
func (gui *Gui) wrappedPromptConfirmationFunction(cancel context.CancelFunc, function func(string) error, getResponse func() string) func() error {
return func() error {
cancel()
if err := gui.c.PopContext(); err != nil {
return err
}
if function != nil {
if err := function(getResponse()); err != nil {
return gui.c.Error(err)
}
}
return nil
}
}
func (gui *Gui) deactivateConfirmationPrompt() {
gui.Mutexes.PopupMutex.Lock()
gui.State.CurrentPopupOpts = nil
gui.Mutexes.PopupMutex.Unlock()
gui.Views.Confirmation.Visible = false
gui.Views.Suggestions.Visible = false
gui.clearConfirmationViewKeyBindings()
}
func (gui *Gui) getMessageHeight(wrap bool, message string, width int) int {
lines := strings.Split(message, "\n")
lineCount := 0
// if we need to wrap, calculate height to fit content within view's width
if wrap {
for _, line := range lines {
lineCount += runewidth.StringWidth(line)/width + 1
}
} else {
lineCount = len(lines)
}
return lineCount
}
func (gui *Gui) getConfirmationPanelDimensions(wrap bool, prompt string) (int, int, int, int) {
panelWidth := gui.getConfirmationPanelWidth()
panelHeight := gui.getMessageHeight(wrap, prompt, panelWidth)
return gui.getConfirmationPanelDimensionsAux(panelWidth, panelHeight)
}
func (gui *Gui) getConfirmationPanelDimensionsForContentHeight(panelWidth, contentHeight int) (int, int, int, int) {
return gui.getConfirmationPanelDimensionsAux(panelWidth, contentHeight)
}
func (gui *Gui) getConfirmationPanelDimensionsAux(panelWidth int, panelHeight int) (int, int, int, int) {
width, height := gui.g.Size()
if panelHeight > height*3/4 {
panelHeight = height * 3 / 4
}
return width/2 - panelWidth/2,
height/2 - panelHeight/2 - panelHeight%2 - 1,
width/2 + panelWidth/2,
height/2 + panelHeight/2
}
func (gui *Gui) getConfirmationPanelWidth() int {
width, _ := gui.g.Size()
// we want a minimum width up to a point, then we do it based on ratio.
panelWidth := 4 * width / 7
minWidth := 80
if panelWidth < minWidth {
if width-2 < minWidth {
panelWidth = width - 2
} else {
panelWidth = minWidth
}
}
return panelWidth
}
func (gui *Gui) prepareConfirmationPanel(
ctx context.Context,
opts types.ConfirmOpts,
) error {
gui.Views.Confirmation.HasLoader = opts.HasLoader
if opts.HasLoader {
gui.g.StartTicking(ctx)
}
gui.Views.Confirmation.Title = opts.Title
// for now we do not support wrapping in our editor
gui.Views.Confirmation.Wrap = !opts.Editable
gui.Views.Confirmation.FgColor = theme.GocuiDefaultTextColor
gui.Views.Confirmation.Mask = runeForMask(opts.Mask)
_ = gui.Views.Confirmation.SetOrigin(0, 0)
gui.findSuggestions = opts.FindSuggestionsFunc
if opts.FindSuggestionsFunc != nil {
suggestionsView := gui.Views.Suggestions
suggestionsView.Wrap = false
suggestionsView.FgColor = theme.GocuiDefaultTextColor
gui.setSuggestions(opts.FindSuggestionsFunc(""))
suggestionsView.Visible = true
suggestionsView.Title = fmt.Sprintf(gui.c.Tr.SuggestionsTitle, gui.c.UserConfig.Keybinding.Universal.TogglePanel)
}
gui.resizeConfirmationPanel()
return nil
}
func runeForMask(mask bool) rune {
if mask {
return '*'
}
return 0
}
func (gui *Gui) createPopupPanel(ctx context.Context, opts types.CreatePopupPanelOpts) error {
gui.Mutexes.PopupMutex.Lock()
defer gui.Mutexes.PopupMutex.Unlock()
ctx, cancel := context.WithCancel(ctx)
// we don't allow interruptions of non-loader popups in case we get stuck somehow
// e.g. a credentials popup never gets its required user input so a process hangs
// forever.
// The proper solution is to have a queue of popup options
if gui.State.CurrentPopupOpts != nil && !gui.State.CurrentPopupOpts.HasLoader {
gui.Log.Error("ignoring create popup panel because a popup panel is already open")
cancel()
return nil
}
// remove any previous keybindings
gui.clearConfirmationViewKeyBindings()
err := gui.prepareConfirmationPanel(
ctx,
types.ConfirmOpts{
Title: opts.Title,
Prompt: opts.Prompt,
HasLoader: opts.HasLoader,
FindSuggestionsFunc: opts.FindSuggestionsFunc,
Editable: opts.Editable,
Mask: opts.Mask,
})
if err != nil {
cancel()
return err
}
confirmationView := gui.Views.Confirmation
confirmationView.Editable = opts.Editable
confirmationView.Editor = gocui.EditorFunc(gui.defaultEditor)
if opts.Editable {
textArea := confirmationView.TextArea
textArea.Clear()
textArea.TypeString(opts.Prompt)
gui.resizeConfirmationPanel()
confirmationView.RenderTextArea()
} else {
if err := gui.renderString(confirmationView, style.AttrBold.Sprint(opts.Prompt)); err != nil {
cancel()
return err
}
}
if err := gui.setKeyBindings(cancel, opts); err != nil {
cancel()
return err
}
gui.State.CurrentPopupOpts = &opts
return gui.c.PushContext(gui.State.Contexts.Confirmation)
}
func (gui *Gui) setKeyBindings(cancel context.CancelFunc, opts types.CreatePopupPanelOpts) error {
actions := utils.ResolvePlaceholderString(
gui.c.Tr.CloseConfirm,
map[string]string{
"keyBindClose": "esc",
"keyBindConfirm": "enter",
},
)
_ = gui.renderString(gui.Views.Options, actions)
var onConfirm func() error
if opts.HandleConfirmPrompt != nil {
onConfirm = gui.wrappedPromptConfirmationFunction(cancel, opts.HandleConfirmPrompt, func() string { return gui.Views.Confirmation.TextArea.GetContent() })
} else {
onConfirm = gui.wrappedConfirmationFunction(cancel, opts.HandleConfirm)
}
keybindingConfig := gui.c.UserConfig.Keybinding
onSuggestionConfirm := gui.wrappedPromptConfirmationFunction(
cancel,
opts.HandleConfirmPrompt,
gui.getSelectedSuggestionValue,
)
bindings := []*types.Binding{
{
ViewName: "confirmation",
Key: keybindings.GetKey(keybindingConfig.Universal.Confirm),
Handler: onConfirm,
},
{
ViewName: "confirmation",
Key: keybindings.GetKey(keybindingConfig.Universal.Return),
Handler: gui.wrappedConfirmationFunction(cancel, opts.HandleClose),
},
{
ViewName: "confirmation",
Key: keybindings.GetKey(keybindingConfig.Universal.TogglePanel),
Handler: func() error {
if len(gui.State.Suggestions) > 0 {
return gui.replaceContext(gui.State.Contexts.Suggestions)
}
return nil
},
},
{
ViewName: "suggestions",
Key: keybindings.GetKey(keybindingConfig.Universal.Confirm),
Handler: onSuggestionConfirm,
},
{
ViewName: "suggestions",
Key: keybindings.GetKey(keybindingConfig.Universal.Return),
Handler: gui.wrappedConfirmationFunction(cancel, opts.HandleClose),
},
{
ViewName: "suggestions",
Key: keybindings.GetKey(keybindingConfig.Universal.TogglePanel),
Handler: func() error { return gui.replaceContext(gui.State.Contexts.Confirmation) },
},
}
for _, binding := range bindings {
if err := gui.SetKeybinding(binding); err != nil {
return err
}
}
return nil
}
func (gui *Gui) clearConfirmationViewKeyBindings() {
keybindingConfig := gui.c.UserConfig.Keybinding
_ = gui.g.DeleteKeybinding("confirmation", keybindings.GetKey(keybindingConfig.Universal.Confirm), gocui.ModNone)
_ = gui.g.DeleteKeybinding("confirmation", keybindings.GetKey(keybindingConfig.Universal.Return), gocui.ModNone)
_ = gui.g.DeleteKeybinding("suggestions", keybindings.GetKey(keybindingConfig.Universal.Confirm), gocui.ModNone)
_ = gui.g.DeleteKeybinding("suggestions", keybindings.GetKey(keybindingConfig.Universal.Return), gocui.ModNone)
}
func (gui *Gui) refreshSuggestions() {
gui.suggestionsAsyncHandler.Do(func() func() {
findSuggestionsFn := gui.findSuggestions
if findSuggestionsFn != nil {
suggestions := gui.findSuggestions(gui.c.GetPromptInput())
return func() { gui.setSuggestions(suggestions) }
} else {
return func() {}
}
})
}
func (gui *Gui) handleAskFocused() error {
keybindingConfig := gui.c.UserConfig.Keybinding
message := utils.ResolvePlaceholderString(
gui.c.Tr.CloseConfirm,
map[string]string{
"keyBindClose": keybindings.Label(keybindingConfig.Universal.Return),
"keyBindConfirm": keybindings.Label(keybindingConfig.Universal.Confirm),
},
)
return gui.renderString(gui.Views.Options, message)
}
-3
View File
@@ -1,3 +0,0 @@
package constants
const SEARCH_PREFIX = "search: "
+208 -156
View File
@@ -1,13 +1,14 @@
package gui
import (
"errors"
"sync"
"sort"
"strings"
"github.com/jesseduffield/generics/maps"
"github.com/jesseduffield/generics/slices"
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/gui/context"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/samber/lo"
)
// This file is for the management of contexts. There is a context stack such that
@@ -15,66 +16,46 @@ import (
// you in the menu context. When contexts are activated/deactivated certain things need
// to happen like showing/hiding views and rendering content.
type ContextMgr struct {
ContextStack []types.Context
sync.RWMutex
gui *Gui
func (gui *Gui) popupViewNames() []string {
popups := slices.Filter(gui.State.Contexts.Flatten(), func(c types.Context) bool {
return c.GetKind() == types.PERSISTENT_POPUP || c.GetKind() == types.TEMPORARY_POPUP
})
allContexts *context.ContextTree
return slices.Map(popups, func(c types.Context) string {
return c.GetViewName()
})
}
func NewContextMgr(
gui *Gui,
allContexts *context.ContextTree,
) *ContextMgr {
return &ContextMgr{
ContextStack: []types.Context{},
RWMutex: sync.RWMutex{},
gui: gui,
allContexts: allContexts,
}
}
// use when you don't want to return to the original context upon
// use replaceContext when you don't want to return to the original context upon
// hitting escape: you want to go that context's parent instead.
func (self *ContextMgr) Replace(c types.Context) error {
func (gui *Gui) replaceContext(c types.Context) error {
if !c.IsFocusable() {
return nil
}
self.Lock()
gui.State.ContextManager.Lock()
if len(self.ContextStack) == 0 {
self.ContextStack = []types.Context{c}
if len(gui.State.ContextManager.ContextStack) == 0 {
gui.State.ContextManager.ContextStack = []types.Context{c}
} else {
// replace the last item with the given item
self.ContextStack = append(self.ContextStack[0:len(self.ContextStack)-1], c)
gui.State.ContextManager.ContextStack = append(gui.State.ContextManager.ContextStack[0:len(gui.State.ContextManager.ContextStack)-1], c)
}
defer self.Unlock()
defer gui.State.ContextManager.Unlock()
return self.ActivateContext(c, types.OnFocusOpts{})
return gui.activateContext(c, types.OnFocusOpts{})
}
func (self *ContextMgr) Push(c types.Context, opts ...types.OnFocusOpts) error {
if len(opts) > 1 {
return errors.New("cannot pass multiple opts to Push")
}
singleOpts := types.OnFocusOpts{}
if len(opts) > 0 {
// using triple dot but you should only ever pass one of these opt structs
singleOpts = opts[0]
}
func (gui *Gui) pushContext(c types.Context, opts types.OnFocusOpts) error {
if !c.IsFocusable() {
return nil
}
contextsToDeactivate, contextToActivate := self.pushToContextStack(c)
contextsToDeactivate, contextToActivate := gui.pushToContextStack(c)
for _, contextToDeactivate := range contextsToDeactivate {
if err := self.deactivateContext(contextToDeactivate, types.OnFocusLostOpts{NewContextKey: c.GetKey()}); err != nil {
if err := gui.deactivateContext(contextToDeactivate, types.OnFocusLostOpts{NewContextKey: c.GetKey()}); err != nil {
return err
}
}
@@ -83,45 +64,43 @@ func (self *ContextMgr) Push(c types.Context, opts ...types.OnFocusOpts) error {
return nil
}
return self.ActivateContext(contextToActivate, singleOpts)
return gui.activateContext(contextToActivate, opts)
}
// Adjusts the context stack based on the context that's being pushed and
// returns (contexts to deactivate, context to activate)
func (self *ContextMgr) pushToContextStack(c types.Context) ([]types.Context, types.Context) {
func (gui *Gui) pushToContextStack(c types.Context) ([]types.Context, types.Context) {
contextsToDeactivate := []types.Context{}
self.Lock()
defer self.Unlock()
gui.State.ContextManager.Lock()
defer gui.State.ContextManager.Unlock()
if len(self.ContextStack) > 0 &&
c.GetKey() == self.ContextStack[len(self.ContextStack)-1].GetKey() {
if len(gui.State.ContextManager.ContextStack) > 0 &&
c == gui.State.ContextManager.ContextStack[len(gui.State.ContextManager.ContextStack)-1] {
// Context being pushed is already on top of the stack: nothing to
// deactivate or activate
return contextsToDeactivate, nil
}
if len(self.ContextStack) == 0 {
self.ContextStack = append(self.ContextStack, c)
if len(gui.State.ContextManager.ContextStack) == 0 {
gui.State.ContextManager.ContextStack = append(gui.State.ContextManager.ContextStack, c)
} else if c.GetKind() == types.SIDE_CONTEXT {
// if we are switching to a side context, remove all other contexts in the stack
contextsToDeactivate = lo.Filter(self.ContextStack, func(context types.Context, _ int) bool {
return context.GetKey() != c.GetKey()
})
self.ContextStack = []types.Context{c}
contextsToDeactivate = gui.State.ContextManager.ContextStack
gui.State.ContextManager.ContextStack = []types.Context{c}
} else if c.GetKind() == types.MAIN_CONTEXT {
// if we're switching to a main context, remove all other main contexts in the stack
contextsToKeep := []types.Context{}
for _, stackContext := range self.ContextStack {
for _, stackContext := range gui.State.ContextManager.ContextStack {
if stackContext.GetKind() == types.MAIN_CONTEXT {
contextsToDeactivate = append(contextsToDeactivate, stackContext)
} else {
contextsToKeep = append(contextsToKeep, stackContext)
}
}
self.ContextStack = append(contextsToKeep, c)
gui.State.ContextManager.ContextStack = append(contextsToKeep, c)
} else {
topContext := self.currentContextWithoutLock()
topContext := gui.currentContextWithoutLock()
// if we're pushing the same context on, we do nothing.
if topContext.GetKey() != c.GetKey() {
@@ -134,74 +113,44 @@ func (self *ContextMgr) pushToContextStack(c types.Context) ([]types.Context, ty
(topContext.GetKind() == types.MAIN_CONTEXT && c.GetKind() == types.MAIN_CONTEXT) {
contextsToDeactivate = append(contextsToDeactivate, topContext)
_, self.ContextStack = slices.Pop(self.ContextStack)
_, gui.State.ContextManager.ContextStack = slices.Pop(gui.State.ContextManager.ContextStack)
}
self.ContextStack = append(self.ContextStack, c)
gui.State.ContextManager.ContextStack = append(gui.State.ContextManager.ContextStack, c)
}
}
return contextsToDeactivate, c
}
func (self *ContextMgr) Pop() error {
self.Lock()
func (gui *Gui) popContext() error {
gui.State.ContextManager.Lock()
if len(self.ContextStack) == 1 {
if len(gui.State.ContextManager.ContextStack) == 1 {
// cannot escape from bottommost context
self.Unlock()
gui.State.ContextManager.Unlock()
return nil
}
var currentContext types.Context
currentContext, self.ContextStack = slices.Pop(self.ContextStack)
currentContext, gui.State.ContextManager.ContextStack = slices.Pop(gui.State.ContextManager.ContextStack)
newContext := self.ContextStack[len(self.ContextStack)-1]
newContext := gui.State.ContextManager.ContextStack[len(gui.State.ContextManager.ContextStack)-1]
self.Unlock()
gui.State.ContextManager.Unlock()
if err := self.deactivateContext(currentContext, types.OnFocusLostOpts{NewContextKey: newContext.GetKey()}); err != nil {
if err := gui.deactivateContext(currentContext, types.OnFocusLostOpts{NewContextKey: newContext.GetKey()}); err != nil {
return err
}
return self.ActivateContext(newContext, types.OnFocusOpts{})
return gui.activateContext(newContext, types.OnFocusOpts{})
}
func (self *ContextMgr) RemoveContexts(contextsToRemove []types.Context) error {
self.Lock()
if len(self.ContextStack) == 1 {
self.Unlock()
return nil
}
rest := lo.Filter(self.ContextStack, func(context types.Context, _ int) bool {
for _, contextToRemove := range contextsToRemove {
if context.GetKey() == contextToRemove.GetKey() {
return false
}
}
return true
})
self.ContextStack = rest
contextToActivate := rest[len(rest)-1]
self.Unlock()
for _, context := range contextsToRemove {
if err := self.deactivateContext(context, types.OnFocusLostOpts{NewContextKey: contextToActivate.GetKey()}); err != nil {
return err
}
}
// activate the item at the top of the stack
return self.ActivateContext(contextToActivate, types.OnFocusOpts{})
}
func (self *ContextMgr) deactivateContext(c types.Context, opts types.OnFocusLostOpts) error {
view, _ := self.gui.c.GocuiGui().View(c.GetViewName())
func (gui *Gui) deactivateContext(c types.Context, opts types.OnFocusLostOpts) error {
view, _ := gui.g.View(c.GetViewName())
if view != nil && view.IsSearching() {
if err := self.gui.onSearchEscape(); err != nil {
if err := gui.onSearchEscape(); err != nil {
return err
}
}
@@ -220,17 +169,34 @@ func (self *ContextMgr) deactivateContext(c types.Context, opts types.OnFocusLos
return nil
}
func (self *ContextMgr) ActivateContext(c types.Context, opts types.OnFocusOpts) error {
// postRefreshUpdate is to be called on a context after the state that it depends on has been refreshed
// if the context's view is set to another context we do nothing.
// if the context's view is the current view we trigger a focus; re-selecting the current item.
func (gui *Gui) postRefreshUpdate(c types.Context) error {
if err := c.HandleRender(); err != nil {
return err
}
if gui.currentViewName() == c.GetViewName() {
if err := c.HandleFocus(types.OnFocusOpts{}); err != nil {
return err
}
}
return nil
}
func (gui *Gui) activateContext(c types.Context, opts types.OnFocusOpts) error {
viewName := c.GetViewName()
v, err := self.gui.c.GocuiGui().View(viewName)
v, err := gui.g.View(viewName)
if err != nil {
return err
}
self.gui.helpers.Window.SetWindowContext(c)
gui.setWindowContext(c)
self.gui.helpers.Window.MoveToTopOfWindow(c)
if _, err := self.gui.c.GocuiGui().SetCurrentView(viewName); err != nil {
gui.moveToTopOfWindow(c)
if _, err := gui.g.SetCurrentView(viewName); err != nil {
return err
}
@@ -241,9 +207,14 @@ func (self *ContextMgr) ActivateContext(c types.Context, opts types.OnFocusOpts)
v.Visible = true
self.gui.c.GocuiGui().Cursor = v.Editable
gui.g.Cursor = v.Editable
self.gui.renderContextOptionsMap(c)
// render the options available for the current context at the bottom of the screen
optionsMap := c.GetOptionsMap()
if optionsMap == nil {
optionsMap = gui.globalOptionsMap()
}
gui.renderOptionsMap(optionsMap)
if err := c.HandleFocus(opts); err != nil {
return err
@@ -252,27 +223,63 @@ func (self *ContextMgr) ActivateContext(c types.Context, opts types.OnFocusOpts)
return nil
}
func (self *ContextMgr) Current() types.Context {
self.RLock()
defer self.RUnlock()
return self.currentContextWithoutLock()
func (gui *Gui) optionsMapToString(optionsMap map[string]string) string {
options := maps.MapToSlice(optionsMap, func(key string, description string) string {
return key + ": " + description
})
sort.Strings(options)
return strings.Join(options, ", ")
}
func (self *ContextMgr) currentContextWithoutLock() types.Context {
if len(self.ContextStack) == 0 {
return self.gui.defaultSideContext()
func (gui *Gui) renderOptionsMap(optionsMap map[string]string) {
_ = gui.renderString(gui.Views.Options, gui.optionsMapToString(optionsMap))
}
// // currently unused
// func (gui *Gui) renderContextStack() string {
// result := ""
// for _, context := range gui.State.ContextManager.ContextStack {
// result += string(context.GetKey()) + "\n"
// }
// return result
// }
func (gui *Gui) currentContext() types.Context {
gui.State.ContextManager.RLock()
defer gui.State.ContextManager.RUnlock()
return gui.currentContextWithoutLock()
}
func (gui *Gui) currentContextWithoutLock() types.Context {
if len(gui.State.ContextManager.ContextStack) == 0 {
return gui.defaultSideContext()
}
return self.ContextStack[len(self.ContextStack)-1]
return gui.State.ContextManager.ContextStack[len(gui.State.ContextManager.ContextStack)-1]
}
// Note that this could return the 'status' context which is not itself a list context.
func (self *ContextMgr) CurrentSide() types.Context {
self.RLock()
defer self.RUnlock()
// the status panel is not yet a list context (and may never be), so this method is not
// quite the same as currentSideContext()
func (gui *Gui) currentSideListContext() types.IListContext {
context := gui.currentSideContext()
listContext, ok := context.(types.IListContext)
if !ok {
return nil
}
return listContext
}
stack := self.ContextStack
func (gui *Gui) currentSideContext() types.Context {
gui.State.ContextManager.RLock()
defer gui.State.ContextManager.RUnlock()
stack := gui.State.ContextManager.ContextStack
// on startup the stack can be empty so we'll return an empty string in that case
if len(stack) == 0 {
return gui.defaultSideContext()
}
// find the first context in the stack with the type of types.SIDE_CONTEXT
for i := range stack {
@@ -283,22 +290,22 @@ func (self *ContextMgr) CurrentSide() types.Context {
}
}
return self.gui.defaultSideContext()
return gui.defaultSideContext()
}
// static as opposed to popup
func (self *ContextMgr) CurrentStatic() types.Context {
self.RLock()
defer self.RUnlock()
func (gui *Gui) currentStaticContext() types.Context {
gui.State.ContextManager.RLock()
defer gui.State.ContextManager.RUnlock()
return self.currentStaticContextWithoutLock()
return gui.currentStaticContextWithoutLock()
}
func (self *ContextMgr) currentStaticContextWithoutLock() types.Context {
stack := self.ContextStack
func (gui *Gui) currentStaticContextWithoutLock() types.Context {
stack := gui.State.ContextManager.ContextStack
if len(stack) == 0 {
return self.gui.defaultSideContext()
return gui.defaultSideContext()
}
// find the first context in the stack without a popup type
@@ -310,43 +317,88 @@ func (self *ContextMgr) currentStaticContextWithoutLock() types.Context {
}
}
return self.gui.defaultSideContext()
return gui.defaultSideContext()
}
func (self *ContextMgr) ForEach(f func(types.Context)) {
self.RLock()
defer self.RUnlock()
for _, context := range self.gui.State.ContextMgr.ContextStack {
f(context)
func (gui *Gui) defaultSideContext() types.Context {
if gui.State.Modes.Filtering.Active() {
return gui.State.Contexts.LocalCommits
} else {
return gui.State.Contexts.Files
}
}
func (self *ContextMgr) IsCurrent(c types.Context) bool {
return self.Current().GetKey() == c.GetKey()
}
// getFocusLayout returns a manager function for when view gain and lose focus
func (gui *Gui) getFocusLayout() func(g *gocui.Gui) error {
var previousView *gocui.View
return func(g *gocui.Gui) error {
newView := gui.g.CurrentView()
// for now we don't consider losing focus to a popup panel as actually losing focus
if newView != previousView && !gui.isPopupPanel(newView.Name()) {
if err := gui.onViewFocusLost(previousView); err != nil {
return err
}
// all list contexts
func (self *ContextMgr) AllList() []types.IListContext {
var listContexts []types.IListContext
for _, context := range self.allContexts.Flatten() {
if listContext, ok := context.(types.IListContext); ok {
listContexts = append(listContexts, listContext)
previousView = newView
}
return nil
}
return listContexts
}
func (self *ContextMgr) AllPatchExplorer() []types.IPatchExplorerContext {
var listContexts []types.IPatchExplorerContext
for _, context := range self.allContexts.Flatten() {
if listContext, ok := context.(types.IPatchExplorerContext); ok {
listContexts = append(listContexts, listContext)
}
func (gui *Gui) onViewFocusLost(oldView *gocui.View) error {
if oldView == nil {
return nil
}
return listContexts
oldView.Highlight = false
_ = oldView.SetOriginX(0)
return nil
}
func (gui *Gui) TransientContexts() []types.Context {
return slices.Filter(gui.State.Contexts.Flatten(), func(context types.Context) bool {
return context.IsTransient()
})
}
func (gui *Gui) rerenderView(view *gocui.View) error {
context, ok := gui.contextForView(view.Name())
if !ok {
gui.Log.Errorf("no context found for view %s", view.Name())
return nil
}
return context.HandleRender()
}
func (gui *Gui) getSideContextSelectedItemId() string {
currentSideContext := gui.currentSideListContext()
if currentSideContext == nil {
return ""
}
return currentSideContext.GetSelectedItemId()
}
// currently unused
// func (gui *Gui) getCurrentSideView() *gocui.View {
// currentSideContext := gui.currentSideContext()
// if currentSideContext == nil {
// return nil
// }
// view, _ := gui.g.View(currentSideContext.GetViewName())
// return view
// }
// currently unused
// func (gui *Gui) renderContextStack() string {
// result := ""
// for _, context := range gui.State.ContextManager.ContextStack {
// result += context.GetViewName() + "\n"
// }
// return result
// }
-38
View File
@@ -16,9 +16,6 @@ type BaseContext struct {
keybindingsFns []types.KeybindingsFn
mouseKeybindingsFns []types.MouseKeybindingsFn
onClickFn func() error
onRenderToMainFn func() error
onFocusFn onFocusFn
onFocusLostFn onFocusLostFn
focusable bool
transient bool
@@ -28,11 +25,6 @@ type BaseContext struct {
*ParentContextMgr
}
type (
onFocusFn = func(types.OnFocusOpts) error
onFocusLostFn = func(types.OnFocusLostOpts) error
)
var _ types.IBaseContext = &BaseContext{}
type NewBaseContextOpts struct {
@@ -137,36 +129,6 @@ func (self *BaseContext) GetOnClick() func() error {
return self.onClickFn
}
func (self *BaseContext) AddOnRenderToMainFn(fn func() error) {
if fn != nil {
self.onRenderToMainFn = fn
}
}
func (self *BaseContext) GetOnRenderToMain() func() error {
return self.onRenderToMainFn
}
func (self *BaseContext) AddOnFocusFn(fn onFocusFn) {
if fn != nil {
self.onFocusFn = fn
}
}
func (self *BaseContext) GetOnFocus() onFocusFn {
return self.onFocusFn
}
func (self *BaseContext) AddOnFocusLostFn(fn onFocusLostFn) {
if fn != nil {
self.onFocusLostFn = fn
}
}
func (self *BaseContext) GetOnFocusLost() onFocusLostFn {
return self.onFocusLostFn
}
func (self *BaseContext) GetMouseKeybindings(opts types.KeybindingsOpts) []*gocui.ViewMouseBinding {
bindings := []*gocui.ViewMouseBinding{}
for i := range self.mouseKeybindingsFns {
+20 -33
View File
@@ -1,8 +1,8 @@
package context
import (
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
@@ -11,40 +11,40 @@ type BranchesContext struct {
*ListContextTrait
}
var (
_ types.IListContext = (*BranchesContext)(nil)
_ types.DiffableContext = (*BranchesContext)(nil)
)
var _ types.IListContext = (*BranchesContext)(nil)
func NewBranchesContext(c *ContextCommon) *BranchesContext {
viewModel := NewBasicViewModel(func() []*models.Branch { return c.Model().Branches })
func NewBranchesContext(
getModel func() []*models.Branch,
view *gocui.View,
getDisplayStrings func(startIdx int, length int) [][]string,
getDisplayStrings := func(startIdx int, length int) [][]string {
return presentation.GetBranchListDisplayStrings(
c.Model().Branches,
c.State().GetRepoState().GetScreenMode() != types.SCREEN_NORMAL,
c.Modes().Diffing.Ref,
c.Tr,
)
}
onFocus func(types.OnFocusOpts) error,
onRenderToMain func() error,
onFocusLost func(opts types.OnFocusLostOpts) error,
self := &BranchesContext{
c *types.HelperCommon,
) *BranchesContext {
viewModel := NewBasicViewModel(getModel)
return &BranchesContext{
BasicViewModel: viewModel,
ListContextTrait: &ListContextTrait{
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
View: c.Views().Branches,
View: view,
WindowName: "branches",
Key: LOCAL_BRANCHES_CONTEXT_KEY,
Kind: types.SIDE_CONTEXT,
Focusable: true,
})),
}), ContextCallbackOpts{
OnFocus: onFocus,
OnFocusLost: onFocusLost,
OnRenderToMain: onRenderToMain,
}),
list: viewModel,
getDisplayStrings: getDisplayStrings,
c: c,
},
}
return self
}
func (self *BranchesContext) GetSelectedItemId() string {
@@ -63,16 +63,3 @@ func (self *BranchesContext) GetSelectedRef() types.Ref {
}
return branch
}
func (self *BranchesContext) GetDiffTerminals() []string {
// for our local branches we want to include both the branch and its upstream
branch := self.GetSelected()
if branch != nil {
names := []string{branch.ID()}
if branch.IsTrackingRemote() {
names = append(names, branch.ID()+"@{u}")
}
return names
}
return nil
}
+18 -28
View File
@@ -1,11 +1,9 @@
package context
import (
"github.com/jesseduffield/generics/slices"
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/gui/filetree"
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
"github.com/jesseduffield/lazygit/pkg/gui/style"
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
@@ -15,28 +13,20 @@ type CommitFilesContext struct {
*DynamicTitleBuilder
}
var (
_ types.IListContext = (*CommitFilesContext)(nil)
_ types.DiffableContext = (*CommitFilesContext)(nil)
)
var _ types.IListContext = (*CommitFilesContext)(nil)
func NewCommitFilesContext(c *ContextCommon) *CommitFilesContext {
viewModel := filetree.NewCommitFileTreeViewModel(
func() []*models.CommitFile { return c.Model().CommitFiles },
c.Log,
c.UserConfig.Gui.ShowFileTree,
)
func NewCommitFilesContext(
getModel func() []*models.CommitFile,
view *gocui.View,
getDisplayStrings func(startIdx int, length int) [][]string,
getDisplayStrings := func(startIdx int, length int) [][]string {
if viewModel.Len() == 0 {
return [][]string{{style.FgRed.Sprint("(none)")}}
}
onFocus func(types.OnFocusOpts) error,
onRenderToMain func() error,
onFocusLost func(opts types.OnFocusLostOpts) error,
lines := presentation.RenderCommitFileTree(viewModel, c.Modes().Diffing.Ref, c.Git().Patch.PatchBuilder)
return slices.Map(lines, func(line string) []string {
return []string{line}
})
}
c *types.HelperCommon,
) *CommitFilesContext {
viewModel := filetree.NewCommitFileTreeViewModel(getModel, c.Log, c.UserConfig.Gui.ShowFileTree)
return &CommitFilesContext{
CommitFileTreeViewModel: viewModel,
@@ -44,14 +34,18 @@ func NewCommitFilesContext(c *ContextCommon) *CommitFilesContext {
ListContextTrait: &ListContextTrait{
Context: NewSimpleContext(
NewBaseContext(NewBaseContextOpts{
View: c.Views().CommitFiles,
View: view,
WindowName: "commits",
Key: COMMIT_FILES_CONTEXT_KEY,
Kind: types.SIDE_CONTEXT,
Focusable: true,
Transient: true,
}),
),
ContextCallbackOpts{
OnFocus: onFocus,
OnFocusLost: onFocusLost,
OnRenderToMain: onRenderToMain,
}),
list: viewModel,
getDisplayStrings: getDisplayStrings,
c: c,
@@ -67,7 +61,3 @@ func (self *CommitFilesContext) GetSelectedItemId() string {
return item.ID()
}
func (self *CommitFilesContext) GetDiffTerminals() []string {
return []string{self.GetRef().RefName()}
}
-112
View File
@@ -1,112 +0,0 @@
package context
import (
"strconv"
"strings"
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
type CommitMessageContext struct {
c *ContextCommon
types.Context
viewModel *CommitMessageViewModel
}
var _ types.Context = (*CommitMessageContext)(nil)
// when selectedIndex (see below) is set to this value, it means that we're not
// currently viewing a commit message of an existing commit: instead we're making our own
// new commit message
const NoCommitIndex = -1
type CommitMessageViewModel struct {
// index of the commit message, where -1 is 'no commit', 0 is the HEAD commit, 1
// is the prior commit, and so on
selectedindex int
// if true, then upon escaping from the commit message panel, we will preserve
// the message so that it's still shown next time we open the panel
preserveMessage bool
// the full preserved message (combined summary and description)
preservedMessage string
// invoked when pressing enter in the commit message panel
onConfirm func(string) error
// The message typed in before cycling through history
// We store this separately to 'preservedMessage' because 'preservedMessage'
// is specifically for committing staged files and we don't want this affected
// by cycling through history in the context of rewording an old commit.
historyMessage string
}
func NewCommitMessageContext(
c *ContextCommon,
) *CommitMessageContext {
viewModel := &CommitMessageViewModel{}
return &CommitMessageContext{
c: c,
viewModel: viewModel,
Context: NewSimpleContext(
NewBaseContext(NewBaseContextOpts{
Kind: types.PERSISTENT_POPUP,
View: c.Views().CommitMessage,
WindowName: "commitMessage",
Key: COMMIT_MESSAGE_CONTEXT_KEY,
Focusable: true,
HasUncontrolledBounds: true,
}),
),
}
}
func (self *CommitMessageContext) SetSelectedIndex(value int) {
self.viewModel.selectedindex = value
}
func (self *CommitMessageContext) GetSelectedIndex() int {
return self.viewModel.selectedindex
}
func (self *CommitMessageContext) GetPreserveMessage() bool {
return self.viewModel.preserveMessage
}
func (self *CommitMessageContext) GetPreservedMessage() string {
return self.viewModel.preservedMessage
}
func (self *CommitMessageContext) SetPreservedMessage(message string) {
self.viewModel.preservedMessage = message
}
func (self *CommitMessageContext) GetHistoryMessage() string {
return self.viewModel.historyMessage
}
func (self *CommitMessageContext) SetHistoryMessage(message string) {
self.viewModel.historyMessage = message
}
func (self *CommitMessageContext) OnConfirm(message string) error {
return self.viewModel.onConfirm(message)
}
func (self *CommitMessageContext) SetPanelState(index int, title string, preserveMessage bool, onConfirm func(string) error) {
self.viewModel.selectedindex = index
self.viewModel.preserveMessage = preserveMessage
self.viewModel.onConfirm = onConfirm
self.GetView().Title = title
}
func (self *CommitMessageContext) RenderCommitLength() {
if !self.c.UserConfig.Gui.CommitLength.Show {
return
}
self.c.Views().CommitMessage.Subtitle = getBufferLength(self.c.Views().CommitMessage)
}
func getBufferLength(view *gocui.View) string {
return " " + strconv.Itoa(strings.Count(view.TextArea.GetContent(), "")-1) + " "
}
-35
View File
@@ -1,35 +0,0 @@
package context
import (
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
type ConfirmationContext struct {
*SimpleContext
c *ContextCommon
State ConfirmationContextState
}
type ConfirmationContextState struct {
OnConfirm func() error
OnClose func() error
}
var _ types.Context = (*ConfirmationContext)(nil)
func NewConfirmationContext(
c *ContextCommon,
) *ConfirmationContext {
return &ConfirmationContext{
c: c,
SimpleContext: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
View: c.Views().Confirmation,
WindowName: "confirmation",
Key: CONFIRMATION_CONTEXT_KEY,
Kind: types.TEMPORARY_POPUP,
Focusable: true,
HasUncontrolledBounds: true,
})),
}
}
+9 -12
View File
@@ -33,14 +33,13 @@ const (
INFORMATION_CONTEXT_KEY types.ContextKey = "information"
LIMIT_CONTEXT_KEY types.ContextKey = "limit"
MENU_CONTEXT_KEY types.ContextKey = "menu"
CONFIRMATION_CONTEXT_KEY types.ContextKey = "confirmation"
SEARCH_CONTEXT_KEY types.ContextKey = "search"
COMMIT_MESSAGE_CONTEXT_KEY types.ContextKey = "commitMessage"
COMMIT_DESCRIPTION_CONTEXT_KEY types.ContextKey = "commitDescription"
SUBMODULES_CONTEXT_KEY types.ContextKey = "submodules"
SUGGESTIONS_CONTEXT_KEY types.ContextKey = "suggestions"
COMMAND_LOG_CONTEXT_KEY types.ContextKey = "cmdLog"
MENU_CONTEXT_KEY types.ContextKey = "menu"
CONFIRMATION_CONTEXT_KEY types.ContextKey = "confirmation"
SEARCH_CONTEXT_KEY types.ContextKey = "search"
COMMIT_MESSAGE_CONTEXT_KEY types.ContextKey = "commitMessage"
SUBMODULES_CONTEXT_KEY types.ContextKey = "submodules"
SUGGESTIONS_CONTEXT_KEY types.ContextKey = "suggestions"
COMMAND_LOG_CONTEXT_KEY types.ContextKey = "cmdLog"
)
var AllContextKeys = []types.ContextKey{
@@ -97,9 +96,8 @@ type ContextTree struct {
CustomPatchBuilder *PatchExplorerContext
CustomPatchBuilderSecondary types.Context
MergeConflicts *MergeConflictsContext
Confirmation *ConfirmationContext
CommitMessage *CommitMessageContext
CommitDescription types.Context
Confirmation types.Context
CommitMessage types.Context
CommandLog types.Context
// display contexts
@@ -131,7 +129,6 @@ func (self *ContextTree) Flatten() []types.Context {
self.Menu,
self.Confirmation,
self.CommitMessage,
self.CommitDescription,
self.MergeConflicts,
self.StagingSecondary,
-11
View File
@@ -1,11 +0,0 @@
package context
import (
"github.com/jesseduffield/lazygit/pkg/common"
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
type ContextCommon struct {
*common.Common
types.IGuiCommon
}
+1 -3
View File
@@ -10,13 +10,11 @@ import (
type ListContextTrait struct {
types.Context
c *ContextCommon
c *types.HelperCommon
list types.IList
getDisplayStrings func(startIdx int, length int) [][]string
}
func (self *ListContextTrait) IsListContext() {}
func (self *ListContextTrait) GetList() types.IList {
return self.list
}
+19 -68
View File
@@ -1,11 +1,8 @@
package context
import (
"log"
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/commands/types/enums"
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
@@ -14,57 +11,36 @@ type LocalCommitsContext struct {
*ViewportListContextTrait
}
var (
_ types.IListContext = (*LocalCommitsContext)(nil)
_ types.DiffableContext = (*LocalCommitsContext)(nil)
)
var _ types.IListContext = (*LocalCommitsContext)(nil)
func NewLocalCommitsContext(c *ContextCommon) *LocalCommitsContext {
viewModel := NewLocalCommitsViewModel(
func() []*models.Commit { return c.Model().Commits },
c,
)
func NewLocalCommitsContext(
getModel func() []*models.Commit,
view *gocui.View,
getDisplayStrings func(startIdx int, length int) [][]string,
getDisplayStrings := func(startIdx int, length int) [][]string {
selectedCommitSha := ""
onFocus func(types.OnFocusOpts) error,
onRenderToMain func() error,
onFocusLost func(opts types.OnFocusLostOpts) error,
if c.CurrentContext().GetKey() == LOCAL_COMMITS_CONTEXT_KEY {
selectedCommit := viewModel.GetSelected()
if selectedCommit != nil {
selectedCommitSha = selectedCommit.Sha
}
}
showYouAreHereLabel := c.Model().WorkingTreeStateAtLastCommitRefresh == enums.REBASE_MODE_REBASING
return 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,
)
}
c *types.HelperCommon,
) *LocalCommitsContext {
viewModel := NewLocalCommitsViewModel(getModel, c)
return &LocalCommitsContext{
LocalCommitsViewModel: viewModel,
ViewportListContextTrait: &ViewportListContextTrait{
ListContextTrait: &ListContextTrait{
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
View: c.Views().Commits,
View: view,
WindowName: "commits",
Key: LOCAL_COMMITS_CONTEXT_KEY,
Kind: types.SIDE_CONTEXT,
Focusable: true,
})),
}), ContextCallbackOpts{
OnFocus: onFocus,
OnFocusLost: onFocusLost,
OnRenderToMain: onRenderToMain,
}),
list: viewModel,
getDisplayStrings: getDisplayStrings,
c: c,
@@ -93,7 +69,7 @@ type LocalCommitsViewModel struct {
showWholeGitGraph bool
}
func NewLocalCommitsViewModel(getModel func() []*models.Commit, c *ContextCommon) *LocalCommitsViewModel {
func NewLocalCommitsViewModel(getModel func() []*models.Commit, c *types.HelperCommon) *LocalCommitsViewModel {
self := &LocalCommitsViewModel{
BasicViewModel: NewBasicViewModel(getModel),
limitCommits: true,
@@ -115,12 +91,6 @@ func (self *LocalCommitsContext) GetSelectedRef() types.Ref {
return commit
}
func (self *LocalCommitsContext) GetDiffTerminals() []string {
itemId := self.GetSelectedItemId()
return []string{itemId}
}
func (self *LocalCommitsViewModel) SetLimitCommits(value bool) {
self.limitCommits = value
}
@@ -140,22 +110,3 @@ func (self *LocalCommitsViewModel) GetShowWholeGitGraph() bool {
func (self *LocalCommitsViewModel) GetCommits() []*models.Commit {
return self.getModel()
}
func shouldShowGraph(c *ContextCommon) bool {
if c.Modes().Filtering.Active() {
return false
}
value := c.UserConfig.Git.Log.ShowGraph
switch value {
case "always":
return true
case "never":
return false
case "when-maximised":
return c.State().GetRepoState().GetScreenMode() != types.SCREEN_NORMAL
}
log.Fatalf("Unknown value for git.log.showGraph: %s. Expected one of: 'always', 'never', 'when-maximised'", value)
return false
}
+17 -3
View File
@@ -2,6 +2,7 @@ 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"
@@ -15,21 +16,34 @@ type MenuContext struct {
var _ types.IListContext = (*MenuContext)(nil)
func NewMenuContext(
c *ContextCommon,
view *gocui.View,
c *types.HelperCommon,
getOptionsMap func() map[string]string,
renderToDescriptionView func(string),
) *MenuContext {
viewModel := NewMenuViewModel()
onFocus := func(types.OnFocusOpts) error {
selectedMenuItem := viewModel.GetSelected()
renderToDescriptionView(selectedMenuItem.Tooltip)
return nil
}
return &MenuContext{
MenuViewModel: viewModel,
ListContextTrait: &ListContextTrait{
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
View: c.Views().Menu,
View: view,
WindowName: "menu",
Key: "menu",
Kind: types.TEMPORARY_POPUP,
OnGetOptionsMap: getOptionsMap,
Focusable: true,
HasUncontrolledBounds: true,
})),
}), ContextCallbackOpts{
OnFocus: onFocus,
}),
getDisplayStrings: viewModel.GetDisplayStrings,
list: viewModel,
c: c,
+11 -3
View File
@@ -3,6 +3,7 @@ package context
import (
"math"
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/gui/mergeconflicts"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/sasha-s/go-deadlock"
@@ -11,7 +12,7 @@ import (
type MergeConflictsContext struct {
types.Context
viewModel *ConflictsViewModel
c *ContextCommon
c *types.HelperCommon
mutex *deadlock.Mutex
}
@@ -24,7 +25,12 @@ type ConflictsViewModel struct {
}
func NewMergeConflictsContext(
c *ContextCommon,
view *gocui.View,
opts ContextCallbackOpts,
c *types.HelperCommon,
getOptionsMap func() map[string]string,
) *MergeConflictsContext {
viewModel := &ConflictsViewModel{
state: mergeconflicts.NewState(),
@@ -37,12 +43,14 @@ func NewMergeConflictsContext(
Context: NewSimpleContext(
NewBaseContext(NewBaseContextOpts{
Kind: types.MAIN_CONTEXT,
View: c.Views().MergeConflicts,
View: view,
WindowName: "main",
Key: MERGE_CONFLICTS_CONTEXT_KEY,
OnGetOptionsMap: getOptionsMap,
Focusable: true,
HighlightOnFocus: true,
}),
opts,
),
c: c,
}
+8 -5
View File
@@ -13,7 +13,7 @@ type PatchExplorerContext struct {
state *patch_exploring.State
viewTrait *ViewTrait
getIncludedLineIndices func() []int
c *ContextCommon
c *types.HelperCommon
mutex *deadlock.Mutex
}
@@ -24,9 +24,11 @@ func NewPatchExplorerContext(
windowName string,
key types.ContextKey,
onFocus func(types.OnFocusOpts) error,
onFocusLost func(opts types.OnFocusLostOpts) error,
getIncludedLineIndices func() []int,
c *ContextCommon,
c *types.HelperCommon,
) *PatchExplorerContext {
return &PatchExplorerContext{
state: nil,
@@ -41,12 +43,13 @@ func NewPatchExplorerContext(
Kind: types.MAIN_CONTEXT,
Focusable: true,
HighlightOnFocus: true,
})),
}), ContextCallbackOpts{
OnFocus: onFocus,
OnFocusLost: onFocusLost,
}),
}
}
func (self *PatchExplorerContext) IsPatchExplorerContext() {}
func (self *PatchExplorerContext) GetState() *patch_exploring.State {
return self.state
}
+19 -25
View File
@@ -1,8 +1,8 @@
package context
import (
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
@@ -11,35 +11,35 @@ type ReflogCommitsContext struct {
*ListContextTrait
}
var (
_ types.IListContext = (*ReflogCommitsContext)(nil)
_ types.DiffableContext = (*ReflogCommitsContext)(nil)
)
var _ types.IListContext = (*ReflogCommitsContext)(nil)
func NewReflogCommitsContext(c *ContextCommon) *ReflogCommitsContext {
viewModel := NewBasicViewModel(func() []*models.Commit { return c.Model().FilteredReflogCommits })
func NewReflogCommitsContext(
getModel func() []*models.Commit,
view *gocui.View,
getDisplayStrings func(startIdx int, length int) [][]string,
getDisplayStrings := func(startIdx int, length int) [][]string {
return presentation.GetReflogCommitListDisplayStrings(
c.Model().FilteredReflogCommits,
c.State().GetRepoState().GetScreenMode() != types.SCREEN_NORMAL,
c.Modes().CherryPicking.SelectedShaSet(),
c.Modes().Diffing.Ref,
c.UserConfig.Gui.TimeFormat,
c.UserConfig.Git.ParseEmoji,
)
}
onFocus func(types.OnFocusOpts) error,
onRenderToMain func() error,
onFocusLost func(opts types.OnFocusLostOpts) error,
c *types.HelperCommon,
) *ReflogCommitsContext {
viewModel := NewBasicViewModel(getModel)
return &ReflogCommitsContext{
BasicViewModel: viewModel,
ListContextTrait: &ListContextTrait{
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
View: c.Views().ReflogCommits,
View: view,
WindowName: "commits",
Key: REFLOG_COMMITS_CONTEXT_KEY,
Kind: types.SIDE_CONTEXT,
Focusable: true,
})),
}), ContextCallbackOpts{
OnFocus: onFocus,
OnFocusLost: onFocusLost,
OnRenderToMain: onRenderToMain,
}),
list: viewModel,
getDisplayStrings: getDisplayStrings,
c: c,
@@ -71,9 +71,3 @@ func (self *ReflogCommitsContext) GetSelectedRef() types.Ref {
func (self *ReflogCommitsContext) GetCommits() []*models.Commit {
return self.getModel()
}
func (self *ReflogCommitsContext) GetDiffTerminals() []string {
itemId := self.GetSelectedItemId()
return []string{itemId}
}
+18 -19
View File
@@ -1,8 +1,8 @@
package context
import (
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
@@ -12,32 +12,37 @@ type RemoteBranchesContext struct {
*DynamicTitleBuilder
}
var (
_ types.IListContext = (*RemoteBranchesContext)(nil)
_ types.DiffableContext = (*RemoteBranchesContext)(nil)
)
var _ types.IListContext = (*RemoteBranchesContext)(nil)
func NewRemoteBranchesContext(
c *ContextCommon,
) *RemoteBranchesContext {
viewModel := NewBasicViewModel(func() []*models.RemoteBranch { return c.Model().RemoteBranches })
getModel func() []*models.RemoteBranch,
view *gocui.View,
getDisplayStrings func(startIdx int, length int) [][]string,
getDisplayStrings := func(startIdx int, length int) [][]string {
return presentation.GetRemoteBranchListDisplayStrings(c.Model().RemoteBranches, c.Modes().Diffing.Ref)
}
onFocus func(types.OnFocusOpts) error,
onRenderToMain func() error,
onFocusLost func(opts types.OnFocusLostOpts) error,
c *types.HelperCommon,
) *RemoteBranchesContext {
viewModel := NewBasicViewModel(getModel)
return &RemoteBranchesContext{
BasicViewModel: viewModel,
DynamicTitleBuilder: NewDynamicTitleBuilder(c.Tr.RemoteBranchesDynamicTitle),
ListContextTrait: &ListContextTrait{
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
View: c.Views().RemoteBranches,
View: view,
WindowName: "branches",
Key: REMOTE_BRANCHES_CONTEXT_KEY,
Kind: types.SIDE_CONTEXT,
Focusable: true,
Transient: true,
})),
}), ContextCallbackOpts{
OnFocus: onFocus,
OnFocusLost: onFocusLost,
OnRenderToMain: onRenderToMain,
}),
list: viewModel,
getDisplayStrings: getDisplayStrings,
c: c,
@@ -61,9 +66,3 @@ func (self *RemoteBranchesContext) GetSelectedRef() types.Ref {
}
return remoteBranch
}
func (self *RemoteBranchesContext) GetDiffTerminals() []string {
itemId := self.GetSelectedItemId()
return []string{itemId}
}
+19 -18
View File
@@ -1,8 +1,8 @@
package context
import (
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
@@ -11,28 +11,35 @@ type RemotesContext struct {
*ListContextTrait
}
var (
_ types.IListContext = (*RemotesContext)(nil)
_ types.DiffableContext = (*RemotesContext)(nil)
)
var _ types.IListContext = (*RemotesContext)(nil)
func NewRemotesContext(c *ContextCommon) *RemotesContext {
viewModel := NewBasicViewModel(func() []*models.Remote { return c.Model().Remotes })
func NewRemotesContext(
getModel func() []*models.Remote,
view *gocui.View,
getDisplayStrings func(startIdx int, length int) [][]string,
getDisplayStrings := func(startIdx int, length int) [][]string {
return presentation.GetRemoteListDisplayStrings(c.Model().Remotes, c.Modes().Diffing.Ref)
}
onFocus func(types.OnFocusOpts) error,
onRenderToMain func() error,
onFocusLost func(opts types.OnFocusLostOpts) error,
c *types.HelperCommon,
) *RemotesContext {
viewModel := NewBasicViewModel(getModel)
return &RemotesContext{
BasicViewModel: viewModel,
ListContextTrait: &ListContextTrait{
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
View: c.Views().Remotes,
View: view,
WindowName: "branches",
Key: REMOTES_CONTEXT_KEY,
Kind: types.SIDE_CONTEXT,
Focusable: true,
})),
}), ContextCallbackOpts{
OnFocus: onFocus,
OnFocusLost: onFocusLost,
OnRenderToMain: onRenderToMain,
}),
list: viewModel,
getDisplayStrings: getDisplayStrings,
c: c,
@@ -48,9 +55,3 @@ func (self *RemotesContext) GetSelectedItemId() string {
return item.ID()
}
func (self *RemotesContext) GetDiffTerminals() []string {
itemId := self.GetSelectedItemId()
return []string{itemId}
}
-146
View File
@@ -1,146 +0,0 @@
package context
import "github.com/jesseduffield/lazygit/pkg/gui/types"
func NewContextTree(c *ContextCommon) *ContextTree {
commitFilesContext := NewCommitFilesContext(c)
return &ContextTree{
Global: NewSimpleContext(
NewBaseContext(NewBaseContextOpts{
Kind: types.GLOBAL_CONTEXT,
View: nil, // TODO: see if this breaks anything
WindowName: "",
Key: GLOBAL_CONTEXT_KEY,
Focusable: false,
HasUncontrolledBounds: true, // setting to true because the global context doesn't even have a view
}),
),
Status: NewSimpleContext(
NewBaseContext(NewBaseContextOpts{
Kind: types.SIDE_CONTEXT,
View: c.Views().Status,
WindowName: "status",
Key: STATUS_CONTEXT_KEY,
Focusable: true,
}),
),
Files: NewWorkingTreeContext(c),
Submodules: NewSubmodulesContext(c),
Menu: NewMenuContext(c),
Remotes: NewRemotesContext(c),
RemoteBranches: NewRemoteBranchesContext(c),
LocalCommits: NewLocalCommitsContext(c),
CommitFiles: commitFilesContext,
ReflogCommits: NewReflogCommitsContext(c),
SubCommits: NewSubCommitsContext(c),
Branches: NewBranchesContext(c),
Tags: NewTagsContext(c),
Stash: NewStashContext(c),
Suggestions: NewSuggestionsContext(c),
Normal: NewSimpleContext(
NewBaseContext(NewBaseContextOpts{
Kind: types.MAIN_CONTEXT,
View: c.Views().Main,
WindowName: "main",
Key: NORMAL_MAIN_CONTEXT_KEY,
Focusable: false,
}),
),
NormalSecondary: NewSimpleContext(
NewBaseContext(NewBaseContextOpts{
Kind: types.MAIN_CONTEXT,
View: c.Views().Secondary,
WindowName: "secondary",
Key: NORMAL_SECONDARY_CONTEXT_KEY,
Focusable: false,
}),
),
Staging: NewPatchExplorerContext(
c.Views().Staging,
"main",
STAGING_MAIN_CONTEXT_KEY,
func() []int { return nil },
c,
),
StagingSecondary: NewPatchExplorerContext(
c.Views().StagingSecondary,
"secondary",
STAGING_SECONDARY_CONTEXT_KEY,
func() []int { return nil },
c,
),
CustomPatchBuilder: NewPatchExplorerContext(
c.Views().PatchBuilding,
"main",
PATCH_BUILDING_MAIN_CONTEXT_KEY,
func() []int {
filename := commitFilesContext.GetSelectedPath()
includedLineIndices, err := c.Git().Patch.PatchBuilder.GetFileIncLineIndices(filename)
if err != nil {
c.Log.Error(err)
return nil
}
return includedLineIndices
},
c,
),
CustomPatchBuilderSecondary: NewSimpleContext(
NewBaseContext(NewBaseContextOpts{
Kind: types.MAIN_CONTEXT,
View: c.Views().PatchBuildingSecondary,
WindowName: "secondary",
Key: PATCH_BUILDING_SECONDARY_CONTEXT_KEY,
Focusable: false,
}),
),
MergeConflicts: NewMergeConflictsContext(
c,
),
Confirmation: NewConfirmationContext(c),
CommitMessage: NewCommitMessageContext(c),
CommitDescription: NewSimpleContext(
NewBaseContext(NewBaseContextOpts{
Kind: types.PERSISTENT_POPUP,
View: c.Views().CommitDescription,
WindowName: "commitDescription",
Key: COMMIT_DESCRIPTION_CONTEXT_KEY,
Focusable: true,
HasUncontrolledBounds: true,
}),
),
Search: NewSimpleContext(
NewBaseContext(NewBaseContextOpts{
Kind: types.PERSISTENT_POPUP,
View: c.Views().Search,
WindowName: "search",
Key: SEARCH_CONTEXT_KEY,
Focusable: true,
}),
),
CommandLog: NewSimpleContext(
NewBaseContext(NewBaseContextOpts{
Kind: types.EXTRAS_CONTEXT,
View: c.Views().Extras,
WindowName: "extras",
Key: COMMAND_LOG_CONTEXT_KEY,
Focusable: true,
}),
),
Snake: NewSimpleContext(
NewBaseContext(NewBaseContextOpts{
Kind: types.SIDE_CONTEXT,
View: c.Views().Snake,
WindowName: "files",
Key: SNAKE_CONTEXT_KEY,
Focusable: true,
}),
),
Options: NewDisplayContext(OPTIONS_CONTEXT_KEY, c.Views().Options, "options"),
AppStatus: NewDisplayContext(APP_STATUS_CONTEXT_KEY, c.Views().AppStatus, "appStatus"),
SearchPrefix: NewDisplayContext(SEARCH_PREFIX_CONTEXT_KEY, c.Views().SearchPrefix, "searchPrefix"),
Information: NewDisplayContext(INFORMATION_CONTEXT_KEY, c.Views().Information, "information"),
Limit: NewDisplayContext(LIMIT_CONTEXT_KEY, c.Views().Limit, "limit"),
}
}
+31 -10
View File
@@ -6,12 +6,29 @@ import (
)
type SimpleContext struct {
OnFocus func(opts types.OnFocusOpts) error
OnFocusLost func(opts types.OnFocusLostOpts) error
OnRender func() error
// this is for pushing some content to the main view
OnRenderToMain func() error
*BaseContext
}
func NewSimpleContext(baseContext *BaseContext) *SimpleContext {
type ContextCallbackOpts struct {
OnFocus func(opts types.OnFocusOpts) error
OnFocusLost func(opts types.OnFocusLostOpts) error
OnRender func() error
OnRenderToMain func() error
}
func NewSimpleContext(baseContext *BaseContext, opts ContextCallbackOpts) *SimpleContext {
return &SimpleContext{
BaseContext: baseContext,
OnFocus: opts.OnFocus,
OnFocusLost: opts.OnFocusLost,
OnRender: opts.OnRender,
OnRenderToMain: opts.OnRenderToMain,
BaseContext: baseContext,
}
}
@@ -28,6 +45,7 @@ func NewDisplayContext(key types.ContextKey, view *gocui.View, windowName string
Focusable: false,
Transient: false,
}),
ContextCallbackOpts{},
)
}
@@ -36,14 +54,14 @@ func (self *SimpleContext) HandleFocus(opts types.OnFocusOpts) error {
self.GetViewTrait().SetHighlight(true)
}
if self.onFocusFn != nil {
if err := self.onFocusFn(opts); err != nil {
if self.OnFocus != nil {
if err := self.OnFocus(opts); err != nil {
return err
}
}
if self.onRenderToMainFn != nil {
if err := self.onRenderToMainFn(); err != nil {
if self.OnRenderToMain != nil {
if err := self.OnRenderToMain(); err != nil {
return err
}
}
@@ -52,19 +70,22 @@ func (self *SimpleContext) HandleFocus(opts types.OnFocusOpts) error {
}
func (self *SimpleContext) HandleFocusLost(opts types.OnFocusLostOpts) error {
if self.onFocusLostFn != nil {
return self.onFocusLostFn(opts)
if self.OnFocusLost != nil {
return self.OnFocusLost(opts)
}
return nil
}
func (self *SimpleContext) HandleRender() error {
if self.OnRender != nil {
return self.OnRender()
}
return nil
}
func (self *SimpleContext) HandleRenderToMain() error {
if self.onRenderToMainFn != nil {
return self.onRenderToMainFn()
if self.OnRenderToMain != nil {
return self.OnRenderToMain()
}
return nil
+18 -19
View File
@@ -1,8 +1,8 @@
package context
import (
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
@@ -11,30 +11,35 @@ type StashContext struct {
*ListContextTrait
}
var (
_ types.IListContext = (*StashContext)(nil)
_ types.DiffableContext = (*StashContext)(nil)
)
var _ types.IListContext = (*StashContext)(nil)
func NewStashContext(
c *ContextCommon,
) *StashContext {
viewModel := NewBasicViewModel(func() []*models.StashEntry { return c.Model().StashEntries })
getModel func() []*models.StashEntry,
view *gocui.View,
getDisplayStrings func(startIdx int, length int) [][]string,
getDisplayStrings := func(startIdx int, length int) [][]string {
return presentation.GetStashEntryListDisplayStrings(c.Model().StashEntries, c.Modes().Diffing.Ref)
}
onFocus func(types.OnFocusOpts) error,
onRenderToMain func() error,
onFocusLost func(opts types.OnFocusLostOpts) error,
c *types.HelperCommon,
) *StashContext {
viewModel := NewBasicViewModel(getModel)
return &StashContext{
BasicViewModel: viewModel,
ListContextTrait: &ListContextTrait{
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
View: c.Views().Stash,
View: view,
WindowName: "stash",
Key: STASH_CONTEXT_KEY,
Kind: types.SIDE_CONTEXT,
Focusable: true,
})),
}), ContextCallbackOpts{
OnFocus: onFocus,
OnFocusLost: onFocusLost,
OnRenderToMain: onRenderToMain,
}),
list: viewModel,
getDisplayStrings: getDisplayStrings,
c: c,
@@ -62,9 +67,3 @@ func (self *StashContext) GetSelectedRef() types.Ref {
}
return stash
}
func (self *StashContext) GetDiffTerminals() []string {
itemId := self.GetSelectedItemId()
return []string{itemId}
}
+20 -45
View File
@@ -3,9 +3,8 @@ package context
import (
"fmt"
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/utils"
)
@@ -16,45 +15,23 @@ type SubCommitsContext struct {
*DynamicTitleBuilder
}
var (
_ types.IListContext = (*SubCommitsContext)(nil)
_ types.DiffableContext = (*SubCommitsContext)(nil)
)
var _ types.IListContext = (*SubCommitsContext)(nil)
func NewSubCommitsContext(
c *ContextCommon,
getModel func() []*models.Commit,
view *gocui.View,
getDisplayStrings func(startIdx int, length int) [][]string,
onFocus func(types.OnFocusOpts) error,
onRenderToMain func() error,
onFocusLost func(opts types.OnFocusLostOpts) error,
c *types.HelperCommon,
) *SubCommitsContext {
viewModel := &SubCommitsViewModel{
BasicViewModel: NewBasicViewModel(
func() []*models.Commit { return c.Model().SubCommits },
),
ref: nil,
limitCommits: true,
}
getDisplayStrings := func(startIdx int, length int) [][]string {
selectedCommitSha := ""
if c.CurrentContext().GetKey() == SUB_COMMITS_CONTEXT_KEY {
selectedCommit := viewModel.GetSelected()
if selectedCommit != nil {
selectedCommitSha = selectedCommit.Sha
}
}
return 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,
)
BasicViewModel: NewBasicViewModel(getModel),
ref: nil,
limitCommits: true,
}
return &SubCommitsContext{
@@ -63,13 +40,17 @@ func NewSubCommitsContext(
ViewportListContextTrait: &ViewportListContextTrait{
ListContextTrait: &ListContextTrait{
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
View: c.Views().SubCommits,
View: view,
WindowName: "branches",
Key: SUB_COMMITS_CONTEXT_KEY,
Kind: types.SIDE_CONTEXT,
Focusable: true,
Transient: true,
})),
}), ContextCallbackOpts{
OnFocus: onFocus,
OnFocusLost: onFocusLost,
OnRenderToMain: onRenderToMain,
}),
list: viewModel,
getDisplayStrings: getDisplayStrings,
c: c,
@@ -130,9 +111,3 @@ func (self *SubCommitsContext) SetLimitCommits(value bool) {
func (self *SubCommitsContext) GetLimitCommits() bool {
return self.limitCommits
}
func (self *SubCommitsContext) GetDiffTerminals() []string {
itemId := self.GetSelectedItemId()
return []string{itemId}
}
+18 -8
View File
@@ -1,8 +1,8 @@
package context
import (
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
@@ -13,23 +13,33 @@ type SubmodulesContext struct {
var _ types.IListContext = (*SubmodulesContext)(nil)
func NewSubmodulesContext(c *ContextCommon) *SubmodulesContext {
viewModel := NewBasicViewModel(func() []*models.SubmoduleConfig { return c.Model().Submodules })
func NewSubmodulesContext(
getModel func() []*models.SubmoduleConfig,
view *gocui.View,
getDisplayStrings func(startIdx int, length int) [][]string,
getDisplayStrings := func(startIdx int, length int) [][]string {
return presentation.GetSubmoduleListDisplayStrings(c.Model().Submodules)
}
onFocus func(types.OnFocusOpts) error,
onRenderToMain func() error,
onFocusLost func(opts types.OnFocusLostOpts) error,
c *types.HelperCommon,
) *SubmodulesContext {
viewModel := NewBasicViewModel(getModel)
return &SubmodulesContext{
BasicViewModel: viewModel,
ListContextTrait: &ListContextTrait{
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
View: c.Views().Submodules,
View: view,
WindowName: "files",
Key: SUBMODULES_CONTEXT_KEY,
Kind: types.SIDE_CONTEXT,
Focusable: true,
})),
}), ContextCallbackOpts{
OnFocus: onFocus,
OnFocusLost: onFocusLost,
OnRenderToMain: onRenderToMain,
}),
list: viewModel,
getDisplayStrings: getDisplayStrings,
c: c,
+16 -49
View File
@@ -1,59 +1,45 @@
package context
import (
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/tasks"
)
type SuggestionsContext struct {
*BasicViewModel[*types.Suggestion]
*ListContextTrait
State *SuggestionsContextState
}
type SuggestionsContextState struct {
Suggestions []*types.Suggestion
OnConfirm func() error
OnClose func() error
AsyncHandler *tasks.AsyncHandler
// FindSuggestions will take a string that the user has typed into a prompt
// and return a slice of suggestions which match that string.
FindSuggestions func(string) []*types.Suggestion
}
var _ types.IListContext = (*SuggestionsContext)(nil)
func NewSuggestionsContext(
c *ContextCommon,
getModel func() []*types.Suggestion,
view *gocui.View,
getDisplayStrings func(startIdx int, length int) [][]string,
onFocus func(types.OnFocusOpts) error,
onRenderToMain func() error,
onFocusLost func(opts types.OnFocusLostOpts) error,
c *types.HelperCommon,
) *SuggestionsContext {
state := &SuggestionsContextState{
AsyncHandler: tasks.NewAsyncHandler(),
}
getModel := func() []*types.Suggestion {
return state.Suggestions
}
getDisplayStrings := func(startIdx int, length int) [][]string {
return presentation.GetSuggestionListDisplayStrings(state.Suggestions)
}
viewModel := NewBasicViewModel(getModel)
return &SuggestionsContext{
State: state,
BasicViewModel: viewModel,
ListContextTrait: &ListContextTrait{
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
View: c.Views().Suggestions,
View: view,
WindowName: "suggestions",
Key: SUGGESTIONS_CONTEXT_KEY,
Kind: types.PERSISTENT_POPUP,
Focusable: true,
HasUncontrolledBounds: true,
})),
}), ContextCallbackOpts{
OnFocus: onFocus,
OnFocusLost: onFocusLost,
OnRenderToMain: onRenderToMain,
}),
list: viewModel,
getDisplayStrings: getDisplayStrings,
c: c,
@@ -69,22 +55,3 @@ func (self *SuggestionsContext) GetSelectedItemId() string {
return item.Value
}
func (self *SuggestionsContext) SetSuggestions(suggestions []*types.Suggestion) {
self.State.Suggestions = suggestions
self.SetSelectedLineIdx(0)
self.c.ResetViewOrigin(self.GetView())
_ = self.HandleRender()
}
func (self *SuggestionsContext) RefreshSuggestions() {
self.State.AsyncHandler.Do(func() func() {
findSuggestionsFn := self.State.FindSuggestions
if findSuggestionsFn != nil {
suggestions := findSuggestionsFn(self.c.GetPromptInput())
return func() { self.SetSuggestions(suggestions) }
} else {
return func() {}
}
})
}
+18 -19
View File
@@ -1,8 +1,8 @@
package context
import (
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
@@ -11,30 +11,35 @@ type TagsContext struct {
*ListContextTrait
}
var (
_ types.IListContext = (*TagsContext)(nil)
_ types.DiffableContext = (*TagsContext)(nil)
)
var _ types.IListContext = (*TagsContext)(nil)
func NewTagsContext(
c *ContextCommon,
) *TagsContext {
viewModel := NewBasicViewModel(func() []*models.Tag { return c.Model().Tags })
getModel func() []*models.Tag,
view *gocui.View,
getDisplayStrings func(startIdx int, length int) [][]string,
getDisplayStrings := func(startIdx int, length int) [][]string {
return presentation.GetTagListDisplayStrings(c.Model().Tags, c.Modes().Diffing.Ref)
}
onFocus func(types.OnFocusOpts) error,
onRenderToMain func() error,
onFocusLost func(opts types.OnFocusLostOpts) error,
c *types.HelperCommon,
) *TagsContext {
viewModel := NewBasicViewModel(getModel)
return &TagsContext{
BasicViewModel: viewModel,
ListContextTrait: &ListContextTrait{
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
View: c.Views().Tags,
View: view,
WindowName: "branches",
Key: TAGS_CONTEXT_KEY,
Kind: types.SIDE_CONTEXT,
Focusable: true,
})),
}), ContextCallbackOpts{
OnFocus: onFocus,
OnFocusLost: onFocusLost,
OnRenderToMain: onRenderToMain,
}),
list: viewModel,
getDisplayStrings: getDisplayStrings,
c: c,
@@ -58,9 +63,3 @@ func (self *TagsContext) GetSelectedRef() types.Ref {
}
return tag
}
func (self *TagsContext) GetDiffTerminals() []string {
itemId := self.GetSelectedItemId()
return []string{itemId}
}
+18 -16
View File
@@ -1,10 +1,9 @@
package context
import (
"github.com/jesseduffield/generics/slices"
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/gui/filetree"
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
@@ -15,30 +14,33 @@ type WorkingTreeContext struct {
var _ types.IListContext = (*WorkingTreeContext)(nil)
func NewWorkingTreeContext(c *ContextCommon) *WorkingTreeContext {
viewModel := filetree.NewFileTreeViewModel(
func() []*models.File { return c.Model().Files },
c.Log,
c.UserConfig.Gui.ShowFileTree,
)
func NewWorkingTreeContext(
getModel func() []*models.File,
view *gocui.View,
getDisplayStrings func(startIdx int, length int) [][]string,
getDisplayStrings := func(startIdx int, length int) [][]string {
lines := presentation.RenderFileTree(viewModel, c.Modes().Diffing.Ref, c.Model().Submodules)
return slices.Map(lines, func(line string) []string {
return []string{line}
})
}
onFocus func(types.OnFocusOpts) error,
onRenderToMain func() error,
onFocusLost func(opts types.OnFocusLostOpts) error,
c *types.HelperCommon,
) *WorkingTreeContext {
viewModel := filetree.NewFileTreeViewModel(getModel, c.Log, c.UserConfig.Gui.ShowFileTree)
return &WorkingTreeContext{
FileTreeViewModel: viewModel,
ListContextTrait: &ListContextTrait{
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
View: c.Views().Files,
View: view,
WindowName: "files",
Key: FILES_CONTEXT_KEY,
Kind: types.SIDE_CONTEXT,
Focusable: true,
})),
}), ContextCallbackOpts{
OnFocus: onFocus,
OnFocusLost: onFocusLost,
OnRenderToMain: onRenderToMain,
}),
list: viewModel,
getDisplayStrings: getDisplayStrings,
c: c,
+259 -9
View File
@@ -6,11 +6,261 @@ import (
)
func (gui *Gui) contextTree() *context.ContextTree {
contextCommon := &context.ContextCommon{
IGuiCommon: gui.c.IGuiCommon,
Common: gui.c.Common,
return &context.ContextTree{
Global: context.NewSimpleContext(
context.NewBaseContext(context.NewBaseContextOpts{
Kind: types.GLOBAL_CONTEXT,
View: nil, // TODO: see if this breaks anything
WindowName: "",
Key: context.GLOBAL_CONTEXT_KEY,
Focusable: false,
HasUncontrolledBounds: true, // setting to true because the global context doesn't even have a view
}),
context.ContextCallbackOpts{
OnRenderToMain: gui.statusRenderToMain,
},
),
Status: context.NewSimpleContext(
context.NewBaseContext(context.NewBaseContextOpts{
Kind: types.SIDE_CONTEXT,
View: gui.Views.Status,
WindowName: "status",
Key: context.STATUS_CONTEXT_KEY,
Focusable: true,
}),
context.ContextCallbackOpts{
OnRenderToMain: gui.statusRenderToMain,
},
),
Snake: context.NewSimpleContext(
context.NewBaseContext(context.NewBaseContextOpts{
Kind: types.SIDE_CONTEXT,
View: gui.Views.Snake,
WindowName: "files",
Key: context.SNAKE_CONTEXT_KEY,
Focusable: true,
}),
context.ContextCallbackOpts{
OnFocus: func(opts types.OnFocusOpts) error {
gui.startSnake()
return nil
},
OnFocusLost: func(opts types.OnFocusLostOpts) error {
gui.snakeGame.Exit()
gui.moveToTopOfWindow(gui.State.Contexts.Submodules)
return nil
},
},
),
Files: gui.filesListContext(),
Submodules: gui.submodulesListContext(),
Menu: gui.menuListContext(),
Remotes: gui.remotesListContext(),
RemoteBranches: gui.remoteBranchesListContext(),
LocalCommits: gui.branchCommitsListContext(),
CommitFiles: gui.commitFilesListContext(),
ReflogCommits: gui.reflogCommitsListContext(),
SubCommits: gui.subCommitsListContext(),
Branches: gui.branchesListContext(),
Tags: gui.tagsListContext(),
Stash: gui.stashListContext(),
Suggestions: gui.suggestionsListContext(),
Normal: context.NewSimpleContext(
context.NewBaseContext(context.NewBaseContextOpts{
Kind: types.MAIN_CONTEXT,
View: gui.Views.Main,
WindowName: "main",
Key: context.NORMAL_MAIN_CONTEXT_KEY,
Focusable: false,
}),
context.ContextCallbackOpts{
OnFocus: func(opts types.OnFocusOpts) error {
return nil // TODO: should we do something here? We should allow for scrolling the panel
},
},
),
NormalSecondary: context.NewSimpleContext(
context.NewBaseContext(context.NewBaseContextOpts{
Kind: types.MAIN_CONTEXT,
View: gui.Views.Secondary,
WindowName: "secondary",
Key: context.NORMAL_SECONDARY_CONTEXT_KEY,
Focusable: false,
}),
context.ContextCallbackOpts{},
),
Staging: context.NewPatchExplorerContext(
gui.Views.Staging,
"main",
context.STAGING_MAIN_CONTEXT_KEY,
func(opts types.OnFocusOpts) error {
gui.Views.Staging.Wrap = false
gui.Views.StagingSecondary.Wrap = false
return gui.refreshStagingPanel(opts)
},
func(opts types.OnFocusLostOpts) error {
gui.State.Contexts.Staging.SetState(nil)
if opts.NewContextKey != context.STAGING_SECONDARY_CONTEXT_KEY {
gui.Views.Staging.Wrap = true
gui.Views.StagingSecondary.Wrap = true
_ = gui.State.Contexts.Staging.Render(false)
_ = gui.State.Contexts.StagingSecondary.Render(false)
}
return nil
},
func() []int { return nil },
gui.c,
),
StagingSecondary: context.NewPatchExplorerContext(
gui.Views.StagingSecondary,
"secondary",
context.STAGING_SECONDARY_CONTEXT_KEY,
func(opts types.OnFocusOpts) error {
gui.Views.Staging.Wrap = false
gui.Views.StagingSecondary.Wrap = false
return gui.refreshStagingPanel(opts)
},
func(opts types.OnFocusLostOpts) error {
gui.State.Contexts.StagingSecondary.SetState(nil)
if opts.NewContextKey != context.STAGING_MAIN_CONTEXT_KEY {
gui.Views.Staging.Wrap = true
gui.Views.StagingSecondary.Wrap = true
_ = gui.State.Contexts.Staging.Render(false)
_ = gui.State.Contexts.StagingSecondary.Render(false)
}
return nil
},
func() []int { return nil },
gui.c,
),
CustomPatchBuilder: context.NewPatchExplorerContext(
gui.Views.PatchBuilding,
"main",
context.PATCH_BUILDING_MAIN_CONTEXT_KEY,
func(opts types.OnFocusOpts) error {
// no need to change wrap on the secondary view because it can't be interacted with
gui.Views.PatchBuilding.Wrap = false
return gui.refreshPatchBuildingPanel(opts)
},
func(opts types.OnFocusLostOpts) error {
gui.Views.PatchBuilding.Wrap = true
if gui.git.Patch.PatchBuilder.IsEmpty() {
gui.git.Patch.PatchBuilder.Reset()
}
return nil
},
func() []int {
filename := gui.State.Contexts.CommitFiles.GetSelectedPath()
includedLineIndices, err := gui.git.Patch.PatchBuilder.GetFileIncLineIndices(filename)
if err != nil {
gui.Log.Error(err)
return nil
}
return includedLineIndices
},
gui.c,
),
CustomPatchBuilderSecondary: context.NewSimpleContext(
context.NewBaseContext(context.NewBaseContextOpts{
Kind: types.MAIN_CONTEXT,
View: gui.Views.PatchBuildingSecondary,
WindowName: "secondary",
Key: context.PATCH_BUILDING_SECONDARY_CONTEXT_KEY,
Focusable: false,
}),
context.ContextCallbackOpts{},
),
MergeConflicts: context.NewMergeConflictsContext(
gui.Views.MergeConflicts,
context.ContextCallbackOpts{
OnFocus: OnFocusWrapper(func() error {
gui.Views.MergeConflicts.Wrap = false
return gui.refreshMergePanel(true)
}),
OnFocusLost: func(opts types.OnFocusLostOpts) error {
gui.State.Contexts.MergeConflicts.SetUserScrolling(false)
gui.State.Contexts.MergeConflicts.GetState().ResetConflictSelection()
gui.Views.MergeConflicts.Wrap = true
return nil
},
},
gui.c,
func() map[string]string {
// wrapping in a function because contexts are initialized before helpers
return gui.helpers.MergeConflicts.GetMergingOptions()
},
),
Confirmation: context.NewSimpleContext(
context.NewBaseContext(context.NewBaseContextOpts{
Kind: types.TEMPORARY_POPUP,
View: gui.Views.Confirmation,
WindowName: "confirmation",
Key: context.CONFIRMATION_CONTEXT_KEY,
Focusable: true,
HasUncontrolledBounds: true,
}),
context.ContextCallbackOpts{
OnFocus: OnFocusWrapper(gui.handleAskFocused),
OnFocusLost: func(types.OnFocusLostOpts) error {
gui.deactivateConfirmationPrompt()
return nil
},
},
),
CommitMessage: context.NewSimpleContext(
context.NewBaseContext(context.NewBaseContextOpts{
Kind: types.PERSISTENT_POPUP,
View: gui.Views.CommitMessage,
WindowName: "commitMessage",
Key: context.COMMIT_MESSAGE_CONTEXT_KEY,
Focusable: true,
HasUncontrolledBounds: true,
}),
context.ContextCallbackOpts{
OnFocus: OnFocusWrapper(gui.handleCommitMessageFocused),
},
),
Search: context.NewSimpleContext(
context.NewBaseContext(context.NewBaseContextOpts{
Kind: types.PERSISTENT_POPUP,
View: gui.Views.Search,
WindowName: "search",
Key: context.SEARCH_CONTEXT_KEY,
Focusable: true,
}),
context.ContextCallbackOpts{},
),
CommandLog: context.NewSimpleContext(
context.NewBaseContext(context.NewBaseContextOpts{
Kind: types.EXTRAS_CONTEXT,
View: gui.Views.Extras,
WindowName: "extras",
Key: context.COMMAND_LOG_CONTEXT_KEY,
Focusable: true,
}),
context.ContextCallbackOpts{
OnFocusLost: func(opts types.OnFocusLostOpts) error {
gui.Views.Extras.Autoscroll = true
return nil
},
},
),
Options: context.NewDisplayContext(context.OPTIONS_CONTEXT_KEY, gui.Views.Options, "options"),
AppStatus: context.NewDisplayContext(context.APP_STATUS_CONTEXT_KEY, gui.Views.AppStatus, "appStatus"),
SearchPrefix: context.NewDisplayContext(context.SEARCH_PREFIX_CONTEXT_KEY, gui.Views.SearchPrefix, "searchPrefix"),
Information: context.NewDisplayContext(context.INFORMATION_CONTEXT_KEY, gui.Views.Information, "information"),
Limit: context.NewDisplayContext(context.LIMIT_CONTEXT_KEY, gui.Views.Limit, "limit"),
}
return context.NewContextTree(contextCommon)
}
// using this wrapper for when an onFocus function doesn't care about any potential
@@ -21,10 +271,10 @@ func OnFocusWrapper(f func() error) func(opts types.OnFocusOpts) error {
}
}
func (gui *Gui) defaultSideContext() types.Context {
if gui.State.Modes.Filtering.Active() {
return gui.State.Contexts.LocalCommits
} else {
return gui.State.Contexts.Files
func (gui *Gui) getPatchExplorerContexts() []types.IPatchExplorerContext {
return []types.IPatchExplorerContext{
gui.State.Contexts.Staging,
gui.State.Contexts.StagingSecondary,
gui.State.Contexts.CustomPatchBuilder,
}
}
+79 -160
View File
@@ -7,121 +7,104 @@ import (
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/gui/controllers"
"github.com/jesseduffield/lazygit/pkg/gui/controllers/helpers"
"github.com/jesseduffield/lazygit/pkg/gui/modes/cherrypicking"
"github.com/jesseduffield/lazygit/pkg/gui/services/custom_commands"
"github.com/jesseduffield/lazygit/pkg/gui/status"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/snake"
)
func (gui *Gui) Helpers() *helpers.Helpers {
return gui.helpers
}
func (gui *Gui) resetHelpersAndControllers() {
func (gui *Gui) resetControllers() {
helperCommon := gui.c
refsHelper := helpers.NewRefsHelper(helperCommon)
osCommand := gui.os
model := gui.State.Model
refsHelper := helpers.NewRefsHelper(
helperCommon,
gui.git,
gui.State.Contexts,
model,
)
rebaseHelper := helpers.NewMergeAndRebaseHelper(helperCommon, refsHelper)
suggestionsHelper := helpers.NewSuggestionsHelper(helperCommon)
setCommitSummary := gui.getCommitMessageSetTextareaTextFn(func() *gocui.View { return gui.Views.CommitMessage })
setCommitDescription := gui.getCommitMessageSetTextareaTextFn(func() *gocui.View { return gui.Views.CommitDescription })
getCommitSummary := func() string {
return strings.TrimSpace(gui.Views.CommitMessage.TextArea.GetContent())
rebaseHelper := helpers.NewMergeAndRebaseHelper(helperCommon, gui.State.Contexts, gui.git, refsHelper)
suggestionsHelper := helpers.NewSuggestionsHelper(helperCommon, model, gui.refreshSuggestions)
setCommitMessage := gui.getSetTextareaTextFn(func() *gocui.View { return gui.Views.CommitMessage })
getSavedCommitMessage := func() string {
return gui.State.savedCommitMessage
}
getCommitDescription := func() string {
return strings.TrimSpace(gui.Views.CommitDescription.TextArea.GetContent())
}
commitsHelper := helpers.NewCommitsHelper(helperCommon,
getCommitSummary,
setCommitSummary,
getCommitDescription,
setCommitDescription,
)
gpgHelper := helpers.NewGpgHelper(helperCommon)
viewHelper := helpers.NewViewHelper(helperCommon, gui.State.Contexts)
recordDirectoryHelper := helpers.NewRecordDirectoryHelper(helperCommon)
patchBuildingHelper := helpers.NewPatchBuildingHelper(helperCommon)
stagingHelper := helpers.NewStagingHelper(helperCommon)
mergeConflictsHelper := helpers.NewMergeConflictsHelper(helperCommon)
refreshHelper := helpers.NewRefreshHelper(helperCommon, refsHelper, rebaseHelper, patchBuildingHelper, stagingHelper, mergeConflictsHelper, gui.fileWatcher)
diffHelper := helpers.NewDiffHelper(helperCommon)
cherryPickHelper := helpers.NewCherryPickHelper(
helperCommon,
rebaseHelper,
)
bisectHelper := helpers.NewBisectHelper(helperCommon)
windowHelper := helpers.NewWindowHelper(helperCommon, viewHelper)
modeHelper := helpers.NewModeHelper(
helperCommon,
diffHelper,
patchBuildingHelper,
cherryPickHelper,
rebaseHelper,
bisectHelper,
)
appStatusHelper := helpers.NewAppStatusHelper(
helperCommon,
func() *status.StatusManager { return gui.statusManager },
)
gpgHelper := helpers.NewGpgHelper(helperCommon, gui.os, gui.git)
gui.helpers = &helpers.Helpers{
Refs: refsHelper,
Host: helpers.NewHostHelper(helperCommon),
PatchBuilding: patchBuildingHelper,
Staging: stagingHelper,
Bisect: bisectHelper,
Suggestions: suggestionsHelper,
Files: helpers.NewFilesHelper(helperCommon),
WorkingTree: helpers.NewWorkingTreeHelper(helperCommon, refsHelper, commitsHelper, gpgHelper),
Tags: helpers.NewTagsHelper(helperCommon),
GPG: helpers.NewGpgHelper(helperCommon),
MergeAndRebase: rebaseHelper,
MergeConflicts: mergeConflictsHelper,
CherryPick: cherryPickHelper,
Upstream: helpers.NewUpstreamHelper(helperCommon, suggestionsHelper.GetRemoteBranchesSuggestionsFunc),
AmendHelper: helpers.NewAmendHelper(helperCommon, gpgHelper),
Commits: commitsHelper,
Snake: helpers.NewSnakeHelper(helperCommon),
Diff: diffHelper,
Repos: helpers.NewRecentReposHelper(helperCommon, recordDirectoryHelper, gui.onNewRepo),
RecordDirectory: recordDirectoryHelper,
Update: helpers.NewUpdateHelper(helperCommon, gui.Updater),
Window: windowHelper,
View: viewHelper,
Refresh: refreshHelper,
Confirmation: helpers.NewConfirmationHelper(helperCommon),
Mode: modeHelper,
AppStatus: appStatusHelper,
WindowArrangement: helpers.NewWindowArrangementHelper(
gui.c,
windowHelper,
modeHelper,
appStatusHelper,
Refs: refsHelper,
Host: helpers.NewHostHelper(helperCommon, gui.git),
PatchBuilding: helpers.NewPatchBuildingHelper(helperCommon, gui.git, gui.State.Contexts),
Bisect: helpers.NewBisectHelper(helperCommon, gui.git),
Suggestions: suggestionsHelper,
Files: helpers.NewFilesHelper(helperCommon, gui.git, osCommand),
WorkingTree: helpers.NewWorkingTreeHelper(helperCommon, gui.git, gui.State.Contexts, refsHelper, model, setCommitMessage, getSavedCommitMessage),
Tags: helpers.NewTagsHelper(helperCommon, gui.git),
GPG: gpgHelper,
MergeAndRebase: rebaseHelper,
MergeConflicts: helpers.NewMergeConflictsHelper(helperCommon, gui.State.Contexts, gui.git),
CherryPick: helpers.NewCherryPickHelper(
helperCommon,
gui.git,
gui.State.Contexts,
func() *cherrypicking.CherryPicking { return gui.State.Modes.CherryPicking },
rebaseHelper,
),
Upstream: helpers.NewUpstreamHelper(helperCommon, model, suggestionsHelper.GetRemoteBranchesSuggestionsFunc),
AmendHelper: helpers.NewAmendHelper(helperCommon, gui.git, gpgHelper),
}
gui.CustomCommandsClient = custom_commands.NewClient(
helperCommon,
gui.os,
gui.git,
gui.State.Contexts,
gui.helpers,
)
common := controllers.NewControllerCommon(helperCommon, gui)
common := controllers.NewControllerCommon(
helperCommon,
osCommand,
gui.git,
gui.helpers,
model,
gui.State.Contexts,
gui.State.Modes,
&gui.Mutexes,
)
syncController := controllers.NewSyncController(
common,
)
submodulesController := controllers.NewSubmodulesController(common)
submodulesController := controllers.NewSubmodulesController(
common,
gui.enterSubmodule,
)
bisectController := controllers.NewBisectController(common)
getCommitMessage := func() string {
return strings.TrimSpace(gui.Views.CommitMessage.TextArea.GetContent())
}
onCommitAttempt := func(message string) {
gui.State.savedCommitMessage = message
gui.Views.CommitMessage.ClearTextArea()
}
onCommitSuccess := func() {
gui.State.savedCommitMessage = ""
_ = gui.c.Refresh(types.RefreshOptions{
Scope: []types.RefreshableView{types.STAGING},
})
}
commitMessageController := controllers.NewCommitMessageController(
common,
)
commitDescriptionController := controllers.NewCommitDescriptionController(
common,
getCommitMessage,
onCommitAttempt,
onCommitSuccess,
)
remoteBranchesController := controllers.NewRemoteBranchesController(common)
@@ -131,6 +114,9 @@ func (gui *Gui) resetHelpersAndControllers() {
tagsController := controllers.NewTagsController(common)
filesController := controllers.NewFilesController(
common,
gui.enterSubmodule,
setCommitMessage,
getSavedCommitMessage,
)
mergeConflictsController := controllers.NewMergeConflictsController(common)
remotesController := controllers.NewRemotesController(
@@ -151,34 +137,7 @@ func (gui *Gui) resetHelpersAndControllers() {
stagingController := controllers.NewStagingController(common, gui.State.Contexts.Staging, gui.State.Contexts.StagingSecondary, false)
stagingSecondaryController := controllers.NewStagingController(common, gui.State.Contexts.StagingSecondary, gui.State.Contexts.Staging, true)
patchBuildingController := controllers.NewPatchBuildingController(common)
snakeController := controllers.NewSnakeController(common)
reflogCommitsController := controllers.NewReflogCommitsController(common)
subCommitsController := controllers.NewSubCommitsController(common)
statusController := controllers.NewStatusController(common)
commandLogController := controllers.NewCommandLogController(common)
confirmationController := controllers.NewConfirmationController(common)
suggestionsController := controllers.NewSuggestionsController(common)
jumpToSideWindowController := controllers.NewJumpToSideWindowController(common)
sideWindowControllerFactory := controllers.NewSideWindowControllerFactory(common)
// allow for navigating between side window contexts
for _, context := range []types.Context{
gui.State.Contexts.Status,
gui.State.Contexts.Remotes,
gui.State.Contexts.Tags,
gui.State.Contexts.Branches,
gui.State.Contexts.RemoteBranches,
gui.State.Contexts.Files,
gui.State.Contexts.Submodules,
gui.State.Contexts.ReflogCommits,
gui.State.Contexts.LocalCommits,
gui.State.Contexts.CommitFiles,
gui.State.Contexts.SubCommits,
gui.State.Contexts.Stash,
} {
controllers.AttachControllers(context, sideWindowControllerFactory.Create(context))
}
snakeController := controllers.NewSnakeController(common, func() *snake.Game { return gui.snakeGame })
setSubCommits := func(commits []*models.Commit) {
gui.Mutexes.SubCommitsMutex.Lock()
@@ -204,7 +163,7 @@ func (gui *Gui) resetHelpersAndControllers() {
gui.State.Contexts.Stash,
} {
controllers.AttachControllers(context, controllers.NewSwitchToDiffFilesController(
common, context, gui.State.Contexts.CommitFiles,
common, gui.SwitchToCommitFilesContext, context,
))
}
@@ -216,14 +175,6 @@ func (gui *Gui) resetHelpersAndControllers() {
controllers.AttachControllers(context, controllers.NewBasicCommitsController(common, context))
}
controllers.AttachControllers(gui.State.Contexts.ReflogCommits,
reflogCommitsController,
)
controllers.AttachControllers(gui.State.Contexts.SubCommits,
subCommitsController,
)
// TODO: add scroll controllers for main panels (need to bring some more functionality across for that e.g. reading more from the currently displayed git command)
controllers.AttachControllers(gui.State.Contexts.Staging,
stagingController,
@@ -299,36 +250,15 @@ func (gui *Gui) resetHelpersAndControllers() {
commitMessageController,
)
controllers.AttachControllers(gui.State.Contexts.CommitDescription,
commitDescriptionController,
)
controllers.AttachControllers(gui.State.Contexts.RemoteBranches,
remoteBranchesController,
)
controllers.AttachControllers(gui.State.Contexts.Status,
statusController,
)
controllers.AttachControllers(gui.State.Contexts.CommandLog,
commandLogController,
)
controllers.AttachControllers(gui.State.Contexts.Confirmation,
confirmationController,
)
controllers.AttachControllers(gui.State.Contexts.Suggestions,
suggestionsController,
)
controllers.AttachControllers(gui.State.Contexts.Global,
syncController,
undoController,
globalController,
contextLinesController,
jumpToSideWindowController,
)
controllers.AttachControllers(gui.State.Contexts.Snake,
@@ -336,19 +266,8 @@ func (gui *Gui) resetHelpersAndControllers() {
)
// this must come last so that we've got our click handlers defined against the context
listControllerFactory := controllers.NewListControllerFactory(common)
for _, context := range gui.c.Context().AllList() {
listControllerFactory := controllers.NewListControllerFactory(gui.c)
for _, context := range gui.getListContexts() {
controllers.AttachControllers(context, listControllerFactory.Create(context))
}
}
func (gui *Gui) getCommitMessageSetTextareaTextFn(getView func() *gocui.View) func(string) {
return func(text string) {
// using a getView function so that we don't need to worry about when the view is created
view := getView()
view.ClearTextArea()
view.TextArea.TypeString(text)
gui.helpers.Confirmation.ResizeCommitMessagePanels()
view.RenderTextArea()
}
}
-3
View File
@@ -7,8 +7,5 @@ func AttachControllers(context types.Context, controllers ...types.IController)
context.AddKeybindingsFn(controller.GetKeybindings)
context.AddMouseKeybindingsFn(controller.GetMouseKeybindings)
context.AddOnClickFn(controller.GetOnClick())
context.AddOnRenderToMainFn(controller.GetOnRenderToMain())
context.AddOnFocusFn(controller.GetOnFocus())
context.AddOnFocusLostFn(controller.GetOnFocusLost())
}
}
-12
View File
@@ -18,15 +18,3 @@ func (self *baseController) GetMouseKeybindings(opts types.KeybindingsOpts) []*g
func (self *baseController) GetOnClick() func() error {
return nil
}
func (self *baseController) GetOnRenderToMain() func() error {
return nil
}
func (self *baseController) GetOnFocus() func(types.OnFocusOpts) error {
return nil
}
func (self *baseController) GetOnFocusLost() func(types.OnFocusLostOpts) error {
return nil
}
+22 -22
View File
@@ -20,15 +20,15 @@ type ContainsCommits interface {
type BasicCommitsController struct {
baseController
c *ControllerCommon
*controllerCommon
context ContainsCommits
}
func NewBasicCommitsController(controllerCommon *ControllerCommon, context ContainsCommits) *BasicCommitsController {
func NewBasicCommitsController(controllerCommon *controllerCommon, context ContainsCommits) *BasicCommitsController {
return &BasicCommitsController{
baseController: baseController{},
c: controllerCommon,
context: context,
baseController: baseController{},
controllerCommon: controllerCommon,
context: context,
}
}
@@ -73,7 +73,7 @@ func (self *BasicCommitsController) GetKeybindings(opts types.KeybindingsOpts) [
},
{
Key: opts.GetKey(opts.Config.Commits.ResetCherryPick),
Handler: self.c.Helpers().CherryPick.Reset,
Handler: self.helpers.CherryPick.Reset,
Description: self.c.Tr.LcResetCherryPick,
},
}
@@ -141,7 +141,7 @@ func (self *BasicCommitsController) copyCommitAttribute(commit *models.Commit) e
func (self *BasicCommitsController) copyCommitSHAToClipboard(commit *models.Commit) error {
self.c.LogAction(self.c.Tr.Actions.CopyCommitSHAToClipboard)
if err := self.c.OS().CopyToClipboard(commit.Sha); err != nil {
if err := self.os.CopyToClipboard(commit.Sha); err != nil {
return self.c.Error(err)
}
@@ -150,13 +150,13 @@ func (self *BasicCommitsController) copyCommitSHAToClipboard(commit *models.Comm
}
func (self *BasicCommitsController) copyCommitURLToClipboard(commit *models.Commit) error {
url, err := self.c.Helpers().Host.GetCommitURL(commit.Sha)
url, err := self.helpers.Host.GetCommitURL(commit.Sha)
if err != nil {
return err
}
self.c.LogAction(self.c.Tr.Actions.CopyCommitURLToClipboard)
if err := self.c.OS().CopyToClipboard(url); err != nil {
if err := self.os.CopyToClipboard(url); err != nil {
return self.c.Error(err)
}
@@ -165,13 +165,13 @@ func (self *BasicCommitsController) copyCommitURLToClipboard(commit *models.Comm
}
func (self *BasicCommitsController) copyCommitDiffToClipboard(commit *models.Commit) error {
diff, err := self.c.Git().Commit.GetCommitDiff(commit.Sha)
diff, err := self.git.Commit.GetCommitDiff(commit.Sha)
if err != nil {
return self.c.Error(err)
}
self.c.LogAction(self.c.Tr.Actions.CopyCommitDiffToClipboard)
if err := self.c.OS().CopyToClipboard(diff); err != nil {
if err := self.os.CopyToClipboard(diff); err != nil {
return self.c.Error(err)
}
@@ -180,7 +180,7 @@ func (self *BasicCommitsController) copyCommitDiffToClipboard(commit *models.Com
}
func (self *BasicCommitsController) copyAuthorToClipboard(commit *models.Commit) error {
author, err := self.c.Git().Commit.GetCommitAuthor(commit.Sha)
author, err := self.git.Commit.GetCommitAuthor(commit.Sha)
if err != nil {
return self.c.Error(err)
}
@@ -188,7 +188,7 @@ func (self *BasicCommitsController) copyAuthorToClipboard(commit *models.Commit)
formattedAuthor := fmt.Sprintf("%s <%s>", author.Name, author.Email)
self.c.LogAction(self.c.Tr.Actions.CopyCommitAuthorToClipboard)
if err := self.c.OS().CopyToClipboard(formattedAuthor); err != nil {
if err := self.os.CopyToClipboard(formattedAuthor); err != nil {
return self.c.Error(err)
}
@@ -197,13 +197,13 @@ func (self *BasicCommitsController) copyAuthorToClipboard(commit *models.Commit)
}
func (self *BasicCommitsController) copyCommitMessageToClipboard(commit *models.Commit) error {
message, err := self.c.Git().Commit.GetCommitMessage(commit.Sha)
message, err := self.git.Commit.GetCommitMessage(commit.Sha)
if err != nil {
return self.c.Error(err)
}
self.c.LogAction(self.c.Tr.Actions.CopyCommitMessageToClipboard)
if err := self.c.OS().CopyToClipboard(message); err != nil {
if err := self.os.CopyToClipboard(message); err != nil {
return self.c.Error(err)
}
@@ -212,13 +212,13 @@ func (self *BasicCommitsController) copyCommitMessageToClipboard(commit *models.
}
func (self *BasicCommitsController) openInBrowser(commit *models.Commit) error {
url, err := self.c.Helpers().Host.GetCommitURL(commit.Sha)
url, err := self.helpers.Host.GetCommitURL(commit.Sha)
if err != nil {
return self.c.Error(err)
}
self.c.LogAction(self.c.Tr.Actions.OpenCommitInBrowser)
if err := self.c.OS().OpenLink(url); err != nil {
if err := self.os.OpenLink(url); err != nil {
return self.c.Error(err)
}
@@ -226,11 +226,11 @@ func (self *BasicCommitsController) openInBrowser(commit *models.Commit) error {
}
func (self *BasicCommitsController) newBranch(commit *models.Commit) error {
return self.c.Helpers().Refs.NewBranch(commit.RefName(), commit.Description(), "")
return self.helpers.Refs.NewBranch(commit.RefName(), commit.Description(), "")
}
func (self *BasicCommitsController) createResetMenu(commit *models.Commit) error {
return self.c.Helpers().Refs.CreateGitResetMenu(commit.Sha)
return self.helpers.Refs.CreateGitResetMenu(commit.Sha)
}
func (self *BasicCommitsController) checkout(commit *models.Commit) error {
@@ -239,15 +239,15 @@ func (self *BasicCommitsController) checkout(commit *models.Commit) error {
Prompt: self.c.Tr.SureCheckoutThisCommit,
HandleConfirm: func() error {
self.c.LogAction(self.c.Tr.Actions.CheckoutCommit)
return self.c.Helpers().Refs.CheckoutRef(commit.Sha, types.CheckoutRefOptions{})
return self.helpers.Refs.CheckoutRef(commit.Sha, types.CheckoutRefOptions{})
},
})
}
func (self *BasicCommitsController) copy(commit *models.Commit) error {
return self.c.Helpers().CherryPick.Copy(commit, self.context.GetCommits(), self.context)
return self.helpers.CherryPick.Copy(commit, self.context.GetCommits(), self.context)
}
func (self *BasicCommitsController) copyRange(*models.Commit) error {
return self.c.Helpers().CherryPick.CopyRange(self.context.GetSelectedLineIdx(), self.context.GetCommits(), self.context)
return self.helpers.CherryPick.CopyRange(self.context.GetSelectedLineIdx(), self.context.GetCommits(), self.context)
}
+24 -24
View File
@@ -12,17 +12,17 @@ import (
type BisectController struct {
baseController
c *ControllerCommon
*controllerCommon
}
var _ types.IController = &BisectController{}
func NewBisectController(
common *ControllerCommon,
common *controllerCommon,
) *BisectController {
return &BisectController{
baseController: baseController{},
c: common,
baseController: baseController{},
controllerCommon: common,
}
}
@@ -42,7 +42,7 @@ func (self *BisectController) GetKeybindings(opts types.KeybindingsOpts) []*type
func (self *BisectController) openMenu(commit *models.Commit) error {
// no shame in getting this directly rather than using the cached value
// given how cheap it is to obtain
info := self.c.Git().Bisect.GetInfo()
info := self.git.Bisect.GetInfo()
if info.Started() {
return self.openMidBisectMenu(info, commit)
} else {
@@ -63,14 +63,14 @@ func (self *BisectController) openMidBisectMenu(info *git_commands.BisectInfo, c
selectCurrentAfter := info.GetCurrentSha() == "" || info.GetCurrentSha() == commit.Sha
// we need to wait to reselect if our bisect commits aren't ancestors of our 'start'
// ref, because we'll be reloading our commits in that case.
waitToReselect := selectCurrentAfter && !self.c.Git().Bisect.ReachableFromStart(info)
waitToReselect := selectCurrentAfter && !self.git.Bisect.ReachableFromStart(info)
menuItems := []*types.MenuItem{
{
Label: fmt.Sprintf(self.c.Tr.Bisect.Mark, commit.ShortSha(), info.NewTerm()),
OnPress: func() error {
self.c.LogAction(self.c.Tr.Actions.BisectMark)
if err := self.c.Git().Bisect.Mark(commit.Sha, info.NewTerm()); err != nil {
if err := self.git.Bisect.Mark(commit.Sha, info.NewTerm()); err != nil {
return self.c.Error(err)
}
@@ -82,7 +82,7 @@ func (self *BisectController) openMidBisectMenu(info *git_commands.BisectInfo, c
Label: fmt.Sprintf(self.c.Tr.Bisect.Mark, commit.ShortSha(), info.OldTerm()),
OnPress: func() error {
self.c.LogAction(self.c.Tr.Actions.BisectMark)
if err := self.c.Git().Bisect.Mark(commit.Sha, info.OldTerm()); err != nil {
if err := self.git.Bisect.Mark(commit.Sha, info.OldTerm()); err != nil {
return self.c.Error(err)
}
@@ -94,7 +94,7 @@ func (self *BisectController) openMidBisectMenu(info *git_commands.BisectInfo, c
Label: fmt.Sprintf(self.c.Tr.Bisect.Skip, commit.ShortSha()),
OnPress: func() error {
self.c.LogAction(self.c.Tr.Actions.BisectSkip)
if err := self.c.Git().Bisect.Skip(commit.Sha); err != nil {
if err := self.git.Bisect.Skip(commit.Sha); err != nil {
return self.c.Error(err)
}
@@ -105,7 +105,7 @@ func (self *BisectController) openMidBisectMenu(info *git_commands.BisectInfo, c
{
Label: self.c.Tr.Bisect.ResetOption,
OnPress: func() error {
return self.c.Helpers().Bisect.Reset()
return self.helpers.Bisect.Reset()
},
Key: 'r',
},
@@ -125,15 +125,15 @@ func (self *BisectController) openStartBisectMenu(info *git_commands.BisectInfo,
Label: fmt.Sprintf(self.c.Tr.Bisect.MarkStart, commit.ShortSha(), info.NewTerm()),
OnPress: func() error {
self.c.LogAction(self.c.Tr.Actions.StartBisect)
if err := self.c.Git().Bisect.Start(); err != nil {
if err := self.git.Bisect.Start(); err != nil {
return self.c.Error(err)
}
if err := self.c.Git().Bisect.Mark(commit.Sha, info.NewTerm()); err != nil {
if err := self.git.Bisect.Mark(commit.Sha, info.NewTerm()); err != nil {
return self.c.Error(err)
}
return self.c.Helpers().Bisect.PostBisectCommandRefresh()
return self.helpers.Bisect.PostBisectCommandRefresh()
},
Key: 'b',
},
@@ -141,15 +141,15 @@ func (self *BisectController) openStartBisectMenu(info *git_commands.BisectInfo,
Label: fmt.Sprintf(self.c.Tr.Bisect.MarkStart, commit.ShortSha(), info.OldTerm()),
OnPress: func() error {
self.c.LogAction(self.c.Tr.Actions.StartBisect)
if err := self.c.Git().Bisect.Start(); err != nil {
if err := self.git.Bisect.Start(); err != nil {
return self.c.Error(err)
}
if err := self.c.Git().Bisect.Mark(commit.Sha, info.OldTerm()); err != nil {
if err := self.git.Bisect.Mark(commit.Sha, info.OldTerm()); err != nil {
return self.c.Error(err)
}
return self.c.Helpers().Bisect.PostBisectCommandRefresh()
return self.helpers.Bisect.PostBisectCommandRefresh()
},
Key: 'g',
},
@@ -163,7 +163,7 @@ func (self *BisectController) showBisectCompleteMessage(candidateShas []string)
prompt = self.c.Tr.Bisect.CompletePromptIndeterminate
}
formattedCommits, err := self.c.Git().Commit.GetCommitsOneline(candidateShas)
formattedCommits, err := self.git.Commit.GetCommitsOneline(candidateShas)
if err != nil {
return self.c.Error(err)
}
@@ -173,17 +173,17 @@ func (self *BisectController) showBisectCompleteMessage(candidateShas []string)
Prompt: fmt.Sprintf(prompt, strings.TrimSpace(formattedCommits)),
HandleConfirm: func() error {
self.c.LogAction(self.c.Tr.Actions.ResetBisect)
if err := self.c.Git().Bisect.Reset(); err != nil {
if err := self.git.Bisect.Reset(); err != nil {
return self.c.Error(err)
}
return self.c.Helpers().Bisect.PostBisectCommandRefresh()
return self.helpers.Bisect.PostBisectCommandRefresh()
},
})
}
func (self *BisectController) afterMark(selectCurrent bool, waitToReselect bool) error {
done, candidateShas, err := self.c.Git().Bisect.IsDone()
done, candidateShas, err := self.git.Bisect.IsDone()
if err != nil {
return self.c.Error(err)
}
@@ -211,15 +211,15 @@ func (self *BisectController) afterBisectMarkRefresh(selectCurrent bool, waitToR
} else {
selectFn()
return self.c.Helpers().Bisect.PostBisectCommandRefresh()
return self.helpers.Bisect.PostBisectCommandRefresh()
}
}
func (self *BisectController) selectCurrentBisectCommit() {
info := self.c.Git().Bisect.GetInfo()
info := self.git.Bisect.GetInfo()
if info.GetCurrentSha() != "" {
// find index of commit with that sha, move cursor to that.
for i, commit := range self.c.Model().Commits {
for i, commit := range self.model.Commits {
if commit.Sha == info.GetCurrentSha() {
self.context().SetSelectedLineIdx(i)
_ = self.context().HandleFocus(types.OnFocusOpts{})
@@ -245,5 +245,5 @@ func (self *BisectController) Context() types.Context {
}
func (self *BisectController) context() *context.LocalCommitsContext {
return self.c.Contexts().LocalCommits
return self.contexts.LocalCommits
}
+34 -58
View File
@@ -14,17 +14,17 @@ import (
type BranchesController struct {
baseController
c *ControllerCommon
*controllerCommon
}
var _ types.IController = &BranchesController{}
func NewBranchesController(
common *ControllerCommon,
common *controllerCommon,
) *BranchesController {
return &BranchesController{
baseController: baseController{},
c: common,
baseController: baseController{},
controllerCommon: common,
}
}
@@ -111,30 +111,6 @@ func (self *BranchesController) GetKeybindings(opts types.KeybindingsOpts) []*ty
}
}
func (self *BranchesController) GetOnRenderToMain() func() error {
return func() error {
return self.c.Helpers().Diff.WithDiffModeCheck(func() error {
var task types.UpdateTask
branch := self.context().GetSelected()
if branch == nil {
task = types.NewRenderStringTask(self.c.Tr.NoBranchesThisRepo)
} else {
cmdObj := self.c.Git().Branch.GetGraphCmdObj(branch.FullRefName())
task = types.NewRunPtyTask(cmdObj.GetCmd())
}
return self.c.RenderToMainViews(types.RefreshMainOpts{
Pair: self.c.MainViewPairs().Normal,
Main: &types.ViewUpdateOpts{
Title: self.c.Tr.LogTitle,
Task: task,
},
})
})
}
}
func (self *BranchesController) setUpstream(selectedBranch *models.Branch) error {
return self.c.Menu(types.CreateMenuOptions{
Title: self.c.Tr.Actions.SetUnsetUpstream,
@@ -142,7 +118,7 @@ func (self *BranchesController) setUpstream(selectedBranch *models.Branch) error
{
LabelColumns: []string{self.c.Tr.LcUnsetUpstream},
OnPress: func() error {
if err := self.c.Git().Branch.UnsetUpstream(selectedBranch.Name); err != nil {
if err := self.git.Branch.UnsetUpstream(selectedBranch.Name); err != nil {
return self.c.Error(err)
}
if err := self.c.Refresh(types.RefreshOptions{
@@ -161,13 +137,13 @@ func (self *BranchesController) setUpstream(selectedBranch *models.Branch) error
{
LabelColumns: []string{self.c.Tr.LcSetUpstream},
OnPress: func() error {
return self.c.Helpers().Upstream.PromptForUpstreamWithoutInitialContent(selectedBranch, func(upstream string) error {
upstreamRemote, upstreamBranch, err := self.c.Helpers().Upstream.ParseUpstream(upstream)
return self.helpers.Upstream.PromptForUpstreamWithoutInitialContent(selectedBranch, func(upstream string) error {
upstreamRemote, upstreamBranch, err := self.helpers.Upstream.ParseUpstream(upstream)
if err != nil {
return self.c.Error(err)
}
if err := self.c.Git().Branch.SetUpstream(upstreamRemote, upstreamBranch, selectedBranch.Name); err != nil {
if err := self.git.Branch.SetUpstream(upstreamRemote, upstreamBranch, selectedBranch.Name); err != nil {
return self.c.Error(err)
}
if err := self.c.Refresh(types.RefreshOptions{
@@ -193,16 +169,16 @@ func (self *BranchesController) Context() types.Context {
}
func (self *BranchesController) context() *context.BranchesContext {
return self.c.Contexts().Branches
return self.contexts.Branches
}
func (self *BranchesController) press(selectedBranch *models.Branch) error {
if selectedBranch == self.c.Helpers().Refs.GetCheckedOutRef() {
if selectedBranch == self.helpers.Refs.GetCheckedOutRef() {
return self.c.ErrorMsg(self.c.Tr.AlreadyCheckedOutBranch)
}
self.c.LogAction(self.c.Tr.Actions.CheckoutBranch)
return self.c.Helpers().Refs.CheckoutRef(selectedBranch.Name, types.CheckoutRefOptions{})
return self.helpers.Refs.CheckoutRef(selectedBranch.Name, types.CheckoutRefOptions{})
}
func (self *BranchesController) handleCreatePullRequest(selectedBranch *models.Branch) error {
@@ -210,7 +186,7 @@ func (self *BranchesController) handleCreatePullRequest(selectedBranch *models.B
}
func (self *BranchesController) handleCreatePullRequestMenu(selectedBranch *models.Branch) error {
checkedOutBranch := self.c.Helpers().Refs.GetCheckedOutRef()
checkedOutBranch := self.helpers.Refs.GetCheckedOutRef()
return self.createPullRequestMenu(selectedBranch, checkedOutBranch)
}
@@ -218,18 +194,18 @@ func (self *BranchesController) handleCreatePullRequestMenu(selectedBranch *mode
func (self *BranchesController) copyPullRequestURL() error {
branch := self.context().GetSelected()
branchExistsOnRemote := self.c.Git().Remote.CheckRemoteBranchExists(branch.Name)
branchExistsOnRemote := self.git.Remote.CheckRemoteBranchExists(branch.Name)
if !branchExistsOnRemote {
return self.c.Error(errors.New(self.c.Tr.NoBranchOnRemote))
}
url, err := self.c.Helpers().Host.GetPullRequestURL(branch.Name, "")
url, err := self.helpers.Host.GetPullRequestURL(branch.Name, "")
if err != nil {
return self.c.Error(err)
}
self.c.LogAction(self.c.Tr.Actions.CopyPullRequestURL)
if err := self.c.OS().CopyToClipboard(url); err != nil {
if err := self.os.CopyToClipboard(url); err != nil {
return self.c.Error(err)
}
@@ -248,7 +224,7 @@ func (self *BranchesController) forceCheckout() error {
Prompt: message,
HandleConfirm: func() error {
self.c.LogAction(self.c.Tr.Actions.ForceCheckoutBranch)
if err := self.c.Git().Branch.Checkout(branch.Name, git_commands.CheckoutOptions{Force: true}); err != nil {
if err := self.git.Branch.Checkout(branch.Name, git_commands.CheckoutOptions{Force: true}); err != nil {
_ = self.c.Error(err)
}
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
@@ -259,10 +235,10 @@ func (self *BranchesController) forceCheckout() error {
func (self *BranchesController) checkoutByName() error {
return self.c.Prompt(types.PromptOpts{
Title: self.c.Tr.BranchName + ":",
FindSuggestionsFunc: self.c.Helpers().Suggestions.GetRefsSuggestionsFunc(),
FindSuggestionsFunc: self.helpers.Suggestions.GetRefsSuggestionsFunc(),
HandleConfirm: func(response string) error {
self.c.LogAction("Checkout branch")
return self.c.Helpers().Refs.CheckoutRef(response, types.CheckoutRefOptions{
return self.helpers.Refs.CheckoutRef(response, types.CheckoutRefOptions{
OnRefNotFound: func(ref string) error {
return self.c.Confirm(types.ConfirmOpts{
Title: self.c.Tr.BranchNotFoundTitle,
@@ -284,7 +260,7 @@ func (self *BranchesController) createNewBranchWithName(newBranchName string) er
return nil
}
if err := self.c.Git().Branch.New(newBranchName, branch.FullRefName()); err != nil {
if err := self.git.Branch.New(newBranchName, branch.FullRefName()); err != nil {
return self.c.Error(err)
}
@@ -293,7 +269,7 @@ func (self *BranchesController) createNewBranchWithName(newBranchName string) er
}
func (self *BranchesController) delete(branch *models.Branch) error {
checkedOutBranch := self.c.Helpers().Refs.GetCheckedOutRef()
checkedOutBranch := self.helpers.Refs.GetCheckedOutRef()
if checkedOutBranch.Name == branch.Name {
return self.c.ErrorMsg(self.c.Tr.CantDeleteCheckOutBranch)
}
@@ -320,7 +296,7 @@ func (self *BranchesController) deleteWithForce(selectedBranch *models.Branch, f
Prompt: message,
HandleConfirm: func() error {
self.c.LogAction(self.c.Tr.Actions.DeleteBranch)
if err := self.c.Git().Branch.Delete(selectedBranch.Name, force); err != nil {
if err := self.git.Branch.Delete(selectedBranch.Name, force); err != nil {
errMessage := err.Error()
if !force && strings.Contains(errMessage, "git branch -D ") {
return self.deleteWithForce(selectedBranch, true)
@@ -334,12 +310,12 @@ func (self *BranchesController) deleteWithForce(selectedBranch *models.Branch, f
func (self *BranchesController) merge() error {
selectedBranchName := self.context().GetSelected().Name
return self.c.Helpers().MergeAndRebase.MergeRefIntoCheckedOutBranch(selectedBranchName)
return self.helpers.MergeAndRebase.MergeRefIntoCheckedOutBranch(selectedBranchName)
}
func (self *BranchesController) rebase() error {
selectedBranchName := self.context().GetSelected().Name
return self.c.Helpers().MergeAndRebase.RebaseOntoRef(selectedBranchName)
return self.helpers.MergeAndRebase.RebaseOntoRef(selectedBranchName)
}
func (self *BranchesController) fastForward(branch *models.Branch) error {
@@ -364,10 +340,10 @@ func (self *BranchesController) fastForward(branch *models.Branch) error {
)
return self.c.WithLoaderPanel(message, func() error {
if branch == self.c.Helpers().Refs.GetCheckedOutRef() {
if branch == self.helpers.Refs.GetCheckedOutRef() {
self.c.LogAction(action)
err := self.c.Git().Sync.Pull(
err := self.git.Sync.Pull(
git_commands.PullOptions{
RemoteName: branch.UpstreamRemote,
BranchName: branch.UpstreamBranch,
@@ -381,7 +357,7 @@ func (self *BranchesController) fastForward(branch *models.Branch) error {
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
} else {
self.c.LogAction(action)
err := self.c.Git().Sync.FastForward(branch.Name, branch.UpstreamRemote, branch.UpstreamBranch)
err := self.git.Sync.FastForward(branch.Name, branch.UpstreamRemote, branch.UpstreamBranch)
if err != nil {
_ = self.c.Error(err)
}
@@ -393,11 +369,11 @@ func (self *BranchesController) fastForward(branch *models.Branch) error {
}
func (self *BranchesController) createTag(branch *models.Branch) error {
return self.c.Helpers().Tags.CreateTagMenu(branch.FullRefName(), func() {})
return self.helpers.Tags.CreateTagMenu(branch.FullRefName(), func() {})
}
func (self *BranchesController) createResetMenu(selectedBranch *models.Branch) error {
return self.c.Helpers().Refs.CreateGitResetMenu(selectedBranch.Name)
return self.helpers.Refs.CreateGitResetMenu(selectedBranch.Name)
}
func (self *BranchesController) rename(branch *models.Branch) error {
@@ -407,7 +383,7 @@ func (self *BranchesController) rename(branch *models.Branch) error {
InitialContent: branch.Name,
HandleConfirm: func(newBranchName string) error {
self.c.LogAction(self.c.Tr.Actions.RenameBranch)
if err := self.c.Git().Branch.Rename(branch.Name, newBranchName); err != nil {
if err := self.git.Branch.Rename(branch.Name, newBranchName); err != nil {
return self.c.Error(err)
}
@@ -415,7 +391,7 @@ func (self *BranchesController) rename(branch *models.Branch) error {
_ = self.c.Refresh(types.RefreshOptions{Mode: types.SYNC, Scope: []types.RefreshableView{types.BRANCHES}})
// now that we've got our stuff again we need to find that branch and reselect it.
for i, newBranch := range self.c.Model().Branches {
for i, newBranch := range self.model.Branches {
if newBranch.Name == newBranchName {
self.context().SetSelectedLineIdx(i)
if err := self.context().HandleRender(); err != nil {
@@ -444,7 +420,7 @@ func (self *BranchesController) rename(branch *models.Branch) error {
}
func (self *BranchesController) newBranch(selectedBranch *models.Branch) error {
return self.c.Helpers().Refs.NewBranch(selectedBranch.FullRefName(), selectedBranch.RefName(), "")
return self.helpers.Refs.NewBranch(selectedBranch.FullRefName(), selectedBranch.RefName(), "")
}
func (self *BranchesController) createPullRequestMenu(selectedBranch *models.Branch, checkedOutBranch *models.Branch) error {
@@ -467,7 +443,7 @@ func (self *BranchesController) createPullRequestMenu(selectedBranch *models.Bra
OnPress: func() error {
return self.c.Prompt(types.PromptOpts{
Title: branch.Name + " →",
FindSuggestionsFunc: self.c.Helpers().Suggestions.GetBranchNameSuggestionsFunc(),
FindSuggestionsFunc: self.helpers.Suggestions.GetBranchNameSuggestionsFunc(),
HandleConfirm: func(targetBranchName string) error {
return self.createPullRequest(branch.Name, targetBranchName)
},
@@ -495,14 +471,14 @@ func (self *BranchesController) createPullRequestMenu(selectedBranch *models.Bra
}
func (self *BranchesController) createPullRequest(from string, to string) error {
url, err := self.c.Helpers().Host.GetPullRequestURL(from, to)
url, err := self.helpers.Host.GetPullRequestURL(from, to)
if err != nil {
return self.c.Error(err)
}
self.c.LogAction(self.c.Tr.Actions.OpenPullRequest)
if err := self.c.OS().OpenLink(url); err != nil {
if err := self.os.OpenLink(url); err != nil {
return self.c.Error(err)
}
@@ -1,42 +0,0 @@
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
}
@@ -1,60 +0,0 @@
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,126 +1,79 @@
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
c *ControllerCommon
*controllerCommon
getCommitMessage func() string
onCommitAttempt func(message string)
onCommitSuccess func()
}
var _ types.IController = &CommitMessageController{}
func NewCommitMessageController(
common *ControllerCommon,
common *controllerCommon,
getCommitMessage func() string,
onCommitAttempt func(message string),
onCommitSuccess func(),
) *CommitMessageController {
return &CommitMessageController{
baseController: baseController{},
c: common,
baseController: baseController{},
controllerCommon: common,
getCommitMessage: getCommitMessage,
onCommitAttempt: onCommitAttempt,
onCommitSuccess: onCommitSuccess,
}
}
// 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,
Description: self.c.Tr.LcConfirm,
Key: opts.GetKey(opts.Config.Universal.SubmitEditorText),
Handler: self.confirm,
},
{
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,
Key: opts.GetKey(opts.Config.Universal.Return),
Handler: self.close,
},
}
return bindings
}
func (self *CommitMessageController) GetOnFocusLost() func(types.OnFocusLostOpts) error {
return func(types.OnFocusLostOpts) error {
self.context().RenderCommitLength()
return nil
}
}
func (self *CommitMessageController) Context() types.Context {
return self.context()
}
func (self *CommitMessageController) context() *context.CommitMessageContext {
return self.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
// 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) confirm() error {
return self.c.Helpers().Commits.HandleCommitConfirm()
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
})
}
func (self *CommitMessageController) close() error {
return self.c.Helpers().Commits.CloseCommitMessagePanel()
return self.c.PopContext()
}
+25 -58
View File
@@ -11,17 +11,17 @@ import (
type CommitFilesController struct {
baseController
c *ControllerCommon
*controllerCommon
}
var _ types.IController = &CommitFilesController{}
func NewCommitFilesController(
common *ControllerCommon,
common *controllerCommon,
) *CommitFilesController {
return &CommitFilesController{
baseController: baseController{},
c: common,
baseController: baseController{},
controllerCommon: common,
}
}
@@ -99,40 +99,7 @@ func (self *CommitFilesController) Context() types.Context {
}
func (self *CommitFilesController) context() *context.CommitFilesContext {
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),
})
}
return self.contexts.CommitFiles
}
func (self *CommitFilesController) onClickMain(opts gocui.ViewMouseBindingOpts) error {
@@ -145,7 +112,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.c.Git().WorkingTree.CheckoutFile(self.context().GetRef().RefName(), node.GetPath()); err != nil {
if err := self.git.WorkingTree.CheckoutFile(self.context().GetRef().RefName(), node.GetPath()); err != nil {
return self.c.Error(err)
}
@@ -153,7 +120,7 @@ func (self *CommitFilesController) checkout(node *filetree.CommitFileNode) error
}
func (self *CommitFilesController) discard(node *filetree.CommitFileNode) error {
if ok, err := self.c.Helpers().PatchBuilding.ValidateNormalWorkingTreeState(); !ok {
if ok, err := self.helpers.PatchBuilding.ValidateNormalWorkingTreeState(); !ok {
return err
}
@@ -163,8 +130,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.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 {
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 {
return err
}
}
@@ -176,7 +143,7 @@ func (self *CommitFilesController) discard(node *filetree.CommitFileNode) error
}
func (self *CommitFilesController) open(node *filetree.CommitFileNode) error {
return self.c.Helpers().Files.OpenFile(node.GetPath())
return self.helpers.Files.OpenFile(node.GetPath())
}
func (self *CommitFilesController) edit(node *filetree.CommitFileNode) error {
@@ -184,13 +151,13 @@ func (self *CommitFilesController) edit(node *filetree.CommitFileNode) error {
return self.c.ErrorMsg(self.c.Tr.ErrCannotEditDirectory)
}
return self.c.Helpers().Files.EditFile(node.GetPath())
return self.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.c.Git().Patch.PatchBuilder.Active() {
if !self.git.Patch.PatchBuilder.Active() {
if err := self.startPatchBuilder(); err != nil {
return err
}
@@ -199,34 +166,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.c.Git().Patch.PatchBuilder.GetFileStatus(file.Name, self.context().GetRef().RefName()) != patch.WHOLE
return self.git.Patch.PatchBuilder.GetFileStatus(file.Name, self.context().GetRef().RefName()) != patch.WHOLE
})
err := node.ForEachFile(func(file *models.CommitFile) error {
if adding {
return self.c.Git().Patch.PatchBuilder.AddFileWhole(file.Name)
return self.git.Patch.PatchBuilder.AddFileWhole(file.Name)
} else {
return self.c.Git().Patch.PatchBuilder.RemoveFile(file.Name)
return self.git.Patch.PatchBuilder.RemoveFile(file.Name)
}
})
if err != nil {
return self.c.Error(err)
}
if self.c.Git().Patch.PatchBuilder.IsEmpty() {
self.c.Git().Patch.PatchBuilder.Reset()
if self.git.Patch.PatchBuilder.IsEmpty() {
self.git.Patch.PatchBuilder.Reset()
}
return self.c.PostRefreshUpdate(self.context())
})
}
if self.c.Git().Patch.PatchBuilder.Active() && self.c.Git().Patch.PatchBuilder.To != self.context().GetRef().RefName() {
if self.git.Patch.PatchBuilder.Active() && self.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.c.Git().Patch.PatchBuilder.Reset()
self.git.Patch.PatchBuilder.Reset()
return toggle()
},
})
@@ -246,9 +213,9 @@ func (self *CommitFilesController) startPatchBuilder() error {
canRebase := commitFilesContext.GetCanRebase()
ref := commitFilesContext.GetRef()
to := ref.RefName()
from, reverse := self.c.Modes().Diffing.GetFromAndReverseArgsForDiff(ref.ParentRefName())
from, reverse := self.modes.Diffing.GetFromAndReverseArgsForDiff(ref.ParentRefName())
self.c.Git().Patch.PatchBuilder.Start(from, to, reverse, canRebase)
self.git.Patch.PatchBuilder.Start(from, to, reverse, canRebase)
return nil
}
@@ -262,21 +229,21 @@ func (self *CommitFilesController) enterCommitFile(node *filetree.CommitFileNode
}
enterTheFile := func() error {
if !self.c.Git().Patch.PatchBuilder.Active() {
if !self.git.Patch.PatchBuilder.Active() {
if err := self.startPatchBuilder(); err != nil {
return err
}
}
return self.c.PushContext(self.c.Contexts().CustomPatchBuilder, opts)
return self.c.PushContext(self.contexts.CustomPatchBuilder, opts)
}
if self.c.Git().Patch.PatchBuilder.Active() && self.c.Git().Patch.PatchBuilder.To != self.context().GetRef().RefName() {
if self.git.Patch.PatchBuilder.Active() && self.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.c.Git().Patch.PatchBuilder.Reset()
self.git.Patch.PatchBuilder.Reset()
return enterTheFile()
},
})
+31 -13
View File
@@ -1,24 +1,42 @@
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 {
*helpers.HelperCommon
IGetHelpers
}
type IGetHelpers interface {
Helpers() *helpers.Helpers
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
}
func NewControllerCommon(
c *helpers.HelperCommon,
IGetHelpers IGetHelpers,
) *ControllerCommon {
return &ControllerCommon{
HelperCommon: c,
IGetHelpers: IGetHelpers,
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,
}
}
@@ -1,65 +0,0 @@
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
c *ControllerCommon
*controllerCommon
}
var _ types.IController = &ContextLinesController{}
func NewContextLinesController(
common *ControllerCommon,
common *controllerCommon,
) *ContextLinesController {
return &ContextLinesController{
baseController: baseController{},
c: common,
baseController: baseController{},
controllerCommon: common,
}
}
@@ -101,7 +101,7 @@ func (self *ContextLinesController) applyChange() error {
}
func (self *ContextLinesController) checkCanChangeContext() error {
if self.c.Git().Patch.PatchBuilder.Active() {
if self.git.Patch.PatchBuilder.Active() {
return errors.New(self.c.Tr.CantChangeContextSizeError)
}
@@ -1,53 +0,0 @@
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, " ")
}
@@ -1,219 +0,0 @@
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
}
@@ -1,69 +0,0 @@
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})
}
+60 -121
View File
@@ -13,16 +13,26 @@ import (
type FilesController struct {
baseController // nolint: unused
c *ControllerCommon
*controllerCommon
enterSubmodule func(submodule *models.SubmoduleConfig) error
setCommitMessage func(message string)
getSavedCommitMessage func() string
}
var _ types.IController = &FilesController{}
func NewFilesController(
common *ControllerCommon,
common *controllerCommon,
enterSubmodule func(submodule *models.SubmoduleConfig) error,
setCommitMessage func(message string),
getSavedCommitMessage func() string,
) *FilesController {
return &FilesController{
c: common,
controllerCommon: common,
enterSubmodule: enterSubmodule,
setCommitMessage: setCommitMessage,
getSavedCommitMessage: getSavedCommitMessage,
}
}
@@ -40,12 +50,12 @@ func (self *FilesController) GetKeybindings(opts types.KeybindingsOpts) []*types
},
{
Key: opts.GetKey(opts.Config.Files.CommitChanges),
Handler: self.c.Helpers().WorkingTree.HandleCommitPress,
Handler: self.helpers.WorkingTree.HandleCommitPress,
Description: self.c.Tr.CommitChanges,
},
{
Key: opts.GetKey(opts.Config.Files.CommitChangesWithoutHook),
Handler: self.c.Helpers().WorkingTree.HandleWIPCommitPress,
Handler: self.helpers.WorkingTree.HandleWIPCommitPress,
Description: self.c.Tr.LcCommitChangesWithoutHook,
},
{
@@ -55,7 +65,7 @@ func (self *FilesController) GetKeybindings(opts types.KeybindingsOpts) []*types
},
{
Key: opts.GetKey(opts.Config.Files.CommitChangesWithEditor),
Handler: self.c.Helpers().WorkingTree.HandleCommitEditorPress,
Handler: self.helpers.WorkingTree.HandleCommitEditorPress,
Description: self.c.Tr.CommitChangesWithEditor,
},
{
@@ -119,7 +129,7 @@ func (self *FilesController) GetKeybindings(opts types.KeybindingsOpts) []*types
},
{
Key: opts.GetKey(opts.Config.Files.OpenMergeTool),
Handler: self.c.Helpers().WorkingTree.OpenMergeTool,
Handler: self.helpers.WorkingTree.OpenMergeTool,
Description: self.c.Tr.LcOpenMergeTool,
},
{
@@ -165,77 +175,6 @@ 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)
}
@@ -291,7 +230,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.c.Model().Files {
for _, modelFile := range self.model.Files {
if modelFile.Name == f.Name {
if optimisticChangeFn(modelFile) {
rerender = true
@@ -306,7 +245,7 @@ func (self *FilesController) optimisticChange(node *filetree.FileNode, optimisti
return err
}
if rerender {
if err := self.c.PostRefreshUpdate(self.c.Contexts().Files); err != nil {
if err := self.c.PostRefreshUpdate(self.contexts.Files); err != nil {
return err
}
}
@@ -317,8 +256,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.c.Mutexes().RefreshingFilesMutex.Lock()
defer self.c.Mutexes().RefreshingFilesMutex.Unlock()
self.mutexes.RefreshingFilesMutex.Lock()
defer self.mutexes.RefreshingFilesMutex.Unlock()
if node.IsFile() {
file := node.File
@@ -330,7 +269,7 @@ func (self *FilesController) pressWithLock(node *filetree.FileNode) error {
return err
}
if err := self.c.Git().WorkingTree.StageFile(file.Name); err != nil {
if err := self.git.WorkingTree.StageFile(file.Name); err != nil {
return self.c.Error(err)
}
} else {
@@ -340,7 +279,7 @@ func (self *FilesController) pressWithLock(node *filetree.FileNode) error {
return err
}
if err := self.c.Git().WorkingTree.UnStageFile(file.Names(), file.Tracked); err != nil {
if err := self.git.WorkingTree.UnStageFile(file.Names(), file.Tracked); err != nil {
return self.c.Error(err)
}
}
@@ -358,7 +297,7 @@ func (self *FilesController) pressWithLock(node *filetree.FileNode) error {
return err
}
if err := self.c.Git().WorkingTree.StageFile(node.Path); err != nil {
if err := self.git.WorkingTree.StageFile(node.Path); err != nil {
return self.c.Error(err)
}
} else {
@@ -369,7 +308,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.c.Git().WorkingTree.UnStageFile([]string{node.Path}, true); err != nil {
if err := self.git.WorkingTree.UnStageFile([]string{node.Path}, true); err != nil {
return self.c.Error(err)
}
}
@@ -410,7 +349,7 @@ func (self *FilesController) Context() types.Context {
}
func (self *FilesController) context() *context.WorkingTreeContext {
return self.c.Contexts().Files
return self.contexts.Files
}
func (self *FilesController) getSelectedFile() *models.File {
@@ -437,10 +376,10 @@ func (self *FilesController) EnterFile(opts types.OnFocusOpts) error {
file := node.File
submoduleConfigs := self.c.Model().Submodules
submoduleConfigs := self.model.Submodules
if file.IsSubmodule(submoduleConfigs) {
submoduleConfig := file.SubmoduleConfig(submoduleConfigs)
return self.c.Helpers().Repos.EnterSubmodule(submoduleConfig)
return self.enterSubmodule(submoduleConfig)
}
if file.HasInlineMergeConflicts {
@@ -450,7 +389,7 @@ func (self *FilesController) EnterFile(opts types.OnFocusOpts) error {
return self.c.ErrorMsg(self.c.Tr.FileStagingRequirements)
}
return self.c.PushContext(self.c.Contexts().Staging, opts)
return self.c.PushContext(self.contexts.Staging, opts)
}
func (self *FilesController) toggleStagedAll() error {
@@ -466,8 +405,8 @@ func (self *FilesController) toggleStagedAll() error {
}
func (self *FilesController) toggleStagedAllWithLock() error {
self.c.Mutexes().RefreshingFilesMutex.Lock()
defer self.c.Mutexes().RefreshingFilesMutex.Unlock()
self.mutexes.RefreshingFilesMutex.Lock()
defer self.mutexes.RefreshingFilesMutex.Unlock()
root := self.context().FileTreeViewModel.GetRoot()
@@ -484,7 +423,7 @@ func (self *FilesController) toggleStagedAllWithLock() error {
return err
}
if err := self.c.Git().WorkingTree.StageAll(); err != nil {
if err := self.git.WorkingTree.StageAll(); err != nil {
return self.c.Error(err)
}
} else {
@@ -494,7 +433,7 @@ func (self *FilesController) toggleStagedAllWithLock() error {
return err
}
if err := self.c.Git().WorkingTree.UnstageAll(); err != nil {
if err := self.git.WorkingTree.UnstageAll(); err != nil {
return self.c.Error(err)
}
}
@@ -505,7 +444,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.c.Git().WorkingTree.UnStageFile(file.Names(), file.Tracked); err != nil {
if err := self.git.WorkingTree.UnStageFile(file.Names(), file.Tracked); err != nil {
return err
}
}
@@ -521,7 +460,7 @@ func (self *FilesController) ignoreOrExcludeTracked(node *filetree.FileNode, trA
return err
}
if err := self.c.Git().WorkingTree.RemoveTrackedFiles(node.GetPath()); err != nil {
if err := self.git.WorkingTree.RemoveTrackedFiles(node.GetPath()); err != nil {
return err
}
@@ -559,7 +498,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.c.Git().WorkingTree.Ignore)
err := self.ignoreOrExcludeFile(node, self.c.Tr.IgnoreTracked, self.c.Tr.IgnoreTrackedPrompt, self.c.Tr.Actions.LcIgnoreExcludeFile, self.git.WorkingTree.Ignore)
if err != nil {
return err
}
@@ -576,7 +515,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.c.Git().WorkingTree.Exclude)
err := self.ignoreOrExcludeFile(node, self.c.Tr.ExcludeTracked, self.c.Tr.ExcludeTrackedPrompt, self.c.Tr.Actions.ExcludeFile, self.git.WorkingTree.Exclude)
if err != nil {
return err
}
@@ -616,19 +555,19 @@ func (self *FilesController) refresh() error {
}
func (self *FilesController) handleAmendCommitPress() error {
if len(self.c.Model().Files) == 0 {
if len(self.model.Files) == 0 {
return self.c.ErrorMsg(self.c.Tr.NoFilesStagedTitle)
}
if !self.c.Helpers().WorkingTree.AnyStagedFiles() {
return self.c.Helpers().WorkingTree.PromptToStageAllAndRetry(self.handleAmendCommitPress)
if !self.helpers.WorkingTree.AnyStagedFiles() {
return self.helpers.WorkingTree.PromptToStageAllAndRetry(self.handleAmendCommitPress)
}
if len(self.c.Model().Commits) == 0 {
if len(self.model.Commits) == 0 {
return self.c.ErrorMsg(self.c.Tr.NoCommitToAmend)
}
return self.c.Helpers().AmendHelper.AmendHead()
return self.helpers.AmendHelper.AmendHead()
}
func (self *FilesController) handleStatusFilterPressed() error {
@@ -667,7 +606,7 @@ func (self *FilesController) edit(node *filetree.FileNode) error {
return self.c.ErrorMsg(self.c.Tr.ErrCannotEditDirectory)
}
return self.c.Helpers().Files.EditFile(node.GetPath())
return self.helpers.Files.EditFile(node.GetPath())
}
func (self *FilesController) Open() error {
@@ -676,7 +615,7 @@ func (self *FilesController) Open() error {
return nil
}
return self.c.Helpers().Files.OpenFile(node.GetPath())
return self.helpers.Files.OpenFile(node.GetPath())
}
func (self *FilesController) switchToMerge() error {
@@ -685,7 +624,7 @@ func (self *FilesController) switchToMerge() error {
return nil
}
return self.c.Helpers().MergeConflicts.SwitchToMerge(file.Name)
return self.helpers.MergeConflicts.SwitchToMerge(file.Name)
}
func (self *FilesController) createStashMenu() error {
@@ -695,28 +634,28 @@ func (self *FilesController) createStashMenu() error {
{
Label: self.c.Tr.LcStashAllChanges,
OnPress: func() error {
if !self.c.Helpers().WorkingTree.IsWorkingTreeDirty() {
if !self.helpers.WorkingTree.IsWorkingTreeDirty() {
return self.c.ErrorMsg(self.c.Tr.NoFilesToStash)
}
return self.handleStashSave(self.c.Git().Stash.Save, self.c.Tr.Actions.StashAllChanges)
return self.handleStashSave(self.git.Stash.Save, self.c.Tr.Actions.StashAllChanges)
},
Key: 'a',
},
{
Label: self.c.Tr.LcStashAllChangesKeepIndex,
OnPress: func() error {
if !self.c.Helpers().WorkingTree.IsWorkingTreeDirty() {
if !self.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.c.Git().Stash.StashAndKeepIndex, self.c.Tr.Actions.StashAllChangesKeepIndex)
return self.handleStashSave(self.git.Stash.StashAndKeepIndex, self.c.Tr.Actions.StashAllChangesKeepIndex)
},
Key: 'i',
},
{
Label: self.c.Tr.LcStashIncludeUntrackedChanges,
OnPress: func() error {
return self.handleStashSave(self.c.Git().Stash.StashIncludeUntrackedChanges, self.c.Tr.Actions.StashIncludeUntrackedChanges)
return self.handleStashSave(self.git.Stash.StashIncludeUntrackedChanges, self.c.Tr.Actions.StashIncludeUntrackedChanges)
},
Key: 'U',
},
@@ -724,24 +663,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.c.Helpers().WorkingTree.AnyStagedFiles() {
if !self.helpers.WorkingTree.AnyStagedFiles() {
return self.c.ErrorMsg(self.c.Tr.NoTrackedStagedFilesStash)
}
return self.handleStashSave(self.c.Git().Stash.SaveStagedChanges, self.c.Tr.Actions.StashStagedChanges)
return self.handleStashSave(self.git.Stash.SaveStagedChanges, self.c.Tr.Actions.StashStagedChanges)
},
Key: 's',
},
{
Label: self.c.Tr.LcStashUnstagedChanges,
OnPress: func() error {
if !self.c.Helpers().WorkingTree.IsWorkingTreeDirty() {
if !self.helpers.WorkingTree.IsWorkingTreeDirty() {
return self.c.ErrorMsg(self.c.Tr.NoFilesToStash)
}
if self.c.Helpers().WorkingTree.AnyStagedFiles() {
return self.handleStashSave(self.c.Git().Stash.StashUnstagedChanges, self.c.Tr.Actions.StashUnstagedChanges)
if self.helpers.WorkingTree.AnyStagedFiles() {
return self.handleStashSave(self.git.Stash.StashUnstagedChanges, self.c.Tr.Actions.StashUnstagedChanges)
}
// ordinary stash
return self.handleStashSave(self.c.Git().Stash.Save, self.c.Tr.Actions.StashUnstagedChanges)
return self.handleStashSave(self.git.Stash.Save, self.c.Tr.Actions.StashUnstagedChanges)
},
Key: 'u',
},
@@ -750,11 +689,11 @@ func (self *FilesController) createStashMenu() error {
}
func (self *FilesController) stash() error {
return self.handleStashSave(self.c.Git().Stash.Save, self.c.Tr.Actions.StashAllChanges)
return self.handleStashSave(self.git.Stash.Save, self.c.Tr.Actions.StashAllChanges)
}
func (self *FilesController) createResetToUpstreamMenu() error {
return self.c.Helpers().Refs.CreateGitResetMenu("@{upstream}")
return self.helpers.Refs.CreateGitResetMenu("@{upstream}")
}
func (self *FilesController) handleToggleDirCollapsed() error {
@@ -765,7 +704,7 @@ func (self *FilesController) handleToggleDirCollapsed() error {
self.context().FileTreeViewModel.ToggleCollapsed(node.GetPath())
if err := self.c.PostRefreshUpdate(self.c.Contexts().Files); err != nil {
if err := self.c.PostRefreshUpdate(self.contexts.Files); err != nil {
self.c.Log.Error(err)
}
@@ -811,7 +750,7 @@ func (self *FilesController) fetch() error {
func (self *FilesController) fetchAux() (err error) {
self.c.LogAction("Fetch")
err = self.c.Git().Sync.Fetch(git_commands.FetchOptions{})
err = self.git.Sync.Fetch(git_commands.FetchOptions{})
if err != nil && strings.Contains(err.Error(), "exit status 128") {
_ = self.c.ErrorMsg(self.c.Tr.PassUnameWrong)
+14 -14
View File
@@ -12,17 +12,17 @@ import (
type FilesRemoveController struct {
baseController
c *ControllerCommon
*controllerCommon
}
var _ types.IController = &FilesRemoveController{}
func NewFilesRemoveController(
common *ControllerCommon,
common *controllerCommon,
) *FilesRemoveController {
return &FilesRemoveController{
baseController: baseController{},
c: common,
baseController: baseController{},
controllerCommon: 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.c.Git().WorkingTree.DiscardAllDirChanges(node); err != nil {
if err := self.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.c.Git().WorkingTree.DiscardUnstagedDirChanges(node); err != nil {
if err := self.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.c.Model().Submodules
submodules := self.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.c.Git().WorkingTree.DiscardAllFileChanges(file); err != nil {
if err := self.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.c.Git().WorkingTree.DiscardUnstagedFileChanges(file); err != nil {
if err := self.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.c.Helpers().WorkingTree.FileForSubmodule(submodule)
file := self.helpers.WorkingTree.FileForSubmodule(submodule)
if file != nil {
if err := self.c.Git().WorkingTree.UnStageFile(file.Names(), file.Tracked); err != nil {
if err := self.git.WorkingTree.UnStageFile(file.Names(), file.Tracked); err != nil {
return self.c.Error(err)
}
}
if err := self.c.Git().Submodule.Stash(submodule); err != nil {
if err := self.git.Submodule.Stash(submodule); err != nil {
return self.c.Error(err)
}
if err := self.c.Git().Submodule.Reset(submodule); err != nil {
if err := self.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.c.Contexts().Files
return self.contexts.Files
}
@@ -1,79 +0,0 @@
package controllers
import (
"fmt"
"strings"
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
type FilteringMenuAction struct {
c *ControllerCommon
}
func (self *FilteringMenuAction) Call() error {
fileName := ""
switch self.c.CurrentSideContext() {
case self.c.Contexts().Files:
node := self.c.Contexts().Files.GetSelected()
if node != nil {
fileName = node.GetPath()
}
case self.c.Contexts().CommitFiles:
node := self.c.Contexts().CommitFiles.GetSelected()
if node != nil {
fileName = node.GetPath()
}
}
menuItems := []*types.MenuItem{}
if fileName != "" {
menuItems = append(menuItems, &types.MenuItem{
Label: fmt.Sprintf("%s '%s'", self.c.Tr.LcFilterBy, fileName),
OnPress: func() error {
return self.setFiltering(fileName)
},
})
}
menuItems = append(menuItems, &types.MenuItem{
Label: self.c.Tr.LcFilterPathOption,
OnPress: func() error {
return self.c.Prompt(types.PromptOpts{
FindSuggestionsFunc: self.c.Helpers().Suggestions.GetFilePathSuggestionsFunc(),
Title: self.c.Tr.EnterFileName,
HandleConfirm: func(response string) error {
return self.setFiltering(strings.TrimSpace(response))
},
})
},
})
if self.c.Modes().Filtering.Active() {
menuItems = append(menuItems, &types.MenuItem{
Label: self.c.Tr.LcExitFilterMode,
OnPress: self.c.Helpers().Mode.ClearFiltering,
})
}
return self.c.Menu(types.CreateMenuOptions{Title: self.c.Tr.FilteringMenuTitle, Items: menuItems})
}
func (self *FilteringMenuAction) setFiltering(path string) error {
self.c.Modes().Filtering.SetPath(path)
repoState := self.c.State().GetRepoState()
if repoState.GetScreenMode() == types.SCREEN_NORMAL {
repoState.SetScreenMode(types.SCREEN_HALF)
}
if err := self.c.PushContext(self.c.Contexts().LocalCommits); err != nil {
return err
}
return self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.COMMITS}, Then: func() {
self.c.Contexts().LocalCommits.SetSelectedLineIdx(0)
self.c.Contexts().LocalCommits.FocusLine()
}})
}
+8 -8
View File
@@ -11,17 +11,17 @@ import (
type GitFlowController struct {
baseController
c *ControllerCommon
*controllerCommon
}
var _ types.IController = &GitFlowController{}
func NewGitFlowController(
common *ControllerCommon,
common *controllerCommon,
) *GitFlowController {
return &GitFlowController{
baseController: baseController{},
c: common,
baseController: baseController{},
controllerCommon: common,
}
}
@@ -39,7 +39,7 @@ func (self *GitFlowController) GetKeybindings(opts types.KeybindingsOpts) []*typ
}
func (self *GitFlowController) handleCreateGitFlowMenu(branch *models.Branch) error {
if !self.c.Git().Flow.GitFlowEnabled() {
if !self.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.c.Git().Flow.StartCmdObj(branchType, name),
self.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.c.Git().Flow.FinishCmdObj(branchName)
cmdObj, err := self.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.c.Contexts().Branches
return self.contexts.Branches
}
+48 -139
View File
@@ -1,21 +1,26 @@
package controllers
import (
"github.com/jesseduffield/gocui"
"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 GlobalController struct {
baseController
c *ControllerCommon
*controllerCommon
}
func NewGlobalController(
common *ControllerCommon,
common *controllerCommon,
) *GlobalController {
return &GlobalController{
baseController: baseController{},
c: common,
baseController: baseController{},
controllerCommon: common,
}
}
@@ -26,143 +31,47 @@ func (self *GlobalController) GetKeybindings(opts types.KeybindingsOpts) []*type
Handler: self.customCommand,
Description: self.c.Tr.LcExecuteCustomCommand,
},
{
Key: opts.GetKey(opts.Config.Universal.CreatePatchOptionsMenu),
Handler: self.createCustomPatchOptionsMenu,
Description: self.c.Tr.ViewPatchOptions,
OpensMenu: true,
},
{
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) 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),
)
},
})
}
// 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)
}
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()
}
+7 -3
View File
@@ -1,20 +1,24 @@
package helpers
import (
"github.com/jesseduffield/lazygit/pkg/commands"
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
type AmendHelper struct {
c *HelperCommon
c *types.HelperCommon
git *commands.GitCommand
gpg *GpgHelper
}
func NewAmendHelper(
c *HelperCommon,
c *types.HelperCommon,
git *commands.GitCommand,
gpg *GpgHelper,
) *AmendHelper {
return &AmendHelper{
c: c,
git: git,
gpg: gpg,
}
}
@@ -24,7 +28,7 @@ func (self *AmendHelper) AmendHead() error {
Title: self.c.Tr.AmendLastCommitTitle,
Prompt: self.c.Tr.SureToAmend,
HandleConfirm: func() error {
cmdObj := self.c.Git().Commit.AmendHeadCmdObj()
cmdObj := self.git.Commit.AmendHeadCmdObj()
self.c.LogAction(self.c.Tr.Actions.AmendCommit)
return self.gpg.WithGpgHandling(cmdObj, self.c.Tr.AmendingStatus, nil)
},

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