mirror of
https://github.com/jesseduffield/lazygit.git
synced 2026-10-03 12:36:57 -04:00
Compare commits
294
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8193731a82 | ||
|
|
1a6d062192 | ||
|
|
1ff13cdfc6 | ||
|
|
19e8cafe41 | ||
|
|
cbb5fe6007 | ||
|
|
c4fc30c243 | ||
|
|
117aa1dcc6 | ||
|
|
c550737a4f | ||
|
|
9e37ae3f5d | ||
|
|
c89ef8b84a | ||
|
|
23befdd13a | ||
|
|
58a83b0862 | ||
|
|
d5b4f7bb3e | ||
|
|
3cee4de39c | ||
|
|
f609a04671 | ||
|
|
7ffb6ffb0f | ||
|
|
3691856021 | ||
|
|
b5ca6a3add | ||
|
|
ecaa4846f1 | ||
|
|
787f9966ec | ||
|
|
013cfc77a1 | ||
|
|
16f7b01fec | ||
|
|
421c6565f9 | ||
|
|
d02deeefd8 | ||
|
|
235f5bb221 | ||
|
|
be3b4bd791 | ||
|
|
67d6447e12 | ||
|
|
fd9d7cb7bb | ||
|
|
3d6965ccbb | ||
|
|
707fa37160 | ||
|
|
7075b66bc6 | ||
|
|
9d55d71fdd | ||
|
|
cc9a20c4ab | ||
|
|
c39fafe6ec | ||
|
|
2a11725749 | ||
|
|
deed9eb18e | ||
|
|
a3759c65e4 | ||
|
|
519760077b | ||
|
|
8390622f70 | ||
|
|
a04fbafcfd | ||
|
|
4fe8dee40f | ||
|
|
6fd80565c7 | ||
|
|
3cda1d03d9 | ||
|
|
d4eb02fac0 | ||
|
|
9c72d8a2b0 | ||
|
|
4f3127ccb8 | ||
|
|
877c27722f | ||
|
|
0dda8b58d1 | ||
|
|
e997d1ae5d | ||
|
|
3e2ef84d56 | ||
|
|
b2eccd6fe8 | ||
|
|
cc25a3b8c5 | ||
|
|
e93d945dba | ||
|
|
246e08096f | ||
|
|
860a52a736 | ||
|
|
18d8b29461 | ||
|
|
7af371701d | ||
|
|
5ccc95b76f | ||
|
|
cdb1d76484 | ||
|
|
9e423e949c | ||
|
|
e1ceb6892a | ||
|
|
40b8557608 | ||
|
|
92e107f52d | ||
|
|
2f6a87df98 | ||
|
|
2c29577e3c | ||
|
|
e72559bb27 | ||
|
|
6da1cf87b1 | ||
|
|
508b869773 | ||
|
|
59cc6843e6 | ||
|
|
f108fd2236 | ||
|
|
8081b59a02 | ||
|
|
26d180a50a | ||
|
|
10fe872c71 | ||
|
|
c74448f00d | ||
|
|
41ab7c44a0 | ||
|
|
3589a43682 | ||
|
|
ad70341139 | ||
|
|
ea93df571d | ||
|
|
bc21921001 | ||
|
|
07cbb62510 | ||
|
|
fc868cdda5 | ||
|
|
864a9ada57 | ||
|
|
f3e9d50d94 | ||
|
|
cdad0b998e | ||
|
|
64012d67a9 | ||
|
|
33d9d4dbf1 | ||
|
|
4c5e250ed8 | ||
|
|
1359fa14c1 | ||
|
|
c222a618a8 | ||
|
|
05c32e292e | ||
|
|
fb4f742416 | ||
|
|
276438b601 | ||
|
|
edec116ceb | ||
|
|
1aae1772ce | ||
|
|
a642395e9f | ||
|
|
70bfeddc90 | ||
|
|
e2a966443b | ||
|
|
0b13c3ca87 | ||
|
|
8b6766de79 | ||
|
|
c9371f812f | ||
|
|
e592d81b60 | ||
|
|
f2f50ccf75 | ||
|
|
75aed98c35 | ||
|
|
7f9818cfa2 | ||
|
|
679148449a | ||
|
|
bc00aeb10d | ||
|
|
4d258bd981 | ||
|
|
c465b0f2ff | ||
|
|
25160b671e | ||
|
|
20bfa42792 | ||
|
|
5a740e34c7 | ||
|
|
0aad599a9b | ||
|
|
b6c892a08a | ||
|
|
28d12e4e5d | ||
|
|
5d5e24a48e | ||
|
|
de598e55a6 | ||
|
|
0fd8392067 | ||
|
|
67ac2c5d9b | ||
|
|
7626fd7df3 | ||
|
|
db409fa69f | ||
|
|
5b8a8d356c | ||
|
|
d7e2ca3f10 | ||
|
|
387fbf6ab6 | ||
|
|
917eb88617 | ||
|
|
005827395d | ||
|
|
7f9fa64074 | ||
|
|
ab06a1c85c | ||
|
|
37048911ca | ||
|
|
2b7b6f71ee | ||
|
|
47d422bb8a | ||
|
|
c3ca77d6bf | ||
|
|
a3cd1e93be | ||
|
|
4cf8d81155 | ||
|
|
7774fe0ab3 | ||
|
|
f7db17f7d0 | ||
|
|
1106981827 | ||
|
|
1dac4158e9 | ||
|
|
18c5780485 | ||
|
|
e895052437 | ||
|
|
ade8d78c63 | ||
|
|
2e74b7177e | ||
|
|
6266e19623 | ||
|
|
c67aa49856 | ||
|
|
3a54089859 | ||
|
|
e2c447b8d0 | ||
|
|
843e12286f | ||
|
|
e60936e964 | ||
|
|
b9cfd89b80 | ||
|
|
de4224bbe4 | ||
|
|
ce91de76e4 | ||
|
|
acfce07aa0 | ||
|
|
81216189e4 | ||
|
|
2b26b380b6 | ||
|
|
1ba8d3060b | ||
|
|
f561007fb7 | ||
|
|
a63d5891e2 | ||
|
|
df38e954f4 | ||
|
|
ea532b4729 | ||
|
|
572d1b5920 | ||
|
|
e8fac6ca73 | ||
|
|
4de3fadb00 | ||
|
|
12f24aa8b4 | ||
|
|
911aa7774b | ||
|
|
1fb0e1e151 | ||
|
|
94bf279b5a | ||
|
|
37381b610d | ||
|
|
6d4df57393 | ||
|
|
3df01aaff0 | ||
|
|
4ee4f6f34b | ||
|
|
7953f7fa86 | ||
|
|
7a8df7795c | ||
|
|
aa493d3a9e | ||
|
|
72731f2c16 | ||
|
|
f680b6e82e | ||
|
|
ec22570eac | ||
|
|
198ead7c14 | ||
|
|
297a020abf | ||
|
|
473d989cde | ||
|
|
061bfce835 | ||
|
|
996e30e5d9 | ||
|
|
ecd2a14a15 | ||
|
|
8dfa8dc48c | ||
|
|
f7c183a429 | ||
|
|
473f86fcaa | ||
|
|
28353da61b | ||
|
|
efd49f07fd | ||
|
|
b8e183acc4 | ||
|
|
821c4b05fe | ||
|
|
4272d84fa2 | ||
|
|
e95505453e | ||
|
|
6dc34d19ea | ||
|
|
a670fbb33c | ||
|
|
98e6c119f5 | ||
|
|
c718a73d0a | ||
|
|
d74c817fd8 | ||
|
|
93d19db158 | ||
|
|
d7b611aa05 | ||
|
|
fc6008fdff | ||
|
|
a65d3119c0 | ||
|
|
51ecf4089b | ||
|
|
0c0fe8997b | ||
|
|
f86309dd03 | ||
|
|
c31fcb7134 | ||
|
|
91ec42f3f8 | ||
|
|
8f628296ad | ||
|
|
61bd3e8dd2 | ||
|
|
7263630967 | ||
|
|
16711c6f1a | ||
|
|
b2023f10b6 | ||
|
|
e915488a83 | ||
|
|
b2d629b50a | ||
|
|
125d4fa9dc | ||
|
|
527a1596f3 | ||
|
|
51d9f70f9e | ||
|
|
83727d48b5 | ||
|
|
8efc175340 | ||
|
|
6a6cb25d7e | ||
|
|
fd782ff568 | ||
|
|
dbfb469bad | ||
|
|
888a976fc0 | ||
|
|
2073730186 | ||
|
|
7a4a0c85c4 | ||
|
|
382ecb6bfe | ||
|
|
03694f7502 | ||
|
|
ee308a4994 | ||
|
|
a2b2336173 | ||
|
|
d7b1deb465 | ||
|
|
dd01639f57 | ||
|
|
525932fbf2 | ||
|
|
b1314349d7 | ||
|
|
9671f549a1 | ||
|
|
a1738a77ae | ||
|
|
689deb72bd | ||
|
|
84372cfad9 | ||
|
|
e429415ed4 | ||
|
|
7402be98b6 | ||
|
|
341b9725d4 | ||
|
|
8f164f7bc5 | ||
|
|
79c11a0458 | ||
|
|
4a4afc4639 | ||
|
|
ebdfd8046a | ||
|
|
d99e983236 | ||
|
|
1c6cfafba7 | ||
|
|
83d642b74f | ||
|
|
b1bc437d1b | ||
|
|
26989ce0ee | ||
|
|
e6356ce10c | ||
|
|
b92da0dc54 | ||
|
|
70e668bfcf | ||
|
|
8dd517870d | ||
|
|
f1753f36c8 | ||
|
|
3ea81d4a6f | ||
|
|
0df5cb1286 | ||
|
|
c43830b027 | ||
|
|
08624b8ef7 | ||
|
|
73b68927af | ||
|
|
bf699d3a79 | ||
|
|
f2e8d549ba | ||
|
|
4ffb6c0fea | ||
|
|
54776052a1 | ||
|
|
9c5eedf748 | ||
|
|
ca08956f77 | ||
|
|
aa74239f05 | ||
|
|
9e7018db8a | ||
|
|
cdea5b4873 | ||
|
|
4b0432423d | ||
|
|
2607580a09 | ||
|
|
ebd56bd8d5 | ||
|
|
ed1547e0cb | ||
|
|
5e388e21c8 | ||
|
|
4c89f9fba5 | ||
|
|
8e7958f78b | ||
|
|
579791e7bc | ||
|
|
595e28d335 | ||
|
|
0551f29de9 | ||
|
|
82b5c20050 | ||
|
|
ecaf1e9002 | ||
|
|
69ff621494 | ||
|
|
f32fe84c9b | ||
|
|
2ded352f3a | ||
|
|
c877a8528f | ||
|
|
d1754a9490 | ||
|
|
33a96a3dd7 | ||
|
|
cd889adfa0 | ||
|
|
77447ea6c3 | ||
|
|
fb05ee369f | ||
|
|
213da4fddd | ||
|
|
e78a09ebab | ||
|
|
7ccb871a45 | ||
|
|
9cc18bd7e6 | ||
|
|
8e2ecd17a9 | ||
|
|
78676dcff6 | ||
|
|
1431ffcebf | ||
|
|
1a2a301aa2 |
@@ -2,9 +2,14 @@
|
||||
|
||||
- **Please check if the PR fulfills these requirements**
|
||||
|
||||
* [ ] Cheatsheets are up-to-date (run `go run scripts/cheatsheet/main.go generate`)
|
||||
* [ ] Cheatsheets are up-to-date (run `go generate ./...`)
|
||||
* [ ] Code has been formatted (see [here](https://github.com/jesseduffield/lazygit/blob/master/CONTRIBUTING.md#code-formatting))
|
||||
* [ ] Tests have been added/updated (see [here](https://github.com/jesseduffield/lazygit/blob/master/pkg/integration/README.md) for the integration test guide)
|
||||
* [ ] Text is internationalised (see [here](https://github.com/jesseduffield/lazygit/blob/master/CONTRIBUTING.md#internationalisation))
|
||||
* [ ] Docs (specifically `docs/Config.md`) have been updated if necessary
|
||||
* [ ] You've read through your own file changes for silly mistakes etc
|
||||
|
||||
<!--
|
||||
Be sure to name your PR with an imperative e.g. 'Add worktrees view'
|
||||
see https://github.com/jesseduffield/lazygit/releases/tag/v0.40.0 for examples
|
||||
-->
|
||||
|
||||
@@ -14,11 +14,11 @@ jobs:
|
||||
- name: Unshallow repo
|
||||
run: git fetch --prune --unshallow
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v1
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: 1.20.x
|
||||
- name: Run goreleaser
|
||||
uses: goreleaser/goreleaser-action@v1
|
||||
uses: goreleaser/goreleaser-action@v4
|
||||
with:
|
||||
distribution: goreleaser
|
||||
version: v1.17.2
|
||||
|
||||
+10
-57
@@ -30,18 +30,9 @@ jobs:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v1
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: 1.20.x
|
||||
- name: Cache build
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
${{matrix.cache_path}}
|
||||
~/go/pkg/mod
|
||||
key: ${{runner.os}}-go-${{hashFiles('**/go.sum')}}-test
|
||||
restore-keys: |
|
||||
${{runner.os}}-go-
|
||||
- name: Test code
|
||||
# we're passing -short so that we skip the integration tests, which will be run in parallel below
|
||||
run: |
|
||||
@@ -89,18 +80,9 @@ jobs:
|
||||
path: ~/git-${{matrix.git-version}}
|
||||
key: ${{runner.os}}-git-${{matrix.git-version}}
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v1
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: 1.20.x
|
||||
- name: Cache build
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: |
|
||||
~/.cache/go-build
|
||||
~/go/pkg/mod
|
||||
key: ${{runner.os}}-go-${{hashFiles('**/go.sum')}}-test
|
||||
restore-keys: |
|
||||
${{runner.os}}-go-
|
||||
- name: Print git version
|
||||
run: git --version
|
||||
- name: Test code
|
||||
@@ -115,18 +97,9 @@ jobs:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v1
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: 1.20.x
|
||||
- name: Cache build
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: |
|
||||
~/.cache/go-build
|
||||
~/go/pkg/mod
|
||||
key: ${{runner.os}}-go-${{hashFiles('**/go.sum')}}-build
|
||||
restore-keys: |
|
||||
${{runner.os}}-go-
|
||||
- name: Build linux binary
|
||||
run: |
|
||||
GOOS=linux go build
|
||||
@@ -151,29 +124,17 @@ jobs:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v1
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: 1.20.x
|
||||
- name: Cache build
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: |
|
||||
~/.cache/go-build
|
||||
~/go/pkg/mod
|
||||
key: ${{runner.os}}-go-${{hashFiles('**/go.sum')}}-build
|
||||
restore-keys: |
|
||||
${{runner.os}}-go-
|
||||
- name: Check Cheatsheet
|
||||
run: |
|
||||
go run scripts/cheatsheet/main.go check
|
||||
- name: Check Vendor Directory
|
||||
# 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 All Auto-Generated Files
|
||||
# ensure all our auto-generated files are up to date
|
||||
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)
|
||||
go generate ./... && git diff --quiet || (git status -s; echo "Auto-generated files not up to date. Run 'go generate ./...' locally and commit the changes" && exit 1)
|
||||
shell: bash # needed so that we get "-o pipefail"
|
||||
- name: Check Filenames
|
||||
run: scripts/check_filenames.sh
|
||||
@@ -185,20 +146,11 @@ jobs:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v1
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: 1.20.x
|
||||
- name: Cache build
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: |
|
||||
~/.cache/go-build
|
||||
~/go/pkg/mod
|
||||
key: ${{runner.os}}-go-${{hashFiles('**/go.sum')}}-test
|
||||
restore-keys: |
|
||||
${{runner.os}}-go-
|
||||
- name: Lint
|
||||
uses: golangci/golangci-lint-action@v3.1.0
|
||||
uses: golangci/golangci-lint-action@v3.7.0
|
||||
with:
|
||||
version: latest
|
||||
- name: errors
|
||||
@@ -206,6 +158,7 @@ jobs:
|
||||
if: ${{ failure() }}
|
||||
check-required-label:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.ref != 'refs/heads/master'
|
||||
steps:
|
||||
- uses: mheap/github-action-required-labels@v5
|
||||
with:
|
||||
|
||||
@@ -12,7 +12,7 @@ jobs:
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Generate Sponsors 💖
|
||||
uses: JamesIves/github-sponsors-readme-action@v1.0.8
|
||||
uses: JamesIves/github-sponsors-readme-action@v1.2.2
|
||||
with:
|
||||
token: ${{ secrets.SPONSORS_TOKEN }}
|
||||
file: 'README.md'
|
||||
|
||||
+1
-1
@@ -35,7 +35,7 @@ lazygit.exe
|
||||
|
||||
test/git_server/data
|
||||
|
||||
test/results/**
|
||||
test/_results/**
|
||||
|
||||
oryxBuildBinary
|
||||
__debug_bin
|
||||
|
||||
+5
-1
@@ -20,7 +20,11 @@ linters:
|
||||
linters-settings:
|
||||
exhaustive:
|
||||
default-signifies-exhaustive: true
|
||||
|
||||
staticcheck:
|
||||
# SA1019 is for checking that we're not using fields marked as deprecated
|
||||
# in a comment. It decides this in a loose way so I'm silencing it. Also because
|
||||
# it's tripping on our own structs.
|
||||
checks: ["all", "-SA1019"]
|
||||
nakedret:
|
||||
# the gods will judge me but I just don't like naked returns at all
|
||||
max-func-lines: 0
|
||||
|
||||
Vendored
+35
-4
@@ -7,7 +7,11 @@
|
||||
"request": "launch",
|
||||
"mode": "auto",
|
||||
"program": "main.go",
|
||||
"args": ["--debug", "--use-config-file=${workspaceFolder}/.vscode/debugger_config.yml"],
|
||||
"args": [
|
||||
"--debug",
|
||||
"--use-config-file=${workspaceFolder}/.vscode/debugger_config.yml"
|
||||
],
|
||||
"hideSystemGoroutines": true,
|
||||
"console": "integratedTerminal",
|
||||
"presentation": {
|
||||
"hidden": true
|
||||
@@ -19,17 +23,44 @@
|
||||
"request": "launch",
|
||||
"mode": "auto",
|
||||
"program": "main.go",
|
||||
"args": ["--logs", "--use-config-file=${workspaceFolder}/.vscode/debugger_config.yml"],
|
||||
"args": [
|
||||
"--logs",
|
||||
"--use-config-file=${workspaceFolder}/.vscode/debugger_config.yml"
|
||||
],
|
||||
"console": "integratedTerminal",
|
||||
"presentation": {
|
||||
"hidden": true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Attach to a running Lazygit",
|
||||
"type": "go",
|
||||
"request": "attach",
|
||||
"mode": "local",
|
||||
"processId": "lazygit",
|
||||
"hideSystemGoroutines": true,
|
||||
"console": "integratedTerminal",
|
||||
},
|
||||
{
|
||||
// To use this, first start an integration test with the "cli" runner and
|
||||
// use the -debug option; e.g.
|
||||
// $ make integration-test-cli -- -debug tag/reset.go
|
||||
"name": "Attach to integration test runner",
|
||||
"type": "go",
|
||||
"request": "attach",
|
||||
"mode": "local",
|
||||
"processId": "test_lazygit",
|
||||
"hideSystemGoroutines": true,
|
||||
"console": "integratedTerminal",
|
||||
},
|
||||
],
|
||||
"compounds": [
|
||||
{
|
||||
"name": "Run with logs",
|
||||
"configurations": ["Tail Lazygit logs", "Debug Lazygit"],
|
||||
"configurations": [
|
||||
"Tail Lazygit logs",
|
||||
"Debug Lazygit"
|
||||
],
|
||||
"stopAll": true
|
||||
}
|
||||
]
|
||||
|
||||
Vendored
+5
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"gopls": {
|
||||
"formatting.gofumpt": true,
|
||||
},
|
||||
}
|
||||
@@ -21,6 +21,8 @@ welcome your pull requests:
|
||||
4. Write a [good commit message](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html).
|
||||
5. Issue that pull request!
|
||||
|
||||
Please do not raise pull request from your fork's master branch: make a feature branch instead. Lazygit maintainers will sometimes push changes to your branch when reviewing a PR and we often can't do this if you use your master branch.
|
||||
|
||||
If you've never written Go in your life, then join the club! Lazygit was the maintainer's first Go program, and most contributors have never used Go before. Go is widely considered an easy-to-learn language, so if you're looking for an open source project to gain dev experience, you've come to the right place.
|
||||
|
||||
## Running in a VSCode dev container
|
||||
@@ -97,6 +99,10 @@ To run gofumpt from your terminal go:
|
||||
go install mvdan.cc/gofumpt@latest && gofumpt -l -w .
|
||||
```
|
||||
|
||||
## Programming Font
|
||||
|
||||
Lazygit supports [Nerd Fonts](https://www.nerdfonts.com) to render certain icons. Sometimes we use some of these icons verbatim in string literals in the code (mainly in tests), so you need to set your development environment to use a nerd font to see these.
|
||||
|
||||
## Internationalisation
|
||||
|
||||
Boy that's a hard word to spell. Anyway, lazygit is translated into several languages within the pkg/i18n package. If you need to render text to the user, you should add a new field to the TranslationSet struct in `pkg/i18n/english.go` and add the actual content within the `EnglishTranslationSet()` method in the same file. Then you can access via `gui.Tr.YourNewText` (or `self.c.Tr.YourNewText`, etc). Although it is appreciated if you translate the text into other languages, it's not expected of you (google translate will likely do a bad job anyway!).
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
.PHONY: all
|
||||
all: build
|
||||
|
||||
.PHONY: build
|
||||
build:
|
||||
go build -gcflags='all=-N -l'
|
||||
|
||||
.PHONY: install
|
||||
install:
|
||||
go install
|
||||
|
||||
.PHONY: run
|
||||
run: build
|
||||
./lazygit
|
||||
|
||||
# Run `make run-debug` in one terminal tab and `make print-log` in another to view the program and its log output side by side
|
||||
.PHONY: run-debug
|
||||
run-debug:
|
||||
go run main.go -debug
|
||||
|
||||
.PHONY: print-log
|
||||
print-log:
|
||||
go run main.go --logs
|
||||
|
||||
.PHONY: unit-test
|
||||
unit-test:
|
||||
go test ./... -short
|
||||
|
||||
.PHONY: test
|
||||
test: unit-test integration-test-all
|
||||
|
||||
# Generate all our auto-generated files (test list, cheatsheets, maybe other things in the future)
|
||||
.PHONY: generate
|
||||
generate:
|
||||
go generate ./...
|
||||
|
||||
.PHONY: format
|
||||
format:
|
||||
gofumpt -l -w .
|
||||
|
||||
# For more details about integration test, see https://github.com/jesseduffield/lazygit/blob/master/pkg/integration/README.md.
|
||||
.PHONY: integration-test-tui
|
||||
integration-test-tui:
|
||||
go run cmd/integration_test/main.go tui $(filter-out $@,$(MAKECMDGOALS))
|
||||
|
||||
.PHONY: integration-test-cli
|
||||
integration-test-cli:
|
||||
go run cmd/integration_test/main.go cli $(filter-out $@,$(MAKECMDGOALS))
|
||||
|
||||
.PHONY: integration-test-all
|
||||
integration-test-all:
|
||||
go test pkg/integration/clients/*.go
|
||||
|
||||
.PHONY: bump-gocui
|
||||
bump-gocui:
|
||||
scripts/bump_gocui.sh
|
||||
|
||||
.PHONY: bump-lazycore
|
||||
bump-lazycore:
|
||||
scripts/bump_lazycore.sh
|
||||
|
||||
.PHONY: record-demo
|
||||
record-demo:
|
||||
demo/record_demo.sh $(filter-out $@,$(MAKECMDGOALS))
|
||||
|
||||
.PHONY: vendor
|
||||
vendor:
|
||||
go mod vendor && go mod tidy
|
||||
@@ -16,7 +16,7 @@ Usage:
|
||||
> go run cmd/integration_test/main.go cli [--slow] [--sandbox] <test1> <test2> ...
|
||||
If you pass no test names, it runs all tests
|
||||
Accepted environment variables:
|
||||
KEY_PRESS_DELAY (e.g. 200): the number of milliseconds to wait between keypresses
|
||||
INPUT_DELAY (e.g. 200): the number of milliseconds to wait between keypresses or mouse clicks
|
||||
|
||||
TUI mode:
|
||||
> go run cmd/integration_test/main.go tui
|
||||
@@ -26,6 +26,29 @@ Usage:
|
||||
> go run cmd/integration_test/main.go help
|
||||
`
|
||||
|
||||
type flagInfo struct {
|
||||
name string // name of the flag; can be used with "-" or "--"
|
||||
flag *bool // a pointer to the variable that should be set to true when this flag is passed
|
||||
}
|
||||
|
||||
// Takes the args that you want to parse (excluding the program name and any
|
||||
// subcommands), and returns the remaining args with the flags removed
|
||||
func parseFlags(args []string, flags []flagInfo) []string {
|
||||
outer:
|
||||
for len(args) > 0 {
|
||||
for _, f := range flags {
|
||||
if args[0] == "-"+f.name || args[0] == "--"+f.name {
|
||||
*f.flag = true
|
||||
args = args[1:]
|
||||
continue outer
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
return args
|
||||
}
|
||||
|
||||
func main() {
|
||||
if len(os.Args) < 2 {
|
||||
log.Fatal(usage)
|
||||
@@ -35,23 +58,26 @@ func main() {
|
||||
case "help":
|
||||
fmt.Println(usage)
|
||||
case "cli":
|
||||
testNames := os.Args[2:]
|
||||
slow := false
|
||||
sandbox := false
|
||||
// get the next arg if it's --slow
|
||||
if len(os.Args) > 2 {
|
||||
if os.Args[2] == "--slow" || os.Args[2] == "-slow" {
|
||||
testNames = os.Args[3:]
|
||||
slow = true
|
||||
} else if os.Args[2] == "--sandbox" || os.Args[2] == "-sandbox" {
|
||||
testNames = os.Args[3:]
|
||||
sandbox = true
|
||||
}
|
||||
}
|
||||
|
||||
clients.RunCLI(testNames, slow, sandbox)
|
||||
waitForDebugger := false
|
||||
raceDetector := false
|
||||
testNames := parseFlags(os.Args[2:], []flagInfo{
|
||||
{"slow", &slow},
|
||||
{"sandbox", &sandbox},
|
||||
{"debug", &waitForDebugger},
|
||||
{"race", &raceDetector},
|
||||
})
|
||||
clients.RunCLI(testNames, slow, sandbox, waitForDebugger, raceDetector)
|
||||
case "tui":
|
||||
clients.RunTUI()
|
||||
raceDetector := false
|
||||
remainingArgs := parseFlags(os.Args[2:], []flagInfo{
|
||||
{"race", &raceDetector},
|
||||
})
|
||||
if len(remainingArgs) > 0 {
|
||||
log.Fatal("tui only supports the -race argument.")
|
||||
}
|
||||
clients.RunTUI(raceDetector)
|
||||
default:
|
||||
log.Fatal(usage)
|
||||
}
|
||||
|
||||
+4
-1
@@ -75,7 +75,10 @@ cursorStyle: block
|
||||
# Font family
|
||||
# You can use any font that is installed on your machine
|
||||
# in CSS-like syntax
|
||||
fontFamily: "DejaVuSansMono Nerd Font"
|
||||
# Download from:
|
||||
# https://github.com/ryanoasis/nerd-fonts/releases/download/v3.0.2/DejaVuSansMono.zip
|
||||
# Not using the mono font because it makes icons too small.
|
||||
fontFamily: "DejaVuSansM Nerd Font"
|
||||
|
||||
# The size of the font
|
||||
fontSize: 8
|
||||
|
||||
+44
-36
@@ -16,6 +16,17 @@ If you want to change the config directory:
|
||||
|
||||
- MacOS: `export XDG_CONFIG_HOME="$HOME/.config"`
|
||||
|
||||
JSON schema is available for `config.yml` so that IntelliSense in Visual Studio Code (completion and error checking) is automatically enabled when the [YAML Red Hat][yaml] extension is installed. However, note that automatic schema detection only works if your config file is in one of the standard paths mentioned above. If you override the path to the file, you can still make IntelliSense work by adding
|
||||
|
||||
```yaml
|
||||
# yaml-language-server: $schema=https://json.schemastore.org/lazygit.json
|
||||
```
|
||||
|
||||
to the top of your config file or via [Visual Studio Code settings.json config][settings].
|
||||
|
||||
[yaml]: https://marketplace.visualstudio.com/items?itemName=redhat.vscode-yaml
|
||||
[settings]: https://github.com/redhat-developer/vscode-yaml#associating-a-schema-to-a-glob-pattern-via-yamlschemas
|
||||
|
||||
## Default
|
||||
|
||||
```yaml
|
||||
@@ -24,10 +35,12 @@ gui:
|
||||
windowSize: 'normal' # one of 'normal' | 'half' | 'full' default is 'normal'
|
||||
scrollHeight: 2 # how many lines you scroll by
|
||||
scrollPastBottom: true # enable scrolling past the bottom
|
||||
scrollOffMargin: 2 # how many lines to keep before/after the cursor when it reaches the top/bottom of the view; see 'Scroll-off Margin' section below
|
||||
scrollOffBehavior: 'margin' # one of 'margin' | 'jump'; see 'Scroll-off Margin' section below
|
||||
sidePanelWidth: 0.3333 # number from 0 to 1
|
||||
expandFocusedSidePanel: false
|
||||
mainPanelSplitMode: 'flexible' # one of 'horizontal' | 'flexible' | 'vertical'
|
||||
language: 'auto' # one of 'auto' | 'en' | 'zh' | 'pl' | 'nl' | 'ja' | 'ko' | 'ru'
|
||||
language: 'auto' # one of 'auto' | 'en' | 'zh-CN' | 'zh-TW' | 'pl' | 'nl' | 'ja' | 'ko' | 'ru'
|
||||
timeFormat: '02 Jan 06' # https://pkg.go.dev/time#Time.Format
|
||||
shortTimeFormat: '3:04PM'
|
||||
theme:
|
||||
@@ -63,14 +76,16 @@ gui:
|
||||
showRandomTip: true
|
||||
showBranchCommitHash: false # show commit hashes alongside branch names
|
||||
showBottomLine: true # for hiding the bottom information line (unless it has important information to tell you)
|
||||
showPanelJumps: true # for showing the jump-to-panel keybindings as panel subtitles
|
||||
showCommandLog: true
|
||||
showIcons: false # deprecated: use nerdFontsVersion instead
|
||||
nerdFontsVersion: "" # nerd fonts version to use ("2" or "3"); empty means don't show nerd font icons
|
||||
commandLogSize: 8
|
||||
splitDiff: 'auto' # one of 'auto' | 'always'
|
||||
skipRewordInEditorWarning: false # for skipping the confirmation before launching the reword editor
|
||||
border: 'single' # one of 'single' | 'double' | 'rounded' | 'hidden'
|
||||
border: 'rounded' # one of 'single' | 'double' | 'rounded' | 'hidden'
|
||||
animateExplosion: true # shows an explosion animation when nuking the working tree
|
||||
portraitMode: 'auto' # one of 'auto' | 'never' | 'always'
|
||||
git:
|
||||
paging:
|
||||
colorArg: always
|
||||
@@ -104,7 +119,6 @@ git:
|
||||
overrideGpg: false # prevents lazygit from spawning a separate process when using GPG
|
||||
disableForcePushing: false
|
||||
parseEmoji: false
|
||||
diffContextSize: 3 # how many lines of context are shown around a change in diffs
|
||||
os:
|
||||
copyToClipboardCmd: '' # See 'Custom Command for Copying to Clipboard' section
|
||||
editPreset: '' # see 'Configuring File Editing' section
|
||||
@@ -149,7 +163,7 @@ keybinding:
|
||||
jumpToBlock: ['1', '2', '3', '4', '5'] # goto the Nth block / panel
|
||||
nextMatch: 'n'
|
||||
prevMatch: 'N'
|
||||
optionMenu: null # show help menu
|
||||
optionMenu: <disabled> # show help menu
|
||||
optionMenu-alt1: '?' # show help menu
|
||||
select: '<space>'
|
||||
goInto: '<enter>'
|
||||
@@ -194,6 +208,7 @@ keybinding:
|
||||
commitChangesWithoutHook: 'w' # commit changes without pre-commit hook
|
||||
amendLastCommit: 'A'
|
||||
commitChangesWithEditor: 'C'
|
||||
confirmDiscard: 'x'
|
||||
ignoreFile: 'i'
|
||||
refreshFiles: 'r'
|
||||
stashAllChanges: 's'
|
||||
@@ -279,7 +294,7 @@ os:
|
||||
open: 'open {{filename}}'
|
||||
```
|
||||
|
||||
### Custom Command for Copying to Clipboard
|
||||
## Custom Command for Copying to Clipboard
|
||||
```yaml
|
||||
os:
|
||||
copyToClipboardCmd: ''
|
||||
@@ -293,29 +308,22 @@ os:
|
||||
```
|
||||
|
||||
|
||||
### Configuring File Editing
|
||||
## Configuring File Editing
|
||||
|
||||
There are two commands for opening files, `o` for "open" and `e` for "edit". `o`
|
||||
acts as if the file was double-clicked in the Finder/Explorer, so it also works
|
||||
for non-text files, whereas `e` opens the file in an editor. `e` can also jump
|
||||
to the right line in the file if you invoke it from the staging panel, for
|
||||
example.
|
||||
There are two commands for opening files, `o` for "open" and `e` for "edit". `o` acts as if the file was double-clicked in the Finder/Explorer, so it also works for non-text files, whereas `e` opens the file in an editor. `e` can also jump to the right line in the file if you invoke it from the staging panel, for example.
|
||||
|
||||
To tell lazygit which editor to use for the `e` command, the easiest way to do
|
||||
that is to provide an editPreset config, e.g.
|
||||
To tell lazygit which editor to use for the `e` command, the easiest way to do that is to provide an editPreset config, e.g.
|
||||
|
||||
```yaml
|
||||
os:
|
||||
editPreset: 'vscode'
|
||||
```
|
||||
|
||||
Supported presets are `vim`, `nvim`, `emacs`, `nano`, `vscode`, `sublime`, `bbedit`,
|
||||
`kakoune`, `helix`, and `xcode`. In many cases lazygit will be able to guess the right preset
|
||||
from your $(git config core.editor), or an environment variable such as $VISUAL or $EDITOR.
|
||||
Supported presets are `vim`, `nvim`, `nvim-remote`, `lvim`, `emacs`, `nano`, `micro`, `vscode`, `sublime`, `bbedit`, `kakoune`, `helix`, 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
|
||||
setting the `edit`, `editAtLine`, and `editAtLineAndWait` options, e.g.:
|
||||
`nvim-remote` is an experimental preset for when you have invoked lazygit from within a neovim process, allowing lazygit to open the file from within the parent process rather than spawning a new one.
|
||||
|
||||
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 setting the `edit`, `editAtLine`, and `editAtLineAndWait` options, e.g.:
|
||||
|
||||
```yaml
|
||||
os:
|
||||
@@ -326,13 +334,11 @@ os:
|
||||
openDirInEditor: 'myeditor {{dir}}'
|
||||
```
|
||||
|
||||
The `editInTerminal` option is used to decide whether lazygit needs to suspend
|
||||
itself to the background before calling the editor.
|
||||
The `editInTerminal` option is used to decide whether lazygit needs to suspend itself to the background before calling the editor. It should really be named `suspend` because for some cases like when lazygit is opened from within a neovim session and you're using the `nvim-remote` preset, you're technically still in a terminal. Nonetheless we're sticking with the name `editInTerminal` for backwards compatibility.
|
||||
|
||||
Contributions of new editor presets are welcome; see the `getPreset` function in
|
||||
[`editor_presets.go`](https://github.com/jesseduffield/lazygit/blob/master/pkg/config/editor_presets.go).
|
||||
Contributions of new editor presets are welcome; see the `getPreset` function in [`editor_presets.go`](https://github.com/jesseduffield/lazygit/blob/master/pkg/config/editor_presets.go).
|
||||
|
||||
### Overriding default config file location
|
||||
## Overriding default config file location
|
||||
|
||||
To override the default config directory, use `CONFIG_DIR="$HOME/.config/lazygit"`. This directory contains the config file in addition to some other files lazygit uses to keep track of state across sessions.
|
||||
|
||||
@@ -346,6 +352,14 @@ or
|
||||
LG_CONFIG_FILE="$HOME/.base_lg_conf,$HOME/.light_theme_lg_conf" lazygit
|
||||
```
|
||||
|
||||
## Scroll-off Margin
|
||||
|
||||
When the selected line gets close to the bottom of the window and you hit down-arrow, there's a feature called "scroll-off margin" that lets the view scroll a little earlier so that you can see a bit of what's coming in the direction that you are moving. This is controlled by the `gui.scrollOffMargin` setting (default: 2), so it keeps 2 lines below the selection visible as you scroll down. It can be set to 0 to scroll only when the selection reaches the bottom of the window.
|
||||
|
||||
That's the behavior when `gui.scrollOffBehavior` is set to "margin" (the default). If you set `gui.scrollOffBehavior` to "jump", then upon reaching the last line of a view and hitting down-arrow the view will scroll by half a page so that the selection ends up in the middle of the view. This may feel a little jarring because the cursor jumps around when continuously moving down, but it has the advantage that the view doesn't scroll as often.
|
||||
|
||||
This setting applies both to all list views (e.g. commits and branches etc), and to the staging view.
|
||||
|
||||
## Color Attributes
|
||||
|
||||
For color attributes you can choose an array of attributes (with max one color attribute)
|
||||
@@ -442,19 +456,18 @@ gui:
|
||||
nerdFontsVersion: "3"
|
||||
```
|
||||
|
||||
Supported versions are "2" and "3". The deprecated config `showIcons` sets the
|
||||
version to "2" for backwards compatibility.
|
||||
Supported versions are "2" and "3". The deprecated config `showIcons` sets the version to "2" for backwards compatibility.
|
||||
|
||||
## Keybindings
|
||||
|
||||
For all possible keybinding options, check [Custom_Keybindings.md](https://github.com/jesseduffield/lazygit/blob/master/docs/keybindings/Custom_Keybindings.md)
|
||||
|
||||
You can disable certain key bindings by specifying `null`.
|
||||
You can disable certain key bindings by specifying `<disabled>`.
|
||||
|
||||
```yaml
|
||||
keybinding:
|
||||
universal:
|
||||
edit: null # disable 'edit file'
|
||||
edit: <disabled> # disable 'edit file'
|
||||
```
|
||||
|
||||
### Example Keybindings For Colemak Users
|
||||
@@ -491,9 +504,7 @@ keybinding:
|
||||
|
||||
## Custom pull request URLs
|
||||
|
||||
Some git provider setups (e.g. on-premises GitLab) can have distinct URLs for git-related calls and
|
||||
the web interface/API itself. To work with those, Lazygit needs to know where it needs to create
|
||||
the pull request. You can do so on your `config.yml` file using the following syntax:
|
||||
Some git provider setups (e.g. on-premises GitLab) can have distinct URLs for git-related calls and the web interface/API itself. To work with those, Lazygit needs to know where it needs to create the pull request. You can do so on your `config.yml` file using the following syntax:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
@@ -508,8 +519,7 @@ Where:
|
||||
|
||||
## Predefined commit message prefix
|
||||
|
||||
In situations where certain naming pattern is used for branches and commits, pattern can be used to populate
|
||||
commit message with prefix that is parsed from the branch name.
|
||||
In situations where certain naming pattern is used for branches and commits, pattern can be used to populate commit message with prefix that is parsed from the branch name.
|
||||
|
||||
Example:
|
||||
|
||||
@@ -539,9 +549,7 @@ Result:
|
||||
|
||||
## Launching not in a repository behaviour
|
||||
|
||||
By default, when launching lazygit from a directory that is not a repository,
|
||||
you will be prompted to choose if you would like to initialize a repo. You can
|
||||
override this behaviour in the config with one of the following:
|
||||
By default, when launching lazygit from a directory that is not a repository, you will be prompted to choose if you would like to initialize a repo. You can override this behaviour in the config with one of the following:
|
||||
|
||||
```yaml
|
||||
# for default prompting behaviour
|
||||
|
||||
+23
-1
@@ -1,6 +1,6 @@
|
||||
# Custom Pagers
|
||||
|
||||
Lazygit supports custom pagers, [configured](/docs/Config.md) in the config.yml file (which can be opened by pressing `o` in the Status panel).
|
||||
Lazygit supports custom pagers, [configured](/docs/Config.md) in the config.yml file (which can be opened by pressing `e` in the Status panel).
|
||||
|
||||
Support does not extend to Windows users, because we're making use of a package which doesn't have Windows support.
|
||||
|
||||
@@ -62,3 +62,25 @@ git:
|
||||
```
|
||||
|
||||
If you set `useConfig: true`, lazygit will use whatever pager is specified in `$GIT_PAGER`, `$PAGER`, or your *git config*. If the pager ends with something like ` | less` we will strip that part out, because less doesn't play nice with our rendering approach. If the custom pager uses less under the hood, that will also break rendering (hence the `--paging=never` flag for the `delta` pager).
|
||||
|
||||
## Using external diff commands
|
||||
|
||||
Some diff tools can't work as a simple pager like the ones above do, because they need access to the entire diff, so just post-processing git's diff is not enough for them. The most notable example is probably [difftastic](https://difftastic.wilfred.me.uk).
|
||||
|
||||
These can be used in lazygit by using the `externalDiffCommand` config; in the case of difftastic, that could be
|
||||
|
||||
```yaml
|
||||
git:
|
||||
paging:
|
||||
externalDiffCommand: difft --color=always
|
||||
```
|
||||
|
||||
The `colorArg`, `pager`, and `useConfig` options are not used in this case.
|
||||
|
||||
You can add whatever extra arguments you prefer for your difftool; for instance
|
||||
|
||||
```yaml
|
||||
git:
|
||||
paging:
|
||||
externalDiffCommand: difft --color=always --display=inline --syntax-highlight=off
|
||||
```
|
||||
|
||||
+3
-3
@@ -1,9 +1,9 @@
|
||||
# Undo/Redo in lazygit
|
||||
|
||||

|
||||
You can undo the last action by pressing 'z' and redo with `ctrl+z`. Here we drop a couple of commits and then undo the actions.
|
||||
Undo uses the reflog which is specific to commits and branches so we can't undo changes to the working tree or stash.
|
||||
|
||||
## Keybindings:
|
||||
'z' to undo, 'ctrl+z' to redo
|
||||

|
||||
|
||||
## How it works
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
_This file is auto-generated. To update, make the changes in the pkg/i18n directory and then run `go run scripts/cheatsheet/main.go generate` from the project root._
|
||||
_This file is auto-generated. To update, make the changes in the pkg/i18n directory and then run `go generate ./...` from the project root._
|
||||
|
||||
# Lazygit Keybindings
|
||||
|
||||
@@ -148,14 +148,14 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd><c-y></kbd>: Copy pull request URL to clipboard
|
||||
<kbd>c</kbd>: Checkout by name
|
||||
<kbd>F</kbd>: Force checkout
|
||||
<kbd>d</kbd>: Delete branch
|
||||
<kbd>d</kbd>: View delete options
|
||||
<kbd>r</kbd>: Rebase checked-out branch onto this branch
|
||||
<kbd>M</kbd>: Merge into currently checked out branch
|
||||
<kbd>f</kbd>: Fast-forward this branch from its upstream
|
||||
<kbd>T</kbd>: Create tag
|
||||
<kbd>g</kbd>: View reset options
|
||||
<kbd>R</kbd>: Rename branch
|
||||
<kbd>u</kbd>: Set/Unset upstream
|
||||
<kbd>u</kbd>: View upstream options
|
||||
<kbd>w</kbd>: View worktree options
|
||||
<kbd><enter></kbd>: View commits
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
@@ -255,7 +255,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>n</kbd>: New branch
|
||||
<kbd>M</kbd>: Merge into currently checked out branch
|
||||
<kbd>r</kbd>: Rebase checked-out branch onto this branch
|
||||
<kbd>d</kbd>: Delete branch
|
||||
<kbd>d</kbd>: Delete remote tag
|
||||
<kbd>u</kbd>: Set as upstream of checked-out branch
|
||||
<kbd>g</kbd>: View reset options
|
||||
<kbd>w</kbd>: View worktree options
|
||||
@@ -332,7 +332,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
|
||||
<pre>
|
||||
<kbd><space></kbd>: Checkout
|
||||
<kbd>d</kbd>: Delete tag
|
||||
<kbd>d</kbd>: View delete options
|
||||
<kbd>P</kbd>: Push tag
|
||||
<kbd>n</kbd>: Create tag
|
||||
<kbd>g</kbd>: View reset options
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
_This file is auto-generated. To update, make the changes in the pkg/i18n directory and then run `go run scripts/cheatsheet/main.go generate` from the project root._
|
||||
_This file is auto-generated. To update, make the changes in the pkg/i18n directory and then run `go generate ./...` from the project root._
|
||||
|
||||
# Lazygit キーバインド
|
||||
|
||||
@@ -172,7 +172,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
|
||||
<pre>
|
||||
<kbd><space></kbd>: チェックアウト
|
||||
<kbd>d</kbd>: タグを削除
|
||||
<kbd>d</kbd>: View delete options
|
||||
<kbd>P</kbd>: タグをpush
|
||||
<kbd>n</kbd>: タグを作成
|
||||
<kbd>g</kbd>: View reset options
|
||||
@@ -220,14 +220,14 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd><c-y></kbd>: Pull RequestのURLをクリップボードにコピー
|
||||
<kbd>c</kbd>: Checkout by name
|
||||
<kbd>F</kbd>: Force checkout
|
||||
<kbd>d</kbd>: ブランチを削除
|
||||
<kbd>d</kbd>: View delete options
|
||||
<kbd>r</kbd>: Rebase checked-out branch onto this branch
|
||||
<kbd>M</kbd>: 現在のブランチにマージ
|
||||
<kbd>f</kbd>: Fast-forward this branch from its upstream
|
||||
<kbd>T</kbd>: タグを作成
|
||||
<kbd>g</kbd>: View reset options
|
||||
<kbd>R</kbd>: ブランチ名を変更
|
||||
<kbd>u</kbd>: Set/Unset upstream
|
||||
<kbd>u</kbd>: View upstream options
|
||||
<kbd>w</kbd>: View worktree options
|
||||
<kbd><enter></kbd>: コミットを閲覧
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
@@ -320,7 +320,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>n</kbd>: 新しいブランチを作成
|
||||
<kbd>M</kbd>: 現在のブランチにマージ
|
||||
<kbd>r</kbd>: Rebase checked-out branch onto this branch
|
||||
<kbd>d</kbd>: ブランチを削除
|
||||
<kbd>d</kbd>: Delete remote tag
|
||||
<kbd>u</kbd>: Set as upstream of checked-out branch
|
||||
<kbd>g</kbd>: View reset options
|
||||
<kbd>w</kbd>: View worktree options
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
_This file is auto-generated. To update, make the changes in the pkg/i18n directory and then run `go run scripts/cheatsheet/main.go generate` from the project root._
|
||||
_This file is auto-generated. To update, make the changes in the pkg/i18n directory and then run `go generate ./...` from the project root._
|
||||
|
||||
# Lazygit 키 바인딩
|
||||
|
||||
@@ -183,14 +183,14 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd><c-y></kbd>: 풀 리퀘스트 URL을 클립보드에 복사
|
||||
<kbd>c</kbd>: 이름으로 체크아웃
|
||||
<kbd>F</kbd>: 강제 체크아웃
|
||||
<kbd>d</kbd>: 브랜치 삭제
|
||||
<kbd>d</kbd>: View delete options
|
||||
<kbd>r</kbd>: 체크아웃된 브랜치를 이 브랜치에 리베이스
|
||||
<kbd>M</kbd>: 현재 브랜치에 병합
|
||||
<kbd>f</kbd>: Fast-forward this branch from its upstream
|
||||
<kbd>T</kbd>: 태그를 생성
|
||||
<kbd>g</kbd>: View reset options
|
||||
<kbd>R</kbd>: 브랜치 이름 변경
|
||||
<kbd>u</kbd>: Set/Unset upstream
|
||||
<kbd>u</kbd>: View upstream options
|
||||
<kbd>w</kbd>: View worktree options
|
||||
<kbd><enter></kbd>: 커밋 보기
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
@@ -239,7 +239,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>n</kbd>: 새 브랜치 생성
|
||||
<kbd>M</kbd>: 현재 브랜치에 병합
|
||||
<kbd>r</kbd>: 체크아웃된 브랜치를 이 브랜치에 리베이스
|
||||
<kbd>d</kbd>: 브랜치 삭제
|
||||
<kbd>d</kbd>: Delete remote tag
|
||||
<kbd>u</kbd>: Set as upstream of checked-out branch
|
||||
<kbd>g</kbd>: View reset options
|
||||
<kbd>w</kbd>: View worktree options
|
||||
@@ -309,7 +309,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
|
||||
<pre>
|
||||
<kbd><space></kbd>: 체크아웃
|
||||
<kbd>d</kbd>: 태그 삭제
|
||||
<kbd>d</kbd>: View delete options
|
||||
<kbd>P</kbd>: 태그를 push
|
||||
<kbd>n</kbd>: 태그를 생성
|
||||
<kbd>g</kbd>: View reset options
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
_This file is auto-generated. To update, make the changes in the pkg/i18n directory and then run `go run scripts/cheatsheet/main.go generate` from the project root._
|
||||
_This file is auto-generated. To update, make the changes in the pkg/i18n directory and then run `go generate ./...` from the project root._
|
||||
|
||||
# Lazygit Sneltoetsen
|
||||
|
||||
@@ -90,14 +90,14 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd><c-y></kbd>: Kopieer de URL van het pull-verzoek naar het klembord
|
||||
<kbd>c</kbd>: Uitchecken bij naam
|
||||
<kbd>F</kbd>: Forceer checkout
|
||||
<kbd>d</kbd>: Verwijder branch
|
||||
<kbd>d</kbd>: View delete options
|
||||
<kbd>r</kbd>: Rebase branch
|
||||
<kbd>M</kbd>: Merge in met huidige checked out branch
|
||||
<kbd>f</kbd>: Fast-forward deze branch vanaf zijn upstream
|
||||
<kbd>T</kbd>: Creëer tag
|
||||
<kbd>g</kbd>: Bekijk reset opties
|
||||
<kbd>R</kbd>: Hernoem branch
|
||||
<kbd>u</kbd>: Set/Unset upstream
|
||||
<kbd>u</kbd>: View upstream options
|
||||
<kbd>w</kbd>: View worktree options
|
||||
<kbd><enter></kbd>: Bekijk commits
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
@@ -233,7 +233,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>n</kbd>: Nieuwe branch
|
||||
<kbd>M</kbd>: Merge in met huidige checked out branch
|
||||
<kbd>r</kbd>: Rebase branch
|
||||
<kbd>d</kbd>: Verwijder branch
|
||||
<kbd>d</kbd>: Delete remote tag
|
||||
<kbd>u</kbd>: Stel in als upstream van uitgecheckte branch
|
||||
<kbd>g</kbd>: Bekijk reset opties
|
||||
<kbd>w</kbd>: View worktree options
|
||||
@@ -332,7 +332,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
|
||||
<pre>
|
||||
<kbd><space></kbd>: Uitchecken
|
||||
<kbd>d</kbd>: Verwijder tag
|
||||
<kbd>d</kbd>: View delete options
|
||||
<kbd>P</kbd>: Push tag
|
||||
<kbd>n</kbd>: Creëer tag
|
||||
<kbd>g</kbd>: Bekijk reset opties
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
_This file is auto-generated. To update, make the changes in the pkg/i18n directory and then run `go run scripts/cheatsheet/main.go generate` from the project root._
|
||||
_This file is auto-generated. To update, make the changes in the pkg/i18n directory and then run `go generate ./...` from the project root._
|
||||
|
||||
# Lazygit Keybindings
|
||||
|
||||
@@ -106,14 +106,14 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd><c-y></kbd>: Skopiuj adres URL żądania pobrania do schowka
|
||||
<kbd>c</kbd>: Przełącz używając nazwy
|
||||
<kbd>F</kbd>: Wymuś przełączenie
|
||||
<kbd>d</kbd>: Usuń gałąź
|
||||
<kbd>d</kbd>: View delete options
|
||||
<kbd>r</kbd>: Zmiana bazy gałęzi
|
||||
<kbd>M</kbd>: Scal do obecnej gałęzi
|
||||
<kbd>f</kbd>: Fast-forward this branch from its upstream
|
||||
<kbd>T</kbd>: Create tag
|
||||
<kbd>g</kbd>: Wyświetl opcje resetu
|
||||
<kbd>R</kbd>: Rename branch
|
||||
<kbd>u</kbd>: Set/Unset upstream
|
||||
<kbd>u</kbd>: View upstream options
|
||||
<kbd>w</kbd>: View worktree options
|
||||
<kbd><enter></kbd>: View commits
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
@@ -232,7 +232,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>n</kbd>: Nowa gałąź
|
||||
<kbd>M</kbd>: Scal do obecnej gałęzi
|
||||
<kbd>r</kbd>: Zmiana bazy gałęzi
|
||||
<kbd>d</kbd>: Usuń gałąź
|
||||
<kbd>d</kbd>: Delete remote tag
|
||||
<kbd>u</kbd>: Set as upstream of checked-out branch
|
||||
<kbd>g</kbd>: Wyświetl opcje resetu
|
||||
<kbd>w</kbd>: View worktree options
|
||||
@@ -325,7 +325,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
|
||||
<pre>
|
||||
<kbd><space></kbd>: Przełącz
|
||||
<kbd>d</kbd>: Delete tag
|
||||
<kbd>d</kbd>: View delete options
|
||||
<kbd>P</kbd>: Push tag
|
||||
<kbd>n</kbd>: Create tag
|
||||
<kbd>g</kbd>: Wyświetl opcje resetu
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
_This file is auto-generated. To update, make the changes in the pkg/i18n directory and then run `go run scripts/cheatsheet/main.go generate` from the project root._
|
||||
_This file is auto-generated. To update, make the changes in the pkg/i18n directory and then run `go generate ./...` from the project root._
|
||||
|
||||
# Lazygit Связки клавиш
|
||||
|
||||
@@ -181,14 +181,14 @@ _Связки клавиш_
|
||||
<kbd><c-y></kbd>: Скопировать URL запроса на принятие изменений в буфер обмена
|
||||
<kbd>c</kbd>: Переключить по названию
|
||||
<kbd>F</kbd>: Принудительное переключение
|
||||
<kbd>d</kbd>: Удалить ветку
|
||||
<kbd>d</kbd>: View delete options
|
||||
<kbd>r</kbd>: Перебазировать переключённую ветку на эту ветку
|
||||
<kbd>M</kbd>: Слияние с текущей переключённой веткой
|
||||
<kbd>f</kbd>: Перемотать эту ветку вперёд из её upstream-ветки
|
||||
<kbd>T</kbd>: Создать тег
|
||||
<kbd>g</kbd>: Просмотреть параметры сброса
|
||||
<kbd>R</kbd>: Переименовать ветку
|
||||
<kbd>u</kbd>: Установить/убрать upstream-ветку
|
||||
<kbd>u</kbd>: View upstream options
|
||||
<kbd>w</kbd>: View worktree options
|
||||
<kbd><enter></kbd>: Просмотреть коммиты
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
@@ -277,7 +277,7 @@ _Связки клавиш_
|
||||
|
||||
<pre>
|
||||
<kbd><space></kbd>: Переключить
|
||||
<kbd>d</kbd>: Удалить тег
|
||||
<kbd>d</kbd>: View delete options
|
||||
<kbd>P</kbd>: Отправить тег
|
||||
<kbd>n</kbd>: Создать тег
|
||||
<kbd>g</kbd>: Просмотреть параметры сброса
|
||||
@@ -294,7 +294,7 @@ _Связки клавиш_
|
||||
<kbd>n</kbd>: Новая ветка
|
||||
<kbd>M</kbd>: Слияние с текущей переключённой веткой
|
||||
<kbd>r</kbd>: Перебазировать переключённую ветку на эту ветку
|
||||
<kbd>d</kbd>: Удалить ветку
|
||||
<kbd>d</kbd>: Delete remote tag
|
||||
<kbd>u</kbd>: Установить как upstream-ветку переключённую ветку
|
||||
<kbd>g</kbd>: Просмотреть параметры сброса
|
||||
<kbd>w</kbd>: View worktree options
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
_This file is auto-generated. To update, make the changes in the pkg/i18n directory and then run `go run scripts/cheatsheet/main.go generate` from the project root._
|
||||
_This file is auto-generated. To update, make the changes in the pkg/i18n directory and then run `go generate ./...` from the project root._
|
||||
|
||||
# Lazygit 按键绑定
|
||||
|
||||
@@ -84,14 +84,14 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd><c-y></kbd>: 将抓取请求 URL 复制到剪贴板
|
||||
<kbd>c</kbd>: 按名称检出
|
||||
<kbd>F</kbd>: 强制检出
|
||||
<kbd>d</kbd>: 删除分支
|
||||
<kbd>d</kbd>: View delete options
|
||||
<kbd>r</kbd>: 将已检出的分支变基到该分支
|
||||
<kbd>M</kbd>: 合并到当前检出的分支
|
||||
<kbd>f</kbd>: 从上游快进此分支
|
||||
<kbd>T</kbd>: 创建标签
|
||||
<kbd>g</kbd>: 查看重置选项
|
||||
<kbd>R</kbd>: 重命名分支
|
||||
<kbd>u</kbd>: Set/Unset upstream
|
||||
<kbd>u</kbd>: View upstream options
|
||||
<kbd>w</kbd>: View worktree options
|
||||
<kbd><enter></kbd>: 查看提交
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
@@ -234,7 +234,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
|
||||
<pre>
|
||||
<kbd><space></kbd>: 检出
|
||||
<kbd>d</kbd>: 删除标签
|
||||
<kbd>d</kbd>: View delete options
|
||||
<kbd>P</kbd>: 推送标签
|
||||
<kbd>n</kbd>: 创建标签
|
||||
<kbd>g</kbd>: 查看重置选项
|
||||
@@ -334,7 +334,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd>n</kbd>: 新分支
|
||||
<kbd>M</kbd>: 合并到当前检出的分支
|
||||
<kbd>r</kbd>: 将已检出的分支变基到该分支
|
||||
<kbd>d</kbd>: 删除分支
|
||||
<kbd>d</kbd>: Delete remote tag
|
||||
<kbd>u</kbd>: 设置为检出分支的上游
|
||||
<kbd>g</kbd>: 查看重置选项
|
||||
<kbd>w</kbd>: View worktree options
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
_This file is auto-generated. To update, make the changes in the pkg/i18n directory and then run `go run scripts/cheatsheet/main.go generate` from the project root._
|
||||
_This file is auto-generated. To update, make the changes in the pkg/i18n directory and then run `go generate ./...` from the project root._
|
||||
|
||||
# Lazygit 鍵盤快捷鍵
|
||||
|
||||
@@ -256,14 +256,14 @@ _說明:`<c-b>` 表示 Ctrl+B、`<a-b>` 表示 Alt+B,`B`表示 Shift+B_
|
||||
<kbd><c-y></kbd>: 複製拉取請求的 URL 到剪貼板
|
||||
<kbd>c</kbd>: 根據名稱檢出
|
||||
<kbd>F</kbd>: 強制檢出
|
||||
<kbd>d</kbd>: 刪除分支
|
||||
<kbd>d</kbd>: View delete options
|
||||
<kbd>r</kbd>: 將已檢出的分支變基至此分支
|
||||
<kbd>M</kbd>: 合併到當前檢出的分支
|
||||
<kbd>f</kbd>: 從上游快進此分支
|
||||
<kbd>T</kbd>: 建立標籤
|
||||
<kbd>g</kbd>: 檢視重設選項
|
||||
<kbd>R</kbd>: 重新命名分支
|
||||
<kbd>u</kbd>: 設定/取消設定上游
|
||||
<kbd>u</kbd>: View upstream options
|
||||
<kbd>w</kbd>: View worktree options
|
||||
<kbd><enter></kbd>: 檢視提交
|
||||
<kbd>/</kbd>: Filter the current view by text
|
||||
@@ -273,7 +273,7 @@ _說明:`<c-b>` 表示 Ctrl+B、`<a-b>` 表示 Alt+B,`B`表示 Shift+B_
|
||||
|
||||
<pre>
|
||||
<kbd><space></kbd>: 檢出
|
||||
<kbd>d</kbd>: 刪除標籤
|
||||
<kbd>d</kbd>: View delete options
|
||||
<kbd>P</kbd>: 推送標籤
|
||||
<kbd>n</kbd>: 建立標籤
|
||||
<kbd>g</kbd>: 檢視重設選項
|
||||
@@ -344,7 +344,7 @@ _說明:`<c-b>` 表示 Ctrl+B、`<a-b>` 表示 Alt+B,`B`表示 Shift+B_
|
||||
<kbd>n</kbd>: 新分支
|
||||
<kbd>M</kbd>: 合併到當前檢出的分支
|
||||
<kbd>r</kbd>: 將已檢出的分支變基至此分支
|
||||
<kbd>d</kbd>: 刪除分支
|
||||
<kbd>d</kbd>: Delete remote tag
|
||||
<kbd>u</kbd>: 將此分支設為當前分支之上游
|
||||
<kbd>g</kbd>: 檢視重設選項
|
||||
<kbd>w</kbd>: View worktree options
|
||||
|
||||
@@ -9,14 +9,13 @@ require (
|
||||
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.5
|
||||
github.com/gdamore/tcell/v2 v2.6.0
|
||||
github.com/go-errors/errors v1.4.2
|
||||
github.com/go-errors/errors v1.5.1
|
||||
github.com/gookit/color v1.4.2
|
||||
github.com/imdario/mergo v0.3.11
|
||||
github.com/integrii/flaggy v1.4.0
|
||||
github.com/jesseduffield/generics v0.0.0-20220320043834-727e535cbe68
|
||||
github.com/jesseduffield/go-git/v5 v5.1.2-0.20221018185014-fdd53fef665d
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20230802092815-ae0a5c181634
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20230925062444-7cd0d7e2a70a
|
||||
github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10
|
||||
github.com/jesseduffield/lazycore v0.0.0-20221012050358-03d2e40243c5
|
||||
github.com/jesseduffield/minimal/gitignore v0.3.3-0.20211018110810-9cde264e6b1e
|
||||
@@ -26,6 +25,7 @@ require (
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0
|
||||
github.com/mattn/go-runewidth v0.0.15
|
||||
github.com/mgutz/str v1.2.0
|
||||
github.com/mitchellh/go-ps v1.0.0
|
||||
github.com/pmezard/go-difflib v1.0.0
|
||||
github.com/sahilm/fuzzy v0.1.0
|
||||
github.com/samber/lo v1.31.0
|
||||
@@ -34,6 +34,7 @@ require (
|
||||
github.com/sirupsen/logrus v1.4.2
|
||||
github.com/spf13/afero v1.9.5
|
||||
github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad
|
||||
github.com/stefanhaller/tcell/v2 v2.6.2-0.20230806061358-2dfa11eddb68
|
||||
github.com/stretchr/testify v1.8.0
|
||||
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778
|
||||
golang.org/x/exp v0.0.0-20220318154914-8dddf5d87bd8
|
||||
@@ -67,10 +68,8 @@ require (
|
||||
github.com/xanzy/ssh-agent v0.2.1 // indirect
|
||||
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa // indirect
|
||||
golang.org/x/net v0.7.0 // indirect
|
||||
golang.org/x/sys v0.10.0 // indirect
|
||||
golang.org/x/term v0.10.0 // indirect
|
||||
golang.org/x/text v0.11.0 // indirect
|
||||
golang.org/x/sys v0.12.0 // indirect
|
||||
golang.org/x/term v0.12.0 // indirect
|
||||
golang.org/x/text v0.13.0 // indirect
|
||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||
)
|
||||
|
||||
replace github.com/gdamore/tcell/v2 v2.6.0 => github.com/stefanhaller/tcell/v2 v2.6.1
|
||||
|
||||
@@ -88,8 +88,8 @@ github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo
|
||||
github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0=
|
||||
github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
|
||||
github.com/go-errors/errors v1.0.2/go.mod h1:psDX2osz5VnTOnFWbDeWwS7yejl+uV3FEWEp4lssFEs=
|
||||
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
|
||||
github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
|
||||
github.com/go-errors/errors v1.5.1 h1:ZwEMSLRCapFLflTpT7NKaAc7ukJ8ZPEjzlxt8rPN8bk=
|
||||
github.com/go-errors/errors v1.5.1/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
|
||||
github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4=
|
||||
github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E=
|
||||
github.com/go-git/go-billy/v5 v5.0.0 h1:7NQHvd9FVid8VL4qVUMm8XifBK+2xCoZ2lSk0agRrHM=
|
||||
@@ -179,8 +179,8 @@ github.com/jesseduffield/generics v0.0.0-20220320043834-727e535cbe68 h1:EQP2Tv8T
|
||||
github.com/jesseduffield/generics v0.0.0-20220320043834-727e535cbe68/go.mod h1:+LLj9/WUPAP8LqCchs7P+7X0R98HiFujVFANdNaxhGk=
|
||||
github.com/jesseduffield/go-git/v5 v5.1.2-0.20221018185014-fdd53fef665d h1:bO+OmbreIv91rCe8NmscRwhFSqkDJtzWCPV4Y+SQuXE=
|
||||
github.com/jesseduffield/go-git/v5 v5.1.2-0.20221018185014-fdd53fef665d/go.mod h1:nGNEErzf+NRznT+N2SWqmHnDnF9aLgANB1CUNEan09o=
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20230802092815-ae0a5c181634 h1:tL9DS59Po4kw1fR+wFn0zUYQhrgxMskTatLcYQl7MMU=
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20230802092815-ae0a5c181634/go.mod h1:pArPLsN1+S6jDrBDukTkpnH89rdhw44wzKdXFICGH3Q=
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20230925062444-7cd0d7e2a70a h1:/Ifbvq4BWY5VKoyPnHIW5oRLiy00S3ebFtVIY2MlJm0=
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20230925062444-7cd0d7e2a70a/go.mod h1:trXE7RRGL2hTsv+Ntk+SHLtRobg9JE138n3Ug/X2Cf4=
|
||||
github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10 h1:jmpr7KpX2+2GRiE91zTgfq49QvgiqB0nbmlwZ8UnOx0=
|
||||
github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10/go.mod h1:aA97kHeNA+sj2Hbki0pvLslmE4CbDyhBeSSTUUnOuVo=
|
||||
github.com/jesseduffield/lazycore v0.0.0-20221012050358-03d2e40243c5 h1:CDuQmfOjAtb1Gms6a1p5L2P8RhbLUq5t8aL7PiQd2uY=
|
||||
@@ -230,6 +230,8 @@ github.com/mgutz/str v1.2.0 h1:4IzWSdIz9qPQWLfKZ0rJcV0jcUDpxvP4JVZ4GXQyvSw=
|
||||
github.com/mgutz/str v1.2.0/go.mod h1:w1v0ofgLaJdoD0HpQ3fycxKD1WtxpjSo151pK/31q6w=
|
||||
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc=
|
||||
github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
@@ -268,8 +270,8 @@ github.com/spf13/afero v1.9.5 h1:stMpOSZFs//0Lv29HduCmli3GUfpFoF3Y1Q/aXj/wVM=
|
||||
github.com/spf13/afero v1.9.5/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ=
|
||||
github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad h1:fiWzISvDn0Csy5H0iwgAuJGQTUpVfEMJJd4nRFXogbc=
|
||||
github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0=
|
||||
github.com/stefanhaller/tcell/v2 v2.6.1 h1:Qo4k5SOUc09q9xyA8juH4jFJy+neM9aQwJHgVO2/zBM=
|
||||
github.com/stefanhaller/tcell/v2 v2.6.1/go.mod h1:PuJ7T6QKbsU7iVOHlhRoV3D/ipIAJsyiV4dbwcVaYj8=
|
||||
github.com/stefanhaller/tcell/v2 v2.6.2-0.20230806061358-2dfa11eddb68 h1:NSTj9xAKUu85d6pAdNFyblL84BfiOB1rVnzxQO/cYUk=
|
||||
github.com/stefanhaller/tcell/v2 v2.6.2-0.20230806061358-2dfa11eddb68/go.mod h1:PuJ7T6QKbsU7iVOHlhRoV3D/ipIAJsyiV4dbwcVaYj8=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0 h1:M2gUjqZET1qApGOWNSnZ49BAIMX4F/1plDv3+l31EJ4=
|
||||
@@ -451,13 +453,13 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA=
|
||||
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.10.0 h1:3R7pNqamzBraeqj/Tj8qt1aQ2HpmlC+Cx/qL/7hn4/c=
|
||||
golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o=
|
||||
golang.org/x/term v0.12.0 h1:/ZfYdc3zq+q02Rv9vGqTeSItdzZTSNDmfTi0mBAuidU=
|
||||
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
@@ -467,8 +469,8 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4=
|
||||
golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
|
||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
|
||||
@@ -62,6 +62,7 @@ func Run(
|
||||
|
||||
func NewCommon(config config.AppConfigurer) (*common.Common, error) {
|
||||
userConfig := config.GetUserConfig()
|
||||
appState := config.GetAppState()
|
||||
|
||||
var err error
|
||||
log := newLogger(config)
|
||||
@@ -74,6 +75,7 @@ func NewCommon(config config.AppConfigurer) (*common.Common, error) {
|
||||
Log: log,
|
||||
Tr: tr,
|
||||
UserConfig: userConfig,
|
||||
AppState: appState,
|
||||
Debug: config.GetDebug(),
|
||||
Fs: afero.NewOsFs(),
|
||||
}, nil
|
||||
|
||||
+12
-16
@@ -63,8 +63,17 @@ func Start(buildInfo *BuildInfo, integrationTest integrationTypes.IntegrationTes
|
||||
log.Fatal(absRepoPath + " is not a valid git repository.")
|
||||
}
|
||||
|
||||
cliArgs.WorkTree = absRepoPath
|
||||
cliArgs.GitDir = filepath.Join(absRepoPath, ".git")
|
||||
err = os.Chdir(absRepoPath)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to change directory to %s: %v", absRepoPath, err)
|
||||
}
|
||||
} else if cliArgs.WorkTree != "" {
|
||||
env.SetWorkTreeEnv(cliArgs.WorkTree)
|
||||
|
||||
if err := os.Chdir(cliArgs.WorkTree); err != nil {
|
||||
log.Fatalf("Failed to change directory to %s: %v", cliArgs.WorkTree, err)
|
||||
}
|
||||
}
|
||||
|
||||
if cliArgs.CustomConfigFile != "" {
|
||||
@@ -75,13 +84,6 @@ func Start(buildInfo *BuildInfo, integrationTest integrationTypes.IntegrationTes
|
||||
os.Setenv("CONFIG_DIR", cliArgs.UseConfigDir)
|
||||
}
|
||||
|
||||
if cliArgs.WorkTree != "" {
|
||||
err := os.Chdir(cliArgs.WorkTree)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to change directory to %s: %v", cliArgs.WorkTree, err)
|
||||
}
|
||||
}
|
||||
|
||||
if cliArgs.GitDir != "" {
|
||||
env.SetGitDirEnv(cliArgs.GitDir)
|
||||
}
|
||||
@@ -118,12 +120,6 @@ func Start(buildInfo *BuildInfo, integrationTest integrationTypes.IntegrationTes
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
if cliArgs.WorkTree != "" {
|
||||
if err := os.Chdir(cliArgs.WorkTree); err != nil {
|
||||
log.Fatal(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
tempDir, err := os.MkdirTemp("", "lazygit-*")
|
||||
if err != nil {
|
||||
log.Fatal(err.Error())
|
||||
@@ -184,10 +180,10 @@ func parseCliArgsAndEnvVars() *cliArgs {
|
||||
useConfigDir := ""
|
||||
flaggy.String(&useConfigDir, "ucd", "use-config-dir", "override default config directory with provided directory")
|
||||
|
||||
workTree := ""
|
||||
workTree := os.Getenv("GIT_WORK_TREE")
|
||||
flaggy.String(&workTree, "w", "work-tree", "equivalent of the --work-tree git argument")
|
||||
|
||||
gitDir := ""
|
||||
gitDir := os.Getenv("GIT_DIR")
|
||||
flaggy.String(&gitDir, "g", "git-dir", "equivalent of the --git-dir git argument")
|
||||
|
||||
customConfigFile := ""
|
||||
|
||||
@@ -1,77 +0,0 @@
|
||||
package cheatsheet
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
|
||||
"github.com/pmezard/go-difflib/difflib"
|
||||
)
|
||||
|
||||
func Check() {
|
||||
dir := GetKeybindingsDir()
|
||||
tmpDir := filepath.Join(os.TempDir(), "lazygit_cheatsheet")
|
||||
err := os.RemoveAll(tmpDir)
|
||||
if err != nil {
|
||||
log.Fatalf("Error occurred while checking if cheatsheets are up to date: %v", err)
|
||||
}
|
||||
err = os.Mkdir(tmpDir, 0o700)
|
||||
if err != nil {
|
||||
log.Fatalf("Error occurred while checking if cheatsheets are up to date: %v", err)
|
||||
}
|
||||
|
||||
generateAtDir(tmpDir)
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
actualContent := obtainContent(dir)
|
||||
expectedContent := obtainContent(tmpDir)
|
||||
|
||||
if expectedContent == "" {
|
||||
log.Fatal("empty expected content")
|
||||
}
|
||||
|
||||
if actualContent != expectedContent {
|
||||
err := difflib.WriteUnifiedDiff(os.Stdout, difflib.UnifiedDiff{
|
||||
A: difflib.SplitLines(expectedContent),
|
||||
B: difflib.SplitLines(actualContent),
|
||||
FromFile: "Expected",
|
||||
FromDate: "",
|
||||
ToFile: "Actual",
|
||||
ToDate: "",
|
||||
Context: 1,
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatalf("Error occurred while checking if cheatsheets are up to date: %v", err)
|
||||
}
|
||||
fmt.Printf("\nCheatsheets are out of date. Please run `%s` at the project root and commit the changes. If you run the script and no keybindings files are updated as a result, try rebasing onto master and trying again.\n", CommandToRun())
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
fmt.Println("\nCheatsheets are up to date")
|
||||
}
|
||||
|
||||
func obtainContent(dir string) string {
|
||||
re := regexp.MustCompile(`Keybindings_\w+\.md$`)
|
||||
|
||||
content := ""
|
||||
err := filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {
|
||||
if re.MatchString(path) {
|
||||
bytes, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
log.Fatalf("Error occurred while checking if cheatsheets are up to date: %v", err)
|
||||
}
|
||||
content += fmt.Sprintf("\n%s\n\n", filepath.Base(path))
|
||||
content += string(bytes)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatalf("Error occurred while checking if cheatsheets are up to date: %v", err)
|
||||
}
|
||||
|
||||
return content
|
||||
}
|
||||
@@ -1,10 +1,12 @@
|
||||
// This "script" generates a file called Keybindings_{{.LANG}}.md
|
||||
// in current working directory.
|
||||
//go:generate go run generator.go
|
||||
|
||||
// This "script" generates files called Keybindings_{{.LANG}}.md
|
||||
// in the docs/keybindings directory.
|
||||
//
|
||||
// The content of this generated file is a keybindings cheatsheet.
|
||||
// The content of these generated files is a keybindings cheatsheet.
|
||||
//
|
||||
// To generate cheatsheet in english run:
|
||||
// go run scripts/generate_cheatsheet.go
|
||||
// To generate the cheatsheets, run:
|
||||
// go generate pkg/cheatsheet/generate.go
|
||||
|
||||
package cheatsheet
|
||||
|
||||
@@ -42,7 +44,7 @@ type headerWithBindings struct {
|
||||
}
|
||||
|
||||
func CommandToRun() string {
|
||||
return "go run scripts/cheatsheet/main.go generate"
|
||||
return "go generate ./..."
|
||||
}
|
||||
|
||||
func GetKeybindingsDir() string {
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
//go:build ignore
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/cheatsheet"
|
||||
)
|
||||
|
||||
func main() {
|
||||
fmt.Printf("Generating cheatsheets in %s...\n", cheatsheet.GetKeybindingsDir())
|
||||
cheatsheet.Generate()
|
||||
}
|
||||
+2
-8
@@ -7,7 +7,6 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/go-errors/errors"
|
||||
"github.com/sasha-s/go-deadlock"
|
||||
"github.com/spf13/afero"
|
||||
|
||||
gogit "github.com/jesseduffield/go-git/v5"
|
||||
@@ -63,7 +62,6 @@ func NewGitCommand(
|
||||
version *git_commands.GitVersion,
|
||||
osCommand *oscommands.OSCommand,
|
||||
gitConfig git_config.IGitConfig,
|
||||
syncMutex *deadlock.Mutex,
|
||||
) (*GitCommand, error) {
|
||||
currentPath, err := os.Getwd()
|
||||
if err != nil {
|
||||
@@ -118,7 +116,6 @@ func NewGitCommand(
|
||||
gitConfig,
|
||||
repoPaths,
|
||||
repository,
|
||||
syncMutex,
|
||||
), nil
|
||||
}
|
||||
|
||||
@@ -129,7 +126,6 @@ func NewGitCommandAux(
|
||||
gitConfig git_config.IGitConfig,
|
||||
repoPaths *git_commands.RepoPaths,
|
||||
repo *gogit.Repository,
|
||||
syncMutex *deadlock.Mutex,
|
||||
) *GitCommand {
|
||||
cmd := NewGitCmdObjBuilder(cmn.Log, osCommand.Cmd)
|
||||
|
||||
@@ -140,7 +136,7 @@ func NewGitCommandAux(
|
||||
// common ones are: cmn, osCommand, dotGitDir, configCommands
|
||||
configCommands := git_commands.NewConfigCommands(cmn, gitConfig, repo)
|
||||
|
||||
gitCommon := git_commands.NewGitCommon(cmn, version, cmd, osCommand, repoPaths, repo, configCommands, syncMutex)
|
||||
gitCommon := git_commands.NewGitCommon(cmn, version, cmd, osCommand, repoPaths, repo, configCommands)
|
||||
|
||||
fileLoader := git_commands.NewFileLoader(gitCommon, cmd, configCommands)
|
||||
statusCommands := git_commands.NewStatusCommands(gitCommon)
|
||||
@@ -159,9 +155,7 @@ func NewGitCommandAux(
|
||||
stashCommands := git_commands.NewStashCommands(gitCommon, fileLoader, workingTreeCommands)
|
||||
patchBuilder := patch.NewPatchBuilder(cmn.Log,
|
||||
func(from string, to string, reverse bool, filename string, plain bool) (string, error) {
|
||||
// TODO: make patch builder take Gui.IgnoreWhitespaceInDiffView into
|
||||
// account. For now we just pass false.
|
||||
return workingTreeCommands.ShowFileDiff(from, to, reverse, filename, plain, false)
|
||||
return workingTreeCommands.ShowFileDiff(from, to, reverse, filename, plain)
|
||||
})
|
||||
patchCommands := git_commands.NewPatchCommands(gitCommon, rebaseCommands, commitCommands, statusCommands, stashCommands, patchBuilder)
|
||||
bisectCommands := git_commands.NewBisectCommands(gitCommon)
|
||||
|
||||
@@ -85,8 +85,8 @@ func (self *BranchCommands) CurrentBranchName() (string, error) {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Delete delete branch
|
||||
func (self *BranchCommands) Delete(branch string, force bool) error {
|
||||
// LocalDelete delete branch locally
|
||||
func (self *BranchCommands) LocalDelete(branch string, force bool) error {
|
||||
cmdArgs := NewGitCmd("branch").
|
||||
ArgIfElse(force, "-D", "-d").
|
||||
Arg(branch).
|
||||
|
||||
@@ -93,7 +93,7 @@ func TestBranchDeleteBranch(t *testing.T) {
|
||||
t.Run(s.testName, func(t *testing.T) {
|
||||
instance := buildBranchCommands(commonDeps{runner: s.runner})
|
||||
|
||||
s.test(instance.Delete("test", s.force))
|
||||
s.test(instance.LocalDelete("test", s.force))
|
||||
s.runner.CheckForMissingCalls()
|
||||
})
|
||||
}
|
||||
|
||||
@@ -38,6 +38,22 @@ func (self *CommitCommands) SetAuthor(value string) error {
|
||||
return self.cmd.New(cmdArgs).Run()
|
||||
}
|
||||
|
||||
// Add a commit's coauthor using Github/Gitlab Co-authored-by metadata. Value is expected to be of the form 'Name <Email>'
|
||||
func (self *CommitCommands) AddCoAuthor(sha string, value string) error {
|
||||
message, err := self.GetCommitMessage(sha)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
message = message + fmt.Sprintf("\nCo-authored-by: %s", value)
|
||||
|
||||
cmdArgs := NewGitCmd("commit").
|
||||
Arg("--allow-empty", "--amend", "--only", "-m", message).
|
||||
ToArgv()
|
||||
|
||||
return self.cmd.New(cmdArgs).Run()
|
||||
}
|
||||
|
||||
// ResetToCommit reset to commit
|
||||
func (self *CommitCommands) ResetToCommit(sha string, strength string, envVars []string) error {
|
||||
cmdArgs := NewGitCmd("reset").Arg("--"+strength, sha).ToArgv()
|
||||
@@ -68,6 +84,19 @@ func (self *CommitCommands) RewordLastCommitInEditorCmdObj() oscommands.ICmdObj
|
||||
return self.cmd.New(NewGitCmd("commit").Arg("--allow-empty", "--amend", "--only").ToArgv())
|
||||
}
|
||||
|
||||
func (self *CommitCommands) RewordLastCommitInEditorWithMessageFileCmdObj(tmpMessageFile string) oscommands.ICmdObj {
|
||||
return self.cmd.New(NewGitCmd("commit").
|
||||
Arg("--allow-empty", "--amend", "--only", "--edit", "--file="+tmpMessageFile).ToArgv())
|
||||
}
|
||||
|
||||
func (self *CommitCommands) CommitInEditorWithMessageFileCmdObj(tmpMessageFile string) oscommands.ICmdObj {
|
||||
return self.cmd.New(NewGitCmd("commit").
|
||||
Arg("--edit").
|
||||
Arg("--file="+tmpMessageFile).
|
||||
ArgIf(self.signoffFlag() != "", self.signoffFlag()).
|
||||
ToArgv())
|
||||
}
|
||||
|
||||
// RewordLastCommit rewords the topmost commit with the given message
|
||||
func (self *CommitCommands) RewordLastCommit(summary string, description string) error {
|
||||
messageArgs := self.commitMessageArgs(summary, description)
|
||||
@@ -183,10 +212,13 @@ func (self *CommitCommands) AmendHeadCmdObj() oscommands.ICmdObj {
|
||||
return self.cmd.New(cmdArgs)
|
||||
}
|
||||
|
||||
func (self *CommitCommands) ShowCmdObj(sha string, filterPath string, ignoreWhitespace bool) oscommands.ICmdObj {
|
||||
contextSize := self.UserConfig.Git.DiffContextSize
|
||||
func (self *CommitCommands) ShowCmdObj(sha string, filterPath string) oscommands.ICmdObj {
|
||||
contextSize := self.AppState.DiffContextSize
|
||||
|
||||
extDiffCmd := self.UserConfig.Git.Paging.ExternalDiffCommand
|
||||
cmdArgs := NewGitCmd("show").
|
||||
ConfigIf(extDiffCmd != "", "diff.external="+extDiffCmd).
|
||||
ArgIfElse(extDiffCmd != "", "--ext-diff", "--no-ext-diff").
|
||||
Arg("--submodule").
|
||||
Arg("--color="+self.UserConfig.Git.Paging.ColorArg).
|
||||
Arg(fmt.Sprintf("--unified=%d", contextSize)).
|
||||
@@ -194,7 +226,7 @@ func (self *CommitCommands) ShowCmdObj(sha string, filterPath string, ignoreWhit
|
||||
Arg("--decorate").
|
||||
Arg("-p").
|
||||
Arg(sha).
|
||||
ArgIf(ignoreWhitespace, "--ignore-all-space").
|
||||
ArgIf(self.AppState.IgnoreWhitespaceInDiffView, "--ignore-all-space").
|
||||
ArgIf(filterPath != "", "--", filterPath).
|
||||
ToArgv()
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -65,8 +66,11 @@ type GetCommitsOptions struct {
|
||||
FilterPath string
|
||||
IncludeRebaseCommits bool
|
||||
RefName string // e.g. "HEAD" or "my_branch"
|
||||
RefForPushedStatus string // the ref to use for determining pushed/unpushed status
|
||||
// determines if we show the whole git graph i.e. pass the '--all' flag
|
||||
All bool
|
||||
// If non-empty, show divergence from this ref (left-right log)
|
||||
RefToShowDivergenceFrom string
|
||||
}
|
||||
|
||||
// GetCommits obtains the commits of the current branch
|
||||
@@ -92,22 +96,26 @@ func (self *CommitLoader) GetCommits(opts GetCommitsOptions) ([]*models.Commit,
|
||||
defer wg.Done()
|
||||
|
||||
logErr = self.getLogCmd(opts).RunAndProcessLines(func(line string) (bool, error) {
|
||||
commit := self.extractCommitFromLine(line)
|
||||
commit := self.extractCommitFromLine(line, opts.RefToShowDivergenceFrom != "")
|
||||
commits = append(commits, commit)
|
||||
return false, nil
|
||||
})
|
||||
})
|
||||
|
||||
var ancestor string
|
||||
var remoteAncestor string
|
||||
go utils.Safe(func() {
|
||||
defer wg.Done()
|
||||
|
||||
ancestor = self.getMergeBase(opts.RefName)
|
||||
if opts.RefToShowDivergenceFrom != "" {
|
||||
remoteAncestor = self.getMergeBase(opts.RefToShowDivergenceFrom)
|
||||
}
|
||||
})
|
||||
|
||||
passedFirstPushedCommit := false
|
||||
// I can get this before
|
||||
firstPushedCommit, err := self.getFirstPushedCommit(opts.RefName)
|
||||
firstPushedCommit, err := self.getFirstPushedCommit(opts.RefForPushedStatus)
|
||||
if err != nil {
|
||||
// must have no upstream branch so we'll consider everything as pushed
|
||||
passedFirstPushedCommit = true
|
||||
@@ -123,10 +131,12 @@ func (self *CommitLoader) GetCommits(opts GetCommitsOptions) ([]*models.Commit,
|
||||
if commit.Sha == firstPushedCommit {
|
||||
passedFirstPushedCommit = true
|
||||
}
|
||||
if passedFirstPushedCommit {
|
||||
commit.Status = models.StatusPushed
|
||||
} else {
|
||||
commit.Status = models.StatusUnpushed
|
||||
if commit.Status != models.StatusRebasing {
|
||||
if passedFirstPushedCommit {
|
||||
commit.Status = models.StatusPushed
|
||||
} else {
|
||||
commit.Status = models.StatusUnpushed
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,8 +144,23 @@ func (self *CommitLoader) GetCommits(opts GetCommitsOptions) ([]*models.Commit,
|
||||
return commits, nil
|
||||
}
|
||||
|
||||
if ancestor != "" {
|
||||
commits = setCommitMergedStatuses(ancestor, commits)
|
||||
if opts.RefToShowDivergenceFrom != "" {
|
||||
sort.SliceStable(commits, func(i, j int) bool {
|
||||
// In the divergence view we want incoming commits to come first
|
||||
return commits[i].Divergence > commits[j].Divergence
|
||||
})
|
||||
|
||||
_, localSectionStart, found := lo.FindIndexOf(commits, func(commit *models.Commit) bool {
|
||||
return commit.Divergence == models.DivergenceLeft
|
||||
})
|
||||
if !found {
|
||||
localSectionStart = len(commits)
|
||||
}
|
||||
|
||||
setCommitMergedStatuses(remoteAncestor, commits[:localSectionStart])
|
||||
setCommitMergedStatuses(ancestor, commits[localSectionStart:])
|
||||
} else {
|
||||
setCommitMergedStatuses(ancestor, commits)
|
||||
}
|
||||
|
||||
return commits, nil
|
||||
@@ -176,8 +201,8 @@ func (self *CommitLoader) MergeRebasingCommits(commits []*models.Commit) ([]*mod
|
||||
// then puts them into a commit object
|
||||
// example input:
|
||||
// 8ad01fe32fcc20f07bc6693f87aa4977c327f1e1|10 hours ago|Jesse Duffield| (HEAD -> master, tag: v0.15.2)|refresh commits when adding a tag
|
||||
func (self *CommitLoader) extractCommitFromLine(line string) *models.Commit {
|
||||
split := strings.SplitN(line, "\x00", 7)
|
||||
func (self *CommitLoader) extractCommitFromLine(line string, showDivergence bool) *models.Commit {
|
||||
split := strings.SplitN(line, "\x00", 8)
|
||||
|
||||
sha := split[0]
|
||||
unixTimestamp := split[1]
|
||||
@@ -186,6 +211,10 @@ func (self *CommitLoader) extractCommitFromLine(line string) *models.Commit {
|
||||
extraInfo := strings.TrimSpace(split[4])
|
||||
parentHashes := split[5]
|
||||
message := split[6]
|
||||
divergence := models.DivergenceNone
|
||||
if showDivergence {
|
||||
divergence = lo.Ternary(split[7] == "<", models.DivergenceLeft, models.DivergenceRight)
|
||||
}
|
||||
|
||||
tags := []string{}
|
||||
|
||||
@@ -219,6 +248,7 @@ func (self *CommitLoader) extractCommitFromLine(line string) *models.Commit {
|
||||
AuthorName: authorName,
|
||||
AuthorEmail: authorEmail,
|
||||
Parents: parents,
|
||||
Divergence: divergence,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -248,7 +278,7 @@ func (self *CommitLoader) getHydratedRebasingCommits(rebaseMode enums.RebaseMode
|
||||
|
||||
fullCommits := map[string]*models.Commit{}
|
||||
err = cmdObj.RunAndProcessLines(func(line string) (bool, error) {
|
||||
commit := self.extractCommitFromLine(line)
|
||||
commit := self.extractCommitFromLine(line, false)
|
||||
fullCommits[commit.Sha] = commit
|
||||
return false, nil
|
||||
})
|
||||
@@ -492,7 +522,11 @@ func (self *CommitLoader) commitFromPatch(content string) *models.Commit {
|
||||
}
|
||||
}
|
||||
|
||||
func setCommitMergedStatuses(ancestor string, commits []*models.Commit) []*models.Commit {
|
||||
func setCommitMergedStatuses(ancestor string, commits []*models.Commit) {
|
||||
if ancestor == "" {
|
||||
return
|
||||
}
|
||||
|
||||
passedAncestor := false
|
||||
for i, commit := range commits {
|
||||
// some commits aren't really commits and don't have sha's, such as the update-ref todo
|
||||
@@ -506,7 +540,6 @@ func setCommitMergedStatuses(ancestor string, commits []*models.Commit) []*model
|
||||
commits[i].Status = models.StatusMerged
|
||||
}
|
||||
}
|
||||
return commits
|
||||
}
|
||||
|
||||
func (self *CommitLoader) getMergeBase(refName string) string {
|
||||
@@ -619,8 +652,13 @@ func (self *CommitLoader) getFirstPushedCommit(refName string) (string, error) {
|
||||
func (self *CommitLoader) getLogCmd(opts GetCommitsOptions) oscommands.ICmdObj {
|
||||
config := self.UserConfig.Git.Log
|
||||
|
||||
refSpec := opts.RefName
|
||||
if opts.RefToShowDivergenceFrom != "" {
|
||||
refSpec += "..." + opts.RefToShowDivergenceFrom
|
||||
}
|
||||
|
||||
cmdArgs := NewGitCmd("log").
|
||||
Arg(opts.RefName).
|
||||
Arg(refSpec).
|
||||
ArgIf(config.Order != "default", "--"+config.Order).
|
||||
ArgIf(opts.All, "--all").
|
||||
Arg("--oneline").
|
||||
@@ -629,6 +667,7 @@ func (self *CommitLoader) getLogCmd(opts GetCommitsOptions) oscommands.ICmdObj {
|
||||
ArgIf(opts.Limit, "-300").
|
||||
ArgIf(opts.FilterPath != "", "--follow").
|
||||
Arg("--no-show-signature").
|
||||
ArgIf(opts.RefToShowDivergenceFrom != "", "--left-right").
|
||||
Arg("--").
|
||||
ArgIf(opts.FilterPath != "", opts.FilterPath).
|
||||
ToArgv()
|
||||
@@ -636,4 +675,4 @@ func (self *CommitLoader) getLogCmd(opts GetCommitsOptions) oscommands.ICmdObj {
|
||||
return self.cmd.New(cmdArgs).DontLog()
|
||||
}
|
||||
|
||||
const prettyFormat = `--pretty=format:%H%x00%at%x00%aN%x00%ae%x00%D%x00%p%x00%s`
|
||||
const prettyFormat = `--pretty=format:%H%x00%at%x00%aN%x00%ae%x00%D%x00%p%x00%s%x00%m`
|
||||
|
||||
@@ -42,10 +42,10 @@ func TestGetCommits(t *testing.T) {
|
||||
testName: "should return no commits if there are none",
|
||||
logOrder: "topo-order",
|
||||
rebaseMode: enums.REBASE_MODE_NONE,
|
||||
opts: GetCommitsOptions{RefName: "HEAD", IncludeRebaseCommits: false},
|
||||
opts: GetCommitsOptions{RefName: "HEAD", RefForPushedStatus: "mybranch", IncludeRebaseCommits: false},
|
||||
runner: oscommands.NewFakeRunner(t).
|
||||
ExpectGitArgs([]string{"merge-base", "HEAD", "HEAD@{u}"}, "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", nil).
|
||||
ExpectGitArgs([]string{"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),
|
||||
ExpectGitArgs([]string{"merge-base", "mybranch", "mybranch@{u}"}, "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", nil).
|
||||
ExpectGitArgs([]string{"log", "HEAD", "--topo-order", "--oneline", "--pretty=format:%H%x00%at%x00%aN%x00%ae%x00%D%x00%p%x00%s%x00%m", "--abbrev=40", "--no-show-signature", "--"}, "", nil),
|
||||
|
||||
expectedCommits: []*models.Commit{},
|
||||
expectedError: nil,
|
||||
@@ -54,10 +54,10 @@ func TestGetCommits(t *testing.T) {
|
||||
testName: "should use proper upstream name for branch",
|
||||
logOrder: "topo-order",
|
||||
rebaseMode: enums.REBASE_MODE_NONE,
|
||||
opts: GetCommitsOptions{RefName: "refs/heads/mybranch", IncludeRebaseCommits: false},
|
||||
opts: GetCommitsOptions{RefName: "refs/heads/mybranch", RefForPushedStatus: "refs/heads/mybranch", IncludeRebaseCommits: false},
|
||||
runner: oscommands.NewFakeRunner(t).
|
||||
ExpectGitArgs([]string{"merge-base", "refs/heads/mybranch", "mybranch@{u}"}, "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", nil).
|
||||
ExpectGitArgs([]string{"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),
|
||||
ExpectGitArgs([]string{"log", "refs/heads/mybranch", "--topo-order", "--oneline", "--pretty=format:%H%x00%at%x00%aN%x00%ae%x00%D%x00%p%x00%s%x00%m", "--abbrev=40", "--no-show-signature", "--"}, "", nil),
|
||||
|
||||
expectedCommits: []*models.Commit{},
|
||||
expectedError: nil,
|
||||
@@ -66,13 +66,13 @@ func TestGetCommits(t *testing.T) {
|
||||
testName: "should return commits if they are present",
|
||||
logOrder: "topo-order",
|
||||
rebaseMode: enums.REBASE_MODE_NONE,
|
||||
opts: GetCommitsOptions{RefName: "HEAD", IncludeRebaseCommits: false},
|
||||
opts: GetCommitsOptions{RefName: "HEAD", RefForPushedStatus: "mybranch", IncludeRebaseCommits: false},
|
||||
mainBranches: []string{"master", "main", "develop"},
|
||||
runner: oscommands.NewFakeRunner(t).
|
||||
// here it's seeing which commits are yet to be pushed
|
||||
ExpectGitArgs([]string{"merge-base", "HEAD", "HEAD@{u}"}, "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", nil).
|
||||
ExpectGitArgs([]string{"merge-base", "mybranch", "mybranch@{u}"}, "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", nil).
|
||||
// here it's actually getting all the commits in a formatted form, one per line
|
||||
ExpectGitArgs([]string{"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).
|
||||
ExpectGitArgs([]string{"log", "HEAD", "--topo-order", "--oneline", "--pretty=format:%H%x00%at%x00%aN%x00%ae%x00%D%x00%p%x00%s%x00%m", "--abbrev=40", "--no-show-signature", "--"}, commitsOutput, nil).
|
||||
// here it's testing which of the configured main branches have an upstream
|
||||
ExpectGitArgs([]string{"rev-parse", "--symbolic-full-name", "master@{u}"}, "refs/remotes/origin/master", nil). // this one does
|
||||
ExpectGitArgs([]string{"rev-parse", "--symbolic-full-name", "main@{u}"}, "", errors.New("error")). // this one doesn't, so it checks origin instead
|
||||
@@ -203,13 +203,13 @@ func TestGetCommits(t *testing.T) {
|
||||
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},
|
||||
opts: GetCommitsOptions{RefName: "HEAD", RefForPushedStatus: "mybranch", IncludeRebaseCommits: false},
|
||||
mainBranches: []string{"master", "main"},
|
||||
runner: oscommands.NewFakeRunner(t).
|
||||
// here it's seeing which commits are yet to be pushed
|
||||
ExpectGitArgs([]string{"merge-base", "HEAD", "HEAD@{u}"}, "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", nil).
|
||||
ExpectGitArgs([]string{"merge-base", "mybranch", "mybranch@{u}"}, "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", nil).
|
||||
// here it's actually getting all the commits in a formatted form, one per line
|
||||
ExpectGitArgs([]string{"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).
|
||||
ExpectGitArgs([]string{"log", "HEAD", "--topo-order", "--oneline", "--pretty=format:%H%x00%at%x00%aN%x00%ae%x00%D%x00%p%x00%s%x00%m", "--abbrev=40", "--no-show-signature", "--"}, singleCommitOutput, nil).
|
||||
// here it's testing which of the configured main branches exist; neither does
|
||||
ExpectGitArgs([]string{"rev-parse", "--symbolic-full-name", "master@{u}"}, "", errors.New("error")).
|
||||
ExpectGitArgs([]string{"rev-parse", "--verify", "--quiet", "refs/remotes/origin/master"}, "", errors.New("error")).
|
||||
@@ -240,13 +240,13 @@ func TestGetCommits(t *testing.T) {
|
||||
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},
|
||||
opts: GetCommitsOptions{RefName: "HEAD", RefForPushedStatus: "mybranch", 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
|
||||
ExpectGitArgs([]string{"merge-base", "HEAD", "HEAD@{u}"}, "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", nil).
|
||||
ExpectGitArgs([]string{"merge-base", "mybranch", "mybranch@{u}"}, "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", nil).
|
||||
// here it's actually getting all the commits in a formatted form, one per line
|
||||
ExpectGitArgs([]string{"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).
|
||||
ExpectGitArgs([]string{"log", "HEAD", "--topo-order", "--oneline", "--pretty=format:%H%x00%at%x00%aN%x00%ae%x00%D%x00%p%x00%s%x00%m", "--abbrev=40", "--no-show-signature", "--"}, singleCommitOutput, nil).
|
||||
// here it's testing which of the configured main branches exist
|
||||
ExpectGitArgs([]string{"rev-parse", "--symbolic-full-name", "master@{u}"}, "refs/remotes/origin/master", nil).
|
||||
ExpectGitArgs([]string{"rev-parse", "--symbolic-full-name", "main@{u}"}, "", errors.New("error")).
|
||||
@@ -279,10 +279,10 @@ func TestGetCommits(t *testing.T) {
|
||||
testName: "should not specify order if `log.order` is `default`",
|
||||
logOrder: "default",
|
||||
rebaseMode: enums.REBASE_MODE_NONE,
|
||||
opts: GetCommitsOptions{RefName: "HEAD", IncludeRebaseCommits: false},
|
||||
opts: GetCommitsOptions{RefName: "HEAD", RefForPushedStatus: "mybranch", IncludeRebaseCommits: false},
|
||||
runner: oscommands.NewFakeRunner(t).
|
||||
ExpectGitArgs([]string{"merge-base", "HEAD", "HEAD@{u}"}, "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", nil).
|
||||
ExpectGitArgs([]string{"log", "HEAD", "--oneline", "--pretty=format:%H%x00%at%x00%aN%x00%ae%x00%D%x00%p%x00%s", "--abbrev=40", "--no-show-signature", "--"}, "", nil),
|
||||
ExpectGitArgs([]string{"merge-base", "mybranch", "mybranch@{u}"}, "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", nil).
|
||||
ExpectGitArgs([]string{"log", "HEAD", "--oneline", "--pretty=format:%H%x00%at%x00%aN%x00%ae%x00%D%x00%p%x00%s%x00%m", "--abbrev=40", "--no-show-signature", "--"}, "", nil),
|
||||
|
||||
expectedCommits: []*models.Commit{},
|
||||
expectedError: nil,
|
||||
@@ -291,10 +291,10 @@ func TestGetCommits(t *testing.T) {
|
||||
testName: "should set filter path",
|
||||
logOrder: "default",
|
||||
rebaseMode: enums.REBASE_MODE_NONE,
|
||||
opts: GetCommitsOptions{RefName: "HEAD", FilterPath: "src"},
|
||||
opts: GetCommitsOptions{RefName: "HEAD", RefForPushedStatus: "mybranch", FilterPath: "src"},
|
||||
runner: oscommands.NewFakeRunner(t).
|
||||
ExpectGitArgs([]string{"merge-base", "HEAD", "HEAD@{u}"}, "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", nil).
|
||||
ExpectGitArgs([]string{"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),
|
||||
ExpectGitArgs([]string{"merge-base", "mybranch", "mybranch@{u}"}, "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", nil).
|
||||
ExpectGitArgs([]string{"log", "HEAD", "--oneline", "--pretty=format:%H%x00%at%x00%aN%x00%ae%x00%D%x00%p%x00%s%x00%m", "--abbrev=40", "--follow", "--no-show-signature", "--", "src"}, "", nil),
|
||||
|
||||
expectedCommits: []*models.Commit{},
|
||||
expectedError: nil,
|
||||
@@ -548,7 +548,8 @@ func TestCommitLoader_setCommitMergedStatuses(t *testing.T) {
|
||||
|
||||
for _, scenario := range scenarios {
|
||||
t.Run(scenario.testName, func(t *testing.T) {
|
||||
expectedCommits := setCommitMergedStatuses(scenario.ancestor, scenario.commits)
|
||||
expectedCommits := scenario.commits
|
||||
setCommitMergedStatuses(scenario.ancestor, expectedCommits)
|
||||
assert.Equal(t, scenario.expectedCommits, expectedCommits)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -186,6 +186,7 @@ func TestCommitShowCmdObj(t *testing.T) {
|
||||
filterPath string
|
||||
contextSize int
|
||||
ignoreWhitespace bool
|
||||
extDiffCmd string
|
||||
expected []string
|
||||
}
|
||||
|
||||
@@ -195,28 +196,40 @@ func TestCommitShowCmdObj(t *testing.T) {
|
||||
filterPath: "",
|
||||
contextSize: 3,
|
||||
ignoreWhitespace: false,
|
||||
expected: []string{"show", "--submodule", "--color=always", "--unified=3", "--stat", "--decorate", "-p", "1234567890"},
|
||||
extDiffCmd: "",
|
||||
expected: []string{"show", "--no-ext-diff", "--submodule", "--color=always", "--unified=3", "--stat", "--decorate", "-p", "1234567890"},
|
||||
},
|
||||
{
|
||||
testName: "Default case with filter path",
|
||||
filterPath: "file.txt",
|
||||
contextSize: 3,
|
||||
ignoreWhitespace: false,
|
||||
expected: []string{"show", "--submodule", "--color=always", "--unified=3", "--stat", "--decorate", "-p", "1234567890", "--", "file.txt"},
|
||||
extDiffCmd: "",
|
||||
expected: []string{"show", "--no-ext-diff", "--submodule", "--color=always", "--unified=3", "--stat", "--decorate", "-p", "1234567890", "--", "file.txt"},
|
||||
},
|
||||
{
|
||||
testName: "Show diff with custom context size",
|
||||
filterPath: "",
|
||||
contextSize: 77,
|
||||
ignoreWhitespace: false,
|
||||
expected: []string{"show", "--submodule", "--color=always", "--unified=77", "--stat", "--decorate", "-p", "1234567890"},
|
||||
extDiffCmd: "",
|
||||
expected: []string{"show", "--no-ext-diff", "--submodule", "--color=always", "--unified=77", "--stat", "--decorate", "-p", "1234567890"},
|
||||
},
|
||||
{
|
||||
testName: "Show diff, ignoring whitespace",
|
||||
filterPath: "",
|
||||
contextSize: 77,
|
||||
ignoreWhitespace: true,
|
||||
expected: []string{"show", "--submodule", "--color=always", "--unified=77", "--stat", "--decorate", "-p", "1234567890", "--ignore-all-space"},
|
||||
extDiffCmd: "",
|
||||
expected: []string{"show", "--no-ext-diff", "--submodule", "--color=always", "--unified=77", "--stat", "--decorate", "-p", "1234567890", "--ignore-all-space"},
|
||||
},
|
||||
{
|
||||
testName: "Show diff with external diff command",
|
||||
filterPath: "",
|
||||
contextSize: 3,
|
||||
ignoreWhitespace: false,
|
||||
extDiffCmd: "difft --color=always",
|
||||
expected: []string{"-c", "diff.external=difft --color=always", "show", "--ext-diff", "--submodule", "--color=always", "--unified=3", "--stat", "--decorate", "-p", "1234567890"},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -224,12 +237,15 @@ func TestCommitShowCmdObj(t *testing.T) {
|
||||
s := s
|
||||
t.Run(s.testName, func(t *testing.T) {
|
||||
userConfig := config.GetDefaultConfig()
|
||||
userConfig.Git.DiffContextSize = s.contextSize
|
||||
userConfig.Git.Paging.ExternalDiffCommand = s.extDiffCmd
|
||||
appState := &config.AppState{}
|
||||
appState.IgnoreWhitespaceInDiffView = s.ignoreWhitespace
|
||||
appState.DiffContextSize = s.contextSize
|
||||
|
||||
runner := oscommands.NewFakeRunner(t).ExpectGitArgs(s.expected, "", nil)
|
||||
instance := buildCommitCommands(commonDeps{userConfig: userConfig, runner: runner})
|
||||
instance := buildCommitCommands(commonDeps{userConfig: userConfig, appState: appState, runner: runner})
|
||||
|
||||
assert.NoError(t, instance.ShowCmdObj("1234567890", s.filterPath, s.ignoreWhitespace).Run())
|
||||
assert.NoError(t, instance.ShowCmdObj("1234567890", s.filterPath).Run())
|
||||
runner.CheckForMissingCalls()
|
||||
})
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
gogit "github.com/jesseduffield/go-git/v5"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
|
||||
"github.com/jesseduffield/lazygit/pkg/common"
|
||||
"github.com/sasha-s/go-deadlock"
|
||||
)
|
||||
|
||||
type GitCommon struct {
|
||||
@@ -15,8 +14,6 @@ type GitCommon struct {
|
||||
repoPaths *RepoPaths
|
||||
repo *gogit.Repository
|
||||
config *ConfigCommands
|
||||
// mutex for doing things like push/pull/fetch
|
||||
syncMutex *deadlock.Mutex
|
||||
}
|
||||
|
||||
func NewGitCommon(
|
||||
@@ -27,7 +24,6 @@ func NewGitCommon(
|
||||
repoPaths *RepoPaths,
|
||||
repo *gogit.Repository,
|
||||
config *ConfigCommands,
|
||||
syncMutex *deadlock.Mutex,
|
||||
) *GitCommon {
|
||||
return &GitCommon{
|
||||
Common: cmn,
|
||||
@@ -37,6 +33,5 @@ func NewGitCommon(
|
||||
repoPaths: repoPaths,
|
||||
repo: repo,
|
||||
config: config,
|
||||
syncMutex: syncMutex,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ import (
|
||||
type commonDeps struct {
|
||||
runner *oscommands.FakeCmdObjRunner
|
||||
userConfig *config.UserConfig
|
||||
appState *config.AppState
|
||||
gitVersion *GitVersion
|
||||
gitConfig *git_config.FakeGitConfig
|
||||
getenv func(string) string
|
||||
@@ -32,7 +33,7 @@ func buildGitCommon(deps commonDeps) *GitCommon {
|
||||
|
||||
gitCommon.Common = deps.common
|
||||
if gitCommon.Common == nil {
|
||||
gitCommon.Common = utils.NewDummyCommonWithUserConfig(deps.userConfig)
|
||||
gitCommon.Common = utils.NewDummyCommonWithUserConfigAndAppState(deps.userConfig, deps.appState)
|
||||
}
|
||||
|
||||
if deps.fs != nil {
|
||||
|
||||
@@ -83,14 +83,14 @@ func (self *FileCommands) GetEditCmdStr(filename string) (string, bool) {
|
||||
}
|
||||
}
|
||||
|
||||
template, editInTerminal := config.GetEditTemplate(&self.UserConfig.OS, self.guessDefaultEditor)
|
||||
template, suspend := config.GetEditTemplate(&self.UserConfig.OS, self.guessDefaultEditor)
|
||||
|
||||
templateValues := map[string]string{
|
||||
"filename": self.cmd.Quote(filename),
|
||||
}
|
||||
|
||||
cmdStr := utils.ResolvePlaceholderString(template, templateValues)
|
||||
return cmdStr, editInTerminal
|
||||
return cmdStr, suspend
|
||||
}
|
||||
|
||||
func (self *FileCommands) GetEditAtLineCmdStr(filename string, lineNumber int) (string, bool) {
|
||||
@@ -101,7 +101,7 @@ func (self *FileCommands) GetEditAtLineCmdStr(filename string, lineNumber int) (
|
||||
}
|
||||
}
|
||||
|
||||
template, editInTerminal := config.GetEditAtLineTemplate(&self.UserConfig.OS, self.guessDefaultEditor)
|
||||
template, suspend := config.GetEditAtLineTemplate(&self.UserConfig.OS, self.guessDefaultEditor)
|
||||
|
||||
templateValues := map[string]string{
|
||||
"filename": self.cmd.Quote(filename),
|
||||
@@ -109,7 +109,7 @@ func (self *FileCommands) GetEditAtLineCmdStr(filename string, lineNumber int) (
|
||||
}
|
||||
|
||||
cmdStr := utils.ResolvePlaceholderString(template, templateValues)
|
||||
return cmdStr, editInTerminal
|
||||
return cmdStr, suspend
|
||||
}
|
||||
|
||||
func (self *FileCommands) GetEditAtLineAndWaitCmdStr(filename string, lineNumber int) string {
|
||||
@@ -131,15 +131,15 @@ func (self *FileCommands) GetEditAtLineAndWaitCmdStr(filename string, lineNumber
|
||||
return cmdStr
|
||||
}
|
||||
|
||||
func (self *FileCommands) GetOpenDirInEditorCmdStr(path string) string {
|
||||
template := config.GetOpenDirInEditorTemplate(&self.UserConfig.OS, self.guessDefaultEditor)
|
||||
func (self *FileCommands) GetOpenDirInEditorCmdStr(path string) (string, bool) {
|
||||
template, suspend := config.GetOpenDirInEditorTemplate(&self.UserConfig.OS, self.guessDefaultEditor)
|
||||
|
||||
templateValues := map[string]string{
|
||||
"dir": self.cmd.Quote(path),
|
||||
}
|
||||
|
||||
cmdStr := utils.ResolvePlaceholderString(template, templateValues)
|
||||
return cmdStr
|
||||
return cmdStr, suspend
|
||||
}
|
||||
|
||||
func (self *FileCommands) guessDefaultEditor() string {
|
||||
|
||||
@@ -179,34 +179,34 @@ func TestEditFileCmdStrLegacy(t *testing.T) {
|
||||
|
||||
func TestEditFileCmd(t *testing.T) {
|
||||
type scenario struct {
|
||||
filename string
|
||||
osConfig config.OSConfig
|
||||
expectedCmdStr string
|
||||
expectedEditInTerminal bool
|
||||
filename string
|
||||
osConfig config.OSConfig
|
||||
expectedCmdStr string
|
||||
suspend bool
|
||||
}
|
||||
|
||||
scenarios := []scenario{
|
||||
{
|
||||
filename: "test",
|
||||
osConfig: config.OSConfig{},
|
||||
expectedCmdStr: `vim -- "test"`,
|
||||
expectedEditInTerminal: true,
|
||||
filename: "test",
|
||||
osConfig: config.OSConfig{},
|
||||
expectedCmdStr: `vim -- "test"`,
|
||||
suspend: true,
|
||||
},
|
||||
{
|
||||
filename: "test",
|
||||
osConfig: config.OSConfig{
|
||||
Edit: "nano {{filename}}",
|
||||
},
|
||||
expectedCmdStr: `nano "test"`,
|
||||
expectedEditInTerminal: true,
|
||||
expectedCmdStr: `nano "test"`,
|
||||
suspend: true,
|
||||
},
|
||||
{
|
||||
filename: "file/with space",
|
||||
osConfig: config.OSConfig{
|
||||
EditPreset: "sublime",
|
||||
},
|
||||
expectedCmdStr: `subl -- "file/with space"`,
|
||||
expectedEditInTerminal: false,
|
||||
expectedCmdStr: `subl -- "file/with space"`,
|
||||
suspend: false,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -218,28 +218,28 @@ func TestEditFileCmd(t *testing.T) {
|
||||
userConfig: userConfig,
|
||||
})
|
||||
|
||||
cmdStr, editInTerminal := instance.GetEditCmdStr(s.filename)
|
||||
cmdStr, suspend := instance.GetEditCmdStr(s.filename)
|
||||
assert.Equal(t, s.expectedCmdStr, cmdStr)
|
||||
assert.Equal(t, s.expectedEditInTerminal, editInTerminal)
|
||||
assert.Equal(t, s.suspend, suspend)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEditFileAtLineCmd(t *testing.T) {
|
||||
type scenario struct {
|
||||
filename string
|
||||
lineNumber int
|
||||
osConfig config.OSConfig
|
||||
expectedCmdStr string
|
||||
expectedEditInTerminal bool
|
||||
filename string
|
||||
lineNumber int
|
||||
osConfig config.OSConfig
|
||||
expectedCmdStr string
|
||||
suspend bool
|
||||
}
|
||||
|
||||
scenarios := []scenario{
|
||||
{
|
||||
filename: "test",
|
||||
lineNumber: 42,
|
||||
osConfig: config.OSConfig{},
|
||||
expectedCmdStr: `vim +42 -- "test"`,
|
||||
expectedEditInTerminal: true,
|
||||
filename: "test",
|
||||
lineNumber: 42,
|
||||
osConfig: config.OSConfig{},
|
||||
expectedCmdStr: `vim +42 -- "test"`,
|
||||
suspend: true,
|
||||
},
|
||||
{
|
||||
filename: "test",
|
||||
@@ -247,8 +247,8 @@ func TestEditFileAtLineCmd(t *testing.T) {
|
||||
osConfig: config.OSConfig{
|
||||
EditAtLine: "nano +{{line}} {{filename}}",
|
||||
},
|
||||
expectedCmdStr: `nano +35 "test"`,
|
||||
expectedEditInTerminal: true,
|
||||
expectedCmdStr: `nano +35 "test"`,
|
||||
suspend: true,
|
||||
},
|
||||
{
|
||||
filename: "file/with space",
|
||||
@@ -256,8 +256,8 @@ func TestEditFileAtLineCmd(t *testing.T) {
|
||||
osConfig: config.OSConfig{
|
||||
EditPreset: "sublime",
|
||||
},
|
||||
expectedCmdStr: `subl -- "file/with space":12`,
|
||||
expectedEditInTerminal: false,
|
||||
expectedCmdStr: `subl -- "file/with space":12`,
|
||||
suspend: false,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -269,9 +269,9 @@ func TestEditFileAtLineCmd(t *testing.T) {
|
||||
userConfig: userConfig,
|
||||
})
|
||||
|
||||
cmdStr, editInTerminal := instance.GetEditAtLineCmdStr(s.filename, s.lineNumber)
|
||||
cmdStr, suspend := instance.GetEditAtLineCmdStr(s.filename, s.lineNumber)
|
||||
assert.Equal(t, s.expectedCmdStr, cmdStr)
|
||||
assert.Equal(t, s.expectedEditInTerminal, editInTerminal)
|
||||
assert.Equal(t, s.suspend, suspend)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -44,6 +44,14 @@ func (self *GitCommandBuilder) Config(value string) *GitCommandBuilder {
|
||||
return self
|
||||
}
|
||||
|
||||
func (self *GitCommandBuilder) ConfigIf(condition bool, ifTrue string) *GitCommandBuilder {
|
||||
if condition {
|
||||
self.Config(ifTrue)
|
||||
}
|
||||
|
||||
return self
|
||||
}
|
||||
|
||||
// the -C arg will make git do a `cd` to the directory before doing anything else
|
||||
func (self *GitCommandBuilder) Dir(path string) *GitCommandBuilder {
|
||||
// repo path comes before the command
|
||||
|
||||
@@ -79,6 +79,12 @@ func (self *RebaseCommands) SetCommitAuthor(commits []*models.Commit, index int,
|
||||
})
|
||||
}
|
||||
|
||||
func (self *RebaseCommands) AddCommitCoAuthor(commits []*models.Commit, index int, value string) error {
|
||||
return self.GenericAmend(commits, index, func() error {
|
||||
return self.commit.AddCoAuthor(commits[index].Sha, value)
|
||||
})
|
||||
}
|
||||
|
||||
func (self *RebaseCommands) GenericAmend(commits []*models.Commit, index int, f func() error) error {
|
||||
if models.IsHeadCommit(commits, index) {
|
||||
// we've selected the top commit so no rebase is required
|
||||
@@ -451,6 +457,20 @@ func (self *RebaseCommands) CherryPickCommits(commits []*models.Commit) error {
|
||||
}).Run()
|
||||
}
|
||||
|
||||
// CherryPickCommitsDuringRebase simply prepends the given commits to the existing git-rebase-todo file
|
||||
func (self *RebaseCommands) CherryPickCommitsDuringRebase(commits []*models.Commit) error {
|
||||
todoLines := lo.Map(commits, func(commit *models.Commit, _ int) daemon.TodoLine {
|
||||
return daemon.TodoLine{
|
||||
Action: "pick",
|
||||
Commit: commit,
|
||||
}
|
||||
})
|
||||
|
||||
todo := daemon.TodoLinesToString(todoLines)
|
||||
filePath := filepath.Join(self.repoPaths.worktreeGitDirPath, "rebase-merge/git-rebase-todo")
|
||||
return utils.PrependStrToTodoFile(filePath, []byte(todo))
|
||||
}
|
||||
|
||||
// we can't start an interactive rebase from the first commit without passing the
|
||||
// '--root' arg
|
||||
func getBaseShaOrRoot(commits []*models.Commit, index int) string {
|
||||
|
||||
@@ -53,7 +53,15 @@ func (self *RemoteCommands) DeleteRemoteBranch(task gocui.Task, remoteName strin
|
||||
Arg(remoteName, "--delete", branchName).
|
||||
ToArgv()
|
||||
|
||||
return self.cmd.New(cmdArgs).PromptOnCredentialRequest(task).WithMutex(self.syncMutex).Run()
|
||||
return self.cmd.New(cmdArgs).PromptOnCredentialRequest(task).Run()
|
||||
}
|
||||
|
||||
func (self *RemoteCommands) DeleteRemoteTag(task gocui.Task, remoteName string, tagName string) error {
|
||||
cmdArgs := NewGitCmd("push").
|
||||
Arg(remoteName, "--delete", tagName).
|
||||
ToArgv()
|
||||
|
||||
return self.cmd.New(cmdArgs).PromptOnCredentialRequest(task).Run()
|
||||
}
|
||||
|
||||
// CheckRemoteBranchExists Returns remote branch
|
||||
|
||||
@@ -91,10 +91,24 @@ func getRepoPathsAux(
|
||||
if err != nil {
|
||||
return nil, errors.Errorf("failed to get repo git dir path: %v", err)
|
||||
}
|
||||
worktreeGitDirPath, err := worktreeGitDirPath(fs, currentPath)
|
||||
if err != nil {
|
||||
return nil, errors.Errorf("failed to get worktree git dir path: %v", err)
|
||||
|
||||
var worktreeGitDirPath string
|
||||
if env.GetWorkTreeEnv() != "" {
|
||||
// This env is set when you pass --work-tree to lazygit. In that case,
|
||||
// we're not dealing with a linked work-tree, we're dealing with a 'specified'
|
||||
// worktree (for lack of a better term). In this case, the worktree has no
|
||||
// .git file and it just contains a bunch of files: it has no idea it's
|
||||
// pointed to by a bare repo. As such it does not have its own git dir within
|
||||
// the bare repo's git dir. Instead, we just use the bare repo's git dir.
|
||||
worktreeGitDirPath = repoGitDirPath
|
||||
} else {
|
||||
var err error
|
||||
worktreeGitDirPath, err = getWorktreeGitDirPath(fs, currentPath)
|
||||
if err != nil {
|
||||
return nil, errors.Errorf("failed to get worktree git dir path: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
repoName := path.Base(repoPath)
|
||||
|
||||
return &RepoPaths{
|
||||
@@ -110,7 +124,7 @@ func getRepoPathsAux(
|
||||
// Returns the path of the git-dir for the worktree. For linked worktrees, the worktree has
|
||||
// a .git file that points to the git-dir (which itself lives in the git-dir
|
||||
// of the repo)
|
||||
func worktreeGitDirPath(fs afero.Fs, worktreePath string) (string, error) {
|
||||
func getWorktreeGitDirPath(fs afero.Fs, worktreePath string) (string, error) {
|
||||
// if .git is a file, we're in a linked worktree, otherwise we're in
|
||||
// the main worktree
|
||||
dotGitPath := path.Join(worktreePath, ".git")
|
||||
@@ -143,6 +157,11 @@ func linkedWorktreeGitDirPath(fs afero.Fs, worktreePath string) (string, error)
|
||||
}
|
||||
|
||||
gitDir := strings.TrimPrefix(gitDirLine[0], "gitdir: ")
|
||||
|
||||
gitDir = filepath.Clean(gitDir)
|
||||
// For windows support
|
||||
gitDir = filepath.ToSlash(gitDir)
|
||||
|
||||
return gitDir, nil
|
||||
}
|
||||
|
||||
@@ -180,19 +199,32 @@ func getCurrentRepoGitDirPath(
|
||||
return "", "", errors.Errorf("could not find git dir for %s: %v", currentPath, err)
|
||||
}
|
||||
|
||||
// confirm whether the next directory up is the worktrees/submodules directory
|
||||
parent := path.Dir(worktreeGitPath)
|
||||
if path.Base(parent) != "worktrees" && path.Base(parent) != "modules" {
|
||||
return "", "", errors.Errorf("could not find git dir for %s", currentPath)
|
||||
_, err = fs.Stat(worktreeGitPath)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
// hardcoding error to get around windows-specific error message
|
||||
return "", "", errors.Errorf("could not find git dir for %s. %s does not exist", currentPath, worktreeGitPath)
|
||||
}
|
||||
return "", "", errors.Errorf("could not find git dir for %s: %v", currentPath, err)
|
||||
}
|
||||
|
||||
// if it's a submodule, we treat it as its own repo
|
||||
if path.Base(parent) == "modules" {
|
||||
// confirm whether the next directory up is the worktrees directory
|
||||
parent := path.Dir(worktreeGitPath)
|
||||
if path.Base(parent) == "worktrees" {
|
||||
gitDirPath := path.Dir(parent)
|
||||
return gitDirPath, path.Dir(gitDirPath), nil
|
||||
}
|
||||
|
||||
// Unlike worktrees, submodules can be nested arbitrarily deep, so we check
|
||||
// if the `modules` directory is anywhere up the chain.
|
||||
if strings.Contains(worktreeGitPath, "/modules/") {
|
||||
// For submodules, we just return the path directly
|
||||
return worktreeGitPath, currentPath, nil
|
||||
}
|
||||
|
||||
gitDirPath := path.Dir(parent)
|
||||
return gitDirPath, path.Dir(gitDirPath), nil
|
||||
// If this error causes issues, we could relax the constraint and just always
|
||||
// return the path
|
||||
return "", "", errors.Errorf("could not find git dir for %s: the path '%s' is not under `worktrees` or `modules` directories", currentPath, worktreeGitPath)
|
||||
}
|
||||
|
||||
// takes a path containing a symlink and returns the true path
|
||||
|
||||
@@ -55,6 +55,24 @@ func TestGetRepoPathsAux(t *testing.T) {
|
||||
},
|
||||
Err: nil,
|
||||
},
|
||||
{
|
||||
Name: "worktree with trailing separator in path",
|
||||
BeforeFunc: func(fs afero.Fs) {
|
||||
// setup for linked worktree
|
||||
_ = fs.MkdirAll("/path/to/repo/.git/worktrees/worktree1", 0o755)
|
||||
_ = afero.WriteFile(fs, "/path/to/repo/worktree1/.git", []byte("gitdir: /path/to/repo/.git/worktrees/worktree1/"), 0o644)
|
||||
},
|
||||
Path: "/path/to/repo/worktree1",
|
||||
Expected: &RepoPaths{
|
||||
currentPath: "/path/to/repo/worktree1",
|
||||
worktreePath: "/path/to/repo/worktree1",
|
||||
worktreeGitDirPath: "/path/to/repo/.git/worktrees/worktree1",
|
||||
repoPath: "/path/to/repo",
|
||||
repoGitDirPath: "/path/to/repo/.git",
|
||||
repoName: "repo",
|
||||
},
|
||||
Err: nil,
|
||||
},
|
||||
{
|
||||
Name: "worktree .git file missing gitdir directive",
|
||||
BeforeFunc: func(fs afero.Fs) {
|
||||
@@ -73,7 +91,7 @@ func TestGetRepoPathsAux(t *testing.T) {
|
||||
},
|
||||
Path: "/path/to/repo/worktree2",
|
||||
Expected: nil,
|
||||
Err: errors.New("failed to get repo git dir path: could not find git dir for /path/to/repo/worktree2"),
|
||||
Err: errors.New("failed to get repo git dir path: could not find git dir for /path/to/repo/worktree2. /nonexistant does not exist"),
|
||||
},
|
||||
{
|
||||
Name: "submodule",
|
||||
@@ -92,6 +110,33 @@ func TestGetRepoPathsAux(t *testing.T) {
|
||||
},
|
||||
Err: nil,
|
||||
},
|
||||
{
|
||||
Name: "submodule in nested directory",
|
||||
BeforeFunc: func(fs afero.Fs) {
|
||||
_ = fs.MkdirAll("/path/to/repo/.git/modules/my/submodule1", 0o755)
|
||||
_ = afero.WriteFile(fs, "/path/to/repo/my/submodule1/.git", []byte("gitdir: /path/to/repo/.git/modules/my/submodule1"), 0o644)
|
||||
},
|
||||
Path: "/path/to/repo/my/submodule1",
|
||||
Expected: &RepoPaths{
|
||||
currentPath: "/path/to/repo/my/submodule1",
|
||||
worktreePath: "/path/to/repo/my/submodule1",
|
||||
worktreeGitDirPath: "/path/to/repo/.git/modules/my/submodule1",
|
||||
repoPath: "/path/to/repo/my/submodule1",
|
||||
repoGitDirPath: "/path/to/repo/.git/modules/my/submodule1",
|
||||
repoName: "submodule1",
|
||||
},
|
||||
Err: nil,
|
||||
},
|
||||
{
|
||||
Name: "submodule git dir not under .git/modules",
|
||||
BeforeFunc: func(fs afero.Fs) {
|
||||
_ = fs.MkdirAll("/random/submodule1", 0o755)
|
||||
_ = afero.WriteFile(fs, "/path/to/repo/my/submodule1/.git", []byte("gitdir: /random/submodule1"), 0o644)
|
||||
},
|
||||
Path: "/path/to/repo/my/submodule1",
|
||||
Expected: nil,
|
||||
Err: errors.New("failed to get repo git dir path: could not find git dir for /path/to/repo/my/submodule1: the path '/random/submodule1' is not under `worktrees` or `modules` directories"),
|
||||
},
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
|
||||
@@ -80,13 +80,13 @@ 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 {
|
||||
func (self *StashCommands) ShowStashEntryCmdObj(index int) oscommands.ICmdObj {
|
||||
cmdArgs := NewGitCmd("stash").Arg("show").
|
||||
Arg("-p").
|
||||
Arg("--stat").
|
||||
Arg(fmt.Sprintf("--color=%s", self.UserConfig.Git.Paging.ColorArg)).
|
||||
Arg(fmt.Sprintf("--unified=%d", self.UserConfig.Git.DiffContextSize)).
|
||||
ArgIf(ignoreWhitespace, "--ignore-all-space").
|
||||
Arg(fmt.Sprintf("--unified=%d", self.AppState.DiffContextSize)).
|
||||
ArgIf(self.AppState.IgnoreWhitespaceInDiffView, "--ignore-all-space").
|
||||
Arg(fmt.Sprintf("stash@{%d}", index)).
|
||||
ToArgv()
|
||||
|
||||
|
||||
@@ -134,10 +134,12 @@ func TestStashStashEntryCmdObj(t *testing.T) {
|
||||
s := s
|
||||
t.Run(s.testName, func(t *testing.T) {
|
||||
userConfig := config.GetDefaultConfig()
|
||||
userConfig.Git.DiffContextSize = s.contextSize
|
||||
instance := buildStashCommands(commonDeps{userConfig: userConfig})
|
||||
appState := &config.AppState{}
|
||||
appState.IgnoreWhitespaceInDiffView = s.ignoreWhitespace
|
||||
appState.DiffContextSize = s.contextSize
|
||||
instance := buildStashCommands(commonDeps{userConfig: userConfig, appState: appState})
|
||||
|
||||
cmdStr := instance.ShowStashEntryCmdObj(s.index, s.ignoreWhitespace).Args()
|
||||
cmdStr := instance.ShowStashEntryCmdObj(s.index).Args()
|
||||
assert.Equal(t, s.expected, cmdStr)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -25,19 +25,16 @@ func NewStatusCommands(
|
||||
// RebaseMode returns "" for non-rebase mode, "normal" for normal rebase
|
||||
// and "interactive" for interactive rebase
|
||||
func (self *StatusCommands) RebaseMode() (enums.RebaseMode, error) {
|
||||
exists, err := self.os.FileExists(filepath.Join(self.repoPaths.WorktreeGitDirPath(), "rebase-apply"))
|
||||
if err != nil {
|
||||
return enums.REBASE_MODE_NONE, err
|
||||
}
|
||||
if exists {
|
||||
ok, err := self.IsInNormalRebase()
|
||||
if err == nil && ok {
|
||||
return enums.REBASE_MODE_NORMAL, nil
|
||||
}
|
||||
exists, err = self.os.FileExists(filepath.Join(self.repoPaths.WorktreeGitDirPath(), "rebase-merge"))
|
||||
if exists {
|
||||
ok, err = self.IsInInteractiveRebase()
|
||||
if err == nil && ok {
|
||||
return enums.REBASE_MODE_INTERACTIVE, err
|
||||
} else {
|
||||
return enums.REBASE_MODE_NONE, err
|
||||
}
|
||||
|
||||
return enums.REBASE_MODE_NONE, err
|
||||
}
|
||||
|
||||
func (self *StatusCommands) WorkingTreeState() enums.RebaseMode {
|
||||
@@ -68,6 +65,14 @@ func IsBareRepo(osCommand *oscommands.OSCommand) (bool, error) {
|
||||
return strconv.ParseBool(strings.TrimSpace(res))
|
||||
}
|
||||
|
||||
func (self *StatusCommands) IsInNormalRebase() (bool, error) {
|
||||
return self.os.FileExists(filepath.Join(self.repoPaths.WorktreeGitDirPath(), "rebase-apply"))
|
||||
}
|
||||
|
||||
func (self *StatusCommands) IsInInteractiveRebase() (bool, error) {
|
||||
return self.os.FileExists(filepath.Join(self.repoPaths.WorktreeGitDirPath(), "rebase-merge"))
|
||||
}
|
||||
|
||||
// IsInMergeState states whether we are still mid-merge
|
||||
func (self *StatusCommands) IsInMergeState() (bool, error) {
|
||||
return self.os.FileExists(filepath.Join(self.repoPaths.WorktreeGitDirPath(), "MERGE_HEAD"))
|
||||
|
||||
@@ -36,7 +36,7 @@ func (self *SyncCommands) PushCmdObj(task gocui.Task, opts PushOpts) (oscommands
|
||||
ArgIf(opts.UpstreamBranch != "", opts.UpstreamBranch).
|
||||
ToArgv()
|
||||
|
||||
cmdObj := self.cmd.New(cmdArgs).PromptOnCredentialRequest(task).WithMutex(self.syncMutex)
|
||||
cmdObj := self.cmd.New(cmdArgs).PromptOnCredentialRequest(task)
|
||||
return cmdObj, nil
|
||||
}
|
||||
|
||||
@@ -70,7 +70,6 @@ func (self *SyncCommands) FetchBackgroundCmdObj() oscommands.ICmdObj {
|
||||
|
||||
cmdObj := self.cmd.New(cmdArgs)
|
||||
cmdObj.DontLog().FailOnCredentialRequest()
|
||||
cmdObj.WithMutex(self.syncMutex)
|
||||
return cmdObj
|
||||
}
|
||||
|
||||
@@ -96,7 +95,7 @@ func (self *SyncCommands) Pull(task gocui.Task, opts PullOptions) error {
|
||||
|
||||
// setting GIT_SEQUENCE_EDITOR to ':' as a way of skipping it, in case the user
|
||||
// has 'pull.rebase = interactive' configured.
|
||||
return self.cmd.New(cmdArgs).AddEnvVars("GIT_SEQUENCE_EDITOR=:").PromptOnCredentialRequest(task).WithMutex(self.syncMutex).Run()
|
||||
return self.cmd.New(cmdArgs).AddEnvVars("GIT_SEQUENCE_EDITOR=:").PromptOnCredentialRequest(task).Run()
|
||||
}
|
||||
|
||||
func (self *SyncCommands) FastForward(
|
||||
@@ -110,7 +109,7 @@ func (self *SyncCommands) FastForward(
|
||||
Arg(remoteBranchName + ":" + branchName).
|
||||
ToArgv()
|
||||
|
||||
return self.cmd.New(cmdArgs).PromptOnCredentialRequest(task).WithMutex(self.syncMutex).Run()
|
||||
return self.cmd.New(cmdArgs).PromptOnCredentialRequest(task).Run()
|
||||
}
|
||||
|
||||
func (self *SyncCommands) FetchRemote(task gocui.Task, remoteName string) error {
|
||||
@@ -118,5 +117,5 @@ func (self *SyncCommands) FetchRemote(task gocui.Task, remoteName string) error
|
||||
Arg(remoteName).
|
||||
ToArgv()
|
||||
|
||||
return self.cmd.New(cmdArgs).PromptOnCredentialRequest(task).WithMutex(self.syncMutex).Run()
|
||||
return self.cmd.New(cmdArgs).PromptOnCredentialRequest(task).Run()
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ func (self *TagCommands) HasTag(tagName string) bool {
|
||||
return self.cmd.New(cmdArgs).Run() == nil
|
||||
}
|
||||
|
||||
func (self *TagCommands) Delete(tagName string) error {
|
||||
func (self *TagCommands) LocalDelete(tagName string) error {
|
||||
cmdArgs := NewGitCmd("tag").Arg("-d", tagName).
|
||||
ToArgv()
|
||||
|
||||
@@ -52,5 +52,5 @@ func (self *TagCommands) Push(task gocui.Task, remoteName string, tagName string
|
||||
cmdArgs := NewGitCmd("push").Arg(remoteName, "tag", tagName).
|
||||
ToArgv()
|
||||
|
||||
return self.cmd.New(cmdArgs).PromptOnCredentialRequest(task).WithMutex(self.syncMutex).Run()
|
||||
return self.cmd.New(cmdArgs).PromptOnCredentialRequest(task).Run()
|
||||
}
|
||||
|
||||
@@ -228,28 +228,31 @@ func (self *WorkingTreeCommands) Exclude(filename string) error {
|
||||
}
|
||||
|
||||
// WorktreeFileDiff returns the diff of a file
|
||||
func (self *WorkingTreeCommands) WorktreeFileDiff(file *models.File, plain bool, cached bool, ignoreWhitespace bool) string {
|
||||
func (self *WorkingTreeCommands) WorktreeFileDiff(file *models.File, plain bool, cached bool) string {
|
||||
// for now we assume an error means the file was deleted
|
||||
s, _ := self.WorktreeFileDiffCmdObj(file, plain, cached, ignoreWhitespace).RunWithOutput()
|
||||
s, _ := self.WorktreeFileDiffCmdObj(file, plain, cached).RunWithOutput()
|
||||
return s
|
||||
}
|
||||
|
||||
func (self *WorkingTreeCommands) WorktreeFileDiffCmdObj(node models.IFile, plain bool, cached bool, ignoreWhitespace bool) oscommands.ICmdObj {
|
||||
func (self *WorkingTreeCommands) WorktreeFileDiffCmdObj(node models.IFile, plain bool, cached bool) oscommands.ICmdObj {
|
||||
colorArg := self.UserConfig.Git.Paging.ColorArg
|
||||
if plain {
|
||||
colorArg = "never"
|
||||
}
|
||||
|
||||
contextSize := self.UserConfig.Git.DiffContextSize
|
||||
contextSize := self.AppState.DiffContextSize
|
||||
prevPath := node.GetPreviousPath()
|
||||
noIndex := !node.GetIsTracked() && !node.GetHasStagedChanges() && !cached && node.GetIsFile()
|
||||
extDiffCmd := self.UserConfig.Git.Paging.ExternalDiffCommand
|
||||
useExtDiff := extDiffCmd != "" && !plain
|
||||
|
||||
cmdArgs := NewGitCmd("diff").
|
||||
ConfigIf(useExtDiff, "diff.external="+extDiffCmd).
|
||||
ArgIfElse(useExtDiff, "--ext-diff", "--no-ext-diff").
|
||||
Arg("--submodule").
|
||||
Arg("--no-ext-diff").
|
||||
Arg(fmt.Sprintf("--unified=%d", contextSize)).
|
||||
Arg(fmt.Sprintf("--color=%s", colorArg)).
|
||||
ArgIf(ignoreWhitespace, "--ignore-all-space").
|
||||
ArgIf(!plain && self.AppState.IgnoreWhitespaceInDiffView, "--ignore-all-space").
|
||||
ArgIf(cached, "--cached").
|
||||
ArgIf(noIndex, "--no-index").
|
||||
Arg("--").
|
||||
@@ -263,32 +266,32 @@ func (self *WorkingTreeCommands) WorktreeFileDiffCmdObj(node models.IFile, plain
|
||||
|
||||
// ShowFileDiff get the diff of specified from and to. Typically this will be used for a single commit so it'll be 123abc^..123abc
|
||||
// but when we're in diff mode it could be any 'from' to any 'to'. The reverse flag is also here thanks to diff mode.
|
||||
func (self *WorkingTreeCommands) ShowFileDiff(from string, to string, reverse bool, fileName string, plain bool,
|
||||
ignoreWhitespace bool,
|
||||
) (string, error) {
|
||||
return self.ShowFileDiffCmdObj(from, to, reverse, fileName, plain, ignoreWhitespace).RunWithOutput()
|
||||
func (self *WorkingTreeCommands) ShowFileDiff(from string, to string, reverse bool, fileName string, plain bool) (string, error) {
|
||||
return self.ShowFileDiffCmdObj(from, to, reverse, fileName, plain).RunWithOutput()
|
||||
}
|
||||
|
||||
func (self *WorkingTreeCommands) ShowFileDiffCmdObj(from string, to string, reverse bool, fileName string, plain bool,
|
||||
ignoreWhitespace bool,
|
||||
) oscommands.ICmdObj {
|
||||
contextSize := self.UserConfig.Git.DiffContextSize
|
||||
func (self *WorkingTreeCommands) ShowFileDiffCmdObj(from string, to string, reverse bool, fileName string, plain bool) oscommands.ICmdObj {
|
||||
contextSize := self.AppState.DiffContextSize
|
||||
|
||||
colorArg := self.UserConfig.Git.Paging.ColorArg
|
||||
if plain {
|
||||
colorArg = "never"
|
||||
}
|
||||
|
||||
extDiffCmd := self.UserConfig.Git.Paging.ExternalDiffCommand
|
||||
useExtDiff := extDiffCmd != "" && !plain
|
||||
|
||||
cmdArgs := NewGitCmd("diff").
|
||||
ConfigIf(useExtDiff, "diff.external="+extDiffCmd).
|
||||
ArgIfElse(useExtDiff, "--ext-diff", "--no-ext-diff").
|
||||
Arg("--submodule").
|
||||
Arg("--no-ext-diff").
|
||||
Arg(fmt.Sprintf("--unified=%d", contextSize)).
|
||||
Arg("--no-renames").
|
||||
Arg(fmt.Sprintf("--color=%s", colorArg)).
|
||||
Arg(from).
|
||||
Arg(to).
|
||||
ArgIf(reverse, "-R").
|
||||
ArgIf(ignoreWhitespace, "--ignore-all-space").
|
||||
ArgIf(!plain && self.AppState.IgnoreWhitespaceInDiffView, "--ignore-all-space").
|
||||
Arg("--").
|
||||
Arg(fileName).
|
||||
ToArgv()
|
||||
|
||||
@@ -231,7 +231,7 @@ func TestWorkingTreeDiff(t *testing.T) {
|
||||
ignoreWhitespace: false,
|
||||
contextSize: 3,
|
||||
runner: oscommands.NewFakeRunner(t).
|
||||
ExpectGitArgs([]string{"diff", "--submodule", "--no-ext-diff", "--unified=3", "--color=always", "--", "test.txt"}, expectedResult, nil),
|
||||
ExpectGitArgs([]string{"diff", "--no-ext-diff", "--submodule", "--unified=3", "--color=always", "--", "test.txt"}, expectedResult, nil),
|
||||
},
|
||||
{
|
||||
testName: "cached",
|
||||
@@ -245,7 +245,7 @@ func TestWorkingTreeDiff(t *testing.T) {
|
||||
ignoreWhitespace: false,
|
||||
contextSize: 3,
|
||||
runner: oscommands.NewFakeRunner(t).
|
||||
ExpectGitArgs([]string{"diff", "--submodule", "--no-ext-diff", "--unified=3", "--color=always", "--cached", "--", "test.txt"}, expectedResult, nil),
|
||||
ExpectGitArgs([]string{"diff", "--no-ext-diff", "--submodule", "--unified=3", "--color=always", "--cached", "--", "test.txt"}, expectedResult, nil),
|
||||
},
|
||||
{
|
||||
testName: "plain",
|
||||
@@ -259,7 +259,7 @@ func TestWorkingTreeDiff(t *testing.T) {
|
||||
ignoreWhitespace: false,
|
||||
contextSize: 3,
|
||||
runner: oscommands.NewFakeRunner(t).
|
||||
ExpectGitArgs([]string{"diff", "--submodule", "--no-ext-diff", "--unified=3", "--color=never", "--", "test.txt"}, expectedResult, nil),
|
||||
ExpectGitArgs([]string{"diff", "--no-ext-diff", "--submodule", "--unified=3", "--color=never", "--", "test.txt"}, expectedResult, nil),
|
||||
},
|
||||
{
|
||||
testName: "File not tracked and file has no staged changes",
|
||||
@@ -273,7 +273,7 @@ func TestWorkingTreeDiff(t *testing.T) {
|
||||
ignoreWhitespace: false,
|
||||
contextSize: 3,
|
||||
runner: oscommands.NewFakeRunner(t).
|
||||
ExpectGitArgs([]string{"diff", "--submodule", "--no-ext-diff", "--unified=3", "--color=always", "--no-index", "--", "/dev/null", "test.txt"}, expectedResult, nil),
|
||||
ExpectGitArgs([]string{"diff", "--no-ext-diff", "--submodule", "--unified=3", "--color=always", "--no-index", "--", "/dev/null", "test.txt"}, expectedResult, nil),
|
||||
},
|
||||
{
|
||||
testName: "Default case (ignore whitespace)",
|
||||
@@ -287,7 +287,7 @@ func TestWorkingTreeDiff(t *testing.T) {
|
||||
ignoreWhitespace: true,
|
||||
contextSize: 3,
|
||||
runner: oscommands.NewFakeRunner(t).
|
||||
ExpectGitArgs([]string{"diff", "--submodule", "--no-ext-diff", "--unified=3", "--color=always", "--ignore-all-space", "--", "test.txt"}, expectedResult, nil),
|
||||
ExpectGitArgs([]string{"diff", "--no-ext-diff", "--submodule", "--unified=3", "--color=always", "--ignore-all-space", "--", "test.txt"}, expectedResult, nil),
|
||||
},
|
||||
{
|
||||
testName: "Show diff with custom context size",
|
||||
@@ -301,7 +301,7 @@ func TestWorkingTreeDiff(t *testing.T) {
|
||||
ignoreWhitespace: false,
|
||||
contextSize: 17,
|
||||
runner: oscommands.NewFakeRunner(t).
|
||||
ExpectGitArgs([]string{"diff", "--submodule", "--no-ext-diff", "--unified=17", "--color=always", "--", "test.txt"}, expectedResult, nil),
|
||||
ExpectGitArgs([]string{"diff", "--no-ext-diff", "--submodule", "--unified=17", "--color=always", "--", "test.txt"}, expectedResult, nil),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -309,10 +309,12 @@ func TestWorkingTreeDiff(t *testing.T) {
|
||||
s := s
|
||||
t.Run(s.testName, func(t *testing.T) {
|
||||
userConfig := config.GetDefaultConfig()
|
||||
userConfig.Git.DiffContextSize = s.contextSize
|
||||
appState := &config.AppState{}
|
||||
appState.IgnoreWhitespaceInDiffView = s.ignoreWhitespace
|
||||
appState.DiffContextSize = s.contextSize
|
||||
|
||||
instance := buildWorkingTreeCommands(commonDeps{runner: s.runner, userConfig: userConfig})
|
||||
result := instance.WorktreeFileDiff(s.file, s.plain, s.cached, s.ignoreWhitespace)
|
||||
instance := buildWorkingTreeCommands(commonDeps{runner: s.runner, userConfig: userConfig, appState: appState})
|
||||
result := instance.WorktreeFileDiff(s.file, s.plain, s.cached)
|
||||
assert.Equal(t, expectedResult, result)
|
||||
s.runner.CheckForMissingCalls()
|
||||
})
|
||||
@@ -343,7 +345,7 @@ func TestWorkingTreeShowFileDiff(t *testing.T) {
|
||||
ignoreWhitespace: false,
|
||||
contextSize: 3,
|
||||
runner: oscommands.NewFakeRunner(t).
|
||||
ExpectGitArgs([]string{"diff", "--submodule", "--no-ext-diff", "--unified=3", "--no-renames", "--color=always", "1234567890", "0987654321", "--", "test.txt"}, expectedResult, nil),
|
||||
ExpectGitArgs([]string{"diff", "--no-ext-diff", "--submodule", "--unified=3", "--no-renames", "--color=always", "1234567890", "0987654321", "--", "test.txt"}, expectedResult, nil),
|
||||
},
|
||||
{
|
||||
testName: "Show diff with custom context size",
|
||||
@@ -354,7 +356,7 @@ func TestWorkingTreeShowFileDiff(t *testing.T) {
|
||||
ignoreWhitespace: false,
|
||||
contextSize: 123,
|
||||
runner: oscommands.NewFakeRunner(t).
|
||||
ExpectGitArgs([]string{"diff", "--submodule", "--no-ext-diff", "--unified=123", "--no-renames", "--color=always", "1234567890", "0987654321", "--", "test.txt"}, expectedResult, nil),
|
||||
ExpectGitArgs([]string{"diff", "--no-ext-diff", "--submodule", "--unified=123", "--no-renames", "--color=always", "1234567890", "0987654321", "--", "test.txt"}, expectedResult, nil),
|
||||
},
|
||||
{
|
||||
testName: "Default case (ignore whitespace)",
|
||||
@@ -365,7 +367,7 @@ func TestWorkingTreeShowFileDiff(t *testing.T) {
|
||||
ignoreWhitespace: true,
|
||||
contextSize: 3,
|
||||
runner: oscommands.NewFakeRunner(t).
|
||||
ExpectGitArgs([]string{"diff", "--submodule", "--no-ext-diff", "--unified=3", "--no-renames", "--color=always", "1234567890", "0987654321", "--ignore-all-space", "--", "test.txt"}, expectedResult, nil),
|
||||
ExpectGitArgs([]string{"diff", "--no-ext-diff", "--submodule", "--unified=3", "--no-renames", "--color=always", "1234567890", "0987654321", "--ignore-all-space", "--", "test.txt"}, expectedResult, nil),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -373,11 +375,13 @@ func TestWorkingTreeShowFileDiff(t *testing.T) {
|
||||
s := s
|
||||
t.Run(s.testName, func(t *testing.T) {
|
||||
userConfig := config.GetDefaultConfig()
|
||||
userConfig.Git.DiffContextSize = s.contextSize
|
||||
appState := &config.AppState{}
|
||||
appState.IgnoreWhitespaceInDiffView = s.ignoreWhitespace
|
||||
appState.DiffContextSize = s.contextSize
|
||||
|
||||
instance := buildWorkingTreeCommands(commonDeps{runner: s.runner, userConfig: userConfig})
|
||||
instance := buildWorkingTreeCommands(commonDeps{runner: s.runner, userConfig: userConfig, appState: appState})
|
||||
|
||||
result, err := instance.ShowFileDiff(s.from, s.to, s.reverse, "test.txt", s.plain, s.ignoreWhitespace)
|
||||
result, err := instance.ShowFileDiff(s.from, s.to, s.reverse, "test.txt", s.plain)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, expectedResult, result)
|
||||
s.runner.CheckForMissingCalls()
|
||||
|
||||
@@ -58,7 +58,7 @@ func (self *WorktreeLoader) GetWorktrees() ([]*models.Worktree, error) {
|
||||
isPathMissing := self.pathExists(path)
|
||||
|
||||
var gitDir string
|
||||
gitDir, err := worktreeGitDirPath(self.Fs, path)
|
||||
gitDir, err := getWorktreeGitDirPath(self.Fs, path)
|
||||
if err != nil {
|
||||
self.Log.Warnf("Could not find git dir for worktree %s: %v", path, err)
|
||||
}
|
||||
|
||||
@@ -186,7 +186,7 @@ branch refs/heads/mybranch-worktree
|
||||
assert.EqualError(t, errors.New(s.expectedErr), err.Error())
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, worktrees, s.expectedWorktrees)
|
||||
assert.EqualValues(t, s.expectedWorktrees, worktrees)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -99,33 +99,30 @@ func (self *HostingServiceMgr) getCandidateServiceDomains() []ServiceDomain {
|
||||
|
||||
serviceDomains := slices.Clone(defaultServiceDomains)
|
||||
|
||||
if len(self.configServiceDomains) > 0 {
|
||||
for gitDomain, typeAndDomain := range self.configServiceDomains {
|
||||
splitData := strings.Split(typeAndDomain, ":")
|
||||
if len(splitData) != 2 {
|
||||
self.log.Errorf("Unexpected format for git service: '%s'. Expected something like 'github.com:github.com'", typeAndDomain)
|
||||
continue
|
||||
}
|
||||
for gitDomain, typeAndDomain := range self.configServiceDomains {
|
||||
provider, webDomain, success := strings.Cut(typeAndDomain, ":")
|
||||
|
||||
provider := splitData[0]
|
||||
webDomain := splitData[1]
|
||||
|
||||
serviceDefinition, ok := serviceDefinitionByProvider[provider]
|
||||
if !ok {
|
||||
providerNames := lo.Map(serviceDefinitions, func(serviceDefinition ServiceDefinition, _ int) string {
|
||||
return serviceDefinition.provider
|
||||
})
|
||||
|
||||
self.log.Errorf("Unknown git service type: '%s'. Expected one of %s", provider, strings.Join(providerNames, ", "))
|
||||
continue
|
||||
}
|
||||
|
||||
serviceDomains = append(serviceDomains, ServiceDomain{
|
||||
gitDomain: gitDomain,
|
||||
webDomain: webDomain,
|
||||
serviceDefinition: serviceDefinition,
|
||||
})
|
||||
// we allow for one ':' for specifying the TCP port
|
||||
if !success || strings.Count(webDomain, ":") > 1 {
|
||||
self.log.Errorf("Unexpected format for git service: '%s'. Expected something like 'github.com:github.com'", typeAndDomain)
|
||||
continue
|
||||
}
|
||||
|
||||
serviceDefinition, ok := serviceDefinitionByProvider[provider]
|
||||
if !ok {
|
||||
providerNames := lo.Map(serviceDefinitions, func(serviceDefinition ServiceDefinition, _ int) string {
|
||||
return serviceDefinition.provider
|
||||
})
|
||||
|
||||
self.log.Errorf("Unknown git service type: '%s'. Expected one of %s", provider, strings.Join(providerNames, ", "))
|
||||
continue
|
||||
}
|
||||
|
||||
serviceDomains = append(serviceDomains, ServiceDomain{
|
||||
gitDomain: gitDomain,
|
||||
webDomain: webDomain,
|
||||
serviceDefinition: serviceDefinition,
|
||||
})
|
||||
}
|
||||
|
||||
return serviceDomains
|
||||
|
||||
@@ -340,6 +340,30 @@ func TestGetPullRequestURL(t *testing.T) {
|
||||
},
|
||||
expectedLoggedErrors: nil,
|
||||
},
|
||||
{
|
||||
testName: "Does not log error when config service webDomain contains a port",
|
||||
from: "feature/profile-page",
|
||||
remoteUrl: "git@my.domain.test:johndoe/social_network.git",
|
||||
configServiceDomains: map[string]string{
|
||||
"my.domain.test": "gitlab:my.domain.test:1111",
|
||||
},
|
||||
test: func(url string, err error) {
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "https://my.domain.test:1111/johndoe/social_network/-/merge_requests/new?merge_request[source_branch]=feature%2Fprofile-page", url)
|
||||
},
|
||||
},
|
||||
{
|
||||
testName: "Logs error when webDomain contains more than one colon",
|
||||
from: "feature/profile-page",
|
||||
remoteUrl: "git@my.domain.test:johndoe/social_network.git",
|
||||
configServiceDomains: map[string]string{
|
||||
"my.domain.test": "gitlab:my.domain.test:1111:2222",
|
||||
},
|
||||
test: func(url string, err error) {
|
||||
assert.Error(t, err)
|
||||
},
|
||||
expectedLoggedErrors: []string{"Unexpected format for git service: 'gitlab:my.domain.test:1111:2222'. Expected something like 'github.com:github.com'"},
|
||||
},
|
||||
{
|
||||
testName: "Logs error when config service domain is malformed",
|
||||
from: "feature/profile-page",
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package models
|
||||
|
||||
import "fmt"
|
||||
|
||||
// Branch : A git branch
|
||||
// duplicating this for now
|
||||
type Branch struct {
|
||||
@@ -43,10 +45,30 @@ func (b *Branch) ParentRefName() string {
|
||||
return b.RefName() + "^"
|
||||
}
|
||||
|
||||
func (b *Branch) FullUpstreamRefName() string {
|
||||
if b.UpstreamRemote == "" || b.UpstreamBranch == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
return fmt.Sprintf("refs/remotes/%s/%s", b.UpstreamRemote, b.UpstreamBranch)
|
||||
}
|
||||
|
||||
func (b *Branch) ShortUpstreamRefName() string {
|
||||
if b.UpstreamRemote == "" || b.UpstreamBranch == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s/%s", b.UpstreamRemote, b.UpstreamBranch)
|
||||
}
|
||||
|
||||
func (b *Branch) ID() string {
|
||||
return b.RefName()
|
||||
}
|
||||
|
||||
func (b *Branch) URN() string {
|
||||
return "branch-" + b.ID()
|
||||
}
|
||||
|
||||
func (b *Branch) Description() string {
|
||||
return b.RefName()
|
||||
}
|
||||
|
||||
@@ -30,6 +30,17 @@ const (
|
||||
ActionConflict = todo.Comment + 1
|
||||
)
|
||||
|
||||
type Divergence int
|
||||
|
||||
// For a divergence log (left/right comparison of two refs) this is set to
|
||||
// either DivergenceLeft or DivergenceRight for each commit; for normal
|
||||
// commit views it is always DivergenceNone.
|
||||
const (
|
||||
DivergenceNone Divergence = iota
|
||||
DivergenceLeft
|
||||
DivergenceRight
|
||||
)
|
||||
|
||||
// Commit : A git commit
|
||||
type Commit struct {
|
||||
Sha string
|
||||
@@ -41,6 +52,7 @@ type Commit struct {
|
||||
AuthorName string // something like 'Jesse Duffield'
|
||||
AuthorEmail string // something like 'jessedduffield@gmail.com'
|
||||
UnixTimestamp int64
|
||||
Divergence Divergence // set to DivergenceNone unless we are showing the divergence view
|
||||
|
||||
// SHAs of parent commits (will be multiple if it's a merge commit)
|
||||
Parents []string
|
||||
|
||||
@@ -24,6 +24,10 @@ func (t *Tag) ID() string {
|
||||
return t.RefName()
|
||||
}
|
||||
|
||||
func (t *Tag) URN() string {
|
||||
return "tag-" + t.ID()
|
||||
}
|
||||
|
||||
func (t *Tag) Description() string {
|
||||
return t.Message
|
||||
}
|
||||
|
||||
@@ -97,12 +97,12 @@ func (self *Patch) LineNumberOfLine(idx int) int {
|
||||
idxInHunk := idx - hunkStartIdx
|
||||
|
||||
if idxInHunk == 0 {
|
||||
return hunk.oldStart
|
||||
return hunk.newStart
|
||||
}
|
||||
|
||||
lines := hunk.bodyLines[:idxInHunk-1]
|
||||
offset := nLinesWithKind(lines, []PatchLineKind{ADDITION, CONTEXT})
|
||||
return hunk.oldStart + offset
|
||||
return hunk.newStart + offset
|
||||
}
|
||||
|
||||
// Returns hunk index containing the line at the given patch line index
|
||||
@@ -149,3 +149,8 @@ func (self *Patch) LineCount() int {
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
// Returns the number of hunks of the patch
|
||||
func (self *Patch) HunkCount() int {
|
||||
return len(self.hunks)
|
||||
}
|
||||
|
||||
@@ -67,6 +67,29 @@ index e48a11c..b2ab81b 100644
|
||||
...
|
||||
`
|
||||
|
||||
const twoHunksWithMoreAdditionsThanRemovals = `diff --git a/filename b/filename
|
||||
index bac359d75..6e5b89f36 100644
|
||||
--- a/filename
|
||||
+++ b/filename
|
||||
@@ -1,5 +1,6 @@
|
||||
apple
|
||||
-grape
|
||||
+orange
|
||||
+kiwi
|
||||
...
|
||||
...
|
||||
...
|
||||
@@ -8,6 +9,8 @@ grape
|
||||
...
|
||||
...
|
||||
...
|
||||
+pear
|
||||
+lemon
|
||||
...
|
||||
...
|
||||
...
|
||||
`
|
||||
|
||||
const twoChangesInOneHunk = `diff --git a/filename b/filename
|
||||
index 9320895..6d79956 100644
|
||||
--- a/filename
|
||||
@@ -572,6 +595,12 @@ func TestLineNumberOfLine(t *testing.T) {
|
||||
indexes: []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 1000},
|
||||
expecteds: []int{1, 1, 1, 1, 1, 1, 2, 2, 3, 4, 5, 8, 8, 9, 10, 11, 12, 13, 14, 15, 15, 15, 15, 15, 15},
|
||||
},
|
||||
{
|
||||
testName: "twoHunksWithMoreAdditionsThanRemovals",
|
||||
patchStr: twoHunksWithMoreAdditionsThanRemovals,
|
||||
indexes: []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 1000},
|
||||
expecteds: []int{1, 1, 1, 1, 1, 1, 2, 2, 3, 4, 5, 6, 9, 9, 10, 11, 12, 13, 14, 15, 16, 16, 16, 16, 16, 16},
|
||||
},
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
|
||||
@@ -12,6 +12,7 @@ type Common struct {
|
||||
Log *logrus.Entry
|
||||
Tr *i18n.TranslationSet
|
||||
UserConfig *config.UserConfig
|
||||
AppState *config.AppState
|
||||
Debug bool
|
||||
// for interacting with the filesystem. We use afero rather than the default
|
||||
// `os` package for the sake of mocking the filesystem in tests
|
||||
|
||||
@@ -283,11 +283,13 @@ func (c *AppConfig) SaveAppState() error {
|
||||
|
||||
// loadAppState loads recorded AppState from file
|
||||
func loadAppState() (*AppState, error) {
|
||||
appState := getDefaultAppState()
|
||||
|
||||
filepath, err := configFilePath("state.yml")
|
||||
if err != nil {
|
||||
if os.IsPermission(err) {
|
||||
// apparently when people have read-only permissions they prefer us to fail silently
|
||||
return getDefaultAppState(), nil
|
||||
return appState, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
@@ -298,10 +300,9 @@ func loadAppState() (*AppState, error) {
|
||||
}
|
||||
|
||||
if len(appStateBytes) == 0 {
|
||||
return getDefaultAppState(), nil
|
||||
return appState, nil
|
||||
}
|
||||
|
||||
appState := &AppState{}
|
||||
err = yaml.Unmarshal(appStateBytes, appState)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -321,6 +322,7 @@ type AppState struct {
|
||||
CustomCommandsHistory []string
|
||||
HideCommandLog bool
|
||||
IgnoreWhitespaceInDiffView bool
|
||||
DiffContextSize int
|
||||
}
|
||||
|
||||
func getDefaultAppState() *AppState {
|
||||
@@ -328,6 +330,7 @@ func getDefaultAppState() *AppState {
|
||||
LastUpdateCheck: 0,
|
||||
RecentRepos: []string{},
|
||||
StartupPopupVersion: 0,
|
||||
DiffContextSize: 3,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -28,13 +28,13 @@ func GetEditAtLineAndWaitTemplate(osConfig *OSConfig, guessDefaultEditor func()
|
||||
return template
|
||||
}
|
||||
|
||||
func GetOpenDirInEditorTemplate(osConfig *OSConfig, guessDefaultEditor func() string) string {
|
||||
func GetOpenDirInEditorTemplate(osConfig *OSConfig, guessDefaultEditor func() string) (string, bool) {
|
||||
preset := getPreset(osConfig, guessDefaultEditor)
|
||||
template := osConfig.OpenDirInEditor
|
||||
if template == "" {
|
||||
template = preset.openDirInEditorTemplate
|
||||
}
|
||||
return template
|
||||
return template, getEditInTerminal(osConfig, preset)
|
||||
}
|
||||
|
||||
type editPreset struct {
|
||||
@@ -42,16 +42,26 @@ type editPreset struct {
|
||||
editAtLineTemplate string
|
||||
editAtLineAndWaitTemplate string
|
||||
openDirInEditorTemplate string
|
||||
editInTerminal bool
|
||||
suspend 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"),
|
||||
"vi": standardTerminalEditorPreset("vi"),
|
||||
"vim": standardTerminalEditorPreset("vim"),
|
||||
"nvim": standardTerminalEditorPreset("nvim"),
|
||||
"nvim-remote": {
|
||||
editTemplate: `nvim --server "$NVIM" --remote-tab {{filename}}`,
|
||||
editAtLineTemplate: `nvim --server "$NVIM" --remote-tab {{filename}}; [ -z "$NVIM" ] || nvim --server "$NVIM" --remote-send ":{{line}}<CR>"`,
|
||||
// No remote-wait support yet. See https://github.com/neovim/neovim/pull/17856
|
||||
editAtLineAndWaitTemplate: `nvim +{{line}} {{filename}}`,
|
||||
openDirInEditorTemplate: `nvim --server "$NVIM" --remote-tab {{dir}}`,
|
||||
suspend: false,
|
||||
},
|
||||
"lvim": standardTerminalEditorPreset("lvim"),
|
||||
"emacs": standardTerminalEditorPreset("emacs"),
|
||||
"micro": standardTerminalEditorPreset("micro"),
|
||||
"nano": standardTerminalEditorPreset("nano"),
|
||||
"kakoune": standardTerminalEditorPreset("kak"),
|
||||
"helix": {
|
||||
@@ -59,35 +69,35 @@ func getPreset(osConfig *OSConfig, guessDefaultEditor func() string) *editPreset
|
||||
editAtLineTemplate: "hx -- {{filename}}:{{line}}",
|
||||
editAtLineAndWaitTemplate: "hx -- {{filename}}:{{line}}",
|
||||
openDirInEditorTemplate: "hx -- {{dir}}",
|
||||
editInTerminal: true,
|
||||
suspend: true,
|
||||
},
|
||||
"vscode": {
|
||||
editTemplate: "code --reuse-window -- {{filename}}",
|
||||
editAtLineTemplate: "code --reuse-window --goto -- {{filename}}:{{line}}",
|
||||
editAtLineAndWaitTemplate: "code --reuse-window --goto --wait -- {{filename}}:{{line}}",
|
||||
openDirInEditorTemplate: "code -- {{dir}}",
|
||||
editInTerminal: false,
|
||||
suspend: false,
|
||||
},
|
||||
"sublime": {
|
||||
editTemplate: "subl -- {{filename}}",
|
||||
editAtLineTemplate: "subl -- {{filename}}:{{line}}",
|
||||
editAtLineAndWaitTemplate: "subl --wait -- {{filename}}:{{line}}",
|
||||
openDirInEditorTemplate: "subl -- {{dir}}",
|
||||
editInTerminal: false,
|
||||
suspend: false,
|
||||
},
|
||||
"bbedit": {
|
||||
editTemplate: "bbedit -- {{filename}}",
|
||||
editAtLineTemplate: "bbedit +{{line}} -- {{filename}}",
|
||||
editAtLineAndWaitTemplate: "bbedit +{{line}} --wait -- {{filename}}",
|
||||
openDirInEditorTemplate: "bbedit -- {{dir}}",
|
||||
editInTerminal: false,
|
||||
suspend: false,
|
||||
},
|
||||
"xcode": {
|
||||
editTemplate: "xed -- {{filename}}",
|
||||
editAtLineTemplate: "xed --line {{line}} -- {{filename}}",
|
||||
editAtLineAndWaitTemplate: "xed --line {{line}} --wait -- {{filename}}",
|
||||
openDirInEditorTemplate: "xed -- {{dir}}",
|
||||
editInTerminal: false,
|
||||
suspend: false,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -123,13 +133,13 @@ func standardTerminalEditorPreset(editor string) *editPreset {
|
||||
editAtLineTemplate: editor + " +{{line}} -- {{filename}}",
|
||||
editAtLineAndWaitTemplate: editor + " +{{line}} -- {{filename}}",
|
||||
openDirInEditorTemplate: editor + " -- {{dir}}",
|
||||
editInTerminal: true,
|
||||
suspend: true,
|
||||
}
|
||||
}
|
||||
|
||||
func getEditInTerminal(osConfig *OSConfig, preset *editPreset) bool {
|
||||
if osConfig.EditInTerminal != nil {
|
||||
return *osConfig.EditInTerminal
|
||||
if osConfig.SuspendOnEdit != nil {
|
||||
return *osConfig.SuspendOnEdit
|
||||
}
|
||||
return preset.editInTerminal
|
||||
return preset.suspend
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ func TestGetEditTemplate(t *testing.T) {
|
||||
expectedEditTemplate string
|
||||
expectedEditAtLineTemplate string
|
||||
expectedEditAtLineAndWaitTemplate string
|
||||
expectedEditInTerminal bool
|
||||
expectedSuspend bool
|
||||
}{
|
||||
{
|
||||
"Default template is vim",
|
||||
@@ -52,9 +52,9 @@ func TestGetEditTemplate(t *testing.T) {
|
||||
{
|
||||
"Overriding a preset with explicit config (edit)",
|
||||
&OSConfig{
|
||||
EditPreset: "vscode",
|
||||
Edit: "myeditor {{filename}}",
|
||||
EditInTerminal: &trueVal,
|
||||
EditPreset: "vscode",
|
||||
Edit: "myeditor {{filename}}",
|
||||
SuspendOnEdit: &trueVal,
|
||||
},
|
||||
func() string { return "" },
|
||||
"myeditor {{filename}}",
|
||||
@@ -65,9 +65,9 @@ func TestGetEditTemplate(t *testing.T) {
|
||||
{
|
||||
"Overriding a preset with explicit config (edit at line)",
|
||||
&OSConfig{
|
||||
EditPreset: "vscode",
|
||||
EditAtLine: "myeditor --line={{line}} {{filename}}",
|
||||
EditInTerminal: &trueVal,
|
||||
EditPreset: "vscode",
|
||||
EditAtLine: "myeditor --line={{line}} {{filename}}",
|
||||
SuspendOnEdit: &trueVal,
|
||||
},
|
||||
func() string { return "" },
|
||||
"code --reuse-window -- {{filename}}",
|
||||
@@ -80,7 +80,7 @@ func TestGetEditTemplate(t *testing.T) {
|
||||
&OSConfig{
|
||||
EditPreset: "vscode",
|
||||
EditAtLineAndWait: "myeditor --line={{line}} -w {{filename}}",
|
||||
EditInTerminal: &trueVal,
|
||||
SuspendOnEdit: &trueVal,
|
||||
},
|
||||
func() string { return "" },
|
||||
"code --reuse-window -- {{filename}}",
|
||||
@@ -111,13 +111,13 @@ func TestGetEditTemplate(t *testing.T) {
|
||||
}
|
||||
for _, s := range scenarios {
|
||||
t.Run(s.name, func(t *testing.T) {
|
||||
template, editInTerminal := GetEditTemplate(s.osConfig, s.guessDefaultEditor)
|
||||
template, suspend := GetEditTemplate(s.osConfig, s.guessDefaultEditor)
|
||||
assert.Equal(t, s.expectedEditTemplate, template)
|
||||
assert.Equal(t, s.expectedEditInTerminal, editInTerminal)
|
||||
assert.Equal(t, s.expectedSuspend, suspend)
|
||||
|
||||
template, editInTerminal = GetEditAtLineTemplate(s.osConfig, s.guessDefaultEditor)
|
||||
template, suspend = GetEditAtLineTemplate(s.osConfig, s.guessDefaultEditor)
|
||||
assert.Equal(t, s.expectedEditAtLineTemplate, template)
|
||||
assert.Equal(t, s.expectedEditInTerminal, editInTerminal)
|
||||
assert.Equal(t, s.expectedSuspend, suspend)
|
||||
|
||||
template = GetEditAtLineAndWaitTemplate(s.osConfig, s.guessDefaultEditor)
|
||||
assert.Equal(t, s.expectedEditAtLineAndWaitTemplate, template)
|
||||
|
||||
+297
-126
@@ -5,141 +5,269 @@ import (
|
||||
)
|
||||
|
||||
type UserConfig struct {
|
||||
Gui GuiConfig `yaml:"gui"`
|
||||
Git GitConfig `yaml:"git"`
|
||||
Update UpdateConfig `yaml:"update"`
|
||||
Refresher RefresherConfig `yaml:"refresher"`
|
||||
ConfirmOnQuit bool `yaml:"confirmOnQuit"`
|
||||
QuitOnTopLevelReturn bool `yaml:"quitOnTopLevelReturn"`
|
||||
Keybinding KeybindingConfig `yaml:"keybinding"`
|
||||
// OS determines what defaults are set for opening files and links
|
||||
OS OSConfig `yaml:"os,omitempty"`
|
||||
DisableStartupPopups bool `yaml:"disableStartupPopups"`
|
||||
CustomCommands []CustomCommand `yaml:"customCommands"`
|
||||
Services map[string]string `yaml:"services"`
|
||||
NotARepository string `yaml:"notARepository"`
|
||||
PromptToReturnFromSubprocess bool `yaml:"promptToReturnFromSubprocess"`
|
||||
// Config relating to the Lazygit UI
|
||||
Gui GuiConfig `yaml:"gui"`
|
||||
// Config relating to git
|
||||
Git GitConfig `yaml:"git"`
|
||||
// Periodic update checks
|
||||
Update UpdateConfig `yaml:"update"`
|
||||
// Background refreshes
|
||||
Refresher RefresherConfig `yaml:"refresher"`
|
||||
// If true, show a confirmation popup before quitting Lazygit
|
||||
ConfirmOnQuit bool `yaml:"confirmOnQuit"`
|
||||
// If true, exit Lazygit when the user presses escape in a context where there is nothing to cancel/close
|
||||
QuitOnTopLevelReturn bool `yaml:"quitOnTopLevelReturn"`
|
||||
// Keybindings
|
||||
Keybinding KeybindingConfig `yaml:"keybinding"`
|
||||
// Config relating to things outside of Lazygit like how files are opened, copying to clipboard, etc
|
||||
OS OSConfig `yaml:"os,omitempty"`
|
||||
// If true, don't display introductory popups upon opening Lazygit.
|
||||
// Lazygit sets this to true upon first runninng the program so that you don't see introductory popups every time you open the program.
|
||||
DisableStartupPopups bool `yaml:"disableStartupPopups"`
|
||||
// User-configured commands that can be invoked from within Lazygit
|
||||
CustomCommands []CustomCommand `yaml:"customCommands"`
|
||||
// See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#custom-pull-request-urls
|
||||
Services map[string]string `yaml:"services"`
|
||||
// What to do when opening Lazygit outside of a git repo.
|
||||
// - 'prompt': (default) ask whether to initialize a new repo or open in the most recent repo
|
||||
// - 'create': initialize a new repo
|
||||
// - 'skip': open most recent repo
|
||||
// - 'quit': exit Lazygit
|
||||
NotARepository string `yaml:"notARepository"`
|
||||
// If true, display a confirmation when subprocess terminates. This allows you to view the output of the subprocess before returning to Lazygit.
|
||||
PromptToReturnFromSubprocess bool `yaml:"promptToReturnFromSubprocess"`
|
||||
}
|
||||
|
||||
type RefresherConfig struct {
|
||||
// File/submodule refresh interval in seconds.
|
||||
// Auto-refresh can be disabled via option 'git.autoRefresh'.
|
||||
RefreshInterval int `yaml:"refreshInterval"`
|
||||
FetchInterval int `yaml:"fetchInterval"`
|
||||
// Re-fetch interval in seconds.
|
||||
// Auto-fetch can be disabled via option 'git.autoFetch'.
|
||||
FetchInterval int `yaml:"fetchInterval"`
|
||||
}
|
||||
|
||||
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"`
|
||||
SkipDiscardChangeWarning bool `yaml:"skipDiscardChangeWarning"`
|
||||
SkipStashWarning bool `yaml:"skipStashWarning"`
|
||||
SidePanelWidth float64 `yaml:"sidePanelWidth"`
|
||||
ExpandFocusedSidePanel bool `yaml:"expandFocusedSidePanel"`
|
||||
MainPanelSplitMode string `yaml:"mainPanelSplitMode"`
|
||||
Language string `yaml:"language"`
|
||||
TimeFormat string `yaml:"timeFormat"`
|
||||
ShortTimeFormat string `yaml:"shortTimeFormat"`
|
||||
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"`
|
||||
NerdFontsVersion string `yaml:"nerdFontsVersion"`
|
||||
ShowBranchCommitHash bool `yaml:"showBranchCommitHash"`
|
||||
CommandLogSize int `yaml:"commandLogSize"`
|
||||
SplitDiff string `yaml:"splitDiff"`
|
||||
SkipRewordInEditorWarning bool `yaml:"skipRewordInEditorWarning"`
|
||||
WindowSize string `yaml:"windowSize"`
|
||||
Border string `yaml:"border"`
|
||||
AnimateExplosion bool `yaml:"animateExplosion"`
|
||||
// See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#custom-author-color
|
||||
AuthorColors map[string]string `yaml:"authorColors"`
|
||||
// See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#custom-branch-color
|
||||
BranchColors map[string]string `yaml:"branchColors"`
|
||||
// The number of lines you scroll by when scrolling the main window
|
||||
ScrollHeight int `yaml:"scrollHeight"`
|
||||
// If true, allow scrolling past the bottom of the content in the main window
|
||||
ScrollPastBottom bool `yaml:"scrollPastBottom"`
|
||||
// See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#scroll-off-margin
|
||||
ScrollOffMargin int `yaml:"scrollOffMargin"`
|
||||
// One of: 'margin' (default) | 'jump'
|
||||
ScrollOffBehavior string `yaml:"scrollOffBehavior"`
|
||||
// If true, capture mouse events.
|
||||
// When mouse events are captured, it's a little harder to select text: e.g. requiring you to hold the option key when on macOS.
|
||||
MouseEvents bool `yaml:"mouseEvents"`
|
||||
// If true, do not show a warning when discarding changes in the staging view.
|
||||
SkipDiscardChangeWarning bool `yaml:"skipDiscardChangeWarning"`
|
||||
// If true, do not show warning when applying/popping the stash
|
||||
SkipStashWarning bool `yaml:"skipStashWarning"`
|
||||
// If true, do not show a warning when attempting to commit without any staged files; instead stage all unstaged files.
|
||||
SkipNoStagedFilesWarning bool `yaml:"skipNoStagedFilesWarning"`
|
||||
// If true, do not show a warning when rewording a commit via an external editor
|
||||
SkipRewordInEditorWarning bool `yaml:"skipRewordInEditorWarning"`
|
||||
// Fraction of the total screen width to use for the left side section. You may want to pick a small number (e.g. 0.2) if you're using a narrow screen, so that you can see more of the main section.
|
||||
// Number from 0 to 1.0.
|
||||
SidePanelWidth float64 `yaml:"sidePanelWidth"`
|
||||
// If true, increase the height of the focused side window; creating an accordion effect.
|
||||
ExpandFocusedSidePanel bool `yaml:"expandFocusedSidePanel"`
|
||||
// Sometimes the main window is split in two (e.g. when the selected file has both staged and unstaged changes). This setting controls how the two sections are split.
|
||||
// Options are:
|
||||
// - 'horizontal': split the window horizontally
|
||||
// - 'vertical': split the window vertically
|
||||
// - 'flexible': (default) split the window horizontally if the window is wide enough, otherwise split vertically
|
||||
MainPanelSplitMode string `yaml:"mainPanelSplitMode"`
|
||||
// One of 'auto' (default) | 'en' | 'zh-CN' | 'zh-TW' | 'pl' | 'nl' | 'ja' | 'ko' | 'ru'
|
||||
Language string `yaml:"language"`
|
||||
// Format used when displaying time e.g. commit time.
|
||||
// Uses Go's time format syntax: https://pkg.go.dev/time#Time.Format
|
||||
TimeFormat string `yaml:"timeFormat"`
|
||||
// Format used when displaying time if the time is less than 24 hours ago.
|
||||
// Uses Go's time format syntax: https://pkg.go.dev/time#Time.Format
|
||||
ShortTimeFormat string `yaml:"shortTimeFormat"`
|
||||
// Config relating to colors and styles.
|
||||
// See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#color-attributes
|
||||
Theme ThemeConfig `yaml:"theme"`
|
||||
// Config relating to the commit length indicator
|
||||
CommitLength CommitLengthConfig `yaml:"commitLength"`
|
||||
// If true, show the '5 of 20' footer at the bottom of list views
|
||||
ShowListFooter bool `yaml:"showListFooter"`
|
||||
// If true, display the files in the file views as a tree. If false, display the files as a flat list.
|
||||
// This can be toggled from within Lazygit with the '~' key, but that will not change the default.
|
||||
ShowFileTree bool `yaml:"showFileTree"`
|
||||
// If true, show a random tip in the command log when Lazygit starts
|
||||
ShowRandomTip bool `yaml:"showRandomTip"`
|
||||
// If true, show the command log
|
||||
ShowCommandLog bool `yaml:"showCommandLog"`
|
||||
// If true, show the bottom line that contains keybinding info and useful buttons. If false, this line will be hidden except to display a loader for an in-progress action.
|
||||
ShowBottomLine bool `yaml:"showBottomLine"`
|
||||
// If true, show jump-to-window keybindings in window titles.
|
||||
ShowPanelJumps bool `yaml:"showPanelJumps"`
|
||||
// Deprecated: use nerdFontsVersion instead
|
||||
ShowIcons bool `yaml:"showIcons"`
|
||||
// Nerd fonts version to use.
|
||||
// One of: '2' | '3' | empty string (default)
|
||||
// If empty, do not show icons.
|
||||
NerdFontsVersion string `yaml:"nerdFontsVersion"`
|
||||
// If true, show commit hashes alongside branch names in the branches view.
|
||||
ShowBranchCommitHash bool `yaml:"showBranchCommitHash"`
|
||||
// Height of the command log view
|
||||
CommandLogSize int `yaml:"commandLogSize"`
|
||||
// Whether to split the main window when viewing file changes.
|
||||
// One of: 'auto' | 'always'
|
||||
// If 'auto', only split the main window when a file has both staged and unstaged changes
|
||||
SplitDiff string `yaml:"splitDiff"`
|
||||
// Default size for focused window. Window size can be changed from within Lazygit with '+' and '_' (but this won't change the default).
|
||||
// One of: 'normal' (default) | 'half' | 'full'
|
||||
WindowSize string `yaml:"windowSize"`
|
||||
// Window border style.
|
||||
// One of 'rounded' (default) | 'single' | 'double' | 'hidden'
|
||||
Border string `yaml:"border"`
|
||||
// If true, show a seriously epic explosion animation when nuking the working tree.
|
||||
AnimateExplosion bool `yaml:"animateExplosion"`
|
||||
// Whether to stack UI components on top of each other.
|
||||
// One of 'auto' (default) | 'always' | 'never'
|
||||
PortraitMode string `yaml:"portraitMode"`
|
||||
}
|
||||
|
||||
type ThemeConfig struct {
|
||||
ActiveBorderColor []string `yaml:"activeBorderColor"`
|
||||
InactiveBorderColor []string `yaml:"inactiveBorderColor"`
|
||||
// Border color of focused window
|
||||
ActiveBorderColor []string `yaml:"activeBorderColor"`
|
||||
// Border color of non-focused windows
|
||||
InactiveBorderColor []string `yaml:"inactiveBorderColor"`
|
||||
// Border color of focused window when searching in that window
|
||||
SearchingActiveBorderColor []string `yaml:"searchingActiveBorderColor"`
|
||||
OptionsTextColor []string `yaml:"optionsTextColor"`
|
||||
SelectedLineBgColor []string `yaml:"selectedLineBgColor"`
|
||||
SelectedRangeBgColor []string `yaml:"selectedRangeBgColor"`
|
||||
CherryPickedCommitBgColor []string `yaml:"cherryPickedCommitBgColor"`
|
||||
CherryPickedCommitFgColor []string `yaml:"cherryPickedCommitFgColor"`
|
||||
MarkedBaseCommitBgColor []string `yaml:"markedBaseCommitBgColor"`
|
||||
MarkedBaseCommitFgColor []string `yaml:"markedBaseCommitFgColor"`
|
||||
UnstagedChangesColor []string `yaml:"unstagedChangesColor"`
|
||||
DefaultFgColor []string `yaml:"defaultFgColor"`
|
||||
// Color of keybindings help text in the bottom line
|
||||
OptionsTextColor []string `yaml:"optionsTextColor"`
|
||||
// Background color of selected line.
|
||||
// See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#highlighting-the-selected-line
|
||||
SelectedLineBgColor []string `yaml:"selectedLineBgColor"`
|
||||
// Background color of selected range
|
||||
// See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#highlighting-the-selected-line
|
||||
SelectedRangeBgColor []string `yaml:"selectedRangeBgColor"`
|
||||
// Foreground color of copied commit
|
||||
CherryPickedCommitFgColor []string `yaml:"cherryPickedCommitFgColor"`
|
||||
// Background color of copied commit
|
||||
CherryPickedCommitBgColor []string `yaml:"cherryPickedCommitBgColor"`
|
||||
// Foreground color of marked base commit (for rebase)
|
||||
MarkedBaseCommitFgColor []string `yaml:"markedBaseCommitFgColor"`
|
||||
// Background color of marked base commit (for rebase)
|
||||
MarkedBaseCommitBgColor []string `yaml:"markedBaseCommitBgColor"`
|
||||
// Color for file with unstaged changes
|
||||
UnstagedChangesColor []string `yaml:"unstagedChangesColor"`
|
||||
// Default text color
|
||||
DefaultFgColor []string `yaml:"defaultFgColor"`
|
||||
}
|
||||
|
||||
type CommitLengthConfig struct {
|
||||
// If true, show an indicator of commit message length
|
||||
Show bool `yaml:"show"`
|
||||
}
|
||||
|
||||
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"`
|
||||
FetchAll bool `yaml:"fetchAll"`
|
||||
BranchLogCmd string `yaml:"branchLogCmd"`
|
||||
AllBranchesLogCmd string `yaml:"allBranchesLogCmd"`
|
||||
OverrideGpg bool `yaml:"overrideGpg"`
|
||||
DisableForcePushing bool `yaml:"disableForcePushing"`
|
||||
CommitPrefixes map[string]CommitPrefixConfig `yaml:"commitPrefixes"`
|
||||
// this should really be under 'gui', not 'git'
|
||||
ParseEmoji bool `yaml:"parseEmoji"`
|
||||
Log LogConfig `yaml:"log"`
|
||||
DiffContextSize int `yaml:"diffContextSize"`
|
||||
// See https://github.com/jesseduffield/lazygit/blob/master/docs/Custom_Pagers.md
|
||||
Paging PagingConfig `yaml:"paging"`
|
||||
// Config relating to committing
|
||||
Commit CommitConfig `yaml:"commit"`
|
||||
// Config relating to merging
|
||||
Merging MergingConfig `yaml:"merging"`
|
||||
// list of branches that are considered 'main' branches, used when displaying commits
|
||||
MainBranches []string `yaml:"mainBranches"`
|
||||
// Prefix to use when skipping hooks. E.g. if set to 'WIP', then pre-commit hooks will be skipped when the commit message starts with 'WIP'
|
||||
SkipHookPrefix string `yaml:"skipHookPrefix"`
|
||||
// If true, periodically fetch from remote
|
||||
AutoFetch bool `yaml:"autoFetch"`
|
||||
// If true, periodically refresh files and submodules
|
||||
AutoRefresh bool `yaml:"autoRefresh"`
|
||||
// If true, pass the --all arg to git fetch
|
||||
FetchAll bool `yaml:"fetchAll"`
|
||||
// Command used when displaying the current branch git log in the main window
|
||||
BranchLogCmd string `yaml:"branchLogCmd"`
|
||||
// Command used to display git log of all branches in the main window
|
||||
AllBranchesLogCmd string `yaml:"allBranchesLogCmd"`
|
||||
// If true, do not spawn a separate process when using GPG
|
||||
OverrideGpg bool `yaml:"overrideGpg"`
|
||||
// If true, do not allow force pushes
|
||||
DisableForcePushing bool `yaml:"disableForcePushing"`
|
||||
// See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#predefined-commit-message-prefix
|
||||
CommitPrefixes map[string]CommitPrefixConfig `yaml:"commitPrefixes"`
|
||||
// If true, parse emoji strings in commit messages e.g. render :rocket: as 🚀
|
||||
// (This should really be under 'gui', not 'git')
|
||||
ParseEmoji bool `yaml:"parseEmoji"`
|
||||
// Config for showing the log in the commits view
|
||||
Log LogConfig `yaml:"log"`
|
||||
}
|
||||
|
||||
type PagingConfig struct {
|
||||
ColorArg string `yaml:"colorArg"`
|
||||
Pager string `yaml:"pager"`
|
||||
UseConfig bool `yaml:"useConfig"`
|
||||
// Value of the --color arg in the git diff command. Some pagers want this to be set to 'always' and some want it set to 'never'
|
||||
ColorArg string `yaml:"colorArg"`
|
||||
// e.g.
|
||||
// diff-so-fancy
|
||||
// delta --dark --paging=never
|
||||
// ydiff -p cat -s --wrap --width={{columnWidth}}
|
||||
Pager string `yaml:"pager"`
|
||||
// If true, Lazygit will use whatever pager is specified in `$GIT_PAGER`, `$PAGER`, or your *git config*. If the pager ends with something like ` | less` we will strip that part out, because less doesn't play nice with our rendering approach. If the custom pager uses less under the hood, that will also break rendering (hence the `--paging=never` flag for the `delta` pager).
|
||||
UseConfig bool `yaml:"useConfig"`
|
||||
// e.g. 'difft --color=always'
|
||||
ExternalDiffCommand string `yaml:"externalDiffCommand"`
|
||||
}
|
||||
|
||||
type CommitConfig struct {
|
||||
// If true, pass '--signoff' flag when committing
|
||||
SignOff bool `yaml:"signOff"`
|
||||
}
|
||||
|
||||
type MergingConfig struct {
|
||||
ManualCommit bool `yaml:"manualCommit"`
|
||||
Args string `yaml:"args"`
|
||||
// If true, run merges in a subprocess so that if a commit message is required, Lazygit will not hang
|
||||
// Only applicable to unix users.
|
||||
ManualCommit bool `yaml:"manualCommit"`
|
||||
// Extra args passed to `git merge`, e.g. --no-ff
|
||||
Args string `yaml:"args"`
|
||||
}
|
||||
|
||||
type LogConfig struct {
|
||||
Order string `yaml:"order"` // one of date-order, author-date-order, topo-order
|
||||
ShowGraph string `yaml:"showGraph"` // one of always, never, when-maximised
|
||||
ShowWholeGraph bool `yaml:"showWholeGraph"`
|
||||
// One of: 'date-order' | 'author-date-order' | 'topo-order'
|
||||
// 'topo-order' makes it easier to read the git log graph, but commits may not
|
||||
// appear chronologically. See https://git-scm.com/docs/
|
||||
Order string `yaml:"order"`
|
||||
// This determines whether the git graph is rendered in the commits panel
|
||||
// One of 'always' | 'never' 'when-maximised'
|
||||
ShowGraph string `yaml:"showGraph"`
|
||||
// displays the whole git graph by default in the commits view (equivalent to passing the `--all` argument to `git log`)
|
||||
ShowWholeGraph bool `yaml:"showWholeGraph"`
|
||||
}
|
||||
|
||||
type CommitPrefixConfig struct {
|
||||
// pattern to match on. E.g. for 'feature/AB-123' to match on the AB-123 use "^\\w+\\/(\\w+-\\w+).*"
|
||||
Pattern string `yaml:"pattern"`
|
||||
// Replace directive. E.g. for 'feature/AB-123' to start the commit message with 'AB-123 ' use "[$1] "
|
||||
Replace string `yaml:"replace"`
|
||||
}
|
||||
|
||||
type UpdateConfig struct {
|
||||
// One of: 'prompt' (default) | 'background' | 'never'
|
||||
Method string `yaml:"method"`
|
||||
Days int64 `yaml:"days"`
|
||||
// Period in days between update checks
|
||||
Days int64 `yaml:"days"`
|
||||
}
|
||||
|
||||
type KeybindingConfig struct {
|
||||
Universal KeybindingUniversalConfig `yaml:"universal"`
|
||||
Status KeybindingStatusConfig `yaml:"status"`
|
||||
Files KeybindingFilesConfig `yaml:"files"`
|
||||
Branches KeybindingBranchesConfig `yaml:"branches"`
|
||||
Worktrees KeybindingWorktreesConfig `yaml:"worktrees"`
|
||||
Commits KeybindingCommitsConfig `yaml:"commits"`
|
||||
Stash KeybindingStashConfig `yaml:"stash"`
|
||||
CommitFiles KeybindingCommitFilesConfig `yaml:"commitFiles"`
|
||||
Main KeybindingMainConfig `yaml:"main"`
|
||||
Submodules KeybindingSubmodulesConfig `yaml:"submodules"`
|
||||
Universal KeybindingUniversalConfig `yaml:"universal"`
|
||||
Status KeybindingStatusConfig `yaml:"status"`
|
||||
Files KeybindingFilesConfig `yaml:"files"`
|
||||
Branches KeybindingBranchesConfig `yaml:"branches"`
|
||||
Worktrees KeybindingWorktreesConfig `yaml:"worktrees"`
|
||||
Commits KeybindingCommitsConfig `yaml:"commits"`
|
||||
Stash KeybindingStashConfig `yaml:"stash"`
|
||||
CommitFiles KeybindingCommitFilesConfig `yaml:"commitFiles"`
|
||||
Main KeybindingMainConfig `yaml:"main"`
|
||||
Submodules KeybindingSubmodulesConfig `yaml:"submodules"`
|
||||
CommitMessage KeybindingCommitMessageConfig `yaml:"commitMessage"`
|
||||
}
|
||||
|
||||
// damn looks like we have some inconsistencies here with -alt and -alt1
|
||||
@@ -220,6 +348,7 @@ type KeybindingFilesConfig struct {
|
||||
CommitChangesWithoutHook string `yaml:"commitChangesWithoutHook"`
|
||||
AmendLastCommit string `yaml:"amendLastCommit"`
|
||||
CommitChangesWithEditor string `yaml:"commitChangesWithEditor"`
|
||||
ConfirmDiscard string `yaml:"confirmDiscard"`
|
||||
IgnoreFile string `yaml:"ignoreFile"`
|
||||
RefreshFiles string `yaml:"refreshFiles"`
|
||||
StashAllChanges string `yaml:"stashAllChanges"`
|
||||
@@ -303,6 +432,10 @@ type KeybindingSubmodulesConfig struct {
|
||||
BulkMenu string `yaml:"bulkMenu"`
|
||||
}
|
||||
|
||||
type KeybindingCommitMessageConfig struct {
|
||||
SwitchToEditor string `yaml:"switchToEditor"`
|
||||
}
|
||||
|
||||
// OSConfig contains config on the level of the os
|
||||
type OSConfig struct {
|
||||
// Command for editing a file. Should contain "{{filename}}".
|
||||
@@ -316,10 +449,10 @@ type OSConfig struct {
|
||||
// window is closed.
|
||||
EditAtLineAndWait string `yaml:"editAtLineAndWait,omitempty"`
|
||||
|
||||
// Whether the given edit commands use the terminal. Used to decide whether
|
||||
// lazygit needs to suspend to the background before calling the editor.
|
||||
// Whether lazygit suspends until an edit process returns
|
||||
// Pointer to bool so that we can distinguish unset (nil) from false.
|
||||
EditInTerminal *bool `yaml:"editInTerminal,omitempty"`
|
||||
// We're naming this `editInTerminal` for backwards compatibility
|
||||
SuspendOnEdit *bool `yaml:"editInTerminal,omitempty"`
|
||||
|
||||
// For opening a directory in an editor
|
||||
OpenDirInEditor string `yaml:"openDirInEditor,omitempty"`
|
||||
@@ -358,7 +491,8 @@ type OSConfig struct {
|
||||
// Deprecated: use OpenLink instead.
|
||||
OpenLinkCommand string `yaml:"openLinkCommand,omitempty"`
|
||||
|
||||
// CopyToClipboardCmd is the command for copying to clipboard
|
||||
// CopyToClipboardCmd is the command for copying to clipboard.
|
||||
// See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#custom-command-for-copying-to-clipboard
|
||||
CopyToClipboardCmd string `yaml:"copyToClipboardCmd,omitempty"`
|
||||
}
|
||||
|
||||
@@ -367,50 +501,79 @@ type CustomCommandAfterHook struct {
|
||||
}
|
||||
|
||||
type CustomCommand struct {
|
||||
Key string `yaml:"key"`
|
||||
Context string `yaml:"context"`
|
||||
Command string `yaml:"command"`
|
||||
Subprocess bool `yaml:"subprocess"`
|
||||
Prompts []CustomCommandPrompt `yaml:"prompts"`
|
||||
LoadingText string `yaml:"loadingText"`
|
||||
Description string `yaml:"description"`
|
||||
Stream bool `yaml:"stream"`
|
||||
ShowOutput bool `yaml:"showOutput"`
|
||||
After CustomCommandAfterHook `yaml:"after"`
|
||||
// The key to trigger the command. Use a single letter or one of the values from https://github.com/jesseduffield/lazygit/blob/master/docs/keybindings/Custom_Keybindings.md
|
||||
Key string `yaml:"key"`
|
||||
// The context in which to listen for the key
|
||||
Context string `yaml:"context"`
|
||||
// The command to run (using Go template syntax for placeholder values)
|
||||
Command string `yaml:"command"`
|
||||
// If true, run the command in a subprocess (e.g. if the command requires user input)
|
||||
Subprocess bool `yaml:"subprocess"`
|
||||
// A list of prompts that will request user input before running the final command
|
||||
Prompts []CustomCommandPrompt `yaml:"prompts"`
|
||||
// Text to display while waiting for command to finish
|
||||
LoadingText string `yaml:"loadingText"`
|
||||
// Label for the custom command when displayed in the keybindings menu
|
||||
Description string `yaml:"description"`
|
||||
// If true, stream the command's output to the Command Log panel
|
||||
Stream bool `yaml:"stream"`
|
||||
// If true, show the command's output in a popup within Lazygit
|
||||
ShowOutput bool `yaml:"showOutput"`
|
||||
// Actions to take after the command has completed
|
||||
After CustomCommandAfterHook `yaml:"after"`
|
||||
}
|
||||
|
||||
type CustomCommandPrompt struct {
|
||||
// one of 'input', 'menu', 'confirm', or 'menuFromCommand'
|
||||
Type string `yaml:"type"`
|
||||
Key string `yaml:"key"`
|
||||
// One of: 'input' | 'menu' | 'confirm' | 'menuFromCommand'
|
||||
Type string `yaml:"type"`
|
||||
// Used to reference the entered value from within the custom command. E.g. a prompt with `key: 'Branch'` can be referred to as `{{.Form.Branch}}` in the command
|
||||
Key string `yaml:"key"`
|
||||
// The title to display in the popup panel
|
||||
Title string `yaml:"title"`
|
||||
|
||||
// these only apply to input prompts
|
||||
InitialValue string `yaml:"initialValue"`
|
||||
Suggestions CustomCommandSuggestions `yaml:"suggestions"`
|
||||
// The initial value to appear in the text box.
|
||||
// Only for input prompts.
|
||||
InitialValue string `yaml:"initialValue"`
|
||||
// Shows suggestions as the input is entered
|
||||
// Only for input prompts.
|
||||
Suggestions CustomCommandSuggestions `yaml:"suggestions"`
|
||||
|
||||
// this only applies to confirm prompts
|
||||
// The message of the confirmation prompt.
|
||||
// Only for confirm prompts.
|
||||
Body string `yaml:"body"`
|
||||
|
||||
// this only applies to menus
|
||||
// Menu options.
|
||||
// Only for menu prompts.
|
||||
Options []CustomCommandMenuOption
|
||||
|
||||
// this only applies to menuFromCommand
|
||||
Command string `yaml:"command"`
|
||||
Filter string `yaml:"filter"`
|
||||
// The command to run to generate menu options
|
||||
// Only for menuFromCommand prompts.
|
||||
Command string `yaml:"command"`
|
||||
// The regexp to run specifying groups which are going to be kept from the command's output.
|
||||
// Only for menuFromCommand prompts.
|
||||
Filter string `yaml:"filter"`
|
||||
// How to format matched groups from the filter to construct a menu item's value.
|
||||
// Only for menuFromCommand prompts.
|
||||
ValueFormat string `yaml:"valueFormat"`
|
||||
// Like valueFormat but for the labels. If `labelFormat` is not specified, `valueFormat` is shown instead.
|
||||
// Only for menuFromCommand prompts.
|
||||
LabelFormat string `yaml:"labelFormat"`
|
||||
}
|
||||
|
||||
type CustomCommandSuggestions struct {
|
||||
Preset string `yaml:"preset"`
|
||||
// Uses built-in logic to obtain the suggestions. One of 'authors' | 'branches' | 'files' | 'refs' | 'remotes' | 'remoteBranches' | 'tags'
|
||||
Preset string `yaml:"preset"`
|
||||
// Command to run such that each line in the output becomes a suggestion. Mutually exclusive with 'preset' field.
|
||||
Command string `yaml:"command"`
|
||||
}
|
||||
|
||||
type CustomCommandMenuOption struct {
|
||||
Name string `yaml:"name"`
|
||||
// The first part of the label
|
||||
Name string `yaml:"name"`
|
||||
// The second part of the label
|
||||
Description string `yaml:"description"`
|
||||
Value string `yaml:"value"`
|
||||
// The value that will be used in the command
|
||||
Value string `yaml:"value"`
|
||||
}
|
||||
|
||||
func GetDefaultConfig() *UserConfig {
|
||||
@@ -418,6 +581,8 @@ func GetDefaultConfig() *UserConfig {
|
||||
Gui: GuiConfig{
|
||||
ScrollHeight: 2,
|
||||
ScrollPastBottom: true,
|
||||
ScrollOffMargin: 2,
|
||||
ScrollOffBehavior: "margin",
|
||||
MouseEvents: true,
|
||||
SkipDiscardChangeWarning: false,
|
||||
SkipStashWarning: false,
|
||||
@@ -446,6 +611,7 @@ func GetDefaultConfig() *UserConfig {
|
||||
ShowListFooter: true,
|
||||
ShowCommandLog: true,
|
||||
ShowBottomLine: true,
|
||||
ShowPanelJumps: true,
|
||||
ShowFileTree: true,
|
||||
ShowRandomTip: true,
|
||||
ShowIcons: false,
|
||||
@@ -454,14 +620,16 @@ func GetDefaultConfig() *UserConfig {
|
||||
CommandLogSize: 8,
|
||||
SplitDiff: "auto",
|
||||
SkipRewordInEditorWarning: false,
|
||||
Border: "single",
|
||||
Border: "rounded",
|
||||
AnimateExplosion: true,
|
||||
PortraitMode: "auto",
|
||||
},
|
||||
Git: GitConfig{
|
||||
Paging: PagingConfig{
|
||||
ColorArg: "always",
|
||||
Pager: "",
|
||||
UseConfig: false,
|
||||
ColorArg: "always",
|
||||
Pager: "",
|
||||
UseConfig: false,
|
||||
ExternalDiffCommand: "",
|
||||
},
|
||||
Commit: CommitConfig{
|
||||
SignOff: false,
|
||||
@@ -485,7 +653,6 @@ func GetDefaultConfig() *UserConfig {
|
||||
DisableForcePushing: false,
|
||||
CommitPrefixes: map[string]CommitPrefixConfig(nil),
|
||||
ParseEmoji: false,
|
||||
DiffContextSize: 3,
|
||||
},
|
||||
Refresher: RefresherConfig{
|
||||
RefreshInterval: 10,
|
||||
@@ -524,7 +691,7 @@ func GetDefaultConfig() *UserConfig {
|
||||
NextMatch: "n",
|
||||
PrevMatch: "N",
|
||||
StartSearch: "/",
|
||||
OptionMenu: "",
|
||||
OptionMenu: "<disabled>",
|
||||
OptionMenuAlt1: "?",
|
||||
Select: "<space>",
|
||||
GoInto: "<enter>",
|
||||
@@ -583,6 +750,7 @@ func GetDefaultConfig() *UserConfig {
|
||||
ToggleTreeView: "`",
|
||||
OpenMergeTool: "M",
|
||||
OpenStatusFilter: "<c-b>",
|
||||
ConfirmDiscard: "x",
|
||||
},
|
||||
Branches: KeybindingBranchesConfig{
|
||||
CopyPullRequestURL: "<c-y>",
|
||||
@@ -648,6 +816,9 @@ func GetDefaultConfig() *UserConfig {
|
||||
Update: "u",
|
||||
BulkMenu: "b",
|
||||
},
|
||||
CommitMessage: KeybindingCommitMessageConfig{
|
||||
SwitchToEditor: "<c-o>",
|
||||
},
|
||||
},
|
||||
OS: OSConfig{},
|
||||
DisableStartupPopups: false,
|
||||
|
||||
Vendored
+10
-1
@@ -14,6 +14,15 @@ func SetGitDirEnv(value string) {
|
||||
os.Setenv("GIT_DIR", value)
|
||||
}
|
||||
|
||||
func UnsetGitDirEnv() {
|
||||
func GetWorkTreeEnv() string {
|
||||
return os.Getenv("GIT_WORK_TREE")
|
||||
}
|
||||
|
||||
func SetWorkTreeEnv(value string) {
|
||||
os.Setenv("GIT_WORK_TREE", value)
|
||||
}
|
||||
|
||||
func UnsetGitLocationEnvVars() {
|
||||
_ = os.Unsetenv("GIT_DIR")
|
||||
_ = os.Unsetenv("GIT_WORK_TREE")
|
||||
}
|
||||
|
||||
@@ -20,10 +20,11 @@ type BaseContext struct {
|
||||
onFocusFn onFocusFn
|
||||
onFocusLostFn onFocusLostFn
|
||||
|
||||
focusable bool
|
||||
transient bool
|
||||
hasControlledBounds bool
|
||||
highlightOnFocus bool
|
||||
focusable bool
|
||||
transient bool
|
||||
hasControlledBounds bool
|
||||
needsRerenderOnWidthChange bool
|
||||
highlightOnFocus bool
|
||||
|
||||
*ParentContextMgr
|
||||
}
|
||||
@@ -36,14 +37,15 @@ type (
|
||||
var _ types.IBaseContext = &BaseContext{}
|
||||
|
||||
type NewBaseContextOpts struct {
|
||||
Kind types.ContextKind
|
||||
Key types.ContextKey
|
||||
View *gocui.View
|
||||
WindowName string
|
||||
Focusable bool
|
||||
Transient bool
|
||||
HasUncontrolledBounds bool // negating for the sake of making false the default
|
||||
HighlightOnFocus bool
|
||||
Kind types.ContextKind
|
||||
Key types.ContextKey
|
||||
View *gocui.View
|
||||
WindowName string
|
||||
Focusable bool
|
||||
Transient bool
|
||||
HasUncontrolledBounds bool // negating for the sake of making false the default
|
||||
HighlightOnFocus bool
|
||||
NeedsRerenderOnWidthChange bool
|
||||
|
||||
OnGetOptionsMap func() map[string]string
|
||||
}
|
||||
@@ -54,17 +56,18 @@ func NewBaseContext(opts NewBaseContextOpts) *BaseContext {
|
||||
hasControlledBounds := !opts.HasUncontrolledBounds
|
||||
|
||||
return &BaseContext{
|
||||
kind: opts.Kind,
|
||||
key: opts.Key,
|
||||
view: opts.View,
|
||||
windowName: opts.WindowName,
|
||||
onGetOptionsMap: opts.OnGetOptionsMap,
|
||||
focusable: opts.Focusable,
|
||||
transient: opts.Transient,
|
||||
hasControlledBounds: hasControlledBounds,
|
||||
highlightOnFocus: opts.HighlightOnFocus,
|
||||
ParentContextMgr: &ParentContextMgr{},
|
||||
viewTrait: viewTrait,
|
||||
kind: opts.Kind,
|
||||
key: opts.Key,
|
||||
view: opts.View,
|
||||
windowName: opts.WindowName,
|
||||
onGetOptionsMap: opts.OnGetOptionsMap,
|
||||
focusable: opts.Focusable,
|
||||
transient: opts.Transient,
|
||||
hasControlledBounds: hasControlledBounds,
|
||||
highlightOnFocus: opts.HighlightOnFocus,
|
||||
needsRerenderOnWidthChange: opts.NeedsRerenderOnWidthChange,
|
||||
ParentContextMgr: &ParentContextMgr{},
|
||||
viewTrait: viewTrait,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -190,6 +193,10 @@ func (self *BaseContext) HasControlledBounds() bool {
|
||||
return self.hasControlledBounds
|
||||
}
|
||||
|
||||
func (self *BaseContext) NeedsRerenderOnWidthChange() bool {
|
||||
return self.needsRerenderOnWidthChange
|
||||
}
|
||||
|
||||
func (self *BaseContext) Title() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
@@ -24,11 +24,13 @@ func NewBranchesContext(c *ContextCommon) *BranchesContext {
|
||||
},
|
||||
)
|
||||
|
||||
getDisplayStrings := func(startIdx int, length int) [][]string {
|
||||
getDisplayStrings := func(_ int, _ int) [][]string {
|
||||
return presentation.GetBranchListDisplayStrings(
|
||||
viewModel.GetItems(),
|
||||
c.State().GetItemOperation,
|
||||
c.State().GetRepoState().GetScreenMode() != types.SCREEN_NORMAL,
|
||||
c.Modes().Diffing.Ref,
|
||||
c.Views().Branches.Width(),
|
||||
c.Tr,
|
||||
c.UserConfig,
|
||||
c.Model().Worktrees,
|
||||
@@ -39,15 +41,18 @@ func NewBranchesContext(c *ContextCommon) *BranchesContext {
|
||||
FilteredListViewModel: viewModel,
|
||||
ListContextTrait: &ListContextTrait{
|
||||
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
|
||||
View: c.Views().Branches,
|
||||
WindowName: "branches",
|
||||
Key: LOCAL_BRANCHES_CONTEXT_KEY,
|
||||
Kind: types.SIDE_CONTEXT,
|
||||
Focusable: true,
|
||||
View: c.Views().Branches,
|
||||
WindowName: "branches",
|
||||
Key: LOCAL_BRANCHES_CONTEXT_KEY,
|
||||
Kind: types.SIDE_CONTEXT,
|
||||
Focusable: true,
|
||||
NeedsRerenderOnWidthChange: true,
|
||||
})),
|
||||
list: viewModel,
|
||||
getDisplayStrings: getDisplayStrings,
|
||||
c: c,
|
||||
ListRenderer: ListRenderer{
|
||||
list: viewModel,
|
||||
getDisplayStrings: getDisplayStrings,
|
||||
},
|
||||
c: c,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ func NewCommitFilesContext(c *ContextCommon) *CommitFilesContext {
|
||||
c.UserConfig.Gui.ShowFileTree,
|
||||
)
|
||||
|
||||
getDisplayStrings := func(startIdx int, length int) [][]string {
|
||||
getDisplayStrings := func(_ int, _ int) [][]string {
|
||||
if viewModel.Len() == 0 {
|
||||
return [][]string{{style.FgRed.Sprint("(none)")}}
|
||||
}
|
||||
@@ -54,9 +54,11 @@ func NewCommitFilesContext(c *ContextCommon) *CommitFilesContext {
|
||||
Transient: true,
|
||||
}),
|
||||
),
|
||||
list: viewModel,
|
||||
getDisplayStrings: getDisplayStrings,
|
||||
c: c,
|
||||
ListRenderer: ListRenderer{
|
||||
list: viewModel,
|
||||
getDisplayStrings: getDisplayStrings,
|
||||
},
|
||||
c: c,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,10 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/keybindings"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
type CommitMessageContext struct {
|
||||
@@ -32,6 +35,8 @@ type CommitMessageViewModel struct {
|
||||
preservedMessage string
|
||||
// invoked when pressing enter in the commit message panel
|
||||
onConfirm func(string, string) error
|
||||
// invoked when pressing the switch-to-editor key binding
|
||||
onSwitchToEditor func(string) error
|
||||
|
||||
// The message typed in before cycling through history
|
||||
// We store this separately to 'preservedMessage' because 'preservedMessage'
|
||||
@@ -98,12 +103,21 @@ func (self *CommitMessageContext) SetPanelState(
|
||||
descriptionTitle string,
|
||||
preserveMessage bool,
|
||||
onConfirm func(string, string) error,
|
||||
onSwitchToEditor func(string) error,
|
||||
) {
|
||||
self.viewModel.selectedindex = index
|
||||
self.viewModel.preserveMessage = preserveMessage
|
||||
self.viewModel.onConfirm = onConfirm
|
||||
self.viewModel.onSwitchToEditor = onSwitchToEditor
|
||||
self.GetView().Title = summaryTitle
|
||||
self.c.Views().CommitDescription.Title = descriptionTitle
|
||||
|
||||
subtitleTemplate := lo.Ternary(onSwitchToEditor != nil, self.c.Tr.CommitDescriptionSubTitle, self.c.Tr.CommitDescriptionSubTitleNoSwitch)
|
||||
self.c.Views().CommitDescription.Subtitle = utils.ResolvePlaceholderString(subtitleTemplate,
|
||||
map[string]string{
|
||||
"togglePanelKeyBinding": keybindings.Label(self.c.UserConfig.Keybinding.Universal.TogglePanel),
|
||||
"switchToEditorKeyBinding": keybindings.Label(self.c.UserConfig.Keybinding.CommitMessage.SwitchToEditor),
|
||||
})
|
||||
}
|
||||
|
||||
func (self *CommitMessageContext) RenderCommitLength() {
|
||||
@@ -117,3 +131,11 @@ func (self *CommitMessageContext) RenderCommitLength() {
|
||||
func getBufferLength(view *gocui.View) string {
|
||||
return " " + strconv.Itoa(strings.Count(view.TextArea.GetContent(), "")-1) + " "
|
||||
}
|
||||
|
||||
func (self *CommitMessageContext) SwitchToEditor(message string) error {
|
||||
return self.viewModel.onSwitchToEditor(message)
|
||||
}
|
||||
|
||||
func (self *CommitMessageContext) CanSwitchToEditor() bool {
|
||||
return self.viewModel.onSwitchToEditor != nil
|
||||
}
|
||||
|
||||
@@ -41,6 +41,10 @@ func (self *FilteredList[T]) ClearFilter() {
|
||||
self.SetFilter("")
|
||||
}
|
||||
|
||||
func (self *FilteredList[T]) ReApplyFilter() {
|
||||
self.applyFilter()
|
||||
}
|
||||
|
||||
func (self *FilteredList[T]) IsFiltering() bool {
|
||||
return self.filter != ""
|
||||
}
|
||||
|
||||
@@ -3,13 +3,15 @@ package context
|
||||
type FilteredListViewModel[T any] struct {
|
||||
*FilteredList[T]
|
||||
*ListViewModel[T]
|
||||
*SearchHistory
|
||||
}
|
||||
|
||||
func NewFilteredListViewModel[T any](getList func() []T, getFilterFields func(T) []string) *FilteredListViewModel[T] {
|
||||
filteredList := NewFilteredList(getList, getFilterFields)
|
||||
|
||||
self := &FilteredListViewModel[T]{
|
||||
FilteredList: filteredList,
|
||||
FilteredList: filteredList,
|
||||
SearchHistory: NewSearchHistory(),
|
||||
}
|
||||
|
||||
listViewModel := NewListViewModel(filteredList.GetFilteredList)
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
package context
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
)
|
||||
|
||||
// Maintains a list of strings that have previously been searched/filtered for
|
||||
type SearchHistory struct {
|
||||
history *utils.HistoryBuffer[string]
|
||||
}
|
||||
|
||||
func NewSearchHistory() *SearchHistory {
|
||||
return &SearchHistory{
|
||||
history: utils.NewHistoryBuffer[string](1000),
|
||||
}
|
||||
}
|
||||
|
||||
func (self *SearchHistory) GetSearchHistory() *utils.HistoryBuffer[string] {
|
||||
return self.history
|
||||
}
|
||||
@@ -4,17 +4,13 @@ import (
|
||||
"fmt"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
)
|
||||
|
||||
type ListContextTrait struct {
|
||||
types.Context
|
||||
ListRenderer
|
||||
|
||||
c *ContextCommon
|
||||
list types.IList
|
||||
getDisplayStrings func(startIdx int, length int) [][]string
|
||||
// Alignment for each column. If nil, the default is left alignment
|
||||
getColumnAlignments func() []utils.Alignment
|
||||
c *ContextCommon
|
||||
// Some contexts, like the commit context, will highlight the path from the selected commit
|
||||
// to its parents, because it's ambiguous otherwise. For these, we need to refresh the viewport
|
||||
// so that we show the highlighted path.
|
||||
@@ -26,16 +22,24 @@ type ListContextTrait struct {
|
||||
|
||||
func (self *ListContextTrait) IsListContext() {}
|
||||
|
||||
func (self *ListContextTrait) GetList() types.IList {
|
||||
return self.list
|
||||
}
|
||||
|
||||
func (self *ListContextTrait) FocusLine() {
|
||||
// Doing this at the end of the layout function because we need the view to be
|
||||
// resized before we focus the line, otherwise if we're in accordion mode
|
||||
// the view could be squashed and won't how to adjust the cursor/origin
|
||||
self.c.AfterLayout(func() error {
|
||||
self.GetViewTrait().FocusPoint(self.list.GetSelectedLineIdx())
|
||||
oldOrigin, _ := self.GetViewTrait().ViewPortYBounds()
|
||||
|
||||
self.GetViewTrait().FocusPoint(
|
||||
self.ModelIndexToViewIndex(self.list.GetSelectedLineIdx()))
|
||||
|
||||
// If FocusPoint() caused the view to scroll (because the selected line
|
||||
// was out of view before), we need to rerender the view port again.
|
||||
// This can happen when pressing , or . to scroll by pages, or < or > to
|
||||
// jump to the top or bottom.
|
||||
newOrigin, _ := self.GetViewTrait().ViewPortYBounds()
|
||||
if self.refreshViewportOnChange && oldOrigin != newOrigin {
|
||||
self.refreshViewport()
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
@@ -48,8 +52,7 @@ func (self *ListContextTrait) FocusLine() {
|
||||
|
||||
func (self *ListContextTrait) refreshViewport() {
|
||||
startIdx, length := self.GetViewTrait().ViewPortYBounds()
|
||||
displayStrings := self.getDisplayStrings(startIdx, length)
|
||||
content := utils.RenderDisplayStrings(displayStrings, nil)
|
||||
content := self.renderLines(startIdx, startIdx+length)
|
||||
self.GetViewTrait().SetViewPortContent(content)
|
||||
}
|
||||
|
||||
@@ -82,14 +85,7 @@ func (self *ListContextTrait) HandleFocusLost(opts types.OnFocusLostOpts) error
|
||||
// OnFocus assumes that the content of the context has already been rendered to the view. OnRender is the function which actually renders the content to the view
|
||||
func (self *ListContextTrait) HandleRender() error {
|
||||
self.list.RefreshSelectedIdx()
|
||||
var columnAlignments []utils.Alignment
|
||||
if self.getColumnAlignments != nil {
|
||||
columnAlignments = self.getColumnAlignments()
|
||||
}
|
||||
content := utils.RenderDisplayStrings(
|
||||
self.getDisplayStrings(0, self.list.Len()),
|
||||
columnAlignments,
|
||||
)
|
||||
content := self.renderLines(-1, -1)
|
||||
self.GetViewTrait().SetContent(content)
|
||||
self.c.Render()
|
||||
self.setFooter()
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
package context
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
"github.com/samber/lo"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
type NonModelItem struct {
|
||||
// Where in the model this should be inserted
|
||||
Index int
|
||||
// Content to render
|
||||
Content string
|
||||
// The column from which to render the item
|
||||
Column int
|
||||
}
|
||||
|
||||
type ListRenderer struct {
|
||||
list types.IList
|
||||
// Function to get the display strings for each model item in the given
|
||||
// range. startIdx and endIdx are model indices. For each model item, return
|
||||
// an array of strings, one for each column; the list renderer will take
|
||||
// care of aligning the columns appropriately.
|
||||
getDisplayStrings func(startIdx int, endIdx int) [][]string
|
||||
// Alignment for each column. If nil, the default is left alignment
|
||||
getColumnAlignments func() []utils.Alignment
|
||||
// Function to insert non-model items (e.g. section headers). If nil, no
|
||||
// such items are inserted
|
||||
getNonModelItems func() []*NonModelItem
|
||||
|
||||
// The remaining fields are private and shouldn't be initialized by clients
|
||||
numNonModelItems int
|
||||
viewIndicesByModelIndex []int
|
||||
modelIndicesByViewIndex []int
|
||||
}
|
||||
|
||||
func (self *ListRenderer) GetList() types.IList {
|
||||
return self.list
|
||||
}
|
||||
|
||||
func (self *ListRenderer) ModelIndexToViewIndex(modelIndex int) int {
|
||||
modelIndex = lo.Clamp(modelIndex, 0, self.list.Len())
|
||||
if self.viewIndicesByModelIndex != nil {
|
||||
return self.viewIndicesByModelIndex[modelIndex]
|
||||
}
|
||||
|
||||
return modelIndex
|
||||
}
|
||||
|
||||
func (self *ListRenderer) ViewIndexToModelIndex(viewIndex int) int {
|
||||
viewIndex = utils.Clamp(viewIndex, 0, self.list.Len()+self.numNonModelItems)
|
||||
if self.modelIndicesByViewIndex != nil {
|
||||
return self.modelIndicesByViewIndex[viewIndex]
|
||||
}
|
||||
|
||||
return viewIndex
|
||||
}
|
||||
|
||||
// startIdx and endIdx are view indices, not model indices. If you want to
|
||||
// render the whole list, pass -1 for both.
|
||||
func (self *ListRenderer) renderLines(startIdx int, endIdx int) string {
|
||||
var columnAlignments []utils.Alignment
|
||||
if self.getColumnAlignments != nil {
|
||||
columnAlignments = self.getColumnAlignments()
|
||||
}
|
||||
nonModelItems := []*NonModelItem{}
|
||||
self.numNonModelItems = 0
|
||||
if self.getNonModelItems != nil {
|
||||
nonModelItems = self.getNonModelItems()
|
||||
self.prepareConversionArrays(nonModelItems)
|
||||
}
|
||||
startModelIdx := 0
|
||||
if startIdx == -1 {
|
||||
startIdx = 0
|
||||
} else {
|
||||
startModelIdx = self.ViewIndexToModelIndex(startIdx)
|
||||
}
|
||||
endModelIdx := self.list.Len()
|
||||
if endIdx == -1 {
|
||||
endIdx = endModelIdx + len(nonModelItems)
|
||||
} else {
|
||||
endModelIdx = self.ViewIndexToModelIndex(endIdx)
|
||||
}
|
||||
lines, columnPositions := utils.RenderDisplayStrings(
|
||||
self.getDisplayStrings(startModelIdx, endModelIdx),
|
||||
columnAlignments)
|
||||
lines = self.insertNonModelItems(nonModelItems, endIdx, startIdx, lines, columnPositions)
|
||||
return strings.Join(lines, "\n")
|
||||
}
|
||||
|
||||
func (self *ListRenderer) prepareConversionArrays(nonModelItems []*NonModelItem) {
|
||||
self.numNonModelItems = len(nonModelItems)
|
||||
self.viewIndicesByModelIndex = lo.Range(self.list.Len() + 1)
|
||||
self.modelIndicesByViewIndex = lo.Range(self.list.Len() + 1)
|
||||
offset := 0
|
||||
for _, item := range nonModelItems {
|
||||
for i := item.Index; i <= self.list.Len(); i++ {
|
||||
self.viewIndicesByModelIndex[i]++
|
||||
}
|
||||
self.modelIndicesByViewIndex = slices.Insert(
|
||||
self.modelIndicesByViewIndex, item.Index+offset, self.modelIndicesByViewIndex[item.Index+offset])
|
||||
offset++
|
||||
}
|
||||
}
|
||||
|
||||
func (self *ListRenderer) insertNonModelItems(
|
||||
nonModelItems []*NonModelItem, endIdx int, startIdx int, lines []string, columnPositions []int,
|
||||
) []string {
|
||||
offset := 0
|
||||
for _, item := range nonModelItems {
|
||||
if item.Index+offset >= endIdx {
|
||||
break
|
||||
}
|
||||
if item.Index+offset >= startIdx {
|
||||
padding := strings.Repeat(" ", columnPositions[item.Column])
|
||||
lines = slices.Insert(lines, item.Index+offset-startIdx, padding+item.Content)
|
||||
}
|
||||
offset++
|
||||
}
|
||||
return lines
|
||||
}
|
||||
@@ -0,0 +1,256 @@
|
||||
package context
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/samber/lo"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestListRenderer_renderLines(t *testing.T) {
|
||||
scenarios := []struct {
|
||||
name string
|
||||
modelStrings []string
|
||||
nonModelIndices []int
|
||||
startIdx int
|
||||
endIdx int
|
||||
expectedOutput string
|
||||
}{
|
||||
{
|
||||
name: "Render whole list",
|
||||
modelStrings: []string{"a", "b", "c"},
|
||||
startIdx: 0,
|
||||
endIdx: 3,
|
||||
expectedOutput: `
|
||||
a
|
||||
b
|
||||
c`,
|
||||
},
|
||||
{
|
||||
name: "Partial list, beginning",
|
||||
modelStrings: []string{"a", "b", "c"},
|
||||
startIdx: 0,
|
||||
endIdx: 2,
|
||||
expectedOutput: `
|
||||
a
|
||||
b`,
|
||||
},
|
||||
{
|
||||
name: "Partial list, end",
|
||||
modelStrings: []string{"a", "b", "c"},
|
||||
startIdx: 1,
|
||||
endIdx: 3,
|
||||
expectedOutput: `
|
||||
b
|
||||
c`,
|
||||
},
|
||||
{
|
||||
name: "Pass an endIdx greater than the model length",
|
||||
modelStrings: []string{"a", "b", "c"},
|
||||
startIdx: 2,
|
||||
endIdx: 5,
|
||||
expectedOutput: `
|
||||
c`,
|
||||
},
|
||||
{
|
||||
name: "Whole list with section headers",
|
||||
modelStrings: []string{"a", "b", "c"},
|
||||
nonModelIndices: []int{1, 3},
|
||||
startIdx: 0,
|
||||
endIdx: 5,
|
||||
expectedOutput: `
|
||||
a
|
||||
--- 1 (0) ---
|
||||
b
|
||||
c
|
||||
--- 3 (1) ---`,
|
||||
},
|
||||
{
|
||||
name: "Multiple consecutive headers",
|
||||
modelStrings: []string{"a", "b", "c"},
|
||||
nonModelIndices: []int{0, 0, 2, 2, 2},
|
||||
startIdx: 0,
|
||||
endIdx: 8,
|
||||
expectedOutput: `
|
||||
--- 0 (0) ---
|
||||
--- 0 (1) ---
|
||||
a
|
||||
b
|
||||
--- 2 (2) ---
|
||||
--- 2 (3) ---
|
||||
--- 2 (4) ---
|
||||
c`,
|
||||
},
|
||||
{
|
||||
name: "Partial list with headers, beginning",
|
||||
modelStrings: []string{"a", "b", "c"},
|
||||
nonModelIndices: []int{1, 3},
|
||||
startIdx: 0,
|
||||
endIdx: 3,
|
||||
expectedOutput: `
|
||||
a
|
||||
--- 1 (0) ---
|
||||
b`,
|
||||
},
|
||||
{
|
||||
name: "Partial list with headers, end (beyond end index)",
|
||||
modelStrings: []string{"a", "b", "c"},
|
||||
nonModelIndices: []int{1, 3},
|
||||
startIdx: 2,
|
||||
endIdx: 7,
|
||||
expectedOutput: `
|
||||
b
|
||||
c
|
||||
--- 3 (1) ---`,
|
||||
},
|
||||
}
|
||||
for _, s := range scenarios {
|
||||
t.Run(s.name, func(t *testing.T) {
|
||||
viewModel := NewListViewModel[string](func() []string { return s.modelStrings })
|
||||
var getNonModelItems func() []*NonModelItem
|
||||
if s.nonModelIndices != nil {
|
||||
getNonModelItems = func() []*NonModelItem {
|
||||
return lo.Map(s.nonModelIndices, func(modelIndex int, nonModelIndex int) *NonModelItem {
|
||||
return &NonModelItem{
|
||||
Index: modelIndex,
|
||||
Content: fmt.Sprintf("--- %d (%d) ---", modelIndex, nonModelIndex),
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
self := &ListRenderer{
|
||||
list: viewModel,
|
||||
getDisplayStrings: func(startIdx int, endIdx int) [][]string {
|
||||
return lo.Map(s.modelStrings[startIdx:endIdx],
|
||||
func(s string, _ int) []string { return []string{s} })
|
||||
},
|
||||
getNonModelItems: getNonModelItems,
|
||||
}
|
||||
|
||||
expectedOutput := strings.Join(lo.Map(
|
||||
strings.Split(strings.TrimPrefix(s.expectedOutput, "\n"), "\n"),
|
||||
func(line string, _ int) string { return strings.TrimSpace(line) }), "\n")
|
||||
|
||||
assert.Equal(t, expectedOutput, self.renderLines(s.startIdx, s.endIdx))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestListRenderer_ModelIndexToViewIndex_and_back(t *testing.T) {
|
||||
scenarios := []struct {
|
||||
name string
|
||||
numModelItems int
|
||||
nonModelIndices []int
|
||||
|
||||
modelIndices []int
|
||||
expectedViewIndices []int
|
||||
|
||||
viewIndices []int
|
||||
expectedModelIndices []int
|
||||
}{
|
||||
{
|
||||
name: "no headers (no getNonModelItems provided)",
|
||||
numModelItems: 3,
|
||||
nonModelIndices: nil, // no get
|
||||
|
||||
modelIndices: []int{-1, 0, 1, 2, 3, 4},
|
||||
expectedViewIndices: []int{0, 0, 1, 2, 3, 3},
|
||||
|
||||
viewIndices: []int{-1, 0, 1, 2, 3, 4},
|
||||
expectedModelIndices: []int{0, 0, 1, 2, 3, 3},
|
||||
},
|
||||
{
|
||||
name: "no headers (getNonModelItems returns zero items)",
|
||||
numModelItems: 3,
|
||||
nonModelIndices: []int{},
|
||||
|
||||
modelIndices: []int{-1, 0, 1, 2, 3, 4},
|
||||
expectedViewIndices: []int{0, 0, 1, 2, 3, 3},
|
||||
|
||||
viewIndices: []int{-1, 0, 1, 2, 3, 4},
|
||||
expectedModelIndices: []int{0, 0, 1, 2, 3, 3},
|
||||
},
|
||||
{
|
||||
name: "basic",
|
||||
numModelItems: 3,
|
||||
nonModelIndices: []int{1, 2},
|
||||
|
||||
/*
|
||||
0: model 0
|
||||
1: --- header 0 ---
|
||||
2: model 1
|
||||
3: --- header 1 ---
|
||||
4: model 2
|
||||
*/
|
||||
|
||||
modelIndices: []int{-1, 0, 1, 2, 3, 4},
|
||||
expectedViewIndices: []int{0, 0, 2, 4, 5, 5},
|
||||
|
||||
viewIndices: []int{-1, 0, 1, 2, 3, 4, 5, 6},
|
||||
expectedModelIndices: []int{0, 0, 1, 1, 2, 2, 3, 3},
|
||||
},
|
||||
{
|
||||
name: "consecutive section headers",
|
||||
numModelItems: 3,
|
||||
nonModelIndices: []int{0, 0, 2, 2, 2, 3, 3},
|
||||
|
||||
/*
|
||||
0: --- header 0 ---
|
||||
1: --- header 1 ---
|
||||
2: model 0
|
||||
3: model 1
|
||||
4: --- header 2 ---
|
||||
5: --- header 3 ---
|
||||
6: --- header 4 ---
|
||||
7: model 2
|
||||
8: --- header 5 ---
|
||||
9: --- header 6 ---
|
||||
*/
|
||||
modelIndices: []int{-1, 0, 1, 2, 3, 4},
|
||||
expectedViewIndices: []int{2, 2, 3, 7, 10, 10},
|
||||
|
||||
viewIndices: []int{-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11},
|
||||
expectedModelIndices: []int{0, 0, 0, 0, 1, 2, 2, 2, 2, 3, 3, 3, 3},
|
||||
},
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
t.Run(s.name, func(t *testing.T) {
|
||||
// Expect lists of equal length for each test:
|
||||
assert.Equal(t, len(s.modelIndices), len(s.expectedViewIndices))
|
||||
assert.Equal(t, len(s.viewIndices), len(s.expectedModelIndices))
|
||||
|
||||
modelInts := lo.Range(s.numModelItems)
|
||||
viewModel := NewListViewModel[int](func() []int { return modelInts })
|
||||
var getNonModelItems func() []*NonModelItem
|
||||
if s.nonModelIndices != nil {
|
||||
getNonModelItems = func() []*NonModelItem {
|
||||
return lo.Map(s.nonModelIndices, func(modelIndex int, _ int) *NonModelItem {
|
||||
return &NonModelItem{Index: modelIndex, Content: ""}
|
||||
})
|
||||
}
|
||||
}
|
||||
self := &ListRenderer{
|
||||
list: viewModel,
|
||||
getDisplayStrings: func(startIdx int, endIdx int) [][]string {
|
||||
return lo.Map(modelInts[startIdx:endIdx],
|
||||
func(i int, _ int) []string { return []string{fmt.Sprint(i)} })
|
||||
},
|
||||
getNonModelItems: getNonModelItems,
|
||||
}
|
||||
|
||||
// Need to render first so that it knows the non-model items
|
||||
self.renderLines(-1, -1)
|
||||
|
||||
for i := 0; i < len(s.modelIndices); i++ {
|
||||
assert.Equal(t, s.expectedViewIndices[i], self.ModelIndexToViewIndex(s.modelIndices[i]))
|
||||
}
|
||||
|
||||
for i := 0; i < len(s.viewIndices); i++ {
|
||||
assert.Equal(t, s.expectedModelIndices[i], self.ViewIndexToModelIndex(s.viewIndices[i]))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -27,7 +27,7 @@ func NewLocalCommitsContext(c *ContextCommon) *LocalCommitsContext {
|
||||
c,
|
||||
)
|
||||
|
||||
getDisplayStrings := func(startIdx int, length int) [][]string {
|
||||
getDisplayStrings := func(startIdx int, endIdx int) [][]string {
|
||||
selectedCommitSha := ""
|
||||
|
||||
if c.CurrentContext().GetKey() == LOCAL_COMMITS_CONTEXT_KEY {
|
||||
@@ -56,7 +56,7 @@ func NewLocalCommitsContext(c *ContextCommon) *LocalCommitsContext {
|
||||
c.UserConfig.Git.ParseEmoji,
|
||||
selectedCommitSha,
|
||||
startIdx,
|
||||
length,
|
||||
endIdx,
|
||||
shouldShowGraph(c),
|
||||
c.Model().BisectInfo,
|
||||
showYouAreHereLabel,
|
||||
@@ -68,14 +68,17 @@ func NewLocalCommitsContext(c *ContextCommon) *LocalCommitsContext {
|
||||
SearchTrait: NewSearchTrait(c),
|
||||
ListContextTrait: &ListContextTrait{
|
||||
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
|
||||
View: c.Views().Commits,
|
||||
WindowName: "commits",
|
||||
Key: LOCAL_COMMITS_CONTEXT_KEY,
|
||||
Kind: types.SIDE_CONTEXT,
|
||||
Focusable: true,
|
||||
View: c.Views().Commits,
|
||||
WindowName: "commits",
|
||||
Key: LOCAL_COMMITS_CONTEXT_KEY,
|
||||
Kind: types.SIDE_CONTEXT,
|
||||
Focusable: true,
|
||||
NeedsRerenderOnWidthChange: true,
|
||||
})),
|
||||
list: viewModel,
|
||||
getDisplayStrings: getDisplayStrings,
|
||||
ListRenderer: ListRenderer{
|
||||
list: viewModel,
|
||||
getDisplayStrings: getDisplayStrings,
|
||||
},
|
||||
c: c,
|
||||
refreshViewportOnChange: true,
|
||||
},
|
||||
|
||||
@@ -34,10 +34,13 @@ func NewMenuContext(
|
||||
Focusable: true,
|
||||
HasUncontrolledBounds: true,
|
||||
})),
|
||||
getDisplayStrings: viewModel.GetDisplayStrings,
|
||||
list: viewModel,
|
||||
c: c,
|
||||
getColumnAlignments: func() []utils.Alignment { return viewModel.columnAlignment },
|
||||
ListRenderer: ListRenderer{
|
||||
list: viewModel,
|
||||
getDisplayStrings: viewModel.GetDisplayStrings,
|
||||
getColumnAlignments: func() []utils.Alignment { return viewModel.columnAlignment },
|
||||
getNonModelItems: viewModel.GetNonModelItems,
|
||||
},
|
||||
c: c,
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -79,7 +82,7 @@ func (self *MenuViewModel) SetMenuItems(items []*types.MenuItem, columnAlignment
|
||||
}
|
||||
|
||||
// TODO: move into presentation package
|
||||
func (self *MenuViewModel) GetDisplayStrings(_startIdx int, _length int) [][]string {
|
||||
func (self *MenuViewModel) GetDisplayStrings(_ int, _ int) [][]string {
|
||||
menuItems := self.FilteredListViewModel.GetItems()
|
||||
showKeys := lo.SomeBy(menuItems, func(item *types.MenuItem) bool {
|
||||
return item.Key != nil
|
||||
@@ -87,6 +90,9 @@ func (self *MenuViewModel) GetDisplayStrings(_startIdx int, _length int) [][]str
|
||||
|
||||
return lo.Map(menuItems, func(item *types.MenuItem, _ int) []string {
|
||||
displayStrings := item.LabelColumns
|
||||
if item.DisabledReason != "" {
|
||||
displayStrings[0] = style.FgDefault.SetStrikethrough().Sprint(displayStrings[0])
|
||||
}
|
||||
|
||||
if !showKeys {
|
||||
return displayStrings
|
||||
@@ -111,6 +117,40 @@ func (self *MenuViewModel) GetDisplayStrings(_startIdx int, _length int) [][]str
|
||||
})
|
||||
}
|
||||
|
||||
func (self *MenuViewModel) GetNonModelItems() []*NonModelItem {
|
||||
// Don't display section headers when we are filtering. The reason is that
|
||||
// filtering changes the order of the items (they are sorted by best match),
|
||||
// so all the sections would be messed up.
|
||||
if self.FilteredListViewModel.IsFiltering() {
|
||||
return []*NonModelItem{}
|
||||
}
|
||||
|
||||
result := []*NonModelItem{}
|
||||
menuItems := self.FilteredListViewModel.GetItems()
|
||||
var prevSection *types.MenuSection = nil
|
||||
for i, menuItem := range menuItems {
|
||||
menuItem := menuItem
|
||||
if menuItem.Section != nil && menuItem.Section != prevSection {
|
||||
if prevSection != nil {
|
||||
result = append(result, &NonModelItem{
|
||||
Index: i,
|
||||
Column: 1,
|
||||
Content: "",
|
||||
})
|
||||
}
|
||||
|
||||
result = append(result, &NonModelItem{
|
||||
Index: i,
|
||||
Column: 1,
|
||||
Content: style.FgGreen.SetBold().Sprintf("--- %s ---", menuItem.Section.Title),
|
||||
})
|
||||
prevSection = menuItem.Section
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func (self *MenuContext) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding {
|
||||
basicBindings := self.ListContextTrait.GetKeybindings(opts)
|
||||
menuItemsWithKeys := lo.Filter(self.menuItems, func(item *types.MenuItem, _ int) bool {
|
||||
@@ -132,6 +172,10 @@ func (self *MenuContext) GetKeybindings(opts types.KeybindingsOpts) []*types.Bin
|
||||
}
|
||||
|
||||
func (self *MenuContext) OnMenuPress(selectedItem *types.MenuItem) error {
|
||||
if selectedItem != nil && selectedItem.DisabledReason != "" {
|
||||
return self.c.ErrorMsg(selectedItem.DisabledReason)
|
||||
}
|
||||
|
||||
if err := self.c.PopContext(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -109,8 +109,9 @@ func (self *PatchExplorerContext) FocusSelection() {
|
||||
_, viewHeight := view.Size()
|
||||
bufferHeight := viewHeight - 1
|
||||
_, origin := view.Origin()
|
||||
numLines := view.LinesHeight()
|
||||
|
||||
newOriginY := state.CalculateOrigin(origin, bufferHeight)
|
||||
newOriginY := state.CalculateOrigin(origin, bufferHeight, numLines)
|
||||
|
||||
_ = view.SetOriginY(newOriginY)
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ func NewReflogCommitsContext(c *ContextCommon) *ReflogCommitsContext {
|
||||
},
|
||||
)
|
||||
|
||||
getDisplayStrings := func(startIdx int, length int) [][]string {
|
||||
getDisplayStrings := func(_ int, _ int) [][]string {
|
||||
return presentation.GetReflogCommitListDisplayStrings(
|
||||
viewModel.GetItems(),
|
||||
c.State().GetRepoState().GetScreenMode() != types.SCREEN_NORMAL,
|
||||
@@ -43,15 +43,18 @@ func NewReflogCommitsContext(c *ContextCommon) *ReflogCommitsContext {
|
||||
FilteredListViewModel: viewModel,
|
||||
ListContextTrait: &ListContextTrait{
|
||||
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
|
||||
View: c.Views().ReflogCommits,
|
||||
WindowName: "commits",
|
||||
Key: REFLOG_COMMITS_CONTEXT_KEY,
|
||||
Kind: types.SIDE_CONTEXT,
|
||||
Focusable: true,
|
||||
View: c.Views().ReflogCommits,
|
||||
WindowName: "commits",
|
||||
Key: REFLOG_COMMITS_CONTEXT_KEY,
|
||||
Kind: types.SIDE_CONTEXT,
|
||||
Focusable: true,
|
||||
NeedsRerenderOnWidthChange: true,
|
||||
})),
|
||||
list: viewModel,
|
||||
getDisplayStrings: getDisplayStrings,
|
||||
c: c,
|
||||
ListRenderer: ListRenderer{
|
||||
list: viewModel,
|
||||
getDisplayStrings: getDisplayStrings,
|
||||
},
|
||||
c: c,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ func NewRemoteBranchesContext(
|
||||
},
|
||||
)
|
||||
|
||||
getDisplayStrings := func(startIdx int, length int) [][]string {
|
||||
getDisplayStrings := func(_ int, _ int) [][]string {
|
||||
return presentation.GetRemoteBranchListDisplayStrings(viewModel.GetItems(), c.Modes().Diffing.Ref)
|
||||
}
|
||||
|
||||
@@ -36,16 +36,19 @@ func NewRemoteBranchesContext(
|
||||
DynamicTitleBuilder: NewDynamicTitleBuilder(c.Tr.RemoteBranchesDynamicTitle),
|
||||
ListContextTrait: &ListContextTrait{
|
||||
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
|
||||
View: c.Views().RemoteBranches,
|
||||
WindowName: "branches",
|
||||
Key: REMOTE_BRANCHES_CONTEXT_KEY,
|
||||
Kind: types.SIDE_CONTEXT,
|
||||
Focusable: true,
|
||||
Transient: true,
|
||||
View: c.Views().RemoteBranches,
|
||||
WindowName: "branches",
|
||||
Key: REMOTE_BRANCHES_CONTEXT_KEY,
|
||||
Kind: types.SIDE_CONTEXT,
|
||||
Focusable: true,
|
||||
Transient: true,
|
||||
NeedsRerenderOnWidthChange: true,
|
||||
})),
|
||||
list: viewModel,
|
||||
getDisplayStrings: getDisplayStrings,
|
||||
c: c,
|
||||
ListRenderer: ListRenderer{
|
||||
list: viewModel,
|
||||
getDisplayStrings: getDisplayStrings,
|
||||
},
|
||||
c: c,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ func NewRemotesContext(c *ContextCommon) *RemotesContext {
|
||||
},
|
||||
)
|
||||
|
||||
getDisplayStrings := func(startIdx int, length int) [][]string {
|
||||
getDisplayStrings := func(_ int, _ int) [][]string {
|
||||
return presentation.GetRemoteListDisplayStrings(viewModel.GetItems(), c.Modes().Diffing.Ref)
|
||||
}
|
||||
|
||||
@@ -38,9 +38,11 @@ func NewRemotesContext(c *ContextCommon) *RemotesContext {
|
||||
Kind: types.SIDE_CONTEXT,
|
||||
Focusable: true,
|
||||
})),
|
||||
list: viewModel,
|
||||
getDisplayStrings: getDisplayStrings,
|
||||
c: c,
|
||||
ListRenderer: ListRenderer{
|
||||
list: viewModel,
|
||||
getDisplayStrings: getDisplayStrings,
|
||||
},
|
||||
c: c,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,12 +9,16 @@ import (
|
||||
|
||||
type SearchTrait struct {
|
||||
c *ContextCommon
|
||||
*SearchHistory
|
||||
|
||||
searchString string
|
||||
}
|
||||
|
||||
func NewSearchTrait(c *ContextCommon) *SearchTrait {
|
||||
return &SearchTrait{c: c}
|
||||
return &SearchTrait{
|
||||
c: c,
|
||||
SearchHistory: NewSearchHistory(),
|
||||
}
|
||||
}
|
||||
|
||||
func (self *SearchTrait) GetSearchString() string {
|
||||
@@ -33,20 +37,32 @@ func (self *SearchTrait) ClearSearchString() {
|
||||
func (self *SearchTrait) IsSearchableContext() {}
|
||||
|
||||
func (self *SearchTrait) onSelectItemWrapper(innerFunc func(int) error) func(int, int, int) error {
|
||||
return func(selectedLineIdx int, index int, total int) error {
|
||||
self.RenderSearchStatus(index, total)
|
||||
|
||||
if total != 0 {
|
||||
if err := innerFunc(selectedLineIdx); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (self *SearchTrait) RenderSearchStatus(index int, total int) {
|
||||
keybindingConfig := self.c.UserConfig.Keybinding
|
||||
|
||||
return func(y int, index int, total int) error {
|
||||
if total == 0 {
|
||||
self.c.SetViewContent(
|
||||
self.c.Views().Search,
|
||||
fmt.Sprintf(
|
||||
self.c.Tr.NoMatchesFor,
|
||||
self.searchString,
|
||||
theme.OptionsFgColor.Sprintf(self.c.Tr.ExitSearchMode, keybindings.Label(keybindingConfig.Universal.Return)),
|
||||
),
|
||||
)
|
||||
return nil
|
||||
}
|
||||
if total == 0 {
|
||||
self.c.SetViewContent(
|
||||
self.c.Views().Search,
|
||||
fmt.Sprintf(
|
||||
self.c.Tr.NoMatchesFor,
|
||||
self.searchString,
|
||||
theme.OptionsFgColor.Sprintf(self.c.Tr.ExitSearchMode, keybindings.Label(keybindingConfig.Universal.Return)),
|
||||
),
|
||||
)
|
||||
} else {
|
||||
self.c.SetViewContent(
|
||||
self.c.Views().Search,
|
||||
fmt.Sprintf(
|
||||
@@ -62,10 +78,6 @@ func (self *SearchTrait) onSelectItemWrapper(innerFunc func(int) error) func(int
|
||||
),
|
||||
),
|
||||
)
|
||||
if err := innerFunc(y); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ func NewStashContext(
|
||||
},
|
||||
)
|
||||
|
||||
getDisplayStrings := func(startIdx int, length int) [][]string {
|
||||
getDisplayStrings := func(_ int, _ int) [][]string {
|
||||
return presentation.GetStashEntryListDisplayStrings(viewModel.GetItems(), c.Modes().Diffing.Ref)
|
||||
}
|
||||
|
||||
@@ -40,9 +40,11 @@ func NewStashContext(
|
||||
Kind: types.SIDE_CONTEXT,
|
||||
Focusable: true,
|
||||
})),
|
||||
list: viewModel,
|
||||
getDisplayStrings: getDisplayStrings,
|
||||
c: c,
|
||||
ListRenderer: ListRenderer{
|
||||
list: viewModel,
|
||||
getDisplayStrings: getDisplayStrings,
|
||||
},
|
||||
c: c,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"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"
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
type SubCommitsContext struct {
|
||||
@@ -36,7 +36,7 @@ func NewSubCommitsContext(
|
||||
limitCommits: true,
|
||||
}
|
||||
|
||||
getDisplayStrings := func(startIdx int, length int) [][]string {
|
||||
getDisplayStrings := func(startIdx int, endIdx int) [][]string {
|
||||
// This can happen if a sub-commits view is asked to be rerendered while
|
||||
// it is invisble; for example when switching screen modes, which
|
||||
// rerenders all views.
|
||||
@@ -72,13 +72,42 @@ func NewSubCommitsContext(
|
||||
c.UserConfig.Git.ParseEmoji,
|
||||
selectedCommitSha,
|
||||
startIdx,
|
||||
length,
|
||||
shouldShowGraph(c),
|
||||
endIdx,
|
||||
// Don't show the graph in the left/right view; we'd like to, but
|
||||
// it's too complicated:
|
||||
shouldShowGraph(c) && viewModel.GetRefToShowDivergenceFrom() == "",
|
||||
git_commands.NewNullBisectInfo(),
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
||||
getNonModelItems := func() []*NonModelItem {
|
||||
result := []*NonModelItem{}
|
||||
if viewModel.GetRefToShowDivergenceFrom() != "" {
|
||||
_, upstreamIdx, found := lo.FindIndexOf(
|
||||
c.Model().SubCommits, func(c *models.Commit) bool { return c.Divergence == models.DivergenceRight })
|
||||
if !found {
|
||||
upstreamIdx = 0
|
||||
}
|
||||
result = append(result, &NonModelItem{
|
||||
Index: upstreamIdx,
|
||||
Content: fmt.Sprintf("--- %s ---", c.Tr.DivergenceSectionHeaderRemote),
|
||||
})
|
||||
|
||||
_, localIdx, found := lo.FindIndexOf(
|
||||
c.Model().SubCommits, func(c *models.Commit) bool { return c.Divergence == models.DivergenceLeft })
|
||||
if !found {
|
||||
localIdx = len(c.Model().SubCommits)
|
||||
}
|
||||
result = append(result, &NonModelItem{
|
||||
Index: localIdx,
|
||||
Content: fmt.Sprintf("--- %s ---", c.Tr.DivergenceSectionHeaderLocal),
|
||||
})
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
ctx := &SubCommitsContext{
|
||||
c: c,
|
||||
SubCommitsViewModel: viewModel,
|
||||
@@ -86,15 +115,19 @@ func NewSubCommitsContext(
|
||||
DynamicTitleBuilder: NewDynamicTitleBuilder(c.Tr.SubCommitsDynamicTitle),
|
||||
ListContextTrait: &ListContextTrait{
|
||||
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
|
||||
View: c.Views().SubCommits,
|
||||
WindowName: "branches",
|
||||
Key: SUB_COMMITS_CONTEXT_KEY,
|
||||
Kind: types.SIDE_CONTEXT,
|
||||
Focusable: true,
|
||||
Transient: true,
|
||||
View: c.Views().SubCommits,
|
||||
WindowName: "branches",
|
||||
Key: SUB_COMMITS_CONTEXT_KEY,
|
||||
Kind: types.SIDE_CONTEXT,
|
||||
Focusable: true,
|
||||
Transient: true,
|
||||
NeedsRerenderOnWidthChange: true,
|
||||
})),
|
||||
list: viewModel,
|
||||
getDisplayStrings: getDisplayStrings,
|
||||
ListRenderer: ListRenderer{
|
||||
list: viewModel,
|
||||
getDisplayStrings: getDisplayStrings,
|
||||
getNonModelItems: getNonModelItems,
|
||||
},
|
||||
c: c,
|
||||
refreshViewportOnChange: true,
|
||||
},
|
||||
@@ -110,7 +143,8 @@ func NewSubCommitsContext(
|
||||
|
||||
type SubCommitsViewModel struct {
|
||||
// name of the ref that the sub-commits are shown for
|
||||
ref types.Ref
|
||||
ref types.Ref
|
||||
refToShowDivergenceFrom string
|
||||
*ListViewModel[*models.Commit]
|
||||
|
||||
limitCommits bool
|
||||
@@ -125,6 +159,14 @@ func (self *SubCommitsViewModel) GetRef() types.Ref {
|
||||
return self.ref
|
||||
}
|
||||
|
||||
func (self *SubCommitsViewModel) SetRefToShowDivergenceFrom(ref string) {
|
||||
self.refToShowDivergenceFrom = ref
|
||||
}
|
||||
|
||||
func (self *SubCommitsViewModel) GetRefToShowDivergenceFrom() string {
|
||||
return self.refToShowDivergenceFrom
|
||||
}
|
||||
|
||||
func (self *SubCommitsViewModel) SetShowBranchHeads(value bool) {
|
||||
self.showBranchHeads = value
|
||||
}
|
||||
@@ -158,10 +200,6 @@ func (self *SubCommitsContext) GetCommits() []*models.Commit {
|
||||
return self.getModel()
|
||||
}
|
||||
|
||||
func (self *SubCommitsContext) Title() string {
|
||||
return fmt.Sprintf(self.c.Tr.SubCommitsDynamicTitle, utils.TruncateWithEllipsis(self.ref.RefName(), 50))
|
||||
}
|
||||
|
||||
func (self *SubCommitsContext) SetLimitCommits(value bool) {
|
||||
self.limitCommits = value
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ func NewSubmodulesContext(c *ContextCommon) *SubmodulesContext {
|
||||
},
|
||||
)
|
||||
|
||||
getDisplayStrings := func(startIdx int, length int) [][]string {
|
||||
getDisplayStrings := func(_ int, _ int) [][]string {
|
||||
return presentation.GetSubmoduleListDisplayStrings(viewModel.GetItems())
|
||||
}
|
||||
|
||||
@@ -35,9 +35,11 @@ func NewSubmodulesContext(c *ContextCommon) *SubmodulesContext {
|
||||
Kind: types.SIDE_CONTEXT,
|
||||
Focusable: true,
|
||||
})),
|
||||
list: viewModel,
|
||||
getDisplayStrings: getDisplayStrings,
|
||||
c: c,
|
||||
ListRenderer: ListRenderer{
|
||||
list: viewModel,
|
||||
getDisplayStrings: getDisplayStrings,
|
||||
},
|
||||
c: c,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ func NewSuggestionsContext(
|
||||
return state.Suggestions
|
||||
}
|
||||
|
||||
getDisplayStrings := func(startIdx int, length int) [][]string {
|
||||
getDisplayStrings := func(_ int, _ int) [][]string {
|
||||
return presentation.GetSuggestionListDisplayStrings(state.Suggestions)
|
||||
}
|
||||
|
||||
@@ -54,9 +54,11 @@ func NewSuggestionsContext(
|
||||
Focusable: true,
|
||||
HasUncontrolledBounds: true,
|
||||
})),
|
||||
list: viewModel,
|
||||
getDisplayStrings: getDisplayStrings,
|
||||
c: c,
|
||||
ListRenderer: ListRenderer{
|
||||
list: viewModel,
|
||||
getDisplayStrings: getDisplayStrings,
|
||||
},
|
||||
c: c,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,8 +26,11 @@ func NewTagsContext(
|
||||
},
|
||||
)
|
||||
|
||||
getDisplayStrings := func(startIdx int, length int) [][]string {
|
||||
return presentation.GetTagListDisplayStrings(viewModel.GetItems(), c.Modes().Diffing.Ref)
|
||||
getDisplayStrings := func(_ int, _ int) [][]string {
|
||||
return presentation.GetTagListDisplayStrings(
|
||||
viewModel.GetItems(),
|
||||
c.State().GetItemOperation,
|
||||
c.Modes().Diffing.Ref, c.Tr)
|
||||
}
|
||||
|
||||
return &TagsContext{
|
||||
@@ -40,9 +43,11 @@ func NewTagsContext(
|
||||
Kind: types.SIDE_CONTEXT,
|
||||
Focusable: true,
|
||||
})),
|
||||
list: viewModel,
|
||||
getDisplayStrings: getDisplayStrings,
|
||||
c: c,
|
||||
ListRenderer: ListRenderer{
|
||||
list: viewModel,
|
||||
getDisplayStrings: getDisplayStrings,
|
||||
},
|
||||
c: c,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ func NewWorkingTreeContext(c *ContextCommon) *WorkingTreeContext {
|
||||
c.UserConfig.Gui.ShowFileTree,
|
||||
)
|
||||
|
||||
getDisplayStrings := func(startIdx int, length int) [][]string {
|
||||
getDisplayStrings := func(_ int, _ int) [][]string {
|
||||
lines := presentation.RenderFileTree(viewModel, c.Modes().Diffing.Ref, c.Model().Submodules)
|
||||
return lo.Map(lines, func(line string, _ int) []string {
|
||||
return []string{line}
|
||||
@@ -41,9 +41,11 @@ func NewWorkingTreeContext(c *ContextCommon) *WorkingTreeContext {
|
||||
Kind: types.SIDE_CONTEXT,
|
||||
Focusable: true,
|
||||
})),
|
||||
list: viewModel,
|
||||
getDisplayStrings: getDisplayStrings,
|
||||
c: c,
|
||||
ListRenderer: ListRenderer{
|
||||
list: viewModel,
|
||||
getDisplayStrings: getDisplayStrings,
|
||||
},
|
||||
c: c,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ func NewWorktreesContext(c *ContextCommon) *WorktreesContext {
|
||||
},
|
||||
)
|
||||
|
||||
getDisplayStrings := func(startIdx int, length int) [][]string {
|
||||
getDisplayStrings := func(_ int, _ int) [][]string {
|
||||
return presentation.GetWorktreeDisplayStrings(
|
||||
c.Tr,
|
||||
viewModel.GetFilteredList(),
|
||||
@@ -38,9 +38,11 @@ func NewWorktreesContext(c *ContextCommon) *WorktreesContext {
|
||||
Kind: types.SIDE_CONTEXT,
|
||||
Focusable: true,
|
||||
})),
|
||||
list: viewModel,
|
||||
getDisplayStrings: getDisplayStrings,
|
||||
c: c,
|
||||
ListRenderer: ListRenderer{
|
||||
list: viewModel,
|
||||
getDisplayStrings: getDisplayStrings,
|
||||
},
|
||||
c: c,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
+15
-10
@@ -47,6 +47,7 @@ func (gui *Gui) resetHelpersAndControllers() {
|
||||
patchBuildingHelper := helpers.NewPatchBuildingHelper(helperCommon)
|
||||
stagingHelper := helpers.NewStagingHelper(helperCommon)
|
||||
mergeConflictsHelper := helpers.NewMergeConflictsHelper(helperCommon)
|
||||
searchHelper := helpers.NewSearchHelper(helperCommon)
|
||||
|
||||
refreshHelper := helpers.NewRefreshHelper(
|
||||
helperCommon,
|
||||
@@ -56,6 +57,7 @@ func (gui *Gui) resetHelpersAndControllers() {
|
||||
stagingHelper,
|
||||
mergeConflictsHelper,
|
||||
worktreeHelper,
|
||||
searchHelper,
|
||||
)
|
||||
diffHelper := helpers.NewDiffHelper(helperCommon)
|
||||
cherryPickHelper := helpers.NewCherryPickHelper(
|
||||
@@ -76,6 +78,13 @@ func (gui *Gui) resetHelpersAndControllers() {
|
||||
helperCommon,
|
||||
func() *status.StatusManager { return gui.statusManager },
|
||||
)
|
||||
|
||||
setSubCommits := func(commits []*models.Commit) {
|
||||
gui.Mutexes.SubCommitsMutex.Lock()
|
||||
defer gui.Mutexes.SubCommitsMutex.Unlock()
|
||||
|
||||
gui.State.Model.SubCommits = commits
|
||||
}
|
||||
gui.helpers = &helpers.Helpers{
|
||||
Refs: refsHelper,
|
||||
Host: helpers.NewHostHelper(helperCommon),
|
||||
@@ -86,6 +95,7 @@ func (gui *Gui) resetHelpersAndControllers() {
|
||||
Files: helpers.NewFilesHelper(helperCommon),
|
||||
WorkingTree: helpers.NewWorkingTreeHelper(helperCommon, refsHelper, commitsHelper, gpgHelper),
|
||||
Tags: helpers.NewTagsHelper(helperCommon, commitsHelper),
|
||||
BranchesHelper: helpers.NewBranchesHelper(helperCommon),
|
||||
GPG: helpers.NewGpgHelper(helperCommon),
|
||||
MergeAndRebase: rebaseHelper,
|
||||
MergeConflicts: mergeConflictsHelper,
|
||||
@@ -104,14 +114,16 @@ func (gui *Gui) resetHelpersAndControllers() {
|
||||
Confirmation: helpers.NewConfirmationHelper(helperCommon),
|
||||
Mode: modeHelper,
|
||||
AppStatus: appStatusHelper,
|
||||
InlineStatus: helpers.NewInlineStatusHelper(helperCommon),
|
||||
WindowArrangement: helpers.NewWindowArrangementHelper(
|
||||
gui.c,
|
||||
windowHelper,
|
||||
modeHelper,
|
||||
appStatusHelper,
|
||||
),
|
||||
Search: helpers.NewSearchHelper(helperCommon),
|
||||
Worktree: worktreeHelper,
|
||||
Search: searchHelper,
|
||||
Worktree: worktreeHelper,
|
||||
SubCommits: helpers.NewSubCommitsHelper(helperCommon, refreshHelper, setSubCommits),
|
||||
}
|
||||
|
||||
gui.CustomCommandsClient = custom_commands.NewClient(
|
||||
@@ -205,13 +217,6 @@ func (gui *Gui) resetHelpersAndControllers() {
|
||||
controllers.AttachControllers(context, sideWindowControllerFactory.Create(context))
|
||||
}
|
||||
|
||||
setSubCommits := func(commits []*models.Commit) {
|
||||
gui.Mutexes.SubCommitsMutex.Lock()
|
||||
defer gui.Mutexes.SubCommitsMutex.Unlock()
|
||||
|
||||
gui.State.Model.SubCommits = commits
|
||||
}
|
||||
|
||||
for _, context := range []controllers.CanSwitchToSubCommits{
|
||||
gui.State.Contexts.Branches,
|
||||
gui.State.Contexts.RemoteBranches,
|
||||
@@ -219,7 +224,7 @@ func (gui *Gui) resetHelpersAndControllers() {
|
||||
gui.State.Contexts.ReflogCommits,
|
||||
} {
|
||||
controllers.AttachControllers(context, controllers.NewSwitchToSubCommitsController(
|
||||
common, setSubCommits, context,
|
||||
common, context,
|
||||
))
|
||||
}
|
||||
|
||||
|
||||
@@ -152,7 +152,7 @@ func (self *BasicCommitsController) copyCommitSHAToClipboard(commit *models.Comm
|
||||
func (self *BasicCommitsController) copyCommitURLToClipboard(commit *models.Commit) error {
|
||||
url, err := self.c.Helpers().Host.GetCommitURL(commit.Sha)
|
||||
if err != nil {
|
||||
return err
|
||||
return self.c.Error(err)
|
||||
}
|
||||
|
||||
self.c.LogAction(self.c.Tr.Actions.CopyCommitURLToClipboard)
|
||||
|
||||
@@ -9,8 +9,10 @@ import (
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/context"
|
||||
"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 BranchesController struct {
|
||||
@@ -32,9 +34,10 @@ func NewBranchesController(
|
||||
func (self *BranchesController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding {
|
||||
return []*types.Binding{
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Universal.Select),
|
||||
Handler: self.checkSelected(self.press),
|
||||
Description: self.c.Tr.Checkout,
|
||||
Key: opts.GetKey(opts.Config.Universal.Select),
|
||||
Handler: self.checkSelected(self.press),
|
||||
GetDisabledReason: self.getDisabledReasonForPress,
|
||||
Description: self.c.Tr.Checkout,
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Universal.New),
|
||||
@@ -70,12 +73,14 @@ func (self *BranchesController) GetKeybindings(opts types.KeybindingsOpts) []*ty
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Universal.Remove),
|
||||
Handler: self.checkSelectedAndReal(self.delete),
|
||||
Description: self.c.Tr.DeleteBranch,
|
||||
Description: self.c.Tr.ViewDeleteOptions,
|
||||
OpensMenu: true,
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Branches.RebaseBranch),
|
||||
Handler: opts.Guards.OutsideFilterMode(self.rebase),
|
||||
Description: self.c.Tr.RebaseBranch,
|
||||
Key: opts.GetKey(opts.Config.Branches.RebaseBranch),
|
||||
Handler: opts.Guards.OutsideFilterMode(self.rebase),
|
||||
Description: self.c.Tr.RebaseBranch,
|
||||
GetDisabledReason: self.getDisabledReasonForRebase,
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Branches.MergeIntoCurrentBranch),
|
||||
@@ -105,8 +110,9 @@ func (self *BranchesController) GetKeybindings(opts types.KeybindingsOpts) []*ty
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Branches.SetUpstream),
|
||||
Handler: self.checkSelected(self.setUpstream),
|
||||
Description: self.c.Tr.SetUnsetUpstream,
|
||||
Handler: self.checkSelected(self.viewUpstreamOptions),
|
||||
Description: self.c.Tr.ViewBranchUpstreamOptions,
|
||||
Tooltip: self.c.Tr.ViewBranchUpstreamOptionsTooltip,
|
||||
OpensMenu: true,
|
||||
},
|
||||
}
|
||||
@@ -136,56 +142,139 @@ func (self *BranchesController) GetOnRenderToMain() func() error {
|
||||
}
|
||||
}
|
||||
|
||||
func (self *BranchesController) setUpstream(selectedBranch *models.Branch) error {
|
||||
return self.c.Menu(types.CreateMenuOptions{
|
||||
Title: self.c.Tr.Actions.SetUnsetUpstream,
|
||||
Items: []*types.MenuItem{
|
||||
{
|
||||
LabelColumns: []string{self.c.Tr.UnsetUpstream},
|
||||
OnPress: func() error {
|
||||
if err := self.c.Git().Branch.UnsetUpstream(selectedBranch.Name); err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
if err := self.c.Refresh(types.RefreshOptions{
|
||||
Mode: types.SYNC,
|
||||
Scope: []types.RefreshableView{
|
||||
types.BRANCHES,
|
||||
types.COMMITS,
|
||||
},
|
||||
}); err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
Key: 'u',
|
||||
},
|
||||
{
|
||||
LabelColumns: []string{self.c.Tr.SetUpstream},
|
||||
OnPress: func() error {
|
||||
return self.c.Helpers().Upstream.PromptForUpstreamWithoutInitialContent(selectedBranch, func(upstream string) error {
|
||||
upstreamRemote, upstreamBranch, err := self.c.Helpers().Upstream.ParseUpstream(upstream)
|
||||
if err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
func (self *BranchesController) viewUpstreamOptions(selectedBranch *models.Branch) error {
|
||||
viewDivergenceItem := &types.MenuItem{
|
||||
LabelColumns: []string{self.c.Tr.ViewDivergenceFromUpstream},
|
||||
OnPress: func() error {
|
||||
branch := self.context().GetSelected()
|
||||
if branch == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := self.c.Git().Branch.SetUpstream(upstreamRemote, upstreamBranch, selectedBranch.Name); err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
if err := self.c.Refresh(types.RefreshOptions{
|
||||
Mode: types.SYNC,
|
||||
Scope: []types.RefreshableView{
|
||||
types.BRANCHES,
|
||||
types.COMMITS,
|
||||
},
|
||||
}); err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
},
|
||||
Key: 's',
|
||||
},
|
||||
return self.c.Helpers().SubCommits.ViewSubCommits(helpers.ViewSubCommitsOpts{
|
||||
Ref: branch,
|
||||
TitleRef: fmt.Sprintf("%s <-> %s", branch.RefName(), branch.ShortUpstreamRefName()),
|
||||
RefToShowDivergenceFrom: branch.FullUpstreamRefName(),
|
||||
Context: self.context(),
|
||||
ShowBranchHeads: false,
|
||||
})
|
||||
},
|
||||
Key: 'v',
|
||||
}
|
||||
|
||||
unsetUpstreamItem := &types.MenuItem{
|
||||
LabelColumns: []string{self.c.Tr.UnsetUpstream},
|
||||
OnPress: func() error {
|
||||
if err := self.c.Git().Branch.UnsetUpstream(selectedBranch.Name); err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
if err := self.c.Refresh(types.RefreshOptions{
|
||||
Mode: types.SYNC,
|
||||
Scope: []types.RefreshableView{
|
||||
types.BRANCHES,
|
||||
types.COMMITS,
|
||||
},
|
||||
}); err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
Key: 'u',
|
||||
}
|
||||
|
||||
setUpstreamItem := &types.MenuItem{
|
||||
LabelColumns: []string{self.c.Tr.SetUpstream},
|
||||
OnPress: func() error {
|
||||
return self.c.Helpers().Upstream.PromptForUpstreamWithoutInitialContent(selectedBranch, func(upstream string) error {
|
||||
upstreamRemote, upstreamBranch, err := self.c.Helpers().Upstream.ParseUpstream(upstream)
|
||||
if err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
|
||||
if err := self.c.Git().Branch.SetUpstream(upstreamRemote, upstreamBranch, selectedBranch.Name); err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
if err := self.c.Refresh(types.RefreshOptions{
|
||||
Mode: types.SYNC,
|
||||
Scope: []types.RefreshableView{
|
||||
types.BRANCHES,
|
||||
types.COMMITS,
|
||||
},
|
||||
}); err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
},
|
||||
Key: 's',
|
||||
}
|
||||
|
||||
upstream := lo.Ternary(selectedBranch.RemoteBranchStoredLocally(),
|
||||
fmt.Sprintf("%s/%s", selectedBranch.UpstreamRemote, selectedBranch.Name),
|
||||
self.c.Tr.UpstreamGenericName)
|
||||
upstreamResetOptions := utils.ResolvePlaceholderString(
|
||||
self.c.Tr.ViewUpstreamResetOptions,
|
||||
map[string]string{"upstream": upstream},
|
||||
)
|
||||
upstreamResetTooltip := utils.ResolvePlaceholderString(
|
||||
self.c.Tr.ViewUpstreamResetOptionsTooltip,
|
||||
map[string]string{"upstream": upstream},
|
||||
)
|
||||
|
||||
upstreamRebaseOptions := utils.ResolvePlaceholderString(
|
||||
self.c.Tr.ViewUpstreamRebaseOptions,
|
||||
map[string]string{"upstream": upstream},
|
||||
)
|
||||
upstreamRebaseTooltip := utils.ResolvePlaceholderString(
|
||||
self.c.Tr.ViewUpstreamRebaseOptionsTooltip,
|
||||
map[string]string{"upstream": upstream},
|
||||
)
|
||||
|
||||
upstreamResetItem := &types.MenuItem{
|
||||
LabelColumns: []string{upstreamResetOptions},
|
||||
OpensMenu: true,
|
||||
OnPress: func() error {
|
||||
err := self.c.Helpers().Refs.CreateGitResetMenu(upstream)
|
||||
if err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
Tooltip: upstreamResetTooltip,
|
||||
Key: 'g',
|
||||
}
|
||||
|
||||
upstreamRebaseItem := &types.MenuItem{
|
||||
LabelColumns: []string{upstreamRebaseOptions},
|
||||
OpensMenu: true,
|
||||
OnPress: func() error {
|
||||
if err := self.c.Helpers().MergeAndRebase.RebaseOntoRef(selectedBranch.ShortUpstreamRefName()); err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
Tooltip: upstreamRebaseTooltip,
|
||||
Key: 'r',
|
||||
}
|
||||
|
||||
if !selectedBranch.RemoteBranchStoredLocally() {
|
||||
viewDivergenceItem.DisabledReason = self.c.Tr.UpstreamNotSetError
|
||||
unsetUpstreamItem.DisabledReason = self.c.Tr.UpstreamNotSetError
|
||||
upstreamResetItem.DisabledReason = self.c.Tr.UpstreamNotSetError
|
||||
upstreamRebaseItem.DisabledReason = self.c.Tr.UpstreamNotSetError
|
||||
}
|
||||
|
||||
options := []*types.MenuItem{
|
||||
viewDivergenceItem,
|
||||
unsetUpstreamItem,
|
||||
setUpstreamItem,
|
||||
upstreamResetItem,
|
||||
upstreamRebaseItem,
|
||||
}
|
||||
|
||||
return self.c.Menu(types.CreateMenuOptions{
|
||||
Title: self.c.Tr.BranchUpstreamOptionsTitle,
|
||||
Items: options,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -211,6 +300,18 @@ func (self *BranchesController) press(selectedBranch *models.Branch) error {
|
||||
return self.c.Helpers().Refs.CheckoutRef(selectedBranch.Name, types.CheckoutRefOptions{})
|
||||
}
|
||||
|
||||
func (self *BranchesController) getDisabledReasonForPress() string {
|
||||
currentBranch := self.c.Helpers().Refs.GetCheckedOutRef()
|
||||
if currentBranch != nil {
|
||||
op := self.c.State().GetItemOperation(currentBranch)
|
||||
if op == types.ItemOperationFastForwarding || op == types.ItemOperationPulling {
|
||||
return self.c.Tr.CantCheckoutBranchWhilePulling
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func (self *BranchesController) worktreeForBranch(branch *models.Branch) (*models.Worktree, bool) {
|
||||
return git_commands.WorktreeForBranch(branch, self.c.Model().Worktrees)
|
||||
}
|
||||
@@ -230,7 +331,10 @@ func (self *BranchesController) promptToCheckoutWorktree(worktree *models.Worktr
|
||||
}
|
||||
|
||||
func (self *BranchesController) handleCreatePullRequest(selectedBranch *models.Branch) error {
|
||||
return self.createPullRequest(selectedBranch.Name, "")
|
||||
if !selectedBranch.IsTrackingRemote() {
|
||||
return self.c.ErrorMsg(self.c.Tr.PullRequestNoUpstream)
|
||||
}
|
||||
return self.createPullRequest(selectedBranch.UpstreamBranch, "")
|
||||
}
|
||||
|
||||
func (self *BranchesController) handleCreatePullRequestMenu(selectedBranch *models.Branch) error {
|
||||
@@ -316,19 +420,6 @@ func (self *BranchesController) createNewBranchWithName(newBranchName string) er
|
||||
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
|
||||
}
|
||||
|
||||
func (self *BranchesController) delete(branch *models.Branch) error {
|
||||
checkedOutBranch := self.c.Helpers().Refs.GetCheckedOutRef()
|
||||
if checkedOutBranch.Name == branch.Name {
|
||||
return self.c.ErrorMsg(self.c.Tr.CantDeleteCheckOutBranch)
|
||||
}
|
||||
|
||||
if self.checkedOutByOtherWorktree(branch) {
|
||||
return self.promptWorktreeBranchDelete(branch)
|
||||
}
|
||||
|
||||
return self.deleteWithForce(branch, false)
|
||||
}
|
||||
|
||||
func (self *BranchesController) checkedOutByOtherWorktree(branch *models.Branch) bool {
|
||||
return git_commands.CheckedOutByOtherWorktree(branch, self.c.Model().Worktrees)
|
||||
}
|
||||
@@ -371,18 +462,34 @@ func (self *BranchesController) promptWorktreeBranchDelete(selectedBranch *model
|
||||
})
|
||||
}
|
||||
|
||||
func (self *BranchesController) deleteWithForce(selectedBranch *models.Branch, force bool) error {
|
||||
title := self.c.Tr.DeleteBranch
|
||||
var templateStr string
|
||||
if force {
|
||||
templateStr = self.c.Tr.ForceDeleteBranchMessage
|
||||
} else {
|
||||
templateStr = self.c.Tr.DeleteBranchMessage
|
||||
func (self *BranchesController) localDelete(branch *models.Branch) error {
|
||||
if self.checkedOutByOtherWorktree(branch) {
|
||||
return self.promptWorktreeBranchDelete(branch)
|
||||
}
|
||||
|
||||
return self.c.WithWaitingStatus(self.c.Tr.DeletingStatus, func(_ gocui.Task) error {
|
||||
self.c.LogAction(self.c.Tr.Actions.DeleteLocalBranch)
|
||||
err := self.c.Git().Branch.LocalDelete(branch.Name, false)
|
||||
if err != nil && strings.Contains(err.Error(), "git branch -D ") {
|
||||
return self.forceDelete(branch)
|
||||
}
|
||||
if err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.BRANCHES}})
|
||||
})
|
||||
}
|
||||
|
||||
func (self *BranchesController) remoteDelete(branch *models.Branch) error {
|
||||
return self.c.Helpers().BranchesHelper.ConfirmDeleteRemote(branch.UpstreamRemote, branch.Name)
|
||||
}
|
||||
|
||||
func (self *BranchesController) forceDelete(branch *models.Branch) error {
|
||||
title := self.c.Tr.ForceDeleteBranchTitle
|
||||
message := utils.ResolvePlaceholderString(
|
||||
templateStr,
|
||||
self.c.Tr.ForceDeleteBranchMessage,
|
||||
map[string]string{
|
||||
"selectedBranchName": selectedBranch.Name,
|
||||
"selectedBranchName": branch.Name,
|
||||
},
|
||||
)
|
||||
|
||||
@@ -390,19 +497,52 @@ func (self *BranchesController) deleteWithForce(selectedBranch *models.Branch, f
|
||||
Title: title,
|
||||
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 {
|
||||
errMessage := err.Error()
|
||||
if !force && strings.Contains(errMessage, "git branch -D ") {
|
||||
return self.deleteWithForce(selectedBranch, true)
|
||||
}
|
||||
return self.c.ErrorMsg(errMessage)
|
||||
if err := self.c.Git().Branch.LocalDelete(branch.Name, true); err != nil {
|
||||
return self.c.ErrorMsg(err.Error())
|
||||
}
|
||||
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.BRANCHES}})
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func (self *BranchesController) delete(branch *models.Branch) error {
|
||||
checkedOutBranch := self.c.Helpers().Refs.GetCheckedOutRef()
|
||||
|
||||
localDeleteItem := &types.MenuItem{
|
||||
Label: self.c.Tr.DeleteLocalBranch,
|
||||
Key: 'c',
|
||||
OnPress: func() error {
|
||||
return self.localDelete(branch)
|
||||
},
|
||||
}
|
||||
if checkedOutBranch.Name == branch.Name {
|
||||
localDeleteItem.DisabledReason = self.c.Tr.CantDeleteCheckOutBranch
|
||||
}
|
||||
|
||||
remoteDeleteItem := &types.MenuItem{
|
||||
Label: self.c.Tr.DeleteRemoteBranch,
|
||||
Key: 'r',
|
||||
OnPress: func() error {
|
||||
return self.remoteDelete(branch)
|
||||
},
|
||||
}
|
||||
if !branch.IsTrackingRemote() || branch.UpstreamGone {
|
||||
remoteDeleteItem.DisabledReason = self.c.Tr.UpstreamNotSetError
|
||||
}
|
||||
|
||||
menuTitle := utils.ResolvePlaceholderString(
|
||||
self.c.Tr.DeleteBranchTitle,
|
||||
map[string]string{
|
||||
"selectedBranchName": branch.Name,
|
||||
},
|
||||
)
|
||||
|
||||
return self.c.Menu(types.CreateMenuOptions{
|
||||
Title: menuTitle,
|
||||
Items: []*types.MenuItem{localDeleteItem, remoteDeleteItem},
|
||||
})
|
||||
}
|
||||
|
||||
func (self *BranchesController) merge() error {
|
||||
selectedBranchName := self.context().GetSelected().Name
|
||||
return self.c.Helpers().MergeAndRebase.MergeRefIntoCheckedOutBranch(selectedBranchName)
|
||||
@@ -413,6 +553,16 @@ func (self *BranchesController) rebase() error {
|
||||
return self.c.Helpers().MergeAndRebase.RebaseOntoRef(selectedBranchName)
|
||||
}
|
||||
|
||||
func (self *BranchesController) getDisabledReasonForRebase() string {
|
||||
selectedBranchName := self.context().GetSelected().Name
|
||||
checkedOutBranch := self.c.Helpers().Refs.GetCheckedOutRef().Name
|
||||
if selectedBranchName == checkedOutBranch {
|
||||
return self.c.Tr.CantRebaseOntoSelf
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func (self *BranchesController) fastForward(branch *models.Branch) error {
|
||||
if !branch.IsTrackingRemote() {
|
||||
return self.c.ErrorMsg(self.c.Tr.FwdNoUpstream)
|
||||
@@ -426,15 +576,7 @@ func (self *BranchesController) fastForward(branch *models.Branch) error {
|
||||
|
||||
action := self.c.Tr.Actions.FastForwardBranch
|
||||
|
||||
message := utils.ResolvePlaceholderString(
|
||||
self.c.Tr.Fetching,
|
||||
map[string]string{
|
||||
"from": fmt.Sprintf("%s/%s", branch.UpstreamRemote, branch.UpstreamBranch),
|
||||
"to": branch.Name,
|
||||
},
|
||||
)
|
||||
|
||||
return self.c.WithLoaderPanel(message, func(task gocui.Task) error {
|
||||
return self.c.WithInlineStatus(branch, types.ItemOperationFastForwarding, context.LOCAL_BRANCHES_CONTEXT_KEY, func(task gocui.Task) error {
|
||||
worktree, ok := self.worktreeForBranch(branch)
|
||||
if ok {
|
||||
self.c.LogAction(action)
|
||||
@@ -454,24 +596,17 @@ func (self *BranchesController) fastForward(branch *models.Branch) error {
|
||||
WorktreeGitDir: worktreeGitDir,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
_ = self.c.Error(err)
|
||||
}
|
||||
|
||||
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
|
||||
_ = self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
|
||||
return err
|
||||
} else {
|
||||
self.c.LogAction(action)
|
||||
|
||||
err := self.c.Git().Sync.FastForward(
|
||||
task, branch.Name, branch.UpstreamRemote, branch.UpstreamBranch,
|
||||
)
|
||||
if err != nil {
|
||||
_ = self.c.Error(err)
|
||||
}
|
||||
_ = self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.BRANCHES}})
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
@@ -490,7 +625,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.c.Git().Branch.Rename(branch.Name, helpers.SanitizedBranchName(newBranchName)); err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
|
||||
@@ -545,7 +680,7 @@ func (self *BranchesController) createPullRequestMenu(selectedBranch *models.Bra
|
||||
{
|
||||
LabelColumns: fromToLabelColumns(branch.Name, self.c.Tr.DefaultBranch),
|
||||
OnPress: func() error {
|
||||
return self.createPullRequest(branch.Name, "")
|
||||
return self.handleCreatePullRequest(branch)
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -553,7 +688,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.c.Helpers().Suggestions.GetRemoteBranchesSuggestionsFunc("/"),
|
||||
HandleConfirm: func(targetBranchName string) error {
|
||||
return self.createPullRequest(branch.Name, targetBranchName)
|
||||
},
|
||||
@@ -568,7 +703,10 @@ func (self *BranchesController) createPullRequestMenu(selectedBranch *models.Bra
|
||||
&types.MenuItem{
|
||||
LabelColumns: fromToLabelColumns(checkedOutBranch.Name, selectedBranch.Name),
|
||||
OnPress: func() error {
|
||||
return self.createPullRequest(checkedOutBranch.Name, selectedBranch.Name)
|
||||
if !checkedOutBranch.IsTrackingRemote() || !selectedBranch.IsTrackingRemote() {
|
||||
return self.c.ErrorMsg(self.c.Tr.PullRequestNoUpstream)
|
||||
}
|
||||
return self.createPullRequest(checkedOutBranch.UpstreamBranch, selectedBranch.UpstreamBranch)
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/context"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
)
|
||||
|
||||
@@ -34,6 +35,10 @@ func (self *CommitDescriptionController) GetKeybindings(opts types.KeybindingsOp
|
||||
Key: opts.GetKey(opts.Config.Universal.ConfirmInEditor),
|
||||
Handler: self.confirm,
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.CommitMessage.SwitchToEditor),
|
||||
Handler: self.switchToEditor,
|
||||
},
|
||||
}
|
||||
|
||||
return bindings
|
||||
@@ -43,7 +48,7 @@ func (self *CommitDescriptionController) Context() types.Context {
|
||||
return self.context()
|
||||
}
|
||||
|
||||
func (self *CommitDescriptionController) context() types.Context {
|
||||
func (self *CommitDescriptionController) context() *context.CommitMessageContext {
|
||||
return self.c.Contexts().CommitMessage
|
||||
}
|
||||
|
||||
@@ -58,3 +63,7 @@ func (self *CommitDescriptionController) close() error {
|
||||
func (self *CommitDescriptionController) confirm() error {
|
||||
return self.c.Helpers().Commits.HandleCommitConfirm()
|
||||
}
|
||||
|
||||
func (self *CommitDescriptionController) switchToEditor() error {
|
||||
return self.c.Helpers().Commits.SwitchToEditor()
|
||||
}
|
||||
|
||||
@@ -22,8 +22,6 @@ func NewCommitMessageController(
|
||||
}
|
||||
}
|
||||
|
||||
// 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{
|
||||
{
|
||||
@@ -48,6 +46,10 @@ func (self *CommitMessageController) GetKeybindings(opts types.KeybindingsOpts)
|
||||
Key: opts.GetKey(opts.Config.Universal.TogglePanel),
|
||||
Handler: self.switchToCommitDescription,
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.CommitMessage.SwitchToEditor),
|
||||
Handler: self.switchToEditor,
|
||||
},
|
||||
}
|
||||
|
||||
return bindings
|
||||
@@ -86,6 +88,10 @@ func (self *CommitMessageController) switchToCommitDescription() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *CommitMessageController) switchToEditor() error {
|
||||
return self.c.Helpers().Commits.SwitchToEditor()
|
||||
}
|
||||
|
||||
func (self *CommitMessageController) handleCommitIndexChange(value int) error {
|
||||
currentIndex := self.context().GetSelectedIndex()
|
||||
newIndex := currentIndex + value
|
||||
|
||||
@@ -113,9 +113,7 @@ func (self *CommitFilesController) GetOnRenderToMain() func() error {
|
||||
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.GetAppState().IgnoreWhitespaceInDiffView,
|
||||
)
|
||||
cmdObj := self.c.Git().WorkingTree.ShowFileDiffCmdObj(from, to, reverse, node.GetPath(), false)
|
||||
task := types.NewRunPtyTask(cmdObj.GetCmd())
|
||||
|
||||
pair := self.c.MainViewPairs().Normal
|
||||
|
||||
@@ -2,6 +2,7 @@ package controllers
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/context"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
@@ -65,7 +66,7 @@ func (self *ContextLinesController) Increase() error {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
|
||||
self.c.UserConfig.Git.DiffContextSize = self.c.UserConfig.Git.DiffContextSize + 1
|
||||
self.c.AppState.DiffContextSize++
|
||||
return self.applyChange()
|
||||
}
|
||||
|
||||
@@ -73,14 +74,14 @@ func (self *ContextLinesController) Increase() error {
|
||||
}
|
||||
|
||||
func (self *ContextLinesController) Decrease() error {
|
||||
old_size := self.c.UserConfig.Git.DiffContextSize
|
||||
old_size := self.c.AppState.DiffContextSize
|
||||
|
||||
if self.isShowingDiff() && old_size > 1 {
|
||||
if err := self.checkCanChangeContext(); err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
|
||||
self.c.UserConfig.Git.DiffContextSize = old_size - 1
|
||||
self.c.AppState.DiffContextSize = old_size - 1
|
||||
return self.applyChange()
|
||||
}
|
||||
|
||||
@@ -88,6 +89,9 @@ func (self *ContextLinesController) Decrease() error {
|
||||
}
|
||||
|
||||
func (self *ContextLinesController) applyChange() error {
|
||||
self.c.Toast(fmt.Sprintf(self.c.Tr.DiffContextSizeChanged, self.c.AppState.DiffContextSize))
|
||||
self.c.SaveAppStateAndLogError()
|
||||
|
||||
currentContext := self.c.CurrentStaticContext()
|
||||
switch currentContext.GetKey() {
|
||||
// we make an exception for our staging and patch building contexts because they actually need to refresh their state afterwards.
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user