mirror of
https://github.com/sourcegit-scm/sourcegit
synced 2025-06-25 12:24:59 +00:00
Compare commits
479 commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
190d2eec73 | ||
![]() |
bfb9d6b6bc | ||
![]() |
9d2f8b1555 | ||
![]() |
f59b34fe25 | ||
![]() |
6e4f35c4e1 | ||
![]() |
6ecdabc212 | ||
![]() |
8b902bd5c9 | ||
![]() |
73eccdb495 | ||
![]() |
9bfc315ace | ||
![]() |
64ffbb113f | ||
![]() |
221e964df0 | ||
![]() |
20daa584e3 | ||
![]() |
c5ad4b837d | ||
![]() |
957c52aac4 | ||
![]() |
8d74586970 | ||
![]() |
dcd8effc32 | ||
![]() |
af2b644792 | ||
![]() |
88fd8f32f1 | ||
![]() |
cadcf40d74 | ||
![]() |
fcf1107304 | ||
![]() |
e81674912c | ||
![]() |
6729d4e896 | ||
![]() |
f9f44ae9cb | ||
![]() |
c67e8e3c64 | ||
![]() |
5ec8ae1296 | ||
![]() |
2d91fed05e | ||
![]() |
3ac803d88c | ||
![]() |
94d25ee6c9 | ||
![]() |
3711399c59 | ||
![]() |
004022648c | ||
![]() |
240db2ea2f | ||
![]() |
5ca1fcfd8f | ||
![]() |
bad8904edc | ||
![]() |
86828b9711 | ||
![]() |
6d682ac409 | ||
![]() |
b06a4cbb8a | ||
![]() |
d404f6dbe2 | ||
![]() |
ae46728bbc | ||
![]() |
957fbbf54f | ||
![]() |
10569022d7 | ||
![]() |
90310a704d | ||
![]() |
a8da8c09ac | ||
![]() |
df7375313e | ||
![]() |
efa6e46471 | ||
![]() |
e102e49f45 | ||
![]() |
dcdc52592c | ||
![]() |
e28b537f89 | ||
![]() |
ed66d2337b | ||
![]() |
0cbf1215e0 | ||
![]() |
74d77ab704 | ||
![]() |
b617181fc5 | ||
![]() |
9dedcacb2f | ||
![]() |
b22733b565 | ||
![]() |
28844c59cf | ||
![]() |
f88652ffdd | ||
![]() |
8dffdef48d | ||
![]() |
158d926189 | ||
![]() |
99b7208a54 | ||
![]() |
05757ebf40 | ||
![]() |
cb6d6a233f | ||
![]() |
79650d1851 | ||
![]() |
7e2f3bec8c | ||
![]() |
7de5991ecb | ||
![]() |
ffac71b15f | ||
![]() |
35eda489be | ||
![]() |
f59851f454 | ||
![]() |
a128b67bd4 | ||
![]() |
196b454ae8 | ||
![]() |
5494093261 | ||
![]() |
c3c7d32167 | ||
![]() |
7c1a894525 | ||
![]() |
bcefb773c1 | ||
![]() |
aa1c8b1cc1 | ||
![]() |
5e303d43d4 | ||
![]() |
7d0536d94b | ||
![]() |
6c04f5390a | ||
![]() |
ee4d8a6a0e | ||
![]() |
0ea4021a64 | ||
![]() |
11a46dbc93 | ||
![]() |
69792b3262 | ||
![]() |
1b1dc2f666 | ||
![]() |
4302d7adb5 | ||
![]() |
932baeec53 | ||
![]() |
637e133f47 | ||
![]() |
a1e76e9bea | ||
![]() |
a8541a780e | ||
![]() |
d55f19586f | ||
![]() |
a22c39519f | ||
![]() |
84fb39f97a | ||
![]() |
fe54d30b70 | ||
![]() |
ba4c0f0cd2 | ||
![]() |
2478d2953b | ||
![]() |
74f52fb266 | ||
![]() |
f830b68f6a | ||
![]() |
d323a2064e | ||
![]() |
203c50350e | ||
![]() |
47012e29dc | ||
![]() |
8db033be99 | ||
![]() |
a2ca071f08 | ||
![]() |
7bba40d03f | ||
![]() |
0c22409b7b | ||
![]() |
f63fe8637b | ||
![]() |
08665e45c1 | ||
![]() |
3bb20868fc | ||
![]() |
ac55bed812 | ||
![]() |
f003f67129 | ||
![]() |
f04b0c5d97 | ||
![]() |
406ace9e79 | ||
![]() |
464fe74580 | ||
![]() |
b969ac161a | ||
![]() |
88c38b4139 | ||
![]() |
54c05ac35a | ||
![]() |
75c32c1a01 | ||
![]() |
a023a9259b | ||
![]() |
eebadd67a1 | ||
![]() |
f716c5ee1e | ||
![]() |
ed496a41fb | ||
![]() |
06a77502bc | ||
![]() |
c2187edbe9 | ||
![]() |
d85f82e171 | ||
![]() |
f7c10d0b33 | ||
![]() |
25e272fa55 | ||
![]() |
98041c803e | ||
![]() |
ee2e7d0127 | ||
![]() |
6517e78ab6 | ||
![]() |
bf43dd828a | ||
![]() |
cd009bda6b | ||
![]() |
cd8ff2e9bf | ||
![]() |
0594196dee | ||
![]() |
425395da29 | ||
![]() |
6e501b1ee4 | ||
![]() |
7b05b011aa | ||
![]() |
f1052c3efc | ||
![]() |
78f9ae2fa9 | ||
![]() |
80df53cf04 | ||
![]() |
57004c4baf | ||
![]() |
fa004ce31b | ||
![]() |
6620bd193e | ||
![]() |
26307e2343 | ||
![]() |
db5bb0aec9 | ||
![]() |
dd432c63e8 | ||
![]() |
b94f26a937 | ||
![]() |
8e5d5b946e | ||
![]() |
a9734ea8e9 | ||
![]() |
e22f0f8513 | ||
![]() |
8b17f3b1f4 | ||
![]() |
7934496cff | ||
![]() |
188408fdfc | ||
![]() |
bc5deac9fe | ||
![]() |
1bd2044589 | ||
![]() |
f0c77ffeb8 | ||
![]() |
60cd210b80 | ||
![]() |
75015d550c | ||
![]() |
e40ca4bbe0 | ||
![]() |
46231a759c | ||
![]() |
fbc8edcc13 | ||
![]() |
3437f5f4a9 | ||
![]() |
40bf69bff3 | ||
![]() |
2aac6779a5 | ||
![]() |
9affca1fb2 | ||
![]() |
729e06d5c0 | ||
![]() |
826619e7c9 | ||
![]() |
056b90a5ae | ||
![]() |
0641a22230 | ||
![]() |
d3bc85418e | ||
![]() |
4887252870 | ||
![]() |
860f6f2369 | ||
![]() |
cfc80d41a1 | ||
![]() |
ac26d5bb06 | ||
![]() |
d7c3bb7150 | ||
![]() |
39d955b033 | ||
![]() |
0e35c56529 | ||
![]() |
22339ab619 | ||
![]() |
ef53dd0025 | ||
![]() |
30d4c1008a | ||
![]() |
ca33107a45 | ||
![]() |
4363b8b6aa | ||
![]() |
f3fe90b2e1 | ||
![]() |
e28b75b860 | ||
![]() |
3377886bab | ||
![]() |
4807cd5eb2 | ||
![]() |
d21b790784 | ||
![]() |
764ae31239 | ||
![]() |
38d67d7f17 | ||
![]() |
76a197aae7 | ||
![]() |
594ffc0d1a | ||
![]() |
fb1f5638ce | ||
![]() |
492da8dd57 | ||
![]() |
0ae39faad1 | ||
![]() |
c112549b69 | ||
![]() |
9fb8af51ff | ||
![]() |
1ee7d1184e | ||
![]() |
44c83ef342 | ||
![]() |
c3ac59ee1a | ||
![]() |
c73f775aa5 | ||
![]() |
afbd0d7135 | ||
![]() |
bf39673b21 | ||
![]() |
b0c0c46441 | ||
![]() |
1fef7a7baa | ||
![]() |
1872740265 | ||
![]() |
09d0122e26 | ||
![]() |
7728f4ffbf | ||
![]() |
936160ea5c | ||
![]() |
d73ae83b01 | ||
![]() |
5e05c008fc | ||
![]() |
598ba6d9f6 | ||
![]() |
3232e6f313 | ||
![]() |
0a6b1faa65 | ||
![]() |
71b90a82b6 | ||
![]() |
d304c50e7f | ||
![]() |
438aa76695 | ||
![]() |
ece51fbd32 | ||
![]() |
224f7a949a | ||
![]() |
e6fdc778b7 | ||
![]() |
53c6fc8999 | ||
![]() |
f0d1d460a9 | ||
![]() |
3386cb177b | ||
![]() |
4d5be9f280 | ||
![]() |
6fa454ace8 | ||
![]() |
75b7724d44 | ||
![]() |
550493b572 | ||
![]() |
eb183589f5 | ||
![]() |
d56c6a5030 | ||
![]() |
f9b6116a76 | ||
![]() |
12d2b7721c | ||
![]() |
119b0fe95c | ||
![]() |
1dfb629cef | ||
![]() |
0e2bb1b276 | ||
![]() |
57ee1f7dbd | ||
![]() |
aaf53ac694 | ||
![]() |
736991198f | ||
![]() |
7dd1389c25 | ||
![]() |
341ac26576 | ||
![]() |
aff003fd6d | ||
![]() |
b78f6b0ea8 | ||
![]() |
5e85f6fefe | ||
![]() |
52991351af | ||
![]() |
4b849d9d5c | ||
![]() |
6b083dcd3e | ||
![]() |
9614b995d8 | ||
![]() |
36c2e083cc | ||
![]() |
fd35e0817d | ||
![]() |
d429a6426a | ||
![]() |
4c1ba717a7 | ||
![]() |
bd553405c2 | ||
![]() |
f121975a28 | ||
![]() |
ea320d2cdf | ||
![]() |
01945f231e | ||
![]() |
506dbc218c | ||
![]() |
d3a740fb95 | ||
![]() |
d3d0e7b15c | ||
![]() |
879b84ac20 | ||
![]() |
7f86ad9f22 | ||
![]() |
0c9cb41e68 | ||
![]() |
86f27c5e58 | ||
![]() |
1f0ab2bfec | ||
![]() |
fd935259aa | ||
![]() |
f46bbd01cd | ||
![]() |
ed1351b1f7 | ||
![]() |
d299469613 | ||
![]() |
e4490d87dc | ||
![]() |
85b223a3d0 | ||
![]() |
6c62789c4c | ||
![]() |
85e08f5eea | ||
![]() |
463d161ac7 | ||
![]() |
5ec51eefb9 | ||
![]() |
bc5c4670de | ||
![]() |
d3363429df | ||
![]() |
f83b6c24ae | ||
![]() |
61bb0f7dc7 | ||
![]() |
20a239621b | ||
![]() |
9e91494a20 | ||
![]() |
d71189c705 | ||
![]() |
55232aeddd | ||
![]() |
6bf930a9e0 | ||
![]() |
5b72b15cf2 | ||
![]() |
0e61a0196b | ||
![]() |
7bb4e355bd | ||
![]() |
57d15dc6d3 | ||
![]() |
65808e5f58 | ||
![]() |
cf7b61dd71 | ||
![]() |
ac1bd7ca85 | ||
![]() |
142ee5a327 | ||
![]() |
afc8a772dd | ||
![]() |
8a45e25106 | ||
![]() |
4e41a6207a | ||
![]() |
a5c25cf9fe | ||
![]() |
11a9d7fdd8 | ||
![]() |
ef4b639f8e | ||
![]() |
c62b4a031f | ||
![]() |
af9cf6ba6a | ||
![]() |
641098ffb2 | ||
![]() |
fcad8eeadc | ||
![]() |
01625ada1a | ||
![]() |
88dc12275a | ||
![]() |
bac21c5714 | ||
![]() |
19a51f227b | ||
![]() |
b6d618a6d7 | ||
![]() |
2573553e01 | ||
![]() |
9dd0beb61f | ||
![]() |
029fd6933f | ||
![]() |
0f6c8976af | ||
![]() |
e446e97f28 | ||
![]() |
3e530de9cc | ||
![]() |
6cf1b20ea6 | ||
![]() |
321ccf9622 | ||
![]() |
ebe0e61367 | ||
![]() |
e8bf58f6c3 | ||
![]() |
15ee2dac91 | ||
![]() |
5d1601086f | ||
![]() |
3eaa24a993 | ||
![]() |
6986e1ac24 | ||
![]() |
2c8370fa92 | ||
![]() |
008708f07c | ||
![]() |
832fcd7487 | ||
![]() |
6df38ad970 | ||
![]() |
0a7b973388 | ||
![]() |
6b050fa557 | ||
![]() |
417ab3ecc2 | ||
![]() |
a413df6f89 | ||
![]() |
ddf643c081 | ||
![]() |
bbc840a5cb | ||
![]() |
c8e21673e4 | ||
![]() |
e45e37d305 | ||
![]() |
b7fa04d141 | ||
![]() |
93a5d7baea | ||
![]() |
e4e2f7b3a7 | ||
![]() |
eae6d10784 | ||
![]() |
4bc5b90e6b | ||
![]() |
df29edd8f0 | ||
![]() |
054bbf7e0c | ||
![]() |
ce00fa6c88 | ||
![]() |
a960e14368 | ||
![]() |
867edd9453 | ||
![]() |
aee4ce6387 | ||
![]() |
d92d279fbe | ||
![]() |
5e080279ce | ||
![]() |
704c6f589d | ||
![]() |
666275c747 | ||
![]() |
c0c52695a3 | ||
![]() |
c529fab869 | ||
![]() |
4b2983b330 | ||
![]() |
8c1d397480 | ||
![]() |
007acb3fa6 | ||
![]() |
3b0c57be84 | ||
![]() |
61bc42612e | ||
![]() |
fe677d40c1 | ||
![]() |
9bde797b24 | ||
![]() |
7501588c95 | ||
![]() |
80aead3a17 | ||
![]() |
847a1e727b | ||
![]() |
98dd37a9bc | ||
![]() |
95ea0a6ba6 | ||
![]() |
b9dc5a8164 | ||
![]() |
63803c9b88 | ||
![]() |
825b74c2a3 | ||
![]() |
48bb8e91de | ||
![]() |
53a55467f1 | ||
![]() |
5681bf489d | ||
![]() |
226bc434f5 | ||
![]() |
30d42b11e2 | ||
![]() |
2698427cd0 | ||
![]() |
b4f1f35e67 | ||
![]() |
92f215d039 | ||
![]() |
2e1cf76c82 | ||
![]() |
951ea8f088 | ||
![]() |
21cb87cec5 | ||
![]() |
f39048df77 | ||
![]() |
bbdeecdcc6 | ||
![]() |
d2e688908c | ||
![]() |
91acf0a32a | ||
![]() |
d44d2b9770 | ||
![]() |
f09367a614 | ||
![]() |
00e56ce9d1 | ||
![]() |
22d4f26bc3 | ||
![]() |
a94c7f55ce | ||
![]() |
1d16925e74 | ||
![]() |
8c4362a98d | ||
![]() |
9efbc7dd7a | ||
![]() |
c519381645 | ||
![]() |
6590812634 | ||
![]() |
ad6ed1512b | ||
![]() |
f73e0687a1 | ||
![]() |
ea680782fe | ||
![]() |
7e282b13fa | ||
![]() |
1386ca30e3 | ||
![]() |
2107676058 | ||
![]() |
f72f1894c3 | ||
![]() |
586ff39da1 | ||
![]() |
9bdbf47522 | ||
![]() |
17c08d42a0 | ||
![]() |
fafa2a53ae | ||
![]() |
7890f7abbf | ||
![]() |
345ad06aba | ||
![]() |
78f4809875 | ||
![]() |
87ebe3741d | ||
![]() |
f2000b4a84 | ||
![]() |
9a6c671a96 | ||
![]() |
34e0ea3bcb | ||
![]() |
7be37424e1 | ||
![]() |
a42df87b9c | ||
![]() |
df5294bcb7 | ||
![]() |
9eae1eeb81 | ||
![]() |
4c3698b171 | ||
![]() |
9f18cbca5b | ||
![]() |
6882ae069f | ||
![]() |
9c53894cb4 | ||
![]() |
06d033464d | ||
![]() |
750ca8ec61 | ||
![]() |
86113701f3 | ||
![]() |
387b68cdfe | ||
![]() |
550c108f84 | ||
![]() |
232482ca92 | ||
![]() |
b4db88a663 | ||
![]() |
41416a6bed | ||
![]() |
5fd074a9b6 | ||
![]() |
413669741d | ||
![]() |
75b4a4b294 | ||
![]() |
d254b557a9 | ||
![]() |
892f3b8032 | ||
![]() |
afe5d4b969 | ||
![]() |
4d31392085 | ||
![]() |
de31d4bad4 | ||
![]() |
5bd7dd428d | ||
![]() |
4c1a04477e | ||
![]() |
090b64d68d | ||
![]() |
231010abc6 | ||
![]() |
3358ff9aee | ||
![]() |
104a3f0bbf | ||
![]() |
a06d1183d7 | ||
![]() |
9f493abd1a | ||
![]() |
349844cf2a | ||
![]() |
021aab8408 | ||
![]() |
c1e31ac4e3 | ||
![]() |
2a43efde07 | ||
![]() |
33ae6a9989 | ||
![]() |
0e967ffc8e | ||
![]() |
c231772298 | ||
![]() |
8b39df32cc | ||
![]() |
928a0ad3c5 | ||
![]() |
9606f128e4 | ||
![]() |
67255a5529 | ||
![]() |
cac4b7edf6 | ||
![]() |
fa44fa773c | ||
![]() |
db46de0261 | ||
![]() |
e9036b5fb9 | ||
![]() |
9ba0b595d9 | ||
![]() |
cf763b47c6 | ||
![]() |
539d3f6eca | ||
![]() |
03216fc31e | ||
![]() |
3cc463d24b | ||
![]() |
33a463ce59 | ||
![]() |
90b37663ed | ||
![]() |
9ebde1943e | ||
![]() |
be3f418680 | ||
![]() |
a97f163860 | ||
![]() |
c1839199ee | ||
![]() |
7d5ffaf867 | ||
![]() |
f0d4cfc9f9 | ||
![]() |
70494485ab | ||
![]() |
c4c04b8b01 | ||
![]() |
e2da44c8fd | ||
![]() |
0acbe3e487 | ||
![]() |
05982e6dc0 | ||
![]() |
e5dc211c35 | ||
![]() |
1e0fd63543 | ||
![]() |
3b1018e0e2 | ||
![]() |
7d20f97f4e | ||
![]() |
09c0edef8e | ||
![]() |
558eb7c9ac | ||
![]() |
b7aa49403b | ||
![]() |
f14a666091 | ||
![]() |
e89dbd8f43 | ||
![]() |
81820e7034 | ||
![]() |
e7f0217a7b | ||
![]() |
0cb2ca78fe | ||
![]() |
17cf402c78 | ||
![]() |
245de9b458 | ||
![]() |
e76328ff38 |
367 changed files with 12902 additions and 5404 deletions
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
|
@ -58,7 +58,7 @@ jobs:
|
|||
if: ${{ matrix.runtime == 'linux-arm64' }}
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y llvm gcc-aarch64-linux-gnu zlib1g-dev:arm64
|
||||
sudo apt-get install -y llvm gcc-aarch64-linux-gnu
|
||||
- name: Build
|
||||
run: dotnet build -c Release
|
||||
- name: Publish
|
||||
|
|
5
.github/workflows/localization-check.yml
vendored
5
.github/workflows/localization-check.yml
vendored
|
@ -4,7 +4,6 @@ on:
|
|||
branches: [ develop ]
|
||||
paths:
|
||||
- 'src/Resources/Locales/**'
|
||||
- 'README.md'
|
||||
workflow_dispatch:
|
||||
workflow_call:
|
||||
|
||||
|
@ -32,8 +31,8 @@ jobs:
|
|||
git config --global user.name 'github-actions[bot]'
|
||||
git config --global user.email 'github-actions[bot]@users.noreply.github.com'
|
||||
if [ -n "$(git status --porcelain)" ]; then
|
||||
git add README.md TRANSLATION.md
|
||||
git commit -m 'doc: Update translation status and missing keys'
|
||||
git add TRANSLATION.md src/Resources/Locales/*.axaml
|
||||
git commit -m 'doc: Update translation status and sort locale files'
|
||||
git push
|
||||
else
|
||||
echo "No changes to commit"
|
||||
|
|
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -37,3 +37,5 @@ build/*.deb
|
|||
build/*.rpm
|
||||
build/*.AppImage
|
||||
SourceGit.app/
|
||||
build.command
|
||||
src/Properties/launchSettings.json
|
||||
|
|
10
README.md
10
README.md
|
@ -11,7 +11,7 @@
|
|||
* Supports Windows/macOS/Linux
|
||||
* Opensource/Free
|
||||
* Fast
|
||||
* Deutsch/English/Español/Français/Italiano/Português/Русский/简体中文/繁體中文/日本語/தமிழ் (Tamil)
|
||||
* Deutsch/English/Español/Français/Italiano/Português/Русский/Українська/简体中文/繁體中文/日本語/தமிழ் (Tamil)
|
||||
* Built-in light/dark themes
|
||||
* Customize theme
|
||||
* Visual commit graph
|
||||
|
@ -35,9 +35,11 @@
|
|||
* Revision Diffs
|
||||
* Branch Diff
|
||||
* Image Diff - Side-By-Side/Swipe/Blend
|
||||
* Git command logs
|
||||
* Search commits
|
||||
* GitFlow
|
||||
* Git LFS
|
||||
* Bisect
|
||||
* Issue Link
|
||||
* Workspace
|
||||
* Custom Action
|
||||
|
@ -52,9 +54,9 @@ You can find the current translation status in [TRANSLATION.md](https://github.c
|
|||
|
||||
## 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.
|
||||
|
||||
|
@ -91,7 +93,7 @@ For **macOS** users:
|
|||
brew tap ybeapps/homebrew-sourcegit
|
||||
brew install --cask --no-quarantine sourcegit
|
||||
```
|
||||
* If you want to install `SourceGit.app` from Github Release manually, you need run following command to make sure it works:
|
||||
* If you want to install `SourceGit.app` from GitHub Release manually, you need run following command to make sure it works:
|
||||
```shell
|
||||
sudo xattr -cr /Applications/SourceGit.app
|
||||
```
|
||||
|
|
419
TRANSLATION.md
419
TRANSLATION.md
|
@ -6,111 +6,220 @@ This document shows the translation status of each locale file in the repository
|
|||
|
||||
### 
|
||||
|
||||
### 
|
||||
### 
|
||||
|
||||
<details>
|
||||
<summary>Missing keys in de_DE.axaml</summary>
|
||||
|
||||
- Text.BranchUpstreamInvalid
|
||||
- Text.Configure.CustomAction.WaitForExit
|
||||
- Text.Configure.Git.PreferredMergeMode
|
||||
- Text.Configure.IssueTracker.AddSampleAzure
|
||||
- Text.ConfirmEmptyCommit.Continue
|
||||
- Text.ConfirmEmptyCommit.NoLocalChanges
|
||||
- Text.ConfirmEmptyCommit.StageAllThenCommit
|
||||
- Text.ConfirmEmptyCommit.WithLocalChanges
|
||||
- Text.CopyFullPath
|
||||
- Text.Diff.First
|
||||
- Text.Diff.Last
|
||||
- Text.Preferences.AI.Streaming
|
||||
- Text.Preferences.Appearance.EditorTabWidth
|
||||
- Text.Preferences.General.ShowTagsInGraph
|
||||
- Text.StashCM.SaveAsPatch
|
||||
- Text.WorkingCopy.ConfirmCommitWithFilter
|
||||
- Text.WorkingCopy.Conflicts.OpenExternalMergeTool
|
||||
- Text.WorkingCopy.Conflicts.OpenExternalMergeToolAllConflicts
|
||||
- Text.WorkingCopy.Conflicts.UseMine
|
||||
- Text.WorkingCopy.Conflicts.UseTheirs
|
||||
|
||||
</details>
|
||||
|
||||
### 
|
||||
### 
|
||||
|
||||
<details>
|
||||
<summary>Missing keys in es_ES.axaml</summary>
|
||||
|
||||
- Text.Configure.Git.PreferredMergeMode
|
||||
- Text.ConfirmEmptyCommit.Continue
|
||||
- Text.ConfirmEmptyCommit.NoLocalChanges
|
||||
- Text.ConfirmEmptyCommit.StageAllThenCommit
|
||||
- Text.ConfirmEmptyCommit.WithLocalChanges
|
||||
- Text.WorkingCopy.ConfirmCommitWithFilter
|
||||
- Text.WorkingCopy.Conflicts.OpenExternalMergeTool
|
||||
- Text.WorkingCopy.Conflicts.OpenExternalMergeToolAllConflicts
|
||||
- Text.WorkingCopy.Conflicts.UseMine
|
||||
- Text.WorkingCopy.Conflicts.UseTheirs
|
||||
- Text.CommitCM.PushRevision
|
||||
- Text.Merge.Edit
|
||||
- Text.Push.Revision
|
||||
- Text.Push.Revision.Title
|
||||
- Text.Stash.Mode
|
||||
- Text.StashCM.CopyMessage
|
||||
- Text.WorkingCopy.AddToGitIgnore.InFolder
|
||||
|
||||
</details>
|
||||
|
||||
### 
|
||||
### 
|
||||
|
||||
<details>
|
||||
<summary>Missing keys in fr_FR.axaml</summary>
|
||||
|
||||
- Text.Avatar.Load
|
||||
- Text.Bisect
|
||||
- Text.Bisect.Abort
|
||||
- Text.Bisect.Bad
|
||||
- Text.Bisect.Detecting
|
||||
- Text.Bisect.Good
|
||||
- Text.Bisect.Skip
|
||||
- Text.Bisect.WaitingForRange
|
||||
- Text.BranchCM.ResetToSelectedCommit
|
||||
- Text.Checkout.RecurseSubmodules
|
||||
- Text.Checkout.WithFastForward
|
||||
- Text.Checkout.WithFastForward.Upstream
|
||||
- Text.CommitCM.CopyAuthor
|
||||
- Text.CommitCM.CopyCommitter
|
||||
- Text.CommitCM.CopySubject
|
||||
- Text.CommitCM.PushRevision
|
||||
- Text.CommitDetail.Changes.Count
|
||||
- Text.CommitMessageTextBox.SubjectCount
|
||||
- Text.Configure.Git.PreferredMergeMode
|
||||
- Text.ConfirmEmptyCommit.Continue
|
||||
- Text.ConfirmEmptyCommit.NoLocalChanges
|
||||
- Text.ConfirmEmptyCommit.StageAllThenCommit
|
||||
- Text.ConfirmEmptyCommit.WithLocalChanges
|
||||
- Text.CreateBranch.OverwriteExisting
|
||||
- Text.DeinitSubmodule
|
||||
- Text.DeinitSubmodule.Force
|
||||
- Text.DeinitSubmodule.Path
|
||||
- Text.Diff.Submodule.Deleted
|
||||
- Text.GitFlow.FinishWithPush
|
||||
- Text.GitFlow.FinishWithSquash
|
||||
- Text.Hotkeys.Global.SwitchWorkspace
|
||||
- Text.Hotkeys.Global.SwitchTab
|
||||
- Text.Hotkeys.TextEditor.OpenExternalMergeTool
|
||||
- Text.Launcher.Workspaces
|
||||
- Text.Launcher.Pages
|
||||
- Text.Merge.Edit
|
||||
- Text.Preferences.Git.IgnoreCRAtEOLInDiff
|
||||
- Text.Pull.RecurseSubmodules
|
||||
- Text.Push.Revision
|
||||
- Text.Push.Revision.Title
|
||||
- Text.Repository.BranchSort
|
||||
- Text.Repository.BranchSort.ByCommitterDate
|
||||
- Text.Repository.BranchSort.ByName
|
||||
- Text.Repository.ClearStashes
|
||||
- Text.Repository.Search.ByContent
|
||||
- Text.Repository.ShowSubmodulesAsTree
|
||||
- Text.Repository.ViewLogs
|
||||
- Text.Repository.Visit
|
||||
- Text.ResetWithoutCheckout
|
||||
- Text.ResetWithoutCheckout.MoveTo
|
||||
- Text.ResetWithoutCheckout.Target
|
||||
- Text.Stash.Mode
|
||||
- Text.StashCM.CopyMessage
|
||||
- Text.Submodule.Deinit
|
||||
- Text.Submodule.Status
|
||||
- Text.Submodule.Status.Modified
|
||||
- Text.Submodule.Status.NotInited
|
||||
- Text.Submodule.Status.RevisionChanged
|
||||
- Text.Submodule.Status.Unmerged
|
||||
- Text.Submodule.URL
|
||||
- Text.ViewLogs
|
||||
- Text.ViewLogs.Clear
|
||||
- Text.ViewLogs.CopyLog
|
||||
- Text.ViewLogs.Delete
|
||||
- Text.WorkingCopy.AddToGitIgnore.InFolder
|
||||
- Text.WorkingCopy.ConfirmCommitWithFilter
|
||||
- Text.WorkingCopy.Conflicts.OpenExternalMergeTool
|
||||
- Text.WorkingCopy.Conflicts.OpenExternalMergeToolAllConflicts
|
||||
- Text.WorkingCopy.Conflicts.UseMine
|
||||
- Text.WorkingCopy.Conflicts.UseTheirs
|
||||
- Text.WorkingCopy.ResetAuthor
|
||||
|
||||
</details>
|
||||
|
||||
### 
|
||||
### 
|
||||
|
||||
<details>
|
||||
<summary>Missing keys in it_IT.axaml</summary>
|
||||
|
||||
- Text.Configure.Git.PreferredMergeMode
|
||||
- Text.ConfirmEmptyCommit.Continue
|
||||
- Text.ConfirmEmptyCommit.NoLocalChanges
|
||||
- Text.ConfirmEmptyCommit.StageAllThenCommit
|
||||
- Text.ConfirmEmptyCommit.WithLocalChanges
|
||||
- Text.CopyFullPath
|
||||
- Text.Preferences.General.ShowTagsInGraph
|
||||
- Text.WorkingCopy.ConfirmCommitWithFilter
|
||||
- Text.WorkingCopy.Conflicts.OpenExternalMergeTool
|
||||
- Text.WorkingCopy.Conflicts.OpenExternalMergeToolAllConflicts
|
||||
- Text.WorkingCopy.Conflicts.UseMine
|
||||
- Text.WorkingCopy.Conflicts.UseTheirs
|
||||
- Text.Avatar.Load
|
||||
- Text.BranchCM.ResetToSelectedCommit
|
||||
- Text.Checkout.WithFastForward
|
||||
- Text.Checkout.WithFastForward.Upstream
|
||||
- Text.CommitCM.PushRevision
|
||||
- Text.CommitDetail.Changes.Count
|
||||
- Text.CreateBranch.OverwriteExisting
|
||||
- Text.DeinitSubmodule
|
||||
- Text.DeinitSubmodule.Force
|
||||
- Text.DeinitSubmodule.Path
|
||||
- Text.Diff.Submodule.Deleted
|
||||
- Text.Hotkeys.Global.SwitchWorkspace
|
||||
- Text.Hotkeys.Global.SwitchTab
|
||||
- Text.Launcher.Workspaces
|
||||
- Text.Launcher.Pages
|
||||
- Text.Merge.Edit
|
||||
- Text.Pull.RecurseSubmodules
|
||||
- Text.Push.Revision
|
||||
- Text.Push.Revision.Title
|
||||
- Text.Repository.ClearStashes
|
||||
- Text.ResetWithoutCheckout
|
||||
- Text.ResetWithoutCheckout.MoveTo
|
||||
- Text.ResetWithoutCheckout.Target
|
||||
- Text.Stash.Mode
|
||||
- Text.StashCM.CopyMessage
|
||||
- Text.Submodule.Deinit
|
||||
- Text.WorkingCopy.AddToGitIgnore.InFolder
|
||||
- Text.WorkingCopy.ResetAuthor
|
||||
|
||||
</details>
|
||||
|
||||
### 
|
||||
### 
|
||||
|
||||
<details>
|
||||
<summary>Missing keys in ja_JP.axaml</summary>
|
||||
|
||||
- Text.Avatar.Load
|
||||
- Text.Bisect
|
||||
- Text.Bisect.Abort
|
||||
- Text.Bisect.Bad
|
||||
- Text.Bisect.Detecting
|
||||
- Text.Bisect.Good
|
||||
- Text.Bisect.Skip
|
||||
- Text.Bisect.WaitingForRange
|
||||
- Text.BranchCM.CompareWithCurrent
|
||||
- Text.BranchCM.ResetToSelectedCommit
|
||||
- Text.Checkout.RecurseSubmodules
|
||||
- Text.Checkout.WithFastForward
|
||||
- Text.Checkout.WithFastForward.Upstream
|
||||
- Text.CommitCM.CopyAuthor
|
||||
- Text.CommitCM.CopyCommitter
|
||||
- Text.CommitCM.CopySubject
|
||||
- Text.CommitCM.PushRevision
|
||||
- Text.CommitDetail.Changes.Count
|
||||
- Text.CommitMessageTextBox.SubjectCount
|
||||
- Text.Configure.Git.PreferredMergeMode
|
||||
- Text.ConfirmEmptyCommit.Continue
|
||||
- Text.ConfirmEmptyCommit.NoLocalChanges
|
||||
- Text.ConfirmEmptyCommit.StageAllThenCommit
|
||||
- Text.ConfirmEmptyCommit.WithLocalChanges
|
||||
- Text.CreateBranch.OverwriteExisting
|
||||
- Text.DeinitSubmodule
|
||||
- Text.DeinitSubmodule.Force
|
||||
- Text.DeinitSubmodule.Path
|
||||
- Text.Diff.Submodule.Deleted
|
||||
- Text.GitFlow.FinishWithPush
|
||||
- Text.GitFlow.FinishWithSquash
|
||||
- Text.Hotkeys.Global.SwitchWorkspace
|
||||
- Text.Hotkeys.Global.SwitchTab
|
||||
- Text.Hotkeys.TextEditor.OpenExternalMergeTool
|
||||
- Text.Launcher.Workspaces
|
||||
- Text.Launcher.Pages
|
||||
- Text.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.Tags.OrderByNameDes
|
||||
- Text.Repository.Search.ByContent
|
||||
- Text.Repository.ShowSubmodulesAsTree
|
||||
- Text.Repository.ViewLogs
|
||||
- Text.Repository.Visit
|
||||
- Text.ResetWithoutCheckout
|
||||
- Text.ResetWithoutCheckout.MoveTo
|
||||
- Text.ResetWithoutCheckout.Target
|
||||
- Text.Stash.Mode
|
||||
- Text.StashCM.CopyMessage
|
||||
- Text.Submodule.Deinit
|
||||
- Text.Submodule.Status
|
||||
- Text.Submodule.Status.Modified
|
||||
- Text.Submodule.Status.NotInited
|
||||
- Text.Submodule.Status.RevisionChanged
|
||||
- Text.Submodule.Status.Unmerged
|
||||
- Text.Submodule.URL
|
||||
- Text.ViewLogs
|
||||
- Text.ViewLogs.Clear
|
||||
- Text.ViewLogs.CopyLog
|
||||
- Text.ViewLogs.Delete
|
||||
- Text.WorkingCopy.AddToGitIgnore.InFolder
|
||||
- Text.WorkingCopy.ConfirmCommitWithFilter
|
||||
- Text.WorkingCopy.Conflicts.OpenExternalMergeTool
|
||||
- Text.WorkingCopy.Conflicts.OpenExternalMergeToolAllConflicts
|
||||
- Text.WorkingCopy.Conflicts.UseMine
|
||||
- Text.WorkingCopy.Conflicts.UseTheirs
|
||||
- Text.WorkingCopy.ResetAuthor
|
||||
|
||||
</details>
|
||||
|
||||
### 
|
||||
### 
|
||||
|
||||
<details>
|
||||
<summary>Missing keys in pt_BR.axaml</summary>
|
||||
|
@ -121,14 +230,32 @@ This document shows the translation status of each locale file in the repository
|
|||
- Text.ApplyStash.DropAfterApply
|
||||
- Text.ApplyStash.RestoreIndex
|
||||
- Text.ApplyStash.Stash
|
||||
- Text.Avatar.Load
|
||||
- Text.Bisect
|
||||
- Text.Bisect.Abort
|
||||
- Text.Bisect.Bad
|
||||
- Text.Bisect.Detecting
|
||||
- Text.Bisect.Good
|
||||
- Text.Bisect.Skip
|
||||
- Text.Bisect.WaitingForRange
|
||||
- Text.BranchCM.CustomAction
|
||||
- Text.BranchCM.MergeMultiBranches
|
||||
- Text.BranchCM.ResetToSelectedCommit
|
||||
- Text.BranchUpstreamInvalid
|
||||
- Text.Checkout.RecurseSubmodules
|
||||
- Text.Checkout.WithFastForward
|
||||
- Text.Checkout.WithFastForward.Upstream
|
||||
- Text.Clone.RecurseSubmodules
|
||||
- Text.CommitCM.CopyAuthor
|
||||
- Text.CommitCM.CopyCommitter
|
||||
- Text.CommitCM.CopySubject
|
||||
- Text.CommitCM.Merge
|
||||
- Text.CommitCM.MergeMultiple
|
||||
- Text.CommitCM.PushRevision
|
||||
- Text.CommitDetail.Changes.Count
|
||||
- Text.CommitDetail.Files.Search
|
||||
- Text.CommitDetail.Info.Children
|
||||
- Text.CommitMessageTextBox.SubjectCount
|
||||
- Text.Configure.CustomAction.Scope.Branch
|
||||
- Text.Configure.CustomAction.WaitForExit
|
||||
- Text.Configure.Git.PreferredMergeMode
|
||||
|
@ -140,19 +267,32 @@ This document shows the translation status of each locale file in the repository
|
|||
- Text.ConfirmEmptyCommit.WithLocalChanges
|
||||
- Text.CopyFullPath
|
||||
- Text.CreateBranch.Name.WarnSpace
|
||||
- Text.CreateBranch.OverwriteExisting
|
||||
- Text.DeinitSubmodule
|
||||
- Text.DeinitSubmodule.Force
|
||||
- Text.DeinitSubmodule.Path
|
||||
- Text.DeleteRepositoryNode.Path
|
||||
- Text.DeleteRepositoryNode.TipForGroup
|
||||
- Text.DeleteRepositoryNode.TipForRepository
|
||||
- Text.Diff.First
|
||||
- Text.Diff.Last
|
||||
- Text.Diff.Submodule.Deleted
|
||||
- Text.Diff.UseBlockNavigation
|
||||
- Text.Fetch.Force
|
||||
- Text.FileCM.ResolveUsing
|
||||
- Text.GitFlow.FinishWithPush
|
||||
- Text.GitFlow.FinishWithSquash
|
||||
- Text.Hotkeys.Global.Clone
|
||||
- Text.Hotkeys.Global.SwitchWorkspace
|
||||
- Text.Hotkeys.Global.SwitchTab
|
||||
- Text.Hotkeys.TextEditor.OpenExternalMergeTool
|
||||
- Text.InProgress.CherryPick.Head
|
||||
- Text.InProgress.Merge.Operating
|
||||
- Text.InProgress.Rebase.StoppedAt
|
||||
- Text.InProgress.Revert.Head
|
||||
- Text.Launcher.Workspaces
|
||||
- Text.Launcher.Pages
|
||||
- Text.Merge.Edit
|
||||
- Text.Merge.Source
|
||||
- Text.MergeMultiple
|
||||
- Text.MergeMultiple.CommitChanges
|
||||
|
@ -163,7 +303,15 @@ This document shows the translation status of each locale file in the repository
|
|||
- Text.Preferences.General.DateFormat
|
||||
- Text.Preferences.General.ShowChildren
|
||||
- Text.Preferences.General.ShowTagsInGraph
|
||||
- Text.Preferences.Git.IgnoreCRAtEOLInDiff
|
||||
- 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.HistoriesLayout
|
||||
- Text.Repository.HistoriesLayout.Horizontal
|
||||
|
@ -171,47 +319,198 @@ This document shows the translation status of each locale file in the repository
|
|||
- Text.Repository.HistoriesOrder
|
||||
- Text.Repository.Notifications.Clear
|
||||
- Text.Repository.OnlyHighlightCurrentBranchInHistories
|
||||
- Text.Repository.Search.ByContent
|
||||
- Text.Repository.ShowSubmodulesAsTree
|
||||
- Text.Repository.Skip
|
||||
- Text.Repository.Tags.OrderByCreatorDate
|
||||
- Text.Repository.Tags.OrderByNameAsc
|
||||
- Text.Repository.Tags.OrderByNameDes
|
||||
- Text.Repository.Tags.OrderByName
|
||||
- Text.Repository.Tags.Sort
|
||||
- Text.Repository.UseRelativeTimeInHistories
|
||||
- Text.Repository.ViewLogs
|
||||
- Text.Repository.Visit
|
||||
- Text.ResetWithoutCheckout
|
||||
- Text.ResetWithoutCheckout.MoveTo
|
||||
- Text.ResetWithoutCheckout.Target
|
||||
- Text.SetUpstream
|
||||
- Text.SetUpstream.Local
|
||||
- Text.SetUpstream.Unset
|
||||
- Text.SetUpstream.Upstream
|
||||
- Text.SHALinkCM.NavigateTo
|
||||
- Text.Stash.AutoRestore
|
||||
- Text.Stash.AutoRestore.Tip
|
||||
- Text.Stash.Mode
|
||||
- Text.StashCM.CopyMessage
|
||||
- Text.StashCM.SaveAsPatch
|
||||
- Text.Submodule.Deinit
|
||||
- Text.Submodule.Status
|
||||
- Text.Submodule.Status.Modified
|
||||
- Text.Submodule.Status.NotInited
|
||||
- Text.Submodule.Status.RevisionChanged
|
||||
- Text.Submodule.Status.Unmerged
|
||||
- Text.Submodule.URL
|
||||
- Text.ViewLogs
|
||||
- Text.ViewLogs.Clear
|
||||
- Text.ViewLogs.CopyLog
|
||||
- Text.ViewLogs.Delete
|
||||
- Text.WorkingCopy.AddToGitIgnore.InFolder
|
||||
- Text.WorkingCopy.CommitToEdit
|
||||
- Text.WorkingCopy.ConfirmCommitWithFilter
|
||||
- Text.WorkingCopy.Conflicts.OpenExternalMergeTool
|
||||
- Text.WorkingCopy.Conflicts.OpenExternalMergeToolAllConflicts
|
||||
- Text.WorkingCopy.Conflicts.UseMine
|
||||
- Text.WorkingCopy.Conflicts.UseTheirs
|
||||
- Text.WorkingCopy.ResetAuthor
|
||||
- Text.WorkingCopy.SignOff
|
||||
|
||||
</details>
|
||||
|
||||
### 
|
||||
|
||||
### 
|
||||
### 
|
||||
|
||||
<details>
|
||||
<summary>Missing keys in ta_IN.axaml</summary>
|
||||
|
||||
- Text.Avatar.Load
|
||||
- Text.Bisect
|
||||
- Text.Bisect.Abort
|
||||
- Text.Bisect.Bad
|
||||
- Text.Bisect.Detecting
|
||||
- Text.Bisect.Good
|
||||
- Text.Bisect.Skip
|
||||
- Text.Bisect.WaitingForRange
|
||||
- Text.BranchCM.CompareWithCurrent
|
||||
- Text.BranchCM.ResetToSelectedCommit
|
||||
- Text.Checkout.RecurseSubmodules
|
||||
- Text.Checkout.WithFastForward
|
||||
- Text.Checkout.WithFastForward.Upstream
|
||||
- Text.CommitCM.CopyAuthor
|
||||
- Text.CommitCM.CopyCommitter
|
||||
- Text.CommitCM.CopySubject
|
||||
- Text.CommitCM.PushRevision
|
||||
- Text.CommitDetail.Changes.Count
|
||||
- Text.CommitMessageTextBox.SubjectCount
|
||||
- Text.Configure.Git.PreferredMergeMode
|
||||
- Text.ConfirmEmptyCommit.Continue
|
||||
- Text.ConfirmEmptyCommit.NoLocalChanges
|
||||
- Text.ConfirmEmptyCommit.StageAllThenCommit
|
||||
- Text.ConfirmEmptyCommit.WithLocalChanges
|
||||
- Text.CreateBranch.OverwriteExisting
|
||||
- Text.DeinitSubmodule
|
||||
- Text.DeinitSubmodule.Force
|
||||
- Text.DeinitSubmodule.Path
|
||||
- Text.Diff.Submodule.Deleted
|
||||
- Text.GitFlow.FinishWithPush
|
||||
- Text.GitFlow.FinishWithSquash
|
||||
- Text.Hotkeys.Global.SwitchWorkspace
|
||||
- Text.Hotkeys.Global.SwitchTab
|
||||
- Text.Hotkeys.TextEditor.OpenExternalMergeTool
|
||||
- Text.Launcher.Workspaces
|
||||
- Text.Launcher.Pages
|
||||
- Text.Merge.Edit
|
||||
- Text.Preferences.Git.IgnoreCRAtEOLInDiff
|
||||
- Text.Pull.RecurseSubmodules
|
||||
- Text.Push.Revision
|
||||
- Text.Push.Revision.Title
|
||||
- Text.Repository.BranchSort
|
||||
- Text.Repository.BranchSort.ByCommitterDate
|
||||
- Text.Repository.BranchSort.ByName
|
||||
- Text.Repository.ClearStashes
|
||||
- Text.Repository.Search.ByContent
|
||||
- Text.Repository.ShowSubmodulesAsTree
|
||||
- Text.Repository.ViewLogs
|
||||
- Text.Repository.Visit
|
||||
- Text.ResetWithoutCheckout
|
||||
- Text.ResetWithoutCheckout.MoveTo
|
||||
- Text.ResetWithoutCheckout.Target
|
||||
- Text.Stash.Mode
|
||||
- Text.StashCM.CopyMessage
|
||||
- Text.Submodule.Deinit
|
||||
- Text.Submodule.Status
|
||||
- Text.Submodule.Status.Modified
|
||||
- Text.Submodule.Status.NotInited
|
||||
- Text.Submodule.Status.RevisionChanged
|
||||
- Text.Submodule.Status.Unmerged
|
||||
- Text.Submodule.URL
|
||||
- Text.UpdateSubmodules.Target
|
||||
- Text.ViewLogs
|
||||
- Text.ViewLogs.Clear
|
||||
- Text.ViewLogs.CopyLog
|
||||
- Text.ViewLogs.Delete
|
||||
- Text.WorkingCopy.AddToGitIgnore.InFolder
|
||||
- Text.WorkingCopy.Conflicts.OpenExternalMergeTool
|
||||
- Text.WorkingCopy.Conflicts.OpenExternalMergeToolAllConflicts
|
||||
- Text.WorkingCopy.Conflicts.UseMine
|
||||
- Text.WorkingCopy.Conflicts.UseTheirs
|
||||
- Text.WorkingCopy.ResetAuthor
|
||||
|
||||
</details>
|
||||
|
||||
### 
|
||||
|
||||
<details>
|
||||
<summary>Missing keys in uk_UA.axaml</summary>
|
||||
|
||||
- Text.Avatar.Load
|
||||
- Text.Bisect
|
||||
- Text.Bisect.Abort
|
||||
- Text.Bisect.Bad
|
||||
- Text.Bisect.Detecting
|
||||
- Text.Bisect.Good
|
||||
- Text.Bisect.Skip
|
||||
- Text.Bisect.WaitingForRange
|
||||
- Text.BranchCM.ResetToSelectedCommit
|
||||
- Text.Checkout.RecurseSubmodules
|
||||
- Text.Checkout.WithFastForward
|
||||
- Text.Checkout.WithFastForward.Upstream
|
||||
- Text.CommitCM.CopyAuthor
|
||||
- Text.CommitCM.CopyCommitter
|
||||
- Text.CommitCM.CopySubject
|
||||
- Text.CommitCM.PushRevision
|
||||
- Text.CommitDetail.Changes.Count
|
||||
- Text.CommitMessageTextBox.SubjectCount
|
||||
- Text.ConfigureWorkspace.Name
|
||||
- Text.CreateBranch.OverwriteExisting
|
||||
- Text.DeinitSubmodule
|
||||
- Text.DeinitSubmodule.Force
|
||||
- Text.DeinitSubmodule.Path
|
||||
- Text.Diff.Submodule.Deleted
|
||||
- Text.GitFlow.FinishWithPush
|
||||
- Text.GitFlow.FinishWithSquash
|
||||
- Text.Hotkeys.Global.SwitchWorkspace
|
||||
- Text.Hotkeys.Global.SwitchTab
|
||||
- Text.Hotkeys.TextEditor.OpenExternalMergeTool
|
||||
- Text.Launcher.Workspaces
|
||||
- Text.Launcher.Pages
|
||||
- Text.Merge.Edit
|
||||
- Text.Preferences.Git.IgnoreCRAtEOLInDiff
|
||||
- Text.Pull.RecurseSubmodules
|
||||
- Text.Push.Revision
|
||||
- Text.Push.Revision.Title
|
||||
- Text.Repository.BranchSort
|
||||
- Text.Repository.BranchSort.ByCommitterDate
|
||||
- Text.Repository.BranchSort.ByName
|
||||
- Text.Repository.ClearStashes
|
||||
- Text.Repository.Search.ByContent
|
||||
- Text.Repository.ShowSubmodulesAsTree
|
||||
- Text.Repository.ViewLogs
|
||||
- Text.Repository.Visit
|
||||
- Text.ResetWithoutCheckout
|
||||
- Text.ResetWithoutCheckout.MoveTo
|
||||
- Text.ResetWithoutCheckout.Target
|
||||
- Text.Stash.Mode
|
||||
- Text.StashCM.CopyMessage
|
||||
- Text.Submodule.Deinit
|
||||
- Text.Submodule.Status
|
||||
- Text.Submodule.Status.Modified
|
||||
- Text.Submodule.Status.NotInited
|
||||
- Text.Submodule.Status.RevisionChanged
|
||||
- Text.Submodule.Status.Unmerged
|
||||
- Text.Submodule.URL
|
||||
- Text.ViewLogs
|
||||
- Text.ViewLogs.Clear
|
||||
- Text.ViewLogs.CopyLog
|
||||
- Text.ViewLogs.Delete
|
||||
- Text.WorkingCopy.AddToGitIgnore.InFolder
|
||||
- Text.WorkingCopy.ResetAuthor
|
||||
|
||||
</details>
|
||||
|
||||
|
|
2
VERSION
2
VERSION
|
@ -1 +1 @@
|
|||
2025.13
|
||||
2025.23
|
|
@ -12,4 +12,4 @@
|
|||
dotnet publish -c Release -r $RUNTIME_IDENTIFIER -o $DESTINATION_FOLDER src/SourceGit.csproj
|
||||
```
|
||||
> [!NOTE]
|
||||
> Please replace the `$RUNTIME_IDENTIFIER` with one of `win-x64`,`win-arm64`,`linux-x64`,`linux-arm64`,`osx-x64`,`osx-arm64`, and replece the `$DESTINATION_FOLDER` with the real path that will store the output executable files.
|
||||
> Please replace the `$RUNTIME_IDENTIFIER` with one of `win-x64`,`win-arm64`,`linux-x64`,`linux-arm64`,`osx-x64`,`osx-arm64`, and replace the `$DESTINATION_FOLDER` with the real path that will store the output executable files.
|
||||
|
|
|
@ -14,6 +14,22 @@ async function parseXml(filePath) {
|
|||
return parser.parseStringPromise(data);
|
||||
}
|
||||
|
||||
async function filterAndSortTranslations(localeData, enUSKeys, enUSData) {
|
||||
const strings = localeData.ResourceDictionary['x:String'];
|
||||
// Remove keys that don't exist in English file
|
||||
const filtered = strings.filter(item => enUSKeys.has(item.$['x:Key']));
|
||||
|
||||
// Sort based on the key order in English file
|
||||
const enUSKeysArray = enUSData.ResourceDictionary['x:String'].map(item => item.$['x:Key']);
|
||||
filtered.sort((a, b) => {
|
||||
const aIndex = enUSKeysArray.indexOf(a.$['x:Key']);
|
||||
const bIndex = enUSKeysArray.indexOf(b.$['x:Key']);
|
||||
return aIndex - bIndex;
|
||||
});
|
||||
|
||||
return filtered;
|
||||
}
|
||||
|
||||
async function calculateTranslationRate() {
|
||||
const enUSData = await parseXml(enUSFile);
|
||||
const enUSKeys = new Set(enUSData.ResourceDictionary['x:String'].map(item => item.$['x:Key']));
|
||||
|
@ -33,6 +49,21 @@ async function calculateTranslationRate() {
|
|||
const localeKeys = new Set(localeData.ResourceDictionary['x:String'].map(item => item.$['x:Key']));
|
||||
const missingKeys = [...enUSKeys].filter(key => !localeKeys.has(key));
|
||||
|
||||
// Sort and clean up extra translations
|
||||
const sortedAndCleaned = await filterAndSortTranslations(localeData, enUSKeys, enUSData);
|
||||
localeData.ResourceDictionary['x:String'] = sortedAndCleaned;
|
||||
|
||||
// Save the updated file
|
||||
const builder = new xml2js.Builder({
|
||||
headless: true,
|
||||
renderOpts: { pretty: true, indent: ' ' }
|
||||
});
|
||||
let xmlStr = builder.buildObject(localeData);
|
||||
|
||||
// Add an empty line before the first x:String
|
||||
xmlStr = xmlStr.replace(' <x:String', '\n <x:String');
|
||||
await fs.writeFile(filePath, xmlStr + '\n', 'utf8');
|
||||
|
||||
if (missingKeys.length > 0) {
|
||||
const progress = ((enUSKeys.size - missingKeys.length) / enUSKeys.size) * 100;
|
||||
const badgeColor = progress >= 75 ? 'yellow' : 'red';
|
||||
|
|
|
@ -37,10 +37,10 @@ namespace SourceGit
|
|||
}
|
||||
}
|
||||
|
||||
public static readonly Command OpenPreferencesCommand = new Command(_ => OpenDialog(new Views.Preferences()));
|
||||
public static readonly Command OpenHotkeysCommand = new Command(_ => OpenDialog(new Views.Hotkeys()));
|
||||
public static readonly Command OpenPreferencesCommand = new Command(_ => ShowWindow(new Views.Preferences(), false));
|
||||
public static readonly Command OpenHotkeysCommand = new Command(_ => ShowWindow(new Views.Hotkeys(), false));
|
||||
public static readonly Command OpenAppDataDirCommand = new Command(_ => Native.OS.OpenInFileManager(Native.OS.DataDir));
|
||||
public static readonly Command OpenAboutCommand = new Command(_ => OpenDialog(new Views.About()));
|
||||
public static readonly Command OpenAboutCommand = new Command(_ => ShowWindow(new Views.About(), false));
|
||||
public static readonly Command CheckForUpdateCommand = new Command(_ => (Current as App)?.Check4Update(true));
|
||||
public static readonly Command QuitCommand = new Command(_ => Quit(0));
|
||||
public static readonly Command CopyTextBlockCommand = new Command(p =>
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
<ResourceInclude x:Key="fr_FR" Source="/Resources/Locales/fr_FR.axaml"/>
|
||||
<ResourceInclude x:Key="it_IT" Source="/Resources/Locales/it_IT.axaml"/>
|
||||
<ResourceInclude x:Key="pt_BR" Source="/Resources/Locales/pt_BR.axaml"/>
|
||||
<ResourceInclude x:Key="uk_UA" Source="/Resources/Locales/uk_UA.axaml"/>
|
||||
<ResourceInclude x:Key="ru_RU" Source="/Resources/Locales/ru_RU.axaml"/>
|
||||
<ResourceInclude x:Key="zh_CN" Source="/Resources/Locales/zh_CN.axaml"/>
|
||||
<ResourceInclude x:Key="zh_TW" Source="/Resources/Locales/zh_TW.axaml"/>
|
||||
|
@ -34,7 +35,7 @@
|
|||
<NativeMenu.Menu>
|
||||
<NativeMenu>
|
||||
<NativeMenuItem Header="{DynamicResource Text.About.Menu}" Command="{x:Static s:App.OpenAboutCommand}"/>
|
||||
<NativeMenuItem Header="{DynamicResource Text.Hotkeys}" Command="{x:Static s:App.OpenHotkeysCommand}"/>
|
||||
<NativeMenuItem Header="{DynamicResource Text.Hotkeys}" Command="{x:Static s:App.OpenHotkeysCommand}" Gesture="F1"/>
|
||||
<NativeMenuItem Header="{DynamicResource Text.SelfUpdate}" Command="{x:Static s:App.CheckForUpdateCommand}" IsVisible="{x:Static s:App.IsCheckForUpdateCommandVisible}"/>
|
||||
<NativeMenuItemSeparator/>
|
||||
<NativeMenuItem Header="{DynamicResource Text.Preferences}" Command="{x:Static s:App.OpenPreferencesCommand}" Gesture="⌘+,"/>
|
||||
|
|
172
src/App.axaml.cs
172
src/App.axaml.cs
|
@ -6,6 +6,7 @@ using System.Net.Http;
|
|||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
|
@ -77,7 +78,7 @@ namespace SourceGit
|
|||
return builder;
|
||||
}
|
||||
|
||||
private static void LogException(Exception ex)
|
||||
public static void LogException(Exception ex)
|
||||
{
|
||||
if (ex == null)
|
||||
return;
|
||||
|
@ -104,10 +105,44 @@ namespace SourceGit
|
|||
#endregion
|
||||
|
||||
#region Utility Functions
|
||||
public static void OpenDialog(Window window)
|
||||
public static void ShowWindow(object data, bool showAsDialog)
|
||||
{
|
||||
var impl = (Views.ChromelessWindow target, bool isDialog) =>
|
||||
{
|
||||
if (Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime { MainWindow: { } owner })
|
||||
window.ShowDialog(owner);
|
||||
{
|
||||
if (isDialog)
|
||||
target.ShowDialog(owner);
|
||||
else
|
||||
target.Show(owner);
|
||||
}
|
||||
else
|
||||
{
|
||||
target.Show();
|
||||
}
|
||||
};
|
||||
|
||||
if (data is Views.ChromelessWindow window)
|
||||
{
|
||||
impl(window, showAsDialog);
|
||||
return;
|
||||
}
|
||||
|
||||
var dataTypeName = data.GetType().FullName;
|
||||
if (string.IsNullOrEmpty(dataTypeName) || !dataTypeName.Contains(".ViewModels.", StringComparison.Ordinal))
|
||||
return;
|
||||
|
||||
var viewTypeName = dataTypeName.Replace(".ViewModels.", ".Views.");
|
||||
var viewType = Type.GetType(viewTypeName);
|
||||
if (viewType == null || !viewType.IsSubclassOf(typeof(Views.ChromelessWindow)))
|
||||
return;
|
||||
|
||||
window = Activator.CreateInstance(viewType) as Views.ChromelessWindow;
|
||||
if (window != null)
|
||||
{
|
||||
window.DataContext = data;
|
||||
impl(window, showAsDialog);
|
||||
}
|
||||
}
|
||||
|
||||
public static void RaiseException(string context, string message)
|
||||
|
@ -266,7 +301,7 @@ namespace SourceGit
|
|||
return await clipboard.GetTextAsync();
|
||||
}
|
||||
}
|
||||
return default;
|
||||
return null;
|
||||
}
|
||||
|
||||
public static string Text(string key, params object[] args)
|
||||
|
@ -288,8 +323,7 @@ namespace SourceGit
|
|||
icon.Height = 12;
|
||||
icon.Stretch = Stretch.Uniform;
|
||||
|
||||
var geo = Current?.FindResource(key) as StreamGeometry;
|
||||
if (geo != null)
|
||||
if (Current?.FindResource(key) is StreamGeometry geo)
|
||||
icon.Data = geo;
|
||||
|
||||
return icon;
|
||||
|
@ -303,7 +337,7 @@ namespace SourceGit
|
|||
return null;
|
||||
}
|
||||
|
||||
public static ViewModels.Launcher GetLauncer()
|
||||
public static ViewModels.Launcher GetLauncher()
|
||||
{
|
||||
return Current is App app ? app._launcher : null;
|
||||
}
|
||||
|
@ -341,15 +375,44 @@ namespace SourceGit
|
|||
{
|
||||
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))
|
||||
return;
|
||||
|
||||
if (TryLaunchAsAskpass(desktop))
|
||||
return;
|
||||
|
||||
_ipcChannel = new Models.IpcChannel();
|
||||
if (!_ipcChannel.IsFirstInstance)
|
||||
{
|
||||
var arg = desktop.Args is { Length: > 0 } ? desktop.Args[0].Trim() : string.Empty;
|
||||
if (!string.IsNullOrEmpty(arg))
|
||||
{
|
||||
if (arg.StartsWith('"') && arg.EndsWith('"'))
|
||||
arg = arg.Substring(1, arg.Length - 2).Trim();
|
||||
|
||||
if (arg.Length > 0 && !Path.IsPathFullyQualified(arg))
|
||||
arg = Path.GetFullPath(arg);
|
||||
}
|
||||
|
||||
_ipcChannel.SendToFirstInstance(arg);
|
||||
Environment.Exit(0);
|
||||
}
|
||||
else
|
||||
{
|
||||
_ipcChannel.MessageReceived += TryOpenRepository;
|
||||
desktop.Exit += (_, _) => _ipcChannel.Dispose();
|
||||
TryLaunchAsNormal(desktop);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
private static bool TryLaunchAsRebaseTodoEditor(string[] args, out int exitCode)
|
||||
|
@ -420,21 +483,37 @@ namespace SourceGit
|
|||
return true;
|
||||
|
||||
var gitDir = Path.GetDirectoryName(file)!;
|
||||
var jobsFile = Path.Combine(gitDir, "sourcegit_rebase_jobs.json");
|
||||
if (!File.Exists(jobsFile))
|
||||
return true;
|
||||
|
||||
var collection = JsonSerializer.Deserialize(File.ReadAllText(jobsFile), JsonCodeGen.Default.InteractiveRebaseJobCollection);
|
||||
var origHeadFile = Path.Combine(gitDir, "rebase-merge", "orig-head");
|
||||
var ontoFile = Path.Combine(gitDir, "rebase-merge", "onto");
|
||||
var doneFile = Path.Combine(gitDir, "rebase-merge", "done");
|
||||
if (!File.Exists(doneFile))
|
||||
var jobsFile = Path.Combine(gitDir, "sourcegit_rebase_jobs.json");
|
||||
if (!File.Exists(ontoFile) || !File.Exists(origHeadFile) || !File.Exists(doneFile) || !File.Exists(jobsFile))
|
||||
return true;
|
||||
|
||||
var done = File.ReadAllText(doneFile).Split(new[] { '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
if (done.Length > collection.Jobs.Count)
|
||||
var origHead = File.ReadAllText(origHeadFile).Trim();
|
||||
var onto = File.ReadAllText(ontoFile).Trim();
|
||||
var collection = JsonSerializer.Deserialize(File.ReadAllText(jobsFile), JsonCodeGen.Default.InteractiveRebaseJobCollection);
|
||||
if (!collection.Onto.Equals(onto) || !collection.OrigHead.Equals(origHead))
|
||||
return true;
|
||||
|
||||
var job = collection.Jobs[done.Length - 1];
|
||||
var done = File.ReadAllText(doneFile).Trim().Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries);
|
||||
if (done.Length == 0)
|
||||
return true;
|
||||
|
||||
var current = done[^1].Trim();
|
||||
var match = REG_REBASE_TODO().Match(current);
|
||||
if (!match.Success)
|
||||
return true;
|
||||
|
||||
var sha = match.Groups[1].Value;
|
||||
foreach (var job in collection.Jobs)
|
||||
{
|
||||
if (job.SHA.StartsWith(sha))
|
||||
{
|
||||
File.WriteAllText(file, job.Message);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -442,7 +521,7 @@ namespace SourceGit
|
|||
private bool TryLaunchAsCoreEditor(IClassicDesktopStyleApplicationLifetime desktop)
|
||||
{
|
||||
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;
|
||||
|
||||
var file = args[1];
|
||||
|
@ -452,8 +531,8 @@ namespace SourceGit
|
|||
return true;
|
||||
}
|
||||
|
||||
var editor = new Views.StandaloneCommitMessageEditor();
|
||||
editor.SetFile(file);
|
||||
var editor = new Views.CommitMessageEditor();
|
||||
editor.AsStandalone(file);
|
||||
desktop.MainWindow = editor;
|
||||
return true;
|
||||
}
|
||||
|
@ -478,23 +557,54 @@ namespace SourceGit
|
|||
|
||||
private void TryLaunchAsNormal(IClassicDesktopStyleApplicationLifetime desktop)
|
||||
{
|
||||
Native.OS.SetupEnternalTools();
|
||||
Native.OS.SetupExternalTools();
|
||||
Models.AvatarManager.Instance.Start();
|
||||
|
||||
string startupRepo = null;
|
||||
if (desktop.Args != null && desktop.Args.Length == 1 && Directory.Exists(desktop.Args[0]))
|
||||
startupRepo = desktop.Args[0];
|
||||
|
||||
var pref = ViewModels.Preferences.Instance;
|
||||
pref.SetCanModify();
|
||||
|
||||
_launcher = new ViewModels.Launcher(startupRepo);
|
||||
desktop.MainWindow = new Views.Launcher() { DataContext = _launcher };
|
||||
desktop.ShutdownMode = ShutdownMode.OnMainWindowClose;
|
||||
|
||||
#if !DISABLE_UPDATE_DETECTION
|
||||
var pref = ViewModels.Preferences.Instance;
|
||||
if (pref.ShouldCheck4UpdateOnStartup())
|
||||
Check4Update();
|
||||
#endif
|
||||
}
|
||||
|
||||
private void TryOpenRepository(string repo)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(repo) && Directory.Exists(repo))
|
||||
{
|
||||
var test = new Commands.QueryRepositoryRootPath(repo).ReadToEnd();
|
||||
if (test.IsSuccess && !string.IsNullOrEmpty(test.StdOut))
|
||||
{
|
||||
Dispatcher.UIThread.Invoke(() =>
|
||||
{
|
||||
var node = ViewModels.Preferences.Instance.FindOrAddNodeByRepositoryPath(test.StdOut.Trim(), null, false);
|
||||
ViewModels.Welcome.Instance.Refresh();
|
||||
_launcher?.OpenRepositoryInTab(node, null);
|
||||
|
||||
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime { MainWindow: Views.Launcher wnd })
|
||||
wnd.BringToTop();
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Dispatcher.UIThread.Invoke(() =>
|
||||
{
|
||||
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime { MainWindow: Views.Launcher launcher })
|
||||
launcher.BringToTop();
|
||||
});
|
||||
}
|
||||
|
||||
private void Check4Update(bool manually = false)
|
||||
{
|
||||
Task.Run(async () =>
|
||||
|
@ -540,11 +650,7 @@ namespace SourceGit
|
|||
{
|
||||
Dispatcher.UIThread.Post(() =>
|
||||
{
|
||||
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime { MainWindow: { } owner })
|
||||
{
|
||||
var dialog = new Views.SelfUpdate() { DataContext = new ViewModels.SelfUpdate() { Data = data } };
|
||||
dialog.ShowDialog(owner);
|
||||
}
|
||||
ShowWindow(new ViewModels.SelfUpdate() { Data = data }, true);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -574,12 +680,24 @@ namespace SourceGit
|
|||
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;
|
||||
}
|
||||
|
||||
[GeneratedRegex(@"^[a-z]+\s+([a-fA-F0-9]{4,40})(\s+.*)?$")]
|
||||
private static partial Regex REG_REBASE_TODO();
|
||||
|
||||
private Models.IpcChannel _ipcChannel = null;
|
||||
private ViewModels.Launcher _launcher = null;
|
||||
private ResourceDictionary _activeLocale = null;
|
||||
private ResourceDictionary _themeOverrides = null;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<!-- This manifest is used on Windows only.
|
||||
Don't remove it as it might cause problems with window transparency and embeded controls.
|
||||
Don't remove it as it might cause problems with window transparency and embedded controls.
|
||||
For more details visit https://learn.microsoft.com/en-us/windows/win32/sbscs/application-manifests -->
|
||||
<assemblyIdentity version="1.0.0.0" name="SourceGit.Desktop"/>
|
||||
|
||||
|
|
|
@ -1,7 +1,4 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace SourceGit.Commands
|
||||
namespace SourceGit.Commands
|
||||
{
|
||||
public class Add : Command
|
||||
{
|
||||
|
@ -12,20 +9,11 @@ namespace SourceGit.Commands
|
|||
Args = includeUntracked ? "add ." : "add -u .";
|
||||
}
|
||||
|
||||
public Add(string repo, List<string> changes)
|
||||
public Add(string repo, Models.Change change)
|
||||
{
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
|
||||
var builder = new StringBuilder();
|
||||
builder.Append("add --");
|
||||
foreach (var c in changes)
|
||||
{
|
||||
builder.Append(" \"");
|
||||
builder.Append(c);
|
||||
builder.Append("\"");
|
||||
}
|
||||
Args = builder.ToString();
|
||||
Args = $"add -- \"{change.Path}\"";
|
||||
}
|
||||
|
||||
public Add(string repo, string pathspecFromFile)
|
||||
|
|
|
@ -1,23 +1,12 @@
|
|||
using System;
|
||||
|
||||
namespace SourceGit.Commands
|
||||
namespace SourceGit.Commands
|
||||
{
|
||||
public class Archive : Command
|
||||
{
|
||||
public Archive(string repo, string revision, string saveTo, Action<string> outputHandler)
|
||||
public Archive(string repo, string revision, string saveTo)
|
||||
{
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
Args = $"archive --format=zip --verbose --output=\"{saveTo}\" {revision}";
|
||||
TraitErrorAsOutput = true;
|
||||
_outputHandler = outputHandler;
|
||||
}
|
||||
|
||||
protected override void OnReadline(string line)
|
||||
{
|
||||
_outputHandler?.Invoke(line);
|
||||
}
|
||||
|
||||
private readonly Action<string> _outputHandler;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,46 +1,8 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace SourceGit.Commands
|
||||
namespace SourceGit.Commands
|
||||
{
|
||||
public partial class AssumeUnchanged
|
||||
public class AssumeUnchanged : Command
|
||||
{
|
||||
[GeneratedRegex(@"^(\w)\s+(.+)$")]
|
||||
private static partial Regex REG_PARSE();
|
||||
|
||||
class ViewCommand : Command
|
||||
{
|
||||
public ViewCommand(string repo)
|
||||
{
|
||||
WorkingDirectory = repo;
|
||||
Args = "ls-files -v";
|
||||
RaiseError = false;
|
||||
}
|
||||
|
||||
public List<string> Result()
|
||||
{
|
||||
Exec();
|
||||
return _outs;
|
||||
}
|
||||
|
||||
protected override void OnReadline(string line)
|
||||
{
|
||||
var match = REG_PARSE().Match(line);
|
||||
if (!match.Success)
|
||||
return;
|
||||
|
||||
if (match.Groups[1].Value == "h")
|
||||
{
|
||||
_outs.Add(match.Groups[2].Value);
|
||||
}
|
||||
}
|
||||
|
||||
private readonly List<string> _outs = new List<string>();
|
||||
}
|
||||
|
||||
class ModCommand : Command
|
||||
{
|
||||
public ModCommand(string repo, string file, bool bAdd)
|
||||
public AssumeUnchanged(string repo, string file, bool bAdd)
|
||||
{
|
||||
var mode = bAdd ? "--assume-unchanged" : "--no-assume-unchanged";
|
||||
|
||||
|
@ -49,27 +11,4 @@ namespace SourceGit.Commands
|
|||
Args = $"update-index {mode} -- \"{file}\"";
|
||||
}
|
||||
}
|
||||
|
||||
public AssumeUnchanged(string repo)
|
||||
{
|
||||
_repo = repo;
|
||||
}
|
||||
|
||||
public List<string> View()
|
||||
{
|
||||
return new ViewCommand(_repo).Result();
|
||||
}
|
||||
|
||||
public void Add(string file)
|
||||
{
|
||||
new ModCommand(_repo, file, true).Exec();
|
||||
}
|
||||
|
||||
public void Remove(string file)
|
||||
{
|
||||
new ModCommand(_repo, file, false).Exec();
|
||||
}
|
||||
|
||||
private readonly string _repo;
|
||||
}
|
||||
}
|
||||
|
|
13
src/Commands/Bisect.cs
Normal file
13
src/Commands/Bisect.cs
Normal file
|
@ -0,0 +1,13 @@
|
|||
namespace SourceGit.Commands
|
||||
{
|
||||
public class Bisect : Command
|
||||
{
|
||||
public Bisect(string repo, string subcmd)
|
||||
{
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
RaiseError = false;
|
||||
Args = $"bisect {subcmd}";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -21,10 +21,17 @@ namespace SourceGit.Commands
|
|||
|
||||
public Models.BlameData Result()
|
||||
{
|
||||
var succ = Exec();
|
||||
if (!succ)
|
||||
var rs = ReadToEnd();
|
||||
if (!rs.IsSuccess)
|
||||
return _result;
|
||||
|
||||
var lines = rs.StdOut.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries);
|
||||
foreach (var line in lines)
|
||||
{
|
||||
return new Models.BlameData();
|
||||
ParseLine(line);
|
||||
|
||||
if (_result.IsBinary)
|
||||
break;
|
||||
}
|
||||
|
||||
if (_needUnifyCommitSHA)
|
||||
|
@ -42,14 +49,9 @@ namespace SourceGit.Commands
|
|||
return _result;
|
||||
}
|
||||
|
||||
protected override void OnReadline(string line)
|
||||
private void ParseLine(string line)
|
||||
{
|
||||
if (_result.IsBinary)
|
||||
return;
|
||||
if (string.IsNullOrEmpty(line))
|
||||
return;
|
||||
|
||||
if (line.IndexOf('\0', StringComparison.Ordinal) >= 0)
|
||||
if (line.Contains('\0', StringComparison.Ordinal))
|
||||
{
|
||||
_result.IsBinary = true;
|
||||
_result.LineInfos.Clear();
|
||||
|
@ -87,7 +89,7 @@ namespace SourceGit.Commands
|
|||
|
||||
private readonly Models.BlameData _result = new Models.BlameData();
|
||||
private readonly StringBuilder _content = new StringBuilder();
|
||||
private readonly string _dateFormat = Models.DateTimeFormat.Actived.DateOnly;
|
||||
private readonly string _dateFormat = Models.DateTimeFormat.Active.DateOnly;
|
||||
private string _lastSHA = string.Empty;
|
||||
private bool _needUnifyCommitSHA = false;
|
||||
private int _minSHALen = 64;
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
namespace SourceGit.Commands
|
||||
using System.Text;
|
||||
|
||||
namespace SourceGit.Commands
|
||||
{
|
||||
public static class Branch
|
||||
{
|
||||
|
@ -11,29 +13,40 @@
|
|||
return cmd.ReadToEnd().StdOut.Trim();
|
||||
}
|
||||
|
||||
public static bool Create(string repo, string name, string basedOn)
|
||||
public static bool Create(string repo, string name, string basedOn, bool force, Models.ICommandLog log)
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
builder.Append("branch ");
|
||||
if (force)
|
||||
builder.Append("-f ");
|
||||
builder.Append(name);
|
||||
builder.Append(" ");
|
||||
builder.Append(basedOn);
|
||||
|
||||
var cmd = new Command();
|
||||
cmd.WorkingDirectory = repo;
|
||||
cmd.Context = repo;
|
||||
cmd.Args = $"branch {name} {basedOn}";
|
||||
cmd.Args = builder.ToString();
|
||||
cmd.Log = log;
|
||||
return cmd.Exec();
|
||||
}
|
||||
|
||||
public static bool Rename(string repo, string name, string to)
|
||||
public static bool Rename(string repo, string name, string to, Models.ICommandLog log)
|
||||
{
|
||||
var cmd = new Command();
|
||||
cmd.WorkingDirectory = repo;
|
||||
cmd.Context = repo;
|
||||
cmd.Args = $"branch -M {name} {to}";
|
||||
cmd.Log = log;
|
||||
return cmd.Exec();
|
||||
}
|
||||
|
||||
public static bool SetUpstream(string repo, string name, string upstream)
|
||||
public static bool SetUpstream(string repo, string name, string upstream, Models.ICommandLog log)
|
||||
{
|
||||
var cmd = new Command();
|
||||
cmd.WorkingDirectory = repo;
|
||||
cmd.Context = repo;
|
||||
cmd.Log = log;
|
||||
|
||||
if (string.IsNullOrEmpty(upstream))
|
||||
cmd.Args = $"branch {name} --unset-upstream";
|
||||
|
@ -43,25 +56,27 @@
|
|||
return cmd.Exec();
|
||||
}
|
||||
|
||||
public static bool DeleteLocal(string repo, string name)
|
||||
public static bool DeleteLocal(string repo, string name, Models.ICommandLog log)
|
||||
{
|
||||
var cmd = new Command();
|
||||
cmd.WorkingDirectory = repo;
|
||||
cmd.Context = repo;
|
||||
cmd.Args = $"branch -D {name}";
|
||||
cmd.Log = log;
|
||||
return cmd.Exec();
|
||||
}
|
||||
|
||||
public static bool DeleteRemote(string repo, string remote, string name)
|
||||
public static bool DeleteRemote(string repo, string remote, string name, Models.ICommandLog log)
|
||||
{
|
||||
bool exists = new Remote(repo).HasBranch(remote, name);
|
||||
if (exists)
|
||||
return new Push(repo, remote, $"refs/heads/{name}", true).Exec();
|
||||
return new Push(repo, remote, $"refs/heads/{name}", true) { Log = log }.Exec();
|
||||
|
||||
var cmd = new Command();
|
||||
cmd.WorkingDirectory = repo;
|
||||
cmd.Context = repo;
|
||||
cmd.Args = $"branch -D -r {remote}/{name}";
|
||||
cmd.Log = log;
|
||||
return cmd.Exec();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace SourceGit.Commands
|
||||
|
@ -12,19 +11,37 @@ namespace SourceGit.Commands
|
|||
Context = repo;
|
||||
}
|
||||
|
||||
public bool Branch(string branch, Action<string> onProgress)
|
||||
public bool Branch(string branch, bool force)
|
||||
{
|
||||
Args = $"checkout --recurse-submodules --progress {branch}";
|
||||
TraitErrorAsOutput = true;
|
||||
_outputHandler = onProgress;
|
||||
var builder = new StringBuilder();
|
||||
builder.Append("checkout --progress ");
|
||||
if (force)
|
||||
builder.Append("--force ");
|
||||
builder.Append(branch);
|
||||
|
||||
Args = builder.ToString();
|
||||
return Exec();
|
||||
}
|
||||
|
||||
public bool Branch(string branch, string basedOn, Action<string> onProgress)
|
||||
public bool Branch(string branch, string basedOn, bool force, bool allowOverwrite)
|
||||
{
|
||||
Args = $"checkout --recurse-submodules --progress -b {branch} {basedOn}";
|
||||
TraitErrorAsOutput = true;
|
||||
_outputHandler = onProgress;
|
||||
var builder = new StringBuilder();
|
||||
builder.Append("checkout --progress ");
|
||||
if (force)
|
||||
builder.Append("--force ");
|
||||
builder.Append(allowOverwrite ? "-B " : "-b ");
|
||||
builder.Append(branch);
|
||||
builder.Append(" ");
|
||||
builder.Append(basedOn);
|
||||
|
||||
Args = builder.ToString();
|
||||
return Exec();
|
||||
}
|
||||
|
||||
public bool Commit(string commitId, bool force)
|
||||
{
|
||||
var option = force ? "--force" : string.Empty;
|
||||
Args = $"checkout {option} --detach --progress {commitId}";
|
||||
return Exec();
|
||||
}
|
||||
|
||||
|
@ -61,20 +78,5 @@ namespace SourceGit.Commands
|
|||
Args = $"checkout --no-overlay {revision} -- \"{file}\"";
|
||||
return Exec();
|
||||
}
|
||||
|
||||
public bool Commit(string commitId, Action<string> onProgress)
|
||||
{
|
||||
Args = $"checkout --detach --progress {commitId}";
|
||||
TraitErrorAsOutput = true;
|
||||
_outputHandler = onProgress;
|
||||
return Exec();
|
||||
}
|
||||
|
||||
protected override void OnReadline(string line)
|
||||
{
|
||||
_outputHandler?.Invoke(line);
|
||||
}
|
||||
|
||||
private Action<string> _outputHandler;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,31 +1,12 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace SourceGit.Commands
|
||||
namespace SourceGit.Commands
|
||||
{
|
||||
public class Clean : Command
|
||||
{
|
||||
public Clean(string repo, bool includeIgnored)
|
||||
public Clean(string repo)
|
||||
{
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
Args = includeIgnored ? "clean -qfdx" : "clean -qfd";
|
||||
}
|
||||
|
||||
public Clean(string repo, List<string> files)
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
builder.Append("clean -qfd --");
|
||||
foreach (var f in files)
|
||||
{
|
||||
builder.Append(" \"");
|
||||
builder.Append(f);
|
||||
builder.Append("\"");
|
||||
}
|
||||
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
Args = builder.ToString();
|
||||
Args = "clean -qfdx";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,16 +1,11 @@
|
|||
using System;
|
||||
|
||||
namespace SourceGit.Commands
|
||||
namespace SourceGit.Commands
|
||||
{
|
||||
public class Clone : Command
|
||||
{
|
||||
private readonly Action<string> _notifyProgress;
|
||||
|
||||
public Clone(string ctx, string path, string url, string localName, string sshKey, string extraArgs, Action<string> ouputHandler)
|
||||
public Clone(string ctx, string path, string url, string localName, string sshKey, string extraArgs)
|
||||
{
|
||||
Context = ctx;
|
||||
WorkingDirectory = path;
|
||||
TraitErrorAsOutput = true;
|
||||
SSHKey = sshKey;
|
||||
Args = "clone --progress --verbose ";
|
||||
|
||||
|
@ -21,13 +16,6 @@ namespace SourceGit.Commands
|
|||
|
||||
if (!string.IsNullOrEmpty(localName))
|
||||
Args += localName;
|
||||
|
||||
_notifyProgress = ouputHandler;
|
||||
}
|
||||
|
||||
protected override void OnReadline(string line)
|
||||
{
|
||||
_notifyProgress?.Invoke(line);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,45 +32,18 @@ namespace SourceGit.Commands
|
|||
public string SSHKey { get; set; } = string.Empty;
|
||||
public string Args { get; set; } = string.Empty;
|
||||
public bool RaiseError { get; set; } = true;
|
||||
public bool TraitErrorAsOutput { get; set; } = false;
|
||||
public Models.ICommandLog Log { get; set; } = null;
|
||||
|
||||
public bool Exec()
|
||||
{
|
||||
Log?.AppendLine($"$ git {Args}\n");
|
||||
|
||||
var start = CreateGitStartInfo();
|
||||
var errs = new List<string>();
|
||||
var proc = new Process() { StartInfo = start };
|
||||
|
||||
proc.OutputDataReceived += (_, e) =>
|
||||
{
|
||||
if (e.Data != null)
|
||||
OnReadline(e.Data);
|
||||
};
|
||||
|
||||
proc.ErrorDataReceived += (_, e) =>
|
||||
{
|
||||
if (string.IsNullOrEmpty(e.Data))
|
||||
{
|
||||
errs.Add(string.Empty);
|
||||
return;
|
||||
}
|
||||
|
||||
if (TraitErrorAsOutput)
|
||||
OnReadline(e.Data);
|
||||
|
||||
// Ignore progress messages
|
||||
if (e.Data.StartsWith("remote: Enumerating objects:", StringComparison.Ordinal))
|
||||
return;
|
||||
if (e.Data.StartsWith("remote: Counting objects:", StringComparison.Ordinal))
|
||||
return;
|
||||
if (e.Data.StartsWith("remote: Compressing objects:", StringComparison.Ordinal))
|
||||
return;
|
||||
if (e.Data.StartsWith("Filtering content:", StringComparison.Ordinal))
|
||||
return;
|
||||
if (REG_PROGRESS().IsMatch(e.Data))
|
||||
return;
|
||||
|
||||
errs.Add(e.Data);
|
||||
};
|
||||
proc.OutputDataReceived += (_, e) => HandleOutput(e.Data, errs);
|
||||
proc.ErrorDataReceived += (_, e) => HandleOutput(e.Data, errs);
|
||||
|
||||
var dummy = null as Process;
|
||||
var dummyProcLock = new object();
|
||||
|
@ -97,6 +70,7 @@ namespace SourceGit.Commands
|
|||
if (RaiseError)
|
||||
Dispatcher.UIThread.Post(() => App.RaiseException(Context, e.Message));
|
||||
|
||||
Log?.AppendLine(string.Empty);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -114,6 +88,7 @@ namespace SourceGit.Commands
|
|||
|
||||
int exitCode = proc.ExitCode;
|
||||
proc.Close();
|
||||
Log?.AppendLine(string.Empty);
|
||||
|
||||
if (!CancellationToken.IsCancellationRequested && exitCode != 0)
|
||||
{
|
||||
|
@ -162,11 +137,6 @@ namespace SourceGit.Commands
|
|||
return rs;
|
||||
}
|
||||
|
||||
protected virtual void OnReadline(string line)
|
||||
{
|
||||
// Implemented by derived class
|
||||
}
|
||||
|
||||
private ProcessStartInfo CreateGitStartInfo()
|
||||
{
|
||||
var start = new ProcessStartInfo();
|
||||
|
@ -222,6 +192,28 @@ namespace SourceGit.Commands
|
|||
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+%")]
|
||||
private static partial Regex REG_PROGRESS();
|
||||
}
|
||||
|
|
|
@ -4,19 +4,18 @@ namespace SourceGit.Commands
|
|||
{
|
||||
public class Commit : Command
|
||||
{
|
||||
public Commit(string repo, string message, bool amend, bool signOff)
|
||||
public Commit(string repo, string message, bool signOff, bool amend, bool resetAuthor)
|
||||
{
|
||||
_tmpFile = Path.GetTempFileName();
|
||||
File.WriteAllText(_tmpFile, message);
|
||||
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
TraitErrorAsOutput = true;
|
||||
Args = $"commit --allow-empty --file=\"{_tmpFile}\"";
|
||||
if (amend)
|
||||
Args += " --amend --no-edit";
|
||||
if (signOff)
|
||||
Args += " --signoff";
|
||||
if (amend)
|
||||
Args += resetAuthor ? " --amend --reset-author --no-edit" : " --amend --no-edit";
|
||||
}
|
||||
|
||||
public bool Run()
|
||||
|
@ -35,6 +34,6 @@ namespace SourceGit.Commands
|
|||
return succ;
|
||||
}
|
||||
|
||||
private string _tmpFile = string.Empty;
|
||||
private readonly string _tmpFile;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,12 +31,19 @@ namespace SourceGit.Commands
|
|||
|
||||
public List<Models.Change> Result()
|
||||
{
|
||||
Exec();
|
||||
_changes.Sort((l, r) => string.Compare(l.Path, r.Path, StringComparison.Ordinal));
|
||||
var rs = ReadToEnd();
|
||||
if (!rs.IsSuccess)
|
||||
return _changes;
|
||||
|
||||
var lines = rs.StdOut.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries);
|
||||
foreach (var line in lines)
|
||||
ParseLine(line);
|
||||
|
||||
_changes.Sort((l, r) => Models.NumericSort.Compare(l.Path, r.Path));
|
||||
return _changes;
|
||||
}
|
||||
|
||||
protected override void OnReadline(string line)
|
||||
private void ParseLine(string line)
|
||||
{
|
||||
var match = REG_FORMAT().Match(line);
|
||||
if (!match.Success)
|
||||
|
|
|
@ -8,7 +8,7 @@ namespace SourceGit.Commands
|
|||
{
|
||||
WorkingDirectory = 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()
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
|
@ -28,34 +28,48 @@ namespace SourceGit.Commands
|
|||
Context = repo;
|
||||
|
||||
if (ignoreWhitespace)
|
||||
Args = $"-c core.autocrlf=false diff --no-ext-diff --patch --ignore-cr-at-eol --ignore-all-space --unified={unified} {opt}";
|
||||
Args = $"diff --no-ext-diff --patch --ignore-all-space --unified={unified} {opt}";
|
||||
else if (Models.DiffOption.IgnoreCRAtEOL)
|
||||
Args = $"diff --no-ext-diff --patch --ignore-cr-at-eol --unified={unified} {opt}";
|
||||
else
|
||||
Args = $"-c core.autocrlf=false diff --no-ext-diff --patch --ignore-cr-at-eol --unified={unified} {opt}";
|
||||
Args = $"diff --no-ext-diff --patch --unified={unified} {opt}";
|
||||
}
|
||||
|
||||
public Models.DiffResult Result()
|
||||
{
|
||||
Exec();
|
||||
var rs = ReadToEnd();
|
||||
var start = 0;
|
||||
var end = rs.StdOut.IndexOf('\n', start);
|
||||
while (end > 0)
|
||||
{
|
||||
var line = rs.StdOut.Substring(start, end - start);
|
||||
ParseLine(line);
|
||||
|
||||
if (_result.IsBinary || _result.IsLFS)
|
||||
start = end + 1;
|
||||
end = rs.StdOut.IndexOf('\n', start);
|
||||
}
|
||||
|
||||
if (start < rs.StdOut.Length)
|
||||
ParseLine(rs.StdOut.Substring(start));
|
||||
|
||||
if (_result.IsBinary || _result.IsLFS || _result.TextDiff.Lines.Count == 0)
|
||||
{
|
||||
_result.TextDiff = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
ProcessInlineHighlights();
|
||||
|
||||
if (_result.TextDiff.Lines.Count == 0)
|
||||
_result.TextDiff = null;
|
||||
else
|
||||
_result.TextDiff.MaxLineNumber = Math.Max(_newLine, _oldLine);
|
||||
}
|
||||
|
||||
return _result;
|
||||
}
|
||||
|
||||
protected override void OnReadline(string line)
|
||||
private void ParseLine(string line)
|
||||
{
|
||||
if (_result.IsBinary)
|
||||
return;
|
||||
|
||||
if (line.StartsWith("old mode ", StringComparison.Ordinal))
|
||||
{
|
||||
_result.OldMode = line.Substring(9);
|
||||
|
@ -80,9 +94,6 @@ namespace SourceGit.Commands
|
|||
return;
|
||||
}
|
||||
|
||||
if (_result.IsBinary)
|
||||
return;
|
||||
|
||||
if (_result.IsLFS)
|
||||
{
|
||||
var ch = line[0];
|
||||
|
@ -94,7 +105,7 @@ namespace SourceGit.Commands
|
|||
}
|
||||
else if (line.StartsWith("-size ", StringComparison.Ordinal))
|
||||
{
|
||||
_result.LFSDiff.Old.Size = long.Parse(line.Substring(6));
|
||||
_result.LFSDiff.Old.Size = long.Parse(line.AsSpan(6));
|
||||
}
|
||||
}
|
||||
else if (ch == '+')
|
||||
|
@ -105,12 +116,12 @@ namespace SourceGit.Commands
|
|||
}
|
||||
else if (line.StartsWith("+size ", StringComparison.Ordinal))
|
||||
{
|
||||
_result.LFSDiff.New.Size = long.Parse(line.Substring(6));
|
||||
_result.LFSDiff.New.Size = long.Parse(line.AsSpan(6));
|
||||
}
|
||||
}
|
||||
else if (line.StartsWith(" size ", StringComparison.Ordinal))
|
||||
{
|
||||
_result.LFSDiff.New.Size = _result.LFSDiff.Old.Size = long.Parse(line.Substring(6));
|
||||
_result.LFSDiff.New.Size = _result.LFSDiff.Old.Size = long.Parse(line.AsSpan(6));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
@ -140,7 +151,8 @@ namespace SourceGit.Commands
|
|||
|
||||
_oldLine = int.Parse(match.Groups[1].Value);
|
||||
_newLine = int.Parse(match.Groups[2].Value);
|
||||
_result.TextDiff.Lines.Add(new Models.TextDiffLine(Models.TextDiffLineType.Indicator, line, 0, 0));
|
||||
_last = new Models.TextDiffLine(Models.TextDiffLineType.Indicator, line, 0, 0);
|
||||
_result.TextDiff.Lines.Add(_last);
|
||||
}
|
||||
}
|
||||
else
|
||||
|
@ -148,7 +160,8 @@ namespace SourceGit.Commands
|
|||
if (line.Length == 0)
|
||||
{
|
||||
ProcessInlineHighlights();
|
||||
_result.TextDiff.Lines.Add(new Models.TextDiffLine(Models.TextDiffLineType.Normal, "", _oldLine, _newLine));
|
||||
_last = new Models.TextDiffLine(Models.TextDiffLineType.Normal, "", _oldLine, _newLine);
|
||||
_result.TextDiff.Lines.Add(_last);
|
||||
_oldLine++;
|
||||
_newLine++;
|
||||
return;
|
||||
|
@ -164,7 +177,8 @@ namespace SourceGit.Commands
|
|||
return;
|
||||
}
|
||||
|
||||
_deleted.Add(new Models.TextDiffLine(Models.TextDiffLineType.Deleted, line.Substring(1), _oldLine, 0));
|
||||
_last = new Models.TextDiffLine(Models.TextDiffLineType.Deleted, line.Substring(1), _oldLine, 0);
|
||||
_deleted.Add(_last);
|
||||
_oldLine++;
|
||||
}
|
||||
else if (ch == '+')
|
||||
|
@ -176,7 +190,8 @@ namespace SourceGit.Commands
|
|||
return;
|
||||
}
|
||||
|
||||
_added.Add(new Models.TextDiffLine(Models.TextDiffLineType.Added, line.Substring(1), 0, _newLine));
|
||||
_last = new Models.TextDiffLine(Models.TextDiffLineType.Added, line.Substring(1), 0, _newLine);
|
||||
_added.Add(_last);
|
||||
_newLine++;
|
||||
}
|
||||
else if (ch != '\\')
|
||||
|
@ -187,7 +202,8 @@ namespace SourceGit.Commands
|
|||
{
|
||||
_oldLine = int.Parse(match.Groups[1].Value);
|
||||
_newLine = int.Parse(match.Groups[2].Value);
|
||||
_result.TextDiff.Lines.Add(new Models.TextDiffLine(Models.TextDiffLineType.Indicator, line, 0, 0));
|
||||
_last = new Models.TextDiffLine(Models.TextDiffLineType.Indicator, line, 0, 0);
|
||||
_result.TextDiff.Lines.Add(_last);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -198,11 +214,16 @@ namespace SourceGit.Commands
|
|||
return;
|
||||
}
|
||||
|
||||
_result.TextDiff.Lines.Add(new Models.TextDiffLine(Models.TextDiffLineType.Normal, line.Substring(1), _oldLine, _newLine));
|
||||
_last = new Models.TextDiffLine(Models.TextDiffLineType.Normal, line.Substring(1), _oldLine, _newLine);
|
||||
_result.TextDiff.Lines.Add(_last);
|
||||
_oldLine++;
|
||||
_newLine++;
|
||||
}
|
||||
}
|
||||
else if (line.Equals("\\ No newline at end of file", StringComparison.Ordinal))
|
||||
{
|
||||
_last.NoNewLineEndOfFile = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -253,6 +274,7 @@ namespace SourceGit.Commands
|
|||
private readonly Models.DiffResult _result = new Models.DiffResult();
|
||||
private readonly List<Models.TextDiffLine> _deleted = new List<Models.TextDiffLine>();
|
||||
private readonly List<Models.TextDiffLine> _added = new List<Models.TextDiffLine>();
|
||||
private Models.TextDiffLine _last = null;
|
||||
private int _oldLine = 0;
|
||||
private int _newLine = 0;
|
||||
}
|
||||
|
|
|
@ -1,39 +1,95 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
using Avalonia.Threading;
|
||||
|
||||
namespace SourceGit.Commands
|
||||
{
|
||||
public static class Discard
|
||||
{
|
||||
public static void All(string repo, bool includeIgnored)
|
||||
/// <summary>
|
||||
/// Discard all local changes (unstaged & staged)
|
||||
/// </summary>
|
||||
/// <param name="repo"></param>
|
||||
/// <param name="includeIgnored"></param>
|
||||
/// <param name="log"></param>
|
||||
public static void All(string repo, bool includeIgnored, Models.ICommandLog log)
|
||||
{
|
||||
new Restore(repo).Exec();
|
||||
new Clean(repo, includeIgnored).Exec();
|
||||
var changes = new QueryLocalChanges(repo).Result();
|
||||
try
|
||||
{
|
||||
foreach (var c in changes)
|
||||
{
|
||||
if (c.WorkTree == Models.ChangeState.Untracked ||
|
||||
c.WorkTree == Models.ChangeState.Added ||
|
||||
c.Index == Models.ChangeState.Added ||
|
||||
c.Index == Models.ChangeState.Renamed)
|
||||
{
|
||||
var fullPath = Path.Combine(repo, c.Path);
|
||||
if (Directory.Exists(fullPath))
|
||||
Directory.Delete(fullPath, true);
|
||||
else
|
||||
File.Delete(fullPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Dispatcher.UIThread.Invoke(() =>
|
||||
{
|
||||
App.RaiseException(repo, $"Failed to discard changes. Reason: {e.Message}");
|
||||
});
|
||||
}
|
||||
|
||||
public static void Changes(string repo, List<Models.Change> changes)
|
||||
{
|
||||
var needClean = new List<string>();
|
||||
var needCheckout = new List<string>();
|
||||
new Reset(repo, "HEAD", "--hard") { Log = log }.Exec();
|
||||
|
||||
if (includeIgnored)
|
||||
new Clean(repo) { Log = log }.Exec();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Discard selected changes (only unstaged).
|
||||
/// </summary>
|
||||
/// <param name="repo"></param>
|
||||
/// <param name="changes"></param>
|
||||
/// <param name="log"></param>
|
||||
public static void Changes(string repo, List<Models.Change> changes, Models.ICommandLog log)
|
||||
{
|
||||
var restores = new List<string>();
|
||||
|
||||
try
|
||||
{
|
||||
foreach (var c in changes)
|
||||
{
|
||||
if (c.WorkTree == Models.ChangeState.Untracked || c.WorkTree == Models.ChangeState.Added)
|
||||
needClean.Add(c.Path);
|
||||
{
|
||||
var fullPath = Path.Combine(repo, c.Path);
|
||||
if (Directory.Exists(fullPath))
|
||||
Directory.Delete(fullPath, true);
|
||||
else
|
||||
needCheckout.Add(c.Path);
|
||||
File.Delete(fullPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
restores.Add(c.Path);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Dispatcher.UIThread.Invoke(() =>
|
||||
{
|
||||
App.RaiseException(repo, $"Failed to discard changes. Reason: {e.Message}");
|
||||
});
|
||||
}
|
||||
|
||||
for (int i = 0; i < needClean.Count; i += 10)
|
||||
if (restores.Count > 0)
|
||||
{
|
||||
var count = Math.Min(10, needClean.Count - i);
|
||||
new Clean(repo, needClean.GetRange(i, count)).Exec();
|
||||
}
|
||||
|
||||
for (int i = 0; i < needCheckout.Count; i += 10)
|
||||
{
|
||||
var count = Math.Min(10, needCheckout.Count - i);
|
||||
new Restore(repo, needCheckout.GetRange(i, count), "--worktree --recurse-submodules").Exec();
|
||||
var pathSpecFile = Path.GetTempFileName();
|
||||
File.WriteAllLines(pathSpecFile, restores);
|
||||
new Restore(repo, pathSpecFile, false) { Log = log }.Exec();
|
||||
File.Delete(pathSpecFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
start.FileName = file;
|
||||
|
@ -40,20 +40,22 @@ namespace SourceGit.Commands
|
|||
start.StandardErrorEncoding = Encoding.UTF8;
|
||||
start.WorkingDirectory = repo;
|
||||
|
||||
log?.AppendLine($"$ {file} {args}\n");
|
||||
|
||||
var proc = new Process() { StartInfo = start };
|
||||
var builder = new StringBuilder();
|
||||
|
||||
proc.OutputDataReceived += (_, e) =>
|
||||
{
|
||||
if (e.Data != null)
|
||||
outputHandler?.Invoke(e.Data);
|
||||
log?.AppendLine(e.Data);
|
||||
};
|
||||
|
||||
proc.ErrorDataReceived += (_, e) =>
|
||||
{
|
||||
if (e.Data != null)
|
||||
{
|
||||
outputHandler?.Invoke(e.Data);
|
||||
log?.AppendLine(e.Data);
|
||||
builder.AppendLine(e.Data);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,15 +1,11 @@
|
|||
using System;
|
||||
|
||||
namespace SourceGit.Commands
|
||||
namespace SourceGit.Commands
|
||||
{
|
||||
public class Fetch : Command
|
||||
{
|
||||
public Fetch(string repo, string remote, bool noTags, bool force, Action<string> outputHandler)
|
||||
public Fetch(string repo, string remote, bool noTags, bool force)
|
||||
{
|
||||
_outputHandler = outputHandler;
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
TraitErrorAsOutput = true;
|
||||
SSHKey = new Config(repo).Get($"remote.{remote}.sshkey");
|
||||
Args = "fetch --progress --verbose ";
|
||||
|
||||
|
@ -24,21 +20,12 @@ namespace SourceGit.Commands
|
|||
Args += remote;
|
||||
}
|
||||
|
||||
public Fetch(string repo, Models.Branch local, Models.Branch remote, Action<string> outputHandler)
|
||||
public Fetch(string repo, Models.Branch local, Models.Branch remote)
|
||||
{
|
||||
_outputHandler = outputHandler;
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
TraitErrorAsOutput = true;
|
||||
SSHKey = new Config(repo).Get($"remote.{remote.Remote}.sshkey");
|
||||
Args = $"fetch --progress --verbose {remote.Remote} {remote.Name}:{local.Name}";
|
||||
}
|
||||
|
||||
protected override void OnReadline(string line)
|
||||
{
|
||||
_outputHandler?.Invoke(line);
|
||||
}
|
||||
|
||||
private readonly Action<string> _outputHandler;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
{
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
Editor = EditorType.None;
|
||||
Args = $"format-patch {commit} -1 --output=\"{saveTo}\"";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,23 +1,12 @@
|
|||
using System;
|
||||
|
||||
namespace SourceGit.Commands
|
||||
namespace SourceGit.Commands
|
||||
{
|
||||
public class GC : Command
|
||||
{
|
||||
public GC(string repo, Action<string> outputHandler)
|
||||
public GC(string repo)
|
||||
{
|
||||
_outputHandler = outputHandler;
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
TraitErrorAsOutput = true;
|
||||
Args = "gc --prune=now";
|
||||
}
|
||||
|
||||
protected override void OnReadline(string line)
|
||||
{
|
||||
_outputHandler?.Invoke(line);
|
||||
}
|
||||
|
||||
private readonly Action<string> _outputHandler;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,52 +1,12 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using System.Text;
|
||||
using Avalonia.Threading;
|
||||
|
||||
namespace SourceGit.Commands
|
||||
{
|
||||
public static class GitFlow
|
||||
{
|
||||
public class BranchDetectResult
|
||||
public static bool Init(string repo, string master, string develop, string feature, string release, string hotfix, string version, Models.ICommandLog log)
|
||||
{
|
||||
public bool IsGitFlowBranch { get; set; } = false;
|
||||
public string Type { get; set; } = string.Empty;
|
||||
public string Prefix { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
public static bool IsEnabled(string repo, List<Models.Branch> branches)
|
||||
{
|
||||
var localBrancheNames = new HashSet<string>();
|
||||
foreach (var branch in branches)
|
||||
{
|
||||
if (branch.IsLocal)
|
||||
localBrancheNames.Add(branch.Name);
|
||||
}
|
||||
|
||||
var config = new Config(repo).ListAll();
|
||||
if (!config.TryGetValue("gitflow.branch.master", out string master) || !localBrancheNames.Contains(master))
|
||||
return false;
|
||||
|
||||
if (!config.TryGetValue("gitflow.branch.develop", out string develop) || !localBrancheNames.Contains(develop))
|
||||
return false;
|
||||
|
||||
return config.ContainsKey("gitflow.prefix.feature") &&
|
||||
config.ContainsKey("gitflow.prefix.release") &&
|
||||
config.ContainsKey("gitflow.prefix.hotfix");
|
||||
}
|
||||
|
||||
public static bool Init(string repo, List<Models.Branch> branches, string master, string develop, string feature, string release, string hotfix, string version)
|
||||
{
|
||||
var current = branches.Find(x => x.IsCurrent);
|
||||
|
||||
var masterBranch = branches.Find(x => x.Name == master);
|
||||
if (masterBranch == null && current != null)
|
||||
Branch.Create(repo, master, current.Head);
|
||||
|
||||
var devBranch = branches.Find(x => x.Name == develop);
|
||||
if (devBranch == null && current != null)
|
||||
Branch.Create(repo, develop, current.Head);
|
||||
|
||||
var config = new Config(repo);
|
||||
config.Set("gitflow.branch.master", master);
|
||||
config.Set("gitflow.branch.develop", develop);
|
||||
|
@ -61,104 +21,72 @@ namespace SourceGit.Commands
|
|||
init.WorkingDirectory = repo;
|
||||
init.Context = repo;
|
||||
init.Args = "flow init -d";
|
||||
init.Log = log;
|
||||
return init.Exec();
|
||||
}
|
||||
|
||||
public static string GetPrefix(string repo, string type)
|
||||
public static bool Start(string repo, Models.GitFlowBranchType type, string name, Models.ICommandLog log)
|
||||
{
|
||||
return new Config(repo).Get($"gitflow.prefix.{type}");
|
||||
}
|
||||
|
||||
public static BranchDetectResult DetectType(string repo, List<Models.Branch> branches, string branch)
|
||||
{
|
||||
var rs = new BranchDetectResult();
|
||||
var localBrancheNames = new HashSet<string>();
|
||||
foreach (var b in branches)
|
||||
{
|
||||
if (b.IsLocal)
|
||||
localBrancheNames.Add(b.Name);
|
||||
}
|
||||
|
||||
var config = new Config(repo).ListAll();
|
||||
if (!config.TryGetValue("gitflow.branch.master", out string master) || !localBrancheNames.Contains(master))
|
||||
return rs;
|
||||
|
||||
if (!config.TryGetValue("gitflow.branch.develop", out string develop) || !localBrancheNames.Contains(develop))
|
||||
return rs;
|
||||
|
||||
if (!config.TryGetValue("gitflow.prefix.feature", out var feature) ||
|
||||
!config.TryGetValue("gitflow.prefix.release", out var release) ||
|
||||
!config.TryGetValue("gitflow.prefix.hotfix", out var hotfix))
|
||||
return rs;
|
||||
|
||||
if (branch.StartsWith(feature, StringComparison.Ordinal))
|
||||
{
|
||||
rs.IsGitFlowBranch = true;
|
||||
rs.Type = "feature";
|
||||
rs.Prefix = feature;
|
||||
}
|
||||
else if (branch.StartsWith(release, StringComparison.Ordinal))
|
||||
{
|
||||
rs.IsGitFlowBranch = true;
|
||||
rs.Type = "release";
|
||||
rs.Prefix = release;
|
||||
}
|
||||
else if (branch.StartsWith(hotfix, StringComparison.Ordinal))
|
||||
{
|
||||
rs.IsGitFlowBranch = true;
|
||||
rs.Type = "hotfix";
|
||||
rs.Prefix = hotfix;
|
||||
}
|
||||
|
||||
return rs;
|
||||
}
|
||||
|
||||
public static bool Start(string repo, string type, string name)
|
||||
{
|
||||
if (!SUPPORTED_BRANCH_TYPES.Contains(type))
|
||||
{
|
||||
Dispatcher.UIThread.Post(() =>
|
||||
{
|
||||
App.RaiseException(repo, "Bad branch type!!!");
|
||||
});
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
var start = new Command();
|
||||
start.WorkingDirectory = repo;
|
||||
start.Context = repo;
|
||||
start.Args = $"flow {type} start {name}";
|
||||
return start.Exec();
|
||||
}
|
||||
|
||||
public static bool Finish(string repo, string type, string name, bool keepBranch)
|
||||
switch (type)
|
||||
{
|
||||
if (!SUPPORTED_BRANCH_TYPES.Contains(type))
|
||||
{
|
||||
Dispatcher.UIThread.Post(() =>
|
||||
{
|
||||
App.RaiseException(repo, "Bad branch type!!!");
|
||||
});
|
||||
|
||||
case Models.GitFlowBranchType.Feature:
|
||||
start.Args = $"flow feature start {name}";
|
||||
break;
|
||||
case Models.GitFlowBranchType.Release:
|
||||
start.Args = $"flow release start {name}";
|
||||
break;
|
||||
case Models.GitFlowBranchType.Hotfix:
|
||||
start.Args = $"flow hotfix start {name}";
|
||||
break;
|
||||
default:
|
||||
Dispatcher.UIThread.Invoke(() => App.RaiseException(repo, "Bad git-flow branch type!!!"));
|
||||
return false;
|
||||
}
|
||||
|
||||
var option = keepBranch ? "-k" : string.Empty;
|
||||
start.Log = log;
|
||||
return start.Exec();
|
||||
}
|
||||
|
||||
public static bool Finish(string repo, Models.GitFlowBranchType type, string name, bool squash, bool push, bool keepBranch, Models.ICommandLog log)
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
builder.Append("flow ");
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case Models.GitFlowBranchType.Feature:
|
||||
builder.Append("feature");
|
||||
break;
|
||||
case Models.GitFlowBranchType.Release:
|
||||
builder.Append("release");
|
||||
break;
|
||||
case Models.GitFlowBranchType.Hotfix:
|
||||
builder.Append("hotfix");
|
||||
break;
|
||||
default:
|
||||
Dispatcher.UIThread.Invoke(() => App.RaiseException(repo, "Bad git-flow branch type!!!"));
|
||||
return false;
|
||||
}
|
||||
|
||||
builder.Append(" finish ");
|
||||
if (squash)
|
||||
builder.Append("--squash ");
|
||||
if (push)
|
||||
builder.Append("--push ");
|
||||
if (keepBranch)
|
||||
builder.Append("-k ");
|
||||
builder.Append(name);
|
||||
|
||||
var finish = new Command();
|
||||
finish.WorkingDirectory = repo;
|
||||
finish.Context = repo;
|
||||
finish.Args = $"flow {type} finish {option} {name}";
|
||||
finish.Args = builder.ToString();
|
||||
finish.Log = log;
|
||||
return finish.Exec();
|
||||
}
|
||||
|
||||
private static readonly List<string> SUPPORTED_BRANCH_TYPES = new List<string>()
|
||||
{
|
||||
"feature",
|
||||
"release",
|
||||
"bugfix",
|
||||
"hotfix",
|
||||
"support",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,7 +8,14 @@ namespace SourceGit.Commands
|
|||
{
|
||||
var file = Path.Combine(repo, ".gitignore");
|
||||
if (!File.Exists(file))
|
||||
{
|
||||
File.WriteAllLines(file, [pattern]);
|
||||
return;
|
||||
}
|
||||
|
||||
var org = File.ReadAllText(file);
|
||||
if (!org.EndsWith('\n'))
|
||||
File.AppendAllLines(file, ["", pattern]);
|
||||
else
|
||||
File.AppendAllLines(file, [pattern]);
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ namespace SourceGit.Commands
|
|||
{
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
Args = $"diff 4b825dc642cb6eb9a060e54bf8d69288fbee4904 {commit} --numstat -- \"{path}\"";
|
||||
Args = $"diff {Models.Commit.EmptyTreeSHA1} {commit} --numstat -- \"{path}\"";
|
||||
RaiseError = false;
|
||||
}
|
||||
|
||||
|
|
|
@ -10,5 +10,10 @@
|
|||
Context = repo;
|
||||
Args = $"diff -a --ignore-cr-at-eol --check {opt}";
|
||||
}
|
||||
|
||||
public bool Result()
|
||||
{
|
||||
return ReadToEnd().IsSuccess;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,23 +10,15 @@ namespace SourceGit.Commands
|
|||
[GeneratedRegex(@"^(.+)\s+([\w.]+)\s+\w+:(\d+)$")]
|
||||
private static partial Regex REG_LOCK();
|
||||
|
||||
class SubCmd : Command
|
||||
private class SubCmd : Command
|
||||
{
|
||||
public SubCmd(string repo, string args, Action<string> onProgress)
|
||||
public SubCmd(string repo, string args, Models.ICommandLog log)
|
||||
{
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
Args = args;
|
||||
TraitErrorAsOutput = true;
|
||||
_outputHandler = onProgress;
|
||||
Log = log;
|
||||
}
|
||||
|
||||
protected override void OnReadline(string line)
|
||||
{
|
||||
_outputHandler?.Invoke(line);
|
||||
}
|
||||
|
||||
private readonly Action<string> _outputHandler;
|
||||
}
|
||||
|
||||
public LFS(string repo)
|
||||
|
@ -44,35 +36,35 @@ namespace SourceGit.Commands
|
|||
return content.Contains("git lfs pre-push");
|
||||
}
|
||||
|
||||
public bool Install()
|
||||
public bool Install(Models.ICommandLog log)
|
||||
{
|
||||
return new SubCmd(_repo, "lfs install --local", null).Exec();
|
||||
return new SubCmd(_repo, "lfs install --local", log).Exec();
|
||||
}
|
||||
|
||||
public bool Track(string pattern, bool isFilenameMode = false)
|
||||
public bool Track(string pattern, bool isFilenameMode, Models.ICommandLog log)
|
||||
{
|
||||
var opt = isFilenameMode ? "--filename" : "";
|
||||
return new SubCmd(_repo, $"lfs track {opt} \"{pattern}\"", null).Exec();
|
||||
return new SubCmd(_repo, $"lfs track {opt} \"{pattern}\"", log).Exec();
|
||||
}
|
||||
|
||||
public void Fetch(string remote, Action<string> outputHandler)
|
||||
public void Fetch(string remote, Models.ICommandLog log)
|
||||
{
|
||||
new SubCmd(_repo, $"lfs fetch {remote}", outputHandler).Exec();
|
||||
new SubCmd(_repo, $"lfs fetch {remote}", log).Exec();
|
||||
}
|
||||
|
||||
public void Pull(string remote, Action<string> outputHandler)
|
||||
public void Pull(string remote, Models.ICommandLog log)
|
||||
{
|
||||
new SubCmd(_repo, $"lfs pull {remote}", outputHandler).Exec();
|
||||
new SubCmd(_repo, $"lfs pull {remote}", log).Exec();
|
||||
}
|
||||
|
||||
public void Push(string remote, Action<string> outputHandler)
|
||||
public void Push(string remote, Models.ICommandLog log)
|
||||
{
|
||||
new SubCmd(_repo, $"lfs push {remote}", outputHandler).Exec();
|
||||
new SubCmd(_repo, $"lfs push {remote}", log).Exec();
|
||||
}
|
||||
|
||||
public void Prune(Action<string> outputHandler)
|
||||
public void Prune(Models.ICommandLog log)
|
||||
{
|
||||
new SubCmd(_repo, "lfs prune", outputHandler).Exec();
|
||||
new SubCmd(_repo, "lfs prune", log).Exec();
|
||||
}
|
||||
|
||||
public List<Models.LFSLock> Locks(string remote)
|
||||
|
@ -101,21 +93,21 @@ namespace SourceGit.Commands
|
|||
return locks;
|
||||
}
|
||||
|
||||
public bool Lock(string remote, string file)
|
||||
public bool Lock(string remote, string file, Models.ICommandLog log)
|
||||
{
|
||||
return new SubCmd(_repo, $"lfs lock --remote={remote} \"{file}\"", null).Exec();
|
||||
return new SubCmd(_repo, $"lfs lock --remote={remote} \"{file}\"", log).Exec();
|
||||
}
|
||||
|
||||
public bool Unlock(string remote, string file, bool force)
|
||||
public bool Unlock(string remote, string file, bool force, Models.ICommandLog log)
|
||||
{
|
||||
var opt = force ? "-f" : "";
|
||||
return new SubCmd(_repo, $"lfs unlock --remote={remote} {opt} \"{file}\"", null).Exec();
|
||||
return new SubCmd(_repo, $"lfs unlock --remote={remote} {opt} \"{file}\"", log).Exec();
|
||||
}
|
||||
|
||||
public bool Unlock(string remote, long id, bool force)
|
||||
public bool Unlock(string remote, long id, bool force, Models.ICommandLog log)
|
||||
{
|
||||
var opt = force ? "-f" : "";
|
||||
return new SubCmd(_repo, $"lfs unlock --remote={remote} {opt} --id={id}", null).Exec();
|
||||
return new SubCmd(_repo, $"lfs unlock --remote={remote} {opt} --id={id}", log).Exec();
|
||||
}
|
||||
|
||||
private readonly string _repo;
|
||||
|
|
|
@ -1,26 +1,30 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace SourceGit.Commands
|
||||
{
|
||||
public class Merge : Command
|
||||
{
|
||||
public Merge(string repo, string source, string mode, Action<string> outputHandler)
|
||||
public Merge(string repo, string source, string mode, bool edit)
|
||||
{
|
||||
_outputHandler = outputHandler;
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
TraitErrorAsOutput = true;
|
||||
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, Action<string> outputHandler)
|
||||
public Merge(string repo, List<string> targets, bool autoCommit, string strategy)
|
||||
{
|
||||
_outputHandler = outputHandler;
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
TraitErrorAsOutput = true;
|
||||
|
||||
var builder = new StringBuilder();
|
||||
builder.Append("merge --progress ");
|
||||
|
@ -37,12 +41,5 @@ namespace SourceGit.Commands
|
|||
|
||||
Args = builder.ToString();
|
||||
}
|
||||
|
||||
protected override void OnReadline(string line)
|
||||
{
|
||||
_outputHandler?.Invoke(line);
|
||||
}
|
||||
|
||||
private readonly Action<string> _outputHandler = null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,7 +24,7 @@ namespace SourceGit.Commands
|
|||
|
||||
if (!File.Exists(toolPath))
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -54,7 +54,7 @@ namespace SourceGit.Commands
|
|||
|
||||
if (!File.Exists(toolPath))
|
||||
{
|
||||
Dispatcher.UIThread.Invoke(() => App.RaiseException(repo, $"Can NOT found external diff tool in '{toolPath}'!"));
|
||||
Dispatcher.UIThread.Invoke(() => App.RaiseException(repo, $"Can NOT find external diff tool in '{toolPath}'!"));
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,32 +1,18 @@
|
|||
using System;
|
||||
|
||||
namespace SourceGit.Commands
|
||||
namespace SourceGit.Commands
|
||||
{
|
||||
public class Pull : Command
|
||||
{
|
||||
public Pull(string repo, string remote, string branch, bool useRebase, bool noTags, Action<string> outputHandler)
|
||||
public Pull(string repo, string remote, string branch, bool useRebase)
|
||||
{
|
||||
_outputHandler = outputHandler;
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
TraitErrorAsOutput = true;
|
||||
SSHKey = new Config(repo).Get($"remote.{remote}.sshkey");
|
||||
Args = "pull --verbose --progress ";
|
||||
|
||||
if (useRebase)
|
||||
Args += "--rebase=true ";
|
||||
|
||||
if (noTags)
|
||||
Args += "--no-tags ";
|
||||
|
||||
Args += $"{remote} {branch}";
|
||||
}
|
||||
|
||||
protected override void OnReadline(string line)
|
||||
{
|
||||
_outputHandler?.Invoke(line);
|
||||
}
|
||||
|
||||
private readonly Action<string> _outputHandler;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,16 +1,11 @@
|
|||
using System;
|
||||
|
||||
namespace SourceGit.Commands
|
||||
namespace SourceGit.Commands
|
||||
{
|
||||
public class Push : Command
|
||||
{
|
||||
public Push(string repo, string local, string remote, string remoteBranch, bool withTags, bool checkSubmodules, bool track, bool force, Action<string> onProgress)
|
||||
public Push(string repo, string local, string remote, string remoteBranch, bool withTags, bool checkSubmodules, bool track, bool force)
|
||||
{
|
||||
_outputHandler = onProgress;
|
||||
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
TraitErrorAsOutput = true;
|
||||
SSHKey = new Config(repo).Get($"remote.{remote}.sshkey");
|
||||
Args = "push --progress --verbose ";
|
||||
|
||||
|
@ -38,12 +33,5 @@ namespace SourceGit.Commands
|
|||
|
||||
Args += $"{remote} {refname}";
|
||||
}
|
||||
|
||||
protected override void OnReadline(string line)
|
||||
{
|
||||
_outputHandler?.Invoke(line);
|
||||
}
|
||||
|
||||
private readonly Action<string> _outputHandler = null;
|
||||
}
|
||||
}
|
||||
|
|
37
src/Commands/QueryAssumeUnchangedFiles.cs
Normal file
37
src/Commands/QueryAssumeUnchangedFiles.cs
Normal file
|
@ -0,0 +1,37 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace SourceGit.Commands
|
||||
{
|
||||
public partial class QueryAssumeUnchangedFiles : Command
|
||||
{
|
||||
[GeneratedRegex(@"^(\w)\s+(.+)$")]
|
||||
private static partial Regex REG_PARSE();
|
||||
|
||||
public QueryAssumeUnchangedFiles(string repo)
|
||||
{
|
||||
WorkingDirectory = repo;
|
||||
Args = "ls-files -v";
|
||||
RaiseError = false;
|
||||
}
|
||||
|
||||
public List<string> Result()
|
||||
{
|
||||
var outs = new List<string>();
|
||||
var rs = ReadToEnd();
|
||||
var lines = rs.StdOut.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries);
|
||||
foreach (var line in lines)
|
||||
{
|
||||
var match = REG_PARSE().Match(line);
|
||||
if (!match.Success)
|
||||
continue;
|
||||
|
||||
if (match.Groups[1].Value == "h")
|
||||
outs.Add(match.Groups[2].Value);
|
||||
}
|
||||
|
||||
return outs;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -14,18 +14,20 @@ namespace SourceGit.Commands
|
|||
{
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
Args = "branch -l --all -v --format=\"%(refname)%00%(objectname)%00%(HEAD)%00%(upstream)%00%(upstream:trackshort)\"";
|
||||
Args = "branch -l --all -v --format=\"%(refname)%00%(committerdate:unix)%00%(objectname)%00%(HEAD)%00%(upstream)%00%(upstream:trackshort)\"";
|
||||
}
|
||||
|
||||
public List<Models.Branch> Result()
|
||||
public List<Models.Branch> Result(out int localBranchesCount)
|
||||
{
|
||||
localBranchesCount = 0;
|
||||
|
||||
var branches = new List<Models.Branch>();
|
||||
var rs = ReadToEnd();
|
||||
if (!rs.IsSuccess)
|
||||
return branches;
|
||||
|
||||
var lines = rs.StdOut.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries);
|
||||
var remoteBranches = new HashSet<string>();
|
||||
var remoteHeads = new Dictionary<string, string>();
|
||||
foreach (var line in lines)
|
||||
{
|
||||
var b = ParseLine(line);
|
||||
|
@ -33,14 +35,27 @@ namespace SourceGit.Commands
|
|||
{
|
||||
branches.Add(b);
|
||||
if (!b.IsLocal)
|
||||
remoteBranches.Add(b.FullName);
|
||||
remoteHeads.Add(b.FullName, b.Head);
|
||||
else
|
||||
localBranchesCount++;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var b in branches)
|
||||
{
|
||||
if (b.IsLocal && !string.IsNullOrEmpty(b.Upstream))
|
||||
b.IsUpsteamGone = !remoteBranches.Contains(b.Upstream);
|
||||
{
|
||||
if (remoteHeads.TryGetValue(b.Upstream, out var upstreamHead))
|
||||
{
|
||||
b.IsUpstreamGone = false;
|
||||
b.TrackStatus ??= new QueryTrackStatus(WorkingDirectory, b.Head, upstreamHead).Result();
|
||||
}
|
||||
else
|
||||
{
|
||||
b.IsUpstreamGone = true;
|
||||
b.TrackStatus ??= new Models.BranchTrackStatus();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return branches;
|
||||
|
@ -49,7 +64,7 @@ namespace SourceGit.Commands
|
|||
private Models.Branch ParseLine(string line)
|
||||
{
|
||||
var parts = line.Split('\0');
|
||||
if (parts.Length != 5)
|
||||
if (parts.Length != 6)
|
||||
return null;
|
||||
|
||||
var branch = new Models.Branch();
|
||||
|
@ -83,14 +98,16 @@ namespace SourceGit.Commands
|
|||
}
|
||||
|
||||
branch.FullName = refName;
|
||||
branch.Head = parts[1];
|
||||
branch.IsCurrent = parts[2] == "*";
|
||||
branch.Upstream = parts[3];
|
||||
branch.IsUpsteamGone = false;
|
||||
branch.CommitterDate = ulong.Parse(parts[1]);
|
||||
branch.Head = parts[2];
|
||||
branch.IsCurrent = parts[3] == "*";
|
||||
branch.Upstream = parts[4];
|
||||
branch.IsUpstreamGone = false;
|
||||
|
||||
if (branch.IsLocal && !string.IsNullOrEmpty(parts[4]) && !parts[4].Equals("=", StringComparison.Ordinal))
|
||||
branch.TrackStatus = new QueryTrackStatus(WorkingDirectory, branch.Name, branch.Upstream).Result();
|
||||
else
|
||||
if (!branch.IsLocal ||
|
||||
string.IsNullOrEmpty(branch.Upstream) ||
|
||||
string.IsNullOrEmpty(parts[5]) ||
|
||||
parts[5].Equals("=", StringComparison.Ordinal))
|
||||
branch.TrackStatus = new Models.BranchTrackStatus();
|
||||
|
||||
return branch;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace SourceGit.Commands
|
||||
{
|
||||
|
@ -14,17 +15,21 @@ namespace SourceGit.Commands
|
|||
|
||||
public List<string> Result()
|
||||
{
|
||||
Exec();
|
||||
return _lines;
|
||||
}
|
||||
|
||||
protected override void OnReadline(string line)
|
||||
var rs = ReadToEnd();
|
||||
var outs = new List<string>();
|
||||
if (rs.IsSuccess)
|
||||
{
|
||||
var lines = rs.StdOut.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries);
|
||||
foreach (var line in lines)
|
||||
{
|
||||
if (line.Contains(_commit))
|
||||
_lines.Add(line.Substring(0, 40));
|
||||
outs.Add(line.Substring(0, 40));
|
||||
}
|
||||
}
|
||||
|
||||
return outs;
|
||||
}
|
||||
|
||||
private string _commit;
|
||||
private List<string> _lines = new List<string>();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,11 +26,7 @@ namespace SourceGit.Commands
|
|||
{
|
||||
search += $"-i --committer=\"{filter}\"";
|
||||
}
|
||||
else if (method == Models.CommitSearchMethod.ByFile)
|
||||
{
|
||||
search += $"-- \"{filter}\"";
|
||||
}
|
||||
else
|
||||
else if (method == Models.CommitSearchMethod.ByMessage)
|
||||
{
|
||||
var argsBuilder = new StringBuilder();
|
||||
argsBuilder.Append(search);
|
||||
|
@ -45,10 +41,18 @@ namespace SourceGit.Commands
|
|||
|
||||
search = argsBuilder.ToString();
|
||||
}
|
||||
else if (method == Models.CommitSearchMethod.ByFile)
|
||||
{
|
||||
search += $"-- \"{filter}\"";
|
||||
}
|
||||
else
|
||||
{
|
||||
search = $"-G\"{filter}\"";
|
||||
}
|
||||
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
Args = $"log -1000 --date-order --no-show-signature --decorate=full --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;
|
||||
}
|
||||
|
||||
|
|
|
@ -90,6 +90,6 @@ namespace SourceGit.Commands
|
|||
|
||||
private List<Models.InteractiveCommit> _commits = [];
|
||||
private Models.InteractiveCommit _current = null;
|
||||
private string _boundary = "";
|
||||
private readonly string _boundary;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,5 +35,39 @@ namespace SourceGit.Commands
|
|||
|
||||
return stream;
|
||||
}
|
||||
|
||||
public static Stream FromLFS(string repo, string oid, long size)
|
||||
{
|
||||
var starter = new ProcessStartInfo();
|
||||
starter.WorkingDirectory = repo;
|
||||
starter.FileName = Native.OS.GitExecutable;
|
||||
starter.Arguments = $"lfs smudge";
|
||||
starter.UseShellExecute = false;
|
||||
starter.CreateNoWindow = true;
|
||||
starter.WindowStyle = ProcessWindowStyle.Hidden;
|
||||
starter.RedirectStandardInput = true;
|
||||
starter.RedirectStandardOutput = true;
|
||||
|
||||
var stream = new MemoryStream();
|
||||
try
|
||||
{
|
||||
var proc = new Process() { StartInfo = starter };
|
||||
proc.Start();
|
||||
proc.StandardInput.WriteLine("version https://git-lfs.github.com/spec/v1");
|
||||
proc.StandardInput.WriteLine($"oid sha256:{oid}");
|
||||
proc.StandardInput.WriteLine($"size {size}");
|
||||
proc.StandardOutput.BaseStream.CopyTo(stream);
|
||||
proc.WaitForExit();
|
||||
proc.Close();
|
||||
|
||||
stream.Position = 0;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
App.RaiseException(repo, $"Failed to query file content: {e}");
|
||||
}
|
||||
|
||||
return stream;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,9 +16,6 @@ namespace SourceGit.Commands
|
|||
|
||||
public long Result()
|
||||
{
|
||||
if (_result != 0)
|
||||
return _result;
|
||||
|
||||
var rs = ReadToEnd();
|
||||
if (rs.IsSuccess)
|
||||
{
|
||||
|
@ -29,7 +26,5 @@ namespace SourceGit.Commands
|
|||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private readonly long _result = 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
using Avalonia.Threading;
|
||||
|
||||
namespace SourceGit.Commands
|
||||
{
|
||||
public partial class QueryLocalChanges : Command
|
||||
|
@ -18,15 +21,20 @@ namespace SourceGit.Commands
|
|||
|
||||
public List<Models.Change> Result()
|
||||
{
|
||||
Exec();
|
||||
return _changes;
|
||||
var outs = new List<Models.Change>();
|
||||
var rs = ReadToEnd();
|
||||
if (!rs.IsSuccess)
|
||||
{
|
||||
Dispatcher.UIThread.Post(() => App.RaiseException(Context, rs.StdErr));
|
||||
return outs;
|
||||
}
|
||||
|
||||
protected override void OnReadline(string line)
|
||||
var lines = rs.StdOut.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries);
|
||||
foreach (var line in lines)
|
||||
{
|
||||
var match = REG_FORMAT().Match(line);
|
||||
if (!match.Success)
|
||||
return;
|
||||
continue;
|
||||
|
||||
var change = new Models.Change() { Path = match.Groups[2].Value };
|
||||
var status = match.Groups[1].Value;
|
||||
|
@ -114,43 +122,44 @@ namespace SourceGit.Commands
|
|||
case "CD":
|
||||
change.Set(Models.ChangeState.Copied, Models.ChangeState.Deleted);
|
||||
break;
|
||||
case "DR":
|
||||
change.Set(Models.ChangeState.Deleted, Models.ChangeState.Renamed);
|
||||
break;
|
||||
case "DC":
|
||||
change.Set(Models.ChangeState.Deleted, Models.ChangeState.Copied);
|
||||
break;
|
||||
case "DD":
|
||||
change.Set(Models.ChangeState.Deleted, Models.ChangeState.Deleted);
|
||||
change.ConflictReason = Models.ConflictReason.BothDeleted;
|
||||
change.Set(Models.ChangeState.None, Models.ChangeState.Conflicted);
|
||||
break;
|
||||
case "AU":
|
||||
change.Set(Models.ChangeState.Added, Models.ChangeState.Unmerged);
|
||||
change.ConflictReason = Models.ConflictReason.AddedByUs;
|
||||
change.Set(Models.ChangeState.None, Models.ChangeState.Conflicted);
|
||||
break;
|
||||
case "UD":
|
||||
change.Set(Models.ChangeState.Unmerged, Models.ChangeState.Deleted);
|
||||
change.ConflictReason = Models.ConflictReason.DeletedByThem;
|
||||
change.Set(Models.ChangeState.None, Models.ChangeState.Conflicted);
|
||||
break;
|
||||
case "UA":
|
||||
change.Set(Models.ChangeState.Unmerged, Models.ChangeState.Added);
|
||||
change.ConflictReason = Models.ConflictReason.AddedByThem;
|
||||
change.Set(Models.ChangeState.None, Models.ChangeState.Conflicted);
|
||||
break;
|
||||
case "DU":
|
||||
change.Set(Models.ChangeState.Deleted, Models.ChangeState.Unmerged);
|
||||
change.ConflictReason = Models.ConflictReason.DeletedByUs;
|
||||
change.Set(Models.ChangeState.None, Models.ChangeState.Conflicted);
|
||||
break;
|
||||
case "AA":
|
||||
change.Set(Models.ChangeState.Added, Models.ChangeState.Added);
|
||||
change.ConflictReason = Models.ConflictReason.BothAdded;
|
||||
change.Set(Models.ChangeState.None, Models.ChangeState.Conflicted);
|
||||
break;
|
||||
case "UU":
|
||||
change.Set(Models.ChangeState.Unmerged, Models.ChangeState.Unmerged);
|
||||
change.ConflictReason = Models.ConflictReason.BothModified;
|
||||
change.Set(Models.ChangeState.None, Models.ChangeState.Conflicted);
|
||||
break;
|
||||
case "??":
|
||||
change.Set(Models.ChangeState.Untracked, Models.ChangeState.Untracked);
|
||||
change.Set(Models.ChangeState.None, Models.ChangeState.Untracked);
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
_changes.Add(change);
|
||||
if (change.Index != Models.ChangeState.None || change.WorkTree != Models.ChangeState.None)
|
||||
outs.Add(change);
|
||||
}
|
||||
|
||||
private readonly List<Models.Change> _changes = new List<Models.Change>();
|
||||
return outs;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace SourceGit.Commands
|
||||
|
@ -17,15 +18,17 @@ namespace SourceGit.Commands
|
|||
|
||||
public List<Models.Remote> Result()
|
||||
{
|
||||
Exec();
|
||||
return _loaded;
|
||||
}
|
||||
var outs = new List<Models.Remote>();
|
||||
var rs = ReadToEnd();
|
||||
if (!rs.IsSuccess)
|
||||
return outs;
|
||||
|
||||
protected override void OnReadline(string line)
|
||||
var lines = rs.StdOut.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries);
|
||||
foreach (var line in lines)
|
||||
{
|
||||
var match = REG_REMOTE().Match(line);
|
||||
if (!match.Success)
|
||||
return;
|
||||
continue;
|
||||
|
||||
var remote = new Models.Remote()
|
||||
{
|
||||
|
@ -33,11 +36,13 @@ namespace SourceGit.Commands
|
|||
URL = match.Groups[2].Value,
|
||||
};
|
||||
|
||||
if (_loaded.Find(x => x.Name == remote.Name) != null)
|
||||
return;
|
||||
_loaded.Add(remote);
|
||||
if (outs.Find(x => x.Name == remote.Name) != null)
|
||||
continue;
|
||||
|
||||
outs.Add(remote);
|
||||
}
|
||||
|
||||
private readonly List<Models.Remote> _loaded = new List<Models.Remote>();
|
||||
return outs;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,23 +6,25 @@ namespace SourceGit.Commands
|
|||
{
|
||||
public partial class QueryStagedChangesWithAmend : Command
|
||||
{
|
||||
[GeneratedRegex(@"^:[\d]{6} ([\d]{6}) ([0-9a-f]{40}) [0-9a-f]{40} ([ACDMTUX])\d{0,6}\t(.*)$")]
|
||||
[GeneratedRegex(@"^:[\d]{6} ([\d]{6}) ([0-9a-f]{40}) [0-9a-f]{40} ([ACDMT])\d{0,6}\t(.*)$")]
|
||||
private static partial Regex REG_FORMAT1();
|
||||
[GeneratedRegex(@"^:[\d]{6} ([\d]{6}) ([0-9a-f]{40}) [0-9a-f]{40} R\d{0,6}\t(.*\t.*)$")]
|
||||
private static partial Regex REG_FORMAT2();
|
||||
|
||||
public QueryStagedChangesWithAmend(string repo)
|
||||
public QueryStagedChangesWithAmend(string repo, string parent)
|
||||
{
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
Args = "diff-index --cached -M HEAD^";
|
||||
Args = $"diff-index --cached -M {parent}";
|
||||
_parent = parent;
|
||||
}
|
||||
|
||||
public List<Models.Change> Result()
|
||||
{
|
||||
var rs = ReadToEnd();
|
||||
if (rs.IsSuccess)
|
||||
{
|
||||
if (!rs.IsSuccess)
|
||||
return [];
|
||||
|
||||
var changes = new List<Models.Change>();
|
||||
var lines = rs.StdOut.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries);
|
||||
foreach (var line in lines)
|
||||
|
@ -37,6 +39,7 @@ namespace SourceGit.Commands
|
|||
{
|
||||
FileMode = match.Groups[1].Value,
|
||||
ObjectHash = match.Groups[2].Value,
|
||||
ParentSHA = _parent,
|
||||
},
|
||||
};
|
||||
change.Set(Models.ChangeState.Renamed);
|
||||
|
@ -54,6 +57,7 @@ namespace SourceGit.Commands
|
|||
{
|
||||
FileMode = match.Groups[1].Value,
|
||||
ObjectHash = match.Groups[2].Value,
|
||||
ParentSHA = _parent,
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -75,9 +79,6 @@ namespace SourceGit.Commands
|
|||
case "T":
|
||||
change.Set(Models.ChangeState.TypeChanged);
|
||||
break;
|
||||
case "U":
|
||||
change.Set(Models.ChangeState.Unmerged);
|
||||
break;
|
||||
}
|
||||
changes.Add(change);
|
||||
}
|
||||
|
@ -86,7 +87,6 @@ namespace SourceGit.Commands
|
|||
return changes;
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
private readonly string _parent;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,76 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace SourceGit.Commands
|
||||
{
|
||||
/// <summary>
|
||||
/// Query stash changes. Requires git >= 2.32.0
|
||||
/// </summary>
|
||||
public partial class QueryStashChanges : Command
|
||||
{
|
||||
[GeneratedRegex(@"^([MADC])\s+(.+)$")]
|
||||
private static partial Regex REG_FORMAT();
|
||||
[GeneratedRegex(@"^R[0-9]{0,4}\s+(.+)$")]
|
||||
private static partial Regex REG_RENAME_FORMAT();
|
||||
|
||||
public QueryStashChanges(string repo, string stash)
|
||||
{
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
Args = $"stash show -u --name-status \"{stash}\"";
|
||||
}
|
||||
|
||||
public List<Models.Change> Result()
|
||||
{
|
||||
var rs = ReadToEnd();
|
||||
if (!rs.IsSuccess)
|
||||
return [];
|
||||
|
||||
var lines = rs.StdOut.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries);
|
||||
var outs = new List<Models.Change>();
|
||||
foreach (var line in lines)
|
||||
{
|
||||
var match = REG_FORMAT().Match(line);
|
||||
if (!match.Success)
|
||||
{
|
||||
match = REG_RENAME_FORMAT().Match(line);
|
||||
if (match.Success)
|
||||
{
|
||||
var renamed = new Models.Change() { Path = match.Groups[1].Value };
|
||||
renamed.Set(Models.ChangeState.Renamed);
|
||||
outs.Add(renamed);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
var change = new Models.Change() { Path = match.Groups[2].Value };
|
||||
var status = match.Groups[1].Value;
|
||||
|
||||
switch (status[0])
|
||||
{
|
||||
case 'M':
|
||||
change.Set(Models.ChangeState.Modified);
|
||||
outs.Add(change);
|
||||
break;
|
||||
case 'A':
|
||||
change.Set(Models.ChangeState.Added);
|
||||
outs.Add(change);
|
||||
break;
|
||||
case 'D':
|
||||
change.Set(Models.ChangeState.Deleted);
|
||||
outs.Add(change);
|
||||
break;
|
||||
case 'C':
|
||||
change.Set(Models.ChangeState.Copied);
|
||||
outs.Add(change);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
outs.Sort((l, r) => string.Compare(l.Path, r.Path, StringComparison.Ordinal));
|
||||
return outs;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -9,52 +9,60 @@ namespace SourceGit.Commands
|
|||
{
|
||||
WorkingDirectory = repo;
|
||||
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()
|
||||
{
|
||||
Exec();
|
||||
return _stashes;
|
||||
}
|
||||
var outs = new List<Models.Stash>();
|
||||
var rs = ReadToEnd();
|
||||
if (!rs.IsSuccess)
|
||||
return outs;
|
||||
|
||||
protected override void OnReadline(string line)
|
||||
var items = rs.StdOut.Split('\0', StringSplitOptions.RemoveEmptyEntries);
|
||||
foreach (var item in items)
|
||||
{
|
||||
switch (_nextLineIdx)
|
||||
var current = new Models.Stash();
|
||||
|
||||
var nextPartIdx = 0;
|
||||
var start = 0;
|
||||
var end = item.IndexOf('\n', start);
|
||||
while (end > 0 && nextPartIdx < 4)
|
||||
{
|
||||
var line = item.Substring(start, end - start);
|
||||
|
||||
switch (nextPartIdx)
|
||||
{
|
||||
case 0:
|
||||
_current = new Models.Stash() { SHA = line };
|
||||
_stashes.Add(_current);
|
||||
current.SHA = line;
|
||||
break;
|
||||
case 1:
|
||||
ParseParent(line);
|
||||
if (line.Length > 6)
|
||||
current.Parents.AddRange(line.Split(' ', StringSplitOptions.RemoveEmptyEntries));
|
||||
break;
|
||||
case 2:
|
||||
_current.Time = ulong.Parse(line);
|
||||
current.Time = ulong.Parse(line);
|
||||
break;
|
||||
case 3:
|
||||
_current.Name = line;
|
||||
break;
|
||||
case 4:
|
||||
_current.Message = line;
|
||||
current.Name = line;
|
||||
break;
|
||||
}
|
||||
|
||||
_nextLineIdx++;
|
||||
if (_nextLineIdx > 4)
|
||||
_nextLineIdx = 0;
|
||||
nextPartIdx++;
|
||||
|
||||
start = end + 1;
|
||||
if (start >= item.Length - 1)
|
||||
break;
|
||||
|
||||
end = item.IndexOf('\n', start);
|
||||
}
|
||||
|
||||
private void ParseParent(string data)
|
||||
{
|
||||
if (data.Length < 8)
|
||||
return;
|
||||
if (start < item.Length)
|
||||
current.Message = item.Substring(start);
|
||||
|
||||
_current.Parents.AddRange(data.Split(separator: ' ', options: StringSplitOptions.RemoveEmptyEntries));
|
||||
outs.Add(current);
|
||||
}
|
||||
return outs;
|
||||
}
|
||||
|
||||
private readonly List<Models.Stash> _stashes = new List<Models.Stash>();
|
||||
private Models.Stash _current = null;
|
||||
private int _nextLineIdx = 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
|
@ -6,12 +7,12 @@ namespace SourceGit.Commands
|
|||
{
|
||||
public partial class QuerySubmodules : Command
|
||||
{
|
||||
[GeneratedRegex(@"^[\-\+ ][0-9a-f]+\s(.*)\s\(.*\)$")]
|
||||
private static partial Regex REG_FORMAT1();
|
||||
[GeneratedRegex(@"^[\-\+ ][0-9a-f]+\s(.*)$")]
|
||||
private static partial Regex REG_FORMAT2();
|
||||
[GeneratedRegex(@"^\s?[\w\?]{1,4}\s+(.+)$")]
|
||||
[GeneratedRegex(@"^([U\-\+ ])([0-9a-f]+)\s(.*?)(\s\(.*\))?$")]
|
||||
private static partial Regex REG_FORMAT_STATUS();
|
||||
[GeneratedRegex(@"^\s?[\w\?]{1,4}\s+(.+)$")]
|
||||
private static partial Regex REG_FORMAT_DIRTY();
|
||||
[GeneratedRegex(@"^submodule\.(\S*)\.(\w+)=(.*)$")]
|
||||
private static partial Regex REG_FORMAT_MODULE_INFO();
|
||||
|
||||
public QuerySubmodules(string repo)
|
||||
{
|
||||
|
@ -25,52 +26,117 @@ namespace SourceGit.Commands
|
|||
var submodules = new List<Models.Submodule>();
|
||||
var rs = ReadToEnd();
|
||||
|
||||
var builder = new StringBuilder();
|
||||
var lines = rs.StdOut.Split(['\r', '\n'], System.StringSplitOptions.RemoveEmptyEntries);
|
||||
foreach (var line in lines)
|
||||
{
|
||||
var match = REG_FORMAT1().Match(line);
|
||||
if (match.Success)
|
||||
{
|
||||
var path = match.Groups[1].Value;
|
||||
builder.Append($"\"{path}\" ");
|
||||
submodules.Add(new Models.Submodule() { Path = path });
|
||||
continue;
|
||||
}
|
||||
|
||||
match = REG_FORMAT2().Match(line);
|
||||
if (match.Success)
|
||||
{
|
||||
var path = match.Groups[1].Value;
|
||||
builder.Append($"\"{path}\" ");
|
||||
submodules.Add(new Models.Submodule() { Path = path });
|
||||
}
|
||||
}
|
||||
|
||||
if (submodules.Count > 0)
|
||||
{
|
||||
Args = $"--no-optional-locks status -uno --porcelain -- {builder}";
|
||||
rs = ReadToEnd();
|
||||
if (!rs.IsSuccess)
|
||||
return submodules;
|
||||
|
||||
var dirty = new HashSet<string>();
|
||||
lines = rs.StdOut.Split(['\r', '\n'], System.StringSplitOptions.RemoveEmptyEntries);
|
||||
var lines = rs.StdOut.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries);
|
||||
var map = new Dictionary<string, Models.Submodule>();
|
||||
var needCheckLocalChanges = false;
|
||||
foreach (var line in lines)
|
||||
{
|
||||
var match = REG_FORMAT_STATUS().Match(line);
|
||||
if (match.Success)
|
||||
{
|
||||
var path = match.Groups[1].Value;
|
||||
dirty.Add(path);
|
||||
var stat = match.Groups[1].Value;
|
||||
var sha = match.Groups[2].Value;
|
||||
var path = match.Groups[3].Value;
|
||||
|
||||
var module = new Models.Submodule() { Path = path, SHA = sha };
|
||||
switch (stat[0])
|
||||
{
|
||||
case '-':
|
||||
module.Status = Models.SubmoduleStatus.NotInited;
|
||||
break;
|
||||
case '+':
|
||||
module.Status = Models.SubmoduleStatus.RevisionChanged;
|
||||
break;
|
||||
case 'U':
|
||||
module.Status = Models.SubmoduleStatus.Unmerged;
|
||||
break;
|
||||
default:
|
||||
module.Status = Models.SubmoduleStatus.Normal;
|
||||
needCheckLocalChanges = true;
|
||||
break;
|
||||
}
|
||||
|
||||
map.Add(path, module);
|
||||
submodules.Add(module);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var submodule in submodules)
|
||||
submodule.IsDirty = dirty.Contains(submodule.Path);
|
||||
if (submodules.Count > 0)
|
||||
{
|
||||
Args = "config --file .gitmodules --list";
|
||||
rs = ReadToEnd();
|
||||
if (rs.IsSuccess)
|
||||
{
|
||||
var modules = new Dictionary<string, ModuleInfo>();
|
||||
lines = rs.StdOut.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries);
|
||||
foreach (var line in lines)
|
||||
{
|
||||
var match = REG_FORMAT_MODULE_INFO().Match(line);
|
||||
if (match.Success)
|
||||
{
|
||||
var name = match.Groups[1].Value;
|
||||
var key = match.Groups[2].Value;
|
||||
var val = match.Groups[3].Value;
|
||||
|
||||
if (!modules.TryGetValue(name, out var m))
|
||||
{
|
||||
m = new ModuleInfo();
|
||||
modules.Add(name, m);
|
||||
}
|
||||
|
||||
if (key.Equals("path", StringComparison.Ordinal))
|
||||
m.Path = val;
|
||||
else if (key.Equals("url", StringComparison.Ordinal))
|
||||
m.URL = val;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var kv in modules)
|
||||
{
|
||||
if (map.TryGetValue(kv.Value.Path, out var m))
|
||||
m.URL = kv.Value.URL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (needCheckLocalChanges)
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
foreach (var kv in map)
|
||||
{
|
||||
if (kv.Value.Status == Models.SubmoduleStatus.Normal)
|
||||
{
|
||||
builder.Append('"');
|
||||
builder.Append(kv.Key);
|
||||
builder.Append("\" ");
|
||||
}
|
||||
}
|
||||
|
||||
Args = $"--no-optional-locks status --porcelain -- {builder}";
|
||||
rs = ReadToEnd();
|
||||
if (!rs.IsSuccess)
|
||||
return submodules;
|
||||
|
||||
lines = rs.StdOut.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries);
|
||||
foreach (var line in lines)
|
||||
{
|
||||
var match = REG_FORMAT_DIRTY().Match(line);
|
||||
if (match.Success)
|
||||
{
|
||||
var path = match.Groups[1].Value;
|
||||
if (map.TryGetValue(path, out var m))
|
||||
m.Status = Models.SubmoduleStatus.Modified;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return submodules;
|
||||
}
|
||||
|
||||
private class ModuleInfo
|
||||
{
|
||||
public string Path { get; set; } = string.Empty;
|
||||
public string URL { get; set; } = string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ namespace SourceGit.Commands
|
|||
|
||||
Context = repo;
|
||||
WorkingDirectory = repo;
|
||||
Args = $"tag -l --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()
|
||||
|
@ -24,17 +24,22 @@ namespace SourceGit.Commands
|
|||
var records = rs.StdOut.Split(_boundary, StringSplitOptions.RemoveEmptyEntries);
|
||||
foreach (var record in records)
|
||||
{
|
||||
var subs = record.Split('\0', StringSplitOptions.None);
|
||||
if (subs.Length != 5)
|
||||
var subs = record.Split('\0');
|
||||
if (subs.Length != 6)
|
||||
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()
|
||||
{
|
||||
Name = subs[0].Substring(10),
|
||||
SHA = string.IsNullOrEmpty(subs[2]) ? subs[1] : subs[2],
|
||||
CreatorDate = ulong.Parse(subs[3]),
|
||||
Message = string.IsNullOrEmpty(message) ? null : message,
|
||||
Name = name,
|
||||
IsAnnotated = subs[1].Equals("tag", StringComparison.Ordinal),
|
||||
SHA = string.IsNullOrEmpty(subs[3]) ? subs[2] : subs[3],
|
||||
CreatorDate = ulong.Parse(subs[4]),
|
||||
Message = message,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
40
src/Commands/QueryUpdatableSubmodules.cs
Normal file
40
src/Commands/QueryUpdatableSubmodules.cs
Normal file
|
@ -0,0 +1,40 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace SourceGit.Commands
|
||||
{
|
||||
public partial class QueryUpdatableSubmodules : Command
|
||||
{
|
||||
[GeneratedRegex(@"^([U\-\+ ])([0-9a-f]+)\s(.*?)(\s\(.*\))?$")]
|
||||
private static partial Regex REG_FORMAT_STATUS();
|
||||
|
||||
public QueryUpdatableSubmodules(string repo)
|
||||
{
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
Args = "submodule status";
|
||||
}
|
||||
|
||||
public List<string> Result()
|
||||
{
|
||||
var submodules = new List<string>();
|
||||
var rs = ReadToEnd();
|
||||
|
||||
var lines = rs.StdOut.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries);
|
||||
foreach (var line in lines)
|
||||
{
|
||||
var match = REG_FORMAT_STATUS().Match(line);
|
||||
if (match.Success)
|
||||
{
|
||||
var stat = match.Groups[1].Value;
|
||||
var path = match.Groups[3].Value;
|
||||
if (!stat.StartsWith(' '))
|
||||
submodules.Add(path);
|
||||
}
|
||||
}
|
||||
|
||||
return submodules;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,33 +1,7 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace SourceGit.Commands
|
||||
namespace SourceGit.Commands
|
||||
{
|
||||
public class Reset : Command
|
||||
{
|
||||
public Reset(string repo)
|
||||
{
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
Args = "reset";
|
||||
}
|
||||
|
||||
public Reset(string repo, List<Models.Change> changes)
|
||||
{
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
|
||||
var builder = new StringBuilder();
|
||||
builder.Append("reset --");
|
||||
foreach (var c in changes)
|
||||
{
|
||||
builder.Append(" \"");
|
||||
builder.Append(c.Path);
|
||||
builder.Append("\"");
|
||||
}
|
||||
Args = builder.ToString();
|
||||
}
|
||||
|
||||
public Reset(string repo, string revision, string mode)
|
||||
{
|
||||
WorkingDirectory = repo;
|
||||
|
|
|
@ -1,29 +1,52 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Text;
|
||||
|
||||
namespace SourceGit.Commands
|
||||
{
|
||||
public class Restore : Command
|
||||
{
|
||||
public Restore(string repo)
|
||||
/// <summary>
|
||||
/// Only used for single staged change.
|
||||
/// </summary>
|
||||
/// <param name="repo"></param>
|
||||
/// <param name="stagedChange"></param>
|
||||
public Restore(string repo, Models.Change stagedChange)
|
||||
{
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
Args = "restore . --source=HEAD --staged --worktree --recurse-submodules";
|
||||
|
||||
var builder = new StringBuilder();
|
||||
builder.Append("restore --staged -- \"");
|
||||
builder.Append(stagedChange.Path);
|
||||
builder.Append('"');
|
||||
|
||||
if (stagedChange.Index == Models.ChangeState.Renamed)
|
||||
{
|
||||
builder.Append(" \"");
|
||||
builder.Append(stagedChange.OriginalPath);
|
||||
builder.Append('"');
|
||||
}
|
||||
|
||||
public Restore(string repo, List<string> files, string extra)
|
||||
Args = builder.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Restore changes given in a path-spec file.
|
||||
/// </summary>
|
||||
/// <param name="repo"></param>
|
||||
/// <param name="pathspecFile"></param>
|
||||
/// <param name="isStaged"></param>
|
||||
public Restore(string repo, string pathspecFile, bool isStaged)
|
||||
{
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
|
||||
StringBuilder builder = new StringBuilder();
|
||||
var builder = new StringBuilder();
|
||||
builder.Append("restore ");
|
||||
if (!string.IsNullOrEmpty(extra))
|
||||
builder.Append(extra).Append(" ");
|
||||
builder.Append("--");
|
||||
foreach (var f in files)
|
||||
builder.Append(' ').Append('"').Append(f).Append('"');
|
||||
builder.Append(isStaged ? "--staged " : "--worktree --recurse-submodules ");
|
||||
builder.Append("--pathspec-from-file=\"");
|
||||
builder.Append(pathspecFile);
|
||||
builder.Append('"');
|
||||
|
||||
Args = builder.ToString();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,15 +10,15 @@ namespace SourceGit.Commands
|
|||
{
|
||||
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();
|
||||
if (isLFSFiltered)
|
||||
{
|
||||
var tmpFile = saveTo + ".tmp";
|
||||
if (ExecCmd(repo, $"show {revision}:\"{file}\"", tmpFile))
|
||||
{
|
||||
ExecCmd(repo, $"lfs smudge", saveTo, tmpFile);
|
||||
}
|
||||
File.Delete(tmpFile);
|
||||
var pointerStream = QueryFileContent.Run(repo, revision, file);
|
||||
ExecCmd(repo, $"lfs smudge", saveTo, pointerStream);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -26,7 +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();
|
||||
starter.WorkingDirectory = repo;
|
||||
|
@ -45,27 +45,11 @@ namespace SourceGit.Commands
|
|||
{
|
||||
var proc = new Process() { StartInfo = starter };
|
||||
proc.Start();
|
||||
|
||||
if (inputFile != null)
|
||||
{
|
||||
using (StreamReader sr = new StreamReader(inputFile))
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
var line = sr.ReadLine();
|
||||
if (line == null)
|
||||
break;
|
||||
proc.StandardInput.WriteLine(line);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (input != null)
|
||||
proc.StandardInput.Write(new StreamReader(input).ReadToEnd());
|
||||
proc.StandardOutput.BaseStream.CopyTo(sw);
|
||||
proc.WaitForExit();
|
||||
var rs = proc.ExitCode == 0;
|
||||
proc.Close();
|
||||
|
||||
return rs;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
@ -73,7 +57,6 @@ namespace SourceGit.Commands
|
|||
{
|
||||
App.RaiseException(repo, "Save file failed: " + e.Message);
|
||||
});
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
using System;
|
||||
using System;
|
||||
|
||||
namespace SourceGit.Commands
|
||||
{
|
||||
|
@ -40,7 +40,7 @@ namespace SourceGit.Commands
|
|||
if (dateEndIdx == -1)
|
||||
return;
|
||||
|
||||
var dateStr = line.Substring(0, dateEndIdx);
|
||||
var dateStr = line.AsSpan(0, dateEndIdx);
|
||||
if (double.TryParse(dateStr, out var date))
|
||||
statistics.AddCommit(line.Substring(dateEndIdx + 1), date);
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace SourceGit.Commands
|
||||
{
|
||||
|
@ -10,10 +11,9 @@ namespace SourceGit.Commands
|
|||
Context = repo;
|
||||
}
|
||||
|
||||
public bool Add(string url, string relativePath, bool recursive, Action<string> outputHandler)
|
||||
public bool Add(string url, string relativePath, bool recursive)
|
||||
{
|
||||
_outputHandler = outputHandler;
|
||||
Args = $"submodule add {url} \"{relativePath}\"";
|
||||
Args = $"-c protocol.file.allow=always submodule add \"{url}\" \"{relativePath}\"";
|
||||
if (!Exec())
|
||||
return false;
|
||||
|
||||
|
@ -29,38 +29,38 @@ namespace SourceGit.Commands
|
|||
}
|
||||
}
|
||||
|
||||
public bool Update(string module, bool init, bool recursive, bool useRemote, Action<string> outputHandler)
|
||||
public bool Update(List<string> modules, bool init, bool recursive, bool useRemote = false)
|
||||
{
|
||||
Args = "submodule update";
|
||||
var builder = new StringBuilder();
|
||||
builder.Append("submodule update");
|
||||
|
||||
if (init)
|
||||
Args += " --init";
|
||||
builder.Append(" --init");
|
||||
if (recursive)
|
||||
Args += " --recursive";
|
||||
builder.Append(" --recursive");
|
||||
if (useRemote)
|
||||
Args += " --remote";
|
||||
if (!string.IsNullOrEmpty(module))
|
||||
Args += $" -- \"{module}\"";
|
||||
builder.Append(" --remote");
|
||||
if (modules.Count > 0)
|
||||
{
|
||||
builder.Append(" --");
|
||||
foreach (var module in modules)
|
||||
builder.Append($" \"{module}\"");
|
||||
}
|
||||
|
||||
_outputHandler = outputHandler;
|
||||
Args = builder.ToString();
|
||||
return Exec();
|
||||
}
|
||||
|
||||
public bool Delete(string relativePath)
|
||||
public bool Deinit(string module, bool force)
|
||||
{
|
||||
Args = $"submodule deinit -f \"{relativePath}\"";
|
||||
if (!Exec())
|
||||
return false;
|
||||
|
||||
Args = $"rm -rf \"{relativePath}\"";
|
||||
Args = force ? $"submodule deinit -f -- \"{module}\"" : $"submodule deinit -- \"{module}\"";
|
||||
return Exec();
|
||||
}
|
||||
|
||||
protected override void OnReadline(string line)
|
||||
public bool Delete(string module)
|
||||
{
|
||||
_outputHandler?.Invoke(line);
|
||||
Args = $"rm -rf \"{module}\"";
|
||||
return Exec();
|
||||
}
|
||||
|
||||
private Action<string> _outputHandler;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,57 +1,51 @@
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.IO;
|
||||
|
||||
namespace SourceGit.Commands
|
||||
{
|
||||
public static class Tag
|
||||
{
|
||||
public static bool Add(string repo, string name, string basedOn)
|
||||
public static bool Add(string repo, string name, string basedOn, Models.ICommandLog log)
|
||||
{
|
||||
var cmd = new Command();
|
||||
cmd.WorkingDirectory = repo;
|
||||
cmd.Context = repo;
|
||||
cmd.Args = $"tag {name} {basedOn}";
|
||||
cmd.Args = $"tag --no-sign {name} {basedOn}";
|
||||
cmd.Log = log;
|
||||
return cmd.Exec();
|
||||
}
|
||||
|
||||
public static bool Add(string repo, string name, string basedOn, string message, bool sign)
|
||||
public static bool Add(string repo, string name, string basedOn, string message, bool sign, Models.ICommandLog log)
|
||||
{
|
||||
var param = sign ? "--sign -a" : "--no-sign -a";
|
||||
var cmd = new Command();
|
||||
cmd.WorkingDirectory = repo;
|
||||
cmd.Context = repo;
|
||||
cmd.Args = $"tag {param} {name} {basedOn} ";
|
||||
cmd.Log = log;
|
||||
|
||||
if (!string.IsNullOrEmpty(message))
|
||||
{
|
||||
string tmp = Path.GetTempFileName();
|
||||
File.WriteAllText(tmp, message);
|
||||
cmd.Args += $"-F \"{tmp}\"";
|
||||
}
|
||||
else
|
||||
{
|
||||
cmd.Args += $"-m {name}";
|
||||
|
||||
var succ = cmd.Exec();
|
||||
File.Delete(tmp);
|
||||
return succ;
|
||||
}
|
||||
|
||||
cmd.Args += $"-m {name}";
|
||||
return cmd.Exec();
|
||||
}
|
||||
|
||||
public static bool Delete(string repo, string name, List<Models.Remote> remotes)
|
||||
public static bool Delete(string repo, string name, Models.ICommandLog log)
|
||||
{
|
||||
var cmd = new Command();
|
||||
cmd.WorkingDirectory = repo;
|
||||
cmd.Context = repo;
|
||||
cmd.Args = $"tag --delete {name}";
|
||||
if (!cmd.Exec())
|
||||
return false;
|
||||
|
||||
if (remotes != null)
|
||||
{
|
||||
foreach (var r in remotes)
|
||||
new Push(repo, r.Name, $"refs/tags/{name}", true).Exec();
|
||||
}
|
||||
|
||||
return true;
|
||||
cmd.Log = log;
|
||||
return cmd.Exec();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,13 +23,11 @@ namespace SourceGit.Commands
|
|||
_patchBuilder.Append(c.DataForAmend.ObjectHash);
|
||||
_patchBuilder.Append("\t");
|
||||
_patchBuilder.Append(c.OriginalPath);
|
||||
_patchBuilder.Append("\n");
|
||||
}
|
||||
else if (c.Index == Models.ChangeState.Added)
|
||||
{
|
||||
_patchBuilder.Append("0 0000000000000000000000000000000000000000\t");
|
||||
_patchBuilder.Append(c.Path);
|
||||
_patchBuilder.Append("\n");
|
||||
}
|
||||
else if (c.Index == Models.ChangeState.Deleted)
|
||||
{
|
||||
|
@ -37,7 +35,6 @@ namespace SourceGit.Commands
|
|||
_patchBuilder.Append(c.DataForAmend.ObjectHash);
|
||||
_patchBuilder.Append("\t");
|
||||
_patchBuilder.Append(c.Path);
|
||||
_patchBuilder.Append("\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -46,8 +43,9 @@ namespace SourceGit.Commands
|
|||
_patchBuilder.Append(c.DataForAmend.ObjectHash);
|
||||
_patchBuilder.Append("\t");
|
||||
_patchBuilder.Append(c.Path);
|
||||
_patchBuilder.Append("\n");
|
||||
}
|
||||
|
||||
_patchBuilder.Append("\n");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,23 +0,0 @@
|
|||
using System;
|
||||
|
||||
namespace SourceGit.Commands
|
||||
{
|
||||
public class UpdateRef : Command
|
||||
{
|
||||
public UpdateRef(string repo, string refName, string toRevision, Action<string> outputHandler)
|
||||
{
|
||||
_outputHandler = outputHandler;
|
||||
|
||||
WorkingDirectory = repo;
|
||||
Context = repo;
|
||||
Args = $"update-ref {refName} {toRevision}";
|
||||
}
|
||||
|
||||
protected override void OnReadline(string line)
|
||||
{
|
||||
_outputHandler?.Invoke(line);
|
||||
}
|
||||
|
||||
private Action<string> _outputHandler;
|
||||
}
|
||||
}
|
|
@ -56,7 +56,7 @@ namespace SourceGit.Commands
|
|||
return worktrees;
|
||||
}
|
||||
|
||||
public bool Add(string fullpath, string name, bool createNew, string tracking, Action<string> outputHandler)
|
||||
public bool Add(string fullpath, string name, bool createNew, string tracking)
|
||||
{
|
||||
Args = "worktree add ";
|
||||
|
||||
|
@ -78,14 +78,12 @@ namespace SourceGit.Commands
|
|||
else if (!string.IsNullOrEmpty(name) && !createNew)
|
||||
Args += name;
|
||||
|
||||
_outputHandler = outputHandler;
|
||||
return Exec();
|
||||
}
|
||||
|
||||
public bool Prune(Action<string> outputHandler)
|
||||
public bool Prune()
|
||||
{
|
||||
Args = "worktree prune -v";
|
||||
_outputHandler = outputHandler;
|
||||
return Exec();
|
||||
}
|
||||
|
||||
|
@ -101,22 +99,14 @@ namespace SourceGit.Commands
|
|||
return Exec();
|
||||
}
|
||||
|
||||
public bool Remove(string fullpath, bool force, Action<string> outputHandler)
|
||||
public bool Remove(string fullpath, bool force)
|
||||
{
|
||||
if (force)
|
||||
Args = $"worktree remove -f \"{fullpath}\"";
|
||||
else
|
||||
Args = $"worktree remove \"{fullpath}\"";
|
||||
|
||||
_outputHandler = outputHandler;
|
||||
return Exec();
|
||||
}
|
||||
|
||||
protected override void OnReadline(string line)
|
||||
{
|
||||
_outputHandler?.Invoke(line);
|
||||
}
|
||||
|
||||
private Action<string> _outputHandler = null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,8 +7,11 @@ namespace SourceGit.Converters
|
|||
{
|
||||
public static class ListConverters
|
||||
{
|
||||
public static readonly FuncValueConverter<IList, string> Count =
|
||||
new FuncValueConverter<IList, string>(v => v == null ? "0" : $"{v.Count}");
|
||||
|
||||
public static readonly FuncValueConverter<IList, string> ToCount =
|
||||
new FuncValueConverter<IList, string>(v => v == null ? " (0)" : $" ({v.Count})");
|
||||
new FuncValueConverter<IList, string>(v => v == null ? "(0)" : $"({v.Count})");
|
||||
|
||||
public static readonly FuncValueConverter<IList, bool> IsNullOrEmpty =
|
||||
new FuncValueConverter<IList, bool>(v => v == null || v.Count == 0);
|
||||
|
|
27
src/Converters/ObjectConverters.cs
Normal file
27
src/Converters/ObjectConverters.cs
Normal file
|
@ -0,0 +1,27 @@
|
|||
using System;
|
||||
using System.Globalization;
|
||||
using Avalonia.Data.Converters;
|
||||
|
||||
namespace SourceGit.Converters
|
||||
{
|
||||
public static class ObjectConverters
|
||||
{
|
||||
public class IsTypeOfConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
if (value == null || parameter == null)
|
||||
return false;
|
||||
|
||||
return value.GetType().IsAssignableTo((Type)parameter);
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
return new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
public static readonly IsTypeOfConverter IsTypeOf = new IsTypeOfConverter();
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
using System;
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
using Avalonia.Data.Converters;
|
||||
|
@ -22,7 +22,7 @@ namespace SourceGit.Converters
|
|||
var home = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
|
||||
var prefixLen = home.EndsWith('/') ? home.Length - 1 : home.Length;
|
||||
if (v.StartsWith(home, StringComparison.Ordinal))
|
||||
return "~" + v.Substring(prefixLen);
|
||||
return $"~{v.AsSpan(prefixLen)}";
|
||||
|
||||
return v;
|
||||
});
|
||||
|
|
|
@ -17,7 +17,7 @@ namespace SourceGit.Models
|
|||
{
|
||||
public interface IAvatarHost
|
||||
{
|
||||
void OnAvatarResourceChanged(string email);
|
||||
void OnAvatarResourceChanged(string email, Bitmap image);
|
||||
}
|
||||
|
||||
public partial class AvatarManager
|
||||
|
@ -26,10 +26,7 @@ namespace SourceGit.Models
|
|||
{
|
||||
get
|
||||
{
|
||||
if (_instance == null)
|
||||
_instance = new AvatarManager();
|
||||
|
||||
return _instance;
|
||||
return _instance ??= new AvatarManager();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -38,7 +35,7 @@ namespace SourceGit.Models
|
|||
[GeneratedRegex(@"^(?:(\d+)\+)?(.+?)@.+\.github\.com$")]
|
||||
private static partial Regex REG_GITHUB_USER_EMAIL();
|
||||
|
||||
private object _synclock = new object();
|
||||
private readonly Lock _synclock = new();
|
||||
private string _storePath;
|
||||
private List<IAvatarHost> _avatars = new List<IAvatarHost>();
|
||||
private Dictionary<string, Bitmap> _resources = new Dictionary<string, Bitmap>();
|
||||
|
@ -119,7 +116,7 @@ namespace SourceGit.Models
|
|||
Dispatcher.UIThread.InvokeAsync(() =>
|
||||
{
|
||||
_resources[email] = img;
|
||||
NotifyResourceChanged(email);
|
||||
NotifyResourceChanged(email, img);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -144,14 +141,13 @@ namespace SourceGit.Models
|
|||
if (_defaultAvatars.Contains(email))
|
||||
return null;
|
||||
|
||||
if (_resources.ContainsKey(email))
|
||||
_resources.Remove(email);
|
||||
|
||||
var localFile = Path.Combine(_storePath, GetEmailHash(email));
|
||||
if (File.Exists(localFile))
|
||||
File.Delete(localFile);
|
||||
|
||||
NotifyResourceChanged(email);
|
||||
NotifyResourceChanged(email, null);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -179,13 +175,40 @@ namespace SourceGit.Models
|
|||
|
||||
lock (_synclock)
|
||||
{
|
||||
if (!_requesting.Contains(email))
|
||||
_requesting.Add(email);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
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);
|
||||
foreach (var c in hash)
|
||||
builder.Append(c.ToString("x2"));
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
private void NotifyResourceChanged(string email)
|
||||
private void NotifyResourceChanged(string email, Bitmap image)
|
||||
{
|
||||
foreach (var avatar in _avatars)
|
||||
{
|
||||
avatar.OnAvatarResourceChanged(email);
|
||||
}
|
||||
avatar.OnAvatarResourceChanged(email, image);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
35
src/Models/Bisect.cs
Normal file
35
src/Models/Bisect.cs
Normal file
|
@ -0,0 +1,35 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace SourceGit.Models
|
||||
{
|
||||
public enum BisectState
|
||||
{
|
||||
None = 0,
|
||||
WaitingForRange,
|
||||
Detecting,
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum BisectCommitFlag
|
||||
{
|
||||
None = 0,
|
||||
Good = 1 << 0,
|
||||
Bad = 1 << 1,
|
||||
}
|
||||
|
||||
public class Bisect
|
||||
{
|
||||
public HashSet<string> Bads
|
||||
{
|
||||
get;
|
||||
set;
|
||||
} = [];
|
||||
|
||||
public HashSet<string> Goods
|
||||
{
|
||||
get;
|
||||
set;
|
||||
} = [];
|
||||
}
|
||||
}
|
|
@ -23,10 +23,17 @@ namespace SourceGit.Models
|
|||
}
|
||||
}
|
||||
|
||||
public enum BranchSortMode
|
||||
{
|
||||
Name = 0,
|
||||
CommitterDate,
|
||||
}
|
||||
|
||||
public class Branch
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string FullName { get; set; }
|
||||
public ulong CommitterDate { get; set; }
|
||||
public string Head { get; set; }
|
||||
public bool IsLocal { get; set; }
|
||||
public bool IsCurrent { get; set; }
|
||||
|
@ -34,7 +41,7 @@ namespace SourceGit.Models
|
|||
public string Upstream { get; set; }
|
||||
public BranchTrackStatus TrackStatus { get; set; }
|
||||
public string Remote { get; set; }
|
||||
public bool IsUpsteamGone { get; set; }
|
||||
public bool IsUpstreamGone { get; set; }
|
||||
|
||||
public string FriendlyName => IsLocal ? Name : $"{Remote}/{Name}";
|
||||
}
|
||||
|
|
|
@ -18,14 +18,27 @@ namespace SourceGit.Models
|
|||
Deleted,
|
||||
Renamed,
|
||||
Copied,
|
||||
Unmerged,
|
||||
Untracked
|
||||
Untracked,
|
||||
Conflicted,
|
||||
}
|
||||
|
||||
public enum ConflictReason
|
||||
{
|
||||
None,
|
||||
BothDeleted,
|
||||
AddedByUs,
|
||||
DeletedByThem,
|
||||
AddedByThem,
|
||||
DeletedByUs,
|
||||
BothAdded,
|
||||
BothModified,
|
||||
}
|
||||
|
||||
public class ChangeDataForAmend
|
||||
{
|
||||
public string FileMode { get; set; } = "";
|
||||
public string ObjectHash { get; set; } = "";
|
||||
public string ParentSHA { get; set; } = "";
|
||||
}
|
||||
|
||||
public class Change
|
||||
|
@ -35,20 +48,14 @@ namespace SourceGit.Models
|
|||
public string Path { get; set; } = "";
|
||||
public string OriginalPath { get; set; } = "";
|
||||
public ChangeDataForAmend DataForAmend { get; set; } = null;
|
||||
public ConflictReason ConflictReason { get; set; } = ConflictReason.None;
|
||||
|
||||
public bool IsConflict
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Index == ChangeState.Unmerged || WorkTree == ChangeState.Unmerged)
|
||||
return true;
|
||||
if (Index == ChangeState.Added && WorkTree == ChangeState.Added)
|
||||
return true;
|
||||
if (Index == ChangeState.Deleted && WorkTree == ChangeState.Deleted)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
public bool IsConflicted => WorkTree == ChangeState.Conflicted;
|
||||
public string ConflictMarker => CONFLICT_MARKERS[(int)ConflictReason];
|
||||
public string ConflictDesc => CONFLICT_DESCS[(int)ConflictReason];
|
||||
|
||||
public string WorkTreeDesc => TYPE_DESCS[(int)WorkTree];
|
||||
public string IndexDesc => TYPE_DESCS[(int)Index];
|
||||
|
||||
public void Set(ChangeState index, ChangeState workTree = ChangeState.None)
|
||||
{
|
||||
|
@ -76,8 +83,44 @@ namespace SourceGit.Models
|
|||
|
||||
if (Path[0] == '"')
|
||||
Path = Path.Substring(1, Path.Length - 2);
|
||||
|
||||
if (!string.IsNullOrEmpty(OriginalPath) && OriginalPath[0] == '"')
|
||||
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,
|
||||
ByMessage,
|
||||
ByFile,
|
||||
ByContent,
|
||||
}
|
||||
|
||||
public class Commit
|
||||
{
|
||||
// As retrieved by: git mktree </dev/null
|
||||
public const string EmptyTreeSHA1 = "4b825dc642cb6eb9a060e54bf8d69288fbee4904";
|
||||
|
||||
public static double OpacityForNotMerged
|
||||
{
|
||||
get;
|
||||
|
@ -29,14 +33,14 @@ namespace SourceGit.Models
|
|||
public User Committer { get; set; } = User.Invalid;
|
||||
public ulong CommitterTime { get; set; } = 0;
|
||||
public string Subject { get; set; } = string.Empty;
|
||||
public List<string> Parents { get; set; } = new List<string>();
|
||||
public List<Decorator> Decorators { get; set; } = new List<Decorator>();
|
||||
public List<string> Parents { get; set; } = new();
|
||||
public List<Decorator> Decorators { get; set; } = new();
|
||||
public bool HasDecorators => Decorators.Count > 0;
|
||||
|
||||
public string AuthorTimeStr => DateTime.UnixEpoch.AddSeconds(AuthorTime).ToLocalTime().ToString(DateTimeFormat.Actived.DateTime);
|
||||
public string CommitterTimeStr => DateTime.UnixEpoch.AddSeconds(CommitterTime).ToLocalTime().ToString(DateTimeFormat.Actived.DateTime);
|
||||
public string AuthorTimeShortStr => DateTime.UnixEpoch.AddSeconds(AuthorTime).ToLocalTime().ToString(DateTimeFormat.Actived.DateOnly);
|
||||
public string CommitterTimeShortStr => DateTime.UnixEpoch.AddSeconds(CommitterTime).ToLocalTime().ToString(DateTimeFormat.Actived.DateOnly);
|
||||
public string AuthorTimeStr => DateTime.UnixEpoch.AddSeconds(AuthorTime).ToLocalTime().ToString(DateTimeFormat.Active.DateTime);
|
||||
public string CommitterTimeStr => DateTime.UnixEpoch.AddSeconds(CommitterTime).ToLocalTime().ToString(DateTimeFormat.Active.DateTime);
|
||||
public string AuthorTimeShortStr => DateTime.UnixEpoch.AddSeconds(AuthorTime).ToLocalTime().ToString(DateTimeFormat.Active.DateOnly);
|
||||
public string CommitterTimeShortStr => DateTime.UnixEpoch.AddSeconds(CommitterTime).ToLocalTime().ToString(DateTimeFormat.Active.DateOnly);
|
||||
|
||||
public bool IsMerged { get; set; } = false;
|
||||
public bool IsCommitterVisible => !Author.Equals(Committer) || AuthorTime != CommitterTime;
|
||||
|
@ -45,7 +49,7 @@ namespace SourceGit.Models
|
|||
public int Color { get; set; } = 0;
|
||||
public double Opacity => IsMerged ? 1 : OpacityForNotMerged;
|
||||
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 void ParseDecorators(string data)
|
||||
|
@ -109,7 +113,7 @@ namespace SourceGit.Models
|
|||
if (l.Type != r.Type)
|
||||
return (int)l.Type - (int)r.Type;
|
||||
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 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 halfWidth = 6;
|
||||
const double unitHeight = 28;
|
||||
const double halfHeight = 14;
|
||||
const double unitHeight = 1;
|
||||
const double halfHeight = 0.5;
|
||||
|
||||
var temp = new CommitGraph();
|
||||
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 string Name { 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
|
||||
{
|
||||
public partial class CommitTemplate : ObservableObject
|
||||
public class CommitTemplate : ObservableObject
|
||||
{
|
||||
public string Name
|
||||
{
|
||||
|
|
|
@ -4,23 +4,28 @@ namespace SourceGit.Models
|
|||
{
|
||||
public class ConventionalCommitType
|
||||
{
|
||||
public string Type { get; set; } = string.Empty;
|
||||
public string Description { get; set; } = string.Empty;
|
||||
public string Name { get; set; }
|
||||
public string Type { get; set; }
|
||||
public string Description { get; set; }
|
||||
|
||||
public static readonly List<ConventionalCommitType> Supported = new List<ConventionalCommitType>()
|
||||
{
|
||||
new ConventionalCommitType("feat", "Adding a new feature"),
|
||||
new ConventionalCommitType("fix", "Fixing a bug"),
|
||||
new ConventionalCommitType("docs", "Updating documentation"),
|
||||
new ConventionalCommitType("style", "Elements or code styles without changing the code logic"),
|
||||
new ConventionalCommitType("test", "Adding or updating tests"),
|
||||
new ConventionalCommitType("chore", "Making changes to the build process or auxiliary tools and libraries"),
|
||||
new ConventionalCommitType("revert", "Undoing a previous commit"),
|
||||
new ConventionalCommitType("refactor", "Restructuring code without changing its external behavior")
|
||||
};
|
||||
public static readonly List<ConventionalCommitType> Supported = [
|
||||
new("Features", "feat", "Adding a new feature"),
|
||||
new("Bug Fixes", "fix", "Fixing a bug"),
|
||||
new("Work In Progress", "wip", "Still being developed and not yet complete"),
|
||||
new("Reverts", "revert", "Undoing a previous commit"),
|
||||
new("Code Refactoring", "refactor", "Restructuring code without changing its external behavior"),
|
||||
new("Performance Improvements", "perf", "Improves performance"),
|
||||
new("Builds", "build", "Changes that affect the build system or external dependencies"),
|
||||
new("Continuous Integrations", "ci", "Changes to CI configuration files and scripts"),
|
||||
new("Documentations", "docs", "Updating documentation"),
|
||||
new("Styles", "style", "Elements or code styles without changing the code logic"),
|
||||
new("Tests", "test", "Adding or updating tests"),
|
||||
new("Chores", "chore", "Other changes that don't modify src or test files"),
|
||||
];
|
||||
|
||||
public ConventionalCommitType(string type, string description)
|
||||
public ConventionalCommitType(string name, string type, string description)
|
||||
{
|
||||
Name = name;
|
||||
Type = type;
|
||||
Description = description;
|
||||
}
|
||||
|
|
19
src/Models/Count.cs
Normal file
19
src/Models/Count.cs
Normal file
|
@ -0,0 +1,19 @@
|
|||
using System;
|
||||
|
||||
namespace SourceGit.Models
|
||||
{
|
||||
public class Count : IDisposable
|
||||
{
|
||||
public int Value { get; set; } = 0;
|
||||
|
||||
public Count(int value)
|
||||
{
|
||||
Value = value;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
}
|
|
@ -25,7 +25,7 @@ namespace SourceGit.Models
|
|||
set;
|
||||
} = 0;
|
||||
|
||||
public static DateTimeFormat Actived
|
||||
public static DateTimeFormat Active
|
||||
{
|
||||
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
|
||||
{
|
||||
/// <summary>
|
||||
/// Enable `--ignore-cr-at-eol` by default?
|
||||
/// </summary>
|
||||
public static bool IgnoreCRAtEOL
|
||||
{
|
||||
get;
|
||||
set;
|
||||
} = true;
|
||||
|
||||
public Change WorkingCopyChange => _workingCopyChange;
|
||||
public bool IsUnstaged => _isUnstaged;
|
||||
public List<string> Revisions => _revisions;
|
||||
|
@ -40,7 +49,7 @@ namespace SourceGit.Models
|
|||
else
|
||||
{
|
||||
if (change.DataForAmend != null)
|
||||
_extra = "--cached HEAD^";
|
||||
_extra = $"--cached {change.DataForAmend.ParentSHA}";
|
||||
else
|
||||
_extra = "--cached";
|
||||
|
||||
|
@ -56,7 +65,7 @@ namespace SourceGit.Models
|
|||
/// <param name="change"></param>
|
||||
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(commit.SHA);
|
||||
_path = change.Path;
|
||||
|
@ -70,7 +79,7 @@ namespace SourceGit.Models
|
|||
/// <param name="file"></param>
|
||||
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(commit.SHA);
|
||||
_path = file;
|
||||
|
@ -115,6 +124,6 @@ namespace SourceGit.Models
|
|||
private readonly string _path;
|
||||
private readonly string _orgPath = 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.RegularExpressions;
|
||||
|
||||
|
@ -16,11 +16,10 @@ namespace SourceGit.Models
|
|||
Deleted,
|
||||
}
|
||||
|
||||
public class TextInlineRange
|
||||
public class TextInlineRange(int p, int n)
|
||||
{
|
||||
public int Start { get; set; }
|
||||
public int End { get; set; }
|
||||
public TextInlineRange(int p, int n) { Start = p; End = p + n - 1; }
|
||||
public int Start { get; set; } = p;
|
||||
public int End { get; set; } = p + n - 1;
|
||||
}
|
||||
|
||||
public class TextDiffLine
|
||||
|
@ -30,6 +29,7 @@ namespace SourceGit.Models
|
|||
public int OldLineNumber { get; set; } = 0;
|
||||
public int NewLineNumber { get; set; } = 0;
|
||||
public List<TextInlineRange> Highlights { get; set; } = new List<TextInlineRange>();
|
||||
public bool NoNewLineEndOfFile { get; set; } = false;
|
||||
|
||||
public string OldLine => OldLineNumber == 0 ? string.Empty : OldLineNumber.ToString();
|
||||
public string NewLine => NewLineNumber == 0 ? string.Empty : NewLineNumber.ToString();
|
||||
|
@ -146,7 +146,7 @@ namespace SourceGit.Models
|
|||
public void GenerateNewPatchFromSelection(Change change, string fileBlobGuid, TextDiffSelection selection, bool revert, string output)
|
||||
{
|
||||
var isTracked = !string.IsNullOrEmpty(fileBlobGuid);
|
||||
var fileGuid = isTracked ? fileBlobGuid.Substring(0, 8) : "00000000";
|
||||
var fileGuid = isTracked ? fileBlobGuid : "00000000";
|
||||
|
||||
var builder = new StringBuilder();
|
||||
builder.Append("diff --git a/").Append(change.Path).Append(" b/").Append(change.Path).Append('\n');
|
||||
|
@ -555,17 +555,7 @@ namespace SourceGit.Models
|
|||
}
|
||||
else if (test.Type == TextDiffLineType.Added)
|
||||
{
|
||||
if (i < start - 1)
|
||||
{
|
||||
if (revert)
|
||||
{
|
||||
newCount++;
|
||||
oldCount++;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (isOldSide)
|
||||
if (i < start - 1 || isOldSide)
|
||||
{
|
||||
if (revert)
|
||||
{
|
||||
|
@ -577,7 +567,6 @@ namespace SourceGit.Models
|
|||
{
|
||||
newCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if (i == end - 1 && tailed)
|
||||
{
|
||||
|
@ -654,9 +643,7 @@ namespace SourceGit.Models
|
|||
public string NewImageSize => New != null ? $"{New.PixelSize.Width} x {New.PixelSize.Height}" : "0 x 0";
|
||||
}
|
||||
|
||||
public class NoOrEOLChange
|
||||
{
|
||||
}
|
||||
public class NoOrEOLChange;
|
||||
|
||||
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(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(11, "meld", "Meld", "Meld.exe", "\"$LOCAL\" \"$BASE\" \"$REMOTE\" --output \"$MERGED\"", "\"$LOCAL\" \"$REMOTE\""),
|
||||
};
|
||||
}
|
||||
else if (OperatingSystem.IsMacOS())
|
||||
|
|
|
@ -107,8 +107,7 @@ namespace SourceGit.Models
|
|||
// 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)
|
||||
|
|
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>
|
||||
/// The minimal version of Git that required by this app.
|
||||
/// </summary>
|
||||
public static readonly System.Version MINIMAL = new System.Version(2, 23, 0);
|
||||
|
||||
/// <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);
|
||||
public static readonly System.Version MINIMAL = new(2, 25, 1);
|
||||
|
||||
/// <summary>
|
||||
/// The minimal version of Git that supports the `stash push` command with the `--pathspec-from-file` option.
|
||||
/// </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>
|
||||
/// The minimal version of Git that supports the `stash push` command with the `--staged` option.
|
||||
/// </summary>
|
||||
public static readonly System.Version STASH_PUSH_ONLY_STAGED = new System.Version(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);
|
||||
public static readonly System.Version STASH_PUSH_ONLY_STAGED = new(2, 35, 0);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,29 +0,0 @@
|
|||
namespace SourceGit.Models
|
||||
{
|
||||
public class Hyperlink
|
||||
{
|
||||
public int Start { get; set; } = 0;
|
||||
public int Length { get; set; } = 0;
|
||||
public string Link { get; set; } = "";
|
||||
public bool IsCommitSHA { get; set; } = false;
|
||||
|
||||
public Hyperlink(int start, int length, string link, bool isCommitSHA = false)
|
||||
{
|
||||
Start = start;
|
||||
Length = length;
|
||||
Link = link;
|
||||
IsCommitSHA = isCommitSHA;
|
||||
}
|
||||
|
||||
public bool Intersect(int start, int length)
|
||||
{
|
||||
if (start == Start)
|
||||
return true;
|
||||
|
||||
if (start < Start)
|
||||
return start + length > Start;
|
||||
|
||||
return start < Start + Length;
|
||||
}
|
||||
}
|
||||
}
|
7
src/Models/ICommandLog.cs
Normal file
7
src/Models/ICommandLog.cs
Normal file
|
@ -0,0 +1,7 @@
|
|||
namespace SourceGit.Models
|
||||
{
|
||||
public interface ICommandLog
|
||||
{
|
||||
void AppendLine(string line);
|
||||
}
|
||||
}
|
|
@ -2,6 +2,8 @@
|
|||
{
|
||||
public interface IRepository
|
||||
{
|
||||
bool MayHaveSubmodules();
|
||||
|
||||
void RefreshBranches();
|
||||
void RefreshWorktrees();
|
||||
void RefreshTags();
|
||||
|
|
10
src/Models/ImageDecoder.cs
Normal file
10
src/Models/ImageDecoder.cs
Normal file
|
@ -0,0 +1,10 @@
|
|||
namespace SourceGit.Models
|
||||
{
|
||||
public enum ImageDecoder
|
||||
{
|
||||
None = 0,
|
||||
Builtin,
|
||||
Pfim,
|
||||
Tiff,
|
||||
}
|
||||
}
|
37
src/Models/InlineElement.cs
Normal file
37
src/Models/InlineElement.cs
Normal file
|
@ -0,0 +1,37 @@
|
|||
namespace SourceGit.Models
|
||||
{
|
||||
public enum InlineElementType
|
||||
{
|
||||
Keyword = 0,
|
||||
Link,
|
||||
CommitSHA,
|
||||
Code,
|
||||
}
|
||||
|
||||
public class InlineElement
|
||||
{
|
||||
public InlineElementType Type { get; }
|
||||
public int Start { get; }
|
||||
public int Length { get; }
|
||||
public string Link { get; }
|
||||
|
||||
public InlineElement(InlineElementType type, int start, int length, string link)
|
||||
{
|
||||
Type = type;
|
||||
Start = start;
|
||||
Length = length;
|
||||
Link = link;
|
||||
}
|
||||
|
||||
public bool IsIntersecting(int start, int length)
|
||||
{
|
||||
if (start == Start)
|
||||
return true;
|
||||
|
||||
if (start < Start)
|
||||
return start + length > Start;
|
||||
|
||||
return start < Start + Length;
|
||||
}
|
||||
}
|
||||
}
|
38
src/Models/InlineElementCollector.cs
Normal file
38
src/Models/InlineElementCollector.cs
Normal file
|
@ -0,0 +1,38 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace SourceGit.Models
|
||||
{
|
||||
public class InlineElementCollector
|
||||
{
|
||||
public int Count => _implementation.Count;
|
||||
public InlineElement this[int index] => _implementation[index];
|
||||
|
||||
public InlineElement Intersect(int start, int length)
|
||||
{
|
||||
foreach (var elem in _implementation)
|
||||
{
|
||||
if (elem.IsIntersecting(start, length))
|
||||
return elem;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public void Add(InlineElement element)
|
||||
{
|
||||
_implementation.Add(element);
|
||||
}
|
||||
|
||||
public void Sort()
|
||||
{
|
||||
_implementation.Sort((l, r) => l.Start.CompareTo(r.Start));
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
_implementation.Clear();
|
||||
}
|
||||
|
||||
private readonly List<InlineElement> _implementation = [];
|
||||
}
|
||||
}
|
|
@ -27,6 +27,8 @@ namespace SourceGit.Models
|
|||
|
||||
public class InteractiveRebaseJobCollection
|
||||
{
|
||||
public string OrigHead { get; set; } = string.Empty;
|
||||
public string Onto { get; set; } = string.Empty;
|
||||
public List<InteractiveRebaseJob> Jobs { get; set; } = new List<InteractiveRebaseJob>();
|
||||
}
|
||||
}
|
||||
|
|
100
src/Models/IpcChannel.cs
Normal file
100
src/Models/IpcChannel.cs
Normal file
|
@ -0,0 +1,100 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.IO.Pipes;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SourceGit.Models
|
||||
{
|
||||
public class IpcChannel : IDisposable
|
||||
{
|
||||
public bool IsFirstInstance { get; }
|
||||
|
||||
public event Action<string> MessageReceived;
|
||||
|
||||
public IpcChannel()
|
||||
{
|
||||
try
|
||||
{
|
||||
_singletonLock = File.Open(Path.Combine(Native.OS.DataDir, "process.lock"), FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None);
|
||||
IsFirstInstance = true;
|
||||
_server = new NamedPipeServerStream(
|
||||
"SourceGitIPCChannel" + Environment.UserName,
|
||||
PipeDirection.In,
|
||||
-1,
|
||||
PipeTransmissionMode.Byte,
|
||||
PipeOptions.Asynchronous | PipeOptions.CurrentUserOnly);
|
||||
_cancellationTokenSource = new CancellationTokenSource();
|
||||
Task.Run(StartServer);
|
||||
}
|
||||
catch
|
||||
{
|
||||
IsFirstInstance = false;
|
||||
}
|
||||
}
|
||||
|
||||
public void SendToFirstInstance(string cmd)
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var client = new NamedPipeClientStream(".", "SourceGitIPCChannel" + Environment.UserName, PipeDirection.Out, PipeOptions.Asynchronous | PipeOptions.CurrentUserOnly))
|
||||
{
|
||||
client.Connect(1000);
|
||||
if (!client.IsConnected)
|
||||
return;
|
||||
|
||||
using (var writer = new StreamWriter(client))
|
||||
{
|
||||
writer.WriteLine(cmd);
|
||||
writer.Flush();
|
||||
}
|
||||
|
||||
if (OperatingSystem.IsWindows())
|
||||
client.WaitForPipeDrain();
|
||||
else
|
||||
Thread.Sleep(1000);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// IGNORE
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_cancellationTokenSource?.Cancel();
|
||||
_singletonLock?.Dispose();
|
||||
}
|
||||
|
||||
private async void StartServer()
|
||||
{
|
||||
using var reader = new StreamReader(_server);
|
||||
|
||||
while (!_cancellationTokenSource.IsCancellationRequested)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _server.WaitForConnectionAsync(_cancellationTokenSource.Token);
|
||||
|
||||
if (!_cancellationTokenSource.IsCancellationRequested)
|
||||
{
|
||||
var line = await reader.ReadToEndAsync(_cancellationTokenSource.Token);
|
||||
MessageReceived?.Invoke(line?.Trim());
|
||||
}
|
||||
|
||||
_server.Disconnect();
|
||||
}
|
||||
catch
|
||||
{
|
||||
if (!_cancellationTokenSource.IsCancellationRequested && _server.IsConnected)
|
||||
_server.Disconnect();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private FileStream _singletonLock = null;
|
||||
private NamedPipeServerStream _server = null;
|
||||
private CancellationTokenSource _cancellationTokenSource = null;
|
||||
}
|
||||
}
|
|
@ -1,5 +1,4 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
|
||||
|
@ -46,7 +45,7 @@ namespace SourceGit.Models
|
|||
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))
|
||||
return;
|
||||
|
@ -60,17 +59,7 @@ namespace SourceGit.Models
|
|||
|
||||
var start = match.Index;
|
||||
var len = match.Length;
|
||||
var intersect = false;
|
||||
foreach (var exist in outs)
|
||||
{
|
||||
if (exist.Intersect(start, len))
|
||||
{
|
||||
intersect = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (intersect)
|
||||
if (outs.Intersect(start, len) != null)
|
||||
continue;
|
||||
|
||||
var link = _urlTemplate;
|
||||
|
@ -81,8 +70,7 @@ namespace SourceGit.Models
|
|||
link = link.Replace($"${j}", group.Value);
|
||||
}
|
||||
|
||||
var range = new Hyperlink(start, len, link);
|
||||
outs.Add(range);
|
||||
outs.Add(new InlineElement(InlineElementType.Link, start, len, link));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 long Size { get; set; } = 0;
|
||||
|
||||
public static LFSObject Parse(string content)
|
||||
{
|
||||
var match = REG_FORMAT().Match(content);
|
||||
if (match.Success)
|
||||
return new() { Oid = match.Groups[1].Value, Size = long.Parse(match.Groups[2].Value) };
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ namespace SourceGit.Models
|
|||
new Locale("Français", "fr_FR"),
|
||||
new Locale("Italiano", "it_IT"),
|
||||
new Locale("Português (Brasil)", "pt_BR"),
|
||||
new Locale("Українська", "uk_UA"),
|
||||
new Locale("Русский", "ru_RU"),
|
||||
new Locale("简体中文", "zh_CN"),
|
||||
new Locale("繁體中文", "zh_TW"),
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
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
|
||||
{
|
||||
|
@ -10,52 +12,35 @@
|
|||
int marker1 = 0;
|
||||
int marker2 = 0;
|
||||
|
||||
char[] tmp1 = new char[len1];
|
||||
char[] tmp2 = new char[len2];
|
||||
|
||||
while (marker1 < len1 && marker2 < len2)
|
||||
{
|
||||
char c1 = s1[marker1];
|
||||
char c2 = s2[marker2];
|
||||
int loc1 = 0;
|
||||
int loc2 = 0;
|
||||
|
||||
bool isDigit1 = char.IsDigit(c1);
|
||||
bool isDigit2 = char.IsDigit(c2);
|
||||
if (isDigit1 != isDigit2)
|
||||
return c1.CompareTo(c2);
|
||||
|
||||
do
|
||||
{
|
||||
tmp1[loc1] = c1;
|
||||
loc1++;
|
||||
marker1++;
|
||||
int subLen1 = 1;
|
||||
while (marker1 + subLen1 < len1 && char.IsDigit(s1[marker1 + subLen1]) == isDigit1)
|
||||
subLen1++;
|
||||
|
||||
if (marker1 < len1)
|
||||
c1 = s1[marker1];
|
||||
else
|
||||
break;
|
||||
} while (char.IsDigit(c1) == isDigit1);
|
||||
int subLen2 = 1;
|
||||
while (marker2 + subLen2 < len2 && char.IsDigit(s2[marker2 + subLen2]) == isDigit2)
|
||||
subLen2++;
|
||||
|
||||
do
|
||||
{
|
||||
tmp2[loc2] = c2;
|
||||
loc2++;
|
||||
marker2++;
|
||||
string sub1 = s1.Substring(marker1, subLen1);
|
||||
string sub2 = s2.Substring(marker2, subLen2);
|
||||
|
||||
if (marker2 < len2)
|
||||
c2 = s2[marker2];
|
||||
else
|
||||
break;
|
||||
} while (char.IsDigit(c2) == isDigit2);
|
||||
marker1 += subLen1;
|
||||
marker2 += subLen2;
|
||||
|
||||
string sub1 = new string(tmp1, 0, loc1);
|
||||
string sub2 = new string(tmp2, 0, loc2);
|
||||
int result;
|
||||
if (isDigit1)
|
||||
result = loc1 == loc2 ? string.CompareOrdinal(sub1, sub2) : loc1 - loc2;
|
||||
result = (subLen1 == subLen2) ? string.CompareOrdinal(sub1, sub2) : (subLen1 - subLen2);
|
||||
else
|
||||
result = string.CompareOrdinal(sub1, sub2);
|
||||
result = string.Compare(sub1, sub2, StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
if (result != 0)
|
||||
return result;
|
||||
|
|
|
@ -6,8 +6,10 @@ namespace SourceGit.Models
|
|||
{
|
||||
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();
|
||||
[GeneratedRegex(@"^git://[^/]+/.+[^/\.]$")]
|
||||
private static partial Regex REG_GIT();
|
||||
[GeneratedRegex(@"^[\w\-]+@[\w\.\-]+(\:[0-9]+)?:([a-zA-z0-9~%][\w\-\./~%]*)?[a-zA-Z0-9](\.git)?$")]
|
||||
private static partial Regex REG_SSH1();
|
||||
[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 = [
|
||||
REG_HTTPS(),
|
||||
REG_GIT(),
|
||||
REG_SSH1(),
|
||||
REG_SSH2(),
|
||||
];
|
||||
|
@ -30,13 +33,10 @@ namespace SourceGit.Models
|
|||
if (string.IsNullOrWhiteSpace(url))
|
||||
return false;
|
||||
|
||||
for (int i = 1; i < URL_FORMATS.Length; i++)
|
||||
{
|
||||
if (URL_FORMATS[i].IsMatch(url))
|
||||
if (REG_SSH1().IsMatch(url))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
return REG_SSH2().IsMatch(url);
|
||||
}
|
||||
|
||||
public static bool IsValidURL(string url)
|
||||
|
@ -50,7 +50,10 @@ namespace SourceGit.Models
|
|||
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)
|
||||
|
|
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