mirror of
https://github.com/gogs/gogs.git
synced 2026-02-28 17:20:59 +01:00
Compare commits
1743 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b3757e424f | ||
|
|
2381fe72cb | ||
|
|
e28bc7023f | ||
|
|
aff8fb28bd | ||
|
|
6a543c8066 | ||
|
|
4d90527a6e | ||
|
|
c64b842df9 | ||
|
|
27c4252548 | ||
|
|
10b93efc4a | ||
|
|
0696d430c9 | ||
|
|
68b231bd89 | ||
|
|
6ec859f2b0 | ||
|
|
452551fa23 | ||
|
|
ec05c64ead | ||
|
|
cd15a17970 | ||
|
|
6072e9a52c | ||
|
|
b78e03934d | ||
|
|
3c0d162961 | ||
|
|
48cf9edcf5 | ||
|
|
0c8c1ee96f | ||
|
|
7d80c5a722 | ||
|
|
5ec21d56ef | ||
|
|
266c8f5a85 | ||
|
|
162504e90c | ||
|
|
5ea0592f61 | ||
|
|
054e97d614 | ||
|
|
28983c94ff | ||
|
|
0cfa489cf0 | ||
|
|
e0af5c280d | ||
|
|
d21dc0da78 | ||
|
|
60aca9ea18 | ||
|
|
6a8ad0b357 | ||
|
|
bd970b8b27 | ||
|
|
0f3155660e | ||
|
|
437df04d8c | ||
|
|
8216360da8 | ||
|
|
c5b85f2733 | ||
|
|
ba88bb5f5f | ||
|
|
5895d43574 | ||
|
|
e78f252713 | ||
|
|
6a78580429 | ||
|
|
6002d72603 | ||
|
|
934734a85a | ||
|
|
491934784f | ||
|
|
fe69a7b116 | ||
|
|
0bef74d499 | ||
|
|
2322de653c | ||
|
|
95b94b2166 | ||
|
|
dd4d623612 | ||
|
|
41a2632ae7 | ||
|
|
f3a228ed61 | ||
|
|
c4fdc26fa5 | ||
|
|
7de71333c6 | ||
|
|
cd9b29ff3f | ||
|
|
50a7c3c20d | ||
|
|
c3f52ab52d | ||
|
|
40fbe7fa8e | ||
|
|
ef922ff757 | ||
|
|
1841316f18 | ||
|
|
0a2f87f941 | ||
|
|
c69900325d | ||
|
|
685737b816 | ||
|
|
5d766bc4d6 | ||
|
|
1083c0cd9a | ||
|
|
7b8f086f72 | ||
|
|
21d538a738 | ||
|
|
36f448f47f | ||
|
|
dbd9f05c06 | ||
|
|
77757f6d39 | ||
|
|
018614cdf0 | ||
|
|
5d9680b00d | ||
|
|
c8982f4165 | ||
|
|
71f0dd858b | ||
|
|
956bdb18c9 | ||
|
|
a07b1f630a | ||
|
|
266586e866 | ||
|
|
b481927d5e | ||
|
|
7bd6052efe | ||
|
|
07801cbf09 | ||
|
|
81e74858d8 | ||
|
|
f4e714d54a | ||
|
|
edccbc0481 | ||
|
|
7e09d210ba | ||
|
|
dab768212a | ||
|
|
c033cfc684 | ||
|
|
89593a82c1 | ||
|
|
ba93504804 | ||
|
|
57cb23ac81 | ||
|
|
5155f026b4 | ||
|
|
d521e716dd | ||
|
|
3b49a99b60 | ||
|
|
f129e0ecb5 | ||
|
|
88143f1934 | ||
|
|
3137665e6e | ||
|
|
f35bd34002 | ||
|
|
b9560ec9cb | ||
|
|
189924cabf | ||
|
|
c2277796e4 | ||
|
|
f97b250509 | ||
|
|
59981b8818 | ||
|
|
40bce6310c | ||
|
|
a4f9e5031f | ||
|
|
4da325a45c | ||
|
|
fd5881fb64 | ||
|
|
0f6e464126 | ||
|
|
6132a82287 | ||
|
|
32a868d431 | ||
|
|
94f91543b6 | ||
|
|
3d52ef6e39 | ||
|
|
a45205b988 | ||
|
|
ec9c14c09d | ||
|
|
b6fc35f637 | ||
|
|
e24d62e583 | ||
|
|
0386b5ae54 | ||
|
|
a0253cab62 | ||
|
|
0f32aeec70 | ||
|
|
78145cd166 | ||
|
|
904f0ebec3 | ||
|
|
039dc33367 | ||
|
|
859009259a | ||
|
|
4c5255f5ad | ||
|
|
279e475b89 | ||
|
|
f4aedda13a | ||
|
|
1381f0f28e | ||
|
|
f967e9d021 | ||
|
|
5179063e71 | ||
|
|
ad4bbf5173 | ||
|
|
35f30a306b | ||
|
|
c37d3f6486 | ||
|
|
bd786b8ef0 | ||
|
|
d02e7d9e6a | ||
|
|
1c87b082c1 | ||
|
|
99d86c7175 | ||
|
|
68ead67a63 | ||
|
|
2d38b75400 | ||
|
|
f59a607361 | ||
|
|
d21767dc9f | ||
|
|
ab78d4e2b7 | ||
|
|
127005d733 | ||
|
|
0ae666f3e6 | ||
|
|
23f2efa8c1 | ||
|
|
5791e1398c | ||
|
|
9b72661767 | ||
|
|
0958fe5a4e | ||
|
|
4a1dc29e23 | ||
|
|
2ca668e79e | ||
|
|
ee59016585 | ||
|
|
7ac09681a2 | ||
|
|
1863f38286 | ||
|
|
6b6f54b79b | ||
|
|
b67ec01d41 | ||
|
|
2c154ccbe7 | ||
|
|
3f95824e65 | ||
|
|
29722af1ae | ||
|
|
563fc7c6d7 | ||
|
|
9b8fa69c15 | ||
|
|
f5dc436441 | ||
|
|
d9d329bec8 | ||
|
|
76879e977b | ||
|
|
9c3c9a8eb9 | ||
|
|
2fd69f13d9 | ||
|
|
eb66060cd7 | ||
|
|
8a19f8a63c | ||
|
|
f7c11a27d0 | ||
|
|
418dab9b96 | ||
|
|
09ad42b918 | ||
|
|
074c92b0a3 | ||
|
|
e893e1fc63 | ||
|
|
6622b7b49a | ||
|
|
afab38b0d7 | ||
|
|
ede58ade4c | ||
|
|
a2dd9ec2e1 | ||
|
|
591c333dcd | ||
|
|
6971143dc5 | ||
|
|
5d6ea4a81b | ||
|
|
2374a3ef64 | ||
|
|
0d9e435bfe | ||
|
|
9af0dd23dd | ||
|
|
39eb6df769 | ||
|
|
c0f25d24b2 | ||
|
|
4f778da35e | ||
|
|
f0f8205b8b | ||
|
|
2bb1de1805 | ||
|
|
0081c6911d | ||
|
|
2527037973 | ||
|
|
1d951cfc49 | ||
|
|
32a0255ce3 | ||
|
|
be3a13a0d6 | ||
|
|
d293aa9ced | ||
|
|
0cfcaca351 | ||
|
|
412ba5b2a9 | ||
|
|
c17f93e6c0 | ||
|
|
a5afa37203 | ||
|
|
6bfbed0616 | ||
|
|
185c90df12 | ||
|
|
7463d9c51d | ||
|
|
ec5a967937 | ||
|
|
4bc98f7aa2 | ||
|
|
207960b459 | ||
|
|
7b3b46c675 | ||
|
|
5e01ecbc05 | ||
|
|
c98aa0e895 | ||
|
|
263203ec28 | ||
|
|
84f28fc5d6 | ||
|
|
9144ea2b1d | ||
|
|
0d73dcaf0f | ||
|
|
a6a3afd130 | ||
|
|
7aa53635fe | ||
|
|
2ee0c61e62 | ||
|
|
3d23c13160 | ||
|
|
e2afd886fd | ||
|
|
aa682b3b7e | ||
|
|
e43479d948 | ||
|
|
a2f2f7717a | ||
|
|
740192564b | ||
|
|
8bcc0e392e | ||
|
|
ae319da5fd | ||
|
|
6d6848af5c | ||
|
|
0d60b58434 | ||
|
|
ad57f18894 | ||
|
|
80cd8f6a29 | ||
|
|
8cb903fbbb | ||
|
|
d1c327d508 | ||
|
|
eaf57229d3 | ||
|
|
4e967bc765 | ||
|
|
64788ffff6 | ||
|
|
3143e35d83 | ||
|
|
8d5a693382 | ||
|
|
1e185787a9 | ||
|
|
1b2ecde1c9 | ||
|
|
e8c3e9bcf8 | ||
|
|
cdfcef04a1 | ||
|
|
8aa35577b3 | ||
|
|
c4086d43db | ||
|
|
8059175a5c | ||
|
|
f8fd084bd2 | ||
|
|
d528704503 | ||
|
|
98076ee72d | ||
|
|
e6ef75204b | ||
|
|
d674cb7f20 | ||
|
|
114c179e5a | ||
|
|
598e062241 | ||
|
|
bf70082c22 | ||
|
|
2994272e91 | ||
|
|
cdedc2d188 | ||
|
|
42a3bbb0f4 | ||
|
|
eb79532812 | ||
|
|
5feb68a589 | ||
|
|
03f7f3ee67 | ||
|
|
6383bf7480 | ||
|
|
f471ef1bc7 | ||
|
|
7ebe0a9916 | ||
|
|
89e93fe01e | ||
|
|
0f8a5fdf49 | ||
|
|
3c91c9063b | ||
|
|
e629c7583b | ||
|
|
539b07e205 | ||
|
|
16d3e7085e | ||
|
|
c47fbc629b | ||
|
|
d04b19545d | ||
|
|
3b0e2c1c3f | ||
|
|
79a2745b4a | ||
|
|
6cc992ea54 | ||
|
|
3376354ed8 | ||
|
|
c2e1370588 | ||
|
|
93616fe776 | ||
|
|
e5972bbcde | ||
|
|
952e510dfa | ||
|
|
8f442dde03 | ||
|
|
c1e53cdc72 | ||
|
|
e9f6a43073 | ||
|
|
67380cf47b | ||
|
|
39fdb0f9c4 | ||
|
|
85fbd6e9c6 | ||
|
|
2cfdce88e0 | ||
|
|
ca6cbb95cc | ||
|
|
7cb440273c | ||
|
|
d96f2a7184 | ||
|
|
adcb1d7c65 | ||
|
|
04fbfad2d4 | ||
|
|
bab051a8c1 | ||
|
|
ad6de46ce4 | ||
|
|
282f7fb8fa | ||
|
|
5034ef787c | ||
|
|
3925166d31 | ||
|
|
7358e46815 | ||
|
|
8f09fc64bd | ||
|
|
b2de3d71c5 | ||
|
|
44ed991726 | ||
|
|
34b92cdb44 | ||
|
|
d3b2ff17d6 | ||
|
|
cc45a8ab06 | ||
|
|
105c528369 | ||
|
|
5d0b334d56 | ||
|
|
b092733c2e | ||
|
|
baeccdb161 | ||
|
|
394fc61129 | ||
|
|
55dc9d898f | ||
|
|
552d5c7ceb | ||
|
|
95065de39a | ||
|
|
a3ea4b8802 | ||
|
|
6bcff7828f | ||
|
|
2cb5ec5983 | ||
|
|
2bec8a4f1e | ||
|
|
7e15ff9486 | ||
|
|
12445fe2ed | ||
|
|
491407ddf8 | ||
|
|
6da55159a2 | ||
|
|
73fedc7275 | ||
|
|
c50d59874d | ||
|
|
b3d9ca4ccd | ||
|
|
4efaf8e882 | ||
|
|
178556142a | ||
|
|
7c1fbed057 | ||
|
|
bc902b8f74 | ||
|
|
8ee14db51e | ||
|
|
5d35578811 | ||
|
|
d09fca3ca9 | ||
|
|
dadd35b636 | ||
|
|
c2afdf2192 | ||
|
|
152e715999 | ||
|
|
99c2ae7b35 | ||
|
|
cd9b926af7 | ||
|
|
9ac46fb983 | ||
|
|
8516dfcb6c | ||
|
|
c1ecb6c60a | ||
|
|
43297148b2 | ||
|
|
c0c1a4b01b | ||
|
|
47a3243ff1 | ||
|
|
22e14a0a67 | ||
|
|
48a0b5b026 | ||
|
|
16eb2eb6a3 | ||
|
|
e6ec1ca1f8 | ||
|
|
6f90835f95 | ||
|
|
643142acab | ||
|
|
7c31f235da | ||
|
|
4f40019130 | ||
|
|
780cc2d110 | ||
|
|
2a13f682e0 | ||
|
|
28cf0e6aaa | ||
|
|
92fb30c526 | ||
|
|
9f44c26789 | ||
|
|
3738b6399e | ||
|
|
62b0dc4853 | ||
|
|
429c92c0ce | ||
|
|
579e5e4fee | ||
|
|
ba27d71abe | ||
|
|
7115e3a4d5 | ||
|
|
0114fdcba4 | ||
|
|
dad5c15520 | ||
|
|
6e171c5225 | ||
|
|
f0b5c3b90a | ||
|
|
c30b856d14 | ||
|
|
13c106af77 | ||
|
|
ce1e4348da | ||
|
|
13a8823bd3 | ||
|
|
bbca2916f7 | ||
|
|
37305a59ca | ||
|
|
bb359a74f1 | ||
|
|
6b98d58906 | ||
|
|
8dca9f95fa | ||
|
|
f50e568fd1 | ||
|
|
f8a48ffaad | ||
|
|
67fb0fe6a5 | ||
|
|
0b273ac4d5 | ||
|
|
84b56c3c53 | ||
|
|
06602a84ff | ||
|
|
b710f6bd65 | ||
|
|
95bd19c509 | ||
|
|
7c5710d31f | ||
|
|
7f7216be6e | ||
|
|
ec332cf903 | ||
|
|
2c5411b00c | ||
|
|
a00c932bbc | ||
|
|
6f9a95f830 | ||
|
|
8bf57be9ba | ||
|
|
b1504ed99a | ||
|
|
9349def72e | ||
|
|
6cda35a75f | ||
|
|
2625193a48 | ||
|
|
f3c3258921 | ||
|
|
4042d1f0c3 | ||
|
|
4a46613916 | ||
|
|
6c8fcb3af2 | ||
|
|
61e27dedf7 | ||
|
|
94392a7af3 | ||
|
|
cc647ba9d5 | ||
|
|
5e89485cec | ||
|
|
8637e67e6f | ||
|
|
4a19fd6441 | ||
|
|
54e0ada9d5 | ||
|
|
cd89f6c502 | ||
|
|
660e7a178a | ||
|
|
15845cb287 | ||
|
|
d0a0239bac | ||
|
|
7e7613cdec | ||
|
|
a5b88c4d0c | ||
|
|
dccb0c15b9 | ||
|
|
3f7f4852ef | ||
|
|
0f33b04c87 | ||
|
|
fd3b9ca3aa | ||
|
|
f1a5a4277d | ||
|
|
f59d2dd034 | ||
|
|
5be881756b | ||
|
|
4296427214 | ||
|
|
7551141dbe | ||
|
|
5544a7037b | ||
|
|
dbed39ba05 | ||
|
|
aa1fc30b89 | ||
|
|
fa12c282f6 | ||
|
|
25b23c4bc9 | ||
|
|
7eafe3213f | ||
|
|
2cb04db526 | ||
|
|
96f92e6105 | ||
|
|
a47aef5460 | ||
|
|
1dd003bd4c | ||
|
|
70fbcd2f27 | ||
|
|
5850308a37 | ||
|
|
53c573ed02 | ||
|
|
10b47eddd2 | ||
|
|
5a9709fa9d | ||
|
|
7e9b42c87d | ||
|
|
b6c14f8b21 | ||
|
|
5077408d78 | ||
|
|
0885784f13 | ||
|
|
3380c946e1 | ||
|
|
d625e41c6c | ||
|
|
eb1bfe0e59 | ||
|
|
042d350762 | ||
|
|
0f26f3678a | ||
|
|
6a81632e36 | ||
|
|
b756806ee9 | ||
|
|
81e6f82caf | ||
|
|
29f76f3936 | ||
|
|
ea192147ea | ||
|
|
99812a80fd | ||
|
|
8ad92bb8a4 | ||
|
|
b85927e488 | ||
|
|
87b408a2e5 | ||
|
|
fc68fb951c | ||
|
|
5448d29b2e | ||
|
|
bb51eb5188 | ||
|
|
89f71b44f7 | ||
|
|
c5d4a9e046 | ||
|
|
edd786446c | ||
|
|
b0b88d9bc5 | ||
|
|
15b0cbe318 | ||
|
|
9f0c571238 | ||
|
|
f70343660d | ||
|
|
c8b45ecc27 | ||
|
|
9e8a8867ea | ||
|
|
2c82fc3edb | ||
|
|
bbe866122d | ||
|
|
87954dbfeb | ||
|
|
699c71d319 | ||
|
|
21f25799b4 | ||
|
|
90af997fec | ||
|
|
b73318bc62 | ||
|
|
e5bf4281b5 | ||
|
|
0c2b9bbb2b | ||
|
|
99385db0c4 | ||
|
|
90dd0657b5 | ||
|
|
ec92565f23 | ||
|
|
2772791fda | ||
|
|
08c976f811 | ||
|
|
72dd299ca0 | ||
|
|
30fda0f1ae | ||
|
|
ab9c5fb5e7 | ||
|
|
28dc5bb566 | ||
|
|
cf6d321991 | ||
|
|
50422f1fc2 | ||
|
|
db3d393576 | ||
|
|
2f105f3979 | ||
|
|
ee28fd9255 | ||
|
|
13d492e92a | ||
|
|
e7fd65f0cf | ||
|
|
2eeb0ec9b0 | ||
|
|
b1133c9934 | ||
|
|
991ce42c48 | ||
|
|
10dc330640 | ||
|
|
dfab54d5a2 | ||
|
|
3e22ae3412 | ||
|
|
55b4e77a5e | ||
|
|
ad7ea88923 | ||
|
|
f2884d8e31 | ||
|
|
36a63dd059 | ||
|
|
03ba257ad2 | ||
|
|
fe60ca408b | ||
|
|
0402c803c6 | ||
|
|
29ccf047d8 | ||
|
|
8aa0a76702 | ||
|
|
2d76de2574 | ||
|
|
4d8b905541 | ||
|
|
452aefd025 | ||
|
|
899e799459 | ||
|
|
2295fafb34 | ||
|
|
a562228c5e | ||
|
|
e74630ae3b | ||
|
|
1f2e173a74 | ||
|
|
46e96c008c | ||
|
|
256cd6374a | ||
|
|
57a47bbc05 | ||
|
|
024fcc836b | ||
|
|
250be011c7 | ||
|
|
4e822c1911 | ||
|
|
26d52ceb48 | ||
|
|
d90acacd86 | ||
|
|
69f5308761 | ||
|
|
4b5e09e4d6 | ||
|
|
4f78abe7dc | ||
|
|
e63b2881b1 | ||
|
|
745167d57a | ||
|
|
d7bdc1de8d | ||
|
|
c912494609 | ||
|
|
69dae1ec1c | ||
|
|
cf85e9eb7b | ||
|
|
599716bb1b | ||
|
|
ae88d76032 | ||
|
|
9266f28822 | ||
|
|
6488ee12be | ||
|
|
c2fb01a257 | ||
|
|
5761342f32 | ||
|
|
57af7432fc | ||
|
|
1c7dcdd6b9 | ||
|
|
25b3836418 | ||
|
|
5aa2bf86f4 | ||
|
|
b0eb47cb1c | ||
|
|
e7a4f96fb6 | ||
|
|
60110adc06 | ||
|
|
5ff2dfb23e | ||
|
|
eac32419fc | ||
|
|
e3d3d424b2 | ||
|
|
971e2c3bd6 | ||
|
|
c083d76567 | ||
|
|
52322ef624 | ||
|
|
a4ea3bd015 | ||
|
|
3d93532c87 | ||
|
|
fff615d5fc | ||
|
|
f1b8d52eb3 | ||
|
|
7ca5f8f119 | ||
|
|
194a742fb9 | ||
|
|
160956dd31 | ||
|
|
089bacd54e | ||
|
|
d950bf68e3 | ||
|
|
7796c9e122 | ||
|
|
528ec9bffd | ||
|
|
32ec4734f9 | ||
|
|
6a98e7d914 | ||
|
|
a752f09055 | ||
|
|
846bf2ca9f | ||
|
|
f4ab50501e | ||
|
|
de10387f41 | ||
|
|
1faaaeb3d9 | ||
|
|
12cb84b97f | ||
|
|
fdcca9292e | ||
|
|
a1f717f65a | ||
|
|
70a281a39b | ||
|
|
9fcf66f0e0 | ||
|
|
98b152030d | ||
|
|
467d7dacb6 | ||
|
|
d62ab49978 | ||
|
|
e30c701386 | ||
|
|
401bf944ef | ||
|
|
326c982660 | ||
|
|
4b25bdfbc4 | ||
|
|
528682a294 | ||
|
|
6aa00f7bcf | ||
|
|
f485fcde96 | ||
|
|
6f6b37f148 | ||
|
|
99c3a9390f | ||
|
|
f0df46c88a | ||
|
|
e84ac64964 | ||
|
|
3a30c06345 | ||
|
|
a10ca2c5f6 | ||
|
|
927d9f092b | ||
|
|
7938506e07 | ||
|
|
6f7276278d | ||
|
|
743d22669a | ||
|
|
84841c8c4b | ||
|
|
274a2ca528 | ||
|
|
d4aaef90e6 | ||
|
|
6efb1e5626 | ||
|
|
73b4acbb63 | ||
|
|
8a248696e9 | ||
|
|
8b35c194ec | ||
|
|
ac05f88641 | ||
|
|
4bbb878d20 | ||
|
|
2ce60ff314 | ||
|
|
17a4d8a5e5 | ||
|
|
04592c385b | ||
|
|
bc00da1721 | ||
|
|
76a0e43e88 | ||
|
|
24caccccdd | ||
|
|
26342b0c24 | ||
|
|
a4eaddff81 | ||
|
|
c041273dd3 | ||
|
|
fb970b9d87 | ||
|
|
0240f520ab | ||
|
|
3d105733a9 | ||
|
|
8df3ba96f3 | ||
|
|
d35a1c30f4 | ||
|
|
e9ae926e04 | ||
|
|
28c03f1147 | ||
|
|
84d9aff8a2 | ||
|
|
12d30255a7 | ||
|
|
7826eae452 | ||
|
|
8a2347592d | ||
|
|
bcd4adb3a0 | ||
|
|
bf5faf76eb | ||
|
|
f473895c41 | ||
|
|
fbf43c31e5 | ||
|
|
3c0c7a9f83 | ||
|
|
13216c5c20 | ||
|
|
6be9a2c1db | ||
|
|
d8612f7704 | ||
|
|
0a78d99a4d | ||
|
|
3df8eb60e3 | ||
|
|
0325bec283 | ||
|
|
dfad51fe9e | ||
|
|
7049cb9d97 | ||
|
|
78b8b63774 | ||
|
|
ba314a7a36 | ||
|
|
39356f4238 | ||
|
|
b3c05026df | ||
|
|
ce36fd7a49 | ||
|
|
69e00f9948 | ||
|
|
3257df0da3 | ||
|
|
0f2869069b | ||
|
|
a27c6f4b75 | ||
|
|
d27ca649c7 | ||
|
|
d9900e4dbc | ||
|
|
e75a1444af | ||
|
|
b35157f4ff | ||
|
|
762ab056a2 | ||
|
|
78481f0e42 | ||
|
|
746c7fd4e7 | ||
|
|
7f26ae0b45 | ||
|
|
b5948f2e71 | ||
|
|
79a1bfd963 | ||
|
|
ac53bb593d | ||
|
|
dd36c431ec | ||
|
|
b1d41cfa60 | ||
|
|
9dda9ef07c | ||
|
|
9a43fcb61c | ||
|
|
5ec8ef0230 | ||
|
|
d3db1b2591 | ||
|
|
3decc0b3d6 | ||
|
|
98b58fa050 | ||
|
|
5e11341232 | ||
|
|
90e93b1f3a | ||
|
|
e6f927f61a | ||
|
|
60ae8ac3d2 | ||
|
|
004fb30ebe | ||
|
|
3fb4f7f498 | ||
|
|
9e09e48502 | ||
|
|
c79774e8d4 | ||
|
|
d6b09c35f7 | ||
|
|
0048980da5 | ||
|
|
d169d00be6 | ||
|
|
ff731ea07d | ||
|
|
9a5a27ea8d | ||
|
|
94d7b62922 | ||
|
|
2df42e369e | ||
|
|
1f5bb08c25 | ||
|
|
01ff65a93e | ||
|
|
8540043c45 | ||
|
|
f57adf3637 | ||
|
|
a75fa58e1c | ||
|
|
6ccb2d36cf | ||
|
|
414e5f09c7 | ||
|
|
a04596659b | ||
|
|
149d62a648 | ||
|
|
db3905c0a3 | ||
|
|
3253e3c5aa | ||
|
|
c9321550e0 | ||
|
|
ac390d28b8 | ||
|
|
561e5f9ccb | ||
|
|
9df5c39bca | ||
|
|
9976fc6782 | ||
|
|
8966f5635d | ||
|
|
85d7afeba0 | ||
|
|
63e21c146a | ||
|
|
9bd9ad4205 | ||
|
|
dd6faf7f9b | ||
|
|
db4da7beec | ||
|
|
b4f47a7623 | ||
|
|
af8eccc02e | ||
|
|
820be19cf5 | ||
|
|
d733efc1cc | ||
|
|
985a0f3450 | ||
|
|
322515a50b | ||
|
|
13e1fa6789 | ||
|
|
c6277cce51 | ||
|
|
6530375dbf | ||
|
|
449a6e490b | ||
|
|
ebea20b4e7 | ||
|
|
f76d821bda | ||
|
|
263304b6b7 | ||
|
|
73e98c91c3 | ||
|
|
2bf8494332 | ||
|
|
df2bdf7ea3 | ||
|
|
514382e2eb | ||
|
|
cb1eadc276 | ||
|
|
1314ba219e | ||
|
|
5267dce210 | ||
|
|
97c48da49f | ||
|
|
1e74ee51ff | ||
|
|
067edcdb90 | ||
|
|
792c13cf0a | ||
|
|
3bd7d3b1c5 | ||
|
|
af847ef94e | ||
|
|
bfed3ea7d3 | ||
|
|
b44e4d7cb0 | ||
|
|
eed9966ad6 | ||
|
|
affa3c2dbf | ||
|
|
6775ac7334 | ||
|
|
2c626371b0 | ||
|
|
3cacec9163 | ||
|
|
ad513a20e9 | ||
|
|
72a8fa3bc8 | ||
|
|
0c9a616326 | ||
|
|
0e9bc2d410 | ||
|
|
0ea0c5ec4f | ||
|
|
58f0c68151 | ||
|
|
12b5a76b0d | ||
|
|
a4452864ea | ||
|
|
d57be01485 | ||
|
|
d8738f5bfd | ||
|
|
9cf7f3e46f | ||
|
|
14aba3489e | ||
|
|
13bd16af92 | ||
|
|
922a6f13a3 | ||
|
|
9c91e27933 | ||
|
|
c2ca103d30 | ||
|
|
c18f67ac6a | ||
|
|
a5b0400be7 | ||
|
|
045f14fbd0 | ||
|
|
05d8664f15 | ||
|
|
52fdecf97b | ||
|
|
9c0f84cee8 | ||
|
|
414eb22ef9 | ||
|
|
1d3ec27cb7 | ||
|
|
a0cd59bd0e | ||
|
|
a2f13eae55 | ||
|
|
2a931937a8 | ||
|
|
dfd6f8f7ab | ||
|
|
275464e7fb | ||
|
|
e2d370f0da | ||
|
|
4cb8bf1b75 | ||
|
|
5335e671be | ||
|
|
2d2d85bba4 | ||
|
|
9df6ce48c5 | ||
|
|
4d5911dbcf | ||
|
|
d57a2b908a | ||
|
|
2f228ddf31 | ||
|
|
1ca171dbe9 | ||
|
|
f6759a731a | ||
|
|
dfbda48afc | ||
|
|
260723e2cc | ||
|
|
1cbc5b49e3 | ||
|
|
f3358f5927 | ||
|
|
f946040fa9 | ||
|
|
434614506e | ||
|
|
7f2733fa1b | ||
|
|
edb7967dc7 | ||
|
|
c9901bbba5 | ||
|
|
4d930f3598 | ||
|
|
13e71acadf | ||
|
|
37ac743da7 | ||
|
|
c47a6c1510 | ||
|
|
94f9ff1ac9 | ||
|
|
97429a25ab | ||
|
|
9e89584cb4 | ||
|
|
ea80274229 | ||
|
|
42a556a082 | ||
|
|
a71a5bfeb4 | ||
|
|
6cee434b04 | ||
|
|
9d44cd79ee | ||
|
|
548440b48f | ||
|
|
8055a0bdac | ||
|
|
83c74878df | ||
|
|
d320915ad2 | ||
|
|
8e160edbd5 | ||
|
|
c0eaae200e | ||
|
|
79ae163296 | ||
|
|
7a91d7e776 | ||
|
|
6465adfe5c | ||
|
|
db14949209 | ||
|
|
ab4bc653ab | ||
|
|
ab4eacd15f | ||
|
|
7845075bd2 | ||
|
|
129638117f | ||
|
|
4438b7793b | ||
|
|
baaf6046a1 | ||
|
|
5418c2c5e4 | ||
|
|
c27038e392 | ||
|
|
51f15880d1 | ||
|
|
d324500959 | ||
|
|
3d218861e2 | ||
|
|
e721c5cf86 | ||
|
|
e3570ae45d | ||
|
|
2f27ee2232 | ||
|
|
c65bd65254 | ||
|
|
72ce06eab8 | ||
|
|
912f7b51e9 | ||
|
|
90fab0be6b | ||
|
|
c9516c4c60 | ||
|
|
6e74dd4388 | ||
|
|
d160c7e565 | ||
|
|
8ac04a3f29 | ||
|
|
b91b35b565 | ||
|
|
ac78bae7b5 | ||
|
|
926e75d721 | ||
|
|
d5a3021a7d | ||
|
|
d8a994ef24 | ||
|
|
7140dbac95 | ||
|
|
acf094fb07 | ||
|
|
7e0baf4136 | ||
|
|
a703f7d7b4 | ||
|
|
a9981d8099 | ||
|
|
5649556a33 | ||
|
|
834d92a47b | ||
|
|
e2f95c2845 | ||
|
|
5609585ec1 | ||
|
|
b7f3d94cd0 | ||
|
|
f6c98465c7 | ||
|
|
aa12135b97 | ||
|
|
f38d5e57dd | ||
|
|
341da3cea7 | ||
|
|
7162095635 | ||
|
|
0b54035d7a | ||
|
|
dbd4697001 | ||
|
|
2408df3f35 | ||
|
|
a1b28fc33c | ||
|
|
a467184e13 | ||
|
|
658bfc2704 | ||
|
|
736a46dff9 | ||
|
|
0f1b26ed1e | ||
|
|
60896c66af | ||
|
|
eb009923f4 | ||
|
|
338af89d56 | ||
|
|
2fdf8fc938 | ||
|
|
89d6b18dad | ||
|
|
b97780ba51 | ||
|
|
ccc94dd11c | ||
|
|
d5ca913b2f | ||
|
|
dab74f21b7 | ||
|
|
9eef2e706c | ||
|
|
12403bdfb0 | ||
|
|
3af1d3c581 | ||
|
|
24829f314b | ||
|
|
779b71eda4 | ||
|
|
9cf4fe043b | ||
|
|
2765b5c7cf | ||
|
|
632c27802c | ||
|
|
dc89c51f3e | ||
|
|
bb595666ac | ||
|
|
e9b9e6eb53 | ||
|
|
58e004f7da | ||
|
|
fd92d91da3 | ||
|
|
d8631b616e | ||
|
|
aa5e837c65 | ||
|
|
4f6c3e8bb2 | ||
|
|
a1d97e8f5c | ||
|
|
daa43cfb6e | ||
|
|
9adfe453d5 | ||
|
|
29cd8ac270 | ||
|
|
d710b5e791 | ||
|
|
c47866b34a | ||
|
|
15d37b7a95 | ||
|
|
15394f613f | ||
|
|
3650bd8528 | ||
|
|
de3be370f7 | ||
|
|
364874937e | ||
|
|
10e4887b2b | ||
|
|
643acdd32d | ||
|
|
8662990746 | ||
|
|
25845ea1a5 | ||
|
|
d3ca5accfd | ||
|
|
8ab5399e83 | ||
|
|
ec478b4b06 | ||
|
|
1feecd6beb | ||
|
|
a3e8c32a30 | ||
|
|
779bb890fa | ||
|
|
1fa4fe706a | ||
|
|
f4bc9263d9 | ||
|
|
600d8edaca | ||
|
|
ce3708b3ea | ||
|
|
23bc9a2911 | ||
|
|
47adc0e8f7 | ||
|
|
5258ee3740 | ||
|
|
59745c62b4 | ||
|
|
0ad5f51059 | ||
|
|
6b3e47b103 | ||
|
|
297e772c20 | ||
|
|
8bf3032b16 | ||
|
|
e40d94bb4f | ||
|
|
7ffe845c61 | ||
|
|
af8e323248 | ||
|
|
b09ddc72d2 | ||
|
|
a881f776d0 | ||
|
|
40f9d4f920 | ||
|
|
dd7608a36e | ||
|
|
662482d366 | ||
|
|
08ff1b7d4b | ||
|
|
3808638df1 | ||
|
|
ee53204e02 | ||
|
|
f15a2f9b25 | ||
|
|
3b102a71a2 | ||
|
|
133397ee0d | ||
|
|
e2b4a24cb6 | ||
|
|
d37cf09ccd | ||
|
|
2cfe6f8c60 | ||
|
|
16270ee9a4 | ||
|
|
1dfead1eef | ||
|
|
894946c319 | ||
|
|
45db167f7a | ||
|
|
10fbb1aa2f | ||
|
|
bc0eee48d5 | ||
|
|
fa5a1cb54f | ||
|
|
e797a225b6 | ||
|
|
6d5c986d4f | ||
|
|
45659d0fd2 | ||
|
|
acfc942ad7 | ||
|
|
db150f2e42 | ||
|
|
9fe9cd8b5c | ||
|
|
f8182ac521 | ||
|
|
4e96a4a62b | ||
|
|
90e9e3c89d | ||
|
|
d4583ebd4b | ||
|
|
b024de7bf5 | ||
|
|
4a0d2edc3d | ||
|
|
8e40f86d2c | ||
|
|
2bfb8bb5fd | ||
|
|
137a49e834 | ||
|
|
a3bdede2ce | ||
|
|
ddf9fa06c7 | ||
|
|
d91004ee71 | ||
|
|
739d5aa1d3 | ||
|
|
04be8c0de5 | ||
|
|
c3ff476ed6 | ||
|
|
fb1708e1af | ||
|
|
a47baa1b7a | ||
|
|
995487e822 | ||
|
|
5e97693e0e | ||
|
|
452bc385fe | ||
|
|
71bb7f6053 | ||
|
|
0255e6a703 | ||
|
|
5a27aea8e0 | ||
|
|
0e4ae27caa | ||
|
|
1c74612b3c | ||
|
|
d3ba246693 | ||
|
|
a93af59b36 | ||
|
|
0d41827f23 | ||
|
|
32efc3ec0a | ||
|
|
d33ed36fb4 | ||
|
|
9d67528c82 | ||
|
|
66d2ba1b4e | ||
|
|
84749736a8 | ||
|
|
857b340498 | ||
|
|
3e0a27157c | ||
|
|
3abad75a1b | ||
|
|
d568019306 | ||
|
|
b3e0efc0c3 | ||
|
|
caa4ca46c0 | ||
|
|
5d192c2ebf | ||
|
|
29c3e9f428 | ||
|
|
532f9fdd99 | ||
|
|
4848620594 | ||
|
|
bead46363b | ||
|
|
57c10aae33 | ||
|
|
0e0cd9100b | ||
|
|
90780a0d90 | ||
|
|
fdad234445 | ||
|
|
bba1847a8e | ||
|
|
a9d68a6884 | ||
|
|
9cf95e4e37 | ||
|
|
8c4588c4c9 | ||
|
|
e35791b2b2 | ||
|
|
5eafe2b17e | ||
|
|
1c1246fcb9 | ||
|
|
8436d69eaf | ||
|
|
20403f75fb | ||
|
|
295de51b99 | ||
|
|
6e03f61617 | ||
|
|
055c1ea02d | ||
|
|
abc5abce30 | ||
|
|
112a7cab31 | ||
|
|
ee814bf8d6 | ||
|
|
a4a23c0268 | ||
|
|
cd89d387b6 | ||
|
|
beefc53e59 | ||
|
|
1becf01cfa | ||
|
|
9fbf54ee6b | ||
|
|
12c8953381 | ||
|
|
b7b30cd85e | ||
|
|
81e5722bcc | ||
|
|
303d091ea9 | ||
|
|
c11c3b6c11 | ||
|
|
566163ab82 | ||
|
|
e2dde6eb0a | ||
|
|
b900150b1d | ||
|
|
4deb876343 | ||
|
|
0617720c0c | ||
|
|
9b8ad01bc0 | ||
|
|
96dee1c354 | ||
|
|
674c5c37be | ||
|
|
5deb726f3f | ||
|
|
1ab8a60d73 | ||
|
|
8eb0577791 | ||
|
|
85335c5f56 | ||
|
|
93f40995b3 | ||
|
|
646e90d833 | ||
|
|
d943429672 | ||
|
|
3a9fd81f59 | ||
|
|
edc414c584 | ||
|
|
a849ac0164 | ||
|
|
b31c7fe074 | ||
|
|
2665728ee7 | ||
|
|
f65dedc3be | ||
|
|
b921161666 | ||
|
|
2cc1ee3fc0 | ||
|
|
ab0ba4bbae | ||
|
|
86f841dd71 | ||
|
|
e3075865e4 | ||
|
|
3509f1f610 | ||
|
|
7ca1821725 | ||
|
|
ee5d6fb025 | ||
|
|
1372cab35a | ||
|
|
e33ddac9bf | ||
|
|
71b9537393 | ||
|
|
b33abc6280 | ||
|
|
9032bd097b | ||
|
|
38efa72146 | ||
|
|
4ae7e64e2a | ||
|
|
863ff19e1f | ||
|
|
eb14fbf95f | ||
|
|
a3eab8185d | ||
|
|
f36c82c3b3 | ||
|
|
1105a3139f | ||
|
|
fb99d50fa1 | ||
|
|
b8d0367a6c | ||
|
|
7ef9a05588 | ||
|
|
c631a4a9b9 | ||
|
|
dccfadf7b8 | ||
|
|
d1675c2701 | ||
|
|
29b07693dd | ||
|
|
f75b5f4287 | ||
|
|
40413c5c6c | ||
|
|
a72f57374d | ||
|
|
98306a5d8a | ||
|
|
cb92af4a6c | ||
|
|
ae2c6d42fd | ||
|
|
ab89be33a9 | ||
|
|
a1a4f1103c | ||
|
|
653e1506ad | ||
|
|
41fdaabcf7 | ||
|
|
8e09e03127 | ||
|
|
688fc515f8 | ||
|
|
53a63de9dc | ||
|
|
f610bfa8a2 | ||
|
|
fc4a4d38d1 | ||
|
|
939d96813c | ||
|
|
f43cc90841 | ||
|
|
a2ef9a2b64 | ||
|
|
c199703e2a | ||
|
|
db719abff2 | ||
|
|
91bab801aa | ||
|
|
17c4400b12 | ||
|
|
efa0e7b27a | ||
|
|
cd966787f3 | ||
|
|
e2f0587ca3 | ||
|
|
9620f48ed0 | ||
|
|
4db0e1d340 | ||
|
|
8a93113192 | ||
|
|
86bce4a2ae | ||
|
|
21d7b5acaf | ||
|
|
bcf6aed452 | ||
|
|
4331d1d2a0 | ||
|
|
62edc5c59a | ||
|
|
cc8c67ff29 | ||
|
|
697b0e2aba | ||
|
|
03427fb005 | ||
|
|
7655337a1f | ||
|
|
bf11ad19ea | ||
|
|
e0f0f72a36 | ||
|
|
ca35ddd078 | ||
|
|
f4309bbb05 | ||
|
|
6dc407c7d9 | ||
|
|
81ed5c4bee | ||
|
|
73474c043b | ||
|
|
0d5dc8a064 | ||
|
|
0cb7396840 | ||
|
|
4ea75dfcbe | ||
|
|
cc22c8a1ae | ||
|
|
2481fe2f56 | ||
|
|
2087156119 | ||
|
|
3870a7a3c8 | ||
|
|
19c234db39 | ||
|
|
8fe5d887ae | ||
|
|
4e6d048ba1 | ||
|
|
0df39b33eb | ||
|
|
7392b6a755 | ||
|
|
cb8134da52 | ||
|
|
590637246b | ||
|
|
053d1424b2 | ||
|
|
4993ab1a76 | ||
|
|
a62290de52 | ||
|
|
8d58e06ad8 | ||
|
|
b8fbf6559d | ||
|
|
44637f03cc | ||
|
|
7bab3d682f | ||
|
|
34f01aab5e | ||
|
|
240fe07287 | ||
|
|
93f03707a7 | ||
|
|
85af36332b | ||
|
|
13fe733037 | ||
|
|
4c896bb620 | ||
|
|
157d868254 | ||
|
|
e16042010e | ||
|
|
227dcc3cb9 | ||
|
|
e914969e4c | ||
|
|
a49af93faf | ||
|
|
76d4af891f | ||
|
|
0721095944 | ||
|
|
c62a6b7a12 | ||
|
|
cadf03db68 | ||
|
|
5ff6eedf5e | ||
|
|
53eb37d529 | ||
|
|
3bcdb3855c | ||
|
|
f00fef0cd0 | ||
|
|
2d3ecbe5b2 | ||
|
|
09c981846b | ||
|
|
037a01c4e4 | ||
|
|
1d95844d55 | ||
|
|
1c9dd11ba7 | ||
|
|
1e7e092992 | ||
|
|
33a99d587a | ||
|
|
9cd16c5b12 | ||
|
|
6673dcb038 | ||
|
|
71142929cc | ||
|
|
d7b924f17d | ||
|
|
b117befc2b | ||
|
|
7786cb76f3 | ||
|
|
eb918c2368 | ||
|
|
8ecbf0f16d | ||
|
|
b13caa23d9 | ||
|
|
fd79fad2ec | ||
|
|
19423957b1 | ||
|
|
3362b3a44f | ||
|
|
50264200f0 | ||
|
|
7436ce6403 | ||
|
|
91789930bc | ||
|
|
ea375c0dcc | ||
|
|
7509fa2c33 | ||
|
|
acdb4d8bdd | ||
|
|
95f9c85bcc | ||
|
|
79dcd7ee6e | ||
|
|
ed001d70e4 | ||
|
|
42a8c15ad0 | ||
|
|
9a27e5ccdc | ||
|
|
71123c816d | ||
|
|
168c69273f | ||
|
|
4df378b892 | ||
|
|
351dfc95a9 | ||
|
|
837155577a | ||
|
|
7e88420bc6 | ||
|
|
5911fc3512 | ||
|
|
4108c12092 | ||
|
|
e444a67d59 | ||
|
|
0cce4439ce | ||
|
|
59c965a5ec | ||
|
|
76bdbcc969 | ||
|
|
477b4d3b50 | ||
|
|
4d31eb2c0d | ||
|
|
d0b0d24f22 | ||
|
|
5d95ffe3eb | ||
|
|
98da7241a0 | ||
|
|
bc17f2f759 | ||
|
|
40f3142264 | ||
|
|
b21160a13a | ||
|
|
5b2afd8ec8 | ||
|
|
7a3eccc709 | ||
|
|
2a8d71367d | ||
|
|
3d5d61778a | ||
|
|
ddcc8d998c | ||
|
|
99e9bbef6c | ||
|
|
0e96a46020 | ||
|
|
df5ed64cca | ||
|
|
2a0bb1fa90 | ||
|
|
c6083c335e | ||
|
|
2e9c4ddedb | ||
|
|
fa8bf0f1d7 | ||
|
|
9a2e43bff2 | ||
|
|
bd5dc626e8 | ||
|
|
626dc1f0bd | ||
|
|
1b0ef0ec0b | ||
|
|
e4a092fb5a | ||
|
|
b886fb4bf0 | ||
|
|
fa5e372f75 | ||
|
|
356f1438a6 | ||
|
|
a19aaa439d | ||
|
|
0d469f261e | ||
|
|
c3440c4dd3 | ||
|
|
718d3ae258 | ||
|
|
b8d48bdb62 | ||
|
|
15d62bba82 | ||
|
|
eb6c634475 | ||
|
|
eec06fb3df | ||
|
|
df05134494 | ||
|
|
9bd3ebe207 | ||
|
|
a576224d0e | ||
|
|
989f30eb41 | ||
|
|
06d293a84e | ||
|
|
120cd4e471 | ||
|
|
1cbd4c01fb | ||
|
|
2528c482e9 | ||
|
|
b1a53f6d8e | ||
|
|
fde9c69679 | ||
|
|
3df5dcc1dc | ||
|
|
14080dd61d | ||
|
|
da2585c11e | ||
|
|
dd8a06a397 | ||
|
|
4e0c697aaf | ||
|
|
3ffbb54337 | ||
|
|
67cfb6735b | ||
|
|
b7508b06fa | ||
|
|
2a0db47935 | ||
|
|
dce2a9e7e1 | ||
|
|
abb02889f2 | ||
|
|
e2ca53029e | ||
|
|
b5f6206a65 | ||
|
|
3de9c11ea7 | ||
|
|
ae54d878c0 | ||
|
|
89244b74c6 | ||
|
|
ca8ce793d1 | ||
|
|
978dc00305 | ||
|
|
bf26808fb3 | ||
|
|
404867f206 | ||
|
|
f0ee33267c | ||
|
|
f3eaa4c592 | ||
|
|
f41360d864 | ||
|
|
e82ee40e9e | ||
|
|
1ee7c33e93 | ||
|
|
e538ff2770 | ||
|
|
76d4b9288b | ||
|
|
05ba8622f0 | ||
|
|
4795fa01d8 | ||
|
|
942fd6be53 | ||
|
|
56dd430a10 | ||
|
|
e0bae9547a | ||
|
|
bfe6027266 | ||
|
|
4d9499c2d3 | ||
|
|
98e989d52c | ||
|
|
5742f9fe69 | ||
|
|
1802d52362 | ||
|
|
cab2911f23 | ||
|
|
81133d45a1 | ||
|
|
a51acf1751 | ||
|
|
edbb67cb3f | ||
|
|
c5e249c0be | ||
|
|
37a372f6f5 | ||
|
|
f122d0856e | ||
|
|
4a6016f5af | ||
|
|
cc8f5add6e | ||
|
|
ec2423ad7c | ||
|
|
c4bab163cb | ||
|
|
0068b8106b | ||
|
|
2580e7b57e | ||
|
|
3d3498bda1 | ||
|
|
29375059e1 | ||
|
|
85449b2f11 | ||
|
|
b83cb36049 | ||
|
|
73d9eebf01 | ||
|
|
b73241ceb1 | ||
|
|
e350d74c8a | ||
|
|
149e540322 | ||
|
|
314664892c | ||
|
|
a9a386a1e5 | ||
|
|
3eae4ecde7 | ||
|
|
ec98deeb8c | ||
|
|
61fdd8c571 | ||
|
|
4813665d0a | ||
|
|
640dce12a8 | ||
|
|
99b958db43 | ||
|
|
22b0dfbb35 | ||
|
|
4a64ae4abf | ||
|
|
926e91820a | ||
|
|
91ae2ad28b | ||
|
|
db30ea03d8 | ||
|
|
0be8b1b1a1 | ||
|
|
d45302a6ba | ||
|
|
834d38a8fb | ||
|
|
5572884c6b | ||
|
|
3460ec1039 | ||
|
|
53bf23d965 | ||
|
|
573305f3d3 | ||
|
|
7ccce4d110 | ||
|
|
9ed60d96a9 | ||
|
|
b6d2b96259 | ||
|
|
e5fe367b82 | ||
|
|
19e8ce0354 | ||
|
|
f907a5c98b | ||
|
|
da607c611d | ||
|
|
3d54f6c0a4 | ||
|
|
2093586241 | ||
|
|
117afe7620 | ||
|
|
d3a5ff7b6b | ||
|
|
dcb391d341 | ||
|
|
830d000667 | ||
|
|
5a14c3cf98 | ||
|
|
e57b2dffa4 | ||
|
|
ca96e04e5f | ||
|
|
9950f5a5bd | ||
|
|
1d7a1b6034 | ||
|
|
a59b1fcc21 | ||
|
|
c5a9be9115 | ||
|
|
f86afb04a2 | ||
|
|
5d1f5f32d0 | ||
|
|
e42fcb033d | ||
|
|
392f3ee210 | ||
|
|
c50a3503e6 | ||
|
|
aaa3f1b2b9 | ||
|
|
2b10fdc4dc | ||
|
|
2f28a0310b | ||
|
|
253513cedd | ||
|
|
eb30cbab81 | ||
|
|
144663a3cf | ||
|
|
ba92f4687e | ||
|
|
968edb3e44 | ||
|
|
3ca544912f | ||
|
|
7f9598141b | ||
|
|
56c66ee486 | ||
|
|
21ad4bf0fe | ||
|
|
0128036514 | ||
|
|
ec8d41765d | ||
|
|
ffbeda077c | ||
|
|
880849a283 | ||
|
|
b2fb7e3fd2 | ||
|
|
737da1a374 | ||
|
|
f63a468dfc | ||
|
|
efaf60ba5a | ||
|
|
e6b2a01e5d | ||
|
|
52c8f69163 | ||
|
|
b80e848d02 | ||
|
|
f12832c61e | ||
|
|
dcc740fd26 | ||
|
|
8966750fd4 | ||
|
|
3623b0927e | ||
|
|
d37da1f392 | ||
|
|
cefc50b278 | ||
|
|
b4877b1e06 | ||
|
|
eea2e05da6 | ||
|
|
2b1e955f91 | ||
|
|
63cdee84d1 | ||
|
|
6a6a7512c2 | ||
|
|
6b30b20765 | ||
|
|
126228d146 | ||
|
|
74dfe439c2 | ||
|
|
1d4a5b1825 | ||
|
|
987dcc5372 | ||
|
|
9b6c835715 | ||
|
|
902b578465 | ||
|
|
3d14e73fd8 | ||
|
|
9bcc3c1ea3 | ||
|
|
6a66e5fc98 | ||
|
|
c0b0ce7b1a | ||
|
|
dc0c0dc06b | ||
|
|
2158e6fc43 | ||
|
|
ee686f6231 | ||
|
|
481be9b5c9 | ||
|
|
9330c943cd | ||
|
|
915bf1d2e3 | ||
|
|
f455125d4d | ||
|
|
df339ad8b0 | ||
|
|
2c653141a8 | ||
|
|
fc56f42dc3 | ||
|
|
0bd4d15e47 | ||
|
|
e04c97b9fa | ||
|
|
f04d773f4f | ||
|
|
4325b01a58 | ||
|
|
052519a7d7 | ||
|
|
e347736c9e | ||
|
|
56006da34b | ||
|
|
efea642d6c | ||
|
|
81d7359fdd | ||
|
|
9a0902523b | ||
|
|
d2808e38fe | ||
|
|
7a9777ae36 | ||
|
|
62533560ce | ||
|
|
dc7e74ebb1 | ||
|
|
9a27e1b90c | ||
|
|
ff5f14431e | ||
|
|
ab9411be2a | ||
|
|
114e6790f8 | ||
|
|
ec5f881384 | ||
|
|
9ab96172fc | ||
|
|
e06558e208 | ||
|
|
54fd4cc5fb | ||
|
|
3deddabfd8 | ||
|
|
0cbf56855a | ||
|
|
917d334ebd | ||
|
|
bb1fbe4e70 | ||
|
|
cceb3364bb | ||
|
|
d370effca5 | ||
|
|
29ed7872f8 | ||
|
|
5dc3dd1704 | ||
|
|
134d8e7681 | ||
|
|
c9b65f0fdc | ||
|
|
951037c0ae | ||
|
|
7046df6028 | ||
|
|
1db3ae6601 | ||
|
|
612d0d6d25 | ||
|
|
18de67380c | ||
|
|
1a901433e2 | ||
|
|
e030109b5a | ||
|
|
35d49d3b34 | ||
|
|
ca5678da32 | ||
|
|
4d3138cd10 | ||
|
|
942284648e | ||
|
|
4f03b81ec7 | ||
|
|
b4970b3cc3 | ||
|
|
85c58eba90 | ||
|
|
84a43b38cf | ||
|
|
7c80eba77f | ||
|
|
9c12ed3b6e | ||
|
|
7b1c10ea7e | ||
|
|
679af4ddea | ||
|
|
f8ae161c74 | ||
|
|
1d57f0d64f | ||
|
|
1559bd58e7 | ||
|
|
6a664e88c7 | ||
|
|
0f438ef0b3 | ||
|
|
a6c7716742 | ||
|
|
1c3754bcec | ||
|
|
ee645af107 | ||
|
|
3e7695ae91 | ||
|
|
2268d28189 | ||
|
|
eee6e4206a | ||
|
|
1bfebdcdf6 | ||
|
|
588a0db218 | ||
|
|
d1e28ac013 | ||
|
|
523dc8b613 | ||
|
|
923c45d721 | ||
|
|
10427b2178 | ||
|
|
020fb43b77 | ||
|
|
675cd997d8 | ||
|
|
908f2924ce | ||
|
|
f28173bf50 | ||
|
|
7835c2212c | ||
|
|
3b62a0fe0e | ||
|
|
2db785b3ed | ||
|
|
647688bd06 | ||
|
|
0d4498429c | ||
|
|
3ec650b0ef | ||
|
|
18c841050b | ||
|
|
b55499d039 | ||
|
|
58436b5ea5 | ||
|
|
d85a1d478e | ||
|
|
676d774d88 | ||
|
|
e7aabf70dc | ||
|
|
6f929dcd9e | ||
|
|
54ca0b2f09 | ||
|
|
2bd64621fc | ||
|
|
05b419b219 | ||
|
|
b163d79a2e | ||
|
|
f255b1e86d | ||
|
|
9372eedf2e | ||
|
|
14a8a46bec | ||
|
|
7679aa1a21 | ||
|
|
9a8aeef478 | ||
|
|
603c7389b8 | ||
|
|
00eb2b221f | ||
|
|
1b5e1bebc2 | ||
|
|
3a81fdf092 | ||
|
|
6f0a41b8b2 | ||
|
|
25ec20d525 | ||
|
|
8e262f3ec4 | ||
|
|
2cee0f84c0 | ||
|
|
94b2816446 | ||
|
|
8411b50f5d | ||
|
|
8a87bee434 | ||
|
|
1dfa693a5c | ||
|
|
d5b92b61d7 | ||
|
|
a374751eb8 | ||
|
|
0af035c37e | ||
|
|
bc82157216 | ||
|
|
8eb4c3121a | ||
|
|
75aab86a8d | ||
|
|
92535c9df0 | ||
|
|
50058b3c6d | ||
|
|
b0226a1d05 | ||
|
|
67ced4aaca | ||
|
|
04806b614e | ||
|
|
cb100c7781 | ||
|
|
5cad124704 | ||
|
|
fad31ca302 | ||
|
|
6b8bef3cf6 | ||
|
|
940898a3ff | ||
|
|
75fe134072 | ||
|
|
16feb5b655 | ||
|
|
659bd29bc5 | ||
|
|
91c9069c4d | ||
|
|
b992deae92 | ||
|
|
c7eaf96b37 | ||
|
|
4323a89c03 | ||
|
|
b90b0c1191 | ||
|
|
f1aa4c0524 | ||
|
|
102b675f96 | ||
|
|
073da3c49d | ||
|
|
0fe6fe663e | ||
|
|
7d72c8333e | ||
|
|
706b0f72e2 | ||
|
|
1f4beb530c | ||
|
|
89bf56a6ac | ||
|
|
31b375782b | ||
|
|
0252629956 | ||
|
|
4f0e31e96d | ||
|
|
f5689ee3a5 | ||
|
|
a020cf803b | ||
|
|
630ebbe6c2 | ||
|
|
b9f5def5dc | ||
|
|
87c3c8172a | ||
|
|
022820103d | ||
|
|
d5fab7f1b9 | ||
|
|
c3ba5590c9 | ||
|
|
379629d28a | ||
|
|
8b92f9cca6 | ||
|
|
78a4e71245 | ||
|
|
63e6e31271 | ||
|
|
2be5837cb0 | ||
|
|
71d8ff247d | ||
|
|
0fbb8c8826 | ||
|
|
e0aab4a7f6 | ||
|
|
db7ac8bc1d | ||
|
|
c6ce6bd4c2 | ||
|
|
b5fdf0947b | ||
|
|
35a65736fa | ||
|
|
04af4b24fd | ||
|
|
a301c7ed26 | ||
|
|
f7c7837fc8 | ||
|
|
02289479ef | ||
|
|
eac91a74d8 | ||
|
|
f17b746a01 | ||
|
|
d3f67d341f | ||
|
|
0f4f81f1fb | ||
|
|
d0c17adfea | ||
|
|
87c05c386f | ||
|
|
8fc8848ce2 | ||
|
|
1f1abb17e2 | ||
|
|
fc7959d3bc | ||
|
|
4dc6285715 | ||
|
|
9825760817 | ||
|
|
9573f9afe9 | ||
|
|
6599869f28 | ||
|
|
932dbccb67 | ||
|
|
5edc2f6d6c | ||
|
|
4dd731cb53 | ||
|
|
a749e6adcf | ||
|
|
b854d3ba40 | ||
|
|
6a6e43f964 | ||
|
|
ae0fadeb0e | ||
|
|
2717ada14c | ||
|
|
e1c04f043b | ||
|
|
a42514613f | ||
|
|
9acf02ad7f | ||
|
|
5c6df9f31b | ||
|
|
533c6a8e08 | ||
|
|
bfed40eec4 | ||
|
|
575300cd57 | ||
|
|
9cba6ff84b | ||
|
|
570ddefc32 | ||
|
|
5676fa5b5d | ||
|
|
fc427432ed | ||
|
|
d7f390a3b0 | ||
|
|
2671c86ba7 | ||
|
|
f1c2276c8d | ||
|
|
2020bafee1 | ||
|
|
bc2f546023 | ||
|
|
ef6d12844c | ||
|
|
a443fcf33a | ||
|
|
b5e6af9587 | ||
|
|
01dc8f8a4f | ||
|
|
aff49b1c9e | ||
|
|
b1941f1da1 | ||
|
|
19c3745488 | ||
|
|
ea6c6bc20a | ||
|
|
db00aa7653 | ||
|
|
215920772a | ||
|
|
6fe868a4d5 | ||
|
|
02d3b66265 | ||
|
|
fe8495e4a5 | ||
|
|
280fde9b7c | ||
|
|
79fb24a397 | ||
|
|
4465c58f4b | ||
|
|
5981f1edcd | ||
|
|
ad5e0b833c | ||
|
|
e5310cdbc1 | ||
|
|
e34d0063c3 | ||
|
|
3e7d8db7a2 | ||
|
|
655b69cb1f | ||
|
|
fcb1f4ec07 | ||
|
|
3cad8d9492 | ||
|
|
e63e0b3105 | ||
|
|
d86c785410 | ||
|
|
b05c7b3faa | ||
|
|
5c39d3fa7d | ||
|
|
c60d8bc069 | ||
|
|
87d64acc9f | ||
|
|
c8aa9c6cb1 | ||
|
|
ecd59deb27 | ||
|
|
e0a099ec11 | ||
|
|
5af872955b | ||
|
|
e00268895c | ||
|
|
4a05c4a759 | ||
|
|
a205acf829 | ||
|
|
d5ad4e1141 | ||
|
|
2dde2a8ad3 | ||
|
|
6dfee30bf0 | ||
|
|
21e13cb51e | ||
|
|
986447335d | ||
|
|
84c727ae66 | ||
|
|
ee1256cf74 | ||
|
|
dfc16d0879 | ||
|
|
a8fd615adc | ||
|
|
c98dad1cf3 | ||
|
|
0d5e57e4ae | ||
|
|
1c35380c2c | ||
|
|
bb7ddb45ff | ||
|
|
939d2054d8 | ||
|
|
3544dafb64 | ||
|
|
fefce965f9 | ||
|
|
14a1101139 | ||
|
|
5a2093b053 | ||
|
|
2f23cf98ea | ||
|
|
4f8b209956 | ||
|
|
043ded0896 | ||
|
|
e07675b480 | ||
|
|
4c30caad1c | ||
|
|
216f0477b5 | ||
|
|
befed9c20c | ||
|
|
e787e73e2f | ||
|
|
f8c09dc1ff | ||
|
|
42a38dfca3 | ||
|
|
91220a2501 | ||
|
|
700ac8dea7 | ||
|
|
69b1d65c9b | ||
|
|
7acbcf9ddd | ||
|
|
2340bb1ed2 | ||
|
|
d9c5b3bcee | ||
|
|
86d3c5cbb3 | ||
|
|
acf428863c | ||
|
|
3fb1b6a608 | ||
|
|
562e47f31c | ||
|
|
9d36fc6986 | ||
|
|
923873db85 | ||
|
|
373731f5e8 | ||
|
|
e75fd2f783 | ||
|
|
a517cfdf7b | ||
|
|
2729eb998c | ||
|
|
b003b18788 | ||
|
|
6a1907d994 | ||
|
|
e303d74ab6 | ||
|
|
e4ecbcdf4a | ||
|
|
f5c7f22cc8 | ||
|
|
2bc3e83e1c | ||
|
|
d600530c20 | ||
|
|
2d1bb0cf49 | ||
|
|
5f1183cecf | ||
|
|
b0bf4cc1cb | ||
|
|
98108e379d | ||
|
|
83e747bfda | ||
|
|
e5ed5904c6 | ||
|
|
1fa5b6711b | ||
|
|
8e0a69f86a | ||
|
|
e2d6b0116e | ||
|
|
cd37fccdfb | ||
|
|
6969c20afd | ||
|
|
f4e54aafa5 | ||
|
|
d185f601d3 | ||
|
|
aff773f1b9 | ||
|
|
10de16beb0 | ||
|
|
ed5a61153f | ||
|
|
47df562ced | ||
|
|
c7ac237b57 | ||
|
|
be89802bd8 | ||
|
|
247017d9ff | ||
|
|
362d64df04 | ||
|
|
373ef5d15e | ||
|
|
121a81a2c5 | ||
|
|
0617448282 | ||
|
|
fa728d8dff | ||
|
|
26ac016b9f | ||
|
|
cbd6276200 | ||
|
|
52ec80fa18 | ||
|
|
c8d92fad30 | ||
|
|
c3061c61a7 | ||
|
|
e4d4662074 | ||
|
|
9899ea71e8 | ||
|
|
b954a22ce2 | ||
|
|
964d0262ff | ||
|
|
b75d0378cb | ||
|
|
357c002c03 | ||
|
|
f432f1f41c | ||
|
|
6f6f38e7c3 | ||
|
|
49e120a67c | ||
|
|
af324a6165 | ||
|
|
7b0ae27549 | ||
|
|
31e7b0f588 | ||
|
|
03ea29eb36 | ||
|
|
32c12d553c | ||
|
|
cc83043edc | ||
|
|
3d9b98fae4 | ||
|
|
36405d0faa | ||
|
|
5020576e80 | ||
|
|
56eb252098 | ||
|
|
fbb3486c95 | ||
|
|
7b92dc3d9d | ||
|
|
645d4d0c5b | ||
|
|
232c22208c | ||
|
|
0806725ca5 | ||
|
|
d504ee0417 | ||
|
|
ae1650824c | ||
|
|
c38754d432 | ||
|
|
85f34ba538 | ||
|
|
00767a0522 | ||
|
|
4dbc322859 | ||
|
|
edc99bc8a4 | ||
|
|
80701d45bb | ||
|
|
aa67de910a | ||
|
|
24658fcbdd | ||
|
|
705224353b | ||
|
|
079a2d68db | ||
|
|
119dec51f2 | ||
|
|
46dce2d653 | ||
|
|
d48cde6ec8 | ||
|
|
3b0c2cb480 | ||
|
|
d464d3b0c3 | ||
|
|
b80aef0fa6 | ||
|
|
0f07a5cb84 | ||
|
|
7cb4aa8d82 | ||
|
|
aa9c36514f | ||
|
|
2d1db4bf05 |
@@ -1,6 +1,6 @@
|
||||
[run]
|
||||
init_cmds = [
|
||||
#["grep", "-rn", "FIXME", "."],
|
||||
["make", "build-dev"],
|
||||
["./gogs", "web"]
|
||||
]
|
||||
watch_all = true
|
||||
@@ -11,10 +11,9 @@ watch_dirs = [
|
||||
"$WORKDIR/routers"
|
||||
]
|
||||
watch_exts = [".go"]
|
||||
ignore_files = [".+_test.go"]
|
||||
build_delay = 1500
|
||||
cmds = [
|
||||
#["go-bindata", "-o=modules/bindata/bindata.go", "-ignore=\\.DS_Store|README", "-pkg=bindata", "conf/..."],
|
||||
["go", "install", "-tags", "sqlite"],# redis memcache cert pam
|
||||
["go", "build", "-tags", "sqlite"],
|
||||
["make", "build-dev"], # TAGS=sqlite cert pam tidb
|
||||
["./gogs", "web"]
|
||||
]
|
||||
7
.codebeatignore
Normal file
7
.codebeatignore
Normal file
@@ -0,0 +1,7 @@
|
||||
conf/**
|
||||
docker/**
|
||||
modules/bindata/**
|
||||
packager/**
|
||||
public/**
|
||||
scripts/**
|
||||
templates/**
|
||||
7
.codebeatsettings
Normal file
7
.codebeatsettings
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"GOLANG": {
|
||||
"TOTAL_LOC": [500, 999, 1999, 9999],
|
||||
"TOO_MANY_FUNCTIONS": [50, 99, 199, 999],
|
||||
"TOO_MANY_IVARS": [20, 50, 70, 99]
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,19 @@
|
||||
.git/*
|
||||
conf/*
|
||||
packager/*
|
||||
scripts/*
|
||||
.git
|
||||
.git/**
|
||||
packager
|
||||
packager/**
|
||||
scripts
|
||||
scripts/**
|
||||
.github/
|
||||
.github/**
|
||||
config.codekit
|
||||
.dockerignore
|
||||
*.yml
|
||||
*.md
|
||||
.bra.toml
|
||||
.editorconfig
|
||||
.gitignore
|
||||
.gopmfile
|
||||
config.codekit
|
||||
LICENSE
|
||||
Dockerfile*
|
||||
vendor
|
||||
vendor/**
|
||||
gogs
|
||||
|
||||
@@ -5,8 +5,21 @@ root = true
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
indent_style = tab
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.yml]
|
||||
[*.go]
|
||||
indent_style = tab
|
||||
indent_size = 4
|
||||
|
||||
[*.tmpl]
|
||||
indent_style = tab
|
||||
indent_size = 2
|
||||
|
||||
[*.{less,yml}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
[*.js]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
|
||||
11
.gitattributes
vendored
Normal file
11
.gitattributes
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
public/conf/gitignore/* linguist-vendored
|
||||
public/conf/license/* linguist-vendored
|
||||
public/assets/* linguist-vendored
|
||||
public/plugins/* linguist-vendored
|
||||
public/plugins/* linguist-vendored
|
||||
public/css/themes/* linguist-vendored
|
||||
public/css/github.min.css linguist-vendored
|
||||
public/css/semantic-2.2.9.min.css linguist-vendored
|
||||
public/js/libs/* linguist-vendored
|
||||
public/js/jquery-1.11.3.min.js linguist-vendored
|
||||
public/js/semantic-2.2.9.min.js linguist-vendored
|
||||
14
CONTRIBUTING.md → .github/CONTRIBUTING.md
vendored
14
CONTRIBUTING.md → .github/CONTRIBUTING.md
vendored
@@ -42,21 +42,11 @@ There is no standard form of making a feature request. Just try to describe the
|
||||
|
||||
### Pull Request
|
||||
|
||||
Pull requests are always welcome, but note that **ALL PULL REQUESTS MUST APPLY TO THE `DEV` BRANCH**.
|
||||
|
||||
We are always thrilled to receive pull requests, and do our best to process them as fast as possible. Not sure if that typo is worth a pull request? Do it! We will appreciate it.
|
||||
|
||||
If your pull request is not accepted on the first try, don't be discouraged! If there's a problem with the implementation, hopefully you received feedback on what to improve.
|
||||
|
||||
We're trying very hard to keep Gogs lean and focused. We don't want it to do everything for everybody. This means that we might decide against incorporating a new feature. We believe you do like to discuss with us first in [Gitter](https://gitter.im/gogits/gogs).
|
||||
Please read detailed information on [Wiki](https://github.com/gogits/gogs/wiki/Contributing-Code).
|
||||
|
||||
### Ask For Help
|
||||
|
||||
Before opening an issue, please make sure your problem isn't already addressed on the [Troubleshooting](http://gogs.io/docs/intro/troubleshooting.md) and [FAQs](http://gogs.io/docs/intro/faqs.html) pages.
|
||||
|
||||
## Things To Notice
|
||||
|
||||
Please take a moment to check that an issue on [GitHub](https://github.com/gogits/gogs/issues) or card on [Trello](https://trello.com/b/uxAoeLUl/gogs-go-git-service) doesn't already exist documenting your bug report or improvement proposal. If it does, it never hurts to add a quick "+1" or "I have this problem too". This will help prioritize the most common problems and requests.
|
||||
Before opening an issue, please make sure your problem isn't already addressed on the [Troubleshooting](https://gogs.io/docs/intro/troubleshooting.html) and [FAQs](https://gogs.io/docs/intro/faqs.html) pages.
|
||||
|
||||
## Code of conduct
|
||||
|
||||
25
.github/ISSUE_TEMPLATE.md
vendored
Normal file
25
.github/ISSUE_TEMPLATE.md
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
The issue will be closed without any reasons if it does not satisfy any of following requirements:
|
||||
|
||||
1. Please speak English, we have forum in [Chinese](https://discuss.gogs.io/c/getting-help/getting-help-chinese).
|
||||
2. Please post questions or config/deploy problems on our forum: https://discuss.gogs.io, here are bugs and feature requests only.
|
||||
3. Please take a moment to search that an issue doesn't already exist.
|
||||
4. Please give all relevant information below for bug reports; incomplete details considered invalid report.
|
||||
|
||||
**You MUST delete above content including this line before posting; too lazy to take this action considered invalid report.**
|
||||
|
||||
- Gogs version (or commit ref):
|
||||
- Git version:
|
||||
- Operating system:
|
||||
- Database (use `[x]`):
|
||||
- [ ] PostgreSQL
|
||||
- [ ] MySQL
|
||||
- [ ] SQLite
|
||||
- Can you reproduce the bug at https://try.gogs.io:
|
||||
- [ ] Yes (provide example URL)
|
||||
- [ ] No
|
||||
- [ ] Not relevant
|
||||
- Log gist (usually found in `log/gogs.log`):
|
||||
|
||||
## Description
|
||||
|
||||
...
|
||||
9
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
9
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
The pull request will be closed without any reasons if it does not satisfy any of following requirements:
|
||||
|
||||
1. Please make sure you are targeting the `develop` branch.
|
||||
2. Please read contributing guidelines:
|
||||
https://github.com/gogits/gogs/wiki/Contributing-Code
|
||||
3. Please describe what your pull request does and which issue you're targeting
|
||||
4. ... if it is not related to any particular issues, explain why we should not reject your pull request.
|
||||
|
||||
**You MUST delete above content including this line before posting; too lazy to take this action considered invalid pull request.**
|
||||
24
.gitignore
vendored
24
.gitignore
vendored
@@ -8,30 +8,12 @@ data/
|
||||
.idea/
|
||||
*.iml
|
||||
public/img/avatar/
|
||||
files/
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
_obj
|
||||
_test
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
_testmain.go
|
||||
*.exe
|
||||
*.exe~
|
||||
/gogs
|
||||
profile/
|
||||
__pycache__
|
||||
*.pem
|
||||
output*
|
||||
.brackets.json
|
||||
docker/fig.yml
|
||||
docker/docker/Dockerfile
|
||||
docker/docker/init_gogs.sh
|
||||
gogs.sublime-project
|
||||
gogs.sublime-workspace
|
||||
*.sublime-project
|
||||
*.sublime-workspace
|
||||
/release
|
||||
|
||||
91
.gopmfile
91
.gopmfile
@@ -2,40 +2,63 @@
|
||||
path = github.com/gogits/gogs
|
||||
|
||||
[deps]
|
||||
github.com/bradfitz/gomemcache = commit:72a68649ba
|
||||
github.com/Unknwon/cae = commit:2e70a1351b
|
||||
github.com/Unknwon/com = commit:47d7d2b81a
|
||||
github.com/Unknwon/i18n = commit:7457d88830
|
||||
github.com/Unknwon/macaron = commit:635c89ac74
|
||||
github.com/Unknwon/paginater = commit:cab2d086fa
|
||||
github.com/codegangsta/cli = commit:142e6cd241
|
||||
github.com/go-sql-driver/mysql = commit:3dd7008ac1
|
||||
github.com/go-xorm/core = commit:515edd92c1
|
||||
github.com/go-xorm/xorm = commit:ce23797899
|
||||
github.com/gogits/chardet = commit:2404f77725
|
||||
github.com/gogits/go-gogs-client = commit:519eee0af0
|
||||
github.com/lib/pq = commit:b269bd035a
|
||||
github.com/macaron-contrib/binding = commit:de6ed78668
|
||||
github.com/macaron-contrib/cache = commit:a139ea1eee
|
||||
github.com/macaron-contrib/captcha = commit:9a0a0b1468
|
||||
github.com/macaron-contrib/csrf = commit:98ddf5a710
|
||||
github.com/macaron-contrib/i18n = commit:da2b19e90b
|
||||
github.com/macaron-contrib/oauth2 = commit:1adb5ce072
|
||||
github.com/macaron-contrib/session = commit:e48134e803
|
||||
github.com/macaron-contrib/toolbox = commit:acbfe36e16
|
||||
github.com/mattn/go-sqlite3 = commit:897b8800a7
|
||||
github.com/mcuadros/go-version = commit:d52711f8d6
|
||||
github.com/microcosm-cc/bluemonday = commit:2b7763a06c
|
||||
github.com/mssola/user_agent = commit:a163d6a569
|
||||
github.com/msteinert/pam = commit:6534f23b39
|
||||
github.com/nfnt/resize = commit:dc93e1b98c
|
||||
github.com/russross/blackfriday = commit:8cec3a854e
|
||||
github.com/shurcooL/sanitized_anchor_name = commit:244f5ac324
|
||||
golang.org/x/net =
|
||||
golang.org/x/text =
|
||||
gopkg.in/ini.v1 = commit:463307112d
|
||||
gopkg.in/redis.v2 = commit:e617904962
|
||||
github.com/bradfitz/gomemcache = commit:2fafb84
|
||||
github.com/urfave/cli = commit:347a988
|
||||
github.com/go-macaron/binding = commit:4892016
|
||||
github.com/go-macaron/cache = commit:5617353
|
||||
github.com/go-macaron/captcha = commit:8aa5919
|
||||
github.com/go-macaron/csrf = commit:6a9a7df
|
||||
github.com/go-macaron/gzip = commit:cad1c65
|
||||
github.com/go-macaron/i18n = commit:ef57533
|
||||
github.com/go-macaron/inject = commit:d8a0b86
|
||||
github.com/go-macaron/session = commit:66031fc
|
||||
github.com/go-macaron/toolbox = commit:82b5115
|
||||
github.com/go-sql-driver/mysql = commit:2e00b5c
|
||||
github.com/go-xorm/builder = commit:9c35786
|
||||
github.com/go-xorm/core = commit:7daacb2
|
||||
github.com/go-xorm/xorm = commit:19f6dfc
|
||||
github.com/gogits/chardet = commit:2404f77
|
||||
github.com/gogits/cron = commit:2fc07a4
|
||||
github.com/gogits/git-module = commit:1b9552b
|
||||
github.com/gogits/go-gogs-client = commit:264a3d5
|
||||
github.com/gogits/go-libravatar = commit:cd1abbd
|
||||
github.com/issue9/identicon = commit:d36b545
|
||||
github.com/jaytaylor/html2text = commit:d16d412
|
||||
github.com/kardianos/minwinsvc = commit:cad6b2b
|
||||
github.com/klauspost/compress = commit:461e8fd
|
||||
github.com/klauspost/cpuid = commit:09cded8
|
||||
github.com/klauspost/crc32 = commit:cb6bfca
|
||||
github.com/lib/pq = commit:67c3f2a
|
||||
github.com/mattn/go-colorable = commit:d228849
|
||||
github.com/mattn/go-isatty = commit:30a891c
|
||||
github.com/mattn/go-sqlite3 = commit:ce9149a
|
||||
github.com/mcuadros/go-version = commit:257f7b9
|
||||
github.com/microcosm-cc/bluemonday = commit:e797637
|
||||
github.com/msteinert/pam = commit:02ccfbf
|
||||
github.com/nfnt/resize = commit:891127d
|
||||
github.com/russross/blackfriday = commit:5f33e7b
|
||||
github.com/satori/go.uuid = commit:b061729
|
||||
github.com/sergi/go-diff = commit:24e2351
|
||||
github.com/shurcooL/sanitized_anchor_name = commit:1dba4b3
|
||||
github.com/Unknwon/cae = commit:c6aac99
|
||||
github.com/Unknwon/com = commit:28b053d
|
||||
github.com/Unknwon/i18n = commit:e0eb0ce
|
||||
github.com/Unknwon/paginater = commit:701c23f
|
||||
github.com/fatih/color = commit:42c364b
|
||||
golang.org/x/crypto = commit:dc137be
|
||||
golang.org/x/net = commit:f249948
|
||||
golang.org/x/sys = commit:d75a526
|
||||
golang.org/x/text = commit:ece019d
|
||||
gopkg.in/alexcesaro/quotedprintable.v3 = commit:2caba25
|
||||
gopkg.in/asn1-ber.v1 = commit:4e86f43
|
||||
gopkg.in/bufio.v1 = commit:567b2bf
|
||||
gopkg.in/clog.v1 = commit:bf4bf4a
|
||||
gopkg.in/editorconfig/editorconfig-core-go.v1 = commit:a872f05
|
||||
gopkg.in/gomail.v2 = commit:81ebce5
|
||||
gopkg.in/ini.v1 = commit:e3c2d47
|
||||
gopkg.in/ldap.v2 = commit:8168ee0
|
||||
gopkg.in/macaron.v1 = commit:8be5635
|
||||
gopkg.in/redis.v2 = commit:e617904
|
||||
|
||||
[res]
|
||||
include = public|scripts|templates
|
||||
|
||||
|
||||
2
.mailmap
Normal file
2
.mailmap
Normal file
@@ -0,0 +1,2 @@
|
||||
Unknwon <u@gogs.io> <joe2010xtmf@163.com>
|
||||
Unknwon <u@gogs.io> 无闻 <u@gogs.io>
|
||||
17
.pkgr.yml
17
.pkgr.yml
@@ -7,13 +7,20 @@ targets:
|
||||
- git
|
||||
debian-8:
|
||||
<<: *debian
|
||||
ubuntu-14.04:
|
||||
<<: *debian
|
||||
ubuntu-12.04:
|
||||
<<: *debian
|
||||
ubuntu-14.04:
|
||||
<<: *debian
|
||||
ubuntu-16.04:
|
||||
<<: *debian
|
||||
build_dependencies:
|
||||
- bzr
|
||||
- mercurial
|
||||
centos-6: &el
|
||||
build_dependencies:
|
||||
- pam-devel
|
||||
# required for go buildpack
|
||||
- perl-Digest-SHA
|
||||
dependencies:
|
||||
- pam
|
||||
- git
|
||||
@@ -21,7 +28,7 @@ targets:
|
||||
<<: *el
|
||||
before:
|
||||
- mv packager/Procfile .
|
||||
- mv packager/.godir .
|
||||
after:
|
||||
- mv bin/main gogs
|
||||
after_install: ./packager/debian/postinst
|
||||
- mv bin/gogs gogs
|
||||
after_install: ./packager/hooks/postinst
|
||||
buildpack: https://github.com/heroku/heroku-buildpack-go.git#v62
|
||||
|
||||
27
.travis.yml
27
.travis.yml
@@ -1,26 +1,17 @@
|
||||
language: go
|
||||
|
||||
go:
|
||||
- 1.2
|
||||
- 1.3
|
||||
- 1.4
|
||||
- 1.5
|
||||
- tip
|
||||
- 1.5.x
|
||||
- 1.6.x
|
||||
- 1.7.x
|
||||
- master
|
||||
|
||||
before_install:
|
||||
- sudo apt-get update -qq
|
||||
- sudo apt-get install -y libpam-dev
|
||||
- go get github.com/msteinert/pam
|
||||
|
||||
script: go build -v -tags "pam"
|
||||
env:
|
||||
- GO15VENDOREXPERIMENT=1
|
||||
|
||||
notifications:
|
||||
email:
|
||||
- u@gogs.io
|
||||
slack: gophercn:o5pSanyTeNhnfYc3QnG0X7Wx
|
||||
webhooks:
|
||||
urls:
|
||||
- https://webhooks.gitter.im/e/b590f8e03882f7aedc3e
|
||||
on_success: change
|
||||
on_failure: always
|
||||
on_start: never
|
||||
script:
|
||||
- go build -v -tags "pam"
|
||||
- go test -v -cover -race ./...
|
||||
|
||||
68
Dockerfile
68
Dockerfile
@@ -1,54 +1,24 @@
|
||||
FROM google/debian:wheezy
|
||||
MAINTAINER u@gogs.io
|
||||
FROM alpine:3.5
|
||||
|
||||
RUN echo "deb http://ftp.debian.org/debian/ wheezy-backports main" >> /etc/apt/sources.list && \
|
||||
apt-get update -qqy && \
|
||||
apt-get install --no-install-recommends -qqy \
|
||||
curl build-essential ca-certificates git \
|
||||
openssh-server rsync libpam-dev && \
|
||||
apt-get autoclean && \
|
||||
apt-get autoremove && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
# Install system utils & Gogs runtime dependencies
|
||||
ADD https://github.com/tianon/gosu/releases/download/1.9/gosu-amd64 /usr/sbin/gosu
|
||||
RUN chmod +x /usr/sbin/gosu \
|
||||
&& apk --no-cache --no-progress add ca-certificates bash git linux-pam s6 curl openssh socat tzdata
|
||||
|
||||
ENV GOROOT /goroot
|
||||
ENV GOPATH /gopath
|
||||
ENV PATH $PATH:$GOROOT/bin:$GOPATH/bin
|
||||
|
||||
COPY . /gopath/src/github.com/gogits/gogs/
|
||||
WORKDIR /gopath/src/github.com/gogits/gogs/
|
||||
|
||||
# Build binary and clean up useless files
|
||||
RUN mkdir /goroot && \
|
||||
curl https://storage.googleapis.com/golang/go1.5.linux-amd64.tar.gz | tar xzf - -C /goroot --strip-components=1 && \
|
||||
go get -v -tags "sqlite redis memcache cert pam" && \
|
||||
go build -tags "sqlite redis memcache cert pam" && \
|
||||
mkdir /app/ && \
|
||||
mv /gopath/src/github.com/gogits/gogs/ /app/gogs/ && \
|
||||
rm -r $GOROOT $GOPATH
|
||||
|
||||
WORKDIR /app/gogs/
|
||||
|
||||
RUN useradd --shell /bin/bash --system --comment gogits git
|
||||
|
||||
# SSH login fix, otherwise user is kicked off after login
|
||||
RUN mkdir /var/run/sshd && \
|
||||
sed 's@session\s*required\s*pam_loginuid.so@session optional pam_loginuid.so@g' -i /etc/pam.d/sshd && \
|
||||
sed 's@UsePrivilegeSeparation yes@UsePrivilegeSeparation no@' -i /etc/ssh/sshd_config && \
|
||||
echo "export VISIBLE=now" >> /etc/profile && \
|
||||
echo "PermitUserEnvironment yes" >> /etc/ssh/sshd_config
|
||||
|
||||
# Setup server keys on startup
|
||||
RUN sed 's@^HostKey@\#HostKey@' -i /etc/ssh/sshd_config && \
|
||||
echo "HostKey /data/ssh/ssh_host_key" >> /etc/ssh/sshd_config && \
|
||||
echo "HostKey /data/ssh/ssh_host_rsa_key" >> /etc/ssh/sshd_config && \
|
||||
echo "HostKey /data/ssh/ssh_host_dsa_key" >> /etc/ssh/sshd_config && \
|
||||
echo "HostKey /data/ssh/ssh_host_ecdsa_key" >> /etc/ssh/sshd_config && \
|
||||
echo "HostKey /data/ssh/ssh_host_ed25519_key" >> /etc/ssh/sshd_config
|
||||
|
||||
# Prepare data
|
||||
ENV GOGS_CUSTOM /data/gogs
|
||||
RUN echo "export GOGS_CUSTOM=/data/gogs" >> /etc/profile
|
||||
|
||||
COPY . /app/gogs/build
|
||||
WORKDIR /app/gogs/build
|
||||
|
||||
RUN ./docker/build-go.sh \
|
||||
&& ./docker/build.sh \
|
||||
&& ./docker/finalize.sh
|
||||
|
||||
# Configure LibC Name Service
|
||||
COPY docker/nsswitch.conf /etc/nsswitch.conf
|
||||
|
||||
# Configure Docker Container
|
||||
VOLUME ["/data"]
|
||||
EXPOSE 22 3000
|
||||
ENTRYPOINT []
|
||||
CMD ["./docker/start.sh"]
|
||||
ENTRYPOINT ["/app/gogs/docker/start.sh"]
|
||||
CMD ["/bin/s6-svscan", "/app/gogs/docker/s6/"]
|
||||
|
||||
22
Dockerfile.aarch64
Normal file
22
Dockerfile.aarch64
Normal file
@@ -0,0 +1,22 @@
|
||||
FROM aarch64/alpine:3.5
|
||||
MAINTAINER atzoum@gmail.com
|
||||
|
||||
# Install system utils & Gogs runtime dependencies
|
||||
ADD https://github.com/tianon/gosu/releases/download/1.9/gosu-arm64 /usr/sbin/gosu
|
||||
RUN chmod +x /usr/sbin/gosu \
|
||||
&& apk --no-cache --no-progress add ca-certificates bash git linux-pam s6 curl openssh socat tzdata
|
||||
|
||||
ENV GOGS_CUSTOM /data/gogs
|
||||
|
||||
COPY . /app/gogs/
|
||||
WORKDIR /app/gogs/
|
||||
RUN ./docker/build.sh
|
||||
|
||||
# Configure LibC Name Service
|
||||
COPY docker/nsswitch.conf /etc/nsswitch.conf
|
||||
|
||||
# Configure Docker Container
|
||||
VOLUME ["/data"]
|
||||
EXPOSE 22 3000
|
||||
ENTRYPOINT ["docker/start.sh"]
|
||||
CMD ["/bin/s6-svscan", "/app/gogs/docker/s6/"]
|
||||
24
Dockerfile.rpi
Normal file
24
Dockerfile.rpi
Normal file
@@ -0,0 +1,24 @@
|
||||
FROM armhf/alpine:3.5
|
||||
|
||||
# Install system utils & Gogs runtime dependencies
|
||||
ADD https://github.com/tianon/gosu/releases/download/1.9/gosu-armhf /usr/sbin/gosu
|
||||
RUN chmod +x /usr/sbin/gosu \
|
||||
&& apk --no-cache --no-progress add ca-certificates bash git linux-pam s6 curl openssh socat tzdata
|
||||
|
||||
ENV GOGS_CUSTOM /data/gogs
|
||||
|
||||
COPY . /app/gogs/build
|
||||
WORKDIR /app/gogs/build
|
||||
|
||||
RUN ./docker/build-go.sh \
|
||||
&& ./docker/build.sh \
|
||||
&& ./docker/finalize.sh
|
||||
|
||||
# Configure LibC Name Service
|
||||
COPY docker/nsswitch.conf /etc/nsswitch.conf
|
||||
|
||||
# Configure Docker Container
|
||||
VOLUME ["/data"]
|
||||
EXPOSE 22 3000
|
||||
ENTRYPOINT ["/app/gogs/docker/start.sh"]
|
||||
CMD ["/bin/s6-svscan", "/app/gogs/docker/s6/"]
|
||||
2
LICENSE
2
LICENSE
@@ -1,4 +1,4 @@
|
||||
Copyright (c) 2014 All Gogs Contributors
|
||||
Copyright (c) The Gogs Authors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
80
Makefile
Normal file
80
Makefile
Normal file
@@ -0,0 +1,80 @@
|
||||
LDFLAGS += -X "github.com/gogits/gogs/modules/setting.BuildTime=$(shell date -u '+%Y-%m-%d %I:%M:%S %Z')"
|
||||
LDFLAGS += -X "github.com/gogits/gogs/modules/setting.BuildGitHash=$(shell git rev-parse HEAD)"
|
||||
|
||||
DATA_FILES := $(shell find conf | sed 's/ /\\ /g')
|
||||
LESS_FILES := $(wildcard public/less/gogs.less public/less/_*.less)
|
||||
GENERATED := modules/bindata/bindata.go public/css/gogs.css
|
||||
|
||||
OS := $(shell uname)
|
||||
|
||||
TAGS = ""
|
||||
BUILD_FLAGS = "-v"
|
||||
|
||||
RELEASE_ROOT = "release"
|
||||
RELEASE_GOGS = "release/gogs"
|
||||
NOW = $(shell date -u '+%Y%m%d%I%M%S')
|
||||
GOVET = go tool vet -composites=false -methods=false -structtags=false
|
||||
|
||||
.PHONY: build pack release bindata clean
|
||||
|
||||
.IGNORE: public/css/gogs.css
|
||||
|
||||
all: build
|
||||
|
||||
check: test
|
||||
|
||||
dist: release
|
||||
|
||||
govet:
|
||||
$(GOVET) gogs.go
|
||||
$(GOVET) models modules routers
|
||||
|
||||
build: $(GENERATED)
|
||||
go install $(BUILD_FLAGS) -ldflags '$(LDFLAGS)' -tags '$(TAGS)'
|
||||
cp '$(GOPATH)/bin/gogs' .
|
||||
|
||||
build-dev: $(GENERATED) govet
|
||||
go install $(BUILD_FLAGS) -tags '$(TAGS)'
|
||||
cp '$(GOPATH)/bin/gogs' .
|
||||
|
||||
build-dev-race: $(GENERATED) govet
|
||||
go install $(BUILD_FLAGS) -race -tags '$(TAGS)'
|
||||
cp '$(GOPATH)/bin/gogs' .
|
||||
|
||||
pack:
|
||||
rm -rf $(RELEASE_GOGS)
|
||||
mkdir -p $(RELEASE_GOGS)
|
||||
cp -r gogs LICENSE README.md README_ZH.md templates public scripts $(RELEASE_GOGS)
|
||||
rm -rf $(RELEASE_GOGS)/public/config.codekit $(RELEASE_GOGS)/public/less
|
||||
cd $(RELEASE_ROOT) && zip -r gogs.$(NOW).zip "gogs"
|
||||
|
||||
release: build pack
|
||||
|
||||
bindata: modules/bindata/bindata.go
|
||||
|
||||
modules/bindata/bindata.go: $(DATA_FILES)
|
||||
go-bindata -o=$@ -ignore="\\.DS_Store|README.md|TRANSLATORS" -pkg=bindata conf/...
|
||||
|
||||
less: public/css/gogs.css
|
||||
|
||||
public/css/gogs.css: $(LESS_FILES)
|
||||
lessc $< $@
|
||||
|
||||
clean:
|
||||
go clean -i ./...
|
||||
|
||||
clean-mac: clean
|
||||
find . -name ".DS_Store" -print0 | xargs -0 rm
|
||||
|
||||
test:
|
||||
go test -cover -race ./...
|
||||
|
||||
fixme:
|
||||
grep -rnw "FIXME" cmd routers models modules
|
||||
|
||||
todo:
|
||||
grep -rnw "TODO" cmd routers models modules
|
||||
|
||||
# Legacy code should be remove by the time of release
|
||||
legacy:
|
||||
grep -rnw "LEGACY" cmd routers models modules
|
||||
140
README.md
140
README.md
@@ -1,89 +1,78 @@
|
||||
Gogs - Go Git Service [](https://travis-ci.org/gogits/gogs)
|
||||
Gogs [](https://travis-ci.org/gogits/gogs) [](https://ci.appveyor.com/project/Unknwon/gogs/branch/master) [](https://crowdin.com/project/gogs) [](https://sourcegraph.com/github.com/gogits/gogs?badge) [](https://gitter.im/gogits/gogs?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
=====================
|
||||
|
||||
[](https://gitter.im/gogits/gogs?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||

|
||||
|
||||
Gogs (Go Git Service) is a painless self-hosted Git service.
|
||||
##### Current tip version: [`.VERSION`](templates/.VERSION) (see [Releases](https://github.com/gogits/gogs/releases) for binary versions)
|
||||
|
||||
##### Current version: 0.6.9 Beta
|
||||
| Web | UI | Preview |
|
||||
|:-------------:|:-------:|:-------:|
|
||||
||||
|
||||
||||
|
||||
||||
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td width="33%"><img src="http://gogs.io/imgs/screenshoots/1.png"></td>
|
||||
<td width="33%"><img src="http://gogs.io/imgs/screenshoots/2.png"></td>
|
||||
<td width="33%"><img src="http://gogs.io/imgs/screenshoots/3.png"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="http://gogs.io/imgs/screenshoots/4.png"></td>
|
||||
<td><img src="http://gogs.io/imgs/screenshoots/5.png"></td>
|
||||
<td><img src="http://gogs.io/imgs/screenshoots/6.png"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><img src="http://gogs.io/imgs/screenshoots/7.png"></td>
|
||||
<td><img src="http://gogs.io/imgs/screenshoots/8.png"></td>
|
||||
<td><img src="http://gogs.io/imgs/screenshoots/9.png"></td>
|
||||
</tr>
|
||||
</table>
|
||||
### Important Notes
|
||||
|
||||
### NOTICES
|
||||
1. **YOU MUST READ [Contributing Code](https://github.com/gogits/gogs/wiki/Contributing-Code) BEFORE STARTING TO WORK ON A PULL REQUEST**.
|
||||
2. Due to testing purpose, data of [try.gogs.io](https://try.gogs.io) was reset in **Jan 28, 2015** and will reset multiple times after. Please do **NOT** put your important data on the site.
|
||||
3. The demo site [try.gogs.io](https://try.gogs.io) is running under `develop` branch.
|
||||
4. If you think there are vulnerabilities in the project, please talk privately to **u@gogs.io**. Thanks!
|
||||
5. If you're interested in using APIs, we have experimental support with [documentation](https://github.com/gogits/go-gogs-client/wiki).
|
||||
6. If your team/company is using Gogs and would like to put your logo on [our website](https://gogs.io), contact us by any means.
|
||||
|
||||
- Due to testing purpose, data of [try.gogs.io](https://try.gogs.io) has been reset in **Jan 28, 2015** and will reset multiple times after. Please do **NOT** put your important data on the site.
|
||||
- The demo site [try.gogs.io](https://try.gogs.io) is running under `develop` branch.
|
||||
- :exclamation::exclamation::exclamation:<span style="color: red">You **MUST** read [CONTRIBUTING.md](CONTRIBUTING.md) before you start filing an issue or making a Pull Request, and **MUST** discuss with us on [Gitter](https://gitter.im/gogits/gogs) for UI changes and feature Pull Requests, otherwise it's high possibilities that we are not going to merge it.</span>:exclamation::exclamation::exclamation:
|
||||
- If you think there are vulnerabilities in the project, please talk privately to **u@gogs.io**. Thanks!
|
||||
|
||||
#### Other language version
|
||||
|
||||
- [简体中文](README_ZH.md)
|
||||
[简体中文](README_ZH.md)
|
||||
|
||||
## Purpose
|
||||
|
||||
The goal of this project is to make the easiest, fastest, and most painless way to set up a self-hosted Git service. With Go, this can be done via an independent binary distribution across **ALL platforms** that Go supports, including Linux, Mac OS X, and Windows.
|
||||
The goal of this project is to make the easiest, fastest, and most painless way of setting up a self-hosted Git service. With Go, this can be done with an independent binary distribution across **ALL platforms** that Go supports, including Linux, Mac OS X, Windows and ARM.
|
||||
|
||||
## Overview
|
||||
|
||||
- Please see the [Documentation](http://gogs.io/docs/intro/) for project design, known issues, and change log.
|
||||
- See the [Trello Board](https://trello.com/b/uxAoeLUl/gogs-go-git-service) to follow the develop team.
|
||||
- Want to try it before doing anything else? Do it [online](https://try.gogs.io/gogs/gogs) or go down to the **Installation -> Install from binary** section!
|
||||
- Having trouble? Get help with [Troubleshooting](http://gogs.io/docs/intro/troubleshooting.md).
|
||||
- Want to help with localization? Check out the [guide](http://gogs.io/docs/features/i18n.html)!
|
||||
- Please see the [Documentation](https://gogs.io/docs/intro) for common usages and change log.
|
||||
- Want to try it before doing anything else? Do it [online](https://try.gogs.io/gogs/gogs)!
|
||||
- Having trouble? Get help with [Troubleshooting](https://gogs.io/docs/intro/troubleshooting.html) or [User Forum](https://discuss.gogs.io/).
|
||||
- Want to help with localization? Check out the [guide](https://gogs.io/docs/features/i18n.html)!
|
||||
|
||||
## Features
|
||||
|
||||
- Activity timeline
|
||||
- SSH/HTTP(S) protocol support
|
||||
- SMTP/LDAP/reverse proxy authentication support
|
||||
- Reverse proxy suburl support
|
||||
- Account/Organization(with team)/Repository management
|
||||
- Repository/Organization webhooks(including Slack)
|
||||
- SSH and HTTP/HTTPS protocols
|
||||
- SMTP/LDAP/Reverse proxy authentication
|
||||
- Reverse proxy with sub-path
|
||||
- Account/Organization/Repository management
|
||||
- Add/Remove repository collaborators
|
||||
- Repository/Organization webhooks (including Slack and Discord)
|
||||
- Repository Git hooks/deploy keys
|
||||
- Add/remove repository collaborators
|
||||
- Gravatar and custom source support
|
||||
- Repository issues, pull requests, wiki and protected branches
|
||||
- Migrate and mirror repository and its wiki
|
||||
- Web editor for repository files and wiki
|
||||
- Jupyter Notebook
|
||||
- Gravatar and Federated avatar with custom source
|
||||
- Mail service
|
||||
- Administration panel
|
||||
- Supports MySQL, PostgreSQL and SQLite3
|
||||
- Social account login (GitHub, Google, QQ, Weibo)
|
||||
- Multi-language support ([14 languages](https://crowdin.com/project/gogs))
|
||||
- Supports MySQL, PostgreSQL, SQLite3, MSSQL and [TiDB](https://github.com/pingcap/tidb) (experimental)
|
||||
- Multi-language support ([23 languages](https://crowdin.com/project/gogs))
|
||||
|
||||
## System Requirements
|
||||
## Hardware Requirements
|
||||
|
||||
- A cheap Raspberry Pi is powerful enough for basic functionality.
|
||||
- At least 2 CPU cores and 1GB RAM would be the baseline for teamwork.
|
||||
- A Raspberry Pi or $5 Digital Ocean Droplet is more than enough to get you started. Some even use 64MB RAM Docker [CaaS](https://blog.docker.com/2016/02/containers-as-a-service-caas/).
|
||||
- 2 CPU cores and 512MB RAM would be the baseline for teamwork.
|
||||
- Increase CPU cores when your team size gets significantly larger, memory footprint remains low.
|
||||
|
||||
## Browser Support
|
||||
|
||||
- Please see [Semantic UI](https://github.com/Semantic-Org/Semantic-UI#browser-support) for specific versions of supported browsers.
|
||||
- The official support minimal size is **1024*768**, UI may still looks right in smaller size but no promises and fixes.
|
||||
- The smallest resolution officially supported is **1024*768**, however the UI may still look right in smaller resolutions, but no promises or fixes.
|
||||
|
||||
## Installation
|
||||
|
||||
Make sure you install the [prerequisites](http://gogs.io/docs/installation/) first.
|
||||
Make sure you install the [prerequisites](https://gogs.io/docs/installation) first.
|
||||
|
||||
There are 5 ways to install Gogs:
|
||||
|
||||
- [Install from binary](http://gogs.io/docs/installation/install_from_binary.md)
|
||||
- [Install from source](http://gogs.io/docs/installation/install_from_source.md)
|
||||
- [Install from packages](http://gogs.io/docs/installation/install_from_packages.md)
|
||||
- [Install from binary](https://gogs.io/docs/installation/install_from_binary.html)
|
||||
- [Install from source](https://gogs.io/docs/installation/install_from_source.html)
|
||||
- [Install from packages](https://gogs.io/docs/installation/install_from_packages.html)
|
||||
- [Ship with Docker](https://github.com/gogits/gogs/tree/master/docker)
|
||||
- [Install with Vagrant](https://github.com/geerlingguy/ansible-vagrant-examples/tree/master/gogs)
|
||||
|
||||
@@ -91,26 +80,55 @@ There are 5 ways to install Gogs:
|
||||
|
||||
- [How To Set Up Gogs on Ubuntu 14.04](https://www.digitalocean.com/community/tutorials/how-to-set-up-gogs-on-ubuntu-14-04)
|
||||
- [Run your own GitHub-like service with the help of Docker](http://blog.hypriot.com/post/run-your-own-github-like-service-with-docker/)
|
||||
- [Dockerized Gogs git server and alpine postgres in 20 minutes or less](http://garthwaite.org/docker-gogs.html)
|
||||
- [Host Your Own Private GitHub with Gogs](https://eladnava.com/host-your-own-private-github-with-gogs-io/)
|
||||
- [使用 Gogs 搭建自己的 Git 服务器](https://mynook.info/blog/post/host-your-own-git-server-using-gogs) (Chinese)
|
||||
- [阿里云上 Ubuntu 14.04 64 位安装 Gogs](http://my.oschina.net/luyao/blog/375654) (Chinese)
|
||||
- [Installing Gogs on FreeBSD](https://www.codejam.info/2015/03/installing-gogs-on-freebsd.html)
|
||||
- [Gogs on Raspberry Pi](http://blog.meinside.pe.kr/Gogs-on-Raspberry-Pi/)
|
||||
- [Cloudflare Full SSL with Gogs using NGINX](http://www.listekconsulting.com/articles/cloudflare-full-ssl-with-gogs-go-git-service-using-nginx/)
|
||||
|
||||
### Screencasts
|
||||
|
||||
- [Instalando Gogs no Ubuntu](http://blog.linuxpro.com.br/2015/08/14/instalando-gogs-no-ubuntu/) (Português)
|
||||
- [How to install Gogs on a Linux Server (DigitalOcean)](https://www.youtube.com/watch?v=deSfX0gqefE)
|
||||
- [Instalando Gogs no Ubuntu](https://www.youtube.com/watch?v=4UkHAR1F7ZA) (Português)
|
||||
|
||||
### Deploy to Cloud
|
||||
|
||||
- [OpenShift](https://github.com/tkisme/gogs-openshift)
|
||||
- [Cloudron](https://cloudron.io/appstore.html#io.gogs.cloudronapp)
|
||||
- [Scaleway](https://www.scaleway.com/imagehub/gogs/)
|
||||
- [Sandstorm](https://github.com/cem/gogs-sandstorm)
|
||||
- [sloppy.io](https://github.com/sloppyio/quickstarters/tree/master/gogs)
|
||||
- [YunoHost](https://github.com/YunoHost-Apps/gogs_ynh)
|
||||
- [DPlatform](https://github.com/j8r/DPlatform)
|
||||
|
||||
## Software and Service Support
|
||||
|
||||
- [Drone](https://github.com/drone/drone) (CI)
|
||||
- [Jenkins](https://wiki.jenkins-ci.org/display/JENKINS/Gogs+Webhook+Plugin) (CI)
|
||||
- [Fabric8](http://fabric8.io/) (DevOps)
|
||||
- [Taiga](https://taiga.io/) (Project Management)
|
||||
- [Puppet](https://forge.puppetlabs.com/Siteminds/gogs) (IT)
|
||||
- [Kanboard](http://kanboard.net/plugin/gogs-webhook) (Project Management)
|
||||
- [BearyChat](https://bearychat.com/) (Team Communication)
|
||||
- [HiWork](http://www.hiwork.cc/) (Team Communication)
|
||||
|
||||
### Product Support
|
||||
|
||||
- [Synology](https://www.synology.com) (Docker)
|
||||
- [One Space](http://www.onespace.cc) (App Store)
|
||||
|
||||
## Acknowledgments
|
||||
|
||||
- Router and middleware mechanism of [Macaron](https://github.com/Unknwon/macaron).
|
||||
- Mail Service, modules design is inspired by [WeTalk](https://github.com/beego/wetalk).
|
||||
- System Monitor Status is inspired by [GoBlog](https://github.com/fuxiaohei/goblog).
|
||||
- Thanks [lavachen](http://www.lavachen.cn/) and [Rocker](http://weibo.com/rocker1989) for designing Logo.
|
||||
- Thanks [Egon Elbre](https://twitter.com/egonelbre) for designing logo.
|
||||
- Thanks [Crowdin](https://crowdin.com/project/gogs) for providing open source translation plan.
|
||||
- Thanks [DigitalOcean](https://www.digitalocean.com) for hosting home and demo sites.
|
||||
- Thanks [KeyCDN](https://www.keycdn.com/) and [QiNiu](http://www.qiniu.com/) for providing CDN service.
|
||||
|
||||
## Contributors
|
||||
|
||||
- Ex-team members [@lunny](https://github.com/lunny) and [@fuxiaohei](https://github.com/fuxiaohei).
|
||||
- See [contributors page](https://github.com/gogits/gogs/graphs/contributors) for full list of contributors.
|
||||
- See [contributors page](https://github.com/gogits/gogs/graphs/contributors) for top 100 contributors.
|
||||
- See [TRANSLATORS](conf/locale/TRANSLATORS) for public list of translators.
|
||||
|
||||
## License
|
||||
|
||||
88
README_ZH.md
88
README_ZH.md
@@ -1,41 +1,44 @@
|
||||
Gogs - Go Git Service [](https://travis-ci.org/gogits/gogs)
|
||||
Gogs [](https://travis-ci.org/gogits/gogs) [](https://ci.appveyor.com/project/Unknwon/gogs/branch/master)
|
||||
=====================
|
||||
|
||||
Gogs (Go Git Service) 是一款可轻易搭建的自助 Git 服务。
|
||||
Gogs 是一款极易搭建的自助 Git 服务。
|
||||
|
||||
## 开发目的
|
||||
|
||||
Gogs 的目标是打造一个最简单、最快速和最轻松的方式搭建自助 Git 服务。使用 Go 语言开发使得 Gogs 能够通过独立的二进制分发,并且支持 Go 语言支持的 **所有平台**,包括 Linux、Mac OS X 以及 Windows。
|
||||
Gogs 的目标是打造一个最简单、最快速和最轻松的方式搭建自助 Git 服务。使用 Go 语言开发使得 Gogs 能够通过独立的二进制分发,并且支持 Go 语言支持的 **所有平台**,包括 Linux、Mac OS X、Windows 以及 ARM 平台。
|
||||
|
||||
## 项目概览
|
||||
|
||||
- 有关项目设计、已知问题和变更日志,请通过 [使用手册](http://gogs.io/docs/intro/) 查看。
|
||||
- 您可以到 [Trello Board](https://trello.com/b/uxAoeLUl/gogs-go-git-service) 跟随开发团队的脚步。
|
||||
- 想要先睹为快?通过 [在线体验](https://try.gogs.io/gogs/gogs) 或查看 **安装部署 -> 二进制安装** 小节。
|
||||
- 使用过程中遇到问题?尝试从 [故障排查](http://gogs.io/docs/intro/troubleshooting.md) 页面获取帮助。
|
||||
- 希望帮助多国语言界面的翻译吗?请立即访问 [详情页面](http://gogs.io/docs/features/i18n.html)!
|
||||
- 有关基本用法和变更日志,请通过 [使用手册](https://gogs.io/docs/intro/) 查看。
|
||||
- 想要先睹为快?直接去 [在线体验](https://try.gogs.io/gogs/gogs) 。
|
||||
- 使用过程中遇到问题?尝试从 [故障排查](https://gogs.io/docs/intro/troubleshooting.html) 页面或 [用户论坛](https://discuss.gogs.io/) 获取帮助。
|
||||
- 希望帮助多国语言界面的翻译吗?请立即访问 [详情页面](https://gogs.io/docs/features/i18n.html)!
|
||||
|
||||
## 功能特性
|
||||
|
||||
- 支持活动时间线
|
||||
- 支持 SSH/HTTP(S) 协议
|
||||
- 支持 SMTP/LDAP/反向代理的用户认证
|
||||
- 支持 SSH 以及 HTTP/HTTPS 协议
|
||||
- 支持 SMTP、LDAP 和反向代理的用户认证
|
||||
- 支持反向代理子路径
|
||||
- 支持用户、组织和仓库管理系统
|
||||
- 支持仓库和组织级别 Web 钩子(包括 Slack 集成)
|
||||
- 支持添加和删除仓库协作者
|
||||
- 支持仓库和组织级别 Web 钩子(包括 Slack 和 Discord 集成)
|
||||
- 支持仓库 Git 钩子和部署密钥
|
||||
- 支持 添加/删除 仓库协作者
|
||||
- 支持 Gravatar 以及自定义源
|
||||
- 支持仓库工单(Issue)、合并请求(Pull Request)、Wiki 和保护分支
|
||||
- 支持迁移和镜像仓库以及它的 Wiki
|
||||
- 支持在线编辑仓库文件和 Wiki
|
||||
- 支持自定义源的 Gravatar 和 Federated Avatar
|
||||
- 支持 Jupyter Notebook
|
||||
- 支持邮件服务
|
||||
- 支持后台管理面板
|
||||
- 支持 MySQL、PostgreSQL 以及 SQLite3 数据库
|
||||
- 支持社交帐号登录(GitHub、Google、QQ、微博)
|
||||
- 支持多语言本地化([14 种语言]([more](https://crowdin.com/project/gogs)))
|
||||
- 支持 MySQL、PostgreSQL、SQLite3、MSSQL 和 [TiDB](https://github.com/pingcap/tidb)(实验性支持) 数据库
|
||||
- 支持多语言本地化([23 种语言]([more](https://crowdin.com/project/gogs)))
|
||||
|
||||
## 系统要求
|
||||
## 硬件要求
|
||||
|
||||
- 最低的系统硬件要求为一个廉价的树莓派
|
||||
- 如果用于团队项目,建议使用 2 核 CPU 及 1GB 内存
|
||||
- 如果用于团队项目管理,建议使用 2 核 CPU 及 512MB 内存
|
||||
- 当团队成员大量增加时,可以考虑添加 CPU 核数,内存占用保持不变
|
||||
|
||||
## 浏览器支持
|
||||
|
||||
@@ -44,28 +47,57 @@ Gogs 的目标是打造一个最简单、最快速和最轻松的方式搭建自
|
||||
|
||||
## 安装部署
|
||||
|
||||
在安装 Gogs 之前,您需要先安装 [基本环境](http://gogs.io/docs/installation/)。
|
||||
在安装 Gogs 之前,您需要先安装 [基本环境](https://gogs.io/docs/installation)。
|
||||
|
||||
然后,您可以通过以下 5 种方式来安装 Gogs:
|
||||
|
||||
- [二进制安装](http://gogs.io/docs/installation/install_from_binary.md)
|
||||
- [源码安装](http://gogs.io/docs/installation/install_from_source.md)
|
||||
- [包管理安装](http://gogs.io/docs/installation/install_from_packages.md)
|
||||
- [二进制安装](https://gogs.io/docs/installation/install_from_binary.html)
|
||||
- [源码安装](https://gogs.io/docs/installation/install_from_source.html)
|
||||
- [包管理安装](https://gogs.io/docs/installation/install_from_packages.html)
|
||||
- [采用 Docker 部署](https://github.com/gogits/gogs/tree/master/docker)
|
||||
- [通过 Vagrant 安装](https://github.com/geerlingguy/ansible-vagrant-examples/tree/master/gogs)
|
||||
|
||||
### 使用教程
|
||||
|
||||
- [使用 Gogs 搭建自己的 Git 服务器](https://mynook.info/blog/post/host-your-own-git-server-using-gogs)
|
||||
- [阿里云上 Ubuntu 14.04 64 位安装 Gogs](http://my.oschina.net/luyao/blog/375654)
|
||||
|
||||
### 云端部署
|
||||
|
||||
- [OpenShift](https://github.com/tkisme/gogs-openshift)
|
||||
- [Cloudron](https://cloudron.io/appstore.html#io.gogs.cloudronapp)
|
||||
- [Scaleway](https://www.scaleway.com/imagehub/gogs/)
|
||||
- [Sandstorm](https://github.com/cem/gogs-sandstorm)
|
||||
- [sloppy.io](https://github.com/sloppyio/quickstarters/tree/master/gogs)
|
||||
- [YunoHost](https://github.com/mbugeia/gogs_ynh)
|
||||
- [DPlatform](https://github.com/j8r/DPlatform)
|
||||
|
||||
## 软件及服务支持
|
||||
|
||||
- [Drone](https://github.com/drone/drone)(CI)
|
||||
- [Jenkins](https://wiki.jenkins-ci.org/display/JENKINS/Gogs+Webhook+Plugin)(CI)
|
||||
- [Fabric8](http://fabric8.io/)(DevOps)
|
||||
- [Taiga](https://taiga.io/)(项目管理)
|
||||
- [Puppet](https://forge.puppetlabs.com/Siteminds/gogs)(IT)
|
||||
- [Kanboard](http://kanboard.net/plugin/gogs-webhook)(项目管理)
|
||||
- [BearyChat](https://bearychat.com/)(团队交流)
|
||||
- [HiWork](http://www.hiwork.cc/)(团队交流)
|
||||
|
||||
### 产品支持
|
||||
|
||||
- [Synology](https://www.synology.com)(Docker)
|
||||
- [One Space](http://www.onespace.cc)(应用商店)
|
||||
|
||||
## 特别鸣谢
|
||||
|
||||
- 基于 [Macaron](https://github.com/Unknwon/macaron) 的路由与中间件机制。
|
||||
- 基于 [WeTalk](https://github.com/beego/wetalk) 修改的邮件服务和模块设计。
|
||||
- 基于 [GoBlog](https://github.com/fuxiaohei/goblog) 修改的系统监视状态。
|
||||
- 感谢 [lavachen](http://www.lavachen.cn/) 和 [Rocker](http://weibo.com/rocker1989) 设计的 Logo。
|
||||
- 感谢 [Egon Elbre](https://twitter.com/egonelbre) 设计的 Logo。
|
||||
- 感谢 [Crowdin](https://crowdin.com/project/gogs) 提供免费的开源项目本地化支持。
|
||||
- 感谢 [DigitalOcean](https://www.digitalocean.com) 提供主站和体验站点的服务器赞助。
|
||||
- 感谢 [KeyCDN](https://www.keycdn.com/) 和 [七牛云存储](http://www.qiniu.com/) 提供 CDN 服务赞助。
|
||||
|
||||
## 贡献成员
|
||||
|
||||
- 前团队成员 [@lunny](https://github.com/lunny) 和 [@fuxiaohei](https://github.com/fuxiaohei)。
|
||||
- 您可以通过查看 [贡献者页面](https://github.com/gogits/gogs/graphs/contributors) 获取完整的贡献者列表。
|
||||
- 您可以通过查看 [贡献者页面](https://github.com/gogits/gogs/graphs/contributors) 获取 TOP 100 的贡献者列表。
|
||||
- 您可以通过查看 [TRANSLATORS](conf/locale/TRANSLATORS) 文件获取公开的翻译人员列表。
|
||||
|
||||
## 授权许可
|
||||
|
||||
20
appveyor.yml
Normal file
20
appveyor.yml
Normal file
@@ -0,0 +1,20 @@
|
||||
version: "{build}"
|
||||
skip_tags: true
|
||||
clone_folder: c:\gopath\src\github.com\gogits\gogs
|
||||
clone_depth: 1
|
||||
|
||||
environment:
|
||||
GOPATH: c:\gopath
|
||||
GOVERSION: 1.7
|
||||
|
||||
build: false
|
||||
deploy: false
|
||||
|
||||
install:
|
||||
- go build -v
|
||||
|
||||
notifications:
|
||||
- provider: Email
|
||||
to:
|
||||
- u@gogs.io
|
||||
on_build_success: false
|
||||
70
cmd/admin.go
Normal file
70
cmd/admin.go
Normal file
@@ -0,0 +1,70 @@
|
||||
// Copyright 2016 The Gogs Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/urfave/cli"
|
||||
|
||||
"github.com/gogits/gogs/models"
|
||||
"github.com/gogits/gogs/modules/setting"
|
||||
)
|
||||
|
||||
var (
|
||||
CmdAdmin = cli.Command{
|
||||
Name: "admin",
|
||||
Usage: "Preform admin operations on command line",
|
||||
Description: `Allow using internal logic of Gogs without hacking into the source code
|
||||
to make automatic initialization process more smoothly`,
|
||||
Subcommands: []cli.Command{
|
||||
subcmdCreateUser,
|
||||
},
|
||||
}
|
||||
|
||||
subcmdCreateUser = cli.Command{
|
||||
Name: "create-user",
|
||||
Usage: "Create a new user in database",
|
||||
Action: runCreateUser,
|
||||
Flags: []cli.Flag{
|
||||
stringFlag("name", "", "Username"),
|
||||
stringFlag("password", "", "User password"),
|
||||
stringFlag("email", "", "User email address"),
|
||||
boolFlag("admin", "User is an admin"),
|
||||
stringFlag("config, c", "custom/conf/app.ini", "Custom configuration file path"),
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
func runCreateUser(c *cli.Context) error {
|
||||
if !c.IsSet("name") {
|
||||
return fmt.Errorf("Username is not specified")
|
||||
} else if !c.IsSet("password") {
|
||||
return fmt.Errorf("Password is not specified")
|
||||
} else if !c.IsSet("email") {
|
||||
return fmt.Errorf("Email is not specified")
|
||||
}
|
||||
|
||||
if c.IsSet("config") {
|
||||
setting.CustomConf = c.String("config")
|
||||
}
|
||||
|
||||
setting.NewContext()
|
||||
models.LoadConfigs()
|
||||
models.SetEngine()
|
||||
|
||||
if err := models.CreateUser(&models.User{
|
||||
Name: c.String("name"),
|
||||
Email: c.String("email"),
|
||||
Passwd: c.String("password"),
|
||||
IsActive: true,
|
||||
IsAdmin: c.Bool("admin"),
|
||||
}); err != nil {
|
||||
return fmt.Errorf("CreateUser: %v", err)
|
||||
}
|
||||
|
||||
fmt.Printf("New user '%s' has been successfully created!\n", c.String("name"))
|
||||
return nil
|
||||
}
|
||||
24
cmd/cert.go
24
cmd/cert.go
@@ -22,7 +22,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
var CmdCert = cli.Command{
|
||||
@@ -32,12 +32,12 @@ var CmdCert = cli.Command{
|
||||
Outputs to 'cert.pem' and 'key.pem' and will overwrite existing files.`,
|
||||
Action: runCert,
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{"host", "", "Comma-separated hostnames and IPs to generate a certificate for", ""},
|
||||
cli.StringFlag{"ecdsa-curve", "", "ECDSA curve to use to generate a key. Valid values are P224, P256, P384, P521", ""},
|
||||
cli.IntFlag{"rsa-bits", 2048, "Size of RSA key to generate. Ignored if --ecdsa-curve is set", ""},
|
||||
cli.StringFlag{"start-date", "", "Creation date formatted as Jan 1 15:04:05 2011", ""},
|
||||
cli.DurationFlag{"duration", 365 * 24 * time.Hour, "Duration that certificate is valid for", ""},
|
||||
cli.BoolFlag{"ca", "whether this cert should be its own Certificate Authority", ""},
|
||||
stringFlag("host", "", "Comma-separated hostnames and IPs to generate a certificate for"),
|
||||
stringFlag("ecdsa-curve", "", "ECDSA curve to use to generate a key. Valid values are P224, P256, P384, P521"),
|
||||
intFlag("rsa-bits", 2048, "Size of RSA key to generate. Ignored if --ecdsa-curve is set"),
|
||||
stringFlag("start-date", "", "Creation date formatted as Jan 1 15:04:05 2011"),
|
||||
durationFlag("duration", 365*24*time.Hour, "Duration that certificate is valid for"),
|
||||
boolFlag("ca", "whether this cert should be its own Certificate Authority"),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ func pemBlockForKey(priv interface{}) *pem.Block {
|
||||
case *ecdsa.PrivateKey:
|
||||
b, err := x509.MarshalECPrivateKey(k)
|
||||
if err != nil {
|
||||
log.Fatal("unable to marshal ECDSA private key: %v", err)
|
||||
log.Fatalf("Unable to marshal ECDSA private key: %v\n", err)
|
||||
}
|
||||
return &pem.Block{Type: "EC PRIVATE KEY", Bytes: b}
|
||||
default:
|
||||
@@ -67,7 +67,7 @@ func pemBlockForKey(priv interface{}) *pem.Block {
|
||||
}
|
||||
}
|
||||
|
||||
func runCert(ctx *cli.Context) {
|
||||
func runCert(ctx *cli.Context) error {
|
||||
if len(ctx.String("host")) == 0 {
|
||||
log.Fatal("Missing required --host parameter")
|
||||
}
|
||||
@@ -114,7 +114,7 @@ func runCert(ctx *cli.Context) {
|
||||
SerialNumber: serialNumber,
|
||||
Subject: pkix.Name{
|
||||
Organization: []string{"Acme Co"},
|
||||
CommonName: "Gogs",
|
||||
CommonName: "Gogs",
|
||||
},
|
||||
NotBefore: notBefore,
|
||||
NotAfter: notAfter,
|
||||
@@ -153,9 +153,11 @@ func runCert(ctx *cli.Context) {
|
||||
|
||||
keyOut, err := os.OpenFile("key.pem", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
|
||||
if err != nil {
|
||||
log.Fatal("failed to open key.pem for writing: %v", err)
|
||||
log.Fatalf("Failed to open key.pem for writing: %v\n", err)
|
||||
}
|
||||
pem.Encode(keyOut, pemBlockForKey(priv))
|
||||
keyOut.Close()
|
||||
log.Println("Written key.pem")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -10,18 +10,19 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
var CmdCert = cli.Command{
|
||||
Name: "cert",
|
||||
Usage: "Generate self-signed certificate",
|
||||
Description: `Generate a self-signed X.509 certificate for a TLS server.
|
||||
Outputs to 'cert.pem' and 'key.pem' and will overwrite existing files.`,
|
||||
Action: runCert,
|
||||
Name: "cert",
|
||||
Usage: "Generate self-signed certificate",
|
||||
Description: `Please use build tags "cert" to rebuild Gogs in order to have this ability`,
|
||||
Action: runCert,
|
||||
}
|
||||
|
||||
func runCert(ctx *cli.Context) {
|
||||
func runCert(ctx *cli.Context) error {
|
||||
fmt.Println("Command cert not available, please use build tags 'cert' to rebuild.")
|
||||
os.Exit(1)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
42
cmd/cmd.go
Normal file
42
cmd/cmd.go
Normal file
@@ -0,0 +1,42 @@
|
||||
// Copyright 2015 The Gogs Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
func stringFlag(name, value, usage string) cli.StringFlag {
|
||||
return cli.StringFlag{
|
||||
Name: name,
|
||||
Value: value,
|
||||
Usage: usage,
|
||||
}
|
||||
}
|
||||
|
||||
func boolFlag(name, usage string) cli.BoolFlag {
|
||||
return cli.BoolFlag{
|
||||
Name: name,
|
||||
Usage: usage,
|
||||
}
|
||||
}
|
||||
|
||||
func intFlag(name string, value int, usage string) cli.IntFlag {
|
||||
return cli.IntFlag{
|
||||
Name: name,
|
||||
Value: value,
|
||||
Usage: usage,
|
||||
}
|
||||
}
|
||||
|
||||
func durationFlag(name string, value time.Duration, usage string) cli.DurationFlag {
|
||||
return cli.DurationFlag{
|
||||
Name: name,
|
||||
Value: value,
|
||||
Usage: usage,
|
||||
}
|
||||
}
|
||||
80
cmd/dump.go
80
cmd/dump.go
@@ -11,8 +11,11 @@ import (
|
||||
"path"
|
||||
"time"
|
||||
|
||||
"io/ioutil"
|
||||
|
||||
"github.com/Unknwon/cae/zip"
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/Unknwon/com"
|
||||
"github.com/urfave/cli"
|
||||
|
||||
"github.com/gogits/gogs/models"
|
||||
"github.com/gogits/gogs/modules/setting"
|
||||
@@ -25,29 +28,41 @@ var CmdDump = cli.Command{
|
||||
It can be used for backup and capture Gogs server image to send to maintainer`,
|
||||
Action: runDump,
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{"config, c", "custom/conf/app.ini", "Custom configuration file path", ""},
|
||||
cli.BoolFlag{"verbose, v", "show process details", ""},
|
||||
stringFlag("config, c", "custom/conf/app.ini", "Custom configuration file path"),
|
||||
boolFlag("verbose, v", "Show process details"),
|
||||
stringFlag("tempdir, t", os.TempDir(), "Temporary dir path"),
|
||||
},
|
||||
}
|
||||
|
||||
func runDump(ctx *cli.Context) {
|
||||
func runDump(ctx *cli.Context) error {
|
||||
if ctx.IsSet("config") {
|
||||
setting.CustomConf = ctx.String("config")
|
||||
}
|
||||
setting.NewConfigContext()
|
||||
models.LoadModelsConfig()
|
||||
setting.NewContext()
|
||||
models.LoadConfigs()
|
||||
models.SetEngine()
|
||||
|
||||
tmpDir := ctx.String("tempdir")
|
||||
if _, err := os.Stat(tmpDir); os.IsNotExist(err) {
|
||||
log.Fatalf("Path does not exist: %s", tmpDir)
|
||||
}
|
||||
TmpWorkDir, err := ioutil.TempDir(tmpDir, "gogs-dump-")
|
||||
if err != nil {
|
||||
log.Fatalf("Fail to create tmp work directory: %v", err)
|
||||
}
|
||||
log.Printf("Creating tmp work dir: %s", TmpWorkDir)
|
||||
|
||||
reposDump := path.Join(TmpWorkDir, "gogs-repo.zip")
|
||||
dbDump := path.Join(TmpWorkDir, "gogs-db.sql")
|
||||
|
||||
log.Printf("Dumping local repositories...%s", setting.RepoRootPath)
|
||||
zip.Verbose = ctx.Bool("verbose")
|
||||
defer os.Remove("gogs-repo.zip")
|
||||
if err := zip.PackTo(setting.RepoRootPath, "gogs-repo.zip", true); err != nil {
|
||||
if err := zip.PackTo(setting.RepoRootPath, reposDump, true); err != nil {
|
||||
log.Fatalf("Fail to dump local repositories: %v", err)
|
||||
}
|
||||
|
||||
log.Printf("Dumping database...")
|
||||
defer os.Remove("gogs-db.sql")
|
||||
if err := models.DumpDatabase("gogs-db.sql"); err != nil {
|
||||
if err := models.DumpDatabase(dbDump); err != nil {
|
||||
log.Fatalf("Fail to dump database: %v", err)
|
||||
}
|
||||
|
||||
@@ -59,16 +74,49 @@ func runDump(ctx *cli.Context) {
|
||||
log.Fatalf("Fail to create %s: %v", fileName, err)
|
||||
}
|
||||
|
||||
workDir, _ := setting.WorkDir()
|
||||
z.AddFile("gogs-repo.zip", path.Join(workDir, "gogs-repo.zip"))
|
||||
z.AddFile("gogs-db.sql", path.Join(workDir, "gogs-db.sql"))
|
||||
z.AddDir("custom", path.Join(workDir, "custom"))
|
||||
z.AddDir("log", path.Join(workDir, "log"))
|
||||
if err := z.AddFile("gogs-repo.zip", reposDump); err != nil {
|
||||
log.Fatalf("Fail to include gogs-repo.zip: %v", err)
|
||||
}
|
||||
if err := z.AddFile("gogs-db.sql", dbDump); err != nil {
|
||||
log.Fatalf("Fail to include gogs-db.sql: %v", err)
|
||||
}
|
||||
customDir, err := os.Stat(setting.CustomPath)
|
||||
if err == nil && customDir.IsDir() {
|
||||
if err := z.AddDir("custom", setting.CustomPath); err != nil {
|
||||
log.Fatalf("Fail to include custom: %v", err)
|
||||
}
|
||||
} else {
|
||||
log.Printf("Custom dir %s doesn't exist, skipped", setting.CustomPath)
|
||||
}
|
||||
|
||||
if err := z.AddDir("log", setting.LogRootPath); err != nil {
|
||||
log.Fatalf("Fail to include log: %v", err)
|
||||
}
|
||||
|
||||
for _, dir := range []string{"attachments", "avatars"} {
|
||||
dirPath := path.Join(setting.AppDataPath, dir)
|
||||
if !com.IsDir(dirPath) {
|
||||
continue
|
||||
}
|
||||
|
||||
if err := z.AddDir(path.Join("data", dir), dirPath); err != nil {
|
||||
log.Fatalf("Fail to include '%s': %v", dirPath, err)
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: SSH key file.
|
||||
if err = z.Close(); err != nil {
|
||||
os.Remove(fileName)
|
||||
log.Fatalf("Fail to save %s: %v", fileName, err)
|
||||
}
|
||||
|
||||
log.Println("Finish dumping!")
|
||||
if err := os.Chmod(fileName, 0600); err != nil {
|
||||
log.Printf("Can't change file access permissions mask to 0600: %v", err)
|
||||
}
|
||||
|
||||
log.Printf("Removing tmp work dir: %s", TmpWorkDir)
|
||||
os.RemoveAll(TmpWorkDir)
|
||||
log.Printf("Finish dumping in file %s", fileName)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
241
cmd/hook.go
Normal file
241
cmd/hook.go
Normal file
@@ -0,0 +1,241 @@
|
||||
// Copyright 2014 The Gogs Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/Unknwon/com"
|
||||
"github.com/urfave/cli"
|
||||
log "gopkg.in/clog.v1"
|
||||
|
||||
"github.com/gogits/git-module"
|
||||
|
||||
"github.com/gogits/gogs/models"
|
||||
"github.com/gogits/gogs/modules/httplib"
|
||||
"github.com/gogits/gogs/modules/setting"
|
||||
http "github.com/gogits/gogs/routers/repo"
|
||||
)
|
||||
|
||||
var (
|
||||
CmdHook = cli.Command{
|
||||
Name: "hook",
|
||||
Usage: "Delegate commands to corresponding Git hooks",
|
||||
Description: "All sub-commands should only be called by Git",
|
||||
Flags: []cli.Flag{
|
||||
stringFlag("config, c", "custom/conf/app.ini", "Custom configuration file path"),
|
||||
},
|
||||
Subcommands: []cli.Command{
|
||||
subcmdHookPreReceive,
|
||||
subcmdHookUpadte,
|
||||
subcmdHookPostReceive,
|
||||
},
|
||||
}
|
||||
|
||||
subcmdHookPreReceive = cli.Command{
|
||||
Name: "pre-receive",
|
||||
Usage: "Delegate pre-receive Git hook",
|
||||
Description: "This command should only be called by Git",
|
||||
Action: runHookPreReceive,
|
||||
}
|
||||
subcmdHookUpadte = cli.Command{
|
||||
Name: "update",
|
||||
Usage: "Delegate update Git hook",
|
||||
Description: "This command should only be called by Git",
|
||||
Action: runHookUpdate,
|
||||
}
|
||||
subcmdHookPostReceive = cli.Command{
|
||||
Name: "post-receive",
|
||||
Usage: "Delegate post-receive Git hook",
|
||||
Description: "This command should only be called by Git",
|
||||
Action: runHookPostReceive,
|
||||
}
|
||||
)
|
||||
|
||||
func runHookPreReceive(c *cli.Context) error {
|
||||
if len(os.Getenv("SSH_ORIGINAL_COMMAND")) == 0 {
|
||||
return nil
|
||||
}
|
||||
setup(c, "hooks/pre-receive.log", true)
|
||||
|
||||
isWiki := strings.Contains(os.Getenv(http.ENV_REPO_CUSTOM_HOOKS_PATH), ".wiki.git/")
|
||||
|
||||
buf := bytes.NewBuffer(nil)
|
||||
scanner := bufio.NewScanner(os.Stdin)
|
||||
for scanner.Scan() {
|
||||
buf.Write(scanner.Bytes())
|
||||
buf.WriteByte('\n')
|
||||
|
||||
if isWiki {
|
||||
continue
|
||||
}
|
||||
|
||||
fields := bytes.Fields(scanner.Bytes())
|
||||
if len(fields) != 3 {
|
||||
continue
|
||||
}
|
||||
oldCommitID := string(fields[0])
|
||||
newCommitID := string(fields[1])
|
||||
branchName := strings.TrimPrefix(string(fields[2]), git.BRANCH_PREFIX)
|
||||
|
||||
// Branch protection
|
||||
repoID := com.StrTo(os.Getenv(http.ENV_REPO_ID)).MustInt64()
|
||||
protectBranch, err := models.GetProtectBranchOfRepoByName(repoID, branchName)
|
||||
if err != nil {
|
||||
if models.IsErrBranchNotExist(err) {
|
||||
continue
|
||||
}
|
||||
fail("Internal error", "GetProtectBranchOfRepoByName [repo_id: %d, branch: %s]: %v", repoID, branchName, err)
|
||||
}
|
||||
if !protectBranch.Protected {
|
||||
continue
|
||||
}
|
||||
|
||||
// Check if whitelist is enabled
|
||||
userID := com.StrTo(os.Getenv(http.ENV_AUTH_USER_ID)).MustInt64()
|
||||
if protectBranch.EnableWhitelist && !models.IsUserInProtectBranchWhitelist(repoID, userID, branchName) {
|
||||
fail(fmt.Sprintf("Branch '%s' is protected and you are not in the push whitelist", branchName), "")
|
||||
}
|
||||
|
||||
// Check if branch allows direct push
|
||||
if protectBranch.RequirePullRequest {
|
||||
fail(fmt.Sprintf("Branch '%s' is protected and commits must be merged through pull request", branchName), "")
|
||||
}
|
||||
|
||||
// check and deletion
|
||||
if newCommitID == git.EMPTY_SHA {
|
||||
fail(fmt.Sprintf("Branch '%s' is protected from deletion", branchName), "")
|
||||
}
|
||||
|
||||
// Check force push
|
||||
output, err := git.NewCommand("rev-list", oldCommitID, "^"+newCommitID).Run()
|
||||
if err != nil {
|
||||
fail("Internal error", "Fail to detect force push: %v", err)
|
||||
} else if len(output) > 0 {
|
||||
fail(fmt.Sprintf("Branch '%s' is protected from force push", branchName), "")
|
||||
}
|
||||
}
|
||||
|
||||
customHooksPath := filepath.Join(os.Getenv(http.ENV_REPO_CUSTOM_HOOKS_PATH), "pre-receive")
|
||||
if !com.IsFile(customHooksPath) {
|
||||
return nil
|
||||
}
|
||||
|
||||
hookCmd := exec.Command(customHooksPath)
|
||||
hookCmd.Stdout = os.Stdout
|
||||
hookCmd.Stdin = buf
|
||||
hookCmd.Stderr = os.Stderr
|
||||
if err := hookCmd.Run(); err != nil {
|
||||
fail("Internal error", "Fail to execute custom pre-receive hook: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func runHookUpdate(c *cli.Context) error {
|
||||
if len(os.Getenv("SSH_ORIGINAL_COMMAND")) == 0 {
|
||||
return nil
|
||||
}
|
||||
setup(c, "hooks/update.log", false)
|
||||
|
||||
args := c.Args()
|
||||
if len(args) != 3 {
|
||||
fail("Arguments received are not equal to three", "Arguments received are not equal to three")
|
||||
} else if len(args[0]) == 0 {
|
||||
fail("First argument 'refName' is empty", "First argument 'refName' is empty")
|
||||
}
|
||||
|
||||
customHooksPath := filepath.Join(os.Getenv(http.ENV_REPO_CUSTOM_HOOKS_PATH), "update")
|
||||
if !com.IsFile(customHooksPath) {
|
||||
return nil
|
||||
}
|
||||
|
||||
hookCmd := exec.Command(customHooksPath, args...)
|
||||
hookCmd.Stdout = os.Stdout
|
||||
hookCmd.Stdin = os.Stdin
|
||||
hookCmd.Stderr = os.Stderr
|
||||
if err := hookCmd.Run(); err != nil {
|
||||
fail("Internal error", "Fail to execute custom pre-receive hook: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func runHookPostReceive(c *cli.Context) error {
|
||||
if len(os.Getenv("SSH_ORIGINAL_COMMAND")) == 0 {
|
||||
return nil
|
||||
}
|
||||
setup(c, "hooks/post-receive.log", true)
|
||||
|
||||
isWiki := strings.Contains(os.Getenv(http.ENV_REPO_CUSTOM_HOOKS_PATH), ".wiki.git/")
|
||||
|
||||
buf := bytes.NewBuffer(nil)
|
||||
scanner := bufio.NewScanner(os.Stdin)
|
||||
for scanner.Scan() {
|
||||
buf.Write(scanner.Bytes())
|
||||
buf.WriteByte('\n')
|
||||
|
||||
// TODO: support news feeds for wiki
|
||||
if isWiki {
|
||||
continue
|
||||
}
|
||||
|
||||
fields := bytes.Fields(scanner.Bytes())
|
||||
if len(fields) != 3 {
|
||||
continue
|
||||
}
|
||||
|
||||
options := models.PushUpdateOptions{
|
||||
OldCommitID: string(fields[0]),
|
||||
NewCommitID: string(fields[1]),
|
||||
RefFullName: string(fields[2]),
|
||||
PusherID: com.StrTo(os.Getenv(http.ENV_AUTH_USER_ID)).MustInt64(),
|
||||
PusherName: os.Getenv(http.ENV_AUTH_USER_NAME),
|
||||
RepoUserName: os.Getenv(http.ENV_REPO_OWNER_NAME),
|
||||
RepoName: os.Getenv(http.ENV_REPO_NAME),
|
||||
}
|
||||
if err := models.PushUpdate(options); err != nil {
|
||||
log.Error(2, "PushUpdate: %v", err)
|
||||
}
|
||||
|
||||
// Ask for running deliver hook and test pull request tasks.
|
||||
reqURL := setting.LocalURL + options.RepoUserName + "/" + options.RepoName + "/tasks/trigger?branch=" +
|
||||
strings.TrimPrefix(options.RefFullName, git.BRANCH_PREFIX) +
|
||||
"&secret=" + os.Getenv(http.ENV_REPO_OWNER_SALT_MD5) +
|
||||
"&pusher=" + os.Getenv(http.ENV_AUTH_USER_ID)
|
||||
log.Trace("Trigger task: %s", reqURL)
|
||||
|
||||
resp, err := httplib.Head(reqURL).SetTLSClientConfig(&tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
}).Response()
|
||||
if err == nil {
|
||||
resp.Body.Close()
|
||||
if resp.StatusCode/100 != 2 {
|
||||
log.Error(2, "Fail to trigger task: not 2xx response code")
|
||||
}
|
||||
} else {
|
||||
log.Error(2, "Fail to trigger task: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
customHooksPath := filepath.Join(os.Getenv(http.ENV_REPO_CUSTOM_HOOKS_PATH), "post-receive")
|
||||
if !com.IsFile(customHooksPath) {
|
||||
return nil
|
||||
}
|
||||
|
||||
hookCmd := exec.Command(customHooksPath)
|
||||
hookCmd.Stdout = os.Stdout
|
||||
hookCmd.Stdin = buf
|
||||
hookCmd.Stderr = os.Stderr
|
||||
if err := hookCmd.Run(); err != nil {
|
||||
fail("Internal error", "Fail to execute custom post-receive hook: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
113
cmd/import.go
Normal file
113
cmd/import.go
Normal file
@@ -0,0 +1,113 @@
|
||||
// Copyright 2016 The Gogs Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/Unknwon/com"
|
||||
"github.com/urfave/cli"
|
||||
|
||||
"github.com/gogits/gogs/modules/setting"
|
||||
)
|
||||
|
||||
var (
|
||||
CmdImport = cli.Command{
|
||||
Name: "import",
|
||||
Usage: "Import portable data as local Gogs data",
|
||||
Description: `Allow user import data from other Gogs installations to local instance
|
||||
without manually hacking the data files`,
|
||||
Subcommands: []cli.Command{
|
||||
subcmdImportLocale,
|
||||
},
|
||||
}
|
||||
|
||||
subcmdImportLocale = cli.Command{
|
||||
Name: "locale",
|
||||
Usage: "Import locale files to local repository",
|
||||
Action: runImportLocale,
|
||||
Flags: []cli.Flag{
|
||||
stringFlag("source", "", "Source directory that stores new locale files"),
|
||||
stringFlag("target", "", "Target directory that stores old locale files"),
|
||||
stringFlag("config, c", "custom/conf/app.ini", "Custom configuration file path"),
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
func runImportLocale(c *cli.Context) error {
|
||||
if !c.IsSet("source") {
|
||||
return fmt.Errorf("Source directory is not specified")
|
||||
} else if !c.IsSet("target") {
|
||||
return fmt.Errorf("Target directory is not specified")
|
||||
}
|
||||
if !com.IsDir(c.String("source")) {
|
||||
return fmt.Errorf("Source directory does not exist or is not a directory")
|
||||
} else if !com.IsDir(c.String("target")) {
|
||||
return fmt.Errorf("Target directory does not exist or is not a directory")
|
||||
}
|
||||
|
||||
if c.IsSet("config") {
|
||||
setting.CustomConf = c.String("config")
|
||||
}
|
||||
|
||||
setting.NewContext()
|
||||
|
||||
now := time.Now()
|
||||
|
||||
line := make([]byte, 0, 100)
|
||||
badChars := []byte(`="`)
|
||||
escapedQuotes := []byte(`\"`)
|
||||
regularQuotes := []byte(`"`)
|
||||
// Cut out en-US.
|
||||
for _, lang := range setting.Langs[1:] {
|
||||
name := fmt.Sprintf("locale_%s.ini", lang)
|
||||
source := filepath.Join(c.String("source"), name)
|
||||
target := filepath.Join(c.String("target"), name)
|
||||
if !com.IsFile(source) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Crowdin surrounds double quotes for strings contain quotes inside,
|
||||
// this breaks INI parser, we need to fix that.
|
||||
sr, err := os.Open(source)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Open: %v", err)
|
||||
}
|
||||
|
||||
tw, err := os.Create(target)
|
||||
if err != nil {
|
||||
if err != nil {
|
||||
return fmt.Errorf("Open: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
scanner := bufio.NewScanner(sr)
|
||||
for scanner.Scan() {
|
||||
line = scanner.Bytes()
|
||||
idx := bytes.Index(line, badChars)
|
||||
if idx > -1 && line[len(line)-1] == '"' {
|
||||
// We still want the "=" sign
|
||||
line = append(line[:idx+1], line[idx+2:len(line)-1]...)
|
||||
line = bytes.Replace(line, escapedQuotes, regularQuotes, -1)
|
||||
}
|
||||
tw.Write(line)
|
||||
tw.WriteString("\n")
|
||||
}
|
||||
sr.Close()
|
||||
tw.Close()
|
||||
|
||||
// Modification time of files from Crowdin often ahead of current,
|
||||
// so we need to set back to current.
|
||||
os.Chtimes(target, now, now)
|
||||
}
|
||||
|
||||
fmt.Println("Locale files has been successfully imported!")
|
||||
return nil
|
||||
}
|
||||
272
cmd/serv.go
Normal file
272
cmd/serv.go
Normal file
@@ -0,0 +1,272 @@
|
||||
// Copyright 2014 The Gogs Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Unknwon/com"
|
||||
"github.com/urfave/cli"
|
||||
log "gopkg.in/clog.v1"
|
||||
|
||||
"github.com/gogits/gogs/models"
|
||||
"github.com/gogits/gogs/modules/setting"
|
||||
http "github.com/gogits/gogs/routers/repo"
|
||||
)
|
||||
|
||||
const (
|
||||
_ACCESS_DENIED_MESSAGE = "Repository does not exist or you do not have access"
|
||||
)
|
||||
|
||||
var Serv = cli.Command{
|
||||
Name: "serv",
|
||||
Usage: "This command should only be called by SSH shell",
|
||||
Description: `Serv provide access auth for repositories`,
|
||||
Action: runServ,
|
||||
Flags: []cli.Flag{
|
||||
stringFlag("config, c", "custom/conf/app.ini", "Custom configuration file path"),
|
||||
},
|
||||
}
|
||||
|
||||
func fail(userMessage, logMessage string, args ...interface{}) {
|
||||
fmt.Fprintln(os.Stderr, "Gogs:", userMessage)
|
||||
|
||||
if len(logMessage) > 0 {
|
||||
if !setting.ProdMode {
|
||||
fmt.Fprintf(os.Stderr, logMessage+"\n", args...)
|
||||
}
|
||||
log.Fatal(3, logMessage, args...)
|
||||
}
|
||||
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
func setup(c *cli.Context, logPath string, connectDB bool) {
|
||||
if c.IsSet("config") {
|
||||
setting.CustomConf = c.String("config")
|
||||
} else if c.GlobalIsSet("config") {
|
||||
setting.CustomConf = c.GlobalString("config")
|
||||
}
|
||||
|
||||
setting.NewContext()
|
||||
|
||||
level := log.TRACE
|
||||
if setting.ProdMode {
|
||||
level = log.ERROR
|
||||
}
|
||||
log.New(log.FILE, log.FileConfig{
|
||||
Level: level,
|
||||
Filename: filepath.Join(setting.LogRootPath, logPath),
|
||||
FileRotationConfig: log.FileRotationConfig{
|
||||
Rotate: true,
|
||||
Daily: true,
|
||||
MaxDays: 3,
|
||||
},
|
||||
})
|
||||
log.Delete(log.CONSOLE) // Remove primary logger
|
||||
|
||||
if !connectDB {
|
||||
return
|
||||
}
|
||||
|
||||
models.LoadConfigs()
|
||||
|
||||
if setting.UseSQLite3 {
|
||||
workDir, _ := setting.WorkDir()
|
||||
os.Chdir(workDir)
|
||||
}
|
||||
|
||||
if err := models.SetEngine(); err != nil {
|
||||
fail("Internal error", "SetEngine: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func parseSSHCmd(cmd string) (string, string) {
|
||||
ss := strings.SplitN(cmd, " ", 2)
|
||||
if len(ss) != 2 {
|
||||
return "", ""
|
||||
}
|
||||
return ss[0], strings.Replace(ss[1], "'/", "'", 1)
|
||||
}
|
||||
|
||||
func checkDeployKey(key *models.PublicKey, repo *models.Repository) {
|
||||
// Check if this deploy key belongs to current repository.
|
||||
if !models.HasDeployKey(key.ID, repo.ID) {
|
||||
fail("Key access denied", "Deploy key access denied: [key_id: %d, repo_id: %d]", key.ID, repo.ID)
|
||||
}
|
||||
|
||||
// Update deploy key activity.
|
||||
deployKey, err := models.GetDeployKeyByRepo(key.ID, repo.ID)
|
||||
if err != nil {
|
||||
fail("Internal error", "GetDeployKey: %v", err)
|
||||
}
|
||||
|
||||
deployKey.Updated = time.Now()
|
||||
if err = models.UpdateDeployKey(deployKey); err != nil {
|
||||
fail("Internal error", "UpdateDeployKey: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
allowedCommands = map[string]models.AccessMode{
|
||||
"git-upload-pack": models.ACCESS_MODE_READ,
|
||||
"git-upload-archive": models.ACCESS_MODE_READ,
|
||||
"git-receive-pack": models.ACCESS_MODE_WRITE,
|
||||
}
|
||||
)
|
||||
|
||||
func runServ(c *cli.Context) error {
|
||||
setup(c, "serv.log", true)
|
||||
|
||||
if setting.SSH.Disabled {
|
||||
println("Gogs: SSH has been disabled")
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(c.Args()) < 1 {
|
||||
fail("Not enough arguments", "Not enough arguments")
|
||||
}
|
||||
|
||||
sshCmd := os.Getenv("SSH_ORIGINAL_COMMAND")
|
||||
if len(sshCmd) == 0 {
|
||||
println("Hi there, You've successfully authenticated, but Gogs does not provide shell access.")
|
||||
println("If this is unexpected, please log in with password and setup Gogs under another user.")
|
||||
return nil
|
||||
}
|
||||
|
||||
verb, args := parseSSHCmd(sshCmd)
|
||||
repoFullName := strings.ToLower(strings.Trim(args, "'"))
|
||||
repoFields := strings.SplitN(repoFullName, "/", 2)
|
||||
if len(repoFields) != 2 {
|
||||
fail("Invalid repository path", "Invalid repository path: %v", args)
|
||||
}
|
||||
ownerName := strings.ToLower(repoFields[0])
|
||||
repoName := strings.TrimSuffix(strings.ToLower(repoFields[1]), ".git")
|
||||
repoName = strings.TrimSuffix(repoName, ".wiki")
|
||||
|
||||
owner, err := models.GetUserByName(ownerName)
|
||||
if err != nil {
|
||||
if models.IsErrUserNotExist(err) {
|
||||
fail("Repository owner does not exist", "Unregistered owner: %s", ownerName)
|
||||
}
|
||||
fail("Internal error", "Fail to get repository owner '%s': %v", ownerName, err)
|
||||
}
|
||||
|
||||
repo, err := models.GetRepositoryByName(owner.ID, repoName)
|
||||
if err != nil {
|
||||
if models.IsErrRepoNotExist(err) {
|
||||
fail(_ACCESS_DENIED_MESSAGE, "Repository does not exist: %s/%s", owner.Name, repoName)
|
||||
}
|
||||
fail("Internal error", "Fail to get repository: %v", err)
|
||||
}
|
||||
repo.Owner = owner
|
||||
|
||||
requestMode, ok := allowedCommands[verb]
|
||||
if !ok {
|
||||
fail("Unknown git command", "Unknown git command '%s'", verb)
|
||||
}
|
||||
|
||||
// Prohibit push to mirror repositories.
|
||||
if requestMode > models.ACCESS_MODE_READ && repo.IsMirror {
|
||||
fail("Mirror repository is read-only", "")
|
||||
}
|
||||
|
||||
// Allow anonymous (user is nil) clone for public repositories.
|
||||
var user *models.User
|
||||
|
||||
key, err := models.GetPublicKeyByID(com.StrTo(strings.TrimPrefix(c.Args()[0], "key-")).MustInt64())
|
||||
if err != nil {
|
||||
fail("Invalid key ID", "Invalid key ID '%s': %v", c.Args()[0], err)
|
||||
}
|
||||
|
||||
if requestMode == models.ACCESS_MODE_WRITE || repo.IsPrivate {
|
||||
// Check deploy key or user key.
|
||||
if key.IsDeployKey() {
|
||||
if key.Mode < requestMode {
|
||||
fail("Key permission denied", "Cannot push with deployment key: %d", key.ID)
|
||||
}
|
||||
checkDeployKey(key, repo)
|
||||
} else {
|
||||
user, err = models.GetUserByKeyID(key.ID)
|
||||
if err != nil {
|
||||
fail("Internal error", "Fail to get user by key ID '%d': %v", key.ID, err)
|
||||
}
|
||||
|
||||
mode, err := models.AccessLevel(user.ID, repo)
|
||||
if err != nil {
|
||||
fail("Internal error", "Fail to check access: %v", err)
|
||||
}
|
||||
|
||||
if mode < requestMode {
|
||||
clientMessage := _ACCESS_DENIED_MESSAGE
|
||||
if mode >= models.ACCESS_MODE_READ {
|
||||
clientMessage = "You do not have sufficient authorization for this action"
|
||||
}
|
||||
fail(clientMessage,
|
||||
"User '%s' does not have level '%v' access to repository '%s'",
|
||||
user.Name, requestMode, repoFullName)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
setting.NewService()
|
||||
// Check if the key can access to the repository in case of it is a deploy key (a deploy keys != user key).
|
||||
// A deploy key doesn't represent a signed in user, so in a site with Service.RequireSignInView activated
|
||||
// we should give read access only in repositories where this deploy key is in use. In other case, a server
|
||||
// or system using an active deploy key can get read access to all the repositories in a Gogs service.
|
||||
if key.IsDeployKey() && setting.Service.RequireSignInView {
|
||||
checkDeployKey(key, repo)
|
||||
}
|
||||
}
|
||||
|
||||
// Update user key activity.
|
||||
if key.ID > 0 {
|
||||
key, err := models.GetPublicKeyByID(key.ID)
|
||||
if err != nil {
|
||||
fail("Internal error", "GetPublicKeyByID: %v", err)
|
||||
}
|
||||
|
||||
key.Updated = time.Now()
|
||||
if err = models.UpdatePublicKey(key); err != nil {
|
||||
fail("Internal error", "UpdatePublicKey: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Special handle for Windows.
|
||||
if setting.IsWindows {
|
||||
verb = strings.Replace(verb, "-", " ", 1)
|
||||
}
|
||||
|
||||
var gitCmd *exec.Cmd
|
||||
verbs := strings.Split(verb, " ")
|
||||
if len(verbs) == 2 {
|
||||
gitCmd = exec.Command(verbs[0], verbs[1], repoFullName)
|
||||
} else {
|
||||
gitCmd = exec.Command(verb, repoFullName)
|
||||
}
|
||||
if requestMode == models.ACCESS_MODE_WRITE {
|
||||
gitCmd.Env = append(os.Environ(), http.ComposeHookEnvs(http.ComposeHookEnvsOptions{
|
||||
AuthUser: user,
|
||||
OwnerName: owner.Name,
|
||||
OwnerSalt: owner.Salt,
|
||||
RepoID: repo.ID,
|
||||
RepoName: repo.Name,
|
||||
RepoPath: repo.RepoPath(),
|
||||
})...)
|
||||
}
|
||||
gitCmd.Dir = setting.RepoRootPath
|
||||
gitCmd.Stdout = os.Stdout
|
||||
gitCmd.Stdin = os.Stdin
|
||||
gitCmd.Stderr = os.Stderr
|
||||
if err = gitCmd.Run(); err != nil {
|
||||
fail("Internal error", "Fail to execute git command: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
239
cmd/serve.go
239
cmd/serve.go
@@ -1,239 +0,0 @@
|
||||
// Copyright 2014 The Gogs Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Unknwon/com"
|
||||
"github.com/codegangsta/cli"
|
||||
|
||||
"github.com/gogits/gogs/models"
|
||||
"github.com/gogits/gogs/modules/httplib"
|
||||
"github.com/gogits/gogs/modules/log"
|
||||
"github.com/gogits/gogs/modules/setting"
|
||||
"github.com/gogits/gogs/modules/uuid"
|
||||
)
|
||||
|
||||
const (
|
||||
_ACCESS_DENIED_MESSAGE = "Repository does not exist or you do not have access"
|
||||
)
|
||||
|
||||
var CmdServ = cli.Command{
|
||||
Name: "serv",
|
||||
Usage: "This command should only be called by SSH shell",
|
||||
Description: `Serv provide access auth for repositories`,
|
||||
Action: runServ,
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{"config, c", "custom/conf/app.ini", "Custom configuration file path", ""},
|
||||
},
|
||||
}
|
||||
|
||||
func setup(logPath string) {
|
||||
setting.NewConfigContext()
|
||||
log.NewGitLogger(filepath.Join(setting.LogRootPath, logPath))
|
||||
|
||||
if setting.DisableSSH {
|
||||
println("Gogs: SSH has been disabled")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
models.LoadModelsConfig()
|
||||
|
||||
if setting.UseSQLite3 {
|
||||
workDir, _ := setting.WorkDir()
|
||||
os.Chdir(workDir)
|
||||
}
|
||||
|
||||
models.SetEngine()
|
||||
}
|
||||
|
||||
func parseCmd(cmd string) (string, string) {
|
||||
ss := strings.SplitN(cmd, " ", 2)
|
||||
if len(ss) != 2 {
|
||||
return "", ""
|
||||
}
|
||||
return ss[0], strings.Replace(ss[1], "'/", "'", 1)
|
||||
}
|
||||
|
||||
var (
|
||||
COMMANDS = map[string]models.AccessMode{
|
||||
"git-upload-pack": models.ACCESS_MODE_READ,
|
||||
"git-upload-archive": models.ACCESS_MODE_READ,
|
||||
"git-receive-pack": models.ACCESS_MODE_WRITE,
|
||||
}
|
||||
)
|
||||
|
||||
func fail(userMessage, logMessage string, args ...interface{}) {
|
||||
fmt.Fprintln(os.Stderr, "Gogs:", userMessage)
|
||||
log.GitLogger.Fatal(3, logMessage, args...)
|
||||
}
|
||||
|
||||
func runServ(c *cli.Context) {
|
||||
if c.IsSet("config") {
|
||||
setting.CustomConf = c.String("config")
|
||||
}
|
||||
setup("serv.log")
|
||||
|
||||
if len(c.Args()) < 1 {
|
||||
fail("Not enough arguments", "Not enough arguments")
|
||||
}
|
||||
|
||||
cmd := os.Getenv("SSH_ORIGINAL_COMMAND")
|
||||
if len(cmd) == 0 {
|
||||
println("Hi there, You've successfully authenticated, but Gogs does not provide shell access.")
|
||||
println("If this is unexpected, please log in with password and setup Gogs under another user.")
|
||||
return
|
||||
}
|
||||
|
||||
verb, args := parseCmd(cmd)
|
||||
repoPath := strings.Trim(args, "'")
|
||||
rr := strings.SplitN(repoPath, "/", 2)
|
||||
if len(rr) != 2 {
|
||||
fail("Invalid repository path", "Invalid repository path: %v", args)
|
||||
}
|
||||
repoUserName := rr[0]
|
||||
repoName := strings.TrimSuffix(rr[1], ".git")
|
||||
|
||||
repoUser, err := models.GetUserByName(repoUserName)
|
||||
if err != nil {
|
||||
if models.IsErrUserNotExist(err) {
|
||||
fail("Repository owner does not exist", "Unregistered owner: %s", repoUserName)
|
||||
}
|
||||
fail("Internal error", "Failed to get repository owner(%s): %v", repoUserName, err)
|
||||
}
|
||||
|
||||
repo, err := models.GetRepositoryByName(repoUser.Id, repoName)
|
||||
if err != nil {
|
||||
if models.IsErrRepoNotExist(err) {
|
||||
fail(_ACCESS_DENIED_MESSAGE, "Repository does not exist: %s/%s", repoUser.Name, repoName)
|
||||
}
|
||||
fail("Internal error", "Failed to get repository: %v", err)
|
||||
}
|
||||
|
||||
requestedMode, has := COMMANDS[verb]
|
||||
if !has {
|
||||
fail("Unknown git command", "Unknown git command %s", verb)
|
||||
}
|
||||
|
||||
// Allow anonymous clone for public repositories.
|
||||
var (
|
||||
keyID int64
|
||||
user *models.User
|
||||
)
|
||||
if requestedMode == models.ACCESS_MODE_WRITE || repo.IsPrivate {
|
||||
keys := strings.Split(c.Args()[0], "-")
|
||||
if len(keys) != 2 {
|
||||
fail("Key ID format error", "Invalid key ID: %s", c.Args()[0])
|
||||
}
|
||||
|
||||
key, err := models.GetPublicKeyByID(com.StrTo(keys[1]).MustInt64())
|
||||
if err != nil {
|
||||
fail("Key ID format error", "Invalid key ID[%s]: %v", c.Args()[0], err)
|
||||
}
|
||||
keyID = key.ID
|
||||
|
||||
// Check deploy key or user key.
|
||||
if key.Type == models.KEY_TYPE_DEPLOY {
|
||||
if key.Mode < requestedMode {
|
||||
fail("Key permission denied", "Cannot push with deployment key: %d", key.ID)
|
||||
}
|
||||
// Check if this deploy key belongs to current repository.
|
||||
if !models.HasDeployKey(key.ID, repo.ID) {
|
||||
fail("Key access denied", "Key access denied: %d-%d", key.ID, repo.ID)
|
||||
}
|
||||
|
||||
// Update deploy key activity.
|
||||
deployKey, err := models.GetDeployKeyByRepo(key.ID, repo.ID)
|
||||
if err != nil {
|
||||
fail("Internal error", "GetDeployKey: %v", err)
|
||||
}
|
||||
|
||||
deployKey.Updated = time.Now()
|
||||
if err = models.UpdateDeployKey(deployKey); err != nil {
|
||||
fail("Internal error", "UpdateDeployKey: %v", err)
|
||||
}
|
||||
} else {
|
||||
user, err = models.GetUserByKeyId(key.ID)
|
||||
if err != nil {
|
||||
fail("internal error", "Failed to get user by key ID(%d): %v", keyID, err)
|
||||
}
|
||||
|
||||
mode, err := models.AccessLevel(user, repo)
|
||||
if err != nil {
|
||||
fail("Internal error", "Fail to check access: %v", err)
|
||||
} else if mode < requestedMode {
|
||||
clientMessage := _ACCESS_DENIED_MESSAGE
|
||||
if mode >= models.ACCESS_MODE_READ {
|
||||
clientMessage = "You do not have sufficient authorization for this action"
|
||||
}
|
||||
fail(clientMessage,
|
||||
"User %s does not have level %v access to repository %s",
|
||||
user.Name, requestedMode, repoPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uuid := uuid.NewV4().String()
|
||||
os.Setenv("uuid", uuid)
|
||||
|
||||
var gitcmd *exec.Cmd
|
||||
verbs := strings.Split(verb, " ")
|
||||
if len(verbs) == 2 {
|
||||
gitcmd = exec.Command(verbs[0], verbs[1], repoPath)
|
||||
} else {
|
||||
gitcmd = exec.Command(verb, repoPath)
|
||||
}
|
||||
gitcmd.Dir = setting.RepoRootPath
|
||||
gitcmd.Stdout = os.Stdout
|
||||
gitcmd.Stdin = os.Stdin
|
||||
gitcmd.Stderr = os.Stderr
|
||||
if err = gitcmd.Run(); err != nil {
|
||||
fail("Internal error", "Failed to execute git command: %v", err)
|
||||
}
|
||||
|
||||
if requestedMode == models.ACCESS_MODE_WRITE {
|
||||
tasks, err := models.GetUpdateTasksByUuid(uuid)
|
||||
if err != nil {
|
||||
log.GitLogger.Fatal(2, "GetUpdateTasksByUuid: %v", err)
|
||||
}
|
||||
|
||||
for _, task := range tasks {
|
||||
err = models.Update(task.RefName, task.OldCommitId, task.NewCommitId,
|
||||
user.Name, repoUserName, repoName, user.Id)
|
||||
if err != nil {
|
||||
log.GitLogger.Error(2, "Failed to update: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if err = models.DelUpdateTasksByUuid(uuid); err != nil {
|
||||
log.GitLogger.Fatal(2, "DelUpdateTasksByUuid: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Send deliver hook request.
|
||||
resp, err := httplib.Head(setting.AppUrl + setting.AppSubUrl + repoUserName + "/" + repoName + "/hooks/trigger").Response()
|
||||
if err == nil {
|
||||
resp.Body.Close()
|
||||
}
|
||||
|
||||
// Update user key activity.
|
||||
if keyID > 0 {
|
||||
key, err := models.GetPublicKeyByID(keyID)
|
||||
if err != nil {
|
||||
fail("Internal error", "GetPublicKeyById: %v", err)
|
||||
}
|
||||
|
||||
key.Updated = time.Now()
|
||||
if err = models.UpdatePublicKey(key); err != nil {
|
||||
fail("Internal error", "UpdatePublicKey: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
// Copyright 2014 The Gogs Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
|
||||
"github.com/gogits/gogs/models"
|
||||
"github.com/gogits/gogs/modules/log"
|
||||
"github.com/gogits/gogs/modules/setting"
|
||||
)
|
||||
|
||||
var CmdUpdate = cli.Command{
|
||||
Name: "update",
|
||||
Usage: "This command should only be called by SSH shell",
|
||||
Description: `Update get pushed info and insert into database`,
|
||||
Action: runUpdate,
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{"config, c", "custom/conf/app.ini", "Custom configuration file path", ""},
|
||||
},
|
||||
}
|
||||
|
||||
func runUpdate(c *cli.Context) {
|
||||
if c.IsSet("config") {
|
||||
setting.CustomConf = c.String("config")
|
||||
}
|
||||
cmd := os.Getenv("SSH_ORIGINAL_COMMAND")
|
||||
if cmd == "" {
|
||||
return
|
||||
}
|
||||
|
||||
setup("update.log")
|
||||
|
||||
args := c.Args()
|
||||
if len(args) != 3 {
|
||||
log.GitLogger.Fatal(2, "received less 3 parameters")
|
||||
} else if args[0] == "" {
|
||||
log.GitLogger.Fatal(2, "refName is empty, shouldn't use")
|
||||
}
|
||||
|
||||
uuid := os.Getenv("uuid")
|
||||
|
||||
task := models.UpdateTask{
|
||||
Uuid: uuid,
|
||||
RefName: args[0],
|
||||
OldCommitId: args[1],
|
||||
NewCommitId: args[2],
|
||||
}
|
||||
|
||||
if err := models.AddUpdateTask(&task); err != nil {
|
||||
log.GitLogger.Fatal(2, err.Error())
|
||||
}
|
||||
}
|
||||
496
cmd/web.go
496
cmd/web.go
@@ -7,42 +7,42 @@ package cmd
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/fcgi"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/Unknwon/macaron"
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/go-macaron/binding"
|
||||
"github.com/go-macaron/cache"
|
||||
"github.com/go-macaron/captcha"
|
||||
"github.com/go-macaron/csrf"
|
||||
"github.com/go-macaron/gzip"
|
||||
"github.com/go-macaron/i18n"
|
||||
"github.com/go-macaron/session"
|
||||
"github.com/go-macaron/toolbox"
|
||||
"github.com/go-xorm/xorm"
|
||||
"github.com/macaron-contrib/binding"
|
||||
"github.com/macaron-contrib/cache"
|
||||
"github.com/macaron-contrib/captcha"
|
||||
"github.com/macaron-contrib/csrf"
|
||||
"github.com/macaron-contrib/i18n"
|
||||
"github.com/macaron-contrib/oauth2"
|
||||
"github.com/macaron-contrib/session"
|
||||
"github.com/macaron-contrib/toolbox"
|
||||
"github.com/mcuadros/go-version"
|
||||
"github.com/urfave/cli"
|
||||
log "gopkg.in/clog.v1"
|
||||
"gopkg.in/ini.v1"
|
||||
"gopkg.in/macaron.v1"
|
||||
|
||||
api "github.com/gogits/go-gogs-client"
|
||||
"github.com/gogits/git-module"
|
||||
"github.com/gogits/go-gogs-client"
|
||||
|
||||
"github.com/gogits/gogs/models"
|
||||
"github.com/gogits/gogs/modules/auth"
|
||||
"github.com/gogits/gogs/modules/auth/apiv1"
|
||||
"github.com/gogits/gogs/modules/avatar"
|
||||
"github.com/gogits/gogs/modules/base"
|
||||
"github.com/gogits/gogs/modules/bindata"
|
||||
"github.com/gogits/gogs/modules/log"
|
||||
"github.com/gogits/gogs/modules/middleware"
|
||||
"github.com/gogits/gogs/modules/context"
|
||||
"github.com/gogits/gogs/modules/mailer"
|
||||
"github.com/gogits/gogs/modules/setting"
|
||||
"github.com/gogits/gogs/modules/template"
|
||||
"github.com/gogits/gogs/routers"
|
||||
"github.com/gogits/gogs/routers/admin"
|
||||
"github.com/gogits/gogs/routers/api/v1"
|
||||
apiv1 "github.com/gogits/gogs/routers/api/v1"
|
||||
"github.com/gogits/gogs/routers/dev"
|
||||
"github.com/gogits/gogs/routers/org"
|
||||
"github.com/gogits/gogs/routers/repo"
|
||||
@@ -56,8 +56,8 @@ var CmdWeb = cli.Command{
|
||||
and it takes care of all the other things for you`,
|
||||
Action: runWeb,
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{"port, p", "3000", "Temporary port number to prevent conflict", ""},
|
||||
cli.StringFlag{"config, c", "custom/conf/app.ini", "Custom configuration file path", ""},
|
||||
stringFlag("port, p", "3000", "Temporary port number to prevent conflict"),
|
||||
stringFlag("config, c", "custom/conf/app.ini", "Custom configuration file path"),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -74,24 +74,36 @@ func checkVersion() {
|
||||
if err != nil {
|
||||
log.Fatal(4, "Fail to read 'templates/.VERSION': %v", err)
|
||||
}
|
||||
if string(data) != setting.AppVer {
|
||||
log.Fatal(4, "Binary and template file version does not match, did you forget to recompile?")
|
||||
tplVer := string(data)
|
||||
if tplVer != setting.AppVer {
|
||||
if version.Compare(tplVer, setting.AppVer, ">") {
|
||||
log.Fatal(4, "Binary version is lower than template file version, did you forget to recompile Gogs?")
|
||||
} else {
|
||||
log.Fatal(4, "Binary version is higher than template file version, did you forget to update template files?")
|
||||
}
|
||||
}
|
||||
|
||||
// Check dependency version.
|
||||
// LEGACY [0.11]: no need to check version as we check in vendor into version control
|
||||
checkers := []VerChecker{
|
||||
{"github.com/go-xorm/xorm", func() string { return xorm.Version }, "0.4.3.0806"},
|
||||
{"github.com/Unknwon/macaron", macaron.Version, "0.5.4"},
|
||||
{"github.com/macaron-contrib/binding", binding.Version, "0.1.0"},
|
||||
{"github.com/macaron-contrib/cache", cache.Version, "0.1.2"},
|
||||
{"github.com/macaron-contrib/csrf", csrf.Version, "0.0.3"},
|
||||
{"github.com/macaron-contrib/i18n", i18n.Version, "0.0.7"},
|
||||
{"github.com/macaron-contrib/session", session.Version, "0.1.6"},
|
||||
{"gopkg.in/ini.v1", ini.Version, "1.3.4"},
|
||||
{"github.com/go-xorm/xorm", func() string { return xorm.Version }, "0.6.0"},
|
||||
{"github.com/go-macaron/binding", binding.Version, "0.3.2"},
|
||||
{"github.com/go-macaron/cache", cache.Version, "0.1.2"},
|
||||
{"github.com/go-macaron/csrf", csrf.Version, "0.1.0"},
|
||||
{"github.com/go-macaron/i18n", i18n.Version, "0.3.0"},
|
||||
{"github.com/go-macaron/session", session.Version, "0.1.6"},
|
||||
{"github.com/go-macaron/toolbox", toolbox.Version, "0.1.3"},
|
||||
{"gopkg.in/ini.v1", ini.Version, "1.8.4"},
|
||||
{"gopkg.in/macaron.v1", macaron.Version, "1.1.7"},
|
||||
{"github.com/gogits/git-module", git.Version, "0.4.12"},
|
||||
{"github.com/gogits/go-gogs-client", gogs.Version, "0.12.1"},
|
||||
}
|
||||
for _, c := range checkers {
|
||||
if !version.Compare(c.Version(), c.Expected, ">=") {
|
||||
log.Fatal(4, "Package '%s' version is too old(%s -> %s), did you forget to update?", c.ImportPath, c.Version(), c.Expected)
|
||||
log.Fatal(4, `Dependency outdated!
|
||||
Package '%s' current version (%s) is below requirement (%s),
|
||||
please use following command to update this package and recompile Gogs:
|
||||
go get -u %[1]s`, c.ImportPath, c.Version(), c.Expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -104,9 +116,9 @@ func newMacaron() *macaron.Macaron {
|
||||
}
|
||||
m.Use(macaron.Recovery())
|
||||
if setting.EnableGzip {
|
||||
m.Use(macaron.Gziper())
|
||||
m.Use(gzip.Gziper())
|
||||
}
|
||||
if setting.Protocol == setting.FCGI {
|
||||
if setting.Protocol == setting.SCHEME_FCGI {
|
||||
m.SetURLPrefix(setting.AppSubUrl)
|
||||
}
|
||||
m.Use(macaron.Static(
|
||||
@@ -122,11 +134,16 @@ func newMacaron() *macaron.Macaron {
|
||||
SkipLogging: setting.DisableRouterLog,
|
||||
},
|
||||
))
|
||||
|
||||
funcMap := template.NewFuncMap()
|
||||
m.Use(macaron.Renderer(macaron.RenderOptions{
|
||||
Directory: path.Join(setting.StaticRootPath, "templates"),
|
||||
Funcs: []template.FuncMap{base.TemplateFuncs},
|
||||
IndentJSON: macaron.Env != macaron.PROD,
|
||||
Directory: path.Join(setting.StaticRootPath, "templates"),
|
||||
AppendDirectories: []string{path.Join(setting.CustomPath, "templates")},
|
||||
Funcs: funcMap,
|
||||
IndentJSON: macaron.Env != macaron.PROD,
|
||||
}))
|
||||
mailer.InitMailRender(path.Join(setting.StaticRootPath, "templates/mail"),
|
||||
path.Join(setting.CustomPath, "templates/mail"), funcMap)
|
||||
|
||||
localeNames, err := bindata.AssetDir("conf/locale")
|
||||
if err != nil {
|
||||
@@ -142,12 +159,13 @@ func newMacaron() *macaron.Macaron {
|
||||
CustomDirectory: path.Join(setting.CustomPath, "conf/locale"),
|
||||
Langs: setting.Langs,
|
||||
Names: setting.Names,
|
||||
DefaultLang: "en-US",
|
||||
Redirect: true,
|
||||
}))
|
||||
m.Use(cache.Cacher(cache.Options{
|
||||
Adapter: setting.CacheAdapter,
|
||||
AdapterConfig: setting.CacheConn,
|
||||
Interval: setting.CacheInternal,
|
||||
Interval: setting.CacheInterval,
|
||||
}))
|
||||
m.Use(captcha.Captchaer(captcha.Options{
|
||||
SubURL: setting.AppSubUrl,
|
||||
@@ -155,6 +173,7 @@ func newMacaron() *macaron.Macaron {
|
||||
m.Use(session.Sessioner(setting.SessionConfig))
|
||||
m.Use(csrf.Csrfer(csrf.Options{
|
||||
Secret: setting.SecretKey,
|
||||
Cookie: setting.CSRFCookieName,
|
||||
SetCookie: true,
|
||||
Header: "X-Csrf-Token",
|
||||
CookiePath: setting.AppSubUrl,
|
||||
@@ -167,18 +186,11 @@ func newMacaron() *macaron.Macaron {
|
||||
},
|
||||
},
|
||||
}))
|
||||
|
||||
// OAuth 2.
|
||||
if setting.OauthService != nil {
|
||||
for _, info := range setting.OauthService.OauthInfos {
|
||||
m.Use(oauth2.NewOAuth2Provider(info.Options, info.AuthUrl, info.TokenUrl))
|
||||
}
|
||||
}
|
||||
m.Use(middleware.Contexter())
|
||||
m.Use(context.Contexter())
|
||||
return m
|
||||
}
|
||||
|
||||
func runWeb(ctx *cli.Context) {
|
||||
func runWeb(ctx *cli.Context) error {
|
||||
if ctx.IsSet("config") {
|
||||
setting.CustomConf = ctx.String("config")
|
||||
}
|
||||
@@ -187,76 +199,33 @@ func runWeb(ctx *cli.Context) {
|
||||
|
||||
m := newMacaron()
|
||||
|
||||
reqSignIn := middleware.Toggle(&middleware.ToggleOptions{SignInRequire: true})
|
||||
ignSignIn := middleware.Toggle(&middleware.ToggleOptions{SignInRequire: setting.Service.RequireSignInView})
|
||||
ignSignInAndCsrf := middleware.Toggle(&middleware.ToggleOptions{DisableCsrf: true})
|
||||
reqSignOut := middleware.Toggle(&middleware.ToggleOptions{SignOutRequire: true})
|
||||
reqSignIn := context.Toggle(&context.ToggleOptions{SignInRequired: true})
|
||||
ignSignIn := context.Toggle(&context.ToggleOptions{SignInRequired: setting.Service.RequireSignInView})
|
||||
ignSignInAndCsrf := context.Toggle(&context.ToggleOptions{DisableCSRF: true})
|
||||
reqSignOut := context.Toggle(&context.ToggleOptions{SignOutRequired: true})
|
||||
|
||||
bind := binding.Bind
|
||||
bindIgnErr := binding.BindIgnErr
|
||||
|
||||
// FIXME: not all routes need go through same middlewares.
|
||||
// Especially some AJAX requests, we can reduce middleware number to improve performance.
|
||||
// Routers.
|
||||
m.Get("/", ignSignIn, routers.Home)
|
||||
m.Get("/explore", ignSignIn, routers.Explore)
|
||||
m.Group("/explore", func() {
|
||||
m.Get("", func(ctx *context.Context) {
|
||||
ctx.Redirect(setting.AppSubUrl + "/explore/repos")
|
||||
})
|
||||
m.Get("/repos", routers.ExploreRepos)
|
||||
m.Get("/users", routers.ExploreUsers)
|
||||
m.Get("/organizations", routers.ExploreOrganizations)
|
||||
}, ignSignIn)
|
||||
m.Combo("/install", routers.InstallInit).Get(routers.Install).
|
||||
Post(bindIgnErr(auth.InstallForm{}), routers.InstallPost)
|
||||
m.Get("/:type(issues|pulls)", reqSignIn, user.Issues)
|
||||
|
||||
// ***** START: API *****
|
||||
// FIXME: custom form error response.
|
||||
m.Group("/api", func() {
|
||||
m.Group("/v1", func() {
|
||||
// Miscellaneous.
|
||||
m.Post("/markdown", bindIgnErr(apiv1.MarkdownForm{}), v1.Markdown)
|
||||
m.Post("/markdown/raw", v1.MarkdownRaw)
|
||||
|
||||
// Users.
|
||||
m.Group("/users", func() {
|
||||
m.Get("/search", v1.SearchUsers)
|
||||
|
||||
m.Group("/:username", func() {
|
||||
m.Get("", v1.GetUserInfo)
|
||||
|
||||
m.Group("/tokens", func() {
|
||||
m.Combo("").Get(v1.ListAccessTokens).
|
||||
Post(bind(v1.CreateAccessTokenForm{}), v1.CreateAccessToken)
|
||||
}, middleware.ApiReqBasicAuth())
|
||||
})
|
||||
})
|
||||
|
||||
// Repositories.
|
||||
m.Combo("/user/repos", middleware.ApiReqToken()).Get(v1.ListMyRepos).
|
||||
Post(bind(api.CreateRepoOption{}), v1.CreateRepo)
|
||||
m.Post("/org/:org/repos", middleware.ApiReqToken(), bind(api.CreateRepoOption{}), v1.CreateOrgRepo)
|
||||
|
||||
m.Group("/repos", func() {
|
||||
m.Get("/search", v1.SearchRepos)
|
||||
|
||||
m.Group("", func() {
|
||||
m.Post("/migrate", bindIgnErr(auth.MigrateRepoForm{}), v1.MigrateRepo)
|
||||
}, middleware.ApiReqToken())
|
||||
|
||||
m.Group("/:username/:reponame", func() {
|
||||
m.Combo("/hooks").Get(v1.ListRepoHooks).
|
||||
Post(bind(api.CreateHookOption{}), v1.CreateRepoHook)
|
||||
m.Patch("/hooks/:id:int", bind(api.EditHookOption{}), v1.EditRepoHook)
|
||||
m.Get("/raw/*", middleware.RepoRef(), v1.GetRepoRawFile)
|
||||
m.Get("/archive/*", v1.GetRepoArchive)
|
||||
}, middleware.ApiRepoAssignment(), middleware.ApiReqToken())
|
||||
})
|
||||
|
||||
m.Any("/*", func(ctx *middleware.Context) {
|
||||
ctx.HandleAPI(404, "Page not found")
|
||||
})
|
||||
})
|
||||
}, ignSignIn)
|
||||
// ***** END: API *****
|
||||
m.Get("/^:type(issues|pulls)$", reqSignIn, user.Issues)
|
||||
|
||||
// ***** START: User *****
|
||||
m.Group("/user", func() {
|
||||
m.Get("/login", user.SignIn)
|
||||
m.Post("/login", bindIgnErr(auth.SignInForm{}), user.SignInPost)
|
||||
m.Get("/info/:name", user.SocialSignIn)
|
||||
m.Get("/sign_up", user.SignUp)
|
||||
m.Post("/sign_up", bindIgnErr(auth.RegisterForm{}), user.SignUpPost)
|
||||
m.Get("/reset_password", user.ResetPasswd)
|
||||
@@ -266,22 +235,29 @@ func runWeb(ctx *cli.Context) {
|
||||
m.Group("/user/settings", func() {
|
||||
m.Get("", user.Settings)
|
||||
m.Post("", bindIgnErr(auth.UpdateProfileForm{}), user.SettingsPost)
|
||||
m.Post("/avatar", binding.MultipartForm(auth.UploadAvatarForm{}), user.SettingsAvatar)
|
||||
m.Get("/email", user.SettingsEmails)
|
||||
m.Post("/email", bindIgnErr(auth.AddEmailForm{}), user.SettingsEmailPost)
|
||||
m.Combo("/avatar").Get(user.SettingsAvatar).
|
||||
Post(binding.MultipartForm(auth.AvatarForm{}), user.SettingsAvatarPost)
|
||||
m.Post("/avatar/delete", user.SettingsDeleteAvatar)
|
||||
m.Combo("/email").Get(user.SettingsEmails).
|
||||
Post(bindIgnErr(auth.AddEmailForm{}), user.SettingsEmailPost)
|
||||
m.Post("/email/delete", user.DeleteEmail)
|
||||
m.Get("/password", user.SettingsPassword)
|
||||
m.Post("/password", bindIgnErr(auth.ChangePasswordForm{}), user.SettingsPasswordPost)
|
||||
m.Combo("/ssh").Get(user.SettingsSSHKeys).
|
||||
Post(bindIgnErr(auth.AddSSHKeyForm{}), user.SettingsSSHKeysPost)
|
||||
m.Post("/ssh/delete", user.DeleteSSHKey)
|
||||
m.Get("/social", user.SettingsSocial)
|
||||
m.Combo("/applications").Get(user.SettingsApplications).
|
||||
Post(bindIgnErr(auth.NewAccessTokenForm{}), user.SettingsApplicationsPost)
|
||||
m.Post("/applications/delete", user.SettingsDeleteApplication)
|
||||
|
||||
m.Group("/organizations", func() {
|
||||
m.Get("", user.SettingsOrganizations)
|
||||
m.Post("/leave", user.SettingsLeaveOrganization)
|
||||
})
|
||||
|
||||
m.Route("/delete", "GET,POST", user.SettingsDelete)
|
||||
}, reqSignIn, func(ctx *middleware.Context) {
|
||||
}, reqSignIn, func(ctx *context.Context) {
|
||||
ctx.Data["PageIsUserSettings"] = true
|
||||
ctx.Data["HasOAuthService"] = setting.OauthService != nil
|
||||
})
|
||||
|
||||
m.Group("/user", func() {
|
||||
@@ -295,25 +271,19 @@ func runWeb(ctx *cli.Context) {
|
||||
})
|
||||
// ***** END: User *****
|
||||
|
||||
// Gravatar service.
|
||||
avt := avatar.CacheServer("public/img/avatar/", "public/img/avatar_default.jpg")
|
||||
os.MkdirAll("public/img/avatar/", os.ModePerm)
|
||||
m.Get("/avatar/:hash", avt.ServeHTTP)
|
||||
|
||||
adminReq := middleware.Toggle(&middleware.ToggleOptions{SignInRequire: true, AdminRequire: true})
|
||||
adminReq := context.Toggle(&context.ToggleOptions{SignInRequired: true, AdminRequired: true})
|
||||
|
||||
// ***** START: Admin *****
|
||||
m.Group("/admin", func() {
|
||||
m.Get("", adminReq, admin.Dashboard)
|
||||
m.Get("/config", admin.Config)
|
||||
m.Post("/config/test_mail", admin.SendTestMail)
|
||||
m.Get("/monitor", admin.Monitor)
|
||||
|
||||
m.Group("/users", func() {
|
||||
m.Get("", admin.Users)
|
||||
m.Get("/new", admin.NewUser)
|
||||
m.Post("/new", bindIgnErr(auth.RegisterForm{}), admin.NewUserPost)
|
||||
m.Get("/:userid", admin.EditUser)
|
||||
m.Post("/:userid", bindIgnErr(auth.AdminEditUserForm{}), admin.EditUserPost)
|
||||
m.Combo("/new").Get(admin.NewUser).Post(bindIgnErr(auth.AdminCrateUserForm{}), admin.NewUserPost)
|
||||
m.Combo("/:userid").Get(admin.EditUser).Post(bindIgnErr(auth.AdminEditUserForm{}), admin.EditUserPost)
|
||||
m.Post("/:userid/delete", admin.DeleteUser)
|
||||
})
|
||||
|
||||
@@ -322,28 +292,35 @@ func runWeb(ctx *cli.Context) {
|
||||
})
|
||||
|
||||
m.Group("/repos", func() {
|
||||
m.Get("", admin.Repositories)
|
||||
m.Get("", admin.Repos)
|
||||
m.Post("/delete", admin.DeleteRepo)
|
||||
})
|
||||
|
||||
m.Group("/auths", func() {
|
||||
m.Get("", admin.Authentications)
|
||||
m.Get("/new", admin.NewAuthSource)
|
||||
m.Post("/new", bindIgnErr(auth.AuthenticationForm{}), admin.NewAuthSourcePost)
|
||||
m.Get("/:authid", admin.EditAuthSource)
|
||||
m.Post("/:authid", bindIgnErr(auth.AuthenticationForm{}), admin.EditAuthSourcePost)
|
||||
m.Combo("/new").Get(admin.NewAuthSource).Post(bindIgnErr(auth.AuthenticationForm{}), admin.NewAuthSourcePost)
|
||||
m.Combo("/:authid").Get(admin.EditAuthSource).
|
||||
Post(bindIgnErr(auth.AuthenticationForm{}), admin.EditAuthSourcePost)
|
||||
m.Post("/:authid/delete", admin.DeleteAuthSource)
|
||||
})
|
||||
|
||||
m.Group("/notices", func() {
|
||||
m.Get("", admin.Notices)
|
||||
m.Get("/:id:int/delete", admin.DeleteNotice)
|
||||
m.Post("/delete", admin.DeleteNotices)
|
||||
m.Get("/empty", admin.EmptyNotices)
|
||||
})
|
||||
}, adminReq)
|
||||
// ***** END: Admin *****
|
||||
|
||||
m.Group("", func() {
|
||||
m.Get("/:username", user.Profile)
|
||||
m.Get("/attachments/:uuid", func(ctx *middleware.Context) {
|
||||
m.Group("/:username", func() {
|
||||
m.Get("", user.Profile)
|
||||
m.Get("/followers", user.Followers)
|
||||
m.Get("/following", user.Following)
|
||||
m.Get("/stars", user.Stars)
|
||||
})
|
||||
|
||||
m.Get("/attachments/:uuid", func(ctx *context.Context) {
|
||||
attach, err := models.GetAttachmentByUUID(ctx.Params(":uuid"))
|
||||
if err != nil {
|
||||
if models.IsErrAttachmentNotExist(err) {
|
||||
@@ -362,6 +339,7 @@ func runWeb(ctx *cli.Context) {
|
||||
defer fr.Close()
|
||||
|
||||
ctx.Header().Set("Cache-Control", "public,max-age=86400")
|
||||
ctx.Header().Set("Content-Disposition", fmt.Sprintf(`inline; filename="%s"`, attach.Name))
|
||||
// Fix #312. Attachments with , in their name are not handled correctly by Google Chrome.
|
||||
// We must put the name in " manually.
|
||||
if err = repo.ServeData(ctx, "\""+attach.Name+"\"", fr); err != nil {
|
||||
@@ -372,29 +350,43 @@ func runWeb(ctx *cli.Context) {
|
||||
m.Post("/issues/attachments", repo.UploadIssueAttachment)
|
||||
}, ignSignIn)
|
||||
|
||||
m.Group("/:username", func() {
|
||||
m.Get("/action/:action", user.Action)
|
||||
}, reqSignIn)
|
||||
|
||||
if macaron.Env == macaron.DEV {
|
||||
m.Get("/template/*", dev.TemplatePreview)
|
||||
}
|
||||
|
||||
reqRepoAdmin := middleware.RequireRepoAdmin()
|
||||
reqRepoAdmin := context.RequireRepoAdmin()
|
||||
reqRepoWriter := context.RequireRepoWriter()
|
||||
|
||||
// ***** START: Organization *****
|
||||
m.Group("/org", func() {
|
||||
m.Get("/create", org.Create)
|
||||
m.Post("/create", bindIgnErr(auth.CreateOrgForm{}), org.CreatePost)
|
||||
m.Group("", func() {
|
||||
m.Get("/create", org.Create)
|
||||
m.Post("/create", bindIgnErr(auth.CreateOrgForm{}), org.CreatePost)
|
||||
}, func(ctx *context.Context) {
|
||||
if !ctx.User.CanCreateOrganization() {
|
||||
ctx.NotFound()
|
||||
}
|
||||
})
|
||||
|
||||
m.Group("/:org", func() {
|
||||
m.Get("/dashboard", user.Dashboard)
|
||||
m.Get("/:type(issues|pulls)", user.Issues)
|
||||
m.Get("/^:type(issues|pulls)$", user.Issues)
|
||||
m.Get("/members", org.Members)
|
||||
m.Get("/members/action/:action", org.MembersAction)
|
||||
|
||||
m.Get("/teams", org.Teams)
|
||||
}, context.OrgAssignment(true))
|
||||
|
||||
m.Group("/:org", func() {
|
||||
m.Get("/teams/:team", org.TeamMembers)
|
||||
m.Get("/teams/:team/repositories", org.TeamRepositories)
|
||||
m.Get("/teams/:team/action/:action", org.TeamsAction)
|
||||
m.Get("/teams/:team/action/repo/:action", org.TeamsRepoAction)
|
||||
}, middleware.OrgAssignment(true, true))
|
||||
m.Route("/teams/:team/action/:action", "GET,POST", org.TeamsAction)
|
||||
m.Route("/teams/:team/action/repo/:action", "GET,POST", org.TeamsRepoAction)
|
||||
}, context.OrgAssignment(true, false, true))
|
||||
|
||||
m.Group("/:org", func() {
|
||||
m.Get("/teams/new", org.NewTeam)
|
||||
@@ -406,6 +398,8 @@ func runWeb(ctx *cli.Context) {
|
||||
m.Group("/settings", func() {
|
||||
m.Combo("").Get(org.Settings).
|
||||
Post(bindIgnErr(auth.UpdateOrgSettingForm{}), org.SettingsPost)
|
||||
m.Post("/avatar", binding.MultipartForm(auth.AvatarForm{}), org.SettingsAvatar)
|
||||
m.Post("/avatar/delete", org.SettingsDeleteAvatar)
|
||||
|
||||
m.Group("/hooks", func() {
|
||||
m.Get("", org.Webhooks)
|
||||
@@ -413,20 +407,19 @@ func runWeb(ctx *cli.Context) {
|
||||
m.Get("/:type/new", repo.WebhooksNew)
|
||||
m.Post("/gogs/new", bindIgnErr(auth.NewWebhookForm{}), repo.WebHooksNewPost)
|
||||
m.Post("/slack/new", bindIgnErr(auth.NewSlackHookForm{}), repo.SlackHooksNewPost)
|
||||
m.Post("/discord/new", bindIgnErr(auth.NewDiscordHookForm{}), repo.DiscordHooksNewPost)
|
||||
m.Get("/:id", repo.WebHooksEdit)
|
||||
m.Post("/gogs/:id", bindIgnErr(auth.NewWebhookForm{}), repo.WebHooksEditPost)
|
||||
m.Post("/slack/:id", bindIgnErr(auth.NewSlackHookForm{}), repo.SlackHooksEditPost)
|
||||
m.Post("/discord/:id", bindIgnErr(auth.NewDiscordHookForm{}), repo.DiscordHooksEditPost)
|
||||
})
|
||||
|
||||
m.Route("/delete", "GET,POST", org.SettingsDelete)
|
||||
})
|
||||
|
||||
m.Route("/invitations/new", "GET,POST", org.Invitation)
|
||||
}, middleware.OrgAssignment(true, true, true))
|
||||
}, context.OrgAssignment(true, true))
|
||||
}, reqSignIn)
|
||||
m.Group("/org", func() {
|
||||
m.Get("/:org", org.Home)
|
||||
}, ignSignIn, middleware.OrgAssignment(true))
|
||||
// ***** END: Organization *****
|
||||
|
||||
// ***** START: Repository *****
|
||||
@@ -443,7 +436,22 @@ func runWeb(ctx *cli.Context) {
|
||||
m.Group("/settings", func() {
|
||||
m.Combo("").Get(repo.Settings).
|
||||
Post(bindIgnErr(auth.RepoSettingForm{}), repo.SettingsPost)
|
||||
m.Route("/collaboration", "GET,POST", repo.Collaboration)
|
||||
m.Group("/collaboration", func() {
|
||||
m.Combo("").Get(repo.SettingsCollaboration).Post(repo.SettingsCollaborationPost)
|
||||
m.Post("/access_mode", repo.ChangeCollaborationAccessMode)
|
||||
m.Post("/delete", repo.DeleteCollaboration)
|
||||
})
|
||||
m.Group("/branches", func() {
|
||||
m.Get("", repo.SettingsBranches)
|
||||
m.Post("/default_branch", repo.UpdateDefaultBranch)
|
||||
m.Combo("/*").Get(repo.SettingsProtectedBranch).
|
||||
Post(bindIgnErr(auth.ProtectBranchForm{}), repo.SettingsProtectedBranchPost)
|
||||
}, func(ctx *context.Context) {
|
||||
if ctx.Repo.Repository.IsMirror {
|
||||
ctx.NotFound()
|
||||
return
|
||||
}
|
||||
})
|
||||
|
||||
m.Group("/hooks", func() {
|
||||
m.Get("", repo.Webhooks)
|
||||
@@ -451,111 +459,184 @@ func runWeb(ctx *cli.Context) {
|
||||
m.Get("/:type/new", repo.WebhooksNew)
|
||||
m.Post("/gogs/new", bindIgnErr(auth.NewWebhookForm{}), repo.WebHooksNewPost)
|
||||
m.Post("/slack/new", bindIgnErr(auth.NewSlackHookForm{}), repo.SlackHooksNewPost)
|
||||
m.Post("/discord/new", bindIgnErr(auth.NewDiscordHookForm{}), repo.DiscordHooksNewPost)
|
||||
m.Get("/:id", repo.WebHooksEdit)
|
||||
m.Post("/:id/test", repo.TestWebhook)
|
||||
m.Post("/gogs/:id", bindIgnErr(auth.NewWebhookForm{}), repo.WebHooksEditPost)
|
||||
m.Post("/slack/:id", bindIgnErr(auth.NewSlackHookForm{}), repo.SlackHooksEditPost)
|
||||
m.Post("/discord/:id", bindIgnErr(auth.NewDiscordHookForm{}), repo.DiscordHooksEditPost)
|
||||
|
||||
m.Group("/git", func() {
|
||||
m.Get("", repo.GitHooks)
|
||||
m.Combo("/:name").Get(repo.GitHooksEdit).
|
||||
Post(repo.GitHooksEditPost)
|
||||
}, middleware.GitHookService())
|
||||
m.Get("", repo.SettingsGitHooks)
|
||||
m.Combo("/:name").Get(repo.SettingsGitHooksEdit).
|
||||
Post(repo.SettingsGitHooksEditPost)
|
||||
}, context.GitHookService())
|
||||
})
|
||||
|
||||
m.Group("/keys", func() {
|
||||
m.Combo("").Get(repo.DeployKeys).
|
||||
Post(bindIgnErr(auth.AddSSHKeyForm{}), repo.DeployKeysPost)
|
||||
m.Combo("").Get(repo.SettingsDeployKeys).
|
||||
Post(bindIgnErr(auth.AddSSHKeyForm{}), repo.SettingsDeployKeysPost)
|
||||
m.Post("/delete", repo.DeleteDeployKey)
|
||||
})
|
||||
|
||||
}, func(ctx *context.Context) {
|
||||
ctx.Data["PageIsSettings"] = true
|
||||
})
|
||||
}, reqSignIn, middleware.RepoAssignment(true), reqRepoAdmin)
|
||||
}, reqSignIn, context.RepoAssignment(), reqRepoAdmin, context.RepoRef())
|
||||
|
||||
m.Get("/:username/:reponame/action/:action", reqSignIn, context.RepoAssignment(), repo.Action)
|
||||
m.Group("/:username/:reponame", func() {
|
||||
m.Get("/action/:action", repo.Action)
|
||||
|
||||
// FIXME: should use different URLs but mostly same logic for comments of issue and pull reuqest.
|
||||
// So they can apply their own enable/disable logic on routers.
|
||||
m.Group("/issues", func() {
|
||||
m.Combo("/new").Get(repo.NewIssue).
|
||||
m.Combo("/new", repo.MustEnableIssues).Get(context.RepoRef(), repo.NewIssue).
|
||||
Post(bindIgnErr(auth.CreateIssueForm{}), repo.NewIssuePost)
|
||||
|
||||
m.Combo("/:index/comments").Post(bindIgnErr(auth.CreateCommentForm{}), repo.NewComment)
|
||||
m.Group("/:index", func() {
|
||||
m.Post("/label", repo.UpdateIssueLabel)
|
||||
m.Post("/milestone", repo.UpdateIssueMilestone)
|
||||
m.Post("/assignee", repo.UpdateIssueAssignee)
|
||||
}, reqRepoAdmin)
|
||||
}, reqRepoWriter)
|
||||
|
||||
m.Group("/:index", func() {
|
||||
m.Post("/title", repo.UpdateIssueTitle)
|
||||
m.Post("/content", repo.UpdateIssueContent)
|
||||
m.Combo("/comments").Post(bindIgnErr(auth.CreateCommentForm{}), repo.NewComment)
|
||||
})
|
||||
})
|
||||
m.Post("/comments/:id", repo.UpdateCommentContent)
|
||||
m.Group("/comments/:id", func() {
|
||||
m.Post("", repo.UpdateCommentContent)
|
||||
m.Post("/delete", repo.DeleteComment)
|
||||
})
|
||||
m.Group("/labels", func() {
|
||||
m.Post("/new", bindIgnErr(auth.CreateLabelForm{}), repo.NewLabel)
|
||||
m.Post("/edit", bindIgnErr(auth.CreateLabelForm{}), repo.UpdateLabel)
|
||||
m.Post("/delete", repo.DeleteLabel)
|
||||
}, reqRepoAdmin)
|
||||
m.Post("/initialize", bindIgnErr(auth.InitializeLabelsForm{}), repo.InitializeLabels)
|
||||
}, reqRepoWriter, context.RepoRef())
|
||||
m.Group("/milestones", func() {
|
||||
m.Get("/new", repo.NewMilestone)
|
||||
m.Post("/new", bindIgnErr(auth.CreateMilestoneForm{}), repo.NewMilestonePost)
|
||||
m.Combo("/new").Get(repo.NewMilestone).
|
||||
Post(bindIgnErr(auth.CreateMilestoneForm{}), repo.NewMilestonePost)
|
||||
m.Get("/:id/edit", repo.EditMilestone)
|
||||
m.Post("/:id/edit", bindIgnErr(auth.CreateMilestoneForm{}), repo.EditMilestonePost)
|
||||
m.Get("/:id/:action", repo.ChangeMilestonStatus)
|
||||
m.Post("/delete", repo.DeleteMilestone)
|
||||
}, reqRepoAdmin)
|
||||
}, reqRepoWriter, context.RepoRef())
|
||||
|
||||
m.Group("/releases", func() {
|
||||
m.Get("/new", repo.NewRelease)
|
||||
m.Post("/new", bindIgnErr(auth.NewReleaseForm{}), repo.NewReleasePost)
|
||||
m.Get("/edit/:tagname", repo.EditRelease)
|
||||
m.Post("/edit/:tagname", bindIgnErr(auth.EditReleaseForm{}), repo.EditReleasePost)
|
||||
}, reqRepoAdmin, middleware.RepoRef())
|
||||
m.Post("/delete", repo.DeleteRelease)
|
||||
m.Get("/edit/*", repo.EditRelease)
|
||||
m.Post("/edit/*", bindIgnErr(auth.EditReleaseForm{}), repo.EditReleasePost)
|
||||
}, reqRepoWriter, context.RepoRef())
|
||||
|
||||
m.Combo("/compare/*").Get(repo.CompareAndPullRequest).
|
||||
// FIXME: Should use ctx.Repo.PullRequest to unify template, currently we have inconsistent URL
|
||||
// for PR in same repository. After select branch on the page, the URL contains redundant head user name.
|
||||
// e.g. /org1/test-repo/compare/master...org1:develop
|
||||
// which should be /org1/test-repo/compare/master...develop
|
||||
m.Combo("/compare/*", repo.MustAllowPulls).Get(repo.CompareAndPullRequest).
|
||||
Post(bindIgnErr(auth.CreateIssueForm{}), repo.CompareAndPullRequestPost)
|
||||
}, reqSignIn, middleware.RepoAssignment(true))
|
||||
|
||||
m.Group("", func() {
|
||||
m.Combo("/_edit/*").Get(repo.EditFile).
|
||||
Post(bindIgnErr(auth.EditRepoFileForm{}), repo.EditFilePost)
|
||||
m.Combo("/_new/*").Get(repo.NewFile).
|
||||
Post(bindIgnErr(auth.EditRepoFileForm{}), repo.NewFilePost)
|
||||
m.Post("/_preview/*", bindIgnErr(auth.EditPreviewDiffForm{}), repo.DiffPreviewPost)
|
||||
m.Combo("/_delete/*").Get(repo.DeleteFile).
|
||||
Post(bindIgnErr(auth.DeleteRepoFileForm{}), repo.DeleteFilePost)
|
||||
|
||||
m.Group("", func() {
|
||||
m.Combo("/_upload/*").Get(repo.UploadFile).
|
||||
Post(bindIgnErr(auth.UploadRepoFileForm{}), repo.UploadFilePost)
|
||||
m.Post("/upload-file", repo.UploadFileToServer)
|
||||
m.Post("/upload-remove", bindIgnErr(auth.RemoveUploadFileForm{}), repo.RemoveUploadFileFromServer)
|
||||
}, func(ctx *context.Context) {
|
||||
if !setting.Repository.Upload.Enabled {
|
||||
ctx.NotFound()
|
||||
return
|
||||
}
|
||||
})
|
||||
}, reqRepoWriter, context.RepoRef(), func(ctx *context.Context) {
|
||||
if !ctx.Repo.CanEnableEditor() {
|
||||
ctx.NotFound()
|
||||
return
|
||||
}
|
||||
})
|
||||
}, reqSignIn, context.RepoAssignment(), repo.MustBeNotBare)
|
||||
|
||||
m.Group("/:username/:reponame", func() {
|
||||
m.Get("/releases", middleware.RepoRef(), repo.Releases)
|
||||
m.Get("/:type(issues|pulls)", repo.RetrieveLabels, repo.Issues)
|
||||
m.Get("/:type(issues|pulls)/:index", repo.ViewIssue)
|
||||
m.Get("/labels/", repo.RetrieveLabels, repo.Labels)
|
||||
m.Get("/milestones", repo.Milestones)
|
||||
m.Get("/branches", repo.Branches)
|
||||
m.Group("", func() {
|
||||
m.Get("/releases", repo.Releases)
|
||||
m.Get("/^:type(issues|pulls)$", repo.RetrieveLabels, repo.Issues)
|
||||
m.Get("/^:type(issues|pulls)$/:index", repo.ViewIssue)
|
||||
m.Get("/labels/", repo.RetrieveLabels, repo.Labels)
|
||||
m.Get("/milestones", repo.Milestones)
|
||||
}, context.RepoRef())
|
||||
|
||||
// m.Get("/branches", repo.Branches)
|
||||
m.Post("/branches/delete/*", reqSignIn, reqRepoWriter, repo.DeleteBranchPost)
|
||||
|
||||
m.Group("/wiki", func() {
|
||||
m.Get("/?:page", repo.Wiki)
|
||||
m.Get("/_pages", repo.WikiPages)
|
||||
|
||||
m.Group("", func() {
|
||||
m.Combo("/_new").Get(repo.NewWiki).
|
||||
Post(bindIgnErr(auth.NewWikiForm{}), repo.NewWikiPost)
|
||||
m.Combo("/:page/_edit").Get(repo.EditWiki).
|
||||
Post(bindIgnErr(auth.NewWikiForm{}), repo.EditWikiPost)
|
||||
m.Post("/:page/delete", repo.DeleteWikiPagePost)
|
||||
}, reqSignIn, reqRepoWriter)
|
||||
}, repo.MustEnableWiki, context.RepoRef())
|
||||
|
||||
m.Get("/archive/*", repo.Download)
|
||||
|
||||
m.Group("/pulls/:index", func() {
|
||||
m.Get("/commits", repo.ViewPullCommits)
|
||||
m.Get("/files", repo.ViewPullFiles)
|
||||
m.Post("/merge", reqRepoAdmin, repo.MergePullRequest)
|
||||
})
|
||||
m.Get("/commits", context.RepoRef(), repo.ViewPullCommits)
|
||||
m.Get("/files", context.RepoRef(), repo.ViewPullFiles)
|
||||
m.Post("/merge", reqRepoWriter, repo.MergePullRequest)
|
||||
}, repo.MustAllowPulls)
|
||||
|
||||
m.Group("", func() {
|
||||
m.Get("/src/*", repo.Home)
|
||||
m.Get("/raw/*", repo.SingleDownload)
|
||||
m.Get("/commits/*", repo.RefCommits)
|
||||
m.Get("/commit/*", repo.Diff)
|
||||
}, middleware.RepoRef())
|
||||
m.Get("/commit/:sha([a-f0-9]{7,40})$", repo.Diff)
|
||||
m.Get("/forks", repo.Forks)
|
||||
}, context.RepoRef())
|
||||
m.Get("/commit/:sha([a-f0-9]{7,40})\\.:ext(patch|diff)", repo.RawDiff)
|
||||
|
||||
m.Get("/compare/:before([a-z0-9]{40})...:after([a-z0-9]{40})", repo.CompareDiff)
|
||||
}, ignSignIn, middleware.RepoAssignment(true))
|
||||
m.Get("/compare/:before([a-z0-9]{40})\\.\\.\\.:after([a-z0-9]{40})", context.RepoRef(), repo.CompareDiff)
|
||||
}, ignSignIn, context.RepoAssignment(), repo.MustBeNotBare)
|
||||
m.Group("/:username/:reponame", func() {
|
||||
m.Get("/stars", repo.Stars)
|
||||
m.Get("/watchers", repo.Watchers)
|
||||
}, ignSignIn, context.RepoAssignment(), context.RepoRef())
|
||||
|
||||
m.Group("/:username", func() {
|
||||
m.Group("/:reponame", func() {
|
||||
m.Get("", repo.Home)
|
||||
m.Get(".git", repo.Home)
|
||||
}, ignSignIn, middleware.RepoAssignment(true, true), middleware.RepoRef())
|
||||
m.Group("", func() {
|
||||
m.Get("/:reponame", repo.Home)
|
||||
}, ignSignIn, context.RepoAssignment(true), context.RepoRef())
|
||||
|
||||
m.Group("/:reponame", func() {
|
||||
m.Any("/*", ignSignInAndCsrf, repo.Http)
|
||||
m.Head("/hooks/trigger", repo.TriggerHook)
|
||||
m.Head("/tasks/trigger", repo.TriggerTask)
|
||||
})
|
||||
// Use the regexp to match the repository name validation
|
||||
m.Group("/:reponame([\\d\\w-_\\.]+\\.git$)", func() {
|
||||
m.Get("", ignSignIn, context.RepoAssignment(true), context.RepoRef(), repo.Home)
|
||||
m.Route("/*", "GET,POST", ignSignInAndCsrf, repo.HTTPContexter(), repo.HTTP)
|
||||
})
|
||||
})
|
||||
// ***** END: Repository *****
|
||||
|
||||
m.Group("/api", func() {
|
||||
apiv1.RegisterRoutes(m)
|
||||
}, ignSignIn)
|
||||
|
||||
// robots.txt
|
||||
m.Get("/robots.txt", func(ctx *middleware.Context) {
|
||||
m.Get("/robots.txt", func(ctx *context.Context) {
|
||||
if setting.HasRobotsTxt {
|
||||
ctx.ServeFileContent(path.Join(setting.CustomPath, "robots.txt"))
|
||||
} else {
|
||||
@@ -568,21 +649,52 @@ func runWeb(ctx *cli.Context) {
|
||||
|
||||
// Flag for port number in case first time run conflict.
|
||||
if ctx.IsSet("port") {
|
||||
setting.AppUrl = strings.Replace(setting.AppUrl, setting.HttpPort, ctx.String("port"), 1)
|
||||
setting.HttpPort = ctx.String("port")
|
||||
setting.AppUrl = strings.Replace(setting.AppUrl, setting.HTTPPort, ctx.String("port"), 1)
|
||||
setting.HTTPPort = ctx.String("port")
|
||||
}
|
||||
|
||||
var err error
|
||||
listenAddr := fmt.Sprintf("%s:%s", setting.HttpAddr, setting.HttpPort)
|
||||
var listenAddr string
|
||||
if setting.Protocol == setting.SCHEME_UNIX_SOCKET {
|
||||
listenAddr = fmt.Sprintf("%s", setting.HTTPAddr)
|
||||
} else {
|
||||
listenAddr = fmt.Sprintf("%s:%s", setting.HTTPAddr, setting.HTTPPort)
|
||||
}
|
||||
log.Info("Listen: %v://%s%s", setting.Protocol, listenAddr, setting.AppSubUrl)
|
||||
|
||||
var err error
|
||||
switch setting.Protocol {
|
||||
case setting.HTTP:
|
||||
case setting.SCHEME_HTTP:
|
||||
err = http.ListenAndServe(listenAddr, m)
|
||||
case setting.HTTPS:
|
||||
server := &http.Server{Addr: listenAddr, TLSConfig: &tls.Config{MinVersion: tls.VersionTLS10}, Handler: m}
|
||||
case setting.SCHEME_HTTPS:
|
||||
server := &http.Server{Addr: listenAddr, TLSConfig: &tls.Config{
|
||||
MinVersion: tls.VersionTLS10,
|
||||
CurvePreferences: []tls.CurveID{tls.CurveP521, tls.CurveP384, tls.CurveP256},
|
||||
PreferServerCipherSuites: true,
|
||||
CipherSuites: []uint16{
|
||||
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
|
||||
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, // Required for HTTP/2 support.
|
||||
tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
|
||||
tls.TLS_RSA_WITH_AES_256_CBC_SHA,
|
||||
},
|
||||
}, Handler: m}
|
||||
err = server.ListenAndServeTLS(setting.CertFile, setting.KeyFile)
|
||||
case setting.FCGI:
|
||||
case setting.SCHEME_FCGI:
|
||||
err = fcgi.Serve(nil, m)
|
||||
case setting.SCHEME_UNIX_SOCKET:
|
||||
os.Remove(listenAddr)
|
||||
|
||||
var listener *net.UnixListener
|
||||
listener, err = net.ListenUnix("unix", &net.UnixAddr{listenAddr, "unix"})
|
||||
if err != nil {
|
||||
break // Handle error after switch
|
||||
}
|
||||
|
||||
// FIXME: add proper implementation of signal capture on all protocols
|
||||
// execute this on SIGTERM or SIGINT: listener.Close()
|
||||
if err = os.Chmod(listenAddr, os.FileMode(setting.UnixSocketPermission)); err != nil {
|
||||
log.Fatal(4, "Failed to set permission of unix socket: %v", err)
|
||||
}
|
||||
err = http.Serve(listener, m)
|
||||
default:
|
||||
log.Fatal(4, "Invalid protocol: %s", setting.Protocol)
|
||||
}
|
||||
@@ -590,4 +702,6 @@ func runWeb(ctx *cli.Context) {
|
||||
if err != nil {
|
||||
log.Fatal(4, "Fail to start server: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
Execute following command in ROOT directory when anything is changed:
|
||||
|
||||
$ go-bindata -o=modules/bindata/bindata.go -ignore="\\.DS_Store|README.md" -pkg=bindata conf/...
|
||||
|
||||
Add -debug flag to make life easier in development(somehow isn't working):
|
||||
|
||||
$ go-bindata -debug -o=modules/bindata/bindata.go -ignore="\\.DS_Store|README.md" -pkg=bindata conf/...
|
||||
$ make bindata
|
||||
357
conf/app.ini
357
conf/app.ini
@@ -1,41 +1,53 @@
|
||||
# NEVER EVER MODIFY THIS FILE
|
||||
# PLEASE MAKE CHANGES ON CORRESPONDING CUSTOM CONFIG FILE
|
||||
# !!! NEVER EVER MODIFY THIS FILE !!!
|
||||
# !!! PLEASE MAKE CHANGES ON CORRESPONDING CUSTOM CONFIG FILE !!!
|
||||
# !!! IF YOU ARE PACKAGING PROVIDER, PLEASE MAKE OWN COPY OF IT !!!
|
||||
|
||||
; App name that shows on every page title
|
||||
APP_NAME = Gogs: Go Git Service
|
||||
; Change it if you run locally
|
||||
APP_NAME = Gogs
|
||||
; The name of the system user that runs Gogs
|
||||
RUN_USER = git
|
||||
; Either "dev", "prod" or "test", default is "dev"
|
||||
; Either "dev", "prod" or "test"
|
||||
RUN_MODE = dev
|
||||
|
||||
[repository]
|
||||
ROOT =
|
||||
SCRIPT_TYPE = bash
|
||||
|
||||
[ui]
|
||||
; Number of repositories that are showed in one explore page
|
||||
EXPLORE_PAGING_NUM = 20
|
||||
; Number of issues that are showed in one page
|
||||
ISSUE_PAGING_NUM = 10
|
||||
|
||||
[markdown]
|
||||
; Enable hard line break extension
|
||||
ENABLE_HARD_LINE_BREAK = false
|
||||
|
||||
[server]
|
||||
PROTOCOL = http
|
||||
DOMAIN = localhost
|
||||
ROOT_URL = %(PROTOCOL)s://%(DOMAIN)s:%(HTTP_PORT)s/
|
||||
HTTP_ADDR =
|
||||
HTTP_ADDR = 0.0.0.0
|
||||
HTTP_PORT = 3000
|
||||
; Permission for unix socket
|
||||
UNIX_SOCKET_PERMISSION = 666
|
||||
; Local (DMZ) URL for Gogs workers (such as SSH update) accessing web service.
|
||||
; In most cases you do not need to change the default value.
|
||||
; Alter it only if your SSH server node is not the same as HTTP node.
|
||||
LOCAL_ROOT_URL = %(PROTOCOL)s://%(HTTP_ADDR)s:%(HTTP_PORT)s/
|
||||
; Disable SSH feature when not available
|
||||
DISABLE_SSH = false
|
||||
; Whether use builtin SSH server or not.
|
||||
START_SSH_SERVER = false
|
||||
; Domain name to be exposed in SSH clone URL
|
||||
SSH_DOMAIN = %(DOMAIN)s
|
||||
; Port number to be exposed in SSH clone URL
|
||||
SSH_PORT = 22
|
||||
; Network interface builtin SSH server listens on
|
||||
SSH_LISTEN_HOST = 0.0.0.0
|
||||
; Port number builtin SSH server listens on
|
||||
SSH_LISTEN_PORT = %(SSH_PORT)s
|
||||
; Root path of SSH directory, default is '~/.ssh', but you have to use '/home/git/.ssh'.
|
||||
SSH_ROOT_PATH =
|
||||
; Choose the ciphers to support for SSH connections
|
||||
SSH_SERVER_CIPHERS = aes128-ctr, aes192-ctr, aes256-ctr, aes128-gcm@openssh.com, arcfour256, arcfour128
|
||||
; Directory to create temporary files when test publick key using ssh-keygen,
|
||||
; default is system temporary directory.
|
||||
SSH_KEY_TEST_PATH =
|
||||
; Path to ssh-keygen, default is 'ssh-keygen' and let shell find out which one to call.
|
||||
SSH_KEYGEN_PATH = ssh-keygen
|
||||
; Indicate whether to check minimum key size with corresponding type
|
||||
MINIMUM_KEY_SIZE_CHECK = false
|
||||
; Disable CDN even in "prod" mode
|
||||
OFFLINE_MODE = false
|
||||
DISABLE_ROUTER_LOG = false
|
||||
; Generate steps:
|
||||
; $ cd path/to/gogs/custom/https
|
||||
; $ ./gogs cert -ca=true -duration=8760h0m0s -host=myhost.example.com
|
||||
;
|
||||
; Or from a .pfx file exported from the Windows certificate store (do
|
||||
@@ -47,13 +59,84 @@ KEY_FILE = custom/https/key.pem
|
||||
; Upper level of template and static file path
|
||||
; default is the path where Gogs is executed
|
||||
STATIC_ROOT_PATH =
|
||||
; Default path for App data
|
||||
APP_DATA_PATH = data
|
||||
; Application level GZIP support
|
||||
ENABLE_GZIP = false
|
||||
; Landing page for non-logged users, can be "home" or "explore"
|
||||
LANDING_PAGE = home
|
||||
|
||||
[repository]
|
||||
; Root path for storing repositories's data, default is "~/<username>/gogs-repositories"
|
||||
ROOT =
|
||||
; The script type server supports, sometimes could be "sh"
|
||||
SCRIPT_TYPE = bash
|
||||
; Default ANSI charset for an unrecognized charset
|
||||
ANSI_CHARSET =
|
||||
; Force every new repository to be private
|
||||
FORCE_PRIVATE = false
|
||||
; Global maximum creation limit of repository per user, -1 means no limit
|
||||
MAX_CREATION_LIMIT = -1
|
||||
; Mirror sync queue length, increase if mirror syncing starts hanging
|
||||
MIRROR_QUEUE_LENGTH = 1000
|
||||
; Patch test queue length, increase if pull request patch testing starts hanging
|
||||
PULL_REQUEST_QUEUE_LENGTH = 1000
|
||||
; Preferred Licenses to place at the top of the list
|
||||
; Name must match file name in conf/license or custom/conf/license
|
||||
PREFERRED_LICENSES = Apache License 2.0,MIT License
|
||||
; Disable ability to interact with repositories by HTTP protocol
|
||||
DISABLE_HTTP_GIT = false
|
||||
; Enable ability to migrate repository by local path
|
||||
ENABLE_LOCAL_PATH_MIGRATION = false
|
||||
; Concurrency is used to retrieve commits information. This variable define
|
||||
; the maximum number of tasks that can be run at the same time. Usually, the
|
||||
; value depend of how many CPUs (cores) you have. If the value is set to zero
|
||||
; or under, GOGS will automatically detect the number of CPUs your system have
|
||||
COMMITS_FETCH_CONCURRENCY = 0
|
||||
|
||||
[repository.editor]
|
||||
; List of file extensions that should have line wraps in the CodeMirror editor.
|
||||
; Separate extensions with a comma. To line wrap files without extension, just put a comma
|
||||
LINE_WRAP_EXTENSIONS = .txt,.md,.markdown,.mdown,.mkd,
|
||||
; Valid file modes that have a preview API associated with them, such as api/v1/markdown.
|
||||
; Separate values by commas. Preview tab in edit mode won't show if the file extension doesn't match
|
||||
PREVIEWABLE_FILE_MODES = markdown
|
||||
|
||||
[repository.upload]
|
||||
; Enable repository file uploads.
|
||||
ENABLED = true
|
||||
; Path to temporarily store uploads (default path gets cleaned by Gogs in every start)
|
||||
TEMP_PATH = data/tmp/uploads
|
||||
; File types that are allowed to be uploaded, e.g. image/jpeg|image/png. Leave empty means allow any file type
|
||||
ALLOWED_TYPES =
|
||||
; Maximum size of each file in MB
|
||||
FILE_MAX_SIZE = 3
|
||||
; Maximum number of files per upload
|
||||
MAX_FILES = 5
|
||||
|
||||
[markdown]
|
||||
; Enable hard line break extension
|
||||
ENABLE_HARD_LINE_BREAK = false
|
||||
; List of custom URL-Schemes that are allowed as links when rendering Markdown
|
||||
; for example git,magnet
|
||||
CUSTOM_URL_SCHEMES =
|
||||
; List of file extensions that should be rendered/edited as Markdown
|
||||
; Separate extensions with a comma. To render files w/o extension as markdown, just put a comma
|
||||
FILE_EXTENSIONS = .md,.markdown,.mdown,.mkd
|
||||
|
||||
[http]
|
||||
; Value for Access-Control-Allow-Origin header, default is not to present
|
||||
ACCESS_CONTROL_ALLOW_ORIGIN =
|
||||
|
||||
; Define allowed algorithms and their minimum key length (use -1 to disable a type)
|
||||
[ssh.minimum_key_sizes]
|
||||
ED25519 = 256
|
||||
ECDSA = 256
|
||||
RSA = 2048
|
||||
DSA = 1024
|
||||
|
||||
[database]
|
||||
; Either "mysql", "postgres" or "sqlite3", it's your choice
|
||||
; Either "mysql", "postgres" or "sqlite3", you can connect to TiDB with MySQL protocol
|
||||
DB_TYPE = mysql
|
||||
HOST = 127.0.0.1:3306
|
||||
NAME = gogs
|
||||
@@ -61,10 +144,12 @@ USER = root
|
||||
PASSWD =
|
||||
; For "postgres" only, either "disable", "require" or "verify-full"
|
||||
SSL_MODE = disable
|
||||
; For "sqlite3" only
|
||||
; For "sqlite3" and "tidb", use absolute path when you start as service
|
||||
PATH = data/gogs.db
|
||||
|
||||
[admin]
|
||||
; Disable regular (non-admin) users to create organizations
|
||||
DISABLE_REGULAR_ORG_CREATION = false
|
||||
|
||||
[security]
|
||||
INSTALL_LOCK = false
|
||||
@@ -74,6 +159,7 @@ SECRET_KEY = !#@FDEWREWR&*(
|
||||
LOGIN_REMEMBER_DAYS = 7
|
||||
COOKIE_USERNAME = gogs_awesome
|
||||
COOKIE_REMEMBER_NAME = gogs_incredible
|
||||
COOKIE_SECURE = false
|
||||
; Reverse proxy authentication header name of user name
|
||||
REVERSE_PROXY_AUTHENTICATION_USER = X-WEBAUTH-USER
|
||||
|
||||
@@ -86,18 +172,18 @@ REGISTER_EMAIL_CONFIRM = false
|
||||
DISABLE_REGISTRATION = false
|
||||
; User must sign in to view anything.
|
||||
REQUIRE_SIGNIN_VIEW = false
|
||||
; Cache avatar as picture
|
||||
ENABLE_CACHE_AVATAR = false
|
||||
; Mail notification
|
||||
ENABLE_NOTIFY_MAIL = false
|
||||
; More detail: https://github.com/gogits/gogs/issues/165
|
||||
ENABLE_REVERSE_PROXY_AUTHENTICATION = false
|
||||
ENABLE_REVERSE_PROXY_AUTO_REGISTRATION = false
|
||||
; Do not check minimum key size with corresponding type
|
||||
DISABLE_MINIMUM_KEY_SIZE_CHECK = false
|
||||
; Enable captcha validation for registration
|
||||
ENABLE_CAPTCHA = true
|
||||
|
||||
[webhook]
|
||||
; Hook task queue length
|
||||
; Types are enabled for users to use, can be "gogs", "slack", "discord"
|
||||
TYPES = gogs, slack, discord
|
||||
; Hook task queue length, increase if webhook shooting starts hanging
|
||||
QUEUE_LENGTH = 1000
|
||||
; Deliver timeout in seconds
|
||||
DELIVER_TIMEOUT = 5
|
||||
@@ -109,67 +195,31 @@ PAGING_NUM = 10
|
||||
[mailer]
|
||||
ENABLED = false
|
||||
; Buffer length of channel, keep it as it is if you don't know what it is.
|
||||
SEND_BUFFER_LEN = 10
|
||||
SEND_BUFFER_LEN = 100
|
||||
; Name displayed in mail title
|
||||
SUBJECT = %(APP_NAME)s
|
||||
; Mail server
|
||||
; Gmail: smtp.gmail.com:587
|
||||
; QQ: smtp.qq.com:25
|
||||
; QQ: smtp.qq.com:465
|
||||
; Note, if the port ends with "465", SMTPS will be used. Using STARTTLS on port 587 is recommended per RFC 6409. If the server supports STARTTLS it will always be used.
|
||||
HOST =
|
||||
HOST =
|
||||
; Disable HELO operation when hostname are different.
|
||||
DISABLE_HELO =
|
||||
DISABLE_HELO =
|
||||
; Custom hostname for HELO operation, default is from system.
|
||||
HELO_HOSTNAME =
|
||||
HELO_HOSTNAME =
|
||||
; Do not verify the certificate of the server. Only use this for self-signed certificates
|
||||
SKIP_VERIFY =
|
||||
SKIP_VERIFY =
|
||||
; Use client certificate
|
||||
USE_CERTIFICATE = false
|
||||
CERT_FILE = custom/mailer/cert.pem
|
||||
KEY_FILE = custom/mailer/key.pem
|
||||
; Mail from address, RFC 5322. This can be just an email address, or the `"Name" <email@example.com>` format
|
||||
; Mail from address, RFC 5322. This can be just an email address, or the `"Name" <email@example.com>` format
|
||||
FROM =
|
||||
; Mailer user name and password
|
||||
USER =
|
||||
PASSWD =
|
||||
|
||||
[oauth]
|
||||
ENABLED = false
|
||||
|
||||
[oauth.github]
|
||||
ENABLED = false
|
||||
CLIENT_ID =
|
||||
CLIENT_SECRET =
|
||||
SCOPES = https://api.github.com/user
|
||||
AUTH_URL = https://github.com/login/oauth/authorize
|
||||
TOKEN_URL = https://github.com/login/oauth/access_token
|
||||
|
||||
; Get client id and secret from
|
||||
; https://console.developers.google.com/project
|
||||
[oauth.google]
|
||||
ENABLED = false
|
||||
CLIENT_ID =
|
||||
CLIENT_SECRET =
|
||||
SCOPES = https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile
|
||||
AUTH_URL = https://accounts.google.com/o/oauth2/auth
|
||||
TOKEN_URL = https://accounts.google.com/o/oauth2/token
|
||||
|
||||
[oauth.qq]
|
||||
ENABLED = false
|
||||
CLIENT_ID =
|
||||
CLIENT_SECRET =
|
||||
SCOPES = get_user_info
|
||||
; QQ 互联
|
||||
AUTH_URL = https://graph.qq.com/oauth2.0/authorize
|
||||
TOKEN_URL = https://graph.qq.com/oauth2.0/token
|
||||
|
||||
[oauth.weibo]
|
||||
ENABLED = false
|
||||
CLIENT_ID =
|
||||
CLIENT_SECRET =
|
||||
SCOPES = all
|
||||
AUTH_URL = https://api.weibo.com/oauth2/authorize
|
||||
TOKEN_URL = https://api.weibo.com/oauth2/access_token
|
||||
; Use text/html as alternative format of content
|
||||
ENABLE_HTML_ALTERNATIVE = false
|
||||
|
||||
[cache]
|
||||
; Either "memory", "redis", or "memcache", default is "memory"
|
||||
@@ -182,7 +232,7 @@ INTERVAL = 60
|
||||
HOST =
|
||||
|
||||
[session]
|
||||
; Either "memory", "file", "redis" or "mysql", default is "memory"
|
||||
; Either "memory", "file", or "redis", default is "memory"
|
||||
PROVIDER = memory
|
||||
; Provider config options
|
||||
; memory: not have any config yet
|
||||
@@ -200,15 +250,21 @@ ENABLE_SET_COOKIE = true
|
||||
GC_INTERVAL_TIME = 86400
|
||||
; Session life time, default is 86400
|
||||
SESSION_LIFE_TIME = 86400
|
||||
; Cookie name for CSRF
|
||||
CSRF_COOKIE_NAME = _csrf
|
||||
|
||||
[picture]
|
||||
; The place to picture data, either "server" or "qiniu", default is "server"
|
||||
SERVICE = server
|
||||
; Path to store user uploaded avatars
|
||||
AVATAR_UPLOAD_PATH = data/avatars
|
||||
; Chinese users can choose "duoshuo"
|
||||
; or a custom avatar source, like: http://cn.gravatar.com/avatar/
|
||||
GRAVATAR_SOURCE = gravatar
|
||||
; This value will be forced to be true in offline mode.
|
||||
DISABLE_GRAVATAR = false
|
||||
; Federated avatar lookup uses DNS to discover avatar associated
|
||||
; with emails, see https://www.libravatar.org
|
||||
; This value will be forced to be false in offline mode or Gravatar is disbaled.
|
||||
ENABLE_FEDERATED_AVATAR = true
|
||||
|
||||
[attachment]
|
||||
; Whether attachments are enabled. Defaults to `true`
|
||||
@@ -228,66 +284,43 @@ MAX_FILES = 5
|
||||
; For more information about the format see http://golang.org/pkg/time/#pkg-constants
|
||||
FORMAT =
|
||||
|
||||
; General settings of loggers
|
||||
[log]
|
||||
ROOT_PATH =
|
||||
; Either "console", "file", "conn", "smtp" or "database", default is "console"
|
||||
; Can be "console" and "file", default is "console"
|
||||
; Use comma to separate multiple modes, e.g. "console, file"
|
||||
MODE = console
|
||||
; Buffer length of channel, keep it as it is if you don't know what it is.
|
||||
BUFFER_LEN = 10000
|
||||
; Either "Trace", "Debug", "Info", "Warn", "Error", "Critical", default is "Trace"
|
||||
BUFFER_LEN = 100
|
||||
; Either "Trace", "Info", "Warn", "Error", "Fatal", default is "Trace"
|
||||
LEVEL = Trace
|
||||
|
||||
; For "console" mode only
|
||||
[log.console]
|
||||
; leave empty to inherit
|
||||
LEVEL =
|
||||
|
||||
; For "file" mode only
|
||||
[log.file]
|
||||
; leave empty to inherit
|
||||
LEVEL =
|
||||
; This enables automated log rotate(switch of following options), default is true
|
||||
; This enables automated log rotate (switch of following options)
|
||||
LOG_ROTATE = true
|
||||
; Max line number of single file, default is 1000000
|
||||
MAX_LINES = 1000000
|
||||
; Segment log daily
|
||||
DAILY_ROTATE = true
|
||||
; Max size shift of single file, default is 28 means 1 << 28, 256MB
|
||||
MAX_SIZE_SHIFT = 28
|
||||
; Segment log daily, default is true
|
||||
DAILY_ROTATE = true
|
||||
; Expired days of log file(delete after max days), default is 7
|
||||
; Max line number of single file
|
||||
MAX_LINES = 1000000
|
||||
; Expired days of log file (delete after max days)
|
||||
MAX_DAYS = 7
|
||||
|
||||
; For "conn" mode only
|
||||
[log.conn]
|
||||
; For "slack" mode only
|
||||
[log.slack]
|
||||
; leave empty to inherit
|
||||
LEVEL =
|
||||
; Reconnect host for every single message, default is false
|
||||
RECONNECT_ON_MSG = false
|
||||
; Try to reconnect when connection is lost, default is false
|
||||
RECONNECT = false
|
||||
; Either "tcp", "unix" or "udp", default is "tcp"
|
||||
PROTOCOL = tcp
|
||||
; Host address
|
||||
ADDR =
|
||||
|
||||
; For "smtp" mode only
|
||||
[log.smtp]
|
||||
LEVEL =
|
||||
; Name displayed in mail title, default is "Diagnostic message from serve"
|
||||
SUBJECT = Diagnostic message from serve
|
||||
; Mail server
|
||||
HOST =
|
||||
; Mailer user name and password
|
||||
USER =
|
||||
PASSWD =
|
||||
; Receivers, can be one or more, e.g. ["1@example.com","2@example.com"]
|
||||
RECEIVERS =
|
||||
|
||||
; For "database" mode only
|
||||
[log.database]
|
||||
LEVEL =
|
||||
; Either "mysql" or "postgres"
|
||||
DRIVER =
|
||||
; Based on xorm, e.g.: root:root@localhost/gogs?charset=utf8
|
||||
CONN =
|
||||
; Webhook URL
|
||||
URL =
|
||||
|
||||
[cron]
|
||||
; Enable running cron tasks periodically.
|
||||
@@ -297,35 +330,95 @@ RUN_AT_START = false
|
||||
|
||||
; Update mirrors
|
||||
[cron.update_mirrors]
|
||||
SCHEDULE = @every 1h
|
||||
SCHEDULE = @every 10m
|
||||
|
||||
; Repository health check
|
||||
[cron.repo_health_check]
|
||||
SCHEDULE = @every 24h
|
||||
; Arguments for command 'git fsck', e.g.: "--unreachable --tags"
|
||||
TIMEOUT = 60s
|
||||
; Arguments for command 'git fsck', e.g. "--unreachable --tags"
|
||||
; see more on http://git-scm.com/docs/git-fsck/1.7.5
|
||||
ARGS =
|
||||
ARGS =
|
||||
|
||||
; Check repository statistics
|
||||
[cron.check_repo_stats]
|
||||
RUN_AT_START = true
|
||||
SCHEDULE = @every 24h
|
||||
|
||||
; Cleanup repository archives
|
||||
[cron.repo_archive_cleanup]
|
||||
RUN_AT_START = false
|
||||
SCHEDULE = @every 24h
|
||||
; Time duration to check if archive should be cleaned
|
||||
OLDER_THAN = 24h
|
||||
|
||||
[git]
|
||||
MAX_GIT_DIFF_LINES = 10000
|
||||
; Arguments for command 'git gc', e.g.: "--aggressive --auto"
|
||||
; Disables highlight of added and removed changes
|
||||
DISABLE_DIFF_HIGHLIGHT = false
|
||||
; Max number of lines allowed of a single file in diff view
|
||||
MAX_GIT_DIFF_LINES = 1000
|
||||
; Max number of characters of a line allowed in diff view
|
||||
MAX_GIT_DIFF_LINE_CHARACTERS = 500
|
||||
; Max number of files shown in diff view
|
||||
MAX_GIT_DIFF_FILES = 100
|
||||
; Arguments for command 'git gc', e.g. "--aggressive --auto"
|
||||
; see more on http://git-scm.com/docs/git-gc/1.7.5
|
||||
GC_ARGS =
|
||||
GC_ARGS =
|
||||
|
||||
; Operation timeout in seconds
|
||||
[git.timeout]
|
||||
MIGRATE = 600
|
||||
MIRROR = 300
|
||||
CLONE = 300
|
||||
PULL = 300
|
||||
GC = 60
|
||||
|
||||
[mirror]
|
||||
; Default interval in hours between each check
|
||||
DEFAULT_INTERVAL = 8
|
||||
|
||||
[api]
|
||||
; Max number of items will response in a page
|
||||
MAX_RESPONSE_ITEMS = 50
|
||||
|
||||
[ui]
|
||||
; Number of repositories that are showed in one explore page
|
||||
EXPLORE_PAGING_NUM = 20
|
||||
; Number of issues that are showed in one page
|
||||
ISSUE_PAGING_NUM = 10
|
||||
; Number of maximum commits showed in one activity feed
|
||||
FEED_MAX_COMMIT_NUM = 5
|
||||
; Value of "theme-color" meta tag, used by Android >= 5.0
|
||||
; An invalid color like "none" or "disable" will have the default style
|
||||
; More info: https://developers.google.com/web/updates/2014/11/Support-for-theme-color-in-Chrome-39-for-Android
|
||||
THEME_COLOR_META_TAG = `#ff5343`
|
||||
; Max size in bytes of files to be displayed (default is 8MB)
|
||||
MAX_DISPLAY_FILE_SIZE = 8388608
|
||||
|
||||
[ui.admin]
|
||||
; Number of users that are showed in one page
|
||||
USER_PAGING_NUM = 50
|
||||
; Number of repos that are showed in one page
|
||||
REPO_PAGING_NUM = 50
|
||||
; Number of notices that are showed in one page
|
||||
NOTICE_PAGING_NUM = 25
|
||||
; Number of organization that are showed in one page
|
||||
ORG_PAGING_NUM = 50
|
||||
|
||||
[ui.user]
|
||||
; Number of repos that are showed in one page
|
||||
REPO_PAGING_NUM = 15
|
||||
|
||||
[i18n]
|
||||
LANGS = en-US,zh-CN,zh-HK,de-DE,fr-FR,nl-NL,lv-LV,ru-RU,ja-JP,es-ES,pt-BR,pl-PL,bg-BG,it-IT
|
||||
NAMES = English,简体中文,繁體中文,Deutsch,Français,Nederlands,Latviešu,Русский,日本語,Español,Português do Brasil,Polski,български,Italiano
|
||||
LANGS = en-US,zh-CN,zh-HK,zh-TW,de-DE,fr-FR,nl-NL,lv-LV,ru-RU,ja-JP,es-ES,pt-BR,pl-PL,bg-BG,it-IT,fi-FI,tr-TR,cs-CZ,sr-SP,sv-SE,ko-KR,gl-ES,uk-UA
|
||||
NAMES = English,简体中文,繁體中文(香港),繁體中文(台湾),Deutsch,Français,Nederlands,Latviešu,Русский,日本語,Español,Português do Brasil,Polski,български,Italiano,Suomalainen,Türkçe,čeština,Српски,Svenska,한국어,Galego,Українська
|
||||
|
||||
; Used for datetimepicker
|
||||
[i18n.datelang]
|
||||
en-US = en
|
||||
zh-CN = zh
|
||||
zh-HK = zh-TW
|
||||
zh-TW = zh-TW
|
||||
de-DE = de
|
||||
fr-FR = fr
|
||||
nl-NL = nl
|
||||
@@ -337,6 +430,22 @@ pt-BR = pt-BR
|
||||
pl-PL = pl
|
||||
bg-BG = bg
|
||||
it-IT = it
|
||||
fi-FI = fi
|
||||
tr-TR = tr
|
||||
cs-CZ = cs-CZ
|
||||
sr-SP = sr
|
||||
sv-SE = sv
|
||||
ko-KR = ko
|
||||
gl-ES = gl
|
||||
uk-UA = uk
|
||||
|
||||
; Extension mapping to highlight class
|
||||
; e.g. .toml=ini
|
||||
[highlight.mapping]
|
||||
|
||||
[other]
|
||||
SHOW_FOOTER_BRANDING = false
|
||||
; Show version information about Gogs and Go in the footer
|
||||
SHOW_FOOTER_VERSION = true
|
||||
; Show time of template execution in the footer
|
||||
SHOW_FOOTER_TEMPLATE_LOAD_TIME = true
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
Leiningen.gitignore
|
||||
@@ -1 +0,0 @@
|
||||
C++.gitignore
|
||||
7
conf/label/Default
Normal file
7
conf/label/Default
Normal file
@@ -0,0 +1,7 @@
|
||||
#ee0701 bug
|
||||
#cccccc duplicate
|
||||
#84b6eb enhancement
|
||||
#128a0c help wanted
|
||||
#e6e6e6 invalid
|
||||
#cc317c question
|
||||
#ffffff wontfix
|
||||
@@ -1,39 +0,0 @@
|
||||
Creative Commons CC0 1.0 Universal
|
||||
CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED HEREUNDER.
|
||||
Statement of Purpose
|
||||
|
||||
The laws of most jurisdictions throughout the world automatically confer exclusive Copyright and Related Rights (defined below) upon the creator and subsequent owner(s) (each and all, an "owner") of an original work of authorship and/or a database (each, a "Work").
|
||||
|
||||
Certain owners wish to permanently relinquish those rights to a Work for the purpose of contributing to a commons of creative, cultural and scientific works ("Commons") that the public can reliably and without fear of later claims of infringement build upon, modify, incorporate in other works, reuse and redistribute as freely as possible in any form whatsoever and for any purposes, including without limitation commercial purposes. These owners may contribute to the Commons to promote the ideal of a free culture and the further production of creative, cultural and scientific works, or to gain reputation or greater distribution for their Work in part through the use and efforts of others.
|
||||
|
||||
For these and/or other purposes and motivations, and without any expectation of additional consideration or compensation, the person associating CC0 with a Work (the "Affirmer"), to the extent that he or she is an owner of Copyright and Related Rights in the Work, voluntarily elects to apply CC0 to the Work and publicly distribute the Work under its terms, with knowledge of his or her Copyright and Related Rights in the Work and the meaning and intended legal effect of CC0 on those rights.
|
||||
|
||||
1. Copyright and Related Rights. A Work made available under CC0 may be protected by copyright and related or neighboring rights ("Copyright and Related Rights"). Copyright and Related Rights include, but are not limited to, the following:
|
||||
|
||||
i. the right to reproduce, adapt, distribute, perform, display, communicate, and translate a Work;
|
||||
|
||||
ii. moral rights retained by the original author(s) and/or performer(s);
|
||||
|
||||
iii. publicity and privacy rights pertaining to a person's image or likeness depicted in a Work;
|
||||
|
||||
iv. rights protecting against unfair competition in regards to a Work, subject to the limitations in paragraph 4(a), below;
|
||||
|
||||
v. rights protecting the extraction, dissemination, use and reuse of data in a Work;
|
||||
|
||||
vi. database rights (such as those arising under Directive 96/9/EC of the European Parliament and of the Council of 11 March 1996 on the legal protection of databases, and under any national implementation thereof, including any amended or successor version of such directive); and
|
||||
|
||||
vii. other similar, equivalent or corresponding rights throughout the world based on applicable law or treaty, and any national implementations thereof.
|
||||
|
||||
2. Waiver. To the greatest extent permitted by, but not in contravention of, applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and unconditionally waives, abandons, and surrenders all of Affirmer's Copyright and Related Rights and associated claims and causes of action, whether now known or unknown (including existing as well as future claims and causes of action), in the Work (i) in all territories worldwide, (ii) for the maximum duration provided by applicable law or treaty (including future time extensions), (iii) in any current or future medium and for any number of copies, and (iv) for any purpose whatsoever, including without limitation commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each member of the public at large and to the detriment of Affirmer's heirs and successors, fully intending that such Waiver shall not be subject to revocation, rescission, cancellation, termination, or any other legal or equitable action to disrupt the quiet enjoyment of the Work by the public as contemplated by Affirmer's express Statement of Purpose.
|
||||
|
||||
3. Public License Fallback. Should any part of the Waiver for any reason be judged legally invalid or ineffective under applicable law, then the Waiver shall be preserved to the maximum extent permitted taking into account Affirmer's express Statement of Purpose. In addition, to the extent the Waiver is so judged Affirmer hereby grants to each affected person a royalty-free, non transferable, non sublicensable, non exclusive, irrevocable and unconditional license to exercise Affirmer's Copyright and Related Rights in the Work (i) in all territories worldwide, (ii) for the maximum duration provided by applicable law or treaty (including future time extensions), (iii) in any current or future medium and for any number of copies, and (iv) for any purpose whatsoever, including without limitation commercial, advertising or promotional purposes (the "License"). The License shall be deemed effective as of the date CC0 was applied by Affirmer to the Work. Should any part of the License for any reason be judged legally invalid or ineffective under applicable law, such partial invalidity or ineffectiveness shall not invalidate the remainder of the License, and in such case Affirmer hereby affirms that he or she will not (i) exercise any of his or her remaining Copyright and Related Rights in the Work or (ii) assert any associated claims and causes of action with respect to the Work, in either case contrary to Affirmer's express Statement of Purpose.
|
||||
|
||||
4. Limitations and Disclaimers.
|
||||
|
||||
a. No trademark or patent rights held by Affirmer are waived, abandoned, surrendered, licensed or otherwise affected by this document.
|
||||
|
||||
b. Affirmer offers the Work as-is and makes no representations or warranties of any kind concerning the Work, express, implied, statutory or otherwise, including without limitation warranties of title, merchantability, fitness for a particular purpose, non infringement, or the absence of latent or other defects, accuracy, or the present or absence of errors, whether or not discoverable, all to the greatest extent permissible under applicable law.
|
||||
|
||||
c. Affirmer disclaims responsibility for clearing rights of other persons that may apply to the Work or any use thereof, including without limitation any person's Copyright and Related Rights in the Work. Further, Affirmer disclaims responsibility for obtaining any necessary consents, permissions or other rights required for any use of the Work.
|
||||
|
||||
d. Affirmer understands and acknowledges that Creative Commons is not a party to this document and has no duty or obligation with respect to this CC0 or use of the Work.
|
||||
@@ -1,6 +1,5 @@
|
||||
ISC License:
|
||||
Copyright (c) 2004-2010 by Internet Systems Consortium, Inc. ("ISC")
|
||||
Copyright (c) 1995-2003 by Internet Software Consortium
|
||||
Copyright (c) Year(s), Company or Person's Name
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
|
||||
|
||||
|
||||
@@ -1,20 +1,79 @@
|
||||
# This file lists all PUBLIC individuals having contributed content to the translation.
|
||||
# Order of name is meaningless.
|
||||
# Entries are in alphabetical order.
|
||||
|
||||
Akihiro YAGASAKI <yaggytter@momiage.com>
|
||||
Alexander Steinhöfer <kontakt@lx-s.de>
|
||||
Alexandre Magno <alexandre.mbm@gmail.com>
|
||||
Barış Arda Yılmaz <ardayilmazgamer@gmail.com>
|
||||
Christoph Kisfeld <christoph.kisfeld@gmail.com>
|
||||
Daniel Speichert <daniel@speichert.pl>
|
||||
Gregor Santner <gdev@live.de>
|
||||
Huimin Wang <wanghm2009@hotmail.co.jp>
|
||||
ilko <email>
|
||||
Thomas Fanninger <gogs.thomas@fanninger.at>
|
||||
Łukasz Jan Niemier <lukasz@niemier.pl>
|
||||
Lafriks <lafriks@gmail.com>
|
||||
Luc Stepniewski <luc@stepniewski.fr>
|
||||
Miguel de la Cruz <miguel@mcrx.me>
|
||||
Marc Schiller <marc@schiller.im>
|
||||
Morten Sørensen <klim8d@gmail.com>
|
||||
Natan Albuquerque <natanalbuquerque5@gmail.com>
|
||||
Adam Strzelecki <ono AT java DOT pl>
|
||||
Adrian Verde <me AT adrianverde DOT com>
|
||||
Akihiro YAGASAKI <yaggytter AT momiage DOT com>
|
||||
Aleksejs Grocevs <aleksejs AT grocevs DOT pro>
|
||||
Aleksey Tarakin <hukendo AT yandex DOT ru>
|
||||
Alexander Steinhöfer <kontakt AT lx-s DOT de>
|
||||
Alexandre Espinosa Menor <aemenor DOT gmail DOT com>
|
||||
Alexandre Magno <alexandre DOT mbm AT gmail DOT com>
|
||||
Anders B. Hansen <anders AT birkoe DOT com>
|
||||
András Schenkerik <moviesharkteam AT gmail DOT com>
|
||||
Andrey Nering <andrey AT nering DOT com DOT br>
|
||||
Andrey Paskal <apaskal AT gmail DOT com>
|
||||
Andrey Solomatin <toadron AT yandex DOT ru>
|
||||
Antoine GIRARD <sapk AT sapk DOT fr>
|
||||
Arthur Aslanyan <arthur DOT e DOT aslanyan AT gmail DOT com>
|
||||
Aurelien Darragon <aurelien DOT darragon AT gmail DOT com>
|
||||
Barış Arda Yılmaz <ardayilmazgamer AT gmail DOT com>
|
||||
Camille Baronnet <gogs AT camillebaronnet DOT fr>
|
||||
Christoph Kisfeld <christoph DOT kisfeld AT gmail DOT com>
|
||||
Cysioland
|
||||
Damaris Padieu <damizx AT hotmail DOT fr>
|
||||
Daniel Speichert <daniel AT speichert DOT pl>
|
||||
David Yzaguirre <dvdyzag AT gmail DOT com>
|
||||
Denys Khomenko
|
||||
Dmitriy Nogay <me AT catwhocode DOT ga>
|
||||
Enrico Testori hypertesto AT gmail DOT com
|
||||
Ezequiel Gonzalez Rial <gonrial AT gmail DOT com>
|
||||
Farhan Naysee <wpmagic70 AT gmail DOT com>
|
||||
Gabriel Dugny <gabriel DOT dugny AT gmail DOT com>
|
||||
Ganesha <reekoheek AT gmail DOT com>
|
||||
Gregor Santner <gdev AT live DOT de>
|
||||
Halil Kaya <halil AT halilkaya DOT net>
|
||||
Hamid Feizabadi <hamidfzm AT gmail DOT com>
|
||||
Huimin Wang <wanghm2009 AT hotmail DOT co DOT jp>
|
||||
ilko <kontact-mr.k AT outlook DOT com">
|
||||
Ilya Makarov
|
||||
Jamie Mansfield <dev AT jamierocks DOT uk>
|
||||
Javier Ortiz Bultron <javier DOT ortiz DOT 78 AT gmail DOT com>
|
||||
Jean THOMAS <contact AT tibounise DOT com>
|
||||
Jonas De Kegel <jonasgithub [AT] gmail [DOT] com>
|
||||
Joubert RedRat <me+github AT redrat DOT com DOT br>
|
||||
Juraj Bubniak <contact AT jbub DOT eu>
|
||||
Lafriks <lafriks AT gmail DOT com>
|
||||
Lauri Ojansivu <x AT xet7 DOT org>
|
||||
Luca Bozzo <luca AT bozzo DOT it>
|
||||
Luca Kröger <l DOT kroeger01 AT gmail DOT com>
|
||||
Luc Stepniewski <luc AT stepniewski DOT fr>
|
||||
Łukasz Jan Niemier <lukasz AT niemier DOT pl>
|
||||
Marc Schiller <marc AT schiller DOT im>
|
||||
Marvin Menzerath <github AT marvin-menzerath DOT de>
|
||||
Michael Härtl <haertl DOT mike AT gmail DOT com>
|
||||
Miguel de la Cruz <miguel AT mcrx DOT me>
|
||||
Mikhail Burdin <xdshot9000 AT gmail DOT com>
|
||||
Morten Sørensen <klim8d AT gmail DOT com>
|
||||
Muhammad Fawwaz Orabi <mfawwaz93 AT gmail DOT com>
|
||||
Nakao Takamasa <at.mattenn AT gmail DOT com>
|
||||
Natan Albuquerque <natanalbuquerque5 AT gmail DOT com>
|
||||
Odilon Junior <odilon DOT junior93 AT gmail DOT com>
|
||||
Oleksandr Yermakov <olexander DOT yermakov AT gmail DOT com>
|
||||
Óscar García Amor <ogarcia AT connectical DOT com>
|
||||
Pablo Saavedra <psaavedra AT igalia DOT com>
|
||||
Pierre Prinetti >meatqrawldotnet<
|
||||
Richard Bukovansky <richard DOT bukovansky @ gmail DOT com>
|
||||
Robert Nuske <robert DOT nuske AT web DOT de>
|
||||
Robin Hübner <profan AT prfn DOT se>
|
||||
Rste Risafov <risafov AT lazy DOT com>
|
||||
SeongJae Park <sj38 DOT park AT gmail DOT com>
|
||||
Sergey Stepanov <sergystepanov AT gmail DOT com>
|
||||
Thomas Fanninger <gogs DOT thomas AT fanninger DOT at>
|
||||
Tilmann Bach <tilmann AT outlook DOT com>
|
||||
Toni Villena Jiménez <tonivj5 AT gmail DOT com>
|
||||
Vladimir Jigulin mogaika AT yandex DOT ru
|
||||
Vladimir Vissoultchev <wqweto AT gmail DOT com>
|
||||
Vongola <me AT vongola DOT tw>
|
||||
YJSoft <yjsoft AT yjsoft DOT pe DOT kr>
|
||||
Oscar Quisbert <quisbert_karos AT outlook DOT com>
|
||||
|
||||
2206
conf/locale/locale_bg-BG.ini
Executable file → Normal file
2206
conf/locale/locale_bg-BG.ini
Executable file → Normal file
File diff suppressed because it is too large
Load Diff
1248
conf/locale/locale_cs-CZ.ini
Normal file
1248
conf/locale/locale_cs-CZ.ini
Normal file
File diff suppressed because it is too large
Load Diff
2206
conf/locale/locale_de-DE.ini
Executable file → Normal file
2206
conf/locale/locale_de-DE.ini
Executable file → Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
2206
conf/locale/locale_es-ES.ini
Executable file → Normal file
2206
conf/locale/locale_es-ES.ini
Executable file → Normal file
File diff suppressed because it is too large
Load Diff
1248
conf/locale/locale_fi-FI.ini
Normal file
1248
conf/locale/locale_fi-FI.ini
Normal file
File diff suppressed because it is too large
Load Diff
2206
conf/locale/locale_fr-FR.ini
Executable file → Normal file
2206
conf/locale/locale_fr-FR.ini
Executable file → Normal file
File diff suppressed because it is too large
Load Diff
1248
conf/locale/locale_gl-ES.ini
Normal file
1248
conf/locale/locale_gl-ES.ini
Normal file
File diff suppressed because it is too large
Load Diff
2206
conf/locale/locale_it-IT.ini
Executable file → Normal file
2206
conf/locale/locale_it-IT.ini
Executable file → Normal file
File diff suppressed because it is too large
Load Diff
2206
conf/locale/locale_ja-JP.ini
Executable file → Normal file
2206
conf/locale/locale_ja-JP.ini
Executable file → Normal file
File diff suppressed because it is too large
Load Diff
1249
conf/locale/locale_ko-KR.ini
Normal file
1249
conf/locale/locale_ko-KR.ini
Normal file
File diff suppressed because it is too large
Load Diff
2206
conf/locale/locale_lv-LV.ini
Executable file → Normal file
2206
conf/locale/locale_lv-LV.ini
Executable file → Normal file
File diff suppressed because it is too large
Load Diff
2206
conf/locale/locale_nl-NL.ini
Executable file → Normal file
2206
conf/locale/locale_nl-NL.ini
Executable file → Normal file
File diff suppressed because it is too large
Load Diff
2206
conf/locale/locale_pl-PL.ini
Executable file → Normal file
2206
conf/locale/locale_pl-PL.ini
Executable file → Normal file
File diff suppressed because it is too large
Load Diff
2206
conf/locale/locale_pt-BR.ini
Executable file → Normal file
2206
conf/locale/locale_pt-BR.ini
Executable file → Normal file
File diff suppressed because it is too large
Load Diff
2206
conf/locale/locale_ru-RU.ini
Executable file → Normal file
2206
conf/locale/locale_ru-RU.ini
Executable file → Normal file
File diff suppressed because it is too large
Load Diff
1248
conf/locale/locale_sr-SP.ini
Normal file
1248
conf/locale/locale_sr-SP.ini
Normal file
File diff suppressed because it is too large
Load Diff
1248
conf/locale/locale_sv-SE.ini
Normal file
1248
conf/locale/locale_sv-SE.ini
Normal file
File diff suppressed because it is too large
Load Diff
1248
conf/locale/locale_tr-TR.ini
Normal file
1248
conf/locale/locale_tr-TR.ini
Normal file
File diff suppressed because it is too large
Load Diff
1248
conf/locale/locale_uk-UA.ini
Normal file
1248
conf/locale/locale_uk-UA.ini
Normal file
File diff suppressed because it is too large
Load Diff
2206
conf/locale/locale_zh-CN.ini
Executable file → Normal file
2206
conf/locale/locale_zh-CN.ini
Executable file → Normal file
File diff suppressed because it is too large
Load Diff
2206
conf/locale/locale_zh-HK.ini
Executable file → Normal file
2206
conf/locale/locale_zh-HK.ini
Executable file → Normal file
File diff suppressed because it is too large
Load Diff
1248
conf/locale/locale_zh-TW.ini
Normal file
1248
conf/locale/locale_zh-TW.ini
Normal file
File diff suppressed because it is too large
Load Diff
1905
config.codekit
1905
config.codekit
File diff suppressed because it is too large
Load Diff
@@ -1,12 +0,0 @@
|
||||
web:
|
||||
build: .
|
||||
links:
|
||||
- mysql
|
||||
ports:
|
||||
- "3000:3000"
|
||||
|
||||
mysql:
|
||||
image: mysql
|
||||
environment:
|
||||
- MYSQL_ROOT_PASSWORD=gogs
|
||||
- MYSQL_DATABASE=gogs
|
||||
@@ -1,12 +1,12 @@
|
||||
# Docker for Gogs
|
||||
|
||||
Visit [Docker Hub](https://hub.docker.com/r/gogs/gogs/) see all available tags.
|
||||
Visit [Docker Hub](https://hub.docker.com/r/gogs/) / [Docker Store](https://store.docker.com/community/images/gogs/gogs) see all available images and tags.
|
||||
|
||||
## Usage
|
||||
|
||||
To keep your data out of Docker container, we do a volume(`/var/gogs` -> `/data`) here, and you can change it based on your situation.
|
||||
To keep your data out of Docker container, we do a volume (`/var/gogs` -> `/data`) here, and you can change it based on your situation.
|
||||
|
||||
```
|
||||
```sh
|
||||
# Pull image from Docker Hub.
|
||||
$ docker pull gogs/gogs
|
||||
|
||||
@@ -17,12 +17,14 @@ $ mkdir -p /var/gogs
|
||||
$ docker run --name=gogs -p 10022:22 -p 10080:3000 -v /var/gogs:/data gogs/gogs
|
||||
|
||||
# Use `docker start` if you have stopped it.
|
||||
$ docker start gogs
|
||||
$ docker start gogs
|
||||
```
|
||||
|
||||
Note: It is important to map the Gogs ssh service from the container to the host and set the appropriate SSH Port and URI settings when setting up Gogs for the first time. To access and clone Gogs Git repositories with the above configuration you would use: `git clone ssh://git@hostname:10022/username/myrepo.git` for example.
|
||||
|
||||
Files will be store in local path `/var/gogs` in my case.
|
||||
|
||||
Directory `/var/gogs` keeps Git repoistories and Gogs data:
|
||||
Directory `/var/gogs` keeps Git repositories and Gogs data:
|
||||
|
||||
/var/gogs
|
||||
|-- git
|
||||
@@ -33,31 +35,81 @@ Directory `/var/gogs` keeps Git repoistories and Gogs data:
|
||||
|-- conf
|
||||
|-- data
|
||||
|-- log
|
||||
|-- templates
|
||||
|
||||
### Volume With Data Container
|
||||
|
||||
If you're more comfortable with mounting data to a data container, the commands you execute at the first time will look like as follows:
|
||||
|
||||
```sh
|
||||
# Create data container
|
||||
docker run --name=gogs-data --entrypoint /bin/true gogs/gogs
|
||||
|
||||
# Use `docker run` for the first time.
|
||||
docker run --name=gogs --volumes-from gogs-data -p 10022:22 -p 10080:3000 gogs/gogs
|
||||
```
|
||||
|
||||
#### Using Docker 1.9 Volume Command
|
||||
|
||||
```sh
|
||||
# Create docker volume.
|
||||
$ docker volume create --name gogs-data
|
||||
|
||||
# Use `docker run` for the first time.
|
||||
$ docker run --name=gogs -p 10022:22 -p 10080:3000 -v gogs-data:/data gogs/gogs
|
||||
```
|
||||
|
||||
## Settings
|
||||
|
||||
### Application
|
||||
|
||||
Most of settings are obvious and easy to understand, but there are some settings can be confusing by running Gogs inside Docker:
|
||||
|
||||
- **Repository Root Path**: keep it as default value `/home/git/gogs-repositories` because `start.sh` already made a symbolic link for you.
|
||||
- **Run User**: keep it as default value `git` because `start.sh` already setup a user with name `git`.
|
||||
- **Domain**: fill in with Docker container IP(e.g. `192.168.99.100`).
|
||||
- **SSH Port**: Use the exposed port from Docker container. For example, your SSH server listens on `22` inside Docker, but you expose it by `10022:22`, then use `10022` for this value.
|
||||
- **HTTP Port**: Use port you want Gogs to listen on inside Docker container. For example, your Gogs listens on `3000` inside Docker, and you expose it by `10080:3000`, but you still use `3000` for this value.
|
||||
- **Application URL**: Use combination of **Domain** and **exposed HTTP Port** values(e.g. `http://192.168.99.100:10080/`).
|
||||
- **Domain**: fill in with Docker container IP (e.g. `192.168.99.100`). But if you want to access your Gogs instance from a different physical machine, please fill in with the hostname or IP address of the Docker host machine.
|
||||
- **SSH Port**: Use the exposed port from Docker container. For example, your SSH server listens on `22` inside Docker, **but** you expose it by `10022:22`, then use `10022` for this value. **Builtin SSH server is not recommended inside Docker Container**
|
||||
- **HTTP Port**: Use port you want Gogs to listen on inside Docker container. For example, your Gogs listens on `3000` inside Docker, **and** you expose it by `10080:3000`, but you still use `3000` for this value.
|
||||
- **Application URL**: Use combination of **Domain** and **exposed HTTP Port** values (e.g. `http://192.168.99.100:10080/`).
|
||||
|
||||
Full documentation of settings can be found [here](http://gogs.io/docs/advanced/configuration_cheat_sheet.html).
|
||||
Full documentation of application settings can be found [here](https://gogs.io/docs/advanced/configuration_cheat_sheet.html).
|
||||
|
||||
## Troubleshooting
|
||||
### Container Options
|
||||
|
||||
If you see the following error:
|
||||
This container have some options available via environment variables, these options are opt-in features that can help the administration of this container:
|
||||
|
||||
```
|
||||
checkVersion()] [E] Binary and template file version does not match
|
||||
```
|
||||
- **SOCAT_LINK**:
|
||||
- <u>Possible value:</u>
|
||||
`true`, `false`, `1`, `0`
|
||||
- <u>Default:</u>
|
||||
`true`
|
||||
- <u>Action:</u>
|
||||
Bind linked docker container to localhost socket using socat.
|
||||
Any exported port from a linked container will be binded to the matching port on localhost.
|
||||
- <u>Disclaimer:</u>
|
||||
As this option rely on the environment variable created by docker when a container is linked, this option should be deactivated in managed environment such as Rancher or Kubernetes (set to `0` or `false`)
|
||||
- **RUN_CROND**:
|
||||
- <u>Possible value:</u>
|
||||
`true`, `false`, `1`, `0`
|
||||
- <u>Default:</u>
|
||||
`false`
|
||||
- <u>Action:</u>
|
||||
Request crond to be run inside the container. Its default configuration will periodically run all scripts from `/etc/periodic/${period}` but custom crontabs can be added to `/var/spool/cron/crontabs/`.
|
||||
|
||||
Run `rm -fr /var/gogs/gogs/templates/` should fix this it. Just remember to backup templates file if you have made modifications youself.
|
||||
## Upgrade
|
||||
|
||||
:exclamation::exclamation::exclamation:<span style="color: red">**Make sure you have volumed data to somewhere outside Docker container**</span>:exclamation::exclamation::exclamation:
|
||||
|
||||
Steps to upgrade Gogs with Docker:
|
||||
|
||||
- `docker pull gogs/gogs`
|
||||
- `docker stop gogs`
|
||||
- `docker rm gogs`
|
||||
- Finally, create container as the first time and don't forget to do same volume and port mapping.
|
||||
|
||||
## Known Issues
|
||||
|
||||
- [Use ctrl+c when clone through SSH makes Docker exit unexpectedly](https://github.com/gogits/gogs/issues/1499)
|
||||
- The docker container can not currently be build on Raspberry 1 (armv6l) as our base image `alpine` does not have a `go` package available for this platform.
|
||||
|
||||
## Useful Links
|
||||
|
||||
- [Share port 22 between Gogs inside Docker & the local system](http://www.ateijelo.com/blog/2016/07/09/share-port-22-between-docker-gogs-ssh-and-local-system)
|
||||
32
docker/build-go.sh
Executable file
32
docker/build-go.sh
Executable file
@@ -0,0 +1,32 @@
|
||||
#!/bin/sh
|
||||
# Build GO version as specified in Dockerfile
|
||||
|
||||
set -x
|
||||
set -e
|
||||
|
||||
# Components versions
|
||||
export GOLANG_VERSION="1.8"
|
||||
export GOLANG_SRC_URL="https://golang.org/dl/go$GOLANG_VERSION.src.tar.gz"
|
||||
export GOLANG_SRC_SHA256="406865f587b44be7092f206d73fc1de252600b79b3cacc587b74b5ef5c623596"
|
||||
|
||||
|
||||
# Install build tools
|
||||
apk add --no-cache --no-progress --virtual build-deps-go gcc musl-dev openssl go
|
||||
|
||||
export GOROOT_BOOTSTRAP="$(go env GOROOT)"
|
||||
|
||||
# Download Go
|
||||
wget -q "$GOLANG_SRC_URL" -O golang.tar.gz
|
||||
echo "$GOLANG_SRC_SHA256 golang.tar.gz" | sha256sum -c -
|
||||
tar -C /usr/local -xzf golang.tar.gz
|
||||
rm golang.tar.gz
|
||||
|
||||
# Build
|
||||
cd /usr/local/go/src
|
||||
# see https://golang.org/issue/14851
|
||||
patch -p2 -i /app/gogs/build/docker/no-pic.patch
|
||||
./make.bash
|
||||
|
||||
# Clean
|
||||
rm /app/gogs/build/docker/*.patch
|
||||
apk del build-deps-go
|
||||
30
docker/build.sh
Executable file
30
docker/build.sh
Executable file
@@ -0,0 +1,30 @@
|
||||
#!/bin/sh
|
||||
set -x
|
||||
set -e
|
||||
|
||||
# Set temp environment vars
|
||||
export GOPATH=/tmp/go
|
||||
export PATH=/usr/local/go/bin:${PATH}:${GOPATH}/bin
|
||||
export GO15VENDOREXPERIMENT=1
|
||||
|
||||
# Install build deps
|
||||
apk --no-cache --no-progress add --virtual build-deps build-base linux-pam-dev
|
||||
|
||||
# Build Gogs
|
||||
mkdir -p ${GOPATH}/src/github.com/gogits/
|
||||
ln -s /app/gogs/build ${GOPATH}/src/github.com/gogits/gogs
|
||||
cd ${GOPATH}/src/github.com/gogits/gogs
|
||||
# Needed since git 2.9.3 or 2.9.4
|
||||
git config --global http.https://gopkg.in.followRedirects true
|
||||
go get -v -tags "sqlite cert pam" ./...
|
||||
make build TAGS="sqlite cert pam"
|
||||
|
||||
# Cleanup GOPATH
|
||||
rm -r $GOPATH
|
||||
|
||||
# Remove build deps
|
||||
apk --no-progress del build-deps
|
||||
|
||||
# Create git user for Gogs
|
||||
adduser -H -D -g 'Gogs Git User' git -h /data/git -s /bin/bash && passwd -u git
|
||||
echo "export GOGS_CUSTOM=${GOGS_CUSTOM}" >> /etc/profile
|
||||
22
docker/finalize.sh
Executable file
22
docker/finalize.sh
Executable file
@@ -0,0 +1,22 @@
|
||||
#!/bin/sh
|
||||
# Finalize the build
|
||||
|
||||
set -x
|
||||
set -e
|
||||
|
||||
# Move to final place
|
||||
mv /app/gogs/build/gogs /app/gogs/
|
||||
mv /app/gogs/build/templates /app/gogs/
|
||||
mv /app/gogs/build/public /app/gogs/
|
||||
mv /app/gogs/build/docker /app/gogs/
|
||||
|
||||
# Final cleaning
|
||||
rm -rf /app/gogs/build
|
||||
rm /app/gogs/docker/build.sh
|
||||
rm /app/gogs/docker/build-go.sh
|
||||
rm /app/gogs/docker/finalize.sh
|
||||
rm /app/gogs/docker/nsswitch.conf
|
||||
rm /app/gogs/docker/README.md
|
||||
|
||||
rm -rf /tmp/go
|
||||
rm -rf /usr/local/go
|
||||
16
docker/no-pic.patch
Normal file
16
docker/no-pic.patch
Normal file
@@ -0,0 +1,16 @@
|
||||
diff --git a/src/cmd/link/internal/ld/lib.go b/src/cmd/link/internal/ld/lib.go
|
||||
index 14f4fa9..5599307 100644
|
||||
--- a/src/cmd/link/internal/ld/lib.go
|
||||
+++ b/src/cmd/link/internal/ld/lib.go
|
||||
@@ -1272,6 +1272,11 @@ func hostlink() {
|
||||
argv = append(argv, peimporteddlls()...)
|
||||
}
|
||||
|
||||
+ // The Go linker does not currently support building PIE
|
||||
+ // executables when using the external linker. See:
|
||||
+ // https://github.com/golang/go/issues/6940
|
||||
+ argv = append(argv, "-fno-PIC")
|
||||
+
|
||||
if l.Debugvlog != 0 {
|
||||
l.Logf("%5.2f host link:", obj.Cputime())
|
||||
for _, v := range argv {
|
||||
16
docker/nsswitch.conf
Normal file
16
docker/nsswitch.conf
Normal file
@@ -0,0 +1,16 @@
|
||||
# /etc/nsswitch.conf
|
||||
|
||||
passwd: compat
|
||||
group: compat
|
||||
shadow: compat
|
||||
|
||||
hosts: files dns
|
||||
networks: files
|
||||
|
||||
protocols: db files
|
||||
services: db files
|
||||
ethers: db files
|
||||
rpc: db files
|
||||
|
||||
netgroup: nis
|
||||
|
||||
5
docker/s6/.s6-svscan/finish
Executable file
5
docker/s6/.s6-svscan/finish
Executable file
@@ -0,0 +1,5 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Cleanup SOCAT services and s6 event folder
|
||||
rm -rf $(find /app/gogs/docker/s6/ -name 'event')
|
||||
rm -rf /app/gogs/docker/s6/SOCAT_*
|
||||
0
docker/s6/crond/down
Normal file
0
docker/s6/crond/down
Normal file
9
docker/s6/crond/run
Executable file
9
docker/s6/crond/run
Executable file
@@ -0,0 +1,9 @@
|
||||
#!/bin/sh
|
||||
# Crontabs are located by default in /var/spool/cron/crontabs/
|
||||
# The default configuration is also calling all the scripts in /etc/periodic/${period}
|
||||
|
||||
if test -f ./setup; then
|
||||
source ./setup
|
||||
fi
|
||||
|
||||
exec gosu root /usr/sbin/crond -fS
|
||||
8
docker/s6/gogs/run
Executable file
8
docker/s6/gogs/run
Executable file
@@ -0,0 +1,8 @@
|
||||
#!/bin/sh
|
||||
|
||||
if test -f ./setup; then
|
||||
source ./setup
|
||||
fi
|
||||
|
||||
export USER=git
|
||||
exec gosu $USER /app/gogs/gogs web
|
||||
23
docker/s6/gogs/setup
Executable file
23
docker/s6/gogs/setup
Executable file
@@ -0,0 +1,23 @@
|
||||
#!/bin/sh
|
||||
|
||||
if ! test -d ~git/.ssh; then
|
||||
mkdir -p ~git/.ssh
|
||||
chmod 700 ~git/.ssh
|
||||
fi
|
||||
|
||||
if ! test -f ~git/.ssh/environment; then
|
||||
echo "GOGS_CUSTOM=${GOGS_CUSTOM}" > ~git/.ssh/environment
|
||||
chmod 600 ~git/.ssh/environment
|
||||
fi
|
||||
|
||||
cd /app/gogs
|
||||
|
||||
# Link volumed data with app data
|
||||
ln -sf /data/gogs/log ./log
|
||||
ln -sf /data/gogs/data ./data
|
||||
|
||||
# Backward Compatibility with Gogs Container v0.6.15
|
||||
ln -sf /data/git /home/git
|
||||
|
||||
chown -R git:git /data /app/gogs ~git/
|
||||
chmod 0755 /data /data/gogs ~git/
|
||||
7
docker/s6/openssh/run
Executable file
7
docker/s6/openssh/run
Executable file
@@ -0,0 +1,7 @@
|
||||
#!/bin/sh
|
||||
|
||||
if test -f ./setup; then
|
||||
source ./setup
|
||||
fi
|
||||
|
||||
exec gosu root /usr/sbin/sshd -D -f /app/gogs/docker/sshd_config
|
||||
23
docker/s6/openssh/setup
Executable file
23
docker/s6/openssh/setup
Executable file
@@ -0,0 +1,23 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Check if host keys are present, else create them
|
||||
if ! test -f /data/ssh/ssh_host_rsa_key; then
|
||||
ssh-keygen -q -f /data/ssh/ssh_host_rsa_key -N '' -t rsa
|
||||
fi
|
||||
|
||||
if ! test -f /data/ssh/ssh_host_dsa_key; then
|
||||
ssh-keygen -q -f /data/ssh/ssh_host_dsa_key -N '' -t dsa
|
||||
fi
|
||||
|
||||
if ! test -f /data/ssh/ssh_host_ecdsa_key; then
|
||||
ssh-keygen -q -f /data/ssh/ssh_host_ecdsa_key -N '' -t ecdsa
|
||||
fi
|
||||
|
||||
if ! test -f /data/ssh/ssh_host_ed25519_key; then
|
||||
ssh-keygen -q -f /data/ssh/ssh_host_ed25519_key -N '' -t ed25519
|
||||
fi
|
||||
|
||||
# Set correct right to ssh keys
|
||||
chown -R root:root /data/ssh/*
|
||||
chmod 0700 /data/ssh
|
||||
chmod 0600 /data/ssh/*
|
||||
7
docker/s6/syslogd/run
Executable file
7
docker/s6/syslogd/run
Executable file
@@ -0,0 +1,7 @@
|
||||
#!/bin/sh
|
||||
|
||||
if test -f ./setup; then
|
||||
source ./setup
|
||||
fi
|
||||
|
||||
exec gosu root /sbin/syslogd -nS -O-
|
||||
16
docker/sshd_config
Normal file
16
docker/sshd_config
Normal file
@@ -0,0 +1,16 @@
|
||||
Port 22
|
||||
AddressFamily any
|
||||
ListenAddress 0.0.0.0
|
||||
ListenAddress ::
|
||||
Protocol 2
|
||||
LogLevel INFO
|
||||
HostKey /data/ssh/ssh_host_rsa_key
|
||||
HostKey /data/ssh/ssh_host_dsa_key
|
||||
HostKey /data/ssh/ssh_host_ecdsa_key
|
||||
HostKey /data/ssh/ssh_host_ed25519_key
|
||||
PermitRootLogin no
|
||||
AuthorizedKeysFile .ssh/authorized_keys
|
||||
PasswordAuthentication no
|
||||
UsePrivilegeSeparation no
|
||||
PermitUserEnvironment yes
|
||||
AllowUsers git
|
||||
100
docker/start.sh
100
docker/start.sh
@@ -1,47 +1,65 @@
|
||||
#!/bin/bash -
|
||||
#
|
||||
#!/bin/sh
|
||||
|
||||
if ! test -d /data/gogs
|
||||
then
|
||||
mkdir -p /var/run/sshd
|
||||
mkdir -p /data/gogs/data /data/gogs/conf /data/gogs/log /data/git
|
||||
create_socat_links() {
|
||||
# Bind linked docker container to localhost socket using socat
|
||||
USED_PORT="3000:22"
|
||||
while read NAME ADDR PORT; do
|
||||
if test -z "$NAME$ADDR$PORT"; then
|
||||
continue
|
||||
elif echo $USED_PORT | grep -E "(^|:)$PORT($|:)" > /dev/null; then
|
||||
echo "init:socat | Can't bind linked container ${NAME} to localhost, port ${PORT} already in use" 1>&2
|
||||
else
|
||||
SERV_FOLDER=/app/gogs/docker/s6/SOCAT_${NAME}_${PORT}
|
||||
mkdir -p ${SERV_FOLDER}
|
||||
CMD="socat -ls TCP4-LISTEN:${PORT},fork,reuseaddr TCP4:${ADDR}:${PORT}"
|
||||
echo -e "#!/bin/sh\nexec $CMD" > ${SERV_FOLDER}/run
|
||||
chmod +x ${SERV_FOLDER}/run
|
||||
USED_PORT="${USED_PORT}:${PORT}"
|
||||
echo "init:socat | Linked container ${NAME} will be binded to localhost on port ${PORT}" 1>&2
|
||||
fi
|
||||
done << EOT
|
||||
$(env | sed -En 's|(.*)_PORT_([0-9]+)_TCP=tcp://(.*):([0-9]+)|\1 \3 \4|p')
|
||||
EOT
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
# Cleanup SOCAT services and s6 event folder
|
||||
# On start and on shutdown in case container has been killed
|
||||
rm -rf $(find /app/gogs/docker/s6/ -name 'event')
|
||||
rm -rf /app/gogs/docker/s6/SOCAT_*
|
||||
}
|
||||
|
||||
create_volume_subfolder() {
|
||||
# Create VOLUME subfolder
|
||||
for f in /data/gogs/data /data/gogs/conf /data/gogs/log /data/git /data/ssh; do
|
||||
if ! test -d $f; then
|
||||
mkdir -p $f
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
cleanup
|
||||
create_volume_subfolder
|
||||
|
||||
LINK=$(echo "$SOCAT_LINK" | tr '[:upper:]' '[:lower:]')
|
||||
if [ "$LINK" = "false" -o "$LINK" = "0" ]; then
|
||||
echo "init:socat | Will not try to create socat links as requested" 1>&2
|
||||
else
|
||||
create_socat_links
|
||||
fi
|
||||
|
||||
if ! test -d /data/ssh
|
||||
then
|
||||
mkdir /data/ssh
|
||||
ssh-keygen -q -f /data/ssh/ssh_host_key -N '' -t rsa1
|
||||
ssh-keygen -q -f /data/ssh/ssh_host_rsa_key -N '' -t rsa
|
||||
ssh-keygen -q -f /data/ssh/ssh_host_dsa_key -N '' -t dsa
|
||||
ssh-keygen -q -f /data/ssh/ssh_host_ecdsa_key -N '' -t ecdsa
|
||||
ssh-keygen -q -f /data/ssh/ssh_host_ed25519_key -N '' -t ed25519
|
||||
chown -R root:root /data/ssh/*
|
||||
chmod 600 /data/ssh/*
|
||||
CROND=$(echo "$RUN_CROND" | tr '[:upper:]' '[:lower:]')
|
||||
if [ "$CROND" = "true" -o "$CROND" = "1" ]; then
|
||||
echo "init:crond | Cron Daemon (crond) will be run as requested by s6" 1>&2
|
||||
rm -f /app/gogs/docker/s6/crond/down
|
||||
else
|
||||
# Tell s6 not to run the crond service
|
||||
touch /app/gogs/docker/s6/crond/down
|
||||
fi
|
||||
|
||||
service ssh start
|
||||
|
||||
# sync templates
|
||||
test -d /data/gogs/templates || cp -ar ./templates /data/gogs/
|
||||
rsync -rtv /data/gogs/templates/ ./templates/
|
||||
|
||||
ln -sf /data/gogs/log ./log
|
||||
ln -sf /data/gogs/data ./data
|
||||
ln -sf /data/git /home/git
|
||||
|
||||
|
||||
if ! test -d ~git/.ssh
|
||||
then
|
||||
mkdir ~git/.ssh
|
||||
chmod 700 ~git/.ssh
|
||||
# Exec CMD or S6 by default if nothing present
|
||||
if [ $# -gt 0 ];then
|
||||
exec "$@"
|
||||
else
|
||||
exec /bin/s6-svscan /app/gogs/docker/s6/
|
||||
fi
|
||||
|
||||
if ! test -f ~git/.ssh/environment
|
||||
then
|
||||
echo "GOGS_CUSTOM=/data/gogs" > ~git/.ssh/environment
|
||||
chown git:git ~git/.ssh/environment
|
||||
chown 600 ~git/.ssh/environment
|
||||
fi
|
||||
|
||||
chown -R git:git /data .
|
||||
exec su git -c "./gogs web"
|
||||
|
||||
18
gogs.go
18
gogs.go
@@ -1,40 +1,40 @@
|
||||
// +build go1.2
|
||||
// +build go1.5
|
||||
|
||||
// Copyright 2014 The Gogs Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Gogs(Go Git Service) is a painless self-hosted Git Service written in Go.
|
||||
// Gogs is a painless self-hosted Git Service.
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"runtime"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/urfave/cli"
|
||||
|
||||
"github.com/gogits/gogs/cmd"
|
||||
"github.com/gogits/gogs/modules/setting"
|
||||
)
|
||||
|
||||
const APP_VER = "0.6.9.0903 Beta"
|
||||
const APP_VER = "0.10.0.0227"
|
||||
|
||||
func init() {
|
||||
runtime.GOMAXPROCS(runtime.NumCPU())
|
||||
setting.AppVer = APP_VER
|
||||
}
|
||||
|
||||
func main() {
|
||||
app := cli.NewApp()
|
||||
app.Name = "Gogs"
|
||||
app.Usage = "Go Git Service"
|
||||
app.Usage = "A painless self-hosted Git service"
|
||||
app.Version = APP_VER
|
||||
app.Commands = []cli.Command{
|
||||
cmd.CmdWeb,
|
||||
cmd.CmdServ,
|
||||
cmd.CmdUpdate,
|
||||
cmd.Serv,
|
||||
cmd.CmdHook,
|
||||
cmd.CmdDump,
|
||||
cmd.CmdCert,
|
||||
cmd.CmdAdmin,
|
||||
cmd.CmdImport,
|
||||
}
|
||||
app.Flags = append(app.Flags, []cli.Flag{}...)
|
||||
app.Run(os.Args)
|
||||
|
||||
132
models/access.go
132
models/access.go
@@ -7,19 +7,46 @@ package models
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/gogits/gogs/modules/log"
|
||||
log "gopkg.in/clog.v1"
|
||||
)
|
||||
|
||||
type AccessMode int
|
||||
|
||||
const (
|
||||
ACCESS_MODE_NONE AccessMode = iota
|
||||
ACCESS_MODE_READ
|
||||
ACCESS_MODE_WRITE
|
||||
ACCESS_MODE_ADMIN
|
||||
ACCESS_MODE_OWNER
|
||||
ACCESS_MODE_NONE AccessMode = iota // 0
|
||||
ACCESS_MODE_READ // 1
|
||||
ACCESS_MODE_WRITE // 2
|
||||
ACCESS_MODE_ADMIN // 3
|
||||
ACCESS_MODE_OWNER // 4
|
||||
)
|
||||
|
||||
func (mode AccessMode) String() string {
|
||||
switch mode {
|
||||
case ACCESS_MODE_READ:
|
||||
return "read"
|
||||
case ACCESS_MODE_WRITE:
|
||||
return "write"
|
||||
case ACCESS_MODE_ADMIN:
|
||||
return "admin"
|
||||
case ACCESS_MODE_OWNER:
|
||||
return "owner"
|
||||
default:
|
||||
return "none"
|
||||
}
|
||||
}
|
||||
|
||||
// ParseAccessMode returns corresponding access mode to given permission string.
|
||||
func ParseAccessMode(permission string) AccessMode {
|
||||
switch permission {
|
||||
case "write":
|
||||
return ACCESS_MODE_WRITE
|
||||
case "admin":
|
||||
return ACCESS_MODE_ADMIN
|
||||
default:
|
||||
return ACCESS_MODE_READ
|
||||
}
|
||||
}
|
||||
|
||||
// Access represents the highest access level of a user to the repository. The only access type
|
||||
// that is not in this table is the real owner of a repository. In case of an organization
|
||||
// repository, the members of the owners team are in this table.
|
||||
@@ -30,48 +57,51 @@ type Access struct {
|
||||
Mode AccessMode
|
||||
}
|
||||
|
||||
func accessLevel(e Engine, u *User, repo *Repository) (AccessMode, error) {
|
||||
func accessLevel(e Engine, userID int64, repo *Repository) (AccessMode, error) {
|
||||
mode := ACCESS_MODE_NONE
|
||||
// Everyone has read access to public repository
|
||||
if !repo.IsPrivate {
|
||||
mode = ACCESS_MODE_READ
|
||||
}
|
||||
|
||||
if u != nil {
|
||||
if u.Id == repo.OwnerID {
|
||||
return ACCESS_MODE_OWNER, nil
|
||||
}
|
||||
|
||||
a := &Access{UserID: u.Id, RepoID: repo.ID}
|
||||
if has, err := e.Get(a); !has || err != nil {
|
||||
return mode, err
|
||||
}
|
||||
return a.Mode, nil
|
||||
if userID <= 0 {
|
||||
return mode, nil
|
||||
}
|
||||
|
||||
return mode, nil
|
||||
if userID == repo.OwnerID {
|
||||
return ACCESS_MODE_OWNER, nil
|
||||
}
|
||||
|
||||
access := &Access{
|
||||
UserID: userID,
|
||||
RepoID: repo.ID,
|
||||
}
|
||||
if has, err := e.Get(access); !has || err != nil {
|
||||
return mode, err
|
||||
}
|
||||
return access.Mode, nil
|
||||
}
|
||||
|
||||
// AccessLevel returns the Access a user has to a repository. Will return NoneAccess if the
|
||||
// user does not have access. User can be nil!
|
||||
func AccessLevel(u *User, repo *Repository) (AccessMode, error) {
|
||||
return accessLevel(x, u, repo)
|
||||
// user does not have access.
|
||||
func AccessLevel(userID int64, repo *Repository) (AccessMode, error) {
|
||||
return accessLevel(x, userID, repo)
|
||||
}
|
||||
|
||||
func hasAccess(e Engine, u *User, repo *Repository, testMode AccessMode) (bool, error) {
|
||||
mode, err := accessLevel(e, u, repo)
|
||||
return testMode <= mode, err
|
||||
func hasAccess(e Engine, userID int64, repo *Repository, testMode AccessMode) (bool, error) {
|
||||
mode, err := accessLevel(e, userID, repo)
|
||||
return mode >= testMode, err
|
||||
}
|
||||
|
||||
// HasAccess returns true if someone has the request access level. User can be nil!
|
||||
func HasAccess(u *User, repo *Repository, testMode AccessMode) (bool, error) {
|
||||
return hasAccess(x, u, repo, testMode)
|
||||
func HasAccess(userID int64, repo *Repository, testMode AccessMode) (bool, error) {
|
||||
return hasAccess(x, userID, repo, testMode)
|
||||
}
|
||||
|
||||
// GetAccessibleRepositories finds all repositories where a user has access to,
|
||||
// besides he/she owns.
|
||||
func (u *User) GetAccessibleRepositories() (map[*Repository]AccessMode, error) {
|
||||
// GetRepositoryAccesses finds all repositories with their access mode where a user has access but does not own.
|
||||
func (u *User) GetRepositoryAccesses() (map[*Repository]AccessMode, error) {
|
||||
accesses := make([]*Access, 0, 10)
|
||||
if err := x.Find(&accesses, &Access{UserID: u.Id}); err != nil {
|
||||
if err := x.Find(&accesses, &Access{UserID: u.ID}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -80,23 +110,34 @@ func (u *User) GetAccessibleRepositories() (map[*Repository]AccessMode, error) {
|
||||
repo, err := GetRepositoryByID(access.RepoID)
|
||||
if err != nil {
|
||||
if IsErrRepoNotExist(err) {
|
||||
log.Error(4, "%v", err)
|
||||
log.Error(4, "GetRepositoryByID: %v", err)
|
||||
continue
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
if err = repo.GetOwner(); err != nil {
|
||||
return nil, err
|
||||
} else if repo.OwnerID == u.Id {
|
||||
} else if repo.OwnerID == u.ID {
|
||||
continue
|
||||
}
|
||||
repos[repo] = access.Mode
|
||||
}
|
||||
|
||||
// FIXME: should we generate an ordered list here? Random looks weird.
|
||||
return repos, nil
|
||||
}
|
||||
|
||||
// GetAccessibleRepositories finds repositories which the user has access but does not own.
|
||||
// If limit is smaller than 1 means returns all found results.
|
||||
func (user *User) GetAccessibleRepositories(limit int) (repos []*Repository, _ error) {
|
||||
sess := x.Where("owner_id !=? ", user.ID).Desc("updated_unix")
|
||||
if limit > 0 {
|
||||
sess.Limit(limit)
|
||||
repos = make([]*Repository, 0, limit)
|
||||
} else {
|
||||
repos = make([]*Repository, 0, 10)
|
||||
}
|
||||
return repos, sess.Join("INNER", "access", "access.user_id = ? AND access.repo_id = repository.id", user.ID).Find(&repos)
|
||||
}
|
||||
|
||||
func maxAccessMode(modes ...AccessMode) AccessMode {
|
||||
max := ACCESS_MODE_NONE
|
||||
for _, mode := range modes {
|
||||
@@ -109,16 +150,8 @@ func maxAccessMode(modes ...AccessMode) AccessMode {
|
||||
|
||||
// FIXME: do corss-comparison so reduce deletions and additions to the minimum?
|
||||
func (repo *Repository) refreshAccesses(e Engine, accessMap map[int64]AccessMode) (err error) {
|
||||
minMode := ACCESS_MODE_READ
|
||||
if !repo.IsPrivate {
|
||||
minMode = ACCESS_MODE_WRITE
|
||||
}
|
||||
|
||||
newAccesses := make([]Access, 0, len(accessMap))
|
||||
for userID, mode := range accessMap {
|
||||
if mode < minMode {
|
||||
continue
|
||||
}
|
||||
newAccesses = append(newAccesses, Access{
|
||||
UserID: userID,
|
||||
RepoID: repo.ID,
|
||||
@@ -135,15 +168,14 @@ func (repo *Repository) refreshAccesses(e Engine, accessMap map[int64]AccessMode
|
||||
return nil
|
||||
}
|
||||
|
||||
// FIXME: should be able to have read-only access.
|
||||
// Give all collaborators write access.
|
||||
// refreshCollaboratorAccesses retrieves repository collaborations with their access modes.
|
||||
func (repo *Repository) refreshCollaboratorAccesses(e Engine, accessMap map[int64]AccessMode) error {
|
||||
collaborators, err := repo.getCollaborators(e)
|
||||
collaborations, err := repo.getCollaborations(e)
|
||||
if err != nil {
|
||||
return fmt.Errorf("getCollaborators: %v", err)
|
||||
return fmt.Errorf("getCollaborations: %v", err)
|
||||
}
|
||||
for _, c := range collaborators {
|
||||
accessMap[c.Id] = ACCESS_MODE_WRITE
|
||||
for _, c := range collaborations {
|
||||
accessMap[c.UserID] = c.Mode
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -185,7 +217,7 @@ func (repo *Repository) recalculateTeamAccesses(e Engine, ignTeamID int64) (err
|
||||
return fmt.Errorf("getMembers '%d': %v", t.ID, err)
|
||||
}
|
||||
for _, m := range t.Members {
|
||||
accessMap[m.Id] = maxAccessMode(accessMap[m.Id], t.Authorize)
|
||||
accessMap[m.ID] = maxAccessMode(accessMap[m.ID], t.Authorize)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -197,7 +229,7 @@ func (repo *Repository) recalculateAccesses(e Engine) error {
|
||||
return repo.recalculateTeamAccesses(e, 0)
|
||||
}
|
||||
|
||||
accessMap := make(map[int64]AccessMode, 20)
|
||||
accessMap := make(map[int64]AccessMode, 10)
|
||||
if err := repo.refreshCollaboratorAccesses(e, accessMap); err != nil {
|
||||
return fmt.Errorf("refreshCollaboratorAccesses: %v", err)
|
||||
}
|
||||
|
||||
482
models/action.go
482
models/action.go
@@ -6,7 +6,6 @@ package models
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"path"
|
||||
"regexp"
|
||||
@@ -14,34 +13,35 @@ import (
|
||||
"time"
|
||||
"unicode"
|
||||
|
||||
"github.com/Unknwon/com"
|
||||
"github.com/go-xorm/xorm"
|
||||
log "gopkg.in/clog.v1"
|
||||
|
||||
"github.com/gogits/git-module"
|
||||
api "github.com/gogits/go-gogs-client"
|
||||
|
||||
"github.com/gogits/gogs/modules/base"
|
||||
"github.com/gogits/gogs/modules/git"
|
||||
"github.com/gogits/gogs/modules/log"
|
||||
"github.com/gogits/gogs/modules/setting"
|
||||
)
|
||||
|
||||
type ActionType int
|
||||
|
||||
const (
|
||||
CREATE_REPO ActionType = iota + 1 // 1
|
||||
RENAME_REPO // 2
|
||||
STAR_REPO // 3
|
||||
FOLLOW_REPO // 4
|
||||
COMMIT_REPO // 5
|
||||
CREATE_ISSUE // 6
|
||||
CREATE_PULL_REQUEST // 7
|
||||
TRANSFER_REPO // 8
|
||||
PUSH_TAG // 9
|
||||
COMMENT_ISSUE // 10
|
||||
MERGE_PULL_REQUEST // 11
|
||||
)
|
||||
|
||||
var (
|
||||
ErrNotImplemented = errors.New("Not implemented yet")
|
||||
ACTION_CREATE_REPO ActionType = iota + 1 // 1
|
||||
ACTION_RENAME_REPO // 2
|
||||
ACTION_STAR_REPO // 3
|
||||
ACTION_WATCH_REPO // 4
|
||||
ACTION_COMMIT_REPO // 5
|
||||
ACTION_CREATE_ISSUE // 6
|
||||
ACTION_CREATE_PULL_REQUEST // 7
|
||||
ACTION_TRANSFER_REPO // 8
|
||||
ACTION_PUSH_TAG // 9
|
||||
ACTION_COMMENT_ISSUE // 10
|
||||
ACTION_MERGE_PULL_REQUEST // 11
|
||||
ACTION_CLOSE_ISSUE // 12
|
||||
ACTION_REOPEN_ISSUE // 13
|
||||
ACTION_CLOSE_PULL_REQUEST // 14
|
||||
ACTION_REOPEN_PULL_REQUEST // 15
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -63,7 +63,7 @@ func init() {
|
||||
IssueReferenceKeywordsPat = regexp.MustCompile(`(?i)(?:)(^| )\S+`)
|
||||
}
|
||||
|
||||
// Action represents user operation type and other information to repository.,
|
||||
// Action represents user operation type and other information to repository,
|
||||
// it implemented interface base.Actioner so that can be used in template render.
|
||||
type Action struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
@@ -71,7 +71,6 @@ type Action struct {
|
||||
OpType ActionType
|
||||
ActUserID int64 // Action user id.
|
||||
ActUserName string // Action user name.
|
||||
ActEmail string
|
||||
ActAvatar string `xorm:"-"`
|
||||
RepoID int64
|
||||
RepoUserName string
|
||||
@@ -79,75 +78,111 @@ type Action struct {
|
||||
RefName string
|
||||
IsPrivate bool `xorm:"NOT NULL DEFAULT false"`
|
||||
Content string `xorm:"TEXT"`
|
||||
Created time.Time `xorm:"created"`
|
||||
Created time.Time `xorm:"-"`
|
||||
CreatedUnix int64
|
||||
}
|
||||
|
||||
func (a *Action) BeforeInsert() {
|
||||
a.CreatedUnix = time.Now().Unix()
|
||||
}
|
||||
|
||||
func (a *Action) AfterSet(colName string, _ xorm.Cell) {
|
||||
switch colName {
|
||||
case "created":
|
||||
a.Created = regulateTimeZone(a.Created)
|
||||
case "created_unix":
|
||||
a.Created = time.Unix(a.CreatedUnix, 0).Local()
|
||||
}
|
||||
}
|
||||
|
||||
func (a Action) GetOpType() int {
|
||||
func (a *Action) GetOpType() int {
|
||||
return int(a.OpType)
|
||||
}
|
||||
|
||||
func (a Action) GetActUserName() string {
|
||||
func (a *Action) GetActUserName() string {
|
||||
return a.ActUserName
|
||||
}
|
||||
|
||||
func (a Action) GetActEmail() string {
|
||||
return a.ActEmail
|
||||
func (a *Action) ShortActUserName() string {
|
||||
return base.EllipsisString(a.ActUserName, 20)
|
||||
}
|
||||
|
||||
func (a Action) GetRepoUserName() string {
|
||||
func (a *Action) GetRepoUserName() string {
|
||||
return a.RepoUserName
|
||||
}
|
||||
|
||||
func (a Action) GetRepoName() string {
|
||||
func (a *Action) ShortRepoUserName() string {
|
||||
return base.EllipsisString(a.RepoUserName, 20)
|
||||
}
|
||||
|
||||
func (a *Action) GetRepoName() string {
|
||||
return a.RepoName
|
||||
}
|
||||
|
||||
func (a Action) GetRepoPath() string {
|
||||
func (a *Action) ShortRepoName() string {
|
||||
return base.EllipsisString(a.RepoName, 33)
|
||||
}
|
||||
|
||||
func (a *Action) GetRepoPath() string {
|
||||
return path.Join(a.RepoUserName, a.RepoName)
|
||||
}
|
||||
|
||||
func (a Action) GetRepoLink() string {
|
||||
func (a *Action) ShortRepoPath() string {
|
||||
return path.Join(a.ShortRepoUserName(), a.ShortRepoName())
|
||||
}
|
||||
|
||||
func (a *Action) GetRepoLink() string {
|
||||
if len(setting.AppSubUrl) > 0 {
|
||||
return path.Join(setting.AppSubUrl, a.GetRepoPath())
|
||||
}
|
||||
return "/" + a.GetRepoPath()
|
||||
}
|
||||
|
||||
func (a Action) GetBranch() string {
|
||||
func (a *Action) GetBranch() string {
|
||||
return a.RefName
|
||||
}
|
||||
|
||||
func (a Action) GetContent() string {
|
||||
func (a *Action) GetContent() string {
|
||||
return a.Content
|
||||
}
|
||||
|
||||
func (a Action) GetCreate() time.Time {
|
||||
func (a *Action) GetCreate() time.Time {
|
||||
return a.Created
|
||||
}
|
||||
|
||||
func (a Action) GetIssueInfos() []string {
|
||||
func (a *Action) GetIssueInfos() []string {
|
||||
return strings.SplitN(a.Content, "|", 2)
|
||||
}
|
||||
|
||||
func (a *Action) GetIssueTitle() string {
|
||||
index := com.StrTo(a.GetIssueInfos()[0]).MustInt64()
|
||||
issue, err := GetIssueByIndex(a.RepoID, index)
|
||||
if err != nil {
|
||||
log.Error(4, "GetIssueByIndex: %v", err)
|
||||
return "500 when get issue"
|
||||
}
|
||||
return issue.Title
|
||||
}
|
||||
|
||||
func (a *Action) GetIssueContent() string {
|
||||
index := com.StrTo(a.GetIssueInfos()[0]).MustInt64()
|
||||
issue, err := GetIssueByIndex(a.RepoID, index)
|
||||
if err != nil {
|
||||
log.Error(4, "GetIssueByIndex: %v", err)
|
||||
return "500 when get issue"
|
||||
}
|
||||
return issue.Content
|
||||
}
|
||||
|
||||
func newRepoAction(e Engine, u *User, repo *Repository) (err error) {
|
||||
if err = notifyWatchers(e, &Action{
|
||||
ActUserID: u.Id,
|
||||
ActUserID: u.ID,
|
||||
ActUserName: u.Name,
|
||||
ActEmail: u.Email,
|
||||
OpType: CREATE_REPO,
|
||||
OpType: ACTION_CREATE_REPO,
|
||||
RepoID: repo.ID,
|
||||
RepoUserName: repo.Owner.Name,
|
||||
RepoName: repo.Name,
|
||||
IsPrivate: repo.IsPrivate,
|
||||
}); err != nil {
|
||||
return fmt.Errorf("notify watchers '%d/%s': %v", u.Id, repo.ID, err)
|
||||
return fmt.Errorf("notify watchers '%d/%d': %v", u.ID, repo.ID, err)
|
||||
}
|
||||
|
||||
log.Trace("action.newRepoAction: %s/%s", u.Name, repo.Name)
|
||||
@@ -161,10 +196,9 @@ func NewRepoAction(u *User, repo *Repository) (err error) {
|
||||
|
||||
func renameRepoAction(e Engine, actUser *User, oldRepoName string, repo *Repository) (err error) {
|
||||
if err = notifyWatchers(e, &Action{
|
||||
ActUserID: actUser.Id,
|
||||
ActUserID: actUser.ID,
|
||||
ActUserName: actUser.Name,
|
||||
ActEmail: actUser.Email,
|
||||
OpType: RENAME_REPO,
|
||||
OpType: ACTION_RENAME_REPO,
|
||||
RepoID: repo.ID,
|
||||
RepoUserName: repo.Owner.Name,
|
||||
RepoName: repo.Name,
|
||||
@@ -187,9 +221,89 @@ func issueIndexTrimRight(c rune) bool {
|
||||
return !unicode.IsDigit(c)
|
||||
}
|
||||
|
||||
// updateIssuesCommit checks if issues are manipulated by commit message.
|
||||
func updateIssuesCommit(u *User, repo *Repository, repoUserName, repoName string, commits []*base.PushCommit) error {
|
||||
for _, c := range commits {
|
||||
type PushCommit struct {
|
||||
Sha1 string
|
||||
Message string
|
||||
AuthorEmail string
|
||||
AuthorName string
|
||||
CommitterEmail string
|
||||
CommitterName string
|
||||
Timestamp time.Time
|
||||
}
|
||||
|
||||
type PushCommits struct {
|
||||
Len int
|
||||
Commits []*PushCommit
|
||||
CompareURL string
|
||||
|
||||
avatars map[string]string
|
||||
}
|
||||
|
||||
func NewPushCommits() *PushCommits {
|
||||
return &PushCommits{
|
||||
avatars: make(map[string]string),
|
||||
}
|
||||
}
|
||||
|
||||
func (pc *PushCommits) ToApiPayloadCommits(repoLink string) []*api.PayloadCommit {
|
||||
commits := make([]*api.PayloadCommit, len(pc.Commits))
|
||||
for i, commit := range pc.Commits {
|
||||
authorUsername := ""
|
||||
author, err := GetUserByEmail(commit.AuthorEmail)
|
||||
if err == nil {
|
||||
authorUsername = author.Name
|
||||
}
|
||||
committerUsername := ""
|
||||
committer, err := GetUserByEmail(commit.CommitterEmail)
|
||||
if err == nil {
|
||||
// TODO: check errors other than email not found.
|
||||
committerUsername = committer.Name
|
||||
}
|
||||
commits[i] = &api.PayloadCommit{
|
||||
ID: commit.Sha1,
|
||||
Message: commit.Message,
|
||||
URL: fmt.Sprintf("%s/commit/%s", repoLink, commit.Sha1),
|
||||
Author: &api.PayloadUser{
|
||||
Name: commit.AuthorName,
|
||||
Email: commit.AuthorEmail,
|
||||
UserName: authorUsername,
|
||||
},
|
||||
Committer: &api.PayloadUser{
|
||||
Name: commit.CommitterName,
|
||||
Email: commit.CommitterEmail,
|
||||
UserName: committerUsername,
|
||||
},
|
||||
Timestamp: commit.Timestamp,
|
||||
}
|
||||
}
|
||||
return commits
|
||||
}
|
||||
|
||||
// AvatarLink tries to match user in database with e-mail
|
||||
// in order to show custom avatar, and falls back to general avatar link.
|
||||
func (push *PushCommits) AvatarLink(email string) string {
|
||||
_, ok := push.avatars[email]
|
||||
if !ok {
|
||||
u, err := GetUserByEmail(email)
|
||||
if err != nil {
|
||||
push.avatars[email] = base.AvatarLink(email)
|
||||
if !IsErrUserNotExist(err) {
|
||||
log.Error(4, "GetUserByEmail: %v", err)
|
||||
}
|
||||
} else {
|
||||
push.avatars[email] = u.RelAvatarLink()
|
||||
}
|
||||
}
|
||||
|
||||
return push.avatars[email]
|
||||
}
|
||||
|
||||
// UpdateIssuesCommit checks if issues are manipulated by commit message.
|
||||
func UpdateIssuesCommit(doer *User, repo *Repository, commits []*PushCommit) error {
|
||||
// Commits are appended in the reverse order.
|
||||
for i := len(commits) - 1; i >= 0; i-- {
|
||||
c := commits[i]
|
||||
|
||||
refMarked := make(map[int64]bool)
|
||||
for _, ref := range IssueReferenceKeywordsPat.FindAllString(c.Message, -1) {
|
||||
ref = ref[strings.IndexByte(ref, byte(' '))+1:]
|
||||
@@ -201,7 +315,7 @@ func updateIssuesCommit(u *User, repo *Repository, repoUserName, repoName string
|
||||
|
||||
// Add repo name if missing
|
||||
if ref[0] == '#' {
|
||||
ref = fmt.Sprintf("%s/%s%s", repoUserName, repoName, ref)
|
||||
ref = fmt.Sprintf("%s%s", repo.FullName(), ref)
|
||||
} else if !strings.Contains(ref, "/") {
|
||||
// FIXME: We don't support User#ID syntax yet
|
||||
// return ErrNotImplemented
|
||||
@@ -210,6 +324,9 @@ func updateIssuesCommit(u *User, repo *Repository, repoUserName, repoName string
|
||||
|
||||
issue, err := GetIssueByRef(ref)
|
||||
if err != nil {
|
||||
if IsErrIssueNotExist(err) {
|
||||
continue
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -218,9 +335,13 @@ func updateIssuesCommit(u *User, repo *Repository, repoUserName, repoName string
|
||||
}
|
||||
refMarked[issue.ID] = true
|
||||
|
||||
url := fmt.Sprintf("%s/%s/%s/commit/%s", setting.AppSubUrl, repoUserName, repoName, c.Sha1)
|
||||
message := fmt.Sprintf(`<a href="%s">%s</a>`, url, c.Message)
|
||||
if _, err = CreateComment(u, repo, issue, 0, 0, COMMENT_TYPE_COMMIT_REF, message, nil); err != nil {
|
||||
msgLines := strings.Split(c.Message, "\n")
|
||||
shortMsg := msgLines[0]
|
||||
if len(msgLines) > 2 {
|
||||
shortMsg += "..."
|
||||
}
|
||||
message := fmt.Sprintf(`<a href="%s/commit/%s">%s</a>`, repo.Link(), c.Sha1, shortMsg)
|
||||
if err = CreateRefComment(doer, repo, issue, message, c.Sha1); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -237,7 +358,7 @@ func updateIssuesCommit(u *User, repo *Repository, repoUserName, repoName string
|
||||
|
||||
// Add repo name if missing
|
||||
if ref[0] == '#' {
|
||||
ref = fmt.Sprintf("%s/%s%s", repoUserName, repoName, ref)
|
||||
ref = fmt.Sprintf("%s%s", repo.FullName(), ref)
|
||||
} else if !strings.Contains(ref, "/") {
|
||||
// We don't support User#ID syntax yet
|
||||
// return ErrNotImplemented
|
||||
@@ -246,6 +367,9 @@ func updateIssuesCommit(u *User, repo *Repository, repoUserName, repoName string
|
||||
|
||||
issue, err := GetIssueByRef(ref)
|
||||
if err != nil {
|
||||
if IsErrIssueNotExist(err) {
|
||||
continue
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -258,7 +382,7 @@ func updateIssuesCommit(u *User, repo *Repository, repoUserName, repoName string
|
||||
continue
|
||||
}
|
||||
|
||||
if err = issue.ChangeStatus(u, true); err != nil {
|
||||
if err = issue.ChangeStatus(doer, repo, true); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -274,7 +398,7 @@ func updateIssuesCommit(u *User, repo *Repository, repoUserName, repoName string
|
||||
|
||||
// Add repo name if missing
|
||||
if ref[0] == '#' {
|
||||
ref = fmt.Sprintf("%s/%s%s", repoUserName, repoName, ref)
|
||||
ref = fmt.Sprintf("%s%s", repo.FullName(), ref)
|
||||
} else if !strings.Contains(ref, "/") {
|
||||
// We don't support User#ID syntax yet
|
||||
// return ErrNotImplemented
|
||||
@@ -283,6 +407,9 @@ func updateIssuesCommit(u *User, repo *Repository, repoUserName, repoName string
|
||||
|
||||
issue, err := GetIssueByRef(ref)
|
||||
if err != nil {
|
||||
if IsErrIssueNotExist(err) {
|
||||
continue
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -295,7 +422,7 @@ func updateIssuesCommit(u *User, repo *Repository, repoUserName, repoName string
|
||||
continue
|
||||
}
|
||||
|
||||
if err = issue.ChangeStatus(u, false); err != nil {
|
||||
if err = issue.ChangeStatus(doer, repo, false); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -303,201 +430,160 @@ func updateIssuesCommit(u *User, repo *Repository, repoUserName, repoName string
|
||||
return nil
|
||||
}
|
||||
|
||||
// CommitRepoAction adds new action for committing repository.
|
||||
func CommitRepoAction(
|
||||
userID, repoUserID int64,
|
||||
userName, actEmail string,
|
||||
repoID int64,
|
||||
repoUserName, repoName string,
|
||||
refFullName string,
|
||||
commit *base.PushCommits,
|
||||
oldCommitID string, newCommitID string) error {
|
||||
type CommitRepoActionOptions struct {
|
||||
PusherName string
|
||||
RepoOwnerID int64
|
||||
RepoName string
|
||||
RefFullName string
|
||||
OldCommitID string
|
||||
NewCommitID string
|
||||
Commits *PushCommits
|
||||
}
|
||||
|
||||
u, err := GetUserByID(userID)
|
||||
// CommitRepoAction adds new commit actio to the repository, and prepare corresponding webhooks.
|
||||
func CommitRepoAction(opts CommitRepoActionOptions) error {
|
||||
pusher, err := GetUserByName(opts.PusherName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("GetUserByID: %v", err)
|
||||
return fmt.Errorf("GetUserByName [%s]: %v", opts.PusherName, err)
|
||||
}
|
||||
|
||||
repo, err := GetRepositoryByName(repoUserID, repoName)
|
||||
repo, err := GetRepositoryByName(opts.RepoOwnerID, opts.RepoName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("GetRepositoryByName: %v", err)
|
||||
} else if err = repo.GetOwner(); err != nil {
|
||||
return fmt.Errorf("GetOwner: %v", err)
|
||||
return fmt.Errorf("GetRepositoryByName [owner_id: %d, name: %s]: %v", opts.RepoOwnerID, opts.RepoName, err)
|
||||
}
|
||||
|
||||
// Change repository bare status and update last updated time.
|
||||
repo.IsBare = false
|
||||
if err = UpdateRepository(repo, false); err != nil {
|
||||
return fmt.Errorf("UpdateRepository: %v", err)
|
||||
}
|
||||
|
||||
isNewBranch := false
|
||||
opType := COMMIT_REPO
|
||||
opType := ACTION_COMMIT_REPO
|
||||
// Check it's tag push or branch.
|
||||
if strings.HasPrefix(refFullName, "refs/tags/") {
|
||||
opType = PUSH_TAG
|
||||
commit = &base.PushCommits{}
|
||||
if strings.HasPrefix(opts.RefFullName, git.TAG_PREFIX) {
|
||||
opType = ACTION_PUSH_TAG
|
||||
opts.Commits = &PushCommits{}
|
||||
} else {
|
||||
// if not the first commit, set the compareUrl
|
||||
if !strings.HasPrefix(oldCommitID, "0000000") {
|
||||
commit.CompareUrl = fmt.Sprintf("%s/%s/compare/%s...%s", repoUserName, repoName, oldCommitID, newCommitID)
|
||||
} else {
|
||||
// TODO: detect branch deletion
|
||||
// if not the first commit, set the compare URL.
|
||||
if opts.OldCommitID == git.EMPTY_SHA {
|
||||
isNewBranch = true
|
||||
} else {
|
||||
opts.Commits.CompareURL = repo.ComposeCompareURL(opts.OldCommitID, opts.NewCommitID)
|
||||
}
|
||||
|
||||
// Change repository bare status and update last updated time.
|
||||
repo.IsBare = false
|
||||
if err = UpdateRepository(repo, false); err != nil {
|
||||
return fmt.Errorf("UpdateRepository: %v", err)
|
||||
}
|
||||
|
||||
if err = updateIssuesCommit(u, repo, repoUserName, repoName, commit.Commits); err != nil {
|
||||
log.Debug("updateIssuesCommit: %v", err)
|
||||
if err = UpdateIssuesCommit(pusher, repo, opts.Commits.Commits); err != nil {
|
||||
log.Error(2, "UpdateIssuesCommit: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
bs, err := json.Marshal(commit)
|
||||
if len(opts.Commits.Commits) > setting.UI.FeedMaxCommitNum {
|
||||
opts.Commits.Commits = opts.Commits.Commits[:setting.UI.FeedMaxCommitNum]
|
||||
}
|
||||
|
||||
data, err := json.Marshal(opts.Commits)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Marshal: %v", err)
|
||||
}
|
||||
|
||||
refName := git.RefEndName(refFullName)
|
||||
|
||||
refName := git.RefEndName(opts.RefFullName)
|
||||
if err = NotifyWatchers(&Action{
|
||||
ActUserID: u.Id,
|
||||
ActUserName: userName,
|
||||
ActEmail: actEmail,
|
||||
ActUserID: pusher.ID,
|
||||
ActUserName: pusher.Name,
|
||||
OpType: opType,
|
||||
Content: string(bs),
|
||||
Content: string(data),
|
||||
RepoID: repo.ID,
|
||||
RepoUserName: repoUserName,
|
||||
RepoName: repoName,
|
||||
RepoUserName: repo.MustOwner().Name,
|
||||
RepoName: repo.Name,
|
||||
RefName: refName,
|
||||
IsPrivate: repo.IsPrivate,
|
||||
}); err != nil {
|
||||
return fmt.Errorf("NotifyWatchers: %v", err)
|
||||
|
||||
}
|
||||
|
||||
repoLink := fmt.Sprintf("%s%s/%s", setting.AppUrl, repoUserName, repoName)
|
||||
payloadRepo := &api.PayloadRepo{
|
||||
ID: repo.ID,
|
||||
Name: repo.LowerName,
|
||||
URL: repoLink,
|
||||
Description: repo.Description,
|
||||
Website: repo.Website,
|
||||
Watchers: repo.NumWatches,
|
||||
Owner: &api.PayloadAuthor{
|
||||
Name: repo.Owner.DisplayName(),
|
||||
Email: repo.Owner.Email,
|
||||
UserName: repo.Owner.Name,
|
||||
},
|
||||
Private: repo.IsPrivate,
|
||||
}
|
||||
|
||||
pusher_email, pusher_name := "", ""
|
||||
pusher, err := GetUserByName(userName)
|
||||
if err == nil {
|
||||
pusher_email = pusher.Email
|
||||
pusher_name = pusher.DisplayName()
|
||||
}
|
||||
payloadSender := &api.PayloadUser{
|
||||
UserName: pusher.Name,
|
||||
ID: pusher.Id,
|
||||
AvatarUrl: setting.AppUrl + pusher.RelAvatarLink(),
|
||||
}
|
||||
defer func() {
|
||||
go HookQueue.Add(repo.ID)
|
||||
}()
|
||||
|
||||
apiPusher := pusher.APIFormat()
|
||||
apiRepo := repo.APIFormat(nil)
|
||||
switch opType {
|
||||
case COMMIT_REPO: // Push
|
||||
commits := make([]*api.PayloadCommit, len(commit.Commits))
|
||||
for i, cmt := range commit.Commits {
|
||||
author_username := ""
|
||||
author, err := GetUserByEmail(cmt.AuthorEmail)
|
||||
if err == nil {
|
||||
author_username = author.Name
|
||||
}
|
||||
commits[i] = &api.PayloadCommit{
|
||||
ID: cmt.Sha1,
|
||||
Message: cmt.Message,
|
||||
URL: fmt.Sprintf("%s/commit/%s", repoLink, cmt.Sha1),
|
||||
Author: &api.PayloadAuthor{
|
||||
Name: cmt.AuthorName,
|
||||
Email: cmt.AuthorEmail,
|
||||
UserName: author_username,
|
||||
},
|
||||
}
|
||||
}
|
||||
p := &api.PushPayload{
|
||||
Ref: refFullName,
|
||||
Before: oldCommitID,
|
||||
After: newCommitID,
|
||||
CompareUrl: setting.AppUrl + commit.CompareUrl,
|
||||
Commits: commits,
|
||||
Repo: payloadRepo,
|
||||
Pusher: &api.PayloadAuthor{
|
||||
Name: pusher_name,
|
||||
Email: pusher_email,
|
||||
UserName: userName,
|
||||
},
|
||||
Sender: payloadSender,
|
||||
}
|
||||
if err = PrepareWebhooks(repo, HOOK_EVENT_PUSH, p); err != nil {
|
||||
return fmt.Errorf("PrepareWebhooks: %v", err)
|
||||
}
|
||||
|
||||
case ACTION_COMMIT_REPO: // Push
|
||||
compareURL := setting.AppUrl + opts.Commits.CompareURL
|
||||
if isNewBranch {
|
||||
return PrepareWebhooks(repo, HOOK_EVENT_CREATE, &api.CreatePayload{
|
||||
compareURL = ""
|
||||
if err = PrepareWebhooks(repo, HOOK_EVENT_CREATE, &api.CreatePayload{
|
||||
Ref: refName,
|
||||
RefType: "branch",
|
||||
Repo: payloadRepo,
|
||||
Sender: payloadSender,
|
||||
})
|
||||
Repo: apiRepo,
|
||||
Sender: apiPusher,
|
||||
}); err != nil {
|
||||
return fmt.Errorf("PrepareWebhooks (new branch): %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
case PUSH_TAG: // Create
|
||||
if err = PrepareWebhooks(repo, HOOK_EVENT_PUSH, &api.PushPayload{
|
||||
Ref: opts.RefFullName,
|
||||
Before: opts.OldCommitID,
|
||||
After: opts.NewCommitID,
|
||||
CompareURL: compareURL,
|
||||
Commits: opts.Commits.ToApiPayloadCommits(repo.HTMLURL()),
|
||||
Repo: apiRepo,
|
||||
Pusher: apiPusher,
|
||||
Sender: apiPusher,
|
||||
}); err != nil {
|
||||
return fmt.Errorf("PrepareWebhooks (new commit): %v", err)
|
||||
}
|
||||
|
||||
case ACTION_PUSH_TAG: // Create
|
||||
return PrepareWebhooks(repo, HOOK_EVENT_CREATE, &api.CreatePayload{
|
||||
Ref: refName,
|
||||
RefType: "tag",
|
||||
Repo: payloadRepo,
|
||||
Sender: payloadSender,
|
||||
Repo: apiRepo,
|
||||
Sender: apiPusher,
|
||||
})
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func transferRepoAction(e Engine, actUser, oldOwner, newOwner *User, repo *Repository) (err error) {
|
||||
func transferRepoAction(e Engine, doer, oldOwner *User, repo *Repository) (err error) {
|
||||
if err = notifyWatchers(e, &Action{
|
||||
ActUserID: actUser.Id,
|
||||
ActUserName: actUser.Name,
|
||||
ActEmail: actUser.Email,
|
||||
OpType: TRANSFER_REPO,
|
||||
ActUserID: doer.ID,
|
||||
ActUserName: doer.Name,
|
||||
OpType: ACTION_TRANSFER_REPO,
|
||||
RepoID: repo.ID,
|
||||
RepoUserName: newOwner.Name,
|
||||
RepoUserName: repo.Owner.Name,
|
||||
RepoName: repo.Name,
|
||||
IsPrivate: repo.IsPrivate,
|
||||
Content: path.Join(oldOwner.LowerName, repo.LowerName),
|
||||
Content: path.Join(oldOwner.Name, repo.Name),
|
||||
}); err != nil {
|
||||
return fmt.Errorf("notify watchers '%d/%s': %v", actUser.Id, repo.ID, err)
|
||||
return fmt.Errorf("notifyWatchers: %v", err)
|
||||
}
|
||||
|
||||
// Remove watch for organization.
|
||||
if repo.Owner.IsOrganization() {
|
||||
if err = watchRepo(e, repo.Owner.Id, repo.ID, false); err != nil {
|
||||
return fmt.Errorf("watch repository: %v", err)
|
||||
if oldOwner.IsOrganization() {
|
||||
if err = watchRepo(e, oldOwner.ID, repo.ID, false); err != nil {
|
||||
return fmt.Errorf("watchRepo [false]: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
log.Trace("action.transferRepoAction: %s/%s", actUser.Name, repo.Name)
|
||||
return nil
|
||||
}
|
||||
|
||||
// TransferRepoAction adds new action for transferring repository.
|
||||
func TransferRepoAction(actUser, oldOwner, newOwner *User, repo *Repository) error {
|
||||
return transferRepoAction(x, actUser, oldOwner, newOwner, repo)
|
||||
// TransferRepoAction adds new action for transferring repository,
|
||||
// the Owner field of repository is assumed to be new owner.
|
||||
func TransferRepoAction(doer, oldOwner *User, repo *Repository) error {
|
||||
return transferRepoAction(x, doer, oldOwner, repo)
|
||||
}
|
||||
|
||||
func mergePullRequestAction(e Engine, actUser *User, repo *Repository, pull *Issue) error {
|
||||
func mergePullRequestAction(e Engine, doer *User, repo *Repository, issue *Issue) error {
|
||||
return notifyWatchers(e, &Action{
|
||||
ActUserID: actUser.Id,
|
||||
ActUserName: actUser.Name,
|
||||
ActEmail: actUser.Email,
|
||||
OpType: MERGE_PULL_REQUEST,
|
||||
Content: fmt.Sprintf("%d|%s", pull.Index, pull.Name),
|
||||
ActUserID: doer.ID,
|
||||
ActUserName: doer.Name,
|
||||
OpType: ACTION_MERGE_PULL_REQUEST,
|
||||
Content: fmt.Sprintf("%d|%s", issue.Index, issue.Title),
|
||||
RepoID: repo.ID,
|
||||
RepoUserName: repo.Owner.Name,
|
||||
RepoName: repo.Name,
|
||||
@@ -511,12 +597,30 @@ func MergePullRequestAction(actUser *User, repo *Repository, pull *Issue) error
|
||||
}
|
||||
|
||||
// GetFeeds returns action list of given user in given context.
|
||||
func GetFeeds(uid, offset int64, isProfile bool) ([]*Action, error) {
|
||||
// actorID is the user who's requesting, ctxUserID is the user/org that is requested.
|
||||
// actorID can be -1 when isProfile is true or to skip the permission check.
|
||||
func GetFeeds(ctxUser *User, actorID, offset int64, isProfile bool) ([]*Action, error) {
|
||||
actions := make([]*Action, 0, 20)
|
||||
sess := x.Limit(20, int(offset)).Desc("id").Where("user_id=?", uid)
|
||||
sess := x.Limit(20, int(offset)).Desc("id").Where("user_id = ?", ctxUser.ID)
|
||||
if isProfile {
|
||||
sess.And("is_private=?", false).And("act_user_id=?", uid)
|
||||
sess.And("is_private = ?", false).And("act_user_id = ?", ctxUser.ID)
|
||||
} else if actorID != -1 && ctxUser.IsOrganization() {
|
||||
// FIXME: only need to get IDs here, not all fields of repository.
|
||||
repos, _, err := ctxUser.GetUserRepositories(actorID, 1, ctxUser.NumRepos)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("GetUserRepositories: %v", err)
|
||||
}
|
||||
|
||||
var repoIDs []int64
|
||||
for _, repo := range repos {
|
||||
repoIDs = append(repoIDs, repo.ID)
|
||||
}
|
||||
|
||||
if len(repoIDs) > 0 {
|
||||
sess.In("repo_id", repoIDs)
|
||||
}
|
||||
}
|
||||
|
||||
err := sess.Find(&actions)
|
||||
return actions, err
|
||||
}
|
||||
|
||||
@@ -5,9 +5,18 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Unknwon/com"
|
||||
"github.com/go-xorm/xorm"
|
||||
log "gopkg.in/clog.v1"
|
||||
|
||||
"github.com/gogits/gogs/modules/base"
|
||||
"github.com/gogits/gogs/modules/setting"
|
||||
)
|
||||
|
||||
type NoticeType int
|
||||
@@ -18,10 +27,22 @@ const (
|
||||
|
||||
// Notice represents a system notice for admin.
|
||||
type Notice struct {
|
||||
Id int64
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
Type NoticeType
|
||||
Description string `xorm:"TEXT"`
|
||||
Created time.Time `xorm:"CREATED"`
|
||||
Created time.Time `xorm:"-"`
|
||||
CreatedUnix int64
|
||||
}
|
||||
|
||||
func (n *Notice) BeforeInsert() {
|
||||
n.CreatedUnix = time.Now().Unix()
|
||||
}
|
||||
|
||||
func (n *Notice) AfterSet(colName string, _ xorm.Cell) {
|
||||
switch colName {
|
||||
case "created_unix":
|
||||
n.Created = time.Unix(n.CreatedUnix, 0).Local()
|
||||
}
|
||||
}
|
||||
|
||||
// TrStr returns a translation format string.
|
||||
@@ -31,6 +52,11 @@ func (n *Notice) TrStr() string {
|
||||
|
||||
// CreateNotice creates new system notice.
|
||||
func CreateNotice(tp NoticeType, desc string) error {
|
||||
// prevent panic if database connection is not available at this point
|
||||
if x == nil {
|
||||
return fmt.Errorf("Could not save notice due database connection not being available: %d %s", tp, desc)
|
||||
}
|
||||
|
||||
n := &Notice{
|
||||
Type: tp,
|
||||
Description: desc,
|
||||
@@ -44,17 +70,41 @@ func CreateRepositoryNotice(desc string) error {
|
||||
return CreateNotice(NOTICE_REPOSITORY, desc)
|
||||
}
|
||||
|
||||
// RemoveAllWithNotice removes all directories in given path and
|
||||
// creates a system notice when error occurs.
|
||||
func RemoveAllWithNotice(title, path string) {
|
||||
var err error
|
||||
// LEGACY [Go 1.7]: workaround for Go not being able to remove read-only files/folders: https://github.com/golang/go/issues/9606
|
||||
// this bug should be fixed on Go 1.7, so the workaround should be removed when Gogs don't support Go 1.6 anymore:
|
||||
// https://github.com/golang/go/commit/2ffb3e5d905b5622204d199128dec06cefd57790
|
||||
// Note: Windows complains when delete target does not exist, therefore we can skip deletion in such cases.
|
||||
if setting.IsWindows && com.IsExist(path) {
|
||||
// converting "/" to "\" in path on Windows
|
||||
path = strings.Replace(path, "/", "\\", -1)
|
||||
err = exec.Command("cmd", "/C", "rmdir", "/S", "/Q", path).Run()
|
||||
} else {
|
||||
err = os.RemoveAll(path)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
desc := fmt.Sprintf("%s [%s]: %v", title, path, err)
|
||||
log.Warn(desc)
|
||||
if err = CreateRepositoryNotice(desc); err != nil {
|
||||
log.Error(4, "CreateRepositoryNotice: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// CountNotices returns number of notices.
|
||||
func CountNotices() int64 {
|
||||
count, _ := x.Count(new(Notice))
|
||||
return count
|
||||
}
|
||||
|
||||
// GetNotices returns given number of notices with offset.
|
||||
func GetNotices(num, offset int) ([]*Notice, error) {
|
||||
notices := make([]*Notice, 0, num)
|
||||
err := x.Limit(num, offset).Desc("id").Find(¬ices)
|
||||
return notices, err
|
||||
// Notices returns number of notices in given page.
|
||||
func Notices(page, pageSize int) ([]*Notice, error) {
|
||||
notices := make([]*Notice, 0, pageSize)
|
||||
return notices, x.Limit(pageSize, (page-1)*pageSize).Desc("id").Find(¬ices)
|
||||
}
|
||||
|
||||
// DeleteNotice deletes a system notice by given ID.
|
||||
@@ -62,3 +112,22 @@ func DeleteNotice(id int64) error {
|
||||
_, err := x.Id(id).Delete(new(Notice))
|
||||
return err
|
||||
}
|
||||
|
||||
// DeleteNotices deletes all notices with ID from start to end (inclusive).
|
||||
func DeleteNotices(start, end int64) error {
|
||||
sess := x.Where("id >= ?", start)
|
||||
if end > 0 {
|
||||
sess.And("id <= ?", end)
|
||||
}
|
||||
_, err := sess.Delete(new(Notice))
|
||||
return err
|
||||
}
|
||||
|
||||
// DeleteNoticesByIDs deletes notices by given IDs.
|
||||
func DeleteNoticesByIDs(ids []int64) error {
|
||||
if len(ids) == 0 {
|
||||
return nil
|
||||
}
|
||||
_, err := x.Where("id IN (" + strings.Join(base.Int64sToStrings(ids), ",") + ")").Delete(new(Notice))
|
||||
return err
|
||||
}
|
||||
|
||||
175
models/attachment.go
Normal file
175
models/attachment.go
Normal file
@@ -0,0 +1,175 @@
|
||||
// Copyright 2017 The Gogs Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package models
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"mime/multipart"
|
||||
"os"
|
||||
"path"
|
||||
"time"
|
||||
|
||||
"github.com/go-xorm/xorm"
|
||||
gouuid "github.com/satori/go.uuid"
|
||||
|
||||
"github.com/gogits/gogs/modules/setting"
|
||||
)
|
||||
|
||||
// Attachment represent a attachment of issue/comment/release.
|
||||
type Attachment struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
UUID string `xorm:"uuid UNIQUE"`
|
||||
IssueID int64 `xorm:"INDEX"`
|
||||
CommentID int64
|
||||
ReleaseID int64 `xorm:"INDEX"`
|
||||
Name string
|
||||
|
||||
Created time.Time `xorm:"-"`
|
||||
CreatedUnix int64
|
||||
}
|
||||
|
||||
func (a *Attachment) BeforeInsert() {
|
||||
a.CreatedUnix = time.Now().Unix()
|
||||
}
|
||||
|
||||
func (a *Attachment) AfterSet(colName string, _ xorm.Cell) {
|
||||
switch colName {
|
||||
case "created_unix":
|
||||
a.Created = time.Unix(a.CreatedUnix, 0).Local()
|
||||
}
|
||||
}
|
||||
|
||||
// AttachmentLocalPath returns where attachment is stored in local file system based on given UUID.
|
||||
func AttachmentLocalPath(uuid string) string {
|
||||
return path.Join(setting.AttachmentPath, uuid[0:1], uuid[1:2], uuid)
|
||||
}
|
||||
|
||||
// LocalPath returns where attachment is stored in local file system.
|
||||
func (attach *Attachment) LocalPath() string {
|
||||
return AttachmentLocalPath(attach.UUID)
|
||||
}
|
||||
|
||||
// NewAttachment creates a new attachment object.
|
||||
func NewAttachment(name string, buf []byte, file multipart.File) (_ *Attachment, err error) {
|
||||
attach := &Attachment{
|
||||
UUID: gouuid.NewV4().String(),
|
||||
Name: name,
|
||||
}
|
||||
|
||||
localPath := attach.LocalPath()
|
||||
if err = os.MkdirAll(path.Dir(localPath), os.ModePerm); err != nil {
|
||||
return nil, fmt.Errorf("MkdirAll: %v", err)
|
||||
}
|
||||
|
||||
fw, err := os.Create(localPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Create: %v", err)
|
||||
}
|
||||
defer fw.Close()
|
||||
|
||||
if _, err = fw.Write(buf); err != nil {
|
||||
return nil, fmt.Errorf("Write: %v", err)
|
||||
} else if _, err = io.Copy(fw, file); err != nil {
|
||||
return nil, fmt.Errorf("Copy: %v", err)
|
||||
}
|
||||
|
||||
if _, err := x.Insert(attach); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return attach, nil
|
||||
}
|
||||
|
||||
func getAttachmentByUUID(e Engine, uuid string) (*Attachment, error) {
|
||||
attach := &Attachment{UUID: uuid}
|
||||
has, err := x.Get(attach)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !has {
|
||||
return nil, ErrAttachmentNotExist{0, uuid}
|
||||
}
|
||||
return attach, nil
|
||||
}
|
||||
|
||||
func getAttachmentsByUUIDs(e Engine, uuids []string) ([]*Attachment, error) {
|
||||
if len(uuids) == 0 {
|
||||
return []*Attachment{}, nil
|
||||
}
|
||||
|
||||
// Silently drop invalid uuids.
|
||||
attachments := make([]*Attachment, 0, len(uuids))
|
||||
return attachments, e.In("uuid", uuids).Find(&attachments)
|
||||
}
|
||||
|
||||
// GetAttachmentByUUID returns attachment by given UUID.
|
||||
func GetAttachmentByUUID(uuid string) (*Attachment, error) {
|
||||
return getAttachmentByUUID(x, uuid)
|
||||
}
|
||||
|
||||
func getAttachmentsByIssueID(e Engine, issueID int64) ([]*Attachment, error) {
|
||||
attachments := make([]*Attachment, 0, 10)
|
||||
return attachments, e.Where("issue_id = ? AND comment_id = 0", issueID).Find(&attachments)
|
||||
}
|
||||
|
||||
// GetAttachmentsByIssueID returns all attachments of an issue.
|
||||
func GetAttachmentsByIssueID(issueID int64) ([]*Attachment, error) {
|
||||
return getAttachmentsByIssueID(x, issueID)
|
||||
}
|
||||
|
||||
func getAttachmentsByCommentID(e Engine, commentID int64) ([]*Attachment, error) {
|
||||
attachments := make([]*Attachment, 0, 10)
|
||||
return attachments, e.Where("comment_id=?", commentID).Find(&attachments)
|
||||
}
|
||||
|
||||
// GetAttachmentsByCommentID returns all attachments if comment by given ID.
|
||||
func GetAttachmentsByCommentID(commentID int64) ([]*Attachment, error) {
|
||||
return getAttachmentsByCommentID(x, commentID)
|
||||
}
|
||||
|
||||
// DeleteAttachment deletes the given attachment and optionally the associated file.
|
||||
func DeleteAttachment(a *Attachment, remove bool) error {
|
||||
_, err := DeleteAttachments([]*Attachment{a}, remove)
|
||||
return err
|
||||
}
|
||||
|
||||
// DeleteAttachments deletes the given attachments and optionally the associated files.
|
||||
func DeleteAttachments(attachments []*Attachment, remove bool) (int, error) {
|
||||
for i, a := range attachments {
|
||||
if remove {
|
||||
if err := os.Remove(a.LocalPath()); err != nil {
|
||||
return i, err
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := x.Delete(a); err != nil {
|
||||
return i, err
|
||||
}
|
||||
}
|
||||
|
||||
return len(attachments), nil
|
||||
}
|
||||
|
||||
// DeleteAttachmentsByIssue deletes all attachments associated with the given issue.
|
||||
func DeleteAttachmentsByIssue(issueId int64, remove bool) (int, error) {
|
||||
attachments, err := GetAttachmentsByIssueID(issueId)
|
||||
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return DeleteAttachments(attachments, remove)
|
||||
}
|
||||
|
||||
// DeleteAttachmentsByComment deletes all attachments associated with the given comment.
|
||||
func DeleteAttachmentsByComment(commentId int64, remove bool) (int, error) {
|
||||
attachments, err := GetAttachmentsByCommentID(commentId)
|
||||
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return DeleteAttachments(attachments, remove)
|
||||
}
|
||||
472
models/comment.go
Normal file
472
models/comment.go
Normal file
@@ -0,0 +1,472 @@
|
||||
// Copyright 2016 The Gogs Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package models
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Unknwon/com"
|
||||
"github.com/go-xorm/xorm"
|
||||
log "gopkg.in/clog.v1"
|
||||
|
||||
api "github.com/gogits/go-gogs-client"
|
||||
|
||||
"github.com/gogits/gogs/modules/markdown"
|
||||
)
|
||||
|
||||
// CommentType defines whether a comment is just a simple comment, an action (like close) or a reference.
|
||||
type CommentType int
|
||||
|
||||
const (
|
||||
// Plain comment, can be associated with a commit (CommitID > 0) and a line (LineNum > 0)
|
||||
COMMENT_TYPE_COMMENT CommentType = iota
|
||||
COMMENT_TYPE_REOPEN
|
||||
COMMENT_TYPE_CLOSE
|
||||
|
||||
// References.
|
||||
COMMENT_TYPE_ISSUE_REF
|
||||
// Reference from a commit (not part of a pull request)
|
||||
COMMENT_TYPE_COMMIT_REF
|
||||
// Reference from a comment
|
||||
COMMENT_TYPE_COMMENT_REF
|
||||
// Reference from a pull request
|
||||
COMMENT_TYPE_PULL_REF
|
||||
)
|
||||
|
||||
type CommentTag int
|
||||
|
||||
const (
|
||||
COMMENT_TAG_NONE CommentTag = iota
|
||||
COMMENT_TAG_POSTER
|
||||
COMMENT_TAG_WRITER
|
||||
COMMENT_TAG_OWNER
|
||||
)
|
||||
|
||||
// Comment represents a comment in commit and issue page.
|
||||
type Comment struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
Type CommentType
|
||||
PosterID int64
|
||||
Poster *User `xorm:"-"`
|
||||
IssueID int64 `xorm:"INDEX"`
|
||||
Issue *Issue `xorm:"-"`
|
||||
CommitID int64
|
||||
Line int64
|
||||
Content string `xorm:"TEXT"`
|
||||
RenderedContent string `xorm:"-"`
|
||||
|
||||
Created time.Time `xorm:"-"`
|
||||
CreatedUnix int64
|
||||
Updated time.Time `xorm:"-"`
|
||||
UpdatedUnix int64
|
||||
|
||||
// Reference issue in commit message
|
||||
CommitSHA string `xorm:"VARCHAR(40)"`
|
||||
|
||||
Attachments []*Attachment `xorm:"-"`
|
||||
|
||||
// For view issue page.
|
||||
ShowTag CommentTag `xorm:"-"`
|
||||
}
|
||||
|
||||
func (c *Comment) BeforeInsert() {
|
||||
c.CreatedUnix = time.Now().Unix()
|
||||
c.UpdatedUnix = c.CreatedUnix
|
||||
}
|
||||
|
||||
func (c *Comment) BeforeUpdate() {
|
||||
c.UpdatedUnix = time.Now().Unix()
|
||||
}
|
||||
|
||||
func (c *Comment) AfterSet(colName string, _ xorm.Cell) {
|
||||
switch colName {
|
||||
case "created_unix":
|
||||
c.Created = time.Unix(c.CreatedUnix, 0).Local()
|
||||
case "updated_unix":
|
||||
c.Updated = time.Unix(c.UpdatedUnix, 0).Local()
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Comment) loadAttributes(e Engine) (err error) {
|
||||
if c.Poster == nil {
|
||||
c.Poster, err = GetUserByID(c.PosterID)
|
||||
if err != nil {
|
||||
if IsErrUserNotExist(err) {
|
||||
c.PosterID = -1
|
||||
c.Poster = NewGhostUser()
|
||||
} else {
|
||||
return fmt.Errorf("getUserByID.(Poster) [%d]: %v", c.PosterID, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if c.Issue == nil {
|
||||
c.Issue, err = getRawIssueByID(e, c.IssueID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("getIssueByID [%d]: %v", c.IssueID, err)
|
||||
}
|
||||
}
|
||||
|
||||
if c.Attachments == nil {
|
||||
c.Attachments, err = getAttachmentsByCommentID(e, c.ID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("getAttachmentsByCommentID [%d]: %v", c.ID, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Comment) LoadAttributes() error {
|
||||
return c.loadAttributes(x)
|
||||
}
|
||||
|
||||
func (c *Comment) AfterDelete() {
|
||||
_, err := DeleteAttachmentsByComment(c.ID, true)
|
||||
|
||||
if err != nil {
|
||||
log.Info("Could not delete files for comment %d on issue #%d: %s", c.ID, c.IssueID, err)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Comment) HTMLURL() string {
|
||||
return fmt.Sprintf("%s#issuecomment-%d", c.Issue.HTMLURL(), c.ID)
|
||||
}
|
||||
|
||||
// This method assumes following fields have been assigned with valid values:
|
||||
// Required - Poster, Issue
|
||||
func (c *Comment) APIFormat() *api.Comment {
|
||||
return &api.Comment{
|
||||
ID: c.ID,
|
||||
HTMLURL: c.HTMLURL(),
|
||||
Poster: c.Poster.APIFormat(),
|
||||
Body: c.Content,
|
||||
Created: c.Created,
|
||||
Updated: c.Updated,
|
||||
}
|
||||
}
|
||||
|
||||
// HashTag returns unique hash tag for comment.
|
||||
func (c *Comment) HashTag() string {
|
||||
return "issuecomment-" + com.ToStr(c.ID)
|
||||
}
|
||||
|
||||
// EventTag returns unique event hash tag for comment.
|
||||
func (c *Comment) EventTag() string {
|
||||
return "event-" + com.ToStr(c.ID)
|
||||
}
|
||||
|
||||
// mailParticipants sends new comment emails to repository watchers
|
||||
// and mentioned people.
|
||||
func (cmt *Comment) mailParticipants(e Engine, opType ActionType, issue *Issue) (err error) {
|
||||
mentions := markdown.FindAllMentions(cmt.Content)
|
||||
if err = updateIssueMentions(e, cmt.IssueID, mentions); err != nil {
|
||||
return fmt.Errorf("UpdateIssueMentions [%d]: %v", cmt.IssueID, err)
|
||||
}
|
||||
|
||||
switch opType {
|
||||
case ACTION_COMMENT_ISSUE:
|
||||
issue.Content = cmt.Content
|
||||
case ACTION_CLOSE_ISSUE:
|
||||
issue.Content = fmt.Sprintf("Closed #%d", issue.Index)
|
||||
case ACTION_REOPEN_ISSUE:
|
||||
issue.Content = fmt.Sprintf("Reopened #%d", issue.Index)
|
||||
}
|
||||
if err = mailIssueCommentToParticipants(issue, cmt.Poster, mentions); err != nil {
|
||||
log.Error(4, "mailIssueCommentToParticipants: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func createComment(e *xorm.Session, opts *CreateCommentOptions) (_ *Comment, err error) {
|
||||
comment := &Comment{
|
||||
Type: opts.Type,
|
||||
PosterID: opts.Doer.ID,
|
||||
Poster: opts.Doer,
|
||||
IssueID: opts.Issue.ID,
|
||||
CommitID: opts.CommitID,
|
||||
CommitSHA: opts.CommitSHA,
|
||||
Line: opts.LineNum,
|
||||
Content: opts.Content,
|
||||
}
|
||||
if _, err = e.Insert(comment); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Compose comment action, could be plain comment, close or reopen issue/pull request.
|
||||
// This object will be used to notify watchers in the end of function.
|
||||
act := &Action{
|
||||
ActUserID: opts.Doer.ID,
|
||||
ActUserName: opts.Doer.Name,
|
||||
Content: fmt.Sprintf("%d|%s", opts.Issue.Index, strings.Split(opts.Content, "\n")[0]),
|
||||
RepoID: opts.Repo.ID,
|
||||
RepoUserName: opts.Repo.Owner.Name,
|
||||
RepoName: opts.Repo.Name,
|
||||
IsPrivate: opts.Repo.IsPrivate,
|
||||
}
|
||||
|
||||
// Check comment type.
|
||||
switch opts.Type {
|
||||
case COMMENT_TYPE_COMMENT:
|
||||
act.OpType = ACTION_COMMENT_ISSUE
|
||||
|
||||
if _, err = e.Exec("UPDATE `issue` SET num_comments=num_comments+1 WHERE id=?", opts.Issue.ID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Check attachments
|
||||
attachments := make([]*Attachment, 0, len(opts.Attachments))
|
||||
for _, uuid := range opts.Attachments {
|
||||
attach, err := getAttachmentByUUID(e, uuid)
|
||||
if err != nil {
|
||||
if IsErrAttachmentNotExist(err) {
|
||||
continue
|
||||
}
|
||||
return nil, fmt.Errorf("getAttachmentByUUID [%s]: %v", uuid, err)
|
||||
}
|
||||
attachments = append(attachments, attach)
|
||||
}
|
||||
|
||||
for i := range attachments {
|
||||
attachments[i].IssueID = opts.Issue.ID
|
||||
attachments[i].CommentID = comment.ID
|
||||
// No assign value could be 0, so ignore AllCols().
|
||||
if _, err = e.Id(attachments[i].ID).Update(attachments[i]); err != nil {
|
||||
return nil, fmt.Errorf("update attachment [%d]: %v", attachments[i].ID, err)
|
||||
}
|
||||
}
|
||||
|
||||
case COMMENT_TYPE_REOPEN:
|
||||
act.OpType = ACTION_REOPEN_ISSUE
|
||||
if opts.Issue.IsPull {
|
||||
act.OpType = ACTION_REOPEN_PULL_REQUEST
|
||||
}
|
||||
|
||||
if opts.Issue.IsPull {
|
||||
_, err = e.Exec("UPDATE `repository` SET num_closed_pulls=num_closed_pulls-1 WHERE id=?", opts.Repo.ID)
|
||||
} else {
|
||||
_, err = e.Exec("UPDATE `repository` SET num_closed_issues=num_closed_issues-1 WHERE id=?", opts.Repo.ID)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
case COMMENT_TYPE_CLOSE:
|
||||
act.OpType = ACTION_CLOSE_ISSUE
|
||||
if opts.Issue.IsPull {
|
||||
act.OpType = ACTION_CLOSE_PULL_REQUEST
|
||||
}
|
||||
|
||||
if opts.Issue.IsPull {
|
||||
_, err = e.Exec("UPDATE `repository` SET num_closed_pulls=num_closed_pulls+1 WHERE id=?", opts.Repo.ID)
|
||||
} else {
|
||||
_, err = e.Exec("UPDATE `repository` SET num_closed_issues=num_closed_issues+1 WHERE id=?", opts.Repo.ID)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Notify watchers for whatever action comes in, ignore if no action type.
|
||||
if act.OpType > 0 {
|
||||
if err = notifyWatchers(e, act); err != nil {
|
||||
log.Error(4, "notifyWatchers: %v", err)
|
||||
}
|
||||
if err = comment.mailParticipants(e, act.OpType, opts.Issue); err != nil {
|
||||
log.Error(4, "MailParticipants: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
return comment, comment.loadAttributes(e)
|
||||
}
|
||||
|
||||
func createStatusComment(e *xorm.Session, doer *User, repo *Repository, issue *Issue) (*Comment, error) {
|
||||
cmtType := COMMENT_TYPE_CLOSE
|
||||
if !issue.IsClosed {
|
||||
cmtType = COMMENT_TYPE_REOPEN
|
||||
}
|
||||
return createComment(e, &CreateCommentOptions{
|
||||
Type: cmtType,
|
||||
Doer: doer,
|
||||
Repo: repo,
|
||||
Issue: issue,
|
||||
})
|
||||
}
|
||||
|
||||
type CreateCommentOptions struct {
|
||||
Type CommentType
|
||||
Doer *User
|
||||
Repo *Repository
|
||||
Issue *Issue
|
||||
|
||||
CommitID int64
|
||||
CommitSHA string
|
||||
LineNum int64
|
||||
Content string
|
||||
Attachments []string // UUIDs of attachments
|
||||
}
|
||||
|
||||
// CreateComment creates comment of issue or commit.
|
||||
func CreateComment(opts *CreateCommentOptions) (comment *Comment, err error) {
|
||||
sess := x.NewSession()
|
||||
defer sessionRelease(sess)
|
||||
if err = sess.Begin(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
comment, err = createComment(sess, opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return comment, sess.Commit()
|
||||
}
|
||||
|
||||
// CreateIssueComment creates a plain issue comment.
|
||||
func CreateIssueComment(doer *User, repo *Repository, issue *Issue, content string, attachments []string) (*Comment, error) {
|
||||
return CreateComment(&CreateCommentOptions{
|
||||
Type: COMMENT_TYPE_COMMENT,
|
||||
Doer: doer,
|
||||
Repo: repo,
|
||||
Issue: issue,
|
||||
Content: content,
|
||||
Attachments: attachments,
|
||||
})
|
||||
}
|
||||
|
||||
// CreateRefComment creates a commit reference comment to issue.
|
||||
func CreateRefComment(doer *User, repo *Repository, issue *Issue, content, commitSHA string) error {
|
||||
if len(commitSHA) == 0 {
|
||||
return fmt.Errorf("cannot create reference with empty commit SHA")
|
||||
}
|
||||
|
||||
// Check if same reference from same commit has already existed.
|
||||
has, err := x.Get(&Comment{
|
||||
Type: COMMENT_TYPE_COMMIT_REF,
|
||||
IssueID: issue.ID,
|
||||
CommitSHA: commitSHA,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("check reference comment: %v", err)
|
||||
} else if has {
|
||||
return nil
|
||||
}
|
||||
|
||||
_, err = CreateComment(&CreateCommentOptions{
|
||||
Type: COMMENT_TYPE_COMMIT_REF,
|
||||
Doer: doer,
|
||||
Repo: repo,
|
||||
Issue: issue,
|
||||
CommitSHA: commitSHA,
|
||||
Content: content,
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
// GetCommentByID returns the comment by given ID.
|
||||
func GetCommentByID(id int64) (*Comment, error) {
|
||||
c := new(Comment)
|
||||
has, err := x.Id(id).Get(c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !has {
|
||||
return nil, ErrCommentNotExist{id, 0}
|
||||
}
|
||||
return c, c.LoadAttributes()
|
||||
}
|
||||
|
||||
// FIXME: use CommentList to improve performance.
|
||||
func loadCommentsAttributes(e Engine, comments []*Comment) (err error) {
|
||||
for i := range comments {
|
||||
if err = comments[i].loadAttributes(e); err != nil {
|
||||
return fmt.Errorf("loadAttributes [%d]: %v", comments[i].ID, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getCommentsByIssueIDSince(e Engine, issueID, since int64) ([]*Comment, error) {
|
||||
comments := make([]*Comment, 0, 10)
|
||||
sess := e.Where("issue_id = ?", issueID).Asc("created_unix")
|
||||
if since > 0 {
|
||||
sess.And("updated_unix >= ?", since)
|
||||
}
|
||||
|
||||
if err := sess.Find(&comments); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return comments, loadCommentsAttributes(e, comments)
|
||||
}
|
||||
|
||||
func getCommentsByRepoIDSince(e Engine, repoID, since int64) ([]*Comment, error) {
|
||||
comments := make([]*Comment, 0, 10)
|
||||
sess := e.Where("issue.repo_id = ?", repoID).Join("INNER", "issue", "issue.id = comment.issue_id", repoID).Asc("created_unix")
|
||||
if since > 0 {
|
||||
sess.And("updated_unix >= ?", since)
|
||||
}
|
||||
if err := sess.Find(&comments); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return comments, loadCommentsAttributes(e, comments)
|
||||
}
|
||||
|
||||
func getCommentsByIssueID(e Engine, issueID int64) ([]*Comment, error) {
|
||||
return getCommentsByIssueIDSince(e, issueID, -1)
|
||||
}
|
||||
|
||||
// GetCommentsByIssueID returns all comments of an issue.
|
||||
func GetCommentsByIssueID(issueID int64) ([]*Comment, error) {
|
||||
return getCommentsByIssueID(x, issueID)
|
||||
}
|
||||
|
||||
// GetCommentsByIssueIDSince returns a list of comments of an issue since a given time point.
|
||||
func GetCommentsByIssueIDSince(issueID, since int64) ([]*Comment, error) {
|
||||
return getCommentsByIssueIDSince(x, issueID, since)
|
||||
}
|
||||
|
||||
// GetCommentsByRepoIDSince returns a list of comments for all issues in a repo since a given time point.
|
||||
func GetCommentsByRepoIDSince(repoID, since int64) ([]*Comment, error) {
|
||||
return getCommentsByRepoIDSince(x, repoID, since)
|
||||
}
|
||||
|
||||
// UpdateComment updates information of comment.
|
||||
func UpdateComment(c *Comment) error {
|
||||
_, err := x.Id(c.ID).AllCols().Update(c)
|
||||
return err
|
||||
}
|
||||
|
||||
// DeleteCommentByID deletes the comment by given ID.
|
||||
func DeleteCommentByID(id int64) error {
|
||||
comment, err := GetCommentByID(id)
|
||||
if err != nil {
|
||||
if IsErrCommentNotExist(err) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
sess := x.NewSession()
|
||||
defer sessionRelease(sess)
|
||||
if err = sess.Begin(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err = sess.Id(comment.ID).Delete(new(Comment)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if comment.Type == COMMENT_TYPE_COMMENT {
|
||||
if _, err = sess.Exec("UPDATE `issue` SET num_comments = num_comments - 1 WHERE id = ?", comment.IssueID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return sess.Commit()
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
// Copyright 2014 The Gogs Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package cron
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/gogits/gogs/models"
|
||||
"github.com/gogits/gogs/modules/cron"
|
||||
"github.com/gogits/gogs/modules/log"
|
||||
"github.com/gogits/gogs/modules/setting"
|
||||
)
|
||||
|
||||
var c = cron.New()
|
||||
|
||||
func NewCronContext() {
|
||||
var (
|
||||
entry *cron.Entry
|
||||
err error
|
||||
)
|
||||
if setting.Cron.UpdateMirror.Enabled {
|
||||
entry, err = c.AddFunc("Update mirrors", setting.Cron.UpdateMirror.Schedule, models.MirrorUpdate)
|
||||
if err != nil {
|
||||
log.Fatal(4, "Cron[Update mirrors]: %v", err)
|
||||
}
|
||||
if setting.Cron.UpdateMirror.RunAtStart {
|
||||
entry.Prev = time.Now()
|
||||
go models.MirrorUpdate()
|
||||
}
|
||||
}
|
||||
if setting.Cron.RepoHealthCheck.Enabled {
|
||||
entry, err = c.AddFunc("Repository health check", setting.Cron.RepoHealthCheck.Schedule, models.GitFsck)
|
||||
if err != nil {
|
||||
log.Fatal(4, "Cron[Repository health check]: %v", err)
|
||||
}
|
||||
if setting.Cron.RepoHealthCheck.RunAtStart {
|
||||
entry.Prev = time.Now()
|
||||
go models.GitFsck()
|
||||
}
|
||||
}
|
||||
if setting.Cron.CheckRepoStats.Enabled {
|
||||
entry, err = c.AddFunc("Check repository statistics", setting.Cron.CheckRepoStats.Schedule, models.CheckRepoStats)
|
||||
if err != nil {
|
||||
log.Fatal(4, "Cron[Check repository statistics]: %v", err)
|
||||
}
|
||||
if setting.Cron.CheckRepoStats.RunAtStart {
|
||||
entry.Prev = time.Now()
|
||||
go models.CheckRepoStats()
|
||||
}
|
||||
}
|
||||
c.Start()
|
||||
}
|
||||
|
||||
// ListTasks returns all running cron tasks.
|
||||
func ListTasks() []*cron.Entry {
|
||||
return c.Entries()
|
||||
}
|
||||
324
models/error.go
324
models/error.go
@@ -18,7 +18,7 @@ func IsErrNameReserved(err error) bool {
|
||||
}
|
||||
|
||||
func (err ErrNameReserved) Error() string {
|
||||
return fmt.Sprintf("name is reserved: [name: %s]", err.Name)
|
||||
return fmt.Sprintf("name is reserved [name: %s]", err.Name)
|
||||
}
|
||||
|
||||
type ErrNamePatternNotAllowed struct {
|
||||
@@ -31,7 +31,7 @@ func IsErrNamePatternNotAllowed(err error) bool {
|
||||
}
|
||||
|
||||
func (err ErrNamePatternNotAllowed) Error() string {
|
||||
return fmt.Sprintf("name pattern is not allowed: [pattern: %s]", err.Pattern)
|
||||
return fmt.Sprintf("name pattern is not allowed [pattern: %s]", err.Pattern)
|
||||
}
|
||||
|
||||
// ____ ___
|
||||
@@ -51,7 +51,7 @@ func IsErrUserAlreadyExist(err error) bool {
|
||||
}
|
||||
|
||||
func (err ErrUserAlreadyExist) Error() string {
|
||||
return fmt.Sprintf("user already exists: [name: %s]", err.Name)
|
||||
return fmt.Sprintf("user already exists [name: %s]", err.Name)
|
||||
}
|
||||
|
||||
type ErrUserNotExist struct {
|
||||
@@ -65,7 +65,7 @@ func IsErrUserNotExist(err error) bool {
|
||||
}
|
||||
|
||||
func (err ErrUserNotExist) Error() string {
|
||||
return fmt.Sprintf("user does not exist: [uid: %d, name: %s]", err.UID, err.Name)
|
||||
return fmt.Sprintf("user does not exist [uid: %d, name: %s]", err.UID, err.Name)
|
||||
}
|
||||
|
||||
type ErrEmailAlreadyUsed struct {
|
||||
@@ -78,7 +78,7 @@ func IsErrEmailAlreadyUsed(err error) bool {
|
||||
}
|
||||
|
||||
func (err ErrEmailAlreadyUsed) Error() string {
|
||||
return fmt.Sprintf("e-mail has been used: [email: %s]", err.Email)
|
||||
return fmt.Sprintf("e-mail has been used [email: %s]", err.Email)
|
||||
}
|
||||
|
||||
type ErrUserOwnRepos struct {
|
||||
@@ -91,7 +91,7 @@ func IsErrUserOwnRepos(err error) bool {
|
||||
}
|
||||
|
||||
func (err ErrUserOwnRepos) Error() string {
|
||||
return fmt.Sprintf("user still has ownership of repositories: [uid: %d]", err.UID)
|
||||
return fmt.Sprintf("user still has ownership of repositories [uid: %d]", err.UID)
|
||||
}
|
||||
|
||||
type ErrUserHasOrgs struct {
|
||||
@@ -104,7 +104,40 @@ func IsErrUserHasOrgs(err error) bool {
|
||||
}
|
||||
|
||||
func (err ErrUserHasOrgs) Error() string {
|
||||
return fmt.Sprintf("user still has membership of organizations: [uid: %d]", err.UID)
|
||||
return fmt.Sprintf("user still has membership of organizations [uid: %d]", err.UID)
|
||||
}
|
||||
|
||||
type ErrReachLimitOfRepo struct {
|
||||
Limit int
|
||||
}
|
||||
|
||||
func IsErrReachLimitOfRepo(err error) bool {
|
||||
_, ok := err.(ErrReachLimitOfRepo)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrReachLimitOfRepo) Error() string {
|
||||
return fmt.Sprintf("user has reached maximum limit of repositories [limit: %d]", err.Limit)
|
||||
}
|
||||
|
||||
// __ __.__ __ .__
|
||||
// / \ / \__| | _|__|
|
||||
// \ \/\/ / | |/ / |
|
||||
// \ /| | <| |
|
||||
// \__/\ / |__|__|_ \__|
|
||||
// \/ \/
|
||||
|
||||
type ErrWikiAlreadyExist struct {
|
||||
Title string
|
||||
}
|
||||
|
||||
func IsErrWikiAlreadyExist(err error) bool {
|
||||
_, ok := err.(ErrWikiAlreadyExist)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrWikiAlreadyExist) Error() string {
|
||||
return fmt.Sprintf("wiki page already exists [title: %s]", err.Title)
|
||||
}
|
||||
|
||||
// __________ ___. .__ .__ ____ __.
|
||||
@@ -114,6 +147,19 @@ func (err ErrUserHasOrgs) Error() string {
|
||||
// |____| |____/|___ /____/__|\___ > |____|__ \___ > ____|
|
||||
// \/ \/ \/ \/\/
|
||||
|
||||
type ErrKeyUnableVerify struct {
|
||||
Result string
|
||||
}
|
||||
|
||||
func IsErrKeyUnableVerify(err error) bool {
|
||||
_, ok := err.(ErrKeyUnableVerify)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrKeyUnableVerify) Error() string {
|
||||
return fmt.Sprintf("Unable to verify key content [result: %s]", err.Result)
|
||||
}
|
||||
|
||||
type ErrKeyNotExist struct {
|
||||
ID int64
|
||||
}
|
||||
@@ -124,7 +170,7 @@ func IsErrKeyNotExist(err error) bool {
|
||||
}
|
||||
|
||||
func (err ErrKeyNotExist) Error() string {
|
||||
return fmt.Sprintf("public key does not exist: [id: %d]", err.ID)
|
||||
return fmt.Sprintf("public key does not exist [id: %d]", err.ID)
|
||||
}
|
||||
|
||||
type ErrKeyAlreadyExist struct {
|
||||
@@ -138,7 +184,7 @@ func IsErrKeyAlreadyExist(err error) bool {
|
||||
}
|
||||
|
||||
func (err ErrKeyAlreadyExist) Error() string {
|
||||
return fmt.Sprintf("public key already exists: [owner_id: %d, content: %s]", err.OwnerID, err.Content)
|
||||
return fmt.Sprintf("public key already exists [owner_id: %d, content: %s]", err.OwnerID, err.Content)
|
||||
}
|
||||
|
||||
type ErrKeyNameAlreadyUsed struct {
|
||||
@@ -152,7 +198,38 @@ func IsErrKeyNameAlreadyUsed(err error) bool {
|
||||
}
|
||||
|
||||
func (err ErrKeyNameAlreadyUsed) Error() string {
|
||||
return fmt.Sprintf("public key already exists: [owner_id: %d, name: %s]", err.OwnerID, err.Name)
|
||||
return fmt.Sprintf("public key already exists [owner_id: %d, name: %s]", err.OwnerID, err.Name)
|
||||
}
|
||||
|
||||
type ErrKeyAccessDenied struct {
|
||||
UserID int64
|
||||
KeyID int64
|
||||
Note string
|
||||
}
|
||||
|
||||
func IsErrKeyAccessDenied(err error) bool {
|
||||
_, ok := err.(ErrKeyAccessDenied)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrKeyAccessDenied) Error() string {
|
||||
return fmt.Sprintf("user does not have access to the key [user_id: %d, key_id: %d, note: %s]",
|
||||
err.UserID, err.KeyID, err.Note)
|
||||
}
|
||||
|
||||
type ErrDeployKeyNotExist struct {
|
||||
ID int64
|
||||
KeyID int64
|
||||
RepoID int64
|
||||
}
|
||||
|
||||
func IsErrDeployKeyNotExist(err error) bool {
|
||||
_, ok := err.(ErrDeployKeyNotExist)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrDeployKeyNotExist) Error() string {
|
||||
return fmt.Sprintf("Deploy key does not exist [id: %d, key_id: %d, repo_id: %d]", err.ID, err.KeyID, err.RepoID)
|
||||
}
|
||||
|
||||
type ErrDeployKeyAlreadyExist struct {
|
||||
@@ -166,7 +243,7 @@ func IsErrDeployKeyAlreadyExist(err error) bool {
|
||||
}
|
||||
|
||||
func (err ErrDeployKeyAlreadyExist) Error() string {
|
||||
return fmt.Sprintf("public key already exists: [key_id: %d, repo_id: %d]", err.KeyID, err.RepoID)
|
||||
return fmt.Sprintf("public key already exists [key_id: %d, repo_id: %d]", err.KeyID, err.RepoID)
|
||||
}
|
||||
|
||||
type ErrDeployKeyNameAlreadyUsed struct {
|
||||
@@ -180,7 +257,7 @@ func IsErrDeployKeyNameAlreadyUsed(err error) bool {
|
||||
}
|
||||
|
||||
func (err ErrDeployKeyNameAlreadyUsed) Error() string {
|
||||
return fmt.Sprintf("public key already exists: [repo_id: %d, name: %s]", err.RepoID, err.Name)
|
||||
return fmt.Sprintf("public key already exists [repo_id: %d, name: %s]", err.RepoID, err.Name)
|
||||
}
|
||||
|
||||
// _____ ___________ __
|
||||
@@ -200,7 +277,19 @@ func IsErrAccessTokenNotExist(err error) bool {
|
||||
}
|
||||
|
||||
func (err ErrAccessTokenNotExist) Error() string {
|
||||
return fmt.Sprintf("access token does not exist: [sha: %s]", err.SHA)
|
||||
return fmt.Sprintf("access token does not exist [sha: %s]", err.SHA)
|
||||
}
|
||||
|
||||
type ErrAccessTokenEmpty struct {
|
||||
}
|
||||
|
||||
func IsErrAccessTokenEmpty(err error) bool {
|
||||
_, ok := err.(ErrAccessTokenEmpty)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrAccessTokenEmpty) Error() string {
|
||||
return fmt.Sprintf("access token is empty")
|
||||
}
|
||||
|
||||
// ________ .__ __ .__
|
||||
@@ -220,7 +309,7 @@ func IsErrLastOrgOwner(err error) bool {
|
||||
}
|
||||
|
||||
func (err ErrLastOrgOwner) Error() string {
|
||||
return fmt.Sprintf("user is the last member of owner team: [uid: %d]", err.UID)
|
||||
return fmt.Sprintf("user is the last member of owner team [uid: %d]", err.UID)
|
||||
}
|
||||
|
||||
// __________ .__ __
|
||||
@@ -259,6 +348,108 @@ func (err ErrRepoAlreadyExist) Error() string {
|
||||
return fmt.Sprintf("repository already exists [uname: %s, name: %s]", err.Uname, err.Name)
|
||||
}
|
||||
|
||||
type ErrInvalidCloneAddr struct {
|
||||
IsURLError bool
|
||||
IsInvalidPath bool
|
||||
IsPermissionDenied bool
|
||||
}
|
||||
|
||||
func IsErrInvalidCloneAddr(err error) bool {
|
||||
_, ok := err.(ErrInvalidCloneAddr)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrInvalidCloneAddr) Error() string {
|
||||
return fmt.Sprintf("invalid clone address [is_url_error: %v, is_invalid_path: %v, is_permission_denied: %v]",
|
||||
err.IsURLError, err.IsInvalidPath, err.IsPermissionDenied)
|
||||
}
|
||||
|
||||
type ErrUpdateTaskNotExist struct {
|
||||
UUID string
|
||||
}
|
||||
|
||||
func IsErrUpdateTaskNotExist(err error) bool {
|
||||
_, ok := err.(ErrUpdateTaskNotExist)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrUpdateTaskNotExist) Error() string {
|
||||
return fmt.Sprintf("update task does not exist [uuid: %s]", err.UUID)
|
||||
}
|
||||
|
||||
type ErrReleaseAlreadyExist struct {
|
||||
TagName string
|
||||
}
|
||||
|
||||
func IsErrReleaseAlreadyExist(err error) bool {
|
||||
_, ok := err.(ErrReleaseAlreadyExist)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrReleaseAlreadyExist) Error() string {
|
||||
return fmt.Sprintf("release tag already exist [tag_name: %s]", err.TagName)
|
||||
}
|
||||
|
||||
type ErrReleaseNotExist struct {
|
||||
ID int64
|
||||
TagName string
|
||||
}
|
||||
|
||||
func IsErrReleaseNotExist(err error) bool {
|
||||
_, ok := err.(ErrReleaseNotExist)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrReleaseNotExist) Error() string {
|
||||
return fmt.Sprintf("release tag does not exist [id: %d, tag_name: %s]", err.ID, err.TagName)
|
||||
}
|
||||
|
||||
type ErrInvalidTagName struct {
|
||||
TagName string
|
||||
}
|
||||
|
||||
func IsErrInvalidTagName(err error) bool {
|
||||
_, ok := err.(ErrInvalidTagName)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrInvalidTagName) Error() string {
|
||||
return fmt.Sprintf("release tag name is not valid [tag_name: %s]", err.TagName)
|
||||
}
|
||||
|
||||
type ErrRepoFileAlreadyExist struct {
|
||||
FileName string
|
||||
}
|
||||
|
||||
func IsErrRepoFileAlreadyExist(err error) bool {
|
||||
_, ok := err.(ErrRepoFileAlreadyExist)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrRepoFileAlreadyExist) Error() string {
|
||||
return fmt.Sprintf("repository file already exists [file_name: %s]", err.FileName)
|
||||
}
|
||||
|
||||
// __________ .__
|
||||
// \______ \____________ ____ ____ | |__
|
||||
// | | _/\_ __ \__ \ / \_/ ___\| | \
|
||||
// | | \ | | \// __ \| | \ \___| Y \
|
||||
// |______ / |__| (____ /___| /\___ >___| /
|
||||
// \/ \/ \/ \/ \/
|
||||
|
||||
type ErrBranchNotExist struct {
|
||||
Name string
|
||||
}
|
||||
|
||||
func IsErrBranchNotExist(err error) bool {
|
||||
_, ok := err.(ErrBranchNotExist)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrBranchNotExist) Error() string {
|
||||
return fmt.Sprintf("branch does not exist [name: %s]", err.Name)
|
||||
}
|
||||
|
||||
// __ __ ___. .__ __
|
||||
// / \ / \ ____\_ |__ | |__ ____ ____ | | __
|
||||
// \ \/\/ // __ \| __ \| | \ / _ \ / _ \| |/ /
|
||||
@@ -310,7 +501,7 @@ func (err ErrIssueNotExist) Error() string {
|
||||
|
||||
type ErrPullRequestNotExist struct {
|
||||
ID int64
|
||||
PullID int64
|
||||
IssueID int64
|
||||
HeadRepoID int64
|
||||
BaseRepoID int64
|
||||
HeadBarcnh string
|
||||
@@ -323,8 +514,8 @@ func IsErrPullRequestNotExist(err error) bool {
|
||||
}
|
||||
|
||||
func (err ErrPullRequestNotExist) Error() string {
|
||||
return fmt.Sprintf("pull request does not exist [id: %d, pull_id: %d, head_repo_id: %d, base_repo_id: %d, head_branch: %s, base_branch: %s]",
|
||||
err.ID, err.PullID, err.HeadRepoID, err.BaseRepoID, err.HeadBarcnh, err.BaseBranch)
|
||||
return fmt.Sprintf("pull request does not exist [id: %d, issue_id: %d, head_repo_id: %d, base_repo_id: %d, head_branch: %s, base_branch: %s]",
|
||||
err.ID, err.IssueID, err.HeadRepoID, err.BaseRepoID, err.HeadBarcnh, err.BaseBranch)
|
||||
}
|
||||
|
||||
// _________ __
|
||||
@@ -335,7 +526,8 @@ func (err ErrPullRequestNotExist) Error() string {
|
||||
// \/ \/ \/ \/ \/
|
||||
|
||||
type ErrCommentNotExist struct {
|
||||
ID int64
|
||||
ID int64
|
||||
IssueID int64
|
||||
}
|
||||
|
||||
func IsErrCommentNotExist(err error) bool {
|
||||
@@ -344,7 +536,7 @@ func IsErrCommentNotExist(err error) bool {
|
||||
}
|
||||
|
||||
func (err ErrCommentNotExist) Error() string {
|
||||
return fmt.Sprintf("comment does not exist [id: %d]", err.ID)
|
||||
return fmt.Sprintf("comment does not exist [id: %d, issue_id: %d]", err.ID, err.IssueID)
|
||||
}
|
||||
|
||||
// .____ ___. .__
|
||||
@@ -355,7 +547,8 @@ func (err ErrCommentNotExist) Error() string {
|
||||
// \/ \/ \/ \/
|
||||
|
||||
type ErrLabelNotExist struct {
|
||||
ID int64
|
||||
LabelID int64
|
||||
RepoID int64
|
||||
}
|
||||
|
||||
func IsErrLabelNotExist(err error) bool {
|
||||
@@ -364,7 +557,7 @@ func IsErrLabelNotExist(err error) bool {
|
||||
}
|
||||
|
||||
func (err ErrLabelNotExist) Error() string {
|
||||
return fmt.Sprintf("label does not exist [id: %d]", err.ID)
|
||||
return fmt.Sprintf("label does not exist [label_id: %d, repo_id: %d]", err.LabelID, err.RepoID)
|
||||
}
|
||||
|
||||
// _____ .__.__ __
|
||||
@@ -408,3 +601,92 @@ func IsErrAttachmentNotExist(err error) bool {
|
||||
func (err ErrAttachmentNotExist) Error() string {
|
||||
return fmt.Sprintf("attachment does not exist [id: %d, uuid: %s]", err.ID, err.UUID)
|
||||
}
|
||||
|
||||
// .____ .__ _________
|
||||
// | | ____ ____ |__| ____ / _____/ ____ __ _________ ____ ____
|
||||
// | | / _ \ / ___\| |/ \ \_____ \ / _ \| | \_ __ \_/ ___\/ __ \
|
||||
// | |__( <_> ) /_/ > | | \ / ( <_> ) | /| | \/\ \__\ ___/
|
||||
// |_______ \____/\___ /|__|___| / /_______ /\____/|____/ |__| \___ >___ >
|
||||
// \/ /_____/ \/ \/ \/ \/
|
||||
|
||||
type ErrLoginSourceNotExist struct {
|
||||
ID int64
|
||||
}
|
||||
|
||||
func IsErrLoginSourceNotExist(err error) bool {
|
||||
_, ok := err.(ErrLoginSourceNotExist)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrLoginSourceNotExist) Error() string {
|
||||
return fmt.Sprintf("login source does not exist [id: %d]", err.ID)
|
||||
}
|
||||
|
||||
type ErrLoginSourceAlreadyExist struct {
|
||||
Name string
|
||||
}
|
||||
|
||||
func IsErrLoginSourceAlreadyExist(err error) bool {
|
||||
_, ok := err.(ErrLoginSourceAlreadyExist)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrLoginSourceAlreadyExist) Error() string {
|
||||
return fmt.Sprintf("login source already exists [name: %s]", err.Name)
|
||||
}
|
||||
|
||||
type ErrLoginSourceInUse struct {
|
||||
ID int64
|
||||
}
|
||||
|
||||
func IsErrLoginSourceInUse(err error) bool {
|
||||
_, ok := err.(ErrLoginSourceInUse)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrLoginSourceInUse) Error() string {
|
||||
return fmt.Sprintf("login source is still used by some users [id: %d]", err.ID)
|
||||
}
|
||||
|
||||
// ___________
|
||||
// \__ ___/___ _____ _____
|
||||
// | |_/ __ \\__ \ / \
|
||||
// | |\ ___/ / __ \| Y Y \
|
||||
// |____| \___ >____ /__|_| /
|
||||
// \/ \/ \/
|
||||
|
||||
type ErrTeamAlreadyExist struct {
|
||||
OrgID int64
|
||||
Name string
|
||||
}
|
||||
|
||||
func IsErrTeamAlreadyExist(err error) bool {
|
||||
_, ok := err.(ErrTeamAlreadyExist)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrTeamAlreadyExist) Error() string {
|
||||
return fmt.Sprintf("team already exists [org_id: %d, name: %s]", err.OrgID, err.Name)
|
||||
}
|
||||
|
||||
// ____ ___ .__ .___
|
||||
// | | \______ | | _________ __| _/
|
||||
// | | /\____ \| | / _ \__ \ / __ |
|
||||
// | | / | |_> > |_( <_> ) __ \_/ /_/ |
|
||||
// |______/ | __/|____/\____(____ /\____ |
|
||||
// |__| \/ \/
|
||||
//
|
||||
|
||||
type ErrUploadNotExist struct {
|
||||
ID int64
|
||||
UUID string
|
||||
}
|
||||
|
||||
func IsErrUploadNotExist(err error) bool {
|
||||
_, ok := err.(ErrAttachmentNotExist)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrUploadNotExist) Error() string {
|
||||
return fmt.Sprintf("attachment does not exist [id: %d, uuid: %s]", err.ID, err.UUID)
|
||||
}
|
||||
|
||||
@@ -8,46 +8,55 @@ import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"html"
|
||||
"html/template"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/html/charset"
|
||||
"golang.org/x/text/transform"
|
||||
|
||||
"github.com/Unknwon/com"
|
||||
"github.com/sergi/go-diff/diffmatchpatch"
|
||||
"golang.org/x/net/html/charset"
|
||||
"golang.org/x/text/transform"
|
||||
log "gopkg.in/clog.v1"
|
||||
|
||||
"github.com/gogits/git-module"
|
||||
|
||||
"github.com/gogits/gogs/modules/base"
|
||||
"github.com/gogits/gogs/modules/git"
|
||||
"github.com/gogits/gogs/modules/log"
|
||||
"github.com/gogits/gogs/modules/process"
|
||||
"github.com/gogits/gogs/modules/setting"
|
||||
"github.com/gogits/gogs/modules/template/highlight"
|
||||
)
|
||||
|
||||
// Diff line types.
|
||||
type DiffLineType uint8
|
||||
|
||||
const (
|
||||
DIFF_LINE_PLAIN = iota + 1
|
||||
DIFF_LINE_PLAIN DiffLineType = iota + 1
|
||||
DIFF_LINE_ADD
|
||||
DIFF_LINE_DEL
|
||||
DIFF_LINE_SECTION
|
||||
)
|
||||
|
||||
type DiffFileType uint8
|
||||
|
||||
const (
|
||||
DIFF_FILE_ADD = iota + 1
|
||||
DIFF_FILE_ADD DiffFileType = iota + 1
|
||||
DIFF_FILE_CHANGE
|
||||
DIFF_FILE_DEL
|
||||
DIFF_FILE_RENAME
|
||||
)
|
||||
|
||||
type DiffLine struct {
|
||||
LeftIdx int
|
||||
RightIdx int
|
||||
Type int
|
||||
Type DiffLineType
|
||||
Content string
|
||||
}
|
||||
|
||||
func (d DiffLine) GetType() int {
|
||||
return d.Type
|
||||
func (d *DiffLine) GetType() int {
|
||||
return int(d.Type)
|
||||
}
|
||||
|
||||
type DiffSection struct {
|
||||
@@ -55,20 +64,154 @@ type DiffSection struct {
|
||||
Lines []*DiffLine
|
||||
}
|
||||
|
||||
var (
|
||||
addedCodePrefix = []byte("<span class=\"added-code\">")
|
||||
removedCodePrefix = []byte("<span class=\"removed-code\">")
|
||||
codeTagSuffix = []byte("</span>")
|
||||
)
|
||||
|
||||
func diffToHTML(diffs []diffmatchpatch.Diff, lineType DiffLineType) template.HTML {
|
||||
buf := bytes.NewBuffer(nil)
|
||||
|
||||
// Reproduce signs which are cutted for inline diff before.
|
||||
switch lineType {
|
||||
case DIFF_LINE_ADD:
|
||||
buf.WriteByte('+')
|
||||
case DIFF_LINE_DEL:
|
||||
buf.WriteByte('-')
|
||||
}
|
||||
|
||||
for i := range diffs {
|
||||
switch {
|
||||
case diffs[i].Type == diffmatchpatch.DiffInsert && lineType == DIFF_LINE_ADD:
|
||||
buf.Write(addedCodePrefix)
|
||||
buf.WriteString(html.EscapeString(diffs[i].Text))
|
||||
buf.Write(codeTagSuffix)
|
||||
case diffs[i].Type == diffmatchpatch.DiffDelete && lineType == DIFF_LINE_DEL:
|
||||
buf.Write(removedCodePrefix)
|
||||
buf.WriteString(html.EscapeString(diffs[i].Text))
|
||||
buf.Write(codeTagSuffix)
|
||||
case diffs[i].Type == diffmatchpatch.DiffEqual:
|
||||
buf.WriteString(html.EscapeString(diffs[i].Text))
|
||||
}
|
||||
}
|
||||
|
||||
return template.HTML(buf.Bytes())
|
||||
}
|
||||
|
||||
// get an specific line by type (add or del) and file line number
|
||||
func (diffSection *DiffSection) GetLine(lineType DiffLineType, idx int) *DiffLine {
|
||||
var (
|
||||
difference = 0
|
||||
addCount = 0
|
||||
delCount = 0
|
||||
matchDiffLine *DiffLine
|
||||
)
|
||||
|
||||
LOOP:
|
||||
for _, diffLine := range diffSection.Lines {
|
||||
switch diffLine.Type {
|
||||
case DIFF_LINE_ADD:
|
||||
addCount++
|
||||
case DIFF_LINE_DEL:
|
||||
delCount++
|
||||
default:
|
||||
if matchDiffLine != nil {
|
||||
break LOOP
|
||||
}
|
||||
difference = diffLine.RightIdx - diffLine.LeftIdx
|
||||
addCount = 0
|
||||
delCount = 0
|
||||
}
|
||||
|
||||
switch lineType {
|
||||
case DIFF_LINE_DEL:
|
||||
if diffLine.RightIdx == 0 && diffLine.LeftIdx == idx-difference {
|
||||
matchDiffLine = diffLine
|
||||
}
|
||||
case DIFF_LINE_ADD:
|
||||
if diffLine.LeftIdx == 0 && diffLine.RightIdx == idx+difference {
|
||||
matchDiffLine = diffLine
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if addCount == delCount {
|
||||
return matchDiffLine
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var diffMatchPatch = diffmatchpatch.New()
|
||||
|
||||
func init() {
|
||||
diffMatchPatch.DiffEditCost = 100
|
||||
}
|
||||
|
||||
// computes inline diff for the given line
|
||||
func (diffSection *DiffSection) GetComputedInlineDiffFor(diffLine *DiffLine) template.HTML {
|
||||
if setting.Git.DisableDiffHighlight {
|
||||
return template.HTML(html.EscapeString(diffLine.Content[1:]))
|
||||
}
|
||||
var (
|
||||
compareDiffLine *DiffLine
|
||||
diff1 string
|
||||
diff2 string
|
||||
)
|
||||
|
||||
// try to find equivalent diff line. ignore, otherwise
|
||||
switch diffLine.Type {
|
||||
case DIFF_LINE_ADD:
|
||||
compareDiffLine = diffSection.GetLine(DIFF_LINE_DEL, diffLine.RightIdx)
|
||||
if compareDiffLine == nil {
|
||||
return template.HTML(html.EscapeString(diffLine.Content))
|
||||
}
|
||||
diff1 = compareDiffLine.Content
|
||||
diff2 = diffLine.Content
|
||||
case DIFF_LINE_DEL:
|
||||
compareDiffLine = diffSection.GetLine(DIFF_LINE_ADD, diffLine.LeftIdx)
|
||||
if compareDiffLine == nil {
|
||||
return template.HTML(html.EscapeString(diffLine.Content))
|
||||
}
|
||||
diff1 = diffLine.Content
|
||||
diff2 = compareDiffLine.Content
|
||||
default:
|
||||
return template.HTML(html.EscapeString(diffLine.Content))
|
||||
}
|
||||
|
||||
diffRecord := diffMatchPatch.DiffMain(diff1[1:], diff2[1:], true)
|
||||
diffRecord = diffMatchPatch.DiffCleanupEfficiency(diffRecord)
|
||||
|
||||
return diffToHTML(diffRecord, diffLine.Type)
|
||||
}
|
||||
|
||||
type DiffFile struct {
|
||||
Name string
|
||||
Index int
|
||||
OldName string
|
||||
Index string // 40-byte SHA, Changed/New: new SHA; Deleted: old SHA
|
||||
Addition, Deletion int
|
||||
Type int
|
||||
Type DiffFileType
|
||||
IsCreated bool
|
||||
IsDeleted bool
|
||||
IsBin bool
|
||||
IsRenamed bool
|
||||
IsSubmodule bool
|
||||
Sections []*DiffSection
|
||||
IsIncomplete bool
|
||||
}
|
||||
|
||||
func (diffFile *DiffFile) GetType() int {
|
||||
return int(diffFile.Type)
|
||||
}
|
||||
|
||||
func (diffFile *DiffFile) GetHighlightClass() string {
|
||||
return highlight.FileNameToHighlightClass(diffFile.Name)
|
||||
}
|
||||
|
||||
type Diff struct {
|
||||
TotalAddition, TotalDeletion int
|
||||
Files []*DiffFile
|
||||
IsIncomplete bool
|
||||
}
|
||||
|
||||
func (diff *Diff) NumFiles() int {
|
||||
@@ -77,39 +220,48 @@ func (diff *Diff) NumFiles() int {
|
||||
|
||||
const DIFF_HEAD = "diff --git "
|
||||
|
||||
func ParsePatch(pid int64, maxlines int, cmd *exec.Cmd, reader io.Reader) (*Diff, error) {
|
||||
scanner := bufio.NewScanner(reader)
|
||||
// TODO: move this function to gogits/git-module
|
||||
func ParsePatch(maxLines, maxLineCharacteres, maxFiles int, reader io.Reader) (*Diff, error) {
|
||||
var (
|
||||
diff = &Diff{Files: make([]*DiffFile, 0)}
|
||||
|
||||
curFile *DiffFile
|
||||
curSection = &DiffSection{
|
||||
Lines: make([]*DiffLine, 0, 10),
|
||||
}
|
||||
|
||||
leftLine, rightLine int
|
||||
isTooLong bool
|
||||
// FIXME: Should use cache in the future.
|
||||
buf bytes.Buffer
|
||||
lineCount int
|
||||
curFileLinesCount int
|
||||
)
|
||||
|
||||
diff := &Diff{Files: make([]*DiffFile, 0)}
|
||||
var i int
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
// fmt.Println(i, line)
|
||||
if strings.HasPrefix(line, "+++ ") || strings.HasPrefix(line, "--- ") {
|
||||
input := bufio.NewReader(reader)
|
||||
isEOF := false
|
||||
for !isEOF {
|
||||
line, err := input.ReadString('\n')
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
isEOF = true
|
||||
} else {
|
||||
return nil, fmt.Errorf("ReadString: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if len(line) > 0 && line[len(line)-1] == '\n' {
|
||||
// Remove line break.
|
||||
line = line[:len(line)-1]
|
||||
}
|
||||
|
||||
if strings.HasPrefix(line, "+++ ") || strings.HasPrefix(line, "--- ") || len(line) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
i = i + 1
|
||||
curFileLinesCount++
|
||||
lineCount++
|
||||
|
||||
// Diff data too large, we only show the first about maxlines lines
|
||||
if i == maxlines {
|
||||
isTooLong = true
|
||||
log.Warn("Diff data too large")
|
||||
if curFileLinesCount >= maxLines || len(line) >= maxLineCharacteres {
|
||||
curFile.IsIncomplete = true
|
||||
}
|
||||
|
||||
switch {
|
||||
@@ -120,10 +272,6 @@ func ParsePatch(pid int64, maxlines int, cmd *exec.Cmd, reader io.Reader) (*Diff
|
||||
curSection.Lines = append(curSection.Lines, diffLine)
|
||||
continue
|
||||
case line[0] == '@':
|
||||
if isTooLong {
|
||||
break
|
||||
}
|
||||
|
||||
curSection = &DiffSection{}
|
||||
curFile.Sections = append(curFile.Sections, curSection)
|
||||
ss := strings.Split(line, "@@")
|
||||
@@ -162,50 +310,82 @@ func ParsePatch(pid int64, maxlines int, cmd *exec.Cmd, reader io.Reader) (*Diff
|
||||
|
||||
// Get new file.
|
||||
if strings.HasPrefix(line, DIFF_HEAD) {
|
||||
if isTooLong {
|
||||
break
|
||||
middle := -1
|
||||
|
||||
// Note: In case file name is surrounded by double quotes (it happens only in git-shell).
|
||||
// e.g. diff --git "a/xxx" "b/xxx"
|
||||
hasQuote := line[len(DIFF_HEAD)] == '"'
|
||||
if hasQuote {
|
||||
middle = strings.Index(line, ` "b/`)
|
||||
} else {
|
||||
middle = strings.Index(line, " b/")
|
||||
}
|
||||
|
||||
beg := len(DIFF_HEAD)
|
||||
a := line[beg : (len(line)-beg)/2+beg]
|
||||
|
||||
// In case file name is surrounded by double quotes(it happens only in git-shell).
|
||||
if a[0] == '"' {
|
||||
a = a[1 : len(a)-1]
|
||||
a = strings.Replace(a, `\"`, `"`, -1)
|
||||
a := line[beg+2 : middle]
|
||||
b := line[middle+3:]
|
||||
if hasQuote {
|
||||
a = string(git.UnescapeChars([]byte(a[1 : len(a)-1])))
|
||||
b = string(git.UnescapeChars([]byte(b[1 : len(b)-1])))
|
||||
}
|
||||
|
||||
curFile = &DiffFile{
|
||||
Name: a[strings.Index(a, "/")+1:],
|
||||
Index: len(diff.Files) + 1,
|
||||
Name: a,
|
||||
Type: DIFF_FILE_CHANGE,
|
||||
Sections: make([]*DiffSection, 0, 10),
|
||||
}
|
||||
diff.Files = append(diff.Files, curFile)
|
||||
if len(diff.Files) >= maxFiles {
|
||||
diff.IsIncomplete = true
|
||||
io.Copy(ioutil.Discard, reader)
|
||||
break
|
||||
}
|
||||
curFileLinesCount = 0
|
||||
|
||||
// Check file diff type.
|
||||
for scanner.Scan() {
|
||||
switch {
|
||||
case strings.HasPrefix(scanner.Text(), "new file"):
|
||||
curFile.Type = DIFF_FILE_ADD
|
||||
curFile.IsDeleted = false
|
||||
curFile.IsCreated = true
|
||||
case strings.HasPrefix(scanner.Text(), "deleted"):
|
||||
curFile.Type = DIFF_FILE_DEL
|
||||
curFile.IsCreated = false
|
||||
curFile.IsDeleted = true
|
||||
case strings.HasPrefix(scanner.Text(), "index"):
|
||||
curFile.Type = DIFF_FILE_CHANGE
|
||||
curFile.IsCreated = false
|
||||
curFile.IsDeleted = false
|
||||
// Check file diff type and submodule.
|
||||
CHECK_TYPE:
|
||||
for {
|
||||
line, err := input.ReadString('\n')
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
isEOF = true
|
||||
} else {
|
||||
return nil, fmt.Errorf("ReadString: %v", err)
|
||||
}
|
||||
}
|
||||
if curFile.Type > 0 {
|
||||
break
|
||||
|
||||
switch {
|
||||
case strings.HasPrefix(line, "new file"):
|
||||
curFile.Type = DIFF_FILE_ADD
|
||||
curFile.IsCreated = true
|
||||
curFile.IsSubmodule = strings.HasSuffix(line, " 160000\n")
|
||||
case strings.HasPrefix(line, "deleted"):
|
||||
curFile.Type = DIFF_FILE_DEL
|
||||
curFile.IsDeleted = true
|
||||
curFile.IsSubmodule = strings.HasSuffix(line, " 160000\n")
|
||||
case strings.HasPrefix(line, "index"):
|
||||
if curFile.IsDeleted {
|
||||
curFile.Index = line[6:46]
|
||||
} else if len(line) >= 88 {
|
||||
curFile.Index = line[49:88]
|
||||
} else {
|
||||
curFile.Index = curFile.Name
|
||||
}
|
||||
break CHECK_TYPE
|
||||
case strings.HasPrefix(line, "similarity index 100%"):
|
||||
curFile.Type = DIFF_FILE_RENAME
|
||||
curFile.IsRenamed = true
|
||||
curFile.OldName = curFile.Name
|
||||
curFile.Name = b
|
||||
curFile.Index = b
|
||||
break CHECK_TYPE
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: detect encoding while parsing.
|
||||
var buf bytes.Buffer
|
||||
for _, f := range diff.Files {
|
||||
buf.Reset()
|
||||
for _, sec := range f.Sections {
|
||||
@@ -232,63 +412,110 @@ func ParsePatch(pid int64, maxlines int, cmd *exec.Cmd, reader io.Reader) (*Diff
|
||||
return diff, nil
|
||||
}
|
||||
|
||||
func GetDiffRange(repoPath, beforeCommitId string, afterCommitId string, maxlines int) (*Diff, error) {
|
||||
repo, err := git.OpenRepository(repoPath)
|
||||
func GetDiffRange(repoPath, beforeCommitID, afterCommitID string, maxLines, maxLineCharacteres, maxFiles int) (*Diff, error) {
|
||||
gitRepo, err := git.OpenRepository(repoPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
commit, err := repo.GetCommit(afterCommitId)
|
||||
commit, err := gitRepo.GetCommit(afterCommitID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rd, wr := io.Pipe()
|
||||
var cmd *exec.Cmd
|
||||
// if "after" commit given
|
||||
if beforeCommitId == "" {
|
||||
if len(beforeCommitID) == 0 {
|
||||
// First commit of repository.
|
||||
if commit.ParentCount() == 0 {
|
||||
cmd = exec.Command("git", "show", afterCommitId)
|
||||
cmd = exec.Command("git", "show", "--full-index", afterCommitID)
|
||||
} else {
|
||||
c, _ := commit.Parent(0)
|
||||
cmd = exec.Command("git", "diff", c.Id.String(), afterCommitId)
|
||||
cmd = exec.Command("git", "diff", "--full-index", "-M", c.ID.String(), afterCommitID)
|
||||
}
|
||||
} else {
|
||||
cmd = exec.Command("git", "diff", beforeCommitId, afterCommitId)
|
||||
cmd = exec.Command("git", "diff", "--full-index", "-M", beforeCommitID, afterCommitID)
|
||||
}
|
||||
cmd.Dir = repoPath
|
||||
cmd.Stdout = wr
|
||||
cmd.Stdin = os.Stdin
|
||||
cmd.Stderr = os.Stderr
|
||||
|
||||
done := make(chan error)
|
||||
go func() {
|
||||
cmd.Start()
|
||||
done <- cmd.Wait()
|
||||
wr.Close()
|
||||
}()
|
||||
defer rd.Close()
|
||||
stdout, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("StdoutPipe: %v", err)
|
||||
}
|
||||
|
||||
desc := fmt.Sprintf("GetDiffRange(%s)", repoPath)
|
||||
pid := process.Add(desc, cmd)
|
||||
go func() {
|
||||
// In case process became zombie.
|
||||
select {
|
||||
case <-time.After(5 * time.Minute):
|
||||
if errKill := process.Kill(pid); errKill != nil {
|
||||
log.Error(4, "git_diff.ParsePatch(Kill): %v", err)
|
||||
}
|
||||
<-done
|
||||
// return "", ErrExecTimeout.Error(), ErrExecTimeout
|
||||
case err = <-done:
|
||||
process.Remove(pid)
|
||||
if err = cmd.Start(); err != nil {
|
||||
return nil, fmt.Errorf("Start: %v", err)
|
||||
}
|
||||
|
||||
pid := process.Add(fmt.Sprintf("GetDiffRange [repo_path: %s]", repoPath), cmd)
|
||||
defer process.Remove(pid)
|
||||
|
||||
diff, err := ParsePatch(maxLines, maxLineCharacteres, maxFiles, stdout)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("ParsePatch: %v", err)
|
||||
}
|
||||
|
||||
if err = cmd.Wait(); err != nil {
|
||||
return nil, fmt.Errorf("Wait: %v", err)
|
||||
}
|
||||
|
||||
return diff, nil
|
||||
}
|
||||
|
||||
type RawDiffType string
|
||||
|
||||
const (
|
||||
RAW_DIFF_NORMAL RawDiffType = "diff"
|
||||
RAW_DIFF_PATCH RawDiffType = "patch"
|
||||
)
|
||||
|
||||
// GetRawDiff dumps diff results of repository in given commit ID to io.Writer.
|
||||
// TODO: move this function to gogits/git-module
|
||||
func GetRawDiff(repoPath, commitID string, diffType RawDiffType, writer io.Writer) error {
|
||||
repo, err := git.OpenRepository(repoPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("OpenRepository: %v", err)
|
||||
}
|
||||
|
||||
commit, err := repo.GetCommit(commitID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("GetCommit: %v", err)
|
||||
}
|
||||
|
||||
var cmd *exec.Cmd
|
||||
switch diffType {
|
||||
case RAW_DIFF_NORMAL:
|
||||
if commit.ParentCount() == 0 {
|
||||
cmd = exec.Command("git", "show", commitID)
|
||||
} else {
|
||||
c, _ := commit.Parent(0)
|
||||
cmd = exec.Command("git", "diff", "-M", c.ID.String(), commitID)
|
||||
}
|
||||
}()
|
||||
case RAW_DIFF_PATCH:
|
||||
if commit.ParentCount() == 0 {
|
||||
cmd = exec.Command("git", "format-patch", "--no-signature", "--stdout", "--root", commitID)
|
||||
} else {
|
||||
c, _ := commit.Parent(0)
|
||||
query := fmt.Sprintf("%s...%s", commitID, c.ID.String())
|
||||
cmd = exec.Command("git", "format-patch", "--no-signature", "--stdout", query)
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("invalid diffType: %s", diffType)
|
||||
}
|
||||
|
||||
return ParsePatch(pid, maxlines, cmd, rd)
|
||||
stderr := new(bytes.Buffer)
|
||||
|
||||
cmd.Dir = repoPath
|
||||
cmd.Stdout = writer
|
||||
cmd.Stderr = stderr
|
||||
|
||||
if err = cmd.Run(); err != nil {
|
||||
return fmt.Errorf("Run: %v - %s", err, stderr)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetDiffCommit(repoPath, commitId string, maxlines int) (*Diff, error) {
|
||||
return GetDiffRange(repoPath, "", commitId, maxlines)
|
||||
func GetDiffCommit(repoPath, commitID string, maxLines, maxLineCharacteres, maxFiles int) (*Diff, error) {
|
||||
return GetDiffRange(repoPath, "", commitID, maxLines, maxLineCharacteres, maxFiles)
|
||||
}
|
||||
|
||||
35
models/git_diff_test.go
Normal file
35
models/git_diff_test.go
Normal file
@@ -0,0 +1,35 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
dmp "github.com/sergi/go-diff/diffmatchpatch"
|
||||
"html/template"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func assertEqual(t *testing.T, s1 string, s2 template.HTML) {
|
||||
if s1 != string(s2) {
|
||||
t.Errorf("%s should be equal %s", s2, s1)
|
||||
}
|
||||
}
|
||||
|
||||
func assertLineEqual(t *testing.T, d1 *DiffLine, d2 *DiffLine) {
|
||||
if d1 != d2 {
|
||||
t.Errorf("%v should be equal %v", d1, d2)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDiffToHTML(t *testing.T) {
|
||||
assertEqual(t, "+foo <span class=\"added-code\">bar</span> biz", diffToHTML([]dmp.Diff{
|
||||
dmp.Diff{dmp.DiffEqual, "foo "},
|
||||
dmp.Diff{dmp.DiffInsert, "bar"},
|
||||
dmp.Diff{dmp.DiffDelete, " baz"},
|
||||
dmp.Diff{dmp.DiffEqual, " biz"},
|
||||
}, DIFF_LINE_ADD))
|
||||
|
||||
assertEqual(t, "-foo <span class=\"removed-code\">bar</span> biz", diffToHTML([]dmp.Diff{
|
||||
dmp.Diff{dmp.DiffEqual, "foo "},
|
||||
dmp.Diff{dmp.DiffDelete, "bar"},
|
||||
dmp.Diff{dmp.DiffInsert, " baz"},
|
||||
dmp.Diff{dmp.DiffEqual, " biz"},
|
||||
}, DIFF_LINE_DEL))
|
||||
}
|
||||
2271
models/issue.go
2271
models/issue.go
File diff suppressed because it is too large
Load Diff
332
models/issue_label.go
Normal file
332
models/issue_label.go
Normal file
@@ -0,0 +1,332 @@
|
||||
// Copyright 2016 The Gogs Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package models
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"html/template"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/go-xorm/xorm"
|
||||
|
||||
api "github.com/gogits/go-gogs-client"
|
||||
|
||||
"github.com/gogits/gogs/modules/base"
|
||||
)
|
||||
|
||||
var labelColorPattern = regexp.MustCompile("#([a-fA-F0-9]{6})")
|
||||
|
||||
// GetLabelTemplateFile loads the label template file by given name,
|
||||
// then parses and returns a list of name-color pairs.
|
||||
func GetLabelTemplateFile(name string) ([][2]string, error) {
|
||||
data, err := getRepoInitFile("label", name)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("getRepoInitFile: %v", err)
|
||||
}
|
||||
|
||||
lines := strings.Split(string(data), "\n")
|
||||
list := make([][2]string, 0, len(lines))
|
||||
for i := 0; i < len(lines); i++ {
|
||||
line := strings.TrimSpace(lines[i])
|
||||
if len(line) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
fields := strings.SplitN(line, " ", 2)
|
||||
if len(fields) != 2 {
|
||||
return nil, fmt.Errorf("line is malformed: %s", line)
|
||||
}
|
||||
|
||||
if !labelColorPattern.MatchString(fields[0]) {
|
||||
return nil, fmt.Errorf("bad HTML color code in line: %s", line)
|
||||
}
|
||||
|
||||
fields[1] = strings.TrimSpace(fields[1])
|
||||
list = append(list, [2]string{fields[1], fields[0]})
|
||||
}
|
||||
|
||||
return list, nil
|
||||
}
|
||||
|
||||
// Label represents a label of repository for issues.
|
||||
type Label struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
RepoID int64 `xorm:"INDEX"`
|
||||
Name string
|
||||
Color string `xorm:"VARCHAR(7)"`
|
||||
NumIssues int
|
||||
NumClosedIssues int
|
||||
NumOpenIssues int `xorm:"-"`
|
||||
IsChecked bool `xorm:"-"`
|
||||
}
|
||||
|
||||
func (label *Label) APIFormat() *api.Label {
|
||||
return &api.Label{
|
||||
ID: label.ID,
|
||||
Name: label.Name,
|
||||
Color: label.Color,
|
||||
}
|
||||
}
|
||||
|
||||
// CalOpenIssues calculates the open issues of label.
|
||||
func (label *Label) CalOpenIssues() {
|
||||
label.NumOpenIssues = label.NumIssues - label.NumClosedIssues
|
||||
}
|
||||
|
||||
// ForegroundColor calculates the text color for labels based
|
||||
// on their background color.
|
||||
func (l *Label) ForegroundColor() template.CSS {
|
||||
if strings.HasPrefix(l.Color, "#") {
|
||||
if color, err := strconv.ParseUint(l.Color[1:], 16, 64); err == nil {
|
||||
r := float32(0xFF & (color >> 16))
|
||||
g := float32(0xFF & (color >> 8))
|
||||
b := float32(0xFF & color)
|
||||
luminance := (0.2126*r + 0.7152*g + 0.0722*b) / 255
|
||||
|
||||
if luminance < 0.66 {
|
||||
return template.CSS("#fff")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// default to black
|
||||
return template.CSS("#000")
|
||||
}
|
||||
|
||||
// NewLabels creates new label(s) for a repository.
|
||||
func NewLabels(labels ...*Label) error {
|
||||
_, err := x.Insert(labels)
|
||||
return err
|
||||
}
|
||||
|
||||
// getLabelOfRepoByID returns a label by ID in given repository.
|
||||
// If pass repoID as 0, then ORM will ignore limitation of repository
|
||||
// and can return arbitrary label with any valid ID.
|
||||
func getLabelOfRepoByID(e Engine, repoID, labelID int64) (*Label, error) {
|
||||
if labelID <= 0 {
|
||||
return nil, ErrLabelNotExist{labelID, repoID}
|
||||
}
|
||||
|
||||
l := &Label{
|
||||
ID: labelID,
|
||||
RepoID: repoID,
|
||||
}
|
||||
has, err := x.Get(l)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !has {
|
||||
return nil, ErrLabelNotExist{l.ID, l.RepoID}
|
||||
}
|
||||
return l, nil
|
||||
}
|
||||
|
||||
// GetLabelByID returns a label by given ID.
|
||||
func GetLabelByID(id int64) (*Label, error) {
|
||||
return getLabelOfRepoByID(x, 0, id)
|
||||
}
|
||||
|
||||
// GetLabelOfRepoByID returns a label by ID in given repository.
|
||||
func GetLabelOfRepoByID(repoID, labelID int64) (*Label, error) {
|
||||
return getLabelOfRepoByID(x, repoID, labelID)
|
||||
}
|
||||
|
||||
// GetLabelsInRepoByIDs returns a list of labels by IDs in given repository,
|
||||
// it silently ignores label IDs that are not belong to the repository.
|
||||
func GetLabelsInRepoByIDs(repoID int64, labelIDs []int64) ([]*Label, error) {
|
||||
labels := make([]*Label, 0, len(labelIDs))
|
||||
return labels, x.Where("repo_id = ?", repoID).In("id", base.Int64sToStrings(labelIDs)).Asc("name").Find(&labels)
|
||||
}
|
||||
|
||||
// GetLabelsByRepoID returns all labels that belong to given repository by ID.
|
||||
func GetLabelsByRepoID(repoID int64) ([]*Label, error) {
|
||||
labels := make([]*Label, 0, 10)
|
||||
return labels, x.Where("repo_id = ?", repoID).Asc("name").Find(&labels)
|
||||
}
|
||||
|
||||
func getLabelsByIssueID(e Engine, issueID int64) ([]*Label, error) {
|
||||
issueLabels, err := getIssueLabels(e, issueID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("getIssueLabels: %v", err)
|
||||
} else if len(issueLabels) == 0 {
|
||||
return []*Label{}, nil
|
||||
}
|
||||
|
||||
labelIDs := make([]int64, len(issueLabels))
|
||||
for i := range issueLabels {
|
||||
labelIDs[i] = issueLabels[i].LabelID
|
||||
}
|
||||
|
||||
labels := make([]*Label, 0, len(labelIDs))
|
||||
return labels, e.Where("id > 0").In("id", base.Int64sToStrings(labelIDs)).Asc("name").Find(&labels)
|
||||
}
|
||||
|
||||
// GetLabelsByIssueID returns all labels that belong to given issue by ID.
|
||||
func GetLabelsByIssueID(issueID int64) ([]*Label, error) {
|
||||
return getLabelsByIssueID(x, issueID)
|
||||
}
|
||||
|
||||
func updateLabel(e Engine, l *Label) error {
|
||||
_, err := e.Id(l.ID).AllCols().Update(l)
|
||||
return err
|
||||
}
|
||||
|
||||
// UpdateLabel updates label information.
|
||||
func UpdateLabel(l *Label) error {
|
||||
return updateLabel(x, l)
|
||||
}
|
||||
|
||||
// DeleteLabel delete a label of given repository.
|
||||
func DeleteLabel(repoID, labelID int64) error {
|
||||
_, err := GetLabelOfRepoByID(repoID, labelID)
|
||||
if err != nil {
|
||||
if IsErrLabelNotExist(err) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
sess := x.NewSession()
|
||||
defer sessionRelease(sess)
|
||||
if err = sess.Begin(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err = sess.Id(labelID).Delete(new(Label)); err != nil {
|
||||
return err
|
||||
} else if _, err = sess.Where("label_id = ?", labelID).Delete(new(IssueLabel)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return sess.Commit()
|
||||
}
|
||||
|
||||
// .___ .____ ___. .__
|
||||
// | | ______ ________ __ ____ | | _____ \_ |__ ____ | |
|
||||
// | |/ ___// ___/ | \_/ __ \| | \__ \ | __ \_/ __ \| |
|
||||
// | |\___ \ \___ \| | /\ ___/| |___ / __ \| \_\ \ ___/| |__
|
||||
// |___/____ >____ >____/ \___ >_______ (____ /___ /\___ >____/
|
||||
// \/ \/ \/ \/ \/ \/ \/
|
||||
|
||||
// IssueLabel represetns an issue-lable relation.
|
||||
type IssueLabel struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
IssueID int64 `xorm:"UNIQUE(s)"`
|
||||
LabelID int64 `xorm:"UNIQUE(s)"`
|
||||
}
|
||||
|
||||
func hasIssueLabel(e Engine, issueID, labelID int64) bool {
|
||||
has, _ := e.Where("issue_id = ? AND label_id = ?", issueID, labelID).Get(new(IssueLabel))
|
||||
return has
|
||||
}
|
||||
|
||||
// HasIssueLabel returns true if issue has been labeled.
|
||||
func HasIssueLabel(issueID, labelID int64) bool {
|
||||
return hasIssueLabel(x, issueID, labelID)
|
||||
}
|
||||
|
||||
func newIssueLabel(e *xorm.Session, issue *Issue, label *Label) (err error) {
|
||||
if _, err = e.Insert(&IssueLabel{
|
||||
IssueID: issue.ID,
|
||||
LabelID: label.ID,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
label.NumIssues++
|
||||
if issue.IsClosed {
|
||||
label.NumClosedIssues++
|
||||
}
|
||||
return updateLabel(e, label)
|
||||
}
|
||||
|
||||
// NewIssueLabel creates a new issue-label relation.
|
||||
func NewIssueLabel(issue *Issue, label *Label) (err error) {
|
||||
if HasIssueLabel(issue.ID, label.ID) {
|
||||
return nil
|
||||
}
|
||||
|
||||
sess := x.NewSession()
|
||||
defer sessionRelease(sess)
|
||||
if err = sess.Begin(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = newIssueLabel(sess, issue, label); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return sess.Commit()
|
||||
}
|
||||
|
||||
func newIssueLabels(e *xorm.Session, issue *Issue, labels []*Label) (err error) {
|
||||
for i := range labels {
|
||||
if hasIssueLabel(e, issue.ID, labels[i].ID) {
|
||||
continue
|
||||
}
|
||||
|
||||
if err = newIssueLabel(e, issue, labels[i]); err != nil {
|
||||
return fmt.Errorf("newIssueLabel: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewIssueLabels creates a list of issue-label relations.
|
||||
func NewIssueLabels(issue *Issue, labels []*Label) (err error) {
|
||||
sess := x.NewSession()
|
||||
defer sessionRelease(sess)
|
||||
if err = sess.Begin(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = newIssueLabels(sess, issue, labels); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return sess.Commit()
|
||||
}
|
||||
|
||||
func getIssueLabels(e Engine, issueID int64) ([]*IssueLabel, error) {
|
||||
issueLabels := make([]*IssueLabel, 0, 10)
|
||||
return issueLabels, e.Where("issue_id=?", issueID).Asc("label_id").Find(&issueLabels)
|
||||
}
|
||||
|
||||
// GetIssueLabels returns all issue-label relations of given issue by ID.
|
||||
func GetIssueLabels(issueID int64) ([]*IssueLabel, error) {
|
||||
return getIssueLabels(x, issueID)
|
||||
}
|
||||
|
||||
func deleteIssueLabel(e *xorm.Session, issue *Issue, label *Label) (err error) {
|
||||
if _, err = e.Delete(&IssueLabel{
|
||||
IssueID: issue.ID,
|
||||
LabelID: label.ID,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
label.NumIssues--
|
||||
if issue.IsClosed {
|
||||
label.NumClosedIssues--
|
||||
}
|
||||
return updateLabel(e, label)
|
||||
}
|
||||
|
||||
// DeleteIssueLabel deletes issue-label relation.
|
||||
func DeleteIssueLabel(issue *Issue, label *Label) (err error) {
|
||||
sess := x.NewSession()
|
||||
defer sessionRelease(sess)
|
||||
if err = sess.Begin(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = deleteIssueLabel(sess, issue, label); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return sess.Commit()
|
||||
}
|
||||
153
models/issue_mail.go
Normal file
153
models/issue_mail.go
Normal file
@@ -0,0 +1,153 @@
|
||||
// Copyright 2016 The Gogs Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package models
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/Unknwon/com"
|
||||
log "gopkg.in/clog.v1"
|
||||
|
||||
"github.com/gogits/gogs/modules/mailer"
|
||||
"github.com/gogits/gogs/modules/markdown"
|
||||
"github.com/gogits/gogs/modules/setting"
|
||||
)
|
||||
|
||||
func (issue *Issue) MailSubject() string {
|
||||
return fmt.Sprintf("[%s] %s (#%d)", issue.Repo.Name, issue.Title, issue.Index)
|
||||
}
|
||||
|
||||
// mailerUser is a wrapper for satisfying mailer.User interface.
|
||||
type mailerUser struct {
|
||||
user *User
|
||||
}
|
||||
|
||||
func (this mailerUser) ID() int64 {
|
||||
return this.user.ID
|
||||
}
|
||||
|
||||
func (this mailerUser) DisplayName() string {
|
||||
return this.user.DisplayName()
|
||||
}
|
||||
|
||||
func (this mailerUser) Email() string {
|
||||
return this.user.Email
|
||||
}
|
||||
|
||||
func (this mailerUser) GenerateActivateCode() string {
|
||||
return this.user.GenerateActivateCode()
|
||||
}
|
||||
|
||||
func (this mailerUser) GenerateEmailActivateCode(email string) string {
|
||||
return this.user.GenerateEmailActivateCode(email)
|
||||
}
|
||||
|
||||
func NewMailerUser(u *User) mailer.User {
|
||||
return mailerUser{u}
|
||||
}
|
||||
|
||||
// mailerRepo is a wrapper for satisfying mailer.Repository interface.
|
||||
type mailerRepo struct {
|
||||
repo *Repository
|
||||
}
|
||||
|
||||
func (this mailerRepo) FullName() string {
|
||||
return this.repo.FullName()
|
||||
}
|
||||
|
||||
func (this mailerRepo) HTMLURL() string {
|
||||
return this.repo.HTMLURL()
|
||||
}
|
||||
|
||||
func (this mailerRepo) ComposeMetas() map[string]string {
|
||||
return this.repo.ComposeMetas()
|
||||
}
|
||||
|
||||
func NewMailerRepo(repo *Repository) mailer.Repository {
|
||||
return mailerRepo{repo}
|
||||
}
|
||||
|
||||
// mailerIssue is a wrapper for satisfying mailer.Issue interface.
|
||||
type mailerIssue struct {
|
||||
issue *Issue
|
||||
}
|
||||
|
||||
func (this mailerIssue) MailSubject() string {
|
||||
return this.issue.MailSubject()
|
||||
}
|
||||
|
||||
func (this mailerIssue) Content() string {
|
||||
return this.issue.Content
|
||||
}
|
||||
|
||||
func (this mailerIssue) HTMLURL() string {
|
||||
return this.issue.HTMLURL()
|
||||
}
|
||||
|
||||
func NewMailerIssue(issue *Issue) mailer.Issue {
|
||||
return mailerIssue{issue}
|
||||
}
|
||||
|
||||
// mailIssueCommentToParticipants can be used for both new issue creation and comment.
|
||||
func mailIssueCommentToParticipants(issue *Issue, doer *User, mentions []string) error {
|
||||
if !setting.Service.EnableNotifyMail {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Mail wahtcers.
|
||||
watchers, err := GetWatchers(issue.RepoID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("GetWatchers [%d]: %v", issue.RepoID, err)
|
||||
}
|
||||
|
||||
tos := make([]string, 0, len(watchers)) // List of email addresses.
|
||||
names := make([]string, 0, len(watchers))
|
||||
for i := range watchers {
|
||||
if watchers[i].UserID == doer.ID {
|
||||
continue
|
||||
}
|
||||
|
||||
to, err := GetUserByID(watchers[i].UserID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("GetUserByID [%d]: %v", watchers[i].UserID, err)
|
||||
}
|
||||
if to.IsOrganization() {
|
||||
continue
|
||||
}
|
||||
|
||||
tos = append(tos, to.Email)
|
||||
names = append(names, to.Name)
|
||||
}
|
||||
mailer.SendIssueCommentMail(NewMailerIssue(issue), NewMailerRepo(issue.Repo), NewMailerUser(doer), tos)
|
||||
|
||||
// Mail mentioned people and exclude watchers.
|
||||
names = append(names, doer.Name)
|
||||
tos = make([]string, 0, len(mentions)) // list of user names.
|
||||
for i := range mentions {
|
||||
if com.IsSliceContainsStr(names, mentions[i]) {
|
||||
continue
|
||||
}
|
||||
|
||||
tos = append(tos, mentions[i])
|
||||
}
|
||||
mailer.SendIssueMentionMail(NewMailerIssue(issue), NewMailerRepo(issue.Repo), NewMailerUser(doer), GetUserEmailsByNames(tos))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// MailParticipants sends new issue thread created emails to repository watchers
|
||||
// and mentioned people.
|
||||
func (issue *Issue) MailParticipants() (err error) {
|
||||
mentions := markdown.FindAllMentions(issue.Content)
|
||||
if err = updateIssueMentions(x, issue.ID, mentions); err != nil {
|
||||
return fmt.Errorf("UpdateIssueMentions [%d]: %v", issue.ID, err)
|
||||
}
|
||||
|
||||
if err = mailIssueCommentToParticipants(issue, issue.Poster, mentions); err != nil {
|
||||
log.Error(4, "mailIssueCommentToParticipants: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
428
models/login.go
428
models/login.go
@@ -1,428 +0,0 @@
|
||||
// Copyright 2014 The Gogs Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package models
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/smtp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-xorm/core"
|
||||
"github.com/go-xorm/xorm"
|
||||
|
||||
"github.com/gogits/gogs/modules/auth/ldap"
|
||||
"github.com/gogits/gogs/modules/auth/pam"
|
||||
"github.com/gogits/gogs/modules/log"
|
||||
)
|
||||
|
||||
type LoginType int
|
||||
|
||||
const (
|
||||
NOTYPE LoginType = iota
|
||||
PLAIN
|
||||
LDAP
|
||||
SMTP
|
||||
PAM
|
||||
)
|
||||
|
||||
var (
|
||||
ErrAuthenticationAlreadyExist = errors.New("Authentication already exist")
|
||||
ErrAuthenticationNotExist = errors.New("Authentication does not exist")
|
||||
ErrAuthenticationUserUsed = errors.New("Authentication has been used by some users")
|
||||
)
|
||||
|
||||
var LoginTypes = map[LoginType]string{
|
||||
LDAP: "LDAP",
|
||||
SMTP: "SMTP",
|
||||
PAM: "PAM",
|
||||
}
|
||||
|
||||
// Ensure structs implemented interface.
|
||||
var (
|
||||
_ core.Conversion = &LDAPConfig{}
|
||||
_ core.Conversion = &SMTPConfig{}
|
||||
_ core.Conversion = &PAMConfig{}
|
||||
)
|
||||
|
||||
type LDAPConfig struct {
|
||||
ldap.Ldapsource
|
||||
}
|
||||
|
||||
func (cfg *LDAPConfig) FromDB(bs []byte) error {
|
||||
return json.Unmarshal(bs, &cfg.Ldapsource)
|
||||
}
|
||||
|
||||
func (cfg *LDAPConfig) ToDB() ([]byte, error) {
|
||||
return json.Marshal(cfg.Ldapsource)
|
||||
}
|
||||
|
||||
type SMTPConfig struct {
|
||||
Auth string
|
||||
Host string
|
||||
Port int
|
||||
TLS bool
|
||||
SkipVerify bool
|
||||
}
|
||||
|
||||
func (cfg *SMTPConfig) FromDB(bs []byte) error {
|
||||
return json.Unmarshal(bs, cfg)
|
||||
}
|
||||
|
||||
func (cfg *SMTPConfig) ToDB() ([]byte, error) {
|
||||
return json.Marshal(cfg)
|
||||
}
|
||||
|
||||
type PAMConfig struct {
|
||||
ServiceName string // pam service (e.g. system-auth)
|
||||
}
|
||||
|
||||
func (cfg *PAMConfig) FromDB(bs []byte) error {
|
||||
return json.Unmarshal(bs, &cfg)
|
||||
}
|
||||
|
||||
func (cfg *PAMConfig) ToDB() ([]byte, error) {
|
||||
return json.Marshal(cfg)
|
||||
}
|
||||
|
||||
type LoginSource struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
Type LoginType
|
||||
Name string `xorm:"UNIQUE"`
|
||||
IsActived bool `xorm:"NOT NULL DEFAULT false"`
|
||||
Cfg core.Conversion `xorm:"TEXT"`
|
||||
AllowAutoRegister bool `xorm:"NOT NULL DEFAULT false"`
|
||||
Created time.Time `xorm:"CREATED"`
|
||||
Updated time.Time `xorm:"UPDATED"`
|
||||
}
|
||||
|
||||
func (source *LoginSource) BeforeSet(colName string, val xorm.Cell) {
|
||||
switch colName {
|
||||
case "type":
|
||||
switch LoginType((*val).(int64)) {
|
||||
case LDAP:
|
||||
source.Cfg = new(LDAPConfig)
|
||||
case SMTP:
|
||||
source.Cfg = new(SMTPConfig)
|
||||
case PAM:
|
||||
source.Cfg = new(PAMConfig)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (source *LoginSource) TypeString() string {
|
||||
return LoginTypes[source.Type]
|
||||
}
|
||||
|
||||
func (source *LoginSource) LDAP() *LDAPConfig {
|
||||
return source.Cfg.(*LDAPConfig)
|
||||
}
|
||||
|
||||
func (source *LoginSource) SMTP() *SMTPConfig {
|
||||
return source.Cfg.(*SMTPConfig)
|
||||
}
|
||||
|
||||
func (source *LoginSource) PAM() *PAMConfig {
|
||||
return source.Cfg.(*PAMConfig)
|
||||
}
|
||||
|
||||
func CreateSource(source *LoginSource) error {
|
||||
_, err := x.Insert(source)
|
||||
return err
|
||||
}
|
||||
|
||||
func GetAuths() ([]*LoginSource, error) {
|
||||
auths := make([]*LoginSource, 0, 5)
|
||||
return auths, x.Find(&auths)
|
||||
}
|
||||
|
||||
func GetLoginSourceByID(id int64) (*LoginSource, error) {
|
||||
source := new(LoginSource)
|
||||
has, err := x.Id(id).Get(source)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !has {
|
||||
return nil, ErrAuthenticationNotExist
|
||||
}
|
||||
return source, nil
|
||||
}
|
||||
|
||||
func UpdateSource(source *LoginSource) error {
|
||||
_, err := x.Id(source.ID).AllCols().Update(source)
|
||||
return err
|
||||
}
|
||||
|
||||
func DelLoginSource(source *LoginSource) error {
|
||||
cnt, err := x.Count(&User{LoginSource: source.ID})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if cnt > 0 {
|
||||
return ErrAuthenticationUserUsed
|
||||
}
|
||||
_, err = x.Id(source.ID).Delete(&LoginSource{})
|
||||
return err
|
||||
}
|
||||
|
||||
// UserSignIn validates user name and password.
|
||||
func UserSignIn(uname, passwd string) (*User, error) {
|
||||
u := new(User)
|
||||
if strings.Contains(uname, "@") {
|
||||
u = &User{Email: uname}
|
||||
} else {
|
||||
u = &User{LowerName: strings.ToLower(uname)}
|
||||
}
|
||||
|
||||
has, err := x.Get(u)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if u.LoginType == NOTYPE && has {
|
||||
u.LoginType = PLAIN
|
||||
}
|
||||
|
||||
// For plain login, user must exist to reach this line.
|
||||
// Now verify password.
|
||||
if u.LoginType == PLAIN {
|
||||
if !u.ValidatePassword(passwd) {
|
||||
return nil, ErrUserNotExist{u.Id, u.Name}
|
||||
}
|
||||
return u, nil
|
||||
}
|
||||
|
||||
if !has {
|
||||
var sources []LoginSource
|
||||
if err = x.UseBool().Find(&sources,
|
||||
&LoginSource{IsActived: true, AllowAutoRegister: true}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, source := range sources {
|
||||
if source.Type == LDAP {
|
||||
u, err := LoginUserLdapSource(nil, uname, passwd,
|
||||
source.ID, source.Cfg.(*LDAPConfig), true)
|
||||
if err == nil {
|
||||
return u, nil
|
||||
}
|
||||
log.Warn("Fail to login(%s) by LDAP(%s): %v", uname, source.Name, err)
|
||||
} else if source.Type == SMTP {
|
||||
u, err := LoginUserSMTPSource(nil, uname, passwd,
|
||||
source.ID, source.Cfg.(*SMTPConfig), true)
|
||||
if err == nil {
|
||||
return u, nil
|
||||
}
|
||||
log.Warn("Fail to login(%s) by SMTP(%s): %v", uname, source.Name, err)
|
||||
} else if source.Type == PAM {
|
||||
u, err := LoginUserPAMSource(nil, uname, passwd,
|
||||
source.ID, source.Cfg.(*PAMConfig), true)
|
||||
if err == nil {
|
||||
return u, nil
|
||||
}
|
||||
log.Warn("Fail to login(%s) by PAM(%s): %v", uname, source.Name, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil, ErrUserNotExist{u.Id, u.Name}
|
||||
}
|
||||
|
||||
var source LoginSource
|
||||
hasSource, err := x.Id(u.LoginSource).Get(&source)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !hasSource {
|
||||
return nil, ErrLoginSourceNotExist
|
||||
} else if !source.IsActived {
|
||||
return nil, ErrLoginSourceNotActived
|
||||
}
|
||||
|
||||
switch u.LoginType {
|
||||
case LDAP:
|
||||
return LoginUserLdapSource(u, u.LoginName, passwd, source.ID, source.Cfg.(*LDAPConfig), false)
|
||||
case SMTP:
|
||||
return LoginUserSMTPSource(u, u.LoginName, passwd, source.ID, source.Cfg.(*SMTPConfig), false)
|
||||
case PAM:
|
||||
return LoginUserPAMSource(u, u.LoginName, passwd, source.ID, source.Cfg.(*PAMConfig), false)
|
||||
}
|
||||
return nil, ErrUnsupportedLoginType
|
||||
}
|
||||
|
||||
// Query if name/passwd can login against the LDAP directory pool
|
||||
// Create a local user if success
|
||||
// Return the same LoginUserPlain semantic
|
||||
// FIXME: https://github.com/gogits/gogs/issues/672
|
||||
func LoginUserLdapSource(u *User, name, passwd string, sourceId int64, cfg *LDAPConfig, autoRegister bool) (*User, error) {
|
||||
fn, sn, mail, admin, logged := cfg.Ldapsource.SearchEntry(name, passwd)
|
||||
if !logged {
|
||||
// User not in LDAP, do nothing
|
||||
return nil, ErrUserNotExist{0, name}
|
||||
}
|
||||
|
||||
if !autoRegister {
|
||||
return u, nil
|
||||
}
|
||||
|
||||
// Fallback.
|
||||
if len(mail) == 0 {
|
||||
mail = fmt.Sprintf("%s@localhost", name)
|
||||
}
|
||||
|
||||
u = &User{
|
||||
LowerName: strings.ToLower(name),
|
||||
Name: name,
|
||||
FullName: fn + " " + sn,
|
||||
LoginType: LDAP,
|
||||
LoginSource: sourceId,
|
||||
LoginName: name,
|
||||
Passwd: passwd,
|
||||
Email: mail,
|
||||
IsAdmin: admin,
|
||||
IsActive: true,
|
||||
}
|
||||
return u, CreateUser(u)
|
||||
}
|
||||
|
||||
type loginAuth struct {
|
||||
username, password string
|
||||
}
|
||||
|
||||
func LoginAuth(username, password string) smtp.Auth {
|
||||
return &loginAuth{username, password}
|
||||
}
|
||||
|
||||
func (a *loginAuth) Start(server *smtp.ServerInfo) (string, []byte, error) {
|
||||
return "LOGIN", []byte(a.username), nil
|
||||
}
|
||||
|
||||
func (a *loginAuth) Next(fromServer []byte, more bool) ([]byte, error) {
|
||||
if more {
|
||||
switch string(fromServer) {
|
||||
case "Username:":
|
||||
return []byte(a.username), nil
|
||||
case "Password:":
|
||||
return []byte(a.password), nil
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
const (
|
||||
SMTP_PLAIN = "PLAIN"
|
||||
SMTP_LOGIN = "LOGIN"
|
||||
)
|
||||
|
||||
var (
|
||||
SMTPAuths = []string{SMTP_PLAIN, SMTP_LOGIN}
|
||||
)
|
||||
|
||||
func SMTPAuth(a smtp.Auth, cfg *SMTPConfig) error {
|
||||
c, err := smtp.Dial(fmt.Sprintf("%s:%d", cfg.Host, cfg.Port))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer c.Close()
|
||||
|
||||
if err = c.Hello("gogs"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if cfg.TLS {
|
||||
if ok, _ := c.Extension("STARTTLS"); ok {
|
||||
if err = c.StartTLS(&tls.Config{
|
||||
InsecureSkipVerify: cfg.SkipVerify,
|
||||
ServerName: cfg.Host,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
return errors.New("SMTP server unsupports TLS")
|
||||
}
|
||||
}
|
||||
|
||||
if ok, _ := c.Extension("AUTH"); ok {
|
||||
if err = c.Auth(a); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return ErrUnsupportedLoginType
|
||||
}
|
||||
|
||||
// Query if name/passwd can login against the LDAP directory pool
|
||||
// Create a local user if success
|
||||
// Return the same LoginUserPlain semantic
|
||||
func LoginUserSMTPSource(u *User, name, passwd string, sourceId int64, cfg *SMTPConfig, autoRegister bool) (*User, error) {
|
||||
var auth smtp.Auth
|
||||
if cfg.Auth == SMTP_PLAIN {
|
||||
auth = smtp.PlainAuth("", name, passwd, cfg.Host)
|
||||
} else if cfg.Auth == SMTP_LOGIN {
|
||||
auth = LoginAuth(name, passwd)
|
||||
} else {
|
||||
return nil, errors.New("Unsupported SMTP auth type")
|
||||
}
|
||||
|
||||
if err := SMTPAuth(auth, cfg); err != nil {
|
||||
if strings.Contains(err.Error(), "Username and Password not accepted") {
|
||||
return nil, ErrUserNotExist{u.Id, u.Name}
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !autoRegister {
|
||||
return u, nil
|
||||
}
|
||||
|
||||
var loginName = name
|
||||
idx := strings.Index(name, "@")
|
||||
if idx > -1 {
|
||||
loginName = name[:idx]
|
||||
}
|
||||
// fake a local user creation
|
||||
u = &User{
|
||||
LowerName: strings.ToLower(loginName),
|
||||
Name: strings.ToLower(loginName),
|
||||
LoginType: SMTP,
|
||||
LoginSource: sourceId,
|
||||
LoginName: name,
|
||||
IsActive: true,
|
||||
Passwd: passwd,
|
||||
Email: name,
|
||||
}
|
||||
err := CreateUser(u)
|
||||
return u, err
|
||||
}
|
||||
|
||||
// Query if name/passwd can login against PAM
|
||||
// Create a local user if success
|
||||
// Return the same LoginUserPlain semantic
|
||||
func LoginUserPAMSource(u *User, name, passwd string, sourceId int64, cfg *PAMConfig, autoRegister bool) (*User, error) {
|
||||
if err := pam.PAMAuth(cfg.ServiceName, name, passwd); err != nil {
|
||||
if strings.Contains(err.Error(), "Authentication failure") {
|
||||
return nil, ErrUserNotExist{u.Id, u.Name}
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !autoRegister {
|
||||
return u, nil
|
||||
}
|
||||
|
||||
// fake a local user creation
|
||||
u = &User{
|
||||
LowerName: strings.ToLower(name),
|
||||
Name: strings.ToLower(name),
|
||||
LoginType: PAM,
|
||||
LoginSource: sourceId,
|
||||
LoginName: name,
|
||||
IsActive: true,
|
||||
Passwd: passwd,
|
||||
Email: name,
|
||||
}
|
||||
err := CreateUser(u)
|
||||
return u, err
|
||||
}
|
||||
558
models/login_source.go
Normal file
558
models/login_source.go
Normal file
@@ -0,0 +1,558 @@
|
||||
// Copyright 2014 The Gogs Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package models
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/smtp"
|
||||
"net/textproto"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Unknwon/com"
|
||||
"github.com/go-macaron/binding"
|
||||
"github.com/go-xorm/core"
|
||||
"github.com/go-xorm/xorm"
|
||||
log "gopkg.in/clog.v1"
|
||||
|
||||
"github.com/gogits/gogs/modules/auth/ldap"
|
||||
"github.com/gogits/gogs/modules/auth/pam"
|
||||
)
|
||||
|
||||
type LoginType int
|
||||
|
||||
// Note: new type must append to the end of list to maintain compatibility.
|
||||
const (
|
||||
LOGIN_NOTYPE LoginType = iota
|
||||
LOGIN_PLAIN // 1
|
||||
LOGIN_LDAP // 2
|
||||
LOGIN_SMTP // 3
|
||||
LOGIN_PAM // 4
|
||||
LOGIN_DLDAP // 5
|
||||
)
|
||||
|
||||
var LoginNames = map[LoginType]string{
|
||||
LOGIN_LDAP: "LDAP (via BindDN)",
|
||||
LOGIN_DLDAP: "LDAP (simple auth)", // Via direct bind
|
||||
LOGIN_SMTP: "SMTP",
|
||||
LOGIN_PAM: "PAM",
|
||||
}
|
||||
|
||||
var SecurityProtocolNames = map[ldap.SecurityProtocol]string{
|
||||
ldap.SECURITY_PROTOCOL_UNENCRYPTED: "Unencrypted",
|
||||
ldap.SECURITY_PROTOCOL_LDAPS: "LDAPS",
|
||||
ldap.SECURITY_PROTOCOL_START_TLS: "StartTLS",
|
||||
}
|
||||
|
||||
// Ensure structs implemented interface.
|
||||
var (
|
||||
_ core.Conversion = &LDAPConfig{}
|
||||
_ core.Conversion = &SMTPConfig{}
|
||||
_ core.Conversion = &PAMConfig{}
|
||||
)
|
||||
|
||||
type LDAPConfig struct {
|
||||
*ldap.Source
|
||||
}
|
||||
|
||||
func (cfg *LDAPConfig) FromDB(bs []byte) error {
|
||||
return json.Unmarshal(bs, &cfg)
|
||||
}
|
||||
|
||||
func (cfg *LDAPConfig) ToDB() ([]byte, error) {
|
||||
return json.Marshal(cfg)
|
||||
}
|
||||
|
||||
func (cfg *LDAPConfig) SecurityProtocolName() string {
|
||||
return SecurityProtocolNames[cfg.SecurityProtocol]
|
||||
}
|
||||
|
||||
type SMTPConfig struct {
|
||||
Auth string
|
||||
Host string
|
||||
Port int
|
||||
AllowedDomains string `xorm:"TEXT"`
|
||||
TLS bool
|
||||
SkipVerify bool
|
||||
}
|
||||
|
||||
func (cfg *SMTPConfig) FromDB(bs []byte) error {
|
||||
return json.Unmarshal(bs, cfg)
|
||||
}
|
||||
|
||||
func (cfg *SMTPConfig) ToDB() ([]byte, error) {
|
||||
return json.Marshal(cfg)
|
||||
}
|
||||
|
||||
type PAMConfig struct {
|
||||
ServiceName string // pam service (e.g. system-auth)
|
||||
}
|
||||
|
||||
func (cfg *PAMConfig) FromDB(bs []byte) error {
|
||||
return json.Unmarshal(bs, &cfg)
|
||||
}
|
||||
|
||||
func (cfg *PAMConfig) ToDB() ([]byte, error) {
|
||||
return json.Marshal(cfg)
|
||||
}
|
||||
|
||||
// LoginSource represents an external way for authorizing users.
|
||||
type LoginSource struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
Type LoginType
|
||||
Name string `xorm:"UNIQUE"`
|
||||
IsActived bool `xorm:"NOT NULL DEFAULT false"`
|
||||
Cfg core.Conversion `xorm:"TEXT"`
|
||||
|
||||
Created time.Time `xorm:"-"`
|
||||
CreatedUnix int64
|
||||
Updated time.Time `xorm:"-"`
|
||||
UpdatedUnix int64
|
||||
}
|
||||
|
||||
func (s *LoginSource) BeforeInsert() {
|
||||
s.CreatedUnix = time.Now().Unix()
|
||||
s.UpdatedUnix = s.CreatedUnix
|
||||
}
|
||||
|
||||
func (s *LoginSource) BeforeUpdate() {
|
||||
s.UpdatedUnix = time.Now().Unix()
|
||||
}
|
||||
|
||||
// Cell2Int64 converts a xorm.Cell type to int64,
|
||||
// and handles possible irregular cases.
|
||||
func Cell2Int64(val xorm.Cell) int64 {
|
||||
switch (*val).(type) {
|
||||
case []uint8:
|
||||
log.Trace("Cell2Int64 ([]uint8): %v", *val)
|
||||
return com.StrTo(string((*val).([]uint8))).MustInt64()
|
||||
}
|
||||
return (*val).(int64)
|
||||
}
|
||||
|
||||
func (source *LoginSource) BeforeSet(colName string, val xorm.Cell) {
|
||||
switch colName {
|
||||
case "type":
|
||||
switch LoginType(Cell2Int64(val)) {
|
||||
case LOGIN_LDAP, LOGIN_DLDAP:
|
||||
source.Cfg = new(LDAPConfig)
|
||||
case LOGIN_SMTP:
|
||||
source.Cfg = new(SMTPConfig)
|
||||
case LOGIN_PAM:
|
||||
source.Cfg = new(PAMConfig)
|
||||
default:
|
||||
panic("unrecognized login source type: " + com.ToStr(*val))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *LoginSource) AfterSet(colName string, _ xorm.Cell) {
|
||||
switch colName {
|
||||
case "created_unix":
|
||||
s.Created = time.Unix(s.CreatedUnix, 0).Local()
|
||||
case "updated_unix":
|
||||
s.Updated = time.Unix(s.UpdatedUnix, 0).Local()
|
||||
}
|
||||
}
|
||||
|
||||
func (source *LoginSource) TypeName() string {
|
||||
return LoginNames[source.Type]
|
||||
}
|
||||
|
||||
func (source *LoginSource) IsLDAP() bool {
|
||||
return source.Type == LOGIN_LDAP
|
||||
}
|
||||
|
||||
func (source *LoginSource) IsDLDAP() bool {
|
||||
return source.Type == LOGIN_DLDAP
|
||||
}
|
||||
|
||||
func (source *LoginSource) IsSMTP() bool {
|
||||
return source.Type == LOGIN_SMTP
|
||||
}
|
||||
|
||||
func (source *LoginSource) IsPAM() bool {
|
||||
return source.Type == LOGIN_PAM
|
||||
}
|
||||
|
||||
func (source *LoginSource) HasTLS() bool {
|
||||
return ((source.IsLDAP() || source.IsDLDAP()) &&
|
||||
source.LDAP().SecurityProtocol > ldap.SECURITY_PROTOCOL_UNENCRYPTED) ||
|
||||
source.IsSMTP()
|
||||
}
|
||||
|
||||
func (source *LoginSource) UseTLS() bool {
|
||||
switch source.Type {
|
||||
case LOGIN_LDAP, LOGIN_DLDAP:
|
||||
return source.LDAP().SecurityProtocol != ldap.SECURITY_PROTOCOL_UNENCRYPTED
|
||||
case LOGIN_SMTP:
|
||||
return source.SMTP().TLS
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (source *LoginSource) SkipVerify() bool {
|
||||
switch source.Type {
|
||||
case LOGIN_LDAP, LOGIN_DLDAP:
|
||||
return source.LDAP().SkipVerify
|
||||
case LOGIN_SMTP:
|
||||
return source.SMTP().SkipVerify
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (source *LoginSource) LDAP() *LDAPConfig {
|
||||
return source.Cfg.(*LDAPConfig)
|
||||
}
|
||||
|
||||
func (source *LoginSource) SMTP() *SMTPConfig {
|
||||
return source.Cfg.(*SMTPConfig)
|
||||
}
|
||||
|
||||
func (source *LoginSource) PAM() *PAMConfig {
|
||||
return source.Cfg.(*PAMConfig)
|
||||
}
|
||||
func CreateLoginSource(source *LoginSource) error {
|
||||
has, err := x.Get(&LoginSource{Name: source.Name})
|
||||
if err != nil {
|
||||
return err
|
||||
} else if has {
|
||||
return ErrLoginSourceAlreadyExist{source.Name}
|
||||
}
|
||||
|
||||
_, err = x.Insert(source)
|
||||
return err
|
||||
}
|
||||
|
||||
func LoginSources() ([]*LoginSource, error) {
|
||||
auths := make([]*LoginSource, 0, 5)
|
||||
return auths, x.Find(&auths)
|
||||
}
|
||||
|
||||
// GetLoginSourceByID returns login source by given ID.
|
||||
func GetLoginSourceByID(id int64) (*LoginSource, error) {
|
||||
source := new(LoginSource)
|
||||
has, err := x.Id(id).Get(source)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !has {
|
||||
return nil, ErrLoginSourceNotExist{id}
|
||||
}
|
||||
return source, nil
|
||||
}
|
||||
|
||||
func UpdateSource(source *LoginSource) error {
|
||||
_, err := x.Id(source.ID).AllCols().Update(source)
|
||||
return err
|
||||
}
|
||||
|
||||
func DeleteSource(source *LoginSource) error {
|
||||
count, err := x.Count(&User{LoginSource: source.ID})
|
||||
if err != nil {
|
||||
return err
|
||||
} else if count > 0 {
|
||||
return ErrLoginSourceInUse{source.ID}
|
||||
}
|
||||
_, err = x.Id(source.ID).Delete(new(LoginSource))
|
||||
return err
|
||||
}
|
||||
|
||||
// CountLoginSources returns number of login sources.
|
||||
func CountLoginSources() int64 {
|
||||
count, _ := x.Count(new(LoginSource))
|
||||
return count
|
||||
}
|
||||
|
||||
// .____ ________ _____ __________
|
||||
// | | \______ \ / _ \\______ \
|
||||
// | | | | \ / /_\ \| ___/
|
||||
// | |___ | ` \/ | \ |
|
||||
// |_______ \/_______ /\____|__ /____|
|
||||
// \/ \/ \/
|
||||
|
||||
func composeFullName(firstname, surname, username string) string {
|
||||
switch {
|
||||
case len(firstname) == 0 && len(surname) == 0:
|
||||
return username
|
||||
case len(firstname) == 0:
|
||||
return surname
|
||||
case len(surname) == 0:
|
||||
return firstname
|
||||
default:
|
||||
return firstname + " " + surname
|
||||
}
|
||||
}
|
||||
|
||||
// LoginViaLDAP queries if login/password is valid against the LDAP directory pool,
|
||||
// and create a local user if success when enabled.
|
||||
func LoginViaLDAP(user *User, login, password string, source *LoginSource, autoRegister bool) (*User, error) {
|
||||
username, fn, sn, mail, isAdmin, succeed := source.Cfg.(*LDAPConfig).SearchEntry(login, password, source.Type == LOGIN_DLDAP)
|
||||
if !succeed {
|
||||
// User not in LDAP, do nothing
|
||||
return nil, ErrUserNotExist{0, login}
|
||||
}
|
||||
|
||||
if !autoRegister {
|
||||
return user, nil
|
||||
}
|
||||
|
||||
// Fallback.
|
||||
if len(username) == 0 {
|
||||
username = login
|
||||
}
|
||||
// Validate username make sure it satisfies requirement.
|
||||
if binding.AlphaDashDotPattern.MatchString(username) {
|
||||
return nil, fmt.Errorf("Invalid pattern for attribute 'username' [%s]: must be valid alpha or numeric or dash(-_) or dot characters", username)
|
||||
}
|
||||
|
||||
if len(mail) == 0 {
|
||||
mail = fmt.Sprintf("%s@localhost", username)
|
||||
}
|
||||
|
||||
user = &User{
|
||||
LowerName: strings.ToLower(username),
|
||||
Name: username,
|
||||
FullName: composeFullName(fn, sn, username),
|
||||
Email: mail,
|
||||
LoginType: source.Type,
|
||||
LoginSource: source.ID,
|
||||
LoginName: login,
|
||||
IsActive: true,
|
||||
IsAdmin: isAdmin,
|
||||
}
|
||||
return user, CreateUser(user)
|
||||
}
|
||||
|
||||
// _________ __________________________
|
||||
// / _____/ / \__ ___/\______ \
|
||||
// \_____ \ / \ / \| | | ___/
|
||||
// / \/ Y \ | | |
|
||||
// /_______ /\____|__ /____| |____|
|
||||
// \/ \/
|
||||
|
||||
type smtpLoginAuth struct {
|
||||
username, password string
|
||||
}
|
||||
|
||||
func (auth *smtpLoginAuth) Start(server *smtp.ServerInfo) (string, []byte, error) {
|
||||
return "LOGIN", []byte(auth.username), nil
|
||||
}
|
||||
|
||||
func (auth *smtpLoginAuth) Next(fromServer []byte, more bool) ([]byte, error) {
|
||||
if more {
|
||||
switch string(fromServer) {
|
||||
case "Username:":
|
||||
return []byte(auth.username), nil
|
||||
case "Password:":
|
||||
return []byte(auth.password), nil
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
const (
|
||||
SMTP_PLAIN = "PLAIN"
|
||||
SMTP_LOGIN = "LOGIN"
|
||||
)
|
||||
|
||||
var SMTPAuths = []string{SMTP_PLAIN, SMTP_LOGIN}
|
||||
|
||||
func SMTPAuth(a smtp.Auth, cfg *SMTPConfig) error {
|
||||
c, err := smtp.Dial(fmt.Sprintf("%s:%d", cfg.Host, cfg.Port))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer c.Close()
|
||||
|
||||
if err = c.Hello("gogs"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if cfg.TLS {
|
||||
if ok, _ := c.Extension("STARTTLS"); ok {
|
||||
if err = c.StartTLS(&tls.Config{
|
||||
InsecureSkipVerify: cfg.SkipVerify,
|
||||
ServerName: cfg.Host,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
return errors.New("SMTP server unsupports TLS")
|
||||
}
|
||||
}
|
||||
|
||||
if ok, _ := c.Extension("AUTH"); ok {
|
||||
if err = c.Auth(a); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return ErrUnsupportedLoginType
|
||||
}
|
||||
|
||||
// LoginViaSMTP queries if login/password is valid against the SMTP,
|
||||
// and create a local user if success when enabled.
|
||||
func LoginViaSMTP(user *User, login, password string, sourceID int64, cfg *SMTPConfig, autoRegister bool) (*User, error) {
|
||||
// Verify allowed domains.
|
||||
if len(cfg.AllowedDomains) > 0 {
|
||||
idx := strings.Index(login, "@")
|
||||
if idx == -1 {
|
||||
return nil, ErrUserNotExist{0, login}
|
||||
} else if !com.IsSliceContainsStr(strings.Split(cfg.AllowedDomains, ","), login[idx+1:]) {
|
||||
return nil, ErrUserNotExist{0, login}
|
||||
}
|
||||
}
|
||||
|
||||
var auth smtp.Auth
|
||||
if cfg.Auth == SMTP_PLAIN {
|
||||
auth = smtp.PlainAuth("", login, password, cfg.Host)
|
||||
} else if cfg.Auth == SMTP_LOGIN {
|
||||
auth = &smtpLoginAuth{login, password}
|
||||
} else {
|
||||
return nil, errors.New("Unsupported SMTP auth type")
|
||||
}
|
||||
|
||||
if err := SMTPAuth(auth, cfg); err != nil {
|
||||
// Check standard error format first,
|
||||
// then fallback to worse case.
|
||||
tperr, ok := err.(*textproto.Error)
|
||||
if (ok && tperr.Code == 535) ||
|
||||
strings.Contains(err.Error(), "Username and Password not accepted") {
|
||||
return nil, ErrUserNotExist{0, login}
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !autoRegister {
|
||||
return user, nil
|
||||
}
|
||||
|
||||
username := login
|
||||
idx := strings.Index(login, "@")
|
||||
if idx > -1 {
|
||||
username = login[:idx]
|
||||
}
|
||||
|
||||
user = &User{
|
||||
LowerName: strings.ToLower(username),
|
||||
Name: strings.ToLower(username),
|
||||
Email: login,
|
||||
Passwd: password,
|
||||
LoginType: LOGIN_SMTP,
|
||||
LoginSource: sourceID,
|
||||
LoginName: login,
|
||||
IsActive: true,
|
||||
}
|
||||
return user, CreateUser(user)
|
||||
}
|
||||
|
||||
// __________ _____ _____
|
||||
// \______ \/ _ \ / \
|
||||
// | ___/ /_\ \ / \ / \
|
||||
// | | / | \/ Y \
|
||||
// |____| \____|__ /\____|__ /
|
||||
// \/ \/
|
||||
|
||||
// LoginViaPAM queries if login/password is valid against the PAM,
|
||||
// and create a local user if success when enabled.
|
||||
func LoginViaPAM(user *User, login, password string, sourceID int64, cfg *PAMConfig, autoRegister bool) (*User, error) {
|
||||
if err := pam.PAMAuth(cfg.ServiceName, login, password); err != nil {
|
||||
if strings.Contains(err.Error(), "Authentication failure") {
|
||||
return nil, ErrUserNotExist{0, login}
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !autoRegister {
|
||||
return user, nil
|
||||
}
|
||||
|
||||
user = &User{
|
||||
LowerName: strings.ToLower(login),
|
||||
Name: login,
|
||||
Email: login,
|
||||
Passwd: password,
|
||||
LoginType: LOGIN_PAM,
|
||||
LoginSource: sourceID,
|
||||
LoginName: login,
|
||||
IsActive: true,
|
||||
}
|
||||
return user, CreateUser(user)
|
||||
}
|
||||
|
||||
func ExternalUserLogin(user *User, login, password string, source *LoginSource, autoRegister bool) (*User, error) {
|
||||
if !source.IsActived {
|
||||
return nil, ErrLoginSourceNotActived
|
||||
}
|
||||
|
||||
switch source.Type {
|
||||
case LOGIN_LDAP, LOGIN_DLDAP:
|
||||
return LoginViaLDAP(user, login, password, source, autoRegister)
|
||||
case LOGIN_SMTP:
|
||||
return LoginViaSMTP(user, login, password, source.ID, source.Cfg.(*SMTPConfig), autoRegister)
|
||||
case LOGIN_PAM:
|
||||
return LoginViaPAM(user, login, password, source.ID, source.Cfg.(*PAMConfig), autoRegister)
|
||||
}
|
||||
|
||||
return nil, ErrUnsupportedLoginType
|
||||
}
|
||||
|
||||
// UserSignIn validates user name and password.
|
||||
func UserSignIn(username, password string) (*User, error) {
|
||||
var user *User
|
||||
if strings.Contains(username, "@") {
|
||||
user = &User{Email: strings.ToLower(username)}
|
||||
} else {
|
||||
user = &User{LowerName: strings.ToLower(username)}
|
||||
}
|
||||
|
||||
hasUser, err := x.Get(user)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if hasUser {
|
||||
switch user.LoginType {
|
||||
case LOGIN_NOTYPE, LOGIN_PLAIN:
|
||||
if user.ValidatePassword(password) {
|
||||
return user, nil
|
||||
}
|
||||
|
||||
return nil, ErrUserNotExist{user.ID, user.Name}
|
||||
|
||||
default:
|
||||
var source LoginSource
|
||||
hasSource, err := x.Id(user.LoginSource).Get(&source)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !hasSource {
|
||||
return nil, ErrLoginSourceNotExist{user.LoginSource}
|
||||
}
|
||||
|
||||
return ExternalUserLogin(user, user.LoginName, password, &source, false)
|
||||
}
|
||||
}
|
||||
|
||||
sources := make([]*LoginSource, 0, 3)
|
||||
if err = x.UseBool().Find(&sources, &LoginSource{IsActived: true}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, source := range sources {
|
||||
authUser, err := ExternalUserLogin(nil, username, password, source, true)
|
||||
if err == nil {
|
||||
return authUser, nil
|
||||
}
|
||||
|
||||
log.Warn("Failed to login '%s' via '%s': %v", username, source.Name, err)
|
||||
}
|
||||
|
||||
return nil, ErrUserNotExist{user.ID, user.Name}
|
||||
}
|
||||
@@ -5,25 +5,18 @@
|
||||
package migrations
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Unknwon/com"
|
||||
"github.com/go-xorm/xorm"
|
||||
"gopkg.in/ini.v1"
|
||||
log "gopkg.in/clog.v1"
|
||||
|
||||
"github.com/gogits/gogs/modules/log"
|
||||
"github.com/gogits/gogs/modules/setting"
|
||||
gouuid "github.com/gogits/gogs/modules/uuid"
|
||||
"github.com/gogits/gogs/modules/base"
|
||||
)
|
||||
|
||||
const _MIN_DB_VER = 0
|
||||
const _MIN_DB_VER = 10
|
||||
|
||||
type Migration interface {
|
||||
Description() string
|
||||
@@ -49,7 +42,7 @@ func (m *migration) Migrate(x *xorm.Engine) error {
|
||||
|
||||
// The version table. Should have only one row with id==1
|
||||
type Version struct {
|
||||
Id int64
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
Version int64
|
||||
}
|
||||
|
||||
@@ -57,14 +50,16 @@ type Version struct {
|
||||
// If you want to "retire" a migration, remove it from the top of the list and
|
||||
// update _MIN_VER_DB accordingly
|
||||
var migrations = []Migration{
|
||||
NewMigration("generate collaboration from access", accessToCollaboration), // V0 -> V1:v0.5.13
|
||||
NewMigration("make authorize 4 if team is owners", ownerTeamUpdate), // V1 -> V2:v0.5.13
|
||||
NewMigration("refactor access table to use id's", accessRefactor), // V2 -> V3:v0.5.13
|
||||
NewMigration("generate team-repo from team", teamToTeamRepo), // V3 -> V4:v0.5.13
|
||||
NewMigration("fix locale file load panic", fixLocaleFileLoadPanic), // V4 -> V5:v0.6.0
|
||||
NewMigration("trim action compare URL prefix", trimCommitActionAppUrlPrefix), // V5 -> V6:v0.6.3
|
||||
NewMigration("generate issue-label from issue", issueToIssueLabel), // V6 -> V7:v0.6.4
|
||||
NewMigration("refactor attachment table", attachmentRefactor), // V7 -> V8:v0.6.4
|
||||
// v0 -> v4 : before 0.6.0 -> last support 0.7.33
|
||||
// v4 -> v10: before 0.7.0 -> last support 0.9.141
|
||||
NewMigration("generate rands and salt for organizations", generateOrgRandsAndSalt), // V10 -> V11:v0.8.5
|
||||
NewMigration("convert date to unix timestamp", convertDateToUnix), // V11 -> V12:v0.9.2
|
||||
NewMigration("convert LDAP UseSSL option to SecurityProtocol", ldapUseSSLToSecurityProtocol), // V12 -> V13:v0.9.37
|
||||
|
||||
// v13 -> v14:v0.9.87
|
||||
NewMigration("set comment updated with created", setCommentUpdatedWithCreated),
|
||||
// v14 -> v15:v0.9.147
|
||||
NewMigration("generate and migrate Git hooks", generateAndMigrateGitHooks),
|
||||
}
|
||||
|
||||
// Migrate database to current version
|
||||
@@ -73,29 +68,15 @@ func Migrate(x *xorm.Engine) error {
|
||||
return fmt.Errorf("sync: %v", err)
|
||||
}
|
||||
|
||||
currentVersion := &Version{Id: 1}
|
||||
currentVersion := &Version{ID: 1}
|
||||
has, err := x.Get(currentVersion)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get: %v", err)
|
||||
} else if !has {
|
||||
// If the user table does not exist it is a fresh installation and we
|
||||
// can skip all migrations.
|
||||
needsMigration, err := x.IsTableExist("user")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if needsMigration {
|
||||
isEmpty, err := x.IsTableEmpty("user")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// If the user table is empty it is a fresh installation and we can
|
||||
// skip all migrations.
|
||||
needsMigration = !isEmpty
|
||||
}
|
||||
if !needsMigration {
|
||||
currentVersion.Version = int64(_MIN_DB_VER + len(migrations))
|
||||
}
|
||||
// If the version record does not exist we think
|
||||
// it is a fresh installation and we can skip all migrations.
|
||||
currentVersion.ID = 0
|
||||
currentVersion.Version = int64(_MIN_DB_VER + len(migrations))
|
||||
|
||||
if _, err = x.InsertOne(currentVersion); err != nil {
|
||||
return fmt.Errorf("insert: %v", err)
|
||||
@@ -103,6 +84,32 @@ func Migrate(x *xorm.Engine) error {
|
||||
}
|
||||
|
||||
v := currentVersion.Version
|
||||
if _MIN_DB_VER > v {
|
||||
log.Fatal(0, `
|
||||
Hi there, thank you for using Gogs for so long!
|
||||
However, Gogs has stopped supporting auto-migration from your previously installed version.
|
||||
But the good news is, it's very easy to fix this problem!
|
||||
You can migrate your older database using a previous release, then you can upgrade to the newest version.
|
||||
|
||||
Please save following instructions to somewhere and start working:
|
||||
|
||||
- If you were using below 0.6.0 (e.g. 0.5.x), download last supported archive from following link:
|
||||
https://github.com/gogits/gogs/releases/tag/v0.7.33
|
||||
- If you were using below 0.7.0 (e.g. 0.6.x), download last supported archive from following link:
|
||||
https://github.com/gogits/gogs/releases/tag/v0.9.141
|
||||
|
||||
Once finished downloading,
|
||||
|
||||
1. Extract the archive and to upgrade steps as usual.
|
||||
2. Run it once. To verify, you should see some migration traces.
|
||||
3. Once it starts web server successfully, stop it.
|
||||
4. Now it's time to put back the release archive you originally intent to upgrade.
|
||||
5. Enjoy!
|
||||
|
||||
In case you're stilling getting this notice, go through instructions again until it disappears.`)
|
||||
return nil
|
||||
}
|
||||
|
||||
if int(v-_MIN_DB_VER) > len(migrations) {
|
||||
// User downgraded Gogs.
|
||||
currentVersion.Version = int64(len(migrations) + _MIN_DB_VER)
|
||||
@@ -129,24 +136,16 @@ func sessionRelease(sess *xorm.Session) {
|
||||
sess.Close()
|
||||
}
|
||||
|
||||
func accessToCollaboration(x *xorm.Engine) (err error) {
|
||||
type Collaboration struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
RepoID int64 `xorm:"UNIQUE(s) INDEX NOT NULL"`
|
||||
UserID int64 `xorm:"UNIQUE(s) INDEX NOT NULL"`
|
||||
Created time.Time
|
||||
func generateOrgRandsAndSalt(x *xorm.Engine) (err error) {
|
||||
type User struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
Rands string `xorm:"VARCHAR(10)"`
|
||||
Salt string `xorm:"VARCHAR(10)"`
|
||||
}
|
||||
|
||||
if err = x.Sync(new(Collaboration)); err != nil {
|
||||
return fmt.Errorf("sync: %v", err)
|
||||
}
|
||||
|
||||
results, err := x.Query("SELECT u.id AS `uid`, a.repo_name AS `repo`, a.mode AS `mode`, a.created as `created` FROM `access` a JOIN `user` u ON a.user_name=u.lower_name")
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "no such column") {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
orgs := make([]*User, 0, 10)
|
||||
if err = x.Where("type=1").And("rands=''").Find(&orgs); err != nil {
|
||||
return fmt.Errorf("select all organizations: %v", err)
|
||||
}
|
||||
|
||||
sess := x.NewSession()
|
||||
@@ -155,78 +154,14 @@ func accessToCollaboration(x *xorm.Engine) (err error) {
|
||||
return err
|
||||
}
|
||||
|
||||
offset := strings.Split(time.Now().String(), " ")[2]
|
||||
for _, result := range results {
|
||||
mode := com.StrTo(result["mode"]).MustInt64()
|
||||
// Collaborators must have write access.
|
||||
if mode < 2 {
|
||||
continue
|
||||
}
|
||||
|
||||
userID := com.StrTo(result["uid"]).MustInt64()
|
||||
repoRefName := string(result["repo"])
|
||||
|
||||
var created time.Time
|
||||
switch {
|
||||
case setting.UseSQLite3:
|
||||
created, _ = time.Parse(time.RFC3339, string(result["created"]))
|
||||
case setting.UseMySQL:
|
||||
created, _ = time.Parse("2006-01-02 15:04:05-0700", string(result["created"])+offset)
|
||||
case setting.UsePostgreSQL:
|
||||
created, _ = time.Parse("2006-01-02T15:04:05Z-0700", string(result["created"])+offset)
|
||||
}
|
||||
|
||||
// find owner of repository
|
||||
parts := strings.SplitN(repoRefName, "/", 2)
|
||||
ownerName := parts[0]
|
||||
repoName := parts[1]
|
||||
|
||||
results, err := sess.Query("SELECT u.id as `uid`, ou.uid as `memberid` FROM `user` u LEFT JOIN org_user ou ON ou.org_id=u.id WHERE u.lower_name=?", ownerName)
|
||||
if err != nil {
|
||||
for _, org := range orgs {
|
||||
if org.Rands, err = base.GetRandomString(10); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(results) < 1 {
|
||||
continue
|
||||
}
|
||||
|
||||
ownerID := com.StrTo(results[0]["uid"]).MustInt64()
|
||||
if ownerID == userID {
|
||||
continue
|
||||
}
|
||||
|
||||
// test if user is member of owning organization
|
||||
isMember := false
|
||||
for _, member := range results {
|
||||
memberID := com.StrTo(member["memberid"]).MustInt64()
|
||||
// We can skip all cases that a user is member of the owning organization
|
||||
if memberID == userID {
|
||||
isMember = true
|
||||
}
|
||||
}
|
||||
if isMember {
|
||||
continue
|
||||
}
|
||||
|
||||
results, err = sess.Query("SELECT id FROM `repository` WHERE owner_id=? AND lower_name=?", ownerID, repoName)
|
||||
if err != nil {
|
||||
if org.Salt, err = base.GetRandomString(10); err != nil {
|
||||
return err
|
||||
} else if len(results) < 1 {
|
||||
continue
|
||||
}
|
||||
|
||||
collaboration := &Collaboration{
|
||||
UserID: userID,
|
||||
RepoID: com.StrTo(results[0]["id"]).MustInt64(),
|
||||
}
|
||||
has, err := sess.Get(collaboration)
|
||||
if err != nil {
|
||||
return err
|
||||
} else if has {
|
||||
continue
|
||||
}
|
||||
|
||||
collaboration.Created = created
|
||||
if _, err = sess.InsertOne(collaboration); err != nil {
|
||||
if _, err = sess.Id(org.ID).Update(org); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -234,375 +169,221 @@ func accessToCollaboration(x *xorm.Engine) (err error) {
|
||||
return sess.Commit()
|
||||
}
|
||||
|
||||
func ownerTeamUpdate(x *xorm.Engine) (err error) {
|
||||
if _, err := x.Exec("UPDATE `team` SET authorize=4 WHERE lower_name=?", "owners"); err != nil {
|
||||
return fmt.Errorf("update owner team table: %v", err)
|
||||
type TAction struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
CreatedUnix int64
|
||||
}
|
||||
|
||||
func (t *TAction) TableName() string { return "action" }
|
||||
|
||||
type TNotice struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
CreatedUnix int64
|
||||
}
|
||||
|
||||
func (t *TNotice) TableName() string { return "notice" }
|
||||
|
||||
type TComment struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
CreatedUnix int64
|
||||
}
|
||||
|
||||
func (t *TComment) TableName() string { return "comment" }
|
||||
|
||||
type TIssue struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
DeadlineUnix int64
|
||||
CreatedUnix int64
|
||||
UpdatedUnix int64
|
||||
}
|
||||
|
||||
func (t *TIssue) TableName() string { return "issue" }
|
||||
|
||||
type TMilestone struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
DeadlineUnix int64
|
||||
ClosedDateUnix int64
|
||||
}
|
||||
|
||||
func (t *TMilestone) TableName() string { return "milestone" }
|
||||
|
||||
type TAttachment struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
CreatedUnix int64
|
||||
}
|
||||
|
||||
func (t *TAttachment) TableName() string { return "attachment" }
|
||||
|
||||
type TLoginSource struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
CreatedUnix int64
|
||||
UpdatedUnix int64
|
||||
}
|
||||
|
||||
func (t *TLoginSource) TableName() string { return "login_source" }
|
||||
|
||||
type TPull struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
MergedUnix int64
|
||||
}
|
||||
|
||||
func (t *TPull) TableName() string { return "pull_request" }
|
||||
|
||||
type TRelease struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
CreatedUnix int64
|
||||
}
|
||||
|
||||
func (t *TRelease) TableName() string { return "release" }
|
||||
|
||||
type TRepo struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
CreatedUnix int64
|
||||
UpdatedUnix int64
|
||||
}
|
||||
|
||||
func (t *TRepo) TableName() string { return "repository" }
|
||||
|
||||
type TMirror struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
UpdatedUnix int64
|
||||
NextUpdateUnix int64
|
||||
}
|
||||
|
||||
func (t *TMirror) TableName() string { return "mirror" }
|
||||
|
||||
type TPublicKey struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
CreatedUnix int64
|
||||
UpdatedUnix int64
|
||||
}
|
||||
|
||||
func (t *TPublicKey) TableName() string { return "public_key" }
|
||||
|
||||
type TDeployKey struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
CreatedUnix int64
|
||||
UpdatedUnix int64
|
||||
}
|
||||
|
||||
func (t *TDeployKey) TableName() string { return "deploy_key" }
|
||||
|
||||
type TAccessToken struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
CreatedUnix int64
|
||||
UpdatedUnix int64
|
||||
}
|
||||
|
||||
func (t *TAccessToken) TableName() string { return "access_token" }
|
||||
|
||||
type TUser struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
CreatedUnix int64
|
||||
UpdatedUnix int64
|
||||
}
|
||||
|
||||
func (t *TUser) TableName() string { return "user" }
|
||||
|
||||
type TWebhook struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
CreatedUnix int64
|
||||
UpdatedUnix int64
|
||||
}
|
||||
|
||||
func (t *TWebhook) TableName() string { return "webhook" }
|
||||
|
||||
func convertDateToUnix(x *xorm.Engine) (err error) {
|
||||
log.Info("This migration could take up to minutes, please be patient.")
|
||||
type Bean struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
Created time.Time
|
||||
Updated time.Time
|
||||
Merged time.Time
|
||||
Deadline time.Time
|
||||
ClosedDate time.Time
|
||||
NextUpdate time.Time
|
||||
}
|
||||
|
||||
var tables = []struct {
|
||||
name string
|
||||
cols []string
|
||||
bean interface{}
|
||||
}{
|
||||
{"action", []string{"created"}, new(TAction)},
|
||||
{"notice", []string{"created"}, new(TNotice)},
|
||||
{"comment", []string{"created"}, new(TComment)},
|
||||
{"issue", []string{"deadline", "created", "updated"}, new(TIssue)},
|
||||
{"milestone", []string{"deadline", "closed_date"}, new(TMilestone)},
|
||||
{"attachment", []string{"created"}, new(TAttachment)},
|
||||
{"login_source", []string{"created", "updated"}, new(TLoginSource)},
|
||||
{"pull_request", []string{"merged"}, new(TPull)},
|
||||
{"release", []string{"created"}, new(TRelease)},
|
||||
{"repository", []string{"created", "updated"}, new(TRepo)},
|
||||
{"mirror", []string{"updated", "next_update"}, new(TMirror)},
|
||||
{"public_key", []string{"created", "updated"}, new(TPublicKey)},
|
||||
{"deploy_key", []string{"created", "updated"}, new(TDeployKey)},
|
||||
{"access_token", []string{"created", "updated"}, new(TAccessToken)},
|
||||
{"user", []string{"created", "updated"}, new(TUser)},
|
||||
{"webhook", []string{"created", "updated"}, new(TWebhook)},
|
||||
}
|
||||
|
||||
for _, table := range tables {
|
||||
log.Info("Converting table: %s", table.name)
|
||||
if err = x.Sync2(table.bean); err != nil {
|
||||
return fmt.Errorf("Sync [table: %s]: %v", table.name, err)
|
||||
}
|
||||
|
||||
offset := 0
|
||||
for {
|
||||
beans := make([]*Bean, 0, 100)
|
||||
if err = x.Sql(fmt.Sprintf("SELECT * FROM `%s` ORDER BY id ASC LIMIT 100 OFFSET %d",
|
||||
table.name, offset)).Find(&beans); err != nil {
|
||||
return fmt.Errorf("select beans [table: %s, offset: %d]: %v", table.name, offset, err)
|
||||
}
|
||||
log.Trace("Table [%s]: offset: %d, beans: %d", table.name, offset, len(beans))
|
||||
if len(beans) == 0 {
|
||||
break
|
||||
}
|
||||
offset += 100
|
||||
|
||||
baseSQL := "UPDATE `" + table.name + "` SET "
|
||||
for _, bean := range beans {
|
||||
valSQLs := make([]string, 0, len(table.cols))
|
||||
for _, col := range table.cols {
|
||||
fieldSQL := ""
|
||||
fieldSQL += col + "_unix = "
|
||||
|
||||
switch col {
|
||||
case "deadline":
|
||||
if bean.Deadline.IsZero() {
|
||||
continue
|
||||
}
|
||||
fieldSQL += com.ToStr(bean.Deadline.Unix())
|
||||
case "created":
|
||||
fieldSQL += com.ToStr(bean.Created.Unix())
|
||||
case "updated":
|
||||
fieldSQL += com.ToStr(bean.Updated.Unix())
|
||||
case "closed_date":
|
||||
fieldSQL += com.ToStr(bean.ClosedDate.Unix())
|
||||
case "merged":
|
||||
fieldSQL += com.ToStr(bean.Merged.Unix())
|
||||
case "next_update":
|
||||
fieldSQL += com.ToStr(bean.NextUpdate.Unix())
|
||||
}
|
||||
|
||||
valSQLs = append(valSQLs, fieldSQL)
|
||||
}
|
||||
|
||||
if len(valSQLs) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
if _, err = x.Exec(baseSQL + strings.Join(valSQLs, ",") + " WHERE id = " + com.ToStr(bean.ID)); err != nil {
|
||||
return fmt.Errorf("update bean [table: %s, id: %d]: %v", table.name, bean.ID, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func accessRefactor(x *xorm.Engine) (err error) {
|
||||
type (
|
||||
AccessMode int
|
||||
Access struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
UserID int64 `xorm:"UNIQUE(s)"`
|
||||
RepoID int64 `xorm:"UNIQUE(s)"`
|
||||
Mode AccessMode
|
||||
}
|
||||
UserRepo struct {
|
||||
UserID int64
|
||||
RepoID int64
|
||||
}
|
||||
)
|
||||
|
||||
// We consiously don't start a session yet as we make only reads for now, no writes
|
||||
|
||||
accessMap := make(map[UserRepo]AccessMode, 50)
|
||||
|
||||
results, err := x.Query("SELECT r.id AS `repo_id`, r.is_private AS `is_private`, r.owner_id AS `owner_id`, u.type AS `owner_type` FROM `repository` r LEFT JOIN `user` u ON r.owner_id=u.id")
|
||||
if err != nil {
|
||||
return fmt.Errorf("select repositories: %v", err)
|
||||
}
|
||||
for _, repo := range results {
|
||||
repoID := com.StrTo(repo["repo_id"]).MustInt64()
|
||||
isPrivate := com.StrTo(repo["is_private"]).MustInt() > 0
|
||||
ownerID := com.StrTo(repo["owner_id"]).MustInt64()
|
||||
ownerIsOrganization := com.StrTo(repo["owner_type"]).MustInt() > 0
|
||||
|
||||
results, err := x.Query("SELECT `user_id` FROM `collaboration` WHERE repo_id=?", repoID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("select collaborators: %v", err)
|
||||
}
|
||||
for _, user := range results {
|
||||
userID := com.StrTo(user["user_id"]).MustInt64()
|
||||
accessMap[UserRepo{userID, repoID}] = 2 // WRITE ACCESS
|
||||
}
|
||||
|
||||
if !ownerIsOrganization {
|
||||
continue
|
||||
}
|
||||
|
||||
// The minimum level to add a new access record,
|
||||
// because public repository has implicit open access.
|
||||
minAccessLevel := AccessMode(0)
|
||||
if !isPrivate {
|
||||
minAccessLevel = 1
|
||||
}
|
||||
|
||||
repoString := "$" + string(repo["repo_id"]) + "|"
|
||||
|
||||
results, err = x.Query("SELECT `id`,`authorize`,`repo_ids` FROM `team` WHERE org_id=? AND authorize>? ORDER BY `authorize` ASC", ownerID, int(minAccessLevel))
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "no such column") {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("select teams from org: %v", err)
|
||||
}
|
||||
|
||||
for _, team := range results {
|
||||
if !strings.Contains(string(team["repo_ids"]), repoString) {
|
||||
continue
|
||||
}
|
||||
teamID := com.StrTo(team["id"]).MustInt64()
|
||||
mode := AccessMode(com.StrTo(team["authorize"]).MustInt())
|
||||
|
||||
results, err := x.Query("SELECT `uid` FROM `team_user` WHERE team_id=?", teamID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("select users from team: %v", err)
|
||||
}
|
||||
for _, user := range results {
|
||||
userID := com.StrTo(user["uid"]).MustInt64()
|
||||
accessMap[UserRepo{userID, repoID}] = mode
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Drop table can't be in a session (at least not in sqlite)
|
||||
if _, err = x.Exec("DROP TABLE `access`"); err != nil {
|
||||
return fmt.Errorf("drop access table: %v", err)
|
||||
}
|
||||
|
||||
// Now we start writing so we make a session
|
||||
sess := x.NewSession()
|
||||
defer sessionRelease(sess)
|
||||
if err = sess.Begin(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = sess.Sync2(new(Access)); err != nil {
|
||||
return fmt.Errorf("sync: %v", err)
|
||||
}
|
||||
|
||||
accesses := make([]*Access, 0, len(accessMap))
|
||||
for ur, mode := range accessMap {
|
||||
accesses = append(accesses, &Access{UserID: ur.UserID, RepoID: ur.RepoID, Mode: mode})
|
||||
}
|
||||
|
||||
if _, err = sess.Insert(accesses); err != nil {
|
||||
return fmt.Errorf("insert accesses: %v", err)
|
||||
}
|
||||
|
||||
return sess.Commit()
|
||||
}
|
||||
|
||||
func teamToTeamRepo(x *xorm.Engine) error {
|
||||
type TeamRepo struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
OrgID int64 `xorm:"INDEX"`
|
||||
TeamID int64 `xorm:"UNIQUE(s)"`
|
||||
RepoID int64 `xorm:"UNIQUE(s)"`
|
||||
}
|
||||
|
||||
teamRepos := make([]*TeamRepo, 0, 50)
|
||||
|
||||
results, err := x.Query("SELECT `id`,`org_id`,`repo_ids` FROM `team`")
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "no such column") {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("select teams: %v", err)
|
||||
}
|
||||
for _, team := range results {
|
||||
orgID := com.StrTo(team["org_id"]).MustInt64()
|
||||
teamID := com.StrTo(team["id"]).MustInt64()
|
||||
|
||||
// #1032: legacy code can have duplicated IDs for same repository.
|
||||
mark := make(map[int64]bool)
|
||||
for _, idStr := range strings.Split(string(team["repo_ids"]), "|") {
|
||||
repoID := com.StrTo(strings.TrimPrefix(idStr, "$")).MustInt64()
|
||||
if repoID == 0 || mark[repoID] {
|
||||
continue
|
||||
}
|
||||
|
||||
mark[repoID] = true
|
||||
teamRepos = append(teamRepos, &TeamRepo{
|
||||
OrgID: orgID,
|
||||
TeamID: teamID,
|
||||
RepoID: repoID,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
sess := x.NewSession()
|
||||
defer sessionRelease(sess)
|
||||
if err = sess.Begin(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = sess.Sync2(new(TeamRepo)); err != nil {
|
||||
return fmt.Errorf("sync2: %v", err)
|
||||
} else if _, err = sess.Insert(teamRepos); err != nil {
|
||||
return fmt.Errorf("insert team-repos: %v", err)
|
||||
}
|
||||
|
||||
return sess.Commit()
|
||||
}
|
||||
|
||||
func fixLocaleFileLoadPanic(_ *xorm.Engine) error {
|
||||
cfg, err := ini.Load(setting.CustomConf)
|
||||
if err != nil {
|
||||
return fmt.Errorf("load custom config: %v", err)
|
||||
}
|
||||
|
||||
cfg.DeleteSection("i18n")
|
||||
if err = cfg.SaveTo(setting.CustomConf); err != nil {
|
||||
return fmt.Errorf("save custom config: %v", err)
|
||||
}
|
||||
|
||||
setting.Langs = strings.Split(strings.Replace(strings.Join(setting.Langs, ","), "fr-CA", "fr-FR", 1), ",")
|
||||
return nil
|
||||
}
|
||||
|
||||
func trimCommitActionAppUrlPrefix(x *xorm.Engine) error {
|
||||
type PushCommit struct {
|
||||
Sha1 string
|
||||
Message string
|
||||
AuthorEmail string
|
||||
AuthorName string
|
||||
}
|
||||
|
||||
type PushCommits struct {
|
||||
Len int
|
||||
Commits []*PushCommit
|
||||
CompareUrl string
|
||||
}
|
||||
|
||||
type Action struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
Content string `xorm:"TEXT"`
|
||||
}
|
||||
|
||||
results, err := x.Query("SELECT `id`,`content` FROM `action` WHERE `op_type`=?", 5)
|
||||
if err != nil {
|
||||
return fmt.Errorf("select commit actions: %v", err)
|
||||
}
|
||||
|
||||
sess := x.NewSession()
|
||||
defer sessionRelease(sess)
|
||||
if err = sess.Begin(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var pushCommits *PushCommits
|
||||
for _, action := range results {
|
||||
actID := com.StrTo(string(action["id"])).MustInt64()
|
||||
if actID == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
pushCommits = new(PushCommits)
|
||||
if err = json.Unmarshal(action["content"], pushCommits); err != nil {
|
||||
return fmt.Errorf("unmarshal action content[%s]: %v", actID, err)
|
||||
}
|
||||
|
||||
infos := strings.Split(pushCommits.CompareUrl, "/")
|
||||
if len(infos) <= 4 {
|
||||
continue
|
||||
}
|
||||
pushCommits.CompareUrl = strings.Join(infos[len(infos)-4:], "/")
|
||||
|
||||
p, err := json.Marshal(pushCommits)
|
||||
if err != nil {
|
||||
return fmt.Errorf("marshal action content[%s]: %v", actID, err)
|
||||
}
|
||||
|
||||
if _, err = sess.Id(actID).Update(&Action{
|
||||
Content: string(p),
|
||||
}); err != nil {
|
||||
return fmt.Errorf("update action[%d]: %v", actID, err)
|
||||
}
|
||||
}
|
||||
return sess.Commit()
|
||||
}
|
||||
|
||||
func issueToIssueLabel(x *xorm.Engine) error {
|
||||
type IssueLabel struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
IssueID int64 `xorm:"UNIQUE(s)"`
|
||||
LabelID int64 `xorm:"UNIQUE(s)"`
|
||||
}
|
||||
|
||||
issueLabels := make([]*IssueLabel, 0, 50)
|
||||
results, err := x.Query("SELECT `id`,`label_ids` FROM `issue`")
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "no such column") {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("select issues: %v", err)
|
||||
}
|
||||
for _, issue := range results {
|
||||
issueID := com.StrTo(issue["id"]).MustInt64()
|
||||
|
||||
// Just in case legacy code can have duplicated IDs for same label.
|
||||
mark := make(map[int64]bool)
|
||||
for _, idStr := range strings.Split(string(issue["label_ids"]), "|") {
|
||||
labelID := com.StrTo(strings.TrimPrefix(idStr, "$")).MustInt64()
|
||||
if labelID == 0 || mark[labelID] {
|
||||
continue
|
||||
}
|
||||
|
||||
mark[labelID] = true
|
||||
issueLabels = append(issueLabels, &IssueLabel{
|
||||
IssueID: issueID,
|
||||
LabelID: labelID,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
sess := x.NewSession()
|
||||
defer sessionRelease(sess)
|
||||
if err = sess.Begin(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = sess.Sync2(new(IssueLabel)); err != nil {
|
||||
return fmt.Errorf("sync2: %v", err)
|
||||
} else if _, err = sess.Insert(issueLabels); err != nil {
|
||||
return fmt.Errorf("insert issue-labels: %v", err)
|
||||
}
|
||||
|
||||
return sess.Commit()
|
||||
}
|
||||
|
||||
func attachmentRefactor(x *xorm.Engine) error {
|
||||
type Attachment struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
UUID string `xorm:"uuid INDEX"`
|
||||
|
||||
// For rename purpose.
|
||||
Path string `xorm:"-"`
|
||||
NewPath string `xorm:"-"`
|
||||
}
|
||||
|
||||
results, err := x.Query("SELECT * FROM `attachment`")
|
||||
if err != nil {
|
||||
return fmt.Errorf("select attachments: %v", err)
|
||||
}
|
||||
|
||||
attachments := make([]*Attachment, 0, len(results))
|
||||
for _, attach := range results {
|
||||
if !com.IsExist(string(attach["path"])) {
|
||||
// If the attachment is already missing, there is no point to update it.
|
||||
continue
|
||||
}
|
||||
attachments = append(attachments, &Attachment{
|
||||
ID: com.StrTo(attach["id"]).MustInt64(),
|
||||
UUID: gouuid.NewV4().String(),
|
||||
Path: string(attach["path"]),
|
||||
})
|
||||
}
|
||||
|
||||
sess := x.NewSession()
|
||||
defer sessionRelease(sess)
|
||||
if err = sess.Begin(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = sess.Sync2(new(Attachment)); err != nil {
|
||||
return fmt.Errorf("Sync2: %v", err)
|
||||
}
|
||||
|
||||
// Note: Roll back for rename can be a dead loop,
|
||||
// so produces a backup file.
|
||||
var buf bytes.Buffer
|
||||
buf.WriteString("# old path -> new path\n")
|
||||
|
||||
// Update database first because this is where error happens the most often.
|
||||
for _, attach := range attachments {
|
||||
if _, err = sess.Id(attach.ID).Update(attach); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
attach.NewPath = path.Join(setting.AttachmentPath, attach.UUID[0:1], attach.UUID[1:2], attach.UUID)
|
||||
buf.WriteString(attach.Path)
|
||||
buf.WriteString("\t")
|
||||
buf.WriteString(attach.NewPath)
|
||||
buf.WriteString("\n")
|
||||
}
|
||||
|
||||
// Then rename attachments.
|
||||
isSucceed := true
|
||||
defer func() {
|
||||
if isSucceed {
|
||||
return
|
||||
}
|
||||
|
||||
dumpPath := path.Join(setting.LogRootPath, "attachment_path.dump")
|
||||
ioutil.WriteFile(dumpPath, buf.Bytes(), 0666)
|
||||
fmt.Println("Fail to rename some attachments, old and new paths are saved into:", dumpPath)
|
||||
}()
|
||||
for _, attach := range attachments {
|
||||
if err = os.MkdirAll(path.Dir(attach.NewPath), os.ModePerm); err != nil {
|
||||
isSucceed = false
|
||||
return err
|
||||
}
|
||||
|
||||
if err = os.Rename(attach.Path, attach.NewPath); err != nil {
|
||||
isSucceed = false
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return sess.Commit()
|
||||
}
|
||||
|
||||
52
models/migrations/v13.go
Normal file
52
models/migrations/v13.go
Normal file
@@ -0,0 +1,52 @@
|
||||
// Copyright 2016 The Gogs Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package migrations
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/Unknwon/com"
|
||||
"github.com/go-xorm/xorm"
|
||||
)
|
||||
|
||||
func ldapUseSSLToSecurityProtocol(x *xorm.Engine) error {
|
||||
results, err := x.Query("SELECT `id`,`cfg` FROM `login_source` WHERE `type` = 2 OR `type` = 5")
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "no such column") {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("select LDAP login sources: %v", err)
|
||||
}
|
||||
|
||||
sess := x.NewSession()
|
||||
defer sessionRelease(sess)
|
||||
if err = sess.Begin(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, result := range results {
|
||||
cfg := map[string]interface{}{}
|
||||
if err = json.Unmarshal(result["cfg"], &cfg); err != nil {
|
||||
return fmt.Errorf("decode JSON config: %v", err)
|
||||
}
|
||||
if com.ToStr(cfg["UseSSL"]) == "true" {
|
||||
cfg["SecurityProtocol"] = 1 // LDAPS
|
||||
}
|
||||
delete(cfg, "UseSSL")
|
||||
|
||||
data, err := json.Marshal(&cfg)
|
||||
if err != nil {
|
||||
return fmt.Errorf("encode JSON config: %v", err)
|
||||
}
|
||||
|
||||
if _, err = sess.Exec("UPDATE `login_source` SET `cfg`=? WHERE `id`=?",
|
||||
string(data), com.StrTo(result["id"]).MustInt64()); err != nil {
|
||||
return fmt.Errorf("update config column: %v", err)
|
||||
}
|
||||
}
|
||||
return sess.Commit()
|
||||
}
|
||||
24
models/migrations/v14.go
Normal file
24
models/migrations/v14.go
Normal file
@@ -0,0 +1,24 @@
|
||||
// Copyright 2016 The Gogs Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package migrations
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/go-xorm/xorm"
|
||||
)
|
||||
|
||||
func setCommentUpdatedWithCreated(x *xorm.Engine) (err error) {
|
||||
type Comment struct {
|
||||
UpdatedUnix int64
|
||||
}
|
||||
|
||||
if err = x.Sync2(new(Comment)); err != nil {
|
||||
return fmt.Errorf("Sync2: %v", err)
|
||||
} else if _, err = x.Exec("UPDATE comment SET updated_unix = created_unix"); err != nil {
|
||||
return fmt.Errorf("set update_unix: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user