mirror of
https://github.com/sourcegit-scm/sourcegit
synced 2025-06-15 07:35:04 +00:00
Compare commits
915 commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
932baeec53 | ||
![]() |
637e133f47 | ||
![]() |
a1e76e9bea | ||
![]() |
a8541a780e | ||
![]() |
d55f19586f | ||
![]() |
a22c39519f | ||
![]() |
84fb39f97a | ||
![]() |
fe54d30b70 | ||
![]() |
ba4c0f0cd2 | ||
![]() |
2478d2953b | ||
![]() |
74f52fb266 | ||
![]() |
f830b68f6a | ||
![]() |
d323a2064e | ||
![]() |
203c50350e | ||
![]() |
47012e29dc | ||
![]() |
8db033be99 | ||
![]() |
a2ca071f08 | ||
![]() |
7bba40d03f | ||
![]() |
0c22409b7b | ||
![]() |
f63fe8637b | ||
![]() |
08665e45c1 | ||
![]() |
3bb20868fc | ||
![]() |
ac55bed812 | ||
![]() |
f003f67129 | ||
![]() |
f04b0c5d97 | ||
![]() |
406ace9e79 | ||
![]() |
464fe74580 | ||
![]() |
b969ac161a | ||
![]() |
88c38b4139 | ||
![]() |
54c05ac35a | ||
![]() |
75c32c1a01 | ||
![]() |
a023a9259b | ||
![]() |
eebadd67a1 | ||
![]() |
f716c5ee1e | ||
![]() |
ed496a41fb | ||
![]() |
06a77502bc | ||
![]() |
c2187edbe9 | ||
![]() |
d85f82e171 | ||
![]() |
f7c10d0b33 | ||
![]() |
25e272fa55 | ||
![]() |
98041c803e | ||
![]() |
ee2e7d0127 | ||
![]() |
6517e78ab6 | ||
![]() |
bf43dd828a | ||
![]() |
cd009bda6b | ||
![]() |
cd8ff2e9bf | ||
![]() |
0594196dee | ||
![]() |
425395da29 | ||
![]() |
6e501b1ee4 | ||
![]() |
7b05b011aa | ||
![]() |
f1052c3efc | ||
![]() |
78f9ae2fa9 | ||
![]() |
80df53cf04 | ||
![]() |
57004c4baf | ||
![]() |
fa004ce31b | ||
![]() |
6620bd193e | ||
![]() |
26307e2343 | ||
![]() |
db5bb0aec9 | ||
![]() |
dd432c63e8 | ||
![]() |
b94f26a937 | ||
![]() |
8e5d5b946e | ||
![]() |
a9734ea8e9 | ||
![]() |
e22f0f8513 | ||
![]() |
8b17f3b1f4 | ||
![]() |
7934496cff | ||
![]() |
188408fdfc | ||
![]() |
bc5deac9fe | ||
![]() |
1bd2044589 | ||
![]() |
f0c77ffeb8 | ||
![]() |
60cd210b80 | ||
![]() |
75015d550c | ||
![]() |
e40ca4bbe0 | ||
![]() |
46231a759c | ||
![]() |
fbc8edcc13 | ||
![]() |
3437f5f4a9 | ||
![]() |
40bf69bff3 | ||
![]() |
2aac6779a5 | ||
![]() |
9affca1fb2 | ||
![]() |
729e06d5c0 | ||
![]() |
826619e7c9 | ||
![]() |
056b90a5ae | ||
![]() |
0641a22230 | ||
![]() |
d3bc85418e | ||
![]() |
4887252870 | ||
![]() |
860f6f2369 | ||
![]() |
cfc80d41a1 | ||
![]() |
ac26d5bb06 | ||
![]() |
d7c3bb7150 | ||
![]() |
39d955b033 | ||
![]() |
0e35c56529 | ||
![]() |
22339ab619 | ||
![]() |
ef53dd0025 | ||
![]() |
30d4c1008a | ||
![]() |
ca33107a45 | ||
![]() |
4363b8b6aa | ||
![]() |
f3fe90b2e1 | ||
![]() |
e28b75b860 | ||
![]() |
3377886bab | ||
![]() |
4807cd5eb2 | ||
![]() |
d21b790784 | ||
![]() |
764ae31239 | ||
![]() |
38d67d7f17 | ||
![]() |
76a197aae7 | ||
![]() |
594ffc0d1a | ||
![]() |
fb1f5638ce | ||
![]() |
492da8dd57 | ||
![]() |
0ae39faad1 | ||
![]() |
c112549b69 | ||
![]() |
9fb8af51ff | ||
![]() |
1ee7d1184e | ||
![]() |
44c83ef342 | ||
![]() |
c3ac59ee1a | ||
![]() |
c73f775aa5 | ||
![]() |
afbd0d7135 | ||
![]() |
bf39673b21 | ||
![]() |
b0c0c46441 | ||
![]() |
1fef7a7baa | ||
![]() |
1872740265 | ||
![]() |
09d0122e26 | ||
![]() |
7728f4ffbf | ||
![]() |
936160ea5c | ||
![]() |
d73ae83b01 | ||
![]() |
5e05c008fc | ||
![]() |
598ba6d9f6 | ||
![]() |
3232e6f313 | ||
![]() |
0a6b1faa65 | ||
![]() |
71b90a82b6 | ||
![]() |
d304c50e7f | ||
![]() |
438aa76695 | ||
![]() |
ece51fbd32 | ||
![]() |
224f7a949a | ||
![]() |
e6fdc778b7 | ||
![]() |
53c6fc8999 | ||
![]() |
f0d1d460a9 | ||
![]() |
3386cb177b | ||
![]() |
4d5be9f280 | ||
![]() |
6fa454ace8 | ||
![]() |
75b7724d44 | ||
![]() |
550493b572 | ||
![]() |
eb183589f5 | ||
![]() |
d56c6a5030 | ||
![]() |
f9b6116a76 | ||
![]() |
12d2b7721c | ||
![]() |
119b0fe95c | ||
![]() |
1dfb629cef | ||
![]() |
0e2bb1b276 | ||
![]() |
57ee1f7dbd | ||
![]() |
aaf53ac694 | ||
![]() |
736991198f | ||
![]() |
7dd1389c25 | ||
![]() |
341ac26576 | ||
![]() |
aff003fd6d | ||
![]() |
b78f6b0ea8 | ||
![]() |
5e85f6fefe | ||
![]() |
52991351af | ||
![]() |
4b849d9d5c | ||
![]() |
6b083dcd3e | ||
![]() |
9614b995d8 | ||
![]() |
36c2e083cc | ||
![]() |
fd35e0817d | ||
![]() |
d429a6426a | ||
![]() |
4c1ba717a7 | ||
![]() |
bd553405c2 | ||
![]() |
f121975a28 | ||
![]() |
ea320d2cdf | ||
![]() |
01945f231e | ||
![]() |
506dbc218c | ||
![]() |
d3a740fb95 | ||
![]() |
d3d0e7b15c | ||
![]() |
879b84ac20 | ||
![]() |
7f86ad9f22 | ||
![]() |
0c9cb41e68 | ||
![]() |
86f27c5e58 | ||
![]() |
1f0ab2bfec | ||
![]() |
fd935259aa | ||
![]() |
f46bbd01cd | ||
![]() |
ed1351b1f7 | ||
![]() |
d299469613 | ||
![]() |
e4490d87dc | ||
![]() |
85b223a3d0 | ||
![]() |
6c62789c4c | ||
![]() |
85e08f5eea | ||
![]() |
463d161ac7 | ||
![]() |
5ec51eefb9 | ||
![]() |
bc5c4670de | ||
![]() |
d3363429df | ||
![]() |
f83b6c24ae | ||
![]() |
61bb0f7dc7 | ||
![]() |
20a239621b | ||
![]() |
9e91494a20 | ||
![]() |
d71189c705 | ||
![]() |
55232aeddd | ||
![]() |
6bf930a9e0 | ||
![]() |
5b72b15cf2 | ||
![]() |
0e61a0196b | ||
![]() |
7bb4e355bd | ||
![]() |
57d15dc6d3 | ||
![]() |
65808e5f58 | ||
![]() |
cf7b61dd71 | ||
![]() |
ac1bd7ca85 | ||
![]() |
142ee5a327 | ||
![]() |
afc8a772dd | ||
![]() |
8a45e25106 | ||
![]() |
4e41a6207a | ||
![]() |
a5c25cf9fe | ||
![]() |
11a9d7fdd8 | ||
![]() |
ef4b639f8e | ||
![]() |
c62b4a031f | ||
![]() |
af9cf6ba6a | ||
![]() |
641098ffb2 | ||
![]() |
fcad8eeadc | ||
![]() |
01625ada1a | ||
![]() |
88dc12275a | ||
![]() |
bac21c5714 | ||
![]() |
19a51f227b | ||
![]() |
b6d618a6d7 | ||
![]() |
2573553e01 | ||
![]() |
9dd0beb61f | ||
![]() |
029fd6933f | ||
![]() |
0f6c8976af | ||
![]() |
e446e97f28 | ||
![]() |
3e530de9cc | ||
![]() |
6cf1b20ea6 | ||
![]() |
321ccf9622 | ||
![]() |
ebe0e61367 | ||
![]() |
e8bf58f6c3 | ||
![]() |
15ee2dac91 | ||
![]() |
5d1601086f | ||
![]() |
3eaa24a993 | ||
![]() |
6986e1ac24 | ||
![]() |
2c8370fa92 | ||
![]() |
008708f07c | ||
![]() |
832fcd7487 | ||
![]() |
6df38ad970 | ||
![]() |
0a7b973388 | ||
![]() |
6b050fa557 | ||
![]() |
417ab3ecc2 | ||
![]() |
a413df6f89 | ||
![]() |
ddf643c081 | ||
![]() |
bbc840a5cb | ||
![]() |
c8e21673e4 | ||
![]() |
e45e37d305 | ||
![]() |
b7fa04d141 | ||
![]() |
93a5d7baea | ||
![]() |
e4e2f7b3a7 | ||
![]() |
eae6d10784 | ||
![]() |
4bc5b90e6b | ||
![]() |
df29edd8f0 | ||
![]() |
054bbf7e0c | ||
![]() |
ce00fa6c88 | ||
![]() |
a960e14368 | ||
![]() |
867edd9453 | ||
![]() |
aee4ce6387 | ||
![]() |
d92d279fbe | ||
![]() |
5e080279ce | ||
![]() |
704c6f589d | ||
![]() |
666275c747 | ||
![]() |
c0c52695a3 | ||
![]() |
c529fab869 | ||
![]() |
4b2983b330 | ||
![]() |
8c1d397480 | ||
![]() |
007acb3fa6 | ||
![]() |
3b0c57be84 | ||
![]() |
61bc42612e | ||
![]() |
fe677d40c1 | ||
![]() |
9bde797b24 | ||
![]() |
7501588c95 | ||
![]() |
80aead3a17 | ||
![]() |
847a1e727b | ||
![]() |
98dd37a9bc | ||
![]() |
95ea0a6ba6 | ||
![]() |
b9dc5a8164 | ||
![]() |
63803c9b88 | ||
![]() |
825b74c2a3 | ||
![]() |
48bb8e91de | ||
![]() |
53a55467f1 | ||
![]() |
5681bf489d | ||
![]() |
226bc434f5 | ||
![]() |
30d42b11e2 | ||
![]() |
2698427cd0 | ||
![]() |
b4f1f35e67 | ||
![]() |
92f215d039 | ||
![]() |
2e1cf76c82 | ||
![]() |
951ea8f088 | ||
![]() |
21cb87cec5 | ||
![]() |
f39048df77 | ||
![]() |
bbdeecdcc6 | ||
![]() |
d2e688908c | ||
![]() |
91acf0a32a | ||
![]() |
d44d2b9770 | ||
![]() |
f09367a614 | ||
![]() |
00e56ce9d1 | ||
![]() |
22d4f26bc3 | ||
![]() |
a94c7f55ce | ||
![]() |
1d16925e74 | ||
![]() |
8c4362a98d | ||
![]() |
9efbc7dd7a | ||
![]() |
c519381645 | ||
![]() |
6590812634 | ||
![]() |
ad6ed1512b | ||
![]() |
f73e0687a1 | ||
![]() |
ea680782fe | ||
![]() |
7e282b13fa | ||
![]() |
1386ca30e3 | ||
![]() |
2107676058 | ||
![]() |
f72f1894c3 | ||
![]() |
586ff39da1 | ||
![]() |
9bdbf47522 | ||
![]() |
17c08d42a0 | ||
![]() |
fafa2a53ae | ||
![]() |
7890f7abbf | ||
![]() |
345ad06aba | ||
![]() |
78f4809875 | ||
![]() |
87ebe3741d | ||
![]() |
f2000b4a84 | ||
![]() |
9a6c671a96 | ||
![]() |
34e0ea3bcb | ||
![]() |
7be37424e1 | ||
![]() |
a42df87b9c | ||
![]() |
df5294bcb7 | ||
![]() |
9eae1eeb81 | ||
![]() |
4c3698b171 | ||
![]() |
9f18cbca5b | ||
![]() |
6882ae069f | ||
![]() |
9c53894cb4 | ||
![]() |
06d033464d | ||
![]() |
750ca8ec61 | ||
![]() |
86113701f3 | ||
![]() |
387b68cdfe | ||
![]() |
550c108f84 | ||
![]() |
232482ca92 | ||
![]() |
b4db88a663 | ||
![]() |
41416a6bed | ||
![]() |
5fd074a9b6 | ||
![]() |
413669741d | ||
![]() |
75b4a4b294 | ||
![]() |
d254b557a9 | ||
![]() |
892f3b8032 | ||
![]() |
afe5d4b969 | ||
![]() |
4d31392085 | ||
![]() |
de31d4bad4 | ||
![]() |
5bd7dd428d | ||
![]() |
4c1a04477e | ||
![]() |
090b64d68d | ||
![]() |
231010abc6 | ||
![]() |
3358ff9aee | ||
![]() |
104a3f0bbf | ||
![]() |
a06d1183d7 | ||
![]() |
9f493abd1a | ||
![]() |
349844cf2a | ||
![]() |
021aab8408 | ||
![]() |
c1e31ac4e3 | ||
![]() |
2a43efde07 | ||
![]() |
33ae6a9989 | ||
![]() |
0e967ffc8e | ||
![]() |
c231772298 | ||
![]() |
8b39df32cc | ||
![]() |
928a0ad3c5 | ||
![]() |
9606f128e4 | ||
![]() |
67255a5529 | ||
![]() |
cac4b7edf6 | ||
![]() |
fa44fa773c | ||
![]() |
db46de0261 | ||
![]() |
e9036b5fb9 | ||
![]() |
9ba0b595d9 | ||
![]() |
cf763b47c6 | ||
![]() |
539d3f6eca | ||
![]() |
03216fc31e | ||
![]() |
3cc463d24b | ||
![]() |
33a463ce59 | ||
![]() |
90b37663ed | ||
![]() |
9ebde1943e | ||
![]() |
be3f418680 | ||
![]() |
a97f163860 | ||
![]() |
c1839199ee | ||
![]() |
7d5ffaf867 | ||
![]() |
f0d4cfc9f9 | ||
![]() |
70494485ab | ||
![]() |
c4c04b8b01 | ||
![]() |
e2da44c8fd | ||
![]() |
0acbe3e487 | ||
![]() |
05982e6dc0 | ||
![]() |
e5dc211c35 | ||
![]() |
1e0fd63543 | ||
![]() |
3b1018e0e2 | ||
![]() |
7d20f97f4e | ||
![]() |
09c0edef8e | ||
![]() |
558eb7c9ac | ||
![]() |
b7aa49403b | ||
![]() |
f14a666091 | ||
![]() |
e89dbd8f43 | ||
![]() |
81820e7034 | ||
![]() |
e7f0217a7b | ||
![]() |
0cb2ca78fe | ||
![]() |
17cf402c78 | ||
![]() |
245de9b458 | ||
![]() |
e76328ff38 | ||
![]() |
61a1b130f2 | ||
![]() |
69d8d963ea | ||
![]() |
48835ca043 | ||
![]() |
241f92a290 | ||
![]() |
12b1204809 | ||
![]() |
cd5a682194 | ||
![]() |
18888de081 | ||
![]() |
13af0a43ed | ||
![]() |
af350c2fcd | ||
![]() |
accccb5ea3 | ||
![]() |
cfabfb7368 | ||
![]() |
1799de4907 | ||
![]() |
a99ab37797 | ||
![]() |
47824dc27a | ||
![]() |
3b18ee0b37 | ||
![]() |
5d90c2ed60 | ||
![]() |
768b324356 | ||
![]() |
8b5f491e34 | ||
![]() |
506af95963 | ||
![]() |
898a8bc69a | ||
![]() |
da38b72ee5 | ||
![]() |
7cda7211f1 | ||
![]() |
1555abd027 | ||
![]() |
7fedef396f | ||
![]() |
2c5ee4fa99 | ||
![]() |
f29402ceec | ||
![]() |
f5c213060e | ||
![]() |
3275dd07d2 | ||
![]() |
67fb0b300f | ||
![]() |
3049730dd5 | ||
![]() |
ad9021e892 | ||
![]() |
39f7f119dd | ||
![]() |
3431ed4bab | ||
![]() |
f3d99d64bf | ||
![]() |
b65c697e5b | ||
![]() |
48f8b6116a | ||
![]() |
fa02c65da5 | ||
![]() |
a37c6b29ec | ||
![]() |
ac7b02590b | ||
![]() |
8c9cf05c1d | ||
![]() |
c615d04038 | ||
![]() |
17d285d9bf | ||
![]() |
ef106e6909 | ||
![]() |
cbc7079e59 | ||
![]() |
904432a8f1 | ||
![]() |
7ef4cca1f5 | ||
![]() |
2deb79f8ce | ||
![]() |
8e55ba1b47 | ||
![]() |
55be1ad1ca | ||
![]() |
1138ba304d | ||
![]() |
ae5fa6a793 | ||
![]() |
0045e06d78 | ||
![]() |
07d99f5fd2 | ||
![]() |
9ee3a00fba | ||
![]() |
1482a005bb | ||
![]() |
ce7196490a | ||
![]() |
276d000bcf | ||
![]() |
b26c8a64ad | ||
![]() |
56ebc182f2 | ||
![]() |
1575ae977e | ||
![]() |
4153eec1a8 | ||
![]() |
fc37677546 | ||
![]() |
4fb853d1fd | ||
![]() |
dccf53e518 | ||
![]() |
ca0fb7ae10 | ||
![]() |
f37ac904b9 | ||
![]() |
467089aec5 | ||
![]() |
380e6713b5 | ||
![]() |
fc85dd3269 | ||
![]() |
0a877c6730 | ||
![]() |
166c925eee | ||
![]() |
7581d761cc | ||
![]() |
88bb603dc9 | ||
![]() |
d335cac167 | ||
![]() |
9590f96a44 | ||
![]() |
03f49ccff0 | ||
![]() |
39f4cd1732 | ||
![]() |
cdc0fbb753 | ||
![]() |
8c1e1a3e6a | ||
![]() |
56253e95c3 | ||
![]() |
5467703a6e | ||
![]() |
7cd5814410 | ||
![]() |
38d87fa1a1 | ||
![]() |
65dbfd336d | ||
![]() |
891e1b2ec8 | ||
![]() |
c349ac10f3 | ||
![]() |
145273b4a7 | ||
![]() |
a5bdcab341 | ||
![]() |
673b335a2a | ||
![]() |
f7197e08eb | ||
![]() |
f02a7b9858 | ||
![]() |
2512d3be7a | ||
![]() |
ae1e46b586 | ||
![]() |
822452a20c | ||
![]() |
760e44877b | ||
![]() |
695db2a319 | ||
![]() |
8d47bd5cd9 | ||
![]() |
808302ce84 | ||
![]() |
b930066b5a | ||
![]() |
a0cddaea80 | ||
![]() |
2b95ea2ab1 | ||
![]() |
c9fe373dda | ||
![]() |
398b14695c | ||
![]() |
5845ef3eb6 | ||
![]() |
3e8bba0d0b | ||
![]() |
7031693489 | ||
![]() |
b4ab4afd3a | ||
![]() |
cea8a90680 | ||
![]() |
265aaa1d67 | ||
![]() |
cdd1926e2f | ||
![]() |
ddfc868df3 | ||
![]() |
10fd0f9d15 | ||
![]() |
99a45335fe | ||
![]() |
8f8385072c | ||
![]() |
a480ba0139 | ||
![]() |
4b41029768 | ||
![]() |
6273c01d71 | ||
![]() |
450dadf76c | ||
![]() |
7caa03a09b | ||
![]() |
d9cf849b9f | ||
![]() |
6fd6bbb6b5 | ||
![]() |
34f8618989 | ||
![]() |
84979b20b3 | ||
![]() |
66517fd4bf | ||
![]() |
a46e52582f | ||
![]() |
db504241ea | ||
![]() |
c3e1fb93b6 | ||
![]() |
c8bee2f6ba | ||
![]() |
9645b65db6 | ||
![]() |
67f4330dd4 | ||
![]() |
9560496c7b | ||
![]() |
b9b684a83d | ||
![]() |
519bdf1ddc | ||
![]() |
0e261cffd2 | ||
![]() |
e430e847ff | ||
![]() |
7331167be2 | ||
![]() |
f07832c385 | ||
![]() |
e4f5c34e0c | ||
![]() |
eaa322dfab | ||
![]() |
77d8afe056 | ||
![]() |
0476a825ef | ||
![]() |
bb2284c4c9 | ||
![]() |
ee7ccc0391 | ||
![]() |
231f3bf668 | ||
![]() |
f5d6e1264d | ||
![]() |
64a41dce39 | ||
![]() |
fa4d9d24e9 | ||
![]() |
91c5c96afc | ||
![]() |
2fc03025ee | ||
![]() |
471452646b | ||
![]() |
f23e3478e6 | ||
![]() |
54d49a9eda | ||
![]() |
5f2bd8ad94 | ||
![]() |
f54c8877d7 | ||
![]() |
f496d15f70 | ||
![]() |
cf8cff6b64 | ||
![]() |
5c279b4b56 | ||
![]() |
0f9087fac6 | ||
![]() |
860f52153b | ||
![]() |
b4fbc2372b | ||
![]() |
2b2f070c4a | ||
![]() |
e65cb50495 | ||
![]() |
cdae9168ed | ||
![]() |
2e51e8939b | ||
![]() |
e3cc987682 | ||
![]() |
c1c0e7b2a0 | ||
![]() |
df6cbcaa28 | ||
![]() |
855466686d | ||
![]() |
bfc3f37e47 | ||
![]() |
18d3b9560b | ||
![]() |
82fdd2e9d4 | ||
![]() |
6ba26770c4 | ||
![]() |
978801c9ff | ||
![]() |
2fb1d7e14a | ||
![]() |
89f655c84d | ||
![]() |
0860245674 | ||
![]() |
43fed8e04d | ||
![]() |
83f23583be | ||
![]() |
ca6d41ee60 | ||
![]() |
773e27fda7 | ||
![]() |
14877d4d63 | ||
![]() |
92fa25ae8d | ||
![]() |
aa0d4b4296 | ||
![]() |
78c0d8d334 | ||
![]() |
81bbe11345 | ||
![]() |
991ebe4082 | ||
![]() |
fb8d4a2542 | ||
![]() |
269903503f | ||
![]() |
5e898a809e | ||
![]() |
71d0b69eee | ||
![]() |
792e61b24f | ||
![]() |
e884f27f67 | ||
![]() |
e28f8611ef | ||
![]() |
5af856b9da | ||
![]() |
2137ad9ec9 | ||
![]() |
11af5d9b29 | ||
![]() |
68e96f428e | ||
![]() |
25e6e261a6 | ||
![]() |
96538b9a62 | ||
![]() |
b75676a7f8 | ||
![]() |
5301a368e0 | ||
![]() |
d4bcc60113 | ||
![]() |
35ee4a47db | ||
![]() |
598bba5210 | ||
![]() |
72c7f24e2f | ||
![]() |
9dffd55c2b | ||
![]() |
7f8372f6b5 | ||
![]() |
4c63c4c90d | ||
![]() |
4418214d38 | ||
![]() |
8c48b9623e | ||
![]() |
25fa223c6b | ||
![]() |
8423f53ace | ||
![]() |
8dd0274bdd | ||
![]() |
5199fb2b74 | ||
![]() |
c90b7afac1 | ||
![]() |
88c7d5bbc9 | ||
![]() |
9cf1cba9b7 | ||
![]() |
639bff9ad8 | ||
![]() |
3e70ff94b0 | ||
![]() |
f930967698 | ||
![]() |
109eee5668 | ||
![]() |
57cb9d0909 | ||
![]() |
caca1dcaaf | ||
![]() |
337ee1a55d | ||
![]() |
fc3043b93c | ||
![]() |
61d7f36be4 | ||
![]() |
bdf439a742 | ||
![]() |
7bfb95f7ce | ||
![]() |
c849c13fcb | ||
![]() |
8f7db4e874 | ||
![]() |
a7e254cac6 | ||
![]() |
79306ad73b | ||
![]() |
a0786bf9cc | ||
![]() |
4d740f4731 | ||
![]() |
c6747f72f9 | ||
![]() |
1f35e83799 | ||
![]() |
1d037c7c57 | ||
![]() |
4868ea1a0b | ||
![]() |
fb6b57bdf5 | ||
![]() |
0b7805b2ee | ||
![]() |
89e09d842d | ||
![]() |
d4341c1195 | ||
![]() |
74e5bcb704 | ||
![]() |
124bdc97f9 | ||
![]() |
52a53cc697 | ||
![]() |
fa4caa2186 | ||
![]() |
9ab602788a | ||
![]() |
b2ab62825e | ||
![]() |
841276852a | ||
![]() |
2b4fc64c73 | ||
![]() |
9da2c787db | ||
![]() |
b5feabfd37 | ||
![]() |
08da3ac5d8 | ||
![]() |
731f1055bc | ||
![]() |
507e502874 | ||
![]() |
b57c0158c1 | ||
![]() |
53f591bdad | ||
![]() |
0e1dfba7ef | ||
![]() |
cbc2e46beb | ||
![]() |
ce16ac63eb | ||
![]() |
68946d2140 | ||
![]() |
c3eca0d7fd | ||
![]() |
69d107430a | ||
![]() |
59638eb731 | ||
![]() |
5d2cd8b2fa | ||
![]() |
3d4a9b86b4 | ||
![]() |
af4645a4ad | ||
![]() |
cf42381802 | ||
![]() |
ed37b42a4a | ||
![]() |
d401e898a2 | ||
![]() |
e28abf8119 | ||
![]() |
14f47a9007 | ||
![]() |
7ae5100fcf | ||
![]() |
36178d5ecf | ||
![]() |
bc66e24407 | ||
![]() |
e39351b4a7 | ||
![]() |
9104060d79 | ||
![]() |
9b07034846 | ||
![]() |
588879eb7f | ||
![]() |
c6aedf1193 | ||
![]() |
3302bdeb26 | ||
![]() |
21e4dcffd0 | ||
![]() |
9757678dcf | ||
![]() |
19e72f8650 | ||
![]() |
eea55ec56f | ||
![]() |
21cfd17cdb | ||
![]() |
e9d16a5102 | ||
![]() |
1bfe1a2755 | ||
![]() |
011a415949 | ||
![]() |
37e5926388 | ||
![]() |
06df27780f | ||
![]() |
37fed93589 | ||
![]() |
af20ab2448 | ||
![]() |
10fba08e43 | ||
![]() |
821063e3ec | ||
![]() |
aebfffee00 | ||
![]() |
0c8179b934 | ||
![]() |
e757e63bf7 | ||
![]() |
59e4f0d388 | ||
![]() |
bead749c57 | ||
![]() |
c61ecb0bb0 | ||
![]() |
7fc7c8fdb6 | ||
![]() |
3b76a30948 | ||
![]() |
ef2e0a8a56 | ||
![]() |
8cc056d2af | ||
![]() |
35abeae758 | ||
![]() |
aa0d066944 | ||
![]() |
2495911bd8 | ||
![]() |
1f0dd15192 | ||
![]() |
6a80e54b47 | ||
![]() |
a915708db3 | ||
![]() |
cf90e51887 | ||
![]() |
f8bc022813 | ||
![]() |
12597fd3e3 | ||
![]() |
9a36e652e6 | ||
![]() |
8ed7a99923 | ||
![]() |
8edd955370 | ||
![]() |
2105fd450d | ||
![]() |
d1f3469250 | ||
![]() |
7089f29b85 | ||
![]() |
dbb1df5f8b | ||
![]() |
e9297096df | ||
![]() |
dacbd2a791 | ||
![]() |
38a8490d16 | ||
![]() |
9aba737d9e | ||
![]() |
65a9406c9a | ||
![]() |
2ee5b4707a | ||
![]() |
3ca4dc488c | ||
![]() |
942f349275 | ||
![]() |
84533599f1 | ||
![]() |
018a32b95b | ||
![]() |
e784696058 | ||
![]() |
3af6012561 | ||
![]() |
a4157e11e6 | ||
![]() |
c2950ad209 | ||
![]() |
9a9f63a787 | ||
![]() |
4f41f44a9f | ||
![]() |
92065dee12 | ||
![]() |
dd254ebf4f | ||
![]() |
6a1026c992 | ||
![]() |
fa07bb5b5a | ||
![]() |
aad42a8297 | ||
![]() |
733ffe1af2 | ||
![]() |
4f6a878177 | ||
![]() |
49ee9c8e33 | ||
![]() |
0192e941f0 | ||
![]() |
37c4545875 | ||
![]() |
ab080b53b1 | ||
![]() |
cc111baf01 | ||
![]() |
e242119a03 | ||
![]() |
620f411e99 | ||
![]() |
e033a93dd8 | ||
![]() |
cce08fb086 | ||
![]() |
cd0b2326f5 | ||
![]() |
75288e7a31 | ||
![]() |
6ae66095c2 | ||
![]() |
cb4ad63ba3 | ||
![]() |
90c04f1db2 | ||
![]() |
6482ef227a | ||
![]() |
38e7b69450 | ||
![]() |
0f13c002f5 | ||
![]() |
b00c81c996 | ||
![]() |
8a472cb472 | ||
![]() |
53ec53a6ee | ||
![]() |
1c8d4d312a | ||
![]() |
c8aecc00cc | ||
![]() |
5b425c4ed0 | ||
![]() |
765286ec0b | ||
![]() |
8ea17e57da | ||
![]() |
6f407fb086 | ||
![]() |
f2b1c06bb8 | ||
![]() |
766b60d05e | ||
![]() |
a9327274c6 | ||
![]() |
5acc6e6928 | ||
![]() |
a112d212dc | ||
![]() |
b9b5220590 | ||
![]() |
cc5f3ebfa5 | ||
![]() |
d8168c3ba6 | ||
![]() |
99b7086cd0 | ||
![]() |
011f5258ad | ||
![]() |
5f4c1bb984 | ||
![]() |
632222a776 | ||
![]() |
458a1ea83c | ||
![]() |
b10e084c54 | ||
![]() |
88e64a4f31 | ||
![]() |
10d8d3b2ef | ||
![]() |
59e3e4c635 | ||
![]() |
f87bf8b4c6 | ||
![]() |
4f3e6398b0 | ||
![]() |
001e813b7e | ||
![]() |
6e6cd7a7f3 | ||
![]() |
a205e17bf2 | ||
![]() |
6e5626f267 | ||
![]() |
91883c9f11 | ||
![]() |
f8e7976ec2 | ||
![]() |
b26838ff68 | ||
![]() |
c939308e4c | ||
![]() |
275a52eb5c | ||
![]() |
52fba29613 | ||
![]() |
d7ba310a24 | ||
![]() |
f1acc96a72 | ||
![]() |
7361b3d403 | ||
![]() |
eca5639e31 | ||
![]() |
dd15f3aa45 | ||
![]() |
afe55a5ff5 | ||
![]() |
810568e179 | ||
![]() |
b06d14fec7 | ||
![]() |
495b3a9296 | ||
![]() |
f74dbdf8ed | ||
![]() |
22ba8b3acf | ||
![]() |
9baa2c9fad | ||
![]() |
f07e838e6f | ||
![]() |
a3d744f426 | ||
![]() |
f06b1d5d51 | ||
![]() |
0e34a77add | ||
![]() |
94c532889b | ||
![]() |
8018abe0a2 | ||
![]() |
5d791b63bf | ||
![]() |
eea3d5db6c | ||
![]() |
df422b2219 | ||
![]() |
5f7b6c3c66 | ||
![]() |
1596ca71a5 | ||
![]() |
7a217336dc | ||
![]() |
0391ec99b4 | ||
![]() |
6fe4d8162b | ||
![]() |
25a2bf603f | ||
![]() |
903a3b9660 | ||
![]() |
85504b1487 | ||
![]() |
8b04ab52d6 | ||
![]() |
7f9be03e5c | ||
![]() |
999c19052c | ||
![]() |
6b5e8c588d | ||
![]() |
a00e62233f | ||
![]() |
68cfb092fc | ||
![]() |
fffa4f9bea | ||
![]() |
108a212108 | ||
![]() |
03b06de907 | ||
![]() |
08a128cd87 | ||
![]() |
84721a7258 | ||
![]() |
702e8ec34f | ||
![]() |
26ebd5ae7e | ||
![]() |
65e820e4d5 | ||
![]() |
68210d8a3e | ||
![]() |
a5e1e5ebc8 | ||
![]() |
0aa2aaaf9d | ||
![]() |
1898385086 | ||
![]() |
0628e3d3f7 | ||
![]() |
8ebf4d76d6 | ||
![]() |
c6afc6a205 | ||
![]() |
4e06944c9f | ||
![]() |
127315528a | ||
![]() |
2a55ba6d07 | ||
![]() |
554ffc728d | ||
![]() |
1e75a518a6 | ||
![]() |
70fa80bab9 | ||
![]() |
4b02199c9b | ||
![]() |
4f350c84c3 | ||
![]() |
7a02854e2d | ||
![]() |
87e7b792bb | ||
![]() |
a6420aff9b | ||
![]() |
addc756fbf | ||
![]() |
ced1737bdc | ||
![]() |
82fc261743 | ||
![]() |
8d12227227 | ||
![]() |
7d0fa3b86d | ||
![]() |
418897c5de | ||
![]() |
c058b4744b | ||
![]() |
dc649e6142 | ||
![]() |
093176d10b | ||
![]() |
7f268a6557 | ||
![]() |
2d500fc35a | ||
![]() |
bb8e66e479 | ||
![]() |
cdda418c2d | ||
![]() |
1f316708fd | ||
![]() |
03d3cbe11e | ||
![]() |
fa46586417 | ||
![]() |
12e8a212d4 | ||
![]() |
5081d2fba2 | ||
![]() |
e407399d03 | ||
![]() |
4254a22d79 | ||
![]() |
c9b00d7bfe | ||
![]() |
f53b71bfe7 | ||
![]() |
7028e08390 | ||
![]() |
ec94c8c1b4 | ||
![]() |
f3adb25c82 | ||
![]() |
9603f2b5aa | ||
![]() |
ae2ffd5b40 | ||
![]() |
2e838840e0 | ||
![]() |
dbf12ff88e | ||
![]() |
1862428a0f | ||
![]() |
50892f7401 | ||
![]() |
fec13cdc87 | ||
![]() |
ed229166ee | ||
![]() |
6c795e1238 | ||
![]() |
7ee7964799 | ||
![]() |
7acd6e42fe | ||
![]() |
9a6d6bb68a | ||
![]() |
37bf6dec5e | ||
![]() |
ed3e7cbfaf | ||
![]() |
df6f2f3585 | ||
![]() |
b2ca6480b8 | ||
![]() |
a899de2b98 | ||
![]() |
31ad317a57 | ||
![]() |
5ec8279d38 | ||
![]() |
7526def448 | ||
![]() |
e4fb9eeb52 | ||
![]() |
cb3727b524 | ||
![]() |
39dff8a93f | ||
![]() |
23326d179a | ||
![]() |
6768a2c1fe | ||
![]() |
1c345df37d | ||
![]() |
c768b1750e | ||
![]() |
707a227aca | ||
![]() |
f418b72c64 | ||
![]() |
5425fa64fe | ||
![]() |
e4cfca0ffd | ||
![]() |
dd85760b7a | ||
![]() |
819602f77c |
430 changed files with 22157 additions and 10099 deletions
17
.github/workflows/build.yml
vendored
17
.github/workflows/build.yml
vendored
|
@ -19,14 +19,25 @@ jobs:
|
|||
os: macos-latest
|
||||
runtime: osx-arm64
|
||||
- name : Linux
|
||||
os: ubuntu-20.04
|
||||
os: ubuntu-latest
|
||||
runtime: linux-x64
|
||||
container: ubuntu:20.04
|
||||
- name : Linux (arm64)
|
||||
os: ubuntu-20.04
|
||||
os: ubuntu-latest
|
||||
runtime: linux-arm64
|
||||
container: ubuntu:20.04
|
||||
name: Build ${{ matrix.name }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
container: ${{ matrix.container || '' }}
|
||||
steps:
|
||||
- name: Install common CLI tools
|
||||
if: ${{ startsWith(matrix.runtime, 'linux-') }}
|
||||
run: |
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
ln -fs /usr/share/zoneinfo/Etc/UTC /etc/localtime
|
||||
apt-get update
|
||||
apt-get install -y sudo
|
||||
sudo apt-get install -y curl wget git unzip zip libicu66 tzdata clang
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v4
|
||||
- name: Setup .NET
|
||||
|
@ -47,7 +58,7 @@ jobs:
|
|||
if: ${{ matrix.runtime == 'linux-arm64' }}
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install clang llvm gcc-aarch64-linux-gnu zlib1g-dev:arm64
|
||||
sudo apt-get install -y llvm gcc-aarch64-linux-gnu
|
||||
- name: Build
|
||||
run: dotnet build -c Release
|
||||
- name: Publish
|
||||
|
|
5
.github/workflows/localization-check.yml
vendored
5
.github/workflows/localization-check.yml
vendored
|
@ -4,7 +4,6 @@ on:
|
|||
branches: [ develop ]
|
||||
paths:
|
||||
- 'src/Resources/Locales/**'
|
||||
- 'README.md'
|
||||
workflow_dispatch:
|
||||
workflow_call:
|
||||
|
||||
|
@ -32,8 +31,8 @@ jobs:
|
|||
git config --global user.name 'github-actions[bot]'
|
||||
git config --global user.email 'github-actions[bot]@users.noreply.github.com'
|
||||
if [ -n "$(git status --porcelain)" ]; then
|
||||
git add README.md TRANSLATION.md
|
||||
git commit -m 'doc: Update translation status and missing keys'
|
||||
git add TRANSLATION.md src/Resources/Locales/*.axaml
|
||||
git commit -m 'doc: Update translation status and sort locale files'
|
||||
git push
|
||||
else
|
||||
echo "No changes to commit"
|
||||
|
|
22
.github/workflows/package.yml
vendored
22
.github/workflows/package.yml
vendored
|
@ -7,12 +7,12 @@ on:
|
|||
required: true
|
||||
type: string
|
||||
jobs:
|
||||
windows-portable:
|
||||
name: Package portable Windows app
|
||||
runs-on: ubuntu-latest
|
||||
windows:
|
||||
name: Package Windows
|
||||
runs-on: windows-2019
|
||||
strategy:
|
||||
matrix:
|
||||
runtime: [win-x64, win-arm64]
|
||||
runtime: [ win-x64, win-arm64 ]
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v4
|
||||
|
@ -22,10 +22,11 @@ jobs:
|
|||
name: sourcegit.${{ matrix.runtime }}
|
||||
path: build/SourceGit
|
||||
- name: Package
|
||||
shell: bash
|
||||
env:
|
||||
VERSION: ${{ inputs.version }}
|
||||
RUNTIME: ${{ matrix.runtime }}
|
||||
run: ./build/scripts/package.windows-portable.sh
|
||||
run: ./build/scripts/package.windows.sh
|
||||
- name: Upload package artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
|
@ -36,7 +37,7 @@ jobs:
|
|||
with:
|
||||
name: sourcegit.${{ matrix.runtime }}
|
||||
osx-app:
|
||||
name: Package OSX app
|
||||
name: Package macOS
|
||||
runs-on: macos-latest
|
||||
strategy:
|
||||
matrix:
|
||||
|
@ -69,6 +70,7 @@ jobs:
|
|||
linux:
|
||||
name: Package Linux
|
||||
runs-on: ubuntu-latest
|
||||
container: ubuntu:20.04
|
||||
strategy:
|
||||
matrix:
|
||||
runtime: [linux-x64, linux-arm64]
|
||||
|
@ -77,9 +79,10 @@ jobs:
|
|||
uses: actions/checkout@v4
|
||||
- name: Download package dependencies
|
||||
run: |
|
||||
sudo add-apt-repository universe
|
||||
sudo apt-get update
|
||||
sudo apt-get install desktop-file-utils rpm libfuse2
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
ln -fs /usr/share/zoneinfo/Etc/UTC /etc/localtime
|
||||
apt-get update
|
||||
apt-get install -y curl wget git dpkg-dev fakeroot tzdata zip unzip desktop-file-utils rpm libfuse2 file build-essential binutils
|
||||
- name: Download build
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
|
@ -89,6 +92,7 @@ jobs:
|
|||
env:
|
||||
VERSION: ${{ inputs.version }}
|
||||
RUNTIME: ${{ matrix.runtime }}
|
||||
APPIMAGE_EXTRACT_AND_RUN: 1
|
||||
run: |
|
||||
mkdir build/SourceGit
|
||||
tar -xf "build/sourcegit.${{ matrix.runtime }}.tar" -C build/SourceGit
|
||||
|
|
39
.github/workflows/publish-packages.yml
vendored
39
.github/workflows/publish-packages.yml
vendored
|
@ -1,39 +0,0 @@
|
|||
name: Publish to Buildkite
|
||||
on:
|
||||
workflow_call:
|
||||
secrets:
|
||||
BUILDKITE_TOKEN:
|
||||
required: true
|
||||
jobs:
|
||||
publish:
|
||||
name: Publish to Buildkite
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
runtime: [linux-x64, linux-arm64]
|
||||
steps:
|
||||
- name: Download package artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: package.${{ matrix.runtime }}
|
||||
path: packages
|
||||
|
||||
- name: Publish DEB package
|
||||
env:
|
||||
BUILDKITE_TOKEN: ${{ secrets.BUILDKITE_TOKEN }}
|
||||
run: |
|
||||
FILE=$(echo packages/*.deb)
|
||||
curl -X POST https://api.buildkite.com/v2/packages/organizations/sourcegit/registries/sourcegit-deb/packages \
|
||||
-H "Authorization: Bearer $BUILDKITE_TOKEN" \
|
||||
-F "file=@$FILE" \
|
||||
--fail
|
||||
|
||||
- name: Publish RPM package
|
||||
env:
|
||||
BUILDKITE_TOKEN: ${{ secrets.BUILDKITE_TOKEN }}
|
||||
run: |
|
||||
FILE=$(echo packages/*.rpm)
|
||||
curl -X POST https://api.buildkite.com/v2/packages/organizations/sourcegit/registries/sourcegit-rpm/packages \
|
||||
-H "Authorization: Bearer $BUILDKITE_TOKEN" \
|
||||
-F "file=@$FILE" \
|
||||
--fail
|
8
.github/workflows/release.yml
vendored
8
.github/workflows/release.yml
vendored
|
@ -24,12 +24,6 @@ jobs:
|
|||
uses: ./.github/workflows/package.yml
|
||||
with:
|
||||
version: ${{ needs.version.outputs.version }}
|
||||
publish-packages:
|
||||
needs: [package, version]
|
||||
name: Publish Packages
|
||||
uses: ./.github/workflows/publish-packages.yml
|
||||
secrets:
|
||||
BUILDKITE_TOKEN: ${{ secrets.BUILDKITE_TOKEN }}
|
||||
release:
|
||||
needs: [package, version]
|
||||
name: Release
|
||||
|
@ -44,7 +38,7 @@ jobs:
|
|||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
TAG: ${{ github.ref_name }}
|
||||
VERSION: ${{ needs.version.outputs.version }}
|
||||
run: gh release create "$TAG" -t "Release $VERSION" --notes-from-tag
|
||||
run: gh release create "$TAG" -t "$VERSION" --notes-from-tag
|
||||
- name: Download artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
|
|
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -37,3 +37,5 @@ build/*.deb
|
|||
build/*.rpm
|
||||
build/*.AppImage
|
||||
SourceGit.app/
|
||||
build.command
|
||||
src/Properties/launchSettings.json
|
||||
|
|
2
LICENSE
2
LICENSE
|
@ -1,6 +1,6 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2024 sourcegit
|
||||
Copyright (c) 2025 sourcegit
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
|
|
95
README.md
95
README.md
|
@ -11,16 +11,16 @@
|
|||
* Supports Windows/macOS/Linux
|
||||
* Opensource/Free
|
||||
* Fast
|
||||
* Deutsch/English/Español/Français/Italiano/Português/Русский/简体中文/繁體中文
|
||||
* Deutsch/English/Español/Français/Italiano/Português/Русский/Українська/简体中文/繁體中文/日本語/தமிழ் (Tamil)
|
||||
* Built-in light/dark themes
|
||||
* Customize theme
|
||||
* Visual commit graph
|
||||
* Supports SSH access with each remote
|
||||
* GIT commands with GUI
|
||||
* Clone/Fetch/Pull/Push...
|
||||
* Merge/Rebase/Reset/Revert/Amend/Cherry-pick...
|
||||
* Amend/Reword
|
||||
* Interactive rebase (Basic)
|
||||
* Merge/Rebase/Reset/Revert/Cherry-pick...
|
||||
* Amend/Reword/Squash
|
||||
* Interactive rebase
|
||||
* Branches
|
||||
* Remotes
|
||||
* Tags
|
||||
|
@ -35,11 +35,14 @@
|
|||
* Revision Diffs
|
||||
* Branch Diff
|
||||
* Image Diff - Side-By-Side/Swipe/Blend
|
||||
* Git command logs
|
||||
* Search commits
|
||||
* GitFlow
|
||||
* Git LFS
|
||||
* Bisect
|
||||
* Issue Link
|
||||
* Workspace
|
||||
* Custom Action
|
||||
* Using AI to generate commit message (C# port of [anjerodev/commitollama](https://github.com/anjerodev/commitollama))
|
||||
|
||||
> [!WARNING]
|
||||
|
@ -47,24 +50,25 @@
|
|||
|
||||
## Translation Status
|
||||
|
||||
[](TRANSLATION.md) [](TRANSLATION.md) [](TRANSLATION.md) [](TRANSLATION.md) [](TRANSLATION.md) [](TRANSLATION.md) [](TRANSLATION.md) [](TRANSLATION.md) [](TRANSLATION.md)
|
||||
You can find the current translation status in [TRANSLATION.md](https://github.com/sourcegit-scm/sourcegit/blob/develop/TRANSLATION.md)
|
||||
|
||||
## How to Use
|
||||
|
||||
**To use this tool, you need to install Git(>=2.23.0) first.**
|
||||
**To use this tool, you need to install Git(>=2.25.1) first.**
|
||||
|
||||
You can download the latest stable from [Releases](https://github.com/sourcegit-scm/sourcegit/releases/latest) or download workflow artifacts from [Github Actions](https://github.com/sourcegit-scm/sourcegit/actions) to try this app based on latest commits.
|
||||
You can download the latest stable from [Releases](https://github.com/sourcegit-scm/sourcegit/releases/latest) or download workflow artifacts from [GitHub Actions](https://github.com/sourcegit-scm/sourcegit/actions) to try this app based on latest commits.
|
||||
|
||||
This software creates a folder `$"{System.Environment.SpecialFolder.ApplicationData}/SourceGit"`, which is platform-dependent, to store user settings, downloaded avatars and crash logs.
|
||||
|
||||
| OS | PATH |
|
||||
|---------|-----------------------------------------------------|
|
||||
| Windows | `C:\Users\USER_NAME\AppData\Roaming\SourceGit` |
|
||||
| Windows | `%APPDATA%\SourceGit` |
|
||||
| Linux | `${HOME}/.config/SourceGit` or `${HOME}/.sourcegit` |
|
||||
| macOS | `${HOME}/Library/Application Support/SourceGit` |
|
||||
|
||||
> [!TIP]
|
||||
> You can open the app data dir from the main menu.
|
||||
> * You can open this data storage directory from the main menu `Open Data Storage Directory`.
|
||||
> * You can create a `data` folder next to the `SourceGit` executable to force this app to store data (user settings, downloaded avatars and crash logs) into it (Portable-Mode). Only works on Windows.
|
||||
|
||||
For **Windows** users:
|
||||
|
||||
|
@ -75,12 +79,12 @@ For **Windows** users:
|
|||
```
|
||||
> [!NOTE]
|
||||
> `winget` will install this software as a commandline tool. You need run `SourceGit` from console or `Win+R` at the first time. Then you can add it to the taskbar.
|
||||
* You can install the latest stable by `scoope` with follow commands:
|
||||
* You can install the latest stable by `scoop` with follow commands:
|
||||
```shell
|
||||
scoop bucket add extras
|
||||
scoop install sourcegit
|
||||
```
|
||||
* Portable versions can be found in [Releases](https://github.com/sourcegit-scm/sourcegit/releases/latest)
|
||||
* Pre-built binaries can be found in [Releases](https://github.com/sourcegit-scm/sourcegit/releases/latest)
|
||||
|
||||
For **macOS** users:
|
||||
|
||||
|
@ -89,7 +93,7 @@ For **macOS** users:
|
|||
brew tap ybeapps/homebrew-sourcegit
|
||||
brew install --cask --no-quarantine sourcegit
|
||||
```
|
||||
* If you want to install `SourceGit.app` from Github Release manually, you need run following command to make sure it works:
|
||||
* If you want to install `SourceGit.app` from GitHub Release manually, you need run following command to make sure it works:
|
||||
```shell
|
||||
sudo xattr -cr /Applications/SourceGit.app
|
||||
```
|
||||
|
@ -98,49 +102,45 @@ For **macOS** users:
|
|||
|
||||
For **Linux** users:
|
||||
|
||||
* For Debian/Ubuntu based distributions, you can add the `sourcegit` repository by following:
|
||||
You may need to install curl and/or gpg first, if you're on a very minimal host:
|
||||
* Thanks [@aikawayataro](https://github.com/aikawayataro) for providing `rpm` and `deb` repositories, hosted on [Codeberg](https://codeberg.org/yataro/-/packages).
|
||||
|
||||
`deb` how to:
|
||||
```shell
|
||||
apt update && apt install curl gpg -y
|
||||
curl https://codeberg.org/api/packages/yataro/debian/repository.key | sudo tee /etc/apt/keyrings/sourcegit.asc
|
||||
echo "deb [signed-by=/etc/apt/keyrings/sourcegit.asc, arch=amd64,arm64] https://codeberg.org/api/packages/yataro/debian generic main" | sudo tee /etc/apt/sources.list.d/sourcegit.list
|
||||
sudo apt update
|
||||
sudo apt install sourcegit
|
||||
```
|
||||
Install the registry signing key:
|
||||
|
||||
`rpm` how to:
|
||||
```shell
|
||||
curl -fsSL "https://packages.buildkite.com/sourcegit/sourcegit-deb/gpgkey" | gpg --dearmor -o /etc/apt/keyrings/sourcegit_sourcegit-deb-archive-keyring.gpg
|
||||
curl https://codeberg.org/api/packages/yataro/rpm.repo | sed -e 's/gpgcheck=1/gpgcheck=0/' > sourcegit.repo
|
||||
|
||||
# Fedora 41 and newer
|
||||
sudo dnf config-manager addrepo --from-repofile=./sourcegit.repo
|
||||
# Fedora 40 and earlier
|
||||
sudo dnf config-manager --add-repo ./sourcegit.repo
|
||||
|
||||
sudo dnf install sourcegit
|
||||
```
|
||||
Configure the source:
|
||||
```shell
|
||||
echo -e "deb [signed-by=/etc/apt/keyrings/sourcegit_sourcegit-deb-archive-keyring.gpg] https://packages.buildkite.com/sourcegit/sourcegit-deb/any/ any main\ndeb-src [signed-by=/etc/apt/keyrings/sourcegit_sourcegit-deb-archive-keyring.gpg] https://packages.buildkite.com/sourcegit/sourcegit-deb/any/ any main" > /etc/apt/sources.list.d/buildkite-sourcegit-sourcegit-deb.list
|
||||
```
|
||||
Update your local repository and install the package:
|
||||
```shell
|
||||
apt update && apt install sourcegit
|
||||
```
|
||||
* For RHEL/Fedora based distributions, you can add the `sourcegit` repository by following:
|
||||
Configure the source:
|
||||
```shell
|
||||
sudo sh -c 'echo -e "[sourcegit-rpm]\nname=sourcegit-rpm\nbaseurl=https://packages.buildkite.com/sourcegit/sourcegit-rpm/rpm_any/rpm_any/\$basearch\nenabled=1\nrepo_gpgcheck=1\ngpgcheck=0\ngpgkey=https://packages.buildkite.com/sourcegit/sourcegit-rpm/gpgkey\npriority=1"' > /etc/yum.repos.d/sourcegit-rpm.repo
|
||||
```
|
||||
Install the package with this command:
|
||||
```shell
|
||||
sudo dnf install -y sourcegit
|
||||
```
|
||||
* `Appimage` files can be found on [AppimageHub](https://appimage.github.io/SourceGit/)
|
||||
* `xdg-open` must be installed to support open native file manager.
|
||||
* Make sure [git-credential-manager](https://github.com/git-ecosystem/git-credential-manager/releases) is installed on your linux.
|
||||
|
||||
If your distribution isn't using `dnf`, please refer to the documentation of your distribution on how to add an `rpm` repository.
|
||||
* `AppImage` files can be found on [AppImage hub](https://appimage.github.io/SourceGit/), `xdg-open` (`xdg-utils`) must be installed to support open native file manager.
|
||||
* Make sure [git-credential-manager](https://github.com/git-ecosystem/git-credential-manager/releases) is installed on your Linux.
|
||||
* Maybe you need to set environment variable `AVALONIA_SCREEN_SCALE_FACTORS`. See https://github.com/AvaloniaUI/Avalonia/wiki/Configuring-X11-per-monitor-DPI.
|
||||
* If you can NOT type accented characters, such as `ê`, `ó`, try to set the environment variable `AVALONIA_IM_MODULE` to `none`.
|
||||
|
||||
## OpenAI
|
||||
|
||||
This software supports using OpenAI or other AI service that has an OpenAI comaptible HTTP API to generate commit message. You need configurate the service in `Preference` window.
|
||||
This software supports using OpenAI or other AI service that has an OpenAI compatible HTTP API to generate commit message. You need configurate the service in `Preference` window.
|
||||
|
||||
For `OpenAI`:
|
||||
|
||||
* `Server` must be `https://api.openai.com/v1/chat/completions`
|
||||
* `Server` must be `https://api.openai.com/v1`
|
||||
|
||||
For other AI service:
|
||||
|
||||
* The `Server` should fill in a URL equivalent to OpenAI's `https://api.openai.com/v1/chat/completions`. For example, when using `Ollama`, it should be `http://localhost:11434/v1/chat/completions` instead of `http://localhost:11434/api/generate`
|
||||
* The `Server` should fill in a URL equivalent to OpenAI's `https://api.openai.com/v1`. For example, when using `Ollama`, it should be `http://localhost:11434/v1` instead of `http://localhost:11434/api/generate`
|
||||
* The `API Key` is optional that depends on the service
|
||||
|
||||
## External Tools
|
||||
|
@ -159,7 +159,7 @@ This app supports open repository in external tools listed in the table below.
|
|||
|
||||
> [!NOTE]
|
||||
> This app will try to find those tools based on some pre-defined or expected locations automatically. If you are using one portable version of these tools, it will not be detected by this app.
|
||||
> To solve this problem you can add a file named `external_editors.json` in app data dir and provide the path directly. For example:
|
||||
> To solve this problem you can add a file named `external_editors.json` in app data storage directory and provide the path directly. For example:
|
||||
```json
|
||||
{
|
||||
"tools": {
|
||||
|
@ -189,6 +189,19 @@ This app supports open repository in external tools listed in the table below.
|
|||
|
||||
Everyone is welcome to submit a PR. Please make sure your PR is based on the latest `develop` branch and the target branch of PR is `develop`.
|
||||
|
||||
In short, here are the commands to get started once [.NET tools are installed](https://dotnet.microsoft.com/en-us/download):
|
||||
|
||||
```sh
|
||||
dotnet nuget add source https://api.nuget.org/v3/index.json -n nuget.org
|
||||
dotnet restore
|
||||
dotnet build
|
||||
dotnet run --project src/SourceGit.csproj
|
||||
```
|
||||
|
||||
Thanks to all the people who contribute.
|
||||
|
||||
[](https://github.com/sourcegit-scm/sourcegit/graphs/contributors)
|
||||
|
||||
## Third-Party Components
|
||||
|
||||
For detailed license information, see [THIRD-PARTY-LICENSES.md](THIRD-PARTY-LICENSES.md).
|
||||
|
|
|
@ -18,7 +18,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{
|
|||
.github\workflows\package.yml = .github\workflows\package.yml
|
||||
.github\workflows\release.yml = .github\workflows\release.yml
|
||||
.github\workflows\localization-check.yml = .github\workflows\localization-check.yml
|
||||
.github\workflows\publish-packages.yml = .github\workflows\publish-packages.yml
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{49A7C2D6-558C-4FAA-8F5D-EEE81497AED7}"
|
||||
|
@ -61,6 +60,8 @@ EndProject
|
|||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "DEBIAN", "DEBIAN", "{F101849D-BDB7-40D4-A516-751150C3CCFC}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
build\resources\deb\DEBIAN\control = build\resources\deb\DEBIAN\control
|
||||
build\resources\deb\DEBIAN\preinst = build\resources\deb\DEBIAN\preinst
|
||||
build\resources\deb\DEBIAN\prerm = build\resources\deb\DEBIAN\prerm
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "rpm", "rpm", "{9BA0B044-0CC9-46F8-B551-204F149BF45D}"
|
||||
|
@ -81,7 +82,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "scripts", "scripts", "{C54D
|
|||
build\scripts\localization-check.js = build\scripts\localization-check.js
|
||||
build\scripts\package.linux.sh = build\scripts\package.linux.sh
|
||||
build\scripts\package.osx-app.sh = build\scripts\package.osx-app.sh
|
||||
build\scripts\package.windows-portable.sh = build\scripts\package.windows-portable.sh
|
||||
build\scripts\package.windows.sh = build\scripts\package.windows.sh
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Global
|
||||
|
|
86
THIRD-PARTY-LICENSES.md
Normal file
86
THIRD-PARTY-LICENSES.md
Normal file
|
@ -0,0 +1,86 @@
|
|||
# Third-Party Licenses
|
||||
|
||||
This project incorporates components from the following third parties:
|
||||
|
||||
## Packages
|
||||
|
||||
### AvaloniaUI
|
||||
|
||||
- **Source**: https://github.com/AvaloniaUI/Avalonia
|
||||
- **Version**: 11.2.5
|
||||
- **License**: MIT License
|
||||
- **License Link**: https://github.com/AvaloniaUI/Avalonia/blob/master/licence.md
|
||||
|
||||
### AvaloniaEdit
|
||||
|
||||
- **Source**: https://github.com/AvaloniaUI/AvaloniaEdit
|
||||
- **Version**: 11.2.0
|
||||
- **License**: MIT License
|
||||
- **License Link**: https://github.com/AvaloniaUI/AvaloniaEdit/blob/master/LICENSE
|
||||
|
||||
### LiveChartsCore.SkiaSharpView.Avalonia
|
||||
|
||||
- **Source**: https://github.com/beto-rodriguez/LiveCharts2
|
||||
- **Version**: 2.0.0-rc5.4
|
||||
- **License**: MIT License
|
||||
- **License Link**: https://github.com/beto-rodriguez/LiveCharts2/blob/master/LICENSE
|
||||
|
||||
### TextMateSharp
|
||||
|
||||
- **Source**: https://github.com/danipen/TextMateSharp
|
||||
- **Version**: 1.0.66
|
||||
- **License**: MIT License
|
||||
- **License Link**: https://github.com/danipen/TextMateSharp/blob/master/LICENSE.md
|
||||
|
||||
### OpenAI .NET SDK
|
||||
|
||||
- **Source**: https://github.com/openai/openai-dotnet
|
||||
- **Version**: 2.2.0-beta2
|
||||
- **License**: MIT License
|
||||
- **License Link**: https://github.com/openai/openai-dotnet/blob/main/LICENSE
|
||||
|
||||
### Azure.AI.OpenAI
|
||||
|
||||
- **Source**: https://github.com/Azure/azure-sdk-for-net
|
||||
- **Version**: 2.2.0-beta2
|
||||
- **License**: MIT License
|
||||
- **License Link**: https://github.com/Azure/azure-sdk-for-net/blob/main/LICENSE.txt
|
||||
|
||||
## Fonts
|
||||
|
||||
### JetBrainsMono
|
||||
|
||||
- **Source**: https://github.com/JetBrains/JetBrainsMono
|
||||
- **Commit**: v2.304
|
||||
- **License**: SIL Open Font License, Version 1.1
|
||||
- **License Link**: https://github.com/JetBrains/JetBrainsMono/blob/v2.304/OFL.txt
|
||||
|
||||
## Grammar Files
|
||||
|
||||
### haxe-TmLanguage
|
||||
|
||||
- **Source**: https://github.com/vshaxe/haxe-TmLanguage
|
||||
- **Commit**: ddad8b4c6d0781ac20be0481174ec1be772c5da5
|
||||
- **License**: MIT License
|
||||
- **License Link**: https://github.com/vshaxe/haxe-TmLanguage/blob/ddad8b4c6d0781ac20be0481174ec1be772c5da5/LICENSE.md
|
||||
|
||||
### coc-toml
|
||||
|
||||
- **Source**: https://github.com/kkiyama117/coc-toml
|
||||
- **Commit**: aac3e0c65955c03314b2733041b19f903b7cc447
|
||||
- **License**: MIT License
|
||||
- **License Link**: https://github.com/kkiyama117/coc-toml/blob/aac3e0c65955c03314b2733041b19f903b7cc447/LICENSE
|
||||
|
||||
### eclipse-buildship
|
||||
|
||||
- **Source**: https://github.com/eclipse/buildship
|
||||
- **Commit**: 6bb773e7692f913dec27105129ebe388de34e68b
|
||||
- **License**: Eclipse Public License 1.0
|
||||
- **License Link**: https://github.com/eclipse-buildship/buildship/blob/6bb773e7692f913dec27105129ebe388de34e68b/README.md
|
||||
|
||||
### vscode-jsp-lang
|
||||
|
||||
- **Source**: https://github.com/samuel-weinhardt/vscode-jsp-lang
|
||||
- **Commit**: 0e89ecdb13650dbbe5a1e85b47b2e1530bf2f355
|
||||
- **License**: MIT License
|
||||
- **License Link**: https://github.com/samuel-weinhardt/vscode-jsp-lang/blob/0e89ecdb13650dbbe5a1e85b47b2e1530bf2f355/LICENSE
|
585
TRANSLATION.md
585
TRANSLATION.md
|
@ -1,213 +1,496 @@
|
|||
### de_DE.axaml: 97.50%
|
||||
# Translation Status
|
||||
|
||||
This document shows the translation status of each locale file in the repository.
|
||||
|
||||
## Details
|
||||
|
||||
### 
|
||||
|
||||
### 
|
||||
|
||||
<details>
|
||||
<summary>Missing Keys</summary>
|
||||
<summary>Missing keys in de_DE.axaml</summary>
|
||||
|
||||
- Text.BranchCM.MergeMultiBranches
|
||||
- Text.CommitCM.Merge
|
||||
- Text.CommitCM.MergeMultiple
|
||||
- Text.CommitDetail.Files.Search
|
||||
- Text.Diff.UseBlockNavigation
|
||||
- Text.FileCM.ResolveUsing
|
||||
- Text.Hotkeys.Global.Clone
|
||||
- Text.InProgress.CherryPick.Head
|
||||
- Text.InProgress.Merge.Operating
|
||||
- Text.InProgress.Rebase.StoppedAt
|
||||
- Text.InProgress.Revert.Head
|
||||
- Text.Merge.Source
|
||||
- Text.MergeMultiple
|
||||
- Text.MergeMultiple.CommitChanges
|
||||
- Text.MergeMultiple.Strategy
|
||||
- Text.MergeMultiple.Targets
|
||||
- Text.Repository.Skip
|
||||
- Text.WorkingCopy.CommitToEdit
|
||||
- Text.Avatar.Load
|
||||
- Text.BranchCM.ResetToSelectedCommit
|
||||
- Text.CommitDetail.Changes.Count
|
||||
- Text.CreateBranch.OverwriteExisting
|
||||
- Text.DeinitSubmodule
|
||||
- Text.DeinitSubmodule.Force
|
||||
- Text.DeinitSubmodule.Path
|
||||
- Text.Diff.Submodule.Deleted
|
||||
- Text.GitFlow.FinishWithPush
|
||||
- Text.GitFlow.FinishWithSquash
|
||||
- Text.Hotkeys.Global.SwitchWorkspace
|
||||
- Text.Hotkeys.Global.SwitchTab
|
||||
- Text.Hotkeys.TextEditor.OpenExternalMergeTool
|
||||
- Text.Launcher.Workspaces
|
||||
- Text.Launcher.Pages
|
||||
- Text.Pull.RecurseSubmodules
|
||||
- Text.Repository.ClearStashes
|
||||
- Text.Repository.ShowSubmodulesAsTree
|
||||
- Text.ResetWithoutCheckout
|
||||
- Text.ResetWithoutCheckout.MoveTo
|
||||
- Text.ResetWithoutCheckout.Target
|
||||
- Text.Submodule.Deinit
|
||||
- Text.Submodule.Status
|
||||
- Text.Submodule.Status.Modified
|
||||
- Text.Submodule.Status.NotInited
|
||||
- Text.Submodule.Status.RevisionChanged
|
||||
- Text.Submodule.Status.Unmerged
|
||||
- Text.Submodule.URL
|
||||
- Text.WorkingCopy.ResetAuthor
|
||||
|
||||
</details>
|
||||
|
||||
### es_ES.axaml: 97.78%
|
||||
|
||||
### 
|
||||
|
||||
<details>
|
||||
<summary>Missing Keys</summary>
|
||||
<summary>Missing keys in es_ES.axaml</summary>
|
||||
|
||||
- Text.BranchCM.MergeMultiBranches
|
||||
- Text.CommitCM.Merge
|
||||
- Text.CommitCM.MergeMultiple
|
||||
- Text.Diff.UseBlockNavigation
|
||||
- Text.FileCM.ResolveUsing
|
||||
- Text.Hotkeys.Global.Clone
|
||||
- Text.InProgress.CherryPick.Head
|
||||
- Text.InProgress.Merge.Operating
|
||||
- Text.InProgress.Rebase.StoppedAt
|
||||
- Text.InProgress.Revert.Head
|
||||
- Text.Merge.Source
|
||||
- Text.MergeMultiple
|
||||
- Text.MergeMultiple.CommitChanges
|
||||
- Text.MergeMultiple.Strategy
|
||||
- Text.MergeMultiple.Targets
|
||||
- Text.Repository.Skip
|
||||
- Text.Avatar.Load
|
||||
|
||||
</details>
|
||||
|
||||
### fr_FR.axaml: 95.00%
|
||||
|
||||
### 
|
||||
|
||||
<details>
|
||||
<summary>Missing Keys</summary>
|
||||
<summary>Missing keys in fr_FR.axaml</summary>
|
||||
|
||||
- Text.BranchCM.MergeMultiBranches
|
||||
- Text.CherryPick.AppendSourceToMessage
|
||||
- Text.CherryPick.Mainline.Tips
|
||||
- Text.CommitCM.CherryPickMultiple
|
||||
- Text.CommitCM.Merge
|
||||
- Text.CommitCM.MergeMultiple
|
||||
- Text.CommitDetail.Files.Search
|
||||
- Text.Diff.UseBlockNavigation
|
||||
- Text.Fetch.Force
|
||||
- Text.FileCM.ResolveUsing
|
||||
- Text.Hotkeys.Global.Clone
|
||||
- Text.InProgress.CherryPick.Head
|
||||
- Text.InProgress.Merge.Operating
|
||||
- Text.InProgress.Rebase.StoppedAt
|
||||
- Text.InProgress.Revert.Head
|
||||
- Text.Merge.Source
|
||||
- Text.MergeMultiple
|
||||
- Text.MergeMultiple.CommitChanges
|
||||
- Text.MergeMultiple.Strategy
|
||||
- Text.MergeMultiple.Targets
|
||||
- Text.Preference.Appearance.FontSize
|
||||
- Text.Preference.Appearance.FontSize.Default
|
||||
- Text.Preference.Appearance.FontSize.Editor
|
||||
- Text.Preference.General.ShowChildren
|
||||
- Text.Repository.CustomActions
|
||||
- Text.Avatar.Load
|
||||
- Text.Bisect
|
||||
- Text.Bisect.Abort
|
||||
- Text.Bisect.Bad
|
||||
- Text.Bisect.Detecting
|
||||
- Text.Bisect.Good
|
||||
- Text.Bisect.Skip
|
||||
- Text.Bisect.WaitingForRange
|
||||
- Text.BranchCM.ResetToSelectedCommit
|
||||
- Text.Checkout.RecurseSubmodules
|
||||
- Text.CommitCM.CopyAuthor
|
||||
- Text.CommitCM.CopyCommitter
|
||||
- Text.CommitCM.CopySubject
|
||||
- Text.CommitDetail.Changes.Count
|
||||
- Text.CommitMessageTextBox.SubjectCount
|
||||
- Text.Configure.Git.PreferredMergeMode
|
||||
- Text.ConfirmEmptyCommit.Continue
|
||||
- Text.ConfirmEmptyCommit.NoLocalChanges
|
||||
- Text.ConfirmEmptyCommit.StageAllThenCommit
|
||||
- Text.ConfirmEmptyCommit.WithLocalChanges
|
||||
- Text.CreateBranch.OverwriteExisting
|
||||
- Text.DeinitSubmodule
|
||||
- Text.DeinitSubmodule.Force
|
||||
- Text.DeinitSubmodule.Path
|
||||
- Text.Diff.Submodule.Deleted
|
||||
- Text.GitFlow.FinishWithPush
|
||||
- Text.GitFlow.FinishWithSquash
|
||||
- Text.Hotkeys.Global.SwitchWorkspace
|
||||
- Text.Hotkeys.Global.SwitchTab
|
||||
- Text.Hotkeys.TextEditor.OpenExternalMergeTool
|
||||
- Text.Launcher.Workspaces
|
||||
- Text.Launcher.Pages
|
||||
- Text.Preferences.Git.IgnoreCRAtEOLInDiff
|
||||
- Text.Pull.RecurseSubmodules
|
||||
- Text.Repository.BranchSort
|
||||
- Text.Repository.BranchSort.ByCommitterDate
|
||||
- Text.Repository.BranchSort.ByName
|
||||
- Text.Repository.ClearStashes
|
||||
- Text.Repository.Search.ByContent
|
||||
- Text.Repository.ShowSubmodulesAsTree
|
||||
- Text.Repository.ViewLogs
|
||||
- Text.Repository.Visit
|
||||
- Text.ResetWithoutCheckout
|
||||
- Text.ResetWithoutCheckout.MoveTo
|
||||
- Text.ResetWithoutCheckout.Target
|
||||
- Text.Submodule.Deinit
|
||||
- Text.Submodule.Status
|
||||
- Text.Submodule.Status.Modified
|
||||
- Text.Submodule.Status.NotInited
|
||||
- Text.Submodule.Status.RevisionChanged
|
||||
- Text.Submodule.Status.Unmerged
|
||||
- Text.Submodule.URL
|
||||
- Text.ViewLogs
|
||||
- Text.ViewLogs.Clear
|
||||
- Text.ViewLogs.CopyLog
|
||||
- Text.ViewLogs.Delete
|
||||
- Text.WorkingCopy.ConfirmCommitWithFilter
|
||||
- Text.WorkingCopy.Conflicts.OpenExternalMergeTool
|
||||
- Text.WorkingCopy.Conflicts.OpenExternalMergeToolAllConflicts
|
||||
- Text.WorkingCopy.Conflicts.UseMine
|
||||
- Text.WorkingCopy.Conflicts.UseTheirs
|
||||
- Text.WorkingCopy.ResetAuthor
|
||||
|
||||
</details>
|
||||
|
||||
### 
|
||||
|
||||
<details>
|
||||
<summary>Missing keys in it_IT.axaml</summary>
|
||||
|
||||
- Text.Avatar.Load
|
||||
- Text.BranchCM.ResetToSelectedCommit
|
||||
- Text.CommitDetail.Changes.Count
|
||||
- Text.CreateBranch.OverwriteExisting
|
||||
- Text.DeinitSubmodule
|
||||
- Text.DeinitSubmodule.Force
|
||||
- Text.DeinitSubmodule.Path
|
||||
- Text.Diff.Submodule.Deleted
|
||||
- Text.Hotkeys.Global.SwitchWorkspace
|
||||
- Text.Hotkeys.Global.SwitchTab
|
||||
- Text.Launcher.Workspaces
|
||||
- Text.Launcher.Pages
|
||||
- Text.Pull.RecurseSubmodules
|
||||
- Text.Repository.ClearStashes
|
||||
- Text.ResetWithoutCheckout
|
||||
- Text.ResetWithoutCheckout.MoveTo
|
||||
- Text.ResetWithoutCheckout.Target
|
||||
- Text.Submodule.Deinit
|
||||
- Text.WorkingCopy.ResetAuthor
|
||||
|
||||
</details>
|
||||
|
||||
### 
|
||||
|
||||
<details>
|
||||
<summary>Missing keys in ja_JP.axaml</summary>
|
||||
|
||||
- Text.Avatar.Load
|
||||
- Text.Bisect
|
||||
- Text.Bisect.Abort
|
||||
- Text.Bisect.Bad
|
||||
- Text.Bisect.Detecting
|
||||
- Text.Bisect.Good
|
||||
- Text.Bisect.Skip
|
||||
- Text.Bisect.WaitingForRange
|
||||
- Text.BranchCM.CompareWithCurrent
|
||||
- Text.BranchCM.ResetToSelectedCommit
|
||||
- Text.Checkout.RecurseSubmodules
|
||||
- Text.CommitCM.CopyAuthor
|
||||
- Text.CommitCM.CopyCommitter
|
||||
- Text.CommitCM.CopySubject
|
||||
- Text.CommitDetail.Changes.Count
|
||||
- Text.CommitMessageTextBox.SubjectCount
|
||||
- Text.Configure.Git.PreferredMergeMode
|
||||
- Text.ConfirmEmptyCommit.Continue
|
||||
- Text.ConfirmEmptyCommit.NoLocalChanges
|
||||
- Text.ConfirmEmptyCommit.StageAllThenCommit
|
||||
- Text.ConfirmEmptyCommit.WithLocalChanges
|
||||
- Text.CreateBranch.OverwriteExisting
|
||||
- Text.DeinitSubmodule
|
||||
- Text.DeinitSubmodule.Force
|
||||
- Text.DeinitSubmodule.Path
|
||||
- Text.Diff.Submodule.Deleted
|
||||
- Text.GitFlow.FinishWithPush
|
||||
- Text.GitFlow.FinishWithSquash
|
||||
- Text.Hotkeys.Global.SwitchWorkspace
|
||||
- Text.Hotkeys.Global.SwitchTab
|
||||
- Text.Hotkeys.TextEditor.OpenExternalMergeTool
|
||||
- Text.Launcher.Workspaces
|
||||
- Text.Launcher.Pages
|
||||
- Text.Preferences.Git.IgnoreCRAtEOLInDiff
|
||||
- Text.Pull.RecurseSubmodules
|
||||
- Text.Repository.BranchSort
|
||||
- Text.Repository.BranchSort.ByCommitterDate
|
||||
- Text.Repository.BranchSort.ByName
|
||||
- Text.Repository.ClearStashes
|
||||
- Text.Repository.FilterCommits
|
||||
- Text.Repository.FilterCommits.Default
|
||||
- Text.Repository.FilterCommits.Exclude
|
||||
- Text.Repository.FilterCommits.Include
|
||||
- Text.Repository.HistoriesOrder
|
||||
- Text.Repository.HistoriesOrder.ByDate
|
||||
- Text.Repository.HistoriesOrder.Topo
|
||||
- Text.Repository.Skip
|
||||
- Text.ScanRepositories
|
||||
- Text.SHALinkCM.NavigateTo
|
||||
- Text.WorkingCopy.CommitToEdit
|
||||
- Text.Repository.Search.ByContent
|
||||
- Text.Repository.ShowSubmodulesAsTree
|
||||
- Text.Repository.ViewLogs
|
||||
- Text.Repository.Visit
|
||||
- Text.ResetWithoutCheckout
|
||||
- Text.ResetWithoutCheckout.MoveTo
|
||||
- Text.ResetWithoutCheckout.Target
|
||||
- Text.Submodule.Deinit
|
||||
- Text.Submodule.Status
|
||||
- Text.Submodule.Status.Modified
|
||||
- Text.Submodule.Status.NotInited
|
||||
- Text.Submodule.Status.RevisionChanged
|
||||
- Text.Submodule.Status.Unmerged
|
||||
- Text.Submodule.URL
|
||||
- Text.ViewLogs
|
||||
- Text.ViewLogs.Clear
|
||||
- Text.ViewLogs.CopyLog
|
||||
- Text.ViewLogs.Delete
|
||||
- Text.WorkingCopy.ConfirmCommitWithFilter
|
||||
- Text.WorkingCopy.Conflicts.OpenExternalMergeTool
|
||||
- Text.WorkingCopy.Conflicts.OpenExternalMergeToolAllConflicts
|
||||
- Text.WorkingCopy.Conflicts.UseMine
|
||||
- Text.WorkingCopy.Conflicts.UseTheirs
|
||||
- Text.WorkingCopy.ResetAuthor
|
||||
|
||||
</details>
|
||||
|
||||
### it_IT.axaml: 95.56%
|
||||
|
||||
### 
|
||||
|
||||
<details>
|
||||
<summary>Missing Keys</summary>
|
||||
<summary>Missing keys in pt_BR.axaml</summary>
|
||||
|
||||
- Text.AIAssistant.Regen
|
||||
- Text.AIAssistant.Use
|
||||
- Text.ApplyStash
|
||||
- Text.ApplyStash.DropAfterApply
|
||||
- Text.ApplyStash.RestoreIndex
|
||||
- Text.ApplyStash.Stash
|
||||
- Text.Avatar.Load
|
||||
- Text.Bisect
|
||||
- Text.Bisect.Abort
|
||||
- Text.Bisect.Bad
|
||||
- Text.Bisect.Detecting
|
||||
- Text.Bisect.Good
|
||||
- Text.Bisect.Skip
|
||||
- Text.Bisect.WaitingForRange
|
||||
- Text.BranchCM.CustomAction
|
||||
- Text.BranchCM.MergeMultiBranches
|
||||
- Text.BranchCM.ResetToSelectedCommit
|
||||
- Text.BranchUpstreamInvalid
|
||||
- Text.Checkout.RecurseSubmodules
|
||||
- Text.Clone.RecurseSubmodules
|
||||
- Text.CommitCM.CopyAuthor
|
||||
- Text.CommitCM.CopyCommitter
|
||||
- Text.CommitCM.CopySubject
|
||||
- Text.CommitCM.Merge
|
||||
- Text.CommitCM.MergeMultiple
|
||||
- Text.CommitDetail.Changes.Count
|
||||
- Text.CommitDetail.Files.Search
|
||||
- Text.CommitDetail.Info.Children
|
||||
- Text.Configure.IssueTracker.AddSampleGitLabMergeRequest
|
||||
- Text.Configure.OpenAI.Preferred
|
||||
- Text.Configure.OpenAI.Preferred.Tip
|
||||
- Text.CommitMessageTextBox.SubjectCount
|
||||
- Text.Configure.CustomAction.Scope.Branch
|
||||
- Text.Configure.CustomAction.WaitForExit
|
||||
- Text.Configure.Git.PreferredMergeMode
|
||||
- Text.Configure.IssueTracker.AddSampleGiteeIssue
|
||||
- Text.Configure.IssueTracker.AddSampleGiteePullRequest
|
||||
- Text.ConfirmEmptyCommit.Continue
|
||||
- Text.ConfirmEmptyCommit.NoLocalChanges
|
||||
- Text.ConfirmEmptyCommit.StageAllThenCommit
|
||||
- Text.ConfirmEmptyCommit.WithLocalChanges
|
||||
- Text.CopyFullPath
|
||||
- Text.CreateBranch.Name.WarnSpace
|
||||
- Text.CreateBranch.OverwriteExisting
|
||||
- Text.DeinitSubmodule
|
||||
- Text.DeinitSubmodule.Force
|
||||
- Text.DeinitSubmodule.Path
|
||||
- Text.DeleteRepositoryNode.Path
|
||||
- Text.DeleteRepositoryNode.TipForGroup
|
||||
- Text.DeleteRepositoryNode.TipForRepository
|
||||
- Text.Diff.First
|
||||
- Text.Diff.Last
|
||||
- Text.Diff.Submodule.Deleted
|
||||
- Text.Diff.UseBlockNavigation
|
||||
- Text.Fetch.Force
|
||||
- Text.FileCM.ResolveUsing
|
||||
- Text.GitFlow.FinishWithPush
|
||||
- Text.GitFlow.FinishWithSquash
|
||||
- Text.Hotkeys.Global.Clone
|
||||
- Text.Hotkeys.Global.SwitchWorkspace
|
||||
- Text.Hotkeys.Global.SwitchTab
|
||||
- Text.Hotkeys.TextEditor.OpenExternalMergeTool
|
||||
- Text.InProgress.CherryPick.Head
|
||||
- Text.InProgress.Merge.Operating
|
||||
- Text.InProgress.Rebase.StoppedAt
|
||||
- Text.InProgress.Revert.Head
|
||||
- Text.Launcher.Workspaces
|
||||
- Text.Launcher.Pages
|
||||
- Text.Merge.Source
|
||||
- Text.MergeMultiple
|
||||
- Text.MergeMultiple.CommitChanges
|
||||
- Text.MergeMultiple.Strategy
|
||||
- Text.MergeMultiple.Targets
|
||||
- Text.Preference.General.ShowChildren
|
||||
- Text.Preferences.AI.Streaming
|
||||
- Text.Preferences.Appearance.EditorTabWidth
|
||||
- Text.Preferences.General.DateFormat
|
||||
- Text.Preferences.General.ShowChildren
|
||||
- Text.Preferences.General.ShowTagsInGraph
|
||||
- Text.Preferences.Git.IgnoreCRAtEOLInDiff
|
||||
- Text.Preferences.Git.SSLVerify
|
||||
- Text.Pull.RecurseSubmodules
|
||||
- Text.Repository.BranchSort
|
||||
- Text.Repository.BranchSort.ByCommitterDate
|
||||
- Text.Repository.BranchSort.ByName
|
||||
- Text.Repository.ClearStashes
|
||||
- Text.Repository.FilterCommits
|
||||
- Text.Repository.FilterCommits.Default
|
||||
- Text.Repository.FilterCommits.Exclude
|
||||
- Text.Repository.FilterCommits.Include
|
||||
- Text.Repository.HistoriesLayout
|
||||
- Text.Repository.HistoriesLayout.Horizontal
|
||||
- Text.Repository.HistoriesLayout.Vertical
|
||||
- Text.Repository.HistoriesOrder
|
||||
- Text.Repository.HistoriesOrder.ByDate
|
||||
- Text.Repository.HistoriesOrder.Topo
|
||||
- Text.Repository.Notifications.Clear
|
||||
- Text.Repository.OnlyHighlightCurrentBranchInHistories
|
||||
- Text.Repository.Search.ByContent
|
||||
- Text.Repository.ShowSubmodulesAsTree
|
||||
- Text.Repository.Skip
|
||||
- Text.SHALinkCM.CopySHA
|
||||
- Text.Repository.Tags.OrderByCreatorDate
|
||||
- Text.Repository.Tags.OrderByName
|
||||
- Text.Repository.Tags.Sort
|
||||
- Text.Repository.UseRelativeTimeInHistories
|
||||
- Text.Repository.ViewLogs
|
||||
- Text.Repository.Visit
|
||||
- Text.ResetWithoutCheckout
|
||||
- Text.ResetWithoutCheckout.MoveTo
|
||||
- Text.ResetWithoutCheckout.Target
|
||||
- Text.SetUpstream
|
||||
- Text.SetUpstream.Local
|
||||
- Text.SetUpstream.Unset
|
||||
- Text.SetUpstream.Upstream
|
||||
- Text.SHALinkCM.NavigateTo
|
||||
- Text.Stash.AutoRestore
|
||||
- Text.Stash.AutoRestore.Tip
|
||||
- Text.StashCM.SaveAsPatch
|
||||
- Text.Submodule.Deinit
|
||||
- Text.Submodule.Status
|
||||
- Text.Submodule.Status.Modified
|
||||
- Text.Submodule.Status.NotInited
|
||||
- Text.Submodule.Status.RevisionChanged
|
||||
- Text.Submodule.Status.Unmerged
|
||||
- Text.Submodule.URL
|
||||
- Text.ViewLogs
|
||||
- Text.ViewLogs.Clear
|
||||
- Text.ViewLogs.CopyLog
|
||||
- Text.ViewLogs.Delete
|
||||
- Text.WorkingCopy.CommitToEdit
|
||||
- Text.WorkingCopy.ConfirmCommitWithFilter
|
||||
- Text.WorkingCopy.Conflicts.OpenExternalMergeTool
|
||||
- Text.WorkingCopy.Conflicts.OpenExternalMergeToolAllConflicts
|
||||
- Text.WorkingCopy.Conflicts.UseMine
|
||||
- Text.WorkingCopy.Conflicts.UseTheirs
|
||||
- Text.WorkingCopy.ResetAuthor
|
||||
- Text.WorkingCopy.SignOff
|
||||
|
||||
</details>
|
||||
|
||||
### pt_BR.axaml: 96.81%
|
||||
### 
|
||||
|
||||
### 
|
||||
|
||||
<details>
|
||||
<summary>Missing Keys</summary>
|
||||
<summary>Missing keys in ta_IN.axaml</summary>
|
||||
|
||||
- Text.BranchCM.MergeMultiBranches
|
||||
- Text.CommitCM.Merge
|
||||
- Text.CommitCM.MergeMultiple
|
||||
- Text.CommitDetail.Files.Search
|
||||
- Text.CommitDetail.Info.Children
|
||||
- Text.Diff.UseBlockNavigation
|
||||
- Text.Fetch.Force
|
||||
- Text.FileCM.ResolveUsing
|
||||
- Text.Hotkeys.Global.Clone
|
||||
- Text.InProgress.CherryPick.Head
|
||||
- Text.InProgress.Merge.Operating
|
||||
- Text.InProgress.Rebase.StoppedAt
|
||||
- Text.InProgress.Revert.Head
|
||||
- Text.Merge.Source
|
||||
- Text.MergeMultiple
|
||||
- Text.MergeMultiple.CommitChanges
|
||||
- Text.MergeMultiple.Strategy
|
||||
- Text.MergeMultiple.Targets
|
||||
- Text.Preference.General.ShowChildren
|
||||
- Text.Repository.FilterCommits
|
||||
- Text.Repository.Skip
|
||||
- Text.SHALinkCM.NavigateTo
|
||||
- Text.WorkingCopy.CommitToEdit
|
||||
- Text.Avatar.Load
|
||||
- Text.Bisect
|
||||
- Text.Bisect.Abort
|
||||
- Text.Bisect.Bad
|
||||
- Text.Bisect.Detecting
|
||||
- Text.Bisect.Good
|
||||
- Text.Bisect.Skip
|
||||
- Text.Bisect.WaitingForRange
|
||||
- Text.BranchCM.CompareWithCurrent
|
||||
- Text.BranchCM.ResetToSelectedCommit
|
||||
- Text.Checkout.RecurseSubmodules
|
||||
- Text.CommitCM.CopyAuthor
|
||||
- Text.CommitCM.CopyCommitter
|
||||
- Text.CommitCM.CopySubject
|
||||
- Text.CommitDetail.Changes.Count
|
||||
- Text.CommitMessageTextBox.SubjectCount
|
||||
- Text.Configure.Git.PreferredMergeMode
|
||||
- Text.ConfirmEmptyCommit.Continue
|
||||
- Text.ConfirmEmptyCommit.NoLocalChanges
|
||||
- Text.ConfirmEmptyCommit.StageAllThenCommit
|
||||
- Text.ConfirmEmptyCommit.WithLocalChanges
|
||||
- Text.CreateBranch.OverwriteExisting
|
||||
- Text.DeinitSubmodule
|
||||
- Text.DeinitSubmodule.Force
|
||||
- Text.DeinitSubmodule.Path
|
||||
- Text.Diff.Submodule.Deleted
|
||||
- Text.GitFlow.FinishWithPush
|
||||
- Text.GitFlow.FinishWithSquash
|
||||
- Text.Hotkeys.Global.SwitchWorkspace
|
||||
- Text.Hotkeys.Global.SwitchTab
|
||||
- Text.Hotkeys.TextEditor.OpenExternalMergeTool
|
||||
- Text.Launcher.Workspaces
|
||||
- Text.Launcher.Pages
|
||||
- Text.Preferences.Git.IgnoreCRAtEOLInDiff
|
||||
- Text.Pull.RecurseSubmodules
|
||||
- Text.Repository.BranchSort
|
||||
- Text.Repository.BranchSort.ByCommitterDate
|
||||
- Text.Repository.BranchSort.ByName
|
||||
- Text.Repository.ClearStashes
|
||||
- Text.Repository.Search.ByContent
|
||||
- Text.Repository.ShowSubmodulesAsTree
|
||||
- Text.Repository.ViewLogs
|
||||
- Text.Repository.Visit
|
||||
- Text.ResetWithoutCheckout
|
||||
- Text.ResetWithoutCheckout.MoveTo
|
||||
- Text.ResetWithoutCheckout.Target
|
||||
- Text.Submodule.Deinit
|
||||
- Text.Submodule.Status
|
||||
- Text.Submodule.Status.Modified
|
||||
- Text.Submodule.Status.NotInited
|
||||
- Text.Submodule.Status.RevisionChanged
|
||||
- Text.Submodule.Status.Unmerged
|
||||
- Text.Submodule.URL
|
||||
- Text.UpdateSubmodules.Target
|
||||
- Text.ViewLogs
|
||||
- Text.ViewLogs.Clear
|
||||
- Text.ViewLogs.CopyLog
|
||||
- Text.ViewLogs.Delete
|
||||
- Text.WorkingCopy.Conflicts.OpenExternalMergeTool
|
||||
- Text.WorkingCopy.Conflicts.OpenExternalMergeToolAllConflicts
|
||||
- Text.WorkingCopy.Conflicts.UseMine
|
||||
- Text.WorkingCopy.Conflicts.UseTheirs
|
||||
- Text.WorkingCopy.ResetAuthor
|
||||
|
||||
</details>
|
||||
|
||||
### ru_RU.axaml: 97.92%
|
||||
|
||||
### 
|
||||
|
||||
<details>
|
||||
<summary>Missing Keys</summary>
|
||||
<summary>Missing keys in uk_UA.axaml</summary>
|
||||
|
||||
- Text.BranchCM.MergeMultiBranches
|
||||
- Text.CommitCM.Merge
|
||||
- Text.CommitCM.MergeMultiple
|
||||
- Text.FileCM.ResolveUsing
|
||||
- Text.Hotkeys.Global.Clone
|
||||
- Text.InProgress.CherryPick.Head
|
||||
- Text.InProgress.Merge.Operating
|
||||
- Text.InProgress.Rebase.StoppedAt
|
||||
- Text.InProgress.Revert.Head
|
||||
- Text.Merge.Source
|
||||
- Text.MergeMultiple
|
||||
- Text.MergeMultiple.CommitChanges
|
||||
- Text.MergeMultiple.Strategy
|
||||
- Text.MergeMultiple.Targets
|
||||
- Text.Repository.Skip
|
||||
- Text.Avatar.Load
|
||||
- Text.Bisect
|
||||
- Text.Bisect.Abort
|
||||
- Text.Bisect.Bad
|
||||
- Text.Bisect.Detecting
|
||||
- Text.Bisect.Good
|
||||
- Text.Bisect.Skip
|
||||
- Text.Bisect.WaitingForRange
|
||||
- Text.BranchCM.ResetToSelectedCommit
|
||||
- Text.Checkout.RecurseSubmodules
|
||||
- Text.CommitCM.CopyAuthor
|
||||
- Text.CommitCM.CopyCommitter
|
||||
- Text.CommitCM.CopySubject
|
||||
- Text.CommitDetail.Changes.Count
|
||||
- Text.CommitMessageTextBox.SubjectCount
|
||||
- Text.ConfigureWorkspace.Name
|
||||
- Text.CreateBranch.OverwriteExisting
|
||||
- Text.DeinitSubmodule
|
||||
- Text.DeinitSubmodule.Force
|
||||
- Text.DeinitSubmodule.Path
|
||||
- Text.Diff.Submodule.Deleted
|
||||
- Text.GitFlow.FinishWithPush
|
||||
- Text.GitFlow.FinishWithSquash
|
||||
- Text.Hotkeys.Global.SwitchWorkspace
|
||||
- Text.Hotkeys.Global.SwitchTab
|
||||
- Text.Hotkeys.TextEditor.OpenExternalMergeTool
|
||||
- Text.Launcher.Workspaces
|
||||
- Text.Launcher.Pages
|
||||
- Text.Preferences.Git.IgnoreCRAtEOLInDiff
|
||||
- Text.Pull.RecurseSubmodules
|
||||
- Text.Repository.BranchSort
|
||||
- Text.Repository.BranchSort.ByCommitterDate
|
||||
- Text.Repository.BranchSort.ByName
|
||||
- Text.Repository.ClearStashes
|
||||
- Text.Repository.Search.ByContent
|
||||
- Text.Repository.ShowSubmodulesAsTree
|
||||
- Text.Repository.ViewLogs
|
||||
- Text.Repository.Visit
|
||||
- Text.ResetWithoutCheckout
|
||||
- Text.ResetWithoutCheckout.MoveTo
|
||||
- Text.ResetWithoutCheckout.Target
|
||||
- Text.Submodule.Deinit
|
||||
- Text.Submodule.Status
|
||||
- Text.Submodule.Status.Modified
|
||||
- Text.Submodule.Status.NotInited
|
||||
- Text.Submodule.Status.RevisionChanged
|
||||
- Text.Submodule.Status.Unmerged
|
||||
- Text.Submodule.URL
|
||||
- Text.ViewLogs
|
||||
- Text.ViewLogs.Clear
|
||||
- Text.ViewLogs.CopyLog
|
||||
- Text.ViewLogs.Delete
|
||||
- Text.WorkingCopy.ResetAuthor
|
||||
|
||||
</details>
|
||||
|
||||
### zh_CN.axaml: 100.00%
|
||||
### 
|
||||
|
||||
|
||||
<details>
|
||||
<summary>Missing Keys</summary>
|
||||
|
||||
|
||||
|
||||
</details>
|
||||
|
||||
### zh_TW.axaml: 100.00%
|
||||
|
||||
|
||||
<details>
|
||||
<summary>Missing Keys</summary>
|
||||
|
||||
|
||||
|
||||
</details>
|
||||
### 
|
2
VERSION
2
VERSION
|
@ -1 +1 @@
|
|||
8.43
|
||||
2025.21
|
|
@ -12,4 +12,4 @@
|
|||
dotnet publish -c Release -r $RUNTIME_IDENTIFIER -o $DESTINATION_FOLDER src/SourceGit.csproj
|
||||
```
|
||||
> [!NOTE]
|
||||
> Please replace the `$RUNTIME_IDENTIFIER` with one of `win-x64`,`win-arm64`,`linux-x64`,`linux-arm64`,`osx-x64`,`osx-arm64`, and replece the `$DESTINATION_FOLDER` with the real path that will store the output executable files.
|
||||
> Please replace the `$RUNTIME_IDENTIFIER` with one of `win-x64`,`win-arm64`,`linux-x64`,`linux-arm64`,`osx-x64`,`osx-arm64`, and replace the `$DESTINATION_FOLDER` with the real path that will store the output executable files.
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
Package: sourcegit
|
||||
Version: 8.23
|
||||
Version: 2025.10
|
||||
Priority: optional
|
||||
Depends: libx11-6, libice6, libsm6
|
||||
Depends: libx11-6, libice6, libsm6, libicu | libicu76 | libicu74 | libicu72 | libicu71 | libicu70 | libicu69 | libicu68 | libicu67 | libicu66 | libicu65 | libicu63 | libicu60 | libicu57 | libicu55 | libicu52, xdg-utils
|
||||
Architecture: amd64
|
||||
Installed-Size: 60440
|
||||
Maintainer: longshuang@msn.cn
|
||||
Description: Open-source & Free Git GUI Client
|
||||
|
|
32
build/resources/deb/DEBIAN/preinst
Executable file
32
build/resources/deb/DEBIAN/preinst
Executable file
|
@ -0,0 +1,32 @@
|
|||
#!/bin/sh
|
||||
|
||||
set -e
|
||||
|
||||
# summary of how this script can be called:
|
||||
# * <new-preinst> `install'
|
||||
# * <new-preinst> `install' <old-version>
|
||||
# * <new-preinst> `upgrade' <old-version>
|
||||
# * <old-preinst> `abort-upgrade' <new-version>
|
||||
# for details, see http://www.debian.org/doc/debian-policy/
|
||||
|
||||
case "$1" in
|
||||
install|upgrade)
|
||||
# Check if SourceGit is running and stop it
|
||||
if pgrep -f '/opt/sourcegit/sourcegit' > /dev/null; then
|
||||
echo "Stopping running SourceGit instance..."
|
||||
pkill -f '/opt/sourcegit/sourcegit' || true
|
||||
# Give the process a moment to terminate
|
||||
sleep 1
|
||||
fi
|
||||
;;
|
||||
|
||||
abort-upgrade)
|
||||
;;
|
||||
|
||||
*)
|
||||
echo "preinst called with unknown argument \`$1'" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
exit 0
|
35
build/resources/deb/DEBIAN/prerm
Executable file
35
build/resources/deb/DEBIAN/prerm
Executable file
|
@ -0,0 +1,35 @@
|
|||
#!/bin/sh
|
||||
|
||||
set -e
|
||||
|
||||
# summary of how this script can be called:
|
||||
# * <prerm> `remove'
|
||||
# * <old-prerm> `upgrade' <new-version>
|
||||
# * <new-prerm> `failed-upgrade' <old-version>
|
||||
# * <conflictor's-prerm> `remove' `in-favour' <package> <new-version>
|
||||
# * <deconfigured's-prerm> `deconfigure' `in-favour'
|
||||
# <package-being-installed> <version> `removing'
|
||||
# <conflicting-package> <version>
|
||||
# for details, see http://www.debian.org/doc/debian-policy/ or
|
||||
# the debian-policy package
|
||||
|
||||
case "$1" in
|
||||
remove|upgrade|deconfigure)
|
||||
if pgrep -f '/opt/sourcegit/sourcegit' > /dev/null; then
|
||||
echo "Stopping running SourceGit instance..."
|
||||
pkill -f '/opt/sourcegit/sourcegit' || true
|
||||
# Give the process a moment to terminate
|
||||
sleep 1
|
||||
fi
|
||||
;;
|
||||
|
||||
failed-upgrade)
|
||||
;;
|
||||
|
||||
*)
|
||||
echo "prerm called with unknown argument \`$1'" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
exit 0
|
|
@ -8,6 +8,7 @@ Source: https://github.com/sourcegit-scm/sourcegit/archive/refs/tags/v%_version.
|
|||
Requires: libX11.so.6()(%{__isa_bits}bit)
|
||||
Requires: libSM.so.6()(%{__isa_bits}bit)
|
||||
Requires: libicu
|
||||
Requires: xdg-utils
|
||||
|
||||
%define _build_id_links none
|
||||
|
||||
|
|
|
@ -6,7 +6,6 @@ const repoRoot = path.join(__dirname, '../../');
|
|||
const localesDir = path.join(repoRoot, 'src/Resources/Locales');
|
||||
const enUSFile = path.join(localesDir, 'en_US.axaml');
|
||||
const outputFile = path.join(repoRoot, 'TRANSLATION.md');
|
||||
const readmeFile = path.join(repoRoot, 'README.md');
|
||||
|
||||
const parser = new xml2js.Parser();
|
||||
|
||||
|
@ -15,45 +14,70 @@ async function parseXml(filePath) {
|
|||
return parser.parseStringPromise(data);
|
||||
}
|
||||
|
||||
async function filterAndSortTranslations(localeData, enUSKeys, enUSData) {
|
||||
const strings = localeData.ResourceDictionary['x:String'];
|
||||
// Remove keys that don't exist in English file
|
||||
const filtered = strings.filter(item => enUSKeys.has(item.$['x:Key']));
|
||||
|
||||
// Sort based on the key order in English file
|
||||
const enUSKeysArray = enUSData.ResourceDictionary['x:String'].map(item => item.$['x:Key']);
|
||||
filtered.sort((a, b) => {
|
||||
const aIndex = enUSKeysArray.indexOf(a.$['x:Key']);
|
||||
const bIndex = enUSKeysArray.indexOf(b.$['x:Key']);
|
||||
return aIndex - bIndex;
|
||||
});
|
||||
|
||||
return filtered;
|
||||
}
|
||||
|
||||
async function calculateTranslationRate() {
|
||||
const enUSData = await parseXml(enUSFile);
|
||||
const enUSKeys = new Set(enUSData.ResourceDictionary['x:String'].map(item => item.$['x:Key']));
|
||||
|
||||
const translationRates = [];
|
||||
const badges = [];
|
||||
|
||||
const files = (await fs.readdir(localesDir)).filter(file => file !== 'en_US.axaml' && file.endsWith('.axaml'));
|
||||
|
||||
// Add en_US badge first
|
||||
badges.push(`[](TRANSLATION.md)`);
|
||||
const lines = [];
|
||||
|
||||
lines.push('# Translation Status');
|
||||
lines.push('This document shows the translation status of each locale file in the repository.');
|
||||
lines.push(`## Details`);
|
||||
lines.push(`### `);
|
||||
|
||||
for (const file of files) {
|
||||
const locale = file.replace('.axaml', '').replace('_', '__');
|
||||
const filePath = path.join(localesDir, file);
|
||||
const localeData = await parseXml(filePath);
|
||||
const localeKeys = new Set(localeData.ResourceDictionary['x:String'].map(item => item.$['x:Key']));
|
||||
|
||||
const missingKeys = [...enUSKeys].filter(key => !localeKeys.has(key));
|
||||
const translationRate = ((enUSKeys.size - missingKeys.length) / enUSKeys.size) * 100;
|
||||
|
||||
translationRates.push(`### ${file}: ${translationRate.toFixed(2)}%\n`);
|
||||
translationRates.push(`<details>\n<summary>Missing Keys</summary>\n\n${missingKeys.map(key => `- ${key}`).join('\n')}\n\n</details>`);
|
||||
// Sort and clean up extra translations
|
||||
const sortedAndCleaned = await filterAndSortTranslations(localeData, enUSKeys, enUSData);
|
||||
localeData.ResourceDictionary['x:String'] = sortedAndCleaned;
|
||||
|
||||
// Add badges
|
||||
const locale = file.replace('.axaml', '').replace('_', '__');
|
||||
const badgeColor = translationRate === 100 ? 'brightgreen' : translationRate >= 75 ? 'yellow' : 'red';
|
||||
badges.push(`[}%25-${badgeColor})](TRANSLATION.md)`);
|
||||
// Save the updated file
|
||||
const builder = new xml2js.Builder({
|
||||
headless: true,
|
||||
renderOpts: { pretty: true, indent: ' ' }
|
||||
});
|
||||
let xmlStr = builder.buildObject(localeData);
|
||||
|
||||
// Add an empty line before the first x:String
|
||||
xmlStr = xmlStr.replace(' <x:String', '\n <x:String');
|
||||
await fs.writeFile(filePath, xmlStr + '\n', 'utf8');
|
||||
|
||||
if (missingKeys.length > 0) {
|
||||
const progress = ((enUSKeys.size - missingKeys.length) / enUSKeys.size) * 100;
|
||||
const badgeColor = progress >= 75 ? 'yellow' : 'red';
|
||||
|
||||
lines.push(`### }%25-${badgeColor})`);
|
||||
lines.push(`<details>\n<summary>Missing keys in ${file}</summary>\n\n${missingKeys.map(key => `- ${key}`).join('\n')}\n\n</details>`)
|
||||
} else {
|
||||
lines.push(`### `);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(translationRates.join('\n\n'));
|
||||
|
||||
await fs.writeFile(outputFile, translationRates.join('\n\n') + '\n', 'utf8');
|
||||
|
||||
// Update README.md
|
||||
let readmeContent = await fs.readFile(readmeFile, 'utf8');
|
||||
const badgeSection = `## Translation Status\n\n${badges.join(' ')}`;
|
||||
console.log(badgeSection);
|
||||
readmeContent = readmeContent.replace(/## Translation Status\n\n.*\n\n/, badgeSection + '\n\n');
|
||||
await fs.writeFile(readmeFile, readmeContent, 'utf8');
|
||||
const content = lines.join('\n\n');
|
||||
console.log(content);
|
||||
await fs.writeFile(outputFile, content, 'utf8');
|
||||
}
|
||||
|
||||
calculateTranslationRate().catch(err => console.error(err));
|
||||
|
|
|
@ -56,8 +56,15 @@ cp -f SourceGit/* resources/deb/opt/sourcegit
|
|||
ln -rsf resources/deb/opt/sourcegit/sourcegit resources/deb/usr/bin
|
||||
cp -r resources/_common/applications resources/deb/usr/share
|
||||
cp -r resources/_common/icons resources/deb/usr/share
|
||||
sed -i -e "s/^Version:.*/Version: $VERSION/" -e "s/^Architecture:.*/Architecture: $arch/" resources/deb/DEBIAN/control
|
||||
dpkg-deb --root-owner-group --build resources/deb "sourcegit_$VERSION-1_$arch.deb"
|
||||
# Calculate installed size in KB
|
||||
installed_size=$(du -sk resources/deb | cut -f1)
|
||||
# Update the control file
|
||||
sed -i -e "s/^Version:.*/Version: $VERSION/" \
|
||||
-e "s/^Architecture:.*/Architecture: $arch/" \
|
||||
-e "s/^Installed-Size:.*/Installed-Size: $installed_size/" \
|
||||
resources/deb/DEBIAN/control
|
||||
# Build deb package with gzip compression
|
||||
dpkg-deb -Zgzip --root-owner-group --build resources/deb "sourcegit_$VERSION-1_$arch.deb"
|
||||
|
||||
rpmbuild -bb --target="$target" resources/rpm/SPECS/build.spec --define "_topdir $(pwd)/resources/rpm" --define "_version $VERSION"
|
||||
mv "resources/rpm/RPMS/$target/sourcegit-$VERSION-1.$target.rpm" ./
|
||||
|
|
|
@ -1,12 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
set -o
|
||||
set -u
|
||||
set pipefail
|
||||
|
||||
cd build
|
||||
|
||||
rm -rf SourceGit/*.pdb
|
||||
|
||||
zip "sourcegit_$VERSION.$RUNTIME.zip" -r SourceGit
|
16
build/scripts/package.windows.sh
Executable file
16
build/scripts/package.windows.sh
Executable file
|
@ -0,0 +1,16 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
set -o
|
||||
set -u
|
||||
set pipefail
|
||||
|
||||
cd build
|
||||
|
||||
rm -rf SourceGit/*.pdb
|
||||
|
||||
if [[ "$OSTYPE" == "msys" || "$OSTYPE" == "cygwin" || "$OSTYPE" == "win32" ]]; then
|
||||
powershell -Command "Compress-Archive -Path SourceGit -DestinationPath \"sourcegit_$VERSION.$RUNTIME.zip\" -Force"
|
||||
else
|
||||
zip "sourcegit_$VERSION.$RUNTIME.zip" -r SourceGit
|
||||
fi
|
|
@ -25,12 +25,34 @@ namespace SourceGit
|
|||
private Action<object> _action = null;
|
||||
}
|
||||
|
||||
public static readonly Command OpenPreferenceCommand = new Command(_ => OpenDialog(new Views.Preference()));
|
||||
public static readonly Command OpenHotkeysCommand = new Command(_ => OpenDialog(new Views.Hotkeys()));
|
||||
public static bool IsCheckForUpdateCommandVisible
|
||||
{
|
||||
get
|
||||
{
|
||||
#if DISABLE_UPDATE_DETECTION
|
||||
return false;
|
||||
#else
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
public static readonly Command OpenPreferencesCommand = new Command(_ => ShowWindow(new Views.Preferences(), false));
|
||||
public static readonly Command OpenHotkeysCommand = new Command(_ => ShowWindow(new Views.Hotkeys(), false));
|
||||
public static readonly Command OpenAppDataDirCommand = new Command(_ => Native.OS.OpenInFileManager(Native.OS.DataDir));
|
||||
public static readonly Command OpenAboutCommand = new Command(_ => OpenDialog(new Views.About()));
|
||||
public static readonly Command CheckForUpdateCommand = new Command(_ => Check4Update(true));
|
||||
public static readonly Command OpenAboutCommand = new Command(_ => ShowWindow(new Views.About(), false));
|
||||
public static readonly Command CheckForUpdateCommand = new Command(_ => (Current as App)?.Check4Update(true));
|
||||
public static readonly Command QuitCommand = new Command(_ => Quit(0));
|
||||
public static readonly Command CopyTextBlockCommand = new Command(p => CopyTextBlock(p as TextBlock));
|
||||
public static readonly Command CopyTextBlockCommand = new Command(p =>
|
||||
{
|
||||
var textBlock = p as TextBlock;
|
||||
if (textBlock == null)
|
||||
return;
|
||||
|
||||
if (textBlock.Inlines is { Count: > 0 } inlines)
|
||||
CopyText(inlines.Text);
|
||||
else if (!string.IsNullOrEmpty(textBlock.Text))
|
||||
CopyText(textBlock.Text);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -46,11 +46,9 @@ namespace SourceGit
|
|||
[JsonSerializable(typeof(Models.ExternalToolPaths))]
|
||||
[JsonSerializable(typeof(Models.InteractiveRebaseJobCollection))]
|
||||
[JsonSerializable(typeof(Models.JetBrainsState))]
|
||||
[JsonSerializable(typeof(Models.OpenAIChatRequest))]
|
||||
[JsonSerializable(typeof(Models.OpenAIChatResponse))]
|
||||
[JsonSerializable(typeof(Models.ThemeOverrides))]
|
||||
[JsonSerializable(typeof(Models.Version))]
|
||||
[JsonSerializable(typeof(Models.RepositorySettings))]
|
||||
[JsonSerializable(typeof(ViewModels.Preference))]
|
||||
[JsonSerializable(typeof(ViewModels.Preferences))]
|
||||
internal partial class JsonCodeGen : JsonSerializerContext { }
|
||||
}
|
||||
|
|
|
@ -16,10 +16,13 @@
|
|||
<ResourceInclude x:Key="fr_FR" Source="/Resources/Locales/fr_FR.axaml"/>
|
||||
<ResourceInclude x:Key="it_IT" Source="/Resources/Locales/it_IT.axaml"/>
|
||||
<ResourceInclude x:Key="pt_BR" Source="/Resources/Locales/pt_BR.axaml"/>
|
||||
<ResourceInclude x:Key="uk_UA" Source="/Resources/Locales/uk_UA.axaml"/>
|
||||
<ResourceInclude x:Key="ru_RU" Source="/Resources/Locales/ru_RU.axaml"/>
|
||||
<ResourceInclude x:Key="zh_CN" Source="/Resources/Locales/zh_CN.axaml"/>
|
||||
<ResourceInclude x:Key="zh_TW" Source="/Resources/Locales/zh_TW.axaml"/>
|
||||
<ResourceInclude x:Key="es_ES" Source="/Resources/Locales/es_ES.axaml"/>
|
||||
<ResourceInclude x:Key="ja_JP" Source="/Resources/Locales/ja_JP.axaml"/>
|
||||
<ResourceInclude x:Key="ta_IN" Source="/Resources/Locales/ta_IN.axaml"/>
|
||||
</ResourceDictionary>
|
||||
</Application.Resources>
|
||||
|
||||
|
@ -32,10 +35,10 @@
|
|||
<NativeMenu.Menu>
|
||||
<NativeMenu>
|
||||
<NativeMenuItem Header="{DynamicResource Text.About.Menu}" Command="{x:Static s:App.OpenAboutCommand}"/>
|
||||
<NativeMenuItem Header="{DynamicResource Text.Hotkeys}" Command="{x:Static s:App.OpenHotkeysCommand}"/>
|
||||
<NativeMenuItem Header="{DynamicResource Text.SelfUpdate}" Command="{x:Static s:App.CheckForUpdateCommand}"/>
|
||||
<NativeMenuItem Header="{DynamicResource Text.Hotkeys}" Command="{x:Static s:App.OpenHotkeysCommand}" Gesture="F1"/>
|
||||
<NativeMenuItem Header="{DynamicResource Text.SelfUpdate}" Command="{x:Static s:App.CheckForUpdateCommand}" IsVisible="{x:Static s:App.IsCheckForUpdateCommandVisible}"/>
|
||||
<NativeMenuItemSeparator/>
|
||||
<NativeMenuItem Header="{DynamicResource Text.Preference}" Command="{x:Static s:App.OpenPreferenceCommand}" Gesture="⌘+,"/>
|
||||
<NativeMenuItem Header="{DynamicResource Text.Preferences}" Command="{x:Static s:App.OpenPreferencesCommand}" Gesture="⌘+,"/>
|
||||
<NativeMenuItem Header="{DynamicResource Text.OpenAppDataDir}" Command="{x:Static s:App.OpenAppDataDirCommand}"/>
|
||||
<NativeMenuItemSeparator/>
|
||||
<NativeMenuItem Header="{DynamicResource Text.Quit}" Command="{x:Static s:App.QuitCommand}" Gesture="⌘+Q"/>
|
||||
|
|
421
src/App.axaml.cs
421
src/App.axaml.cs
|
@ -1,10 +1,13 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Net.Http;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Avalonia;
|
||||
|
@ -22,6 +25,7 @@ namespace SourceGit
|
|||
{
|
||||
public partial class App : Application
|
||||
{
|
||||
#region App Entry Point
|
||||
[STAThread]
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
|
@ -34,15 +38,14 @@ namespace SourceGit
|
|||
|
||||
TaskScheduler.UnobservedTaskException += (_, e) =>
|
||||
{
|
||||
LogException(e.Exception);
|
||||
e.SetObserved();
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
if (TryLaunchedAsRebaseTodoEditor(args, out int exitTodo))
|
||||
if (TryLaunchAsRebaseTodoEditor(args, out int exitTodo))
|
||||
Environment.Exit(exitTodo);
|
||||
else if (TryLaunchedAsRebaseMessageEditor(args, out int exitMessage))
|
||||
else if (TryLaunchAsRebaseMessageEditor(args, out int exitMessage))
|
||||
Environment.Exit(exitMessage);
|
||||
else
|
||||
BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
|
||||
|
@ -75,38 +78,71 @@ namespace SourceGit
|
|||
return builder;
|
||||
}
|
||||
|
||||
public override void Initialize()
|
||||
public static void LogException(Exception ex)
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
|
||||
var pref = ViewModels.Preference.Instance;
|
||||
pref.PropertyChanged += (_, _) => pref.Save();
|
||||
|
||||
SetLocale(pref.Locale);
|
||||
SetTheme(pref.Theme, pref.ThemeOverrides);
|
||||
SetFonts(pref.DefaultFontFamily, pref.MonospaceFontFamily, pref.OnlyUseMonoFontInEditor);
|
||||
}
|
||||
|
||||
public override void OnFrameworkInitializationCompleted()
|
||||
{
|
||||
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||
{
|
||||
BindingPlugins.DataValidators.RemoveAt(0);
|
||||
|
||||
if (TryLaunchedAsCoreEditor(desktop))
|
||||
if (ex == null)
|
||||
return;
|
||||
|
||||
if (TryLaunchedAsAskpass(desktop))
|
||||
return;
|
||||
var builder = new StringBuilder();
|
||||
builder.Append($"Crash::: {ex.GetType().FullName}: {ex.Message}\n\n");
|
||||
builder.Append("----------------------------\n");
|
||||
builder.Append($"Version: {Assembly.GetExecutingAssembly().GetName().Version}\n");
|
||||
builder.Append($"OS: {Environment.OSVersion}\n");
|
||||
builder.Append($"Framework: {AppDomain.CurrentDomain.SetupInformation.TargetFrameworkName}\n");
|
||||
builder.Append($"Source: {ex.Source}\n");
|
||||
builder.Append($"Thread Name: {Thread.CurrentThread.Name ?? "Unnamed"}\n");
|
||||
builder.Append($"User: {Environment.UserName}\n");
|
||||
builder.Append($"App Start Time: {Process.GetCurrentProcess().StartTime}\n");
|
||||
builder.Append($"Exception Time: {DateTime.Now}\n");
|
||||
builder.Append($"Memory Usage: {Process.GetCurrentProcess().PrivateMemorySize64 / 1024 / 1024} MB\n");
|
||||
builder.Append($"---------------------------\n\n");
|
||||
builder.Append(ex);
|
||||
|
||||
TryLaunchedAsNormal(desktop);
|
||||
}
|
||||
var time = DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss");
|
||||
var file = Path.Combine(Native.OS.DataDir, $"crash_{time}.log");
|
||||
File.WriteAllText(file, builder.ToString());
|
||||
}
|
||||
#endregion
|
||||
|
||||
public static void OpenDialog(Window window)
|
||||
#region Utility Functions
|
||||
public static void ShowWindow(object data, bool showAsDialog)
|
||||
{
|
||||
var impl = (Views.ChromelessWindow target, bool isDialog) =>
|
||||
{
|
||||
if (Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime { MainWindow: { } owner })
|
||||
window.ShowDialog(owner);
|
||||
{
|
||||
if (isDialog)
|
||||
target.ShowDialog(owner);
|
||||
else
|
||||
target.Show(owner);
|
||||
}
|
||||
else
|
||||
{
|
||||
target.Show();
|
||||
}
|
||||
};
|
||||
|
||||
if (data is Views.ChromelessWindow window)
|
||||
{
|
||||
impl(window, showAsDialog);
|
||||
return;
|
||||
}
|
||||
|
||||
var dataTypeName = data.GetType().FullName;
|
||||
if (string.IsNullOrEmpty(dataTypeName) || !dataTypeName.Contains(".ViewModels.", StringComparison.Ordinal))
|
||||
return;
|
||||
|
||||
var viewTypeName = dataTypeName.Replace(".ViewModels.", ".Views.");
|
||||
var viewType = Type.GetType(viewTypeName);
|
||||
if (viewType == null || !viewType.IsSubclassOf(typeof(Views.ChromelessWindow)))
|
||||
return;
|
||||
|
||||
window = Activator.CreateInstance(viewType) as Views.ChromelessWindow;
|
||||
if (window != null)
|
||||
{
|
||||
window.DataContext = data;
|
||||
impl(window, showAsDialog);
|
||||
}
|
||||
}
|
||||
|
||||
public static void RaiseException(string context, string message)
|
||||
|
@ -204,6 +240,9 @@ namespace SourceGit
|
|||
app._fontsOverrides = null;
|
||||
}
|
||||
|
||||
defaultFont = app.FixFontFamilyName(defaultFont);
|
||||
monospaceFont = app.FixFontFamilyName(monospaceFont);
|
||||
|
||||
var resDic = new ResourceDictionary();
|
||||
if (!string.IsNullOrEmpty(defaultFont))
|
||||
resDic.Add("Fonts.Default", new FontFamily(defaultFont));
|
||||
|
@ -299,26 +338,11 @@ namespace SourceGit
|
|||
return null;
|
||||
}
|
||||
|
||||
public static ViewModels.Launcher GetLauncer()
|
||||
public static ViewModels.Launcher GetLauncher()
|
||||
{
|
||||
return Current is App app ? app._launcher : null;
|
||||
}
|
||||
|
||||
public static ViewModels.Repository FindOpenedRepository(string repoPath)
|
||||
{
|
||||
if (Current is App app && app._launcher != null)
|
||||
{
|
||||
foreach (var page in app._launcher.Pages)
|
||||
{
|
||||
var id = page.Node.Id.Replace("\\", "/");
|
||||
if (id == repoPath && page.Data is ViewModels.Repository repo)
|
||||
return repo;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static void Quit(int exitCode)
|
||||
{
|
||||
if (Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||
|
@ -331,94 +355,68 @@ namespace SourceGit
|
|||
Environment.Exit(exitCode);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
private static void CopyTextBlock(TextBlock textBlock)
|
||||
#region Overrides
|
||||
public override void Initialize()
|
||||
{
|
||||
if (textBlock == null)
|
||||
return;
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
|
||||
if (textBlock.Inlines is { Count: > 0 } inlines)
|
||||
CopyText(inlines.Text);
|
||||
else if (!string.IsNullOrEmpty(textBlock.Text))
|
||||
CopyText(textBlock.Text);
|
||||
var pref = ViewModels.Preferences.Instance;
|
||||
pref.PropertyChanged += (_, _) => pref.Save();
|
||||
|
||||
SetLocale(pref.Locale);
|
||||
SetTheme(pref.Theme, pref.ThemeOverrides);
|
||||
SetFonts(pref.DefaultFontFamily, pref.MonospaceFontFamily, pref.OnlyUseMonoFontInEditor);
|
||||
}
|
||||
|
||||
private static void LogException(Exception ex)
|
||||
public override void OnFrameworkInitializationCompleted()
|
||||
{
|
||||
if (ex == null)
|
||||
return;
|
||||
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||
{
|
||||
BindingPlugins.DataValidators.RemoveAt(0);
|
||||
|
||||
var builder = new StringBuilder();
|
||||
builder.Append($"Crash::: {ex.GetType().FullName}: {ex.Message}\n\n");
|
||||
builder.Append("----------------------------\n");
|
||||
builder.Append($"Version: {Assembly.GetExecutingAssembly().GetName().Version}\n");
|
||||
builder.Append($"OS: {Environment.OSVersion.ToString()}\n");
|
||||
builder.Append($"Framework: {AppDomain.CurrentDomain.SetupInformation.TargetFrameworkName}\n");
|
||||
builder.Append($"Source: {ex.Source}\n");
|
||||
builder.Append($"---------------------------\n\n");
|
||||
builder.Append(ex.StackTrace);
|
||||
while (ex.InnerException != null)
|
||||
// Disable tooltip if window is not active.
|
||||
ToolTip.ToolTipOpeningEvent.AddClassHandler<Control>((c, e) =>
|
||||
{
|
||||
ex = ex.InnerException;
|
||||
builder.Append($"\n\nInnerException::: {ex.GetType().FullName}: {ex.Message}\n");
|
||||
builder.Append(ex.StackTrace);
|
||||
}
|
||||
|
||||
var time = DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss");
|
||||
var file = Path.Combine(Native.OS.DataDir, $"crash_{time}.log");
|
||||
File.WriteAllText(file, builder.ToString());
|
||||
}
|
||||
|
||||
private static void Check4Update(bool manually = false)
|
||||
{
|
||||
Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
// Fetch lastest release information.
|
||||
var client = new HttpClient() { Timeout = TimeSpan.FromSeconds(5) };
|
||||
var data = await client.GetStringAsync("https://sourcegit-scm.github.io/data/version.json");
|
||||
|
||||
// Parse json into Models.Version.
|
||||
var ver = JsonSerializer.Deserialize(data, JsonCodeGen.Default.Version);
|
||||
if (ver == null)
|
||||
return;
|
||||
|
||||
// Check if already up-to-date.
|
||||
if (!ver.IsNewVersion)
|
||||
{
|
||||
if (manually)
|
||||
ShowSelfUpdateResult(new Models.AlreadyUpToDate());
|
||||
return;
|
||||
}
|
||||
|
||||
// Should not check ignored tag if this is called manually.
|
||||
if (!manually)
|
||||
{
|
||||
var pref = ViewModels.Preference.Instance;
|
||||
if (ver.TagName == pref.IgnoreUpdateTag)
|
||||
return;
|
||||
}
|
||||
|
||||
ShowSelfUpdateResult(ver);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (manually)
|
||||
ShowSelfUpdateResult(e);
|
||||
}
|
||||
var topLevel = TopLevel.GetTopLevel(c);
|
||||
if (topLevel is not Window { IsActive: true })
|
||||
e.Cancel = true;
|
||||
});
|
||||
|
||||
if (TryLaunchAsCoreEditor(desktop))
|
||||
return;
|
||||
|
||||
if (TryLaunchAsAskpass(desktop))
|
||||
return;
|
||||
|
||||
_ipcChannel = new Models.IpcChannel();
|
||||
if (!_ipcChannel.IsFirstInstance)
|
||||
{
|
||||
var arg = desktop.Args is { Length: > 0 } ? desktop.Args[0].Trim() : string.Empty;
|
||||
if (!string.IsNullOrEmpty(arg))
|
||||
{
|
||||
if (arg.StartsWith('"') && arg.EndsWith('"'))
|
||||
arg = arg.Substring(1, arg.Length - 2).Trim();
|
||||
|
||||
if (arg.Length > 0 && !Path.IsPathFullyQualified(arg))
|
||||
arg = Path.GetFullPath(arg);
|
||||
}
|
||||
|
||||
private static void ShowSelfUpdateResult(object data)
|
||||
{
|
||||
Dispatcher.UIThread.Post(() =>
|
||||
{
|
||||
OpenDialog(new Views.SelfUpdate() { DataContext = new ViewModels.SelfUpdate() { Data = data } });
|
||||
});
|
||||
_ipcChannel.SendToFirstInstance(arg);
|
||||
Environment.Exit(0);
|
||||
}
|
||||
else
|
||||
{
|
||||
_ipcChannel.MessageReceived += TryOpenRepository;
|
||||
desktop.Exit += (_, _) => _ipcChannel.Dispose();
|
||||
TryLaunchAsNormal(desktop);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
private static bool TryLaunchedAsRebaseTodoEditor(string[] args, out int exitCode)
|
||||
private static bool TryLaunchAsRebaseTodoEditor(string[] args, out int exitCode)
|
||||
{
|
||||
exitCode = -1;
|
||||
|
||||
|
@ -471,7 +469,7 @@ namespace SourceGit
|
|||
return true;
|
||||
}
|
||||
|
||||
private static bool TryLaunchedAsRebaseMessageEditor(string[] args, out int exitCode)
|
||||
private static bool TryLaunchAsRebaseMessageEditor(string[] args, out int exitCode)
|
||||
{
|
||||
exitCode = -1;
|
||||
|
||||
|
@ -486,26 +484,42 @@ namespace SourceGit
|
|||
return true;
|
||||
|
||||
var gitDir = Path.GetDirectoryName(file)!;
|
||||
var jobsFile = Path.Combine(gitDir, "sourcegit_rebase_jobs.json");
|
||||
if (!File.Exists(jobsFile))
|
||||
return true;
|
||||
|
||||
var collection = JsonSerializer.Deserialize(File.ReadAllText(jobsFile), JsonCodeGen.Default.InteractiveRebaseJobCollection);
|
||||
var origHeadFile = Path.Combine(gitDir, "rebase-merge", "orig-head");
|
||||
var ontoFile = Path.Combine(gitDir, "rebase-merge", "onto");
|
||||
var doneFile = Path.Combine(gitDir, "rebase-merge", "done");
|
||||
if (!File.Exists(doneFile))
|
||||
var jobsFile = Path.Combine(gitDir, "sourcegit_rebase_jobs.json");
|
||||
if (!File.Exists(ontoFile) || !File.Exists(origHeadFile) || !File.Exists(doneFile) || !File.Exists(jobsFile))
|
||||
return true;
|
||||
|
||||
var done = File.ReadAllText(doneFile).Split(new[] { '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
if (done.Length > collection.Jobs.Count)
|
||||
var origHead = File.ReadAllText(origHeadFile).Trim();
|
||||
var onto = File.ReadAllText(ontoFile).Trim();
|
||||
var collection = JsonSerializer.Deserialize(File.ReadAllText(jobsFile), JsonCodeGen.Default.InteractiveRebaseJobCollection);
|
||||
if (!collection.Onto.Equals(onto) || !collection.OrigHead.Equals(origHead))
|
||||
return true;
|
||||
|
||||
var job = collection.Jobs[done.Length - 1];
|
||||
var done = File.ReadAllText(doneFile).Trim().Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries);
|
||||
if (done.Length == 0)
|
||||
return true;
|
||||
|
||||
var current = done[^1].Trim();
|
||||
var match = REG_REBASE_TODO().Match(current);
|
||||
if (!match.Success)
|
||||
return true;
|
||||
|
||||
var sha = match.Groups[1].Value;
|
||||
foreach (var job in collection.Jobs)
|
||||
{
|
||||
if (job.SHA.StartsWith(sha))
|
||||
{
|
||||
File.WriteAllText(file, job.Message);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool TryLaunchedAsCoreEditor(IClassicDesktopStyleApplicationLifetime desktop)
|
||||
private bool TryLaunchAsCoreEditor(IClassicDesktopStyleApplicationLifetime desktop)
|
||||
{
|
||||
var args = desktop.Args;
|
||||
if (args == null || args.Length <= 1 || !args[0].Equals("--core-editor", StringComparison.Ordinal))
|
||||
|
@ -513,14 +527,18 @@ namespace SourceGit
|
|||
|
||||
var file = args[1];
|
||||
if (!File.Exists(file))
|
||||
{
|
||||
desktop.Shutdown(-1);
|
||||
else
|
||||
desktop.MainWindow = new Views.StandaloneCommitMessageEditor(file);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool TryLaunchedAsAskpass(IClassicDesktopStyleApplicationLifetime desktop)
|
||||
var editor = new Views.StandaloneCommitMessageEditor();
|
||||
editor.SetFile(file);
|
||||
desktop.MainWindow = editor;
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool TryLaunchAsAskpass(IClassicDesktopStyleApplicationLifetime desktop)
|
||||
{
|
||||
var launchAsAskpass = Environment.GetEnvironmentVariable("SOURCEGIT_LAUNCH_AS_ASKPASS");
|
||||
if (launchAsAskpass is not "TRUE")
|
||||
|
@ -529,30 +547,159 @@ namespace SourceGit
|
|||
var args = desktop.Args;
|
||||
if (args?.Length > 0)
|
||||
{
|
||||
desktop.MainWindow = new Views.Askpass(args[0]);
|
||||
var askpass = new Views.Askpass();
|
||||
askpass.TxtDescription.Text = args[0];
|
||||
desktop.MainWindow = askpass;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void TryLaunchedAsNormal(IClassicDesktopStyleApplicationLifetime desktop)
|
||||
private void TryLaunchAsNormal(IClassicDesktopStyleApplicationLifetime desktop)
|
||||
{
|
||||
Native.OS.SetupEnternalTools();
|
||||
Native.OS.SetupExternalTools();
|
||||
Models.AvatarManager.Instance.Start();
|
||||
|
||||
string startupRepo = null;
|
||||
if (desktop.Args != null && desktop.Args.Length == 1 && Directory.Exists(desktop.Args[0]))
|
||||
startupRepo = desktop.Args[0];
|
||||
|
||||
var pref = ViewModels.Preferences.Instance;
|
||||
pref.SetCanModify();
|
||||
|
||||
_launcher = new ViewModels.Launcher(startupRepo);
|
||||
desktop.MainWindow = new Views.Launcher() { DataContext = _launcher };
|
||||
desktop.ShutdownMode = ShutdownMode.OnMainWindowClose;
|
||||
|
||||
var pref = ViewModels.Preference.Instance;
|
||||
#if !DISABLE_UPDATE_DETECTION
|
||||
if (pref.ShouldCheck4UpdateOnStartup())
|
||||
Check4Update();
|
||||
#endif
|
||||
}
|
||||
|
||||
private void TryOpenRepository(string repo)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(repo) && Directory.Exists(repo))
|
||||
{
|
||||
var test = new Commands.QueryRepositoryRootPath(repo).ReadToEnd();
|
||||
if (test.IsSuccess && !string.IsNullOrEmpty(test.StdOut))
|
||||
{
|
||||
Dispatcher.UIThread.Invoke(() =>
|
||||
{
|
||||
var node = ViewModels.Preferences.Instance.FindOrAddNodeByRepositoryPath(test.StdOut.Trim(), null, false);
|
||||
ViewModels.Welcome.Instance.Refresh();
|
||||
_launcher?.OpenRepositoryInTab(node, null);
|
||||
|
||||
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime { MainWindow: Views.Launcher wnd })
|
||||
wnd.BringToTop();
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Dispatcher.UIThread.Invoke(() =>
|
||||
{
|
||||
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime { MainWindow: Views.Launcher launcher })
|
||||
launcher.BringToTop();
|
||||
});
|
||||
}
|
||||
|
||||
private void Check4Update(bool manually = false)
|
||||
{
|
||||
Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
// Fetch latest release information.
|
||||
var client = new HttpClient() { Timeout = TimeSpan.FromSeconds(5) };
|
||||
var data = await client.GetStringAsync("https://sourcegit-scm.github.io/data/version.json");
|
||||
|
||||
// Parse JSON into Models.Version.
|
||||
var ver = JsonSerializer.Deserialize(data, JsonCodeGen.Default.Version);
|
||||
if (ver == null)
|
||||
return;
|
||||
|
||||
// Check if already up-to-date.
|
||||
if (!ver.IsNewVersion)
|
||||
{
|
||||
if (manually)
|
||||
ShowSelfUpdateResult(new Models.AlreadyUpToDate());
|
||||
return;
|
||||
}
|
||||
|
||||
// Should not check ignored tag if this is called manually.
|
||||
if (!manually)
|
||||
{
|
||||
var pref = ViewModels.Preferences.Instance;
|
||||
if (ver.TagName == pref.IgnoreUpdateTag)
|
||||
return;
|
||||
}
|
||||
|
||||
ShowSelfUpdateResult(ver);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (manually)
|
||||
ShowSelfUpdateResult(new Models.SelfUpdateFailed(e));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void ShowSelfUpdateResult(object data)
|
||||
{
|
||||
Dispatcher.UIThread.Post(() =>
|
||||
{
|
||||
ShowWindow(new ViewModels.SelfUpdate() { Data = data }, true);
|
||||
});
|
||||
}
|
||||
|
||||
private string FixFontFamilyName(string input)
|
||||
{
|
||||
if (string.IsNullOrEmpty(input))
|
||||
return string.Empty;
|
||||
|
||||
var parts = input.Split(',');
|
||||
var trimmed = new List<string>();
|
||||
|
||||
foreach (var part in parts)
|
||||
{
|
||||
var t = part.Trim();
|
||||
if (string.IsNullOrEmpty(t))
|
||||
continue;
|
||||
|
||||
// Collapse multiple spaces into single space
|
||||
var prevChar = '\0';
|
||||
var sb = new StringBuilder();
|
||||
|
||||
foreach (var c in t)
|
||||
{
|
||||
if (c == ' ' && prevChar == ' ')
|
||||
continue;
|
||||
sb.Append(c);
|
||||
prevChar = c;
|
||||
}
|
||||
|
||||
var name = sb.ToString();
|
||||
var idx = name.IndexOf('#');
|
||||
if (idx >= 0)
|
||||
{
|
||||
if (!name.Equals("fonts:Inter#Inter", StringComparison.Ordinal) &&
|
||||
!name.Equals("fonts:SourceGit#JetBrains Mono", StringComparison.Ordinal))
|
||||
continue;
|
||||
}
|
||||
|
||||
trimmed.Add(name);
|
||||
}
|
||||
|
||||
return trimmed.Count > 0 ? string.Join(',', trimmed) : string.Empty;
|
||||
}
|
||||
|
||||
[GeneratedRegex(@"^[a-z]+\s+([a-fA-F0-9]{4,40})(\s+.*)?$")]
|
||||
private static partial Regex REG_REBASE_TODO();
|
||||
|
||||
private Models.IpcChannel _ipcChannel = null;
|
||||
private ViewModels.Launcher _launcher = null;
|
||||
private ResourceDictionary _activeLocale = null;
|
||||
private ResourceDictionary _themeOverrides = null;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<!-- This manifest is used on Windows only.
|
||||
Don't remove it as it might cause problems with window transparency and embeded controls.
|
||||
Don't remove it as it might cause problems with window transparency and embedded controls.
|
||||
For more details visit https://learn.microsoft.com/en-us/windows/win32/sbscs/application-manifests -->
|
||||
<assemblyIdentity version="1.0.0.0" name="SourceGit.Desktop"/>
|
||||
|
||||
|
|
|
@ -1,7 +1,4 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace SourceGit.Commands
|
||||
namespace SourceGit.Commands
|
||||
{
|
||||
public class Add : Command
|
||||
{
|
||||
|
@ -12,20 +9,18 @@ namespace SourceGit.Commands
|
|||
Args = includeUntracked ? "add ." : "add -u .";
|
||||
}
|
||||
|
||||
public Add(string repo, List<Models.Change> changes)
|
||||
public Add(string repo, Models.Change change)
|
||||
{
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
|
||||
var builder = new StringBuilder();
|
||||
builder.Append("add --");
|
||||
foreach (var c in changes)
|
||||
{
|
||||
builder.Append(" \"");
|
||||
builder.Append(c.Path);
|
||||
builder.Append("\"");
|
||||
Args = $"add -- \"{change.Path}\"";
|
||||
}
|
||||
Args = builder.ToString();
|
||||
|
||||
public Add(string repo, string pathspecFromFile)
|
||||
{
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
Args = $"add --pathspec-from-file=\"{pathspecFromFile}\"";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,23 +1,12 @@
|
|||
using System;
|
||||
|
||||
namespace SourceGit.Commands
|
||||
namespace SourceGit.Commands
|
||||
{
|
||||
public class Archive : Command
|
||||
{
|
||||
public Archive(string repo, string revision, string saveTo, Action<string> outputHandler)
|
||||
public Archive(string repo, string revision, string saveTo)
|
||||
{
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
Args = $"archive --format=zip --verbose --output=\"{saveTo}\" {revision}";
|
||||
TraitErrorAsOutput = true;
|
||||
_outputHandler = outputHandler;
|
||||
}
|
||||
|
||||
protected override void OnReadline(string line)
|
||||
{
|
||||
_outputHandler?.Invoke(line);
|
||||
}
|
||||
|
||||
private readonly Action<string> _outputHandler;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,46 +1,8 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace SourceGit.Commands
|
||||
namespace SourceGit.Commands
|
||||
{
|
||||
public partial class AssumeUnchanged
|
||||
public class AssumeUnchanged : Command
|
||||
{
|
||||
[GeneratedRegex(@"^(\w)\s+(.+)$")]
|
||||
private static partial Regex REG_PARSE();
|
||||
|
||||
class ViewCommand : Command
|
||||
{
|
||||
public ViewCommand(string repo)
|
||||
{
|
||||
WorkingDirectory = repo;
|
||||
Args = "ls-files -v";
|
||||
RaiseError = false;
|
||||
}
|
||||
|
||||
public List<string> Result()
|
||||
{
|
||||
Exec();
|
||||
return _outs;
|
||||
}
|
||||
|
||||
protected override void OnReadline(string line)
|
||||
{
|
||||
var match = REG_PARSE().Match(line);
|
||||
if (!match.Success)
|
||||
return;
|
||||
|
||||
if (match.Groups[1].Value == "h")
|
||||
{
|
||||
_outs.Add(match.Groups[2].Value);
|
||||
}
|
||||
}
|
||||
|
||||
private readonly List<string> _outs = new List<string>();
|
||||
}
|
||||
|
||||
class ModCommand : Command
|
||||
{
|
||||
public ModCommand(string repo, string file, bool bAdd)
|
||||
public AssumeUnchanged(string repo, string file, bool bAdd)
|
||||
{
|
||||
var mode = bAdd ? "--assume-unchanged" : "--no-assume-unchanged";
|
||||
|
||||
|
@ -49,27 +11,4 @@ namespace SourceGit.Commands
|
|||
Args = $"update-index {mode} -- \"{file}\"";
|
||||
}
|
||||
}
|
||||
|
||||
public AssumeUnchanged(string repo)
|
||||
{
|
||||
_repo = repo;
|
||||
}
|
||||
|
||||
public List<string> View()
|
||||
{
|
||||
return new ViewCommand(_repo).Result();
|
||||
}
|
||||
|
||||
public void Add(string file)
|
||||
{
|
||||
new ModCommand(_repo, file, true).Exec();
|
||||
}
|
||||
|
||||
public void Remove(string file)
|
||||
{
|
||||
new ModCommand(_repo, file, false).Exec();
|
||||
}
|
||||
|
||||
private readonly string _repo;
|
||||
}
|
||||
}
|
||||
|
|
13
src/Commands/Bisect.cs
Normal file
13
src/Commands/Bisect.cs
Normal file
|
@ -0,0 +1,13 @@
|
|||
namespace SourceGit.Commands
|
||||
{
|
||||
public class Bisect : Command
|
||||
{
|
||||
public Bisect(string repo, string subcmd)
|
||||
{
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
RaiseError = false;
|
||||
Args = $"bisect {subcmd}";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -21,10 +21,17 @@ namespace SourceGit.Commands
|
|||
|
||||
public Models.BlameData Result()
|
||||
{
|
||||
var succ = Exec();
|
||||
if (!succ)
|
||||
var rs = ReadToEnd();
|
||||
if (!rs.IsSuccess)
|
||||
return _result;
|
||||
|
||||
var lines = rs.StdOut.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries);
|
||||
foreach (var line in lines)
|
||||
{
|
||||
return new Models.BlameData();
|
||||
ParseLine(line);
|
||||
|
||||
if (_result.IsBinary)
|
||||
break;
|
||||
}
|
||||
|
||||
if (_needUnifyCommitSHA)
|
||||
|
@ -42,13 +49,8 @@ namespace SourceGit.Commands
|
|||
return _result;
|
||||
}
|
||||
|
||||
protected override void OnReadline(string line)
|
||||
private void ParseLine(string line)
|
||||
{
|
||||
if (_result.IsBinary)
|
||||
return;
|
||||
if (string.IsNullOrEmpty(line))
|
||||
return;
|
||||
|
||||
if (line.IndexOf('\0', StringComparison.Ordinal) >= 0)
|
||||
{
|
||||
_result.IsBinary = true;
|
||||
|
@ -65,7 +67,7 @@ namespace SourceGit.Commands
|
|||
var commit = match.Groups[1].Value;
|
||||
var author = match.Groups[2].Value;
|
||||
var timestamp = int.Parse(match.Groups[3].Value);
|
||||
var when = DateTime.UnixEpoch.AddSeconds(timestamp).ToLocalTime().ToString("yyyy/MM/dd");
|
||||
var when = DateTime.UnixEpoch.AddSeconds(timestamp).ToLocalTime().ToString(_dateFormat);
|
||||
|
||||
var info = new Models.BlameLineInfo()
|
||||
{
|
||||
|
@ -87,6 +89,7 @@ namespace SourceGit.Commands
|
|||
|
||||
private readonly Models.BlameData _result = new Models.BlameData();
|
||||
private readonly StringBuilder _content = new StringBuilder();
|
||||
private readonly string _dateFormat = Models.DateTimeFormat.Active.DateOnly;
|
||||
private string _lastSHA = string.Empty;
|
||||
private bool _needUnifyCommitSHA = false;
|
||||
private int _minSHALen = 64;
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
namespace SourceGit.Commands
|
||||
using System.Text;
|
||||
|
||||
namespace SourceGit.Commands
|
||||
{
|
||||
public static class Branch
|
||||
{
|
||||
|
@ -11,29 +13,40 @@
|
|||
return cmd.ReadToEnd().StdOut.Trim();
|
||||
}
|
||||
|
||||
public static bool Create(string repo, string name, string basedOn)
|
||||
public static bool Create(string repo, string name, string basedOn, bool force, Models.ICommandLog log)
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
builder.Append("branch ");
|
||||
if (force)
|
||||
builder.Append("-f ");
|
||||
builder.Append(name);
|
||||
builder.Append(" ");
|
||||
builder.Append(basedOn);
|
||||
|
||||
var cmd = new Command();
|
||||
cmd.WorkingDirectory = repo;
|
||||
cmd.Context = repo;
|
||||
cmd.Args = $"branch {name} {basedOn}";
|
||||
cmd.Args = builder.ToString();
|
||||
cmd.Log = log;
|
||||
return cmd.Exec();
|
||||
}
|
||||
|
||||
public static bool Rename(string repo, string name, string to)
|
||||
public static bool Rename(string repo, string name, string to, Models.ICommandLog log)
|
||||
{
|
||||
var cmd = new Command();
|
||||
cmd.WorkingDirectory = repo;
|
||||
cmd.Context = repo;
|
||||
cmd.Args = $"branch -M {name} {to}";
|
||||
cmd.Log = log;
|
||||
return cmd.Exec();
|
||||
}
|
||||
|
||||
public static bool SetUpstream(string repo, string name, string upstream)
|
||||
public static bool SetUpstream(string repo, string name, string upstream, Models.ICommandLog log)
|
||||
{
|
||||
var cmd = new Command();
|
||||
cmd.WorkingDirectory = repo;
|
||||
cmd.Context = repo;
|
||||
cmd.Log = log;
|
||||
|
||||
if (string.IsNullOrEmpty(upstream))
|
||||
cmd.Args = $"branch {name} --unset-upstream";
|
||||
|
@ -43,32 +56,27 @@
|
|||
return cmd.Exec();
|
||||
}
|
||||
|
||||
public static bool DeleteLocal(string repo, string name)
|
||||
public static bool DeleteLocal(string repo, string name, Models.ICommandLog log)
|
||||
{
|
||||
var cmd = new Command();
|
||||
cmd.WorkingDirectory = repo;
|
||||
cmd.Context = repo;
|
||||
cmd.Args = $"branch -D {name}";
|
||||
cmd.Log = log;
|
||||
return cmd.Exec();
|
||||
}
|
||||
|
||||
public static bool DeleteRemote(string repo, string remote, string name)
|
||||
public static bool DeleteRemote(string repo, string remote, string name, Models.ICommandLog log)
|
||||
{
|
||||
bool exists = new Remote(repo).HasBranch(remote, name);
|
||||
if (exists)
|
||||
return new Push(repo, remote, $"refs/heads/{name}", true) { Log = log }.Exec();
|
||||
|
||||
var cmd = new Command();
|
||||
cmd.WorkingDirectory = repo;
|
||||
cmd.Context = repo;
|
||||
|
||||
bool exists = new Remote(repo).HasBranch(remote, name);
|
||||
if (exists)
|
||||
{
|
||||
cmd.SSHKey = new Config(repo).Get($"remote.{remote}.sshkey");
|
||||
cmd.Args = $"push {remote} --delete {name}";
|
||||
}
|
||||
else
|
||||
{
|
||||
cmd.Args = $"branch -D -r {remote}/{name}";
|
||||
}
|
||||
|
||||
cmd.Log = log;
|
||||
return cmd.Exec();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace SourceGit.Commands
|
||||
|
@ -12,19 +11,37 @@ namespace SourceGit.Commands
|
|||
Context = repo;
|
||||
}
|
||||
|
||||
public bool Branch(string branch, Action<string> onProgress)
|
||||
public bool Branch(string branch, bool force)
|
||||
{
|
||||
Args = $"checkout --recurse-submodules --progress {branch}";
|
||||
TraitErrorAsOutput = true;
|
||||
_outputHandler = onProgress;
|
||||
var builder = new StringBuilder();
|
||||
builder.Append("checkout --progress ");
|
||||
if (force)
|
||||
builder.Append("--force ");
|
||||
builder.Append(branch);
|
||||
|
||||
Args = builder.ToString();
|
||||
return Exec();
|
||||
}
|
||||
|
||||
public bool Branch(string branch, string basedOn, Action<string> onProgress)
|
||||
public bool Branch(string branch, string basedOn, bool force, bool allowOverwrite)
|
||||
{
|
||||
Args = $"checkout --recurse-submodules --progress -b {branch} {basedOn}";
|
||||
TraitErrorAsOutput = true;
|
||||
_outputHandler = onProgress;
|
||||
var builder = new StringBuilder();
|
||||
builder.Append("checkout --progress ");
|
||||
if (force)
|
||||
builder.Append("--force ");
|
||||
builder.Append(allowOverwrite ? "-B " : "-b ");
|
||||
builder.Append(branch);
|
||||
builder.Append(" ");
|
||||
builder.Append(basedOn);
|
||||
|
||||
Args = builder.ToString();
|
||||
return Exec();
|
||||
}
|
||||
|
||||
public bool Commit(string commitId, bool force)
|
||||
{
|
||||
var option = force ? "--force" : string.Empty;
|
||||
Args = $"checkout {option} --detach --progress {commitId}";
|
||||
return Exec();
|
||||
}
|
||||
|
||||
|
@ -61,20 +78,5 @@ namespace SourceGit.Commands
|
|||
Args = $"checkout --no-overlay {revision} -- \"{file}\"";
|
||||
return Exec();
|
||||
}
|
||||
|
||||
public bool Commit(string commitId, Action<string> onProgress)
|
||||
{
|
||||
Args = $"checkout --detach --progress {commitId}";
|
||||
TraitErrorAsOutput = true;
|
||||
_outputHandler = onProgress;
|
||||
return Exec();
|
||||
}
|
||||
|
||||
protected override void OnReadline(string line)
|
||||
{
|
||||
_outputHandler?.Invoke(line);
|
||||
}
|
||||
|
||||
private Action<string> _outputHandler;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,31 +1,12 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace SourceGit.Commands
|
||||
namespace SourceGit.Commands
|
||||
{
|
||||
public class Clean : Command
|
||||
{
|
||||
public Clean(string repo, bool includeIgnored)
|
||||
public Clean(string repo)
|
||||
{
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
Args = includeIgnored ? "clean -qfdx" : "clean -qfd";
|
||||
}
|
||||
|
||||
public Clean(string repo, List<string> files)
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
builder.Append("clean -qfd --");
|
||||
foreach (var f in files)
|
||||
{
|
||||
builder.Append(" \"");
|
||||
builder.Append(f);
|
||||
builder.Append("\"");
|
||||
}
|
||||
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
Args = builder.ToString();
|
||||
Args = "clean -qfdx";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,18 +1,13 @@
|
|||
using System;
|
||||
|
||||
namespace SourceGit.Commands
|
||||
namespace SourceGit.Commands
|
||||
{
|
||||
public class Clone : Command
|
||||
{
|
||||
private readonly Action<string> _notifyProgress;
|
||||
|
||||
public Clone(string ctx, string path, string url, string localName, string sshKey, string extraArgs, Action<string> ouputHandler)
|
||||
public Clone(string ctx, string path, string url, string localName, string sshKey, string extraArgs)
|
||||
{
|
||||
Context = ctx;
|
||||
WorkingDirectory = path;
|
||||
TraitErrorAsOutput = true;
|
||||
SSHKey = sshKey;
|
||||
Args = "clone --progress --verbose --recurse-submodules ";
|
||||
Args = "clone --progress --verbose ";
|
||||
|
||||
if (!string.IsNullOrEmpty(extraArgs))
|
||||
Args += $"{extraArgs} ";
|
||||
|
@ -21,13 +16,6 @@ namespace SourceGit.Commands
|
|||
|
||||
if (!string.IsNullOrEmpty(localName))
|
||||
Args += localName;
|
||||
|
||||
_notifyProgress = ouputHandler;
|
||||
}
|
||||
|
||||
protected override void OnReadline(string line)
|
||||
{
|
||||
_notifyProgress?.Invoke(line);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
|||
using System.Diagnostics;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
|
||||
using Avalonia.Threading;
|
||||
|
||||
|
@ -10,11 +11,6 @@ namespace SourceGit.Commands
|
|||
{
|
||||
public partial class Command
|
||||
{
|
||||
public class CancelToken
|
||||
{
|
||||
public bool Requested { get; set; } = false;
|
||||
}
|
||||
|
||||
public class ReadToEndResult
|
||||
{
|
||||
public bool IsSuccess { get; set; } = false;
|
||||
|
@ -30,82 +26,51 @@ namespace SourceGit.Commands
|
|||
}
|
||||
|
||||
public string Context { get; set; } = string.Empty;
|
||||
public CancelToken Cancel { get; set; } = null;
|
||||
public CancellationToken CancellationToken { get; set; } = CancellationToken.None;
|
||||
public string WorkingDirectory { get; set; } = null;
|
||||
public EditorType Editor { get; set; } = EditorType.CoreEditor; // Only used in Exec() mode
|
||||
public string SSHKey { get; set; } = string.Empty;
|
||||
public string Args { get; set; } = string.Empty;
|
||||
public bool RaiseError { get; set; } = true;
|
||||
public bool TraitErrorAsOutput { get; set; } = false;
|
||||
public Models.ICommandLog Log { get; set; } = null;
|
||||
|
||||
public bool Exec()
|
||||
{
|
||||
Log?.AppendLine($"$ git {Args}\n");
|
||||
|
||||
var start = CreateGitStartInfo();
|
||||
var errs = new List<string>();
|
||||
var proc = new Process() { StartInfo = start };
|
||||
var isCancelled = false;
|
||||
|
||||
proc.OutputDataReceived += (_, e) =>
|
||||
{
|
||||
if (Cancel != null && Cancel.Requested)
|
||||
{
|
||||
isCancelled = true;
|
||||
proc.CancelErrorRead();
|
||||
proc.CancelOutputRead();
|
||||
if (!proc.HasExited)
|
||||
proc.Kill(true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.Data != null)
|
||||
OnReadline(e.Data);
|
||||
};
|
||||
|
||||
proc.ErrorDataReceived += (_, e) =>
|
||||
{
|
||||
if (Cancel != null && Cancel.Requested)
|
||||
{
|
||||
isCancelled = true;
|
||||
proc.CancelErrorRead();
|
||||
proc.CancelOutputRead();
|
||||
if (!proc.HasExited)
|
||||
proc.Kill(true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(e.Data))
|
||||
{
|
||||
errs.Add(string.Empty);
|
||||
return;
|
||||
}
|
||||
|
||||
if (TraitErrorAsOutput)
|
||||
OnReadline(e.Data);
|
||||
|
||||
// Ignore progress messages
|
||||
if (e.Data.StartsWith("remote: Enumerating objects:", StringComparison.Ordinal))
|
||||
return;
|
||||
if (e.Data.StartsWith("remote: Counting objects:", StringComparison.Ordinal))
|
||||
return;
|
||||
if (e.Data.StartsWith("remote: Compressing objects:", StringComparison.Ordinal))
|
||||
return;
|
||||
if (e.Data.StartsWith("Filtering content:", StringComparison.Ordinal))
|
||||
return;
|
||||
if (REG_PROGRESS().IsMatch(e.Data))
|
||||
return;
|
||||
|
||||
errs.Add(e.Data);
|
||||
};
|
||||
proc.OutputDataReceived += (_, e) => HandleOutput(e.Data, errs);
|
||||
proc.ErrorDataReceived += (_, e) => HandleOutput(e.Data, errs);
|
||||
|
||||
var dummy = null as Process;
|
||||
var dummyProcLock = new object();
|
||||
try
|
||||
{
|
||||
proc.Start();
|
||||
|
||||
// It not safe, please only use `CancellationToken` in readonly commands.
|
||||
if (CancellationToken.CanBeCanceled)
|
||||
{
|
||||
dummy = proc;
|
||||
CancellationToken.Register(() =>
|
||||
{
|
||||
lock (dummyProcLock)
|
||||
{
|
||||
if (dummy is { HasExited: false })
|
||||
dummy.Kill();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (RaiseError)
|
||||
Dispatcher.UIThread.Post(() => App.RaiseException(Context, e.Message));
|
||||
|
||||
Log?.AppendLine(string.Empty);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -113,15 +78,24 @@ namespace SourceGit.Commands
|
|||
proc.BeginErrorReadLine();
|
||||
proc.WaitForExit();
|
||||
|
||||
if (dummy != null)
|
||||
{
|
||||
lock (dummyProcLock)
|
||||
{
|
||||
dummy = null;
|
||||
}
|
||||
}
|
||||
|
||||
int exitCode = proc.ExitCode;
|
||||
proc.Close();
|
||||
Log?.AppendLine(string.Empty);
|
||||
|
||||
if (!isCancelled && exitCode != 0)
|
||||
if (!CancellationToken.IsCancellationRequested && exitCode != 0)
|
||||
{
|
||||
if (RaiseError)
|
||||
{
|
||||
var errMsg = string.Join("\n", errs);
|
||||
if (!string.IsNullOrWhiteSpace(errMsg))
|
||||
var errMsg = string.Join("\n", errs).Trim();
|
||||
if (!string.IsNullOrEmpty(errMsg))
|
||||
Dispatcher.UIThread.Post(() => App.RaiseException(Context, errMsg));
|
||||
}
|
||||
|
||||
|
@ -163,11 +137,6 @@ namespace SourceGit.Commands
|
|||
return rs;
|
||||
}
|
||||
|
||||
protected virtual void OnReadline(string line)
|
||||
{
|
||||
// Implemented by derived class
|
||||
}
|
||||
|
||||
private ProcessStartInfo CreateGitStartInfo()
|
||||
{
|
||||
var start = new ProcessStartInfo();
|
||||
|
@ -192,13 +161,12 @@ namespace SourceGit.Commands
|
|||
if (!start.Environment.ContainsKey("GIT_SSH_COMMAND") && !string.IsNullOrEmpty(SSHKey))
|
||||
start.Environment.Add("GIT_SSH_COMMAND", $"ssh -i '{SSHKey}'");
|
||||
|
||||
// Force using en_US.UTF-8 locale to avoid GCM crash
|
||||
// Force using en_US.UTF-8 locale
|
||||
if (OperatingSystem.IsLinux())
|
||||
start.Environment.Add("LANG", "en_US.UTF-8");
|
||||
|
||||
// Fix macOS `PATH` env
|
||||
if (OperatingSystem.IsMacOS() && !string.IsNullOrEmpty(Native.OS.CustomPathEnv))
|
||||
start.Environment.Add("PATH", Native.OS.CustomPathEnv);
|
||||
{
|
||||
start.Environment.Add("LANG", "C");
|
||||
start.Environment.Add("LC_ALL", "C");
|
||||
}
|
||||
|
||||
// Force using this app as git editor.
|
||||
switch (Editor)
|
||||
|
@ -224,6 +192,28 @@ namespace SourceGit.Commands
|
|||
return start;
|
||||
}
|
||||
|
||||
private void HandleOutput(string line, List<string> errs)
|
||||
{
|
||||
line = line ?? string.Empty;
|
||||
Log?.AppendLine(line);
|
||||
|
||||
// Lines to hide in error message.
|
||||
if (line.Length > 0)
|
||||
{
|
||||
if (line.StartsWith("remote: Enumerating objects:", StringComparison.Ordinal) ||
|
||||
line.StartsWith("remote: Counting objects:", StringComparison.Ordinal) ||
|
||||
line.StartsWith("remote: Compressing objects:", StringComparison.Ordinal) ||
|
||||
line.StartsWith("Filtering content:", StringComparison.Ordinal) ||
|
||||
line.StartsWith("hint:", StringComparison.Ordinal))
|
||||
return;
|
||||
|
||||
if (REG_PROGRESS().IsMatch(line))
|
||||
return;
|
||||
}
|
||||
|
||||
errs.Add(line);
|
||||
}
|
||||
|
||||
[GeneratedRegex(@"\d+%")]
|
||||
private static partial Regex REG_PROGRESS();
|
||||
}
|
||||
|
|
|
@ -4,19 +4,18 @@ namespace SourceGit.Commands
|
|||
{
|
||||
public class Commit : Command
|
||||
{
|
||||
public Commit(string repo, string message, bool amend, bool signOff)
|
||||
public Commit(string repo, string message, bool signOff, bool amend, bool resetAuthor)
|
||||
{
|
||||
_tmpFile = Path.GetTempFileName();
|
||||
File.WriteAllText(_tmpFile, message);
|
||||
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
TraitErrorAsOutput = true;
|
||||
Args = $"commit --allow-empty --file=\"{_tmpFile}\"";
|
||||
if (amend)
|
||||
Args += " --amend --no-edit";
|
||||
if (signOff)
|
||||
Args += " --signoff";
|
||||
if (amend)
|
||||
Args += resetAuthor ? " --amend --reset-author --no-edit" : " --amend --no-edit";
|
||||
}
|
||||
|
||||
public bool Run()
|
||||
|
|
|
@ -6,8 +6,10 @@ namespace SourceGit.Commands
|
|||
{
|
||||
public partial class CompareRevisions : Command
|
||||
{
|
||||
[GeneratedRegex(@"^(\s?[\w\?]{1,4})\s+(.+)$")]
|
||||
[GeneratedRegex(@"^([MADC])\s+(.+)$")]
|
||||
private static partial Regex REG_FORMAT();
|
||||
[GeneratedRegex(@"^R[0-9]{0,4}\s+(.+)$")]
|
||||
private static partial Regex REG_RENAME_FORMAT();
|
||||
|
||||
public CompareRevisions(string repo, string start, string end)
|
||||
{
|
||||
|
@ -18,18 +20,44 @@ namespace SourceGit.Commands
|
|||
Args = $"diff --name-status {based} {end}";
|
||||
}
|
||||
|
||||
public CompareRevisions(string repo, string start, string end, string path)
|
||||
{
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
|
||||
var based = string.IsNullOrEmpty(start) ? "-R" : start;
|
||||
Args = $"diff --name-status {based} {end} -- \"{path}\"";
|
||||
}
|
||||
|
||||
public List<Models.Change> Result()
|
||||
{
|
||||
Exec();
|
||||
var rs = ReadToEnd();
|
||||
if (!rs.IsSuccess)
|
||||
return _changes;
|
||||
|
||||
var lines = rs.StdOut.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries);
|
||||
foreach (var line in lines)
|
||||
ParseLine(line);
|
||||
|
||||
_changes.Sort((l, r) => string.Compare(l.Path, r.Path, StringComparison.Ordinal));
|
||||
return _changes;
|
||||
}
|
||||
|
||||
protected override void OnReadline(string line)
|
||||
private void ParseLine(string line)
|
||||
{
|
||||
var match = REG_FORMAT().Match(line);
|
||||
if (!match.Success)
|
||||
{
|
||||
match = REG_RENAME_FORMAT().Match(line);
|
||||
if (match.Success)
|
||||
{
|
||||
var renamed = new Models.Change() { Path = match.Groups[1].Value };
|
||||
renamed.Set(Models.ChangeState.Renamed);
|
||||
_changes.Add(renamed);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
var change = new Models.Change() { Path = match.Groups[2].Value };
|
||||
var status = match.Groups[1].Value;
|
||||
|
@ -48,10 +76,6 @@ namespace SourceGit.Commands
|
|||
change.Set(Models.ChangeState.Deleted);
|
||||
_changes.Add(change);
|
||||
break;
|
||||
case 'R':
|
||||
change.Set(Models.ChangeState.Renamed);
|
||||
_changes.Add(change);
|
||||
break;
|
||||
case 'C':
|
||||
change.Set(Models.ChangeState.Copied);
|
||||
_changes.Add(change);
|
||||
|
|
|
@ -29,7 +29,7 @@ namespace SourceGit.Commands
|
|||
var rs = new Dictionary<string, string>();
|
||||
if (output.IsSuccess)
|
||||
{
|
||||
var lines = output.StdOut.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
var lines = output.StdOut.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries);
|
||||
foreach (var line in lines)
|
||||
{
|
||||
var idx = line.IndexOf('=', StringComparison.Ordinal);
|
||||
|
|
|
@ -8,7 +8,7 @@ namespace SourceGit.Commands
|
|||
{
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
Args = "status -uno --ignore-submodules=dirty --porcelain";
|
||||
Args = "--no-optional-locks status -uno --ignore-submodules=all --porcelain";
|
||||
}
|
||||
|
||||
public int Result()
|
||||
|
@ -16,7 +16,7 @@ namespace SourceGit.Commands
|
|||
var rs = ReadToEnd();
|
||||
if (rs.IsSuccess)
|
||||
{
|
||||
var lines = rs.StdOut.Split('\n', StringSplitOptions.RemoveEmptyEntries);
|
||||
var lines = rs.StdOut.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries);
|
||||
return lines.Length;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
|
@ -28,34 +28,48 @@ namespace SourceGit.Commands
|
|||
Context = repo;
|
||||
|
||||
if (ignoreWhitespace)
|
||||
Args = $"-c core.autocrlf=false diff --no-ext-diff --patch --ignore-cr-at-eol --ignore-all-space --unified={unified} {opt}";
|
||||
Args = $"diff --no-ext-diff --patch --ignore-all-space --unified={unified} {opt}";
|
||||
else if (Models.DiffOption.IgnoreCRAtEOL)
|
||||
Args = $"diff --no-ext-diff --patch --ignore-cr-at-eol --unified={unified} {opt}";
|
||||
else
|
||||
Args = $"-c core.autocrlf=false diff --no-ext-diff --patch --ignore-cr-at-eol --unified={unified} {opt}";
|
||||
Args = $"diff --no-ext-diff --patch --unified={unified} {opt}";
|
||||
}
|
||||
|
||||
public Models.DiffResult Result()
|
||||
{
|
||||
Exec();
|
||||
var rs = ReadToEnd();
|
||||
var start = 0;
|
||||
var end = rs.StdOut.IndexOf('\n', start);
|
||||
while (end > 0)
|
||||
{
|
||||
var line = rs.StdOut.Substring(start, end - start);
|
||||
ParseLine(line);
|
||||
|
||||
if (_result.IsBinary || _result.IsLFS)
|
||||
start = end + 1;
|
||||
end = rs.StdOut.IndexOf('\n', start);
|
||||
}
|
||||
|
||||
if (start < rs.StdOut.Length)
|
||||
ParseLine(rs.StdOut.Substring(start));
|
||||
|
||||
if (_result.IsBinary || _result.IsLFS || _result.TextDiff.Lines.Count == 0)
|
||||
{
|
||||
_result.TextDiff = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
ProcessInlineHighlights();
|
||||
|
||||
if (_result.TextDiff.Lines.Count == 0)
|
||||
_result.TextDiff = null;
|
||||
else
|
||||
_result.TextDiff.MaxLineNumber = Math.Max(_newLine, _oldLine);
|
||||
}
|
||||
|
||||
return _result;
|
||||
}
|
||||
|
||||
protected override void OnReadline(string line)
|
||||
private void ParseLine(string line)
|
||||
{
|
||||
if (_result.IsBinary)
|
||||
return;
|
||||
|
||||
if (line.StartsWith("old mode ", StringComparison.Ordinal))
|
||||
{
|
||||
_result.OldMode = line.Substring(9);
|
||||
|
@ -68,8 +82,17 @@ namespace SourceGit.Commands
|
|||
return;
|
||||
}
|
||||
|
||||
if (_result.IsBinary)
|
||||
if (line.StartsWith("deleted file mode ", StringComparison.Ordinal))
|
||||
{
|
||||
_result.OldMode = line.Substring(18);
|
||||
return;
|
||||
}
|
||||
|
||||
if (line.StartsWith("new file mode ", StringComparison.Ordinal))
|
||||
{
|
||||
_result.NewMode = line.Substring(14);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_result.IsLFS)
|
||||
{
|
||||
|
@ -82,7 +105,7 @@ namespace SourceGit.Commands
|
|||
}
|
||||
else if (line.StartsWith("-size ", StringComparison.Ordinal))
|
||||
{
|
||||
_result.LFSDiff.Old.Size = long.Parse(line.Substring(6));
|
||||
_result.LFSDiff.Old.Size = long.Parse(line.AsSpan(6));
|
||||
}
|
||||
}
|
||||
else if (ch == '+')
|
||||
|
@ -93,12 +116,12 @@ namespace SourceGit.Commands
|
|||
}
|
||||
else if (line.StartsWith("+size ", StringComparison.Ordinal))
|
||||
{
|
||||
_result.LFSDiff.New.Size = long.Parse(line.Substring(6));
|
||||
_result.LFSDiff.New.Size = long.Parse(line.AsSpan(6));
|
||||
}
|
||||
}
|
||||
else if (line.StartsWith(" size ", StringComparison.Ordinal))
|
||||
{
|
||||
_result.LFSDiff.New.Size = _result.LFSDiff.Old.Size = long.Parse(line.Substring(6));
|
||||
_result.LFSDiff.New.Size = _result.LFSDiff.Old.Size = long.Parse(line.AsSpan(6));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
@ -128,7 +151,8 @@ namespace SourceGit.Commands
|
|||
|
||||
_oldLine = int.Parse(match.Groups[1].Value);
|
||||
_newLine = int.Parse(match.Groups[2].Value);
|
||||
_result.TextDiff.Lines.Add(new Models.TextDiffLine(Models.TextDiffLineType.Indicator, line, 0, 0));
|
||||
_last = new Models.TextDiffLine(Models.TextDiffLineType.Indicator, line, 0, 0);
|
||||
_result.TextDiff.Lines.Add(_last);
|
||||
}
|
||||
}
|
||||
else
|
||||
|
@ -136,7 +160,8 @@ namespace SourceGit.Commands
|
|||
if (line.Length == 0)
|
||||
{
|
||||
ProcessInlineHighlights();
|
||||
_result.TextDiff.Lines.Add(new Models.TextDiffLine(Models.TextDiffLineType.Normal, "", _oldLine, _newLine));
|
||||
_last = new Models.TextDiffLine(Models.TextDiffLineType.Normal, "", _oldLine, _newLine);
|
||||
_result.TextDiff.Lines.Add(_last);
|
||||
_oldLine++;
|
||||
_newLine++;
|
||||
return;
|
||||
|
@ -152,7 +177,8 @@ namespace SourceGit.Commands
|
|||
return;
|
||||
}
|
||||
|
||||
_deleted.Add(new Models.TextDiffLine(Models.TextDiffLineType.Deleted, line.Substring(1), _oldLine, 0));
|
||||
_last = new Models.TextDiffLine(Models.TextDiffLineType.Deleted, line.Substring(1), _oldLine, 0);
|
||||
_deleted.Add(_last);
|
||||
_oldLine++;
|
||||
}
|
||||
else if (ch == '+')
|
||||
|
@ -164,7 +190,8 @@ namespace SourceGit.Commands
|
|||
return;
|
||||
}
|
||||
|
||||
_added.Add(new Models.TextDiffLine(Models.TextDiffLineType.Added, line.Substring(1), 0, _newLine));
|
||||
_last = new Models.TextDiffLine(Models.TextDiffLineType.Added, line.Substring(1), 0, _newLine);
|
||||
_added.Add(_last);
|
||||
_newLine++;
|
||||
}
|
||||
else if (ch != '\\')
|
||||
|
@ -175,7 +202,8 @@ namespace SourceGit.Commands
|
|||
{
|
||||
_oldLine = int.Parse(match.Groups[1].Value);
|
||||
_newLine = int.Parse(match.Groups[2].Value);
|
||||
_result.TextDiff.Lines.Add(new Models.TextDiffLine(Models.TextDiffLineType.Indicator, line, 0, 0));
|
||||
_last = new Models.TextDiffLine(Models.TextDiffLineType.Indicator, line, 0, 0);
|
||||
_result.TextDiff.Lines.Add(_last);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -186,11 +214,16 @@ namespace SourceGit.Commands
|
|||
return;
|
||||
}
|
||||
|
||||
_result.TextDiff.Lines.Add(new Models.TextDiffLine(Models.TextDiffLineType.Normal, line.Substring(1), _oldLine, _newLine));
|
||||
_last = new Models.TextDiffLine(Models.TextDiffLineType.Normal, line.Substring(1), _oldLine, _newLine);
|
||||
_result.TextDiff.Lines.Add(_last);
|
||||
_oldLine++;
|
||||
_newLine++;
|
||||
}
|
||||
}
|
||||
else if (line.Equals("\\ No newline at end of file", StringComparison.Ordinal))
|
||||
{
|
||||
_last.NoNewLineEndOfFile = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -241,6 +274,7 @@ namespace SourceGit.Commands
|
|||
private readonly Models.DiffResult _result = new Models.DiffResult();
|
||||
private readonly List<Models.TextDiffLine> _deleted = new List<Models.TextDiffLine>();
|
||||
private readonly List<Models.TextDiffLine> _added = new List<Models.TextDiffLine>();
|
||||
private Models.TextDiffLine _last = null;
|
||||
private int _oldLine = 0;
|
||||
private int _newLine = 0;
|
||||
}
|
||||
|
|
|
@ -1,39 +1,95 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
using Avalonia.Threading;
|
||||
|
||||
namespace SourceGit.Commands
|
||||
{
|
||||
public static class Discard
|
||||
{
|
||||
public static void All(string repo, bool includeIgnored)
|
||||
/// <summary>
|
||||
/// Discard all local changes (unstaged & staged)
|
||||
/// </summary>
|
||||
/// <param name="repo"></param>
|
||||
/// <param name="includeIgnored"></param>
|
||||
/// <param name="log"></param>
|
||||
public static void All(string repo, bool includeIgnored, Models.ICommandLog log)
|
||||
{
|
||||
new Restore(repo).Exec();
|
||||
new Clean(repo, includeIgnored).Exec();
|
||||
var changes = new QueryLocalChanges(repo).Result();
|
||||
try
|
||||
{
|
||||
foreach (var c in changes)
|
||||
{
|
||||
if (c.WorkTree == Models.ChangeState.Untracked ||
|
||||
c.WorkTree == Models.ChangeState.Added ||
|
||||
c.Index == Models.ChangeState.Added ||
|
||||
c.Index == Models.ChangeState.Renamed)
|
||||
{
|
||||
var fullPath = Path.Combine(repo, c.Path);
|
||||
if (Directory.Exists(fullPath))
|
||||
Directory.Delete(fullPath, true);
|
||||
else
|
||||
File.Delete(fullPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Dispatcher.UIThread.Invoke(() =>
|
||||
{
|
||||
App.RaiseException(repo, $"Failed to discard changes. Reason: {e.Message}");
|
||||
});
|
||||
}
|
||||
|
||||
public static void Changes(string repo, List<Models.Change> changes)
|
||||
{
|
||||
var needClean = new List<string>();
|
||||
var needCheckout = new List<string>();
|
||||
new Reset(repo, "HEAD", "--hard") { Log = log }.Exec();
|
||||
|
||||
if (includeIgnored)
|
||||
new Clean(repo) { Log = log }.Exec();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Discard selected changes (only unstaged).
|
||||
/// </summary>
|
||||
/// <param name="repo"></param>
|
||||
/// <param name="changes"></param>
|
||||
/// <param name="log"></param>
|
||||
public static void Changes(string repo, List<Models.Change> changes, Models.ICommandLog log)
|
||||
{
|
||||
var restores = new List<string>();
|
||||
|
||||
try
|
||||
{
|
||||
foreach (var c in changes)
|
||||
{
|
||||
if (c.WorkTree == Models.ChangeState.Untracked || c.WorkTree == Models.ChangeState.Added)
|
||||
needClean.Add(c.Path);
|
||||
{
|
||||
var fullPath = Path.Combine(repo, c.Path);
|
||||
if (Directory.Exists(fullPath))
|
||||
Directory.Delete(fullPath, true);
|
||||
else
|
||||
needCheckout.Add(c.Path);
|
||||
File.Delete(fullPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
restores.Add(c.Path);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Dispatcher.UIThread.Invoke(() =>
|
||||
{
|
||||
App.RaiseException(repo, $"Failed to discard changes. Reason: {e.Message}");
|
||||
});
|
||||
}
|
||||
|
||||
for (int i = 0; i < needClean.Count; i += 10)
|
||||
if (restores.Count > 0)
|
||||
{
|
||||
var count = Math.Min(10, needClean.Count - i);
|
||||
new Clean(repo, needClean.GetRange(i, count)).Exec();
|
||||
}
|
||||
|
||||
for (int i = 0; i < needCheckout.Count; i += 10)
|
||||
{
|
||||
var count = Math.Min(10, needCheckout.Count - i);
|
||||
new Restore(repo, needCheckout.GetRange(i, count), "--worktree --recurse-submodules").Exec();
|
||||
var pathSpecFile = Path.GetTempFileName();
|
||||
File.WriteAllLines(pathSpecFile, restores);
|
||||
new Restore(repo, pathSpecFile, false) { Log = log }.Exec();
|
||||
File.Delete(pathSpecFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,7 +8,26 @@ namespace SourceGit.Commands
|
|||
{
|
||||
public static class ExecuteCustomAction
|
||||
{
|
||||
public static void Run(string repo, string file, string args, Action<string> outputHandler)
|
||||
public static void Run(string repo, string file, string args)
|
||||
{
|
||||
var start = new ProcessStartInfo();
|
||||
start.FileName = file;
|
||||
start.Arguments = args;
|
||||
start.UseShellExecute = false;
|
||||
start.CreateNoWindow = true;
|
||||
start.WorkingDirectory = repo;
|
||||
|
||||
try
|
||||
{
|
||||
Process.Start(start);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Dispatcher.UIThread.Invoke(() => App.RaiseException(repo, e.Message));
|
||||
}
|
||||
}
|
||||
|
||||
public static void RunAndWait(string repo, string file, string args, Models.ICommandLog log)
|
||||
{
|
||||
var start = new ProcessStartInfo();
|
||||
start.FileName = file;
|
||||
|
@ -21,13 +40,7 @@ namespace SourceGit.Commands
|
|||
start.StandardErrorEncoding = Encoding.UTF8;
|
||||
start.WorkingDirectory = repo;
|
||||
|
||||
// Force using en_US.UTF-8 locale to avoid GCM crash
|
||||
if (OperatingSystem.IsLinux())
|
||||
start.Environment.Add("LANG", "en_US.UTF-8");
|
||||
|
||||
// Fix macOS `PATH` env
|
||||
if (OperatingSystem.IsMacOS() && !string.IsNullOrEmpty(Native.OS.CustomPathEnv))
|
||||
start.Environment.Add("PATH", Native.OS.CustomPathEnv);
|
||||
log?.AppendLine($"$ {file} {args}\n");
|
||||
|
||||
var proc = new Process() { StartInfo = start };
|
||||
var builder = new StringBuilder();
|
||||
|
@ -35,14 +48,14 @@ namespace SourceGit.Commands
|
|||
proc.OutputDataReceived += (_, e) =>
|
||||
{
|
||||
if (e.Data != null)
|
||||
outputHandler?.Invoke(e.Data);
|
||||
log?.AppendLine(e.Data);
|
||||
};
|
||||
|
||||
proc.ErrorDataReceived += (_, e) =>
|
||||
{
|
||||
if (e.Data != null)
|
||||
{
|
||||
outputHandler?.Invoke(e.Data);
|
||||
log?.AppendLine(e.Data);
|
||||
builder.AppendLine(e.Data);
|
||||
}
|
||||
};
|
||||
|
@ -53,26 +66,21 @@ namespace SourceGit.Commands
|
|||
proc.BeginOutputReadLine();
|
||||
proc.BeginErrorReadLine();
|
||||
proc.WaitForExit();
|
||||
|
||||
var exitCode = proc.ExitCode;
|
||||
if (exitCode != 0)
|
||||
{
|
||||
var errMsg = builder.ToString().Trim();
|
||||
if (!string.IsNullOrEmpty(errMsg))
|
||||
Dispatcher.UIThread.Invoke(() => App.RaiseException(repo, errMsg));
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Dispatcher.UIThread.Invoke(() =>
|
||||
{
|
||||
App.RaiseException(repo, e.Message);
|
||||
});
|
||||
Dispatcher.UIThread.Invoke(() => App.RaiseException(repo, e.Message));
|
||||
}
|
||||
|
||||
var exitCode = proc.ExitCode;
|
||||
proc.Close();
|
||||
|
||||
if (exitCode != 0)
|
||||
{
|
||||
var errMsg = builder.ToString();
|
||||
Dispatcher.UIThread.Invoke(() =>
|
||||
{
|
||||
App.RaiseException(repo, errMsg);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,15 +1,11 @@
|
|||
using System;
|
||||
|
||||
namespace SourceGit.Commands
|
||||
namespace SourceGit.Commands
|
||||
{
|
||||
public class Fetch : Command
|
||||
{
|
||||
public Fetch(string repo, string remote, bool noTags, bool prune, bool force, Action<string> outputHandler)
|
||||
public Fetch(string repo, string remote, bool noTags, bool force)
|
||||
{
|
||||
_outputHandler = outputHandler;
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
TraitErrorAsOutput = true;
|
||||
SSHKey = new Config(repo).Get($"remote.{remote}.sshkey");
|
||||
Args = "fetch --progress --verbose ";
|
||||
|
||||
|
@ -21,27 +17,15 @@ namespace SourceGit.Commands
|
|||
if (force)
|
||||
Args += "--force ";
|
||||
|
||||
if (prune)
|
||||
Args += "--prune ";
|
||||
|
||||
Args += remote;
|
||||
}
|
||||
|
||||
public Fetch(string repo, Models.Branch local, Models.Branch remote, Action<string> outputHandler)
|
||||
public Fetch(string repo, Models.Branch local, Models.Branch remote)
|
||||
{
|
||||
_outputHandler = outputHandler;
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
TraitErrorAsOutput = true;
|
||||
SSHKey = new Config(repo).Get($"remote.{remote.Remote}.sshkey");
|
||||
Args = $"fetch --progress --verbose {remote.Remote} {remote.Name}:{local.Name}";
|
||||
}
|
||||
|
||||
protected override void OnReadline(string line)
|
||||
{
|
||||
_outputHandler?.Invoke(line);
|
||||
}
|
||||
|
||||
private readonly Action<string> _outputHandler;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
{
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
Editor = EditorType.None;
|
||||
Args = $"format-patch {commit} -1 --output=\"{saveTo}\"";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,23 +1,12 @@
|
|||
using System;
|
||||
|
||||
namespace SourceGit.Commands
|
||||
namespace SourceGit.Commands
|
||||
{
|
||||
public class GC : Command
|
||||
{
|
||||
public GC(string repo, Action<string> outputHandler)
|
||||
public GC(string repo)
|
||||
{
|
||||
_outputHandler = outputHandler;
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
TraitErrorAsOutput = true;
|
||||
Args = "gc --prune=now";
|
||||
}
|
||||
|
||||
protected override void OnReadline(string line)
|
||||
{
|
||||
_outputHandler?.Invoke(line);
|
||||
}
|
||||
|
||||
private readonly Action<string> _outputHandler;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,8 @@ using System.Collections.Generic;
|
|||
using System.Text;
|
||||
using System.Threading;
|
||||
|
||||
using Avalonia.Threading;
|
||||
|
||||
namespace SourceGit.Commands
|
||||
{
|
||||
/// <summary>
|
||||
|
@ -20,82 +22,78 @@ namespace SourceGit.Commands
|
|||
}
|
||||
}
|
||||
|
||||
public GenerateCommitMessage(Models.OpenAIService service, string repo, List<Models.Change> changes, CancellationToken cancelToken, Action<string> onProgress)
|
||||
public GenerateCommitMessage(Models.OpenAIService service, string repo, List<Models.Change> changes, CancellationToken cancelToken, Action<string> onResponse)
|
||||
{
|
||||
_service = service;
|
||||
_repo = repo;
|
||||
_changes = changes;
|
||||
_cancelToken = cancelToken;
|
||||
_onProgress = onProgress;
|
||||
_onResponse = onResponse;
|
||||
}
|
||||
|
||||
public string Result()
|
||||
public void Exec()
|
||||
{
|
||||
try
|
||||
{
|
||||
var summarybuilder = new StringBuilder();
|
||||
var bodyBuilder = new StringBuilder();
|
||||
_onResponse?.Invoke("Waiting for pre-file analyzing to completed...\n\n");
|
||||
|
||||
var responseBuilder = new StringBuilder();
|
||||
var summaryBuilder = new StringBuilder();
|
||||
foreach (var change in _changes)
|
||||
{
|
||||
if (_cancelToken.IsCancellationRequested)
|
||||
return "";
|
||||
return;
|
||||
|
||||
_onProgress?.Invoke($"Analyzing {change.Path}...");
|
||||
responseBuilder.Append("- ");
|
||||
summaryBuilder.Append("- ");
|
||||
|
||||
var summary = GenerateChangeSummary(change);
|
||||
summarybuilder.Append("- ");
|
||||
summarybuilder.Append(summary);
|
||||
summarybuilder.Append("(file: ");
|
||||
summarybuilder.Append(change.Path);
|
||||
summarybuilder.Append(")");
|
||||
summarybuilder.AppendLine();
|
||||
var rs = new GetDiffContent(_repo, new Models.DiffOption(change, false)).ReadToEnd();
|
||||
if (rs.IsSuccess)
|
||||
{
|
||||
_service.Chat(
|
||||
_service.AnalyzeDiffPrompt,
|
||||
$"Here is the `git diff` output: {rs.StdOut}",
|
||||
_cancelToken,
|
||||
update =>
|
||||
{
|
||||
responseBuilder.Append(update);
|
||||
summaryBuilder.Append(update);
|
||||
|
||||
bodyBuilder.Append("- ");
|
||||
bodyBuilder.Append(summary);
|
||||
bodyBuilder.AppendLine();
|
||||
_onResponse?.Invoke($"Waiting for pre-file analyzing to completed...\n\n{responseBuilder}");
|
||||
});
|
||||
}
|
||||
|
||||
responseBuilder.Append("\n");
|
||||
summaryBuilder.Append("(file: ");
|
||||
summaryBuilder.Append(change.Path);
|
||||
summaryBuilder.Append(")\n");
|
||||
}
|
||||
|
||||
if (_cancelToken.IsCancellationRequested)
|
||||
return "";
|
||||
return;
|
||||
|
||||
_onProgress?.Invoke($"Generating commit message...");
|
||||
|
||||
var body = bodyBuilder.ToString();
|
||||
var subject = GenerateSubject(summarybuilder.ToString());
|
||||
return string.Format("{0}\n\n{1}", subject, body);
|
||||
var responseBody = responseBuilder.ToString();
|
||||
var subjectBuilder = new StringBuilder();
|
||||
_service.Chat(
|
||||
_service.GenerateSubjectPrompt,
|
||||
$"Here are the summaries changes:\n{summaryBuilder}",
|
||||
_cancelToken,
|
||||
update =>
|
||||
{
|
||||
subjectBuilder.Append(update);
|
||||
_onResponse?.Invoke($"{subjectBuilder}\n\n{responseBody}");
|
||||
});
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
App.RaiseException(_repo, $"Failed to generate commit message: {e}");
|
||||
return "";
|
||||
Dispatcher.UIThread.Post(() => App.RaiseException(_repo, $"Failed to generate commit message: {e}"));
|
||||
}
|
||||
}
|
||||
|
||||
private string GenerateChangeSummary(Models.Change change)
|
||||
{
|
||||
var rs = new GetDiffContent(_repo, new Models.DiffOption(change, false)).ReadToEnd();
|
||||
var diff = rs.IsSuccess ? rs.StdOut : "unknown change";
|
||||
|
||||
var rsp = _service.Chat(_service.AnalyzeDiffPrompt, $"Here is the `git diff` output: {diff}", _cancelToken);
|
||||
if (rsp != null && rsp.Choices.Count > 0)
|
||||
return rsp.Choices[0].Message.Content;
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
private string GenerateSubject(string summary)
|
||||
{
|
||||
var rsp = _service.Chat(_service.GenerateSubjectPrompt, $"Here are the summaries changes:\n{summary}", _cancelToken);
|
||||
if (rsp != null && rsp.Choices.Count > 0)
|
||||
return rsp.Choices[0].Message.Content;
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
private Models.OpenAIService _service;
|
||||
private string _repo;
|
||||
private List<Models.Change> _changes;
|
||||
private CancellationToken _cancelToken;
|
||||
private Action<string> _onProgress;
|
||||
private Action<string> _onResponse;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,52 +1,12 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using System.Text;
|
||||
using Avalonia.Threading;
|
||||
|
||||
namespace SourceGit.Commands
|
||||
{
|
||||
public static class GitFlow
|
||||
{
|
||||
public class BranchDetectResult
|
||||
public static bool Init(string repo, string master, string develop, string feature, string release, string hotfix, string version, Models.ICommandLog log)
|
||||
{
|
||||
public bool IsGitFlowBranch { get; set; } = false;
|
||||
public string Type { get; set; } = string.Empty;
|
||||
public string Prefix { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
public static bool IsEnabled(string repo, List<Models.Branch> branches)
|
||||
{
|
||||
var localBrancheNames = new HashSet<string>();
|
||||
foreach (var branch in branches)
|
||||
{
|
||||
if (branch.IsLocal)
|
||||
localBrancheNames.Add(branch.Name);
|
||||
}
|
||||
|
||||
var config = new Config(repo).ListAll();
|
||||
if (!config.TryGetValue("gitflow.branch.master", out string master) || !localBrancheNames.Contains(master))
|
||||
return false;
|
||||
|
||||
if (!config.TryGetValue("gitflow.branch.develop", out string develop) || !localBrancheNames.Contains(develop))
|
||||
return false;
|
||||
|
||||
return config.ContainsKey("gitflow.prefix.feature") &&
|
||||
config.ContainsKey("gitflow.prefix.release") &&
|
||||
config.ContainsKey("gitflow.prefix.hotfix");
|
||||
}
|
||||
|
||||
public static bool Init(string repo, List<Models.Branch> branches, string master, string develop, string feature, string release, string hotfix, string version)
|
||||
{
|
||||
var current = branches.Find(x => x.IsCurrent);
|
||||
|
||||
var masterBranch = branches.Find(x => x.Name == master);
|
||||
if (masterBranch == null && current != null)
|
||||
Branch.Create(repo, master, current.Head);
|
||||
|
||||
var devBranch = branches.Find(x => x.Name == develop);
|
||||
if (devBranch == null && current != null)
|
||||
Branch.Create(repo, develop, current.Head);
|
||||
|
||||
var config = new Config(repo);
|
||||
config.Set("gitflow.branch.master", master);
|
||||
config.Set("gitflow.branch.develop", develop);
|
||||
|
@ -61,104 +21,72 @@ namespace SourceGit.Commands
|
|||
init.WorkingDirectory = repo;
|
||||
init.Context = repo;
|
||||
init.Args = "flow init -d";
|
||||
init.Log = log;
|
||||
return init.Exec();
|
||||
}
|
||||
|
||||
public static string GetPrefix(string repo, string type)
|
||||
public static bool Start(string repo, Models.GitFlowBranchType type, string name, Models.ICommandLog log)
|
||||
{
|
||||
return new Config(repo).Get($"gitflow.prefix.{type}");
|
||||
}
|
||||
|
||||
public static BranchDetectResult DetectType(string repo, List<Models.Branch> branches, string branch)
|
||||
{
|
||||
var rs = new BranchDetectResult();
|
||||
var localBrancheNames = new HashSet<string>();
|
||||
foreach (var b in branches)
|
||||
{
|
||||
if (b.IsLocal)
|
||||
localBrancheNames.Add(b.Name);
|
||||
}
|
||||
|
||||
var config = new Config(repo).ListAll();
|
||||
if (!config.TryGetValue("gitflow.branch.master", out string master) || !localBrancheNames.Contains(master))
|
||||
return rs;
|
||||
|
||||
if (!config.TryGetValue("gitflow.branch.develop", out string develop) || !localBrancheNames.Contains(develop))
|
||||
return rs;
|
||||
|
||||
if (!config.TryGetValue("gitflow.prefix.feature", out var feature) ||
|
||||
!config.TryGetValue("gitflow.prefix.release", out var release) ||
|
||||
!config.TryGetValue("gitflow.prefix.hotfix", out var hotfix))
|
||||
return rs;
|
||||
|
||||
if (branch.StartsWith(feature, StringComparison.Ordinal))
|
||||
{
|
||||
rs.IsGitFlowBranch = true;
|
||||
rs.Type = "feature";
|
||||
rs.Prefix = feature;
|
||||
}
|
||||
else if (branch.StartsWith(release, StringComparison.Ordinal))
|
||||
{
|
||||
rs.IsGitFlowBranch = true;
|
||||
rs.Type = "release";
|
||||
rs.Prefix = release;
|
||||
}
|
||||
else if (branch.StartsWith(hotfix, StringComparison.Ordinal))
|
||||
{
|
||||
rs.IsGitFlowBranch = true;
|
||||
rs.Type = "hotfix";
|
||||
rs.Prefix = hotfix;
|
||||
}
|
||||
|
||||
return rs;
|
||||
}
|
||||
|
||||
public static bool Start(string repo, string type, string name)
|
||||
{
|
||||
if (!SUPPORTED_BRANCH_TYPES.Contains(type))
|
||||
{
|
||||
Dispatcher.UIThread.Post(() =>
|
||||
{
|
||||
App.RaiseException(repo, "Bad branch type!!!");
|
||||
});
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
var start = new Command();
|
||||
start.WorkingDirectory = repo;
|
||||
start.Context = repo;
|
||||
start.Args = $"flow {type} start {name}";
|
||||
return start.Exec();
|
||||
}
|
||||
|
||||
public static bool Finish(string repo, string type, string name, bool keepBranch)
|
||||
switch (type)
|
||||
{
|
||||
if (!SUPPORTED_BRANCH_TYPES.Contains(type))
|
||||
{
|
||||
Dispatcher.UIThread.Post(() =>
|
||||
{
|
||||
App.RaiseException(repo, "Bad branch type!!!");
|
||||
});
|
||||
|
||||
case Models.GitFlowBranchType.Feature:
|
||||
start.Args = $"flow feature start {name}";
|
||||
break;
|
||||
case Models.GitFlowBranchType.Release:
|
||||
start.Args = $"flow release start {name}";
|
||||
break;
|
||||
case Models.GitFlowBranchType.Hotfix:
|
||||
start.Args = $"flow hotfix start {name}";
|
||||
break;
|
||||
default:
|
||||
Dispatcher.UIThread.Invoke(() => App.RaiseException(repo, "Bad git-flow branch type!!!"));
|
||||
return false;
|
||||
}
|
||||
|
||||
var option = keepBranch ? "-k" : string.Empty;
|
||||
start.Log = log;
|
||||
return start.Exec();
|
||||
}
|
||||
|
||||
public static bool Finish(string repo, Models.GitFlowBranchType type, string name, bool squash, bool push, bool keepBranch, Models.ICommandLog log)
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
builder.Append("flow ");
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case Models.GitFlowBranchType.Feature:
|
||||
builder.Append("feature");
|
||||
break;
|
||||
case Models.GitFlowBranchType.Release:
|
||||
builder.Append("release");
|
||||
break;
|
||||
case Models.GitFlowBranchType.Hotfix:
|
||||
builder.Append("hotfix");
|
||||
break;
|
||||
default:
|
||||
Dispatcher.UIThread.Invoke(() => App.RaiseException(repo, "Bad git-flow branch type!!!"));
|
||||
return false;
|
||||
}
|
||||
|
||||
builder.Append(" finish ");
|
||||
if (squash)
|
||||
builder.Append("--squash ");
|
||||
if (push)
|
||||
builder.Append("--push ");
|
||||
if (keepBranch)
|
||||
builder.Append("-k ");
|
||||
builder.Append(name);
|
||||
|
||||
var finish = new Command();
|
||||
finish.WorkingDirectory = repo;
|
||||
finish.Context = repo;
|
||||
finish.Args = $"flow {type} finish {option} {name}";
|
||||
finish.Args = builder.ToString();
|
||||
finish.Log = log;
|
||||
return finish.Exec();
|
||||
}
|
||||
|
||||
private static readonly List<string> SUPPORTED_BRANCH_TYPES = new List<string>()
|
||||
{
|
||||
"feature",
|
||||
"release",
|
||||
"bugfix",
|
||||
"hotfix",
|
||||
"support",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,7 +8,14 @@ namespace SourceGit.Commands
|
|||
{
|
||||
var file = Path.Combine(repo, ".gitignore");
|
||||
if (!File.Exists(file))
|
||||
{
|
||||
File.WriteAllLines(file, [pattern]);
|
||||
return;
|
||||
}
|
||||
|
||||
var org = File.ReadAllText(file);
|
||||
if (!org.EndsWith('\n'))
|
||||
File.AppendAllLines(file, ["", pattern]);
|
||||
else
|
||||
File.AppendAllLines(file, [pattern]);
|
||||
}
|
||||
|
|
24
src/Commands/IsBareRepository.cs
Normal file
24
src/Commands/IsBareRepository.cs
Normal file
|
@ -0,0 +1,24 @@
|
|||
using System.IO;
|
||||
|
||||
namespace SourceGit.Commands
|
||||
{
|
||||
public class IsBareRepository : Command
|
||||
{
|
||||
public IsBareRepository(string path)
|
||||
{
|
||||
WorkingDirectory = path;
|
||||
Args = "rev-parse --is-bare-repository";
|
||||
}
|
||||
|
||||
public bool Result()
|
||||
{
|
||||
if (!Directory.Exists(Path.Combine(WorkingDirectory, "refs")) ||
|
||||
!Directory.Exists(Path.Combine(WorkingDirectory, "objects")) ||
|
||||
!File.Exists(Path.Combine(WorkingDirectory, "HEAD")))
|
||||
return false;
|
||||
|
||||
var rs = ReadToEnd();
|
||||
return rs.IsSuccess && rs.StdOut.Trim() == "true";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -11,7 +11,7 @@ namespace SourceGit.Commands
|
|||
{
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
Args = $"diff 4b825dc642cb6eb9a060e54bf8d69288fbee4904 {commit} --numstat -- \"{path}\"";
|
||||
Args = $"diff {Models.Commit.EmptyTreeSHA1} {commit} --numstat -- \"{path}\"";
|
||||
RaiseError = false;
|
||||
}
|
||||
|
||||
|
|
17
src/Commands/IsCommitSHA.cs
Normal file
17
src/Commands/IsCommitSHA.cs
Normal file
|
@ -0,0 +1,17 @@
|
|||
namespace SourceGit.Commands
|
||||
{
|
||||
public class IsCommitSHA : Command
|
||||
{
|
||||
public IsCommitSHA(string repo, string hash)
|
||||
{
|
||||
WorkingDirectory = repo;
|
||||
Args = $"cat-file -t {hash}";
|
||||
}
|
||||
|
||||
public bool Result()
|
||||
{
|
||||
var rs = ReadToEnd();
|
||||
return rs.IsSuccess && rs.StdOut.Trim().Equals("commit");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -10,5 +10,10 @@
|
|||
Context = repo;
|
||||
Args = $"diff -a --ignore-cr-at-eol --check {opt}";
|
||||
}
|
||||
|
||||
public bool Result()
|
||||
{
|
||||
return ReadToEnd().IsSuccess;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,26 +7,18 @@ namespace SourceGit.Commands
|
|||
{
|
||||
public partial class LFS
|
||||
{
|
||||
[GeneratedRegex(@"^(.+)\s+(\w+)\s+\w+:(\d+)$")]
|
||||
[GeneratedRegex(@"^(.+)\s+([\w.]+)\s+\w+:(\d+)$")]
|
||||
private static partial Regex REG_LOCK();
|
||||
|
||||
class SubCmd : Command
|
||||
{
|
||||
public SubCmd(string repo, string args, Action<string> onProgress)
|
||||
public SubCmd(string repo, string args, Models.ICommandLog log)
|
||||
{
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
Args = args;
|
||||
TraitErrorAsOutput = true;
|
||||
_outputHandler = onProgress;
|
||||
Log = log;
|
||||
}
|
||||
|
||||
protected override void OnReadline(string line)
|
||||
{
|
||||
_outputHandler?.Invoke(line);
|
||||
}
|
||||
|
||||
private readonly Action<string> _outputHandler;
|
||||
}
|
||||
|
||||
public LFS(string repo)
|
||||
|
@ -44,35 +36,35 @@ namespace SourceGit.Commands
|
|||
return content.Contains("git lfs pre-push");
|
||||
}
|
||||
|
||||
public bool Install()
|
||||
public bool Install(Models.ICommandLog log)
|
||||
{
|
||||
return new SubCmd(_repo, "lfs install --local", null).Exec();
|
||||
return new SubCmd(_repo, "lfs install --local", log).Exec();
|
||||
}
|
||||
|
||||
public bool Track(string pattern, bool isFilenameMode = false)
|
||||
public bool Track(string pattern, bool isFilenameMode, Models.ICommandLog log)
|
||||
{
|
||||
var opt = isFilenameMode ? "--filename" : "";
|
||||
return new SubCmd(_repo, $"lfs track {opt} \"{pattern}\"", null).Exec();
|
||||
return new SubCmd(_repo, $"lfs track {opt} \"{pattern}\"", log).Exec();
|
||||
}
|
||||
|
||||
public void Fetch(string remote, Action<string> outputHandler)
|
||||
public void Fetch(string remote, Models.ICommandLog log)
|
||||
{
|
||||
new SubCmd(_repo, $"lfs fetch {remote}", outputHandler).Exec();
|
||||
new SubCmd(_repo, $"lfs fetch {remote}", log).Exec();
|
||||
}
|
||||
|
||||
public void Pull(string remote, Action<string> outputHandler)
|
||||
public void Pull(string remote, Models.ICommandLog log)
|
||||
{
|
||||
new SubCmd(_repo, $"lfs pull {remote}", outputHandler).Exec();
|
||||
new SubCmd(_repo, $"lfs pull {remote}", log).Exec();
|
||||
}
|
||||
|
||||
public void Push(string remote, Action<string> outputHandler)
|
||||
public void Push(string remote, Models.ICommandLog log)
|
||||
{
|
||||
new SubCmd(_repo, $"lfs push {remote}", outputHandler).Exec();
|
||||
new SubCmd(_repo, $"lfs push {remote}", log).Exec();
|
||||
}
|
||||
|
||||
public void Prune(Action<string> outputHandler)
|
||||
public void Prune(Models.ICommandLog log)
|
||||
{
|
||||
new SubCmd(_repo, "lfs prune", outputHandler).Exec();
|
||||
new SubCmd(_repo, "lfs prune", log).Exec();
|
||||
}
|
||||
|
||||
public List<Models.LFSLock> Locks(string remote)
|
||||
|
@ -82,7 +74,7 @@ namespace SourceGit.Commands
|
|||
var rs = cmd.ReadToEnd();
|
||||
if (rs.IsSuccess)
|
||||
{
|
||||
var lines = rs.StdOut.Split(new[] { '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
var lines = rs.StdOut.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries);
|
||||
foreach (var line in lines)
|
||||
{
|
||||
var match = REG_LOCK().Match(line);
|
||||
|
@ -101,21 +93,21 @@ namespace SourceGit.Commands
|
|||
return locks;
|
||||
}
|
||||
|
||||
public bool Lock(string remote, string file)
|
||||
public bool Lock(string remote, string file, Models.ICommandLog log)
|
||||
{
|
||||
return new SubCmd(_repo, $"lfs lock --remote={remote} \"{file}\"", null).Exec();
|
||||
return new SubCmd(_repo, $"lfs lock --remote={remote} \"{file}\"", log).Exec();
|
||||
}
|
||||
|
||||
public bool Unlock(string remote, string file, bool force)
|
||||
public bool Unlock(string remote, string file, bool force, Models.ICommandLog log)
|
||||
{
|
||||
var opt = force ? "-f" : "";
|
||||
return new SubCmd(_repo, $"lfs unlock --remote={remote} {opt} \"{file}\"", null).Exec();
|
||||
return new SubCmd(_repo, $"lfs unlock --remote={remote} {opt} \"{file}\"", log).Exec();
|
||||
}
|
||||
|
||||
public bool Unlock(string remote, long id, bool force)
|
||||
public bool Unlock(string remote, long id, bool force, Models.ICommandLog log)
|
||||
{
|
||||
var opt = force ? "-f" : "";
|
||||
return new SubCmd(_repo, $"lfs unlock --remote={remote} {opt} --id={id}", null).Exec();
|
||||
return new SubCmd(_repo, $"lfs unlock --remote={remote} {opt} --id={id}", log).Exec();
|
||||
}
|
||||
|
||||
private readonly string _repo;
|
||||
|
|
|
@ -1,26 +1,21 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace SourceGit.Commands
|
||||
{
|
||||
public class Merge : Command
|
||||
{
|
||||
public Merge(string repo, string source, string mode, Action<string> outputHandler)
|
||||
public Merge(string repo, string source, string mode)
|
||||
{
|
||||
_outputHandler = outputHandler;
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
TraitErrorAsOutput = true;
|
||||
Args = $"merge --progress {source} {mode}";
|
||||
}
|
||||
|
||||
public Merge(string repo, List<string> targets, bool autoCommit, string strategy, Action<string> outputHandler)
|
||||
public Merge(string repo, List<string> targets, bool autoCommit, string strategy)
|
||||
{
|
||||
_outputHandler = outputHandler;
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
TraitErrorAsOutput = true;
|
||||
|
||||
var builder = new StringBuilder();
|
||||
builder.Append("merge --progress ");
|
||||
|
@ -37,12 +32,5 @@ namespace SourceGit.Commands
|
|||
|
||||
Args = builder.ToString();
|
||||
}
|
||||
|
||||
protected override void OnReadline(string line)
|
||||
{
|
||||
_outputHandler?.Invoke(line);
|
||||
}
|
||||
|
||||
private readonly Action<string> _outputHandler = null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,15 +13,18 @@ namespace SourceGit.Commands
|
|||
cmd.Context = repo;
|
||||
cmd.RaiseError = true;
|
||||
|
||||
// NOTE: If no <file> names are specified, 'git mergetool' will run the merge tool program on every file with merge conflicts.
|
||||
var fileArg = string.IsNullOrEmpty(file) ? "" : $"\"{file}\"";
|
||||
|
||||
if (toolType == 0)
|
||||
{
|
||||
cmd.Args = $"mergetool \"{file}\"";
|
||||
cmd.Args = $"mergetool {fileArg}";
|
||||
return cmd.Exec();
|
||||
}
|
||||
|
||||
if (!File.Exists(toolPath))
|
||||
{
|
||||
Dispatcher.UIThread.Post(() => App.RaiseException(repo, $"Can NOT found external merge tool in '{toolPath}'!"));
|
||||
Dispatcher.UIThread.Post(() => App.RaiseException(repo, $"Can NOT find external merge tool in '{toolPath}'!"));
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -32,7 +35,7 @@ namespace SourceGit.Commands
|
|||
return false;
|
||||
}
|
||||
|
||||
cmd.Args = $"-c mergetool.sourcegit.cmd=\"\\\"{toolPath}\\\" {supported.Cmd}\" -c mergetool.writeToTemp=true -c mergetool.keepBackup=false -c mergetool.trustExitCode=true mergetool --tool=sourcegit \"{file}\"";
|
||||
cmd.Args = $"-c mergetool.sourcegit.cmd=\"\\\"{toolPath}\\\" {supported.Cmd}\" -c mergetool.writeToTemp=true -c mergetool.keepBackup=false -c mergetool.trustExitCode=true mergetool --tool=sourcegit {fileArg}";
|
||||
return cmd.Exec();
|
||||
}
|
||||
|
||||
|
@ -51,7 +54,7 @@ namespace SourceGit.Commands
|
|||
|
||||
if (!File.Exists(toolPath))
|
||||
{
|
||||
Dispatcher.UIThread.Invoke(() => App.RaiseException(repo, $"Can NOT found external diff tool in '{toolPath}'!"));
|
||||
Dispatcher.UIThread.Invoke(() => App.RaiseException(repo, $"Can NOT find external diff tool in '{toolPath}'!"));
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,33 +1,18 @@
|
|||
using System;
|
||||
|
||||
namespace SourceGit.Commands
|
||||
namespace SourceGit.Commands
|
||||
{
|
||||
public class Pull : Command
|
||||
{
|
||||
public Pull(string repo, string remote, string branch, bool useRebase, bool noTags, bool prune, Action<string> outputHandler)
|
||||
public Pull(string repo, string remote, string branch, bool useRebase)
|
||||
{
|
||||
_outputHandler = outputHandler;
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
TraitErrorAsOutput = true;
|
||||
SSHKey = new Config(repo).Get($"remote.{remote}.sshkey");
|
||||
Args = "pull --verbose --progress --tags ";
|
||||
Args = "pull --verbose --progress ";
|
||||
|
||||
if (useRebase)
|
||||
Args += "--rebase ";
|
||||
if (noTags)
|
||||
Args += "--no-tags ";
|
||||
if (prune)
|
||||
Args += "--prune ";
|
||||
Args += "--rebase=true ";
|
||||
|
||||
Args += $"{remote} {branch}";
|
||||
}
|
||||
|
||||
protected override void OnReadline(string line)
|
||||
{
|
||||
_outputHandler?.Invoke(line);
|
||||
}
|
||||
|
||||
private readonly Action<string> _outputHandler;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,16 +1,11 @@
|
|||
using System;
|
||||
|
||||
namespace SourceGit.Commands
|
||||
namespace SourceGit.Commands
|
||||
{
|
||||
public class Push : Command
|
||||
{
|
||||
public Push(string repo, string local, string remote, string remoteBranch, bool withTags, bool checkSubmodules, bool track, bool force, Action<string> onProgress)
|
||||
public Push(string repo, string local, string remote, string remoteBranch, bool withTags, bool checkSubmodules, bool track, bool force)
|
||||
{
|
||||
_outputHandler = onProgress;
|
||||
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
TraitErrorAsOutput = true;
|
||||
SSHKey = new Config(repo).Get($"remote.{remote}.sshkey");
|
||||
Args = "push --progress --verbose ";
|
||||
|
||||
|
@ -26,7 +21,7 @@ namespace SourceGit.Commands
|
|||
Args += $"{remote} {local}:{remoteBranch}";
|
||||
}
|
||||
|
||||
public Push(string repo, string remote, string tag, bool isDelete)
|
||||
public Push(string repo, string remote, string refname, bool isDelete)
|
||||
{
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
|
@ -36,14 +31,7 @@ namespace SourceGit.Commands
|
|||
if (isDelete)
|
||||
Args += "--delete ";
|
||||
|
||||
Args += $"{remote} refs/tags/{tag}";
|
||||
Args += $"{remote} {refname}";
|
||||
}
|
||||
|
||||
protected override void OnReadline(string line)
|
||||
{
|
||||
_outputHandler?.Invoke(line);
|
||||
}
|
||||
|
||||
private readonly Action<string> _outputHandler = null;
|
||||
}
|
||||
}
|
||||
|
|
37
src/Commands/QueryAssumeUnchangedFiles.cs
Normal file
37
src/Commands/QueryAssumeUnchangedFiles.cs
Normal file
|
@ -0,0 +1,37 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace SourceGit.Commands
|
||||
{
|
||||
public partial class QueryAssumeUnchangedFiles : Command
|
||||
{
|
||||
[GeneratedRegex(@"^(\w)\s+(.+)$")]
|
||||
private static partial Regex REG_PARSE();
|
||||
|
||||
public QueryAssumeUnchangedFiles(string repo)
|
||||
{
|
||||
WorkingDirectory = repo;
|
||||
Args = "ls-files -v";
|
||||
RaiseError = false;
|
||||
}
|
||||
|
||||
public List<string> Result()
|
||||
{
|
||||
var outs = new List<string>();
|
||||
var rs = ReadToEnd();
|
||||
var lines = rs.StdOut.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries);
|
||||
foreach (var line in lines)
|
||||
{
|
||||
var match = REG_PARSE().Match(line);
|
||||
if (!match.Success)
|
||||
continue;
|
||||
|
||||
if (match.Groups[1].Value == "h")
|
||||
outs.Add(match.Groups[2].Value);
|
||||
}
|
||||
|
||||
return outs;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -14,22 +14,52 @@ namespace SourceGit.Commands
|
|||
{
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
Args = "branch -l --all -v --format=\"%(refname)%00%(objectname)%00%(HEAD)%00%(upstream)%00%(upstream:trackshort)\"";
|
||||
Args = "branch -l --all -v --format=\"%(refname)%00%(committerdate:unix)%00%(objectname)%00%(HEAD)%00%(upstream)%00%(upstream:trackshort)\"";
|
||||
}
|
||||
|
||||
public List<Models.Branch> Result()
|
||||
public List<Models.Branch> Result(out int localBranchesCount)
|
||||
{
|
||||
localBranchesCount = 0;
|
||||
|
||||
var branches = new List<Models.Branch>();
|
||||
var rs = ReadToEnd();
|
||||
if (!rs.IsSuccess)
|
||||
return branches;
|
||||
|
||||
var lines = rs.StdOut.Split('\n', StringSplitOptions.RemoveEmptyEntries);
|
||||
var lines = rs.StdOut.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries);
|
||||
var remoteHeads = new Dictionary<string, string>();
|
||||
foreach (var line in lines)
|
||||
{
|
||||
var b = ParseLine(line);
|
||||
if (b != null)
|
||||
{
|
||||
branches.Add(b);
|
||||
if (!b.IsLocal)
|
||||
remoteHeads.Add(b.FullName, b.Head);
|
||||
else
|
||||
localBranchesCount++;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var b in branches)
|
||||
{
|
||||
if (b.IsLocal && !string.IsNullOrEmpty(b.Upstream))
|
||||
{
|
||||
if (remoteHeads.TryGetValue(b.Upstream, out var upstreamHead))
|
||||
{
|
||||
b.IsUpstreamGone = false;
|
||||
|
||||
if (b.TrackStatus == null)
|
||||
b.TrackStatus = new QueryTrackStatus(WorkingDirectory, b.Head, upstreamHead).Result();
|
||||
}
|
||||
else
|
||||
{
|
||||
b.IsUpstreamGone = true;
|
||||
|
||||
if (b.TrackStatus == null)
|
||||
b.TrackStatus = new Models.BranchTrackStatus();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return branches;
|
||||
|
@ -38,7 +68,7 @@ namespace SourceGit.Commands
|
|||
private Models.Branch ParseLine(string line)
|
||||
{
|
||||
var parts = line.Split('\0');
|
||||
if (parts.Length != 5)
|
||||
if (parts.Length != 6)
|
||||
return null;
|
||||
|
||||
var branch = new Models.Branch();
|
||||
|
@ -72,13 +102,16 @@ namespace SourceGit.Commands
|
|||
}
|
||||
|
||||
branch.FullName = refName;
|
||||
branch.Head = parts[1];
|
||||
branch.IsCurrent = parts[2] == "*";
|
||||
branch.Upstream = parts[3];
|
||||
branch.CommitterDate = ulong.Parse(parts[1]);
|
||||
branch.Head = parts[2];
|
||||
branch.IsCurrent = parts[3] == "*";
|
||||
branch.Upstream = parts[4];
|
||||
branch.IsUpstreamGone = false;
|
||||
|
||||
if (branch.IsLocal && !string.IsNullOrEmpty(parts[4]) && !parts[4].Equals("=", StringComparison.Ordinal))
|
||||
branch.TrackStatus = new QueryTrackStatus(WorkingDirectory, branch.Name, branch.Upstream).Result();
|
||||
else
|
||||
if (!branch.IsLocal ||
|
||||
string.IsNullOrEmpty(branch.Upstream) ||
|
||||
string.IsNullOrEmpty(parts[5]) ||
|
||||
parts[5].Equals("=", StringComparison.Ordinal))
|
||||
branch.TrackStatus = new Models.BranchTrackStatus();
|
||||
|
||||
return branch;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace SourceGit.Commands
|
||||
{
|
||||
|
@ -9,22 +10,26 @@ namespace SourceGit.Commands
|
|||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
_commit = commit;
|
||||
Args = $"rev-list -{max} --parents --branches --remotes ^{commit}";
|
||||
Args = $"rev-list -{max} --parents --branches --remotes --ancestry-path ^{commit}";
|
||||
}
|
||||
|
||||
public IEnumerable<string> Result()
|
||||
public List<string> Result()
|
||||
{
|
||||
Exec();
|
||||
return _lines;
|
||||
}
|
||||
|
||||
protected override void OnReadline(string line)
|
||||
var rs = ReadToEnd();
|
||||
var outs = new List<string>();
|
||||
if (rs.IsSuccess)
|
||||
{
|
||||
var lines = rs.StdOut.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries);
|
||||
foreach (var line in lines)
|
||||
{
|
||||
if (line.Contains(_commit))
|
||||
_lines.Add(line.Substring(0, 40));
|
||||
outs.Add(line.Substring(0, 40));
|
||||
}
|
||||
}
|
||||
|
||||
return outs;
|
||||
}
|
||||
|
||||
private string _commit;
|
||||
private List<string> _lines = new List<string>();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ namespace SourceGit.Commands
|
|||
{
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
Args = $"show --no-show-signature --pretty=format:%B -s {sha}";
|
||||
Args = $"show --no-show-signature --format=%B -s {sha}";
|
||||
}
|
||||
|
||||
public string Result()
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
|
||||
const string baseArgs = "show --no-show-signature --pretty=format:\"%G?%n%GS%n%GK\" -s";
|
||||
const string baseArgs = "show --no-show-signature --format=%G?%n%GS%n%GK -s";
|
||||
const string fakeSignersFileArg = "-c gpg.ssh.allowedSignersFile=/dev/null";
|
||||
Args = $"{(useFakeSignersFile ? fakeSignersFileArg : string.Empty)} {baseArgs} {sha}";
|
||||
}
|
||||
|
@ -18,7 +18,7 @@
|
|||
if (!rs.IsSuccess)
|
||||
return null;
|
||||
|
||||
var raw = rs.StdOut.Trim();
|
||||
var raw = rs.StdOut.Trim().ReplaceLineEndings("\n");
|
||||
if (raw.Length <= 1)
|
||||
return null;
|
||||
|
||||
|
@ -29,7 +29,6 @@
|
|||
Signer = lines[1],
|
||||
Key = lines[2]
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,13 +6,11 @@ namespace SourceGit.Commands
|
|||
{
|
||||
public class QueryCommits : Command
|
||||
{
|
||||
public QueryCommits(string repo, bool useTopoOrder, string limits, bool needFindHead = true)
|
||||
public QueryCommits(string repo, string limits, bool needFindHead = true)
|
||||
{
|
||||
var order = useTopoOrder ? "--topo-order" : "--date-order";
|
||||
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
Args = $"log {order} --no-show-signature --decorate=full --pretty=format:%H%n%P%n%D%n%aN±%aE%n%at%n%cN±%cE%n%ct%n%s {limits}";
|
||||
Args = $"log --no-show-signature --decorate=full --format=%H%n%P%n%D%n%aN±%aE%n%at%n%cN±%cE%n%ct%n%s {limits}";
|
||||
_findFirstMerged = needFindHead;
|
||||
}
|
||||
|
||||
|
@ -20,20 +18,20 @@ namespace SourceGit.Commands
|
|||
{
|
||||
string search = onlyCurrentBranch ? string.Empty : "--branches --remotes ";
|
||||
|
||||
if (method == Models.CommitSearchMethod.ByUser)
|
||||
if (method == Models.CommitSearchMethod.ByAuthor)
|
||||
{
|
||||
search += $"-i --author=\"{filter}\" --committer=\"{filter}\"";
|
||||
search += $"-i --author=\"{filter}\"";
|
||||
}
|
||||
else if (method == Models.CommitSearchMethod.ByFile)
|
||||
else if (method == Models.CommitSearchMethod.ByCommitter)
|
||||
{
|
||||
search += $"-- \"{filter}\"";
|
||||
search += $"-i --committer=\"{filter}\"";
|
||||
}
|
||||
else
|
||||
else if (method == Models.CommitSearchMethod.ByMessage)
|
||||
{
|
||||
var argsBuilder = new StringBuilder();
|
||||
argsBuilder.Append(search);
|
||||
|
||||
var words = filter.Split(new[] { ' ', '\t', '\r' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
var words = filter.Split([' ', '\t', '\r'], StringSplitOptions.RemoveEmptyEntries);
|
||||
foreach (var word in words)
|
||||
{
|
||||
var escaped = word.Trim().Replace("\"", "\\\"", StringComparison.Ordinal);
|
||||
|
@ -43,10 +41,18 @@ namespace SourceGit.Commands
|
|||
|
||||
search = argsBuilder.ToString();
|
||||
}
|
||||
else if (method == Models.CommitSearchMethod.ByFile)
|
||||
{
|
||||
search += $"-- \"{filter}\"";
|
||||
}
|
||||
else
|
||||
{
|
||||
search = $"-G\"{filter}\"";
|
||||
}
|
||||
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
Args = $"log -1000 --date-order --no-show-signature --decorate=full --pretty=format:%H%n%P%n%D%n%aN±%aE%n%at%n%cN±%cE%n%ct%n%s " + search;
|
||||
Args = $"log -1000 --date-order --no-show-signature --decorate=full --format=%H%n%P%n%D%n%aN±%aE%n%at%n%cN±%cE%n%ct%n%s {search}";
|
||||
_findFirstMerged = false;
|
||||
}
|
||||
|
||||
|
@ -122,7 +128,7 @@ namespace SourceGit.Commands
|
|||
Args = $"log --since=\"{_commits[^1].CommitterTimeStr}\" --format=\"%H\"";
|
||||
|
||||
var rs = ReadToEnd();
|
||||
var shas = rs.StdOut.Split('\n', StringSplitOptions.RemoveEmptyEntries);
|
||||
var shas = rs.StdOut.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries);
|
||||
if (shas.Length == 0)
|
||||
return;
|
||||
|
||||
|
|
|
@ -3,18 +3,18 @@ using System.Collections.Generic;
|
|||
|
||||
namespace SourceGit.Commands
|
||||
{
|
||||
public class QueryCommitsWithFullMessage : Command
|
||||
public class QueryCommitsForInteractiveRebase : Command
|
||||
{
|
||||
public QueryCommitsWithFullMessage(string repo, string args)
|
||||
public QueryCommitsForInteractiveRebase(string repo, string on)
|
||||
{
|
||||
_boundary = $"----- BOUNDARY OF COMMIT {Guid.NewGuid()} -----";
|
||||
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
Args = $"log --date-order --no-show-signature --decorate=full --pretty=format:\"%H%n%P%n%D%n%aN±%aE%n%at%n%cN±%cE%n%ct%n%B%n{_boundary}\" {args}";
|
||||
Args = $"log --date-order --no-show-signature --decorate=full --format=\"%H%n%P%n%D%n%aN±%aE%n%at%n%cN±%cE%n%ct%n%B%n{_boundary}\" {on}..HEAD";
|
||||
}
|
||||
|
||||
public List<Models.CommitWithMessage> Result()
|
||||
public List<Models.InteractiveCommit> Result()
|
||||
{
|
||||
var rs = ReadToEnd();
|
||||
if (!rs.IsSuccess)
|
||||
|
@ -29,7 +29,7 @@ namespace SourceGit.Commands
|
|||
switch (nextPartIdx)
|
||||
{
|
||||
case 0:
|
||||
_current = new Models.CommitWithMessage();
|
||||
_current = new Models.InteractiveCommit();
|
||||
_current.Commit.SHA = line;
|
||||
_commits.Add(_current);
|
||||
break;
|
||||
|
@ -52,7 +52,7 @@ namespace SourceGit.Commands
|
|||
_current.Commit.CommitterTime = ulong.Parse(line);
|
||||
break;
|
||||
default:
|
||||
var boundary = rs.StdOut.IndexOf(_boundary, end + 1);
|
||||
var boundary = rs.StdOut.IndexOf(_boundary, end + 1, StringComparison.Ordinal);
|
||||
if (boundary > end)
|
||||
{
|
||||
_current.Message = rs.StdOut.Substring(start, boundary - start - 1);
|
||||
|
@ -88,8 +88,8 @@ namespace SourceGit.Commands
|
|||
_current.Commit.Parents.AddRange(data.Split(separator: ' ', options: StringSplitOptions.RemoveEmptyEntries));
|
||||
}
|
||||
|
||||
private List<Models.CommitWithMessage> _commits = new List<Models.CommitWithMessage>();
|
||||
private Models.CommitWithMessage _current = null;
|
||||
private List<Models.InteractiveCommit> _commits = [];
|
||||
private Models.InteractiveCommit _current = null;
|
||||
private string _boundary = "";
|
||||
}
|
||||
}
|
|
@ -35,5 +35,39 @@ namespace SourceGit.Commands
|
|||
|
||||
return stream;
|
||||
}
|
||||
|
||||
public static Stream FromLFS(string repo, string oid, long size)
|
||||
{
|
||||
var starter = new ProcessStartInfo();
|
||||
starter.WorkingDirectory = repo;
|
||||
starter.FileName = Native.OS.GitExecutable;
|
||||
starter.Arguments = $"lfs smudge";
|
||||
starter.UseShellExecute = false;
|
||||
starter.CreateNoWindow = true;
|
||||
starter.WindowStyle = ProcessWindowStyle.Hidden;
|
||||
starter.RedirectStandardInput = true;
|
||||
starter.RedirectStandardOutput = true;
|
||||
|
||||
var stream = new MemoryStream();
|
||||
try
|
||||
{
|
||||
var proc = new Process() { StartInfo = starter };
|
||||
proc.Start();
|
||||
proc.StandardInput.WriteLine("version https://git-lfs.github.com/spec/v1");
|
||||
proc.StandardInput.WriteLine($"oid sha256:{oid}");
|
||||
proc.StandardInput.WriteLine($"size {size}");
|
||||
proc.StandardOutput.BaseStream.CopyTo(stream);
|
||||
proc.WaitForExit();
|
||||
proc.Close();
|
||||
|
||||
stream.Position = 0;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
App.RaiseException(repo, $"Failed to query file content: {e}");
|
||||
}
|
||||
|
||||
return stream;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
26
src/Commands/QueryGitCommonDir.cs
Normal file
26
src/Commands/QueryGitCommonDir.cs
Normal file
|
@ -0,0 +1,26 @@
|
|||
using System.IO;
|
||||
|
||||
namespace SourceGit.Commands
|
||||
{
|
||||
public class QueryGitCommonDir : Command
|
||||
{
|
||||
public QueryGitCommonDir(string workDir)
|
||||
{
|
||||
WorkingDirectory = workDir;
|
||||
Args = "rev-parse --git-common-dir";
|
||||
RaiseError = false;
|
||||
}
|
||||
|
||||
public string Result()
|
||||
{
|
||||
var rs = ReadToEnd().StdOut;
|
||||
if (string.IsNullOrEmpty(rs))
|
||||
return null;
|
||||
|
||||
rs = rs.Trim();
|
||||
if (Path.IsPathRooted(rs))
|
||||
return rs;
|
||||
return Path.GetFullPath(Path.Combine(WorkingDirectory, rs));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,6 +1,9 @@
|
|||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
using Avalonia.Threading;
|
||||
|
||||
namespace SourceGit.Commands
|
||||
{
|
||||
public partial class QueryLocalChanges : Command
|
||||
|
@ -13,20 +16,25 @@ namespace SourceGit.Commands
|
|||
{
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
Args = $"status -u{UNTRACKED[includeUntracked ? 1 : 0]} --ignore-submodules=dirty --porcelain";
|
||||
Args = $"--no-optional-locks status -u{UNTRACKED[includeUntracked ? 1 : 0]} --ignore-submodules=dirty --porcelain";
|
||||
}
|
||||
|
||||
public List<Models.Change> Result()
|
||||
{
|
||||
Exec();
|
||||
return _changes;
|
||||
var outs = new List<Models.Change>();
|
||||
var rs = ReadToEnd();
|
||||
if (!rs.IsSuccess)
|
||||
{
|
||||
Dispatcher.UIThread.Post(() => App.RaiseException(Context, rs.StdErr));
|
||||
return outs;
|
||||
}
|
||||
|
||||
protected override void OnReadline(string line)
|
||||
var lines = rs.StdOut.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries);
|
||||
foreach (var line in lines)
|
||||
{
|
||||
var match = REG_FORMAT().Match(line);
|
||||
if (!match.Success)
|
||||
return;
|
||||
continue;
|
||||
|
||||
var change = new Models.Change() { Path = match.Groups[2].Value };
|
||||
var status = match.Groups[1].Value;
|
||||
|
@ -114,43 +122,44 @@ namespace SourceGit.Commands
|
|||
case "CD":
|
||||
change.Set(Models.ChangeState.Copied, Models.ChangeState.Deleted);
|
||||
break;
|
||||
case "DR":
|
||||
change.Set(Models.ChangeState.Deleted, Models.ChangeState.Renamed);
|
||||
break;
|
||||
case "DC":
|
||||
change.Set(Models.ChangeState.Deleted, Models.ChangeState.Copied);
|
||||
break;
|
||||
case "DD":
|
||||
change.Set(Models.ChangeState.Deleted, Models.ChangeState.Deleted);
|
||||
change.ConflictReason = Models.ConflictReason.BothDeleted;
|
||||
change.Set(Models.ChangeState.None, Models.ChangeState.Conflicted);
|
||||
break;
|
||||
case "AU":
|
||||
change.Set(Models.ChangeState.Added, Models.ChangeState.Unmerged);
|
||||
change.ConflictReason = Models.ConflictReason.AddedByUs;
|
||||
change.Set(Models.ChangeState.None, Models.ChangeState.Conflicted);
|
||||
break;
|
||||
case "UD":
|
||||
change.Set(Models.ChangeState.Unmerged, Models.ChangeState.Deleted);
|
||||
change.ConflictReason = Models.ConflictReason.DeletedByThem;
|
||||
change.Set(Models.ChangeState.None, Models.ChangeState.Conflicted);
|
||||
break;
|
||||
case "UA":
|
||||
change.Set(Models.ChangeState.Unmerged, Models.ChangeState.Added);
|
||||
change.ConflictReason = Models.ConflictReason.AddedByThem;
|
||||
change.Set(Models.ChangeState.None, Models.ChangeState.Conflicted);
|
||||
break;
|
||||
case "DU":
|
||||
change.Set(Models.ChangeState.Deleted, Models.ChangeState.Unmerged);
|
||||
change.ConflictReason = Models.ConflictReason.DeletedByUs;
|
||||
change.Set(Models.ChangeState.None, Models.ChangeState.Conflicted);
|
||||
break;
|
||||
case "AA":
|
||||
change.Set(Models.ChangeState.Added, Models.ChangeState.Added);
|
||||
change.ConflictReason = Models.ConflictReason.BothAdded;
|
||||
change.Set(Models.ChangeState.None, Models.ChangeState.Conflicted);
|
||||
break;
|
||||
case "UU":
|
||||
change.Set(Models.ChangeState.Unmerged, Models.ChangeState.Unmerged);
|
||||
change.ConflictReason = Models.ConflictReason.BothModified;
|
||||
change.Set(Models.ChangeState.None, Models.ChangeState.Conflicted);
|
||||
break;
|
||||
case "??":
|
||||
change.Set(Models.ChangeState.Untracked, Models.ChangeState.Untracked);
|
||||
change.Set(Models.ChangeState.None, Models.ChangeState.Untracked);
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
_changes.Add(change);
|
||||
if (change.Index != Models.ChangeState.None || change.WorkTree != Models.ChangeState.None)
|
||||
outs.Add(change);
|
||||
}
|
||||
|
||||
private readonly List<Models.Change> _changes = new List<Models.Change>();
|
||||
return outs;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,9 +20,12 @@ namespace SourceGit.Commands
|
|||
if (!output.IsSuccess)
|
||||
return rs;
|
||||
|
||||
var lines = output.StdOut.Split('\n');
|
||||
var lines = output.StdOut.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries);
|
||||
foreach (var line in lines)
|
||||
{
|
||||
if (line.EndsWith("/HEAD", StringComparison.Ordinal))
|
||||
continue;
|
||||
|
||||
if (line.StartsWith("refs/heads/", StringComparison.Ordinal))
|
||||
rs.Add(new() { Name = line.Substring("refs/heads/".Length), Type = Models.DecoratorType.LocalBranchHead });
|
||||
else if (line.StartsWith("refs/remotes/", StringComparison.Ordinal))
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace SourceGit.Commands
|
||||
|
@ -17,15 +18,17 @@ namespace SourceGit.Commands
|
|||
|
||||
public List<Models.Remote> Result()
|
||||
{
|
||||
Exec();
|
||||
return _loaded;
|
||||
}
|
||||
var outs = new List<Models.Remote>();
|
||||
var rs = ReadToEnd();
|
||||
if (!rs.IsSuccess)
|
||||
return outs;
|
||||
|
||||
protected override void OnReadline(string line)
|
||||
var lines = rs.StdOut.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries);
|
||||
foreach (var line in lines)
|
||||
{
|
||||
var match = REG_REMOTE().Match(line);
|
||||
if (!match.Success)
|
||||
return;
|
||||
continue;
|
||||
|
||||
var remote = new Models.Remote()
|
||||
{
|
||||
|
@ -33,11 +36,13 @@ namespace SourceGit.Commands
|
|||
URL = match.Groups[2].Value,
|
||||
};
|
||||
|
||||
if (_loaded.Find(x => x.Name == remote.Name) != null)
|
||||
return;
|
||||
_loaded.Add(remote);
|
||||
if (outs.Find(x => x.Name == remote.Name) != null)
|
||||
continue;
|
||||
|
||||
outs.Add(remote);
|
||||
}
|
||||
|
||||
private readonly List<Models.Remote> _loaded = new List<Models.Remote>();
|
||||
return outs;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
21
src/Commands/QueryRevisionByRefName.cs
Normal file
21
src/Commands/QueryRevisionByRefName.cs
Normal file
|
@ -0,0 +1,21 @@
|
|||
namespace SourceGit.Commands
|
||||
{
|
||||
public class QueryRevisionByRefName : Command
|
||||
{
|
||||
public QueryRevisionByRefName(string repo, string refname)
|
||||
{
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
Args = $"rev-parse {refname}";
|
||||
}
|
||||
|
||||
public string Result()
|
||||
{
|
||||
var rs = ReadToEnd();
|
||||
if (rs.IsSuccess && !string.IsNullOrEmpty(rs.StdOut))
|
||||
return rs.StdOut.Trim();
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,4 +1,6 @@
|
|||
namespace SourceGit.Commands
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace SourceGit.Commands
|
||||
{
|
||||
public class QueryRevisionFileNames : Command
|
||||
{
|
||||
|
@ -9,13 +11,17 @@
|
|||
Args = $"ls-tree -r -z --name-only {revision}";
|
||||
}
|
||||
|
||||
public string[] Result()
|
||||
public List<string> Result()
|
||||
{
|
||||
var rs = ReadToEnd();
|
||||
if (rs.IsSuccess)
|
||||
return rs.StdOut.Split('\0', System.StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
if (!rs.IsSuccess)
|
||||
return [];
|
||||
|
||||
var lines = rs.StdOut.Split('\0', System.StringSplitOptions.RemoveEmptyEntries);
|
||||
var outs = new List<string>();
|
||||
foreach (var line in lines)
|
||||
outs.Add(line);
|
||||
return outs;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ namespace SourceGit.Commands
|
|||
{
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
Args = $"show --no-show-signature --decorate=full --pretty=format:%H%n%P%n%D%n%aN±%aE%n%at%n%cN±%cE%n%ct%n%s -s {sha}";
|
||||
Args = $"show --no-show-signature --decorate=full --format=%H%n%P%n%D%n%aN±%aE%n%at%n%cN±%cE%n%ct%n%s -s {sha}";
|
||||
}
|
||||
|
||||
public Models.Commit Result()
|
||||
|
|
|
@ -6,25 +6,27 @@ namespace SourceGit.Commands
|
|||
{
|
||||
public partial class QueryStagedChangesWithAmend : Command
|
||||
{
|
||||
[GeneratedRegex(@"^:[\d]{6} ([\d]{6}) ([0-9a-f]{40}) [0-9a-f]{40} ([ACDMTUX])\d{0,6}\t(.*)$")]
|
||||
[GeneratedRegex(@"^:[\d]{6} ([\d]{6}) ([0-9a-f]{40}) [0-9a-f]{40} ([ACDMT])\d{0,6}\t(.*)$")]
|
||||
private static partial Regex REG_FORMAT1();
|
||||
[GeneratedRegex(@"^:[\d]{6} ([\d]{6}) ([0-9a-f]{40}) [0-9a-f]{40} R\d{0,6}\t(.*\t.*)$")]
|
||||
private static partial Regex REG_FORMAT2();
|
||||
|
||||
public QueryStagedChangesWithAmend(string repo)
|
||||
public QueryStagedChangesWithAmend(string repo, string parent)
|
||||
{
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
Args = "diff-index --cached -M HEAD^";
|
||||
Args = $"diff-index --cached -M {parent}";
|
||||
_parent = parent;
|
||||
}
|
||||
|
||||
public List<Models.Change> Result()
|
||||
{
|
||||
var rs = ReadToEnd();
|
||||
if (rs.IsSuccess)
|
||||
{
|
||||
if (!rs.IsSuccess)
|
||||
return [];
|
||||
|
||||
var changes = new List<Models.Change>();
|
||||
var lines = rs.StdOut.Split('\n', StringSplitOptions.RemoveEmptyEntries);
|
||||
var lines = rs.StdOut.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries);
|
||||
foreach (var line in lines)
|
||||
{
|
||||
var match = REG_FORMAT2().Match(line);
|
||||
|
@ -37,6 +39,7 @@ namespace SourceGit.Commands
|
|||
{
|
||||
FileMode = match.Groups[1].Value,
|
||||
ObjectHash = match.Groups[2].Value,
|
||||
ParentSHA = _parent,
|
||||
},
|
||||
};
|
||||
change.Set(Models.ChangeState.Renamed);
|
||||
|
@ -54,6 +57,7 @@ namespace SourceGit.Commands
|
|||
{
|
||||
FileMode = match.Groups[1].Value,
|
||||
ObjectHash = match.Groups[2].Value,
|
||||
ParentSHA = _parent,
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -75,9 +79,6 @@ namespace SourceGit.Commands
|
|||
case "T":
|
||||
change.Set(Models.ChangeState.TypeChanged);
|
||||
break;
|
||||
case "U":
|
||||
change.Set(Models.ChangeState.Unmerged);
|
||||
break;
|
||||
}
|
||||
changes.Add(change);
|
||||
}
|
||||
|
@ -86,7 +87,6 @@ namespace SourceGit.Commands
|
|||
return changes;
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
private readonly string _parent;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,60 +0,0 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace SourceGit.Commands
|
||||
{
|
||||
public partial class QueryStashChanges : Command
|
||||
{
|
||||
[GeneratedRegex(@"^(\s?[\w\?]{1,4})\s+(.+)$")]
|
||||
private static partial Regex REG_FORMAT();
|
||||
|
||||
public QueryStashChanges(string repo, string sha)
|
||||
{
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
Args = $"diff --name-status --pretty=format: {sha}^ {sha}";
|
||||
}
|
||||
|
||||
public List<Models.Change> Result()
|
||||
{
|
||||
Exec();
|
||||
return _changes;
|
||||
}
|
||||
|
||||
protected override void OnReadline(string line)
|
||||
{
|
||||
var match = REG_FORMAT().Match(line);
|
||||
if (!match.Success)
|
||||
return;
|
||||
|
||||
var change = new Models.Change() { Path = match.Groups[2].Value };
|
||||
var status = match.Groups[1].Value;
|
||||
|
||||
switch (status[0])
|
||||
{
|
||||
case 'M':
|
||||
change.Set(Models.ChangeState.Modified);
|
||||
_changes.Add(change);
|
||||
break;
|
||||
case 'A':
|
||||
change.Set(Models.ChangeState.Added);
|
||||
_changes.Add(change);
|
||||
break;
|
||||
case 'D':
|
||||
change.Set(Models.ChangeState.Deleted);
|
||||
_changes.Add(change);
|
||||
break;
|
||||
case 'R':
|
||||
change.Set(Models.ChangeState.Renamed);
|
||||
_changes.Add(change);
|
||||
break;
|
||||
case 'C':
|
||||
change.Set(Models.ChangeState.Copied);
|
||||
_changes.Add(change);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly List<Models.Change> _changes = new List<Models.Change>();
|
||||
}
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace SourceGit.Commands
|
||||
{
|
||||
|
@ -8,41 +9,65 @@ namespace SourceGit.Commands
|
|||
{
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
Args = "stash list --pretty=format:%H%n%ct%n%gd%n%s";
|
||||
Args = "stash list --format=%H%n%P%n%ct%n%gd%n%s";
|
||||
}
|
||||
|
||||
public List<Models.Stash> Result()
|
||||
{
|
||||
Exec();
|
||||
return _stashes;
|
||||
}
|
||||
var outs = new List<Models.Stash>();
|
||||
var rs = ReadToEnd();
|
||||
if (!rs.IsSuccess)
|
||||
return outs;
|
||||
|
||||
protected override void OnReadline(string line)
|
||||
var nextPartIdx = 0;
|
||||
var start = 0;
|
||||
var end = rs.StdOut.IndexOf('\n', start);
|
||||
while (end > 0)
|
||||
{
|
||||
switch (_nextLineIdx)
|
||||
var line = rs.StdOut.Substring(start, end - start);
|
||||
|
||||
switch (nextPartIdx)
|
||||
{
|
||||
case 0:
|
||||
_current = new Models.Stash() { SHA = line };
|
||||
_stashes.Add(_current);
|
||||
outs.Add(_current);
|
||||
break;
|
||||
case 1:
|
||||
_current.Time = ulong.Parse(line);
|
||||
ParseParent(line);
|
||||
break;
|
||||
case 2:
|
||||
_current.Name = line;
|
||||
_current.Time = ulong.Parse(line);
|
||||
break;
|
||||
case 3:
|
||||
_current.Name = line;
|
||||
break;
|
||||
case 4:
|
||||
_current.Message = line;
|
||||
break;
|
||||
}
|
||||
|
||||
_nextLineIdx++;
|
||||
if (_nextLineIdx > 3)
|
||||
_nextLineIdx = 0;
|
||||
nextPartIdx++;
|
||||
if (nextPartIdx > 4)
|
||||
nextPartIdx = 0;
|
||||
|
||||
start = end + 1;
|
||||
end = rs.StdOut.IndexOf('\n', start);
|
||||
}
|
||||
|
||||
if (start < rs.StdOut.Length)
|
||||
_current.Message = rs.StdOut.Substring(start);
|
||||
|
||||
return outs;
|
||||
}
|
||||
|
||||
private void ParseParent(string data)
|
||||
{
|
||||
if (data.Length < 8)
|
||||
return;
|
||||
|
||||
_current.Parents.AddRange(data.Split(separator: ' ', options: StringSplitOptions.RemoveEmptyEntries));
|
||||
}
|
||||
|
||||
private readonly List<Models.Stash> _stashes = new List<Models.Stash>();
|
||||
private Models.Stash _current = null;
|
||||
private int _nextLineIdx = 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
|
@ -6,12 +7,12 @@ namespace SourceGit.Commands
|
|||
{
|
||||
public partial class QuerySubmodules : Command
|
||||
{
|
||||
[GeneratedRegex(@"^[\-\+ ][0-9a-f]+\s(.*)\s\(.*\)$")]
|
||||
private static partial Regex REG_FORMAT1();
|
||||
[GeneratedRegex(@"^[\-\+ ][0-9a-f]+\s(.*)$")]
|
||||
private static partial Regex REG_FORMAT2();
|
||||
[GeneratedRegex(@"^\s?[\w\?]{1,4}\s+(.+)$")]
|
||||
[GeneratedRegex(@"^([U\-\+ ])([0-9a-f]+)\s(.*?)(\s\(.*\))?$")]
|
||||
private static partial Regex REG_FORMAT_STATUS();
|
||||
[GeneratedRegex(@"^\s?[\w\?]{1,4}\s+(.+)$")]
|
||||
private static partial Regex REG_FORMAT_DIRTY();
|
||||
[GeneratedRegex(@"^submodule\.(\S*)\.(\w+)=(.*)$")]
|
||||
private static partial Regex REG_FORMAT_MODULE_INFO();
|
||||
|
||||
public QuerySubmodules(string repo)
|
||||
{
|
||||
|
@ -24,55 +25,118 @@ namespace SourceGit.Commands
|
|||
{
|
||||
var submodules = new List<Models.Submodule>();
|
||||
var rs = ReadToEnd();
|
||||
if (!rs.IsSuccess)
|
||||
return submodules;
|
||||
|
||||
var builder = new StringBuilder();
|
||||
var lines = rs.StdOut.Split('\n', System.StringSplitOptions.RemoveEmptyEntries);
|
||||
foreach (var line in lines)
|
||||
{
|
||||
var match = REG_FORMAT1().Match(line);
|
||||
if (match.Success)
|
||||
{
|
||||
var path = match.Groups[1].Value;
|
||||
builder.Append($"\"{path}\" ");
|
||||
submodules.Add(new Models.Submodule() { Path = path });
|
||||
continue;
|
||||
}
|
||||
|
||||
match = REG_FORMAT2().Match(line);
|
||||
if (match.Success)
|
||||
{
|
||||
var path = match.Groups[1].Value;
|
||||
builder.Append($"\"{path}\" ");
|
||||
submodules.Add(new Models.Submodule() { Path = path });
|
||||
}
|
||||
}
|
||||
|
||||
if (submodules.Count > 0)
|
||||
{
|
||||
Args = $"status -uno --porcelain -- {builder}";
|
||||
rs = ReadToEnd();
|
||||
if (!rs.IsSuccess)
|
||||
return submodules;
|
||||
|
||||
var dirty = new HashSet<string>();
|
||||
lines = rs.StdOut.Split('\n', System.StringSplitOptions.RemoveEmptyEntries);
|
||||
var lines = rs.StdOut.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries);
|
||||
var map = new Dictionary<string, Models.Submodule>();
|
||||
var needCheckLocalChanges = false;
|
||||
foreach (var line in lines)
|
||||
{
|
||||
var match = REG_FORMAT_STATUS().Match(line);
|
||||
if (match.Success)
|
||||
{
|
||||
var path = match.Groups[1].Value;
|
||||
dirty.Add(path);
|
||||
var stat = match.Groups[1].Value;
|
||||
var sha = match.Groups[2].Value;
|
||||
var path = match.Groups[3].Value;
|
||||
|
||||
var module = new Models.Submodule() { Path = path, SHA = sha };
|
||||
switch (stat[0])
|
||||
{
|
||||
case '-':
|
||||
module.Status = Models.SubmoduleStatus.NotInited;
|
||||
break;
|
||||
case '+':
|
||||
module.Status = Models.SubmoduleStatus.RevisionChanged;
|
||||
break;
|
||||
case 'U':
|
||||
module.Status = Models.SubmoduleStatus.Unmerged;
|
||||
break;
|
||||
default:
|
||||
module.Status = Models.SubmoduleStatus.Normal;
|
||||
needCheckLocalChanges = true;
|
||||
break;
|
||||
}
|
||||
|
||||
map.Add(path, module);
|
||||
submodules.Add(module);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var submodule in submodules)
|
||||
submodule.IsDirty = dirty.Contains(submodule.Path);
|
||||
if (submodules.Count > 0)
|
||||
{
|
||||
Args = "config --file .gitmodules --list";
|
||||
rs = ReadToEnd();
|
||||
if (rs.IsSuccess)
|
||||
{
|
||||
var modules = new Dictionary<string, ModuleInfo>();
|
||||
lines = rs.StdOut.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries);
|
||||
foreach (var line in lines)
|
||||
{
|
||||
var match = REG_FORMAT_MODULE_INFO().Match(line);
|
||||
if (match.Success)
|
||||
{
|
||||
var name = match.Groups[1].Value;
|
||||
var key = match.Groups[2].Value;
|
||||
var val = match.Groups[3].Value;
|
||||
|
||||
if (!modules.TryGetValue(name, out var m))
|
||||
{
|
||||
m = new ModuleInfo();
|
||||
modules.Add(name, m);
|
||||
}
|
||||
|
||||
if (key.Equals("path", StringComparison.Ordinal))
|
||||
m.Path = val;
|
||||
else if (key.Equals("url", StringComparison.Ordinal))
|
||||
m.URL = val;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var kv in modules)
|
||||
{
|
||||
if (map.TryGetValue(kv.Value.Path, out var m))
|
||||
m.URL = kv.Value.URL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (needCheckLocalChanges)
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
foreach (var kv in map)
|
||||
{
|
||||
if (kv.Value.Status == Models.SubmoduleStatus.Normal)
|
||||
{
|
||||
builder.Append('"');
|
||||
builder.Append(kv.Key);
|
||||
builder.Append("\" ");
|
||||
}
|
||||
}
|
||||
|
||||
Args = $"--no-optional-locks status --porcelain -- {builder}";
|
||||
rs = ReadToEnd();
|
||||
if (!rs.IsSuccess)
|
||||
return submodules;
|
||||
|
||||
lines = rs.StdOut.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries);
|
||||
foreach (var line in lines)
|
||||
{
|
||||
var match = REG_FORMAT_DIRTY().Match(line);
|
||||
if (match.Success)
|
||||
{
|
||||
var path = match.Groups[1].Value;
|
||||
if (map.TryGetValue(path, out var m))
|
||||
m.Status = Models.SubmoduleStatus.Modified;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return submodules;
|
||||
}
|
||||
|
||||
private class ModuleInfo
|
||||
{
|
||||
public string Path { get; set; } = string.Empty;
|
||||
public string URL { get; set; } = string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ namespace SourceGit.Commands
|
|||
|
||||
Context = repo;
|
||||
WorkingDirectory = repo;
|
||||
Args = $"tag -l --sort=-creatordate --format=\"{_boundary}%(refname)%00%(objectname)%00%(*objectname)%00%(contents:subject)%0a%0a%(contents:body)\"";
|
||||
Args = $"tag -l --format=\"{_boundary}%(refname)%00%(objecttype)%00%(objectname)%00%(*objectname)%00%(creatordate:unix)%00%(contents:subject)%0a%0a%(contents:body)\"";
|
||||
}
|
||||
|
||||
public List<Models.Tag> Result()
|
||||
|
@ -25,15 +25,21 @@ namespace SourceGit.Commands
|
|||
foreach (var record in records)
|
||||
{
|
||||
var subs = record.Split('\0', StringSplitOptions.None);
|
||||
if (subs.Length != 4)
|
||||
if (subs.Length != 6)
|
||||
continue;
|
||||
|
||||
var message = subs[3].Trim();
|
||||
var name = subs[0].Substring(10);
|
||||
var message = subs[5].Trim();
|
||||
if (!string.IsNullOrEmpty(message) && message.Equals(name, StringComparison.Ordinal))
|
||||
message = null;
|
||||
|
||||
tags.Add(new Models.Tag()
|
||||
{
|
||||
Name = subs[0].Substring(10),
|
||||
SHA = string.IsNullOrEmpty(subs[2]) ? subs[1] : subs[2],
|
||||
Message = string.IsNullOrEmpty(message) ? null : message,
|
||||
Name = name,
|
||||
IsAnnotated = subs[1].Equals("tag", StringComparison.Ordinal),
|
||||
SHA = string.IsNullOrEmpty(subs[3]) ? subs[2] : subs[3],
|
||||
CreatorDate = ulong.Parse(subs[4]),
|
||||
Message = message,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@ namespace SourceGit.Commands
|
|||
if (!rs.IsSuccess)
|
||||
return status;
|
||||
|
||||
var lines = rs.StdOut.Split(new char[] { '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
var lines = rs.StdOut.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries);
|
||||
foreach (var line in lines)
|
||||
{
|
||||
if (line[0] == '>')
|
||||
|
|
40
src/Commands/QueryUpdatableSubmodules.cs
Normal file
40
src/Commands/QueryUpdatableSubmodules.cs
Normal file
|
@ -0,0 +1,40 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace SourceGit.Commands
|
||||
{
|
||||
public partial class QueryUpdatableSubmodules : Command
|
||||
{
|
||||
[GeneratedRegex(@"^([U\-\+ ])([0-9a-f]+)\s(.*?)(\s\(.*\))?$")]
|
||||
private static partial Regex REG_FORMAT_STATUS();
|
||||
|
||||
public QueryUpdatableSubmodules(string repo)
|
||||
{
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
Args = "submodule status";
|
||||
}
|
||||
|
||||
public List<string> Result()
|
||||
{
|
||||
var submodules = new List<string>();
|
||||
var rs = ReadToEnd();
|
||||
|
||||
var lines = rs.StdOut.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries);
|
||||
foreach (var line in lines)
|
||||
{
|
||||
var match = REG_FORMAT_STATUS().Match(line);
|
||||
if (match.Success)
|
||||
{
|
||||
var stat = match.Groups[1].Value;
|
||||
var path = match.Groups[3].Value;
|
||||
if (!stat.StartsWith(' '))
|
||||
submodules.Add(path);
|
||||
}
|
||||
}
|
||||
|
||||
return submodules;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,33 +1,7 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace SourceGit.Commands
|
||||
namespace SourceGit.Commands
|
||||
{
|
||||
public class Reset : Command
|
||||
{
|
||||
public Reset(string repo)
|
||||
{
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
Args = "reset";
|
||||
}
|
||||
|
||||
public Reset(string repo, List<Models.Change> changes)
|
||||
{
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
|
||||
var builder = new StringBuilder();
|
||||
builder.Append("reset --");
|
||||
foreach (var c in changes)
|
||||
{
|
||||
builder.Append(" \"");
|
||||
builder.Append(c.Path);
|
||||
builder.Append("\"");
|
||||
}
|
||||
Args = builder.ToString();
|
||||
}
|
||||
|
||||
public Reset(string repo, string revision, string mode)
|
||||
{
|
||||
WorkingDirectory = repo;
|
||||
|
|
|
@ -1,29 +1,52 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Text;
|
||||
|
||||
namespace SourceGit.Commands
|
||||
{
|
||||
public class Restore : Command
|
||||
{
|
||||
public Restore(string repo)
|
||||
/// <summary>
|
||||
/// Only used for single staged change.
|
||||
/// </summary>
|
||||
/// <param name="repo"></param>
|
||||
/// <param name="stagedChange"></param>
|
||||
public Restore(string repo, Models.Change stagedChange)
|
||||
{
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
Args = "restore . --source=HEAD --staged --worktree --recurse-submodules";
|
||||
|
||||
var builder = new StringBuilder();
|
||||
builder.Append("restore --staged -- \"");
|
||||
builder.Append(stagedChange.Path);
|
||||
builder.Append('"');
|
||||
|
||||
if (stagedChange.Index == Models.ChangeState.Renamed)
|
||||
{
|
||||
builder.Append(" \"");
|
||||
builder.Append(stagedChange.OriginalPath);
|
||||
builder.Append('"');
|
||||
}
|
||||
|
||||
public Restore(string repo, List<string> files, string extra)
|
||||
Args = builder.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Restore changes given in a path-spec file.
|
||||
/// </summary>
|
||||
/// <param name="repo"></param>
|
||||
/// <param name="pathspecFile"></param>
|
||||
/// <param name="isStaged"></param>
|
||||
public Restore(string repo, string pathspecFile, bool isStaged)
|
||||
{
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
|
||||
StringBuilder builder = new StringBuilder();
|
||||
var builder = new StringBuilder();
|
||||
builder.Append("restore ");
|
||||
if (!string.IsNullOrEmpty(extra))
|
||||
builder.Append(extra).Append(" ");
|
||||
builder.Append("--");
|
||||
foreach (var f in files)
|
||||
builder.Append(' ').Append('"').Append(f).Append('"');
|
||||
builder.Append(isStaged ? "--staged " : "--worktree --recurse-submodules ");
|
||||
builder.Append("--pathspec-from-file=\"");
|
||||
builder.Append(pathspecFile);
|
||||
builder.Append('"');
|
||||
|
||||
Args = builder.ToString();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,6 +37,19 @@ namespace SourceGit.Commands
|
|||
return true;
|
||||
}
|
||||
|
||||
public static bool ProcessStashChanges(string repo, List<Models.DiffOption> opts, string saveTo)
|
||||
{
|
||||
using (var sw = File.Create(saveTo))
|
||||
{
|
||||
foreach (var opt in opts)
|
||||
{
|
||||
if (!ProcessSingleChange(repo, opt, sw))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool ProcessSingleChange(string repo, Models.DiffOption opt, FileStream writer)
|
||||
{
|
||||
var starter = new ProcessStartInfo();
|
||||
|
|
|
@ -13,12 +13,8 @@ namespace SourceGit.Commands
|
|||
var isLFSFiltered = new IsLFSFiltered(repo, revision, file).Result();
|
||||
if (isLFSFiltered)
|
||||
{
|
||||
var tmpFile = saveTo + ".tmp";
|
||||
if (ExecCmd(repo, $"show {revision}:\"{file}\"", tmpFile))
|
||||
{
|
||||
ExecCmd(repo, $"lfs smudge", saveTo, tmpFile);
|
||||
}
|
||||
File.Delete(tmpFile);
|
||||
var pointerStream = QueryFileContent.Run(repo, revision, file);
|
||||
ExecCmd(repo, $"lfs smudge", saveTo, pointerStream);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -26,7 +22,7 @@ namespace SourceGit.Commands
|
|||
}
|
||||
}
|
||||
|
||||
private static bool ExecCmd(string repo, string args, string outputFile, string inputFile = null)
|
||||
private static bool ExecCmd(string repo, string args, string outputFile, Stream input = null)
|
||||
{
|
||||
var starter = new ProcessStartInfo();
|
||||
starter.WorkingDirectory = repo;
|
||||
|
@ -45,21 +41,8 @@ namespace SourceGit.Commands
|
|||
{
|
||||
var proc = new Process() { StartInfo = starter };
|
||||
proc.Start();
|
||||
|
||||
if (inputFile != null)
|
||||
{
|
||||
using (StreamReader sr = new StreamReader(inputFile))
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
var line = sr.ReadLine();
|
||||
if (line == null)
|
||||
break;
|
||||
proc.StandardInput.WriteLine(line);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (input != null)
|
||||
proc.StandardInput.Write(new StreamReader(input).ReadToEnd());
|
||||
proc.StandardOutput.BaseStream.CopyTo(sw);
|
||||
proc.WaitForExit();
|
||||
var rs = proc.ExitCode == 0;
|
||||
|
|
|
@ -11,72 +11,84 @@ namespace SourceGit.Commands
|
|||
Context = repo;
|
||||
}
|
||||
|
||||
public bool Push(string message)
|
||||
{
|
||||
Args = $"stash push -m \"{message}\"";
|
||||
return Exec();
|
||||
}
|
||||
|
||||
public bool Push(List<Models.Change> changes, string message, bool onlyStaged, bool keepIndex)
|
||||
public bool Push(string message, bool includeUntracked = true, bool keepIndex = false)
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
builder.Append("stash push ");
|
||||
if (onlyStaged)
|
||||
builder.Append("--staged ");
|
||||
if (includeUntracked)
|
||||
builder.Append("--include-untracked ");
|
||||
if (keepIndex)
|
||||
builder.Append("--keep-index ");
|
||||
builder.Append("-m \"");
|
||||
builder.Append(message);
|
||||
builder.Append("\"");
|
||||
|
||||
Args = builder.ToString();
|
||||
return Exec();
|
||||
}
|
||||
|
||||
public bool Push(string message, List<Models.Change> changes, bool keepIndex)
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
builder.Append("stash push --include-untracked ");
|
||||
if (keepIndex)
|
||||
builder.Append("--keep-index ");
|
||||
builder.Append("-m \"");
|
||||
builder.Append(message);
|
||||
builder.Append("\" -- ");
|
||||
|
||||
if (onlyStaged)
|
||||
{
|
||||
foreach (var c in changes)
|
||||
builder.Append($"\"{c.Path}\" ");
|
||||
}
|
||||
else
|
||||
{
|
||||
var needAdd = new List<Models.Change>();
|
||||
foreach (var c in changes)
|
||||
{
|
||||
builder.Append($"\"{c.Path}\" ");
|
||||
|
||||
if (c.WorkTree == Models.ChangeState.Added || c.WorkTree == Models.ChangeState.Untracked)
|
||||
{
|
||||
needAdd.Add(c);
|
||||
if (needAdd.Count > 10)
|
||||
{
|
||||
new Add(WorkingDirectory, needAdd).Exec();
|
||||
needAdd.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (needAdd.Count > 0)
|
||||
{
|
||||
new Add(WorkingDirectory, needAdd).Exec();
|
||||
needAdd.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
Args = builder.ToString();
|
||||
return Exec();
|
||||
}
|
||||
|
||||
public bool Apply(string name)
|
||||
public bool Push(string message, string pathspecFromFile, bool keepIndex)
|
||||
{
|
||||
Args = $"stash apply -q {name}";
|
||||
var builder = new StringBuilder();
|
||||
builder.Append("stash push --include-untracked --pathspec-from-file=\"");
|
||||
builder.Append(pathspecFromFile);
|
||||
builder.Append("\" ");
|
||||
if (keepIndex)
|
||||
builder.Append("--keep-index ");
|
||||
builder.Append("-m \"");
|
||||
builder.Append(message);
|
||||
builder.Append("\"");
|
||||
|
||||
Args = builder.ToString();
|
||||
return Exec();
|
||||
}
|
||||
|
||||
public bool PushOnlyStaged(string message, bool keepIndex)
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
builder.Append("stash push --staged ");
|
||||
if (keepIndex)
|
||||
builder.Append("--keep-index ");
|
||||
builder.Append("-m \"");
|
||||
builder.Append(message);
|
||||
builder.Append("\"");
|
||||
Args = builder.ToString();
|
||||
return Exec();
|
||||
}
|
||||
|
||||
public bool Apply(string name, bool restoreIndex)
|
||||
{
|
||||
var opts = restoreIndex ? "--index" : string.Empty;
|
||||
Args = $"stash apply -q {opts} \"{name}\"";
|
||||
return Exec();
|
||||
}
|
||||
|
||||
public bool Pop(string name)
|
||||
{
|
||||
Args = $"stash pop -q {name}";
|
||||
Args = $"stash pop -q --index \"{name}\"";
|
||||
return Exec();
|
||||
}
|
||||
|
||||
public bool Drop(string name)
|
||||
{
|
||||
Args = $"stash drop -q {name}";
|
||||
Args = $"stash drop -q \"{name}\"";
|
||||
return Exec();
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
using System;
|
||||
using System;
|
||||
|
||||
namespace SourceGit.Commands
|
||||
{
|
||||
|
@ -8,7 +8,7 @@ namespace SourceGit.Commands
|
|||
{
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
Args = $"log --date-order --branches --remotes -{max} --pretty=format:\"%ct$%aN\"";
|
||||
Args = $"log --date-order --branches --remotes -{max} --format=%ct$%aN±%aE";
|
||||
}
|
||||
|
||||
public Models.Statistics Result()
|
||||
|
@ -40,7 +40,7 @@ namespace SourceGit.Commands
|
|||
if (dateEndIdx == -1)
|
||||
return;
|
||||
|
||||
var dateStr = line.Substring(0, dateEndIdx);
|
||||
var dateStr = line.AsSpan(0, dateEndIdx);
|
||||
if (double.TryParse(dateStr, out var date))
|
||||
statistics.AddCommit(line.Substring(dateEndIdx + 1), date);
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace SourceGit.Commands
|
||||
{
|
||||
|
@ -10,10 +11,9 @@ namespace SourceGit.Commands
|
|||
Context = repo;
|
||||
}
|
||||
|
||||
public bool Add(string url, string relativePath, bool recursive, Action<string> outputHandler)
|
||||
public bool Add(string url, string relativePath, bool recursive)
|
||||
{
|
||||
_outputHandler = outputHandler;
|
||||
Args = $"submodule add {url} \"{relativePath}\"";
|
||||
Args = $"-c protocol.file.allow=always submodule add \"{url}\" \"{relativePath}\"";
|
||||
if (!Exec())
|
||||
return false;
|
||||
|
||||
|
@ -29,38 +29,38 @@ namespace SourceGit.Commands
|
|||
}
|
||||
}
|
||||
|
||||
public bool Update(string module, bool init, bool recursive, bool useRemote, Action<string> outputHandler)
|
||||
public bool Update(List<string> modules, bool init, bool recursive, bool useRemote = false)
|
||||
{
|
||||
Args = "submodule update";
|
||||
var builder = new StringBuilder();
|
||||
builder.Append("submodule update");
|
||||
|
||||
if (init)
|
||||
Args += " --init";
|
||||
builder.Append(" --init");
|
||||
if (recursive)
|
||||
Args += " --recursive";
|
||||
builder.Append(" --recursive");
|
||||
if (useRemote)
|
||||
Args += " --remote";
|
||||
if (!string.IsNullOrEmpty(module))
|
||||
Args += $" -- \"{module}\"";
|
||||
builder.Append(" --remote");
|
||||
if (modules.Count > 0)
|
||||
{
|
||||
builder.Append(" --");
|
||||
foreach (var module in modules)
|
||||
builder.Append($" \"{module}\"");
|
||||
}
|
||||
|
||||
_outputHandler = outputHandler;
|
||||
Args = builder.ToString();
|
||||
return Exec();
|
||||
}
|
||||
|
||||
public bool Delete(string relativePath)
|
||||
public bool Deinit(string module, bool force)
|
||||
{
|
||||
Args = $"submodule deinit -f \"{relativePath}\"";
|
||||
if (!Exec())
|
||||
return false;
|
||||
|
||||
Args = $"rm -rf \"{relativePath}\"";
|
||||
Args = force ? $"submodule deinit -f -- \"{module}\"" : $"submodule deinit -- \"{module}\"";
|
||||
return Exec();
|
||||
}
|
||||
|
||||
protected override void OnReadline(string line)
|
||||
public bool Delete(string module)
|
||||
{
|
||||
_outputHandler?.Invoke(line);
|
||||
Args = $"rm -rf \"{module}\"";
|
||||
return Exec();
|
||||
}
|
||||
|
||||
private Action<string> _outputHandler;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,59 +1,51 @@
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.IO;
|
||||
|
||||
namespace SourceGit.Commands
|
||||
{
|
||||
public static class Tag
|
||||
{
|
||||
public static bool Add(string repo, string name, string basedOn)
|
||||
public static bool Add(string repo, string name, string basedOn, Models.ICommandLog log)
|
||||
{
|
||||
var cmd = new Command();
|
||||
cmd.WorkingDirectory = repo;
|
||||
cmd.Context = repo;
|
||||
cmd.Args = $"tag {name} {basedOn}";
|
||||
cmd.Args = $"tag --no-sign {name} {basedOn}";
|
||||
cmd.Log = log;
|
||||
return cmd.Exec();
|
||||
}
|
||||
|
||||
public static bool Add(string repo, string name, string basedOn, string message, bool sign)
|
||||
public static bool Add(string repo, string name, string basedOn, string message, bool sign, Models.ICommandLog log)
|
||||
{
|
||||
var param = sign ? "--sign -a" : "--no-sign -a";
|
||||
var cmd = new Command();
|
||||
cmd.WorkingDirectory = repo;
|
||||
cmd.Context = repo;
|
||||
cmd.Args = $"tag {param} {name} {basedOn} ";
|
||||
cmd.Log = log;
|
||||
|
||||
if (!string.IsNullOrEmpty(message))
|
||||
{
|
||||
string tmp = Path.GetTempFileName();
|
||||
File.WriteAllText(tmp, message);
|
||||
cmd.Args += $"-F \"{tmp}\"";
|
||||
}
|
||||
else
|
||||
{
|
||||
cmd.Args += $"-m {name}";
|
||||
|
||||
var succ = cmd.Exec();
|
||||
File.Delete(tmp);
|
||||
return succ;
|
||||
}
|
||||
|
||||
cmd.Args += $"-m {name}";
|
||||
return cmd.Exec();
|
||||
}
|
||||
|
||||
public static bool Delete(string repo, string name, List<Models.Remote> remotes)
|
||||
public static bool Delete(string repo, string name, Models.ICommandLog log)
|
||||
{
|
||||
var cmd = new Command();
|
||||
cmd.WorkingDirectory = repo;
|
||||
cmd.Context = repo;
|
||||
cmd.Args = $"tag --delete {name}";
|
||||
if (!cmd.Exec())
|
||||
return false;
|
||||
|
||||
if (remotes != null)
|
||||
{
|
||||
foreach (var r in remotes)
|
||||
{
|
||||
new Push(repo, r.Name, name, true).Exec();
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
cmd.Log = log;
|
||||
return cmd.Exec();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,23 +0,0 @@
|
|||
using System;
|
||||
|
||||
namespace SourceGit.Commands
|
||||
{
|
||||
public class UpdateRef : Command
|
||||
{
|
||||
public UpdateRef(string repo, string refName, string toRevision, Action<string> outputHandler)
|
||||
{
|
||||
_outputHandler = outputHandler;
|
||||
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
Args = $"update-ref {refName} {toRevision}";
|
||||
}
|
||||
|
||||
protected override void OnReadline(string line)
|
||||
{
|
||||
_outputHandler?.Invoke(line);
|
||||
}
|
||||
|
||||
private Action<string> _outputHandler;
|
||||
}
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
namespace SourceGit.Commands
|
||||
{
|
||||
public class Version : Command
|
||||
{
|
||||
public Version()
|
||||
{
|
||||
Args = "--version";
|
||||
RaiseError = false;
|
||||
}
|
||||
|
||||
public string Query()
|
||||
{
|
||||
var rs = ReadToEnd();
|
||||
if (!rs.IsSuccess || string.IsNullOrWhiteSpace(rs.StdOut))
|
||||
return string.Empty;
|
||||
return rs.StdOut.Trim().Substring("git version ".Length);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
namespace SourceGit.Commands
|
||||
{
|
||||
|
@ -20,12 +21,13 @@ namespace SourceGit.Commands
|
|||
var last = null as Models.Worktree;
|
||||
if (rs.IsSuccess)
|
||||
{
|
||||
var lines = rs.StdOut.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
var lines = rs.StdOut.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries);
|
||||
foreach (var line in lines)
|
||||
{
|
||||
if (line.StartsWith("worktree ", StringComparison.Ordinal))
|
||||
{
|
||||
last = new Models.Worktree() { FullPath = line.Substring(9).Trim() };
|
||||
last.RelativePath = Path.GetRelativePath(WorkingDirectory, last.FullPath);
|
||||
worktrees.Add(last);
|
||||
}
|
||||
else if (line.StartsWith("bare", StringComparison.Ordinal))
|
||||
|
@ -54,7 +56,7 @@ namespace SourceGit.Commands
|
|||
return worktrees;
|
||||
}
|
||||
|
||||
public bool Add(string fullpath, string name, bool createNew, string tracking, Action<string> outputHandler)
|
||||
public bool Add(string fullpath, string name, bool createNew, string tracking)
|
||||
{
|
||||
Args = "worktree add ";
|
||||
|
||||
|
@ -73,15 +75,15 @@ namespace SourceGit.Commands
|
|||
|
||||
if (!string.IsNullOrEmpty(tracking))
|
||||
Args += tracking;
|
||||
else if (!string.IsNullOrEmpty(name) && !createNew)
|
||||
Args += name;
|
||||
|
||||
_outputHandler = outputHandler;
|
||||
return Exec();
|
||||
}
|
||||
|
||||
public bool Prune(Action<string> outputHandler)
|
||||
public bool Prune()
|
||||
{
|
||||
Args = "worktree prune -v";
|
||||
_outputHandler = outputHandler;
|
||||
return Exec();
|
||||
}
|
||||
|
||||
|
@ -97,22 +99,14 @@ namespace SourceGit.Commands
|
|||
return Exec();
|
||||
}
|
||||
|
||||
public bool Remove(string fullpath, bool force, Action<string> outputHandler)
|
||||
public bool Remove(string fullpath, bool force)
|
||||
{
|
||||
if (force)
|
||||
Args = $"worktree remove -f \"{fullpath}\"";
|
||||
else
|
||||
Args = $"worktree remove \"{fullpath}\"";
|
||||
|
||||
_outputHandler = outputHandler;
|
||||
return Exec();
|
||||
}
|
||||
|
||||
protected override void OnReadline(string line)
|
||||
{
|
||||
_outputHandler?.Invoke(line);
|
||||
}
|
||||
|
||||
private Action<string> _outputHandler = null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using Avalonia.Data.Converters;
|
||||
using Avalonia.Media;
|
||||
|
||||
namespace SourceGit.Converters
|
||||
{
|
||||
|
@ -6,5 +7,8 @@ namespace SourceGit.Converters
|
|||
{
|
||||
public static readonly FuncValueConverter<bool, double> ToPageTabWidth =
|
||||
new FuncValueConverter<bool, double>(x => x ? 200 : double.NaN);
|
||||
|
||||
public static readonly FuncValueConverter<bool, FontWeight> IsBoldToFontWeight =
|
||||
new FuncValueConverter<bool, FontWeight>(x => x ? FontWeight.Bold : FontWeight.Normal);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,10 +23,10 @@ namespace SourceGit.Converters
|
|||
new FuncValueConverter<int, bool>(v => v != 1);
|
||||
|
||||
public static readonly FuncValueConverter<int, bool> IsSubjectLengthBad =
|
||||
new FuncValueConverter<int, bool>(v => v > ViewModels.Preference.Instance.SubjectGuideLength);
|
||||
new FuncValueConverter<int, bool>(v => v > ViewModels.Preferences.Instance.SubjectGuideLength);
|
||||
|
||||
public static readonly FuncValueConverter<int, bool> IsSubjectLengthGood =
|
||||
new FuncValueConverter<int, bool>(v => v <= ViewModels.Preference.Instance.SubjectGuideLength);
|
||||
new FuncValueConverter<int, bool>(v => v <= ViewModels.Preferences.Instance.SubjectGuideLength);
|
||||
|
||||
public static readonly FuncValueConverter<int, Thickness> ToTreeMargin =
|
||||
new FuncValueConverter<int, Thickness>(v => new Thickness(v * 16, 0, 0, 0));
|
||||
|
|
|
@ -7,8 +7,11 @@ namespace SourceGit.Converters
|
|||
{
|
||||
public static class ListConverters
|
||||
{
|
||||
public static readonly FuncValueConverter<IList, string> Count =
|
||||
new FuncValueConverter<IList, string>(v => v == null ? "0" : $"{v.Count}");
|
||||
|
||||
public static readonly FuncValueConverter<IList, string> ToCount =
|
||||
new FuncValueConverter<IList, string>(v => v == null ? " (0)" : $" ({v.Count})");
|
||||
new FuncValueConverter<IList, string>(v => v == null ? "(0)" : $"({v.Count})");
|
||||
|
||||
public static readonly FuncValueConverter<IList, bool> IsNullOrEmpty =
|
||||
new FuncValueConverter<IList, bool>(v => v == null || v.Count == 0);
|
||||
|
|
27
src/Converters/ObjectConverters.cs
Normal file
27
src/Converters/ObjectConverters.cs
Normal file
|
@ -0,0 +1,27 @@
|
|||
using System;
|
||||
using System.Globalization;
|
||||
using Avalonia.Data.Converters;
|
||||
|
||||
namespace SourceGit.Converters
|
||||
{
|
||||
public static class ObjectConverters
|
||||
{
|
||||
public class IsTypeOfConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
if (value == null || parameter == null)
|
||||
return false;
|
||||
|
||||
return value.GetType().IsAssignableTo((Type)parameter);
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
return new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
public static readonly IsTypeOfConverter IsTypeOf = new IsTypeOfConverter();
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
using System;
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
using Avalonia.Data.Converters;
|
||||
|
@ -22,7 +22,7 @@ namespace SourceGit.Converters
|
|||
var home = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
|
||||
var prefixLen = home.EndsWith('/') ? home.Length - 1 : home.Length;
|
||||
if (v.StartsWith(home, StringComparison.Ordinal))
|
||||
return "~" + v.Substring(prefixLen);
|
||||
return $"~{v.AsSpan(prefixLen)}";
|
||||
|
||||
return v;
|
||||
});
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
using System;
|
||||
using System.Globalization;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
using Avalonia.Data.Converters;
|
||||
using Avalonia.Styling;
|
||||
|
||||
namespace SourceGit.Converters
|
||||
{
|
||||
public static partial class StringConverters
|
||||
public static class StringConverters
|
||||
{
|
||||
public class ToLocaleConverter : IValueConverter
|
||||
{
|
||||
|
@ -68,22 +67,6 @@ namespace SourceGit.Converters
|
|||
public static readonly FuncValueConverter<string, string> ToShortSHA =
|
||||
new FuncValueConverter<string, string>(v => v == null ? string.Empty : (v.Length > 10 ? v.Substring(0, 10) : v));
|
||||
|
||||
public static readonly FuncValueConverter<string, bool> UnderRecommendGitVersion =
|
||||
new(v =>
|
||||
{
|
||||
var match = REG_GIT_VERSION().Match(v ?? "");
|
||||
if (match.Success)
|
||||
{
|
||||
var major = int.Parse(match.Groups[1].Value);
|
||||
var minor = int.Parse(match.Groups[2].Value);
|
||||
var build = int.Parse(match.Groups[3].Value);
|
||||
|
||||
return new Version(major, minor, build) < MINIMAL_GIT_VERSION;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
public static readonly FuncValueConverter<string, string> TrimRefsPrefix =
|
||||
new FuncValueConverter<string, string>(v =>
|
||||
{
|
||||
|
@ -96,9 +79,10 @@ namespace SourceGit.Converters
|
|||
return v;
|
||||
});
|
||||
|
||||
[GeneratedRegex(@"^[\s\w]*(\d+)\.(\d+)[\.\-](\d+).*$")]
|
||||
private static partial Regex REG_GIT_VERSION();
|
||||
public static readonly FuncValueConverter<string, bool> ContainsSpaces =
|
||||
new FuncValueConverter<string, bool>(v => v != null && v.Contains(' '));
|
||||
|
||||
private static readonly Version MINIMAL_GIT_VERSION = new Version(2, 23, 0);
|
||||
public static readonly FuncValueConverter<string, bool> IsNotNullOrWhitespace =
|
||||
new FuncValueConverter<string, bool>(v => v != null && v.Trim().Length > 0);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,14 +2,22 @@
|
|||
{
|
||||
public class ApplyWhiteSpaceMode
|
||||
{
|
||||
public static readonly ApplyWhiteSpaceMode[] Supported =
|
||||
[
|
||||
new ApplyWhiteSpaceMode("No Warn", "Turns off the trailing whitespace warning", "nowarn"),
|
||||
new ApplyWhiteSpaceMode("Warn", "Outputs warnings for a few such errors, but applies", "warn"),
|
||||
new ApplyWhiteSpaceMode("Error", "Raise errors and refuses to apply the patch", "error"),
|
||||
new ApplyWhiteSpaceMode("Error All", "Similar to 'error', but shows more", "error-all"),
|
||||
];
|
||||
|
||||
public string Name { get; set; }
|
||||
public string Desc { get; set; }
|
||||
public string Arg { get; set; }
|
||||
|
||||
public ApplyWhiteSpaceMode(string n, string d, string a)
|
||||
{
|
||||
Name = App.Text(n);
|
||||
Desc = App.Text(d);
|
||||
Name = n;
|
||||
Desc = d;
|
||||
Arg = a;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
|
@ -17,7 +17,7 @@ namespace SourceGit.Models
|
|||
{
|
||||
public interface IAvatarHost
|
||||
{
|
||||
void OnAvatarResourceChanged(string email);
|
||||
void OnAvatarResourceChanged(string email, Bitmap image);
|
||||
}
|
||||
|
||||
public partial class AvatarManager
|
||||
|
@ -35,7 +35,7 @@ namespace SourceGit.Models
|
|||
|
||||
private static AvatarManager _instance = null;
|
||||
|
||||
[GeneratedRegex(@"^(?:(\d+)\+)?(.+?)@users\.noreply\.github\.com$")]
|
||||
[GeneratedRegex(@"^(?:(\d+)\+)?(.+?)@.+\.github\.com$")]
|
||||
private static partial Regex REG_GITHUB_USER_EMAIL();
|
||||
|
||||
private object _synclock = new object();
|
||||
|
@ -43,6 +43,7 @@ namespace SourceGit.Models
|
|||
private List<IAvatarHost> _avatars = new List<IAvatarHost>();
|
||||
private Dictionary<string, Bitmap> _resources = new Dictionary<string, Bitmap>();
|
||||
private HashSet<string> _requesting = new HashSet<string>();
|
||||
private HashSet<string> _defaultAvatars = new HashSet<string>();
|
||||
|
||||
public void Start()
|
||||
{
|
||||
|
@ -50,8 +51,8 @@ namespace SourceGit.Models
|
|||
if (!Directory.Exists(_storePath))
|
||||
Directory.CreateDirectory(_storePath);
|
||||
|
||||
var icon = AssetLoader.Open(new Uri($"avares://SourceGit/Resources/Images/github.png", UriKind.RelativeOrAbsolute));
|
||||
_resources.Add("noreply@github.com", new Bitmap(icon));
|
||||
LoadDefaultAvatar("noreply@github.com", "github.png");
|
||||
LoadDefaultAvatar("unrealbot@epicgames.com", "unreal.png");
|
||||
|
||||
Task.Run(() =>
|
||||
{
|
||||
|
@ -118,7 +119,7 @@ namespace SourceGit.Models
|
|||
Dispatcher.UIThread.InvokeAsync(() =>
|
||||
{
|
||||
_resources[email] = img;
|
||||
NotifyResourceChanged(email);
|
||||
NotifyResourceChanged(email, img);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -140,7 +141,7 @@ namespace SourceGit.Models
|
|||
{
|
||||
if (forceRefetch)
|
||||
{
|
||||
if (email.Equals("noreply@github.com", StringComparison.Ordinal))
|
||||
if (_defaultAvatars.Contains(email))
|
||||
return null;
|
||||
|
||||
if (_resources.ContainsKey(email))
|
||||
|
@ -150,7 +151,7 @@ namespace SourceGit.Models
|
|||
if (File.Exists(localFile))
|
||||
File.Delete(localFile);
|
||||
|
||||
NotifyResourceChanged(email);
|
||||
NotifyResourceChanged(email, null);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -185,22 +186,58 @@ namespace SourceGit.Models
|
|||
return null;
|
||||
}
|
||||
|
||||
public void SetFromLocal(string email, string file)
|
||||
{
|
||||
try
|
||||
{
|
||||
Bitmap image = null;
|
||||
|
||||
using (var stream = File.OpenRead(file))
|
||||
{
|
||||
image = Bitmap.DecodeToWidth(stream, 128);
|
||||
}
|
||||
|
||||
if (image == null)
|
||||
return;
|
||||
|
||||
if (_resources.ContainsKey(email))
|
||||
_resources[email] = image;
|
||||
else
|
||||
_resources.Add(email, image);
|
||||
|
||||
_requesting.Remove(email);
|
||||
|
||||
var store = Path.Combine(_storePath, GetEmailHash(email));
|
||||
File.Copy(file, store, true);
|
||||
NotifyResourceChanged(email, image);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
private void LoadDefaultAvatar(string key, string img)
|
||||
{
|
||||
var icon = AssetLoader.Open(new Uri($"avares://SourceGit/Resources/Images/{img}", UriKind.RelativeOrAbsolute));
|
||||
_resources.Add(key, new Bitmap(icon));
|
||||
_defaultAvatars.Add(key);
|
||||
}
|
||||
|
||||
private string GetEmailHash(string email)
|
||||
{
|
||||
var lowered = email.ToLower(CultureInfo.CurrentCulture).Trim();
|
||||
var hash = MD5.Create().ComputeHash(Encoding.Default.GetBytes(lowered));
|
||||
var builder = new StringBuilder();
|
||||
var hash = MD5.HashData(Encoding.Default.GetBytes(lowered));
|
||||
var builder = new StringBuilder(hash.Length * 2);
|
||||
foreach (var c in hash)
|
||||
builder.Append(c.ToString("x2"));
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
private void NotifyResourceChanged(string email)
|
||||
private void NotifyResourceChanged(string email, Bitmap image)
|
||||
{
|
||||
foreach (var avatar in _avatars)
|
||||
{
|
||||
avatar.OnAvatarResourceChanged(email);
|
||||
}
|
||||
avatar.OnAvatarResourceChanged(email, image);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
35
src/Models/Bisect.cs
Normal file
35
src/Models/Bisect.cs
Normal file
|
@ -0,0 +1,35 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace SourceGit.Models
|
||||
{
|
||||
public enum BisectState
|
||||
{
|
||||
None = 0,
|
||||
WaitingForRange,
|
||||
Detecting,
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum BisectCommitFlag
|
||||
{
|
||||
None = 0,
|
||||
Good = 1 << 0,
|
||||
Bad = 1 << 1,
|
||||
}
|
||||
|
||||
public class Bisect
|
||||
{
|
||||
public HashSet<string> Bads
|
||||
{
|
||||
get;
|
||||
set;
|
||||
} = [];
|
||||
|
||||
public HashSet<string> Goods
|
||||
{
|
||||
get;
|
||||
set;
|
||||
} = [];
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue