mirror of
https://github.com/tj/git-extras.git
synced 2026-09-11 16:06:20 -04:00
Compare commits
1351 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4cf28ec559 | ||
|
|
09b77e8ed6 | ||
|
|
4c38988ba9 | ||
|
|
dbf68bac59 | ||
|
|
03569856db | ||
|
|
d11e45f0b1 | ||
|
|
fd2e03cf55 | ||
|
|
5a14757944 | ||
|
|
b450b36036 | ||
|
|
7f36d37283 | ||
|
|
184d37e980 | ||
|
|
e92878b81b | ||
|
|
b9e1d88f2c | ||
|
|
99207eee8e | ||
|
|
9d6319becb | ||
|
|
2bdeb59a73 | ||
|
|
c5a0fdf660 | ||
|
|
8d1c970031 | ||
|
|
c06fad59d2 | ||
|
|
65dd36ae62 | ||
|
|
967e339f91 | ||
|
|
ae3d30fb6e | ||
|
|
032090e847 | ||
|
|
5367101f2e | ||
|
|
6ed2643252 | ||
|
|
b9bd309a87 | ||
|
|
6f4cf0c1bb | ||
|
|
f5c48e4842 | ||
|
|
daf9d4148e | ||
|
|
eb34655aea | ||
|
|
1f1320f4d6 | ||
|
|
4d05955bc5 | ||
|
|
b57eb8b92a | ||
|
|
cf6d40d446 | ||
|
|
14586c7da0 | ||
|
|
5c9a7a2533 | ||
|
|
215382b12d | ||
|
|
13cbdfc51e | ||
|
|
3ddc315a76 | ||
|
|
c63b46a33b | ||
|
|
d4cd272556 | ||
|
|
783812b627 | ||
|
|
c49ca70814 | ||
|
|
18ecffef5e | ||
|
|
aadf5f874b | ||
|
|
18d1017325 | ||
|
|
a841845d9a | ||
|
|
8eed5f49ad | ||
|
|
64687599ae | ||
|
|
8ce63003c0 | ||
|
|
1a9b0c2ab4 | ||
|
|
82cc37d0f5 | ||
|
|
df53711fc7 | ||
|
|
e691826b4b | ||
|
|
fdaca2cd26 | ||
|
|
3db275cbe2 | ||
|
|
1345c5977d | ||
|
|
2f4b57d52b | ||
|
|
c23da8ae9c | ||
|
|
f1f50ac5ba | ||
|
|
0989b773de | ||
|
|
65203312c1 | ||
|
|
a2c424aada | ||
|
|
d291b2775c | ||
|
|
fb0bc6950b | ||
|
|
21a086c009 | ||
|
|
8eb1d72f07 | ||
|
|
233686d2a2 | ||
|
|
5c3fb1b062 | ||
|
|
5ca9360109 | ||
|
|
16d1445271 | ||
|
|
4d2d393142 | ||
|
|
ffee88a246 | ||
|
|
da03b7b133 | ||
|
|
f0671e2733 | ||
|
|
2c161aae02 | ||
|
|
f0b9e18e54 | ||
|
|
c32869de37 | ||
|
|
e1a4e7195d | ||
|
|
b766a655ce | ||
|
|
e34dea9b7a | ||
|
|
3bac05e690 | ||
|
|
a7ad306a6b | ||
|
|
dcb8bd6c54 | ||
|
|
a1bf09e8e0 | ||
|
|
615ea971cb | ||
|
|
e692b9619a | ||
|
|
a17d2fb15d | ||
|
|
52897f73b9 | ||
|
|
5f19424a50 | ||
|
|
b54d8c7c50 | ||
|
|
9049967232 | ||
|
|
cdbcef3227 | ||
|
|
a4b637346d | ||
|
|
1843afb38a | ||
|
|
28b48c7432 | ||
|
|
2c2bd07693 | ||
|
|
e39b1ab6a6 | ||
|
|
058cb0763e | ||
|
|
3d964687fc | ||
|
|
ca18c6e552 | ||
|
|
cf9fe2d23f | ||
|
|
c13ab52138 | ||
|
|
58c5b50514 | ||
|
|
5020890c34 | ||
|
|
4d8060e8cb | ||
|
|
7a65311b1c | ||
|
|
186bbdcb87 | ||
|
|
bfef91f30a | ||
|
|
78c5ca28a0 | ||
|
|
90221f2149 | ||
|
|
263dcb4a79 | ||
|
|
cc1c0e1a38 | ||
|
|
04bf403861 | ||
|
|
5fe4226865 | ||
|
|
bddb987383 | ||
|
|
df1997c0dc | ||
|
|
954ba1fb7c | ||
|
|
e4152db482 | ||
|
|
dee7c7f1d0 | ||
|
|
e793f8979b | ||
|
|
d2dde69d14 | ||
|
|
49650eda62 | ||
|
|
76cd9bdf45 | ||
|
|
f95e339c03 | ||
|
|
db71be578e | ||
|
|
cb29c62c65 | ||
|
|
47e940272a | ||
|
|
8af43892f6 | ||
|
|
34609bd800 | ||
|
|
0b5d2cce55 | ||
|
|
d7fb4495f1 | ||
|
|
5e543cb620 | ||
|
|
3a258cbaad | ||
|
|
ba9e6f0c5c | ||
|
|
5d7ed18c50 | ||
|
|
2d221c1b67 | ||
|
|
f5bcbe5b0d | ||
|
|
6d73f74f83 | ||
|
|
bbf90c4623 | ||
|
|
2fca5edb7d | ||
|
|
d7f0fcc546 | ||
|
|
df825715ba | ||
|
|
2d5b12c01f | ||
|
|
753555744b | ||
|
|
d5eaa1895b | ||
|
|
b372bab52a | ||
|
|
a008308267 | ||
|
|
5d0fe32617 | ||
|
|
75d0dbff6d | ||
|
|
9c3343d1ee | ||
|
|
82daeb7c94 | ||
|
|
73fbf8e08d | ||
|
|
36e6de10c8 | ||
|
|
e1138b8190 | ||
|
|
589ef33e35 | ||
|
|
e4e7310c49 | ||
|
|
4611b24320 | ||
|
|
04eb5c04e6 | ||
|
|
e29dcbb023 | ||
|
|
d34fa527a0 | ||
|
|
35100b4e6f | ||
|
|
cd348dad21 | ||
|
|
aea25d2d4b | ||
|
|
4fff03f429 | ||
|
|
7c4152ce83 | ||
|
|
960ec8d2fd | ||
|
|
1426002037 | ||
|
|
193a6dd2a8 | ||
|
|
2d1aa6daea | ||
|
|
71af8cec42 | ||
|
|
3ac3c54b06 | ||
|
|
584e0283f5 | ||
|
|
62aa91f221 | ||
|
|
25b5d734c7 | ||
|
|
06aa5dbb1f | ||
|
|
8058b1718f | ||
|
|
956781ac1b | ||
|
|
3b9ea70e09 | ||
|
|
6e6f59b264 | ||
|
|
01e2494294 | ||
|
|
e3c8359e4e | ||
|
|
9accb6bb04 | ||
|
|
66961ad491 | ||
|
|
81665422b2 | ||
|
|
48ef95d104 | ||
|
|
bf240df083 | ||
|
|
d597f684b1 | ||
|
|
7acc1de7fa | ||
|
|
8a6e39d589 | ||
|
|
006549d661 | ||
|
|
233b3f0dd4 | ||
|
|
fdf89cd93f | ||
|
|
95c54f6cdb | ||
|
|
34237934d4 | ||
|
|
8c744569c4 | ||
|
|
458cb9fdad | ||
|
|
12b27415ea | ||
|
|
0701c9e4d7 | ||
|
|
42fd8a9e72 | ||
|
|
311edadd55 | ||
|
|
d8dd44d8eb | ||
|
|
41df8dc7f3 | ||
|
|
48beb58670 | ||
|
|
5e07a96c57 | ||
|
|
3ef09dac72 | ||
|
|
799aa6e19a | ||
|
|
aa6b547f55 | ||
|
|
f334dff09c | ||
|
|
055110dd35 | ||
|
|
745c7ab500 | ||
|
|
7cb5c55e13 | ||
|
|
0d7334a63f | ||
|
|
5754b81047 | ||
|
|
2aadd3ed41 | ||
|
|
d2a09c1753 | ||
|
|
c7756455cb | ||
|
|
4fcd233bf2 | ||
|
|
a73c19c14b | ||
|
|
6bf2b0bc4c | ||
|
|
d17f02979e | ||
|
|
716459f79d | ||
|
|
02263ca949 | ||
|
|
613b909765 | ||
|
|
f556430122 | ||
|
|
9390d450aa | ||
|
|
3d6f9456a3 | ||
|
|
27ee8f0132 | ||
|
|
678a0c2e71 | ||
|
|
11a2801231 | ||
|
|
d7565ee844 | ||
|
|
02bbdba792 | ||
|
|
f513f58d9e | ||
|
|
c036c9cc22 | ||
|
|
42458d838b | ||
|
|
b2794c18a1 | ||
|
|
760428fa46 | ||
|
|
76a3c1f28d | ||
|
|
2025ebfb96 | ||
|
|
5467c2ff5a | ||
|
|
843b958c9a | ||
|
|
7aa178acc9 | ||
|
|
2d722c93b8 | ||
|
|
b772251074 | ||
|
|
777fcade17 | ||
|
|
e370b19190 | ||
|
|
82b33ac4c7 | ||
|
|
fff5fde917 | ||
|
|
edffba3c2a | ||
|
|
11640196b2 | ||
|
|
6b0ef19caf | ||
|
|
d914ed3a89 | ||
|
|
b2743d785f | ||
|
|
2370a4e27b | ||
|
|
2fdd664594 | ||
|
|
b33ec65e5f | ||
|
|
3f2ad3fa8f | ||
|
|
e0101470ca | ||
|
|
86bb1a6764 | ||
|
|
96890821b0 | ||
|
|
0c0be92445 | ||
|
|
cc9b972e25 | ||
|
|
760e64b864 | ||
|
|
f6fa554077 | ||
|
|
bc8876b063 | ||
|
|
c30ac80d72 | ||
|
|
4de7b34c9f | ||
|
|
0edcf8b7e6 | ||
|
|
5feb0db212 | ||
|
|
703a8481c6 | ||
|
|
3413eeb058 | ||
|
|
a7b7f05b5b | ||
|
|
a07c5ece5d | ||
|
|
296f2a7ad2 | ||
|
|
e9708ba9f4 | ||
|
|
3c91de7a1e | ||
|
|
3c632d9e32 | ||
|
|
3821b2c537 | ||
|
|
ffad35c8db | ||
|
|
d28ad34bcb | ||
|
|
b8e58ee9ba | ||
|
|
b225ed4934 | ||
|
|
30383f9551 | ||
|
|
1f72512ef0 | ||
|
|
b8b02d8ef1 | ||
|
|
d8b1007f68 | ||
|
|
64993dbc7f | ||
|
|
4a4c5296f3 | ||
|
|
206fe7ccfb | ||
|
|
f1eab6a8a7 | ||
|
|
cd8fe4f6e2 | ||
|
|
bebcbd11ca | ||
|
|
fb1e5db6d7 | ||
|
|
910242c92b | ||
|
|
48b2c8fd37 | ||
|
|
d86abec6a2 | ||
|
|
1894f453f6 | ||
|
|
c35c84a279 | ||
|
|
a62a67ef5f | ||
|
|
d0c40851c0 | ||
|
|
52ada403ef | ||
|
|
b9786c763f | ||
|
|
6880198fda | ||
|
|
687a5607dc | ||
|
|
7b1a062e93 | ||
|
|
6a15a5339b | ||
|
|
a424d0915e | ||
|
|
458727bafb | ||
|
|
d7b527c1ae | ||
|
|
0fad3cfd03 | ||
|
|
7d9e05062e | ||
|
|
c8d9374491 | ||
|
|
f3028db073 | ||
|
|
d73bfbb98b | ||
|
|
42e1f9d5c6 | ||
|
|
b7841ad3aa | ||
|
|
4ea6cf0221 | ||
|
|
8f60fb29f4 | ||
|
|
96b95392ac | ||
|
|
16c1f0d3dc | ||
|
|
c675d0afb8 | ||
|
|
4c82bbecc3 | ||
|
|
faa56b1bcb | ||
|
|
e6caa931a5 | ||
|
|
6b45d4322b | ||
|
|
e92d54cadb | ||
|
|
444722cf38 | ||
|
|
b2b5c97105 | ||
|
|
d3772eb469 | ||
|
|
41732e4113 | ||
|
|
18bcfcd38e | ||
|
|
aa5a380996 | ||
|
|
68e6a15401 | ||
|
|
05ce91fb9e | ||
|
|
d2a159ed25 | ||
|
|
f66f7e484f | ||
|
|
fca42774f8 | ||
|
|
ef13ed06a3 | ||
|
|
fa35cdab99 | ||
|
|
8ed7da3f02 | ||
|
|
54f8205478 | ||
|
|
19531065ca | ||
|
|
4f5b5ccd48 | ||
|
|
ad8c77bfa3 | ||
|
|
8490a50304 | ||
|
|
aa174b3905 | ||
|
|
c006e5ece8 | ||
|
|
171d06965e | ||
|
|
6d9f861d0e | ||
|
|
041e3fd8da | ||
|
|
35d544b8c6 | ||
|
|
da34968ec3 | ||
|
|
35d44c97cc | ||
|
|
317eea64db | ||
|
|
2ef6ea4b96 | ||
|
|
c41f0164cc | ||
|
|
3cc866dd97 | ||
|
|
2929c41637 | ||
|
|
64e76db350 | ||
|
|
74fe4f6aba | ||
|
|
2e610158a8 | ||
|
|
65be1e5f8f | ||
|
|
9683fb6510 | ||
|
|
964663f1a4 | ||
|
|
e9668cc04b | ||
|
|
8a487271cb | ||
|
|
1a4cd4ca3d | ||
|
|
e116406ff1 | ||
|
|
34ba1eb083 | ||
|
|
6a455e7d29 | ||
|
|
6afc2fb4a8 | ||
|
|
c56b61d2bd | ||
|
|
c386733efd | ||
|
|
ecf0f4847f | ||
|
|
e031dc54e9 | ||
|
|
8e519e3e66 | ||
|
|
c52ba59d10 | ||
|
|
9ee5d06469 | ||
|
|
ec417833ee | ||
|
|
b4b81e290e | ||
|
|
c92f03d303 | ||
|
|
af06d49c33 | ||
|
|
e7270b3667 | ||
|
|
87cff6d5cb | ||
|
|
aaf9ef343b | ||
|
|
73cc666030 | ||
|
|
eaeeef3b80 | ||
|
|
73bb1e0f59 | ||
|
|
238038139d | ||
|
|
85e1c4ff0a | ||
|
|
a0d7039482 | ||
|
|
49a34037ad | ||
|
|
2dc9c26e36 | ||
|
|
2f3772b09a | ||
|
|
4a7fcb7d1c | ||
|
|
6e77d950eb | ||
|
|
572ae0dd46 | ||
|
|
a0a32abc0b | ||
|
|
edf7b060d2 | ||
|
|
2076ac9c38 | ||
|
|
4c9d59059e | ||
|
|
97de52a3be | ||
|
|
a994411e58 | ||
|
|
f06ba9f70b | ||
|
|
5a79873bd6 | ||
|
|
27f42beb81 | ||
|
|
6ccd2fa439 | ||
|
|
9dca93eeb2 | ||
|
|
11cc90d5f6 | ||
|
|
90495d0d5e | ||
|
|
f2136ec433 | ||
|
|
a04f27b255 | ||
|
|
f27875d5bc | ||
|
|
cb66d36434 | ||
|
|
d4ff718ac3 | ||
|
|
23fcac8c4f | ||
|
|
fa8165ba7e | ||
|
|
db9ca55c71 | ||
|
|
672e330f47 | ||
|
|
852e630314 | ||
|
|
54199b8d08 | ||
|
|
edc4814ced | ||
|
|
8f133a32ba | ||
|
|
4eb4e7ffdb | ||
|
|
acf4123234 | ||
|
|
9e843da98d | ||
|
|
e8ee36733f | ||
|
|
db3389a3cc | ||
|
|
3583825137 | ||
|
|
31079e0a60 | ||
|
|
45eed8f166 | ||
|
|
c038741194 | ||
|
|
63eaba4572 | ||
|
|
f2a3e50cab | ||
|
|
91b3e47243 | ||
|
|
b85fc019ca | ||
|
|
2c9b071fde | ||
|
|
6e83793dda | ||
|
|
2a5c08aaf8 | ||
|
|
5afaa731e0 | ||
|
|
bea25d1ceb | ||
|
|
739ca052b4 | ||
|
|
629fc03581 | ||
|
|
dd0337c3cf | ||
|
|
6b4188404b | ||
|
|
79ae65fe64 | ||
|
|
f6cf59c380 | ||
|
|
362a0c29ae | ||
|
|
be50c2f3f5 | ||
|
|
3917c774ec | ||
|
|
b708a8a4d3 | ||
|
|
80cd52701e | ||
|
|
265b03e400 | ||
|
|
4b9f1619f6 | ||
|
|
6438dee914 | ||
|
|
2a65a7aa8b | ||
|
|
2c689db6d7 | ||
|
|
2067d26276 | ||
|
|
aefa3c9ff6 | ||
|
|
3b2f6611d6 | ||
|
|
ecff5e1a3c | ||
|
|
eb33c9bf5a | ||
|
|
bd8fa031cc | ||
|
|
4d5b53aea2 | ||
|
|
69e96cba95 | ||
|
|
e836b6ce4c | ||
|
|
16bb0df9f9 | ||
|
|
bb7d389084 | ||
|
|
7a66b157f0 | ||
|
|
2fe53ac791 | ||
|
|
d0636d2b07 | ||
|
|
41ebe0e0bf | ||
|
|
6b8729a819 | ||
|
|
29f6d0e0e0 | ||
|
|
83349426d1 | ||
|
|
45647fdab3 | ||
|
|
67c2bf595f | ||
|
|
4d4b87ccb8 | ||
|
|
292149c46f | ||
|
|
704b1b4fdd | ||
|
|
a6a1754093 | ||
|
|
b0d3a77ac0 | ||
|
|
46ea733983 | ||
|
|
f4e313d3f2 | ||
|
|
1924eb9197 | ||
|
|
952d1eb76f | ||
|
|
f2934bd0bd | ||
|
|
677f570f34 | ||
|
|
227c19bb67 | ||
|
|
1f605df053 | ||
|
|
17d1a471a4 | ||
|
|
f00c1389d6 | ||
|
|
4f35f8cad8 | ||
|
|
5ea4a626a6 | ||
|
|
4bb273a802 | ||
|
|
4a47ea034b | ||
|
|
7b9fbc4389 | ||
|
|
2883b67644 | ||
|
|
f788c789f3 | ||
|
|
3e8c3ab0c4 | ||
|
|
ff991ac99e | ||
|
|
a14547e92f | ||
|
|
203f5b48ad | ||
|
|
7bdfb1f55f | ||
|
|
cf3e1e7e7d | ||
|
|
fd36fc0ea2 | ||
|
|
eb9ddbe8a9 | ||
|
|
256044c04f | ||
|
|
0cd55d88bf | ||
|
|
bba1f964b8 | ||
|
|
7015501161 | ||
|
|
ff0b1cfb0b | ||
|
|
d90b02ce22 | ||
|
|
5b62b4a884 | ||
|
|
f7d127b360 | ||
|
|
1d7d4c2446 | ||
|
|
a85d5f57bb | ||
|
|
1a414f5512 | ||
|
|
e24c1302f4 | ||
|
|
883199093c | ||
|
|
6a3d61ff12 | ||
|
|
c4e94a4130 | ||
|
|
a921e88d76 | ||
|
|
a56e14488e | ||
|
|
55cefa9021 | ||
|
|
3484df9ca4 | ||
|
|
66288296b2 | ||
|
|
5bffcb4c89 | ||
|
|
4867571df0 | ||
|
|
a2250dd205 | ||
|
|
f053fc48db | ||
|
|
ebdebadd3b | ||
|
|
fa5017a4a5 | ||
|
|
d4de567ca7 | ||
|
|
f2587339cf | ||
|
|
2dc5d5104e | ||
|
|
500ea2b119 | ||
|
|
df06f3e3bd | ||
|
|
defa89c794 | ||
|
|
58d13f88d4 | ||
|
|
85248595e3 | ||
|
|
0b49719ae4 | ||
|
|
3080b81bcc | ||
|
|
c03e0ad841 | ||
|
|
ac9e35735b | ||
|
|
a26f1e6f8e | ||
|
|
7d3ab83ec3 | ||
|
|
eae05bbb98 | ||
|
|
820b7cd41b | ||
|
|
1712db2146 | ||
|
|
c3c0bdfbbb | ||
|
|
4b09e94853 | ||
|
|
26447e5bbe | ||
|
|
f4f7c1e3b5 | ||
|
|
6d1982d6cf | ||
|
|
ecdd665f1c | ||
|
|
a214c93ad4 | ||
|
|
044b06b6cb | ||
|
|
5b0a9d22b6 | ||
|
|
eecfb9af47 | ||
|
|
39df4dfa64 | ||
|
|
f355fb4c94 | ||
|
|
7a89f7c6e4 | ||
|
|
99238bdf0a | ||
|
|
f29dad042c | ||
|
|
87b7c959b4 | ||
|
|
088ea058d9 | ||
|
|
c4db208ca4 | ||
|
|
44dc1b9dba | ||
|
|
9f806bb828 | ||
|
|
49fab7267b | ||
|
|
3730339067 | ||
|
|
a815bb0fbd | ||
|
|
13fa83b24e | ||
|
|
eb27b24b73 | ||
|
|
5f961fe8e3 | ||
|
|
a5edb6d8b7 | ||
|
|
7e10554506 | ||
|
|
700580aae6 | ||
|
|
6e7766f5a4 | ||
|
|
3b7637ce40 | ||
|
|
edebba89b9 | ||
|
|
8b992cc9bc | ||
|
|
7393fb41ed | ||
|
|
b7d507567b | ||
|
|
2590998ee0 | ||
|
|
8545d97b8d | ||
|
|
424ec4d2a5 | ||
|
|
1cb4979ed7 | ||
|
|
a347e49178 | ||
|
|
e4aaf9f697 | ||
|
|
792b697450 | ||
|
|
4e1cd73e59 | ||
|
|
b7a1a0d8b2 | ||
|
|
d681b972cc | ||
|
|
28819e388e | ||
|
|
f30005acfd | ||
|
|
691d640b2d | ||
|
|
0f76863d9b | ||
|
|
a4f85ae6ff | ||
|
|
787ee2c997 | ||
|
|
d34b6ae0bc | ||
|
|
f5c26c7fb3 | ||
|
|
731e9d8db9 | ||
|
|
8b58e570cb | ||
|
|
ba6d991851 | ||
|
|
bd6a1fcafe | ||
|
|
317e546b0a | ||
|
|
a6732ef6db | ||
|
|
3802e83e79 | ||
|
|
658f133691 | ||
|
|
6f416b20c3 | ||
|
|
f7514bebbf | ||
|
|
d024755718 | ||
|
|
0ab9b47371 | ||
|
|
4325238aee | ||
|
|
9785199036 | ||
|
|
c27a3505dc | ||
|
|
ee14220f66 | ||
|
|
1b94ca130c | ||
|
|
fd2151f9bb | ||
|
|
6add6488d7 | ||
|
|
22a44eb206 | ||
|
|
420c678526 | ||
|
|
c53edf00da | ||
|
|
ebf7903d0e | ||
|
|
4ab42e218e | ||
|
|
e9ddc1dc3b | ||
|
|
f5d99bcac9 | ||
|
|
9ee318e8d4 | ||
|
|
5dbab48aff | ||
|
|
de364cfac1 | ||
|
|
8714ac4aa1 | ||
|
|
d4bf79df8f | ||
|
|
16591d3788 | ||
|
|
80e93704eb | ||
|
|
8216e6bbbc | ||
|
|
f959b4d4d3 | ||
|
|
db2d0456ac | ||
|
|
5639160b8b | ||
|
|
c2efbc889d | ||
|
|
ab99fb3eb2 | ||
|
|
17fce79d3c | ||
|
|
693c84f8f0 | ||
|
|
b7d960eadb | ||
|
|
199364e932 | ||
|
|
3b21b87036 | ||
|
|
fc8eaf0d28 | ||
|
|
6aecc2b9fa | ||
|
|
43bc038d41 | ||
|
|
82896c8553 | ||
|
|
f1aeed4344 | ||
|
|
46bc0959e1 | ||
|
|
167f0c8b50 | ||
|
|
e7ca960f0c | ||
|
|
d520e0641e | ||
|
|
9573a7653b | ||
|
|
af39b2b2ee | ||
|
|
78835c7525 | ||
|
|
a88958ba20 | ||
|
|
5b7c076fba | ||
|
|
942bab3b32 | ||
|
|
7b76644bc6 | ||
|
|
6b5f5d8762 | ||
|
|
18d8221dd2 | ||
|
|
cfcb4d92c6 | ||
|
|
1f0a044677 | ||
|
|
9f621306cb | ||
|
|
c59ddb73b4 | ||
|
|
669c9180b4 | ||
|
|
12cd938db3 | ||
|
|
4defd6d676 | ||
|
|
be8a1548d4 | ||
|
|
15759061f5 | ||
|
|
423c49ab92 | ||
|
|
227ef26e7f | ||
|
|
9e2a625b29 | ||
|
|
d990270de7 | ||
|
|
4dc76ef81b | ||
|
|
ab6c51bb8f | ||
|
|
c064956f85 | ||
|
|
a75411b029 | ||
|
|
93150aa411 | ||
|
|
61b2a2c703 | ||
|
|
be15db9cdf | ||
|
|
6bd1ebd68b | ||
|
|
9a6c60e6a7 | ||
|
|
acb3f93ae4 | ||
|
|
4d885acda0 | ||
|
|
ce6d635c33 | ||
|
|
d4c7581835 | ||
|
|
4b29351f2f | ||
|
|
9baae93a9b | ||
|
|
9334c9db31 | ||
|
|
0e7cba55a8 | ||
|
|
276a307780 | ||
|
|
88ee400c3f | ||
|
|
f55690e702 | ||
|
|
204406bd29 | ||
|
|
0bae07f7b9 | ||
|
|
27310926d3 | ||
|
|
3428b278d3 | ||
|
|
d01ad47953 | ||
|
|
b41a4cf05b | ||
|
|
bd8545f748 | ||
|
|
4917796d63 | ||
|
|
14159245a9 | ||
|
|
5cdfe42113 | ||
|
|
383f95f7c8 | ||
|
|
0a125edeaf | ||
|
|
684d6acc93 | ||
|
|
0b91cd9bae | ||
|
|
61211634c9 | ||
|
|
759b3841b8 | ||
|
|
fe39ac8af2 | ||
|
|
ca62bdcfe6 | ||
|
|
fda5f3e4ef | ||
|
|
7263b6c611 | ||
|
|
6eb04bcca8 | ||
|
|
de15bf7fb0 | ||
|
|
49771ba018 | ||
|
|
6e9da2a4e7 | ||
|
|
4a05ad29e4 | ||
|
|
344b145062 | ||
|
|
9744830d4a | ||
|
|
02dc8b5b21 | ||
|
|
d3cccebbd2 | ||
|
|
9260fd92bf | ||
|
|
5957544dee | ||
|
|
9263aec739 | ||
|
|
1cc77481bd | ||
|
|
534c72b91f | ||
|
|
57c0eb776c | ||
|
|
b00aacee00 | ||
|
|
cc0dff2f49 | ||
|
|
b0ba3e5346 | ||
|
|
422c9a8e69 | ||
|
|
b01359efc5 | ||
|
|
518db6e648 | ||
|
|
2289c25029 | ||
|
|
719dd2c3af | ||
|
|
28363eff4e | ||
|
|
b2c7de9a5a | ||
|
|
9c1feecca5 | ||
|
|
c80b4e773f | ||
|
|
e93f206bad | ||
|
|
484b98bb04 | ||
|
|
64b4a599e8 | ||
|
|
c258e5d4e8 | ||
|
|
2451c0c202 | ||
|
|
bb5f4b0234 | ||
|
|
2e8b368141 | ||
|
|
e31f61ba34 | ||
|
|
df06f9af2e | ||
|
|
b8ea5c4a02 | ||
|
|
82162ea8c8 | ||
|
|
15ae263312 | ||
|
|
c5bd28dd51 | ||
|
|
a7779a1029 | ||
|
|
9ea8f98e5c | ||
|
|
a118f306b1 | ||
|
|
4aec48c85c | ||
|
|
c9463bf6ef | ||
|
|
705bb32a7a | ||
|
|
3b621f1052 | ||
|
|
13f15d5339 | ||
|
|
b105dd837a | ||
|
|
3ffbe23fbe | ||
|
|
79629e6b6e | ||
|
|
cd328bfb61 | ||
|
|
76694bccdd | ||
|
|
29a5596d9e | ||
|
|
20a9e95b9a | ||
|
|
c4a9bb46d9 | ||
|
|
8cbbf9095a | ||
|
|
2292e1cebe | ||
|
|
fd2de52949 | ||
|
|
7f14cc4488 | ||
|
|
4f8e411f71 | ||
|
|
82d6a7265f | ||
|
|
1ed75978a9 | ||
|
|
54de081be7 | ||
|
|
009f4fb6d1 | ||
|
|
575f5dfd19 | ||
|
|
2a70117f02 | ||
|
|
c5d29821da | ||
|
|
4d92c8bdbf | ||
|
|
9f48c6a1cc | ||
|
|
c3a303ef70 | ||
|
|
e1f0f6937a | ||
|
|
77e55640d7 | ||
|
|
ec64fe6500 | ||
|
|
7aa7bcf740 | ||
|
|
2d6e4eaf8a | ||
|
|
b89c12e79b | ||
|
|
e101d825a2 | ||
|
|
dead6873db | ||
|
|
c8142ca539 | ||
|
|
db62a6d95c | ||
|
|
71a12810e0 | ||
|
|
5550cfa4dc | ||
|
|
6e87dc3e55 | ||
|
|
2137124977 | ||
|
|
ad5365fb53 | ||
|
|
e476562a46 | ||
|
|
779f467612 | ||
|
|
7ff05f2977 | ||
|
|
249a6cd27c | ||
|
|
f0a8b9e17e | ||
|
|
3ae8d22637 | ||
|
|
39b9624381 | ||
|
|
1b84048bfc | ||
|
|
805d43831f | ||
|
|
91aa82b62b | ||
|
|
b649730a2c | ||
|
|
f011c7656a | ||
|
|
5c11295852 | ||
|
|
6d49449a4d | ||
|
|
8e15d4245d | ||
|
|
fae4c29870 | ||
|
|
241069ddac | ||
|
|
93c2830ce7 | ||
|
|
5a8ce5e364 | ||
|
|
9d941e15e5 | ||
|
|
3efe6ab2df | ||
|
|
a319b6f25e | ||
|
|
d6f505f85a | ||
|
|
554d779344 | ||
|
|
fb3bc8d7ae | ||
|
|
ebe7448e67 | ||
|
|
3aafbc68c1 | ||
|
|
8dbc34a6a7 | ||
|
|
e62a6584ca | ||
|
|
20b84f5b17 | ||
|
|
5d15928e82 | ||
|
|
93ae8fd81e | ||
|
|
dcf94a3323 | ||
|
|
ce7cca1281 | ||
|
|
ca78ac71ba | ||
|
|
c87d59346b | ||
|
|
9b3e25f407 | ||
|
|
f3954e1974 | ||
|
|
d38d3f4ffa | ||
|
|
5bceec4c16 | ||
|
|
76f23c213d | ||
|
|
82b6eb4331 | ||
|
|
a3c03d021e | ||
|
|
c1ac836c7f | ||
|
|
855053dcee | ||
|
|
27e992b260 | ||
|
|
e18ee20d18 | ||
|
|
77036074ef | ||
|
|
66560432ce | ||
|
|
311ef28b66 | ||
|
|
5384091183 | ||
|
|
a062420224 | ||
|
|
88902b7586 | ||
|
|
3ff05a6f53 | ||
|
|
b33f85a5d1 | ||
|
|
c783733e16 | ||
|
|
f60f127d57 | ||
|
|
3de481706d | ||
|
|
0c8abf8624 | ||
|
|
dbf87bf94e | ||
|
|
362e08a2b6 | ||
|
|
90ea286a50 | ||
|
|
1ec4835946 | ||
|
|
c9729611e4 | ||
|
|
6e65f37392 | ||
|
|
859bf38c67 | ||
|
|
76663c26db | ||
|
|
a1c5742a70 | ||
|
|
299efd84af | ||
|
|
61c197bdbd | ||
|
|
217f536606 | ||
|
|
e2305d4501 | ||
|
|
f50447e6bd | ||
|
|
fece2a517a | ||
|
|
eb046dcdb3 | ||
|
|
09bbb56a39 | ||
|
|
381a0ff1fb | ||
|
|
34f5245672 | ||
|
|
1f374d1ea9 | ||
|
|
1aadd1c52f | ||
|
|
becb14ad01 | ||
|
|
3c16905754 | ||
|
|
a39c89e34e | ||
|
|
b485aabea0 | ||
|
|
3783a27a39 | ||
|
|
61fee79cc7 | ||
|
|
e7826a717c | ||
|
|
b865a2bae5 | ||
|
|
24439aac6d | ||
|
|
b8418cfba5 | ||
|
|
98f85c90b0 | ||
|
|
c43d4f2539 | ||
|
|
e91343e376 | ||
|
|
301b436220 | ||
|
|
ddb097d65d | ||
|
|
9090d3bbe9 | ||
|
|
555282985d | ||
|
|
71cf48b0c1 | ||
|
|
56a2e27910 | ||
|
|
3603b67eb7 | ||
|
|
ac0b5fe5ed | ||
|
|
d286839da1 | ||
|
|
edbcb3f4c0 | ||
|
|
511c135e2f | ||
|
|
8a8a483f41 | ||
|
|
0ca4c6f221 | ||
|
|
5b45eb702b | ||
|
|
a4a001dde9 | ||
|
|
9d2df44d18 | ||
|
|
e6cdd2cf56 | ||
|
|
0fde295f5e | ||
|
|
a192fe688a | ||
|
|
afce25cd65 | ||
|
|
98d0d8c642 | ||
|
|
f0ecd82129 | ||
|
|
19ece6192e | ||
|
|
3481ff245e | ||
|
|
642eedeab5 | ||
|
|
12e416a324 | ||
|
|
e7083decec | ||
|
|
e6398ba24f | ||
|
|
64f9e4d797 | ||
|
|
6bf64fbb23 | ||
|
|
a20d4bc890 | ||
|
|
2ef6df6182 | ||
|
|
672c87e9e4 | ||
|
|
ef73afb39e | ||
|
|
cf0fa9da2d | ||
|
|
7ec7a18671 | ||
|
|
a1530d90f2 | ||
|
|
90ba942cf6 | ||
|
|
39ed77253c | ||
|
|
94556f380b | ||
|
|
bb02bdcd55 | ||
|
|
1ff64e2fed | ||
|
|
2b042bdd59 | ||
|
|
64639e1437 | ||
|
|
3c874ce9f4 | ||
|
|
176ebf9da1 | ||
|
|
168f59e772 | ||
|
|
cbad7c63eb | ||
|
|
d46c515d6d | ||
|
|
4354cca461 | ||
|
|
4c06b7f074 | ||
|
|
a09eefde25 | ||
|
|
91ca995edc | ||
|
|
35e1f7845a | ||
|
|
1d8534b9cd | ||
|
|
b4f4dd1197 | ||
|
|
106acae943 | ||
|
|
b804d36a21 | ||
|
|
5a80928b89 | ||
|
|
889a65883f | ||
|
|
7dce2bbbdf | ||
|
|
546ddf78af | ||
|
|
7f662708fa | ||
|
|
f799e96910 | ||
|
|
dfe0c79f45 | ||
|
|
4b795fc42f | ||
|
|
77f1c86b0d | ||
|
|
02a440953b | ||
|
|
7ae2e0b9ef | ||
|
|
c4cd2ca34d | ||
|
|
d82c9b4576 | ||
|
|
166f9599e7 | ||
|
|
19f66943cb | ||
|
|
b2817c7299 | ||
|
|
ed7dcb7682 | ||
|
|
faaae9fa6b | ||
|
|
5b285380e7 | ||
|
|
cc488a035d | ||
|
|
7b74567550 | ||
|
|
bb1623ba5d | ||
|
|
5f61bfad6e | ||
|
|
332bec0f1a | ||
|
|
91aff4fe20 | ||
|
|
f8b81131a5 | ||
|
|
60098c9629 | ||
|
|
81e8a45b38 | ||
|
|
37d6012791 | ||
|
|
07505518fa | ||
|
|
88c3437a24 | ||
|
|
b517022e15 | ||
|
|
8d0eae5c7f | ||
|
|
61bfc44eec | ||
|
|
c0ad975f22 | ||
|
|
6a000ec99b | ||
|
|
c8aca55cd7 | ||
|
|
80f060b213 | ||
|
|
e91fd9623f | ||
|
|
86e1a7014c | ||
|
|
9916356b14 | ||
|
|
26a8b901a0 | ||
|
|
490d12b6f7 | ||
|
|
0393d560f8 | ||
|
|
639210660e | ||
|
|
f6f45990ab | ||
|
|
38c7b03372 | ||
|
|
2c1d766640 | ||
|
|
80a7928b67 | ||
|
|
9b89da3a95 | ||
|
|
b988558ea4 | ||
|
|
469434a8eb | ||
|
|
6073594656 | ||
|
|
fed97bf8db | ||
|
|
3ad2f6fa4a | ||
|
|
ccd5303040 | ||
|
|
a7f830b1e7 | ||
|
|
967b51c964 | ||
|
|
2ced182fcc | ||
|
|
98bbfe7968 | ||
|
|
a1d0445cc1 | ||
|
|
4a8037fa62 | ||
|
|
d87599c344 | ||
|
|
37aacf3e41 | ||
|
|
87d61693b1 | ||
|
|
bb4acf0757 | ||
|
|
0e05f63147 | ||
|
|
30747be220 | ||
|
|
55bfebbc21 | ||
|
|
e042c5ceb7 | ||
|
|
da76b2ae86 | ||
|
|
1eba324e94 | ||
|
|
a66c9446b5 | ||
|
|
dae38840f9 | ||
|
|
0c7bf665f2 | ||
|
|
ecf7f05f12 | ||
|
|
2c855e991c | ||
|
|
ad7811e991 | ||
|
|
dae750f734 | ||
|
|
7b06400ac7 | ||
|
|
6808b0fd79 | ||
|
|
7e147993d4 | ||
|
|
afb905c5ae | ||
|
|
5fb7f6b77c | ||
|
|
3b02c65a94 | ||
|
|
ad83e447a6 | ||
|
|
2d8b703e7c | ||
|
|
11fd45791b | ||
|
|
17b82ec9ee | ||
|
|
fa4c5dd456 | ||
|
|
84699acc6e | ||
|
|
3632ad959f | ||
|
|
9859396131 | ||
|
|
98a1820d39 | ||
|
|
ad75f873af | ||
|
|
dc498c0614 | ||
|
|
2912d3522c | ||
|
|
b1fba8f3fe | ||
|
|
d51f4fde37 | ||
|
|
d367affd57 | ||
|
|
96329378b9 | ||
|
|
4527656f7c | ||
|
|
bd05487375 | ||
|
|
09e039ceae | ||
|
|
7de280f12f | ||
|
|
2113e8c1d7 | ||
|
|
5c27f6b2d1 | ||
|
|
ff4ad92a7a | ||
|
|
40023f9f14 | ||
|
|
b9ca124980 | ||
|
|
7fbe00c311 | ||
|
|
ca4cb46dde | ||
|
|
cdc4d8a0cf | ||
|
|
8b4e50f74a | ||
|
|
aeebb1c1c1 | ||
|
|
a212a78c1b | ||
|
|
ea0af0c391 | ||
|
|
8a559a070a | ||
|
|
d5e10f6701 | ||
|
|
8f1729d5f7 | ||
|
|
db95415385 | ||
|
|
f943a2db50 | ||
|
|
04b63cbc10 | ||
|
|
7b287a8ea7 | ||
|
|
ddfd3d9752 | ||
|
|
c4ffb30410 | ||
|
|
94bf129721 | ||
|
|
3039f9405e | ||
|
|
d8395ba2e9 | ||
|
|
3c5523617e | ||
|
|
9629fdf13c | ||
|
|
24ad1c8281 | ||
|
|
3a8c947922 | ||
|
|
4b4276e3fc | ||
|
|
371dcdff13 | ||
|
|
46db56905d | ||
|
|
f0194060c5 | ||
|
|
22406cef6a | ||
|
|
5e14c08fc5 | ||
|
|
434b7681ce | ||
|
|
36f8f2ba1e | ||
|
|
526fb77823 | ||
|
|
1956d385bd | ||
|
|
cdb773c065 | ||
|
|
20e6455559 | ||
|
|
8dbcd446db | ||
|
|
c2ea7f081a | ||
|
|
f40e6c37a6 | ||
|
|
6ebfdc5329 | ||
|
|
d97faf2424 | ||
|
|
651a2bac97 | ||
|
|
467d52a73e | ||
|
|
3dcf09f883 | ||
|
|
b99a84691e | ||
|
|
95db4e7ad5 | ||
|
|
3d0b4feb80 | ||
|
|
329106c845 | ||
|
|
78de79ce96 | ||
|
|
6ba68941e8 | ||
|
|
ba5e5989a3 | ||
|
|
fd72edd98f | ||
|
|
7d1ede1022 | ||
|
|
7ddd1f7a60 | ||
|
|
8652172f9b | ||
|
|
4ff5dc1ee0 | ||
|
|
d0e667961c | ||
|
|
4fd095b741 | ||
|
|
a7064395c2 | ||
|
|
fa3cf6ab41 | ||
|
|
935c5a37b3 | ||
|
|
39238ded46 | ||
|
|
203b7649a2 | ||
|
|
73973049d6 | ||
|
|
cb15c94eab | ||
|
|
fea722e7e9 | ||
|
|
392a2d67db | ||
|
|
13842de9e0 | ||
|
|
dc208cddda | ||
|
|
3707e2e826 | ||
|
|
fd4fbd3950 | ||
|
|
3f7bcba984 | ||
|
|
dafb457e81 | ||
|
|
a56d2a17e4 | ||
|
|
8a3f9884c8 | ||
|
|
96bb7cb873 | ||
|
|
51fbc16913 | ||
|
|
02d6222031 | ||
|
|
84c2d1014b | ||
|
|
f754d38f30 | ||
|
|
a498332c96 | ||
|
|
8283401d2b | ||
|
|
a67b42711d | ||
|
|
c7635f8f21 | ||
|
|
aecef0eaed | ||
|
|
69af52b99e | ||
|
|
a5da0ec34a | ||
|
|
b1784fd378 | ||
|
|
9b45e6b831 | ||
|
|
95e8812370 | ||
|
|
4480479994 | ||
|
|
09d967dfbd | ||
|
|
eb57e1f25c | ||
|
|
72f08b1095 | ||
|
|
6988f4325b | ||
|
|
e2b6eaf028 | ||
|
|
438de98926 | ||
|
|
c85443c326 | ||
|
|
7d3e55a84a | ||
|
|
150f781d05 | ||
|
|
62dd4a2258 | ||
|
|
6e4152d3b0 | ||
|
|
d1251c21db | ||
|
|
399bc037fa | ||
|
|
e16e90e9b9 | ||
|
|
b84faa58b6 | ||
|
|
a1ac472f9e | ||
|
|
2810323aad | ||
|
|
80d680b0a6 | ||
|
|
fcbb25a882 | ||
|
|
b4a1fe0aa3 | ||
|
|
4bb6e3d47d | ||
|
|
346f5519c7 | ||
|
|
3055a664f9 | ||
|
|
05b0a5d3d5 | ||
|
|
28982cbd2d | ||
|
|
34c9a486bf | ||
|
|
c143b75711 | ||
|
|
61da46ff11 | ||
|
|
d6c155c477 | ||
|
|
b375870693 | ||
|
|
530e5778cc | ||
|
|
b109c866b7 | ||
|
|
c00202d51e | ||
|
|
5b1ac3ccc6 | ||
|
|
336501382e | ||
|
|
23c270bc0f | ||
|
|
9f24b17b4a | ||
|
|
888919c2e9 | ||
|
|
a51fb418b2 | ||
|
|
3330af6cd4 | ||
|
|
1a25e57069 | ||
|
|
00050b91da | ||
|
|
4398f110f1 | ||
|
|
668cc9ef78 | ||
|
|
9b50ce3000 | ||
|
|
d8d33f5a3e | ||
|
|
fbe171e280 | ||
|
|
29cd63bea2 | ||
|
|
055abc3e4c | ||
|
|
de8d216c54 | ||
|
|
634fbb54b8 | ||
|
|
e4ea8690db | ||
|
|
49e8cf17a4 | ||
|
|
522213a28f | ||
|
|
bf89063212 | ||
|
|
b2c937e03a | ||
|
|
06a8f74f28 | ||
|
|
46525fbfe8 | ||
|
|
d94663312f | ||
|
|
d690268b43 | ||
|
|
28df09b103 | ||
|
|
14ef2a88ef | ||
|
|
a48402153b | ||
|
|
475124ac6e | ||
|
|
c1b0067a97 | ||
|
|
30ac2ae550 | ||
|
|
6df9673936 | ||
|
|
722a4061ac | ||
|
|
bfeb897a6d | ||
|
|
2a9ce31d25 | ||
|
|
a618a56754 | ||
|
|
fabb2c5541 | ||
|
|
f12e7ac27c | ||
|
|
a86d850688 | ||
|
|
8750491323 | ||
|
|
48eb195ee0 | ||
|
|
83b9348c91 | ||
|
|
f20e260a9a | ||
|
|
a58ee0a4e9 | ||
|
|
b1a790b851 | ||
|
|
fc89e17a3c | ||
|
|
30d2f07360 | ||
|
|
ced7ec0c97 | ||
|
|
e7b371b355 | ||
|
|
0fd45396ce | ||
|
|
d4e40101f7 | ||
|
|
b0a6d04f9f | ||
|
|
cb2aa2792f | ||
|
|
ee543f184a | ||
|
|
50650ec314 | ||
|
|
104e953dd3 | ||
|
|
dae7399f7e | ||
|
|
0325c46a43 | ||
|
|
32aee50bc6 | ||
|
|
c50f096ee3 | ||
|
|
a36b7f4934 | ||
|
|
4d07797b40 | ||
|
|
e1cedf7fd2 | ||
|
|
c5c542da6c | ||
|
|
1199186652 | ||
|
|
195fb17458 | ||
|
|
e738f760ae | ||
|
|
a695f36f0b | ||
|
|
b99a6a0fed | ||
|
|
42fa7ceaac | ||
|
|
158266684d | ||
|
|
13326b89e5 | ||
|
|
bc17c1cb2e | ||
|
|
909072ad54 | ||
|
|
8259a0dcb4 | ||
|
|
182d17cd33 | ||
|
|
bfd38ca891 | ||
|
|
af404cf6ae | ||
|
|
ce26630596 | ||
|
|
dab6179163 | ||
|
|
f8a6252339 | ||
|
|
e300ddfdcc | ||
|
|
0f5985889b | ||
|
|
48f85b4039 | ||
|
|
b46740bd33 | ||
|
|
67d48c7682 | ||
|
|
8585e0060d | ||
|
|
fdca066f90 | ||
|
|
866416b65e | ||
|
|
fe618642eb | ||
|
|
15be9bb5ea | ||
|
|
1b514d3cbc | ||
|
|
77502c7908 | ||
|
|
fc08a9a165 | ||
|
|
73d5209743 | ||
|
|
399073af7b | ||
|
|
93d58ec74a | ||
|
|
1b2b61169a | ||
|
|
2f5a6c1399 | ||
|
|
fb573b2c4d | ||
|
|
903512c02b | ||
|
|
a4c04f2c2b | ||
|
|
033ca547d5 | ||
|
|
b1614182fe | ||
|
|
fe3a781de0 | ||
|
|
b1edc30cb4 | ||
|
|
70fd146eb1 | ||
|
|
eb63d0bb1a | ||
|
|
081e14f88f | ||
|
|
292586d96f | ||
|
|
5596354446 | ||
|
|
7701ba4069 | ||
|
|
ec83f827b1 | ||
|
|
9a9be49b63 | ||
|
|
fc726204e3 | ||
|
|
0b0b688c17 | ||
|
|
7b7913fab1 | ||
|
|
e7e6c96f3e | ||
|
|
d7d6ff2fed | ||
|
|
3770264901 | ||
|
|
48b4855502 | ||
|
|
f51c38357f | ||
|
|
5ee706b585 | ||
|
|
e5585bd846 | ||
|
|
53e4f3c89d | ||
|
|
e7990ed810 | ||
|
|
cb0e56639c | ||
|
|
421441aba1 | ||
|
|
52d0344269 | ||
|
|
e807967655 | ||
|
|
0374044790 | ||
|
|
67241a46a8 | ||
|
|
9074608bd8 | ||
|
|
92c34e8e00 | ||
|
|
1748fb29c5 | ||
|
|
89e728f78a | ||
|
|
f0e0322923 | ||
|
|
0a3bc99d9a | ||
|
|
76e0dfef97 | ||
|
|
53efa98c15 | ||
|
|
2f59bdfe91 | ||
|
|
6d7f10c0c6 | ||
|
|
55c8ad0141 | ||
|
|
db0f610bc1 | ||
|
|
3c7d4e67f4 | ||
|
|
e294617d10 | ||
|
|
27554380d3 | ||
|
|
13d4803056 | ||
|
|
daf0c0c048 | ||
|
|
b9478d64b7 | ||
|
|
b6968afd5d | ||
|
|
ab67327ec0 | ||
|
|
53470fa7ad | ||
|
|
ccf029dabe | ||
|
|
ac18bd16b4 | ||
|
|
a1b71202e7 | ||
|
|
68455cf996 | ||
|
|
d9724f3b46 | ||
|
|
4cd35e8737 | ||
|
|
293119cf6c | ||
|
|
32d034abd5 |
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
|
||||||
306
AUTHORS
306
AUTHORS
|
|
@ -1,26 +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>
|
||||||
|
- 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
|
||||||
```````````````````````
|
```````````````````````
|
||||||
|
|
||||||
- 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
|
||||||
|
- Jesús Espino
|
||||||
|
- Rémy HUBSCHER
|
||||||
|
- go2null
|
||||||
|
- Andrew Janke
|
||||||
|
- David Baumgold
|
||||||
|
- Jan Schulz
|
||||||
|
- Kenneth Reitz
|
||||||
|
- Mark Pitman
|
||||||
|
- Peter Benjamin
|
||||||
|
- Günther Grill
|
||||||
|
- Luke Childs
|
||||||
|
- Sasha Khamkov
|
||||||
|
- equt
|
||||||
|
- oikarinen
|
||||||
|
- vyas
|
||||||
|
- Don Harper
|
||||||
|
- Pierre Ayoub
|
||||||
|
- Robin Winslow
|
||||||
|
- Ross Smith II
|
||||||
|
- Yi EungJun
|
||||||
|
- grindhold
|
||||||
|
- wyattscarpenter
|
||||||
|
- Aggelos Orfanakos
|
||||||
|
- Camille Reynders
|
||||||
|
- Carlos Prado
|
||||||
|
- Chris Hall
|
||||||
|
- Damian Krzeminski
|
||||||
|
- NANRI
|
||||||
|
- Alexander Krasnukhin
|
||||||
|
- Phally
|
||||||
|
- Rico Sta. Cruz
|
||||||
|
- Takuma Yamaguchi
|
||||||
|
- thomas menzel
|
||||||
|
- vr8ce
|
||||||
|
- Brian J Brennan
|
||||||
|
- Dominik Gedon
|
||||||
|
- Hogan Long
|
||||||
|
- Ivan Malopinsky
|
||||||
|
- John Bachir
|
||||||
|
- Jonathan "Duke" Leto
|
||||||
|
- Julio Napurí
|
||||||
|
- Justin Dugger
|
||||||
|
- Nils Winkler
|
||||||
|
- Philipp Klose
|
||||||
|
- Raphael Boidol
|
||||||
|
- Richard Russon
|
||||||
|
- Sam Bostock
|
||||||
|
- Vladimir Jimenez
|
||||||
|
- gisphm
|
||||||
|
- jacobherrington
|
||||||
|
- overengineer
|
||||||
|
- phigoro
|
||||||
|
- wooorm
|
||||||
|
- Alexis GRIMALDI
|
||||||
|
- Allan Odgaard
|
||||||
|
- Andre Cerqueira
|
||||||
|
- Austin Ziegler
|
||||||
|
- Beth Skurrie
|
||||||
|
- Brice Dutheil
|
||||||
|
- Curtis McEnroe
|
||||||
|
- Daniele Paolella
|
||||||
|
- David Rogers
|
||||||
|
- Devin Withers
|
||||||
|
- Domenico Rotiroti
|
||||||
|
- Evan Grim
|
||||||
|
- Florian H
|
||||||
|
- Gert Van Gool
|
||||||
|
- Guillaume Seren
|
||||||
|
- Hozefa
|
||||||
|
- Igor Ostapenko
|
||||||
|
- Jean Jordaan
|
||||||
|
- Joshua Li
|
||||||
|
- Justin Guenther
|
||||||
|
- Kylie McClain
|
||||||
|
- Marc Harter
|
||||||
|
- Matiss
|
||||||
|
- Nate Jones
|
||||||
|
- Newell Zhu
|
||||||
|
- Patryk Małek
|
||||||
|
- Paul Schreiber
|
||||||
|
- Richard Littauer
|
||||||
|
- Tjaart van der Walt
|
||||||
|
- Tom Vincent
|
||||||
|
- Wil Moore III
|
||||||
|
- William Montgomery
|
||||||
|
- Ye Lin Aung
|
||||||
|
- luozexuan
|
||||||
|
- roxchgt
|
||||||
|
- soffolk
|
||||||
|
- 0xflotus
|
||||||
|
- Abdullah
|
||||||
|
- Adam Parkin
|
||||||
|
- Adriaan Zonnenberg
|
||||||
|
- Akim Demaille
|
||||||
|
- Alessandro Pagiaro
|
||||||
|
- Alex McHale
|
||||||
|
- Aloxaf
|
||||||
|
- Amir Tocker
|
||||||
|
- Ammar Najjar
|
||||||
|
- Amory Meltzer
|
||||||
|
- Andreas Duering
|
||||||
|
- Andrei Petcu
|
||||||
|
- Andrew Griffiths
|
||||||
|
- Andrew Marcinkevičius
|
||||||
|
- Andrew Starr-Bochicchio
|
||||||
|
- Andrew Sullivan Cant
|
||||||
|
- Andrey Elizarov
|
||||||
|
- Angel Aguilera
|
||||||
|
- Antoine Beaupré
|
||||||
|
- Aurélien Scoubeau
|
||||||
|
- Balazs Nadasdi
|
||||||
|
- Ben Parnell
|
||||||
|
- Beni Ben Zikry
|
||||||
|
- Brandon Zylstra
|
||||||
|
- Brian Goad
|
||||||
|
- Brian Murrell
|
||||||
|
- Bruno Sutic
|
||||||
|
- Caleb Maclennan
|
||||||
|
- Carl Casbolt
|
||||||
|
- Carlos Rodríguez
|
||||||
|
- Christophe Badoit
|
||||||
|
- Ciro Nunes
|
||||||
|
- CleanMachine1
|
||||||
|
- CodeByZach
|
||||||
|
- Craig MacGregor
|
||||||
|
- Dan Goodliffe
|
||||||
|
- Dan Jackson
|
||||||
|
- Dan Kilman
|
||||||
|
- Daniel Bruder
|
||||||
|
- Daniel Lublin
|
||||||
|
- Daniel Schildt
|
||||||
|
- Dave James Miller
|
||||||
|
- David Hartmann
|
||||||
|
- Dominic Barnes
|
||||||
|
- Dung Quang
|
||||||
|
- Edward Betts
|
||||||
|
- Eli Schwartz
|
||||||
|
- Emil Kjelsrud
|
||||||
|
- Erik Skultety
|
||||||
|
- François M
|
||||||
|
- George Crabtree
|
||||||
|
- Gerrit-K
|
||||||
|
- Greg Allen
|
||||||
|
- Guilhem Saurel
|
||||||
|
- Guillermo Rauch
|
||||||
|
- Gunnlaugur Thor Briem
|
||||||
|
- Hasse Ramlev Hansen
|
||||||
|
- Heiko Becker
|
||||||
|
- Hugo Ruiz-Mireles
|
||||||
|
- Isaac Mungai
|
||||||
|
- J.C. Yamokoski
|
||||||
|
- Jack O. Wasey
|
||||||
|
- James Manning
|
||||||
|
- James Zhu
|
||||||
|
- Jan Krueger
|
||||||
|
- Jared Baur
|
||||||
|
- Jarod Stewart
|
||||||
|
- Jason Young
|
||||||
|
- Jens K. Mueller
|
||||||
|
- Jeremy Lin
|
||||||
|
- Jesse Sipprell
|
||||||
|
- Jianjin Fan
|
||||||
|
- Jobin Kurian
|
||||||
|
- Johannes Ewald
|
||||||
|
- John Evans
|
||||||
|
- John Hoffmann
|
||||||
|
- Jon Ander Peñalba
|
||||||
|
- Josh McKinney
|
||||||
|
- Joshua Appelman
|
||||||
|
- José María Gutiérrez @TheTechOddBug
|
||||||
|
- Katrin Leinweber
|
||||||
|
- Kevin Woo
|
||||||
|
- Konstantin Schukraft
|
||||||
|
- Kurban Mallachiev
|
||||||
|
- Leandro López
|
||||||
|
- Lone
|
||||||
|
- Luis Miguel Hernanz
|
||||||
|
- Maarten Winter
|
||||||
|
- Marco Glasbergen
|
||||||
|
- Matan Rosenberg
|
||||||
|
- Mathieu D. (MatToufoutu)
|
||||||
|
- Matt
|
||||||
|
- Matt Colyer
|
||||||
|
- Matt Headley
|
||||||
|
- Matthew Avant
|
||||||
|
- Mattias Andersson
|
||||||
|
- Michael Komitee
|
||||||
|
- Michael Matuzak
|
||||||
|
- Michele Bologna
|
||||||
|
- Montak Oleg
|
||||||
|
- Moritz Grauel
|
||||||
|
- Nate Eagleson
|
||||||
|
- Nate Fischer
|
||||||
|
- Nathan Rajlich
|
||||||
|
- Nick Campbell
|
||||||
|
- Nick Payne
|
||||||
|
- Nicolas Kosinski
|
||||||
- Niklas Fiekas
|
- Niklas Fiekas
|
||||||
|
- Oliver Kopp
|
||||||
|
- Orestis
|
||||||
|
- Peter Bittner
|
||||||
|
- Piotr Górski
|
||||||
|
- Prayag Verma
|
||||||
|
- Przemek Kitszel
|
||||||
|
- R. Martinho Fernandes
|
||||||
|
- Raphael Fleischlin
|
||||||
|
- Rasmus Wriedt Larsen
|
||||||
|
- René
|
||||||
|
- Revisor
|
||||||
|
- Ricardo Fernández Serrata
|
||||||
|
- Riceball LEE
|
||||||
|
- Rob Kennedy
|
||||||
|
- Robin von Bülow
|
||||||
|
- Russ
|
||||||
|
- Ryan Bohn
|
||||||
|
- Sachin Gupta
|
||||||
|
- Sam Thursfield
|
||||||
|
- Sandro
|
||||||
|
- Sascha Andres
|
||||||
|
- SchleimKeim
|
||||||
|
- Sean Molenaar
|
||||||
|
- Sebastian Gniazdowski
|
||||||
|
- Sebastián Mancilla
|
||||||
|
- Simon Tate
|
||||||
|
- Stefan VanBuren
|
||||||
|
- Stephen Mathieson
|
||||||
|
- Steve Mao
|
||||||
|
- Stu Feldt
|
||||||
|
- SukkaW
|
||||||
|
- Tim Preston
|
||||||
|
- Timothy Hwang
|
||||||
|
- Tin Lai
|
||||||
|
- Todd Wolfson
|
||||||
|
- Tom Andrade
|
||||||
|
- Tom Ashworth
|
||||||
|
- Tony
|
||||||
|
- TweeKane
|
||||||
|
- Valérian Galliat
|
||||||
|
- Vitaly Chikunov
|
||||||
|
- Wei Lee
|
||||||
|
- Wei Wu
|
||||||
|
- Wen Sun
|
||||||
|
- Wiktor Żurawik
|
||||||
|
- Xavier Krantz
|
||||||
|
- Xiaopei Li
|
||||||
|
- Zeeshan Ahmed
|
||||||
|
- ax1036
|
||||||
|
- creynders
|
||||||
|
- dead-horse
|
||||||
|
- eszabpt
|
||||||
|
- fengkx
|
||||||
|
- johnpyp
|
||||||
|
- jykntr
|
||||||
|
- kang
|
||||||
|
- meza
|
||||||
|
- neydroid
|
||||||
|
- nulltask
|
||||||
|
- phanium
|
||||||
|
- sgleizes
|
||||||
|
- tfendin
|
||||||
|
- tiemonl
|
||||||
|
- zentarul
|
||||||
|
- zeroDivisible
|
||||||
|
- zhiyanfoo
|
||||||
|
- Áron Hoffmann
|
||||||
|
- Étienne BERSAC
|
||||||
|
- ☃ pitr
|
||||||
|
- 单元源
|
||||||
|
|
|
||||||
34
CONTRIBUTING.md
Normal file
34
CONTRIBUTING.md
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
# Contributing
|
||||||
|
|
||||||
|
Thanks for contributing! Please read this document before you make a PR.
|
||||||
|
|
||||||
|
## Supported Platforms
|
||||||
|
|
||||||
|
Any changes must support the following platforms:
|
||||||
|
|
||||||
|
- macOS
|
||||||
|
- Linux
|
||||||
|
- OpenBSD (You may need to browse their man page)
|
||||||
|
|
||||||
|
Your change must also be compatible with the dependency constraints that we specify in [Installation](./Installation.md). If you aren't sure if a feature is compatible, check the manual or release notes. For example, the Bash changelog is [here](https://git.savannah.gnu.org/cgit/bash.git/tree/NEWS?h=devel).
|
||||||
|
|
||||||
|
If you aren't able to test your new command on a platform, make that clear in your PR; someone else may be able to test it on their system.
|
||||||
|
|
||||||
|
## Adding a New Command
|
||||||
|
|
||||||
|
Let's say you wish to add a new command. Assuming your new command is named `foo`:
|
||||||
|
|
||||||
|
1. Write a bash script under `./bin` called `git-foo`. The script should be started with `#!/usr/bin/env bash`.
|
||||||
|
2. Read `./man/Readme.md` and write documentation for `git-foo`.
|
||||||
|
3. Don't forget to introduce it in `Commands.md`.
|
||||||
|
4. Update `./etc/git-extras-completion.zsh`. Just follow existing code.
|
||||||
|
5. (Optional) Update `./etc/bash_completion.sh`.
|
||||||
|
6. (Optional) Update `./etc/git-extras.fish`.
|
||||||
|
7. (Optional) Add a test under `./tests`.
|
||||||
|
8. Run `./check_integrity.sh foo` to check if all done. (You may also run `./check_integrity.sh` to check all commands.)
|
||||||
|
|
||||||
|
You are welcome to open up an issue to discuss new commands or features before opening a pull request.
|
||||||
|
|
||||||
|
## Submitting a pull request
|
||||||
|
|
||||||
|
Please follow the suggestion in `./.github/PULL_REQUEST_TEMPLATE.md`.
|
||||||
1729
Commands.md
Normal file
1729
Commands.md
Normal file
File diff suppressed because it is too large
Load diff
1465
History.md
1465
History.md
File diff suppressed because it is too large
Load diff
196
Installation.md
Normal file
196
Installation.md
Normal file
|
|
@ -0,0 +1,196 @@
|
||||||
|
# Installation
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
|
||||||
|
Some commands require extra dependencies which are unavailable in some platforms.
|
||||||
|
You may need to install them manually. They are:
|
||||||
|
|
||||||
|
* bash 4.0+
|
||||||
|
* Git 2.17+
|
||||||
|
* `column`
|
||||||
|
* `awk`
|
||||||
|
* `find`
|
||||||
|
* `tput`
|
||||||
|
* cURL (only required for `git-fork`, `git-ignore-io`, `git-pull-request`)
|
||||||
|
* `ps` (only required for `git-changelog`)
|
||||||
|
* `rsync` (only required for `git-rscp`, `git-scp`)
|
||||||
|
* `xargs` (only required for `git-delete-merged-branches`, `git-force-clone`, `git-sed`, `git-scp`)
|
||||||
|
|
||||||
|
If `bash --version` shows a lower version, you need to update it.
|
||||||
|
For example, the default bash in Mac is usually too old and you may need to update it via `brew install bash`.
|
||||||
|
|
||||||
|
## Installing with a package manager
|
||||||
|
|
||||||
|
Note that only the Homebrew package is maintained by the git-extras developers directly.
|
||||||
|
Other packages are maintained by the distribution's packagers or third-party volunteers.
|
||||||
|
|
||||||
|
[](https://repology.org/project/git-extras/versions)
|
||||||
|
|
||||||
|
### Debian
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ sudo $apt_pref update
|
||||||
|
$ sudo $apt_pref install git-extras
|
||||||
|
```
|
||||||
|
|
||||||
|
### Fedora
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ sudo dnf install git-extras
|
||||||
|
```
|
||||||
|
|
||||||
|
### openSUSE
|
||||||
|
|
||||||
|
Substitute your openSUSE version in the command below (in this case we are considering openSUSE Leap 15.6):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ sudo zypper ar https://download.opensuse.org/repositories/devel:/tools:/scm/15.6/devel:tools:scm.repo
|
||||||
|
```
|
||||||
|
|
||||||
|
and install it:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ sudo zypper in -y git-extras
|
||||||
|
```
|
||||||
|
|
||||||
|
### RHEL/CentOS (requires [EPEL](https://fedoraproject.org/wiki/EPEL))
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ sudo yum install git-extras
|
||||||
|
```
|
||||||
|
|
||||||
|
### Ubuntu
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ sudo apt-get install git-extras
|
||||||
|
```
|
||||||
|
|
||||||
|
### Nix/NixOS
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ nix-env -i git-extras
|
||||||
|
```
|
||||||
|
|
||||||
|
### CRUX
|
||||||
|
|
||||||
|
[Abdullah](https://github.com/AWAN) has written a [Pkgfile](https://abdullah.today/ports/git-extras/Pkgfile) for his beloved [distro](https://crux.nu).
|
||||||
|
|
||||||
|
### Homebrew
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ brew install git-extras
|
||||||
|
```
|
||||||
|
|
||||||
|
Installing from Homebrew will not give you the option omit certain `git-extras` if they conflict with existing git aliases. To have this option, build from source.
|
||||||
|
|
||||||
|
### Arch Linux
|
||||||
|
|
||||||
|
* [git-extras](https://aur.archlinux.org/packages/git-extras/)
|
||||||
|
* [git-extras-git](https://aur.archlinux.org/packages/git-extras-git/)
|
||||||
|
|
||||||
|
### Windows
|
||||||
|
|
||||||
|
First, please install `Git for Windows 2.x` from 'https://github.com/git-for-windows/git/releases'.
|
||||||
|
Second, clone the `git-extras` repo into any folder you like.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/tj/git-extras.git
|
||||||
|
# checkout the latest tag (optional)
|
||||||
|
git checkout $(git describe --tags $(git rev-list --tags --max-count=1))
|
||||||
|
```
|
||||||
|
|
||||||
|
After that, execute the `install.cmd` in the command prompt. If you installed
|
||||||
|
git as admin, you need to run this prompt as admin, too. Per default, it finds
|
||||||
|
a `Git for Windows 2.x` if it's in the path (first path in `where git.exe` wins)
|
||||||
|
or installed in the default location `%ProgramFiles%\Git`. The fallback is
|
||||||
|
`C:\SCM\PortableGit`. If you didn't install git into one of these dirs, you have
|
||||||
|
to supply the path for the install location, e.g. if git is installed
|
||||||
|
in `c:\git`:
|
||||||
|
|
||||||
|
```batch
|
||||||
|
install.cmd "C:\git"
|
||||||
|
```
|
||||||
|
|
||||||
|
Last, to use `git summary` and `git ignore-io`, you need to copy
|
||||||
|
`column.exe` from a [msys2][1] installation from `folder-your-msys2-installed/usr/bin`
|
||||||
|
to `folder-your-git-installed/usr/bin` or wait for git 2.7.1, which will include column.exe.
|
||||||
|
|
||||||
|
### FreeBSD
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ pkg install git-extras
|
||||||
|
```
|
||||||
|
|
||||||
|
### BSD
|
||||||
|
|
||||||
|
Use the instructions to build from source below. Make sure you are using `gmake` (GNU `make`) instead of `make`.
|
||||||
|
|
||||||
|
## Building from source
|
||||||
|
|
||||||
|
Read [Dependencies](#dependencies) and ensure they are installed.
|
||||||
|
|
||||||
|
Obtain the git-extras source by cloning [its GitHub repo](https://github.com/tj/git-extras.git) or downloading a tarball of a [release](https://github.com/tj/git-extras/releases). Then install it by doing `make install` from the source tree.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ git clone https://github.com/tj/git-extras.git
|
||||||
|
$ cd git-extras
|
||||||
|
# checkout the latest tag
|
||||||
|
$ git checkout $(git describe --tags $(git rev-list --tags --max-count=1))
|
||||||
|
$ [sudo] make install
|
||||||
|
```
|
||||||
|
|
||||||
|
By default, git-extras is installed under `/usr/local`. To install it at an
|
||||||
|
alternate location, specify a `PREFIX` when calling `make`.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Non-root users can install under their home directory
|
||||||
|
make install PREFIX=$HOME/.local
|
||||||
|
|
||||||
|
# For third-party software kept under /opt
|
||||||
|
make install PREFIX=/opt
|
||||||
|
```
|
||||||
|
|
||||||
|
If you want to relocate the bulk of the installation but still have configuration
|
||||||
|
files installed to the system `/etc` dir or other alternate location, you can
|
||||||
|
specify `SYSCONFDIR` in addition to `PREFIX`.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
$ sudo make install PREFIX=/usr/local SYSCONFDIR=/etc
|
||||||
|
```
|
||||||
|
|
||||||
|
See the Makefile for details on advanced configuration options.
|
||||||
|
|
||||||
|
## Installing from network
|
||||||
|
|
||||||
|
One-liner:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -sSL https://raw.githubusercontent.com/tj/git-extras/main/install.sh | sudo bash /dev/stdin
|
||||||
|
```
|
||||||
|
|
||||||
|
## Installing as Zsh plugin
|
||||||
|
|
||||||
|
[ZInit](https://github.com/zdharma/zinit) can install git-extras by using:
|
||||||
|
|
||||||
|
```zsh
|
||||||
|
zinit ice as"program" pick"$ZPFX/bin/git-*" src"etc/git-extras-completion.zsh" make"PREFIX=$ZPFX"
|
||||||
|
zinit light tj/git-extras
|
||||||
|
|
||||||
|
# or with the for syntax + async load
|
||||||
|
zinit lucid wait'0a' for \
|
||||||
|
as"program" pick"$ZPFX/bin/git-*" src"etc/git-extras-completion.zsh" make"PREFIX=$ZPFX" tj/git-extras
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
`$ZPFX` is `~/.zinit/polaris` by default. Use `zinit update tj/git-extras` to update.
|
||||||
|
This method installs in `$HOME`, so you don't need to ask administrator to install package.
|
||||||
|
|
||||||
|
## Updating
|
||||||
|
|
||||||
|
If you installed git-extras with a package manager, use that package manager's tools to update it.
|
||||||
|
|
||||||
|
If you installed git-extras from source, you can run `git-extras update` to update it to the latest release. Be aware that this may lose any configuration options you specified during the original installation.
|
||||||
|
|
||||||
|
Enjoy `git-extras`!
|
||||||
|
|
||||||
|
[1]: https://sourceforge.net/projects/msys2/
|
||||||
2
LICENSE
2
LICENSE
|
|
@ -1,6 +1,6 @@
|
||||||
(The MIT License)
|
(The MIT License)
|
||||||
|
|
||||||
Copyright (c) 2010-2011 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
|
||||||
|
|
|
||||||
124
Makefile
124
Makefile
|
|
@ -1,27 +1,116 @@
|
||||||
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"
|
||||||
|
|
||||||
|
COMMANDS = $(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)"
|
|
||||||
@$(foreach BIN, $(BINS), \
|
|
||||||
echo "... installing $(notdir $(BIN))"; \
|
|
||||||
cp -f $(BIN) $(DESTDIR)$(BINPREFIX); \
|
|
||||||
)
|
|
||||||
cp -f man/git-*.1 $(DESTDIR)$(MANPREFIX)
|
|
||||||
@mkdir -p $(DESTDIR)/etc/bash_completion.d
|
|
||||||
cp -f etc/bash_completion.sh $(DESTDIR)/etc/bash_completion.d/git-extras
|
|
||||||
|
|
||||||
man/%.html: man/%.md
|
install: check
|
||||||
|
@if [ "$(INSTALL_VIA)" = brew ]; then \
|
||||||
|
git apply brew-release.patch || { echo "Can't apply brew release patch"; exit 1; } \
|
||||||
|
fi
|
||||||
|
mkdir -p $(DESTDIR)$(MANPREFIX)
|
||||||
|
mkdir -p $(DESTDIR)$(BINPREFIX)
|
||||||
|
@echo '... installing bins to '$(DESTDIR)$(BINPREFIX)
|
||||||
|
@echo '... installing man pages to '$(DESTDIR)$(MANPREFIX)
|
||||||
|
$(eval TEMPFILE := $(shell mktemp -q $${TMPDIR:-/tmp}/git-extras.XXXXXX 2>/dev/null || mktemp -q))
|
||||||
|
@# chmod from rw-------(default) to rwxrwxr-x, so that users can exec the scripts
|
||||||
|
@chmod 775 $(TEMPFILE)
|
||||||
|
$(eval EXISTED_ALIASES := $(shell \
|
||||||
|
git config --get-regexp 'alias\..*' | awk '{print "git-" substr($$1, 7)}'))
|
||||||
|
@$(foreach COMMAND, $(COMMANDS), \
|
||||||
|
should_install='yes'; \
|
||||||
|
if [ ! -z "$(filter $(COMMAND), $(EXISTED_ALIASES))" ] && [ "$$SKIP_CONFLICT_CHECK" != yes ]; then \
|
||||||
|
read -p "$(COMMAND) conflicts with an alias, still install it? [y/n]: " answer; \
|
||||||
|
if [ "$$answer" = 'n' ] || [ "$$answer" = 'N' ]; then \
|
||||||
|
should_install="no"; \
|
||||||
|
fi; \
|
||||||
|
fi; \
|
||||||
|
if [ "$$should_install" = 'yes' ]; then \
|
||||||
|
echo "... installing $(COMMAND)"; \
|
||||||
|
head -1 bin/$(COMMAND) > $(TEMPFILE); \
|
||||||
|
cat $(LIB) >> $(TEMPFILE); \
|
||||||
|
if ! grep "$(COMMAND)" not_need_git_repo >/dev/null; then \
|
||||||
|
cat ./helper/is-git-repo >> $(TEMPFILE); \
|
||||||
|
fi; \
|
||||||
|
if grep "$(COMMAND)" need_git_commit >/dev/null; then \
|
||||||
|
cat ./helper/has-git-commit >> $(TEMPFILE); \
|
||||||
|
fi; \
|
||||||
|
tail -n +2 bin/$(COMMAND) >> $(TEMPFILE); \
|
||||||
|
cp -f $(TEMPFILE) $(DESTDIR)$(BINPREFIX)/$(COMMAND); \
|
||||||
|
fi; \
|
||||||
|
)
|
||||||
|
@if [ -z "$(wildcard man/git-*.1)" ]; then \
|
||||||
|
echo "WARNING: man pages not created, use 'make docs' (which requires 'ronn' ruby lib)"; \
|
||||||
|
else \
|
||||||
|
cp -f man/git-*.1 $(DESTDIR)$(MANPREFIX); \
|
||||||
|
echo "cp -f man/git-*.1 $(DESTDIR)$(MANPREFIX)"; \
|
||||||
|
fi
|
||||||
|
@mkdir -p $(COMPL_DIR)
|
||||||
|
cp -f etc/bash_completion.sh $(COMPL_DIR)/git-extras
|
||||||
|
@echo ""
|
||||||
|
@echo "If you are a zsh user, you may want to 'source $(CODE_DIR)etc/git-extras-completion.zsh'" \
|
||||||
|
"and put this line into ~/.zshrc to enable zsh completion"
|
||||||
|
@echo "If you are a fish user, you may want to copy or link '$(CODE_DIR)etc/git-extras.fish'" \
|
||||||
|
"to '~/.config/fish/completions/'"
|
||||||
|
|
||||||
|
man/index.txt: $(MANS)
|
||||||
|
echo '# manuals' > $@.tmp
|
||||||
|
for file in $(sort $^) ; do \
|
||||||
|
extra=$${file%.md} ; \
|
||||||
|
extra=$${extra#man/} ; \
|
||||||
|
echo "$$extra(1) $$extra" >> $@.tmp ; \
|
||||||
|
done
|
||||||
|
mv -f $@.tmp $@
|
||||||
|
|
||||||
|
man/git-extras.md: $(MAN_BINS)
|
||||||
|
ln=$$(awk '/## COMMANDS/{print NR};' $@) ; \
|
||||||
|
awk "NR <= $$ln+1" $@ > $@.tmp
|
||||||
|
for file in $(sort $^) ; do \
|
||||||
|
head -n1 $$file | \
|
||||||
|
sed 's/^/ - **/;s/ -- /** /' >> $@.tmp ; \
|
||||||
|
done
|
||||||
|
ln=$$(awk '/## AUTHOR/{print NR};' $@) ; \
|
||||||
|
awk "NR >= $$ln-1" $@ >> $@.tmp
|
||||||
|
mv -f $@.tmp $@
|
||||||
|
|
||||||
|
man/%.html: man/%.md man/index.txt
|
||||||
ronn \
|
ronn \
|
||||||
--manual "Git Extras" \
|
--manual "Git Extras" \
|
||||||
--html \
|
--html \
|
||||||
|
|
@ -43,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
|
||||||
|
|
||||||
|
|
@ -51,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
|
||||||
|
|
|
||||||
668
Readme.md
668
Readme.md
|
|
@ -2,669 +2,33 @@
|
||||||
|
|
||||||
Little git extras.
|
Little git extras.
|
||||||
|
|
||||||
## Installation
|
|
||||||
|
|
||||||
Clone / Tarball:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ make install
|
|
||||||
```
|
|
||||||
|
|
||||||
One-liner:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ (cd /tmp && git clone --depth 1 https://github.com/visionmedia/git-extras.git && cd git-extras && sudo make install)
|
|
||||||
```
|
|
||||||
|
|
||||||
[MacPorts](http://www.macports.org/)
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ sudo port install git-extras
|
|
||||||
```
|
|
||||||
|
|
||||||
[Brew](http://brew.sh/):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ brew install git-extras
|
|
||||||
```
|
|
||||||
|
|
||||||
## Screencasts
|
## Screencasts
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
|
See the [Installation](Installation.md) page.
|
||||||
|
|
||||||
## Commands
|
## Commands
|
||||||
|
|
||||||
- `git extras`
|
Go to the [Commands](Commands.md) page for basic usage and examples.
|
||||||
- `git squash`
|
|
||||||
- `git summary`
|
|
||||||
- `git effort`
|
|
||||||
- `git changelog`
|
|
||||||
- `git commits-since`
|
|
||||||
- `git count`
|
|
||||||
- `git create-branch`
|
|
||||||
- `git delete-branch`
|
|
||||||
- `git delete-submodule`
|
|
||||||
- `git delete-tag`
|
|
||||||
- `git delete-merged-branches`
|
|
||||||
- `git fresh-branch`
|
|
||||||
- `git graft`
|
|
||||||
- `git alias`
|
|
||||||
- `git ignore`
|
|
||||||
- `git info`
|
|
||||||
- `git fork`
|
|
||||||
- `git release`
|
|
||||||
- `git contrib`
|
|
||||||
- `git repl`
|
|
||||||
- `git undo`
|
|
||||||
- `git gh-pages`
|
|
||||||
- `git setup`
|
|
||||||
- `git touch`
|
|
||||||
- `git obliterate`
|
|
||||||
- `git feature`
|
|
||||||
- `git refactor`
|
|
||||||
- `git bug`
|
|
||||||
- `git local-commits`
|
|
||||||
- `git archive-file`
|
|
||||||
- `git missing`
|
|
||||||
- `git lock`
|
|
||||||
- `git unlock`
|
|
||||||
- `git reset-file`
|
|
||||||
|
|
||||||
## git-extras
|
__GIT utilities__ -- repo summary, repl, changelog population, author commit percentages and more
|
||||||
|
|
||||||
The main `git-extras` command.
|
## Contributing
|
||||||
|
|
||||||
Output the current `--version`:
|
Interested in contributing? Awesome!
|
||||||
|
|
||||||
```bash
|
Please read [Contributing](CONTRIBUTING.md) before you make a PR, thanks!
|
||||||
$ git extras
|
|
||||||
```
|
|
||||||
|
|
||||||
List available commands:
|
## The change of the default branch
|
||||||
|
|
||||||
```bash
|
As of Git Extras 6.4 the assumed default branch name changed from `master` to `main`.
|
||||||
$ git extras --help
|
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`.
|
||||||
```
|
|
||||||
|
|
||||||
Update to the latest `git-extras`:
|
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.
|
||||||
|
|
||||||
```bash
|
For example, `git config git-extras.default-branch master`.
|
||||||
$ git extras update
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
## git-gh-pages
|
|
||||||
|
|
||||||
Sets up the `gh-pages` branch. (See [GitHub Pages](http://pages.github.com/) documentation.)
|
|
||||||
|
|
||||||
## git-[feature|refactor|bug] [finish] <name>
|
|
||||||
|
|
||||||
Create the given feature, refactor, or bug branch `name`:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ git feature dependencies
|
|
||||||
```
|
|
||||||
|
|
||||||
Afterwards, the same command will check it out:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ git checkout master
|
|
||||||
$ git feature dependencies
|
|
||||||
```
|
|
||||||
|
|
||||||
When finished, we can `feature finish` to merge it into the current branch:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ git checkout master
|
|
||||||
$ git feature finish dependencies
|
|
||||||
```
|
|
||||||
|
|
||||||
All of this works with `feature`, `bug`, or `refactor`.
|
|
||||||
|
|
||||||
## git-contrib <author>
|
|
||||||
|
|
||||||
Output `author`'s contributions to a project:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ git contrib visionmedia
|
|
||||||
visionmedia (18):
|
|
||||||
Export STATUS_CODES
|
|
||||||
Replaced several Array.prototype.slice.call() calls with Array.prototype.unshift.call()
|
|
||||||
Moved help msg to node-repl
|
|
||||||
Added multiple arg support for sys.puts(), print(), etc.
|
|
||||||
Fix stack output on socket error
|
|
||||||
...
|
|
||||||
```
|
|
||||||
|
|
||||||
## git-summary
|
|
||||||
|
|
||||||
Outputs a repo summary:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ git summary
|
|
||||||
|
|
||||||
project : git-extras
|
|
||||||
repo age : 10 months ago
|
|
||||||
commits : 163
|
|
||||||
active : 60 days
|
|
||||||
files : 93
|
|
||||||
authors :
|
|
||||||
97 Tj Holowaychuk 59.5%
|
|
||||||
37 Jonhnny Weslley 22.7%
|
|
||||||
8 Kenneth Reitz 4.9%
|
|
||||||
5 Aggelos Orfanakos 3.1%
|
|
||||||
3 Jonathan "Duke" Leto 1.8%
|
|
||||||
2 Gert Van Gool 1.2%
|
|
||||||
2 Domenico Rotiroti 1.2%
|
|
||||||
2 Devin Withers 1.2%
|
|
||||||
2 TJ Holowaychuk 1.2%
|
|
||||||
1 Nick Campbell 0.6%
|
|
||||||
1 Alex McHale 0.6%
|
|
||||||
1 Jason Young 0.6%
|
|
||||||
1 Jens K. Mueller 0.6%
|
|
||||||
1 Guillermo Rauch 0.6%
|
|
||||||
```
|
|
||||||
|
|
||||||
This command can also take a *commitish*, and will print a summary for commits in
|
|
||||||
the commmitish range:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ git summary v42..
|
|
||||||
```
|
|
||||||
|
|
||||||
This command can also take an options `--line`, will print a summary by lines
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ git summary --line
|
|
||||||
|
|
||||||
project : git-extras
|
|
||||||
lines : 8420
|
|
||||||
authors :
|
|
||||||
2905 Tj Holowaychuk 34.5%
|
|
||||||
1901 Jonhnny Weslley 22.6%
|
|
||||||
1474 nickl- 17.5%
|
|
||||||
653 Leila Muhtasib 7.8%
|
|
||||||
275 Tony 3.3%
|
|
||||||
267 Jesús Espino 3.2%
|
|
||||||
199 Philipp Klose 2.4%
|
|
||||||
180 Michael Komitee 2.1%
|
|
||||||
178 Tom Vincent 2.1%
|
|
||||||
119 TJ Holowaychuk 1.4%
|
|
||||||
114 Damian Krzeminski 1.4%
|
|
||||||
66 Kenneth Reitz 0.8%
|
|
||||||
22 Not Committed Yet 0.3%
|
|
||||||
17 David Baumgold 0.2%
|
|
||||||
12 Brian J Brennan 0.1%
|
|
||||||
6 Leandro López 0.1%
|
|
||||||
6 Jan Krueger 0.1%
|
|
||||||
6 Gunnlaugur Thor Briem 0.1%
|
|
||||||
3 Hogan Long 0.0%
|
|
||||||
3 Curtis McEnroe 0.0%
|
|
||||||
3 Alex McHale 0.0%
|
|
||||||
3 Aggelos Orfanakos 0.0%
|
|
||||||
2 Phally 0.0%
|
|
||||||
2 NANRI 0.0%
|
|
||||||
2 Moritz Grauel 0.0%
|
|
||||||
1 Jean Jordaan 0.0%
|
|
||||||
1 Daniel Schildt 0.0%
|
|
||||||
```
|
|
||||||
|
|
||||||
## git-effort [file ....]
|
|
||||||
|
|
||||||
Displays "effort" statistics, currently just the number of commits per file, showing highlighting where the most activity is. The "active days" column is the total number of days which contributed modifications to this file.
|
|
||||||
|
|
||||||
```
|
|
||||||
node (master): git effort --above 15 {src,lib}/*
|
|
||||||
```
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
If you wish to ignore files with commits `<=` a value you may use `--above`:
|
|
||||||
|
|
||||||
```
|
|
||||||
$ git effort --above 5
|
|
||||||
```
|
|
||||||
|
|
||||||
By default `git ls-files` is used, however you may pass one or more files to `git-effort(1)`, for example:
|
|
||||||
|
|
||||||
```
|
|
||||||
$ git effort bin/* lib/*
|
|
||||||
```
|
|
||||||
|
|
||||||
## git-repl
|
|
||||||
|
|
||||||
GIT read-eval-print-loop:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ git repl
|
|
||||||
|
|
||||||
git> ls-files
|
|
||||||
History.md
|
|
||||||
Makefile
|
|
||||||
Readme.md
|
|
||||||
bin/git-changelog
|
|
||||||
bin/git-count
|
|
||||||
bin/git-delete-branch
|
|
||||||
bin/git-delete-tag
|
|
||||||
bin/git-ignore
|
|
||||||
bin/git-release
|
|
||||||
|
|
||||||
git> quit
|
|
||||||
```
|
|
||||||
|
|
||||||
## git-commits-since [date]
|
|
||||||
|
|
||||||
List commits since `date` (defaults to "last week"):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ git commits-since
|
|
||||||
... changes since last week
|
|
||||||
TJ Holowaychuk - Fixed readme
|
|
||||||
TJ Holowaychuk - Added git-repl
|
|
||||||
TJ Holowaychuk - Added git-delete-tag
|
|
||||||
TJ Holowaychuk - Added git-delete-branch
|
|
||||||
|
|
||||||
$ git commits-since yesterday
|
|
||||||
... changes since yesterday
|
|
||||||
TJ Holowaychuk - Fixed readme
|
|
||||||
```
|
|
||||||
|
|
||||||
## git-count
|
|
||||||
|
|
||||||
Output commit count:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ git count
|
|
||||||
|
|
||||||
total 1844
|
|
||||||
```
|
|
||||||
|
|
||||||
Output detailed commit count:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ git count --all
|
|
||||||
|
|
||||||
visionmedia (1285)
|
|
||||||
Tj Holowaychuk (430)
|
|
||||||
Aaron Heckmann (48)
|
|
||||||
csausdev (34)
|
|
||||||
ciaranj (26)
|
|
||||||
Guillermo Rauch (6)
|
|
||||||
Brian McKinney (2)
|
|
||||||
Nick Poulden (2)
|
|
||||||
Benny Wong (2)
|
|
||||||
Justin Lilly (1)
|
|
||||||
isaacs (1)
|
|
||||||
Adam Sanderson (1)
|
|
||||||
Viktor Kelemen (1)
|
|
||||||
Gregory Ritter (1)
|
|
||||||
Greg Ritter (1)
|
|
||||||
ewoudj (1)
|
|
||||||
James Herdman (1)
|
|
||||||
Matt Colyer (1)
|
|
||||||
|
|
||||||
total 1844
|
|
||||||
```
|
|
||||||
|
|
||||||
## git-fork
|
|
||||||
|
|
||||||
Fork the given github <repo>. Like clone but forks first.
|
|
||||||
|
|
||||||
```Shell
|
|
||||||
$ git fork https://github.com/LearnBoost/expect.js
|
|
||||||
```
|
|
||||||
|
|
||||||
or just:
|
|
||||||
|
|
||||||
```Shell
|
|
||||||
$ git fork LearnBoost/expect.js
|
|
||||||
```
|
|
||||||
|
|
||||||
Does the following:
|
|
||||||
- forks the repo (prompts for github username and pass)
|
|
||||||
- clones the repo into the current directory
|
|
||||||
- adds the original repo as a remote so can track upstream changes
|
|
||||||
- all remotes refs use git over ssh
|
|
||||||
|
|
||||||
|
|
||||||
```Shell
|
|
||||||
$ cd expect.js && git remote -v
|
|
||||||
origin git@github.com:<user>/expect.js (fetch)
|
|
||||||
origin git@github.com:<user>/expect.js (push)
|
|
||||||
original git@github.com:LearnBoost/expect.js (fetch)
|
|
||||||
original git@github.com:LearnBoost/expect.js (push)
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
## git-release
|
|
||||||
|
|
||||||
Release commit with the given <tag>:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ git release 0.1.0
|
|
||||||
```
|
|
||||||
|
|
||||||
Does the following:
|
|
||||||
|
|
||||||
- Executes _.git/hooks/pre-release.sh_ (if present)
|
|
||||||
- Commits changes (to changelog etc) with message "Release <tag>"
|
|
||||||
- Tags with the given <tag>
|
|
||||||
- Push the branch / tags
|
|
||||||
- Executes _.git/hooks/post-release.sh_ (if present)
|
|
||||||
|
|
||||||
## git-alias
|
|
||||||
|
|
||||||
Define, search and show aliases.
|
|
||||||
|
|
||||||
Define a new alias:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ git alias last "cat-file commit HEAD"
|
|
||||||
```
|
|
||||||
|
|
||||||
Search for aliases that match a pattern (one argument):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ git alias ^la
|
|
||||||
last = cat-file commit HEAD
|
|
||||||
```
|
|
||||||
|
|
||||||
Show all aliases (no arguments):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ git alias
|
|
||||||
s = status
|
|
||||||
amend = commit --amend
|
|
||||||
rank = shortlog -sn --no-merges
|
|
||||||
whatis = show -s --pretty='tformat:%h (%s, %ad)' --date=short
|
|
||||||
whois = !sh -c 'git log -i -1 --pretty="format:%an <%ae>
|
|
||||||
```
|
|
||||||
|
|
||||||
## git-ignore [pattern ...]
|
|
||||||
|
|
||||||
Too lazy to open up `.gitignore`? Me too!
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ git ignore build "*.o" "*.log"
|
|
||||||
... added 'build'
|
|
||||||
... added '*.o'
|
|
||||||
... added '*.log'
|
|
||||||
```
|
|
||||||
|
|
||||||
Without any patterns, `git-ignore` displays currently ignored patterns:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ git ignore
|
|
||||||
build
|
|
||||||
*.o
|
|
||||||
*.log
|
|
||||||
```
|
|
||||||
|
|
||||||
## git-info
|
|
||||||
|
|
||||||
Show information about the repo:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ git info
|
|
||||||
|
|
||||||
## Remote URLs:
|
|
||||||
|
|
||||||
origin git@github.com:sampleAuthor/git-extras.git (fetch)
|
|
||||||
origin git@github.com:sampleAuthor/git-extras.git (push)
|
|
||||||
|
|
||||||
## Remote Branches:
|
|
||||||
|
|
||||||
origin/HEAD -> origin/master
|
|
||||||
origin/myBranch
|
|
||||||
|
|
||||||
## Local Branches:
|
|
||||||
|
|
||||||
myBranch
|
|
||||||
* master
|
|
||||||
|
|
||||||
## Most Recent Commit:
|
|
||||||
|
|
||||||
commit e3952df2c172c6f3eb533d8d0b1a6c77250769a7
|
|
||||||
Author: Sample Author <sampleAuthor@gmail.com>
|
|
||||||
|
|
||||||
Added git-info command.
|
|
||||||
|
|
||||||
Type 'git log' for more commits, or 'git show <commit id>' for full commit details.
|
|
||||||
|
|
||||||
## Configuration (.git/config):
|
|
||||||
|
|
||||||
color.diff=auto
|
|
||||||
color.status=auto
|
|
||||||
color.branch=auto
|
|
||||||
user.name=Sample Author
|
|
||||||
user.email=sampleAuthor@gmail.com
|
|
||||||
core.repositoryformatversion=0
|
|
||||||
core.filemode=true
|
|
||||||
core.bare=false
|
|
||||||
core.logallrefupdates=true
|
|
||||||
core.ignorecase=true
|
|
||||||
remote.origin.fetch=+refs/heads/*:refs/remotes/origin/*
|
|
||||||
remote.origin.url=git@github.com:mub/git-extras.git
|
|
||||||
branch.master.remote=origin
|
|
||||||
branch.master.merge=refs/heads/master
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
If you wish to omit the config section, you may use `--no-config`:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ git info --no-config
|
|
||||||
```
|
|
||||||
|
|
||||||
## git-create-branch <name>
|
|
||||||
|
|
||||||
Create local and remote branch `name`:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ git create-branch development
|
|
||||||
```
|
|
||||||
|
|
||||||
## git-delete-branch <name>
|
|
||||||
|
|
||||||
Delete local and remote branch `name`:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ git delete-branch integration
|
|
||||||
```
|
|
||||||
|
|
||||||
## git-delete-submodule <name>
|
|
||||||
|
|
||||||
Delete submodule `name`:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ git delete-submodule lib/foo
|
|
||||||
```
|
|
||||||
|
|
||||||
## git-delete-tag <name>
|
|
||||||
|
|
||||||
Delete local and remote tag `name`:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ git delete-tag 0.0.1
|
|
||||||
```
|
|
||||||
|
|
||||||
## git-delete-merged-branches
|
|
||||||
|
|
||||||
Deletes branches that are listed in `git branch --merged`.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ git delete-merged-branches
|
|
||||||
Deleted feature/themes (was c029ab3).
|
|
||||||
Deleted feature/live_preview (was a81b002).
|
|
||||||
Deleted feature/dashboard (was 923befa).
|
|
||||||
...
|
|
||||||
```
|
|
||||||
|
|
||||||
## git-fresh-branch <name>
|
|
||||||
|
|
||||||
Create empty local branch `name`:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ git fresh-branch docs
|
|
||||||
```
|
|
||||||
|
|
||||||
## git-graft <src-branch> [dest-branch]
|
|
||||||
|
|
||||||
Merge commits from `src-branch` into `dest-branch`. (`dest-branch` defaults to `master`.)
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ git graft new_feature dev
|
|
||||||
$ git graft new_feature
|
|
||||||
```
|
|
||||||
|
|
||||||
## git-squash <src-branch|commit ref> [msg]
|
|
||||||
|
|
||||||
Merge commits from `src-branch` into the current branch as a _single_ commit.
|
|
||||||
Also works if a commit reference from the current branch is provided.
|
|
||||||
When `[msg]` is given `git-commit(1)` will be invoked with that message. This is
|
|
||||||
useful when small individual commits within a topic branch are irrelevant and
|
|
||||||
you want to consider the topic as a single change.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ git squash fixed-cursor-styling
|
|
||||||
$ git squash fixed-cursor-styling "Fixed cursor styling"
|
|
||||||
$ git squash 95b7c52
|
|
||||||
$ git squash HEAD~3
|
|
||||||
$ git squash HEAD~3 "Work on a feature"
|
|
||||||
```
|
|
||||||
|
|
||||||
## git-changelog
|
|
||||||
|
|
||||||
Populate a file whose name matches `change|history -i_` with commits
|
|
||||||
since the previous tag. (If there are no tags, populates commits since the project began.)
|
|
||||||
|
|
||||||
Opens the changelog in `$EDITOR` when set.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ git changelog --tag 1.5.2 && cat History.md
|
|
||||||
|
|
||||||
1.5.2 / 2010-08-05
|
|
||||||
==================
|
|
||||||
|
|
||||||
* Docs for git-ignore. Closes #3
|
|
||||||
* Merge branch 'ignore'
|
|
||||||
* Added git-ignore
|
|
||||||
* Fixed <tag> in docs
|
|
||||||
* Install docs
|
|
||||||
* Merge branch 'release'
|
|
||||||
* Added git-release
|
|
||||||
* Passing args to git shortlog
|
|
||||||
* Added --all support to git-count
|
|
||||||
* Initial commit
|
|
||||||
```
|
|
||||||
|
|
||||||
List commits:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ git changelog --list
|
|
||||||
|
|
||||||
* Docs for git-ignore. Closes #3
|
|
||||||
* Merge branch 'ignore'
|
|
||||||
* Added git-ignore
|
|
||||||
* Fixed <tag> in docs
|
|
||||||
* Install docs
|
|
||||||
* Merge branch 'release'
|
|
||||||
* Added git-release
|
|
||||||
* Passing args to git shortlog
|
|
||||||
* Added --all support to git-count
|
|
||||||
* Initial commit
|
|
||||||
```
|
|
||||||
|
|
||||||
## git-undo
|
|
||||||
|
|
||||||
Remove the latest commit:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git undo
|
|
||||||
```
|
|
||||||
|
|
||||||
Remove the latest 3 commits:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git undo 3
|
|
||||||
```
|
|
||||||
|
|
||||||
## git-setup [dir]
|
|
||||||
|
|
||||||
Set up a git repository (if one doesn't exist), add all files, and make an initial commit. `dir` defaults to the current working directory.
|
|
||||||
|
|
||||||
## git-touch [filename]
|
|
||||||
|
|
||||||
Call `touch` on the given file, and add it to the current index. One-step creation of new files.
|
|
||||||
|
|
||||||
## git-obliterate [filename]
|
|
||||||
|
|
||||||
Completely remove a file from the repository, including past commits and tags.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git obliterate secrets.json
|
|
||||||
```
|
|
||||||
|
|
||||||
## git-local-commits
|
|
||||||
|
|
||||||
List all commits on the local branch that have not yet been sent to origin. Any additional arguments will be passed directly to git log.
|
|
||||||
|
|
||||||
## git-archive-file
|
|
||||||
|
|
||||||
Creates an zip archive of the current git repository. The name of the archive will depend on the current HEAD of your git respository.
|
|
||||||
|
|
||||||
## git-missing [branch1] branch2
|
|
||||||
|
|
||||||
Print out which commits are on one branch or the other but not both.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ git missing master
|
|
||||||
< d14b8f0 only on current checked out branch
|
|
||||||
> 97ef387 only on master
|
|
||||||
```
|
|
||||||
|
|
||||||
## git-rebase-patch patch-file
|
|
||||||
|
|
||||||
Given a patch that doesn't apply to the current HEAD, find the latest commit
|
|
||||||
it applies to and do a rebase. For example:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ git rebase-patch test.patch
|
|
||||||
Trying to find a commit the patch applies to...
|
|
||||||
Patch applied to dbcf408dd26 as 7dc8b23ae1a
|
|
||||||
First, rewinding head to replay your work on top of it...
|
|
||||||
Applying: test.patch
|
|
||||||
Using index info to reconstruct a base tree...
|
|
||||||
Falling back to patching base and 3-way merge...
|
|
||||||
Auto-merging README.txt
|
|
||||||
```
|
|
||||||
|
|
||||||
## git-lock [filename]
|
|
||||||
|
|
||||||
Lock a local file `filename`:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ git lock config/database.yml
|
|
||||||
```
|
|
||||||
|
|
||||||
## git-unlock [filename]
|
|
||||||
|
|
||||||
Unlock a local file `filename`
|
|
||||||
|
|
||||||
## git-reset-file [filename] commit
|
|
||||||
|
|
||||||
Reset one file to `HEAD` or certain commit
|
|
||||||
|
|
||||||
Reset one file to HEAD
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ git reset-file .htaccess
|
|
||||||
```
|
|
||||||
|
|
||||||
or reset one file to certain commit
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ git reset-file .htaccess dc82b19
|
|
||||||
```
|
|
||||||
|
|
|
||||||
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 @@
|
||||||
#!/bin/sh
|
#!/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)"\)
|
||||||
|
|
|
||||||
54
bin/git-authors
Executable file
54
bin/git-authors
Executable file
|
|
@ -0,0 +1,54 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
LIST=false
|
||||||
|
NO_EMAIL=false
|
||||||
|
FILE=""
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case $1 in
|
||||||
|
-l|--list )
|
||||||
|
LIST=true
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--no-email )
|
||||||
|
NO_EMAIL=true
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
* )
|
||||||
|
break
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if ! $LIST; then
|
||||||
|
FILE=$1
|
||||||
|
if [ -z "$FILE" ]; then
|
||||||
|
FILE=$(find . -mindepth 1 -maxdepth 1 \( -iname '*authors*' -o -iname '*contributors*' \) | head -n1)
|
||||||
|
if [ -z "$FILE" ]; then
|
||||||
|
FILE='AUTHORS'
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
#
|
||||||
|
# list authors sorted by number of commits (descending).
|
||||||
|
#
|
||||||
|
|
||||||
|
authors() {
|
||||||
|
if $NO_EMAIL; then
|
||||||
|
# email will be used to uniq authors.
|
||||||
|
git shortlog HEAD -sne | awk '{$1=""; sub(" ", ""); print}' | awk -F'<' '!x[$1]++' | awk -F'<' '!x[$2]++' \
|
||||||
|
| awk -F'<' '{gsub(/ +$/, "", $1); print $1}'
|
||||||
|
else
|
||||||
|
git shortlog HEAD -sne | awk '{$1=""; sub(" ", ""); print}' | awk -F'<' '!x[$1]++' | awk -F'<' '!x[$2]++'
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
#
|
||||||
|
# authors.
|
||||||
|
#
|
||||||
|
|
||||||
|
if $LIST; then
|
||||||
|
authors
|
||||||
|
else
|
||||||
|
authors >> "$FILE"
|
||||||
|
fi
|
||||||
11
bin/git-back
11
bin/git-back
|
|
@ -1,11 +0,0 @@
|
||||||
#!/bin/sh
|
|
||||||
|
|
||||||
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
|
||||||
14
bin/git-bug
14
bin/git-bug
|
|
@ -1,14 +0,0 @@
|
||||||
#!/bin/sh
|
|
||||||
|
|
||||||
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
|
|
||||||
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
|
||||||
|
|
@ -1,56 +1,597 @@
|
||||||
#!/bin/sh
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
FILE=""
|
DEF_TAG_RECENT="n.n.n"
|
||||||
LIST=false
|
GIT_LOG_OPTS="$(git config changelog.opts)"
|
||||||
TAG="n.n.n"
|
GIT_LOG_FORMAT="$(git config changelog.format)"
|
||||||
GIT_LOG_OPTS=""
|
[[ -z "$GIT_LOG_FORMAT" ]] && GIT_LOG_FORMAT=' * %s'
|
||||||
|
GIT_MERGELOG_FORMAT="$(git config changelog.mergeformat)"
|
||||||
|
[[ -z "$GIT_MERGELOG_FORMAT" ]] && GIT_MERGELOG_FORMAT=' * %s%n%w(64,4,4)%b'
|
||||||
|
GIT_EDITOR="$(git var GIT_EDITOR)"
|
||||||
|
PROGNAME="git-changelog"
|
||||||
|
|
||||||
while [ "$1" != "" ]; do
|
git_editor() {
|
||||||
case $1 in
|
if test -z "${GIT_EDITOR:+set}"
|
||||||
-l | --list )
|
then
|
||||||
LIST=true
|
GIT_EDITOR="$(git var GIT_EDITOR)" || return $?
|
||||||
;;
|
fi
|
||||||
-t | --tag )
|
|
||||||
TAG=$2
|
|
||||||
shift
|
|
||||||
;;
|
|
||||||
--no-merges )
|
|
||||||
GIT_LOG_OPTS='--no-merges'
|
|
||||||
;;
|
|
||||||
* )
|
|
||||||
FILE=$1
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
shift
|
|
||||||
done
|
|
||||||
|
|
||||||
DATE=`date +'%Y-%m-%d'`
|
eval "$GIT_EDITOR" '"$@"'
|
||||||
HEAD="\n$TAG / $DATE\n"
|
}
|
||||||
for i in $(seq 5 ${#HEAD}); do HEAD="$HEAD="; done
|
|
||||||
HEAD="$HEAD\n\n"
|
|
||||||
|
|
||||||
if $LIST; then
|
_usage() {
|
||||||
lasttag=$(git rev-list --tags --max-count=1 2>/dev/null)
|
cat << EOF
|
||||||
version=$(git describe --tags --abbrev=0 $lasttag 2>/dev/null)
|
usage: $PROGNAME options [file]
|
||||||
if test -z "$version"; then
|
usage: $PROGNAME -h|help|?
|
||||||
git log $GIT_LOG_OPTS --pretty="format: * %s"
|
|
||||||
|
Generate a Changelog from git(1) tags (annotated or lightweight) and commit
|
||||||
|
messages. Existing Changelog files with filenames that begin with 'Change' or
|
||||||
|
'History' will be identified automatically and their content will be appended
|
||||||
|
to the new output generated (unless the -p|--prune-old option is used). If no
|
||||||
|
tags exist, then all commits are output; if tags exist, then only the most-
|
||||||
|
recent commits are output up to the last identified tag.
|
||||||
|
|
||||||
|
OPTIONS:
|
||||||
|
-a, --all Retrieve all commits (ignores --start-tag/commit, --final-tag)
|
||||||
|
-l, --list Display commits as a list, with no titles
|
||||||
|
-t, --tag Tag label to use for most-recent (untagged) commits
|
||||||
|
-f, --final-tag Newest tag to retrieve commits from in a range
|
||||||
|
-s, --start-tag Oldest tag to retrieve commits from in a range
|
||||||
|
--start-commit Like --start-tag but use commit instead of tag
|
||||||
|
-n, --no-merges Suppress commits from merged branches
|
||||||
|
-m, --merges-only Only uses merge commits (uses both subject and body of commit)
|
||||||
|
-p, --prune-old Replace existing Changelog entirely with new content
|
||||||
|
-x, --stdout Write output to stdout instead of to a Changelog file
|
||||||
|
-h, --help, ? Show this message
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
_error() {
|
||||||
|
[ $# -eq 0 ] && _usage && exit 0
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "ERROR: " "$@"
|
||||||
|
echo
|
||||||
|
}
|
||||||
|
|
||||||
|
# _setValueForKeyFakeAssocArray()
|
||||||
|
# /*!
|
||||||
|
# @abstract Set value for key from a fake associative array
|
||||||
|
# @discussion
|
||||||
|
# Iterates over target_ary (an indexed array), searching for target_key, if the
|
||||||
|
# key is found its value is set to new_value otherwise the target_key and
|
||||||
|
# new_value are appended to the array.
|
||||||
|
#
|
||||||
|
# The indexed array values must conform to this format:
|
||||||
|
# "key:value"
|
||||||
|
# Where key and value are separated by a single colon character.
|
||||||
|
#
|
||||||
|
# Specify empty values as an empty, quoted string.
|
||||||
|
#
|
||||||
|
# So-called "fake" associative arrays are useful for environments where the
|
||||||
|
# installed version of bash(1) precedes 4.0.
|
||||||
|
# @param target_key Key to retrieve
|
||||||
|
# @param new_value New or updated value
|
||||||
|
# @param target_ary Indexed array to scan
|
||||||
|
# @return Returns new array with updated key (status 0) or an empty array
|
||||||
|
# (status 1) on failure.
|
||||||
|
# */
|
||||||
|
_setValueForKeyFakeAssocArray() {
|
||||||
|
# parameter list supports empty arguments!
|
||||||
|
local target_key="$1"; shift
|
||||||
|
local new_value="$1"; shift
|
||||||
|
local target_ary=()
|
||||||
|
local defaultIFS="$IFS"
|
||||||
|
local IFS="$defaultIFS"
|
||||||
|
local found=false
|
||||||
|
|
||||||
|
IFS=$' ' target_ary=( $1 ) IFS="$defaultIFS"
|
||||||
|
|
||||||
|
[[ -z "${target_key}" || "${#target_ary[@]}" -eq 0 ]] && echo "${value}" && return 1
|
||||||
|
|
||||||
|
local _target_ary_length="${#target_ary[@]}"
|
||||||
|
local i
|
||||||
|
for (( i=0; i<"${_target_ary_length}"; i++ )); do
|
||||||
|
local __val="${target_ary[$i]}"
|
||||||
|
|
||||||
|
if [[ "${__val%%:*}" == "${target_key}" ]]; then
|
||||||
|
target_ary[$i]="${__val%%:*}:${new_value}"
|
||||||
|
found=true
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
|
||||||
|
unset __val
|
||||||
|
done
|
||||||
|
unset i _target_ary_length
|
||||||
|
|
||||||
|
# key not found, append
|
||||||
|
[[ "$found" == false ]] && target_ary+=( "${target_key}:${new_value}" )
|
||||||
|
|
||||||
|
printf "%s" "${target_ary[*]}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# _valueForKeyFakeAssocArray()
|
||||||
|
# /*!
|
||||||
|
# @abstract Fetch value for key from a fake associative array
|
||||||
|
# @discussion
|
||||||
|
# Iterates over target_ary (an indexed array), searching for target_key, if the
|
||||||
|
# key is found its value is returned.
|
||||||
|
#
|
||||||
|
# The indexed array values must conform to this format:
|
||||||
|
# "key:value"
|
||||||
|
# Where key and value are separated by a single colon character.
|
||||||
|
#
|
||||||
|
# So-called "fake" associative arrays are useful for environments where the
|
||||||
|
# installed version of bash(1) precedes 4.0.
|
||||||
|
# @param target_key Key to retrieve
|
||||||
|
# @param target_ary Indexed array to scan
|
||||||
|
# @return Returns string containing value (status 0) or an empty string
|
||||||
|
# (status 1) on failure.
|
||||||
|
# */
|
||||||
|
_valueForKeyFakeAssocArray() {
|
||||||
|
local target_key="$1"
|
||||||
|
local target_ary=()
|
||||||
|
local defaultIFS="$IFS"
|
||||||
|
local IFS="$defaultIFS"
|
||||||
|
local value=""
|
||||||
|
|
||||||
|
IFS=$' ' target_ary=( $2 ) IFS="$defaultIFS"
|
||||||
|
|
||||||
|
[[ -z "${target_key}" || "${#target_ary[@]}" -eq 0 ]] && echo "${value}" && return 1
|
||||||
|
|
||||||
|
local t
|
||||||
|
for t in "${target_ary[@]}"; do
|
||||||
|
if [[ "${t%%:*}" == "${target_key}" ]]; then
|
||||||
|
value="${t#*:}"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
unset t
|
||||||
|
|
||||||
|
echo -e "${value}"; return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
_fetchCommitRange() {
|
||||||
|
local list_all="${1:-false}"
|
||||||
|
local start_tag="$2"
|
||||||
|
local final_tag="$3"
|
||||||
|
|
||||||
|
# This shellcheck disable is applied to the whole if-body
|
||||||
|
# shellcheck disable=SC2086
|
||||||
|
if [[ "$list_all" == true ]]; then
|
||||||
|
git log $GIT_LOG_OPTS --pretty=format:"${CUR_GIT_LOG_FORMAT}"
|
||||||
|
elif [[ -n "$final_tag" && "$start_tag" == "null" ]]; then
|
||||||
|
git log $GIT_LOG_OPTS --pretty=format:"${CUR_GIT_LOG_FORMAT}" "${final_tag}"
|
||||||
|
elif [[ -n "$final_tag" ]]; then
|
||||||
|
git log $GIT_LOG_OPTS --pretty=format:"${CUR_GIT_LOG_FORMAT}" "${start_tag}"'..'"${final_tag}"
|
||||||
|
elif [[ -n "$start_tag" ]]; then
|
||||||
|
git log $GIT_LOG_OPTS --pretty=format:"${CUR_GIT_LOG_FORMAT}" "${start_tag}"'..'
|
||||||
|
fi | sed 's/^ \* \*/ */g'
|
||||||
|
}
|
||||||
|
|
||||||
|
_formatCommitPlain() {
|
||||||
|
local start_tag="$1"
|
||||||
|
local final_tag="$2"
|
||||||
|
|
||||||
|
printf "%s\n" "$(_fetchCommitRange "false" "$start_tag" "$final_tag")"
|
||||||
|
}
|
||||||
|
|
||||||
|
_formatCommitPretty() {
|
||||||
|
local title_tag="$1"
|
||||||
|
local title_date="$2"
|
||||||
|
local start_tag="$3"
|
||||||
|
local final_tag="$4"
|
||||||
|
local title="$title_tag / $title_date"
|
||||||
|
local title_underline=""
|
||||||
|
|
||||||
|
local i
|
||||||
|
for i in $(seq ${#title}); do
|
||||||
|
title_underline+="="
|
||||||
|
done
|
||||||
|
unset i
|
||||||
|
|
||||||
|
printf '\n%s\n%s\n' "$title" "$title_underline"
|
||||||
|
printf "\n%s\n" "$(_fetchCommitRange "false" "$start_tag" "$final_tag")"
|
||||||
|
}
|
||||||
|
|
||||||
|
commitList() {
|
||||||
|
# parameter list supports empty arguments!
|
||||||
|
local list_all="${1:-false}"; shift
|
||||||
|
local title_tag="$1"; shift
|
||||||
|
local start_tag="$1"; shift
|
||||||
|
local final_tag="$1"; shift
|
||||||
|
local list_style="${1:-false}"; shift # enable/disable list format
|
||||||
|
local start_commit="$1"; shift
|
||||||
|
local changelog="$FILE"
|
||||||
|
local title_date="$(date +'%Y-%m-%d')"
|
||||||
|
local tags_list=()
|
||||||
|
local tags_list_keys=()
|
||||||
|
local defaultIFS="$IFS"
|
||||||
|
local IFS="$defaultIFS"
|
||||||
|
|
||||||
|
if [[ -n "$start_commit" && "$final_tag" == "null" \
|
||||||
|
&& "$start_tag" == "null" ]]; then
|
||||||
|
# if there is not tag after $start_commit,
|
||||||
|
# output directly without fetch all tags
|
||||||
|
if [[ "$list_style" == true ]]; then
|
||||||
|
_formatCommitPlain "${start_commit}~"
|
||||||
|
else
|
||||||
|
_formatCommitPretty "$title_tag" "$title_date" "$start_commit~"
|
||||||
|
fi
|
||||||
|
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
#
|
||||||
|
# Tags look like this:
|
||||||
|
#
|
||||||
|
# >git log --tags --simplify-by-decoration --date="short" --pretty="format:%h$%x09%ad$%x09%d"
|
||||||
|
#
|
||||||
|
# ecf1f2b$ 2015-03-15$ (HEAD, tag: v1.0.1, origin/master, origin/HEAD, master, hotfix/1.0.2)
|
||||||
|
# a473e9c$ 2015-03-04$ (tag: v1.0.0)
|
||||||
|
# f2cb562$ 2015-02-19$ (tag: v0.9.2)
|
||||||
|
# 6197c2b$ 2015-02-19$ (tag: v0.9.1)
|
||||||
|
# 1e5f5e6$ 2015-02-16$ (tag: v0.9.0)
|
||||||
|
# 3de8ab5$ 2015-02-11$ (origin/feature/restore-auto)
|
||||||
|
# a15afd1$ 2015-02-02$ (origin/feature/versionable)
|
||||||
|
# 38a44e0$ 2015-02-02$ (origin/feature/save-auto)
|
||||||
|
# 3244b80$ 2015-01-16$ (origin/feature/silent-history, upstream)
|
||||||
|
# 85e45f8$ 2014-08-25$
|
||||||
|
#
|
||||||
|
# The most-recent tag will be preceded by "HEAD, " if there have been zero
|
||||||
|
# commits since the tag. Also notice that with gitflow, we see features.
|
||||||
|
#
|
||||||
|
|
||||||
|
# fetch our tags
|
||||||
|
local _ref _date _tag _tab='%x09'
|
||||||
|
local _tag_regex='tag: *'
|
||||||
|
while IFS=$'\t' read -r _ref _date _tag; do
|
||||||
|
[[ -z "${_tag}" ]] && continue
|
||||||
|
# strip out tags form ()
|
||||||
|
# git v2.2.0+ supports '%D', like '%d' without the " (", ")" wrapping. One day we should use it instead.
|
||||||
|
_tag="${_tag# }"; _tag="${_tag//[()]/}"
|
||||||
|
# trap tag if it points to last commit (HEAD)
|
||||||
|
_tag="${_tag#HEAD*, }"
|
||||||
|
# strip out branches
|
||||||
|
[[ ! "${_tag}" =~ ${_tag_regex} ]] && continue
|
||||||
|
# strip out any additional tags pointing to same commit, remove tag label
|
||||||
|
_tag="${_tag%%,*}"; _tag="${_tag#tag: }"
|
||||||
|
# add tag to assoc array; copy tag to tag_list_keys for ordered iteration
|
||||||
|
tags_list+=( "${_tag}:${_ref}=>${_date}" )
|
||||||
|
tags_list_keys+=( "${_tag}" )
|
||||||
|
done <<< "$(git log --tags --simplify-by-decoration --date="short" --pretty="format:%h${_tab}%ad${_tab}%d")"
|
||||||
|
IFS="$defaultIFS"
|
||||||
|
unset _tag_regex
|
||||||
|
unset _ref _date _tag _tab
|
||||||
|
|
||||||
|
local _tags_list_keys_length="${#tags_list_keys[@]}"
|
||||||
|
if [[ "${_tags_list_keys_length}" -eq 0 ]]
|
||||||
|
then
|
||||||
|
unset _tags_list_keys_length
|
||||||
|
if [[ "$list_style" == true ]]; then
|
||||||
|
printf "%s" "$(_fetchCommitRange "true")"
|
||||||
|
else
|
||||||
|
local title="$title_tag / $title_date"
|
||||||
|
local title_underline=""
|
||||||
|
|
||||||
|
local i
|
||||||
|
for i in $(seq ${#title}); do
|
||||||
|
title_underline+="="
|
||||||
|
done
|
||||||
|
unset i
|
||||||
|
|
||||||
|
printf '\n%s\n%s\n' "$title" "$title_underline"
|
||||||
|
printf "\n%s\n" "$(_fetchCommitRange "true")"
|
||||||
|
fi
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
local _final_tag_found=false
|
||||||
|
local _start_tag_found=false
|
||||||
|
local i
|
||||||
|
for (( i=0; i<"${_tags_list_keys_length}"; i++ )); do
|
||||||
|
local __curr_tag="${tags_list_keys[$i]}"
|
||||||
|
local __prev_tag="${tags_list_keys[$i+1]:-null}"
|
||||||
|
local __curr_date
|
||||||
|
__curr_date="$(_valueForKeyFakeAssocArray "${__curr_tag}" "${tags_list[*]}")"
|
||||||
|
__curr_date="${__curr_date##*=>}"
|
||||||
|
|
||||||
|
# output latest commits, up until the most-recent tag, these are all
|
||||||
|
# new commits made since the last tagged commit.
|
||||||
|
if [[ $i -eq 0 && ( -z "$final_tag" || "$final_tag" == "null" ) ]]; then
|
||||||
|
if [[ "$list_style" == true ]]; then
|
||||||
|
_formatCommitPlain "${__curr_tag}" >> "$tmpfile"
|
||||||
|
else
|
||||||
|
_formatCommitPretty "$title_tag" "$title_date" "${__curr_tag}"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# both final_tag and start_tag are "null", user just wanted recent commits
|
||||||
|
[[ "$final_tag" == "null" && "$start_tag" == "null" ]] && break;
|
||||||
|
|
||||||
|
# find the specified final tag, continue until found
|
||||||
|
if [[ -n "$final_tag" && "$final_tag" != "null" ]]; then
|
||||||
|
[[ "$final_tag" == "${__curr_tag}" ]] && _final_tag_found=true
|
||||||
|
[[ "$final_tag" != "${__curr_tag}" && "${_final_tag_found}" == false ]] && continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
# find the specified start tag, break when found
|
||||||
|
if [[ -n "$start_tag" ]]; then
|
||||||
|
[[ "$start_tag" == "${__curr_tag}" ]] && _start_tag_found=true
|
||||||
|
if [[ "${_start_tag_found}" == true ]]; then
|
||||||
|
if [[ -n "$start_commit" ]]; then
|
||||||
|
|
||||||
|
# output commits after start_commit to its closest tag
|
||||||
|
if [[ "$list_style" == true ]]; then
|
||||||
|
_formatCommitPlain "$start_commit~" "${__curr_tag}"
|
||||||
|
else
|
||||||
|
_formatCommitPretty "${__curr_tag}" "${__curr_date}" \
|
||||||
|
"$start_commit~" "${__curr_tag}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
|
||||||
|
[[ "$start_tag" != "${__curr_tag}" ]] && break
|
||||||
|
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# output commits made between prev_tag and curr_tag, these are all of the
|
||||||
|
# commits related to the tag of interest.
|
||||||
|
if [[ "$list_style" == true ]]; then
|
||||||
|
_formatCommitPlain "${__prev_tag}" "${__curr_tag}"
|
||||||
|
else
|
||||||
|
_formatCommitPretty "${__curr_tag}" "${__curr_date}" "${__prev_tag}" "${__curr_tag}"
|
||||||
|
fi
|
||||||
|
unset __curr_date
|
||||||
|
unset __prev_tag
|
||||||
|
unset __curr_tag
|
||||||
|
done
|
||||||
|
unset i
|
||||||
|
unset _start_tag_found
|
||||||
|
unset _final_tag_found
|
||||||
|
unset _tags_list_keys_length
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
commitListPlain() {
|
||||||
|
local list_all="${1:-false}"
|
||||||
|
local start_tag="$2"
|
||||||
|
local final_tag="$3"
|
||||||
|
local start_commit="$4"
|
||||||
|
|
||||||
|
commitList "$list_all" "" "$start_tag" "$final_tag" "true" "$start_commit"
|
||||||
|
}
|
||||||
|
|
||||||
|
commitListPretty() {
|
||||||
|
local list_all="${1:-false}"
|
||||||
|
local title_tag="$2"
|
||||||
|
local start_tag="$3"
|
||||||
|
local final_tag="$4"
|
||||||
|
local start_commit="$5"
|
||||||
|
local title_date="$(date +'%Y-%m-%d')"
|
||||||
|
|
||||||
|
commitList "$list_all" "$title_tag" "$start_tag" "$final_tag" "false" \
|
||||||
|
"$start_commit"
|
||||||
|
}
|
||||||
|
|
||||||
|
_exit() {
|
||||||
|
local pid_list=()
|
||||||
|
local defaultIFS="$IFS"
|
||||||
|
local IFS="$defaultIFS"
|
||||||
|
|
||||||
|
stty sane; echo; echo "caught signal, shutting down"
|
||||||
|
|
||||||
|
IFS=$'\n'
|
||||||
|
# The format of `ps` is different between Windows and other platforms,
|
||||||
|
# so we need to calculate the total column number(COL_NUM) of header first.
|
||||||
|
# Why don't we just use the last column?
|
||||||
|
# Because the body of CMD column may contain space and be treated as multiple fields.
|
||||||
|
pid_list=( $(ps -f |
|
||||||
|
awk -v ppid=$$ 'NR == 1 {
|
||||||
|
COL_NUM = NF
|
||||||
|
}
|
||||||
|
$3 == ppid {
|
||||||
|
# filter out temp processes created in this subshell
|
||||||
|
if ($COL_NUM != "ps" && $COL_NUM != "awk" && $COL_NUM !~ "bash$")
|
||||||
|
print $2
|
||||||
|
}')
|
||||||
|
)
|
||||||
|
IFS="$defaultIFS"
|
||||||
|
|
||||||
|
local _pid
|
||||||
|
for _pid in "${pid_list[@]}"; do
|
||||||
|
echo "killing: ${_pid}"
|
||||||
|
kill -TERM "${_pid}"
|
||||||
|
done
|
||||||
|
|
||||||
|
wait; stty sane; exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
trap '_exit' SIGINT SIGQUIT SIGTERM
|
||||||
|
|
||||||
|
main() {
|
||||||
|
local start_tag="null" # empty string and "null" mean two different things!
|
||||||
|
local final_tag="null"
|
||||||
|
|
||||||
|
local option=(
|
||||||
|
"list_all:false"
|
||||||
|
"list_style:false"
|
||||||
|
"title_tag:$DEF_TAG_RECENT"
|
||||||
|
"start_tag:"
|
||||||
|
"start_commit:"
|
||||||
|
"final_tag:"
|
||||||
|
"output_file:"
|
||||||
|
"use_stdout:false"
|
||||||
|
"prune_old:false"
|
||||||
|
)
|
||||||
|
|
||||||
|
#
|
||||||
|
# We work chronologically backwards from NOW towards start_tag where NOW also
|
||||||
|
# includes the most-recent (un-tagged) commits. If no start_tag has been
|
||||||
|
# specified, we work back to the very first commit; if a final_tag has been
|
||||||
|
# specified, we begin at the final_tag and work backwards towards start_tag.
|
||||||
|
#
|
||||||
|
|
||||||
|
# An existing ChangeLog/History file will be appended to the output unless the
|
||||||
|
# prune old (-p | --prune-old) option has been enabled.
|
||||||
|
|
||||||
|
while [ "$1" != "" ]; do
|
||||||
|
case $1 in
|
||||||
|
-a | --all )
|
||||||
|
option=( $(_setValueForKeyFakeAssocArray "list_all" true "${option[*]}") )
|
||||||
|
;;
|
||||||
|
-l | --list )
|
||||||
|
option=( $(_setValueForKeyFakeAssocArray "list_style" true "${option[*]}") )
|
||||||
|
;;
|
||||||
|
-t | --tag )
|
||||||
|
option=( $(_setValueForKeyFakeAssocArray "title_tag" "$2" "${option[*]}") )
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
-f | --final-tag )
|
||||||
|
option=( $(_setValueForKeyFakeAssocArray "final_tag" "$2" "${option[*]}") )
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
-s | --start-tag )
|
||||||
|
option=( $(_setValueForKeyFakeAssocArray "start_tag" "$2" "${option[*]}") )
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--start-commit )
|
||||||
|
option=( $(_setValueForKeyFakeAssocArray "start_commit" "$2" "${option[*]}") )
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
-n | --no-merges )
|
||||||
|
GIT_LOG_OPTS='--no-merges'
|
||||||
|
CUR_GIT_LOG_FORMAT="$GIT_LOG_FORMAT"
|
||||||
|
;;
|
||||||
|
-m | --merges-only )
|
||||||
|
GIT_LOG_OPTS='--merges'
|
||||||
|
CUR_GIT_LOG_FORMAT="$GIT_MERGELOG_FORMAT"
|
||||||
|
;;
|
||||||
|
-p | --prune-old )
|
||||||
|
option=( $(_setValueForKeyFakeAssocArray "prune_old" true "${option[*]}") )
|
||||||
|
;;
|
||||||
|
-x | --stdout )
|
||||||
|
option=( $(_setValueForKeyFakeAssocArray "use_stdout" true "${option[*]}") )
|
||||||
|
;;
|
||||||
|
-h | ? | help | --help )
|
||||||
|
_usage
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
* )
|
||||||
|
[[ "${1:0:1}" == '-' ]] && _error "Invalid option: $1" && _usage && exit 1
|
||||||
|
option=( $(_setValueForKeyFakeAssocArray "output_file" "$1" "${option[*]}") )
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
shift
|
||||||
|
done
|
||||||
|
|
||||||
|
# The default log format unless already set
|
||||||
|
[[ -z "$CUR_GIT_LOG_FORMAT" ]] && CUR_GIT_LOG_FORMAT="$GIT_LOG_FORMAT"
|
||||||
|
|
||||||
|
local _tag="$(_valueForKeyFakeAssocArray "start_tag" "${option[*]}")"
|
||||||
|
local start_commit="$(_valueForKeyFakeAssocArray "start_commit" "${option[*]}")"
|
||||||
|
|
||||||
|
if [[ -n "$start_commit" ]]; then
|
||||||
|
if [[ -n "${_tag}" ]]; then
|
||||||
|
_error "--start-tag could not use with --start-commit!"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
start_tag="$(git describe --tags --contains "$start_commit" 2>/dev/null || echo 'null')"
|
||||||
|
if [[ -z "$start_tag" ]]; then
|
||||||
|
_error "Could find the associative tag for the start-commit!"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# remove suffix from the $start_tag when no tag matched exactly
|
||||||
|
start_tag="${start_tag%%~*}"
|
||||||
|
# also remove "^0" added sometimes when tag matched exactly
|
||||||
|
start_tag="${start_tag%%^0}"
|
||||||
|
|
||||||
|
elif [[ -n "${_tag}" ]]; then
|
||||||
|
start_tag="$(git describe --tags --abbrev=0 "${_tag}" 2>/dev/null)"
|
||||||
|
if [[ -z "$start_tag" ]]; then
|
||||||
|
_error "Specified start-tag does not exist!"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -n "${_tag}" ]]; then
|
||||||
|
if [[ -n "$start_commit" ]]; then
|
||||||
|
_error "--start-tag could not use with --start-commit!"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
fi
|
||||||
|
unset _tag
|
||||||
|
|
||||||
|
local _tag="$(_valueForKeyFakeAssocArray "final_tag" "${option[*]}")"
|
||||||
|
if [[ -n "${_tag}" ]]; then
|
||||||
|
final_tag="$(git describe --tags --abbrev=0 "${_tag}" 2>/dev/null)"
|
||||||
|
if [[ -z "$final_tag" ]]; then
|
||||||
|
_error "Specified final-tag does not exist!"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
unset _tag
|
||||||
|
|
||||||
|
#
|
||||||
|
# generate changelog
|
||||||
|
#
|
||||||
|
local tmpfile changelog title_tag
|
||||||
|
tmpfile="$(git_extra_mktemp)"
|
||||||
|
changelog="$(_valueForKeyFakeAssocArray "output_file" "${option[*]}")"
|
||||||
|
title_tag="$(_valueForKeyFakeAssocArray "title_tag" "${option[*]}")"
|
||||||
|
|
||||||
|
if [[ "$(_valueForKeyFakeAssocArray "list_style" "${option[*]}")" == true ]]; then
|
||||||
|
if [[ "$(_valueForKeyFakeAssocArray "list_all" "${option[*]}")" == true ]]; then
|
||||||
|
commitListPlain "true" >> "$tmpfile"
|
||||||
|
else
|
||||||
|
commitListPlain "false" "$start_tag" "$final_tag" \
|
||||||
|
"$start_commit" >> "$tmpfile"
|
||||||
|
fi
|
||||||
else
|
else
|
||||||
git log $GIT_LOG_OPTS --pretty="format: * %s" $version..
|
if [[ "$(_valueForKeyFakeAssocArray "list_all" "${option[*]}")" == true ]]; then
|
||||||
|
commitListPretty "true" "$title_tag" >> "$tmpfile"
|
||||||
|
else
|
||||||
|
commitListPretty "false" "$title_tag" "$start_tag" "$final_tag" \
|
||||||
|
"$start_commit" >> "$tmpfile"
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
exit
|
|
||||||
fi
|
|
||||||
|
|
||||||
CHANGELOG=$FILE
|
if [[ -z "$changelog" ]]; then
|
||||||
if test "$CHANGELOG" = ""; then
|
changelog="$(find . -mindepth 1 -maxdepth 1 \( -iname '*change*' -o -iname '*history*' \) | head -n1)"
|
||||||
CHANGELOG=`ls | egrep 'change|history' -i|head -n1`
|
if [[ -z "$changelog" ]]; then
|
||||||
if test "$CHANGELOG" = ""; then
|
changelog="History.md";
|
||||||
CHANGELOG='History.md';
|
fi
|
||||||
fi
|
fi
|
||||||
fi
|
|
||||||
tmp="/tmp/changelog"
|
# append existing changelog?
|
||||||
printf "$HEAD" > $tmp
|
if [[ -f "$changelog" \
|
||||||
git-changelog $GIT_LOG_OPTS --list >> $tmp
|
&& "$(_valueForKeyFakeAssocArray "prune_old" "${option[*]}")" == false ]]; then
|
||||||
printf '\n' >> $tmp
|
cat "$changelog" >> "$tmpfile"
|
||||||
if [ -f $CHANGELOG ]; then cat $CHANGELOG >> $tmp; fi
|
fi
|
||||||
mv $tmp $CHANGELOG
|
|
||||||
test -n "$EDITOR" && $EDITOR $CHANGELOG
|
# output file to stdout or move into place
|
||||||
|
if [[ "$(_valueForKeyFakeAssocArray "use_stdout" "${option[*]}")" == true ]]; then
|
||||||
|
cat "$tmpfile"
|
||||||
|
rm -f "$tmpfile"
|
||||||
|
else
|
||||||
|
cp -f "$tmpfile" "$changelog"
|
||||||
|
rm -f "$tmpfile"
|
||||||
|
|
||||||
|
if [[ -n "$GIT_EDITOR" ]]; then
|
||||||
|
git_editor "$changelog" || _exit
|
||||||
|
else
|
||||||
|
less "$changelog" || _exit
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
main "$@"
|
||||||
|
|
||||||
|
exit 0
|
||||||
|
|
|
||||||
43
bin/git-clear
Executable file
43
bin/git-clear
Executable file
|
|
@ -0,0 +1,43 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
PROGNAME="git-clear"
|
||||||
|
FORCE=0
|
||||||
|
|
||||||
|
_usage() {
|
||||||
|
cat << EOF
|
||||||
|
usage: $PROGNAME options
|
||||||
|
usage: $PROGNAME -h|help|?
|
||||||
|
|
||||||
|
clear git repository
|
||||||
|
|
||||||
|
OPTIONS:
|
||||||
|
-f, --force Force clear without questioning user
|
||||||
|
-h, --help, ? Show this message
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
# Read arguments
|
||||||
|
while [ "$1" != "" ]; do
|
||||||
|
case $1 in
|
||||||
|
-f|--force)
|
||||||
|
FORCE=1
|
||||||
|
;;
|
||||||
|
-h|--help|?)
|
||||||
|
_usage
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
shift
|
||||||
|
done
|
||||||
|
|
||||||
|
# Only wait for answer if not forced by user
|
||||||
|
if [[ $FORCE == 0 ]]; then
|
||||||
|
echo -n "Sure? - THIS COMMAND MAY DELETE FILES THAT CANNOT BE RECOVERED, including those in .gitignore [y/N]: "
|
||||||
|
read -r clean
|
||||||
|
else
|
||||||
|
clean=y
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$clean" = "y" ]; then
|
||||||
|
git clean -d -f -x && git reset --hard
|
||||||
|
fi
|
||||||
7
bin/git-clear-soft
Executable file
7
bin/git-clear-soft
Executable file
|
|
@ -0,0 +1,7 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
echo -n "Sure? - This command may delete files that cannot be recovered. Files and directories in .gitignore will be preserved [y/N]: "
|
||||||
|
read -r answer
|
||||||
|
if [ "$answer" = "y" ]
|
||||||
|
then git clean -d -f && git reset --hard
|
||||||
|
fi
|
||||||
17
bin/git-coauthor
Executable file
17
bin/git-coauthor
Executable file
|
|
@ -0,0 +1,17 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -e -o pipefail
|
||||||
|
|
||||||
|
coauthor=$1
|
||||||
|
email=$2
|
||||||
|
|
||||||
|
test -z "$coauthor" && echo "co-author required." 1>&2 && exit 1
|
||||||
|
test -z "$email" && echo "co-author email required." 1>&2 && exit 1
|
||||||
|
|
||||||
|
old_message="$(git log --format=%B -n1)"
|
||||||
|
if [[ "$old_message" == *"Co-authored-by:"* ]]; then
|
||||||
|
git commit --amend -m "$old_message
|
||||||
|
Co-authored-by: $coauthor <$email>"
|
||||||
|
else
|
||||||
|
git commit --amend -m "$old_message" -m "Co-authored-by: $coauthor <$email>"
|
||||||
|
fi
|
||||||
|
|
@ -1,6 +1,36 @@
|
||||||
#!/bin/sh
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
SINCE="last week"
|
REF=""
|
||||||
test $# -ne 0 && SINCE=$@
|
SINCE=""
|
||||||
echo "... commits since $SINCE" >&2
|
|
||||||
git log --pretty='%an - %s' --after="@{$SINCE}"
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
-r|--ref)
|
||||||
|
if [[ -z "$2" ]]; then
|
||||||
|
echo "error: option $1 requires an argument" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
REF="$2"
|
||||||
|
shift
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
# non-flag args accumulation, e.g. "last " + "week" -> "last week"
|
||||||
|
SINCE="${SINCE:+$SINCE }$1"
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ -n "$REF" && -n "$SINCE" ]]; then
|
||||||
|
echo "error: '--ref <ref>' and '<date>' are mutually exclusive" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
SINCE="${SINCE:-last week}"
|
||||||
|
|
||||||
|
if [[ -n "$REF" ]]; then
|
||||||
|
git log --pretty='%h %an - %s' "$REF..HEAD"
|
||||||
|
else
|
||||||
|
git log --pretty='%h %an - %s' --after="@{$SINCE}"
|
||||||
|
fi
|
||||||
|
|
|
||||||
1
bin/git-continue
Symbolic link
1
bin/git-continue
Symbolic link
|
|
@ -0,0 +1 @@
|
||||||
|
git-abort
|
||||||
|
|
@ -1,9 +1,7 @@
|
||||||
#!/bin/sh
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
user="$*"
|
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 @@
|
||||||
#!/bin/sh
|
#!/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 @@
|
||||||
#!/bin/sh
|
#!/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,20 +1,17 @@
|
||||||
#!/bin/sh
|
#!/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
|
||||||
|
|
||||||
# Concatenate all branch references
|
|
||||||
local_branches=""
|
|
||||||
origin_branches=""
|
|
||||||
origin_refs=""
|
|
||||||
for branch in "$@"
|
for branch in "$@"
|
||||||
do
|
do
|
||||||
local_branches=$local_branches" $branch"
|
remote=$(git config "branch.$branch.remote" || echo "origin")
|
||||||
origin_branches=$origin_branches" origin/$branch"
|
ref=$(git config "branch.$branch.merge" || echo "refs/heads/$branch")
|
||||||
origin_refs=$origin_refs" :refs/heads/$branch"
|
|
||||||
done
|
|
||||||
|
|
||||||
# Delete all branches
|
git branch -D "$branch" || true
|
||||||
git branch -D $local_branches
|
# Avoid deleting local upstream
|
||||||
git branch -d -r $origin_branches
|
[ "$remote" = "." ] && continue
|
||||||
git push origin $origin_refs
|
git branch -d -r "$remote/$branch" || continue
|
||||||
|
git push "$remote" ":$ref"
|
||||||
|
done
|
||||||
|
|
|
||||||
60
bin/git-delete-gone-branches
Executable file
60
bin/git-delete-gone-branches
Executable file
|
|
@ -0,0 +1,60 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
dry_run=false
|
||||||
|
force=false
|
||||||
|
prune=false
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
-n|--dry-run)
|
||||||
|
dry_run=true
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
-f|--force)
|
||||||
|
force=true
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
-p|--prune)
|
||||||
|
prune=true
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Unknown option: $1"
|
||||||
|
echo "Usage: git delete-gone-branches [-n|--dry-run] [-f|--force] [-p|--prune]"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ "$prune" = true ]; then
|
||||||
|
git fetch --prune
|
||||||
|
fi
|
||||||
|
|
||||||
|
gone_branches=$(git for-each-ref --format \
|
||||||
|
'%(if:equals=gone)%(upstream:track,nobracket)%(then)%(refname:short)%(end)' refs/heads/ | sed '/^$/d')
|
||||||
|
|
||||||
|
if [ -z "$gone_branches" ]; then
|
||||||
|
echo "No branches with a gone remote found."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$dry_run" = true ]; then
|
||||||
|
echo "Would delete the following branches:"
|
||||||
|
echo "$gone_branches"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "The following branches will be deleted:"
|
||||||
|
echo "$gone_branches"
|
||||||
|
|
||||||
|
if [ "$force" = false ]; then
|
||||||
|
read -r -p "Delete these branches? [y/N] " confirm
|
||||||
|
if [[ ! "$confirm" =~ ^[yY]$ ]]; then
|
||||||
|
echo "Aborted."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "$gone_branches" | xargs git branch -D
|
||||||
|
|
@ -1,3 +1,7 @@
|
||||||
#!/bin/sh
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
git branch --merged | grep -v "\*" | grep -v master | 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 @@
|
||||||
#!/bin/sh
|
#!/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 config --remove-section submodule.$NAME
|
NAME="${1%/}"
|
||||||
git config --file=.gitmodules --remove-section submodule.$NAME
|
test -z "$(git config --file='.gitmodules' "submodule.$NAME.url")" \
|
||||||
git rm --cached $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 @@
|
||||||
#!/bin/sh
|
#!/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
|
||||||
|
|
|
||||||
15
bin/git-delta
Executable file
15
bin/git-delta
Executable file
|
|
@ -0,0 +1,15 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
branch=$(git_extra_default_branch)
|
||||||
|
filter=ACM
|
||||||
|
|
||||||
|
if test $# -eq 1; then
|
||||||
|
branch=$1
|
||||||
|
else
|
||||||
|
if test $# -eq 2; then
|
||||||
|
branch=$1
|
||||||
|
filter=$2
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
git diff --name-only --diff-filter="$filter" "$branch"
|
||||||
204
bin/git-effort
204
bin/git-effort
|
|
@ -1,15 +1,29 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
tmp=/tmp/.git-effort
|
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,17 +31,18 @@ date() {
|
||||||
#
|
#
|
||||||
|
|
||||||
hide_cursor() {
|
hide_cursor() {
|
||||||
printf '\033[?25l'
|
tputq civis
|
||||||
}
|
}
|
||||||
|
|
||||||
#
|
#
|
||||||
# show cursor
|
# show cursor, and remove temporary file
|
||||||
#
|
#
|
||||||
|
|
||||||
show_cursor() {
|
show_cursor_and_cleanup() {
|
||||||
printf '\033[?25h'
|
tputq cnorm
|
||||||
printf '\033[m\n'
|
tputq sgr0
|
||||||
exit 1
|
rm "$tmp" > /dev/null 2>&1
|
||||||
|
exit 0
|
||||||
}
|
}
|
||||||
|
|
||||||
#
|
#
|
||||||
|
|
@ -35,10 +50,7 @@ show_cursor() {
|
||||||
#
|
#
|
||||||
|
|
||||||
active_days() {
|
active_days() {
|
||||||
date $1 | uniq | awk '
|
echo "$1" | sort -r | uniq | wc -l
|
||||||
{ sum += 1 }
|
|
||||||
END { print sum }
|
|
||||||
'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#
|
#
|
||||||
|
|
@ -46,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"
|
||||||
}
|
}
|
||||||
|
|
||||||
#
|
#
|
||||||
|
|
@ -93,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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -103,40 +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 INT
|
trap show_cursor_and_cleanup INT
|
||||||
|
|
||||||
# loop files
|
|
||||||
|
|
||||||
heading
|
heading
|
||||||
effort "${files[@]}" | tee $tmp && sort_effort
|
|
||||||
|
# send paths to effort
|
||||||
|
nPaths=${#paths[@]}
|
||||||
|
nProcs=$(procs)
|
||||||
|
nJobs="\j"
|
||||||
|
for ((i=0; i<nPaths; ++i))
|
||||||
|
do
|
||||||
|
while (( ${nJobs@P} >= nProcs )); do
|
||||||
|
wait -n
|
||||||
|
done
|
||||||
|
effort "${paths[i]}" &
|
||||||
|
done|tee "$tmp"
|
||||||
|
|
||||||
|
# if more than one path, sort and print
|
||||||
|
test "$(wc -l "$tmp" | awk '{print $1}')" -gt 1 && sort_effort
|
||||||
echo
|
echo
|
||||||
|
|
||||||
show_cursor
|
show_cursor_and_cleanup
|
||||||
|
|
|
||||||
|
|
@ -1,29 +1,54 @@
|
||||||
#!/bin/sh
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
VERSION="1.9.1"
|
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/visionmedia/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
|
||||||
|
|
|
||||||
115
bin/git-feature
115
bin/git-feature
|
|
@ -1,14 +1,105 @@
|
||||||
#!/bin/sh
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
if test "$1" = "finish"; then
|
branch_prefix=$(git config --get git-extras.feature.prefix)
|
||||||
test -z $2 && echo "feature <name> required." 1>&2 && exit 1
|
|
||||||
branch=feature/$2
|
if [ -z "$branch_prefix" ]; then
|
||||||
git merge --no-ff $branch && git delete-branch $branch
|
branch_prefix="feature"
|
||||||
else
|
fi
|
||||||
test -z $1 && echo "feature <name> required." 1>&2 && exit 1
|
|
||||||
if test -n "$2"; then
|
branch_separator=$(git config --get git-extras.feature.separator)
|
||||||
echo "too many arguments; if not finishing a feature, only <name> of new feature is expected" && exit 1
|
|
||||||
fi
|
if [ -z "$branch_separator" ]; then
|
||||||
branch=feature/$1
|
branch_separator="/"
|
||||||
git checkout -b $branch &> /dev/null
|
fi
|
||||||
|
|
||||||
|
merge_mode="--no-ff"
|
||||||
|
finish=false
|
||||||
|
declare -a argv
|
||||||
|
while test $# != 0
|
||||||
|
do
|
||||||
|
case $1 in
|
||||||
|
-a|--alias )
|
||||||
|
if [[ -n $2 ]] && [[ $2 != -- ]]
|
||||||
|
then
|
||||||
|
shift # shift -a|-alias
|
||||||
|
branch_prefix=$1
|
||||||
|
else
|
||||||
|
echo >&2 "option $1 requires a value"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
-r|--remote )
|
||||||
|
if [[ -n $2 ]] && [[ $2 != -- ]]
|
||||||
|
then
|
||||||
|
remote=$2
|
||||||
|
shift
|
||||||
|
else
|
||||||
|
remote="origin"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
-s|--separator )
|
||||||
|
if [[ -n $2 ]] && [[ $2 != -- ]]
|
||||||
|
then
|
||||||
|
branch_separator=$2
|
||||||
|
shift
|
||||||
|
else
|
||||||
|
echo >&2 "option $1 requires a value"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
--squash )
|
||||||
|
merge_mode="--squash"
|
||||||
|
;;
|
||||||
|
--from )
|
||||||
|
start_point=$2
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
-- )
|
||||||
|
# terminate argument parsing
|
||||||
|
shift
|
||||||
|
argv+=("$@")
|
||||||
|
break
|
||||||
|
;;
|
||||||
|
finish )
|
||||||
|
finish=true
|
||||||
|
;;
|
||||||
|
* )
|
||||||
|
argv+=("$1")
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
shift
|
||||||
|
done
|
||||||
|
|
||||||
|
concatargs() {
|
||||||
|
str=$(IFS='-'; echo "$*")
|
||||||
|
branch="$branch_prefix$branch_separator$str"
|
||||||
|
}
|
||||||
|
|
||||||
|
test -z "${argv[0]}" && echo "$branch_prefix" "<name> required." 1>&2 && exit 1
|
||||||
|
|
||||||
|
concatargs "${argv[@]}"
|
||||||
|
|
||||||
|
if "${finish}"
|
||||||
|
then
|
||||||
|
git merge ${merge_mode} "$branch" && git delete-branch "$branch"
|
||||||
|
else
|
||||||
|
if [[ -n $remote ]] && [[ -z $start_point ]]
|
||||||
|
then
|
||||||
|
git create-branch -r "$remote" "$branch"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -z $remote ]] && [[ -z $start_point ]]
|
||||||
|
then
|
||||||
|
git create-branch "$branch"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -n $remote ]] && [[ -n $start_point ]]
|
||||||
|
then
|
||||||
|
git create-branch -r "$remote" --from "$start_point" "$branch"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -z $remote ]] && [[ -n $start_point ]]
|
||||||
|
then
|
||||||
|
git create-branch --from "$start_point" "$branch"
|
||||||
|
fi
|
||||||
fi
|
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
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,24 @@
|
||||||
#!/bin/sh
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
branch=$1
|
branch=$1
|
||||||
|
|
||||||
test -z $branch && echo "branch required." 1>&2 && exit 1
|
test -z "$branch" && echo "branch required." 1>&2 && exit 1
|
||||||
|
|
||||||
git symbolic-ref HEAD refs/heads/$branch
|
changes=$(git status --porcelain)
|
||||||
rm .git/index
|
|
||||||
git clean -fdx
|
clean()
|
||||||
|
{
|
||||||
|
git symbolic-ref HEAD "refs/heads/$branch"
|
||||||
|
rm .git/index
|
||||||
|
git clean -fdx
|
||||||
|
}
|
||||||
|
|
||||||
|
if [ -n "$changes" ]; then
|
||||||
|
read -rp "All untracked changes will be lost. Continue [y/N]? " res
|
||||||
|
case $res in
|
||||||
|
[Yy]* ) ;;
|
||||||
|
* ) exit 0;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
|
clean
|
||||||
|
|
|
||||||
40
bin/git-get
Executable file
40
bin/git-get
Executable file
|
|
@ -0,0 +1,40 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
_usage() {
|
||||||
|
printf '%s\n' "usage: ${0##*/} <url>
|
||||||
|
usage: ${0##*/} --help
|
||||||
|
|
||||||
|
Clone a repository in a particular directory."
|
||||||
|
}
|
||||||
|
|
||||||
|
if (( $# == 0 )); then
|
||||||
|
_usage
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
for arg; do
|
||||||
|
if [ "$arg" = '-h' ] || [ "$arg" = '--help' ]; then
|
||||||
|
_usage
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
url=$1
|
||||||
|
if ! shift; then
|
||||||
|
printf 'ERROR: Failed to shift' >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
clone_path=$(git config --get git-extras.get.clone-path)
|
||||||
|
|
||||||
|
if [ -z "$clone_path" ]; then
|
||||||
|
printf 'ERROR: %s\n' "Git configuration key 'git-extras.get.clone-path' must be set to a directory to clone under" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
dirname=${url%/}
|
||||||
|
dirname=${dirname%.git}
|
||||||
|
dirname=${dirname##*/}
|
||||||
|
|
||||||
|
mkdir -p "$clone_path"
|
||||||
|
git clone "$url" "$clone_path/$dirname" "$@"
|
||||||
|
|
@ -1,12 +1,21 @@
|
||||||
#!/bin/sh
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
echo 'setting up gh-pages'
|
echo 'setting up gh-pages'
|
||||||
git symbolic-ref HEAD refs/heads/gh-pages \
|
echo '-------------------'
|
||||||
&& rm .git/index \
|
|
||||||
&& git clean -fdx \
|
echo 'Tell me your github account username: '
|
||||||
&& echo 'My Page' > index.html \
|
read -r username
|
||||||
&& git add . \
|
|
||||||
&& git commit -a -m 'Initial commit' \
|
echo 'Now, tell me your repository name: '
|
||||||
&& git push -u origin gh-pages \
|
read -r repository
|
||||||
&& git fetch origin \
|
|
||||||
&& echo 'complete'
|
git stash \
|
||||||
|
&& git checkout -b 'gh-pages' \
|
||||||
|
&& echo 'My Page' > index.html \
|
||||||
|
&& git add . \
|
||||||
|
&& git commit -a -m 'Initial commit' \
|
||||||
|
&& git remote add origin https://github.com/"$username"/"$repository".git \
|
||||||
|
&& git push -u origin gh-pages \
|
||||||
|
&& echo 'Complete' \
|
||||||
|
&& echo '-------------------' \
|
||||||
|
&& echo 'You can find your last changes in the stash!'
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
#!/bin/sh
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
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"
|
||||||
|
|
|
||||||
119
bin/git-guilt
Executable file
119
bin/git-guilt
Executable file
|
|
@ -0,0 +1,119 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
for param in "$@"
|
||||||
|
do
|
||||||
|
case $param in
|
||||||
|
-h)
|
||||||
|
echo 'Usage: git-guilt [<options>] <since> <until>'
|
||||||
|
echo 'Calculates the change in blame between two revisions'
|
||||||
|
echo 'Example: git guilt HEAD~3 HEAD'
|
||||||
|
echo
|
||||||
|
echo 'Options:'
|
||||||
|
echo
|
||||||
|
echo ' -h, --help output usage information'
|
||||||
|
echo ' -e, --email display author emails instead of names'
|
||||||
|
echo ' -w, --ignore-whitespace ignore whitespace only changes when attributing blame'
|
||||||
|
echo ' -d, --debug output debug information'
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
-e|--email )
|
||||||
|
EMAIL='-e'
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
-w|--ignore-whitespace )
|
||||||
|
NOT_WHITESPACE='-w'
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
-d|--debug )
|
||||||
|
DEBUG=$(git_extra_mktemp)
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
cd "$(git-root)" || exit # cd for git blame
|
||||||
|
MERGED_LOG=$(git_extra_mktemp)
|
||||||
|
if [[ $EMAIL == '-e' ]]
|
||||||
|
then
|
||||||
|
PATTERN='s/^author-mail <\(.*\)>/\1/p'
|
||||||
|
else
|
||||||
|
PATTERN='s/^author //p'
|
||||||
|
fi
|
||||||
|
|
||||||
|
for file in $(git diff --name-only "$@")
|
||||||
|
do
|
||||||
|
test -n "$DEBUG" && echo "git blame $file"
|
||||||
|
# $1 - since $2 - until
|
||||||
|
# shellcheck disable=SC2086
|
||||||
|
git blame $NOT_WHITESPACE --line-porcelain "$1" -- "$file" 2> /dev/null |
|
||||||
|
LC_ALL=C sed -n "$PATTERN" | sort | uniq -c | LC_ALL=C sed 's/^\(.\)/- \1/' >> "$MERGED_LOG"
|
||||||
|
# if $2 not given, use current commit as "until"
|
||||||
|
# shellcheck disable=SC2086
|
||||||
|
git blame $NOT_WHITESPACE --line-porcelain "${2-@}" -- "$file" 2> /dev/null |
|
||||||
|
LC_ALL=C sed -n "$PATTERN" | sort | uniq -c | LC_ALL=C sed 's/^\(.\)/+ \1/' >> "$MERGED_LOG"
|
||||||
|
done
|
||||||
|
|
||||||
|
DEBUG="$DEBUG" awk '
|
||||||
|
NR==1 {
|
||||||
|
# the index of $2 does not change in each line
|
||||||
|
name_start_at = index($0, $3)
|
||||||
|
}
|
||||||
|
/^\+/ {
|
||||||
|
contributors[substr($0, name_start_at)] += $2
|
||||||
|
}
|
||||||
|
/^-/ {
|
||||||
|
contributors[substr($0, name_start_at)] -= $2
|
||||||
|
}
|
||||||
|
END {
|
||||||
|
for (people in contributors) {
|
||||||
|
if (ENVIRON["DEBUG"]) {
|
||||||
|
printf("%d %s\n", contributors[people], people) >> ENVIRON["DEBUG"]
|
||||||
|
}
|
||||||
|
if (contributors[people] != 0) {
|
||||||
|
printf("%d %s\n", contributors[people], people)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}' "$MERGED_LOG" | sort -nr | # only gawk supports built-in sort function
|
||||||
|
while read -r line
|
||||||
|
do
|
||||||
|
people=${line#* }
|
||||||
|
num=${line%% *}
|
||||||
|
|
||||||
|
if [[ $num -gt 0 ]]
|
||||||
|
then
|
||||||
|
printf "%-29s \033[00;32m" "$people"
|
||||||
|
if [[ $num -ge 50 ]]
|
||||||
|
then
|
||||||
|
len=${#num}
|
||||||
|
for (( i = 0; i < 48 - len; i++ ))
|
||||||
|
do
|
||||||
|
printf "+"
|
||||||
|
done
|
||||||
|
printf "(%s)" "$num"
|
||||||
|
else
|
||||||
|
for (( i = 0; i < num; i++ ))
|
||||||
|
do
|
||||||
|
printf "+"
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
printf "%-29s \033[00;31m" "$people"
|
||||||
|
if [[ $num -le -50 ]]
|
||||||
|
then
|
||||||
|
len=${#num}
|
||||||
|
for (( i = 0; i < 48 - len; i++ ))
|
||||||
|
do
|
||||||
|
printf "-"
|
||||||
|
done
|
||||||
|
printf "(%s)" "$num"
|
||||||
|
else
|
||||||
|
for (( i = 0; i > num; i-- ))
|
||||||
|
do
|
||||||
|
printf "-"
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
printf "\033[00m\n"
|
||||||
|
done
|
||||||
|
|
||||||
|
test -n "$DEBUG" && sort -nr "$DEBUG"
|
||||||
283
bin/git-ignore
283
bin/git-ignore
|
|
@ -1,50 +1,285 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
function show_contents {
|
GIT_DIR=$(git rev-parse --git-dir 2>/dev/null)
|
||||||
test -f "$2" && echo "$1 gitignore: $2" && cat "$2"
|
|
||||||
|
# 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}"
|
||||||
|
if [ -f "$file" ]; then
|
||||||
|
echo "$1 gitignore: $2" && cat "$file"
|
||||||
|
else
|
||||||
|
echo "There is no $1 .gitignore yet"
|
||||||
|
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() {
|
||||||
|
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_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}"
|
||||||
|
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 "$1" && test "$pattern" && grep -q "$pattern" "$1") || echo "$pattern" >> "$1"
|
(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
|
||||||
71
bin/git-info
71
bin/git-info
|
|
@ -1,11 +1,40 @@
|
||||||
#!/bin/sh
|
#!/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() {
|
||||||
|
|
@ -20,23 +49,31 @@ remote_urls() {
|
||||||
git remote -v
|
git remote -v
|
||||||
}
|
}
|
||||||
|
|
||||||
# Show info similar to svn
|
echon() {
|
||||||
|
echo "$@"
|
||||||
|
echo
|
||||||
|
}
|
||||||
|
|
||||||
echo
|
echo
|
||||||
echo "## Remote URLs:\n"
|
echon "${COLOR_TITLE}## Remote URLs:${NORMAL}"
|
||||||
echo "$(remote_urls)\n"
|
echon "$(remote_urls)"
|
||||||
|
|
||||||
echo "## Remote Branches:\n"
|
echon "${COLOR_TITLE}## Remote Branches:${NORMAL}"
|
||||||
echo "$(remote_branches)\n"
|
echon "$(remote_branches)"
|
||||||
|
|
||||||
echo "## Local Branches:\n"
|
echon "${COLOR_TITLE}## Local Branches:${NORMAL}"
|
||||||
echo "$(local_branches)\n"
|
echon "$(local_branches)"
|
||||||
|
|
||||||
echo "## Most Recent Commit:\n"
|
SUBMODULES_LOG=$(submodules)
|
||||||
echo "$(most_recent_commit)\n"
|
if [ -n "$SUBMODULES_LOG" ]; then
|
||||||
echo "Type 'git log' for more commits, or 'git show <commit id>' for full commit details.\n"
|
echon "${COLOR_TITLE}## Submodule(s):${NORMAL}"
|
||||||
|
echon "$SUBMODULES_LOG"
|
||||||
if test "$1" != "--no-config"; then
|
fi
|
||||||
echo "## Configuration (.git/config):\n"
|
|
||||||
echo "$(get_config)\n"
|
echon "${COLOR_TITLE}## Most Recent Commit:${NORMAL}"
|
||||||
|
echon "$(most_recent_commit)"
|
||||||
|
|
||||||
|
if [ -n "$HIDE_CONFIG" ]; then
|
||||||
|
echon "${COLOR_TITLE}## Configuration (.git/config):${NORMAL}"
|
||||||
|
echon "$(get_config)"
|
||||||
fi
|
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 @@
|
||||||
#!/bin/sh
|
#!/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 @@
|
||||||
#!/bin/sh
|
#!/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"
|
||||||
|
|
|
||||||
3
bin/git-locked
Executable file
3
bin/git-locked
Executable file
|
|
@ -0,0 +1,3 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
git ls-files -v | grep ^S | sed -e 's|S ||'
|
||||||
87
bin/git-magic
Executable file
87
bin/git-magic
Executable file
|
|
@ -0,0 +1,87 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
|
||||||
|
# Bash unofficial strict mode
|
||||||
|
set -eo pipefail
|
||||||
|
IFS=$'\n\t'
|
||||||
|
|
||||||
|
cd "$(git rev-parse --show-toplevel)"
|
||||||
|
|
||||||
|
USAGE='git-magic [-a] [-m msg] [-e] [-p] [-f]'
|
||||||
|
|
||||||
|
ALL=false
|
||||||
|
PUSH=false
|
||||||
|
FORCE=''
|
||||||
|
ARGS=()
|
||||||
|
|
||||||
|
while getopts "m:eapfh" arg; do
|
||||||
|
case "${arg}" in
|
||||||
|
m)
|
||||||
|
ARGS+=("-m")
|
||||||
|
ARGS+=("${OPTARG}")
|
||||||
|
;;
|
||||||
|
e)
|
||||||
|
ARGS+=("-e")
|
||||||
|
;;
|
||||||
|
a)
|
||||||
|
ALL=true
|
||||||
|
;;
|
||||||
|
p)
|
||||||
|
PUSH=true
|
||||||
|
;;
|
||||||
|
f)
|
||||||
|
FORCE='--force-with-lease'
|
||||||
|
;;
|
||||||
|
h)
|
||||||
|
echo "$USAGE"
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
?)
|
||||||
|
echo "${USAGE}"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
shift $((OPTIND-1))
|
||||||
|
|
||||||
|
if [[ $# != 0 ]]; then
|
||||||
|
echo "Unknown arguments: $*"
|
||||||
|
echo "${USAGE}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
set -- "${ARGS[@]}" # restore positional parameters
|
||||||
|
|
||||||
|
if [[ $ALL == true ]]; then
|
||||||
|
|
||||||
|
# Check if there is no changes to stage
|
||||||
|
if [[ -z $(git status --porcelain) ]]; then
|
||||||
|
echo "No changes to commit"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Get confirmation from user
|
||||||
|
git status
|
||||||
|
echo "Everything will be added"
|
||||||
|
read -rp "Press enter to continue"
|
||||||
|
|
||||||
|
# Restore staging area so that, for example,
|
||||||
|
# add and modify will not be separate entries in status
|
||||||
|
git restore --staged . || true
|
||||||
|
git add .
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Commit with generated message
|
||||||
|
git commit --no-edit "$@" -m "$(git status --porcelain -uno)"
|
||||||
|
|
||||||
|
if [[ $PUSH == true ]]; then
|
||||||
|
git push $FORCE
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --no-edit by default. use option -e to override this
|
||||||
|
|
||||||
|
# Arguments are passed with quoted "$@" to avoid misparsing
|
||||||
|
|
||||||
|
# Generated message comes after user provided arguments
|
||||||
|
# so that user can insert title before it
|
||||||
48
bin/git-merge-into
Executable file
48
bin/git-merge-into
Executable file
|
|
@ -0,0 +1,48 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
current_branch() {
|
||||||
|
git rev-parse --abbrev-ref HEAD
|
||||||
|
}
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
echo "Usage: git merge-into [src] dest [--ff-only]"
|
||||||
|
}
|
||||||
|
|
||||||
|
cur_branch=$(current_branch)
|
||||||
|
|
||||||
|
stashed=0
|
||||||
|
if [ -n "$(git status --porcelain)" ];
|
||||||
|
then
|
||||||
|
echo "Local modifications detected, stashing"
|
||||||
|
stashed=1
|
||||||
|
git stash
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "${!#}" = '--ff-only' ]; then
|
||||||
|
case $# in
|
||||||
|
2 ) # dest --ff
|
||||||
|
git push "$(git rev-parse --show-toplevel)" "$cur_branch":"$1";;
|
||||||
|
3 )
|
||||||
|
git push "$(git rev-parse --show-toplevel)" "$1":"$2";;
|
||||||
|
* )
|
||||||
|
usage
|
||||||
|
esac
|
||||||
|
else
|
||||||
|
case $# in
|
||||||
|
1 )
|
||||||
|
git checkout "$1"
|
||||||
|
git merge "$cur_branch" "$1" && git checkout "$cur_branch"
|
||||||
|
;;
|
||||||
|
2 )
|
||||||
|
git checkout "$2"
|
||||||
|
git merge "$1" "$2" && git checkout "$cur_branch"
|
||||||
|
;;
|
||||||
|
* )
|
||||||
|
usage
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ $stashed -eq 1 ];
|
||||||
|
then
|
||||||
|
git stash pop;
|
||||||
|
fi
|
||||||
25
bin/git-merge-repo
Executable file
25
bin/git-merge-repo
Executable file
|
|
@ -0,0 +1,25 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
repo=$1
|
||||||
|
branch=$2
|
||||||
|
prefix=$3
|
||||||
|
flat=0
|
||||||
|
|
||||||
|
if test "$prefix" = "."; then
|
||||||
|
prefix=$(mktemp -u 'git-merge-repo.XXXXXXX')
|
||||||
|
flat=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
git subtree add -P "$prefix" "$repo" "$branch" "${@:4}"
|
||||||
|
|
||||||
|
message=$(git log -1 --pretty=%B)
|
||||||
|
|
||||||
|
if test $flat -eq 1; then
|
||||||
|
git stash -u
|
||||||
|
mv -i "$prefix"/* ./
|
||||||
|
git undo
|
||||||
|
git add .
|
||||||
|
git commit -am "$message"
|
||||||
|
git stash apply
|
||||||
|
rm -drf "$prefix"
|
||||||
|
fi
|
||||||
|
|
@ -1,21 +1,52 @@
|
||||||
#!/bin/sh
|
#!/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 [[ $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 @@
|
||||||
#!/bin/sh
|
#!/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
|
||||||
77
bin/git-pr
Executable file
77
bin/git-pr
Executable file
|
|
@ -0,0 +1,77 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# Based on https://gist.github.com/gnarf/5406589 and https://gist.github.com/jhnns/d654d9d6da6d3b749986
|
||||||
|
TO_MERGE=
|
||||||
|
|
||||||
|
pull() {
|
||||||
|
local remote="$1"
|
||||||
|
local id="$2"
|
||||||
|
local branch="$3"
|
||||||
|
local ref="refs/pull/$id/head"
|
||||||
|
|
||||||
|
if [ -n "$TO_MERGE" ]; then
|
||||||
|
ref="refs/pull/$id/merge"
|
||||||
|
fi
|
||||||
|
|
||||||
|
git fetch -fu "$remote" "$ref:$branch" && \
|
||||||
|
git checkout "$branch" && \
|
||||||
|
git config --local --replace "branch.$branch.merge" "$ref" && \
|
||||||
|
git config --local --replace "branch.$branch.remote" "$remote"
|
||||||
|
}
|
||||||
|
|
||||||
|
pull_pr_if_matched() {
|
||||||
|
if [[ $1 =~ ^(.*):([0-9]+)|(https?://[^/]+/.+)/pull/([0-9]+).*$ ]]; then
|
||||||
|
if [[ -n ${BASH_REMATCH[2]} ]]; then
|
||||||
|
remote="${BASH_REMATCH[1]:-origin}"
|
||||||
|
id="${BASH_REMATCH[2]}"
|
||||||
|
else
|
||||||
|
remote="${BASH_REMATCH[3]}.git"
|
||||||
|
id="${BASH_REMATCH[4]}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
branch=pr/$id
|
||||||
|
pull "$remote" "$id" "$branch"
|
||||||
|
return $?
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "$1 doesn't match the pr id pattern."
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
for arg in "$@"; do
|
||||||
|
case "$arg" in
|
||||||
|
-m|--merge)
|
||||||
|
TO_MERGE=1
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
# set the argument back
|
||||||
|
set -- "$@" "$arg"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
shift
|
||||||
|
done
|
||||||
|
|
||||||
|
test -z "$1" && echo "pr number required." 1>&2 && exit 1
|
||||||
|
|
||||||
|
if test "$1" = "clean"; then
|
||||||
|
git for-each-ref refs/heads/pr/* --format='%(refname)' | while read -r ref; do
|
||||||
|
git branch -D "${ref#refs/heads/}"
|
||||||
|
done
|
||||||
|
|
||||||
|
elif [[ "$1" =~ ^[0-9]+$ ]]; then
|
||||||
|
remote_pref=${2:-$(git config --get git-extras.pr.remote)}
|
||||||
|
remote=${remote_pref:-origin}
|
||||||
|
id=$1
|
||||||
|
branch=pr/$id
|
||||||
|
pull "$remote" "$id" "$branch"
|
||||||
|
|
||||||
|
else
|
||||||
|
rc=1
|
||||||
|
while [ "$1" != "" ]; do
|
||||||
|
pull_pr_if_matched "$1"
|
||||||
|
rc=$?
|
||||||
|
shift
|
||||||
|
done
|
||||||
|
|
||||||
|
exit "$rc"
|
||||||
|
fi
|
||||||
83
bin/git-psykorebase
Executable file
83
bin/git-psykorebase
Executable file
|
|
@ -0,0 +1,83 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
PROGRAM=$0
|
||||||
|
PRIMARY_BRANCH=""
|
||||||
|
SECONDARY_BRANCH=""
|
||||||
|
FF="--ff"
|
||||||
|
CONTINUE="no"
|
||||||
|
|
||||||
|
current_branch() {
|
||||||
|
git rev-parse --abbrev-ref HEAD
|
||||||
|
}
|
||||||
|
|
||||||
|
function usage()
|
||||||
|
{
|
||||||
|
echo "USAGE: ${PROGRAM} PRIMARY_BRANCH [SECONDARY_BRANCH] [--no-ff]"
|
||||||
|
echo "USAGE: ${PROGRAM} --continue"
|
||||||
|
echo ""
|
||||||
|
echo "OPTIONS:"
|
||||||
|
echo " --no-ff: Force rebase commit."
|
||||||
|
echo " -c|--continue: Continue after the user updates conflicts."
|
||||||
|
}
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]
|
||||||
|
do
|
||||||
|
key="$1"
|
||||||
|
|
||||||
|
case $key in
|
||||||
|
--no-ff)
|
||||||
|
FF="--no-ff"
|
||||||
|
;;
|
||||||
|
-c|--continue)
|
||||||
|
CONTINUE=yes
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
if [[ "$PRIMARY_BRANCH" == "" ]]; then
|
||||||
|
PRIMARY_BRANCH=$key
|
||||||
|
elif [[ "$SECONDARY_BRANCH" == "" ]]; then
|
||||||
|
SECONDARY_BRANCH=$key
|
||||||
|
else
|
||||||
|
usage
|
||||||
|
exit 20 # Error during arguments parsing
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
shift # past argument or value
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ "$SECONDARY_BRANCH" == "" ]]; then
|
||||||
|
SECONDARY_BRANCH=$(current_branch)
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$CONTINUE" == "yes" ]]; then
|
||||||
|
TARGET_BRANCH=$(current_branch)
|
||||||
|
SECONDARY_BRANCH=${TARGET_BRANCH%"-rebased-on-top-of-"*}
|
||||||
|
PRIMARY_BRANCH=${TARGET_BRANCH#*"-rebased-on-top-of-"}
|
||||||
|
if [[ "${SECONDARY_BRANCH}-rebased-on-top-of-${PRIMARY_BRANCH}" != $TARGET_BRANCH ]]; then
|
||||||
|
echo "Couldn't continue rebasing on ${TARGET_BRANCH}"
|
||||||
|
exit 30 # Impossible to detect PRIMARY_BRANCH AND SECONDARY_BRANCH
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Continuing rebasing of $SECONDARY_BRANCH on top of $PRIMARY_BRANCH"
|
||||||
|
git commit || exit 51
|
||||||
|
git branch -d "${SECONDARY_BRANCH}" || exit 52
|
||||||
|
git branch -m "${TARGET_BRANCH}" "${SECONDARY_BRANCH}" || exit 53
|
||||||
|
|
||||||
|
elif [[ "$PRIMARY_BRANCH" == "" ]]; then
|
||||||
|
usage
|
||||||
|
exit 10 # Missing arguments PRIMARY_BRANCH
|
||||||
|
else
|
||||||
|
echo "Rebasing $SECONDARY_BRANCH on top of $PRIMARY_BRANCH"
|
||||||
|
TARGET_BRANCH="${SECONDARY_BRANCH}-rebased-on-top-of-${PRIMARY_BRANCH}"
|
||||||
|
|
||||||
|
git checkout "${PRIMARY_BRANCH}" || exit 41
|
||||||
|
git checkout -b "${TARGET_BRANCH}" || exit 42
|
||||||
|
|
||||||
|
if git merge "${SECONDARY_BRANCH}" ${FF} \
|
||||||
|
-m "Psycho-rebased branch ${SECONDARY_BRANCH} on top of ${PRIMARY_BRANCH}"; then
|
||||||
|
git branch -d "${SECONDARY_BRANCH}" || exit 43
|
||||||
|
git branch -m "${TARGET_BRANCH}" "${SECONDARY_BRANCH}" || exit 44
|
||||||
|
else
|
||||||
|
echo "Resolve the conflict and run ``${PROGRAM} --continue``."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
@ -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
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
#!/bin/sh
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
# Warn on a dirty work tree.
|
# Warn on a dirty work tree.
|
||||||
git rev-parse --verify HEAD >/dev/null || exit 1
|
git rev-parse --verify HEAD >/dev/null || exit 1
|
||||||
|
|
@ -15,28 +15,28 @@ then
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Use a temporary index.
|
# Use a temporary index.
|
||||||
index="/tmp/$(basename $0).$$.tmp"
|
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,15 +0,0 @@
|
||||||
#!/bin/sh
|
|
||||||
# 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
|
|
||||||
fi
|
|
||||||
136
bin/git-release
136
bin/git-release
|
|
@ -1,21 +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
|
||||||
if test -f $hook; then
|
# compat without extname
|
||||||
|
if test ! -f "$hook"; then
|
||||||
|
hook=.git/hooks/$1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if test -f "$hook"; then
|
||||||
echo "... $1"
|
echo "... $1"
|
||||||
. $hook
|
shift
|
||||||
|
if test -x "$hook"; then
|
||||||
|
$hook "$@"
|
||||||
|
else
|
||||||
|
. "$hook" "$@"
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
exit_with_msg() {
|
||||||
|
>&2 echo "$1"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
if test $# -gt 0; then
|
if test $# -gt 0; then
|
||||||
hook pre-release
|
remote=''
|
||||||
echo "... releasing $1"
|
|
||||||
git commit -a -m "Release $1"
|
# check for flags
|
||||||
git tag $1 -a -m "Release $1" \
|
while test $# != 0
|
||||||
&& git push $2 \
|
do
|
||||||
&& git push $2 --tags \
|
case "$1" in
|
||||||
&& hook post-release \
|
-c) need_changelog=true;;
|
||||||
|
-r) remote=$2; shift ;;
|
||||||
|
-m) msg=$2; shift ;;
|
||||||
|
-s)
|
||||||
|
test -n "$keyid" &&
|
||||||
|
exit_with_msg "Please use '-s' OR '-u'"
|
||||||
|
sign=true
|
||||||
|
;;
|
||||||
|
-u)
|
||||||
|
test -n "$sign" &&
|
||||||
|
exit_with_msg "Please use '-s' OR '-u'"
|
||||||
|
keyid=$2
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--semver)
|
||||||
|
test -z "$2" &&
|
||||||
|
exit_with_msg "major/minor/patch required for --semver option"
|
||||||
|
semver=$2
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--prefix)
|
||||||
|
test -z "$2" &&
|
||||||
|
exit_with_msg "prefix string required for --prefix option"
|
||||||
|
prefix="$2"
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--no-empty-commit) no_empty_commit=true;;
|
||||||
|
--) shift; hook_args="$hook_args $*"; break;;
|
||||||
|
*) test -z "$version" && version=$1 ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
shift
|
||||||
|
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ -n "$semver" ]; then
|
||||||
|
if [ -z "$(git tag)" ]; then
|
||||||
|
echo "there is no tag in the git repo" 1>&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
latest_tag=$(git describe --tags "$(git rev-list --tags --max-count=1)")
|
||||||
|
|
||||||
|
if [[ ! "$latest_tag" =~ \
|
||||||
|
^$prefix([^0-9]*)([1-9][0-9]+|[0-9])\.([1-9][0-9]+|[0-9])\.([1-9][0-9]+|[0-9])(.*) ]]; then
|
||||||
|
echo "the latest tag doesn't match semver format requirement" 1>&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
case "$semver" in
|
||||||
|
major ) version="${BASH_REMATCH[2]}" ;;
|
||||||
|
minor ) version="${BASH_REMATCH[3]}" ;;
|
||||||
|
patch ) version="${BASH_REMATCH[4]}" ;;
|
||||||
|
* ) echo "invalid semver argument given: $semver" 1>&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
(( ++version ))
|
||||||
|
|
||||||
|
case "$semver" in
|
||||||
|
major ) version="$prefix${BASH_REMATCH[1]}$version.0.0${BASH_REMATCH[5]}" ;;
|
||||||
|
minor ) version="$prefix${BASH_REMATCH[1]}${BASH_REMATCH[2]}.$version.0${BASH_REMATCH[5]}" ;;
|
||||||
|
patch ) version="$prefix${BASH_REMATCH[1]}${BASH_REMATCH[2]}.${BASH_REMATCH[3]}.$version${BASH_REMATCH[5]}" ;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
|
hook_args="$version"
|
||||||
|
|
||||||
|
if [ -z "$msg" ]; then
|
||||||
|
msg="Release ${version}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# shellcheck disable=SC2086
|
||||||
|
hook pre-release $hook_args \
|
||||||
|
|| exit_with_msg "pre-release hook failed! Cancelling release."
|
||||||
|
echo "... releasing $version"
|
||||||
|
if [ "$need_changelog" = true ]; then
|
||||||
|
git-changelog -t "$version"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$no_empty_commit" = true ]; then
|
||||||
|
git commit -a -m "$msg" || true
|
||||||
|
else
|
||||||
|
git commit -a -m "$msg" --allow-empty
|
||||||
|
fi
|
||||||
|
|
||||||
|
declare -a sign_args
|
||||||
|
if [ "$sign" = true ]; then
|
||||||
|
sign_args=("-s")
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -n "$keyid" ]; then
|
||||||
|
sign_args=("-u" "$keyid")
|
||||||
|
fi
|
||||||
|
|
||||||
|
# shellcheck disable=SC2086
|
||||||
|
git tag "${sign_args[@]}" $version -a -m "$msg" \
|
||||||
|
&& git push $remote --tags \
|
||||||
|
&& git push $remote \
|
||||||
|
&& hook post-release $hook_args \
|
||||||
&& echo "... complete"
|
&& echo "... complete"
|
||||||
else
|
else
|
||||||
echo "tag required" 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
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
#!/bin/sh
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
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/tag/$old
|
git push origin ":refs/tags/$old"
|
||||||
|
|
|
||||||
80
bin/git-repl
80
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
|
||||||
|
# shellcheck disable=SC2086
|
||||||
eval ${cmd:1}
|
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
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,17 @@
|
||||||
#!/bin/sh
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
file="$1"
|
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
|
||||||
|
|
|
||||||
30
bin/git-root
Executable file
30
bin/git-root
Executable file
|
|
@ -0,0 +1,30 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
git_root() {
|
||||||
|
git rev-parse --show-toplevel
|
||||||
|
}
|
||||||
|
|
||||||
|
# get the relative path of current path according to root of repo
|
||||||
|
git_root_relative() {
|
||||||
|
rel=$(git rev-parse --show-prefix)
|
||||||
|
if [ -z "$rel" ]; then
|
||||||
|
# git rev-parse --show-prefix will output empty string when we are in the root dir
|
||||||
|
echo "."
|
||||||
|
else
|
||||||
|
echo "$rel"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
if test $# -eq 0; then
|
||||||
|
git_root
|
||||||
|
else
|
||||||
|
case "$1" in
|
||||||
|
-r|--relative)
|
||||||
|
git_root_relative
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
git_root
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
1
bin/git-rscp
Symbolic link
1
bin/git-rscp
Symbolic link
|
|
@ -0,0 +1 @@
|
||||||
|
git-scp
|
||||||
267
bin/git-scp
Executable file
267
bin/git-scp
Executable file
|
|
@ -0,0 +1,267 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
COLOR_RED() { test -t 1 && echo -n "$(tput setaf 1)"; }
|
||||||
|
COLOR_GREEN() { test -t 1 && echo -n "$(tput setaf 2)"; }
|
||||||
|
COLOR_YELLOW(){ test -t 1 && echo -n "$(tput setaf 3)"; }
|
||||||
|
COLOR_BLUE() { test -t 1 && echo -n "$(tput setaf 4)"; }
|
||||||
|
COLOR_RESET() { test -t 1 && echo -n "$(tput sgr 0)"; }
|
||||||
|
|
||||||
|
function _test_git_scp()
|
||||||
|
{
|
||||||
|
command -v rsync > /dev/null || _error requires rsync
|
||||||
|
command -v git > /dev/null || _error requires git
|
||||||
|
command -v ssh > /dev/null || _error requires ssh
|
||||||
|
command -v php > /dev/null || _info optional php
|
||||||
|
command -v dos2unix > /dev/null || _info optional dos2unix
|
||||||
|
}
|
||||||
|
|
||||||
|
function set_remote()
|
||||||
|
{
|
||||||
|
remote=$1
|
||||||
|
if [ "$(git remote | grep -c -- "^$remote$")" -eq 0 ]
|
||||||
|
then
|
||||||
|
COLOR_RED
|
||||||
|
echo "Remote $remote does not exist in your git config"
|
||||||
|
COLOR_RESET
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check file for PHP syntax errors
|
||||||
|
# takes a list of filenames
|
||||||
|
function php_lint()
|
||||||
|
{
|
||||||
|
local error_count=()
|
||||||
|
for i
|
||||||
|
do
|
||||||
|
# check if file exists
|
||||||
|
# check if file ends with ".php"
|
||||||
|
test ! -f "$i" && continue
|
||||||
|
case "$i" in
|
||||||
|
*\.php|*\.phtml)
|
||||||
|
if ! php -l "$i" > /dev/null; then
|
||||||
|
error_count[${#error_count[@]}]="$i"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# syntax check fails, force exit
|
||||||
|
test ${#error_count[@]} -gt 0 &&
|
||||||
|
COLOR_RED &&
|
||||||
|
echo "Error: ${#error_count[@]} PHP syntax error found" &&
|
||||||
|
echo "${error_count[@]}" | tr " " '\n' &&
|
||||||
|
COLOR_RESET &&
|
||||||
|
exit 255
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
function _dos2unix()
|
||||||
|
{
|
||||||
|
command -v dos2unix > /dev/null && dos2unix "$@"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
function _sanitize()
|
||||||
|
{
|
||||||
|
git config --get-all extras.scp.sanitize | while read -r i
|
||||||
|
do
|
||||||
|
case $i in
|
||||||
|
php_lint) php_lint "$@";; # git config --global --add extras.scp.sanitize php_lint
|
||||||
|
dos2unix) _dos2unix "$@";; # git config --global --add extras.scp.sanitize dos2unix
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
return $?
|
||||||
|
}
|
||||||
|
|
||||||
|
function scp_and_stage
|
||||||
|
{
|
||||||
|
local verbose=0 interactive=0 dry_run=0
|
||||||
|
while :
|
||||||
|
do
|
||||||
|
case "$1" in
|
||||||
|
-v|--verbose) verbose=1; shift;;
|
||||||
|
-i|--interactive) verbose=1; interactive=1; shift;;
|
||||||
|
-n|--dry-run) verbose=1; dry_run=1; shift;;
|
||||||
|
*) break;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
set_remote "$1"
|
||||||
|
shift
|
||||||
|
|
||||||
|
local refhead
|
||||||
|
refhead="$(git rev-parse --quiet --verify "$1")"
|
||||||
|
if [ -n "$refhead" ]
|
||||||
|
then
|
||||||
|
shift
|
||||||
|
[ "$(git branch --contains "$refhead" | grep -c '\*')" -eq 0 ] &&
|
||||||
|
_error "refhead provided is not part of current branch"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ $# -ge 1 ]
|
||||||
|
then
|
||||||
|
list=$(git ls-files "$@")" "$(git ls-files -o "$@")
|
||||||
|
elif [ -n "$refhead" ]
|
||||||
|
then
|
||||||
|
[ "$verbose" -eq 1 ] && git --no-pager diff --stat "$refhead"
|
||||||
|
list=$(git diff "$refhead" --name-only)
|
||||||
|
else
|
||||||
|
[ "$verbose" -eq 1 ] && git --no-pager diff
|
||||||
|
list=$(git diff --name-only)
|
||||||
|
fi
|
||||||
|
|
||||||
|
deleted=$(for i in $list; do [ -f "$i" ] || echo "$i"; done)
|
||||||
|
list=$(for i in $list; do [ -f "$i" ] && echo "$i"; done)
|
||||||
|
|
||||||
|
if [ "$interactive" -eq 1 ] && [ "$dry_run" -eq 0 ] && { [ -n "$list" ] || [ -n "$deleted" ]; }
|
||||||
|
then
|
||||||
|
local reply
|
||||||
|
read -r -p "Proceed with sync to $remote? [y/N] " reply
|
||||||
|
case "$reply" in
|
||||||
|
y|Y|yes|YES) ;;
|
||||||
|
*) _info "Aborted, nothing synced."; exit 0;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
|
local status=0
|
||||||
|
|
||||||
|
if [ -n "$list" ]
|
||||||
|
then
|
||||||
|
local _TMP=${0///}
|
||||||
|
# shellcheck disable=SC2086
|
||||||
|
echo "$list" > "$_TMP"
|
||||||
|
if [ "$dry_run" -eq 1 ] || _sanitize $list
|
||||||
|
then
|
||||||
|
_info "Pushing to $remote ($(git config "remote.$remote.url"))"
|
||||||
|
if [ "$dry_run" -eq 1 ]
|
||||||
|
then
|
||||||
|
rsync -rlDvn --files-from="$_TMP" ./ "$(git config "remote.$remote.url")/"
|
||||||
|
else
|
||||||
|
rsync -rlDv --files-from="$_TMP" ./ "$(git config "remote.$remote.url")/" &&
|
||||||
|
git add --force $list
|
||||||
|
fi
|
||||||
|
status=$?
|
||||||
|
else
|
||||||
|
status=1
|
||||||
|
fi
|
||||||
|
rm "$_TMP"
|
||||||
|
fi
|
||||||
|
|
||||||
|
deleted=$(for i in $deleted; do echo "$(git config "remote.$remote.url" | cut -d: -f2)/$i"; done)
|
||||||
|
|
||||||
|
if [ -n "$deleted" ]
|
||||||
|
then
|
||||||
|
COLOR_RED
|
||||||
|
echo Deleted remote files
|
||||||
|
if [ "$dry_run" -eq 1 ]
|
||||||
|
then
|
||||||
|
echo "$deleted"
|
||||||
|
else
|
||||||
|
if ssh "$(git config "remote.$remote.url" | cut -d: -f1)" -t "rm $deleted"
|
||||||
|
then
|
||||||
|
echo "$deleted"
|
||||||
|
else
|
||||||
|
status=1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
COLOR_RESET
|
||||||
|
fi
|
||||||
|
|
||||||
|
return "$status"
|
||||||
|
}
|
||||||
|
|
||||||
|
function reverse_scp()
|
||||||
|
{
|
||||||
|
local verbose=0 interactive=0 dry_run=0
|
||||||
|
while :
|
||||||
|
do
|
||||||
|
case "$1" in
|
||||||
|
-v|--verbose) verbose=1; shift;;
|
||||||
|
-i|--interactive) verbose=1; interactive=1; shift;;
|
||||||
|
-n|--dry-run) verbose=1; dry_run=1; shift;;
|
||||||
|
*) break;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
set_remote "$1"
|
||||||
|
shift
|
||||||
|
|
||||||
|
local _TMP=${0///}
|
||||||
|
echo "$@" > "$_TMP"
|
||||||
|
|
||||||
|
local status=0
|
||||||
|
if [ "$verbose" -eq 1 ]
|
||||||
|
then
|
||||||
|
rsync -rlDvn --files-from="$_TMP" "$(git config "remote.$remote.url")/" ./
|
||||||
|
status=$?
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$dry_run" -eq 1 ]
|
||||||
|
then
|
||||||
|
rm "$_TMP"
|
||||||
|
return "$status"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$interactive" -eq 1 ]
|
||||||
|
then
|
||||||
|
local reply
|
||||||
|
read -r -p "Proceed with copy from $remote? [y/N] " reply
|
||||||
|
case "$reply" in
|
||||||
|
y|Y|yes|YES) ;;
|
||||||
|
*) _info "Aborted, nothing copied."; rm "$_TMP"; exit 0;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
|
rsync -rlDv --files-from="$_TMP" "$(git config "remote.$remote.url")/" ./ &&
|
||||||
|
rm "$_TMP"
|
||||||
|
}
|
||||||
|
|
||||||
|
function _info()
|
||||||
|
{
|
||||||
|
COLOR_YELLOW
|
||||||
|
test $# -gt 0 && echo "$@"
|
||||||
|
COLOR_RESET
|
||||||
|
}
|
||||||
|
|
||||||
|
function _usage()
|
||||||
|
{
|
||||||
|
echo "Usage:
|
||||||
|
git scp -h|help|?
|
||||||
|
git scp [options] <remote> [ref|file..] # scp and stage your files to specified remote
|
||||||
|
git scp [options] <remote> [<ref>] # show diff relative to <ref> and upload unstaged files to <remote>
|
||||||
|
git rscp [options] <remote> [<file|directory>] # copy <remote> files to current working directory
|
||||||
|
|
||||||
|
OPTIONS:
|
||||||
|
-v, --verbose print what will be synced before syncing
|
||||||
|
-i, --interactive prompt for confirmation before syncing (implies --verbose)
|
||||||
|
-n, --dry-run show what would be synced, change nothing (implies --verbose; no staging, no remote writes/deletes)
|
||||||
|
"
|
||||||
|
|
||||||
|
case $1 in
|
||||||
|
-v|verbose|--verbose) grep -A100 '^#* OPTIONS #*$' "$0" ;;
|
||||||
|
esac
|
||||||
|
exit
|
||||||
|
}
|
||||||
|
|
||||||
|
function _error()
|
||||||
|
{
|
||||||
|
[ $# -eq 0 ] && _usage && exit 0
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "ERROR: $*"
|
||||||
|
echo
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
### OPTIONS ###
|
||||||
|
case $(basename "$0") in
|
||||||
|
git-scp)
|
||||||
|
case $1 in
|
||||||
|
''|-h|'?'|help|--help) shift; _test_git_scp; _usage "$@";;
|
||||||
|
*) scp_and_stage "$@";;
|
||||||
|
esac
|
||||||
|
;;
|
||||||
|
git-rscp) reverse_scp "$@";;
|
||||||
|
esac
|
||||||
104
bin/git-sed
Executable file
104
bin/git-sed
Executable file
|
|
@ -0,0 +1,104 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat <<EOF
|
||||||
|
usage: git sed [ -c ] [ -f <flags> ] <search> <replacement> [ <flags> ]
|
||||||
|
|
||||||
|
Run git grep and then send results to sed for replacement with the
|
||||||
|
given flags, if they are provided via -f or as the third argument.
|
||||||
|
|
||||||
|
Also runs git commit if -c is provided.
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
# don't commit by default
|
||||||
|
do_commit() {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
pathspec=
|
||||||
|
while [ "$1" != "" ]; do
|
||||||
|
case "$1" in
|
||||||
|
-c|--commit)
|
||||||
|
if git status --porcelain | grep .; then
|
||||||
|
echo "you need to commit your changes before running with --commit"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
do_commit() {
|
||||||
|
git commit -m"replace $search with $replacement
|
||||||
|
|
||||||
|
actual command:
|
||||||
|
|
||||||
|
$command" -a
|
||||||
|
}
|
||||||
|
;;
|
||||||
|
-f|--flags)
|
||||||
|
if [ "$2" = "" ]; then
|
||||||
|
usage
|
||||||
|
echo "missing argument for $1"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
shift
|
||||||
|
flags=$1
|
||||||
|
;;
|
||||||
|
-h|--help)
|
||||||
|
usage
|
||||||
|
exit
|
||||||
|
;;
|
||||||
|
--)
|
||||||
|
pathspec="$*"
|
||||||
|
break
|
||||||
|
;;
|
||||||
|
-*)
|
||||||
|
usage
|
||||||
|
echo "unknown flag: $1"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
if [ "$search" = "" ]; then
|
||||||
|
search="$1"
|
||||||
|
elif [ "$replacement" = "" ]; then
|
||||||
|
replacement="$1"
|
||||||
|
elif [ "$flags" = "" ]; then
|
||||||
|
flags="$1"
|
||||||
|
else
|
||||||
|
usage
|
||||||
|
echo "too many arguments: $1"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
shift
|
||||||
|
done
|
||||||
|
|
||||||
|
all="$search$replacement$flags"
|
||||||
|
case "$all" in
|
||||||
|
*/*)
|
||||||
|
ascii="$(for((i=32;i<=127;i++)) do printf '%b' "\\$(printf '%03o' "$i")"; done)"
|
||||||
|
escaped="${all//-/\\-}"
|
||||||
|
escaped="${escaped//[/\\[}"
|
||||||
|
sep="$(printf '%s' "$ascii" | tr -d "$escaped")"
|
||||||
|
sep="$(printf %.1s "$sep")"
|
||||||
|
if [ "$sep" = "" ] ; then
|
||||||
|
echo 'could not find an unused character for sed separator character'
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
sep=/
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
r=$(xargs -r false < /dev/null > /dev/null 2>&1 && echo r)
|
||||||
|
need_bak=$(sed -i s/hello/world/ "$(git_extra_mktemp)" > /dev/null 2>&1 || echo true)
|
||||||
|
|
||||||
|
if [ "$need_bak" ]; then
|
||||||
|
command="git grep -lz '$search' $pathspec | xargs -0$r sed -i '' 's$sep$search$sep$replacement$sep$flags'"
|
||||||
|
# shellcheck disable=SC2086
|
||||||
|
git grep -lz "$search" $pathspec | xargs -0"$r" sed -i '' "s$sep$search$sep$replacement$sep$flags"
|
||||||
|
else
|
||||||
|
command="git grep -lz '$search' $pathspec | xargs -0$r sed -i 's$sep$search$sep$replacement$sep$flags'"
|
||||||
|
# shellcheck disable=SC2086
|
||||||
|
git grep -lz "$search" $pathspec | xargs -0"$r" sed -i "s$sep$search$sep$replacement$sep$flags"
|
||||||
|
fi
|
||||||
|
do_commit
|
||||||
|
|
@ -1,4 +1,11 @@
|
||||||
#!/bin/sh
|
#!/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
|
||||||
|
|
@ -13,4 +20,4 @@ mkdir -p "$dir" \
|
||||||
&& gitdirexists \
|
&& gitdirexists \
|
||||||
&& git init \
|
&& git init \
|
||||||
&& git add . \
|
&& git add . \
|
||||||
&& git commit -m 'Initial commit'
|
&& git commit --allow-empty -m "$COMMIT_MESSAGE"
|
||||||
|
|
|
||||||
3
bin/git-show-merged-branches
Executable file
3
bin/git-show-merged-branches
Executable file
|
|
@ -0,0 +1,3 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
git branch --no-color --merged | grep -v "\*" | grep -v "$(git_extra_default_branch)" | tr -d ' '
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
#!/bin/sh
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
git log --all --graph --decorate --oneline --simplify-by-decoration
|
git log --all --graph --decorate --oneline --simplify-by-decoration
|
||||||
|
|
|
||||||
3
bin/git-show-unmerged-branches
Executable file
3
bin/git-show-unmerged-branches
Executable file
|
|
@ -0,0 +1,3 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
git branch --no-color --no-merged | grep -v "\*" | grep -v "$(git_extra_default_branch)" | tr -d ' '
|
||||||
|
|
@ -1,7 +1,26 @@
|
||||||
#!/bin/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,9 +31,10 @@ 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"
|
||||||
}
|
}
|
||||||
|
|
||||||
commit_if_msg_provided() {
|
commit_if_msg_provided() {
|
||||||
|
|
@ -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,23 +1,81 @@
|
||||||
#!/bin/sh
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
commit=""
|
|
||||||
test $# -ne 0 && commit=$@
|
cd "$(git root)" || { echo "Can't cd to top level directory";exit 1; }
|
||||||
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 }
|
||||||
'
|
'
|
||||||
|
|
@ -26,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 @@
|
||||||
#!/bin/sh
|
#!/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
|
||||||
|
|
|
||||||
71
bin/git-undo
71
bin/git-undo
|
|
@ -1,19 +1,72 @@
|
||||||
#!/bin/sh
|
#!/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 @@
|
||||||
#!/bin/sh
|
#!/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"
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue