mirror of
https://github.com/sourcegit-scm/sourcegit
synced 2025-06-25 12:24:59 +00:00
Compare commits
479 commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
190d2eec73 | ||
![]() |
bfb9d6b6bc | ||
![]() |
9d2f8b1555 | ||
![]() |
f59b34fe25 | ||
![]() |
6e4f35c4e1 | ||
![]() |
6ecdabc212 | ||
![]() |
8b902bd5c9 | ||
![]() |
73eccdb495 | ||
![]() |
9bfc315ace | ||
![]() |
64ffbb113f | ||
![]() |
221e964df0 | ||
![]() |
20daa584e3 | ||
![]() |
c5ad4b837d | ||
![]() |
957c52aac4 | ||
![]() |
8d74586970 | ||
![]() |
dcd8effc32 | ||
![]() |
af2b644792 | ||
![]() |
88fd8f32f1 | ||
![]() |
cadcf40d74 | ||
![]() |
fcf1107304 | ||
![]() |
e81674912c | ||
![]() |
6729d4e896 | ||
![]() |
f9f44ae9cb | ||
![]() |
c67e8e3c64 | ||
![]() |
5ec8ae1296 | ||
![]() |
2d91fed05e | ||
![]() |
3ac803d88c | ||
![]() |
94d25ee6c9 | ||
![]() |
3711399c59 | ||
![]() |
004022648c | ||
![]() |
240db2ea2f | ||
![]() |
5ca1fcfd8f | ||
![]() |
bad8904edc | ||
![]() |
86828b9711 | ||
![]() |
6d682ac409 | ||
![]() |
b06a4cbb8a | ||
![]() |
d404f6dbe2 | ||
![]() |
ae46728bbc | ||
![]() |
957fbbf54f | ||
![]() |
10569022d7 | ||
![]() |
90310a704d | ||
![]() |
a8da8c09ac | ||
![]() |
df7375313e | ||
![]() |
efa6e46471 | ||
![]() |
e102e49f45 | ||
![]() |
dcdc52592c | ||
![]() |
e28b537f89 | ||
![]() |
ed66d2337b | ||
![]() |
0cbf1215e0 | ||
![]() |
74d77ab704 | ||
![]() |
b617181fc5 | ||
![]() |
9dedcacb2f | ||
![]() |
b22733b565 | ||
![]() |
28844c59cf | ||
![]() |
f88652ffdd | ||
![]() |
8dffdef48d | ||
![]() |
158d926189 | ||
![]() |
99b7208a54 | ||
![]() |
05757ebf40 | ||
![]() |
cb6d6a233f | ||
![]() |
79650d1851 | ||
![]() |
7e2f3bec8c | ||
![]() |
7de5991ecb | ||
![]() |
ffac71b15f | ||
![]() |
35eda489be | ||
![]() |
f59851f454 | ||
![]() |
a128b67bd4 | ||
![]() |
196b454ae8 | ||
![]() |
5494093261 | ||
![]() |
c3c7d32167 | ||
![]() |
7c1a894525 | ||
![]() |
bcefb773c1 | ||
![]() |
aa1c8b1cc1 | ||
![]() |
5e303d43d4 | ||
![]() |
7d0536d94b | ||
![]() |
6c04f5390a | ||
![]() |
ee4d8a6a0e | ||
![]() |
0ea4021a64 | ||
![]() |
11a46dbc93 | ||
![]() |
69792b3262 | ||
![]() |
1b1dc2f666 | ||
![]() |
4302d7adb5 | ||
![]() |
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 |
367 changed files with 12902 additions and 5404 deletions
|
@ -71,20 +71,20 @@ dotnet_style_predefined_type_for_member_access = true:suggestion
|
||||||
|
|
||||||
# name all constant fields using PascalCase
|
# name all constant fields using PascalCase
|
||||||
dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = suggestion
|
dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = suggestion
|
||||||
dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields
|
dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields
|
||||||
dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style
|
dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style
|
||||||
|
|
||||||
dotnet_naming_symbols.constant_fields.applicable_kinds = field
|
dotnet_naming_symbols.constant_fields.applicable_kinds = field
|
||||||
dotnet_naming_symbols.constant_fields.required_modifiers = const
|
dotnet_naming_symbols.constant_fields.required_modifiers = const
|
||||||
|
|
||||||
dotnet_naming_style.pascal_case_style.capitalization = pascal_case
|
dotnet_naming_style.pascal_case_style.capitalization = pascal_case
|
||||||
|
|
||||||
# private static fields should have s_ prefix
|
# private static fields should have s_ prefix
|
||||||
dotnet_naming_rule.private_static_fields_should_have_prefix.severity = suggestion
|
dotnet_naming_rule.private_static_fields_should_have_prefix.severity = suggestion
|
||||||
dotnet_naming_rule.private_static_fields_should_have_prefix.symbols = private_static_fields
|
dotnet_naming_rule.private_static_fields_should_have_prefix.symbols = private_static_fields
|
||||||
dotnet_naming_rule.private_static_fields_should_have_prefix.style = private_static_prefix_style
|
dotnet_naming_rule.private_static_fields_should_have_prefix.style = private_static_prefix_style
|
||||||
|
|
||||||
dotnet_naming_symbols.private_static_fields.applicable_kinds = field
|
dotnet_naming_symbols.private_static_fields.applicable_kinds = field
|
||||||
dotnet_naming_symbols.private_static_fields.required_modifiers = static
|
dotnet_naming_symbols.private_static_fields.required_modifiers = static
|
||||||
dotnet_naming_symbols.private_static_fields.applicable_accessibilities = private
|
dotnet_naming_symbols.private_static_fields.applicable_accessibilities = private
|
||||||
|
|
||||||
|
@ -93,7 +93,7 @@ dotnet_naming_style.private_static_prefix_style.capitalization = camel_case
|
||||||
|
|
||||||
# internal and private fields should be _camelCase
|
# internal and private fields should be _camelCase
|
||||||
dotnet_naming_rule.camel_case_for_private_internal_fields.severity = suggestion
|
dotnet_naming_rule.camel_case_for_private_internal_fields.severity = suggestion
|
||||||
dotnet_naming_rule.camel_case_for_private_internal_fields.symbols = private_internal_fields
|
dotnet_naming_rule.camel_case_for_private_internal_fields.symbols = private_internal_fields
|
||||||
dotnet_naming_rule.camel_case_for_private_internal_fields.style = camel_case_underscore_style
|
dotnet_naming_rule.camel_case_for_private_internal_fields.style = camel_case_underscore_style
|
||||||
|
|
||||||
dotnet_naming_symbols.private_internal_fields.applicable_kinds = field
|
dotnet_naming_symbols.private_internal_fields.applicable_kinds = field
|
||||||
|
|
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
|
@ -58,7 +58,7 @@ jobs:
|
||||||
if: ${{ matrix.runtime == 'linux-arm64' }}
|
if: ${{ matrix.runtime == 'linux-arm64' }}
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get install -y llvm gcc-aarch64-linux-gnu zlib1g-dev:arm64
|
sudo apt-get install -y llvm gcc-aarch64-linux-gnu
|
||||||
- name: Build
|
- name: Build
|
||||||
run: dotnet build -c Release
|
run: dotnet build -c Release
|
||||||
- name: Publish
|
- name: Publish
|
||||||
|
|
5
.github/workflows/localization-check.yml
vendored
5
.github/workflows/localization-check.yml
vendored
|
@ -4,7 +4,6 @@ on:
|
||||||
branches: [ develop ]
|
branches: [ develop ]
|
||||||
paths:
|
paths:
|
||||||
- 'src/Resources/Locales/**'
|
- 'src/Resources/Locales/**'
|
||||||
- 'README.md'
|
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
workflow_call:
|
workflow_call:
|
||||||
|
|
||||||
|
@ -32,8 +31,8 @@ jobs:
|
||||||
git config --global user.name 'github-actions[bot]'
|
git config --global user.name 'github-actions[bot]'
|
||||||
git config --global user.email 'github-actions[bot]@users.noreply.github.com'
|
git config --global user.email 'github-actions[bot]@users.noreply.github.com'
|
||||||
if [ -n "$(git status --porcelain)" ]; then
|
if [ -n "$(git status --porcelain)" ]; then
|
||||||
git add README.md TRANSLATION.md
|
git add TRANSLATION.md src/Resources/Locales/*.axaml
|
||||||
git commit -m 'doc: Update translation status and missing keys'
|
git commit -m 'doc: Update translation status and sort locale files'
|
||||||
git push
|
git push
|
||||||
else
|
else
|
||||||
echo "No changes to commit"
|
echo "No changes to commit"
|
||||||
|
|
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -37,3 +37,5 @@ build/*.deb
|
||||||
build/*.rpm
|
build/*.rpm
|
||||||
build/*.AppImage
|
build/*.AppImage
|
||||||
SourceGit.app/
|
SourceGit.app/
|
||||||
|
build.command
|
||||||
|
src/Properties/launchSettings.json
|
||||||
|
|
10
README.md
10
README.md
|
@ -11,7 +11,7 @@
|
||||||
* Supports Windows/macOS/Linux
|
* Supports Windows/macOS/Linux
|
||||||
* Opensource/Free
|
* Opensource/Free
|
||||||
* Fast
|
* Fast
|
||||||
* Deutsch/English/Español/Français/Italiano/Português/Русский/简体中文/繁體中文/日本語/தமிழ் (Tamil)
|
* Deutsch/English/Español/Français/Italiano/Português/Русский/Українська/简体中文/繁體中文/日本語/தமிழ் (Tamil)
|
||||||
* Built-in light/dark themes
|
* Built-in light/dark themes
|
||||||
* Customize theme
|
* Customize theme
|
||||||
* Visual commit graph
|
* Visual commit graph
|
||||||
|
@ -35,9 +35,11 @@
|
||||||
* Revision Diffs
|
* Revision Diffs
|
||||||
* Branch Diff
|
* Branch Diff
|
||||||
* Image Diff - Side-By-Side/Swipe/Blend
|
* Image Diff - Side-By-Side/Swipe/Blend
|
||||||
|
* Git command logs
|
||||||
* Search commits
|
* Search commits
|
||||||
* GitFlow
|
* GitFlow
|
||||||
* Git LFS
|
* Git LFS
|
||||||
|
* Bisect
|
||||||
* Issue Link
|
* Issue Link
|
||||||
* Workspace
|
* Workspace
|
||||||
* Custom Action
|
* Custom Action
|
||||||
|
@ -52,9 +54,9 @@ You can find the current translation status in [TRANSLATION.md](https://github.c
|
||||||
|
|
||||||
## How to Use
|
## 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.
|
This software creates a folder `$"{System.Environment.SpecialFolder.ApplicationData}/SourceGit"`, which is platform-dependent, to store user settings, downloaded avatars and crash logs.
|
||||||
|
|
||||||
|
@ -91,7 +93,7 @@ For **macOS** users:
|
||||||
brew tap ybeapps/homebrew-sourcegit
|
brew tap ybeapps/homebrew-sourcegit
|
||||||
brew install --cask --no-quarantine 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
|
```shell
|
||||||
sudo xattr -cr /Applications/SourceGit.app
|
sudo xattr -cr /Applications/SourceGit.app
|
||||||
```
|
```
|
||||||
|
|
419
TRANSLATION.md
419
TRANSLATION.md
|
@ -6,111 +6,220 @@ This document shows the translation status of each locale file in the repository
|
||||||
|
|
||||||
### 
|
### 
|
||||||
|
|
||||||
### 
|
### 
|
||||||
|
|
||||||
<details>
|
### 
|
||||||
<summary>Missing keys in de_DE.axaml</summary>
|
|
||||||
|
|
||||||
- Text.BranchUpstreamInvalid
|
|
||||||
- Text.Configure.CustomAction.WaitForExit
|
|
||||||
- Text.Configure.Git.PreferredMergeMode
|
|
||||||
- Text.Configure.IssueTracker.AddSampleAzure
|
|
||||||
- Text.ConfirmEmptyCommit.Continue
|
|
||||||
- Text.ConfirmEmptyCommit.NoLocalChanges
|
|
||||||
- Text.ConfirmEmptyCommit.StageAllThenCommit
|
|
||||||
- Text.ConfirmEmptyCommit.WithLocalChanges
|
|
||||||
- Text.CopyFullPath
|
|
||||||
- Text.Diff.First
|
|
||||||
- Text.Diff.Last
|
|
||||||
- Text.Preferences.AI.Streaming
|
|
||||||
- Text.Preferences.Appearance.EditorTabWidth
|
|
||||||
- Text.Preferences.General.ShowTagsInGraph
|
|
||||||
- Text.StashCM.SaveAsPatch
|
|
||||||
- Text.WorkingCopy.ConfirmCommitWithFilter
|
|
||||||
- Text.WorkingCopy.Conflicts.OpenExternalMergeTool
|
|
||||||
- Text.WorkingCopy.Conflicts.OpenExternalMergeToolAllConflicts
|
|
||||||
- Text.WorkingCopy.Conflicts.UseMine
|
|
||||||
- Text.WorkingCopy.Conflicts.UseTheirs
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
### 
|
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary>Missing keys in es_ES.axaml</summary>
|
<summary>Missing keys in es_ES.axaml</summary>
|
||||||
|
|
||||||
- Text.Configure.Git.PreferredMergeMode
|
- Text.CommitCM.PushRevision
|
||||||
- Text.ConfirmEmptyCommit.Continue
|
- Text.Merge.Edit
|
||||||
- Text.ConfirmEmptyCommit.NoLocalChanges
|
- Text.Push.Revision
|
||||||
- Text.ConfirmEmptyCommit.StageAllThenCommit
|
- Text.Push.Revision.Title
|
||||||
- Text.ConfirmEmptyCommit.WithLocalChanges
|
- Text.Stash.Mode
|
||||||
- Text.WorkingCopy.ConfirmCommitWithFilter
|
- Text.StashCM.CopyMessage
|
||||||
- Text.WorkingCopy.Conflicts.OpenExternalMergeTool
|
- Text.WorkingCopy.AddToGitIgnore.InFolder
|
||||||
- Text.WorkingCopy.Conflicts.OpenExternalMergeToolAllConflicts
|
|
||||||
- Text.WorkingCopy.Conflicts.UseMine
|
|
||||||
- Text.WorkingCopy.Conflicts.UseTheirs
|
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
### 
|
### 
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary>Missing keys in fr_FR.axaml</summary>
|
<summary>Missing keys in fr_FR.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.ResetToSelectedCommit
|
||||||
|
- Text.Checkout.RecurseSubmodules
|
||||||
|
- Text.Checkout.WithFastForward
|
||||||
|
- Text.Checkout.WithFastForward.Upstream
|
||||||
|
- Text.CommitCM.CopyAuthor
|
||||||
|
- Text.CommitCM.CopyCommitter
|
||||||
|
- Text.CommitCM.CopySubject
|
||||||
|
- Text.CommitCM.PushRevision
|
||||||
|
- Text.CommitDetail.Changes.Count
|
||||||
|
- Text.CommitMessageTextBox.SubjectCount
|
||||||
- Text.Configure.Git.PreferredMergeMode
|
- Text.Configure.Git.PreferredMergeMode
|
||||||
- Text.ConfirmEmptyCommit.Continue
|
- Text.ConfirmEmptyCommit.Continue
|
||||||
- Text.ConfirmEmptyCommit.NoLocalChanges
|
- Text.ConfirmEmptyCommit.NoLocalChanges
|
||||||
- Text.ConfirmEmptyCommit.StageAllThenCommit
|
- Text.ConfirmEmptyCommit.StageAllThenCommit
|
||||||
- Text.ConfirmEmptyCommit.WithLocalChanges
|
- 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.Merge.Edit
|
||||||
|
- Text.Preferences.Git.IgnoreCRAtEOLInDiff
|
||||||
|
- Text.Pull.RecurseSubmodules
|
||||||
|
- Text.Push.Revision
|
||||||
|
- Text.Push.Revision.Title
|
||||||
|
- 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.Stash.Mode
|
||||||
|
- Text.StashCM.CopyMessage
|
||||||
|
- 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.AddToGitIgnore.InFolder
|
||||||
- Text.WorkingCopy.ConfirmCommitWithFilter
|
- Text.WorkingCopy.ConfirmCommitWithFilter
|
||||||
- Text.WorkingCopy.Conflicts.OpenExternalMergeTool
|
- Text.WorkingCopy.Conflicts.OpenExternalMergeTool
|
||||||
- Text.WorkingCopy.Conflicts.OpenExternalMergeToolAllConflicts
|
- Text.WorkingCopy.Conflicts.OpenExternalMergeToolAllConflicts
|
||||||
- Text.WorkingCopy.Conflicts.UseMine
|
- Text.WorkingCopy.Conflicts.UseMine
|
||||||
- Text.WorkingCopy.Conflicts.UseTheirs
|
- Text.WorkingCopy.Conflicts.UseTheirs
|
||||||
|
- Text.WorkingCopy.ResetAuthor
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
### 
|
### 
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary>Missing keys in it_IT.axaml</summary>
|
<summary>Missing keys in it_IT.axaml</summary>
|
||||||
|
|
||||||
- Text.Configure.Git.PreferredMergeMode
|
- Text.Avatar.Load
|
||||||
- Text.ConfirmEmptyCommit.Continue
|
- Text.BranchCM.ResetToSelectedCommit
|
||||||
- Text.ConfirmEmptyCommit.NoLocalChanges
|
- Text.Checkout.WithFastForward
|
||||||
- Text.ConfirmEmptyCommit.StageAllThenCommit
|
- Text.Checkout.WithFastForward.Upstream
|
||||||
- Text.ConfirmEmptyCommit.WithLocalChanges
|
- Text.CommitCM.PushRevision
|
||||||
- Text.CopyFullPath
|
- Text.CommitDetail.Changes.Count
|
||||||
- Text.Preferences.General.ShowTagsInGraph
|
- Text.CreateBranch.OverwriteExisting
|
||||||
- Text.WorkingCopy.ConfirmCommitWithFilter
|
- Text.DeinitSubmodule
|
||||||
- Text.WorkingCopy.Conflicts.OpenExternalMergeTool
|
- Text.DeinitSubmodule.Force
|
||||||
- Text.WorkingCopy.Conflicts.OpenExternalMergeToolAllConflicts
|
- Text.DeinitSubmodule.Path
|
||||||
- Text.WorkingCopy.Conflicts.UseMine
|
- Text.Diff.Submodule.Deleted
|
||||||
- Text.WorkingCopy.Conflicts.UseTheirs
|
- Text.Hotkeys.Global.SwitchWorkspace
|
||||||
|
- Text.Hotkeys.Global.SwitchTab
|
||||||
|
- Text.Launcher.Workspaces
|
||||||
|
- Text.Launcher.Pages
|
||||||
|
- Text.Merge.Edit
|
||||||
|
- Text.Pull.RecurseSubmodules
|
||||||
|
- Text.Push.Revision
|
||||||
|
- Text.Push.Revision.Title
|
||||||
|
- Text.Repository.ClearStashes
|
||||||
|
- Text.ResetWithoutCheckout
|
||||||
|
- Text.ResetWithoutCheckout.MoveTo
|
||||||
|
- Text.ResetWithoutCheckout.Target
|
||||||
|
- Text.Stash.Mode
|
||||||
|
- Text.StashCM.CopyMessage
|
||||||
|
- Text.Submodule.Deinit
|
||||||
|
- Text.WorkingCopy.AddToGitIgnore.InFolder
|
||||||
|
- Text.WorkingCopy.ResetAuthor
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
### 
|
### 
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary>Missing keys in ja_JP.axaml</summary>
|
<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.Checkout.WithFastForward
|
||||||
|
- Text.Checkout.WithFastForward.Upstream
|
||||||
|
- Text.CommitCM.CopyAuthor
|
||||||
|
- Text.CommitCM.CopyCommitter
|
||||||
|
- Text.CommitCM.CopySubject
|
||||||
|
- Text.CommitCM.PushRevision
|
||||||
|
- Text.CommitDetail.Changes.Count
|
||||||
|
- Text.CommitMessageTextBox.SubjectCount
|
||||||
- Text.Configure.Git.PreferredMergeMode
|
- Text.Configure.Git.PreferredMergeMode
|
||||||
- Text.ConfirmEmptyCommit.Continue
|
- Text.ConfirmEmptyCommit.Continue
|
||||||
- Text.ConfirmEmptyCommit.NoLocalChanges
|
- Text.ConfirmEmptyCommit.NoLocalChanges
|
||||||
- Text.ConfirmEmptyCommit.StageAllThenCommit
|
- Text.ConfirmEmptyCommit.StageAllThenCommit
|
||||||
- Text.ConfirmEmptyCommit.WithLocalChanges
|
- 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.Merge.Edit
|
||||||
|
- Text.Preferences.Git.IgnoreCRAtEOLInDiff
|
||||||
|
- Text.Pull.RecurseSubmodules
|
||||||
|
- Text.Push.Revision
|
||||||
|
- Text.Push.Revision.Title
|
||||||
|
- Text.Repository.BranchSort
|
||||||
|
- Text.Repository.BranchSort.ByCommitterDate
|
||||||
|
- Text.Repository.BranchSort.ByName
|
||||||
|
- Text.Repository.ClearStashes
|
||||||
- Text.Repository.FilterCommits
|
- Text.Repository.FilterCommits
|
||||||
- Text.Repository.Tags.OrderByNameDes
|
- Text.Repository.Search.ByContent
|
||||||
|
- Text.Repository.ShowSubmodulesAsTree
|
||||||
|
- Text.Repository.ViewLogs
|
||||||
|
- Text.Repository.Visit
|
||||||
|
- Text.ResetWithoutCheckout
|
||||||
|
- Text.ResetWithoutCheckout.MoveTo
|
||||||
|
- Text.ResetWithoutCheckout.Target
|
||||||
|
- Text.Stash.Mode
|
||||||
|
- Text.StashCM.CopyMessage
|
||||||
|
- 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.AddToGitIgnore.InFolder
|
||||||
- Text.WorkingCopy.ConfirmCommitWithFilter
|
- Text.WorkingCopy.ConfirmCommitWithFilter
|
||||||
- Text.WorkingCopy.Conflicts.OpenExternalMergeTool
|
- Text.WorkingCopy.Conflicts.OpenExternalMergeTool
|
||||||
- Text.WorkingCopy.Conflicts.OpenExternalMergeToolAllConflicts
|
- Text.WorkingCopy.Conflicts.OpenExternalMergeToolAllConflicts
|
||||||
- Text.WorkingCopy.Conflicts.UseMine
|
- Text.WorkingCopy.Conflicts.UseMine
|
||||||
- Text.WorkingCopy.Conflicts.UseTheirs
|
- Text.WorkingCopy.Conflicts.UseTheirs
|
||||||
|
- Text.WorkingCopy.ResetAuthor
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
### 
|
### 
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary>Missing keys in pt_BR.axaml</summary>
|
<summary>Missing keys in pt_BR.axaml</summary>
|
||||||
|
@ -121,14 +230,32 @@ This document shows the translation status of each locale file in the repository
|
||||||
- Text.ApplyStash.DropAfterApply
|
- Text.ApplyStash.DropAfterApply
|
||||||
- Text.ApplyStash.RestoreIndex
|
- Text.ApplyStash.RestoreIndex
|
||||||
- Text.ApplyStash.Stash
|
- 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.CustomAction
|
||||||
- Text.BranchCM.MergeMultiBranches
|
- Text.BranchCM.MergeMultiBranches
|
||||||
|
- Text.BranchCM.ResetToSelectedCommit
|
||||||
- Text.BranchUpstreamInvalid
|
- Text.BranchUpstreamInvalid
|
||||||
|
- Text.Checkout.RecurseSubmodules
|
||||||
|
- Text.Checkout.WithFastForward
|
||||||
|
- Text.Checkout.WithFastForward.Upstream
|
||||||
- Text.Clone.RecurseSubmodules
|
- Text.Clone.RecurseSubmodules
|
||||||
|
- Text.CommitCM.CopyAuthor
|
||||||
|
- Text.CommitCM.CopyCommitter
|
||||||
|
- Text.CommitCM.CopySubject
|
||||||
- Text.CommitCM.Merge
|
- Text.CommitCM.Merge
|
||||||
- Text.CommitCM.MergeMultiple
|
- Text.CommitCM.MergeMultiple
|
||||||
|
- Text.CommitCM.PushRevision
|
||||||
|
- Text.CommitDetail.Changes.Count
|
||||||
- Text.CommitDetail.Files.Search
|
- Text.CommitDetail.Files.Search
|
||||||
- Text.CommitDetail.Info.Children
|
- Text.CommitDetail.Info.Children
|
||||||
|
- Text.CommitMessageTextBox.SubjectCount
|
||||||
- Text.Configure.CustomAction.Scope.Branch
|
- Text.Configure.CustomAction.Scope.Branch
|
||||||
- Text.Configure.CustomAction.WaitForExit
|
- Text.Configure.CustomAction.WaitForExit
|
||||||
- Text.Configure.Git.PreferredMergeMode
|
- Text.Configure.Git.PreferredMergeMode
|
||||||
|
@ -140,19 +267,32 @@ This document shows the translation status of each locale file in the repository
|
||||||
- Text.ConfirmEmptyCommit.WithLocalChanges
|
- Text.ConfirmEmptyCommit.WithLocalChanges
|
||||||
- Text.CopyFullPath
|
- Text.CopyFullPath
|
||||||
- Text.CreateBranch.Name.WarnSpace
|
- Text.CreateBranch.Name.WarnSpace
|
||||||
|
- Text.CreateBranch.OverwriteExisting
|
||||||
|
- Text.DeinitSubmodule
|
||||||
|
- Text.DeinitSubmodule.Force
|
||||||
|
- Text.DeinitSubmodule.Path
|
||||||
- Text.DeleteRepositoryNode.Path
|
- Text.DeleteRepositoryNode.Path
|
||||||
- Text.DeleteRepositoryNode.TipForGroup
|
- Text.DeleteRepositoryNode.TipForGroup
|
||||||
- Text.DeleteRepositoryNode.TipForRepository
|
- Text.DeleteRepositoryNode.TipForRepository
|
||||||
- Text.Diff.First
|
- Text.Diff.First
|
||||||
- Text.Diff.Last
|
- Text.Diff.Last
|
||||||
|
- Text.Diff.Submodule.Deleted
|
||||||
- Text.Diff.UseBlockNavigation
|
- Text.Diff.UseBlockNavigation
|
||||||
- Text.Fetch.Force
|
- Text.Fetch.Force
|
||||||
- Text.FileCM.ResolveUsing
|
- Text.FileCM.ResolveUsing
|
||||||
|
- Text.GitFlow.FinishWithPush
|
||||||
|
- Text.GitFlow.FinishWithSquash
|
||||||
- Text.Hotkeys.Global.Clone
|
- Text.Hotkeys.Global.Clone
|
||||||
|
- Text.Hotkeys.Global.SwitchWorkspace
|
||||||
|
- Text.Hotkeys.Global.SwitchTab
|
||||||
|
- Text.Hotkeys.TextEditor.OpenExternalMergeTool
|
||||||
- Text.InProgress.CherryPick.Head
|
- Text.InProgress.CherryPick.Head
|
||||||
- Text.InProgress.Merge.Operating
|
- Text.InProgress.Merge.Operating
|
||||||
- Text.InProgress.Rebase.StoppedAt
|
- Text.InProgress.Rebase.StoppedAt
|
||||||
- Text.InProgress.Revert.Head
|
- Text.InProgress.Revert.Head
|
||||||
|
- Text.Launcher.Workspaces
|
||||||
|
- Text.Launcher.Pages
|
||||||
|
- Text.Merge.Edit
|
||||||
- Text.Merge.Source
|
- Text.Merge.Source
|
||||||
- Text.MergeMultiple
|
- Text.MergeMultiple
|
||||||
- Text.MergeMultiple.CommitChanges
|
- Text.MergeMultiple.CommitChanges
|
||||||
|
@ -163,7 +303,15 @@ This document shows the translation status of each locale file in the repository
|
||||||
- Text.Preferences.General.DateFormat
|
- Text.Preferences.General.DateFormat
|
||||||
- Text.Preferences.General.ShowChildren
|
- Text.Preferences.General.ShowChildren
|
||||||
- Text.Preferences.General.ShowTagsInGraph
|
- Text.Preferences.General.ShowTagsInGraph
|
||||||
|
- Text.Preferences.Git.IgnoreCRAtEOLInDiff
|
||||||
- Text.Preferences.Git.SSLVerify
|
- Text.Preferences.Git.SSLVerify
|
||||||
|
- Text.Pull.RecurseSubmodules
|
||||||
|
- Text.Push.Revision
|
||||||
|
- Text.Push.Revision.Title
|
||||||
|
- Text.Repository.BranchSort
|
||||||
|
- Text.Repository.BranchSort.ByCommitterDate
|
||||||
|
- Text.Repository.BranchSort.ByName
|
||||||
|
- Text.Repository.ClearStashes
|
||||||
- Text.Repository.FilterCommits
|
- Text.Repository.FilterCommits
|
||||||
- Text.Repository.HistoriesLayout
|
- Text.Repository.HistoriesLayout
|
||||||
- Text.Repository.HistoriesLayout.Horizontal
|
- Text.Repository.HistoriesLayout.Horizontal
|
||||||
|
@ -171,47 +319,198 @@ This document shows the translation status of each locale file in the repository
|
||||||
- Text.Repository.HistoriesOrder
|
- Text.Repository.HistoriesOrder
|
||||||
- Text.Repository.Notifications.Clear
|
- Text.Repository.Notifications.Clear
|
||||||
- Text.Repository.OnlyHighlightCurrentBranchInHistories
|
- Text.Repository.OnlyHighlightCurrentBranchInHistories
|
||||||
|
- Text.Repository.Search.ByContent
|
||||||
|
- Text.Repository.ShowSubmodulesAsTree
|
||||||
- Text.Repository.Skip
|
- Text.Repository.Skip
|
||||||
- Text.Repository.Tags.OrderByCreatorDate
|
- Text.Repository.Tags.OrderByCreatorDate
|
||||||
- Text.Repository.Tags.OrderByNameAsc
|
- Text.Repository.Tags.OrderByName
|
||||||
- Text.Repository.Tags.OrderByNameDes
|
|
||||||
- Text.Repository.Tags.Sort
|
- Text.Repository.Tags.Sort
|
||||||
- Text.Repository.UseRelativeTimeInHistories
|
- Text.Repository.UseRelativeTimeInHistories
|
||||||
|
- Text.Repository.ViewLogs
|
||||||
|
- Text.Repository.Visit
|
||||||
|
- Text.ResetWithoutCheckout
|
||||||
|
- Text.ResetWithoutCheckout.MoveTo
|
||||||
|
- Text.ResetWithoutCheckout.Target
|
||||||
- Text.SetUpstream
|
- Text.SetUpstream
|
||||||
- Text.SetUpstream.Local
|
- Text.SetUpstream.Local
|
||||||
- Text.SetUpstream.Unset
|
- Text.SetUpstream.Unset
|
||||||
- Text.SetUpstream.Upstream
|
- Text.SetUpstream.Upstream
|
||||||
- Text.SHALinkCM.NavigateTo
|
- Text.SHALinkCM.NavigateTo
|
||||||
- Text.Stash.AutoRestore
|
- Text.Stash.Mode
|
||||||
- Text.Stash.AutoRestore.Tip
|
- Text.StashCM.CopyMessage
|
||||||
- Text.StashCM.SaveAsPatch
|
- 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.AddToGitIgnore.InFolder
|
||||||
- Text.WorkingCopy.CommitToEdit
|
- Text.WorkingCopy.CommitToEdit
|
||||||
- Text.WorkingCopy.ConfirmCommitWithFilter
|
- Text.WorkingCopy.ConfirmCommitWithFilter
|
||||||
- Text.WorkingCopy.Conflicts.OpenExternalMergeTool
|
- Text.WorkingCopy.Conflicts.OpenExternalMergeTool
|
||||||
- Text.WorkingCopy.Conflicts.OpenExternalMergeToolAllConflicts
|
- Text.WorkingCopy.Conflicts.OpenExternalMergeToolAllConflicts
|
||||||
- Text.WorkingCopy.Conflicts.UseMine
|
- Text.WorkingCopy.Conflicts.UseMine
|
||||||
- Text.WorkingCopy.Conflicts.UseTheirs
|
- Text.WorkingCopy.Conflicts.UseTheirs
|
||||||
|
- Text.WorkingCopy.ResetAuthor
|
||||||
- Text.WorkingCopy.SignOff
|
- Text.WorkingCopy.SignOff
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
### 
|
### 
|
||||||
|
|
||||||
### 
|
### 
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary>Missing keys in ta_IN.axaml</summary>
|
<summary>Missing keys in ta_IN.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.Checkout.WithFastForward
|
||||||
|
- Text.Checkout.WithFastForward.Upstream
|
||||||
|
- Text.CommitCM.CopyAuthor
|
||||||
|
- Text.CommitCM.CopyCommitter
|
||||||
|
- Text.CommitCM.CopySubject
|
||||||
|
- Text.CommitCM.PushRevision
|
||||||
|
- Text.CommitDetail.Changes.Count
|
||||||
|
- Text.CommitMessageTextBox.SubjectCount
|
||||||
- Text.Configure.Git.PreferredMergeMode
|
- Text.Configure.Git.PreferredMergeMode
|
||||||
- Text.ConfirmEmptyCommit.Continue
|
- Text.ConfirmEmptyCommit.Continue
|
||||||
- Text.ConfirmEmptyCommit.NoLocalChanges
|
- Text.ConfirmEmptyCommit.NoLocalChanges
|
||||||
- Text.ConfirmEmptyCommit.StageAllThenCommit
|
- Text.ConfirmEmptyCommit.StageAllThenCommit
|
||||||
- Text.ConfirmEmptyCommit.WithLocalChanges
|
- 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.Merge.Edit
|
||||||
|
- Text.Preferences.Git.IgnoreCRAtEOLInDiff
|
||||||
|
- Text.Pull.RecurseSubmodules
|
||||||
|
- Text.Push.Revision
|
||||||
|
- Text.Push.Revision.Title
|
||||||
|
- 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.Stash.Mode
|
||||||
|
- Text.StashCM.CopyMessage
|
||||||
|
- 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.UpdateSubmodules.Target
|
||||||
|
- Text.ViewLogs
|
||||||
|
- Text.ViewLogs.Clear
|
||||||
|
- Text.ViewLogs.CopyLog
|
||||||
|
- Text.ViewLogs.Delete
|
||||||
|
- Text.WorkingCopy.AddToGitIgnore.InFolder
|
||||||
- Text.WorkingCopy.Conflicts.OpenExternalMergeTool
|
- Text.WorkingCopy.Conflicts.OpenExternalMergeTool
|
||||||
- Text.WorkingCopy.Conflicts.OpenExternalMergeToolAllConflicts
|
- Text.WorkingCopy.Conflicts.OpenExternalMergeToolAllConflicts
|
||||||
- Text.WorkingCopy.Conflicts.UseMine
|
- Text.WorkingCopy.Conflicts.UseMine
|
||||||
- Text.WorkingCopy.Conflicts.UseTheirs
|
- Text.WorkingCopy.Conflicts.UseTheirs
|
||||||
|
- Text.WorkingCopy.ResetAuthor
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
### 
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Missing keys in uk_UA.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.ResetToSelectedCommit
|
||||||
|
- Text.Checkout.RecurseSubmodules
|
||||||
|
- Text.Checkout.WithFastForward
|
||||||
|
- Text.Checkout.WithFastForward.Upstream
|
||||||
|
- Text.CommitCM.CopyAuthor
|
||||||
|
- Text.CommitCM.CopyCommitter
|
||||||
|
- Text.CommitCM.CopySubject
|
||||||
|
- Text.CommitCM.PushRevision
|
||||||
|
- 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.Merge.Edit
|
||||||
|
- Text.Preferences.Git.IgnoreCRAtEOLInDiff
|
||||||
|
- Text.Pull.RecurseSubmodules
|
||||||
|
- Text.Push.Revision
|
||||||
|
- Text.Push.Revision.Title
|
||||||
|
- 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.Stash.Mode
|
||||||
|
- Text.StashCM.CopyMessage
|
||||||
|
- 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.AddToGitIgnore.InFolder
|
||||||
|
- Text.WorkingCopy.ResetAuthor
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
|
|
2
VERSION
2
VERSION
|
@ -1 +1 @@
|
||||||
2025.13
|
2025.23
|
|
@ -12,4 +12,4 @@
|
||||||
dotnet publish -c Release -r $RUNTIME_IDENTIFIER -o $DESTINATION_FOLDER src/SourceGit.csproj
|
dotnet publish -c Release -r $RUNTIME_IDENTIFIER -o $DESTINATION_FOLDER src/SourceGit.csproj
|
||||||
```
|
```
|
||||||
> [!NOTE]
|
> [!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.
|
||||||
|
|
|
@ -14,6 +14,22 @@ async function parseXml(filePath) {
|
||||||
return parser.parseStringPromise(data);
|
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() {
|
async function calculateTranslationRate() {
|
||||||
const enUSData = await parseXml(enUSFile);
|
const enUSData = await parseXml(enUSFile);
|
||||||
const enUSKeys = new Set(enUSData.ResourceDictionary['x:String'].map(item => item.$['x:Key']));
|
const enUSKeys = new Set(enUSData.ResourceDictionary['x:String'].map(item => item.$['x:Key']));
|
||||||
|
@ -33,6 +49,21 @@ async function calculateTranslationRate() {
|
||||||
const localeKeys = new Set(localeData.ResourceDictionary['x:String'].map(item => item.$['x:Key']));
|
const localeKeys = new Set(localeData.ResourceDictionary['x:String'].map(item => item.$['x:Key']));
|
||||||
const missingKeys = [...enUSKeys].filter(key => !localeKeys.has(key));
|
const missingKeys = [...enUSKeys].filter(key => !localeKeys.has(key));
|
||||||
|
|
||||||
|
// Sort and clean up extra translations
|
||||||
|
const sortedAndCleaned = await filterAndSortTranslations(localeData, enUSKeys, enUSData);
|
||||||
|
localeData.ResourceDictionary['x:String'] = sortedAndCleaned;
|
||||||
|
|
||||||
|
// 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) {
|
if (missingKeys.length > 0) {
|
||||||
const progress = ((enUSKeys.size - missingKeys.length) / enUSKeys.size) * 100;
|
const progress = ((enUSKeys.size - missingKeys.length) / enUSKeys.size) * 100;
|
||||||
const badgeColor = progress >= 75 ? 'yellow' : 'red';
|
const badgeColor = progress >= 75 ? 'yellow' : 'red';
|
||||||
|
|
|
@ -37,10 +37,10 @@ namespace SourceGit
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static readonly Command OpenPreferencesCommand = new Command(_ => OpenDialog(new Views.Preferences()));
|
public static readonly Command OpenPreferencesCommand = new Command(_ => ShowWindow(new Views.Preferences(), false));
|
||||||
public static readonly Command OpenHotkeysCommand = new Command(_ => OpenDialog(new Views.Hotkeys()));
|
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 OpenAppDataDirCommand = new Command(_ => Native.OS.OpenInFileManager(Native.OS.DataDir));
|
||||||
public static readonly Command OpenAboutCommand = new Command(_ => OpenDialog(new Views.About()));
|
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 CheckForUpdateCommand = new Command(_ => (Current as App)?.Check4Update(true));
|
||||||
public static readonly Command QuitCommand = new Command(_ => Quit(0));
|
public static readonly Command QuitCommand = new Command(_ => Quit(0));
|
||||||
public static readonly Command CopyTextBlockCommand = new Command(p =>
|
public static readonly Command CopyTextBlockCommand = new Command(p =>
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
<ResourceInclude x:Key="fr_FR" Source="/Resources/Locales/fr_FR.axaml"/>
|
<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="it_IT" Source="/Resources/Locales/it_IT.axaml"/>
|
||||||
<ResourceInclude x:Key="pt_BR" Source="/Resources/Locales/pt_BR.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="ru_RU" Source="/Resources/Locales/ru_RU.axaml"/>
|
||||||
<ResourceInclude x:Key="zh_CN" Source="/Resources/Locales/zh_CN.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="zh_TW" Source="/Resources/Locales/zh_TW.axaml"/>
|
||||||
|
@ -34,7 +35,7 @@
|
||||||
<NativeMenu.Menu>
|
<NativeMenu.Menu>
|
||||||
<NativeMenu>
|
<NativeMenu>
|
||||||
<NativeMenuItem Header="{DynamicResource Text.About.Menu}" Command="{x:Static s:App.OpenAboutCommand}"/>
|
<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.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}"/>
|
<NativeMenuItem Header="{DynamicResource Text.SelfUpdate}" Command="{x:Static s:App.CheckForUpdateCommand}" IsVisible="{x:Static s:App.IsCheckForUpdateCommandVisible}"/>
|
||||||
<NativeMenuItemSeparator/>
|
<NativeMenuItemSeparator/>
|
||||||
<NativeMenuItem Header="{DynamicResource Text.Preferences}" Command="{x:Static s:App.OpenPreferencesCommand}" Gesture="⌘+,"/>
|
<NativeMenuItem Header="{DynamicResource Text.Preferences}" Command="{x:Static s:App.OpenPreferencesCommand}" Gesture="⌘+,"/>
|
||||||
|
|
178
src/App.axaml.cs
178
src/App.axaml.cs
|
@ -6,6 +6,7 @@ using System.Net.Http;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
@ -77,7 +78,7 @@ namespace SourceGit
|
||||||
return builder;
|
return builder;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void LogException(Exception ex)
|
public static void LogException(Exception ex)
|
||||||
{
|
{
|
||||||
if (ex == null)
|
if (ex == null)
|
||||||
return;
|
return;
|
||||||
|
@ -104,10 +105,44 @@ namespace SourceGit
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Utility Functions
|
#region Utility Functions
|
||||||
public static void OpenDialog(Window window)
|
public static void ShowWindow(object data, bool showAsDialog)
|
||||||
{
|
{
|
||||||
if (Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime { MainWindow: { } owner })
|
var impl = (Views.ChromelessWindow target, bool isDialog) =>
|
||||||
window.ShowDialog(owner);
|
{
|
||||||
|
if (Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime { MainWindow: { } 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)
|
public static void RaiseException(string context, string message)
|
||||||
|
@ -266,7 +301,7 @@ namespace SourceGit
|
||||||
return await clipboard.GetTextAsync();
|
return await clipboard.GetTextAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return default;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string Text(string key, params object[] args)
|
public static string Text(string key, params object[] args)
|
||||||
|
@ -288,8 +323,7 @@ namespace SourceGit
|
||||||
icon.Height = 12;
|
icon.Height = 12;
|
||||||
icon.Stretch = Stretch.Uniform;
|
icon.Stretch = Stretch.Uniform;
|
||||||
|
|
||||||
var geo = Current?.FindResource(key) as StreamGeometry;
|
if (Current?.FindResource(key) is StreamGeometry geo)
|
||||||
if (geo != null)
|
|
||||||
icon.Data = geo;
|
icon.Data = geo;
|
||||||
|
|
||||||
return icon;
|
return icon;
|
||||||
|
@ -303,7 +337,7 @@ namespace SourceGit
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ViewModels.Launcher GetLauncer()
|
public static ViewModels.Launcher GetLauncher()
|
||||||
{
|
{
|
||||||
return Current is App app ? app._launcher : null;
|
return Current is App app ? app._launcher : null;
|
||||||
}
|
}
|
||||||
|
@ -341,13 +375,42 @@ namespace SourceGit
|
||||||
{
|
{
|
||||||
BindingPlugins.DataValidators.RemoveAt(0);
|
BindingPlugins.DataValidators.RemoveAt(0);
|
||||||
|
|
||||||
|
// Disable tooltip if window is not active.
|
||||||
|
ToolTip.ToolTipOpeningEvent.AddClassHandler<Control>((c, e) =>
|
||||||
|
{
|
||||||
|
var topLevel = TopLevel.GetTopLevel(c);
|
||||||
|
if (topLevel is not Window { IsActive: true })
|
||||||
|
e.Cancel = true;
|
||||||
|
});
|
||||||
|
|
||||||
if (TryLaunchAsCoreEditor(desktop))
|
if (TryLaunchAsCoreEditor(desktop))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (TryLaunchAsAskpass(desktop))
|
if (TryLaunchAsAskpass(desktop))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
TryLaunchAsNormal(desktop);
|
_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);
|
||||||
|
}
|
||||||
|
|
||||||
|
_ipcChannel.SendToFirstInstance(arg);
|
||||||
|
Environment.Exit(0);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_ipcChannel.MessageReceived += TryOpenRepository;
|
||||||
|
desktop.Exit += (_, _) => _ipcChannel.Dispose();
|
||||||
|
TryLaunchAsNormal(desktop);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
@ -420,21 +483,37 @@ namespace SourceGit
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
var gitDir = Path.GetDirectoryName(file)!;
|
var gitDir = Path.GetDirectoryName(file)!;
|
||||||
var jobsFile = Path.Combine(gitDir, "sourcegit_rebase_jobs.json");
|
var origHeadFile = Path.Combine(gitDir, "rebase-merge", "orig-head");
|
||||||
if (!File.Exists(jobsFile))
|
var ontoFile = Path.Combine(gitDir, "rebase-merge", "onto");
|
||||||
return true;
|
|
||||||
|
|
||||||
var collection = JsonSerializer.Deserialize(File.ReadAllText(jobsFile), JsonCodeGen.Default.InteractiveRebaseJobCollection);
|
|
||||||
var doneFile = Path.Combine(gitDir, "rebase-merge", "done");
|
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;
|
return true;
|
||||||
|
|
||||||
var done = File.ReadAllText(doneFile).Split(new[] { '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries);
|
var origHead = File.ReadAllText(origHeadFile).Trim();
|
||||||
if (done.Length > collection.Jobs.Count)
|
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;
|
return true;
|
||||||
|
|
||||||
var job = collection.Jobs[done.Length - 1];
|
var done = File.ReadAllText(doneFile).Trim().Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries);
|
||||||
File.WriteAllText(file, job.Message);
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -442,7 +521,7 @@ namespace SourceGit
|
||||||
private bool TryLaunchAsCoreEditor(IClassicDesktopStyleApplicationLifetime desktop)
|
private bool TryLaunchAsCoreEditor(IClassicDesktopStyleApplicationLifetime desktop)
|
||||||
{
|
{
|
||||||
var args = desktop.Args;
|
var args = desktop.Args;
|
||||||
if (args == null || args.Length <= 1 || !args[0].Equals("--core-editor", StringComparison.Ordinal))
|
if (args is not { Length: > 1 } || !args[0].Equals("--core-editor", StringComparison.Ordinal))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
var file = args[1];
|
var file = args[1];
|
||||||
|
@ -452,8 +531,8 @@ namespace SourceGit
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
var editor = new Views.StandaloneCommitMessageEditor();
|
var editor = new Views.CommitMessageEditor();
|
||||||
editor.SetFile(file);
|
editor.AsStandalone(file);
|
||||||
desktop.MainWindow = editor;
|
desktop.MainWindow = editor;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -478,23 +557,54 @@ namespace SourceGit
|
||||||
|
|
||||||
private void TryLaunchAsNormal(IClassicDesktopStyleApplicationLifetime desktop)
|
private void TryLaunchAsNormal(IClassicDesktopStyleApplicationLifetime desktop)
|
||||||
{
|
{
|
||||||
Native.OS.SetupEnternalTools();
|
Native.OS.SetupExternalTools();
|
||||||
Models.AvatarManager.Instance.Start();
|
Models.AvatarManager.Instance.Start();
|
||||||
|
|
||||||
string startupRepo = null;
|
string startupRepo = null;
|
||||||
if (desktop.Args != null && desktop.Args.Length == 1 && Directory.Exists(desktop.Args[0]))
|
if (desktop.Args != null && desktop.Args.Length == 1 && Directory.Exists(desktop.Args[0]))
|
||||||
startupRepo = desktop.Args[0];
|
startupRepo = desktop.Args[0];
|
||||||
|
|
||||||
|
var pref = ViewModels.Preferences.Instance;
|
||||||
|
pref.SetCanModify();
|
||||||
|
|
||||||
_launcher = new ViewModels.Launcher(startupRepo);
|
_launcher = new ViewModels.Launcher(startupRepo);
|
||||||
desktop.MainWindow = new Views.Launcher() { DataContext = _launcher };
|
desktop.MainWindow = new Views.Launcher() { DataContext = _launcher };
|
||||||
|
desktop.ShutdownMode = ShutdownMode.OnMainWindowClose;
|
||||||
|
|
||||||
#if !DISABLE_UPDATE_DETECTION
|
#if !DISABLE_UPDATE_DETECTION
|
||||||
var pref = ViewModels.Preferences.Instance;
|
|
||||||
if (pref.ShouldCheck4UpdateOnStartup())
|
if (pref.ShouldCheck4UpdateOnStartup())
|
||||||
Check4Update();
|
Check4Update();
|
||||||
#endif
|
#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)
|
private void Check4Update(bool manually = false)
|
||||||
{
|
{
|
||||||
Task.Run(async () =>
|
Task.Run(async () =>
|
||||||
|
@ -540,11 +650,7 @@ namespace SourceGit
|
||||||
{
|
{
|
||||||
Dispatcher.UIThread.Post(() =>
|
Dispatcher.UIThread.Post(() =>
|
||||||
{
|
{
|
||||||
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime { MainWindow: { } owner })
|
ShowWindow(new ViewModels.SelfUpdate() { Data = data }, true);
|
||||||
{
|
|
||||||
var dialog = new Views.SelfUpdate() { DataContext = new ViewModels.SelfUpdate() { Data = data } };
|
|
||||||
dialog.ShowDialog(owner);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -574,12 +680,24 @@ namespace SourceGit
|
||||||
prevChar = c;
|
prevChar = c;
|
||||||
}
|
}
|
||||||
|
|
||||||
trimmed.Add(sb.ToString());
|
var name = sb.ToString();
|
||||||
|
if (name.Contains('#', StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
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;
|
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 ViewModels.Launcher _launcher = null;
|
||||||
private ResourceDictionary _activeLocale = null;
|
private ResourceDictionary _activeLocale = null;
|
||||||
private ResourceDictionary _themeOverrides = null;
|
private ResourceDictionary _themeOverrides = null;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
|
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||||
<!-- This manifest is used on Windows only.
|
<!-- 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 -->
|
For more details visit https://learn.microsoft.com/en-us/windows/win32/sbscs/application-manifests -->
|
||||||
<assemblyIdentity version="1.0.0.0" name="SourceGit.Desktop"/>
|
<assemblyIdentity version="1.0.0.0" name="SourceGit.Desktop"/>
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,4 @@
|
||||||
using System.Collections.Generic;
|
namespace SourceGit.Commands
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace SourceGit.Commands
|
|
||||||
{
|
{
|
||||||
public class Add : Command
|
public class Add : Command
|
||||||
{
|
{
|
||||||
|
@ -12,20 +9,11 @@ namespace SourceGit.Commands
|
||||||
Args = includeUntracked ? "add ." : "add -u .";
|
Args = includeUntracked ? "add ." : "add -u .";
|
||||||
}
|
}
|
||||||
|
|
||||||
public Add(string repo, List<string> changes)
|
public Add(string repo, Models.Change change)
|
||||||
{
|
{
|
||||||
WorkingDirectory = repo;
|
WorkingDirectory = repo;
|
||||||
Context = repo;
|
Context = repo;
|
||||||
|
Args = $"add -- \"{change.Path}\"";
|
||||||
var builder = new StringBuilder();
|
|
||||||
builder.Append("add --");
|
|
||||||
foreach (var c in changes)
|
|
||||||
{
|
|
||||||
builder.Append(" \"");
|
|
||||||
builder.Append(c);
|
|
||||||
builder.Append("\"");
|
|
||||||
}
|
|
||||||
Args = builder.ToString();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Add(string repo, string pathspecFromFile)
|
public Add(string repo, string pathspecFromFile)
|
||||||
|
|
|
@ -1,23 +1,12 @@
|
||||||
using System;
|
namespace SourceGit.Commands
|
||||||
|
|
||||||
namespace SourceGit.Commands
|
|
||||||
{
|
{
|
||||||
public class Archive : Command
|
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;
|
WorkingDirectory = repo;
|
||||||
Context = repo;
|
Context = repo;
|
||||||
Args = $"archive --format=zip --verbose --output=\"{saveTo}\" {revision}";
|
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,75 +1,14 @@
|
||||||
using System.Collections.Generic;
|
namespace SourceGit.Commands
|
||||||
using System.Text.RegularExpressions;
|
|
||||||
|
|
||||||
namespace SourceGit.Commands
|
|
||||||
{
|
{
|
||||||
public partial class AssumeUnchanged
|
public class AssumeUnchanged : Command
|
||||||
{
|
{
|
||||||
[GeneratedRegex(@"^(\w)\s+(.+)$")]
|
public AssumeUnchanged(string repo, string file, bool bAdd)
|
||||||
private static partial Regex REG_PARSE();
|
|
||||||
|
|
||||||
class ViewCommand : Command
|
|
||||||
{
|
{
|
||||||
public ViewCommand(string repo)
|
var mode = bAdd ? "--assume-unchanged" : "--no-assume-unchanged";
|
||||||
{
|
|
||||||
WorkingDirectory = repo;
|
|
||||||
Args = "ls-files -v";
|
|
||||||
RaiseError = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<string> Result()
|
WorkingDirectory = repo;
|
||||||
{
|
Context = repo;
|
||||||
Exec();
|
Args = $"update-index {mode} -- \"{file}\"";
|
||||||
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)
|
|
||||||
{
|
|
||||||
var mode = bAdd ? "--assume-unchanged" : "--no-assume-unchanged";
|
|
||||||
|
|
||||||
WorkingDirectory = repo;
|
|
||||||
Context = repo;
|
|
||||||
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()
|
public Models.BlameData Result()
|
||||||
{
|
{
|
||||||
var succ = Exec();
|
var rs = ReadToEnd();
|
||||||
if (!succ)
|
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)
|
if (_needUnifyCommitSHA)
|
||||||
|
@ -42,14 +49,9 @@ namespace SourceGit.Commands
|
||||||
return _result;
|
return _result;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnReadline(string line)
|
private void ParseLine(string line)
|
||||||
{
|
{
|
||||||
if (_result.IsBinary)
|
if (line.Contains('\0', StringComparison.Ordinal))
|
||||||
return;
|
|
||||||
if (string.IsNullOrEmpty(line))
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (line.IndexOf('\0', StringComparison.Ordinal) >= 0)
|
|
||||||
{
|
{
|
||||||
_result.IsBinary = true;
|
_result.IsBinary = true;
|
||||||
_result.LineInfos.Clear();
|
_result.LineInfos.Clear();
|
||||||
|
@ -87,7 +89,7 @@ namespace SourceGit.Commands
|
||||||
|
|
||||||
private readonly Models.BlameData _result = new Models.BlameData();
|
private readonly Models.BlameData _result = new Models.BlameData();
|
||||||
private readonly StringBuilder _content = new StringBuilder();
|
private readonly StringBuilder _content = new StringBuilder();
|
||||||
private readonly string _dateFormat = Models.DateTimeFormat.Actived.DateOnly;
|
private readonly string _dateFormat = Models.DateTimeFormat.Active.DateOnly;
|
||||||
private string _lastSHA = string.Empty;
|
private string _lastSHA = string.Empty;
|
||||||
private bool _needUnifyCommitSHA = false;
|
private bool _needUnifyCommitSHA = false;
|
||||||
private int _minSHALen = 64;
|
private int _minSHALen = 64;
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
namespace SourceGit.Commands
|
using System.Text;
|
||||||
|
|
||||||
|
namespace SourceGit.Commands
|
||||||
{
|
{
|
||||||
public static class Branch
|
public static class Branch
|
||||||
{
|
{
|
||||||
|
@ -11,29 +13,40 @@
|
||||||
return cmd.ReadToEnd().StdOut.Trim();
|
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();
|
var cmd = new Command();
|
||||||
cmd.WorkingDirectory = repo;
|
cmd.WorkingDirectory = repo;
|
||||||
cmd.Context = repo;
|
cmd.Context = repo;
|
||||||
cmd.Args = $"branch {name} {basedOn}";
|
cmd.Args = builder.ToString();
|
||||||
|
cmd.Log = log;
|
||||||
return cmd.Exec();
|
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();
|
var cmd = new Command();
|
||||||
cmd.WorkingDirectory = repo;
|
cmd.WorkingDirectory = repo;
|
||||||
cmd.Context = repo;
|
cmd.Context = repo;
|
||||||
cmd.Args = $"branch -M {name} {to}";
|
cmd.Args = $"branch -M {name} {to}";
|
||||||
|
cmd.Log = log;
|
||||||
return cmd.Exec();
|
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();
|
var cmd = new Command();
|
||||||
cmd.WorkingDirectory = repo;
|
cmd.WorkingDirectory = repo;
|
||||||
cmd.Context = repo;
|
cmd.Context = repo;
|
||||||
|
cmd.Log = log;
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(upstream))
|
if (string.IsNullOrEmpty(upstream))
|
||||||
cmd.Args = $"branch {name} --unset-upstream";
|
cmd.Args = $"branch {name} --unset-upstream";
|
||||||
|
@ -43,25 +56,27 @@
|
||||||
return cmd.Exec();
|
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();
|
var cmd = new Command();
|
||||||
cmd.WorkingDirectory = repo;
|
cmd.WorkingDirectory = repo;
|
||||||
cmd.Context = repo;
|
cmd.Context = repo;
|
||||||
cmd.Args = $"branch -D {name}";
|
cmd.Args = $"branch -D {name}";
|
||||||
|
cmd.Log = log;
|
||||||
return cmd.Exec();
|
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);
|
bool exists = new Remote(repo).HasBranch(remote, name);
|
||||||
if (exists)
|
if (exists)
|
||||||
return new Push(repo, remote, $"refs/heads/{name}", true).Exec();
|
return new Push(repo, remote, $"refs/heads/{name}", true) { Log = log }.Exec();
|
||||||
|
|
||||||
var cmd = new Command();
|
var cmd = new Command();
|
||||||
cmd.WorkingDirectory = repo;
|
cmd.WorkingDirectory = repo;
|
||||||
cmd.Context = repo;
|
cmd.Context = repo;
|
||||||
cmd.Args = $"branch -D -r {remote}/{name}";
|
cmd.Args = $"branch -D -r {remote}/{name}";
|
||||||
|
cmd.Log = log;
|
||||||
return cmd.Exec();
|
return cmd.Exec();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
using System;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
namespace SourceGit.Commands
|
namespace SourceGit.Commands
|
||||||
|
@ -12,19 +11,37 @@ namespace SourceGit.Commands
|
||||||
Context = repo;
|
Context = repo;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool Branch(string branch, Action<string> onProgress)
|
public bool Branch(string branch, bool force)
|
||||||
{
|
{
|
||||||
Args = $"checkout --recurse-submodules --progress {branch}";
|
var builder = new StringBuilder();
|
||||||
TraitErrorAsOutput = true;
|
builder.Append("checkout --progress ");
|
||||||
_outputHandler = onProgress;
|
if (force)
|
||||||
|
builder.Append("--force ");
|
||||||
|
builder.Append(branch);
|
||||||
|
|
||||||
|
Args = builder.ToString();
|
||||||
return Exec();
|
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}";
|
var builder = new StringBuilder();
|
||||||
TraitErrorAsOutput = true;
|
builder.Append("checkout --progress ");
|
||||||
_outputHandler = onProgress;
|
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();
|
return Exec();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,20 +78,5 @@ namespace SourceGit.Commands
|
||||||
Args = $"checkout --no-overlay {revision} -- \"{file}\"";
|
Args = $"checkout --no-overlay {revision} -- \"{file}\"";
|
||||||
return Exec();
|
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;
|
namespace SourceGit.Commands
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace SourceGit.Commands
|
|
||||||
{
|
{
|
||||||
public class Clean : Command
|
public class Clean : Command
|
||||||
{
|
{
|
||||||
public Clean(string repo, bool includeIgnored)
|
public Clean(string repo)
|
||||||
{
|
{
|
||||||
WorkingDirectory = repo;
|
WorkingDirectory = repo;
|
||||||
Context = repo;
|
Context = repo;
|
||||||
Args = includeIgnored ? "clean -qfdx" : "clean -qfd";
|
Args = "clean -qfdx";
|
||||||
}
|
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,16 +1,11 @@
|
||||||
using System;
|
namespace SourceGit.Commands
|
||||||
|
|
||||||
namespace SourceGit.Commands
|
|
||||||
{
|
{
|
||||||
public class Clone : Command
|
public class Clone : Command
|
||||||
{
|
{
|
||||||
private readonly Action<string> _notifyProgress;
|
public Clone(string ctx, string path, string url, string localName, string sshKey, string extraArgs)
|
||||||
|
|
||||||
public Clone(string ctx, string path, string url, string localName, string sshKey, string extraArgs, Action<string> ouputHandler)
|
|
||||||
{
|
{
|
||||||
Context = ctx;
|
Context = ctx;
|
||||||
WorkingDirectory = path;
|
WorkingDirectory = path;
|
||||||
TraitErrorAsOutput = true;
|
|
||||||
SSHKey = sshKey;
|
SSHKey = sshKey;
|
||||||
Args = "clone --progress --verbose ";
|
Args = "clone --progress --verbose ";
|
||||||
|
|
||||||
|
@ -21,13 +16,6 @@ namespace SourceGit.Commands
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(localName))
|
if (!string.IsNullOrEmpty(localName))
|
||||||
Args += localName;
|
Args += localName;
|
||||||
|
|
||||||
_notifyProgress = ouputHandler;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnReadline(string line)
|
|
||||||
{
|
|
||||||
_notifyProgress?.Invoke(line);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,45 +32,18 @@ namespace SourceGit.Commands
|
||||||
public string SSHKey { get; set; } = string.Empty;
|
public string SSHKey { get; set; } = string.Empty;
|
||||||
public string Args { get; set; } = string.Empty;
|
public string Args { get; set; } = string.Empty;
|
||||||
public bool RaiseError { get; set; } = true;
|
public bool RaiseError { get; set; } = true;
|
||||||
public bool TraitErrorAsOutput { get; set; } = false;
|
public Models.ICommandLog Log { get; set; } = null;
|
||||||
|
|
||||||
public bool Exec()
|
public bool Exec()
|
||||||
{
|
{
|
||||||
|
Log?.AppendLine($"$ git {Args}\n");
|
||||||
|
|
||||||
var start = CreateGitStartInfo();
|
var start = CreateGitStartInfo();
|
||||||
var errs = new List<string>();
|
var errs = new List<string>();
|
||||||
var proc = new Process() { StartInfo = start };
|
var proc = new Process() { StartInfo = start };
|
||||||
|
|
||||||
proc.OutputDataReceived += (_, e) =>
|
proc.OutputDataReceived += (_, e) => HandleOutput(e.Data, errs);
|
||||||
{
|
proc.ErrorDataReceived += (_, e) => HandleOutput(e.Data, errs);
|
||||||
if (e.Data != null)
|
|
||||||
OnReadline(e.Data);
|
|
||||||
};
|
|
||||||
|
|
||||||
proc.ErrorDataReceived += (_, e) =>
|
|
||||||
{
|
|
||||||
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);
|
|
||||||
};
|
|
||||||
|
|
||||||
var dummy = null as Process;
|
var dummy = null as Process;
|
||||||
var dummyProcLock = new object();
|
var dummyProcLock = new object();
|
||||||
|
@ -97,6 +70,7 @@ namespace SourceGit.Commands
|
||||||
if (RaiseError)
|
if (RaiseError)
|
||||||
Dispatcher.UIThread.Post(() => App.RaiseException(Context, e.Message));
|
Dispatcher.UIThread.Post(() => App.RaiseException(Context, e.Message));
|
||||||
|
|
||||||
|
Log?.AppendLine(string.Empty);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -114,6 +88,7 @@ namespace SourceGit.Commands
|
||||||
|
|
||||||
int exitCode = proc.ExitCode;
|
int exitCode = proc.ExitCode;
|
||||||
proc.Close();
|
proc.Close();
|
||||||
|
Log?.AppendLine(string.Empty);
|
||||||
|
|
||||||
if (!CancellationToken.IsCancellationRequested && exitCode != 0)
|
if (!CancellationToken.IsCancellationRequested && exitCode != 0)
|
||||||
{
|
{
|
||||||
|
@ -162,11 +137,6 @@ namespace SourceGit.Commands
|
||||||
return rs;
|
return rs;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual void OnReadline(string line)
|
|
||||||
{
|
|
||||||
// Implemented by derived class
|
|
||||||
}
|
|
||||||
|
|
||||||
private ProcessStartInfo CreateGitStartInfo()
|
private ProcessStartInfo CreateGitStartInfo()
|
||||||
{
|
{
|
||||||
var start = new ProcessStartInfo();
|
var start = new ProcessStartInfo();
|
||||||
|
@ -222,6 +192,28 @@ namespace SourceGit.Commands
|
||||||
return start;
|
return start;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void HandleOutput(string line, List<string> errs)
|
||||||
|
{
|
||||||
|
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+%")]
|
[GeneratedRegex(@"\d+%")]
|
||||||
private static partial Regex REG_PROGRESS();
|
private static partial Regex REG_PROGRESS();
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,19 +4,18 @@ namespace SourceGit.Commands
|
||||||
{
|
{
|
||||||
public class Commit : Command
|
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();
|
_tmpFile = Path.GetTempFileName();
|
||||||
File.WriteAllText(_tmpFile, message);
|
File.WriteAllText(_tmpFile, message);
|
||||||
|
|
||||||
WorkingDirectory = repo;
|
WorkingDirectory = repo;
|
||||||
Context = repo;
|
Context = repo;
|
||||||
TraitErrorAsOutput = true;
|
|
||||||
Args = $"commit --allow-empty --file=\"{_tmpFile}\"";
|
Args = $"commit --allow-empty --file=\"{_tmpFile}\"";
|
||||||
if (amend)
|
|
||||||
Args += " --amend --no-edit";
|
|
||||||
if (signOff)
|
if (signOff)
|
||||||
Args += " --signoff";
|
Args += " --signoff";
|
||||||
|
if (amend)
|
||||||
|
Args += resetAuthor ? " --amend --reset-author --no-edit" : " --amend --no-edit";
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool Run()
|
public bool Run()
|
||||||
|
@ -35,6 +34,6 @@ namespace SourceGit.Commands
|
||||||
return succ;
|
return succ;
|
||||||
}
|
}
|
||||||
|
|
||||||
private string _tmpFile = string.Empty;
|
private readonly string _tmpFile;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,12 +31,19 @@ namespace SourceGit.Commands
|
||||||
|
|
||||||
public List<Models.Change> Result()
|
public List<Models.Change> Result()
|
||||||
{
|
{
|
||||||
Exec();
|
var rs = ReadToEnd();
|
||||||
_changes.Sort((l, r) => string.Compare(l.Path, r.Path, StringComparison.Ordinal));
|
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) => Models.NumericSort.Compare(l.Path, r.Path));
|
||||||
return _changes;
|
return _changes;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnReadline(string line)
|
private void ParseLine(string line)
|
||||||
{
|
{
|
||||||
var match = REG_FORMAT().Match(line);
|
var match = REG_FORMAT().Match(line);
|
||||||
if (!match.Success)
|
if (!match.Success)
|
||||||
|
|
|
@ -8,7 +8,7 @@ namespace SourceGit.Commands
|
||||||
{
|
{
|
||||||
WorkingDirectory = repo;
|
WorkingDirectory = repo;
|
||||||
Context = repo;
|
Context = repo;
|
||||||
Args = "--no-optional-locks status -uno --ignore-submodules=dirty --porcelain";
|
Args = "--no-optional-locks status -uno --ignore-submodules=all --porcelain";
|
||||||
}
|
}
|
||||||
|
|
||||||
public int Result()
|
public int Result()
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
|
@ -28,34 +28,48 @@ namespace SourceGit.Commands
|
||||||
Context = repo;
|
Context = repo;
|
||||||
|
|
||||||
if (ignoreWhitespace)
|
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
|
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()
|
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;
|
_result.TextDiff = null;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
ProcessInlineHighlights();
|
ProcessInlineHighlights();
|
||||||
|
_result.TextDiff.MaxLineNumber = Math.Max(_newLine, _oldLine);
|
||||||
if (_result.TextDiff.Lines.Count == 0)
|
|
||||||
_result.TextDiff = null;
|
|
||||||
else
|
|
||||||
_result.TextDiff.MaxLineNumber = Math.Max(_newLine, _oldLine);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return _result;
|
return _result;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnReadline(string line)
|
private void ParseLine(string line)
|
||||||
{
|
{
|
||||||
|
if (_result.IsBinary)
|
||||||
|
return;
|
||||||
|
|
||||||
if (line.StartsWith("old mode ", StringComparison.Ordinal))
|
if (line.StartsWith("old mode ", StringComparison.Ordinal))
|
||||||
{
|
{
|
||||||
_result.OldMode = line.Substring(9);
|
_result.OldMode = line.Substring(9);
|
||||||
|
@ -80,9 +94,6 @@ namespace SourceGit.Commands
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_result.IsBinary)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (_result.IsLFS)
|
if (_result.IsLFS)
|
||||||
{
|
{
|
||||||
var ch = line[0];
|
var ch = line[0];
|
||||||
|
@ -94,7 +105,7 @@ namespace SourceGit.Commands
|
||||||
}
|
}
|
||||||
else if (line.StartsWith("-size ", StringComparison.Ordinal))
|
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 == '+')
|
else if (ch == '+')
|
||||||
|
@ -105,12 +116,12 @@ namespace SourceGit.Commands
|
||||||
}
|
}
|
||||||
else if (line.StartsWith("+size ", StringComparison.Ordinal))
|
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))
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -140,7 +151,8 @@ namespace SourceGit.Commands
|
||||||
|
|
||||||
_oldLine = int.Parse(match.Groups[1].Value);
|
_oldLine = int.Parse(match.Groups[1].Value);
|
||||||
_newLine = int.Parse(match.Groups[2].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
|
else
|
||||||
|
@ -148,7 +160,8 @@ namespace SourceGit.Commands
|
||||||
if (line.Length == 0)
|
if (line.Length == 0)
|
||||||
{
|
{
|
||||||
ProcessInlineHighlights();
|
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++;
|
_oldLine++;
|
||||||
_newLine++;
|
_newLine++;
|
||||||
return;
|
return;
|
||||||
|
@ -164,7 +177,8 @@ namespace SourceGit.Commands
|
||||||
return;
|
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++;
|
_oldLine++;
|
||||||
}
|
}
|
||||||
else if (ch == '+')
|
else if (ch == '+')
|
||||||
|
@ -176,7 +190,8 @@ namespace SourceGit.Commands
|
||||||
return;
|
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++;
|
_newLine++;
|
||||||
}
|
}
|
||||||
else if (ch != '\\')
|
else if (ch != '\\')
|
||||||
|
@ -187,7 +202,8 @@ namespace SourceGit.Commands
|
||||||
{
|
{
|
||||||
_oldLine = int.Parse(match.Groups[1].Value);
|
_oldLine = int.Parse(match.Groups[1].Value);
|
||||||
_newLine = int.Parse(match.Groups[2].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
|
else
|
||||||
{
|
{
|
||||||
|
@ -198,11 +214,16 @@ namespace SourceGit.Commands
|
||||||
return;
|
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++;
|
_oldLine++;
|
||||||
_newLine++;
|
_newLine++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (line.Equals("\\ No newline at end of file", StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
_last.NoNewLineEndOfFile = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -253,6 +274,7 @@ namespace SourceGit.Commands
|
||||||
private readonly Models.DiffResult _result = new Models.DiffResult();
|
private readonly Models.DiffResult _result = new Models.DiffResult();
|
||||||
private readonly List<Models.TextDiffLine> _deleted = new List<Models.TextDiffLine>();
|
private readonly List<Models.TextDiffLine> _deleted = new List<Models.TextDiffLine>();
|
||||||
private readonly List<Models.TextDiffLine> _added = 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 _oldLine = 0;
|
||||||
private int _newLine = 0;
|
private int _newLine = 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,39 +1,95 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
using Avalonia.Threading;
|
||||||
|
|
||||||
namespace SourceGit.Commands
|
namespace SourceGit.Commands
|
||||||
{
|
{
|
||||||
public static class Discard
|
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();
|
var changes = new QueryLocalChanges(repo).Result();
|
||||||
new Clean(repo, includeIgnored).Exec();
|
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}");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
new Reset(repo, "HEAD", "--hard") { Log = log }.Exec();
|
||||||
|
|
||||||
|
if (includeIgnored)
|
||||||
|
new Clean(repo) { Log = log }.Exec();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void Changes(string repo, List<Models.Change> changes)
|
/// <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 needClean = new List<string>();
|
var restores = new List<string>();
|
||||||
var needCheckout = new List<string>();
|
|
||||||
|
|
||||||
foreach (var c in changes)
|
try
|
||||||
{
|
{
|
||||||
if (c.WorkTree == Models.ChangeState.Untracked || c.WorkTree == Models.ChangeState.Added)
|
foreach (var c in changes)
|
||||||
needClean.Add(c.Path);
|
{
|
||||||
else
|
if (c.WorkTree == Models.ChangeState.Untracked || c.WorkTree == Models.ChangeState.Added)
|
||||||
needCheckout.Add(c.Path);
|
{
|
||||||
|
var fullPath = Path.Combine(repo, c.Path);
|
||||||
|
if (Directory.Exists(fullPath))
|
||||||
|
Directory.Delete(fullPath, true);
|
||||||
|
else
|
||||||
|
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);
|
var pathSpecFile = Path.GetTempFileName();
|
||||||
new Clean(repo, needClean.GetRange(i, count)).Exec();
|
File.WriteAllLines(pathSpecFile, restores);
|
||||||
}
|
new Restore(repo, pathSpecFile, false) { Log = log }.Exec();
|
||||||
|
File.Delete(pathSpecFile);
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,7 +27,7 @@ namespace SourceGit.Commands
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void RunAndWait(string repo, string file, string args, Action<string> outputHandler)
|
public static void RunAndWait(string repo, string file, string args, Models.ICommandLog log)
|
||||||
{
|
{
|
||||||
var start = new ProcessStartInfo();
|
var start = new ProcessStartInfo();
|
||||||
start.FileName = file;
|
start.FileName = file;
|
||||||
|
@ -40,20 +40,22 @@ namespace SourceGit.Commands
|
||||||
start.StandardErrorEncoding = Encoding.UTF8;
|
start.StandardErrorEncoding = Encoding.UTF8;
|
||||||
start.WorkingDirectory = repo;
|
start.WorkingDirectory = repo;
|
||||||
|
|
||||||
|
log?.AppendLine($"$ {file} {args}\n");
|
||||||
|
|
||||||
var proc = new Process() { StartInfo = start };
|
var proc = new Process() { StartInfo = start };
|
||||||
var builder = new StringBuilder();
|
var builder = new StringBuilder();
|
||||||
|
|
||||||
proc.OutputDataReceived += (_, e) =>
|
proc.OutputDataReceived += (_, e) =>
|
||||||
{
|
{
|
||||||
if (e.Data != null)
|
if (e.Data != null)
|
||||||
outputHandler?.Invoke(e.Data);
|
log?.AppendLine(e.Data);
|
||||||
};
|
};
|
||||||
|
|
||||||
proc.ErrorDataReceived += (_, e) =>
|
proc.ErrorDataReceived += (_, e) =>
|
||||||
{
|
{
|
||||||
if (e.Data != null)
|
if (e.Data != null)
|
||||||
{
|
{
|
||||||
outputHandler?.Invoke(e.Data);
|
log?.AppendLine(e.Data);
|
||||||
builder.AppendLine(e.Data);
|
builder.AppendLine(e.Data);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,15 +1,11 @@
|
||||||
using System;
|
namespace SourceGit.Commands
|
||||||
|
|
||||||
namespace SourceGit.Commands
|
|
||||||
{
|
{
|
||||||
public class Fetch : Command
|
public class Fetch : Command
|
||||||
{
|
{
|
||||||
public Fetch(string repo, string remote, bool noTags, bool force, Action<string> outputHandler)
|
public Fetch(string repo, string remote, bool noTags, bool force)
|
||||||
{
|
{
|
||||||
_outputHandler = outputHandler;
|
|
||||||
WorkingDirectory = repo;
|
WorkingDirectory = repo;
|
||||||
Context = repo;
|
Context = repo;
|
||||||
TraitErrorAsOutput = true;
|
|
||||||
SSHKey = new Config(repo).Get($"remote.{remote}.sshkey");
|
SSHKey = new Config(repo).Get($"remote.{remote}.sshkey");
|
||||||
Args = "fetch --progress --verbose ";
|
Args = "fetch --progress --verbose ";
|
||||||
|
|
||||||
|
@ -24,21 +20,12 @@ namespace SourceGit.Commands
|
||||||
Args += remote;
|
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;
|
WorkingDirectory = repo;
|
||||||
Context = repo;
|
Context = repo;
|
||||||
TraitErrorAsOutput = true;
|
|
||||||
SSHKey = new Config(repo).Get($"remote.{remote.Remote}.sshkey");
|
SSHKey = new Config(repo).Get($"remote.{remote.Remote}.sshkey");
|
||||||
Args = $"fetch --progress --verbose {remote.Remote} {remote.Name}:{local.Name}";
|
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;
|
WorkingDirectory = repo;
|
||||||
Context = repo;
|
Context = repo;
|
||||||
|
Editor = EditorType.None;
|
||||||
Args = $"format-patch {commit} -1 --output=\"{saveTo}\"";
|
Args = $"format-patch {commit} -1 --output=\"{saveTo}\"";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,23 +1,12 @@
|
||||||
using System;
|
namespace SourceGit.Commands
|
||||||
|
|
||||||
namespace SourceGit.Commands
|
|
||||||
{
|
{
|
||||||
public class GC : Command
|
public class GC : Command
|
||||||
{
|
{
|
||||||
public GC(string repo, Action<string> outputHandler)
|
public GC(string repo)
|
||||||
{
|
{
|
||||||
_outputHandler = outputHandler;
|
|
||||||
WorkingDirectory = repo;
|
WorkingDirectory = repo;
|
||||||
Context = repo;
|
Context = repo;
|
||||||
TraitErrorAsOutput = true;
|
|
||||||
Args = "gc --prune=now";
|
Args = "gc --prune=now";
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnReadline(string line)
|
|
||||||
{
|
|
||||||
_outputHandler?.Invoke(line);
|
|
||||||
}
|
|
||||||
|
|
||||||
private readonly Action<string> _outputHandler;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,52 +1,12 @@
|
||||||
using System;
|
using System.Text;
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
using Avalonia.Threading;
|
using Avalonia.Threading;
|
||||||
|
|
||||||
namespace SourceGit.Commands
|
namespace SourceGit.Commands
|
||||||
{
|
{
|
||||||
public static class GitFlow
|
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);
|
var config = new Config(repo);
|
||||||
config.Set("gitflow.branch.master", master);
|
config.Set("gitflow.branch.master", master);
|
||||||
config.Set("gitflow.branch.develop", develop);
|
config.Set("gitflow.branch.develop", develop);
|
||||||
|
@ -61,104 +21,72 @@ namespace SourceGit.Commands
|
||||||
init.WorkingDirectory = repo;
|
init.WorkingDirectory = repo;
|
||||||
init.Context = repo;
|
init.Context = repo;
|
||||||
init.Args = "flow init -d";
|
init.Args = "flow init -d";
|
||||||
|
init.Log = log;
|
||||||
return init.Exec();
|
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();
|
var start = new Command();
|
||||||
start.WorkingDirectory = repo;
|
start.WorkingDirectory = repo;
|
||||||
start.Context = repo;
|
start.Context = repo;
|
||||||
start.Args = $"flow {type} start {name}";
|
|
||||||
|
switch (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;
|
||||||
|
}
|
||||||
|
|
||||||
|
start.Log = log;
|
||||||
return start.Exec();
|
return start.Exec();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool Finish(string repo, string type, string name, bool keepBranch)
|
public static bool Finish(string repo, Models.GitFlowBranchType type, string name, bool squash, bool push, bool keepBranch, Models.ICommandLog log)
|
||||||
{
|
{
|
||||||
if (!SUPPORTED_BRANCH_TYPES.Contains(type))
|
var builder = new StringBuilder();
|
||||||
{
|
builder.Append("flow ");
|
||||||
Dispatcher.UIThread.Post(() =>
|
|
||||||
{
|
|
||||||
App.RaiseException(repo, "Bad branch type!!!");
|
|
||||||
});
|
|
||||||
|
|
||||||
return false;
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
var option = keepBranch ? "-k" : string.Empty;
|
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();
|
var finish = new Command();
|
||||||
finish.WorkingDirectory = repo;
|
finish.WorkingDirectory = repo;
|
||||||
finish.Context = repo;
|
finish.Context = repo;
|
||||||
finish.Args = $"flow {type} finish {option} {name}";
|
finish.Args = builder.ToString();
|
||||||
|
finish.Log = log;
|
||||||
return finish.Exec();
|
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");
|
var file = Path.Combine(repo, ".gitignore");
|
||||||
if (!File.Exists(file))
|
if (!File.Exists(file))
|
||||||
|
{
|
||||||
File.WriteAllLines(file, [pattern]);
|
File.WriteAllLines(file, [pattern]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var org = File.ReadAllText(file);
|
||||||
|
if (!org.EndsWith('\n'))
|
||||||
|
File.AppendAllLines(file, ["", pattern]);
|
||||||
else
|
else
|
||||||
File.AppendAllLines(file, [pattern]);
|
File.AppendAllLines(file, [pattern]);
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,7 @@ namespace SourceGit.Commands
|
||||||
{
|
{
|
||||||
WorkingDirectory = repo;
|
WorkingDirectory = repo;
|
||||||
Context = repo;
|
Context = repo;
|
||||||
Args = $"diff 4b825dc642cb6eb9a060e54bf8d69288fbee4904 {commit} --numstat -- \"{path}\"";
|
Args = $"diff {Models.Commit.EmptyTreeSHA1} {commit} --numstat -- \"{path}\"";
|
||||||
RaiseError = false;
|
RaiseError = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,5 +10,10 @@
|
||||||
Context = repo;
|
Context = repo;
|
||||||
Args = $"diff -a --ignore-cr-at-eol --check {opt}";
|
Args = $"diff -a --ignore-cr-at-eol --check {opt}";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool Result()
|
||||||
|
{
|
||||||
|
return ReadToEnd().IsSuccess;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,23 +10,15 @@ namespace SourceGit.Commands
|
||||||
[GeneratedRegex(@"^(.+)\s+([\w.]+)\s+\w+:(\d+)$")]
|
[GeneratedRegex(@"^(.+)\s+([\w.]+)\s+\w+:(\d+)$")]
|
||||||
private static partial Regex REG_LOCK();
|
private static partial Regex REG_LOCK();
|
||||||
|
|
||||||
class SubCmd : Command
|
private class SubCmd : Command
|
||||||
{
|
{
|
||||||
public SubCmd(string repo, string args, Action<string> onProgress)
|
public SubCmd(string repo, string args, Models.ICommandLog log)
|
||||||
{
|
{
|
||||||
WorkingDirectory = repo;
|
WorkingDirectory = repo;
|
||||||
Context = repo;
|
Context = repo;
|
||||||
Args = args;
|
Args = args;
|
||||||
TraitErrorAsOutput = true;
|
Log = log;
|
||||||
_outputHandler = onProgress;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnReadline(string line)
|
|
||||||
{
|
|
||||||
_outputHandler?.Invoke(line);
|
|
||||||
}
|
|
||||||
|
|
||||||
private readonly Action<string> _outputHandler;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public LFS(string repo)
|
public LFS(string repo)
|
||||||
|
@ -44,35 +36,35 @@ namespace SourceGit.Commands
|
||||||
return content.Contains("git lfs pre-push");
|
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" : "";
|
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)
|
public List<Models.LFSLock> Locks(string remote)
|
||||||
|
@ -101,21 +93,21 @@ namespace SourceGit.Commands
|
||||||
return locks;
|
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" : "";
|
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" : "";
|
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;
|
private readonly string _repo;
|
||||||
|
|
|
@ -1,26 +1,30 @@
|
||||||
using System;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
namespace SourceGit.Commands
|
namespace SourceGit.Commands
|
||||||
{
|
{
|
||||||
public class Merge : Command
|
public class Merge : Command
|
||||||
{
|
{
|
||||||
public Merge(string repo, string source, string mode, Action<string> outputHandler)
|
public Merge(string repo, string source, string mode, bool edit)
|
||||||
{
|
{
|
||||||
_outputHandler = outputHandler;
|
|
||||||
WorkingDirectory = repo;
|
WorkingDirectory = repo;
|
||||||
Context = repo;
|
Context = repo;
|
||||||
TraitErrorAsOutput = true;
|
Editor = EditorType.CoreEditor;
|
||||||
Args = $"merge --progress {source} {mode}";
|
|
||||||
|
var builder = new StringBuilder();
|
||||||
|
builder.Append("merge --progress ");
|
||||||
|
builder.Append(edit ? "--edit " : "--no-edit ");
|
||||||
|
builder.Append(source);
|
||||||
|
builder.Append(' ');
|
||||||
|
builder.Append(mode);
|
||||||
|
|
||||||
|
Args = builder.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
WorkingDirectory = repo;
|
||||||
Context = repo;
|
Context = repo;
|
||||||
TraitErrorAsOutput = true;
|
|
||||||
|
|
||||||
var builder = new StringBuilder();
|
var builder = new StringBuilder();
|
||||||
builder.Append("merge --progress ");
|
builder.Append("merge --progress ");
|
||||||
|
@ -37,12 +41,5 @@ namespace SourceGit.Commands
|
||||||
|
|
||||||
Args = builder.ToString();
|
Args = builder.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnReadline(string line)
|
|
||||||
{
|
|
||||||
_outputHandler?.Invoke(line);
|
|
||||||
}
|
|
||||||
|
|
||||||
private readonly Action<string> _outputHandler = null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,7 +24,7 @@ namespace SourceGit.Commands
|
||||||
|
|
||||||
if (!File.Exists(toolPath))
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,7 +54,7 @@ namespace SourceGit.Commands
|
||||||
|
|
||||||
if (!File.Exists(toolPath))
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,32 +1,18 @@
|
||||||
using System;
|
namespace SourceGit.Commands
|
||||||
|
|
||||||
namespace SourceGit.Commands
|
|
||||||
{
|
{
|
||||||
public class Pull : Command
|
public class Pull : Command
|
||||||
{
|
{
|
||||||
public Pull(string repo, string remote, string branch, bool useRebase, bool noTags, Action<string> outputHandler)
|
public Pull(string repo, string remote, string branch, bool useRebase)
|
||||||
{
|
{
|
||||||
_outputHandler = outputHandler;
|
|
||||||
WorkingDirectory = repo;
|
WorkingDirectory = repo;
|
||||||
Context = repo;
|
Context = repo;
|
||||||
TraitErrorAsOutput = true;
|
|
||||||
SSHKey = new Config(repo).Get($"remote.{remote}.sshkey");
|
SSHKey = new Config(repo).Get($"remote.{remote}.sshkey");
|
||||||
Args = "pull --verbose --progress ";
|
Args = "pull --verbose --progress ";
|
||||||
|
|
||||||
if (useRebase)
|
if (useRebase)
|
||||||
Args += "--rebase=true ";
|
Args += "--rebase=true ";
|
||||||
|
|
||||||
if (noTags)
|
|
||||||
Args += "--no-tags ";
|
|
||||||
|
|
||||||
Args += $"{remote} {branch}";
|
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 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;
|
WorkingDirectory = repo;
|
||||||
Context = repo;
|
Context = repo;
|
||||||
TraitErrorAsOutput = true;
|
|
||||||
SSHKey = new Config(repo).Get($"remote.{remote}.sshkey");
|
SSHKey = new Config(repo).Get($"remote.{remote}.sshkey");
|
||||||
Args = "push --progress --verbose ";
|
Args = "push --progress --verbose ";
|
||||||
|
|
||||||
|
@ -38,12 +33,5 @@ namespace SourceGit.Commands
|
||||||
|
|
||||||
Args += $"{remote} {refname}";
|
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,18 +14,20 @@ namespace SourceGit.Commands
|
||||||
{
|
{
|
||||||
WorkingDirectory = repo;
|
WorkingDirectory = repo;
|
||||||
Context = 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 branches = new List<Models.Branch>();
|
||||||
var rs = ReadToEnd();
|
var rs = ReadToEnd();
|
||||||
if (!rs.IsSuccess)
|
if (!rs.IsSuccess)
|
||||||
return branches;
|
return branches;
|
||||||
|
|
||||||
var lines = rs.StdOut.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries);
|
var lines = rs.StdOut.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries);
|
||||||
var remoteBranches = new HashSet<string>();
|
var remoteHeads = new Dictionary<string, string>();
|
||||||
foreach (var line in lines)
|
foreach (var line in lines)
|
||||||
{
|
{
|
||||||
var b = ParseLine(line);
|
var b = ParseLine(line);
|
||||||
|
@ -33,14 +35,27 @@ namespace SourceGit.Commands
|
||||||
{
|
{
|
||||||
branches.Add(b);
|
branches.Add(b);
|
||||||
if (!b.IsLocal)
|
if (!b.IsLocal)
|
||||||
remoteBranches.Add(b.FullName);
|
remoteHeads.Add(b.FullName, b.Head);
|
||||||
|
else
|
||||||
|
localBranchesCount++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var b in branches)
|
foreach (var b in branches)
|
||||||
{
|
{
|
||||||
if (b.IsLocal && !string.IsNullOrEmpty(b.Upstream))
|
if (b.IsLocal && !string.IsNullOrEmpty(b.Upstream))
|
||||||
b.IsUpsteamGone = !remoteBranches.Contains(b.Upstream);
|
{
|
||||||
|
if (remoteHeads.TryGetValue(b.Upstream, out var upstreamHead))
|
||||||
|
{
|
||||||
|
b.IsUpstreamGone = false;
|
||||||
|
b.TrackStatus ??= new QueryTrackStatus(WorkingDirectory, b.Head, upstreamHead).Result();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
b.IsUpstreamGone = true;
|
||||||
|
b.TrackStatus ??= new Models.BranchTrackStatus();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return branches;
|
return branches;
|
||||||
|
@ -49,7 +64,7 @@ namespace SourceGit.Commands
|
||||||
private Models.Branch ParseLine(string line)
|
private Models.Branch ParseLine(string line)
|
||||||
{
|
{
|
||||||
var parts = line.Split('\0');
|
var parts = line.Split('\0');
|
||||||
if (parts.Length != 5)
|
if (parts.Length != 6)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
var branch = new Models.Branch();
|
var branch = new Models.Branch();
|
||||||
|
@ -83,14 +98,16 @@ namespace SourceGit.Commands
|
||||||
}
|
}
|
||||||
|
|
||||||
branch.FullName = refName;
|
branch.FullName = refName;
|
||||||
branch.Head = parts[1];
|
branch.CommitterDate = ulong.Parse(parts[1]);
|
||||||
branch.IsCurrent = parts[2] == "*";
|
branch.Head = parts[2];
|
||||||
branch.Upstream = parts[3];
|
branch.IsCurrent = parts[3] == "*";
|
||||||
branch.IsUpsteamGone = false;
|
branch.Upstream = parts[4];
|
||||||
|
branch.IsUpstreamGone = false;
|
||||||
|
|
||||||
if (branch.IsLocal && !string.IsNullOrEmpty(parts[4]) && !parts[4].Equals("=", StringComparison.Ordinal))
|
if (!branch.IsLocal ||
|
||||||
branch.TrackStatus = new QueryTrackStatus(WorkingDirectory, branch.Name, branch.Upstream).Result();
|
string.IsNullOrEmpty(branch.Upstream) ||
|
||||||
else
|
string.IsNullOrEmpty(parts[5]) ||
|
||||||
|
parts[5].Equals("=", StringComparison.Ordinal))
|
||||||
branch.TrackStatus = new Models.BranchTrackStatus();
|
branch.TrackStatus = new Models.BranchTrackStatus();
|
||||||
|
|
||||||
return branch;
|
return branch;
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
using System.Collections.Generic;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace SourceGit.Commands
|
namespace SourceGit.Commands
|
||||||
{
|
{
|
||||||
|
@ -14,17 +15,21 @@ namespace SourceGit.Commands
|
||||||
|
|
||||||
public List<string> Result()
|
public List<string> Result()
|
||||||
{
|
{
|
||||||
Exec();
|
var rs = ReadToEnd();
|
||||||
return _lines;
|
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))
|
||||||
|
outs.Add(line.Substring(0, 40));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected override void OnReadline(string line)
|
return outs;
|
||||||
{
|
|
||||||
if (line.Contains(_commit))
|
|
||||||
_lines.Add(line.Substring(0, 40));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private string _commit;
|
private string _commit;
|
||||||
private List<string> _lines = new List<string>();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,11 +26,7 @@ namespace SourceGit.Commands
|
||||||
{
|
{
|
||||||
search += $"-i --committer=\"{filter}\"";
|
search += $"-i --committer=\"{filter}\"";
|
||||||
}
|
}
|
||||||
else if (method == Models.CommitSearchMethod.ByFile)
|
else if (method == Models.CommitSearchMethod.ByMessage)
|
||||||
{
|
|
||||||
search += $"-- \"{filter}\"";
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
{
|
||||||
var argsBuilder = new StringBuilder();
|
var argsBuilder = new StringBuilder();
|
||||||
argsBuilder.Append(search);
|
argsBuilder.Append(search);
|
||||||
|
@ -45,10 +41,18 @@ namespace SourceGit.Commands
|
||||||
|
|
||||||
search = argsBuilder.ToString();
|
search = argsBuilder.ToString();
|
||||||
}
|
}
|
||||||
|
else if (method == Models.CommitSearchMethod.ByFile)
|
||||||
|
{
|
||||||
|
search += $"-- \"{filter}\"";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
search = $"-G\"{filter}\"";
|
||||||
|
}
|
||||||
|
|
||||||
WorkingDirectory = repo;
|
WorkingDirectory = repo;
|
||||||
Context = repo;
|
Context = repo;
|
||||||
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;
|
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;
|
_findFirstMerged = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -90,6 +90,6 @@ namespace SourceGit.Commands
|
||||||
|
|
||||||
private List<Models.InteractiveCommit> _commits = [];
|
private List<Models.InteractiveCommit> _commits = [];
|
||||||
private Models.InteractiveCommit _current = null;
|
private Models.InteractiveCommit _current = null;
|
||||||
private string _boundary = "";
|
private readonly string _boundary;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,5 +35,39 @@ namespace SourceGit.Commands
|
||||||
|
|
||||||
return stream;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,9 +16,6 @@ namespace SourceGit.Commands
|
||||||
|
|
||||||
public long Result()
|
public long Result()
|
||||||
{
|
{
|
||||||
if (_result != 0)
|
|
||||||
return _result;
|
|
||||||
|
|
||||||
var rs = ReadToEnd();
|
var rs = ReadToEnd();
|
||||||
if (rs.IsSuccess)
|
if (rs.IsSuccess)
|
||||||
{
|
{
|
||||||
|
@ -29,7 +26,5 @@ namespace SourceGit.Commands
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly long _result = 0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
using System.Collections.Generic;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
|
using Avalonia.Threading;
|
||||||
|
|
||||||
namespace SourceGit.Commands
|
namespace SourceGit.Commands
|
||||||
{
|
{
|
||||||
public partial class QueryLocalChanges : Command
|
public partial class QueryLocalChanges : Command
|
||||||
|
@ -18,139 +21,145 @@ namespace SourceGit.Commands
|
||||||
|
|
||||||
public List<Models.Change> Result()
|
public List<Models.Change> Result()
|
||||||
{
|
{
|
||||||
Exec();
|
var outs = new List<Models.Change>();
|
||||||
return _changes;
|
var rs = ReadToEnd();
|
||||||
}
|
if (!rs.IsSuccess)
|
||||||
|
|
||||||
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)
|
|
||||||
{
|
{
|
||||||
case " M":
|
Dispatcher.UIThread.Post(() => App.RaiseException(Context, rs.StdErr));
|
||||||
change.Set(Models.ChangeState.None, Models.ChangeState.Modified);
|
return outs;
|
||||||
break;
|
|
||||||
case " T":
|
|
||||||
change.Set(Models.ChangeState.None, Models.ChangeState.TypeChanged);
|
|
||||||
break;
|
|
||||||
case " A":
|
|
||||||
change.Set(Models.ChangeState.None, Models.ChangeState.Added);
|
|
||||||
break;
|
|
||||||
case " D":
|
|
||||||
change.Set(Models.ChangeState.None, Models.ChangeState.Deleted);
|
|
||||||
break;
|
|
||||||
case " R":
|
|
||||||
change.Set(Models.ChangeState.None, Models.ChangeState.Renamed);
|
|
||||||
break;
|
|
||||||
case " C":
|
|
||||||
change.Set(Models.ChangeState.None, Models.ChangeState.Copied);
|
|
||||||
break;
|
|
||||||
case "M":
|
|
||||||
change.Set(Models.ChangeState.Modified);
|
|
||||||
break;
|
|
||||||
case "MM":
|
|
||||||
change.Set(Models.ChangeState.Modified, Models.ChangeState.Modified);
|
|
||||||
break;
|
|
||||||
case "MT":
|
|
||||||
change.Set(Models.ChangeState.Modified, Models.ChangeState.TypeChanged);
|
|
||||||
break;
|
|
||||||
case "MD":
|
|
||||||
change.Set(Models.ChangeState.Modified, Models.ChangeState.Deleted);
|
|
||||||
break;
|
|
||||||
case "T":
|
|
||||||
change.Set(Models.ChangeState.TypeChanged);
|
|
||||||
break;
|
|
||||||
case "TM":
|
|
||||||
change.Set(Models.ChangeState.TypeChanged, Models.ChangeState.Modified);
|
|
||||||
break;
|
|
||||||
case "TT":
|
|
||||||
change.Set(Models.ChangeState.TypeChanged, Models.ChangeState.TypeChanged);
|
|
||||||
break;
|
|
||||||
case "TD":
|
|
||||||
change.Set(Models.ChangeState.TypeChanged, Models.ChangeState.Deleted);
|
|
||||||
break;
|
|
||||||
case "A":
|
|
||||||
change.Set(Models.ChangeState.Added);
|
|
||||||
break;
|
|
||||||
case "AM":
|
|
||||||
change.Set(Models.ChangeState.Added, Models.ChangeState.Modified);
|
|
||||||
break;
|
|
||||||
case "AT":
|
|
||||||
change.Set(Models.ChangeState.Added, Models.ChangeState.TypeChanged);
|
|
||||||
break;
|
|
||||||
case "AD":
|
|
||||||
change.Set(Models.ChangeState.Added, Models.ChangeState.Deleted);
|
|
||||||
break;
|
|
||||||
case "D":
|
|
||||||
change.Set(Models.ChangeState.Deleted);
|
|
||||||
break;
|
|
||||||
case "R":
|
|
||||||
change.Set(Models.ChangeState.Renamed);
|
|
||||||
break;
|
|
||||||
case "RM":
|
|
||||||
change.Set(Models.ChangeState.Renamed, Models.ChangeState.Modified);
|
|
||||||
break;
|
|
||||||
case "RT":
|
|
||||||
change.Set(Models.ChangeState.Renamed, Models.ChangeState.TypeChanged);
|
|
||||||
break;
|
|
||||||
case "RD":
|
|
||||||
change.Set(Models.ChangeState.Renamed, Models.ChangeState.Deleted);
|
|
||||||
break;
|
|
||||||
case "C":
|
|
||||||
change.Set(Models.ChangeState.Copied);
|
|
||||||
break;
|
|
||||||
case "CM":
|
|
||||||
change.Set(Models.ChangeState.Copied, Models.ChangeState.Modified);
|
|
||||||
break;
|
|
||||||
case "CT":
|
|
||||||
change.Set(Models.ChangeState.Copied, Models.ChangeState.TypeChanged);
|
|
||||||
break;
|
|
||||||
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);
|
|
||||||
break;
|
|
||||||
case "AU":
|
|
||||||
change.Set(Models.ChangeState.Added, Models.ChangeState.Unmerged);
|
|
||||||
break;
|
|
||||||
case "UD":
|
|
||||||
change.Set(Models.ChangeState.Unmerged, Models.ChangeState.Deleted);
|
|
||||||
break;
|
|
||||||
case "UA":
|
|
||||||
change.Set(Models.ChangeState.Unmerged, Models.ChangeState.Added);
|
|
||||||
break;
|
|
||||||
case "DU":
|
|
||||||
change.Set(Models.ChangeState.Deleted, Models.ChangeState.Unmerged);
|
|
||||||
break;
|
|
||||||
case "AA":
|
|
||||||
change.Set(Models.ChangeState.Added, Models.ChangeState.Added);
|
|
||||||
break;
|
|
||||||
case "UU":
|
|
||||||
change.Set(Models.ChangeState.Unmerged, Models.ChangeState.Unmerged);
|
|
||||||
break;
|
|
||||||
case "??":
|
|
||||||
change.Set(Models.ChangeState.Untracked, Models.ChangeState.Untracked);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_changes.Add(change);
|
var lines = rs.StdOut.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries);
|
||||||
}
|
foreach (var line in lines)
|
||||||
|
{
|
||||||
|
var match = REG_FORMAT().Match(line);
|
||||||
|
if (!match.Success)
|
||||||
|
continue;
|
||||||
|
|
||||||
private readonly List<Models.Change> _changes = new List<Models.Change>();
|
var change = new Models.Change() { Path = match.Groups[2].Value };
|
||||||
|
var status = match.Groups[1].Value;
|
||||||
|
|
||||||
|
switch (status)
|
||||||
|
{
|
||||||
|
case " M":
|
||||||
|
change.Set(Models.ChangeState.None, Models.ChangeState.Modified);
|
||||||
|
break;
|
||||||
|
case " T":
|
||||||
|
change.Set(Models.ChangeState.None, Models.ChangeState.TypeChanged);
|
||||||
|
break;
|
||||||
|
case " A":
|
||||||
|
change.Set(Models.ChangeState.None, Models.ChangeState.Added);
|
||||||
|
break;
|
||||||
|
case " D":
|
||||||
|
change.Set(Models.ChangeState.None, Models.ChangeState.Deleted);
|
||||||
|
break;
|
||||||
|
case " R":
|
||||||
|
change.Set(Models.ChangeState.None, Models.ChangeState.Renamed);
|
||||||
|
break;
|
||||||
|
case " C":
|
||||||
|
change.Set(Models.ChangeState.None, Models.ChangeState.Copied);
|
||||||
|
break;
|
||||||
|
case "M":
|
||||||
|
change.Set(Models.ChangeState.Modified);
|
||||||
|
break;
|
||||||
|
case "MM":
|
||||||
|
change.Set(Models.ChangeState.Modified, Models.ChangeState.Modified);
|
||||||
|
break;
|
||||||
|
case "MT":
|
||||||
|
change.Set(Models.ChangeState.Modified, Models.ChangeState.TypeChanged);
|
||||||
|
break;
|
||||||
|
case "MD":
|
||||||
|
change.Set(Models.ChangeState.Modified, Models.ChangeState.Deleted);
|
||||||
|
break;
|
||||||
|
case "T":
|
||||||
|
change.Set(Models.ChangeState.TypeChanged);
|
||||||
|
break;
|
||||||
|
case "TM":
|
||||||
|
change.Set(Models.ChangeState.TypeChanged, Models.ChangeState.Modified);
|
||||||
|
break;
|
||||||
|
case "TT":
|
||||||
|
change.Set(Models.ChangeState.TypeChanged, Models.ChangeState.TypeChanged);
|
||||||
|
break;
|
||||||
|
case "TD":
|
||||||
|
change.Set(Models.ChangeState.TypeChanged, Models.ChangeState.Deleted);
|
||||||
|
break;
|
||||||
|
case "A":
|
||||||
|
change.Set(Models.ChangeState.Added);
|
||||||
|
break;
|
||||||
|
case "AM":
|
||||||
|
change.Set(Models.ChangeState.Added, Models.ChangeState.Modified);
|
||||||
|
break;
|
||||||
|
case "AT":
|
||||||
|
change.Set(Models.ChangeState.Added, Models.ChangeState.TypeChanged);
|
||||||
|
break;
|
||||||
|
case "AD":
|
||||||
|
change.Set(Models.ChangeState.Added, Models.ChangeState.Deleted);
|
||||||
|
break;
|
||||||
|
case "D":
|
||||||
|
change.Set(Models.ChangeState.Deleted);
|
||||||
|
break;
|
||||||
|
case "R":
|
||||||
|
change.Set(Models.ChangeState.Renamed);
|
||||||
|
break;
|
||||||
|
case "RM":
|
||||||
|
change.Set(Models.ChangeState.Renamed, Models.ChangeState.Modified);
|
||||||
|
break;
|
||||||
|
case "RT":
|
||||||
|
change.Set(Models.ChangeState.Renamed, Models.ChangeState.TypeChanged);
|
||||||
|
break;
|
||||||
|
case "RD":
|
||||||
|
change.Set(Models.ChangeState.Renamed, Models.ChangeState.Deleted);
|
||||||
|
break;
|
||||||
|
case "C":
|
||||||
|
change.Set(Models.ChangeState.Copied);
|
||||||
|
break;
|
||||||
|
case "CM":
|
||||||
|
change.Set(Models.ChangeState.Copied, Models.ChangeState.Modified);
|
||||||
|
break;
|
||||||
|
case "CT":
|
||||||
|
change.Set(Models.ChangeState.Copied, Models.ChangeState.TypeChanged);
|
||||||
|
break;
|
||||||
|
case "CD":
|
||||||
|
change.Set(Models.ChangeState.Copied, Models.ChangeState.Deleted);
|
||||||
|
break;
|
||||||
|
case "DD":
|
||||||
|
change.ConflictReason = Models.ConflictReason.BothDeleted;
|
||||||
|
change.Set(Models.ChangeState.None, Models.ChangeState.Conflicted);
|
||||||
|
break;
|
||||||
|
case "AU":
|
||||||
|
change.ConflictReason = Models.ConflictReason.AddedByUs;
|
||||||
|
change.Set(Models.ChangeState.None, Models.ChangeState.Conflicted);
|
||||||
|
break;
|
||||||
|
case "UD":
|
||||||
|
change.ConflictReason = Models.ConflictReason.DeletedByThem;
|
||||||
|
change.Set(Models.ChangeState.None, Models.ChangeState.Conflicted);
|
||||||
|
break;
|
||||||
|
case "UA":
|
||||||
|
change.ConflictReason = Models.ConflictReason.AddedByThem;
|
||||||
|
change.Set(Models.ChangeState.None, Models.ChangeState.Conflicted);
|
||||||
|
break;
|
||||||
|
case "DU":
|
||||||
|
change.ConflictReason = Models.ConflictReason.DeletedByUs;
|
||||||
|
change.Set(Models.ChangeState.None, Models.ChangeState.Conflicted);
|
||||||
|
break;
|
||||||
|
case "AA":
|
||||||
|
change.ConflictReason = Models.ConflictReason.BothAdded;
|
||||||
|
change.Set(Models.ChangeState.None, Models.ChangeState.Conflicted);
|
||||||
|
break;
|
||||||
|
case "UU":
|
||||||
|
change.ConflictReason = Models.ConflictReason.BothModified;
|
||||||
|
change.Set(Models.ChangeState.None, Models.ChangeState.Conflicted);
|
||||||
|
break;
|
||||||
|
case "??":
|
||||||
|
change.Set(Models.ChangeState.None, Models.ChangeState.Untracked);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (change.Index != Models.ChangeState.None || change.WorkTree != Models.ChangeState.None)
|
||||||
|
outs.Add(change);
|
||||||
|
}
|
||||||
|
|
||||||
|
return outs;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
using System.Collections.Generic;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
namespace SourceGit.Commands
|
namespace SourceGit.Commands
|
||||||
|
@ -17,27 +18,31 @@ namespace SourceGit.Commands
|
||||||
|
|
||||||
public List<Models.Remote> Result()
|
public List<Models.Remote> Result()
|
||||||
{
|
{
|
||||||
Exec();
|
var outs = new List<Models.Remote>();
|
||||||
return _loaded;
|
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;
|
|
||||||
|
|
||||||
var remote = new Models.Remote()
|
|
||||||
{
|
{
|
||||||
Name = match.Groups[1].Value,
|
var match = REG_REMOTE().Match(line);
|
||||||
URL = match.Groups[2].Value,
|
if (!match.Success)
|
||||||
};
|
continue;
|
||||||
|
|
||||||
if (_loaded.Find(x => x.Name == remote.Name) != null)
|
var remote = new Models.Remote()
|
||||||
return;
|
{
|
||||||
_loaded.Add(remote);
|
Name = match.Groups[1].Value,
|
||||||
|
URL = match.Groups[2].Value,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (outs.Find(x => x.Name == remote.Name) != null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
outs.Add(remote);
|
||||||
|
}
|
||||||
|
|
||||||
|
return outs;
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly List<Models.Remote> _loaded = new List<Models.Remote>();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,87 +6,87 @@ namespace SourceGit.Commands
|
||||||
{
|
{
|
||||||
public partial class QueryStagedChangesWithAmend : Command
|
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();
|
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.*)$")]
|
[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();
|
private static partial Regex REG_FORMAT2();
|
||||||
|
|
||||||
public QueryStagedChangesWithAmend(string repo)
|
public QueryStagedChangesWithAmend(string repo, string parent)
|
||||||
{
|
{
|
||||||
WorkingDirectory = repo;
|
WorkingDirectory = repo;
|
||||||
Context = repo;
|
Context = repo;
|
||||||
Args = "diff-index --cached -M HEAD^";
|
Args = $"diff-index --cached -M {parent}";
|
||||||
|
_parent = parent;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Models.Change> Result()
|
public List<Models.Change> Result()
|
||||||
{
|
{
|
||||||
var rs = ReadToEnd();
|
var rs = ReadToEnd();
|
||||||
if (rs.IsSuccess)
|
if (!rs.IsSuccess)
|
||||||
|
return [];
|
||||||
|
|
||||||
|
var changes = new List<Models.Change>();
|
||||||
|
var lines = rs.StdOut.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries);
|
||||||
|
foreach (var line in lines)
|
||||||
{
|
{
|
||||||
var changes = new List<Models.Change>();
|
var match = REG_FORMAT2().Match(line);
|
||||||
var lines = rs.StdOut.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries);
|
if (match.Success)
|
||||||
foreach (var line in lines)
|
|
||||||
{
|
{
|
||||||
var match = REG_FORMAT2().Match(line);
|
var change = new Models.Change()
|
||||||
if (match.Success)
|
|
||||||
{
|
{
|
||||||
var change = new Models.Change()
|
Path = match.Groups[3].Value,
|
||||||
|
DataForAmend = new Models.ChangeDataForAmend()
|
||||||
{
|
{
|
||||||
Path = match.Groups[3].Value,
|
FileMode = match.Groups[1].Value,
|
||||||
DataForAmend = new Models.ChangeDataForAmend()
|
ObjectHash = match.Groups[2].Value,
|
||||||
{
|
ParentSHA = _parent,
|
||||||
FileMode = match.Groups[1].Value,
|
},
|
||||||
ObjectHash = match.Groups[2].Value,
|
};
|
||||||
},
|
change.Set(Models.ChangeState.Renamed);
|
||||||
};
|
changes.Add(change);
|
||||||
change.Set(Models.ChangeState.Renamed);
|
continue;
|
||||||
changes.Add(change);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
match = REG_FORMAT1().Match(line);
|
|
||||||
if (match.Success)
|
|
||||||
{
|
|
||||||
var change = new Models.Change()
|
|
||||||
{
|
|
||||||
Path = match.Groups[4].Value,
|
|
||||||
DataForAmend = new Models.ChangeDataForAmend()
|
|
||||||
{
|
|
||||||
FileMode = match.Groups[1].Value,
|
|
||||||
ObjectHash = match.Groups[2].Value,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
var type = match.Groups[3].Value;
|
|
||||||
switch (type)
|
|
||||||
{
|
|
||||||
case "A":
|
|
||||||
change.Set(Models.ChangeState.Added);
|
|
||||||
break;
|
|
||||||
case "C":
|
|
||||||
change.Set(Models.ChangeState.Copied);
|
|
||||||
break;
|
|
||||||
case "D":
|
|
||||||
change.Set(Models.ChangeState.Deleted);
|
|
||||||
break;
|
|
||||||
case "M":
|
|
||||||
change.Set(Models.ChangeState.Modified);
|
|
||||||
break;
|
|
||||||
case "T":
|
|
||||||
change.Set(Models.ChangeState.TypeChanged);
|
|
||||||
break;
|
|
||||||
case "U":
|
|
||||||
change.Set(Models.ChangeState.Unmerged);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
changes.Add(change);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return changes;
|
match = REG_FORMAT1().Match(line);
|
||||||
|
if (match.Success)
|
||||||
|
{
|
||||||
|
var change = new Models.Change()
|
||||||
|
{
|
||||||
|
Path = match.Groups[4].Value,
|
||||||
|
DataForAmend = new Models.ChangeDataForAmend()
|
||||||
|
{
|
||||||
|
FileMode = match.Groups[1].Value,
|
||||||
|
ObjectHash = match.Groups[2].Value,
|
||||||
|
ParentSHA = _parent,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
var type = match.Groups[3].Value;
|
||||||
|
switch (type)
|
||||||
|
{
|
||||||
|
case "A":
|
||||||
|
change.Set(Models.ChangeState.Added);
|
||||||
|
break;
|
||||||
|
case "C":
|
||||||
|
change.Set(Models.ChangeState.Copied);
|
||||||
|
break;
|
||||||
|
case "D":
|
||||||
|
change.Set(Models.ChangeState.Deleted);
|
||||||
|
break;
|
||||||
|
case "M":
|
||||||
|
change.Set(Models.ChangeState.Modified);
|
||||||
|
break;
|
||||||
|
case "T":
|
||||||
|
change.Set(Models.ChangeState.TypeChanged);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
changes.Add(change);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return [];
|
return changes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private readonly string _parent;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,76 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Text.RegularExpressions;
|
|
||||||
|
|
||||||
namespace SourceGit.Commands
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Query stash changes. Requires git >= 2.32.0
|
|
||||||
/// </summary>
|
|
||||||
public partial class QueryStashChanges : Command
|
|
||||||
{
|
|
||||||
[GeneratedRegex(@"^([MADC])\s+(.+)$")]
|
|
||||||
private static partial Regex REG_FORMAT();
|
|
||||||
[GeneratedRegex(@"^R[0-9]{0,4}\s+(.+)$")]
|
|
||||||
private static partial Regex REG_RENAME_FORMAT();
|
|
||||||
|
|
||||||
public QueryStashChanges(string repo, string stash)
|
|
||||||
{
|
|
||||||
WorkingDirectory = repo;
|
|
||||||
Context = repo;
|
|
||||||
Args = $"stash show -u --name-status \"{stash}\"";
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<Models.Change> Result()
|
|
||||||
{
|
|
||||||
var rs = ReadToEnd();
|
|
||||||
if (!rs.IsSuccess)
|
|
||||||
return [];
|
|
||||||
|
|
||||||
var lines = rs.StdOut.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries);
|
|
||||||
var outs = new List<Models.Change>();
|
|
||||||
foreach (var line in lines)
|
|
||||||
{
|
|
||||||
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);
|
|
||||||
outs.Add(renamed);
|
|
||||||
}
|
|
||||||
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
outs.Add(change);
|
|
||||||
break;
|
|
||||||
case 'A':
|
|
||||||
change.Set(Models.ChangeState.Added);
|
|
||||||
outs.Add(change);
|
|
||||||
break;
|
|
||||||
case 'D':
|
|
||||||
change.Set(Models.ChangeState.Deleted);
|
|
||||||
outs.Add(change);
|
|
||||||
break;
|
|
||||||
case 'C':
|
|
||||||
change.Set(Models.ChangeState.Copied);
|
|
||||||
outs.Add(change);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
outs.Sort((l, r) => string.Compare(l.Path, r.Path, StringComparison.Ordinal));
|
|
||||||
return outs;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -9,52 +9,60 @@ namespace SourceGit.Commands
|
||||||
{
|
{
|
||||||
WorkingDirectory = repo;
|
WorkingDirectory = repo;
|
||||||
Context = repo;
|
Context = repo;
|
||||||
Args = "stash list --format=%H%n%P%n%ct%n%gd%n%s";
|
Args = $"stash list -z --no-show-signature --format=\"%H%n%P%n%ct%n%gd%n%B\"";
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Models.Stash> Result()
|
public List<Models.Stash> Result()
|
||||||
{
|
{
|
||||||
Exec();
|
var outs = new List<Models.Stash>();
|
||||||
return _stashes;
|
var rs = ReadToEnd();
|
||||||
}
|
if (!rs.IsSuccess)
|
||||||
|
return outs;
|
||||||
|
|
||||||
protected override void OnReadline(string line)
|
var items = rs.StdOut.Split('\0', StringSplitOptions.RemoveEmptyEntries);
|
||||||
{
|
foreach (var item in items)
|
||||||
switch (_nextLineIdx)
|
|
||||||
{
|
{
|
||||||
case 0:
|
var current = new Models.Stash();
|
||||||
_current = new Models.Stash() { SHA = line };
|
|
||||||
_stashes.Add(_current);
|
var nextPartIdx = 0;
|
||||||
break;
|
var start = 0;
|
||||||
case 1:
|
var end = item.IndexOf('\n', start);
|
||||||
ParseParent(line);
|
while (end > 0 && nextPartIdx < 4)
|
||||||
break;
|
{
|
||||||
case 2:
|
var line = item.Substring(start, end - start);
|
||||||
_current.Time = ulong.Parse(line);
|
|
||||||
break;
|
switch (nextPartIdx)
|
||||||
case 3:
|
{
|
||||||
_current.Name = line;
|
case 0:
|
||||||
break;
|
current.SHA = line;
|
||||||
case 4:
|
break;
|
||||||
_current.Message = line;
|
case 1:
|
||||||
break;
|
if (line.Length > 6)
|
||||||
|
current.Parents.AddRange(line.Split(' ', StringSplitOptions.RemoveEmptyEntries));
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
current.Time = ulong.Parse(line);
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
current.Name = line;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
nextPartIdx++;
|
||||||
|
|
||||||
|
start = end + 1;
|
||||||
|
if (start >= item.Length - 1)
|
||||||
|
break;
|
||||||
|
|
||||||
|
end = item.IndexOf('\n', start);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (start < item.Length)
|
||||||
|
current.Message = item.Substring(start);
|
||||||
|
|
||||||
|
outs.Add(current);
|
||||||
}
|
}
|
||||||
|
return outs;
|
||||||
_nextLineIdx++;
|
|
||||||
if (_nextLineIdx > 4)
|
|
||||||
_nextLineIdx = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
|
@ -6,12 +7,12 @@ namespace SourceGit.Commands
|
||||||
{
|
{
|
||||||
public partial class QuerySubmodules : Command
|
public partial class QuerySubmodules : Command
|
||||||
{
|
{
|
||||||
[GeneratedRegex(@"^[\-\+ ][0-9a-f]+\s(.*)\s\(.*\)$")]
|
[GeneratedRegex(@"^([U\-\+ ])([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+(.+)$")]
|
|
||||||
private static partial Regex REG_FORMAT_STATUS();
|
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)
|
public QuerySubmodules(string repo)
|
||||||
{
|
{
|
||||||
|
@ -25,52 +26,117 @@ namespace SourceGit.Commands
|
||||||
var submodules = new List<Models.Submodule>();
|
var submodules = new List<Models.Submodule>();
|
||||||
var rs = ReadToEnd();
|
var rs = ReadToEnd();
|
||||||
|
|
||||||
var builder = new StringBuilder();
|
var lines = rs.StdOut.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries);
|
||||||
var lines = rs.StdOut.Split(['\r', '\n'], System.StringSplitOptions.RemoveEmptyEntries);
|
var map = new Dictionary<string, Models.Submodule>();
|
||||||
|
var needCheckLocalChanges = false;
|
||||||
foreach (var line in lines)
|
foreach (var line in lines)
|
||||||
{
|
{
|
||||||
var match = REG_FORMAT1().Match(line);
|
var match = REG_FORMAT_STATUS().Match(line);
|
||||||
if (match.Success)
|
if (match.Success)
|
||||||
{
|
{
|
||||||
var path = match.Groups[1].Value;
|
var stat = match.Groups[1].Value;
|
||||||
builder.Append($"\"{path}\" ");
|
var sha = match.Groups[2].Value;
|
||||||
submodules.Add(new Models.Submodule() { Path = path });
|
var path = match.Groups[3].Value;
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
match = REG_FORMAT2().Match(line);
|
var module = new Models.Submodule() { Path = path, SHA = sha };
|
||||||
if (match.Success)
|
switch (stat[0])
|
||||||
{
|
{
|
||||||
var path = match.Groups[1].Value;
|
case '-':
|
||||||
builder.Append($"\"{path}\" ");
|
module.Status = Models.SubmoduleStatus.NotInited;
|
||||||
submodules.Add(new Models.Submodule() { Path = path });
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (submodules.Count > 0)
|
if (submodules.Count > 0)
|
||||||
{
|
{
|
||||||
Args = $"--no-optional-locks status -uno --porcelain -- {builder}";
|
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();
|
rs = ReadToEnd();
|
||||||
if (!rs.IsSuccess)
|
if (!rs.IsSuccess)
|
||||||
return submodules;
|
return submodules;
|
||||||
|
|
||||||
var dirty = new HashSet<string>();
|
lines = rs.StdOut.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries);
|
||||||
lines = rs.StdOut.Split(['\r', '\n'], System.StringSplitOptions.RemoveEmptyEntries);
|
|
||||||
foreach (var line in lines)
|
foreach (var line in lines)
|
||||||
{
|
{
|
||||||
var match = REG_FORMAT_STATUS().Match(line);
|
var match = REG_FORMAT_DIRTY().Match(line);
|
||||||
if (match.Success)
|
if (match.Success)
|
||||||
{
|
{
|
||||||
var path = match.Groups[1].Value;
|
var path = match.Groups[1].Value;
|
||||||
dirty.Add(path);
|
if (map.TryGetValue(path, out var m))
|
||||||
|
m.Status = Models.SubmoduleStatus.Modified;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var submodule in submodules)
|
|
||||||
submodule.IsDirty = dirty.Contains(submodule.Path);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return submodules;
|
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;
|
Context = repo;
|
||||||
WorkingDirectory = repo;
|
WorkingDirectory = repo;
|
||||||
Args = $"tag -l --format=\"{_boundary}%(refname)%00%(objectname)%00%(*objectname)%00%(creatordate:unix)%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()
|
public List<Models.Tag> Result()
|
||||||
|
@ -24,17 +24,22 @@ namespace SourceGit.Commands
|
||||||
var records = rs.StdOut.Split(_boundary, StringSplitOptions.RemoveEmptyEntries);
|
var records = rs.StdOut.Split(_boundary, StringSplitOptions.RemoveEmptyEntries);
|
||||||
foreach (var record in records)
|
foreach (var record in records)
|
||||||
{
|
{
|
||||||
var subs = record.Split('\0', StringSplitOptions.None);
|
var subs = record.Split('\0');
|
||||||
if (subs.Length != 5)
|
if (subs.Length != 6)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
var message = subs[4].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()
|
tags.Add(new Models.Tag()
|
||||||
{
|
{
|
||||||
Name = subs[0].Substring(10),
|
Name = name,
|
||||||
SHA = string.IsNullOrEmpty(subs[2]) ? subs[1] : subs[2],
|
IsAnnotated = subs[1].Equals("tag", StringComparison.Ordinal),
|
||||||
CreatorDate = ulong.Parse(subs[3]),
|
SHA = string.IsNullOrEmpty(subs[3]) ? subs[2] : subs[3],
|
||||||
Message = string.IsNullOrEmpty(message) ? null : message,
|
CreatorDate = ulong.Parse(subs[4]),
|
||||||
|
Message = message,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
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;
|
namespace SourceGit.Commands
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace SourceGit.Commands
|
|
||||||
{
|
{
|
||||||
public class Reset : Command
|
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)
|
public Reset(string repo, string revision, string mode)
|
||||||
{
|
{
|
||||||
WorkingDirectory = repo;
|
WorkingDirectory = repo;
|
||||||
|
|
|
@ -1,29 +1,52 @@
|
||||||
using System.Collections.Generic;
|
using System.Text;
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace SourceGit.Commands
|
namespace SourceGit.Commands
|
||||||
{
|
{
|
||||||
public class Restore : Command
|
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;
|
WorkingDirectory = repo;
|
||||||
Context = 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('"');
|
||||||
|
}
|
||||||
|
|
||||||
|
Args = builder.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Restore(string repo, List<string> files, string extra)
|
/// <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;
|
WorkingDirectory = repo;
|
||||||
Context = repo;
|
Context = repo;
|
||||||
|
|
||||||
StringBuilder builder = new StringBuilder();
|
var builder = new StringBuilder();
|
||||||
builder.Append("restore ");
|
builder.Append("restore ");
|
||||||
if (!string.IsNullOrEmpty(extra))
|
builder.Append(isStaged ? "--staged " : "--worktree --recurse-submodules ");
|
||||||
builder.Append(extra).Append(" ");
|
builder.Append("--pathspec-from-file=\"");
|
||||||
builder.Append("--");
|
builder.Append(pathspecFile);
|
||||||
foreach (var f in files)
|
builder.Append('"');
|
||||||
builder.Append(' ').Append('"').Append(f).Append('"');
|
|
||||||
Args = builder.ToString();
|
Args = builder.ToString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,15 +10,15 @@ namespace SourceGit.Commands
|
||||||
{
|
{
|
||||||
public static void Run(string repo, string revision, string file, string saveTo)
|
public static void Run(string repo, string revision, string file, string saveTo)
|
||||||
{
|
{
|
||||||
|
var dir = Path.GetDirectoryName(saveTo);
|
||||||
|
if (!Directory.Exists(dir))
|
||||||
|
Directory.CreateDirectory(dir);
|
||||||
|
|
||||||
var isLFSFiltered = new IsLFSFiltered(repo, revision, file).Result();
|
var isLFSFiltered = new IsLFSFiltered(repo, revision, file).Result();
|
||||||
if (isLFSFiltered)
|
if (isLFSFiltered)
|
||||||
{
|
{
|
||||||
var tmpFile = saveTo + ".tmp";
|
var pointerStream = QueryFileContent.Run(repo, revision, file);
|
||||||
if (ExecCmd(repo, $"show {revision}:\"{file}\"", tmpFile))
|
ExecCmd(repo, $"lfs smudge", saveTo, pointerStream);
|
||||||
{
|
|
||||||
ExecCmd(repo, $"lfs smudge", saveTo, tmpFile);
|
|
||||||
}
|
|
||||||
File.Delete(tmpFile);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -26,7 +26,7 @@ namespace SourceGit.Commands
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool ExecCmd(string repo, string args, string outputFile, string inputFile = null)
|
private static void ExecCmd(string repo, string args, string outputFile, Stream input = null)
|
||||||
{
|
{
|
||||||
var starter = new ProcessStartInfo();
|
var starter = new ProcessStartInfo();
|
||||||
starter.WorkingDirectory = repo;
|
starter.WorkingDirectory = repo;
|
||||||
|
@ -45,27 +45,11 @@ namespace SourceGit.Commands
|
||||||
{
|
{
|
||||||
var proc = new Process() { StartInfo = starter };
|
var proc = new Process() { StartInfo = starter };
|
||||||
proc.Start();
|
proc.Start();
|
||||||
|
if (input != null)
|
||||||
if (inputFile != null)
|
proc.StandardInput.Write(new StreamReader(input).ReadToEnd());
|
||||||
{
|
|
||||||
using (StreamReader sr = new StreamReader(inputFile))
|
|
||||||
{
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
var line = sr.ReadLine();
|
|
||||||
if (line == null)
|
|
||||||
break;
|
|
||||||
proc.StandardInput.WriteLine(line);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
proc.StandardOutput.BaseStream.CopyTo(sw);
|
proc.StandardOutput.BaseStream.CopyTo(sw);
|
||||||
proc.WaitForExit();
|
proc.WaitForExit();
|
||||||
var rs = proc.ExitCode == 0;
|
|
||||||
proc.Close();
|
proc.Close();
|
||||||
|
|
||||||
return rs;
|
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
|
@ -73,7 +57,6 @@ namespace SourceGit.Commands
|
||||||
{
|
{
|
||||||
App.RaiseException(repo, "Save file failed: " + e.Message);
|
App.RaiseException(repo, "Save file failed: " + e.Message);
|
||||||
});
|
});
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace SourceGit.Commands
|
namespace SourceGit.Commands
|
||||||
{
|
{
|
||||||
|
@ -40,7 +40,7 @@ namespace SourceGit.Commands
|
||||||
if (dateEndIdx == -1)
|
if (dateEndIdx == -1)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var dateStr = line.Substring(0, dateEndIdx);
|
var dateStr = line.AsSpan(0, dateEndIdx);
|
||||||
if (double.TryParse(dateStr, out var date))
|
if (double.TryParse(dateStr, out var date))
|
||||||
statistics.AddCommit(line.Substring(dateEndIdx + 1), date);
|
statistics.AddCommit(line.Substring(dateEndIdx + 1), date);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
using System;
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
namespace SourceGit.Commands
|
namespace SourceGit.Commands
|
||||||
{
|
{
|
||||||
|
@ -10,10 +11,9 @@ namespace SourceGit.Commands
|
||||||
Context = repo;
|
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 = $"-c protocol.file.allow=always submodule add \"{url}\" \"{relativePath}\"";
|
||||||
Args = $"submodule add {url} \"{relativePath}\"";
|
|
||||||
if (!Exec())
|
if (!Exec())
|
||||||
return false;
|
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)
|
if (init)
|
||||||
Args += " --init";
|
builder.Append(" --init");
|
||||||
if (recursive)
|
if (recursive)
|
||||||
Args += " --recursive";
|
builder.Append(" --recursive");
|
||||||
if (useRemote)
|
if (useRemote)
|
||||||
Args += " --remote";
|
builder.Append(" --remote");
|
||||||
if (!string.IsNullOrEmpty(module))
|
if (modules.Count > 0)
|
||||||
Args += $" -- \"{module}\"";
|
{
|
||||||
|
builder.Append(" --");
|
||||||
|
foreach (var module in modules)
|
||||||
|
builder.Append($" \"{module}\"");
|
||||||
|
}
|
||||||
|
|
||||||
_outputHandler = outputHandler;
|
Args = builder.ToString();
|
||||||
return Exec();
|
return Exec();
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool Delete(string relativePath)
|
public bool Deinit(string module, bool force)
|
||||||
{
|
{
|
||||||
Args = $"submodule deinit -f \"{relativePath}\"";
|
Args = force ? $"submodule deinit -f -- \"{module}\"" : $"submodule deinit -- \"{module}\"";
|
||||||
if (!Exec())
|
|
||||||
return false;
|
|
||||||
|
|
||||||
Args = $"rm -rf \"{relativePath}\"";
|
|
||||||
return Exec();
|
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,57 +1,51 @@
|
||||||
using System.Collections.Generic;
|
using System.IO;
|
||||||
using System.IO;
|
|
||||||
|
|
||||||
namespace SourceGit.Commands
|
namespace SourceGit.Commands
|
||||||
{
|
{
|
||||||
public static class Tag
|
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();
|
var cmd = new Command();
|
||||||
cmd.WorkingDirectory = repo;
|
cmd.WorkingDirectory = repo;
|
||||||
cmd.Context = repo;
|
cmd.Context = repo;
|
||||||
cmd.Args = $"tag {name} {basedOn}";
|
cmd.Args = $"tag --no-sign {name} {basedOn}";
|
||||||
|
cmd.Log = log;
|
||||||
return cmd.Exec();
|
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 param = sign ? "--sign -a" : "--no-sign -a";
|
||||||
var cmd = new Command();
|
var cmd = new Command();
|
||||||
cmd.WorkingDirectory = repo;
|
cmd.WorkingDirectory = repo;
|
||||||
cmd.Context = repo;
|
cmd.Context = repo;
|
||||||
cmd.Args = $"tag {param} {name} {basedOn} ";
|
cmd.Args = $"tag {param} {name} {basedOn} ";
|
||||||
|
cmd.Log = log;
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(message))
|
if (!string.IsNullOrEmpty(message))
|
||||||
{
|
{
|
||||||
string tmp = Path.GetTempFileName();
|
string tmp = Path.GetTempFileName();
|
||||||
File.WriteAllText(tmp, message);
|
File.WriteAllText(tmp, message);
|
||||||
cmd.Args += $"-F \"{tmp}\"";
|
cmd.Args += $"-F \"{tmp}\"";
|
||||||
}
|
|
||||||
else
|
var succ = cmd.Exec();
|
||||||
{
|
File.Delete(tmp);
|
||||||
cmd.Args += $"-m {name}";
|
return succ;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cmd.Args += $"-m {name}";
|
||||||
return cmd.Exec();
|
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();
|
var cmd = new Command();
|
||||||
cmd.WorkingDirectory = repo;
|
cmd.WorkingDirectory = repo;
|
||||||
cmd.Context = repo;
|
cmd.Context = repo;
|
||||||
cmd.Args = $"tag --delete {name}";
|
cmd.Args = $"tag --delete {name}";
|
||||||
if (!cmd.Exec())
|
cmd.Log = log;
|
||||||
return false;
|
return cmd.Exec();
|
||||||
|
|
||||||
if (remotes != null)
|
|
||||||
{
|
|
||||||
foreach (var r in remotes)
|
|
||||||
new Push(repo, r.Name, $"refs/tags/{name}", true).Exec();
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,13 +23,11 @@ namespace SourceGit.Commands
|
||||||
_patchBuilder.Append(c.DataForAmend.ObjectHash);
|
_patchBuilder.Append(c.DataForAmend.ObjectHash);
|
||||||
_patchBuilder.Append("\t");
|
_patchBuilder.Append("\t");
|
||||||
_patchBuilder.Append(c.OriginalPath);
|
_patchBuilder.Append(c.OriginalPath);
|
||||||
_patchBuilder.Append("\n");
|
|
||||||
}
|
}
|
||||||
else if (c.Index == Models.ChangeState.Added)
|
else if (c.Index == Models.ChangeState.Added)
|
||||||
{
|
{
|
||||||
_patchBuilder.Append("0 0000000000000000000000000000000000000000\t");
|
_patchBuilder.Append("0 0000000000000000000000000000000000000000\t");
|
||||||
_patchBuilder.Append(c.Path);
|
_patchBuilder.Append(c.Path);
|
||||||
_patchBuilder.Append("\n");
|
|
||||||
}
|
}
|
||||||
else if (c.Index == Models.ChangeState.Deleted)
|
else if (c.Index == Models.ChangeState.Deleted)
|
||||||
{
|
{
|
||||||
|
@ -37,7 +35,6 @@ namespace SourceGit.Commands
|
||||||
_patchBuilder.Append(c.DataForAmend.ObjectHash);
|
_patchBuilder.Append(c.DataForAmend.ObjectHash);
|
||||||
_patchBuilder.Append("\t");
|
_patchBuilder.Append("\t");
|
||||||
_patchBuilder.Append(c.Path);
|
_patchBuilder.Append(c.Path);
|
||||||
_patchBuilder.Append("\n");
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -46,8 +43,9 @@ namespace SourceGit.Commands
|
||||||
_patchBuilder.Append(c.DataForAmend.ObjectHash);
|
_patchBuilder.Append(c.DataForAmend.ObjectHash);
|
||||||
_patchBuilder.Append("\t");
|
_patchBuilder.Append("\t");
|
||||||
_patchBuilder.Append(c.Path);
|
_patchBuilder.Append(c.Path);
|
||||||
_patchBuilder.Append("\n");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_patchBuilder.Append("\n");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -56,7 +56,7 @@ namespace SourceGit.Commands
|
||||||
return worktrees;
|
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 ";
|
Args = "worktree add ";
|
||||||
|
|
||||||
|
@ -78,14 +78,12 @@ namespace SourceGit.Commands
|
||||||
else if (!string.IsNullOrEmpty(name) && !createNew)
|
else if (!string.IsNullOrEmpty(name) && !createNew)
|
||||||
Args += name;
|
Args += name;
|
||||||
|
|
||||||
_outputHandler = outputHandler;
|
|
||||||
return Exec();
|
return Exec();
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool Prune(Action<string> outputHandler)
|
public bool Prune()
|
||||||
{
|
{
|
||||||
Args = "worktree prune -v";
|
Args = "worktree prune -v";
|
||||||
_outputHandler = outputHandler;
|
|
||||||
return Exec();
|
return Exec();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -101,22 +99,14 @@ namespace SourceGit.Commands
|
||||||
return Exec();
|
return Exec();
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool Remove(string fullpath, bool force, Action<string> outputHandler)
|
public bool Remove(string fullpath, bool force)
|
||||||
{
|
{
|
||||||
if (force)
|
if (force)
|
||||||
Args = $"worktree remove -f \"{fullpath}\"";
|
Args = $"worktree remove -f \"{fullpath}\"";
|
||||||
else
|
else
|
||||||
Args = $"worktree remove \"{fullpath}\"";
|
Args = $"worktree remove \"{fullpath}\"";
|
||||||
|
|
||||||
_outputHandler = outputHandler;
|
|
||||||
return Exec();
|
return Exec();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnReadline(string line)
|
|
||||||
{
|
|
||||||
_outputHandler?.Invoke(line);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Action<string> _outputHandler = null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,8 +7,11 @@ namespace SourceGit.Converters
|
||||||
{
|
{
|
||||||
public static class ListConverters
|
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 =
|
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 =
|
public static readonly FuncValueConverter<IList, bool> IsNullOrEmpty =
|
||||||
new FuncValueConverter<IList, bool>(v => v == null || v.Count == 0);
|
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 System.IO;
|
||||||
|
|
||||||
using Avalonia.Data.Converters;
|
using Avalonia.Data.Converters;
|
||||||
|
@ -22,7 +22,7 @@ namespace SourceGit.Converters
|
||||||
var home = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
|
var home = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
|
||||||
var prefixLen = home.EndsWith('/') ? home.Length - 1 : home.Length;
|
var prefixLen = home.EndsWith('/') ? home.Length - 1 : home.Length;
|
||||||
if (v.StartsWith(home, StringComparison.Ordinal))
|
if (v.StartsWith(home, StringComparison.Ordinal))
|
||||||
return "~" + v.Substring(prefixLen);
|
return $"~{v.AsSpan(prefixLen)}";
|
||||||
|
|
||||||
return v;
|
return v;
|
||||||
});
|
});
|
||||||
|
|
|
@ -17,7 +17,7 @@ namespace SourceGit.Models
|
||||||
{
|
{
|
||||||
public interface IAvatarHost
|
public interface IAvatarHost
|
||||||
{
|
{
|
||||||
void OnAvatarResourceChanged(string email);
|
void OnAvatarResourceChanged(string email, Bitmap image);
|
||||||
}
|
}
|
||||||
|
|
||||||
public partial class AvatarManager
|
public partial class AvatarManager
|
||||||
|
@ -26,10 +26,7 @@ namespace SourceGit.Models
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
if (_instance == null)
|
return _instance ??= new AvatarManager();
|
||||||
_instance = new AvatarManager();
|
|
||||||
|
|
||||||
return _instance;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,7 +35,7 @@ namespace SourceGit.Models
|
||||||
[GeneratedRegex(@"^(?:(\d+)\+)?(.+?)@.+\.github\.com$")]
|
[GeneratedRegex(@"^(?:(\d+)\+)?(.+?)@.+\.github\.com$")]
|
||||||
private static partial Regex REG_GITHUB_USER_EMAIL();
|
private static partial Regex REG_GITHUB_USER_EMAIL();
|
||||||
|
|
||||||
private object _synclock = new object();
|
private readonly Lock _synclock = new();
|
||||||
private string _storePath;
|
private string _storePath;
|
||||||
private List<IAvatarHost> _avatars = new List<IAvatarHost>();
|
private List<IAvatarHost> _avatars = new List<IAvatarHost>();
|
||||||
private Dictionary<string, Bitmap> _resources = new Dictionary<string, Bitmap>();
|
private Dictionary<string, Bitmap> _resources = new Dictionary<string, Bitmap>();
|
||||||
|
@ -119,7 +116,7 @@ namespace SourceGit.Models
|
||||||
Dispatcher.UIThread.InvokeAsync(() =>
|
Dispatcher.UIThread.InvokeAsync(() =>
|
||||||
{
|
{
|
||||||
_resources[email] = img;
|
_resources[email] = img;
|
||||||
NotifyResourceChanged(email);
|
NotifyResourceChanged(email, img);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -144,14 +141,13 @@ namespace SourceGit.Models
|
||||||
if (_defaultAvatars.Contains(email))
|
if (_defaultAvatars.Contains(email))
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
if (_resources.ContainsKey(email))
|
_resources.Remove(email);
|
||||||
_resources.Remove(email);
|
|
||||||
|
|
||||||
var localFile = Path.Combine(_storePath, GetEmailHash(email));
|
var localFile = Path.Combine(_storePath, GetEmailHash(email));
|
||||||
if (File.Exists(localFile))
|
if (File.Exists(localFile))
|
||||||
File.Delete(localFile);
|
File.Delete(localFile);
|
||||||
|
|
||||||
NotifyResourceChanged(email);
|
NotifyResourceChanged(email, null);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -179,13 +175,40 @@ namespace SourceGit.Models
|
||||||
|
|
||||||
lock (_synclock)
|
lock (_synclock)
|
||||||
{
|
{
|
||||||
if (!_requesting.Contains(email))
|
_requesting.Add(email);
|
||||||
_requesting.Add(email);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
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;
|
||||||
|
|
||||||
|
_resources[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)
|
private void LoadDefaultAvatar(string key, string img)
|
||||||
{
|
{
|
||||||
var icon = AssetLoader.Open(new Uri($"avares://SourceGit/Resources/Images/{img}", UriKind.RelativeOrAbsolute));
|
var icon = AssetLoader.Open(new Uri($"avares://SourceGit/Resources/Images/{img}", UriKind.RelativeOrAbsolute));
|
||||||
|
@ -196,19 +219,17 @@ namespace SourceGit.Models
|
||||||
private string GetEmailHash(string email)
|
private string GetEmailHash(string email)
|
||||||
{
|
{
|
||||||
var lowered = email.ToLower(CultureInfo.CurrentCulture).Trim();
|
var lowered = email.ToLower(CultureInfo.CurrentCulture).Trim();
|
||||||
var hash = MD5.HashData(Encoding.Default.GetBytes(lowered).AsSpan());
|
var hash = MD5.HashData(Encoding.Default.GetBytes(lowered));
|
||||||
var builder = new StringBuilder(hash.Length * 2);
|
var builder = new StringBuilder(hash.Length * 2);
|
||||||
foreach (var c in hash)
|
foreach (var c in hash)
|
||||||
builder.Append(c.ToString("x2"));
|
builder.Append(c.ToString("x2"));
|
||||||
return builder.ToString();
|
return builder.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void NotifyResourceChanged(string email)
|
private void NotifyResourceChanged(string email, Bitmap image)
|
||||||
{
|
{
|
||||||
foreach (var avatar in _avatars)
|
foreach (var avatar in _avatars)
|
||||||
{
|
avatar.OnAvatarResourceChanged(email, image);
|
||||||
avatar.OnAvatarResourceChanged(email);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
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;
|
||||||
|
} = [];
|
||||||
|
}
|
||||||
|
}
|
|
@ -23,10 +23,17 @@ namespace SourceGit.Models
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public enum BranchSortMode
|
||||||
|
{
|
||||||
|
Name = 0,
|
||||||
|
CommitterDate,
|
||||||
|
}
|
||||||
|
|
||||||
public class Branch
|
public class Branch
|
||||||
{
|
{
|
||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
public string FullName { get; set; }
|
public string FullName { get; set; }
|
||||||
|
public ulong CommitterDate { get; set; }
|
||||||
public string Head { get; set; }
|
public string Head { get; set; }
|
||||||
public bool IsLocal { get; set; }
|
public bool IsLocal { get; set; }
|
||||||
public bool IsCurrent { get; set; }
|
public bool IsCurrent { get; set; }
|
||||||
|
@ -34,7 +41,7 @@ namespace SourceGit.Models
|
||||||
public string Upstream { get; set; }
|
public string Upstream { get; set; }
|
||||||
public BranchTrackStatus TrackStatus { get; set; }
|
public BranchTrackStatus TrackStatus { get; set; }
|
||||||
public string Remote { get; set; }
|
public string Remote { get; set; }
|
||||||
public bool IsUpsteamGone { get; set; }
|
public bool IsUpstreamGone { get; set; }
|
||||||
|
|
||||||
public string FriendlyName => IsLocal ? Name : $"{Remote}/{Name}";
|
public string FriendlyName => IsLocal ? Name : $"{Remote}/{Name}";
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,14 +18,27 @@ namespace SourceGit.Models
|
||||||
Deleted,
|
Deleted,
|
||||||
Renamed,
|
Renamed,
|
||||||
Copied,
|
Copied,
|
||||||
Unmerged,
|
Untracked,
|
||||||
Untracked
|
Conflicted,
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum ConflictReason
|
||||||
|
{
|
||||||
|
None,
|
||||||
|
BothDeleted,
|
||||||
|
AddedByUs,
|
||||||
|
DeletedByThem,
|
||||||
|
AddedByThem,
|
||||||
|
DeletedByUs,
|
||||||
|
BothAdded,
|
||||||
|
BothModified,
|
||||||
}
|
}
|
||||||
|
|
||||||
public class ChangeDataForAmend
|
public class ChangeDataForAmend
|
||||||
{
|
{
|
||||||
public string FileMode { get; set; } = "";
|
public string FileMode { get; set; } = "";
|
||||||
public string ObjectHash { get; set; } = "";
|
public string ObjectHash { get; set; } = "";
|
||||||
|
public string ParentSHA { get; set; } = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
public class Change
|
public class Change
|
||||||
|
@ -35,20 +48,14 @@ namespace SourceGit.Models
|
||||||
public string Path { get; set; } = "";
|
public string Path { get; set; } = "";
|
||||||
public string OriginalPath { get; set; } = "";
|
public string OriginalPath { get; set; } = "";
|
||||||
public ChangeDataForAmend DataForAmend { get; set; } = null;
|
public ChangeDataForAmend DataForAmend { get; set; } = null;
|
||||||
|
public ConflictReason ConflictReason { get; set; } = ConflictReason.None;
|
||||||
|
|
||||||
public bool IsConflict
|
public bool IsConflicted => WorkTree == ChangeState.Conflicted;
|
||||||
{
|
public string ConflictMarker => CONFLICT_MARKERS[(int)ConflictReason];
|
||||||
get
|
public string ConflictDesc => CONFLICT_DESCS[(int)ConflictReason];
|
||||||
{
|
|
||||||
if (Index == ChangeState.Unmerged || WorkTree == ChangeState.Unmerged)
|
public string WorkTreeDesc => TYPE_DESCS[(int)WorkTree];
|
||||||
return true;
|
public string IndexDesc => TYPE_DESCS[(int)Index];
|
||||||
if (Index == ChangeState.Added && WorkTree == ChangeState.Added)
|
|
||||||
return true;
|
|
||||||
if (Index == ChangeState.Deleted && WorkTree == ChangeState.Deleted)
|
|
||||||
return true;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Set(ChangeState index, ChangeState workTree = ChangeState.None)
|
public void Set(ChangeState index, ChangeState workTree = ChangeState.None)
|
||||||
{
|
{
|
||||||
|
@ -76,8 +83,44 @@ namespace SourceGit.Models
|
||||||
|
|
||||||
if (Path[0] == '"')
|
if (Path[0] == '"')
|
||||||
Path = Path.Substring(1, Path.Length - 2);
|
Path = Path.Substring(1, Path.Length - 2);
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(OriginalPath) && OriginalPath[0] == '"')
|
if (!string.IsNullOrEmpty(OriginalPath) && OriginalPath[0] == '"')
|
||||||
OriginalPath = OriginalPath.Substring(1, OriginalPath.Length - 2);
|
OriginalPath = OriginalPath.Substring(1, OriginalPath.Length - 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static readonly string[] TYPE_DESCS =
|
||||||
|
[
|
||||||
|
"Unknown",
|
||||||
|
"Modified",
|
||||||
|
"Type Changed",
|
||||||
|
"Added",
|
||||||
|
"Deleted",
|
||||||
|
"Renamed",
|
||||||
|
"Copied",
|
||||||
|
"Untracked",
|
||||||
|
"Conflict"
|
||||||
|
];
|
||||||
|
private static readonly string[] CONFLICT_MARKERS =
|
||||||
|
[
|
||||||
|
string.Empty,
|
||||||
|
"DD",
|
||||||
|
"AU",
|
||||||
|
"UD",
|
||||||
|
"UA",
|
||||||
|
"DU",
|
||||||
|
"AA",
|
||||||
|
"UU"
|
||||||
|
];
|
||||||
|
private static readonly string[] CONFLICT_DESCS =
|
||||||
|
[
|
||||||
|
string.Empty,
|
||||||
|
"Both deleted",
|
||||||
|
"Added by us",
|
||||||
|
"Deleted by them",
|
||||||
|
"Added by them",
|
||||||
|
"Deleted by us",
|
||||||
|
"Both added",
|
||||||
|
"Both modified"
|
||||||
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,10 +13,14 @@ namespace SourceGit.Models
|
||||||
ByCommitter,
|
ByCommitter,
|
||||||
ByMessage,
|
ByMessage,
|
||||||
ByFile,
|
ByFile,
|
||||||
|
ByContent,
|
||||||
}
|
}
|
||||||
|
|
||||||
public class Commit
|
public class Commit
|
||||||
{
|
{
|
||||||
|
// As retrieved by: git mktree </dev/null
|
||||||
|
public const string EmptyTreeSHA1 = "4b825dc642cb6eb9a060e54bf8d69288fbee4904";
|
||||||
|
|
||||||
public static double OpacityForNotMerged
|
public static double OpacityForNotMerged
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
|
@ -29,14 +33,14 @@ namespace SourceGit.Models
|
||||||
public User Committer { get; set; } = User.Invalid;
|
public User Committer { get; set; } = User.Invalid;
|
||||||
public ulong CommitterTime { get; set; } = 0;
|
public ulong CommitterTime { get; set; } = 0;
|
||||||
public string Subject { get; set; } = string.Empty;
|
public string Subject { get; set; } = string.Empty;
|
||||||
public List<string> Parents { get; set; } = new List<string>();
|
public List<string> Parents { get; set; } = new();
|
||||||
public List<Decorator> Decorators { get; set; } = new List<Decorator>();
|
public List<Decorator> Decorators { get; set; } = new();
|
||||||
public bool HasDecorators => Decorators.Count > 0;
|
public bool HasDecorators => Decorators.Count > 0;
|
||||||
|
|
||||||
public string AuthorTimeStr => DateTime.UnixEpoch.AddSeconds(AuthorTime).ToLocalTime().ToString(DateTimeFormat.Actived.DateTime);
|
public string AuthorTimeStr => DateTime.UnixEpoch.AddSeconds(AuthorTime).ToLocalTime().ToString(DateTimeFormat.Active.DateTime);
|
||||||
public string CommitterTimeStr => DateTime.UnixEpoch.AddSeconds(CommitterTime).ToLocalTime().ToString(DateTimeFormat.Actived.DateTime);
|
public string CommitterTimeStr => DateTime.UnixEpoch.AddSeconds(CommitterTime).ToLocalTime().ToString(DateTimeFormat.Active.DateTime);
|
||||||
public string AuthorTimeShortStr => DateTime.UnixEpoch.AddSeconds(AuthorTime).ToLocalTime().ToString(DateTimeFormat.Actived.DateOnly);
|
public string AuthorTimeShortStr => DateTime.UnixEpoch.AddSeconds(AuthorTime).ToLocalTime().ToString(DateTimeFormat.Active.DateOnly);
|
||||||
public string CommitterTimeShortStr => DateTime.UnixEpoch.AddSeconds(CommitterTime).ToLocalTime().ToString(DateTimeFormat.Actived.DateOnly);
|
public string CommitterTimeShortStr => DateTime.UnixEpoch.AddSeconds(CommitterTime).ToLocalTime().ToString(DateTimeFormat.Active.DateOnly);
|
||||||
|
|
||||||
public bool IsMerged { get; set; } = false;
|
public bool IsMerged { get; set; } = false;
|
||||||
public bool IsCommitterVisible => !Author.Equals(Committer) || AuthorTime != CommitterTime;
|
public bool IsCommitterVisible => !Author.Equals(Committer) || AuthorTime != CommitterTime;
|
||||||
|
@ -45,7 +49,7 @@ namespace SourceGit.Models
|
||||||
public int Color { get; set; } = 0;
|
public int Color { get; set; } = 0;
|
||||||
public double Opacity => IsMerged ? 1 : OpacityForNotMerged;
|
public double Opacity => IsMerged ? 1 : OpacityForNotMerged;
|
||||||
public FontWeight FontWeight => IsCurrentHead ? FontWeight.Bold : FontWeight.Regular;
|
public FontWeight FontWeight => IsCurrentHead ? FontWeight.Bold : FontWeight.Regular;
|
||||||
public Thickness Margin { get; set; } = new Thickness(0);
|
public Thickness Margin { get; set; } = new(0);
|
||||||
public IBrush Brush => CommitGraph.Pens[Color].Brush;
|
public IBrush Brush => CommitGraph.Pens[Color].Brush;
|
||||||
|
|
||||||
public void ParseDecorators(string data)
|
public void ParseDecorators(string data)
|
||||||
|
@ -109,7 +113,7 @@ namespace SourceGit.Models
|
||||||
if (l.Type != r.Type)
|
if (l.Type != r.Type)
|
||||||
return (int)l.Type - (int)r.Type;
|
return (int)l.Type - (int)r.Type;
|
||||||
else
|
else
|
||||||
return string.Compare(l.Name, r.Name, StringComparison.Ordinal);
|
return NumericSort.Compare(l.Name, r.Name);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -117,6 +121,6 @@ namespace SourceGit.Models
|
||||||
public class CommitFullMessage
|
public class CommitFullMessage
|
||||||
{
|
{
|
||||||
public string Message { get; set; } = string.Empty;
|
public string Message { get; set; } = string.Empty;
|
||||||
public List<Hyperlink> Links { get; set; } = [];
|
public InlineElementCollector Inlines { get; set; } = new();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,8 +64,8 @@ namespace SourceGit.Models
|
||||||
{
|
{
|
||||||
const double unitWidth = 12;
|
const double unitWidth = 12;
|
||||||
const double halfWidth = 6;
|
const double halfWidth = 6;
|
||||||
const double unitHeight = 28;
|
const double unitHeight = 1;
|
||||||
const double halfHeight = 14;
|
const double halfHeight = 0.5;
|
||||||
|
|
||||||
var temp = new CommitGraph();
|
var temp = new CommitGraph();
|
||||||
var unsolved = new List<PathHelper>();
|
var unsolved = new List<PathHelper>();
|
||||||
|
|
|
@ -1,8 +1,49 @@
|
||||||
namespace SourceGit.Models
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace SourceGit.Models
|
||||||
{
|
{
|
||||||
public class CommitLink
|
public class CommitLink
|
||||||
{
|
{
|
||||||
public string Name { get; set; } = null;
|
public string Name { get; set; } = null;
|
||||||
public string URLPrefix { get; set; } = null;
|
public string URLPrefix { get; set; } = null;
|
||||||
|
|
||||||
|
public CommitLink(string name, string prefix)
|
||||||
|
{
|
||||||
|
Name = name;
|
||||||
|
URLPrefix = prefix;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<CommitLink> Get(List<Remote> remotes)
|
||||||
|
{
|
||||||
|
var outs = new List<CommitLink>();
|
||||||
|
|
||||||
|
foreach (var remote in remotes)
|
||||||
|
{
|
||||||
|
if (remote.TryGetVisitURL(out var url))
|
||||||
|
{
|
||||||
|
var trimmedUrl = url.AsSpan();
|
||||||
|
if (url.EndsWith(".git"))
|
||||||
|
trimmedUrl = url.AsSpan(0, url.Length - 4);
|
||||||
|
|
||||||
|
if (url.StartsWith("https://github.com/", StringComparison.Ordinal))
|
||||||
|
outs.Add(new($"Github ({trimmedUrl.Slice(19)})", $"{url}/commit/"));
|
||||||
|
else if (url.StartsWith("https://gitlab.", StringComparison.Ordinal))
|
||||||
|
outs.Add(new($"GitLab ({trimmedUrl.Slice(trimmedUrl.Slice(15).IndexOf('/') + 16)})", $"{url}/-/commit/"));
|
||||||
|
else if (url.StartsWith("https://gitee.com/", StringComparison.Ordinal))
|
||||||
|
outs.Add(new($"Gitee ({trimmedUrl.Slice(18)})", $"{url}/commit/"));
|
||||||
|
else if (url.StartsWith("https://bitbucket.org/", StringComparison.Ordinal))
|
||||||
|
outs.Add(new($"BitBucket ({trimmedUrl.Slice(22)})", $"{url}/commits/"));
|
||||||
|
else if (url.StartsWith("https://codeberg.org/", StringComparison.Ordinal))
|
||||||
|
outs.Add(new($"Codeberg ({trimmedUrl.Slice(21)})", $"{url}/commit/"));
|
||||||
|
else if (url.StartsWith("https://gitea.org/", StringComparison.Ordinal))
|
||||||
|
outs.Add(new($"Gitea ({trimmedUrl.Slice(18)})", $"{url}/commit/"));
|
||||||
|
else if (url.StartsWith("https://git.sr.ht/", StringComparison.Ordinal))
|
||||||
|
outs.Add(new($"sourcehut ({trimmedUrl.Slice(18)})", $"{url}/commit/"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return outs;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
|
||||||
namespace SourceGit.Models
|
namespace SourceGit.Models
|
||||||
{
|
{
|
||||||
public partial class CommitTemplate : ObservableObject
|
public class CommitTemplate : ObservableObject
|
||||||
{
|
{
|
||||||
public string Name
|
public string Name
|
||||||
{
|
{
|
||||||
|
|
|
@ -4,23 +4,28 @@ namespace SourceGit.Models
|
||||||
{
|
{
|
||||||
public class ConventionalCommitType
|
public class ConventionalCommitType
|
||||||
{
|
{
|
||||||
public string Type { get; set; } = string.Empty;
|
public string Name { get; set; }
|
||||||
public string Description { get; set; } = string.Empty;
|
public string Type { get; set; }
|
||||||
|
public string Description { get; set; }
|
||||||
|
|
||||||
public static readonly List<ConventionalCommitType> Supported = new List<ConventionalCommitType>()
|
public static readonly List<ConventionalCommitType> Supported = [
|
||||||
{
|
new("Features", "feat", "Adding a new feature"),
|
||||||
new ConventionalCommitType("feat", "Adding a new feature"),
|
new("Bug Fixes", "fix", "Fixing a bug"),
|
||||||
new ConventionalCommitType("fix", "Fixing a bug"),
|
new("Work In Progress", "wip", "Still being developed and not yet complete"),
|
||||||
new ConventionalCommitType("docs", "Updating documentation"),
|
new("Reverts", "revert", "Undoing a previous commit"),
|
||||||
new ConventionalCommitType("style", "Elements or code styles without changing the code logic"),
|
new("Code Refactoring", "refactor", "Restructuring code without changing its external behavior"),
|
||||||
new ConventionalCommitType("test", "Adding or updating tests"),
|
new("Performance Improvements", "perf", "Improves performance"),
|
||||||
new ConventionalCommitType("chore", "Making changes to the build process or auxiliary tools and libraries"),
|
new("Builds", "build", "Changes that affect the build system or external dependencies"),
|
||||||
new ConventionalCommitType("revert", "Undoing a previous commit"),
|
new("Continuous Integrations", "ci", "Changes to CI configuration files and scripts"),
|
||||||
new ConventionalCommitType("refactor", "Restructuring code without changing its external behavior")
|
new("Documentations", "docs", "Updating documentation"),
|
||||||
};
|
new("Styles", "style", "Elements or code styles without changing the code logic"),
|
||||||
|
new("Tests", "test", "Adding or updating tests"),
|
||||||
|
new("Chores", "chore", "Other changes that don't modify src or test files"),
|
||||||
|
];
|
||||||
|
|
||||||
public ConventionalCommitType(string type, string description)
|
public ConventionalCommitType(string name, string type, string description)
|
||||||
{
|
{
|
||||||
|
Name = name;
|
||||||
Type = type;
|
Type = type;
|
||||||
Description = description;
|
Description = description;
|
||||||
}
|
}
|
||||||
|
|
19
src/Models/Count.cs
Normal file
19
src/Models/Count.cs
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace SourceGit.Models
|
||||||
|
{
|
||||||
|
public class Count : IDisposable
|
||||||
|
{
|
||||||
|
public int Value { get; set; } = 0;
|
||||||
|
|
||||||
|
public Count(int value)
|
||||||
|
{
|
||||||
|
Value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
// Ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -25,7 +25,7 @@ namespace SourceGit.Models
|
||||||
set;
|
set;
|
||||||
} = 0;
|
} = 0;
|
||||||
|
|
||||||
public static DateTimeFormat Actived
|
public static DateTimeFormat Active
|
||||||
{
|
{
|
||||||
get => Supported[ActiveIndex];
|
get => Supported[ActiveIndex];
|
||||||
}
|
}
|
||||||
|
|
22
src/Models/DealWithChangesAfterStashing.cs
Normal file
22
src/Models/DealWithChangesAfterStashing.cs
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace SourceGit.Models
|
||||||
|
{
|
||||||
|
public class DealWithChangesAfterStashing
|
||||||
|
{
|
||||||
|
public string Label { get; set; }
|
||||||
|
public string Desc { get; set; }
|
||||||
|
|
||||||
|
public static readonly List<DealWithChangesAfterStashing> Supported = [
|
||||||
|
new ("Discard", "All (or selected) changes will be discarded"),
|
||||||
|
new ("Keep Index", "Staged changes are left intact"),
|
||||||
|
new ("Keep All", "All (or selected) changes are left intact"),
|
||||||
|
];
|
||||||
|
|
||||||
|
public DealWithChangesAfterStashing(string label, string desc)
|
||||||
|
{
|
||||||
|
Label = label;
|
||||||
|
Desc = desc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,6 +5,15 @@ namespace SourceGit.Models
|
||||||
{
|
{
|
||||||
public class DiffOption
|
public class DiffOption
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Enable `--ignore-cr-at-eol` by default?
|
||||||
|
/// </summary>
|
||||||
|
public static bool IgnoreCRAtEOL
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
set;
|
||||||
|
} = true;
|
||||||
|
|
||||||
public Change WorkingCopyChange => _workingCopyChange;
|
public Change WorkingCopyChange => _workingCopyChange;
|
||||||
public bool IsUnstaged => _isUnstaged;
|
public bool IsUnstaged => _isUnstaged;
|
||||||
public List<string> Revisions => _revisions;
|
public List<string> Revisions => _revisions;
|
||||||
|
@ -40,7 +49,7 @@ namespace SourceGit.Models
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (change.DataForAmend != null)
|
if (change.DataForAmend != null)
|
||||||
_extra = "--cached HEAD^";
|
_extra = $"--cached {change.DataForAmend.ParentSHA}";
|
||||||
else
|
else
|
||||||
_extra = "--cached";
|
_extra = "--cached";
|
||||||
|
|
||||||
|
@ -56,7 +65,7 @@ namespace SourceGit.Models
|
||||||
/// <param name="change"></param>
|
/// <param name="change"></param>
|
||||||
public DiffOption(Commit commit, Change change)
|
public DiffOption(Commit commit, Change change)
|
||||||
{
|
{
|
||||||
var baseRevision = commit.Parents.Count == 0 ? "4b825dc642cb6eb9a060e54bf8d69288fbee4904" : $"{commit.SHA}^";
|
var baseRevision = commit.Parents.Count == 0 ? Commit.EmptyTreeSHA1 : $"{commit.SHA}^";
|
||||||
_revisions.Add(baseRevision);
|
_revisions.Add(baseRevision);
|
||||||
_revisions.Add(commit.SHA);
|
_revisions.Add(commit.SHA);
|
||||||
_path = change.Path;
|
_path = change.Path;
|
||||||
|
@ -70,7 +79,7 @@ namespace SourceGit.Models
|
||||||
/// <param name="file"></param>
|
/// <param name="file"></param>
|
||||||
public DiffOption(Commit commit, string file)
|
public DiffOption(Commit commit, string file)
|
||||||
{
|
{
|
||||||
var baseRevision = commit.Parents.Count == 0 ? "4b825dc642cb6eb9a060e54bf8d69288fbee4904" : $"{commit.SHA}^";
|
var baseRevision = commit.Parents.Count == 0 ? Commit.EmptyTreeSHA1 : $"{commit.SHA}^";
|
||||||
_revisions.Add(baseRevision);
|
_revisions.Add(baseRevision);
|
||||||
_revisions.Add(commit.SHA);
|
_revisions.Add(commit.SHA);
|
||||||
_path = file;
|
_path = file;
|
||||||
|
@ -115,6 +124,6 @@ namespace SourceGit.Models
|
||||||
private readonly string _path;
|
private readonly string _path;
|
||||||
private readonly string _orgPath = string.Empty;
|
private readonly string _orgPath = string.Empty;
|
||||||
private readonly string _extra = string.Empty;
|
private readonly string _extra = string.Empty;
|
||||||
private readonly List<string> _revisions = new List<string>();
|
private readonly List<string> _revisions = [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
|
@ -16,11 +16,10 @@ namespace SourceGit.Models
|
||||||
Deleted,
|
Deleted,
|
||||||
}
|
}
|
||||||
|
|
||||||
public class TextInlineRange
|
public class TextInlineRange(int p, int n)
|
||||||
{
|
{
|
||||||
public int Start { get; set; }
|
public int Start { get; set; } = p;
|
||||||
public int End { get; set; }
|
public int End { get; set; } = p + n - 1;
|
||||||
public TextInlineRange(int p, int n) { Start = p; End = p + n - 1; }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public class TextDiffLine
|
public class TextDiffLine
|
||||||
|
@ -30,6 +29,7 @@ namespace SourceGit.Models
|
||||||
public int OldLineNumber { get; set; } = 0;
|
public int OldLineNumber { get; set; } = 0;
|
||||||
public int NewLineNumber { get; set; } = 0;
|
public int NewLineNumber { get; set; } = 0;
|
||||||
public List<TextInlineRange> Highlights { get; set; } = new List<TextInlineRange>();
|
public List<TextInlineRange> Highlights { get; set; } = new List<TextInlineRange>();
|
||||||
|
public bool NoNewLineEndOfFile { get; set; } = false;
|
||||||
|
|
||||||
public string OldLine => OldLineNumber == 0 ? string.Empty : OldLineNumber.ToString();
|
public string OldLine => OldLineNumber == 0 ? string.Empty : OldLineNumber.ToString();
|
||||||
public string NewLine => NewLineNumber == 0 ? string.Empty : NewLineNumber.ToString();
|
public string NewLine => NewLineNumber == 0 ? string.Empty : NewLineNumber.ToString();
|
||||||
|
@ -146,7 +146,7 @@ namespace SourceGit.Models
|
||||||
public void GenerateNewPatchFromSelection(Change change, string fileBlobGuid, TextDiffSelection selection, bool revert, string output)
|
public void GenerateNewPatchFromSelection(Change change, string fileBlobGuid, TextDiffSelection selection, bool revert, string output)
|
||||||
{
|
{
|
||||||
var isTracked = !string.IsNullOrEmpty(fileBlobGuid);
|
var isTracked = !string.IsNullOrEmpty(fileBlobGuid);
|
||||||
var fileGuid = isTracked ? fileBlobGuid.Substring(0, 8) : "00000000";
|
var fileGuid = isTracked ? fileBlobGuid : "00000000";
|
||||||
|
|
||||||
var builder = new StringBuilder();
|
var builder = new StringBuilder();
|
||||||
builder.Append("diff --git a/").Append(change.Path).Append(" b/").Append(change.Path).Append('\n');
|
builder.Append("diff --git a/").Append(change.Path).Append(" b/").Append(change.Path).Append('\n');
|
||||||
|
@ -555,7 +555,7 @@ namespace SourceGit.Models
|
||||||
}
|
}
|
||||||
else if (test.Type == TextDiffLineType.Added)
|
else if (test.Type == TextDiffLineType.Added)
|
||||||
{
|
{
|
||||||
if (i < start - 1)
|
if (i < start - 1 || isOldSide)
|
||||||
{
|
{
|
||||||
if (revert)
|
if (revert)
|
||||||
{
|
{
|
||||||
|
@ -565,18 +565,7 @@ namespace SourceGit.Models
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (isOldSide)
|
newCount++;
|
||||||
{
|
|
||||||
if (revert)
|
|
||||||
{
|
|
||||||
newCount++;
|
|
||||||
oldCount++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
newCount++;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (i == end - 1 && tailed)
|
if (i == end - 1 && tailed)
|
||||||
|
@ -654,9 +643,7 @@ namespace SourceGit.Models
|
||||||
public string NewImageSize => New != null ? $"{New.PixelSize.Width} x {New.PixelSize.Height}" : "0 x 0";
|
public string NewImageSize => New != null ? $"{New.PixelSize.Width} x {New.PixelSize.Height}" : "0 x 0";
|
||||||
}
|
}
|
||||||
|
|
||||||
public class NoOrEOLChange
|
public class NoOrEOLChange;
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public class FileModeDiff
|
public class FileModeDiff
|
||||||
{
|
{
|
||||||
|
|
12
src/Models/DirtyState.cs
Normal file
12
src/Models/DirtyState.cs
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace SourceGit.Models
|
||||||
|
{
|
||||||
|
[Flags]
|
||||||
|
public enum DirtyState
|
||||||
|
{
|
||||||
|
None = 0,
|
||||||
|
HasLocalChanges = 1 << 0,
|
||||||
|
HasPendingPullOrPush = 1 << 1,
|
||||||
|
}
|
||||||
|
}
|
|
@ -43,6 +43,7 @@ namespace SourceGit.Models
|
||||||
new ExternalMerger(8, "codium", "VSCodium", "VSCodium.exe", "-n --wait \"$MERGED\"", "-n --wait --diff \"$LOCAL\" \"$REMOTE\""),
|
new ExternalMerger(8, "codium", "VSCodium", "VSCodium.exe", "-n --wait \"$MERGED\"", "-n --wait --diff \"$LOCAL\" \"$REMOTE\""),
|
||||||
new ExternalMerger(9, "p4merge", "P4Merge", "p4merge.exe", "-tw 4 \"$BASE\" \"$LOCAL\" \"$REMOTE\" \"$MERGED\"", "-tw 4 \"$LOCAL\" \"$REMOTE\""),
|
new ExternalMerger(9, "p4merge", "P4Merge", "p4merge.exe", "-tw 4 \"$BASE\" \"$LOCAL\" \"$REMOTE\" \"$MERGED\"", "-tw 4 \"$LOCAL\" \"$REMOTE\""),
|
||||||
new ExternalMerger(10, "plastic_merge", "Plastic SCM", "mergetool.exe", "-s=\"$REMOTE\" -b=\"$BASE\" -d=\"$LOCAL\" -r=\"$MERGED\" --automatic", "-s=\"$LOCAL\" -d=\"$REMOTE\""),
|
new ExternalMerger(10, "plastic_merge", "Plastic SCM", "mergetool.exe", "-s=\"$REMOTE\" -b=\"$BASE\" -d=\"$LOCAL\" -r=\"$MERGED\" --automatic", "-s=\"$LOCAL\" -d=\"$REMOTE\""),
|
||||||
|
new ExternalMerger(11, "meld", "Meld", "Meld.exe", "\"$LOCAL\" \"$BASE\" \"$REMOTE\" --output \"$MERGED\"", "\"$LOCAL\" \"$REMOTE\""),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
else if (OperatingSystem.IsMacOS())
|
else if (OperatingSystem.IsMacOS())
|
||||||
|
|
|
@ -107,8 +107,7 @@ namespace SourceGit.Models
|
||||||
// Ignore
|
// Ignore
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_customPaths == null)
|
_customPaths ??= new ExternalToolPaths();
|
||||||
_customPaths = new ExternalToolPaths();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void TryAdd(string name, string icon, Func<string> finder, Func<string, string> execArgsGenerator = null)
|
public void TryAdd(string name, string icon, Func<string> finder, Func<string, string> execArgsGenerator = null)
|
||||||
|
|
46
src/Models/GitFlow.cs
Normal file
46
src/Models/GitFlow.cs
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
namespace SourceGit.Models
|
||||||
|
{
|
||||||
|
public enum GitFlowBranchType
|
||||||
|
{
|
||||||
|
None = 0,
|
||||||
|
Feature,
|
||||||
|
Release,
|
||||||
|
Hotfix,
|
||||||
|
}
|
||||||
|
|
||||||
|
public class GitFlow
|
||||||
|
{
|
||||||
|
public string Master { get; set; } = string.Empty;
|
||||||
|
public string Develop { get; set; } = string.Empty;
|
||||||
|
public string FeaturePrefix { get; set; } = string.Empty;
|
||||||
|
public string ReleasePrefix { get; set; } = string.Empty;
|
||||||
|
public string HotfixPrefix { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
public bool IsValid
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return !string.IsNullOrEmpty(Master) &&
|
||||||
|
!string.IsNullOrEmpty(Develop) &&
|
||||||
|
!string.IsNullOrEmpty(FeaturePrefix) &&
|
||||||
|
!string.IsNullOrEmpty(ReleasePrefix) &&
|
||||||
|
!string.IsNullOrEmpty(HotfixPrefix);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetPrefix(GitFlowBranchType type)
|
||||||
|
{
|
||||||
|
switch (type)
|
||||||
|
{
|
||||||
|
case GitFlowBranchType.Feature:
|
||||||
|
return FeaturePrefix;
|
||||||
|
case GitFlowBranchType.Release:
|
||||||
|
return ReleasePrefix;
|
||||||
|
case GitFlowBranchType.Hotfix:
|
||||||
|
return HotfixPrefix;
|
||||||
|
default:
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,26 +5,16 @@
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The minimal version of Git that required by this app.
|
/// The minimal version of Git that required by this app.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static readonly System.Version MINIMAL = new System.Version(2, 23, 0);
|
public static readonly System.Version MINIMAL = new(2, 25, 1);
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The minimal version of Git that supports the `add` command with the `--pathspec-from-file` option.
|
|
||||||
/// </summary>
|
|
||||||
public static readonly System.Version ADD_WITH_PATHSPECFILE = new System.Version(2, 25, 0);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The minimal version of Git that supports the `stash push` command with the `--pathspec-from-file` option.
|
/// The minimal version of Git that supports the `stash push` command with the `--pathspec-from-file` option.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static readonly System.Version STASH_PUSH_WITH_PATHSPECFILE = new System.Version(2, 26, 0);
|
public static readonly System.Version STASH_PUSH_WITH_PATHSPECFILE = new(2, 26, 0);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The minimal version of Git that supports the `stash push` command with the `--staged` option.
|
/// The minimal version of Git that supports the `stash push` command with the `--staged` option.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static readonly System.Version STASH_PUSH_ONLY_STAGED = new System.Version(2, 35, 0);
|
public static readonly System.Version STASH_PUSH_ONLY_STAGED = new(2, 35, 0);
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The minimal version of Git that supports the `stash show` command with the `-u` option.
|
|
||||||
/// </summary>
|
|
||||||
public static readonly System.Version STASH_SHOW_WITH_UNTRACKED = new System.Version(2, 32, 0);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,29 +0,0 @@
|
||||||
namespace SourceGit.Models
|
|
||||||
{
|
|
||||||
public class Hyperlink
|
|
||||||
{
|
|
||||||
public int Start { get; set; } = 0;
|
|
||||||
public int Length { get; set; } = 0;
|
|
||||||
public string Link { get; set; } = "";
|
|
||||||
public bool IsCommitSHA { get; set; } = false;
|
|
||||||
|
|
||||||
public Hyperlink(int start, int length, string link, bool isCommitSHA = false)
|
|
||||||
{
|
|
||||||
Start = start;
|
|
||||||
Length = length;
|
|
||||||
Link = link;
|
|
||||||
IsCommitSHA = isCommitSHA;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Intersect(int start, int length)
|
|
||||||
{
|
|
||||||
if (start == Start)
|
|
||||||
return true;
|
|
||||||
|
|
||||||
if (start < Start)
|
|
||||||
return start + length > Start;
|
|
||||||
|
|
||||||
return start < Start + Length;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
7
src/Models/ICommandLog.cs
Normal file
7
src/Models/ICommandLog.cs
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
namespace SourceGit.Models
|
||||||
|
{
|
||||||
|
public interface ICommandLog
|
||||||
|
{
|
||||||
|
void AppendLine(string line);
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,6 +2,8 @@
|
||||||
{
|
{
|
||||||
public interface IRepository
|
public interface IRepository
|
||||||
{
|
{
|
||||||
|
bool MayHaveSubmodules();
|
||||||
|
|
||||||
void RefreshBranches();
|
void RefreshBranches();
|
||||||
void RefreshWorktrees();
|
void RefreshWorktrees();
|
||||||
void RefreshTags();
|
void RefreshTags();
|
||||||
|
|
10
src/Models/ImageDecoder.cs
Normal file
10
src/Models/ImageDecoder.cs
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
namespace SourceGit.Models
|
||||||
|
{
|
||||||
|
public enum ImageDecoder
|
||||||
|
{
|
||||||
|
None = 0,
|
||||||
|
Builtin,
|
||||||
|
Pfim,
|
||||||
|
Tiff,
|
||||||
|
}
|
||||||
|
}
|
37
src/Models/InlineElement.cs
Normal file
37
src/Models/InlineElement.cs
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
namespace SourceGit.Models
|
||||||
|
{
|
||||||
|
public enum InlineElementType
|
||||||
|
{
|
||||||
|
Keyword = 0,
|
||||||
|
Link,
|
||||||
|
CommitSHA,
|
||||||
|
Code,
|
||||||
|
}
|
||||||
|
|
||||||
|
public class InlineElement
|
||||||
|
{
|
||||||
|
public InlineElementType Type { get; }
|
||||||
|
public int Start { get; }
|
||||||
|
public int Length { get; }
|
||||||
|
public string Link { get; }
|
||||||
|
|
||||||
|
public InlineElement(InlineElementType type, int start, int length, string link)
|
||||||
|
{
|
||||||
|
Type = type;
|
||||||
|
Start = start;
|
||||||
|
Length = length;
|
||||||
|
Link = link;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsIntersecting(int start, int length)
|
||||||
|
{
|
||||||
|
if (start == Start)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (start < Start)
|
||||||
|
return start + length > Start;
|
||||||
|
|
||||||
|
return start < Start + Length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
38
src/Models/InlineElementCollector.cs
Normal file
38
src/Models/InlineElementCollector.cs
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace SourceGit.Models
|
||||||
|
{
|
||||||
|
public class InlineElementCollector
|
||||||
|
{
|
||||||
|
public int Count => _implementation.Count;
|
||||||
|
public InlineElement this[int index] => _implementation[index];
|
||||||
|
|
||||||
|
public InlineElement Intersect(int start, int length)
|
||||||
|
{
|
||||||
|
foreach (var elem in _implementation)
|
||||||
|
{
|
||||||
|
if (elem.IsIntersecting(start, length))
|
||||||
|
return elem;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Add(InlineElement element)
|
||||||
|
{
|
||||||
|
_implementation.Add(element);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Sort()
|
||||||
|
{
|
||||||
|
_implementation.Sort((l, r) => l.Start.CompareTo(r.Start));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Clear()
|
||||||
|
{
|
||||||
|
_implementation.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly List<InlineElement> _implementation = [];
|
||||||
|
}
|
||||||
|
}
|
|
@ -27,6 +27,8 @@ namespace SourceGit.Models
|
||||||
|
|
||||||
public class InteractiveRebaseJobCollection
|
public class InteractiveRebaseJobCollection
|
||||||
{
|
{
|
||||||
|
public string OrigHead { get; set; } = string.Empty;
|
||||||
|
public string Onto { get; set; } = string.Empty;
|
||||||
public List<InteractiveRebaseJob> Jobs { get; set; } = new List<InteractiveRebaseJob>();
|
public List<InteractiveRebaseJob> Jobs { get; set; } = new List<InteractiveRebaseJob>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
100
src/Models/IpcChannel.cs
Normal file
100
src/Models/IpcChannel.cs
Normal file
|
@ -0,0 +1,100 @@
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.IO.Pipes;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace SourceGit.Models
|
||||||
|
{
|
||||||
|
public class IpcChannel : IDisposable
|
||||||
|
{
|
||||||
|
public bool IsFirstInstance { get; }
|
||||||
|
|
||||||
|
public event Action<string> MessageReceived;
|
||||||
|
|
||||||
|
public IpcChannel()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_singletonLock = File.Open(Path.Combine(Native.OS.DataDir, "process.lock"), FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None);
|
||||||
|
IsFirstInstance = true;
|
||||||
|
_server = new NamedPipeServerStream(
|
||||||
|
"SourceGitIPCChannel" + Environment.UserName,
|
||||||
|
PipeDirection.In,
|
||||||
|
-1,
|
||||||
|
PipeTransmissionMode.Byte,
|
||||||
|
PipeOptions.Asynchronous | PipeOptions.CurrentUserOnly);
|
||||||
|
_cancellationTokenSource = new CancellationTokenSource();
|
||||||
|
Task.Run(StartServer);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
IsFirstInstance = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SendToFirstInstance(string cmd)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using (var client = new NamedPipeClientStream(".", "SourceGitIPCChannel" + Environment.UserName, PipeDirection.Out, PipeOptions.Asynchronous | PipeOptions.CurrentUserOnly))
|
||||||
|
{
|
||||||
|
client.Connect(1000);
|
||||||
|
if (!client.IsConnected)
|
||||||
|
return;
|
||||||
|
|
||||||
|
using (var writer = new StreamWriter(client))
|
||||||
|
{
|
||||||
|
writer.WriteLine(cmd);
|
||||||
|
writer.Flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (OperatingSystem.IsWindows())
|
||||||
|
client.WaitForPipeDrain();
|
||||||
|
else
|
||||||
|
Thread.Sleep(1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// IGNORE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_cancellationTokenSource?.Cancel();
|
||||||
|
_singletonLock?.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void StartServer()
|
||||||
|
{
|
||||||
|
using var reader = new StreamReader(_server);
|
||||||
|
|
||||||
|
while (!_cancellationTokenSource.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await _server.WaitForConnectionAsync(_cancellationTokenSource.Token);
|
||||||
|
|
||||||
|
if (!_cancellationTokenSource.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
var line = await reader.ReadToEndAsync(_cancellationTokenSource.Token);
|
||||||
|
MessageReceived?.Invoke(line?.Trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
_server.Disconnect();
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
if (!_cancellationTokenSource.IsCancellationRequested && _server.IsConnected)
|
||||||
|
_server.Disconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private FileStream _singletonLock = null;
|
||||||
|
private NamedPipeServerStream _server = null;
|
||||||
|
private CancellationTokenSource _cancellationTokenSource = null;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,4 @@
|
||||||
using System.Collections.Generic;
|
using System.Text.RegularExpressions;
|
||||||
using System.Text.RegularExpressions;
|
|
||||||
|
|
||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
|
||||||
|
@ -46,7 +45,7 @@ namespace SourceGit.Models
|
||||||
set => SetProperty(ref _urlTemplate, value);
|
set => SetProperty(ref _urlTemplate, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Matches(List<Hyperlink> outs, string message)
|
public void Matches(InlineElementCollector outs, string message)
|
||||||
{
|
{
|
||||||
if (_regex == null || string.IsNullOrEmpty(_urlTemplate))
|
if (_regex == null || string.IsNullOrEmpty(_urlTemplate))
|
||||||
return;
|
return;
|
||||||
|
@ -60,17 +59,7 @@ namespace SourceGit.Models
|
||||||
|
|
||||||
var start = match.Index;
|
var start = match.Index;
|
||||||
var len = match.Length;
|
var len = match.Length;
|
||||||
var intersect = false;
|
if (outs.Intersect(start, len) != null)
|
||||||
foreach (var exist in outs)
|
|
||||||
{
|
|
||||||
if (exist.Intersect(start, len))
|
|
||||||
{
|
|
||||||
intersect = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (intersect)
|
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
var link = _urlTemplate;
|
var link = _urlTemplate;
|
||||||
|
@ -81,8 +70,7 @@ namespace SourceGit.Models
|
||||||
link = link.Replace($"${j}", group.Value);
|
link = link.Replace($"${j}", group.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
var range = new Hyperlink(start, len, link);
|
outs.Add(new InlineElement(InlineElementType.Link, start, len, link));
|
||||||
outs.Add(range);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,22 @@
|
||||||
namespace SourceGit.Models
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
|
namespace SourceGit.Models
|
||||||
{
|
{
|
||||||
public class LFSObject
|
public partial class LFSObject
|
||||||
{
|
{
|
||||||
|
[GeneratedRegex(@"^version https://git-lfs.github.com/spec/v\d+\r?\noid sha256:([0-9a-f]+)\r?\nsize (\d+)[\r\n]*$")]
|
||||||
|
private static partial Regex REG_FORMAT();
|
||||||
|
|
||||||
public string Oid { get; set; } = string.Empty;
|
public string Oid { get; set; } = string.Empty;
|
||||||
public long Size { get; set; } = 0;
|
public long Size { get; set; } = 0;
|
||||||
|
|
||||||
|
public static LFSObject Parse(string content)
|
||||||
|
{
|
||||||
|
var match = REG_FORMAT().Match(content);
|
||||||
|
if (match.Success)
|
||||||
|
return new() { Oid = match.Groups[1].Value, Size = long.Parse(match.Groups[2].Value) };
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,7 @@ namespace SourceGit.Models
|
||||||
new Locale("Français", "fr_FR"),
|
new Locale("Français", "fr_FR"),
|
||||||
new Locale("Italiano", "it_IT"),
|
new Locale("Italiano", "it_IT"),
|
||||||
new Locale("Português (Brasil)", "pt_BR"),
|
new Locale("Português (Brasil)", "pt_BR"),
|
||||||
|
new Locale("Українська", "uk_UA"),
|
||||||
new Locale("Русский", "ru_RU"),
|
new Locale("Русский", "ru_RU"),
|
||||||
new Locale("简体中文", "zh_CN"),
|
new Locale("简体中文", "zh_CN"),
|
||||||
new Locale("繁體中文", "zh_TW"),
|
new Locale("繁體中文", "zh_TW"),
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
namespace SourceGit.Models
|
namespace SourceGit.Models
|
||||||
{
|
{
|
||||||
public class Null
|
public class Null;
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
namespace SourceGit.Models
|
using System;
|
||||||
|
|
||||||
|
namespace SourceGit.Models
|
||||||
{
|
{
|
||||||
public static class NumericSort
|
public static class NumericSort
|
||||||
{
|
{
|
||||||
|
@ -10,52 +12,35 @@
|
||||||
int marker1 = 0;
|
int marker1 = 0;
|
||||||
int marker2 = 0;
|
int marker2 = 0;
|
||||||
|
|
||||||
char[] tmp1 = new char[len1];
|
|
||||||
char[] tmp2 = new char[len2];
|
|
||||||
|
|
||||||
while (marker1 < len1 && marker2 < len2)
|
while (marker1 < len1 && marker2 < len2)
|
||||||
{
|
{
|
||||||
char c1 = s1[marker1];
|
char c1 = s1[marker1];
|
||||||
char c2 = s2[marker2];
|
char c2 = s2[marker2];
|
||||||
int loc1 = 0;
|
|
||||||
int loc2 = 0;
|
|
||||||
|
|
||||||
bool isDigit1 = char.IsDigit(c1);
|
bool isDigit1 = char.IsDigit(c1);
|
||||||
bool isDigit2 = char.IsDigit(c2);
|
bool isDigit2 = char.IsDigit(c2);
|
||||||
if (isDigit1 != isDigit2)
|
if (isDigit1 != isDigit2)
|
||||||
return c1.CompareTo(c2);
|
return c1.CompareTo(c2);
|
||||||
|
|
||||||
do
|
int subLen1 = 1;
|
||||||
{
|
while (marker1 + subLen1 < len1 && char.IsDigit(s1[marker1 + subLen1]) == isDigit1)
|
||||||
tmp1[loc1] = c1;
|
subLen1++;
|
||||||
loc1++;
|
|
||||||
marker1++;
|
|
||||||
|
|
||||||
if (marker1 < len1)
|
int subLen2 = 1;
|
||||||
c1 = s1[marker1];
|
while (marker2 + subLen2 < len2 && char.IsDigit(s2[marker2 + subLen2]) == isDigit2)
|
||||||
else
|
subLen2++;
|
||||||
break;
|
|
||||||
} while (char.IsDigit(c1) == isDigit1);
|
|
||||||
|
|
||||||
do
|
string sub1 = s1.Substring(marker1, subLen1);
|
||||||
{
|
string sub2 = s2.Substring(marker2, subLen2);
|
||||||
tmp2[loc2] = c2;
|
|
||||||
loc2++;
|
|
||||||
marker2++;
|
|
||||||
|
|
||||||
if (marker2 < len2)
|
marker1 += subLen1;
|
||||||
c2 = s2[marker2];
|
marker2 += subLen2;
|
||||||
else
|
|
||||||
break;
|
|
||||||
} while (char.IsDigit(c2) == isDigit2);
|
|
||||||
|
|
||||||
string sub1 = new string(tmp1, 0, loc1);
|
|
||||||
string sub2 = new string(tmp2, 0, loc2);
|
|
||||||
int result;
|
int result;
|
||||||
if (isDigit1)
|
if (isDigit1)
|
||||||
result = loc1 == loc2 ? string.CompareOrdinal(sub1, sub2) : loc1 - loc2;
|
result = (subLen1 == subLen2) ? string.CompareOrdinal(sub1, sub2) : (subLen1 - subLen2);
|
||||||
else
|
else
|
||||||
result = string.CompareOrdinal(sub1, sub2);
|
result = string.Compare(sub1, sub2, StringComparison.OrdinalIgnoreCase);
|
||||||
|
|
||||||
if (result != 0)
|
if (result != 0)
|
||||||
return result;
|
return result;
|
||||||
|
|
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