mirror of
https://github.com/tj/git-extras.git
synced 2026-09-11 07:56:18 -04:00
Compare commits
1160 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 |
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
|
||||||
311
AUTHORS
311
AUTHORS
|
|
@ -1,33 +1,296 @@
|
||||||
git-extras is written and maintained by TJ Holowaychuk and
|
git-extras is written and maintained by various contributors:
|
||||||
various contributors:
|
|
||||||
|
|
||||||
Development Lead
|
|
||||||
````````````````
|
|
||||||
|
|
||||||
|
Maintainers
|
||||||
|
```````````
|
||||||
- TJ Holowaychuk <tj@vision-media.ca>
|
- TJ Holowaychuk <tj@vision-media.ca>
|
||||||
|
|
||||||
Maintainer
|
|
||||||
````````````````
|
|
||||||
- Hemanth.HM <hemanth.hm@gmail.com>
|
- 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
|
Patches and Suggestions
|
||||||
```````````````````````
|
```````````````````````
|
||||||
|
|
||||||
- Mark Eissler
|
|
||||||
- Chern Jie
|
|
||||||
- Kenneth Reitz
|
|
||||||
- Aggelos Orfanakos
|
|
||||||
- Jonathan "Duke" Leto
|
|
||||||
- Domenico Rotiroti
|
|
||||||
- Jason Young
|
|
||||||
- Nick Campbell
|
|
||||||
- Jonhnny Weslley
|
- Jonhnny Weslley
|
||||||
- Alex McHale
|
- Tobias Fendin
|
||||||
- Gert Van Gool
|
- LeeW
|
||||||
- Devin Withers
|
- timfeirg
|
||||||
- David Baumgold
|
- Niklas Schlimm
|
||||||
|
- nickl-
|
||||||
|
- CJ
|
||||||
|
- Paul Wise
|
||||||
|
- Damien Tardy-Panis
|
||||||
|
- Mark Eissler
|
||||||
|
- Yuriy VG
|
||||||
- Leila Muhtasib
|
- Leila Muhtasib
|
||||||
- Duane Johnson
|
- Richard Fearn
|
||||||
- Todd Wolfson
|
- Bill Wood
|
||||||
- Niklas Fiekas
|
- 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
|
- 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`.
|
||||||
1045
Commands.md
1045
Commands.md
File diff suppressed because it is too large
Load diff
1193
History.md
1193
History.md
File diff suppressed because it is too large
Load diff
184
Installation.md
184
Installation.md
|
|
@ -1,24 +1,196 @@
|
||||||
## Debian
|
# 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
|
```bash
|
||||||
$ sudo $apt_pref update
|
$ sudo $apt_pref update
|
||||||
$ sudo $apt_pref install git-extras
|
$ sudo $apt_pref install git-extras
|
||||||
```
|
```
|
||||||
|
|
||||||
## Mac
|
### 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
|
```bash
|
||||||
$ brew install git-extras
|
$ brew install git-extras
|
||||||
```
|
```
|
||||||
|
|
||||||
## Clone / Tarball:
|
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
|
```bash
|
||||||
$ make install
|
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:
|
One-liner:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ (cd /tmp && git clone --depth 1 https://github.com/tj/git-extras.git && cd git-extras && sudo make install)
|
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/
|
||||||
|
|
|
||||||
2
LICENSE
2
LICENSE
|
|
@ -1,6 +1,6 @@
|
||||||
(The MIT License)
|
(The MIT License)
|
||||||
|
|
||||||
Copyright (c) 2015 TJ Holowaychuk <tj@vision-media.ca>
|
Copyright (c) 2010 TJ Holowaychuk <tj@vision-media.ca> and Contributors
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining
|
Permission is hereby granted, free of charge, to any person obtaining
|
||||||
a copy of this software and associated documentation files (the
|
a copy of this software and associated documentation files (the
|
||||||
|
|
|
||||||
123
Makefile
123
Makefile
|
|
@ -1,37 +1,80 @@
|
||||||
PREFIX ?= /usr/local
|
PREFIX ?= /usr/local
|
||||||
BINPREFIX ?= "$(PREFIX)/bin"
|
BINPREFIX ?= "$(PREFIX)/bin"
|
||||||
MANPREFIX ?= "$(PREFIX)/share/man/man1"
|
SYSCONFDIR ?= $(PREFIX)/etc
|
||||||
|
SHELL := bash
|
||||||
|
|
||||||
|
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 = $(wildcard bin/git-*)
|
BINS = $(wildcard bin/git-*)
|
||||||
MANS = $(wildcard man/git-*.md)
|
MANS = $(wildcard man/git-*.md)
|
||||||
|
MAN_BINS = $(filter-out man/git-extras.md, $(MANS))
|
||||||
MAN_HTML = $(MANS:.md=.html)
|
MAN_HTML = $(MANS:.md=.html)
|
||||||
MAN_PAGES = $(MANS:.md=.1)
|
MAN_PAGES = $(MANS:.md=.1)
|
||||||
|
CODE_DIR := $(dir $(abspath $(lastword $(MAKEFILE_LIST))))
|
||||||
|
INSTALL_VIA ?= source
|
||||||
|
# Libraries used by all commands
|
||||||
LIB = "helper/reset-env" "helper/git-extra-utility"
|
LIB = "helper/reset-env" "helper/git-extra-utility"
|
||||||
|
|
||||||
COMMANDS_USED_WITHOUT_GIT_REPO = git-alias git-extras git-fork git-setup
|
COMMANDS = $(subst bin/, , $(BINS))
|
||||||
COMMANDS_USED_WITH_GIT_REPO = $(filter-out $(COMMANDS_USED_WITHOUT_GIT_REPO), \
|
|
||||||
$(subst bin/, , $(BINS)))
|
default: install
|
||||||
|
|
||||||
docs: $(MAN_HTML) $(MAN_PAGES)
|
docs: $(MAN_HTML) $(MAN_PAGES)
|
||||||
|
|
||||||
install:
|
check:
|
||||||
@mkdir -p $(DESTDIR)$(MANPREFIX)
|
@echo "Check dependencies before installation"
|
||||||
@mkdir -p $(DESTDIR)$(BINPREFIX)
|
@./check_dependencies.sh
|
||||||
@echo "... installing bins to $(DESTDIR)$(BINPREFIX)"
|
@echo
|
||||||
@echo "... installing man pages to $(DESTDIR)$(MANPREFIX)"
|
|
||||||
$(eval TEMPFILE := $(shell mktemp -t git-extra.XXX))
|
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 from rw-------(default) to rwxrwxr-x, so that users can exec the scripts
|
||||||
@chmod 775 $(TEMPFILE)
|
@chmod 775 $(TEMPFILE)
|
||||||
@$(foreach COMMAND, $(COMMANDS_USED_WITH_GIT_REPO), \
|
$(eval EXISTED_ALIASES := $(shell \
|
||||||
echo "... installing $(COMMAND)"; \
|
git config --get-regexp 'alias\..*' | awk '{print "git-" substr($$1, 7)}'))
|
||||||
head -1 bin/$(COMMAND) | cat - $(LIB) ./helper/is-git-repo > $(TEMPFILE); \
|
@$(foreach COMMAND, $(COMMANDS), \
|
||||||
tail -n +2 bin/$(COMMAND) >> $(TEMPFILE); \
|
should_install='yes'; \
|
||||||
cp -f $(TEMPFILE) $(DESTDIR)$(BINPREFIX)/$(COMMAND); \
|
if [ ! -z "$(filter $(COMMAND), $(EXISTED_ALIASES))" ] && [ "$$SKIP_CONFLICT_CHECK" != yes ]; then \
|
||||||
)
|
read -p "$(COMMAND) conflicts with an alias, still install it? [y/n]: " answer; \
|
||||||
@$(foreach COMMAND, $(COMMANDS_USED_WITHOUT_GIT_REPO), \
|
if [ "$$answer" = 'n' ] || [ "$$answer" = 'N' ]; then \
|
||||||
echo "... installing $(COMMAND)"; \
|
should_install="no"; \
|
||||||
head -1 bin/$(COMMAND) | cat - $(LIB) > $(TEMPFILE); \
|
fi; \
|
||||||
tail -n +2 bin/$(COMMAND) >> $(TEMPFILE); \
|
fi; \
|
||||||
cp -f $(TEMPFILE) $(DESTDIR)$(BINPREFIX)/$(COMMAND); \
|
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 \
|
@if [ -z "$(wildcard man/git-*.1)" ]; then \
|
||||||
echo "WARNING: man pages not created, use 'make docs' (which requires 'ronn' ruby lib)"; \
|
echo "WARNING: man pages not created, use 'make docs' (which requires 'ronn' ruby lib)"; \
|
||||||
|
|
@ -39,10 +82,35 @@ install:
|
||||||
cp -f man/git-*.1 $(DESTDIR)$(MANPREFIX); \
|
cp -f man/git-*.1 $(DESTDIR)$(MANPREFIX); \
|
||||||
echo "cp -f man/git-*.1 $(DESTDIR)$(MANPREFIX)"; \
|
echo "cp -f man/git-*.1 $(DESTDIR)$(MANPREFIX)"; \
|
||||||
fi
|
fi
|
||||||
@mkdir -p $(DESTDIR)/etc/bash_completion.d
|
@mkdir -p $(COMPL_DIR)
|
||||||
cp -f etc/bash_completion.sh $(DESTDIR)/etc/bash_completion.d/git-extras
|
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/%.html: man/%.md
|
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 \
|
ronn \
|
||||||
--manual "Git Extras" \
|
--manual "Git Extras" \
|
||||||
--html \
|
--html \
|
||||||
|
|
@ -64,7 +132,7 @@ uninstall:
|
||||||
echo "... uninstalling $(DESTDIR)$(MANPREFIX)/$(notdir $(MAN))"; \
|
echo "... uninstalling $(DESTDIR)$(MANPREFIX)/$(notdir $(MAN))"; \
|
||||||
rm -f $(DESTDIR)$(MANPREFIX)/$(notdir $(MAN)); \
|
rm -f $(DESTDIR)$(MANPREFIX)/$(notdir $(MAN)); \
|
||||||
)
|
)
|
||||||
rm -f $(DESTDIR)/etc/bash_completion.d/git-extras
|
rm -f $(COMPL_DIR)/git-extras
|
||||||
|
|
||||||
clean: docclean
|
clean: docclean
|
||||||
|
|
||||||
|
|
@ -72,4 +140,7 @@ docclean:
|
||||||
rm -f man/*.1
|
rm -f man/*.1
|
||||||
rm -f man/*.html
|
rm -f man/*.html
|
||||||
|
|
||||||
.PHONY: docs clean docclean install uninstall
|
test:
|
||||||
|
bats ./tests
|
||||||
|
|
||||||
|
.PHONY: default docs check install uninstall clean docclean test
|
||||||
|
|
|
||||||
21
Readme.md
21
Readme.md
|
|
@ -6,14 +6,29 @@ Little git extras.
|
||||||
|
|
||||||
Just getting started? Check out these screencasts:
|
Just getting started? Check out these screencasts:
|
||||||
|
|
||||||
* [introduction](https://vimeo.com/45506445) -- covering git-ignore, git-setup, git-changelog, git-release, git-effort and more
|
* [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
|
||||||
|
|
||||||
See [Installation](Installation.md) page.
|
See the [Installation](Installation.md) page.
|
||||||
|
|
||||||
## Commands
|
## Commands
|
||||||
|
|
||||||
Go to [Commands](Commands.md) page for basic usage and examples.
|
Go to the [Commands](Commands.md) page for basic usage and examples.
|
||||||
|
|
||||||
__GIT utilities__ -- repo summary, repl, changelog population, author commit percentages and more
|
__GIT utilities__ -- repo summary, repl, changelog population, author commit percentages and more
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
Interested in contributing? Awesome!
|
||||||
|
|
||||||
|
Please read [Contributing](CONTRIBUTING.md) before you make a PR, thanks!
|
||||||
|
|
||||||
|
## The change of the default branch
|
||||||
|
|
||||||
|
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`.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
For example, `git config git-extras.default-branch master`.
|
||||||
|
|
|
||||||
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"
|
||||||
|
|
@ -1,7 +1,45 @@
|
||||||
#!/usr/bin/env bash
|
#!/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
|
case $# in
|
||||||
0) git config --get-regexp 'alias.*' | colrm 1 6 | sed 's/[ ]/ = /' | sort ;;
|
0)
|
||||||
1) git alias | grep -e -- "$1" ;;
|
if [[ -z "$options" ]]; then
|
||||||
*) git config --global "alias.$1" "$2" ;;
|
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
|
esac
|
||||||
|
|
|
||||||
|
|
@ -1,34 +1,37 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
# extract current branch name
|
# extract current branch name
|
||||||
BRANCH=$(git name-rev HEAD 2> /dev/null | awk "{ print \$2 }")
|
BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null)
|
||||||
|
|
||||||
# get name of the most top folder of current directory, used for the
|
# get name of the most top folder of current directory, used for the
|
||||||
# output filename
|
# output filename
|
||||||
ARCHIVE_NAME=$(basename $(pwd))
|
ARCHIVE_NAME=$(basename "$(git rev-parse --show-toplevel)")
|
||||||
|
|
||||||
if [[ $BRANCH = tags* ]]; then
|
if [[ $BRANCH = tags* ]]; then
|
||||||
BRANCH=$(git describe)
|
BRANCH=$(git describe)
|
||||||
echo Building for tag \"$BRANCH\"
|
echo Building for tag "\"$BRANCH\""
|
||||||
FILENAME=$ARCHIVE_NAME.$BRANCH.zip
|
FILENAME=$ARCHIVE_NAME.$BRANCH.zip
|
||||||
else
|
else
|
||||||
echo Building archive on branch \"$BRANCH\"
|
echo Building archive on branch "\"$BRANCH\""
|
||||||
# get a version string, so archives will not be overwritten when creating
|
# get a version string, so archives will not be overwritten when creating
|
||||||
# many of them
|
# many of them
|
||||||
VERSION=$(git describe --always --long)
|
VERSION=$(git describe --always --long)
|
||||||
# if not on master append branch name into the filename
|
# if not on master append branch name into the filename
|
||||||
if [ "$BRANCH" = "master" ]; then
|
if [ "$BRANCH" = "$(git_extra_default_branch)" ]; then
|
||||||
FILENAME=$ARCHIVE_NAME.$VERSION.zip
|
FILENAME=$ARCHIVE_NAME.$VERSION.zip
|
||||||
else
|
else
|
||||||
FILENAME=$ARCHIVE_NAME.$VERSION.$BRANCH.zip
|
FILENAME=$ARCHIVE_NAME.$VERSION.$BRANCH.zip
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# rename invalid chars for the file path
|
||||||
|
FILENAME=${FILENAME//\//-}
|
||||||
|
FILENAME=${FILENAME//\\/-}
|
||||||
# combine path and filename
|
# combine path and filename
|
||||||
OUTPUT=$(pwd)/$FILENAME
|
OUTPUT=$PWD/$FILENAME
|
||||||
|
|
||||||
# building archive
|
# building archive
|
||||||
git archive --format zip --output $OUTPUT $BRANCH
|
git archive --format zip --output "$OUTPUT" "$BRANCH"
|
||||||
|
|
||||||
# also display size of the resulting file
|
# also display size of the resulting file
|
||||||
echo Saved to \"$FILENAME\" \(`du -h $OUTPUT | cut -f1`\)
|
echo Saved to \""$FILENAME"\" \("$(du -h "$OUTPUT" | cut -f1)"\)
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,29 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
LIST=false
|
LIST=false
|
||||||
|
NO_EMAIL=false
|
||||||
FILE=""
|
FILE=""
|
||||||
EDITOR=$(git var GIT_EDITOR)
|
|
||||||
|
|
||||||
if test "$1" = "-l" || test "$1" = "--list"; then
|
while [[ $# -gt 0 ]]; do
|
||||||
LIST=true
|
case $1 in
|
||||||
else
|
-l|--list )
|
||||||
|
LIST=true
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--no-email )
|
||||||
|
NO_EMAIL=true
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
* )
|
||||||
|
break
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if ! $LIST; then
|
||||||
FILE=$1
|
FILE=$1
|
||||||
if test "$FILE" = ""; then
|
if [ -z "$FILE" ]; then
|
||||||
FILE=`ls | egrep 'authors|contributors' -i|head -n1`
|
FILE=$(find . -mindepth 1 -maxdepth 1 \( -iname '*authors*' -o -iname '*contributors*' \) | head -n1)
|
||||||
if test "$FILE" = ""; then
|
if [ -z "$FILE" ]; then
|
||||||
FILE='AUTHORS'
|
FILE='AUTHORS'
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
@ -21,7 +34,13 @@ fi
|
||||||
#
|
#
|
||||||
|
|
||||||
authors() {
|
authors() {
|
||||||
git shortlog -sne | awk '{$1=""; sub(" ", ""); print}'
|
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
|
||||||
}
|
}
|
||||||
|
|
||||||
#
|
#
|
||||||
|
|
@ -29,8 +48,7 @@ authors() {
|
||||||
#
|
#
|
||||||
|
|
||||||
if $LIST; then
|
if $LIST; then
|
||||||
echo "$(authors)"
|
authors
|
||||||
else
|
else
|
||||||
authors >> $FILE
|
authors >> "$FILE"
|
||||||
test -n "$EDITOR" && $EDITOR $FILE
|
|
||||||
fi
|
fi
|
||||||
|
|
|
||||||
11
bin/git-back
11
bin/git-back
|
|
@ -1,11 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
if test $# -eq 0; then
|
|
||||||
git reset --soft HEAD~1
|
|
||||||
else
|
|
||||||
if `echo $1 | grep -q [^[:digit:]]`; then
|
|
||||||
echo "$1 is not a number" 1>&2
|
|
||||||
else
|
|
||||||
git reset --soft HEAD~$1
|
|
||||||
fi
|
|
||||||
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
|
||||||
15
bin/git-bug
15
bin/git-bug
|
|
@ -1,15 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
if test "$1" = "finish"; then
|
|
||||||
test -z $2 && echo "bug <name> required." 1>&2 && exit 1
|
|
||||||
branch=bug/$2
|
|
||||||
git merge --no-ff $branch && git delete-branch $branch
|
|
||||||
else
|
|
||||||
test -z $1 && echo "bug <name> required." 1>&2 && exit 1
|
|
||||||
if test -n "$2"; then
|
|
||||||
echo "too many arguments; if not finishing a bug, only <name> of new bug is expected" && exit 1
|
|
||||||
fi
|
|
||||||
branch=bug/$1
|
|
||||||
git checkout -b $branch &> /dev/null
|
|
||||||
test -n $? && git checkout $branch &> /dev/null
|
|
||||||
fi
|
|
||||||
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
|
||||||
|
|
@ -4,9 +4,20 @@ DEF_TAG_RECENT="n.n.n"
|
||||||
GIT_LOG_OPTS="$(git config changelog.opts)"
|
GIT_LOG_OPTS="$(git config changelog.opts)"
|
||||||
GIT_LOG_FORMAT="$(git config changelog.format)"
|
GIT_LOG_FORMAT="$(git config changelog.format)"
|
||||||
[[ -z "$GIT_LOG_FORMAT" ]] && GIT_LOG_FORMAT=' * %s'
|
[[ -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)"
|
GIT_EDITOR="$(git var GIT_EDITOR)"
|
||||||
PROGNAME="git-changelog"
|
PROGNAME="git-changelog"
|
||||||
|
|
||||||
|
git_editor() {
|
||||||
|
if test -z "${GIT_EDITOR:+set}"
|
||||||
|
then
|
||||||
|
GIT_EDITOR="$(git var GIT_EDITOR)" || return $?
|
||||||
|
fi
|
||||||
|
|
||||||
|
eval "$GIT_EDITOR" '"$@"'
|
||||||
|
}
|
||||||
|
|
||||||
_usage() {
|
_usage() {
|
||||||
cat << EOF
|
cat << EOF
|
||||||
usage: $PROGNAME options [file]
|
usage: $PROGNAME options [file]
|
||||||
|
|
@ -20,12 +31,14 @@ tags exist, then all commits are output; if tags exist, then only the most-
|
||||||
recent commits are output up to the last identified tag.
|
recent commits are output up to the last identified tag.
|
||||||
|
|
||||||
OPTIONS:
|
OPTIONS:
|
||||||
-a, --all Retrieve all commits (ignores --start-tag, --final-tag)
|
-a, --all Retrieve all commits (ignores --start-tag/commit, --final-tag)
|
||||||
-l, --list Display commits as a list, with no titles
|
-l, --list Display commits as a list, with no titles
|
||||||
-t, --tag Tag label to use for most-recent (untagged) commits
|
-t, --tag Tag label to use for most-recent (untagged) commits
|
||||||
-f, --final-tag Newest tag to retrieve commits from in a range
|
-f, --final-tag Newest tag to retrieve commits from in a range
|
||||||
-s, --start-tag Oldest 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
|
-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
|
-p, --prune-old Replace existing Changelog entirely with new content
|
||||||
-x, --stdout Write output to stdout instead of to a Changelog file
|
-x, --stdout Write output to stdout instead of to a Changelog file
|
||||||
-h, --help, ? Show this message
|
-h, --help, ? Show this message
|
||||||
|
|
@ -142,14 +155,16 @@ _fetchCommitRange() {
|
||||||
local start_tag="$2"
|
local start_tag="$2"
|
||||||
local final_tag="$3"
|
local final_tag="$3"
|
||||||
|
|
||||||
|
# This shellcheck disable is applied to the whole if-body
|
||||||
|
# shellcheck disable=SC2086
|
||||||
if [[ "$list_all" == true ]]; then
|
if [[ "$list_all" == true ]]; then
|
||||||
git log $GIT_LOG_OPTS --pretty=format:"${GIT_LOG_FORMAT}"
|
git log $GIT_LOG_OPTS --pretty=format:"${CUR_GIT_LOG_FORMAT}"
|
||||||
elif [[ -n "$final_tag" && "$start_tag" == "null" ]]; then
|
elif [[ -n "$final_tag" && "$start_tag" == "null" ]]; then
|
||||||
git log $GIT_LOG_OPTS --pretty=format:"${GIT_LOG_FORMAT}" "${final_tag}"
|
git log $GIT_LOG_OPTS --pretty=format:"${CUR_GIT_LOG_FORMAT}" "${final_tag}"
|
||||||
elif [[ -n "$final_tag" ]]; then
|
elif [[ -n "$final_tag" ]]; then
|
||||||
git log $GIT_LOG_OPTS --pretty=format:"${GIT_LOG_FORMAT}" "${start_tag}"'..'"${final_tag}"
|
git log $GIT_LOG_OPTS --pretty=format:"${CUR_GIT_LOG_FORMAT}" "${start_tag}"'..'"${final_tag}"
|
||||||
elif [[ -n "$start_tag" ]]; then
|
elif [[ -n "$start_tag" ]]; then
|
||||||
git log $GIT_LOG_OPTS --pretty=format:"${GIT_LOG_FORMAT}" "${start_tag}"'..'
|
git log $GIT_LOG_OPTS --pretty=format:"${CUR_GIT_LOG_FORMAT}" "${start_tag}"'..'
|
||||||
fi | sed 's/^ \* \*/ */g'
|
fi | sed 's/^ \* \*/ */g'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -157,7 +172,7 @@ _formatCommitPlain() {
|
||||||
local start_tag="$1"
|
local start_tag="$1"
|
||||||
local final_tag="$2"
|
local final_tag="$2"
|
||||||
|
|
||||||
printf "%s" "$(_fetchCommitRange "false" "$start_tag" "$final_tag")"
|
printf "%s\n" "$(_fetchCommitRange "false" "$start_tag" "$final_tag")"
|
||||||
}
|
}
|
||||||
|
|
||||||
_formatCommitPretty() {
|
_formatCommitPretty() {
|
||||||
|
|
@ -184,7 +199,8 @@ commitList() {
|
||||||
local title_tag="$1"; shift
|
local title_tag="$1"; shift
|
||||||
local start_tag="$1"; shift
|
local start_tag="$1"; shift
|
||||||
local final_tag="$1"; shift
|
local final_tag="$1"; shift
|
||||||
local list_style="${1:-false}" # enable/disable list format
|
local list_style="${1:-false}"; shift # enable/disable list format
|
||||||
|
local start_commit="$1"; shift
|
||||||
local changelog="$FILE"
|
local changelog="$FILE"
|
||||||
local title_date="$(date +'%Y-%m-%d')"
|
local title_date="$(date +'%Y-%m-%d')"
|
||||||
local tags_list=()
|
local tags_list=()
|
||||||
|
|
@ -192,6 +208,19 @@ commitList() {
|
||||||
local defaultIFS="$IFS"
|
local defaultIFS="$IFS"
|
||||||
local IFS="$defaultIFS"
|
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:
|
# Tags look like this:
|
||||||
#
|
#
|
||||||
|
|
@ -214,18 +243,18 @@ commitList() {
|
||||||
|
|
||||||
# fetch our tags
|
# fetch our tags
|
||||||
local _ref _date _tag _tab='%x09'
|
local _ref _date _tag _tab='%x09'
|
||||||
local _tag_regex='^[[:alnum:][:blank:][:punct:]]+/.*'
|
local _tag_regex='tag: *'
|
||||||
while IFS=$'\t' read _ref _date _tag; do
|
while IFS=$'\t' read -r _ref _date _tag; do
|
||||||
[[ -z "${_tag}" ]] && continue
|
[[ -z "${_tag}" ]] && continue
|
||||||
# strip out tags form ()
|
# strip out tags form ()
|
||||||
# git v2.2.0+ supports '%D', like '%d' without the " (", ")" wrapping. One day we should use it instead.
|
# git v2.2.0+ supports '%D', like '%d' without the " (", ")" wrapping. One day we should use it instead.
|
||||||
_tag="${_tag# }"; _tag="${_tag//[()]/}"
|
_tag="${_tag# }"; _tag="${_tag//[()]/}"
|
||||||
# trap tag if it points to last commit (HEAD)
|
# trap tag if it points to last commit (HEAD)
|
||||||
_tag="${_tag##HEAD, }"
|
_tag="${_tag#HEAD*, }"
|
||||||
|
# strip out branches
|
||||||
|
[[ ! "${_tag}" =~ ${_tag_regex} ]] && continue
|
||||||
# strip out any additional tags pointing to same commit, remove tag label
|
# strip out any additional tags pointing to same commit, remove tag label
|
||||||
_tag="${_tag%%,*}"; _tag="${_tag#tag: }"
|
_tag="${_tag%%,*}"; _tag="${_tag#tag: }"
|
||||||
# strip out tags that are actually feature branches (git-flow support)
|
|
||||||
[[ "${_tag}" =~ ${_tag_regex} ]] && continue
|
|
||||||
# add tag to assoc array; copy tag to tag_list_keys for ordered iteration
|
# add tag to assoc array; copy tag to tag_list_keys for ordered iteration
|
||||||
tags_list+=( "${_tag}:${_ref}=>${_date}" )
|
tags_list+=( "${_tag}:${_ref}=>${_date}" )
|
||||||
tags_list_keys+=( "${_tag}" )
|
tags_list_keys+=( "${_tag}" )
|
||||||
|
|
@ -235,13 +264,35 @@ commitList() {
|
||||||
unset _ref _date _tag _tab
|
unset _ref _date _tag _tab
|
||||||
|
|
||||||
local _tags_list_keys_length="${#tags_list_keys[@]}"
|
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 _final_tag_found=false
|
||||||
local _start_tag_found=false
|
local _start_tag_found=false
|
||||||
local i
|
local i
|
||||||
for (( i=0; i<"${_tags_list_keys_length}"; i++ )); do
|
for (( i=0; i<"${_tags_list_keys_length}"; i++ )); do
|
||||||
local __curr_tag="${tags_list_keys[$i]}"
|
local __curr_tag="${tags_list_keys[$i]}"
|
||||||
local __prev_tag="${tags_list_keys[$i+1]:-null}"
|
local __prev_tag="${tags_list_keys[$i+1]:-null}"
|
||||||
local __curr_date="$(_valueForKeyFakeAssocArray "${__curr_tag}" "${tags_list[*]}")"
|
local __curr_date
|
||||||
|
__curr_date="$(_valueForKeyFakeAssocArray "${__curr_tag}" "${tags_list[*]}")"
|
||||||
__curr_date="${__curr_date##*=>}"
|
__curr_date="${__curr_date##*=>}"
|
||||||
|
|
||||||
# output latest commits, up until the most-recent tag, these are all
|
# output latest commits, up until the most-recent tag, these are all
|
||||||
|
|
@ -266,7 +317,23 @@ commitList() {
|
||||||
# find the specified start tag, break when found
|
# find the specified start tag, break when found
|
||||||
if [[ -n "$start_tag" ]]; then
|
if [[ -n "$start_tag" ]]; then
|
||||||
[[ "$start_tag" == "${__curr_tag}" ]] && _start_tag_found=true
|
[[ "$start_tag" == "${__curr_tag}" ]] && _start_tag_found=true
|
||||||
[[ "$start_tag" != "${__curr_tag}" && "${_start_tag_found}" == true ]] && break
|
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
|
fi
|
||||||
|
|
||||||
# output commits made between prev_tag and curr_tag, these are all of the
|
# output commits made between prev_tag and curr_tag, these are all of the
|
||||||
|
|
@ -292,8 +359,9 @@ commitListPlain() {
|
||||||
local list_all="${1:-false}"
|
local list_all="${1:-false}"
|
||||||
local start_tag="$2"
|
local start_tag="$2"
|
||||||
local final_tag="$3"
|
local final_tag="$3"
|
||||||
|
local start_commit="$4"
|
||||||
|
|
||||||
commitList "$list_all" "" "$start_tag" "$final_tag" "true"
|
commitList "$list_all" "" "$start_tag" "$final_tag" "true" "$start_commit"
|
||||||
}
|
}
|
||||||
|
|
||||||
commitListPretty() {
|
commitListPretty() {
|
||||||
|
|
@ -301,11 +369,48 @@ commitListPretty() {
|
||||||
local title_tag="$2"
|
local title_tag="$2"
|
||||||
local start_tag="$3"
|
local start_tag="$3"
|
||||||
local final_tag="$4"
|
local final_tag="$4"
|
||||||
|
local start_commit="$5"
|
||||||
local title_date="$(date +'%Y-%m-%d')"
|
local title_date="$(date +'%Y-%m-%d')"
|
||||||
|
|
||||||
commitList "$list_all" "$title_tag" "$start_tag" "$final_tag"
|
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() {
|
main() {
|
||||||
local start_tag="null" # empty string and "null" mean two different things!
|
local start_tag="null" # empty string and "null" mean two different things!
|
||||||
local final_tag="null"
|
local final_tag="null"
|
||||||
|
|
@ -315,6 +420,7 @@ main() {
|
||||||
"list_style:false"
|
"list_style:false"
|
||||||
"title_tag:$DEF_TAG_RECENT"
|
"title_tag:$DEF_TAG_RECENT"
|
||||||
"start_tag:"
|
"start_tag:"
|
||||||
|
"start_commit:"
|
||||||
"final_tag:"
|
"final_tag:"
|
||||||
"output_file:"
|
"output_file:"
|
||||||
"use_stdout:false"
|
"use_stdout:false"
|
||||||
|
|
@ -351,8 +457,17 @@ main() {
|
||||||
option=( $(_setValueForKeyFakeAssocArray "start_tag" "$2" "${option[*]}") )
|
option=( $(_setValueForKeyFakeAssocArray "start_tag" "$2" "${option[*]}") )
|
||||||
shift
|
shift
|
||||||
;;
|
;;
|
||||||
|
--start-commit )
|
||||||
|
option=( $(_setValueForKeyFakeAssocArray "start_commit" "$2" "${option[*]}") )
|
||||||
|
shift
|
||||||
|
;;
|
||||||
-n | --no-merges )
|
-n | --no-merges )
|
||||||
GIT_LOG_OPTS='--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 )
|
-p | --prune-old )
|
||||||
option=( $(_setValueForKeyFakeAssocArray "prune_old" true "${option[*]}") )
|
option=( $(_setValueForKeyFakeAssocArray "prune_old" true "${option[*]}") )
|
||||||
|
|
@ -372,14 +487,44 @@ main() {
|
||||||
shift
|
shift
|
||||||
done
|
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 _tag="$(_valueForKeyFakeAssocArray "start_tag" "${option[*]}")"
|
||||||
if [[ -n "${_tag}" ]]; then
|
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)"
|
start_tag="$(git describe --tags --abbrev=0 "${_tag}" 2>/dev/null)"
|
||||||
if [[ -z "$start_tag" ]]; then
|
if [[ -z "$start_tag" ]]; then
|
||||||
_error "Specified start-tag does not exist!"
|
_error "Specified start-tag does not exist!"
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
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
|
unset _tag
|
||||||
|
|
||||||
local _tag="$(_valueForKeyFakeAssocArray "final_tag" "${option[*]}")"
|
local _tag="$(_valueForKeyFakeAssocArray "final_tag" "${option[*]}")"
|
||||||
|
|
@ -395,26 +540,29 @@ main() {
|
||||||
#
|
#
|
||||||
# generate changelog
|
# generate changelog
|
||||||
#
|
#
|
||||||
local tmpfile="$(git_extra_mktemp)"
|
local tmpfile changelog title_tag
|
||||||
local changelog="$(_valueForKeyFakeAssocArray "output_file" "${option[*]}")"
|
tmpfile="$(git_extra_mktemp)"
|
||||||
local title_tag="$(_valueForKeyFakeAssocArray "title_tag" "${option[*]}")"
|
changelog="$(_valueForKeyFakeAssocArray "output_file" "${option[*]}")"
|
||||||
|
title_tag="$(_valueForKeyFakeAssocArray "title_tag" "${option[*]}")"
|
||||||
|
|
||||||
if [[ "$(_valueForKeyFakeAssocArray "list_style" "${option[*]}")" == true ]]; then
|
if [[ "$(_valueForKeyFakeAssocArray "list_style" "${option[*]}")" == true ]]; then
|
||||||
if [[ "$(_valueForKeyFakeAssocArray "list_all" "${option[*]}")" == true ]]; then
|
if [[ "$(_valueForKeyFakeAssocArray "list_all" "${option[*]}")" == true ]]; then
|
||||||
commitListPlain "true" >> "$tmpfile"
|
commitListPlain "true" >> "$tmpfile"
|
||||||
else
|
else
|
||||||
commitListPlain "false" "$start_tag" "$final_tag" >> "$tmpfile"
|
commitListPlain "false" "$start_tag" "$final_tag" \
|
||||||
|
"$start_commit" >> "$tmpfile"
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
if [[ "$(_valueForKeyFakeAssocArray "list_all" "${option[*]}")" == true ]]; then
|
if [[ "$(_valueForKeyFakeAssocArray "list_all" "${option[*]}")" == true ]]; then
|
||||||
commitListPretty "true" "$title_tag" >> "$tmpfile"
|
commitListPretty "true" "$title_tag" >> "$tmpfile"
|
||||||
else
|
else
|
||||||
commitListPretty "false" "$title_tag" "$start_tag" "$final_tag" >> "$tmpfile"
|
commitListPretty "false" "$title_tag" "$start_tag" "$final_tag" \
|
||||||
|
"$start_commit" >> "$tmpfile"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ -z "$changelog" ]]; then
|
if [[ -z "$changelog" ]]; then
|
||||||
changelog="$(ls | egrep 'change|history' -i | head -n1)"
|
changelog="$(find . -mindepth 1 -maxdepth 1 \( -iname '*change*' -o -iname '*history*' \) | head -n1)"
|
||||||
if [[ -z "$changelog" ]]; then
|
if [[ -z "$changelog" ]]; then
|
||||||
changelog="History.md";
|
changelog="History.md";
|
||||||
fi
|
fi
|
||||||
|
|
@ -431,8 +579,14 @@ main() {
|
||||||
cat "$tmpfile"
|
cat "$tmpfile"
|
||||||
rm -f "$tmpfile"
|
rm -f "$tmpfile"
|
||||||
else
|
else
|
||||||
mv -f "$tmpfile" "$changelog"
|
cp -f "$tmpfile" "$changelog"
|
||||||
[[ -n "$GIT_EDITOR" ]] && $GIT_EDITOR "$changelog"
|
rm -f "$tmpfile"
|
||||||
|
|
||||||
|
if [[ -n "$GIT_EDITOR" ]]; then
|
||||||
|
git_editor "$changelog" || _exit
|
||||||
|
else
|
||||||
|
less "$changelog" || _exit
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
if test "$1" = "finish"; then
|
|
||||||
test -z $2 && echo "chore <name> required." 1>&2 && exit 1
|
|
||||||
branch=chore/$2
|
|
||||||
git merge --no-ff $branch && git delete-branch $branch
|
|
||||||
else
|
|
||||||
test -z $1 && echo "chore <name> required." 1>&2 && exit 1
|
|
||||||
if test -n "$2"; then
|
|
||||||
echo "too many arguments; if not finishing a chore, only <name> of new chore is expected" && exit 1
|
|
||||||
fi
|
|
||||||
branch=chore/$1
|
|
||||||
git checkout -b $branch &> /dev/null
|
|
||||||
test -n $? && git checkout $branch &> /dev/null
|
|
||||||
fi
|
|
||||||
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 @@
|
||||||
#!/usr/bin/env bash
|
#!/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
|
||||||
|
|
@ -4,6 +4,4 @@ user="$*"
|
||||||
|
|
||||||
test -z "$user" && echo "user name required." 1>&2 && exit 1
|
test -z "$user" && echo "user name required." 1>&2 && exit 1
|
||||||
|
|
||||||
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
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
if test "$1" = "--all"; then
|
if test "$1" = "--all"; then
|
||||||
git shortlog -n -s $@ | awk '{print substr($0,index($0,$2)) " (" $1 ")"}'
|
git shortlog -n -s | awk '{print substr($0,index($0,$2)) " (" $1 ")"}'
|
||||||
echo
|
echo
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo total `git rev-list --count HEAD`
|
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
|
||||||
|
|
@ -1,8 +1,76 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
branch=$1
|
test $# -eq 0 && echo "branch argument required." 1>&2 && exit 1
|
||||||
test -z $branch && echo "branch required." 1>&2 && exit 1
|
|
||||||
|
|
||||||
git push origin HEAD:refs/heads/$branch
|
# preference takes lowest priority; look for remote from prefs first
|
||||||
git fetch origin
|
REMOTE_PREF=$(git config git-extras.create-branch.remote)
|
||||||
git checkout --track -b $branch origin/$branch
|
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,16 +1,17 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
set -e
|
||||||
|
|
||||||
# Assert there is at least one branch provided
|
# Assert there is at least one branch provided
|
||||||
test -z $1 && echo "branch required." 1>&2 && exit 1
|
test -z "$1" && echo "branch required." 1>&2 && exit 1
|
||||||
|
|
||||||
for branch in "$@"
|
for branch in "$@"
|
||||||
do
|
do
|
||||||
remote=$(git config branch.$branch.remote)
|
remote=$(git config "branch.$branch.remote" || echo "origin")
|
||||||
test -z $remote && remote="origin"
|
ref=$(git config "branch.$branch.merge" || echo "refs/heads/$branch")
|
||||||
ref=$(git config branch.$branch.merge)
|
|
||||||
test -z $ref && ref="refs/heads/$branch"
|
|
||||||
|
|
||||||
git branch -D $branch
|
git branch -D "$branch" || true
|
||||||
git branch -d -r $remote/$branch
|
# Avoid deleting local upstream
|
||||||
git push $remote :$ref
|
[ "$remote" = "." ] && continue
|
||||||
|
git branch -d -r "$remote/$branch" || continue
|
||||||
|
git push "$remote" ":$ref"
|
||||||
done
|
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
|
||||||
|
|
@ -1,3 +1,7 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
git branch --no-color --merged | grep -v "\*" | grep -v master | grep -v svn | xargs -n 1 git branch -d
|
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
|
||||||
|
|
@ -1,13 +1,50 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
submodule=$1
|
abort() {
|
||||||
|
error="$1" && shift
|
||||||
|
echo "ERROR: $*" 1>&2
|
||||||
|
test -z "$FORCE" && exit "$error"
|
||||||
|
}
|
||||||
|
|
||||||
test -z $submodule && echo "submodule required" 1>&2 && exit 1
|
# Don't abort on failures. This allows to cleanup after a previous failure.
|
||||||
test ! -f .gitmodules && echo ".gitmodules file not found" 1>&2 && exit 2
|
[ "$1" = '--force' ] && FORCE=1 && shift
|
||||||
|
|
||||||
NAME=$(echo $submodule | sed 's/\/$//g')
|
test -z "$1" && abort 1 'Submodule required'
|
||||||
test -z $(git config --file=.gitmodules submodule.$NAME.url) && echo "submodule not found" 1>&2 && exit 3
|
cd "$(git root)" || abort 5 'Cannot change to repository root'
|
||||||
|
test ! -f '.gitmodules' && abort 2 '.gitmodules file not found'
|
||||||
|
|
||||||
git submodule deinit $NAME
|
NAME="${1%/}"
|
||||||
git rm --cached $NAME
|
test -z "$(git config --file='.gitmodules' "submodule.$NAME.url")" \
|
||||||
rm -rf .git/modules/$NAME
|
&& 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,7 +1,16 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
# Assert there is at least one tag provided
|
# Assert there is at least one tag provided
|
||||||
test -z $1 && echo "tag required." 1>&2 && exit 1
|
test -z "$1" && echo "tag required." 1>&2 && exit 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
|
# Concatenate all the tag references
|
||||||
local_tags=""
|
local_tags=""
|
||||||
|
|
@ -13,5 +22,7 @@ do
|
||||||
done
|
done
|
||||||
|
|
||||||
# Delete all the tags
|
# Delete all the tags
|
||||||
|
# shellcheck disable=SC2086
|
||||||
git tag -d $local_tags
|
git tag -d $local_tags
|
||||||
git push origin $origin_refs
|
# shellcheck disable=SC2086
|
||||||
|
git push "$origin" $origin_refs
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
branch=master
|
branch=$(git_extra_default_branch)
|
||||||
filter=ACM
|
filter=ACM
|
||||||
|
|
||||||
if test $# -eq 1; then
|
if test $# -eq 1; then
|
||||||
|
|
@ -12,4 +12,4 @@ else
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
git diff --name-only --diff-filter=$filter $branch
|
git diff --name-only --diff-filter="$filter" "$branch"
|
||||||
193
bin/git-effort
193
bin/git-effort
|
|
@ -1,15 +1,29 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
tmp=$(git_extra_mktemp)
|
tmp=$(git_extra_mktemp)
|
||||||
above='0'
|
above=0
|
||||||
|
# if the output won't be printed to tty, disable the color
|
||||||
|
test -t 1 && to_tty=true
|
||||||
color=
|
color=
|
||||||
|
|
||||||
#
|
#
|
||||||
# get date for the given <commit>
|
# print usage message
|
||||||
#
|
#
|
||||||
|
usage() {
|
||||||
|
echo 1>&2 "usage: git effort [--above <value>] [<path>...] [-- [<log options>...]]"
|
||||||
|
}
|
||||||
|
|
||||||
date() {
|
#
|
||||||
git log --pretty='format: %ai' $1 | cut -d ' ' -f 2
|
# 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
|
||||||
}
|
}
|
||||||
|
|
||||||
#
|
#
|
||||||
|
|
@ -17,7 +31,7 @@ date() {
|
||||||
#
|
#
|
||||||
|
|
||||||
hide_cursor() {
|
hide_cursor() {
|
||||||
printf '\033[?25l'
|
tputq civis
|
||||||
}
|
}
|
||||||
|
|
||||||
#
|
#
|
||||||
|
|
@ -25,10 +39,10 @@ hide_cursor() {
|
||||||
#
|
#
|
||||||
|
|
||||||
show_cursor_and_cleanup() {
|
show_cursor_and_cleanup() {
|
||||||
printf '\033[?25h'
|
tputq cnorm
|
||||||
printf '\033[m\n'
|
tputq sgr0
|
||||||
rm "$tmp" > /dev/null 2>&1
|
rm "$tmp" > /dev/null 2>&1
|
||||||
exit 1
|
exit 0
|
||||||
}
|
}
|
||||||
|
|
||||||
#
|
#
|
||||||
|
|
@ -36,10 +50,7 @@ show_cursor_and_cleanup() {
|
||||||
#
|
#
|
||||||
|
|
||||||
active_days() {
|
active_days() {
|
||||||
date $1 | uniq | awk '
|
echo "$1" | sort -r | uniq | wc -l
|
||||||
{ sum += 1 }
|
|
||||||
END { print sum }
|
|
||||||
'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#
|
#
|
||||||
|
|
@ -47,45 +58,65 @@ active_days() {
|
||||||
#
|
#
|
||||||
|
|
||||||
color_for() {
|
color_for() {
|
||||||
test $1 -gt 10 && color='33'
|
if [ "$to_tty" = true ]; then
|
||||||
test $1 -gt 25 && color='33;1'
|
if [ "$1" -gt 200 ]; then color="$(tputq setaf 1)$(tputq bold)"
|
||||||
test $1 -gt 50 && color='93'
|
elif [ "$1" -gt 150 ]; then color="$(tputq setaf 1)" # red
|
||||||
test $1 -gt 75 && color='93;1'
|
elif [ "$1" -gt 125 ]; then color="$(tputq setaf 2)$(tputq bold)"
|
||||||
test $1 -gt 100 && color='31'
|
elif [ "$1" -gt 100 ]; then color="$(tputq setaf 2)" # green
|
||||||
test $1 -gt 125 && color='31;1'
|
elif [ "$1" -gt 75 ]; then color="$(tputq setaf 5)$(tputq bold)"
|
||||||
test $1 -gt 150 && color='91'
|
elif [ "$1" -gt 50 ]; then color="$(tputq setaf 5)" # purplish
|
||||||
test $1 -gt 200 && color='91;1'
|
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 <file ...>
|
# compute the effort of the given <path ...>
|
||||||
#
|
#
|
||||||
|
|
||||||
effort() {
|
effort() {
|
||||||
for file in "$@"; do
|
path=$1
|
||||||
commits=`git log --oneline "$file" | wc -l`
|
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'
|
color='90'
|
||||||
|
|
||||||
# ignore <= --above
|
# ignore <= --above
|
||||||
test $commits -le $above && continue
|
test "$commits" -le "$above" && exit 0
|
||||||
|
|
||||||
# commits
|
# commits
|
||||||
color_for $commits
|
color_for $(( commits - above ))
|
||||||
len=${#file}
|
len=${#path}
|
||||||
dot="."
|
dot="."
|
||||||
f_dot="$file"
|
f_dot="$path"
|
||||||
i=0 ; while test $i -lt $((45-$len)) ; do
|
i=0 ; while test $i -lt $(( columns - len )) ; do
|
||||||
f_dot=$f_dot$dot
|
f_dot=$f_dot$dot
|
||||||
i=$(($i+1))
|
i=$((i+1))
|
||||||
done
|
done
|
||||||
|
|
||||||
printf " \033[${color}m%s %-10d" $f_dot $commits
|
msg=$(printf " ${color}%s %-10d" "$f_dot" "$commits")
|
||||||
|
|
||||||
# active days
|
# active days
|
||||||
active=`active_days "$file"`
|
active=$(active_days "$commit_dates")
|
||||||
color_for $active
|
color_for $(( active - above ))
|
||||||
printf "\033[${color}m %d\033[0m\n" $active
|
msg="$msg $(printf "${color} %d${reset_color}\n" "$active")"
|
||||||
done
|
echo "$msg"
|
||||||
}
|
}
|
||||||
|
|
||||||
#
|
#
|
||||||
|
|
@ -94,7 +125,7 @@ effort() {
|
||||||
|
|
||||||
heading() {
|
heading() {
|
||||||
echo
|
echo
|
||||||
printf " %-45s %-10s %s\n" 'file' 'commits' 'active days'
|
printf " %-${columns}s %-10s %s\n" 'path' 'commits' 'active days'
|
||||||
echo
|
echo
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -104,42 +135,100 @@ heading() {
|
||||||
|
|
||||||
sort_effort() {
|
sort_effort() {
|
||||||
clear
|
clear
|
||||||
echo
|
echo " "
|
||||||
heading
|
heading
|
||||||
cat $tmp | sort -rn -k 2
|
< "$tmp" sort -rn -k 2
|
||||||
}
|
}
|
||||||
|
|
||||||
# --above <n-commits>
|
#
|
||||||
|
# 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
|
||||||
|
|
||||||
if test "$1" = "--above"; then
|
|
||||||
shift; above=$1
|
|
||||||
shift
|
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
|
fi
|
||||||
|
|
||||||
# [file ...]
|
# remove empty quotes that appear when there are no arguments
|
||||||
|
args_to_git_log="${args_to_git_log#\ \'\'}"
|
||||||
|
export args_to_git_log
|
||||||
|
|
||||||
declare -a files=()
|
# [path ...]
|
||||||
if test $# -ge 1; then
|
|
||||||
files=("$@")
|
if test "${#paths}" -eq 0; then
|
||||||
else
|
|
||||||
save_ifs=$IFS
|
save_ifs=$IFS
|
||||||
IFS=`echo -en "\n\b"`
|
IFS=$(echo -en "\n\b")
|
||||||
files=(`git ls-files`)
|
paths=($(git ls-files))
|
||||||
IFS=$safe_ifs
|
IFS=$save_ifs
|
||||||
unset save_ifs
|
unset save_ifs
|
||||||
fi
|
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
|
||||||
|
|
||||||
hide_cursor
|
hide_cursor
|
||||||
trap show_cursor_and_cleanup INT
|
trap show_cursor_and_cleanup INT
|
||||||
|
|
||||||
# loop files
|
|
||||||
|
|
||||||
heading
|
heading
|
||||||
|
|
||||||
effort "${files[@]}" | tee $tmp
|
# send paths to effort
|
||||||
test "$(wc -l $tmp | cut -d' ' -f1 )" -gt 1 && sort_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
|
echo
|
||||||
|
|
||||||
show_cursor_and_cleanup
|
show_cursor_and_cleanup
|
||||||
|
|
|
||||||
|
|
@ -1,29 +1,54 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
VERSION="3.0.0"
|
VERSION="7.6.0-dev"
|
||||||
|
INSTALL_SCRIPT="https://raw.githubusercontent.com/tj/git-extras/main/install.sh"
|
||||||
|
|
||||||
update() {
|
update() {
|
||||||
local bin=$(which git-extras)
|
local bin="$(command -v git-extras)"
|
||||||
local prefix=${bin%/*/*}
|
local prefix=${bin%/*/*}
|
||||||
local orig=$PWD
|
local orig=$PWD
|
||||||
|
|
||||||
cd /tmp \
|
curl -s $INSTALL_SCRIPT | PREFIX="$prefix" bash /dev/stdin \
|
||||||
&& rm -fr ./git-extras \
|
|
||||||
&& git clone --depth 1 https://github.com/tj/git-extras.git \
|
|
||||||
&& cd git-extras \
|
|
||||||
&& PREFIX="$prefix" make install \
|
|
||||||
&& cd "$orig" \
|
&& cd "$orig" \
|
||||||
&& echo "... updated git-extras $VERSION -> $(git extras --version)"
|
&& 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
|
case "$1" in
|
||||||
-v|--version)
|
-v|--version)
|
||||||
echo $VERSION && exit 0
|
echo $VERSION && exit 0
|
||||||
;;
|
;;
|
||||||
update)
|
update)
|
||||||
update
|
platform=$(uname -s)
|
||||||
|
if [ "${platform::9}" = "CYGWIN_NT" ] || \
|
||||||
|
[ "${platform::5}" = "MINGW" ] || \
|
||||||
|
[ "${platform::7}" = "MSYS_NT" ]
|
||||||
|
then
|
||||||
|
updateForWindows
|
||||||
|
else
|
||||||
|
update
|
||||||
|
fi
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
man git-extras
|
git extras --help
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
|
||||||
116
bin/git-feature
116
bin/git-feature
|
|
@ -1,25 +1,105 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
concatargs(){
|
branch_prefix=$(git config --get git-extras.feature.prefix)
|
||||||
delim='-'
|
|
||||||
str=
|
if [ -z "$branch_prefix" ]; then
|
||||||
for i in "$@"; do
|
branch_prefix="feature"
|
||||||
str="$str$delim$i"
|
fi
|
||||||
done
|
|
||||||
branch=feature/${str:1}
|
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"
|
||||||
}
|
}
|
||||||
|
|
||||||
if test "$1" = "finish"; then
|
test -z "${argv[0]}" && echo "$branch_prefix" "<name> required." 1>&2 && exit 1
|
||||||
test -z $2 && echo "feature <name> required." 1>&2 && exit 1
|
|
||||||
branch=feature/$2
|
concatargs "${argv[@]}"
|
||||||
git merge --no-ff $branch && git delete-branch $branch
|
|
||||||
|
if "${finish}"
|
||||||
|
then
|
||||||
|
git merge ${merge_mode} "$branch" && git delete-branch "$branch"
|
||||||
else
|
else
|
||||||
test -z $1 && echo "feature <name> required." 1>&2 && exit 1
|
if [[ -n $remote ]] && [[ -z $start_point ]]
|
||||||
if test -n "$2"; then
|
then
|
||||||
concatargs ${@:2}
|
git create-branch -r "$remote" "$branch"
|
||||||
else
|
fi
|
||||||
branch=feature/$1
|
|
||||||
|
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
|
||||||
git checkout -b $branch &> /dev/null
|
|
||||||
test -n $? && git checkout $branch &> /dev/null
|
|
||||||
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
|
||||||
69
bin/git-fork
69
bin/git-fork
|
|
@ -1,34 +1,67 @@
|
||||||
#!/usr/bin/env sh
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
abort() {
|
abort() {
|
||||||
echo $@
|
echo "$@"
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
url="$1"
|
||||||
|
test -z "$url" && url=$(git remote get-url origin 2> /dev/null) && origin=true
|
||||||
# validate repo url
|
# validate repo url
|
||||||
test -z "$1" && abort "github repo needs to be specified as an argument"
|
test -z "$url" && abort "github repo needs to be specified as an argument"
|
||||||
|
|
||||||
# validate user
|
# validate user
|
||||||
echo "Enter your github username"
|
echo "Enter your github username"
|
||||||
read user
|
read -r user
|
||||||
[ "$user" = 'n' ] && abort "git username required"
|
[ -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
|
# extract owner + project from repo url
|
||||||
project=${1##*/}
|
project=${url##*/}
|
||||||
owner=${1%/$project}
|
owner=${url%/"$project"}
|
||||||
owner=${owner##*/}
|
project=${project%.git}
|
||||||
|
if [[ $owner == git@* ]]; then
|
||||||
|
owner=${owner##*:}
|
||||||
|
else
|
||||||
|
owner=${owner##*/}
|
||||||
|
fi
|
||||||
|
|
||||||
# validate
|
# validate
|
||||||
[ -z "$project" -o -z "$owner" ] && abort "github repo needs to be specified as an argument"
|
[[ -z "$project" || -z "$owner" ]] && abort "github repo needs to be specified as an argument"
|
||||||
|
|
||||||
# create pull request
|
# create fork
|
||||||
curl -X POST -u "$user" "https://api.github.com/repos/$owner/$project/forks"
|
if ! curl -qsf \
|
||||||
[ $? = 0 ] || abort "fork failed"
|
-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
|
||||||
|
|
||||||
# clone forked repo into current dir
|
echo "Add GitHub remote branch via SSH (you will be prompted to verify the server's credentials)? (y/n)"
|
||||||
git clone "git@github.com:$user/$project" "$project"
|
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
|
||||||
|
|
||||||
# add reference to origin fork so can merge in upstream changes
|
if [ "$origin" = true ]; then
|
||||||
cd "$project"
|
git remote rename origin upstream
|
||||||
git remote add upstream "git@github.com:$owner/$project"
|
git remote add origin "${remote_prefix}${user}/${project}.git"
|
||||||
git fetch upstream
|
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
|
||||||
|
|
|
||||||
|
|
@ -2,21 +2,21 @@
|
||||||
|
|
||||||
branch=$1
|
branch=$1
|
||||||
|
|
||||||
test -z $branch && echo "branch required." 1>&2 && exit 1
|
test -z "$branch" && echo "branch required." 1>&2 && exit 1
|
||||||
|
|
||||||
changes=`git status --porcelain`
|
changes=$(git status --porcelain)
|
||||||
|
|
||||||
clean()
|
clean()
|
||||||
{
|
{
|
||||||
git symbolic-ref HEAD refs/heads/$branch
|
git symbolic-ref HEAD "refs/heads/$branch"
|
||||||
rm .git/index
|
rm .git/index
|
||||||
git clean -fdx
|
git clean -fdx
|
||||||
}
|
}
|
||||||
|
|
||||||
if [ ! -z "$changes" ]; then
|
if [ -n "$changes" ]; then
|
||||||
read -p "All untracked changes will be lost. Continue [y/N]? " res
|
read -rp "All untracked changes will be lost. Continue [y/N]? " res
|
||||||
case $res in
|
case $res in
|
||||||
[Yy]* ) clean; break;;
|
[Yy]* ) ;;
|
||||||
* ) exit 0;;
|
* ) exit 0;;
|
||||||
esac
|
esac
|
||||||
fi
|
fi
|
||||||
|
|
|
||||||
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" "$@"
|
||||||
|
|
@ -4,10 +4,10 @@ echo 'setting up gh-pages'
|
||||||
echo '-------------------'
|
echo '-------------------'
|
||||||
|
|
||||||
echo 'Tell me your github account username: '
|
echo 'Tell me your github account username: '
|
||||||
read username
|
read -r username
|
||||||
|
|
||||||
echo 'Now, tell me your repository name: '
|
echo 'Now, tell me your repository name: '
|
||||||
read repository
|
read -r repository
|
||||||
|
|
||||||
git stash \
|
git stash \
|
||||||
&& git checkout -b 'gh-pages' \
|
&& git checkout -b 'gh-pages' \
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,9 @@
|
||||||
src=$1
|
src=$1
|
||||||
dst=$2
|
dst=$2
|
||||||
|
|
||||||
test -z $src && echo "source branch required." 1>&2 && exit 1
|
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 checkout "$dst" \
|
||||||
&& git merge --no-ff $src \
|
&& git merge --no-ff "$src" \
|
||||||
&& git branch -d $src
|
&& git branch -d "$src"
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
#!/bin/bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
for param in $*
|
for param in "$@"
|
||||||
do
|
do
|
||||||
case $param in
|
case $param in
|
||||||
-h|--help )
|
-h)
|
||||||
echo 'Usage: git-guilt [<options>] <since> <until>'
|
echo 'Usage: git-guilt [<options>] <since> <until>'
|
||||||
echo 'Calculates the change in blame between two revisions'
|
echo 'Calculates the change in blame between two revisions'
|
||||||
echo 'Example: git guilt HEAD~3 HEAD'
|
echo 'Example: git guilt HEAD~3 HEAD'
|
||||||
|
|
@ -31,7 +31,7 @@ do
|
||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
|
|
||||||
cd "$(git-root)" # cd for git blame
|
cd "$(git-root)" || exit # cd for git blame
|
||||||
MERGED_LOG=$(git_extra_mktemp)
|
MERGED_LOG=$(git_extra_mktemp)
|
||||||
if [[ $EMAIL == '-e' ]]
|
if [[ $EMAIL == '-e' ]]
|
||||||
then
|
then
|
||||||
|
|
@ -44,11 +44,13 @@ for file in $(git diff --name-only "$@")
|
||||||
do
|
do
|
||||||
test -n "$DEBUG" && echo "git blame $file"
|
test -n "$DEBUG" && echo "git blame $file"
|
||||||
# $1 - since $2 - until
|
# $1 - since $2 - until
|
||||||
git blame $NOT_WHITESPACE --line-porcelain "$1" -- "$file" 2> /dev/null |
|
# shellcheck disable=SC2086
|
||||||
sed -n "$PATTERN" | sort | uniq -c | sed 's/^\(.\)/- \1/' >> $MERGED_LOG
|
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"
|
# if $2 not given, use current commit as "until"
|
||||||
|
# shellcheck disable=SC2086
|
||||||
git blame $NOT_WHITESPACE --line-porcelain "${2-@}" -- "$file" 2> /dev/null |
|
git blame $NOT_WHITESPACE --line-porcelain "${2-@}" -- "$file" 2> /dev/null |
|
||||||
sed -n "$PATTERN" | sort | uniq -c | sed 's/^\(.\)/+ \1/' >> $MERGED_LOG
|
LC_ALL=C sed -n "$PATTERN" | sort | uniq -c | LC_ALL=C sed 's/^\(.\)/+ \1/' >> "$MERGED_LOG"
|
||||||
done
|
done
|
||||||
|
|
||||||
DEBUG="$DEBUG" awk '
|
DEBUG="$DEBUG" awk '
|
||||||
|
|
@ -71,20 +73,15 @@ END {
|
||||||
printf("%d %s\n", contributors[people], people)
|
printf("%d %s\n", contributors[people], people)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}' $MERGED_LOG | sort -nr | # only gawk supports built-in sort function
|
}' "$MERGED_LOG" | sort -nr | # only gawk supports built-in sort function
|
||||||
while read line
|
while read -r line
|
||||||
do
|
do
|
||||||
people=${line#* }
|
people=${line#* }
|
||||||
num=${line%% *}
|
num=${line%% *}
|
||||||
|
|
||||||
if [[ $num -gt 0 ]]
|
if [[ $num -gt 0 ]]
|
||||||
then
|
then
|
||||||
if [[ ${#people} -ge 29 ]]
|
printf "%-29s \033[00;32m" "$people"
|
||||||
then
|
|
||||||
printf "%-29s \033[00;32m" "$people"
|
|
||||||
else
|
|
||||||
printf "%-29s\t\033[00;32m" "$people"
|
|
||||||
fi
|
|
||||||
if [[ $num -ge 50 ]]
|
if [[ $num -ge 50 ]]
|
||||||
then
|
then
|
||||||
len=${#num}
|
len=${#num}
|
||||||
|
|
@ -92,7 +89,7 @@ do
|
||||||
do
|
do
|
||||||
printf "+"
|
printf "+"
|
||||||
done
|
done
|
||||||
printf "(%s)" $num
|
printf "(%s)" "$num"
|
||||||
else
|
else
|
||||||
for (( i = 0; i < num; i++ ))
|
for (( i = 0; i < num; i++ ))
|
||||||
do
|
do
|
||||||
|
|
@ -100,12 +97,7 @@ do
|
||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
if [[ ${#people} -ge 29 ]]
|
printf "%-29s \033[00;31m" "$people"
|
||||||
then
|
|
||||||
printf "%-29s \033[00;31m" "$people"
|
|
||||||
else
|
|
||||||
printf "%-29s\t\033[00;31m" "$people"
|
|
||||||
fi
|
|
||||||
if [[ $num -le -50 ]]
|
if [[ $num -le -50 ]]
|
||||||
then
|
then
|
||||||
len=${#num}
|
len=${#num}
|
||||||
|
|
@ -113,7 +105,7 @@ do
|
||||||
do
|
do
|
||||||
printf "-"
|
printf "-"
|
||||||
done
|
done
|
||||||
printf "(%s)" $num
|
printf "(%s)" "$num"
|
||||||
else
|
else
|
||||||
for (( i = 0; i > num; i-- ))
|
for (( i = 0; i > num; i-- ))
|
||||||
do
|
do
|
||||||
|
|
|
||||||
277
bin/git-ignore
277
bin/git-ignore
|
|
@ -1,6 +1,28 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
function show_contents {
|
GIT_DIR=$(git rev-parse --git-dir 2>/dev/null)
|
||||||
|
|
||||||
|
# awk script that takes 2 input files. The first one is newline deilmeted
|
||||||
|
# string of patterns to look for, second one is the file to remove them from.
|
||||||
|
# outfile has to be passed with -v upon call and is the file result is
|
||||||
|
# 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}"
|
local file="${2/#~/$HOME}"
|
||||||
if [ -f "$file" ]; then
|
if [ -f "$file" ]; then
|
||||||
echo "$1 gitignore: $2" && cat "$file"
|
echo "$1 gitignore: $2" && cat "$file"
|
||||||
|
|
@ -9,50 +31,255 @@ function show_contents {
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
function show_global {
|
cd_to_git_root() {
|
||||||
show_contents Global `git config --global core.excludesfile`
|
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
|
||||||
}
|
}
|
||||||
|
|
||||||
function add_global {
|
global_ignore() {
|
||||||
add_patterns `git config --global core.excludesfile` "$@"
|
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
|
||||||
}
|
}
|
||||||
|
|
||||||
function show_local {
|
show_global() {
|
||||||
cd "$(git root)"
|
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
|
show_contents Local .gitignore
|
||||||
}
|
}
|
||||||
|
|
||||||
function add_local {
|
add_local() {
|
||||||
cd "$(git root)"
|
cd_to_git_root --warn
|
||||||
add_patterns .gitignore "$@"
|
add_patterns .gitignore "$@"
|
||||||
}
|
}
|
||||||
|
|
||||||
function add_patterns {
|
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"
|
echo "Adding pattern(s) to: $1"
|
||||||
local file="${1/#~/$HOME}"
|
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
|
for pattern in "${@:2}"; do
|
||||||
echo "... adding '$pattern'"
|
echo "... adding '$pattern'"
|
||||||
(test -f "$file" && test "$pattern" && grep -q -- "$pattern" "$file") || echo "$pattern" >> "$file"
|
(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
|
if test $# -eq 0; then
|
||||||
show_global
|
show_global
|
||||||
echo "---------------------------------"
|
echo "---------------------------------"
|
||||||
show_local
|
show_local
|
||||||
|
echo "---------------------------------"
|
||||||
|
show_private
|
||||||
else
|
else
|
||||||
case "$1" in
|
# parse flags and args
|
||||||
-l|--local)
|
level="none"
|
||||||
test $# -gt 1 && add_local "${@:2}" && echo
|
action="none"
|
||||||
show_local
|
declare -a args
|
||||||
;;
|
while [ $# -gt 0 ]; do
|
||||||
-g|--global)
|
case "$1" in
|
||||||
test $# -gt 1 && add_global "${@:2}" && echo
|
-h|--help)
|
||||||
show_global
|
_usage
|
||||||
;;
|
exit 0
|
||||||
*)
|
;;
|
||||||
add_local "$@"
|
-l|--local)
|
||||||
;;
|
level="local"
|
||||||
esac
|
;;
|
||||||
fi
|
-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
|
||||||
|
|
|
||||||
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
|
||||||
60
bin/git-info
60
bin/git-info
|
|
@ -1,11 +1,40 @@
|
||||||
#!/usr/bin/env bash
|
#!/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() {
|
get_config() {
|
||||||
git config --list
|
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() {
|
most_recent_commit() {
|
||||||
git log --max-count=1 --pretty=short
|
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() {
|
local_branches() {
|
||||||
|
|
@ -21,27 +50,30 @@ remote_urls() {
|
||||||
}
|
}
|
||||||
|
|
||||||
echon() {
|
echon() {
|
||||||
echo "$@"
|
echo "$@"
|
||||||
echo
|
echo
|
||||||
}
|
}
|
||||||
|
|
||||||
# Show info similar to svn
|
|
||||||
|
|
||||||
echo
|
echo
|
||||||
echon "## Remote URLs:"
|
echon "${COLOR_TITLE}## Remote URLs:${NORMAL}"
|
||||||
echon "$(remote_urls)"
|
echon "$(remote_urls)"
|
||||||
|
|
||||||
echon "## Remote Branches:"
|
echon "${COLOR_TITLE}## Remote Branches:${NORMAL}"
|
||||||
echon "$(remote_branches)"
|
echon "$(remote_branches)"
|
||||||
|
|
||||||
echon "## Local Branches:"
|
echon "${COLOR_TITLE}## Local Branches:${NORMAL}"
|
||||||
echon "$(local_branches)"
|
echon "$(local_branches)"
|
||||||
|
|
||||||
echon "## Most Recent Commit:"
|
SUBMODULES_LOG=$(submodules)
|
||||||
echon "$(most_recent_commit)"
|
if [ -n "$SUBMODULES_LOG" ]; then
|
||||||
echon "Type 'git log' for more commits, or 'git show <commit id>' for full commit details."
|
echon "${COLOR_TITLE}## Submodule(s):${NORMAL}"
|
||||||
|
echon "$SUBMODULES_LOG"
|
||||||
|
fi
|
||||||
|
|
||||||
if test "$1" != "--no-config"; then
|
echon "${COLOR_TITLE}## Most Recent Commit:${NORMAL}"
|
||||||
echon "## Configuration (.git/config):"
|
echon "$(most_recent_commit)"
|
||||||
|
|
||||||
|
if [ -n "$HIDE_CONFIG" ]; then
|
||||||
|
echon "${COLOR_TITLE}## Configuration (.git/config):${NORMAL}"
|
||||||
echon "$(get_config)"
|
echon "$(get_config)"
|
||||||
fi
|
fi
|
||||||
|
|
|
||||||
|
|
@ -1,60 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
project=${PWD##*/}
|
|
||||||
|
|
||||||
#
|
|
||||||
# list the last modified author for each line
|
|
||||||
#
|
|
||||||
function single_file {
|
|
||||||
while read data
|
|
||||||
do
|
|
||||||
if [[ $(file $data) = *text* ]]; then #
|
|
||||||
git blame --line-porcelain $data | sed -n 's/^author //p';
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
}
|
|
||||||
|
|
||||||
#
|
|
||||||
# list the author for all file
|
|
||||||
#
|
|
||||||
function lines {
|
|
||||||
git ls-files | single_file
|
|
||||||
}
|
|
||||||
|
|
||||||
#
|
|
||||||
# count the line count
|
|
||||||
#
|
|
||||||
function count {
|
|
||||||
lines | wc -l
|
|
||||||
}
|
|
||||||
|
|
||||||
#
|
|
||||||
# sort by author modified lines
|
|
||||||
#
|
|
||||||
function authors {
|
|
||||||
lines | sort | uniq -c | sort -rn
|
|
||||||
}
|
|
||||||
|
|
||||||
#
|
|
||||||
# list as percentage for author modified lines
|
|
||||||
#
|
|
||||||
function result {
|
|
||||||
authors | awk '
|
|
||||||
{ args[NR] = $0; sum += $0 }
|
|
||||||
END {
|
|
||||||
printf " %-9s: %i\n", "lines", sum
|
|
||||||
printf " %-9s: \n", "authors"
|
|
||||||
for (i = 1; i <= NR; ++i) {
|
|
||||||
printf " %-30s %2.1f%%\n", args[i], 100 * args[i] / sum
|
|
||||||
}
|
|
||||||
}
|
|
||||||
'
|
|
||||||
}
|
|
||||||
|
|
||||||
# summary
|
|
||||||
|
|
||||||
echo
|
|
||||||
echo " project : $project"
|
|
||||||
result
|
|
||||||
echo
|
|
||||||
|
|
||||||
|
|
@ -1,6 +1,3 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
ref=$(git symbolic-ref HEAD)
|
git log "@{upstream}..@" "$@"
|
||||||
branch=${ref#refs/heads/}
|
|
||||||
|
|
||||||
git log origin/${branch}..${branch} $*
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
filename=$1
|
filename="$1"
|
||||||
test -z $filename && echo "filename required." 1>&2 && exit 1
|
test -z "$filename" && echo "filename required." 1>&2 && exit 1
|
||||||
git update-index --skip-worktree $filename
|
git update-index --skip-worktree "$filename"
|
||||||
|
|
|
||||||
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
|
||||||
|
|
@ -1,15 +1,24 @@
|
||||||
#!/bin/bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
current_branch() {
|
current_branch() {
|
||||||
git rev-parse --abbrev-ref HEAD
|
git rev-parse --abbrev-ref HEAD
|
||||||
}
|
}
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
echo "Usage: git merge-into [src] dest [--ff]"
|
echo "Usage: git merge-into [src] dest [--ff-only]"
|
||||||
}
|
}
|
||||||
|
|
||||||
cur_branch=$(current_branch)
|
cur_branch=$(current_branch)
|
||||||
if [ "${!#}" == '--ff' ]; then
|
|
||||||
|
stashed=0
|
||||||
|
if [ -n "$(git status --porcelain)" ];
|
||||||
|
then
|
||||||
|
echo "Local modifications detected, stashing"
|
||||||
|
stashed=1
|
||||||
|
git stash
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "${!#}" = '--ff-only' ]; then
|
||||||
case $# in
|
case $# in
|
||||||
2 ) # dest --ff
|
2 ) # dest --ff
|
||||||
git push "$(git rev-parse --show-toplevel)" "$cur_branch":"$1";;
|
git push "$(git rev-parse --show-toplevel)" "$cur_branch":"$1";;
|
||||||
|
|
@ -32,3 +41,8 @@ else
|
||||||
usage
|
usage
|
||||||
esac
|
esac
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if [ $stashed -eq 1 ];
|
||||||
|
then
|
||||||
|
git stash pop;
|
||||||
|
fi
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ branch=$2
|
||||||
prefix=$3
|
prefix=$3
|
||||||
flat=0
|
flat=0
|
||||||
|
|
||||||
if test $prefix = "."; then
|
if test "$prefix" = "."; then
|
||||||
prefix=$(mktemp -u 'git-merge-repo.XXXXXXX')
|
prefix=$(mktemp -u 'git-merge-repo.XXXXXXX')
|
||||||
flat=1
|
flat=1
|
||||||
fi
|
fi
|
||||||
|
|
@ -16,10 +16,10 @@ message=$(git log -1 --pretty=%B)
|
||||||
|
|
||||||
if test $flat -eq 1; then
|
if test $flat -eq 1; then
|
||||||
git stash -u
|
git stash -u
|
||||||
mv -i $prefix/* ./
|
mv -i "$prefix"/* ./
|
||||||
git undo
|
git undo
|
||||||
git add .
|
git add .
|
||||||
git commit -am "$message"
|
git commit -am "$message"
|
||||||
git stash apply
|
git stash apply
|
||||||
rm -drf $prefix
|
rm -drf "$prefix"
|
||||||
fi
|
fi
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,52 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
# grab first two non-option arguments
|
usage() {
|
||||||
for arg in $*; do
|
echo 1>&2 "usage: git missing [<first branch>] <second branch> [<git log options>] [[--] <path>...]"
|
||||||
if [ -n "${arg}" ]; then
|
}
|
||||||
test -z "$firstbranch" && firstbranch="${arg}" && continue
|
|
||||||
test -z "$secondbranch" && secondbranch="${arg}" && continue
|
|
||||||
else
|
|
||||||
# add anything else to a passthrough
|
|
||||||
passthrough="$passthrough $arg"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
test -z "$firstbranch" && echo "at least one branch required" && exit 1
|
if [ "${#}" -lt 1 ]
|
||||||
|
then
|
||||||
if test -z "$secondbranch"; then
|
usage
|
||||||
secondbranch=$firstbranch
|
exit 1
|
||||||
firstbranch=
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
git log $passthrough $firstbranch...$secondbranch --format="%m %h %s" --left-right
|
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"
|
||||||
|
|
@ -1,4 +1,23 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
file="$1"
|
|
||||||
|
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
|
test -z "$file" && echo "file required." 1>&2 && exit 1
|
||||||
git filter-branch -f --index-filter "git rm -r --cached '$file' --ignore-unmatch" --prune-empty --tag-name-filter cat -- --all
|
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
|
||||||
76
bin/git-pr
76
bin/git-pr
|
|
@ -1,11 +1,77 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# Based on https://gist.github.com/gnarf/5406589
|
# 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
|
if test "$1" = "clean"; then
|
||||||
git for-each-ref refs/heads/pr/* --format='%(refname)' | while read ref; do
|
git for-each-ref refs/heads/pr/* --format='%(refname)' | while read -r ref; do
|
||||||
git branch -D ${ref#refs/heads/}
|
git branch -D "${ref#refs/heads/}"
|
||||||
done
|
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
|
else
|
||||||
test -z $1 && echo "pr number required." 1>&2 && exit 1
|
rc=1
|
||||||
git fetch -fu ${2:-origin} refs/pull/$1/head:pr/$1 && git checkout pr/$1
|
while [ "$1" != "" ]; do
|
||||||
|
pull_pr_if_matched "$1"
|
||||||
|
rc=$?
|
||||||
|
shift
|
||||||
|
done
|
||||||
|
|
||||||
|
exit "$rc"
|
||||||
fi
|
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
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
#
|
#
|
||||||
|
|
||||||
abort() {
|
abort() {
|
||||||
echo $@
|
echo >&2 "$@"
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -14,51 +14,72 @@ abort() {
|
||||||
#
|
#
|
||||||
|
|
||||||
json() {
|
json() {
|
||||||
|
local title="${1//\"/\\\"}"
|
||||||
|
local body="${2//\"/\\\"}"
|
||||||
|
local head="${3//\"/\\\"}"
|
||||||
|
local base="${4//\"/\\\"}"
|
||||||
cat <<EOF
|
cat <<EOF
|
||||||
{
|
{
|
||||||
"title": "$1",
|
"title": "$title",
|
||||||
"body": "$2",
|
"body": "$body",
|
||||||
"head": "$3",
|
"head": "$head",
|
||||||
"base": "$4"
|
"base": "$base"
|
||||||
}
|
}
|
||||||
EOF
|
EOF
|
||||||
}
|
}
|
||||||
|
|
||||||
# user
|
# personal access token
|
||||||
|
# config name is github-personal-access-token '_' is not allowed in git config
|
||||||
|
|
||||||
user=$(git config --global user.email)
|
github_personal_access_token=$(git config --default "$GITHUB_TOKEN" git-extras.github-personal-access-token)
|
||||||
test -z "$user" && abort "git config user.email required"
|
|
||||||
|
test -z "$github_personal_access_token" && abort "GITHUB_TOKEN or git config git-extras.github-personal-access-token required"
|
||||||
|
|
||||||
# branch
|
# branch
|
||||||
|
|
||||||
branch=${1-$(git symbolic-ref HEAD | sed 's/refs\/heads\///')}
|
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
|
# make sure it's pushed
|
||||||
|
|
||||||
git push origin $branch || abort "failed to push $branch"
|
git push "$remote" "$branch" || abort "failed to push $branch"
|
||||||
|
|
||||||
# lame hack to get project
|
remote_url=$(git config remote."$remote".url)
|
||||||
|
if [[ "$remote_url" == git@* ]]; then
|
||||||
project=$(git config remote.origin.url | sed 's/^.*://' | sed 's/\.git$//')
|
project=${remote_url##*:}
|
||||||
|
else
|
||||||
|
project=${remote_url#https://*/}
|
||||||
|
fi
|
||||||
|
project=${project%.git}
|
||||||
|
|
||||||
# prompt
|
# prompt
|
||||||
|
|
||||||
echo
|
echo
|
||||||
echo " create pull-request for $project '$branch'"
|
echo " create pull-request for $project '$branch'"
|
||||||
echo
|
echo
|
||||||
printf " title: " && read title
|
printf " title: " && read -r title
|
||||||
printf " body: " && read body
|
printf " body: " && read -r body
|
||||||
printf " base [master]: " && read base
|
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
|
echo
|
||||||
|
|
||||||
# create pull request
|
# create pull request
|
||||||
|
|
||||||
if [ -z "$base" ]
|
if [ -z "$base" ]
|
||||||
then
|
then
|
||||||
base="master"
|
base="$(git_extra_default_branch)"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
body=$(json "$title" "$body" $branch "$base")
|
body=$(json "$title" "$body" "$branch" "$base")
|
||||||
|
|
||||||
curl -u "$user" "https://api.github.com/repos/$project/pulls" -d "$body"
|
|
||||||
|
|
||||||
|
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
|
||||||
|
|
@ -17,26 +17,26 @@ fi
|
||||||
# Use a temporary index.
|
# Use a temporary index.
|
||||||
index=$(git_extra_mktemp)
|
index=$(git_extra_mktemp)
|
||||||
cleanup() {
|
cleanup() {
|
||||||
rm $index
|
rm "$index"
|
||||||
exit 2
|
exit 2
|
||||||
}
|
}
|
||||||
trap cleanup 2
|
trap cleanup INT
|
||||||
|
|
||||||
# Go back in history while parent commits are available.
|
# Go back in history while parent commits are available.
|
||||||
echo "Trying to find a commit the patch applies to..."
|
echo "Trying to find a commit the patch applies to..."
|
||||||
rev=$(git rev-parse HEAD)
|
rev=$(git rev-parse HEAD)
|
||||||
while [ $? = 0 ]
|
while [ $? = 0 ]
|
||||||
do
|
do
|
||||||
GIT_INDEX_FILE=$index git read-tree $rev
|
GIT_INDEX_FILE=$index git read-tree "$rev"
|
||||||
|
|
||||||
# Try to apply the patch.
|
# Try to apply the patch.
|
||||||
GIT_INDEX_FILE=$index git apply --cached $1 >/dev/null 2>&1
|
GIT_INDEX_FILE=$index git apply --cached "$1" &>/dev/null
|
||||||
patch_failed=$?
|
patch_failed=$?
|
||||||
|
|
||||||
# Do it again, but show the error, if the problem is the patch itself.
|
# Do it again, but show the error, if the problem is the patch itself.
|
||||||
if [ $patch_failed = 128 ]
|
if [ $patch_failed = 128 ]
|
||||||
then
|
then
|
||||||
GIT_INDEX_FILE=$index git apply --index --check $1
|
GIT_INDEX_FILE=$index git apply --index --check "$1"
|
||||||
exit $patch_failed
|
exit $patch_failed
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
@ -45,19 +45,19 @@ do
|
||||||
then
|
then
|
||||||
# Manufacture a commit.
|
# Manufacture a commit.
|
||||||
tree=$(GIT_INDEX_FILE=$index git write-tree)
|
tree=$(GIT_INDEX_FILE=$index git write-tree)
|
||||||
commit=$(git commit-tree $tree -p $rev -m "$1")
|
commit=$(git commit-tree "$tree" -p "$rev" -m "$1")
|
||||||
rm $index
|
rm "$index"
|
||||||
|
|
||||||
echo "Patch applied to $(git rev-parse --short $rev) as $(git rev-parse --short $commit)"
|
echo "Patch applied to $(git rev-parse --short "$rev") as $(git rev-parse --short "$commit")"
|
||||||
|
|
||||||
git cherry-pick $commit
|
git cherry-pick "$commit"
|
||||||
exit $?
|
exit $?
|
||||||
fi
|
fi
|
||||||
|
|
||||||
rev=$(git rev-parse --verify -q $rev^)
|
rev=$(git rev-parse --verify -q "$rev^")
|
||||||
done
|
done
|
||||||
|
|
||||||
# No compatible commit found. Restore.
|
# No compatible commit found. Restore.
|
||||||
echo "Failed to find a commit the patch applies to."
|
echo "Failed to find a commit the patch applies to."
|
||||||
rm $index
|
rm "$index"
|
||||||
exit 1
|
exit 1
|
||||||
|
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
# Removed unnecessary git checkout for bug, feature and refactor
|
|
||||||
|
|
||||||
if test "$1" = "finish"; then
|
|
||||||
test -z $2 && echo "refactor <name> required." 1>&2 && exit 1
|
|
||||||
branch=refactor/$2
|
|
||||||
git merge --no-ff $branch && git delete-branch $branch
|
|
||||||
else
|
|
||||||
test -z $1 && echo "refactor <name> required." 1>&2 && exit 1
|
|
||||||
if test -n "$2"; then
|
|
||||||
echo "too many arguments; if not finishing a refactor, only <name> of new refactoring is expected" && exit 1
|
|
||||||
fi
|
|
||||||
branch=refactor/$1
|
|
||||||
git checkout -b $branch &> /dev/null
|
|
||||||
test -n $? && git checkout $branch &> /dev/null
|
|
||||||
fi
|
|
||||||
127
bin/git-release
127
bin/git-release
|
|
@ -1,48 +1,137 @@
|
||||||
#!/usr/bin/env sh
|
#!/usr/bin/env bash
|
||||||
|
set -e
|
||||||
|
|
||||||
hook() {
|
hook() {
|
||||||
local hook=.git/hooks/$1.sh
|
local hook=.git/hooks/$1.sh
|
||||||
# compat without extname
|
# compat without extname
|
||||||
if test ! -f $hook; then
|
if test ! -f "$hook"; then
|
||||||
hook=.git/hooks/$1
|
hook=.git/hooks/$1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if test -f $hook; then
|
if test -f "$hook"; then
|
||||||
echo "... $1"
|
echo "... $1"
|
||||||
if test -x $hook; then
|
shift
|
||||||
$hook
|
if test -x "$hook"; then
|
||||||
|
$hook "$@"
|
||||||
else
|
else
|
||||||
. $hook
|
. "$hook" "$@"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
exit_with_msg() {
|
||||||
|
>&2 echo "$1"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
if test $# -gt 0; then
|
if test $# -gt 0; then
|
||||||
version=$1
|
|
||||||
shift #remove version from arguments list
|
|
||||||
remote=''
|
remote=''
|
||||||
hook pre-release
|
|
||||||
echo "... releasing $version"
|
|
||||||
# check for flags
|
# check for flags
|
||||||
while test $# != 0
|
while test $# != 0
|
||||||
do
|
do
|
||||||
case "$1" in
|
case "$1" in
|
||||||
-c) git-changelog -t "$version" ;;
|
-c) need_changelog=true;;
|
||||||
-r) remote=$2; shift ;;
|
-r) remote=$2; shift ;;
|
||||||
-m) msg=$2; shift ;;
|
-m) msg=$2; shift ;;
|
||||||
--) shift; break;;
|
-s)
|
||||||
*) usage ;;
|
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
|
esac
|
||||||
|
|
||||||
shift
|
shift
|
||||||
|
|
||||||
done
|
done
|
||||||
if [ -z "$msg" ]; then
|
|
||||||
msg="Release ${version}"
|
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
|
fi
|
||||||
git commit -a -m "$msg"
|
|
||||||
git tag $version -a -m "$msg" \
|
hook_args="$version"
|
||||||
&& git push $remote \
|
|
||||||
|
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 --tags \
|
||||||
&& hook post-release \
|
&& git push $remote \
|
||||||
|
&& hook post-release $hook_args \
|
||||||
&& echo "... complete"
|
&& echo "... complete"
|
||||||
else
|
else
|
||||||
echo "tag required" 1>&2 && exit 1
|
echo "tag required" 1>&2 && exit 1
|
||||||
|
|
|
||||||
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
|
||||||
|
|
@ -3,10 +3,10 @@
|
||||||
old=$1
|
old=$1
|
||||||
new=$2
|
new=$2
|
||||||
|
|
||||||
test -z $old && echo "old tag name required." 1>&2 && exit 1
|
test -z "$old" && echo "old tag name required." 1>&2 && exit 1
|
||||||
test -z $new && echo "new 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 "$new" "$old"
|
||||||
git tag -d $old
|
git tag -d "$old"
|
||||||
git push origin $new
|
git push origin "$new"
|
||||||
git push origin :refs/tags/$old
|
git push origin ":refs/tags/$old"
|
||||||
|
|
|
||||||
82
bin/git-repl
82
bin/git-repl
|
|
@ -1,43 +1,99 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
shopt -s extglob
|
||||||
|
|
||||||
git version
|
git version
|
||||||
echo "git-extras version ""$(git-extras -v)"
|
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"
|
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
|
||||||
# Current branch
|
# Current branch
|
||||||
cur=`git symbolic-ref HEAD 2> /dev/null | cut -d/ -f3-`
|
cur=$(git symbolic-ref HEAD 2> /dev/null | cut -d/ -f3-)
|
||||||
|
|
||||||
# Prompt
|
# Prompt
|
||||||
if test -n "$cur"; then
|
if test -n "$cur"; then
|
||||||
prompt="git ($cur)> "
|
cur_string=" ($cur)"
|
||||||
else
|
else
|
||||||
prompt="git> "
|
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)
|
||||||
|
|
||||||
|
prefix=$(git config --get --default 'git' git-extras.repl.prefix)
|
||||||
|
|
||||||
|
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
|
fi
|
||||||
|
|
||||||
# Readline
|
prompt_base="$prefix$project_name$cur_string$es_string$prompt_character"
|
||||||
read -e -r -p "$prompt" cmd
|
prompt_base_stripped=$(echo "$prompt_base" | awk '{$1=$1};1')
|
||||||
|
prompt="$prompt_base_stripped "
|
||||||
|
|
||||||
# EOF
|
# Use arguments as a command if any are provided.
|
||||||
test $? -ne 0 && break
|
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
|
||||||
|
|
||||||
# History
|
# Add command to history if it is not all whitespace
|
||||||
history -s "$cmd"
|
if [[ ! "$cmd" =~ ^[[:space:]]*$ ]]; then
|
||||||
|
history -s "$cmd"
|
||||||
|
fi
|
||||||
|
|
||||||
# Built-in commands
|
# Built-in commands
|
||||||
case $cmd in
|
case $cmd in
|
||||||
ls) cmd=ls-files;;
|
ls) cmd=ls-files;;
|
||||||
"") continue;;
|
"")
|
||||||
quit) 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
|
||||||
|
|
||||||
|
history -a
|
||||||
|
|
||||||
if [[ $cmd == !* ]]; then
|
if [[ $cmd == !* ]]; then
|
||||||
eval ${cmd:1}
|
# shellcheck disable=SC2086
|
||||||
|
eval ${cmd:1}
|
||||||
elif [[ $cmd == git* ]]; then
|
elif [[ $cmd == git* ]]; then
|
||||||
|
# shellcheck disable=SC2086
|
||||||
eval $cmd
|
eval $cmd
|
||||||
else
|
else
|
||||||
eval git "$cmd"
|
eval git "$cmd"
|
||||||
fi
|
fi
|
||||||
|
exit_status=$?
|
||||||
done
|
done
|
||||||
|
|
||||||
echo
|
echo
|
||||||
|
|
|
||||||
|
|
@ -4,14 +4,14 @@ file="$1"
|
||||||
commit="$2"
|
commit="$2"
|
||||||
|
|
||||||
if [[ -f $file ]]; then
|
if [[ -f $file ]]; then
|
||||||
git rm --cached -q -f -- $file
|
git rm --cached -q -f -- "$file"
|
||||||
if [[ -z $commit ]]; then
|
if [[ -z $commit ]]; then
|
||||||
git checkout HEAD -- $file
|
git checkout HEAD -- "$file"
|
||||||
echo "Reset '$1' to HEAD"
|
echo "Reset '$1' to HEAD"
|
||||||
else
|
else
|
||||||
git checkout $commit -- $file
|
git checkout "$commit" -- "$file"
|
||||||
echo "Reset '$1' to $commit"
|
echo "Reset '$1' to $commit"
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
echo "File '$1' not found in `pwd`"
|
echo "File '$1' not found in $PWD"
|
||||||
fi
|
fi
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,13 @@ git_root() {
|
||||||
|
|
||||||
# get the relative path of current path according to root of repo
|
# get the relative path of current path according to root of repo
|
||||||
git_root_relative() {
|
git_root_relative() {
|
||||||
git rev-parse --show-prefix
|
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
|
if test $# -eq 0; then
|
||||||
|
|
|
||||||
181
bin/git-scp
181
bin/git-scp
|
|
@ -1,10 +1,10 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
COLOR_RED() { echo -en "\033[31m"; }
|
COLOR_RED() { test -t 1 && echo -n "$(tput setaf 1)"; }
|
||||||
COLOR_GREEN() { echo -en "\033[32m"; }
|
COLOR_GREEN() { test -t 1 && echo -n "$(tput setaf 2)"; }
|
||||||
COLOR_YELLOW(){ echo -en "\033[33m"; }
|
COLOR_YELLOW(){ test -t 1 && echo -n "$(tput setaf 3)"; }
|
||||||
COLOR_BLUE() { echo -en "\033[34m"; }
|
COLOR_BLUE() { test -t 1 && echo -n "$(tput setaf 4)"; }
|
||||||
COLOR_RESET() { echo -en "\033[0m"; }
|
COLOR_RESET() { test -t 1 && echo -n "$(tput sgr 0)"; }
|
||||||
|
|
||||||
function _test_git_scp()
|
function _test_git_scp()
|
||||||
{
|
{
|
||||||
|
|
@ -18,7 +18,7 @@ function _test_git_scp()
|
||||||
function set_remote()
|
function set_remote()
|
||||||
{
|
{
|
||||||
remote=$1
|
remote=$1
|
||||||
if [ $(git remote | grep -c -- ^$remote$) -eq 0 ]
|
if [ "$(git remote | grep -c -- "^$remote$")" -eq 0 ]
|
||||||
then
|
then
|
||||||
COLOR_RED
|
COLOR_RED
|
||||||
echo "Remote $remote does not exist in your git config"
|
echo "Remote $remote does not exist in your git config"
|
||||||
|
|
@ -39,8 +39,9 @@ function php_lint()
|
||||||
test ! -f "$i" && continue
|
test ! -f "$i" && continue
|
||||||
case "$i" in
|
case "$i" in
|
||||||
*\.php|*\.phtml)
|
*\.php|*\.phtml)
|
||||||
php -l "$i" > /dev/null
|
if ! php -l "$i" > /dev/null; then
|
||||||
[ $? -gt 0 ] && error_count[${#error_count[@]}]="$i"
|
error_count[${#error_count[@]}]="$i"
|
||||||
|
fi
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
|
|
@ -58,17 +59,17 @@ function php_lint()
|
||||||
|
|
||||||
function _dos2unix()
|
function _dos2unix()
|
||||||
{
|
{
|
||||||
command -v dos2unix > /dev/null && dos2unix $@
|
command -v dos2unix > /dev/null && dos2unix "$@"
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
function _sanitize()
|
function _sanitize()
|
||||||
{
|
{
|
||||||
git config --get-all extras.scp.sanitize | while read i
|
git config --get-all extras.scp.sanitize | while read -r i
|
||||||
do
|
do
|
||||||
case $i in
|
case $i in
|
||||||
php_lint) php_lint $@;; # git config --global --add extras.scp.sanitize php_lint
|
php_lint) php_lint "$@";; # git config --global --add extras.scp.sanitize php_lint
|
||||||
dos2unix) _dos2unix $@;; # git config --global --add extras.scp.sanitize dos2unix
|
dos2unix) _dos2unix "$@";; # git config --global --add extras.scp.sanitize dos2unix
|
||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
return $?
|
return $?
|
||||||
|
|
@ -76,14 +77,26 @@ function _sanitize()
|
||||||
|
|
||||||
function scp_and_stage
|
function scp_and_stage
|
||||||
{
|
{
|
||||||
set_remote $1
|
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
|
shift
|
||||||
|
|
||||||
local refhead="$(git rev-parse --quiet --verify $1)"
|
local refhead
|
||||||
|
refhead="$(git rev-parse --quiet --verify "$1")"
|
||||||
if [ -n "$refhead" ]
|
if [ -n "$refhead" ]
|
||||||
then
|
then
|
||||||
shift
|
shift
|
||||||
[ $(git branch --contains "$refhead" | grep -c '\*') -eq 0 ] &&
|
[ "$(git branch --contains "$refhead" | grep -c '\*')" -eq 0 ] &&
|
||||||
_error "refhead provided is not part of current branch"
|
_error "refhead provided is not part of current branch"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
@ -92,46 +105,117 @@ function scp_and_stage
|
||||||
list=$(git ls-files "$@")" "$(git ls-files -o "$@")
|
list=$(git ls-files "$@")" "$(git ls-files -o "$@")
|
||||||
elif [ -n "$refhead" ]
|
elif [ -n "$refhead" ]
|
||||||
then
|
then
|
||||||
git diff --stat $refhead
|
[ "$verbose" -eq 1 ] && git --no-pager diff --stat "$refhead"
|
||||||
list=$(git diff $refhead --name-only)
|
list=$(git diff "$refhead" --name-only)
|
||||||
else
|
else
|
||||||
git diff
|
[ "$verbose" -eq 1 ] && git --no-pager diff
|
||||||
list=$(git diff --name-only)
|
list=$(git diff --name-only)
|
||||||
fi
|
fi
|
||||||
|
|
||||||
deleted=$(for i in $list; do [ -f $i ] || echo $i; done)
|
deleted=$(for i in $list; do [ -f "$i" ] || echo "$i"; done)
|
||||||
list=$(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" ]
|
if [ -n "$list" ]
|
||||||
then
|
then
|
||||||
local _TMP=${0///}
|
local _TMP=${0///}
|
||||||
echo "$list" > $_TMP &&
|
# shellcheck disable=SC2086
|
||||||
_sanitize $list &&
|
echo "$list" > "$_TMP"
|
||||||
_info Pushing to $remote \($(git config remote.$remote.url)\) &&
|
if [ "$dry_run" -eq 1 ] || _sanitize $list
|
||||||
rsync -rlDv --files-from=$_TMP ./ "$(git config remote.$remote.url)/" &&
|
then
|
||||||
git add --force $list &&
|
_info "Pushing to $remote ($(git config "remote.$remote.url"))"
|
||||||
rm $_TMP
|
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
|
fi
|
||||||
|
|
||||||
deleted=$(for i in $deleted; do echo $(git config remote.$remote.url | cut -d: -f2)/$i; done)
|
deleted=$(for i in $deleted; do echo "$(git config "remote.$remote.url" | cut -d: -f2)/$i"; done)
|
||||||
|
|
||||||
[ -n "$deleted" ] &&
|
if [ -n "$deleted" ]
|
||||||
COLOR_RED &&
|
then
|
||||||
echo Deleted remote files &&
|
COLOR_RED
|
||||||
ssh $(git config remote.$remote.url | cut -d: -f1) -t "rm $deleted" &&
|
echo Deleted remote files
|
||||||
echo "$deleted"
|
if [ "$dry_run" -eq 1 ]
|
||||||
COLOR_RESET
|
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()
|
function reverse_scp()
|
||||||
{
|
{
|
||||||
set_remote $1
|
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
|
shift
|
||||||
|
|
||||||
local _TMP=${0///}
|
local _TMP=${0///}
|
||||||
echo $@ > $_TMP &&
|
echo "$@" > "$_TMP"
|
||||||
rsync -rlDv --files-from=$_TMP "$(git config remote.$remote.url)/" ./ &&
|
|
||||||
rm $_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()
|
function _info()
|
||||||
|
|
@ -145,13 +229,18 @@ function _usage()
|
||||||
{
|
{
|
||||||
echo "Usage:
|
echo "Usage:
|
||||||
git scp -h|help|?
|
git scp -h|help|?
|
||||||
git scp <remote> [ref|file..] # scp and stage your files to specified remote
|
git scp [options] <remote> [ref|file..] # scp and stage your files to specified remote
|
||||||
git scp <remote> [<ref>] # show diff relative to <ref> and upload unstaged files to <remote>
|
git scp [options] <remote> [<ref>] # show diff relative to <ref> and upload unstaged files to <remote>
|
||||||
git rscp <remote> [<file|directory>] # copy <remote> files to current working directory
|
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
|
case $1 in
|
||||||
-v|verbose|--verbose) grep -A100 '^#* OPTIONS #*$' $0;;
|
-v|verbose|--verbose) grep -A100 '^#* OPTIONS #*$' "$0" ;;
|
||||||
esac
|
esac
|
||||||
exit
|
exit
|
||||||
}
|
}
|
||||||
|
|
@ -161,18 +250,18 @@ function _error()
|
||||||
[ $# -eq 0 ] && _usage && exit 0
|
[ $# -eq 0 ] && _usage && exit 0
|
||||||
|
|
||||||
echo
|
echo
|
||||||
echo ERROR: "$@"
|
echo "ERROR: $*"
|
||||||
echo
|
echo
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
### OPTIONS ###
|
### OPTIONS ###
|
||||||
case $(basename $0) in
|
case $(basename "$0") in
|
||||||
git-scp)
|
git-scp)
|
||||||
case $1 in
|
case $1 in
|
||||||
''|-h|?|help|--help) shift; _test_git_scp; _usage $@;;
|
''|-h|'?'|help|--help) shift; _test_git_scp; _usage "$@";;
|
||||||
*) scp_and_stage $@;;
|
*) scp_and_stage "$@";;
|
||||||
esac
|
esac
|
||||||
;;
|
;;
|
||||||
git-rscp) reverse_scp $@;;
|
git-rscp) reverse_scp "$@";;
|
||||||
esac
|
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
|
||||||
|
|
@ -1,5 +1,12 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
COMMIT_MESSAGE='Initial commit'
|
||||||
|
|
||||||
|
if [ "$1" = "-m" ]; then
|
||||||
|
COMMIT_MESSAGE=$2
|
||||||
|
shift; shift
|
||||||
|
fi
|
||||||
|
|
||||||
gitdirexists(){
|
gitdirexists(){
|
||||||
if [ -d ".git" ]; then
|
if [ -d ".git" ]; then
|
||||||
echo ".git directory already exists, aborting"
|
echo ".git directory already exists, aborting"
|
||||||
|
|
@ -13,4 +20,4 @@ mkdir -p "$dir" \
|
||||||
&& gitdirexists \
|
&& gitdirexists \
|
||||||
&& git init \
|
&& git init \
|
||||||
&& git add . \
|
&& git add . \
|
||||||
&& git commit --allow-empty -m 'Initial commit'
|
&& git commit --allow-empty -m "$COMMIT_MESSAGE"
|
||||||
|
|
|
||||||
2
bin/git-show-merged-branches
Normal file → Executable file
2
bin/git-show-merged-branches
Normal file → Executable file
|
|
@ -1,3 +1,3 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
git branch --no-color --merged | grep -v "\*" | grep -v master | tr -d ' '
|
git branch --no-color --merged | grep -v "\*" | grep -v "$(git_extra_default_branch)" | tr -d ' '
|
||||||
|
|
|
||||||
2
bin/git-show-unmerged-branches
Normal file → Executable file
2
bin/git-show-unmerged-branches
Normal file → Executable file
|
|
@ -1,3 +1,3 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
git branch --no-color --no-merged | grep -v "\*" | grep -v master | tr -d ' '
|
git branch --no-color --no-merged | grep -v "\*" | grep -v "$(git_extra_default_branch)" | tr -d ' '
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,26 @@
|
||||||
#!/usr/bin/env bash
|
#!/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"
|
src="$1"
|
||||||
msg="$2"
|
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() {
|
is_branch() {
|
||||||
git show-ref --verify --quiet "refs/heads/$src"
|
git show-ref --verify --quiet "refs/heads/$src"
|
||||||
|
|
@ -12,7 +31,8 @@ is_commit_reference() {
|
||||||
}
|
}
|
||||||
|
|
||||||
is_on_current_branch() {
|
is_on_current_branch() {
|
||||||
local commit_sha=`git rev-parse "$src"`
|
local commit_sha
|
||||||
|
commit_sha=$(git rev-parse "$src")
|
||||||
git rev-list HEAD |
|
git rev-list HEAD |
|
||||||
grep -q -- "$commit_sha"
|
grep -q -- "$commit_sha"
|
||||||
}
|
}
|
||||||
|
|
@ -23,8 +43,8 @@ commit_if_msg_provided() {
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
prompt_continuation_if_squashing_master() {
|
prompt_continuation_if_squashing_default_branch() {
|
||||||
if [[ $src =~ ^master$ ]]; then
|
if [[ $src == $(git_extra_default_branch) ]]; then
|
||||||
read -p "Warning: squashing '$src'! Continue [y/N]? " -r
|
read -p "Warning: squashing '$src'! Continue [y/N]? " -r
|
||||||
if ! [[ $REPLY =~ ^[Yy]$ ]]; then
|
if ! [[ $REPLY =~ ^[Yy]$ ]]; then
|
||||||
echo "Exiting"
|
echo "Exiting"
|
||||||
|
|
@ -34,19 +54,26 @@ prompt_continuation_if_squashing_master() {
|
||||||
}
|
}
|
||||||
|
|
||||||
squash_branch() {
|
squash_branch() {
|
||||||
prompt_continuation_if_squashing_master
|
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
|
git merge --squash "$src" || exit 1 # quits if `git merge` fails
|
||||||
commit_if_msg_provided
|
commit_if_msg_provided
|
||||||
}
|
}
|
||||||
|
|
||||||
squash_current_branch() {
|
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
|
git reset --soft "$src" || exit 1 # quits if `git reset` fails
|
||||||
commit_if_msg_provided
|
commit_if_msg_provided
|
||||||
}
|
}
|
||||||
|
|
||||||
if `is_branch`; then
|
if is_branch; then
|
||||||
squash_branch
|
squash_branch
|
||||||
elif `is_commit_reference` && `is_on_current_branch`; then
|
elif is_commit_reference && is_on_current_branch; then
|
||||||
squash_current_branch
|
squash_current_branch
|
||||||
else
|
else
|
||||||
echo "Source branch or commit reference required." 1>&2 && exit 1
|
echo "Source branch or commit reference required." 1>&2 && exit 1
|
||||||
|
|
|
||||||
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
|
||||||
243
bin/git-summary
243
bin/git-summary
|
|
@ -1,27 +1,81 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
SUBDIRECTORY_OK=Yes
|
|
||||||
source "$(git --exec-path)/git-sh-setup"
|
|
||||||
cd_to_toplevel
|
|
||||||
|
|
||||||
commit=""
|
cd "$(git root)" || { echo "Can't cd to top level directory";exit 1; }
|
||||||
test $# -ne 0 && commit=$@
|
|
||||||
project=${PWD##*/}
|
PROJECT_FULL_PATH=
|
||||||
|
SUMMARY_BY_LINE=
|
||||||
|
DEDUP_BY_EMAIL=
|
||||||
|
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>
|
# get date for the given <commit>
|
||||||
#
|
#
|
||||||
|
commit_date() {
|
||||||
date() {
|
# the $1 can be empty
|
||||||
git log --pretty='format: %ai' $1 | cut -d ' ' -f 2
|
# shellcheck disable=SC2086
|
||||||
|
git log $MERGES_ARG --pretty='format: %ai' "$1" | cut -d ' ' -f 2
|
||||||
}
|
}
|
||||||
|
|
||||||
#
|
#
|
||||||
# get active days for the given <commit>
|
# get active days for the given <commit>
|
||||||
#
|
#
|
||||||
|
|
||||||
active_days() {
|
active_days() {
|
||||||
date $1 | uniq | awk '
|
commit_date "$1" | sort -r | uniq | awk '
|
||||||
{ sum += 1 }
|
{ sum += 1 }
|
||||||
END { print sum }
|
END { print sum }
|
||||||
'
|
'
|
||||||
|
|
@ -30,59 +84,176 @@ active_days() {
|
||||||
#
|
#
|
||||||
# get the commit total
|
# get the commit total
|
||||||
#
|
#
|
||||||
|
|
||||||
commit_count() {
|
commit_count() {
|
||||||
git log --oneline $commit | wc -l | tr -d ' '
|
# shellcheck disable=SC2086
|
||||||
|
git rev-list $MERGES_ARG --count "$commit"
|
||||||
}
|
}
|
||||||
|
|
||||||
#
|
#
|
||||||
# total file count
|
# total file count
|
||||||
#
|
#
|
||||||
|
|
||||||
file_count() {
|
file_count() {
|
||||||
git ls-files | wc -l | tr -d ' '
|
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
|
# list authors
|
||||||
#
|
#
|
||||||
|
format_authors() {
|
||||||
authors() {
|
# a rare unicode character is used as separator to avoid conflicting with
|
||||||
git shortlog -n -s $commit | awk '
|
# 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 }
|
{ args[NR] = $0; sum += $0 }
|
||||||
END {
|
END {
|
||||||
for (i = 1; i <= NR; ++i) {
|
for (i = 1; i <= NR; ++i) {
|
||||||
printf "%s,%2.1f%%\n", args[i], 100 * args[i] / sum
|
printf "%s♪%2.1f%%\n", args[i], 100 * args[i] / sum
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
' | column -t -s,
|
' | column -t -s♪ | sed "s/\\\x09/\t/g"
|
||||||
}
|
}
|
||||||
|
|
||||||
#
|
#
|
||||||
# fetch repository age from oldest commit
|
# fetch repository age from oldest commit
|
||||||
#
|
#
|
||||||
|
|
||||||
repository_age() {
|
repository_age() {
|
||||||
git log --reverse --pretty=oneline --format="%ar" | head -n 1 | sed 's/ago//'
|
git log --reverse --pretty=oneline --format="%ar" | head -n 1 | LC_ALL=C sed 's/ago//'
|
||||||
}
|
}
|
||||||
|
|
||||||
# summary
|
#
|
||||||
|
# fetch repository age of the latest commit
|
||||||
|
#
|
||||||
|
last_active() {
|
||||||
|
git log --pretty=oneline --format="%ar" -n 1
|
||||||
|
}
|
||||||
|
|
||||||
if test "$1" = "--line"; then
|
#
|
||||||
git line-summary
|
# list the last modified author for each line
|
||||||
echo
|
#
|
||||||
else
|
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
|
||||||
|
}
|
||||||
|
|
||||||
echo
|
current_branch_name() {
|
||||||
echo " project : $project"
|
git rev-parse --abbrev-ref HEAD
|
||||||
echo " repo age :" $(repository_age)
|
}
|
||||||
echo " active :" $(active_days) days
|
|
||||||
echo " commits :" $(commit_count)
|
#
|
||||||
if test "$commit" = ""; then
|
# list the author for all file
|
||||||
echo " files :" $(file_count)
|
#
|
||||||
|
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
|
fi
|
||||||
echo " authors : "
|
}
|
||||||
authors
|
|
||||||
echo
|
|
||||||
|
|
||||||
|
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
|
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 "$@"
|
||||||
|
|
||||||
|
|
@ -1,8 +1,13 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
filename="$*"
|
USAGE(){
|
||||||
|
echo "git touch <filename> [ <filename> . . .]" 1>&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
test -z "$filename" && echo "filename required" 1>&2 && exit 1
|
test $# -lt 1 && USAGE
|
||||||
|
|
||||||
touch "$filename" \
|
for filename in "$@"; do
|
||||||
&& git add "$filename"
|
touch "$filename" \
|
||||||
|
&& git add "$filename"
|
||||||
|
done
|
||||||
|
|
|
||||||
69
bin/git-undo
69
bin/git-undo
|
|
@ -1,19 +1,72 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
back="^"
|
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
|
case "$1" in
|
||||||
-h|--hard)
|
-h)
|
||||||
test $2 -gt 1 > /dev/null 2>&1 && back="~$2"
|
cat << EOL
|
||||||
git reset --hard HEAD$back && exit 0;
|
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)
|
-s|--soft)
|
||||||
test $2 -gt 1 > /dev/null 2>&1 && back="~$2"
|
parm1=soft
|
||||||
|
parm2=${2:-1}
|
||||||
|
parm3=reset
|
||||||
|
;;
|
||||||
|
"")
|
||||||
|
parm1=soft
|
||||||
|
parm2=1
|
||||||
|
;;
|
||||||
|
*[!0-9]*)
|
||||||
|
echo "Invalid parameter: $1"
|
||||||
|
exit 1
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
test $1 -gt 1 > /dev/null 2>&1 && back="~$1"
|
parm1=soft
|
||||||
|
parm2="$1"
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
git reset --soft HEAD$back
|
if [[ ! $parm2 =~ ^[1-9][0-9]*$ ]]; then
|
||||||
git reset
|
echo "Invalid undo count: $parm2"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
_undo "$parm1" "$parm2" "$parm3"
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
filename=$1
|
filename="$1"
|
||||||
test -z $filename && echo "filename required." 1>&2 && exit 1
|
test -z "$filename" && echo "filename required." 1>&2 && exit 1
|
||||||
git update-index --no-skip-worktree $filename
|
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
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue