mirror of
https://github.com/sourcegit-scm/sourcegit
synced 2025-06-26 12:55:00 +00:00
Compare commits
410 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 |
311 changed files with 10009 additions and 4429 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,14 +93,14 @@ 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
|
||||||
dotnet_naming_symbols.private_internal_fields.applicable_accessibilities = private, internal
|
dotnet_naming_symbols.private_internal_fields.applicable_accessibilities = private, internal
|
||||||
|
|
||||||
dotnet_naming_style.camel_case_underscore_style.required_prefix = _
|
dotnet_naming_style.camel_case_underscore_style.required_prefix = _
|
||||||
dotnet_naming_style.camel_case_underscore_style.capitalization = camel_case
|
dotnet_naming_style.camel_case_underscore_style.capitalization = camel_case
|
||||||
|
|
||||||
# use accessibility modifiers
|
# use accessibility modifiers
|
||||||
dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion
|
dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion
|
||||||
|
|
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
|
||||||
|
|
|
@ -39,6 +39,7 @@
|
||||||
* Search commits
|
* Search commits
|
||||||
* GitFlow
|
* GitFlow
|
||||||
* Git LFS
|
* Git LFS
|
||||||
|
* Bisect
|
||||||
* Issue Link
|
* Issue Link
|
||||||
* Workspace
|
* Workspace
|
||||||
* Custom Action
|
* Custom Action
|
||||||
|
@ -53,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.
|
||||||
|
|
||||||
|
@ -92,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
|
||||||
```
|
```
|
||||||
|
|
400
TRANSLATION.md
400
TRANSLATION.md
|
@ -6,145 +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.CommitCM.CopyAuthor
|
|
||||||
- Text.CommitCM.CopyCommitter
|
|
||||||
- Text.CommitCM.CopySubject
|
|
||||||
- Text.CommitMessageTextBox.SubjectCount
|
|
||||||
- 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.Repository.ViewLogs
|
|
||||||
- Text.StashCM.SaveAsPatch
|
|
||||||
- Text.ViewLogs
|
|
||||||
- Text.ViewLogs.Clear
|
|
||||||
- Text.ViewLogs.CopyLog
|
|
||||||
- Text.ViewLogs.Delete
|
|
||||||
- Text.WorkingCopy.ConfirmCommitWithFilter
|
|
||||||
- Text.WorkingCopy.Conflicts.OpenExternalMergeTool
|
|
||||||
- Text.WorkingCopy.Conflicts.OpenExternalMergeToolAllConflicts
|
|
||||||
- Text.WorkingCopy.Conflicts.UseMine
|
|
||||||
- Text.WorkingCopy.Conflicts.UseTheirs
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
### 
|
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary>Missing keys in es_ES.axaml</summary>
|
<summary>Missing keys in es_ES.axaml</summary>
|
||||||
|
|
||||||
- Text.CommitCM.CopyAuthor
|
- Text.CommitCM.PushRevision
|
||||||
- Text.CommitCM.CopyCommitter
|
- Text.Merge.Edit
|
||||||
- Text.CommitCM.CopySubject
|
- Text.Push.Revision
|
||||||
- Text.Repository.ViewLogs
|
- Text.Push.Revision.Title
|
||||||
- Text.ViewLogs
|
- Text.Stash.Mode
|
||||||
- Text.ViewLogs.Clear
|
- Text.StashCM.CopyMessage
|
||||||
- Text.ViewLogs.CopyLog
|
- Text.WorkingCopy.AddToGitIgnore.InFolder
|
||||||
- Text.ViewLogs.Delete
|
|
||||||
|
|
||||||
</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.CopyAuthor
|
||||||
- Text.CommitCM.CopyCommitter
|
- Text.CommitCM.CopyCommitter
|
||||||
- Text.CommitCM.CopySubject
|
- Text.CommitCM.CopySubject
|
||||||
|
- Text.CommitCM.PushRevision
|
||||||
|
- Text.CommitDetail.Changes.Count
|
||||||
- Text.CommitMessageTextBox.SubjectCount
|
- 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.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
|
||||||
- Text.ViewLogs.Clear
|
- Text.ViewLogs.Clear
|
||||||
- Text.ViewLogs.CopyLog
|
- Text.ViewLogs.CopyLog
|
||||||
- Text.ViewLogs.Delete
|
- 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.CommitCM.CopyAuthor
|
- Text.Avatar.Load
|
||||||
- Text.CommitCM.CopyCommitter
|
- Text.BranchCM.ResetToSelectedCommit
|
||||||
- Text.CommitCM.CopySubject
|
- Text.Checkout.WithFastForward
|
||||||
- Text.CommitMessageTextBox.SubjectCount
|
- Text.Checkout.WithFastForward.Upstream
|
||||||
- Text.Configure.Git.PreferredMergeMode
|
- Text.CommitCM.PushRevision
|
||||||
- Text.ConfirmEmptyCommit.Continue
|
- Text.CommitDetail.Changes.Count
|
||||||
- Text.ConfirmEmptyCommit.NoLocalChanges
|
- Text.CreateBranch.OverwriteExisting
|
||||||
- Text.ConfirmEmptyCommit.StageAllThenCommit
|
- Text.DeinitSubmodule
|
||||||
- Text.ConfirmEmptyCommit.WithLocalChanges
|
- Text.DeinitSubmodule.Force
|
||||||
- Text.CopyFullPath
|
- Text.DeinitSubmodule.Path
|
||||||
- Text.Preferences.General.ShowTagsInGraph
|
- Text.Diff.Submodule.Deleted
|
||||||
- Text.Repository.ViewLogs
|
- Text.Hotkeys.Global.SwitchWorkspace
|
||||||
- Text.ViewLogs
|
- Text.Hotkeys.Global.SwitchTab
|
||||||
- Text.ViewLogs.Clear
|
- Text.Launcher.Workspaces
|
||||||
- Text.ViewLogs.CopyLog
|
- Text.Launcher.Pages
|
||||||
- Text.ViewLogs.Delete
|
- Text.Merge.Edit
|
||||||
- Text.WorkingCopy.ConfirmCommitWithFilter
|
- Text.Pull.RecurseSubmodules
|
||||||
- Text.WorkingCopy.Conflicts.OpenExternalMergeTool
|
- Text.Push.Revision
|
||||||
- Text.WorkingCopy.Conflicts.OpenExternalMergeToolAllConflicts
|
- Text.Push.Revision.Title
|
||||||
- Text.WorkingCopy.Conflicts.UseMine
|
- Text.Repository.ClearStashes
|
||||||
- Text.WorkingCopy.Conflicts.UseTheirs
|
- 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.CopyAuthor
|
||||||
- Text.CommitCM.CopyCommitter
|
- Text.CommitCM.CopyCommitter
|
||||||
- Text.CommitCM.CopySubject
|
- Text.CommitCM.CopySubject
|
||||||
|
- Text.CommitCM.PushRevision
|
||||||
|
- Text.CommitDetail.Changes.Count
|
||||||
- Text.CommitMessageTextBox.SubjectCount
|
- 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.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
|
||||||
- Text.ViewLogs.Clear
|
- Text.ViewLogs.Clear
|
||||||
- Text.ViewLogs.CopyLog
|
- Text.ViewLogs.CopyLog
|
||||||
- Text.ViewLogs.Delete
|
- 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>
|
||||||
|
@ -155,15 +230,29 @@ 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.CopyAuthor
|
||||||
- Text.CommitCM.CopyCommitter
|
- Text.CommitCM.CopyCommitter
|
||||||
- Text.CommitCM.CopySubject
|
- 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.CommitMessageTextBox.SubjectCount
|
||||||
|
@ -178,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
|
||||||
|
@ -201,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
|
||||||
|
@ -209,94 +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.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
|
||||||
- Text.ViewLogs.Clear
|
- Text.ViewLogs.Clear
|
||||||
- Text.ViewLogs.CopyLog
|
- Text.ViewLogs.CopyLog
|
||||||
- Text.ViewLogs.Delete
|
- 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>
|
### 
|
||||||
<summary>Missing keys in ru_RU.axaml</summary>
|
|
||||||
|
|
||||||
- Text.CommitCM.CopyAuthor
|
|
||||||
- Text.CommitCM.CopyCommitter
|
|
||||||
- Text.CommitCM.CopySubject
|
|
||||||
- Text.CommitMessageTextBox.SubjectCount
|
|
||||||
- Text.Repository.ViewLogs
|
|
||||||
- Text.ViewLogs
|
|
||||||
- Text.ViewLogs.Clear
|
|
||||||
- Text.ViewLogs.CopyLog
|
|
||||||
- Text.ViewLogs.Delete
|
|
||||||
|
|
||||||
</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.CopyAuthor
|
||||||
- Text.CommitCM.CopyCommitter
|
- Text.CommitCM.CopyCommitter
|
||||||
- Text.CommitCM.CopySubject
|
- Text.CommitCM.CopySubject
|
||||||
|
- Text.CommitCM.PushRevision
|
||||||
|
- Text.CommitDetail.Changes.Count
|
||||||
- Text.CommitMessageTextBox.SubjectCount
|
- 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.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
|
||||||
- Text.ViewLogs.Clear
|
- Text.ViewLogs.Clear
|
||||||
- Text.ViewLogs.CopyLog
|
- Text.ViewLogs.CopyLog
|
||||||
- Text.ViewLogs.Delete
|
- 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>
|
||||||
|
|
||||||
### 
|
### 
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary>Missing keys in uk_UA.axaml</summary>
|
<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.CopyAuthor
|
||||||
- Text.CommitCM.CopyCommitter
|
- Text.CommitCM.CopyCommitter
|
||||||
- Text.CommitCM.CopySubject
|
- Text.CommitCM.CopySubject
|
||||||
|
- Text.CommitCM.PushRevision
|
||||||
|
- Text.CommitDetail.Changes.Count
|
||||||
- Text.CommitMessageTextBox.SubjectCount
|
- Text.CommitMessageTextBox.SubjectCount
|
||||||
- Text.ConfigureWorkspace.Name
|
- 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.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
|
||||||
- Text.ViewLogs.Clear
|
- Text.ViewLogs.Clear
|
||||||
- Text.ViewLogs.CopyLog
|
- Text.ViewLogs.CopyLog
|
||||||
- Text.ViewLogs.Delete
|
- Text.ViewLogs.Delete
|
||||||
|
- Text.WorkingCopy.AddToGitIgnore.InFolder
|
||||||
|
- Text.WorkingCopy.ResetAuthor
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
|
|
2
VERSION
2
VERSION
|
@ -1 +1 @@
|
||||||
2025.14
|
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.
|
||||||
|
|
|
@ -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 =>
|
||||||
|
|
|
@ -35,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="⌘+,"/>
|
||||||
|
|
|
@ -78,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;
|
||||||
|
@ -105,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)
|
||||||
|
@ -267,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)
|
||||||
|
@ -289,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;
|
||||||
|
@ -304,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;
|
||||||
}
|
}
|
||||||
|
@ -342,6 +375,14 @@ 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;
|
||||||
|
|
||||||
|
@ -351,7 +392,17 @@ namespace SourceGit
|
||||||
_ipcChannel = new Models.IpcChannel();
|
_ipcChannel = new Models.IpcChannel();
|
||||||
if (!_ipcChannel.IsFirstInstance)
|
if (!_ipcChannel.IsFirstInstance)
|
||||||
{
|
{
|
||||||
_ipcChannel.SendToFirstInstance(desktop.Args is { Length: 1 } ? desktop.Args[0] : string.Empty);
|
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);
|
Environment.Exit(0);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -445,7 +496,7 @@ namespace SourceGit
|
||||||
if (!collection.Onto.Equals(onto) || !collection.OrigHead.Equals(origHead))
|
if (!collection.Onto.Equals(onto) || !collection.OrigHead.Equals(origHead))
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
var done = File.ReadAllText(doneFile).Trim().Split([ '\r', '\n' ], StringSplitOptions.RemoveEmptyEntries);
|
var done = File.ReadAllText(doneFile).Trim().Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries);
|
||||||
if (done.Length == 0)
|
if (done.Length == 0)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
|
@ -470,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];
|
||||||
|
@ -480,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;
|
||||||
}
|
}
|
||||||
|
@ -506,7 +557,7 @@ 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;
|
||||||
|
@ -518,6 +569,7 @@ namespace SourceGit
|
||||||
|
|
||||||
_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
|
||||||
if (pref.ShouldCheck4UpdateOnStartup())
|
if (pref.ShouldCheck4UpdateOnStartup())
|
||||||
|
@ -598,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);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -632,7 +680,15 @@ 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;
|
||||||
|
|
|
@ -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)
|
||||||
|
|
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}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -51,7 +51,7 @@ namespace SourceGit.Commands
|
||||||
|
|
||||||
private void ParseLine(string line)
|
private void ParseLine(string line)
|
||||||
{
|
{
|
||||||
if (line.IndexOf('\0', StringComparison.Ordinal) >= 0)
|
if (line.Contains('\0', StringComparison.Ordinal))
|
||||||
{
|
{
|
||||||
_result.IsBinary = true;
|
_result.IsBinary = true;
|
||||||
_result.LineInfos.Clear();
|
_result.LineInfos.Clear();
|
||||||
|
@ -89,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,12 +13,20 @@
|
||||||
return cmd.ReadToEnd().StdOut.Trim();
|
return cmd.ReadToEnd().StdOut.Trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool Create(string repo, string name, string basedOn, Models.ICommandLog log)
|
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;
|
cmd.Log = log;
|
||||||
return cmd.Exec();
|
return cmd.Exec();
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,15 +11,37 @@ namespace SourceGit.Commands
|
||||||
Context = repo;
|
Context = repo;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool Branch(string branch)
|
public bool Branch(string branch, bool force)
|
||||||
{
|
{
|
||||||
Args = $"checkout --recurse-submodules --progress {branch}";
|
var builder = new StringBuilder();
|
||||||
|
builder.Append("checkout --progress ");
|
||||||
|
if (force)
|
||||||
|
builder.Append("--force ");
|
||||||
|
builder.Append(branch);
|
||||||
|
|
||||||
|
Args = builder.ToString();
|
||||||
return Exec();
|
return Exec();
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool Branch(string branch, string basedOn)
|
public bool Branch(string branch, string basedOn, bool force, bool allowOverwrite)
|
||||||
{
|
{
|
||||||
Args = $"checkout --recurse-submodules --progress -b {branch} {basedOn}";
|
var builder = new StringBuilder();
|
||||||
|
builder.Append("checkout --progress ");
|
||||||
|
if (force)
|
||||||
|
builder.Append("--force ");
|
||||||
|
builder.Append(allowOverwrite ? "-B " : "-b ");
|
||||||
|
builder.Append(branch);
|
||||||
|
builder.Append(" ");
|
||||||
|
builder.Append(basedOn);
|
||||||
|
|
||||||
|
Args = builder.ToString();
|
||||||
|
return Exec();
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Commit(string commitId, bool force)
|
||||||
|
{
|
||||||
|
var option = force ? "--force" : string.Empty;
|
||||||
|
Args = $"checkout {option} --detach --progress {commitId}";
|
||||||
return Exec();
|
return Exec();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -56,11 +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)
|
|
||||||
{
|
|
||||||
Args = $"checkout --detach --progress {commitId}";
|
|
||||||
return Exec();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,44 +36,14 @@ namespace SourceGit.Commands
|
||||||
|
|
||||||
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 };
|
||||||
|
|
||||||
Log?.AppendLine($"$ git {Args}\n");
|
proc.OutputDataReceived += (_, e) => HandleOutput(e.Data, errs);
|
||||||
|
proc.ErrorDataReceived += (_, e) => HandleOutput(e.Data, errs);
|
||||||
proc.OutputDataReceived += (_, e) =>
|
|
||||||
{
|
|
||||||
if (e.Data == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
Log?.AppendLine(e.Data);
|
|
||||||
};
|
|
||||||
|
|
||||||
proc.ErrorDataReceived += (_, e) =>
|
|
||||||
{
|
|
||||||
if (string.IsNullOrEmpty(e.Data))
|
|
||||||
{
|
|
||||||
errs.Add(string.Empty);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Log?.AppendLine(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();
|
||||||
|
@ -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,7 +4,7 @@ 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);
|
||||||
|
@ -12,10 +12,10 @@ namespace SourceGit.Commands
|
||||||
WorkingDirectory = repo;
|
WorkingDirectory = repo;
|
||||||
Context = repo;
|
Context = repo;
|
||||||
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()
|
||||||
|
@ -34,6 +34,6 @@ namespace SourceGit.Commands
|
||||||
return succ;
|
return succ;
|
||||||
}
|
}
|
||||||
|
|
||||||
private string _tmpFile = string.Empty;
|
private readonly string _tmpFile;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,7 +39,7 @@ namespace SourceGit.Commands
|
||||||
foreach (var line in lines)
|
foreach (var line in lines)
|
||||||
ParseLine(line);
|
ParseLine(line);
|
||||||
|
|
||||||
_changes.Sort((l, r) => string.Compare(l.Path, r.Path, StringComparison.Ordinal));
|
_changes.Sort((l, r) => Models.NumericSort.Compare(l.Path, r.Path));
|
||||||
return _changes;
|
return _changes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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,9 +28,11 @@ 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 --unified={unified} {opt}";
|
Args = $"diff --no-ext-diff --patch --unified={unified} {opt}";
|
||||||
}
|
}
|
||||||
|
|
||||||
public Models.DiffResult Result()
|
public Models.DiffResult Result()
|
||||||
|
@ -103,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 == '+')
|
||||||
|
@ -114,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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
{
|
{
|
||||||
|
/// <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)
|
public static void All(string repo, bool includeIgnored, Models.ICommandLog log)
|
||||||
{
|
{
|
||||||
new Restore(repo) { Log = log }.Exec();
|
var changes = new QueryLocalChanges(repo).Result();
|
||||||
new Clean(repo, includeIgnored) { Log = log }.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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <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)
|
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)) { Log = log }.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") { Log = log }.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);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -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,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, Models.ICommandLog log)
|
|
||||||
{
|
|
||||||
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, log);
|
|
||||||
|
|
||||||
var devBranch = branches.Find(x => x.Name == develop);
|
|
||||||
if (devBranch == null && current != null)
|
|
||||||
Branch.Create(repo, develop, current.Head, log);
|
|
||||||
|
|
||||||
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);
|
||||||
|
@ -65,103 +25,68 @@ namespace SourceGit.Commands
|
||||||
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, Models.ICommandLog log)
|
|
||||||
{
|
|
||||||
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;
|
start.Log = log;
|
||||||
return start.Exec();
|
return start.Exec();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool Finish(string repo, string type, string name, bool keepBranch, Models.ICommandLog log)
|
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;
|
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,7 +10,7 @@ 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, Models.ICommandLog log)
|
public SubCmd(string repo, string args, Models.ICommandLog log)
|
||||||
{
|
{
|
||||||
|
|
|
@ -5,11 +5,20 @@ namespace SourceGit.Commands
|
||||||
{
|
{
|
||||||
public class Merge : Command
|
public class Merge : Command
|
||||||
{
|
{
|
||||||
public Merge(string repo, string source, string mode)
|
public Merge(string repo, string source, string mode, bool edit)
|
||||||
{
|
{
|
||||||
WorkingDirectory = repo;
|
WorkingDirectory = repo;
|
||||||
Context = repo;
|
Context = repo;
|
||||||
Args = $"merge --progress {source} {mode}";
|
Editor = EditorType.CoreEditor;
|
||||||
|
|
||||||
|
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)
|
public Merge(string repo, List<string> targets, bool autoCommit, string strategy)
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
{
|
{
|
||||||
public class Pull : Command
|
public class Pull : Command
|
||||||
{
|
{
|
||||||
public Pull(string repo, string remote, string branch, bool useRebase, bool noTags)
|
public Pull(string repo, string remote, string branch, bool useRebase)
|
||||||
{
|
{
|
||||||
WorkingDirectory = repo;
|
WorkingDirectory = repo;
|
||||||
Context = repo;
|
Context = repo;
|
||||||
|
@ -12,9 +12,6 @@
|
||||||
if (useRebase)
|
if (useRebase)
|
||||||
Args += "--rebase=true ";
|
Args += "--rebase=true ";
|
||||||
|
|
||||||
if (noTags)
|
|
||||||
Args += "--no-tags ";
|
|
||||||
|
|
||||||
Args += $"{remote} {branch}";
|
Args += $"{remote} {branch}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.IsUpstreamGone = !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.Upstream = parts[4];
|
||||||
branch.IsUpstreamGone = false;
|
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;
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,8 @@
|
||||||
using System.Collections.Generic;
|
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
|
||||||
|
@ -22,7 +24,10 @@ namespace SourceGit.Commands
|
||||||
var outs = new List<Models.Change>();
|
var outs = new List<Models.Change>();
|
||||||
var rs = ReadToEnd();
|
var rs = ReadToEnd();
|
||||||
if (!rs.IsSuccess)
|
if (!rs.IsSuccess)
|
||||||
|
{
|
||||||
|
Dispatcher.UIThread.Post(() => App.RaiseException(Context, rs.StdErr));
|
||||||
return outs;
|
return outs;
|
||||||
|
}
|
||||||
|
|
||||||
var lines = rs.StdOut.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries);
|
var lines = rs.StdOut.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries);
|
||||||
foreach (var line in lines)
|
foreach (var line in lines)
|
||||||
|
@ -117,37 +122,36 @@ namespace SourceGit.Commands
|
||||||
case "CD":
|
case "CD":
|
||||||
change.Set(Models.ChangeState.Copied, Models.ChangeState.Deleted);
|
change.Set(Models.ChangeState.Copied, Models.ChangeState.Deleted);
|
||||||
break;
|
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":
|
case "DD":
|
||||||
change.Set(Models.ChangeState.Deleted, Models.ChangeState.Deleted);
|
change.ConflictReason = Models.ConflictReason.BothDeleted;
|
||||||
|
change.Set(Models.ChangeState.None, Models.ChangeState.Conflicted);
|
||||||
break;
|
break;
|
||||||
case "AU":
|
case "AU":
|
||||||
change.Set(Models.ChangeState.Added, Models.ChangeState.Unmerged);
|
change.ConflictReason = Models.ConflictReason.AddedByUs;
|
||||||
|
change.Set(Models.ChangeState.None, Models.ChangeState.Conflicted);
|
||||||
break;
|
break;
|
||||||
case "UD":
|
case "UD":
|
||||||
change.Set(Models.ChangeState.Unmerged, Models.ChangeState.Deleted);
|
change.ConflictReason = Models.ConflictReason.DeletedByThem;
|
||||||
|
change.Set(Models.ChangeState.None, Models.ChangeState.Conflicted);
|
||||||
break;
|
break;
|
||||||
case "UA":
|
case "UA":
|
||||||
change.Set(Models.ChangeState.Unmerged, Models.ChangeState.Added);
|
change.ConflictReason = Models.ConflictReason.AddedByThem;
|
||||||
|
change.Set(Models.ChangeState.None, Models.ChangeState.Conflicted);
|
||||||
break;
|
break;
|
||||||
case "DU":
|
case "DU":
|
||||||
change.Set(Models.ChangeState.Deleted, Models.ChangeState.Unmerged);
|
change.ConflictReason = Models.ConflictReason.DeletedByUs;
|
||||||
|
change.Set(Models.ChangeState.None, Models.ChangeState.Conflicted);
|
||||||
break;
|
break;
|
||||||
case "AA":
|
case "AA":
|
||||||
change.Set(Models.ChangeState.Added, Models.ChangeState.Added);
|
change.ConflictReason = Models.ConflictReason.BothAdded;
|
||||||
|
change.Set(Models.ChangeState.None, Models.ChangeState.Conflicted);
|
||||||
break;
|
break;
|
||||||
case "UU":
|
case "UU":
|
||||||
change.Set(Models.ChangeState.Unmerged, Models.ChangeState.Unmerged);
|
change.ConflictReason = Models.ConflictReason.BothModified;
|
||||||
|
change.Set(Models.ChangeState.None, Models.ChangeState.Conflicted);
|
||||||
break;
|
break;
|
||||||
case "??":
|
case "??":
|
||||||
change.Set(Models.ChangeState.Untracked, Models.ChangeState.Untracked);
|
change.Set(Models.ChangeState.None, Models.ChangeState.Untracked);
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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,7 +9,7 @@ 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()
|
||||||
|
@ -19,55 +19,50 @@ namespace SourceGit.Commands
|
||||||
if (!rs.IsSuccess)
|
if (!rs.IsSuccess)
|
||||||
return outs;
|
return outs;
|
||||||
|
|
||||||
var nextPartIdx = 0;
|
var items = rs.StdOut.Split('\0', StringSplitOptions.RemoveEmptyEntries);
|
||||||
var start = 0;
|
foreach (var item in items)
|
||||||
var end = rs.StdOut.IndexOf('\n', start);
|
|
||||||
while (end > 0)
|
|
||||||
{
|
{
|
||||||
var line = rs.StdOut.Substring(start, end - start);
|
var current = new Models.Stash();
|
||||||
|
|
||||||
switch (nextPartIdx)
|
var nextPartIdx = 0;
|
||||||
|
var start = 0;
|
||||||
|
var end = item.IndexOf('\n', start);
|
||||||
|
while (end > 0 && nextPartIdx < 4)
|
||||||
{
|
{
|
||||||
case 0:
|
var line = item.Substring(start, end - start);
|
||||||
_current = new Models.Stash() { SHA = line };
|
|
||||||
outs.Add(_current);
|
switch (nextPartIdx)
|
||||||
break;
|
{
|
||||||
case 1:
|
case 0:
|
||||||
ParseParent(line);
|
current.SHA = line;
|
||||||
break;
|
break;
|
||||||
case 2:
|
case 1:
|
||||||
_current.Time = ulong.Parse(line);
|
if (line.Length > 6)
|
||||||
break;
|
current.Parents.AddRange(line.Split(' ', StringSplitOptions.RemoveEmptyEntries));
|
||||||
case 3:
|
break;
|
||||||
_current.Name = line;
|
case 2:
|
||||||
break;
|
current.Time = ulong.Parse(line);
|
||||||
case 4:
|
break;
|
||||||
_current.Message = line;
|
case 3:
|
||||||
|
current.Name = line;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
nextPartIdx++;
|
||||||
|
|
||||||
|
start = end + 1;
|
||||||
|
if (start >= item.Length - 1)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
end = item.IndexOf('\n', start);
|
||||||
}
|
}
|
||||||
|
|
||||||
nextPartIdx++;
|
if (start < item.Length)
|
||||||
if (nextPartIdx > 4)
|
current.Message = item.Substring(start);
|
||||||
nextPartIdx = 0;
|
|
||||||
|
|
||||||
start = end + 1;
|
outs.Add(current);
|
||||||
end = rs.StdOut.IndexOf('\n', start);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (start < rs.StdOut.Length)
|
|
||||||
_current.Message = rs.StdOut.Substring(start);
|
|
||||||
|
|
||||||
return outs;
|
return outs;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ParseParent(string data)
|
|
||||||
{
|
|
||||||
if (data.Length < 8)
|
|
||||||
return;
|
|
||||||
|
|
||||||
_current.Parents.AddRange(data.Split(separator: ' ', options: StringSplitOptions.RemoveEmptyEntries));
|
|
||||||
}
|
|
||||||
|
|
||||||
private Models.Stash _current = null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,7 +40,7 @@ namespace SourceGit.Commands
|
||||||
if (dateEndIdx == -1)
|
if (dateEndIdx == -1)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var dateStr = line.AsSpan().Slice(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,7 @@
|
||||||
namespace SourceGit.Commands
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace SourceGit.Commands
|
||||||
{
|
{
|
||||||
public class Submodule : Command
|
public class Submodule : Command
|
||||||
{
|
{
|
||||||
|
@ -10,7 +13,7 @@
|
||||||
|
|
||||||
public bool Add(string url, string relativePath, bool recursive)
|
public bool Add(string url, string relativePath, bool recursive)
|
||||||
{
|
{
|
||||||
Args = $"submodule add {url} \"{relativePath}\"";
|
Args = $"-c protocol.file.allow=always submodule add \"{url}\" \"{relativePath}\"";
|
||||||
if (!Exec())
|
if (!Exec())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
@ -26,29 +29,37 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool Update(string module, bool init, bool recursive, bool useRemote)
|
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}\"");
|
||||||
|
}
|
||||||
|
|
||||||
|
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 Exec();
|
||||||
return false;
|
}
|
||||||
|
|
||||||
Args = $"rm -rf \"{relativePath}\"";
|
public bool Delete(string module)
|
||||||
|
{
|
||||||
|
Args = $"rm -rf \"{module}\"";
|
||||||
return Exec();
|
return Exec();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ namespace SourceGit.Commands
|
||||||
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;
|
cmd.Log = log;
|
||||||
return cmd.Exec();
|
return cmd.Exec();
|
||||||
}
|
}
|
||||||
|
@ -28,12 +28,13 @@ namespace SourceGit.Commands
|
||||||
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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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,12 +0,0 @@
|
||||||
namespace SourceGit.Commands
|
|
||||||
{
|
|
||||||
public class UpdateRef : Command
|
|
||||||
{
|
|
||||||
public UpdateRef(string repo, string refName, string toRevision)
|
|
||||||
{
|
|
||||||
WorkingDirectory = repo;
|
|
||||||
Context = repo;
|
|
||||||
Args = $"update-ref {refName} {toRevision}";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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; }
|
||||||
|
|
|
@ -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,25 +4,24 @@ namespace SourceGit.Models
|
||||||
{
|
{
|
||||||
public class ConventionalCommitType
|
public class ConventionalCommitType
|
||||||
{
|
{
|
||||||
public string Name { get; set; } = string.Empty;
|
public string Name { get; set; }
|
||||||
public string Type { get; set; } = string.Empty;
|
public string Type { get; set; }
|
||||||
public string Description { get; set; } = string.Empty;
|
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("Features", "feat", "Adding a new feature"),
|
new("Bug Fixes", "fix", "Fixing a bug"),
|
||||||
new ConventionalCommitType("Bug Fixes", "fix", "Fixing a bug"),
|
new("Work In Progress", "wip", "Still being developed and not yet complete"),
|
||||||
new ConventionalCommitType("Work In Progress", "wip", "Still being developed and not yet complete"),
|
new("Reverts", "revert", "Undoing a previous commit"),
|
||||||
new ConventionalCommitType("Reverts", "revert", "Undoing a previous commit"),
|
new("Code Refactoring", "refactor", "Restructuring code without changing its external behavior"),
|
||||||
new ConventionalCommitType("Code Refactoring", "refactor", "Restructuring code without changing its external behavior"),
|
new("Performance Improvements", "perf", "Improves performance"),
|
||||||
new ConventionalCommitType("Performance Improvements", "pref", "Improves performance"),
|
new("Builds", "build", "Changes that affect the build system or external dependencies"),
|
||||||
new ConventionalCommitType("Builds", "build", "Changes that affect the build system or external dependencies"),
|
new("Continuous Integrations", "ci", "Changes to CI configuration files and scripts"),
|
||||||
new ConventionalCommitType("Continuous Integrations", "ci", "Changes to CI configuration files and scripts"),
|
new("Documentations", "docs", "Updating documentation"),
|
||||||
new ConventionalCommitType("Documentations", "docs", "Updating documentation"),
|
new("Styles", "style", "Elements or code styles without changing the code logic"),
|
||||||
new ConventionalCommitType("Styles", "style", "Elements or code styles without changing the code logic"),
|
new("Tests", "test", "Adding or updating tests"),
|
||||||
new ConventionalCommitType("Tests", "test", "Adding or updating tests"),
|
new("Chores", "chore", "Other changes that don't modify src or test files"),
|
||||||
new ConventionalCommitType("Chores", "chore", "Other changes that don't modify src or test files"),
|
];
|
||||||
};
|
|
||||||
|
|
||||||
public ConventionalCommitType(string name, string type, string description)
|
public ConventionalCommitType(string name, string type, string 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
|
||||||
|
@ -147,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');
|
||||||
|
@ -556,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)
|
||||||
{
|
{
|
||||||
|
@ -566,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)
|
||||||
|
@ -655,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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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 = [];
|
||||||
|
}
|
||||||
|
}
|
|
@ -8,10 +8,7 @@ namespace SourceGit.Models
|
||||||
{
|
{
|
||||||
public class IpcChannel : IDisposable
|
public class IpcChannel : IDisposable
|
||||||
{
|
{
|
||||||
public bool IsFirstInstance
|
public bool IsFirstInstance { get; }
|
||||||
{
|
|
||||||
get => _isFirstInstance;
|
|
||||||
}
|
|
||||||
|
|
||||||
public event Action<string> MessageReceived;
|
public event Action<string> MessageReceived;
|
||||||
|
|
||||||
|
@ -19,10 +16,10 @@ namespace SourceGit.Models
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_singletoneLock = File.Open(Path.Combine(Native.OS.DataDir, "process.lock"), FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None);
|
_singletonLock = File.Open(Path.Combine(Native.OS.DataDir, "process.lock"), FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None);
|
||||||
_isFirstInstance = true;
|
IsFirstInstance = true;
|
||||||
_server = new NamedPipeServerStream(
|
_server = new NamedPipeServerStream(
|
||||||
"SourceGitIPCChannel",
|
"SourceGitIPCChannel" + Environment.UserName,
|
||||||
PipeDirection.In,
|
PipeDirection.In,
|
||||||
-1,
|
-1,
|
||||||
PipeTransmissionMode.Byte,
|
PipeTransmissionMode.Byte,
|
||||||
|
@ -32,7 +29,7 @@ namespace SourceGit.Models
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
_isFirstInstance = false;
|
IsFirstInstance = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,7 +37,7 @@ namespace SourceGit.Models
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
using (var client = new NamedPipeClientStream(".", "SourceGitIPCChannel", PipeDirection.Out))
|
using (var client = new NamedPipeClientStream(".", "SourceGitIPCChannel" + Environment.UserName, PipeDirection.Out, PipeOptions.Asynchronous | PipeOptions.CurrentUserOnly))
|
||||||
{
|
{
|
||||||
client.Connect(1000);
|
client.Connect(1000);
|
||||||
if (!client.IsConnected)
|
if (!client.IsConnected)
|
||||||
|
@ -67,7 +64,7 @@ namespace SourceGit.Models
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
_cancellationTokenSource?.Cancel();
|
_cancellationTokenSource?.Cancel();
|
||||||
_singletoneLock?.Dispose();
|
_singletonLock?.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void StartServer()
|
private async void StartServer()
|
||||||
|
@ -96,8 +93,7 @@ namespace SourceGit.Models
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private FileStream _singletoneLock = null;
|
private FileStream _singletonLock = null;
|
||||||
private bool _isFirstInstance = false;
|
|
||||||
private NamedPipeServerStream _server = null;
|
private NamedPipeServerStream _server = null;
|
||||||
private CancellationTokenSource _cancellationTokenSource = 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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -6,8 +6,10 @@ namespace SourceGit.Models
|
||||||
{
|
{
|
||||||
public partial class Remote
|
public partial class Remote
|
||||||
{
|
{
|
||||||
[GeneratedRegex(@"^https?://([-a-zA-Z0-9:%._\+~#=]+@)?[-a-zA-Z0-9:%._\+~#=]{1,256}(\.[a-zA-Z0-9()]{1,6})?(:[0-9]{1,5})?\b(/[-a-zA-Z0-9()@:%_\+.~#?&=]+)+(\.git)?$")]
|
[GeneratedRegex(@"^https?://[^/]+/.+[^/\.]$")]
|
||||||
private static partial Regex REG_HTTPS();
|
private static partial Regex REG_HTTPS();
|
||||||
|
[GeneratedRegex(@"^git://[^/]+/.+[^/\.]$")]
|
||||||
|
private static partial Regex REG_GIT();
|
||||||
[GeneratedRegex(@"^[\w\-]+@[\w\.\-]+(\:[0-9]+)?:([a-zA-z0-9~%][\w\-\./~%]*)?[a-zA-Z0-9](\.git)?$")]
|
[GeneratedRegex(@"^[\w\-]+@[\w\.\-]+(\:[0-9]+)?:([a-zA-z0-9~%][\w\-\./~%]*)?[a-zA-Z0-9](\.git)?$")]
|
||||||
private static partial Regex REG_SSH1();
|
private static partial Regex REG_SSH1();
|
||||||
[GeneratedRegex(@"^ssh://([\w\-]+@)?[\w\.\-]+(\:[0-9]+)?/([a-zA-z0-9~%][\w\-\./~%]*)?[a-zA-Z0-9](\.git)?$")]
|
[GeneratedRegex(@"^ssh://([\w\-]+@)?[\w\.\-]+(\:[0-9]+)?/([a-zA-z0-9~%][\w\-\./~%]*)?[a-zA-Z0-9](\.git)?$")]
|
||||||
|
@ -18,6 +20,7 @@ namespace SourceGit.Models
|
||||||
|
|
||||||
private static readonly Regex[] URL_FORMATS = [
|
private static readonly Regex[] URL_FORMATS = [
|
||||||
REG_HTTPS(),
|
REG_HTTPS(),
|
||||||
|
REG_GIT(),
|
||||||
REG_SSH1(),
|
REG_SSH1(),
|
||||||
REG_SSH2(),
|
REG_SSH2(),
|
||||||
];
|
];
|
||||||
|
@ -30,13 +33,10 @@ namespace SourceGit.Models
|
||||||
if (string.IsNullOrWhiteSpace(url))
|
if (string.IsNullOrWhiteSpace(url))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
for (int i = 1; i < URL_FORMATS.Length; i++)
|
if (REG_SSH1().IsMatch(url))
|
||||||
{
|
return true;
|
||||||
if (URL_FORMATS[i].IsMatch(url))
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
return REG_SSH2().IsMatch(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool IsValidURL(string url)
|
public static bool IsValidURL(string url)
|
||||||
|
@ -50,7 +50,10 @@ namespace SourceGit.Models
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return url.EndsWith(".git", StringComparison.Ordinal) && Directory.Exists(url);
|
return url.StartsWith("file://", StringComparison.Ordinal) ||
|
||||||
|
url.StartsWith("./", StringComparison.Ordinal) ||
|
||||||
|
url.StartsWith("../", StringComparison.Ordinal) ||
|
||||||
|
Directory.Exists(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool TryGetVisitURL(out string url)
|
public bool TryGetVisitURL(out string url)
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
|
@ -32,12 +32,24 @@ namespace SourceGit.Models
|
||||||
set;
|
set;
|
||||||
} = false;
|
} = false;
|
||||||
|
|
||||||
public bool OnlyHighlighCurrentBranchInHistories
|
public bool OnlyHighlightCurrentBranchInHistories
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
set;
|
set;
|
||||||
} = false;
|
} = false;
|
||||||
|
|
||||||
|
public BranchSortMode LocalBranchSortMode
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
set;
|
||||||
|
} = BranchSortMode.Name;
|
||||||
|
|
||||||
|
public BranchSortMode RemoteBranchSortMode
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
set;
|
||||||
|
} = BranchSortMode.Name;
|
||||||
|
|
||||||
public TagSortMode TagSortMode
|
public TagSortMode TagSortMode
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
|
@ -68,18 +80,6 @@ namespace SourceGit.Models
|
||||||
set;
|
set;
|
||||||
} = true;
|
} = true;
|
||||||
|
|
||||||
public bool FetchWithoutTagsOnPull
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
set;
|
|
||||||
} = false;
|
|
||||||
|
|
||||||
public bool FetchAllBranchesOnPull
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
set;
|
|
||||||
} = true;
|
|
||||||
|
|
||||||
public bool CheckSubmodulesOnPush
|
public bool CheckSubmodulesOnPush
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
|
@ -110,6 +110,12 @@ namespace SourceGit.Models
|
||||||
set;
|
set;
|
||||||
} = true;
|
} = true;
|
||||||
|
|
||||||
|
public bool UpdateSubmodulesOnCheckoutBranch
|
||||||
|
{
|
||||||
|
get;
|
||||||
|
set;
|
||||||
|
} = true;
|
||||||
|
|
||||||
public AvaloniaList<Filter> HistoriesFilters
|
public AvaloniaList<Filter> HistoriesFilters
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
|
@ -170,19 +176,13 @@ namespace SourceGit.Models
|
||||||
set;
|
set;
|
||||||
} = false;
|
} = false;
|
||||||
|
|
||||||
public bool KeepIndexWhenStash
|
public int ChangesAfterStashing
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
set;
|
set;
|
||||||
} = false;
|
} = 0;
|
||||||
|
|
||||||
public bool AutoRestoreAfterStash
|
public string PreferredOpenAIService
|
||||||
{
|
|
||||||
get;
|
|
||||||
set;
|
|
||||||
} = false;
|
|
||||||
|
|
||||||
public string PreferedOpenAIService
|
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
set;
|
set;
|
||||||
|
@ -281,9 +281,8 @@ namespace SourceGit.Models
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < HistoriesFilters.Count; i++)
|
foreach (var filter in HistoriesFilters)
|
||||||
{
|
{
|
||||||
var filter = HistoriesFilters[i];
|
|
||||||
if (filter.Type != type)
|
if (filter.Type != type)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
@ -326,28 +325,28 @@ namespace SourceGit.Models
|
||||||
if (filter.Mode == FilterMode.Included)
|
if (filter.Mode == FilterMode.Included)
|
||||||
includedRefs.Add(filter.Pattern);
|
includedRefs.Add(filter.Pattern);
|
||||||
else if (filter.Mode == FilterMode.Excluded)
|
else if (filter.Mode == FilterMode.Excluded)
|
||||||
excludedBranches.Add($"--exclude=\"{filter.Pattern.Substring(11)}\" --decorate-refs-exclude=\"{filter.Pattern}\"");
|
excludedBranches.Add($"--exclude=\"{filter.Pattern.AsSpan(11)}\" --decorate-refs-exclude=\"{filter.Pattern}\"");
|
||||||
}
|
}
|
||||||
else if (filter.Type == FilterType.LocalBranchFolder)
|
else if (filter.Type == FilterType.LocalBranchFolder)
|
||||||
{
|
{
|
||||||
if (filter.Mode == FilterMode.Included)
|
if (filter.Mode == FilterMode.Included)
|
||||||
includedRefs.Add($"--branches={filter.Pattern.Substring(11)}/*");
|
includedRefs.Add($"--branches={filter.Pattern.AsSpan(11)}/*");
|
||||||
else if (filter.Mode == FilterMode.Excluded)
|
else if (filter.Mode == FilterMode.Excluded)
|
||||||
excludedBranches.Add($"--exclude=\"{filter.Pattern.Substring(11)}/*\" --decorate-refs-exclude=\"{filter.Pattern}/*\"");
|
excludedBranches.Add($"--exclude=\"{filter.Pattern.AsSpan(11)}/*\" --decorate-refs-exclude=\"{filter.Pattern}/*\"");
|
||||||
}
|
}
|
||||||
else if (filter.Type == FilterType.RemoteBranch)
|
else if (filter.Type == FilterType.RemoteBranch)
|
||||||
{
|
{
|
||||||
if (filter.Mode == FilterMode.Included)
|
if (filter.Mode == FilterMode.Included)
|
||||||
includedRefs.Add(filter.Pattern);
|
includedRefs.Add(filter.Pattern);
|
||||||
else if (filter.Mode == FilterMode.Excluded)
|
else if (filter.Mode == FilterMode.Excluded)
|
||||||
excludedRemotes.Add($"--exclude=\"{filter.Pattern.Substring(13)}\" --decorate-refs-exclude=\"{filter.Pattern}\"");
|
excludedRemotes.Add($"--exclude=\"{filter.Pattern.AsSpan(13)}\" --decorate-refs-exclude=\"{filter.Pattern}\"");
|
||||||
}
|
}
|
||||||
else if (filter.Type == FilterType.RemoteBranchFolder)
|
else if (filter.Type == FilterType.RemoteBranchFolder)
|
||||||
{
|
{
|
||||||
if (filter.Mode == FilterMode.Included)
|
if (filter.Mode == FilterMode.Included)
|
||||||
includedRefs.Add($"--remotes={filter.Pattern.Substring(13)}/*");
|
includedRefs.Add($"--remotes={filter.Pattern.AsSpan(13)}/*");
|
||||||
else if (filter.Mode == FilterMode.Excluded)
|
else if (filter.Mode == FilterMode.Excluded)
|
||||||
excludedRemotes.Add($"--exclude=\"{filter.Pattern.Substring(13)}/*\" --decorate-refs-exclude=\"{filter.Pattern}/*\"");
|
excludedRemotes.Add($"--exclude=\"{filter.Pattern.AsSpan(13)}/*\" --decorate-refs-exclude=\"{filter.Pattern}/*\"");
|
||||||
}
|
}
|
||||||
else if (filter.Type == FilterType.Tag)
|
else if (filter.Type == FilterType.Tag)
|
||||||
{
|
{
|
||||||
|
@ -399,6 +398,7 @@ namespace SourceGit.Models
|
||||||
|
|
||||||
public void PushCommitMessage(string message)
|
public void PushCommitMessage(string message)
|
||||||
{
|
{
|
||||||
|
message = message.Trim().ReplaceLineEndings("\n");
|
||||||
var existIdx = CommitMessages.IndexOf(message);
|
var existIdx = CommitMessages.IndexOf(message);
|
||||||
if (existIdx == 0)
|
if (existIdx == 0)
|
||||||
return;
|
return;
|
||||||
|
@ -446,5 +446,19 @@ namespace SourceGit.Models
|
||||||
if (act != null)
|
if (act != null)
|
||||||
CustomActions.Remove(act);
|
CustomActions.Remove(act);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void MoveCustomActionUp(CustomAction act)
|
||||||
|
{
|
||||||
|
var idx = CustomActions.IndexOf(act);
|
||||||
|
if (idx > 0)
|
||||||
|
CustomActions.Move(idx - 1, idx);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void MoveCustomActionDown(CustomAction act)
|
||||||
|
{
|
||||||
|
var idx = CustomActions.IndexOf(act);
|
||||||
|
if (idx < CustomActions.Count - 1)
|
||||||
|
CustomActions.Move(idx + 1, idx);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
using Avalonia.Media.Imaging;
|
using System.Globalization;
|
||||||
|
using System.IO;
|
||||||
|
using Avalonia.Media.Imaging;
|
||||||
|
|
||||||
namespace SourceGit.Models
|
namespace SourceGit.Models
|
||||||
{
|
{
|
||||||
|
@ -9,10 +11,17 @@ namespace SourceGit.Models
|
||||||
|
|
||||||
public class RevisionImageFile
|
public class RevisionImageFile
|
||||||
{
|
{
|
||||||
public Bitmap Image { get; set; } = null;
|
public Bitmap Image { get; }
|
||||||
public long FileSize { get; set; } = 0;
|
public long FileSize { get; }
|
||||||
public string ImageType { get; set; } = string.Empty;
|
public string ImageType { get; }
|
||||||
public string ImageSize => Image != null ? $"{Image.PixelSize.Width} x {Image.PixelSize.Height}" : "0 x 0";
|
public string ImageSize => Image != null ? $"{Image.PixelSize.Width} x {Image.PixelSize.Height}" : "0 x 0";
|
||||||
|
|
||||||
|
public RevisionImageFile(string file, Bitmap img, long size)
|
||||||
|
{
|
||||||
|
Image = img;
|
||||||
|
FileSize = size;
|
||||||
|
ImageType = Path.GetExtension(file)!.Substring(1).ToUpper(CultureInfo.CurrentCulture);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class RevisionTextFile
|
public class RevisionTextFile
|
||||||
|
|
|
@ -33,9 +33,7 @@ namespace SourceGit.Models
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class AlreadyUpToDate
|
public class AlreadyUpToDate;
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public class SelfUpdateFailed
|
public class SelfUpdateFailed
|
||||||
{
|
{
|
||||||
|
|
|
@ -42,7 +42,8 @@ namespace SourceGit.Models
|
||||||
new ShellOrTerminal("mac-terminal", "Terminal", ""),
|
new ShellOrTerminal("mac-terminal", "Terminal", ""),
|
||||||
new ShellOrTerminal("iterm2", "iTerm", ""),
|
new ShellOrTerminal("iterm2", "iTerm", ""),
|
||||||
new ShellOrTerminal("warp", "Warp", ""),
|
new ShellOrTerminal("warp", "Warp", ""),
|
||||||
new ShellOrTerminal("ghostty", "Ghostty", "")
|
new ShellOrTerminal("ghostty", "Ghostty", ""),
|
||||||
|
new ShellOrTerminal("kitty", "kitty", "")
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -58,6 +59,7 @@ namespace SourceGit.Models
|
||||||
new ShellOrTerminal("foot", "Foot", "foot"),
|
new ShellOrTerminal("foot", "Foot", "foot"),
|
||||||
new ShellOrTerminal("wezterm", "WezTerm", "wezterm"),
|
new ShellOrTerminal("wezterm", "WezTerm", "wezterm"),
|
||||||
new ShellOrTerminal("ptyxis", "Ptyxis", "ptyxis"),
|
new ShellOrTerminal("ptyxis", "Ptyxis", "ptyxis"),
|
||||||
|
new ShellOrTerminal("kitty", "kitty", "kitty"),
|
||||||
new ShellOrTerminal("custom", "Custom", ""),
|
new ShellOrTerminal("custom", "Custom", ""),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,24 @@ namespace SourceGit.Models
|
||||||
public ulong Time { get; set; } = 0;
|
public ulong Time { get; set; } = 0;
|
||||||
public string Message { get; set; } = "";
|
public string Message { get; set; } = "";
|
||||||
|
|
||||||
public string TimeStr => DateTime.UnixEpoch.AddSeconds(Time).ToLocalTime().ToString(DateTimeFormat.Actived.DateTime);
|
public string Subject
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var idx = Message.IndexOf('\n', StringComparison.Ordinal);
|
||||||
|
return idx > 0 ? Message.Substring(0, idx).Trim() : Message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string TimeStr
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return DateTime.UnixEpoch
|
||||||
|
.AddSeconds(Time)
|
||||||
|
.ToLocalTime()
|
||||||
|
.ToString(DateTimeFormat.Active.DateTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,25 +26,23 @@ namespace SourceGit.Models
|
||||||
|
|
||||||
public class StatisticsReport
|
public class StatisticsReport
|
||||||
{
|
{
|
||||||
public static readonly string[] WEEKDAYS = ["SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT"];
|
|
||||||
|
|
||||||
public int Total { get; set; } = 0;
|
public int Total { get; set; } = 0;
|
||||||
public List<StatisticsAuthor> Authors { get; set; } = new List<StatisticsAuthor>();
|
public List<StatisticsAuthor> Authors { get; set; } = new();
|
||||||
public List<ISeries> Series { get; set; } = new List<ISeries>();
|
public List<ISeries> Series { get; set; } = new();
|
||||||
public List<Axis> XAxes { get; set; } = new List<Axis>();
|
public List<Axis> XAxes { get; set; } = new();
|
||||||
public List<Axis> YAxes { get; set; } = new List<Axis>();
|
public List<Axis> YAxes { get; set; } = new();
|
||||||
public StatisticsAuthor SelectedAuthor { get => _selectedAuthor; set => ChangeAuthor(value); }
|
public StatisticsAuthor SelectedAuthor { get => _selectedAuthor; set => ChangeAuthor(value); }
|
||||||
|
|
||||||
public StatisticsReport(StatisticsMode mode, DateTime start)
|
public StatisticsReport(StatisticsMode mode, DateTime start)
|
||||||
{
|
{
|
||||||
_mode = mode;
|
_mode = mode;
|
||||||
|
|
||||||
YAxes = [new Axis()
|
YAxes.Add(new Axis()
|
||||||
{
|
{
|
||||||
TextSize = 10,
|
TextSize = 10,
|
||||||
MinLimit = 0,
|
MinLimit = 0,
|
||||||
SeparatorsPaint = new SolidColorPaint(new SKColor(0x40808080)) { StrokeThickness = 1 }
|
SeparatorsPaint = new SolidColorPaint(new SKColor(0x40808080)) { StrokeThickness = 1 }
|
||||||
}];
|
});
|
||||||
|
|
||||||
if (mode == StatisticsMode.ThisWeek)
|
if (mode == StatisticsMode.ThisWeek)
|
||||||
{
|
{
|
||||||
|
@ -72,7 +70,7 @@ namespace SourceGit.Models
|
||||||
{
|
{
|
||||||
Total++;
|
Total++;
|
||||||
|
|
||||||
var normalized = DateTime.MinValue;
|
DateTime normalized;
|
||||||
if (_mode == StatisticsMode.ThisWeek || _mode == StatisticsMode.ThisMonth)
|
if (_mode == StatisticsMode.ThisWeek || _mode == StatisticsMode.ThisMonth)
|
||||||
normalized = time.Date;
|
normalized = time.Date;
|
||||||
else
|
else
|
||||||
|
@ -172,26 +170,27 @@ namespace SourceGit.Models
|
||||||
ChangeColor(_fillColor);
|
ChangeColor(_fillColor);
|
||||||
}
|
}
|
||||||
|
|
||||||
private StatisticsMode _mode = StatisticsMode.All;
|
private static readonly string[] WEEKDAYS = ["SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT"];
|
||||||
private Dictionary<User, int> _mapUsers = new Dictionary<User, int>();
|
private StatisticsMode _mode;
|
||||||
private Dictionary<DateTime, int> _mapSamples = new Dictionary<DateTime, int>();
|
private Dictionary<User, int> _mapUsers = new();
|
||||||
private Dictionary<User, Dictionary<DateTime, int>> _mapUserSamples = new Dictionary<User, Dictionary<DateTime, int>>();
|
private Dictionary<DateTime, int> _mapSamples = new();
|
||||||
|
private Dictionary<User, Dictionary<DateTime, int>> _mapUserSamples = new();
|
||||||
private StatisticsAuthor _selectedAuthor = null;
|
private StatisticsAuthor _selectedAuthor = null;
|
||||||
private uint _fillColor = 255;
|
private uint _fillColor = 255;
|
||||||
}
|
}
|
||||||
|
|
||||||
public class Statistics
|
public class Statistics
|
||||||
{
|
{
|
||||||
public StatisticsReport All { get; set; }
|
public StatisticsReport All { get; }
|
||||||
public StatisticsReport Month { get; set; }
|
public StatisticsReport Month { get; }
|
||||||
public StatisticsReport Week { get; set; }
|
public StatisticsReport Week { get; }
|
||||||
|
|
||||||
public Statistics()
|
public Statistics()
|
||||||
{
|
{
|
||||||
_today = DateTime.Now.ToLocalTime().Date;
|
var today = DateTime.Now.ToLocalTime().Date;
|
||||||
var weekOffset = (7 + (int)_today.DayOfWeek - (int)CultureInfo.CurrentCulture.DateTimeFormat.FirstDayOfWeek) % 7;
|
var weekOffset = (7 + (int)today.DayOfWeek - (int)CultureInfo.CurrentCulture.DateTimeFormat.FirstDayOfWeek) % 7;
|
||||||
_thisWeekStart = _today.AddDays(-weekOffset);
|
_thisWeekStart = today.AddDays(-weekOffset);
|
||||||
_thisMonthStart = _today.AddDays(1 - _today.Day);
|
_thisMonthStart = today.AddDays(1 - today.Day);
|
||||||
|
|
||||||
All = new StatisticsReport(StatisticsMode.All, DateTime.MinValue);
|
All = new StatisticsReport(StatisticsMode.All, DateTime.MinValue);
|
||||||
Month = new StatisticsReport(StatisticsMode.ThisMonth, _thisMonthStart);
|
Month = new StatisticsReport(StatisticsMode.ThisMonth, _thisMonthStart);
|
||||||
|
@ -200,7 +199,13 @@ namespace SourceGit.Models
|
||||||
|
|
||||||
public void AddCommit(string author, double timestamp)
|
public void AddCommit(string author, double timestamp)
|
||||||
{
|
{
|
||||||
var user = User.FindOrAdd(author);
|
var emailIdx = author.IndexOf('±', StringComparison.Ordinal);
|
||||||
|
var email = author.Substring(emailIdx + 1).ToLower(CultureInfo.CurrentCulture);
|
||||||
|
if (!_users.TryGetValue(email, out var user))
|
||||||
|
{
|
||||||
|
user = User.FindOrAdd(author);
|
||||||
|
_users.Add(email, user);
|
||||||
|
}
|
||||||
|
|
||||||
var time = DateTime.UnixEpoch.AddSeconds(timestamp).ToLocalTime();
|
var time = DateTime.UnixEpoch.AddSeconds(timestamp).ToLocalTime();
|
||||||
if (time >= _thisWeekStart)
|
if (time >= _thisWeekStart)
|
||||||
|
@ -214,13 +219,15 @@ namespace SourceGit.Models
|
||||||
|
|
||||||
public void Complete()
|
public void Complete()
|
||||||
{
|
{
|
||||||
|
_users.Clear();
|
||||||
|
|
||||||
All.Complete();
|
All.Complete();
|
||||||
Month.Complete();
|
Month.Complete();
|
||||||
Week.Complete();
|
Week.Complete();
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly DateTime _today;
|
|
||||||
private readonly DateTime _thisMonthStart;
|
private readonly DateTime _thisMonthStart;
|
||||||
private readonly DateTime _thisWeekStart;
|
private readonly DateTime _thisWeekStart;
|
||||||
|
private readonly Dictionary<string, User> _users = new();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,20 @@
|
||||||
namespace SourceGit.Models
|
namespace SourceGit.Models
|
||||||
{
|
{
|
||||||
|
public enum SubmoduleStatus
|
||||||
|
{
|
||||||
|
Normal = 0,
|
||||||
|
NotInited,
|
||||||
|
RevisionChanged,
|
||||||
|
Unmerged,
|
||||||
|
Modified,
|
||||||
|
}
|
||||||
|
|
||||||
public class Submodule
|
public class Submodule
|
||||||
{
|
{
|
||||||
public string Path { get; set; } = "";
|
public string Path { get; set; } = string.Empty;
|
||||||
public bool IsDirty { get; set; } = false;
|
public string SHA { get; set; } = string.Empty;
|
||||||
|
public string URL { get; set; } = string.Empty;
|
||||||
|
public SubmoduleStatus Status { get; set; } = SubmoduleStatus.Normal;
|
||||||
|
public bool IsDirty => Status > SubmoduleStatus.NotInited;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,13 +5,13 @@ namespace SourceGit.Models
|
||||||
public enum TagSortMode
|
public enum TagSortMode
|
||||||
{
|
{
|
||||||
CreatorDate = 0,
|
CreatorDate = 0,
|
||||||
NameInAscending,
|
Name,
|
||||||
NameInDescending,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public class Tag : ObservableObject
|
public class Tag : ObservableObject
|
||||||
{
|
{
|
||||||
public string Name { get; set; } = string.Empty;
|
public string Name { get; set; } = string.Empty;
|
||||||
|
public bool IsAnnotated { get; set; } = false;
|
||||||
public string SHA { get; set; } = string.Empty;
|
public string SHA { get; set; } = string.Empty;
|
||||||
public ulong CreatorDate { get; set; } = 0;
|
public ulong CreatorDate { get; set; } = 0;
|
||||||
public string Message { get; set; } = string.Empty;
|
public string Message { get; set; } = string.Empty;
|
||||||
|
|
|
@ -102,7 +102,7 @@ namespace SourceGit.Models
|
||||||
private int? Integer()
|
private int? Integer()
|
||||||
{
|
{
|
||||||
var start = _pos;
|
var start = _pos;
|
||||||
while (Peek() is char c && c >= '0' && c <= '9')
|
while (Peek() is >= '0' and <= '9')
|
||||||
{
|
{
|
||||||
_pos++;
|
_pos++;
|
||||||
}
|
}
|
||||||
|
@ -118,7 +118,7 @@ namespace SourceGit.Models
|
||||||
// text token start
|
// text token start
|
||||||
var tok = _pos;
|
var tok = _pos;
|
||||||
bool esc = false;
|
bool esc = false;
|
||||||
while (Next() is char c)
|
while (Next() is { } c)
|
||||||
{
|
{
|
||||||
if (esc)
|
if (esc)
|
||||||
{
|
{
|
||||||
|
@ -129,7 +129,7 @@ namespace SourceGit.Models
|
||||||
{
|
{
|
||||||
case ESCAPE:
|
case ESCAPE:
|
||||||
// allow to escape only \ and $
|
// allow to escape only \ and $
|
||||||
if (Peek() is char nc && (nc == ESCAPE || nc == VARIABLE_ANCHOR))
|
if (Peek() is { } nc && (nc == ESCAPE || nc == VARIABLE_ANCHOR))
|
||||||
{
|
{
|
||||||
esc = true;
|
esc = true;
|
||||||
FlushText(tok, _pos - 1);
|
FlushText(tok, _pos - 1);
|
||||||
|
@ -173,7 +173,7 @@ namespace SourceGit.Models
|
||||||
if (Next() != VARIABLE_START)
|
if (Next() != VARIABLE_START)
|
||||||
return null;
|
return null;
|
||||||
int name_start = _pos;
|
int name_start = _pos;
|
||||||
while (Next() is char c)
|
while (Next() is { } c)
|
||||||
{
|
{
|
||||||
// name character, continue advancing
|
// name character, continue advancing
|
||||||
if (IsNameChar(c))
|
if (IsNameChar(c))
|
||||||
|
@ -228,7 +228,7 @@ namespace SourceGit.Models
|
||||||
var sb = new StringBuilder();
|
var sb = new StringBuilder();
|
||||||
var tok = _pos;
|
var tok = _pos;
|
||||||
var esc = false;
|
var esc = false;
|
||||||
while (Next() is char c)
|
while (Next() is { } c)
|
||||||
{
|
{
|
||||||
if (esc)
|
if (esc)
|
||||||
{
|
{
|
||||||
|
@ -277,7 +277,7 @@ namespace SourceGit.Models
|
||||||
var sb = new StringBuilder();
|
var sb = new StringBuilder();
|
||||||
var tok = _pos;
|
var tok = _pos;
|
||||||
var esc = false;
|
var esc = false;
|
||||||
while (Next() is char c)
|
while (Next() is { } c)
|
||||||
{
|
{
|
||||||
if (esc)
|
if (esc)
|
||||||
{
|
{
|
||||||
|
@ -402,7 +402,7 @@ namespace SourceGit.Models
|
||||||
|
|
||||||
sb.AppendJoin(", ", paths);
|
sb.AppendJoin(", ", paths);
|
||||||
if (max < context.changes.Count)
|
if (max < context.changes.Count)
|
||||||
sb.AppendFormat(" and {0} other files", context.changes.Count - max);
|
sb.Append($" and {context.changes.Count - max} other files");
|
||||||
|
|
||||||
return sb.ToString();
|
return sb.ToString();
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,30 +2,22 @@
|
||||||
|
|
||||||
namespace SourceGit.Models
|
namespace SourceGit.Models
|
||||||
{
|
{
|
||||||
public class TextInlineChange
|
public class TextInlineChange(int dp, int dc, int ap, int ac)
|
||||||
{
|
{
|
||||||
public int DeletedStart { get; set; }
|
public int DeletedStart { get; set; } = dp;
|
||||||
public int DeletedCount { get; set; }
|
public int DeletedCount { get; set; } = dc;
|
||||||
public int AddedStart { get; set; }
|
public int AddedStart { get; set; } = ap;
|
||||||
public int AddedCount { get; set; }
|
public int AddedCount { get; set; } = ac;
|
||||||
|
|
||||||
class Chunk
|
private class Chunk(int hash, int start, int size)
|
||||||
{
|
{
|
||||||
public int Hash;
|
public readonly int Hash = hash;
|
||||||
|
public readonly int Start = start;
|
||||||
|
public readonly int Size = size;
|
||||||
public bool Modified;
|
public bool Modified;
|
||||||
public int Start;
|
|
||||||
public int Size;
|
|
||||||
|
|
||||||
public Chunk(int hash, int start, int size)
|
|
||||||
{
|
|
||||||
Hash = hash;
|
|
||||||
Modified = false;
|
|
||||||
Start = start;
|
|
||||||
Size = size;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
enum Edit
|
private enum Edit
|
||||||
{
|
{
|
||||||
None,
|
None,
|
||||||
DeletedRight,
|
DeletedRight,
|
||||||
|
@ -34,7 +26,7 @@ namespace SourceGit.Models
|
||||||
AddedLeft,
|
AddedLeft,
|
||||||
}
|
}
|
||||||
|
|
||||||
class EditResult
|
private class EditResult
|
||||||
{
|
{
|
||||||
public Edit State;
|
public Edit State;
|
||||||
public int DeleteStart;
|
public int DeleteStart;
|
||||||
|
@ -43,14 +35,6 @@ namespace SourceGit.Models
|
||||||
public int AddEnd;
|
public int AddEnd;
|
||||||
}
|
}
|
||||||
|
|
||||||
public TextInlineChange(int dp, int dc, int ap, int ac)
|
|
||||||
{
|
|
||||||
DeletedStart = dp;
|
|
||||||
DeletedCount = dc;
|
|
||||||
AddedStart = ap;
|
|
||||||
AddedCount = ac;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static List<TextInlineChange> Compare(string oldValue, string newValue)
|
public static List<TextInlineChange> Compare(string oldValue, string newValue)
|
||||||
{
|
{
|
||||||
var hashes = new Dictionary<string, int>();
|
var hashes = new Dictionary<string, int>();
|
||||||
|
@ -204,11 +188,10 @@ namespace SourceGit.Models
|
||||||
|
|
||||||
for (int i = 0; i <= half; i++)
|
for (int i = 0; i <= half; i++)
|
||||||
{
|
{
|
||||||
|
|
||||||
for (int j = -i; j <= i; j += 2)
|
for (int j = -i; j <= i; j += 2)
|
||||||
{
|
{
|
||||||
var idx = j + half;
|
var idx = j + half;
|
||||||
int o, n;
|
int o;
|
||||||
if (j == -i || (j != i && forward[idx - 1] < forward[idx + 1]))
|
if (j == -i || (j != i && forward[idx - 1] < forward[idx + 1]))
|
||||||
{
|
{
|
||||||
o = forward[idx + 1];
|
o = forward[idx + 1];
|
||||||
|
@ -220,7 +203,7 @@ namespace SourceGit.Models
|
||||||
rs.State = Edit.DeletedRight;
|
rs.State = Edit.DeletedRight;
|
||||||
}
|
}
|
||||||
|
|
||||||
n = o - j;
|
var n = o - j;
|
||||||
|
|
||||||
var startX = o;
|
var startX = o;
|
||||||
var startY = n;
|
var startY = n;
|
||||||
|
@ -258,7 +241,7 @@ namespace SourceGit.Models
|
||||||
for (int j = -i; j <= i; j += 2)
|
for (int j = -i; j <= i; j += 2)
|
||||||
{
|
{
|
||||||
var idx = j + half;
|
var idx = j + half;
|
||||||
int o, n;
|
int o;
|
||||||
if (j == -i || (j != i && reverse[idx + 1] <= reverse[idx - 1]))
|
if (j == -i || (j != i && reverse[idx + 1] <= reverse[idx - 1]))
|
||||||
{
|
{
|
||||||
o = reverse[idx + 1] - 1;
|
o = reverse[idx + 1] - 1;
|
||||||
|
@ -270,7 +253,7 @@ namespace SourceGit.Models
|
||||||
rs.State = Edit.AddedLeft;
|
rs.State = Edit.AddedLeft;
|
||||||
}
|
}
|
||||||
|
|
||||||
n = o - (j + delta);
|
var n = o - (j + delta);
|
||||||
|
|
||||||
var endX = o;
|
var endX = o;
|
||||||
var endY = n;
|
var endY = n;
|
||||||
|
@ -312,8 +295,7 @@ namespace SourceGit.Models
|
||||||
|
|
||||||
private static void AddChunk(List<Chunk> chunks, Dictionary<string, int> hashes, string data, int start)
|
private static void AddChunk(List<Chunk> chunks, Dictionary<string, int> hashes, string data, int start)
|
||||||
{
|
{
|
||||||
int hash;
|
if (hashes.TryGetValue(data, out var hash))
|
||||||
if (hashes.TryGetValue(data, out hash))
|
|
||||||
{
|
{
|
||||||
chunks.Add(new Chunk(hash, start, data.Length));
|
chunks.Add(new Chunk(hash, start, data.Length));
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,11 +26,7 @@ namespace SourceGit.Models
|
||||||
|
|
||||||
public override bool Equals(object obj)
|
public override bool Equals(object obj)
|
||||||
{
|
{
|
||||||
if (obj == null || !(obj is User))
|
return obj is User other && Name == other.Name && Email == other.Email;
|
||||||
return false;
|
|
||||||
|
|
||||||
var other = obj as User;
|
|
||||||
return Name == other.Name && Email == other.Email;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override int GetHashCode()
|
public override int GetHashCode()
|
||||||
|
|
|
@ -12,27 +12,50 @@ namespace SourceGit.Models
|
||||||
{
|
{
|
||||||
_repo = repo;
|
_repo = repo;
|
||||||
|
|
||||||
_wcWatcher = new FileSystemWatcher();
|
var testGitDir = new DirectoryInfo(Path.Combine(fullpath, ".git")).FullName;
|
||||||
_wcWatcher.Path = fullpath;
|
var desiredDir = new DirectoryInfo(gitDir).FullName;
|
||||||
_wcWatcher.Filter = "*";
|
if (testGitDir.Equals(desiredDir, StringComparison.Ordinal))
|
||||||
_wcWatcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.DirectoryName | NotifyFilters.FileName | NotifyFilters.Size | NotifyFilters.CreationTime;
|
{
|
||||||
_wcWatcher.IncludeSubdirectories = true;
|
var combined = new FileSystemWatcher();
|
||||||
_wcWatcher.Created += OnWorkingCopyChanged;
|
combined.Path = fullpath;
|
||||||
_wcWatcher.Renamed += OnWorkingCopyChanged;
|
combined.Filter = "*";
|
||||||
_wcWatcher.Changed += OnWorkingCopyChanged;
|
combined.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.DirectoryName | NotifyFilters.FileName | NotifyFilters.Size | NotifyFilters.CreationTime;
|
||||||
_wcWatcher.Deleted += OnWorkingCopyChanged;
|
combined.IncludeSubdirectories = true;
|
||||||
_wcWatcher.EnableRaisingEvents = true;
|
combined.Created += OnRepositoryChanged;
|
||||||
|
combined.Renamed += OnRepositoryChanged;
|
||||||
|
combined.Changed += OnRepositoryChanged;
|
||||||
|
combined.Deleted += OnRepositoryChanged;
|
||||||
|
combined.EnableRaisingEvents = true;
|
||||||
|
|
||||||
_repoWatcher = new FileSystemWatcher();
|
_watchers.Add(combined);
|
||||||
_repoWatcher.Path = gitDir;
|
}
|
||||||
_repoWatcher.Filter = "*";
|
else
|
||||||
_repoWatcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.DirectoryName | NotifyFilters.FileName;
|
{
|
||||||
_repoWatcher.IncludeSubdirectories = true;
|
var wc = new FileSystemWatcher();
|
||||||
_repoWatcher.Created += OnRepositoryChanged;
|
wc.Path = fullpath;
|
||||||
_repoWatcher.Renamed += OnRepositoryChanged;
|
wc.Filter = "*";
|
||||||
_repoWatcher.Changed += OnRepositoryChanged;
|
wc.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.DirectoryName | NotifyFilters.FileName | NotifyFilters.Size | NotifyFilters.CreationTime;
|
||||||
_repoWatcher.Deleted += OnRepositoryChanged;
|
wc.IncludeSubdirectories = true;
|
||||||
_repoWatcher.EnableRaisingEvents = true;
|
wc.Created += OnWorkingCopyChanged;
|
||||||
|
wc.Renamed += OnWorkingCopyChanged;
|
||||||
|
wc.Changed += OnWorkingCopyChanged;
|
||||||
|
wc.Deleted += OnWorkingCopyChanged;
|
||||||
|
wc.EnableRaisingEvents = true;
|
||||||
|
|
||||||
|
var git = new FileSystemWatcher();
|
||||||
|
git.Path = gitDir;
|
||||||
|
git.Filter = "*";
|
||||||
|
git.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.DirectoryName | NotifyFilters.FileName;
|
||||||
|
git.IncludeSubdirectories = true;
|
||||||
|
git.Created += OnGitDirChanged;
|
||||||
|
git.Renamed += OnGitDirChanged;
|
||||||
|
git.Changed += OnGitDirChanged;
|
||||||
|
git.Deleted += OnGitDirChanged;
|
||||||
|
git.EnableRaisingEvents = true;
|
||||||
|
|
||||||
|
_watchers.Add(wc);
|
||||||
|
_watchers.Add(git);
|
||||||
|
}
|
||||||
|
|
||||||
_timer = new Timer(Tick, null, 100, 100);
|
_timer = new Timer(Tick, null, 100, 100);
|
||||||
}
|
}
|
||||||
|
@ -77,22 +100,13 @@ namespace SourceGit.Models
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
_repoWatcher.EnableRaisingEvents = false;
|
foreach (var watcher in _watchers)
|
||||||
_repoWatcher.Created -= OnRepositoryChanged;
|
{
|
||||||
_repoWatcher.Renamed -= OnRepositoryChanged;
|
watcher.EnableRaisingEvents = false;
|
||||||
_repoWatcher.Changed -= OnRepositoryChanged;
|
watcher.Dispose();
|
||||||
_repoWatcher.Deleted -= OnRepositoryChanged;
|
}
|
||||||
_repoWatcher.Dispose();
|
|
||||||
_repoWatcher = null;
|
|
||||||
|
|
||||||
_wcWatcher.EnableRaisingEvents = false;
|
|
||||||
_wcWatcher.Created -= OnWorkingCopyChanged;
|
|
||||||
_wcWatcher.Renamed -= OnWorkingCopyChanged;
|
|
||||||
_wcWatcher.Changed -= OnWorkingCopyChanged;
|
|
||||||
_wcWatcher.Deleted -= OnWorkingCopyChanged;
|
|
||||||
_wcWatcher.Dispose();
|
|
||||||
_wcWatcher = null;
|
|
||||||
|
|
||||||
|
_watchers.Clear();
|
||||||
_timer.Dispose();
|
_timer.Dispose();
|
||||||
_timer = null;
|
_timer = null;
|
||||||
}
|
}
|
||||||
|
@ -107,7 +121,6 @@ namespace SourceGit.Models
|
||||||
{
|
{
|
||||||
_updateBranch = 0;
|
_updateBranch = 0;
|
||||||
_updateWC = 0;
|
_updateWC = 0;
|
||||||
_updateSubmodules = 0;
|
|
||||||
|
|
||||||
if (_updateTags > 0)
|
if (_updateTags > 0)
|
||||||
{
|
{
|
||||||
|
@ -115,10 +128,15 @@ namespace SourceGit.Models
|
||||||
Task.Run(_repo.RefreshTags);
|
Task.Run(_repo.RefreshTags);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_updateSubmodules > 0 || _repo.MayHaveSubmodules())
|
||||||
|
{
|
||||||
|
_updateSubmodules = 0;
|
||||||
|
Task.Run(_repo.RefreshSubmodules);
|
||||||
|
}
|
||||||
|
|
||||||
Task.Run(_repo.RefreshBranches);
|
Task.Run(_repo.RefreshBranches);
|
||||||
Task.Run(_repo.RefreshCommits);
|
Task.Run(_repo.RefreshCommits);
|
||||||
Task.Run(_repo.RefreshWorkingCopyChanges);
|
Task.Run(_repo.RefreshWorkingCopyChanges);
|
||||||
Task.Run(_repo.RefreshSubmodules);
|
|
||||||
Task.Run(_repo.RefreshWorktrees);
|
Task.Run(_repo.RefreshWorktrees);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -150,14 +168,63 @@ namespace SourceGit.Models
|
||||||
|
|
||||||
private void OnRepositoryChanged(object o, FileSystemEventArgs e)
|
private void OnRepositoryChanged(object o, FileSystemEventArgs e)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(e.Name) || e.Name.EndsWith(".lock", StringComparison.Ordinal))
|
if (string.IsNullOrEmpty(e.Name) || e.Name.Equals(".git", StringComparison.Ordinal))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var name = e.Name.Replace("\\", "/");
|
var name = e.Name.Replace('\\', '/').TrimEnd('/');
|
||||||
if (name.StartsWith("modules", StringComparison.Ordinal) && name.EndsWith("HEAD", StringComparison.Ordinal))
|
if (name.EndsWith("/.git", StringComparison.Ordinal))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (name.StartsWith(".git/", StringComparison.Ordinal))
|
||||||
|
HandleGitDirFileChanged(name.Substring(5));
|
||||||
|
else
|
||||||
|
HandleWorkingCopyFileChanged(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnGitDirChanged(object o, FileSystemEventArgs e)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(e.Name))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var name = e.Name.Replace('\\', '/').TrimEnd('/');
|
||||||
|
HandleGitDirFileChanged(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnWorkingCopyChanged(object o, FileSystemEventArgs e)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(e.Name))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var name = e.Name.Replace('\\', '/').TrimEnd('/');
|
||||||
|
if (name.Equals(".git", StringComparison.Ordinal) ||
|
||||||
|
name.StartsWith(".git/", StringComparison.Ordinal) ||
|
||||||
|
name.EndsWith("/.git", StringComparison.Ordinal))
|
||||||
|
return;
|
||||||
|
|
||||||
|
HandleWorkingCopyFileChanged(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleGitDirFileChanged(string name)
|
||||||
|
{
|
||||||
|
if (name.Contains("fsmonitor--daemon/", StringComparison.Ordinal) ||
|
||||||
|
name.EndsWith(".lock", StringComparison.Ordinal) ||
|
||||||
|
name.StartsWith("lfs/", StringComparison.Ordinal))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (name.StartsWith("modules", StringComparison.Ordinal))
|
||||||
{
|
{
|
||||||
_updateSubmodules = DateTime.Now.AddSeconds(1).ToFileTime();
|
if (name.EndsWith("/HEAD", StringComparison.Ordinal) ||
|
||||||
_updateWC = DateTime.Now.AddSeconds(1).ToFileTime();
|
name.EndsWith("/ORIG_HEAD", StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
_updateSubmodules = DateTime.Now.AddSeconds(1).ToFileTime();
|
||||||
|
_updateWC = DateTime.Now.AddSeconds(1).ToFileTime();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (name.Equals("MERGE_HEAD", StringComparison.Ordinal) ||
|
||||||
|
name.Equals("AUTO_MERGE", StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
if (_repo.MayHaveSubmodules())
|
||||||
|
_updateSubmodules = DateTime.Now.AddSeconds(1).ToFileTime();
|
||||||
}
|
}
|
||||||
else if (name.StartsWith("refs/tags", StringComparison.Ordinal))
|
else if (name.StartsWith("refs/tags", StringComparison.Ordinal))
|
||||||
{
|
{
|
||||||
|
@ -168,6 +235,7 @@ namespace SourceGit.Models
|
||||||
_updateStashes = DateTime.Now.AddSeconds(.5).ToFileTime();
|
_updateStashes = DateTime.Now.AddSeconds(.5).ToFileTime();
|
||||||
}
|
}
|
||||||
else if (name.Equals("HEAD", StringComparison.Ordinal) ||
|
else if (name.Equals("HEAD", StringComparison.Ordinal) ||
|
||||||
|
name.Equals("BISECT_START", StringComparison.Ordinal) ||
|
||||||
name.StartsWith("refs/heads/", StringComparison.Ordinal) ||
|
name.StartsWith("refs/heads/", StringComparison.Ordinal) ||
|
||||||
name.StartsWith("refs/remotes/", StringComparison.Ordinal) ||
|
name.StartsWith("refs/remotes/", StringComparison.Ordinal) ||
|
||||||
(name.StartsWith("worktrees/", StringComparison.Ordinal) && name.EndsWith("/HEAD", StringComparison.Ordinal)))
|
(name.StartsWith("worktrees/", StringComparison.Ordinal) && name.EndsWith("/HEAD", StringComparison.Ordinal)))
|
||||||
|
@ -180,14 +248,16 @@ namespace SourceGit.Models
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnWorkingCopyChanged(object o, FileSystemEventArgs e)
|
private void HandleWorkingCopyFileChanged(string name)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(e.Name))
|
if (name.StartsWith(".vs/", StringComparison.Ordinal))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var name = e.Name.Replace("\\", "/");
|
if (name.Equals(".gitmodules", StringComparison.Ordinal))
|
||||||
if (name == ".git" || name.StartsWith(".git/", StringComparison.Ordinal))
|
{
|
||||||
|
_updateSubmodules = DateTime.Now.AddSeconds(1).ToFileTime();
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
lock (_lockSubmodule)
|
lock (_lockSubmodule)
|
||||||
{
|
{
|
||||||
|
@ -205,8 +275,7 @@ namespace SourceGit.Models
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly IRepository _repo = null;
|
private readonly IRepository _repo = null;
|
||||||
private FileSystemWatcher _repoWatcher = null;
|
private List<FileSystemWatcher> _watchers = [];
|
||||||
private FileSystemWatcher _wcWatcher = null;
|
|
||||||
private Timer _timer = null;
|
private Timer _timer = null;
|
||||||
private int _lockCount = 0;
|
private int _lockCount = 0;
|
||||||
private long _updateWC = 0;
|
private long _updateWC = 0;
|
||||||
|
@ -215,7 +284,7 @@ namespace SourceGit.Models
|
||||||
private long _updateStashes = 0;
|
private long _updateStashes = 0;
|
||||||
private long _updateTags = 0;
|
private long _updateTags = 0;
|
||||||
|
|
||||||
private object _lockSubmodule = new object();
|
private readonly Lock _lockSubmodule = new();
|
||||||
private List<string> _submodules = new List<string>();
|
private List<string> _submodules = new List<string>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
using System;
|
||||||
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
|
||||||
namespace SourceGit.Models
|
namespace SourceGit.Models
|
||||||
{
|
{
|
||||||
|
@ -22,12 +23,12 @@ namespace SourceGit.Models
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
if (IsDetached)
|
if (IsDetached)
|
||||||
return $"deteched HEAD at {Head.Substring(10)}";
|
return $"detached HEAD at {Head.AsSpan(10)}";
|
||||||
|
|
||||||
if (Branch.StartsWith("refs/heads/", System.StringComparison.Ordinal))
|
if (Branch.StartsWith("refs/heads/", StringComparison.Ordinal))
|
||||||
return Branch.Substring(11);
|
return Branch.Substring(11);
|
||||||
|
|
||||||
if (Branch.StartsWith("refs/remotes/", System.StringComparison.Ordinal))
|
if (Branch.StartsWith("refs/remotes/", StringComparison.Ordinal))
|
||||||
return Branch.Substring(13);
|
return Branch.Substring(13);
|
||||||
|
|
||||||
return Branch;
|
return Branch;
|
||||||
|
|
|
@ -5,6 +5,8 @@ using System.IO;
|
||||||
using System.Runtime.Versioning;
|
using System.Runtime.Versioning;
|
||||||
|
|
||||||
using Avalonia;
|
using Avalonia;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Platform;
|
||||||
|
|
||||||
namespace SourceGit.Native
|
namespace SourceGit.Native
|
||||||
{
|
{
|
||||||
|
@ -16,6 +18,21 @@ namespace SourceGit.Native
|
||||||
builder.With(new X11PlatformOptions() { EnableIme = true });
|
builder.With(new X11PlatformOptions() { EnableIme = true });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void SetupWindow(Window window)
|
||||||
|
{
|
||||||
|
if (OS.UseSystemWindowFrame)
|
||||||
|
{
|
||||||
|
window.ExtendClientAreaChromeHints = ExtendClientAreaChromeHints.Default;
|
||||||
|
window.ExtendClientAreaToDecorationsHint = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
window.ExtendClientAreaChromeHints = ExtendClientAreaChromeHints.NoChrome;
|
||||||
|
window.ExtendClientAreaToDecorationsHint = true;
|
||||||
|
window.Classes.Add("custom_window_frame");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public string FindGitExecutable()
|
public string FindGitExecutable()
|
||||||
{
|
{
|
||||||
return FindExecutable("git");
|
return FindExecutable("git");
|
||||||
|
@ -103,8 +120,8 @@ namespace SourceGit.Native
|
||||||
private string FindExecutable(string filename)
|
private string FindExecutable(string filename)
|
||||||
{
|
{
|
||||||
var pathVariable = Environment.GetEnvironmentVariable("PATH") ?? string.Empty;
|
var pathVariable = Environment.GetEnvironmentVariable("PATH") ?? string.Empty;
|
||||||
var pathes = pathVariable.Split(Path.PathSeparator, StringSplitOptions.RemoveEmptyEntries);
|
var paths = pathVariable.Split(Path.PathSeparator, StringSplitOptions.RemoveEmptyEntries);
|
||||||
foreach (var path in pathes)
|
foreach (var path in paths)
|
||||||
{
|
{
|
||||||
var test = Path.Combine(path, filename);
|
var test = Path.Combine(path, filename);
|
||||||
if (File.Exists(test))
|
if (File.Exists(test))
|
||||||
|
|
|
@ -5,6 +5,8 @@ using System.IO;
|
||||||
using System.Runtime.Versioning;
|
using System.Runtime.Versioning;
|
||||||
|
|
||||||
using Avalonia;
|
using Avalonia;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Platform;
|
||||||
|
|
||||||
namespace SourceGit.Native
|
namespace SourceGit.Native
|
||||||
{
|
{
|
||||||
|
@ -36,6 +38,12 @@ namespace SourceGit.Native
|
||||||
Environment.SetEnvironmentVariable("PATH", path);
|
Environment.SetEnvironmentVariable("PATH", path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void SetupWindow(Window window)
|
||||||
|
{
|
||||||
|
window.ExtendClientAreaChromeHints = ExtendClientAreaChromeHints.SystemChrome;
|
||||||
|
window.ExtendClientAreaToDecorationsHint = true;
|
||||||
|
}
|
||||||
|
|
||||||
public string FindGitExecutable()
|
public string FindGitExecutable()
|
||||||
{
|
{
|
||||||
var gitPathVariants = new List<string>() {
|
var gitPathVariants = new List<string>() {
|
||||||
|
@ -59,6 +67,8 @@ namespace SourceGit.Native
|
||||||
return "Warp";
|
return "Warp";
|
||||||
case "ghostty":
|
case "ghostty":
|
||||||
return "Ghostty";
|
return "Ghostty";
|
||||||
|
case "kitty":
|
||||||
|
return "kitty";
|
||||||
}
|
}
|
||||||
|
|
||||||
return string.Empty;
|
return string.Empty;
|
||||||
|
|
|
@ -6,6 +6,7 @@ using System.Text;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
using Avalonia;
|
using Avalonia;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
|
||||||
namespace SourceGit.Native
|
namespace SourceGit.Native
|
||||||
{
|
{
|
||||||
|
@ -14,6 +15,7 @@ namespace SourceGit.Native
|
||||||
public interface IBackend
|
public interface IBackend
|
||||||
{
|
{
|
||||||
void SetupApp(AppBuilder builder);
|
void SetupApp(AppBuilder builder);
|
||||||
|
void SetupWindow(Window window);
|
||||||
|
|
||||||
string FindGitExecutable();
|
string FindGitExecutable();
|
||||||
string FindTerminal(Models.ShellOrTerminal shell);
|
string FindTerminal(Models.ShellOrTerminal shell);
|
||||||
|
@ -68,6 +70,12 @@ namespace SourceGit.Native
|
||||||
set;
|
set;
|
||||||
} = [];
|
} = [];
|
||||||
|
|
||||||
|
public static bool UseSystemWindowFrame
|
||||||
|
{
|
||||||
|
get => OperatingSystem.IsLinux() && _enableSystemWindowFrame;
|
||||||
|
set => _enableSystemWindowFrame = value;
|
||||||
|
}
|
||||||
|
|
||||||
static OS()
|
static OS()
|
||||||
{
|
{
|
||||||
if (OperatingSystem.IsWindows())
|
if (OperatingSystem.IsWindows())
|
||||||
|
@ -116,11 +124,16 @@ namespace SourceGit.Native
|
||||||
Directory.CreateDirectory(DataDir);
|
Directory.CreateDirectory(DataDir);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void SetupEnternalTools()
|
public static void SetupExternalTools()
|
||||||
{
|
{
|
||||||
ExternalTools = _backend.FindExternalTools();
|
ExternalTools = _backend.FindExternalTools();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void SetupForWindow(Window window)
|
||||||
|
{
|
||||||
|
_backend.SetupWindow(window);
|
||||||
|
}
|
||||||
|
|
||||||
public static string FindGitExecutable()
|
public static string FindGitExecutable()
|
||||||
{
|
{
|
||||||
return _backend.FindGitExecutable();
|
return _backend.FindGitExecutable();
|
||||||
|
@ -225,5 +238,6 @@ namespace SourceGit.Native
|
||||||
|
|
||||||
private static IBackend _backend = null;
|
private static IBackend _backend = null;
|
||||||
private static string _gitExecutable = string.Empty;
|
private static string _gitExecutable = string.Empty;
|
||||||
|
private static bool _enableSystemWindowFrame = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
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