Compare commits
542 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
945edb253b | ||
|
|
cbc82cd3c1 | ||
|
|
29ee239987 | ||
|
|
3f7e107d09 | ||
|
|
22c0d79e2d | ||
|
|
e174e5254d | ||
|
|
de5bcb8b9c | ||
|
|
98666186ee | ||
|
|
941d3c6648 | ||
|
|
5c518eda0a | ||
|
|
df72eee201 | ||
|
|
131113b065 | ||
|
|
e85310c0a9 | ||
|
|
cd17b46b55 | ||
|
|
d0d92c7697 | ||
|
|
89a9b4e6d5 | ||
|
|
592a2ff196 | ||
|
|
17ed90c790 | ||
|
|
9e0b335669 | ||
|
|
194c554357 | ||
|
|
c65790b53d | ||
|
|
2f37c0caaf | ||
|
|
86a39e3aea | ||
|
|
326b1ca8c9 | ||
|
|
72fe770974 | ||
|
|
db8c398fa3 | ||
|
|
861bcc38be | ||
|
|
cd3874ffb7 | ||
|
|
10fe88a2cf | ||
|
|
1a38bfb76d | ||
|
|
beaebb7dc7 | ||
|
|
6d5d054c30 | ||
|
|
2344155379 | ||
|
|
48347d4d86 | ||
|
|
61deaaddb7 | ||
|
|
0046e9c469 | ||
|
|
733145d132 | ||
|
|
f285d80d0e | ||
|
|
0ffccbd3ee | ||
|
|
1fc120de2d | ||
|
|
d5e443e8e3 | ||
|
|
a3c84296bf | ||
|
|
cc039d1f9b | ||
|
|
2484ec9c11 | ||
|
|
5f9de1f034 | ||
|
|
66eaaf9cbb | ||
|
|
87ac193b5e | ||
|
|
11e57edbb3 | ||
|
|
4f2c42ea47 | ||
|
|
820f3d5cbb | ||
|
|
081598d989 | ||
|
|
09f268befc | ||
|
|
4bc974c83c | ||
|
|
63da8f48da | ||
|
|
32d6a17240 | ||
|
|
84d869a3a0 | ||
|
|
a1c6619401 | ||
|
|
3524f6baa9 | ||
|
|
ac5cbc1d2c | ||
|
|
a045313e08 | ||
|
|
9bd2dc3050 | ||
|
|
02fef3136f | ||
|
|
8fe0e00cd9 | ||
|
|
f7f19bbc02 | ||
|
|
95ae806e09 | ||
|
|
d8a6f173c3 | ||
|
|
431f1aa766 | ||
|
|
379dcf0972 | ||
|
|
0d25d113c9 | ||
|
|
7c70913e8d | ||
|
|
1c5858c515 | ||
|
|
c3767bb3b3 | ||
|
|
b92d27ee7f | ||
|
|
6eff139c40 | ||
|
|
07462303ab | ||
|
|
d12f81b44e | ||
|
|
600112780c | ||
|
|
4c73c8889f | ||
|
|
68d5c2bc10 | ||
|
|
7db1fee877 | ||
|
|
8f786e3fd9 | ||
|
|
1c704e11f2 | ||
|
|
e0dd1cb29d | ||
|
|
827837b0b9 | ||
|
|
e83ef9858b | ||
|
|
504d506575 | ||
|
|
823b436b53 | ||
|
|
212327d746 | ||
|
|
cc138fc70e | ||
|
|
d953712377 | ||
|
|
69ac0036e6 | ||
|
|
975a5315b0 | ||
|
|
8f734b11e3 | ||
|
|
17b4cabc71 | ||
|
|
75db4faf69 | ||
|
|
e1f5601d4b | ||
|
|
b60ecdaa24 | ||
|
|
9fb9962ce7 | ||
|
|
25c93c678d | ||
|
|
b8baef7b8f | ||
|
|
235a47bb80 | ||
|
|
c107eed890 | ||
|
|
abddea060e | ||
|
|
3e40369fd2 | ||
|
|
0f0fda1660 | ||
|
|
bd2170a99c | ||
|
|
c039e5bed0 | ||
|
|
527c025a0c | ||
|
|
53cded77f1 | ||
|
|
4a4dc676fc | ||
|
|
c61bfbdd4c | ||
|
|
e38d9d5f22 | ||
|
|
97f060d38d | ||
|
|
357b8fa98f | ||
|
|
8754d766e2 | ||
|
|
2388c3ee9a | ||
|
|
61890cb9de | ||
|
|
5a0d0bb299 | ||
|
|
2746b1bd38 | ||
|
|
e09aac6450 | ||
|
|
19a6368377 | ||
|
|
e6122122e9 | ||
|
|
492614ebc7 | ||
|
|
d31f0ed39b | ||
|
|
b505c295d2 | ||
|
|
0b9d7edd47 | ||
|
|
e9fbb608a8 | ||
|
|
6ba05c94ea | ||
|
|
07fec6d00e | ||
|
|
a69b985086 | ||
|
|
471733fe03 | ||
|
|
0d3a193ab5 | ||
|
|
ab9fa291a8 | ||
|
|
cadc74eeec | ||
|
|
fc3a57b5e2 | ||
|
|
7ff07e1454 | ||
|
|
3e779bca8d | ||
|
|
0f1abcb10c | ||
|
|
55538a3695 | ||
|
|
878a15aff4 | ||
|
|
60e33f5d8c | ||
|
|
b422692746 | ||
|
|
f34be1896a | ||
|
|
c350cdba43 | ||
|
|
1a933eaa73 | ||
|
|
bd46e9c5b0 | ||
|
|
09b7ae21bc | ||
|
|
acfc961909 | ||
|
|
f502f75e1f | ||
|
|
ff97ef7b94 | ||
|
|
a2c780b085 | ||
|
|
b99305c909 | ||
|
|
d84dfc23e7 | ||
|
|
9d8fd3594e | ||
|
|
e68dbeb7eb | ||
|
|
32ddf0c296 | ||
|
|
c453bfeb32 | ||
|
|
f6ca450d36 | ||
|
|
d5f617ec92 | ||
|
|
6d104bfa91 | ||
|
|
e583cc2519 | ||
|
|
0d208b7957 | ||
|
|
43e5c042a2 | ||
|
|
39844ffef9 | ||
|
|
f5c8aac97d | ||
|
|
b6447ebdbb | ||
|
|
466fc4227e | ||
|
|
c034c88be4 | ||
|
|
72830efc45 | ||
|
|
c98eddc185 | ||
|
|
3b2353b5ae | ||
|
|
3f567c952c | ||
|
|
4f7f6a073c | ||
|
|
0e008cc15f | ||
|
|
1ad9c6faac | ||
|
|
06fe726ee7 | ||
|
|
1b6e46973e | ||
|
|
63e2ccfccf | ||
|
|
6fd4d49db7 | ||
|
|
2393bc791d | ||
|
|
398f91decb | ||
|
|
c937a93f79 | ||
|
|
9402d8b0c0 | ||
|
|
2a0615da10 | ||
|
|
4be5eaae7b | ||
|
|
038dcb546e | ||
|
|
1184990e16 | ||
|
|
ac5088eee6 | ||
|
|
e36899d5c5 | ||
|
|
403526bc50 | ||
|
|
a5d27764cd | ||
|
|
43758cbb5f | ||
|
|
0079015102 | ||
|
|
7a2176f479 | ||
|
|
e0bdfad63a | ||
|
|
f07fc31f8b | ||
|
|
4bb577ab7d | ||
|
|
8305d8e72f | ||
|
|
f68166e858 | ||
|
|
8925b161a7 | ||
|
|
0a1298765c | ||
|
|
273678f081 | ||
|
|
790235f64b | ||
|
|
abc0f7f0aa | ||
|
|
ab81f27fc7 | ||
|
|
dbb01b028d | ||
|
|
0c886eddfb | ||
|
|
399346c2ee | ||
|
|
7a170bbccf | ||
|
|
8c0ea8f45f | ||
|
|
afbc028ad6 | ||
|
|
e331dfcaf8 | ||
|
|
1337f6e76a | ||
|
|
4de31da4be | ||
|
|
23c51ba708 | ||
|
|
19a3ac603d | ||
|
|
f4938deaae | ||
|
|
639df512f3 | ||
|
|
a8858cbd12 | ||
|
|
1a19b1412d | ||
|
|
95d451e59a | ||
|
|
6c1d2d45ef | ||
|
|
f6b3a9b184 | ||
|
|
cdc50e8557 | ||
|
|
0173fdb9df | ||
|
|
9661ea04f3 | ||
|
|
0228e25084 | ||
|
|
935f774834 | ||
|
|
dcc7855fd0 | ||
|
|
a8e22ed82f | ||
|
|
d44638130c | ||
|
|
76a27f417f | ||
|
|
adc2529019 | ||
|
|
43ab7318d3 | ||
|
|
cb372d469f | ||
|
|
88ba6efdd5 | ||
|
|
e011e9bc42 | ||
|
|
ad93b4c863 | ||
|
|
198cbee498 | ||
|
|
daca07eaca | ||
|
|
34acaf7ac4 | ||
|
|
d967f65329 | ||
|
|
306ac41fd8 | ||
|
|
c101993405 | ||
|
|
6430ab6ac9 | ||
|
|
e09f3905e9 | ||
|
|
53e73313a2 | ||
|
|
0891797bf8 | ||
|
|
cfe3605e6b | ||
|
|
75ab8ec4d9 | ||
|
|
77faf85cfc | ||
|
|
3d343e9b57 | ||
|
|
3a607061a2 | ||
|
|
695b092c41 | ||
|
|
a38d1a3b68 | ||
|
|
2dc5e6d503 | ||
|
|
0dcfa09ff2 | ||
|
|
d5401ab200 | ||
|
|
b6f8ebc0ca | ||
|
|
3e24069722 | ||
|
|
c722ea5afc | ||
|
|
c759c7ac65 | ||
|
|
e50bd812fc | ||
|
|
7ff022f1e7 | ||
|
|
1db8801771 | ||
|
|
666ea3a4a0 | ||
|
|
47d50989c4 | ||
|
|
e4f70278dd | ||
|
|
0afffd03ca | ||
|
|
6c5e409ffa | ||
|
|
800b40ecc4 | ||
|
|
097f687efe | ||
|
|
aa30e00643 | ||
|
|
cf56dcf9ff | ||
|
|
c14a4eed0e | ||
|
|
a1b688f070 | ||
|
|
4793232a35 | ||
|
|
7835fce708 | ||
|
|
535152e15e | ||
|
|
160af3bb99 | ||
|
|
328b57e2cf | ||
|
|
20a94447d7 | ||
|
|
865c7c2332 | ||
|
|
11c7cbe3ac | ||
|
|
276ac3a92e | ||
|
|
a4beabf4b9 | ||
|
|
c35255b7a9 | ||
|
|
319064f040 | ||
|
|
f5f726e9c4 | ||
|
|
c56b303b29 | ||
|
|
4886b8350e | ||
|
|
af26b5f3e0 | ||
|
|
70cd6700e7 | ||
|
|
d11f8989d9 | ||
|
|
0fca27d022 | ||
|
|
255319e597 | ||
|
|
5d038dfd33 | ||
|
|
0577d3b97f | ||
|
|
a26c15dafa | ||
|
|
c71bcc64ed | ||
|
|
a365615490 | ||
|
|
9489a94473 | ||
|
|
e0ff46fe53 | ||
|
|
cce6f405a5 | ||
|
|
e39d2ed44b | ||
|
|
7a7e885773 | ||
|
|
34fd18a395 | ||
|
|
a1ee11e54e | ||
|
|
7b850c56c4 | ||
|
|
88c01c1ded | ||
|
|
27994f7de8 | ||
|
|
670f0e37c7 | ||
|
|
822dc5dada | ||
|
|
e20d8366e1 | ||
|
|
76e9582739 | ||
|
|
50f20de8f3 | ||
|
|
8e3f5e19e0 | ||
|
|
61c2778de1 | ||
|
|
3c17bf761a | ||
|
|
696d6dc20c | ||
|
|
f14effe5f5 | ||
|
|
b95abd95ef | ||
|
|
ea6712dec8 | ||
|
|
de37a66ef3 | ||
|
|
efb82a58ae | ||
|
|
19a6a32625 | ||
|
|
270658fc00 | ||
|
|
ff856b7630 | ||
|
|
ca3afa2a39 | ||
|
|
99a8b1ae8b | ||
|
|
ccc771d8b1 | ||
|
|
cf5a85b80f | ||
|
|
2f7bd2896c | ||
|
|
8f904ffd72 | ||
|
|
ced81e11f0 | ||
|
|
6d0fa8bc29 | ||
|
|
21a808a52b | ||
|
|
89c272eed5 | ||
|
|
1b6d34e76a | ||
|
|
6711543634 | ||
|
|
f6e83cdbdf | ||
|
|
3b51d7cd00 | ||
|
|
66512ca253 | ||
|
|
1a6a69a8f1 | ||
|
|
933874fb25 | ||
|
|
c0f9795910 | ||
|
|
658e5a9faf | ||
|
|
99824c8a7b | ||
|
|
60060551bf | ||
|
|
c269ad1370 | ||
|
|
2edd2b74ff | ||
|
|
181f91d2ef | ||
|
|
643cdd3461 | ||
|
|
5c70d2724b | ||
|
|
55712f509c | ||
|
|
d91493b587 | ||
|
|
9da1382e09 | ||
|
|
4e8e4612bd | ||
|
|
adfc00bcdc | ||
|
|
b0eaf507a5 | ||
|
|
b9ecb82cb7 | ||
|
|
448d9caf1b | ||
|
|
6d2bf0b0b5 | ||
|
|
5160668efd | ||
|
|
0eb1e4a86b | ||
|
|
0c4c00c1bf | ||
|
|
cc7d78f1ee | ||
|
|
b8d5adcb84 | ||
|
|
a5f483fae9 | ||
|
|
775d910bdc | ||
|
|
18a1070c2c | ||
|
|
9fafd7ebc1 | ||
|
|
bc14b01d03 | ||
|
|
80c6e0a8c4 | ||
|
|
8742c4c110 | ||
|
|
32ecc6d745 | ||
|
|
834e42897d | ||
|
|
500267417b | ||
|
|
18bcc0df4d | ||
|
|
5ae0e75e5e | ||
|
|
1fd8cadd9e | ||
|
|
9d79d32c94 | ||
|
|
17b4b4cb33 | ||
|
|
79ef98739d | ||
|
|
c2eaeab1f0 | ||
|
|
32d1289af7 | ||
|
|
ea55643cb2 | ||
|
|
dcb6216713 | ||
|
|
9c8b241292 | ||
|
|
7c4d360645 | ||
|
|
ad77ac639e | ||
|
|
cf1e9f79b1 | ||
|
|
8f0741a458 | ||
|
|
6f2b62f729 | ||
|
|
8469239d84 | ||
|
|
af54d7f015 | ||
|
|
cb9ad5bc73 | ||
|
|
5470bb4121 | ||
|
|
0e53a26d6f | ||
|
|
3938138ebc | ||
|
|
05f0e5120a | ||
|
|
5532289086 | ||
|
|
78b2bc4f60 | ||
|
|
d33f89fd60 | ||
|
|
c0da212f54 | ||
|
|
9585f49490 | ||
|
|
1e0310a86d | ||
|
|
bf45e5b0e3 | ||
|
|
9a0f094f58 | ||
|
|
ee89ad6ae7 | ||
|
|
22e5aafd59 | ||
|
|
abd0803ef4 | ||
|
|
372b333662 | ||
|
|
18f09a14e6 | ||
|
|
ed564adb4a | ||
|
|
9a99748d3b | ||
|
|
9163110640 | ||
|
|
6c1c110ce0 | ||
|
|
45c249acca | ||
|
|
1df1053947 | ||
|
|
14cff0bd07 | ||
|
|
87d1b9a547 | ||
|
|
959d6fa2ca | ||
|
|
e47c597b3a | ||
|
|
bfcb348923 | ||
|
|
c6da4c8a47 | ||
|
|
1fedda6a75 | ||
|
|
467775fc1c | ||
|
|
3b6b68a2a1 | ||
|
|
3a23cb87b7 | ||
|
|
ac03665df3 | ||
|
|
1be44eae84 | ||
|
|
b72841ca0c | ||
|
|
12425f0aa7 | ||
|
|
727ba9f42e | ||
|
|
73a0a65ee1 | ||
|
|
ac5696574c | ||
|
|
1a43d64de3 | ||
|
|
990dc8c4ea | ||
|
|
59cdd7d46e | ||
|
|
4451cbc50b | ||
|
|
01fa106de3 | ||
|
|
9fc4262887 | ||
|
|
cecd5733a8 | ||
|
|
1d733f3adc | ||
|
|
c69fce2e9d | ||
|
|
df0e3e52fe | ||
|
|
d5f64602a8 | ||
|
|
4287f8ae90 | ||
|
|
190309e5c1 | ||
|
|
ac65586bd5 | ||
|
|
af8d362caa | ||
|
|
5f7ac97a39 | ||
|
|
b8b59baa27 | ||
|
|
2be613679e | ||
|
|
28fe3d6cf9 | ||
|
|
b6b21bc98e | ||
|
|
eb69d98f99 | ||
|
|
fb9596a3ff | ||
|
|
0d33a746ba | ||
|
|
f3fc98a3d0 | ||
|
|
17d7bcdeaf | ||
|
|
d0a3f1eecf | ||
|
|
7164f37266 | ||
|
|
e9245cd53b | ||
|
|
80d6bbef86 | ||
|
|
3d751c03fe | ||
|
|
4e0d0f7d75 | ||
|
|
b8b3eee961 | ||
|
|
7947668e18 | ||
|
|
619c28ce56 | ||
|
|
53aef7846a | ||
|
|
227067fdd1 | ||
|
|
3df4a9484f | ||
|
|
2229a6e133 | ||
|
|
3101c50582 | ||
|
|
70ee4faf15 | ||
|
|
360b7c1def | ||
|
|
bdeb78c9a0 | ||
|
|
9481920101 | ||
|
|
a2b3cd0823 | ||
|
|
8fac19c175 | ||
|
|
b9708c9f88 | ||
|
|
7b90d2496b | ||
|
|
0367399cf3 | ||
|
|
3072c93e13 | ||
|
|
4ea446205c | ||
|
|
5a76b57952 | ||
|
|
6de291ff44 | ||
|
|
64f0eeb42e | ||
|
|
baa9eff318 | ||
|
|
fcaf4e339c | ||
|
|
e91fb21233 | ||
|
|
99a6439641 | ||
|
|
768b9453f8 | ||
|
|
e95b2e5f0b | ||
|
|
950cfeff6f | ||
|
|
fce895ed0d | ||
|
|
c789bba673 | ||
|
|
b384fcf6af | ||
|
|
60cf549a32 | ||
|
|
6f0b32f95e | ||
|
|
f89bc10af1 | ||
|
|
a66ac8092e | ||
|
|
bd04ecff69 | ||
|
|
c00c834b35 | ||
|
|
9d9d775f50 | ||
|
|
38036e0d20 | ||
|
|
9713a15167 | ||
|
|
b641d6bd96 | ||
|
|
67a42f49b4 | ||
|
|
bbc88071e9 | ||
|
|
ca2eec60fe | ||
|
|
c1b7a21631 | ||
|
|
91832f2c5e | ||
|
|
c64fb87b2b | ||
|
|
fa08c6c2a2 | ||
|
|
3cf84a5af1 | ||
|
|
61f0801bd3 | ||
|
|
eb4b5cd43b | ||
|
|
c92510ceba | ||
|
|
65a24d70c3 | ||
|
|
7fb2cafd0c | ||
|
|
3b765e5417 | ||
|
|
57f6a552d2 | ||
|
|
35cae80de9 | ||
|
|
b4b4cd83dd | ||
|
|
31c33dfdcb | ||
|
|
79940b7ba9 | ||
|
|
2ce8ac5850 | ||
|
|
f8b484f638 | ||
|
|
73e2c1005a | ||
|
|
97e0a6dc45 | ||
|
|
9bad0337fe | ||
|
|
f03544f392 | ||
|
|
0aba49af2b | ||
|
|
ccbc5e569c | ||
|
|
415aad600c | ||
|
|
d23577168f | ||
|
|
5c204b2813 | ||
|
|
52b132fe01 | ||
|
|
ca715c5b23 |
@@ -2,24 +2,12 @@ version: 2
|
||||
jobs:
|
||||
build:
|
||||
docker:
|
||||
- image: circleci/golang:1.11
|
||||
|
||||
- image: circleci/golang:1.13
|
||||
environment:
|
||||
GO111MODULE: "on"
|
||||
working_directory: /go/src/github.com/jesseduffield/lazygit
|
||||
steps:
|
||||
- checkout
|
||||
- run:
|
||||
name: Ensure go.mod file is up to date
|
||||
command: |
|
||||
export GO111MODULE=on
|
||||
rm go.sum
|
||||
mv go.mod /tmp/
|
||||
go mod init
|
||||
export GO111MODULE=auto
|
||||
|
||||
if [ $(diff /tmp/go.mod go.mod|wc -l) -gt 0 ]; then
|
||||
diff /tmp/go.mod go.mod
|
||||
exit 1;
|
||||
fi
|
||||
- run:
|
||||
name: Run gofmt -s
|
||||
command: |
|
||||
@@ -29,28 +17,28 @@ jobs:
|
||||
fi
|
||||
- restore_cache:
|
||||
keys:
|
||||
- pkg-cache-{{ checksum "Gopkg.lock" }}-v2
|
||||
- pkg-cache-{{ checksum "go.sum" }}-v5
|
||||
- run:
|
||||
name: Run tests
|
||||
command: |
|
||||
./test.sh
|
||||
- run:
|
||||
name: Compile project on every platform
|
||||
command: |
|
||||
go get github.com/mitchellh/gox
|
||||
gox -parallel 10 -os "linux freebsd netbsd windows" -osarch "darwin/i386 darwin/amd64"
|
||||
- run:
|
||||
name: Push on codecov result
|
||||
command: |
|
||||
bash <(curl -s https://codecov.io/bash)
|
||||
- run:
|
||||
name: Compile project on every platform
|
||||
command: |
|
||||
go get github.com/mitchellh/gox
|
||||
GOFLAGS=-mod=vendor gox -parallel 10 -os "linux freebsd netbsd windows" -osarch "darwin/i386 darwin/amd64"
|
||||
- save_cache:
|
||||
key: pkg-cache-{{ checksum "Gopkg.lock" }}-v2
|
||||
key: pkg-cache-{{ checksum "go.sum" }}-v5
|
||||
paths:
|
||||
- ~/.cache/go-build
|
||||
|
||||
release:
|
||||
docker:
|
||||
- image: circleci/golang:1.10
|
||||
- image: circleci/golang:1.13
|
||||
working_directory: /go/src/github.com/jesseduffield/lazygit
|
||||
steps:
|
||||
- checkout
|
||||
|
||||
5
.github/FUNDING.yml
vendored
Normal file
5
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: [jesseduffield]
|
||||
ko_fi: jesseduffield
|
||||
custom: ['https://donorbox.org/lazygit']
|
||||
3
.github/ISSUE_TEMPLATE/bug_report.md
vendored
3
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -1,6 +1,9 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: bug
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
|
||||
14
.github/ISSUE_TEMPLATE/discussion.md
vendored
Normal file
14
.github/ISSUE_TEMPLATE/discussion.md
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
---
|
||||
name: Discussion
|
||||
about: Begin a discussion
|
||||
title: ''
|
||||
labels: discussion
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Topic**
|
||||
A clear and concise description of what you want to discuss
|
||||
|
||||
**Your thoughts**
|
||||
What you have to say about the topic
|
||||
3
.github/ISSUE_TEMPLATE/feature_request.md
vendored
3
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -1,6 +1,9 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: enhancement
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -23,4 +23,6 @@ lazygit
|
||||
!.gitignore
|
||||
!.goreleaser.yml
|
||||
!.circleci/
|
||||
!.github/
|
||||
!.github/
|
||||
|
||||
test/git_server/data
|
||||
18
Dockerfile
18
Dockerfile
@@ -1,13 +1,15 @@
|
||||
# run with:
|
||||
# docker build -t lazygit .
|
||||
# docker run -it lazygit:latest
|
||||
# docker run -it lazygit:latest /bin/sh -l
|
||||
|
||||
FROM golang:alpine
|
||||
FROM golang:1.13-alpine3.10
|
||||
WORKDIR /go/src/github.com/jesseduffield/lazygit/
|
||||
COPY ./ .
|
||||
RUN CGO_ENABLED=0 GOOS=linux go build
|
||||
|
||||
FROM alpine:3.10
|
||||
RUN apk add -U git xdg-utils
|
||||
|
||||
ADD . /go/src/github.com/jesseduffield/lazygit
|
||||
|
||||
RUN go install github.com/jesseduffield/lazygit
|
||||
|
||||
WORKDIR /go/src/github.com/jesseduffield/lazygit
|
||||
WORKDIR /go/src/github.com/jesseduffield/lazygit/
|
||||
COPY --from=0 /go/src/github.com/jesseduffield/lazygit /go/src/github.com/jesseduffield/lazygit
|
||||
COPY --from=0 /go/src/github.com/jesseduffield/lazygit/lazygit /bin/
|
||||
RUN echo "alias gg=lazygit" >> ~/.profile
|
||||
|
||||
634
Gopkg.lock
generated
634
Gopkg.lock
generated
@@ -1,634 +0,0 @@
|
||||
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
|
||||
|
||||
|
||||
[[projects]]
|
||||
digest = "1:e24ea5dbc89fbab51635ee32e5be4f61a9267cae20788efcae4c07efb4abec99"
|
||||
name = "github.com/aws/aws-sdk-go"
|
||||
packages = [
|
||||
"aws",
|
||||
"aws/awserr",
|
||||
"aws/awsutil",
|
||||
"aws/client",
|
||||
"aws/client/metadata",
|
||||
"aws/corehandlers",
|
||||
"aws/credentials",
|
||||
"aws/credentials/ec2rolecreds",
|
||||
"aws/credentials/endpointcreds",
|
||||
"aws/credentials/stscreds",
|
||||
"aws/csm",
|
||||
"aws/defaults",
|
||||
"aws/ec2metadata",
|
||||
"aws/endpoints",
|
||||
"aws/request",
|
||||
"aws/session",
|
||||
"aws/signer/v4",
|
||||
"internal/sdkio",
|
||||
"internal/sdkrand",
|
||||
"internal/sdkuri",
|
||||
"internal/shareddefaults",
|
||||
"private/protocol",
|
||||
"private/protocol/eventstream",
|
||||
"private/protocol/eventstream/eventstreamapi",
|
||||
"private/protocol/query",
|
||||
"private/protocol/query/queryutil",
|
||||
"private/protocol/rest",
|
||||
"private/protocol/restxml",
|
||||
"private/protocol/xml/xmlutil",
|
||||
"service/s3",
|
||||
"service/sts",
|
||||
]
|
||||
pruneopts = "NUT"
|
||||
revision = "4324bc9d8865bdb3e6aa86ec7772ca1272d2750e"
|
||||
version = "v1.15.21"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:37011b20a70e205b93ebea5287e1afa5618db54bf3998c36ff5a8e4b146a170a"
|
||||
name = "github.com/bgentry/go-netrc"
|
||||
packages = ["netrc"]
|
||||
pruneopts = "NUT"
|
||||
revision = "9fd32a8b3d3d3f9d43c341bfe098430e07609480"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:cd7ba2b29e93e2a8384e813dfc80ebb0f85d9214762e6ca89bb55a58092eab87"
|
||||
name = "github.com/cloudfoundry/jibber_jabber"
|
||||
packages = ["."]
|
||||
pruneopts = "NUT"
|
||||
revision = "bcc4c8345a21301bf47c032ff42dd1aae2fe3027"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:a2c1d0e43bd3baaa071d1b9ed72c27d78169b2b269f71c105ac4ba34b1be4a39"
|
||||
name = "github.com/davecgh/go-spew"
|
||||
packages = ["spew"]
|
||||
pruneopts = "NUT"
|
||||
revision = "346938d642f2ec3594ed81d874461961cd0faa76"
|
||||
version = "v1.1.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:de4a74b504df31145ffa8ca0c4edbffa2f3eb7f466753962184611b618fa5981"
|
||||
name = "github.com/emirpasic/gods"
|
||||
packages = [
|
||||
"containers",
|
||||
"lists",
|
||||
"lists/arraylist",
|
||||
"trees",
|
||||
"trees/binaryheap",
|
||||
"utils",
|
||||
]
|
||||
pruneopts = "NUT"
|
||||
revision = "f6c17b524822278a87e3b3bd809fec33b51f5b46"
|
||||
version = "v1.9.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:ade392a843b2035effb4b4a2efa2c3bab3eb29b992e98bacf9c898b0ecb54e45"
|
||||
name = "github.com/fatih/color"
|
||||
packages = ["."]
|
||||
pruneopts = "NUT"
|
||||
revision = "5b77d2a35fb0ede96d138fc9a99f5c9b6aef11b4"
|
||||
version = "v1.7.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:1b91ae0dc69a41d4c2ed23ea5cffb721ea63f5037ca4b81e6d6771fbb8f45129"
|
||||
name = "github.com/fsnotify/fsnotify"
|
||||
packages = ["."]
|
||||
pruneopts = "NUT"
|
||||
revision = "c2828203cd70a50dcccfb2761f8b1f8ceef9a8e9"
|
||||
version = "v1.4.7"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:74d9b0a7b4107b41e0ade759fac64502876f82d29fb23d77b3dd24b194ee3dd5"
|
||||
name = "github.com/go-ini/ini"
|
||||
packages = ["."]
|
||||
pruneopts = "NUT"
|
||||
revision = "5cf292cae48347c2490ac1a58fe36735fb78df7e"
|
||||
version = "v1.38.2"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:4a8ed9b8cf22bd03bee5d74179fa06a282e4a73b6de949f7a865ff56cd2537e0"
|
||||
name = "github.com/golang-collections/collections"
|
||||
packages = ["stack"]
|
||||
pruneopts = "NUT"
|
||||
revision = "604e922904d35e97f98a774db7881f049cd8d970"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:a5d940c38bf56f121721bfa747c66356df387cb9d5318c570c6d4170aab62862"
|
||||
name = "github.com/hashicorp/go-cleanhttp"
|
||||
packages = ["."]
|
||||
pruneopts = "NUT"
|
||||
revision = "d5fe4b57a186c716b0e00b8c301cbd9b4182694d"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:b634d733abf079dc191d359e5a8d31479f1795d00e656f8a018a459571046266"
|
||||
name = "github.com/hashicorp/go-getter"
|
||||
packages = ["helper/url"]
|
||||
pruneopts = "NUT"
|
||||
revision = "4bda8fa99001c61db3cad96b421d4c12a81f256d"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:fbab03227343a0285fc74a68dd2ff46cda7edecbbe5a3e98d2cecd00cc67b217"
|
||||
name = "github.com/hashicorp/go-safetemp"
|
||||
packages = ["."]
|
||||
pruneopts = "NUT"
|
||||
revision = "b1a1dbde6fdc11e3ae79efd9039009e22d4ae240"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:0b06ffe0c0764e413a6738e3f045d6bb14117359aef80a09f8c60fbff2ecad6b"
|
||||
name = "github.com/hashicorp/go-version"
|
||||
packages = ["."]
|
||||
pruneopts = "NUT"
|
||||
revision = "b5a281d3160aa11950a6182bd9a9dc2cb1e02d50"
|
||||
version = "v1.0.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:11c6c696067d3127ecf332b10f89394d386d9083f82baf71f40f2da31841a009"
|
||||
name = "github.com/hashicorp/hcl"
|
||||
packages = [
|
||||
".",
|
||||
"hcl/ast",
|
||||
"hcl/parser",
|
||||
"hcl/printer",
|
||||
"hcl/scanner",
|
||||
"hcl/strconv",
|
||||
"hcl/token",
|
||||
"json/parser",
|
||||
"json/scanner",
|
||||
"json/token",
|
||||
]
|
||||
pruneopts = "NUT"
|
||||
revision = "ef8a98b0bbce4a65b5aa4c368430a80ddc533168"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:d457d39e88f678ed14ac29517c3d74927a48dbc6a9f073fa241cf364a68cbe5c"
|
||||
name = "github.com/heroku/rollrus"
|
||||
packages = ["."]
|
||||
pruneopts = "NUT"
|
||||
revision = "fc0cef2ff331aebb24cd4e9ded7e20650f3d7006"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:62fe3a7ea2050ecbd753a71889026f83d73329337ada66325cbafd5dea5f713d"
|
||||
name = "github.com/jbenet/go-context"
|
||||
packages = ["io"]
|
||||
pruneopts = "NUT"
|
||||
revision = "d14ea06fba99483203c19d92cfcd13ebe73135f4"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:490643e333b848f3d6ab772c21082d706663dcf4a3c0fbe9a4b4ef7b205ce6c7"
|
||||
name = "github.com/jesseduffield/go-getter"
|
||||
packages = ["."]
|
||||
pruneopts = "NUT"
|
||||
revision = "906e15686e6309ff310c1c10463ab53287c3a678"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:71e6c15797951d3fabaa944d70253e36a6cee96bf54ca0bc43ca3de3b4814bbb"
|
||||
name = "github.com/jesseduffield/gocui"
|
||||
packages = ["."]
|
||||
pruneopts = "NUT"
|
||||
revision = "2cb6e95bbbf850bb32cc1799e07d08ff0f144746"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:ac6d01547ec4f7f673311b4663909269bfb8249952de3279799289467837c3cc"
|
||||
name = "github.com/jmespath/go-jmespath"
|
||||
packages = ["."]
|
||||
pruneopts = "NUT"
|
||||
revision = "0b12d6b5"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:263f9b0a0bcbfff9d5e7d9f2aa11f53995d98214fe0fb97e429e7a5f4534a0f9"
|
||||
name = "github.com/kardianos/osext"
|
||||
packages = ["."]
|
||||
pruneopts = "NUT"
|
||||
revision = "ae77be60afb1dcacde03767a8c37337fad28ac14"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:8021af4dcbd531ae89433c8c3a6520e51064114aaf8eb1724c3cf911c497c9ba"
|
||||
name = "github.com/kevinburke/ssh_config"
|
||||
packages = ["."]
|
||||
pruneopts = "NUT"
|
||||
revision = "9fc7bb800b555d63157c65a904c86a2cc7b4e795"
|
||||
version = "0.4"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:d244f8666a838fe6ad70ec8fe77f50ebc29fdc3331a2729ba5886bef8435d10d"
|
||||
name = "github.com/magiconair/properties"
|
||||
packages = ["."]
|
||||
pruneopts = "NUT"
|
||||
revision = "c2353362d570a7bfa228149c62842019201cfb71"
|
||||
version = "v1.8.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:08c231ec84231a7e23d67e4b58f975e1423695a32467a362ee55a803f9de8061"
|
||||
name = "github.com/mattn/go-colorable"
|
||||
packages = ["."]
|
||||
pruneopts = "NUT"
|
||||
revision = "167de6bfdfba052fa6b2d3664c8f5272e23c9072"
|
||||
version = "v0.0.9"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:bc4f7eec3b7be8c6cb1f0af6c1e3333d5bb71072951aaaae2f05067b0803f287"
|
||||
name = "github.com/mattn/go-isatty"
|
||||
packages = ["."]
|
||||
pruneopts = "NUT"
|
||||
revision = "0360b2af4f38e8d38c7fce2a9f4e702702d73a39"
|
||||
version = "v0.0.3"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:cb591533458f6eb6e2c1065ff3eac6b50263d7847deb23fc9f79b25bc608970e"
|
||||
name = "github.com/mattn/go-runewidth"
|
||||
packages = ["."]
|
||||
pruneopts = "NUT"
|
||||
revision = "9e777a8366cce605130a531d2cd6363d07ad7317"
|
||||
version = "v0.0.2"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:a25c9a6b41e100f4ce164db80260f2b687095ba9d8b46a1d6072d3686cc020db"
|
||||
name = "github.com/mgutz/str"
|
||||
packages = ["."]
|
||||
pruneopts = "NUT"
|
||||
revision = "968bf66e3da857419e4f6e71b2d5c9ae95682dc4"
|
||||
version = "v1.2.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:a4df73029d2c42fabcb6b41e327d2f87e685284ec03edf76921c267d9cfc9c23"
|
||||
name = "github.com/mitchellh/go-homedir"
|
||||
packages = ["."]
|
||||
pruneopts = "NUT"
|
||||
revision = "58046073cbffe2f25d425fe1331102f55cf719de"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:18b773b92ac82a451c1276bd2776c1e55ce057ee202691ab33c8d6690efcc048"
|
||||
name = "github.com/mitchellh/go-testing-interface"
|
||||
packages = ["."]
|
||||
pruneopts = "NUT"
|
||||
revision = "a61a99592b77c9ba629d254a693acffaeb4b7e28"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:5fe20cfe4ef484c237cec9f947b2a6fa90bad4b8610fd014f0e4211e13d82d5d"
|
||||
name = "github.com/mitchellh/mapstructure"
|
||||
packages = ["."]
|
||||
pruneopts = "NUT"
|
||||
revision = "f15292f7a699fcc1a38a80977f80a046874ba8ac"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:2c34c77bf3ec848da26e48af58fc511ed52750961fa848399d122882b8890928"
|
||||
name = "github.com/nicksnyder/go-i18n"
|
||||
packages = [
|
||||
"v2/i18n",
|
||||
"v2/internal",
|
||||
"v2/internal/plural",
|
||||
]
|
||||
pruneopts = "NUT"
|
||||
revision = "a16b91a3ba80db3a2301c70d1d302d42251c9079"
|
||||
version = "v2.0.0-beta.5"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:34d9354c2c5d916c05864327553047df59fc10e86ff1f408e4136eba0a25a5ec"
|
||||
name = "github.com/nsf/termbox-go"
|
||||
packages = ["."]
|
||||
pruneopts = "NUT"
|
||||
revision = "5c94acc5e6eb520f1bcd183974e01171cc4c23b3"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:cf254277d898b713195cc6b4a3fac8bf738b9f1121625df27843b52b267eec6c"
|
||||
name = "github.com/pelletier/go-buffruneio"
|
||||
packages = ["."]
|
||||
pruneopts = "NUT"
|
||||
revision = "c37440a7cf42ac63b919c752ca73a85067e05992"
|
||||
version = "v0.2.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:51ea800cff51752ff68e12e04106f5887b4daec6f9356721238c28019f0b42db"
|
||||
name = "github.com/pelletier/go-toml"
|
||||
packages = ["."]
|
||||
pruneopts = "NUT"
|
||||
revision = "c01d1270ff3e442a8a57cddc1c92dc1138598194"
|
||||
version = "v1.2.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:5cf3f025cbee5951a4ee961de067c8a89fc95a5adabead774f82822efabab121"
|
||||
name = "github.com/pkg/errors"
|
||||
packages = ["."]
|
||||
pruneopts = "NUT"
|
||||
revision = "645ef00459ed84a119197bfb8d8205042c6df63d"
|
||||
version = "v0.8.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:0028cb19b2e4c3112225cd871870f2d9cf49b9b4276531f03438a88e94be86fe"
|
||||
name = "github.com/pmezard/go-difflib"
|
||||
packages = ["difflib"]
|
||||
pruneopts = "NUT"
|
||||
revision = "792786c7400a136282c1664665ae0a8db921c6c2"
|
||||
version = "v1.0.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:d917313f309bda80d27274d53985bc65651f81a5b66b820749ac7f8ef061fd04"
|
||||
name = "github.com/sergi/go-diff"
|
||||
packages = ["diffmatchpatch"]
|
||||
pruneopts = "NUT"
|
||||
revision = "1744e2970ca51c86172c8190fadad617561ed6e7"
|
||||
version = "v1.0.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:41618aee8828e62dfe62d44f579c06892d0e98907d1c6d5bcd83bfe8536ec5a3"
|
||||
name = "github.com/shibukawa/configdir"
|
||||
packages = ["."]
|
||||
pruneopts = "NUT"
|
||||
revision = "e180dbdc8da04c4fa04272e875ce64949f38bd3e"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:b2339e83ce9b5c4f79405f949429a7f68a9a904fed903c672aac1e7ceb7f5f02"
|
||||
name = "github.com/sirupsen/logrus"
|
||||
packages = ["."]
|
||||
pruneopts = "NUT"
|
||||
revision = "3e01752db0189b9157070a0e1668a620f9a85da2"
|
||||
version = "v1.0.6"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:330e9062b308ac597e28485699c02223bd052437a6eed32a173c9227dcb9d95a"
|
||||
name = "github.com/spf13/afero"
|
||||
packages = [
|
||||
".",
|
||||
"mem",
|
||||
]
|
||||
pruneopts = "NUT"
|
||||
revision = "787d034dfe70e44075ccc060d346146ef53270ad"
|
||||
version = "v1.1.1"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:3fa7947ca83b98ae553590d993886e845a4bff19b7b007e869c6e0dd3b9da9cd"
|
||||
name = "github.com/spf13/cast"
|
||||
packages = ["."]
|
||||
pruneopts = "NUT"
|
||||
revision = "8965335b8c7107321228e3e3702cab9832751bac"
|
||||
version = "v1.2.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:f29f83301ed096daed24a90f4af591b7560cb14b9cc3e1827abbf04db7269ab5"
|
||||
name = "github.com/spf13/jwalterweatherman"
|
||||
packages = ["."]
|
||||
pruneopts = "NUT"
|
||||
revision = "14d3d4c518341bea657dd8a226f5121c0ff8c9f2"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:e3707aeaccd2adc89eba6c062fec72116fe1fc1ba71097da85b4d8ae1668a675"
|
||||
name = "github.com/spf13/pflag"
|
||||
packages = ["."]
|
||||
pruneopts = "NUT"
|
||||
revision = "9a97c102cda95a86cec2345a6f09f55a939babf5"
|
||||
version = "v1.0.2"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:454979540e2a1582f375a17c106cf4e11e3bcac4baffb4af23e515c87f87de13"
|
||||
name = "github.com/spf13/viper"
|
||||
packages = ["."]
|
||||
pruneopts = "NUT"
|
||||
revision = "907c19d40d9a6c9bb55f040ff4ae45271a4754b9"
|
||||
version = "v1.1.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:0e9a5ac14bcc11f205031a671b28c7e05cb88b2ebbe06f383c1ab0b2c12c7cb5"
|
||||
name = "github.com/spkg/bom"
|
||||
packages = ["."]
|
||||
pruneopts = "NUT"
|
||||
revision = "59b7046e48ad6bac800c5e1dd5142282cbfcf154"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:ccca1dcd18bc54e23b517a3c5babeff2e3924a7d8fc1932162225876cfe4bfb0"
|
||||
name = "github.com/src-d/gcfg"
|
||||
packages = [
|
||||
".",
|
||||
"scanner",
|
||||
"token",
|
||||
"types",
|
||||
]
|
||||
pruneopts = "NUT"
|
||||
revision = "f187355171c936ac84a82793659ebb4936bc1c23"
|
||||
version = "v1.3.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:bacb8b590716ab7c33f2277240972c9582d389593ee8d66fc10074e0508b8126"
|
||||
name = "github.com/stretchr/testify"
|
||||
packages = ["assert"]
|
||||
pruneopts = "NUT"
|
||||
revision = "f35b8ab0b5a2cef36673838d662e249dd9c94686"
|
||||
version = "v1.2.2"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:e42372d3f4921ec35df07f9b23239631e9d28580f7c1edcca212bc6daddc68fe"
|
||||
name = "github.com/stvp/roll"
|
||||
packages = ["."]
|
||||
pruneopts = "NUT"
|
||||
revision = "3627a5cbeaeaa68023abd02bb8687925265f2f63"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:cd5ffc5bda4e0296ab3e4de90dbb415259c78e45e7fab13694b14cde8ab74541"
|
||||
name = "github.com/tcnksm/go-gitconfig"
|
||||
packages = ["."]
|
||||
pruneopts = "NUT"
|
||||
revision = "d154598bacbf4501c095a309753c5d4af66caa81"
|
||||
version = "v0.1.2"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:07e8742c479bab0066149ad02a710024154e76874fd0a2dba002d87702725825"
|
||||
name = "github.com/ulikunitz/xz"
|
||||
packages = [
|
||||
".",
|
||||
"internal/hash",
|
||||
"internal/xlog",
|
||||
"lzma",
|
||||
]
|
||||
pruneopts = "NUT"
|
||||
revision = "0c6b41e72360850ca4f98dc341fd999726ea007f"
|
||||
version = "v0.5.4"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:3148cb3478c26a92b4c1a18abb9428234b281e278af6267840721a24b6cbc6a3"
|
||||
name = "github.com/xanzy/ssh-agent"
|
||||
packages = ["."]
|
||||
pruneopts = "NUT"
|
||||
revision = "640f0ab560aeb89d523bb6ac322b1244d5c3796c"
|
||||
version = "v0.2.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:dfcb1b2db354cafa48fc3cdafe4905a08bec4a9757919ab07155db0ca23855b4"
|
||||
name = "golang.org/x/crypto"
|
||||
packages = [
|
||||
"cast5",
|
||||
"curve25519",
|
||||
"ed25519",
|
||||
"ed25519/internal/edwards25519",
|
||||
"internal/chacha20",
|
||||
"internal/subtle",
|
||||
"openpgp",
|
||||
"openpgp/armor",
|
||||
"openpgp/elgamal",
|
||||
"openpgp/errors",
|
||||
"openpgp/packet",
|
||||
"openpgp/s2k",
|
||||
"poly1305",
|
||||
"ssh",
|
||||
"ssh/agent",
|
||||
"ssh/knownhosts",
|
||||
"ssh/terminal",
|
||||
]
|
||||
pruneopts = "NUT"
|
||||
revision = "de0752318171da717af4ce24d0a2e8626afaeb11"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:76ee51c3f468493aff39dbacc401e8831fbb765104cbf613b89bef01cf4bad70"
|
||||
name = "golang.org/x/net"
|
||||
packages = ["context"]
|
||||
pruneopts = "NUT"
|
||||
revision = "c39426892332e1bb5ec0a434a079bf82f5d30c54"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
digest = "1:ec76a40fbfda0c329ee58f4e3b14b4279a939efce89eca020e934e2e5234eddd"
|
||||
name = "golang.org/x/sys"
|
||||
packages = [
|
||||
"unix",
|
||||
"windows",
|
||||
]
|
||||
pruneopts = "NUT"
|
||||
revision = "98c5dad5d1a0e8a73845ecc8897d0bd56586511d"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:a95288ef1ef4dfad6cba7fe30843e1683f71bc28c912ca1ba3f6a539d44db739"
|
||||
name = "golang.org/x/text"
|
||||
packages = [
|
||||
"internal/gen",
|
||||
"internal/tag",
|
||||
"internal/triegen",
|
||||
"internal/ucd",
|
||||
"language",
|
||||
"transform",
|
||||
"unicode/cldr",
|
||||
"unicode/norm",
|
||||
]
|
||||
pruneopts = "NUT"
|
||||
revision = "f21a4dfb5e38f5895301dc265a8def02365cc3d0"
|
||||
version = "v0.3.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:47a697b155f5214ff14e68e39ce9c2e8d93e1fb035ae5ba7e247d044e0ce64e3"
|
||||
name = "gopkg.in/src-d/go-billy.v4"
|
||||
packages = [
|
||||
".",
|
||||
"helper/chroot",
|
||||
"helper/polyfill",
|
||||
"osfs",
|
||||
"util",
|
||||
]
|
||||
pruneopts = "NUT"
|
||||
revision = "83cf655d40b15b427014d7875d10850f96edba14"
|
||||
version = "v4.2.0"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:e66078da2bd6e53c72518d7f6ae0c3c8c7f34c0df12c39435ce34a6bce165525"
|
||||
name = "gopkg.in/src-d/go-git.v4"
|
||||
packages = [
|
||||
".",
|
||||
"config",
|
||||
"internal/revision",
|
||||
"plumbing",
|
||||
"plumbing/cache",
|
||||
"plumbing/filemode",
|
||||
"plumbing/format/config",
|
||||
"plumbing/format/diff",
|
||||
"plumbing/format/gitignore",
|
||||
"plumbing/format/idxfile",
|
||||
"plumbing/format/index",
|
||||
"plumbing/format/objfile",
|
||||
"plumbing/format/packfile",
|
||||
"plumbing/format/pktline",
|
||||
"plumbing/object",
|
||||
"plumbing/protocol/packp",
|
||||
"plumbing/protocol/packp/capability",
|
||||
"plumbing/protocol/packp/sideband",
|
||||
"plumbing/revlist",
|
||||
"plumbing/storer",
|
||||
"plumbing/transport",
|
||||
"plumbing/transport/client",
|
||||
"plumbing/transport/file",
|
||||
"plumbing/transport/git",
|
||||
"plumbing/transport/http",
|
||||
"plumbing/transport/internal/common",
|
||||
"plumbing/transport/server",
|
||||
"plumbing/transport/ssh",
|
||||
"storage",
|
||||
"storage/filesystem",
|
||||
"storage/filesystem/dotgit",
|
||||
"storage/memory",
|
||||
"utils/binary",
|
||||
"utils/diff",
|
||||
"utils/ioutil",
|
||||
"utils/merkletrie",
|
||||
"utils/merkletrie/filesystem",
|
||||
"utils/merkletrie/index",
|
||||
"utils/merkletrie/internal/frame",
|
||||
"utils/merkletrie/noder",
|
||||
]
|
||||
pruneopts = "NUT"
|
||||
revision = "43d17e14b714665ab5bc2ecc220b6740779d733f"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:b233ad4ec87ac916e7bf5e678e98a2cb9e8b52f6de6ad3e11834fc7a71b8e3bf"
|
||||
name = "gopkg.in/warnings.v0"
|
||||
packages = ["."]
|
||||
pruneopts = "NUT"
|
||||
revision = "ec4a0fea49c7b46c2aeb0b51aac55779c607e52b"
|
||||
version = "v0.1.2"
|
||||
|
||||
[[projects]]
|
||||
digest = "1:7c95b35057a0ff2e19f707173cc1a947fa43a6eb5c4d300d196ece0334046082"
|
||||
name = "gopkg.in/yaml.v2"
|
||||
packages = ["."]
|
||||
pruneopts = "NUT"
|
||||
revision = "5420a8b6744d3b0345ab293f6fcba19c978f1183"
|
||||
version = "v2.2.1"
|
||||
|
||||
[solve-meta]
|
||||
analyzer-name = "dep"
|
||||
analyzer-version = 1
|
||||
input-imports = [
|
||||
"github.com/cloudfoundry/jibber_jabber",
|
||||
"github.com/fatih/color",
|
||||
"github.com/golang-collections/collections/stack",
|
||||
"github.com/heroku/rollrus",
|
||||
"github.com/jesseduffield/go-getter",
|
||||
"github.com/jesseduffield/gocui",
|
||||
"github.com/kardianos/osext",
|
||||
"github.com/mgutz/str",
|
||||
"github.com/nicksnyder/go-i18n/v2/i18n",
|
||||
"github.com/shibukawa/configdir",
|
||||
"github.com/sirupsen/logrus",
|
||||
"github.com/spf13/viper",
|
||||
"github.com/spkg/bom",
|
||||
"github.com/stretchr/testify/assert",
|
||||
"github.com/tcnksm/go-gitconfig",
|
||||
"golang.org/x/text/language",
|
||||
"gopkg.in/src-d/go-git.v4",
|
||||
"gopkg.in/src-d/go-git.v4/plumbing",
|
||||
"gopkg.in/yaml.v2",
|
||||
]
|
||||
solver-name = "gps-cdcl"
|
||||
solver-version = 1
|
||||
46
Gopkg.toml
46
Gopkg.toml
@@ -1,46 +0,0 @@
|
||||
# Gopkg.toml example
|
||||
#
|
||||
# Refer to https://golang.github.io/dep/docs/Gopkg.toml.html
|
||||
# for detailed Gopkg.toml documentation.
|
||||
#
|
||||
# required = ["github.com/user/thing/cmd/thing"]
|
||||
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
|
||||
#
|
||||
# [[constraint]]
|
||||
# name = "github.com/user/project"
|
||||
# version = "1.0.0"
|
||||
#
|
||||
# [[constraint]]
|
||||
# name = "github.com/user/project2"
|
||||
# branch = "dev"
|
||||
# source = "github.com/myfork/project2"
|
||||
#
|
||||
# [[override]]
|
||||
# name = "github.com/x/y"
|
||||
# version = "2.4.0"
|
||||
#
|
||||
|
||||
[prune]
|
||||
go-tests = true
|
||||
unused-packages = true
|
||||
non-go = true
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/fatih/color"
|
||||
version = "1.7.0"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/golang-collections/collections"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/jesseduffield/gocui"
|
||||
|
||||
[[constraint]]
|
||||
name = "gopkg.in/src-d/go-git.v4"
|
||||
revision = "43d17e14b714665ab5bc2ecc220b6740779d733f"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/spkg/bom"
|
||||
118
README.md
118
README.md
@@ -1,31 +1,44 @@
|
||||
# lazygit [](https://circleci.com/gh/jesseduffield/lazygit) [](https://codecov.io/gh/jesseduffield/lazygit) [](https://goreportcard.com/report/github.com/jesseduffield/lazygit) [](https://golangci.com) [](http://godoc.org/github.com/jesseduffield/lazygit) []()
|
||||
|
||||
A simple terminal UI for git commands, written in Go with the [gocui](https://github.com/jroimartin/gocui "gocui") library.
|
||||
A simple terminal UI for git commands, written in Go with the [gocui](https://github.com/jroimartin/gocui 'gocui') library.
|
||||
|
||||
Are YOU tired of typing every git command directly into the terminal, but you're
|
||||
too stubborn to use Sourcetree because you'll never forgive Atlassian for making
|
||||
Jira? This is the app for you!
|
||||
Rant time: You've heard it before, git is _powerful_, but what good is that power when everything is so damn hard to do? Interactive rebasing requires you to edit a goddamn TODO file in your editor? *Are you kidding me?* To stage part of a file you need to use a command line program stepping through each hunk and if a hunk can't be split down any further but contains code you don't want to stage, bad luck? *Are you KIDDING me?!* Sometimes you get asked to stash your changes when switching branches only to realise that after you switch and unstash that there weren't even any conflicts and it would have been fine to just checkout the branch directly? *YOU HAVE GOT TO BE KIDDING ME!*
|
||||
|
||||
If you're a mere mortal like me and you're tired of hearing how powerful git is when in your daily life it's a powerful pain in your ass, lazygit might be for you.
|
||||
|
||||

|
||||
|
||||
* [Installation](https://github.com/jesseduffield/lazygit#installation)
|
||||
* [Usage](https://github.com/jesseduffield/lazygit#usage),
|
||||
[Keybindings](https://github.com/jesseduffield/lazygit/blob/master/docs/Keybindings.md)
|
||||
* [Cool Features](https://github.com/jesseduffield/lazygit#cool-features)
|
||||
* [Contributing](https://github.com/jesseduffield/lazygit#contributing)
|
||||
* [Video Tutorial](https://www.youtube.com/watch?v=VDXvbHZYeKY)
|
||||
* [Twitch Stream](https://www.twitch.tv/jesseduffield)
|
||||
- [Installation](https://github.com/jesseduffield/lazygit#installation)
|
||||
- [Usage](https://github.com/jesseduffield/lazygit#usage),
|
||||
[Keybindings](/docs/keybindings)
|
||||
- [Cool Features](https://github.com/jesseduffield/lazygit#cool-features)
|
||||
- [Contributing](https://github.com/jesseduffield/lazygit#contributing)
|
||||
- [Video Tutorial](https://youtu.be/VDXvbHZYeKY)
|
||||
- [Rebase Magic Video Tutorial](https://youtu.be/4XaToVut_hs)
|
||||
- [Twitch Stream](https://www.twitch.tv/jesseduffield)
|
||||
|
||||
[<img src="https://i.imgur.com/sVEktDn.png">](https://youtu.be/CPLdltN7wgE)
|
||||
|
||||
Github Sponsors is matching all donations dollar-for-dollar for 12 months so if you're feeling generous consider [sponsoring me](https://github.com/sponsors/jesseduffield)
|
||||
|
||||
## Installation
|
||||
|
||||
### Homebrew
|
||||
```sh
|
||||
brew tap jesseduffield/lazygit
|
||||
Normally the lazygit formula can be found in the Homebrew core but we suggest you tap our formula to get the frequently updated one. It works with Linux, too.
|
||||
|
||||
Tap:
|
||||
```
|
||||
brew install jesseduffield/lazygit/lazygit
|
||||
```
|
||||
|
||||
Core:
|
||||
|
||||
```
|
||||
brew install lazygit
|
||||
```
|
||||
|
||||
### Ubuntu
|
||||
|
||||
Packages for Ubuntu 16.04, 18.04 and 18.10 are available via [Launchpad PPA](https://launchpad.net/~lazygit-team).
|
||||
|
||||
**Release builds**
|
||||
@@ -49,6 +62,7 @@ sudo apt-get install lazygit
|
||||
```
|
||||
|
||||
### Void Linux
|
||||
|
||||
Packages for Void Linux are available in the distro repo
|
||||
|
||||
They follow upstream latest releases
|
||||
@@ -58,80 +72,114 @@ sudo xbps-install -S lazygit
|
||||
```
|
||||
|
||||
### Arch Linux
|
||||
|
||||
Packages for Arch Linux are available via AUR (Arch User Repository).
|
||||
|
||||
There are two packages. The stable one which is built with the latest release
|
||||
and the git version which builds from the most recent commit.
|
||||
|
||||
* Stable: https://aur.archlinux.org/packages/lazygit/
|
||||
* Development: https://aur.archlinux.org/packages/lazygit-git/
|
||||
- Stable: https://aur.archlinux.org/packages/lazygit/
|
||||
- Development: https://aur.archlinux.org/packages/lazygit-git/
|
||||
|
||||
Instruction of how to install AUR content can be found here:
|
||||
https://wiki.archlinux.org/index.php/Arch_User_Repository
|
||||
|
||||
### Conda
|
||||
|
||||
Released versions are available for different platforms, see https://anaconda.org/conda-forge/lazygit
|
||||
|
||||
```sh
|
||||
conda install -c conda-forge lazygit
|
||||
```
|
||||
|
||||
### Binary Release (Windows/Linux/OSX)
|
||||
|
||||
You can download a binary release [here](https://github.com/jesseduffield/lazygit/releases).
|
||||
|
||||
### Go
|
||||
|
||||
```sh
|
||||
go get github.com/jesseduffield/lazygit
|
||||
```
|
||||
|
||||
Please note:
|
||||
If you get an error claiming that lazygit cannot be found or is not defined, you
|
||||
may need to add `~/go/bin` to your $PATH (MacOS/Linux), or `%HOME%\go\bin`
|
||||
may need to add `~/go/bin` to your \$PATH (MacOS/Linux), or `%HOME%\go\bin`
|
||||
(Windows). Not to be mistaked for `C:\Go\bin` (which is for Go's own binaries,
|
||||
not apps like Lazygit).
|
||||
|
||||
## Usage
|
||||
|
||||
Call `lazygit` in your terminal inside a git repository. If you want, you can
|
||||
also add an alias for this with `echo "alias lg='lazygit'" >> ~/.zshrc` (or
|
||||
whichever rc file you're using).
|
||||
|
||||
* Basic video tutorial [here](https://www.youtube.com/watch?v=VDXvbHZYeKY).
|
||||
* List of keybindings
|
||||
[here](/docs/Keybindings.md).
|
||||
- Basic video tutorial [here](https://youtu.be/VDXvbHZYeKY).
|
||||
- Rebase Magic tutorial [here](https://youtu.be/4XaToVut_hs)
|
||||
- List of keybindings
|
||||
[here](/docs/keybindings).
|
||||
|
||||
## Changing Directory On Exit
|
||||
|
||||
If you change repos in lazygit and want your shell to change directory into that repo on exiting lazygit, add this to your `~./zhsrc` (or other rc file):
|
||||
```
|
||||
lg()
|
||||
{
|
||||
export LAZYGIT_NEW_DIR_FILE=~/.lazygit/newdir
|
||||
|
||||
lazygit "$@"
|
||||
|
||||
if [ -f $LAZYGIT_NEW_DIR_FILE ]; then
|
||||
cd "$(cat $LAZYGIT_NEW_DIR_FILE)"
|
||||
rm -f $LAZYGIT_NEW_DIR_FILE > /dev/null
|
||||
fi
|
||||
}
|
||||
```
|
||||
Then `source ~/.zshrc` and from now on when you call `lg` and exit you'll switch directories to whatever you were in inside lazyigt. To override this behaviour you can exit using `shift+Q` rather than just `q`.
|
||||
|
||||
## Cool features
|
||||
* Adding files easily
|
||||
* Resolving merge conflicts
|
||||
* Easily check out recent branches
|
||||
* Scroll through logs/diffs of branches/commits/stash
|
||||
* Quick pushing/pulling
|
||||
* Squash down and rename commits
|
||||
|
||||
- Adding files easily
|
||||
- Resolving merge conflicts
|
||||
- Easily check out recent branches
|
||||
- Scroll through logs/diffs of branches/commits/stash
|
||||
- Quick pushing/pulling
|
||||
- Squash down and rename commits
|
||||
|
||||
### Resolving merge conflicts
|
||||
|
||||

|
||||
|
||||
### Viewing commit diffs
|
||||

|
||||
### Interactive Rebasing
|
||||
|
||||
## Milestones
|
||||
- [x] Easy Installation (homebrew, release binaries)
|
||||
- [ ] Configurable Keybindings
|
||||
- [ ] Configurable Color Themes
|
||||
- [ ] Spawning Subprocesses (help needed - have a look at https://github.com/jesseduffield/lazygit/pull/18)
|
||||
- [ ] Maintainability
|
||||
- [ ] Performance
|
||||
- [ ] i18n
|
||||

|
||||
|
||||
## Contributing
|
||||
|
||||
We love your input! Please check out the [contributing guide](CONTRIBUTING.md).
|
||||
For contributor discussion about things not better discussed here in the repo, join the slack channel
|
||||
|
||||
[](https://join.slack.com/t/lazygit/shared_invite/enQtNDE3MjIwNTYyMDA0LTM3Yjk3NzdiYzhhNTA1YjM4Y2M4MWNmNDBkOTI0YTE4YjQ1ZmI2YWRhZTgwNjg2YzhhYjg3NDBlMmQyMTI5N2M)
|
||||
|
||||
## Donate
|
||||
|
||||
If you would like to support the development of lazygit, consider [sponsoring me](https://github.com/sponsors/jesseduffield) (github is matching all donations dollar-for-dollar for 12 months)
|
||||
|
||||
## Work in progress
|
||||
|
||||
This is still a work in progress so there's still bugs to iron out and as this
|
||||
is my first project in Go the code could no doubt use an increase in quality,
|
||||
but I'll be improving on it whenever I find the time. If you have any feedback
|
||||
feel free to [raise an issue](https://github.com/jesseduffield/lazygit/issues)/[submit a PR](https://github.com/jesseduffield/lazygit/pulls).
|
||||
|
||||
## Social
|
||||
|
||||
If you want to see what I (Jesse) am up to in terms of development, follow me on
|
||||
[twitter](https://twitter.com/DuffieldJesse) or watch me program on
|
||||
[twitch](https://www.twitch.tv/jesseduffield).
|
||||
|
||||
## Alternatives
|
||||
|
||||
If you find that lazygit doesn't quite satisfy your requirements, these may be a better fit:
|
||||
|
||||
- [tig](https://github.com/jonas/tig)
|
||||
|
||||
@@ -2,11 +2,13 @@
|
||||
|
||||
## Default:
|
||||
|
||||
```
|
||||
```yaml
|
||||
gui:
|
||||
# stuff relating to the UI
|
||||
scrollHeight: 2 # how many lines you scroll by
|
||||
scrollPastBottom: true # enable scrolling past the bottom
|
||||
theme:
|
||||
lightTheme: false # For terminals with a light background
|
||||
activeBorderColor:
|
||||
- white
|
||||
- bold
|
||||
@@ -16,6 +18,13 @@
|
||||
- blue
|
||||
commitLength:
|
||||
show: true
|
||||
mouseEvents: true
|
||||
git:
|
||||
merging:
|
||||
# only applicable to unix users
|
||||
manualCommit: false
|
||||
skipHookPrefix: WIP
|
||||
autoFetch: true
|
||||
update:
|
||||
method: prompt # can be: prompt | background | never
|
||||
days: 14 # how often an update is checked for
|
||||
@@ -27,21 +36,21 @@
|
||||
|
||||
### Windows:
|
||||
|
||||
```
|
||||
```yaml
|
||||
os:
|
||||
openCommand: 'cmd /c "start "" {{filename}}"'
|
||||
```
|
||||
|
||||
### Linux:
|
||||
|
||||
```
|
||||
```yaml
|
||||
os:
|
||||
openCommand: 'sh -c "xdg-open {{filename}} >/dev/null"'
|
||||
```
|
||||
|
||||
### OSX:
|
||||
|
||||
```
|
||||
```yaml
|
||||
os:
|
||||
openCommand: 'open {{filename}}'
|
||||
```
|
||||
@@ -50,7 +59,7 @@
|
||||
|
||||
for users of VSCode
|
||||
|
||||
```
|
||||
```yaml
|
||||
os:
|
||||
openCommand: 'code -r {{filename}}'
|
||||
```
|
||||
@@ -73,6 +82,21 @@ The available attributes are:
|
||||
- reverse # useful for high-contrast
|
||||
- underline
|
||||
|
||||
## Light terminal theme:
|
||||
|
||||
If you have issues with a light terminal theme where you can't read / see the text add these settings
|
||||
|
||||
```yaml
|
||||
gui:
|
||||
theme:
|
||||
lightTheme: true
|
||||
activeBorderColor:
|
||||
- black
|
||||
- bold
|
||||
inactiveBorderColor:
|
||||
- black
|
||||
```
|
||||
|
||||
## Example Coloring:
|
||||
|
||||

|
||||
|
||||
@@ -1,85 +0,0 @@
|
||||
# Keybindings:
|
||||
|
||||
## Global:
|
||||
|
||||
<pre>
|
||||
<kbd>←</kbd><kbd>→</kbd><kbd>↑</kbd><kbd>↓</kbd>/<kbd>h</kbd><kbd>j</kbd><kbd>k</kbd><kbd>l</kbd>: navigate
|
||||
<kbd>PgUp</kbd>/<kbd>PgDn</kbd> or <kbd>ctrl</kbd>+<kbd>u</kbd>/<kbd>ctrl</kbd>+<kbd>d</kbd>: scroll diff panel
|
||||
(for <kbd>PgUp</kbd> and <kbd>PgDn</kbd>, use <kbd>fn</kbd>+<kbd>up</kbd>/<kbd>fn</kbd>+<kbd>down</kbd> on osx)
|
||||
<kbd>q</kbd>: quit
|
||||
<kbd>p</kbd>: pull
|
||||
<kbd>shift</kbd>+<kbd>P</kbd>: push
|
||||
</pre>
|
||||
|
||||
## Status Panel:
|
||||
|
||||
<pre>
|
||||
<kbd>e</kbd>: edit config file
|
||||
<kbd>o</kbd>: open config file
|
||||
</pre>
|
||||
|
||||
## Files Panel:
|
||||
|
||||
<pre>
|
||||
<kbd>space</kbd>: toggle staged
|
||||
<kbd>a</kbd>: stage/unstage all
|
||||
<kbd>c</kbd>: commit changes
|
||||
<kbd>shift</kbd>+<kbd>C</kbd>: commit using git editor
|
||||
<kbd>shift</kbd>+<kbd>S</kbd>: stash files
|
||||
<kbd>t</kbd>: add patched (i.e. pick chunks of a file to add)
|
||||
<kbd>o</kbd>: open
|
||||
<kbd>e</kbd>: edit
|
||||
<kbd>s</kbd>: open in sublime (requires 'subl' command)
|
||||
<kbd>v</kbd>: open in vscode (requires 'code' command)
|
||||
<kbd>i</kbd>: add to .gitignore
|
||||
<kbd>d</kbd>: delete if untracked checkout if tracked (aka go away)
|
||||
<kbd>shift</kbd>+<kbd>R</kbd>: refresh files
|
||||
<kbd>shift</kbd>+<kbd>A</kbd>: abort merge
|
||||
</pre>
|
||||
|
||||
## Branches Panel:
|
||||
|
||||
<pre>
|
||||
<kbd>space</kbd>: checkout branch
|
||||
<kbd>f</kbd>: force checkout branch
|
||||
<kbd>m</kbd>: merge into currently checked out branch
|
||||
<kbd>c</kbd>: checkout by name
|
||||
<kbd>n</kbd>: new branch
|
||||
<kbd>d</kbd>: delete branch
|
||||
<kbd>D</kbd>: force delete branch
|
||||
</pre>
|
||||
|
||||
## Commits Panel:
|
||||
|
||||
<pre>
|
||||
<kbd>s</kbd>: squash down (only available for topmost commit)
|
||||
<kbd>r</kbd>: rename commit
|
||||
<kbd>shift</kbd>+<kbd>R</kbd>: rename commit using git editor
|
||||
<kbd>g</kbd>: reset to this commit
|
||||
</pre>
|
||||
|
||||
## Stash Panel:
|
||||
|
||||
<pre>
|
||||
<kbd>space</kbd>: apply
|
||||
<kbd>g</kbd>: pop
|
||||
<kbd>d</kbd>: drop
|
||||
</pre>
|
||||
|
||||
## Popup Panel:
|
||||
|
||||
<pre>
|
||||
<kbd>esc</kbd>: close/cancel
|
||||
<kbd>enter</kbd>: confirm
|
||||
<kbd>tab</kbd>: enter newline (if editing)
|
||||
</pre>
|
||||
|
||||
## Resolving Merge Conflicts (Diff Panel):
|
||||
|
||||
<pre>
|
||||
<kbd>←</kbd><kbd>→</kbd>/<kbd>h</kbd><kbd>l</kbd>: navigate conflicts
|
||||
<kbd>↑</kbd><kbd>↓</kbd>/<kbd>k</kbd><kbd>j</kbd>: select hunk
|
||||
<kbd>space</kbd>: pick hunk
|
||||
<kbd>b</kbd>: pick both hunks
|
||||
<kbd>z</kbd>: undo (only available while still inside diff panel)
|
||||
</pre>
|
||||
128
docs/keybindings/Keybindings_en.md
Normal file
128
docs/keybindings/Keybindings_en.md
Normal file
@@ -0,0 +1,128 @@
|
||||
# Lazygit menu
|
||||
|
||||
## Global
|
||||
|
||||
<pre>
|
||||
<kbd>m</kbd>: view merge/rebase options
|
||||
<kbd>P</kbd>: push
|
||||
<kbd>p</kbd>: pull
|
||||
<kbd>R</kbd>: refresh
|
||||
</pre>
|
||||
|
||||
## Status
|
||||
|
||||
<pre>
|
||||
<kbd>e</kbd>: edit config file
|
||||
<kbd>o</kbd>: open config file
|
||||
<kbd>u</kbd>: check for update
|
||||
<kbd>s</kbd>: switch to a recent repo
|
||||
</pre>
|
||||
|
||||
## Files
|
||||
|
||||
<pre>
|
||||
<kbd>c</kbd>: commit changes
|
||||
<kbd>w</kbd>: commit changes without pre-commit hook
|
||||
<kbd>A</kbd>: amend last commit
|
||||
<kbd>C</kbd>: commit changes using git editor
|
||||
<kbd>space</kbd>: toggle staged
|
||||
<kbd>d</kbd>: view 'discard changes' options
|
||||
<kbd>e</kbd>: edit file
|
||||
<kbd>o</kbd>: open file
|
||||
<kbd>i</kbd>: add to .gitignore
|
||||
<kbd>r</kbd>: refresh files
|
||||
<kbd>S</kbd>: stash files
|
||||
<kbd>a</kbd>: stage/unstage all
|
||||
<kbd>t</kbd>: add patch
|
||||
<kbd>D</kbd>: view reset options
|
||||
<kbd>enter</kbd>: stage individual hunks/lines
|
||||
<kbd>f</kbd>: fetch
|
||||
<kbd>X</kbd>: execute custom command
|
||||
</pre>
|
||||
|
||||
## Branches
|
||||
|
||||
<pre>
|
||||
<kbd>space</kbd>: checkout
|
||||
<kbd>o</kbd>: create pull request
|
||||
<kbd>c</kbd>: checkout by name
|
||||
<kbd>F</kbd>: force checkout
|
||||
<kbd>n</kbd>: new branch
|
||||
<kbd>d</kbd>: delete branch
|
||||
<kbd>r</kbd>: rebase branch
|
||||
<kbd>M</kbd>: merge into currently checked out branch
|
||||
<kbd>f</kbd>: fast-forward this branch from its upstream
|
||||
</pre>
|
||||
|
||||
## Commits
|
||||
|
||||
<pre>
|
||||
<kbd>s</kbd>: squash down
|
||||
<kbd>r</kbd>: reword commit
|
||||
<kbd>R</kbd>: rename commit with editor
|
||||
<kbd>g</kbd>: reset to this commit
|
||||
<kbd>f</kbd>: fixup commit
|
||||
<kbd>F</kbd>: create fixup commit for this commit
|
||||
<kbd>S</kbd>: squash above commits
|
||||
<kbd>d</kbd>: delete commit
|
||||
<kbd>ctrl+j</kbd>: move commit down one
|
||||
<kbd>ctrl+k</kbd>: move commit up one
|
||||
<kbd>e</kbd>: edit commit
|
||||
<kbd>A</kbd>: amend commit with staged changes
|
||||
<kbd>p</kbd>: pick commit (when mid-rebase)
|
||||
<kbd>t</kbd>: revert commit
|
||||
<kbd>c</kbd>: copy commit (cherry-pick)
|
||||
<kbd>C</kbd>: copy commit range (cherry-pick)
|
||||
<kbd>v</kbd>: paste commits (cherry-pick)
|
||||
<kbd>enter</kbd>: view commit's files
|
||||
<kbd>space</kbd>: select commit to diff with another commit
|
||||
</pre>
|
||||
|
||||
## Stash
|
||||
|
||||
<pre>
|
||||
<kbd>space</kbd>: apply
|
||||
<kbd>g</kbd>: pop
|
||||
<kbd>d</kbd>: drop
|
||||
</pre>
|
||||
|
||||
## Commit files
|
||||
|
||||
<pre>
|
||||
<kbd>esc</kbd>: go back
|
||||
<kbd>c</kbd>: checkout file
|
||||
<kbd>d</kbd>: discard this commit's changes to this file
|
||||
<kbd>o</kbd>: open file
|
||||
</pre>
|
||||
|
||||
## Main (Normal)
|
||||
|
||||
<pre>
|
||||
<kbd>PgDn</kbd>: scroll down (fn+up)
|
||||
<kbd>PgUp</kbd>: scroll up (fn+down)
|
||||
</pre>
|
||||
|
||||
## Main (Staging)
|
||||
|
||||
<pre>
|
||||
<kbd>esc</kbd>: return to files panel
|
||||
<kbd>▲</kbd>: select previous line
|
||||
<kbd>▼</kbd>: select next line
|
||||
<kbd>◄</kbd>: select previous hunk
|
||||
<kbd>►</kbd>: select next hunk
|
||||
<kbd>space</kbd>: stage line
|
||||
<kbd>a</kbd>: stage hunk
|
||||
</pre>
|
||||
|
||||
## Main (Merging)
|
||||
|
||||
<pre>
|
||||
<kbd>esc</kbd>: return to files panel
|
||||
<kbd>space</kbd>: pick hunk
|
||||
<kbd>b</kbd>: pick both hunks
|
||||
<kbd>◄</kbd>: select previous conflict
|
||||
<kbd>►</kbd>: select next conflict
|
||||
<kbd>▲</kbd>: select top hunk
|
||||
<kbd>▼</kbd>: select bottom hunk
|
||||
<kbd>z</kbd>: undo
|
||||
</pre>
|
||||
128
docs/keybindings/Keybindings_nl.md
Normal file
128
docs/keybindings/Keybindings_nl.md
Normal file
@@ -0,0 +1,128 @@
|
||||
# Lazygit menu
|
||||
|
||||
## Global
|
||||
|
||||
<pre>
|
||||
<kbd>m</kbd>: bekijk merge/rebase opties
|
||||
<kbd>P</kbd>: push
|
||||
<kbd>p</kbd>: pull
|
||||
<kbd>R</kbd>: verversen
|
||||
</pre>
|
||||
|
||||
## Status
|
||||
|
||||
<pre>
|
||||
<kbd>e</kbd>: verander config file
|
||||
<kbd>o</kbd>: open config file
|
||||
<kbd>u</kbd>: check voor updates
|
||||
<kbd>s</kbd>: wissel naar een recente repo
|
||||
</pre>
|
||||
|
||||
## Bestanden
|
||||
|
||||
<pre>
|
||||
<kbd>c</kbd>: Commit veranderingen
|
||||
<kbd>w</kbd>: commit veranderingen zonder pre-commit hook
|
||||
<kbd>A</kbd>: wijzig laatste commit
|
||||
<kbd>C</kbd>: commit veranderingen met de git editor
|
||||
<kbd>space</kbd>: toggle staged
|
||||
<kbd>d</kbd>: bekijk 'veranderingen ongedaan maken' opties
|
||||
<kbd>e</kbd>: verander bestand
|
||||
<kbd>o</kbd>: open bestand
|
||||
<kbd>i</kbd>: voeg toe aan .gitignore
|
||||
<kbd>r</kbd>: refresh bestanden
|
||||
<kbd>S</kbd>: stash-bestanden
|
||||
<kbd>a</kbd>: toggle staged alle
|
||||
<kbd>t</kbd>: bewerkingen toevoegen
|
||||
<kbd>D</kbd>: bekijk reset opties
|
||||
<kbd>enter</kbd>: stage individuele hunks/lijnen
|
||||
<kbd>f</kbd>: fetch
|
||||
<kbd>X</kbd>: voor aangepast commando uit
|
||||
</pre>
|
||||
|
||||
## Branches
|
||||
|
||||
<pre>
|
||||
<kbd>space</kbd>: uitchecken
|
||||
<kbd>o</kbd>: maak een pull-aanvraag
|
||||
<kbd>c</kbd>: uitchecken bij naam
|
||||
<kbd>F</kbd>: forceer checkout
|
||||
<kbd>n</kbd>: nieuwe branch
|
||||
<kbd>d</kbd>: verwijder branch
|
||||
<kbd>r</kbd>: rebase branch
|
||||
<kbd>M</kbd>: merge in met huidige checked out branch
|
||||
<kbd>f</kbd>: fast-forward this branch from its upstream
|
||||
</pre>
|
||||
|
||||
## Commits
|
||||
|
||||
<pre>
|
||||
<kbd>s</kbd>: squash beneden
|
||||
<kbd>r</kbd>: hernoem commit
|
||||
<kbd>R</kbd>: rename commit with editor
|
||||
<kbd>g</kbd>: reset naar deze commit
|
||||
<kbd>f</kbd>: Fixup commit
|
||||
<kbd>F</kbd>: creëer fixup commit voor deze commit
|
||||
<kbd>S</kbd>: squash bovenstaande commits
|
||||
<kbd>d</kbd>: verwijder commit
|
||||
<kbd>ctrl+j</kbd>: verplaats commit 1 omlaag
|
||||
<kbd>ctrl+k</kbd>: verplaats commit 1 omhoog
|
||||
<kbd>e</kbd>: verander commit
|
||||
<kbd>A</kbd>: wijzig commit met staged veranderingen
|
||||
<kbd>p</kbd>: pick commit (when mid-rebase)
|
||||
<kbd>t</kbd>: commit omgedaan maken
|
||||
<kbd>c</kbd>: kopiëer commit (cherry-pick)
|
||||
<kbd>C</kbd>: kopiëer commit reeks (cherry-pick)
|
||||
<kbd>v</kbd>: plak commits (cherry-pick)
|
||||
<kbd>enter</kbd>: bekijk gecommite bestanden
|
||||
<kbd>space</kbd>: select commit to diff with another commit
|
||||
</pre>
|
||||
|
||||
## Stash
|
||||
|
||||
<pre>
|
||||
<kbd>space</kbd>: toepassen
|
||||
<kbd>g</kbd>: pop
|
||||
<kbd>d</kbd>: drop
|
||||
</pre>
|
||||
|
||||
## Commit bestanden
|
||||
|
||||
<pre>
|
||||
<kbd>esc</kbd>: ga terug
|
||||
<kbd>c</kbd>: bestand uitchecken
|
||||
<kbd>d</kbd>: uitsluit deze commit zijn veranderingen aan dit bestand
|
||||
<kbd>o</kbd>: open bestand
|
||||
</pre>
|
||||
|
||||
## Hoofd (Stage Lines/Hunks)
|
||||
|
||||
<pre>
|
||||
<kbd>esc</kbd>: ga terug naar het bestanden paneel
|
||||
<kbd>▲</kbd>: selecteer de vorige lijn
|
||||
<kbd>▼</kbd>: selecteer de volgende lijn
|
||||
<kbd>◄</kbd>: selecteer de vorige hunk
|
||||
<kbd>►</kbd>: selecteer de volgende hunk
|
||||
<kbd>space</kbd>: stage lijn
|
||||
<kbd>a</kbd>: stage hunk
|
||||
</pre>
|
||||
|
||||
## Hoofd (Merging)
|
||||
|
||||
<pre>
|
||||
<kbd>esc</kbd>: ga terug naar het bestanden paneel
|
||||
<kbd>space</kbd>: pick hunk
|
||||
<kbd>b</kbd>: pick beide hunks
|
||||
<kbd>◄</kbd>: selecteer voorgaand conflict
|
||||
<kbd>►</kbd>: selecteer volgende conflict
|
||||
<kbd>▲</kbd>: selecteer bovenste hunk
|
||||
<kbd>▼</kbd>: selecteer onderste hunk
|
||||
<kbd>z</kbd>: ongedaan maken
|
||||
</pre>
|
||||
|
||||
## Hoofd (Normaal)
|
||||
|
||||
<pre>
|
||||
<kbd>PgDn</kbd>: scroll omlaag (fn+up)
|
||||
<kbd>PgUp</kbd>: scroll omhoog (fn+down)
|
||||
</pre>
|
||||
128
docs/keybindings/Keybindings_pl.md
Normal file
128
docs/keybindings/Keybindings_pl.md
Normal file
@@ -0,0 +1,128 @@
|
||||
# Lazygit menu
|
||||
|
||||
## Globalne
|
||||
|
||||
<pre>
|
||||
<kbd>m</kbd>: view merge/rebase options
|
||||
<kbd>P</kbd>: push
|
||||
<kbd>p</kbd>: pull
|
||||
<kbd>R</kbd>: odśwież
|
||||
</pre>
|
||||
|
||||
## Status
|
||||
|
||||
<pre>
|
||||
<kbd>e</kbd>: edytuj plik konfiguracyjny
|
||||
<kbd>o</kbd>: otwórz plik konfiguracyjny
|
||||
<kbd>u</kbd>: sprawdź aktualizacje
|
||||
<kbd>s</kbd>: switch to a recent repo
|
||||
</pre>
|
||||
|
||||
## Pliki
|
||||
|
||||
<pre>
|
||||
<kbd>c</kbd>: commituj zmiany
|
||||
<kbd>w</kbd>: commit changes without pre-commit hook
|
||||
<kbd>A</kbd>: zmień ostatnie zatwierdzenie
|
||||
<kbd>C</kbd>: commituj zmiany używając edytora z gita
|
||||
<kbd>space</kbd>: przełącz zatwierdzenie
|
||||
<kbd>d</kbd>: view 'discard changes' options
|
||||
<kbd>e</kbd>: edytuj plik
|
||||
<kbd>o</kbd>: otwórz plik
|
||||
<kbd>i</kbd>: dodaj do .gitignore
|
||||
<kbd>r</kbd>: odśwież pliki
|
||||
<kbd>S</kbd>: przechowaj pliki
|
||||
<kbd>a</kbd>: przełącz wszystkie zatwierdzenia
|
||||
<kbd>t</kbd>: dodaj łatkę
|
||||
<kbd>D</kbd>: view reset options
|
||||
<kbd>enter</kbd>: zatwierdź pojedyncze linie
|
||||
<kbd>f</kbd>: fetch
|
||||
<kbd>X</kbd>: execute custom command
|
||||
</pre>
|
||||
|
||||
## Gałęzie
|
||||
|
||||
<pre>
|
||||
<kbd>space</kbd>: przełącz
|
||||
<kbd>o</kbd>: utwórz żądanie wyciągnięcia
|
||||
<kbd>c</kbd>: przełącz używając nazwy
|
||||
<kbd>F</kbd>: wymuś przełączenie
|
||||
<kbd>n</kbd>: nowa gałąź
|
||||
<kbd>d</kbd>: usuń gałąź
|
||||
<kbd>r</kbd>: rebase branch
|
||||
<kbd>M</kbd>: scal do obecnej gałęzi
|
||||
<kbd>f</kbd>: fast-forward this branch from its upstream
|
||||
</pre>
|
||||
|
||||
## Commity
|
||||
|
||||
<pre>
|
||||
<kbd>s</kbd>: ściśnij w dół
|
||||
<kbd>r</kbd>: przemianuj commit
|
||||
<kbd>R</kbd>: przemianuj commit w edytorze
|
||||
<kbd>g</kbd>: zresetuj do tego commita
|
||||
<kbd>f</kbd>: napraw commit
|
||||
<kbd>F</kbd>: create fixup commit for this commit
|
||||
<kbd>S</kbd>: squash above commits
|
||||
<kbd>d</kbd>: delete commit
|
||||
<kbd>ctrl+j</kbd>: move commit down one
|
||||
<kbd>ctrl+k</kbd>: move commit up one
|
||||
<kbd>e</kbd>: edit commit
|
||||
<kbd>A</kbd>: amend commit with staged changes
|
||||
<kbd>p</kbd>: pick commit (when mid-rebase)
|
||||
<kbd>t</kbd>: revert commit
|
||||
<kbd>c</kbd>: copy commit (cherry-pick)
|
||||
<kbd>C</kbd>: copy commit range (cherry-pick)
|
||||
<kbd>v</kbd>: paste commits (cherry-pick)
|
||||
<kbd>enter</kbd>: view commit's files
|
||||
<kbd>space</kbd>: select commit to diff with another commit
|
||||
</pre>
|
||||
|
||||
## Schowek
|
||||
|
||||
<pre>
|
||||
<kbd>space</kbd>: zastosuj
|
||||
<kbd>g</kbd>: wyciągnij
|
||||
<kbd>d</kbd>: porzuć
|
||||
</pre>
|
||||
|
||||
## Commit files
|
||||
|
||||
<pre>
|
||||
<kbd>esc</kbd>: go back
|
||||
<kbd>c</kbd>: checkout file
|
||||
<kbd>d</kbd>: discard this commit's changes to this file
|
||||
<kbd>o</kbd>: otwórz plik
|
||||
</pre>
|
||||
|
||||
## Main (Normal)
|
||||
|
||||
<pre>
|
||||
<kbd>PgDn</kbd>: scroll down (fn+up)
|
||||
<kbd>PgUp</kbd>: scroll up (fn+down)
|
||||
</pre>
|
||||
|
||||
## Main (Zatwierdzanie)
|
||||
|
||||
<pre>
|
||||
<kbd>esc</kbd>: wróć do panelu plików
|
||||
<kbd>▲</kbd>: select previous line
|
||||
<kbd>▼</kbd>: select next line
|
||||
<kbd>◄</kbd>: select previous hunk
|
||||
<kbd>►</kbd>: select next hunk
|
||||
<kbd>space</kbd>: zatwierdź linię
|
||||
<kbd>a</kbd>: zatwierdź kawałek
|
||||
</pre>
|
||||
|
||||
## Main (Merging)
|
||||
|
||||
<pre>
|
||||
<kbd>esc</kbd>: wróć do panelu plików
|
||||
<kbd>space</kbd>: pick hunk
|
||||
<kbd>b</kbd>: pick both hunks
|
||||
<kbd>◄</kbd>: select previous conflict
|
||||
<kbd>►</kbd>: select next conflict
|
||||
<kbd>▲</kbd>: select top hunk
|
||||
<kbd>▼</kbd>: select bottom hunk
|
||||
<kbd>z</kbd>: undo
|
||||
</pre>
|
||||
BIN
docs/resources/interactive-rebase.png
Normal file
BIN
docs/resources/interactive-rebase.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 178 KiB |
77
go.mod
77
go.mod
@@ -1,62 +1,39 @@
|
||||
module github.com/jesseduffield/lazygit
|
||||
|
||||
go 1.13
|
||||
|
||||
require (
|
||||
github.com/aws/aws-sdk-go v1.15.21
|
||||
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d
|
||||
github.com/aws/aws-sdk-go v1.25.31 // indirect
|
||||
github.com/cloudfoundry/jibber_jabber v0.0.0-20151120183258-bcc4c8345a21
|
||||
github.com/davecgh/go-spew v1.1.0
|
||||
github.com/emirpasic/gods v1.9.0
|
||||
github.com/fatih/color v1.7.0
|
||||
github.com/fsnotify/fsnotify v1.4.7
|
||||
github.com/go-ini/ini v1.38.2
|
||||
github.com/go-errors/errors v1.0.1
|
||||
github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3
|
||||
github.com/hashicorp/go-cleanhttp v0.0.0-20171218145408-d5fe4b57a186
|
||||
github.com/hashicorp/go-getter v0.0.0-20180809191950-4bda8fa99001
|
||||
github.com/hashicorp/go-safetemp v0.0.0-20180326211150-b1a1dbde6fdc
|
||||
github.com/hashicorp/go-version v1.0.0
|
||||
github.com/hashicorp/hcl v0.0.0-20180404174102-ef8a98b0bbce
|
||||
github.com/heroku/rollrus v0.0.0-20180515183152-fc0cef2ff331
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99
|
||||
github.com/google/go-cmp v0.3.1 // indirect
|
||||
github.com/hashicorp/go-cleanhttp v0.5.1 // indirect
|
||||
github.com/hashicorp/go-getter v1.4.0 // indirect
|
||||
github.com/hashicorp/go-version v1.2.0 // indirect
|
||||
github.com/integrii/flaggy v1.3.0
|
||||
github.com/jesseduffield/go-getter v0.0.0-20180822080847-906e15686e63
|
||||
github.com/jesseduffield/gocui v0.0.0-20180905104005-2cb6e95bbbf8
|
||||
github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8
|
||||
github.com/kardianos/osext v0.0.0-20170510131534-ae77be60afb1
|
||||
github.com/kevinburke/ssh_config v0.0.0-20180317175531-9fc7bb800b55
|
||||
github.com/magiconair/properties v1.8.0
|
||||
github.com/mattn/go-colorable v0.0.9
|
||||
github.com/mattn/go-isatty v0.0.3
|
||||
github.com/mattn/go-runewidth v0.0.2
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20191110053728-01cdcccd0508
|
||||
github.com/jesseduffield/pty v1.1.3
|
||||
github.com/jesseduffield/rollrus v0.0.0-20190701125922-dd028cb0bfd7
|
||||
github.com/jesseduffield/termbox-go v0.0.0-20190630083001-9dd53af7214e // indirect
|
||||
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0
|
||||
github.com/mattn/go-colorable v0.1.4 // indirect
|
||||
github.com/mattn/go-isatty v0.0.10 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.6 // indirect
|
||||
github.com/mgutz/str v1.2.0
|
||||
github.com/mitchellh/go-homedir v0.0.0-20180801233206-58046073cbff
|
||||
github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77
|
||||
github.com/mitchellh/mapstructure v0.0.0-20180715050151-f15292f7a699
|
||||
github.com/nicksnyder/go-i18n v0.0.0-20180803040939-a16b91a3ba80
|
||||
github.com/nsf/termbox-go v0.0.0-20180613055208-5c94acc5e6eb
|
||||
github.com/pelletier/go-buffruneio v0.2.0
|
||||
github.com/pelletier/go-toml v1.2.0
|
||||
github.com/pkg/errors v0.8.0
|
||||
github.com/pmezard/go-difflib v1.0.0
|
||||
github.com/sergi/go-diff v1.0.0
|
||||
github.com/nicksnyder/go-i18n/v2 v2.0.2
|
||||
github.com/onsi/ginkgo v1.10.3 // indirect
|
||||
github.com/onsi/gomega v1.7.1 // indirect
|
||||
github.com/shibukawa/configdir v0.0.0-20170330084843-e180dbdc8da0
|
||||
github.com/sirupsen/logrus v1.0.6
|
||||
github.com/spf13/afero v1.1.1
|
||||
github.com/spf13/cast v1.2.0
|
||||
github.com/spf13/jwalterweatherman v0.0.0-20180814060501-14d3d4c51834
|
||||
github.com/spf13/pflag v1.0.2
|
||||
github.com/spf13/viper v1.1.0
|
||||
github.com/sirupsen/logrus v1.4.2
|
||||
github.com/spf13/viper v1.5.0
|
||||
github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad
|
||||
github.com/src-d/gcfg v1.3.0
|
||||
github.com/stretchr/testify v1.2.2
|
||||
github.com/stvp/roll v0.0.0-20170522205222-3627a5cbeaea
|
||||
github.com/stretchr/testify v1.4.0
|
||||
github.com/tcnksm/go-gitconfig v0.1.2
|
||||
github.com/ulikunitz/xz v0.5.4
|
||||
github.com/xanzy/ssh-agent v0.2.0
|
||||
golang.org/x/crypto v0.0.0-20180808211826-de0752318171
|
||||
golang.org/x/net v0.0.0-20180811021610-c39426892332
|
||||
golang.org/x/sys v0.0.0-20180810173357-98c5dad5d1a0
|
||||
golang.org/x/text v0.3.0
|
||||
gopkg.in/src-d/go-billy.v4 v4.2.0
|
||||
gopkg.in/src-d/go-git.v4 v4.0.0-20180807092216-43d17e14b714
|
||||
gopkg.in/warnings.v0 v0.1.2
|
||||
gopkg.in/yaml.v2 v2.2.1
|
||||
github.com/ulikunitz/xz v0.5.6 // indirect
|
||||
golang.org/x/text v0.3.2
|
||||
gopkg.in/src-d/go-git.v4 v4.13.1
|
||||
gopkg.in/yaml.v2 v2.2.5
|
||||
)
|
||||
|
||||
419
go.sum
419
go.sum
@@ -1,163 +1,370 @@
|
||||
github.com/BurntSushi/toml v0.3.0 h1:e1/Ivsx3Z0FVTV0NSOv/aVgbUWyQuzj7DDnFblkRvsY=
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
|
||||
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
|
||||
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
|
||||
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
|
||||
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
|
||||
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
|
||||
github.com/BurntSushi/toml v0.3.0/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||
github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7 h1:uSoVVbwJiQipAclBbw+8quDsfcvFjOpI5iCf4p/cqCs=
|
||||
github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA=
|
||||
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
|
||||
github.com/aws/aws-sdk-go v1.15.21 h1:STLvc6RrpycslC1NRtTvt/YSgDkIGCTrB9K9vE5R2oQ=
|
||||
github.com/aws/aws-sdk-go v1.15.21/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0=
|
||||
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
|
||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
|
||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
|
||||
github.com/aws/aws-sdk-go v1.15.78/go.mod h1:E3/ieXAlvM0XWO57iftYVDLLvQ824smPP3ATZkfNZeM=
|
||||
github.com/aws/aws-sdk-go v1.25.31 h1:14mdh3HsTgRekePPkYcCbAaEXJknc3mN7f4XfsiMMDA=
|
||||
github.com/aws/aws-sdk-go v1.25.31/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d h1:xDfNPAt8lFiC1UJrqV3uuy861HCTo708pDMbjHHdCas=
|
||||
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d/go.mod h1:6QX/PXZ00z/TKoufEY6K/a0k6AhaJrQKdFe6OfVXsa4=
|
||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||
github.com/cheggaaa/pb v1.0.27/go.mod h1:pQciLPpbU0oxA0h+VJYYLxO+XeDQb5pZijXscXHm81s=
|
||||
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=
|
||||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
|
||||
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
|
||||
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/emirpasic/gods v1.9.0 h1:rUF4PuzEjMChMiNsVjdI+SyLu7rEqpQ5reNFnhC7oFo=
|
||||
github.com/emirpasic/gods v1.9.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
|
||||
github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg=
|
||||
github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
|
||||
github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
|
||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BHsljHzVlRcyQhjrss6TZTdY2VfCqZPbv5k3iBFa2ZQ=
|
||||
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
|
||||
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/gliderlabs/ssh v0.1.1 h1:j3L6gSLQalDETeEg/Jg0mGY0/y/N6zI2xX1978P0Uqw=
|
||||
github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
|
||||
github.com/go-ini/ini v1.25.4/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
|
||||
github.com/go-ini/ini v1.38.2 h1:6Hl/z3p3iFkA0dlDfzYxuFuUGD+kaweypF6btsR2/Q4=
|
||||
github.com/go-ini/ini v1.38.2/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
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.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w=
|
||||
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
|
||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
|
||||
github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3 h1:zN2lZNZRflqFyxVaTIU61KNKQ9C0055u9CAfpmqUvo4=
|
||||
github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3/go.mod h1:nPpo7qLxd6XL3hWJG/O60sR8ZKfMCiIoNap5GvD12KU=
|
||||
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ=
|
||||
github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20180825215210-0210a2f0f73c h1:16eHWuMGvCjSfgRJKqIzapE78onvvTbdi1rMkU00lZw=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20180825215210-0210a2f0f73c/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/hashicorp/go-cleanhttp v0.0.0-20171218145408-d5fe4b57a186 h1:URgjUo+bs1KwatoNbwG0uCO4dHN4r1jsp4a5AGgHRjo=
|
||||
github.com/hashicorp/go-cleanhttp v0.0.0-20171218145408-d5fe4b57a186/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
||||
github.com/hashicorp/go-getter v0.0.0-20180809191950-4bda8fa99001 h1:MFPzqpPED05pFyGjNPJEC2sXM6EHTzFyvX+0s0JoZ48=
|
||||
github.com/hashicorp/go-getter v0.0.0-20180809191950-4bda8fa99001/go.mod h1:6rdJFnhkXnzGOJbvkrdv4t9nLwKcVA+tmbQeUlkIzrU=
|
||||
github.com/hashicorp/go-safetemp v0.0.0-20180326211150-b1a1dbde6fdc h1:wAa9fGALVHfjYxZuXRnmuJG2CnwRpJYOTvY6YdErAh0=
|
||||
github.com/hashicorp/go-safetemp v0.0.0-20180326211150-b1a1dbde6fdc/go.mod h1:oaerMy3BhqiTbVye6QuFhFtIceqFoDHxNAB65b+Rj1I=
|
||||
github.com/hashicorp/go-version v1.0.0 h1:21MVWPKDphxa7ineQQTrCU5brh7OuVVAzGOCnnCPtE8=
|
||||
github.com/hashicorp/go-version v1.0.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||
github.com/hashicorp/hcl v0.0.0-20180404174102-ef8a98b0bbce h1:xdsDDbiBDQTKASoGEZ+pEmF1OnWuu8AQ9I8iNbHNeno=
|
||||
github.com/hashicorp/hcl v0.0.0-20180404174102-ef8a98b0bbce/go.mod h1:oZtUIOe8dh44I2q6ScRibXws4Ajl+d+nod3AaR9vL5w=
|
||||
github.com/heroku/rollrus v0.0.0-20180515183152-fc0cef2ff331 h1:qio0y/sQdhbHRA3cmgczo04MaSV2zw+n46G1owvgWIk=
|
||||
github.com/heroku/rollrus v0.0.0-20180515183152-fc0cef2ff331/go.mod h1:BT+PgT529opmb6mcUY+Fg0IwVRRmwqFyavEMU17GnBg=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
|
||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVoDkXMzJM=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
||||
github.com/hashicorp/go-getter v1.4.0 h1:ENHNi8494porjD0ZhIrjlAHnveSFhY7hvOJrV/fsKkw=
|
||||
github.com/hashicorp/go-getter v1.4.0/go.mod h1:7qxyCd8rBfcShwsvxgIguu4KbS3l8bUCwg2Umn7RjeY=
|
||||
github.com/hashicorp/go-safetemp v1.0.0 h1:2HR189eFNrjHQyENnQMMpCiBAsRxzbTMIgBhEyExpmo=
|
||||
github.com/hashicorp/go-safetemp v1.0.0/go.mod h1:oaerMy3BhqiTbVye6QuFhFtIceqFoDHxNAB65b+Rj1I=
|
||||
github.com/hashicorp/go-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||
github.com/hashicorp/go-version v1.2.0 h1:3vNe/fWF5CBgRIguda1meWhsZHy3m8gCJ5wx+dIzX/E=
|
||||
github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/integrii/flaggy v1.3.0 h1:8I5Qqz22C6+EwUqJuaN5ITh77obI8VSg6RwYLe2VB7o=
|
||||
github.com/integrii/flaggy v1.3.0/go.mod h1:tnTxHeTJbah0gQ6/K0RW0J7fMUBk9MCF5blhm43LNpI=
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
|
||||
github.com/jesseduffield/go-getter v0.0.0-20180822080847-906e15686e63 h1:Nrr/yUxNjXWYK0B3IqcFlYh1ICnesJDB4ogcfOVc5Ns=
|
||||
github.com/jesseduffield/go-getter v0.0.0-20180822080847-906e15686e63 h1:tbm85YuPi3d1LFAUr6yZyzZ4vR96ygV98rezZZ+ywbg=
|
||||
github.com/jesseduffield/go-getter v0.0.0-20180822080847-906e15686e63/go.mod h1:fNqjRf+4XnTo2PrGN1JRb79b/BeoHwP4lU00f39SQY0=
|
||||
github.com/jesseduffield/gocui v0.0.0-20180905104005-2cb6e95bbbf8 h1:YMYnpIu5HUJfx/yfIwnZhFrgWTg51FQWtvZi+PMzQm8=
|
||||
github.com/jesseduffield/gocui v0.0.0-20180905104005-2cb6e95bbbf8/go.mod h1:2RtZznzYKt8RLRwvFiSkXjU0Ei8WwHdubgnlaYH47dw=
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20191110053728-01cdcccd0508 h1:8CPQLUe+0QXxnbnUfaMxh1UGxg3rYCqCvbxecC9rrIY=
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20191110053728-01cdcccd0508/go.mod h1:2RtZznzYKt8RLRwvFiSkXjU0Ei8WwHdubgnlaYH47dw=
|
||||
github.com/jesseduffield/pty v1.1.3 h1:JTPQ/bbGH3IBwQhxKZiFxfD4wv7cYrY7LNTwbucsZ64=
|
||||
github.com/jesseduffield/pty v1.1.3/go.mod h1:7jlS40+UhOqkZJDIG1B/H21xnuET/+fvbbnHCa8wSIo=
|
||||
github.com/jesseduffield/roll v0.0.0-20190629104057-695be2e62b00 h1:+JaOkfBNYQYlGD7dgru8mCwYNEc5tRRI8mThlVANhSM=
|
||||
github.com/jesseduffield/roll v0.0.0-20190629104057-695be2e62b00/go.mod h1:cWNQljQAWYBp4wchyGfql4q2jRNZXxiE1KhVQgz+JaM=
|
||||
github.com/jesseduffield/rollrus v0.0.0-20190701125922-dd028cb0bfd7 h1:CRD7bVjlGIiV+M0jlsa+XWpneW0KY0e7Y4z3GWb5S4o=
|
||||
github.com/jesseduffield/rollrus v0.0.0-20190701125922-dd028cb0bfd7/go.mod h1:VspA3aTkEo0Q7TPCLmX1uHNP+Wb4iSDX09hmTRo1QYc=
|
||||
github.com/jesseduffield/termbox-go v0.0.0-20190630083001-9dd53af7214e h1:tth7wr6+sfSbdpRWWrwvLYyS56HyIRVfq0Qcl2h28wM=
|
||||
github.com/jesseduffield/termbox-go v0.0.0-20190630083001-9dd53af7214e/go.mod h1:anMibpZtqNxjDbxrcDEAwSdaJ37vyUeM1f/M4uekib4=
|
||||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8 h1:12VvqtR6Aowv3l/EQUlocDHW2Cp4G9WJVH7uyH8QFJE=
|
||||
github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
|
||||
github.com/jtolds/gls v4.2.1+incompatible h1:fSuqC+Gmlu6l/ZYAoZzx2pyucC8Xza35fpRVWLVmUEE=
|
||||
github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
github.com/kardianos/osext v0.0.0-20170510131534-ae77be60afb1 h1:PJPDf8OUfOK1bb/NeTKd4f1QXZItOX389VN3B6qC8ro=
|
||||
github.com/kardianos/osext v0.0.0-20170510131534-ae77be60afb1/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8=
|
||||
github.com/kevinburke/ssh_config v0.0.0-20180317175531-9fc7bb800b55 h1:S38dC4mEwxdw/U41+97VWdbun8mTcTjwg5Ujfg8QPME=
|
||||
github.com/kevinburke/ssh_config v0.0.0-20180317175531-9fc7bb800b55/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
|
||||
github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY=
|
||||
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM=
|
||||
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
|
||||
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 h1:iQTw/8FWTuc7uiaSepXwyf3o52HaUYcV+Tu66S3F5GA=
|
||||
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8=
|
||||
github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd h1:Coekwdh0v2wtGp9Gmz1Ze3eVRAWJMLokvN3QjdzCHLY=
|
||||
github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
|
||||
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4=
|
||||
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4=
|
||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||
github.com/mattn/go-isatty v0.0.3 h1:ns/ykhmWi7G9O+8a448SecJU3nSMBXJfqQkl0upE1jI=
|
||||
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/mattn/go-runewidth v0.0.2 h1:UnlwIPBGaTZfPQ6T1IGzPI0EkYAQmT9fAEJ/poFC63o=
|
||||
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||
github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA=
|
||||
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.10 h1:qxFzApOv4WsAL965uUPIsXzAKCZxN2p9UqdhFS4ZW10=
|
||||
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
|
||||
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||
github.com/mattn/go-runewidth v0.0.6 h1:V2iyH+aX9C5fsYCpK60U8BYIvmhqxuOL3JZcqc1NB7k=
|
||||
github.com/mattn/go-runewidth v0.0.6/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
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 v0.0.0-20180801233206-58046073cbff h1:jM4Eo4qMmmcqePS3u6X2lcEELtVuXWkWJIS/pRI3oSk=
|
||||
github.com/mitchellh/go-homedir v0.0.0-20180801233206-58046073cbff/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77 h1:7GoSOOW2jpsfkntVKaS2rAr1TJqfcxotyaUcuxoZSzg=
|
||||
github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
|
||||
github.com/mitchellh/mapstructure v0.0.0-20180715050151-f15292f7a699 h1:KXZJFdun9knAVAR8tg/aHJEr5DgtcbqyvzacK+CDCaI=
|
||||
github.com/mitchellh/mapstructure v0.0.0-20180715050151-f15292f7a699/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/nicksnyder/go-i18n v0.0.0-20180803040939-a16b91a3ba80/go.mod h1:e4Di5xjP9oTVrC6y3C7C0HoSYXjSbhh/dU0eUV32nB4=
|
||||
github.com/nicksnyder/go-i18n/v2 v2.0.0-beta.5 h1:/TjjTS4kg7vC+05gD0LE4+97f/+PRFICnK/7wJPk7kE=
|
||||
github.com/nicksnyder/go-i18n/v2 v2.0.0-beta.5/go.mod h1:4Opqa6/HIv0lhG3WRAkqzO0afezkRhxXI0P8EJkqeRU=
|
||||
github.com/nsf/termbox-go v0.0.0-20180613055208-5c94acc5e6eb h1:YahEjAGkJtCrkqgVHhX6n8ZX+CZ3hDRL9fjLYugLfSs=
|
||||
github.com/nsf/termbox-go v0.0.0-20180613055208-5c94acc5e6eb/go.mod h1:IuKpRQcYE1Tfu+oAQqaLisqDeXgjyyltCfsaoYN18NQ=
|
||||
github.com/onsi/ginkgo v1.6.0 h1:Ix8l273rp3QzYgXSR+c8d1fTG7UPgYkOSELPhiY/YGw=
|
||||
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
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-testing-interface v1.0.0 h1:fzU/JVNcaqHQEcVFAKeR41fkiLdIPrefOvVG1VZ96U0=
|
||||
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
|
||||
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
|
||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/nicksnyder/go-i18n/v2 v2.0.2 h1:KsHGcTByIM0mHZKQGy0nlJLOjPNjQ6MVib/3PvsBDNY=
|
||||
github.com/nicksnyder/go-i18n/v2 v2.0.2/go.mod h1:JXS4+OKhbcwDoVTEj0sLFWL1vOwec2g/YBAxZ9owJqY=
|
||||
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/gomega v1.4.1 h1:PZSj/UFNaVp3KxrzHOcS7oyuWA7LoOY/77yCTEFu21U=
|
||||
github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
|
||||
github.com/pelletier/go-buffruneio v0.2.0 h1:U4t4R6YkofJ5xHm3dJzuRpPZ0mr5MMCoAWooScCR7aA=
|
||||
github.com/onsi/ginkgo v1.10.3 h1:OoxbjfXVZyod1fmWYhI7SEyaD8B00ynP3T+D5GiyHOY=
|
||||
github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/gomega v1.7.1 h1:K0jcRCwNQM3vFGh1ppMtDh/+7ApJrjldlX8fA0jDTLQ=
|
||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||
github.com/pelletier/go-buffruneio v0.2.0/go.mod h1:JkE26KsDizTr40EUHkXVtNPvgGtbSNq5BcowyYOWdKo=
|
||||
github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
|
||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||
github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
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/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_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
||||
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
|
||||
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
||||
github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
|
||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||
github.com/shibukawa/configdir v0.0.0-20170330084843-e180dbdc8da0 h1:Xuk8ma/ibJ1fOy4Ee11vHhUFHQNpHhrBneOCNHVXS5w=
|
||||
github.com/shibukawa/configdir v0.0.0-20170330084843-e180dbdc8da0/go.mod h1:7AwjWCpdPhkSmNAgUv5C7EJ4AbmjEB3r047r3DXWu3Y=
|
||||
github.com/sirupsen/logrus v1.0.6 h1:hcP1GmhGigz/O7h1WVUM5KklBp1JoNS9FggWKdj/j3s=
|
||||
github.com/sirupsen/logrus v1.0.6/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc=
|
||||
github.com/smartystreets/assertions v0.0.0-20180820201707-7c9eb446e3cf h1:6V1qxN6Usn4jy8unvggSJz/NC790tefw8Zdy6OZS5co=
|
||||
github.com/smartystreets/assertions v0.0.0-20180820201707-7c9eb446e3cf/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
github.com/smartystreets/goconvey v0.0.0-20180222194500-ef6db91d284a h1:JSvGDIbmil4Ui/dDdFBExb7/cmkNjyX5F97oglmvCDo=
|
||||
github.com/smartystreets/goconvey v0.0.0-20180222194500-ef6db91d284a/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s=
|
||||
github.com/spf13/afero v1.1.1 h1:Lt3ihYMlE+lreX1GS4Qw4ZsNpYQLxIXKBTEOXm3nt6I=
|
||||
github.com/spf13/afero v1.1.1/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
||||
github.com/spf13/cast v1.2.0 h1:HHl1DSRbEQN2i8tJmtS6ViPyHx35+p51amrdsiTCrkg=
|
||||
github.com/spf13/cast v1.2.0/go.mod h1:r2rcYCSwa1IExKTDiTfzaxqT2FNHs8hODu4LnUfgKEg=
|
||||
github.com/spf13/jwalterweatherman v0.0.0-20180814060501-14d3d4c51834 h1:kJI9pPzfsULT/72wy7mxkRQZPtKWgFdCA2RTGZ4v8/E=
|
||||
github.com/spf13/jwalterweatherman v0.0.0-20180814060501-14d3d4c51834/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
|
||||
github.com/spf13/pflag v1.0.2 h1:Fy0orTDgHdbnzHcsOgfCN4LtHf0ec3wwtiwJqwvf3Gc=
|
||||
github.com/spf13/pflag v1.0.2/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/spf13/viper v1.1.0 h1:V7OZpY8i3C1x/pDmU0zNNlfVoDz112fSYvtWMjjS3f4=
|
||||
github.com/spf13/viper v1.1.0/go.mod h1:A8kyI5cUJhb8N+3pkfONlcEcZbueH6nhAm0Fq7SrnBM=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sirupsen/logrus v1.3.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
|
||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI=
|
||||
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
||||
github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8=
|
||||
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk=
|
||||
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
|
||||
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
|
||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/spf13/viper v1.5.0 h1:GpsTwfsQ27oS/Aha/6d1oD7tpKIqWnOA6tgOX9HHkt4=
|
||||
github.com/spf13/viper v1.5.0/go.mod h1:AkYRkVJF8TkSG/xet6PzXX+l39KhhXa2pdqVSxnTcn4=
|
||||
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/src-d/gcfg v1.3.0 h1:2BEDr8r0I0b8h/fOqwtxCEiq2HJu8n2JGZJQFGXWLjg=
|
||||
github.com/src-d/gcfg v1.3.0/go.mod h1:p/UMsR43ujA89BJY9duynAwIpvqEujIH/jFlfL7jWoI=
|
||||
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
|
||||
github.com/src-d/gcfg v1.4.0 h1:xXbNR5AlLSA315x2UO+fTSSAXCDf+Ar38/6oyGbDKQ4=
|
||||
github.com/src-d/gcfg v1.4.0/go.mod h1:p/UMsR43ujA89BJY9duynAwIpvqEujIH/jFlfL7jWoI=
|
||||
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.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stvp/roll v0.0.0-20170522205222-3627a5cbeaea h1:jysxIKov/4GJ33wI2aRvuIK7yBwB28E5almlgDLPRpM=
|
||||
github.com/stvp/roll v0.0.0-20170522205222-3627a5cbeaea/go.mod h1:Ffmqrj3nXIMIjeA4uW3Qjj0Ud9eDoTG0fu4JxyAr/tE=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
|
||||
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
||||
github.com/tcnksm/go-gitconfig v0.1.2 h1:iiDhRitByXAEyjgBqsKi9QU4o2TNtv9kPP3RgPgXBPw=
|
||||
github.com/tcnksm/go-gitconfig v0.1.2/go.mod h1:/8EhP4H7oJZdIPyT+/UIsG87kTzrzM4UsLGSItWYCpE=
|
||||
github.com/ulikunitz/xz v0.5.4 h1:zATC2OoZ8H1TZll3FpbX+ikwmadbO699PE06cIkm9oU=
|
||||
github.com/ulikunitz/xz v0.5.4/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8=
|
||||
github.com/xanzy/ssh-agent v0.2.0 h1:Adglfbi5p9Z0BmK2oKU9nTG+zKfniSfnaMYB+ULd+Ro=
|
||||
github.com/xanzy/ssh-agent v0.2.0/go.mod h1:0NyE30eGUDliuLEHJgYte/zncp2zdTStcOnWhgSqHD8=
|
||||
golang.org/x/crypto v0.0.0-20180808211826-de0752318171 h1:vYogbvSFj2YXcjQxFHu/rASSOt9sLytpCaSkiwQ135I=
|
||||
golang.org/x/crypto v0.0.0-20180808211826-de0752318171/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/net v0.0.0-20180811021610-c39426892332 h1:efGso+ep0DjyCBJPjvoz0HI6UldX4Md2F1rZFe1ir0E=
|
||||
golang.org/x/net v0.0.0-20180811021610-c39426892332/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
|
||||
github.com/ulikunitz/xz v0.5.5/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8=
|
||||
github.com/ulikunitz/xz v0.5.6 h1:jGHAfXawEGZQ3blwU5wnWKQJvAraT7Ftq9EXjnXYgt8=
|
||||
github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8=
|
||||
github.com/xanzy/ssh-agent v0.2.1 h1:TCbipTQL2JiiCprBWx9frJ2eJlCYT00NmctrHxVAr70=
|
||||
github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4=
|
||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
||||
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
|
||||
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190506204251-e1dfcc566284/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc=
|
||||
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80 h1:Ao/3l156eZf2AW5wK8a7/smtodRU+gha3+BeqJ69lRk=
|
||||
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180810173357-98c5dad5d1a0 h1:8H8QZJ30plJyIVj60H3lr8TZGIq2Fh3Cyrs/ZNg1foU=
|
||||
golang.org/x/sys v0.0.0-20180810173357-98c5dad5d1a0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/text v0.0.0-20171214130843-f21a4dfb5e38/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190221075227-b4e8571b14e0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e h1:D5TXcfTk7xF7hvieo4QErS3qqCB4teTffacDWr7CI+0=
|
||||
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191008105621-543471e840be h1:QAcqgptGM8IQBC9K/RC4o+O9YmqEm0diQn9QmZw/0mU=
|
||||
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
gopkg.in/airbrake/gobrake.v2 v2.0.9 h1:7z2uVWwn7oVeeugY1DtlPAy5H+KYgB1KeKTnqjNatLo=
|
||||
gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
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/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
|
||||
golang.org/x/tools v0.0.0-20190729092621-ff9f1409240a/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI=
|
||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
||||
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||
google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/cheggaaa/pb.v1 v1.0.27/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
|
||||
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2 h1:OAj3g0cR6Dx/R07QgQe8wkA9RNjB2u4i700xBkIT4e0=
|
||||
gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo=
|
||||
gopkg.in/ini.v1 v1.38.2 h1:dGcbywv4RufeGeiMycPT/plKB5FtmLKLnWKwBiLhUA4=
|
||||
gopkg.in/ini.v1 v1.38.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/src-d/go-billy.v4 v4.2.0 h1:VGbrP1EsYxtvVPEiHui+4//imr4E5MGEFLx66bQtusg=
|
||||
gopkg.in/src-d/go-billy.v4 v4.2.0/go.mod h1:ZHSF0JP+7oD97194otDUCD7Ofbk63+xFcfWP5bT6h+Q=
|
||||
gopkg.in/src-d/go-git-fixtures.v3 v3.1.1 h1:XWW/s5W18RaJpmo1l0IYGqXKuJITWRFuA45iOf1dKJs=
|
||||
gopkg.in/src-d/go-git-fixtures.v3 v3.1.1/go.mod h1:dLBcvytrw/TYZsNTWCnkNF2DSIlzWYqTe3rJR56Ac7g=
|
||||
gopkg.in/src-d/go-git.v4 v4.0.0-20180807092216-43d17e14b714 h1:+wM2BGgQ1znCKBexOB4OrGVSDw8mtKNUSq3wqxZhi/k=
|
||||
gopkg.in/src-d/go-git.v4 v4.0.0-20180807092216-43d17e14b714/go.mod h1:CzbUWqMn4pvmvndg3gnh5iZFmSsbhyhUWdI0IQ60AQo=
|
||||
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
|
||||
gopkg.in/src-d/go-billy.v4 v4.3.2 h1:0SQA1pRztfTFx2miS8sA97XvooFeNOmvUenF4o0EcVg=
|
||||
gopkg.in/src-d/go-billy.v4 v4.3.2/go.mod h1:nDjArDMp+XMs1aFAESLRjfGSgfvoYN0hDfzEk0GjC98=
|
||||
gopkg.in/src-d/go-git-fixtures.v3 v3.5.0 h1:ivZFOIltbce2Mo8IjzUHAFoq/IylO9WHhNOAJK+LsJg=
|
||||
gopkg.in/src-d/go-git-fixtures.v3 v3.5.0/go.mod h1:dLBcvytrw/TYZsNTWCnkNF2DSIlzWYqTe3rJR56Ac7g=
|
||||
gopkg.in/src-d/go-git.v4 v4.13.1 h1:SRtFyV8Kxc0UP7aCHcijOMQGPxHSmMOPrzulQWolkYE=
|
||||
gopkg.in/src-d/go-git.v4 v4.13.1/go.mod h1:nx5NYcxdKxq5fpltdHnPa2Exj4Sx0EclMWZQbYDu2z8=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
|
||||
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
|
||||
gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
|
||||
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.5 h1:ymVxjfMaHvXD8RqPRmzHHsB3VvucivSkIAvJFDI5O3c=
|
||||
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||
|
||||
61
main.go
61
main.go
@@ -1,12 +1,14 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
|
||||
"github.com/go-errors/errors"
|
||||
"github.com/integrii/flaggy"
|
||||
"github.com/jesseduffield/lazygit/pkg/app"
|
||||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
)
|
||||
@@ -16,10 +18,6 @@ var (
|
||||
version = "unversioned"
|
||||
date string
|
||||
buildSource = "unknown"
|
||||
|
||||
configFlag = flag.Bool("config", false, "Print the current default config")
|
||||
debuggingFlag = flag.Bool("debug", false, "a boolean")
|
||||
versionFlag = flag.Bool("v", false, "Print the current version")
|
||||
)
|
||||
|
||||
func projectPath(path string) string {
|
||||
@@ -28,26 +26,61 @@ func projectPath(path string) string {
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
if *versionFlag {
|
||||
flaggy.DefaultParser.ShowVersionWithVersionFlag = false
|
||||
|
||||
repoPath := "."
|
||||
flaggy.String(&repoPath, "p", "path", "Path of git repo")
|
||||
|
||||
dump := ""
|
||||
flaggy.AddPositionalValue(&dump, "gitargs", 1, false, "Todo file")
|
||||
flaggy.DefaultParser.PositionalFlags[0].Hidden = true
|
||||
|
||||
versionFlag := false
|
||||
flaggy.Bool(&versionFlag, "v", "version", "Print the current version")
|
||||
|
||||
debuggingFlag := false
|
||||
flaggy.Bool(&debuggingFlag, "d", "debug", "Run in debug mode with logging")
|
||||
|
||||
configFlag := false
|
||||
flaggy.Bool(&configFlag, "c", "config", "Print the current default config")
|
||||
|
||||
flaggy.Parse()
|
||||
|
||||
if versionFlag {
|
||||
fmt.Printf("commit=%s, build date=%s, build source=%s, version=%s, os=%s, arch=%s\n", commit, date, buildSource, version, runtime.GOOS, runtime.GOARCH)
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
if *configFlag {
|
||||
if configFlag {
|
||||
fmt.Printf("%s\n", config.GetDefaultConfig())
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
if repoPath != "." {
|
||||
if err := os.Chdir(repoPath); err != nil {
|
||||
log.Fatal(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
appConfig, err := config.NewAppConfig("lazygit", version, commit, date, buildSource, debuggingFlag)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
log.Fatal(err.Error())
|
||||
}
|
||||
|
||||
app, err := app.NewApp(appConfig)
|
||||
|
||||
if err == nil {
|
||||
err = app.Run()
|
||||
}
|
||||
|
||||
app, err := app.Setup(appConfig)
|
||||
if err != nil {
|
||||
app.Log.Error(err.Error())
|
||||
panic(err)
|
||||
}
|
||||
if errorMessage, known := app.KnownError(err); known {
|
||||
log.Fatal(errorMessage)
|
||||
}
|
||||
newErr := errors.Wrap(err, 0)
|
||||
stackTrace := newErr.ErrorStack()
|
||||
app.Log.Error(stackTrace)
|
||||
|
||||
app.Gui.RunWithSubprocesses()
|
||||
log.Fatal(fmt.Sprintf("%s\n\n%s", app.Tr.SLocalize("ErrorOccurred"), stackTrace))
|
||||
}
|
||||
}
|
||||
|
||||
151
pkg/app/app.go
151
pkg/app/app.go
@@ -1,16 +1,21 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/heroku/rollrus"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands"
|
||||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui"
|
||||
"github.com/jesseduffield/lazygit/pkg/i18n"
|
||||
"github.com/jesseduffield/lazygit/pkg/updates"
|
||||
"github.com/jesseduffield/rollrus"
|
||||
"github.com/shibukawa/configdir"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
@@ -18,24 +23,47 @@ import (
|
||||
type App struct {
|
||||
closers []io.Closer
|
||||
|
||||
Config config.AppConfigurer
|
||||
Log *logrus.Entry
|
||||
OSCommand *commands.OSCommand
|
||||
GitCommand *commands.GitCommand
|
||||
Gui *gui.Gui
|
||||
Tr *i18n.Localizer
|
||||
Updater *updates.Updater // may only need this on the Gui
|
||||
Config config.AppConfigurer
|
||||
Log *logrus.Entry
|
||||
OSCommand *commands.OSCommand
|
||||
GitCommand *commands.GitCommand
|
||||
Gui *gui.Gui
|
||||
Tr *i18n.Localizer
|
||||
Updater *updates.Updater // may only need this on the Gui
|
||||
ClientContext string
|
||||
}
|
||||
|
||||
type errorMapping struct {
|
||||
originalError string
|
||||
newError string
|
||||
}
|
||||
|
||||
func newProductionLogger(config config.AppConfigurer) *logrus.Logger {
|
||||
log := logrus.New()
|
||||
log.Out = ioutil.Discard
|
||||
log.SetLevel(logrus.ErrorLevel)
|
||||
return log
|
||||
}
|
||||
|
||||
func newDevelopmentLogger() *logrus.Logger {
|
||||
func globalConfigDir() string {
|
||||
configDirs := configdir.New("jesseduffield", "lazygit")
|
||||
configDir := configDirs.QueryFolders(configdir.Global)[0]
|
||||
return configDir.Path
|
||||
}
|
||||
|
||||
func getLogLevel() logrus.Level {
|
||||
strLevel := os.Getenv("LOG_LEVEL")
|
||||
level, err := logrus.ParseLevel(strLevel)
|
||||
if err != nil {
|
||||
return logrus.DebugLevel
|
||||
}
|
||||
return level
|
||||
}
|
||||
|
||||
func newDevelopmentLogger(config config.AppConfigurer) *logrus.Logger {
|
||||
log := logrus.New()
|
||||
file, err := os.OpenFile("development.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
|
||||
log.SetLevel(getLogLevel())
|
||||
file, err := os.OpenFile(filepath.Join(globalConfigDir(), "development.log"), os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
|
||||
if err != nil {
|
||||
panic("unable to log to file") // TODO: don't panic (also, remove this call to the `panic` function)
|
||||
}
|
||||
@@ -46,12 +74,17 @@ func newDevelopmentLogger() *logrus.Logger {
|
||||
func newLogger(config config.AppConfigurer) *logrus.Entry {
|
||||
var log *logrus.Logger
|
||||
environment := "production"
|
||||
if config.GetDebug() {
|
||||
if config.GetDebug() || os.Getenv("DEBUG") == "TRUE" {
|
||||
environment = "development"
|
||||
log = newDevelopmentLogger()
|
||||
log = newDevelopmentLogger(config)
|
||||
} else {
|
||||
log = newProductionLogger(config)
|
||||
}
|
||||
|
||||
// highly recommended: tail -f development.log | humanlog
|
||||
// https://github.com/aybabtme/humanlog
|
||||
log.Formatter = &logrus.JSONFormatter{}
|
||||
|
||||
if config.GetUserConfig().GetString("reporting") == "on" {
|
||||
// this isn't really a secret token: it only has permission to push new rollbar items
|
||||
hook := rollrus.NewHook("23432119147a4367abf7c0de2aa99a2d", environment)
|
||||
@@ -65,23 +98,34 @@ func newLogger(config config.AppConfigurer) *logrus.Entry {
|
||||
})
|
||||
}
|
||||
|
||||
// Setup bootstrap a new application
|
||||
func Setup(config config.AppConfigurer) (*App, error) {
|
||||
// NewApp bootstrap a new application
|
||||
func NewApp(config config.AppConfigurer) (*App, error) {
|
||||
app := &App{
|
||||
closers: []io.Closer{},
|
||||
Config: config,
|
||||
}
|
||||
var err error
|
||||
app.Log = newLogger(config)
|
||||
app.OSCommand = commands.NewOSCommand(app.Log, config)
|
||||
|
||||
app.Tr = i18n.NewLocalizer(app.Log)
|
||||
|
||||
app.GitCommand, err = commands.NewGitCommand(app.Log, app.OSCommand, app.Tr)
|
||||
// if we are being called in 'demon' mode, we can just return here
|
||||
app.ClientContext = os.Getenv("LAZYGIT_CLIENT_COMMAND")
|
||||
if app.ClientContext != "" {
|
||||
return app, nil
|
||||
}
|
||||
|
||||
app.OSCommand = commands.NewOSCommand(app.Log, config)
|
||||
|
||||
app.Updater, err = updates.NewUpdater(app.Log, config, app.OSCommand, app.Tr)
|
||||
if err != nil {
|
||||
return app, err
|
||||
}
|
||||
app.Updater, err = updates.NewUpdater(app.Log, config, app.OSCommand, app.Tr)
|
||||
|
||||
if err := app.setupRepo(); err != nil {
|
||||
return app, err
|
||||
}
|
||||
|
||||
app.GitCommand, err = commands.NewGitCommand(app.Log, app.OSCommand, app.Tr, app.Config)
|
||||
if err != nil {
|
||||
return app, err
|
||||
}
|
||||
@@ -92,6 +136,58 @@ func Setup(config config.AppConfigurer) (*App, error) {
|
||||
return app, nil
|
||||
}
|
||||
|
||||
func (app *App) setupRepo() error {
|
||||
// if we are not in a git repo, we ask if we want to `git init`
|
||||
if err := app.OSCommand.RunCommand("git status"); err != nil {
|
||||
if !strings.Contains(err.Error(), "Not a git repository") {
|
||||
return err
|
||||
}
|
||||
fmt.Print(app.Tr.SLocalize("CreateRepo"))
|
||||
response, _ := bufio.NewReader(os.Stdin).ReadString('\n')
|
||||
if strings.Trim(response, " \n") != "y" {
|
||||
os.Exit(1)
|
||||
}
|
||||
if err := app.OSCommand.RunCommand("git init"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (app *App) Run() error {
|
||||
if app.ClientContext == "INTERACTIVE_REBASE" {
|
||||
return app.Rebase()
|
||||
}
|
||||
|
||||
if app.ClientContext == "EXIT_IMMEDIATELY" {
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
err := app.Gui.RunWithSubprocesses()
|
||||
return err
|
||||
}
|
||||
|
||||
// Rebase contains logic for when we've been run in demon mode, meaning we've
|
||||
// given lazygit as a command for git to call e.g. to edit a file
|
||||
func (app *App) Rebase() error {
|
||||
app.Log.Info("Lazygit invoked as interactive rebase demon")
|
||||
app.Log.Info("args: ", os.Args)
|
||||
|
||||
if strings.HasSuffix(os.Args[1], "git-rebase-todo") {
|
||||
if err := ioutil.WriteFile(os.Args[1], []byte(os.Getenv("LAZYGIT_REBASE_TODO")), 0644); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
} else if strings.HasSuffix(os.Args[1], ".git/COMMIT_EDITMSG") {
|
||||
// if we are rebasing and squashing, we'll see a COMMIT_EDITMSG
|
||||
// but in this case we don't need to edit it, so we'll just return
|
||||
} else {
|
||||
app.Log.Info("Lazygit demon did not match on any use cases")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close closes any resources
|
||||
func (app *App) Close() error {
|
||||
for _, closer := range app.closers {
|
||||
@@ -102,3 +198,22 @@ func (app *App) Close() error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// KnownError takes an error and tells us whether it's an error that we know about where we can print a nicely formatted version of it rather than panicking with a stack trace
|
||||
func (app *App) KnownError(err error) (string, bool) {
|
||||
errorMessage := err.Error()
|
||||
|
||||
mappings := []errorMapping{
|
||||
{
|
||||
originalError: "fatal: not a git repository (or any of the parent directories): .git",
|
||||
newError: app.Tr.SLocalize("notARepository"),
|
||||
},
|
||||
}
|
||||
|
||||
for _, mapping := range mappings {
|
||||
if strings.Contains(errorMessage, mapping.originalError) {
|
||||
return mapping.newError, true
|
||||
}
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/theme"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
)
|
||||
@@ -10,13 +13,21 @@ import (
|
||||
// Branch : A git branch
|
||||
// duplicating this for now
|
||||
type Branch struct {
|
||||
Name string
|
||||
Recency string
|
||||
Name string
|
||||
Recency string
|
||||
Pushables string
|
||||
Pullables string
|
||||
Selected bool
|
||||
}
|
||||
|
||||
// GetDisplayString returns the dispaly string of branch
|
||||
func (b *Branch) GetDisplayString() string {
|
||||
return utils.WithPadding(b.Recency, 4) + utils.ColoredString(b.Name, b.GetColor())
|
||||
// GetDisplayStrings returns the display string of branch
|
||||
func (b *Branch) GetDisplayStrings(isFocused bool) []string {
|
||||
displayName := utils.ColoredString(b.Name, b.GetColor())
|
||||
if isFocused && b.Selected && b.Pushables != "" && b.Pullables != "" {
|
||||
displayName = fmt.Sprintf("%s ↑%s↓%s", displayName, b.Pushables, b.Pullables)
|
||||
}
|
||||
|
||||
return []string{b.Recency, displayName}
|
||||
}
|
||||
|
||||
// GetColor branch color
|
||||
@@ -29,7 +40,7 @@ func (b *Branch) GetColor() color.Attribute {
|
||||
case "hotfix":
|
||||
return color.FgRed
|
||||
default:
|
||||
return color.FgWhite
|
||||
return theme.DefaultTextColor
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
package git
|
||||
package commands
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/commands"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
@@ -20,35 +19,34 @@ import (
|
||||
// our safe branches, then add the remaining safe branches, ensuring uniqueness
|
||||
// along the way
|
||||
|
||||
// if we find out we need to use one of these functions in the git.go file, we
|
||||
// can just pull them out of here and put them there and then call them from in here
|
||||
|
||||
// BranchListBuilder returns a list of Branch objects for the current repo
|
||||
type BranchListBuilder struct {
|
||||
Log *logrus.Entry
|
||||
GitCommand *commands.GitCommand
|
||||
GitCommand *GitCommand
|
||||
}
|
||||
|
||||
// NewBranchListBuilder builds a new branch list builder
|
||||
func NewBranchListBuilder(log *logrus.Entry, gitCommand *commands.GitCommand) (*BranchListBuilder, error) {
|
||||
func NewBranchListBuilder(log *logrus.Entry, gitCommand *GitCommand) (*BranchListBuilder, error) {
|
||||
return &BranchListBuilder{
|
||||
Log: log,
|
||||
GitCommand: gitCommand,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (b *BranchListBuilder) obtainCurrentBranch() commands.Branch {
|
||||
// I used go-git for this, but that breaks if you've just done a git init,
|
||||
// even though you're on 'master'
|
||||
branchName, err := b.GitCommand.OSCommand.RunCommandWithOutput("git symbolic-ref --short HEAD")
|
||||
func (b *BranchListBuilder) obtainCurrentBranch() *Branch {
|
||||
branchName, err := b.GitCommand.CurrentBranchName()
|
||||
if err != nil {
|
||||
branchName, err = b.GitCommand.OSCommand.RunCommandWithOutput("git rev-parse --short HEAD")
|
||||
if err != nil {
|
||||
panic(err.Error())
|
||||
}
|
||||
panic(err.Error())
|
||||
}
|
||||
return commands.Branch{Name: strings.TrimSpace(branchName), Recency: " *"}
|
||||
|
||||
return &Branch{Name: strings.TrimSpace(branchName)}
|
||||
}
|
||||
|
||||
func (b *BranchListBuilder) obtainReflogBranches() []commands.Branch {
|
||||
branches := make([]commands.Branch, 0)
|
||||
func (b *BranchListBuilder) obtainReflogBranches() []*Branch {
|
||||
branches := make([]*Branch, 0)
|
||||
rawString, err := b.GitCommand.OSCommand.RunCommandWithOutput("git reflog -n100 --pretty='%cr|%gs' --grep-reflog='checkout: moving' HEAD")
|
||||
if err != nil {
|
||||
return branches
|
||||
@@ -58,29 +56,29 @@ func (b *BranchListBuilder) obtainReflogBranches() []commands.Branch {
|
||||
for _, line := range branchLines {
|
||||
timeNumber, timeUnit, branchName := branchInfoFromLine(line)
|
||||
timeUnit = abbreviatedTimeUnit(timeUnit)
|
||||
branch := commands.Branch{Name: branchName, Recency: timeNumber + timeUnit}
|
||||
branch := &Branch{Name: branchName, Recency: timeNumber + timeUnit}
|
||||
branches = append(branches, branch)
|
||||
}
|
||||
return branches
|
||||
return uniqueByName(branches)
|
||||
}
|
||||
|
||||
func (b *BranchListBuilder) obtainSafeBranches() []commands.Branch {
|
||||
branches := make([]commands.Branch, 0)
|
||||
func (b *BranchListBuilder) obtainSafeBranches() []*Branch {
|
||||
branches := make([]*Branch, 0)
|
||||
|
||||
bIter, err := b.GitCommand.Repo.Branches()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
err = bIter.ForEach(func(b *plumbing.Reference) error {
|
||||
bIter.ForEach(func(b *plumbing.Reference) error {
|
||||
name := b.Name().Short()
|
||||
branches = append(branches, commands.Branch{Name: name})
|
||||
branches = append(branches, &Branch{Name: name})
|
||||
return nil
|
||||
})
|
||||
|
||||
return branches
|
||||
}
|
||||
|
||||
func (b *BranchListBuilder) appendNewBranches(finalBranches, newBranches, existingBranches []commands.Branch, included bool) []commands.Branch {
|
||||
func (b *BranchListBuilder) appendNewBranches(finalBranches, newBranches, existingBranches []*Branch, included bool) []*Branch {
|
||||
for _, newBranch := range newBranches {
|
||||
if included == branchIncluded(newBranch.Name, existingBranches) {
|
||||
finalBranches = append(finalBranches, newBranch)
|
||||
@@ -89,7 +87,7 @@ func (b *BranchListBuilder) appendNewBranches(finalBranches, newBranches, existi
|
||||
return finalBranches
|
||||
}
|
||||
|
||||
func sanitisedReflogName(reflogBranch commands.Branch, safeBranches []commands.Branch) string {
|
||||
func sanitisedReflogName(reflogBranch *Branch, safeBranches []*Branch) string {
|
||||
for _, safeBranch := range safeBranches {
|
||||
if strings.ToLower(safeBranch.Name) == strings.ToLower(reflogBranch.Name) {
|
||||
return safeBranch.Name
|
||||
@@ -99,15 +97,12 @@ func sanitisedReflogName(reflogBranch commands.Branch, safeBranches []commands.B
|
||||
}
|
||||
|
||||
// Build the list of branches for the current repo
|
||||
func (b *BranchListBuilder) Build() []commands.Branch {
|
||||
branches := make([]commands.Branch, 0)
|
||||
func (b *BranchListBuilder) Build() []*Branch {
|
||||
branches := make([]*Branch, 0)
|
||||
head := b.obtainCurrentBranch()
|
||||
safeBranches := b.obtainSafeBranches()
|
||||
if len(safeBranches) == 0 {
|
||||
return append(branches, head)
|
||||
}
|
||||
|
||||
reflogBranches := b.obtainReflogBranches()
|
||||
reflogBranches = uniqueByName(append([]commands.Branch{head}, reflogBranches...))
|
||||
for i, reflogBranch := range reflogBranches {
|
||||
reflogBranches[i].Name = sanitisedReflogName(reflogBranch, safeBranches)
|
||||
}
|
||||
@@ -115,10 +110,16 @@ func (b *BranchListBuilder) Build() []commands.Branch {
|
||||
branches = b.appendNewBranches(branches, reflogBranches, safeBranches, true)
|
||||
branches = b.appendNewBranches(branches, safeBranches, branches, false)
|
||||
|
||||
if len(branches) == 0 || branches[0].Name != head.Name {
|
||||
branches = append([]*Branch{head}, branches...)
|
||||
}
|
||||
|
||||
branches[0].Recency = " *"
|
||||
|
||||
return branches
|
||||
}
|
||||
|
||||
func branchIncluded(branchName string, branches []commands.Branch) bool {
|
||||
func branchIncluded(branchName string, branches []*Branch) bool {
|
||||
for _, existingBranch := range branches {
|
||||
if strings.ToLower(existingBranch.Name) == strings.ToLower(branchName) {
|
||||
return true
|
||||
@@ -127,8 +128,8 @@ func branchIncluded(branchName string, branches []commands.Branch) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func uniqueByName(branches []commands.Branch) []commands.Branch {
|
||||
finalBranches := make([]commands.Branch, 0)
|
||||
func uniqueByName(branches []*Branch) []*Branch {
|
||||
finalBranches := make([]*Branch, 0)
|
||||
for _, branch := range branches {
|
||||
if branchIncluded(branch.Name, finalBranches) {
|
||||
continue
|
||||
60
pkg/commands/commit.go
Normal file
60
pkg/commands/commit.go
Normal file
@@ -0,0 +1,60 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"github.com/fatih/color"
|
||||
"github.com/jesseduffield/lazygit/pkg/theme"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
)
|
||||
|
||||
// Commit : A git commit
|
||||
type Commit struct {
|
||||
Sha string
|
||||
Name string
|
||||
Status string // one of "unpushed", "pushed", "merged", "rebasing" or "selected"
|
||||
DisplayString string
|
||||
Action string // one of "", "pick", "edit", "squash", "reword", "drop", "fixup"
|
||||
Copied bool // to know if this commit is ready to be cherry-picked somewhere
|
||||
}
|
||||
|
||||
// GetDisplayStrings is a function.
|
||||
func (c *Commit) GetDisplayStrings(isFocused bool) []string {
|
||||
red := color.New(color.FgRed)
|
||||
yellow := color.New(color.FgYellow)
|
||||
green := color.New(color.FgGreen)
|
||||
blue := color.New(color.FgBlue)
|
||||
cyan := color.New(color.FgCyan)
|
||||
defaultColor := color.New(theme.DefaultTextColor)
|
||||
magenta := color.New(color.FgMagenta)
|
||||
|
||||
// for some reason, setting the background to blue pads out the other commits
|
||||
// horizontally. For the sake of accessibility I'm considering this a feature,
|
||||
// not a bug
|
||||
copied := color.New(color.FgCyan, color.BgBlue)
|
||||
|
||||
var shaColor *color.Color
|
||||
switch c.Status {
|
||||
case "unpushed":
|
||||
shaColor = red
|
||||
case "pushed":
|
||||
shaColor = yellow
|
||||
case "merged":
|
||||
shaColor = green
|
||||
case "rebasing":
|
||||
shaColor = blue
|
||||
case "selected":
|
||||
shaColor = magenta
|
||||
default:
|
||||
shaColor = defaultColor
|
||||
}
|
||||
|
||||
if c.Copied {
|
||||
shaColor = copied
|
||||
}
|
||||
|
||||
actionString := ""
|
||||
if c.Action != "" {
|
||||
actionString = cyan.Sprint(utils.WithPadding(c.Action, 7)) + " "
|
||||
}
|
||||
|
||||
return []string{shaColor.Sprint(c.Sha), actionString + defaultColor.Sprint(c.Name)}
|
||||
}
|
||||
42
pkg/commands/commit_file.go
Normal file
42
pkg/commands/commit_file.go
Normal file
@@ -0,0 +1,42 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"github.com/fatih/color"
|
||||
"github.com/jesseduffield/lazygit/pkg/theme"
|
||||
)
|
||||
|
||||
// CommitFile : A git commit file
|
||||
type CommitFile struct {
|
||||
Sha string
|
||||
Name string
|
||||
DisplayString string
|
||||
Status int // one of 'WHOLE' 'PART' 'NONE'
|
||||
}
|
||||
|
||||
const (
|
||||
// UNSELECTED is for when the commit file has not been added to the patch in any way
|
||||
UNSELECTED = iota
|
||||
// WHOLE is for when you want to add the whole diff of a file to the patch,
|
||||
// including e.g. if it was deleted
|
||||
WHOLE = iota
|
||||
// PART is for when you're only talking about specific lines that have been modified
|
||||
PART
|
||||
)
|
||||
|
||||
// GetDisplayStrings is a function.
|
||||
func (f *CommitFile) GetDisplayStrings(isFocused bool) []string {
|
||||
yellow := color.New(color.FgYellow)
|
||||
green := color.New(color.FgGreen)
|
||||
defaultColor := color.New(theme.DefaultTextColor)
|
||||
|
||||
var colour *color.Color
|
||||
switch f.Status {
|
||||
case UNSELECTED:
|
||||
colour = defaultColor
|
||||
case WHOLE:
|
||||
colour = green
|
||||
case PART:
|
||||
colour = yellow
|
||||
}
|
||||
return []string{colour.Sprint(f.DisplayString)}
|
||||
}
|
||||
295
pkg/commands/commit_list_builder.go
Normal file
295
pkg/commands/commit_list_builder.go
Normal file
@@ -0,0 +1,295 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/jesseduffield/lazygit/pkg/i18n"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// context:
|
||||
// here we get the commits from git log but format them to show whether they're
|
||||
// unpushed/pushed/merged into the base branch or not, or if they're yet to
|
||||
// be processed as part of a rebase (these won't appear in git log but we
|
||||
// grab them from the rebase-related files in the .git directory to show them
|
||||
|
||||
// if we find out we need to use one of these functions in the git.go file, we
|
||||
// can just pull them out of here and put them there and then call them from in here
|
||||
|
||||
// CommitListBuilder returns a list of Branch objects for the current repo
|
||||
type CommitListBuilder struct {
|
||||
Log *logrus.Entry
|
||||
GitCommand *GitCommand
|
||||
OSCommand *OSCommand
|
||||
Tr *i18n.Localizer
|
||||
CherryPickedCommits []*Commit
|
||||
DiffEntries []*Commit
|
||||
}
|
||||
|
||||
// NewCommitListBuilder builds a new commit list builder
|
||||
func NewCommitListBuilder(log *logrus.Entry, gitCommand *GitCommand, osCommand *OSCommand, tr *i18n.Localizer, cherryPickedCommits []*Commit, diffEntries []*Commit) (*CommitListBuilder, error) {
|
||||
return &CommitListBuilder{
|
||||
Log: log,
|
||||
GitCommand: gitCommand,
|
||||
OSCommand: osCommand,
|
||||
Tr: tr,
|
||||
CherryPickedCommits: cherryPickedCommits,
|
||||
DiffEntries: diffEntries,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetCommits obtains the commits of the current branch
|
||||
func (c *CommitListBuilder) GetCommits() ([]*Commit, error) {
|
||||
commits := []*Commit{}
|
||||
var rebasingCommits []*Commit
|
||||
rebaseMode, err := c.GitCommand.RebaseMode()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if rebaseMode != "" {
|
||||
// here we want to also prepend the commits that we're in the process of rebasing
|
||||
rebasingCommits, err = c.getRebasingCommits(rebaseMode)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(rebasingCommits) > 0 {
|
||||
commits = append(commits, rebasingCommits...)
|
||||
}
|
||||
}
|
||||
|
||||
unpushedCommits := c.getUnpushedCommits()
|
||||
log := c.getLog()
|
||||
|
||||
// now we can split it up and turn it into commits
|
||||
for _, line := range utils.SplitLines(log) {
|
||||
splitLine := strings.Split(line, " ")
|
||||
sha := splitLine[0]
|
||||
_, unpushed := unpushedCommits[sha]
|
||||
status := map[bool]string{true: "unpushed", false: "pushed"}[unpushed]
|
||||
commits = append(commits, &Commit{
|
||||
Sha: sha,
|
||||
Name: strings.Join(splitLine[1:], " "),
|
||||
Status: status,
|
||||
DisplayString: strings.Join(splitLine, " "),
|
||||
})
|
||||
}
|
||||
if rebaseMode != "" {
|
||||
currentCommit := commits[len(rebasingCommits)]
|
||||
blue := color.New(color.FgYellow)
|
||||
youAreHere := blue.Sprintf("<-- %s ---", c.Tr.SLocalize("YouAreHere"))
|
||||
currentCommit.Name = fmt.Sprintf("%s %s", youAreHere, currentCommit.Name)
|
||||
}
|
||||
|
||||
commits, err = c.setCommitMergedStatuses(commits)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
commits, err = c.setCommitCherryPickStatuses(commits)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, commit := range commits {
|
||||
for _, entry := range c.DiffEntries {
|
||||
if entry.Sha == commit.Sha {
|
||||
commit.Status = "selected"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return commits, nil
|
||||
}
|
||||
|
||||
// getRebasingCommits obtains the commits that we're in the process of rebasing
|
||||
func (c *CommitListBuilder) getRebasingCommits(rebaseMode string) ([]*Commit, error) {
|
||||
switch rebaseMode {
|
||||
case "normal":
|
||||
return c.getNormalRebasingCommits()
|
||||
case "interactive":
|
||||
return c.getInteractiveRebasingCommits()
|
||||
default:
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *CommitListBuilder) getNormalRebasingCommits() ([]*Commit, error) {
|
||||
rewrittenCount := 0
|
||||
bytesContent, err := ioutil.ReadFile(fmt.Sprintf("%s/rebase-apply/rewritten", c.GitCommand.DotGitDir))
|
||||
if err == nil {
|
||||
content := string(bytesContent)
|
||||
rewrittenCount = len(strings.Split(content, "\n"))
|
||||
}
|
||||
|
||||
// we know we're rebasing, so lets get all the files whose names have numbers
|
||||
commits := []*Commit{}
|
||||
err = filepath.Walk(fmt.Sprintf("%s/rebase-apply", c.GitCommand.DotGitDir), func(path string, f os.FileInfo, err error) error {
|
||||
if rewrittenCount > 0 {
|
||||
rewrittenCount--
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
re := regexp.MustCompile(`^\d+$`)
|
||||
if !re.MatchString(f.Name()) {
|
||||
return nil
|
||||
}
|
||||
bytesContent, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
content := string(bytesContent)
|
||||
commit, err := c.commitFromPatch(content)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
commits = append([]*Commit{commit}, commits...)
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return commits, nil
|
||||
}
|
||||
|
||||
// git-rebase-todo example:
|
||||
// pick ac446ae94ee560bdb8d1d057278657b251aaef17 ac446ae
|
||||
// pick afb893148791a2fbd8091aeb81deba4930c73031 afb8931
|
||||
|
||||
// git-rebase-todo.backup example:
|
||||
// pick 49cbba374296938ea86bbd4bf4fee2f6ba5cccf6 third commit on master
|
||||
// pick ac446ae94ee560bdb8d1d057278657b251aaef17 blah commit on master
|
||||
// pick afb893148791a2fbd8091aeb81deba4930c73031 fourth commit on master
|
||||
|
||||
// getInteractiveRebasingCommits takes our git-rebase-todo and our git-rebase-todo.backup files
|
||||
// and extracts out the sha and names of commits that we still have to go
|
||||
// in the rebase:
|
||||
func (c *CommitListBuilder) getInteractiveRebasingCommits() ([]*Commit, error) {
|
||||
bytesContent, err := ioutil.ReadFile(fmt.Sprintf("%s/rebase-merge/git-rebase-todo", c.GitCommand.DotGitDir))
|
||||
if err != nil {
|
||||
c.Log.Info(fmt.Sprintf("error occurred reading git-rebase-todo: %s", err.Error()))
|
||||
// we assume an error means the file doesn't exist so we just return
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
commits := []*Commit{}
|
||||
lines := strings.Split(string(bytesContent), "\n")
|
||||
for _, line := range lines {
|
||||
if line == "" || line == "noop" {
|
||||
return commits, nil
|
||||
}
|
||||
splitLine := strings.Split(line, " ")
|
||||
commits = append([]*Commit{{
|
||||
Sha: splitLine[1][0:7],
|
||||
Name: strings.Join(splitLine[2:], " "),
|
||||
Status: "rebasing",
|
||||
Action: splitLine[0],
|
||||
}}, commits...)
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// assuming the file starts like this:
|
||||
// From e93d4193e6dd45ca9cf3a5a273d7ba6cd8b8fb20 Mon Sep 17 00:00:00 2001
|
||||
// From: Lazygit Tester <test@example.com>
|
||||
// Date: Wed, 5 Dec 2018 21:03:23 +1100
|
||||
// Subject: second commit on master
|
||||
func (c *CommitListBuilder) commitFromPatch(content string) (*Commit, error) {
|
||||
lines := strings.Split(content, "\n")
|
||||
sha := strings.Split(lines[0], " ")[1][0:7]
|
||||
name := strings.TrimPrefix(lines[3], "Subject: ")
|
||||
return &Commit{
|
||||
Sha: sha,
|
||||
Name: name,
|
||||
Status: "rebasing",
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *CommitListBuilder) setCommitMergedStatuses(commits []*Commit) ([]*Commit, error) {
|
||||
ancestor, err := c.getMergeBase()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if ancestor == "" {
|
||||
return commits, nil
|
||||
}
|
||||
passedAncestor := false
|
||||
for i, commit := range commits {
|
||||
if strings.HasPrefix(ancestor, commit.Sha) {
|
||||
passedAncestor = true
|
||||
}
|
||||
if commit.Status != "pushed" {
|
||||
continue
|
||||
}
|
||||
if passedAncestor {
|
||||
commits[i].Status = "merged"
|
||||
}
|
||||
}
|
||||
return commits, nil
|
||||
}
|
||||
|
||||
func (c *CommitListBuilder) setCommitCherryPickStatuses(commits []*Commit) ([]*Commit, error) {
|
||||
for _, commit := range commits {
|
||||
for _, cherryPickedCommit := range c.CherryPickedCommits {
|
||||
if commit.Sha == cherryPickedCommit.Sha {
|
||||
commit.Copied = true
|
||||
}
|
||||
}
|
||||
}
|
||||
return commits, nil
|
||||
}
|
||||
|
||||
func (c *CommitListBuilder) getMergeBase() (string, error) {
|
||||
currentBranch, err := c.GitCommand.CurrentBranchName()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
baseBranch := "master"
|
||||
if strings.HasPrefix(currentBranch, "feature/") {
|
||||
baseBranch = "develop"
|
||||
}
|
||||
|
||||
// swallowing error because it's not a big deal; probably because there are no commits yet
|
||||
output, _ := c.OSCommand.RunCommandWithOutput(fmt.Sprintf("git merge-base HEAD %s", baseBranch))
|
||||
return output, nil
|
||||
}
|
||||
|
||||
// getUnpushedCommits Returns the sha's of the commits that have not yet been pushed
|
||||
// to the remote branch of the current branch, a map is returned to ease look up
|
||||
func (c *CommitListBuilder) getUnpushedCommits() map[string]bool {
|
||||
pushables := map[string]bool{}
|
||||
o, err := c.OSCommand.RunCommandWithOutput("git rev-list @{u}..HEAD --abbrev-commit")
|
||||
if err != nil {
|
||||
return pushables
|
||||
}
|
||||
for _, p := range utils.SplitLines(o) {
|
||||
pushables[p] = true
|
||||
}
|
||||
|
||||
return pushables
|
||||
}
|
||||
|
||||
// getLog gets the git log (currently limited to 30 commits for performance
|
||||
// until we work out lazy loading
|
||||
func (c *CommitListBuilder) getLog() string {
|
||||
// currently limiting to 30 for performance reasons
|
||||
// TODO: add lazyloading when you scroll down
|
||||
result, err := c.OSCommand.RunCommandWithOutput("git log --oneline -30")
|
||||
if err != nil {
|
||||
// assume if there is an error there are no commits yet for this branch
|
||||
return ""
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
314
pkg/commands/commit_list_builder_test.go
Normal file
314
pkg/commands/commit_list_builder_test.go
Normal file
@@ -0,0 +1,314 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"os/exec"
|
||||
"testing"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/i18n"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// NewDummyCommitListBuilder creates a new dummy CommitListBuilder for testing
|
||||
func NewDummyCommitListBuilder() *CommitListBuilder {
|
||||
osCommand := NewDummyOSCommand()
|
||||
|
||||
return &CommitListBuilder{
|
||||
Log: NewDummyLog(),
|
||||
GitCommand: NewDummyGitCommandWithOSCommand(osCommand),
|
||||
OSCommand: osCommand,
|
||||
Tr: i18n.NewLocalizer(NewDummyLog()),
|
||||
CherryPickedCommits: []*Commit{},
|
||||
}
|
||||
}
|
||||
|
||||
// TestCommitListBuilderGetUnpushedCommits is a function.
|
||||
func TestCommitListBuilderGetUnpushedCommits(t *testing.T) {
|
||||
type scenario struct {
|
||||
testName string
|
||||
command func(string, ...string) *exec.Cmd
|
||||
test func(map[string]bool)
|
||||
}
|
||||
|
||||
scenarios := []scenario{
|
||||
{
|
||||
"Can't retrieve pushable commits",
|
||||
func(string, ...string) *exec.Cmd {
|
||||
return exec.Command("test")
|
||||
},
|
||||
func(pushables map[string]bool) {
|
||||
assert.EqualValues(t, map[string]bool{}, pushables)
|
||||
},
|
||||
},
|
||||
{
|
||||
"Retrieve pushable commits",
|
||||
func(cmd string, args ...string) *exec.Cmd {
|
||||
return exec.Command("echo", "8a2bb0e\n78976bc")
|
||||
},
|
||||
func(pushables map[string]bool) {
|
||||
assert.Len(t, pushables, 2)
|
||||
assert.EqualValues(t, map[string]bool{"8a2bb0e": true, "78976bc": true}, pushables)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
t.Run(s.testName, func(t *testing.T) {
|
||||
c := NewDummyCommitListBuilder()
|
||||
c.OSCommand.SetCommand(s.command)
|
||||
s.test(c.getUnpushedCommits())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestCommitListBuilderGetMergeBase is a function.
|
||||
func TestCommitListBuilderGetMergeBase(t *testing.T) {
|
||||
type scenario struct {
|
||||
testName string
|
||||
command func(string, ...string) *exec.Cmd
|
||||
test func(string, error)
|
||||
}
|
||||
|
||||
scenarios := []scenario{
|
||||
{
|
||||
"swallows an error if the call to merge-base returns an error",
|
||||
func(cmd string, args ...string) *exec.Cmd {
|
||||
assert.EqualValues(t, "git", cmd)
|
||||
|
||||
switch args[0] {
|
||||
case "symbolic-ref":
|
||||
assert.EqualValues(t, []string{"symbolic-ref", "--short", "HEAD"}, args)
|
||||
return exec.Command("echo", "master")
|
||||
case "merge-base":
|
||||
assert.EqualValues(t, []string{"merge-base", "HEAD", "master"}, args)
|
||||
return exec.Command("test")
|
||||
}
|
||||
return nil
|
||||
},
|
||||
func(output string, err error) {
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, "", output)
|
||||
},
|
||||
},
|
||||
{
|
||||
"returns the commit when master",
|
||||
func(cmd string, args ...string) *exec.Cmd {
|
||||
assert.EqualValues(t, "git", cmd)
|
||||
|
||||
switch args[0] {
|
||||
case "symbolic-ref":
|
||||
assert.EqualValues(t, []string{"symbolic-ref", "--short", "HEAD"}, args)
|
||||
return exec.Command("echo", "master")
|
||||
case "merge-base":
|
||||
assert.EqualValues(t, []string{"merge-base", "HEAD", "master"}, args)
|
||||
return exec.Command("echo", "blah")
|
||||
}
|
||||
return nil
|
||||
},
|
||||
func(output string, err error) {
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "blah\n", output)
|
||||
},
|
||||
},
|
||||
{
|
||||
"checks against develop when a feature branch",
|
||||
func(cmd string, args ...string) *exec.Cmd {
|
||||
assert.EqualValues(t, "git", cmd)
|
||||
|
||||
switch args[0] {
|
||||
case "symbolic-ref":
|
||||
assert.EqualValues(t, []string{"symbolic-ref", "--short", "HEAD"}, args)
|
||||
return exec.Command("echo", "feature/test")
|
||||
case "merge-base":
|
||||
assert.EqualValues(t, []string{"merge-base", "HEAD", "develop"}, args)
|
||||
return exec.Command("echo", "blah")
|
||||
}
|
||||
return nil
|
||||
},
|
||||
func(output string, err error) {
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "blah\n", output)
|
||||
},
|
||||
},
|
||||
{
|
||||
"bubbles up error if there is one",
|
||||
func(cmd string, args ...string) *exec.Cmd {
|
||||
return exec.Command("test")
|
||||
},
|
||||
func(output string, err error) {
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, "", output)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
t.Run(s.testName, func(t *testing.T) {
|
||||
c := NewDummyCommitListBuilder()
|
||||
c.OSCommand.SetCommand(s.command)
|
||||
s.test(c.getMergeBase())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestCommitListBuilderGetLog is a function.
|
||||
func TestCommitListBuilderGetLog(t *testing.T) {
|
||||
type scenario struct {
|
||||
testName string
|
||||
command func(string, ...string) *exec.Cmd
|
||||
test func(string)
|
||||
}
|
||||
|
||||
scenarios := []scenario{
|
||||
{
|
||||
"Retrieves logs",
|
||||
func(cmd string, args ...string) *exec.Cmd {
|
||||
assert.EqualValues(t, "git", cmd)
|
||||
assert.EqualValues(t, []string{"log", "--oneline", "-30"}, args)
|
||||
|
||||
return exec.Command("echo", "6f0b32f commands/git : add GetCommits tests refactor\n9d9d775 circle : remove new line")
|
||||
},
|
||||
func(output string) {
|
||||
assert.EqualValues(t, "6f0b32f commands/git : add GetCommits tests refactor\n9d9d775 circle : remove new line\n", output)
|
||||
},
|
||||
},
|
||||
{
|
||||
"An error occurred when retrieving logs",
|
||||
func(cmd string, args ...string) *exec.Cmd {
|
||||
assert.EqualValues(t, "git", cmd)
|
||||
assert.EqualValues(t, []string{"log", "--oneline", "-30"}, args)
|
||||
return exec.Command("test")
|
||||
},
|
||||
func(output string) {
|
||||
assert.Empty(t, output)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
t.Run(s.testName, func(t *testing.T) {
|
||||
c := NewDummyCommitListBuilder()
|
||||
c.OSCommand.SetCommand(s.command)
|
||||
s.test(c.getLog())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestCommitListBuilderGetCommits is a function.
|
||||
func TestCommitListBuilderGetCommits(t *testing.T) {
|
||||
type scenario struct {
|
||||
testName string
|
||||
command func(string, ...string) *exec.Cmd
|
||||
test func([]*Commit, error)
|
||||
}
|
||||
|
||||
scenarios := []scenario{
|
||||
{
|
||||
"No data found",
|
||||
func(cmd string, args ...string) *exec.Cmd {
|
||||
assert.EqualValues(t, "git", cmd)
|
||||
|
||||
switch args[0] {
|
||||
case "rev-list":
|
||||
assert.EqualValues(t, []string{"rev-list", "@{u}..HEAD", "--abbrev-commit"}, args)
|
||||
return exec.Command("echo")
|
||||
case "log":
|
||||
assert.EqualValues(t, []string{"log", "--oneline", "-30"}, args)
|
||||
return exec.Command("echo")
|
||||
case "merge-base":
|
||||
assert.EqualValues(t, []string{"merge-base", "HEAD", "master"}, args)
|
||||
return exec.Command("test")
|
||||
case "symbolic-ref":
|
||||
assert.EqualValues(t, []string{"symbolic-ref", "--short", "HEAD"}, args)
|
||||
return exec.Command("echo", "master")
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
func(commits []*Commit, err error) {
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, commits, 0)
|
||||
},
|
||||
},
|
||||
{
|
||||
"GetCommits returns 2 commits, 1 unpushed, the other merged",
|
||||
func(cmd string, args ...string) *exec.Cmd {
|
||||
assert.EqualValues(t, "git", cmd)
|
||||
|
||||
switch args[0] {
|
||||
case "rev-list":
|
||||
assert.EqualValues(t, []string{"rev-list", "@{u}..HEAD", "--abbrev-commit"}, args)
|
||||
return exec.Command("echo", "8a2bb0e")
|
||||
case "log":
|
||||
assert.EqualValues(t, []string{"log", "--oneline", "-30"}, args)
|
||||
return exec.Command("echo", "8a2bb0e commit 1\n78976bc commit 2")
|
||||
case "merge-base":
|
||||
assert.EqualValues(t, []string{"merge-base", "HEAD", "master"}, args)
|
||||
return exec.Command("echo", "78976bc")
|
||||
case "symbolic-ref":
|
||||
assert.EqualValues(t, []string{"symbolic-ref", "--short", "HEAD"}, args)
|
||||
return exec.Command("echo", "master")
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
func(commits []*Commit, err error) {
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, commits, 2)
|
||||
assert.EqualValues(t, []*Commit{
|
||||
{
|
||||
Sha: "8a2bb0e",
|
||||
Name: "commit 1",
|
||||
Status: "unpushed",
|
||||
DisplayString: "8a2bb0e commit 1",
|
||||
},
|
||||
{
|
||||
Sha: "78976bc",
|
||||
Name: "commit 2",
|
||||
Status: "merged",
|
||||
DisplayString: "78976bc commit 2",
|
||||
},
|
||||
}, commits)
|
||||
},
|
||||
},
|
||||
{
|
||||
"GetCommits bubbles up an error from setCommitMergedStatuses",
|
||||
func(cmd string, args ...string) *exec.Cmd {
|
||||
assert.EqualValues(t, "git", cmd)
|
||||
|
||||
switch args[0] {
|
||||
case "rev-list":
|
||||
assert.EqualValues(t, []string{"rev-list", "@{u}..HEAD", "--abbrev-commit"}, args)
|
||||
return exec.Command("echo", "8a2bb0e")
|
||||
case "log":
|
||||
assert.EqualValues(t, []string{"log", "--oneline", "-30"}, args)
|
||||
return exec.Command("echo", "8a2bb0e commit 1\n78976bc commit 2")
|
||||
case "merge-base":
|
||||
assert.EqualValues(t, []string{"merge-base", "HEAD", "master"}, args)
|
||||
return exec.Command("echo", "78976bc")
|
||||
case "symbolic-ref":
|
||||
assert.EqualValues(t, []string{"symbolic-ref", "--short", "HEAD"}, args)
|
||||
// here's where we are returning the error
|
||||
return exec.Command("test")
|
||||
case "rev-parse":
|
||||
assert.EqualValues(t, []string{"rev-parse", "--short", "HEAD"}, args)
|
||||
// here too
|
||||
return exec.Command("test")
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
func(commits []*Commit, err error) {
|
||||
assert.Error(t, err)
|
||||
assert.Len(t, commits, 0)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
t.Run(s.testName, func(t *testing.T) {
|
||||
c := NewDummyCommitListBuilder()
|
||||
c.OSCommand.SetCommand(s.command)
|
||||
s.test(c.GetCommits())
|
||||
})
|
||||
}
|
||||
}
|
||||
58
pkg/commands/dummies.go
Normal file
58
pkg/commands/dummies.go
Normal file
@@ -0,0 +1,58 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
"github.com/jesseduffield/lazygit/pkg/i18n"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/spf13/viper"
|
||||
yaml "gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
// This file exports dummy constructors for use by tests in other packages
|
||||
|
||||
// NewDummyOSCommand creates a new dummy OSCommand for testing
|
||||
func NewDummyOSCommand() *OSCommand {
|
||||
return NewOSCommand(NewDummyLog(), NewDummyAppConfig())
|
||||
}
|
||||
|
||||
// NewDummyAppConfig creates a new dummy AppConfig for testing
|
||||
func NewDummyAppConfig() *config.AppConfig {
|
||||
appConfig := &config.AppConfig{
|
||||
Name: "lazygit",
|
||||
Version: "unversioned",
|
||||
Commit: "",
|
||||
BuildDate: "",
|
||||
Debug: false,
|
||||
BuildSource: "",
|
||||
UserConfig: viper.New(),
|
||||
}
|
||||
_ = yaml.Unmarshal([]byte{}, appConfig.AppState)
|
||||
return appConfig
|
||||
}
|
||||
|
||||
// NewDummyLog creates a new dummy Log for testing
|
||||
func NewDummyLog() *logrus.Entry {
|
||||
log := logrus.New()
|
||||
log.Out = ioutil.Discard
|
||||
return log.WithField("test", "test")
|
||||
}
|
||||
|
||||
// NewDummyGitCommand creates a new dummy GitCommand for testing
|
||||
func NewDummyGitCommand() *GitCommand {
|
||||
return NewDummyGitCommandWithOSCommand(NewDummyOSCommand())
|
||||
}
|
||||
|
||||
// NewDummyGitCommandWithOSCommand creates a new dummy GitCommand for testing
|
||||
func NewDummyGitCommandWithOSCommand(osCommand *OSCommand) *GitCommand {
|
||||
return &GitCommand{
|
||||
Log: NewDummyLog(),
|
||||
OSCommand: osCommand,
|
||||
Tr: i18n.NewLocalizer(NewDummyLog()),
|
||||
Config: NewDummyAppConfig(),
|
||||
getGlobalGitConfig: func(string) (string, error) { return "", nil },
|
||||
getLocalGitConfig: func(string) (string, error) { return "", nil },
|
||||
removeFile: func(string) error { return nil },
|
||||
}
|
||||
}
|
||||
14
pkg/commands/errors.go
Normal file
14
pkg/commands/errors.go
Normal file
@@ -0,0 +1,14 @@
|
||||
package commands
|
||||
|
||||
import "github.com/go-errors/errors"
|
||||
|
||||
// WrapError wraps an error for the sake of showing a stack trace at the top level
|
||||
// the go-errors package, for some reason, does not return nil when you try to wrap
|
||||
// a non-error, so we're just doing it here
|
||||
func WrapError(err error) error {
|
||||
if err == nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return errors.Wrap(err, 0)
|
||||
}
|
||||
95
pkg/commands/exec_live_default.go
Normal file
95
pkg/commands/exec_live_default.go
Normal file
@@ -0,0 +1,95 @@
|
||||
// +build !windows
|
||||
|
||||
package commands
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/go-errors/errors"
|
||||
|
||||
"github.com/jesseduffield/pty"
|
||||
)
|
||||
|
||||
// RunCommandWithOutputLiveWrapper runs a command and return every word that gets written in stdout
|
||||
// Output is a function that executes by every word that gets read by bufio
|
||||
// As return of output you need to give a string that will be written to stdin
|
||||
// NOTE: If the return data is empty it won't written anything to stdin
|
||||
func RunCommandWithOutputLiveWrapper(c *OSCommand, command string, output func(string) string) error {
|
||||
cmd := c.ExecutableFromString(command)
|
||||
cmd.Env = append(cmd.Env, "LANG=en_US.UTF-8", "LC_ALL=en_US.UTF-8")
|
||||
|
||||
var stderr bytes.Buffer
|
||||
cmd.Stderr = &stderr
|
||||
|
||||
ptmx, err := pty.Start(cmd)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
go func() {
|
||||
scanner := bufio.NewScanner(ptmx)
|
||||
scanner.Split(scanWordsWithNewLines)
|
||||
for scanner.Scan() {
|
||||
toOutput := strings.Trim(scanner.Text(), " ")
|
||||
_, _ = ptmx.WriteString(output(toOutput))
|
||||
}
|
||||
}()
|
||||
|
||||
err = cmd.Wait()
|
||||
ptmx.Close()
|
||||
if err != nil {
|
||||
return errors.New(stderr.String())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// scanWordsWithNewLines is a copy of bufio.ScanWords but this also captures new lines
|
||||
// For specific comments about this function take a look at: bufio.ScanWords
|
||||
func scanWordsWithNewLines(data []byte, atEOF bool) (advance int, token []byte, err error) {
|
||||
start := 0
|
||||
for width := 0; start < len(data); start += width {
|
||||
var r rune
|
||||
r, width = utf8.DecodeRune(data[start:])
|
||||
if !isSpace(r) {
|
||||
break
|
||||
}
|
||||
}
|
||||
for width, i := 0, start; i < len(data); i += width {
|
||||
var r rune
|
||||
r, width = utf8.DecodeRune(data[i:])
|
||||
if isSpace(r) {
|
||||
return i + width, data[start:i], nil
|
||||
}
|
||||
}
|
||||
if atEOF && len(data) > start {
|
||||
return len(data), data[start:], nil
|
||||
}
|
||||
return start, nil, nil
|
||||
}
|
||||
|
||||
// isSpace is also copied from the bufio package and has been modified to also captures new lines
|
||||
// For specific comments about this function take a look at: bufio.isSpace
|
||||
func isSpace(r rune) bool {
|
||||
if r <= '\u00FF' {
|
||||
switch r {
|
||||
case ' ', '\t', '\v', '\f':
|
||||
return true
|
||||
case '\u0085', '\u00A0':
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
if '\u2000' <= r && r <= '\u200a' {
|
||||
return true
|
||||
}
|
||||
switch r {
|
||||
case '\u1680', '\u2028', '\u2029', '\u202f', '\u205f', '\u3000':
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
9
pkg/commands/exec_live_win.go
Normal file
9
pkg/commands/exec_live_win.go
Normal file
@@ -0,0 +1,9 @@
|
||||
// +build windows
|
||||
|
||||
package commands
|
||||
|
||||
// RunCommandWithOutputLiveWrapper runs a command live but because of windows compatibility this command can't be ran there
|
||||
// TODO: Remove this hack and replace it with a proper way to run commands live on windows
|
||||
func RunCommandWithOutputLiveWrapper(c *OSCommand, command string, output func(string) string) error {
|
||||
return c.RunCommand(command)
|
||||
}
|
||||
38
pkg/commands/file.go
Normal file
38
pkg/commands/file.go
Normal file
@@ -0,0 +1,38 @@
|
||||
package commands
|
||||
|
||||
import "github.com/fatih/color"
|
||||
|
||||
// File : A file from git status
|
||||
// duplicating this for now
|
||||
type File struct {
|
||||
Name string
|
||||
HasStagedChanges bool
|
||||
HasUnstagedChanges bool
|
||||
Tracked bool
|
||||
Deleted bool
|
||||
HasMergeConflicts bool
|
||||
HasInlineMergeConflicts bool
|
||||
DisplayString string
|
||||
Type string // one of 'file', 'directory', and 'other'
|
||||
ShortStatus string // e.g. 'AD', ' A', 'M ', '??'
|
||||
}
|
||||
|
||||
// GetDisplayStrings returns the display string of a file
|
||||
func (f *File) GetDisplayStrings(isFocused bool) []string {
|
||||
// potentially inefficient to be instantiating these color
|
||||
// objects with each render
|
||||
red := color.New(color.FgRed)
|
||||
green := color.New(color.FgGreen)
|
||||
if !f.Tracked && !f.HasStagedChanges {
|
||||
return []string{red.Sprint(f.DisplayString)}
|
||||
}
|
||||
|
||||
output := green.Sprint(f.DisplayString[0:1])
|
||||
output += red.Sprint(f.DisplayString[1:3])
|
||||
if f.HasUnstagedChanges {
|
||||
output += red.Sprint(f.Name)
|
||||
} else {
|
||||
output += green.Sprint(f.Name)
|
||||
}
|
||||
return []string{output}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,32 +1,5 @@
|
||||
package commands
|
||||
|
||||
// File : A staged/unstaged file
|
||||
type File struct {
|
||||
Name string
|
||||
HasStagedChanges bool
|
||||
HasUnstagedChanges bool
|
||||
Tracked bool
|
||||
Deleted bool
|
||||
HasMergeConflicts bool
|
||||
DisplayString string
|
||||
Type string // one of 'file', 'directory', and 'other'
|
||||
}
|
||||
|
||||
// Commit : A git commit
|
||||
type Commit struct {
|
||||
Sha string
|
||||
Name string
|
||||
Pushed bool
|
||||
DisplayString string
|
||||
}
|
||||
|
||||
// StashEntry : A git stash entry
|
||||
type StashEntry struct {
|
||||
Index int
|
||||
Name string
|
||||
DisplayString string
|
||||
}
|
||||
|
||||
// Conflict : A git conflict with a start middle and end corresponding to line
|
||||
// numbers in the file where the conflict bars appear
|
||||
type Conflict struct {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,16 +1,19 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/go-errors/errors"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
|
||||
"github.com/mgutz/str"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
gitconfig "github.com/tcnksm/go-gitconfig"
|
||||
)
|
||||
@@ -22,6 +25,7 @@ type Platform struct {
|
||||
shellArg string
|
||||
escapedQuote string
|
||||
openCommand string
|
||||
openLinkCommand string
|
||||
fallbackEscapedQuote string
|
||||
}
|
||||
|
||||
@@ -47,14 +51,66 @@ func NewOSCommand(log *logrus.Entry, config config.AppConfigurer) *OSCommand {
|
||||
}
|
||||
}
|
||||
|
||||
// SetCommand sets the command function used by the struct.
|
||||
// To be used for testing only
|
||||
func (c *OSCommand) SetCommand(cmd func(string, ...string) *exec.Cmd) {
|
||||
c.command = cmd
|
||||
}
|
||||
|
||||
// RunCommandWithOutput wrapper around commands returning their output and error
|
||||
func (c *OSCommand) RunCommandWithOutput(command string) (string, error) {
|
||||
c.Log.WithField("command", command).Info("RunCommand")
|
||||
splitCmd := str.ToArgv(command)
|
||||
c.Log.Info(splitCmd)
|
||||
return sanitisedCommandOutput(
|
||||
c.command(splitCmd[0], splitCmd[1:]...).CombinedOutput(),
|
||||
)
|
||||
cmd := c.ExecutableFromString(command)
|
||||
return sanitisedCommandOutput(cmd.CombinedOutput())
|
||||
}
|
||||
|
||||
// RunExecutableWithOutput runs an executable file and returns its output
|
||||
func (c *OSCommand) RunExecutableWithOutput(cmd *exec.Cmd) (string, error) {
|
||||
return sanitisedCommandOutput(cmd.CombinedOutput())
|
||||
}
|
||||
|
||||
// RunExecutable runs an executable file and returns an error if there was one
|
||||
func (c *OSCommand) RunExecutable(cmd *exec.Cmd) error {
|
||||
_, err := c.RunExecutableWithOutput(cmd)
|
||||
return err
|
||||
}
|
||||
|
||||
// ExecutableFromString takes a string like `git status` and returns an executable command for it
|
||||
func (c *OSCommand) ExecutableFromString(commandStr string) *exec.Cmd {
|
||||
splitCmd := str.ToArgv(commandStr)
|
||||
cmd := c.command(splitCmd[0], splitCmd[1:]...)
|
||||
cmd.Env = append(os.Environ(), "GIT_OPTIONAL_LOCKS=0")
|
||||
return cmd
|
||||
}
|
||||
|
||||
// RunCommandWithOutputLive runs RunCommandWithOutputLiveWrapper
|
||||
func (c *OSCommand) RunCommandWithOutputLive(command string, output func(string) string) error {
|
||||
return RunCommandWithOutputLiveWrapper(c, command, output)
|
||||
}
|
||||
|
||||
// DetectUnamePass detect a username / password question in a command
|
||||
// ask is a function that gets executen when this function detect you need to fillin a password
|
||||
// The ask argument will be "username" or "password" and expects the user's password or username back
|
||||
func (c *OSCommand) DetectUnamePass(command string, ask func(string) string) error {
|
||||
ttyText := ""
|
||||
errMessage := c.RunCommandWithOutputLive(command, func(word string) string {
|
||||
ttyText = ttyText + " " + word
|
||||
|
||||
prompts := map[string]string{
|
||||
"password": `Password\s*for\s*'.+':`,
|
||||
"username": `Username\s*for\s*'.+':`,
|
||||
}
|
||||
|
||||
for askFor, pattern := range prompts {
|
||||
if match, _ := regexp.MatchString(pattern, ttyText); match {
|
||||
ttyText = ""
|
||||
return ask(askFor)
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
})
|
||||
return errMessage
|
||||
}
|
||||
|
||||
// RunCommand runs a command and just returns the error
|
||||
@@ -91,7 +147,7 @@ func sanitisedCommandOutput(output []byte, err error) (string, error) {
|
||||
// errors like 'exit status 1' are not very useful so we'll create an error
|
||||
// from the combined output
|
||||
if outputString == "" {
|
||||
return "", err
|
||||
return "", WrapError(err)
|
||||
}
|
||||
return outputString, errors.New(outputString)
|
||||
}
|
||||
@@ -110,6 +166,18 @@ func (c *OSCommand) OpenFile(filename string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// OpenLink opens a file with the given
|
||||
func (c *OSCommand) OpenLink(link string) error {
|
||||
commandTemplate := c.Config.GetUserConfig().GetString("os.openLinkCommand")
|
||||
templateValues := map[string]string{
|
||||
"link": c.Quote(link),
|
||||
}
|
||||
|
||||
command := utils.ResolvePlaceholderString(commandTemplate, templateValues)
|
||||
err := c.RunCommand(command)
|
||||
return err
|
||||
}
|
||||
|
||||
// EditFile opens a file in a subprocess using whatever editor is available,
|
||||
// falling back to core.editor, VISUAL, EDITOR, then vi
|
||||
func (c *OSCommand) EditFile(filename string) (*exec.Cmd, error) {
|
||||
@@ -134,8 +202,13 @@ func (c *OSCommand) EditFile(filename string) (*exec.Cmd, error) {
|
||||
}
|
||||
|
||||
// PrepareSubProcess iniPrepareSubProcessrocess then tells the Gui to switch to it
|
||||
// TODO: see if this needs to exist, given that ExecutableFromString does the same things
|
||||
func (c *OSCommand) PrepareSubProcess(cmdName string, commandArgs ...string) *exec.Cmd {
|
||||
return c.command(cmdName, commandArgs...)
|
||||
cmd := c.command(cmdName, commandArgs...)
|
||||
if cmd != nil {
|
||||
cmd.Env = append(os.Environ(), "GIT_OPTIONAL_LOCKS=0")
|
||||
}
|
||||
return cmd
|
||||
}
|
||||
|
||||
// Quote wraps a message in platform-specific quotation marks
|
||||
@@ -158,10 +231,155 @@ func (c *OSCommand) Unquote(message string) string {
|
||||
func (c *OSCommand) AppendLineToFile(filename, line string) error {
|
||||
f, err := os.OpenFile(filename, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0600)
|
||||
if err != nil {
|
||||
return err
|
||||
return WrapError(err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
_, err = f.WriteString("\n" + line)
|
||||
return err
|
||||
if err != nil {
|
||||
return WrapError(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateTempFile writes a string to a new temp file and returns the file's name
|
||||
func (c *OSCommand) CreateTempFile(filename, content string) (string, error) {
|
||||
tmpfile, err := ioutil.TempFile("", filename)
|
||||
if err != nil {
|
||||
c.Log.Error(err)
|
||||
return "", WrapError(err)
|
||||
}
|
||||
|
||||
if _, err := tmpfile.WriteString(content); err != nil {
|
||||
c.Log.Error(err)
|
||||
return "", WrapError(err)
|
||||
}
|
||||
if err := tmpfile.Close(); err != nil {
|
||||
c.Log.Error(err)
|
||||
return "", WrapError(err)
|
||||
}
|
||||
|
||||
return tmpfile.Name(), nil
|
||||
}
|
||||
|
||||
// CreateFileWithContent creates a file with the given content
|
||||
func (c *OSCommand) CreateFileWithContent(path string, content string) error {
|
||||
if err := os.MkdirAll(filepath.Dir(path), os.ModePerm); err != nil {
|
||||
c.Log.Error(err)
|
||||
return err
|
||||
}
|
||||
|
||||
if err := ioutil.WriteFile(path, []byte(content), 0644); err != nil {
|
||||
c.Log.Error(err)
|
||||
return WrapError(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Remove removes a file or directory at the specified path
|
||||
func (c *OSCommand) Remove(filename string) error {
|
||||
err := os.RemoveAll(filename)
|
||||
return WrapError(err)
|
||||
}
|
||||
|
||||
// FileExists checks whether a file exists at the specified path
|
||||
func (c *OSCommand) FileExists(path string) (bool, error) {
|
||||
if _, err := os.Stat(path); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return false, nil
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// RunPreparedCommand takes a pointer to an exec.Cmd and runs it
|
||||
// this is useful if you need to give your command some environment variables
|
||||
// before running it
|
||||
func (c *OSCommand) RunPreparedCommand(cmd *exec.Cmd) error {
|
||||
out, err := cmd.CombinedOutput()
|
||||
outString := string(out)
|
||||
c.Log.Info(outString)
|
||||
if err != nil {
|
||||
if len(outString) == 0 {
|
||||
return err
|
||||
}
|
||||
return errors.New(outString)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetLazygitPath returns the path of the currently executed file
|
||||
func (c *OSCommand) GetLazygitPath() string {
|
||||
ex, err := os.Executable() // get the executable path for git to use
|
||||
if err != nil {
|
||||
ex = os.Args[0] // fallback to the first call argument if needed
|
||||
}
|
||||
return filepath.ToSlash(ex)
|
||||
}
|
||||
|
||||
// RunCustomCommand returns the pointer to a custom command
|
||||
func (c *OSCommand) RunCustomCommand(command string) *exec.Cmd {
|
||||
return c.PrepareSubProcess(c.Platform.shell, c.Platform.shellArg, command)
|
||||
}
|
||||
|
||||
// PipeCommands runs a heap of commands and pipes their inputs/outputs together like A | B | C
|
||||
func (c *OSCommand) PipeCommands(commandStrings ...string) error {
|
||||
|
||||
cmds := make([]*exec.Cmd, len(commandStrings))
|
||||
|
||||
for i, str := range commandStrings {
|
||||
cmds[i] = c.ExecutableFromString(str)
|
||||
}
|
||||
|
||||
for i := 0; i < len(cmds)-1; i++ {
|
||||
stdout, err := cmds[i].StdoutPipe()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmds[i+1].Stdin = stdout
|
||||
}
|
||||
|
||||
// keeping this here in case I adapt this code for some other purpose in the future
|
||||
// cmds[len(cmds)-1].Stdout = os.Stdout
|
||||
|
||||
finalErrors := []string{}
|
||||
|
||||
wg := sync.WaitGroup{}
|
||||
wg.Add(len(cmds))
|
||||
|
||||
for _, cmd := range cmds {
|
||||
currentCmd := cmd
|
||||
go func() {
|
||||
stderr, err := currentCmd.StderrPipe()
|
||||
if err != nil {
|
||||
c.Log.Error(err)
|
||||
}
|
||||
|
||||
if err := currentCmd.Start(); err != nil {
|
||||
c.Log.Error(err)
|
||||
}
|
||||
|
||||
if b, err := ioutil.ReadAll(stderr); err == nil {
|
||||
if len(b) > 0 {
|
||||
finalErrors = append(finalErrors, string(b))
|
||||
}
|
||||
}
|
||||
|
||||
if err := currentCmd.Wait(); err != nil {
|
||||
c.Log.Error(err)
|
||||
}
|
||||
|
||||
wg.Done()
|
||||
}()
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
if len(finalErrors) > 0 {
|
||||
return errors.New(strings.Join(finalErrors, "\n"))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ func getPlatform() *Platform {
|
||||
shellArg: "-c",
|
||||
escapedQuote: "'",
|
||||
openCommand: "open {{filename}}",
|
||||
openLinkCommand: "open {{link}}",
|
||||
fallbackEscapedQuote: "\"",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,34 +1,15 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"testing"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
"github.com/spf13/viper"
|
||||
"github.com/stretchr/testify/assert"
|
||||
yaml "gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
func newDummyOSCommand() *OSCommand {
|
||||
return NewOSCommand(newDummyLog(), newDummyAppConfig())
|
||||
}
|
||||
|
||||
func newDummyAppConfig() *config.AppConfig {
|
||||
appConfig := &config.AppConfig{
|
||||
Name: "lazygit",
|
||||
Version: "unversioned",
|
||||
Commit: "",
|
||||
BuildDate: "",
|
||||
Debug: false,
|
||||
BuildSource: "",
|
||||
UserConfig: viper.New(),
|
||||
}
|
||||
_ = yaml.Unmarshal([]byte{}, appConfig.AppState)
|
||||
return appConfig
|
||||
}
|
||||
|
||||
// TestOSCommandRunCommandWithOutput is a function.
|
||||
func TestOSCommandRunCommandWithOutput(t *testing.T) {
|
||||
type scenario struct {
|
||||
command string
|
||||
@@ -52,10 +33,11 @@ func TestOSCommandRunCommandWithOutput(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
s.test(newDummyOSCommand().RunCommandWithOutput(s.command))
|
||||
s.test(NewDummyOSCommand().RunCommandWithOutput(s.command))
|
||||
}
|
||||
}
|
||||
|
||||
// TestOSCommandRunCommand is a function.
|
||||
func TestOSCommandRunCommand(t *testing.T) {
|
||||
type scenario struct {
|
||||
command string
|
||||
@@ -72,10 +54,11 @@ func TestOSCommandRunCommand(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
s.test(newDummyOSCommand().RunCommand(s.command))
|
||||
s.test(NewDummyOSCommand().RunCommand(s.command))
|
||||
}
|
||||
}
|
||||
|
||||
// TestOSCommandOpenFile is a function.
|
||||
func TestOSCommandOpenFile(t *testing.T) {
|
||||
type scenario struct {
|
||||
filename string
|
||||
@@ -118,7 +101,7 @@ func TestOSCommandOpenFile(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
OSCmd := newDummyOSCommand()
|
||||
OSCmd := NewDummyOSCommand()
|
||||
OSCmd.command = s.command
|
||||
OSCmd.Config.GetUserConfig().Set("os.openCommand", "open {{filename}}")
|
||||
|
||||
@@ -126,6 +109,7 @@ func TestOSCommandOpenFile(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestOSCommandEditFile is a function.
|
||||
func TestOSCommandEditFile(t *testing.T) {
|
||||
type scenario struct {
|
||||
filename string
|
||||
@@ -246,7 +230,7 @@ func TestOSCommandEditFile(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
OSCmd := newDummyOSCommand()
|
||||
OSCmd := NewDummyOSCommand()
|
||||
OSCmd.command = s.command
|
||||
OSCmd.getGlobalGitConfig = s.getGlobalGitConfig
|
||||
OSCmd.getenv = s.getenv
|
||||
@@ -255,8 +239,9 @@ func TestOSCommandEditFile(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestOSCommandQuote is a function.
|
||||
func TestOSCommandQuote(t *testing.T) {
|
||||
osCommand := newDummyOSCommand()
|
||||
osCommand := NewDummyOSCommand()
|
||||
|
||||
actual := osCommand.Quote("hello `test`")
|
||||
|
||||
@@ -267,7 +252,7 @@ func TestOSCommandQuote(t *testing.T) {
|
||||
|
||||
// TestOSCommandQuoteSingleQuote tests the quote function with ' quotes explicitly for Linux
|
||||
func TestOSCommandQuoteSingleQuote(t *testing.T) {
|
||||
osCommand := newDummyOSCommand()
|
||||
osCommand := NewDummyOSCommand()
|
||||
|
||||
osCommand.Platform.os = "linux"
|
||||
|
||||
@@ -278,9 +263,9 @@ func TestOSCommandQuoteSingleQuote(t *testing.T) {
|
||||
assert.EqualValues(t, expected, actual)
|
||||
}
|
||||
|
||||
// TestOSCommandQuoteSingleQuote tests the quote function with " quotes explicitly for Linux
|
||||
// TestOSCommandQuoteDoubleQuote tests the quote function with " quotes explicitly for Linux
|
||||
func TestOSCommandQuoteDoubleQuote(t *testing.T) {
|
||||
osCommand := newDummyOSCommand()
|
||||
osCommand := NewDummyOSCommand()
|
||||
|
||||
osCommand.Platform.os = "linux"
|
||||
|
||||
@@ -291,8 +276,9 @@ func TestOSCommandQuoteDoubleQuote(t *testing.T) {
|
||||
assert.EqualValues(t, expected, actual)
|
||||
}
|
||||
|
||||
// TestOSCommandUnquote is a function.
|
||||
func TestOSCommandUnquote(t *testing.T) {
|
||||
osCommand := newDummyOSCommand()
|
||||
osCommand := NewDummyOSCommand()
|
||||
|
||||
actual := osCommand.Unquote(`hello "test"`)
|
||||
|
||||
@@ -301,6 +287,7 @@ func TestOSCommandUnquote(t *testing.T) {
|
||||
assert.EqualValues(t, expected, actual)
|
||||
}
|
||||
|
||||
// TestOSCommandFileType is a function.
|
||||
func TestOSCommandFileType(t *testing.T) {
|
||||
type scenario struct {
|
||||
path string
|
||||
@@ -353,7 +340,38 @@ func TestOSCommandFileType(t *testing.T) {
|
||||
|
||||
for _, s := range scenarios {
|
||||
s.setup()
|
||||
s.test(newDummyOSCommand().FileType(s.path))
|
||||
s.test(NewDummyOSCommand().FileType(s.path))
|
||||
_ = os.RemoveAll(s.path)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOSCommandCreateTempFile(t *testing.T) {
|
||||
type scenario struct {
|
||||
testName string
|
||||
filename string
|
||||
content string
|
||||
test func(string, error)
|
||||
}
|
||||
|
||||
scenarios := []scenario{
|
||||
{
|
||||
"valid case",
|
||||
"filename",
|
||||
"content",
|
||||
func(path string, err error) {
|
||||
assert.NoError(t, err)
|
||||
|
||||
content, err := ioutil.ReadFile(path)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, "content", string(content))
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
t.Run(s.testName, func(t *testing.T) {
|
||||
s.test(NewDummyOSCommand().CreateTempFile(s.filename, s.content))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
229
pkg/commands/patch_manager.go
Normal file
229
pkg/commands/patch_manager.go
Normal file
@@ -0,0 +1,229 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"sort"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type fileInfo struct {
|
||||
mode int // one of WHOLE/PART
|
||||
includedLineIndices []int
|
||||
diff string
|
||||
}
|
||||
|
||||
type applyPatchFunc func(patch string, flags ...string) error
|
||||
|
||||
// PatchManager manages the building of a patch for a commit to be applied to another commit (or the working tree, or removed from the current commit)
|
||||
type PatchManager struct {
|
||||
CommitSha string
|
||||
fileInfoMap map[string]*fileInfo
|
||||
Log *logrus.Entry
|
||||
ApplyPatch applyPatchFunc
|
||||
}
|
||||
|
||||
// NewPatchManager returns a new PatchModifier
|
||||
func NewPatchManager(log *logrus.Entry, applyPatch applyPatchFunc) *PatchManager {
|
||||
return &PatchManager{
|
||||
Log: log,
|
||||
ApplyPatch: applyPatch,
|
||||
}
|
||||
}
|
||||
|
||||
// NewPatchManager returns a new PatchModifier
|
||||
func (p *PatchManager) Start(commitSha string, diffMap map[string]string) {
|
||||
p.CommitSha = commitSha
|
||||
p.fileInfoMap = map[string]*fileInfo{}
|
||||
for filename, diff := range diffMap {
|
||||
p.fileInfoMap[filename] = &fileInfo{
|
||||
mode: UNSELECTED,
|
||||
diff: diff,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *PatchManager) AddFile(filename string) {
|
||||
p.fileInfoMap[filename].mode = WHOLE
|
||||
p.fileInfoMap[filename].includedLineIndices = nil
|
||||
}
|
||||
|
||||
func (p *PatchManager) RemoveFile(filename string) {
|
||||
p.fileInfoMap[filename].mode = UNSELECTED
|
||||
p.fileInfoMap[filename].includedLineIndices = nil
|
||||
}
|
||||
|
||||
func (p *PatchManager) ToggleFileWhole(filename string) {
|
||||
info := p.fileInfoMap[filename]
|
||||
switch info.mode {
|
||||
case UNSELECTED:
|
||||
p.AddFile(filename)
|
||||
case WHOLE:
|
||||
p.RemoveFile(filename)
|
||||
case PART:
|
||||
p.AddFile(filename)
|
||||
}
|
||||
}
|
||||
|
||||
func getIndicesForRange(first, last int) []int {
|
||||
indices := []int{}
|
||||
for i := first; i <= last; i++ {
|
||||
indices = append(indices, i)
|
||||
}
|
||||
return indices
|
||||
}
|
||||
|
||||
func (p *PatchManager) AddFileLineRange(filename string, firstLineIdx, lastLineIdx int) {
|
||||
info := p.fileInfoMap[filename]
|
||||
info.mode = PART
|
||||
info.includedLineIndices = utils.UnionInt(info.includedLineIndices, getIndicesForRange(firstLineIdx, lastLineIdx))
|
||||
}
|
||||
|
||||
func (p *PatchManager) RemoveFileLineRange(filename string, firstLineIdx, lastLineIdx int) {
|
||||
info := p.fileInfoMap[filename]
|
||||
info.mode = PART
|
||||
info.includedLineIndices = utils.DifferenceInt(info.includedLineIndices, getIndicesForRange(firstLineIdx, lastLineIdx))
|
||||
if len(info.includedLineIndices) == 0 {
|
||||
p.RemoveFile(filename)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *PatchManager) RenderPlainPatchForFile(filename string, reverse bool, keepOriginalHeader bool) string {
|
||||
info := p.fileInfoMap[filename]
|
||||
if info == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
switch info.mode {
|
||||
case WHOLE:
|
||||
// use the whole diff
|
||||
// the reverse flag is only for part patches so we're ignoring it here
|
||||
return info.diff
|
||||
case PART:
|
||||
// generate a new diff with just the selected lines
|
||||
m := NewPatchModifier(p.Log, filename, info.diff)
|
||||
return m.ModifiedPatchForLines(info.includedLineIndices, reverse, keepOriginalHeader)
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func (p *PatchManager) RenderPatchForFile(filename string, plain bool, reverse bool, keepOriginalHeader bool) string {
|
||||
patch := p.RenderPlainPatchForFile(filename, reverse, keepOriginalHeader)
|
||||
if plain {
|
||||
return patch
|
||||
}
|
||||
parser, err := NewPatchParser(p.Log, patch)
|
||||
if err != nil {
|
||||
// swallowing for now
|
||||
return ""
|
||||
}
|
||||
// not passing included lines because we don't want to see them in the secondary panel
|
||||
return parser.Render(-1, -1, nil)
|
||||
}
|
||||
|
||||
func (p *PatchManager) RenderEachFilePatch(plain bool) []string {
|
||||
// sort files by name then iterate through and render each patch
|
||||
filenames := make([]string, len(p.fileInfoMap))
|
||||
index := 0
|
||||
for filename := range p.fileInfoMap {
|
||||
filenames[index] = filename
|
||||
index++
|
||||
}
|
||||
|
||||
sort.Strings(filenames)
|
||||
output := []string{}
|
||||
for _, filename := range filenames {
|
||||
patch := p.RenderPatchForFile(filename, plain, false, true)
|
||||
if patch != "" {
|
||||
output = append(output, patch)
|
||||
}
|
||||
}
|
||||
|
||||
return output
|
||||
}
|
||||
|
||||
func (p *PatchManager) RenderAggregatedPatchColored(plain bool) string {
|
||||
result := ""
|
||||
for _, patch := range p.RenderEachFilePatch(plain) {
|
||||
if patch != "" {
|
||||
result += patch + "\n"
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (p *PatchManager) GetFileStatus(filename string) int {
|
||||
info := p.fileInfoMap[filename]
|
||||
if info == nil {
|
||||
return UNSELECTED
|
||||
}
|
||||
return info.mode
|
||||
}
|
||||
|
||||
func (p *PatchManager) GetFileIncLineIndices(filename string) []int {
|
||||
info := p.fileInfoMap[filename]
|
||||
if info == nil {
|
||||
return []int{}
|
||||
}
|
||||
return info.includedLineIndices
|
||||
}
|
||||
|
||||
func (p *PatchManager) ApplyPatches(reverse bool) error {
|
||||
// for whole patches we'll apply the patch in reverse
|
||||
// but for part patches we'll apply a reverse patch forwards
|
||||
for filename, info := range p.fileInfoMap {
|
||||
if info.mode == UNSELECTED {
|
||||
continue
|
||||
}
|
||||
|
||||
applyFlags := []string{"index", "3way"}
|
||||
reverseOnGenerate := false
|
||||
if reverse {
|
||||
if info.mode == WHOLE {
|
||||
applyFlags = append(applyFlags, "reverse")
|
||||
} else {
|
||||
reverseOnGenerate = true
|
||||
}
|
||||
}
|
||||
|
||||
var err error
|
||||
// first run we try with the original header, then without
|
||||
for _, keepOriginalHeader := range []bool{true, false} {
|
||||
patch := p.RenderPatchForFile(filename, true, reverseOnGenerate, keepOriginalHeader)
|
||||
if patch == "" {
|
||||
continue
|
||||
}
|
||||
if err = p.ApplyPatch(patch, applyFlags...); err != nil {
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// clears the patch
|
||||
func (p *PatchManager) Reset() {
|
||||
p.CommitSha = ""
|
||||
p.fileInfoMap = map[string]*fileInfo{}
|
||||
}
|
||||
|
||||
func (p *PatchManager) CommitSelected() bool {
|
||||
return p.CommitSha != ""
|
||||
}
|
||||
|
||||
func (p *PatchManager) IsEmpty() bool {
|
||||
for _, fileInfo := range p.fileInfoMap {
|
||||
if fileInfo.mode == WHOLE || (fileInfo.mode == PART && len(fileInfo.includedLineIndices) > 0) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
260
pkg/commands/patch_modifier.go
Normal file
260
pkg/commands/patch_modifier.go
Normal file
@@ -0,0 +1,260 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
var hunkHeaderRegexp = regexp.MustCompile(`(?m)^@@ -(\d+)[^\+]+\+(\d+)[^@]+@@(.*)$`)
|
||||
var patchHeaderRegexp = regexp.MustCompile(`(?ms)(^diff.*?)^@@`)
|
||||
|
||||
type PatchHunk struct {
|
||||
header string
|
||||
FirstLineIdx int
|
||||
LastLineIdx int
|
||||
bodyLines []string
|
||||
}
|
||||
|
||||
func newHunk(header string, body string, firstLineIdx int) *PatchHunk {
|
||||
bodyLines := strings.SplitAfter(header+body, "\n")[1:] // dropping the header line
|
||||
|
||||
return &PatchHunk{
|
||||
header: header,
|
||||
FirstLineIdx: firstLineIdx,
|
||||
LastLineIdx: firstLineIdx + len(bodyLines),
|
||||
bodyLines: bodyLines,
|
||||
}
|
||||
}
|
||||
|
||||
func (hunk *PatchHunk) updatedLines(lineIndices []int, reverse bool) []string {
|
||||
skippedNewlineMessageIndex := -1
|
||||
newLines := []string{}
|
||||
|
||||
lineIdx := hunk.FirstLineIdx
|
||||
for _, line := range hunk.bodyLines {
|
||||
lineIdx++ // incrementing at the start to skip the header line
|
||||
if line == "" {
|
||||
break
|
||||
}
|
||||
isLineSelected := utils.IncludesInt(lineIndices, lineIdx)
|
||||
|
||||
firstChar, content := line[:1], line[1:]
|
||||
transformedFirstChar := transformedFirstChar(firstChar, reverse, isLineSelected)
|
||||
|
||||
if isLineSelected || (transformedFirstChar == "\\" && skippedNewlineMessageIndex != lineIdx) || transformedFirstChar == " " {
|
||||
newLines = append(newLines, transformedFirstChar+content)
|
||||
continue
|
||||
}
|
||||
|
||||
if transformedFirstChar == "+" {
|
||||
// we don't want to include the 'newline at end of file' line if it involves an addition we're not including
|
||||
skippedNewlineMessageIndex = lineIdx + 1
|
||||
}
|
||||
}
|
||||
|
||||
return newLines
|
||||
}
|
||||
|
||||
func transformedFirstChar(firstChar string, reverse bool, isLineSelected bool) string {
|
||||
if reverse {
|
||||
if !isLineSelected && firstChar == "+" {
|
||||
return " "
|
||||
} else if firstChar == "-" {
|
||||
return "+"
|
||||
} else if firstChar == "+" {
|
||||
return "-"
|
||||
} else {
|
||||
return firstChar
|
||||
}
|
||||
}
|
||||
|
||||
if !isLineSelected && firstChar == "-" {
|
||||
return " "
|
||||
}
|
||||
|
||||
return firstChar
|
||||
}
|
||||
|
||||
func (hunk *PatchHunk) formatHeader(oldStart int, oldLength int, newStart int, newLength int, heading string) string {
|
||||
return fmt.Sprintf("@@ -%d,%d +%d,%d @@%s\n", oldStart, oldLength, newStart, newLength, heading)
|
||||
}
|
||||
|
||||
func (hunk *PatchHunk) formatWithChanges(lineIndices []int, reverse bool, startOffset int) (int, string) {
|
||||
bodyLines := hunk.updatedLines(lineIndices, reverse)
|
||||
startOffset, header, ok := hunk.updatedHeader(bodyLines, startOffset, reverse)
|
||||
if !ok {
|
||||
return startOffset, ""
|
||||
}
|
||||
return startOffset, header + strings.Join(bodyLines, "")
|
||||
}
|
||||
|
||||
func (hunk *PatchHunk) updatedHeader(newBodyLines []string, startOffset int, reverse bool) (int, string, bool) {
|
||||
changeCount := 0
|
||||
oldLength := 0
|
||||
newLength := 0
|
||||
for _, line := range newBodyLines {
|
||||
switch line[:1] {
|
||||
case "+":
|
||||
newLength++
|
||||
changeCount++
|
||||
case "-":
|
||||
oldLength++
|
||||
changeCount++
|
||||
case " ":
|
||||
oldLength++
|
||||
newLength++
|
||||
}
|
||||
}
|
||||
|
||||
if changeCount == 0 {
|
||||
// if nothing has changed we just return nothing
|
||||
return startOffset, "", false
|
||||
}
|
||||
|
||||
// get oldstart, newstart, and heading from header
|
||||
match := hunkHeaderRegexp.FindStringSubmatch(hunk.header)
|
||||
|
||||
var oldStart int
|
||||
if reverse {
|
||||
oldStart = mustConvertToInt(match[2])
|
||||
} else {
|
||||
oldStart = mustConvertToInt(match[1])
|
||||
}
|
||||
heading := match[3]
|
||||
|
||||
var newStartOffset int
|
||||
// if the hunk went from zero to positive length, we need to increment the starting point by one
|
||||
// if the hunk went from positive to zero length, we need to decrement the starting point by one
|
||||
if oldLength == 0 {
|
||||
newStartOffset = 1
|
||||
} else if newLength == 0 {
|
||||
newStartOffset = -1
|
||||
} else {
|
||||
newStartOffset = 0
|
||||
}
|
||||
|
||||
newStart := oldStart + startOffset + newStartOffset
|
||||
|
||||
newStartOffset = startOffset + newLength - oldLength
|
||||
formattedHeader := hunk.formatHeader(oldStart, oldLength, newStart, newLength, heading)
|
||||
return newStartOffset, formattedHeader, true
|
||||
}
|
||||
|
||||
func mustConvertToInt(s string) int {
|
||||
i, err := strconv.Atoi(s)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return i
|
||||
}
|
||||
|
||||
func GetHeaderFromDiff(diff string) string {
|
||||
match := patchHeaderRegexp.FindStringSubmatch(diff)
|
||||
if len(match) <= 1 {
|
||||
return ""
|
||||
}
|
||||
return match[1]
|
||||
}
|
||||
|
||||
func GetHunksFromDiff(diff string) []*PatchHunk {
|
||||
headers := hunkHeaderRegexp.FindAllString(diff, -1)
|
||||
bodies := hunkHeaderRegexp.Split(diff, -1)[1:] // discarding top bit
|
||||
|
||||
headerFirstLineIndices := []int{}
|
||||
for lineIdx, line := range strings.Split(diff, "\n") {
|
||||
if strings.HasPrefix(line, "@@ -") {
|
||||
headerFirstLineIndices = append(headerFirstLineIndices, lineIdx)
|
||||
}
|
||||
}
|
||||
|
||||
hunks := make([]*PatchHunk, len(headers))
|
||||
for index, header := range headers {
|
||||
hunks[index] = newHunk(header, bodies[index], headerFirstLineIndices[index])
|
||||
}
|
||||
|
||||
return hunks
|
||||
}
|
||||
|
||||
type PatchModifier struct {
|
||||
Log *logrus.Entry
|
||||
filename string
|
||||
hunks []*PatchHunk
|
||||
header string
|
||||
}
|
||||
|
||||
func NewPatchModifier(log *logrus.Entry, filename string, diffText string) *PatchModifier {
|
||||
return &PatchModifier{
|
||||
Log: log,
|
||||
filename: filename,
|
||||
hunks: GetHunksFromDiff(diffText),
|
||||
header: GetHeaderFromDiff(diffText),
|
||||
}
|
||||
}
|
||||
|
||||
func (d *PatchModifier) ModifiedPatchForLines(lineIndices []int, reverse bool, keepOriginalHeader bool) string {
|
||||
// step one is getting only those hunks which we care about
|
||||
hunksInRange := []*PatchHunk{}
|
||||
outer:
|
||||
for _, hunk := range d.hunks {
|
||||
// if there is any line in our lineIndices array that the hunk contains, we append it
|
||||
for _, lineIdx := range lineIndices {
|
||||
if lineIdx >= hunk.FirstLineIdx && lineIdx <= hunk.LastLineIdx {
|
||||
hunksInRange = append(hunksInRange, hunk)
|
||||
continue outer
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// step 2 is collecting all the hunks with new headers
|
||||
startOffset := 0
|
||||
formattedHunks := ""
|
||||
var formattedHunk string
|
||||
for _, hunk := range hunksInRange {
|
||||
startOffset, formattedHunk = hunk.formatWithChanges(lineIndices, reverse, startOffset)
|
||||
formattedHunks += formattedHunk
|
||||
}
|
||||
|
||||
if formattedHunks == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
var fileHeader string
|
||||
// for staging/unstaging lines we don't want the original header because
|
||||
// it makes git confused e.g. when dealing with deleted/added files
|
||||
// but with building and applying patches the original header gives git
|
||||
// information it needs to cleanly apply patches
|
||||
if keepOriginalHeader {
|
||||
fileHeader = d.header
|
||||
} else {
|
||||
fileHeader = fmt.Sprintf("--- a/%s\n+++ b/%s\n", d.filename, d.filename)
|
||||
}
|
||||
|
||||
return fileHeader + formattedHunks
|
||||
}
|
||||
|
||||
func (d *PatchModifier) ModifiedPatchForRange(firstLineIdx int, lastLineIdx int, reverse bool, keepOriginalHeader bool) string {
|
||||
// generate array of consecutive line indices from our range
|
||||
selectedLines := []int{}
|
||||
for i := firstLineIdx; i <= lastLineIdx; i++ {
|
||||
selectedLines = append(selectedLines, i)
|
||||
}
|
||||
return d.ModifiedPatchForLines(selectedLines, reverse, keepOriginalHeader)
|
||||
}
|
||||
|
||||
func (d *PatchModifier) OriginalPatchLength() int {
|
||||
if len(d.hunks) == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
return d.hunks[len(d.hunks)-1].LastLineIdx
|
||||
}
|
||||
|
||||
func ModifiedPatchForRange(log *logrus.Entry, filename string, diffText string, firstLineIdx int, lastLineIdx int, reverse bool, keepOriginalHeader bool) string {
|
||||
p := NewPatchModifier(log, filename, diffText)
|
||||
return p.ModifiedPatchForRange(firstLineIdx, lastLineIdx, reverse, keepOriginalHeader)
|
||||
}
|
||||
511
pkg/commands/patch_modifier_test.go
Normal file
511
pkg/commands/patch_modifier_test.go
Normal file
@@ -0,0 +1,511 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
const simpleDiff = `diff --git a/filename b/filename
|
||||
index dcd3485..1ba5540 100644
|
||||
--- a/filename
|
||||
+++ b/filename
|
||||
@@ -1,5 +1,5 @@
|
||||
apple
|
||||
-orange
|
||||
+grape
|
||||
...
|
||||
...
|
||||
...
|
||||
`
|
||||
|
||||
const addNewlineToEndOfFile = `diff --git a/filename b/filename
|
||||
index 80a73f1..e48a11c 100644
|
||||
--- a/filename
|
||||
+++ b/filename
|
||||
@@ -60,4 +60,4 @@ grape
|
||||
...
|
||||
...
|
||||
...
|
||||
-last line
|
||||
\ No newline at end of file
|
||||
+last line
|
||||
`
|
||||
|
||||
const removeNewlinefromEndOfFile = `diff --git a/filename b/filename
|
||||
index e48a11c..80a73f1 100644
|
||||
--- a/filename
|
||||
+++ b/filename
|
||||
@@ -60,4 +60,4 @@ grape
|
||||
...
|
||||
...
|
||||
...
|
||||
-last line
|
||||
+last line
|
||||
\ No newline at end of file
|
||||
`
|
||||
|
||||
const twoHunks = `diff --git a/filename b/filename
|
||||
index e48a11c..b2ab81b 100644
|
||||
--- a/filename
|
||||
+++ b/filename
|
||||
@@ -1,5 +1,5 @@
|
||||
apple
|
||||
-grape
|
||||
+orange
|
||||
...
|
||||
...
|
||||
...
|
||||
@@ -8,6 +8,8 @@ grape
|
||||
...
|
||||
...
|
||||
...
|
||||
+pear
|
||||
+lemon
|
||||
...
|
||||
...
|
||||
...
|
||||
`
|
||||
|
||||
const newFile = `diff --git a/newfile b/newfile
|
||||
new file mode 100644
|
||||
index 0000000..4e680cc
|
||||
--- /dev/null
|
||||
+++ b/newfile
|
||||
@@ -0,0 +1,3 @@
|
||||
+apple
|
||||
+orange
|
||||
+grape
|
||||
`
|
||||
|
||||
const addNewlineToPreviouslyEmptyFile = `diff --git a/newfile b/newfile
|
||||
index e69de29..c6568ea 100644
|
||||
--- a/newfile
|
||||
+++ b/newfile
|
||||
@@ -0,0 +1 @@
|
||||
+new line
|
||||
\ No newline at end of file
|
||||
`
|
||||
|
||||
// TestModifyPatchForRange is a function.
|
||||
func TestModifyPatchForRange(t *testing.T) {
|
||||
type scenario struct {
|
||||
testName string
|
||||
filename string
|
||||
diffText string
|
||||
firstLineIndex int
|
||||
lastLineIndex int
|
||||
reverse bool
|
||||
expected string
|
||||
}
|
||||
|
||||
scenarios := []scenario{
|
||||
{
|
||||
testName: "nothing selected",
|
||||
filename: "filename",
|
||||
firstLineIndex: -1,
|
||||
lastLineIndex: -1,
|
||||
reverse: false,
|
||||
diffText: simpleDiff,
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
testName: "only context selected",
|
||||
filename: "filename",
|
||||
firstLineIndex: 5,
|
||||
lastLineIndex: 5,
|
||||
reverse: false,
|
||||
diffText: simpleDiff,
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
testName: "whole range selected",
|
||||
filename: "filename",
|
||||
firstLineIndex: 0,
|
||||
lastLineIndex: 11,
|
||||
reverse: false,
|
||||
diffText: simpleDiff,
|
||||
expected: `--- a/filename
|
||||
+++ b/filename
|
||||
@@ -1,5 +1,5 @@
|
||||
apple
|
||||
-orange
|
||||
+grape
|
||||
...
|
||||
...
|
||||
...
|
||||
`,
|
||||
},
|
||||
{
|
||||
testName: "only removal selected",
|
||||
filename: "filename",
|
||||
firstLineIndex: 6,
|
||||
lastLineIndex: 6,
|
||||
reverse: false,
|
||||
diffText: simpleDiff,
|
||||
expected: `--- a/filename
|
||||
+++ b/filename
|
||||
@@ -1,5 +1,4 @@
|
||||
apple
|
||||
-orange
|
||||
...
|
||||
...
|
||||
...
|
||||
`,
|
||||
},
|
||||
{
|
||||
testName: "only addition selected",
|
||||
filename: "filename",
|
||||
firstLineIndex: 7,
|
||||
lastLineIndex: 7,
|
||||
reverse: false,
|
||||
diffText: simpleDiff,
|
||||
expected: `--- a/filename
|
||||
+++ b/filename
|
||||
@@ -1,5 +1,6 @@
|
||||
apple
|
||||
orange
|
||||
+grape
|
||||
...
|
||||
...
|
||||
...
|
||||
`,
|
||||
},
|
||||
{
|
||||
testName: "range that extends beyond diff bounds",
|
||||
filename: "filename",
|
||||
firstLineIndex: -100,
|
||||
lastLineIndex: 100,
|
||||
reverse: false,
|
||||
diffText: simpleDiff,
|
||||
expected: `--- a/filename
|
||||
+++ b/filename
|
||||
@@ -1,5 +1,5 @@
|
||||
apple
|
||||
-orange
|
||||
+grape
|
||||
...
|
||||
...
|
||||
...
|
||||
`,
|
||||
},
|
||||
{
|
||||
testName: "whole range reversed",
|
||||
filename: "filename",
|
||||
firstLineIndex: 0,
|
||||
lastLineIndex: 11,
|
||||
reverse: true,
|
||||
diffText: simpleDiff,
|
||||
expected: `--- a/filename
|
||||
+++ b/filename
|
||||
@@ -1,5 +1,5 @@
|
||||
apple
|
||||
+orange
|
||||
-grape
|
||||
...
|
||||
...
|
||||
...
|
||||
`,
|
||||
},
|
||||
{
|
||||
testName: "removal reversed",
|
||||
filename: "filename",
|
||||
firstLineIndex: 6,
|
||||
lastLineIndex: 6,
|
||||
reverse: true,
|
||||
diffText: simpleDiff,
|
||||
expected: `--- a/filename
|
||||
+++ b/filename
|
||||
@@ -1,5 +1,6 @@
|
||||
apple
|
||||
+orange
|
||||
grape
|
||||
...
|
||||
...
|
||||
...
|
||||
`,
|
||||
},
|
||||
{
|
||||
testName: "removal reversed",
|
||||
filename: "filename",
|
||||
firstLineIndex: 7,
|
||||
lastLineIndex: 7,
|
||||
reverse: true,
|
||||
diffText: simpleDiff,
|
||||
expected: `--- a/filename
|
||||
+++ b/filename
|
||||
@@ -1,5 +1,4 @@
|
||||
apple
|
||||
-grape
|
||||
...
|
||||
...
|
||||
...
|
||||
`,
|
||||
},
|
||||
{
|
||||
testName: "add newline to end of file",
|
||||
filename: "filename",
|
||||
firstLineIndex: -100,
|
||||
lastLineIndex: 100,
|
||||
reverse: false,
|
||||
diffText: addNewlineToEndOfFile,
|
||||
expected: `--- a/filename
|
||||
+++ b/filename
|
||||
@@ -60,4 +60,4 @@ grape
|
||||
...
|
||||
...
|
||||
...
|
||||
-last line
|
||||
\ No newline at end of file
|
||||
+last line
|
||||
`,
|
||||
},
|
||||
{
|
||||
testName: "add newline to end of file, addition only",
|
||||
filename: "filename",
|
||||
firstLineIndex: 8,
|
||||
lastLineIndex: 8,
|
||||
reverse: true,
|
||||
diffText: addNewlineToEndOfFile,
|
||||
expected: `--- a/filename
|
||||
+++ b/filename
|
||||
@@ -60,4 +60,5 @@ grape
|
||||
...
|
||||
...
|
||||
...
|
||||
+last line
|
||||
\ No newline at end of file
|
||||
last line
|
||||
`,
|
||||
},
|
||||
{
|
||||
testName: "add newline to end of file, removal only",
|
||||
filename: "filename",
|
||||
firstLineIndex: 10,
|
||||
lastLineIndex: 10,
|
||||
reverse: true,
|
||||
diffText: addNewlineToEndOfFile,
|
||||
expected: `--- a/filename
|
||||
+++ b/filename
|
||||
@@ -60,4 +60,3 @@ grape
|
||||
...
|
||||
...
|
||||
...
|
||||
-last line
|
||||
`,
|
||||
},
|
||||
{
|
||||
testName: "remove newline from end of file",
|
||||
filename: "filename",
|
||||
firstLineIndex: -100,
|
||||
lastLineIndex: 100,
|
||||
reverse: false,
|
||||
diffText: removeNewlinefromEndOfFile,
|
||||
expected: `--- a/filename
|
||||
+++ b/filename
|
||||
@@ -60,4 +60,4 @@ grape
|
||||
...
|
||||
...
|
||||
...
|
||||
-last line
|
||||
+last line
|
||||
\ No newline at end of file
|
||||
`,
|
||||
},
|
||||
{
|
||||
testName: "remove newline from end of file, removal only",
|
||||
filename: "filename",
|
||||
firstLineIndex: 8,
|
||||
lastLineIndex: 8,
|
||||
reverse: false,
|
||||
diffText: removeNewlinefromEndOfFile,
|
||||
expected: `--- a/filename
|
||||
+++ b/filename
|
||||
@@ -60,4 +60,3 @@ grape
|
||||
...
|
||||
...
|
||||
...
|
||||
-last line
|
||||
`,
|
||||
},
|
||||
{
|
||||
testName: "remove newline from end of file, addition only",
|
||||
filename: "filename",
|
||||
firstLineIndex: 9,
|
||||
lastLineIndex: 9,
|
||||
reverse: false,
|
||||
diffText: removeNewlinefromEndOfFile,
|
||||
expected: `--- a/filename
|
||||
+++ b/filename
|
||||
@@ -60,4 +60,5 @@ grape
|
||||
...
|
||||
...
|
||||
...
|
||||
last line
|
||||
+last line
|
||||
\ No newline at end of file
|
||||
`,
|
||||
},
|
||||
{
|
||||
testName: "staging two whole hunks",
|
||||
filename: "filename",
|
||||
firstLineIndex: -100,
|
||||
lastLineIndex: 100,
|
||||
reverse: false,
|
||||
diffText: twoHunks,
|
||||
expected: `--- a/filename
|
||||
+++ b/filename
|
||||
@@ -1,5 +1,5 @@
|
||||
apple
|
||||
-grape
|
||||
+orange
|
||||
...
|
||||
...
|
||||
...
|
||||
@@ -8,6 +8,8 @@ grape
|
||||
...
|
||||
...
|
||||
...
|
||||
+pear
|
||||
+lemon
|
||||
...
|
||||
...
|
||||
...
|
||||
`,
|
||||
},
|
||||
{
|
||||
testName: "staging part of both hunks",
|
||||
filename: "filename",
|
||||
firstLineIndex: 7,
|
||||
lastLineIndex: 15,
|
||||
reverse: false,
|
||||
diffText: twoHunks,
|
||||
expected: `--- a/filename
|
||||
+++ b/filename
|
||||
@@ -1,5 +1,6 @@
|
||||
apple
|
||||
grape
|
||||
+orange
|
||||
...
|
||||
...
|
||||
...
|
||||
@@ -8,6 +9,7 @@ grape
|
||||
...
|
||||
...
|
||||
...
|
||||
+pear
|
||||
...
|
||||
...
|
||||
...
|
||||
`,
|
||||
},
|
||||
{
|
||||
testName: "staging part of both hunks, reversed",
|
||||
filename: "filename",
|
||||
firstLineIndex: 7,
|
||||
lastLineIndex: 15,
|
||||
reverse: true,
|
||||
diffText: twoHunks,
|
||||
expected: `--- a/filename
|
||||
+++ b/filename
|
||||
@@ -1,5 +1,4 @@
|
||||
apple
|
||||
-orange
|
||||
...
|
||||
...
|
||||
...
|
||||
@@ -8,8 +7,7 @@ grape
|
||||
...
|
||||
...
|
||||
...
|
||||
-pear
|
||||
lemon
|
||||
...
|
||||
...
|
||||
...
|
||||
`,
|
||||
},
|
||||
{
|
||||
testName: "adding a new file",
|
||||
filename: "newfile",
|
||||
firstLineIndex: -100,
|
||||
lastLineIndex: 100,
|
||||
reverse: false,
|
||||
diffText: newFile,
|
||||
expected: `--- a/newfile
|
||||
+++ b/newfile
|
||||
@@ -0,0 +1,3 @@
|
||||
+apple
|
||||
+orange
|
||||
+grape
|
||||
`,
|
||||
},
|
||||
{
|
||||
testName: "adding part of a new file",
|
||||
filename: "newfile",
|
||||
firstLineIndex: 6,
|
||||
lastLineIndex: 7,
|
||||
reverse: false,
|
||||
diffText: newFile,
|
||||
expected: `--- a/newfile
|
||||
+++ b/newfile
|
||||
@@ -0,0 +1,2 @@
|
||||
+apple
|
||||
+orange
|
||||
`,
|
||||
},
|
||||
{
|
||||
testName: "adding a new file, reversed",
|
||||
filename: "newfile",
|
||||
firstLineIndex: -100,
|
||||
lastLineIndex: 100,
|
||||
reverse: true,
|
||||
diffText: newFile,
|
||||
expected: `--- a/newfile
|
||||
+++ b/newfile
|
||||
@@ -1,3 +0,0 @@
|
||||
-apple
|
||||
-orange
|
||||
-grape
|
||||
`,
|
||||
},
|
||||
{
|
||||
testName: "adding a new line to a previously empty file",
|
||||
filename: "newfile",
|
||||
firstLineIndex: -100,
|
||||
lastLineIndex: 100,
|
||||
reverse: false,
|
||||
diffText: addNewlineToPreviouslyEmptyFile,
|
||||
expected: `--- a/newfile
|
||||
+++ b/newfile
|
||||
@@ -0,0 +1,1 @@
|
||||
+new line
|
||||
\ No newline at end of file
|
||||
`,
|
||||
},
|
||||
{
|
||||
testName: "adding a new line to a previously empty file, reversed",
|
||||
filename: "newfile",
|
||||
firstLineIndex: -100,
|
||||
lastLineIndex: 100,
|
||||
reverse: true,
|
||||
diffText: addNewlineToPreviouslyEmptyFile,
|
||||
expected: `--- a/newfile
|
||||
+++ b/newfile
|
||||
@@ -1,1 +0,0 @@
|
||||
-new line
|
||||
\ No newline at end of file
|
||||
`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
t.Run(s.testName, func(t *testing.T) {
|
||||
result := ModifiedPatchForRange(nil, s.filename, s.diffText, s.firstLineIndex, s.lastLineIndex, s.reverse, false)
|
||||
if !assert.Equal(t, s.expected, result) {
|
||||
fmt.Println(result)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
213
pkg/commands/patch_parser.go
Normal file
213
pkg/commands/patch_parser.go
Normal file
@@ -0,0 +1,213 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/jesseduffield/lazygit/pkg/theme"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const (
|
||||
PATCH_HEADER = iota
|
||||
COMMIT_SHA
|
||||
COMMIT_DESCRIPTION
|
||||
HUNK_HEADER
|
||||
ADDITION
|
||||
DELETION
|
||||
CONTEXT
|
||||
NEWLINE_MESSAGE
|
||||
)
|
||||
|
||||
// the job of this file is to parse a diff, find out where the hunks begin and end, which lines are stageable, and how to find the next hunk from the current position or the next stageable line from the current position.
|
||||
|
||||
type PatchLine struct {
|
||||
Kind int
|
||||
Content string // something like '+ hello' (note the first character is not removed)
|
||||
}
|
||||
|
||||
type PatchParser struct {
|
||||
Log *logrus.Entry
|
||||
PatchLines []*PatchLine
|
||||
PatchHunks []*PatchHunk
|
||||
HunkStarts []int
|
||||
StageableLines []int // rename to mention we're talking about indexes
|
||||
}
|
||||
|
||||
// NewPatchParser builds a new branch list builder
|
||||
func NewPatchParser(log *logrus.Entry, patch string) (*PatchParser, error) {
|
||||
hunkStarts, stageableLines, patchLines, err := parsePatch(patch)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
patchHunks := GetHunksFromDiff(patch)
|
||||
|
||||
return &PatchParser{
|
||||
Log: log,
|
||||
HunkStarts: hunkStarts, // deprecated
|
||||
StageableLines: stageableLines,
|
||||
PatchLines: patchLines,
|
||||
PatchHunks: patchHunks,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetHunkContainingLine takes a line index and an offset and finds the hunk
|
||||
// which contains the line index, then returns the hunk considering the offset.
|
||||
// e.g. if the offset is 1 it will return the next hunk.
|
||||
func (p *PatchParser) GetHunkContainingLine(lineIndex int, offset int) *PatchHunk {
|
||||
if len(p.PatchHunks) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
for index, hunk := range p.PatchHunks {
|
||||
if lineIndex >= hunk.FirstLineIdx && lineIndex <= hunk.LastLineIdx {
|
||||
resultIndex := index + offset
|
||||
if resultIndex < 0 {
|
||||
resultIndex = 0
|
||||
} else if resultIndex > len(p.PatchHunks)-1 {
|
||||
resultIndex = len(p.PatchHunks) - 1
|
||||
}
|
||||
return p.PatchHunks[resultIndex]
|
||||
}
|
||||
}
|
||||
|
||||
// if your cursor is past the last hunk, select the last hunk
|
||||
if lineIndex > p.PatchHunks[len(p.PatchHunks)-1].LastLineIdx {
|
||||
return p.PatchHunks[len(p.PatchHunks)-1]
|
||||
}
|
||||
|
||||
// otherwise select the first
|
||||
return p.PatchHunks[0]
|
||||
}
|
||||
|
||||
// selected means you've got it highlighted with your cursor
|
||||
// included means the line has been included in the patch (only applicable when
|
||||
// building a patch)
|
||||
func (l *PatchLine) render(selected bool, included bool) string {
|
||||
content := l.Content
|
||||
if len(content) == 0 {
|
||||
content = " " // using the space so that we can still highlight if necessary
|
||||
}
|
||||
|
||||
// for hunk headers we need to start off cyan and then use white for the message
|
||||
if l.Kind == HUNK_HEADER {
|
||||
re := regexp.MustCompile("(@@.*?@@)(.*)")
|
||||
match := re.FindStringSubmatch(content)
|
||||
return coloredString(color.FgCyan, match[1], selected, included) + coloredString(theme.DefaultTextColor, match[2], selected, false)
|
||||
}
|
||||
|
||||
var colorAttr color.Attribute
|
||||
switch l.Kind {
|
||||
case PATCH_HEADER:
|
||||
colorAttr = color.Bold
|
||||
case ADDITION:
|
||||
colorAttr = color.FgGreen
|
||||
case DELETION:
|
||||
colorAttr = color.FgRed
|
||||
case COMMIT_SHA:
|
||||
colorAttr = color.FgYellow
|
||||
default:
|
||||
colorAttr = theme.DefaultTextColor
|
||||
}
|
||||
|
||||
return coloredString(colorAttr, content, selected, included)
|
||||
}
|
||||
|
||||
func coloredString(colorAttr color.Attribute, str string, selected bool, included bool) string {
|
||||
var cl *color.Color
|
||||
attributes := []color.Attribute{colorAttr}
|
||||
if selected {
|
||||
attributes = append(attributes, color.BgBlue)
|
||||
}
|
||||
cl = color.New(attributes...)
|
||||
var clIncluded *color.Color
|
||||
if included {
|
||||
clIncluded = color.New(append(attributes, color.BgGreen)...)
|
||||
} else {
|
||||
clIncluded = color.New(attributes...)
|
||||
}
|
||||
|
||||
if len(str) < 2 {
|
||||
return utils.ColoredStringDirect(str, clIncluded)
|
||||
}
|
||||
|
||||
return utils.ColoredStringDirect(str[:1], clIncluded) + utils.ColoredStringDirect(str[1:], cl)
|
||||
}
|
||||
|
||||
func parsePatch(patch string) ([]int, []int, []*PatchLine, error) {
|
||||
lines := strings.Split(patch, "\n")
|
||||
hunkStarts := []int{}
|
||||
stageableLines := []int{}
|
||||
pastFirstHunkHeader := false
|
||||
pastCommitDescription := true
|
||||
patchLines := make([]*PatchLine, len(lines))
|
||||
var lineKind int
|
||||
var firstChar string
|
||||
for index, line := range lines {
|
||||
firstChar = " "
|
||||
if len(line) > 0 {
|
||||
firstChar = line[:1]
|
||||
}
|
||||
if index == 0 && strings.HasPrefix(line, "commit") {
|
||||
lineKind = COMMIT_SHA
|
||||
pastCommitDescription = false
|
||||
} else if !pastCommitDescription {
|
||||
if strings.HasPrefix(line, "diff") || strings.HasPrefix(line, "---") {
|
||||
pastCommitDescription = true
|
||||
lineKind = PATCH_HEADER
|
||||
} else {
|
||||
lineKind = COMMIT_DESCRIPTION
|
||||
}
|
||||
} else if firstChar == "@" {
|
||||
pastFirstHunkHeader = true
|
||||
hunkStarts = append(hunkStarts, index)
|
||||
lineKind = HUNK_HEADER
|
||||
} else if pastFirstHunkHeader {
|
||||
switch firstChar {
|
||||
case "-":
|
||||
lineKind = DELETION
|
||||
stageableLines = append(stageableLines, index)
|
||||
case "+":
|
||||
lineKind = ADDITION
|
||||
stageableLines = append(stageableLines, index)
|
||||
case "\\":
|
||||
lineKind = NEWLINE_MESSAGE
|
||||
case " ":
|
||||
lineKind = CONTEXT
|
||||
}
|
||||
} else {
|
||||
lineKind = PATCH_HEADER
|
||||
}
|
||||
patchLines[index] = &PatchLine{Kind: lineKind, Content: line}
|
||||
}
|
||||
return hunkStarts, stageableLines, patchLines, nil
|
||||
}
|
||||
|
||||
// Render returns the coloured string of the diff with any selected lines highlighted
|
||||
func (p *PatchParser) Render(firstLineIndex int, lastLineIndex int, incLineIndices []int) string {
|
||||
renderedLines := make([]string, len(p.PatchLines))
|
||||
for index, patchLine := range p.PatchLines {
|
||||
selected := index >= firstLineIndex && index <= lastLineIndex
|
||||
included := utils.IncludesInt(incLineIndices, index)
|
||||
renderedLines[index] = patchLine.render(selected, included)
|
||||
}
|
||||
result := strings.Join(renderedLines, "\n")
|
||||
if strings.TrimSpace(utils.Decolorise(result)) == "" {
|
||||
return ""
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// GetNextStageableLineIndex takes a line index and returns the line index of the next stageable line
|
||||
// note this will actually include the current index if it is stageable
|
||||
func (p *PatchParser) GetNextStageableLineIndex(currentIndex int) int {
|
||||
for _, lineIndex := range p.StageableLines {
|
||||
if lineIndex >= currentIndex {
|
||||
return lineIndex
|
||||
}
|
||||
}
|
||||
return p.StageableLines[len(p.StageableLines)-1]
|
||||
}
|
||||
169
pkg/commands/patch_rebases.go
Normal file
169
pkg/commands/patch_rebases.go
Normal file
@@ -0,0 +1,169 @@
|
||||
package commands
|
||||
|
||||
import "github.com/go-errors/errors"
|
||||
|
||||
// DeletePatchesFromCommit applies a patch in reverse for a commit
|
||||
func (c *GitCommand) DeletePatchesFromCommit(commits []*Commit, commitIndex int, p *PatchManager) error {
|
||||
if err := c.BeginInteractiveRebaseForCommit(commits, commitIndex); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// apply each patch in reverse
|
||||
if err := p.ApplyPatches(true); err != nil {
|
||||
if err := c.GenericMerge("rebase", "abort"); err != nil {
|
||||
return err
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// time to amend the selected commit
|
||||
if _, err := c.AmendHead(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.onSuccessfulContinue = func() error {
|
||||
c.PatchManager.Reset()
|
||||
return nil
|
||||
}
|
||||
|
||||
// continue
|
||||
return c.GenericMerge("rebase", "continue")
|
||||
}
|
||||
|
||||
func (c *GitCommand) MovePatchToSelectedCommit(commits []*Commit, sourceCommitIdx int, destinationCommitIdx int, p *PatchManager) error {
|
||||
if sourceCommitIdx < destinationCommitIdx {
|
||||
if err := c.BeginInteractiveRebaseForCommit(commits, destinationCommitIdx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// apply each patch forward
|
||||
if err := p.ApplyPatches(false); err != nil {
|
||||
if err := c.GenericMerge("rebase", "abort"); err != nil {
|
||||
return err
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// amend the destination commit
|
||||
if _, err := c.AmendHead(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.onSuccessfulContinue = func() error {
|
||||
c.PatchManager.Reset()
|
||||
return nil
|
||||
}
|
||||
|
||||
// continue
|
||||
return c.GenericMerge("rebase", "continue")
|
||||
}
|
||||
|
||||
if len(commits)-1 < sourceCommitIdx {
|
||||
return errors.New("index outside of range of commits")
|
||||
}
|
||||
|
||||
// we can make this GPG thing possible it just means we need to do this in two parts:
|
||||
// one where we handle the possibility of a credential request, and the other
|
||||
// where we continue the rebase
|
||||
if c.usingGpg() {
|
||||
return errors.New(c.Tr.SLocalize("DisabledForGPG"))
|
||||
}
|
||||
|
||||
baseIndex := sourceCommitIdx + 1
|
||||
todo := ""
|
||||
for i, commit := range commits[0:baseIndex] {
|
||||
a := "pick"
|
||||
if i == sourceCommitIdx || i == destinationCommitIdx {
|
||||
a = "edit"
|
||||
}
|
||||
todo = a + " " + commit.Sha + " " + commit.Name + "\n" + todo
|
||||
}
|
||||
|
||||
cmd, err := c.PrepareInteractiveRebaseCommand(commits[baseIndex].Sha, todo, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := c.OSCommand.RunPreparedCommand(cmd); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// apply each patch in reverse
|
||||
if err := p.ApplyPatches(true); err != nil {
|
||||
if err := c.GenericMerge("rebase", "abort"); err != nil {
|
||||
return err
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// amend the source commit
|
||||
if _, err := c.AmendHead(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if c.onSuccessfulContinue != nil {
|
||||
return errors.New("You are midway through another rebase operation. Please abort to start again")
|
||||
}
|
||||
|
||||
c.onSuccessfulContinue = func() error {
|
||||
// now we should be up to the destination, so let's apply forward these patches to that.
|
||||
// ideally we would ensure we're on the right commit but I'm not sure if that check is necessary
|
||||
if err := p.ApplyPatches(false); err != nil {
|
||||
if err := c.GenericMerge("rebase", "abort"); err != nil {
|
||||
return err
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// amend the destination commit
|
||||
if _, err := c.AmendHead(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.onSuccessfulContinue = func() error {
|
||||
c.PatchManager.Reset()
|
||||
return nil
|
||||
}
|
||||
|
||||
return c.GenericMerge("rebase", "continue")
|
||||
}
|
||||
|
||||
return c.GenericMerge("rebase", "continue")
|
||||
}
|
||||
|
||||
func (c *GitCommand) PullPatchIntoIndex(commits []*Commit, commitIdx int, p *PatchManager) error {
|
||||
if err := c.BeginInteractiveRebaseForCommit(commits, commitIdx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := p.ApplyPatches(true); err != nil {
|
||||
if err := c.GenericMerge("rebase", "abort"); err != nil {
|
||||
return err
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// amend the commit
|
||||
if _, err := c.AmendHead(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if c.onSuccessfulContinue != nil {
|
||||
return errors.New("You are midway through another rebase operation. Please abort to start again")
|
||||
}
|
||||
|
||||
c.onSuccessfulContinue = func() error {
|
||||
// add patches to index
|
||||
if err := p.ApplyPatches(false); err != nil {
|
||||
if err := c.GenericMerge("rebase", "abort"); err != nil {
|
||||
return err
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
c.PatchManager.Reset()
|
||||
return nil
|
||||
}
|
||||
|
||||
return c.GenericMerge("rebase", "continue")
|
||||
}
|
||||
106
pkg/commands/pull_request.go
Normal file
106
pkg/commands/pull_request.go
Normal file
@@ -0,0 +1,106 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/go-errors/errors"
|
||||
)
|
||||
|
||||
// Service is a service that repository is on (Github, Bitbucket, ...)
|
||||
type Service struct {
|
||||
Name string
|
||||
PullRequestURL string
|
||||
}
|
||||
|
||||
// PullRequest opens a link in browser to create new pull request
|
||||
// with selected branch
|
||||
type PullRequest struct {
|
||||
GitServices []*Service
|
||||
GitCommand *GitCommand
|
||||
}
|
||||
|
||||
// RepoInformation holds some basic information about the repo
|
||||
type RepoInformation struct {
|
||||
Owner string
|
||||
Repository string
|
||||
}
|
||||
|
||||
func getServices() []*Service {
|
||||
return []*Service{
|
||||
{
|
||||
Name: "github.com",
|
||||
PullRequestURL: "https://github.com/%s/%s/compare/%s?expand=1",
|
||||
},
|
||||
{
|
||||
Name: "bitbucket.org",
|
||||
PullRequestURL: "https://bitbucket.org/%s/%s/pull-requests/new?source=%s&t=1",
|
||||
},
|
||||
{
|
||||
Name: "gitlab.com",
|
||||
PullRequestURL: "https://gitlab.com/%s/%s/merge_requests/new?merge_request[source_branch]=%s",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// NewPullRequest creates new instance of PullRequest
|
||||
func NewPullRequest(gitCommand *GitCommand) *PullRequest {
|
||||
return &PullRequest{
|
||||
GitServices: getServices(),
|
||||
GitCommand: gitCommand,
|
||||
}
|
||||
}
|
||||
|
||||
// Create opens link to new pull request in browser
|
||||
func (pr *PullRequest) Create(branch *Branch) error {
|
||||
branchExistsOnRemote := pr.GitCommand.CheckRemoteBranchExists(branch)
|
||||
|
||||
if !branchExistsOnRemote {
|
||||
return errors.New(pr.GitCommand.Tr.SLocalize("NoBranchOnRemote"))
|
||||
}
|
||||
|
||||
repoURL := pr.GitCommand.GetRemoteURL()
|
||||
var gitService *Service
|
||||
|
||||
for _, service := range pr.GitServices {
|
||||
if strings.Contains(repoURL, service.Name) {
|
||||
gitService = service
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if gitService == nil {
|
||||
return errors.New(pr.GitCommand.Tr.SLocalize("UnsupportedGitService"))
|
||||
}
|
||||
|
||||
repoInfo := getRepoInfoFromURL(repoURL)
|
||||
|
||||
return pr.GitCommand.OSCommand.OpenLink(fmt.Sprintf(
|
||||
gitService.PullRequestURL, repoInfo.Owner, repoInfo.Repository, branch.Name,
|
||||
))
|
||||
}
|
||||
|
||||
func getRepoInfoFromURL(url string) *RepoInformation {
|
||||
isHTTP := strings.HasPrefix(url, "http")
|
||||
|
||||
if isHTTP {
|
||||
splits := strings.Split(url, "/")
|
||||
owner := splits[len(splits)-2]
|
||||
repo := strings.TrimSuffix(splits[len(splits)-1], ".git")
|
||||
|
||||
return &RepoInformation{
|
||||
Owner: owner,
|
||||
Repository: repo,
|
||||
}
|
||||
}
|
||||
|
||||
tmpSplit := strings.Split(url, ":")
|
||||
splits := strings.Split(tmpSplit[1], "/")
|
||||
owner := splits[0]
|
||||
repo := strings.TrimSuffix(splits[1], ".git")
|
||||
|
||||
return &RepoInformation{
|
||||
Owner: owner,
|
||||
Repository: repo,
|
||||
}
|
||||
}
|
||||
154
pkg/commands/pull_request_test.go
Normal file
154
pkg/commands/pull_request_test.go
Normal file
@@ -0,0 +1,154 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"os/exec"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// TestGetRepoInfoFromURL is a function.
|
||||
func TestGetRepoInfoFromURL(t *testing.T) {
|
||||
type scenario struct {
|
||||
testName string
|
||||
repoURL string
|
||||
test func(*RepoInformation)
|
||||
}
|
||||
|
||||
scenarios := []scenario{
|
||||
{
|
||||
"Returns repository information for git remote url",
|
||||
"git@github.com:petersmith/super_calculator",
|
||||
func(repoInfo *RepoInformation) {
|
||||
assert.EqualValues(t, repoInfo.Owner, "petersmith")
|
||||
assert.EqualValues(t, repoInfo.Repository, "super_calculator")
|
||||
},
|
||||
},
|
||||
{
|
||||
"Returns repository information for http remote url",
|
||||
"https://my_username@bitbucket.org/johndoe/social_network.git",
|
||||
func(repoInfo *RepoInformation) {
|
||||
assert.EqualValues(t, repoInfo.Owner, "johndoe")
|
||||
assert.EqualValues(t, repoInfo.Repository, "social_network")
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
t.Run(s.testName, func(t *testing.T) {
|
||||
s.test(getRepoInfoFromURL(s.repoURL))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestCreatePullRequest is a function.
|
||||
func TestCreatePullRequest(t *testing.T) {
|
||||
type scenario struct {
|
||||
testName string
|
||||
branch *Branch
|
||||
command func(string, ...string) *exec.Cmd
|
||||
test func(err error)
|
||||
}
|
||||
|
||||
scenarios := []scenario{
|
||||
{
|
||||
"Opens a link to new pull request on bitbucket",
|
||||
&Branch{
|
||||
Name: "feature/profile-page",
|
||||
},
|
||||
func(cmd string, args ...string) *exec.Cmd {
|
||||
// Handle git remote url call
|
||||
if strings.HasPrefix(cmd, "git") {
|
||||
return exec.Command("echo", "git@bitbucket.org:johndoe/social_network.git")
|
||||
}
|
||||
|
||||
assert.Equal(t, cmd, "open")
|
||||
assert.Equal(t, args, []string{"https://bitbucket.org/johndoe/social_network/pull-requests/new?source=feature/profile-page&t=1"})
|
||||
return exec.Command("echo")
|
||||
},
|
||||
func(err error) {
|
||||
assert.NoError(t, err)
|
||||
},
|
||||
},
|
||||
{
|
||||
"Opens a link to new pull request on bitbucket with http remote url",
|
||||
&Branch{
|
||||
Name: "feature/events",
|
||||
},
|
||||
func(cmd string, args ...string) *exec.Cmd {
|
||||
// Handle git remote url call
|
||||
if strings.HasPrefix(cmd, "git") {
|
||||
return exec.Command("echo", "https://my_username@bitbucket.org/johndoe/social_network.git")
|
||||
}
|
||||
|
||||
assert.Equal(t, cmd, "open")
|
||||
assert.Equal(t, args, []string{"https://bitbucket.org/johndoe/social_network/pull-requests/new?source=feature/events&t=1"})
|
||||
return exec.Command("echo")
|
||||
},
|
||||
func(err error) {
|
||||
assert.NoError(t, err)
|
||||
},
|
||||
},
|
||||
{
|
||||
"Opens a link to new pull request on github",
|
||||
&Branch{
|
||||
Name: "feature/sum-operation",
|
||||
},
|
||||
func(cmd string, args ...string) *exec.Cmd {
|
||||
// Handle git remote url call
|
||||
if strings.HasPrefix(cmd, "git") {
|
||||
return exec.Command("echo", "git@github.com:peter/calculator.git")
|
||||
}
|
||||
|
||||
assert.Equal(t, cmd, "open")
|
||||
assert.Equal(t, args, []string{"https://github.com/peter/calculator/compare/feature/sum-operation?expand=1"})
|
||||
return exec.Command("echo")
|
||||
},
|
||||
func(err error) {
|
||||
assert.NoError(t, err)
|
||||
},
|
||||
},
|
||||
{
|
||||
"Opens a link to new pull request on gitlab",
|
||||
&Branch{
|
||||
Name: "feature/ui",
|
||||
},
|
||||
func(cmd string, args ...string) *exec.Cmd {
|
||||
// Handle git remote url call
|
||||
if strings.HasPrefix(cmd, "git") {
|
||||
return exec.Command("echo", "git@gitlab.com:peter/calculator.git")
|
||||
}
|
||||
|
||||
assert.Equal(t, cmd, "open")
|
||||
assert.Equal(t, args, []string{"https://gitlab.com/peter/calculator/merge_requests/new?merge_request[source_branch]=feature/ui"})
|
||||
return exec.Command("echo")
|
||||
},
|
||||
func(err error) {
|
||||
assert.NoError(t, err)
|
||||
},
|
||||
},
|
||||
{
|
||||
"Throws an error if git service is unsupported",
|
||||
&Branch{
|
||||
Name: "feature/divide-operation",
|
||||
},
|
||||
func(cmd string, args ...string) *exec.Cmd {
|
||||
return exec.Command("echo", "git@something.com:peter/calculator.git")
|
||||
},
|
||||
func(err error) {
|
||||
assert.Error(t, err)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
t.Run(s.testName, func(t *testing.T) {
|
||||
gitCommand := NewDummyGitCommand()
|
||||
gitCommand.OSCommand.command = s.command
|
||||
gitCommand.OSCommand.Config.GetUserConfig().Set("os.openLinkCommand", "open {{link}}")
|
||||
dummyPullRequest := NewPullRequest(gitCommand)
|
||||
s.test(dummyPullRequest.Create(s.branch))
|
||||
})
|
||||
}
|
||||
}
|
||||
13
pkg/commands/stash_entry.go
Normal file
13
pkg/commands/stash_entry.go
Normal file
@@ -0,0 +1,13 @@
|
||||
package commands
|
||||
|
||||
// StashEntry : A git stash entry
|
||||
type StashEntry struct {
|
||||
Index int
|
||||
Name string
|
||||
DisplayString string
|
||||
}
|
||||
|
||||
// GetDisplayStrings returns the display string of branch
|
||||
func (s *StashEntry) GetDisplayStrings(isFocused bool) []string {
|
||||
return []string{s.DisplayString}
|
||||
}
|
||||
0
pkg/commands/testdata/a_dir/file
vendored
Normal file
0
pkg/commands/testdata/a_dir/file
vendored
Normal file
0
pkg/commands/testdata/a_file
vendored
Normal file
0
pkg/commands/testdata/a_file
vendored
Normal file
@@ -3,6 +3,7 @@ package config
|
||||
import (
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/shibukawa/configdir"
|
||||
@@ -12,14 +13,16 @@ import (
|
||||
|
||||
// AppConfig contains the base configuration fields required for lazygit.
|
||||
type AppConfig struct {
|
||||
Debug bool `long:"debug" env:"DEBUG" default:"false"`
|
||||
Version string `long:"version" env:"VERSION" default:"unversioned"`
|
||||
Commit string `long:"commit" env:"COMMIT"`
|
||||
BuildDate string `long:"build-date" env:"BUILD_DATE"`
|
||||
Name string `long:"name" env:"NAME" default:"lazygit"`
|
||||
BuildSource string `long:"build-source" env:"BUILD_SOURCE" default:""`
|
||||
UserConfig *viper.Viper
|
||||
AppState *AppState
|
||||
Debug bool `long:"debug" env:"DEBUG" default:"false"`
|
||||
Version string `long:"version" env:"VERSION" default:"unversioned"`
|
||||
Commit string `long:"commit" env:"COMMIT"`
|
||||
BuildDate string `long:"build-date" env:"BUILD_DATE"`
|
||||
Name string `long:"name" env:"NAME" default:"lazygit"`
|
||||
BuildSource string `long:"build-source" env:"BUILD_SOURCE" default:""`
|
||||
UserConfig *viper.Viper
|
||||
UserConfigDir string
|
||||
AppState *AppState
|
||||
IsNewRepo bool
|
||||
}
|
||||
|
||||
// AppConfigurer interface allows individual app config structs to inherit Fields
|
||||
@@ -32,28 +35,37 @@ type AppConfigurer interface {
|
||||
GetName() string
|
||||
GetBuildSource() string
|
||||
GetUserConfig() *viper.Viper
|
||||
GetUserConfigDir() string
|
||||
GetAppState() *AppState
|
||||
WriteToUserConfig(string, string) error
|
||||
WriteToUserConfig(string, interface{}) error
|
||||
SaveAppState() error
|
||||
LoadAppState() error
|
||||
SetIsNewRepo(bool)
|
||||
GetIsNewRepo() bool
|
||||
}
|
||||
|
||||
// NewAppConfig makes a new app config
|
||||
func NewAppConfig(name, version, commit, date string, buildSource string, debuggingFlag *bool) (*AppConfig, error) {
|
||||
userConfig, err := LoadConfig("config", true)
|
||||
func NewAppConfig(name, version, commit, date string, buildSource string, debuggingFlag bool) (*AppConfig, error) {
|
||||
userConfig, userConfigPath, err := LoadConfig("config", true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if os.Getenv("DEBUG") == "TRUE" {
|
||||
debuggingFlag = true
|
||||
}
|
||||
|
||||
appConfig := &AppConfig{
|
||||
Name: "lazygit",
|
||||
Version: version,
|
||||
Commit: commit,
|
||||
BuildDate: date,
|
||||
Debug: *debuggingFlag,
|
||||
BuildSource: buildSource,
|
||||
UserConfig: userConfig,
|
||||
AppState: &AppState{},
|
||||
Name: "lazygit",
|
||||
Version: version,
|
||||
Commit: commit,
|
||||
BuildDate: date,
|
||||
Debug: debuggingFlag,
|
||||
BuildSource: buildSource,
|
||||
UserConfig: userConfig,
|
||||
UserConfigDir: filepath.Dir(userConfigPath),
|
||||
AppState: &AppState{},
|
||||
IsNewRepo: false,
|
||||
}
|
||||
|
||||
if err := appConfig.LoadAppState(); err != nil {
|
||||
@@ -63,6 +75,16 @@ func NewAppConfig(name, version, commit, date string, buildSource string, debugg
|
||||
return appConfig, nil
|
||||
}
|
||||
|
||||
// GetIsNewRepo returns known repo boolean
|
||||
func (c *AppConfig) GetIsNewRepo() bool {
|
||||
return c.IsNewRepo
|
||||
}
|
||||
|
||||
// SetIsNewRepo set if the current repo is known
|
||||
func (c *AppConfig) SetIsNewRepo(toSet bool) {
|
||||
c.IsNewRepo = toSet
|
||||
}
|
||||
|
||||
// GetDebug returns debug flag
|
||||
func (c *AppConfig) GetDebug() bool {
|
||||
return c.Debug
|
||||
@@ -104,6 +126,10 @@ func (c *AppConfig) GetAppState() *AppState {
|
||||
return c.AppState
|
||||
}
|
||||
|
||||
func (c *AppConfig) GetUserConfigDir() string {
|
||||
return c.UserConfigDir
|
||||
}
|
||||
|
||||
func newViper(filename string) (*viper.Viper, error) {
|
||||
v := viper.New()
|
||||
v.SetConfigType("yaml")
|
||||
@@ -112,23 +138,24 @@ func newViper(filename string) (*viper.Viper, error) {
|
||||
}
|
||||
|
||||
// LoadConfig gets the user's config
|
||||
func LoadConfig(filename string, withDefaults bool) (*viper.Viper, error) {
|
||||
func LoadConfig(filename string, withDefaults bool) (*viper.Viper, string, error) {
|
||||
v, err := newViper(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, "", err
|
||||
}
|
||||
if withDefaults {
|
||||
if err = LoadDefaults(v, GetDefaultConfig()); err != nil {
|
||||
return nil, err
|
||||
return nil, "", err
|
||||
}
|
||||
if err = LoadDefaults(v, GetPlatformDefaultConfig()); err != nil {
|
||||
return nil, err
|
||||
return nil, "", err
|
||||
}
|
||||
}
|
||||
if err = LoadAndMergeFile(v, filename+".yml"); err != nil {
|
||||
return nil, err
|
||||
configPath, err := LoadAndMergeFile(v, filename+".yml")
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
return v, nil
|
||||
return v, configPath, nil
|
||||
}
|
||||
|
||||
// LoadDefaults loads in the defaults defined in this file
|
||||
@@ -153,22 +180,22 @@ func prepareConfigFile(filename string) (string, error) {
|
||||
}
|
||||
|
||||
// LoadAndMergeFile Loads the config/state file, creating
|
||||
// the file as an empty one if it does not exist
|
||||
func LoadAndMergeFile(v *viper.Viper, filename string) error {
|
||||
// the file has an empty one if it does not exist
|
||||
func LoadAndMergeFile(v *viper.Viper, filename string) (string, error) {
|
||||
configPath, err := prepareConfigFile(filename)
|
||||
if err != nil {
|
||||
return err
|
||||
return "", err
|
||||
}
|
||||
|
||||
v.AddConfigPath(filepath.Dir(configPath))
|
||||
return v.MergeInConfig()
|
||||
return configPath, v.MergeInConfig()
|
||||
}
|
||||
|
||||
// WriteToUserConfig adds a key/value pair to the user's config and saves it
|
||||
func (c *AppConfig) WriteToUserConfig(key, value string) error {
|
||||
func (c *AppConfig) WriteToUserConfig(key string, value interface{}) error {
|
||||
// reloading the user config directly (without defaults) so that we're not
|
||||
// writing any defaults back to the user's config
|
||||
v, err := LoadConfig("config", false)
|
||||
v, _, err := LoadConfig("config", false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -177,7 +204,7 @@ func (c *AppConfig) WriteToUserConfig(key, value string) error {
|
||||
return v.WriteConfig()
|
||||
}
|
||||
|
||||
// SaveAppState marhsalls the AppState struct and writes it to the disk
|
||||
// SaveAppState marshalls the AppState struct and writes it to the disk
|
||||
func (c *AppConfig) SaveAppState() error {
|
||||
marshalledAppState, err := yaml.Marshal(c.AppState)
|
||||
if err != nil {
|
||||
@@ -214,7 +241,10 @@ func GetDefaultConfig() []byte {
|
||||
`gui:
|
||||
## stuff relating to the UI
|
||||
scrollHeight: 2
|
||||
scrollPastBottom: true
|
||||
mouseEvents: true
|
||||
theme:
|
||||
lightTheme: false
|
||||
activeBorderColor:
|
||||
- white
|
||||
- bold
|
||||
@@ -224,10 +254,16 @@ func GetDefaultConfig() []byte {
|
||||
- blue
|
||||
commitLength:
|
||||
show: true
|
||||
git:
|
||||
merging:
|
||||
manualCommit: false
|
||||
skipHookPrefix: 'WIP'
|
||||
autoFetch: true
|
||||
update:
|
||||
method: prompt # can be: prompt | background | never
|
||||
days: 14 # how often a update is checked for
|
||||
reporting: 'undetermined' # one of: 'on' | 'off' | 'undetermined'
|
||||
splashUpdatesIndex: 0
|
||||
confirmOnQuit: false
|
||||
`)
|
||||
}
|
||||
@@ -241,9 +277,9 @@ type AppState struct {
|
||||
|
||||
func getDefaultAppState() []byte {
|
||||
return []byte(`
|
||||
lastUpdateCheck: 0
|
||||
recentRepos: []
|
||||
`)
|
||||
lastUpdateCheck: 0
|
||||
recentRepos: []
|
||||
`)
|
||||
}
|
||||
|
||||
// // commenting this out until we use it again
|
||||
|
||||
@@ -6,5 +6,6 @@ package config
|
||||
func GetPlatformDefaultConfig() []byte {
|
||||
return []byte(
|
||||
`os:
|
||||
openCommand: 'open {{filename}}'`)
|
||||
openCommand: 'open {{filename}}'
|
||||
openLinkCommand: 'open {{link}}'`)
|
||||
}
|
||||
|
||||
@@ -4,5 +4,6 @@ package config
|
||||
func GetPlatformDefaultConfig() []byte {
|
||||
return []byte(
|
||||
`os:
|
||||
openCommand: 'sh -c "xdg-open {{filename}} >/dev/null"'`)
|
||||
openCommand: 'sh -c "xdg-open {{filename}} >/dev/null"'
|
||||
openLinkCommand: 'sh -c "xdg-open {{link}} >/dev/null"'`)
|
||||
}
|
||||
|
||||
@@ -4,5 +4,6 @@ package config
|
||||
func GetPlatformDefaultConfig() []byte {
|
||||
return []byte(
|
||||
`os:
|
||||
openCommand: 'cmd /c "start "" {{filename}}"'`)
|
||||
openCommand: 'cmd /c "start "" {{filename}}"'
|
||||
openLinkCommand: 'cmd /c "start "" {{link}}"'`)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
package gui
|
||||
|
||||
import "github.com/jesseduffield/lazygit/pkg/utils"
|
||||
import (
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
)
|
||||
|
||||
type appStatus struct {
|
||||
name string
|
||||
@@ -42,3 +45,26 @@ func (m *statusManager) getStatusString() string {
|
||||
}
|
||||
return topStatus.name
|
||||
}
|
||||
|
||||
// WithWaitingStatus wraps a function and shows a waiting status while the function is still executing
|
||||
func (gui *Gui) WithWaitingStatus(name string, f func() error) error {
|
||||
go func() {
|
||||
gui.g.Update(func(g *gocui.Gui) error {
|
||||
gui.statusManager.addWaitingStatus(name)
|
||||
return nil
|
||||
})
|
||||
|
||||
defer gui.g.Update(func(g *gocui.Gui) error {
|
||||
gui.statusManager.removeStatus(name)
|
||||
return nil
|
||||
})
|
||||
|
||||
if err := f(); err != nil {
|
||||
gui.g.Update(func(g *gocui.Gui) error {
|
||||
return gui.createErrorPanel(gui.g, err.Error())
|
||||
})
|
||||
}
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -6,26 +6,163 @@ import (
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands"
|
||||
"github.com/jesseduffield/lazygit/pkg/git"
|
||||
)
|
||||
|
||||
// list panel functions
|
||||
|
||||
func (gui *Gui) getSelectedBranch() *commands.Branch {
|
||||
selectedLine := gui.State.Panels.Branches.SelectedLine
|
||||
if selectedLine == -1 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return gui.State.Branches[selectedLine]
|
||||
}
|
||||
|
||||
func (gui *Gui) handleBranchesClick(g *gocui.Gui, v *gocui.View) error {
|
||||
itemCount := len(gui.State.Branches)
|
||||
handleSelect := gui.handleBranchSelect
|
||||
selectedLine := &gui.State.Panels.Branches.SelectedLine
|
||||
|
||||
return gui.handleClick(v, itemCount, selectedLine, handleSelect)
|
||||
}
|
||||
|
||||
// may want to standardise how these select methods work
|
||||
func (gui *Gui) handleBranchSelect(g *gocui.Gui, v *gocui.View) error {
|
||||
if gui.popupPanelFocused() {
|
||||
return nil
|
||||
}
|
||||
|
||||
gui.State.SplitMainPanel = false
|
||||
|
||||
if _, err := gui.g.SetCurrentView(v.Name()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
gui.getMainView().Title = "Log"
|
||||
|
||||
// This really shouldn't happen: there should always be a master branch
|
||||
if len(gui.State.Branches) == 0 {
|
||||
return gui.renderString(g, "main", gui.Tr.SLocalize("NoBranchesThisRepo"))
|
||||
}
|
||||
branch := gui.getSelectedBranch()
|
||||
if err := gui.focusPoint(0, gui.State.Panels.Branches.SelectedLine, len(gui.State.Branches), v); err != nil {
|
||||
return err
|
||||
}
|
||||
go func() {
|
||||
_ = gui.RenderSelectedBranchUpstreamDifferences()
|
||||
}()
|
||||
go func() {
|
||||
graph, err := gui.GitCommand.GetBranchGraph(branch.Name)
|
||||
if err != nil && strings.HasPrefix(graph, "fatal: ambiguous argument") {
|
||||
graph = gui.Tr.SLocalize("NoTrackingThisBranch")
|
||||
}
|
||||
_ = gui.renderString(g, "main", graph)
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) RenderSelectedBranchUpstreamDifferences() error {
|
||||
// here we tell the selected branch that it is selected.
|
||||
// this is necessary for showing stats on a branch that is selected, because
|
||||
// the displaystring function doesn't have access to gui state to tell if it's selected
|
||||
for i, branch := range gui.State.Branches {
|
||||
branch.Selected = i == gui.State.Panels.Branches.SelectedLine
|
||||
}
|
||||
|
||||
branch := gui.getSelectedBranch()
|
||||
branch.Pushables, branch.Pullables = gui.GitCommand.GetBranchUpstreamDifferenceCount(branch.Name)
|
||||
return gui.renderListPanel(gui.getBranchesView(), gui.State.Branches)
|
||||
}
|
||||
|
||||
// gui.refreshStatus is called at the end of this because that's when we can
|
||||
// be sure there is a state.Branches array to pick the current branch from
|
||||
func (gui *Gui) refreshBranches(g *gocui.Gui) error {
|
||||
g.Update(func(g *gocui.Gui) error {
|
||||
builder, err := commands.NewBranchListBuilder(gui.Log, gui.GitCommand)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
gui.State.Branches = builder.Build()
|
||||
|
||||
gui.refreshSelectedLine(&gui.State.Panels.Branches.SelectedLine, len(gui.State.Branches))
|
||||
if err := gui.RenderSelectedBranchUpstreamDifferences(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return gui.refreshStatus(g)
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) handleBranchesNextLine(g *gocui.Gui, v *gocui.View) error {
|
||||
if gui.popupPanelFocused() {
|
||||
return nil
|
||||
}
|
||||
|
||||
panelState := gui.State.Panels.Branches
|
||||
gui.changeSelectedLine(&panelState.SelectedLine, len(gui.State.Branches), false)
|
||||
|
||||
if err := gui.resetOrigin(gui.getMainView()); err != nil {
|
||||
return err
|
||||
}
|
||||
return gui.handleBranchSelect(gui.g, v)
|
||||
}
|
||||
|
||||
func (gui *Gui) handleBranchesPrevLine(g *gocui.Gui, v *gocui.View) error {
|
||||
if gui.popupPanelFocused() {
|
||||
return nil
|
||||
}
|
||||
|
||||
panelState := gui.State.Panels.Branches
|
||||
gui.changeSelectedLine(&panelState.SelectedLine, len(gui.State.Branches), true)
|
||||
|
||||
if err := gui.resetOrigin(gui.getMainView()); err != nil {
|
||||
return err
|
||||
}
|
||||
return gui.handleBranchSelect(gui.g, v)
|
||||
}
|
||||
|
||||
// specific functions
|
||||
|
||||
func (gui *Gui) handleBranchPress(g *gocui.Gui, v *gocui.View) error {
|
||||
index := gui.getItemPosition(gui.getBranchesView(g))
|
||||
if index == 0 {
|
||||
if gui.State.Panels.Branches.SelectedLine == -1 {
|
||||
return nil
|
||||
}
|
||||
if gui.State.Panels.Branches.SelectedLine == 0 {
|
||||
return gui.createErrorPanel(g, gui.Tr.SLocalize("AlreadyCheckedOutBranch"))
|
||||
}
|
||||
branch := gui.getSelectedBranch(gui.getBranchesView(g))
|
||||
if err := gui.GitCommand.Checkout(branch.Name, false); err != nil {
|
||||
gui.createErrorPanel(g, err.Error())
|
||||
branch := gui.getSelectedBranch()
|
||||
return gui.handleCheckoutBranch(branch.Name)
|
||||
}
|
||||
|
||||
func (gui *Gui) handleCreatePullRequestPress(g *gocui.Gui, v *gocui.View) error {
|
||||
pullRequest := commands.NewPullRequest(gui.GitCommand)
|
||||
|
||||
branch := gui.getSelectedBranch()
|
||||
if err := pullRequest.Create(branch); err != nil {
|
||||
return gui.createErrorPanel(g, err.Error())
|
||||
}
|
||||
return gui.refreshSidePanels(g)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) handleGitFetch(g *gocui.Gui, v *gocui.View) error {
|
||||
if err := gui.createLoaderPanel(gui.g, v, gui.Tr.SLocalize("FetchWait")); err != nil {
|
||||
return err
|
||||
}
|
||||
go func() {
|
||||
unamePassOpend, err := gui.fetch(g, v, true)
|
||||
gui.HandleCredentialsPopup(g, unamePassOpend, err)
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) handleForceCheckout(g *gocui.Gui, v *gocui.View) error {
|
||||
branch := gui.getSelectedBranch(v)
|
||||
branch := gui.getSelectedBranch()
|
||||
message := gui.Tr.SLocalize("SureForceCheckout")
|
||||
title := gui.Tr.SLocalize("ForceCheckoutBranch")
|
||||
return gui.createConfirmationPanel(g, v, title, message, func(g *gocui.Gui, v *gocui.View) error {
|
||||
return gui.createConfirmationPanel(g, v, true, title, message, func(g *gocui.Gui, v *gocui.View) error {
|
||||
if err := gui.GitCommand.Checkout(branch.Name, true); err != nil {
|
||||
gui.createErrorPanel(g, err.Error())
|
||||
}
|
||||
@@ -33,12 +170,45 @@ func (gui *Gui) handleForceCheckout(g *gocui.Gui, v *gocui.View) error {
|
||||
}, nil)
|
||||
}
|
||||
|
||||
func (gui *Gui) handleCheckoutBranch(branchName string) error {
|
||||
if err := gui.GitCommand.Checkout(branchName, false); err != nil {
|
||||
// note, this will only work for english-language git commands. If we force git to use english, and the error isn't this one, then the user will receive an english command they may not understand. I'm not sure what the best solution to this is. Running the command once in english and a second time in the native language is one option
|
||||
|
||||
if strings.Contains(err.Error(), "Please commit your changes or stash them before you switch branch") {
|
||||
// offer to autostash changes
|
||||
return gui.createConfirmationPanel(gui.g, gui.getBranchesView(), true, gui.Tr.SLocalize("AutoStashTitle"), gui.Tr.SLocalize("AutoStashPrompt"), func(g *gocui.Gui, v *gocui.View) error {
|
||||
if err := gui.GitCommand.StashSave(gui.Tr.SLocalize("StashPrefix") + branchName); err != nil {
|
||||
return gui.createErrorPanel(g, err.Error())
|
||||
}
|
||||
if err := gui.GitCommand.Checkout(branchName, false); err != nil {
|
||||
return gui.createErrorPanel(g, err.Error())
|
||||
}
|
||||
|
||||
// checkout successful so we select the new branch
|
||||
gui.State.Panels.Branches.SelectedLine = 0
|
||||
|
||||
if err := gui.GitCommand.StashDo(0, "pop"); err != nil {
|
||||
if err := gui.refreshSidePanels(g); err != nil {
|
||||
return err
|
||||
}
|
||||
return gui.createErrorPanel(g, err.Error())
|
||||
}
|
||||
return gui.refreshSidePanels(g)
|
||||
}, nil)
|
||||
}
|
||||
|
||||
if err := gui.createErrorPanel(gui.g, err.Error()); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
gui.State.Panels.Branches.SelectedLine = 0
|
||||
return gui.refreshSidePanels(gui.g)
|
||||
}
|
||||
|
||||
func (gui *Gui) handleCheckoutByName(g *gocui.Gui, v *gocui.View) error {
|
||||
gui.createPromptPanel(g, v, gui.Tr.SLocalize("BranchName")+":", func(g *gocui.Gui, v *gocui.View) error {
|
||||
if err := gui.GitCommand.Checkout(gui.trimmedContent(v), false); err != nil {
|
||||
return gui.createErrorPanel(g, err.Error())
|
||||
}
|
||||
return gui.refreshSidePanels(g)
|
||||
return gui.handleCheckoutBranch(gui.trimmedContent(v))
|
||||
})
|
||||
return nil
|
||||
}
|
||||
@@ -70,93 +240,115 @@ func (gui *Gui) handleForceDeleteBranch(g *gocui.Gui, v *gocui.View) error {
|
||||
}
|
||||
|
||||
func (gui *Gui) deleteBranch(g *gocui.Gui, v *gocui.View, force bool) error {
|
||||
selectedBranch := gui.getSelectedBranch()
|
||||
if selectedBranch == nil {
|
||||
return nil
|
||||
}
|
||||
checkedOutBranch := gui.State.Branches[0]
|
||||
selectedBranch := gui.getSelectedBranch(v)
|
||||
if checkedOutBranch.Name == selectedBranch.Name {
|
||||
return gui.createErrorPanel(g, gui.Tr.SLocalize("CantDeleteCheckOutBranch"))
|
||||
}
|
||||
return gui.deleteNamedBranch(g, v, selectedBranch, force)
|
||||
}
|
||||
|
||||
func (gui *Gui) deleteNamedBranch(g *gocui.Gui, v *gocui.View, selectedBranch *commands.Branch, force bool) error {
|
||||
title := gui.Tr.SLocalize("DeleteBranch")
|
||||
var messageId string
|
||||
var messageID string
|
||||
if force {
|
||||
messageId = "ForceDeleteBranchMessage"
|
||||
messageID = "ForceDeleteBranchMessage"
|
||||
} else {
|
||||
messageId = "DeleteBranchMessage"
|
||||
messageID = "DeleteBranchMessage"
|
||||
}
|
||||
message := gui.Tr.TemplateLocalize(
|
||||
messageId,
|
||||
messageID,
|
||||
Teml{
|
||||
"selectedBranchName": selectedBranch.Name,
|
||||
},
|
||||
)
|
||||
return gui.createConfirmationPanel(g, v, title, message, func(g *gocui.Gui, v *gocui.View) error {
|
||||
return gui.createConfirmationPanel(g, v, true, title, message, func(g *gocui.Gui, v *gocui.View) error {
|
||||
if err := gui.GitCommand.DeleteBranch(selectedBranch.Name, force); err != nil {
|
||||
return gui.createErrorPanel(g, err.Error())
|
||||
errMessage := err.Error()
|
||||
if !force && strings.Contains(errMessage, "is not fully merged") {
|
||||
return gui.deleteNamedBranch(g, v, selectedBranch, true)
|
||||
}
|
||||
return gui.createErrorPanel(g, errMessage)
|
||||
}
|
||||
return gui.refreshSidePanels(g)
|
||||
}, nil)
|
||||
}
|
||||
|
||||
func (gui *Gui) handleMerge(g *gocui.Gui, v *gocui.View) error {
|
||||
checkedOutBranch := gui.State.Branches[0]
|
||||
selectedBranch := gui.getSelectedBranch(v)
|
||||
defer gui.refreshSidePanels(g)
|
||||
if checkedOutBranch.Name == selectedBranch.Name {
|
||||
checkedOutBranch := gui.State.Branches[0].Name
|
||||
selectedBranch := gui.getSelectedBranch().Name
|
||||
if checkedOutBranch == selectedBranch {
|
||||
return gui.createErrorPanel(g, gui.Tr.SLocalize("CantMergeBranchIntoItself"))
|
||||
}
|
||||
if err := gui.GitCommand.Merge(selectedBranch.Name); err != nil {
|
||||
return gui.createErrorPanel(g, err.Error())
|
||||
}
|
||||
return nil
|
||||
prompt := gui.Tr.TemplateLocalize(
|
||||
"ConfirmMerge",
|
||||
Teml{
|
||||
"checkedOutBranch": checkedOutBranch,
|
||||
"selectedBranch": selectedBranch,
|
||||
},
|
||||
)
|
||||
return gui.createConfirmationPanel(g, v, true, gui.Tr.SLocalize("MergingTitle"), prompt,
|
||||
func(g *gocui.Gui, v *gocui.View) error {
|
||||
|
||||
err := gui.GitCommand.Merge(selectedBranch)
|
||||
return gui.handleGenericMergeCommandResult(err)
|
||||
}, nil)
|
||||
}
|
||||
|
||||
func (gui *Gui) getSelectedBranch(v *gocui.View) commands.Branch {
|
||||
lineNumber := gui.getItemPosition(v)
|
||||
return gui.State.Branches[lineNumber]
|
||||
func (gui *Gui) handleRebase(g *gocui.Gui, v *gocui.View) error {
|
||||
checkedOutBranch := gui.State.Branches[0].Name
|
||||
selectedBranch := gui.getSelectedBranch().Name
|
||||
if selectedBranch == checkedOutBranch {
|
||||
return gui.createErrorPanel(g, gui.Tr.SLocalize("CantRebaseOntoSelf"))
|
||||
}
|
||||
prompt := gui.Tr.TemplateLocalize(
|
||||
"ConfirmRebase",
|
||||
Teml{
|
||||
"checkedOutBranch": checkedOutBranch,
|
||||
"selectedBranch": selectedBranch,
|
||||
},
|
||||
)
|
||||
return gui.createConfirmationPanel(g, v, true, gui.Tr.SLocalize("RebasingTitle"), prompt,
|
||||
func(g *gocui.Gui, v *gocui.View) error {
|
||||
|
||||
err := gui.GitCommand.RebaseBranch(selectedBranch)
|
||||
return gui.handleGenericMergeCommandResult(err)
|
||||
}, nil)
|
||||
}
|
||||
|
||||
func (gui *Gui) renderBranchesOptions(g *gocui.Gui) error {
|
||||
return gui.renderGlobalOptions(g)
|
||||
}
|
||||
|
||||
// may want to standardise how these select methods work
|
||||
func (gui *Gui) handleBranchSelect(g *gocui.Gui, v *gocui.View) error {
|
||||
if err := gui.renderBranchesOptions(g); err != nil {
|
||||
return err
|
||||
func (gui *Gui) handleFastForward(g *gocui.Gui, v *gocui.View) error {
|
||||
branch := gui.getSelectedBranch()
|
||||
if branch == nil {
|
||||
return nil
|
||||
}
|
||||
// This really shouldn't happen: there should always be a master branch
|
||||
if len(gui.State.Branches) == 0 {
|
||||
return gui.renderString(g, "main", gui.Tr.SLocalize("NoBranchesThisRepo"))
|
||||
if branch.Pushables == "" {
|
||||
return nil
|
||||
}
|
||||
if branch.Pushables == "?" {
|
||||
return gui.createErrorPanel(gui.g, gui.Tr.SLocalize("FwdNoUpstream"))
|
||||
}
|
||||
if branch.Pushables != "0" {
|
||||
return gui.createErrorPanel(gui.g, gui.Tr.SLocalize("FwdCommitsToPush"))
|
||||
}
|
||||
upstream := "origin" // hardcoding for now
|
||||
message := gui.Tr.TemplateLocalize(
|
||||
"Fetching",
|
||||
Teml{
|
||||
"from": fmt.Sprintf("%s/%s", upstream, branch.Name),
|
||||
"to": branch.Name,
|
||||
},
|
||||
)
|
||||
go func() {
|
||||
branch := gui.getSelectedBranch(v)
|
||||
diff, err := gui.GitCommand.GetBranchGraph(branch.Name)
|
||||
if err != nil && strings.HasPrefix(diff, "fatal: ambiguous argument") {
|
||||
diff = gui.Tr.SLocalize("NoTrackingThisBranch")
|
||||
_ = gui.createLoaderPanel(gui.g, v, message)
|
||||
if err := gui.GitCommand.FastForward(branch.Name); err != nil {
|
||||
_ = gui.createErrorPanel(gui.g, err.Error())
|
||||
} else {
|
||||
_ = gui.closeConfirmationPrompt(gui.g, true)
|
||||
_ = gui.RenderSelectedBranchUpstreamDifferences()
|
||||
}
|
||||
gui.renderString(g, "main", diff)
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
// gui.refreshStatus is called at the end of this because that's when we can
|
||||
// be sure there is a state.Branches array to pick the current branch from
|
||||
func (gui *Gui) refreshBranches(g *gocui.Gui) error {
|
||||
g.Update(func(g *gocui.Gui) error {
|
||||
v, err := g.View("branches")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
builder, err := git.NewBranchListBuilder(gui.Log, gui.GitCommand)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
gui.State.Branches = builder.Build()
|
||||
v.Clear()
|
||||
for _, branch := range gui.State.Branches {
|
||||
fmt.Fprintln(v, branch.GetDisplayString())
|
||||
}
|
||||
gui.resetOrigin(v)
|
||||
return gui.refreshStatus(g)
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
228
pkg/gui/commit_files_panel.go
Normal file
228
pkg/gui/commit_files_panel.go
Normal file
@@ -0,0 +1,228 @@
|
||||
package gui
|
||||
|
||||
import (
|
||||
"github.com/go-errors/errors"
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands"
|
||||
)
|
||||
|
||||
func (gui *Gui) getSelectedCommitFile(g *gocui.Gui) *commands.CommitFile {
|
||||
selectedLine := gui.State.Panels.CommitFiles.SelectedLine
|
||||
if selectedLine == -1 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return gui.State.CommitFiles[selectedLine]
|
||||
}
|
||||
|
||||
func (gui *Gui) handleCommitFilesClick(g *gocui.Gui, v *gocui.View) error {
|
||||
itemCount := len(gui.State.CommitFiles)
|
||||
handleSelect := gui.handleCommitFileSelect
|
||||
selectedLine := &gui.State.Panels.CommitFiles.SelectedLine
|
||||
|
||||
return gui.handleClick(v, itemCount, selectedLine, handleSelect)
|
||||
}
|
||||
|
||||
func (gui *Gui) handleCommitFileSelect(g *gocui.Gui, v *gocui.View) error {
|
||||
if gui.popupPanelFocused() {
|
||||
return nil
|
||||
}
|
||||
|
||||
gui.getMainView().Title = "Patch"
|
||||
gui.State.Panels.LineByLine = nil
|
||||
|
||||
commitFile := gui.getSelectedCommitFile(g)
|
||||
if commitFile == nil {
|
||||
return gui.renderString(g, "commitFiles", gui.Tr.SLocalize("NoCommiteFiles"))
|
||||
}
|
||||
|
||||
if err := gui.refreshSecondaryPatchPanel(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := gui.focusPoint(0, gui.State.Panels.CommitFiles.SelectedLine, len(gui.State.CommitFiles), v); err != nil {
|
||||
return err
|
||||
}
|
||||
commitText, err := gui.GitCommand.ShowCommitFile(commitFile.Sha, commitFile.Name, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return gui.renderString(g, "main", commitText)
|
||||
}
|
||||
|
||||
func (gui *Gui) handleCommitFilesNextLine(g *gocui.Gui, v *gocui.View) error {
|
||||
panelState := gui.State.Panels.CommitFiles
|
||||
gui.changeSelectedLine(&panelState.SelectedLine, len(gui.State.CommitFiles), false)
|
||||
|
||||
return gui.handleCommitFileSelect(gui.g, v)
|
||||
}
|
||||
|
||||
func (gui *Gui) handleCommitFilesPrevLine(g *gocui.Gui, v *gocui.View) error {
|
||||
panelState := gui.State.Panels.CommitFiles
|
||||
gui.changeSelectedLine(&panelState.SelectedLine, len(gui.State.CommitFiles), true)
|
||||
|
||||
return gui.handleCommitFileSelect(gui.g, v)
|
||||
}
|
||||
|
||||
func (gui *Gui) handleSwitchToCommitsPanel(g *gocui.Gui, v *gocui.View) error {
|
||||
commitsView, err := g.View("commits")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return gui.switchFocus(g, v, commitsView)
|
||||
}
|
||||
|
||||
func (gui *Gui) handleCheckoutCommitFile(g *gocui.Gui, v *gocui.View) error {
|
||||
file := gui.State.CommitFiles[gui.State.Panels.CommitFiles.SelectedLine]
|
||||
|
||||
if err := gui.GitCommand.CheckoutFile(file.Sha, file.Name); err != nil {
|
||||
return gui.createErrorPanel(gui.g, err.Error())
|
||||
}
|
||||
|
||||
return gui.refreshFiles()
|
||||
}
|
||||
|
||||
func (gui *Gui) handleDiscardOldFileChange(g *gocui.Gui, v *gocui.View) error {
|
||||
if ok, err := gui.validateNormalWorkingTreeState(); !ok {
|
||||
return err
|
||||
}
|
||||
|
||||
fileName := gui.State.CommitFiles[gui.State.Panels.CommitFiles.SelectedLine].Name
|
||||
|
||||
return gui.createConfirmationPanel(gui.g, v, true, gui.Tr.SLocalize("DiscardFileChangesTitle"), gui.Tr.SLocalize("DiscardFileChangesPrompt"), func(g *gocui.Gui, v *gocui.View) error {
|
||||
return gui.WithWaitingStatus(gui.Tr.SLocalize("RebasingStatus"), func() error {
|
||||
if err := gui.GitCommand.DiscardOldFileChanges(gui.State.Commits, gui.State.Panels.Commits.SelectedLine, fileName); err != nil {
|
||||
if err := gui.handleGenericMergeCommandResult(err); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return gui.refreshSidePanels(gui.g)
|
||||
})
|
||||
}, nil)
|
||||
}
|
||||
|
||||
func (gui *Gui) refreshCommitFilesView() error {
|
||||
if err := gui.refreshSecondaryPatchPanel(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := gui.refreshPatchBuildingPanel(-1); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
commit := gui.getSelectedCommit(gui.g)
|
||||
if commit == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
files, err := gui.GitCommand.GetCommitFiles(commit.Sha, gui.GitCommand.PatchManager)
|
||||
if err != nil {
|
||||
return gui.createErrorPanel(gui.g, err.Error())
|
||||
}
|
||||
gui.State.CommitFiles = files
|
||||
|
||||
gui.refreshSelectedLine(&gui.State.Panels.CommitFiles.SelectedLine, len(gui.State.CommitFiles))
|
||||
|
||||
if err := gui.renderListPanel(gui.getCommitFilesView(), gui.State.CommitFiles); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return gui.handleCommitFileSelect(gui.g, gui.getCommitFilesView())
|
||||
}
|
||||
|
||||
func (gui *Gui) handleOpenOldCommitFile(g *gocui.Gui, v *gocui.View) error {
|
||||
file := gui.getSelectedCommitFile(g)
|
||||
return gui.openFile(file.Name)
|
||||
}
|
||||
|
||||
func (gui *Gui) handleToggleFileForPatch(g *gocui.Gui, v *gocui.View) error {
|
||||
if ok, err := gui.validateNormalWorkingTreeState(); !ok {
|
||||
return err
|
||||
}
|
||||
|
||||
commitFile := gui.getSelectedCommitFile(g)
|
||||
if commitFile == nil {
|
||||
return gui.renderString(g, "commitFiles", gui.Tr.SLocalize("NoCommiteFiles"))
|
||||
}
|
||||
|
||||
toggleTheFile := func() error {
|
||||
if !gui.GitCommand.PatchManager.CommitSelected() {
|
||||
if err := gui.startPatchManager(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
gui.GitCommand.PatchManager.ToggleFileWhole(commitFile.Name)
|
||||
|
||||
return gui.refreshCommitFilesView()
|
||||
}
|
||||
|
||||
if gui.GitCommand.PatchManager.CommitSelected() && gui.GitCommand.PatchManager.CommitSha != commitFile.Sha {
|
||||
return gui.createConfirmationPanel(g, v, true, gui.Tr.SLocalize("DiscardPatch"), gui.Tr.SLocalize("DiscardPatchConfirm"), func(g *gocui.Gui, v *gocui.View) error {
|
||||
gui.GitCommand.PatchManager.Reset()
|
||||
return toggleTheFile()
|
||||
}, nil)
|
||||
}
|
||||
|
||||
return toggleTheFile()
|
||||
}
|
||||
|
||||
func (gui *Gui) startPatchManager() error {
|
||||
diffMap := map[string]string{}
|
||||
for _, commitFile := range gui.State.CommitFiles {
|
||||
commitText, err := gui.GitCommand.ShowCommitFile(commitFile.Sha, commitFile.Name, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
diffMap[commitFile.Name] = commitText
|
||||
}
|
||||
|
||||
commit := gui.getSelectedCommit(gui.g)
|
||||
if commit == nil {
|
||||
return errors.New("No commit selected")
|
||||
}
|
||||
|
||||
gui.GitCommand.PatchManager.Start(commit.Sha, diffMap)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) handleEnterCommitFile(g *gocui.Gui, v *gocui.View) error {
|
||||
return gui.enterCommitFile(-1)
|
||||
}
|
||||
|
||||
func (gui *Gui) enterCommitFile(selectedLineIdx int) error {
|
||||
if ok, err := gui.validateNormalWorkingTreeState(); !ok {
|
||||
return err
|
||||
}
|
||||
|
||||
commitFile := gui.getSelectedCommitFile(gui.g)
|
||||
if commitFile == nil {
|
||||
return gui.renderString(gui.g, "commitFiles", gui.Tr.SLocalize("NoCommiteFiles"))
|
||||
}
|
||||
|
||||
enterTheFile := func(selectedLineIdx int) error {
|
||||
if !gui.GitCommand.PatchManager.CommitSelected() {
|
||||
if err := gui.startPatchManager(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := gui.changeContext("patch-building"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := gui.switchFocus(gui.g, gui.getCommitFilesView(), gui.getMainView()); err != nil {
|
||||
return err
|
||||
}
|
||||
return gui.refreshPatchBuildingPanel(selectedLineIdx)
|
||||
}
|
||||
|
||||
if gui.GitCommand.PatchManager.CommitSelected() && gui.GitCommand.PatchManager.CommitSha != commitFile.Sha {
|
||||
return gui.createConfirmationPanel(gui.g, gui.getCommitFilesView(), false, gui.Tr.SLocalize("DiscardPatch"), gui.Tr.SLocalize("DiscardPatchConfirm"), func(g *gocui.Gui, v *gocui.View) error {
|
||||
gui.GitCommand.PatchManager.Reset()
|
||||
return enterTheFile(selectedLineIdx)
|
||||
}, nil)
|
||||
}
|
||||
|
||||
return enterTheFile(selectedLineIdx)
|
||||
}
|
||||
@@ -1,42 +1,66 @@
|
||||
package gui
|
||||
|
||||
import (
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
)
|
||||
|
||||
// runSyncOrAsyncCommand takes the output of a command that may have returned
|
||||
// either no error, an error, or a subprocess to execute, and if a subprocess
|
||||
// needs to be set on the gui object, it does so, and then returns the error
|
||||
// the bool returned tells us whether the calling code should continue
|
||||
func (gui *Gui) runSyncOrAsyncCommand(sub *exec.Cmd, err error) (bool, error) {
|
||||
if err != nil {
|
||||
if err != gui.Errors.ErrSubProcess {
|
||||
return false, gui.createErrorPanel(gui.g, err.Error())
|
||||
}
|
||||
}
|
||||
if sub != nil {
|
||||
gui.SubProcess = sub
|
||||
return false, gui.Errors.ErrSubProcess
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (gui *Gui) handleCommitConfirm(g *gocui.Gui, v *gocui.View) error {
|
||||
message := gui.trimmedContent(v)
|
||||
if message == "" {
|
||||
return gui.createErrorPanel(g, gui.Tr.SLocalize("CommitWithoutMessageErr"))
|
||||
}
|
||||
sub, err := gui.GitCommand.Commit(g, message)
|
||||
flags := ""
|
||||
skipHookPrefix := gui.Config.GetUserConfig().GetString("git.skipHookPrefix")
|
||||
if skipHookPrefix != "" && strings.HasPrefix(message, skipHookPrefix) {
|
||||
flags = "--no-verify"
|
||||
}
|
||||
ok, err := gui.runSyncOrAsyncCommand(gui.GitCommand.Commit(message, flags))
|
||||
if err != nil {
|
||||
// TODO need to find a way to send through this error
|
||||
if err != gui.Errors.ErrSubProcess {
|
||||
return gui.createErrorPanel(g, err.Error())
|
||||
}
|
||||
return err
|
||||
}
|
||||
if sub != nil {
|
||||
gui.SubProcess = sub
|
||||
return gui.Errors.ErrSubProcess
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
gui.refreshFiles(g)
|
||||
|
||||
v.Clear()
|
||||
v.SetCursor(0, 0)
|
||||
g.SetViewOnBottom("commitMessage")
|
||||
gui.switchFocus(g, v, gui.getFilesView(g))
|
||||
return gui.refreshCommits(g)
|
||||
_ = v.SetCursor(0, 0)
|
||||
_ = v.SetOrigin(0, 0)
|
||||
_, _ = g.SetViewOnBottom("commitMessage")
|
||||
_ = gui.switchFocus(g, v, gui.getFilesView())
|
||||
return gui.refreshSidePanels(g)
|
||||
}
|
||||
|
||||
func (gui *Gui) handleCommitClose(g *gocui.Gui, v *gocui.View) error {
|
||||
g.SetViewOnBottom("commitMessage")
|
||||
return gui.switchFocus(g, v, gui.getFilesView(g))
|
||||
return gui.switchFocus(g, v, gui.getFilesView())
|
||||
}
|
||||
|
||||
func (gui *Gui) handleCommitFocused(g *gocui.Gui, v *gocui.View) error {
|
||||
if _, err := g.SetViewOnTop("commitMessage"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
message := gui.Tr.TemplateLocalize(
|
||||
"CloseConfirm",
|
||||
Teml{
|
||||
@@ -47,41 +71,15 @@ func (gui *Gui) handleCommitFocused(g *gocui.Gui, v *gocui.View) error {
|
||||
return gui.renderString(g, "options", message)
|
||||
}
|
||||
|
||||
func (gui *Gui) simpleEditor(v *gocui.View, key gocui.Key, ch rune, mod gocui.Modifier) {
|
||||
switch {
|
||||
case key == gocui.KeyBackspace || key == gocui.KeyBackspace2:
|
||||
v.EditDelete(true)
|
||||
case key == gocui.KeyDelete:
|
||||
v.EditDelete(false)
|
||||
case key == gocui.KeyArrowDown:
|
||||
v.MoveCursor(0, 1, false)
|
||||
case key == gocui.KeyArrowUp:
|
||||
v.MoveCursor(0, -1, false)
|
||||
case key == gocui.KeyArrowLeft:
|
||||
v.MoveCursor(-1, 0, false)
|
||||
case key == gocui.KeyArrowRight:
|
||||
v.MoveCursor(1, 0, false)
|
||||
case key == gocui.KeyTab:
|
||||
v.EditNewLine()
|
||||
case key == gocui.KeySpace:
|
||||
v.EditWrite(' ')
|
||||
case key == gocui.KeyInsert:
|
||||
v.Overwrite = !v.Overwrite
|
||||
default:
|
||||
v.EditWrite(ch)
|
||||
}
|
||||
|
||||
gui.RenderCommitLength()
|
||||
}
|
||||
|
||||
func (gui *Gui) getBufferLength(view *gocui.View) string {
|
||||
return " " + strconv.Itoa(strings.Count(view.Buffer(), "")-1) + " "
|
||||
}
|
||||
|
||||
// RenderCommitLength is a function.
|
||||
func (gui *Gui) RenderCommitLength() {
|
||||
if !gui.Config.GetUserConfig().GetBool("gui.commitLength.show") {
|
||||
return
|
||||
}
|
||||
v := gui.getCommitMessageView(gui.g)
|
||||
v := gui.getCommitMessageView()
|
||||
v.Subtitle = gui.getBufferLength(v)
|
||||
}
|
||||
|
||||
@@ -1,105 +1,200 @@
|
||||
package gui
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/go-errors/errors"
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
)
|
||||
|
||||
// list panel functions
|
||||
|
||||
func (gui *Gui) getSelectedCommit(g *gocui.Gui) *commands.Commit {
|
||||
selectedLine := gui.State.Panels.Commits.SelectedLine
|
||||
if selectedLine == -1 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return gui.State.Commits[selectedLine]
|
||||
}
|
||||
|
||||
func (gui *Gui) handleCommitsClick(g *gocui.Gui, v *gocui.View) error {
|
||||
if gui.popupPanelFocused() {
|
||||
return nil
|
||||
}
|
||||
|
||||
prevSelectedLineIdx := gui.State.Panels.Commits.SelectedLine
|
||||
newSelectedLineIdx := v.SelectedLineIdx()
|
||||
|
||||
if newSelectedLineIdx > len(gui.State.Commits)-1 {
|
||||
return gui.handleCommitSelect(gui.g, v)
|
||||
}
|
||||
|
||||
gui.State.Panels.Commits.SelectedLine = newSelectedLineIdx
|
||||
|
||||
if prevSelectedLineIdx == newSelectedLineIdx && gui.currentViewName() == v.Name() {
|
||||
return gui.handleSwitchToCommitFilesPanel(gui.g, v)
|
||||
} else {
|
||||
return gui.handleCommitSelect(gui.g, v)
|
||||
}
|
||||
}
|
||||
|
||||
func (gui *Gui) handleCommitSelect(g *gocui.Gui, v *gocui.View) error {
|
||||
if gui.popupPanelFocused() {
|
||||
return nil
|
||||
}
|
||||
|
||||
// this probably belongs in an 'onFocus' function than a 'commit selected' function
|
||||
if err := gui.refreshSecondaryPatchPanel(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := gui.g.SetCurrentView(v.Name()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
gui.getMainView().Title = "Patch"
|
||||
gui.getSecondaryView().Title = "Custom Patch"
|
||||
gui.State.Panels.LineByLine = nil
|
||||
|
||||
commit := gui.getSelectedCommit(g)
|
||||
if commit == nil {
|
||||
return gui.renderString(g, "main", gui.Tr.SLocalize("NoCommitsThisBranch"))
|
||||
}
|
||||
|
||||
if err := gui.focusPoint(0, gui.State.Panels.Commits.SelectedLine, len(gui.State.Commits), v); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// if specific diff mode is on, don't show diff
|
||||
if gui.State.Panels.Commits.SpecificDiffMode {
|
||||
return nil
|
||||
}
|
||||
|
||||
commitText, err := gui.GitCommand.Show(commit.Sha)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return gui.renderString(g, "main", commitText)
|
||||
}
|
||||
|
||||
func (gui *Gui) refreshCommits(g *gocui.Gui) error {
|
||||
g.Update(func(*gocui.Gui) error {
|
||||
gui.State.Commits = gui.GitCommand.GetCommits()
|
||||
v, err := g.View("commits")
|
||||
builder, err := commands.NewCommitListBuilder(gui.Log, gui.GitCommand, gui.OSCommand, gui.Tr, gui.State.CherryPickedCommits, gui.State.DiffEntries)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
return err
|
||||
}
|
||||
commits, err := builder.GetCommits()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
gui.State.Commits = commits
|
||||
|
||||
gui.refreshSelectedLine(&gui.State.Panels.Commits.SelectedLine, len(gui.State.Commits))
|
||||
|
||||
isFocused := gui.g.CurrentView().Name() == "commits"
|
||||
list, err := utils.RenderList(gui.State.Commits, isFocused)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
v := gui.getCommitsView()
|
||||
v.Clear()
|
||||
red := color.New(color.FgRed)
|
||||
yellow := color.New(color.FgYellow)
|
||||
white := color.New(color.FgWhite)
|
||||
shaColor := white
|
||||
for _, commit := range gui.State.Commits {
|
||||
if commit.Pushed {
|
||||
shaColor = red
|
||||
} else {
|
||||
shaColor = yellow
|
||||
}
|
||||
shaColor.Fprint(v, commit.Sha+" ")
|
||||
white.Fprintln(v, commit.Name)
|
||||
}
|
||||
fmt.Fprint(v, list)
|
||||
|
||||
gui.refreshStatus(g)
|
||||
if g.CurrentView().Name() == "commits" {
|
||||
if g.CurrentView() == v {
|
||||
gui.handleCommitSelect(g, v)
|
||||
}
|
||||
if g.CurrentView() == gui.getCommitFilesView() || (g.CurrentView() == gui.getMainView() || gui.State.Context == "patch-building") {
|
||||
return gui.refreshCommitFilesView()
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) handleCommitsNextLine(g *gocui.Gui, v *gocui.View) error {
|
||||
if gui.popupPanelFocused() {
|
||||
return nil
|
||||
}
|
||||
|
||||
panelState := gui.State.Panels.Commits
|
||||
gui.changeSelectedLine(&panelState.SelectedLine, len(gui.State.Commits), false)
|
||||
|
||||
if err := gui.resetOrigin(gui.getMainView()); err != nil {
|
||||
return err
|
||||
}
|
||||
return gui.handleCommitSelect(gui.g, v)
|
||||
}
|
||||
|
||||
func (gui *Gui) handleCommitsPrevLine(g *gocui.Gui, v *gocui.View) error {
|
||||
if gui.popupPanelFocused() {
|
||||
return nil
|
||||
}
|
||||
|
||||
panelState := gui.State.Panels.Commits
|
||||
gui.changeSelectedLine(&panelState.SelectedLine, len(gui.State.Commits), true)
|
||||
|
||||
if err := gui.resetOrigin(gui.getMainView()); err != nil {
|
||||
return err
|
||||
}
|
||||
return gui.handleCommitSelect(gui.g, v)
|
||||
}
|
||||
|
||||
// specific functions
|
||||
|
||||
func (gui *Gui) handleResetToCommit(g *gocui.Gui, commitView *gocui.View) error {
|
||||
return gui.createConfirmationPanel(g, commitView, gui.Tr.SLocalize("ResetToCommit"), gui.Tr.SLocalize("SureResetThisCommit"), func(g *gocui.Gui, v *gocui.View) error {
|
||||
commit, err := gui.getSelectedCommit(g)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
return gui.createConfirmationPanel(g, commitView, true, gui.Tr.SLocalize("ResetToCommit"), gui.Tr.SLocalize("SureResetThisCommit"), func(g *gocui.Gui, v *gocui.View) error {
|
||||
commit := gui.getSelectedCommit(g)
|
||||
if commit == nil {
|
||||
panic(errors.New(gui.Tr.SLocalize("NoCommitsThisBranch")))
|
||||
}
|
||||
if err := gui.GitCommand.ResetToCommit(commit.Sha); err != nil {
|
||||
|
||||
if err := gui.GitCommand.ResetToCommit(commit.Sha, "mixed"); err != nil {
|
||||
return gui.createErrorPanel(g, err.Error())
|
||||
}
|
||||
if err := gui.refreshCommits(g); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := gui.refreshFiles(g); err != nil {
|
||||
if err := gui.refreshFiles(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
gui.resetOrigin(commitView)
|
||||
return gui.handleCommitSelect(g, nil)
|
||||
gui.State.Panels.Commits.SelectedLine = 0
|
||||
return gui.handleCommitSelect(g, commitView)
|
||||
}, nil)
|
||||
}
|
||||
|
||||
func (gui *Gui) renderCommitsOptions(g *gocui.Gui) error {
|
||||
return gui.renderGlobalOptions(g)
|
||||
}
|
||||
|
||||
func (gui *Gui) handleCommitSelect(g *gocui.Gui, v *gocui.View) error {
|
||||
if err := gui.renderCommitsOptions(g); err != nil {
|
||||
return err
|
||||
}
|
||||
commit, err := gui.getSelectedCommit(g)
|
||||
if err != nil {
|
||||
if err.Error() != gui.Tr.SLocalize("NoCommitsThisBranch") {
|
||||
return err
|
||||
}
|
||||
return gui.renderString(g, "main", gui.Tr.SLocalize("NoCommitsThisBranch"))
|
||||
}
|
||||
commitText := gui.GitCommand.Show(commit.Sha)
|
||||
return gui.renderString(g, "main", commitText)
|
||||
}
|
||||
|
||||
func (gui *Gui) handleCommitSquashDown(g *gocui.Gui, v *gocui.View) error {
|
||||
if gui.getItemPosition(v) != 0 {
|
||||
return gui.createErrorPanel(g, gui.Tr.SLocalize("OnlySquashTopmostCommit"))
|
||||
}
|
||||
if len(gui.State.Commits) == 1 {
|
||||
if len(gui.State.Commits) <= 1 {
|
||||
return gui.createErrorPanel(g, gui.Tr.SLocalize("YouNoCommitsToSquash"))
|
||||
}
|
||||
commit, err := gui.getSelectedCommit(g)
|
||||
|
||||
applied, err := gui.handleMidRebaseCommand("squash")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := gui.GitCommand.SquashPreviousTwoCommits(commit.Name); err != nil {
|
||||
return gui.createErrorPanel(g, err.Error())
|
||||
if applied {
|
||||
return nil
|
||||
}
|
||||
if err := gui.refreshCommits(g); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
gui.refreshStatus(g)
|
||||
return gui.handleCommitSelect(g, v)
|
||||
|
||||
gui.createConfirmationPanel(g, v, true, gui.Tr.SLocalize("Squash"), gui.Tr.SLocalize("SureSquashThisCommit"), func(g *gocui.Gui, v *gocui.View) error {
|
||||
return gui.WithWaitingStatus(gui.Tr.SLocalize("SquashingStatus"), func() error {
|
||||
err := gui.GitCommand.InteractiveRebase(gui.State.Commits, gui.State.Panels.Commits.SelectedLine, "squash")
|
||||
return gui.handleGenericMergeCommandResult(err)
|
||||
})
|
||||
}, nil)
|
||||
return nil
|
||||
}
|
||||
|
||||
// TODO: move to files panel
|
||||
func (gui *Gui) anyUnStagedChanges(files []commands.File) bool {
|
||||
func (gui *Gui) anyUnStagedChanges(files []*commands.File) bool {
|
||||
for _, file := range files {
|
||||
if file.Tracked && file.HasUnstagedChanges {
|
||||
return true
|
||||
@@ -109,32 +204,37 @@ func (gui *Gui) anyUnStagedChanges(files []commands.File) bool {
|
||||
}
|
||||
|
||||
func (gui *Gui) handleCommitFixup(g *gocui.Gui, v *gocui.View) error {
|
||||
if len(gui.State.Commits) == 1 {
|
||||
if len(gui.State.Commits) <= 1 {
|
||||
return gui.createErrorPanel(g, gui.Tr.SLocalize("YouNoCommitsToSquash"))
|
||||
}
|
||||
if gui.anyUnStagedChanges(gui.State.Files) {
|
||||
return gui.createErrorPanel(g, gui.Tr.SLocalize("CantFixupWhileUnstagedChanges"))
|
||||
}
|
||||
branch := gui.State.Branches[0]
|
||||
commit, err := gui.getSelectedCommit(g)
|
||||
|
||||
applied, err := gui.handleMidRebaseCommand("fixup")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
message := gui.Tr.SLocalize("SureFixupThisCommit")
|
||||
gui.createConfirmationPanel(g, v, gui.Tr.SLocalize("Fixup"), message, func(g *gocui.Gui, v *gocui.View) error {
|
||||
if err := gui.GitCommand.SquashFixupCommit(branch.Name, commit.Sha); err != nil {
|
||||
return gui.createErrorPanel(g, err.Error())
|
||||
}
|
||||
if err := gui.refreshCommits(g); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return gui.refreshStatus(g)
|
||||
if applied {
|
||||
return nil
|
||||
}
|
||||
|
||||
gui.createConfirmationPanel(g, v, true, gui.Tr.SLocalize("Fixup"), gui.Tr.SLocalize("SureFixupThisCommit"), func(g *gocui.Gui, v *gocui.View) error {
|
||||
return gui.WithWaitingStatus(gui.Tr.SLocalize("FixingStatus"), func() error {
|
||||
err := gui.GitCommand.InteractiveRebase(gui.State.Commits, gui.State.Panels.Commits.SelectedLine, "fixup")
|
||||
return gui.handleGenericMergeCommandResult(err)
|
||||
})
|
||||
}, nil)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) handleRenameCommit(g *gocui.Gui, v *gocui.View) error {
|
||||
if gui.getItemPosition(gui.getCommitsView(g)) != 0 {
|
||||
applied, err := gui.handleMidRebaseCommand("reword")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if applied {
|
||||
return nil
|
||||
}
|
||||
|
||||
if gui.State.Panels.Commits.SelectedLine != 0 {
|
||||
return gui.createErrorPanel(g, gui.Tr.SLocalize("OnlyRenameTopCommit"))
|
||||
}
|
||||
return gui.createPromptPanel(g, v, gui.Tr.SLocalize("renameCommit"), func(g *gocui.Gui, v *gocui.View) error {
|
||||
@@ -146,34 +246,390 @@ func (gui *Gui) handleRenameCommit(g *gocui.Gui, v *gocui.View) error {
|
||||
}
|
||||
return gui.handleCommitSelect(g, v)
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) handleRenameCommitEditor(g *gocui.Gui, v *gocui.View) error {
|
||||
if gui.getItemPosition(gui.getCommitsView(g)) != 0 {
|
||||
return gui.createErrorPanel(g, gui.Tr.SLocalize("OnlyRenameTopCommit"))
|
||||
applied, err := gui.handleMidRebaseCommand("reword")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if applied {
|
||||
return nil
|
||||
}
|
||||
|
||||
gui.SubProcess = gui.GitCommand.PrepareCommitAmendSubProcess()
|
||||
g.Update(func(g *gocui.Gui) error {
|
||||
subProcess, err := gui.GitCommand.RewordCommit(gui.State.Commits, gui.State.Panels.Commits.SelectedLine)
|
||||
if err != nil {
|
||||
return gui.createErrorPanel(gui.g, err.Error())
|
||||
}
|
||||
if subProcess != nil {
|
||||
gui.SubProcess = subProcess
|
||||
return gui.Errors.ErrSubProcess
|
||||
})
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) getSelectedCommit(g *gocui.Gui) (commands.Commit, error) {
|
||||
v, err := g.View("commits")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
// handleMidRebaseCommand sees if the selected commit is in fact a rebasing
|
||||
// commit meaning you are trying to edit the todo file rather than actually
|
||||
// begin a rebase. It then updates the todo file with that action
|
||||
func (gui *Gui) handleMidRebaseCommand(action string) (bool, error) {
|
||||
selectedCommit := gui.State.Commits[gui.State.Panels.Commits.SelectedLine]
|
||||
if selectedCommit.Status != "rebasing" {
|
||||
return false, nil
|
||||
}
|
||||
if len(gui.State.Commits) == 0 {
|
||||
return commands.Commit{}, errors.New(gui.Tr.SLocalize("NoCommitsThisBranch"))
|
||||
|
||||
// for now we do not support setting 'reword' because it requires an editor
|
||||
// and that means we either unconditionally wait around for the subprocess to ask for
|
||||
// our input or we set a lazygit client as the EDITOR env variable and have it
|
||||
// request us to edit the commit message when prompted.
|
||||
if action == "reword" {
|
||||
return true, gui.createErrorPanel(gui.g, gui.Tr.SLocalize("rewordNotSupported"))
|
||||
}
|
||||
lineNumber := gui.getItemPosition(v)
|
||||
if lineNumber > len(gui.State.Commits)-1 {
|
||||
gui.Log.Info(gui.Tr.SLocalize("PotentialErrInGetselectedCommit"), gui.State.Commits, lineNumber)
|
||||
return gui.State.Commits[len(gui.State.Commits)-1], nil
|
||||
|
||||
if err := gui.GitCommand.EditRebaseTodo(gui.State.Panels.Commits.SelectedLine, action); err != nil {
|
||||
return false, gui.createErrorPanel(gui.g, err.Error())
|
||||
}
|
||||
return gui.State.Commits[lineNumber], nil
|
||||
return true, gui.refreshCommits(gui.g)
|
||||
}
|
||||
|
||||
// handleMoveTodoDown like handleMidRebaseCommand but for moving an item up in the todo list
|
||||
func (gui *Gui) handleMoveTodoDown(index int) (bool, error) {
|
||||
selectedCommit := gui.State.Commits[index]
|
||||
if selectedCommit.Status != "rebasing" {
|
||||
return false, nil
|
||||
}
|
||||
if gui.State.Commits[index+1].Status != "rebasing" {
|
||||
return true, nil
|
||||
}
|
||||
if err := gui.GitCommand.MoveTodoDown(index); err != nil {
|
||||
return true, gui.createErrorPanel(gui.g, err.Error())
|
||||
}
|
||||
return true, gui.refreshCommits(gui.g)
|
||||
}
|
||||
|
||||
func (gui *Gui) handleCommitDelete(g *gocui.Gui, v *gocui.View) error {
|
||||
applied, err := gui.handleMidRebaseCommand("drop")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if applied {
|
||||
return nil
|
||||
}
|
||||
|
||||
return gui.createConfirmationPanel(gui.g, v, true, gui.Tr.SLocalize("DeleteCommitTitle"), gui.Tr.SLocalize("DeleteCommitPrompt"), func(*gocui.Gui, *gocui.View) error {
|
||||
return gui.WithWaitingStatus(gui.Tr.SLocalize("DeletingStatus"), func() error {
|
||||
err := gui.GitCommand.InteractiveRebase(gui.State.Commits, gui.State.Panels.Commits.SelectedLine, "drop")
|
||||
return gui.handleGenericMergeCommandResult(err)
|
||||
})
|
||||
}, nil)
|
||||
}
|
||||
|
||||
func (gui *Gui) handleCommitMoveDown(g *gocui.Gui, v *gocui.View) error {
|
||||
index := gui.State.Panels.Commits.SelectedLine
|
||||
selectedCommit := gui.State.Commits[index]
|
||||
if selectedCommit.Status == "rebasing" {
|
||||
if gui.State.Commits[index+1].Status != "rebasing" {
|
||||
return nil
|
||||
}
|
||||
if err := gui.GitCommand.MoveTodoDown(index); err != nil {
|
||||
return gui.createErrorPanel(gui.g, err.Error())
|
||||
}
|
||||
gui.State.Panels.Commits.SelectedLine++
|
||||
return gui.refreshCommits(gui.g)
|
||||
}
|
||||
|
||||
return gui.WithWaitingStatus(gui.Tr.SLocalize("MovingStatus"), func() error {
|
||||
err := gui.GitCommand.MoveCommitDown(gui.State.Commits, index)
|
||||
if err == nil {
|
||||
gui.State.Panels.Commits.SelectedLine++
|
||||
}
|
||||
return gui.handleGenericMergeCommandResult(err)
|
||||
})
|
||||
}
|
||||
|
||||
func (gui *Gui) handleCommitMoveUp(g *gocui.Gui, v *gocui.View) error {
|
||||
index := gui.State.Panels.Commits.SelectedLine
|
||||
if index == 0 {
|
||||
return nil
|
||||
}
|
||||
selectedCommit := gui.State.Commits[index]
|
||||
if selectedCommit.Status == "rebasing" {
|
||||
if err := gui.GitCommand.MoveTodoDown(index - 1); err != nil {
|
||||
return gui.createErrorPanel(gui.g, err.Error())
|
||||
}
|
||||
gui.State.Panels.Commits.SelectedLine--
|
||||
return gui.refreshCommits(gui.g)
|
||||
}
|
||||
|
||||
return gui.WithWaitingStatus(gui.Tr.SLocalize("MovingStatus"), func() error {
|
||||
err := gui.GitCommand.MoveCommitDown(gui.State.Commits, index-1)
|
||||
if err == nil {
|
||||
gui.State.Panels.Commits.SelectedLine--
|
||||
}
|
||||
return gui.handleGenericMergeCommandResult(err)
|
||||
})
|
||||
}
|
||||
|
||||
func (gui *Gui) handleCommitEdit(g *gocui.Gui, v *gocui.View) error {
|
||||
applied, err := gui.handleMidRebaseCommand("edit")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if applied {
|
||||
return nil
|
||||
}
|
||||
|
||||
return gui.WithWaitingStatus(gui.Tr.SLocalize("RebasingStatus"), func() error {
|
||||
err = gui.GitCommand.InteractiveRebase(gui.State.Commits, gui.State.Panels.Commits.SelectedLine, "edit")
|
||||
return gui.handleGenericMergeCommandResult(err)
|
||||
})
|
||||
}
|
||||
|
||||
func (gui *Gui) handleCommitAmendTo(g *gocui.Gui, v *gocui.View) error {
|
||||
return gui.createConfirmationPanel(gui.g, v, true, gui.Tr.SLocalize("AmendCommitTitle"), gui.Tr.SLocalize("AmendCommitPrompt"), func(*gocui.Gui, *gocui.View) error {
|
||||
return gui.WithWaitingStatus(gui.Tr.SLocalize("AmendingStatus"), func() error {
|
||||
err := gui.GitCommand.AmendTo(gui.State.Commits[gui.State.Panels.Commits.SelectedLine].Sha)
|
||||
return gui.handleGenericMergeCommandResult(err)
|
||||
})
|
||||
}, nil)
|
||||
}
|
||||
|
||||
func (gui *Gui) handleCommitPick(g *gocui.Gui, v *gocui.View) error {
|
||||
applied, err := gui.handleMidRebaseCommand("pick")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if applied {
|
||||
return nil
|
||||
}
|
||||
|
||||
// at this point we aren't actually rebasing so we will interpret this as an
|
||||
// attempt to pull. We might revoke this later after enabling configurable keybindings
|
||||
return gui.pullFiles(g, v)
|
||||
}
|
||||
|
||||
func (gui *Gui) handleCommitRevert(g *gocui.Gui, v *gocui.View) error {
|
||||
if err := gui.GitCommand.Revert(gui.State.Commits[gui.State.Panels.Commits.SelectedLine].Sha); err != nil {
|
||||
return gui.createErrorPanel(gui.g, err.Error())
|
||||
}
|
||||
gui.State.Panels.Commits.SelectedLine++
|
||||
return gui.refreshCommits(gui.g)
|
||||
}
|
||||
|
||||
func (gui *Gui) handleCopyCommit(g *gocui.Gui, v *gocui.View) error {
|
||||
// get currently selected commit, add the sha to state.
|
||||
commit := gui.State.Commits[gui.State.Panels.Commits.SelectedLine]
|
||||
|
||||
// we will un-copy it if it's already copied
|
||||
for index, cherryPickedCommit := range gui.State.CherryPickedCommits {
|
||||
if commit.Sha == cherryPickedCommit.Sha {
|
||||
gui.State.CherryPickedCommits = append(gui.State.CherryPickedCommits[0:index], gui.State.CherryPickedCommits[index+1:]...)
|
||||
return gui.refreshCommits(gui.g)
|
||||
}
|
||||
}
|
||||
|
||||
gui.addCommitToCherryPickedCommits(gui.State.Panels.Commits.SelectedLine)
|
||||
return gui.refreshCommits(gui.g)
|
||||
}
|
||||
|
||||
func (gui *Gui) addCommitToCherryPickedCommits(index int) {
|
||||
// not super happy with modifying the state of the Commits array here
|
||||
// but the alternative would be very tricky
|
||||
gui.State.Commits[index].Copied = true
|
||||
|
||||
newCommits := []*commands.Commit{}
|
||||
for _, commit := range gui.State.Commits {
|
||||
if commit.Copied {
|
||||
// duplicating just the things we need to put in the rebase TODO list
|
||||
newCommits = append(newCommits, &commands.Commit{Name: commit.Name, Sha: commit.Sha})
|
||||
}
|
||||
}
|
||||
|
||||
gui.State.CherryPickedCommits = newCommits
|
||||
}
|
||||
|
||||
func (gui *Gui) handleCopyCommitRange(g *gocui.Gui, v *gocui.View) error {
|
||||
// whenever I add a commit, I need to make sure I retain its order
|
||||
|
||||
// find the last commit that is copied that's above our position
|
||||
// if there are none, startIndex = 0
|
||||
startIndex := 0
|
||||
for index, commit := range gui.State.Commits[0:gui.State.Panels.Commits.SelectedLine] {
|
||||
if commit.Copied {
|
||||
startIndex = index
|
||||
}
|
||||
}
|
||||
|
||||
gui.Log.Info("commit copy start index: " + strconv.Itoa(startIndex))
|
||||
|
||||
for index := startIndex; index <= gui.State.Panels.Commits.SelectedLine; index++ {
|
||||
gui.addCommitToCherryPickedCommits(index)
|
||||
}
|
||||
|
||||
return gui.refreshCommits(gui.g)
|
||||
}
|
||||
|
||||
// HandlePasteCommits begins a cherry-pick rebase with the commits the user has copied
|
||||
func (gui *Gui) HandlePasteCommits(g *gocui.Gui, v *gocui.View) error {
|
||||
return gui.createConfirmationPanel(g, v, true, gui.Tr.SLocalize("CherryPick"), gui.Tr.SLocalize("SureCherryPick"), func(g *gocui.Gui, v *gocui.View) error {
|
||||
return gui.WithWaitingStatus(gui.Tr.SLocalize("CherryPickingStatus"), func() error {
|
||||
err := gui.GitCommand.CherryPickCommits(gui.State.CherryPickedCommits)
|
||||
return gui.handleGenericMergeCommandResult(err)
|
||||
})
|
||||
}, nil)
|
||||
}
|
||||
|
||||
func (gui *Gui) handleSwitchToCommitFilesPanel(g *gocui.Gui, v *gocui.View) error {
|
||||
if err := gui.refreshCommitFilesView(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return gui.switchFocus(g, gui.getCommitsView(), gui.getCommitFilesView())
|
||||
}
|
||||
|
||||
func (gui *Gui) handleToggleDiffCommit(g *gocui.Gui, v *gocui.View) error {
|
||||
selectLimit := 2
|
||||
|
||||
// get selected commit
|
||||
commit := gui.getSelectedCommit(g)
|
||||
if commit == nil {
|
||||
return gui.renderString(g, "main", gui.Tr.SLocalize("NoCommitsThisBranch"))
|
||||
}
|
||||
|
||||
// if already selected commit delete
|
||||
if idx, has := gui.hasCommit(gui.State.DiffEntries, commit.Sha); has {
|
||||
gui.State.DiffEntries = gui.unchooseCommit(gui.State.DiffEntries, idx)
|
||||
} else {
|
||||
if len(gui.State.DiffEntries) == selectLimit {
|
||||
gui.State.DiffEntries = gui.unchooseCommit(gui.State.DiffEntries, 0)
|
||||
}
|
||||
gui.State.DiffEntries = append(gui.State.DiffEntries, commit)
|
||||
}
|
||||
|
||||
gui.setDiffMode()
|
||||
|
||||
// if selected two commits, display diff between
|
||||
if len(gui.State.DiffEntries) == selectLimit {
|
||||
commitText, err := gui.GitCommand.DiffCommits(gui.State.DiffEntries[0].Sha, gui.State.DiffEntries[1].Sha)
|
||||
|
||||
if err != nil {
|
||||
return gui.createErrorPanel(gui.g, err.Error())
|
||||
}
|
||||
|
||||
return gui.renderString(g, "main", commitText)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) setDiffMode() {
|
||||
v := gui.getCommitsView()
|
||||
if len(gui.State.DiffEntries) != 0 {
|
||||
gui.State.Panels.Commits.SpecificDiffMode = true
|
||||
v.Title = gui.Tr.SLocalize("CommitsDiffTitle")
|
||||
} else {
|
||||
gui.State.Panels.Commits.SpecificDiffMode = false
|
||||
v.Title = gui.Tr.SLocalize("CommitsTitle")
|
||||
}
|
||||
|
||||
gui.refreshCommits(gui.g)
|
||||
}
|
||||
|
||||
func (gui *Gui) hasCommit(commits []*commands.Commit, target string) (int, bool) {
|
||||
for idx, commit := range commits {
|
||||
if commit.Sha == target {
|
||||
return idx, true
|
||||
}
|
||||
}
|
||||
return -1, false
|
||||
}
|
||||
|
||||
func (gui *Gui) unchooseCommit(commits []*commands.Commit, i int) []*commands.Commit {
|
||||
return append(commits[:i], commits[i+1:]...)
|
||||
}
|
||||
|
||||
func (gui *Gui) handleCreateFixupCommit(g *gocui.Gui, v *gocui.View) error {
|
||||
commit := gui.getSelectedCommit(g)
|
||||
if commit == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return gui.createConfirmationPanel(g, v, true, gui.Tr.SLocalize("CreateFixupCommit"), gui.Tr.TemplateLocalize(
|
||||
"SureCreateFixupCommit",
|
||||
Teml{
|
||||
"commit": commit.Sha,
|
||||
},
|
||||
), func(g *gocui.Gui, v *gocui.View) error {
|
||||
if err := gui.GitCommand.CreateFixupCommit(commit.Sha); err != nil {
|
||||
return gui.createErrorPanel(g, err.Error())
|
||||
}
|
||||
|
||||
return gui.refreshSidePanels(gui.g)
|
||||
}, nil)
|
||||
}
|
||||
|
||||
func (gui *Gui) handleSquashAllAboveFixupCommits(g *gocui.Gui, v *gocui.View) error {
|
||||
commit := gui.getSelectedCommit(g)
|
||||
if commit == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return gui.createConfirmationPanel(g, v, true, gui.Tr.SLocalize("SquashAboveCommits"), gui.Tr.TemplateLocalize(
|
||||
"SureSquashAboveCommits",
|
||||
Teml{
|
||||
"commit": commit.Sha,
|
||||
},
|
||||
), func(g *gocui.Gui, v *gocui.View) error {
|
||||
return gui.WithWaitingStatus(gui.Tr.SLocalize("SquashingStatus"), func() error {
|
||||
err := gui.GitCommand.SquashAllAboveFixupCommits(commit.Sha)
|
||||
return gui.handleGenericMergeCommandResult(err)
|
||||
})
|
||||
}, nil)
|
||||
}
|
||||
|
||||
type resetOption struct {
|
||||
description string
|
||||
command string
|
||||
}
|
||||
|
||||
// GetDisplayStrings is a function.
|
||||
func (r *resetOption) GetDisplayStrings(isFocused bool) []string {
|
||||
return []string{r.description, color.New(color.FgRed).Sprint(r.command)}
|
||||
}
|
||||
|
||||
func (gui *Gui) handleCreateCommitResetMenu(g *gocui.Gui, v *gocui.View) error {
|
||||
commit := gui.getSelectedCommit(g)
|
||||
if commit == nil {
|
||||
return gui.createErrorPanel(gui.g, gui.Tr.SLocalize("NoCommitsThisBranch"))
|
||||
}
|
||||
|
||||
strengths := []string{"soft", "mixed", "hard"}
|
||||
options := make([]*resetOption, len(strengths))
|
||||
for i, strength := range strengths {
|
||||
options[i] = &resetOption{
|
||||
description: fmt.Sprintf("%s reset", strength),
|
||||
command: fmt.Sprintf("reset --%s %s", strength, commit.Sha),
|
||||
}
|
||||
}
|
||||
|
||||
handleMenuPress := func(index int) error {
|
||||
if err := gui.GitCommand.ResetToCommit(commit.Sha, strengths[index]); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := gui.refreshCommits(g); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := gui.refreshFiles(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := gui.resetOrigin(gui.getCommitsView()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
gui.State.Panels.Commits.SelectedLine = 0
|
||||
return gui.handleCommitSelect(g, gui.getCommitsView())
|
||||
}
|
||||
|
||||
return gui.createMenu(fmt.Sprintf("%s %s", gui.Tr.SLocalize("resetTo"), commit.Sha), options, len(options), handleMenuPress)
|
||||
}
|
||||
|
||||
@@ -8,47 +8,56 @@ package gui
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/theme"
|
||||
)
|
||||
|
||||
func (gui *Gui) wrappedConfirmationFunction(function func(*gocui.Gui, *gocui.View) error) func(*gocui.Gui, *gocui.View) error {
|
||||
func (gui *Gui) wrappedConfirmationFunction(function func(*gocui.Gui, *gocui.View) error, returnFocusOnClose bool) func(*gocui.Gui, *gocui.View) error {
|
||||
return func(g *gocui.Gui, v *gocui.View) error {
|
||||
if function != nil {
|
||||
if err := function(g, v); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return gui.closeConfirmationPrompt(g)
|
||||
return gui.closeConfirmationPrompt(g, returnFocusOnClose)
|
||||
}
|
||||
}
|
||||
|
||||
func (gui *Gui) closeConfirmationPrompt(g *gocui.Gui) error {
|
||||
func (gui *Gui) closeConfirmationPrompt(g *gocui.Gui, returnFocusOnClose bool) error {
|
||||
view, err := g.View("confirmation")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
return nil // if it's already been closed we can just return
|
||||
}
|
||||
if err := gui.returnFocus(g, view); err != nil {
|
||||
panic(err)
|
||||
if returnFocusOnClose {
|
||||
if err := gui.returnFocus(g, view); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
g.DeleteKeybindings("confirmation")
|
||||
return g.DeleteView("confirmation")
|
||||
}
|
||||
|
||||
func (gui *Gui) getMessageHeight(message string, width int) int {
|
||||
func (gui *Gui) getMessageHeight(wrap bool, message string, width int) int {
|
||||
lines := strings.Split(message, "\n")
|
||||
lineCount := 0
|
||||
for _, line := range lines {
|
||||
lineCount += len(line)/width + 1
|
||||
// if we need to wrap, calculate height to fit content within view's width
|
||||
if wrap {
|
||||
for _, line := range lines {
|
||||
lineCount += len(line)/width + 1
|
||||
}
|
||||
} else {
|
||||
lineCount = len(lines)
|
||||
}
|
||||
return lineCount
|
||||
}
|
||||
|
||||
func (gui *Gui) getConfirmationPanelDimensions(g *gocui.Gui, prompt string) (int, int, int, int) {
|
||||
func (gui *Gui) getConfirmationPanelDimensions(g *gocui.Gui, wrap bool, prompt string) (int, int, int, int) {
|
||||
width, height := g.Size()
|
||||
panelWidth := width / 2
|
||||
panelHeight := gui.getMessageHeight(prompt, panelWidth)
|
||||
panelWidth := 4 * width / 7
|
||||
panelHeight := gui.getMessageHeight(wrap, prompt, panelWidth)
|
||||
return width/2 - panelWidth/2,
|
||||
height/2 - panelHeight/2 - panelHeight%2 - 1,
|
||||
width/2 + panelWidth/2,
|
||||
@@ -57,42 +66,57 @@ func (gui *Gui) getConfirmationPanelDimensions(g *gocui.Gui, prompt string) (int
|
||||
|
||||
func (gui *Gui) createPromptPanel(g *gocui.Gui, currentView *gocui.View, title string, handleConfirm func(*gocui.Gui, *gocui.View) error) error {
|
||||
gui.onNewPopupPanel()
|
||||
confirmationView, err := gui.prepareConfirmationPanel(currentView, title, "")
|
||||
confirmationView, err := gui.prepareConfirmationPanel(currentView, title, "", false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
confirmationView.Editable = true
|
||||
return gui.setKeyBindings(g, handleConfirm, nil)
|
||||
// in the future we might want to give createPromptPanel the returnFocusOnClose arg too, but for now we're always setting it to true
|
||||
return gui.setKeyBindings(g, handleConfirm, nil, true)
|
||||
}
|
||||
|
||||
func (gui *Gui) prepareConfirmationPanel(currentView *gocui.View, title, prompt string) (*gocui.View, error) {
|
||||
x0, y0, x1, y1 := gui.getConfirmationPanelDimensions(gui.g, prompt)
|
||||
func (gui *Gui) prepareConfirmationPanel(currentView *gocui.View, title, prompt string, hasLoader bool) (*gocui.View, error) {
|
||||
x0, y0, x1, y1 := gui.getConfirmationPanelDimensions(gui.g, true, prompt)
|
||||
confirmationView, err := gui.g.SetView("confirmation", x0, y0, x1, y1, 0)
|
||||
if err != nil {
|
||||
if err != gocui.ErrUnknownView {
|
||||
if err.Error() != "unknown view" {
|
||||
return nil, err
|
||||
}
|
||||
confirmationView.HasLoader = hasLoader
|
||||
confirmationView.Title = title
|
||||
confirmationView.FgColor = gocui.ColorWhite
|
||||
}
|
||||
confirmationView.Clear()
|
||||
|
||||
if err := gui.switchFocus(gui.g, currentView, confirmationView); err != nil {
|
||||
return nil, err
|
||||
confirmationView.Wrap = true
|
||||
confirmationView.FgColor = theme.GocuiDefaultTextColor
|
||||
}
|
||||
gui.g.Update(func(g *gocui.Gui) error {
|
||||
return gui.switchFocus(gui.g, currentView, confirmationView)
|
||||
})
|
||||
return confirmationView, nil
|
||||
}
|
||||
|
||||
func (gui *Gui) onNewPopupPanel() {
|
||||
gui.g.SetViewOnBottom("commitMessage")
|
||||
viewNames := []string{"commitMessage",
|
||||
"credentials",
|
||||
"menu"}
|
||||
for _, viewName := range viewNames {
|
||||
_, _ = gui.g.SetViewOnBottom(viewName)
|
||||
}
|
||||
}
|
||||
|
||||
func (gui *Gui) createConfirmationPanel(g *gocui.Gui, currentView *gocui.View, title, prompt string, handleConfirm, handleClose func(*gocui.Gui, *gocui.View) error) error {
|
||||
func (gui *Gui) createLoaderPanel(g *gocui.Gui, currentView *gocui.View, prompt string) error {
|
||||
return gui.createPopupPanel(g, currentView, "", prompt, true, true, nil, nil)
|
||||
}
|
||||
|
||||
// it is very important that within this function we never include the original prompt in any error messages, because it may contain e.g. a user password
|
||||
func (gui *Gui) createConfirmationPanel(g *gocui.Gui, currentView *gocui.View, returnFocusOnClose bool, title, prompt string, handleConfirm, handleClose func(*gocui.Gui, *gocui.View) error) error {
|
||||
return gui.createPopupPanel(g, currentView, title, prompt, false, returnFocusOnClose, handleConfirm, handleClose)
|
||||
}
|
||||
|
||||
func (gui *Gui) createPopupPanel(g *gocui.Gui, currentView *gocui.View, title, prompt string, hasLoader bool, returnFocusOnClose bool, handleConfirm, handleClose func(*gocui.Gui, *gocui.View) error) error {
|
||||
gui.onNewPopupPanel()
|
||||
g.Update(func(g *gocui.Gui) error {
|
||||
// delete the existing confirmation panel if it exists
|
||||
if view, _ := g.View("confirmation"); view != nil {
|
||||
if err := gui.closeConfirmationPrompt(g); err != nil {
|
||||
if err := gui.closeConfirmationPrompt(g, true); err != nil {
|
||||
errMessage := gui.Tr.TemplateLocalize(
|
||||
"CantCloseConfirmationPrompt",
|
||||
Teml{
|
||||
@@ -102,7 +126,7 @@ func (gui *Gui) createConfirmationPanel(g *gocui.Gui, currentView *gocui.View, t
|
||||
gui.Log.Error(errMessage)
|
||||
}
|
||||
}
|
||||
confirmationView, err := gui.prepareConfirmationPanel(currentView, title, prompt)
|
||||
confirmationView, err := gui.prepareConfirmationPanel(currentView, title, prompt, hasLoader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -110,12 +134,12 @@ func (gui *Gui) createConfirmationPanel(g *gocui.Gui, currentView *gocui.View, t
|
||||
if err := gui.renderString(g, "confirmation", prompt); err != nil {
|
||||
return err
|
||||
}
|
||||
return gui.setKeyBindings(g, handleConfirm, handleClose)
|
||||
return gui.setKeyBindings(g, handleConfirm, handleClose, returnFocusOnClose)
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) setKeyBindings(g *gocui.Gui, handleConfirm, handleClose func(*gocui.Gui, *gocui.View) error) error {
|
||||
func (gui *Gui) setKeyBindings(g *gocui.Gui, handleConfirm, handleClose func(*gocui.Gui, *gocui.View) error, returnFocusOnClose bool) error {
|
||||
actions := gui.Tr.TemplateLocalize(
|
||||
"CloseConfirm",
|
||||
Teml{
|
||||
@@ -126,20 +150,37 @@ func (gui *Gui) setKeyBindings(g *gocui.Gui, handleConfirm, handleClose func(*go
|
||||
if err := gui.renderString(g, "options", actions); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := g.SetKeybinding("confirmation", gocui.KeyEnter, gocui.ModNone, gui.wrappedConfirmationFunction(handleConfirm)); err != nil {
|
||||
if err := g.SetKeybinding("confirmation", gocui.KeyEnter, gocui.ModNone, gui.wrappedConfirmationFunction(handleConfirm, returnFocusOnClose)); err != nil {
|
||||
return err
|
||||
}
|
||||
return g.SetKeybinding("confirmation", gocui.KeyEsc, gocui.ModNone, gui.wrappedConfirmationFunction(handleClose))
|
||||
return g.SetKeybinding("confirmation", gocui.KeyEsc, gocui.ModNone, gui.wrappedConfirmationFunction(handleClose, returnFocusOnClose))
|
||||
}
|
||||
|
||||
func (gui *Gui) createMessagePanel(g *gocui.Gui, currentView *gocui.View, title, prompt string) error {
|
||||
return gui.createConfirmationPanel(g, currentView, title, prompt, nil, nil)
|
||||
return gui.createPopupPanel(g, currentView, title, prompt, false, true, nil, nil)
|
||||
}
|
||||
|
||||
// createSpecificErrorPanel allows you to create an error popup, specifying the
|
||||
// view to be focused when the user closes the popup, and a boolean specifying
|
||||
// whether we will log the error. If the message may include a user password,
|
||||
// this function is to be used over the more generic createErrorPanel, with
|
||||
// willLog set to false
|
||||
func (gui *Gui) createSpecificErrorPanel(message string, nextView *gocui.View, willLog bool) error {
|
||||
if willLog {
|
||||
go func() {
|
||||
// when reporting is switched on this log call sometimes introduces
|
||||
// a delay on the error panel popping up. Here I'm adding a second wait
|
||||
// so that the error is logged while the user is reading the error message
|
||||
time.Sleep(time.Second)
|
||||
gui.Log.Error(message)
|
||||
}()
|
||||
}
|
||||
|
||||
colorFunction := color.New(color.FgRed).SprintFunc()
|
||||
coloredMessage := colorFunction(strings.TrimSpace(message))
|
||||
return gui.createConfirmationPanel(gui.g, nextView, true, gui.Tr.SLocalize("Error"), coloredMessage, nil, nil)
|
||||
}
|
||||
|
||||
func (gui *Gui) createErrorPanel(g *gocui.Gui, message string) error {
|
||||
gui.Log.Error(message)
|
||||
currentView := g.CurrentView()
|
||||
colorFunction := color.New(color.FgRed).SprintFunc()
|
||||
coloredMessage := colorFunction(strings.TrimSpace(message))
|
||||
return gui.createConfirmationPanel(g, currentView, gui.Tr.SLocalize("Error"), coloredMessage, nil, nil)
|
||||
return gui.createSpecificErrorPanel(message, g.CurrentView(), true)
|
||||
}
|
||||
|
||||
45
pkg/gui/context.go
Normal file
45
pkg/gui/context.go
Normal file
@@ -0,0 +1,45 @@
|
||||
package gui
|
||||
|
||||
func (gui *Gui) changeContext(context string) error {
|
||||
oldContext := gui.State.Context
|
||||
|
||||
if gui.State.Context == context {
|
||||
return nil
|
||||
}
|
||||
|
||||
contextMap := gui.GetContextMap()
|
||||
|
||||
oldBindings := contextMap[oldContext]
|
||||
for _, binding := range oldBindings {
|
||||
if err := gui.g.DeleteKeybinding(binding.ViewName, binding.Key, binding.Modifier); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
bindings := contextMap[context]
|
||||
for _, binding := range bindings {
|
||||
if err := gui.g.SetKeybinding(binding.ViewName, binding.Key, binding.Modifier, binding.Handler); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
gui.State.Context = context
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) setInitialContext() error {
|
||||
contextMap := gui.GetContextMap()
|
||||
|
||||
initialContext := "normal"
|
||||
|
||||
bindings := contextMap[initialContext]
|
||||
for _, binding := range bindings {
|
||||
if err := gui.g.SetKeybinding(binding.ViewName, binding.Key, binding.Modifier, binding.Handler); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
gui.State.Context = initialContext
|
||||
|
||||
return nil
|
||||
}
|
||||
104
pkg/gui/credentials_panel.go
Normal file
104
pkg/gui/credentials_panel.go
Normal file
@@ -0,0 +1,104 @@
|
||||
package gui
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
)
|
||||
|
||||
type credentials chan string
|
||||
|
||||
// waitForPassUname wait for a username or password input from the credentials popup
|
||||
func (gui *Gui) waitForPassUname(g *gocui.Gui, currentView *gocui.View, passOrUname string) string {
|
||||
gui.credentials = make(chan string)
|
||||
g.Update(func(g *gocui.Gui) error {
|
||||
credentialsView, _ := g.View("credentials")
|
||||
if passOrUname == "username" {
|
||||
credentialsView.Title = gui.Tr.SLocalize("CredentialsUsername")
|
||||
credentialsView.Mask = 0
|
||||
} else {
|
||||
credentialsView.Title = gui.Tr.SLocalize("CredentialsPassword")
|
||||
credentialsView.Mask = '*'
|
||||
}
|
||||
err := gui.switchFocus(g, currentView, credentialsView)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
gui.RenderCommitLength()
|
||||
return nil
|
||||
})
|
||||
|
||||
// wait for username/passwords input
|
||||
userInput := <-gui.credentials
|
||||
return userInput + "\n"
|
||||
}
|
||||
|
||||
func (gui *Gui) handleSubmitCredential(g *gocui.Gui, v *gocui.View) error {
|
||||
message := gui.trimmedContent(v)
|
||||
gui.credentials <- message
|
||||
err := gui.refreshFiles()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
v.Clear()
|
||||
err = v.SetCursor(0, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = g.SetViewOnBottom("credentials")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
nextView, err := gui.g.View("confirmation")
|
||||
if err != nil {
|
||||
nextView = gui.getFilesView()
|
||||
}
|
||||
err = gui.switchFocus(g, nil, nextView)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return gui.refreshCommits(g)
|
||||
}
|
||||
|
||||
func (gui *Gui) handleCloseCredentialsView(g *gocui.Gui, v *gocui.View) error {
|
||||
_, err := g.SetViewOnBottom("credentials")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
gui.credentials <- ""
|
||||
return gui.switchFocus(g, nil, gui.getFilesView())
|
||||
}
|
||||
|
||||
func (gui *Gui) handleCredentialsViewFocused(g *gocui.Gui, v *gocui.View) error {
|
||||
if _, err := g.SetViewOnTop("credentials"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
message := gui.Tr.TemplateLocalize(
|
||||
"CloseConfirm",
|
||||
Teml{
|
||||
"keyBindClose": "esc",
|
||||
"keyBindConfirm": "enter",
|
||||
},
|
||||
)
|
||||
return gui.renderString(g, "options", message)
|
||||
}
|
||||
|
||||
// HandleCredentialsPopup handles the views after executing a command that might ask for credentials
|
||||
func (gui *Gui) HandleCredentialsPopup(g *gocui.Gui, popupOpened bool, cmdErr error) {
|
||||
if popupOpened {
|
||||
_, _ = gui.g.SetViewOnBottom("credentials")
|
||||
}
|
||||
if cmdErr != nil {
|
||||
errMessage := cmdErr.Error()
|
||||
if strings.Contains(errMessage, "Invalid username or password") {
|
||||
errMessage = gui.Tr.SLocalize("PassUnameWrong")
|
||||
}
|
||||
// we are not logging this error because it may contain a password
|
||||
_ = gui.createSpecificErrorPanel(errMessage, gui.getFilesView(), false)
|
||||
} else {
|
||||
_ = gui.closeConfirmationPrompt(g, true)
|
||||
_ = gui.refreshSidePanels(g)
|
||||
}
|
||||
}
|
||||
@@ -7,16 +7,161 @@ import (
|
||||
|
||||
// "strings"
|
||||
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
)
|
||||
|
||||
func (gui *Gui) stagedFiles() []commands.File {
|
||||
// list panel functions
|
||||
|
||||
func (gui *Gui) getSelectedFile(g *gocui.Gui) (*commands.File, error) {
|
||||
selectedLine := gui.State.Panels.Files.SelectedLine
|
||||
if selectedLine == -1 {
|
||||
return &commands.File{}, gui.Errors.ErrNoFiles
|
||||
}
|
||||
|
||||
return gui.State.Files[selectedLine], nil
|
||||
}
|
||||
|
||||
func (gui *Gui) handleFilesClick(g *gocui.Gui, v *gocui.View) error {
|
||||
if gui.popupPanelFocused() {
|
||||
return nil
|
||||
}
|
||||
|
||||
prevSelectedLineIdx := gui.State.Panels.Files.SelectedLine
|
||||
newSelectedLineIdx := v.SelectedLineIdx()
|
||||
|
||||
if newSelectedLineIdx > len(gui.State.Files)-1 {
|
||||
return gui.handleFileSelect(gui.g, v, false)
|
||||
}
|
||||
|
||||
gui.State.Panels.Files.SelectedLine = newSelectedLineIdx
|
||||
|
||||
if prevSelectedLineIdx == newSelectedLineIdx && gui.currentViewName() == v.Name() {
|
||||
return gui.handleFilePress(gui.g, v)
|
||||
} else {
|
||||
return gui.handleFileSelect(gui.g, v, true)
|
||||
}
|
||||
}
|
||||
|
||||
func (gui *Gui) handleFileSelect(g *gocui.Gui, v *gocui.View, alreadySelected bool) error {
|
||||
if _, err := gui.g.SetCurrentView(v.Name()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
file, err := gui.getSelectedFile(g)
|
||||
if err != nil {
|
||||
if err != gui.Errors.ErrNoFiles {
|
||||
return err
|
||||
}
|
||||
return gui.renderString(g, "main", gui.Tr.SLocalize("NoChangedFiles"))
|
||||
}
|
||||
|
||||
if err := gui.focusPoint(0, gui.State.Panels.Files.SelectedLine, len(gui.State.Files), v); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if file.HasInlineMergeConflicts {
|
||||
return gui.refreshMergePanel()
|
||||
}
|
||||
|
||||
content := gui.GitCommand.Diff(file, false, false)
|
||||
contentCached := gui.GitCommand.Diff(file, false, true)
|
||||
leftContent := content
|
||||
if file.HasStagedChanges && file.HasUnstagedChanges {
|
||||
gui.State.SplitMainPanel = true
|
||||
gui.getMainView().Title = gui.Tr.SLocalize("UnstagedChanges")
|
||||
gui.getSecondaryView().Title = gui.Tr.SLocalize("StagedChanges")
|
||||
} else {
|
||||
gui.State.SplitMainPanel = false
|
||||
if file.HasUnstagedChanges {
|
||||
leftContent = content
|
||||
gui.getMainView().Title = gui.Tr.SLocalize("UnstagedChanges")
|
||||
} else {
|
||||
leftContent = contentCached
|
||||
gui.getMainView().Title = gui.Tr.SLocalize("StagedChanges")
|
||||
}
|
||||
}
|
||||
|
||||
if alreadySelected {
|
||||
g.Update(func(*gocui.Gui) error {
|
||||
if err := gui.setViewContent(gui.g, gui.getSecondaryView(), contentCached); err != nil {
|
||||
return err
|
||||
}
|
||||
return gui.setViewContent(gui.g, gui.getMainView(), leftContent)
|
||||
})
|
||||
return nil
|
||||
}
|
||||
if err := gui.renderString(g, "secondary", contentCached); err != nil {
|
||||
return err
|
||||
}
|
||||
return gui.renderString(g, "main", leftContent)
|
||||
}
|
||||
|
||||
func (gui *Gui) refreshFiles() error {
|
||||
selectedFile, _ := gui.getSelectedFile(gui.g)
|
||||
|
||||
filesView := gui.getFilesView()
|
||||
if filesView == nil {
|
||||
// if the filesView hasn't been instantiated yet we just return
|
||||
return nil
|
||||
}
|
||||
if err := gui.refreshStateFiles(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
gui.g.Update(func(g *gocui.Gui) error {
|
||||
|
||||
filesView.Clear()
|
||||
isFocused := gui.g.CurrentView().Name() == "files"
|
||||
list, err := utils.RenderList(gui.State.Files, isFocused)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Fprint(filesView, list)
|
||||
|
||||
if filesView == g.CurrentView() {
|
||||
newSelectedFile, _ := gui.getSelectedFile(gui.g)
|
||||
alreadySelected := newSelectedFile.Name == selectedFile.Name
|
||||
return gui.handleFileSelect(g, filesView, alreadySelected)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) handleFilesNextLine(g *gocui.Gui, v *gocui.View) error {
|
||||
if gui.popupPanelFocused() {
|
||||
return nil
|
||||
}
|
||||
|
||||
panelState := gui.State.Panels.Files
|
||||
gui.changeSelectedLine(&panelState.SelectedLine, len(gui.State.Files), false)
|
||||
|
||||
return gui.handleFileSelect(gui.g, v, false)
|
||||
}
|
||||
|
||||
func (gui *Gui) handleFilesPrevLine(g *gocui.Gui, v *gocui.View) error {
|
||||
if gui.popupPanelFocused() {
|
||||
return nil
|
||||
}
|
||||
|
||||
panelState := gui.State.Panels.Files
|
||||
gui.changeSelectedLine(&panelState.SelectedLine, len(gui.State.Files), true)
|
||||
|
||||
return gui.handleFileSelect(gui.g, v, false)
|
||||
}
|
||||
|
||||
// specific functions
|
||||
|
||||
func (gui *Gui) stagedFiles() []*commands.File {
|
||||
files := gui.State.Files
|
||||
result := make([]commands.File, 0)
|
||||
result := make([]*commands.File, 0)
|
||||
for _, file := range files {
|
||||
if file.HasStagedChanges {
|
||||
result = append(result, file)
|
||||
@@ -25,9 +170,9 @@ func (gui *Gui) stagedFiles() []commands.File {
|
||||
return result
|
||||
}
|
||||
|
||||
func (gui *Gui) trackedFiles() []commands.File {
|
||||
func (gui *Gui) trackedFiles() []*commands.File {
|
||||
files := gui.State.Files
|
||||
result := make([]commands.File, 0)
|
||||
result := make([]*commands.File, 0)
|
||||
for _, file := range files {
|
||||
if file.Tracked {
|
||||
result = append(result, file)
|
||||
@@ -44,6 +189,33 @@ func (gui *Gui) stageSelectedFile(g *gocui.Gui) error {
|
||||
return gui.GitCommand.StageFile(file.Name)
|
||||
}
|
||||
|
||||
func (gui *Gui) handleEnterFile(g *gocui.Gui, v *gocui.View) error {
|
||||
return gui.enterFile(false, -1)
|
||||
}
|
||||
|
||||
func (gui *Gui) enterFile(forceSecondaryFocused bool, selectedLineIdx int) error {
|
||||
file, err := gui.getSelectedFile(gui.g)
|
||||
if err != nil {
|
||||
if err != gui.Errors.ErrNoFiles {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if file.HasInlineMergeConflicts {
|
||||
return gui.handleSwitchToMerge(gui.g, gui.getFilesView())
|
||||
}
|
||||
if file.HasMergeConflicts {
|
||||
return gui.createErrorPanel(gui.g, gui.Tr.SLocalize("FileStagingRequirements"))
|
||||
}
|
||||
if err := gui.changeContext("staging"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := gui.switchFocus(gui.g, gui.getFilesView(), gui.getMainView()); err != nil {
|
||||
return err
|
||||
}
|
||||
return gui.refreshStagingPanel(forceSecondaryFocused, selectedLineIdx)
|
||||
}
|
||||
|
||||
func (gui *Gui) handleFilePress(g *gocui.Gui, v *gocui.View) error {
|
||||
file, err := gui.getSelectedFile(g)
|
||||
if err != nil {
|
||||
@@ -53,7 +225,7 @@ func (gui *Gui) handleFilePress(g *gocui.Gui, v *gocui.View) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if file.HasMergeConflicts {
|
||||
if file.HasInlineMergeConflicts {
|
||||
return gui.handleSwitchToMerge(g, v)
|
||||
}
|
||||
|
||||
@@ -63,11 +235,11 @@ func (gui *Gui) handleFilePress(g *gocui.Gui, v *gocui.View) error {
|
||||
gui.GitCommand.UnStageFile(file.Name, file.Tracked)
|
||||
}
|
||||
|
||||
if err := gui.refreshFiles(g); err != nil {
|
||||
if err := gui.refreshFiles(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return gui.handleFileSelect(g, v)
|
||||
return gui.handleFileSelect(g, v, true)
|
||||
}
|
||||
|
||||
func (gui *Gui) allFilesStaged() bool {
|
||||
@@ -90,71 +262,11 @@ func (gui *Gui) handleStageAll(g *gocui.Gui, v *gocui.View) error {
|
||||
_ = gui.createErrorPanel(g, err.Error())
|
||||
}
|
||||
|
||||
if err := gui.refreshFiles(g); err != nil {
|
||||
if err := gui.refreshFiles(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return gui.handleFileSelect(g, v)
|
||||
}
|
||||
|
||||
func (gui *Gui) handleAddPatch(g *gocui.Gui, v *gocui.View) error {
|
||||
file, err := gui.getSelectedFile(g)
|
||||
if err != nil {
|
||||
if err == gui.Errors.ErrNoFiles {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
if !file.HasUnstagedChanges {
|
||||
return gui.createErrorPanel(g, gui.Tr.SLocalize("FileHasNoUnstagedChanges"))
|
||||
}
|
||||
if !file.Tracked {
|
||||
return gui.createErrorPanel(g, gui.Tr.SLocalize("CannotGitAdd"))
|
||||
}
|
||||
|
||||
gui.SubProcess = gui.GitCommand.AddPatch(file.Name)
|
||||
return gui.Errors.ErrSubProcess
|
||||
}
|
||||
|
||||
func (gui *Gui) getSelectedFile(g *gocui.Gui) (commands.File, error) {
|
||||
if len(gui.State.Files) == 0 {
|
||||
return commands.File{}, gui.Errors.ErrNoFiles
|
||||
}
|
||||
filesView, err := g.View("files")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
lineNumber := gui.getItemPosition(filesView)
|
||||
return gui.State.Files[lineNumber], nil
|
||||
}
|
||||
|
||||
func (gui *Gui) handleFileRemove(g *gocui.Gui, v *gocui.View) error {
|
||||
file, err := gui.getSelectedFile(g)
|
||||
if err != nil {
|
||||
if err == gui.Errors.ErrNoFiles {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
var deleteVerb string
|
||||
if file.Tracked {
|
||||
deleteVerb = gui.Tr.SLocalize("checkout")
|
||||
} else {
|
||||
deleteVerb = gui.Tr.SLocalize("delete")
|
||||
}
|
||||
message := gui.Tr.TemplateLocalize(
|
||||
"SureTo",
|
||||
Teml{
|
||||
"deleteVerb": deleteVerb,
|
||||
"fileName": file.Name,
|
||||
},
|
||||
)
|
||||
return gui.createConfirmationPanel(g, v, strings.Title(deleteVerb)+" file", message, func(g *gocui.Gui, v *gocui.View) error {
|
||||
if err := gui.GitCommand.RemoveFile(file); err != nil {
|
||||
return err
|
||||
}
|
||||
return gui.refreshFiles(g)
|
||||
}, nil)
|
||||
return gui.handleFileSelect(g, v, false)
|
||||
}
|
||||
|
||||
func (gui *Gui) handleIgnoreFile(g *gocui.Gui, v *gocui.View) error {
|
||||
@@ -168,37 +280,30 @@ func (gui *Gui) handleIgnoreFile(g *gocui.Gui, v *gocui.View) error {
|
||||
if err := gui.GitCommand.Ignore(file.Name); err != nil {
|
||||
return gui.createErrorPanel(g, err.Error())
|
||||
}
|
||||
return gui.refreshFiles(g)
|
||||
return gui.refreshFiles()
|
||||
}
|
||||
|
||||
func (gui *Gui) renderfilesOptions(g *gocui.Gui, file *commands.File) error {
|
||||
return gui.renderGlobalOptions(g)
|
||||
}
|
||||
|
||||
func (gui *Gui) handleFileSelect(g *gocui.Gui, v *gocui.View) error {
|
||||
file, err := gui.getSelectedFile(g)
|
||||
if err != nil {
|
||||
if err != gui.Errors.ErrNoFiles {
|
||||
return err
|
||||
}
|
||||
gui.renderString(g, "main", gui.Tr.SLocalize("NoChangedFiles"))
|
||||
return gui.renderfilesOptions(g, nil)
|
||||
}
|
||||
gui.renderfilesOptions(g, &file)
|
||||
var content string
|
||||
if file.HasMergeConflicts {
|
||||
return gui.refreshMergePanel(g)
|
||||
func (gui *Gui) handleWIPCommitPress(g *gocui.Gui, filesView *gocui.View) error {
|
||||
skipHookPreifx := gui.Config.GetUserConfig().GetString("git.skipHookPrefix")
|
||||
if skipHookPreifx == "" {
|
||||
return gui.createErrorPanel(gui.g, gui.Tr.SLocalize("SkipHookPrefixNotConfigured"))
|
||||
}
|
||||
|
||||
content = gui.GitCommand.Diff(file)
|
||||
return gui.renderString(g, "main", content)
|
||||
if err := gui.renderString(g, "commitMessage", skipHookPreifx); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := gui.getCommitMessageView().SetCursor(len(skipHookPreifx), 0); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return gui.handleCommitPress(g, filesView)
|
||||
}
|
||||
|
||||
func (gui *Gui) handleCommitPress(g *gocui.Gui, filesView *gocui.View) error {
|
||||
if len(gui.stagedFiles()) == 0 && !gui.State.HasMergeConflicts {
|
||||
if len(gui.stagedFiles()) == 0 && gui.State.WorkingTreeState == "normal" {
|
||||
return gui.createErrorPanel(g, gui.Tr.SLocalize("NoStagedFilesToCommit"))
|
||||
}
|
||||
commitMessageView := gui.getCommitMessageView(g)
|
||||
commitMessageView := gui.getCommitMessageView()
|
||||
g.Update(func(g *gocui.Gui) error {
|
||||
g.SetViewOnTop("commitMessage")
|
||||
gui.switchFocus(g, filesView, commitMessageView)
|
||||
@@ -208,10 +313,34 @@ func (gui *Gui) handleCommitPress(g *gocui.Gui, filesView *gocui.View) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) handleAmendCommitPress(g *gocui.Gui, filesView *gocui.View) error {
|
||||
if len(gui.stagedFiles()) == 0 && gui.State.WorkingTreeState == "normal" {
|
||||
return gui.createErrorPanel(g, gui.Tr.SLocalize("NoStagedFilesToCommit"))
|
||||
}
|
||||
if len(gui.State.Commits) == 0 {
|
||||
return gui.createErrorPanel(g, gui.Tr.SLocalize("NoCommitToAmend"))
|
||||
}
|
||||
|
||||
title := strings.Title(gui.Tr.SLocalize("AmendLastCommit"))
|
||||
question := gui.Tr.SLocalize("SureToAmend")
|
||||
|
||||
return gui.createConfirmationPanel(g, filesView, true, title, question, func(g *gocui.Gui, v *gocui.View) error {
|
||||
ok, err := gui.runSyncOrAsyncCommand(gui.GitCommand.AmendHead())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
return gui.refreshSidePanels(g)
|
||||
}, nil)
|
||||
}
|
||||
|
||||
// handleCommitEditorPress - handle when the user wants to commit changes via
|
||||
// their editor rather than via the popup panel
|
||||
func (gui *Gui) handleCommitEditorPress(g *gocui.Gui, filesView *gocui.View) error {
|
||||
if len(gui.stagedFiles()) == 0 && !gui.State.HasMergeConflicts {
|
||||
if len(gui.stagedFiles()) == 0 && gui.State.WorkingTreeState == "normal" {
|
||||
return gui.createErrorPanel(g, gui.Tr.SLocalize("NoStagedFilesToCommit"))
|
||||
}
|
||||
gui.PrepareSubProcess(g, "git", "commit")
|
||||
@@ -227,21 +356,14 @@ func (gui *Gui) PrepareSubProcess(g *gocui.Gui, commands ...string) {
|
||||
}
|
||||
|
||||
func (gui *Gui) editFile(filename string) error {
|
||||
sub, err := gui.OSCommand.EditFile(filename)
|
||||
if err != nil {
|
||||
return gui.createErrorPanel(gui.g, err.Error())
|
||||
}
|
||||
if sub != nil {
|
||||
gui.SubProcess = sub
|
||||
return gui.Errors.ErrSubProcess
|
||||
}
|
||||
return nil
|
||||
_, err := gui.runSyncOrAsyncCommand(gui.OSCommand.EditFile(filename))
|
||||
return err
|
||||
}
|
||||
|
||||
func (gui *Gui) handleFileEdit(g *gocui.Gui, v *gocui.View) error {
|
||||
file, err := gui.getSelectedFile(g)
|
||||
if err != nil {
|
||||
return err
|
||||
return gui.createErrorPanel(gui.g, err.Error())
|
||||
}
|
||||
|
||||
return gui.editFile(file.Name)
|
||||
@@ -250,47 +372,21 @@ func (gui *Gui) handleFileEdit(g *gocui.Gui, v *gocui.View) error {
|
||||
func (gui *Gui) handleFileOpen(g *gocui.Gui, v *gocui.View) error {
|
||||
file, err := gui.getSelectedFile(g)
|
||||
if err != nil {
|
||||
return err
|
||||
return gui.createErrorPanel(gui.g, err.Error())
|
||||
}
|
||||
return gui.openFile(file.Name)
|
||||
}
|
||||
|
||||
func (gui *Gui) handleRefreshFiles(g *gocui.Gui, v *gocui.View) error {
|
||||
return gui.refreshFiles(g)
|
||||
return gui.refreshFiles()
|
||||
}
|
||||
|
||||
func (gui *Gui) refreshStateFiles() {
|
||||
func (gui *Gui) refreshStateFiles() error {
|
||||
// get files to stage
|
||||
files := gui.GitCommand.GetStatusFiles()
|
||||
gui.State.Files = gui.GitCommand.MergeStatusFiles(gui.State.Files, files)
|
||||
gui.updateHasMergeConflictStatus()
|
||||
}
|
||||
|
||||
func (gui *Gui) updateHasMergeConflictStatus() error {
|
||||
merging, err := gui.GitCommand.IsInMergeState()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
gui.State.HasMergeConflicts = merging
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) renderFile(file commands.File, filesView *gocui.View) {
|
||||
// potentially inefficient to be instantiating these color
|
||||
// objects with each render
|
||||
red := color.New(color.FgRed)
|
||||
green := color.New(color.FgGreen)
|
||||
if !file.Tracked && !file.HasStagedChanges {
|
||||
red.Fprintln(filesView, file.DisplayString)
|
||||
return
|
||||
}
|
||||
green.Fprint(filesView, file.DisplayString[0:1])
|
||||
red.Fprint(filesView, file.DisplayString[1:3])
|
||||
if file.HasUnstagedChanges {
|
||||
red.Fprintln(filesView, file.Name)
|
||||
} else {
|
||||
green.Fprintln(filesView, file.Name)
|
||||
}
|
||||
gui.refreshSelectedLine(&gui.State.Panels.Files.SelectedLine, len(gui.State.Files))
|
||||
return gui.updateWorkTreeState()
|
||||
}
|
||||
|
||||
func (gui *Gui) catSelectedFile(g *gocui.Gui) (string, error) {
|
||||
@@ -312,84 +408,68 @@ func (gui *Gui) catSelectedFile(g *gocui.Gui) (string, error) {
|
||||
return cat, nil
|
||||
}
|
||||
|
||||
func (gui *Gui) refreshFiles(g *gocui.Gui) error {
|
||||
filesView, err := g.View("files")
|
||||
if err != nil {
|
||||
func (gui *Gui) pullFiles(g *gocui.Gui, v *gocui.View) error {
|
||||
if err := gui.createLoaderPanel(gui.g, v, gui.Tr.SLocalize("PullWait")); err != nil {
|
||||
return err
|
||||
}
|
||||
gui.refreshStateFiles()
|
||||
filesView.Clear()
|
||||
for _, file := range gui.State.Files {
|
||||
gui.renderFile(file, filesView)
|
||||
}
|
||||
gui.correctCursor(filesView)
|
||||
if filesView == g.CurrentView() {
|
||||
gui.handleFileSelect(g, filesView)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) pullFiles(g *gocui.Gui, v *gocui.View) error {
|
||||
gui.createMessagePanel(g, v, "", gui.Tr.SLocalize("PullWait"))
|
||||
go func() {
|
||||
if err := gui.GitCommand.Pull(); err != nil {
|
||||
gui.createErrorPanel(g, err.Error())
|
||||
} else {
|
||||
gui.closeConfirmationPrompt(g)
|
||||
gui.refreshCommits(g)
|
||||
gui.refreshStatus(g)
|
||||
}
|
||||
gui.refreshFiles(g)
|
||||
unamePassOpend := false
|
||||
err := gui.GitCommand.Pull(func(passOrUname string) string {
|
||||
unamePassOpend = true
|
||||
return gui.waitForPassUname(g, v, passOrUname)
|
||||
})
|
||||
gui.HandleCredentialsPopup(g, unamePassOpend, err)
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) pushWithForceFlag(currentView *gocui.View, force bool) error {
|
||||
if err := gui.createMessagePanel(gui.g, currentView, "", gui.Tr.SLocalize("PushWait")); err != nil {
|
||||
func (gui *Gui) pushWithForceFlag(g *gocui.Gui, v *gocui.View, force bool) error {
|
||||
if err := gui.createLoaderPanel(gui.g, v, gui.Tr.SLocalize("PushWait")); err != nil {
|
||||
return err
|
||||
}
|
||||
go func() {
|
||||
unamePassOpend := false
|
||||
branchName := gui.State.Branches[0].Name
|
||||
if err := gui.GitCommand.Push(branchName, force); err != nil {
|
||||
_ = gui.createErrorPanel(gui.g, err.Error())
|
||||
} else {
|
||||
_ = gui.closeConfirmationPrompt(gui.g)
|
||||
_ = gui.refreshCommits(gui.g)
|
||||
_ = gui.refreshStatus(gui.g)
|
||||
}
|
||||
err := gui.GitCommand.Push(branchName, force, func(passOrUname string) string {
|
||||
unamePassOpend = true
|
||||
return gui.waitForPassUname(g, v, passOrUname)
|
||||
})
|
||||
gui.HandleCredentialsPopup(g, unamePassOpend, err)
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) pushFiles(g *gocui.Gui, v *gocui.View) error {
|
||||
// if we have pullables we'll ask if the user wants to force push
|
||||
_, pullables := gui.GitCommand.UpstreamDifferenceCount()
|
||||
_, pullables := gui.GitCommand.GetCurrentBranchUpstreamDifferenceCount()
|
||||
if pullables == "?" || pullables == "0" {
|
||||
return gui.pushWithForceFlag(v, false)
|
||||
return gui.pushWithForceFlag(g, v, false)
|
||||
}
|
||||
err := gui.createConfirmationPanel(g, nil, gui.Tr.SLocalize("ForcePush"), gui.Tr.SLocalize("ForcePushPrompt"), func(g *gocui.Gui, v *gocui.View) error {
|
||||
return gui.pushWithForceFlag(v, true)
|
||||
err := gui.createConfirmationPanel(g, nil, true, gui.Tr.SLocalize("ForcePush"), gui.Tr.SLocalize("ForcePushPrompt"), func(g *gocui.Gui, v *gocui.View) error {
|
||||
return gui.pushWithForceFlag(g, v, true)
|
||||
}, nil)
|
||||
return err
|
||||
}
|
||||
|
||||
func (gui *Gui) handleSwitchToMerge(g *gocui.Gui, v *gocui.View) error {
|
||||
mergeView, err := g.View("main")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
file, err := gui.getSelectedFile(g)
|
||||
if err != nil {
|
||||
if err != gui.Errors.ErrNoFiles {
|
||||
return err
|
||||
return gui.createErrorPanel(gui.g, err.Error())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if !file.HasMergeConflicts {
|
||||
if !file.HasInlineMergeConflicts {
|
||||
return gui.createErrorPanel(g, gui.Tr.SLocalize("FileNoMergeCons"))
|
||||
}
|
||||
gui.switchFocus(g, v, mergeView)
|
||||
return gui.refreshMergePanel(g)
|
||||
if err := gui.changeContext("merging"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := gui.switchFocus(g, v, gui.getMainView()); err != nil {
|
||||
return err
|
||||
}
|
||||
return gui.refreshMergePanel()
|
||||
}
|
||||
|
||||
func (gui *Gui) handleAbortMerge(g *gocui.Gui, v *gocui.View) error {
|
||||
@@ -398,16 +478,7 @@ func (gui *Gui) handleAbortMerge(g *gocui.Gui, v *gocui.View) error {
|
||||
}
|
||||
gui.createMessagePanel(g, v, "", gui.Tr.SLocalize("MergeAborted"))
|
||||
gui.refreshStatus(g)
|
||||
return gui.refreshFiles(g)
|
||||
}
|
||||
|
||||
func (gui *Gui) handleResetHard(g *gocui.Gui, v *gocui.View) error {
|
||||
return gui.createConfirmationPanel(g, v, gui.Tr.SLocalize("ClearFilePanel"), gui.Tr.SLocalize("SureResetHardHead"), func(g *gocui.Gui, v *gocui.View) error {
|
||||
if err := gui.GitCommand.ResetHard(); err != nil {
|
||||
gui.createErrorPanel(g, err.Error())
|
||||
}
|
||||
return gui.refreshFiles(g)
|
||||
}, nil)
|
||||
return gui.refreshFiles()
|
||||
}
|
||||
|
||||
func (gui *Gui) openFile(filename string) error {
|
||||
@@ -416,3 +487,191 @@ func (gui *Gui) openFile(filename string) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) anyFilesWithMergeConflicts() bool {
|
||||
for _, file := range gui.State.Files {
|
||||
if file.HasMergeConflicts {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type discardOption struct {
|
||||
handler func(fileName *commands.File) error
|
||||
description string
|
||||
}
|
||||
|
||||
type discardAllOption struct {
|
||||
handler func() error
|
||||
description string
|
||||
command string
|
||||
}
|
||||
|
||||
// GetDisplayStrings is a function.
|
||||
func (r *discardOption) GetDisplayStrings(isFocused bool) []string {
|
||||
return []string{r.description}
|
||||
}
|
||||
|
||||
// GetDisplayStrings is a function.
|
||||
func (r *discardAllOption) GetDisplayStrings(isFocused bool) []string {
|
||||
return []string{r.description, color.New(color.FgRed).Sprint(r.command)}
|
||||
}
|
||||
|
||||
func (gui *Gui) handleCreateDiscardMenu(g *gocui.Gui, v *gocui.View) error {
|
||||
file, err := gui.getSelectedFile(g)
|
||||
if err != nil {
|
||||
if err != gui.Errors.ErrNoFiles {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
options := []*discardOption{
|
||||
{
|
||||
description: gui.Tr.SLocalize("discardAllChanges"),
|
||||
handler: func(file *commands.File) error {
|
||||
return gui.GitCommand.DiscardAllFileChanges(file)
|
||||
},
|
||||
},
|
||||
{
|
||||
description: gui.Tr.SLocalize("cancel"),
|
||||
handler: func(file *commands.File) error {
|
||||
return nil
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if file.HasStagedChanges && file.HasUnstagedChanges {
|
||||
discardUnstagedChanges := &discardOption{
|
||||
description: gui.Tr.SLocalize("discardUnstagedChanges"),
|
||||
handler: func(file *commands.File) error {
|
||||
return gui.GitCommand.DiscardUnstagedFileChanges(file)
|
||||
},
|
||||
}
|
||||
|
||||
options = append(options[:1], append([]*discardOption{discardUnstagedChanges}, options[1:]...)...)
|
||||
}
|
||||
|
||||
handleMenuPress := func(index int) error {
|
||||
file, err := gui.getSelectedFile(g)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := options[index].handler(file); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return gui.refreshFiles()
|
||||
}
|
||||
|
||||
return gui.createMenu(file.Name, options, len(options), handleMenuPress)
|
||||
}
|
||||
|
||||
func (gui *Gui) handleCreateResetMenu(g *gocui.Gui, v *gocui.View) error {
|
||||
options := []*discardAllOption{
|
||||
{
|
||||
description: gui.Tr.SLocalize("discardAllChangesToAllFiles"),
|
||||
command: "reset --hard HEAD && git clean -fd",
|
||||
handler: func() error {
|
||||
return gui.GitCommand.ResetAndClean()
|
||||
},
|
||||
},
|
||||
{
|
||||
description: gui.Tr.SLocalize("discardAnyUnstagedChanges"),
|
||||
command: "git checkout -- .",
|
||||
handler: func() error {
|
||||
return gui.GitCommand.DiscardAnyUnstagedFileChanges()
|
||||
},
|
||||
},
|
||||
{
|
||||
description: gui.Tr.SLocalize("discardUntrackedFiles"),
|
||||
command: "git clean -fd",
|
||||
handler: func() error {
|
||||
return gui.GitCommand.RemoveUntrackedFiles()
|
||||
},
|
||||
},
|
||||
{
|
||||
description: gui.Tr.SLocalize("softReset"),
|
||||
command: "git reset --soft HEAD",
|
||||
handler: func() error {
|
||||
return gui.GitCommand.ResetSoftHead()
|
||||
},
|
||||
},
|
||||
{
|
||||
description: gui.Tr.SLocalize("hardReset"),
|
||||
command: "git reset --hard HEAD",
|
||||
handler: func() error {
|
||||
return gui.GitCommand.ResetHardHead()
|
||||
},
|
||||
},
|
||||
{
|
||||
description: gui.Tr.SLocalize("cancel"),
|
||||
handler: func() error {
|
||||
return nil
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
handleMenuPress := func(index int) error {
|
||||
if err := options[index].handler(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return gui.refreshFiles()
|
||||
}
|
||||
|
||||
return gui.createMenu("", options, len(options), handleMenuPress)
|
||||
}
|
||||
|
||||
func (gui *Gui) handleCustomCommand(g *gocui.Gui, v *gocui.View) error {
|
||||
return gui.createPromptPanel(g, v, gui.Tr.SLocalize("CustomCommand"), func(g *gocui.Gui, v *gocui.View) error {
|
||||
command := gui.trimmedContent(v)
|
||||
gui.SubProcess = gui.OSCommand.RunCustomCommand(command)
|
||||
return gui.Errors.ErrSubProcess
|
||||
})
|
||||
}
|
||||
|
||||
type stashOption struct {
|
||||
description string
|
||||
handler func() error
|
||||
}
|
||||
|
||||
// GetDisplayStrings is a function.
|
||||
func (o *stashOption) GetDisplayStrings(isFocused bool) []string {
|
||||
return []string{o.description}
|
||||
}
|
||||
|
||||
func (gui *Gui) handleCreateStashMenu(g *gocui.Gui, v *gocui.View) error {
|
||||
options := []*stashOption{
|
||||
{
|
||||
description: gui.Tr.SLocalize("stashAllChanges"),
|
||||
handler: func() error {
|
||||
return gui.handleStashSave(gui.GitCommand.StashSave)
|
||||
},
|
||||
},
|
||||
{
|
||||
description: gui.Tr.SLocalize("stashStagedChanges"),
|
||||
handler: func() error {
|
||||
return gui.handleStashSave(gui.GitCommand.StashSaveStagedChanges)
|
||||
},
|
||||
},
|
||||
{
|
||||
description: gui.Tr.SLocalize("cancel"),
|
||||
handler: func() error {
|
||||
return nil
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
handleMenuPress := func(index int) error {
|
||||
return options[index].handler()
|
||||
}
|
||||
|
||||
return gui.createMenu(gui.Tr.SLocalize("stashOptions"), options, len(options), handleMenuPress)
|
||||
}
|
||||
|
||||
func (gui *Gui) handleStashChanges(g *gocui.Gui, v *gocui.View) error {
|
||||
return gui.handleStashSave(gui.GitCommand.StashSave)
|
||||
}
|
||||
|
||||
761
pkg/gui/gui.go
761
pkg/gui/gui.go
@@ -1,30 +1,37 @@
|
||||
package gui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math"
|
||||
"os"
|
||||
"sync"
|
||||
|
||||
// "io"
|
||||
// "io/ioutil"
|
||||
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-errors/errors"
|
||||
|
||||
// "strings"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/golang-collections/collections/stack"
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands"
|
||||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
"github.com/jesseduffield/lazygit/pkg/i18n"
|
||||
"github.com/jesseduffield/lazygit/pkg/theme"
|
||||
"github.com/jesseduffield/lazygit/pkg/updates"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const StartupPopupVersion = 1
|
||||
|
||||
// OverlappingEdges determines if panel edges overlap
|
||||
var OverlappingEdges = false
|
||||
|
||||
@@ -33,6 +40,7 @@ var OverlappingEdges = false
|
||||
type SentinelErrors struct {
|
||||
ErrSubProcess error
|
||||
ErrNoFiles error
|
||||
ErrSwitchRepo error
|
||||
}
|
||||
|
||||
// GenerateSentinelErrors makes the sentinel errors for the gui. We're defining it here
|
||||
@@ -49,6 +57,7 @@ func (gui *Gui) GenerateSentinelErrors() {
|
||||
gui.Errors = SentinelErrors{
|
||||
ErrSubProcess: errors.New(gui.Tr.SLocalize("RunningSubprocess")),
|
||||
ErrNoFiles: errors.New(gui.Tr.SLocalize("NoChangedFiles")),
|
||||
ErrSwitchRepo: errors.New("switching repo"),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,37 +77,120 @@ type Gui struct {
|
||||
Errors SentinelErrors
|
||||
Updater *updates.Updater
|
||||
statusManager *statusManager
|
||||
credentials credentials
|
||||
waitForIntro sync.WaitGroup
|
||||
}
|
||||
|
||||
// for now the staging panel state, unlike the other panel states, is going to be
|
||||
// non-mutative, so that we don't accidentally end up
|
||||
// with mismatches of data. We might change this in the future
|
||||
type lineByLinePanelState struct {
|
||||
SelectedLineIdx int
|
||||
FirstLineIdx int
|
||||
LastLineIdx int
|
||||
Diff string
|
||||
PatchParser *commands.PatchParser
|
||||
SelectMode int // one of LINE, HUNK, or RANGE
|
||||
SecondaryFocused bool // this is for if we show the left or right panel
|
||||
}
|
||||
|
||||
type mergingPanelState struct {
|
||||
ConflictIndex int
|
||||
ConflictTop bool
|
||||
Conflicts []commands.Conflict
|
||||
EditHistory *stack.Stack
|
||||
}
|
||||
|
||||
type filePanelState struct {
|
||||
SelectedLine int
|
||||
}
|
||||
|
||||
type branchPanelState struct {
|
||||
SelectedLine int
|
||||
}
|
||||
|
||||
type commitPanelState struct {
|
||||
SelectedLine int
|
||||
SpecificDiffMode bool
|
||||
}
|
||||
|
||||
type stashPanelState struct {
|
||||
SelectedLine int
|
||||
}
|
||||
|
||||
type menuPanelState struct {
|
||||
SelectedLine int
|
||||
OnPress func(g *gocui.Gui, v *gocui.View) error
|
||||
}
|
||||
|
||||
type commitFilesPanelState struct {
|
||||
SelectedLine int
|
||||
}
|
||||
|
||||
type statusPanelState struct {
|
||||
pushables string
|
||||
pullables string
|
||||
}
|
||||
|
||||
type panelStates struct {
|
||||
Files *filePanelState
|
||||
Branches *branchPanelState
|
||||
Commits *commitPanelState
|
||||
Stash *stashPanelState
|
||||
Menu *menuPanelState
|
||||
LineByLine *lineByLinePanelState
|
||||
Merging *mergingPanelState
|
||||
CommitFiles *commitFilesPanelState
|
||||
Status *statusPanelState
|
||||
}
|
||||
|
||||
type guiState struct {
|
||||
Files []commands.File
|
||||
Branches []commands.Branch
|
||||
Commits []commands.Commit
|
||||
StashEntries []commands.StashEntry
|
||||
PreviousView string
|
||||
HasMergeConflicts bool
|
||||
ConflictIndex int
|
||||
ConflictTop bool
|
||||
Conflicts []commands.Conflict
|
||||
EditHistory *stack.Stack
|
||||
Platform commands.Platform
|
||||
Updating bool
|
||||
Keys []Binding
|
||||
Files []*commands.File
|
||||
Branches []*commands.Branch
|
||||
Commits []*commands.Commit
|
||||
StashEntries []*commands.StashEntry
|
||||
CommitFiles []*commands.CommitFile
|
||||
DiffEntries []*commands.Commit
|
||||
MenuItemCount int // can't store the actual list because it's of interface{} type
|
||||
PreviousView string
|
||||
Platform commands.Platform
|
||||
Updating bool
|
||||
Panels *panelStates
|
||||
WorkingTreeState string // one of "merging", "rebasing", "normal"
|
||||
Context string // important not to set this value directly but to use gui.changeContext("new context")
|
||||
CherryPickedCommits []*commands.Commit
|
||||
SplitMainPanel bool
|
||||
RetainOriginalDir bool
|
||||
}
|
||||
|
||||
// for now the split view will always be on
|
||||
|
||||
// NewGui builds a new gui handler
|
||||
func NewGui(log *logrus.Entry, gitCommand *commands.GitCommand, oSCommand *commands.OSCommand, tr *i18n.Localizer, config config.AppConfigurer, updater *updates.Updater) (*Gui, error) {
|
||||
|
||||
initialState := guiState{
|
||||
Files: make([]commands.File, 0),
|
||||
PreviousView: "files",
|
||||
Commits: make([]commands.Commit, 0),
|
||||
StashEntries: make([]commands.StashEntry, 0),
|
||||
ConflictIndex: 0,
|
||||
ConflictTop: true,
|
||||
Conflicts: make([]commands.Conflict, 0),
|
||||
EditHistory: stack.New(),
|
||||
Platform: *oSCommand.Platform,
|
||||
Files: make([]*commands.File, 0),
|
||||
PreviousView: "files",
|
||||
Commits: make([]*commands.Commit, 0),
|
||||
CherryPickedCommits: make([]*commands.Commit, 0),
|
||||
StashEntries: make([]*commands.StashEntry, 0),
|
||||
DiffEntries: make([]*commands.Commit, 0),
|
||||
Platform: *oSCommand.Platform,
|
||||
Panels: &panelStates{
|
||||
Files: &filePanelState{SelectedLine: -1},
|
||||
Branches: &branchPanelState{SelectedLine: 0},
|
||||
Commits: &commitPanelState{SelectedLine: -1},
|
||||
CommitFiles: &commitFilesPanelState{SelectedLine: -1},
|
||||
Stash: &stashPanelState{SelectedLine: -1},
|
||||
Menu: &menuPanelState{SelectedLine: 0},
|
||||
Merging: &mergingPanelState{
|
||||
ConflictIndex: 0,
|
||||
ConflictTop: true,
|
||||
Conflicts: []commands.Conflict{},
|
||||
EditHistory: stack.New(),
|
||||
},
|
||||
Status: &statusPanelState{},
|
||||
},
|
||||
}
|
||||
|
||||
gui := &Gui{
|
||||
@@ -117,22 +209,41 @@ func NewGui(log *logrus.Entry, gitCommand *commands.GitCommand, oSCommand *comma
|
||||
return gui, nil
|
||||
}
|
||||
|
||||
func (gui *Gui) scrollUpMain(g *gocui.Gui, v *gocui.View) error {
|
||||
mainView, _ := g.View("main")
|
||||
func (gui *Gui) scrollUpView(viewName string) error {
|
||||
mainView, _ := gui.g.View(viewName)
|
||||
ox, oy := mainView.Origin()
|
||||
if oy >= 1 {
|
||||
return mainView.SetOrigin(ox, oy-gui.Config.GetUserConfig().GetInt("gui.scrollHeight"))
|
||||
newOy := int(math.Max(0, float64(oy-gui.Config.GetUserConfig().GetInt("gui.scrollHeight"))))
|
||||
return mainView.SetOrigin(ox, newOy)
|
||||
}
|
||||
|
||||
func (gui *Gui) scrollDownView(viewName string) error {
|
||||
mainView, _ := gui.g.View(viewName)
|
||||
ox, oy := mainView.Origin()
|
||||
y := oy
|
||||
if !gui.Config.GetUserConfig().GetBool("gui.scrollPastBottom") {
|
||||
_, sy := mainView.Size()
|
||||
y += sy
|
||||
}
|
||||
if y < len(mainView.BufferLines()) {
|
||||
return mainView.SetOrigin(ox, oy+gui.Config.GetUserConfig().GetInt("gui.scrollHeight"))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) scrollUpMain(g *gocui.Gui, v *gocui.View) error {
|
||||
return gui.scrollUpView("main")
|
||||
}
|
||||
|
||||
func (gui *Gui) scrollDownMain(g *gocui.Gui, v *gocui.View) error {
|
||||
mainView, _ := g.View("main")
|
||||
ox, oy := mainView.Origin()
|
||||
if oy < len(mainView.BufferLines()) {
|
||||
return mainView.SetOrigin(ox, oy+gui.Config.GetUserConfig().GetInt("gui.scrollHeight"))
|
||||
}
|
||||
return nil
|
||||
return gui.scrollDownView("main")
|
||||
}
|
||||
|
||||
func (gui *Gui) scrollUpSecondary(g *gocui.Gui, v *gocui.View) error {
|
||||
return gui.scrollUpView("secondary")
|
||||
}
|
||||
|
||||
func (gui *Gui) scrollDownSecondary(g *gocui.Gui, v *gocui.View) error {
|
||||
return gui.scrollDownView("secondary")
|
||||
}
|
||||
|
||||
func (gui *Gui) handleRefresh(g *gocui.Gui, v *gocui.View) error {
|
||||
@@ -146,19 +257,143 @@ func max(a, b int) int {
|
||||
return b
|
||||
}
|
||||
|
||||
// getFocusLayout returns a manager function for when view gain and lose focus
|
||||
func (gui *Gui) getFocusLayout() func(g *gocui.Gui) error {
|
||||
var previousView *gocui.View
|
||||
return func(g *gocui.Gui) error {
|
||||
newView := gui.g.CurrentView()
|
||||
if err := gui.onFocusChange(); err != nil {
|
||||
return err
|
||||
}
|
||||
// for now we don't consider losing focus to a popup panel as actually losing focus
|
||||
if newView != previousView && !gui.isPopupPanel(newView.Name()) {
|
||||
if err := gui.onFocusLost(previousView, newView); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := gui.onFocus(newView); err != nil {
|
||||
return err
|
||||
}
|
||||
previousView = newView
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (gui *Gui) onFocusChange() error {
|
||||
currentView := gui.g.CurrentView()
|
||||
for _, view := range gui.g.Views() {
|
||||
view.Highlight = view == currentView
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) onFocusLost(v *gocui.View, newView *gocui.View) error {
|
||||
if v == nil {
|
||||
return nil
|
||||
}
|
||||
switch v.Name() {
|
||||
case "branches":
|
||||
// This stops the branches panel from showing the upstream/downstream changes to the selected branch, when it loses focus
|
||||
// inside renderListPanel it checks to see if the panel has focus
|
||||
if err := gui.renderListPanel(gui.getBranchesView(), gui.State.Branches); err != nil {
|
||||
return err
|
||||
}
|
||||
case "main":
|
||||
// if we have lost focus to a first-class panel, we need to do some cleanup
|
||||
if err := gui.changeContext("normal"); err != nil {
|
||||
return err
|
||||
}
|
||||
case "commitFiles":
|
||||
if gui.State.Context != "patch-building" {
|
||||
if _, err := gui.g.SetViewOnBottom(v.Name()); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
gui.Log.Info(v.Name() + " focus lost")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) onFocus(v *gocui.View) error {
|
||||
if v == nil {
|
||||
return nil
|
||||
}
|
||||
gui.Log.Info(v.Name() + " focus gained")
|
||||
return nil
|
||||
}
|
||||
|
||||
// layout is called for every screen re-render e.g. when the screen is resized
|
||||
func (gui *Gui) layout(g *gocui.Gui) error {
|
||||
g.Highlight = true
|
||||
width, height := g.Size()
|
||||
version := gui.Config.GetVersion()
|
||||
leftSideWidth := width / 3
|
||||
statusFilesBoundary := 2
|
||||
filesBranchesBoundary := 2 * height / 5 // height - 20
|
||||
commitsBranchesBoundary := 3 * height / 5 // height - 10
|
||||
commitsStashBoundary := height - 5 // height - 5
|
||||
optionsVersionBoundary := width - max(len(version), 1)
|
||||
minimumHeight := 16
|
||||
|
||||
information := gui.Config.GetVersion()
|
||||
if gui.g.Mouse {
|
||||
donate := color.New(color.FgMagenta, color.Underline).Sprint(gui.Tr.SLocalize("Donate"))
|
||||
information = donate + " " + information
|
||||
}
|
||||
|
||||
minimumHeight := 9
|
||||
minimumWidth := 10
|
||||
if height < minimumHeight || width < minimumWidth {
|
||||
v, err := g.SetView("limit", 0, 0, width-1, height-1, 0)
|
||||
if err != nil {
|
||||
if err.Error() != "unknown view" {
|
||||
return err
|
||||
}
|
||||
v.Title = gui.Tr.SLocalize("NotEnoughSpace")
|
||||
v.Wrap = true
|
||||
_, _ = g.SetViewOnTop("limit")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
currView := gui.g.CurrentView()
|
||||
currentCyclebleView := gui.State.PreviousView
|
||||
if currView != nil {
|
||||
viewName := currView.Name()
|
||||
usePreviouseView := true
|
||||
for _, view := range cyclableViews {
|
||||
if view == viewName {
|
||||
currentCyclebleView = viewName
|
||||
usePreviouseView = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if usePreviouseView {
|
||||
currentCyclebleView = gui.State.PreviousView
|
||||
}
|
||||
}
|
||||
|
||||
usableSpace := height - 7
|
||||
extraSpace := usableSpace - (usableSpace/3)*3
|
||||
|
||||
vHeights := map[string]int{
|
||||
"status": 3,
|
||||
"files": (usableSpace / 3) + extraSpace,
|
||||
"branches": usableSpace / 3,
|
||||
"commits": usableSpace / 3,
|
||||
"stash": 3,
|
||||
"options": 1,
|
||||
}
|
||||
|
||||
if height < 28 {
|
||||
defaultHeight := 3
|
||||
if height < 21 {
|
||||
defaultHeight = 1
|
||||
}
|
||||
vHeights = map[string]int{
|
||||
"status": defaultHeight,
|
||||
"files": defaultHeight,
|
||||
"branches": defaultHeight,
|
||||
"commits": defaultHeight,
|
||||
"stash": defaultHeight,
|
||||
"options": defaultHeight,
|
||||
}
|
||||
vHeights[currentCyclebleView] = height - defaultHeight*4 - 1
|
||||
}
|
||||
|
||||
optionsVersionBoundary := width - max(len(utils.Decolorise(information)), 1)
|
||||
|
||||
appStatus := gui.statusManager.getStatusString()
|
||||
appStatusOptionsBoundary := 0
|
||||
@@ -171,104 +406,143 @@ func (gui *Gui) layout(g *gocui.Gui) error {
|
||||
panelSpacing = 0
|
||||
}
|
||||
|
||||
if height < minimumHeight || width < minimumWidth {
|
||||
v, err := g.SetView("limit", 0, 0, max(width-1, 2), max(height-1, 2), 0)
|
||||
if err != nil {
|
||||
if err != gocui.ErrUnknownView {
|
||||
return err
|
||||
}
|
||||
v.Title = gui.Tr.SLocalize("NotEnoughSpace")
|
||||
v.Wrap = true
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
_, _ = g.SetViewOnBottom("limit")
|
||||
g.DeleteView("limit")
|
||||
|
||||
optionsTop := height - 2
|
||||
// hiding options if there's not enough space
|
||||
if height < 30 {
|
||||
optionsTop = height - 1
|
||||
textColor := theme.GocuiDefaultTextColor
|
||||
leftSideWidth := width / 3
|
||||
panelSplitX := width - 1
|
||||
if gui.State.SplitMainPanel {
|
||||
units := 7
|
||||
leftSideWidth = width / units
|
||||
panelSplitX = (1 + ((units - 1) / 2)) * width / units
|
||||
}
|
||||
|
||||
v, err := g.SetView("main", leftSideWidth+panelSpacing, 0, width-1, optionsTop, gocui.LEFT)
|
||||
main := "main"
|
||||
secondary := "secondary"
|
||||
swappingMainPanels := gui.State.Panels.LineByLine != nil && gui.State.Panels.LineByLine.SecondaryFocused
|
||||
if swappingMainPanels {
|
||||
main = "secondary"
|
||||
secondary = "main"
|
||||
}
|
||||
|
||||
v, err := g.SetView(main, leftSideWidth+panelSpacing, 0, panelSplitX, height-2, gocui.LEFT)
|
||||
if err != nil {
|
||||
if err != gocui.ErrUnknownView {
|
||||
if err.Error() != "unknown view" {
|
||||
return err
|
||||
}
|
||||
v.Title = gui.Tr.SLocalize("DiffTitle")
|
||||
v.Wrap = true
|
||||
v.FgColor = gocui.ColorWhite
|
||||
v.FgColor = textColor
|
||||
}
|
||||
|
||||
if v, err := g.SetView("status", 0, 0, leftSideWidth, statusFilesBoundary, gocui.BOTTOM|gocui.RIGHT); err != nil {
|
||||
if err != gocui.ErrUnknownView {
|
||||
hiddenViewOffset := 0
|
||||
if !gui.State.SplitMainPanel {
|
||||
hiddenViewOffset = 9999
|
||||
}
|
||||
secondaryView, err := g.SetView(secondary, panelSplitX+1+hiddenViewOffset, hiddenViewOffset, width-1+hiddenViewOffset, height-2+hiddenViewOffset, gocui.LEFT)
|
||||
if err != nil {
|
||||
if err.Error() != "unknown view" {
|
||||
return err
|
||||
}
|
||||
secondaryView.Title = gui.Tr.SLocalize("DiffTitle")
|
||||
secondaryView.Wrap = true
|
||||
secondaryView.FgColor = gocui.ColorWhite
|
||||
}
|
||||
|
||||
if v, err := g.SetView("status", 0, 0, leftSideWidth, vHeights["status"]-1, gocui.BOTTOM|gocui.RIGHT); err != nil {
|
||||
if err.Error() != "unknown view" {
|
||||
return err
|
||||
}
|
||||
v.Title = gui.Tr.SLocalize("StatusTitle")
|
||||
v.FgColor = gocui.ColorWhite
|
||||
v.FgColor = textColor
|
||||
}
|
||||
|
||||
filesView, err := g.SetView("files", 0, statusFilesBoundary+panelSpacing, leftSideWidth, filesBranchesBoundary, gocui.TOP|gocui.BOTTOM)
|
||||
filesView, err := g.SetViewBeneath("files", "status", vHeights["files"])
|
||||
if err != nil {
|
||||
if err != gocui.ErrUnknownView {
|
||||
if err.Error() != "unknown view" {
|
||||
return err
|
||||
}
|
||||
filesView.Highlight = true
|
||||
filesView.Title = gui.Tr.SLocalize("FilesTitle")
|
||||
v.FgColor = gocui.ColorWhite
|
||||
v.FgColor = textColor
|
||||
}
|
||||
|
||||
if v, err := g.SetView("branches", 0, filesBranchesBoundary+panelSpacing, leftSideWidth, commitsBranchesBoundary, gocui.TOP|gocui.BOTTOM); err != nil {
|
||||
if err != gocui.ErrUnknownView {
|
||||
branchesView, err := g.SetViewBeneath("branches", "files", vHeights["branches"])
|
||||
if err != nil {
|
||||
if err.Error() != "unknown view" {
|
||||
return err
|
||||
}
|
||||
v.Title = gui.Tr.SLocalize("BranchesTitle")
|
||||
v.FgColor = gocui.ColorWhite
|
||||
branchesView.Title = gui.Tr.SLocalize("BranchesTitle")
|
||||
branchesView.FgColor = textColor
|
||||
}
|
||||
|
||||
if v, err := g.SetView("commits", 0, commitsBranchesBoundary+panelSpacing, leftSideWidth, commitsStashBoundary, gocui.TOP|gocui.BOTTOM); err != nil {
|
||||
if err != gocui.ErrUnknownView {
|
||||
if v, err := g.SetViewBeneath("commitFiles", "branches", vHeights["commits"]); err != nil {
|
||||
if err.Error() != "unknown view" {
|
||||
return err
|
||||
}
|
||||
v.Title = gui.Tr.SLocalize("CommitsTitle")
|
||||
v.FgColor = gocui.ColorWhite
|
||||
v.Title = gui.Tr.SLocalize("CommitFiles")
|
||||
v.FgColor = textColor
|
||||
}
|
||||
|
||||
if v, err := g.SetView("stash", 0, commitsStashBoundary+panelSpacing, leftSideWidth, optionsTop, gocui.TOP|gocui.RIGHT); err != nil {
|
||||
if err != gocui.ErrUnknownView {
|
||||
commitsView, err := g.SetViewBeneath("commits", "branches", vHeights["commits"])
|
||||
if err != nil {
|
||||
if err.Error() != "unknown view" {
|
||||
return err
|
||||
}
|
||||
v.Title = gui.Tr.SLocalize("StashTitle")
|
||||
v.FgColor = gocui.ColorWhite
|
||||
commitsView.Title = gui.Tr.SLocalize("CommitsTitle")
|
||||
commitsView.FgColor = textColor
|
||||
}
|
||||
|
||||
if v, err := g.SetView("options", appStatusOptionsBoundary-1, optionsTop, optionsVersionBoundary-1, optionsTop+2, 0); err != nil {
|
||||
if err != gocui.ErrUnknownView {
|
||||
stashView, err := g.SetViewBeneath("stash", "commits", vHeights["stash"])
|
||||
if err != nil {
|
||||
if err.Error() != "unknown view" {
|
||||
return err
|
||||
}
|
||||
stashView.Title = gui.Tr.SLocalize("StashTitle")
|
||||
stashView.FgColor = textColor
|
||||
}
|
||||
|
||||
if v, err := g.SetView("options", appStatusOptionsBoundary-1, height-2, optionsVersionBoundary-1, height, 0); err != nil {
|
||||
if err.Error() != "unknown view" {
|
||||
return err
|
||||
}
|
||||
v.Frame = false
|
||||
if v.FgColor, err = gui.GetOptionsPanelTextColor(); err != nil {
|
||||
return err
|
||||
}
|
||||
userConfig := gui.Config.GetUserConfig()
|
||||
v.FgColor = theme.GetColor(userConfig.GetStringSlice("gui.theme.optionsTextColor"))
|
||||
}
|
||||
|
||||
if gui.getCommitMessageView(g) == nil {
|
||||
if gui.getCommitMessageView() == nil {
|
||||
// doesn't matter where this view starts because it will be hidden
|
||||
if commitMessageView, err := g.SetView("commitMessage", 0, 0, width/2, height/2, 0); err != nil {
|
||||
if err != gocui.ErrUnknownView {
|
||||
if commitMessageView, err := g.SetView("commitMessage", width, height, width*2, height*2, 0); err != nil {
|
||||
if err.Error() != "unknown view" {
|
||||
return err
|
||||
}
|
||||
g.SetViewOnBottom("commitMessage")
|
||||
commitMessageView.Title = gui.Tr.SLocalize("CommitMessage")
|
||||
commitMessageView.FgColor = gocui.ColorWhite
|
||||
commitMessageView.FgColor = textColor
|
||||
commitMessageView.Editable = true
|
||||
commitMessageView.Editor = gocui.EditorFunc(gui.simpleEditor)
|
||||
}
|
||||
}
|
||||
|
||||
if appStatusView, err := g.SetView("appStatus", -1, optionsTop, width, optionsTop+2, 0); err != nil {
|
||||
if err != gocui.ErrUnknownView {
|
||||
if check, _ := g.View("credentials"); check == nil {
|
||||
// doesn't matter where this view starts because it will be hidden
|
||||
if credentialsView, err := g.SetView("credentials", width, height, width*2, height*2, 0); err != nil {
|
||||
if err.Error() != "unknown view" {
|
||||
return err
|
||||
}
|
||||
_, err := g.SetViewOnBottom("credentials")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
credentialsView.Title = gui.Tr.SLocalize("CredentialsUsername")
|
||||
credentialsView.FgColor = textColor
|
||||
credentialsView.Editable = true
|
||||
}
|
||||
}
|
||||
|
||||
if appStatusView, err := g.SetView("appStatus", -1, height-2, width, height, 0); err != nil {
|
||||
if err.Error() != "unknown view" {
|
||||
return err
|
||||
}
|
||||
appStatusView.BgColor = gocui.ColorDefault
|
||||
@@ -279,67 +553,135 @@ func (gui *Gui) layout(g *gocui.Gui) error {
|
||||
}
|
||||
}
|
||||
|
||||
if v, err := g.SetView("version", optionsVersionBoundary-1, optionsTop, width, optionsTop+2, 0); err != nil {
|
||||
if err != gocui.ErrUnknownView {
|
||||
if v, err := g.SetView("information", optionsVersionBoundary-1, height-2, width, height, 0); err != nil {
|
||||
if err.Error() != "unknown view" {
|
||||
return err
|
||||
}
|
||||
v.BgColor = gocui.ColorDefault
|
||||
v.FgColor = gocui.ColorGreen
|
||||
v.Frame = false
|
||||
if err := gui.renderString(g, "version", version); err != nil {
|
||||
if err := gui.renderString(g, "information", information); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// these are only called once (it's a place to put all the things you want
|
||||
// to happen on startup after the screen is first rendered)
|
||||
gui.Updater.CheckForNewUpdate(gui.onBackgroundUpdateCheckFinish, false)
|
||||
gui.handleFileSelect(g, filesView)
|
||||
gui.refreshFiles(g)
|
||||
gui.refreshBranches(g)
|
||||
gui.refreshCommits(g)
|
||||
gui.refreshStashEntries(g)
|
||||
if err := gui.switchFocus(g, nil, filesView); err != nil {
|
||||
// doing this here because it'll only happen once
|
||||
if err := gui.loadNewRepo(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if gui.Config.GetUserConfig().GetString("reporting") == "undetermined" {
|
||||
if err := gui.promptAnonymousReporting(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if gui.g.CurrentView() == nil {
|
||||
if _, err := gui.g.SetCurrentView(gui.getFilesView().Name()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := gui.switchFocus(gui.g, nil, gui.getFilesView()); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
type listViewState struct {
|
||||
selectedLine int
|
||||
lineCount int
|
||||
}
|
||||
|
||||
listViews := map[*gocui.View]listViewState{
|
||||
filesView: {selectedLine: gui.State.Panels.Files.SelectedLine, lineCount: len(gui.State.Files)},
|
||||
branchesView: {selectedLine: gui.State.Panels.Branches.SelectedLine, lineCount: len(gui.State.Branches)},
|
||||
commitsView: {selectedLine: gui.State.Panels.Commits.SelectedLine, lineCount: len(gui.State.Commits)},
|
||||
stashView: {selectedLine: gui.State.Panels.Stash.SelectedLine, lineCount: len(gui.State.StashEntries)},
|
||||
}
|
||||
|
||||
// menu view might not exist so we check to be safe
|
||||
if menuView, err := gui.g.View("menu"); err == nil {
|
||||
listViews[menuView] = listViewState{selectedLine: gui.State.Panels.Menu.SelectedLine, lineCount: gui.State.MenuItemCount}
|
||||
}
|
||||
for view, state := range listViews {
|
||||
// check if the selected line is now out of view and if so refocus it
|
||||
if err := gui.focusPoint(0, state.selectedLine, state.lineCount, view); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// here is a good place log some stuff
|
||||
// if you download humanlog and do tail -f development.log | humanlog
|
||||
// this will let you see these branches as prettified json
|
||||
// gui.Log.Info(utils.AsJson(gui.State.Branches[0:4]))
|
||||
return gui.resizeCurrentPopupPanel(g)
|
||||
}
|
||||
|
||||
func (gui *Gui) promptAnonymousReporting() error {
|
||||
return gui.createConfirmationPanel(gui.g, nil, gui.Tr.SLocalize("AnonymousReportingTitle"), gui.Tr.SLocalize("AnonymousReportingPrompt"), func(g *gocui.Gui, v *gocui.View) error {
|
||||
func (gui *Gui) loadNewRepo() error {
|
||||
gui.Updater.CheckForNewUpdate(gui.onBackgroundUpdateCheckFinish, false)
|
||||
if err := gui.updateRecentRepoList(); err != nil {
|
||||
return err
|
||||
}
|
||||
gui.waitForIntro.Done()
|
||||
|
||||
if err := gui.refreshSidePanels(gui.g); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) showInitialPopups(tasks []func(chan struct{}) error) {
|
||||
gui.waitForIntro.Add(len(tasks))
|
||||
done := make(chan struct{})
|
||||
|
||||
go func() {
|
||||
for _, task := range tasks {
|
||||
go func() {
|
||||
if err := task(done); err != nil {
|
||||
_ = gui.createErrorPanel(gui.g, err.Error())
|
||||
}
|
||||
}()
|
||||
|
||||
<-done
|
||||
gui.waitForIntro.Done()
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (gui *Gui) showShamelessSelfPromotionMessage(done chan struct{}) error {
|
||||
onConfirm := func(g *gocui.Gui, v *gocui.View) error {
|
||||
done <- struct{}{}
|
||||
return gui.Config.WriteToUserConfig("startupPopupVersion", StartupPopupVersion)
|
||||
}
|
||||
|
||||
return gui.createConfirmationPanel(gui.g, nil, true, gui.Tr.SLocalize("ShamelessSelfPromotionTitle"), gui.Tr.SLocalize("ShamelessSelfPromotionMessage"), onConfirm, onConfirm)
|
||||
}
|
||||
|
||||
func (gui *Gui) promptAnonymousReporting(done chan struct{}) error {
|
||||
return gui.createConfirmationPanel(gui.g, nil, true, gui.Tr.SLocalize("AnonymousReportingTitle"), gui.Tr.SLocalize("AnonymousReportingPrompt"), func(g *gocui.Gui, v *gocui.View) error {
|
||||
done <- struct{}{}
|
||||
return gui.Config.WriteToUserConfig("reporting", "on")
|
||||
}, func(g *gocui.Gui, v *gocui.View) error {
|
||||
done <- struct{}{}
|
||||
return gui.Config.WriteToUserConfig("reporting", "off")
|
||||
})
|
||||
}
|
||||
|
||||
func (gui *Gui) fetch(g *gocui.Gui) error {
|
||||
gui.GitCommand.Fetch()
|
||||
gui.refreshStatus(g)
|
||||
return nil
|
||||
}
|
||||
func (gui *Gui) fetch(g *gocui.Gui, v *gocui.View, canAskForCredentials bool) (unamePassOpend bool, err error) {
|
||||
unamePassOpend = false
|
||||
err = gui.GitCommand.Fetch(func(passOrUname string) string {
|
||||
unamePassOpend = true
|
||||
return gui.waitForPassUname(gui.g, v, passOrUname)
|
||||
}, canAskForCredentials)
|
||||
|
||||
func (gui *Gui) updateLoader(g *gocui.Gui) error {
|
||||
if view, _ := g.View("confirmation"); view != nil {
|
||||
content := gui.trimmedContent(view)
|
||||
if strings.Contains(content, "...") {
|
||||
staticContent := strings.Split(content, "...")[0] + "..."
|
||||
if err := gui.renderString(g, "confirmation", staticContent+" "+utils.Loader()); err != nil {
|
||||
return err
|
||||
}
|
||||
if canAskForCredentials && err != nil && strings.Contains(err.Error(), "exit status 128") {
|
||||
colorFunction := color.New(color.FgRed).SprintFunc()
|
||||
coloredMessage := colorFunction(strings.TrimSpace(gui.Tr.SLocalize("PassUnameWrong")))
|
||||
close := func(g *gocui.Gui, v *gocui.View) error {
|
||||
return nil
|
||||
}
|
||||
_ = gui.createConfirmationPanel(g, v, true, gui.Tr.SLocalize("Error"), coloredMessage, close, close)
|
||||
}
|
||||
return nil
|
||||
|
||||
gui.refreshStatus(g)
|
||||
return unamePassOpend, err
|
||||
}
|
||||
|
||||
func (gui *Gui) renderAppStatus(g *gocui.Gui) error {
|
||||
func (gui *Gui) renderAppStatus() error {
|
||||
appStatus := gui.statusManager.getStatusString()
|
||||
if appStatus != "" {
|
||||
return gui.renderString(gui.g, "appStatus", appStatus)
|
||||
@@ -347,23 +689,41 @@ func (gui *Gui) renderAppStatus(g *gocui.Gui) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) renderGlobalOptions(g *gocui.Gui) error {
|
||||
return gui.renderOptionsMap(g, map[string]string{
|
||||
func (gui *Gui) renderGlobalOptions() error {
|
||||
return gui.renderOptionsMap(map[string]string{
|
||||
"PgUp/PgDn": gui.Tr.SLocalize("scroll"),
|
||||
"← → ↑ ↓": gui.Tr.SLocalize("navigate"),
|
||||
"esc/q": gui.Tr.SLocalize("close"),
|
||||
"x": gui.Tr.SLocalize("menu"),
|
||||
"1-5": gui.Tr.SLocalize("jump"),
|
||||
})
|
||||
}
|
||||
|
||||
func (gui *Gui) goEvery(g *gocui.Gui, interval time.Duration, function func(*gocui.Gui) error) {
|
||||
func (gui *Gui) goEvery(interval time.Duration, function func() error) {
|
||||
go func() {
|
||||
for range time.Tick(interval) {
|
||||
function(g)
|
||||
_ = function()
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (gui *Gui) startBackgroundFetch() {
|
||||
gui.waitForIntro.Wait()
|
||||
isNew := gui.Config.GetIsNewRepo()
|
||||
if !isNew {
|
||||
time.After(60 * time.Second)
|
||||
}
|
||||
_, err := gui.fetch(gui.g, gui.g.CurrentView(), false)
|
||||
if err != nil && strings.Contains(err.Error(), "exit status 128") && isNew {
|
||||
_ = gui.createConfirmationPanel(gui.g, gui.g.CurrentView(), true, gui.Tr.SLocalize("NoAutomaticGitFetchTitle"), gui.Tr.SLocalize("NoAutomaticGitFetchBody"), nil, nil)
|
||||
} else {
|
||||
gui.goEvery(time.Second*60, func() error {
|
||||
_, err := gui.fetch(gui.g, gui.g.CurrentView(), false)
|
||||
return err
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Run setup the gui with keybindings and start the mainloop
|
||||
func (gui *Gui) Run() error {
|
||||
g, err := gocui.NewGui(gocui.OutputNormal, OverlappingEdges)
|
||||
@@ -372,23 +732,43 @@ func (gui *Gui) Run() error {
|
||||
}
|
||||
defer g.Close()
|
||||
|
||||
if gui.Config.GetUserConfig().GetBool("gui.mouseEvents") {
|
||||
g.Mouse = true
|
||||
}
|
||||
|
||||
gui.g = g // TODO: always use gui.g rather than passing g around everywhere
|
||||
|
||||
if err := gui.SetColorScheme(); err != nil {
|
||||
if err := gui.setColorScheme(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
gui.goEvery(g, time.Second*60, gui.fetch)
|
||||
gui.goEvery(g, time.Second*10, gui.refreshFiles)
|
||||
gui.goEvery(g, time.Millisecond*50, gui.updateLoader)
|
||||
gui.goEvery(g, time.Millisecond*50, gui.renderAppStatus)
|
||||
popupTasks := []func(chan struct{}) error{}
|
||||
if gui.Config.GetUserConfig().GetString("reporting") == "undetermined" {
|
||||
popupTasks = append(popupTasks, gui.promptAnonymousReporting)
|
||||
}
|
||||
configPopupVersion := gui.Config.GetUserConfig().GetInt("StartupPopupVersion")
|
||||
// -1 means we've disabled these popups
|
||||
if configPopupVersion != -1 && configPopupVersion < StartupPopupVersion {
|
||||
popupTasks = append(popupTasks, gui.showShamelessSelfPromotionMessage)
|
||||
}
|
||||
gui.showInitialPopups(popupTasks)
|
||||
|
||||
g.SetManagerFunc(gui.layout)
|
||||
gui.waitForIntro.Add(1)
|
||||
if gui.Config.GetUserConfig().GetBool("git.autoFetch") {
|
||||
go gui.startBackgroundFetch()
|
||||
}
|
||||
|
||||
gui.goEvery(time.Second*10, gui.refreshFiles)
|
||||
gui.goEvery(time.Millisecond*50, gui.renderAppStatus)
|
||||
|
||||
g.SetManager(gocui.ManagerFunc(gui.layout), gocui.ManagerFunc(gui.getFocusLayout()))
|
||||
|
||||
if err = gui.keybindings(g); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
gui.Log.Warn("starting main loop")
|
||||
|
||||
err = g.MainLoop()
|
||||
return err
|
||||
}
|
||||
@@ -396,35 +776,102 @@ func (gui *Gui) Run() error {
|
||||
// RunWithSubprocesses loops, instantiating a new gocui.Gui with each iteration
|
||||
// if the error returned from a run is a ErrSubProcess, it runs the subprocess
|
||||
// otherwise it handles the error, possibly by quitting the application
|
||||
func (gui *Gui) RunWithSubprocesses() {
|
||||
func (gui *Gui) RunWithSubprocesses() error {
|
||||
for {
|
||||
if err := gui.Run(); err != nil {
|
||||
if err == gocui.ErrQuit {
|
||||
if !gui.State.RetainOriginalDir {
|
||||
if err := gui.recordCurrentDirectory(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
break
|
||||
} else if err == gui.Errors.ErrSwitchRepo {
|
||||
continue
|
||||
} else if err == gui.Errors.ErrSubProcess {
|
||||
gui.SubProcess.Stdin = os.Stdin
|
||||
gui.SubProcess.Stdout = os.Stdout
|
||||
gui.SubProcess.Stderr = os.Stderr
|
||||
gui.SubProcess.Run()
|
||||
gui.SubProcess.Stdout = ioutil.Discard
|
||||
gui.SubProcess.Stderr = ioutil.Discard
|
||||
gui.SubProcess.Stdin = nil
|
||||
gui.SubProcess = nil
|
||||
if err := gui.runCommand(); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
log.Panicln(err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) quit(g *gocui.Gui, v *gocui.View) error {
|
||||
if gui.State.Updating {
|
||||
return gui.createUpdateQuitConfirmation(g, v)
|
||||
func (gui *Gui) runCommand() error {
|
||||
gui.SubProcess.Stdout = os.Stdout
|
||||
gui.SubProcess.Stderr = os.Stdout
|
||||
gui.SubProcess.Stdin = os.Stdin
|
||||
|
||||
fmt.Fprintf(os.Stdout, "\n%s\n\n", utils.ColoredString("+ "+strings.Join(gui.SubProcess.Args, " "), color.FgBlue))
|
||||
|
||||
if err := gui.SubProcess.Run(); err != nil {
|
||||
// not handling the error explicitly because usually we're going to see it
|
||||
// in the output anyway
|
||||
gui.Log.Error(err)
|
||||
}
|
||||
if gui.Config.GetUserConfig().GetBool("confirmOnQuit") {
|
||||
return gui.createConfirmationPanel(g, v, "", gui.Tr.SLocalize("ConfirmQuit"), func(g *gocui.Gui, v *gocui.View) error {
|
||||
return gocui.ErrQuit
|
||||
}, nil)
|
||||
}
|
||||
return gocui.ErrQuit
|
||||
|
||||
gui.SubProcess.Stdout = ioutil.Discard
|
||||
gui.SubProcess.Stderr = ioutil.Discard
|
||||
gui.SubProcess.Stdin = nil
|
||||
gui.SubProcess = nil
|
||||
|
||||
fmt.Fprintf(os.Stdout, "\n%s", utils.ColoredString(gui.Tr.SLocalize("pressEnterToReturn"), color.FgGreen))
|
||||
fmt.Scanln() // wait for enter press
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) handleDonate(g *gocui.Gui, v *gocui.View) error {
|
||||
if !gui.g.Mouse {
|
||||
return nil
|
||||
}
|
||||
|
||||
cx, _ := v.Cursor()
|
||||
if cx > len(gui.Tr.SLocalize("Donate")) {
|
||||
return nil
|
||||
}
|
||||
return gui.OSCommand.OpenLink("https://github.com/sponsors/jesseduffield")
|
||||
}
|
||||
|
||||
// setColorScheme sets the color scheme for the app based on the user config
|
||||
func (gui *Gui) setColorScheme() error {
|
||||
userConfig := gui.Config.GetUserConfig()
|
||||
theme.UpdateTheme(userConfig)
|
||||
|
||||
gui.g.FgColor = theme.InactiveBorderColor
|
||||
gui.g.SelFgColor = theme.ActiveBorderColor
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) handleMouseDownMain(g *gocui.Gui, v *gocui.View) error {
|
||||
if gui.popupPanelFocused() {
|
||||
return nil
|
||||
}
|
||||
|
||||
switch g.CurrentView().Name() {
|
||||
case "files":
|
||||
return gui.enterFile(false, v.SelectedLineIdx())
|
||||
case "commitFiles":
|
||||
return gui.enterCommitFile(v.SelectedLineIdx())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) handleMouseDownSecondary(g *gocui.Gui, v *gocui.View) error {
|
||||
if gui.popupPanelFocused() {
|
||||
return nil
|
||||
}
|
||||
|
||||
switch g.CurrentView().Name() {
|
||||
case "files":
|
||||
return gui.enterFile(true, v.SelectedLineIdx())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package gui
|
||||
|
||||
import "github.com/jesseduffield/gocui"
|
||||
import (
|
||||
"github.com/jesseduffield/gocui"
|
||||
)
|
||||
|
||||
// Binding - a keybinding mapping a key and modifier to a handler. The keypress
|
||||
// is only handled if the given view has focus, or handled globally if the view
|
||||
@@ -10,35 +12,102 @@ type Binding struct {
|
||||
Handler func(*gocui.Gui, *gocui.View) error
|
||||
Key interface{} // FIXME: find out how to get `gocui.Key | rune`
|
||||
Modifier gocui.Modifier
|
||||
KeyReadable string
|
||||
Description string
|
||||
Alternative string
|
||||
}
|
||||
|
||||
func (gui *Gui) GetKeybindings() []Binding {
|
||||
bindings := []Binding{
|
||||
// GetDisplayStrings returns the display string of a file
|
||||
func (b *Binding) GetDisplayStrings(isFocused bool) []string {
|
||||
return []string{b.GetKey(), b.Description}
|
||||
}
|
||||
|
||||
// GetKey is a function.
|
||||
func (b *Binding) GetKey() string {
|
||||
key := 0
|
||||
|
||||
switch b.Key.(type) {
|
||||
case rune:
|
||||
key = int(b.Key.(rune))
|
||||
case gocui.Key:
|
||||
if b.Key.(gocui.Key) == gocui.KeyCtrlJ {
|
||||
return "ctrl+j"
|
||||
}
|
||||
if b.Key.(gocui.Key) == gocui.KeyCtrlK {
|
||||
return "ctrl+k"
|
||||
}
|
||||
key = int(b.Key.(gocui.Key))
|
||||
}
|
||||
|
||||
// special keys
|
||||
switch key {
|
||||
case 27:
|
||||
return "esc"
|
||||
case 13:
|
||||
return "enter"
|
||||
case 32:
|
||||
return "space"
|
||||
case 65514:
|
||||
return "►"
|
||||
case 65515:
|
||||
return "◄"
|
||||
case 65517:
|
||||
return "▲"
|
||||
case 65516:
|
||||
return "▼"
|
||||
case 65508:
|
||||
return "PgUp"
|
||||
case 65507:
|
||||
return "PgDn"
|
||||
case 9:
|
||||
return "tab"
|
||||
}
|
||||
|
||||
return string(key)
|
||||
}
|
||||
|
||||
// GetInitialKeybindings is a function.
|
||||
func (gui *Gui) GetInitialKeybindings() []*Binding {
|
||||
bindings := []*Binding{
|
||||
{
|
||||
ViewName: "",
|
||||
Key: 'q',
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.quit,
|
||||
Handler: gui.handleQuit,
|
||||
}, {
|
||||
ViewName: "",
|
||||
Key: 'Q',
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleQuitWithoutChangingDirectory,
|
||||
}, {
|
||||
ViewName: "",
|
||||
Key: gocui.KeyCtrlC,
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.quit,
|
||||
Handler: gui.handleQuit,
|
||||
}, {
|
||||
ViewName: "",
|
||||
Key: gocui.KeyEsc,
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.quit,
|
||||
Handler: gui.handleQuit,
|
||||
}, {
|
||||
ViewName: "",
|
||||
Key: gocui.KeyPgup,
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.scrollUpMain,
|
||||
Alternative: "fn+up",
|
||||
}, {
|
||||
ViewName: "",
|
||||
Key: gocui.KeyPgdn,
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.scrollDownMain,
|
||||
Alternative: "fn+down",
|
||||
}, {
|
||||
ViewName: "",
|
||||
Key: gocui.KeyPgup,
|
||||
Key: 'K',
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.scrollUpMain,
|
||||
}, {
|
||||
ViewName: "",
|
||||
Key: gocui.KeyPgdn,
|
||||
Key: 'J',
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.scrollDownMain,
|
||||
}, {
|
||||
@@ -51,6 +120,12 @@ func (gui *Gui) GetKeybindings() []Binding {
|
||||
Key: gocui.KeyCtrlD,
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.scrollDownMain,
|
||||
}, {
|
||||
ViewName: "",
|
||||
Key: 'm',
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleCreateRebaseOptionsMenu,
|
||||
Description: gui.Tr.SLocalize("ViewMergeRebaseOptions"),
|
||||
}, {
|
||||
ViewName: "",
|
||||
Key: 'P',
|
||||
@@ -73,7 +148,22 @@ func (gui *Gui) GetKeybindings() []Binding {
|
||||
ViewName: "",
|
||||
Key: 'x',
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleMenu,
|
||||
Handler: gui.handleCreateOptionsMenu,
|
||||
}, {
|
||||
ViewName: "",
|
||||
Key: '?',
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleCreateOptionsMenu,
|
||||
}, {
|
||||
ViewName: "",
|
||||
Key: gocui.MouseMiddle,
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleCreateOptionsMenu,
|
||||
}, {
|
||||
ViewName: "",
|
||||
Key: gocui.KeyCtrlP,
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleCreatePatchOptionsMenu,
|
||||
}, {
|
||||
ViewName: "status",
|
||||
Key: 'e',
|
||||
@@ -93,11 +183,31 @@ func (gui *Gui) GetKeybindings() []Binding {
|
||||
Handler: gui.handleCheckForUpdate,
|
||||
Description: gui.Tr.SLocalize("checkForUpdate"),
|
||||
}, {
|
||||
ViewName: "status",
|
||||
Key: 's',
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleCreateRecentReposMenu,
|
||||
Description: gui.Tr.SLocalize("SwitchRepo"),
|
||||
},
|
||||
{
|
||||
ViewName: "files",
|
||||
Key: 'c',
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleCommitPress,
|
||||
Description: gui.Tr.SLocalize("CommitChanges"),
|
||||
},
|
||||
{
|
||||
ViewName: "files",
|
||||
Key: 'w',
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleWIPCommitPress,
|
||||
Description: gui.Tr.SLocalize("commitChangesWithoutHook"),
|
||||
}, {
|
||||
ViewName: "files",
|
||||
Key: 'A',
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleAmendCommitPress,
|
||||
Description: gui.Tr.SLocalize("AmendLastCommit"),
|
||||
}, {
|
||||
ViewName: "files",
|
||||
Key: 'C',
|
||||
@@ -109,20 +219,13 @@ func (gui *Gui) GetKeybindings() []Binding {
|
||||
Key: gocui.KeySpace,
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleFilePress,
|
||||
KeyReadable: "space",
|
||||
Description: gui.Tr.SLocalize("toggleStaged"),
|
||||
}, {
|
||||
ViewName: "files",
|
||||
Key: 'd',
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleFileRemove,
|
||||
Description: gui.Tr.SLocalize("removeFile"),
|
||||
}, {
|
||||
ViewName: "files",
|
||||
Key: 'm',
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleSwitchToMerge,
|
||||
Description: gui.Tr.SLocalize("resolveMergeConflicts"),
|
||||
Handler: gui.handleCreateDiscardMenu,
|
||||
Description: gui.Tr.SLocalize("viewDiscardOptions"),
|
||||
}, {
|
||||
ViewName: "files",
|
||||
Key: 'e',
|
||||
@@ -149,101 +252,58 @@ func (gui *Gui) GetKeybindings() []Binding {
|
||||
Description: gui.Tr.SLocalize("refreshFiles"),
|
||||
}, {
|
||||
ViewName: "files",
|
||||
Key: 'S',
|
||||
Key: 's',
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleStashSave,
|
||||
Description: gui.Tr.SLocalize("stashFiles"),
|
||||
Handler: gui.handleStashChanges,
|
||||
Description: gui.Tr.SLocalize("stashAllChanges"),
|
||||
}, {
|
||||
ViewName: "files",
|
||||
Key: 'A',
|
||||
Key: 'S',
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleAbortMerge,
|
||||
Description: gui.Tr.SLocalize("abortMerge"),
|
||||
Handler: gui.handleCreateStashMenu,
|
||||
Description: gui.Tr.SLocalize("viewStashOptions"),
|
||||
}, {
|
||||
ViewName: "files",
|
||||
Key: 'a',
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleStageAll,
|
||||
Description: gui.Tr.SLocalize("toggleStagedAll"),
|
||||
}, {
|
||||
ViewName: "files",
|
||||
Key: 't',
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleAddPatch,
|
||||
Description: gui.Tr.SLocalize("addPatch"),
|
||||
}, {
|
||||
ViewName: "files",
|
||||
Key: 'D',
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleResetHard,
|
||||
Description: gui.Tr.SLocalize("resetHard"),
|
||||
Handler: gui.handleCreateResetMenu,
|
||||
Description: gui.Tr.SLocalize("viewResetOptions"),
|
||||
}, {
|
||||
ViewName: "main",
|
||||
Key: gocui.KeyEsc,
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleEscapeMerge,
|
||||
ViewName: "files",
|
||||
Key: gocui.KeyEnter,
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleEnterFile,
|
||||
Description: gui.Tr.SLocalize("StageLines"),
|
||||
}, {
|
||||
ViewName: "main",
|
||||
Key: gocui.KeySpace,
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handlePickHunk,
|
||||
ViewName: "files",
|
||||
Key: 'f',
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleGitFetch,
|
||||
Description: gui.Tr.SLocalize("fetch"),
|
||||
}, {
|
||||
ViewName: "main",
|
||||
Key: 'b',
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handlePickBothHunks,
|
||||
}, {
|
||||
ViewName: "main",
|
||||
Key: gocui.KeyArrowLeft,
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleSelectPrevConflict,
|
||||
}, {
|
||||
ViewName: "main",
|
||||
Key: gocui.KeyArrowRight,
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleSelectNextConflict,
|
||||
}, {
|
||||
ViewName: "main",
|
||||
Key: gocui.KeyArrowUp,
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleSelectTop,
|
||||
}, {
|
||||
ViewName: "main",
|
||||
Key: gocui.KeyArrowDown,
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleSelectBottom,
|
||||
}, {
|
||||
ViewName: "main",
|
||||
Key: 'h',
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleSelectPrevConflict,
|
||||
}, {
|
||||
ViewName: "main",
|
||||
Key: 'l',
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleSelectNextConflict,
|
||||
}, {
|
||||
ViewName: "main",
|
||||
Key: 'k',
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleSelectTop,
|
||||
}, {
|
||||
ViewName: "main",
|
||||
Key: 'j',
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleSelectBottom,
|
||||
}, {
|
||||
ViewName: "main",
|
||||
Key: 'z',
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handlePopFileSnapshot,
|
||||
ViewName: "files",
|
||||
Key: 'X',
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleCustomCommand,
|
||||
Description: gui.Tr.SLocalize("executeCustomCommand"),
|
||||
}, {
|
||||
ViewName: "branches",
|
||||
Key: gocui.KeySpace,
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleBranchPress,
|
||||
KeyReadable: "space",
|
||||
Description: gui.Tr.SLocalize("checkout"),
|
||||
}, {
|
||||
ViewName: "branches",
|
||||
Key: 'o',
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleCreatePullRequestPress,
|
||||
Description: gui.Tr.SLocalize("createPullRequest"),
|
||||
}, {
|
||||
ViewName: "branches",
|
||||
Key: 'c',
|
||||
@@ -270,16 +330,22 @@ func (gui *Gui) GetKeybindings() []Binding {
|
||||
Description: gui.Tr.SLocalize("deleteBranch"),
|
||||
}, {
|
||||
ViewName: "branches",
|
||||
Key: 'D',
|
||||
Key: 'r',
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleForceDeleteBranch,
|
||||
Description: gui.Tr.SLocalize("forceDeleteBranch"),
|
||||
Handler: gui.handleRebase,
|
||||
Description: gui.Tr.SLocalize("rebaseBranch"),
|
||||
}, {
|
||||
ViewName: "branches",
|
||||
Key: 'm',
|
||||
Key: 'M',
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleMerge,
|
||||
Description: gui.Tr.SLocalize("mergeIntoCurrentBranch"),
|
||||
}, {
|
||||
ViewName: "branches",
|
||||
Key: 'f',
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleFastForward,
|
||||
Description: gui.Tr.SLocalize("FastForward"),
|
||||
}, {
|
||||
ViewName: "commits",
|
||||
Key: 's',
|
||||
@@ -302,7 +368,7 @@ func (gui *Gui) GetKeybindings() []Binding {
|
||||
ViewName: "commits",
|
||||
Key: 'g',
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleResetToCommit,
|
||||
Handler: gui.handleCreateCommitResetMenu,
|
||||
Description: gui.Tr.SLocalize("resetToThisCommit"),
|
||||
}, {
|
||||
ViewName: "commits",
|
||||
@@ -310,12 +376,95 @@ func (gui *Gui) GetKeybindings() []Binding {
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleCommitFixup,
|
||||
Description: gui.Tr.SLocalize("fixupCommit"),
|
||||
}, {
|
||||
ViewName: "commits",
|
||||
Key: 'F',
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleCreateFixupCommit,
|
||||
Description: gui.Tr.SLocalize("createFixupCommit"),
|
||||
}, {
|
||||
ViewName: "commits",
|
||||
Key: 'S',
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleSquashAllAboveFixupCommits,
|
||||
Description: gui.Tr.SLocalize("squashAboveCommits"),
|
||||
}, {
|
||||
ViewName: "commits",
|
||||
Key: 'd',
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleCommitDelete,
|
||||
Description: gui.Tr.SLocalize("deleteCommit"),
|
||||
}, {
|
||||
ViewName: "commits",
|
||||
Key: gocui.KeyCtrlJ,
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleCommitMoveDown,
|
||||
Description: gui.Tr.SLocalize("moveDownCommit"),
|
||||
}, {
|
||||
ViewName: "commits",
|
||||
Key: gocui.KeyCtrlK,
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleCommitMoveUp,
|
||||
Description: gui.Tr.SLocalize("moveUpCommit"),
|
||||
}, {
|
||||
ViewName: "commits",
|
||||
Key: 'e',
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleCommitEdit,
|
||||
Description: gui.Tr.SLocalize("editCommit"),
|
||||
}, {
|
||||
ViewName: "commits",
|
||||
Key: 'A',
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleCommitAmendTo,
|
||||
Description: gui.Tr.SLocalize("amendToCommit"),
|
||||
}, {
|
||||
ViewName: "commits",
|
||||
Key: 'p',
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleCommitPick,
|
||||
Description: gui.Tr.SLocalize("pickCommit"),
|
||||
}, {
|
||||
ViewName: "commits",
|
||||
Key: 't',
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleCommitRevert,
|
||||
Description: gui.Tr.SLocalize("revertCommit"),
|
||||
}, {
|
||||
ViewName: "commits",
|
||||
Key: 'c',
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleCopyCommit,
|
||||
Description: gui.Tr.SLocalize("cherryPickCopy"),
|
||||
}, {
|
||||
ViewName: "commits",
|
||||
Key: 'C',
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleCopyCommitRange,
|
||||
Description: gui.Tr.SLocalize("cherryPickCopyRange"),
|
||||
}, {
|
||||
ViewName: "commits",
|
||||
Key: 'v',
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.HandlePasteCommits,
|
||||
Description: gui.Tr.SLocalize("pasteCommits"),
|
||||
}, {
|
||||
ViewName: "commits",
|
||||
Key: gocui.KeyEnter,
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleSwitchToCommitFilesPanel,
|
||||
Description: gui.Tr.SLocalize("viewCommitFiles"),
|
||||
}, {
|
||||
ViewName: "commits",
|
||||
Key: gocui.KeySpace,
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleToggleDiffCommit,
|
||||
Description: gui.Tr.SLocalize("CommitsDiff"),
|
||||
}, {
|
||||
ViewName: "stash",
|
||||
Key: gocui.KeySpace,
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleStashApply,
|
||||
KeyReadable: "space",
|
||||
Description: gui.Tr.SLocalize("apply"),
|
||||
}, {
|
||||
ViewName: "stash",
|
||||
@@ -339,6 +488,16 @@ func (gui *Gui) GetKeybindings() []Binding {
|
||||
Key: gocui.KeyEsc,
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleCommitClose,
|
||||
}, {
|
||||
ViewName: "credentials",
|
||||
Key: gocui.KeyEnter,
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleSubmitCredential,
|
||||
}, {
|
||||
ViewName: "credentials",
|
||||
Key: gocui.KeyEsc,
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleCloseCredentialsView,
|
||||
}, {
|
||||
ViewName: "menu",
|
||||
Key: gocui.KeyEsc,
|
||||
@@ -350,39 +509,459 @@ func (gui *Gui) GetKeybindings() []Binding {
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleMenuClose,
|
||||
}, {
|
||||
ViewName: "menu",
|
||||
Key: gocui.KeySpace,
|
||||
ViewName: "information",
|
||||
Key: gocui.MouseLeft,
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleMenuPress,
|
||||
Handler: gui.handleDonate,
|
||||
}, {
|
||||
ViewName: "commitFiles",
|
||||
Key: gocui.KeyEsc,
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleSwitchToCommitsPanel,
|
||||
Description: gui.Tr.SLocalize("goBack"),
|
||||
}, {
|
||||
ViewName: "commitFiles",
|
||||
Key: 'c',
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleCheckoutCommitFile,
|
||||
Description: gui.Tr.SLocalize("checkoutCommitFile"),
|
||||
}, {
|
||||
ViewName: "commitFiles",
|
||||
Key: 'd',
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleDiscardOldFileChange,
|
||||
Description: gui.Tr.SLocalize("discardOldFileChange"),
|
||||
},
|
||||
{
|
||||
ViewName: "commitFiles",
|
||||
Key: 'o',
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleOpenOldCommitFile,
|
||||
Description: gui.Tr.SLocalize("openFile"),
|
||||
},
|
||||
{
|
||||
ViewName: "commitFiles",
|
||||
Key: gocui.KeySpace,
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleToggleFileForPatch,
|
||||
Description: gui.Tr.SLocalize("toggleAddToPatch"),
|
||||
},
|
||||
{
|
||||
ViewName: "commitFiles",
|
||||
Key: gocui.KeyEnter,
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleEnterCommitFile,
|
||||
Description: gui.Tr.SLocalize("enterFile"),
|
||||
},
|
||||
{
|
||||
ViewName: "secondary",
|
||||
Key: gocui.MouseWheelUp,
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.scrollUpSecondary,
|
||||
},
|
||||
{
|
||||
ViewName: "secondary",
|
||||
Key: gocui.MouseWheelDown,
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.scrollDownSecondary,
|
||||
},
|
||||
}
|
||||
|
||||
// Would make these keybindings global but that interferes with editing
|
||||
// input in the confirmation panel
|
||||
for _, viewName := range []string{"status", "files", "branches", "commits", "stash", "menu"} {
|
||||
bindings = append(bindings, []Binding{
|
||||
for _, viewName := range []string{"status", "branches", "files", "commits", "commitFiles", "stash", "menu"} {
|
||||
bindings = append(bindings, []*Binding{
|
||||
{ViewName: viewName, Key: gocui.KeyTab, Modifier: gocui.ModNone, Handler: gui.nextView},
|
||||
{ViewName: viewName, Key: gocui.KeyArrowLeft, Modifier: gocui.ModNone, Handler: gui.previousView},
|
||||
{ViewName: viewName, Key: gocui.KeyArrowRight, Modifier: gocui.ModNone, Handler: gui.nextView},
|
||||
{ViewName: viewName, Key: gocui.KeyArrowUp, Modifier: gocui.ModNone, Handler: gui.cursorUp},
|
||||
{ViewName: viewName, Key: gocui.KeyArrowDown, Modifier: gocui.ModNone, Handler: gui.cursorDown},
|
||||
{ViewName: viewName, Key: 'h', Modifier: gocui.ModNone, Handler: gui.previousView},
|
||||
{ViewName: viewName, Key: 'l', Modifier: gocui.ModNone, Handler: gui.nextView},
|
||||
{ViewName: viewName, Key: 'k', Modifier: gocui.ModNone, Handler: gui.cursorUp},
|
||||
{ViewName: viewName, Key: 'j', Modifier: gocui.ModNone, Handler: gui.cursorDown},
|
||||
}...)
|
||||
}
|
||||
|
||||
// Appends keybindings to jump to a particular sideView using numbers
|
||||
for i, viewName := range []string{"status", "files", "branches", "commits", "stash"} {
|
||||
bindings = append(bindings, &Binding{ViewName: "", Key: rune(i+1) + '0', Modifier: gocui.ModNone, Handler: gui.goToSideView(viewName)})
|
||||
}
|
||||
|
||||
listPanelMap := map[string]struct {
|
||||
prevLine func(*gocui.Gui, *gocui.View) error
|
||||
nextLine func(*gocui.Gui, *gocui.View) error
|
||||
onClick func(*gocui.Gui, *gocui.View) error
|
||||
}{
|
||||
"menu": {prevLine: gui.handleMenuPrevLine, nextLine: gui.handleMenuNextLine, onClick: gui.handleMenuClick},
|
||||
"files": {prevLine: gui.handleFilesPrevLine, nextLine: gui.handleFilesNextLine, onClick: gui.handleFilesClick},
|
||||
"branches": {prevLine: gui.handleBranchesPrevLine, nextLine: gui.handleBranchesNextLine, onClick: gui.handleBranchesClick},
|
||||
"commits": {prevLine: gui.handleCommitsPrevLine, nextLine: gui.handleCommitsNextLine, onClick: gui.handleCommitsClick},
|
||||
"stash": {prevLine: gui.handleStashPrevLine, nextLine: gui.handleStashNextLine, onClick: gui.handleStashEntrySelect},
|
||||
"status": {onClick: gui.handleStatusClick},
|
||||
"commitFiles": {prevLine: gui.handleCommitFilesPrevLine, nextLine: gui.handleCommitFilesNextLine, onClick: gui.handleCommitFilesClick},
|
||||
}
|
||||
|
||||
for viewName, functions := range listPanelMap {
|
||||
bindings = append(bindings, []*Binding{
|
||||
{ViewName: viewName, Key: 'k', Modifier: gocui.ModNone, Handler: functions.prevLine},
|
||||
{ViewName: viewName, Key: gocui.KeyArrowUp, Modifier: gocui.ModNone, Handler: functions.prevLine},
|
||||
{ViewName: viewName, Key: gocui.MouseWheelUp, Modifier: gocui.ModNone, Handler: functions.prevLine},
|
||||
{ViewName: viewName, Key: 'j', Modifier: gocui.ModNone, Handler: functions.nextLine},
|
||||
{ViewName: viewName, Key: gocui.KeyArrowDown, Modifier: gocui.ModNone, Handler: functions.nextLine},
|
||||
{ViewName: viewName, Key: gocui.MouseWheelDown, Modifier: gocui.ModNone, Handler: functions.nextLine},
|
||||
{ViewName: viewName, Key: gocui.MouseLeft, Modifier: gocui.ModNone, Handler: functions.onClick},
|
||||
}...)
|
||||
}
|
||||
|
||||
return bindings
|
||||
}
|
||||
|
||||
// GetCurrentKeybindings gets the list of keybindings given the current context
|
||||
func (gui *Gui) GetCurrentKeybindings() []*Binding {
|
||||
bindings := gui.GetInitialKeybindings()
|
||||
currentContext := gui.State.Context
|
||||
contextBindings := gui.GetContextMap()[currentContext]
|
||||
|
||||
return append(bindings, contextBindings...)
|
||||
}
|
||||
|
||||
func (gui *Gui) keybindings(g *gocui.Gui) error {
|
||||
bindings := gui.GetKeybindings()
|
||||
bindings := gui.GetInitialKeybindings()
|
||||
|
||||
for _, binding := range bindings {
|
||||
if err := g.SetKeybinding(binding.ViewName, binding.Key, binding.Modifier, binding.Handler); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := gui.setInitialContext(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) GetContextMap() map[string][]*Binding {
|
||||
return map[string][]*Binding{
|
||||
"normal": {
|
||||
{
|
||||
ViewName: "secondary",
|
||||
Key: gocui.MouseLeft,
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleMouseDownSecondary,
|
||||
},
|
||||
{
|
||||
ViewName: "main",
|
||||
Key: gocui.MouseWheelDown,
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.scrollDownMain,
|
||||
Description: gui.Tr.SLocalize("ScrollDown"),
|
||||
Alternative: "fn+up",
|
||||
}, {
|
||||
ViewName: "main",
|
||||
Key: gocui.MouseWheelUp,
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.scrollUpMain,
|
||||
Description: gui.Tr.SLocalize("ScrollUp"),
|
||||
Alternative: "fn+down",
|
||||
}, {
|
||||
ViewName: "main",
|
||||
Key: gocui.MouseLeft,
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleMouseDownMain,
|
||||
},
|
||||
},
|
||||
"staging": {
|
||||
{
|
||||
ViewName: "secondary",
|
||||
Key: gocui.MouseLeft,
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleTogglePanelClick,
|
||||
},
|
||||
{
|
||||
ViewName: "main",
|
||||
Key: gocui.KeyEsc,
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleStagingEscape,
|
||||
Description: gui.Tr.SLocalize("ReturnToFilesPanel"),
|
||||
}, {
|
||||
ViewName: "main",
|
||||
Key: gocui.KeyArrowUp,
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleSelectPrevLine,
|
||||
Description: gui.Tr.SLocalize("PrevLine"),
|
||||
}, {
|
||||
ViewName: "main",
|
||||
Key: gocui.KeyArrowDown,
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleSelectNextLine,
|
||||
Description: gui.Tr.SLocalize("NextLine"),
|
||||
}, {
|
||||
ViewName: "main",
|
||||
Key: 'k',
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleSelectPrevLine,
|
||||
}, {
|
||||
ViewName: "main",
|
||||
Key: 'j',
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleSelectNextLine,
|
||||
}, {
|
||||
ViewName: "main",
|
||||
Key: gocui.KeyArrowLeft,
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleSelectPrevHunk,
|
||||
Description: gui.Tr.SLocalize("PrevHunk"),
|
||||
}, {
|
||||
ViewName: "main",
|
||||
Key: gocui.KeyArrowRight,
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleSelectNextHunk,
|
||||
Description: gui.Tr.SLocalize("NextHunk"),
|
||||
}, {
|
||||
ViewName: "main",
|
||||
Key: 'h',
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleSelectPrevHunk,
|
||||
}, {
|
||||
ViewName: "main",
|
||||
Key: 'l',
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleSelectNextHunk,
|
||||
}, {
|
||||
ViewName: "main",
|
||||
Key: gocui.KeySpace,
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleStageSelection,
|
||||
Description: gui.Tr.SLocalize("StageSelection"),
|
||||
}, {
|
||||
ViewName: "main",
|
||||
Key: 'd',
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleResetSelection,
|
||||
Description: gui.Tr.SLocalize("ResetSelection"),
|
||||
}, {
|
||||
ViewName: "main",
|
||||
Key: 'v',
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleToggleSelectRange,
|
||||
Description: gui.Tr.SLocalize("ToggleDragSelect"),
|
||||
}, {
|
||||
ViewName: "main",
|
||||
Key: 'a',
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleToggleSelectHunk,
|
||||
Description: gui.Tr.SLocalize("ToggleSelectHunk"),
|
||||
}, {
|
||||
ViewName: "main",
|
||||
Key: gocui.KeyTab,
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleTogglePanel,
|
||||
Description: gui.Tr.SLocalize("TogglePanel"),
|
||||
}, {
|
||||
ViewName: "main",
|
||||
Key: gocui.MouseLeft,
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleMouseDown,
|
||||
}, {
|
||||
ViewName: "main",
|
||||
Key: gocui.MouseLeft,
|
||||
Modifier: gocui.ModMotion,
|
||||
Handler: gui.handleMouseDrag,
|
||||
}, {
|
||||
ViewName: "main",
|
||||
Key: gocui.MouseWheelUp,
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleMouseScrollUp,
|
||||
}, {
|
||||
ViewName: "main",
|
||||
Key: gocui.MouseWheelDown,
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleMouseScrollDown,
|
||||
},
|
||||
},
|
||||
"patch-building": {
|
||||
{
|
||||
ViewName: "main",
|
||||
Key: gocui.KeyEsc,
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleEscapePatchBuildingPanel,
|
||||
Description: gui.Tr.SLocalize("ExitLineByLineMode"),
|
||||
}, {
|
||||
ViewName: "main",
|
||||
Key: gocui.KeyArrowUp,
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleSelectPrevLine,
|
||||
Description: gui.Tr.SLocalize("PrevLine"),
|
||||
}, {
|
||||
ViewName: "main",
|
||||
Key: gocui.KeyArrowDown,
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleSelectNextLine,
|
||||
Description: gui.Tr.SLocalize("NextLine"),
|
||||
}, {
|
||||
ViewName: "main",
|
||||
Key: 'k',
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleSelectPrevLine,
|
||||
}, {
|
||||
ViewName: "main",
|
||||
Key: 'j',
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleSelectNextLine,
|
||||
}, {
|
||||
ViewName: "main",
|
||||
Key: gocui.MouseWheelUp,
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleSelectPrevLine,
|
||||
}, {
|
||||
ViewName: "main",
|
||||
Key: gocui.MouseWheelDown,
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleSelectNextLine,
|
||||
}, {
|
||||
ViewName: "main",
|
||||
Key: gocui.KeyArrowLeft,
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleSelectPrevHunk,
|
||||
Description: gui.Tr.SLocalize("PrevHunk"),
|
||||
}, {
|
||||
ViewName: "main",
|
||||
Key: gocui.KeyArrowRight,
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleSelectNextHunk,
|
||||
Description: gui.Tr.SLocalize("NextHunk"),
|
||||
}, {
|
||||
ViewName: "main",
|
||||
Key: 'h',
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleSelectPrevHunk,
|
||||
}, {
|
||||
ViewName: "main",
|
||||
Key: 'l',
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleSelectNextHunk,
|
||||
}, {
|
||||
ViewName: "main",
|
||||
Key: gocui.KeySpace,
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleAddSelectionToPatch,
|
||||
Description: gui.Tr.SLocalize("StageSelection"),
|
||||
}, {
|
||||
ViewName: "main",
|
||||
Key: 'd',
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleRemoveSelectionFromPatch,
|
||||
Description: gui.Tr.SLocalize("ResetSelection"),
|
||||
}, {
|
||||
ViewName: "main",
|
||||
Key: 'v',
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleToggleSelectRange,
|
||||
Description: gui.Tr.SLocalize("ToggleDragSelect"),
|
||||
}, {
|
||||
ViewName: "main",
|
||||
Key: 'a',
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleToggleSelectHunk,
|
||||
Description: gui.Tr.SLocalize("ToggleSelectHunk"),
|
||||
}, {
|
||||
ViewName: "main",
|
||||
Key: gocui.MouseLeft,
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleMouseDown,
|
||||
}, {
|
||||
ViewName: "main",
|
||||
Key: gocui.MouseLeft,
|
||||
Modifier: gocui.ModMotion,
|
||||
Handler: gui.handleMouseDrag,
|
||||
}, {
|
||||
ViewName: "main",
|
||||
Key: gocui.MouseWheelUp,
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleMouseScrollUp,
|
||||
}, {
|
||||
ViewName: "main",
|
||||
Key: gocui.MouseWheelDown,
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleMouseScrollDown,
|
||||
},
|
||||
},
|
||||
"merging": {
|
||||
{
|
||||
ViewName: "main",
|
||||
Key: gocui.KeyEsc,
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleEscapeMerge,
|
||||
Description: gui.Tr.SLocalize("ReturnToFilesPanel"),
|
||||
}, {
|
||||
ViewName: "main",
|
||||
Key: gocui.KeySpace,
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handlePickHunk,
|
||||
Description: gui.Tr.SLocalize("PickHunk"),
|
||||
}, {
|
||||
ViewName: "main",
|
||||
Key: 'b',
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handlePickBothHunks,
|
||||
Description: gui.Tr.SLocalize("PickBothHunks"),
|
||||
}, {
|
||||
ViewName: "main",
|
||||
Key: gocui.KeyArrowLeft,
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleSelectPrevConflict,
|
||||
Description: gui.Tr.SLocalize("PrevConflict"),
|
||||
}, {
|
||||
ViewName: "main",
|
||||
Key: gocui.KeyArrowRight,
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleSelectNextConflict,
|
||||
|
||||
Description: gui.Tr.SLocalize("NextConflict"),
|
||||
}, {
|
||||
ViewName: "main",
|
||||
Key: gocui.KeyArrowUp,
|
||||
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleSelectTop,
|
||||
Description: gui.Tr.SLocalize("SelectTop"),
|
||||
}, {
|
||||
ViewName: "main",
|
||||
Key: gocui.KeyArrowDown,
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleSelectBottom,
|
||||
Description: gui.Tr.SLocalize("SelectBottom"),
|
||||
}, {
|
||||
ViewName: "main",
|
||||
Key: gocui.MouseWheelUp,
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleSelectTop,
|
||||
}, {
|
||||
ViewName: "main",
|
||||
Key: gocui.MouseWheelDown,
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleSelectBottom,
|
||||
}, {
|
||||
ViewName: "main",
|
||||
Key: 'h',
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleSelectPrevConflict,
|
||||
}, {
|
||||
ViewName: "main",
|
||||
Key: 'l',
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleSelectNextConflict,
|
||||
}, {
|
||||
ViewName: "main",
|
||||
Key: 'k',
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleSelectTop,
|
||||
}, {
|
||||
ViewName: "main",
|
||||
Key: 'j',
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handleSelectBottom,
|
||||
}, {
|
||||
ViewName: "main",
|
||||
Key: 'z',
|
||||
Modifier: gocui.ModNone,
|
||||
Handler: gui.handlePopFileSnapshot,
|
||||
Description: gui.Tr.SLocalize("Undo"),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
316
pkg/gui/line_by_line_panel.go
Normal file
316
pkg/gui/line_by_line_panel.go
Normal file
@@ -0,0 +1,316 @@
|
||||
package gui
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands"
|
||||
)
|
||||
|
||||
// Currently there are two 'pseudo-panels' that make use of this 'pseudo-panel'.
|
||||
// One is the staging panel where we stage files line-by-line, the other is the
|
||||
// patch building panel where we add lines of an old commit's file to a patch.
|
||||
// This file contains the logic around selecting lines and displaying the diffs
|
||||
// staging_panel.go and patch_building_panel.go have functions specific to their
|
||||
// use cases
|
||||
|
||||
// these represent what select mode we're in
|
||||
const (
|
||||
LINE = iota
|
||||
RANGE
|
||||
HUNK
|
||||
)
|
||||
|
||||
// returns whether the patch is empty so caller can escape if necessary
|
||||
// both diffs should be non-coloured because we'll parse them and colour them here
|
||||
func (gui *Gui) refreshLineByLinePanel(diff string, secondaryDiff string, secondaryFocused bool, selectedLineIdx int) (bool, error) {
|
||||
state := gui.State.Panels.LineByLine
|
||||
|
||||
patchParser, err := commands.NewPatchParser(gui.Log, diff)
|
||||
if err != nil {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if len(patchParser.StageableLines) == 0 {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
var firstLineIdx int
|
||||
var lastLineIdx int
|
||||
selectMode := LINE
|
||||
// if we have clicked from the outside to focus the main view we'll pass in a non-negative line index so that we can instantly select that line
|
||||
if selectedLineIdx >= 0 {
|
||||
selectMode = RANGE
|
||||
firstLineIdx, lastLineIdx = selectedLineIdx, selectedLineIdx
|
||||
} else if state != nil {
|
||||
if state.SelectMode == HUNK {
|
||||
// this is tricky: we need to find out which hunk we just staged based on our old `state.PatchParser` (as opposed to the new `patchParser`)
|
||||
// we do this by getting the first line index of the original hunk, then
|
||||
// finding the next stageable line, then getting its containing hunk
|
||||
// in the new diff
|
||||
selectMode = HUNK
|
||||
prevNewHunk := state.PatchParser.GetHunkContainingLine(state.SelectedLineIdx, 0)
|
||||
selectedLineIdx = patchParser.GetNextStageableLineIndex(prevNewHunk.FirstLineIdx)
|
||||
newHunk := patchParser.GetHunkContainingLine(selectedLineIdx, 0)
|
||||
firstLineIdx, lastLineIdx = newHunk.FirstLineIdx, newHunk.LastLineIdx
|
||||
} else {
|
||||
selectedLineIdx = patchParser.GetNextStageableLineIndex(state.SelectedLineIdx)
|
||||
firstLineIdx, lastLineIdx = selectedLineIdx, selectedLineIdx
|
||||
}
|
||||
} else {
|
||||
selectedLineIdx = patchParser.StageableLines[0]
|
||||
firstLineIdx, lastLineIdx = selectedLineIdx, selectedLineIdx
|
||||
}
|
||||
|
||||
gui.State.Panels.LineByLine = &lineByLinePanelState{
|
||||
PatchParser: patchParser,
|
||||
SelectedLineIdx: selectedLineIdx,
|
||||
SelectMode: selectMode,
|
||||
FirstLineIdx: firstLineIdx,
|
||||
LastLineIdx: lastLineIdx,
|
||||
Diff: diff,
|
||||
SecondaryFocused: secondaryFocused,
|
||||
}
|
||||
|
||||
if err := gui.refreshMainView(); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if err := gui.focusSelection(selectMode == HUNK); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
secondaryView := gui.getSecondaryView()
|
||||
secondaryView.Highlight = true
|
||||
secondaryView.Wrap = false
|
||||
|
||||
secondaryPatchParser, err := commands.NewPatchParser(gui.Log, secondaryDiff)
|
||||
if err != nil {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
gui.g.Update(func(*gocui.Gui) error {
|
||||
return gui.setViewContent(gui.g, gui.getSecondaryView(), secondaryPatchParser.Render(-1, -1, nil))
|
||||
})
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (gui *Gui) handleSelectPrevLine(g *gocui.Gui, v *gocui.View) error {
|
||||
return gui.handleCycleLine(-1)
|
||||
}
|
||||
|
||||
func (gui *Gui) handleSelectNextLine(g *gocui.Gui, v *gocui.View) error {
|
||||
return gui.handleCycleLine(+1)
|
||||
}
|
||||
|
||||
func (gui *Gui) handleSelectPrevHunk(g *gocui.Gui, v *gocui.View) error {
|
||||
state := gui.State.Panels.LineByLine
|
||||
newHunk := state.PatchParser.GetHunkContainingLine(state.SelectedLineIdx, -1)
|
||||
|
||||
return gui.selectNewHunk(newHunk)
|
||||
}
|
||||
|
||||
func (gui *Gui) handleSelectNextHunk(g *gocui.Gui, v *gocui.View) error {
|
||||
state := gui.State.Panels.LineByLine
|
||||
newHunk := state.PatchParser.GetHunkContainingLine(state.SelectedLineIdx, 1)
|
||||
|
||||
return gui.selectNewHunk(newHunk)
|
||||
}
|
||||
|
||||
func (gui *Gui) selectNewHunk(newHunk *commands.PatchHunk) error {
|
||||
state := gui.State.Panels.LineByLine
|
||||
state.SelectedLineIdx = state.PatchParser.GetNextStageableLineIndex(newHunk.FirstLineIdx)
|
||||
if state.SelectMode == HUNK {
|
||||
state.FirstLineIdx, state.LastLineIdx = newHunk.FirstLineIdx, newHunk.LastLineIdx
|
||||
} else {
|
||||
state.FirstLineIdx, state.LastLineIdx = state.SelectedLineIdx, state.SelectedLineIdx
|
||||
}
|
||||
|
||||
if err := gui.refreshMainView(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return gui.focusSelection(true)
|
||||
}
|
||||
|
||||
func (gui *Gui) handleCycleLine(change int) error {
|
||||
state := gui.State.Panels.LineByLine
|
||||
|
||||
if state.SelectMode == HUNK {
|
||||
newHunk := state.PatchParser.GetHunkContainingLine(state.SelectedLineIdx, change)
|
||||
return gui.selectNewHunk(newHunk)
|
||||
}
|
||||
|
||||
return gui.handleSelectNewLine(state.SelectedLineIdx + change)
|
||||
}
|
||||
|
||||
func (gui *Gui) handleSelectNewLine(newSelectedLineIdx int) error {
|
||||
state := gui.State.Panels.LineByLine
|
||||
|
||||
if newSelectedLineIdx < 0 {
|
||||
newSelectedLineIdx = 0
|
||||
} else if newSelectedLineIdx > len(state.PatchParser.PatchLines)-1 {
|
||||
newSelectedLineIdx = len(state.PatchParser.PatchLines) - 1
|
||||
}
|
||||
|
||||
state.SelectedLineIdx = newSelectedLineIdx
|
||||
|
||||
if state.SelectMode == RANGE {
|
||||
if state.SelectedLineIdx < state.FirstLineIdx {
|
||||
state.FirstLineIdx = state.SelectedLineIdx
|
||||
} else {
|
||||
state.LastLineIdx = state.SelectedLineIdx
|
||||
}
|
||||
} else {
|
||||
state.LastLineIdx = state.SelectedLineIdx
|
||||
state.FirstLineIdx = state.SelectedLineIdx
|
||||
}
|
||||
|
||||
if err := gui.refreshMainView(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return gui.focusSelection(false)
|
||||
}
|
||||
|
||||
func (gui *Gui) handleMouseDown(g *gocui.Gui, v *gocui.View) error {
|
||||
state := gui.State.Panels.LineByLine
|
||||
|
||||
if gui.popupPanelFocused() {
|
||||
return nil
|
||||
}
|
||||
|
||||
newSelectedLineIdx := v.SelectedLineIdx()
|
||||
state.FirstLineIdx = newSelectedLineIdx
|
||||
state.LastLineIdx = newSelectedLineIdx
|
||||
|
||||
state.SelectMode = RANGE
|
||||
|
||||
return gui.handleSelectNewLine(newSelectedLineIdx)
|
||||
}
|
||||
|
||||
func (gui *Gui) handleMouseDrag(g *gocui.Gui, v *gocui.View) error {
|
||||
if gui.popupPanelFocused() {
|
||||
return nil
|
||||
}
|
||||
|
||||
return gui.handleSelectNewLine(v.SelectedLineIdx())
|
||||
}
|
||||
|
||||
func (gui *Gui) handleMouseScrollUp(g *gocui.Gui, v *gocui.View) error {
|
||||
state := gui.State.Panels.LineByLine
|
||||
|
||||
if gui.popupPanelFocused() {
|
||||
return nil
|
||||
}
|
||||
|
||||
state.SelectMode = LINE
|
||||
|
||||
return gui.handleCycleLine(-1)
|
||||
}
|
||||
|
||||
func (gui *Gui) handleMouseScrollDown(g *gocui.Gui, v *gocui.View) error {
|
||||
state := gui.State.Panels.LineByLine
|
||||
|
||||
if gui.popupPanelFocused() {
|
||||
return nil
|
||||
}
|
||||
|
||||
state.SelectMode = LINE
|
||||
|
||||
return gui.handleCycleLine(1)
|
||||
}
|
||||
|
||||
func (gui *Gui) refreshMainView() error {
|
||||
state := gui.State.Panels.LineByLine
|
||||
|
||||
var includedLineIndices []int
|
||||
// I'd prefer not to have knowledge of contexts using this file but I'm not sure
|
||||
// how to get around this
|
||||
if gui.State.Context == "patch-building" {
|
||||
filename := gui.State.CommitFiles[gui.State.Panels.CommitFiles.SelectedLine].Name
|
||||
includedLineIndices = gui.GitCommand.PatchManager.GetFileIncLineIndices(filename)
|
||||
}
|
||||
colorDiff := state.PatchParser.Render(state.FirstLineIdx, state.LastLineIdx, includedLineIndices)
|
||||
|
||||
mainView := gui.getMainView()
|
||||
mainView.Highlight = true
|
||||
mainView.Wrap = false
|
||||
|
||||
gui.g.Update(func(*gocui.Gui) error {
|
||||
return gui.setViewContent(gui.g, gui.getMainView(), colorDiff)
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// focusSelection works out the best focus for the staging panel given the
|
||||
// selected line and size of the hunk
|
||||
func (gui *Gui) focusSelection(includeCurrentHunk bool) error {
|
||||
stagingView := gui.getMainView()
|
||||
state := gui.State.Panels.LineByLine
|
||||
|
||||
_, viewHeight := stagingView.Size()
|
||||
bufferHeight := viewHeight - 1
|
||||
_, origin := stagingView.Origin()
|
||||
|
||||
firstLineIdx := state.SelectedLineIdx
|
||||
lastLineIdx := state.SelectedLineIdx
|
||||
|
||||
if includeCurrentHunk {
|
||||
hunk := state.PatchParser.GetHunkContainingLine(state.SelectedLineIdx, 0)
|
||||
firstLineIdx = hunk.FirstLineIdx
|
||||
lastLineIdx = hunk.LastLineIdx
|
||||
}
|
||||
|
||||
margin := 0 // we may want to have a margin in place to show context but right now I'm thinking we keep this at zero
|
||||
|
||||
var newOrigin int
|
||||
if firstLineIdx-origin < margin {
|
||||
newOrigin = firstLineIdx - margin
|
||||
} else if lastLineIdx-origin > bufferHeight-margin {
|
||||
newOrigin = lastLineIdx - bufferHeight + margin
|
||||
} else {
|
||||
newOrigin = origin
|
||||
}
|
||||
|
||||
gui.g.Update(func(*gocui.Gui) error {
|
||||
if err := stagingView.SetOrigin(0, newOrigin); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return stagingView.SetCursor(0, state.SelectedLineIdx-newOrigin)
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) handleToggleSelectRange(g *gocui.Gui, v *gocui.View) error {
|
||||
state := gui.State.Panels.LineByLine
|
||||
if state.SelectMode == RANGE {
|
||||
state.SelectMode = LINE
|
||||
} else {
|
||||
state.SelectMode = RANGE
|
||||
}
|
||||
state.FirstLineIdx, state.LastLineIdx = state.SelectedLineIdx, state.SelectedLineIdx
|
||||
|
||||
return gui.refreshMainView()
|
||||
}
|
||||
|
||||
func (gui *Gui) handleToggleSelectHunk(g *gocui.Gui, v *gocui.View) error {
|
||||
state := gui.State.Panels.LineByLine
|
||||
|
||||
if state.SelectMode == HUNK {
|
||||
state.SelectMode = LINE
|
||||
state.FirstLineIdx, state.LastLineIdx = state.SelectedLineIdx, state.SelectedLineIdx
|
||||
} else {
|
||||
state.SelectMode = HUNK
|
||||
selectedHunk := state.PatchParser.GetHunkContainingLine(state.SelectedLineIdx, 0)
|
||||
state.FirstLineIdx, state.LastLineIdx = selectedHunk.FirstLineIdx, selectedHunk.LastLineIdx
|
||||
}
|
||||
|
||||
if err := gui.refreshMainView(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return gui.focusSelection(state.SelectMode == HUNK)
|
||||
}
|
||||
@@ -2,46 +2,49 @@ package gui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/theme"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
)
|
||||
|
||||
func (gui *Gui) handleMenuPress(g *gocui.Gui, v *gocui.View) error {
|
||||
lineNumber := gui.getItemPosition(v)
|
||||
if gui.State.Keys[lineNumber].Key == nil {
|
||||
return nil
|
||||
}
|
||||
if len(gui.State.Keys) > lineNumber {
|
||||
err := gui.handleMenuClose(g, v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return gui.State.Keys[lineNumber].Handler(g, v)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
// list panel functions
|
||||
|
||||
func (gui *Gui) handleMenuSelect(g *gocui.Gui, v *gocui.View) error {
|
||||
// doing nothing for now
|
||||
// but it is needed for switch in newLineFocused
|
||||
return nil
|
||||
return gui.focusPoint(0, gui.State.Panels.Menu.SelectedLine, gui.State.MenuItemCount, v)
|
||||
}
|
||||
|
||||
func (gui *Gui) renderMenuOptions(g *gocui.Gui) error {
|
||||
func (gui *Gui) handleMenuNextLine(g *gocui.Gui, v *gocui.View) error {
|
||||
panelState := gui.State.Panels.Menu
|
||||
gui.changeSelectedLine(&panelState.SelectedLine, v.LinesHeight(), false)
|
||||
|
||||
return gui.handleMenuSelect(g, v)
|
||||
}
|
||||
|
||||
func (gui *Gui) handleMenuPrevLine(g *gocui.Gui, v *gocui.View) error {
|
||||
panelState := gui.State.Panels.Menu
|
||||
gui.changeSelectedLine(&panelState.SelectedLine, v.LinesHeight(), true)
|
||||
|
||||
return gui.handleMenuSelect(g, v)
|
||||
}
|
||||
|
||||
// specific functions
|
||||
|
||||
func (gui *Gui) renderMenuOptions() error {
|
||||
optionsMap := map[string]string{
|
||||
"esc/q": gui.Tr.SLocalize("close"),
|
||||
"↑ ↓": gui.Tr.SLocalize("navigate"),
|
||||
"space": gui.Tr.SLocalize("execute"),
|
||||
}
|
||||
return gui.renderOptionsMap(g, optionsMap)
|
||||
return gui.renderOptionsMap(optionsMap)
|
||||
}
|
||||
|
||||
func (gui *Gui) handleMenuClose(g *gocui.Gui, v *gocui.View) error {
|
||||
// better to delete because for example after closing update confirmation panel,
|
||||
// the focus isn't set back to any of panels and one is unable to even quit
|
||||
//_, err := g.SetViewOnBottom(v.Name())
|
||||
for _, key := range []gocui.Key{gocui.KeySpace, gocui.KeyEnter} {
|
||||
if err := g.DeleteKeybinding("menu", key, gocui.ModNone); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
err := g.DeleteView("menu")
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -49,83 +52,66 @@ func (gui *Gui) handleMenuClose(g *gocui.Gui, v *gocui.View) error {
|
||||
return gui.returnFocus(g, v)
|
||||
}
|
||||
|
||||
func (gui *Gui) GetKey(binding Binding) string {
|
||||
r, ok := binding.Key.(rune)
|
||||
key := ""
|
||||
|
||||
if ok {
|
||||
key = string(r)
|
||||
} else if binding.KeyReadable != "" {
|
||||
key = binding.KeyReadable
|
||||
}
|
||||
|
||||
return key
|
||||
}
|
||||
|
||||
func (gui *Gui) GetMaxKeyLength(bindings []Binding) int {
|
||||
max := 0
|
||||
for _, binding := range bindings {
|
||||
keyLength := len(gui.GetKey(binding))
|
||||
if keyLength > max {
|
||||
max = keyLength
|
||||
}
|
||||
}
|
||||
return max
|
||||
}
|
||||
|
||||
func (gui *Gui) handleMenu(g *gocui.Gui, v *gocui.View) error {
|
||||
var (
|
||||
contentGlobal, contentPanel []string
|
||||
bindingsGlobal, bindingsPanel []Binding
|
||||
)
|
||||
// clear keys slice, so we don't have ghost elements
|
||||
gui.State.Keys = gui.State.Keys[:0]
|
||||
bindings := gui.GetKeybindings()
|
||||
padWidth := gui.GetMaxKeyLength(bindings)
|
||||
|
||||
for _, binding := range bindings {
|
||||
key := gui.GetKey(binding)
|
||||
if key != "" && binding.Description != "" {
|
||||
content := fmt.Sprintf("%s %s", utils.WithPadding(key, padWidth), binding.Description)
|
||||
switch binding.ViewName {
|
||||
case "":
|
||||
contentGlobal = append(contentGlobal, content)
|
||||
bindingsGlobal = append(bindingsGlobal, binding)
|
||||
case v.Name():
|
||||
contentPanel = append(contentPanel, content)
|
||||
bindingsPanel = append(bindingsPanel, binding)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// append dummy element to have a separator between
|
||||
// panel and global keybindings
|
||||
contentPanel = append(contentPanel, "")
|
||||
bindingsPanel = append(bindingsPanel, Binding{})
|
||||
|
||||
content := append(contentPanel, contentGlobal...)
|
||||
gui.State.Keys = append(bindingsPanel, bindingsGlobal...)
|
||||
// append newline at the end so the last line would be selectable
|
||||
contentJoined := strings.Join(content, "\n") + "\n"
|
||||
|
||||
// y1-1 so there will not be an extra space at the end of panel
|
||||
x0, y0, x1, y1 := gui.getConfirmationPanelDimensions(g, contentJoined)
|
||||
menuView, _ := g.SetView("menu", x0, y0, x1, y1-1, 0)
|
||||
menuView.Title = strings.Title(gui.Tr.SLocalize("menu"))
|
||||
menuView.FgColor = gocui.ColorWhite
|
||||
|
||||
if err := gui.renderMenuOptions(g); err != nil {
|
||||
func (gui *Gui) createMenu(title string, items interface{}, itemCount int, handlePress func(int) error) error {
|
||||
isFocused := gui.g.CurrentView().Name() == "menu"
|
||||
gui.State.MenuItemCount = itemCount
|
||||
list, err := utils.RenderList(items, isFocused)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Fprint(menuView, contentJoined)
|
||||
x0, y0, x1, y1 := gui.getConfirmationPanelDimensions(gui.g, false, list)
|
||||
menuView, _ := gui.g.SetView("menu", x0, y0, x1, y1, 0)
|
||||
menuView.Title = title
|
||||
menuView.FgColor = theme.GocuiDefaultTextColor
|
||||
menuView.Clear()
|
||||
fmt.Fprint(menuView, list)
|
||||
gui.State.Panels.Menu.SelectedLine = 0
|
||||
|
||||
g.Update(func(g *gocui.Gui) error {
|
||||
_, err := g.SetViewOnTop("menu")
|
||||
if err != nil {
|
||||
wrappedHandlePress := func(g *gocui.Gui, v *gocui.View) error {
|
||||
selectedLine := gui.State.Panels.Menu.SelectedLine
|
||||
if err := handlePress(selectedLine); err != nil {
|
||||
return err
|
||||
}
|
||||
return gui.switchFocus(g, v, menuView)
|
||||
if _, err := gui.g.View("menu"); err == nil {
|
||||
if _, err := gui.g.SetViewOnBottom("menu"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return gui.returnFocus(gui.g, menuView)
|
||||
}
|
||||
|
||||
gui.State.Panels.Menu.OnPress = wrappedHandlePress
|
||||
|
||||
for _, key := range []gocui.Key{gocui.KeySpace, gocui.KeyEnter, 'y'} {
|
||||
_ = gui.g.DeleteKeybinding("menu", key, gocui.ModNone)
|
||||
|
||||
if err := gui.g.SetKeybinding("menu", key, gocui.ModNone, wrappedHandlePress); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
gui.g.Update(func(g *gocui.Gui) error {
|
||||
if _, err := gui.g.View("menu"); err == nil {
|
||||
if _, err := g.SetViewOnTop("menu"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
currentView := gui.g.CurrentView()
|
||||
return gui.switchFocus(gui.g, currentView, menuView)
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) handleMenuClick(g *gocui.Gui, v *gocui.View) error {
|
||||
itemCount := gui.State.MenuItemCount
|
||||
handleSelect := gui.handleMenuSelect
|
||||
selectedLine := &gui.State.Panels.Menu.SelectedLine
|
||||
|
||||
if err := gui.handleClick(v, itemCount, selectedLine, handleSelect); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return gui.State.Panels.Menu.OnPress(g, v)
|
||||
}
|
||||
|
||||
@@ -11,8 +11,10 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/golang-collections/collections/stack"
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands"
|
||||
"github.com/jesseduffield/lazygit/pkg/theme"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
)
|
||||
|
||||
@@ -20,11 +22,13 @@ func (gui *Gui) findConflicts(content string) ([]commands.Conflict, error) {
|
||||
conflicts := make([]commands.Conflict, 0)
|
||||
var newConflict commands.Conflict
|
||||
for i, line := range utils.SplitLines(content) {
|
||||
if line == "<<<<<<< HEAD" || line == "<<<<<<< MERGE_HEAD" || line == "<<<<<<< Updated upstream" {
|
||||
trimmedLine := strings.TrimPrefix(line, "++")
|
||||
gui.Log.Info(trimmedLine)
|
||||
if trimmedLine == "<<<<<<< HEAD" || trimmedLine == "<<<<<<< MERGE_HEAD" || trimmedLine == "<<<<<<< Updated upstream" || trimmedLine == "<<<<<<< ours" {
|
||||
newConflict = commands.Conflict{Start: i}
|
||||
} else if line == "=======" {
|
||||
} else if trimmedLine == "=======" {
|
||||
newConflict.Middle = i
|
||||
} else if strings.HasPrefix(line, ">>>>>>> ") {
|
||||
} else if strings.HasPrefix(trimmedLine, ">>>>>>> ") {
|
||||
newConflict.End = i
|
||||
conflicts = append(conflicts, newConflict)
|
||||
}
|
||||
@@ -47,7 +51,7 @@ func (gui *Gui) coloredConflictFile(content string, conflicts []commands.Conflic
|
||||
conflict, remainingConflicts := gui.shiftConflict(conflicts)
|
||||
var outputBuffer bytes.Buffer
|
||||
for i, line := range utils.SplitLines(content) {
|
||||
colourAttr := color.FgWhite
|
||||
colourAttr := theme.DefaultTextColor
|
||||
if i == conflict.Start || i == conflict.Middle || i == conflict.End {
|
||||
colourAttr = color.FgRed
|
||||
}
|
||||
@@ -64,29 +68,29 @@ func (gui *Gui) coloredConflictFile(content string, conflicts []commands.Conflic
|
||||
}
|
||||
|
||||
func (gui *Gui) handleSelectTop(g *gocui.Gui, v *gocui.View) error {
|
||||
gui.State.ConflictTop = true
|
||||
return gui.refreshMergePanel(g)
|
||||
gui.State.Panels.Merging.ConflictTop = true
|
||||
return gui.refreshMergePanel()
|
||||
}
|
||||
|
||||
func (gui *Gui) handleSelectBottom(g *gocui.Gui, v *gocui.View) error {
|
||||
gui.State.ConflictTop = false
|
||||
return gui.refreshMergePanel(g)
|
||||
gui.State.Panels.Merging.ConflictTop = false
|
||||
return gui.refreshMergePanel()
|
||||
}
|
||||
|
||||
func (gui *Gui) handleSelectNextConflict(g *gocui.Gui, v *gocui.View) error {
|
||||
if gui.State.ConflictIndex >= len(gui.State.Conflicts)-1 {
|
||||
if gui.State.Panels.Merging.ConflictIndex >= len(gui.State.Panels.Merging.Conflicts)-1 {
|
||||
return nil
|
||||
}
|
||||
gui.State.ConflictIndex++
|
||||
return gui.refreshMergePanel(g)
|
||||
gui.State.Panels.Merging.ConflictIndex++
|
||||
return gui.refreshMergePanel()
|
||||
}
|
||||
|
||||
func (gui *Gui) handleSelectPrevConflict(g *gocui.Gui, v *gocui.View) error {
|
||||
if gui.State.ConflictIndex <= 0 {
|
||||
if gui.State.Panels.Merging.ConflictIndex <= 0 {
|
||||
return nil
|
||||
}
|
||||
gui.State.ConflictIndex--
|
||||
return gui.refreshMergePanel(g)
|
||||
gui.State.Panels.Merging.ConflictIndex--
|
||||
return gui.refreshMergePanel()
|
||||
}
|
||||
|
||||
func (gui *Gui) isIndexToDelete(i int, conflict commands.Conflict, pick string) bool {
|
||||
@@ -133,108 +137,112 @@ func (gui *Gui) pushFileSnapshot(g *gocui.Gui) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
gui.State.EditHistory.Push(content)
|
||||
gui.State.Panels.Merging.EditHistory.Push(content)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) handlePopFileSnapshot(g *gocui.Gui, v *gocui.View) error {
|
||||
if gui.State.EditHistory.Len() == 0 {
|
||||
if gui.State.Panels.Merging.EditHistory.Len() == 0 {
|
||||
return nil
|
||||
}
|
||||
prevContent := gui.State.EditHistory.Pop().(string)
|
||||
prevContent := gui.State.Panels.Merging.EditHistory.Pop().(string)
|
||||
gitFile, err := gui.getSelectedFile(g)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ioutil.WriteFile(gitFile.Name, []byte(prevContent), 0644)
|
||||
return gui.refreshMergePanel(g)
|
||||
return gui.refreshMergePanel()
|
||||
}
|
||||
|
||||
func (gui *Gui) handlePickHunk(g *gocui.Gui, v *gocui.View) error {
|
||||
conflict := gui.State.Conflicts[gui.State.ConflictIndex]
|
||||
conflict := gui.State.Panels.Merging.Conflicts[gui.State.Panels.Merging.ConflictIndex]
|
||||
gui.pushFileSnapshot(g)
|
||||
pick := "bottom"
|
||||
if gui.State.ConflictTop {
|
||||
if gui.State.Panels.Merging.ConflictTop {
|
||||
pick = "top"
|
||||
}
|
||||
err := gui.resolveConflict(g, conflict, pick)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
gui.refreshMergePanel(g)
|
||||
return nil
|
||||
|
||||
// if that was the last conflict, finish the merge for this file
|
||||
if len(gui.State.Panels.Merging.Conflicts) == 1 {
|
||||
if err := gui.handleCompleteMerge(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return gui.refreshMergePanel()
|
||||
}
|
||||
|
||||
func (gui *Gui) handlePickBothHunks(g *gocui.Gui, v *gocui.View) error {
|
||||
conflict := gui.State.Conflicts[gui.State.ConflictIndex]
|
||||
conflict := gui.State.Panels.Merging.Conflicts[gui.State.Panels.Merging.ConflictIndex]
|
||||
gui.pushFileSnapshot(g)
|
||||
err := gui.resolveConflict(g, conflict, "both")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return gui.refreshMergePanel(g)
|
||||
return gui.refreshMergePanel()
|
||||
}
|
||||
|
||||
func (gui *Gui) refreshMergePanel(g *gocui.Gui) error {
|
||||
cat, err := gui.catSelectedFile(g)
|
||||
func (gui *Gui) refreshMergePanel() error {
|
||||
panelState := gui.State.Panels.Merging
|
||||
cat, err := gui.catSelectedFile(gui.g)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if cat == "" {
|
||||
return nil
|
||||
}
|
||||
gui.State.Conflicts, err = gui.findConflicts(cat)
|
||||
panelState.Conflicts, err = gui.findConflicts(cat)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(gui.State.Conflicts) == 0 {
|
||||
return gui.handleCompleteMerge(g)
|
||||
} else if gui.State.ConflictIndex > len(gui.State.Conflicts)-1 {
|
||||
gui.State.ConflictIndex = len(gui.State.Conflicts) - 1
|
||||
// handle potential fixes that the user made in their editor since we last refreshed
|
||||
if len(panelState.Conflicts) == 0 {
|
||||
return gui.handleCompleteMerge()
|
||||
} else if panelState.ConflictIndex > len(panelState.Conflicts)-1 {
|
||||
panelState.ConflictIndex = len(panelState.Conflicts) - 1
|
||||
}
|
||||
hasFocus := gui.currentViewName(g) == "main"
|
||||
if hasFocus {
|
||||
gui.renderMergeOptions(g)
|
||||
}
|
||||
content, err := gui.coloredConflictFile(cat, gui.State.Conflicts, gui.State.ConflictIndex, gui.State.ConflictTop, hasFocus)
|
||||
|
||||
hasFocus := gui.currentViewName() == "main"
|
||||
content, err := gui.coloredConflictFile(cat, panelState.Conflicts, panelState.ConflictIndex, panelState.ConflictTop, hasFocus)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := gui.scrollToConflict(g); err != nil {
|
||||
if err := gui.renderString(gui.g, "main", content); err != nil {
|
||||
return err
|
||||
}
|
||||
return gui.renderString(g, "main", content)
|
||||
if err := gui.scrollToConflict(gui.g); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
mainView := gui.getMainView()
|
||||
mainView.Wrap = false
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) scrollToConflict(g *gocui.Gui) error {
|
||||
mainView, err := g.View("main")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(gui.State.Conflicts) == 0 {
|
||||
panelState := gui.State.Panels.Merging
|
||||
if len(panelState.Conflicts) == 0 {
|
||||
return nil
|
||||
}
|
||||
conflict := gui.State.Conflicts[gui.State.ConflictIndex]
|
||||
ox, _ := mainView.Origin()
|
||||
_, height := mainView.Size()
|
||||
mergingView := gui.getMainView()
|
||||
conflict := panelState.Conflicts[panelState.ConflictIndex]
|
||||
ox, _ := mergingView.Origin()
|
||||
_, height := mergingView.Size()
|
||||
conflictMiddle := (conflict.End + conflict.Start) / 2
|
||||
newOriginY := int(math.Max(0, float64(conflictMiddle-(height/2))))
|
||||
return mainView.SetOrigin(ox, newOriginY)
|
||||
gui.g.Update(func(g *gocui.Gui) error {
|
||||
return mergingView.SetOrigin(ox, newOriginY)
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) switchToMerging(g *gocui.Gui) error {
|
||||
gui.State.ConflictIndex = 0
|
||||
gui.State.ConflictTop = true
|
||||
_, err := g.SetCurrentView("main")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return gui.refreshMergePanel(g)
|
||||
}
|
||||
|
||||
func (gui *Gui) renderMergeOptions(g *gocui.Gui) error {
|
||||
return gui.renderOptionsMap(g, map[string]string{
|
||||
func (gui *Gui) renderMergeOptions() error {
|
||||
return gui.renderOptionsMap(map[string]string{
|
||||
"↑ ↓": gui.Tr.SLocalize("selectHunk"),
|
||||
"← →": gui.Tr.SLocalize("navigateConflicts"),
|
||||
"space": gui.Tr.SLocalize("pickHunk"),
|
||||
@@ -244,20 +252,40 @@ func (gui *Gui) renderMergeOptions(g *gocui.Gui) error {
|
||||
}
|
||||
|
||||
func (gui *Gui) handleEscapeMerge(g *gocui.Gui, v *gocui.View) error {
|
||||
filesView, err := g.View("files")
|
||||
if err != nil {
|
||||
gui.State.Panels.Merging.EditHistory = stack.New()
|
||||
if err := gui.refreshFiles(); err != nil {
|
||||
return err
|
||||
}
|
||||
gui.refreshFiles(g)
|
||||
return gui.switchFocus(g, v, filesView)
|
||||
// it's possible this method won't be called from the merging view so we need to
|
||||
// ensure we only 'return' focus if we already have it
|
||||
if gui.g.CurrentView() == gui.getMainView() {
|
||||
return gui.switchFocus(g, v, gui.getFilesView())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) handleCompleteMerge(g *gocui.Gui) error {
|
||||
filesView, err := g.View("files")
|
||||
if err != nil {
|
||||
func (gui *Gui) handleCompleteMerge() error {
|
||||
if err := gui.stageSelectedFile(gui.g); err != nil {
|
||||
return err
|
||||
}
|
||||
gui.stageSelectedFile(g)
|
||||
gui.refreshFiles(g)
|
||||
return gui.switchFocus(g, nil, filesView)
|
||||
if err := gui.refreshFiles(); err != nil {
|
||||
return err
|
||||
}
|
||||
// if we got conflicts after unstashing, we don't want to call any git
|
||||
// commands to continue rebasing/merging here
|
||||
if gui.State.WorkingTreeState == "normal" {
|
||||
return gui.handleEscapeMerge(gui.g, gui.getMainView())
|
||||
}
|
||||
// if there are no more files with merge conflicts, we should ask whether the user wants to continue
|
||||
if !gui.anyFilesWithMergeConflicts() {
|
||||
return gui.promptToContinue()
|
||||
}
|
||||
return gui.handleEscapeMerge(gui.g, gui.getMainView())
|
||||
}
|
||||
|
||||
// promptToContinue asks the user if they want to continue the rebase/merge that's in progress
|
||||
func (gui *Gui) promptToContinue() error {
|
||||
return gui.createConfirmationPanel(gui.g, gui.getFilesView(), true, "continue", gui.Tr.SLocalize("ConflictsResolved"), func(g *gocui.Gui, v *gocui.View) error {
|
||||
return gui.genericMergeCommand("continue")
|
||||
}, nil)
|
||||
}
|
||||
|
||||
53
pkg/gui/options_menu_panel.go
Normal file
53
pkg/gui/options_menu_panel.go
Normal file
@@ -0,0 +1,53 @@
|
||||
package gui
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/go-errors/errors"
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
)
|
||||
|
||||
func (gui *Gui) getBindings(v *gocui.View) []*Binding {
|
||||
var (
|
||||
bindingsGlobal, bindingsPanel []*Binding
|
||||
)
|
||||
|
||||
bindings := gui.GetCurrentKeybindings()
|
||||
|
||||
for _, binding := range bindings {
|
||||
if binding.GetKey() != "" && binding.Description != "" {
|
||||
switch binding.ViewName {
|
||||
case "":
|
||||
bindingsGlobal = append(bindingsGlobal, binding)
|
||||
case v.Name():
|
||||
bindingsPanel = append(bindingsPanel, binding)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// append dummy element to have a separator between
|
||||
// panel and global keybindings
|
||||
bindingsPanel = append(bindingsPanel, &Binding{})
|
||||
return append(bindingsPanel, bindingsGlobal...)
|
||||
}
|
||||
|
||||
func (gui *Gui) handleCreateOptionsMenu(g *gocui.Gui, v *gocui.View) error {
|
||||
bindings := gui.getBindings(v)
|
||||
|
||||
handleMenuPress := func(index int) error {
|
||||
if bindings[index].Key == nil {
|
||||
return nil
|
||||
}
|
||||
if index >= len(bindings) {
|
||||
return errors.New("Index is greater than size of bindings")
|
||||
}
|
||||
err := gui.handleMenuClose(g, v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return bindings[index].Handler(g, v)
|
||||
}
|
||||
|
||||
return gui.createMenu(strings.Title(gui.Tr.SLocalize("menu")), bindings, len(bindings), handleMenuPress)
|
||||
}
|
||||
116
pkg/gui/patch_building_panel.go
Normal file
116
pkg/gui/patch_building_panel.go
Normal file
@@ -0,0 +1,116 @@
|
||||
package gui
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/gocui"
|
||||
)
|
||||
|
||||
func (gui *Gui) refreshPatchBuildingPanel(selectedLineIdx int) error {
|
||||
if !gui.GitCommand.PatchManager.CommitSelected() {
|
||||
return gui.handleEscapePatchBuildingPanel(gui.g, nil)
|
||||
}
|
||||
|
||||
gui.State.SplitMainPanel = true
|
||||
|
||||
gui.getMainView().Title = "Patch"
|
||||
gui.getSecondaryView().Title = "Custom Patch"
|
||||
|
||||
// get diff from commit file that's currently selected
|
||||
commitFile := gui.getSelectedCommitFile(gui.g)
|
||||
if commitFile == nil {
|
||||
return gui.renderString(gui.g, "commitFiles", gui.Tr.SLocalize("NoCommiteFiles"))
|
||||
}
|
||||
|
||||
diff, err := gui.GitCommand.ShowCommitFile(commitFile.Sha, commitFile.Name, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
secondaryDiff := gui.GitCommand.PatchManager.RenderPatchForFile(commitFile.Name, true, false, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
empty, err := gui.refreshLineByLinePanel(diff, secondaryDiff, false, selectedLineIdx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if empty {
|
||||
return gui.handleEscapePatchBuildingPanel(gui.g, nil)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) handleAddSelectionToPatch(g *gocui.Gui, v *gocui.View) error {
|
||||
state := gui.State.Panels.LineByLine
|
||||
|
||||
// add range of lines to those set for the file
|
||||
commitFile := gui.getSelectedCommitFile(gui.g)
|
||||
if commitFile == nil {
|
||||
return gui.renderString(gui.g, "commitFiles", gui.Tr.SLocalize("NoCommiteFiles"))
|
||||
}
|
||||
|
||||
gui.GitCommand.PatchManager.AddFileLineRange(commitFile.Name, state.FirstLineIdx, state.LastLineIdx)
|
||||
|
||||
if err := gui.refreshCommitFilesView(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := gui.refreshPatchBuildingPanel(-1); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) handleRemoveSelectionFromPatch(g *gocui.Gui, v *gocui.View) error {
|
||||
state := gui.State.Panels.LineByLine
|
||||
|
||||
// add range of lines to those set for the file
|
||||
commitFile := gui.getSelectedCommitFile(gui.g)
|
||||
if commitFile == nil {
|
||||
return gui.renderString(gui.g, "commitFiles", gui.Tr.SLocalize("NoCommiteFiles"))
|
||||
}
|
||||
|
||||
gui.GitCommand.PatchManager.RemoveFileLineRange(commitFile.Name, state.FirstLineIdx, state.LastLineIdx)
|
||||
|
||||
if err := gui.refreshCommitFilesView(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := gui.refreshPatchBuildingPanel(-1); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) handleEscapePatchBuildingPanel(g *gocui.Gui, v *gocui.View) error {
|
||||
gui.State.Panels.LineByLine = nil
|
||||
gui.changeContext("normal")
|
||||
|
||||
if gui.GitCommand.PatchManager.IsEmpty() {
|
||||
gui.GitCommand.PatchManager.Reset()
|
||||
gui.State.SplitMainPanel = false
|
||||
}
|
||||
|
||||
return gui.switchFocus(gui.g, nil, gui.getCommitFilesView())
|
||||
}
|
||||
|
||||
func (gui *Gui) refreshSecondaryPatchPanel() error {
|
||||
if gui.GitCommand.PatchManager.CommitSelected() {
|
||||
gui.State.SplitMainPanel = true
|
||||
secondaryView := gui.getSecondaryView()
|
||||
secondaryView.Highlight = true
|
||||
secondaryView.Wrap = false
|
||||
|
||||
gui.g.Update(func(*gocui.Gui) error {
|
||||
return gui.setViewContent(gui.g, gui.getSecondaryView(), gui.GitCommand.PatchManager.RenderAggregatedPatchColored(false))
|
||||
})
|
||||
} else {
|
||||
gui.State.SplitMainPanel = false
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
127
pkg/gui/patch_options_panel.go
Normal file
127
pkg/gui/patch_options_panel.go
Normal file
@@ -0,0 +1,127 @@
|
||||
package gui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
)
|
||||
|
||||
type patchMenuOption struct {
|
||||
displayName string
|
||||
function func() error
|
||||
}
|
||||
|
||||
// GetDisplayStrings is a function.
|
||||
func (o *patchMenuOption) GetDisplayStrings(isFocused bool) []string {
|
||||
return []string{o.displayName}
|
||||
}
|
||||
|
||||
func (gui *Gui) handleCreatePatchOptionsMenu(g *gocui.Gui, v *gocui.View) error {
|
||||
if !gui.GitCommand.PatchManager.CommitSelected() {
|
||||
return gui.createErrorPanel(gui.g, gui.Tr.SLocalize("NoPatchError"))
|
||||
}
|
||||
|
||||
options := []*patchMenuOption{
|
||||
{displayName: fmt.Sprintf("remove patch from original commit (%s)", gui.GitCommand.PatchManager.CommitSha), function: gui.handleDeletePatchFromCommit},
|
||||
{displayName: "pull patch out into index", function: gui.handlePullPatchIntoWorkingTree},
|
||||
{displayName: "reset patch", function: gui.handleResetPatch},
|
||||
}
|
||||
|
||||
selectedCommit := gui.getSelectedCommit(gui.g)
|
||||
if selectedCommit != nil && gui.GitCommand.PatchManager.CommitSha != selectedCommit.Sha {
|
||||
// adding this option to index 1
|
||||
options = append(
|
||||
options[:1],
|
||||
append(
|
||||
[]*patchMenuOption{
|
||||
{
|
||||
displayName: fmt.Sprintf("move patch to selected commit (%s)", selectedCommit.Sha),
|
||||
function: gui.handleMovePatchToSelectedCommit,
|
||||
},
|
||||
}, options[1:]...,
|
||||
)...,
|
||||
)
|
||||
}
|
||||
|
||||
handleMenuPress := func(index int) error {
|
||||
return options[index].function()
|
||||
}
|
||||
|
||||
return gui.createMenu(gui.Tr.SLocalize("PatchOptionsTitle"), options, len(options), handleMenuPress)
|
||||
}
|
||||
|
||||
func (gui *Gui) getPatchCommitIndex() int {
|
||||
for index, commit := range gui.State.Commits {
|
||||
if commit.Sha == gui.GitCommand.PatchManager.CommitSha {
|
||||
return index
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
func (gui *Gui) validateNormalWorkingTreeState() (bool, error) {
|
||||
if gui.State.WorkingTreeState != "normal" {
|
||||
return false, gui.createErrorPanel(gui.g, gui.Tr.SLocalize("CantPatchWhileRebasingError"))
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (gui *Gui) returnFocusFromLineByLinePanelIfNecessary() error {
|
||||
if gui.State.Context == "patch-building" {
|
||||
return gui.handleEscapePatchBuildingPanel(gui.g, nil)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) handleDeletePatchFromCommit() error {
|
||||
if ok, err := gui.validateNormalWorkingTreeState(); !ok {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := gui.returnFocusFromLineByLinePanelIfNecessary(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return gui.WithWaitingStatus(gui.Tr.SLocalize("RebasingStatus"), func() error {
|
||||
commitIndex := gui.getPatchCommitIndex()
|
||||
err := gui.GitCommand.DeletePatchesFromCommit(gui.State.Commits, commitIndex, gui.GitCommand.PatchManager)
|
||||
return gui.handleGenericMergeCommandResult(err)
|
||||
})
|
||||
}
|
||||
|
||||
func (gui *Gui) handleMovePatchToSelectedCommit() error {
|
||||
if ok, err := gui.validateNormalWorkingTreeState(); !ok {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := gui.returnFocusFromLineByLinePanelIfNecessary(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return gui.WithWaitingStatus(gui.Tr.SLocalize("RebasingStatus"), func() error {
|
||||
commitIndex := gui.getPatchCommitIndex()
|
||||
err := gui.GitCommand.MovePatchToSelectedCommit(gui.State.Commits, commitIndex, gui.State.Panels.Commits.SelectedLine, gui.GitCommand.PatchManager)
|
||||
return gui.handleGenericMergeCommandResult(err)
|
||||
})
|
||||
}
|
||||
|
||||
func (gui *Gui) handlePullPatchIntoWorkingTree() error {
|
||||
if ok, err := gui.validateNormalWorkingTreeState(); !ok {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := gui.returnFocusFromLineByLinePanelIfNecessary(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return gui.WithWaitingStatus(gui.Tr.SLocalize("RebasingStatus"), func() error {
|
||||
commitIndex := gui.getPatchCommitIndex()
|
||||
err := gui.GitCommand.PullPatchIntoIndex(gui.State.Commits, commitIndex, gui.GitCommand.PatchManager)
|
||||
return gui.handleGenericMergeCommandResult(err)
|
||||
})
|
||||
}
|
||||
|
||||
func (gui *Gui) handleResetPatch() error {
|
||||
gui.GitCommand.PatchManager.Reset()
|
||||
return gui.refreshCommitFilesView()
|
||||
}
|
||||
48
pkg/gui/quitting.go
Normal file
48
pkg/gui/quitting.go
Normal file
@@ -0,0 +1,48 @@
|
||||
package gui
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
)
|
||||
|
||||
// when a user runs lazygit with the LAZYGIT_NEW_DIR_FILE env variable defined
|
||||
// we will write the current directory to that file on exit so that their
|
||||
// shell can then change to that directory. That means you don't get kicked
|
||||
// back to the directory that you started with.
|
||||
func (gui *Gui) recordCurrentDirectory() error {
|
||||
if os.Getenv("LAZYGIT_NEW_DIR_FILE") == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
// determine current directory, set it in LAZYGIT_NEW_DIR_FILE
|
||||
dirName, err := os.Getwd()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return gui.OSCommand.CreateFileWithContent(os.Getenv("LAZYGIT_NEW_DIR_FILE"), dirName)
|
||||
}
|
||||
|
||||
func (gui *Gui) handleQuitWithoutChangingDirectory(g *gocui.Gui, v *gocui.View) error {
|
||||
gui.State.RetainOriginalDir = true
|
||||
return gui.quit(v)
|
||||
}
|
||||
|
||||
func (gui *Gui) handleQuit(g *gocui.Gui, v *gocui.View) error {
|
||||
gui.State.RetainOriginalDir = false
|
||||
return gui.quit(v)
|
||||
}
|
||||
|
||||
func (gui *Gui) quit(v *gocui.View) error {
|
||||
if gui.State.Updating {
|
||||
return gui.createUpdateQuitConfirmation(gui.g, v)
|
||||
}
|
||||
if gui.Config.GetUserConfig().GetBool("confirmOnQuit") {
|
||||
return gui.createConfirmationPanel(gui.g, v, true, "", gui.Tr.SLocalize("ConfirmQuit"), func(g *gocui.Gui, v *gocui.View) error {
|
||||
return gocui.ErrQuit
|
||||
}, nil)
|
||||
}
|
||||
|
||||
return gocui.ErrQuit
|
||||
}
|
||||
98
pkg/gui/rebase_options_panel.go
Normal file
98
pkg/gui/rebase_options_panel.go
Normal file
@@ -0,0 +1,98 @@
|
||||
package gui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
)
|
||||
|
||||
type option struct {
|
||||
value string
|
||||
}
|
||||
|
||||
// GetDisplayStrings is a function.
|
||||
func (r *option) GetDisplayStrings(isFocused bool) []string {
|
||||
return []string{r.value}
|
||||
}
|
||||
|
||||
func (gui *Gui) handleCreateRebaseOptionsMenu(g *gocui.Gui, v *gocui.View) error {
|
||||
options := []*option{
|
||||
{value: "continue"},
|
||||
{value: "abort"},
|
||||
}
|
||||
|
||||
if gui.State.WorkingTreeState == "rebasing" {
|
||||
options = append(options, &option{value: "skip"})
|
||||
}
|
||||
|
||||
options = append(options, &option{value: "cancel"})
|
||||
|
||||
handleMenuPress := func(index int) error {
|
||||
command := options[index].value
|
||||
if command == "cancel" {
|
||||
return nil
|
||||
}
|
||||
return gui.genericMergeCommand(command)
|
||||
}
|
||||
|
||||
var title string
|
||||
if gui.State.WorkingTreeState == "merging" {
|
||||
title = gui.Tr.SLocalize("MergeOptionsTitle")
|
||||
} else {
|
||||
title = gui.Tr.SLocalize("RebaseOptionsTitle")
|
||||
}
|
||||
|
||||
return gui.createMenu(title, options, len(options), handleMenuPress)
|
||||
}
|
||||
|
||||
func (gui *Gui) genericMergeCommand(command string) error {
|
||||
status := gui.State.WorkingTreeState
|
||||
|
||||
if status != "merging" && status != "rebasing" {
|
||||
return gui.createErrorPanel(gui.g, gui.Tr.SLocalize("NotMergingOrRebasing"))
|
||||
}
|
||||
|
||||
commandType := strings.Replace(status, "ing", "e", 1)
|
||||
// we should end up with a command like 'git merge --continue'
|
||||
|
||||
// it's impossible for a rebase to require a commit so we'll use a subprocess only if it's a merge
|
||||
if status == "merging" && command != "abort" && gui.Config.GetUserConfig().GetBool("git.merging.manualCommit") {
|
||||
sub := gui.OSCommand.PrepareSubProcess("git", commandType, fmt.Sprintf("--%s", command))
|
||||
if sub != nil {
|
||||
gui.SubProcess = sub
|
||||
return gui.Errors.ErrSubProcess
|
||||
}
|
||||
return nil
|
||||
}
|
||||
result := gui.GitCommand.GenericMerge(commandType, command)
|
||||
if err := gui.handleGenericMergeCommandResult(result); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) handleGenericMergeCommandResult(result error) error {
|
||||
if err := gui.refreshSidePanels(gui.g); err != nil {
|
||||
return err
|
||||
}
|
||||
if result == nil {
|
||||
return nil
|
||||
} else if result == gui.Errors.ErrSubProcess {
|
||||
return result
|
||||
} else if strings.Contains(result.Error(), "No changes - did you forget to use") {
|
||||
return gui.genericMergeCommand("skip")
|
||||
} else if strings.Contains(result.Error(), "The previous cherry-pick is now empty") {
|
||||
return gui.genericMergeCommand("continue")
|
||||
} else if strings.Contains(result.Error(), "When you have resolved this problem") || strings.Contains(result.Error(), "fix conflicts") || strings.Contains(result.Error(), "Resolve all conflicts manually") {
|
||||
return gui.createConfirmationPanel(gui.g, gui.getFilesView(), true, gui.Tr.SLocalize("FoundConflictsTitle"), gui.Tr.SLocalize("FoundConflicts"),
|
||||
func(g *gocui.Gui, v *gocui.View) error {
|
||||
return nil
|
||||
}, func(g *gocui.Gui, v *gocui.View) error {
|
||||
return gui.genericMergeCommand("abort")
|
||||
},
|
||||
)
|
||||
} else {
|
||||
return gui.createErrorPanel(gui.g, result.Error())
|
||||
}
|
||||
}
|
||||
76
pkg/gui/recent_repos_panel.go
Normal file
76
pkg/gui/recent_repos_panel.go
Normal file
@@ -0,0 +1,76 @@
|
||||
package gui
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
)
|
||||
|
||||
type recentRepo struct {
|
||||
path string
|
||||
}
|
||||
|
||||
// GetDisplayStrings returns the path from a recent repo.
|
||||
func (r *recentRepo) GetDisplayStrings(isFocused bool) []string {
|
||||
yellow := color.New(color.FgMagenta)
|
||||
base := filepath.Base(r.path)
|
||||
path := yellow.Sprint(r.path)
|
||||
return []string{base, path}
|
||||
}
|
||||
|
||||
func (gui *Gui) handleCreateRecentReposMenu(g *gocui.Gui, v *gocui.View) error {
|
||||
recentRepoPaths := gui.Config.GetAppState().RecentRepos
|
||||
reposCount := utils.Min(len(recentRepoPaths), 20)
|
||||
// we won't show the current repo hence the -1
|
||||
recentRepos := make([]*recentRepo, reposCount-1)
|
||||
for i, path := range recentRepoPaths[1:reposCount] {
|
||||
recentRepos[i] = &recentRepo{path: path}
|
||||
}
|
||||
|
||||
handleMenuPress := func(index int) error {
|
||||
repo := recentRepos[index]
|
||||
if err := os.Chdir(repo.path); err != nil {
|
||||
return err
|
||||
}
|
||||
newGitCommand, err := commands.NewGitCommand(gui.Log, gui.OSCommand, gui.Tr, gui.Config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
gui.GitCommand = newGitCommand
|
||||
return gui.Errors.ErrSwitchRepo
|
||||
}
|
||||
|
||||
return gui.createMenu(gui.Tr.SLocalize("RecentRepos"), recentRepos, len(recentRepos), handleMenuPress)
|
||||
}
|
||||
|
||||
// updateRecentRepoList registers the fact that we opened lazygit in this repo,
|
||||
// so that we can open the same repo via the 'recent repos' menu
|
||||
func (gui *Gui) updateRecentRepoList() error {
|
||||
recentRepos := gui.Config.GetAppState().RecentRepos
|
||||
currentRepo, err := os.Getwd()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
known, recentRepos := newRecentReposList(recentRepos, currentRepo)
|
||||
gui.Config.SetIsNewRepo(known)
|
||||
gui.Config.GetAppState().RecentRepos = recentRepos
|
||||
return gui.Config.SaveAppState()
|
||||
}
|
||||
|
||||
// newRecentReposList returns a new repo list with a new entry but only when it doesn't exist yet
|
||||
func newRecentReposList(recentRepos []string, currentRepo string) (bool, []string) {
|
||||
isNew := true
|
||||
newRepos := []string{currentRepo}
|
||||
for _, repo := range recentRepos {
|
||||
if repo != currentRepo {
|
||||
newRepos = append(newRepos, repo)
|
||||
} else {
|
||||
isNew = false
|
||||
}
|
||||
}
|
||||
return isNew, newRepos
|
||||
}
|
||||
151
pkg/gui/staging_panel.go
Normal file
151
pkg/gui/staging_panel.go
Normal file
@@ -0,0 +1,151 @@
|
||||
package gui
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands"
|
||||
)
|
||||
|
||||
func (gui *Gui) refreshStagingPanel(forceSecondaryFocused bool, selectedLineIdx int) error {
|
||||
gui.State.SplitMainPanel = true
|
||||
|
||||
state := gui.State.Panels.LineByLine
|
||||
|
||||
file, err := gui.getSelectedFile(gui.g)
|
||||
if err != nil {
|
||||
if err != gui.Errors.ErrNoFiles {
|
||||
return err
|
||||
}
|
||||
return gui.handleStagingEscape(gui.g, nil)
|
||||
}
|
||||
|
||||
if !file.HasUnstagedChanges && !file.HasStagedChanges {
|
||||
return gui.handleStagingEscape(gui.g, nil)
|
||||
}
|
||||
|
||||
secondaryFocused := false
|
||||
if forceSecondaryFocused {
|
||||
secondaryFocused = true
|
||||
} else {
|
||||
if state != nil {
|
||||
secondaryFocused = state.SecondaryFocused
|
||||
}
|
||||
}
|
||||
|
||||
if (secondaryFocused && !file.HasStagedChanges) || (!secondaryFocused && !file.HasUnstagedChanges) {
|
||||
secondaryFocused = !secondaryFocused
|
||||
}
|
||||
|
||||
if secondaryFocused {
|
||||
gui.getMainView().Title = gui.Tr.SLocalize("StagedChanges")
|
||||
gui.getSecondaryView().Title = gui.Tr.SLocalize("UnstagedChanges")
|
||||
} else {
|
||||
gui.getMainView().Title = gui.Tr.SLocalize("UnstagedChanges")
|
||||
gui.getSecondaryView().Title = gui.Tr.SLocalize("StagedChanges")
|
||||
}
|
||||
|
||||
// note for custom diffs, we'll need to send a flag here saying not to use the custom diff
|
||||
diff := gui.GitCommand.Diff(file, true, secondaryFocused)
|
||||
secondaryDiff := gui.GitCommand.Diff(file, true, !secondaryFocused)
|
||||
|
||||
// if we have e.g. a deleted file with nothing else to the diff will have only
|
||||
// 4-5 lines in which case we'll swap panels
|
||||
if len(strings.Split(diff, "\n")) < 5 {
|
||||
if len(strings.Split(secondaryDiff, "\n")) < 5 {
|
||||
return gui.handleStagingEscape(gui.g, nil)
|
||||
}
|
||||
secondaryFocused = !secondaryFocused
|
||||
diff, secondaryDiff = secondaryDiff, diff
|
||||
}
|
||||
|
||||
empty, err := gui.refreshLineByLinePanel(diff, secondaryDiff, secondaryFocused, selectedLineIdx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if empty {
|
||||
return gui.handleStagingEscape(gui.g, nil)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) handleTogglePanelClick(g *gocui.Gui, v *gocui.View) error {
|
||||
state := gui.State.Panels.LineByLine
|
||||
|
||||
state.SecondaryFocused = !state.SecondaryFocused
|
||||
|
||||
return gui.refreshStagingPanel(false, v.SelectedLineIdx())
|
||||
}
|
||||
|
||||
func (gui *Gui) handleTogglePanel(g *gocui.Gui, v *gocui.View) error {
|
||||
state := gui.State.Panels.LineByLine
|
||||
|
||||
state.SecondaryFocused = !state.SecondaryFocused
|
||||
return gui.refreshStagingPanel(false, -1)
|
||||
}
|
||||
|
||||
func (gui *Gui) handleStagingEscape(g *gocui.Gui, v *gocui.View) error {
|
||||
gui.State.Panels.LineByLine = nil
|
||||
|
||||
return gui.switchFocus(gui.g, nil, gui.getFilesView())
|
||||
}
|
||||
|
||||
func (gui *Gui) handleStageSelection(g *gocui.Gui, v *gocui.View) error {
|
||||
return gui.applySelection(false)
|
||||
}
|
||||
|
||||
func (gui *Gui) handleResetSelection(g *gocui.Gui, v *gocui.View) error {
|
||||
return gui.applySelection(true)
|
||||
}
|
||||
|
||||
func (gui *Gui) applySelection(reverse bool) error {
|
||||
state := gui.State.Panels.LineByLine
|
||||
|
||||
if !reverse && state.SecondaryFocused {
|
||||
return gui.createErrorPanel(gui.g, gui.Tr.SLocalize("CantStageStaged"))
|
||||
}
|
||||
|
||||
file, err := gui.getSelectedFile(gui.g)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
patch := commands.ModifiedPatchForRange(gui.Log, file.Name, state.Diff, state.FirstLineIdx, state.LastLineIdx, reverse, false)
|
||||
|
||||
if patch == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
// apply the patch then refresh this panel
|
||||
// create a new temp file with the patch, then call git apply with that patch
|
||||
applyFlags := []string{}
|
||||
if !reverse || state.SecondaryFocused {
|
||||
applyFlags = append(applyFlags, "cached")
|
||||
}
|
||||
err = gui.GitCommand.ApplyPatch(patch, applyFlags...)
|
||||
if err != nil {
|
||||
return gui.createErrorPanel(gui.g, err.Error())
|
||||
}
|
||||
|
||||
if state.SelectMode == RANGE {
|
||||
state.SelectMode = LINE
|
||||
}
|
||||
|
||||
if err := gui.refreshFiles(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := gui.refreshStagingPanel(false, -1); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) handleMouseDownSecondaryWhileStaging(g *gocui.Gui, v *gocui.View) error {
|
||||
state := gui.State.Panels.LineByLine
|
||||
|
||||
state.SecondaryFocused = !state.SecondaryFocused
|
||||
|
||||
return gui.refreshStagingPanel(false, -1)
|
||||
}
|
||||
@@ -5,53 +5,102 @@ import (
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
)
|
||||
|
||||
// list panel functions
|
||||
|
||||
func (gui *Gui) getSelectedStashEntry(v *gocui.View) *commands.StashEntry {
|
||||
selectedLine := gui.State.Panels.Stash.SelectedLine
|
||||
if selectedLine == -1 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return gui.State.StashEntries[selectedLine]
|
||||
}
|
||||
|
||||
func (gui *Gui) handleStashEntrySelect(g *gocui.Gui, v *gocui.View) error {
|
||||
if gui.popupPanelFocused() {
|
||||
return nil
|
||||
}
|
||||
|
||||
gui.State.SplitMainPanel = false
|
||||
|
||||
if _, err := gui.g.SetCurrentView(v.Name()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
gui.getMainView().Title = "Stash"
|
||||
|
||||
stashEntry := gui.getSelectedStashEntry(v)
|
||||
if stashEntry == nil {
|
||||
return gui.renderString(g, "main", gui.Tr.SLocalize("NoStashEntries"))
|
||||
}
|
||||
if err := gui.focusPoint(0, gui.State.Panels.Stash.SelectedLine, len(gui.State.StashEntries), v); err != nil {
|
||||
return err
|
||||
}
|
||||
go func() {
|
||||
// doing this asynchronously cos it can take time
|
||||
diff, _ := gui.GitCommand.GetStashEntryDiff(stashEntry.Index)
|
||||
_ = gui.renderString(g, "main", diff)
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) refreshStashEntries(g *gocui.Gui) error {
|
||||
g.Update(func(g *gocui.Gui) error {
|
||||
v, err := g.View("stash")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
gui.State.StashEntries = gui.GitCommand.GetStashEntries()
|
||||
v.Clear()
|
||||
for _, stashEntry := range gui.State.StashEntries {
|
||||
fmt.Fprintln(v, stashEntry.DisplayString)
|
||||
|
||||
gui.refreshSelectedLine(&gui.State.Panels.Stash.SelectedLine, len(gui.State.StashEntries))
|
||||
|
||||
isFocused := gui.g.CurrentView().Name() == "stash"
|
||||
list, err := utils.RenderList(gui.State.StashEntries, isFocused)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return gui.resetOrigin(v)
|
||||
|
||||
v := gui.getStashView()
|
||||
v.Clear()
|
||||
fmt.Fprint(v, list)
|
||||
|
||||
if err := gui.resetOrigin(v); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) getSelectedStashEntry(v *gocui.View) *commands.StashEntry {
|
||||
if len(gui.State.StashEntries) == 0 {
|
||||
func (gui *Gui) handleStashNextLine(g *gocui.Gui, v *gocui.View) error {
|
||||
if gui.popupPanelFocused() {
|
||||
return nil
|
||||
}
|
||||
stashView, _ := gui.g.View("stash")
|
||||
lineNumber := gui.getItemPosition(stashView)
|
||||
return &gui.State.StashEntries[lineNumber]
|
||||
}
|
||||
|
||||
func (gui *Gui) renderStashOptions(g *gocui.Gui) error {
|
||||
return gui.renderGlobalOptions(g)
|
||||
}
|
||||
panelState := gui.State.Panels.Stash
|
||||
gui.changeSelectedLine(&panelState.SelectedLine, len(gui.State.StashEntries), false)
|
||||
|
||||
func (gui *Gui) handleStashEntrySelect(g *gocui.Gui, v *gocui.View) error {
|
||||
if err := gui.renderStashOptions(g); err != nil {
|
||||
if err := gui.resetOrigin(gui.getMainView()); err != nil {
|
||||
return err
|
||||
}
|
||||
go func() {
|
||||
stashEntry := gui.getSelectedStashEntry(v)
|
||||
if stashEntry == nil {
|
||||
gui.renderString(g, "main", gui.Tr.SLocalize("NoStashEntries"))
|
||||
return
|
||||
}
|
||||
diff, _ := gui.GitCommand.GetStashEntryDiff(stashEntry.Index)
|
||||
gui.renderString(g, "main", diff)
|
||||
}()
|
||||
return nil
|
||||
return gui.handleStashEntrySelect(gui.g, v)
|
||||
}
|
||||
|
||||
func (gui *Gui) handleStashPrevLine(g *gocui.Gui, v *gocui.View) error {
|
||||
if gui.popupPanelFocused() {
|
||||
return nil
|
||||
}
|
||||
|
||||
panelState := gui.State.Panels.Stash
|
||||
gui.changeSelectedLine(&panelState.SelectedLine, len(gui.State.StashEntries), true)
|
||||
|
||||
if err := gui.resetOrigin(gui.getMainView()); err != nil {
|
||||
return err
|
||||
}
|
||||
return gui.handleStashEntrySelect(gui.g, v)
|
||||
}
|
||||
|
||||
// specific functions
|
||||
|
||||
func (gui *Gui) handleStashApply(g *gocui.Gui, v *gocui.View) error {
|
||||
return gui.stashDo(g, v, "apply")
|
||||
}
|
||||
@@ -63,7 +112,7 @@ func (gui *Gui) handleStashPop(g *gocui.Gui, v *gocui.View) error {
|
||||
func (gui *Gui) handleStashDrop(g *gocui.Gui, v *gocui.View) error {
|
||||
title := gui.Tr.SLocalize("StashDrop")
|
||||
message := gui.Tr.SLocalize("SureDropStashEntry")
|
||||
return gui.createConfirmationPanel(g, v, title, message, func(g *gocui.Gui, v *gocui.View) error {
|
||||
return gui.createConfirmationPanel(g, v, true, title, message, func(g *gocui.Gui, v *gocui.View) error {
|
||||
return gui.stashDo(g, v, "drop")
|
||||
}, nil)
|
||||
}
|
||||
@@ -83,19 +132,18 @@ func (gui *Gui) stashDo(g *gocui.Gui, v *gocui.View, method string) error {
|
||||
gui.createErrorPanel(g, err.Error())
|
||||
}
|
||||
gui.refreshStashEntries(g)
|
||||
return gui.refreshFiles(g)
|
||||
return gui.refreshFiles()
|
||||
}
|
||||
|
||||
func (gui *Gui) handleStashSave(g *gocui.Gui, filesView *gocui.View) error {
|
||||
func (gui *Gui) handleStashSave(stashFunc func(message string) error) error {
|
||||
if len(gui.trackedFiles()) == 0 && len(gui.stagedFiles()) == 0 {
|
||||
return gui.createErrorPanel(g, gui.Tr.SLocalize("NoTrackedStagedFilesStash"))
|
||||
return gui.createErrorPanel(gui.g, gui.Tr.SLocalize("NoTrackedStagedFilesStash"))
|
||||
}
|
||||
gui.createPromptPanel(g, filesView, gui.Tr.SLocalize("StashChanges"), func(g *gocui.Gui, v *gocui.View) error {
|
||||
if err := gui.GitCommand.StashSave(gui.trimmedContent(v)); err != nil {
|
||||
return gui.createPromptPanel(gui.g, gui.getFilesView(), gui.Tr.SLocalize("StashChanges"), func(g *gocui.Gui, v *gocui.View) error {
|
||||
if err := stashFunc(gui.trimmedContent(v)); err != nil {
|
||||
gui.createErrorPanel(g, err.Error())
|
||||
}
|
||||
gui.refreshStashEntries(g)
|
||||
return gui.refreshFiles(g)
|
||||
return gui.refreshFiles()
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package gui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/jesseduffield/gocui"
|
||||
@@ -9,6 +10,8 @@ import (
|
||||
)
|
||||
|
||||
func (gui *Gui) refreshStatus(g *gocui.Gui) error {
|
||||
state := gui.State.Panels.Status
|
||||
|
||||
v, err := g.View("status")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@@ -18,52 +21,96 @@ func (gui *Gui) refreshStatus(g *gocui.Gui) error {
|
||||
// contents end up cleared
|
||||
g.Update(func(*gocui.Gui) error {
|
||||
v.Clear()
|
||||
pushables, pullables := gui.GitCommand.UpstreamDifferenceCount()
|
||||
fmt.Fprint(v, "↑"+pushables+"↓"+pullables)
|
||||
branches := gui.State.Branches
|
||||
if err := gui.updateHasMergeConflictStatus(); err != nil {
|
||||
state.pushables, state.pullables = gui.GitCommand.GetCurrentBranchUpstreamDifferenceCount()
|
||||
if err := gui.updateWorkTreeState(); err != nil {
|
||||
return err
|
||||
}
|
||||
if gui.State.HasMergeConflicts {
|
||||
fmt.Fprint(v, utils.ColoredString(" (merging)", color.FgYellow))
|
||||
status := fmt.Sprintf("↑%s↓%s", state.pushables, state.pullables)
|
||||
branches := gui.State.Branches
|
||||
|
||||
if gui.State.WorkingTreeState != "normal" {
|
||||
status += utils.ColoredString(fmt.Sprintf(" (%s)", gui.State.WorkingTreeState), color.FgYellow)
|
||||
}
|
||||
|
||||
if len(branches) == 0 {
|
||||
return nil
|
||||
if len(branches) > 0 {
|
||||
branch := branches[0]
|
||||
name := utils.ColoredString(branch.Name, branch.GetColor())
|
||||
repoName := utils.GetCurrentRepoName()
|
||||
status += fmt.Sprintf(" %s → %s", repoName, name)
|
||||
}
|
||||
branch := branches[0]
|
||||
name := utils.ColoredString(branch.Name, branch.GetColor())
|
||||
repo := utils.GetCurrentRepoName()
|
||||
fmt.Fprint(v, " "+repo+" → "+name)
|
||||
|
||||
fmt.Fprint(v, status)
|
||||
return nil
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) renderStatusOptions(g *gocui.Gui) error {
|
||||
return gui.renderGlobalOptions(g)
|
||||
func runeCount(str string) int {
|
||||
return len([]rune(str))
|
||||
}
|
||||
|
||||
func cursorInSubstring(cx int, prefix string, substring string) bool {
|
||||
return cx >= runeCount(prefix) && cx < runeCount(prefix+substring)
|
||||
}
|
||||
|
||||
func (gui *Gui) handleCheckForUpdate(g *gocui.Gui, v *gocui.View) error {
|
||||
gui.Updater.CheckForNewUpdate(gui.onUserUpdateCheckFinish, true)
|
||||
return gui.createMessagePanel(gui.g, v, "", gui.Tr.SLocalize("CheckingForUpdates"))
|
||||
return gui.createLoaderPanel(gui.g, v, gui.Tr.SLocalize("CheckingForUpdates"))
|
||||
}
|
||||
|
||||
func (gui *Gui) handleStatusClick(g *gocui.Gui, v *gocui.View) error {
|
||||
state := gui.State.Panels.Status
|
||||
|
||||
cx, _ := v.Cursor()
|
||||
upstreamStatus := fmt.Sprintf("↑%s↓%s", state.pushables, state.pullables)
|
||||
repoName := utils.GetCurrentRepoName()
|
||||
gui.Log.Warn(gui.State.WorkingTreeState)
|
||||
switch gui.State.WorkingTreeState {
|
||||
case "rebasing", "merging":
|
||||
workingTreeStatus := fmt.Sprintf("(%s)", gui.State.WorkingTreeState)
|
||||
if cursorInSubstring(cx, upstreamStatus+" ", workingTreeStatus) {
|
||||
return gui.handleCreateRebaseOptionsMenu(gui.g, v)
|
||||
}
|
||||
if cursorInSubstring(cx, upstreamStatus+" "+workingTreeStatus+" ", repoName) {
|
||||
return gui.handleCreateRecentReposMenu(gui.g, v)
|
||||
}
|
||||
default:
|
||||
if cursorInSubstring(cx, upstreamStatus+" ", repoName) {
|
||||
return gui.handleCreateRecentReposMenu(gui.g, v)
|
||||
}
|
||||
}
|
||||
|
||||
return gui.handleStatusSelect(gui.g, v)
|
||||
}
|
||||
|
||||
func (gui *Gui) handleStatusSelect(g *gocui.Gui, v *gocui.View) error {
|
||||
dashboardString := fmt.Sprintf(
|
||||
"%s\n\n%s\n\n%s\n\n%s\n\n%s",
|
||||
lazygitTitle(),
|
||||
"Keybindings: https://github.com/jesseduffield/lazygit/blob/master/docs/Keybindings.md",
|
||||
"Config Options: https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md",
|
||||
"Tutorial: https://www.youtube.com/watch?v=VDXvbHZYeKY",
|
||||
"Raise an Issue: https://github.com/jesseduffield/lazygit/issues",
|
||||
)
|
||||
if gui.popupPanelFocused() {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := gui.renderString(g, "main", dashboardString); err != nil {
|
||||
gui.State.SplitMainPanel = false
|
||||
|
||||
if _, err := gui.g.SetCurrentView(v.Name()); err != nil {
|
||||
return err
|
||||
}
|
||||
return gui.renderStatusOptions(g)
|
||||
|
||||
gui.getMainView().Title = ""
|
||||
|
||||
magenta := color.New(color.FgMagenta)
|
||||
|
||||
dashboardString := strings.Join(
|
||||
[]string{
|
||||
lazygitTitle(),
|
||||
"Copyright (c) 2018 Jesse Duffield",
|
||||
"Keybindings: https://github.com/jesseduffield/lazygit/blob/master/docs/keybindings",
|
||||
"Config Options: https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md",
|
||||
"Tutorial: https://youtu.be/VDXvbHZYeKY",
|
||||
"Raise an Issue: https://github.com/jesseduffield/lazygit/issues",
|
||||
magenta.Sprint("Become a sponsor (github is matching all donations for 12 months): https://github.com/sponsors/jesseduffield"), // caffeine ain't free
|
||||
}, "\n\n")
|
||||
|
||||
return gui.renderString(g, "main", dashboardString)
|
||||
}
|
||||
|
||||
func (gui *Gui) handleOpenConfig(g *gocui.Gui, v *gocui.View) error {
|
||||
@@ -86,3 +133,24 @@ func lazygitTitle() string {
|
||||
__/ | __/ |
|
||||
|___/ |___/ `
|
||||
}
|
||||
|
||||
func (gui *Gui) updateWorkTreeState() error {
|
||||
merging, err := gui.GitCommand.IsInMergeState()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if merging {
|
||||
gui.State.WorkingTreeState = "merging"
|
||||
return nil
|
||||
}
|
||||
rebaseMode, err := gui.GitCommand.RebaseMode()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if rebaseMode != "" {
|
||||
gui.State.WorkingTreeState = "rebasing"
|
||||
return nil
|
||||
}
|
||||
gui.State.WorkingTreeState = "normal"
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
package gui
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/gocui"
|
||||
)
|
||||
|
||||
// GetAttribute gets the gocui color attribute from the string
|
||||
func (gui *Gui) GetAttribute(key string) gocui.Attribute {
|
||||
colorMap := map[string]gocui.Attribute{
|
||||
"default": gocui.ColorDefault,
|
||||
"black": gocui.ColorBlack,
|
||||
"red": gocui.ColorRed,
|
||||
"green": gocui.ColorGreen,
|
||||
"yellow": gocui.ColorYellow,
|
||||
"blue": gocui.ColorBlue,
|
||||
"magenta": gocui.ColorMagenta,
|
||||
"cyan": gocui.ColorCyan,
|
||||
"white": gocui.ColorWhite,
|
||||
"bold": gocui.AttrBold,
|
||||
"reverse": gocui.AttrReverse,
|
||||
"underline": gocui.AttrUnderline,
|
||||
}
|
||||
value, present := colorMap[key]
|
||||
if present {
|
||||
return value
|
||||
}
|
||||
return gocui.ColorWhite
|
||||
}
|
||||
|
||||
// GetColor bitwise OR's a list of attributes obtained via the given keys
|
||||
func (gui *Gui) GetColor(keys []string) gocui.Attribute {
|
||||
var attribute gocui.Attribute
|
||||
for _, key := range keys {
|
||||
attribute = attribute | gui.GetAttribute(key)
|
||||
}
|
||||
return attribute
|
||||
}
|
||||
|
||||
// GetOptionsPanelTextColor gets the color of the options panel text
|
||||
func (gui *Gui) GetOptionsPanelTextColor() (gocui.Attribute, error) {
|
||||
userConfig := gui.Config.GetUserConfig()
|
||||
optionsColor := userConfig.GetStringSlice("gui.theme.optionsTextColor")
|
||||
return gui.GetColor(optionsColor), nil
|
||||
}
|
||||
|
||||
// SetColorScheme sets the color scheme for the app based on the user config
|
||||
func (gui *Gui) SetColorScheme() error {
|
||||
userConfig := gui.Config.GetUserConfig()
|
||||
activeBorderColor := userConfig.GetStringSlice("gui.theme.activeBorderColor")
|
||||
inactiveBorderColor := userConfig.GetStringSlice("gui.theme.inactiveBorderColor")
|
||||
gui.g.FgColor = gui.GetColor(inactiveBorderColor)
|
||||
gui.g.SelFgColor = gui.GetColor(activeBorderColor)
|
||||
return nil
|
||||
}
|
||||
@@ -6,7 +6,7 @@ func (gui *Gui) showUpdatePrompt(newVersion string) error {
|
||||
title := "New version available!"
|
||||
message := "Download latest version? (enter/esc)"
|
||||
currentView := gui.g.CurrentView()
|
||||
return gui.createConfirmationPanel(gui.g, currentView, title, message, func(g *gocui.Gui, v *gocui.View) error {
|
||||
return gui.createConfirmationPanel(gui.g, currentView, true, title, message, func(g *gocui.Gui, v *gocui.View) error {
|
||||
gui.startUpdating(newVersion)
|
||||
return nil
|
||||
}, nil)
|
||||
@@ -59,7 +59,7 @@ func (gui *Gui) onUpdateFinish(err error) error {
|
||||
func (gui *Gui) createUpdateQuitConfirmation(g *gocui.Gui, v *gocui.View) error {
|
||||
title := "Currently Updating"
|
||||
message := "An update is in progress. Are you sure you want to quit?"
|
||||
return gui.createConfirmationPanel(gui.g, v, title, message, func(g *gocui.Gui, v *gocui.View) error {
|
||||
return gui.createConfirmationPanel(gui.g, v, true, title, message, func(g *gocui.Gui, v *gocui.View) error {
|
||||
return gocui.ErrQuit
|
||||
}, nil)
|
||||
}
|
||||
|
||||
@@ -13,10 +13,17 @@ import (
|
||||
var cyclableViews = []string{"status", "files", "branches", "commits", "stash"}
|
||||
|
||||
func (gui *Gui) refreshSidePanels(g *gocui.Gui) error {
|
||||
gui.refreshBranches(g)
|
||||
gui.refreshFiles(g)
|
||||
gui.refreshCommits(g)
|
||||
return nil
|
||||
if err := gui.refreshBranches(g); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := gui.refreshFiles(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := gui.refreshCommits(g); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return gui.refreshStashEntries(g)
|
||||
}
|
||||
|
||||
func (gui *Gui) nextView(g *gocui.Gui, v *gocui.View) error {
|
||||
@@ -24,8 +31,13 @@ func (gui *Gui) nextView(g *gocui.Gui, v *gocui.View) error {
|
||||
if v == nil || v.Name() == cyclableViews[len(cyclableViews)-1] {
|
||||
focusedViewName = cyclableViews[0]
|
||||
} else {
|
||||
// if we're in the commitFiles view we'll act like we're in the commits view
|
||||
viewName := v.Name()
|
||||
if viewName == "commitFiles" {
|
||||
viewName = "commits"
|
||||
}
|
||||
for i := range cyclableViews {
|
||||
if v.Name() == cyclableViews[i] {
|
||||
if viewName == cyclableViews[i] {
|
||||
focusedViewName = cyclableViews[i+1]
|
||||
break
|
||||
}
|
||||
@@ -33,7 +45,7 @@ func (gui *Gui) nextView(g *gocui.Gui, v *gocui.View) error {
|
||||
message := gui.Tr.TemplateLocalize(
|
||||
"IssntListOfViews",
|
||||
Teml{
|
||||
"name": v.Name(),
|
||||
"name": viewName,
|
||||
},
|
||||
)
|
||||
gui.Log.Info(message)
|
||||
@@ -53,8 +65,13 @@ func (gui *Gui) previousView(g *gocui.Gui, v *gocui.View) error {
|
||||
if v == nil || v.Name() == cyclableViews[0] {
|
||||
focusedViewName = cyclableViews[len(cyclableViews)-1]
|
||||
} else {
|
||||
// if we're in the commitFiles view we'll act like we're in the commits view
|
||||
viewName := v.Name()
|
||||
if viewName == "commitFiles" {
|
||||
viewName = "commits"
|
||||
}
|
||||
for i := range cyclableViews {
|
||||
if v.Name() == cyclableViews[i] {
|
||||
if viewName == cyclableViews[i] {
|
||||
focusedViewName = cyclableViews[i-1] // TODO: make this work properly
|
||||
break
|
||||
}
|
||||
@@ -62,7 +79,7 @@ func (gui *Gui) previousView(g *gocui.Gui, v *gocui.View) error {
|
||||
message := gui.Tr.TemplateLocalize(
|
||||
"IssntListOfViews",
|
||||
Teml{
|
||||
"name": v.Name(),
|
||||
"name": viewName,
|
||||
},
|
||||
)
|
||||
gui.Log.Info(message)
|
||||
@@ -78,31 +95,33 @@ func (gui *Gui) previousView(g *gocui.Gui, v *gocui.View) error {
|
||||
}
|
||||
|
||||
func (gui *Gui) newLineFocused(g *gocui.Gui, v *gocui.View) error {
|
||||
mainView, _ := g.View("main")
|
||||
mainView.SetOrigin(0, 0)
|
||||
|
||||
switch v.Name() {
|
||||
case "menu":
|
||||
return gui.handleMenuSelect(g, v)
|
||||
case "status":
|
||||
return gui.handleStatusSelect(g, v)
|
||||
case "files":
|
||||
return gui.handleFileSelect(g, v)
|
||||
return gui.handleFileSelect(g, v, false)
|
||||
case "branches":
|
||||
return gui.handleBranchSelect(g, v)
|
||||
case "commits":
|
||||
return gui.handleCommitSelect(g, v)
|
||||
case "commitFiles":
|
||||
return gui.handleCommitFileSelect(g, v)
|
||||
case "stash":
|
||||
return gui.handleStashEntrySelect(g, v)
|
||||
case "confirmation":
|
||||
return nil
|
||||
case "commitMessage":
|
||||
return gui.handleCommitFocused(g, v)
|
||||
case "credentials":
|
||||
return gui.handleCredentialsViewFocused(g, v)
|
||||
case "main":
|
||||
// TODO: pull this out into a 'view focused' function
|
||||
gui.refreshMergePanel(g)
|
||||
if gui.State.Context == "merging" {
|
||||
return gui.refreshMergePanel()
|
||||
}
|
||||
v.Highlight = false
|
||||
return nil
|
||||
case "commits":
|
||||
return gui.handleCommitSelect(g, v)
|
||||
case "stash":
|
||||
return gui.handleStashEntrySelect(g, v)
|
||||
default:
|
||||
panic(gui.Tr.SLocalize("NoViewMachingNewLineFocusedSwitchStatement"))
|
||||
}
|
||||
@@ -120,22 +139,42 @@ func (gui *Gui) returnFocus(g *gocui.Gui, v *gocui.View) error {
|
||||
return gui.switchFocus(g, v, previousView)
|
||||
}
|
||||
|
||||
func (gui *Gui) goToSideView(sideViewName string) func(g *gocui.Gui, v *gocui.View) error {
|
||||
return func(g *gocui.Gui, v *gocui.View) error {
|
||||
view, err := g.View(sideViewName)
|
||||
if err != nil {
|
||||
gui.Log.Error(err)
|
||||
return nil
|
||||
}
|
||||
err = gui.closePopupPanels()
|
||||
if err != nil {
|
||||
gui.Log.Error(err)
|
||||
return nil
|
||||
}
|
||||
return gui.switchFocus(g, nil, view)
|
||||
}
|
||||
}
|
||||
|
||||
func (gui *Gui) closePopupPanels() error {
|
||||
gui.onNewPopupPanel()
|
||||
err := gui.closeConfirmationPrompt(gui.g, true)
|
||||
if err != nil {
|
||||
gui.Log.Error(err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// pass in oldView = nil if you don't want to be able to return to your old view
|
||||
// TODO: move some of this logic into our onFocusLost and onFocus hooks
|
||||
func (gui *Gui) switchFocus(g *gocui.Gui, oldView, newView *gocui.View) error {
|
||||
// we assume we'll never want to return focus to a confirmation panel i.e.
|
||||
// we should never stack confirmation panels
|
||||
if oldView != nil && oldView.Name() != "confirmation" {
|
||||
oldView.Highlight = false
|
||||
message := gui.Tr.TemplateLocalize(
|
||||
"settingPreviewsViewTo",
|
||||
Teml{
|
||||
"oldViewName": oldView.Name(),
|
||||
},
|
||||
)
|
||||
gui.Log.Info(message)
|
||||
// we assume we'll never want to return focus to a popup panel i.e.
|
||||
// we should never stack popup panels
|
||||
if oldView != nil && !gui.isPopupPanel(oldView.Name()) {
|
||||
gui.State.PreviousView = oldView.Name()
|
||||
}
|
||||
newView.Highlight = true
|
||||
|
||||
gui.Log.Info("setting highlight to true for view" + newView.Name())
|
||||
message := gui.Tr.TemplateLocalize(
|
||||
"newFocusedViewIs",
|
||||
Teml{
|
||||
@@ -146,90 +185,77 @@ func (gui *Gui) switchFocus(g *gocui.Gui, oldView, newView *gocui.View) error {
|
||||
if _, err := g.SetCurrentView(newView.Name()); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := g.SetViewOnTop(newView.Name()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
g.Cursor = newView.Editable
|
||||
|
||||
if err := gui.renderPanelOptions(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return gui.newLineFocused(g, newView)
|
||||
}
|
||||
|
||||
func (gui *Gui) getItemPosition(v *gocui.View) int {
|
||||
gui.correctCursor(v)
|
||||
_, cy := v.Cursor()
|
||||
_, oy := v.Origin()
|
||||
return oy + cy
|
||||
}
|
||||
|
||||
func (gui *Gui) cursorUp(g *gocui.Gui, v *gocui.View) error {
|
||||
// swallowing cursor movements in main
|
||||
// TODO: pull this out
|
||||
if v == nil || v.Name() == "main" {
|
||||
return nil
|
||||
}
|
||||
|
||||
ox, oy := v.Origin()
|
||||
cx, cy := v.Cursor()
|
||||
if err := v.SetCursor(cx, cy-1); err != nil && oy > 0 {
|
||||
if err := v.SetOrigin(ox, oy-1); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
gui.newLineFocused(g, v)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) cursorDown(g *gocui.Gui, v *gocui.View) error {
|
||||
// swallowing cursor movements in main
|
||||
// TODO: pull this out
|
||||
if v == nil || v.Name() == "main" {
|
||||
return nil
|
||||
}
|
||||
cx, cy := v.Cursor()
|
||||
ox, oy := v.Origin()
|
||||
if cy+oy >= len(v.BufferLines())-2 {
|
||||
return nil
|
||||
}
|
||||
if err := v.SetCursor(cx, cy+1); err != nil {
|
||||
if err := v.SetOrigin(ox, oy+1); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
gui.newLineFocused(g, v)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) resetOrigin(v *gocui.View) error {
|
||||
if err := v.SetCursor(0, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
_ = v.SetCursor(0, 0)
|
||||
return v.SetOrigin(0, 0)
|
||||
}
|
||||
|
||||
// if the cursor down past the last item, move it to the last line
|
||||
func (gui *Gui) correctCursor(v *gocui.View) error {
|
||||
cx, cy := v.Cursor()
|
||||
_, oy := v.Origin()
|
||||
lineCount := len(v.BufferLines()) - 2
|
||||
if cy >= lineCount-oy {
|
||||
return v.SetCursor(cx, lineCount-oy)
|
||||
func (gui *Gui) focusPoint(cx int, cy int, lineCount int, v *gocui.View) error {
|
||||
if cy < 0 || cy > lineCount {
|
||||
return nil
|
||||
}
|
||||
ox, oy := v.Origin()
|
||||
_, height := v.Size()
|
||||
|
||||
ly := height - 1
|
||||
if ly == -1 {
|
||||
ly = 0
|
||||
}
|
||||
|
||||
// if line is above origin, move origin and set cursor to zero
|
||||
// if line is below origin + height, move origin and set cursor to max
|
||||
// otherwise set cursor to value - origin
|
||||
if ly > lineCount {
|
||||
_ = v.SetCursor(cx, cy)
|
||||
_ = v.SetOrigin(ox, 0)
|
||||
} else if cy < oy {
|
||||
_ = v.SetCursor(cx, 0)
|
||||
_ = v.SetOrigin(ox, cy)
|
||||
} else if cy > oy+ly {
|
||||
_ = v.SetCursor(cx, ly)
|
||||
_ = v.SetOrigin(ox, cy-ly)
|
||||
} else {
|
||||
_ = v.SetCursor(cx, cy-oy)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) cleanString(s string) string {
|
||||
output := string(bom.Clean([]byte(s)))
|
||||
return utils.NormalizeLinefeeds(output)
|
||||
}
|
||||
|
||||
func (gui *Gui) setViewContent(g *gocui.Gui, v *gocui.View, s string) error {
|
||||
v.Clear()
|
||||
fmt.Fprint(v, gui.cleanString(s))
|
||||
return nil
|
||||
}
|
||||
|
||||
// renderString resets the origin of a view and sets its content
|
||||
func (gui *Gui) renderString(g *gocui.Gui, viewName, s string) error {
|
||||
g.Update(func(*gocui.Gui) error {
|
||||
v, err := g.View(viewName)
|
||||
// just in case the view disappeared as this function was called, we'll
|
||||
// silently return if it's not found
|
||||
if err != nil {
|
||||
return nil
|
||||
return nil // return gracefully if view has been deleted
|
||||
}
|
||||
v.Clear()
|
||||
output := string(bom.Clean([]byte(s)))
|
||||
output = utils.NormalizeLinefeeds(output)
|
||||
fmt.Fprint(v, output)
|
||||
v.Wrap = true
|
||||
return nil
|
||||
if err := v.SetOrigin(0, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
return gui.setViewContent(gui.g, v, s)
|
||||
})
|
||||
return nil
|
||||
}
|
||||
@@ -243,29 +269,49 @@ func (gui *Gui) optionsMapToString(optionsMap map[string]string) string {
|
||||
return strings.Join(optionsArray, ", ")
|
||||
}
|
||||
|
||||
func (gui *Gui) renderOptionsMap(g *gocui.Gui, optionsMap map[string]string) error {
|
||||
return gui.renderString(g, "options", gui.optionsMapToString(optionsMap))
|
||||
func (gui *Gui) renderOptionsMap(optionsMap map[string]string) error {
|
||||
return gui.renderString(gui.g, "options", gui.optionsMapToString(optionsMap))
|
||||
}
|
||||
|
||||
// TODO: refactor properly
|
||||
// i'm so sorry but had to add this getBranchesView
|
||||
func (gui *Gui) getFilesView(g *gocui.Gui) *gocui.View {
|
||||
v, _ := g.View("files")
|
||||
func (gui *Gui) getFilesView() *gocui.View {
|
||||
v, _ := gui.g.View("files")
|
||||
return v
|
||||
}
|
||||
|
||||
func (gui *Gui) getCommitsView(g *gocui.Gui) *gocui.View {
|
||||
v, _ := g.View("commits")
|
||||
func (gui *Gui) getCommitsView() *gocui.View {
|
||||
v, _ := gui.g.View("commits")
|
||||
return v
|
||||
}
|
||||
|
||||
func (gui *Gui) getCommitMessageView(g *gocui.Gui) *gocui.View {
|
||||
v, _ := g.View("commitMessage")
|
||||
func (gui *Gui) getCommitMessageView() *gocui.View {
|
||||
v, _ := gui.g.View("commitMessage")
|
||||
return v
|
||||
}
|
||||
|
||||
func (gui *Gui) getBranchesView(g *gocui.Gui) *gocui.View {
|
||||
v, _ := g.View("branches")
|
||||
func (gui *Gui) getBranchesView() *gocui.View {
|
||||
v, _ := gui.g.View("branches")
|
||||
return v
|
||||
}
|
||||
|
||||
func (gui *Gui) getMainView() *gocui.View {
|
||||
v, _ := gui.g.View("main")
|
||||
return v
|
||||
}
|
||||
|
||||
func (gui *Gui) getSecondaryView() *gocui.View {
|
||||
v, _ := gui.g.View("secondary")
|
||||
return v
|
||||
}
|
||||
|
||||
func (gui *Gui) getStashView() *gocui.View {
|
||||
v, _ := gui.g.View("stash")
|
||||
return v
|
||||
}
|
||||
|
||||
func (gui *Gui) getCommitFilesView() *gocui.View {
|
||||
v, _ := gui.g.View("commitFiles")
|
||||
return v
|
||||
}
|
||||
|
||||
@@ -273,14 +319,14 @@ func (gui *Gui) trimmedContent(v *gocui.View) string {
|
||||
return strings.TrimSpace(v.Buffer())
|
||||
}
|
||||
|
||||
func (gui *Gui) currentViewName(g *gocui.Gui) string {
|
||||
currentView := g.CurrentView()
|
||||
func (gui *Gui) currentViewName() string {
|
||||
currentView := gui.g.CurrentView()
|
||||
return currentView.Name()
|
||||
}
|
||||
|
||||
func (gui *Gui) resizeCurrentPopupPanel(g *gocui.Gui) error {
|
||||
v := g.CurrentView()
|
||||
if v.Name() == "commitMessage" || v.Name() == "confirmation" {
|
||||
if gui.isPopupPanel(v.Name()) {
|
||||
return gui.resizePopupPanel(g, v)
|
||||
}
|
||||
return nil
|
||||
@@ -290,7 +336,7 @@ func (gui *Gui) resizePopupPanel(g *gocui.Gui, v *gocui.View) error {
|
||||
// If the confirmation panel is already displayed, just resize the width,
|
||||
// otherwise continue
|
||||
content := v.Buffer()
|
||||
x0, y0, x1, y1 := gui.getConfirmationPanelDimensions(g, content)
|
||||
x0, y0, x1, y1 := gui.getConfirmationPanelDimensions(g, v.Wrap, content)
|
||||
vx0, vy0, vx1, vy1 := v.Dimensions()
|
||||
if vx0 == x0 && vy0 == y0 && vx1 == x1 && vy1 == y1 {
|
||||
return nil
|
||||
@@ -299,3 +345,107 @@ func (gui *Gui) resizePopupPanel(g *gocui.Gui, v *gocui.View) error {
|
||||
_, err := g.SetView(v.Name(), x0, y0, x1, y1, 0)
|
||||
return err
|
||||
}
|
||||
|
||||
// generalFocusLine takes a lineNumber to focus, and a bottomLine to ensure we can see
|
||||
func (gui *Gui) generalFocusLine(lineNumber int, bottomLine int, v *gocui.View) error {
|
||||
_, height := v.Size()
|
||||
overScroll := bottomLine - height + 1
|
||||
if overScroll < 0 {
|
||||
overScroll = 0
|
||||
}
|
||||
if err := v.SetOrigin(0, overScroll); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := v.SetCursor(0, lineNumber-overScroll); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) changeSelectedLine(line *int, total int, up bool) {
|
||||
if up {
|
||||
if *line == -1 || *line == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
*line--
|
||||
} else {
|
||||
if *line == -1 || *line == total-1 {
|
||||
return
|
||||
}
|
||||
|
||||
*line++
|
||||
}
|
||||
}
|
||||
|
||||
func (gui *Gui) refreshSelectedLine(line *int, total int) {
|
||||
if *line == -1 && total > 0 {
|
||||
*line = 0
|
||||
} else if total-1 < *line {
|
||||
*line = total - 1
|
||||
}
|
||||
}
|
||||
|
||||
func (gui *Gui) renderListPanel(v *gocui.View, items interface{}) error {
|
||||
gui.g.Update(func(g *gocui.Gui) error {
|
||||
isFocused := gui.g.CurrentView().Name() == v.Name()
|
||||
list, err := utils.RenderList(items, isFocused)
|
||||
if err != nil {
|
||||
return gui.createErrorPanel(gui.g, err.Error())
|
||||
}
|
||||
v.Clear()
|
||||
fmt.Fprint(v, list)
|
||||
return nil
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) renderPanelOptions() error {
|
||||
currentView := gui.g.CurrentView()
|
||||
switch currentView.Name() {
|
||||
case "menu":
|
||||
return gui.renderMenuOptions()
|
||||
case "main":
|
||||
if gui.State.Context == "merging" {
|
||||
return gui.renderMergeOptions()
|
||||
}
|
||||
}
|
||||
return gui.renderGlobalOptions()
|
||||
}
|
||||
|
||||
func (gui *Gui) handleFocusView(g *gocui.Gui, v *gocui.View) error {
|
||||
_, err := gui.g.SetCurrentView(v.Name())
|
||||
return err
|
||||
}
|
||||
|
||||
func (gui *Gui) isPopupPanel(viewName string) bool {
|
||||
return viewName == "commitMessage" || viewName == "credentials" || viewName == "confirmation" || viewName == "menu"
|
||||
}
|
||||
|
||||
func (gui *Gui) popupPanelFocused() bool {
|
||||
return gui.isPopupPanel(gui.currentViewName())
|
||||
}
|
||||
|
||||
func (gui *Gui) handleClick(v *gocui.View, itemCount int, selectedLine *int, handleSelect func(*gocui.Gui, *gocui.View) error) error {
|
||||
if gui.popupPanelFocused() && v != nil && !gui.isPopupPanel(v.Name()) {
|
||||
return nil
|
||||
}
|
||||
|
||||
if _, err := gui.g.SetCurrentView(v.Name()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
newSelectedLine := v.SelectedLineIdx()
|
||||
|
||||
if newSelectedLine < 0 {
|
||||
newSelectedLine = 0
|
||||
}
|
||||
|
||||
if newSelectedLine > itemCount-1 {
|
||||
newSelectedLine = itemCount - 1
|
||||
}
|
||||
|
||||
*selectedLine = newSelectedLine
|
||||
|
||||
return handleSelect(gui.g, v)
|
||||
}
|
||||
|
||||
@@ -16,6 +16,9 @@ func addDutch(i18nObject *i18n.Bundle) error {
|
||||
}, &i18n.Message{
|
||||
ID: "DiffTitle",
|
||||
Other: "Diff",
|
||||
}, &i18n.Message{
|
||||
ID: "LogTitle",
|
||||
Other: "Log",
|
||||
}, &i18n.Message{
|
||||
ID: "FilesTitle",
|
||||
Other: "Bestanden",
|
||||
@@ -25,18 +28,51 @@ func addDutch(i18nObject *i18n.Bundle) error {
|
||||
}, &i18n.Message{
|
||||
ID: "CommitsTitle",
|
||||
Other: "Commits",
|
||||
}, &i18n.Message{
|
||||
ID: "CommitsDiffTitle",
|
||||
Other: "Commits (specific diff mode)",
|
||||
}, &i18n.Message{
|
||||
ID: "CommitsDiff",
|
||||
Other: "select commit to diff with another commit",
|
||||
}, &i18n.Message{
|
||||
ID: "StashTitle",
|
||||
Other: "Stash",
|
||||
}, &i18n.Message{
|
||||
ID: "UnstagedChanges",
|
||||
Other: `Unstaged Changes`,
|
||||
}, &i18n.Message{
|
||||
ID: "StagedChanges",
|
||||
Other: `Staged Changes`,
|
||||
}, &i18n.Message{
|
||||
ID: "MergingMainTitle",
|
||||
Other: "Resolve merge conflicts",
|
||||
}, &i18n.Message{
|
||||
ID: "CommitMessage",
|
||||
Other: "Commit Bericht",
|
||||
Other: "Commit bericht",
|
||||
}, &i18n.Message{
|
||||
ID: "CredentialsUsername",
|
||||
Other: "Gebruikersnaam",
|
||||
}, &i18n.Message{
|
||||
ID: "CredentialsPassword",
|
||||
Other: "Wachtwoord",
|
||||
}, &i18n.Message{
|
||||
ID: "PassUnameWrong",
|
||||
Other: "Wachtwoord en/of gebruikersnaam verkeert",
|
||||
}, &i18n.Message{
|
||||
ID: "CommitChanges",
|
||||
Other: "Commit Veranderingen",
|
||||
Other: "Commit veranderingen",
|
||||
}, &i18n.Message{
|
||||
ID: "AmendLastCommit",
|
||||
Other: "wijzig laatste commit",
|
||||
}, &i18n.Message{
|
||||
ID: "SureToAmend",
|
||||
Other: "Weet je zeker dat je de laatste commit wilt wijzigen? U kunt het commit-bericht wijzigen vanuit het commits-paneel.",
|
||||
}, &i18n.Message{
|
||||
ID: "NoCommitToAmend",
|
||||
Other: "Er is geen commits om te wijzigen.",
|
||||
}, &i18n.Message{
|
||||
ID: "CommitChangesWithEditor",
|
||||
Other: "commit Veranderingen met de git editor",
|
||||
Other: "commit veranderingen met de git editor",
|
||||
}, &i18n.Message{
|
||||
ID: "StatusTitle",
|
||||
Other: "Status",
|
||||
@@ -52,9 +88,6 @@ func addDutch(i18nObject *i18n.Bundle) error {
|
||||
}, &i18n.Message{
|
||||
ID: "execute",
|
||||
Other: "uitvoeren",
|
||||
}, &i18n.Message{
|
||||
ID: "stashFiles",
|
||||
Other: "stash-bestanden",
|
||||
}, &i18n.Message{
|
||||
ID: "open",
|
||||
Other: "open",
|
||||
@@ -74,11 +107,14 @@ func addDutch(i18nObject *i18n.Bundle) error {
|
||||
ID: "refresh",
|
||||
Other: "verversen",
|
||||
}, &i18n.Message{
|
||||
ID: "addPatch",
|
||||
Other: "verandering toevoegen",
|
||||
ID: "push",
|
||||
Other: "push",
|
||||
}, &i18n.Message{
|
||||
ID: "pull",
|
||||
Other: "pull",
|
||||
}, &i18n.Message{
|
||||
ID: "edit",
|
||||
Other: "verander",
|
||||
Other: "bewerken",
|
||||
}, &i18n.Message{
|
||||
ID: "scroll",
|
||||
Other: "scroll",
|
||||
@@ -87,13 +123,13 @@ func addDutch(i18nObject *i18n.Bundle) error {
|
||||
Other: "samenvoegen afbreken",
|
||||
}, &i18n.Message{
|
||||
ID: "resolveMergeConflicts",
|
||||
Other: "verhelp samenvoegen fouten",
|
||||
Other: "los merge conflicten op",
|
||||
}, &i18n.Message{
|
||||
ID: "checkout",
|
||||
Other: "uitchecken",
|
||||
}, &i18n.Message{
|
||||
ID: "NoChangedFiles",
|
||||
Other: "Geen Bestanden verandert",
|
||||
Other: "Geen bestanden veranderd",
|
||||
}, &i18n.Message{
|
||||
ID: "FileHasNoUnstagedChanges",
|
||||
Other: "Het bestand heeft geen unstaged veranderingen om toe te voegen",
|
||||
@@ -109,27 +145,30 @@ func addDutch(i18nObject *i18n.Bundle) error {
|
||||
}, &i18n.Message{
|
||||
ID: "NoFilesDisplay",
|
||||
Other: "Geen bestanden om te laten zien",
|
||||
}, &i18n.Message{
|
||||
ID: "NotAFile",
|
||||
Other: "Dit is geen bestand",
|
||||
}, &i18n.Message{
|
||||
ID: "PullWait",
|
||||
Other: "Pulling...",
|
||||
Other: "Pullen...",
|
||||
}, &i18n.Message{
|
||||
ID: "PushWait",
|
||||
Other: "Pushing...",
|
||||
Other: "Pushen...",
|
||||
}, &i18n.Message{
|
||||
ID: "FetchWait",
|
||||
Other: "Fetchen...",
|
||||
}, &i18n.Message{
|
||||
ID: "FileNoMergeCons",
|
||||
Other: "Dit bestand heeft geen merge conflicten",
|
||||
}, &i18n.Message{
|
||||
ID: "SureResetHardHead",
|
||||
Other: "Weet je het zeker dat je `reset --hard HEAD` wil uitvoeren? het kan dat je hierdoor bestanden verliest",
|
||||
}, &i18n.Message{
|
||||
ID: "SureTo",
|
||||
Other: "Weet je het zeker dat je {{.fileName}} wilt {{.deleteVerb}} (je veranderingen zullen worden verwijdert)",
|
||||
Other: "Weet je het zeker dat je {{.fileName}} wilt {{.deleteVerb}} (je veranderingen zullen worden verwijderd)",
|
||||
}, &i18n.Message{
|
||||
ID: "AlreadyCheckedOutBranch",
|
||||
Other: "Je hebt uitgecheckt op deze branch",
|
||||
Other: "Je hebt deze branch al uitgecheckt",
|
||||
}, &i18n.Message{
|
||||
ID: "SureForceCheckout",
|
||||
Other: "Weet je zeker dat je het uitchecken wil forceren? al je locale verandering zullen worden verwijdert",
|
||||
Other: "Weet je zeker dat je het uitchecken wil forceren? Al je lokale verandering zullen worden verwijdert",
|
||||
}, &i18n.Message{
|
||||
ID: "ForceCheckoutBranch",
|
||||
Other: "Forceer uitchecken op deze branch",
|
||||
@@ -147,10 +186,13 @@ func addDutch(i18nObject *i18n.Bundle) error {
|
||||
Other: "Verwijder branch",
|
||||
}, &i18n.Message{
|
||||
ID: "DeleteBranchMessage",
|
||||
Other: "Weet je zeker dat je branch {{.selectedBranchName}} wil verwijderen?",
|
||||
Other: "Weet je zeker dat je branch {{.selectedBranchName}} wilt verwijderen?",
|
||||
}, &i18n.Message{
|
||||
ID: "ForceDeleteBranchMessage",
|
||||
Other: "Weet je zeker dat je branch {{.selectedBranchName}} geforceerd wil verwijderen?",
|
||||
}, &i18n.Message{
|
||||
ID: "rebaseBranch",
|
||||
Other: "rebase branch",
|
||||
}, &i18n.Message{
|
||||
ID: "CantMergeBranchIntoItself",
|
||||
Other: "Je kan niet een branch in zichzelf mergen",
|
||||
@@ -159,7 +201,7 @@ func addDutch(i18nObject *i18n.Bundle) error {
|
||||
Other: "forceer checkout",
|
||||
}, &i18n.Message{
|
||||
ID: "merge",
|
||||
Other: "merge",
|
||||
Other: "samenvoegen",
|
||||
}, &i18n.Message{
|
||||
ID: "checkoutByName",
|
||||
Other: "uitchecken bij naam",
|
||||
@@ -198,7 +240,7 @@ func addDutch(i18nObject *i18n.Bundle) error {
|
||||
Other: "squash beneden",
|
||||
}, &i18n.Message{
|
||||
ID: "rename",
|
||||
Other: "hernoem",
|
||||
Other: "hernoemen",
|
||||
}, &i18n.Message{
|
||||
ID: "resetToThisCommit",
|
||||
Other: "reset naar deze commit",
|
||||
@@ -235,12 +277,9 @@ func addDutch(i18nObject *i18n.Bundle) error {
|
||||
}, &i18n.Message{
|
||||
ID: "PotentialErrInGetselectedCommit",
|
||||
Other: "Er is mogelijk een error in getSelected Commit (geen match tussen ui en state)",
|
||||
}, &i18n.Message{
|
||||
ID: "NoCommitsThisBranch",
|
||||
Other: "Geen commits voor deze branch",
|
||||
}, &i18n.Message{
|
||||
ID: "Error",
|
||||
Other: "Fout",
|
||||
Other: "Foutmelding",
|
||||
}, &i18n.Message{
|
||||
ID: "resizingPopupPanel",
|
||||
Other: "resizen popup paneel",
|
||||
@@ -249,16 +288,16 @@ func addDutch(i18nObject *i18n.Bundle) error {
|
||||
Other: "subprocess lopend",
|
||||
}, &i18n.Message{
|
||||
ID: "selectHunk",
|
||||
Other: "selecteer Hunk",
|
||||
Other: "selecteer stuk",
|
||||
}, &i18n.Message{
|
||||
ID: "navigateConflicts",
|
||||
Other: "navigeer conflicts",
|
||||
}, &i18n.Message{
|
||||
ID: "pickHunk",
|
||||
Other: "kies Hunk",
|
||||
Other: "kies stuk",
|
||||
}, &i18n.Message{
|
||||
ID: "pickBothHunks",
|
||||
Other: "kies bijde hunks",
|
||||
Other: "kies beide stukken",
|
||||
}, &i18n.Message{
|
||||
ID: "undo",
|
||||
Other: "ongedaan maken",
|
||||
@@ -295,21 +334,12 @@ func addDutch(i18nObject *i18n.Bundle) error {
|
||||
}, &i18n.Message{
|
||||
ID: "NoViewMachingNewLineFocusedSwitchStatement",
|
||||
Other: "Er machen geen weergave met de newLineFocused switch declaratie",
|
||||
}, &i18n.Message{
|
||||
ID: "settingPreviewsViewTo",
|
||||
Other: "vorige weergave instellen op: {{.oldViewName}}",
|
||||
}, &i18n.Message{
|
||||
ID: "newFocusedViewIs",
|
||||
Other: "nieuw gefocussed weergave is {{.newFocusedView}}",
|
||||
}, &i18n.Message{
|
||||
ID: "CantCloseConfirmationPrompt",
|
||||
Other: "Kon de bevestiging prompt niet sluiten: {{.error}}",
|
||||
}, &i18n.Message{
|
||||
ID: "NoChangedFiles",
|
||||
Other: "Geen veranderde files",
|
||||
}, &i18n.Message{
|
||||
ID: "ClearFilePanel",
|
||||
Other: "maak bestandsvenster leeg",
|
||||
}, &i18n.Message{
|
||||
ID: "MergeAborted",
|
||||
Other: "Merge afgebroken",
|
||||
@@ -324,31 +354,31 @@ func addDutch(i18nObject *i18n.Bundle) error {
|
||||
Other: "Forceer push",
|
||||
}, &i18n.Message{
|
||||
ID: "ForcePushPrompt",
|
||||
Other: "Jou branch is afgeweken van de remote branch. Druk 'esc' om te anuleren, of 'enter' om geforceert te pushen.",
|
||||
Other: "Jouw branch is afgeweken van de remote branch. Druk 'esc' om te annuleren, of 'enter' om geforceert te pushen.",
|
||||
}, &i18n.Message{
|
||||
ID: "checkForUpdate",
|
||||
Other: "check voor updates",
|
||||
}, &i18n.Message{
|
||||
ID: "CheckingForUpdates",
|
||||
Other: "checken voor updates...",
|
||||
Other: "zoeken naar updates...",
|
||||
}, &i18n.Message{
|
||||
ID: "OnLatestVersionErr",
|
||||
Other: "Je hebt al de laatste versie",
|
||||
}, &i18n.Message{
|
||||
ID: "MajorVersionErr",
|
||||
Other: "Nieuwe versie ({{.newVersion}}) is niet teruggaand compatibele vergeleken met de huidige versie ({{.currentVersion}})",
|
||||
Other: "Nieuwe versie ({{.newVersion}}) is niet backwards compatibele vergeleken met de huidige versie ({{.currentVersion}})",
|
||||
}, &i18n.Message{
|
||||
ID: "CouldNotFindBinaryErr",
|
||||
Other: "Kon geen binary vinden op {{.url}}",
|
||||
}, &i18n.Message{
|
||||
ID: "AnonymousReportingTitle",
|
||||
Other: "Help maak lazygit beter",
|
||||
Other: "Help lazygit te verbeteren",
|
||||
}, &i18n.Message{
|
||||
ID: "AnonymousReportingPrompt",
|
||||
Other: "Zou je anonieme data rapportage willen aanzetten om lazygit beter te kunnen maken? (enter/esc)",
|
||||
}, &i18n.Message{
|
||||
ID: "removeFile",
|
||||
Other: `Verwijder als untracked / uitchecken wordt gevolgd (ga weg)`,
|
||||
ID: "GitconfigParseErr",
|
||||
Other: `Gogit kon je gitconfig bestand niet goed parsen door de aanwezigheid van losstaande '\' tekens. Het weghalen van deze tekens zou het probleem moeten oplossen. `,
|
||||
}, &i18n.Message{
|
||||
ID: "editFile",
|
||||
Other: `verander bestand`,
|
||||
@@ -361,15 +391,372 @@ func addDutch(i18nObject *i18n.Bundle) error {
|
||||
}, &i18n.Message{
|
||||
ID: "refreshFiles",
|
||||
Other: `refresh bestanden`,
|
||||
}, &i18n.Message{
|
||||
ID: "resetHard",
|
||||
Other: `harde reset`,
|
||||
}, &i18n.Message{
|
||||
ID: "mergeIntoCurrentBranch",
|
||||
Other: `merge in met huidige checked out branch`,
|
||||
}, &i18n.Message{
|
||||
ID: "ConfirmQuit",
|
||||
Other: `Weet je zeker dat je dit programma wil sluiten?`,
|
||||
}, &i18n.Message{
|
||||
ID: "SwitchRepo",
|
||||
Other: "wissel naar een recente repo",
|
||||
}, &i18n.Message{
|
||||
ID: "UnsupportedGitService",
|
||||
Other: `Niet-ondersteunde git-service`,
|
||||
}, &i18n.Message{
|
||||
ID: "createPullRequest",
|
||||
Other: `maak een pull-aanvraag`,
|
||||
}, &i18n.Message{
|
||||
ID: "NoBranchOnRemote",
|
||||
Other: `Deze branch bestaat niet op de remote. U moet het eerst naar de remote pushen.`,
|
||||
}, &i18n.Message{
|
||||
ID: "fetch",
|
||||
Other: `fetch`,
|
||||
}, &i18n.Message{
|
||||
ID: "NoAutomaticGitFetchTitle",
|
||||
Other: `Geen automatische git fetch`,
|
||||
}, &i18n.Message{
|
||||
ID: "NoAutomaticGitFetchBody",
|
||||
Other: `Lazygit kan niet "git fetch" uitvoeren in een privé repository, gebruik f in het branches paneel om "git fetch" manueel uit te voeren`,
|
||||
}, &i18n.Message{
|
||||
ID: "StageLines",
|
||||
Other: `stage individuele hunks/lijnen`,
|
||||
}, &i18n.Message{
|
||||
ID: "FileStagingRequirements",
|
||||
Other: `Kan alleen individuele lijnen stagen van getrackte bestanden met onstaged veranderingen`,
|
||||
}, &i18n.Message{
|
||||
ID: "StagingTitle",
|
||||
Other: `Stage Lines/Hunks`,
|
||||
}, &i18n.Message{
|
||||
ID: "StageHunk",
|
||||
Other: `stage hunk`,
|
||||
}, &i18n.Message{
|
||||
ID: "StageLine",
|
||||
Other: `stage lijn`,
|
||||
}, &i18n.Message{
|
||||
ID: "ReturnToFilesPanel",
|
||||
Other: `ga terug naar het bestanden paneel`,
|
||||
}, &i18n.Message{
|
||||
ID: "CantFindHunks",
|
||||
Other: `Kan geen hunks vinden in deze patch`,
|
||||
}, &i18n.Message{
|
||||
ID: "CantFindHunk",
|
||||
Other: `Kan geen hunk vinden`,
|
||||
}, &i18n.Message{
|
||||
ID: "RebasingTitle",
|
||||
Other: "Rebasing",
|
||||
}, &i18n.Message{
|
||||
ID: "MergingTitle",
|
||||
Other: "Merging",
|
||||
}, &i18n.Message{
|
||||
ID: "ConfirmRebase",
|
||||
Other: "Weet je zeker dat je {{.checkedOutBranch}} op {{.selectedBranch}} wil rebasen?",
|
||||
}, &i18n.Message{
|
||||
ID: "ConfirmMerge",
|
||||
Other: "Weet je zeker dat je {{.selectedBranch}} in {{.checkedOutBranch}} wil mergen?",
|
||||
}, &i18n.Message{
|
||||
ID: "FwdNoUpstream",
|
||||
Other: "Kan niet de branch vooruitspoelen zonder upstream",
|
||||
}, &i18n.Message{
|
||||
ID: "ErrorOccurred",
|
||||
Other: "Er is iets fout gegaan! Zou je hier een issue aan willen maken: https://github.com/jesseduffield/lazygit/issues",
|
||||
}, &i18n.Message{
|
||||
ID: "FwdCommitsToPush",
|
||||
Other: "Je kan niet vooruitspoelen als de branch geen nieuwe commits heeft",
|
||||
}, &i18n.Message{
|
||||
ID: "MainTitle",
|
||||
Other: "Hoofd",
|
||||
}, &i18n.Message{
|
||||
ID: "NormalTitle",
|
||||
Other: "Normaal",
|
||||
}, &i18n.Message{
|
||||
ID: "softReset",
|
||||
Other: "zacht reset",
|
||||
}, &i18n.Message{
|
||||
ID: "CantRebaseOntoSelf",
|
||||
Other: "Je kan niet een branch rebasen op zichzelf",
|
||||
}, &i18n.Message{
|
||||
ID: "SureSquashThisCommit",
|
||||
Other: "Weet je zeker dat je deze commit wil samenvoegen met de commit hieronder?",
|
||||
}, &i18n.Message{
|
||||
ID: "Squash",
|
||||
Other: "Squash",
|
||||
}, &i18n.Message{
|
||||
ID: "pickCommit",
|
||||
Other: "pick commit (when mid-rebase)",
|
||||
}, &i18n.Message{
|
||||
ID: "revertCommit",
|
||||
Other: "commit omgedaan maken",
|
||||
}, &i18n.Message{
|
||||
ID: "deleteCommit",
|
||||
Other: "verwijder commit",
|
||||
}, &i18n.Message{
|
||||
ID: "moveDownCommit",
|
||||
Other: "verplaats commit 1 omlaag",
|
||||
}, &i18n.Message{
|
||||
ID: "moveUpCommit",
|
||||
Other: "verplaats commit 1 omhoog",
|
||||
}, &i18n.Message{
|
||||
ID: "editCommit",
|
||||
Other: "verander commit",
|
||||
}, &i18n.Message{
|
||||
ID: "amendToCommit",
|
||||
Other: "wijzig commit met staged veranderingen",
|
||||
}, &i18n.Message{
|
||||
ID: "FoundConflicts",
|
||||
Other: "Conflicten!, Om af te breken druk 'esc', anders druk op 'enter'",
|
||||
}, &i18n.Message{
|
||||
ID: "FoundConflictsTitle",
|
||||
Other: "Auto-merge mislukt",
|
||||
}, &i18n.Message{
|
||||
ID: "Undo",
|
||||
Other: "ongedaan maken",
|
||||
}, &i18n.Message{
|
||||
ID: "PickHunk",
|
||||
Other: "pick hunk",
|
||||
}, &i18n.Message{
|
||||
ID: "PickBothHunks",
|
||||
Other: "pick beide hunks",
|
||||
}, &i18n.Message{
|
||||
ID: "ViewMergeRebaseOptions",
|
||||
Other: "bekijk merge/rebase opties",
|
||||
}, &i18n.Message{
|
||||
ID: "NotMergingOrRebasing",
|
||||
Other: "Je bent momenteel niet aan het rebasen of mergen",
|
||||
}, &i18n.Message{
|
||||
ID: "RecentRepos",
|
||||
Other: "recent repositories",
|
||||
}, &i18n.Message{
|
||||
ID: "MergeOptionsTitle",
|
||||
Other: "Merge Opties",
|
||||
}, &i18n.Message{
|
||||
ID: "RebaseOptionsTitle",
|
||||
Other: "Rebase Opties",
|
||||
}, &i18n.Message{
|
||||
ID: "ConflictsResolved",
|
||||
Other: "alle merge conflicten zijn opgelost. Wilt je verder gaan?",
|
||||
}, &i18n.Message{
|
||||
ID: "NoRoom",
|
||||
Other: "Niet genoeg ruimte",
|
||||
}, &i18n.Message{
|
||||
ID: "YouAreHere",
|
||||
Other: "JE BENT HIER",
|
||||
}, &i18n.Message{
|
||||
ID: "rewordNotSupported",
|
||||
Other: "herformatteren van commits in interactief rebasen is nog niet ondersteund",
|
||||
}, &i18n.Message{
|
||||
ID: "cherryPickCopy",
|
||||
Other: "kopiëer commit (cherry-pick)",
|
||||
}, &i18n.Message{
|
||||
ID: "cherryPickCopyRange",
|
||||
Other: "kopiëer commit reeks (cherry-pick)",
|
||||
}, &i18n.Message{
|
||||
ID: "pasteCommits",
|
||||
Other: "plak commits (cherry-pick)",
|
||||
}, &i18n.Message{
|
||||
ID: "SureCherryPick",
|
||||
Other: "Weet je zeker dat je de gekopieerde commits naar deze branch wil cherry-picken?",
|
||||
}, &i18n.Message{
|
||||
ID: "CherryPick",
|
||||
Other: "Cherry-Pick",
|
||||
}, &i18n.Message{
|
||||
ID: "CannotRebaseOntoFirstCommit",
|
||||
Other: "Je kan niet interactief rebasen naar de eerste commit",
|
||||
}, &i18n.Message{
|
||||
ID: "CannotSquashOntoSecondCommit",
|
||||
Other: "Je kan niet een squash/fixup doen naar de 2de commit",
|
||||
}, &i18n.Message{
|
||||
ID: "Donate",
|
||||
Other: "Doneer",
|
||||
}, &i18n.Message{
|
||||
ID: "PrevLine",
|
||||
Other: "selecteer de vorige lijn",
|
||||
}, &i18n.Message{
|
||||
ID: "NextLine",
|
||||
Other: "selecteer de volgende lijn",
|
||||
}, &i18n.Message{
|
||||
ID: "PrevHunk",
|
||||
Other: "selecteer de vorige hunk",
|
||||
}, &i18n.Message{
|
||||
ID: "NextHunk",
|
||||
Other: "selecteer de volgende hunk",
|
||||
}, &i18n.Message{
|
||||
ID: "PrevConflict",
|
||||
Other: "selecteer voorgaand conflict",
|
||||
}, &i18n.Message{
|
||||
ID: "NextConflict",
|
||||
Other: "selecteer volgende conflict",
|
||||
}, &i18n.Message{
|
||||
ID: "SelectTop",
|
||||
Other: "selecteer bovenste hunk",
|
||||
}, &i18n.Message{
|
||||
ID: "SelectBottom",
|
||||
Other: "selecteer onderste hunk",
|
||||
}, &i18n.Message{
|
||||
ID: "ScrollDown",
|
||||
Other: "scroll omlaag",
|
||||
}, &i18n.Message{
|
||||
ID: "ScrollUp",
|
||||
Other: "scroll omhoog",
|
||||
}, &i18n.Message{
|
||||
ID: "AmendCommitTitle",
|
||||
Other: "Commit wijzigen",
|
||||
}, &i18n.Message{
|
||||
ID: "AmendCommitPrompt",
|
||||
Other: "Weet je zeker dat je deze commit wil wijzigen met de vorige staged bestanden?",
|
||||
}, &i18n.Message{
|
||||
ID: "DeleteCommitTitle",
|
||||
Other: "Verwijder Commit",
|
||||
}, &i18n.Message{
|
||||
ID: "DeleteCommitPrompt",
|
||||
Other: "Weet je zeker dat je deze commit wil verwijderen?",
|
||||
}, &i18n.Message{
|
||||
ID: "SquashingStatus",
|
||||
Other: "squashing",
|
||||
}, &i18n.Message{
|
||||
ID: "FixingStatus",
|
||||
Other: "fixing up",
|
||||
}, &i18n.Message{
|
||||
ID: "DeletingStatus",
|
||||
Other: "verwijderen",
|
||||
}, &i18n.Message{
|
||||
ID: "MovingStatus",
|
||||
Other: "verplaatsen",
|
||||
}, &i18n.Message{
|
||||
ID: "RebasingStatus",
|
||||
Other: "rebasing",
|
||||
}, &i18n.Message{
|
||||
ID: "AmendingStatus",
|
||||
Other: "wijzigen",
|
||||
}, &i18n.Message{
|
||||
ID: "CherryPickingStatus",
|
||||
Other: "cherry-picking",
|
||||
}, &i18n.Message{
|
||||
ID: "CommitFiles",
|
||||
Other: "Commit bestanden",
|
||||
}, &i18n.Message{
|
||||
ID: "viewCommitFiles",
|
||||
Other: "bekijk gecommite bestanden",
|
||||
}, &i18n.Message{
|
||||
ID: "CommitFilesTitle",
|
||||
Other: "Commit bestanden",
|
||||
}, &i18n.Message{
|
||||
ID: "goBack",
|
||||
Other: "ga terug",
|
||||
}, &i18n.Message{
|
||||
ID: "NoCommiteFiles",
|
||||
Other: "Geen bestanden voor deze commit",
|
||||
}, &i18n.Message{
|
||||
ID: "checkoutCommitFile",
|
||||
Other: "bestand uitchecken",
|
||||
}, &i18n.Message{
|
||||
ID: "discardOldFileChange",
|
||||
Other: "uitsluit deze commit zijn veranderingen aan dit bestand",
|
||||
}, &i18n.Message{
|
||||
ID: "DiscardFileChangesTitle",
|
||||
Other: "uitsluit bestand zijn veranderingen",
|
||||
}, &i18n.Message{
|
||||
ID: "DiscardFileChangesPrompt",
|
||||
Other: "Weet je zeker dat je de wijzigingen van deze commit in dit bestand wilt weggooien? Als dit bestand is gecreëerd in deze commit dan zal dit bestand worden verwijdert",
|
||||
}, &i18n.Message{
|
||||
ID: "DisabledForGPG",
|
||||
Other: "Onderdelen niet beschikbaar voor gebruikers die GPG gebruiken",
|
||||
}, &i18n.Message{
|
||||
ID: "CreateRepo",
|
||||
Other: "Niet in een git repository. Creëer een nieuwe git repository? (y/n): ",
|
||||
}, &i18n.Message{
|
||||
ID: "AutoStashTitle",
|
||||
Other: "Autostash?",
|
||||
}, &i18n.Message{
|
||||
ID: "AutoStashPrompt",
|
||||
Other: "Je moet je veranderingen stashen en poppen om ze over te bregen. Dit automatisch doen? (enter/esc)",
|
||||
}, &i18n.Message{
|
||||
ID: "StashPrefix",
|
||||
Other: "Auto-stashing veranderingen voor ",
|
||||
}, &i18n.Message{
|
||||
ID: "viewDiscardOptions",
|
||||
Other: "bekijk 'veranderingen ongedaan maken' opties",
|
||||
}, &i18n.Message{
|
||||
ID: "cancel",
|
||||
Other: "anuleren",
|
||||
}, &i18n.Message{
|
||||
ID: "discardAllChanges",
|
||||
Other: "negeer alle wijzigingen",
|
||||
}, &i18n.Message{
|
||||
ID: "discardUnstagedChanges",
|
||||
Other: "negeer unstaged wijzigingen",
|
||||
}, &i18n.Message{
|
||||
ID: "discardAllChangesToAllFiles",
|
||||
Other: "verwijder werkende tree",
|
||||
}, &i18n.Message{
|
||||
ID: "discardAnyUnstagedChanges",
|
||||
Other: "discard unstaged changes",
|
||||
}, &i18n.Message{
|
||||
ID: "discardUntrackedFiles",
|
||||
Other: "negeer niet-gevonden bestanden",
|
||||
}, &i18n.Message{
|
||||
ID: "viewResetOptions",
|
||||
Other: `bekijk reset opties`,
|
||||
}, &i18n.Message{
|
||||
ID: "hardReset",
|
||||
Other: "harde reset",
|
||||
}, &i18n.Message{
|
||||
ID: "createFixupCommit",
|
||||
Other: `creëer fixup commit voor deze commit`,
|
||||
}, &i18n.Message{
|
||||
ID: "squashAboveCommits",
|
||||
Other: `squash bovenstaande commits`,
|
||||
}, &i18n.Message{
|
||||
ID: "SquashAboveCommits",
|
||||
Other: `Squash bovenstaande commits`,
|
||||
}, &i18n.Message{
|
||||
ID: "SureSquashAboveCommits",
|
||||
Other: `Weet je zeker dat je alles wil squash/fixup! voor de bovenstaand commits {{.commit}}?`,
|
||||
}, &i18n.Message{
|
||||
ID: "CreateFixupCommit",
|
||||
Other: `Creëer fixup commit`,
|
||||
}, &i18n.Message{
|
||||
ID: "SureCreateFixupCommit",
|
||||
Other: `Weet je zeker dat je een fixup wil maken! commit voor commit {{.commit}}?`,
|
||||
}, &i18n.Message{
|
||||
ID: "executeCustomCommand",
|
||||
Other: "voor aangepast commando uit",
|
||||
}, &i18n.Message{
|
||||
ID: "CustomCommand",
|
||||
Other: "Aangepast commando:",
|
||||
}, &i18n.Message{
|
||||
ID: "commitChangesWithoutHook",
|
||||
Other: "commit veranderingen zonder pre-commit hook",
|
||||
}, &i18n.Message{
|
||||
ID: "SkipHookPrefixNotConfigured",
|
||||
Other: "Je hebt nog niet een commit bericht voorvoegsel ingesteld voor het overslaan van hooks. Set `git.skipHookPrefix = 'WIP'` in je config",
|
||||
}, &i18n.Message{
|
||||
ID: "resetTo",
|
||||
Other: `reset to`,
|
||||
}, &i18n.Message{
|
||||
ID: "pressEnterToReturn",
|
||||
Other: "Press enter to return to lazygit",
|
||||
}, &i18n.Message{
|
||||
ID: "viewStashOptions",
|
||||
Other: "view stash options",
|
||||
}, &i18n.Message{
|
||||
ID: "stashAllChanges",
|
||||
Other: "stash-bestanden",
|
||||
}, &i18n.Message{
|
||||
ID: "stashStagedChanges",
|
||||
Other: "stash staged changes",
|
||||
}, &i18n.Message{
|
||||
ID: "stashOptions",
|
||||
Other: "Stash options",
|
||||
}, &i18n.Message{
|
||||
ID: "notARepository",
|
||||
Other: "Error: must be run inside a git repository",
|
||||
}, &i18n.Message{
|
||||
ID: "jump",
|
||||
Other: "jump to panel",
|
||||
}, &i18n.Message{
|
||||
ID: "ExitLineByLineMode",
|
||||
Other: `exit line-by-line mode`,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -24,6 +24,9 @@ func addEnglish(i18nObject *i18n.Bundle) error {
|
||||
}, &i18n.Message{
|
||||
ID: "DiffTitle",
|
||||
Other: "Diff",
|
||||
}, &i18n.Message{
|
||||
ID: "LogTitle",
|
||||
Other: "Log",
|
||||
}, &i18n.Message{
|
||||
ID: "FilesTitle",
|
||||
Other: "Files",
|
||||
@@ -33,15 +36,63 @@ func addEnglish(i18nObject *i18n.Bundle) error {
|
||||
}, &i18n.Message{
|
||||
ID: "CommitsTitle",
|
||||
Other: "Commits",
|
||||
}, &i18n.Message{
|
||||
ID: "CommitsDiffTitle",
|
||||
Other: "Commits (specific diff mode)",
|
||||
}, &i18n.Message{
|
||||
ID: "CommitsDiff",
|
||||
Other: "select commit to diff with another commit",
|
||||
}, &i18n.Message{
|
||||
ID: "StashTitle",
|
||||
Other: "Stash",
|
||||
}, &i18n.Message{
|
||||
ID: "UnstagedChanges",
|
||||
Other: `Unstaged Changes`,
|
||||
}, &i18n.Message{
|
||||
ID: "StagedChanges",
|
||||
Other: `Staged Changes`,
|
||||
}, &i18n.Message{
|
||||
ID: "PatchBuildingMainTitle",
|
||||
Other: `Add Lines/Hunks To Patch`,
|
||||
}, &i18n.Message{
|
||||
ID: "MergingMainTitle",
|
||||
Other: "Resolve merge conflicts",
|
||||
}, &i18n.Message{
|
||||
ID: "MainTitle",
|
||||
Other: "Main",
|
||||
}, &i18n.Message{
|
||||
ID: "StagingTitle",
|
||||
Other: "Staging",
|
||||
}, &i18n.Message{
|
||||
ID: "MergingTitle",
|
||||
Other: "Merging",
|
||||
}, &i18n.Message{
|
||||
ID: "NormalTitle",
|
||||
Other: "Normal",
|
||||
}, &i18n.Message{
|
||||
ID: "CommitMessage",
|
||||
Other: "Commit message",
|
||||
}, &i18n.Message{
|
||||
ID: "CredentialsUsername",
|
||||
Other: "Username",
|
||||
}, &i18n.Message{
|
||||
ID: "CredentialsPassword",
|
||||
Other: "Password",
|
||||
}, &i18n.Message{
|
||||
ID: "PassUnameWrong",
|
||||
Other: "Password and/or username wrong",
|
||||
}, &i18n.Message{
|
||||
ID: "CommitChanges",
|
||||
Other: "commit changes",
|
||||
}, &i18n.Message{
|
||||
ID: "AmendLastCommit",
|
||||
Other: "amend last commit",
|
||||
}, &i18n.Message{
|
||||
ID: "SureToAmend",
|
||||
Other: "Are you sure you want to amend last commit? Afterwards, you can change commit message from the commits panel.",
|
||||
}, &i18n.Message{
|
||||
ID: "NoCommitToAmend",
|
||||
Other: "There's no commit to amend.",
|
||||
}, &i18n.Message{
|
||||
ID: "CommitChangesWithEditor",
|
||||
Other: "commit changes using git editor",
|
||||
@@ -60,9 +111,6 @@ func addEnglish(i18nObject *i18n.Bundle) error {
|
||||
}, &i18n.Message{
|
||||
ID: "execute",
|
||||
Other: "execute",
|
||||
}, &i18n.Message{
|
||||
ID: "stashFiles",
|
||||
Other: "stash files",
|
||||
}, &i18n.Message{
|
||||
ID: "open",
|
||||
Other: "open",
|
||||
@@ -87,9 +135,6 @@ func addEnglish(i18nObject *i18n.Bundle) error {
|
||||
}, &i18n.Message{
|
||||
ID: "pull",
|
||||
Other: "pull",
|
||||
}, &i18n.Message{
|
||||
ID: "addPatch",
|
||||
Other: "add patch",
|
||||
}, &i18n.Message{
|
||||
ID: "edit",
|
||||
Other: "edit",
|
||||
@@ -133,11 +178,14 @@ func addEnglish(i18nObject *i18n.Bundle) error {
|
||||
ID: "PushWait",
|
||||
Other: "Pushing...",
|
||||
}, &i18n.Message{
|
||||
ID: "FileNoMergeCons",
|
||||
Other: "This file has no merge conflicts",
|
||||
ID: "FetchWait",
|
||||
Other: "Fetching...",
|
||||
}, &i18n.Message{
|
||||
ID: "SureResetHardHead",
|
||||
Other: "Are you sure you want `reset --hard HEAD`? You may lose changes",
|
||||
ID: "FileNoMergeCons",
|
||||
Other: "This file has no inline merge conflicts",
|
||||
}, &i18n.Message{
|
||||
ID: "softReset",
|
||||
Other: "soft reset",
|
||||
}, &i18n.Message{
|
||||
ID: "SureTo",
|
||||
Other: "Are you sure you want to {{.deleteVerb}} {{.fileName}} (you will lose your changes)?",
|
||||
@@ -167,7 +215,13 @@ func addEnglish(i18nObject *i18n.Bundle) error {
|
||||
Other: "Are you sure you want to delete the branch {{.selectedBranchName}}?",
|
||||
}, &i18n.Message{
|
||||
ID: "ForceDeleteBranchMessage",
|
||||
Other: "Are you sure you want to force delete the branch {{.selectedBranchName}}?",
|
||||
Other: "{{.selectedBranchName}} is not fully merged. Are you sure you want to delete it?",
|
||||
}, &i18n.Message{
|
||||
ID: "rebaseBranch",
|
||||
Other: "rebase branch",
|
||||
}, &i18n.Message{
|
||||
ID: "CantRebaseOntoSelf",
|
||||
Other: "You cannot rebase a branch onto itself",
|
||||
}, &i18n.Message{
|
||||
ID: "CantMergeBranchIntoItself",
|
||||
Other: "You cannot merge a branch into itself",
|
||||
@@ -239,13 +293,40 @@ func addEnglish(i18nObject *i18n.Bundle) error {
|
||||
Other: "Fixup",
|
||||
}, &i18n.Message{
|
||||
ID: "SureFixupThisCommit",
|
||||
Other: "Are you sure you want to fixup this commit? The commit beneath will be squashed up into this one",
|
||||
Other: "Are you sure you want to 'fixup' this commit? It will be merged into the commit below",
|
||||
}, &i18n.Message{
|
||||
ID: "SureSquashThisCommit",
|
||||
Other: "Are you sure you want to squash this commit into the commit below?",
|
||||
}, &i18n.Message{
|
||||
ID: "Squash",
|
||||
Other: "Squash",
|
||||
}, &i18n.Message{
|
||||
ID: "pickCommit",
|
||||
Other: "pick commit (when mid-rebase)",
|
||||
}, &i18n.Message{
|
||||
ID: "revertCommit",
|
||||
Other: "revert commit",
|
||||
}, &i18n.Message{
|
||||
ID: "OnlyRenameTopCommit",
|
||||
Other: "Can only rename topmost commit",
|
||||
Other: "Can only reword topmost commit from within lazygit. Use shift+R instead",
|
||||
}, &i18n.Message{
|
||||
ID: "renameCommit",
|
||||
Other: "rename commit",
|
||||
Other: "reword commit",
|
||||
}, &i18n.Message{
|
||||
ID: "deleteCommit",
|
||||
Other: "delete commit",
|
||||
}, &i18n.Message{
|
||||
ID: "moveDownCommit",
|
||||
Other: "move commit down one",
|
||||
}, &i18n.Message{
|
||||
ID: "moveUpCommit",
|
||||
Other: "move commit up one",
|
||||
}, &i18n.Message{
|
||||
ID: "editCommit",
|
||||
Other: "edit commit",
|
||||
}, &i18n.Message{
|
||||
ID: "amendToCommit",
|
||||
Other: "amend commit with staged changes",
|
||||
}, &i18n.Message{
|
||||
ID: "renameCommitEditor",
|
||||
Other: "rename commit with editor",
|
||||
@@ -312,9 +393,6 @@ func addEnglish(i18nObject *i18n.Bundle) error {
|
||||
}, &i18n.Message{
|
||||
ID: "NoViewMachingNewLineFocusedSwitchStatement",
|
||||
Other: "No view matching newLineFocused switch statement",
|
||||
}, &i18n.Message{
|
||||
ID: "settingPreviewsViewTo",
|
||||
Other: "setting previous view to: {{.oldViewName}}",
|
||||
}, &i18n.Message{
|
||||
ID: "newFocusedViewIs",
|
||||
Other: "new focused view is {{.newFocusedView}}",
|
||||
@@ -324,9 +402,6 @@ func addEnglish(i18nObject *i18n.Bundle) error {
|
||||
}, &i18n.Message{
|
||||
ID: "NoChangedFiles",
|
||||
Other: "No changed files",
|
||||
}, &i18n.Message{
|
||||
ID: "ClearFilePanel",
|
||||
Other: "Clear file panel",
|
||||
}, &i18n.Message{
|
||||
ID: "MergeAborted",
|
||||
Other: "Merge aborted",
|
||||
@@ -363,12 +438,22 @@ func addEnglish(i18nObject *i18n.Bundle) error {
|
||||
}, &i18n.Message{
|
||||
ID: "AnonymousReportingPrompt",
|
||||
Other: "Would you like to enable anonymous reporting data to help improve lazygit? (enter/esc)",
|
||||
}, &i18n.Message{
|
||||
ID: "ShamelessSelfPromotionTitle",
|
||||
Other: "Shameless Self Promotion",
|
||||
}, &i18n.Message{
|
||||
ID: "ShamelessSelfPromotionMessage",
|
||||
Other: `Thanks for using lazygit! Three things to share with you:
|
||||
|
||||
1) lazygit now has basic mouse support!
|
||||
|
||||
2) If you want to learn about lazygit's features, watch this vid:
|
||||
https://youtu.be/CPLdltN7wgE
|
||||
|
||||
3) Github are now matching any donations dollar-for-dollar for the next 12 months, so if you've been tossing up over whether to click the donate link in the bottom right corner, now is the time!`,
|
||||
}, &i18n.Message{
|
||||
ID: "GitconfigParseErr",
|
||||
Other: `Gogit failed to parse your gitconfig file due to the presence of unquoted '\' characters. Removing these should fix the issue.`,
|
||||
}, &i18n.Message{
|
||||
ID: "removeFile",
|
||||
Other: `delete if untracked / checkout if tracked`,
|
||||
}, &i18n.Message{
|
||||
ID: "editFile",
|
||||
Other: `edit file`,
|
||||
@@ -381,15 +466,374 @@ func addEnglish(i18nObject *i18n.Bundle) error {
|
||||
}, &i18n.Message{
|
||||
ID: "refreshFiles",
|
||||
Other: `refresh files`,
|
||||
}, &i18n.Message{
|
||||
ID: "resetHard",
|
||||
Other: `reset hard`,
|
||||
}, &i18n.Message{
|
||||
ID: "mergeIntoCurrentBranch",
|
||||
Other: `merge into currently checked out branch`,
|
||||
}, &i18n.Message{
|
||||
ID: "ConfirmQuit",
|
||||
Other: `Are you sure you want to quit?`,
|
||||
}, &i18n.Message{
|
||||
ID: "SwitchRepo",
|
||||
Other: `switch to a recent repo`,
|
||||
}, &i18n.Message{
|
||||
ID: "UnsupportedGitService",
|
||||
Other: `Unsupported git service`,
|
||||
}, &i18n.Message{
|
||||
ID: "createPullRequest",
|
||||
Other: `create pull request`,
|
||||
}, &i18n.Message{
|
||||
ID: "NoBranchOnRemote",
|
||||
Other: `This branch doesn't exist on remote. You need to push it to remote first.`,
|
||||
}, &i18n.Message{
|
||||
ID: "fetch",
|
||||
Other: `fetch`,
|
||||
}, &i18n.Message{
|
||||
ID: "NoAutomaticGitFetchTitle",
|
||||
Other: `No automatic git fetch`,
|
||||
}, &i18n.Message{
|
||||
ID: "NoAutomaticGitFetchBody",
|
||||
Other: `Lazygit can't use "git fetch" in a private repo; use 'f' in the files panel to run "git fetch" manually`,
|
||||
}, &i18n.Message{
|
||||
ID: "StageLines",
|
||||
Other: `stage individual hunks/lines`,
|
||||
}, &i18n.Message{
|
||||
ID: "FileStagingRequirements",
|
||||
Other: `Can only stage individual lines for tracked files`,
|
||||
}, &i18n.Message{
|
||||
ID: "SelectHunk",
|
||||
Other: `select hunk`,
|
||||
}, &i18n.Message{
|
||||
ID: "StageSelection",
|
||||
Other: `stage selection`,
|
||||
}, &i18n.Message{
|
||||
ID: "ResetSelection",
|
||||
Other: `reset selection`,
|
||||
}, &i18n.Message{
|
||||
ID: "ToggleDragSelect",
|
||||
Other: `toggle drag select`,
|
||||
}, &i18n.Message{
|
||||
ID: "ToggleSelectHunk",
|
||||
Other: `toggle select hunk`,
|
||||
},
|
||||
&i18n.Message{
|
||||
ID: "TogglePanel",
|
||||
Other: `switch to other panel`,
|
||||
},
|
||||
&i18n.Message{
|
||||
ID: "CantStageStaged",
|
||||
Other: `You can't stage an already staged change!`,
|
||||
}, &i18n.Message{
|
||||
ID: "ReturnToFilesPanel",
|
||||
Other: `return to files panel`,
|
||||
}, &i18n.Message{
|
||||
ID: "CantFindHunks",
|
||||
Other: `Could not find any hunks in this patch`,
|
||||
}, &i18n.Message{
|
||||
ID: "CantFindHunk",
|
||||
Other: `Could not find hunk`,
|
||||
}, &i18n.Message{
|
||||
ID: "FastForward",
|
||||
Other: `fast-forward this branch from its upstream`,
|
||||
}, &i18n.Message{
|
||||
ID: "Fetching",
|
||||
Other: "fetching and fast-forwarding {{.from}} -> {{.to}} ...",
|
||||
}, &i18n.Message{
|
||||
ID: "FoundConflicts",
|
||||
Other: "Conflicts! To abort press 'esc', otherwise press 'enter'",
|
||||
}, &i18n.Message{
|
||||
ID: "FoundConflictsTitle",
|
||||
Other: "Auto-merge failed",
|
||||
}, &i18n.Message{
|
||||
ID: "Undo",
|
||||
Other: "undo",
|
||||
}, &i18n.Message{
|
||||
ID: "PickHunk",
|
||||
Other: "pick hunk",
|
||||
}, &i18n.Message{
|
||||
ID: "PickBothHunks",
|
||||
Other: "pick both hunks",
|
||||
}, &i18n.Message{
|
||||
ID: "ViewMergeRebaseOptions",
|
||||
Other: "view merge/rebase options",
|
||||
}, &i18n.Message{
|
||||
ID: "NotMergingOrRebasing",
|
||||
Other: "You are currently neither rebasing nor merging",
|
||||
}, &i18n.Message{
|
||||
ID: "RecentRepos",
|
||||
Other: "recent repositories",
|
||||
}, &i18n.Message{
|
||||
ID: "MergeOptionsTitle",
|
||||
Other: "Merge Options",
|
||||
}, &i18n.Message{
|
||||
ID: "RebaseOptionsTitle",
|
||||
Other: "Rebase Options",
|
||||
}, &i18n.Message{
|
||||
ID: "ConflictsResolved",
|
||||
Other: "all merge conflicts resolved. Continue?",
|
||||
}, &i18n.Message{
|
||||
ID: "RebasingTitle",
|
||||
Other: "Rebasing",
|
||||
}, &i18n.Message{
|
||||
ID: "MergingTitle",
|
||||
Other: "Merging",
|
||||
}, &i18n.Message{
|
||||
ID: "ConfirmRebase",
|
||||
Other: "Are you sure you want to rebase {{.checkedOutBranch}} onto {{.selectedBranch}}?",
|
||||
}, &i18n.Message{
|
||||
ID: "ConfirmMerge",
|
||||
Other: "Are you sure you want to merge {{.selectedBranch}} into {{.checkedOutBranch}}?",
|
||||
}, &i18n.Message{}, &i18n.Message{
|
||||
ID: "FwdNoUpstream",
|
||||
Other: "Cannot fast-forward a branch with no upstream",
|
||||
}, &i18n.Message{
|
||||
ID: "FwdCommitsToPush",
|
||||
Other: "Cannot fast-forward a branch with commits to push",
|
||||
}, &i18n.Message{
|
||||
ID: "ErrorOccurred",
|
||||
Other: "An error occurred! Please create an issue at https://github.com/jesseduffield/lazygit/issues",
|
||||
}, &i18n.Message{
|
||||
ID: "NoRoom",
|
||||
Other: "Not enough room",
|
||||
}, &i18n.Message{
|
||||
ID: "YouAreHere",
|
||||
Other: "YOU ARE HERE",
|
||||
}, &i18n.Message{
|
||||
ID: "rewordNotSupported",
|
||||
Other: "rewording commits while interactively rebasing is not currently supported",
|
||||
}, &i18n.Message{
|
||||
ID: "cherryPickCopy",
|
||||
Other: "copy commit (cherry-pick)",
|
||||
}, &i18n.Message{
|
||||
ID: "cherryPickCopyRange",
|
||||
Other: "copy commit range (cherry-pick)",
|
||||
}, &i18n.Message{
|
||||
ID: "pasteCommits",
|
||||
Other: "paste commits (cherry-pick)",
|
||||
}, &i18n.Message{
|
||||
ID: "SureCherryPick",
|
||||
Other: "Are you sure you want to cherry-pick the copied commits onto this branch?",
|
||||
}, &i18n.Message{
|
||||
ID: "CherryPick",
|
||||
Other: "Cherry-Pick",
|
||||
}, &i18n.Message{
|
||||
ID: "CannotRebaseOntoFirstCommit",
|
||||
Other: "You cannot interactive rebase onto the first commit",
|
||||
}, &i18n.Message{
|
||||
ID: "CannotSquashOntoSecondCommit",
|
||||
Other: "You cannot squash/fixup onto the second commit",
|
||||
}, &i18n.Message{
|
||||
ID: "Donate",
|
||||
Other: "Donate",
|
||||
}, &i18n.Message{
|
||||
ID: "PrevLine",
|
||||
Other: "select previous line",
|
||||
}, &i18n.Message{
|
||||
ID: "NextLine",
|
||||
Other: "select next line",
|
||||
}, &i18n.Message{
|
||||
ID: "PrevHunk",
|
||||
Other: "select previous hunk",
|
||||
}, &i18n.Message{
|
||||
ID: "NextHunk",
|
||||
Other: "select next hunk",
|
||||
}, &i18n.Message{
|
||||
ID: "PrevConflict",
|
||||
Other: "select previous conflict",
|
||||
}, &i18n.Message{
|
||||
ID: "NextConflict",
|
||||
Other: "select next conflict",
|
||||
}, &i18n.Message{
|
||||
ID: "SelectTop",
|
||||
Other: "select top hunk",
|
||||
}, &i18n.Message{
|
||||
ID: "SelectBottom",
|
||||
Other: "select bottom hunk",
|
||||
}, &i18n.Message{
|
||||
ID: "ScrollDown",
|
||||
Other: "scroll down",
|
||||
}, &i18n.Message{
|
||||
ID: "ScrollUp",
|
||||
Other: "scroll up",
|
||||
}, &i18n.Message{
|
||||
ID: "AmendCommitTitle",
|
||||
Other: "Amend Commit",
|
||||
}, &i18n.Message{
|
||||
ID: "AmendCommitPrompt",
|
||||
Other: "Are you sure you want to amend this commit with your staged files?",
|
||||
}, &i18n.Message{
|
||||
ID: "DeleteCommitTitle",
|
||||
Other: "Delete Commit",
|
||||
}, &i18n.Message{
|
||||
ID: "DeleteCommitPrompt",
|
||||
Other: "Are you sure you want to delete this commit?",
|
||||
}, &i18n.Message{
|
||||
ID: "SquashingStatus",
|
||||
Other: "squashing",
|
||||
}, &i18n.Message{
|
||||
ID: "FixingStatus",
|
||||
Other: "fixing up",
|
||||
}, &i18n.Message{
|
||||
ID: "DeletingStatus",
|
||||
Other: "deleting",
|
||||
}, &i18n.Message{
|
||||
ID: "MovingStatus",
|
||||
Other: "moving",
|
||||
}, &i18n.Message{
|
||||
ID: "RebasingStatus",
|
||||
Other: "rebasing",
|
||||
}, &i18n.Message{
|
||||
ID: "AmendingStatus",
|
||||
Other: "amending",
|
||||
}, &i18n.Message{
|
||||
ID: "CherryPickingStatus",
|
||||
Other: "cherry-picking",
|
||||
}, &i18n.Message{
|
||||
ID: "CommitFiles",
|
||||
Other: "Commit files",
|
||||
}, &i18n.Message{
|
||||
ID: "viewCommitFiles",
|
||||
Other: "view commit's files",
|
||||
}, &i18n.Message{
|
||||
ID: "CommitFilesTitle",
|
||||
Other: "Commit files",
|
||||
}, &i18n.Message{
|
||||
ID: "goBack",
|
||||
Other: "go back",
|
||||
}, &i18n.Message{
|
||||
ID: "NoCommiteFiles",
|
||||
Other: "No files for this commit",
|
||||
}, &i18n.Message{
|
||||
ID: "checkoutCommitFile",
|
||||
Other: "checkout file",
|
||||
}, &i18n.Message{
|
||||
ID: "discardOldFileChange",
|
||||
Other: "discard this commit's changes to this file",
|
||||
}, &i18n.Message{
|
||||
ID: "DiscardFileChangesTitle",
|
||||
Other: "Discard file changes",
|
||||
}, &i18n.Message{
|
||||
ID: "DiscardFileChangesPrompt",
|
||||
Other: "Are you sure you want to discard this commit's changes to this file? If this file was created in this commit, it will be deleted",
|
||||
}, &i18n.Message{
|
||||
ID: "DisabledForGPG",
|
||||
Other: "Feature not available for users using GPG",
|
||||
}, &i18n.Message{
|
||||
ID: "CreateRepo",
|
||||
Other: "Not in a git repository. Create a new git repository? (y/n): ",
|
||||
}, &i18n.Message{
|
||||
ID: "AutoStashTitle",
|
||||
Other: "Autostash?",
|
||||
}, &i18n.Message{
|
||||
ID: "AutoStashPrompt",
|
||||
Other: "You must stash and pop your changes to bring them across. Do this automatically? (enter/esc)",
|
||||
}, &i18n.Message{
|
||||
ID: "StashPrefix",
|
||||
Other: "Auto-stashing changes for ",
|
||||
}, &i18n.Message{
|
||||
ID: "viewDiscardOptions",
|
||||
Other: "view 'discard changes' options",
|
||||
}, &i18n.Message{
|
||||
ID: "cancel",
|
||||
Other: "cancel",
|
||||
}, &i18n.Message{
|
||||
ID: "discardAllChanges",
|
||||
Other: "discard all changes",
|
||||
}, &i18n.Message{
|
||||
ID: "discardUnstagedChanges",
|
||||
Other: "discard unstaged changes",
|
||||
}, &i18n.Message{
|
||||
ID: "discardAllChangesToAllFiles",
|
||||
Other: "nuke working tree",
|
||||
}, &i18n.Message{
|
||||
ID: "discardAnyUnstagedChanges",
|
||||
Other: "discard unstaged changes",
|
||||
}, &i18n.Message{
|
||||
ID: "discardUntrackedFiles",
|
||||
Other: "discard untracked files",
|
||||
}, &i18n.Message{
|
||||
ID: "hardReset",
|
||||
Other: "hard reset",
|
||||
}, &i18n.Message{
|
||||
ID: "viewResetOptions",
|
||||
Other: `view reset options`,
|
||||
}, &i18n.Message{
|
||||
ID: "createFixupCommit",
|
||||
Other: `create fixup commit for this commit`,
|
||||
}, &i18n.Message{
|
||||
ID: "squashAboveCommits",
|
||||
Other: `squash above commits`,
|
||||
}, &i18n.Message{
|
||||
ID: "SquashAboveCommits",
|
||||
Other: `Squash above commits`,
|
||||
}, &i18n.Message{
|
||||
ID: "SureSquashAboveCommits",
|
||||
Other: `Are you sure you want to squash all fixup! commits above {{.commit}}?`,
|
||||
}, &i18n.Message{
|
||||
ID: "CreateFixupCommit",
|
||||
Other: `Create fixup commit`,
|
||||
}, &i18n.Message{
|
||||
ID: "SureCreateFixupCommit",
|
||||
Other: `Are you sure you want to create a fixup! commit for commit {{.commit}}?`,
|
||||
}, &i18n.Message{
|
||||
ID: "executeCustomCommand",
|
||||
Other: "execute custom command",
|
||||
}, &i18n.Message{
|
||||
ID: "CustomCommand",
|
||||
Other: "Custom Command:",
|
||||
}, &i18n.Message{
|
||||
ID: "commitChangesWithoutHook",
|
||||
Other: "commit changes without pre-commit hook",
|
||||
}, &i18n.Message{
|
||||
ID: "SkipHookPrefixNotConfigured",
|
||||
Other: "You have not configured a commit message prefix for skipping hooks. Set `git.skipHookPrefix = 'WIP'` in your config",
|
||||
}, &i18n.Message{
|
||||
ID: "resetTo",
|
||||
Other: `reset to`,
|
||||
}, &i18n.Message{
|
||||
ID: "pressEnterToReturn",
|
||||
Other: "Press enter to return to lazygit",
|
||||
}, &i18n.Message{
|
||||
ID: "viewStashOptions",
|
||||
Other: "view stash options",
|
||||
}, &i18n.Message{
|
||||
ID: "stashAllChanges",
|
||||
Other: "stash changes",
|
||||
}, &i18n.Message{
|
||||
ID: "stashStagedChanges",
|
||||
Other: "stash staged changes",
|
||||
}, &i18n.Message{
|
||||
ID: "stashOptions",
|
||||
Other: "Stash options",
|
||||
}, &i18n.Message{
|
||||
ID: "notARepository",
|
||||
Other: "Error: must be run inside a git repository",
|
||||
}, &i18n.Message{
|
||||
ID: "jump",
|
||||
Other: "jump to panel",
|
||||
}, &i18n.Message{
|
||||
ID: "DiscardPatch",
|
||||
Other: "Discard Patch",
|
||||
}, &i18n.Message{
|
||||
ID: "DiscardPatchConfirm",
|
||||
Other: "You can only build a patch from one commit at a time. Discard current patch?",
|
||||
}, &i18n.Message{
|
||||
ID: "CantPatchWhileRebasingError",
|
||||
Other: "You cannot build a patch or run patch commands while in a merging or rebasing state",
|
||||
}, &i18n.Message{
|
||||
ID: "toggleAddToPatch",
|
||||
Other: "toggle file included in patch",
|
||||
}, &i18n.Message{
|
||||
ID: "PatchOptionsTitle",
|
||||
Other: "Patch Options",
|
||||
}, &i18n.Message{
|
||||
ID: "NoPatchError",
|
||||
Other: "No patch created yet. To start building a patch, use 'space' on a commit file or enter to add specific lines",
|
||||
}, &i18n.Message{
|
||||
ID: "enterFile",
|
||||
Other: "enter file to add selected lines to the patch",
|
||||
}, &i18n.Message{
|
||||
ID: "ExitLineByLineMode",
|
||||
Other: `exit line-by-line mode`,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -87,7 +87,7 @@ func detectLanguage(langDetector func() (string, error)) string {
|
||||
// setupLocalizer creates a new localizer using given userLang
|
||||
func setupLocalizer(log *logrus.Entry, userLang string) *Localizer {
|
||||
// create a i18n bundle that can be used to add translations and other things
|
||||
i18nBundle := &i18n.Bundle{DefaultLanguage: language.English}
|
||||
i18nBundle := i18n.NewBundle(language.English)
|
||||
|
||||
addBundles(log, i18nBundle)
|
||||
|
||||
|
||||
@@ -16,10 +16,13 @@ func getDummyLog() *logrus.Entry {
|
||||
log.Out = ioutil.Discard
|
||||
return log.WithField("test", "test")
|
||||
}
|
||||
|
||||
// TestNewLocalizer is a function.
|
||||
func TestNewLocalizer(t *testing.T) {
|
||||
assert.NotNil(t, NewLocalizer(getDummyLog()))
|
||||
}
|
||||
|
||||
// TestDetectLanguage is a function.
|
||||
func TestDetectLanguage(t *testing.T) {
|
||||
type scenario struct {
|
||||
langDetector func() (string, error)
|
||||
@@ -46,6 +49,7 @@ func TestDetectLanguage(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestLocalizer is a function.
|
||||
func TestLocalizer(t *testing.T) {
|
||||
type scenario struct {
|
||||
userLang string
|
||||
@@ -76,7 +80,7 @@ func TestLocalizer(t *testing.T) {
|
||||
},
|
||||
}))
|
||||
assert.Equal(t, "Diff", l.SLocalize("DiffTitle"))
|
||||
assert.Equal(t, "Weet je zeker dat je branch test wil verwijderen?", l.TemplateLocalize("DeleteBranchMessage", Teml{"selectedBranchName": "test"}))
|
||||
assert.Equal(t, "Weet je zeker dat je branch test wilt verwijderen?", l.TemplateLocalize("DeleteBranchMessage", Teml{"selectedBranchName": "test"}))
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -14,6 +14,9 @@ func addPolish(i18nObject *i18n.Bundle) error {
|
||||
}, &i18n.Message{
|
||||
ID: "DiffTitle",
|
||||
Other: "Różnice",
|
||||
}, &i18n.Message{
|
||||
ID: "LogTitle",
|
||||
Other: "Log",
|
||||
}, &i18n.Message{
|
||||
ID: "FilesTitle",
|
||||
Other: "Pliki",
|
||||
@@ -23,15 +26,48 @@ func addPolish(i18nObject *i18n.Bundle) error {
|
||||
}, &i18n.Message{
|
||||
ID: "CommitsTitle",
|
||||
Other: "Commity",
|
||||
}, &i18n.Message{
|
||||
ID: "CommitsDiffTitle",
|
||||
Other: "Commits (specific diff mode)",
|
||||
}, &i18n.Message{
|
||||
ID: "CommitsDiff",
|
||||
Other: "select commit to diff with another commit",
|
||||
}, &i18n.Message{
|
||||
ID: "StashTitle",
|
||||
Other: "Schowek",
|
||||
}, &i18n.Message{
|
||||
ID: "UnstagedChanges",
|
||||
Other: `Unstaged Changes`,
|
||||
}, &i18n.Message{
|
||||
ID: "StagedChanges",
|
||||
Other: `Staged Changes`,
|
||||
}, &i18n.Message{
|
||||
ID: "MergingMainTitle",
|
||||
Other: "Resolve merge conflicts",
|
||||
}, &i18n.Message{
|
||||
ID: "CommitMessage",
|
||||
Other: "Wiadomość commita",
|
||||
}, &i18n.Message{
|
||||
ID: "CredentialsUsername",
|
||||
Other: "Username",
|
||||
}, &i18n.Message{
|
||||
ID: "CredentialsPassword",
|
||||
Other: "Password",
|
||||
}, &i18n.Message{
|
||||
ID: "PassUnameWrong",
|
||||
Other: "Password and/or username wrong",
|
||||
}, &i18n.Message{
|
||||
ID: "CommitChanges",
|
||||
Other: "commituj zmiany",
|
||||
}, &i18n.Message{
|
||||
ID: "AmendLastCommit",
|
||||
Other: "zmień ostatnie zatwierdzenie",
|
||||
}, &i18n.Message{
|
||||
ID: "SureToAmend",
|
||||
Other: "Czy na pewno chcesz zmienić ostatnie zatwierdzenie? Możesz zmienić komunikat zatwierdzenia z panelu zatwierdzeń.",
|
||||
}, &i18n.Message{
|
||||
ID: "NoCommitToAmend",
|
||||
Other: "Nie ma zobowiązania do zmiany.",
|
||||
}, &i18n.Message{
|
||||
ID: "CommitChangesWithEditor",
|
||||
Other: "commituj zmiany używając edytora z gita",
|
||||
@@ -50,9 +86,6 @@ func addPolish(i18nObject *i18n.Bundle) error {
|
||||
}, &i18n.Message{
|
||||
ID: "execute",
|
||||
Other: "wykonaj",
|
||||
}, &i18n.Message{
|
||||
ID: "stashFiles",
|
||||
Other: "przechowaj pliki",
|
||||
}, &i18n.Message{
|
||||
ID: "open",
|
||||
Other: "otwórz",
|
||||
@@ -71,9 +104,6 @@ func addPolish(i18nObject *i18n.Bundle) error {
|
||||
}, &i18n.Message{
|
||||
ID: "refresh",
|
||||
Other: "odśwież",
|
||||
}, &i18n.Message{
|
||||
ID: "addPatch",
|
||||
Other: "dodaj łatkę",
|
||||
}, &i18n.Message{
|
||||
ID: "edit",
|
||||
Other: "edytuj",
|
||||
@@ -113,12 +143,12 @@ func addPolish(i18nObject *i18n.Bundle) error {
|
||||
}, &i18n.Message{
|
||||
ID: "PushWait",
|
||||
Other: "Wypychanie zmian...",
|
||||
}, &i18n.Message{
|
||||
ID: "FetchWait",
|
||||
Other: "Fetching...",
|
||||
}, &i18n.Message{
|
||||
ID: "FileNoMergeCons",
|
||||
Other: "Ten plik nie powoduje konfliktów scalania",
|
||||
}, &i18n.Message{
|
||||
ID: "SureResetHardHead",
|
||||
Other: "Jesteś pewny, że chcesz wykonać `reset --hard HEAD`? Możesz stracić wprowadzone zmiany",
|
||||
}, &i18n.Message{
|
||||
ID: "SureTo",
|
||||
Other: "Jesteś pewny, że chcesz {{.deleteVerb}} {{.fileName}} (stracisz swoje wprowadzone zmiany)?",
|
||||
@@ -149,6 +179,12 @@ func addPolish(i18nObject *i18n.Bundle) error {
|
||||
}, &i18n.Message{
|
||||
ID: "ForceDeleteBranchMessage",
|
||||
Other: "Na pewno wymusić usunięcie gałęzi {{.selectedBranchName}}?",
|
||||
}, &i18n.Message{
|
||||
ID: "rebaseBranch",
|
||||
Other: "rebase branch",
|
||||
}, &i18n.Message{
|
||||
ID: "CantRebaseOntoSelf",
|
||||
Other: "You cannot rebase a branch onto itself",
|
||||
}, &i18n.Message{
|
||||
ID: "CantMergeBranchIntoItself",
|
||||
Other: "Nie możesz scalić gałęzi do samej siebie",
|
||||
@@ -233,9 +269,6 @@ func addPolish(i18nObject *i18n.Bundle) error {
|
||||
}, &i18n.Message{
|
||||
ID: "PotentialErrInGetselectedCommit",
|
||||
Other: "potencjalny błąd w getSelected Commit (niedopasowane ui i stan)",
|
||||
}, &i18n.Message{
|
||||
ID: "NoCommitsThisBranch",
|
||||
Other: "Brak commitów dla tej gałęzi",
|
||||
}, &i18n.Message{
|
||||
ID: "Error",
|
||||
Other: "Błąd",
|
||||
@@ -293,21 +326,12 @@ func addPolish(i18nObject *i18n.Bundle) error {
|
||||
}, &i18n.Message{
|
||||
ID: "NoViewMachingNewLineFocusedSwitchStatement",
|
||||
Other: "Brak widoku pasującego do instrukcji przełączania newLineFocused",
|
||||
}, &i18n.Message{
|
||||
ID: "settingPreviewsViewTo",
|
||||
Other: "ustawianie poprzedniego widoku na: {{.oldViewName}}",
|
||||
}, &i18n.Message{
|
||||
ID: "newFocusedViewIs",
|
||||
Other: "nowy skupiony widok to {{.newFocusedView}}",
|
||||
}, &i18n.Message{
|
||||
ID: "CantCloseConfirmationPrompt",
|
||||
Other: "Nie można zamknąć monitu potwierdzenia: {{.error}}",
|
||||
}, &i18n.Message{
|
||||
ID: "NoChangedFiles",
|
||||
Other: "Brak zmienionych plików",
|
||||
}, &i18n.Message{
|
||||
ID: "ClearFilePanel",
|
||||
Other: "Wyczyść panel plików",
|
||||
}, &i18n.Message{
|
||||
ID: "MergeAborted",
|
||||
Other: "Scalanie anulowane",
|
||||
@@ -344,9 +368,6 @@ func addPolish(i18nObject *i18n.Bundle) error {
|
||||
}, &i18n.Message{
|
||||
ID: "AnonymousReportingPrompt",
|
||||
Other: "Włączyć anonimowe raportowanie błędów w celu pomocy w usprawnianiu lazygita (enter/esc)?",
|
||||
}, &i18n.Message{
|
||||
ID: "removeFile",
|
||||
Other: `usuń jeśli nie śledzony / przełącz jeśli śledzony`,
|
||||
}, &i18n.Message{
|
||||
ID: "editFile",
|
||||
Other: `edytuj plik`,
|
||||
@@ -359,15 +380,366 @@ func addPolish(i18nObject *i18n.Bundle) error {
|
||||
}, &i18n.Message{
|
||||
ID: "refreshFiles",
|
||||
Other: `odśwież pliki`,
|
||||
}, &i18n.Message{
|
||||
ID: "resetHard",
|
||||
Other: `zresetuj twardo`,
|
||||
}, &i18n.Message{
|
||||
ID: "mergeIntoCurrentBranch",
|
||||
Other: `scal do obecnej gałęzi`,
|
||||
}, &i18n.Message{
|
||||
ID: "ConfirmQuit",
|
||||
Other: `Na pewno chcesz wyjść z programu?`,
|
||||
}, &i18n.Message{
|
||||
ID: "UnsupportedGitService",
|
||||
Other: `Nieobsługiwana usługa git`,
|
||||
}, &i18n.Message{
|
||||
ID: "createPullRequest",
|
||||
Other: `utwórz żądanie wyciągnięcia`,
|
||||
}, &i18n.Message{
|
||||
ID: "NoBranchOnRemote",
|
||||
Other: `Ta gałąź nie istnieje na zdalnym. Najpierw musisz go odepchnąć na odległość.`,
|
||||
}, &i18n.Message{
|
||||
ID: "fetch",
|
||||
Other: `fetch`,
|
||||
}, &i18n.Message{
|
||||
ID: "NoAutomaticGitFetchTitle",
|
||||
Other: `No automatic git fetch`,
|
||||
}, &i18n.Message{
|
||||
ID: "NoAutomaticGitFetchBody",
|
||||
Other: `Lazygit can't use "git fetch" in a private repo use f in the branches panel to run "git fetch" manually`,
|
||||
}, &i18n.Message{
|
||||
ID: "StageLines",
|
||||
Other: `zatwierdź pojedyncze linie`,
|
||||
}, &i18n.Message{
|
||||
ID: "FileStagingRequirements",
|
||||
Other: `Można tylko zatwierdzić pojedyncze linie dla śledzonych plików z niezatwierdzonymi zmianami`,
|
||||
}, &i18n.Message{
|
||||
ID: "StagingTitle",
|
||||
Other: `Zatwierdzanie`,
|
||||
}, &i18n.Message{
|
||||
ID: "StageHunk",
|
||||
Other: `zatwierdź kawałek`,
|
||||
}, &i18n.Message{
|
||||
ID: "StageLine",
|
||||
Other: `zatwierdź linię`,
|
||||
}, &i18n.Message{
|
||||
ID: "ReturnToFilesPanel",
|
||||
Other: `wróć do panelu plików`,
|
||||
}, &i18n.Message{
|
||||
ID: "CantFindHunks",
|
||||
Other: `Nie można znaleźć żadnych kawałków w tej łatce`,
|
||||
}, &i18n.Message{
|
||||
ID: "CantFindHunk",
|
||||
Other: `Nie można znaleźć kawałka`,
|
||||
}, &i18n.Message{
|
||||
ID: "RebasingTitle",
|
||||
Other: "Rebasing",
|
||||
}, &i18n.Message{
|
||||
ID: "MergingTitle",
|
||||
Other: "Merging",
|
||||
}, &i18n.Message{
|
||||
ID: "ConfirmRebase",
|
||||
Other: "Are you sure you want to rebase {{.checkedOutBranch}} onto {{.selectedBranch}}?",
|
||||
}, &i18n.Message{
|
||||
ID: "ConfirmMerge",
|
||||
Other: "Are you sure you want to merge {{.selectedBranch}} into {{.checkedOutBranch}}?",
|
||||
}, &i18n.Message{}, &i18n.Message{
|
||||
ID: "FwdNoUpstream",
|
||||
Other: "Cannot fast-forward a branch with no upstream",
|
||||
}, &i18n.Message{
|
||||
ID: "FwdCommitsToPush",
|
||||
Other: "Cannot fast-forward a branch with commits to push",
|
||||
}, &i18n.Message{
|
||||
ID: "ErrorOccurred",
|
||||
Other: "An error occurred! Please create an issue at https://github.com/jesseduffield/lazygit/issues",
|
||||
}, &i18n.Message{
|
||||
ID: "MainTitle",
|
||||
Other: "Main",
|
||||
}, &i18n.Message{
|
||||
ID: "NormalTitle",
|
||||
Other: "Normal",
|
||||
}, &i18n.Message{
|
||||
ID: "softReset",
|
||||
Other: "soft reset",
|
||||
}, &i18n.Message{
|
||||
ID: "SureSquashThisCommit",
|
||||
Other: "Are you sure you want to squash this commit into the commit below?",
|
||||
}, &i18n.Message{
|
||||
ID: "Squash",
|
||||
Other: "Squash",
|
||||
}, &i18n.Message{
|
||||
ID: "pickCommit",
|
||||
Other: "pick commit (when mid-rebase)",
|
||||
}, &i18n.Message{
|
||||
ID: "revertCommit",
|
||||
Other: "revert commit",
|
||||
}, &i18n.Message{
|
||||
ID: "deleteCommit",
|
||||
Other: "delete commit",
|
||||
}, &i18n.Message{
|
||||
ID: "moveDownCommit",
|
||||
Other: "move commit down one",
|
||||
}, &i18n.Message{
|
||||
ID: "moveUpCommit",
|
||||
Other: "move commit up one",
|
||||
}, &i18n.Message{
|
||||
ID: "editCommit",
|
||||
Other: "edit commit",
|
||||
}, &i18n.Message{
|
||||
ID: "amendToCommit",
|
||||
Other: "amend commit with staged changes",
|
||||
}, &i18n.Message{
|
||||
ID: "FoundConflicts",
|
||||
Other: "Conflicts! To abort press 'esc', otherwise press 'enter'",
|
||||
}, &i18n.Message{
|
||||
ID: "FoundConflictsTitle",
|
||||
Other: "Auto-merge failed",
|
||||
}, &i18n.Message{
|
||||
ID: "Undo",
|
||||
Other: "undo",
|
||||
}, &i18n.Message{
|
||||
ID: "PickHunk",
|
||||
Other: "pick hunk",
|
||||
}, &i18n.Message{
|
||||
ID: "PickBothHunks",
|
||||
Other: "pick both hunks",
|
||||
}, &i18n.Message{
|
||||
ID: "ViewMergeRebaseOptions",
|
||||
Other: "view merge/rebase options",
|
||||
}, &i18n.Message{
|
||||
ID: "NotMergingOrRebasing",
|
||||
Other: "You are currently neither rebasing nor merging",
|
||||
}, &i18n.Message{
|
||||
ID: "RecentRepos",
|
||||
Other: "recent repositories",
|
||||
}, &i18n.Message{
|
||||
ID: "MergeOptionsTitle",
|
||||
Other: "Merge Options",
|
||||
}, &i18n.Message{
|
||||
ID: "RebaseOptionsTitle",
|
||||
Other: "Rebase Options",
|
||||
}, &i18n.Message{
|
||||
ID: "ConflictsResolved",
|
||||
Other: "all merge conflicts resolved. Continue?",
|
||||
}, &i18n.Message{
|
||||
ID: "NoRoom",
|
||||
Other: "Not enough room",
|
||||
}, &i18n.Message{
|
||||
ID: "YouAreHere",
|
||||
Other: "YOU ARE HERE",
|
||||
}, &i18n.Message{
|
||||
ID: "rewordNotSupported",
|
||||
Other: "rewording commits while interactively rebasing is not currently supported",
|
||||
}, &i18n.Message{
|
||||
ID: "cherryPickCopy",
|
||||
Other: "copy commit (cherry-pick)",
|
||||
}, &i18n.Message{
|
||||
ID: "cherryPickCopyRange",
|
||||
Other: "copy commit range (cherry-pick)",
|
||||
}, &i18n.Message{
|
||||
ID: "pasteCommits",
|
||||
Other: "paste commits (cherry-pick)",
|
||||
}, &i18n.Message{
|
||||
ID: "SureCherryPick",
|
||||
Other: "Are you sure you want to cherry-pick the copied commits onto this branch?",
|
||||
}, &i18n.Message{
|
||||
ID: "CherryPick",
|
||||
Other: "Cherry-Pick",
|
||||
}, &i18n.Message{
|
||||
ID: "CannotRebaseOntoFirstCommit",
|
||||
Other: "You cannot interactive rebase onto the first commit",
|
||||
}, &i18n.Message{
|
||||
ID: "CannotSquashOntoSecondCommit",
|
||||
Other: "You cannot squash/fixup onto the second commit",
|
||||
}, &i18n.Message{
|
||||
ID: "Donate",
|
||||
Other: "Donate",
|
||||
}, &i18n.Message{
|
||||
ID: "PrevLine",
|
||||
Other: "select previous line",
|
||||
}, &i18n.Message{
|
||||
ID: "NextLine",
|
||||
Other: "select next line",
|
||||
}, &i18n.Message{
|
||||
ID: "PrevHunk",
|
||||
Other: "select previous hunk",
|
||||
}, &i18n.Message{
|
||||
ID: "NextHunk",
|
||||
Other: "select next hunk",
|
||||
}, &i18n.Message{
|
||||
ID: "PrevConflict",
|
||||
Other: "select previous conflict",
|
||||
}, &i18n.Message{
|
||||
ID: "NextConflict",
|
||||
Other: "select next conflict",
|
||||
}, &i18n.Message{
|
||||
ID: "SelectTop",
|
||||
Other: "select top hunk",
|
||||
}, &i18n.Message{
|
||||
ID: "SelectBottom",
|
||||
Other: "select bottom hunk",
|
||||
}, &i18n.Message{
|
||||
ID: "ScrollDown",
|
||||
Other: "scroll down",
|
||||
}, &i18n.Message{
|
||||
ID: "ScrollUp",
|
||||
Other: "scroll up",
|
||||
}, &i18n.Message{
|
||||
ID: "AmendCommitTitle",
|
||||
Other: "Amend Commit",
|
||||
}, &i18n.Message{
|
||||
ID: "AmendCommitPrompt",
|
||||
Other: "Are you sure you want to amend this commit with your staged files?",
|
||||
}, &i18n.Message{
|
||||
ID: "DeleteCommitTitle",
|
||||
Other: "Delete Commit",
|
||||
}, &i18n.Message{
|
||||
ID: "DeleteCommitPrompt",
|
||||
Other: "Are you sure you want to delete this commit?",
|
||||
}, &i18n.Message{
|
||||
ID: "SquashingStatus",
|
||||
Other: "squashing",
|
||||
}, &i18n.Message{
|
||||
ID: "FixingStatus",
|
||||
Other: "fixing up",
|
||||
}, &i18n.Message{
|
||||
ID: "DeletingStatus",
|
||||
Other: "deleting",
|
||||
}, &i18n.Message{
|
||||
ID: "MovingStatus",
|
||||
Other: "moving",
|
||||
}, &i18n.Message{
|
||||
ID: "RebasingStatus",
|
||||
Other: "rebasing",
|
||||
}, &i18n.Message{
|
||||
ID: "AmendingStatus",
|
||||
Other: "amending",
|
||||
}, &i18n.Message{
|
||||
ID: "CherryPickingStatus",
|
||||
Other: "cherry-picking",
|
||||
}, &i18n.Message{
|
||||
ID: "CommitFiles",
|
||||
Other: "Commit files",
|
||||
}, &i18n.Message{
|
||||
ID: "viewCommitFiles",
|
||||
Other: "view commit's files",
|
||||
}, &i18n.Message{
|
||||
ID: "CommitFilesTitle",
|
||||
Other: "Commit files",
|
||||
}, &i18n.Message{
|
||||
ID: "goBack",
|
||||
Other: "go back",
|
||||
}, &i18n.Message{
|
||||
ID: "NoCommiteFiles",
|
||||
Other: "No files for this commit",
|
||||
}, &i18n.Message{
|
||||
ID: "checkoutCommitFile",
|
||||
Other: "checkout file",
|
||||
}, &i18n.Message{
|
||||
ID: "discardOldFileChange",
|
||||
Other: "discard this commit's changes to this file",
|
||||
}, &i18n.Message{
|
||||
ID: "DiscardFileChangesTitle",
|
||||
Other: "Discard file changes",
|
||||
}, &i18n.Message{
|
||||
ID: "DiscardFileChangesPrompt",
|
||||
Other: "Are you sure you want to discard this commit's changes to this file? If this file was created in this commit, it will be deleted",
|
||||
}, &i18n.Message{
|
||||
ID: "DisabledForGPG",
|
||||
Other: "Feature not available for users using GPG",
|
||||
}, &i18n.Message{
|
||||
ID: "CreateRepo",
|
||||
Other: "Not in a git repository. Create a new git repository? (y/n): ",
|
||||
}, &i18n.Message{
|
||||
ID: "AutoStashTitle",
|
||||
Other: "Autostash?",
|
||||
}, &i18n.Message{
|
||||
ID: "AutoStashPrompt",
|
||||
Other: "You must stash and pop your changes to bring them across. Do this automatically? (enter/esc)",
|
||||
}, &i18n.Message{
|
||||
ID: "StashPrefix",
|
||||
Other: "Auto-stashing changes for ",
|
||||
}, &i18n.Message{
|
||||
ID: "viewDiscardOptions",
|
||||
Other: "view 'discard changes' options",
|
||||
}, &i18n.Message{
|
||||
ID: "cancel",
|
||||
Other: "cancel",
|
||||
}, &i18n.Message{
|
||||
ID: "discardAllChanges",
|
||||
Other: "discard all changes",
|
||||
}, &i18n.Message{
|
||||
ID: "discardUnstagedChanges",
|
||||
Other: "discard unstaged changes",
|
||||
}, &i18n.Message{
|
||||
ID: "discardAllChangesToAllFiles",
|
||||
Other: "nuke working tree",
|
||||
}, &i18n.Message{
|
||||
ID: "discardAnyUnstagedChanges",
|
||||
Other: "discard unstaged changes",
|
||||
}, &i18n.Message{
|
||||
ID: "discardUntrackedFiles",
|
||||
Other: "discard untracked files",
|
||||
}, &i18n.Message{
|
||||
ID: "hardReset",
|
||||
Other: "hard reset",
|
||||
}, &i18n.Message{
|
||||
ID: "viewResetOptions",
|
||||
Other: `view reset options`,
|
||||
}, &i18n.Message{
|
||||
ID: "createFixupCommit",
|
||||
Other: `create fixup commit for this commit`,
|
||||
}, &i18n.Message{
|
||||
ID: "squashAboveCommits",
|
||||
Other: `squash above commits`,
|
||||
}, &i18n.Message{
|
||||
ID: "SquashAboveCommits",
|
||||
Other: `Squash above commits`,
|
||||
}, &i18n.Message{
|
||||
ID: "SureSquashAboveCommits",
|
||||
Other: `Are you sure you want to squash all fixup! commits above {{.commit}}?`,
|
||||
}, &i18n.Message{
|
||||
ID: "CreateFixupCommit",
|
||||
Other: `Create fixup commit`,
|
||||
}, &i18n.Message{
|
||||
ID: "SureCreateFixupCommit",
|
||||
Other: `Are you sure you want to create a fixup! commit for commit {{.commit}}?`,
|
||||
}, &i18n.Message{
|
||||
ID: "executeCustomCommand",
|
||||
Other: "execute custom command",
|
||||
}, &i18n.Message{
|
||||
ID: "CustomCommand",
|
||||
Other: "Custom Command:",
|
||||
}, &i18n.Message{
|
||||
ID: "commitChangesWithoutHook",
|
||||
Other: "commit changes without pre-commit hook",
|
||||
}, &i18n.Message{
|
||||
ID: "SkipHookPrefixNotConfigured",
|
||||
Other: "You have not configured a commit message prefix for skipping hooks. Set `git.skipHookPrefix = 'WIP'` in your config",
|
||||
}, &i18n.Message{
|
||||
ID: "resetTo",
|
||||
Other: `reset to`,
|
||||
}, &i18n.Message{
|
||||
ID: "pressEnterToReturn",
|
||||
Other: "Press enter to return to lazygit",
|
||||
}, &i18n.Message{
|
||||
ID: "viewStashOptions",
|
||||
Other: "view stash options",
|
||||
}, &i18n.Message{
|
||||
ID: "stashAllChanges",
|
||||
Other: "przechowaj pliki",
|
||||
}, &i18n.Message{
|
||||
ID: "stashStagedChanges",
|
||||
Other: "stash staged changes",
|
||||
}, &i18n.Message{
|
||||
ID: "stashOptions",
|
||||
Other: "Stash options",
|
||||
}, &i18n.Message{
|
||||
ID: "notARepository",
|
||||
Other: "Error: must be run inside a git repository",
|
||||
}, &i18n.Message{
|
||||
ID: "jump",
|
||||
Other: "jump to panel",
|
||||
}, &i18n.Message{
|
||||
ID: "ExitLineByLineMode",
|
||||
Other: `exit line-by-line mode`,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/go-errors/errors"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
|
||||
45
pkg/test/utils.go
Normal file
45
pkg/test/utils.go
Normal file
@@ -0,0 +1,45 @@
|
||||
package test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/mgutz/str"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// CommandSwapper takes a command, verifies that it is what it's expected to be
|
||||
// and then returns a replacement command that will actually be called by the os
|
||||
type CommandSwapper struct {
|
||||
Expect string
|
||||
Replace string
|
||||
}
|
||||
|
||||
// SwapCommand verifies the command is what we expected, and swaps it out for a different command
|
||||
func (i *CommandSwapper) SwapCommand(t *testing.T, cmd string, args []string) *exec.Cmd {
|
||||
splitCmd := str.ToArgv(i.Expect)
|
||||
assert.EqualValues(t, splitCmd[0], cmd, fmt.Sprintf("received command: %s %s", cmd, strings.Join(args, " ")))
|
||||
if len(splitCmd) > 1 {
|
||||
assert.EqualValues(t, splitCmd[1:], args, fmt.Sprintf("received command: %s %s", cmd, strings.Join(args, " ")))
|
||||
}
|
||||
|
||||
splitCmd = str.ToArgv(i.Replace)
|
||||
return exec.Command(splitCmd[0], splitCmd[1:]...)
|
||||
}
|
||||
|
||||
// CreateMockCommand creates a command function that will verify its receiving the right sequence of commands from lazygit
|
||||
func CreateMockCommand(t *testing.T, swappers []*CommandSwapper) func(cmd string, args ...string) *exec.Cmd {
|
||||
commandIndex := 0
|
||||
|
||||
return func(cmd string, args ...string) *exec.Cmd {
|
||||
var command *exec.Cmd
|
||||
if commandIndex > len(swappers)-1 {
|
||||
assert.Fail(t, fmt.Sprintf("too many commands run. This command was (%s %s)", cmd, strings.Join(args, " ")))
|
||||
}
|
||||
command = swappers[commandIndex].SwapCommand(t, cmd, args)
|
||||
commandIndex++
|
||||
return command
|
||||
}
|
||||
}
|
||||
104
pkg/theme/theme.go
Normal file
104
pkg/theme/theme.go
Normal file
@@ -0,0 +1,104 @@
|
||||
package theme
|
||||
|
||||
import (
|
||||
"github.com/fatih/color"
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
var (
|
||||
// DefaultTextColor is the default text color
|
||||
DefaultTextColor = color.FgWhite
|
||||
// DefaultHiTextColor is the default highlighted text color
|
||||
DefaultHiTextColor = color.FgHiWhite
|
||||
|
||||
// GocuiDefaultTextColor does the same as DefaultTextColor but this one only colors gocui default text colors
|
||||
GocuiDefaultTextColor gocui.Attribute
|
||||
|
||||
// ActiveBorderColor is the border color of the active frame
|
||||
ActiveBorderColor gocui.Attribute
|
||||
|
||||
// InactiveBorderColor is the border color of the inactive active frames
|
||||
InactiveBorderColor gocui.Attribute
|
||||
)
|
||||
|
||||
// UpdateTheme updates all theme variables
|
||||
func UpdateTheme(userConfig *viper.Viper) {
|
||||
ActiveBorderColor = getColor(userConfig.GetStringSlice("gui.theme.activeBorderColor"))
|
||||
InactiveBorderColor = getColor(userConfig.GetStringSlice("gui.theme.inactiveBorderColor"))
|
||||
|
||||
isLightTheme := userConfig.GetBool("gui.theme.lightTheme")
|
||||
if isLightTheme {
|
||||
DefaultTextColor = color.FgBlack
|
||||
DefaultHiTextColor = color.FgHiBlack
|
||||
GocuiDefaultTextColor = gocui.ColorBlack
|
||||
} else {
|
||||
DefaultTextColor = color.FgWhite
|
||||
DefaultHiTextColor = color.FgHiWhite
|
||||
GocuiDefaultTextColor = gocui.ColorWhite
|
||||
}
|
||||
}
|
||||
|
||||
// getAttribute gets the gocui color attribute from the string
|
||||
func getAttribute(key string) gocui.Attribute {
|
||||
colorMap := map[string]gocui.Attribute{
|
||||
"default": gocui.ColorDefault,
|
||||
"black": gocui.ColorBlack,
|
||||
"red": gocui.ColorRed,
|
||||
"green": gocui.ColorGreen,
|
||||
"yellow": gocui.ColorYellow,
|
||||
"blue": gocui.ColorBlue,
|
||||
"magenta": gocui.ColorMagenta,
|
||||
"cyan": gocui.ColorCyan,
|
||||
"white": gocui.ColorWhite,
|
||||
"bold": gocui.AttrBold,
|
||||
"reverse": gocui.AttrReverse,
|
||||
"underline": gocui.AttrUnderline,
|
||||
}
|
||||
value, present := colorMap[key]
|
||||
if present {
|
||||
return value
|
||||
}
|
||||
return gocui.ColorWhite
|
||||
}
|
||||
|
||||
// getColor bitwise OR's a list of attributes obtained via the given keys
|
||||
func getColor(keys []string) gocui.Attribute {
|
||||
var attribute gocui.Attribute
|
||||
for _, key := range keys {
|
||||
attribute |= getAttribute(key)
|
||||
}
|
||||
return attribute
|
||||
}
|
||||
|
||||
// GetAttribute gets the gocui color attribute from the string
|
||||
func GetAttribute(key string) gocui.Attribute {
|
||||
colorMap := map[string]gocui.Attribute{
|
||||
"default": gocui.ColorDefault,
|
||||
"black": gocui.ColorBlack,
|
||||
"red": gocui.ColorRed,
|
||||
"green": gocui.ColorGreen,
|
||||
"yellow": gocui.ColorYellow,
|
||||
"blue": gocui.ColorBlue,
|
||||
"magenta": gocui.ColorMagenta,
|
||||
"cyan": gocui.ColorCyan,
|
||||
"white": gocui.ColorWhite,
|
||||
"bold": gocui.AttrBold,
|
||||
"reverse": gocui.AttrReverse,
|
||||
"underline": gocui.AttrUnderline,
|
||||
}
|
||||
value, present := colorMap[key]
|
||||
if present {
|
||||
return value
|
||||
}
|
||||
return gocui.ColorWhite
|
||||
}
|
||||
|
||||
// GetColor bitwise OR's a list of attributes obtained via the given keys
|
||||
func GetColor(keys []string) gocui.Attribute {
|
||||
var attribute gocui.Attribute
|
||||
for _, key := range keys {
|
||||
attribute |= GetAttribute(key)
|
||||
}
|
||||
return attribute
|
||||
}
|
||||
@@ -2,9 +2,7 @@ package updates
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
@@ -13,9 +11,11 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-errors/errors"
|
||||
|
||||
"github.com/kardianos/osext"
|
||||
|
||||
getter "github.com/jesseduffield/go-getter"
|
||||
"github.com/jesseduffield/go-getter"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands"
|
||||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
"github.com/jesseduffield/lazygit/pkg/i18n"
|
||||
@@ -36,25 +36,24 @@ type Updaterer interface {
|
||||
Update()
|
||||
}
|
||||
|
||||
var (
|
||||
projectUrl = "https://github.com/jesseduffield/lazygit"
|
||||
const (
|
||||
PROJECT_URL = "https://github.com/jesseduffield/lazygit"
|
||||
)
|
||||
|
||||
// NewUpdater creates a new updater
|
||||
func NewUpdater(log *logrus.Entry, config config.AppConfigurer, osCommand *commands.OSCommand, tr *i18n.Localizer) (*Updater, error) {
|
||||
contextLogger := log.WithField("context", "updates")
|
||||
|
||||
updater := &Updater{
|
||||
return &Updater{
|
||||
Log: contextLogger,
|
||||
Config: config,
|
||||
OSCommand: osCommand,
|
||||
Tr: tr,
|
||||
}
|
||||
return updater, nil
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (u *Updater) getLatestVersionNumber() (string, error) {
|
||||
req, err := http.NewRequest("GET", projectUrl+"/releases/latest", nil)
|
||||
req, err := http.NewRequest("GET", PROJECT_URL+"/releases/latest", nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -65,17 +64,16 @@ func (u *Updater) getLatestVersionNumber() (string, error) {
|
||||
return "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
|
||||
dec := json.NewDecoder(resp.Body)
|
||||
data := struct {
|
||||
TagName string `json:"tag_name"`
|
||||
}{}
|
||||
if err := dec.Decode(&data); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
byt := []byte(body)
|
||||
var dat map[string]interface{}
|
||||
if err := json.Unmarshal(byt, &dat); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return dat["tag_name"].(string), nil
|
||||
return data.TagName, nil
|
||||
}
|
||||
|
||||
// RecordLastUpdateCheck records last time an update check was performed
|
||||
@@ -225,14 +223,14 @@ func (u *Updater) getBinaryUrl(newVersion string) (string, error) {
|
||||
}
|
||||
url := fmt.Sprintf(
|
||||
"%s/releases/download/%s/lazygit_%s_%s_%s.%s",
|
||||
projectUrl,
|
||||
PROJECT_URL,
|
||||
newVersion,
|
||||
newVersion[1:],
|
||||
u.mappedOs(runtime.GOOS),
|
||||
u.mappedArch(runtime.GOARCH),
|
||||
extension,
|
||||
)
|
||||
u.Log.Info("url for latest release is " + url)
|
||||
u.Log.Info("Url for latest release is " + url)
|
||||
return url, nil
|
||||
}
|
||||
|
||||
@@ -251,7 +249,7 @@ func (u *Updater) update(newVersion string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
u.Log.Info("updating with url " + rawUrl)
|
||||
u.Log.Info("Updating with url " + rawUrl)
|
||||
return u.downloadAndInstall(rawUrl)
|
||||
}
|
||||
|
||||
@@ -262,15 +260,11 @@ func (u *Updater) downloadAndInstall(rawUrl string) error {
|
||||
}
|
||||
|
||||
g := new(getter.HttpGetter)
|
||||
tempDir, err := ioutil.TempDir("", "lazygit")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer os.RemoveAll(tempDir)
|
||||
u.Log.Info("temp directory is " + tempDir)
|
||||
configDir := u.Config.GetUserConfigDir()
|
||||
u.Log.Info("Download directory is " + configDir)
|
||||
|
||||
// Get it!
|
||||
if err := g.Get(tempDir, url); err != nil {
|
||||
if err := g.Get(configDir, url); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -279,14 +273,14 @@ func (u *Updater) downloadAndInstall(rawUrl string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
u.Log.Info("binary path is " + binaryPath)
|
||||
u.Log.Info("Binary path is " + binaryPath)
|
||||
|
||||
binaryName := filepath.Base(binaryPath)
|
||||
u.Log.Info("binary name is " + binaryName)
|
||||
u.Log.Info("Binary name is " + binaryName)
|
||||
|
||||
// Verify the main file exists
|
||||
tempPath := filepath.Join(tempDir, binaryName)
|
||||
u.Log.Info("temp path to binary is " + tempPath)
|
||||
tempPath := filepath.Join(configDir, binaryName)
|
||||
u.Log.Info("Temp path to binary is " + tempPath)
|
||||
if _, err := os.Stat(tempPath); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -296,7 +290,7 @@ func (u *Updater) downloadAndInstall(rawUrl string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
u.Log.Info("update complete!")
|
||||
u.Log.Info("Update complete!")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-errors/errors"
|
||||
|
||||
"github.com/fatih/color"
|
||||
)
|
||||
|
||||
@@ -28,10 +33,11 @@ func SplitLines(multilineString string) []string {
|
||||
|
||||
// WithPadding pads a string as much as you want
|
||||
func WithPadding(str string, padding int) string {
|
||||
if padding-len(str) < 0 {
|
||||
uncoloredStr := Decolorise(str)
|
||||
if padding < len(uncoloredStr) {
|
||||
return str
|
||||
}
|
||||
return str + strings.Repeat(" ", padding-len(str))
|
||||
return str + strings.Repeat(" ", padding-len(uncoloredStr))
|
||||
}
|
||||
|
||||
// ColoredString takes a string and a colour attribute and returns a colored
|
||||
@@ -99,3 +105,197 @@ func ResolvePlaceholderString(str string, arguments map[string]string) string {
|
||||
}
|
||||
return str
|
||||
}
|
||||
|
||||
// Min returns the minimum of two integers
|
||||
func Min(x, y int) int {
|
||||
if x < y {
|
||||
return x
|
||||
}
|
||||
return y
|
||||
}
|
||||
|
||||
type Displayable interface {
|
||||
GetDisplayStrings(bool) []string
|
||||
}
|
||||
|
||||
// RenderList takes a slice of items, confirms they implement the Displayable
|
||||
// interface, then generates a list of their displaystrings to write to a panel's
|
||||
// buffer
|
||||
func RenderList(slice interface{}, isFocused bool) (string, error) {
|
||||
s := reflect.ValueOf(slice)
|
||||
if s.Kind() != reflect.Slice {
|
||||
return "", errors.New("RenderList given a non-slice type")
|
||||
}
|
||||
|
||||
displayables := make([]Displayable, s.Len())
|
||||
|
||||
for i := 0; i < s.Len(); i++ {
|
||||
value, ok := s.Index(i).Interface().(Displayable)
|
||||
if !ok {
|
||||
return "", errors.New("item does not implement the Displayable interface")
|
||||
}
|
||||
displayables[i] = value
|
||||
}
|
||||
|
||||
return renderDisplayableList(displayables, isFocused)
|
||||
}
|
||||
|
||||
// renderDisplayableList takes a list of displayable items, obtains their display
|
||||
// strings via GetDisplayStrings() and then returns a single string containing
|
||||
// each item's string representation on its own line, with appropriate horizontal
|
||||
// padding between the item's own strings
|
||||
func renderDisplayableList(items []Displayable, isFocused bool) (string, error) {
|
||||
if len(items) == 0 {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
stringArrays := getDisplayStringArrays(items, isFocused)
|
||||
|
||||
if !displayArraysAligned(stringArrays) {
|
||||
return "", errors.New("Each item must return the same number of strings to display")
|
||||
}
|
||||
|
||||
padWidths := getPadWidths(stringArrays)
|
||||
paddedDisplayStrings := getPaddedDisplayStrings(stringArrays, padWidths)
|
||||
|
||||
return strings.Join(paddedDisplayStrings, "\n"), nil
|
||||
}
|
||||
|
||||
// Decolorise strips a string of color
|
||||
func Decolorise(str string) string {
|
||||
re := regexp.MustCompile(`\x1B\[([0-9]{1,2}(;[0-9]{1,2})?)?[m|K]`)
|
||||
return re.ReplaceAllString(str, "")
|
||||
}
|
||||
|
||||
func getPadWidths(stringArrays [][]string) []int {
|
||||
if len(stringArrays[0]) <= 1 {
|
||||
return []int{}
|
||||
}
|
||||
padWidths := make([]int, len(stringArrays[0])-1)
|
||||
for i := range padWidths {
|
||||
for _, strings := range stringArrays {
|
||||
uncoloredString := Decolorise(strings[i])
|
||||
if len(uncoloredString) > padWidths[i] {
|
||||
padWidths[i] = len(uncoloredString)
|
||||
}
|
||||
}
|
||||
}
|
||||
return padWidths
|
||||
}
|
||||
|
||||
func getPaddedDisplayStrings(stringArrays [][]string, padWidths []int) []string {
|
||||
paddedDisplayStrings := make([]string, len(stringArrays))
|
||||
for i, stringArray := range stringArrays {
|
||||
if len(stringArray) == 0 {
|
||||
continue
|
||||
}
|
||||
for j, padWidth := range padWidths {
|
||||
paddedDisplayStrings[i] += WithPadding(stringArray[j], padWidth) + " "
|
||||
}
|
||||
paddedDisplayStrings[i] += stringArray[len(padWidths)]
|
||||
}
|
||||
return paddedDisplayStrings
|
||||
}
|
||||
|
||||
// displayArraysAligned returns true if every string array returned from our
|
||||
// list of displayables has the same length
|
||||
func displayArraysAligned(stringArrays [][]string) bool {
|
||||
for _, strings := range stringArrays {
|
||||
if len(strings) != len(stringArrays[0]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func getDisplayStringArrays(displayables []Displayable, isFocused bool) [][]string {
|
||||
stringArrays := make([][]string, len(displayables))
|
||||
for i, item := range displayables {
|
||||
stringArrays[i] = item.GetDisplayStrings(isFocused)
|
||||
}
|
||||
return stringArrays
|
||||
}
|
||||
|
||||
// IncludesString if the list contains the string
|
||||
func IncludesString(list []string, a string) bool {
|
||||
for _, b := range list {
|
||||
if b == a {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// IncludesInt if the list contains the Int
|
||||
func IncludesInt(list []int, a int) bool {
|
||||
for _, b := range list {
|
||||
if b == a {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// NextIndex returns the index of the element that comes after the given number
|
||||
func NextIndex(numbers []int, currentNumber int) int {
|
||||
for index, number := range numbers {
|
||||
if number > currentNumber {
|
||||
return index
|
||||
}
|
||||
}
|
||||
return len(numbers) - 1
|
||||
}
|
||||
|
||||
// PrevIndex returns the index that comes before the given number, cycling if we reach the end
|
||||
func PrevIndex(numbers []int, currentNumber int) int {
|
||||
end := len(numbers) - 1
|
||||
for i := end; i >= 0; i-- {
|
||||
if numbers[i] < currentNumber {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func AsJson(i interface{}) string {
|
||||
bytes, _ := json.MarshalIndent(i, "", " ")
|
||||
return string(bytes)
|
||||
}
|
||||
|
||||
// UnionInt returns the union of two int arrays
|
||||
func UnionInt(a, b []int) []int {
|
||||
m := make(map[int]bool)
|
||||
|
||||
for _, item := range a {
|
||||
m[item] = true
|
||||
}
|
||||
|
||||
for _, item := range b {
|
||||
if _, ok := m[item]; !ok {
|
||||
// this does not mutate the original a slice
|
||||
// though it does mutate the backing array I believe
|
||||
// but that doesn't matter because if you later want to append to the
|
||||
// original a it must see that the backing array has been changed
|
||||
// and create a new one
|
||||
a = append(a, item)
|
||||
}
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
// DifferenceInt returns the difference of two int arrays
|
||||
func DifferenceInt(a, b []int) []int {
|
||||
result := []int{}
|
||||
m := make(map[int]bool)
|
||||
|
||||
for _, item := range b {
|
||||
m[item] = true
|
||||
}
|
||||
|
||||
for _, item := range a {
|
||||
if _, ok := m[item]; !ok {
|
||||
result = append(result, item)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// TestSplitLines is a function.
|
||||
func TestSplitLines(t *testing.T) {
|
||||
type scenario struct {
|
||||
multilineString string
|
||||
@@ -35,6 +36,7 @@ func TestSplitLines(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestWithPadding is a function.
|
||||
func TestWithPadding(t *testing.T) {
|
||||
type scenario struct {
|
||||
str string
|
||||
@@ -60,6 +62,7 @@ func TestWithPadding(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestTrimTrailingNewline is a function.
|
||||
func TestTrimTrailingNewline(t *testing.T) {
|
||||
type scenario struct {
|
||||
str string
|
||||
@@ -82,6 +85,7 @@ func TestTrimTrailingNewline(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestNormalizeLinefeeds is a function.
|
||||
func TestNormalizeLinefeeds(t *testing.T) {
|
||||
type scenario struct {
|
||||
byteArray []byte
|
||||
@@ -115,6 +119,7 @@ func TestNormalizeLinefeeds(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestResolvePlaceholderString is a function.
|
||||
func TestResolvePlaceholderString(t *testing.T) {
|
||||
type scenario struct {
|
||||
templateString string
|
||||
@@ -164,6 +169,407 @@ func TestResolvePlaceholderString(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
assert.EqualValues(t, string(s.expected), ResolvePlaceholderString(s.templateString, s.arguments))
|
||||
assert.EqualValues(t, s.expected, ResolvePlaceholderString(s.templateString, s.arguments))
|
||||
}
|
||||
}
|
||||
|
||||
// TestDisplayArraysAligned is a function.
|
||||
func TestDisplayArraysAligned(t *testing.T) {
|
||||
type scenario struct {
|
||||
input [][]string
|
||||
expected bool
|
||||
}
|
||||
|
||||
scenarios := []scenario{
|
||||
{
|
||||
[][]string{{"", ""}, {"", ""}},
|
||||
true,
|
||||
},
|
||||
{
|
||||
[][]string{{""}, {"", ""}},
|
||||
false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
assert.EqualValues(t, s.expected, displayArraysAligned(s.input))
|
||||
}
|
||||
}
|
||||
|
||||
type myDisplayable struct {
|
||||
strings []string
|
||||
}
|
||||
|
||||
type myStruct struct{}
|
||||
|
||||
// GetDisplayStrings is a function.
|
||||
func (d *myDisplayable) GetDisplayStrings(isFocused bool) []string {
|
||||
if isFocused {
|
||||
return append(d.strings, "blah")
|
||||
}
|
||||
return d.strings
|
||||
}
|
||||
|
||||
// TestGetDisplayStringArrays is a function.
|
||||
func TestGetDisplayStringArrays(t *testing.T) {
|
||||
type scenario struct {
|
||||
input []Displayable
|
||||
isFocused bool
|
||||
expected [][]string
|
||||
}
|
||||
|
||||
scenarios := []scenario{
|
||||
{
|
||||
[]Displayable{
|
||||
Displayable(&myDisplayable{[]string{"a", "b"}}),
|
||||
Displayable(&myDisplayable{[]string{"c", "d"}}),
|
||||
},
|
||||
false,
|
||||
[][]string{{"a", "b"}, {"c", "d"}},
|
||||
},
|
||||
{
|
||||
[]Displayable{
|
||||
Displayable(&myDisplayable{[]string{"a", "b"}}),
|
||||
Displayable(&myDisplayable{[]string{"c", "d"}}),
|
||||
},
|
||||
true,
|
||||
[][]string{{"a", "b", "blah"}, {"c", "d", "blah"}},
|
||||
},
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
assert.EqualValues(t, s.expected, getDisplayStringArrays(s.input, s.isFocused))
|
||||
}
|
||||
}
|
||||
|
||||
// TestRenderDisplayableList is a function.
|
||||
func TestRenderDisplayableList(t *testing.T) {
|
||||
type scenario struct {
|
||||
input []Displayable
|
||||
isFocused bool
|
||||
expectedString string
|
||||
expectedErrorMessage string
|
||||
}
|
||||
|
||||
scenarios := []scenario{
|
||||
{
|
||||
[]Displayable{
|
||||
Displayable(&myDisplayable{[]string{}}),
|
||||
Displayable(&myDisplayable{[]string{}}),
|
||||
},
|
||||
false,
|
||||
"\n",
|
||||
"",
|
||||
},
|
||||
{
|
||||
[]Displayable{
|
||||
Displayable(&myDisplayable{[]string{"aa", "b"}}),
|
||||
Displayable(&myDisplayable{[]string{"c", "d"}}),
|
||||
},
|
||||
false,
|
||||
"aa b\nc d",
|
||||
"",
|
||||
},
|
||||
{
|
||||
[]Displayable{
|
||||
Displayable(&myDisplayable{[]string{"a"}}),
|
||||
Displayable(&myDisplayable{[]string{"b", "c"}}),
|
||||
},
|
||||
false,
|
||||
"",
|
||||
"Each item must return the same number of strings to display",
|
||||
},
|
||||
{
|
||||
[]Displayable{
|
||||
Displayable(&myDisplayable{[]string{"a"}}),
|
||||
Displayable(&myDisplayable{[]string{"b"}}),
|
||||
},
|
||||
true,
|
||||
"a blah\nb blah",
|
||||
"",
|
||||
},
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
str, err := renderDisplayableList(s.input, s.isFocused)
|
||||
assert.EqualValues(t, s.expectedString, str)
|
||||
if s.expectedErrorMessage != "" {
|
||||
assert.EqualError(t, err, s.expectedErrorMessage)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestRenderList is a function.
|
||||
func TestRenderList(t *testing.T) {
|
||||
type scenario struct {
|
||||
input interface{}
|
||||
isFocused bool
|
||||
expectedString string
|
||||
expectedErrorMessage string
|
||||
}
|
||||
|
||||
scenarios := []scenario{
|
||||
{
|
||||
[]*myDisplayable{
|
||||
{[]string{"aa", "b"}},
|
||||
{[]string{"c", "d"}},
|
||||
},
|
||||
false,
|
||||
"aa b\nc d",
|
||||
"",
|
||||
},
|
||||
{
|
||||
[]*myStruct{
|
||||
{},
|
||||
{},
|
||||
},
|
||||
false,
|
||||
"",
|
||||
"item does not implement the Displayable interface",
|
||||
},
|
||||
{
|
||||
&myStruct{},
|
||||
false,
|
||||
"",
|
||||
"RenderList given a non-slice type",
|
||||
},
|
||||
{
|
||||
[]*myDisplayable{
|
||||
{[]string{"a"}},
|
||||
},
|
||||
true,
|
||||
"a blah",
|
||||
"",
|
||||
},
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
str, err := RenderList(s.input, s.isFocused)
|
||||
assert.EqualValues(t, s.expectedString, str)
|
||||
if s.expectedErrorMessage != "" {
|
||||
assert.EqualError(t, err, s.expectedErrorMessage)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestGetPaddedDisplayStrings is a function.
|
||||
func TestGetPaddedDisplayStrings(t *testing.T) {
|
||||
type scenario struct {
|
||||
stringArrays [][]string
|
||||
padWidths []int
|
||||
expected []string
|
||||
}
|
||||
|
||||
scenarios := []scenario{
|
||||
{
|
||||
[][]string{{"a", "b"}, {"c", "d"}},
|
||||
[]int{1},
|
||||
[]string{"a b", "c d"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
assert.EqualValues(t, s.expected, getPaddedDisplayStrings(s.stringArrays, s.padWidths))
|
||||
}
|
||||
}
|
||||
|
||||
// TestGetPadWidths is a function.
|
||||
func TestGetPadWidths(t *testing.T) {
|
||||
type scenario struct {
|
||||
stringArrays [][]string
|
||||
expected []int
|
||||
}
|
||||
|
||||
scenarios := []scenario{
|
||||
{
|
||||
[][]string{{""}, {""}},
|
||||
[]int{},
|
||||
},
|
||||
{
|
||||
[][]string{{"a"}, {""}},
|
||||
[]int{},
|
||||
},
|
||||
{
|
||||
[][]string{{"aa", "b", "ccc"}, {"c", "d", "e"}},
|
||||
[]int{2, 1},
|
||||
},
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
assert.EqualValues(t, s.expected, getPadWidths(s.stringArrays))
|
||||
}
|
||||
}
|
||||
|
||||
// TestMin is a function.
|
||||
func TestMin(t *testing.T) {
|
||||
type scenario struct {
|
||||
a int
|
||||
b int
|
||||
expected int
|
||||
}
|
||||
|
||||
scenarios := []scenario{
|
||||
{
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
},
|
||||
{
|
||||
1,
|
||||
2,
|
||||
1,
|
||||
},
|
||||
{
|
||||
2,
|
||||
1,
|
||||
1,
|
||||
},
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
assert.EqualValues(t, s.expected, Min(s.a, s.b))
|
||||
}
|
||||
}
|
||||
|
||||
// TestIncludesString is a function.
|
||||
func TestIncludesString(t *testing.T) {
|
||||
type scenario struct {
|
||||
list []string
|
||||
element string
|
||||
expected bool
|
||||
}
|
||||
|
||||
scenarios := []scenario{
|
||||
{
|
||||
[]string{"a", "b"},
|
||||
"a",
|
||||
true,
|
||||
},
|
||||
{
|
||||
[]string{"a", "b"},
|
||||
"c",
|
||||
false,
|
||||
},
|
||||
{
|
||||
[]string{"a", "b"},
|
||||
"",
|
||||
false,
|
||||
},
|
||||
{
|
||||
[]string{""},
|
||||
"",
|
||||
true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
assert.EqualValues(t, s.expected, IncludesString(s.list, s.element))
|
||||
}
|
||||
}
|
||||
|
||||
func TestNextIndex(t *testing.T) {
|
||||
type scenario struct {
|
||||
testName string
|
||||
list []int
|
||||
element int
|
||||
expected int
|
||||
}
|
||||
|
||||
scenarios := []scenario{
|
||||
{
|
||||
// I'm not really fussed about how it behaves here
|
||||
"no elements",
|
||||
[]int{},
|
||||
1,
|
||||
-1,
|
||||
},
|
||||
{
|
||||
"one element",
|
||||
[]int{1},
|
||||
1,
|
||||
0,
|
||||
},
|
||||
{
|
||||
"two elements",
|
||||
[]int{1, 2},
|
||||
1,
|
||||
1,
|
||||
},
|
||||
{
|
||||
"two elements, giving second one",
|
||||
[]int{1, 2},
|
||||
2,
|
||||
1,
|
||||
},
|
||||
{
|
||||
"three elements, giving second one",
|
||||
[]int{1, 2, 3},
|
||||
2,
|
||||
2,
|
||||
},
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
t.Run(s.testName, func(t *testing.T) {
|
||||
assert.EqualValues(t, s.expected, NextIndex(s.list, s.element))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrevIndex(t *testing.T) {
|
||||
type scenario struct {
|
||||
testName string
|
||||
list []int
|
||||
element int
|
||||
expected int
|
||||
}
|
||||
|
||||
scenarios := []scenario{
|
||||
{
|
||||
// I'm not really fussed about how it behaves here
|
||||
"no elements",
|
||||
[]int{},
|
||||
1,
|
||||
0,
|
||||
},
|
||||
{
|
||||
"one element",
|
||||
[]int{1},
|
||||
1,
|
||||
0,
|
||||
},
|
||||
{
|
||||
"two elements",
|
||||
[]int{1, 2},
|
||||
1,
|
||||
0,
|
||||
},
|
||||
{
|
||||
"three elements, giving second one",
|
||||
[]int{1, 2, 3},
|
||||
2,
|
||||
0,
|
||||
},
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
t.Run(s.testName, func(t *testing.T) {
|
||||
assert.EqualValues(t, s.expected, PrevIndex(s.list, s.element))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAsJson(t *testing.T) {
|
||||
type myStruct struct {
|
||||
a string
|
||||
}
|
||||
|
||||
output := AsJson(&myStruct{a: "foo"})
|
||||
|
||||
// no idea why this is returning empty hashes but it's works in the app ¯\_(ツ)_/¯
|
||||
assert.EqualValues(t, "{}", output)
|
||||
}
|
||||
|
||||
5
scripts/bump_modules.sh
Executable file
5
scripts/bump_modules.sh
Executable file
@@ -0,0 +1,5 @@
|
||||
#!/bin/sh
|
||||
|
||||
GO111MODULE=on
|
||||
mv go.mod /tmp/
|
||||
go mod init
|
||||
@@ -1,54 +1,140 @@
|
||||
// run:
|
||||
// LANG=en go run generate_cheatsheet.go
|
||||
// to generate Keybindings_en.md file in current directory
|
||||
// change LANG to generate cheatsheet in different language (if supported)
|
||||
// This "script" generates a file called Keybindings_{{.LANG}}.md
|
||||
// in current working directory.
|
||||
//
|
||||
// The content of this generated file is a keybindings cheatsheet.
|
||||
//
|
||||
// To generate cheatsheet in english run:
|
||||
// LANG=en go run scripts/generate_cheatsheet.go
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/app"
|
||||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui"
|
||||
)
|
||||
|
||||
type bindingSection struct {
|
||||
title string
|
||||
bindings []*gui.Binding
|
||||
}
|
||||
|
||||
func main() {
|
||||
appConfig, _ := config.NewAppConfig("", "", "", "", "", new(bool))
|
||||
a, _ := app.NewApp(appConfig)
|
||||
lang := a.Tr.GetLanguage()
|
||||
name := "Keybindings_" + lang + ".md"
|
||||
bindings := a.Gui.GetKeybindings()
|
||||
padWidth := a.Gui.GetMaxKeyLength(bindings)
|
||||
file, _ := os.Create(name)
|
||||
current := "v"
|
||||
content := ""
|
||||
title := ""
|
||||
langs := []string{"pl", "nl", "en"}
|
||||
mConfig, _ := config.NewAppConfig("", "", "", "", "", true)
|
||||
|
||||
file.WriteString("# Lazygit " + a.Tr.SLocalize("menu"))
|
||||
|
||||
for _, binding := range bindings {
|
||||
if key := a.Gui.GetKey(binding); key != "" && (binding.Description != "" || key == "x") {
|
||||
if binding.ViewName != current {
|
||||
current = binding.ViewName
|
||||
if current == "" {
|
||||
title = a.Tr.SLocalize("GlobalTitle")
|
||||
} else {
|
||||
title = a.Tr.SLocalize(strings.Title(current) + "Title")
|
||||
}
|
||||
content = fmt.Sprintf("</pre>\n\n## %s\n<pre>\n", title)
|
||||
file.WriteString(content)
|
||||
}
|
||||
// workaround to include menu keybinding in cheatsheet
|
||||
// could not add this Description field directly to keybindings.go,
|
||||
// because then menu key would be displayed in menu itself and that is undesirable
|
||||
if key == "x" {
|
||||
binding.Description = a.Tr.SLocalize("menu")
|
||||
}
|
||||
content = fmt.Sprintf("\t<kbd>%s</kbd>%s %s\n", key, strings.TrimPrefix(utils.WithPadding(key, padWidth), key), binding.Description)
|
||||
file.WriteString(content)
|
||||
for _, lang := range langs {
|
||||
os.Setenv("LC_ALL", lang)
|
||||
mApp, _ := app.NewApp(mConfig)
|
||||
file, err := os.Create(getProjectRoot() + "/docs/keybindings/Keybindings_" + lang + ".md")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
bindingSections := getBindingSections(mApp)
|
||||
content := formatSections(mApp, bindingSections)
|
||||
writeString(file, content)
|
||||
}
|
||||
}
|
||||
|
||||
func writeString(file *os.File, str string) {
|
||||
_, err := file.WriteString(str)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func localisedTitle(mApp *app.App, str string) string {
|
||||
viewTitle := strings.Title(str) + "Title"
|
||||
return mApp.Tr.SLocalize(viewTitle)
|
||||
}
|
||||
|
||||
func formatTitle(title string) string {
|
||||
return fmt.Sprintf("\n## %s\n\n", title)
|
||||
}
|
||||
|
||||
func formatBinding(binding *gui.Binding) string {
|
||||
if binding.Alternative != "" {
|
||||
return fmt.Sprintf(" <kbd>%s</kbd>: %s (%s)\n", binding.GetKey(), binding.Description, binding.Alternative)
|
||||
}
|
||||
return fmt.Sprintf(" <kbd>%s</kbd>: %s\n", binding.GetKey(), binding.Description)
|
||||
}
|
||||
|
||||
func getBindingSections(mApp *app.App) []*bindingSection {
|
||||
bindingSections := []*bindingSection{}
|
||||
|
||||
// TODO: add context-based keybindings
|
||||
for _, binding := range mApp.Gui.GetInitialKeybindings() {
|
||||
if binding.Description == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
viewName := binding.ViewName
|
||||
if viewName == "" {
|
||||
viewName = "global"
|
||||
}
|
||||
title := localisedTitle(mApp, viewName)
|
||||
|
||||
bindingSections = addBinding(title, bindingSections, binding)
|
||||
}
|
||||
|
||||
for contextName, contextBindings := range mApp.Gui.GetContextMap() {
|
||||
translatedView := localisedTitle(mApp, contextBindings[0].ViewName)
|
||||
translatedContextName := localisedTitle(mApp, contextName)
|
||||
title := fmt.Sprintf("%s (%s)", translatedView, translatedContextName)
|
||||
|
||||
for _, binding := range contextBindings {
|
||||
bindingSections = addBinding(title, bindingSections, binding)
|
||||
}
|
||||
}
|
||||
|
||||
return bindingSections
|
||||
}
|
||||
|
||||
func addBinding(title string, bindingSections []*bindingSection, binding *gui.Binding) []*bindingSection {
|
||||
if binding.Description == "" && binding.Alternative == "" {
|
||||
return bindingSections
|
||||
}
|
||||
|
||||
for _, section := range bindingSections {
|
||||
if title == section.title {
|
||||
section.bindings = append(section.bindings, binding)
|
||||
return bindingSections
|
||||
}
|
||||
}
|
||||
|
||||
section := &bindingSection{
|
||||
title: title,
|
||||
bindings: []*gui.Binding{binding},
|
||||
}
|
||||
|
||||
return append(bindingSections, section)
|
||||
}
|
||||
|
||||
func formatSections(mApp *app.App, bindingSections []*bindingSection) string {
|
||||
content := fmt.Sprintf("# Lazygit %s\n", mApp.Tr.SLocalize("menu"))
|
||||
|
||||
for _, section := range bindingSections {
|
||||
content += formatTitle(section.title)
|
||||
content += "<pre>\n"
|
||||
for _, binding := range section.bindings {
|
||||
content += formatBinding(binding)
|
||||
}
|
||||
content += "</pre>\n"
|
||||
}
|
||||
|
||||
return content
|
||||
}
|
||||
|
||||
func getProjectRoot() string {
|
||||
dir, err := os.Getwd()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return strings.Split(dir, "lazygit")[0] + "lazygit"
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// call from project root with
|
||||
// go run bin/push_new_patch.go
|
||||
// go run scripts/push_new_patch/main.go
|
||||
|
||||
// goreleaser expects a $GITHUB_TOKEN env variable to be defined
|
||||
// in order to push the release got github
|
||||
@@ -29,7 +29,7 @@ func main() {
|
||||
|
||||
splitVersion := strings.Split(stringVersion, ".")
|
||||
patch := splitVersion[len(splitVersion)-1]
|
||||
newPatch, err := strconv.Atoi(patch)
|
||||
newPatch, _ := strconv.Atoi(patch)
|
||||
splitVersion[len(splitVersion)-1] = strconv.FormatInt(int64(newPatch)+1, 10)
|
||||
newVersion := strings.Join(splitVersion, ".")
|
||||
|
||||
14
test.sh
14
test.sh
@@ -3,9 +3,21 @@
|
||||
set -e
|
||||
echo "" > coverage.txt
|
||||
|
||||
export GOFLAGS=-mod=vendor
|
||||
|
||||
use_go_test=false
|
||||
if command -v gotest; then
|
||||
use_go_test=true
|
||||
fi
|
||||
|
||||
for d in $( find ./* -maxdepth 10 ! -path "./vendor*" ! -path "./.git*" ! -path "./scripts*" -type d); do
|
||||
if ls $d/*.go &> /dev/null; then
|
||||
go test -v -race -coverprofile=profile.out -covermode=atomic $d
|
||||
args="-race -coverprofile=profile.out -covermode=atomic $d"
|
||||
if [ "$use_go_test" == true ]; then
|
||||
gotest $args
|
||||
else
|
||||
go test $args
|
||||
fi
|
||||
if [ -f profile.out ]; then
|
||||
cat profile.out >> coverage.txt
|
||||
rm profile.out
|
||||
|
||||
25
test/hooks/pre-push
Normal file
25
test/hooks/pre-push
Normal file
@@ -0,0 +1,25 @@
|
||||
#!/bin/bash
|
||||
|
||||
# test pre-push hook for testing the lazygit credentials view
|
||||
#
|
||||
# to enable, use:
|
||||
# chmod +x .git/hooks/pre-push
|
||||
#
|
||||
# this will hang if you're using git from the command line, so only enable this
|
||||
# when you are testing the credentials view in lazygit
|
||||
|
||||
exec < /dev/tty
|
||||
|
||||
echo -n "Username for 'github': "
|
||||
read username
|
||||
|
||||
echo -n "Password for 'github': "
|
||||
read password
|
||||
|
||||
if [ "$username" = "username" -a "$password" = "password" ]; then
|
||||
echo "success"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
>&2 echo "incorrect username/password"
|
||||
exit 1
|
||||
@@ -1,35 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -ex; rm -rf repo; mkdir repo; cd repo
|
||||
|
||||
git init
|
||||
git config user.email "test@example.com"
|
||||
git config user.name "Lazygit Tester"
|
||||
|
||||
|
||||
echo "deleted" > deleted_staged
|
||||
echo "deleted_unstaged" > deleted_unstaged
|
||||
echo "modified_staged" > modified_staged
|
||||
echo "modified_unstaged" > modified_unstaged
|
||||
echo "renamed" > renamed_before
|
||||
|
||||
git add .
|
||||
git commit -m "files to delete"
|
||||
rm deleted_staged
|
||||
rm deleted_unstaged
|
||||
|
||||
rm renamed_before
|
||||
echo "renamed" > renamed_after
|
||||
echo "more" >> modified_staged
|
||||
echo "more" >> modified_unstaged
|
||||
echo "untracked_staged" > untracked_staged
|
||||
echo "untracked_unstaged" > untracked_unstaged
|
||||
echo "blah" > "file with space staged"
|
||||
echo "blah" > "file with space unstaged"
|
||||
echo "same name as branch" > master
|
||||
|
||||
git add deleted_staged
|
||||
git add modified_staged
|
||||
git add untracked_staged
|
||||
git add "file with space staged"
|
||||
git add renamed_before
|
||||
git add renamed_after
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/bin/bash
|
||||
#!/bin/sh
|
||||
set -ex; rm -rf repo; mkdir repo; cd repo
|
||||
|
||||
git init
|
||||
@@ -24,30 +24,132 @@ git add file1
|
||||
git add directory
|
||||
git commit -m "first commit"
|
||||
|
||||
git checkout -b develop
|
||||
git checkout -b feature/cherry-picking
|
||||
|
||||
echo "this is file number 1 that I'm going to cherry-pick" > cherrypicking1
|
||||
echo "this is file number 2 that I'm going to cherry-pick" > cherrypicking2
|
||||
|
||||
git add .
|
||||
|
||||
git commit -am "first commit freshman year"
|
||||
|
||||
echo "this is file number 3 that I'm going to cherry-pick" > cherrypicking3
|
||||
|
||||
git add .
|
||||
|
||||
git commit -am "second commit subway eat fresh"
|
||||
|
||||
echo "this is file number 4 that I'm going to cherry-pick" > cherrypicking4
|
||||
|
||||
git add .
|
||||
|
||||
git commit -am "third commit fresh"
|
||||
|
||||
echo "this is file number 5 that I'm going to cherry-pick" > cherrypicking5
|
||||
|
||||
git add .
|
||||
|
||||
git commit -am "fourth commit cool"
|
||||
|
||||
echo "this is file number 6 that I'm going to cherry-pick" > cherrypicking6
|
||||
|
||||
git add .
|
||||
|
||||
git commit -am "fifth commit nice"
|
||||
|
||||
echo "this is file number 7 that I'm going to cherry-pick" > cherrypicking7
|
||||
|
||||
git add .
|
||||
|
||||
git commit -am "sixth commit haha"
|
||||
|
||||
echo "this is file number 8 that I'm going to cherry-pick" > cherrypicking8
|
||||
|
||||
git add .
|
||||
|
||||
git commit -am "seventh commit yeah"
|
||||
|
||||
echo "this is file number 9 that I'm going to cherry-pick" > cherrypicking9
|
||||
|
||||
git add .
|
||||
|
||||
git commit -am "eighth commit woo"
|
||||
|
||||
|
||||
git checkout -b develop
|
||||
echo "once upon a time there was a dog" >> file1
|
||||
add_spacing file1
|
||||
echo "once upon a time there was another dog" >> file1
|
||||
git add file1
|
||||
|
||||
echo "test2" > directory/file
|
||||
echo "test2" > directory/file2
|
||||
git add directory
|
||||
|
||||
git commit -m "first commit on develop"
|
||||
|
||||
git checkout master
|
||||
|
||||
git checkout master
|
||||
echo "once upon a time there was a cat" >> file1
|
||||
add_spacing file1
|
||||
echo "once upon a time there was another cat" >> file1
|
||||
git add file1
|
||||
|
||||
echo "test3" > directory/file
|
||||
echo "test3" > directory/file2
|
||||
git add directory
|
||||
git commit -m "first commit on master"
|
||||
|
||||
git commit -m "first commit on develop"
|
||||
|
||||
git merge develop # should have a merge conflict here
|
||||
git checkout develop
|
||||
echo "once upon a time there was a mouse" >> file3
|
||||
git add file3
|
||||
git commit -m "second commit on develop"
|
||||
|
||||
|
||||
git checkout master
|
||||
echo "once upon a time there was a horse" >> file3
|
||||
git add file3
|
||||
git commit -m "second commit on master"
|
||||
|
||||
|
||||
git checkout develop
|
||||
echo "once upon a time there was a mouse" >> file4
|
||||
git add file4
|
||||
git commit -m "third commit on develop"
|
||||
|
||||
|
||||
git checkout master
|
||||
echo "once upon a time there was a horse" >> file4
|
||||
git add file4
|
||||
git commit -m "third commit on master"
|
||||
|
||||
|
||||
git checkout develop
|
||||
echo "once upon a time there was a mouse" >> file5
|
||||
git add file5
|
||||
git commit -m "fourth commit on develop"
|
||||
|
||||
|
||||
git checkout master
|
||||
echo "once upon a time there was a horse" >> file5
|
||||
git add file5
|
||||
git commit -m "fourth commit on master"
|
||||
|
||||
|
||||
# this is for the autostash feature
|
||||
|
||||
git checkout -b base_branch
|
||||
|
||||
echo "original1\noriginal2\noriginal3" > file
|
||||
git add file
|
||||
git commit -m "file"
|
||||
|
||||
git checkout -b other_branch
|
||||
|
||||
git checkout base_branch
|
||||
|
||||
echo "new1\noriginal2\noriginal3" > file
|
||||
git add file
|
||||
git commit -m "file changed"
|
||||
|
||||
git checkout other_branch
|
||||
|
||||
echo "new2\noriginal2\noriginal3" > file
|
||||
|
||||
2
vendor/github.com/aws/aws-sdk-go/NOTICE.txt
generated
vendored
2
vendor/github.com/aws/aws-sdk-go/NOTICE.txt
generated
vendored
@@ -1,3 +1,3 @@
|
||||
AWS SDK for Go
|
||||
Copyright 2015 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
Copyright 2015 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
Copyright 2014-2015 Stripe, Inc.
|
||||
|
||||
23
vendor/github.com/aws/aws-sdk-go/aws/awserr/error.go
generated
vendored
23
vendor/github.com/aws/aws-sdk-go/aws/awserr/error.go
generated
vendored
@@ -138,8 +138,27 @@ type RequestFailure interface {
|
||||
RequestID() string
|
||||
}
|
||||
|
||||
// NewRequestFailure returns a new request error wrapper for the given Error
|
||||
// provided.
|
||||
// NewRequestFailure returns a wrapped error with additional information for
|
||||
// request status code, and service requestID.
|
||||
//
|
||||
// Should be used to wrap all request which involve service requests. Even if
|
||||
// the request failed without a service response, but had an HTTP status code
|
||||
// that may be meaningful.
|
||||
func NewRequestFailure(err Error, statusCode int, reqID string) RequestFailure {
|
||||
return newRequestError(err, statusCode, reqID)
|
||||
}
|
||||
|
||||
// UnmarshalError provides the interface for the SDK failing to unmarshal data.
|
||||
type UnmarshalError interface {
|
||||
awsError
|
||||
Bytes() []byte
|
||||
}
|
||||
|
||||
// NewUnmarshalError returns an initialized UnmarshalError error wrapper adding
|
||||
// the bytes that fail to unmarshal to the error.
|
||||
func NewUnmarshalError(err error, msg string, bytes []byte) UnmarshalError {
|
||||
return &unmarshalError{
|
||||
awsError: New("UnmarshalError", msg, err),
|
||||
bytes: bytes,
|
||||
}
|
||||
}
|
||||
|
||||
31
vendor/github.com/aws/aws-sdk-go/aws/awserr/types.go
generated
vendored
31
vendor/github.com/aws/aws-sdk-go/aws/awserr/types.go
generated
vendored
@@ -1,6 +1,9 @@
|
||||
package awserr
|
||||
|
||||
import "fmt"
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// SprintError returns a string of the formatted error code.
|
||||
//
|
||||
@@ -119,6 +122,7 @@ type requestError struct {
|
||||
awsError
|
||||
statusCode int
|
||||
requestID string
|
||||
bytes []byte
|
||||
}
|
||||
|
||||
// newRequestError returns a wrapped error with additional information for
|
||||
@@ -170,6 +174,29 @@ func (r requestError) OrigErrs() []error {
|
||||
return []error{r.OrigErr()}
|
||||
}
|
||||
|
||||
type unmarshalError struct {
|
||||
awsError
|
||||
bytes []byte
|
||||
}
|
||||
|
||||
// Error returns the string representation of the error.
|
||||
// Satisfies the error interface.
|
||||
func (e unmarshalError) Error() string {
|
||||
extra := hex.Dump(e.bytes)
|
||||
return SprintError(e.Code(), e.Message(), extra, e.OrigErr())
|
||||
}
|
||||
|
||||
// String returns the string representation of the error.
|
||||
// Alias for Error to satisfy the stringer interface.
|
||||
func (e unmarshalError) String() string {
|
||||
return e.Error()
|
||||
}
|
||||
|
||||
// Bytes returns the bytes that failed to unmarshal.
|
||||
func (e unmarshalError) Bytes() []byte {
|
||||
return e.bytes
|
||||
}
|
||||
|
||||
// An error list that satisfies the golang interface
|
||||
type errorList []error
|
||||
|
||||
@@ -181,7 +208,7 @@ func (e errorList) Error() string {
|
||||
// How do we want to handle the array size being zero
|
||||
if size := len(e); size > 0 {
|
||||
for i := 0; i < size; i++ {
|
||||
msg += fmt.Sprintf("%s", e[i].Error())
|
||||
msg += e[i].Error()
|
||||
// We check the next index to see if it is within the slice.
|
||||
// If it is, then we append a newline. We do this, because unit tests
|
||||
// could be broken with the additional '\n'
|
||||
|
||||
2
vendor/github.com/aws/aws-sdk-go/aws/awsutil/equal.go
generated
vendored
2
vendor/github.com/aws/aws-sdk-go/aws/awsutil/equal.go
generated
vendored
@@ -15,7 +15,7 @@ func DeepEqual(a, b interface{}) bool {
|
||||
rb := reflect.Indirect(reflect.ValueOf(b))
|
||||
|
||||
if raValid, rbValid := ra.IsValid(), rb.IsValid(); !raValid && !rbValid {
|
||||
// If the elements are both nil, and of the same type the are equal
|
||||
// If the elements are both nil, and of the same type they are equal
|
||||
// If they are of different types they are not equal
|
||||
return reflect.TypeOf(a) == reflect.TypeOf(b)
|
||||
} else if raValid != rbValid {
|
||||
|
||||
11
vendor/github.com/aws/aws-sdk-go/aws/awsutil/path_value.go
generated
vendored
11
vendor/github.com/aws/aws-sdk-go/aws/awsutil/path_value.go
generated
vendored
@@ -185,13 +185,12 @@ func ValuesAtPath(i interface{}, path string) ([]interface{}, error) {
|
||||
// SetValueAtPath sets a value at the case insensitive lexical path inside
|
||||
// of a structure.
|
||||
func SetValueAtPath(i interface{}, path string, v interface{}) {
|
||||
if rvals := rValuesAtPath(i, path, true, false, v == nil); rvals != nil {
|
||||
for _, rval := range rvals {
|
||||
if rval.Kind() == reflect.Ptr && rval.IsNil() {
|
||||
continue
|
||||
}
|
||||
setValue(rval, v)
|
||||
rvals := rValuesAtPath(i, path, true, false, v == nil)
|
||||
for _, rval := range rvals {
|
||||
if rval.Kind() == reflect.Ptr && rval.IsNil() {
|
||||
continue
|
||||
}
|
||||
setValue(rval, v)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user