mirror of
https://github.com/jesseduffield/lazygit.git
synced 2026-09-29 10:36:45 -04:00
Compare commits
445
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
017367f940 | ||
|
|
16369dc3c2 | ||
|
|
86bb73cff1 | ||
|
|
bac12231c3 | ||
|
|
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 | ||
|
|
7847203d6e | ||
|
|
af3101c7f2 | ||
|
|
ec7e9f9228 | ||
|
|
ab5875c78f | ||
|
|
f6af4c29d4 | ||
|
|
996ad5bf26 | ||
|
|
096628e366 | ||
|
|
b432fb5efe | ||
|
|
c390c9d58e | ||
|
|
e25403c650 | ||
|
|
5c9a9e777c | ||
|
|
39d24fdeb7 | ||
|
|
bbb2b32816 | ||
|
|
0152639ef0 | ||
|
|
c43195efb6 | ||
|
|
17db918cba | ||
|
|
befd7fe63e | ||
|
|
de28391052 | ||
|
|
92f0aa23cc | ||
|
|
c8520fbe78 | ||
|
|
8ce60662f2 | ||
|
|
c5acbb6c7c | ||
|
|
4aca854b59 | ||
|
|
f7bf125037 | ||
|
|
c92ed07082 | ||
|
|
df0c3b3ea8 | ||
|
|
280b0429c3 | ||
|
|
cb22db675a | ||
|
|
bb21034972 | ||
|
|
ec5bc93576 | ||
|
|
f43fd7af79 | ||
|
|
a200fccba9 | ||
|
|
37dc31b815 | ||
|
|
1712ad08ae | ||
|
|
53f0c1eb4f | ||
|
|
c53adbbb84 | ||
|
|
e932aaaeaf | ||
|
|
b44d0c4bde | ||
|
|
917cfd7586 | ||
|
|
c7e6a73512 | ||
|
|
b7ba06fa5b | ||
|
|
51e205ce11 | ||
|
|
6984a4f4d4 | ||
|
|
4baecec7bf | ||
|
|
b262e7f9b8 | ||
|
|
b92c294059 | ||
|
|
07d03df8df | ||
|
|
9cc1d65280 | ||
|
|
893c1cab75 | ||
|
|
25d57350df | ||
|
|
eb68591396 | ||
|
|
6de9e230a0 | ||
|
|
668d29736c | ||
|
|
be667682f0 | ||
|
|
045bce5dfc | ||
|
|
3827479643 | ||
|
|
d41a195ee6 | ||
|
|
71d2fd37e2 | ||
|
|
66de981e91 | ||
|
|
375451785c | ||
|
|
a6af31a4cb | ||
|
|
94daf7bddc | ||
|
|
4eb73393bb | ||
|
|
f5c9764dd2 | ||
|
|
6dc25d796b | ||
|
|
0c07963a2e | ||
|
|
092d5dd608 | ||
|
|
e9bbd816de | ||
|
|
eb6f089a2a | ||
|
|
cb240081a8 | ||
|
|
9c57444adc | ||
|
|
7b8ebfa27e | ||
|
|
8ab05d6834 | ||
|
|
774df817fd | ||
|
|
18903a7dde | ||
|
|
7cfbfb7183 | ||
|
|
975d2bedb6 | ||
|
|
5c78394299 | ||
|
|
e33fe37a99 | ||
|
|
ea54cb6e9c | ||
|
|
08f0e28e55 | ||
|
|
2d4706e96e | ||
|
|
d30eef69b1 | ||
|
|
91909331b5 | ||
|
|
f31e213edc | ||
|
|
7b302d8c29 | ||
|
|
a1fae41051 | ||
|
|
b16bb409fc | ||
|
|
a77d24fdc6 | ||
|
|
eecf07cd12 | ||
|
|
06be88aef7 | ||
|
|
4c5b1574f1 | ||
|
|
de57cfd6ff | ||
|
|
2b24c15938 | ||
|
|
e874f94cf8 | ||
|
|
0a410141ae | ||
|
|
e356b29b4a | ||
|
|
bc4ce26cef | ||
|
|
ae66f720f5 | ||
|
|
9c15ba0c0b | ||
|
|
b3060065d9 | ||
|
|
a313b16704 | ||
|
|
b93b9dae88 | ||
|
|
277142fc4b | ||
|
|
18a508b29c | ||
|
|
cdfad864ae | ||
|
|
fb4453c18a | ||
|
|
b5ff55e538 | ||
|
|
81a9133261 | ||
|
|
ca6f9c4155 | ||
|
|
7569180cac | ||
|
|
eeec373728 | ||
|
|
0604e43813 | ||
|
|
27ade502ee | ||
|
|
3a7468ecac | ||
|
|
7b05dacb98 | ||
|
|
b73efb2c22 | ||
|
|
a06a5cadee | ||
|
|
3cd2d6fa5c | ||
|
|
ec839e9e96 | ||
|
|
6f2f9f6677 | ||
|
|
2082fdf84a | ||
|
|
894485190b | ||
|
|
87b2455dbb | ||
|
|
2e68967e02 | ||
|
|
71422a8549 | ||
|
|
9c69a5df69 | ||
|
|
9b24995990 | ||
|
|
142f06357f | ||
|
|
18ea68c23a | ||
|
|
4b2622d93b | ||
|
|
ae0193698e | ||
|
|
6b4a638415 | ||
|
|
4b3c9f5b35 | ||
|
|
6c69549ced | ||
|
|
5868750aba | ||
|
|
ab3052f642 | ||
|
|
53f4ccb809 | ||
|
|
077ae99438 | ||
|
|
fe8adf9eb8 | ||
|
|
c713d550c0 | ||
|
|
d19d89ea9d | ||
|
|
e8ec41fb0f | ||
|
|
7682ec029b | ||
|
|
03f726038e | ||
|
|
796945aad0 | ||
|
|
db02c13bf6 | ||
|
|
1ce9a87544 | ||
|
|
35e6e6347a | ||
|
|
54708233ac | ||
|
|
c679fd1924 | ||
|
|
9a79154d05 | ||
|
|
60872c91e6 | ||
|
|
afc4aedd4f | ||
|
|
f8ba899b87 | ||
|
|
52447e5d46 | ||
|
|
6794149ec8 | ||
|
|
a4772ae606 | ||
|
|
f30e09856c | ||
|
|
9b7f978e3e | ||
|
|
f17417219a | ||
|
|
d7f84aed8a | ||
|
|
deecbadda9 | ||
|
|
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.18.x
|
||||
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
|
||||
|
||||
+16
-63
@@ -1,7 +1,7 @@
|
||||
name: Continuous Integration
|
||||
|
||||
env:
|
||||
GO_VERSION: 1.18
|
||||
GO_VERSION: 1.20
|
||||
|
||||
on:
|
||||
push:
|
||||
@@ -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.18.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-
|
||||
go-version: 1.20.x
|
||||
- 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.18.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-
|
||||
go-version: 1.20.x
|
||||
- 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.18.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-
|
||||
go-version: 1.20.x
|
||||
- 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.18.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
|
||||
go-version: 1.20.x
|
||||
- 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.18.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-
|
||||
go-version: 1.20.x
|
||||
- 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'
|
||||
|
||||
+3
-2
@@ -35,9 +35,10 @@ lazygit.exe
|
||||
|
||||
test/git_server/data
|
||||
|
||||
test/results/**
|
||||
test/_results/**
|
||||
|
||||
oryxBuildBinary
|
||||
__debug_bin
|
||||
|
||||
.worktrees
|
||||
.worktrees
|
||||
demo/output/*
|
||||
|
||||
+7
-2
@@ -20,10 +20,15 @@ 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
|
||||
|
||||
run:
|
||||
go: 1.18
|
||||
go: '1.20'
|
||||
timeout: 10m
|
||||
|
||||
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,
|
||||
},
|
||||
}
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
# docker build -t lazygit .
|
||||
# docker run -it lazygit:latest /bin/sh
|
||||
|
||||
FROM golang:1.18 as build
|
||||
FROM golang:1.20 as build
|
||||
WORKDIR /go/src/github.com/jesseduffield/lazygit/
|
||||
COPY go.mod go.sum ./
|
||||
RUN go mod download
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
This directory contains stuff for recording lazygit demos.
|
||||
|
||||
+112
@@ -0,0 +1,112 @@
|
||||
# Specify a command to be executed
|
||||
# like `/bin/bash -l`, `ls`, or any other commands
|
||||
# the default is bash for Linux
|
||||
# or powershell.exe for Windows
|
||||
command: echo "YOU NEED TO SPECIFY YOUR OWN COMMAND WITH THE -d ARG"
|
||||
|
||||
# Specify the current working directory path
|
||||
# the default is the current working directory path
|
||||
cwd: null
|
||||
|
||||
# Export additional ENV variables
|
||||
env:
|
||||
recording: true
|
||||
|
||||
# Explicitly set the number of columns
|
||||
# or use `auto` to take the current
|
||||
# number of columns of your shell
|
||||
cols: 120 # 100
|
||||
|
||||
# Explicitly set the number of rows
|
||||
# or use `auto` to take the current
|
||||
# number of rows of your shell
|
||||
rows: 35 # 30
|
||||
|
||||
# Amount of times to repeat GIF
|
||||
# If value is -1, play once
|
||||
# If value is 0, loop indefinitely
|
||||
# If value is a positive number, loop n times
|
||||
repeat: 0
|
||||
|
||||
# Quality
|
||||
# 1 - 100
|
||||
# Higher quality seems to make no difference, but running it through
|
||||
# gifsicle ends up with a much better compressed version.
|
||||
quality: 100
|
||||
|
||||
# Delay between frames in ms
|
||||
# If the value is `auto` use the actual recording delays
|
||||
frameDelay: auto
|
||||
|
||||
# Maximum delay between frames in ms
|
||||
# Ignored if the `frameDelay` isn't set to `auto`
|
||||
# Set to `auto` to prevent limiting the max idle time
|
||||
maxIdleTime: 2000
|
||||
|
||||
# The surrounding frame box
|
||||
# The `type` can be null, window, floating, or solid`
|
||||
# To hide the title use the value null
|
||||
# Don't forget to add a backgroundColor style with a null as type
|
||||
frameBox:
|
||||
type: floating
|
||||
title: Lazygit
|
||||
style:
|
||||
border: 0px black solid
|
||||
backgroundColor: "#1d1d1d"
|
||||
margin: -5px
|
||||
|
||||
# Add a watermark image to the rendered gif
|
||||
# You need to specify an absolute path for
|
||||
# the image on your machine or a URL, and you can also
|
||||
# add your own CSS styles
|
||||
watermark:
|
||||
imagePath: null
|
||||
style:
|
||||
position: absolute
|
||||
right: 15px
|
||||
bottom: 15px
|
||||
width: 100px
|
||||
opacity: 0.9
|
||||
|
||||
# Cursor style can be one of
|
||||
# `block`, `underline`, or `bar`
|
||||
cursorStyle: block
|
||||
|
||||
# Font family
|
||||
# You can use any font that is installed on your machine
|
||||
# in CSS-like syntax
|
||||
# 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
|
||||
|
||||
# The height of lines
|
||||
lineHeight: 1
|
||||
|
||||
# The spacing between letters
|
||||
letterSpacing: 0
|
||||
|
||||
# Theme
|
||||
theme:
|
||||
background: "transparent"
|
||||
foreground: "#dddad6"
|
||||
cursor: "#c7c7c7"
|
||||
black: "#7a7a7a"
|
||||
red: "#fc4384"
|
||||
green: "#b3e33b"
|
||||
yellow: "#ffa727"
|
||||
blue: "#102895"
|
||||
magenta: "#c930c7"
|
||||
cyan: "#00c5c7"
|
||||
white: "#c7c7c7"
|
||||
brightBlack: "#676767"
|
||||
brightRed: "#ff7fac"
|
||||
brightGreen: "#c8ed71"
|
||||
brightYellow: "#ebdf86"
|
||||
brightBlue: "#6871ff"
|
||||
brightMagenta: "#ff76ff"
|
||||
brightCyan: "#5ffdff"
|
||||
brightWhite: "#fffefe"
|
||||
Executable
+81
@@ -0,0 +1,81 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -e
|
||||
|
||||
TYPE=$1
|
||||
TEST=$2
|
||||
|
||||
usage() {
|
||||
echo "Usage: $0 [gif|mp4] <test path>"
|
||||
echo "e.g. using full path: $0 gif pkg/integration/tests/demo/nuke_working_tree.go"
|
||||
exit 1
|
||||
}
|
||||
|
||||
if [ "$#" -ne 2 ]
|
||||
then
|
||||
usage
|
||||
fi
|
||||
|
||||
if [ "$TYPE" != "gif" ] && [ "$TYPE" != "mp4" ]
|
||||
then
|
||||
usage
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "$TEST" ]
|
||||
then
|
||||
usage
|
||||
fi
|
||||
|
||||
WORKTREE_PATH=$(git worktree list | grep assets | awk '{print $1}')
|
||||
|
||||
if [ -z "$WORKTREE_PATH" ]
|
||||
then
|
||||
echo "Could not find assets worktree. You'll need to create a worktree for the assets branch using the following command:"
|
||||
echo "git worktree add .worktrees/assets assets"
|
||||
echo "The assets branch has no shared history with the main branch: it exists to store assets which are too large to store in the main branch."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
OUTPUT_DIR="$WORKTREE_PATH/demo"
|
||||
|
||||
if ! command -v terminalizer &> /dev/null
|
||||
then
|
||||
echo "terminalizer could not be found"
|
||||
echo "Install it with: npm install -g terminalizer"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! command -v "gifsicle" &> /dev/null
|
||||
then
|
||||
echo "gifsicle could not be found"
|
||||
echo "Install it with: npm install -g gifsicle"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Get last part of the test path and set that as the output name
|
||||
# example test path: pkg/integration/tests/01_basic_test.go
|
||||
# For that we want: NAME=01_basic_test
|
||||
NAME=$(echo "$TEST" | sed -e 's/.*\///' | sed -e 's/\..*//')
|
||||
|
||||
# Add the demo to the tests list (if missing) so that it can be run
|
||||
go generate pkg/integration/tests/tests.go
|
||||
|
||||
mkdir -p "$OUTPUT_DIR"
|
||||
|
||||
# First we record the demo into a yaml representation
|
||||
terminalizer -c demo/config.yml record --skip-sharing -d "go run cmd/integration_test/main.go cli --slow $TEST" "$OUTPUT_DIR/$NAME"
|
||||
# Then we render it into a gif
|
||||
terminalizer render "$OUTPUT_DIR/$NAME" -o "$OUTPUT_DIR/$NAME.gif"
|
||||
|
||||
# Then we convert it to either an mp4 or gif based on the command line argument
|
||||
if [ "$TYPE" = "mp4" ]
|
||||
then
|
||||
COMPRESSED_PATH="$OUTPUT_DIR/$NAME.mp4"
|
||||
ffmpeg -y -i "$OUTPUT_DIR/$NAME.gif" -movflags faststart -pix_fmt yuv420p -vf "scale=trunc(iw/2)*2:trunc(ih/2)*2" "$COMPRESSED_PATH"
|
||||
else
|
||||
COMPRESSED_PATH="$OUTPUT_DIR/$NAME-compressed.gif"
|
||||
gifsicle --colors 256 --use-col=web -O3 < "$OUTPUT_DIR/$NAME.gif" > "$COMPRESSED_PATH"
|
||||
fi
|
||||
|
||||
echo "Demo recorded to $COMPRESSED_PATH"
|
||||
+59
-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:
|
||||
@@ -62,15 +75,17 @@ gui:
|
||||
showListFooter: true # for seeing the '5 of 20' message in list panels
|
||||
showRandomTip: true
|
||||
showBranchCommitHash: false # show commit hashes alongside branch names
|
||||
experimentalShowBranchHeads: false # visualize branch heads with (*) in commits list
|
||||
showBottomLine: true # for hiding the bottom information line (unless it has important information to tell you)
|
||||
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,8 +119,8 @@ 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
|
||||
edit: ''
|
||||
editAtLine: ''
|
||||
@@ -148,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>'
|
||||
@@ -193,6 +208,7 @@ keybinding:
|
||||
commitChangesWithoutHook: 'w' # commit changes without pre-commit hook
|
||||
amendLastCommit: 'A'
|
||||
commitChangesWithEditor: 'C'
|
||||
confirmDiscard: 'x'
|
||||
ignoreFile: 'i'
|
||||
refreshFiles: 'r'
|
||||
stashAllChanges: 's'
|
||||
@@ -278,29 +294,36 @@ os:
|
||||
open: 'open {{filename}}'
|
||||
```
|
||||
|
||||
### Configuring File Editing
|
||||
## Custom Command for Copying to Clipboard
|
||||
```yaml
|
||||
os:
|
||||
copyToClipboardCmd: ''
|
||||
```
|
||||
Specify an external command to invoke when copying to clipboard is requested. `{{text}` will be replaced by text to be copied. Default is to copy to system clipboard.
|
||||
|
||||
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.
|
||||
If you are working on a terminal that supports OSC52, the following command will let you take advantage of it:
|
||||
```
|
||||
os:
|
||||
copyToClipboardCmd: printf "\033]52;c;$(printf {{text}} | base64)\a" > /dev/tty
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
## 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.
|
||||
|
||||
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`, `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:
|
||||
@@ -311,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.
|
||||
|
||||
@@ -331,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)
|
||||
@@ -427,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
|
||||
@@ -476,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:
|
||||
@@ -493,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:
|
||||
|
||||
@@ -524,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
|
||||
```
|
||||
|
||||
@@ -6,4 +6,5 @@
|
||||
* [Keybindings](./keybindings)
|
||||
* [Undo/Redo](./Undoing.md)
|
||||
* [Searching/Filtering](./Searching.md)
|
||||
* [Stacked Branches](./Stacked_Branches.md)
|
||||
* [Dev docs](./dev)
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
# Working with stacked branches
|
||||
|
||||
When working on a large branch it can often be useful to break it down into
|
||||
smaller pieces, and it can help to create separate branches for each independent
|
||||
chunk of changes. For example, you could have one branch for preparatory
|
||||
refactorings, one for backend changes, and one for frontend changes. Those
|
||||
branches would then all be stacked onto each other.
|
||||
|
||||
Git has support for rebasing such a stack as a whole; you can enable it by
|
||||
setting the git config `rebase.updateRfs` to true. If you then rebase the
|
||||
topmost branch of the stack, the other ones in the stack will follow. This
|
||||
includes interactive rebases, so for example amending a commit in the first
|
||||
branch of the stack will "just work" in the sense that it keeps the other
|
||||
branches properly stacked onto it.
|
||||
|
||||
Lazygit visualizes the invidual branch heads in the stack by marking them with a
|
||||
cyan asterisk (or a cyan branch symbol if you are using [nerd
|
||||
fonts](Config.md#display-nerd-fonts-icons)).
|
||||
+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
|
||||
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
# Demo Recordings
|
||||
|
||||
We want our demo recordings to be consistent and easy to update if we make changes to Lazygit's UI. Luckily for us, we have an existing recording system for the sake of our integration tests, so we can piggyback on that.
|
||||
|
||||
You'll want to familiarise yourself with how integration tests are written: see [here](../../pkg/integration/README.md).
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Ideally we'd run this whole thing through docker but we haven't got that working. So you will need:
|
||||
```
|
||||
# for recording
|
||||
npm i -g terminalizer
|
||||
# for gif compression
|
||||
npm i -g gifsicle
|
||||
# for mp4 conversion
|
||||
brew install ffmpeg
|
||||
|
||||
# font with icons
|
||||
wget https://github.com/ryanoasis/nerd-fonts/releases/download/v3.0.2/DejaVuSansMono.tar.xz && \
|
||||
tar -xf DejaVuSansMono.tar.xz -C /usr/local/share/fonts && \
|
||||
rm DejaVuSansMono.tar.xz
|
||||
```
|
||||
|
||||
## Creating a demo
|
||||
|
||||
Demos are found in `pkg/integration/tests/demo/`. They are like regular integration tests but have `IsDemo: true` which has a few effects:
|
||||
* The bottom row of the UI is quieter so that we can render captions
|
||||
* Fetch/Push/Pull have artificial latency to mimic a network request
|
||||
* The loader at the bottom-right does not appear
|
||||
|
||||
In demos, we don't need to be as strict in our assertions as we are in tests. But it's still good to have some basic assertions so that if we automate the process of updating demos we'll know if one of them has broken.
|
||||
|
||||
You can use the same flow as we use with integration tests when you're writing a demo:
|
||||
* Setup the repo
|
||||
* Run the demo in sandbox mode to get a feel of what needs to happen
|
||||
* Come back and write the code to make it happen
|
||||
|
||||
### Adding captions
|
||||
|
||||
It's good to add captions explaining what task if being performed. Use the existing demos as a guide.
|
||||
|
||||
### Setting up the assets worktree
|
||||
|
||||
We store assets (which includes demo recordings) in the `assets` branch, which is a branch that shares no history with the main branch and exists purely for storing assets. Storing them separately means we don't clog up the code branches with large binaries.
|
||||
|
||||
The scripts and demo definitions live in the code branches but the output lives in the assets branch so to be able to create a video from a demo you'll need to create a linked worktree for the assets branch which you can do with:
|
||||
|
||||
```sh
|
||||
git worktree add .worktrees/assets assets
|
||||
```
|
||||
|
||||
Outputs will be stored in `.worktrees/assets/demos/`. We'll store three separate things:
|
||||
* the yaml of the recording
|
||||
* the original gif
|
||||
* either the compressed gif or the mp4 depending on the output you chose (see below)
|
||||
|
||||
### Recording the demo
|
||||
|
||||
Once you're happy with your demo you can record it using:
|
||||
```sh
|
||||
scripts/record_demo.sh [gif|mp4] <path>
|
||||
# e.g.
|
||||
scripts/record_demo.sh gif pkg/integration/tests/demo/interactive_rebase.go
|
||||
```
|
||||
|
||||
~~The gif format is for use in the first video of the readme (it has a larger size but has auto-play and looping)~~
|
||||
~~The mp4 format is for everything else (no looping, requires clicking, but smaller size).~~
|
||||
|
||||
Turns out that you can't store mp4s in a repo and link them from a README so we're gonna just use gifs across the board for now.
|
||||
|
||||
### Including demos in README/docs
|
||||
|
||||
If you've followed the above steps you'll end up with your output in your assets worktree.
|
||||
|
||||
Within that worktree, stage all three output files and raise a PR against the assets branch.
|
||||
|
||||
Then back in the code branch, in the doc, you can embed the recording like so:
|
||||
```md
|
||||

|
||||
```
|
||||
|
||||
This means we can update assets without needing to update the docs that embed them.
|
||||
@@ -2,3 +2,4 @@
|
||||
|
||||
* [Busy/Idle tracking](./Busy.md).
|
||||
* [Integration Tests](../../pkg/integration/README.md)
|
||||
* [Demo Recordings](./Demo_Recordings.md)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -84,6 +84,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd><c-j></kbd>: Move commit down one
|
||||
<kbd><c-k></kbd>: Move commit up one
|
||||
<kbd>v</kbd>: Paste commits (cherry-pick)
|
||||
<kbd>B</kbd>: Mark commit as base commit for rebase
|
||||
<kbd>A</kbd>: Amend commit with staged changes
|
||||
<kbd>a</kbd>: Set/Reset commit author
|
||||
<kbd>t</kbd>: Revert commit
|
||||
@@ -147,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
|
||||
@@ -254,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
|
||||
@@ -331,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 キーバインド
|
||||
|
||||
@@ -103,6 +103,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd><c-j></kbd>: コミットを1つ下に移動
|
||||
<kbd><c-k></kbd>: コミットを1つ上に移動
|
||||
<kbd>v</kbd>: コミットを貼り付け (cherry-pick)
|
||||
<kbd>B</kbd>: Mark commit as base commit for rebase
|
||||
<kbd>A</kbd>: ステージされた変更でamendコミット
|
||||
<kbd>a</kbd>: Set/Reset commit author
|
||||
<kbd>t</kbd>: コミットをrevert
|
||||
@@ -171,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
|
||||
@@ -219,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
|
||||
@@ -319,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
|
||||
@@ -265,6 +265,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd><c-j></kbd>: 커밋을 1개 아래로 이동
|
||||
<kbd><c-k></kbd>: 커밋을 1개 위로 이동
|
||||
<kbd>v</kbd>: 커밋을 붙여넣기 (cherry-pick)
|
||||
<kbd>B</kbd>: Mark commit as base commit for rebase
|
||||
<kbd>A</kbd>: Amend commit with staged changes
|
||||
<kbd>a</kbd>: Set/Reset commit author
|
||||
<kbd>t</kbd>: 커밋 되돌리기
|
||||
@@ -308,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
|
||||
@@ -143,6 +143,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd><c-j></kbd>: Verplaats commit 1 naar beneden
|
||||
<kbd><c-k></kbd>: Verplaats commit 1 naar boven
|
||||
<kbd>v</kbd>: Plak commits (cherry-pick)
|
||||
<kbd>B</kbd>: Mark commit as base commit for rebase
|
||||
<kbd>A</kbd>: Wijzig commit met staged veranderingen
|
||||
<kbd>a</kbd>: Set/Reset commit author
|
||||
<kbd>t</kbd>: Commit ongedaan maken
|
||||
@@ -232,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
|
||||
@@ -331,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
|
||||
|
||||
@@ -69,6 +69,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd><c-j></kbd>: Przenieś commit 1 w dół
|
||||
<kbd><c-k></kbd>: Przenieś commit 1 w górę
|
||||
<kbd>v</kbd>: Wklej commity (przebieranie)
|
||||
<kbd>B</kbd>: Mark commit as base commit for rebase
|
||||
<kbd>A</kbd>: Popraw commit zmianami z poczekalni
|
||||
<kbd>a</kbd>: Set/Reset commit author
|
||||
<kbd>t</kbd>: Odwróć commit
|
||||
@@ -105,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
|
||||
@@ -231,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
|
||||
@@ -324,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 Связки клавиш
|
||||
|
||||
@@ -151,6 +151,7 @@ _Связки клавиш_
|
||||
<kbd><c-j></kbd>: Переместить коммит вниз на один
|
||||
<kbd><c-k></kbd>: Переместить коммит вверх на один
|
||||
<kbd>v</kbd>: Вставить отобранные коммиты (cherry-pick)
|
||||
<kbd>B</kbd>: Mark commit as base commit for rebase
|
||||
<kbd>A</kbd>: Править последний коммит с проиндексированными изменениями
|
||||
<kbd>a</kbd>: Установить/убрать автора коммита
|
||||
<kbd>t</kbd>: Отменить коммит
|
||||
@@ -180,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
|
||||
@@ -276,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>: Просмотреть параметры сброса
|
||||
@@ -293,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
|
||||
@@ -147,6 +147,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
<kbd><c-j></kbd>: 下移提交
|
||||
<kbd><c-k></kbd>: 上移提交
|
||||
<kbd>v</kbd>: 粘贴提交(拣选)
|
||||
<kbd>B</kbd>: Mark commit as base commit for rebase
|
||||
<kbd>A</kbd>: 用已暂存的更改来修补提交
|
||||
<kbd>a</kbd>: Set/Reset commit author
|
||||
<kbd>t</kbd>: 还原提交
|
||||
@@ -233,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>: 查看重置选项
|
||||
@@ -333,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 鍵盤快捷鍵
|
||||
|
||||
@@ -191,6 +191,7 @@ _說明:`<c-b>` 表示 Ctrl+B、`<a-b>` 表示 Alt+B,`B`表示 Shift+B_
|
||||
<kbd><c-j></kbd>: 向下移動提交
|
||||
<kbd><c-k></kbd>: 向上移動提交
|
||||
<kbd>v</kbd>: 貼上提交 (揀選)
|
||||
<kbd>B</kbd>: Mark commit as base commit for rebase
|
||||
<kbd>A</kbd>: 使用已預存的更改修正提交
|
||||
<kbd>a</kbd>: 設置/重設提交作者
|
||||
<kbd>t</kbd>: 還原提交
|
||||
@@ -255,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
|
||||
@@ -272,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>: 檢視重設選項
|
||||
@@ -343,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
|
||||
|
||||
@@ -1,24 +1,21 @@
|
||||
module github.com/jesseduffield/lazygit
|
||||
|
||||
go 1.18
|
||||
go 1.20
|
||||
|
||||
require (
|
||||
github.com/OpenPeeDeeP/xdg v1.0.0
|
||||
github.com/atotto/clipboard v0.1.4
|
||||
github.com/aybabtme/humanlog v0.4.1
|
||||
github.com/cli/safeexec v1.0.0
|
||||
github.com/cloudfoundry/jibber_jabber v0.0.0-20151120183258-bcc4c8345a21
|
||||
github.com/creack/pty v1.1.11
|
||||
github.com/fsmiamoto/git-todo-parser v0.0.5
|
||||
github.com/fsnotify/fsnotify v1.4.7
|
||||
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.20230723014157-03e858e46144
|
||||
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,17 +23,21 @@ require (
|
||||
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0
|
||||
github.com/kyokomi/emoji/v2 v2.2.8
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0
|
||||
github.com/mattn/go-runewidth v0.0.14
|
||||
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
|
||||
github.com/sanity-io/litter v1.5.2
|
||||
github.com/sasha-s/go-deadlock v0.3.1
|
||||
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
|
||||
gopkg.in/ozeidan/fuzzy-patricia.v3 v3.0.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
)
|
||||
@@ -64,13 +65,11 @@ require (
|
||||
github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 // indirect
|
||||
github.com/rivo/uniseg v0.4.4 // indirect
|
||||
github.com/sergi/go-diff v1.1.0 // indirect
|
||||
github.com/spf13/afero v1.9.5 // indirect
|
||||
github.com/xanzy/ssh-agent v0.2.1 // indirect
|
||||
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa // indirect
|
||||
golang.org/x/exp v0.0.0-20220318154914-8dddf5d87bd8 // 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
|
||||
)
|
||||
|
||||
@@ -55,8 +55,6 @@ github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/cli/safeexec v1.0.0 h1:0VngyaIyqACHdcMNWfo6+KdUYnqEr2Sg+bSP1pdF+dI=
|
||||
github.com/cli/safeexec v1.0.0/go.mod h1:Z/D4tTN8Vs5gXYHDCbaM1S/anmEDnJb1iW0+EJ5zx3Q=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cloudfoundry/jibber_jabber v0.0.0-20151120183258-bcc4c8345a21 h1:tuijfIjZyjZaHq9xDUh0tNitwXshJpbLkqMOJv4H3do=
|
||||
github.com/cloudfoundry/jibber_jabber v0.0.0-20151120183258-bcc4c8345a21/go.mod h1:po7NpZ/QiTKzBKyrsEAxwnTamCoh8uDk/egRpQ7siIc=
|
||||
@@ -84,18 +82,14 @@ github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL
|
||||
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
|
||||
github.com/fsmiamoto/git-todo-parser v0.0.5 h1:Bhzd/vz/6Qm3udfkd6NO9fWfD3TpwR9ucp3N75/J5I8=
|
||||
github.com/fsmiamoto/git-todo-parser v0.0.5/go.mod h1:B+AgTbNE2BARvJqzXygThzqxLIaEWvwr2sxKYYb0Fas=
|
||||
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko=
|
||||
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
|
||||
github.com/gdamore/tcell/v2 v2.4.0/go.mod h1:cTTuF84Dlj/RqmaCIV5p4w8uG1zWdk0SF6oBpwHp4fU=
|
||||
github.com/gdamore/tcell/v2 v2.6.0 h1:OKbluoP9VYmJwZwq/iLb4BxwKcwGthaa1YNBJIyCySg=
|
||||
github.com/gdamore/tcell/v2 v2.6.0/go.mod h1:be9omFATkdr0D9qewWW3d+MEvl5dha+Etb5y65J2H8Y=
|
||||
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=
|
||||
@@ -185,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.20230723014157-03e858e46144 h1:gwy5JzP6+PhcPFG1obkUSLGcTkUY88sLKlCPOFjwtak=
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20230723014157-03e858e46144/go.mod h1:dJ/BEUt3OWtaRg/PmuJWendRqREhre9JQ1SLvqrVJ8s=
|
||||
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=
|
||||
@@ -218,7 +212,6 @@ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0
|
||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||
github.com/kyokomi/emoji/v2 v2.2.8 h1:jcofPxjHWEkJtkIbcLHvZhxKgCPl6C7MyjTrD4KDqUE=
|
||||
github.com/kyokomi/emoji/v2 v2.2.8/go.mod h1:JUcn42DTdsXJo1SWanHh4HKDEyPaR5CqkmoirZZP9qE=
|
||||
github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||
github.com/mattn/go-colorable v0.1.0/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||
@@ -230,13 +223,15 @@ github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd
|
||||
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
|
||||
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
|
||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||
github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
|
||||
github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU=
|
||||
github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
|
||||
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
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=
|
||||
@@ -246,15 +241,14 @@ github.com/onsi/gomega v1.7.1 h1:K0jcRCwNQM3vFGh1ppMtDh/+7ApJrjldlX8fA0jDTLQ=
|
||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||
github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 h1:q2e307iGHPdTGp0hoxKjt1H5pDo6utceo3dQVK3I5XQ=
|
||||
github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5/go.mod h1:jvVRKCrJTQWu0XVbaOlby/2lO20uSCHEMzzplHXte1o=
|
||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg=
|
||||
github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.4.3/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis=
|
||||
@@ -276,6 +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.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=
|
||||
@@ -314,7 +310,6 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
|
||||
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 h1:7I4JAnoQBe7ZtJcBaYHi5UtiO8tQHbUSXxL+pnGRANg=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa h1:zuSxTR4o9y82ebqCUJYNGJbGPo6sKVl54f/TVDObg1c=
|
||||
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
@@ -458,14 +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-20201210144234-2321bbc49cbf/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=
|
||||
@@ -475,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=
|
||||
|
||||
+9
-4
@@ -13,7 +13,6 @@ import (
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/spf13/afero"
|
||||
|
||||
"github.com/jesseduffield/generics/slices"
|
||||
appTypes "github.com/jesseduffield/lazygit/pkg/app/types"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
|
||||
@@ -63,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)
|
||||
@@ -75,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
|
||||
@@ -267,7 +268,11 @@ func (app *App) Run(startArgs appTypes.StartArgs) error {
|
||||
|
||||
// Close closes any resources
|
||||
func (app *App) Close() error {
|
||||
return slices.TryForEach(app.closers, func(closer io.Closer) error {
|
||||
return closer.Close()
|
||||
})
|
||||
for _, closer := range app.closers {
|
||||
if err := closer.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -5,12 +5,12 @@ import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
|
||||
"github.com/fsmiamoto/git-todo-parser/todo"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/common"
|
||||
"github.com/jesseduffield/lazygit/pkg/secureexec"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
@@ -92,7 +92,7 @@ func getDaemonKind() DaemonKind {
|
||||
}
|
||||
|
||||
func getCommentChar() byte {
|
||||
cmd := secureexec.Command("git", "config", "--get", "--null", "core.commentChar")
|
||||
cmd := exec.Command("git", "config", "--get", "--null", "core.commentChar")
|
||||
if output, err := cmd.Output(); err == nil && len(output) == 2 {
|
||||
return output[0]
|
||||
}
|
||||
|
||||
@@ -6,10 +6,10 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/fsmiamoto/git-todo-parser/todo"
|
||||
"github.com/jesseduffield/generics/slices"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/common"
|
||||
"github.com/jesseduffield/lazygit/pkg/env"
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
type TodoLine struct {
|
||||
@@ -26,11 +26,11 @@ func (self *TodoLine) ToString() string {
|
||||
}
|
||||
|
||||
func TodoLinesToString(todoLines []TodoLine) string {
|
||||
lines := slices.Map(todoLines, func(todoLine TodoLine) string {
|
||||
lines := lo.Map(todoLines, func(todoLine TodoLine, _ int) string {
|
||||
return todoLine.ToString()
|
||||
})
|
||||
|
||||
return strings.Join(slices.Reverse(lines), "")
|
||||
return strings.Join(lo.Reverse(lines), "")
|
||||
}
|
||||
|
||||
type ChangeTodoAction struct {
|
||||
|
||||
+14
-18
@@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"runtime/debug"
|
||||
@@ -17,7 +18,6 @@ import (
|
||||
"github.com/jesseduffield/lazygit/pkg/env"
|
||||
integrationTypes "github.com/jesseduffield/lazygit/pkg/integration/types"
|
||||
"github.com/jesseduffield/lazygit/pkg/logs/tail"
|
||||
"github.com/jesseduffield/lazygit/pkg/secureexec"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
"github.com/samber/lo"
|
||||
"gopkg.in/yaml.v3"
|
||||
@@ -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 := ""
|
||||
@@ -280,7 +276,7 @@ func mergeBuildInfo(buildInfo *BuildInfo) {
|
||||
}
|
||||
|
||||
func getGitVersionInfo() string {
|
||||
cmd := secureexec.Command("git", "--version")
|
||||
cmd := exec.Command("git", "--version")
|
||||
stdout, _ := cmd.Output()
|
||||
gitVersion := strings.Trim(strings.TrimPrefix(string(stdout), "git version "), " \r\n")
|
||||
return gitVersion
|
||||
|
||||
+3
-3
@@ -3,8 +3,8 @@ package app
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/jesseduffield/generics/slices"
|
||||
"github.com/jesseduffield/lazygit/pkg/i18n"
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
type errorMapping struct {
|
||||
@@ -18,7 +18,7 @@ func knownError(tr *i18n.TranslationSet, err error) (string, bool) {
|
||||
|
||||
knownErrorMessages := []string{tr.MinGitVersionError}
|
||||
|
||||
if slices.Contains(knownErrorMessages, errorMessage) {
|
||||
if lo.Contains(knownErrorMessages, errorMessage) {
|
||||
return errorMessage, true
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ func knownError(tr *i18n.TranslationSet, err error) (string, bool) {
|
||||
},
|
||||
}
|
||||
|
||||
if mapping, ok := slices.Find(mappings, func(mapping errorMapping) bool {
|
||||
if mapping, ok := lo.Find(mappings, func(mapping errorMapping) bool {
|
||||
return strings.Contains(errorMessage, mapping.originalError)
|
||||
}); ok {
|
||||
return mapping.newError, true
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -15,7 +17,6 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/jesseduffield/generics/maps"
|
||||
"github.com/jesseduffield/generics/slices"
|
||||
"github.com/jesseduffield/lazycore/pkg/utils"
|
||||
"github.com/jesseduffield/lazygit/pkg/app"
|
||||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
@@ -23,6 +24,7 @@ import (
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/jesseduffield/lazygit/pkg/i18n"
|
||||
"github.com/samber/lo"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
type bindingSection struct {
|
||||
@@ -42,7 +44,7 @@ type headerWithBindings struct {
|
||||
}
|
||||
|
||||
func CommandToRun() string {
|
||||
return "go run scripts/cheatsheet/main.go generate"
|
||||
return "go generate ./..."
|
||||
}
|
||||
|
||||
func GetKeybindingsDir() string {
|
||||
@@ -129,7 +131,7 @@ func localisedTitle(tr *i18n.TranslationSet, str string) string {
|
||||
|
||||
func getBindingSections(bindings []*types.Binding, tr *i18n.TranslationSet) []*bindingSection {
|
||||
excludedViews := []string{"stagingSecondary", "patchBuildingSecondary"}
|
||||
bindingsToDisplay := slices.Filter(bindings, func(binding *types.Binding) bool {
|
||||
bindingsToDisplay := lo.Filter(bindings, func(binding *types.Binding, _ int) bool {
|
||||
if lo.Contains(excludedViews, binding.ViewName) {
|
||||
return false
|
||||
}
|
||||
@@ -162,7 +164,7 @@ func getBindingSections(bindings []*types.Binding, tr *i18n.TranslationSet) []*b
|
||||
return a.header.title < b.header.title
|
||||
})
|
||||
|
||||
return slices.Map(bindingGroups, func(hb headerWithBindings) *bindingSection {
|
||||
return lo.Map(bindingGroups, func(hb headerWithBindings, _ int) *bindingSection {
|
||||
return &bindingSection{
|
||||
title: hb.header.title,
|
||||
bindings: hb.bindings,
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
+5
-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"
|
||||
@@ -48,6 +47,7 @@ type GitCommand struct {
|
||||
|
||||
type Loaders struct {
|
||||
BranchLoader *git_commands.BranchLoader
|
||||
MergedBranchLoader *git_commands.MergedBranchLoader
|
||||
CommitFileLoader *git_commands.CommitFileLoader
|
||||
CommitLoader *git_commands.CommitLoader
|
||||
FileLoader *git_commands.FileLoader
|
||||
@@ -63,7 +63,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 +117,6 @@ func NewGitCommand(
|
||||
gitConfig,
|
||||
repoPaths,
|
||||
repository,
|
||||
syncMutex,
|
||||
), nil
|
||||
}
|
||||
|
||||
@@ -129,7 +127,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 +137,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,15 +156,14 @@ 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)
|
||||
worktreeCommands := git_commands.NewWorktreeCommands(gitCommon)
|
||||
|
||||
branchLoader := git_commands.NewBranchLoader(cmn, cmd, branchCommands.CurrentBranchInfo, configCommands)
|
||||
mergedBranchLoader := git_commands.NewMergedBranchLoader(gitCommon)
|
||||
commitFileLoader := git_commands.NewCommitFileLoader(cmn, cmd)
|
||||
commitLoader := git_commands.NewCommitLoader(cmn, cmd, statusCommands.RebaseMode, gitCommon)
|
||||
reflogCommitLoader := git_commands.NewReflogCommitLoader(cmn, cmd)
|
||||
@@ -206,6 +202,7 @@ func NewGitCommandAux(
|
||||
Worktrees: worktreeLoader,
|
||||
StashLoader: stashLoader,
|
||||
TagLoader: tagLoader,
|
||||
MergedBranchLoader: mergedBranchLoader,
|
||||
},
|
||||
RepoPaths: repoPaths,
|
||||
}
|
||||
|
||||
@@ -125,6 +125,15 @@ func (self *BisectCommands) Start() error {
|
||||
return self.cmd.New(cmdArgs).StreamOutput().Run()
|
||||
}
|
||||
|
||||
func (self *BisectCommands) StartWithTerms(oldTerm string, newTerm string) error {
|
||||
cmdArgs := NewGitCmd("bisect").Arg("start").
|
||||
Arg("--term-old=" + oldTerm).
|
||||
Arg("--term-new=" + newTerm).
|
||||
ToArgv()
|
||||
|
||||
return self.cmd.New(cmdArgs).StreamOutput().Run()
|
||||
}
|
||||
|
||||
// tells us whether we've found our problem commit(s). We return a string slice of
|
||||
// commit sha's if we're done, and that slice may have more that one item if
|
||||
// skipped commits are involved.
|
||||
|
||||
@@ -2,7 +2,7 @@ package git_commands
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/generics/maps"
|
||||
"github.com/jesseduffield/generics/slices"
|
||||
"github.com/samber/lo"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
@@ -97,5 +97,5 @@ func (self *BisectInfo) Bisecting() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
return slices.Contains(maps.Values(self.statusMap), BisectStatusOld)
|
||||
return lo.Contains(maps.Values(self.statusMap), BisectStatusOld)
|
||||
}
|
||||
|
||||
@@ -70,8 +70,23 @@ func (self *BranchCommands) CurrentBranchInfo() (BranchInfo, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Delete delete branch
|
||||
func (self *BranchCommands) Delete(branch string, force bool) error {
|
||||
// CurrentBranchName get name of current branch
|
||||
func (self *BranchCommands) CurrentBranchName() (string, error) {
|
||||
cmdArgs := NewGitCmd("rev-parse").
|
||||
Arg("--abbrev-ref").
|
||||
Arg("--verify").
|
||||
Arg("HEAD").
|
||||
ToArgv()
|
||||
|
||||
output, err := self.cmd.New(cmdArgs).DontLog().RunWithOutput()
|
||||
if err == nil {
|
||||
return strings.TrimSpace(output), nil
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
|
||||
// LocalDelete delete branch locally
|
||||
func (self *BranchCommands) LocalDelete(branch string, force bool) error {
|
||||
cmdArgs := NewGitCmd("branch").
|
||||
ArgIfElse(force, "-D", "-d").
|
||||
Arg(branch).
|
||||
|
||||
@@ -6,13 +6,13 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/jesseduffield/generics/set"
|
||||
"github.com/jesseduffield/generics/slices"
|
||||
"github.com/jesseduffield/go-git/v5/config"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
|
||||
"github.com/jesseduffield/lazygit/pkg/common"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
"github.com/samber/lo"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
// context:
|
||||
@@ -75,7 +75,7 @@ outer:
|
||||
if strings.EqualFold(reflogBranch.Name, branch.Name) {
|
||||
branch.Recency = reflogBranch.Recency
|
||||
branchesWithRecency = append(branchesWithRecency, branch)
|
||||
branches = slices.Remove(branches, j)
|
||||
branches = utils.Remove(branches, j)
|
||||
continue outer
|
||||
}
|
||||
}
|
||||
@@ -87,14 +87,14 @@ outer:
|
||||
return a.Name < b.Name
|
||||
})
|
||||
|
||||
branches = slices.Prepend(branches, branchesWithRecency...)
|
||||
branches = utils.Prepend(branches, branchesWithRecency...)
|
||||
|
||||
foundHead := false
|
||||
for i, branch := range branches {
|
||||
if branch.Head {
|
||||
foundHead = true
|
||||
branch.Recency = " *"
|
||||
branches = slices.Move(branches, i, 0)
|
||||
branches = utils.Move(branches, i, 0)
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -103,7 +103,7 @@ outer:
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
branches = slices.Prepend(branches, &models.Branch{Name: info.RefName, DisplayName: info.DisplayName, Head: true, DetachedHead: info.DetachedHead, Recency: " *"})
|
||||
branches = utils.Prepend(branches, &models.Branch{Name: info.RefName, DisplayName: info.DisplayName, Head: true, DetachedHead: info.DetachedHead, Recency: " *"})
|
||||
}
|
||||
|
||||
configBranches, err := self.config.Branches()
|
||||
@@ -131,7 +131,7 @@ func (self *BranchLoader) obtainBranches() []*models.Branch {
|
||||
trimmedOutput := strings.TrimSpace(output)
|
||||
outputLines := strings.Split(trimmedOutput, "\n")
|
||||
|
||||
return slices.FilterMap(outputLines, func(line string) (*models.Branch, bool) {
|
||||
return lo.FilterMap(outputLines, func(line string, _ int) (*models.Branch, bool) {
|
||||
if line == "" {
|
||||
return nil, false
|
||||
}
|
||||
@@ -171,7 +171,7 @@ var branchFields = []string{
|
||||
"upstream:short",
|
||||
"upstream:track",
|
||||
"subject",
|
||||
fmt.Sprintf("objectname:short=%d", utils.COMMIT_HASH_SHORT_SIZE),
|
||||
"objectname",
|
||||
}
|
||||
|
||||
// Obtain branch information from parsed line output of getRawBranches()
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ package git_commands
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/jesseduffield/generics/slices"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
|
||||
"github.com/jesseduffield/lazygit/pkg/common"
|
||||
@@ -52,7 +51,7 @@ func getCommitFilesFromFilenames(filenames string) []*models.CommitFile {
|
||||
}
|
||||
|
||||
// typical result looks like 'A my_file' meaning my_file was added
|
||||
return slices.Map(lo.Chunk(lines, 2), func(chunk []string) *models.CommitFile {
|
||||
return lo.Map(lo.Chunk(lines, 2), func(chunk []string, _ int) *models.CommitFile {
|
||||
return &models.CommitFile{
|
||||
ChangeStatus: chunk[0],
|
||||
Name: chunk[1],
|
||||
|
||||
@@ -6,12 +6,12 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/fsmiamoto/git-todo-parser/todo"
|
||||
"github.com/jesseduffield/generics/slices"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/types/enums"
|
||||
@@ -66,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
|
||||
@@ -93,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
|
||||
@@ -124,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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,8 +144,23 @@ func (self *CommitLoader) GetCommits(opts GetCommitsOptions) ([]*models.Commit,
|
||||
return commits, nil
|
||||
}
|
||||
|
||||
if ancestor != "" {
|
||||
commits = self.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
|
||||
@@ -177,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]
|
||||
@@ -187,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{}
|
||||
|
||||
@@ -220,6 +248,7 @@ func (self *CommitLoader) extractCommitFromLine(line string) *models.Commit {
|
||||
AuthorName: authorName,
|
||||
AuthorEmail: authorEmail,
|
||||
Parents: parents,
|
||||
Divergence: divergence,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -233,7 +262,7 @@ func (self *CommitLoader) getHydratedRebasingCommits(rebaseMode enums.RebaseMode
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
commitShas := slices.FilterMap(commits, func(commit *models.Commit) (string, bool) {
|
||||
commitShas := lo.FilterMap(commits, func(commit *models.Commit, _ int) (string, bool) {
|
||||
return commit.Sha, commit.Sha != ""
|
||||
})
|
||||
|
||||
@@ -249,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
|
||||
})
|
||||
@@ -379,7 +408,7 @@ func (self *CommitLoader) getInteractiveRebasingCommits() ([]*models.Commit, err
|
||||
// Command does not have a commit associated, skip
|
||||
continue
|
||||
}
|
||||
commits = slices.Prepend(commits, &models.Commit{
|
||||
commits = utils.Prepend(commits, &models.Commit{
|
||||
Sha: t.Commit,
|
||||
Name: t.Msg,
|
||||
Status: models.StatusRebasing,
|
||||
@@ -493,10 +522,15 @@ func (self *CommitLoader) commitFromPatch(content string) *models.Commit {
|
||||
}
|
||||
}
|
||||
|
||||
func (self *CommitLoader) 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 {
|
||||
if strings.HasPrefix(ancestor, commit.Sha) {
|
||||
// some commits aren't really commits and don't have sha's, such as the update-ref todo
|
||||
if commit.Sha != "" && strings.HasPrefix(ancestor, commit.Sha) {
|
||||
passedAncestor = true
|
||||
}
|
||||
if commit.Status != models.StatusPushed && commit.Status != models.StatusUnpushed {
|
||||
@@ -506,7 +540,6 @@ func (self *CommitLoader) setCommitMergedStatuses(ancestor string, commits []*mo
|
||||
commits[i].Status = models.StatusMerged
|
||||
}
|
||||
}
|
||||
return commits
|
||||
}
|
||||
|
||||
func (self *CommitLoader) getMergeBase(refName string) string {
|
||||
@@ -535,50 +568,26 @@ func (self *CommitLoader) getMergeBase(refName string) string {
|
||||
}
|
||||
|
||||
func (self *CommitLoader) getExistingMainBranches() []string {
|
||||
var existingBranches []string
|
||||
var wg sync.WaitGroup
|
||||
|
||||
mainBranches := self.UserConfig.Git.MainBranches
|
||||
existingBranches = make([]string, len(mainBranches))
|
||||
|
||||
for i, branchName := range mainBranches {
|
||||
wg.Add(1)
|
||||
i := i
|
||||
branchName := branchName
|
||||
go utils.Safe(func() {
|
||||
defer wg.Done()
|
||||
existingBranches := utils.ConcurrentMap(mainBranches, func(mainBranch string) string {
|
||||
// Try to determine upstream of local main branch
|
||||
if ref, err := self.cmd.New(
|
||||
NewGitCmd("rev-parse").Arg("--symbolic-full-name", mainBranch+"@{u}").ToArgv(),
|
||||
).DontLog().RunWithOutput(); err == nil {
|
||||
return strings.TrimSpace(ref)
|
||||
}
|
||||
|
||||
// Try to determine upstream of local main branch
|
||||
if ref, err := self.cmd.New(
|
||||
NewGitCmd("rev-parse").Arg("--symbolic-full-name", branchName+"@{u}").ToArgv(),
|
||||
).DontLog().RunWithOutput(); err == nil {
|
||||
existingBranches[i] = strings.TrimSpace(ref)
|
||||
return
|
||||
}
|
||||
// If this failed, fallback to the local branch
|
||||
ref := "refs/heads/" + mainBranch
|
||||
if err := self.cmd.New(
|
||||
NewGitCmd("rev-parse").Arg("--verify", "--quiet", ref).ToArgv(),
|
||||
).DontLog().Run(); err == nil {
|
||||
return ref
|
||||
}
|
||||
|
||||
// If this failed, a local branch for this main branch doesn't exist or it
|
||||
// has no upstream configured. Try looking for one in the "origin" remote.
|
||||
ref := "refs/remotes/origin/" + branchName
|
||||
if err := self.cmd.New(
|
||||
NewGitCmd("rev-parse").Arg("--verify", "--quiet", ref).ToArgv(),
|
||||
).DontLog().Run(); err == nil {
|
||||
existingBranches[i] = ref
|
||||
return
|
||||
}
|
||||
|
||||
// If this failed as well, try if we have the main branch as a local
|
||||
// branch. This covers the case where somebody is using git locally
|
||||
// for something, but never pushing anywhere.
|
||||
ref = "refs/heads/" + branchName
|
||||
if err := self.cmd.New(
|
||||
NewGitCmd("rev-parse").Arg("--verify", "--quiet", ref).ToArgv(),
|
||||
).DontLog().Run(); err == nil {
|
||||
existingBranches[i] = ref
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
return ""
|
||||
})
|
||||
|
||||
existingBranches = lo.Filter(existingBranches, func(branch string, _ int) bool {
|
||||
return branch != ""
|
||||
@@ -619,8 +628,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 +643,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 +651,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,20 +66,18 @@ 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
|
||||
ExpectGitArgs([]string{"rev-parse", "--verify", "--quiet", "refs/remotes/origin/main"}, "", nil). // yep, origin/main exists
|
||||
ExpectGitArgs([]string{"rev-parse", "--symbolic-full-name", "develop@{u}"}, "", errors.New("error")). // this one doesn't, so it checks origin instead
|
||||
ExpectGitArgs([]string{"rev-parse", "--verify", "--quiet", "refs/remotes/origin/develop"}, "", errors.New("error")). // doesn't exist there, either, so it checks for a local branch
|
||||
ExpectGitArgs([]string{"rev-parse", "--verify", "--quiet", "refs/heads/develop"}, "", errors.New("error")). // no local branch either
|
||||
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}"}, "refs/remotes/origin/main", nil). // this one does too
|
||||
ExpectGitArgs([]string{"rev-parse", "--symbolic-full-name", "develop@{u}"}, "", errors.New("error")). // this one doesn't, so check for a local branch
|
||||
ExpectGitArgs([]string{"rev-parse", "--verify", "--quiet", "refs/heads/develop"}, "", errors.New("error")). // no local branch either
|
||||
// here it's seeing where our branch diverged from the master branch so that we can mark that commit and parent commits as 'merged'
|
||||
ExpectGitArgs([]string{"merge-base", "HEAD", "refs/remotes/origin/master", "refs/remotes/origin/main"}, "26c07b1ab33860a1a7591a0638f9925ccf497ffa", nil),
|
||||
|
||||
@@ -203,19 +201,17 @@ 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")).
|
||||
ExpectGitArgs([]string{"rev-parse", "--verify", "--quiet", "refs/heads/master"}, "", errors.New("error")).
|
||||
ExpectGitArgs([]string{"rev-parse", "--symbolic-full-name", "main@{u}"}, "", errors.New("error")).
|
||||
ExpectGitArgs([]string{"rev-parse", "--verify", "--quiet", "refs/remotes/origin/main"}, "", errors.New("error")).
|
||||
ExpectGitArgs([]string{"rev-parse", "--verify", "--quiet", "refs/heads/main"}, "", errors.New("error")),
|
||||
|
||||
expectedCommits: []*models.Commit{
|
||||
@@ -240,17 +236,16 @@ 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")).
|
||||
ExpectGitArgs([]string{"rev-parse", "--verify", "--quiet", "refs/remotes/origin/main"}, "", errors.New("error")).
|
||||
ExpectGitArgs([]string{"rev-parse", "--verify", "--quiet", "refs/heads/main"}, "", errors.New("error")).
|
||||
ExpectGitArgs([]string{"rev-parse", "--symbolic-full-name", "develop@{u}"}, "refs/remotes/origin/develop", nil).
|
||||
ExpectGitArgs([]string{"rev-parse", "--symbolic-full-name", "1.0-hotfixes@{u}"}, "refs/remotes/origin/1.0-hotfixes", nil).
|
||||
@@ -279,10 +274,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 +286,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,
|
||||
@@ -506,3 +501,51 @@ func TestCommitLoader_getConflictedCommitImpl(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommitLoader_setCommitMergedStatuses(t *testing.T) {
|
||||
type scenario struct {
|
||||
testName string
|
||||
commits []*models.Commit
|
||||
ancestor string
|
||||
expectedCommits []*models.Commit
|
||||
}
|
||||
|
||||
scenarios := []scenario{
|
||||
{
|
||||
testName: "basic",
|
||||
commits: []*models.Commit{
|
||||
{Sha: "12345", Name: "1", Action: models.ActionNone, Status: models.StatusUnpushed},
|
||||
{Sha: "67890", Name: "2", Action: models.ActionNone, Status: models.StatusPushed},
|
||||
{Sha: "abcde", Name: "3", Action: models.ActionNone, Status: models.StatusPushed},
|
||||
},
|
||||
ancestor: "67890",
|
||||
expectedCommits: []*models.Commit{
|
||||
{Sha: "12345", Name: "1", Action: models.ActionNone, Status: models.StatusUnpushed},
|
||||
{Sha: "67890", Name: "2", Action: models.ActionNone, Status: models.StatusMerged},
|
||||
{Sha: "abcde", Name: "3", Action: models.ActionNone, Status: models.StatusMerged},
|
||||
},
|
||||
},
|
||||
{
|
||||
testName: "with update-ref",
|
||||
commits: []*models.Commit{
|
||||
{Sha: "12345", Name: "1", Action: models.ActionNone, Status: models.StatusUnpushed},
|
||||
{Sha: "", Name: "", Action: todo.UpdateRef, Status: models.StatusNone},
|
||||
{Sha: "abcde", Name: "3", Action: models.ActionNone, Status: models.StatusPushed},
|
||||
},
|
||||
ancestor: "deadbeef",
|
||||
expectedCommits: []*models.Commit{
|
||||
{Sha: "12345", Name: "1", Action: models.ActionNone, Status: models.StatusUnpushed},
|
||||
{Sha: "", Name: "", Action: todo.UpdateRef, Status: models.StatusNone},
|
||||
{Sha: "abcde", Name: "3", Action: models.ActionNone, Status: models.StatusPushed},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, scenario := range scenarios {
|
||||
t.Run(scenario.testName, func(t *testing.T) {
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,3 +107,7 @@ func (self *ConfigCommands) GetCoreCommentChar() byte {
|
||||
|
||||
return '#'
|
||||
}
|
||||
|
||||
func (self *ConfigCommands) GetRebaseUpdateRefs() bool {
|
||||
return self.gitConfig.GetBool("rebase.updateRefs")
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
package git_commands
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/generics/set"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
)
|
||||
|
||||
// Returns a set of branch names which are merged into a main branch.
|
||||
// This
|
||||
type MergedBranchLoader struct {
|
||||
c *GitCommon
|
||||
}
|
||||
|
||||
func NewMergedBranchLoader(c *GitCommon) *MergedBranchLoader {
|
||||
return &MergedBranchLoader{c: c}
|
||||
}
|
||||
|
||||
// TODO: check against upstreams, and share code with commit loader that determines main branches
|
||||
func (self *MergedBranchLoader) Load() *set.Set[string] {
|
||||
set := set.New[string]()
|
||||
|
||||
mainBranches := self.c.UserConfig.Git.MainBranches
|
||||
results := utils.ConcurrentMap(mainBranches, func(mainBranch string) []string {
|
||||
mergedBranches, err := self.GetMergedBranches(mainBranch)
|
||||
if err != nil {
|
||||
self.c.Log.Warnf("Failed to get merged branches for %s: %s", mainBranch, err)
|
||||
return nil
|
||||
}
|
||||
return mergedBranches
|
||||
})
|
||||
|
||||
for i := 0; i < len(mainBranches); i++ {
|
||||
for _, mergedBranch := range results[i] {
|
||||
set.Add(mergedBranch)
|
||||
}
|
||||
}
|
||||
|
||||
return set
|
||||
}
|
||||
|
||||
func (self *MergedBranchLoader) GetMergedBranches(mainBranch string) ([]string, error) {
|
||||
// git for-each-ref --merged master --format '%(refname)' refs/heads/
|
||||
cmdArgs := NewGitCmd("for-each-ref").Arg(
|
||||
"--merged",
|
||||
mainBranch,
|
||||
"--format",
|
||||
"%(refname)",
|
||||
"refs/heads/",
|
||||
).ToArgv()
|
||||
|
||||
output, err := self.c.cmd.New(cmdArgs).DontLog().RunWithOutput()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
branches := utils.SplitLines(output)
|
||||
|
||||
return branches, nil
|
||||
}
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
|
||||
"github.com/fsmiamoto/git-todo-parser/todo"
|
||||
"github.com/go-errors/errors"
|
||||
"github.com/jesseduffield/generics/slices"
|
||||
"github.com/jesseduffield/lazygit/pkg/app/daemon"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
|
||||
@@ -80,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
|
||||
@@ -105,7 +110,13 @@ func (self *RebaseCommands) MoveCommitDown(commits []*models.Commit, index int)
|
||||
|
||||
sha := commits[index].Sha
|
||||
|
||||
self.os.LogCommand(fmt.Sprintf("Moving TODO down: %s", utils.ShortSha(sha)), false)
|
||||
msg := utils.ResolvePlaceholderString(
|
||||
self.Tr.Log.MoveCommitDown,
|
||||
map[string]string{
|
||||
"shortSha": utils.ShortSha(sha),
|
||||
},
|
||||
)
|
||||
self.os.LogCommand(msg, false)
|
||||
|
||||
return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{
|
||||
baseShaOrRoot: baseShaOrRoot,
|
||||
@@ -119,7 +130,13 @@ func (self *RebaseCommands) MoveCommitUp(commits []*models.Commit, index int) er
|
||||
|
||||
sha := commits[index].Sha
|
||||
|
||||
self.os.LogCommand(fmt.Sprintf("Moving TODO up: %s", utils.ShortSha(sha)), false)
|
||||
msg := utils.ResolvePlaceholderString(
|
||||
self.Tr.Log.MoveCommitUp,
|
||||
map[string]string{
|
||||
"shortSha": utils.ShortSha(sha),
|
||||
},
|
||||
)
|
||||
self.os.LogCommand(msg, false)
|
||||
|
||||
return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{
|
||||
baseShaOrRoot: baseShaOrRoot,
|
||||
@@ -150,15 +167,37 @@ func (self *RebaseCommands) InteractiveRebase(commits []*models.Commit, index in
|
||||
}
|
||||
|
||||
func (self *RebaseCommands) EditRebase(branchRef string) error {
|
||||
self.os.LogCommand(fmt.Sprintf("Beginning interactive rebase at '%s'", branchRef), false)
|
||||
msg := utils.ResolvePlaceholderString(
|
||||
self.Tr.Log.EditRebase,
|
||||
map[string]string{
|
||||
"ref": branchRef,
|
||||
},
|
||||
)
|
||||
self.os.LogCommand(msg, false)
|
||||
return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{
|
||||
baseShaOrRoot: branchRef,
|
||||
instruction: daemon.NewInsertBreakInstruction(),
|
||||
}).Run()
|
||||
}
|
||||
|
||||
func (self *RebaseCommands) EditRebaseFromBaseCommit(targetBranchName string, baseCommit string) error {
|
||||
msg := utils.ResolvePlaceholderString(
|
||||
self.Tr.Log.EditRebaseFromBaseCommit,
|
||||
map[string]string{
|
||||
"baseCommit": baseCommit,
|
||||
"targetBranchName": targetBranchName,
|
||||
},
|
||||
)
|
||||
self.os.LogCommand(msg, false)
|
||||
return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{
|
||||
baseShaOrRoot: baseCommit,
|
||||
onto: targetBranchName,
|
||||
instruction: daemon.NewInsertBreakInstruction(),
|
||||
}).Run()
|
||||
}
|
||||
|
||||
func logTodoChanges(changes []daemon.ChangeTodoAction) string {
|
||||
changeTodoStr := strings.Join(slices.Map(changes, func(c daemon.ChangeTodoAction) string {
|
||||
changeTodoStr := strings.Join(lo.Map(changes, func(c daemon.ChangeTodoAction, _ int) string {
|
||||
return fmt.Sprintf("%s:%s", c.Sha, c.NewAction)
|
||||
}), "\n")
|
||||
return fmt.Sprintf("Changing TODO actions: %s", changeTodoStr)
|
||||
@@ -166,6 +205,7 @@ func logTodoChanges(changes []daemon.ChangeTodoAction) string {
|
||||
|
||||
type PrepareInteractiveRebaseCommandOpts struct {
|
||||
baseShaOrRoot string
|
||||
onto string
|
||||
instruction daemon.Instruction
|
||||
overrideEditor bool
|
||||
keepCommitsThatBecomeEmpty bool
|
||||
@@ -184,6 +224,7 @@ func (self *RebaseCommands) PrepareInteractiveRebaseCommand(opts PrepareInteract
|
||||
ArgIf(opts.keepCommitsThatBecomeEmpty && !self.version.IsOlderThan(2, 26, 0), "--empty=keep").
|
||||
Arg("--no-autosquash").
|
||||
ArgIf(!self.version.IsOlderThan(2, 22, 0), "--rebase-merges").
|
||||
ArgIf(opts.onto != "", "--onto", opts.onto).
|
||||
Arg(opts.baseShaOrRoot).
|
||||
ToArgv()
|
||||
|
||||
@@ -307,6 +348,13 @@ func (self *RebaseCommands) RebaseBranch(branchName string) error {
|
||||
return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{baseShaOrRoot: branchName}).Run()
|
||||
}
|
||||
|
||||
func (self *RebaseCommands) RebaseBranchFromBaseCommit(targetBranchName string, baseCommit string) error {
|
||||
return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{
|
||||
baseShaOrRoot: baseCommit,
|
||||
onto: targetBranchName,
|
||||
}).Run()
|
||||
}
|
||||
|
||||
func (self *RebaseCommands) GenericMergeOrRebaseActionCmdObj(commandType string, command string) oscommands.ICmdObj {
|
||||
cmdArgs := NewGitCmd(commandType).Arg("--" + command).ToArgv()
|
||||
|
||||
@@ -395,7 +443,13 @@ func (self *RebaseCommands) CherryPickCommits(commits []*models.Commit) error {
|
||||
commitLines := lo.Map(commits, func(commit *models.Commit, _ int) string {
|
||||
return fmt.Sprintf("%s %s", utils.ShortSha(commit.Sha), commit.Name)
|
||||
})
|
||||
self.os.LogCommand(fmt.Sprintf("Cherry-picking commits:\n%s", strings.Join(commitLines, "\n")), false)
|
||||
msg := utils.ResolvePlaceholderString(
|
||||
self.Tr.Log.CherryPickCommits,
|
||||
map[string]string{
|
||||
"commitLines": strings.Join(commitLines, "\n"),
|
||||
},
|
||||
)
|
||||
self.os.LogCommand(msg, false)
|
||||
|
||||
return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{
|
||||
baseShaOrRoot: "HEAD",
|
||||
@@ -403,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
|
||||
|
||||
@@ -4,12 +4,13 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/jesseduffield/generics/slices"
|
||||
gogit "github.com/jesseduffield/go-git/v5"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
|
||||
"github.com/jesseduffield/lazygit/pkg/common"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
"github.com/samber/lo"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
type RemoteLoader struct {
|
||||
@@ -53,7 +54,7 @@ func (self *RemoteLoader) GetRemotes() ([]*models.Remote, error) {
|
||||
return nil, remoteBranchesErr
|
||||
}
|
||||
|
||||
remotes := slices.Map(goGitRemotes, func(goGitRemote *gogit.Remote) *models.Remote {
|
||||
remotes := lo.Map(goGitRemotes, func(goGitRemote *gogit.Remote, _ int) *models.Remote {
|
||||
remoteName := goGitRemote.Config().Name
|
||||
branches := remoteBranchesByRemoteName[remoteName]
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -5,11 +5,11 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/jesseduffield/generics/slices"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
|
||||
"github.com/jesseduffield/lazygit/pkg/common"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
type StashLoader struct {
|
||||
@@ -69,7 +69,7 @@ func (self *StashLoader) getUnfilteredStashEntries() []*models.StashEntry {
|
||||
cmdArgs := NewGitCmd("stash").Arg("list", "-z", "--pretty=%gs").ToArgv()
|
||||
|
||||
rawString, _ := self.cmd.New(cmdArgs).DontLog().RunWithOutput()
|
||||
return slices.MapWithIndex(utils.SplitNul(rawString), func(line string, index int) *models.StashEntry {
|
||||
return lo.Map(utils.SplitNul(rawString), func(line string, index int) *models.StashEntry {
|
||||
return self.stashEntryFromLine(line, index)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package git_commands
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -24,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 {
|
||||
@@ -67,7 +65,26 @@ 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"))
|
||||
}
|
||||
|
||||
// Full ref (e.g. "refs/heads/mybranch") of the branch that is currently
|
||||
// being rebased, or empty string when we're not in a rebase
|
||||
func (self *StatusCommands) BranchBeingRebased() string {
|
||||
for _, dir := range []string{"rebase-merge", "rebase-apply"} {
|
||||
if bytesContent, err := os.ReadFile(filepath.Join(self.repoPaths.WorktreeGitDirPath(), dir, "head-name")); err == nil {
|
||||
return strings.TrimSpace(string(bytesContent))
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -12,16 +12,19 @@ func NewTagCommands(gitCommon *GitCommon) *TagCommands {
|
||||
}
|
||||
}
|
||||
|
||||
func (self *TagCommands) CreateLightweight(tagName string, ref string) error {
|
||||
cmdArgs := NewGitCmd("tag").Arg("--", tagName).
|
||||
func (self *TagCommands) CreateLightweight(tagName string, ref string, force bool) error {
|
||||
cmdArgs := NewGitCmd("tag").
|
||||
ArgIf(force, "--force").
|
||||
Arg("--", tagName).
|
||||
ArgIf(len(ref) > 0, ref).
|
||||
ToArgv()
|
||||
|
||||
return self.cmd.New(cmdArgs).Run()
|
||||
}
|
||||
|
||||
func (self *TagCommands) CreateAnnotated(tagName, ref, msg string) error {
|
||||
func (self *TagCommands) CreateAnnotated(tagName, ref, msg string, force bool) error {
|
||||
cmdArgs := NewGitCmd("tag").Arg(tagName).
|
||||
ArgIf(force, "--force").
|
||||
ArgIf(len(ref) > 0, ref).
|
||||
Arg("-m", msg).
|
||||
ToArgv()
|
||||
@@ -29,7 +32,16 @@ func (self *TagCommands) CreateAnnotated(tagName, ref, msg string) error {
|
||||
return self.cmd.New(cmdArgs).Run()
|
||||
}
|
||||
|
||||
func (self *TagCommands) Delete(tagName string) error {
|
||||
func (self *TagCommands) HasTag(tagName string) bool {
|
||||
cmdArgs := NewGitCmd("show-ref").
|
||||
Arg("--tags", "--quiet", "--verify", "--").
|
||||
Arg("refs/tags/" + tagName).
|
||||
ToArgv()
|
||||
|
||||
return self.cmd.New(cmdArgs).Run() == nil
|
||||
}
|
||||
|
||||
func (self *TagCommands) LocalDelete(tagName string) error {
|
||||
cmdArgs := NewGitCmd("tag").Arg("-d", tagName).
|
||||
ToArgv()
|
||||
|
||||
@@ -40,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()
|
||||
}
|
||||
|
||||
@@ -3,11 +3,11 @@ package git_commands
|
||||
import (
|
||||
"regexp"
|
||||
|
||||
"github.com/jesseduffield/generics/slices"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
|
||||
"github.com/jesseduffield/lazygit/pkg/common"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
type TagLoader struct {
|
||||
@@ -38,7 +38,7 @@ func (self *TagLoader) GetTags() ([]*models.Tag, error) {
|
||||
|
||||
lineRegex := regexp.MustCompile(`^([^\s]+)(\s+)?(.*)$`)
|
||||
|
||||
tags := slices.Map(split, func(line string) *models.Tag {
|
||||
tags := lo.Map(split, func(line string, _ int) *models.Tag {
|
||||
matches := lineRegex.FindStringSubmatch(line)
|
||||
tagName := matches[1]
|
||||
message := ""
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -7,8 +7,6 @@ import (
|
||||
"os/exec"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/secureexec"
|
||||
)
|
||||
|
||||
// including license from https://github.com/tcnksm/go-gitconfig because this file is an adaptation of that repo's code
|
||||
@@ -55,10 +53,10 @@ func runGitConfigCmd(cmd *exec.Cmd) (string, error) {
|
||||
|
||||
func getGitConfigCmd(key string) *exec.Cmd {
|
||||
gitArgs := []string{"config", "--get", "--null", key}
|
||||
return secureexec.Command("git", gitArgs...)
|
||||
return exec.Command("git", gitArgs...)
|
||||
}
|
||||
|
||||
func getGitConfigGeneralCmd(args string) *exec.Cmd {
|
||||
gitArgs := append([]string{"config"}, strings.Split(args, " ")...)
|
||||
return secureexec.Command("git", gitArgs...)
|
||||
return exec.Command("git", gitArgs...)
|
||||
}
|
||||
|
||||
@@ -8,9 +8,10 @@ import (
|
||||
"github.com/go-errors/errors"
|
||||
"github.com/jesseduffield/lazygit/pkg/i18n"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
"github.com/samber/lo"
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/jesseduffield/generics/slices"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
// This package is for handling logic specific to a git hosting service like github, gitlab, bitbucket, gitea, etc.
|
||||
@@ -98,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 := slices.Map(serviceDefinitions, func(serviceDefinition ServiceDefinition) 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
|
||||
}
|
||||
|
||||
@@ -73,10 +73,6 @@ type ICmdObj interface {
|
||||
}
|
||||
|
||||
type CmdObj struct {
|
||||
// the secureexec package will swap out the first arg with the full path to the binary,
|
||||
// so we store these args separately so that ToString() will output the original
|
||||
args []string
|
||||
|
||||
cmd *exec.Cmd
|
||||
|
||||
runner ICmdObjRunner
|
||||
@@ -121,7 +117,7 @@ func (self *CmdObj) GetCmd() *exec.Cmd {
|
||||
|
||||
func (self *CmdObj) ToString() string {
|
||||
// if a given arg contains a space, we need to wrap it in quotes
|
||||
quotedArgs := lo.Map(self.args, func(arg string, _ int) string {
|
||||
quotedArgs := lo.Map(self.cmd.Args, func(arg string, _ int) string {
|
||||
if strings.Contains(arg, " ") {
|
||||
return `"` + arg + `"`
|
||||
}
|
||||
@@ -132,7 +128,7 @@ func (self *CmdObj) ToString() string {
|
||||
}
|
||||
|
||||
func (self *CmdObj) Args() []string {
|
||||
return self.args
|
||||
return self.cmd.Args
|
||||
}
|
||||
|
||||
func (self *CmdObj) AddEnvVars(vars ...string) ICmdObj {
|
||||
|
||||
@@ -3,9 +3,9 @@ package oscommands
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/secureexec"
|
||||
"github.com/mgutz/str"
|
||||
)
|
||||
|
||||
@@ -27,11 +27,10 @@ type CmdObjBuilder struct {
|
||||
var _ ICmdObjBuilder = &CmdObjBuilder{}
|
||||
|
||||
func (self *CmdObjBuilder) New(args []string) ICmdObj {
|
||||
cmd := secureexec.Command(args[0], args[1:]...)
|
||||
cmd := exec.Command(args[0], args[1:]...)
|
||||
cmd.Env = os.Environ()
|
||||
|
||||
return &CmdObj{
|
||||
args: args,
|
||||
cmd: cmd,
|
||||
runner: self.runner,
|
||||
}
|
||||
|
||||
@@ -27,7 +27,8 @@ func TestCmdObjToString(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, scenario := range scenarios {
|
||||
cmdObj := &CmdObj{args: scenario.cmdArgs}
|
||||
cmd := exec.Command(scenario.cmdArgs[0], scenario.cmdArgs[1:]...)
|
||||
cmdObj := &CmdObj{cmd: cmd}
|
||||
actual := cmdObj.ToString()
|
||||
if actual != scenario.expected {
|
||||
t.Errorf("Expected %s, got %s", quote(scenario.expected), quote(actual))
|
||||
|
||||
@@ -3,7 +3,6 @@ package oscommands
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
@@ -106,7 +105,7 @@ func CopyDir(src string, dst string) (err error) {
|
||||
return //nolint: nakedret
|
||||
}
|
||||
|
||||
entries, err := ioutil.ReadDir(src)
|
||||
entries, err := os.ReadDir(src)
|
||||
if err != nil {
|
||||
return //nolint: nakedret
|
||||
}
|
||||
@@ -121,8 +120,14 @@ func CopyDir(src string, dst string) (err error) {
|
||||
return //nolint: nakedret
|
||||
}
|
||||
} else {
|
||||
var info os.FileInfo
|
||||
info, err = entry.Info()
|
||||
if err != nil {
|
||||
return //nolint: nakedret
|
||||
}
|
||||
|
||||
// Skip symlinks.
|
||||
if entry.Mode()&os.ModeSymlink != 0 {
|
||||
if info.Mode()&os.ModeSymlink != 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
@@ -3,8 +3,6 @@ package oscommands
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
@@ -124,27 +122,7 @@ func (self *FakeCmdObjRunner) ExpectFunc(description string, fn func(cmdObj ICmd
|
||||
func (self *FakeCmdObjRunner) ExpectArgs(expectedArgs []string, output string, err error) *FakeCmdObjRunner {
|
||||
description := fmt.Sprintf("matches args %s", strings.Join(expectedArgs, " "))
|
||||
self.ExpectFunc(description, func(cmdObj ICmdObj) bool {
|
||||
args := cmdObj.GetCmd().Args
|
||||
|
||||
if runtime.GOOS == "windows" {
|
||||
// thanks to the secureexec package, the first arg is something like
|
||||
// '"C:\\Program Files\\Git\\mingw64\\bin\\<command>.exe"
|
||||
// on windows so we'll just ensure it contains our program
|
||||
if !strings.Contains(args[0], expectedArgs[0]) {
|
||||
return false
|
||||
}
|
||||
} else {
|
||||
// first arg is the program name
|
||||
if expectedArgs[0] != args[0] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
if !slices.Equal(expectedArgs[1:], args[1:]) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
return slices.Equal(expectedArgs, cmdObj.GetCmd().Args)
|
||||
}, output, err)
|
||||
|
||||
return self
|
||||
@@ -153,18 +131,7 @@ func (self *FakeCmdObjRunner) ExpectArgs(expectedArgs []string, output string, e
|
||||
func (self *FakeCmdObjRunner) ExpectGitArgs(expectedArgs []string, output string, err error) *FakeCmdObjRunner {
|
||||
description := fmt.Sprintf("matches git args %s", strings.Join(expectedArgs, " "))
|
||||
self.ExpectFunc(description, func(cmdObj ICmdObj) bool {
|
||||
// first arg is 'git' on unix and something like '"C:\\Program Files\\Git\\mingw64\\bin\\git.exe" on windows so we'll just ensure it ends in either 'git' or 'git.exe'
|
||||
re := regexp.MustCompile(`git(\.exe)?$`)
|
||||
args := cmdObj.GetCmd().Args
|
||||
if !re.MatchString(args[0]) {
|
||||
return false
|
||||
}
|
||||
|
||||
if !slices.Equal(expectedArgs, args[1:]) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
return slices.Equal(expectedArgs, cmdObj.GetCmd().Args[1:])
|
||||
}, output, err)
|
||||
|
||||
return self
|
||||
|
||||
@@ -13,7 +13,6 @@ import (
|
||||
"github.com/samber/lo"
|
||||
|
||||
"github.com/atotto/clipboard"
|
||||
"github.com/jesseduffield/generics/slices"
|
||||
"github.com/jesseduffield/kill"
|
||||
"github.com/jesseduffield/lazygit/pkg/common"
|
||||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
@@ -118,7 +117,15 @@ func (c *OSCommand) Quote(message string) string {
|
||||
|
||||
// AppendLineToFile adds a new line in file
|
||||
func (c *OSCommand) AppendLineToFile(filename, line string) error {
|
||||
c.LogCommand(fmt.Sprintf("Appending '%s' to file '%s'", line, filename), false)
|
||||
msg := utils.ResolvePlaceholderString(
|
||||
c.Tr.Log.AppendingLineToFile,
|
||||
map[string]string{
|
||||
"line": line,
|
||||
"filename": filename,
|
||||
},
|
||||
)
|
||||
c.LogCommand(msg, false)
|
||||
|
||||
f, err := os.OpenFile(filename, os.O_APPEND|os.O_RDWR|os.O_CREATE, 0o600)
|
||||
if err != nil {
|
||||
return utils.WrapError(err)
|
||||
@@ -155,7 +162,13 @@ func (c *OSCommand) AppendLineToFile(filename, line string) error {
|
||||
|
||||
// CreateFileWithContent creates a file with the given content
|
||||
func (c *OSCommand) CreateFileWithContent(path string, content string) error {
|
||||
c.LogCommand(fmt.Sprintf("Creating file '%s'", path), false)
|
||||
msg := utils.ResolvePlaceholderString(
|
||||
c.Tr.Log.CreateFileWithContent,
|
||||
map[string]string{
|
||||
"path": path,
|
||||
},
|
||||
)
|
||||
c.LogCommand(msg, false)
|
||||
if err := os.MkdirAll(filepath.Dir(path), os.ModePerm); err != nil {
|
||||
c.Log.Error(err)
|
||||
return err
|
||||
@@ -171,7 +184,13 @@ func (c *OSCommand) CreateFileWithContent(path string, content string) error {
|
||||
|
||||
// Remove removes a file or directory at the specified path
|
||||
func (c *OSCommand) Remove(filename string) error {
|
||||
c.LogCommand(fmt.Sprintf("Removing '%s'", filename), false)
|
||||
msg := utils.ResolvePlaceholderString(
|
||||
c.Tr.Log.Remove,
|
||||
map[string]string{
|
||||
"filename": filename,
|
||||
},
|
||||
)
|
||||
c.LogCommand(msg, false)
|
||||
err := os.RemoveAll(filename)
|
||||
return utils.WrapError(err)
|
||||
}
|
||||
@@ -189,7 +208,7 @@ func (c *OSCommand) FileExists(path string) (bool, error) {
|
||||
|
||||
// PipeCommands runs a heap of commands and pipes their inputs/outputs together like A | B | C
|
||||
func (c *OSCommand) PipeCommands(cmdObjs ...ICmdObj) error {
|
||||
cmds := slices.Map(cmdObjs, func(cmdObj ICmdObj) *exec.Cmd {
|
||||
cmds := lo.Map(cmdObjs, func(cmdObj ICmdObj, _ int) *exec.Cmd {
|
||||
return cmdObj.GetCmd()
|
||||
})
|
||||
|
||||
@@ -266,12 +285,32 @@ func PrepareForChildren(cmd *exec.Cmd) {
|
||||
func (c *OSCommand) CopyToClipboard(str string) error {
|
||||
escaped := strings.Replace(str, "\n", "\\n", -1)
|
||||
truncated := utils.TruncateWithEllipsis(escaped, 40)
|
||||
c.LogCommand(fmt.Sprintf("Copying '%s' to clipboard", truncated), false)
|
||||
|
||||
msg := utils.ResolvePlaceholderString(
|
||||
c.Tr.Log.CopyToClipboard,
|
||||
map[string]string{
|
||||
"str": truncated,
|
||||
},
|
||||
)
|
||||
c.LogCommand(msg, false)
|
||||
if c.UserConfig.OS.CopyToClipboardCmd != "" {
|
||||
cmdStr := utils.ResolvePlaceholderString(c.UserConfig.OS.CopyToClipboardCmd, map[string]string{
|
||||
"text": c.Cmd.Quote(str),
|
||||
})
|
||||
return c.Cmd.NewShell(cmdStr).Run()
|
||||
}
|
||||
|
||||
return clipboard.WriteAll(str)
|
||||
}
|
||||
|
||||
func (c *OSCommand) RemoveFile(path string) error {
|
||||
c.LogCommand(fmt.Sprintf("Deleting path '%s'", path), false)
|
||||
msg := utils.ResolvePlaceholderString(
|
||||
c.Tr.Log.RemoveFile,
|
||||
map[string]string{
|
||||
"path": path,
|
||||
},
|
||||
)
|
||||
c.LogCommand(msg, false)
|
||||
|
||||
return c.removeFileFn(path)
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ package oscommands
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/cli/safeexec"
|
||||
"github.com/go-errors/errors"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
@@ -20,13 +19,11 @@ func TestOSCommandOpenFileWindows(t *testing.T) {
|
||||
test func(error)
|
||||
}
|
||||
|
||||
fullCmdPath, _ := safeexec.LookPath("cmd")
|
||||
|
||||
scenarios := []scenario{
|
||||
{
|
||||
filename: "test",
|
||||
runner: NewFakeRunner(t).
|
||||
ExpectArgs([]string{fullCmdPath, "/c", "start", "", "test"}, "", errors.New("error")),
|
||||
ExpectArgs([]string{"cmd", "/c", "start", "", "test"}, "", errors.New("error")),
|
||||
test: func(err error) {
|
||||
assert.Error(t, err)
|
||||
},
|
||||
@@ -34,7 +31,7 @@ func TestOSCommandOpenFileWindows(t *testing.T) {
|
||||
{
|
||||
filename: "test",
|
||||
runner: NewFakeRunner(t).
|
||||
ExpectArgs([]string{fullCmdPath, "/c", "start", "", "test"}, "", nil),
|
||||
ExpectArgs([]string{"cmd", "/c", "start", "", "test"}, "", nil),
|
||||
test: func(err error) {
|
||||
assert.NoError(t, err)
|
||||
},
|
||||
@@ -42,7 +39,7 @@ func TestOSCommandOpenFileWindows(t *testing.T) {
|
||||
{
|
||||
filename: "filename with spaces",
|
||||
runner: NewFakeRunner(t).
|
||||
ExpectArgs([]string{fullCmdPath, "/c", "start", "", "filename with spaces"}, "", nil),
|
||||
ExpectArgs([]string{"cmd", "/c", "start", "", "filename with spaces"}, "", nil),
|
||||
test: func(err error) {
|
||||
assert.NoError(t, err)
|
||||
},
|
||||
@@ -50,7 +47,7 @@ func TestOSCommandOpenFileWindows(t *testing.T) {
|
||||
{
|
||||
filename: "let's_test_with_single_quote",
|
||||
runner: NewFakeRunner(t).
|
||||
ExpectArgs([]string{fullCmdPath, "/c", "start", "", "let's_test_with_single_quote"}, "", nil),
|
||||
ExpectArgs([]string{"cmd", "/c", "start", "", "let's_test_with_single_quote"}, "", nil),
|
||||
test: func(err error) {
|
||||
assert.NoError(t, err)
|
||||
},
|
||||
@@ -58,7 +55,7 @@ func TestOSCommandOpenFileWindows(t *testing.T) {
|
||||
{
|
||||
filename: "$USER.txt",
|
||||
runner: NewFakeRunner(t).
|
||||
ExpectArgs([]string{fullCmdPath, "/c", "start", "", "$USER.txt"}, "", nil),
|
||||
ExpectArgs([]string{"cmd", "/c", "start", "", "$USER.txt"}, "", nil),
|
||||
test: func(err error) {
|
||||
assert.NoError(t, err)
|
||||
},
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/jesseduffield/generics/maps"
|
||||
"github.com/jesseduffield/generics/slices"
|
||||
"github.com/samber/lo"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
@@ -209,10 +208,10 @@ func (p *PatchBuilder) renderEachFilePatch(plain bool) []string {
|
||||
filenames := maps.Keys(p.fileInfoMap)
|
||||
|
||||
sort.Strings(filenames)
|
||||
patches := slices.Map(filenames, func(filename string) string {
|
||||
patches := lo.Map(filenames, func(filename string, _ int) string {
|
||||
return p.RenderPatchForFile(filename, plain, false)
|
||||
})
|
||||
output := slices.Filter(patches, func(patch string) bool {
|
||||
output := lo.Filter(patches, func(patch string, _ int) bool {
|
||||
return patch != ""
|
||||
})
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -318,8 +319,10 @@ type AppState struct {
|
||||
StartupPopupVersion int
|
||||
|
||||
// these are for custom commands typed in directly, not for custom commands in the lazygit config
|
||||
CustomCommandsHistory []string
|
||||
HideCommandLog bool
|
||||
CustomCommandsHistory []string
|
||||
HideCommandLog bool
|
||||
IgnoreWhitespaceInDiffView bool
|
||||
DiffContextSize int
|
||||
}
|
||||
|
||||
func getDefaultAppState() *AppState {
|
||||
@@ -327,6 +330,7 @@ func getDefaultAppState() *AppState {
|
||||
LastUpdateCheck: 0,
|
||||
RecentRepos: []string{},
|
||||
StartupPopupVersion: 0,
|
||||
DiffContextSize: 3,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,18 +1,17 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func isWSL() bool {
|
||||
data, err := ioutil.ReadFile("/proc/sys/kernel/osrelease")
|
||||
data, err := os.ReadFile("/proc/sys/kernel/osrelease")
|
||||
return err == nil && strings.Contains(string(data), "microsoft")
|
||||
}
|
||||
|
||||
func isContainer() bool {
|
||||
data, err := ioutil.ReadFile("/proc/1/cgroup")
|
||||
data, err := os.ReadFile("/proc/1/cgroup")
|
||||
|
||||
if strings.Contains(string(data), "docker") ||
|
||||
strings.Contains(string(data), "/lxc/") ||
|
||||
|
||||
@@ -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,25 @@ 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,
|
||||
},
|
||||
"emacs": standardTerminalEditorPreset("emacs"),
|
||||
"micro": standardTerminalEditorPreset("micro"),
|
||||
"nano": standardTerminalEditorPreset("nano"),
|
||||
"kakoune": standardTerminalEditorPreset("kak"),
|
||||
"helix": {
|
||||
@@ -59,35 +68,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 +132,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)
|
||||
|
||||
+317
-137
@@ -5,139 +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"`
|
||||
ExperimentalShowBranchHeads bool `yaml:"experimentalShowBranchHeads"`
|
||||
CommandLogSize int `yaml:"commandLogSize"`
|
||||
SplitDiff string `yaml:"splitDiff"`
|
||||
SkipRewordInEditorWarning bool `yaml:"skipRewordInEditorWarning"`
|
||||
WindowSize string `yaml:"windowSize"`
|
||||
Border string `yaml:"border"`
|
||||
// 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"`
|
||||
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
|
||||
@@ -218,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"`
|
||||
@@ -268,6 +399,7 @@ type KeybindingCommitsConfig struct {
|
||||
CherryPickCopy string `yaml:"cherryPickCopy"`
|
||||
CherryPickCopyRange string `yaml:"cherryPickCopyRange"`
|
||||
PasteCommits string `yaml:"pasteCommits"`
|
||||
MarkCommitAsBaseForRebase string `yaml:"markCommitAsBaseForRebase"`
|
||||
CreateTag string `yaml:"tagCommit"`
|
||||
CheckoutCommit string `yaml:"checkoutCommit"`
|
||||
ResetCherryPick string `yaml:"resetCherryPick"`
|
||||
@@ -300,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}}".
|
||||
@@ -313,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"`
|
||||
@@ -354,6 +490,10 @@ type OSConfig struct {
|
||||
// OpenLinkCommand is the command for opening a link
|
||||
// Deprecated: use OpenLink instead.
|
||||
OpenLinkCommand string `yaml:"openLinkCommand,omitempty"`
|
||||
|
||||
// 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"`
|
||||
}
|
||||
|
||||
type CustomCommandAfterHook struct {
|
||||
@@ -361,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 {
|
||||
@@ -412,6 +581,8 @@ func GetDefaultConfig() *UserConfig {
|
||||
Gui: GuiConfig{
|
||||
ScrollHeight: 2,
|
||||
ScrollPastBottom: true,
|
||||
ScrollOffMargin: 2,
|
||||
ScrollOffBehavior: "margin",
|
||||
MouseEvents: true,
|
||||
SkipDiscardChangeWarning: false,
|
||||
SkipStashWarning: false,
|
||||
@@ -430,30 +601,35 @@ func GetDefaultConfig() *UserConfig {
|
||||
SelectedRangeBgColor: []string{"blue"},
|
||||
CherryPickedCommitBgColor: []string{"cyan"},
|
||||
CherryPickedCommitFgColor: []string{"blue"},
|
||||
MarkedBaseCommitBgColor: []string{"yellow"},
|
||||
MarkedBaseCommitFgColor: []string{"blue"},
|
||||
UnstagedChangesColor: []string{"red"},
|
||||
DefaultFgColor: []string{"default"},
|
||||
},
|
||||
CommitLength: CommitLengthConfig{Show: true},
|
||||
SkipNoStagedFilesWarning: false,
|
||||
ShowListFooter: true,
|
||||
ShowCommandLog: true,
|
||||
ShowBottomLine: true,
|
||||
ShowFileTree: true,
|
||||
ShowRandomTip: true,
|
||||
ShowIcons: false,
|
||||
NerdFontsVersion: "",
|
||||
ExperimentalShowBranchHeads: false,
|
||||
ShowBranchCommitHash: false,
|
||||
CommandLogSize: 8,
|
||||
SplitDiff: "auto",
|
||||
SkipRewordInEditorWarning: false,
|
||||
Border: "single",
|
||||
CommitLength: CommitLengthConfig{Show: true},
|
||||
SkipNoStagedFilesWarning: false,
|
||||
ShowListFooter: true,
|
||||
ShowCommandLog: true,
|
||||
ShowBottomLine: true,
|
||||
ShowPanelJumps: true,
|
||||
ShowFileTree: true,
|
||||
ShowRandomTip: true,
|
||||
ShowIcons: false,
|
||||
NerdFontsVersion: "",
|
||||
ShowBranchCommitHash: false,
|
||||
CommandLogSize: 8,
|
||||
SplitDiff: "auto",
|
||||
SkipRewordInEditorWarning: false,
|
||||
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,
|
||||
@@ -477,7 +653,6 @@ func GetDefaultConfig() *UserConfig {
|
||||
DisableForcePushing: false,
|
||||
CommitPrefixes: map[string]CommitPrefixConfig(nil),
|
||||
ParseEmoji: false,
|
||||
DiffContextSize: 3,
|
||||
},
|
||||
Refresher: RefresherConfig{
|
||||
RefreshInterval: 10,
|
||||
@@ -516,7 +691,7 @@ func GetDefaultConfig() *UserConfig {
|
||||
NextMatch: "n",
|
||||
PrevMatch: "N",
|
||||
StartSearch: "/",
|
||||
OptionMenu: "",
|
||||
OptionMenu: "<disabled>",
|
||||
OptionMenuAlt1: "?",
|
||||
Select: "<space>",
|
||||
GoInto: "<enter>",
|
||||
@@ -575,6 +750,7 @@ func GetDefaultConfig() *UserConfig {
|
||||
ToggleTreeView: "`",
|
||||
OpenMergeTool: "M",
|
||||
OpenStatusFilter: "<c-b>",
|
||||
ConfirmDiscard: "x",
|
||||
},
|
||||
Branches: KeybindingBranchesConfig{
|
||||
CopyPullRequestURL: "<c-y>",
|
||||
@@ -612,6 +788,7 @@ func GetDefaultConfig() *UserConfig {
|
||||
CherryPickCopy: "c",
|
||||
CherryPickCopyRange: "C",
|
||||
PasteCommits: "v",
|
||||
MarkCommitAsBaseForRebase: "B",
|
||||
CreateTag: "T",
|
||||
CheckoutCommit: "<space>",
|
||||
ResetCherryPick: "<c-R>",
|
||||
@@ -639,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")
|
||||
}
|
||||
|
||||
+3
-3
@@ -4,9 +4,9 @@ import (
|
||||
"errors"
|
||||
"sync"
|
||||
|
||||
"github.com/jesseduffield/generics/slices"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/context"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
@@ -134,7 +134,7 @@ func (self *ContextMgr) pushToContextStack(c types.Context) ([]types.Context, ty
|
||||
(topContext.GetKind() == types.MAIN_CONTEXT && c.GetKind() == types.MAIN_CONTEXT) {
|
||||
|
||||
contextsToDeactivate = append(contextsToDeactivate, topContext)
|
||||
_, self.ContextStack = slices.Pop(self.ContextStack)
|
||||
_, self.ContextStack = utils.Pop(self.ContextStack)
|
||||
}
|
||||
|
||||
self.ContextStack = append(self.ContextStack, c)
|
||||
@@ -154,7 +154,7 @@ func (self *ContextMgr) Pop() error {
|
||||
}
|
||||
|
||||
var currentContext types.Context
|
||||
currentContext, self.ContextStack = slices.Pop(self.ContextStack)
|
||||
currentContext, self.ContextStack = utils.Pop(self.ContextStack)
|
||||
|
||||
newContext := self.ContextStack[len(self.ContextStack)-1]
|
||||
|
||||
|
||||
@@ -24,14 +24,16 @@ 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.Tr,
|
||||
c.UserConfig,
|
||||
c.Model().Worktrees,
|
||||
c.Model().MergedBranches,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -45,9 +47,11 @@ func NewBranchesContext(c *ContextCommon) *BranchesContext {
|
||||
Kind: types.SIDE_CONTEXT,
|
||||
Focusable: true,
|
||||
})),
|
||||
list: viewModel,
|
||||
getDisplayStrings: getDisplayStrings,
|
||||
c: c,
|
||||
ListRenderer: ListRenderer{
|
||||
list: viewModel,
|
||||
getDisplayStrings: getDisplayStrings,
|
||||
},
|
||||
c: c,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -83,3 +87,7 @@ func (self *BranchesContext) GetDiffTerminals() []string {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *BranchesContext) ShowBranchHeadsInSubCommits() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
package context
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/generics/slices"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/filetree"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/style"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
type CommitFilesContext struct {
|
||||
@@ -28,13 +28,13 @@ 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)")}}
|
||||
}
|
||||
|
||||
lines := presentation.RenderCommitFileTree(viewModel, c.Modes().Diffing.Ref, c.Git().Patch.PatchBuilder)
|
||||
return slices.Map(lines, func(line string) []string {
|
||||
return lo.Map(lines, func(line string, _ int) []string {
|
||||
return []string{line}
|
||||
})
|
||||
}
|
||||
@@ -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,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user