mirror of
https://github.com/tj/git-extras.git
synced 2026-09-11 07:56:18 -04:00
Compare commits
1825 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4cf28ec559 | ||
|
|
09b77e8ed6 | ||
|
|
4c38988ba9 | ||
|
|
dbf68bac59 | ||
|
|
03569856db | ||
|
|
d11e45f0b1 | ||
|
|
fd2e03cf55 | ||
|
|
5a14757944 | ||
|
|
b450b36036 | ||
|
|
7f36d37283 | ||
|
|
184d37e980 | ||
|
|
e92878b81b | ||
|
|
b9e1d88f2c | ||
|
|
99207eee8e | ||
|
|
9d6319becb | ||
|
|
2bdeb59a73 | ||
|
|
c5a0fdf660 | ||
|
|
8d1c970031 | ||
|
|
c06fad59d2 | ||
|
|
65dd36ae62 | ||
|
|
967e339f91 | ||
|
|
ae3d30fb6e | ||
|
|
032090e847 | ||
|
|
5367101f2e | ||
|
|
6ed2643252 | ||
|
|
b9bd309a87 | ||
|
|
6f4cf0c1bb | ||
|
|
f5c48e4842 | ||
|
|
daf9d4148e | ||
|
|
eb34655aea | ||
|
|
1f1320f4d6 | ||
|
|
4d05955bc5 | ||
|
|
b57eb8b92a | ||
|
|
cf6d40d446 | ||
|
|
14586c7da0 | ||
|
|
5c9a7a2533 | ||
|
|
215382b12d | ||
|
|
13cbdfc51e | ||
|
|
3ddc315a76 | ||
|
|
c63b46a33b | ||
|
|
d4cd272556 | ||
|
|
783812b627 | ||
|
|
c49ca70814 | ||
|
|
18ecffef5e | ||
|
|
aadf5f874b | ||
|
|
18d1017325 | ||
|
|
a841845d9a | ||
|
|
8eed5f49ad | ||
|
|
64687599ae | ||
|
|
8ce63003c0 | ||
|
|
1a9b0c2ab4 | ||
|
|
82cc37d0f5 | ||
|
|
df53711fc7 | ||
|
|
e691826b4b | ||
|
|
fdaca2cd26 | ||
|
|
3db275cbe2 | ||
|
|
1345c5977d | ||
|
|
2f4b57d52b | ||
|
|
c23da8ae9c | ||
|
|
f1f50ac5ba | ||
|
|
0989b773de | ||
|
|
65203312c1 | ||
|
|
a2c424aada | ||
|
|
d291b2775c | ||
|
|
fb0bc6950b | ||
|
|
21a086c009 | ||
|
|
8eb1d72f07 | ||
|
|
233686d2a2 | ||
|
|
5c3fb1b062 | ||
|
|
5ca9360109 | ||
|
|
16d1445271 | ||
|
|
4d2d393142 | ||
|
|
ffee88a246 | ||
|
|
da03b7b133 | ||
|
|
f0671e2733 | ||
|
|
2c161aae02 | ||
|
|
f0b9e18e54 | ||
|
|
c32869de37 | ||
|
|
e1a4e7195d | ||
|
|
b766a655ce | ||
|
|
e34dea9b7a | ||
|
|
3bac05e690 | ||
|
|
a7ad306a6b | ||
|
|
dcb8bd6c54 | ||
|
|
a1bf09e8e0 | ||
|
|
615ea971cb | ||
|
|
e692b9619a | ||
|
|
a17d2fb15d | ||
|
|
52897f73b9 | ||
|
|
5f19424a50 | ||
|
|
b54d8c7c50 | ||
|
|
9049967232 | ||
|
|
cdbcef3227 | ||
|
|
a4b637346d | ||
|
|
1843afb38a | ||
|
|
28b48c7432 | ||
|
|
2c2bd07693 | ||
|
|
e39b1ab6a6 | ||
|
|
058cb0763e | ||
|
|
3d964687fc | ||
|
|
ca18c6e552 | ||
|
|
cf9fe2d23f | ||
|
|
c13ab52138 | ||
|
|
58c5b50514 | ||
|
|
5020890c34 | ||
|
|
4d8060e8cb | ||
|
|
7a65311b1c | ||
|
|
186bbdcb87 | ||
|
|
bfef91f30a | ||
|
|
78c5ca28a0 | ||
|
|
90221f2149 | ||
|
|
263dcb4a79 | ||
|
|
cc1c0e1a38 | ||
|
|
04bf403861 | ||
|
|
5fe4226865 | ||
|
|
bddb987383 | ||
|
|
df1997c0dc | ||
|
|
954ba1fb7c | ||
|
|
e4152db482 | ||
|
|
dee7c7f1d0 | ||
|
|
e793f8979b | ||
|
|
d2dde69d14 | ||
|
|
49650eda62 | ||
|
|
76cd9bdf45 | ||
|
|
f95e339c03 | ||
|
|
db71be578e | ||
|
|
cb29c62c65 | ||
|
|
47e940272a | ||
|
|
8af43892f6 | ||
|
|
34609bd800 | ||
|
|
0b5d2cce55 | ||
|
|
d7fb4495f1 | ||
|
|
5e543cb620 | ||
|
|
3a258cbaad | ||
|
|
ba9e6f0c5c | ||
|
|
5d7ed18c50 | ||
|
|
2d221c1b67 | ||
|
|
f5bcbe5b0d | ||
|
|
6d73f74f83 | ||
|
|
bbf90c4623 | ||
|
|
2fca5edb7d | ||
|
|
d7f0fcc546 | ||
|
|
df825715ba | ||
|
|
2d5b12c01f | ||
|
|
753555744b | ||
|
|
d5eaa1895b | ||
|
|
b372bab52a | ||
|
|
a008308267 | ||
|
|
5d0fe32617 | ||
|
|
75d0dbff6d | ||
|
|
9c3343d1ee | ||
|
|
82daeb7c94 | ||
|
|
73fbf8e08d | ||
|
|
36e6de10c8 | ||
|
|
e1138b8190 | ||
|
|
589ef33e35 | ||
|
|
e4e7310c49 | ||
|
|
4611b24320 | ||
|
|
04eb5c04e6 | ||
|
|
e29dcbb023 | ||
|
|
d34fa527a0 | ||
|
|
35100b4e6f | ||
|
|
cd348dad21 | ||
|
|
aea25d2d4b | ||
|
|
4fff03f429 | ||
|
|
7c4152ce83 | ||
|
|
960ec8d2fd | ||
|
|
1426002037 | ||
|
|
193a6dd2a8 | ||
|
|
2d1aa6daea | ||
|
|
71af8cec42 | ||
|
|
3ac3c54b06 | ||
|
|
584e0283f5 | ||
|
|
62aa91f221 | ||
|
|
25b5d734c7 | ||
|
|
06aa5dbb1f | ||
|
|
8058b1718f | ||
|
|
956781ac1b | ||
|
|
3b9ea70e09 | ||
|
|
6e6f59b264 | ||
|
|
01e2494294 | ||
|
|
e3c8359e4e | ||
|
|
9accb6bb04 | ||
|
|
66961ad491 | ||
|
|
81665422b2 | ||
|
|
48ef95d104 | ||
|
|
bf240df083 | ||
|
|
d597f684b1 | ||
|
|
7acc1de7fa | ||
|
|
8a6e39d589 | ||
|
|
006549d661 | ||
|
|
233b3f0dd4 | ||
|
|
fdf89cd93f | ||
|
|
95c54f6cdb | ||
|
|
34237934d4 | ||
|
|
8c744569c4 | ||
|
|
458cb9fdad | ||
|
|
12b27415ea | ||
|
|
0701c9e4d7 | ||
|
|
42fd8a9e72 | ||
|
|
311edadd55 | ||
|
|
d8dd44d8eb | ||
|
|
41df8dc7f3 | ||
|
|
48beb58670 | ||
|
|
5e07a96c57 | ||
|
|
3ef09dac72 | ||
|
|
799aa6e19a | ||
|
|
aa6b547f55 | ||
|
|
f334dff09c | ||
|
|
055110dd35 | ||
|
|
745c7ab500 | ||
|
|
7cb5c55e13 | ||
|
|
0d7334a63f | ||
|
|
5754b81047 | ||
|
|
2aadd3ed41 | ||
|
|
d2a09c1753 | ||
|
|
c7756455cb | ||
|
|
4fcd233bf2 | ||
|
|
a73c19c14b | ||
|
|
6bf2b0bc4c | ||
|
|
d17f02979e | ||
|
|
716459f79d | ||
|
|
02263ca949 | ||
|
|
613b909765 | ||
|
|
f556430122 | ||
|
|
9390d450aa | ||
|
|
3d6f9456a3 | ||
|
|
27ee8f0132 | ||
|
|
678a0c2e71 | ||
|
|
11a2801231 | ||
|
|
d7565ee844 | ||
|
|
02bbdba792 | ||
|
|
f513f58d9e | ||
|
|
c036c9cc22 | ||
|
|
42458d838b | ||
|
|
b2794c18a1 | ||
|
|
760428fa46 | ||
|
|
76a3c1f28d | ||
|
|
2025ebfb96 | ||
|
|
5467c2ff5a | ||
|
|
843b958c9a | ||
|
|
7aa178acc9 | ||
|
|
2d722c93b8 | ||
|
|
b772251074 | ||
|
|
777fcade17 | ||
|
|
e370b19190 | ||
|
|
82b33ac4c7 | ||
|
|
fff5fde917 | ||
|
|
edffba3c2a | ||
|
|
11640196b2 | ||
|
|
6b0ef19caf | ||
|
|
d914ed3a89 | ||
|
|
b2743d785f | ||
|
|
2370a4e27b | ||
|
|
2fdd664594 | ||
|
|
b33ec65e5f | ||
|
|
3f2ad3fa8f | ||
|
|
e0101470ca | ||
|
|
86bb1a6764 | ||
|
|
96890821b0 | ||
|
|
0c0be92445 | ||
|
|
cc9b972e25 | ||
|
|
760e64b864 | ||
|
|
f6fa554077 | ||
|
|
bc8876b063 | ||
|
|
c30ac80d72 | ||
|
|
4de7b34c9f | ||
|
|
0edcf8b7e6 | ||
|
|
5feb0db212 | ||
|
|
703a8481c6 | ||
|
|
3413eeb058 | ||
|
|
a7b7f05b5b | ||
|
|
a07c5ece5d | ||
|
|
296f2a7ad2 | ||
|
|
e9708ba9f4 | ||
|
|
3c91de7a1e | ||
|
|
3c632d9e32 | ||
|
|
3821b2c537 | ||
|
|
ffad35c8db | ||
|
|
d28ad34bcb | ||
|
|
b8e58ee9ba | ||
|
|
b225ed4934 | ||
|
|
30383f9551 | ||
|
|
1f72512ef0 | ||
|
|
b8b02d8ef1 | ||
|
|
d8b1007f68 | ||
|
|
64993dbc7f | ||
|
|
4a4c5296f3 | ||
|
|
206fe7ccfb | ||
|
|
f1eab6a8a7 | ||
|
|
cd8fe4f6e2 | ||
|
|
bebcbd11ca | ||
|
|
fb1e5db6d7 | ||
|
|
910242c92b | ||
|
|
48b2c8fd37 | ||
|
|
d86abec6a2 | ||
|
|
1894f453f6 | ||
|
|
c35c84a279 | ||
|
|
a62a67ef5f | ||
|
|
d0c40851c0 | ||
|
|
52ada403ef | ||
|
|
b9786c763f | ||
|
|
6880198fda | ||
|
|
687a5607dc | ||
|
|
7b1a062e93 | ||
|
|
6a15a5339b | ||
|
|
a424d0915e | ||
|
|
458727bafb | ||
|
|
d7b527c1ae | ||
|
|
0fad3cfd03 | ||
|
|
7d9e05062e | ||
|
|
c8d9374491 | ||
|
|
f3028db073 | ||
|
|
d73bfbb98b | ||
|
|
42e1f9d5c6 | ||
|
|
b7841ad3aa | ||
|
|
4ea6cf0221 | ||
|
|
8f60fb29f4 | ||
|
|
96b95392ac | ||
|
|
16c1f0d3dc | ||
|
|
c675d0afb8 | ||
|
|
4c82bbecc3 | ||
|
|
faa56b1bcb | ||
|
|
e6caa931a5 | ||
|
|
6b45d4322b | ||
|
|
e92d54cadb | ||
|
|
444722cf38 | ||
|
|
b2b5c97105 | ||
|
|
d3772eb469 | ||
|
|
41732e4113 | ||
|
|
18bcfcd38e | ||
|
|
aa5a380996 | ||
|
|
68e6a15401 | ||
|
|
05ce91fb9e | ||
|
|
d2a159ed25 | ||
|
|
f66f7e484f | ||
|
|
fca42774f8 | ||
|
|
ef13ed06a3 | ||
|
|
fa35cdab99 | ||
|
|
8ed7da3f02 | ||
|
|
54f8205478 | ||
|
|
19531065ca | ||
|
|
4f5b5ccd48 | ||
|
|
ad8c77bfa3 | ||
|
|
8490a50304 | ||
|
|
aa174b3905 | ||
|
|
c006e5ece8 | ||
|
|
171d06965e | ||
|
|
6d9f861d0e | ||
|
|
041e3fd8da | ||
|
|
35d544b8c6 | ||
|
|
da34968ec3 | ||
|
|
35d44c97cc | ||
|
|
317eea64db | ||
|
|
2ef6ea4b96 | ||
|
|
c41f0164cc | ||
|
|
3cc866dd97 | ||
|
|
2929c41637 | ||
|
|
64e76db350 | ||
|
|
74fe4f6aba | ||
|
|
2e610158a8 | ||
|
|
65be1e5f8f | ||
|
|
9683fb6510 | ||
|
|
964663f1a4 | ||
|
|
e9668cc04b | ||
|
|
8a487271cb | ||
|
|
1a4cd4ca3d | ||
|
|
e116406ff1 | ||
|
|
34ba1eb083 | ||
|
|
6a455e7d29 | ||
|
|
6afc2fb4a8 | ||
|
|
c56b61d2bd | ||
|
|
c386733efd | ||
|
|
ecf0f4847f | ||
|
|
e031dc54e9 | ||
|
|
8e519e3e66 | ||
|
|
c52ba59d10 | ||
|
|
9ee5d06469 | ||
|
|
ec417833ee | ||
|
|
b4b81e290e | ||
|
|
c92f03d303 | ||
|
|
af06d49c33 | ||
|
|
e7270b3667 | ||
|
|
87cff6d5cb | ||
|
|
aaf9ef343b | ||
|
|
73cc666030 | ||
|
|
eaeeef3b80 | ||
|
|
73bb1e0f59 | ||
|
|
238038139d | ||
|
|
85e1c4ff0a | ||
|
|
a0d7039482 | ||
|
|
49a34037ad | ||
|
|
2dc9c26e36 | ||
|
|
2f3772b09a | ||
|
|
4a7fcb7d1c | ||
|
|
6e77d950eb | ||
|
|
572ae0dd46 | ||
|
|
a0a32abc0b | ||
|
|
edf7b060d2 | ||
|
|
2076ac9c38 | ||
|
|
4c9d59059e | ||
|
|
97de52a3be | ||
|
|
a994411e58 | ||
|
|
f06ba9f70b | ||
|
|
5a79873bd6 | ||
|
|
27f42beb81 | ||
|
|
6ccd2fa439 | ||
|
|
9dca93eeb2 | ||
|
|
11cc90d5f6 | ||
|
|
90495d0d5e | ||
|
|
f2136ec433 | ||
|
|
a04f27b255 | ||
|
|
f27875d5bc | ||
|
|
cb66d36434 | ||
|
|
d4ff718ac3 | ||
|
|
23fcac8c4f | ||
|
|
fa8165ba7e | ||
|
|
db9ca55c71 | ||
|
|
672e330f47 | ||
|
|
852e630314 | ||
|
|
54199b8d08 | ||
|
|
edc4814ced | ||
|
|
8f133a32ba | ||
|
|
4eb4e7ffdb | ||
|
|
acf4123234 | ||
|
|
9e843da98d | ||
|
|
e8ee36733f | ||
|
|
db3389a3cc | ||
|
|
3583825137 | ||
|
|
31079e0a60 | ||
|
|
45eed8f166 | ||
|
|
c038741194 | ||
|
|
63eaba4572 | ||
|
|
f2a3e50cab | ||
|
|
91b3e47243 | ||
|
|
b85fc019ca | ||
|
|
2c9b071fde | ||
|
|
6e83793dda | ||
|
|
2a5c08aaf8 | ||
|
|
5afaa731e0 | ||
|
|
bea25d1ceb | ||
|
|
739ca052b4 | ||
|
|
629fc03581 | ||
|
|
dd0337c3cf | ||
|
|
6b4188404b | ||
|
|
79ae65fe64 | ||
|
|
f6cf59c380 | ||
|
|
362a0c29ae | ||
|
|
be50c2f3f5 | ||
|
|
3917c774ec | ||
|
|
b708a8a4d3 | ||
|
|
80cd52701e | ||
|
|
265b03e400 | ||
|
|
4b9f1619f6 | ||
|
|
6438dee914 | ||
|
|
2a65a7aa8b | ||
|
|
2c689db6d7 | ||
|
|
2067d26276 | ||
|
|
aefa3c9ff6 | ||
|
|
3b2f6611d6 | ||
|
|
ecff5e1a3c | ||
|
|
eb33c9bf5a | ||
|
|
bd8fa031cc | ||
|
|
4d5b53aea2 | ||
|
|
69e96cba95 | ||
|
|
e836b6ce4c | ||
|
|
16bb0df9f9 | ||
|
|
bb7d389084 | ||
|
|
7a66b157f0 | ||
|
|
2fe53ac791 | ||
|
|
d0636d2b07 | ||
|
|
41ebe0e0bf | ||
|
|
6b8729a819 | ||
|
|
29f6d0e0e0 | ||
|
|
83349426d1 | ||
|
|
45647fdab3 | ||
|
|
67c2bf595f | ||
|
|
4d4b87ccb8 | ||
|
|
292149c46f | ||
|
|
704b1b4fdd | ||
|
|
a6a1754093 | ||
|
|
b0d3a77ac0 | ||
|
|
46ea733983 | ||
|
|
f4e313d3f2 | ||
|
|
1924eb9197 | ||
|
|
952d1eb76f | ||
|
|
f2934bd0bd | ||
|
|
677f570f34 | ||
|
|
227c19bb67 | ||
|
|
1f605df053 | ||
|
|
17d1a471a4 | ||
|
|
f00c1389d6 | ||
|
|
4f35f8cad8 | ||
|
|
5ea4a626a6 | ||
|
|
4bb273a802 | ||
|
|
4a47ea034b | ||
|
|
7b9fbc4389 | ||
|
|
2883b67644 | ||
|
|
f788c789f3 | ||
|
|
3e8c3ab0c4 | ||
|
|
ff991ac99e | ||
|
|
a14547e92f | ||
|
|
203f5b48ad | ||
|
|
7bdfb1f55f | ||
|
|
cf3e1e7e7d | ||
|
|
fd36fc0ea2 | ||
|
|
eb9ddbe8a9 | ||
|
|
256044c04f | ||
|
|
0cd55d88bf | ||
|
|
bba1f964b8 | ||
|
|
7015501161 | ||
|
|
ff0b1cfb0b | ||
|
|
d90b02ce22 | ||
|
|
5b62b4a884 | ||
|
|
f7d127b360 | ||
|
|
1d7d4c2446 | ||
|
|
a85d5f57bb | ||
|
|
1a414f5512 | ||
|
|
e24c1302f4 | ||
|
|
883199093c | ||
|
|
6a3d61ff12 | ||
|
|
c4e94a4130 | ||
|
|
a921e88d76 | ||
|
|
a56e14488e | ||
|
|
55cefa9021 | ||
|
|
3484df9ca4 | ||
|
|
66288296b2 | ||
|
|
5bffcb4c89 | ||
|
|
4867571df0 | ||
|
|
a2250dd205 | ||
|
|
f053fc48db | ||
|
|
ebdebadd3b | ||
|
|
fa5017a4a5 | ||
|
|
d4de567ca7 | ||
|
|
f2587339cf | ||
|
|
2dc5d5104e | ||
|
|
500ea2b119 | ||
|
|
df06f3e3bd | ||
|
|
defa89c794 | ||
|
|
58d13f88d4 | ||
|
|
85248595e3 | ||
|
|
0b49719ae4 | ||
|
|
3080b81bcc | ||
|
|
c03e0ad841 | ||
|
|
ac9e35735b | ||
|
|
a26f1e6f8e | ||
|
|
7d3ab83ec3 | ||
|
|
eae05bbb98 | ||
|
|
820b7cd41b | ||
|
|
1712db2146 | ||
|
|
c3c0bdfbbb | ||
|
|
4b09e94853 | ||
|
|
26447e5bbe | ||
|
|
f4f7c1e3b5 | ||
|
|
6d1982d6cf | ||
|
|
ecdd665f1c | ||
|
|
a214c93ad4 | ||
|
|
044b06b6cb | ||
|
|
5b0a9d22b6 | ||
|
|
eecfb9af47 | ||
|
|
39df4dfa64 | ||
|
|
f355fb4c94 | ||
|
|
7a89f7c6e4 | ||
|
|
99238bdf0a | ||
|
|
f29dad042c | ||
|
|
87b7c959b4 | ||
|
|
088ea058d9 | ||
|
|
c4db208ca4 | ||
|
|
44dc1b9dba | ||
|
|
9f806bb828 | ||
|
|
49fab7267b | ||
|
|
3730339067 | ||
|
|
a815bb0fbd | ||
|
|
13fa83b24e | ||
|
|
eb27b24b73 | ||
|
|
5f961fe8e3 | ||
|
|
a5edb6d8b7 | ||
|
|
7e10554506 | ||
|
|
700580aae6 | ||
|
|
6e7766f5a4 | ||
|
|
3b7637ce40 | ||
|
|
edebba89b9 | ||
|
|
8b992cc9bc | ||
|
|
7393fb41ed | ||
|
|
b7d507567b | ||
|
|
2590998ee0 | ||
|
|
8545d97b8d | ||
|
|
424ec4d2a5 | ||
|
|
1cb4979ed7 | ||
|
|
a347e49178 | ||
|
|
e4aaf9f697 | ||
|
|
792b697450 | ||
|
|
4e1cd73e59 | ||
|
|
b7a1a0d8b2 | ||
|
|
d681b972cc | ||
|
|
28819e388e | ||
|
|
f30005acfd | ||
|
|
691d640b2d | ||
|
|
0f76863d9b | ||
|
|
a4f85ae6ff | ||
|
|
787ee2c997 | ||
|
|
d34b6ae0bc | ||
|
|
f5c26c7fb3 | ||
|
|
731e9d8db9 | ||
|
|
8b58e570cb | ||
|
|
ba6d991851 | ||
|
|
bd6a1fcafe | ||
|
|
317e546b0a | ||
|
|
a6732ef6db | ||
|
|
3802e83e79 | ||
|
|
658f133691 | ||
|
|
6f416b20c3 | ||
|
|
f7514bebbf | ||
|
|
d024755718 | ||
|
|
0ab9b47371 | ||
|
|
4325238aee | ||
|
|
9785199036 | ||
|
|
c27a3505dc | ||
|
|
ee14220f66 | ||
|
|
1b94ca130c | ||
|
|
fd2151f9bb | ||
|
|
6add6488d7 | ||
|
|
22a44eb206 | ||
|
|
420c678526 | ||
|
|
c53edf00da | ||
|
|
ebf7903d0e | ||
|
|
4ab42e218e | ||
|
|
e9ddc1dc3b | ||
|
|
f5d99bcac9 | ||
|
|
9ee318e8d4 | ||
|
|
5dbab48aff | ||
|
|
de364cfac1 | ||
|
|
8714ac4aa1 | ||
|
|
d4bf79df8f | ||
|
|
16591d3788 | ||
|
|
80e93704eb | ||
|
|
8216e6bbbc | ||
|
|
f959b4d4d3 | ||
|
|
db2d0456ac | ||
|
|
5639160b8b | ||
|
|
c2efbc889d | ||
|
|
ab99fb3eb2 | ||
|
|
17fce79d3c | ||
|
|
693c84f8f0 | ||
|
|
b7d960eadb | ||
|
|
199364e932 | ||
|
|
3b21b87036 | ||
|
|
fc8eaf0d28 | ||
|
|
6aecc2b9fa | ||
|
|
43bc038d41 | ||
|
|
82896c8553 | ||
|
|
f1aeed4344 | ||
|
|
46bc0959e1 | ||
|
|
167f0c8b50 | ||
|
|
e7ca960f0c | ||
|
|
d520e0641e | ||
|
|
9573a7653b | ||
|
|
af39b2b2ee | ||
|
|
78835c7525 | ||
|
|
a88958ba20 | ||
|
|
5b7c076fba | ||
|
|
942bab3b32 | ||
|
|
7b76644bc6 | ||
|
|
6b5f5d8762 | ||
|
|
18d8221dd2 | ||
|
|
cfcb4d92c6 | ||
|
|
1f0a044677 | ||
|
|
9f621306cb | ||
|
|
c59ddb73b4 | ||
|
|
669c9180b4 | ||
|
|
12cd938db3 | ||
|
|
4defd6d676 | ||
|
|
be8a1548d4 | ||
|
|
15759061f5 | ||
|
|
423c49ab92 | ||
|
|
227ef26e7f | ||
|
|
9e2a625b29 | ||
|
|
d990270de7 | ||
|
|
4dc76ef81b | ||
|
|
ab6c51bb8f | ||
|
|
c064956f85 | ||
|
|
a75411b029 | ||
|
|
93150aa411 | ||
|
|
61b2a2c703 | ||
|
|
be15db9cdf | ||
|
|
6bd1ebd68b | ||
|
|
9a6c60e6a7 | ||
|
|
acb3f93ae4 | ||
|
|
4d885acda0 | ||
|
|
ce6d635c33 | ||
|
|
d4c7581835 | ||
|
|
4b29351f2f | ||
|
|
9baae93a9b | ||
|
|
9334c9db31 | ||
|
|
0e7cba55a8 | ||
|
|
276a307780 | ||
|
|
88ee400c3f | ||
|
|
f55690e702 | ||
|
|
204406bd29 | ||
|
|
0bae07f7b9 | ||
|
|
27310926d3 | ||
|
|
3428b278d3 | ||
|
|
d01ad47953 | ||
|
|
b41a4cf05b | ||
|
|
bd8545f748 | ||
|
|
4917796d63 | ||
|
|
14159245a9 | ||
|
|
5cdfe42113 | ||
|
|
383f95f7c8 | ||
|
|
0a125edeaf | ||
|
|
684d6acc93 | ||
|
|
0b91cd9bae | ||
|
|
61211634c9 | ||
|
|
759b3841b8 | ||
|
|
fe39ac8af2 | ||
|
|
ca62bdcfe6 | ||
|
|
fda5f3e4ef | ||
|
|
7263b6c611 | ||
|
|
6eb04bcca8 | ||
|
|
de15bf7fb0 | ||
|
|
49771ba018 | ||
|
|
6e9da2a4e7 | ||
|
|
4a05ad29e4 | ||
|
|
344b145062 | ||
|
|
9744830d4a | ||
|
|
02dc8b5b21 | ||
|
|
d3cccebbd2 | ||
|
|
9260fd92bf | ||
|
|
5957544dee | ||
|
|
9263aec739 | ||
|
|
1cc77481bd | ||
|
|
534c72b91f | ||
|
|
57c0eb776c | ||
|
|
b00aacee00 | ||
|
|
cc0dff2f49 | ||
|
|
b0ba3e5346 | ||
|
|
422c9a8e69 | ||
|
|
b01359efc5 | ||
|
|
518db6e648 | ||
|
|
2289c25029 | ||
|
|
719dd2c3af | ||
|
|
28363eff4e | ||
|
|
b2c7de9a5a | ||
|
|
9c1feecca5 | ||
|
|
c80b4e773f | ||
|
|
e93f206bad | ||
|
|
484b98bb04 | ||
|
|
64b4a599e8 | ||
|
|
c258e5d4e8 | ||
|
|
2451c0c202 | ||
|
|
bb5f4b0234 | ||
|
|
2e8b368141 | ||
|
|
e31f61ba34 | ||
|
|
df06f9af2e | ||
|
|
b8ea5c4a02 | ||
|
|
82162ea8c8 | ||
|
|
15ae263312 | ||
|
|
c5bd28dd51 | ||
|
|
a7779a1029 | ||
|
|
9ea8f98e5c | ||
|
|
a118f306b1 | ||
|
|
4aec48c85c | ||
|
|
c9463bf6ef | ||
|
|
705bb32a7a | ||
|
|
3b621f1052 | ||
|
|
13f15d5339 | ||
|
|
b105dd837a | ||
|
|
3ffbe23fbe | ||
|
|
79629e6b6e | ||
|
|
cd328bfb61 | ||
|
|
76694bccdd | ||
|
|
29a5596d9e | ||
|
|
20a9e95b9a | ||
|
|
c4a9bb46d9 | ||
|
|
8cbbf9095a | ||
|
|
2292e1cebe | ||
|
|
fd2de52949 | ||
|
|
7f14cc4488 | ||
|
|
4f8e411f71 | ||
|
|
82d6a7265f | ||
|
|
1ed75978a9 | ||
|
|
54de081be7 | ||
|
|
009f4fb6d1 | ||
|
|
575f5dfd19 | ||
|
|
2a70117f02 | ||
|
|
c5d29821da | ||
|
|
4d92c8bdbf | ||
|
|
9f48c6a1cc | ||
|
|
c3a303ef70 | ||
|
|
e1f0f6937a | ||
|
|
77e55640d7 | ||
|
|
ec64fe6500 | ||
|
|
7aa7bcf740 | ||
|
|
2d6e4eaf8a | ||
|
|
b89c12e79b | ||
|
|
e101d825a2 | ||
|
|
dead6873db | ||
|
|
c8142ca539 | ||
|
|
db62a6d95c | ||
|
|
71a12810e0 | ||
|
|
5550cfa4dc | ||
|
|
6e87dc3e55 | ||
|
|
2137124977 | ||
|
|
ad5365fb53 | ||
|
|
e476562a46 | ||
|
|
779f467612 | ||
|
|
7ff05f2977 | ||
|
|
249a6cd27c | ||
|
|
f0a8b9e17e | ||
|
|
3ae8d22637 | ||
|
|
39b9624381 | ||
|
|
1b84048bfc | ||
|
|
805d43831f | ||
|
|
91aa82b62b | ||
|
|
b649730a2c | ||
|
|
f011c7656a | ||
|
|
5c11295852 | ||
|
|
6d49449a4d | ||
|
|
8e15d4245d | ||
|
|
fae4c29870 | ||
|
|
241069ddac | ||
|
|
93c2830ce7 | ||
|
|
5a8ce5e364 | ||
|
|
9d941e15e5 | ||
|
|
3efe6ab2df | ||
|
|
a319b6f25e | ||
|
|
d6f505f85a | ||
|
|
554d779344 | ||
|
|
fb3bc8d7ae | ||
|
|
ebe7448e67 | ||
|
|
3aafbc68c1 | ||
|
|
8dbc34a6a7 | ||
|
|
e62a6584ca | ||
|
|
20b84f5b17 | ||
|
|
5d15928e82 | ||
|
|
93ae8fd81e | ||
|
|
dcf94a3323 | ||
|
|
ce7cca1281 | ||
|
|
ca78ac71ba | ||
|
|
c87d59346b | ||
|
|
9b3e25f407 | ||
|
|
f3954e1974 | ||
|
|
d38d3f4ffa | ||
|
|
5bceec4c16 | ||
|
|
76f23c213d | ||
|
|
82b6eb4331 | ||
|
|
a3c03d021e | ||
|
|
c1ac836c7f | ||
|
|
855053dcee | ||
|
|
27e992b260 | ||
|
|
e18ee20d18 | ||
|
|
77036074ef | ||
|
|
66560432ce | ||
|
|
311ef28b66 | ||
|
|
5384091183 | ||
|
|
a062420224 | ||
|
|
88902b7586 | ||
|
|
3ff05a6f53 | ||
|
|
b33f85a5d1 | ||
|
|
c783733e16 | ||
|
|
f60f127d57 | ||
|
|
3de481706d | ||
|
|
0c8abf8624 | ||
|
|
dbf87bf94e | ||
|
|
362e08a2b6 | ||
|
|
90ea286a50 | ||
|
|
1ec4835946 | ||
|
|
c9729611e4 | ||
|
|
6e65f37392 | ||
|
|
859bf38c67 | ||
|
|
76663c26db | ||
|
|
a1c5742a70 | ||
|
|
299efd84af | ||
|
|
61c197bdbd | ||
|
|
217f536606 | ||
|
|
e2305d4501 | ||
|
|
f50447e6bd | ||
|
|
fece2a517a | ||
|
|
eb046dcdb3 | ||
|
|
09bbb56a39 | ||
|
|
381a0ff1fb | ||
|
|
34f5245672 | ||
|
|
1f374d1ea9 | ||
|
|
1aadd1c52f | ||
|
|
becb14ad01 | ||
|
|
3c16905754 | ||
|
|
a39c89e34e | ||
|
|
b485aabea0 | ||
|
|
3783a27a39 | ||
|
|
61fee79cc7 | ||
|
|
e7826a717c | ||
|
|
b865a2bae5 | ||
|
|
24439aac6d | ||
|
|
b8418cfba5 | ||
|
|
98f85c90b0 | ||
|
|
c43d4f2539 | ||
|
|
e91343e376 | ||
|
|
301b436220 | ||
|
|
ddb097d65d | ||
|
|
9090d3bbe9 | ||
|
|
555282985d | ||
|
|
71cf48b0c1 | ||
|
|
56a2e27910 | ||
|
|
3603b67eb7 | ||
|
|
ac0b5fe5ed | ||
|
|
d286839da1 | ||
|
|
edbcb3f4c0 | ||
|
|
511c135e2f | ||
|
|
8a8a483f41 | ||
|
|
0ca4c6f221 | ||
|
|
5b45eb702b | ||
|
|
a4a001dde9 | ||
|
|
9d2df44d18 | ||
|
|
e6cdd2cf56 | ||
|
|
0fde295f5e | ||
|
|
a192fe688a | ||
|
|
afce25cd65 | ||
|
|
98d0d8c642 | ||
|
|
f0ecd82129 | ||
|
|
19ece6192e | ||
|
|
3481ff245e | ||
|
|
642eedeab5 | ||
|
|
12e416a324 | ||
|
|
e7083decec | ||
|
|
e6398ba24f | ||
|
|
64f9e4d797 | ||
|
|
6bf64fbb23 | ||
|
|
a20d4bc890 | ||
|
|
2ef6df6182 | ||
|
|
672c87e9e4 | ||
|
|
ef73afb39e | ||
|
|
cf0fa9da2d | ||
|
|
7ec7a18671 | ||
|
|
a1530d90f2 | ||
|
|
90ba942cf6 | ||
|
|
39ed77253c | ||
|
|
94556f380b | ||
|
|
bb02bdcd55 | ||
|
|
1ff64e2fed | ||
|
|
2b042bdd59 | ||
|
|
64639e1437 | ||
|
|
3c874ce9f4 | ||
|
|
176ebf9da1 | ||
|
|
168f59e772 | ||
|
|
cbad7c63eb | ||
|
|
d46c515d6d | ||
|
|
4354cca461 | ||
|
|
4c06b7f074 | ||
|
|
a09eefde25 | ||
|
|
91ca995edc | ||
|
|
35e1f7845a | ||
|
|
1d8534b9cd | ||
|
|
b4f4dd1197 | ||
|
|
106acae943 | ||
|
|
b804d36a21 | ||
|
|
5a80928b89 | ||
|
|
889a65883f | ||
|
|
7dce2bbbdf | ||
|
|
546ddf78af | ||
|
|
7f662708fa | ||
|
|
f799e96910 | ||
|
|
dfe0c79f45 | ||
|
|
4b795fc42f | ||
|
|
77f1c86b0d | ||
|
|
02a440953b | ||
|
|
7ae2e0b9ef | ||
|
|
c4cd2ca34d | ||
|
|
d82c9b4576 | ||
|
|
166f9599e7 | ||
|
|
19f66943cb | ||
|
|
b2817c7299 | ||
|
|
ed7dcb7682 | ||
|
|
faaae9fa6b | ||
|
|
5b285380e7 | ||
|
|
cc488a035d | ||
|
|
7b74567550 | ||
|
|
bb1623ba5d | ||
|
|
5f61bfad6e | ||
|
|
332bec0f1a | ||
|
|
91aff4fe20 | ||
|
|
f8b81131a5 | ||
|
|
60098c9629 | ||
|
|
81e8a45b38 | ||
|
|
37d6012791 | ||
|
|
07505518fa | ||
|
|
88c3437a24 | ||
|
|
b517022e15 | ||
|
|
8d0eae5c7f | ||
|
|
61bfc44eec | ||
|
|
c0ad975f22 | ||
|
|
6a000ec99b | ||
|
|
c8aca55cd7 | ||
|
|
80f060b213 | ||
|
|
e91fd9623f | ||
|
|
86e1a7014c | ||
|
|
9916356b14 | ||
|
|
26a8b901a0 | ||
|
|
490d12b6f7 | ||
|
|
0393d560f8 | ||
|
|
639210660e | ||
|
|
f6f45990ab | ||
|
|
38c7b03372 | ||
|
|
2c1d766640 | ||
|
|
80a7928b67 | ||
|
|
9b89da3a95 | ||
|
|
b988558ea4 | ||
|
|
469434a8eb | ||
|
|
6073594656 | ||
|
|
fed97bf8db | ||
|
|
3ad2f6fa4a | ||
|
|
ccd5303040 | ||
|
|
a7f830b1e7 | ||
|
|
967b51c964 | ||
|
|
2ced182fcc | ||
|
|
98bbfe7968 | ||
|
|
a1d0445cc1 | ||
|
|
4a8037fa62 | ||
|
|
d87599c344 | ||
|
|
37aacf3e41 | ||
|
|
87d61693b1 | ||
|
|
bb4acf0757 | ||
|
|
0e05f63147 | ||
|
|
30747be220 | ||
|
|
55bfebbc21 | ||
|
|
e042c5ceb7 | ||
|
|
da76b2ae86 | ||
|
|
1eba324e94 | ||
|
|
a66c9446b5 | ||
|
|
dae38840f9 | ||
|
|
0c7bf665f2 | ||
|
|
ecf7f05f12 | ||
|
|
2c855e991c | ||
|
|
ad7811e991 | ||
|
|
dae750f734 | ||
|
|
7b06400ac7 | ||
|
|
6808b0fd79 | ||
|
|
7e147993d4 | ||
|
|
afb905c5ae | ||
|
|
5fb7f6b77c | ||
|
|
3b02c65a94 | ||
|
|
ad83e447a6 | ||
|
|
2d8b703e7c | ||
|
|
11fd45791b | ||
|
|
17b82ec9ee | ||
|
|
fa4c5dd456 | ||
|
|
84699acc6e | ||
|
|
3632ad959f | ||
|
|
9859396131 | ||
|
|
98a1820d39 | ||
|
|
ad75f873af | ||
|
|
dc498c0614 | ||
|
|
2912d3522c | ||
|
|
b1fba8f3fe | ||
|
|
d51f4fde37 | ||
|
|
d367affd57 | ||
|
|
96329378b9 | ||
|
|
4527656f7c | ||
|
|
bd05487375 | ||
|
|
09e039ceae | ||
|
|
7de280f12f | ||
|
|
2113e8c1d7 | ||
|
|
5c27f6b2d1 | ||
|
|
ff4ad92a7a | ||
|
|
40023f9f14 | ||
|
|
b9ca124980 | ||
|
|
7fbe00c311 | ||
|
|
ca4cb46dde | ||
|
|
cdc4d8a0cf | ||
|
|
8b4e50f74a | ||
|
|
aeebb1c1c1 | ||
|
|
a212a78c1b | ||
|
|
ea0af0c391 | ||
|
|
8a559a070a | ||
|
|
d5e10f6701 | ||
|
|
8f1729d5f7 | ||
|
|
db95415385 | ||
|
|
f943a2db50 | ||
|
|
04b63cbc10 | ||
|
|
7b287a8ea7 | ||
|
|
ddfd3d9752 | ||
|
|
c4ffb30410 | ||
|
|
94bf129721 | ||
|
|
3039f9405e | ||
|
|
d8395ba2e9 | ||
|
|
3c5523617e | ||
|
|
9629fdf13c | ||
|
|
24ad1c8281 | ||
|
|
3a8c947922 | ||
|
|
4b4276e3fc | ||
|
|
371dcdff13 | ||
|
|
46db56905d | ||
|
|
f0194060c5 | ||
|
|
22406cef6a | ||
|
|
5e14c08fc5 | ||
|
|
434b7681ce | ||
|
|
36f8f2ba1e | ||
|
|
526fb77823 | ||
|
|
1956d385bd | ||
|
|
cdb773c065 | ||
|
|
20e6455559 | ||
|
|
8dbcd446db | ||
|
|
c2ea7f081a | ||
|
|
f40e6c37a6 | ||
|
|
6ebfdc5329 | ||
|
|
d97faf2424 | ||
|
|
651a2bac97 | ||
|
|
467d52a73e | ||
|
|
3dcf09f883 | ||
|
|
b99a84691e | ||
|
|
95db4e7ad5 | ||
|
|
3d0b4feb80 | ||
|
|
329106c845 | ||
|
|
78de79ce96 | ||
|
|
6ba68941e8 | ||
|
|
ba5e5989a3 | ||
|
|
fd72edd98f | ||
|
|
7d1ede1022 | ||
|
|
7ddd1f7a60 | ||
|
|
8652172f9b | ||
|
|
4ff5dc1ee0 | ||
|
|
d0e667961c | ||
|
|
4fd095b741 | ||
|
|
a7064395c2 | ||
|
|
fa3cf6ab41 | ||
|
|
935c5a37b3 | ||
|
|
39238ded46 | ||
|
|
203b7649a2 | ||
|
|
73973049d6 | ||
|
|
cb15c94eab | ||
|
|
fea722e7e9 | ||
|
|
392a2d67db | ||
|
|
13842de9e0 | ||
|
|
dc208cddda | ||
|
|
3707e2e826 | ||
|
|
fd4fbd3950 | ||
|
|
3f7bcba984 | ||
|
|
dafb457e81 | ||
|
|
a56d2a17e4 | ||
|
|
8a3f9884c8 | ||
|
|
96bb7cb873 | ||
|
|
51fbc16913 | ||
|
|
02d6222031 | ||
|
|
84c2d1014b | ||
|
|
f754d38f30 | ||
|
|
a498332c96 | ||
|
|
8283401d2b | ||
|
|
a67b42711d | ||
|
|
c7635f8f21 | ||
|
|
aecef0eaed | ||
|
|
69af52b99e | ||
|
|
a5da0ec34a | ||
|
|
b1784fd378 | ||
|
|
9b45e6b831 | ||
|
|
95e8812370 | ||
|
|
4480479994 | ||
|
|
09d967dfbd | ||
|
|
eb57e1f25c | ||
|
|
72f08b1095 | ||
|
|
6988f4325b | ||
|
|
e2b6eaf028 | ||
|
|
438de98926 | ||
|
|
c85443c326 | ||
|
|
7d3e55a84a | ||
|
|
150f781d05 | ||
|
|
62dd4a2258 | ||
|
|
6e4152d3b0 | ||
|
|
d1251c21db | ||
|
|
399bc037fa | ||
|
|
e16e90e9b9 | ||
|
|
b84faa58b6 | ||
|
|
a1ac472f9e | ||
|
|
2810323aad | ||
|
|
80d680b0a6 | ||
|
|
fcbb25a882 | ||
|
|
b4a1fe0aa3 | ||
|
|
4bb6e3d47d | ||
|
|
346f5519c7 | ||
|
|
3055a664f9 | ||
|
|
05b0a5d3d5 | ||
|
|
28982cbd2d | ||
|
|
34c9a486bf | ||
|
|
c143b75711 | ||
|
|
61da46ff11 | ||
|
|
d6c155c477 | ||
|
|
b375870693 | ||
|
|
530e5778cc | ||
|
|
b109c866b7 | ||
|
|
c00202d51e | ||
|
|
5b1ac3ccc6 | ||
|
|
336501382e | ||
|
|
23c270bc0f | ||
|
|
9f24b17b4a | ||
|
|
888919c2e9 | ||
|
|
a51fb418b2 | ||
|
|
3330af6cd4 | ||
|
|
1a25e57069 | ||
|
|
00050b91da | ||
|
|
4398f110f1 | ||
|
|
668cc9ef78 | ||
|
|
9b50ce3000 | ||
|
|
d8d33f5a3e | ||
|
|
fbe171e280 | ||
|
|
29cd63bea2 | ||
|
|
055abc3e4c | ||
|
|
de8d216c54 | ||
|
|
634fbb54b8 | ||
|
|
e4ea8690db | ||
|
|
49e8cf17a4 | ||
|
|
522213a28f | ||
|
|
bf89063212 | ||
|
|
b2c937e03a | ||
|
|
06a8f74f28 | ||
|
|
46525fbfe8 | ||
|
|
d94663312f | ||
|
|
d690268b43 | ||
|
|
28df09b103 | ||
|
|
14ef2a88ef | ||
|
|
a48402153b | ||
|
|
475124ac6e | ||
|
|
c1b0067a97 | ||
|
|
30ac2ae550 | ||
|
|
6df9673936 | ||
|
|
722a4061ac | ||
|
|
bfeb897a6d | ||
|
|
2a9ce31d25 | ||
|
|
a618a56754 | ||
|
|
fabb2c5541 | ||
|
|
f12e7ac27c | ||
|
|
a86d850688 | ||
|
|
8750491323 | ||
|
|
48eb195ee0 | ||
|
|
83b9348c91 | ||
|
|
f20e260a9a | ||
|
|
a58ee0a4e9 | ||
|
|
b1a790b851 | ||
|
|
fc89e17a3c | ||
|
|
30d2f07360 | ||
|
|
ced7ec0c97 | ||
|
|
e7b371b355 | ||
|
|
0fd45396ce | ||
|
|
d4e40101f7 | ||
|
|
b0a6d04f9f | ||
|
|
cb2aa2792f | ||
|
|
ee543f184a | ||
|
|
50650ec314 | ||
|
|
104e953dd3 | ||
|
|
dae7399f7e | ||
|
|
0325c46a43 | ||
|
|
32aee50bc6 | ||
|
|
c50f096ee3 | ||
|
|
a36b7f4934 | ||
|
|
4d07797b40 | ||
|
|
e1cedf7fd2 | ||
|
|
c5c542da6c | ||
|
|
1199186652 | ||
|
|
195fb17458 | ||
|
|
e738f760ae | ||
|
|
a695f36f0b | ||
|
|
b99a6a0fed | ||
|
|
42fa7ceaac | ||
|
|
158266684d | ||
|
|
13326b89e5 | ||
|
|
bc17c1cb2e | ||
|
|
909072ad54 | ||
|
|
8259a0dcb4 | ||
|
|
182d17cd33 | ||
|
|
bfd38ca891 | ||
|
|
af404cf6ae | ||
|
|
ce26630596 | ||
|
|
dab6179163 | ||
|
|
f8a6252339 | ||
|
|
e300ddfdcc | ||
|
|
0f5985889b | ||
|
|
48f85b4039 | ||
|
|
b46740bd33 | ||
|
|
67d48c7682 | ||
|
|
8585e0060d | ||
|
|
fdca066f90 | ||
|
|
866416b65e | ||
|
|
fe618642eb | ||
|
|
15be9bb5ea | ||
|
|
1b514d3cbc | ||
|
|
77502c7908 | ||
|
|
fc08a9a165 | ||
|
|
73d5209743 | ||
|
|
399073af7b | ||
|
|
93d58ec74a | ||
|
|
1b2b61169a | ||
|
|
2f5a6c1399 | ||
|
|
fb573b2c4d | ||
|
|
903512c02b | ||
|
|
a4c04f2c2b | ||
|
|
033ca547d5 | ||
|
|
b1614182fe | ||
|
|
fe3a781de0 | ||
|
|
b1edc30cb4 | ||
|
|
70fd146eb1 | ||
|
|
eb63d0bb1a | ||
|
|
081e14f88f | ||
|
|
292586d96f | ||
|
|
5596354446 | ||
|
|
7701ba4069 | ||
|
|
ec83f827b1 | ||
|
|
9a9be49b63 | ||
|
|
fc726204e3 | ||
|
|
0b0b688c17 | ||
|
|
7b7913fab1 | ||
|
|
e7e6c96f3e | ||
|
|
d7d6ff2fed | ||
|
|
3770264901 | ||
|
|
48b4855502 | ||
|
|
f51c38357f | ||
|
|
5ee706b585 | ||
|
|
e5585bd846 | ||
|
|
53e4f3c89d | ||
|
|
e7990ed810 | ||
|
|
cb0e56639c | ||
|
|
421441aba1 | ||
|
|
52d0344269 | ||
|
|
e807967655 | ||
|
|
0374044790 | ||
|
|
67241a46a8 | ||
|
|
9074608bd8 | ||
|
|
92c34e8e00 | ||
|
|
1748fb29c5 | ||
|
|
89e728f78a | ||
|
|
f0e0322923 | ||
|
|
0a3bc99d9a | ||
|
|
76e0dfef97 | ||
|
|
53efa98c15 | ||
|
|
2f59bdfe91 | ||
|
|
6d7f10c0c6 | ||
|
|
55c8ad0141 | ||
|
|
db0f610bc1 | ||
|
|
3c7d4e67f4 | ||
|
|
e294617d10 | ||
|
|
27554380d3 | ||
|
|
13d4803056 | ||
|
|
daf0c0c048 | ||
|
|
b9478d64b7 | ||
|
|
b6968afd5d | ||
|
|
ab67327ec0 | ||
|
|
53470fa7ad | ||
|
|
ccf029dabe | ||
|
|
ac18bd16b4 | ||
|
|
a1b71202e7 | ||
|
|
68455cf996 | ||
|
|
d9724f3b46 | ||
|
|
4cd35e8737 | ||
|
|
293119cf6c | ||
|
|
32d034abd5 | ||
|
|
5d06124a9c | ||
|
|
5f35004d08 | ||
|
|
48c8fd783e | ||
|
|
be5362b025 | ||
|
|
526c4f6664 | ||
|
|
f0e3a84822 | ||
|
|
0127a0bd1c | ||
|
|
b94fdd11c2 | ||
|
|
aa5457c14c | ||
|
|
e422f5ecbe | ||
|
|
9bbeb99177 | ||
|
|
8127f10640 | ||
|
|
9bc92e65c2 | ||
|
|
39945ab856 | ||
|
|
9fd861f775 | ||
|
|
c62563bc96 | ||
|
|
ba481f35c2 | ||
|
|
3d8db9dbb9 | ||
|
|
ee1a4f176a | ||
|
|
fb779658e3 | ||
|
|
36596a33b3 | ||
|
|
1f8971bdbf | ||
|
|
b2576adae0 | ||
|
|
458c08d96b | ||
|
|
2024744f2b | ||
|
|
95b7c52812 | ||
|
|
04c43430aa | ||
|
|
be12410e31 | ||
|
|
04d9deceb2 | ||
|
|
bbcaf7c324 | ||
|
|
42c63ac3f2 | ||
|
|
bd80a4e512 | ||
|
|
390ba55211 | ||
|
|
b298c0cba4 | ||
|
|
08cdb7effb | ||
|
|
732ec9638f | ||
|
|
d2029c6efb | ||
|
|
3bd1b84759 | ||
|
|
8df35250c6 | ||
|
|
a9bad79e98 | ||
|
|
b200552c90 | ||
|
|
74acf8cab4 | ||
|
|
128bc2d5f8 | ||
|
|
619232ef86 | ||
|
|
1a6cbf2433 | ||
|
|
b328920fe5 | ||
|
|
a0e614e6ff | ||
|
|
0353b63e63 | ||
|
|
ab5c0364f6 | ||
|
|
3b3efd7483 | ||
|
|
47bb6e0f74 | ||
|
|
039bb5c318 | ||
|
|
a87403b57f | ||
|
|
45b3515ff8 | ||
|
|
698d027a0a | ||
|
|
e4290dfb32 | ||
|
|
d437418b3c | ||
|
|
ef00c0c2c6 | ||
|
|
ad045b7777 | ||
|
|
04076cd5df | ||
|
|
4145f228e4 | ||
|
|
de6463fbee | ||
|
|
374df5cabf | ||
|
|
0687de734c | ||
|
|
1479b9633a | ||
|
|
a4cf7462f0 | ||
|
|
f2a41c54b1 | ||
|
|
3c6a0844b2 | ||
|
|
fde9d1cd30 | ||
|
|
e91da7c2b4 | ||
|
|
e777095a9f | ||
|
|
31bbed4ac8 | ||
|
|
0faaec7ce9 | ||
|
|
9760f523ce | ||
|
|
519f678938 | ||
|
|
a781c8498f | ||
|
|
a909c8d20f | ||
|
|
82466d2a52 | ||
|
|
6faf7513dd | ||
|
|
2b6e35d5eb | ||
|
|
62c8243ac5 | ||
|
|
9b787421e7 | ||
|
|
a55cc84a11 | ||
|
|
5708c4f359 | ||
|
|
e3320e2671 | ||
|
|
93d92ae338 | ||
|
|
e5c8d79e66 | ||
|
|
63d707c33c | ||
|
|
c45e2b8f5f | ||
|
|
c136afba67 | ||
|
|
ed24416bf7 | ||
|
|
13117264c3 | ||
|
|
dbc9fe0be4 | ||
|
|
0386eb5bbe | ||
|
|
84855b803d | ||
|
|
12b864e752 | ||
|
|
9800b326e5 | ||
|
|
111f04bb57 | ||
|
|
a03bb363dd | ||
|
|
82cdab83f5 | ||
|
|
dc52254843 | ||
|
|
7d2f933945 | ||
|
|
fe584f631d | ||
|
|
46175a69b9 | ||
|
|
f6bb7ba042 | ||
|
|
a79c51c7ad | ||
|
|
d3ad5d75c8 | ||
|
|
0295063981 | ||
|
|
4f55198dca | ||
|
|
1235e4a5ad | ||
|
|
ae5b596117 | ||
|
|
ce888d0664 | ||
|
|
d8492e68eb | ||
|
|
b26c6524b5 | ||
|
|
89440e019b | ||
|
|
cbee46ceea | ||
|
|
afefba7488 | ||
|
|
274b3dc819 | ||
|
|
2527e38102 | ||
|
|
4f711b23e6 | ||
|
|
8eeed2752b | ||
|
|
a06d552933 | ||
|
|
e94413f729 | ||
|
|
e71fa3f9e8 | ||
|
|
71c68cb9f4 | ||
|
|
7b8c38a4bd | ||
|
|
446bcd6f11 | ||
|
|
614d03364a | ||
|
|
41c1719d84 | ||
|
|
fbd2d8fe73 | ||
|
|
708c81934b | ||
|
|
670f531717 | ||
|
|
ee0097b448 | ||
|
|
2c346d256a | ||
|
|
77f90aeba0 | ||
|
|
867299c3d9 | ||
|
|
567aef7680 | ||
|
|
72881c18ad | ||
|
|
9b5a66a33b | ||
|
|
5b72ac1b32 | ||
|
|
fb38f798c0 | ||
|
|
9b62449166 | ||
|
|
115280714e | ||
|
|
2bcd016f29 | ||
|
|
27a6e72498 | ||
|
|
04df3cbdaa | ||
|
|
86a102e63e | ||
|
|
082f556969 | ||
|
|
2838a8cf26 | ||
|
|
17cc7b0e57 | ||
|
|
1e267d5916 | ||
|
|
74d9c030be | ||
|
|
7f324175de | ||
|
|
501d4b2759 | ||
|
|
1f2b3ea203 | ||
|
|
4353eee352 | ||
|
|
f076565a98 | ||
|
|
2dc72a1d3b | ||
|
|
79c8753436 | ||
|
|
95630d7fcb | ||
|
|
b7f5529b73 | ||
|
|
2ca31a8268 | ||
|
|
b7fe4c123a | ||
|
|
7007b6e6e5 | ||
|
|
2aeb45a160 | ||
|
|
e47d8d1a17 | ||
|
|
c749fdc54f | ||
|
|
8f4a17044d | ||
|
|
aa084d9b7b | ||
|
|
6459db39aa | ||
|
|
515e94a0cd | ||
|
|
5f86b54f33 | ||
|
|
7398d10bd4 | ||
|
|
815db8b89e | ||
|
|
ca7579e774 | ||
|
|
60f8371d55 | ||
|
|
ea7d0dcd92 | ||
|
|
2646f387fa | ||
|
|
9fad21c498 | ||
|
|
56fd961fc8 | ||
|
|
df853d1c5b | ||
|
|
2dc7ff9793 | ||
|
|
394c2c074b | ||
|
|
a546165861 | ||
|
|
126bff7b20 | ||
|
|
618e6a27c1 | ||
|
|
901c53c215 | ||
|
|
c66e9536fe | ||
|
|
8b3040ed1b | ||
|
|
604b9f3c58 | ||
|
|
d489ee24f5 | ||
|
|
2287e07204 | ||
|
|
834a43c1bb | ||
|
|
abf55ae76c | ||
|
|
4e17ad4b69 | ||
|
|
c10be63473 | ||
|
|
cf474ecb7a | ||
|
|
cb6c34bc26 | ||
|
|
82041039a5 | ||
|
|
c2c50d4659 | ||
|
|
f6498e39f3 | ||
|
|
50a0208f10 | ||
|
|
175b432eba | ||
|
|
479bdce215 | ||
|
|
38e41cf680 | ||
|
|
85c0b6dd88 | ||
|
|
adcaf78418 | ||
|
|
d90b17fde1 | ||
|
|
9bfe4af5db | ||
|
|
58b6270281 | ||
|
|
a915f6bce2 | ||
|
|
465eabef9b | ||
|
|
d58c8cff42 | ||
|
|
e08ace3b46 | ||
|
|
0c693b9244 | ||
|
|
868c6fb0ad | ||
|
|
fc32c729a3 | ||
|
|
e3952df2c1 | ||
|
|
363338a55e | ||
|
|
313d128887 | ||
|
|
755f07f5e4 | ||
|
|
cd7242711b | ||
|
|
3c07115b23 | ||
|
|
db657ed486 | ||
|
|
4fa2c1d144 | ||
|
|
962778061e | ||
|
|
c9fedfa27f | ||
|
|
510e2c4d1b | ||
|
|
e04e8b4cf8 | ||
|
|
c6f1a3eb3b | ||
|
|
67a5be6cc9 | ||
|
|
2e53ff6091 | ||
|
|
37424606ba | ||
|
|
c94619f920 | ||
|
|
b3cf0c8b3d | ||
|
|
0b71ff6797 | ||
|
|
e75980a841 | ||
|
|
1c95138455 | ||
|
|
f4936f4e23 | ||
|
|
785f546f68 | ||
|
|
f6ac845dfa | ||
|
|
1914503d2f | ||
|
|
0e97b5f9a0 | ||
|
|
b91534b234 | ||
|
|
cfb0d15aad | ||
|
|
a4905546c0 | ||
|
|
3ebb6bc45b | ||
|
|
69140cec81 | ||
|
|
f89f974bca | ||
|
|
148c89707e | ||
|
|
c7444a5674 | ||
|
|
059e01a864 | ||
|
|
401e0a21e6 | ||
|
|
d79a310ed1 | ||
|
|
32f836bfd1 | ||
|
|
50e47e0be0 | ||
|
|
736ffc30bf | ||
|
|
8fcabe34a3 | ||
|
|
5cb1590d93 | ||
|
|
361f47ce40 | ||
|
|
bbd32d870a | ||
|
|
82c9f9d904 | ||
|
|
226f58ab6f | ||
|
|
e94e18aee5 | ||
|
|
0308bd0ba0 | ||
|
|
1d839fdd15 | ||
|
|
54f3b65b68 | ||
|
|
f07ca4b4d3 | ||
|
|
acc244e553 | ||
|
|
2db67fd831 | ||
|
|
738c784352 | ||
|
|
a1ed706328 | ||
|
|
643d20801b | ||
|
|
4db5ee02eb | ||
|
|
e20cc82872 | ||
|
|
f6e8cc6ecc | ||
|
|
6aa2944727 | ||
|
|
38cba1a781 | ||
|
|
a75f6db5bc | ||
|
|
6fce92796c | ||
|
|
5a15aea7ab | ||
|
|
b5c41548ce | ||
|
|
9b0bc89eac | ||
|
|
3d8f8f340b | ||
|
|
be49961019 | ||
|
|
8f1a669c46 | ||
|
|
77ff0fe0d4 | ||
|
|
f07197c2cd | ||
|
|
c2bd285ef8 | ||
|
|
9c7232afcf | ||
|
|
6ae2ba17ff | ||
|
|
c1d2dfc04c | ||
|
|
453251a208 | ||
|
|
a1ea7e3a32 | ||
|
|
e7280f0305 | ||
|
|
4a56adb294 | ||
|
|
0c2a86f058 | ||
|
|
07d5ee82ff | ||
|
|
1c62e436b7 | ||
|
|
183920262a | ||
|
|
e64d209fbf | ||
|
|
d644366919 | ||
|
|
86b3edb06d | ||
|
|
9dca3c36f6 | ||
|
|
902996edd2 | ||
|
|
3e3b4c918c | ||
|
|
a5c66eb7f4 | ||
|
|
86111254a4 | ||
|
|
bfc3f788a1 | ||
|
|
1d61a21b46 | ||
|
|
948308bbf0 | ||
|
|
8ad024a515 | ||
|
|
8ff4ec94e7 | ||
|
|
ad19d2fdcf | ||
|
|
e4df35beff | ||
|
|
1c9867b476 | ||
|
|
62b51ad83c | ||
|
|
3c26c94972 | ||
|
|
40b131d9cc | ||
|
|
38e6b5e6ca | ||
|
|
b4fe82ffb4 | ||
|
|
7a632e9026 | ||
|
|
2b77c93bf4 | ||
|
|
6e0107ca76 | ||
|
|
391431d267 | ||
|
|
c4e938d656 | ||
|
|
cca7848b5b | ||
|
|
7780038a39 | ||
|
|
7a5833c88c | ||
|
|
a5772a670c | ||
|
|
ecb2abae94 | ||
|
|
24bef3da5a | ||
|
|
4ea2749225 | ||
|
|
f51e56b4e2 | ||
|
|
897ed16e6b | ||
|
|
834b06e895 | ||
|
|
035668cde5 | ||
|
|
54b1eecd8e | ||
|
|
6a9fe62c7d | ||
|
|
6721c10087 | ||
|
|
3a82acaa8e | ||
|
|
0ce6d057ba | ||
|
|
e448ea7f06 | ||
|
|
ac486037bf | ||
|
|
74eff27090 | ||
|
|
b7222ed26e | ||
|
|
1f7edd9d24 | ||
|
|
50169613f7 | ||
|
|
2847dacabf | ||
|
|
11ee5ed4f8 | ||
|
|
b194a23c02 | ||
|
|
5117150a84 | ||
|
|
758e8ce8b9 | ||
|
|
b8023be66a | ||
|
|
954f3d5f45 | ||
|
|
e28c359a7a | ||
|
|
89e2047442 | ||
|
|
a5392d54c3 | ||
|
|
38d8a22589 | ||
|
|
8c4cabd803 | ||
|
|
a0de28fe07 | ||
|
|
dcd1f2ecfe | ||
|
|
2d94afe185 | ||
|
|
6de78f63f4 | ||
|
|
ec0a1d655b | ||
|
|
aa6ec8fd7b | ||
|
|
7d68c2bc7d | ||
|
|
0b97430c8d | ||
|
|
5d267749a6 | ||
|
|
28f52e69ee | ||
|
|
3315c8e19a | ||
|
|
f659f34375 | ||
|
|
c8dc5a8a19 | ||
|
|
b48ee68b8d | ||
|
|
9e5b89842b | ||
|
|
27e8049fd7 | ||
|
|
9a6324eab8 | ||
|
|
5578905323 | ||
|
|
a272211895 | ||
|
|
1304fdc924 | ||
|
|
ae511e0ef0 | ||
|
|
f3fb2dda61 | ||
|
|
8f9a69a969 | ||
|
|
b74a9d7080 | ||
|
|
1a1a722527 | ||
|
|
5ce9bb7147 | ||
|
|
a867eb0f5c | ||
|
|
2b692f861f | ||
|
|
f235670a3e | ||
|
|
6f63a770c5 | ||
|
|
729b231f21 | ||
|
|
54fd21a755 | ||
|
|
0a15fb839c | ||
|
|
a14e95a727 | ||
|
|
c909df34fc | ||
|
|
3ed8d50f5c | ||
|
|
fcf8a696c6 | ||
|
|
deadcd64a5 | ||
|
|
fac85e1a76 | ||
|
|
88d3c87f80 | ||
|
|
26c4da6085 | ||
|
|
1d0bab6797 | ||
|
|
ec95872934 | ||
|
|
0043345e68 | ||
|
|
99d324308b | ||
|
|
ec756502d6 | ||
|
|
e9dbcd19f9 | ||
|
|
c64a82b76e | ||
|
|
1233825519 | ||
|
|
df322db5b3 | ||
|
|
01f7eb07c2 | ||
|
|
eb6c2bb009 | ||
|
|
81296601e9 | ||
|
|
ea66a802ba | ||
|
|
bf25069fab | ||
|
|
c9540edc9e | ||
|
|
7e6bc6a608 | ||
|
|
306a1b37b9 | ||
|
|
3ab9284fde | ||
|
|
3f06078531 | ||
|
|
cfc94def59 | ||
|
|
86156ae011 | ||
|
|
3c04b29ba5 | ||
|
|
75af76d2ac | ||
|
|
ccd7c8e204 | ||
|
|
9bc2f5900a | ||
|
|
b2713b5e56 | ||
|
|
e53bb372c1 | ||
|
|
68cf02be47 | ||
|
|
81ede0a4ba | ||
|
|
f9440c40ef | ||
|
|
672beba34b | ||
|
|
6f98d7954a | ||
|
|
4c9a87025d | ||
|
|
1b5d9890f9 | ||
|
|
938bf5420f | ||
|
|
573440a2b3 | ||
|
|
3f5afc18cd | ||
|
|
4d7a4d9ce7 | ||
|
|
42c8da315f | ||
|
|
ce43c42087 | ||
|
|
a3cbb7df9a | ||
|
|
90bc5ff7ba | ||
|
|
be4e67fdc5 | ||
|
|
7927ce0544 | ||
|
|
843fc7ad11 | ||
|
|
0bc7afe27e | ||
|
|
3662dd95bf | ||
|
|
aefe5c0f1c | ||
|
|
8d4f55b048 | ||
|
|
0147cc40b5 | ||
|
|
488a15de7a | ||
|
|
cefe4689f2 | ||
|
|
9a1fcf98e9 | ||
|
|
4b6d4ff743 | ||
|
|
85c95d1002 | ||
|
|
d978428973 | ||
|
|
c23784b187 | ||
|
|
22ac367284 | ||
|
|
27e0d0e007 | ||
|
|
db43e1f6aa | ||
|
|
a7813455cf | ||
|
|
eebf9c41a7 | ||
|
|
7318faccd9 | ||
|
|
2675fbf411 | ||
|
|
d91aab34e5 | ||
|
|
b60ca7c604 | ||
|
|
c187a50608 | ||
|
|
e2aeb48871 | ||
|
|
183efac185 | ||
|
|
b8c9f0e423 | ||
|
|
468ceaa511 | ||
|
|
1158c19b61 | ||
|
|
d666fa409a |
30
.editorconfig
Normal file
30
.editorconfig
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
indent_style = space
|
||||||
|
charset = utf-8
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
insert_final_newline = true
|
||||||
|
|
||||||
|
[Makefile]
|
||||||
|
indent_style = tab
|
||||||
|
|
||||||
|
[*.patch]
|
||||||
|
trim_trailing_whitespace = false
|
||||||
|
|
||||||
|
[*.md]
|
||||||
|
trim_trailing_whitespace = false
|
||||||
|
|
||||||
|
[*.html]
|
||||||
|
trim_trailing_whitespace = false
|
||||||
|
|
||||||
|
[*.py]
|
||||||
|
indent_size = 4
|
||||||
|
|
||||||
|
[*.bats]
|
||||||
|
indent_style = tab
|
||||||
|
indent_size = 4
|
||||||
|
|
||||||
|
[tests/*.sh]
|
||||||
|
indent_style = tab
|
||||||
|
indent_size = 4
|
||||||
2
.github/.ignore_words
vendored
Normal file
2
.github/.ignore_words
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
gool
|
||||||
|
Cant
|
||||||
12
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
12
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
<!--
|
||||||
|
|
||||||
|
Note
|
||||||
|
|
||||||
|
* Mark the PR as draft until it's ready to be reviewed.
|
||||||
|
* Please update the documentation to reflect the changes made in the PR.
|
||||||
|
* If the command is covered by tests under ./tests, please add/update tests for any changes unless you have a good reason.
|
||||||
|
* Make a new commit to resolve conversations instead of `push -f`.
|
||||||
|
* To resolve merge conflicts, merge from the `main` branch instead of rebasing over `main`.
|
||||||
|
* Please wait for the reviewer to mark a conversation as resolved.
|
||||||
|
|
||||||
|
-->
|
||||||
6
.github/dependabot.yml
vendored
Normal file
6
.github/dependabot.yml
vendored
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
version: 2
|
||||||
|
updates:
|
||||||
|
- package-ecosystem: "github-actions"
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: "weekly"
|
||||||
116
.github/workflows/ci.yml
vendored
Normal file
116
.github/workflows/ci.yml
vendored
Normal file
|
|
@ -0,0 +1,116 @@
|
||||||
|
name: ci
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
pull_request:
|
||||||
|
branches: [main]
|
||||||
|
jobs:
|
||||||
|
lint:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Check out code.
|
||||||
|
uses: actions/checkout@v7
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
- name: 'Get Changed Files'
|
||||||
|
id: 'files'
|
||||||
|
run: |
|
||||||
|
CHANGED_FILES=$(git diff --name-only "${BASE_SHA:-$BEFORE_SHA}...${HEAD_SHA:-$GITHUB_REF}")
|
||||||
|
echo "::notice::Changed files: $CHANGED_FILES"
|
||||||
|
{
|
||||||
|
echo "added_modified<<EOF"
|
||||||
|
echo "$CHANGED_FILES"
|
||||||
|
echo "EOF"
|
||||||
|
} >> "$GITHUB_OUTPUT"
|
||||||
|
env:
|
||||||
|
BASE_SHA: ${{ github.event.pull_request.base.sha }}
|
||||||
|
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
|
||||||
|
BEFORE_SHA: "${{ github.event.before }}"
|
||||||
|
- name: 'Check Generated Manuals'
|
||||||
|
run: |
|
||||||
|
failed=0
|
||||||
|
while IFS= read -r source; do
|
||||||
|
case "$source" in
|
||||||
|
man/git-*.md)
|
||||||
|
stem=${source%.md}
|
||||||
|
for generated in "$stem.1" "$stem.html"; do
|
||||||
|
if ! grep -Fqx -- "$generated" <<< "$CHANGED_FILES"; then
|
||||||
|
echo "::error file=$source::$generated must be updated with $source"
|
||||||
|
failed=1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done <<< "$CHANGED_FILES"
|
||||||
|
exit "$failed"
|
||||||
|
env:
|
||||||
|
CHANGED_FILES: "${{ steps.files.outputs.added_modified }}"
|
||||||
|
- uses: 'actions/setup-go@v7'
|
||||||
|
with:
|
||||||
|
go-version: '1.20'
|
||||||
|
- name: 'Install EditorConfig Lint'
|
||||||
|
run: go install 'github.com/editorconfig-checker/editorconfig-checker/cmd/editorconfig-checker@latest'
|
||||||
|
- name: 'Check EditorConfig Lint'
|
||||||
|
run: echo "$FILES" | xargs ~/go/bin/editorconfig-checker
|
||||||
|
env:
|
||||||
|
# NOTE: use env to pass the output in order to avoid possible injection attacks
|
||||||
|
FILES: "${{ steps.files.outputs.added_modified }}"
|
||||||
|
- name: checkstyle
|
||||||
|
run: ./scripts/checkstyle.py
|
||||||
|
- name: Shellcheck
|
||||||
|
run: shellcheck --severity=error bin/* ./*.sh
|
||||||
|
|
||||||
|
typo:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Check out code.
|
||||||
|
uses: actions/checkout@v7
|
||||||
|
- name: Install codespell
|
||||||
|
run: python3 -m pip install codespell==2.4
|
||||||
|
- name: spell check
|
||||||
|
run: |
|
||||||
|
git grep --cached -l '' . | \
|
||||||
|
grep -v -e 'History\.md' -e 'AUTHORS' -e 'man/.*\.1' -e 'man/.*\.html' | \
|
||||||
|
xargs codespell --ignore-words=.github/.ignore_words
|
||||||
|
|
||||||
|
test-bats:
|
||||||
|
name: 'Test with Bats'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v7
|
||||||
|
with:
|
||||||
|
submodules: recursive
|
||||||
|
- name: Setup Bats
|
||||||
|
id: setup-bats
|
||||||
|
uses: bats-core/bats-action@4.0.0
|
||||||
|
with:
|
||||||
|
bats-version: 'v1.8.1'
|
||||||
|
- name: Test with Bats
|
||||||
|
env:
|
||||||
|
BATS_LIB_PATH: ${{ steps.setup-bats.outputs.lib-path }}
|
||||||
|
TERM: xterm
|
||||||
|
run: bats ./tests
|
||||||
|
|
||||||
|
build:
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
platform:
|
||||||
|
- ubuntu-latest
|
||||||
|
- macos-latest
|
||||||
|
runs-on: ${{ matrix.platform }}
|
||||||
|
steps:
|
||||||
|
- name: Check out code.
|
||||||
|
uses: actions/checkout@v7
|
||||||
|
- name: Linux Install
|
||||||
|
if: matrix.platform == 'ubuntu-latest'
|
||||||
|
run: sudo apt-get install -y bsdmainutils
|
||||||
|
- name: Script
|
||||||
|
run: ./check_integrity.sh
|
||||||
|
- name: Brew release
|
||||||
|
if: matrix.platform == 'macos-latest'
|
||||||
|
run: |
|
||||||
|
mkdir ../release && git archive --format=tar.gz HEAD > ../release/git-extras-release.tar.gz
|
||||||
|
cd ../release
|
||||||
|
tar -xzf git-extras-release.tar.gz && make PREFIX=$(pwd) INSTALL_VIA=brew
|
||||||
|
./bin/git-extras update | grep "brew upgrade git-extras"
|
||||||
18
.github/workflows/stale.yaml
vendored
Normal file
18
.github/workflows/stale.yaml
vendored
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
name: "Maintenance: Close Stale PRs"
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: "30 1 * * *"
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
pull-requests: "write"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
stale:
|
||||||
|
runs-on: "ubuntu-latest"
|
||||||
|
if: github.repository_owner == 'tj'
|
||||||
|
steps:
|
||||||
|
- uses: "actions/stale@v11"
|
||||||
|
with:
|
||||||
|
close-pr-message: "This PR was closed because it has been stalled for 365 days with no activity. Feel free to make a new PR if you wish to continue"
|
||||||
|
days-before-pr-stale: 350
|
||||||
|
days-before-pr-close: 15
|
||||||
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
.venv/
|
||||||
|
__pycache__/
|
||||||
|
coverage/
|
||||||
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
[submodule "vendor/bats-all"]
|
||||||
|
path = vendor/bats-all
|
||||||
|
url = https://github.com/hyperupcall/bats-all
|
||||||
5
.pytest.ini
Normal file
5
.pytest.ini
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
[pytest]
|
||||||
|
minversion = 7.4
|
||||||
|
addopts = -ra -q
|
||||||
|
testpaths = tests
|
||||||
|
faulthandler_timeout = 5
|
||||||
296
AUTHORS
Normal file
296
AUTHORS
Normal file
|
|
@ -0,0 +1,296 @@
|
||||||
|
git-extras is written and maintained by various contributors:
|
||||||
|
|
||||||
|
Maintainers
|
||||||
|
```````````
|
||||||
|
- TJ Holowaychuk <tj@vision-media.ca>
|
||||||
|
- Hemanth.HM <hemanth.hm@gmail.com>
|
||||||
|
- Nimit Kalra <me@nimit.io>
|
||||||
|
- Nicolai Skogheim <nicolai.skogheim@gmail.com>
|
||||||
|
- spacewander <spacewanderlzx@gmail.com>
|
||||||
|
- Edwin Kofler <edwin@kofler.dev>
|
||||||
|
- Leroy <vanpipy@gmail.com>
|
||||||
|
|
||||||
|
Patches and Suggestions
|
||||||
|
```````````````````````
|
||||||
|
- Jonhnny Weslley
|
||||||
|
- Tobias Fendin
|
||||||
|
- LeeW
|
||||||
|
- timfeirg
|
||||||
|
- Niklas Schlimm
|
||||||
|
- nickl-
|
||||||
|
- CJ
|
||||||
|
- Paul Wise
|
||||||
|
- Damien Tardy-Panis
|
||||||
|
- Mark Eissler
|
||||||
|
- Yuriy VG
|
||||||
|
- Leila Muhtasib
|
||||||
|
- Richard Fearn
|
||||||
|
- Bill Wood
|
||||||
|
- Jesús Espino
|
||||||
|
- Rémy HUBSCHER
|
||||||
|
- go2null
|
||||||
|
- Andrew Janke
|
||||||
|
- David Baumgold
|
||||||
|
- Jan Schulz
|
||||||
|
- Kenneth Reitz
|
||||||
|
- Mark Pitman
|
||||||
|
- Peter Benjamin
|
||||||
|
- Günther Grill
|
||||||
|
- Luke Childs
|
||||||
|
- Sasha Khamkov
|
||||||
|
- equt
|
||||||
|
- oikarinen
|
||||||
|
- vyas
|
||||||
|
- Don Harper
|
||||||
|
- Pierre Ayoub
|
||||||
|
- Robin Winslow
|
||||||
|
- Ross Smith II
|
||||||
|
- Yi EungJun
|
||||||
|
- grindhold
|
||||||
|
- wyattscarpenter
|
||||||
|
- Aggelos Orfanakos
|
||||||
|
- Camille Reynders
|
||||||
|
- Carlos Prado
|
||||||
|
- Chris Hall
|
||||||
|
- Damian Krzeminski
|
||||||
|
- NANRI
|
||||||
|
- Alexander Krasnukhin
|
||||||
|
- Phally
|
||||||
|
- Rico Sta. Cruz
|
||||||
|
- Takuma Yamaguchi
|
||||||
|
- thomas menzel
|
||||||
|
- vr8ce
|
||||||
|
- Brian J Brennan
|
||||||
|
- Dominik Gedon
|
||||||
|
- Hogan Long
|
||||||
|
- Ivan Malopinsky
|
||||||
|
- John Bachir
|
||||||
|
- Jonathan "Duke" Leto
|
||||||
|
- Julio Napurí
|
||||||
|
- Justin Dugger
|
||||||
|
- Nils Winkler
|
||||||
|
- Philipp Klose
|
||||||
|
- Raphael Boidol
|
||||||
|
- Richard Russon
|
||||||
|
- Sam Bostock
|
||||||
|
- Vladimir Jimenez
|
||||||
|
- gisphm
|
||||||
|
- jacobherrington
|
||||||
|
- overengineer
|
||||||
|
- phigoro
|
||||||
|
- wooorm
|
||||||
|
- Alexis GRIMALDI
|
||||||
|
- Allan Odgaard
|
||||||
|
- Andre Cerqueira
|
||||||
|
- Austin Ziegler
|
||||||
|
- Beth Skurrie
|
||||||
|
- Brice Dutheil
|
||||||
|
- Curtis McEnroe
|
||||||
|
- Daniele Paolella
|
||||||
|
- David Rogers
|
||||||
|
- Devin Withers
|
||||||
|
- Domenico Rotiroti
|
||||||
|
- Evan Grim
|
||||||
|
- Florian H
|
||||||
|
- Gert Van Gool
|
||||||
|
- Guillaume Seren
|
||||||
|
- Hozefa
|
||||||
|
- Igor Ostapenko
|
||||||
|
- Jean Jordaan
|
||||||
|
- Joshua Li
|
||||||
|
- Justin Guenther
|
||||||
|
- Kylie McClain
|
||||||
|
- Marc Harter
|
||||||
|
- Matiss
|
||||||
|
- Nate Jones
|
||||||
|
- Newell Zhu
|
||||||
|
- Patryk Małek
|
||||||
|
- Paul Schreiber
|
||||||
|
- Richard Littauer
|
||||||
|
- Tjaart van der Walt
|
||||||
|
- Tom Vincent
|
||||||
|
- Wil Moore III
|
||||||
|
- William Montgomery
|
||||||
|
- Ye Lin Aung
|
||||||
|
- luozexuan
|
||||||
|
- roxchgt
|
||||||
|
- soffolk
|
||||||
|
- 0xflotus
|
||||||
|
- Abdullah
|
||||||
|
- Adam Parkin
|
||||||
|
- Adriaan Zonnenberg
|
||||||
|
- Akim Demaille
|
||||||
|
- Alessandro Pagiaro
|
||||||
|
- Alex McHale
|
||||||
|
- Aloxaf
|
||||||
|
- Amir Tocker
|
||||||
|
- Ammar Najjar
|
||||||
|
- Amory Meltzer
|
||||||
|
- Andreas Duering
|
||||||
|
- Andrei Petcu
|
||||||
|
- Andrew Griffiths
|
||||||
|
- Andrew Marcinkevičius
|
||||||
|
- Andrew Starr-Bochicchio
|
||||||
|
- Andrew Sullivan Cant
|
||||||
|
- Andrey Elizarov
|
||||||
|
- Angel Aguilera
|
||||||
|
- Antoine Beaupré
|
||||||
|
- Aurélien Scoubeau
|
||||||
|
- Balazs Nadasdi
|
||||||
|
- Ben Parnell
|
||||||
|
- Beni Ben Zikry
|
||||||
|
- Brandon Zylstra
|
||||||
|
- Brian Goad
|
||||||
|
- Brian Murrell
|
||||||
|
- Bruno Sutic
|
||||||
|
- Caleb Maclennan
|
||||||
|
- Carl Casbolt
|
||||||
|
- Carlos Rodríguez
|
||||||
|
- Christophe Badoit
|
||||||
|
- Ciro Nunes
|
||||||
|
- CleanMachine1
|
||||||
|
- CodeByZach
|
||||||
|
- Craig MacGregor
|
||||||
|
- Dan Goodliffe
|
||||||
|
- Dan Jackson
|
||||||
|
- Dan Kilman
|
||||||
|
- Daniel Bruder
|
||||||
|
- Daniel Lublin
|
||||||
|
- Daniel Schildt
|
||||||
|
- Dave James Miller
|
||||||
|
- David Hartmann
|
||||||
|
- Dominic Barnes
|
||||||
|
- Dung Quang
|
||||||
|
- Edward Betts
|
||||||
|
- Eli Schwartz
|
||||||
|
- Emil Kjelsrud
|
||||||
|
- Erik Skultety
|
||||||
|
- François M
|
||||||
|
- George Crabtree
|
||||||
|
- Gerrit-K
|
||||||
|
- Greg Allen
|
||||||
|
- Guilhem Saurel
|
||||||
|
- Guillermo Rauch
|
||||||
|
- Gunnlaugur Thor Briem
|
||||||
|
- Hasse Ramlev Hansen
|
||||||
|
- Heiko Becker
|
||||||
|
- Hugo Ruiz-Mireles
|
||||||
|
- Isaac Mungai
|
||||||
|
- J.C. Yamokoski
|
||||||
|
- Jack O. Wasey
|
||||||
|
- James Manning
|
||||||
|
- James Zhu
|
||||||
|
- Jan Krueger
|
||||||
|
- Jared Baur
|
||||||
|
- Jarod Stewart
|
||||||
|
- Jason Young
|
||||||
|
- Jens K. Mueller
|
||||||
|
- Jeremy Lin
|
||||||
|
- Jesse Sipprell
|
||||||
|
- Jianjin Fan
|
||||||
|
- Jobin Kurian
|
||||||
|
- Johannes Ewald
|
||||||
|
- John Evans
|
||||||
|
- John Hoffmann
|
||||||
|
- Jon Ander Peñalba
|
||||||
|
- Josh McKinney
|
||||||
|
- Joshua Appelman
|
||||||
|
- José María Gutiérrez @TheTechOddBug
|
||||||
|
- Katrin Leinweber
|
||||||
|
- Kevin Woo
|
||||||
|
- Konstantin Schukraft
|
||||||
|
- Kurban Mallachiev
|
||||||
|
- Leandro López
|
||||||
|
- Lone
|
||||||
|
- Luis Miguel Hernanz
|
||||||
|
- Maarten Winter
|
||||||
|
- Marco Glasbergen
|
||||||
|
- Matan Rosenberg
|
||||||
|
- Mathieu D. (MatToufoutu)
|
||||||
|
- Matt
|
||||||
|
- Matt Colyer
|
||||||
|
- Matt Headley
|
||||||
|
- Matthew Avant
|
||||||
|
- Mattias Andersson
|
||||||
|
- Michael Komitee
|
||||||
|
- Michael Matuzak
|
||||||
|
- Michele Bologna
|
||||||
|
- Montak Oleg
|
||||||
|
- Moritz Grauel
|
||||||
|
- Nate Eagleson
|
||||||
|
- Nate Fischer
|
||||||
|
- Nathan Rajlich
|
||||||
|
- Nick Campbell
|
||||||
|
- Nick Payne
|
||||||
|
- Nicolas Kosinski
|
||||||
|
- Niklas Fiekas
|
||||||
|
- Oliver Kopp
|
||||||
|
- Orestis
|
||||||
|
- Peter Bittner
|
||||||
|
- Piotr Górski
|
||||||
|
- Prayag Verma
|
||||||
|
- Przemek Kitszel
|
||||||
|
- R. Martinho Fernandes
|
||||||
|
- Raphael Fleischlin
|
||||||
|
- Rasmus Wriedt Larsen
|
||||||
|
- René
|
||||||
|
- Revisor
|
||||||
|
- Ricardo Fernández Serrata
|
||||||
|
- Riceball LEE
|
||||||
|
- Rob Kennedy
|
||||||
|
- Robin von Bülow
|
||||||
|
- Russ
|
||||||
|
- Ryan Bohn
|
||||||
|
- Sachin Gupta
|
||||||
|
- Sam Thursfield
|
||||||
|
- Sandro
|
||||||
|
- Sascha Andres
|
||||||
|
- SchleimKeim
|
||||||
|
- Sean Molenaar
|
||||||
|
- Sebastian Gniazdowski
|
||||||
|
- Sebastián Mancilla
|
||||||
|
- Simon Tate
|
||||||
|
- Stefan VanBuren
|
||||||
|
- Stephen Mathieson
|
||||||
|
- Steve Mao
|
||||||
|
- Stu Feldt
|
||||||
|
- SukkaW
|
||||||
|
- Tim Preston
|
||||||
|
- Timothy Hwang
|
||||||
|
- Tin Lai
|
||||||
|
- Todd Wolfson
|
||||||
|
- Tom Andrade
|
||||||
|
- Tom Ashworth
|
||||||
|
- Tony
|
||||||
|
- TweeKane
|
||||||
|
- Valérian Galliat
|
||||||
|
- Vitaly Chikunov
|
||||||
|
- Wei Lee
|
||||||
|
- Wei Wu
|
||||||
|
- Wen Sun
|
||||||
|
- Wiktor Żurawik
|
||||||
|
- Xavier Krantz
|
||||||
|
- Xiaopei Li
|
||||||
|
- Zeeshan Ahmed
|
||||||
|
- ax1036
|
||||||
|
- creynders
|
||||||
|
- dead-horse
|
||||||
|
- eszabpt
|
||||||
|
- fengkx
|
||||||
|
- johnpyp
|
||||||
|
- jykntr
|
||||||
|
- kang
|
||||||
|
- meza
|
||||||
|
- neydroid
|
||||||
|
- nulltask
|
||||||
|
- phanium
|
||||||
|
- sgleizes
|
||||||
|
- tfendin
|
||||||
|
- tiemonl
|
||||||
|
- zentarul
|
||||||
|
- zeroDivisible
|
||||||
|
- zhiyanfoo
|
||||||
|
- Áron Hoffmann
|
||||||
|
- Étienne BERSAC
|
||||||
|
- ☃ pitr
|
||||||
|
- 单元源
|
||||||
34
CONTRIBUTING.md
Normal file
34
CONTRIBUTING.md
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
# Contributing
|
||||||
|
|
||||||
|
Thanks for contributing! Please read this document before you make a PR.
|
||||||
|
|
||||||
|
## Supported Platforms
|
||||||
|
|
||||||
|
Any changes must support the following platforms:
|
||||||
|
|
||||||
|
- macOS
|
||||||
|
- Linux
|
||||||
|
- OpenBSD (You may need to browse their man page)
|
||||||
|
|
||||||
|
Your change must also be compatible with the dependency constraints that we specify in [Installation](./Installation.md). If you aren't sure if a feature is compatible, check the manual or release notes. For example, the Bash changelog is [here](https://git.savannah.gnu.org/cgit/bash.git/tree/NEWS?h=devel).
|
||||||
|
|
||||||
|
If you aren't able to test your new command on a platform, make that clear in your PR; someone else may be able to test it on their system.
|
||||||
|
|
||||||
|
## Adding a New Command
|
||||||
|
|
||||||
|
Let's say you wish to add a new command. Assuming your new command is named `foo`:
|
||||||
|
|
||||||
|
1. Write a bash script under `./bin` called `git-foo`. The script should be started with `#!/usr/bin/env bash`.
|
||||||
|
2. Read `./man/Readme.md` and write documentation for `git-foo`.
|
||||||
|
3. Don't forget to introduce it in `Commands.md`.
|
||||||
|
4. Update `./etc/git-extras-completion.zsh`. Just follow existing code.
|
||||||
|
5. (Optional) Update `./etc/bash_completion.sh`.
|
||||||
|
6. (Optional) Update `./etc/git-extras.fish`.
|
||||||
|
7. (Optional) Add a test under `./tests`.
|
||||||
|
8. Run `./check_integrity.sh foo` to check if all done. (You may also run `./check_integrity.sh` to check all commands.)
|
||||||
|
|
||||||
|
You are welcome to open up an issue to discuss new commands or features before opening a pull request.
|
||||||
|
|
||||||
|
## Submitting a pull request
|
||||||
|
|
||||||
|
Please follow the suggestion in `./.github/PULL_REQUEST_TEMPLATE.md`.
|
||||||
1729
Commands.md
Normal file
1729
Commands.md
Normal file
File diff suppressed because it is too large
Load diff
1700
History.md
1700
History.md
File diff suppressed because it is too large
Load diff
196
Installation.md
Normal file
196
Installation.md
Normal file
|
|
@ -0,0 +1,196 @@
|
||||||
|
# Installation
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
|
||||||
|
Some commands require extra dependencies which are unavailable in some platforms.
|
||||||
|
You may need to install them manually. They are:
|
||||||
|
|
||||||
|
* bash 4.0+
|
||||||
|
* Git 2.17+
|
||||||
|
* `column`
|
||||||
|
* `awk`
|
||||||
|
* `find`
|
||||||
|
* `tput`
|
||||||
|
* cURL (only required for `git-fork`, `git-ignore-io`, `git-pull-request`)
|
||||||
|
* `ps` (only required for `git-changelog`)
|
||||||
|
* `rsync` (only required for `git-rscp`, `git-scp`)
|
||||||
|
* `xargs` (only required for `git-delete-merged-branches`, `git-force-clone`, `git-sed`, `git-scp`)
|
||||||
|
|
||||||
|
If `bash --version` shows a lower version, you need to update it.
|
||||||
|
For example, the default bash in Mac is usually too old and you may need to update it via `brew install bash`.
|
||||||
|
|
||||||
|
## Installing with a package manager
|
||||||
|
|
||||||
|
Note that only the Homebrew package is maintained by the git-extras developers directly.
|
||||||
|
Other packages are maintained by the distribution's packagers or third-party volunteers.
|
||||||
|
|
||||||
|
[](https://repology.org/project/git-extras/versions)
|
||||||
|
|
||||||
|
### Debian
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ sudo $apt_pref update
|
||||||
|
$ sudo $apt_pref install git-extras
|
||||||
|
```
|
||||||
|
|
||||||
|
### Fedora
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ sudo dnf install git-extras
|
||||||
|
```
|
||||||
|
|
||||||
|
### openSUSE
|
||||||
|
|
||||||
|
Substitute your openSUSE version in the command below (in this case we are considering openSUSE Leap 15.6):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ sudo zypper ar https://download.opensuse.org/repositories/devel:/tools:/scm/15.6/devel:tools:scm.repo
|
||||||
|
```
|
||||||
|
|
||||||
|
and install it:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ sudo zypper in -y git-extras
|
||||||
|
```
|
||||||
|
|
||||||
|
### RHEL/CentOS (requires [EPEL](https://fedoraproject.org/wiki/EPEL))
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ sudo yum install git-extras
|
||||||
|
```
|
||||||
|
|
||||||
|
### Ubuntu
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ sudo apt-get install git-extras
|
||||||
|
```
|
||||||
|
|
||||||
|
### Nix/NixOS
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ nix-env -i git-extras
|
||||||
|
```
|
||||||
|
|
||||||
|
### CRUX
|
||||||
|
|
||||||
|
[Abdullah](https://github.com/AWAN) has written a [Pkgfile](https://abdullah.today/ports/git-extras/Pkgfile) for his beloved [distro](https://crux.nu).
|
||||||
|
|
||||||
|
### Homebrew
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ brew install git-extras
|
||||||
|
```
|
||||||
|
|
||||||
|
Installing from Homebrew will not give you the option omit certain `git-extras` if they conflict with existing git aliases. To have this option, build from source.
|
||||||
|
|
||||||
|
### Arch Linux
|
||||||
|
|
||||||
|
* [git-extras](https://aur.archlinux.org/packages/git-extras/)
|
||||||
|
* [git-extras-git](https://aur.archlinux.org/packages/git-extras-git/)
|
||||||
|
|
||||||
|
### Windows
|
||||||
|
|
||||||
|
First, please install `Git for Windows 2.x` from 'https://github.com/git-for-windows/git/releases'.
|
||||||
|
Second, clone the `git-extras` repo into any folder you like.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/tj/git-extras.git
|
||||||
|
# checkout the latest tag (optional)
|
||||||
|
git checkout $(git describe --tags $(git rev-list --tags --max-count=1))
|
||||||
|
```
|
||||||
|
|
||||||
|
After that, execute the `install.cmd` in the command prompt. If you installed
|
||||||
|
git as admin, you need to run this prompt as admin, too. Per default, it finds
|
||||||
|
a `Git for Windows 2.x` if it's in the path (first path in `where git.exe` wins)
|
||||||
|
or installed in the default location `%ProgramFiles%\Git`. The fallback is
|
||||||
|
`C:\SCM\PortableGit`. If you didn't install git into one of these dirs, you have
|
||||||
|
to supply the path for the install location, e.g. if git is installed
|
||||||
|
in `c:\git`:
|
||||||
|
|
||||||
|
```batch
|
||||||
|
install.cmd "C:\git"
|
||||||
|
```
|
||||||
|
|
||||||
|
Last, to use `git summary` and `git ignore-io`, you need to copy
|
||||||
|
`column.exe` from a [msys2][1] installation from `folder-your-msys2-installed/usr/bin`
|
||||||
|
to `folder-your-git-installed/usr/bin` or wait for git 2.7.1, which will include column.exe.
|
||||||
|
|
||||||
|
### FreeBSD
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ pkg install git-extras
|
||||||
|
```
|
||||||
|
|
||||||
|
### BSD
|
||||||
|
|
||||||
|
Use the instructions to build from source below. Make sure you are using `gmake` (GNU `make`) instead of `make`.
|
||||||
|
|
||||||
|
## Building from source
|
||||||
|
|
||||||
|
Read [Dependencies](#dependencies) and ensure they are installed.
|
||||||
|
|
||||||
|
Obtain the git-extras source by cloning [its GitHub repo](https://github.com/tj/git-extras.git) or downloading a tarball of a [release](https://github.com/tj/git-extras/releases). Then install it by doing `make install` from the source tree.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ git clone https://github.com/tj/git-extras.git
|
||||||
|
$ cd git-extras
|
||||||
|
# checkout the latest tag
|
||||||
|
$ git checkout $(git describe --tags $(git rev-list --tags --max-count=1))
|
||||||
|
$ [sudo] make install
|
||||||
|
```
|
||||||
|
|
||||||
|
By default, git-extras is installed under `/usr/local`. To install it at an
|
||||||
|
alternate location, specify a `PREFIX` when calling `make`.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Non-root users can install under their home directory
|
||||||
|
make install PREFIX=$HOME/.local
|
||||||
|
|
||||||
|
# For third-party software kept under /opt
|
||||||
|
make install PREFIX=/opt
|
||||||
|
```
|
||||||
|
|
||||||
|
If you want to relocate the bulk of the installation but still have configuration
|
||||||
|
files installed to the system `/etc` dir or other alternate location, you can
|
||||||
|
specify `SYSCONFDIR` in addition to `PREFIX`.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
$ sudo make install PREFIX=/usr/local SYSCONFDIR=/etc
|
||||||
|
```
|
||||||
|
|
||||||
|
See the Makefile for details on advanced configuration options.
|
||||||
|
|
||||||
|
## Installing from network
|
||||||
|
|
||||||
|
One-liner:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -sSL https://raw.githubusercontent.com/tj/git-extras/main/install.sh | sudo bash /dev/stdin
|
||||||
|
```
|
||||||
|
|
||||||
|
## Installing as Zsh plugin
|
||||||
|
|
||||||
|
[ZInit](https://github.com/zdharma/zinit) can install git-extras by using:
|
||||||
|
|
||||||
|
```zsh
|
||||||
|
zinit ice as"program" pick"$ZPFX/bin/git-*" src"etc/git-extras-completion.zsh" make"PREFIX=$ZPFX"
|
||||||
|
zinit light tj/git-extras
|
||||||
|
|
||||||
|
# or with the for syntax + async load
|
||||||
|
zinit lucid wait'0a' for \
|
||||||
|
as"program" pick"$ZPFX/bin/git-*" src"etc/git-extras-completion.zsh" make"PREFIX=$ZPFX" tj/git-extras
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
`$ZPFX` is `~/.zinit/polaris` by default. Use `zinit update tj/git-extras` to update.
|
||||||
|
This method installs in `$HOME`, so you don't need to ask administrator to install package.
|
||||||
|
|
||||||
|
## Updating
|
||||||
|
|
||||||
|
If you installed git-extras with a package manager, use that package manager's tools to update it.
|
||||||
|
|
||||||
|
If you installed git-extras from source, you can run `git-extras update` to update it to the latest release. Be aware that this may lose any configuration options you specified during the original installation.
|
||||||
|
|
||||||
|
Enjoy `git-extras`!
|
||||||
|
|
||||||
|
[1]: https://sourceforge.net/projects/msys2/
|
||||||
22
LICENSE
Normal file
22
LICENSE
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
(The MIT License)
|
||||||
|
|
||||||
|
Copyright (c) 2010 TJ Holowaychuk <tj@vision-media.ca> and Contributors
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
a copy of this software and associated documentation files (the
|
||||||
|
'Software'), to deal in the Software without restriction, including
|
||||||
|
without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be
|
||||||
|
included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||||
|
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||||
|
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||||
|
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||||
|
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
157
Makefile
157
Makefile
|
|
@ -1,29 +1,146 @@
|
||||||
|
PREFIX ?= /usr/local
|
||||||
|
BINPREFIX ?= "$(PREFIX)/bin"
|
||||||
|
SYSCONFDIR ?= $(PREFIX)/etc
|
||||||
|
SHELL := bash
|
||||||
|
|
||||||
PREFIX = /usr/local
|
OS = $(shell uname)
|
||||||
|
ifeq ($(OS), FreeBSD)
|
||||||
|
MANPREFIX ?= "$(PREFIX)/man/man1"
|
||||||
|
ifeq ($(MANPREFIX), /usr/local)
|
||||||
|
MANPREFIX = "/usr/local/man/man1"
|
||||||
|
endif
|
||||||
|
else
|
||||||
|
MANPREFIX ?= "$(PREFIX)/share/man/man1"
|
||||||
|
endif
|
||||||
|
ifeq ($(OS), Darwin)
|
||||||
|
COMPL_DIR ?= "$(DESTDIR)$(SYSCONFDIR)/bash_completion.d"
|
||||||
|
else ifeq ($(OS), FreeBSD)
|
||||||
|
COMPL_DIR ?= "$(DESTDIR)$(SYSCONFDIR)/bash_completion.d"
|
||||||
|
else
|
||||||
|
COMPL_DIR ?= "$(DESTDIR)$(SYSCONFDIR)/bash-completion/completions"
|
||||||
|
endif
|
||||||
|
|
||||||
BINS = \
|
BINS = $(wildcard bin/git-*)
|
||||||
git-count \
|
MANS = $(wildcard man/git-*.md)
|
||||||
git-ignore \
|
MAN_BINS = $(filter-out man/git-extras.md, $(MANS))
|
||||||
git-changelog \
|
MAN_HTML = $(MANS:.md=.html)
|
||||||
git-delete-branch \
|
MAN_PAGES = $(MANS:.md=.1)
|
||||||
git-delete-tag \
|
CODE_DIR := $(dir $(abspath $(lastword $(MAKEFILE_LIST))))
|
||||||
git-repl \
|
INSTALL_VIA ?= source
|
||||||
git-commits-since \
|
# Libraries used by all commands
|
||||||
git-summary \
|
LIB = "helper/reset-env" "helper/git-extra-utility"
|
||||||
git-contrib \
|
|
||||||
git-release
|
|
||||||
|
|
||||||
install:
|
COMMANDS = $(subst bin/, , $(BINS))
|
||||||
@echo "... installing to $(PREFIX)/bin"
|
|
||||||
@$(foreach BIN, $(BINS), \
|
default: install
|
||||||
echo "... installing $(BIN)"; \
|
|
||||||
cp -f bin/$(BIN) $(PREFIX)/bin/$(BIN); \
|
docs: $(MAN_HTML) $(MAN_PAGES)
|
||||||
|
|
||||||
|
check:
|
||||||
|
@echo "Check dependencies before installation"
|
||||||
|
@./check_dependencies.sh
|
||||||
|
@echo
|
||||||
|
|
||||||
|
install: check
|
||||||
|
@if [ "$(INSTALL_VIA)" = brew ]; then \
|
||||||
|
git apply brew-release.patch || { echo "Can't apply brew release patch"; exit 1; } \
|
||||||
|
fi
|
||||||
|
mkdir -p $(DESTDIR)$(MANPREFIX)
|
||||||
|
mkdir -p $(DESTDIR)$(BINPREFIX)
|
||||||
|
@echo '... installing bins to '$(DESTDIR)$(BINPREFIX)
|
||||||
|
@echo '... installing man pages to '$(DESTDIR)$(MANPREFIX)
|
||||||
|
$(eval TEMPFILE := $(shell mktemp -q $${TMPDIR:-/tmp}/git-extras.XXXXXX 2>/dev/null || mktemp -q))
|
||||||
|
@# chmod from rw-------(default) to rwxrwxr-x, so that users can exec the scripts
|
||||||
|
@chmod 775 $(TEMPFILE)
|
||||||
|
$(eval EXISTED_ALIASES := $(shell \
|
||||||
|
git config --get-regexp 'alias\..*' | awk '{print "git-" substr($$1, 7)}'))
|
||||||
|
@$(foreach COMMAND, $(COMMANDS), \
|
||||||
|
should_install='yes'; \
|
||||||
|
if [ ! -z "$(filter $(COMMAND), $(EXISTED_ALIASES))" ] && [ "$$SKIP_CONFLICT_CHECK" != yes ]; then \
|
||||||
|
read -p "$(COMMAND) conflicts with an alias, still install it? [y/n]: " answer; \
|
||||||
|
if [ "$$answer" = 'n' ] || [ "$$answer" = 'N' ]; then \
|
||||||
|
should_install="no"; \
|
||||||
|
fi; \
|
||||||
|
fi; \
|
||||||
|
if [ "$$should_install" = 'yes' ]; then \
|
||||||
|
echo "... installing $(COMMAND)"; \
|
||||||
|
head -1 bin/$(COMMAND) > $(TEMPFILE); \
|
||||||
|
cat $(LIB) >> $(TEMPFILE); \
|
||||||
|
if ! grep "$(COMMAND)" not_need_git_repo >/dev/null; then \
|
||||||
|
cat ./helper/is-git-repo >> $(TEMPFILE); \
|
||||||
|
fi; \
|
||||||
|
if grep "$(COMMAND)" need_git_commit >/dev/null; then \
|
||||||
|
cat ./helper/has-git-commit >> $(TEMPFILE); \
|
||||||
|
fi; \
|
||||||
|
tail -n +2 bin/$(COMMAND) >> $(TEMPFILE); \
|
||||||
|
cp -f $(TEMPFILE) $(DESTDIR)$(BINPREFIX)/$(COMMAND); \
|
||||||
|
fi; \
|
||||||
)
|
)
|
||||||
|
@if [ -z "$(wildcard man/git-*.1)" ]; then \
|
||||||
|
echo "WARNING: man pages not created, use 'make docs' (which requires 'ronn' ruby lib)"; \
|
||||||
|
else \
|
||||||
|
cp -f man/git-*.1 $(DESTDIR)$(MANPREFIX); \
|
||||||
|
echo "cp -f man/git-*.1 $(DESTDIR)$(MANPREFIX)"; \
|
||||||
|
fi
|
||||||
|
@mkdir -p $(COMPL_DIR)
|
||||||
|
cp -f etc/bash_completion.sh $(COMPL_DIR)/git-extras
|
||||||
|
@echo ""
|
||||||
|
@echo "If you are a zsh user, you may want to 'source $(CODE_DIR)etc/git-extras-completion.zsh'" \
|
||||||
|
"and put this line into ~/.zshrc to enable zsh completion"
|
||||||
|
@echo "If you are a fish user, you may want to copy or link '$(CODE_DIR)etc/git-extras.fish'" \
|
||||||
|
"to '~/.config/fish/completions/'"
|
||||||
|
|
||||||
|
man/index.txt: $(MANS)
|
||||||
|
echo '# manuals' > $@.tmp
|
||||||
|
for file in $(sort $^) ; do \
|
||||||
|
extra=$${file%.md} ; \
|
||||||
|
extra=$${extra#man/} ; \
|
||||||
|
echo "$$extra(1) $$extra" >> $@.tmp ; \
|
||||||
|
done
|
||||||
|
mv -f $@.tmp $@
|
||||||
|
|
||||||
|
man/git-extras.md: $(MAN_BINS)
|
||||||
|
ln=$$(awk '/## COMMANDS/{print NR};' $@) ; \
|
||||||
|
awk "NR <= $$ln+1" $@ > $@.tmp
|
||||||
|
for file in $(sort $^) ; do \
|
||||||
|
head -n1 $$file | \
|
||||||
|
sed 's/^/ - **/;s/ -- /** /' >> $@.tmp ; \
|
||||||
|
done
|
||||||
|
ln=$$(awk '/## AUTHOR/{print NR};' $@) ; \
|
||||||
|
awk "NR >= $$ln-1" $@ >> $@.tmp
|
||||||
|
mv -f $@.tmp $@
|
||||||
|
|
||||||
|
man/%.html: man/%.md man/index.txt
|
||||||
|
ronn \
|
||||||
|
--manual "Git Extras" \
|
||||||
|
--html \
|
||||||
|
--pipe \
|
||||||
|
$< > $@
|
||||||
|
|
||||||
|
man/%.1: man/%.md
|
||||||
|
ronn -r \
|
||||||
|
--manual "Git Extras" \
|
||||||
|
--pipe \
|
||||||
|
$< > $@
|
||||||
|
|
||||||
uninstall:
|
uninstall:
|
||||||
@$(foreach BIN, $(BINS), \
|
@$(foreach BIN, $(BINS), \
|
||||||
echo "... uninstalling $(PREFIX)/bin/$(BIN)"; \
|
echo "... uninstalling $(DESTDIR)$(BINPREFIX)/$(notdir $(BIN))"; \
|
||||||
rm -f $(PREFIX)/bin/$(BIN); \
|
rm -f $(DESTDIR)$(BINPREFIX)/$(notdir $(BIN)); \
|
||||||
)
|
)
|
||||||
|
@$(foreach MAN, $(MAN_PAGES), \
|
||||||
|
echo "... uninstalling $(DESTDIR)$(MANPREFIX)/$(notdir $(MAN))"; \
|
||||||
|
rm -f $(DESTDIR)$(MANPREFIX)/$(notdir $(MAN)); \
|
||||||
|
)
|
||||||
|
rm -f $(COMPL_DIR)/git-extras
|
||||||
|
|
||||||
.PHONY: install uninstall
|
clean: docclean
|
||||||
|
|
||||||
|
docclean:
|
||||||
|
rm -f man/*.1
|
||||||
|
rm -f man/*.html
|
||||||
|
|
||||||
|
test:
|
||||||
|
bats ./tests
|
||||||
|
|
||||||
|
.PHONY: default docs check install uninstall clean docclean test
|
||||||
|
|
|
||||||
202
Readme.md
202
Readme.md
|
|
@ -1,200 +1,34 @@
|
||||||
|
|
||||||
# Git Extras
|
# Git Extras
|
||||||
|
|
||||||
Little git extras.
|
Little git extras.
|
||||||
|
|
||||||
|
## Screencasts
|
||||||
|
|
||||||
|
Just getting started? Check out these screencasts:
|
||||||
|
|
||||||
|
* [introduction](https://vimeo.com/45506445) ([archive.org link](https://web.archive.org/web/20230219181235id_/vod-progressive.akamaized.net/exp=1676834145~acl=%2Fvimeo-prod-skyfire-std-us%2F01%2F4101%2F1%2F45506445%2F107111328.mp4~hmac=065b68f23c4b6a222463097b36d1c346995a9559baa9b819972da95550018471/vimeo-prod-skyfire-std-us/01/4101/1/45506445/107111328.mp4)) -- covers `git-ignore`, `git-setup`, `git-changelog`, `git-release`, `git-effort` and more
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
$ make install
|
See the [Installation](Installation.md) page.
|
||||||
|
|
||||||
## Commands
|
## Commands
|
||||||
|
|
||||||
- git summary
|
Go to the [Commands](Commands.md) page for basic usage and examples.
|
||||||
- git changelog
|
|
||||||
- git commits-since
|
|
||||||
- git count
|
|
||||||
- git delete-branch
|
|
||||||
- git delete-tag
|
|
||||||
- git ignore
|
|
||||||
- git release
|
|
||||||
- git contrib
|
|
||||||
- git repl
|
|
||||||
|
|
||||||
## git-contrib
|
__GIT utilities__ -- repo summary, repl, changelog population, author commit percentages and more
|
||||||
|
|
||||||
Output a user's contributions to a project, based on the author name:
|
## Contributing
|
||||||
|
|
||||||
$ git contrib visionmedia
|
Interested in contributing? Awesome!
|
||||||
visionmedia (18):
|
|
||||||
Export STATUS_CODES
|
|
||||||
Replaced several Array.prototype.slice.call() calls with Array.prototype.unshift.call()
|
|
||||||
Moved help msg to node-repl
|
|
||||||
Added multiple arg support for sys.puts(), print(), etc.
|
|
||||||
Fix stack output on socket error
|
|
||||||
...
|
|
||||||
|
|
||||||
## git-summary
|
Please read [Contributing](CONTRIBUTING.md) before you make a PR, thanks!
|
||||||
|
|
||||||
Outputs a repo summary:
|
## The change of the default branch
|
||||||
|
|
||||||
$ git summary
|
As of Git Extras 6.4 the assumed default branch name changed from `master` to `main`.
|
||||||
|
This affects the Git Extras commands `git archive-file`, `git delete-merged-branches`, `git delta`, `git pull-request`, `git show-merged-branches`, `git show-unmerged-branches`, and `git squash`.
|
||||||
|
|
||||||
project: express
|
To change the default branch name to `master`: change either the configuration `git-extras.default-branch` or `init.defaultBranch` to `master`; the former takes precedence.
|
||||||
commits: 1893
|
|
||||||
files : 111
|
|
||||||
authors:
|
|
||||||
1285 visionmedia
|
|
||||||
478 Tj Holowaychuk
|
|
||||||
48 Aaron Heckmann
|
|
||||||
34 csausdev
|
|
||||||
26 ciaranj
|
|
||||||
6 Guillermo Rauch
|
|
||||||
3 Nick Poulden
|
|
||||||
2 Brian McKinney
|
|
||||||
2 Benny Wong
|
|
||||||
1 Justin Lilly
|
|
||||||
1 James Herdman
|
|
||||||
1 Adam Sanderson
|
|
||||||
1 Viktor Kelemen
|
|
||||||
1 Gregory Ritter
|
|
||||||
1 Greg Ritter
|
|
||||||
1 ewoudj
|
|
||||||
1 isaacs
|
|
||||||
1 Matt Colyer
|
|
||||||
|
|
||||||
|
For example, `git config git-extras.default-branch master`.
|
||||||
## git-repl
|
|
||||||
|
|
||||||
GIT read-eval-print-loop:
|
|
||||||
|
|
||||||
$ git repl
|
|
||||||
|
|
||||||
git> ls-files
|
|
||||||
History.md
|
|
||||||
Makefile
|
|
||||||
Readme.md
|
|
||||||
bin/git-changelog
|
|
||||||
bin/git-count
|
|
||||||
bin/git-delete-branch
|
|
||||||
bin/git-delete-tag
|
|
||||||
bin/git-ignore
|
|
||||||
bin/git-release
|
|
||||||
|
|
||||||
git> quit
|
|
||||||
|
|
||||||
## git-commits-since [date]
|
|
||||||
|
|
||||||
List of commits since the given _date_, defaulting to "last week":
|
|
||||||
|
|
||||||
$ git commits-since
|
|
||||||
... changes since last week
|
|
||||||
TJ Holowaychuk - Fixed readme
|
|
||||||
TJ Holowaychuk - Added git-repl
|
|
||||||
TJ Holowaychuk - Added git-delete-tag
|
|
||||||
TJ Holowaychuk - Added git-delete-branch
|
|
||||||
|
|
||||||
$ git commits-since yesterday
|
|
||||||
... changes since yesterday
|
|
||||||
TJ Holowaychuk - Fixed readme
|
|
||||||
|
|
||||||
## git-count
|
|
||||||
|
|
||||||
Output commit total:
|
|
||||||
|
|
||||||
$ git count
|
|
||||||
|
|
||||||
total 1844
|
|
||||||
|
|
||||||
Output verbose commit count details:
|
|
||||||
|
|
||||||
$ git count --all
|
|
||||||
|
|
||||||
visionmedia (1285)
|
|
||||||
Tj Holowaychuk (430)
|
|
||||||
Aaron Heckmann (48)
|
|
||||||
csausdev (34)
|
|
||||||
ciaranj (26)
|
|
||||||
Guillermo Rauch (6)
|
|
||||||
Brian McKinney (2)
|
|
||||||
Nick Poulden (2)
|
|
||||||
Benny Wong (2)
|
|
||||||
Justin Lilly (1)
|
|
||||||
isaacs (1)
|
|
||||||
Adam Sanderson (1)
|
|
||||||
Viktor Kelemen (1)
|
|
||||||
Gregory Ritter (1)
|
|
||||||
Greg Ritter (1)
|
|
||||||
ewoudj (1)
|
|
||||||
James Herdman (1)
|
|
||||||
Matt Colyer (1)
|
|
||||||
|
|
||||||
total 1844
|
|
||||||
|
|
||||||
## git-release
|
|
||||||
|
|
||||||
Release commit with the given <tag>.
|
|
||||||
|
|
||||||
$ git release 0.1.0
|
|
||||||
|
|
||||||
Does the following:
|
|
||||||
|
|
||||||
- Commits changes (to changelog etc) with message "Release <tag>"
|
|
||||||
- Tags with the given <tag>
|
|
||||||
- Pushes the branch / tags
|
|
||||||
|
|
||||||
## git-ignore
|
|
||||||
|
|
||||||
To lazy to open up _.gitignore_? me too! simply pass some patterns:
|
|
||||||
|
|
||||||
$ git ignore build "*.o" "*.log"
|
|
||||||
... added 'build'
|
|
||||||
... added '*.o'
|
|
||||||
... added '*.log'
|
|
||||||
|
|
||||||
## git delete-branch <name>
|
|
||||||
|
|
||||||
Deletes local and remote branch _name_.
|
|
||||||
|
|
||||||
$ git delete-branch integration
|
|
||||||
|
|
||||||
## git delete-tag <name>
|
|
||||||
|
|
||||||
Deletes local and remote tag _name_.
|
|
||||||
|
|
||||||
$ git delete-tag 0.0.1
|
|
||||||
|
|
||||||
## git-changelog
|
|
||||||
|
|
||||||
Populates the file named matching _change|history -i_ with the commits
|
|
||||||
since the previous tag or since the project began when no tags are present. Opens the changelog in **$EDITOR** when set.
|
|
||||||
|
|
||||||
$ git changelog && cat History.md
|
|
||||||
|
|
||||||
n.n.n / 2010-08-05
|
|
||||||
==================
|
|
||||||
|
|
||||||
* Docs for git-ignore. Closes #3
|
|
||||||
* Merge branch 'ignore'
|
|
||||||
* Added git-ignore
|
|
||||||
* Fixed <tag> in docs
|
|
||||||
* Install docs
|
|
||||||
* Merge branch 'release'
|
|
||||||
* Added git-release
|
|
||||||
* Passing args to git shortlog
|
|
||||||
* Added --all support to git-count
|
|
||||||
* Initial commit
|
|
||||||
|
|
||||||
Listing commits:
|
|
||||||
|
|
||||||
$ git changelog --list
|
|
||||||
|
|
||||||
* Docs for git-ignore. Closes #3
|
|
||||||
* Merge branch 'ignore'
|
|
||||||
* Added git-ignore
|
|
||||||
* Fixed <tag> in docs
|
|
||||||
* Install docs
|
|
||||||
* Merge branch 'release'
|
|
||||||
* Added git-release
|
|
||||||
* Passing args to git shortlog
|
|
||||||
* Added --all support to git-count
|
|
||||||
* Initial commit
|
|
||||||
|
|
|
||||||
45
bin/git-abort
Executable file
45
bin/git-abort
Executable file
|
|
@ -0,0 +1,45 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# git rev-parse emits an error if not in a git repo so only need to bail out
|
||||||
|
gitdir="$(git rev-parse --git-dir)" || exit
|
||||||
|
|
||||||
|
discover_op() {
|
||||||
|
local op
|
||||||
|
for op in cherry_pick merge rebase revert ; do
|
||||||
|
if [ -f "${gitdir}/${op^^}_HEAD" ]; then
|
||||||
|
echo "${op/_/-}"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
validate_op() {
|
||||||
|
local op="$1"
|
||||||
|
if [ -z "$op" ]; then
|
||||||
|
echo "No active operation found" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if [[ "$(echo "$op" | wc -l)" -gt 1 ]]; then
|
||||||
|
echo "Multiple active operations found:" >&2
|
||||||
|
for o in $op; do
|
||||||
|
echo " - $o: HEAD: $(cat "${gitdir}/${o}_HEAD")" >&2
|
||||||
|
done
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
discover_action() {
|
||||||
|
local action=${1/git-/}
|
||||||
|
if [ "$action" != "abort" ] && [ "$action" != "continue" ]; then
|
||||||
|
echo "Invalid action: $1" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "$action"
|
||||||
|
}
|
||||||
|
|
||||||
|
action=$(discover_action "$(basename "$0")")
|
||||||
|
op=$(discover_op)
|
||||||
|
validate_op "$op"
|
||||||
|
|
||||||
|
git "$op" "--$action"
|
||||||
45
bin/git-alias
Executable file
45
bin/git-alias
Executable file
|
|
@ -0,0 +1,45 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
options=""
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<HERE
|
||||||
|
usage: git alias [options] # list all aliases
|
||||||
|
or: git alias [options] <search-pattern> # show aliases matching pattern
|
||||||
|
or: git alias [options] <alias-name> <command> # alias a command
|
||||||
|
options:
|
||||||
|
|
||||||
|
--global Show or create alias in the system config
|
||||||
|
--local Show or create alias in the repository config
|
||||||
|
HERE
|
||||||
|
}
|
||||||
|
|
||||||
|
if [[ "$1" == "--local" || "$1" == "--global" ]]; then
|
||||||
|
options=$1
|
||||||
|
shift
|
||||||
|
fi
|
||||||
|
|
||||||
|
case $# in
|
||||||
|
0)
|
||||||
|
if [[ -z "$options" ]]; then
|
||||||
|
git config --get-regexp 'alias.*' | sed 's/^alias\.//' | sed 's/[ ]/ = /' | sort
|
||||||
|
else
|
||||||
|
git config "$options" --get-regexp 'alias.*' | sed 's/^alias\.//' | sed 's/[ ]/ = /' | sort
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
1)
|
||||||
|
if [[ -z "$options" ]]; then
|
||||||
|
git config --get-regexp 'alias.*' | sed 's/^alias\.//' | sed 's/[ ]/ = /' | sort | grep -e "$1"
|
||||||
|
else
|
||||||
|
git config "$options" --get-regexp 'alias.*' | sed 's/^alias\.//' | sed 's/[ ]/ = /' | sort | grep -e "$1"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
2)
|
||||||
|
if [[ -z "$options" ]]; then
|
||||||
|
git config alias."$1" "$2"
|
||||||
|
else
|
||||||
|
git config "$options" alias."$1" "$2"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
*) >&2 echo "error: too many arguments." && usage && exit 1 ;;
|
||||||
|
esac
|
||||||
37
bin/git-archive-file
Executable file
37
bin/git-archive-file
Executable file
|
|
@ -0,0 +1,37 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# extract current branch name
|
||||||
|
BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null)
|
||||||
|
|
||||||
|
# get name of the most top folder of current directory, used for the
|
||||||
|
# output filename
|
||||||
|
ARCHIVE_NAME=$(basename "$(git rev-parse --show-toplevel)")
|
||||||
|
|
||||||
|
if [[ $BRANCH = tags* ]]; then
|
||||||
|
BRANCH=$(git describe)
|
||||||
|
echo Building for tag "\"$BRANCH\""
|
||||||
|
FILENAME=$ARCHIVE_NAME.$BRANCH.zip
|
||||||
|
else
|
||||||
|
echo Building archive on branch "\"$BRANCH\""
|
||||||
|
# get a version string, so archives will not be overwritten when creating
|
||||||
|
# many of them
|
||||||
|
VERSION=$(git describe --always --long)
|
||||||
|
# if not on master append branch name into the filename
|
||||||
|
if [ "$BRANCH" = "$(git_extra_default_branch)" ]; then
|
||||||
|
FILENAME=$ARCHIVE_NAME.$VERSION.zip
|
||||||
|
else
|
||||||
|
FILENAME=$ARCHIVE_NAME.$VERSION.$BRANCH.zip
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# rename invalid chars for the file path
|
||||||
|
FILENAME=${FILENAME//\//-}
|
||||||
|
FILENAME=${FILENAME//\\/-}
|
||||||
|
# combine path and filename
|
||||||
|
OUTPUT=$PWD/$FILENAME
|
||||||
|
|
||||||
|
# building archive
|
||||||
|
git archive --format zip --output "$OUTPUT" "$BRANCH"
|
||||||
|
|
||||||
|
# also display size of the resulting file
|
||||||
|
echo Saved to \""$FILENAME"\" \("$(du -h "$OUTPUT" | cut -f1)"\)
|
||||||
54
bin/git-authors
Executable file
54
bin/git-authors
Executable file
|
|
@ -0,0 +1,54 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
LIST=false
|
||||||
|
NO_EMAIL=false
|
||||||
|
FILE=""
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case $1 in
|
||||||
|
-l|--list )
|
||||||
|
LIST=true
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--no-email )
|
||||||
|
NO_EMAIL=true
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
* )
|
||||||
|
break
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if ! $LIST; then
|
||||||
|
FILE=$1
|
||||||
|
if [ -z "$FILE" ]; then
|
||||||
|
FILE=$(find . -mindepth 1 -maxdepth 1 \( -iname '*authors*' -o -iname '*contributors*' \) | head -n1)
|
||||||
|
if [ -z "$FILE" ]; then
|
||||||
|
FILE='AUTHORS'
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
#
|
||||||
|
# list authors sorted by number of commits (descending).
|
||||||
|
#
|
||||||
|
|
||||||
|
authors() {
|
||||||
|
if $NO_EMAIL; then
|
||||||
|
# email will be used to uniq authors.
|
||||||
|
git shortlog HEAD -sne | awk '{$1=""; sub(" ", ""); print}' | awk -F'<' '!x[$1]++' | awk -F'<' '!x[$2]++' \
|
||||||
|
| awk -F'<' '{gsub(/ +$/, "", $1); print $1}'
|
||||||
|
else
|
||||||
|
git shortlog HEAD -sne | awk '{$1=""; sub(" ", ""); print}' | awk -F'<' '!x[$1]++' | awk -F'<' '!x[$2]++'
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
#
|
||||||
|
# authors.
|
||||||
|
#
|
||||||
|
|
||||||
|
if $LIST; then
|
||||||
|
authors
|
||||||
|
else
|
||||||
|
authors >> "$FILE"
|
||||||
|
fi
|
||||||
92
bin/git-browse
Executable file
92
bin/git-browse
Executable file
|
|
@ -0,0 +1,92 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
remote=${1:-""}
|
||||||
|
branch=""
|
||||||
|
filename=${2:-""}
|
||||||
|
line1=${3:-""}
|
||||||
|
line2=${4:-""}
|
||||||
|
|
||||||
|
# get remote name
|
||||||
|
if [[ $remote == "" ]]; then
|
||||||
|
branch="$(git rev-parse --abbrev-ref HEAD 2>/dev/null)"
|
||||||
|
remote=$(git config branch."${branch}".remote || echo "origin")
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ $remote == "" ]]; then
|
||||||
|
echo "Remote not found"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
remote_url=$(git remote get-url "$remote") || exit $?
|
||||||
|
|
||||||
|
if [[ $remote_url = git@* ]]; then
|
||||||
|
url=$(echo "$remote_url" | sed -E -e 's/:/\//' -e 's/\.git$//' -e 's/.*@(.*)/http:\/\/\1/')
|
||||||
|
elif [[ $remote_url = http* ]]; then
|
||||||
|
url=${remote_url%.git}
|
||||||
|
fi
|
||||||
|
|
||||||
|
# construct urls
|
||||||
|
commit_hash=$(git rev-parse HEAD 2>/dev/null)
|
||||||
|
commit_or_branch=${commit_hash:-${branch}}
|
||||||
|
|
||||||
|
if [[ $remote_url =~ gitlab ]]; then
|
||||||
|
# construct gitlab urls
|
||||||
|
# https://gitlab.com/<user_or_group>/<repo>/-/blob/<commit_or_branch>/<filename>#L<line1>-<line2>
|
||||||
|
if [[ -n ${filename} ]]; then
|
||||||
|
url="${url}/-/blob/${commit_or_branch}/${filename}"
|
||||||
|
if [[ -n "${line1}" ]]; then
|
||||||
|
url="${url}#L${line1}"
|
||||||
|
if [[ -n "${line2}" ]]; then
|
||||||
|
url="${url}-${line2}"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
elif [[ $remote_url =~ github ]]; then
|
||||||
|
# construct github urls
|
||||||
|
# https://github.com/<user_or_org>/<repo>/blob/<commit_or_branch>/<filename>#L<line1>-L<line2>
|
||||||
|
if [[ -n "${filename}" ]]; then
|
||||||
|
url="${url}/blob/${commit_or_branch}/${filename}"
|
||||||
|
if [[ -n "${line1}" ]]; then
|
||||||
|
url="${url}#L${line1}"
|
||||||
|
if [[ -n "${line2}" ]]; then
|
||||||
|
url="${url}-L${line2}"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
elif [[ $remote_url =~ bitbucket ]]; then
|
||||||
|
# construct bitbucket urls
|
||||||
|
# https://bitbucket.org/<user_or_org>/<repo>/src/<commit_or_branch>/<filename>#lines-<line1>:<line2>
|
||||||
|
if [[ -n ${filename} ]]; then
|
||||||
|
url=${url}/src/${commit_or_branch}/${filename}
|
||||||
|
if [[ -n "${line1}" ]]; then
|
||||||
|
url="${url}#lines-${line1}"
|
||||||
|
if [[ -n "${line2}" ]]; then
|
||||||
|
url="${url}:${line2}"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# open url
|
||||||
|
case "$OSTYPE" in
|
||||||
|
darwin*)
|
||||||
|
# MacOS
|
||||||
|
open "$url"
|
||||||
|
;;
|
||||||
|
msys)
|
||||||
|
# Git-Bash on Windows
|
||||||
|
start "$url"
|
||||||
|
;;
|
||||||
|
linux*)
|
||||||
|
# Handle WSL on Windows
|
||||||
|
if uname -a | grep -i -q Microsoft && command -v powershell.exe; then
|
||||||
|
powershell.exe -NoProfile start "$url"
|
||||||
|
else
|
||||||
|
xdg-open "$url"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
# fall back to xdg-open for BSDs, etc.
|
||||||
|
xdg-open "$url"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
60
bin/git-browse-ci
Executable file
60
bin/git-browse-ci
Executable file
|
|
@ -0,0 +1,60 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -e -o pipefail
|
||||||
|
if [[ $1 == "" ]]
|
||||||
|
then
|
||||||
|
branch=$(git rev-parse --abbrev-ref HEAD &> /dev/null)
|
||||||
|
remote=$(git config branch."${branch}".remote || echo "origin")
|
||||||
|
else
|
||||||
|
remote=$1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -z $remote ]]
|
||||||
|
then
|
||||||
|
echo "Remote not found"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
remote_url=$(git remote get-url "${remote}") || exit $?
|
||||||
|
|
||||||
|
if [[ $remote_url = git@* ]]
|
||||||
|
then
|
||||||
|
url=$(echo "${remote_url}" | sed -E -e 's/:/\//' -e 's/\.git$//' -e 's/.*@(.*)/http:\/\/\1/')
|
||||||
|
elif [[ $remote_url = http* ]]
|
||||||
|
then
|
||||||
|
url=${remote_url%.git}
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ $url =~ github ]]
|
||||||
|
then
|
||||||
|
ci_url=${url}/actions
|
||||||
|
elif [[ $url =~ gitlab ]]
|
||||||
|
then
|
||||||
|
ci_url=${url}/-/pipelines
|
||||||
|
elif [[ $url =~ bitbucket ]]
|
||||||
|
then
|
||||||
|
ci_url=${url}/addon/pipelines/home
|
||||||
|
fi
|
||||||
|
case "$OSTYPE" in
|
||||||
|
darwin*)
|
||||||
|
# MacOS
|
||||||
|
open "${ci_url}"
|
||||||
|
;;
|
||||||
|
msys)
|
||||||
|
# Git-Bash on Windows
|
||||||
|
start "${ci_url}"
|
||||||
|
;;
|
||||||
|
linux*)
|
||||||
|
# Handle WSL on Windows
|
||||||
|
if uname -a | grep -i -q Microsoft && command -v powershell.exe
|
||||||
|
then
|
||||||
|
powershell.exe -NoProfile start "${ci_url}"
|
||||||
|
else
|
||||||
|
xdg-open "${ci_url}"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
# fall back to xdg-open for BSDs, etc.
|
||||||
|
xdg-open "${ci_url}"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
81
bin/git-brv
Executable file
81
bin/git-brv
Executable file
|
|
@ -0,0 +1,81 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# A sorted prettier branch -vv
|
||||||
|
|
||||||
|
if ! (( BASH_VERSINFO[0] > 4 ||
|
||||||
|
BASH_VERSINFO[0] == 4 && BASH_VERSINFO[1] >= 2 )); then
|
||||||
|
printf >&2 'This script requires bash 4.2 or newer\n'
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# allow the user to set a default reverse option in an environment variable
|
||||||
|
reverse=$(git config --get git-extras.brv.reverse || echo false)
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case $1 in
|
||||||
|
-r | --reverse)
|
||||||
|
reverse=true
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
break
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ -t 1 ]]; then
|
||||||
|
shopt -s checkwinsize
|
||||||
|
COLUMNS=$(tput cols)
|
||||||
|
color_branch_local=$(git config --get-color color.branch.local normal)
|
||||||
|
color_branch_current=$(git config --get-color color.branch.current green)
|
||||||
|
color_diff_commit=$(git config --get-color color.diff.commit yellow)
|
||||||
|
color_branch_upstream=$(git config --get-color color.branch.upstream blue)
|
||||||
|
reset=$(tput sgr0)
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
declare -A upstream date hash message
|
||||||
|
eval "$(
|
||||||
|
git for-each-ref --format='upstream[%(refname:short)]=%(upstream:short)' \
|
||||||
|
--shell 'refs/heads/**'
|
||||||
|
)"
|
||||||
|
|
||||||
|
for b in "${!upstream[@]}"; do
|
||||||
|
blen=${#b} ulen=${#upstream[$b]}
|
||||||
|
(( bwidth = blen > bwidth ? blen : bwidth ))
|
||||||
|
(( uwidth = ulen > uwidth ? ulen : uwidth ))
|
||||||
|
IFS=/ read -r 'date[$b]' 'hash[$b]' 'message[$b]' < <(
|
||||||
|
git log --no-walk=unsorted --format=%ct/%h/%s "$b" --
|
||||||
|
)
|
||||||
|
hlen=${#hash[$b]}
|
||||||
|
(( hwidth = hlen > hwidth ? hlen : hwidth ))
|
||||||
|
done
|
||||||
|
|
||||||
|
mapfile -t ordered < <(
|
||||||
|
# the reverse option of git-brv causes the sort to be sorted normally rather than in reverse
|
||||||
|
reverse_opt=""
|
||||||
|
if [[ $reverse = false ]]; then
|
||||||
|
reverse_opt="-r"
|
||||||
|
fi
|
||||||
|
for b in "${!date[@]}"; do
|
||||||
|
printf '%d\t%s\n' "${date[$b]}" "$b"
|
||||||
|
done | sort -n $reverse_opt | cut -f2-
|
||||||
|
)
|
||||||
|
|
||||||
|
current=$(git symbolic-ref -q --short HEAD)
|
||||||
|
|
||||||
|
for b in "${ordered[@]}"; do
|
||||||
|
branch_color=$color_branch_local
|
||||||
|
if [[ $b = "$current" ]]; then
|
||||||
|
branch_color=$color_branch_current
|
||||||
|
fi
|
||||||
|
if [[ -t 1 ]]; then
|
||||||
|
msg=${message[$b]:0:COLUMNS-bwidth-uwidth-hwidth-14}
|
||||||
|
else
|
||||||
|
msg=${message[$b]}
|
||||||
|
fi
|
||||||
|
printf '%(%Y-%m-%d)T %s%*s%s %s%*s%s %s%*s%s %s\n' \
|
||||||
|
"${date[$b]}" \
|
||||||
|
"$branch_color" "-$bwidth" "$b" "$reset" \
|
||||||
|
"$color_branch_upstream" "-$uwidth" "${upstream[$b]}" "$reset" \
|
||||||
|
"$color_diff_commit" "-$hwidth" "${hash[$b]}" "$reset" \
|
||||||
|
"$msg"
|
||||||
|
done
|
||||||
231
bin/git-bulk
Executable file
231
bin/git-bulk
Executable file
|
|
@ -0,0 +1,231 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
inverse=$(tput rev)
|
||||||
|
reset=$(tput sgr0)
|
||||||
|
txtbld=$(tput bold)
|
||||||
|
bldred=${txtbld}$(tput setaf 1)
|
||||||
|
|
||||||
|
# default option settings
|
||||||
|
guardedmode=false
|
||||||
|
singlemode=false
|
||||||
|
allwsmode=false
|
||||||
|
quiet=false
|
||||||
|
no_follow_symlinks=false
|
||||||
|
no_follow_hidden=false
|
||||||
|
|
||||||
|
#
|
||||||
|
# print usage message
|
||||||
|
#
|
||||||
|
usage() {
|
||||||
|
echo 1>&2 "usage: git bulk [--no-follow-symlinks] [--no-follow-hidden] [-q|--quiet] [-g] ([-a]|[-w <ws-name>]) <git command>"
|
||||||
|
echo 1>&2 " git bulk --addworkspace <ws-name> <ws-root-directory> (--from <URL or file>)"
|
||||||
|
echo 1>&2 " git bulk --removeworkspace <ws-name>"
|
||||||
|
echo 1>&2 " git bulk --addcurrent <ws-name>"
|
||||||
|
echo 1>&2 " git bulk --purge"
|
||||||
|
echo 1>&2 " git bulk --listall"
|
||||||
|
}
|
||||||
|
|
||||||
|
cdfail() {
|
||||||
|
echo 1>&2 "failed to change directory: $1"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# add another workspace to global git config
|
||||||
|
addworkspace() {
|
||||||
|
git config --global bulkworkspaces."$wsname" "$wsdir";
|
||||||
|
if [ -n "$source" ]; then
|
||||||
|
if [ ! -d "$wsdir" ]; then echo 1>&2 "Path of workspace doesn't exist, make it first."; exit 1; fi
|
||||||
|
regex='http(s)?://|ssh://|(git@)?.*:.*/.*'
|
||||||
|
if [[ "$source" =~ $regex ]]; then
|
||||||
|
pushd "$wsdir" > /dev/null || cdfail "$wsdir"
|
||||||
|
git clone "$source"
|
||||||
|
popd > /dev/null || cdfail "$OLDPWD"
|
||||||
|
else
|
||||||
|
source=$(realpath "$source" 2>/dev/null)
|
||||||
|
if [ -f "$source" ]; then
|
||||||
|
pushd "$wsdir" > /dev/null || cdfail "$wsdir"
|
||||||
|
while IFS= read -r line; do
|
||||||
|
if [ -n "$line" ]; then
|
||||||
|
# the git clone command to take the complete line in the repository.txt as separate argument. This facilitated the cloning of the repository with a custom folder name.
|
||||||
|
# shellcheck disable=SC2086
|
||||||
|
git clone $line;
|
||||||
|
fi
|
||||||
|
done < "$source"
|
||||||
|
popd > /dev/null || cdfail "$OLDPWD"
|
||||||
|
else
|
||||||
|
echo 1>&2 "format of URL or file unknown"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# add current directory
|
||||||
|
addcurrent() { git config --global bulkworkspaces."$wsname" "$PWD"; }
|
||||||
|
|
||||||
|
# remove workspace from global git config
|
||||||
|
removeworkspace() { checkWSName && git config --global --unset bulkworkspaces."$wsname"; }
|
||||||
|
|
||||||
|
# remove workspace from global git config
|
||||||
|
purge() { git config --global --remove-section bulkworkspaces; }
|
||||||
|
|
||||||
|
# list all current workspace locations defined
|
||||||
|
listall() { git config --global --get-regexp bulkworkspaces; }
|
||||||
|
|
||||||
|
# guarded execution of a git command in one specific repository
|
||||||
|
guardedExecution () {
|
||||||
|
if [ "${quiet?}" != "true" ] || $guardedmode; then
|
||||||
|
echo 1>&2 "${bldred}->${reset} executing ${inverse}git $gitcommand${reset} in repository ${leadingpath%/*}/${bldred}${curdir##*/}${reset}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if $guardedmode; then
|
||||||
|
echo 1>&2 -n " Execute command here (y/n)? "
|
||||||
|
read -n 1 -r </dev/tty; echo 1>&2
|
||||||
|
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
||||||
|
git "$@"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
git "$@"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# check if the passed command is known as a core git command
|
||||||
|
checkGitCommand () {
|
||||||
|
if git help -a | grep -o -q "\b${corecommand}\b"; then
|
||||||
|
echo 1>&2 "Core command \"$corecommand\" accepted."
|
||||||
|
else
|
||||||
|
if git config --get-regexp alias | grep -o -q "\.${corecommand} "; then
|
||||||
|
echo 1>&2 "Alias ${corecommand} accepted."
|
||||||
|
else
|
||||||
|
usage && echo 1>&2 "error: unknown GIT command: $corecommand" && exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# check if workspace name is registered
|
||||||
|
checkWSName () {
|
||||||
|
while read -r workspace; do
|
||||||
|
parseWsName "$workspace"
|
||||||
|
if [[ $rwsname == "$wsname" ]]; then return; fi
|
||||||
|
done <<< "$(listall)"
|
||||||
|
# when here the ws name was not found
|
||||||
|
usage && echo 1>&2 "error: unknown workspace name: $wsname" && exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# parse out wsname from workspacespec
|
||||||
|
parseWsName () {
|
||||||
|
local wsspec="$1"
|
||||||
|
# Get the workspace value from its specification in the `.gitconfig`.
|
||||||
|
# May be an absolute path or a variable name of the form: `$VARNAME`
|
||||||
|
rwsdir=${wsspec#* }
|
||||||
|
if [[ ${rwsdir:0:1} == '$' ]]; then
|
||||||
|
# Dereference the `rwsdir` value which is a variable name.
|
||||||
|
rwsdir_varname=${rwsdir:1}
|
||||||
|
rwsdir=${!rwsdir_varname}
|
||||||
|
if [[ -z "${rwsdir}" ]]; then
|
||||||
|
echo 1>&2 "error: bad environment variable: $rwsdir_varname" && exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
rwsname=${wsspec#*.} && rwsname=${rwsname%% *}
|
||||||
|
}
|
||||||
|
|
||||||
|
# detects the wsname of the current directory
|
||||||
|
wsnameToCurrent () {
|
||||||
|
while read -r workspace; do
|
||||||
|
if [ -z "$workspace" ]; then continue; fi
|
||||||
|
parseWsName "$workspace"
|
||||||
|
if echo "$PWD" | grep -o -q "$rwsdir"; then wsname="$rwsname" && return; fi
|
||||||
|
done <<< "$(listall)"
|
||||||
|
# when here then not in workspace dir
|
||||||
|
echo 1>&2 "error: you are not in a workspace directory. your registered workspaces are:" && \
|
||||||
|
wslist="$(listall)" && echo 1>&2 "${wslist:-'<no workspaces defined yet>'}" && exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# helper to check number of arguments.
|
||||||
|
allowedargcount () {
|
||||||
|
if [ "$paramcount" -ne "${1:-0}" ] && [ "$paramcount" -ne "${2:-0}" ]; then
|
||||||
|
echo 1>&2 "error: wrong number of arguments" && usage;
|
||||||
|
exit 1;
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# execute the bulk operation
|
||||||
|
executBulkOp () {
|
||||||
|
checkGitCommand
|
||||||
|
if ! $allwsmode && ! $singlemode; then wsnameToCurrent; fi # by default git bulk works within the 'current' workspace
|
||||||
|
listall | while read -r workspacespec; do
|
||||||
|
parseWsName "$workspacespec"
|
||||||
|
if [[ -n $wsname ]] && [[ $rwsname != "$wsname" ]]; then continue; fi
|
||||||
|
cd "$rwsdir" || exit 1
|
||||||
|
local actual=$PWD
|
||||||
|
[ "${quiet?}" != "true" ] && echo 1>&2 "Executing bulk operation in workspace ${inverse}$actual${reset}"
|
||||||
|
|
||||||
|
# build `find` flags depending on command-line options
|
||||||
|
local find_flags=()
|
||||||
|
if [[ "$no_follow_symlinks" == true ]]; then
|
||||||
|
find_flags+=(-P)
|
||||||
|
else
|
||||||
|
find_flags+=(-L)
|
||||||
|
fi
|
||||||
|
# find all git repositories under the workspace on which we want to operate
|
||||||
|
readarray allGitFolders < <(find "${find_flags[@]}" . -name ".git" 2>/dev/null)
|
||||||
|
|
||||||
|
for line in "${allGitFolders[@]}"; do
|
||||||
|
local gitrepodir=${line::${#line}-5} # cut the .git part of find results to have the root git directory of that repository
|
||||||
|
cd "$gitrepodir" || exit 1 # into git repo location
|
||||||
|
local curdir=$PWD
|
||||||
|
local leadingpath=${curdir#"${actual}"}
|
||||||
|
# do not execute if we do not want to consider a ".git" directory under a hidden directory
|
||||||
|
if [ $no_follow_hidden = false ] || ! [[ "$leadingpath" =~ "/." ]]; then
|
||||||
|
guardedExecution "$@"
|
||||||
|
fi
|
||||||
|
cd "$rwsdir" || exit 1 # back to origin location of last find command
|
||||||
|
done
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
paramcount="${#}"
|
||||||
|
|
||||||
|
# if no arguments show usage
|
||||||
|
if [[ $paramcount -le 0 ]]; then usage; fi
|
||||||
|
|
||||||
|
# parse command parameters
|
||||||
|
while [ "${#}" -ge 1 ] ; do
|
||||||
|
case "$1" in
|
||||||
|
--quiet|-q) quiet='true' ;;
|
||||||
|
--listall|--purge)
|
||||||
|
butilcommand="${1:2}" && break ;;
|
||||||
|
--removeworkspace|--addcurrent|--addworkspace)
|
||||||
|
butilcommand="${1:2}" && wsname="$2" && wsdir="$3" && if [ "$4" = "--from" ]; then source="$5"; fi && break ;;
|
||||||
|
--no-follow-symlinks)
|
||||||
|
no_follow_symlinks=true ;;
|
||||||
|
--no-follow-hidden)
|
||||||
|
no_follow_hidden=true ;;
|
||||||
|
-a)
|
||||||
|
allwsmode=true ;;
|
||||||
|
-g)
|
||||||
|
guardedmode=true ;;
|
||||||
|
-w)
|
||||||
|
singlemode=true && shift && wsname="$1" && checkWSName ;;
|
||||||
|
--*)
|
||||||
|
usage && echo 1>&2 "error: unknown argument $1" && exit 1 ;;
|
||||||
|
-*)
|
||||||
|
usage && echo 1>&2 "error: unknown argument $1" && exit 1 ;;
|
||||||
|
*) # git core commands
|
||||||
|
butilcommand="executBulkOp" && corecommand="$1" && gitcommand="$*" && break ;;
|
||||||
|
esac && shift
|
||||||
|
done
|
||||||
|
|
||||||
|
# check option compatibility
|
||||||
|
if $allwsmode && $singlemode; then echo 1>&2 "error: options -w and -a are incompatible" && exit 1; fi
|
||||||
|
|
||||||
|
# if single mode check the supplied workspace name
|
||||||
|
if $singlemode; then echo 1>&2 "Selected single workspace mode in workspace: $wsname" && checkWSName; fi
|
||||||
|
|
||||||
|
# check right number of arguments
|
||||||
|
case $butilcommand in
|
||||||
|
listall|purge) allowedargcount 1;;
|
||||||
|
addcurrent|removeworkspace) allowedargcount 2;;
|
||||||
|
addworkspace) allowedargcount 3 5;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# pass the origin arguments to the 'executBulkOp'
|
||||||
|
$butilcommand "$@" # run user command
|
||||||
|
|
@ -1,22 +1,597 @@
|
||||||
#!/bin/sh
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
CHANGELOG=`ls | egrep 'change|history' -i`
|
DEF_TAG_RECENT="n.n.n"
|
||||||
DATE=`date +'%Y-%m-%d'`
|
GIT_LOG_OPTS="$(git config changelog.opts)"
|
||||||
HEAD="\nn.n.n / $DATE \n==================\n"
|
GIT_LOG_FORMAT="$(git config changelog.format)"
|
||||||
|
[[ -z "$GIT_LOG_FORMAT" ]] && GIT_LOG_FORMAT=' * %s'
|
||||||
|
GIT_MERGELOG_FORMAT="$(git config changelog.mergeformat)"
|
||||||
|
[[ -z "$GIT_MERGELOG_FORMAT" ]] && GIT_MERGELOG_FORMAT=' * %s%n%w(64,4,4)%b'
|
||||||
|
GIT_EDITOR="$(git var GIT_EDITOR)"
|
||||||
|
PROGNAME="git-changelog"
|
||||||
|
|
||||||
if test "$1" = "--list"; then
|
git_editor() {
|
||||||
version=`git tag | tail -n 1`
|
if test -z "${GIT_EDITOR:+set}"
|
||||||
if test -z "$version"; then
|
then
|
||||||
git log --pretty="format: * %s"
|
GIT_EDITOR="$(git var GIT_EDITOR)" || return $?
|
||||||
else
|
|
||||||
git log --pretty="format: * %s" $version..
|
|
||||||
fi
|
fi
|
||||||
else
|
|
||||||
tmp="/tmp/changelog"
|
eval "$GIT_EDITOR" '"$@"'
|
||||||
echo $HEAD > $tmp
|
}
|
||||||
git-changelog --list >> $tmp
|
|
||||||
echo '' >> $tmp
|
_usage() {
|
||||||
cat $CHANGELOG >> $tmp
|
cat << EOF
|
||||||
mv $tmp $CHANGELOG
|
usage: $PROGNAME options [file]
|
||||||
test -n "$EDITOR" && $EDITOR $CHANGELOG
|
usage: $PROGNAME -h|help|?
|
||||||
fi
|
|
||||||
|
Generate a Changelog from git(1) tags (annotated or lightweight) and commit
|
||||||
|
messages. Existing Changelog files with filenames that begin with 'Change' or
|
||||||
|
'History' will be identified automatically and their content will be appended
|
||||||
|
to the new output generated (unless the -p|--prune-old option is used). If no
|
||||||
|
tags exist, then all commits are output; if tags exist, then only the most-
|
||||||
|
recent commits are output up to the last identified tag.
|
||||||
|
|
||||||
|
OPTIONS:
|
||||||
|
-a, --all Retrieve all commits (ignores --start-tag/commit, --final-tag)
|
||||||
|
-l, --list Display commits as a list, with no titles
|
||||||
|
-t, --tag Tag label to use for most-recent (untagged) commits
|
||||||
|
-f, --final-tag Newest tag to retrieve commits from in a range
|
||||||
|
-s, --start-tag Oldest tag to retrieve commits from in a range
|
||||||
|
--start-commit Like --start-tag but use commit instead of tag
|
||||||
|
-n, --no-merges Suppress commits from merged branches
|
||||||
|
-m, --merges-only Only uses merge commits (uses both subject and body of commit)
|
||||||
|
-p, --prune-old Replace existing Changelog entirely with new content
|
||||||
|
-x, --stdout Write output to stdout instead of to a Changelog file
|
||||||
|
-h, --help, ? Show this message
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
_error() {
|
||||||
|
[ $# -eq 0 ] && _usage && exit 0
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "ERROR: " "$@"
|
||||||
|
echo
|
||||||
|
}
|
||||||
|
|
||||||
|
# _setValueForKeyFakeAssocArray()
|
||||||
|
# /*!
|
||||||
|
# @abstract Set value for key from a fake associative array
|
||||||
|
# @discussion
|
||||||
|
# Iterates over target_ary (an indexed array), searching for target_key, if the
|
||||||
|
# key is found its value is set to new_value otherwise the target_key and
|
||||||
|
# new_value are appended to the array.
|
||||||
|
#
|
||||||
|
# The indexed array values must conform to this format:
|
||||||
|
# "key:value"
|
||||||
|
# Where key and value are separated by a single colon character.
|
||||||
|
#
|
||||||
|
# Specify empty values as an empty, quoted string.
|
||||||
|
#
|
||||||
|
# So-called "fake" associative arrays are useful for environments where the
|
||||||
|
# installed version of bash(1) precedes 4.0.
|
||||||
|
# @param target_key Key to retrieve
|
||||||
|
# @param new_value New or updated value
|
||||||
|
# @param target_ary Indexed array to scan
|
||||||
|
# @return Returns new array with updated key (status 0) or an empty array
|
||||||
|
# (status 1) on failure.
|
||||||
|
# */
|
||||||
|
_setValueForKeyFakeAssocArray() {
|
||||||
|
# parameter list supports empty arguments!
|
||||||
|
local target_key="$1"; shift
|
||||||
|
local new_value="$1"; shift
|
||||||
|
local target_ary=()
|
||||||
|
local defaultIFS="$IFS"
|
||||||
|
local IFS="$defaultIFS"
|
||||||
|
local found=false
|
||||||
|
|
||||||
|
IFS=$' ' target_ary=( $1 ) IFS="$defaultIFS"
|
||||||
|
|
||||||
|
[[ -z "${target_key}" || "${#target_ary[@]}" -eq 0 ]] && echo "${value}" && return 1
|
||||||
|
|
||||||
|
local _target_ary_length="${#target_ary[@]}"
|
||||||
|
local i
|
||||||
|
for (( i=0; i<"${_target_ary_length}"; i++ )); do
|
||||||
|
local __val="${target_ary[$i]}"
|
||||||
|
|
||||||
|
if [[ "${__val%%:*}" == "${target_key}" ]]; then
|
||||||
|
target_ary[$i]="${__val%%:*}:${new_value}"
|
||||||
|
found=true
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
|
||||||
|
unset __val
|
||||||
|
done
|
||||||
|
unset i _target_ary_length
|
||||||
|
|
||||||
|
# key not found, append
|
||||||
|
[[ "$found" == false ]] && target_ary+=( "${target_key}:${new_value}" )
|
||||||
|
|
||||||
|
printf "%s" "${target_ary[*]}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# _valueForKeyFakeAssocArray()
|
||||||
|
# /*!
|
||||||
|
# @abstract Fetch value for key from a fake associative array
|
||||||
|
# @discussion
|
||||||
|
# Iterates over target_ary (an indexed array), searching for target_key, if the
|
||||||
|
# key is found its value is returned.
|
||||||
|
#
|
||||||
|
# The indexed array values must conform to this format:
|
||||||
|
# "key:value"
|
||||||
|
# Where key and value are separated by a single colon character.
|
||||||
|
#
|
||||||
|
# So-called "fake" associative arrays are useful for environments where the
|
||||||
|
# installed version of bash(1) precedes 4.0.
|
||||||
|
# @param target_key Key to retrieve
|
||||||
|
# @param target_ary Indexed array to scan
|
||||||
|
# @return Returns string containing value (status 0) or an empty string
|
||||||
|
# (status 1) on failure.
|
||||||
|
# */
|
||||||
|
_valueForKeyFakeAssocArray() {
|
||||||
|
local target_key="$1"
|
||||||
|
local target_ary=()
|
||||||
|
local defaultIFS="$IFS"
|
||||||
|
local IFS="$defaultIFS"
|
||||||
|
local value=""
|
||||||
|
|
||||||
|
IFS=$' ' target_ary=( $2 ) IFS="$defaultIFS"
|
||||||
|
|
||||||
|
[[ -z "${target_key}" || "${#target_ary[@]}" -eq 0 ]] && echo "${value}" && return 1
|
||||||
|
|
||||||
|
local t
|
||||||
|
for t in "${target_ary[@]}"; do
|
||||||
|
if [[ "${t%%:*}" == "${target_key}" ]]; then
|
||||||
|
value="${t#*:}"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
unset t
|
||||||
|
|
||||||
|
echo -e "${value}"; return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
_fetchCommitRange() {
|
||||||
|
local list_all="${1:-false}"
|
||||||
|
local start_tag="$2"
|
||||||
|
local final_tag="$3"
|
||||||
|
|
||||||
|
# This shellcheck disable is applied to the whole if-body
|
||||||
|
# shellcheck disable=SC2086
|
||||||
|
if [[ "$list_all" == true ]]; then
|
||||||
|
git log $GIT_LOG_OPTS --pretty=format:"${CUR_GIT_LOG_FORMAT}"
|
||||||
|
elif [[ -n "$final_tag" && "$start_tag" == "null" ]]; then
|
||||||
|
git log $GIT_LOG_OPTS --pretty=format:"${CUR_GIT_LOG_FORMAT}" "${final_tag}"
|
||||||
|
elif [[ -n "$final_tag" ]]; then
|
||||||
|
git log $GIT_LOG_OPTS --pretty=format:"${CUR_GIT_LOG_FORMAT}" "${start_tag}"'..'"${final_tag}"
|
||||||
|
elif [[ -n "$start_tag" ]]; then
|
||||||
|
git log $GIT_LOG_OPTS --pretty=format:"${CUR_GIT_LOG_FORMAT}" "${start_tag}"'..'
|
||||||
|
fi | sed 's/^ \* \*/ */g'
|
||||||
|
}
|
||||||
|
|
||||||
|
_formatCommitPlain() {
|
||||||
|
local start_tag="$1"
|
||||||
|
local final_tag="$2"
|
||||||
|
|
||||||
|
printf "%s\n" "$(_fetchCommitRange "false" "$start_tag" "$final_tag")"
|
||||||
|
}
|
||||||
|
|
||||||
|
_formatCommitPretty() {
|
||||||
|
local title_tag="$1"
|
||||||
|
local title_date="$2"
|
||||||
|
local start_tag="$3"
|
||||||
|
local final_tag="$4"
|
||||||
|
local title="$title_tag / $title_date"
|
||||||
|
local title_underline=""
|
||||||
|
|
||||||
|
local i
|
||||||
|
for i in $(seq ${#title}); do
|
||||||
|
title_underline+="="
|
||||||
|
done
|
||||||
|
unset i
|
||||||
|
|
||||||
|
printf '\n%s\n%s\n' "$title" "$title_underline"
|
||||||
|
printf "\n%s\n" "$(_fetchCommitRange "false" "$start_tag" "$final_tag")"
|
||||||
|
}
|
||||||
|
|
||||||
|
commitList() {
|
||||||
|
# parameter list supports empty arguments!
|
||||||
|
local list_all="${1:-false}"; shift
|
||||||
|
local title_tag="$1"; shift
|
||||||
|
local start_tag="$1"; shift
|
||||||
|
local final_tag="$1"; shift
|
||||||
|
local list_style="${1:-false}"; shift # enable/disable list format
|
||||||
|
local start_commit="$1"; shift
|
||||||
|
local changelog="$FILE"
|
||||||
|
local title_date="$(date +'%Y-%m-%d')"
|
||||||
|
local tags_list=()
|
||||||
|
local tags_list_keys=()
|
||||||
|
local defaultIFS="$IFS"
|
||||||
|
local IFS="$defaultIFS"
|
||||||
|
|
||||||
|
if [[ -n "$start_commit" && "$final_tag" == "null" \
|
||||||
|
&& "$start_tag" == "null" ]]; then
|
||||||
|
# if there is not tag after $start_commit,
|
||||||
|
# output directly without fetch all tags
|
||||||
|
if [[ "$list_style" == true ]]; then
|
||||||
|
_formatCommitPlain "${start_commit}~"
|
||||||
|
else
|
||||||
|
_formatCommitPretty "$title_tag" "$title_date" "$start_commit~"
|
||||||
|
fi
|
||||||
|
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
#
|
||||||
|
# Tags look like this:
|
||||||
|
#
|
||||||
|
# >git log --tags --simplify-by-decoration --date="short" --pretty="format:%h$%x09%ad$%x09%d"
|
||||||
|
#
|
||||||
|
# ecf1f2b$ 2015-03-15$ (HEAD, tag: v1.0.1, origin/master, origin/HEAD, master, hotfix/1.0.2)
|
||||||
|
# a473e9c$ 2015-03-04$ (tag: v1.0.0)
|
||||||
|
# f2cb562$ 2015-02-19$ (tag: v0.9.2)
|
||||||
|
# 6197c2b$ 2015-02-19$ (tag: v0.9.1)
|
||||||
|
# 1e5f5e6$ 2015-02-16$ (tag: v0.9.0)
|
||||||
|
# 3de8ab5$ 2015-02-11$ (origin/feature/restore-auto)
|
||||||
|
# a15afd1$ 2015-02-02$ (origin/feature/versionable)
|
||||||
|
# 38a44e0$ 2015-02-02$ (origin/feature/save-auto)
|
||||||
|
# 3244b80$ 2015-01-16$ (origin/feature/silent-history, upstream)
|
||||||
|
# 85e45f8$ 2014-08-25$
|
||||||
|
#
|
||||||
|
# The most-recent tag will be preceded by "HEAD, " if there have been zero
|
||||||
|
# commits since the tag. Also notice that with gitflow, we see features.
|
||||||
|
#
|
||||||
|
|
||||||
|
# fetch our tags
|
||||||
|
local _ref _date _tag _tab='%x09'
|
||||||
|
local _tag_regex='tag: *'
|
||||||
|
while IFS=$'\t' read -r _ref _date _tag; do
|
||||||
|
[[ -z "${_tag}" ]] && continue
|
||||||
|
# strip out tags form ()
|
||||||
|
# git v2.2.0+ supports '%D', like '%d' without the " (", ")" wrapping. One day we should use it instead.
|
||||||
|
_tag="${_tag# }"; _tag="${_tag//[()]/}"
|
||||||
|
# trap tag if it points to last commit (HEAD)
|
||||||
|
_tag="${_tag#HEAD*, }"
|
||||||
|
# strip out branches
|
||||||
|
[[ ! "${_tag}" =~ ${_tag_regex} ]] && continue
|
||||||
|
# strip out any additional tags pointing to same commit, remove tag label
|
||||||
|
_tag="${_tag%%,*}"; _tag="${_tag#tag: }"
|
||||||
|
# add tag to assoc array; copy tag to tag_list_keys for ordered iteration
|
||||||
|
tags_list+=( "${_tag}:${_ref}=>${_date}" )
|
||||||
|
tags_list_keys+=( "${_tag}" )
|
||||||
|
done <<< "$(git log --tags --simplify-by-decoration --date="short" --pretty="format:%h${_tab}%ad${_tab}%d")"
|
||||||
|
IFS="$defaultIFS"
|
||||||
|
unset _tag_regex
|
||||||
|
unset _ref _date _tag _tab
|
||||||
|
|
||||||
|
local _tags_list_keys_length="${#tags_list_keys[@]}"
|
||||||
|
if [[ "${_tags_list_keys_length}" -eq 0 ]]
|
||||||
|
then
|
||||||
|
unset _tags_list_keys_length
|
||||||
|
if [[ "$list_style" == true ]]; then
|
||||||
|
printf "%s" "$(_fetchCommitRange "true")"
|
||||||
|
else
|
||||||
|
local title="$title_tag / $title_date"
|
||||||
|
local title_underline=""
|
||||||
|
|
||||||
|
local i
|
||||||
|
for i in $(seq ${#title}); do
|
||||||
|
title_underline+="="
|
||||||
|
done
|
||||||
|
unset i
|
||||||
|
|
||||||
|
printf '\n%s\n%s\n' "$title" "$title_underline"
|
||||||
|
printf "\n%s\n" "$(_fetchCommitRange "true")"
|
||||||
|
fi
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
local _final_tag_found=false
|
||||||
|
local _start_tag_found=false
|
||||||
|
local i
|
||||||
|
for (( i=0; i<"${_tags_list_keys_length}"; i++ )); do
|
||||||
|
local __curr_tag="${tags_list_keys[$i]}"
|
||||||
|
local __prev_tag="${tags_list_keys[$i+1]:-null}"
|
||||||
|
local __curr_date
|
||||||
|
__curr_date="$(_valueForKeyFakeAssocArray "${__curr_tag}" "${tags_list[*]}")"
|
||||||
|
__curr_date="${__curr_date##*=>}"
|
||||||
|
|
||||||
|
# output latest commits, up until the most-recent tag, these are all
|
||||||
|
# new commits made since the last tagged commit.
|
||||||
|
if [[ $i -eq 0 && ( -z "$final_tag" || "$final_tag" == "null" ) ]]; then
|
||||||
|
if [[ "$list_style" == true ]]; then
|
||||||
|
_formatCommitPlain "${__curr_tag}" >> "$tmpfile"
|
||||||
|
else
|
||||||
|
_formatCommitPretty "$title_tag" "$title_date" "${__curr_tag}"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# both final_tag and start_tag are "null", user just wanted recent commits
|
||||||
|
[[ "$final_tag" == "null" && "$start_tag" == "null" ]] && break;
|
||||||
|
|
||||||
|
# find the specified final tag, continue until found
|
||||||
|
if [[ -n "$final_tag" && "$final_tag" != "null" ]]; then
|
||||||
|
[[ "$final_tag" == "${__curr_tag}" ]] && _final_tag_found=true
|
||||||
|
[[ "$final_tag" != "${__curr_tag}" && "${_final_tag_found}" == false ]] && continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
# find the specified start tag, break when found
|
||||||
|
if [[ -n "$start_tag" ]]; then
|
||||||
|
[[ "$start_tag" == "${__curr_tag}" ]] && _start_tag_found=true
|
||||||
|
if [[ "${_start_tag_found}" == true ]]; then
|
||||||
|
if [[ -n "$start_commit" ]]; then
|
||||||
|
|
||||||
|
# output commits after start_commit to its closest tag
|
||||||
|
if [[ "$list_style" == true ]]; then
|
||||||
|
_formatCommitPlain "$start_commit~" "${__curr_tag}"
|
||||||
|
else
|
||||||
|
_formatCommitPretty "${__curr_tag}" "${__curr_date}" \
|
||||||
|
"$start_commit~" "${__curr_tag}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
|
||||||
|
[[ "$start_tag" != "${__curr_tag}" ]] && break
|
||||||
|
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# output commits made between prev_tag and curr_tag, these are all of the
|
||||||
|
# commits related to the tag of interest.
|
||||||
|
if [[ "$list_style" == true ]]; then
|
||||||
|
_formatCommitPlain "${__prev_tag}" "${__curr_tag}"
|
||||||
|
else
|
||||||
|
_formatCommitPretty "${__curr_tag}" "${__curr_date}" "${__prev_tag}" "${__curr_tag}"
|
||||||
|
fi
|
||||||
|
unset __curr_date
|
||||||
|
unset __prev_tag
|
||||||
|
unset __curr_tag
|
||||||
|
done
|
||||||
|
unset i
|
||||||
|
unset _start_tag_found
|
||||||
|
unset _final_tag_found
|
||||||
|
unset _tags_list_keys_length
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
commitListPlain() {
|
||||||
|
local list_all="${1:-false}"
|
||||||
|
local start_tag="$2"
|
||||||
|
local final_tag="$3"
|
||||||
|
local start_commit="$4"
|
||||||
|
|
||||||
|
commitList "$list_all" "" "$start_tag" "$final_tag" "true" "$start_commit"
|
||||||
|
}
|
||||||
|
|
||||||
|
commitListPretty() {
|
||||||
|
local list_all="${1:-false}"
|
||||||
|
local title_tag="$2"
|
||||||
|
local start_tag="$3"
|
||||||
|
local final_tag="$4"
|
||||||
|
local start_commit="$5"
|
||||||
|
local title_date="$(date +'%Y-%m-%d')"
|
||||||
|
|
||||||
|
commitList "$list_all" "$title_tag" "$start_tag" "$final_tag" "false" \
|
||||||
|
"$start_commit"
|
||||||
|
}
|
||||||
|
|
||||||
|
_exit() {
|
||||||
|
local pid_list=()
|
||||||
|
local defaultIFS="$IFS"
|
||||||
|
local IFS="$defaultIFS"
|
||||||
|
|
||||||
|
stty sane; echo; echo "caught signal, shutting down"
|
||||||
|
|
||||||
|
IFS=$'\n'
|
||||||
|
# The format of `ps` is different between Windows and other platforms,
|
||||||
|
# so we need to calculate the total column number(COL_NUM) of header first.
|
||||||
|
# Why don't we just use the last column?
|
||||||
|
# Because the body of CMD column may contain space and be treated as multiple fields.
|
||||||
|
pid_list=( $(ps -f |
|
||||||
|
awk -v ppid=$$ 'NR == 1 {
|
||||||
|
COL_NUM = NF
|
||||||
|
}
|
||||||
|
$3 == ppid {
|
||||||
|
# filter out temp processes created in this subshell
|
||||||
|
if ($COL_NUM != "ps" && $COL_NUM != "awk" && $COL_NUM !~ "bash$")
|
||||||
|
print $2
|
||||||
|
}')
|
||||||
|
)
|
||||||
|
IFS="$defaultIFS"
|
||||||
|
|
||||||
|
local _pid
|
||||||
|
for _pid in "${pid_list[@]}"; do
|
||||||
|
echo "killing: ${_pid}"
|
||||||
|
kill -TERM "${_pid}"
|
||||||
|
done
|
||||||
|
|
||||||
|
wait; stty sane; exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
trap '_exit' SIGINT SIGQUIT SIGTERM
|
||||||
|
|
||||||
|
main() {
|
||||||
|
local start_tag="null" # empty string and "null" mean two different things!
|
||||||
|
local final_tag="null"
|
||||||
|
|
||||||
|
local option=(
|
||||||
|
"list_all:false"
|
||||||
|
"list_style:false"
|
||||||
|
"title_tag:$DEF_TAG_RECENT"
|
||||||
|
"start_tag:"
|
||||||
|
"start_commit:"
|
||||||
|
"final_tag:"
|
||||||
|
"output_file:"
|
||||||
|
"use_stdout:false"
|
||||||
|
"prune_old:false"
|
||||||
|
)
|
||||||
|
|
||||||
|
#
|
||||||
|
# We work chronologically backwards from NOW towards start_tag where NOW also
|
||||||
|
# includes the most-recent (un-tagged) commits. If no start_tag has been
|
||||||
|
# specified, we work back to the very first commit; if a final_tag has been
|
||||||
|
# specified, we begin at the final_tag and work backwards towards start_tag.
|
||||||
|
#
|
||||||
|
|
||||||
|
# An existing ChangeLog/History file will be appended to the output unless the
|
||||||
|
# prune old (-p | --prune-old) option has been enabled.
|
||||||
|
|
||||||
|
while [ "$1" != "" ]; do
|
||||||
|
case $1 in
|
||||||
|
-a | --all )
|
||||||
|
option=( $(_setValueForKeyFakeAssocArray "list_all" true "${option[*]}") )
|
||||||
|
;;
|
||||||
|
-l | --list )
|
||||||
|
option=( $(_setValueForKeyFakeAssocArray "list_style" true "${option[*]}") )
|
||||||
|
;;
|
||||||
|
-t | --tag )
|
||||||
|
option=( $(_setValueForKeyFakeAssocArray "title_tag" "$2" "${option[*]}") )
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
-f | --final-tag )
|
||||||
|
option=( $(_setValueForKeyFakeAssocArray "final_tag" "$2" "${option[*]}") )
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
-s | --start-tag )
|
||||||
|
option=( $(_setValueForKeyFakeAssocArray "start_tag" "$2" "${option[*]}") )
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--start-commit )
|
||||||
|
option=( $(_setValueForKeyFakeAssocArray "start_commit" "$2" "${option[*]}") )
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
-n | --no-merges )
|
||||||
|
GIT_LOG_OPTS='--no-merges'
|
||||||
|
CUR_GIT_LOG_FORMAT="$GIT_LOG_FORMAT"
|
||||||
|
;;
|
||||||
|
-m | --merges-only )
|
||||||
|
GIT_LOG_OPTS='--merges'
|
||||||
|
CUR_GIT_LOG_FORMAT="$GIT_MERGELOG_FORMAT"
|
||||||
|
;;
|
||||||
|
-p | --prune-old )
|
||||||
|
option=( $(_setValueForKeyFakeAssocArray "prune_old" true "${option[*]}") )
|
||||||
|
;;
|
||||||
|
-x | --stdout )
|
||||||
|
option=( $(_setValueForKeyFakeAssocArray "use_stdout" true "${option[*]}") )
|
||||||
|
;;
|
||||||
|
-h | ? | help | --help )
|
||||||
|
_usage
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
* )
|
||||||
|
[[ "${1:0:1}" == '-' ]] && _error "Invalid option: $1" && _usage && exit 1
|
||||||
|
option=( $(_setValueForKeyFakeAssocArray "output_file" "$1" "${option[*]}") )
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
shift
|
||||||
|
done
|
||||||
|
|
||||||
|
# The default log format unless already set
|
||||||
|
[[ -z "$CUR_GIT_LOG_FORMAT" ]] && CUR_GIT_LOG_FORMAT="$GIT_LOG_FORMAT"
|
||||||
|
|
||||||
|
local _tag="$(_valueForKeyFakeAssocArray "start_tag" "${option[*]}")"
|
||||||
|
local start_commit="$(_valueForKeyFakeAssocArray "start_commit" "${option[*]}")"
|
||||||
|
|
||||||
|
if [[ -n "$start_commit" ]]; then
|
||||||
|
if [[ -n "${_tag}" ]]; then
|
||||||
|
_error "--start-tag could not use with --start-commit!"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
start_tag="$(git describe --tags --contains "$start_commit" 2>/dev/null || echo 'null')"
|
||||||
|
if [[ -z "$start_tag" ]]; then
|
||||||
|
_error "Could find the associative tag for the start-commit!"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# remove suffix from the $start_tag when no tag matched exactly
|
||||||
|
start_tag="${start_tag%%~*}"
|
||||||
|
# also remove "^0" added sometimes when tag matched exactly
|
||||||
|
start_tag="${start_tag%%^0}"
|
||||||
|
|
||||||
|
elif [[ -n "${_tag}" ]]; then
|
||||||
|
start_tag="$(git describe --tags --abbrev=0 "${_tag}" 2>/dev/null)"
|
||||||
|
if [[ -z "$start_tag" ]]; then
|
||||||
|
_error "Specified start-tag does not exist!"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -n "${_tag}" ]]; then
|
||||||
|
if [[ -n "$start_commit" ]]; then
|
||||||
|
_error "--start-tag could not use with --start-commit!"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
fi
|
||||||
|
unset _tag
|
||||||
|
|
||||||
|
local _tag="$(_valueForKeyFakeAssocArray "final_tag" "${option[*]}")"
|
||||||
|
if [[ -n "${_tag}" ]]; then
|
||||||
|
final_tag="$(git describe --tags --abbrev=0 "${_tag}" 2>/dev/null)"
|
||||||
|
if [[ -z "$final_tag" ]]; then
|
||||||
|
_error "Specified final-tag does not exist!"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
unset _tag
|
||||||
|
|
||||||
|
#
|
||||||
|
# generate changelog
|
||||||
|
#
|
||||||
|
local tmpfile changelog title_tag
|
||||||
|
tmpfile="$(git_extra_mktemp)"
|
||||||
|
changelog="$(_valueForKeyFakeAssocArray "output_file" "${option[*]}")"
|
||||||
|
title_tag="$(_valueForKeyFakeAssocArray "title_tag" "${option[*]}")"
|
||||||
|
|
||||||
|
if [[ "$(_valueForKeyFakeAssocArray "list_style" "${option[*]}")" == true ]]; then
|
||||||
|
if [[ "$(_valueForKeyFakeAssocArray "list_all" "${option[*]}")" == true ]]; then
|
||||||
|
commitListPlain "true" >> "$tmpfile"
|
||||||
|
else
|
||||||
|
commitListPlain "false" "$start_tag" "$final_tag" \
|
||||||
|
"$start_commit" >> "$tmpfile"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
if [[ "$(_valueForKeyFakeAssocArray "list_all" "${option[*]}")" == true ]]; then
|
||||||
|
commitListPretty "true" "$title_tag" >> "$tmpfile"
|
||||||
|
else
|
||||||
|
commitListPretty "false" "$title_tag" "$start_tag" "$final_tag" \
|
||||||
|
"$start_commit" >> "$tmpfile"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -z "$changelog" ]]; then
|
||||||
|
changelog="$(find . -mindepth 1 -maxdepth 1 \( -iname '*change*' -o -iname '*history*' \) | head -n1)"
|
||||||
|
if [[ -z "$changelog" ]]; then
|
||||||
|
changelog="History.md";
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# append existing changelog?
|
||||||
|
if [[ -f "$changelog" \
|
||||||
|
&& "$(_valueForKeyFakeAssocArray "prune_old" "${option[*]}")" == false ]]; then
|
||||||
|
cat "$changelog" >> "$tmpfile"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# output file to stdout or move into place
|
||||||
|
if [[ "$(_valueForKeyFakeAssocArray "use_stdout" "${option[*]}")" == true ]]; then
|
||||||
|
cat "$tmpfile"
|
||||||
|
rm -f "$tmpfile"
|
||||||
|
else
|
||||||
|
cp -f "$tmpfile" "$changelog"
|
||||||
|
rm -f "$tmpfile"
|
||||||
|
|
||||||
|
if [[ -n "$GIT_EDITOR" ]]; then
|
||||||
|
git_editor "$changelog" || _exit
|
||||||
|
else
|
||||||
|
less "$changelog" || _exit
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
main "$@"
|
||||||
|
|
||||||
|
exit 0
|
||||||
|
|
|
||||||
43
bin/git-clear
Executable file
43
bin/git-clear
Executable file
|
|
@ -0,0 +1,43 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
PROGNAME="git-clear"
|
||||||
|
FORCE=0
|
||||||
|
|
||||||
|
_usage() {
|
||||||
|
cat << EOF
|
||||||
|
usage: $PROGNAME options
|
||||||
|
usage: $PROGNAME -h|help|?
|
||||||
|
|
||||||
|
clear git repository
|
||||||
|
|
||||||
|
OPTIONS:
|
||||||
|
-f, --force Force clear without questioning user
|
||||||
|
-h, --help, ? Show this message
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
# Read arguments
|
||||||
|
while [ "$1" != "" ]; do
|
||||||
|
case $1 in
|
||||||
|
-f|--force)
|
||||||
|
FORCE=1
|
||||||
|
;;
|
||||||
|
-h|--help|?)
|
||||||
|
_usage
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
shift
|
||||||
|
done
|
||||||
|
|
||||||
|
# Only wait for answer if not forced by user
|
||||||
|
if [[ $FORCE == 0 ]]; then
|
||||||
|
echo -n "Sure? - THIS COMMAND MAY DELETE FILES THAT CANNOT BE RECOVERED, including those in .gitignore [y/N]: "
|
||||||
|
read -r clean
|
||||||
|
else
|
||||||
|
clean=y
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$clean" = "y" ]; then
|
||||||
|
git clean -d -f -x && git reset --hard
|
||||||
|
fi
|
||||||
7
bin/git-clear-soft
Executable file
7
bin/git-clear-soft
Executable file
|
|
@ -0,0 +1,7 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
echo -n "Sure? - This command may delete files that cannot be recovered. Files and directories in .gitignore will be preserved [y/N]: "
|
||||||
|
read -r answer
|
||||||
|
if [ "$answer" = "y" ]
|
||||||
|
then git clean -d -f && git reset --hard
|
||||||
|
fi
|
||||||
17
bin/git-coauthor
Executable file
17
bin/git-coauthor
Executable file
|
|
@ -0,0 +1,17 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -e -o pipefail
|
||||||
|
|
||||||
|
coauthor=$1
|
||||||
|
email=$2
|
||||||
|
|
||||||
|
test -z "$coauthor" && echo "co-author required." 1>&2 && exit 1
|
||||||
|
test -z "$email" && echo "co-author email required." 1>&2 && exit 1
|
||||||
|
|
||||||
|
old_message="$(git log --format=%B -n1)"
|
||||||
|
if [[ "$old_message" == *"Co-authored-by:"* ]]; then
|
||||||
|
git commit --amend -m "$old_message
|
||||||
|
Co-authored-by: $coauthor <$email>"
|
||||||
|
else
|
||||||
|
git commit --amend -m "$old_message" -m "Co-authored-by: $coauthor <$email>"
|
||||||
|
fi
|
||||||
|
|
@ -1,6 +1,36 @@
|
||||||
#!/bin/sh
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
SINCE="last week"
|
REF=""
|
||||||
test $# -ne 0 && SINCE=$@
|
SINCE=""
|
||||||
echo "... commits since $SINCE" >&2
|
|
||||||
git log --pretty='%an - %s' --after="@{$SINCE}"
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
-r|--ref)
|
||||||
|
if [[ -z "$2" ]]; then
|
||||||
|
echo "error: option $1 requires an argument" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
REF="$2"
|
||||||
|
shift
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
# non-flag args accumulation, e.g. "last " + "week" -> "last week"
|
||||||
|
SINCE="${SINCE:+$SINCE }$1"
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ -n "$REF" && -n "$SINCE" ]]; then
|
||||||
|
echo "error: '--ref <ref>' and '<date>' are mutually exclusive" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
SINCE="${SINCE:-last week}"
|
||||||
|
|
||||||
|
if [[ -n "$REF" ]]; then
|
||||||
|
git log --pretty='%h %an - %s' "$REF..HEAD"
|
||||||
|
else
|
||||||
|
git log --pretty='%h %an - %s' --after="@{$SINCE}"
|
||||||
|
fi
|
||||||
|
|
|
||||||
1
bin/git-continue
Symbolic link
1
bin/git-continue
Symbolic link
|
|
@ -0,0 +1 @@
|
||||||
|
git-abort
|
||||||
|
|
@ -1,10 +1,7 @@
|
||||||
#!/bin/sh
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
user="$1"
|
user="$*"
|
||||||
if test -z "$user"; then
|
|
||||||
echo "user name required." && exit 1
|
test -z "$user" && echo "user name required." 1>&2 && exit 1
|
||||||
else
|
|
||||||
count=`git log --oneline --pretty="format: %an" | grep "$user" | wc -l`
|
git shortlog --format=format:"%h %s" --author="$user"
|
||||||
test $count -eq 0 && echo "$user did not contribute." && exit 1
|
|
||||||
git shortlog | grep "$user" -A $count
|
|
||||||
fi
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,8 @@
|
||||||
#!/bin/sh
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
arg=$1; shift
|
if test "$1" = "--all"; then
|
||||||
if test "$arg" = "--all"; then
|
git shortlog -n -s | awk '{print substr($0,index($0,$2)) " (" $1 ")"}'
|
||||||
git shortlog -n $@ | grep "):" | sed 's|:||'
|
|
||||||
echo
|
echo
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo total `git log --oneline | wc -l`
|
echo total "$(git rev-list --count HEAD)"
|
||||||
|
|
|
||||||
78
bin/git-cp
Executable file
78
bin/git-cp
Executable file
|
|
@ -0,0 +1,78 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
PROGRAM=$0
|
||||||
|
CURRENT_FILENAME=""
|
||||||
|
DESTINATION_FILENAME=""
|
||||||
|
|
||||||
|
function usage()
|
||||||
|
{
|
||||||
|
echo 1>&2 "USAGE: ${PROGRAM} CURRENT_FILENAME DESTINATION_FILENAME"
|
||||||
|
}
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]
|
||||||
|
do
|
||||||
|
key="$1"
|
||||||
|
|
||||||
|
if [[ "$CURRENT_FILENAME" == "" ]]; then
|
||||||
|
CURRENT_FILENAME=$key
|
||||||
|
elif [[ "$DESTINATION_FILENAME" == "" ]]; then
|
||||||
|
DESTINATION_FILENAME=$key
|
||||||
|
else
|
||||||
|
usage
|
||||||
|
exit 30 # Error during arguments parsing
|
||||||
|
fi
|
||||||
|
shift # past argument or value
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ "$DESTINATION_FILENAME" == "" ]]; then
|
||||||
|
usage
|
||||||
|
exit 20 # Missing arguments CURRENT_FILENAME
|
||||||
|
elif [[ "$CURRENT_FILENAME" == "" ]]; then
|
||||||
|
usage
|
||||||
|
exit 10 # Missing arguments CURRENT_FILENAME
|
||||||
|
else
|
||||||
|
if [ -e "$DESTINATION_FILENAME" ]; then
|
||||||
|
echo 1>&2 "$DESTINATION_FILENAME already exists."
|
||||||
|
echo 1>&2 "Make sure to remove the destination first."
|
||||||
|
exit 40
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$DESTINATION_FILENAME" == */ ]]; then
|
||||||
|
echo 1>&2 "$DESTINATION_FILENAME is not a file path."
|
||||||
|
exit 80
|
||||||
|
fi
|
||||||
|
if [[ "$DESTINATION_FILENAME" == */* ]]; then
|
||||||
|
DESTINATION_DIR="${DESTINATION_FILENAME%/*}"
|
||||||
|
if ! mkdir -p "$DESTINATION_DIR"; then
|
||||||
|
echo 1>&2 "Failed to create destination directory: $DESTINATION_DIR"
|
||||||
|
exit 160
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
echo "Copying $CURRENT_FILENAME into $DESTINATION_FILENAME"
|
||||||
|
|
||||||
|
# Pre-check that the source and destination will work
|
||||||
|
git mv --dry-run "${CURRENT_FILENAME}" "${DESTINATION_FILENAME}"
|
||||||
|
|
||||||
|
# Make a new branch and switch to it
|
||||||
|
BRANCH_NAME="git-cp-$(date +%s)"
|
||||||
|
git checkout -b "$BRANCH_NAME"
|
||||||
|
|
||||||
|
# Move the original file to the new destination, in this branch
|
||||||
|
git mv "${CURRENT_FILENAME}" "${DESTINATION_FILENAME}"
|
||||||
|
git commit -nm "--Duplicate $CURRENT_FILENAME history into $DESTINATION_FILENAME"
|
||||||
|
|
||||||
|
# Restore the original file to keep it in the history
|
||||||
|
git checkout HEAD~ "${CURRENT_FILENAME}"
|
||||||
|
git commit -nm "--Restore $CURRENT_FILENAME"
|
||||||
|
|
||||||
|
# Switch to the original branch and merge this back in.
|
||||||
|
git checkout -
|
||||||
|
git merge --no-ff "$BRANCH_NAME" -m "Copy $CURRENT_FILENAME into $DESTINATION_FILENAME"
|
||||||
|
|
||||||
|
# We're now done with the branch, so delete it.
|
||||||
|
# We shouldn't need -D here, as we've already merged it back in.
|
||||||
|
git branch -d "$BRANCH_NAME"
|
||||||
|
fi
|
||||||
76
bin/git-create-branch
Executable file
76
bin/git-create-branch
Executable file
|
|
@ -0,0 +1,76 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
test $# -eq 0 && echo "branch argument required." 1>&2 && exit 1
|
||||||
|
|
||||||
|
# preference takes lowest priority; look for remote from prefs first
|
||||||
|
REMOTE_PREF=$(git config git-extras.create-branch.remote)
|
||||||
|
if [ -n "$REMOTE_PREF" ]; then
|
||||||
|
REMOTE=$REMOTE_PREF
|
||||||
|
fi
|
||||||
|
|
||||||
|
while test $# != 0
|
||||||
|
do
|
||||||
|
case $1 in
|
||||||
|
-r|--remote)
|
||||||
|
if [[ -n $2 ]]
|
||||||
|
then
|
||||||
|
REMOTE=$2
|
||||||
|
shift
|
||||||
|
else
|
||||||
|
REMOTE=origin
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
--from)
|
||||||
|
if [[ -n $2 ]]; then
|
||||||
|
START_POINT=$2
|
||||||
|
shift
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
BRANCH=$1
|
||||||
|
esac
|
||||||
|
shift
|
||||||
|
done
|
||||||
|
|
||||||
|
# handle ambiguous `-r` option argument by shift
|
||||||
|
if [[ -z $BRANCH ]] && [[ -n $REMOTE ]]
|
||||||
|
then
|
||||||
|
BRANCH=$REMOTE
|
||||||
|
REMOTE=origin
|
||||||
|
fi
|
||||||
|
|
||||||
|
test -z "$BRANCH" && echo "branch argument required." 1>&2 && exit 1
|
||||||
|
|
||||||
|
if [[ -n $REMOTE ]]
|
||||||
|
then
|
||||||
|
stderr=$(git_extra_mktemp)
|
||||||
|
git ls-remote --exit-code "$REMOTE" 1>/dev/null 2>"$stderr"
|
||||||
|
REMOTE_EXIT=$?
|
||||||
|
REMOTE_ERROR=$(<"$stderr")
|
||||||
|
rm -f "$stderr"
|
||||||
|
if [ $REMOTE_EXIT -eq 0 ]
|
||||||
|
then
|
||||||
|
if [[ -n $START_POINT ]]; then
|
||||||
|
git fetch "$REMOTE"
|
||||||
|
git checkout --track -b "$BRANCH" "$START_POINT"
|
||||||
|
git push "$REMOTE" "HEAD:refs/heads/$BRANCH"
|
||||||
|
else
|
||||||
|
git push "$REMOTE" "HEAD:refs/heads/$BRANCH"
|
||||||
|
git fetch "$REMOTE"
|
||||||
|
git checkout --track -b "$BRANCH" "$REMOTE/$BRANCH"
|
||||||
|
fi
|
||||||
|
exit $?
|
||||||
|
else
|
||||||
|
echo
|
||||||
|
echo " Error connecting to remote '$REMOTE': $REMOTE_ERROR"
|
||||||
|
echo
|
||||||
|
exit $REMOTE_EXIT
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -n $START_POINT ]]
|
||||||
|
then
|
||||||
|
git checkout -b "$BRANCH" "$START_POINT"
|
||||||
|
else
|
||||||
|
git checkout -b "$BRANCH"
|
||||||
|
fi
|
||||||
|
|
@ -1,5 +1,17 @@
|
||||||
#!/bin/sh
|
#!/usr/bin/env bash
|
||||||
|
set -e
|
||||||
|
|
||||||
test -z $1 && echo "branch required." && exit 1
|
# Assert there is at least one branch provided
|
||||||
git branch -D $1
|
test -z "$1" && echo "branch required." 1>&2 && exit 1
|
||||||
git push origin :$1
|
|
||||||
|
for branch in "$@"
|
||||||
|
do
|
||||||
|
remote=$(git config "branch.$branch.remote" || echo "origin")
|
||||||
|
ref=$(git config "branch.$branch.merge" || echo "refs/heads/$branch")
|
||||||
|
|
||||||
|
git branch -D "$branch" || true
|
||||||
|
# Avoid deleting local upstream
|
||||||
|
[ "$remote" = "." ] && continue
|
||||||
|
git branch -d -r "$remote/$branch" || continue
|
||||||
|
git push "$remote" ":$ref"
|
||||||
|
done
|
||||||
|
|
|
||||||
60
bin/git-delete-gone-branches
Executable file
60
bin/git-delete-gone-branches
Executable file
|
|
@ -0,0 +1,60 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
dry_run=false
|
||||||
|
force=false
|
||||||
|
prune=false
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
-n|--dry-run)
|
||||||
|
dry_run=true
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
-f|--force)
|
||||||
|
force=true
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
-p|--prune)
|
||||||
|
prune=true
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Unknown option: $1"
|
||||||
|
echo "Usage: git delete-gone-branches [-n|--dry-run] [-f|--force] [-p|--prune]"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ "$prune" = true ]; then
|
||||||
|
git fetch --prune
|
||||||
|
fi
|
||||||
|
|
||||||
|
gone_branches=$(git for-each-ref --format \
|
||||||
|
'%(if:equals=gone)%(upstream:track,nobracket)%(then)%(refname:short)%(end)' refs/heads/ | sed '/^$/d')
|
||||||
|
|
||||||
|
if [ -z "$gone_branches" ]; then
|
||||||
|
echo "No branches with a gone remote found."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$dry_run" = true ]; then
|
||||||
|
echo "Would delete the following branches:"
|
||||||
|
echo "$gone_branches"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "The following branches will be deleted:"
|
||||||
|
echo "$gone_branches"
|
||||||
|
|
||||||
|
if [ "$force" = false ]; then
|
||||||
|
read -r -p "Delete these branches? [y/N] " confirm
|
||||||
|
if [[ ! "$confirm" =~ ^[yY]$ ]]; then
|
||||||
|
echo "Aborted."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "$gone_branches" | xargs git branch -D
|
||||||
7
bin/git-delete-merged-branches
Executable file
7
bin/git-delete-merged-branches
Executable file
|
|
@ -0,0 +1,7 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
branches=$(git branch --no-color --merged | grep -vE "^(\*|\+)" | grep -v "$(git_extra_default_branch)" | grep -v svn)
|
||||||
|
if [ -n "$branches" ]
|
||||||
|
then
|
||||||
|
echo "$branches" | xargs git branch -d
|
||||||
|
fi
|
||||||
28
bin/git-delete-squashed-branches
Executable file
28
bin/git-delete-squashed-branches
Executable file
|
|
@ -0,0 +1,28 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
proceed=false
|
||||||
|
if [[ $# -gt 0 && ("$1" == "--proceed" || "$1" == "-p") ]]; then
|
||||||
|
proceed=true
|
||||||
|
shift
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ $# -eq 0 ]]; then
|
||||||
|
targetBranch=$(git rev-parse --abbrev-ref HEAD)
|
||||||
|
else
|
||||||
|
targetBranch=$1
|
||||||
|
git checkout "$targetBranch"
|
||||||
|
fi
|
||||||
|
|
||||||
|
git for-each-ref refs/heads/ "--format=%(refname:short)" | while read -r branch; do
|
||||||
|
mergeBase=$(git merge-base "$targetBranch" "$branch")
|
||||||
|
|
||||||
|
if [[ $(git cherry "$targetBranch" "$(git commit-tree "$(git rev-parse "$branch^{tree}")" -p "$mergeBase" -m _)") == "-"* ]]; then
|
||||||
|
if [[ $proceed == true ]]; then
|
||||||
|
git branch -D "$branch" || true
|
||||||
|
else
|
||||||
|
git branch -D "$branch"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done
|
||||||
50
bin/git-delete-submodule
Executable file
50
bin/git-delete-submodule
Executable file
|
|
@ -0,0 +1,50 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
abort() {
|
||||||
|
error="$1" && shift
|
||||||
|
echo "ERROR: $*" 1>&2
|
||||||
|
test -z "$FORCE" && exit "$error"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Don't abort on failures. This allows to cleanup after a previous failure.
|
||||||
|
[ "$1" = '--force' ] && FORCE=1 && shift
|
||||||
|
|
||||||
|
test -z "$1" && abort 1 'Submodule required'
|
||||||
|
cd "$(git root)" || abort 5 'Cannot change to repository root'
|
||||||
|
test ! -f '.gitmodules' && abort 2 '.gitmodules file not found'
|
||||||
|
|
||||||
|
NAME="${1%/}"
|
||||||
|
test -z "$(git config --file='.gitmodules' "submodule.$NAME.url")" \
|
||||||
|
&& abort 3 'Submodule not found'
|
||||||
|
|
||||||
|
# 1. Handle the .git directory
|
||||||
|
# 1.a. Delete the relevant section from .git/config
|
||||||
|
git submodule deinit -f "$NAME" || abort 4 "Failed to deinitialize $NAME"
|
||||||
|
# 1.b. Delete empty submodule directory
|
||||||
|
git rm -f "$NAME"
|
||||||
|
|
||||||
|
# 2. Handle .gitmodules file
|
||||||
|
# 2.a. Delete the relevant line from .gitmodules
|
||||||
|
git config --file='.gitmodules' --remove-section "submodule.$NAME" 2>/dev/null || :
|
||||||
|
# 2.b and stage changes
|
||||||
|
git add '.gitmodules'
|
||||||
|
# 2.c. Delete empty .gitmodules
|
||||||
|
[ "$(wc -l '.gitmodules' | cut -d' ' -f1)" = '0' ] && git rm -f '.gitmodules'
|
||||||
|
|
||||||
|
# 3. Need to confirm and commit the changes for yourself
|
||||||
|
git_status_text="$(git submodule status 2>&1)"
|
||||||
|
git_status_exit=$?
|
||||||
|
if [ "$git_status_exit" -eq 0 ] \
|
||||||
|
&& printf '%s' "DUMMY$git_status_text" | grep -v "$NAME" > /dev/null; then
|
||||||
|
# grep fails when piping in an empty string, so we add a DUMMY prefix
|
||||||
|
|
||||||
|
echo "Successfully deleted $NAME."
|
||||||
|
else
|
||||||
|
abort 6 "Failed to delete $NAME with error:\n$git_status_text"
|
||||||
|
fi
|
||||||
|
printf '\n%s\n' '== git submodule status =='
|
||||||
|
printf '%s\n' "$git_status_text"
|
||||||
|
printf '%s\n' '=========================='
|
||||||
|
# shellcheck disable=SC2016
|
||||||
|
echo 'Confirm the output of `git submodule status` above (if any)' \
|
||||||
|
'and then commit the changes.'
|
||||||
|
|
@ -1,5 +1,28 @@
|
||||||
#!/bin/sh
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
test -z $1 && echo "tag required." && exit 1
|
# Assert there is at least one tag provided
|
||||||
git tag -d $1
|
test -z "$1" && echo "tag required." 1>&2 && exit 1
|
||||||
git push origin :refs/tags/$1
|
|
||||||
|
# Detect the default remote exists or not
|
||||||
|
default_remote=$(git config git-extras.default-remote)
|
||||||
|
|
||||||
|
if [[ -n "$default_remote" ]]; then
|
||||||
|
origin="$default_remote"
|
||||||
|
else
|
||||||
|
origin=origin
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Concatenate all the tag references
|
||||||
|
local_tags=""
|
||||||
|
origin_refs=""
|
||||||
|
for tagname in "$@"
|
||||||
|
do
|
||||||
|
local_tags=$local_tags" $tagname"
|
||||||
|
origin_refs=$origin_refs" :refs/tags/$tagname"
|
||||||
|
done
|
||||||
|
|
||||||
|
# Delete all the tags
|
||||||
|
# shellcheck disable=SC2086
|
||||||
|
git tag -d $local_tags
|
||||||
|
# shellcheck disable=SC2086
|
||||||
|
git push "$origin" $origin_refs
|
||||||
|
|
|
||||||
15
bin/git-delta
Executable file
15
bin/git-delta
Executable file
|
|
@ -0,0 +1,15 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
branch=$(git_extra_default_branch)
|
||||||
|
filter=ACM
|
||||||
|
|
||||||
|
if test $# -eq 1; then
|
||||||
|
branch=$1
|
||||||
|
else
|
||||||
|
if test $# -eq 2; then
|
||||||
|
branch=$1
|
||||||
|
filter=$2
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
git diff --name-only --diff-filter="$filter" "$branch"
|
||||||
234
bin/git-effort
Executable file
234
bin/git-effort
Executable file
|
|
@ -0,0 +1,234 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
tmp=$(git_extra_mktemp)
|
||||||
|
above=0
|
||||||
|
# if the output won't be printed to tty, disable the color
|
||||||
|
test -t 1 && to_tty=true
|
||||||
|
color=
|
||||||
|
|
||||||
|
#
|
||||||
|
# print usage message
|
||||||
|
#
|
||||||
|
usage() {
|
||||||
|
echo 1>&2 "usage: git effort [--above <value>] [<path>...] [-- [<log options>...]]"
|
||||||
|
}
|
||||||
|
|
||||||
|
#
|
||||||
|
# get dates for the given <commit>
|
||||||
|
#
|
||||||
|
dates() {
|
||||||
|
eval "git log $args_to_git_log --pretty='format: %ad' --date=short -- \"$1\""
|
||||||
|
}
|
||||||
|
|
||||||
|
# tput, being quiet about unknown capabilities
|
||||||
|
tputq() {
|
||||||
|
tput "$@" 2>/dev/null
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
#
|
||||||
|
# hide cursor
|
||||||
|
#
|
||||||
|
|
||||||
|
hide_cursor() {
|
||||||
|
tputq civis
|
||||||
|
}
|
||||||
|
|
||||||
|
#
|
||||||
|
# show cursor, and remove temporary file
|
||||||
|
#
|
||||||
|
|
||||||
|
show_cursor_and_cleanup() {
|
||||||
|
tputq cnorm
|
||||||
|
tputq sgr0
|
||||||
|
rm "$tmp" > /dev/null 2>&1
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
|
|
||||||
|
#
|
||||||
|
# get active days for the given <commit>
|
||||||
|
#
|
||||||
|
|
||||||
|
active_days() {
|
||||||
|
echo "$1" | sort -r | uniq | wc -l
|
||||||
|
}
|
||||||
|
|
||||||
|
#
|
||||||
|
# set 'color' based on the given <num>
|
||||||
|
#
|
||||||
|
|
||||||
|
color_for() {
|
||||||
|
if [ "$to_tty" = true ]; then
|
||||||
|
if [ "$1" -gt 200 ]; then color="$(tputq setaf 1)$(tputq bold)"
|
||||||
|
elif [ "$1" -gt 150 ]; then color="$(tputq setaf 1)" # red
|
||||||
|
elif [ "$1" -gt 125 ]; then color="$(tputq setaf 2)$(tputq bold)"
|
||||||
|
elif [ "$1" -gt 100 ]; then color="$(tputq setaf 2)" # green
|
||||||
|
elif [ "$1" -gt 75 ]; then color="$(tputq setaf 5)$(tputq bold)"
|
||||||
|
elif [ "$1" -gt 50 ]; then color="$(tputq setaf 5)" # purplish
|
||||||
|
elif [ "$1" -gt 25 ]; then color="$(tputq setaf 3)$(tputq bold)"
|
||||||
|
elif [ "$1" -gt 10 ]; then color="$(tputq setaf 3)" # yellow
|
||||||
|
else color="$(tputq sgr0)" # default color
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
color=""
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
#
|
||||||
|
# compute the effort of the given <path ...>
|
||||||
|
#
|
||||||
|
|
||||||
|
effort() {
|
||||||
|
path=$1
|
||||||
|
local commit_dates
|
||||||
|
local color reset_color commits len dot f_dot i msg active
|
||||||
|
reset_color=""
|
||||||
|
test "$to_tty" = true && reset_color="$(tputq sgr0)"
|
||||||
|
if ! commit_dates=$(dates "$path"); then
|
||||||
|
exit 255
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Ensure it's not just an empty line
|
||||||
|
if [ -z "$(head -c 1 <<<"$commit_dates")" ]
|
||||||
|
then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
commits=$(wc -l <<<"$commit_dates")
|
||||||
|
color='90'
|
||||||
|
|
||||||
|
# ignore <= --above
|
||||||
|
test "$commits" -le "$above" && exit 0
|
||||||
|
|
||||||
|
# commits
|
||||||
|
color_for $(( commits - above ))
|
||||||
|
len=${#path}
|
||||||
|
dot="."
|
||||||
|
f_dot="$path"
|
||||||
|
i=0 ; while test $i -lt $(( columns - len )) ; do
|
||||||
|
f_dot=$f_dot$dot
|
||||||
|
i=$((i+1))
|
||||||
|
done
|
||||||
|
|
||||||
|
msg=$(printf " ${color}%s %-10d" "$f_dot" "$commits")
|
||||||
|
|
||||||
|
# active days
|
||||||
|
active=$(active_days "$commit_dates")
|
||||||
|
color_for $(( active - above ))
|
||||||
|
msg="$msg $(printf "${color} %d${reset_color}\n" "$active")"
|
||||||
|
echo "$msg"
|
||||||
|
}
|
||||||
|
|
||||||
|
#
|
||||||
|
# print heading
|
||||||
|
#
|
||||||
|
|
||||||
|
heading() {
|
||||||
|
echo
|
||||||
|
printf " %-${columns}s %-10s %s\n" 'path' 'commits' 'active days'
|
||||||
|
echo
|
||||||
|
}
|
||||||
|
|
||||||
|
#
|
||||||
|
# output sorted results
|
||||||
|
#
|
||||||
|
|
||||||
|
sort_effort() {
|
||||||
|
clear
|
||||||
|
echo " "
|
||||||
|
heading
|
||||||
|
< "$tmp" sort -rn -k 2
|
||||||
|
}
|
||||||
|
|
||||||
|
#
|
||||||
|
# get processor count, default 8
|
||||||
|
#
|
||||||
|
procs() {
|
||||||
|
if ! (nproc --all || getconf NPROCESSORS_ONLN || getconf _NPROCESSORS_ONLN || grep -c processor /proc/cpuinfo) 2>/dev/null; then
|
||||||
|
echo 8
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
declare -a paths=()
|
||||||
|
while [ "${#}" -ge 1 ] ; do
|
||||||
|
|
||||||
|
case "$1" in
|
||||||
|
--above)
|
||||||
|
shift
|
||||||
|
above=$1
|
||||||
|
;;
|
||||||
|
--)
|
||||||
|
shift
|
||||||
|
args_to_git_log=$(printf " %q" "${@:1}")
|
||||||
|
break
|
||||||
|
;;
|
||||||
|
--*)
|
||||||
|
usage
|
||||||
|
echo 1>&2 "error: unknown argument $1"
|
||||||
|
echo 1>&2 "error: if that argument was meant for git-log,"
|
||||||
|
echo 1>&2 "error: please put it after two dashes ( -- )."
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
paths+=( "$1" )
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
shift
|
||||||
|
done
|
||||||
|
|
||||||
|
# Exit if above-value is not an int
|
||||||
|
if [ -z "${above##*[!0-9]*}" ] ; then
|
||||||
|
echo "error: argument to --above was not an integer" 1>&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# remove empty quotes that appear when there are no arguments
|
||||||
|
args_to_git_log="${args_to_git_log#\ \'\'}"
|
||||||
|
export args_to_git_log
|
||||||
|
|
||||||
|
# [path ...]
|
||||||
|
|
||||||
|
if test "${#paths}" -eq 0; then
|
||||||
|
save_ifs=$IFS
|
||||||
|
IFS=$(echo -en "\n\b")
|
||||||
|
paths=($(git ls-files))
|
||||||
|
IFS=$save_ifs
|
||||||
|
unset save_ifs
|
||||||
|
fi
|
||||||
|
|
||||||
|
# set column width to match longest filename
|
||||||
|
max=0
|
||||||
|
for path in "${paths[@]}"; do
|
||||||
|
cur=${#path}
|
||||||
|
if [[ $max -lt $cur ]]; then
|
||||||
|
max=$cur
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
columns=$(( max + 5 ))
|
||||||
|
export columns
|
||||||
|
|
||||||
|
# hide cursor
|
||||||
|
|
||||||
|
hide_cursor
|
||||||
|
trap show_cursor_and_cleanup INT
|
||||||
|
|
||||||
|
heading
|
||||||
|
|
||||||
|
# send paths to effort
|
||||||
|
nPaths=${#paths[@]}
|
||||||
|
nProcs=$(procs)
|
||||||
|
nJobs="\j"
|
||||||
|
for ((i=0; i<nPaths; ++i))
|
||||||
|
do
|
||||||
|
while (( ${nJobs@P} >= nProcs )); do
|
||||||
|
wait -n
|
||||||
|
done
|
||||||
|
effort "${paths[i]}" &
|
||||||
|
done|tee "$tmp"
|
||||||
|
|
||||||
|
# if more than one path, sort and print
|
||||||
|
test "$(wc -l "$tmp" | awk '{print $1}')" -gt 1 && sort_effort
|
||||||
|
echo
|
||||||
|
|
||||||
|
show_cursor_and_cleanup
|
||||||
54
bin/git-extras
Executable file
54
bin/git-extras
Executable file
|
|
@ -0,0 +1,54 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
VERSION="7.6.0-dev"
|
||||||
|
INSTALL_SCRIPT="https://raw.githubusercontent.com/tj/git-extras/main/install.sh"
|
||||||
|
|
||||||
|
update() {
|
||||||
|
local bin="$(command -v git-extras)"
|
||||||
|
local prefix=${bin%/*/*}
|
||||||
|
local orig=$PWD
|
||||||
|
|
||||||
|
curl -s $INSTALL_SCRIPT | PREFIX="$prefix" bash /dev/stdin \
|
||||||
|
&& cd "$orig" \
|
||||||
|
&& echo "... updated git-extras $VERSION -> $(git extras --version)"
|
||||||
|
}
|
||||||
|
|
||||||
|
updateForWindows() {
|
||||||
|
local bin="$(command -v git-extras)"
|
||||||
|
local prefix=${bin%/*/*}
|
||||||
|
local orig=$PWD
|
||||||
|
|
||||||
|
# we need to clean up /tmp manually on windows
|
||||||
|
cd /tmp \
|
||||||
|
&& rm -rf ./git-extras \
|
||||||
|
&& echo "Setting up 'git-extras'...." \
|
||||||
|
&& git clone https://github.com/tj/git-extras.git &> /dev/null \
|
||||||
|
&& cd git-extras \
|
||||||
|
&& git checkout \
|
||||||
|
$(git describe --tags $(git rev-list --tags --max-count=1)) \
|
||||||
|
&> /dev/null \
|
||||||
|
&& ./install.cmd "$prefix" \
|
||||||
|
&& cd "$orig" \
|
||||||
|
&& echo "... updated git-extras $VERSION -> $(git extras --version)"
|
||||||
|
rm -rf /tmp/git-extras &> /dev/null
|
||||||
|
}
|
||||||
|
|
||||||
|
case "$1" in
|
||||||
|
-v|--version)
|
||||||
|
echo $VERSION && exit 0
|
||||||
|
;;
|
||||||
|
update)
|
||||||
|
platform=$(uname -s)
|
||||||
|
if [ "${platform::9}" = "CYGWIN_NT" ] || \
|
||||||
|
[ "${platform::5}" = "MINGW" ] || \
|
||||||
|
[ "${platform::7}" = "MSYS_NT" ]
|
||||||
|
then
|
||||||
|
updateForWindows
|
||||||
|
else
|
||||||
|
update
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
git extras --help
|
||||||
|
;;
|
||||||
|
esac
|
||||||
105
bin/git-feature
Executable file
105
bin/git-feature
Executable file
|
|
@ -0,0 +1,105 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
branch_prefix=$(git config --get git-extras.feature.prefix)
|
||||||
|
|
||||||
|
if [ -z "$branch_prefix" ]; then
|
||||||
|
branch_prefix="feature"
|
||||||
|
fi
|
||||||
|
|
||||||
|
branch_separator=$(git config --get git-extras.feature.separator)
|
||||||
|
|
||||||
|
if [ -z "$branch_separator" ]; then
|
||||||
|
branch_separator="/"
|
||||||
|
fi
|
||||||
|
|
||||||
|
merge_mode="--no-ff"
|
||||||
|
finish=false
|
||||||
|
declare -a argv
|
||||||
|
while test $# != 0
|
||||||
|
do
|
||||||
|
case $1 in
|
||||||
|
-a|--alias )
|
||||||
|
if [[ -n $2 ]] && [[ $2 != -- ]]
|
||||||
|
then
|
||||||
|
shift # shift -a|-alias
|
||||||
|
branch_prefix=$1
|
||||||
|
else
|
||||||
|
echo >&2 "option $1 requires a value"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
-r|--remote )
|
||||||
|
if [[ -n $2 ]] && [[ $2 != -- ]]
|
||||||
|
then
|
||||||
|
remote=$2
|
||||||
|
shift
|
||||||
|
else
|
||||||
|
remote="origin"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
-s|--separator )
|
||||||
|
if [[ -n $2 ]] && [[ $2 != -- ]]
|
||||||
|
then
|
||||||
|
branch_separator=$2
|
||||||
|
shift
|
||||||
|
else
|
||||||
|
echo >&2 "option $1 requires a value"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
--squash )
|
||||||
|
merge_mode="--squash"
|
||||||
|
;;
|
||||||
|
--from )
|
||||||
|
start_point=$2
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
-- )
|
||||||
|
# terminate argument parsing
|
||||||
|
shift
|
||||||
|
argv+=("$@")
|
||||||
|
break
|
||||||
|
;;
|
||||||
|
finish )
|
||||||
|
finish=true
|
||||||
|
;;
|
||||||
|
* )
|
||||||
|
argv+=("$1")
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
shift
|
||||||
|
done
|
||||||
|
|
||||||
|
concatargs() {
|
||||||
|
str=$(IFS='-'; echo "$*")
|
||||||
|
branch="$branch_prefix$branch_separator$str"
|
||||||
|
}
|
||||||
|
|
||||||
|
test -z "${argv[0]}" && echo "$branch_prefix" "<name> required." 1>&2 && exit 1
|
||||||
|
|
||||||
|
concatargs "${argv[@]}"
|
||||||
|
|
||||||
|
if "${finish}"
|
||||||
|
then
|
||||||
|
git merge ${merge_mode} "$branch" && git delete-branch "$branch"
|
||||||
|
else
|
||||||
|
if [[ -n $remote ]] && [[ -z $start_point ]]
|
||||||
|
then
|
||||||
|
git create-branch -r "$remote" "$branch"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -z $remote ]] && [[ -z $start_point ]]
|
||||||
|
then
|
||||||
|
git create-branch "$branch"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -n $remote ]] && [[ -n $start_point ]]
|
||||||
|
then
|
||||||
|
git create-branch -r "$remote" --from "$start_point" "$branch"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -z $remote ]] && [[ -n $start_point ]]
|
||||||
|
then
|
||||||
|
git create-branch --from "$start_point" "$branch"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
109
bin/git-force-clone
Executable file
109
bin/git-force-clone
Executable file
|
|
@ -0,0 +1,109 @@
|
||||||
|
#! /usr/bin/env bash
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
_usage() {
|
||||||
|
echo "
|
||||||
|
Usage:
|
||||||
|
git-force-clone -b branch remote_url destination_path
|
||||||
|
|
||||||
|
Example:
|
||||||
|
git-force-clone -b master git@github.com:me/repo.git ./repo_dir
|
||||||
|
|
||||||
|
Provides the basic functionality of 'git clone', but if the destination git
|
||||||
|
repository already exists it will force-reset it to resemble a clone of the
|
||||||
|
remote.
|
||||||
|
|
||||||
|
Because it doesn't actually delete the directory, it is usually significantly
|
||||||
|
faster than the alternative of deleting the directory and cloning the
|
||||||
|
repository from scratch.
|
||||||
|
|
||||||
|
**CAUTION**: If the repository exists, this will destroy *all* local work:
|
||||||
|
changed files will be reset, local branches and other remotes will be removed.
|
||||||
|
|
||||||
|
OPTIONS:
|
||||||
|
-b, --branch The branch to pull from the remote
|
||||||
|
-h, --help Display this help message
|
||||||
|
"
|
||||||
|
}
|
||||||
|
|
||||||
|
_check() {
|
||||||
|
if [ -z "$1" ]; then
|
||||||
|
echo "Error: Missing ${2}"
|
||||||
|
_usage
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
main() {
|
||||||
|
while [[ -n "${1:-}" ]] && [[ "${1:0:1}" == "-" ]]; do
|
||||||
|
case $1 in
|
||||||
|
-b | --branch )
|
||||||
|
branch=${2:-}
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
-h | --help )
|
||||||
|
_usage
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
* )
|
||||||
|
echo "Error: Invalid option: $1" >>/dev/stderr
|
||||||
|
_usage
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
shift
|
||||||
|
done
|
||||||
|
|
||||||
|
remote_url=${1:-}
|
||||||
|
destination_path=${2:-}
|
||||||
|
|
||||||
|
_check "${remote_url}" "remote_url"
|
||||||
|
_check "${destination_path}" "destination_path"
|
||||||
|
|
||||||
|
if [ -d "${destination_path}/.git" ]; then
|
||||||
|
(
|
||||||
|
cd "${destination_path}"
|
||||||
|
|
||||||
|
# Delete all remotes
|
||||||
|
for remote in $(git remote); do
|
||||||
|
git remote rm "${remote}"
|
||||||
|
done
|
||||||
|
|
||||||
|
# Add origin
|
||||||
|
git remote add origin "${remote_url}"
|
||||||
|
git fetch origin
|
||||||
|
|
||||||
|
# Set default branch
|
||||||
|
if [ -z "${branch:-}" ]; then
|
||||||
|
branch=$(LC_ALL=C git remote show origin | grep -oP '(?<=HEAD branch: )[^ ]+$')
|
||||||
|
git remote set-head origin "${branch}"
|
||||||
|
else
|
||||||
|
git remote set-head origin -a
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Make sure current branch is clean
|
||||||
|
git clean -fd
|
||||||
|
git reset --hard HEAD
|
||||||
|
|
||||||
|
# Get on the desired branch
|
||||||
|
git checkout "${branch}"
|
||||||
|
git reset --hard "origin/${branch}"
|
||||||
|
|
||||||
|
# Delete all other branches
|
||||||
|
# shellcheck disable=SC2063
|
||||||
|
branches=$(git branch | grep -v '*' | xargs)
|
||||||
|
if [ -n "${branches}" ]; then
|
||||||
|
git branch -D "${branches}"
|
||||||
|
fi
|
||||||
|
)
|
||||||
|
elif [ -n "${branch:-}" ]; then
|
||||||
|
git clone -b "${branch}" "${remote_url}" "${destination_path}"
|
||||||
|
else
|
||||||
|
git clone "${remote_url}" "${destination_path}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
main "$@"
|
||||||
|
|
||||||
|
exit 0
|
||||||
67
bin/git-fork
Executable file
67
bin/git-fork
Executable file
|
|
@ -0,0 +1,67 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
abort() {
|
||||||
|
echo "$@"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
url="$1"
|
||||||
|
test -z "$url" && url=$(git remote get-url origin 2> /dev/null) && origin=true
|
||||||
|
# validate repo url
|
||||||
|
test -z "$url" && abort "github repo needs to be specified as an argument"
|
||||||
|
|
||||||
|
# validate user
|
||||||
|
echo "Enter your github username"
|
||||||
|
read -r user
|
||||||
|
[ -n "$user" ] || abort "git username required"
|
||||||
|
# personal access token
|
||||||
|
# config name is github-personal-access-token '_' is not allowed in git config
|
||||||
|
|
||||||
|
github_personal_access_token=$(git config --default "$GITHUB_TOKEN" git-extras.github-personal-access-token)
|
||||||
|
|
||||||
|
test -z "$github_personal_access_token" && abort "GITHUB_TOKEN, or git config git-extras.github-personal-access-token required"
|
||||||
|
|
||||||
|
# extract owner + project from repo url
|
||||||
|
project=${url##*/}
|
||||||
|
owner=${url%/"$project"}
|
||||||
|
project=${project%.git}
|
||||||
|
if [[ $owner == git@* ]]; then
|
||||||
|
owner=${owner##*:}
|
||||||
|
else
|
||||||
|
owner=${owner##*/}
|
||||||
|
fi
|
||||||
|
|
||||||
|
# validate
|
||||||
|
[[ -z "$project" || -z "$owner" ]] && abort "github repo needs to be specified as an argument"
|
||||||
|
|
||||||
|
# create fork
|
||||||
|
if ! curl -qsf \
|
||||||
|
-X POST \
|
||||||
|
-u "$user:$github_personal_access_token" \
|
||||||
|
-H "X-GitHub-OTP: $MFA_CODE" \
|
||||||
|
"https://api.github.com/repos/$owner/$project/forks"
|
||||||
|
then
|
||||||
|
abort "fork failed"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Add GitHub remote branch via SSH (you will be prompted to verify the server's credentials)? (y/n)"
|
||||||
|
read -r use_ssh
|
||||||
|
# Check if user has ssh configured with GitHub
|
||||||
|
if [ -n "$use_ssh" ] && ssh -T git@github.com 2>&1 | grep -qi 'success'; then
|
||||||
|
remote_prefix="git@github.com:"
|
||||||
|
else
|
||||||
|
remote_prefix="https://github.com/"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$origin" = true ]; then
|
||||||
|
git remote rename origin upstream
|
||||||
|
git remote add origin "${remote_prefix}${user}/${project}.git"
|
||||||
|
git fetch origin
|
||||||
|
else
|
||||||
|
# clone forked repo into current dir
|
||||||
|
git clone "${remote_prefix}${user}/${project}.git" "$project"
|
||||||
|
# add reference to origin fork so can merge in upstream changes
|
||||||
|
cd "$project" || exit
|
||||||
|
git remote add upstream "${remote_prefix}${owner}/${project}.git"
|
||||||
|
git fetch upstream
|
||||||
|
fi
|
||||||
24
bin/git-fresh-branch
Executable file
24
bin/git-fresh-branch
Executable file
|
|
@ -0,0 +1,24 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
branch=$1
|
||||||
|
|
||||||
|
test -z "$branch" && echo "branch required." 1>&2 && exit 1
|
||||||
|
|
||||||
|
changes=$(git status --porcelain)
|
||||||
|
|
||||||
|
clean()
|
||||||
|
{
|
||||||
|
git symbolic-ref HEAD "refs/heads/$branch"
|
||||||
|
rm .git/index
|
||||||
|
git clean -fdx
|
||||||
|
}
|
||||||
|
|
||||||
|
if [ -n "$changes" ]; then
|
||||||
|
read -rp "All untracked changes will be lost. Continue [y/N]? " res
|
||||||
|
case $res in
|
||||||
|
[Yy]* ) ;;
|
||||||
|
* ) exit 0;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
|
clean
|
||||||
40
bin/git-get
Executable file
40
bin/git-get
Executable file
|
|
@ -0,0 +1,40 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
_usage() {
|
||||||
|
printf '%s\n' "usage: ${0##*/} <url>
|
||||||
|
usage: ${0##*/} --help
|
||||||
|
|
||||||
|
Clone a repository in a particular directory."
|
||||||
|
}
|
||||||
|
|
||||||
|
if (( $# == 0 )); then
|
||||||
|
_usage
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
for arg; do
|
||||||
|
if [ "$arg" = '-h' ] || [ "$arg" = '--help' ]; then
|
||||||
|
_usage
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
url=$1
|
||||||
|
if ! shift; then
|
||||||
|
printf 'ERROR: Failed to shift' >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
clone_path=$(git config --get git-extras.get.clone-path)
|
||||||
|
|
||||||
|
if [ -z "$clone_path" ]; then
|
||||||
|
printf 'ERROR: %s\n' "Git configuration key 'git-extras.get.clone-path' must be set to a directory to clone under" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
dirname=${url%/}
|
||||||
|
dirname=${dirname%.git}
|
||||||
|
dirname=${dirname##*/}
|
||||||
|
|
||||||
|
mkdir -p "$clone_path"
|
||||||
|
git clone "$url" "$clone_path/$dirname" "$@"
|
||||||
21
bin/git-gh-pages
Executable file
21
bin/git-gh-pages
Executable file
|
|
@ -0,0 +1,21 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
echo 'setting up gh-pages'
|
||||||
|
echo '-------------------'
|
||||||
|
|
||||||
|
echo 'Tell me your github account username: '
|
||||||
|
read -r username
|
||||||
|
|
||||||
|
echo 'Now, tell me your repository name: '
|
||||||
|
read -r repository
|
||||||
|
|
||||||
|
git stash \
|
||||||
|
&& git checkout -b 'gh-pages' \
|
||||||
|
&& echo 'My Page' > index.html \
|
||||||
|
&& git add . \
|
||||||
|
&& git commit -a -m 'Initial commit' \
|
||||||
|
&& git remote add origin https://github.com/"$username"/"$repository".git \
|
||||||
|
&& git push -u origin gh-pages \
|
||||||
|
&& echo 'Complete' \
|
||||||
|
&& echo '-------------------' \
|
||||||
|
&& echo 'You can find your last changes in the stash!'
|
||||||
11
bin/git-graft
Executable file
11
bin/git-graft
Executable file
|
|
@ -0,0 +1,11 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
src=$1
|
||||||
|
dst=$2
|
||||||
|
|
||||||
|
test -z "$src" && echo "source branch required." 1>&2 && exit 1
|
||||||
|
test -z "$dst" && echo "destination branch required." 1>&2 && exit 1
|
||||||
|
|
||||||
|
git checkout "$dst" \
|
||||||
|
&& git merge --no-ff "$src" \
|
||||||
|
&& git branch -d "$src"
|
||||||
119
bin/git-guilt
Executable file
119
bin/git-guilt
Executable file
|
|
@ -0,0 +1,119 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
for param in "$@"
|
||||||
|
do
|
||||||
|
case $param in
|
||||||
|
-h)
|
||||||
|
echo 'Usage: git-guilt [<options>] <since> <until>'
|
||||||
|
echo 'Calculates the change in blame between two revisions'
|
||||||
|
echo 'Example: git guilt HEAD~3 HEAD'
|
||||||
|
echo
|
||||||
|
echo 'Options:'
|
||||||
|
echo
|
||||||
|
echo ' -h, --help output usage information'
|
||||||
|
echo ' -e, --email display author emails instead of names'
|
||||||
|
echo ' -w, --ignore-whitespace ignore whitespace only changes when attributing blame'
|
||||||
|
echo ' -d, --debug output debug information'
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
-e|--email )
|
||||||
|
EMAIL='-e'
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
-w|--ignore-whitespace )
|
||||||
|
NOT_WHITESPACE='-w'
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
-d|--debug )
|
||||||
|
DEBUG=$(git_extra_mktemp)
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
cd "$(git-root)" || exit # cd for git blame
|
||||||
|
MERGED_LOG=$(git_extra_mktemp)
|
||||||
|
if [[ $EMAIL == '-e' ]]
|
||||||
|
then
|
||||||
|
PATTERN='s/^author-mail <\(.*\)>/\1/p'
|
||||||
|
else
|
||||||
|
PATTERN='s/^author //p'
|
||||||
|
fi
|
||||||
|
|
||||||
|
for file in $(git diff --name-only "$@")
|
||||||
|
do
|
||||||
|
test -n "$DEBUG" && echo "git blame $file"
|
||||||
|
# $1 - since $2 - until
|
||||||
|
# shellcheck disable=SC2086
|
||||||
|
git blame $NOT_WHITESPACE --line-porcelain "$1" -- "$file" 2> /dev/null |
|
||||||
|
LC_ALL=C sed -n "$PATTERN" | sort | uniq -c | LC_ALL=C sed 's/^\(.\)/- \1/' >> "$MERGED_LOG"
|
||||||
|
# if $2 not given, use current commit as "until"
|
||||||
|
# shellcheck disable=SC2086
|
||||||
|
git blame $NOT_WHITESPACE --line-porcelain "${2-@}" -- "$file" 2> /dev/null |
|
||||||
|
LC_ALL=C sed -n "$PATTERN" | sort | uniq -c | LC_ALL=C sed 's/^\(.\)/+ \1/' >> "$MERGED_LOG"
|
||||||
|
done
|
||||||
|
|
||||||
|
DEBUG="$DEBUG" awk '
|
||||||
|
NR==1 {
|
||||||
|
# the index of $2 does not change in each line
|
||||||
|
name_start_at = index($0, $3)
|
||||||
|
}
|
||||||
|
/^\+/ {
|
||||||
|
contributors[substr($0, name_start_at)] += $2
|
||||||
|
}
|
||||||
|
/^-/ {
|
||||||
|
contributors[substr($0, name_start_at)] -= $2
|
||||||
|
}
|
||||||
|
END {
|
||||||
|
for (people in contributors) {
|
||||||
|
if (ENVIRON["DEBUG"]) {
|
||||||
|
printf("%d %s\n", contributors[people], people) >> ENVIRON["DEBUG"]
|
||||||
|
}
|
||||||
|
if (contributors[people] != 0) {
|
||||||
|
printf("%d %s\n", contributors[people], people)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}' "$MERGED_LOG" | sort -nr | # only gawk supports built-in sort function
|
||||||
|
while read -r line
|
||||||
|
do
|
||||||
|
people=${line#* }
|
||||||
|
num=${line%% *}
|
||||||
|
|
||||||
|
if [[ $num -gt 0 ]]
|
||||||
|
then
|
||||||
|
printf "%-29s \033[00;32m" "$people"
|
||||||
|
if [[ $num -ge 50 ]]
|
||||||
|
then
|
||||||
|
len=${#num}
|
||||||
|
for (( i = 0; i < 48 - len; i++ ))
|
||||||
|
do
|
||||||
|
printf "+"
|
||||||
|
done
|
||||||
|
printf "(%s)" "$num"
|
||||||
|
else
|
||||||
|
for (( i = 0; i < num; i++ ))
|
||||||
|
do
|
||||||
|
printf "+"
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
printf "%-29s \033[00;31m" "$people"
|
||||||
|
if [[ $num -le -50 ]]
|
||||||
|
then
|
||||||
|
len=${#num}
|
||||||
|
for (( i = 0; i < 48 - len; i++ ))
|
||||||
|
do
|
||||||
|
printf "-"
|
||||||
|
done
|
||||||
|
printf "(%s)" "$num"
|
||||||
|
else
|
||||||
|
for (( i = 0; i > num; i-- ))
|
||||||
|
do
|
||||||
|
printf "-"
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
printf "\033[00m\n"
|
||||||
|
done
|
||||||
|
|
||||||
|
test -n "$DEBUG" && sort -nr "$DEBUG"
|
||||||
289
bin/git-ignore
289
bin/git-ignore
|
|
@ -1,10 +1,285 @@
|
||||||
#!/bin/sh
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
if test $# -eq 0; then
|
GIT_DIR=$(git rev-parse --git-dir 2>/dev/null)
|
||||||
echo "arguments required" && exit 1
|
|
||||||
else
|
# awk script that takes 2 input files. The first one is newline deilmeted
|
||||||
for pattern in $@; do
|
# string of patterns to look for, second one is the file to remove them from.
|
||||||
echo $pattern >> .gitignore
|
# outfile has to be passed with -v upon call and is the file result is
|
||||||
echo "... added '$pattern'"
|
# written to.
|
||||||
|
read -r -d '' AWK_SCRIPT <<'EOF'
|
||||||
|
# process the first "file" (newline split argument list)
|
||||||
|
NR==FNR { a[b++]=$0; next; }
|
||||||
|
# process file
|
||||||
|
{
|
||||||
|
found=0;
|
||||||
|
for (i=0; i != b; i++) {
|
||||||
|
if (a[i] == $0) { found=1; }
|
||||||
|
}
|
||||||
|
if (found == 1) { print "... removing '"$0"'"; }
|
||||||
|
else { print $0 > outfile }
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
show_contents() {
|
||||||
|
local file="${2/#~/$HOME}"
|
||||||
|
if [ -f "$file" ]; then
|
||||||
|
echo "$1 gitignore: $2" && cat "$file"
|
||||||
|
else
|
||||||
|
echo "There is no $1 .gitignore yet"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
cd_to_git_root() {
|
||||||
|
local error_level="$1"
|
||||||
|
|
||||||
|
if ! git rev-parse --git-dir &>/dev/null; then
|
||||||
|
if [ "$error_level" = '--warn' ]; then
|
||||||
|
echo "Warning: Not currently in a Git repository" >&2
|
||||||
|
elif [ "$error_level" = '--error' ]; then
|
||||||
|
echo "Error: Not currently in a Git repository" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
local result=
|
||||||
|
if result=$(git rev-parse --show-toplevel 2>/dev/null); then
|
||||||
|
cd "$result" || exit
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
global_ignore() {
|
||||||
|
if ! git config --global core.excludesFile 2>/dev/null; then
|
||||||
|
if [ -f "$HOME/.gitignore" ]; then
|
||||||
|
echo "$HOME/.gitignore"
|
||||||
|
else
|
||||||
|
echo "${XDG_CONFIG_HOME:-$HOME/.config}/git/ignore"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
show_global() {
|
||||||
|
show_contents Global "$(global_ignore)"
|
||||||
|
}
|
||||||
|
|
||||||
|
add_global() {
|
||||||
|
local global_gitignore
|
||||||
|
global_gitignore="$(global_ignore)"
|
||||||
|
if [ -z "$global_gitignore" ]; then
|
||||||
|
echo "Can't find global .gitignore."
|
||||||
|
echo ""
|
||||||
|
echo "Use 'git config --global --add core.excludesfile ~/.gitignore-global' to set the path to your global gitignore file to '~/.gitignore-global'."
|
||||||
|
echo ""
|
||||||
|
else
|
||||||
|
add_patterns "$global_gitignore" "$@"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
show_local() {
|
||||||
|
cd_to_git_root --warn
|
||||||
|
show_contents Local .gitignore
|
||||||
|
}
|
||||||
|
|
||||||
|
add_local() {
|
||||||
|
cd_to_git_root --warn
|
||||||
|
add_patterns .gitignore "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
show_private() {
|
||||||
|
cd_to_git_root --error
|
||||||
|
show_contents Private "${GIT_DIR}/info/exclude"
|
||||||
|
}
|
||||||
|
|
||||||
|
add_private() {
|
||||||
|
cd_to_git_root --error
|
||||||
|
test -d "${GIT_DIR}/info" || mkdir -p "${GIT_DIR}/info"
|
||||||
|
add_patterns "${GIT_DIR}/info/exclude" "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
add_patterns() {
|
||||||
|
echo "Adding pattern(s) to: $1"
|
||||||
|
local file="${1/#~/$HOME}"
|
||||||
|
dir_name=$(dirname "$file")
|
||||||
|
if [ ! -d "$dir_name" ]; then
|
||||||
|
mkdir -p "$dir_name"
|
||||||
|
fi
|
||||||
|
if [ -s "$file" ]; then
|
||||||
|
# If the content of $file doesn't end with a newline, add one
|
||||||
|
test "$(tail -c 1 "$file")" != "" && echo "" >> "$file"
|
||||||
|
fi
|
||||||
|
for pattern in "${@:2}"; do
|
||||||
|
echo "... adding '$pattern'"
|
||||||
|
(test -f "$file" && test "$pattern" && grep -q -F -x -- "$pattern" "$file") || echo "$pattern" >> "$file"
|
||||||
done
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
edit_file() {
|
||||||
|
local file="${2/#~/$HOME}"
|
||||||
|
if [ -f "$file" ]; then
|
||||||
|
echo "Editing $1 gitignore: ($2)" && eval "$(git var GIT_EDITOR) $(printf '%q' "$file")"
|
||||||
|
echo "Done."
|
||||||
|
else
|
||||||
|
echo "There is no $1 .gitignore yet." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
_usage() {
|
||||||
|
cat << EOF
|
||||||
|
Usage: git ignore [OPTION]... [PATTERN]...
|
||||||
|
Modify or display local global or private gitignore files.
|
||||||
|
|
||||||
|
OPTIONs set context (local, global, private) and action (display, add,
|
||||||
|
remove, edit). In case of collision, last flag overwrites preceding ones.
|
||||||
|
Defaults are: local context, action is display (see man git ignore for
|
||||||
|
more details).
|
||||||
|
Context examples:
|
||||||
|
local: .../repo/.gitignore
|
||||||
|
private: .../repo/.git/info/exclude
|
||||||
|
global: \$HOME/.gitignore
|
||||||
|
|
||||||
|
PATTERNs are the gitignore patterns as specified in git documentation.
|
||||||
|
|
||||||
|
Options:
|
||||||
|
-l, --local Set context to local gitignore
|
||||||
|
-g, --global Set context to global gitignore
|
||||||
|
-p, --private Set context to private gitignore
|
||||||
|
-e, --edit Set action to edit
|
||||||
|
-r, --remove Set action to remove
|
||||||
|
-h, --help Print this message
|
||||||
|
-- End of options delimiter
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
remove_patterns() {
|
||||||
|
declare -a args
|
||||||
|
local tmpfile
|
||||||
|
args=( "${@:3}" )
|
||||||
|
local file="${2/#~/$HOME}"
|
||||||
|
if [ -f "$file" ]; then
|
||||||
|
tmpfile="$(mktemp -q "${TMPDIR:-/tmp}"/git-extras-ignore.XXXXXX 2>/dev/null)"
|
||||||
|
echo "Removing patterns from $1 gitignore: $2"
|
||||||
|
awk -v outfile="$tmpfile" "${AWK_SCRIPT}"\
|
||||||
|
<(printf '%s\n' "${args[@]}") "$file"
|
||||||
|
cat "$tmpfile" > "$file"
|
||||||
|
rm "$tmpfile"
|
||||||
|
else
|
||||||
|
echo "There is no $1 .gitignore yet"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# can have only 1 action and 1 level, if conflicting flags are passed
|
||||||
|
# latter ones overwrite former
|
||||||
|
# sanity of args not tested (e.g. there should be no args if -e is supplied)
|
||||||
|
if test $# -eq 0; then
|
||||||
|
show_global
|
||||||
|
echo "---------------------------------"
|
||||||
|
show_local
|
||||||
|
echo "---------------------------------"
|
||||||
|
show_private
|
||||||
|
else
|
||||||
|
# parse flags and args
|
||||||
|
level="none"
|
||||||
|
action="none"
|
||||||
|
declare -a args
|
||||||
|
while [ $# -gt 0 ]; do
|
||||||
|
case "$1" in
|
||||||
|
-h|--help)
|
||||||
|
_usage
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
-l|--local)
|
||||||
|
level="local"
|
||||||
|
;;
|
||||||
|
-g|--global)
|
||||||
|
level="global"
|
||||||
|
;;
|
||||||
|
-p|--private)
|
||||||
|
level="private"
|
||||||
|
;;
|
||||||
|
-e|--edit)
|
||||||
|
action="edit"
|
||||||
|
;;
|
||||||
|
-r|--remove)
|
||||||
|
action="remove"
|
||||||
|
;;
|
||||||
|
--)
|
||||||
|
shift
|
||||||
|
args+=("$@")
|
||||||
|
break
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
args+=("$1")
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
shift
|
||||||
|
done
|
||||||
|
# compatible with previous version
|
||||||
|
if [ "$action" = "none" ]; then
|
||||||
|
case "$level" in
|
||||||
|
local)
|
||||||
|
test ${#args[@]} -ne 0 && add_local "${args[@]}" && echo
|
||||||
|
show_local
|
||||||
|
;;
|
||||||
|
global)
|
||||||
|
test ${#args[@]} -ne 0 && add_global "${args[@]}" && echo
|
||||||
|
show_global
|
||||||
|
;;
|
||||||
|
private)
|
||||||
|
test ${#args[@]} -ne 0 && add_private "${args[@]}" && echo
|
||||||
|
show_private
|
||||||
|
;;
|
||||||
|
none)
|
||||||
|
add_local "${args[@]}"
|
||||||
|
# maybe show_local here too?
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
exit 0
|
||||||
|
# open file in editor
|
||||||
|
elif [ "$action" = "edit" ]; then
|
||||||
|
case "$level" in
|
||||||
|
local|none)
|
||||||
|
cd_to_git_root --warn
|
||||||
|
edit_file 'local' .gitignore && echo
|
||||||
|
;;
|
||||||
|
global)
|
||||||
|
global_gitignore="$(global_ignore)"
|
||||||
|
edit_file global "$global_gitignore" && echo
|
||||||
|
;;
|
||||||
|
private)
|
||||||
|
cd_to_git_root --error
|
||||||
|
test -d "${GIT_DIR}/info" || mkdir -p "${GIT_DIR}/info"
|
||||||
|
edit_file private "${GIT_DIR}/info/exclude" && echo
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
exit 0
|
||||||
|
# remove entries
|
||||||
|
else
|
||||||
|
if [ ${#args[@]} -eq 0 ]; then
|
||||||
|
echo "Nothing to remove."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
case "$level" in
|
||||||
|
local|none)
|
||||||
|
cd_to_git_root --warn
|
||||||
|
remove_patterns 'local' .gitignore "${args[@]}" && echo
|
||||||
|
show_local
|
||||||
|
;;
|
||||||
|
global)
|
||||||
|
global_gitignore="$(global_ignore)"
|
||||||
|
remove_patterns global "$global_gitignore" "${args[@]}" && echo
|
||||||
|
show_global
|
||||||
|
;;
|
||||||
|
private)
|
||||||
|
cd_to_git_root --error
|
||||||
|
remove_patterns private "${GIT_DIR}/info/exclude" "${args[@]}" && echo
|
||||||
|
show_private
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
exit 0
|
||||||
|
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
151
bin/git-ignore-io
Executable file
151
bin/git-ignore-io
Executable file
|
|
@ -0,0 +1,151 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
gitignore_io_url="https://www.toptal.com/developers/gitignore/api/"
|
||||||
|
default_path="$HOME/.gi_list"
|
||||||
|
if [[ -n "$XDG_CACHE_HOME" ]]; then
|
||||||
|
default_path="$XDG_CACHE_HOME/git-extras/gi_list"
|
||||||
|
mkdir -p "$XDG_CACHE_HOME/git-extras"
|
||||||
|
fi
|
||||||
|
|
||||||
|
update_gi_list() {
|
||||||
|
curl -L -s "${gitignore_io_url}/list" > "$default_path"
|
||||||
|
}
|
||||||
|
|
||||||
|
print_in_alphabetical_order() {
|
||||||
|
local first_character previous_first_character ignorable
|
||||||
|
local first=true
|
||||||
|
for ignorable in $(echo "$gi_list" | sort);
|
||||||
|
do
|
||||||
|
first_character=${ignorable:0:1}
|
||||||
|
if [[ $first_character = "$previous_first_character" ]]; then
|
||||||
|
printf " %s" "$ignorable"
|
||||||
|
elif [[ $first = true ]]; then
|
||||||
|
previous_first_character=$first_character
|
||||||
|
first=false
|
||||||
|
printf "%s" "$ignorable"
|
||||||
|
else
|
||||||
|
previous_first_character=$first_character
|
||||||
|
printf "\n%s" "$ignorable"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
echo
|
||||||
|
}
|
||||||
|
|
||||||
|
print_in_table_format() {
|
||||||
|
echo "$gi_list" | column
|
||||||
|
}
|
||||||
|
|
||||||
|
search() {
|
||||||
|
for type in $gi_list;
|
||||||
|
do
|
||||||
|
if [[ "$type" == *$1* ]]
|
||||||
|
then
|
||||||
|
echo "$type"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
print_last_modified_time() {
|
||||||
|
if ! gi_list_date=$(stat -c "%y" "$default_path" 2> /dev/null); then
|
||||||
|
if ! gi_list_date=$(stat -f "%t%Sm" "$default_path" 2> /dev/null); then
|
||||||
|
if ! gi_list_date=$(date -r "$default_path" +%s 2> /dev/null); then
|
||||||
|
gi_list_date=0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
echo "Last update time: $gi_list_date"
|
||||||
|
}
|
||||||
|
|
||||||
|
gi() {
|
||||||
|
curl -L -s $gitignore_io_url/"$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
gi_replace() {
|
||||||
|
gi "$1" > .gitignore
|
||||||
|
}
|
||||||
|
|
||||||
|
gi_append() {
|
||||||
|
gi "$1" >> .gitignore
|
||||||
|
}
|
||||||
|
|
||||||
|
show_usage() {
|
||||||
|
echo "Usage:"
|
||||||
|
echo " git ignore-io <types>... Show gitignore template"
|
||||||
|
echo " [-a|--append] <types>... Append new .gitignore content to .gitignore under the current directory"
|
||||||
|
echo " [-r|--replace] <types>... Export new .gitignore to the current directory (The old one will be replaced)"
|
||||||
|
echo " [-l|--list-in-table] Print available types in table format"
|
||||||
|
echo " [-L|--list-alphabetically] Print available types in alphabetical order "
|
||||||
|
echo " [-s|--search] <word> Search word in available types"
|
||||||
|
echo " [-t|--show-update-time] Show the last modified time of $default_path (where the list of available types is stored)"
|
||||||
|
echo " [-u|--update-list] Update $default_path"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
check_list_exist() {
|
||||||
|
if ! [ -f "$default_path" ]; then
|
||||||
|
echo "-----Initial gitignore.io list----"
|
||||||
|
update_gi_list
|
||||||
|
echo "-----Save to $default_path-----"
|
||||||
|
echo
|
||||||
|
fi
|
||||||
|
gi_list=$(tr "," "\n" < "$default_path" 2>/dev/null)
|
||||||
|
}
|
||||||
|
|
||||||
|
check_list_exist
|
||||||
|
if [[ $# -eq 0 ]]; then
|
||||||
|
show_usage
|
||||||
|
else
|
||||||
|
case $1 in
|
||||||
|
-a|--append|-r|--replace)
|
||||||
|
opt=$1
|
||||||
|
shift
|
||||||
|
if [[ $# -eq 0 ]]; then
|
||||||
|
echo "There should be at least one type"
|
||||||
|
echo
|
||||||
|
show_usage
|
||||||
|
exit
|
||||||
|
fi
|
||||||
|
|
||||||
|
gi_to_curl=$(echo "$@" | tr " " ",")
|
||||||
|
case $opt in
|
||||||
|
-a|--append)
|
||||||
|
gi_append "$gi_to_curl"
|
||||||
|
;;
|
||||||
|
-r|--replace)
|
||||||
|
gi_replace "$gi_to_curl"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
exit
|
||||||
|
;;
|
||||||
|
-t|--show-update-time)
|
||||||
|
print_last_modified_time
|
||||||
|
;;
|
||||||
|
-u|--update-list)
|
||||||
|
update_gi_list
|
||||||
|
;;
|
||||||
|
-s|--search)
|
||||||
|
opt=$1
|
||||||
|
shift
|
||||||
|
if [[ $# -eq 0 ]]; then
|
||||||
|
show_usage
|
||||||
|
exit
|
||||||
|
fi
|
||||||
|
search "$1"
|
||||||
|
;;
|
||||||
|
-L|--list-alphabetically)
|
||||||
|
print_in_alphabetical_order
|
||||||
|
;;
|
||||||
|
-l|--list-in-table)
|
||||||
|
print_in_table_format
|
||||||
|
;;
|
||||||
|
-*)
|
||||||
|
echo No Such option
|
||||||
|
show_usage
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
gi_to_curl=$(echo "$@" | tr " " ",")
|
||||||
|
gi "$gi_to_curl"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
79
bin/git-info
Executable file
79
bin/git-info
Executable file
|
|
@ -0,0 +1,79 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
GREEN="$(tput setaf 2)"
|
||||||
|
NORMAL="$(tput sgr0)"
|
||||||
|
if [ "$1" = "--color" ] || [ "$2" = "--color" ] || \
|
||||||
|
[ "$1" = "-c" ] || [ "$2" = "-c" ] ; then
|
||||||
|
COLOR_TITLE="$GREEN"
|
||||||
|
else
|
||||||
|
COLOR_TITLE="$NORMAL"
|
||||||
|
fi
|
||||||
|
|
||||||
|
HIDE_CONFIG=
|
||||||
|
if [ "$1" != "--no-config" ] && [ "$2" != "--no-config" ]; then
|
||||||
|
HIDE_CONFIG=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
get_config() {
|
||||||
|
cmd_get_config="$(git config --get-all git-extras.info.config-grep)"
|
||||||
|
if [ -z "$cmd_get_config" ]; then
|
||||||
|
git config --list
|
||||||
|
else
|
||||||
|
eval "$cmd_get_config"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
most_recent_commit() {
|
||||||
|
cmd_get_log="$(git config --get-all git-extras.info.log)"
|
||||||
|
if [ -z "$cmd_get_log" ]; then
|
||||||
|
git log --max-count=1 --pretty=short
|
||||||
|
else
|
||||||
|
eval "$cmd_get_log"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
submodules() {
|
||||||
|
# short sha1
|
||||||
|
git submodule status | sed 's/\([^abcdef0-9]\{0,2\}\)\([abcdef0-9]\{7\}\)\([abcdef0-9]\{33\}\)\(.*\)/\1\2\4/'
|
||||||
|
}
|
||||||
|
|
||||||
|
local_branches() {
|
||||||
|
git branch
|
||||||
|
}
|
||||||
|
|
||||||
|
remote_branches() {
|
||||||
|
git branch -r
|
||||||
|
}
|
||||||
|
|
||||||
|
remote_urls() {
|
||||||
|
git remote -v
|
||||||
|
}
|
||||||
|
|
||||||
|
echon() {
|
||||||
|
echo "$@"
|
||||||
|
echo
|
||||||
|
}
|
||||||
|
|
||||||
|
echo
|
||||||
|
echon "${COLOR_TITLE}## Remote URLs:${NORMAL}"
|
||||||
|
echon "$(remote_urls)"
|
||||||
|
|
||||||
|
echon "${COLOR_TITLE}## Remote Branches:${NORMAL}"
|
||||||
|
echon "$(remote_branches)"
|
||||||
|
|
||||||
|
echon "${COLOR_TITLE}## Local Branches:${NORMAL}"
|
||||||
|
echon "$(local_branches)"
|
||||||
|
|
||||||
|
SUBMODULES_LOG=$(submodules)
|
||||||
|
if [ -n "$SUBMODULES_LOG" ]; then
|
||||||
|
echon "${COLOR_TITLE}## Submodule(s):${NORMAL}"
|
||||||
|
echon "$SUBMODULES_LOG"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echon "${COLOR_TITLE}## Most Recent Commit:${NORMAL}"
|
||||||
|
echon "$(most_recent_commit)"
|
||||||
|
|
||||||
|
if [ -n "$HIDE_CONFIG" ]; then
|
||||||
|
echon "${COLOR_TITLE}## Configuration (.git/config):${NORMAL}"
|
||||||
|
echon "$(get_config)"
|
||||||
|
fi
|
||||||
3
bin/git-local-commits
Executable file
3
bin/git-local-commits
Executable file
|
|
@ -0,0 +1,3 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
git log "@{upstream}..@" "$@"
|
||||||
5
bin/git-lock
Executable file
5
bin/git-lock
Executable file
|
|
@ -0,0 +1,5 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
filename="$1"
|
||||||
|
test -z "$filename" && echo "filename required." 1>&2 && exit 1
|
||||||
|
git update-index --skip-worktree "$filename"
|
||||||
3
bin/git-locked
Executable file
3
bin/git-locked
Executable file
|
|
@ -0,0 +1,3 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
git ls-files -v | grep ^S | sed -e 's|S ||'
|
||||||
87
bin/git-magic
Executable file
87
bin/git-magic
Executable file
|
|
@ -0,0 +1,87 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
|
||||||
|
# Bash unofficial strict mode
|
||||||
|
set -eo pipefail
|
||||||
|
IFS=$'\n\t'
|
||||||
|
|
||||||
|
cd "$(git rev-parse --show-toplevel)"
|
||||||
|
|
||||||
|
USAGE='git-magic [-a] [-m msg] [-e] [-p] [-f]'
|
||||||
|
|
||||||
|
ALL=false
|
||||||
|
PUSH=false
|
||||||
|
FORCE=''
|
||||||
|
ARGS=()
|
||||||
|
|
||||||
|
while getopts "m:eapfh" arg; do
|
||||||
|
case "${arg}" in
|
||||||
|
m)
|
||||||
|
ARGS+=("-m")
|
||||||
|
ARGS+=("${OPTARG}")
|
||||||
|
;;
|
||||||
|
e)
|
||||||
|
ARGS+=("-e")
|
||||||
|
;;
|
||||||
|
a)
|
||||||
|
ALL=true
|
||||||
|
;;
|
||||||
|
p)
|
||||||
|
PUSH=true
|
||||||
|
;;
|
||||||
|
f)
|
||||||
|
FORCE='--force-with-lease'
|
||||||
|
;;
|
||||||
|
h)
|
||||||
|
echo "$USAGE"
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
?)
|
||||||
|
echo "${USAGE}"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
shift $((OPTIND-1))
|
||||||
|
|
||||||
|
if [[ $# != 0 ]]; then
|
||||||
|
echo "Unknown arguments: $*"
|
||||||
|
echo "${USAGE}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
set -- "${ARGS[@]}" # restore positional parameters
|
||||||
|
|
||||||
|
if [[ $ALL == true ]]; then
|
||||||
|
|
||||||
|
# Check if there is no changes to stage
|
||||||
|
if [[ -z $(git status --porcelain) ]]; then
|
||||||
|
echo "No changes to commit"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Get confirmation from user
|
||||||
|
git status
|
||||||
|
echo "Everything will be added"
|
||||||
|
read -rp "Press enter to continue"
|
||||||
|
|
||||||
|
# Restore staging area so that, for example,
|
||||||
|
# add and modify will not be separate entries in status
|
||||||
|
git restore --staged . || true
|
||||||
|
git add .
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Commit with generated message
|
||||||
|
git commit --no-edit "$@" -m "$(git status --porcelain -uno)"
|
||||||
|
|
||||||
|
if [[ $PUSH == true ]]; then
|
||||||
|
git push $FORCE
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --no-edit by default. use option -e to override this
|
||||||
|
|
||||||
|
# Arguments are passed with quoted "$@" to avoid misparsing
|
||||||
|
|
||||||
|
# Generated message comes after user provided arguments
|
||||||
|
# so that user can insert title before it
|
||||||
48
bin/git-merge-into
Executable file
48
bin/git-merge-into
Executable file
|
|
@ -0,0 +1,48 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
current_branch() {
|
||||||
|
git rev-parse --abbrev-ref HEAD
|
||||||
|
}
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
echo "Usage: git merge-into [src] dest [--ff-only]"
|
||||||
|
}
|
||||||
|
|
||||||
|
cur_branch=$(current_branch)
|
||||||
|
|
||||||
|
stashed=0
|
||||||
|
if [ -n "$(git status --porcelain)" ];
|
||||||
|
then
|
||||||
|
echo "Local modifications detected, stashing"
|
||||||
|
stashed=1
|
||||||
|
git stash
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "${!#}" = '--ff-only' ]; then
|
||||||
|
case $# in
|
||||||
|
2 ) # dest --ff
|
||||||
|
git push "$(git rev-parse --show-toplevel)" "$cur_branch":"$1";;
|
||||||
|
3 )
|
||||||
|
git push "$(git rev-parse --show-toplevel)" "$1":"$2";;
|
||||||
|
* )
|
||||||
|
usage
|
||||||
|
esac
|
||||||
|
else
|
||||||
|
case $# in
|
||||||
|
1 )
|
||||||
|
git checkout "$1"
|
||||||
|
git merge "$cur_branch" "$1" && git checkout "$cur_branch"
|
||||||
|
;;
|
||||||
|
2 )
|
||||||
|
git checkout "$2"
|
||||||
|
git merge "$1" "$2" && git checkout "$cur_branch"
|
||||||
|
;;
|
||||||
|
* )
|
||||||
|
usage
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ $stashed -eq 1 ];
|
||||||
|
then
|
||||||
|
git stash pop;
|
||||||
|
fi
|
||||||
25
bin/git-merge-repo
Executable file
25
bin/git-merge-repo
Executable file
|
|
@ -0,0 +1,25 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
repo=$1
|
||||||
|
branch=$2
|
||||||
|
prefix=$3
|
||||||
|
flat=0
|
||||||
|
|
||||||
|
if test "$prefix" = "."; then
|
||||||
|
prefix=$(mktemp -u 'git-merge-repo.XXXXXXX')
|
||||||
|
flat=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
git subtree add -P "$prefix" "$repo" "$branch" "${@:4}"
|
||||||
|
|
||||||
|
message=$(git log -1 --pretty=%B)
|
||||||
|
|
||||||
|
if test $flat -eq 1; then
|
||||||
|
git stash -u
|
||||||
|
mv -i "$prefix"/* ./
|
||||||
|
git undo
|
||||||
|
git add .
|
||||||
|
git commit -am "$message"
|
||||||
|
git stash apply
|
||||||
|
rm -drf "$prefix"
|
||||||
|
fi
|
||||||
52
bin/git-missing
Executable file
52
bin/git-missing
Executable file
|
|
@ -0,0 +1,52 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
echo 1>&2 "usage: git missing [<first branch>] <second branch> [<git log options>] [[--] <path>...]"
|
||||||
|
}
|
||||||
|
|
||||||
|
if [ "${#}" -lt 1 ]
|
||||||
|
then
|
||||||
|
usage
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
declare -a git_log_args=()
|
||||||
|
declare -a branches=()
|
||||||
|
declare -a pathspec=()
|
||||||
|
declare parse_path=false
|
||||||
|
|
||||||
|
for arg in "$@" ; do
|
||||||
|
|
||||||
|
if [[ $parse_path == true ]]; then
|
||||||
|
pathspec+=("$@")
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
|
||||||
|
case "$arg" in
|
||||||
|
--)
|
||||||
|
parse_path=true
|
||||||
|
;;
|
||||||
|
--*)
|
||||||
|
git_log_args+=( "$arg" )
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
branches+=( "$arg" )
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
firstbranch=
|
||||||
|
secondbranch=
|
||||||
|
if [ ${#branches[@]} -eq 2 ]
|
||||||
|
then
|
||||||
|
firstbranch="${branches[0]}"
|
||||||
|
secondbranch="${branches[1]}"
|
||||||
|
elif [ ${#branches[@]} -eq 1 ]
|
||||||
|
then
|
||||||
|
secondbranch="${branches[0]}"
|
||||||
|
else
|
||||||
|
echo >&2 "error: at least one branch required"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
git log "${git_log_args[@]}" "$firstbranch"..."$secondbranch" --format="%m %h %s" --left-right -- "${pathspec[@]}"
|
||||||
36
bin/git-mr
Executable file
36
bin/git-mr
Executable file
|
|
@ -0,0 +1,36 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
if [ -z "${1-}" ] ; then
|
||||||
|
echo "mr number or URL required. See --help for usage." 1>&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
remote_ref_kind=merge-requests
|
||||||
|
|
||||||
|
if test "$1" = "clean"; then
|
||||||
|
git for-each-ref refs/heads/mr/* --format='%(refname)' | while read -r ref; do
|
||||||
|
git branch -D "${ref#refs/heads/}"
|
||||||
|
done
|
||||||
|
exit 0
|
||||||
|
elif [[ $1 =~ ^(https?://[^/]+/(.+))/merge_requests/([0-9]+).*$ ]]; then
|
||||||
|
remote=${BASH_REMATCH[1]}.git
|
||||||
|
id=${BASH_REMATCH[3]}
|
||||||
|
elif [[ $1 =~ ^(https?://[^/]+/(.+))/pulls/([0-9]+).*$ ]]; then
|
||||||
|
# Forgejo/Codeberg pull request URL, e.g.
|
||||||
|
# https://codeberg.org/owner/repository/pulls/453
|
||||||
|
remote=${BASH_REMATCH[1]}.git
|
||||||
|
id=${BASH_REMATCH[3]}
|
||||||
|
remote_ref_kind=pull
|
||||||
|
else
|
||||||
|
id=$1
|
||||||
|
remote=${2:-origin}
|
||||||
|
fi
|
||||||
|
|
||||||
|
branch=mr/$id
|
||||||
|
remote_ref=refs/$remote_ref_kind/$id/head
|
||||||
|
git fetch -fu "$remote" "$remote_ref:$branch"
|
||||||
|
git checkout "$branch"
|
||||||
|
git config --local --replace "branch.$branch.merge" "$remote_ref"
|
||||||
|
git config --local --replace "branch.$branch.remote" "$remote"
|
||||||
23
bin/git-obliterate
Executable file
23
bin/git-obliterate
Executable file
|
|
@ -0,0 +1,23 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
file=""
|
||||||
|
range=""
|
||||||
|
for i in "$@"
|
||||||
|
do
|
||||||
|
# use '--' to separate file list and rev-list arguments
|
||||||
|
test "$i" == '--' && shift && break
|
||||||
|
file="$file"' '"$i"
|
||||||
|
shift
|
||||||
|
done
|
||||||
|
test -n "$*" && range=("$@")
|
||||||
|
|
||||||
|
test -z "$file" && echo "file required." 1>&2 && exit 1
|
||||||
|
if [ -z "${range[*]}" ]
|
||||||
|
then
|
||||||
|
git filter-branch -f --index-filter "git rm -r --cached ""$file"" --ignore-unmatch" \
|
||||||
|
--prune-empty --tag-name-filter cat -- --all
|
||||||
|
else
|
||||||
|
# $range is an array so that we can forward multiple rev-list arguments
|
||||||
|
git filter-branch -f --index-filter "git rm -r --cached ""$file"" --ignore-unmatch" \
|
||||||
|
--prune-empty --tag-name-filter cat -- "${range[@]}"
|
||||||
|
fi
|
||||||
11
bin/git-paste
Executable file
11
bin/git-paste
Executable file
|
|
@ -0,0 +1,11 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -e
|
||||||
|
set -o pipefail
|
||||||
|
|
||||||
|
if ! command -v pastebinit &>/dev/null; then
|
||||||
|
echo >&2 "To run 'git paste', you need to install pastebinit in your system"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
test $# -ne 0 || set -- '@{u}'
|
||||||
|
git format-patch --stdout "$@" | pastebinit -f diff
|
||||||
77
bin/git-pr
Executable file
77
bin/git-pr
Executable file
|
|
@ -0,0 +1,77 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# Based on https://gist.github.com/gnarf/5406589 and https://gist.github.com/jhnns/d654d9d6da6d3b749986
|
||||||
|
TO_MERGE=
|
||||||
|
|
||||||
|
pull() {
|
||||||
|
local remote="$1"
|
||||||
|
local id="$2"
|
||||||
|
local branch="$3"
|
||||||
|
local ref="refs/pull/$id/head"
|
||||||
|
|
||||||
|
if [ -n "$TO_MERGE" ]; then
|
||||||
|
ref="refs/pull/$id/merge"
|
||||||
|
fi
|
||||||
|
|
||||||
|
git fetch -fu "$remote" "$ref:$branch" && \
|
||||||
|
git checkout "$branch" && \
|
||||||
|
git config --local --replace "branch.$branch.merge" "$ref" && \
|
||||||
|
git config --local --replace "branch.$branch.remote" "$remote"
|
||||||
|
}
|
||||||
|
|
||||||
|
pull_pr_if_matched() {
|
||||||
|
if [[ $1 =~ ^(.*):([0-9]+)|(https?://[^/]+/.+)/pull/([0-9]+).*$ ]]; then
|
||||||
|
if [[ -n ${BASH_REMATCH[2]} ]]; then
|
||||||
|
remote="${BASH_REMATCH[1]:-origin}"
|
||||||
|
id="${BASH_REMATCH[2]}"
|
||||||
|
else
|
||||||
|
remote="${BASH_REMATCH[3]}.git"
|
||||||
|
id="${BASH_REMATCH[4]}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
branch=pr/$id
|
||||||
|
pull "$remote" "$id" "$branch"
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "$1 doesn't match the pr id pattern."
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
for arg in "$@"; do
|
||||||
|
case "$arg" in
|
||||||
|
-m|--merge)
|
||||||
|
TO_MERGE=1
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
# set the argument back
|
||||||
|
set -- "$@" "$arg"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
shift
|
||||||
|
done
|
||||||
|
|
||||||
|
test -z "$1" && echo "pr number required." 1>&2 && exit 1
|
||||||
|
|
||||||
|
if test "$1" = "clean"; then
|
||||||
|
git for-each-ref refs/heads/pr/* --format='%(refname)' | while read -r ref; do
|
||||||
|
git branch -D "${ref#refs/heads/}"
|
||||||
|
done
|
||||||
|
|
||||||
|
elif [[ "$1" =~ ^[0-9]+$ ]]; then
|
||||||
|
remote_pref=${2:-$(git config --get git-extras.pr.remote)}
|
||||||
|
remote=${remote_pref:-origin}
|
||||||
|
id=$1
|
||||||
|
branch=pr/$id
|
||||||
|
pull "$remote" "$id" "$branch"
|
||||||
|
|
||||||
|
else
|
||||||
|
rc=1
|
||||||
|
while [ "$1" != "" ]; do
|
||||||
|
pull_pr_if_matched "$1"
|
||||||
|
rc=$?
|
||||||
|
shift
|
||||||
|
done
|
||||||
|
|
||||||
|
exit "$rc"
|
||||||
|
fi
|
||||||
83
bin/git-psykorebase
Executable file
83
bin/git-psykorebase
Executable file
|
|
@ -0,0 +1,83 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
PROGRAM=$0
|
||||||
|
PRIMARY_BRANCH=""
|
||||||
|
SECONDARY_BRANCH=""
|
||||||
|
FF="--ff"
|
||||||
|
CONTINUE="no"
|
||||||
|
|
||||||
|
current_branch() {
|
||||||
|
git rev-parse --abbrev-ref HEAD
|
||||||
|
}
|
||||||
|
|
||||||
|
function usage()
|
||||||
|
{
|
||||||
|
echo "USAGE: ${PROGRAM} PRIMARY_BRANCH [SECONDARY_BRANCH] [--no-ff]"
|
||||||
|
echo "USAGE: ${PROGRAM} --continue"
|
||||||
|
echo ""
|
||||||
|
echo "OPTIONS:"
|
||||||
|
echo " --no-ff: Force rebase commit."
|
||||||
|
echo " -c|--continue: Continue after the user updates conflicts."
|
||||||
|
}
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]
|
||||||
|
do
|
||||||
|
key="$1"
|
||||||
|
|
||||||
|
case $key in
|
||||||
|
--no-ff)
|
||||||
|
FF="--no-ff"
|
||||||
|
;;
|
||||||
|
-c|--continue)
|
||||||
|
CONTINUE=yes
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
if [[ "$PRIMARY_BRANCH" == "" ]]; then
|
||||||
|
PRIMARY_BRANCH=$key
|
||||||
|
elif [[ "$SECONDARY_BRANCH" == "" ]]; then
|
||||||
|
SECONDARY_BRANCH=$key
|
||||||
|
else
|
||||||
|
usage
|
||||||
|
exit 20 # Error during arguments parsing
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
shift # past argument or value
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ "$SECONDARY_BRANCH" == "" ]]; then
|
||||||
|
SECONDARY_BRANCH=$(current_branch)
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$CONTINUE" == "yes" ]]; then
|
||||||
|
TARGET_BRANCH=$(current_branch)
|
||||||
|
SECONDARY_BRANCH=${TARGET_BRANCH%"-rebased-on-top-of-"*}
|
||||||
|
PRIMARY_BRANCH=${TARGET_BRANCH#*"-rebased-on-top-of-"}
|
||||||
|
if [[ "${SECONDARY_BRANCH}-rebased-on-top-of-${PRIMARY_BRANCH}" != $TARGET_BRANCH ]]; then
|
||||||
|
echo "Couldn't continue rebasing on ${TARGET_BRANCH}"
|
||||||
|
exit 30 # Impossible to detect PRIMARY_BRANCH AND SECONDARY_BRANCH
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Continuing rebasing of $SECONDARY_BRANCH on top of $PRIMARY_BRANCH"
|
||||||
|
git commit || exit 51
|
||||||
|
git branch -d "${SECONDARY_BRANCH}" || exit 52
|
||||||
|
git branch -m "${TARGET_BRANCH}" "${SECONDARY_BRANCH}" || exit 53
|
||||||
|
|
||||||
|
elif [[ "$PRIMARY_BRANCH" == "" ]]; then
|
||||||
|
usage
|
||||||
|
exit 10 # Missing arguments PRIMARY_BRANCH
|
||||||
|
else
|
||||||
|
echo "Rebasing $SECONDARY_BRANCH on top of $PRIMARY_BRANCH"
|
||||||
|
TARGET_BRANCH="${SECONDARY_BRANCH}-rebased-on-top-of-${PRIMARY_BRANCH}"
|
||||||
|
|
||||||
|
git checkout "${PRIMARY_BRANCH}" || exit 41
|
||||||
|
git checkout -b "${TARGET_BRANCH}" || exit 42
|
||||||
|
|
||||||
|
if git merge "${SECONDARY_BRANCH}" ${FF} \
|
||||||
|
-m "Psycho-rebased branch ${SECONDARY_BRANCH} on top of ${PRIMARY_BRANCH}"; then
|
||||||
|
git branch -d "${SECONDARY_BRANCH}" || exit 43
|
||||||
|
git branch -m "${TARGET_BRANCH}" "${SECONDARY_BRANCH}" || exit 44
|
||||||
|
else
|
||||||
|
echo "Resolve the conflict and run ``${PROGRAM} --continue``."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
85
bin/git-pull-request
Executable file
85
bin/git-pull-request
Executable file
|
|
@ -0,0 +1,85 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
#
|
||||||
|
# Echo <msg> and exit
|
||||||
|
#
|
||||||
|
|
||||||
|
abort() {
|
||||||
|
echo >&2 "$@"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
#
|
||||||
|
# Produce json with <title>, <body>, <head> and <base>
|
||||||
|
#
|
||||||
|
|
||||||
|
json() {
|
||||||
|
local title="${1//\"/\\\"}"
|
||||||
|
local body="${2//\"/\\\"}"
|
||||||
|
local head="${3//\"/\\\"}"
|
||||||
|
local base="${4//\"/\\\"}"
|
||||||
|
cat <<EOF
|
||||||
|
{
|
||||||
|
"title": "$title",
|
||||||
|
"body": "$body",
|
||||||
|
"head": "$head",
|
||||||
|
"base": "$base"
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
# personal access token
|
||||||
|
# config name is github-personal-access-token '_' is not allowed in git config
|
||||||
|
|
||||||
|
github_personal_access_token=$(git config --default "$GITHUB_TOKEN" git-extras.github-personal-access-token)
|
||||||
|
|
||||||
|
test -z "$github_personal_access_token" && abort "GITHUB_TOKEN or git config git-extras.github-personal-access-token required"
|
||||||
|
|
||||||
|
# branch
|
||||||
|
|
||||||
|
branch=${1-$(git symbolic-ref HEAD | sed 's/refs\/heads\///')}
|
||||||
|
remote=$(git config branch."$branch".remote)
|
||||||
|
if [ -z "$remote" ]; then
|
||||||
|
echo 'no upstream found, push to origin as default'
|
||||||
|
remote="origin"
|
||||||
|
fi
|
||||||
|
[ "$remote" = "." ] && abort "the upstream should be a remote branch."
|
||||||
|
|
||||||
|
# make sure it's pushed
|
||||||
|
|
||||||
|
git push "$remote" "$branch" || abort "failed to push $branch"
|
||||||
|
|
||||||
|
remote_url=$(git config remote."$remote".url)
|
||||||
|
if [[ "$remote_url" == git@* ]]; then
|
||||||
|
project=${remote_url##*:}
|
||||||
|
else
|
||||||
|
project=${remote_url#https://*/}
|
||||||
|
fi
|
||||||
|
project=${project%.git}
|
||||||
|
|
||||||
|
# prompt
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo " create pull-request for $project '$branch'"
|
||||||
|
echo
|
||||||
|
printf " title: " && read -r title
|
||||||
|
printf " body: " && read -r body
|
||||||
|
printf " base [%s]: " "$(git_extra_default_branch)" && read -r base
|
||||||
|
printf " GitHub two-factor authentication code (leave blank if not set up): " && read -r mfa_code
|
||||||
|
echo
|
||||||
|
|
||||||
|
# create pull request
|
||||||
|
|
||||||
|
if [ -z "$base" ]
|
||||||
|
then
|
||||||
|
base="$(git_extra_default_branch)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
body=$(json "$title" "$body" "$branch" "$base")
|
||||||
|
|
||||||
|
curl \
|
||||||
|
-X POST \
|
||||||
|
-H "Accept: application/vnd.github.v3+json" \
|
||||||
|
-H "Authorization: token $github_personal_access_token" \
|
||||||
|
-H "X-GitHub-OTP: $mfa_code" \
|
||||||
|
"https://api.github.com/repos/$project/pulls" -d "$body"
|
||||||
161
bin/git-reauthor
Executable file
161
bin/git-reauthor
Executable file
|
|
@ -0,0 +1,161 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
|
||||||
|
init_variables() {
|
||||||
|
COMMAND=${0#*-}
|
||||||
|
|
||||||
|
CONFIG=false
|
||||||
|
ALL=false
|
||||||
|
unset OLD_EMAIL
|
||||||
|
unset CORRECT_EMAIL
|
||||||
|
unset CORRECT_NAME
|
||||||
|
TYPE='both'
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat << EOF
|
||||||
|
usage: git ${COMMAND} [<options>]
|
||||||
|
|
||||||
|
Options
|
||||||
|
-a, --all rewrite all identities in commits and tags
|
||||||
|
-c, --use-config define correct values from user Git config
|
||||||
|
-e, --correct-email <email> define the correct email to set
|
||||||
|
-n, --correct-name <name> define the correct name to set
|
||||||
|
-o, --old-email <email> rewrite identities matching old email in commits and tags
|
||||||
|
-t, --type <id> define the type of identities affected by the rewrite
|
||||||
|
author, committer, both (default)
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
error() {
|
||||||
|
if [[ -n "$1" ]]; then
|
||||||
|
local msg=$( echo "error: $1" | sed 's/\\n/\\n /g' )
|
||||||
|
echo -e "${msg}" >&2
|
||||||
|
fi
|
||||||
|
usage
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
reauthor() {
|
||||||
|
local author='
|
||||||
|
if ${ALL} || [ "${GIT_AUTHOR_EMAIL}" = "${OLD_EMAIL}" ]; then
|
||||||
|
[ -z "${CORRECT_EMAIL+x}" ] || export GIT_AUTHOR_EMAIL="${CORRECT_EMAIL}"
|
||||||
|
[ -z "${CORRECT_NAME+x}" ] || export GIT_AUTHOR_NAME="${CORRECT_NAME}"
|
||||||
|
fi
|
||||||
|
'
|
||||||
|
local committer='
|
||||||
|
if ${ALL} || [ "${GIT_COMMITTER_EMAIL}" = "${OLD_EMAIL}" ]; then
|
||||||
|
[ -z "${CORRECT_EMAIL+x}" ] || export GIT_COMMITTER_EMAIL="${CORRECT_EMAIL}"
|
||||||
|
[ -z "${CORRECT_NAME+x}" ] || export GIT_COMMITTER_NAME="${CORRECT_NAME}"
|
||||||
|
fi
|
||||||
|
'
|
||||||
|
local filter
|
||||||
|
|
||||||
|
case "${TYPE}" in
|
||||||
|
author) filter="${author}" ;;
|
||||||
|
committer) filter="${committer}" ;;
|
||||||
|
both) filter="${author} ${committer}" ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
export ALL
|
||||||
|
export OLD_EMAIL
|
||||||
|
export CORRECT_EMAIL
|
||||||
|
export CORRECT_NAME
|
||||||
|
|
||||||
|
git filter-branch --force --env-filter "${filter}" \
|
||||||
|
--tag-name-filter cat -- --branches --tags
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
parse_options() {
|
||||||
|
while [[ "$#" -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--all|-a)
|
||||||
|
ALL=true
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--correct-email|-e)
|
||||||
|
[[ -n "${2+x}" ]] || error 'Missing correct-email value'
|
||||||
|
CORRECT_EMAIL="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
-h)
|
||||||
|
usage
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
--correct-name|-n)
|
||||||
|
[[ -n "${2+x}" ]] || error 'Missing correct-name value'
|
||||||
|
CORRECT_NAME="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--old-email|-o)
|
||||||
|
[[ -n "${2+x}" ]] || error 'Missing old-email value'
|
||||||
|
OLD_EMAIL="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--type|-t)
|
||||||
|
[[ -n "${2+x}" ]] || error 'Missing type value'
|
||||||
|
TYPE="$2"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--use-config|-c)
|
||||||
|
CONFIG=true
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
error "invalid option '$1'"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if ${CONFIG}; then
|
||||||
|
# use config values if not explicitly already defined
|
||||||
|
[[ -n "${CORRECT_EMAIL+x}" ]] || CORRECT_EMAIL=$( git config user.email )
|
||||||
|
[[ -n "${CORRECT_NAME+x}" ]] || CORRECT_NAME=$( git config user.name )
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
validate_options() {
|
||||||
|
# Either OLD_EMAIL should be set or ALL should be true
|
||||||
|
if [[ -z "${OLD_EMAIL+x}" ]] && ! ${ALL}; then
|
||||||
|
msg="missing target of the rewrite"
|
||||||
|
msg="${msg}\nuse either --old-email option or --all flag"
|
||||||
|
error "${msg}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# OLD_EMAIL shouldn't be set if ALL is true as well to prevent misuse
|
||||||
|
if [[ -n "${OLD_EMAIL+x}" ]] && ${ALL}; then
|
||||||
|
msg="ambiguous target of the rewrite"
|
||||||
|
msg="${msg}\nuse either --old-email option or --all flag"
|
||||||
|
error "${msg}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# CORRECT_NAME should be either unset or set to non-empty string
|
||||||
|
[[ -n "${CORRECT_NAME-x}" ]] || error "empty name is not allowed"
|
||||||
|
|
||||||
|
# Either CORRECT_EMAIL or CORRECT_NAME should be set
|
||||||
|
if [[ -z "${CORRECT_EMAIL+x}" ]] && [[ -z "${CORRECT_NAME+x}" ]]; then
|
||||||
|
msg="missing correct email and/or name to set"
|
||||||
|
msg="${msg}\nuse --correct-email and/or --correct-name options"
|
||||||
|
msg="${msg}\nor --use-config flag with user values set in Git config"
|
||||||
|
error "${msg}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# TYPE should be a valid identifier
|
||||||
|
if [[ "${TYPE}" != 'both' ]] \
|
||||||
|
&& [[ "${TYPE}" != 'author' ]] \
|
||||||
|
&& [[ "${TYPE}" != 'committer' ]]; then
|
||||||
|
error "invalid type '${TYPE}'"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
init_variables
|
||||||
|
parse_options "$@"
|
||||||
|
validate_options
|
||||||
|
|
||||||
|
reauthor
|
||||||
63
bin/git-rebase-patch
Executable file
63
bin/git-rebase-patch
Executable file
|
|
@ -0,0 +1,63 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# Warn on a dirty work tree.
|
||||||
|
git rev-parse --verify HEAD >/dev/null || exit 1
|
||||||
|
git update-index -q --ignore-submodules --refresh
|
||||||
|
if ! git diff-files --quiet --ignore-submodules
|
||||||
|
then
|
||||||
|
echo "WARNING (dirty work tree): The patch will only be checked against actual commits."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Warn on a dirty index.
|
||||||
|
if ! git diff-index --cached --quiet --ignore-submodules HEAD --
|
||||||
|
then
|
||||||
|
echo "WARNING (dirty index): The patch will only be checked against actual commits."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Use a temporary index.
|
||||||
|
index=$(git_extra_mktemp)
|
||||||
|
cleanup() {
|
||||||
|
rm "$index"
|
||||||
|
exit 2
|
||||||
|
}
|
||||||
|
trap cleanup INT
|
||||||
|
|
||||||
|
# Go back in history while parent commits are available.
|
||||||
|
echo "Trying to find a commit the patch applies to..."
|
||||||
|
rev=$(git rev-parse HEAD)
|
||||||
|
while [ $? = 0 ]
|
||||||
|
do
|
||||||
|
GIT_INDEX_FILE=$index git read-tree "$rev"
|
||||||
|
|
||||||
|
# Try to apply the patch.
|
||||||
|
GIT_INDEX_FILE=$index git apply --cached "$1" &>/dev/null
|
||||||
|
patch_failed=$?
|
||||||
|
|
||||||
|
# Do it again, but show the error, if the problem is the patch itself.
|
||||||
|
if [ $patch_failed = 128 ]
|
||||||
|
then
|
||||||
|
GIT_INDEX_FILE=$index git apply --index --check "$1"
|
||||||
|
exit $patch_failed
|
||||||
|
fi
|
||||||
|
|
||||||
|
# The patch applied. Commit and rebase.
|
||||||
|
if [ $patch_failed = 0 ]
|
||||||
|
then
|
||||||
|
# Manufacture a commit.
|
||||||
|
tree=$(GIT_INDEX_FILE=$index git write-tree)
|
||||||
|
commit=$(git commit-tree "$tree" -p "$rev" -m "$1")
|
||||||
|
rm "$index"
|
||||||
|
|
||||||
|
echo "Patch applied to $(git rev-parse --short "$rev") as $(git rev-parse --short "$commit")"
|
||||||
|
|
||||||
|
git cherry-pick "$commit"
|
||||||
|
exit $?
|
||||||
|
fi
|
||||||
|
|
||||||
|
rev=$(git rev-parse --verify -q "$rev^")
|
||||||
|
done
|
||||||
|
|
||||||
|
# No compatible commit found. Restore.
|
||||||
|
echo "Failed to find a commit the patch applies to."
|
||||||
|
rm "$index"
|
||||||
|
exit 1
|
||||||
140
bin/git-release
140
bin/git-release
|
|
@ -1,12 +1,138 @@
|
||||||
#!/bin/sh
|
#!/usr/bin/env bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
hook() {
|
||||||
|
local hook=.git/hooks/$1.sh
|
||||||
|
# compat without extname
|
||||||
|
if test ! -f "$hook"; then
|
||||||
|
hook=.git/hooks/$1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if test -f "$hook"; then
|
||||||
|
echo "... $1"
|
||||||
|
shift
|
||||||
|
if test -x "$hook"; then
|
||||||
|
$hook "$@"
|
||||||
|
else
|
||||||
|
. "$hook" "$@"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
exit_with_msg() {
|
||||||
|
>&2 echo "$1"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
if test $# -gt 0; then
|
if test $# -gt 0; then
|
||||||
echo "... releasing $1"
|
remote=''
|
||||||
git commit -a -m "Release $1" \
|
|
||||||
&& git tag "$1" \
|
# check for flags
|
||||||
&& git push \
|
while test $# != 0
|
||||||
&& git push --tags \
|
do
|
||||||
|
case "$1" in
|
||||||
|
-c) need_changelog=true;;
|
||||||
|
-r) remote=$2; shift ;;
|
||||||
|
-m) msg=$2; shift ;;
|
||||||
|
-s)
|
||||||
|
test -n "$keyid" &&
|
||||||
|
exit_with_msg "Please use '-s' OR '-u'"
|
||||||
|
sign=true
|
||||||
|
;;
|
||||||
|
-u)
|
||||||
|
test -n "$sign" &&
|
||||||
|
exit_with_msg "Please use '-s' OR '-u'"
|
||||||
|
keyid=$2
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--semver)
|
||||||
|
test -z "$2" &&
|
||||||
|
exit_with_msg "major/minor/patch required for --semver option"
|
||||||
|
semver=$2
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--prefix)
|
||||||
|
test -z "$2" &&
|
||||||
|
exit_with_msg "prefix string required for --prefix option"
|
||||||
|
prefix="$2"
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--no-empty-commit) no_empty_commit=true;;
|
||||||
|
--) shift; hook_args="$hook_args $*"; break;;
|
||||||
|
*) test -z "$version" && version=$1 ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
shift
|
||||||
|
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ -n "$semver" ]; then
|
||||||
|
if [ -z "$(git tag)" ]; then
|
||||||
|
echo "there is no tag in the git repo" 1>&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
latest_tag=$(git describe --tags "$(git rev-list --tags --max-count=1)")
|
||||||
|
|
||||||
|
if [[ ! "$latest_tag" =~ \
|
||||||
|
^$prefix([^0-9]*)([1-9][0-9]+|[0-9])\.([1-9][0-9]+|[0-9])\.([1-9][0-9]+|[0-9])(.*) ]]; then
|
||||||
|
echo "the latest tag doesn't match semver format requirement" 1>&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
case "$semver" in
|
||||||
|
major ) version="${BASH_REMATCH[2]}" ;;
|
||||||
|
minor ) version="${BASH_REMATCH[3]}" ;;
|
||||||
|
patch ) version="${BASH_REMATCH[4]}" ;;
|
||||||
|
* ) echo "invalid semver argument given: $semver" 1>&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
(( ++version ))
|
||||||
|
|
||||||
|
case "$semver" in
|
||||||
|
major ) version="$prefix${BASH_REMATCH[1]}$version.0.0${BASH_REMATCH[5]}" ;;
|
||||||
|
minor ) version="$prefix${BASH_REMATCH[1]}${BASH_REMATCH[2]}.$version.0${BASH_REMATCH[5]}" ;;
|
||||||
|
patch ) version="$prefix${BASH_REMATCH[1]}${BASH_REMATCH[2]}.${BASH_REMATCH[3]}.$version${BASH_REMATCH[5]}" ;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
|
hook_args="$version"
|
||||||
|
|
||||||
|
if [ -z "$msg" ]; then
|
||||||
|
msg="Release ${version}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# shellcheck disable=SC2086
|
||||||
|
hook pre-release $hook_args \
|
||||||
|
|| exit_with_msg "pre-release hook failed! Cancelling release."
|
||||||
|
echo "... releasing $version"
|
||||||
|
if [ "$need_changelog" = true ]; then
|
||||||
|
git-changelog -t "$version"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$no_empty_commit" = true ]; then
|
||||||
|
git commit -a -m "$msg" || true
|
||||||
|
else
|
||||||
|
git commit -a -m "$msg" --allow-empty
|
||||||
|
fi
|
||||||
|
|
||||||
|
declare -a sign_args
|
||||||
|
if [ "$sign" = true ]; then
|
||||||
|
sign_args=("-s")
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -n "$keyid" ]; then
|
||||||
|
sign_args=("-u" "$keyid")
|
||||||
|
fi
|
||||||
|
|
||||||
|
# shellcheck disable=SC2086
|
||||||
|
git tag "${sign_args[@]}" $version -a -m "$msg" \
|
||||||
|
&& git push $remote --tags \
|
||||||
|
&& git push $remote \
|
||||||
|
&& hook post-release $hook_args \
|
||||||
&& echo "... complete"
|
&& echo "... complete"
|
||||||
else
|
else
|
||||||
echo "tag required" && exit 1
|
echo "tag required" 1>&2 && exit 1
|
||||||
fi
|
fi
|
||||||
|
|
|
||||||
23
bin/git-rename-branch
Executable file
23
bin/git-rename-branch
Executable file
|
|
@ -0,0 +1,23 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Assert there is at least one branch provided
|
||||||
|
test -z "$1" && echo "new branch name required." 1>&2 && exit 1
|
||||||
|
|
||||||
|
if [ -z "$2" ]; then
|
||||||
|
new_branch="$1"
|
||||||
|
old_branch="$(git symbolic-ref --short -q HEAD)"
|
||||||
|
else
|
||||||
|
new_branch="$2"
|
||||||
|
old_branch="$1"
|
||||||
|
fi
|
||||||
|
|
||||||
|
remote=$(git config branch."$old_branch".remote; true)
|
||||||
|
|
||||||
|
git branch -m "$old_branch" "$new_branch"
|
||||||
|
# check if the branch is tracking a remote branch
|
||||||
|
if [[ -n "$remote" && "$remote" != "." ]]
|
||||||
|
then
|
||||||
|
git push "$remote" :"$old_branch"
|
||||||
|
git push --set-upstream "$remote" "$new_branch"
|
||||||
|
fi
|
||||||
101
bin/git-rename-file
Executable file
101
bin/git-rename-file
Executable file
|
|
@ -0,0 +1,101 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# Usage function
|
||||||
|
usage() {
|
||||||
|
cat <<EOF
|
||||||
|
Usage: git rename-file [OPTIONS] <source> <destination>
|
||||||
|
|
||||||
|
Description:
|
||||||
|
Rename a file or directory and ensure Git recognizes the change, regardless of filesystem case-sensitivity.
|
||||||
|
It combines the functionality of the "mv" command and "git mv". This is particularly useful for renaming files or directories
|
||||||
|
to change only their case, which might not be detected by Git on case-insensitive filesystems.
|
||||||
|
|
||||||
|
Options:
|
||||||
|
-h, --help Show this help message and exit.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
git rename-file old_filename new_filename
|
||||||
|
git rename-file old_directory new_directory
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check for help option
|
||||||
|
if [[ "$1" == "-h" || "$1" == "--help" ]]; then
|
||||||
|
usage
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check for correct number of arguments
|
||||||
|
if [ "$#" -ne 2 ]; then
|
||||||
|
echo "Error: Incorrect number of arguments."
|
||||||
|
echo ""
|
||||||
|
usage >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Assign variables
|
||||||
|
SOURCE="$1"
|
||||||
|
DESTINATION="$2"
|
||||||
|
TEMP_NAME="${SOURCE}.temp"
|
||||||
|
|
||||||
|
# Function to check if a file or directory exists in a case-sensitive manner
|
||||||
|
check_case_sensitive_exists() {
|
||||||
|
local path="$1"
|
||||||
|
local dir
|
||||||
|
local base
|
||||||
|
|
||||||
|
dir=$(dirname "$path")
|
||||||
|
base=$(basename "$path")
|
||||||
|
|
||||||
|
if [ -e "$dir" ]; then
|
||||||
|
if (cd "$dir" && find . -maxdepth 1 -name "$base" | grep -q "$base"); then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check if source exists and is under version control
|
||||||
|
if ! check_case_sensitive_exists "$SOURCE"; then
|
||||||
|
echo "Error: Source '$SOURCE' does not exist."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! git ls-files --error-unmatch "$SOURCE" > /dev/null 2>&1; then
|
||||||
|
echo "Error: Source '$SOURCE' is not under version control. If file or directory is new, it must at least be staged."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check if destination already exists
|
||||||
|
if check_case_sensitive_exists "$DESTINATION"; then
|
||||||
|
echo "Error: Destination '$DESTINATION' already exists."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check if the destination directory exists
|
||||||
|
DEST_DIR=$(dirname "$DESTINATION")
|
||||||
|
if ! check_case_sensitive_exists "$DEST_DIR"; then
|
||||||
|
echo "Error: Destination directory '$DEST_DIR' does not exist."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Create a rollback function
|
||||||
|
rollback() {
|
||||||
|
echo "Rolling back changes..."
|
||||||
|
if [ -e "$TEMP_NAME" ]; then
|
||||||
|
git mv -f "$TEMP_NAME" "$SOURCE"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Trap errors to trigger rollback
|
||||||
|
trap 'rollback' ERR
|
||||||
|
|
||||||
|
# Move the file to a temporary name within the Git repository
|
||||||
|
git mv "$SOURCE" "$TEMP_NAME"
|
||||||
|
|
||||||
|
# Move the temporary file to the desired destination
|
||||||
|
git mv "$TEMP_NAME" "$DESTINATION"
|
||||||
|
|
||||||
|
echo "Successfully renamed '$SOURCE' to '$DESTINATION'."
|
||||||
20
bin/git-rename-remote
Executable file
20
bin/git-rename-remote
Executable file
|
|
@ -0,0 +1,20 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
old=$1
|
||||||
|
new=$2
|
||||||
|
|
||||||
|
test -z "$old" && echo "old remote name required." 1>&2 && exit 1
|
||||||
|
test -z "$new" && echo "new remote name required." 1>&2 && exit 1
|
||||||
|
|
||||||
|
if ! git config --get "remote.$old.fetch" > /dev/null; then
|
||||||
|
echo "remote $old doesn't exist"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if git config --get "remote.$new.fetch" > /dev/null; then
|
||||||
|
git remote remove "$new"
|
||||||
|
fi
|
||||||
|
git remote rename "$old $new"
|
||||||
|
git remote -v
|
||||||
12
bin/git-rename-tag
Executable file
12
bin/git-rename-tag
Executable file
|
|
@ -0,0 +1,12 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
old=$1
|
||||||
|
new=$2
|
||||||
|
|
||||||
|
test -z "$old" && echo "old tag name required." 1>&2 && exit 1
|
||||||
|
test -z "$new" && echo "new tag name required." 1>&2 && exit 1
|
||||||
|
|
||||||
|
git tag "$new" "$old"
|
||||||
|
git tag -d "$old"
|
||||||
|
git push origin "$new"
|
||||||
|
git push origin ":refs/tags/$old"
|
||||||
97
bin/git-repl
97
bin/git-repl
|
|
@ -1,24 +1,99 @@
|
||||||
#!/bin/sh
|
#!/usr/bin/env bash
|
||||||
|
shopt -s extglob
|
||||||
|
|
||||||
|
git version
|
||||||
|
echo "git-extras version ""$(git-extras -v)"
|
||||||
|
echo "Type 'ls' to ls files below current directory; '!command' to execute any command or just 'subcommand' to execute any git subcommand; 'quit', 'exit', 'q', ^D, or ^C to exit the git repl."
|
||||||
|
|
||||||
|
HISTIGNORE=${HISTIGNORE:-+([[:space:]])}
|
||||||
|
HISTCONTROL=${HISTCONTROL:-ignoredups}
|
||||||
|
use_local_history=$(git config --get --default 'false' git-extras.repl.use-local-history)
|
||||||
|
if [[ "$use_local_history" == "true" ]]; then
|
||||||
|
HISTFILE="$(git rev-parse --show-toplevel)/.git_extras_repl_history"
|
||||||
|
else
|
||||||
|
HISTFILE=${XDG_STATE_HOME:-$HOME/.local/state}/git_extras_repl_history
|
||||||
|
fi
|
||||||
|
|
||||||
|
# file doesn't exist, is empty, or contains only whitespace
|
||||||
|
if [[ ! -f "$HISTFILE" ]] || [[ ! -s "$HISTFILE" ]] || ! grep -q '[^[:space:]]' "$HISTFILE"; then
|
||||||
|
# `history -r` .... `history -a` are not happy with an empty initial file in bash 3.2.57, which is what MacOS 26 ships with
|
||||||
|
echo '!echo welcome to git-repl!' >> "$HISTFILE"
|
||||||
|
fi
|
||||||
|
history -r
|
||||||
|
|
||||||
while true; do
|
while true; do
|
||||||
# Readline
|
# Current branch
|
||||||
read -e -r -p "git> " cmd
|
cur=$(git symbolic-ref HEAD 2> /dev/null | cut -d/ -f3-)
|
||||||
|
|
||||||
# EOF
|
# Prompt
|
||||||
test $? -ne 0 && break
|
if test -n "$cur"; then
|
||||||
|
cur_string=" ($cur)"
|
||||||
|
else
|
||||||
|
cur_string=""
|
||||||
|
fi
|
||||||
|
if test -n "$exit_status" && test "$exit_status" -ne 0; then
|
||||||
|
es_string=$' \e[31m['"$exit_status"$']\e[0m'
|
||||||
|
else
|
||||||
|
es_string=""
|
||||||
|
fi
|
||||||
|
prompt_character=$(git config --get --default '>' git-extras.repl.prompt-character)
|
||||||
|
|
||||||
# History
|
prefix=$(git config --get --default 'git' git-extras.repl.prefix)
|
||||||
history -s "$cmd"
|
|
||||||
|
show_project_name=$(git config --get --default 'false' git-extras.repl.show-project-name)
|
||||||
|
if [[ "$show_project_name" == "true" ]]; then
|
||||||
|
project_name=" $(basename "$(git rev-parse --show-toplevel)" .git)"
|
||||||
|
else
|
||||||
|
project_name=""
|
||||||
|
fi
|
||||||
|
|
||||||
|
prompt_base="$prefix$project_name$cur_string$es_string$prompt_character"
|
||||||
|
prompt_base_stripped=$(echo "$prompt_base" | awk '{$1=$1};1')
|
||||||
|
prompt="$prompt_base_stripped "
|
||||||
|
|
||||||
|
# Use arguments as a command if any are provided.
|
||||||
|
if [ $# -ne 0 ]; then
|
||||||
|
cmd=$*
|
||||||
|
set --
|
||||||
|
echo "$prompt" "$cmd" # It is as though you had entered a command.
|
||||||
|
else
|
||||||
|
# Readline
|
||||||
|
read -e -r -p "$prompt" cmd
|
||||||
|
# Check for EOF, and end the program if so (handles ^D).
|
||||||
|
test $? -ne 0 && break
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Add command to history if it is not all whitespace
|
||||||
|
if [[ ! "$cmd" =~ ^[[:space:]]*$ ]]; then
|
||||||
|
history -s "$cmd"
|
||||||
|
fi
|
||||||
|
|
||||||
# Built-in commands
|
# Built-in commands
|
||||||
case $cmd in
|
case $cmd in
|
||||||
quit)
|
ls) cmd=ls-files;;
|
||||||
break
|
"")
|
||||||
|
on_enter_cmd=$(git config --get --default '' git-extras.repl.on-enter-command)
|
||||||
|
if test -n "$on_enter_cmd"; then
|
||||||
|
cmd="$on_enter_cmd"
|
||||||
|
else
|
||||||
|
continue
|
||||||
|
fi
|
||||||
;;
|
;;
|
||||||
|
quit|exit|q) break;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
# Execute
|
history -a
|
||||||
git $cmd
|
|
||||||
|
if [[ $cmd == !* ]]; then
|
||||||
|
# shellcheck disable=SC2086
|
||||||
|
eval ${cmd:1}
|
||||||
|
elif [[ $cmd == git* ]]; then
|
||||||
|
# shellcheck disable=SC2086
|
||||||
|
eval $cmd
|
||||||
|
else
|
||||||
|
eval git "$cmd"
|
||||||
|
fi
|
||||||
|
exit_status=$?
|
||||||
done
|
done
|
||||||
|
|
||||||
echo
|
echo
|
||||||
17
bin/git-reset-file
Executable file
17
bin/git-reset-file
Executable file
|
|
@ -0,0 +1,17 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
file="$1"
|
||||||
|
commit="$2"
|
||||||
|
|
||||||
|
if [[ -f $file ]]; then
|
||||||
|
git rm --cached -q -f -- "$file"
|
||||||
|
if [[ -z $commit ]]; then
|
||||||
|
git checkout HEAD -- "$file"
|
||||||
|
echo "Reset '$1' to HEAD"
|
||||||
|
else
|
||||||
|
git checkout "$commit" -- "$file"
|
||||||
|
echo "Reset '$1' to $commit"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "File '$1' not found in $PWD"
|
||||||
|
fi
|
||||||
30
bin/git-root
Executable file
30
bin/git-root
Executable file
|
|
@ -0,0 +1,30 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
git_root() {
|
||||||
|
git rev-parse --show-toplevel
|
||||||
|
}
|
||||||
|
|
||||||
|
# get the relative path of current path according to root of repo
|
||||||
|
git_root_relative() {
|
||||||
|
rel=$(git rev-parse --show-prefix)
|
||||||
|
if [ -z "$rel" ]; then
|
||||||
|
# git rev-parse --show-prefix will output empty string when we are in the root dir
|
||||||
|
echo "."
|
||||||
|
else
|
||||||
|
echo "$rel"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
if test $# -eq 0; then
|
||||||
|
git_root
|
||||||
|
else
|
||||||
|
case "$1" in
|
||||||
|
-r|--relative)
|
||||||
|
git_root_relative
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
git_root
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
1
bin/git-rscp
Symbolic link
1
bin/git-rscp
Symbolic link
|
|
@ -0,0 +1 @@
|
||||||
|
git-scp
|
||||||
267
bin/git-scp
Executable file
267
bin/git-scp
Executable file
|
|
@ -0,0 +1,267 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
COLOR_RED() { test -t 1 && echo -n "$(tput setaf 1)"; }
|
||||||
|
COLOR_GREEN() { test -t 1 && echo -n "$(tput setaf 2)"; }
|
||||||
|
COLOR_YELLOW(){ test -t 1 && echo -n "$(tput setaf 3)"; }
|
||||||
|
COLOR_BLUE() { test -t 1 && echo -n "$(tput setaf 4)"; }
|
||||||
|
COLOR_RESET() { test -t 1 && echo -n "$(tput sgr 0)"; }
|
||||||
|
|
||||||
|
function _test_git_scp()
|
||||||
|
{
|
||||||
|
command -v rsync > /dev/null || _error requires rsync
|
||||||
|
command -v git > /dev/null || _error requires git
|
||||||
|
command -v ssh > /dev/null || _error requires ssh
|
||||||
|
command -v php > /dev/null || _info optional php
|
||||||
|
command -v dos2unix > /dev/null || _info optional dos2unix
|
||||||
|
}
|
||||||
|
|
||||||
|
function set_remote()
|
||||||
|
{
|
||||||
|
remote=$1
|
||||||
|
if [ "$(git remote | grep -c -- "^$remote$")" -eq 0 ]
|
||||||
|
then
|
||||||
|
COLOR_RED
|
||||||
|
echo "Remote $remote does not exist in your git config"
|
||||||
|
COLOR_RESET
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check file for PHP syntax errors
|
||||||
|
# takes a list of filenames
|
||||||
|
function php_lint()
|
||||||
|
{
|
||||||
|
local error_count=()
|
||||||
|
for i
|
||||||
|
do
|
||||||
|
# check if file exists
|
||||||
|
# check if file ends with ".php"
|
||||||
|
test ! -f "$i" && continue
|
||||||
|
case "$i" in
|
||||||
|
*\.php|*\.phtml)
|
||||||
|
if ! php -l "$i" > /dev/null; then
|
||||||
|
error_count[${#error_count[@]}]="$i"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# syntax check fails, force exit
|
||||||
|
test ${#error_count[@]} -gt 0 &&
|
||||||
|
COLOR_RED &&
|
||||||
|
echo "Error: ${#error_count[@]} PHP syntax error found" &&
|
||||||
|
echo "${error_count[@]}" | tr " " '\n' &&
|
||||||
|
COLOR_RESET &&
|
||||||
|
exit 255
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
function _dos2unix()
|
||||||
|
{
|
||||||
|
command -v dos2unix > /dev/null && dos2unix "$@"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
function _sanitize()
|
||||||
|
{
|
||||||
|
git config --get-all extras.scp.sanitize | while read -r i
|
||||||
|
do
|
||||||
|
case $i in
|
||||||
|
php_lint) php_lint "$@";; # git config --global --add extras.scp.sanitize php_lint
|
||||||
|
dos2unix) _dos2unix "$@";; # git config --global --add extras.scp.sanitize dos2unix
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
return $?
|
||||||
|
}
|
||||||
|
|
||||||
|
function scp_and_stage
|
||||||
|
{
|
||||||
|
local verbose=0 interactive=0 dry_run=0
|
||||||
|
while :
|
||||||
|
do
|
||||||
|
case "$1" in
|
||||||
|
-v|--verbose) verbose=1; shift;;
|
||||||
|
-i|--interactive) verbose=1; interactive=1; shift;;
|
||||||
|
-n|--dry-run) verbose=1; dry_run=1; shift;;
|
||||||
|
*) break;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
set_remote "$1"
|
||||||
|
shift
|
||||||
|
|
||||||
|
local refhead
|
||||||
|
refhead="$(git rev-parse --quiet --verify "$1")"
|
||||||
|
if [ -n "$refhead" ]
|
||||||
|
then
|
||||||
|
shift
|
||||||
|
[ "$(git branch --contains "$refhead" | grep -c '\*')" -eq 0 ] &&
|
||||||
|
_error "refhead provided is not part of current branch"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ $# -ge 1 ]
|
||||||
|
then
|
||||||
|
list=$(git ls-files "$@")" "$(git ls-files -o "$@")
|
||||||
|
elif [ -n "$refhead" ]
|
||||||
|
then
|
||||||
|
[ "$verbose" -eq 1 ] && git --no-pager diff --stat "$refhead"
|
||||||
|
list=$(git diff "$refhead" --name-only)
|
||||||
|
else
|
||||||
|
[ "$verbose" -eq 1 ] && git --no-pager diff
|
||||||
|
list=$(git diff --name-only)
|
||||||
|
fi
|
||||||
|
|
||||||
|
deleted=$(for i in $list; do [ -f "$i" ] || echo "$i"; done)
|
||||||
|
list=$(for i in $list; do [ -f "$i" ] && echo "$i"; done)
|
||||||
|
|
||||||
|
if [ "$interactive" -eq 1 ] && [ "$dry_run" -eq 0 ] && { [ -n "$list" ] || [ -n "$deleted" ]; }
|
||||||
|
then
|
||||||
|
local reply
|
||||||
|
read -r -p "Proceed with sync to $remote? [y/N] " reply
|
||||||
|
case "$reply" in
|
||||||
|
y|Y|yes|YES) ;;
|
||||||
|
*) _info "Aborted, nothing synced."; exit 0;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
|
local status=0
|
||||||
|
|
||||||
|
if [ -n "$list" ]
|
||||||
|
then
|
||||||
|
local _TMP=${0///}
|
||||||
|
# shellcheck disable=SC2086
|
||||||
|
echo "$list" > "$_TMP"
|
||||||
|
if [ "$dry_run" -eq 1 ] || _sanitize $list
|
||||||
|
then
|
||||||
|
_info "Pushing to $remote ($(git config "remote.$remote.url"))"
|
||||||
|
if [ "$dry_run" -eq 1 ]
|
||||||
|
then
|
||||||
|
rsync -rlDvn --files-from="$_TMP" ./ "$(git config "remote.$remote.url")/"
|
||||||
|
else
|
||||||
|
rsync -rlDv --files-from="$_TMP" ./ "$(git config "remote.$remote.url")/" &&
|
||||||
|
git add --force $list
|
||||||
|
fi
|
||||||
|
status=$?
|
||||||
|
else
|
||||||
|
status=1
|
||||||
|
fi
|
||||||
|
rm "$_TMP"
|
||||||
|
fi
|
||||||
|
|
||||||
|
deleted=$(for i in $deleted; do echo "$(git config "remote.$remote.url" | cut -d: -f2)/$i"; done)
|
||||||
|
|
||||||
|
if [ -n "$deleted" ]
|
||||||
|
then
|
||||||
|
COLOR_RED
|
||||||
|
echo Deleted remote files
|
||||||
|
if [ "$dry_run" -eq 1 ]
|
||||||
|
then
|
||||||
|
echo "$deleted"
|
||||||
|
else
|
||||||
|
if ssh "$(git config "remote.$remote.url" | cut -d: -f1)" -t "rm $deleted"
|
||||||
|
then
|
||||||
|
echo "$deleted"
|
||||||
|
else
|
||||||
|
status=1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
COLOR_RESET
|
||||||
|
fi
|
||||||
|
|
||||||
|
return "$status"
|
||||||
|
}
|
||||||
|
|
||||||
|
function reverse_scp()
|
||||||
|
{
|
||||||
|
local verbose=0 interactive=0 dry_run=0
|
||||||
|
while :
|
||||||
|
do
|
||||||
|
case "$1" in
|
||||||
|
-v|--verbose) verbose=1; shift;;
|
||||||
|
-i|--interactive) verbose=1; interactive=1; shift;;
|
||||||
|
-n|--dry-run) verbose=1; dry_run=1; shift;;
|
||||||
|
*) break;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
set_remote "$1"
|
||||||
|
shift
|
||||||
|
|
||||||
|
local _TMP=${0///}
|
||||||
|
echo "$@" > "$_TMP"
|
||||||
|
|
||||||
|
local status=0
|
||||||
|
if [ "$verbose" -eq 1 ]
|
||||||
|
then
|
||||||
|
rsync -rlDvn --files-from="$_TMP" "$(git config "remote.$remote.url")/" ./
|
||||||
|
status=$?
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$dry_run" -eq 1 ]
|
||||||
|
then
|
||||||
|
rm "$_TMP"
|
||||||
|
return "$status"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$interactive" -eq 1 ]
|
||||||
|
then
|
||||||
|
local reply
|
||||||
|
read -r -p "Proceed with copy from $remote? [y/N] " reply
|
||||||
|
case "$reply" in
|
||||||
|
y|Y|yes|YES) ;;
|
||||||
|
*) _info "Aborted, nothing copied."; rm "$_TMP"; exit 0;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
|
rsync -rlDv --files-from="$_TMP" "$(git config "remote.$remote.url")/" ./ &&
|
||||||
|
rm "$_TMP"
|
||||||
|
}
|
||||||
|
|
||||||
|
function _info()
|
||||||
|
{
|
||||||
|
COLOR_YELLOW
|
||||||
|
test $# -gt 0 && echo "$@"
|
||||||
|
COLOR_RESET
|
||||||
|
}
|
||||||
|
|
||||||
|
function _usage()
|
||||||
|
{
|
||||||
|
echo "Usage:
|
||||||
|
git scp -h|help|?
|
||||||
|
git scp [options] <remote> [ref|file..] # scp and stage your files to specified remote
|
||||||
|
git scp [options] <remote> [<ref>] # show diff relative to <ref> and upload unstaged files to <remote>
|
||||||
|
git rscp [options] <remote> [<file|directory>] # copy <remote> files to current working directory
|
||||||
|
|
||||||
|
OPTIONS:
|
||||||
|
-v, --verbose print what will be synced before syncing
|
||||||
|
-i, --interactive prompt for confirmation before syncing (implies --verbose)
|
||||||
|
-n, --dry-run show what would be synced, change nothing (implies --verbose; no staging, no remote writes/deletes)
|
||||||
|
"
|
||||||
|
|
||||||
|
case $1 in
|
||||||
|
-v|verbose|--verbose) grep -A100 '^#* OPTIONS #*$' "$0" ;;
|
||||||
|
esac
|
||||||
|
exit
|
||||||
|
}
|
||||||
|
|
||||||
|
function _error()
|
||||||
|
{
|
||||||
|
[ $# -eq 0 ] && _usage && exit 0
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "ERROR: $*"
|
||||||
|
echo
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
### OPTIONS ###
|
||||||
|
case $(basename "$0") in
|
||||||
|
git-scp)
|
||||||
|
case $1 in
|
||||||
|
''|-h|'?'|help|--help) shift; _test_git_scp; _usage "$@";;
|
||||||
|
*) scp_and_stage "$@";;
|
||||||
|
esac
|
||||||
|
;;
|
||||||
|
git-rscp) reverse_scp "$@";;
|
||||||
|
esac
|
||||||
104
bin/git-sed
Executable file
104
bin/git-sed
Executable file
|
|
@ -0,0 +1,104 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<EOF
|
||||||
|
usage: git sed [ -c ] [ -f <flags> ] <search> <replacement> [ <flags> ]
|
||||||
|
|
||||||
|
Run git grep and then send results to sed for replacement with the
|
||||||
|
given flags, if they are provided via -f or as the third argument.
|
||||||
|
|
||||||
|
Also runs git commit if -c is provided.
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
# don't commit by default
|
||||||
|
do_commit() {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
pathspec=
|
||||||
|
while [ "$1" != "" ]; do
|
||||||
|
case "$1" in
|
||||||
|
-c|--commit)
|
||||||
|
if git status --porcelain | grep .; then
|
||||||
|
echo "you need to commit your changes before running with --commit"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
do_commit() {
|
||||||
|
git commit -m"replace $search with $replacement
|
||||||
|
|
||||||
|
actual command:
|
||||||
|
|
||||||
|
$command" -a
|
||||||
|
}
|
||||||
|
;;
|
||||||
|
-f|--flags)
|
||||||
|
if [ "$2" = "" ]; then
|
||||||
|
usage
|
||||||
|
echo "missing argument for $1"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
shift
|
||||||
|
flags=$1
|
||||||
|
;;
|
||||||
|
-h|--help)
|
||||||
|
usage
|
||||||
|
exit
|
||||||
|
;;
|
||||||
|
--)
|
||||||
|
pathspec="$*"
|
||||||
|
break
|
||||||
|
;;
|
||||||
|
-*)
|
||||||
|
usage
|
||||||
|
echo "unknown flag: $1"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
if [ "$search" = "" ]; then
|
||||||
|
search="$1"
|
||||||
|
elif [ "$replacement" = "" ]; then
|
||||||
|
replacement="$1"
|
||||||
|
elif [ "$flags" = "" ]; then
|
||||||
|
flags="$1"
|
||||||
|
else
|
||||||
|
usage
|
||||||
|
echo "too many arguments: $1"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
shift
|
||||||
|
done
|
||||||
|
|
||||||
|
all="$search$replacement$flags"
|
||||||
|
case "$all" in
|
||||||
|
*/*)
|
||||||
|
ascii="$(for((i=32;i<=127;i++)) do printf '%b' "\\$(printf '%03o' "$i")"; done)"
|
||||||
|
escaped="${all//-/\\-}"
|
||||||
|
escaped="${escaped//[/\\[}"
|
||||||
|
sep="$(printf '%s' "$ascii" | tr -d "$escaped")"
|
||||||
|
sep="$(printf %.1s "$sep")"
|
||||||
|
if [ "$sep" = "" ] ; then
|
||||||
|
echo 'could not find an unused character for sed separator character'
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
sep=/
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
r=$(xargs -r false < /dev/null > /dev/null 2>&1 && echo r)
|
||||||
|
need_bak=$(sed -i s/hello/world/ "$(git_extra_mktemp)" > /dev/null 2>&1 || echo true)
|
||||||
|
|
||||||
|
if [ "$need_bak" ]; then
|
||||||
|
command="git grep -lz '$search' $pathspec | xargs -0$r sed -i '' 's$sep$search$sep$replacement$sep$flags'"
|
||||||
|
# shellcheck disable=SC2086
|
||||||
|
git grep -lz "$search" $pathspec | xargs -0"$r" sed -i '' "s$sep$search$sep$replacement$sep$flags"
|
||||||
|
else
|
||||||
|
command="git grep -lz '$search' $pathspec | xargs -0$r sed -i 's$sep$search$sep$replacement$sep$flags'"
|
||||||
|
# shellcheck disable=SC2086
|
||||||
|
git grep -lz "$search" $pathspec | xargs -0"$r" sed -i "s$sep$search$sep$replacement$sep$flags"
|
||||||
|
fi
|
||||||
|
do_commit
|
||||||
23
bin/git-setup
Executable file
23
bin/git-setup
Executable file
|
|
@ -0,0 +1,23 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
COMMIT_MESSAGE='Initial commit'
|
||||||
|
|
||||||
|
if [ "$1" = "-m" ]; then
|
||||||
|
COMMIT_MESSAGE=$2
|
||||||
|
shift; shift
|
||||||
|
fi
|
||||||
|
|
||||||
|
gitdirexists(){
|
||||||
|
if [ -d ".git" ]; then
|
||||||
|
echo ".git directory already exists, aborting"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
dir=$(test -z "$*" && echo "." || echo "$*")
|
||||||
|
mkdir -p "$dir" \
|
||||||
|
&& cd "$dir" \
|
||||||
|
&& gitdirexists \
|
||||||
|
&& git init \
|
||||||
|
&& git add . \
|
||||||
|
&& git commit --allow-empty -m "$COMMIT_MESSAGE"
|
||||||
3
bin/git-show-merged-branches
Executable file
3
bin/git-show-merged-branches
Executable file
|
|
@ -0,0 +1,3 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
git branch --no-color --merged | grep -v "\*" | grep -v "$(git_extra_default_branch)" | tr -d ' '
|
||||||
3
bin/git-show-tree
Executable file
3
bin/git-show-tree
Executable file
|
|
@ -0,0 +1,3 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
git log --all --graph --decorate --oneline --simplify-by-decoration
|
||||||
3
bin/git-show-unmerged-branches
Executable file
3
bin/git-show-unmerged-branches
Executable file
|
|
@ -0,0 +1,3 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
git branch --no-color --no-merged | grep -v "\*" | grep -v "$(git_extra_default_branch)" | tr -d ' '
|
||||||
80
bin/git-squash
Executable file
80
bin/git-squash
Executable file
|
|
@ -0,0 +1,80 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
SQUASH_MSG=
|
||||||
|
for arg in "$@"; do
|
||||||
|
case "$arg" in
|
||||||
|
--squash-msg)
|
||||||
|
SQUASH_MSG=1
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
# set the argument back
|
||||||
|
set -- "$@" "$arg"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
shift
|
||||||
|
done
|
||||||
|
|
||||||
|
src="$1"
|
||||||
|
msg="$2"
|
||||||
|
if [[ -n "$msg" ]] && [[ -n "$SQUASH_MSG" ]]; then
|
||||||
|
>&2 echo "When commit message is given, --squash-msg is not allowed."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
is_branch() {
|
||||||
|
git show-ref --verify --quiet "refs/heads/$src"
|
||||||
|
}
|
||||||
|
|
||||||
|
is_commit_reference() {
|
||||||
|
git rev-parse --verify --quiet "$src" > /dev/null 2>&1
|
||||||
|
}
|
||||||
|
|
||||||
|
is_on_current_branch() {
|
||||||
|
local commit_sha
|
||||||
|
commit_sha=$(git rev-parse "$src")
|
||||||
|
git rev-list HEAD |
|
||||||
|
grep -q -- "$commit_sha"
|
||||||
|
}
|
||||||
|
|
||||||
|
commit_if_msg_provided() {
|
||||||
|
if test -n "$msg"; then
|
||||||
|
git commit -a -m "$msg"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
prompt_continuation_if_squashing_default_branch() {
|
||||||
|
if [[ $src == $(git_extra_default_branch) ]]; then
|
||||||
|
read -p "Warning: squashing '$src'! Continue [y/N]? " -r
|
||||||
|
if ! [[ $REPLY =~ ^[Yy]$ ]]; then
|
||||||
|
echo "Exiting"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
squash_branch() {
|
||||||
|
prompt_continuation_if_squashing_default_branch
|
||||||
|
if [ -n "$SQUASH_MSG" ]; then
|
||||||
|
base=$(git merge-base "$src" @)
|
||||||
|
msg=$(git log "$base".."$src" --format="%s%n%n%b" --no-merges --reverse)
|
||||||
|
fi
|
||||||
|
git merge --squash "$src" || exit 1 # quits if `git merge` fails
|
||||||
|
commit_if_msg_provided
|
||||||
|
}
|
||||||
|
|
||||||
|
squash_current_branch() {
|
||||||
|
if [ -n "$SQUASH_MSG" ]; then
|
||||||
|
msg=$(git log "$src"..@ --format="%s%n%n%b" --no-merges --reverse)
|
||||||
|
fi
|
||||||
|
git reset --soft "$src" || exit 1 # quits if `git reset` fails
|
||||||
|
commit_if_msg_provided
|
||||||
|
}
|
||||||
|
|
||||||
|
if is_branch; then
|
||||||
|
squash_branch
|
||||||
|
elif is_commit_reference && is_on_current_branch; then
|
||||||
|
squash_current_branch
|
||||||
|
else
|
||||||
|
echo "Source branch or commit reference required." 1>&2 && exit 1
|
||||||
|
fi
|
||||||
91
bin/git-stamp
Executable file
91
bin/git-stamp
Executable file
|
|
@ -0,0 +1,91 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
|
||||||
|
init_variables() {
|
||||||
|
COMMAND=${0#*-}
|
||||||
|
|
||||||
|
REPLACE=false
|
||||||
|
unset ID
|
||||||
|
unset MSG
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat << EOF
|
||||||
|
usage: git ${COMMAND} [<options>] <id> [<messages>]
|
||||||
|
|
||||||
|
Options:
|
||||||
|
-r, --replace replace all previous stamps with same id
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
error() {
|
||||||
|
if [[ -n "$1" ]]; then
|
||||||
|
echo "error: $1" >&2
|
||||||
|
fi
|
||||||
|
usage
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
stamp() {
|
||||||
|
local commit_msg=$( git log -1 --pretty=%B )
|
||||||
|
local stamp_msg
|
||||||
|
[[ -n "${MSG}" ]] && stamp_msg="${ID} ${MSG}" || stamp_msg="${ID}"
|
||||||
|
|
||||||
|
if ${REPLACE}; then
|
||||||
|
# remove previous stamps with same ID from the commit message
|
||||||
|
commit_msg=$(
|
||||||
|
echo "${commit_msg}" \
|
||||||
|
| grep --ignore-case --invert-match "^${ID}\b" \
|
||||||
|
| cat -s
|
||||||
|
)
|
||||||
|
fi
|
||||||
|
|
||||||
|
# append the stamp to the commit message in a new paragraph
|
||||||
|
git commit --amend \
|
||||||
|
--message "${commit_msg}" \
|
||||||
|
--message "${stamp_msg}" \
|
||||||
|
> /dev/null
|
||||||
|
|
||||||
|
# show result
|
||||||
|
git log -1 --pretty=full
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
parse_options() {
|
||||||
|
while [[ "$#" -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
-h)
|
||||||
|
usage
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
--replace|-r)
|
||||||
|
REPLACE=true
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
break
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
ID="$1"
|
||||||
|
MSG="${*:2}"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
validate_options() {
|
||||||
|
# ID should be set to non-empty string
|
||||||
|
if [[ -z "${ID}" ]]; then
|
||||||
|
error "missing stamp identifier"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
init_variables
|
||||||
|
parse_options "$@"
|
||||||
|
validate_options
|
||||||
|
|
||||||
|
stamp
|
||||||
301
bin/git-standup
Executable file
301
bin/git-standup
Executable file
|
|
@ -0,0 +1,301 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# Code modified from https://github.com/kamranahmedse/git-standup,
|
||||||
|
# under the MIT LICENSE.
|
||||||
|
usage() {
|
||||||
|
cat <<EOS
|
||||||
|
Usage:
|
||||||
|
git standup [-a <author name>] [-w <weekstart-weekend>|-d <days-ago>] [-m <max-dir-depth>] [-D date-format] [-L] [-h] [-f] [-B] [-n <number-of-commits] [-F <gpg|authordate>]
|
||||||
|
|
||||||
|
-a - Specify author to restrict search to, default to current git user.
|
||||||
|
Use "-a all" if you don't want the restriction.
|
||||||
|
-w - Specify weekday range to limit search to
|
||||||
|
-m - Specify the depth of recursive directory search
|
||||||
|
-L - Toggle inclusion of symbolic links in recursive directory search
|
||||||
|
-d - Specify the number of days back to include
|
||||||
|
-D - Specify the date format for "git log" (default: relative)
|
||||||
|
-h - Display this help screen
|
||||||
|
-f - Fetch the latest commits beforehand
|
||||||
|
-B - Display the commits in branch groups
|
||||||
|
-n - Limit the number of commits displayed per group
|
||||||
|
-F gpg - Show if commit is GPG signed (G) or not (N)
|
||||||
|
-F authordate - Print author date instead of commit date
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
git standup -a "John Doe" -w "MON-FRI" -m 3
|
||||||
|
EOS
|
||||||
|
}
|
||||||
|
|
||||||
|
warn() {
|
||||||
|
>&2 echo "${BOLD}${RED}WARNING: $1${NORMAL}"
|
||||||
|
}
|
||||||
|
|
||||||
|
git rev-parse --show-toplevel > /dev/null 2>&1
|
||||||
|
in_git_repo=$?
|
||||||
|
|
||||||
|
# Use colors, but only if connected to a terminal, and that terminal
|
||||||
|
# supports them.
|
||||||
|
if command -v tput &>/dev/null; then
|
||||||
|
ncolors=$(tput colors)
|
||||||
|
fi
|
||||||
|
if [[ -t 1 ]] && [[ -n "$ncolors" ]] && [[ "$ncolors" -ge 8 ]] ; then
|
||||||
|
RED="$(tput setaf 1)"
|
||||||
|
GREEN="$(tput setaf 2)"
|
||||||
|
YELLOW="$(tput setaf 3)"
|
||||||
|
BOLD="$(tput bold)"
|
||||||
|
NORMAL="$(tput sgr0)"
|
||||||
|
BOLD=$(tput bold)
|
||||||
|
UNDERLINE=$(tput smul)
|
||||||
|
NORMAL=$(tput sgr0)
|
||||||
|
COLOR=always
|
||||||
|
else
|
||||||
|
RED=""
|
||||||
|
GREEN=""
|
||||||
|
YELLOW=""
|
||||||
|
BOLD=""
|
||||||
|
NORMAL=""
|
||||||
|
BOLD=""
|
||||||
|
UNDERLINE=""
|
||||||
|
NORMAL=""
|
||||||
|
COLOR=never
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Only enable exit-on-error after the non-critical colorization stuff,
|
||||||
|
# which may fail on systems lacking tput or terminfo
|
||||||
|
set -e
|
||||||
|
|
||||||
|
RANGE_SPECIFIED=
|
||||||
|
COMMIT_DATE_FORMAT=%cd
|
||||||
|
USE_GPG_FORMAT=no
|
||||||
|
|
||||||
|
while getopts "hgfF:Bd:a:w:m:D:n:L" opt; do
|
||||||
|
case $opt in
|
||||||
|
h)
|
||||||
|
usage
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
a)
|
||||||
|
if [[ "$OPTARG" = 'all' ]] ; then
|
||||||
|
AUTHOR=".*"
|
||||||
|
else
|
||||||
|
AUTHOR="$OPTARG"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
d)
|
||||||
|
test -n "$RANGE_SPECIFIED" && warn "-d option is conflict with -w"
|
||||||
|
RANGE_SPECIFIED=yes
|
||||||
|
if [ "$OPTARG" -lt 1 ]; then
|
||||||
|
>&2 echo "Specify days less than one is invalid"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
SINCE="$OPTARG days ago"
|
||||||
|
;;
|
||||||
|
w)
|
||||||
|
if [ -n "$RANGE_SPECIFIED" ]; then
|
||||||
|
warn "-w option is conflict with -d"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
RANGE_SPECIFIED=yes
|
||||||
|
|
||||||
|
week_range=${OPTARG}
|
||||||
|
week_start="${week_range%%-*}"
|
||||||
|
week_start="${week_start:="Mon"}"
|
||||||
|
week_end="${week_range##*-}"
|
||||||
|
week_end=${week_end:="Fri"}
|
||||||
|
|
||||||
|
## In case it is the start of week, we need to
|
||||||
|
## show the commits since the last weekend
|
||||||
|
shopt -s nocasematch
|
||||||
|
if [[ "$week_start" == "$(LC_ALL=C date +%a)" ]] ; then
|
||||||
|
SINCE="last $week_end";
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
f)
|
||||||
|
FETCH_LAST_COMMIT=true
|
||||||
|
;;
|
||||||
|
m)
|
||||||
|
MAXDEPTH=$((OPTARG + 1))
|
||||||
|
if [ "$MAXDEPTH" -lt 1 ]; then
|
||||||
|
>&2 echo "Specify depth less than one is invalid"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
L)
|
||||||
|
INCLUDE_LINKS=-L
|
||||||
|
;;
|
||||||
|
D)
|
||||||
|
GIT_DATE_FORMAT=${OPTARG}
|
||||||
|
;;
|
||||||
|
g)
|
||||||
|
warn "-g option is deprecated, use '-F gpg' instead"
|
||||||
|
USE_GPG_FORMAT=yes
|
||||||
|
;;
|
||||||
|
B)
|
||||||
|
GROUP_BY_BRANCHES=true
|
||||||
|
;;
|
||||||
|
n)
|
||||||
|
MAX_COMMIT_NUM=${OPTARG}
|
||||||
|
;;
|
||||||
|
F)
|
||||||
|
case $OPTARG in
|
||||||
|
gpg)
|
||||||
|
USE_GPG_FORMAT=yes
|
||||||
|
;;
|
||||||
|
authordate)
|
||||||
|
COMMIT_DATE_FORMAT=%ad
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
warn "Invalid argument for -F: $OPTARG"
|
||||||
|
usage
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
;;
|
||||||
|
\?)
|
||||||
|
usage
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
shift $((OPTIND-1))
|
||||||
|
|
||||||
|
if [[ $# -gt 0 ]]; then
|
||||||
|
warn "please upgrade to new-style interface. Run 'git help standup' to get more info."
|
||||||
|
if [[ $# -gt 3 ]] ; then
|
||||||
|
usage
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
AUTHOR=$1
|
||||||
|
SINCE=$2
|
||||||
|
UNTIL=$3
|
||||||
|
fi
|
||||||
|
|
||||||
|
AUTHOR=${AUTHOR:="$(git config user.name || echo '')"}
|
||||||
|
if [ -z "${AUTHOR}" ]; then
|
||||||
|
warn "please configure an author with 'git config user.name' or specify an author via '-a'"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
FETCH_LAST_COMMIT=${FETCH_LAST_COMMIT:=false}
|
||||||
|
MAXDEPTH=${MAXDEPTH:=2}
|
||||||
|
GIT_PRETTY_FORMAT="%Cred%h%Creset - %s %Cgreen(${COMMIT_DATE_FORMAT}) %C(bold blue)<%an>%Creset"
|
||||||
|
GIT_DATE_FORMAT=${GIT_DATE_FORMAT:=relative}
|
||||||
|
if [[ "$USE_GPG_FORMAT" == 'yes' ]]; then
|
||||||
|
GIT_PRETTY_FORMAT="$GIT_PRETTY_FORMAT %C(yellow)gpg: %G?%Creset"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Handle config of implicit week
|
||||||
|
IMPLICIT_WEEK=$(git config --get git-extras.standup.implicit-week || echo '')
|
||||||
|
if [[ -z "$RANGE_SPECIFIED" ]] && [[ -n "${IMPLICIT_WEEK}" ]]; then
|
||||||
|
week_start=${IMPLICIT_WEEK%%-*}
|
||||||
|
week_end=${IMPLICIT_WEEK##*-}
|
||||||
|
shopt -s nocasematch
|
||||||
|
if [[ "$week_start" == "$(LC_ALL=C date +%a)" ]]; then
|
||||||
|
SINCE="last $week_end"
|
||||||
|
fi
|
||||||
|
UNTIL=today
|
||||||
|
else
|
||||||
|
SINCE=${SINCE:=yesterday}
|
||||||
|
UNTIL=${UNTIL:=today}
|
||||||
|
fi
|
||||||
|
|
||||||
|
GIT_LOG_COMMAND="git --no-pager log \
|
||||||
|
--no-merges
|
||||||
|
--since \"$SINCE\"
|
||||||
|
--until \"$UNTIL\"
|
||||||
|
--author=\"$AUTHOR\"
|
||||||
|
--abbrev-commit
|
||||||
|
--oneline
|
||||||
|
--color=$COLOR
|
||||||
|
--pretty=format:'$GIT_PRETTY_FORMAT'
|
||||||
|
--date='$GIT_DATE_FORMAT'"
|
||||||
|
if [[ -n "$MAX_COMMIT_NUM" ]]; then
|
||||||
|
GIT_LOG_COMMAND="$GIT_LOG_COMMAND --max-count=$MAX_COMMIT_NUM"
|
||||||
|
fi
|
||||||
|
|
||||||
|
git_output() {
|
||||||
|
if [ "$GROUP_BY_BRANCHES" = true ]; then
|
||||||
|
local branches
|
||||||
|
branches=$(git branch --sort=-committerdate | awk '{print substr($0, 3)}')
|
||||||
|
for branch in $branches; do
|
||||||
|
# shellcheck disable=SC2086
|
||||||
|
if output=$(eval $GIT_LOG_COMMAND "$branch"); then
|
||||||
|
if [[ -n "$output" ]] ; then
|
||||||
|
echo "${GREEN}${branch}${NORMAL}"
|
||||||
|
echo "$output"
|
||||||
|
echo ""
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
# TODO optimize:return if the latest commit of a branch is earlier than the 'since' day
|
||||||
|
done
|
||||||
|
else
|
||||||
|
# shellcheck disable=SC2086
|
||||||
|
eval $GIT_LOG_COMMAND --all
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
## For when the command has been run in a non-repo directory
|
||||||
|
if [[ $in_git_repo != 0 ]]; then
|
||||||
|
## Set delimiter to newline for the loop
|
||||||
|
IFS=$'\n'
|
||||||
|
## Recursively search for git repositories
|
||||||
|
# shellcheck disable=SC2086
|
||||||
|
PROJECT_DIRS=$(find $INCLUDE_LINKS . -maxdepth "$MAXDEPTH" -mindepth 0 -name .git)
|
||||||
|
|
||||||
|
# Fetch the latest commits, if required
|
||||||
|
if [ "$FETCH_LAST_COMMIT" = true ]; then
|
||||||
|
|
||||||
|
echo "${BOLD}${GREEN}Fetching commits ..${NORMAL}"
|
||||||
|
|
||||||
|
# Foreach of the project directories, fetch the commits
|
||||||
|
for DIR in $PROJECT_DIRS; do
|
||||||
|
DIR="$(dirname "$DIR")"
|
||||||
|
pushd "$DIR" > /dev/null
|
||||||
|
|
||||||
|
if [[ -d ".git" ]] ; then
|
||||||
|
echo " ${YELLOW}$(basename "$DIR")${NORMAL}"
|
||||||
|
git fetch --all > /dev/null 2>&1
|
||||||
|
fi
|
||||||
|
|
||||||
|
popd > /dev/null
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Get the standup details for each of the projects
|
||||||
|
for DIR in $PROJECT_DIRS; do
|
||||||
|
DIR="$(dirname "$DIR")"
|
||||||
|
pushd "$DIR" > /dev/null
|
||||||
|
## Show the detail only if it is a git repository
|
||||||
|
if [[ -d ".git" || -f ".git" ]] ; then
|
||||||
|
if GITOUT=$(git_output); then
|
||||||
|
## Only output if there is some activities
|
||||||
|
if [[ -n "$GITOUT" ]] ; then
|
||||||
|
echo "${BOLD}${UNDERLINE}${YELLOW}$(basename "$DIR")${NORMAL}"
|
||||||
|
echo "$GITOUT"
|
||||||
|
echo ""
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "Repository under $DIR could not be queried." >&2
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
popd > /dev/null
|
||||||
|
done
|
||||||
|
else
|
||||||
|
if [ "$FETCH_LAST_COMMIT" = true ]; then
|
||||||
|
echo "${GREEN}Fetching commits ..${NORMAL}"
|
||||||
|
git fetch --all > /dev/null 2>&1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if GITOUT=$(git_output); then
|
||||||
|
if [[ -n "$GITOUT" ]] ; then
|
||||||
|
echo "$GITOUT"
|
||||||
|
else
|
||||||
|
if [[ $AUTHOR = '.*' ]] ; then
|
||||||
|
AUTHOR="all the contributors"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "${YELLOW}Seems like $AUTHOR did nothing!${NORMAL}"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
268
bin/git-summary
268
bin/git-summary
|
|
@ -1,13 +1,259 @@
|
||||||
#!/bin/sh
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
project=${PWD##*/}
|
|
||||||
commit_count=`git log --oneline | wc -l | tr -d ' '`
|
|
||||||
file_count=`git ls-files | wc -l | tr -d ' '`
|
|
||||||
authors=`git shortlog -n -s`
|
|
||||||
|
|
||||||
echo
|
cd "$(git root)" || { echo "Can't cd to top level directory";exit 1; }
|
||||||
echo " project: $project"
|
|
||||||
echo " commits: $commit_count"
|
PROJECT_FULL_PATH=
|
||||||
echo " files : $file_count"
|
SUMMARY_BY_LINE=
|
||||||
echo " authors: \n$authors"
|
DEDUP_BY_EMAIL=
|
||||||
echo
|
MERGES_ARG=
|
||||||
|
OUTPUT_STYLE=
|
||||||
|
for arg in "$@"; do
|
||||||
|
case "$arg" in
|
||||||
|
--full-path)
|
||||||
|
PROJECT_FULL_PATH=1
|
||||||
|
;;
|
||||||
|
--line)
|
||||||
|
SUMMARY_BY_LINE=1
|
||||||
|
;;
|
||||||
|
--dedup-by-email)
|
||||||
|
DEDUP_BY_EMAIL=1
|
||||||
|
;;
|
||||||
|
--no-merges)
|
||||||
|
MERGES_ARG="--no-merges"
|
||||||
|
;;
|
||||||
|
--output-style)
|
||||||
|
OUTPUT_STYLE="$2"
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
-*)
|
||||||
|
>&2 echo "unknown argument $arg found"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
# set the argument back
|
||||||
|
set -- "$@" "$arg"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
shift
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ -n "$DEDUP_BY_EMAIL" ] && [ -n "$SUMMARY_BY_LINE" ]; then
|
||||||
|
>&2 echo "--dedup-by-email used with --line is not supported"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -n "$MERGES_ARG" ] && [ -n "$SUMMARY_BY_LINE" ]; then
|
||||||
|
>&2 echo "--no-merges used with --line is not supported"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
commit="HEAD"
|
||||||
|
if [ -n "$SUMMARY_BY_LINE" ]; then
|
||||||
|
paths=( "$@" )
|
||||||
|
else
|
||||||
|
[ $# -ne 0 ] && commit=$*
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -n "$PROJECT_FULL_PATH" ]]; then
|
||||||
|
project=${PWD/${HOME}/\~}
|
||||||
|
else
|
||||||
|
project=${PWD##*/}
|
||||||
|
fi
|
||||||
|
|
||||||
|
#
|
||||||
|
# get date for the given <commit>
|
||||||
|
#
|
||||||
|
commit_date() {
|
||||||
|
# the $1 can be empty
|
||||||
|
# shellcheck disable=SC2086
|
||||||
|
git log $MERGES_ARG --pretty='format: %ai' "$1" | cut -d ' ' -f 2
|
||||||
|
}
|
||||||
|
|
||||||
|
#
|
||||||
|
# get active days for the given <commit>
|
||||||
|
#
|
||||||
|
active_days() {
|
||||||
|
commit_date "$1" | sort -r | uniq | awk '
|
||||||
|
{ sum += 1 }
|
||||||
|
END { print sum }
|
||||||
|
'
|
||||||
|
}
|
||||||
|
|
||||||
|
#
|
||||||
|
# get the commit total
|
||||||
|
#
|
||||||
|
commit_count() {
|
||||||
|
# shellcheck disable=SC2086
|
||||||
|
git rev-list $MERGES_ARG --count "$commit"
|
||||||
|
}
|
||||||
|
|
||||||
|
#
|
||||||
|
# total file count
|
||||||
|
#
|
||||||
|
file_count() {
|
||||||
|
git ls-files | wc -l | tr -d ' '
|
||||||
|
}
|
||||||
|
|
||||||
|
#
|
||||||
|
# remove duplicate authors who belong to the same email address
|
||||||
|
#
|
||||||
|
dedup_by_email() {
|
||||||
|
# in:
|
||||||
|
# 27 luo zexuan <LuoZexuan@xxx.com>
|
||||||
|
# 7 罗泽轩 <luozexuan@xxx.com>
|
||||||
|
# out:
|
||||||
|
# 34 luo zexuan
|
||||||
|
LC_ALL=C awk '
|
||||||
|
{
|
||||||
|
sum += $1
|
||||||
|
last_field = tolower($NF)
|
||||||
|
if (last_field in emails) {
|
||||||
|
emails[last_field] += $1
|
||||||
|
} else {
|
||||||
|
email = last_field
|
||||||
|
emails[email] = $1
|
||||||
|
# set commits/email to empty
|
||||||
|
$1=$NF=""
|
||||||
|
sub(/^[[:space:]]+/, "", $0)
|
||||||
|
sub(/[[:space:]]+$/, "", $0)
|
||||||
|
name = $0
|
||||||
|
if (name in names) {
|
||||||
|
# when the same name is associated with existed email,
|
||||||
|
# merge the previous email into the later one.
|
||||||
|
emails[email] += emails[names[name]]
|
||||||
|
emails[names[name]] = 0
|
||||||
|
}
|
||||||
|
names[name] = email
|
||||||
|
}
|
||||||
|
}
|
||||||
|
END {
|
||||||
|
for (name in names) {
|
||||||
|
email = names[name]
|
||||||
|
printf "%6d\t%s\n", emails[email], name
|
||||||
|
}
|
||||||
|
}' | sort -rn -k 1
|
||||||
|
}
|
||||||
|
|
||||||
|
#
|
||||||
|
# list authors
|
||||||
|
#
|
||||||
|
format_authors() {
|
||||||
|
# a rare unicode character is used as separator to avoid conflicting with
|
||||||
|
# author name. However, Linux column utility will escape tab if separator
|
||||||
|
# specified, so we do unesaping after it.
|
||||||
|
LC_ALL=C awk '
|
||||||
|
{ args[NR] = $0; sum += $0 }
|
||||||
|
END {
|
||||||
|
for (i = 1; i <= NR; ++i) {
|
||||||
|
printf "%s♪%2.1f%%\n", args[i], 100 * args[i] / sum
|
||||||
|
}
|
||||||
|
}
|
||||||
|
' | column -t -s♪ | sed "s/\\\x09/\t/g"
|
||||||
|
}
|
||||||
|
|
||||||
|
#
|
||||||
|
# fetch repository age from oldest commit
|
||||||
|
#
|
||||||
|
repository_age() {
|
||||||
|
git log --reverse --pretty=oneline --format="%ar" | head -n 1 | LC_ALL=C sed 's/ago//'
|
||||||
|
}
|
||||||
|
|
||||||
|
#
|
||||||
|
# fetch repository age of the latest commit
|
||||||
|
#
|
||||||
|
last_active() {
|
||||||
|
git log --pretty=oneline --format="%ar" -n 1
|
||||||
|
}
|
||||||
|
|
||||||
|
#
|
||||||
|
# list the last modified author for each line
|
||||||
|
#
|
||||||
|
single_file() {
|
||||||
|
while read -r data
|
||||||
|
do
|
||||||
|
if [[ $(file "$data") = *text* ]]; then
|
||||||
|
git blame --line-porcelain "$data" 2>/dev/null | grep "^author " | LC_ALL=C sed -n 's/^author //p';
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
current_branch_name() {
|
||||||
|
git rev-parse --abbrev-ref HEAD
|
||||||
|
}
|
||||||
|
|
||||||
|
#
|
||||||
|
# list the author for all file
|
||||||
|
#
|
||||||
|
lines() {
|
||||||
|
git ls-files -- "$@" | single_file
|
||||||
|
}
|
||||||
|
|
||||||
|
#
|
||||||
|
# get the number of the lines
|
||||||
|
#
|
||||||
|
line_count() {
|
||||||
|
lines "$@" | wc -l
|
||||||
|
}
|
||||||
|
|
||||||
|
uncommitted_changes_count() {
|
||||||
|
git status --porcelain | wc -l
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
COLUMN_CMD_DELIMTER="¬" # Hopefully, this symbol is not used in branch names... I use it as a separator for columns
|
||||||
|
SP="$COLUMN_CMD_DELIMTER|"
|
||||||
|
|
||||||
|
print_summary_by_line() {
|
||||||
|
if [ "$OUTPUT_STYLE" = "tabular" ]; then
|
||||||
|
tabular_headers="# Repo $SP Lines"
|
||||||
|
echo -e "$tabular_headers\n$project $SP $(line_count "${paths[@]}")" | column -t -s "$COLUMN_CMD_DELIMTER"
|
||||||
|
elif [ "$OUTPUT_STYLE" = "oneline" ]; then
|
||||||
|
echo "$project / lines: $(line_count "${paths[@]}")"
|
||||||
|
elif [ -n "$SUMMARY_BY_LINE" ]; then
|
||||||
|
echo
|
||||||
|
echo " project : $project"
|
||||||
|
echo " lines : $(line_count "${paths[@]}")"
|
||||||
|
echo " authors :"
|
||||||
|
lines "${paths[@]}" | sort | uniq -c | sort -rn | format_authors
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
print_summary() {
|
||||||
|
if [ "$OUTPUT_STYLE" = "tabular" ]; then
|
||||||
|
tabular_headers="# Repo $SP Age $SP Last active $SP Active on $SP Commits $SP Uncommitted $SP Branch"
|
||||||
|
echo -e "$tabular_headers\n$project $SP $(repository_age) $SP $(last_active) $SP $(active_days "$commit") days $SP $(commit_count "$commit") $SP $(uncommitted_changes_count) $SP $(current_branch_name)" | column -t -s "$COLUMN_CMD_DELIMTER"
|
||||||
|
elif [ "$OUTPUT_STYLE" = "oneline" ]; then
|
||||||
|
echo "$project / age: $(repository_age) / last active: $(last_active) / active on $(active_days "$commit") days / commits: $(commit_count "$commit") / uncommitted: $(uncommitted_changes_count) / branch: $(current_branch_name)"
|
||||||
|
else
|
||||||
|
echo
|
||||||
|
echo " project : $project"
|
||||||
|
echo " repo age : $(repository_age)"
|
||||||
|
echo " branch: : $(current_branch_name)"
|
||||||
|
echo " last active : $(last_active)"
|
||||||
|
echo " active on : $(active_days "$commit") days"
|
||||||
|
echo " commits : $(commit_count "$commit")"
|
||||||
|
|
||||||
|
# The file count doesn't support passing a git ref so ignore it if a ref is given
|
||||||
|
if [ "$commit" = "HEAD" ]; then
|
||||||
|
echo " files : $(file_count)"
|
||||||
|
fi
|
||||||
|
echo " uncommitted : $(uncommitted_changes_count)"
|
||||||
|
echo " authors : "
|
||||||
|
if [ -n "$DEDUP_BY_EMAIL" ]; then
|
||||||
|
# the $commit can be empty
|
||||||
|
# shellcheck disable=SC2086
|
||||||
|
git shortlog $MERGES_ARG -n -s -e "$commit" | dedup_by_email | format_authors
|
||||||
|
else
|
||||||
|
# shellcheck disable=SC2086
|
||||||
|
git shortlog $MERGES_ARG -n -s "$commit" | format_authors
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
if [ -n "$SUMMARY_BY_LINE" ]; then
|
||||||
|
print_summary_by_line
|
||||||
|
else
|
||||||
|
print_summary
|
||||||
|
fi
|
||||||
|
|
|
||||||
101
bin/git-sync
Executable file
101
bin/git-sync
Executable file
|
|
@ -0,0 +1,101 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
function _usage()
|
||||||
|
{
|
||||||
|
local command="git sync"
|
||||||
|
cat << EOS
|
||||||
|
Usage:
|
||||||
|
${command} [<remote> <branch>]
|
||||||
|
${command} -h | --help
|
||||||
|
${command} -s | --soft
|
||||||
|
${command} -f | --force
|
||||||
|
|
||||||
|
Sync local branch with <remote>/<branch>.
|
||||||
|
When <remote> and <branch> are not specified on the command line, upstream of local branch will be used by default.
|
||||||
|
All changes and untracked files and directories will be removed unless you add -s(--soft).
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
Sync with upstream of local branch:
|
||||||
|
${command}
|
||||||
|
|
||||||
|
Sync with origin/master:
|
||||||
|
${command} origin master
|
||||||
|
|
||||||
|
Sync without cleaning untracked files:
|
||||||
|
${command} -s
|
||||||
|
|
||||||
|
Sync without interaction:
|
||||||
|
${command} -f
|
||||||
|
EOS
|
||||||
|
}
|
||||||
|
|
||||||
|
function main()
|
||||||
|
{
|
||||||
|
while [ "$1" != "" ]; do
|
||||||
|
case $1 in
|
||||||
|
-h | --help)
|
||||||
|
_usage
|
||||||
|
exit
|
||||||
|
;;
|
||||||
|
-s | --soft)
|
||||||
|
local soft="true"
|
||||||
|
;;
|
||||||
|
-f | --force)
|
||||||
|
local force="YES"
|
||||||
|
;;
|
||||||
|
* )
|
||||||
|
if [ "${remote}" = "" ]; then
|
||||||
|
local remote="$1"
|
||||||
|
elif [ "${branch}" = "" ]; then
|
||||||
|
local branch="$1"
|
||||||
|
else
|
||||||
|
echo -e "Error: too many arguments.\n"
|
||||||
|
_usage
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
shift
|
||||||
|
done
|
||||||
|
|
||||||
|
local remote_branch
|
||||||
|
if [ -z "${remote}" ]; then
|
||||||
|
if ! remote_branch="$(git rev-parse --abbrev-ref --symbolic-full-name '@{u}' 2>/dev/null)"; then
|
||||||
|
echo "There is no upstream information of local branch."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
local branch="$(git rev-parse --abbrev-ref --symbolic-full-name @)"
|
||||||
|
local remote=$(git config "branch.${branch}.remote")
|
||||||
|
elif [ -z "${branch}" ]; then
|
||||||
|
echo -e "Error: too few arguments.\n"
|
||||||
|
_usage
|
||||||
|
exit 1
|
||||||
|
else
|
||||||
|
remote_branch="${remote}/${branch}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "${force}" != "YES" ]; then
|
||||||
|
if [ "${soft}" = "true" ]; then
|
||||||
|
echo -n "Are you sure you want to sync with '${remote_branch}'? [y/N]: "
|
||||||
|
else
|
||||||
|
echo -n "Are you sure you want to clean all changes & sync with '${remote_branch}'? [y/N]: "
|
||||||
|
fi
|
||||||
|
local force
|
||||||
|
read -r force
|
||||||
|
fi
|
||||||
|
case "${force}" in
|
||||||
|
"Y" | "y" | "yes" | "Yes" | "YES" )
|
||||||
|
if [ "${soft}" = "true" ]; then
|
||||||
|
git fetch "${remote}" "${branch}" && git reset --hard "${remote_branch}"
|
||||||
|
else
|
||||||
|
git fetch "${remote}" "${branch}" && git reset --hard "${remote_branch}" && git clean -d -f -x
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
* )
|
||||||
|
echo "Canceled."
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
main "$@"
|
||||||
|
|
||||||
13
bin/git-touch
Executable file
13
bin/git-touch
Executable file
|
|
@ -0,0 +1,13 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
USAGE(){
|
||||||
|
echo "git touch <filename> [ <filename> . . .]" 1>&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
test $# -lt 1 && USAGE
|
||||||
|
|
||||||
|
for filename in "$@"; do
|
||||||
|
touch "$filename" \
|
||||||
|
&& git add "$filename"
|
||||||
|
done
|
||||||
72
bin/git-undo
Executable file
72
bin/git-undo
Executable file
|
|
@ -0,0 +1,72 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
cstr="commits"
|
||||||
|
ccnt=$(git rev-list --count HEAD)
|
||||||
|
if [ "$ccnt" -eq 1 ]; then cstr="commit"; fi
|
||||||
|
parm3=""
|
||||||
|
|
||||||
|
function _undo()
|
||||||
|
{
|
||||||
|
type=${1:-soft}
|
||||||
|
undo_cnt=${2:-1}
|
||||||
|
reset=${3:-""}
|
||||||
|
|
||||||
|
if [ "$undo_cnt" -gt "$ccnt" ]; then
|
||||||
|
echo "Only $ccnt $cstr, cannot undo $undo_cnt"
|
||||||
|
elif [ "$type" = "hard" ] && [ "$ccnt" -eq "$undo_cnt" ]; then
|
||||||
|
echo "Cannot hard undo all commits"
|
||||||
|
elif [ "$type" = "soft" ] && [ "$ccnt" -eq 1 ]; then
|
||||||
|
git update-ref -d HEAD
|
||||||
|
else
|
||||||
|
git reset "--$type" "HEAD~$undo_cnt"
|
||||||
|
fi
|
||||||
|
if [ "$reset" != "" ]; then git reset; fi
|
||||||
|
}
|
||||||
|
|
||||||
|
case "$1" in
|
||||||
|
-h)
|
||||||
|
cat << EOL
|
||||||
|
This will erase any changes since your last commit.
|
||||||
|
If you want to get help info, run "git undo --help" instead.
|
||||||
|
Do you want to continue? [yN]"
|
||||||
|
EOL
|
||||||
|
read -r res
|
||||||
|
case "${res}" in
|
||||||
|
"Y" | "y")
|
||||||
|
parm1=hard
|
||||||
|
parm2=${2:-1}
|
||||||
|
;;
|
||||||
|
* )
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
;;
|
||||||
|
--hard)
|
||||||
|
parm1=hard
|
||||||
|
parm2=${2:-1}
|
||||||
|
;;
|
||||||
|
-s|--soft)
|
||||||
|
parm1=soft
|
||||||
|
parm2=${2:-1}
|
||||||
|
parm3=reset
|
||||||
|
;;
|
||||||
|
"")
|
||||||
|
parm1=soft
|
||||||
|
parm2=1
|
||||||
|
;;
|
||||||
|
*[!0-9]*)
|
||||||
|
echo "Invalid parameter: $1"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
parm1=soft
|
||||||
|
parm2="$1"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
if [[ ! $parm2 =~ ^[1-9][0-9]*$ ]]; then
|
||||||
|
echo "Invalid undo count: $parm2"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
_undo "$parm1" "$parm2" "$parm3"
|
||||||
5
bin/git-unlock
Executable file
5
bin/git-unlock
Executable file
|
|
@ -0,0 +1,5 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
filename="$1"
|
||||||
|
test -z "$filename" && echo "filename required." 1>&2 && exit 1
|
||||||
|
git update-index --no-skip-worktree "$filename"
|
||||||
14
bin/git-unwip
Executable file
14
bin/git-unwip
Executable file
|
|
@ -0,0 +1,14 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# Based on scripts from git-utils:
|
||||||
|
# * https://github.com/ddollar/git-utils/blob/master/git-unwip
|
||||||
|
|
||||||
|
# Check if the last commit is a 'WIP' commit
|
||||||
|
LAST_COMMIT=`git log -1 --pretty=%B | tr -d '[:space:]'`
|
||||||
|
|
||||||
|
if [ 'WIP' != $LAST_COMMIT ]; then
|
||||||
|
echo 'Last commit is not a WIP commit, so it will not be unWIP-ed.'
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
git undo --soft
|
||||||
131
bin/git-utimes
Executable file
131
bin/git-utimes
Executable file
|
|
@ -0,0 +1,131 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
#
|
||||||
|
# Change files modification time to their last commit date
|
||||||
|
#
|
||||||
|
|
||||||
|
# Bash unofficial strict mode
|
||||||
|
set -euo pipefail
|
||||||
|
IFS=$'\n\t'
|
||||||
|
|
||||||
|
if [[ "${1:-}" == "--newer" ]]; then
|
||||||
|
op=le
|
||||||
|
shift
|
||||||
|
else
|
||||||
|
op=eq
|
||||||
|
fi
|
||||||
|
|
||||||
|
# BSD systems
|
||||||
|
if date -j &>/dev/null; then
|
||||||
|
stat_flags="-f %m"
|
||||||
|
date_flags="-r"
|
||||||
|
else
|
||||||
|
# Non-BSD systems
|
||||||
|
stat_flags="-c %Y"
|
||||||
|
date_flags="-d@"
|
||||||
|
fi
|
||||||
|
|
||||||
|
bash_opts=()
|
||||||
|
if bash --help 2>&1 | grep -q -- '--noprofile'; then
|
||||||
|
bash_opts+=(--noprofile)
|
||||||
|
fi
|
||||||
|
if bash --help 2>&1 | grep -q -- '--norc'; then
|
||||||
|
bash_opts+=(--norc)
|
||||||
|
fi
|
||||||
|
|
||||||
|
status_opts=(--porcelain --short)
|
||||||
|
# %ct: committer date, UNIX timestamp / %at: author date, UNIX timestamp
|
||||||
|
log_opts=(--format='%ct')
|
||||||
|
if git status --help 2>&1 | grep -q -- "--no-renames"; then
|
||||||
|
status_opts+=(--no-renames)
|
||||||
|
log_opts+=(--no-renames)
|
||||||
|
fi
|
||||||
|
if git status --help 2>&1 | grep -q -- "--untracked-files"; then
|
||||||
|
status_opts+=(--untracked-files=no)
|
||||||
|
fi
|
||||||
|
if git status --help 2>&1 | grep -q -- "--ignored"; then
|
||||||
|
status_opts+=(--ignored=no)
|
||||||
|
fi
|
||||||
|
|
||||||
|
prefix="$(git rev-parse --show-prefix) "
|
||||||
|
strip="${#prefix}"
|
||||||
|
|
||||||
|
tmpfile=$(mktemp)
|
||||||
|
# shellcheck disable=SC2064
|
||||||
|
trap "rm -f '${tmpfile}'" 0
|
||||||
|
|
||||||
|
awk_flags=(
|
||||||
|
-F'\t'
|
||||||
|
-v date_flags="${date_flags}"
|
||||||
|
-v op="${op}"
|
||||||
|
-v stat_flags="${stat_flags}"
|
||||||
|
-v strip="${strip}"
|
||||||
|
-v tmpfile="${tmpfile}"
|
||||||
|
)
|
||||||
|
|
||||||
|
# sanity check, not required:
|
||||||
|
if awk --help 2>&1 | grep -q -- '--posix'; then
|
||||||
|
awk_flags+=(--posix)
|
||||||
|
fi
|
||||||
|
|
||||||
|
read -r -d '' awk_script <<"EOF" || true
|
||||||
|
BEGIN {
|
||||||
|
seen[""]=1
|
||||||
|
print "#!/usr/bin/env bash"
|
||||||
|
print "set +e"
|
||||||
|
print "t() {"
|
||||||
|
print " test -e \"$2\" || return 0"
|
||||||
|
printf(" test \"$(stat %s \"$2\" 2>/dev/null)\" -%s \"$1\" && return 0\n", stat_flags, op)
|
||||||
|
if (date_flags == "-d@") {
|
||||||
|
print " echo \"+ touch -h -d@$1 $2\""
|
||||||
|
print " touch -h -d@$1 \"$2\""
|
||||||
|
} else {
|
||||||
|
print " t=$(date -r$1 \"+%Y%m%d%H%M.%S\")"
|
||||||
|
print " echo \"+ touch -h -t $t $2\""
|
||||||
|
print " touch -h -t $t \"$2\""
|
||||||
|
}
|
||||||
|
print "}"
|
||||||
|
}
|
||||||
|
FILENAME==tmpfile {
|
||||||
|
skip[$1]=1
|
||||||
|
next
|
||||||
|
}
|
||||||
|
# skip blank lines
|
||||||
|
!/^$/ {
|
||||||
|
# skip deletes
|
||||||
|
if (substr($1, length($1), 1) ~ /D/) {
|
||||||
|
next
|
||||||
|
}
|
||||||
|
if (NF == 1) {
|
||||||
|
ct=$1
|
||||||
|
next
|
||||||
|
}
|
||||||
|
$2 = substr($2, strip, length($2)- strip + 1)
|
||||||
|
if ($2 in seen) {
|
||||||
|
next
|
||||||
|
}
|
||||||
|
if ($2 in skip) {
|
||||||
|
next
|
||||||
|
}
|
||||||
|
seen[$2]=1
|
||||||
|
# remove enclosing double quotes that git adds:
|
||||||
|
if (substr($2, 1, 1) == "\"" && substr($2, length($2), 1) == "\"") {
|
||||||
|
$2 = substr($2, 2, length($2) - 2)
|
||||||
|
# unescape remaining double quotes
|
||||||
|
gsub(/\\"/, "\"", $2)
|
||||||
|
# unescape escaped backslashes
|
||||||
|
gsub(/\\\\/, "\\", $2)
|
||||||
|
}
|
||||||
|
# escape apostrophes: ' => '\''
|
||||||
|
gsub(/'/, "'\\''", $2)
|
||||||
|
printf("t %s '%s'\n", ct, $2)
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# prefix is stripped:
|
||||||
|
git --no-pager status "${status_opts[@]}" . \
|
||||||
|
| cut -c 4- >"${tmpfile}"
|
||||||
|
|
||||||
|
# prefix is not stripped:
|
||||||
|
git --no-pager log --raw --no-merges "${log_opts[@]}" . \
|
||||||
|
| awk "${awk_flags[@]}" "${awk_script}" "${tmpfile}" - \
|
||||||
|
| BASH_ENV='' bash "${bash_opts[@]}" -
|
||||||
8
bin/git-wip
Executable file
8
bin/git-wip
Executable file
|
|
@ -0,0 +1,8 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# Based on scripts from git-utils:
|
||||||
|
# * https://github.com/ddollar/git-utils/blob/master/git-wip
|
||||||
|
# * https://github.com/ddollar/git-utils/blob/master/git-addremove
|
||||||
|
|
||||||
|
git add --all
|
||||||
|
git commit --all --message="WIP"
|
||||||
24
brew-release.patch
Normal file
24
brew-release.patch
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
diff --git a/bin/git-extras b/bin/git-extras
|
||||||
|
index e49cd24..4ae28b5 100755
|
||||||
|
--- a/bin/git-extras
|
||||||
|
+++ b/bin/git-extras
|
||||||
|
@@ -4,13 +4,12 @@ VERSION="4.3.0"
|
||||||
|
INSTALL_SCRIPT="https://raw.githubusercontent.com/tj/git-extras/main/install.sh"
|
||||||
|
|
||||||
|
update() {
|
||||||
|
- local bin="$(command -v git-extras)"
|
||||||
|
- local prefix=${bin%/*/*}
|
||||||
|
- local orig=$PWD
|
||||||
|
-
|
||||||
|
- curl -s $INSTALL_SCRIPT | PREFIX="$prefix" bash /dev/stdin \
|
||||||
|
- && cd "$orig" \
|
||||||
|
- && echo "... updated git-extras $VERSION -> $(git extras --version)"
|
||||||
|
+ echo "This git-extras installation is managed by Homebrew."
|
||||||
|
+ echo "If you'd like to update git-extras, run the following:"
|
||||||
|
+ echo
|
||||||
|
+ echo " brew upgrade git-extras"
|
||||||
|
+ echo
|
||||||
|
+ return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
updateForWindows() {
|
||||||
10
check_dependencies.sh
Executable file
10
check_dependencies.sh
Executable file
|
|
@ -0,0 +1,10 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
err() {
|
||||||
|
echo >&2 "$1"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if ! command -v column >/dev/null 2>&1; then
|
||||||
|
err "Need to install dependency 'column' before installation (can be found in bsdmainutils)"
|
||||||
|
fi
|
||||||
95
check_integrity.sh
Executable file
95
check_integrity.sh
Executable file
|
|
@ -0,0 +1,95 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
err() {
|
||||||
|
echo >&2 "$1"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
make_doc() {
|
||||||
|
echo "touch man/git-$1.md && make man/git-$1.{1,html}"
|
||||||
|
}
|
||||||
|
|
||||||
|
check_bash_script() {
|
||||||
|
local cmd="git-$1"
|
||||||
|
|
||||||
|
test -f "bin/$cmd" \
|
||||||
|
|| err "bin/$cmd does not exist"
|
||||||
|
|
||||||
|
test -x "bin/$cmd" \
|
||||||
|
|| err "Run 'chmod +x bin/$cmd' to make it executable"
|
||||||
|
|
||||||
|
shebang=$(head -n1 "bin/$cmd")
|
||||||
|
[[ "$shebang" =~ \#![[:space:]]*/usr/bin/env[[:space:]]+bash ]] \
|
||||||
|
|| err "Start git-$1 with '#!/usr/bin/env bash'"
|
||||||
|
}
|
||||||
|
|
||||||
|
check_git_extras_cmd_list() {
|
||||||
|
local whitelist=('extras')
|
||||||
|
for cmd in "${whitelist[@]}"; do
|
||||||
|
test "$1" == "$cmd" && return
|
||||||
|
done
|
||||||
|
|
||||||
|
grep "\- \*\*git\-$1(1)\*\*" man/git-extras.md >/dev/null \
|
||||||
|
|| err "Add git-$1 in the list of commands in man/git-extras.md via $(make_doc "$1")"
|
||||||
|
}
|
||||||
|
|
||||||
|
check_man_page_index() {
|
||||||
|
grep "git\-$1(1) git\-$1" man/index.txt >/dev/null \
|
||||||
|
|| err "Add git-$1 to index.txt via $(make_doc "$1")"
|
||||||
|
}
|
||||||
|
|
||||||
|
check_documentation() {
|
||||||
|
local cmd="git-$1"
|
||||||
|
test -f "man/$cmd.md" || err "man/$cmd.md is required for bin/$cmd"
|
||||||
|
|
||||||
|
if [ ! -f "man/$cmd.1" ] || [ ! -f "man/$cmd.html" ]
|
||||||
|
then
|
||||||
|
err "Create man/$cmd.1 and man/$cmd.html via $(make_doc "$1")"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "man/$cmd.md" -nt "man/$cmd.1" ] || [ "man/$cmd.md" -nt "man/$cmd.html" ]
|
||||||
|
then
|
||||||
|
err "man/$cmd.md, man/$cmd.1, and man/$cmd.html all exist, but man/$cmd.md is newer. You should rm man/$cmd.{1,html} && $(make_doc "$1")"
|
||||||
|
fi
|
||||||
|
|
||||||
|
check_git_extras_cmd_list "$@"
|
||||||
|
check_man_page_index "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
check_Commands_page() {
|
||||||
|
# These are special cases. All listed together, so we ignore them
|
||||||
|
local whitelist=('bug' 'chore' 'feature' 'refactor')
|
||||||
|
for cmd in "${whitelist[@]}"; do
|
||||||
|
test "$1" == "$cmd" && return
|
||||||
|
done
|
||||||
|
|
||||||
|
grep "\- \[\`git $1\`\](#git-$1)" Commands.md >/dev/null \
|
||||||
|
|| err "Add git-$1 in the list of commands in Commands.md"
|
||||||
|
|
||||||
|
grep "^## git $1" Commands.md >/dev/null \
|
||||||
|
|| err "Add description of git-$1 in Commands.md"
|
||||||
|
}
|
||||||
|
|
||||||
|
check_completion() {
|
||||||
|
grep "$1:" etc/git-extras-completion.zsh > /dev/null || \
|
||||||
|
err "Add git-$1 to the completion list at the end of etc/git-extras-completion.zsh"
|
||||||
|
}
|
||||||
|
|
||||||
|
check() {
|
||||||
|
check_bash_script "$1"
|
||||||
|
check_documentation "$1"
|
||||||
|
check_Commands_page "$1"
|
||||||
|
check_completion "$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
test $# == 0 && set -- $(find bin | cut -b 5- | xargs)
|
||||||
|
|
||||||
|
./bin/git-utimes --newer
|
||||||
|
|
||||||
|
for name in "$@"; do
|
||||||
|
name=${name#git-}
|
||||||
|
[[ "$name" == "rscp" ]] && continue
|
||||||
|
check "$name"
|
||||||
|
done
|
||||||
|
|
||||||
|
echo 'All done'
|
||||||
220
etc/bash_completion.sh
Normal file
220
etc/bash_completion.sh
Normal file
|
|
@ -0,0 +1,220 @@
|
||||||
|
# shellcheck shell=bash
|
||||||
|
# bash completion support for git-extras.
|
||||||
|
|
||||||
|
__gitex_heads_unique() {
|
||||||
|
local branch specified=("${COMP_WORDS[@]:2}")
|
||||||
|
for branch in $(__git_heads); do
|
||||||
|
[[ " ${specified[*]} " == *" $branch "* ]] || printf '%s\n' "$branch"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
_git_authors(){
|
||||||
|
__gitcomp "-l --list --no-email"
|
||||||
|
}
|
||||||
|
|
||||||
|
_git_browse(){
|
||||||
|
__git_complete_remote_or_refspec
|
||||||
|
}
|
||||||
|
|
||||||
|
_git_browse_ci(){
|
||||||
|
__git_complete_remote_or_refspec
|
||||||
|
}
|
||||||
|
|
||||||
|
_git_brv(){
|
||||||
|
__gitcomp "-r --reverse"
|
||||||
|
}
|
||||||
|
|
||||||
|
_git_changelog(){
|
||||||
|
local s_opts=( '-a' '-l' '-t' '-f' '-s' '-n' '-p' '-x' '-h' '?' )
|
||||||
|
local l_opts=(
|
||||||
|
'--all'
|
||||||
|
'--list'
|
||||||
|
'--tag'
|
||||||
|
'--final-tag'
|
||||||
|
'--start-tag'
|
||||||
|
'--start-commit'
|
||||||
|
'--no-merges'
|
||||||
|
'--prune-old'
|
||||||
|
'--stdout'
|
||||||
|
'--help'
|
||||||
|
)
|
||||||
|
local merged_opts_str=""
|
||||||
|
merged_opts_str+="$(printf "%s " "${s_opts[@]}")"
|
||||||
|
merged_opts_str+="$(printf "%s " "${l_opts[@]}")"
|
||||||
|
|
||||||
|
__gitcomp "$merged_opts_str"
|
||||||
|
}
|
||||||
|
|
||||||
|
_git_coauthor(){
|
||||||
|
local oldIfs=$IFS
|
||||||
|
IFS=$'\n'
|
||||||
|
local cur="${COMP_WORDS[COMP_CWORD]}"
|
||||||
|
local selection=
|
||||||
|
if ((COMP_CWORD == 2)); then
|
||||||
|
for line in $(git authors --list); do
|
||||||
|
selection+="${line% *}"$'\n'
|
||||||
|
done
|
||||||
|
elif ((COMP_CWORD == 3)); then
|
||||||
|
local chosen_name="${COMP_WORDS[COMP_CWORD-1]}"
|
||||||
|
for line in $(git authors --list); do
|
||||||
|
if [ "$chosen_name" = "${line% *}" ]; then
|
||||||
|
local email=${line#*<}
|
||||||
|
email=${email%>*}
|
||||||
|
selection+="$email"$'\n'
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
compopt +o default
|
||||||
|
compopt -o filenames
|
||||||
|
COMPREPLY=($(compgen -W "$selection" -- "$cur"))
|
||||||
|
IFS=$oldIfs
|
||||||
|
}
|
||||||
|
|
||||||
|
_git_contrib(){
|
||||||
|
# git completion function modified from
|
||||||
|
# https://github.com/markgandolfo/git-bash-completion/blob/master/git-completion.bash
|
||||||
|
contributors="$(git shortlog -s | cut -f2)"
|
||||||
|
local all c s=$'\n' IFS=$'\n'
|
||||||
|
local cur="${COMP_WORDS[COMP_CWORD]}"
|
||||||
|
for c in $contributors; do
|
||||||
|
all="$all$c $s"
|
||||||
|
done
|
||||||
|
COMPREPLY=($(compgen -W "$all" -- "$cur"))
|
||||||
|
}
|
||||||
|
|
||||||
|
_git_commits_since(){
|
||||||
|
__gitcomp "-r --ref"
|
||||||
|
}
|
||||||
|
|
||||||
|
_git_count(){
|
||||||
|
__gitcomp "--all"
|
||||||
|
}
|
||||||
|
|
||||||
|
__git_cp(){
|
||||||
|
__git_complete_file
|
||||||
|
}
|
||||||
|
|
||||||
|
_git_delete_branch(){
|
||||||
|
__gitcomp "$(__gitex_heads_unique)"
|
||||||
|
}
|
||||||
|
|
||||||
|
_git_delete_squashed_branches(){
|
||||||
|
__gitcomp "$(__git_heads)"
|
||||||
|
}
|
||||||
|
|
||||||
|
_git_delete_submodule(){
|
||||||
|
__gitcomp "$(git submodule status | awk '{print $2}')"
|
||||||
|
}
|
||||||
|
|
||||||
|
_git_delete_tag(){
|
||||||
|
__gitcomp "$(__git_tags)"
|
||||||
|
}
|
||||||
|
|
||||||
|
_git_effort(){
|
||||||
|
__git_has_doubledash && return
|
||||||
|
|
||||||
|
case "$cur" in
|
||||||
|
--*)
|
||||||
|
# shellcheck disable=SC2154
|
||||||
|
__gitcomp "
|
||||||
|
--above
|
||||||
|
$__git_log_common_options
|
||||||
|
$__git_log_shortlog_options
|
||||||
|
"
|
||||||
|
return
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
_git_extras(){
|
||||||
|
__gitcomp "--version update"
|
||||||
|
}
|
||||||
|
|
||||||
|
__git_extras_workflow(){
|
||||||
|
__gitcomp "$(__git_heads | grep -- "^$1/" | sed s/^"$1"\\///g) finish"
|
||||||
|
}
|
||||||
|
|
||||||
|
_git_feature(){
|
||||||
|
__git_extras_workflow "feature"
|
||||||
|
}
|
||||||
|
|
||||||
|
_git_graft(){
|
||||||
|
__gitcomp "$(__git_heads)"
|
||||||
|
}
|
||||||
|
|
||||||
|
_git_ignore(){
|
||||||
|
case "$cur" in
|
||||||
|
--*)
|
||||||
|
__gitcomp "--global --local --private --edit --remove"
|
||||||
|
return
|
||||||
|
;;
|
||||||
|
-*)
|
||||||
|
__gitcomp "--global --local --private --edit --remove -g -l -p -e -r"
|
||||||
|
return
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
_git_info(){
|
||||||
|
__gitcomp "--color -c --no-config"
|
||||||
|
}
|
||||||
|
|
||||||
|
_git_missing(){
|
||||||
|
# Suggest all known refs
|
||||||
|
__gitcomp "$(git for-each-ref --format='%(refname:short)')"
|
||||||
|
}
|
||||||
|
|
||||||
|
_git_psykorebase(){
|
||||||
|
__gitcomp "$(__git_heads) --continue --no-ff"
|
||||||
|
}
|
||||||
|
|
||||||
|
_git_reauthor(){
|
||||||
|
local prev="${COMP_WORDS[COMP_CWORD-1]}"
|
||||||
|
local comp
|
||||||
|
|
||||||
|
if [[ "${prev}" == '--type' ]] || [[ "${prev}" == '-t' ]]; then
|
||||||
|
comp='author committer full'
|
||||||
|
else
|
||||||
|
comp='--all --correct-email --correct-name --old-email --type --use-config'
|
||||||
|
fi
|
||||||
|
|
||||||
|
__gitcomp "${comp}"
|
||||||
|
}
|
||||||
|
|
||||||
|
__git_extras_rename(){
|
||||||
|
if ((COMP_CWORD == 2 || COMP_CWORD == 3)); then
|
||||||
|
__gitcomp "$1"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
_git_rename_branch(){
|
||||||
|
__git_extras_rename "$(__git_heads)"
|
||||||
|
}
|
||||||
|
|
||||||
|
_git_rename_remote(){
|
||||||
|
__git_extras_rename "$(__git_remotes)"
|
||||||
|
}
|
||||||
|
|
||||||
|
_git_rename_tag(){
|
||||||
|
__git_extras_rename "$(__git_tags)"
|
||||||
|
}
|
||||||
|
|
||||||
|
_git_rscp(){
|
||||||
|
__git_complete_remote_or_refspec
|
||||||
|
}
|
||||||
|
|
||||||
|
_git_scp(){
|
||||||
|
__git_complete_remote_or_refspec
|
||||||
|
}
|
||||||
|
|
||||||
|
_git_squash(){
|
||||||
|
__gitcomp "$(__git_heads)"
|
||||||
|
}
|
||||||
|
|
||||||
|
_git_stamp(){
|
||||||
|
__gitcomp '--replace -r'
|
||||||
|
}
|
||||||
|
|
||||||
|
_git_undo(){
|
||||||
|
__gitcomp "--hard --soft -h -s"
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue