mirror of
https://github.com/gogs/gogs.git
synced 2026-02-28 17:20:59 +01:00
Compare commits
512 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 |
@@ -13,8 +13,7 @@ watch_dirs = [
|
||||
watch_exts = [".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"],
|
||||
["go", "install", "-race"], # sqlite redis memcache cert pam tidb
|
||||
["go", "build", "-race"],
|
||||
["./gogs", "web"]
|
||||
]
|
||||
@@ -1,6 +1,14 @@
|
||||
.git
|
||||
.git/
|
||||
.git/*
|
||||
conf
|
||||
conf/
|
||||
conf/*
|
||||
packager
|
||||
packager/
|
||||
packager/*
|
||||
scripts
|
||||
scripts/
|
||||
scripts/*
|
||||
*.yml
|
||||
*.md
|
||||
@@ -9,4 +17,4 @@ scripts/*
|
||||
.gitignore
|
||||
.gopmfile
|
||||
config.codekit
|
||||
LICENSE
|
||||
LICENSE
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -35,3 +35,5 @@ docker/docker/Dockerfile
|
||||
docker/docker/init_gogs.sh
|
||||
gogs.sublime-project
|
||||
gogs.sublime-workspace
|
||||
.tags*
|
||||
release
|
||||
|
||||
65
.gopmfile
65
.gopmfile
@@ -3,37 +3,46 @@ 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/codegangsta/cli = commit:0302d39
|
||||
github.com/go-macaron/binding = commit:2502aaf
|
||||
github.com/go-macaron/cache = commit:5617353
|
||||
github.com/go-macaron/captcha = commit:8aa5919
|
||||
github.com/go-macaron/csrf = commit:715bca0
|
||||
github.com/go-macaron/gzip = commit:4938e9b
|
||||
github.com/go-macaron/i18n = commit:d2d3329
|
||||
github.com/go-macaron/inject = commit:c5ab7bf
|
||||
github.com/go-macaron/session = commit:66031fc
|
||||
github.com/go-macaron/toolbox = commit:ab30a81
|
||||
github.com/go-sql-driver/mysql = commit:d512f20
|
||||
github.com/go-xorm/core = commit:acb6f00
|
||||
github.com/go-xorm/xorm = commit:a8fba4d
|
||||
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/gogits/git-shell = commit:de77627
|
||||
github.com/gogits/go-gogs-client = commit:4b541fa
|
||||
github.com/issue9/identicon = commit:f8c0d2c
|
||||
github.com/klauspost/compress = commit:bcd0709
|
||||
github.com/klauspost/cpuid = commit:8d9fe96
|
||||
github.com/klauspost/crc32 = commit:0aff1ea
|
||||
github.com/lib/pq = commit:11fc39a
|
||||
github.com/mattn/go-sqlite3 = commit:5651a9d
|
||||
github.com/mcuadros/go-version = commit:d52711f
|
||||
github.com/microcosm-cc/bluemonday = commit:4ac6f27
|
||||
github.com/msteinert/pam = commit:02ccfbf
|
||||
github.com/nfnt/resize = commit:dc93e1b98c
|
||||
github.com/russross/blackfriday = commit:8cec3a854e
|
||||
github.com/shurcooL/sanitized_anchor_name = commit:244f5ac324
|
||||
github.com/russross/blackfriday = commit:300106c
|
||||
github.com/shurcooL/sanitized_anchor_name = commit:10ef21a
|
||||
github.com/Unknwon/cae = commit:7f5e046
|
||||
github.com/Unknwon/com = commit:28b053d
|
||||
github.com/Unknwon/i18n = commit:3b48b66
|
||||
github.com/Unknwon/paginater = commit:7748a72
|
||||
golang.org/x/net =
|
||||
golang.org/x/text =
|
||||
gopkg.in/ini.v1 = commit:463307112d
|
||||
golang.org/x/text =
|
||||
golang.org/x/crypto =
|
||||
gopkg.in/asn1-ber.v1 = commit:4e86f43
|
||||
gopkg.in/gomail.v2 = commit:df6fc79
|
||||
gopkg.in/ini.v1 = commit:65f8c74
|
||||
gopkg.in/ldap.v2 = e9a325d
|
||||
gopkg.in/macaron.v1 = commit:1c6dd87
|
||||
gopkg.in/redis.v2 = commit:e617904962
|
||||
|
||||
[res]
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
language: go
|
||||
|
||||
go:
|
||||
- 1.2
|
||||
- 1.3
|
||||
- 1.4
|
||||
- 1.5
|
||||
- tip
|
||||
|
||||
before_install:
|
||||
- sudo apt-get update -qq
|
||||
- sudo apt-get install -y libpam-dev
|
||||
- go get github.com/msteinert/pam
|
||||
|
||||
install:
|
||||
- go get -t -v ./...
|
||||
|
||||
script: go build -v -tags "pam"
|
||||
|
||||
notifications:
|
||||
|
||||
@@ -42,7 +42,7 @@ 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**.
|
||||
Pull requests are always welcome, but note that **ALL PULL REQUESTS MUST APPLY TO THE `develop` 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.
|
||||
|
||||
|
||||
66
Dockerfile
66
Dockerfile
@@ -1,54 +1,22 @@
|
||||
FROM google/debian:wheezy
|
||||
MAINTAINER u@gogs.io
|
||||
FROM alpine:3.2
|
||||
MAINTAINER roemer.jp@gmail.com
|
||||
|
||||
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.6/gosu-amd64 /usr/sbin/gosu
|
||||
RUN echo "@edge http://dl-4.alpinelinux.org/alpine/edge/main" | tee -a /etc/apk/repositories \
|
||||
&& echo "@community http://dl-4.alpinelinux.org/alpine/edge/community" | tee -a /etc/apk/repositories \
|
||||
&& apk -U --no-progress upgrade \
|
||||
&& apk -U --no-progress add ca-certificates bash git linux-pam s6@edge curl openssh socat \
|
||||
&& chmod +x /usr/sbin/gosu
|
||||
|
||||
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/
|
||||
WORKDIR /app/gogs/
|
||||
RUN ./docker/build.sh
|
||||
|
||||
# Configure Docker Container
|
||||
VOLUME ["/data"]
|
||||
EXPOSE 22 3000
|
||||
ENTRYPOINT []
|
||||
CMD ["./docker/start.sh"]
|
||||
ENTRYPOINT ["docker/start.sh"]
|
||||
CMD ["/bin/s6-svscan", "/app/gogs/docker/s6/"]
|
||||
|
||||
22
Dockerfile.rpi
Normal file
22
Dockerfile.rpi
Normal file
@@ -0,0 +1,22 @@
|
||||
FROM sander85/rpi-alpine:latest
|
||||
MAINTAINER roemer.jp@gmail.com, raxetul@gmail.com
|
||||
|
||||
# Install system utils & Gogs runtime dependencies
|
||||
ADD https://github.com/tianon/gosu/releases/download/1.6/gosu-armhf /usr/sbin/gosu
|
||||
RUN echo "@edge http://dl-4.alpinelinux.org/alpine/edge/main" | tee -a /etc/apk/repositories \
|
||||
&& echo "@community http://dl-4.alpinelinux.org/alpine/edge/community" | tee -a /etc/apk/repositories \
|
||||
&& apk -U --no-progress upgrade \
|
||||
&& apk -U --no-progress add ca-certificates bash git linux-pam s6@edge curl openssh socat \
|
||||
&& chmod +x /usr/sbin/gosu
|
||||
|
||||
ENV GOGS_CUSTOM /data/gogs
|
||||
|
||||
COPY . /app/gogs/
|
||||
WORKDIR /app/gogs/
|
||||
RUN ./docker/build.sh
|
||||
|
||||
# Configure Docker Container
|
||||
VOLUME ["/data"]
|
||||
EXPOSE 22 3000
|
||||
ENTRYPOINT ["docker/start.sh"]
|
||||
CMD ["/usr/bin/s6-svscan", "/app/gogs/docker/s6/"]
|
||||
46
Makefile
Normal file
46
Makefile
Normal file
@@ -0,0 +1,46 @@
|
||||
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
|
||||
|
||||
TAGS = ""
|
||||
|
||||
RELEASE_ROOT = "release"
|
||||
RELEASE_GOGS = "release/gogs"
|
||||
NOW = $(shell date -u '+%Y%m%d%I%M%S')
|
||||
|
||||
.PHONY: build pack release bindata clean
|
||||
|
||||
build: $(GENERATED)
|
||||
go install -ldflags '$(LDFLAGS)' -tags '$(TAGS)'
|
||||
cp '$(GOPATH)/bin/gogs' .
|
||||
|
||||
govet:
|
||||
go tool vet -composites=false -methods=false -structtags=false .
|
||||
|
||||
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" -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
|
||||
99
README.md
99
README.md
@@ -3,72 +3,61 @@ Gogs - Go Git Service [](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 version: 0.6.9 Beta
|
||||
##### Current version: 0.7.33 Beta
|
||||
|
||||
<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>
|
||||
| Web | UI | Preview |
|
||||
|:-------------:|:-------:|:-------:|
|
||||
||||
|
||||
||||
|
||||
||||
|
||||
|
||||
### NOTICES
|
||||
|
||||
- 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:
|
||||
- :bangbang:<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, otherwise it's high possibilities that we are not going to merge it.</span>:bangbang:
|
||||
- Please [start discussion](http://forum.gogs.io/category/2/general-discussion) or [ask a question](http://forum.gogs.io/category/4/getting-help) on [the forum](http://forum.gogs.io/). GitHub issue tracker only keeps **bugs** and **feature requests**, all other topics will be closed without reason.
|
||||
- If you think there are vulnerabilities in the project, please talk privately to **u@gogs.io**. Thanks!
|
||||
- If you're interested in using APIs, we have experimental support with [documentation](https://github.com/gogits/go-gogs-client/wiki).
|
||||
- If your team/company is using Gogs and would like to put your logo on [our website](http://gogs.io), contact us by any means.
|
||||
|
||||
#### 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.
|
||||
- Please see the [Documentation](http://gogs.io/docs/intro) for common usages 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 try it before doing anything else? Do it [online](https://try.gogs.io/gogs/gogs)!
|
||||
- Having trouble? Get help with [Troubleshooting](http://gogs.io/docs/intro/troubleshooting.html).
|
||||
- Want to help with localization? Check out the [guide](http://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
|
||||
- Repository/Organization webhooks (including Slack)
|
||||
- Repository Git hooks/deploy keys
|
||||
- Add/remove repository collaborators
|
||||
- Gravatar and custom source support
|
||||
- Repository issues and pull requests
|
||||
- Add/Remove repository collaborators
|
||||
- Gravatar and custom source
|
||||
- Mail service
|
||||
- Administration panel
|
||||
- Supports MySQL, PostgreSQL and SQLite3
|
||||
- Social account login (GitHub, Google, QQ, Weibo)
|
||||
- Supports MySQL, PostgreSQL, SQLite3 and [TiDB](https://github.com/pingcap/tidb) (experimental)
|
||||
- Multi-language support ([14 languages](https://crowdin.com/project/gogs))
|
||||
|
||||
## System 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.
|
||||
- 2 CPU cores and 1GB RAM would be the baseline for teamwork.
|
||||
|
||||
## Browser Support
|
||||
|
||||
@@ -77,13 +66,13 @@ The goal of this project is to make the easiest, fastest, and most painless way
|
||||
|
||||
## Installation
|
||||
|
||||
Make sure you install the [prerequisites](http://gogs.io/docs/installation/) first.
|
||||
Make sure you install the [prerequisites](http://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](http://gogs.io/docs/installation/install_from_binary.html)
|
||||
- [Install from source](http://gogs.io/docs/installation/install_from_source.html)
|
||||
- [Install from packages](http://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,6 +80,7 @@ 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/)
|
||||
- [使用 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/)
|
||||
@@ -99,17 +89,38 @@ There are 5 ways to install Gogs:
|
||||
|
||||
- [Instalando Gogs no Ubuntu](http://blog.linuxpro.com.br/2015/08/14/instalando-gogs-no-ubuntu/) (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/)
|
||||
- [Portal](https://portaldemo.xyz/cloud/)
|
||||
- [Sandstorm](https://github.com/cem/gogs-sandstorm)
|
||||
- [sloppy.io](https://github.com/sloppyio/quickstarters/tree/master/gogs)
|
||||
|
||||
## Software and Service Support
|
||||
|
||||
- [Drone](https://github.com/drone/drone) (CI)
|
||||
- [Fabric8](http://fabric8.io/) (DevOps)
|
||||
- [Taiga](https://taiga.io/) (Project Management)
|
||||
|
||||
### 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).
|
||||
- Router and middleware mechanism of [Macaron](https://github.com/go-macaron/macaron).
|
||||
- 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 [Crowdin](https://crowdin.com/project/gogs) for providing open source translation plan.
|
||||
- Thanks [DigitalOcean](https://www.digitalocean.com) for hosting home and demo sites.
|
||||
|
||||
## Contributors
|
||||
|
||||
- Ex-team members [@lunny](https://github.com/lunny) and [@fuxiaohei](https://github.com/fuxiaohei).
|
||||
- Ex-team members [@lunny](https://github.com/lunny), [@fuxiaohei](https://github.com/fuxiaohei) and [@slene](https://github.com/slene).
|
||||
- See [contributors page](https://github.com/gogits/gogs/graphs/contributors) for full list of contributors.
|
||||
- See [TRANSLATORS](conf/locale/TRANSLATORS) for public list of translators.
|
||||
|
||||
|
||||
58
README_ZH.md
58
README_ZH.md
@@ -1,35 +1,35 @@
|
||||
Gogs - Go Git Service [](https://travis-ci.org/gogits/gogs)
|
||||
=====================
|
||||
|
||||
Gogs (Go Git Service) 是一款可轻易搭建的自助 Git 服务。
|
||||
Gogs (Go Git Service) 是一款极易搭建的自助 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/) 查看。
|
||||
- 有关基本用法和变更日志,请通过 [使用手册](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/intro/troubleshooting.html) 页面获取帮助。
|
||||
- 希望帮助多国语言界面的翻译吗?请立即访问 [详情页面](http://gogs.io/docs/features/i18n.html)!
|
||||
|
||||
## 功能特性
|
||||
|
||||
- 支持活动时间线
|
||||
- 支持 SSH/HTTP(S) 协议
|
||||
- 支持 SMTP/LDAP/反向代理的用户认证
|
||||
- 支持 SSH 以及 HTTP/HTTPS 协议
|
||||
- 支持 SMTP、LDAP 和反向代理的用户认证
|
||||
- 支持反向代理子路径
|
||||
- 支持用户、组织和仓库管理系统
|
||||
- 支持仓库和组织级别 Web 钩子(包括 Slack 集成)
|
||||
- 支持仓库 Git 钩子和部署密钥
|
||||
- 支持 添加/删除 仓库协作者
|
||||
- 支持仓库工单(Issue)和合并请求(Pull Request)
|
||||
- 支持添加和删除仓库协作者
|
||||
- 支持 Gravatar 以及自定义源
|
||||
- 支持邮件服务
|
||||
- 支持后台管理面板
|
||||
- 支持 MySQL、PostgreSQL 以及 SQLite3 数据库
|
||||
- 支持社交帐号登录(GitHub、Google、QQ、微博)
|
||||
- 支持 MySQL、PostgreSQL、SQLite3 和 [TiDB](https://github.com/pingcap/tidb)(实验性支持) 数据库
|
||||
- 支持多语言本地化([14 种语言]([more](https://crowdin.com/project/gogs)))
|
||||
|
||||
## 系统要求
|
||||
@@ -44,27 +44,53 @@ Gogs 的目标是打造一个最简单、最快速和最轻松的方式搭建自
|
||||
|
||||
## 安装部署
|
||||
|
||||
在安装 Gogs 之前,您需要先安装 [基本环境](http://gogs.io/docs/installation/)。
|
||||
在安装 Gogs 之前,您需要先安装 [基本环境](http://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)
|
||||
- [二进制安装](http://gogs.io/docs/installation/install_from_binary.html)
|
||||
- [源码安装](http://gogs.io/docs/installation/install_from_source.html)
|
||||
- [包管理安装](http://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/)
|
||||
- [Portal](https://portaldemo.xyz/cloud/)
|
||||
- [Sandstorm](https://github.com/cem/gogs-sandstorm)
|
||||
- [sloppy.io](https://github.com/sloppyio/quickstarters/tree/master/gogs)
|
||||
|
||||
## 软件及服务支持
|
||||
|
||||
- [Drone](https://github.com/drone/drone)(CI)
|
||||
- [Fabric8](http://fabric8.io/)(DevOps)
|
||||
- [Taiga](https://taiga.io/)(项目管理)
|
||||
|
||||
### 产品支持
|
||||
|
||||
- [Synology](https://www.synology.com)(Docker)
|
||||
- [One Space](http://www.onespace.cc)(应用商店)
|
||||
|
||||
## 特别鸣谢
|
||||
|
||||
- 基于 [Macaron](https://github.com/Unknwon/macaron) 的路由与中间件机制。
|
||||
- 基于 [WeTalk](https://github.com/beego/wetalk) 修改的邮件服务和模块设计。
|
||||
- 基于 [Macaron](https://github.com/go-macaron/macaron) 的路由与中间件机制。
|
||||
- 基于 [WeTalk](https://github.com/beego/wetalk) 修改的模块设计。
|
||||
- 基于 [GoBlog](https://github.com/fuxiaohei/goblog) 修改的系统监视状态。
|
||||
- 感谢 [lavachen](http://www.lavachen.cn/) 和 [Rocker](http://weibo.com/rocker1989) 设计的 Logo。
|
||||
- 感谢 [Crowdin](https://crowdin.com/project/gogs) 提供免费的开源项目本地化支持。
|
||||
- 感谢 [DigitalOcean](https://www.digitalocean.com) 提供主站和体验站点的服务器赞助。
|
||||
|
||||
## 贡献成员
|
||||
|
||||
- 前团队成员 [@lunny](https://github.com/lunny) 和 [@fuxiaohei](https://github.com/fuxiaohei)。
|
||||
- 前团队成员 [@lunny](https://github.com/lunny)、[@fuxiaohei](https://github.com/fuxiaohei) 和 [@slene](https://github.com/slene)。
|
||||
- 您可以通过查看 [贡献者页面](https://github.com/gogits/gogs/graphs/contributors) 获取完整的贡献者列表。
|
||||
- 您可以通过查看 [TRANSLATORS](conf/locale/TRANSLATORS) 文件获取公开的翻译人员列表。
|
||||
|
||||
|
||||
14
cmd/cert.go
14
cmd/cert.go
@@ -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"),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
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/codegangsta/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,
|
||||
}
|
||||
}
|
||||
51
cmd/dump.go
51
cmd/dump.go
@@ -11,6 +11,8 @@ import (
|
||||
"path"
|
||||
"time"
|
||||
|
||||
"io/ioutil"
|
||||
|
||||
"github.com/Unknwon/cae/zip"
|
||||
"github.com/codegangsta/cli"
|
||||
|
||||
@@ -25,8 +27,8 @@ 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"),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -34,20 +36,27 @@ func runDump(ctx *cli.Context) {
|
||||
if ctx.IsSet("config") {
|
||||
setting.CustomConf = ctx.String("config")
|
||||
}
|
||||
setting.NewConfigContext()
|
||||
models.LoadModelsConfig()
|
||||
setting.NewContext()
|
||||
models.LoadConfigs()
|
||||
models.SetEngine()
|
||||
|
||||
TmpWorkDir, err := ioutil.TempDir(os.TempDir(), "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 +68,30 @@ 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)
|
||||
}
|
||||
// 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!")
|
||||
log.Printf("Removing tmp work dir: %s", TmpWorkDir)
|
||||
os.RemoveAll(TmpWorkDir)
|
||||
log.Printf("Finish dumping in file %s", fileName)
|
||||
}
|
||||
|
||||
131
cmd/serve.go
131
cmd/serve.go
@@ -5,6 +5,7 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
@@ -32,12 +33,12 @@ var CmdServ = cli.Command{
|
||||
Description: `Serv provide access auth for repositories`,
|
||||
Action: runServ,
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{"config, c", "custom/conf/app.ini", "Custom configuration file path", ""},
|
||||
stringFlag("config, c", "custom/conf/app.ini", "Custom configuration file path"),
|
||||
},
|
||||
}
|
||||
|
||||
func setup(logPath string) {
|
||||
setting.NewConfigContext()
|
||||
setting.NewContext()
|
||||
log.NewGitLogger(filepath.Join(setting.LogRootPath, logPath))
|
||||
|
||||
if setting.DisableSSH {
|
||||
@@ -45,9 +46,9 @@ func setup(logPath string) {
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
models.LoadModelsConfig()
|
||||
models.LoadConfigs()
|
||||
|
||||
if setting.UseSQLite3 {
|
||||
if setting.UseSQLite3 || setting.UseTiDB {
|
||||
workDir, _ := setting.WorkDir()
|
||||
os.Chdir(workDir)
|
||||
}
|
||||
@@ -64,7 +65,7 @@ func parseCmd(cmd string) (string, string) {
|
||||
}
|
||||
|
||||
var (
|
||||
COMMANDS = map[string]models.AccessMode{
|
||||
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,
|
||||
@@ -73,7 +74,56 @@ var (
|
||||
|
||||
func fail(userMessage, logMessage string, args ...interface{}) {
|
||||
fmt.Fprintln(os.Stderr, "Gogs:", userMessage)
|
||||
log.GitLogger.Fatal(3, logMessage, args...)
|
||||
|
||||
if len(logMessage) > 0 {
|
||||
if !setting.ProdMode {
|
||||
fmt.Fprintf(os.Stderr, logMessage+"\n", args...)
|
||||
}
|
||||
log.GitLogger.Fatal(3, logMessage, args...)
|
||||
return
|
||||
}
|
||||
|
||||
log.GitLogger.Close()
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
func handleUpdateTask(uuid string, user *models.User, username, reponame string, isWiki bool) {
|
||||
task, err := models.GetUpdateTaskByUUID(uuid)
|
||||
if err != nil {
|
||||
if models.IsErrUpdateTaskNotExist(err) {
|
||||
log.GitLogger.Trace("No update task is presented: %s", uuid)
|
||||
return
|
||||
}
|
||||
log.GitLogger.Fatal(2, "GetUpdateTaskByUUID: %v", err)
|
||||
} else if err = models.DeleteUpdateTaskByUUID(uuid); err != nil {
|
||||
log.GitLogger.Fatal(2, "DeleteUpdateTaskByUUID: %v", err)
|
||||
}
|
||||
|
||||
if isWiki {
|
||||
return
|
||||
}
|
||||
|
||||
if err = models.Update(task.RefName, task.OldCommitID, task.NewCommitID,
|
||||
user.Name, username, reponame, user.Id); err != nil {
|
||||
log.GitLogger.Error(2, "Update: %v", err)
|
||||
}
|
||||
|
||||
// Ask for running deliver hook and test pull request tasks.
|
||||
reqURL := setting.AppUrl + username + "/" + reponame + "/tasks/trigger?branch=" +
|
||||
strings.TrimPrefix(task.RefName, "refs/heads/")
|
||||
log.GitLogger.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.GitLogger.Error(2, "Fail to trigger task: not 2xx response code")
|
||||
}
|
||||
} else {
|
||||
log.GitLogger.Error(2, "Fail to trigger task: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func runServ(c *cli.Context) {
|
||||
@@ -94,35 +144,46 @@ func runServ(c *cli.Context) {
|
||||
}
|
||||
|
||||
verb, args := parseCmd(cmd)
|
||||
repoPath := strings.Trim(args, "'")
|
||||
repoPath := strings.ToLower(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")
|
||||
username := strings.ToLower(rr[0])
|
||||
reponame := strings.ToLower(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)
|
||||
isWiki := false
|
||||
if strings.HasSuffix(reponame, ".wiki") {
|
||||
isWiki = true
|
||||
reponame = reponame[:len(reponame)-5]
|
||||
}
|
||||
|
||||
repo, err := models.GetRepositoryByName(repoUser.Id, repoName)
|
||||
repoUser, err := models.GetUserByName(username)
|
||||
if err != nil {
|
||||
if models.IsErrUserNotExist(err) {
|
||||
fail("Repository owner does not exist", "Unregistered owner: %s", username)
|
||||
}
|
||||
fail("Internal error", "Failed to get repository owner(%s): %v", username, 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(_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]
|
||||
requestedMode, has := allowedCommands[verb]
|
||||
if !has {
|
||||
fail("Unknown git command", "Unknown git command %s", verb)
|
||||
}
|
||||
|
||||
// Prohibit push to mirror repositories.
|
||||
if requestedMode > models.ACCESS_MODE_READ && repo.IsMirror {
|
||||
fail("mirror repository is read-only", "")
|
||||
}
|
||||
|
||||
// Allow anonymous clone for public repositories.
|
||||
var (
|
||||
keyID int64
|
||||
@@ -131,12 +192,12 @@ func runServ(c *cli.Context) {
|
||||
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])
|
||||
fail("Key ID format error", "Invalid key argument: %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)
|
||||
fail("Invalid key ID", "Invalid key ID[%s]: %v", c.Args()[0], err)
|
||||
}
|
||||
keyID = key.ID
|
||||
|
||||
@@ -161,7 +222,7 @@ func runServ(c *cli.Context) {
|
||||
fail("Internal error", "UpdateDeployKey: %v", err)
|
||||
}
|
||||
} else {
|
||||
user, err = models.GetUserByKeyId(key.ID)
|
||||
user, err = models.GetUserByKeyID(key.ID)
|
||||
if err != nil {
|
||||
fail("internal error", "Failed to get user by key ID(%d): %v", keyID, err)
|
||||
}
|
||||
@@ -184,6 +245,11 @@ func runServ(c *cli.Context) {
|
||||
uuid := uuid.NewV4().String()
|
||||
os.Setenv("uuid", uuid)
|
||||
|
||||
// Special handle for Windows.
|
||||
if setting.IsWindows {
|
||||
verb = strings.Replace(verb, "-", " ", 1)
|
||||
}
|
||||
|
||||
var gitcmd *exec.Cmd
|
||||
verbs := strings.Split(verb, " ")
|
||||
if len(verbs) == 2 {
|
||||
@@ -200,28 +266,7 @@ func runServ(c *cli.Context) {
|
||||
}
|
||||
|
||||
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()
|
||||
handleUpdateTask(uuid, user, username, reponame, isWiki)
|
||||
}
|
||||
|
||||
// Update user key activity.
|
||||
|
||||
@@ -20,7 +20,7 @@ var CmdUpdate = cli.Command{
|
||||
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", ""},
|
||||
stringFlag("config, c", "custom/conf/app.ini", "Custom configuration file path"),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -42,16 +42,14 @@ func runUpdate(c *cli.Context) {
|
||||
log.GitLogger.Fatal(2, "refName is empty, shouldn't use")
|
||||
}
|
||||
|
||||
uuid := os.Getenv("uuid")
|
||||
|
||||
task := models.UpdateTask{
|
||||
Uuid: uuid,
|
||||
UUID: os.Getenv("uuid"),
|
||||
RefName: args[0],
|
||||
OldCommitId: args[1],
|
||||
NewCommitId: args[2],
|
||||
OldCommitID: args[1],
|
||||
NewCommitID: args[2],
|
||||
}
|
||||
|
||||
if err := models.AddUpdateTask(&task); err != nil {
|
||||
log.GitLogger.Fatal(2, err.Error())
|
||||
log.GitLogger.Fatal(2, "AddUpdateTask: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
203
cmd/web.go
203
cmd/web.go
@@ -7,7 +7,7 @@ package cmd
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"html/template"
|
||||
gotmpl "html/template"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/fcgi"
|
||||
@@ -15,34 +15,31 @@ import (
|
||||
"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"
|
||||
"gopkg.in/ini.v1"
|
||||
|
||||
api "github.com/gogits/go-gogs-client"
|
||||
"gopkg.in/macaron.v1"
|
||||
|
||||
"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/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 +53,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"),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -80,13 +77,14 @@ func checkVersion() {
|
||||
|
||||
// Check dependency version.
|
||||
checkers := []VerChecker{
|
||||
{"github.com/go-xorm/xorm", func() string { return xorm.Version }, "0.4.3.0806"},
|
||||
{"github.com/go-xorm/xorm", func() string { return xorm.Version }, "0.4.4.1029"},
|
||||
{"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"},
|
||||
{"github.com/go-macaron/binding", binding.Version, "0.1.0"},
|
||||
{"github.com/go-macaron/cache", cache.Version, "0.1.2"},
|
||||
{"github.com/go-macaron/csrf", csrf.Version, "0.0.3"},
|
||||
{"github.com/go-macaron/i18n", i18n.Version, "0.2.0"},
|
||||
{"github.com/go-macaron/session", session.Version, "0.1.6"},
|
||||
{"github.com/go-macaron/toolbox", toolbox.Version, "0.1.0"},
|
||||
{"gopkg.in/ini.v1", ini.Version, "1.3.4"},
|
||||
}
|
||||
for _, c := range checkers {
|
||||
@@ -104,7 +102,7 @@ func newMacaron() *macaron.Macaron {
|
||||
}
|
||||
m.Use(macaron.Recovery())
|
||||
if setting.EnableGzip {
|
||||
m.Use(macaron.Gziper())
|
||||
m.Use(gzip.Gziper())
|
||||
}
|
||||
if setting.Protocol == setting.FCGI {
|
||||
m.SetURLPrefix(setting.AppSubUrl)
|
||||
@@ -124,7 +122,7 @@ func newMacaron() *macaron.Macaron {
|
||||
))
|
||||
m.Use(macaron.Renderer(macaron.RenderOptions{
|
||||
Directory: path.Join(setting.StaticRootPath, "templates"),
|
||||
Funcs: []template.FuncMap{base.TemplateFuncs},
|
||||
Funcs: []gotmpl.FuncMap{template.Funcs},
|
||||
IndentJSON: macaron.Env != macaron.PROD,
|
||||
}))
|
||||
|
||||
@@ -142,6 +140,7 @@ 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{
|
||||
@@ -167,13 +166,6 @@ 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())
|
||||
return m
|
||||
}
|
||||
@@ -192,7 +184,6 @@ func runWeb(ctx *cli.Context) {
|
||||
ignSignInAndCsrf := middleware.Toggle(&middleware.ToggleOptions{DisableCsrf: true})
|
||||
reqSignOut := middleware.Toggle(&middleware.ToggleOptions{SignOutRequire: true})
|
||||
|
||||
bind := binding.Bind
|
||||
bindIgnErr := binding.BindIgnErr
|
||||
|
||||
// Routers.
|
||||
@@ -200,55 +191,11 @@ func runWeb(ctx *cli.Context) {
|
||||
m.Get("/explore", ignSignIn, routers.Explore)
|
||||
m.Combo("/install", routers.InstallInit).Get(routers.Install).
|
||||
Post(bindIgnErr(auth.InstallForm{}), routers.InstallPost)
|
||||
m.Get("/:type(issues|pulls)", reqSignIn, user.Issues)
|
||||
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")
|
||||
})
|
||||
})
|
||||
apiv1.RegisterRoutes(m)
|
||||
}, ignSignIn)
|
||||
// ***** END: API *****
|
||||
|
||||
@@ -256,7 +203,6 @@ func runWeb(ctx *cli.Context) {
|
||||
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)
|
||||
@@ -267,21 +213,20 @@ func runWeb(ctx *cli.Context) {
|
||||
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("/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.Route("/delete", "GET,POST", user.SettingsDelete)
|
||||
}, reqSignIn, func(ctx *middleware.Context) {
|
||||
ctx.Data["PageIsUserSettings"] = true
|
||||
ctx.Data["HasOAuthService"] = setting.OauthService != nil
|
||||
})
|
||||
|
||||
m.Group("/user", func() {
|
||||
@@ -311,7 +256,7 @@ func runWeb(ctx *cli.Context) {
|
||||
m.Group("/users", func() {
|
||||
m.Get("", admin.Users)
|
||||
m.Get("/new", admin.NewUser)
|
||||
m.Post("/new", bindIgnErr(auth.RegisterForm{}), admin.NewUserPost)
|
||||
m.Post("/new", bindIgnErr(auth.AdminCrateUserForm{}), admin.NewUserPost)
|
||||
m.Get("/:userid", admin.EditUser)
|
||||
m.Post("/:userid", bindIgnErr(auth.AdminEditUserForm{}), admin.EditUserPost)
|
||||
m.Post("/:userid/delete", admin.DeleteUser)
|
||||
@@ -322,21 +267,23 @@ 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("/: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 *****
|
||||
@@ -377,6 +324,7 @@ func runWeb(ctx *cli.Context) {
|
||||
}
|
||||
|
||||
reqRepoAdmin := middleware.RequireRepoAdmin()
|
||||
reqRepoPusher := middleware.RequireRepoPusher()
|
||||
|
||||
// ***** START: Organization *****
|
||||
m.Group("/org", func() {
|
||||
@@ -385,16 +333,16 @@ func runWeb(ctx *cli.Context) {
|
||||
|
||||
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)
|
||||
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)
|
||||
}, middleware.OrgAssignment(true))
|
||||
|
||||
m.Group("/:org", func() {
|
||||
m.Get("/teams/new", org.NewTeam)
|
||||
@@ -406,6 +354,7 @@ 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.UploadAvatarForm{}), org.SettingsAvatar)
|
||||
|
||||
m.Group("/hooks", func() {
|
||||
m.Get("", org.Webhooks)
|
||||
@@ -422,11 +371,8 @@ func runWeb(ctx *cli.Context) {
|
||||
})
|
||||
|
||||
m.Route("/invitations/new", "GET,POST", org.Invitation)
|
||||
}, middleware.OrgAssignment(true, true, true))
|
||||
}, middleware.OrgAssignment(true, true))
|
||||
}, reqSignIn)
|
||||
m.Group("/org", func() {
|
||||
m.Get("/:org", org.Home)
|
||||
}, ignSignIn, middleware.OrgAssignment(true))
|
||||
// ***** END: Organization *****
|
||||
|
||||
// ***** START: Repository *****
|
||||
@@ -452,6 +398,7 @@ func runWeb(ctx *cli.Context) {
|
||||
m.Post("/gogs/new", bindIgnErr(auth.NewWebhookForm{}), repo.WebHooksNewPost)
|
||||
m.Post("/slack/new", bindIgnErr(auth.NewSlackHookForm{}), repo.SlackHooksNewPost)
|
||||
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)
|
||||
|
||||
@@ -468,14 +415,16 @@ func runWeb(ctx *cli.Context) {
|
||||
m.Post("/delete", repo.DeleteDeployKey)
|
||||
})
|
||||
|
||||
}, func(ctx *middleware.Context) {
|
||||
ctx.Data["PageIsSettings"] = true
|
||||
})
|
||||
}, reqSignIn, middleware.RepoAssignment(true), reqRepoAdmin)
|
||||
}, reqSignIn, middleware.RepoAssignment(), reqRepoAdmin, middleware.RepoRef())
|
||||
|
||||
m.Group("/:username/:reponame", func() {
|
||||
m.Get("/action/:action", repo.Action)
|
||||
|
||||
m.Group("/issues", func() {
|
||||
m.Combo("/new").Get(repo.NewIssue).
|
||||
m.Combo("/new", repo.MustEnableIssues).Get(middleware.RepoRef(), repo.NewIssue).
|
||||
Post(bindIgnErr(auth.CreateIssueForm{}), repo.NewIssuePost)
|
||||
|
||||
m.Combo("/:index/comments").Post(bindIgnErr(auth.CreateCommentForm{}), repo.NewComment)
|
||||
@@ -495,61 +444,81 @@ func runWeb(ctx *cli.Context) {
|
||||
m.Post("/new", bindIgnErr(auth.CreateLabelForm{}), repo.NewLabel)
|
||||
m.Post("/edit", bindIgnErr(auth.CreateLabelForm{}), repo.UpdateLabel)
|
||||
m.Post("/delete", repo.DeleteLabel)
|
||||
}, reqRepoAdmin)
|
||||
}, reqRepoAdmin, middleware.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)
|
||||
}, reqRepoAdmin, middleware.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)
|
||||
m.Post("/delete", repo.DeleteRelease)
|
||||
}, reqRepoAdmin, middleware.RepoRef())
|
||||
|
||||
m.Combo("/compare/*").Get(repo.CompareAndPullRequest).
|
||||
m.Combo("/compare/*", repo.MustEnablePulls).Get(repo.CompareAndPullRequest).
|
||||
Post(bindIgnErr(auth.CreateIssueForm{}), repo.CompareAndPullRequestPost)
|
||||
}, reqSignIn, middleware.RepoAssignment(true))
|
||||
}, reqSignIn, middleware.RepoAssignment())
|
||||
|
||||
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)
|
||||
}, middleware.RepoRef())
|
||||
|
||||
// m.Get("/branches", repo.Branches)
|
||||
|
||||
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)
|
||||
}, reqSignIn, reqRepoPusher)
|
||||
}, repo.MustEnableWiki, middleware.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)
|
||||
})
|
||||
}, repo.MustEnablePulls)
|
||||
|
||||
m.Group("", func() {
|
||||
m.Get("/src/*", repo.Home)
|
||||
m.Get("/raw/*", repo.SingleDownload)
|
||||
m.Get("/commits/*", repo.RefCommits)
|
||||
m.Get("/commit/*", repo.Diff)
|
||||
m.Get("/stars", repo.Stars)
|
||||
m.Get("/watchers", repo.Watchers)
|
||||
m.Get("/forks", repo.Forks)
|
||||
}, middleware.RepoRef())
|
||||
|
||||
m.Get("/compare/:before([a-z0-9]{40})...:after([a-z0-9]{40})", repo.CompareDiff)
|
||||
}, ignSignIn, middleware.RepoAssignment(true))
|
||||
}, ignSignIn, middleware.RepoAssignment())
|
||||
|
||||
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.Get("\\.git$", repo.Home)
|
||||
}, ignSignIn, middleware.RepoAssignment(true), middleware.RepoRef())
|
||||
|
||||
m.Group("/:reponame", func() {
|
||||
m.Any("/*", ignSignInAndCsrf, repo.Http)
|
||||
m.Head("/hooks/trigger", repo.TriggerHook)
|
||||
m.Any("/*", ignSignInAndCsrf, repo.HTTP)
|
||||
m.Head("/tasks/trigger", repo.TriggerTask)
|
||||
})
|
||||
})
|
||||
// ***** END: Repository *****
|
||||
|
||||
76
conf/app.ini
76
conf/app.ini
@@ -11,12 +11,30 @@ RUN_MODE = dev
|
||||
[repository]
|
||||
ROOT =
|
||||
SCRIPT_TYPE = bash
|
||||
; Default ANSI charset
|
||||
ANSI_CHARSET =
|
||||
; Force every new repository to be private
|
||||
FORCE_PRIVATE = false
|
||||
; Patch test queue length, make it as large as possible
|
||||
PULL_REQUEST_QUEUE_LENGTH = 10000
|
||||
|
||||
[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
|
||||
|
||||
[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
|
||||
|
||||
[markdown]
|
||||
; Enable hard line break extension
|
||||
@@ -30,6 +48,8 @@ HTTP_ADDR =
|
||||
HTTP_PORT = 3000
|
||||
; Disable SSH feature when not available
|
||||
DISABLE_SSH = false
|
||||
; Whether use builtin SSH server or not.
|
||||
START_SSH_SERVER = false
|
||||
SSH_PORT = 22
|
||||
; Disable CDN even in "prod" mode
|
||||
OFFLINE_MODE = false
|
||||
@@ -61,7 +81,7 @@ USER = root
|
||||
PASSWD =
|
||||
; For "postgres" only, either "disable", "require" or "verify-full"
|
||||
SSL_MODE = disable
|
||||
; For "sqlite3" only
|
||||
; For "sqlite3" and "tidb"
|
||||
PATH = data/gogs.db
|
||||
|
||||
[admin]
|
||||
@@ -95,6 +115,18 @@ 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
|
||||
|
||||
; used to filter keys which are too short
|
||||
[service.minimum_key_sizes]
|
||||
ED25519 = 256
|
||||
ECDSA = 256
|
||||
NTRU = 1087
|
||||
MCE = 1702
|
||||
McE = 1702
|
||||
RSA = 1024
|
||||
DSA = 1024
|
||||
|
||||
[webhook]
|
||||
; Hook task queue length
|
||||
@@ -109,7 +141,7 @@ 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
|
||||
@@ -133,44 +165,6 @@ FROM =
|
||||
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
|
||||
|
||||
[cache]
|
||||
; Either "memory", "redis", or "memcache", default is "memory"
|
||||
ADAPTER = memory
|
||||
@@ -340,3 +334,5 @@ it-IT = it
|
||||
|
||||
[other]
|
||||
SHOW_FOOTER_BRANDING = false
|
||||
; Show version information about gogs and go in the footer
|
||||
SHOW_FOOTER_VERSION = true
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
Leiningen.gitignore
|
||||
@@ -1 +0,0 @@
|
||||
C++.gitignore
|
||||
@@ -1,20 +1,30 @@
|
||||
# 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>
|
||||
Akihiro YAGASAKI <yaggytter AT momiage DOT com>
|
||||
Alexander Steinhöfer <kontakt AT lx-s DOT de>
|
||||
Alexandre Magno <alexandre DOT mbm AT gmail DOT com>
|
||||
Andrey Nering <andrey AT nering DOT com DOT br>
|
||||
Arthur Aslanyan <arthur DOT e DOT aslanyan AT gmail DOT com>
|
||||
Barış Arda Yılmaz <ardayilmazgamer AT gmail DOT com>
|
||||
Christoph Kisfeld <christoph DOT kisfeld AT gmail DOT com>
|
||||
Daniel Speichert <daniel AT speichert DOT pl>
|
||||
Dmitriy Nogay <me AT catwhocode DOT ga>
|
||||
Gregor Santner <gdev AT live DOT de>
|
||||
Hamid Feizabadi <hamidfzm AT gmail DOT com>
|
||||
Huimin Wang <wanghm2009 AT hotmail DOT co DOT jp>
|
||||
ilko
|
||||
Lafriks <lafriks AT gmail DOT com>
|
||||
Lauri Ojansivu <x AT xet7 DOT org>
|
||||
Luc Stepniewski <luc AT stepniewski DOT fr>
|
||||
Marc Schiller <marc AT schiller DOT im>
|
||||
Miguel de la Cruz <miguel AT mcrx DOT me>
|
||||
Morten Sørensen <klim8d 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>
|
||||
Thomas Fanninger <gogs DOT thomas AT fanninger DOT at>
|
||||
Tilmann Bach <tilmann AT outlook DOT com>
|
||||
Vladimir Vissoultchev <wqweto AT gmail DOT com>
|
||||
YJSoft <yjsoft AT yjsoft DOT pe DOT kr>
|
||||
Łukasz Jan Niemier <lukasz AT niemier DOT pl>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -5,7 +5,6 @@ dashboard = Dashboard
|
||||
explore = Explore
|
||||
help = Help
|
||||
sign_in = Sign In
|
||||
social_sign_in = Social Sign In: 2nd Step <small>associate account</small>
|
||||
sign_out = Sign Out
|
||||
sign_up = Sign Up
|
||||
register = Register
|
||||
@@ -14,12 +13,12 @@ version = Version
|
||||
page = Page
|
||||
template = Template
|
||||
language = Language
|
||||
create_new = Create new...
|
||||
create_new = Create...
|
||||
user_profile_and_more = User profile and more
|
||||
signed_in_as = Signed in as
|
||||
|
||||
username = Username
|
||||
email = E-mail
|
||||
email = Email
|
||||
password = Password
|
||||
re_type = Re-Type
|
||||
captcha = Captcha
|
||||
@@ -54,7 +53,8 @@ code = Code
|
||||
[install]
|
||||
install = Installation
|
||||
title = Install Steps For First-time Run
|
||||
requite_db_desc = Gogs requires MySQL, PostgreSQL or SQLite3.
|
||||
docker_helper = If you're running Gogs inside Docker, please read <a target="_blank" href="%s">Guidelines</a> carefully before you change anything in this page!
|
||||
requite_db_desc = Gogs requires MySQL, PostgreSQL, SQLite3 or TiDB.
|
||||
db_title = Database Settings
|
||||
db_type = Database Type
|
||||
host = Host
|
||||
@@ -64,8 +64,11 @@ db_name = Database Name
|
||||
db_helper = Please use INNODB engine with utf8_general_ci charset for MySQL.
|
||||
ssl_mode = SSL Mode
|
||||
path = Path
|
||||
sqlite_helper = The file path of SQLite3 database.
|
||||
err_empty_sqlite_path = SQLite3 database path cannot be empty.
|
||||
sqlite_helper = The file path of SQLite3 or TiDB database.
|
||||
err_empty_db_path = SQLite3 or TiDB database path cannot be empty.
|
||||
err_invalid_tidb_name = TiDB database name does not allow characters "." and "-".
|
||||
no_admin_and_disable_registration = You cannot disable registration without creating an admin account.
|
||||
err_empty_admin_password = Admin password cannot be empty.
|
||||
|
||||
general_title = Application General Settings
|
||||
app_name = Application Name
|
||||
@@ -81,14 +84,14 @@ ssh_port_helper = Port number which your SSH server is using, leave it empty to
|
||||
http_port = HTTP Port
|
||||
http_port_helper = Port number which application will listen on.
|
||||
app_url = Application URL
|
||||
app_url_helper = This affects HTTP/HTTPS clone URL and somewhere in e-mail.
|
||||
app_url_helper = This affects HTTP/HTTPS clone URL and somewhere in email.
|
||||
|
||||
optional_title = Optional Settings
|
||||
email_title = E-mail Service Settings
|
||||
email_title = Email Service Settings
|
||||
smtp_host = SMTP Host
|
||||
smtp_from = From
|
||||
smtp_from_helper = Mail from address, RFC 5322. It can be just an email address, or the "Name" <email@example.com> format.
|
||||
mailer_user = Sender E-mail
|
||||
mailer_user = Sender Email
|
||||
mailer_password = Sender Password
|
||||
register_confirm = Enable Register Confirmation
|
||||
mail_notify = Enable Mail Notification
|
||||
@@ -99,6 +102,8 @@ disable_gravatar = Disable Gravatar Service
|
||||
disable_gravatar_popup = Disable Gravatar and custom sources, all avatars are uploaded by users or default.
|
||||
disable_registration = Disable Self-registration
|
||||
disable_registration_popup = Disable user self-registration, only admin can create accounts.
|
||||
enable_captcha = Enable Captcha
|
||||
enable_captcha_popup = Require validate captcha for user self-registration.
|
||||
require_sign_in_view = Enable Require Sign In to View Pages
|
||||
require_sign_in_view_popup = Only signed in users can view pages, visitors will only be able to see sign in/up pages.
|
||||
admin_setting_desc = You do not have to create an admin account right now, user whoever ID=1 will gain admin access automatically.
|
||||
@@ -106,7 +111,7 @@ admin_title = Admin Account Settings
|
||||
admin_name = Username
|
||||
admin_password = Password
|
||||
confirm_password = Confirm Password
|
||||
admin_email = E-mail
|
||||
admin_email = Admin Email
|
||||
install_gogs = Install Gogs
|
||||
test_git_failed = Fail to test 'git' command: %v
|
||||
sqlite3_not_available = Your release version does not support SQLite3, please download the official binary version from %s, NOT the gobuild version.
|
||||
@@ -118,7 +123,7 @@ invalid_admin_setting = Admin account setting is invalid: %v
|
||||
install_success = Welcome! We're glad that you chose Gogs, have fun and take care.
|
||||
|
||||
[home]
|
||||
uname_holder = Username or E-mail
|
||||
uname_holder = Username or email
|
||||
password_holder = Password
|
||||
switch_dashboard_context = Switch Dashboard Context
|
||||
my_repos = My Repositories
|
||||
@@ -142,19 +147,25 @@ remember_me = Remember Me
|
||||
forgot_password= Forgot Password
|
||||
forget_password = Forgot password?
|
||||
sign_up_now = Need an account? Sign up now.
|
||||
confirmation_mail_sent_prompt = A new confirmation e-mail has been sent to <b>%s</b>, please check your inbox within the next %d hours to complete the registration process.
|
||||
sign_in_email = Sign in to your e-mail
|
||||
confirmation_mail_sent_prompt = A new confirmation email has been sent to <b>%s</b>, please check your inbox within the next %d hours to complete the registration process.
|
||||
active_your_account = Activate Your Account
|
||||
resent_limit_prompt = Sorry, you already requested an activation email recently. Please wait 3 minutes then try again.
|
||||
has_unconfirmed_mail = Hi %s, you have an unconfirmed e-mail address (<b>%s</b>). If you haven't received a confirmation e-mail or need to resend a new one, please click on the button below.
|
||||
resend_mail = Click here to resend your activation e-mail
|
||||
email_not_associate = This e-mail address is not associated with any account.
|
||||
send_reset_mail = Click here to (re)send your password reset e-mail
|
||||
has_unconfirmed_mail = Hi %s, you have an unconfirmed email address (<b>%s</b>). If you haven't received a confirmation email or need to resend a new one, please click on the button below.
|
||||
resend_mail = Click here to resend your activation email
|
||||
email_not_associate = This email address is not associated with any account.
|
||||
send_reset_mail = Click here to (re)send your password reset email
|
||||
reset_password = Reset Your Password
|
||||
invalid_code = Sorry, your confirmation code has expired or not valid.
|
||||
reset_password_helper = Click here to reset your password
|
||||
password_too_short = Password length cannot be less then 6.
|
||||
|
||||
[mail]
|
||||
activate_account = Please activate your account
|
||||
activate_email = Verify your email address
|
||||
reset_password = Reset your password
|
||||
register_success = Registration successful, welcome
|
||||
register_notify = Welcome on board
|
||||
|
||||
[modal]
|
||||
yes = Yes
|
||||
no = No
|
||||
@@ -163,7 +174,7 @@ modify = Modify
|
||||
[form]
|
||||
UserName = Username
|
||||
RepoName = Repository name
|
||||
Email = E-mail address
|
||||
Email = Email address
|
||||
Password = Password
|
||||
Retype = Re-type password
|
||||
SSHTitle = SSH key name
|
||||
@@ -171,7 +182,7 @@ HttpsUrl = HTTPS URL
|
||||
PayloadUrl = Payload URL
|
||||
TeamName = Team name
|
||||
AuthName = Authorization name
|
||||
AdminEmail = Admin E-mail
|
||||
AdminEmail = Admin email
|
||||
|
||||
require_error = ` cannot be empty.`
|
||||
alpha_dash_error = ` must be valid alpha or numeric or dash(-_) characters.`
|
||||
@@ -179,17 +190,18 @@ alpha_dash_dot_error = ` must be valid alpha or numeric or dash(-_) or dot chara
|
||||
size_error = ` must be size %s.`
|
||||
min_size_error = ` must contain at least %s characters.`
|
||||
max_size_error = ` must contain at most %s characters.`
|
||||
email_error = ` is not a valid e-mail address.`
|
||||
email_error = ` is not a valid email address.`
|
||||
url_error = ` is not a valid URL.`
|
||||
include_error = ` must contain substring '%s'.`
|
||||
unknown_error = Unknown error:
|
||||
captcha_incorrect = Captcha didn't match.
|
||||
password_not_match = Password and confirm password are not same.
|
||||
|
||||
username_been_taken = Username has been already taken.
|
||||
repo_name_been_taken = Repository name has been already taken.
|
||||
org_name_been_taken = Organization name has been already taken.
|
||||
team_name_been_taken = Team name has been already taken.
|
||||
email_been_used = E-mail address has been already used.
|
||||
username_been_taken = Username has already been taken.
|
||||
repo_name_been_taken = Repository name has already been taken.
|
||||
org_name_been_taken = Organization name has already been taken.
|
||||
team_name_been_taken = Team name has already been taken.
|
||||
email_been_used = Email address has already been used.
|
||||
illegal_team_name = Team name contains illegal characters.
|
||||
username_password_incorrect = Username or password is not correct.
|
||||
enterred_invalid_repo_name = Please make sure that the repository name you entered is correct.
|
||||
@@ -206,7 +218,7 @@ still_own_repo = Your account still has ownership over at least one repository,
|
||||
still_has_org = Your account still has membership in at least one organization, you have to leave or delete your memberships first.
|
||||
org_still_own_repo = This organization still have ownership of repository, you have to delete or transfer them first.
|
||||
|
||||
still_own_user = This authentication still is in use by at least one user, please remove them from the authentication and try again.
|
||||
still_own_user = This authentication is still in use by at least one user, please remove them from the authentication and try again.
|
||||
|
||||
target_branch_not_exist = Target branch does not exist.
|
||||
|
||||
@@ -234,19 +246,19 @@ delete = Delete Account
|
||||
uid = Uid
|
||||
|
||||
public_profile = Public Profile
|
||||
profile_desc = Your E-mail address is public and will be used for any account related notifications, and any web based operations made via the site.
|
||||
profile_desc = Your email address is public and will be used for any account related notifications, and any web based operations made via the site.
|
||||
full_name = Full Name
|
||||
website = Website
|
||||
location = Location
|
||||
update_profile = Update Profile
|
||||
update_profile_success = Your profile has been updated successfully.
|
||||
change_username = Username Changed
|
||||
change_username_desc = You changed your username. This will affect the way how links relate to your account. Do you want to continue?
|
||||
change_username_prompt = This change will affect the way how links relate to your account.
|
||||
continue = Continue
|
||||
cancel = Cancel
|
||||
|
||||
enable_custom_avatar = Enable Custom Avatar
|
||||
enable_custom_avatar_helper = Enable this to disable fetch from Gravatar
|
||||
enable_custom_avatar_helper = Disable fetch from Gravatar
|
||||
choose_new_avatar = Choose new avatar
|
||||
update_avatar = Update Avatar Setting
|
||||
uploaded_avatar_not_a_image = Uploaded file is not a image.
|
||||
@@ -256,19 +268,23 @@ update_avatar_success = Your avatar setting has been updated successfully.
|
||||
change_password = Change Password
|
||||
old_password = Current Password
|
||||
new_password = New Password
|
||||
retype_new_password = Retype New Password
|
||||
password_incorrect = Current password is not correct.
|
||||
change_password_success = Your password was successfully changed. You can now sign using this new password.
|
||||
|
||||
emails = E-mail Addresses
|
||||
manage_emails = Manage e-mail addresses
|
||||
email_desc = Your primary e-mail address will be used for notifications and other operations.
|
||||
emails = Email Addresses
|
||||
manage_emails = Manage email addresses
|
||||
email_desc = Your primary email address will be used for notifications and other operations.
|
||||
primary = Primary
|
||||
primary_email = Set as primary
|
||||
delete_email = Delete
|
||||
add_new_email = Add new e-mail address
|
||||
add_email = Add e-mail
|
||||
add_email_confirmation_sent = A new confirmation e-mail has been sent to <b>%s</b>, please check your inbox within the next %d hours to complete the confirmation process.
|
||||
add_email_success = Your new E-mail address was successfully added.
|
||||
email_deletion = Email Deletion
|
||||
email_deletion_desc = Deleting this email address will remove related information from your account. Do you want to continue?
|
||||
email_deletion_success = Email has been deleted successfully!
|
||||
add_new_email = Add new email address
|
||||
add_email = Add email
|
||||
add_email_confirmation_sent = A new confirmation email has been sent to '%s', please check your inbox within the next %d hours to complete the confirmation process.
|
||||
add_email_success = Your new email address was successfully added.
|
||||
|
||||
manage_ssh_keys = Manage SSH Keys
|
||||
add_key = Add Key
|
||||
@@ -319,7 +335,9 @@ repo_name = Repository Name
|
||||
repo_name_helper = A good repository name is usually composed of short, memorable and unique keywords.
|
||||
visibility = Visibility
|
||||
visiblity_helper = This repository is <span class="ui red text">Private</span>
|
||||
visiblity_helper_forced = Site admin has forced all new repositories to be <span class="ui red text">Private</span>
|
||||
visiblity_fork_helper = (Change of this value will affect all forks)
|
||||
clone_helper = Need help cloning? Visit <a target="_blank" href="%s">Help</a>!
|
||||
fork_repo = Fork Repository
|
||||
fork_from = Fork From
|
||||
fork_visiblity_helper = You cannot alter the visibility of a forked repository.
|
||||
@@ -330,10 +348,13 @@ license = License
|
||||
license_helper = Select a license file
|
||||
readme = Readme
|
||||
readme_helper = Select a readme template
|
||||
auto_init = Initialize this repository selected files and template
|
||||
auto_init = Initialize this repository with selected files and template
|
||||
create_repo = Create Repository
|
||||
default_branch = Default Branch
|
||||
mirror_interval = Mirror Interval (hour)
|
||||
watchers = Watchers
|
||||
stargazers = Stargazers
|
||||
forks = Forks
|
||||
|
||||
form.name_reserved = Repository name '%s' is reserved.
|
||||
form.name_pattern_not_allowed = Repository name pattern '%s' is not allowed.
|
||||
@@ -344,14 +365,16 @@ migrate_type_helper = This repository will be a <span class="text blue">mirror</
|
||||
migrate_repo = Migrate Repository
|
||||
migrate.clone_address = Clone Address
|
||||
migrate.clone_address_desc = This can be a HTTP/HTTPS/GIT URL or local server path.
|
||||
migrate.permission_denied = You are not allowed to import local repositories.
|
||||
migrate.invalid_local_path = Invalid local path, it does not exist or not a directory.
|
||||
migrate.failed = Migration failed: %v
|
||||
|
||||
forked_from = forked from
|
||||
fork_from_self = You cannot fork repository you already owned!
|
||||
fork_from_self = You cannot fork a repository you already own!
|
||||
copy_link = Copy
|
||||
click_to_copy = Copy to clipboard
|
||||
copy_link_success = Copied!
|
||||
copy_link_error = Press ⌘-C or Ctrl-C to copy
|
||||
copied = Copied OK
|
||||
clone_helper = Need help cloning? Visit <a target="_blank" href="%s">Help</a>!
|
||||
unwatch = Unwatch
|
||||
watch = Watch
|
||||
unstar = Unstar
|
||||
@@ -363,10 +386,12 @@ quick_guide = Quick Guide
|
||||
clone_this_repo = Clone this repository
|
||||
create_new_repo_command = Create a new repository on the command line
|
||||
push_exist_repo = Push an existing repository from the command line
|
||||
repo_is_empty = This repository is empty, please come back later!
|
||||
|
||||
code = Code
|
||||
branch = Branch
|
||||
tree = Tree
|
||||
branch_and_tags = Branches & Tags
|
||||
filter_branch_and_tag = Filter branch or tag
|
||||
branches = Branches
|
||||
tags = Tags
|
||||
issues = Issues
|
||||
@@ -435,9 +460,9 @@ issues.num_comments = %d comments
|
||||
issues.commented_at = `commented <a id="%[1]s" href="#%[1]s">%[2]s</a>`
|
||||
issues.no_content = There is no content yet.
|
||||
issues.close_issue = Close
|
||||
issues.close_comment_issue = Close and comment
|
||||
issues.close_comment_issue = Comment and close
|
||||
issues.reopen_issue = Reopen
|
||||
issues.reopen_comment_issue = Reopen and comment
|
||||
issues.reopen_comment_issue = Comment and reopen
|
||||
issues.create_comment = Comment
|
||||
issues.closed_at = `closed <a id="%[1]s" href="#%[1]s">%[2]s</a>`
|
||||
issues.reopened_at = `reopened <a id="%[1]s" href="#%[1]s">%[2]s</a>`
|
||||
@@ -458,9 +483,10 @@ issues.label_edit = Edit
|
||||
issues.label_delete = Delete
|
||||
issues.label_modify = Label Modification
|
||||
issues.label_deletion = Label Deletion
|
||||
issues.label_deletion_desc = Delete this label will remove its information in all related issues. Do you want to continue?
|
||||
issues.label_deletion_desc = Deleting this label will remove its information in all related issues. Do you want to continue?
|
||||
issues.label_deletion_success = Label has been deleted successfully!
|
||||
|
||||
pulls.new = New Pull Request
|
||||
pulls.compare_changes = Compare Changes
|
||||
pulls.compare_changes_desc = Compare two branches and make a pull request for changes.
|
||||
pulls.compare_base = base
|
||||
@@ -479,10 +505,12 @@ pulls.reopen_to_merge = Please reopen this pull request to perform merge operati
|
||||
pulls.merged = Merged
|
||||
pulls.has_merged = This pull request has been merged successfully!
|
||||
pulls.data_broken = Data of this pull request has been broken due to deletion of fork information.
|
||||
pulls.is_checking = The conflict checking is still in progress, please refresh page in few moments.
|
||||
pulls.can_auto_merge_desc = You can perform auto-merge operation on this pull request.
|
||||
pulls.cannot_auto_merge_desc = You can't perform auto-merge operation because there are conflicts between commits.
|
||||
pulls.cannot_auto_merge_helper = Please use command line tool to solve it.
|
||||
pulls.merge_pull_request = Merge Pull Request
|
||||
pulls.open_unmerged_pull_exists = `You can't perform reopen operation because there is already an open pull request (#%d) from same repository with same merge information and is waiting for merging.`
|
||||
|
||||
milestones.new = New Milestone
|
||||
milestones.open_tab = %d Open
|
||||
@@ -497,27 +525,50 @@ milestones.title = Title
|
||||
milestones.desc = Description
|
||||
milestones.due_date = Due Date (optional)
|
||||
milestones.clear = Clear
|
||||
milestones.invalid_due_date_format = Due date format is invalid, must be 'year-mm-dd'.
|
||||
milestones.invalid_due_date_format = Due date format is invalid, must be 'yyyy-mm-dd'.
|
||||
milestones.create_success = Milestone '%s' has been created successfully!
|
||||
milestones.edit = Edit Milestone
|
||||
milestones.edit_subheader = Use better description for milestones so people won't be confused.
|
||||
milestones.edit_subheader = Use a better description for milestones so people won't be confused.
|
||||
milestones.cancel = Cancel
|
||||
milestones.modify = Modify Milestone
|
||||
milestones.edit_success = Changes of milestone '%s' has been saved successfully!
|
||||
milestones.deletion = Milestone Deletion
|
||||
milestones.deletion_desc = Delete this milestone will remove its information in all related issues. Do you want to continue?
|
||||
milestones.deletion_desc = Deleting this milestone will remove its information in all related issues. Do you want to continue?
|
||||
milestones.deletion_success = Milestone has been deleted successfully!
|
||||
|
||||
wiki = Wiki
|
||||
wiki.welcome = Welcome to Wiki!
|
||||
wiki.welcome_desc = Wiki is the place where you would like to document your project together and make it better.
|
||||
wiki.create_first_page = Create the first page
|
||||
wiki.page = Page
|
||||
wiki.filter_page = Filter page
|
||||
wiki.new_page = Create New Page
|
||||
wiki.default_commit_message = Write a note about this update (optional).
|
||||
wiki.save_page = Save Page
|
||||
wiki.last_commit_info = %s edited this page %s
|
||||
wiki.edit_page_button = Edit
|
||||
wiki.new_page_button = New Page
|
||||
wiki.page_already_exists = Wiki page with same name already exists.
|
||||
wiki.pages = Pages
|
||||
wiki.last_updated = Last updated %s
|
||||
|
||||
settings = Settings
|
||||
settings.options = Options
|
||||
settings.collaboration = Collaboration
|
||||
settings.hooks = Webhooks
|
||||
settings.githooks = Git Hooks
|
||||
settings.basic_settings = Basic Settings
|
||||
settings.danger_zone = Danger Zone
|
||||
settings.site = Official Site
|
||||
settings.update_settings = Update Settings
|
||||
settings.change_reponame_prompt = This change will affect how links relate to the repository.
|
||||
settings.advanced_settings = Advanced Settings
|
||||
settings.wiki_desc = Enable wiki to allow people write documents
|
||||
settings.issues_desc = Enable builtin lightweight issue tracker
|
||||
settings.use_external_issue_tracker = Use external issue tracker
|
||||
settings.tracker_url_format = External Issue Tracker URL Format
|
||||
settings.tracker_url_format_desc = You can use placeholder <code>{user} {repo} {index}</code> for user name, repository name and issue index.
|
||||
settings.pulls_desc = Enable pull requests to accept public contributions
|
||||
settings.danger_zone = Danger Zone
|
||||
settings.transfer = Transfer Ownership
|
||||
settings.transfer_desc = Transfer this repository to another user or to an organization in which you have admin rights.
|
||||
settings.new_owner_has_same_repo = The new owner already has a repository with same name. Please choose another name.
|
||||
@@ -531,6 +582,7 @@ settings.delete_notices_2 = - This operation will permanently delete the everyth
|
||||
settings.delete_notices_fork_1 = - If this repository is public, all forks will be became independent after deletion.
|
||||
settings.delete_notices_fork_2 = - If this repository is private, all forks will be removed at the same time.
|
||||
settings.delete_notices_fork_3 = - If you want to keep all forks after deletion, please change visibility of this repository to public first.
|
||||
settings.deletion_success = Repository has been deleted successfully!
|
||||
settings.update_settings_success = Repository options has been updated successfully.
|
||||
settings.transfer_owner = New Owner
|
||||
settings.make_transfer = Make Transfer
|
||||
@@ -539,12 +591,16 @@ settings.confirm_delete = Confirm Deletion
|
||||
settings.add_collaborator = Add New Collaborator
|
||||
settings.add_collaborator_success = New collaborator has been added.
|
||||
settings.remove_collaborator_success = Collaborator has been removed.
|
||||
settings.search_user_placeholder = Search user...
|
||||
settings.user_is_org_member = User is organization member who cannot be added as a collaborator.
|
||||
settings.add_webhook = Add Webhook
|
||||
settings.hooks_desc = Webhooks are much like basic HTTP POST event triggers. Whenever something occurs in Gogs, we will handle the notification to the target host you specify. Learn more in this <a target="_blank" href="%s">Webhooks Guide</a>.
|
||||
settings.webhook_deletion = Delete Webhook
|
||||
settings.webhook_deletion_desc = Delete this webhook will remove its information and all delivery history. Do you want to continue?
|
||||
settings.webhook_deletion_success = Webhook has been deleted successfully!
|
||||
settings.webhook.test_delivery = Test Delivery
|
||||
settings.webhook.test_delivery_desc = Send a fake push event delivery to test your webhook settings
|
||||
settings.webhook.test_delivery_success = Test webhook has been added to delivery queue. It may taks few seconds before it shows up in the delivery history.
|
||||
settings.webhook.request = Request
|
||||
settings.webhook.response = Response
|
||||
settings.webhook.headers = Headers
|
||||
@@ -584,14 +640,15 @@ settings.slack_domain = Domain
|
||||
settings.slack_channel = Channel
|
||||
settings.deploy_keys = Deploy Keys
|
||||
settings.add_deploy_key = Add Deploy Key
|
||||
settings.deploy_key_desc = Deploy key only has read-only access. It is not same as personal account SSH keys.
|
||||
settings.no_deploy_keys = You haven't added any deploy key.
|
||||
settings.title = Title
|
||||
settings.deploy_key_content = Content
|
||||
settings.key_been_used = Deploy key content has been used.
|
||||
settings.key_name_used = Deploy key with same name has already existed.
|
||||
settings.key_name_used = Deploy key with the same name already exists.
|
||||
settings.add_key_success = New deploy key '%s' has been added successfully!
|
||||
settings.deploy_key_deletion = Delete Deploy Key
|
||||
settings.deploy_key_deletion_desc = Delete this deploy key will remove all related accesses for this repository. Do you want to continue?
|
||||
settings.deploy_key_deletion_desc = Deleting this deploy key will remove all related accesses for this repository. Do you want to continue?
|
||||
settings.deploy_key_deletion_success = Deploy key has been deleted successfully!
|
||||
|
||||
diff.browse_source = Browse Source
|
||||
@@ -611,26 +668,33 @@ release.stable = Stable
|
||||
release.edit = edit
|
||||
release.ahead = <strong>%d</strong> commits to %s since this release
|
||||
release.source_code = Source Code
|
||||
release.new_subheader = Publish releases to iterate product.
|
||||
release.edit_subheader = Detailed change log can help users understand what has been improved.
|
||||
release.tag_name = Tag name
|
||||
release.target = Target
|
||||
release.tag_helper = Choose an existing tag, or create a new tag on publish.
|
||||
release.release_title = Release title
|
||||
release.content_with_md = Content with <a href="%s">Markdown</a>
|
||||
release.title = Title
|
||||
release.content = Content
|
||||
release.write = Write
|
||||
release.preview = Preview
|
||||
release.content_placeholder = Write some content
|
||||
release.loading = Loading...
|
||||
release.prerelease_desc = This is a pre-release
|
||||
release.prerelease_helper = We’ll point out that this release is not production-ready.
|
||||
release.prerelease_helper = We'll point out that this release is not production-ready.
|
||||
release.cancel = Cancel
|
||||
release.publish = Publish Release
|
||||
release.save_draft = Save Draft
|
||||
release.edit_release = Edit Release
|
||||
release.tag_name_already_exist = Release with this tag name has already existed.
|
||||
release.delete_release = Delete This Release
|
||||
release.deletion = Release Deletion
|
||||
release.deletion_desc = Deleting this release will delete the corresponding Git tag. Do you want to continue?
|
||||
release.deletion_success = Release has been deleted successfully!
|
||||
release.tag_name_already_exist = Release with this tag name already exists.
|
||||
release.downloads = Downloads
|
||||
|
||||
[org]
|
||||
org_name_holder = Organization Name
|
||||
org_full_name_holder = Organization Full Name
|
||||
org_name_helper = Great organization names are short and memorable.
|
||||
org_email_helper = Organization's E-mail receives all notifications and confirmations.
|
||||
create_org = Create Organization
|
||||
repo_updated = Updated
|
||||
people = People
|
||||
@@ -655,9 +719,9 @@ settings.full_name = Full Name
|
||||
settings.website = Website
|
||||
settings.location = Location
|
||||
settings.update_settings = Update Settings
|
||||
settings.change_orgname = Organization Name Changed
|
||||
settings.change_orgname_desc = Organization name has been changed. This will affect how links relate to the organization. Do you want to continue?
|
||||
settings.update_setting_success = Organization settings were successfully updated.
|
||||
settings.update_setting_success = Organization settings has been updated successfully.
|
||||
settings.change_orgname_prompt = This change will affect how links relate to the organization.
|
||||
settings.update_avatar_success = Organization avatar setting has been updated successfully.
|
||||
settings.delete = Delete Organization
|
||||
settings.delete_account = Delete This Organization
|
||||
settings.delete_prompt = The organization will be permanently removed, and this <strong>CANNOT</strong> be undone!
|
||||
@@ -666,16 +730,17 @@ settings.delete_org_title = Organization Deletion
|
||||
settings.delete_org_desc = This organization is going to be deleted permanently, do you want to continue?
|
||||
settings.hooks_desc = Add webhooks that will be triggered for <strong>all repositories</strong> under this organization.
|
||||
|
||||
members.membership_visibility = Membership Visibility:
|
||||
members.public = Public
|
||||
members.public_helper = make private
|
||||
members.private = Private
|
||||
members.private_helper = make public
|
||||
members.member_role = Member Role:
|
||||
members.owner = Owner
|
||||
members.member = Member
|
||||
members.conceal = Conceal
|
||||
members.remove = Remove
|
||||
members.leave = Leave
|
||||
members.invite_desc = Start typing a username to invite a new member to %s:
|
||||
members.invite_desc = Add a new member to %s:
|
||||
members.invite_now = Invite Now
|
||||
|
||||
teams.join = Join
|
||||
@@ -700,6 +765,7 @@ teams.read_permission_desc = This team grants <strong>Read</strong> access: memb
|
||||
teams.write_permission_desc = This team grants <strong>Write</strong> access: members can read from and push to the team's repositories.
|
||||
teams.admin_permission_desc = This team grants <strong>Admin</strong> access: members can read from, push to, and add collaborators to the team's repositories.
|
||||
teams.repositories = Team Repositories
|
||||
teams.search_repo_placeholder = Search repository...
|
||||
teams.add_team_repository = Add Team Repository
|
||||
teams.remove_repo = Remove
|
||||
teams.add_nonexistent_repo = The repository you're trying to add does not exist, please create it first.
|
||||
@@ -713,8 +779,9 @@ authentication = Authentications
|
||||
config = Configuration
|
||||
notices = System Notices
|
||||
monitor = Monitoring
|
||||
prev = Prev.
|
||||
next = Next
|
||||
first_page = First
|
||||
last_page = Last
|
||||
total = Total: %d
|
||||
|
||||
dashboard.statistic = Statistic
|
||||
dashboard.operations = Operations
|
||||
@@ -729,6 +796,8 @@ dashboard.delete_inactivate_accounts = Delete all inactive accounts
|
||||
dashboard.delete_inactivate_accounts_success = All inactivate accounts have been deleted successfully.
|
||||
dashboard.delete_repo_archives = Delete all repositories archives
|
||||
dashboard.delete_repo_archives_success = All repositories archives have been deleted successfully.
|
||||
dashboard.delete_missing_repos = Delete all repository records that lost Git files
|
||||
dashboard.delete_missing_repos_success = All repository records that lost Git files have been deleted successfully.
|
||||
dashboard.git_gc_repos = Do garbage collection on repositories
|
||||
dashboard.git_gc_repos_success = All repositories have done garbage collection successfully.
|
||||
dashboard.resync_all_sshkeys = Rewrite '.ssh/authorized_keys' file (caution: non-Gogs keys will be lost)
|
||||
@@ -773,19 +842,24 @@ users.activated = Activated
|
||||
users.admin = Admin
|
||||
users.repos = Repos
|
||||
users.created = Created
|
||||
users.send_register_notify = Send Registration Notification To User
|
||||
users.new_success = New account '%s' has been created successfully.
|
||||
users.edit = Edit
|
||||
users.auth_source = Authorization Source
|
||||
users.auth_source = Authentication Source
|
||||
users.local = Local
|
||||
users.auth_login_name = Authorization Login Name
|
||||
users.auth_login_name = Authentication Login Name
|
||||
users.password_helper = Leave it empty to remain unchanged.
|
||||
users.update_profile_success = Account profile has been updated successfully.
|
||||
users.edit_account = Edit Account
|
||||
users.is_activated = This account is activated
|
||||
users.is_admin = This account has administrator permissions
|
||||
users.allow_git_hook = This account has permissions to create Git hooks
|
||||
users.allow_import_local = This account has permissions to import local repositories
|
||||
users.update_profile = Update Account Profile
|
||||
users.delete_account = Delete This Account
|
||||
users.still_own_repo = This account still has ownership over at least one repository, you have to delete or transfer them first.
|
||||
users.still_has_org = This account still has membership in at least one organization, you have to leave or delete the organizations first.
|
||||
users.deletion_success = Account has been deleted successfully!
|
||||
|
||||
orgs.org_manage_panel = Organization Manage Panel
|
||||
orgs.name = Name
|
||||
@@ -800,41 +874,49 @@ repos.watches = Watches
|
||||
repos.stars = Stars
|
||||
repos.issues = Issues
|
||||
|
||||
auths.auth_manage_panel = Authorization Manage Panel
|
||||
auths.new = Add New Authorization Source
|
||||
auths.auth_manage_panel = Authentication Manage Panel
|
||||
auths.new = Add New Source
|
||||
auths.name = Name
|
||||
auths.type = Type
|
||||
auths.enabled = Enabled
|
||||
auths.updated = Updated
|
||||
auths.auth_type = Authorization Type
|
||||
auths.auth_name = Authorization Name
|
||||
auths.auth_type = Authentication Type
|
||||
auths.auth_name = Authentication Name
|
||||
auths.domain = Domain
|
||||
auths.host = Host
|
||||
auths.port = Port
|
||||
auths.bind_dn = Bind DN
|
||||
auths.bind_password = Bind Password
|
||||
auths.bind_password_helper = Warning: This password is stored in plain text. Do not use a high privileged account.
|
||||
auths.user_base = User Search Base
|
||||
auths.user_dn = User DN
|
||||
auths.attribute_username = Username attribute
|
||||
auths.attribute_username_placeholder = Leave empty to use sign-in form field value for user name.
|
||||
auths.attribute_name = First name attribute
|
||||
auths.attribute_surname = Surname attribute
|
||||
auths.attribute_mail = E-mail attribute
|
||||
auths.attribute_mail = Email attribute
|
||||
auths.filter = User Filter
|
||||
auths.admin_filter = Admin Filter
|
||||
auths.ms_ad_sa = Ms Ad SA
|
||||
auths.smtp_auth = SMTP Authorization Type
|
||||
auths.smtp_auth = SMTP Authentication Type
|
||||
auths.smtphost = SMTP Host
|
||||
auths.smtpport = SMTP Port
|
||||
auths.allowed_domains = Allowed Domains
|
||||
auths.allowed_domains_helper = Leave it empty to not restrict any domains. Multiple domains should be separated by comma ','.
|
||||
auths.enable_tls = Enable TLS Encryption
|
||||
auths.skip_tls_verify = Skip TLS Verify
|
||||
auths.pam_service_name = PAM Service Name
|
||||
auths.enable_auto_register = Enable Auto Registration
|
||||
auths.tips = Tips
|
||||
auths.edit = Edit Authorization Setting
|
||||
auths.activated = This authentication has activated
|
||||
auths.update_success = Authorization setting has been updated successfully.
|
||||
auths.update = Update Authorization Setting
|
||||
auths.delete = Delete This Authorization
|
||||
auths.delete_auth_title = Authorization Deletion
|
||||
auths.delete_auth_desc = This authorization is going to be deleted, do you want to continue?
|
||||
auths.edit = Edit Authentication Setting
|
||||
auths.activated = This authentication is activate
|
||||
auths.new_success = New authentication '%s' has been added successfully.
|
||||
auths.update_success = Authentication setting has been updated successfully.
|
||||
auths.update = Update Authentication Setting
|
||||
auths.delete = Delete This Authentication
|
||||
auths.delete_auth_title = Authentication Deletion
|
||||
auths.delete_auth_desc = This authentication is going to be deleted, do you want to continue?
|
||||
auths.deletion_success = Authentication has been deleted successfully!
|
||||
|
||||
config.server_config = Server Configuration
|
||||
config.app_name = Application Name
|
||||
@@ -858,14 +940,16 @@ config.db_user = User
|
||||
config.db_ssl_mode = SSL Mode
|
||||
config.db_ssl_mode_helper = (for "postgres" only)
|
||||
config.db_path = Path
|
||||
config.db_path_helper = (for "sqlite3" only)
|
||||
config.db_path_helper = (for "sqlite3" and "tidb")
|
||||
config.service_config = Service Configuration
|
||||
config.register_email_confirm = Require E-mail Confirmation
|
||||
config.register_email_confirm = Require Email Confirmation
|
||||
config.disable_register = Disable Registration
|
||||
config.show_registration_button = Show Register Button
|
||||
config.require_sign_in_view = Require Sign In View
|
||||
config.mail_notify = Mail Notification
|
||||
config.enable_cache_avatar = Enable Cache Avatar
|
||||
config.mail_notify = Mail Notification
|
||||
config.disable_key_size_check = Disable Minimum Key Size Check
|
||||
config.enable_captcha = Enable Captcha
|
||||
config.active_code_lives = Active Code Lives
|
||||
config.reset_password_code_lives = Reset Password Code Lives
|
||||
config.webhook_config = Webhook Configuration
|
||||
@@ -911,16 +995,23 @@ monitor.start = Start Time
|
||||
monitor.execute_time = Execution Time
|
||||
|
||||
notices.system_notice_list = System Notices
|
||||
notices.view_detail_header = View Notice Detail
|
||||
notices.actions = Actions
|
||||
notices.select_all = Select All
|
||||
notices.deselect_all = Deselect All
|
||||
notices.inverse_selection = Inverse Selection
|
||||
notices.delete_selected = Delete Selected
|
||||
notices.delete_all = Delete All Notices
|
||||
notices.type = Type
|
||||
notices.type_1 = Repository
|
||||
notices.desc = Description
|
||||
notices.op = Op.
|
||||
notices.delete_success = System notice has been deleted successfully.
|
||||
notices.delete_success = System notices have been deleted successfully.
|
||||
|
||||
[action]
|
||||
create_repo = created repository <a href="%s">%s</a>
|
||||
rename_repo = renamed repository from <code>%[1]s</code> to <a href="%[2]s">%[3]s</a>
|
||||
commit_repo = pushed to <a href="%s/src/%s">%[2]s</a> at <a href="%[1]s">%[3]s</a>
|
||||
commit_repo = pushed to <a href="%[1]s/src/%[2]s">%[3]s</a> at <a href="%[1]s">%[4]s</a>
|
||||
create_issue = `opened issue <a href="%s/issues/%s">%s#%[2]s</a>`
|
||||
create_pull_request = `created pull request <a href="%s/pulls/%s">%s#%[2]s</a>`
|
||||
comment_issue = `commented on issue <a href="%s/issues/%s">%s#%[2]s</a>`
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
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
|
||||
@@ -17,12 +17,12 @@ $ 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
|
||||
```
|
||||
|
||||
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,7 +33,17 @@ 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:
|
||||
|
||||
```
|
||||
# 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
|
||||
```
|
||||
|
||||
## Settings
|
||||
|
||||
@@ -41,23 +51,24 @@ Most of settings are obvious and easy to understand, but there are some settings
|
||||
|
||||
- **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`).
|
||||
- **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.
|
||||
- **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/`).
|
||||
- **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).
|
||||
|
||||
## Troubleshooting
|
||||
## Upgrade
|
||||
|
||||
If you see the following error:
|
||||
:exclamation::exclamation::exclamation:<span style="color: red">**Make sure you have volumed data to somewhere outside Docker container**</span>:exclamation::exclamation::exclamation:
|
||||
|
||||
```
|
||||
checkVersion()] [E] Binary and template file version does not match
|
||||
```
|
||||
Steps to upgrade Gogs with Docker:
|
||||
|
||||
Run `rm -fr /var/gogs/gogs/templates/` should fix this it. Just remember to backup templates file if you have made modifications youself.
|
||||
- `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)
|
||||
- `.dockerignore` seems to be ignored during Docker Hub Automated build
|
||||
|
||||
27
docker/build.sh
Executable file
27
docker/build.sh
Executable file
@@ -0,0 +1,27 @@
|
||||
#!/bin/sh
|
||||
set -x
|
||||
set -e
|
||||
|
||||
# Set temp environment vars
|
||||
export GOPATH=/tmp/go
|
||||
export PATH=${PATH}:${GOPATH}/bin
|
||||
|
||||
# Install build deps
|
||||
apk -U --no-progress add linux-pam-dev go@community gcc musl-dev
|
||||
|
||||
# Init go environment to build Gogs
|
||||
mkdir -p ${GOPATH}/src/github.com/gogits/
|
||||
ln -s /app/gogs/ ${GOPATH}/src/github.com/gogits/gogs
|
||||
cd ${GOPATH}/src/github.com/gogits/gogs
|
||||
go get -v -tags "sqlite redis memcache cert pam"
|
||||
go build -tags "sqlite redis memcache cert pam"
|
||||
|
||||
# Cleanup GOPATH
|
||||
rm -r $GOPATH
|
||||
|
||||
# Remove build deps
|
||||
apk --no-progress del linux-pam-dev go gcc musl-dev
|
||||
|
||||
# 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
|
||||
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_*
|
||||
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
|
||||
27
docker/s6/openssh/setup
Executable file
27
docker/s6/openssh/setup
Executable file
@@ -0,0 +1,27 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Check if host keys are present, else create them
|
||||
if ! test -f /data/ssh/ssh_host_key; then
|
||||
ssh-keygen -q -f /data/ssh/ssh_host_key -N '' -t rsa1
|
||||
fi
|
||||
|
||||
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-
|
||||
17
docker/sshd_config
Normal file
17
docker/sshd_config
Normal file
@@ -0,0 +1,17 @@
|
||||
Port 22
|
||||
AddressFamily any
|
||||
ListenAddress 0.0.0.0
|
||||
ListenAddress ::
|
||||
Protocol 2
|
||||
LogLevel INFO
|
||||
HostKey /data/ssh/ssh_host_key
|
||||
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
|
||||
@@ -1,47 +1,56 @@
|
||||
#!/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/*
|
||||
# Exec CMD or S6 by default if nothing present
|
||||
if [ $# -gt 0 ];then
|
||||
exec "$@"
|
||||
else
|
||||
exec /bin/s6-svscan /app/gogs/docker/s6/
|
||||
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
|
||||
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"
|
||||
|
||||
6
gogs.go
6
gogs.go
@@ -1,10 +1,10 @@
|
||||
// +build go1.2
|
||||
// +build go1.3
|
||||
|
||||
// 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 (Go Git Service) is a painless self-hosted Git Service.
|
||||
package main
|
||||
|
||||
import (
|
||||
@@ -17,7 +17,7 @@ import (
|
||||
"github.com/gogits/gogs/modules/setting"
|
||||
)
|
||||
|
||||
const APP_VER = "0.6.9.0903 Beta"
|
||||
const APP_VER = "0.7.33.1205 Beta"
|
||||
|
||||
func init() {
|
||||
runtime.GOMAXPROCS(runtime.NumCPU())
|
||||
|
||||
@@ -36,19 +36,19 @@ func accessLevel(e Engine, u *User, repo *Repository) (AccessMode, error) {
|
||||
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 u == nil {
|
||||
return mode, nil
|
||||
}
|
||||
|
||||
return mode, 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
|
||||
}
|
||||
|
||||
// AccessLevel returns the Access a user has to a repository. Will return NoneAccess if the
|
||||
@@ -67,9 +67,8 @@ func HasAccess(u *User, repo *Repository, testMode AccessMode) (bool, error) {
|
||||
return hasAccess(x, u, 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 {
|
||||
return nil, err
|
||||
@@ -80,7 +79,7 @@ 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
|
||||
@@ -92,11 +91,28 @@ func (u *User) GetAccessibleRepositories() (map[*Repository]AccessMode, error) {
|
||||
}
|
||||
repos[repo] = access.Mode
|
||||
}
|
||||
|
||||
// FIXME: should we generate an ordered list here? Random looks weird.
|
||||
return repos, nil
|
||||
}
|
||||
|
||||
// GetAccessibleRepositories finds all repositories where a user has access but does not own.
|
||||
func (u *User) GetAccessibleRepositories() ([]*Repository, error) {
|
||||
accesses := make([]*Access, 0, 10)
|
||||
if err := x.Find(&accesses, &Access{UserID: u.Id}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(accesses) == 0 {
|
||||
return []*Repository{}, nil
|
||||
}
|
||||
|
||||
repoIDs := make([]int64, 0, len(accesses))
|
||||
for _, access := range accesses {
|
||||
repoIDs = append(repoIDs, access.RepoID)
|
||||
}
|
||||
repos := make([]*Repository, 0, len(repoIDs))
|
||||
return repos, x.Where("owner_id != ?", u.Id).In("id", repoIDs).Desc("updated").Find(&repos)
|
||||
}
|
||||
|
||||
func maxAccessMode(modes ...AccessMode) AccessMode {
|
||||
max := ACCESS_MODE_NONE
|
||||
for _, mode := range modes {
|
||||
|
||||
126
models/action.go
126
models/action.go
@@ -14,6 +14,7 @@ import (
|
||||
"time"
|
||||
"unicode"
|
||||
|
||||
"github.com/Unknwon/com"
|
||||
"github.com/go-xorm/xorm"
|
||||
|
||||
api "github.com/gogits/go-gogs-client"
|
||||
@@ -136,6 +137,26 @@ 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.Name
|
||||
}
|
||||
|
||||
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,
|
||||
@@ -147,7 +168,7 @@ func newRepoAction(e Engine, u *User, repo *Repository) (err error) {
|
||||
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)
|
||||
@@ -187,9 +208,52 @@ func issueIndexTrimRight(c rune) bool {
|
||||
return !unicode.IsDigit(c)
|
||||
}
|
||||
|
||||
type PushCommit struct {
|
||||
Sha1 string
|
||||
Message string
|
||||
AuthorEmail string
|
||||
AuthorName string
|
||||
}
|
||||
|
||||
type PushCommits struct {
|
||||
Len int
|
||||
Commits []*PushCommit
|
||||
CompareUrl string
|
||||
|
||||
avatars map[string]string
|
||||
}
|
||||
|
||||
func NewPushCommits() *PushCommits {
|
||||
return &PushCommits{
|
||||
avatars: make(map[string]string),
|
||||
}
|
||||
}
|
||||
|
||||
// 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.AvatarLink()
|
||||
}
|
||||
}
|
||||
|
||||
return push.avatars[email]
|
||||
}
|
||||
|
||||
// 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 {
|
||||
func updateIssuesCommit(u *User, repo *Repository, repoUserName, repoName string, 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:]
|
||||
@@ -210,6 +274,9 @@ func updateIssuesCommit(u *User, repo *Repository, repoUserName, repoName string
|
||||
|
||||
issue, err := GetIssueByRef(ref)
|
||||
if err != nil {
|
||||
if IsErrIssueNotExist(err) {
|
||||
continue
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -220,7 +287,7 @@ func updateIssuesCommit(u *User, repo *Repository, repoUserName, repoName string
|
||||
|
||||
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 {
|
||||
if err = CreateRefComment(u, repo, issue, message, c.Sha1); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -246,6 +313,9 @@ func updateIssuesCommit(u *User, repo *Repository, repoUserName, repoName string
|
||||
|
||||
issue, err := GetIssueByRef(ref)
|
||||
if err != nil {
|
||||
if IsErrIssueNotExist(err) {
|
||||
continue
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -283,6 +353,9 @@ func updateIssuesCommit(u *User, repo *Repository, repoUserName, repoName string
|
||||
|
||||
issue, err := GetIssueByRef(ref)
|
||||
if err != nil {
|
||||
if IsErrIssueNotExist(err) {
|
||||
continue
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -310,7 +383,7 @@ func CommitRepoAction(
|
||||
repoID int64,
|
||||
repoUserName, repoName string,
|
||||
refFullName string,
|
||||
commit *base.PushCommits,
|
||||
commit *PushCommits,
|
||||
oldCommitID string, newCommitID string) error {
|
||||
|
||||
u, err := GetUserByID(userID)
|
||||
@@ -325,12 +398,18 @@ func CommitRepoAction(
|
||||
return fmt.Errorf("GetOwner: %v", 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
|
||||
// Check it's tag push or branch.
|
||||
if strings.HasPrefix(refFullName, "refs/tags/") {
|
||||
opType = PUSH_TAG
|
||||
commit = &base.PushCommits{}
|
||||
commit = &PushCommits{}
|
||||
} else {
|
||||
// if not the first commit, set the compareUrl
|
||||
if !strings.HasPrefix(oldCommitID, "0000000") {
|
||||
@@ -339,17 +418,19 @@ func CommitRepoAction(
|
||||
isNewBranch = true
|
||||
}
|
||||
|
||||
// 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)
|
||||
// NOTE: limit to detect latest 100 commits.
|
||||
if len(commit.Commits) > 100 {
|
||||
commit.Commits = commit.Commits[len(commit.Commits)-100:]
|
||||
}
|
||||
|
||||
if err = updateIssuesCommit(u, repo, repoUserName, repoName, commit.Commits); err != nil {
|
||||
log.Debug("updateIssuesCommit: %v", err)
|
||||
log.Error(4, "updateIssuesCommit: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if len(commit.Commits) > setting.FeedMaxCommitNum {
|
||||
commit.Commits = commit.Commits[:setting.FeedMaxCommitNum]
|
||||
}
|
||||
|
||||
bs, err := json.Marshal(commit)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Marshal: %v", err)
|
||||
@@ -370,24 +451,9 @@ func CommitRepoAction(
|
||||
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,
|
||||
}
|
||||
payloadRepo := repo.ComposePayload()
|
||||
|
||||
pusher_email, pusher_name := "", ""
|
||||
pusher, err := GetUserByName(userName)
|
||||
@@ -413,7 +479,7 @@ func CommitRepoAction(
|
||||
commits[i] = &api.PayloadCommit{
|
||||
ID: cmt.Sha1,
|
||||
Message: cmt.Message,
|
||||
URL: fmt.Sprintf("%s/commit/%s", repoLink, cmt.Sha1),
|
||||
URL: fmt.Sprintf("%s/commit/%s", repo.RepoLink(), cmt.Sha1),
|
||||
Author: &api.PayloadAuthor{
|
||||
Name: cmt.AuthorName,
|
||||
Email: cmt.AuthorEmail,
|
||||
@@ -472,7 +538,7 @@ func transferRepoAction(e Engine, actUser, oldOwner, newOwner *User, repo *Repos
|
||||
IsPrivate: repo.IsPrivate,
|
||||
Content: path.Join(oldOwner.LowerName, repo.LowerName),
|
||||
}); err != nil {
|
||||
return fmt.Errorf("notify watchers '%d/%s': %v", actUser.Id, repo.ID, err)
|
||||
return fmt.Errorf("notify watchers '%d/%d': %v", actUser.Id, repo.ID, err)
|
||||
}
|
||||
|
||||
// Remove watch for organization.
|
||||
|
||||
@@ -5,9 +5,12 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Unknwon/com"
|
||||
|
||||
"github.com/gogits/gogs/modules/base"
|
||||
)
|
||||
|
||||
type NoticeType int
|
||||
@@ -18,7 +21,7 @@ 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"`
|
||||
@@ -50,11 +53,10 @@ func CountNotices() int64 {
|
||||
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 +64,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
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ import (
|
||||
|
||||
var c = cron.New()
|
||||
|
||||
func NewCronContext() {
|
||||
func NewContext() {
|
||||
var (
|
||||
entry *cron.Entry
|
||||
err error
|
||||
|
||||
174
models/error.go
174
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,27 @@ 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 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 +134,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 +157,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 +171,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 +185,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 +230,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 +244,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 +264,7 @@ 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)
|
||||
}
|
||||
|
||||
// ________ .__ __ .__
|
||||
@@ -220,7 +284,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 +323,62 @@ 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)
|
||||
}
|
||||
|
||||
// __ __ ___. .__ __
|
||||
// / \ / \ ____\_ |__ | |__ ____ ____ | | __
|
||||
// \ \/\/ // __ \| __ \| | \ / _ \ / _ \| |/ /
|
||||
@@ -310,7 +430,7 @@ func (err ErrIssueNotExist) Error() string {
|
||||
|
||||
type ErrPullRequestNotExist struct {
|
||||
ID int64
|
||||
PullID int64
|
||||
IssueID int64
|
||||
HeadRepoID int64
|
||||
BaseRepoID int64
|
||||
HeadBarcnh string
|
||||
@@ -323,8 +443,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)
|
||||
}
|
||||
|
||||
// _________ __
|
||||
@@ -408,3 +528,23 @@ 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)
|
||||
}
|
||||
|
||||
// _____ __ .__ __ .__ __ .__
|
||||
// / _ \ __ ___/ |_| |__ ____ _____/ |_|__| ____ _____ _/ |_|__| ____ ____
|
||||
// / /_\ \| | \ __\ | \_/ __ \ / \ __\ |/ ___\\__ \\ __\ |/ _ \ / \
|
||||
// / | \ | /| | | Y \ ___/| | \ | | \ \___ / __ \| | | ( <_> ) | \
|
||||
// \____|__ /____/ |__| |___| /\___ >___| /__| |__|\___ >____ /__| |__|\____/|___| /
|
||||
// \/ \/ \/ \/ \/ \/ \/
|
||||
|
||||
type ErrAuthenticationNotExist struct {
|
||||
ID int64
|
||||
}
|
||||
|
||||
func IsErrAuthenticationNotExist(err error) bool {
|
||||
_, ok := err.(ErrAuthenticationNotExist)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrAuthenticationNotExist) Error() string {
|
||||
return fmt.Sprintf("Authentication does not exist [id: %d]", err.ID)
|
||||
}
|
||||
|
||||
@@ -12,15 +12,14 @@ import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Unknwon/com"
|
||||
"golang.org/x/net/html/charset"
|
||||
"golang.org/x/text/transform"
|
||||
|
||||
"github.com/Unknwon/com"
|
||||
"github.com/gogits/git-shell"
|
||||
|
||||
"github.com/gogits/gogs/modules/base"
|
||||
"github.com/gogits/gogs/modules/git"
|
||||
"github.com/gogits/gogs/modules/log"
|
||||
"github.com/gogits/gogs/modules/process"
|
||||
)
|
||||
@@ -37,6 +36,7 @@ const (
|
||||
DIFF_FILE_ADD = iota + 1
|
||||
DIFF_FILE_CHANGE
|
||||
DIFF_FILE_DEL
|
||||
DIFF_FILE_RENAME
|
||||
)
|
||||
|
||||
type DiffLine struct {
|
||||
@@ -57,12 +57,14 @@ type DiffSection struct {
|
||||
|
||||
type DiffFile struct {
|
||||
Name string
|
||||
OldName string
|
||||
Index int
|
||||
Addition, Deletion int
|
||||
Type int
|
||||
IsCreated bool
|
||||
IsDeleted bool
|
||||
IsBin bool
|
||||
IsRenamed bool
|
||||
Sections []*DiffSection
|
||||
}
|
||||
|
||||
@@ -77,39 +79,53 @@ 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)
|
||||
func ParsePatch(maxlines 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
|
||||
)
|
||||
|
||||
diff := &Diff{Files: make([]*DiffFile, 0)}
|
||||
var i int
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
// fmt.Println(i, line)
|
||||
input := bufio.NewReader(reader)
|
||||
isEOF := false
|
||||
for {
|
||||
if isEOF {
|
||||
break
|
||||
}
|
||||
|
||||
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, "--- ") {
|
||||
continue
|
||||
}
|
||||
|
||||
if line == "" {
|
||||
} else if len(line) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
i = i + 1
|
||||
lineCount++
|
||||
|
||||
// Diff data too large, we only show the first about maxlines lines
|
||||
if i == maxlines {
|
||||
isTooLong = true
|
||||
if lineCount >= maxlines {
|
||||
log.Warn("Diff data too large")
|
||||
diff.Files = nil
|
||||
return diff, nil
|
||||
}
|
||||
|
||||
switch {
|
||||
@@ -120,10 +136,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,21 +174,27 @@ 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:],
|
||||
Name: a,
|
||||
Index: len(diff.Files) + 1,
|
||||
Type: DIFF_FILE_CHANGE,
|
||||
Sections: make([]*DiffSection, 0, 10),
|
||||
@@ -184,20 +202,30 @@ func ParsePatch(pid int64, maxlines int, cmd *exec.Cmd, reader io.Reader) (*Diff
|
||||
diff.Files = append(diff.Files, curFile)
|
||||
|
||||
// Check file diff type.
|
||||
for scanner.Scan() {
|
||||
for {
|
||||
line, err := input.ReadString('\n')
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
isEOF = true
|
||||
} else {
|
||||
return nil, fmt.Errorf("ReadString: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
switch {
|
||||
case strings.HasPrefix(scanner.Text(), "new file"):
|
||||
case strings.HasPrefix(line, "new file"):
|
||||
curFile.Type = DIFF_FILE_ADD
|
||||
curFile.IsDeleted = false
|
||||
curFile.IsCreated = true
|
||||
case strings.HasPrefix(scanner.Text(), "deleted"):
|
||||
case strings.HasPrefix(line, "deleted"):
|
||||
curFile.Type = DIFF_FILE_DEL
|
||||
curFile.IsCreated = false
|
||||
curFile.IsDeleted = true
|
||||
case strings.HasPrefix(scanner.Text(), "index"):
|
||||
case strings.HasPrefix(line, "index"):
|
||||
curFile.Type = DIFF_FILE_CHANGE
|
||||
curFile.IsCreated = false
|
||||
curFile.IsDeleted = false
|
||||
case strings.HasPrefix(line, "similarity index 100%"):
|
||||
curFile.Type = DIFF_FILE_RENAME
|
||||
curFile.IsRenamed = true
|
||||
curFile.OldName = curFile.Name
|
||||
curFile.Name = b
|
||||
}
|
||||
if curFile.Type > 0 {
|
||||
break
|
||||
@@ -206,6 +234,8 @@ func ParsePatch(pid int64, maxlines int, cmd *exec.Cmd, reader io.Reader) (*Diff
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: detect encoding while parsing.
|
||||
var buf bytes.Buffer
|
||||
for _, f := range diff.Files {
|
||||
buf.Reset()
|
||||
for _, sec := range f.Sections {
|
||||
@@ -232,61 +262,55 @@ 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) {
|
||||
func GetDiffRange(repoPath, beforeCommitID string, afterCommitID string, maxlines int) (*Diff, error) {
|
||||
repo, err := git.OpenRepository(repoPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
commit, err := repo.GetCommit(afterCommitId)
|
||||
commit, err := repo.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", afterCommitID)
|
||||
} else {
|
||||
c, _ := commit.Parent(0)
|
||||
cmd = exec.Command("git", "diff", c.Id.String(), afterCommitId)
|
||||
cmd = exec.Command("git", "diff", "-M", c.ID.String(), afterCommitID)
|
||||
}
|
||||
} else {
|
||||
cmd = exec.Command("git", "diff", beforeCommitId, afterCommitId)
|
||||
cmd = exec.Command("git", "diff", "-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)
|
||||
}
|
||||
|
||||
return ParsePatch(pid, maxlines, cmd, rd)
|
||||
pid := process.Add(fmt.Sprintf("GetDiffRange (%s)", repoPath), cmd)
|
||||
defer process.Remove(pid)
|
||||
|
||||
diff, err := ParsePatch(maxlines, 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
|
||||
}
|
||||
|
||||
func GetDiffCommit(repoPath, commitId string, maxlines int) (*Diff, error) {
|
||||
|
||||
336
models/issue.go
336
models/issue.go
@@ -9,7 +9,6 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"mime/multipart"
|
||||
"os"
|
||||
"path"
|
||||
@@ -20,9 +19,7 @@ import (
|
||||
"github.com/go-xorm/xorm"
|
||||
|
||||
"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"
|
||||
gouuid "github.com/gogits/gogs/modules/uuid"
|
||||
)
|
||||
@@ -95,15 +92,6 @@ func (i *Issue) AfterSet(colName string, _ xorm.Cell) {
|
||||
if err != nil {
|
||||
log.Error(3, "GetUserByID[%d]: %v", i.ID, err)
|
||||
}
|
||||
case "is_pull":
|
||||
if !i.IsPull {
|
||||
return
|
||||
}
|
||||
|
||||
i.PullRequest, err = GetPullRequestByPullID(i.ID)
|
||||
if err != nil {
|
||||
log.Error(3, "GetPullRequestByPullID[%d]: %v", i.ID, err)
|
||||
}
|
||||
case "created":
|
||||
i.Created = regulateTimeZone(i.Created)
|
||||
}
|
||||
@@ -236,7 +224,7 @@ func (i *Issue) changeStatus(e *xorm.Session, doer *User, isClosed bool) (err er
|
||||
}
|
||||
i.IsClosed = isClosed
|
||||
|
||||
if err = updateIssue(e, i); err != nil {
|
||||
if err = updateIssueCols(e, i, "is_closed"); err != nil {
|
||||
return err
|
||||
} else if err = updateIssueUsersByStatus(e, i.ID, isClosed); err != nil {
|
||||
return err
|
||||
@@ -285,6 +273,15 @@ func (i *Issue) ChangeStatus(doer *User, isClosed bool) (err error) {
|
||||
return sess.Commit()
|
||||
}
|
||||
|
||||
func (i *Issue) GetPullRequest() (err error) {
|
||||
if i.PullRequest != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
i.PullRequest, err = GetPullRequestByIssueID(i.ID)
|
||||
return err
|
||||
}
|
||||
|
||||
// It's caller's responsibility to create action.
|
||||
func newIssue(e *xorm.Session, repo *Repository, issue *Issue, labelIDs []int64, uuids []string, isPull bool) (err error) {
|
||||
if _, err = e.Insert(issue); err != nil {
|
||||
@@ -546,7 +543,7 @@ func GetIssueCountByPoster(uid, rid int64, isClosed bool) int64 {
|
||||
// IssueUser represents an issue-user relation.
|
||||
type IssueUser struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
UID int64 `xorm:"uid INDEX"` // User ID.
|
||||
UID int64 `xorm:"INDEX"` // User ID.
|
||||
IssueID int64
|
||||
RepoID int64 `xorm:"INDEX"`
|
||||
MilestoneID int64
|
||||
@@ -721,32 +718,28 @@ func GetIssueStats(opts *IssueStatsOptions) *IssueStats {
|
||||
if opts.AssigneeID > 0 {
|
||||
baseCond += " AND assignee_id=" + com.ToStr(opts.AssigneeID)
|
||||
}
|
||||
if opts.IsPull {
|
||||
baseCond += " AND issue.is_pull=1"
|
||||
} else {
|
||||
baseCond += " AND issue.is_pull=0"
|
||||
}
|
||||
baseCond += " AND issue.is_pull=?"
|
||||
|
||||
switch opts.FilterMode {
|
||||
case FM_ALL, FM_ASSIGN:
|
||||
results, _ := x.Query(queryStr+baseCond, false)
|
||||
results, _ := x.Query(queryStr+baseCond, false, opts.IsPull)
|
||||
stats.OpenCount = parseCountResult(results)
|
||||
results, _ = x.Query(queryStr+baseCond, true)
|
||||
results, _ = x.Query(queryStr+baseCond, true, opts.IsPull)
|
||||
stats.ClosedCount = parseCountResult(results)
|
||||
|
||||
case FM_CREATE:
|
||||
baseCond += " AND poster_id=?"
|
||||
results, _ := x.Query(queryStr+baseCond, false, opts.UserID)
|
||||
results, _ := x.Query(queryStr+baseCond, false, opts.IsPull, opts.UserID)
|
||||
stats.OpenCount = parseCountResult(results)
|
||||
results, _ = x.Query(queryStr+baseCond, true, opts.UserID)
|
||||
results, _ = x.Query(queryStr+baseCond, true, opts.IsPull, opts.UserID)
|
||||
stats.ClosedCount = parseCountResult(results)
|
||||
|
||||
case FM_MENTION:
|
||||
queryStr += " INNER JOIN `issue_user` ON `issue`.id=`issue_user`.issue_id"
|
||||
baseCond += " AND `issue_user`.uid=? AND `issue_user`.is_mentioned=?"
|
||||
results, _ := x.Query(queryStr+baseCond, false, opts.UserID, true)
|
||||
results, _ := x.Query(queryStr+baseCond, false, opts.IsPull, opts.UserID, true)
|
||||
stats.OpenCount = parseCountResult(results)
|
||||
results, _ = x.Query(queryStr+baseCond, true, opts.UserID, true)
|
||||
results, _ = x.Query(queryStr+baseCond, true, opts.IsPull, opts.UserID, true)
|
||||
stats.ClosedCount = parseCountResult(results)
|
||||
}
|
||||
return stats
|
||||
@@ -758,7 +751,7 @@ func GetUserIssueStats(repoID, uid int64, repoIDs []int64, filterMode int, isPul
|
||||
|
||||
queryStr := "SELECT COUNT(*) FROM `issue` "
|
||||
baseCond := " WHERE issue.is_closed=?"
|
||||
if repoID > 0 {
|
||||
if repoID > 0 || len(repoIDs) == 0 {
|
||||
baseCond += " AND issue.repo_id=" + com.ToStr(repoID)
|
||||
} else {
|
||||
baseCond += " AND issue.repo_id IN (" + strings.Join(base.Int64sToStrings(repoIDs), ",") + ")"
|
||||
@@ -821,11 +814,17 @@ func updateIssue(e Engine, issue *Issue) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// UpdateIssue updates information of issue.
|
||||
// UpdateIssue updates all fields of given issue.
|
||||
func UpdateIssue(issue *Issue) error {
|
||||
return updateIssue(x, issue)
|
||||
}
|
||||
|
||||
// updateIssueCols updates specific fields of given issue.
|
||||
func updateIssueCols(e Engine, issue *Issue, cols ...string) error {
|
||||
_, err := e.Id(issue.ID).Cols(cols...).Update(issue)
|
||||
return err
|
||||
}
|
||||
|
||||
func updateIssueUsersByStatus(e Engine, issueID int64, isClosed bool) error {
|
||||
_, err := e.Exec("UPDATE `issue_user` SET is_closed=? WHERE issue_id=?", isClosed, issueID)
|
||||
return err
|
||||
@@ -894,233 +893,6 @@ func UpdateIssueUsersByMentions(uids []int64, iid int64) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// __________ .__ .__ __________ __
|
||||
// \______ \__ __| | | |\______ \ ____ ________ __ ____ _______/ |_
|
||||
// | ___/ | \ | | | | _// __ \/ ____/ | \_/ __ \ / ___/\ __\
|
||||
// | | | | / |_| |_| | \ ___< <_| | | /\ ___/ \___ \ | |
|
||||
// |____| |____/|____/____/____|_ /\___ >__ |____/ \___ >____ > |__|
|
||||
// \/ \/ |__| \/ \/
|
||||
|
||||
type PullRequestType int
|
||||
|
||||
const (
|
||||
PULL_REQUEST_GOGS = iota
|
||||
PLLL_ERQUEST_GIT
|
||||
)
|
||||
|
||||
// PullRequest represents relation between pull request and repositories.
|
||||
type PullRequest struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
PullID int64 `xorm:"INDEX"`
|
||||
Pull *Issue `xorm:"-"`
|
||||
PullIndex int64
|
||||
HeadRepoID int64
|
||||
HeadRepo *Repository `xorm:"-"`
|
||||
BaseRepoID int64
|
||||
HeadUserName string
|
||||
HeadBarcnh string
|
||||
BaseBranch string
|
||||
MergeBase string `xorm:"VARCHAR(40)"`
|
||||
MergedCommitID string `xorm:"VARCHAR(40)"`
|
||||
Type PullRequestType
|
||||
CanAutoMerge bool
|
||||
HasMerged bool
|
||||
Merged time.Time
|
||||
MergerID int64
|
||||
Merger *User `xorm:"-"`
|
||||
}
|
||||
|
||||
func (pr *PullRequest) AfterSet(colName string, _ xorm.Cell) {
|
||||
var err error
|
||||
switch colName {
|
||||
case "head_repo_id":
|
||||
pr.HeadRepo, err = GetRepositoryByID(pr.HeadRepoID)
|
||||
if err != nil {
|
||||
log.Error(3, "GetRepositoryByID[%d]: %v", pr.ID, err)
|
||||
}
|
||||
case "merger_id":
|
||||
if !pr.HasMerged {
|
||||
return
|
||||
}
|
||||
|
||||
pr.Merger, err = GetUserByID(pr.MergerID)
|
||||
if err != nil {
|
||||
if IsErrUserNotExist(err) {
|
||||
pr.MergerID = -1
|
||||
pr.Merger = NewFakeUser()
|
||||
} else {
|
||||
log.Error(3, "GetUserByID[%d]: %v", pr.ID, err)
|
||||
}
|
||||
}
|
||||
case "merged":
|
||||
if !pr.HasMerged {
|
||||
return
|
||||
}
|
||||
|
||||
pr.Merged = regulateTimeZone(pr.Merged)
|
||||
}
|
||||
}
|
||||
|
||||
// Merge merges pull request to base repository.
|
||||
func (pr *PullRequest) Merge(doer *User, baseGitRepo *git.Repository) (err error) {
|
||||
sess := x.NewSession()
|
||||
defer sessionRelease(sess)
|
||||
if err = sess.Begin(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = pr.Pull.changeStatus(sess, doer, true); err != nil {
|
||||
return fmt.Errorf("Pull.changeStatus: %v", err)
|
||||
}
|
||||
|
||||
headRepoPath := RepoPath(pr.HeadUserName, pr.HeadRepo.Name)
|
||||
headGitRepo, err := git.OpenRepository(headRepoPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("OpenRepository: %v", err)
|
||||
}
|
||||
pr.MergedCommitID, err = headGitRepo.GetCommitIdOfBranch(pr.HeadBarcnh)
|
||||
if err != nil {
|
||||
return fmt.Errorf("GetCommitIdOfBranch: %v", err)
|
||||
}
|
||||
|
||||
if err = mergePullRequestAction(sess, doer, pr.Pull.Repo, pr.Pull); err != nil {
|
||||
return fmt.Errorf("mergePullRequestAction: %v", err)
|
||||
}
|
||||
|
||||
pr.HasMerged = true
|
||||
pr.Merged = time.Now()
|
||||
pr.MergerID = doer.Id
|
||||
if _, err = sess.Id(pr.ID).AllCols().Update(pr); err != nil {
|
||||
return fmt.Errorf("update pull request: %v", err)
|
||||
}
|
||||
|
||||
// Clone base repo.
|
||||
tmpBasePath := path.Join("data/tmp/repos", com.ToStr(time.Now().Nanosecond())+".git")
|
||||
os.MkdirAll(path.Dir(tmpBasePath), os.ModePerm)
|
||||
defer os.RemoveAll(path.Dir(tmpBasePath))
|
||||
|
||||
var stderr string
|
||||
if _, stderr, err = process.ExecTimeout(5*time.Minute,
|
||||
fmt.Sprintf("PullRequest.Merge(git clone): %s", tmpBasePath),
|
||||
"git", "clone", baseGitRepo.Path, tmpBasePath); err != nil {
|
||||
return fmt.Errorf("git clone: %s", stderr)
|
||||
}
|
||||
|
||||
// Check out base branch.
|
||||
if _, stderr, err = process.ExecDir(-1, tmpBasePath,
|
||||
fmt.Sprintf("PullRequest.Merge(git checkout): %s", tmpBasePath),
|
||||
"git", "checkout", pr.BaseBranch); err != nil {
|
||||
return fmt.Errorf("git checkout: %s", stderr)
|
||||
}
|
||||
|
||||
// Pull commits.
|
||||
if _, stderr, err = process.ExecDir(-1, tmpBasePath,
|
||||
fmt.Sprintf("PullRequest.Merge(git pull): %s", tmpBasePath),
|
||||
"git", "pull", headRepoPath, pr.HeadBarcnh); err != nil {
|
||||
return fmt.Errorf("git pull: %s", stderr)
|
||||
}
|
||||
|
||||
// Push back to upstream.
|
||||
if _, stderr, err = process.ExecDir(-1, tmpBasePath,
|
||||
fmt.Sprintf("PullRequest.Merge(git push): %s", tmpBasePath),
|
||||
"git", "push", baseGitRepo.Path, pr.BaseBranch); err != nil {
|
||||
return fmt.Errorf("git push: %s", stderr)
|
||||
}
|
||||
|
||||
return sess.Commit()
|
||||
}
|
||||
|
||||
// NewPullRequest creates new pull request with labels for repository.
|
||||
func NewPullRequest(repo *Repository, pull *Issue, labelIDs []int64, uuids []string, pr *PullRequest, patch []byte) (err error) {
|
||||
sess := x.NewSession()
|
||||
defer sessionRelease(sess)
|
||||
if err = sess.Begin(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = newIssue(sess, repo, pull, labelIDs, uuids, true); err != nil {
|
||||
return fmt.Errorf("newIssue: %v", err)
|
||||
}
|
||||
|
||||
// Notify watchers.
|
||||
act := &Action{
|
||||
ActUserID: pull.Poster.Id,
|
||||
ActUserName: pull.Poster.Name,
|
||||
ActEmail: pull.Poster.Email,
|
||||
OpType: CREATE_PULL_REQUEST,
|
||||
Content: fmt.Sprintf("%d|%s", pull.Index, pull.Name),
|
||||
RepoID: repo.ID,
|
||||
RepoUserName: repo.Owner.Name,
|
||||
RepoName: repo.Name,
|
||||
IsPrivate: repo.IsPrivate,
|
||||
}
|
||||
if err = notifyWatchers(sess, act); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Test apply patch.
|
||||
repoPath, err := repo.RepoPath()
|
||||
if err != nil {
|
||||
return fmt.Errorf("RepoPath: %v", err)
|
||||
}
|
||||
patchPath := path.Join(repoPath, "pulls", com.ToStr(pr.ID)+".patch")
|
||||
|
||||
os.MkdirAll(path.Dir(patchPath), os.ModePerm)
|
||||
if err = ioutil.WriteFile(patchPath, patch, 0644); err != nil {
|
||||
return fmt.Errorf("save patch: %v", err)
|
||||
}
|
||||
defer os.Remove(patchPath)
|
||||
|
||||
stdout, stderr, err := process.ExecDir(-1, repoPath,
|
||||
fmt.Sprintf("NewPullRequest(git apply --check): %d", repo.ID),
|
||||
"git", "apply", "--check", "-v", patchPath)
|
||||
if err != nil {
|
||||
if strings.Contains(stderr, "fatal:") {
|
||||
return fmt.Errorf("git apply --check: %v - %s", err, stderr)
|
||||
}
|
||||
}
|
||||
pr.CanAutoMerge = !strings.Contains(stdout, "error: patch failed:")
|
||||
|
||||
pr.PullID = pull.ID
|
||||
pr.PullIndex = pull.Index
|
||||
if _, err = sess.Insert(pr); err != nil {
|
||||
return fmt.Errorf("insert pull repo: %v", err)
|
||||
}
|
||||
|
||||
return sess.Commit()
|
||||
}
|
||||
|
||||
// GetUnmergedPullRequest returnss a pull request hasn't been merged by given info.
|
||||
func GetUnmergedPullRequest(headRepoID, baseRepoID int64, headBranch, baseBranch string) (*PullRequest, error) {
|
||||
pr := &PullRequest{
|
||||
HeadRepoID: headRepoID,
|
||||
BaseRepoID: baseRepoID,
|
||||
HeadBarcnh: headBranch,
|
||||
BaseBranch: baseBranch,
|
||||
}
|
||||
|
||||
has, err := x.Where("has_merged=?", false).Get(pr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !has {
|
||||
return nil, ErrPullRequestNotExist{0, 0, headRepoID, baseRepoID, headBranch, baseBranch}
|
||||
}
|
||||
|
||||
return pr, nil
|
||||
}
|
||||
|
||||
// GetPullRequestByPullID returns pull repo by given pull ID.
|
||||
func GetPullRequestByPullID(pullID int64) (*PullRequest, error) {
|
||||
pr := new(PullRequest)
|
||||
has, err := x.Where("pull_id=?", pullID).Get(pr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !has {
|
||||
return nil, ErrPullRequestNotExist{0, pullID, 0, 0, "", ""}
|
||||
}
|
||||
return pr, nil
|
||||
}
|
||||
|
||||
// .____ ___. .__
|
||||
// | | _____ \_ |__ ____ | |
|
||||
// | | \__ \ | __ \_/ __ \| |
|
||||
@@ -1525,7 +1297,7 @@ func changeMilestoneIssueStats(e *xorm.Session, issue *Issue) error {
|
||||
}
|
||||
|
||||
// ChangeMilestoneIssueStats updates the open/closed issues counter and progress
|
||||
// for the milestone associated witht the given issue.
|
||||
// for the milestone associated with the given issue.
|
||||
func ChangeMilestoneIssueStats(issue *Issue) (err error) {
|
||||
sess := x.NewSession()
|
||||
defer sessionRelease(sess)
|
||||
@@ -1599,8 +1371,8 @@ func ChangeMilestoneAssign(oldMid int64, issue *Issue) (err error) {
|
||||
}
|
||||
|
||||
// DeleteMilestoneByID deletes a milestone by given ID.
|
||||
func DeleteMilestoneByID(mid int64) error {
|
||||
m, err := GetMilestoneByID(mid)
|
||||
func DeleteMilestoneByID(id int64) error {
|
||||
m, err := GetMilestoneByID(id)
|
||||
if err != nil {
|
||||
if IsErrMilestoneNotExist(err) {
|
||||
return nil
|
||||
@@ -1619,7 +1391,7 @@ func DeleteMilestoneByID(mid int64) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err = sess.Id(m.ID).Delete(m); err != nil {
|
||||
if _, err = sess.Id(m.ID).Delete(new(Milestone)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -1685,6 +1457,9 @@ type Comment struct {
|
||||
RenderedContent string `xorm:"-"`
|
||||
Created time.Time `xorm:"CREATED"`
|
||||
|
||||
// Reference issue in commit message
|
||||
CommitSHA string `xorm:"VARCHAR(40)"`
|
||||
|
||||
Attachments []*Attachment `xorm:"-"`
|
||||
|
||||
// For view issue page.
|
||||
@@ -1733,14 +1508,15 @@ func (c *Comment) EventTag() string {
|
||||
return "event-" + com.ToStr(c.ID)
|
||||
}
|
||||
|
||||
func createComment(e *xorm.Session, u *User, repo *Repository, issue *Issue, commitID, line int64, cmtType CommentType, content string, uuids []string) (_ *Comment, err error) {
|
||||
func createComment(e *xorm.Session, u *User, repo *Repository, issue *Issue, commitID, line int64, cmtType CommentType, content, commitSHA string, uuids []string) (_ *Comment, err error) {
|
||||
comment := &Comment{
|
||||
PosterID: u.Id,
|
||||
Type: cmtType,
|
||||
IssueID: issue.ID,
|
||||
CommitID: commitID,
|
||||
Line: line,
|
||||
Content: content,
|
||||
PosterID: u.Id,
|
||||
Type: cmtType,
|
||||
IssueID: issue.ID,
|
||||
CommitID: commitID,
|
||||
Line: line,
|
||||
Content: content,
|
||||
CommitSHA: commitSHA,
|
||||
}
|
||||
if _, err = e.Insert(comment); err != nil {
|
||||
return nil, err
|
||||
@@ -1819,18 +1595,18 @@ func createStatusComment(e *xorm.Session, doer *User, repo *Repository, issue *I
|
||||
if !issue.IsClosed {
|
||||
cmtType = COMMENT_TYPE_REOPEN
|
||||
}
|
||||
return createComment(e, doer, repo, issue, 0, 0, cmtType, "", nil)
|
||||
return createComment(e, doer, repo, issue, 0, 0, cmtType, "", "", nil)
|
||||
}
|
||||
|
||||
// CreateComment creates comment of issue or commit.
|
||||
func CreateComment(doer *User, repo *Repository, issue *Issue, commitID, line int64, cmtType CommentType, content string, attachments []string) (comment *Comment, err error) {
|
||||
func CreateComment(doer *User, repo *Repository, issue *Issue, commitID, line int64, cmtType CommentType, content, commitSHA string, attachments []string) (comment *Comment, err error) {
|
||||
sess := x.NewSession()
|
||||
defer sessionRelease(sess)
|
||||
if err = sess.Begin(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
comment, err = createComment(sess, doer, repo, issue, commitID, line, cmtType, content, attachments)
|
||||
comment, err = createComment(sess, doer, repo, issue, commitID, line, cmtType, content, commitSHA, attachments)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -1840,7 +1616,29 @@ func CreateComment(doer *User, repo *Repository, issue *Issue, commitID, line in
|
||||
|
||||
// CreateIssueComment creates a plain issue comment.
|
||||
func CreateIssueComment(doer *User, repo *Repository, issue *Issue, content string, attachments []string) (*Comment, error) {
|
||||
return CreateComment(doer, repo, issue, 0, 0, COMMENT_TYPE_COMMENT, content, attachments)
|
||||
return CreateComment(doer, repo, issue, 0, 0, COMMENT_TYPE_COMMENT, content, "", 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(doer, repo, issue, 0, 0, COMMENT_TYPE_COMMIT_REF, content, commitSHA, nil)
|
||||
return err
|
||||
}
|
||||
|
||||
// GetCommentByID returns the comment by given ID.
|
||||
|
||||
346
models/login.go
346
models/login.go
@@ -13,6 +13,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Unknwon/com"
|
||||
"github.com/go-xorm/core"
|
||||
"github.com/go-xorm/xorm"
|
||||
|
||||
@@ -23,24 +24,26 @@ import (
|
||||
|
||||
type LoginType int
|
||||
|
||||
// Note: new type must be added at the end of list to maintain compatibility.
|
||||
const (
|
||||
NOTYPE LoginType = iota
|
||||
PLAIN
|
||||
LDAP
|
||||
SMTP
|
||||
PAM
|
||||
DLDAP
|
||||
)
|
||||
|
||||
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",
|
||||
var LoginNames = map[LoginType]string{
|
||||
LDAP: "LDAP (via BindDN)",
|
||||
DLDAP: "LDAP (simple auth)",
|
||||
SMTP: "SMTP",
|
||||
PAM: "PAM",
|
||||
}
|
||||
|
||||
// Ensure structs implemented interface.
|
||||
@@ -51,23 +54,24 @@ var (
|
||||
)
|
||||
|
||||
type LDAPConfig struct {
|
||||
ldap.Ldapsource
|
||||
*ldap.Source
|
||||
}
|
||||
|
||||
func (cfg *LDAPConfig) FromDB(bs []byte) error {
|
||||
return json.Unmarshal(bs, &cfg.Ldapsource)
|
||||
return json.Unmarshal(bs, &cfg)
|
||||
}
|
||||
|
||||
func (cfg *LDAPConfig) ToDB() ([]byte, error) {
|
||||
return json.Marshal(cfg.Ldapsource)
|
||||
return json.Marshal(cfg)
|
||||
}
|
||||
|
||||
type SMTPConfig struct {
|
||||
Auth string
|
||||
Host string
|
||||
Port int
|
||||
TLS bool
|
||||
SkipVerify bool
|
||||
Auth string
|
||||
Host string
|
||||
Port int
|
||||
AllowedDomains string `xorm:"TEXT"`
|
||||
TLS bool
|
||||
SkipVerify bool
|
||||
}
|
||||
|
||||
func (cfg *SMTPConfig) FromDB(bs []byte) error {
|
||||
@@ -91,32 +95,71 @@ func (cfg *PAMConfig) ToDB() ([]byte, error) {
|
||||
}
|
||||
|
||||
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"`
|
||||
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:"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:
|
||||
case LDAP, DLDAP:
|
||||
source.Cfg = new(LDAPConfig)
|
||||
case SMTP:
|
||||
source.Cfg = new(SMTPConfig)
|
||||
case PAM:
|
||||
source.Cfg = new(PAMConfig)
|
||||
default:
|
||||
panic("unrecognized login source type: " + com.ToStr(*val))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (source *LoginSource) TypeString() string {
|
||||
return LoginTypes[source.Type]
|
||||
func (source *LoginSource) TypeName() string {
|
||||
return LoginNames[source.Type]
|
||||
}
|
||||
|
||||
func (source *LoginSource) IsLDAP() bool {
|
||||
return source.Type == LDAP
|
||||
}
|
||||
|
||||
func (source *LoginSource) IsDLDAP() bool {
|
||||
return source.Type == DLDAP
|
||||
}
|
||||
|
||||
func (source *LoginSource) IsSMTP() bool {
|
||||
return source.Type == SMTP
|
||||
}
|
||||
|
||||
func (source *LoginSource) IsPAM() bool {
|
||||
return source.Type == PAM
|
||||
}
|
||||
|
||||
func (source *LoginSource) UseTLS() bool {
|
||||
switch source.Type {
|
||||
case LDAP, DLDAP:
|
||||
return source.LDAP().UseSSL
|
||||
case SMTP:
|
||||
return source.SMTP().TLS
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (source *LoginSource) SkipVerify() bool {
|
||||
switch source.Type {
|
||||
case LDAP, DLDAP:
|
||||
return source.LDAP().SkipVerify
|
||||
case SMTP:
|
||||
return source.SMTP().SkipVerify
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (source *LoginSource) LDAP() *LDAPConfig {
|
||||
@@ -131,23 +174,30 @@ func (source *LoginSource) PAM() *PAMConfig {
|
||||
return source.Cfg.(*PAMConfig)
|
||||
}
|
||||
|
||||
// CountLoginSources returns number of login sources.
|
||||
func CountLoginSources() int64 {
|
||||
count, _ := x.Count(new(LoginSource))
|
||||
return count
|
||||
}
|
||||
|
||||
func CreateSource(source *LoginSource) error {
|
||||
_, err := x.Insert(source)
|
||||
return err
|
||||
}
|
||||
|
||||
func GetAuths() ([]*LoginSource, error) {
|
||||
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, ErrAuthenticationNotExist
|
||||
return nil, ErrAuthenticationNotExist{id}
|
||||
}
|
||||
return source, nil
|
||||
}
|
||||
@@ -157,110 +207,34 @@ func UpdateSource(source *LoginSource) error {
|
||||
return err
|
||||
}
|
||||
|
||||
func DelLoginSource(source *LoginSource) error {
|
||||
cnt, err := x.Count(&User{LoginSource: source.ID})
|
||||
func DeleteSource(source *LoginSource) error {
|
||||
count, err := x.Count(&User{LoginSource: source.ID})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if cnt > 0 {
|
||||
} else if count > 0 {
|
||||
return ErrAuthenticationUserUsed
|
||||
}
|
||||
_, err = x.Id(source.ID).Delete(&LoginSource{})
|
||||
_, err = x.Id(source.ID).Delete(new(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)
|
||||
// LoginUserLDAPSource queries if loginName/passwd can login against the LDAP directory pool,
|
||||
// and create a local user if success when enabled.
|
||||
// It returns the same LoginUserPlain semantic.
|
||||
func LoginUserLDAPSource(u *User, loginName, passwd string, source *LoginSource, autoRegister bool) (*User, error) {
|
||||
cfg := source.Cfg.(*LDAPConfig)
|
||||
directBind := (source.Type == DLDAP)
|
||||
name, fn, sn, mail, admin, logged := cfg.SearchEntry(loginName, passwd, directBind)
|
||||
if !logged {
|
||||
// User not in LDAP, do nothing
|
||||
return nil, ErrUserNotExist{0, name}
|
||||
return nil, ErrUserNotExist{0, loginName}
|
||||
}
|
||||
|
||||
if !autoRegister {
|
||||
@@ -268,6 +242,9 @@ func LoginUserLdapSource(u *User, name, passwd string, sourceId int64, cfg *LDAP
|
||||
}
|
||||
|
||||
// Fallback.
|
||||
if len(name) == 0 {
|
||||
name = loginName
|
||||
}
|
||||
if len(mail) == 0 {
|
||||
mail = fmt.Sprintf("%s@localhost", name)
|
||||
}
|
||||
@@ -275,11 +252,10 @@ func LoginUserLdapSource(u *User, name, passwd string, sourceId int64, cfg *LDAP
|
||||
u = &User{
|
||||
LowerName: strings.ToLower(name),
|
||||
Name: name,
|
||||
FullName: fn + " " + sn,
|
||||
LoginType: LDAP,
|
||||
LoginSource: sourceId,
|
||||
LoginName: name,
|
||||
Passwd: passwd,
|
||||
FullName: composeFullName(fn, sn, name),
|
||||
LoginType: source.Type,
|
||||
LoginSource: source.ID,
|
||||
LoginName: loginName,
|
||||
Email: mail,
|
||||
IsAdmin: admin,
|
||||
IsActive: true,
|
||||
@@ -287,6 +263,26 @@ func LoginUserLdapSource(u *User, name, passwd string, sourceId int64, cfg *LDAP
|
||||
return u, CreateUser(u)
|
||||
}
|
||||
|
||||
func composeFullName(firstName, surename, userName string) string {
|
||||
switch {
|
||||
case len(firstName) == 0 && len(surename) == 0:
|
||||
return userName
|
||||
case len(firstName) == 0:
|
||||
return surename
|
||||
case len(surename) == 0:
|
||||
return firstName
|
||||
default:
|
||||
return firstName + " " + surename
|
||||
}
|
||||
}
|
||||
|
||||
// _________ __________________________
|
||||
// / _____/ / \__ ___/\______ \
|
||||
// \_____ \ / \ / \| | | ___/
|
||||
// / \/ Y \ | | |
|
||||
// /_______ /\____|__ /____| |____|
|
||||
// \/ \/
|
||||
|
||||
type loginAuth struct {
|
||||
username, password string
|
||||
}
|
||||
@@ -316,9 +312,7 @@ const (
|
||||
SMTP_LOGIN = "LOGIN"
|
||||
)
|
||||
|
||||
var (
|
||||
SMTPAuths = []string{SMTP_PLAIN, SMTP_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))
|
||||
@@ -357,6 +351,16 @@ func SMTPAuth(a smtp.Auth, cfg *SMTPConfig) error {
|
||||
// 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) {
|
||||
// Verify allowed domains.
|
||||
if len(cfg.AllowedDomains) > 0 {
|
||||
idx := strings.Index(name, "@")
|
||||
if idx == -1 {
|
||||
return nil, ErrUserNotExist{0, name}
|
||||
} else if !com.IsSliceContainsStr(strings.Split(cfg.AllowedDomains, ","), name[idx+1:]) {
|
||||
return nil, ErrUserNotExist{0, name}
|
||||
}
|
||||
}
|
||||
|
||||
var auth smtp.Auth
|
||||
if cfg.Auth == SMTP_PLAIN {
|
||||
auth = smtp.PlainAuth("", name, passwd, cfg.Host)
|
||||
@@ -368,7 +372,7 @@ func LoginUserSMTPSource(u *User, name, passwd string, sourceId int64, cfg *SMTP
|
||||
|
||||
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, ErrUserNotExist{0, name}
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
@@ -397,13 +401,20 @@ func LoginUserSMTPSource(u *User, name, passwd string, sourceId int64, cfg *SMTP
|
||||
return u, err
|
||||
}
|
||||
|
||||
// __________ _____ _____
|
||||
// \______ \/ _ \ / \
|
||||
// | ___/ /_\ \ / \ / \
|
||||
// | | / | \/ Y \
|
||||
// |____| \____|__ /\____|__ /
|
||||
// \/ \/
|
||||
|
||||
// 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, ErrUserNotExist{0, name}
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
@@ -415,7 +426,7 @@ func LoginUserPAMSource(u *User, name, passwd string, sourceId int64, cfg *PAMCo
|
||||
// fake a local user creation
|
||||
u = &User{
|
||||
LowerName: strings.ToLower(name),
|
||||
Name: strings.ToLower(name),
|
||||
Name: name,
|
||||
LoginType: PAM,
|
||||
LoginSource: sourceId,
|
||||
LoginName: name,
|
||||
@@ -423,6 +434,75 @@ func LoginUserPAMSource(u *User, name, passwd string, sourceId int64, cfg *PAMCo
|
||||
Passwd: passwd,
|
||||
Email: name,
|
||||
}
|
||||
err := CreateUser(u)
|
||||
return u, err
|
||||
return u, CreateUser(u)
|
||||
}
|
||||
|
||||
func ExternalUserLogin(u *User, name, passwd string, source *LoginSource, autoRegister bool) (*User, error) {
|
||||
if !source.IsActived {
|
||||
return nil, ErrLoginSourceNotActived
|
||||
}
|
||||
|
||||
switch source.Type {
|
||||
case LDAP, DLDAP:
|
||||
return LoginUserLDAPSource(u, name, passwd, source, autoRegister)
|
||||
case SMTP:
|
||||
return LoginUserSMTPSource(u, name, passwd, source.ID, source.Cfg.(*SMTPConfig), autoRegister)
|
||||
case PAM:
|
||||
return LoginUserPAMSource(u, name, passwd, source.ID, source.Cfg.(*PAMConfig), autoRegister)
|
||||
}
|
||||
|
||||
return nil, ErrUnsupportedLoginType
|
||||
}
|
||||
|
||||
// UserSignIn validates user name and password.
|
||||
func UserSignIn(uname, passwd string) (*User, error) {
|
||||
var u *User
|
||||
if strings.Contains(uname, "@") {
|
||||
u = &User{Email: strings.ToLower(uname)}
|
||||
} else {
|
||||
u = &User{LowerName: strings.ToLower(uname)}
|
||||
}
|
||||
|
||||
userExists, err := x.Get(u)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if userExists {
|
||||
switch u.LoginType {
|
||||
case NOTYPE, PLAIN:
|
||||
if u.ValidatePassword(passwd) {
|
||||
return u, nil
|
||||
}
|
||||
|
||||
return nil, ErrUserNotExist{u.Id, u.Name}
|
||||
|
||||
default:
|
||||
var source LoginSource
|
||||
hasSource, err := x.Id(u.LoginSource).Get(&source)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !hasSource {
|
||||
return nil, ErrLoginSourceNotExist
|
||||
}
|
||||
|
||||
return ExternalUserLogin(u, u.LoginName, passwd, &source, false)
|
||||
}
|
||||
}
|
||||
|
||||
var sources []LoginSource
|
||||
if err = x.UseBool().Find(&sources, &LoginSource{IsActived: true}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, source := range sources {
|
||||
u, err := ExternalUserLogin(nil, uname, passwd, &source, true)
|
||||
if err == nil {
|
||||
return u, nil
|
||||
}
|
||||
|
||||
log.Warn("Failed to login '%s' via '%s': %v", uname, source.Name, err)
|
||||
}
|
||||
|
||||
return nil, ErrUserNotExist{u.Id, u.Name}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -65,6 +66,8 @@ var migrations = []Migration{
|
||||
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
|
||||
NewMigration("rename pull request fields", renamePullRequestFields), // V8 -> V9:v0.6.16
|
||||
NewMigration("clean up migrate repo info", cleanUpMigrateRepoInfo), // V9 -> V10:v0.6.20
|
||||
}
|
||||
|
||||
// Migrate database to current version
|
||||
@@ -103,6 +106,11 @@ func Migrate(x *xorm.Engine) error {
|
||||
}
|
||||
|
||||
v := currentVersion.Version
|
||||
if _MIN_DB_VER > v {
|
||||
log.Fatal(4, "Gogs no longer supports auto-migration from your previously installed version. Please try to upgrade to a lower version first, then upgrade to current version.")
|
||||
return nil
|
||||
}
|
||||
|
||||
if int(v-_MIN_DB_VER) > len(migrations) {
|
||||
// User downgraded Gogs.
|
||||
currentVersion.Version = int64(len(migrations) + _MIN_DB_VER)
|
||||
@@ -453,7 +461,7 @@ func trimCommitActionAppUrlPrefix(x *xorm.Engine) error {
|
||||
|
||||
pushCommits = new(PushCommits)
|
||||
if err = json.Unmarshal(action["content"], pushCommits); err != nil {
|
||||
return fmt.Errorf("unmarshal action content[%s]: %v", actID, err)
|
||||
return fmt.Errorf("unmarshal action content[%d]: %v", actID, err)
|
||||
}
|
||||
|
||||
infos := strings.Split(pushCommits.CompareUrl, "/")
|
||||
@@ -464,7 +472,7 @@ func trimCommitActionAppUrlPrefix(x *xorm.Engine) error {
|
||||
|
||||
p, err := json.Marshal(pushCommits)
|
||||
if err != nil {
|
||||
return fmt.Errorf("marshal action content[%s]: %v", actID, err)
|
||||
return fmt.Errorf("marshal action content[%d]: %v", actID, err)
|
||||
}
|
||||
|
||||
if _, err = sess.Id(actID).Update(&Action{
|
||||
@@ -606,3 +614,96 @@ func attachmentRefactor(x *xorm.Engine) error {
|
||||
|
||||
return sess.Commit()
|
||||
}
|
||||
|
||||
func renamePullRequestFields(x *xorm.Engine) (err error) {
|
||||
type PullRequest struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
PullID int64 `xorm:"INDEX"`
|
||||
PullIndex int64
|
||||
HeadBarcnh string
|
||||
|
||||
IssueID int64 `xorm:"INDEX"`
|
||||
Index int64
|
||||
HeadBranch string
|
||||
}
|
||||
|
||||
if err = x.Sync(new(PullRequest)); err != nil {
|
||||
return fmt.Errorf("sync: %v", err)
|
||||
}
|
||||
|
||||
results, err := x.Query("SELECT `id`,`pull_id`,`pull_index`,`head_barcnh` FROM `pull_request`")
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "no such column") {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("select pull requests: %v", err)
|
||||
}
|
||||
|
||||
sess := x.NewSession()
|
||||
defer sessionRelease(sess)
|
||||
if err = sess.Begin(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var pull *PullRequest
|
||||
for _, pr := range results {
|
||||
pull = &PullRequest{
|
||||
ID: com.StrTo(pr["id"]).MustInt64(),
|
||||
IssueID: com.StrTo(pr["pull_id"]).MustInt64(),
|
||||
Index: com.StrTo(pr["pull_index"]).MustInt64(),
|
||||
HeadBranch: string(pr["head_barcnh"]),
|
||||
}
|
||||
if _, err = sess.Id(pull.ID).Update(pull); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return sess.Commit()
|
||||
}
|
||||
|
||||
func cleanUpMigrateRepoInfo(x *xorm.Engine) (err error) {
|
||||
type (
|
||||
User struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
LowerName string
|
||||
}
|
||||
Repository struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
OwnerID int64
|
||||
LowerName string
|
||||
}
|
||||
)
|
||||
|
||||
repos := make([]*Repository, 0, 25)
|
||||
if err = x.Where("is_mirror=?", false).Find(&repos); err != nil {
|
||||
return fmt.Errorf("select all non-mirror repositories: %v", err)
|
||||
}
|
||||
var user *User
|
||||
for _, repo := range repos {
|
||||
user = &User{ID: repo.OwnerID}
|
||||
has, err := x.Get(user)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get owner of repository[%d - %d]: %v", repo.ID, repo.OwnerID, err)
|
||||
} else if !has {
|
||||
continue
|
||||
}
|
||||
|
||||
configPath := filepath.Join(setting.RepoRootPath, user.LowerName, repo.LowerName+".git/config")
|
||||
|
||||
// In case repository file is somehow missing.
|
||||
if !com.IsFile(configPath) {
|
||||
continue
|
||||
}
|
||||
|
||||
cfg, err := ini.Load(configPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("open config file: %v", err)
|
||||
}
|
||||
cfg.DeleteSection("remote \"origin\"")
|
||||
if err = cfg.SaveToIndent(configPath, "\t"); err != nil {
|
||||
return fmt.Errorf("save config file: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
_ "github.com/lib/pq"
|
||||
|
||||
"github.com/gogits/gogs/models/migrations"
|
||||
"github.com/gogits/gogs/modules/log"
|
||||
"github.com/gogits/gogs/modules/setting"
|
||||
)
|
||||
|
||||
@@ -46,20 +47,22 @@ func sessionRelease(sess *xorm.Session) {
|
||||
// Note: get back time.Time from database Go sees it at UTC where they are really Local.
|
||||
// So this function makes correct timezone offset.
|
||||
func regulateTimeZone(t time.Time) time.Time {
|
||||
if setting.UseSQLite3 {
|
||||
if !setting.UseMySQL {
|
||||
return t
|
||||
}
|
||||
|
||||
zone := t.Local().Format("-0700")
|
||||
if len(zone) != 5 {
|
||||
log.Error(4, "Unprocessable timezone: %s - %s", t.Local(), zone)
|
||||
return t
|
||||
}
|
||||
offset := com.StrTo(zone[2:3]).MustInt()
|
||||
hour := com.StrTo(zone[2:3]).MustInt()
|
||||
minutes := com.StrTo(zone[3:5]).MustInt()
|
||||
|
||||
if zone[0] == '-' {
|
||||
return t.Add(time.Duration(offset) * time.Hour)
|
||||
return t.Add(time.Duration(hour) * time.Hour).Add(time.Duration(minutes) * time.Minute)
|
||||
}
|
||||
return t.Add(-1 * time.Duration(offset) * time.Hour)
|
||||
return t.Add(-1 * time.Duration(hour) * time.Hour).Add(-1 * time.Duration(minutes) * time.Minute)
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -72,11 +75,12 @@ var (
|
||||
}
|
||||
|
||||
EnableSQLite3 bool
|
||||
EnableTidb bool
|
||||
)
|
||||
|
||||
func init() {
|
||||
tables = append(tables,
|
||||
new(User), new(PublicKey), new(Oauth2), new(AccessToken),
|
||||
new(User), new(PublicKey), new(AccessToken),
|
||||
new(Repository), new(DeployKey), new(Collaboration), new(Access),
|
||||
new(Watch), new(Star), new(Follow), new(Action),
|
||||
new(Issue), new(PullRequest), new(Comment), new(Attachment), new(IssueUser),
|
||||
@@ -92,7 +96,7 @@ func init() {
|
||||
}
|
||||
}
|
||||
|
||||
func LoadModelsConfig() {
|
||||
func LoadConfigs() {
|
||||
sec := setting.Cfg.Section("database")
|
||||
DbCfg.Type = sec.Key("DB_TYPE").String()
|
||||
switch DbCfg.Type {
|
||||
@@ -102,6 +106,8 @@ func LoadModelsConfig() {
|
||||
setting.UseMySQL = true
|
||||
case "postgres":
|
||||
setting.UsePostgreSQL = true
|
||||
case "tidb":
|
||||
setting.UseTiDB = true
|
||||
}
|
||||
DbCfg.Host = sec.Key("HOST").String()
|
||||
DbCfg.Name = sec.Key("NAME").String()
|
||||
@@ -143,6 +149,14 @@ func getEngine() (*xorm.Engine, error) {
|
||||
return nil, fmt.Errorf("Fail to create directories: %v", err)
|
||||
}
|
||||
cnnstr = "file:" + DbCfg.Path + "?cache=shared&mode=rwc"
|
||||
case "tidb":
|
||||
if !EnableTidb {
|
||||
return nil, fmt.Errorf("Unknown database type: %s", DbCfg.Type)
|
||||
}
|
||||
if err := os.MkdirAll(path.Dir(DbCfg.Path), os.ModePerm); err != nil {
|
||||
return nil, fmt.Errorf("Fail to create directories: %v", err)
|
||||
}
|
||||
cnnstr = "goleveldb://" + DbCfg.Path
|
||||
default:
|
||||
return nil, fmt.Errorf("Unknown database type: %s", DbCfg.Type)
|
||||
}
|
||||
@@ -224,11 +238,11 @@ func GetStatistic() (stats Statistic) {
|
||||
stats.Counter.Access, _ = x.Count(new(Access))
|
||||
stats.Counter.Issue, _ = x.Count(new(Issue))
|
||||
stats.Counter.Comment, _ = x.Count(new(Comment))
|
||||
stats.Counter.Oauth, _ = x.Count(new(Oauth2))
|
||||
stats.Counter.Oauth = 0
|
||||
stats.Counter.Follow, _ = x.Count(new(Follow))
|
||||
stats.Counter.Mirror, _ = x.Count(new(Mirror))
|
||||
stats.Counter.Release, _ = x.Count(new(Release))
|
||||
stats.Counter.LoginSource, _ = x.Count(new(LoginSource))
|
||||
stats.Counter.LoginSource = CountLoginSources()
|
||||
stats.Counter.Webhook, _ = x.Count(new(Webhook))
|
||||
stats.Counter.Milestone, _ = x.Count(new(Milestone))
|
||||
stats.Counter.Label, _ = x.Count(new(Label))
|
||||
|
||||
18
models/models_tidb.go
Normal file
18
models/models_tidb.go
Normal file
@@ -0,0 +1,18 @@
|
||||
// +build tidb go1.4.2
|
||||
|
||||
// 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 models
|
||||
|
||||
import (
|
||||
_ "github.com/go-xorm/tidb"
|
||||
"github.com/ngaut/log"
|
||||
_ "github.com/pingcap/tidb"
|
||||
)
|
||||
|
||||
func init() {
|
||||
EnableTidb = true
|
||||
log.SetLevelByString("error")
|
||||
}
|
||||
106
models/oauth2.go
106
models/oauth2.go
@@ -1,106 +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 (
|
||||
"errors"
|
||||
"time"
|
||||
)
|
||||
|
||||
type OauthType int
|
||||
|
||||
const (
|
||||
GITHUB OauthType = iota + 1
|
||||
GOOGLE
|
||||
TWITTER
|
||||
QQ
|
||||
WEIBO
|
||||
BITBUCKET
|
||||
FACEBOOK
|
||||
)
|
||||
|
||||
var (
|
||||
ErrOauth2RecordNotExist = errors.New("OAuth2 record does not exist")
|
||||
ErrOauth2NotAssociated = errors.New("OAuth2 is not associated with user")
|
||||
)
|
||||
|
||||
type Oauth2 struct {
|
||||
Id int64
|
||||
Uid int64 `xorm:"unique(s)"` // userId
|
||||
User *User `xorm:"-"`
|
||||
Type int `xorm:"unique(s) unique(oauth)"` // twitter,github,google...
|
||||
Identity string `xorm:"unique(s) unique(oauth)"` // id..
|
||||
Token string `xorm:"TEXT not null"`
|
||||
Created time.Time `xorm:"CREATED"`
|
||||
Updated time.Time
|
||||
HasRecentActivity bool `xorm:"-"`
|
||||
}
|
||||
|
||||
func BindUserOauth2(userId, oauthId int64) error {
|
||||
_, err := x.Id(oauthId).Update(&Oauth2{Uid: userId})
|
||||
return err
|
||||
}
|
||||
|
||||
func AddOauth2(oa *Oauth2) error {
|
||||
_, err := x.Insert(oa)
|
||||
return err
|
||||
}
|
||||
|
||||
func GetOauth2(identity string) (oa *Oauth2, err error) {
|
||||
oa = &Oauth2{Identity: identity}
|
||||
isExist, err := x.Get(oa)
|
||||
if err != nil {
|
||||
return
|
||||
} else if !isExist {
|
||||
return nil, ErrOauth2RecordNotExist
|
||||
} else if oa.Uid == -1 {
|
||||
return oa, ErrOauth2NotAssociated
|
||||
}
|
||||
oa.User, err = GetUserByID(oa.Uid)
|
||||
return oa, err
|
||||
}
|
||||
|
||||
func GetOauth2ById(id int64) (oa *Oauth2, err error) {
|
||||
oa = new(Oauth2)
|
||||
has, err := x.Id(id).Get(oa)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !has {
|
||||
return nil, ErrOauth2RecordNotExist
|
||||
}
|
||||
return oa, nil
|
||||
}
|
||||
|
||||
// UpdateOauth2 updates given OAuth2.
|
||||
func UpdateOauth2(oa *Oauth2) error {
|
||||
_, err := x.Id(oa.Id).AllCols().Update(oa)
|
||||
return err
|
||||
}
|
||||
|
||||
// GetOauthByUserId returns list of oauthes that are related to given user.
|
||||
func GetOauthByUserId(uid int64) ([]*Oauth2, error) {
|
||||
socials := make([]*Oauth2, 0, 5)
|
||||
err := x.Find(&socials, Oauth2{Uid: uid})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, social := range socials {
|
||||
social.HasRecentActivity = social.Updated.Add(7 * 24 * time.Hour).After(time.Now())
|
||||
}
|
||||
return socials, err
|
||||
}
|
||||
|
||||
// DeleteOauth2ById deletes a oauth2 by ID.
|
||||
func DeleteOauth2ById(id int64) error {
|
||||
_, err := x.Delete(&Oauth2{Id: id})
|
||||
return err
|
||||
}
|
||||
|
||||
// CleanUnbindOauth deletes all unbind OAuthes.
|
||||
func CleanUnbindOauth() error {
|
||||
_, err := x.Delete(&Oauth2{Uid: -1})
|
||||
return err
|
||||
}
|
||||
102
models/org.go
102
models/org.go
@@ -10,7 +10,7 @@ import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/gogits/gogs/modules/base"
|
||||
"github.com/go-xorm/xorm"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -93,17 +93,6 @@ func (org *User) RemoveOrgRepo(repoID int64) error {
|
||||
return org.removeOrgRepo(x, repoID)
|
||||
}
|
||||
|
||||
// IsOrgEmailUsed returns true if the e-mail has been used in organization account.
|
||||
func IsOrgEmailUsed(email string) (bool, error) {
|
||||
if len(email) == 0 {
|
||||
return false, nil
|
||||
}
|
||||
return x.Get(&User{
|
||||
Email: email,
|
||||
Type: ORGANIZATION,
|
||||
})
|
||||
}
|
||||
|
||||
// CreateOrganization creates record of a new organization.
|
||||
func CreateOrganization(org, owner *User) (err error) {
|
||||
if err = IsUsableName(org.Name); err != nil {
|
||||
@@ -117,18 +106,9 @@ func CreateOrganization(org, owner *User) (err error) {
|
||||
return ErrUserAlreadyExist{org.Name}
|
||||
}
|
||||
|
||||
isExist, err = IsOrgEmailUsed(org.Email)
|
||||
if err != nil {
|
||||
return err
|
||||
} else if isExist {
|
||||
return ErrEmailAlreadyUsed{org.Email}
|
||||
}
|
||||
|
||||
org.LowerName = strings.ToLower(org.Name)
|
||||
org.FullName = org.Name
|
||||
org.Avatar = base.EncodeMd5(org.Email)
|
||||
org.AvatarEmail = org.Email
|
||||
// No password for organization.
|
||||
org.UseCustomAvatar = true
|
||||
org.NumTeams = 1
|
||||
org.NumMembers = 1
|
||||
|
||||
@@ -141,6 +121,17 @@ func CreateOrganization(org, owner *User) (err error) {
|
||||
if _, err = sess.Insert(org); err != nil {
|
||||
return fmt.Errorf("insert organization: %v", err)
|
||||
}
|
||||
org.GenerateRandomAvatar()
|
||||
|
||||
// Add initial creator to organization and owner team.
|
||||
if _, err = sess.Insert(&OrgUser{
|
||||
Uid: owner.Id,
|
||||
OrgID: org.Id,
|
||||
IsOwner: true,
|
||||
NumTeams: 1,
|
||||
}); err != nil {
|
||||
return fmt.Errorf("insert org-user relation: %v", err)
|
||||
}
|
||||
|
||||
// Create default owner team.
|
||||
t := &Team{
|
||||
@@ -154,23 +145,11 @@ func CreateOrganization(org, owner *User) (err error) {
|
||||
return fmt.Errorf("insert owner team: %v", err)
|
||||
}
|
||||
|
||||
// Add initial creator to organization and owner team.
|
||||
ou := &OrgUser{
|
||||
Uid: owner.Id,
|
||||
OrgID: org.Id,
|
||||
IsOwner: true,
|
||||
NumTeams: 1,
|
||||
}
|
||||
if _, err = sess.Insert(ou); err != nil {
|
||||
return fmt.Errorf("insert org-user relation: %v", err)
|
||||
}
|
||||
|
||||
tu := &TeamUser{
|
||||
if _, err = sess.Insert(&TeamUser{
|
||||
Uid: owner.Id,
|
||||
OrgID: org.Id,
|
||||
TeamID: t.ID,
|
||||
}
|
||||
if _, err = sess.Insert(tu); err != nil {
|
||||
}); err != nil {
|
||||
return fmt.Errorf("insert team-user relation: %v", err)
|
||||
}
|
||||
|
||||
@@ -205,14 +184,12 @@ func CountOrganizations() int64 {
|
||||
return count
|
||||
}
|
||||
|
||||
// GetOrganizations returns given number of organizations with offset.
|
||||
func GetOrganizations(num, offset int) ([]*User, error) {
|
||||
orgs := make([]*User, 0, num)
|
||||
err := x.Limit(num, offset).Where("type=1").Asc("id").Find(&orgs)
|
||||
return orgs, err
|
||||
// Organizations returns number of organizations in given page.
|
||||
func Organizations(page, pageSize int) ([]*User, error) {
|
||||
orgs := make([]*User, 0, pageSize)
|
||||
return orgs, x.Limit(pageSize, (page-1)*pageSize).Where("type=1").Asc("id").Find(&orgs)
|
||||
}
|
||||
|
||||
// TODO: need some kind of mechanism to record failure.
|
||||
// DeleteOrganization completely and permanently deletes everything of organization.
|
||||
func DeleteOrganization(org *User) (err error) {
|
||||
if err := DeleteUser(org); err != nil {
|
||||
@@ -220,23 +197,23 @@ func DeleteOrganization(org *User) (err error) {
|
||||
}
|
||||
|
||||
sess := x.NewSession()
|
||||
defer sess.Close()
|
||||
defer sessionRelease(sess)
|
||||
if err = sess.Begin(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err = sess.Delete(&Team{OrgID: org.Id}); err != nil {
|
||||
sess.Rollback()
|
||||
return err
|
||||
if err = deleteBeans(sess,
|
||||
&Team{OrgID: org.Id},
|
||||
&OrgUser{OrgID: org.Id},
|
||||
&TeamUser{OrgID: org.Id},
|
||||
); err != nil {
|
||||
return fmt.Errorf("deleteBeans: %v", err)
|
||||
}
|
||||
if _, err = sess.Delete(&OrgUser{OrgID: org.Id}); err != nil {
|
||||
sess.Rollback()
|
||||
return err
|
||||
}
|
||||
if _, err = sess.Delete(&TeamUser{OrgID: org.Id}); err != nil {
|
||||
sess.Rollback()
|
||||
return err
|
||||
|
||||
if err = deleteUser(sess, org); err != nil {
|
||||
return fmt.Errorf("deleteUser: %v", err)
|
||||
}
|
||||
|
||||
return sess.Commit()
|
||||
}
|
||||
|
||||
@@ -275,6 +252,25 @@ func IsPublicMembership(orgId, uid int64) bool {
|
||||
return has
|
||||
}
|
||||
|
||||
func getOwnedOrgsByUserID(sess *xorm.Session, userID int64) ([]*User, error) {
|
||||
orgs := make([]*User, 0, 10)
|
||||
return orgs, sess.Where("`org_user`.uid=?", userID).And("`org_user`.is_owner=?", true).
|
||||
Join("INNER", "`org_user`", "`org_user`.org_id=`user`.id").Find(&orgs)
|
||||
}
|
||||
|
||||
// GetOwnedOrgsByUserID returns a list of organizations are owned by given user ID.
|
||||
func GetOwnedOrgsByUserID(userID int64) ([]*User, error) {
|
||||
sess := x.NewSession()
|
||||
return getOwnedOrgsByUserID(sess, userID)
|
||||
}
|
||||
|
||||
// GetOwnedOrganizationsByUserIDDesc returns a list of organizations are owned by
|
||||
// given user ID and descring order by given condition.
|
||||
func GetOwnedOrgsByUserIDDesc(userID int64, desc string) ([]*User, error) {
|
||||
sess := x.NewSession()
|
||||
return getOwnedOrgsByUserID(sess.Desc(desc), userID)
|
||||
}
|
||||
|
||||
// GetOrgUsersByUserId returns all organization-user relations by user ID.
|
||||
func GetOrgUsersByUserId(uid int64) ([]*OrgUser, error) {
|
||||
ous := make([]*OrgUser, 0, 10)
|
||||
|
||||
@@ -13,7 +13,6 @@ import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
@@ -33,25 +32,8 @@ const (
|
||||
_TPL_PUBLICK_KEY = `command="%s serv key-%d --config='%s'",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty %s` + "\n"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrKeyUnableVerify = errors.New("Unable to verify public key")
|
||||
)
|
||||
|
||||
var sshOpLocker = sync.Mutex{}
|
||||
|
||||
var (
|
||||
SSHPath string // SSH directory.
|
||||
appPath string // Execution(binary) path.
|
||||
)
|
||||
|
||||
// exePath returns the executable path.
|
||||
func exePath() (string, error) {
|
||||
file, err := exec.LookPath(os.Args[0])
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return filepath.Abs(file)
|
||||
}
|
||||
var SSHPath string // SSH directory.
|
||||
|
||||
// homeDir returns the home directory of current user.
|
||||
func homeDir() string {
|
||||
@@ -63,16 +45,9 @@ func homeDir() string {
|
||||
}
|
||||
|
||||
func init() {
|
||||
var err error
|
||||
|
||||
if appPath, err = exePath(); err != nil {
|
||||
log.Fatal(4, "fail to get app path: %v\n", err)
|
||||
}
|
||||
appPath = strings.Replace(appPath, "\\", "/", -1)
|
||||
|
||||
// Determine and create .ssh path.
|
||||
SSHPath = filepath.Join(homeDir(), ".ssh")
|
||||
if err = os.MkdirAll(SSHPath, 0700); err != nil {
|
||||
if err := os.MkdirAll(SSHPath, 0700); err != nil {
|
||||
log.Fatal(4, "fail to create '%s': %v", SSHPath, err)
|
||||
}
|
||||
}
|
||||
@@ -114,17 +89,7 @@ func (k *PublicKey) OmitEmail() string {
|
||||
|
||||
// GetAuthorizedString generates and returns formatted public key string for authorized_keys file.
|
||||
func (key *PublicKey) GetAuthorizedString() string {
|
||||
return fmt.Sprintf(_TPL_PUBLICK_KEY, appPath, key.ID, setting.CustomConf, key.Content)
|
||||
}
|
||||
|
||||
var minimumKeySizes = map[string]int{
|
||||
"(ED25519)": 256,
|
||||
"(ECDSA)": 256,
|
||||
"(NTRU)": 1087,
|
||||
"(MCE)": 1702,
|
||||
"(McE)": 1702,
|
||||
"(RSA)": 1024,
|
||||
"(DSA)": 1024,
|
||||
return fmt.Sprintf(_TPL_PUBLICK_KEY, setting.AppPath, key.ID, setting.CustomConf, key.Content)
|
||||
}
|
||||
|
||||
func extractTypeFromBase64Key(key string) (string, error) {
|
||||
@@ -153,7 +118,7 @@ func parseKeyString(content string) (string, error) {
|
||||
|
||||
if len(lines) == 1 {
|
||||
// Parse openssh format
|
||||
parts := strings.Fields(lines[0])
|
||||
parts := strings.SplitN(lines[0], " ", 3)
|
||||
switch len(parts) {
|
||||
case 0:
|
||||
return "", errors.New("Empty key")
|
||||
@@ -228,9 +193,9 @@ func CheckPublicKeyString(content string) (_ string, err error) {
|
||||
tmpFile.Close()
|
||||
|
||||
// Check if ssh-keygen recognizes its contents.
|
||||
stdout, stderr, err := process.Exec("CheckPublicKeyString", "ssh-keygen", "-l", "-f", tmpPath)
|
||||
stdout, stderr, err := process.Exec("CheckPublicKeyString", "ssh-keygen", "-lf", tmpPath)
|
||||
if err != nil {
|
||||
return "", errors.New("ssh-keygen -l -f: " + stderr)
|
||||
return "", errors.New("ssh-keygen -lf: " + stderr)
|
||||
} else if len(stdout) < 2 {
|
||||
return "", errors.New("ssh-keygen returned not enough output to evaluate the key: " + stdout)
|
||||
}
|
||||
@@ -242,7 +207,7 @@ func CheckPublicKeyString(content string) (_ string, err error) {
|
||||
|
||||
sshKeygenOutput := strings.Split(stdout, " ")
|
||||
if len(sshKeygenOutput) < 4 {
|
||||
return content, ErrKeyUnableVerify
|
||||
return content, ErrKeyUnableVerify{stdout}
|
||||
}
|
||||
|
||||
// Check if key type and key size match.
|
||||
@@ -251,9 +216,10 @@ func CheckPublicKeyString(content string) (_ string, err error) {
|
||||
if keySize == 0 {
|
||||
return "", errors.New("cannot get key size of the given key")
|
||||
}
|
||||
keyType := strings.TrimSpace(sshKeygenOutput[len(sshKeygenOutput)-1])
|
||||
if minimumKeySize := minimumKeySizes[keyType]; minimumKeySize == 0 {
|
||||
return "", errors.New("sorry, unrecognized public key type")
|
||||
|
||||
keyType := strings.Trim(sshKeygenOutput[len(sshKeygenOutput)-1], " ()\n")
|
||||
if minimumKeySize := setting.Service.MinimumKeySizes[keyType]; minimumKeySize == 0 {
|
||||
return "", fmt.Errorf("unrecognized public key type: %s", keyType)
|
||||
} else if keySize < minimumKeySize {
|
||||
return "", fmt.Errorf("the minimum accepted size of a public key %s is %d", keyType, minimumKeySize)
|
||||
}
|
||||
@@ -321,9 +287,9 @@ func addKey(e Engine, key *PublicKey) (err error) {
|
||||
if err = ioutil.WriteFile(tmpPath, []byte(key.Content), 0644); err != nil {
|
||||
return err
|
||||
}
|
||||
stdout, stderr, err := process.Exec("AddPublicKey", "ssh-keygen", "-l", "-f", tmpPath)
|
||||
stdout, stderr, err := process.Exec("AddPublicKey", "ssh-keygen", "-lf", tmpPath)
|
||||
if err != nil {
|
||||
return errors.New("ssh-keygen -l -f: " + stderr)
|
||||
return errors.New("ssh-keygen -lf: " + stderr)
|
||||
} else if len(stdout) < 2 {
|
||||
return errors.New("not enough output for calculating fingerprint: " + stdout)
|
||||
}
|
||||
@@ -337,23 +303,23 @@ func addKey(e Engine, key *PublicKey) (err error) {
|
||||
}
|
||||
|
||||
// AddPublicKey adds new public key to database and authorized_keys file.
|
||||
func AddPublicKey(ownerID int64, name, content string) (err error) {
|
||||
if err = checkKeyContent(content); err != nil {
|
||||
return err
|
||||
func AddPublicKey(ownerID int64, name, content string) (*PublicKey, error) {
|
||||
if err := checkKeyContent(content); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Key name of same user cannot be duplicated.
|
||||
has, err := x.Where("owner_id=? AND name=?", ownerID, name).Get(new(PublicKey))
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
} else if has {
|
||||
return ErrKeyNameAlreadyUsed{ownerID, name}
|
||||
return nil, ErrKeyNameAlreadyUsed{ownerID, name}
|
||||
}
|
||||
|
||||
sess := x.NewSession()
|
||||
defer sessionRelease(sess)
|
||||
if err = sess.Begin(); err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
key := &PublicKey{
|
||||
@@ -364,10 +330,10 @@ func AddPublicKey(ownerID int64, name, content string) (err error) {
|
||||
Type: KEY_TYPE_USER,
|
||||
}
|
||||
if err = addKey(sess, key); err != nil {
|
||||
return fmt.Errorf("addKey: %v", err)
|
||||
return nil, fmt.Errorf("addKey: %v", err)
|
||||
}
|
||||
|
||||
return sess.Commit()
|
||||
return key, sess.Commit()
|
||||
}
|
||||
|
||||
// GetPublicKeyByID returns public key by given ID.
|
||||
@@ -382,6 +348,19 @@ func GetPublicKeyByID(keyID int64) (*PublicKey, error) {
|
||||
return key, nil
|
||||
}
|
||||
|
||||
// SearchPublicKeyByContent searches content as prefix (leak e-mail part)
|
||||
// and returns public key found.
|
||||
func SearchPublicKeyByContent(content string) (*PublicKey, error) {
|
||||
key := new(PublicKey)
|
||||
has, err := x.Where("content like ?", content+"%").Get(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !has {
|
||||
return nil, ErrKeyNotExist{}
|
||||
}
|
||||
return key, nil
|
||||
}
|
||||
|
||||
// ListPublicKeys returns a list of public keys belongs to given user.
|
||||
func ListPublicKeys(uid int64) ([]*PublicKey, error) {
|
||||
keys := make([]*PublicKey, 0, 5)
|
||||
@@ -471,12 +450,18 @@ func deletePublicKey(e *xorm.Session, keyID int64) error {
|
||||
}
|
||||
|
||||
// DeletePublicKey deletes SSH key information both in database and authorized_keys file.
|
||||
func DeletePublicKey(id int64) (err error) {
|
||||
has, err := x.Id(id).Get(new(PublicKey))
|
||||
func DeletePublicKey(doer *User, id int64) (err error) {
|
||||
key, err := GetPublicKeyByID(id)
|
||||
if err != nil {
|
||||
return err
|
||||
} else if !has {
|
||||
return nil
|
||||
if IsErrKeyNotExist(err) {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("GetPublicKeyByID: %v", err)
|
||||
}
|
||||
|
||||
// Check if user has access to delete this key.
|
||||
if !doer.IsAdmin && doer.Id != key.OwnerID {
|
||||
return ErrKeyAccessDenied{doer.Id, key.ID, "public"}
|
||||
}
|
||||
|
||||
sess := x.NewSession()
|
||||
@@ -540,6 +525,7 @@ type DeployKey struct {
|
||||
RepoID int64 `xorm:"UNIQUE(s) INDEX"`
|
||||
Name string
|
||||
Fingerprint string
|
||||
Content string `xorm:"-"`
|
||||
Created time.Time `xorm:"CREATED"`
|
||||
Updated time.Time // Note: Updated must below Created for AfterSet.
|
||||
HasRecentActivity bool `xorm:"-"`
|
||||
@@ -554,6 +540,16 @@ func (k *DeployKey) AfterSet(colName string, _ xorm.Cell) {
|
||||
}
|
||||
}
|
||||
|
||||
// GetContent gets associated public key content.
|
||||
func (k *DeployKey) GetContent() error {
|
||||
pkey, err := GetPublicKeyByID(k.KeyID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
k.Content = pkey.Content
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkDeployKey(e Engine, keyID, repoID int64, name string) error {
|
||||
// Note: We want error detail, not just true or false here.
|
||||
has, err := e.Where("key_id=? AND repo_id=?", keyID, repoID).Get(new(DeployKey))
|
||||
@@ -574,18 +570,19 @@ func checkDeployKey(e Engine, keyID, repoID int64, name string) error {
|
||||
}
|
||||
|
||||
// addDeployKey adds new key-repo relation.
|
||||
func addDeployKey(e *xorm.Session, keyID, repoID int64, name, fingerprint string) (err error) {
|
||||
if err = checkDeployKey(e, keyID, repoID, name); err != nil {
|
||||
return err
|
||||
func addDeployKey(e *xorm.Session, keyID, repoID int64, name, fingerprint string) (*DeployKey, error) {
|
||||
if err := checkDeployKey(e, keyID, repoID, name); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, err = e.Insert(&DeployKey{
|
||||
key := &DeployKey{
|
||||
KeyID: keyID,
|
||||
RepoID: repoID,
|
||||
Name: name,
|
||||
Fingerprint: fingerprint,
|
||||
})
|
||||
return err
|
||||
}
|
||||
_, err := e.Insert(key)
|
||||
return key, err
|
||||
}
|
||||
|
||||
// HasDeployKey returns true if public key is a deploy key of given repository.
|
||||
@@ -595,39 +592,52 @@ func HasDeployKey(keyID, repoID int64) bool {
|
||||
}
|
||||
|
||||
// AddDeployKey add new deploy key to database and authorized_keys file.
|
||||
func AddDeployKey(repoID int64, name, content string) (err error) {
|
||||
if err = checkKeyContent(content); err != nil {
|
||||
return err
|
||||
func AddDeployKey(repoID int64, name, content string) (*DeployKey, error) {
|
||||
if err := checkKeyContent(content); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
key := &PublicKey{
|
||||
pkey := &PublicKey{
|
||||
Content: content,
|
||||
Mode: ACCESS_MODE_READ,
|
||||
Type: KEY_TYPE_DEPLOY,
|
||||
}
|
||||
has, err := x.Get(key)
|
||||
has, err := x.Get(pkey)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sess := x.NewSession()
|
||||
defer sessionRelease(sess)
|
||||
if err = sess.Begin(); err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// First time use this deploy key.
|
||||
if !has {
|
||||
if err = addKey(sess, key); err != nil {
|
||||
return nil
|
||||
if err = addKey(sess, pkey); err != nil {
|
||||
return nil, fmt.Errorf("addKey: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if err = addDeployKey(sess, key.ID, repoID, name, key.Fingerprint); err != nil {
|
||||
return err
|
||||
key, err := addDeployKey(sess, pkey.ID, repoID, name, pkey.Fingerprint)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("addDeployKey: %v", err)
|
||||
}
|
||||
|
||||
return sess.Commit()
|
||||
return key, sess.Commit()
|
||||
}
|
||||
|
||||
// GetDeployKeyByID returns deploy key by given ID.
|
||||
func GetDeployKeyByID(id int64) (*DeployKey, error) {
|
||||
key := new(DeployKey)
|
||||
has, err := x.Id(id).Get(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !has {
|
||||
return nil, ErrDeployKeyNotExist{id, 0, 0}
|
||||
}
|
||||
return key, nil
|
||||
}
|
||||
|
||||
// GetDeployKeyByRepo returns deploy key by given public key ID and repository ID.
|
||||
@@ -636,8 +646,13 @@ func GetDeployKeyByRepo(keyID, repoID int64) (*DeployKey, error) {
|
||||
KeyID: keyID,
|
||||
RepoID: repoID,
|
||||
}
|
||||
_, err := x.Get(key)
|
||||
return key, err
|
||||
has, err := x.Get(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !has {
|
||||
return nil, ErrDeployKeyNotExist{0, keyID, repoID}
|
||||
}
|
||||
return key, nil
|
||||
}
|
||||
|
||||
// UpdateDeployKey updates deploy key information.
|
||||
@@ -647,13 +662,27 @@ func UpdateDeployKey(key *DeployKey) error {
|
||||
}
|
||||
|
||||
// DeleteDeployKey deletes deploy key from its repository authorized_keys file if needed.
|
||||
func DeleteDeployKey(id int64) error {
|
||||
key := &DeployKey{ID: id}
|
||||
has, err := x.Id(key.ID).Get(key)
|
||||
func DeleteDeployKey(doer *User, id int64) error {
|
||||
key, err := GetDeployKeyByID(id)
|
||||
if err != nil {
|
||||
return err
|
||||
} else if !has {
|
||||
return nil
|
||||
if IsErrDeployKeyNotExist(err) {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("GetDeployKeyByID: %v", err)
|
||||
}
|
||||
|
||||
// Check if user has access to delete this key.
|
||||
if !doer.IsAdmin {
|
||||
repo, err := GetRepositoryByID(key.RepoID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("GetRepositoryByID: %v", err)
|
||||
}
|
||||
yes, err := HasAccess(doer, repo, ACCESS_MODE_ADMIN)
|
||||
if err != nil {
|
||||
return fmt.Errorf("HasAccess: %v", err)
|
||||
} else if !yes {
|
||||
return ErrKeyAccessDenied{doer.Id, key.ID, "deploy"}
|
||||
}
|
||||
}
|
||||
|
||||
sess := x.NewSession()
|
||||
@@ -667,7 +696,7 @@ func DeleteDeployKey(id int64) error {
|
||||
}
|
||||
|
||||
// Check if this is the last reference to same key content.
|
||||
has, err = sess.Where("key_id=?", key.KeyID).Get(new(DeployKey))
|
||||
has, err := sess.Where("key_id=?", key.KeyID).Get(new(DeployKey))
|
||||
if err != nil {
|
||||
return err
|
||||
} else if !has {
|
||||
|
||||
559
models/pull.go
Normal file
559
models/pull.go
Normal file
@@ -0,0 +1,559 @@
|
||||
// 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 models
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Unknwon/com"
|
||||
"github.com/go-xorm/xorm"
|
||||
|
||||
"github.com/gogits/gogs/modules/git"
|
||||
"github.com/gogits/gogs/modules/log"
|
||||
"github.com/gogits/gogs/modules/process"
|
||||
"github.com/gogits/gogs/modules/setting"
|
||||
)
|
||||
|
||||
type PullRequestType int
|
||||
|
||||
const (
|
||||
PULL_REQUEST_GOGS PullRequestType = iota
|
||||
PLLL_ERQUEST_GIT
|
||||
)
|
||||
|
||||
type PullRequestStatus int
|
||||
|
||||
const (
|
||||
PULL_REQUEST_STATUS_CONFLICT PullRequestStatus = iota
|
||||
PULL_REQUEST_STATUS_CHECKING
|
||||
PULL_REQUEST_STATUS_MERGEABLE
|
||||
)
|
||||
|
||||
// PullRequest represents relation between pull request and repositories.
|
||||
type PullRequest struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
Type PullRequestType
|
||||
Status PullRequestStatus
|
||||
|
||||
IssueID int64 `xorm:"INDEX"`
|
||||
Issue *Issue `xorm:"-"`
|
||||
Index int64
|
||||
|
||||
HeadRepoID int64
|
||||
HeadRepo *Repository `xorm:"-"`
|
||||
BaseRepoID int64
|
||||
BaseRepo *Repository `xorm:"-"`
|
||||
HeadUserName string
|
||||
HeadBranch string
|
||||
BaseBranch string
|
||||
MergeBase string `xorm:"VARCHAR(40)"`
|
||||
|
||||
HasMerged bool
|
||||
MergedCommitID string `xorm:"VARCHAR(40)"`
|
||||
Merged time.Time
|
||||
MergerID int64
|
||||
Merger *User `xorm:"-"`
|
||||
}
|
||||
|
||||
// Note: don't try to get Pull because will end up recursive querying.
|
||||
func (pr *PullRequest) AfterSet(colName string, _ xorm.Cell) {
|
||||
switch colName {
|
||||
case "merged":
|
||||
if !pr.HasMerged {
|
||||
return
|
||||
}
|
||||
|
||||
pr.Merged = regulateTimeZone(pr.Merged)
|
||||
}
|
||||
}
|
||||
|
||||
func (pr *PullRequest) getHeadRepo(e Engine) (err error) {
|
||||
pr.HeadRepo, err = getRepositoryByID(e, pr.HeadRepoID)
|
||||
if err != nil && !IsErrRepoNotExist(err) {
|
||||
return fmt.Errorf("getRepositoryByID(head): %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pr *PullRequest) GetHeadRepo() (err error) {
|
||||
return pr.getHeadRepo(x)
|
||||
}
|
||||
|
||||
func (pr *PullRequest) GetBaseRepo() (err error) {
|
||||
if pr.BaseRepo != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
pr.BaseRepo, err = GetRepositoryByID(pr.BaseRepoID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("GetRepositoryByID(base): %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pr *PullRequest) GetMerger() (err error) {
|
||||
if !pr.HasMerged || pr.Merger != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
pr.Merger, err = GetUserByID(pr.MergerID)
|
||||
if IsErrUserNotExist(err) {
|
||||
pr.MergerID = -1
|
||||
pr.Merger = NewFakeUser()
|
||||
} else if err != nil {
|
||||
return fmt.Errorf("GetUserByID: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsChecking returns true if this pull request is still checking conflict.
|
||||
func (pr *PullRequest) IsChecking() bool {
|
||||
return pr.Status == PULL_REQUEST_STATUS_CHECKING
|
||||
}
|
||||
|
||||
// CanAutoMerge returns true if this pull request can be merged automatically.
|
||||
func (pr *PullRequest) CanAutoMerge() bool {
|
||||
return pr.Status == PULL_REQUEST_STATUS_MERGEABLE
|
||||
}
|
||||
|
||||
// Merge merges pull request to base repository.
|
||||
func (pr *PullRequest) Merge(doer *User, baseGitRepo *git.Repository) (err error) {
|
||||
sess := x.NewSession()
|
||||
defer sessionRelease(sess)
|
||||
if err = sess.Begin(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = pr.Issue.changeStatus(sess, doer, true); err != nil {
|
||||
return fmt.Errorf("Issue.changeStatus: %v", err)
|
||||
}
|
||||
|
||||
if err = pr.getHeadRepo(sess); err != nil {
|
||||
return fmt.Errorf("getHeadRepo: %v", err)
|
||||
}
|
||||
|
||||
headRepoPath := RepoPath(pr.HeadUserName, pr.HeadRepo.Name)
|
||||
headGitRepo, err := git.OpenRepository(headRepoPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("OpenRepository: %v", err)
|
||||
}
|
||||
pr.MergedCommitID, err = headGitRepo.GetCommitIdOfBranch(pr.HeadBranch)
|
||||
if err != nil {
|
||||
return fmt.Errorf("GetCommitIdOfBranch: %v", err)
|
||||
}
|
||||
|
||||
if err = mergePullRequestAction(sess, doer, pr.Issue.Repo, pr.Issue); err != nil {
|
||||
return fmt.Errorf("mergePullRequestAction: %v", err)
|
||||
}
|
||||
|
||||
pr.HasMerged = true
|
||||
pr.Merged = time.Now()
|
||||
pr.MergerID = doer.Id
|
||||
if _, err = sess.Id(pr.ID).AllCols().Update(pr); err != nil {
|
||||
return fmt.Errorf("update pull request: %v", err)
|
||||
}
|
||||
|
||||
// Clone base repo.
|
||||
tmpBasePath := path.Join(setting.AppDataPath, "tmp/repos", com.ToStr(time.Now().Nanosecond())+".git")
|
||||
os.MkdirAll(path.Dir(tmpBasePath), os.ModePerm)
|
||||
defer os.RemoveAll(path.Dir(tmpBasePath))
|
||||
|
||||
var stderr string
|
||||
if _, stderr, err = process.ExecTimeout(5*time.Minute,
|
||||
fmt.Sprintf("PullRequest.Merge (git clone): %s", tmpBasePath),
|
||||
"git", "clone", baseGitRepo.Path, tmpBasePath); err != nil {
|
||||
return fmt.Errorf("git clone: %s", stderr)
|
||||
}
|
||||
|
||||
// Check out base branch.
|
||||
if _, stderr, err = process.ExecDir(-1, tmpBasePath,
|
||||
fmt.Sprintf("PullRequest.Merge (git checkout): %s", tmpBasePath),
|
||||
"git", "checkout", pr.BaseBranch); err != nil {
|
||||
return fmt.Errorf("git checkout: %s", stderr)
|
||||
}
|
||||
|
||||
// Add head repo remote.
|
||||
if _, stderr, err = process.ExecDir(-1, tmpBasePath,
|
||||
fmt.Sprintf("PullRequest.Merge (git remote add): %s", tmpBasePath),
|
||||
"git", "remote", "add", "head_repo", headRepoPath); err != nil {
|
||||
return fmt.Errorf("git remote add [%s -> %s]: %s", headRepoPath, tmpBasePath, stderr)
|
||||
}
|
||||
|
||||
// Merge commits.
|
||||
if _, stderr, err = process.ExecDir(-1, tmpBasePath,
|
||||
fmt.Sprintf("PullRequest.Merge (git fetch): %s", tmpBasePath),
|
||||
"git", "fetch", "head_repo"); err != nil {
|
||||
return fmt.Errorf("git fetch [%s -> %s]: %s", headRepoPath, tmpBasePath, stderr)
|
||||
}
|
||||
|
||||
if _, stderr, err = process.ExecDir(-1, tmpBasePath,
|
||||
fmt.Sprintf("PullRequest.Merge (git merge --no-ff --no-commit): %s", tmpBasePath),
|
||||
"git", "merge", "--no-ff", "--no-commit", "head_repo/"+pr.HeadBranch); err != nil {
|
||||
return fmt.Errorf("git merge --no-ff --no-commit [%s]: %v - %s", tmpBasePath, err, stderr)
|
||||
}
|
||||
|
||||
sig := doer.NewGitSig()
|
||||
if _, stderr, err = process.ExecDir(-1, tmpBasePath,
|
||||
fmt.Sprintf("PullRequest.Merge (git merge): %s", tmpBasePath),
|
||||
"git", "commit", fmt.Sprintf("--author='%s <%s>'", sig.Name, sig.Email),
|
||||
"-m", fmt.Sprintf("Merge branch '%s' of %s/%s into %s", pr.HeadBranch, pr.HeadUserName, pr.HeadRepo.Name, pr.BaseBranch)); err != nil {
|
||||
return fmt.Errorf("git commit [%s]: %v - %s", tmpBasePath, err, stderr)
|
||||
}
|
||||
|
||||
// Push back to upstream.
|
||||
if _, stderr, err = process.ExecDir(-1, tmpBasePath,
|
||||
fmt.Sprintf("PullRequest.Merge (git push): %s", tmpBasePath),
|
||||
"git", "push", baseGitRepo.Path, pr.BaseBranch); err != nil {
|
||||
return fmt.Errorf("git push: %s", stderr)
|
||||
}
|
||||
|
||||
return sess.Commit()
|
||||
}
|
||||
|
||||
// patchConflicts is a list of conflit description from Git.
|
||||
var patchConflicts = []string{
|
||||
"patch does not apply",
|
||||
"already exists in working directory",
|
||||
"unrecognized input",
|
||||
}
|
||||
|
||||
// testPatch checks if patch can be merged to base repository without conflit.
|
||||
// FIXME: make a mechanism to clean up stable local copies.
|
||||
func (pr *PullRequest) testPatch() (err error) {
|
||||
if pr.BaseRepo == nil {
|
||||
pr.BaseRepo, err = GetRepositoryByID(pr.BaseRepoID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("GetRepositoryByID: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
patchPath, err := pr.BaseRepo.PatchPath(pr.Index)
|
||||
if err != nil {
|
||||
return fmt.Errorf("BaseRepo.PatchPath: %v", err)
|
||||
}
|
||||
|
||||
// Fast fail if patch does not exist, this assumes data is cruppted.
|
||||
if !com.IsFile(patchPath) {
|
||||
log.Trace("PullRequest[%d].testPatch: ignored cruppted data", pr.ID)
|
||||
return nil
|
||||
}
|
||||
|
||||
log.Trace("PullRequest[%d].testPatch(patchPath): %s", pr.ID, patchPath)
|
||||
|
||||
if err := pr.BaseRepo.UpdateLocalCopy(); err != nil {
|
||||
return fmt.Errorf("UpdateLocalCopy: %v", err)
|
||||
}
|
||||
|
||||
// Checkout base branch.
|
||||
_, stderr, err := process.ExecDir(-1, pr.BaseRepo.LocalCopyPath(),
|
||||
fmt.Sprintf("PullRequest.Merge(git checkout): %v", pr.BaseRepo.ID),
|
||||
"git", "checkout", pr.BaseBranch)
|
||||
if err != nil {
|
||||
return fmt.Errorf("git checkout: %s", stderr)
|
||||
}
|
||||
|
||||
pr.Status = PULL_REQUEST_STATUS_CHECKING
|
||||
_, stderr, err = process.ExecDir(-1, pr.BaseRepo.LocalCopyPath(),
|
||||
fmt.Sprintf("testPatch(git apply --check): %d", pr.BaseRepo.ID),
|
||||
"git", "apply", "--check", patchPath)
|
||||
if err != nil {
|
||||
for i := range patchConflicts {
|
||||
if strings.Contains(stderr, patchConflicts[i]) {
|
||||
log.Trace("PullRequest[%d].testPatch(apply): has conflit", pr.ID)
|
||||
fmt.Println(stderr)
|
||||
pr.Status = PULL_REQUEST_STATUS_CONFLICT
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf("git apply --check: %v - %s", err, stderr)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewPullRequest creates new pull request with labels for repository.
|
||||
func NewPullRequest(repo *Repository, pull *Issue, labelIDs []int64, uuids []string, pr *PullRequest, patch []byte) (err error) {
|
||||
sess := x.NewSession()
|
||||
defer sessionRelease(sess)
|
||||
if err = sess.Begin(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = newIssue(sess, repo, pull, labelIDs, uuids, true); err != nil {
|
||||
return fmt.Errorf("newIssue: %v", err)
|
||||
}
|
||||
|
||||
// Notify watchers.
|
||||
act := &Action{
|
||||
ActUserID: pull.Poster.Id,
|
||||
ActUserName: pull.Poster.Name,
|
||||
ActEmail: pull.Poster.Email,
|
||||
OpType: CREATE_PULL_REQUEST,
|
||||
Content: fmt.Sprintf("%d|%s", pull.Index, pull.Name),
|
||||
RepoID: repo.ID,
|
||||
RepoUserName: repo.Owner.Name,
|
||||
RepoName: repo.Name,
|
||||
IsPrivate: repo.IsPrivate,
|
||||
}
|
||||
if err = notifyWatchers(sess, act); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
pr.Index = pull.Index
|
||||
if err = repo.SavePatch(pr.Index, patch); err != nil {
|
||||
return fmt.Errorf("SavePatch: %v", err)
|
||||
}
|
||||
|
||||
pr.BaseRepo = repo
|
||||
if err = pr.testPatch(); err != nil {
|
||||
return fmt.Errorf("testPatch: %v", err)
|
||||
}
|
||||
if pr.Status == PULL_REQUEST_STATUS_CHECKING {
|
||||
pr.Status = PULL_REQUEST_STATUS_MERGEABLE
|
||||
}
|
||||
|
||||
pr.IssueID = pull.ID
|
||||
if _, err = sess.Insert(pr); err != nil {
|
||||
return fmt.Errorf("insert pull repo: %v", err)
|
||||
}
|
||||
|
||||
return sess.Commit()
|
||||
}
|
||||
|
||||
// GetUnmergedPullRequest returnss a pull request that is open and has not been merged
|
||||
// by given head/base and repo/branch.
|
||||
func GetUnmergedPullRequest(headRepoID, baseRepoID int64, headBranch, baseBranch string) (*PullRequest, error) {
|
||||
pr := new(PullRequest)
|
||||
has, err := x.Where("head_repo_id=? AND head_branch=? AND base_repo_id=? AND base_branch=? AND has_merged=? AND issue.is_closed=?",
|
||||
headRepoID, headBranch, baseRepoID, baseBranch, false, false).
|
||||
Join("INNER", "issue", "issue.id=pull_request.issue_id").Get(pr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !has {
|
||||
return nil, ErrPullRequestNotExist{0, 0, headRepoID, baseRepoID, headBranch, baseBranch}
|
||||
}
|
||||
|
||||
return pr, nil
|
||||
}
|
||||
|
||||
// GetUnmergedPullRequestsByHeadInfo returnss all pull requests that are open and has not been merged
|
||||
// by given head information (repo and branch).
|
||||
func GetUnmergedPullRequestsByHeadInfo(repoID int64, branch string) ([]*PullRequest, error) {
|
||||
prs := make([]*PullRequest, 0, 2)
|
||||
return prs, x.Where("head_repo_id=? AND head_branch=? AND has_merged=? AND issue.is_closed=?",
|
||||
repoID, branch, false, false).
|
||||
Join("INNER", "issue", "issue.id=pull_request.issue_id").Find(&prs)
|
||||
}
|
||||
|
||||
// GetUnmergedPullRequestsByBaseInfo returnss all pull requests that are open and has not been merged
|
||||
// by given base information (repo and branch).
|
||||
func GetUnmergedPullRequestsByBaseInfo(repoID int64, branch string) ([]*PullRequest, error) {
|
||||
prs := make([]*PullRequest, 0, 2)
|
||||
return prs, x.Where("base_repo_id=? AND base_branch=? AND has_merged=? AND issue.is_closed=?",
|
||||
repoID, branch, false, false).
|
||||
Join("INNER", "issue", "issue.id=pull_request.issue_id").Find(&prs)
|
||||
}
|
||||
|
||||
// GetPullRequestByID returns a pull request by given ID.
|
||||
func GetPullRequestByID(id int64) (*PullRequest, error) {
|
||||
pr := new(PullRequest)
|
||||
has, err := x.Id(id).Get(pr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !has {
|
||||
return nil, ErrPullRequestNotExist{id, 0, 0, 0, "", ""}
|
||||
}
|
||||
return pr, nil
|
||||
}
|
||||
|
||||
// GetPullRequestByIssueID returns pull request by given issue ID.
|
||||
func GetPullRequestByIssueID(issueID int64) (*PullRequest, error) {
|
||||
pr := &PullRequest{
|
||||
IssueID: issueID,
|
||||
}
|
||||
has, err := x.Get(pr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !has {
|
||||
return nil, ErrPullRequestNotExist{0, issueID, 0, 0, "", ""}
|
||||
}
|
||||
return pr, nil
|
||||
}
|
||||
|
||||
// Update updates all fields of pull request.
|
||||
func (pr *PullRequest) Update() error {
|
||||
_, err := x.Id(pr.ID).AllCols().Update(pr)
|
||||
return err
|
||||
}
|
||||
|
||||
// Update updates specific fields of pull request.
|
||||
func (pr *PullRequest) UpdateCols(cols ...string) error {
|
||||
_, err := x.Id(pr.ID).Cols(cols...).Update(pr)
|
||||
return err
|
||||
}
|
||||
|
||||
var PullRequestQueue = NewUniqueQueue(setting.Repository.PullRequestQueueLength)
|
||||
|
||||
// UpdatePatch generates and saves a new patch.
|
||||
func (pr *PullRequest) UpdatePatch() (err error) {
|
||||
if err = pr.GetHeadRepo(); err != nil {
|
||||
return fmt.Errorf("GetHeadRepo: %v", err)
|
||||
} else if pr.HeadRepo == nil {
|
||||
log.Trace("PullRequest[%d].UpdatePatch: ignored cruppted data", pr.ID)
|
||||
return nil
|
||||
}
|
||||
|
||||
if err = pr.GetBaseRepo(); err != nil {
|
||||
return fmt.Errorf("GetBaseRepo: %v", err)
|
||||
} else if err = pr.BaseRepo.GetOwner(); err != nil {
|
||||
return fmt.Errorf("GetOwner: %v", err)
|
||||
}
|
||||
|
||||
headGitRepo, err := git.OpenRepository(pr.HeadRepo.RepoPath())
|
||||
if err != nil {
|
||||
return fmt.Errorf("OpenRepository: %v", err)
|
||||
}
|
||||
|
||||
// Add a temporary remote.
|
||||
tmpRemote := com.ToStr(time.Now().UnixNano())
|
||||
if err = headGitRepo.AddRemote(tmpRemote, RepoPath(pr.BaseRepo.Owner.Name, pr.BaseRepo.Name)); err != nil {
|
||||
return fmt.Errorf("AddRemote: %v", err)
|
||||
}
|
||||
defer func() {
|
||||
headGitRepo.RemoveRemote(tmpRemote)
|
||||
}()
|
||||
remoteBranch := "remotes/" + tmpRemote + "/" + pr.BaseBranch
|
||||
pr.MergeBase, err = headGitRepo.GetMergeBase(remoteBranch, pr.HeadBranch)
|
||||
if err != nil {
|
||||
return fmt.Errorf("GetMergeBase: %v", err)
|
||||
} else if err = pr.Update(); err != nil {
|
||||
return fmt.Errorf("Update: %v", err)
|
||||
}
|
||||
|
||||
patch, err := headGitRepo.GetPatch(pr.MergeBase, pr.HeadBranch)
|
||||
if err != nil {
|
||||
return fmt.Errorf("GetPatch: %v", err)
|
||||
}
|
||||
|
||||
if err = pr.BaseRepo.SavePatch(pr.Index, patch); err != nil {
|
||||
return fmt.Errorf("BaseRepo.SavePatch: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddToTaskQueue adds itself to pull request test task queue.
|
||||
func (pr *PullRequest) AddToTaskQueue() {
|
||||
go PullRequestQueue.AddFunc(pr.ID, func() {
|
||||
pr.Status = PULL_REQUEST_STATUS_CHECKING
|
||||
if err := pr.UpdateCols("status"); err != nil {
|
||||
log.Error(5, "AddToTaskQueue.UpdateCols[%d].(add to queue): %v", pr.ID, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func addHeadRepoTasks(prs []*PullRequest) {
|
||||
for _, pr := range prs {
|
||||
log.Trace("addHeadRepoTasks[%d]: composing new test task", pr.ID)
|
||||
if err := pr.UpdatePatch(); err != nil {
|
||||
log.Error(4, "UpdatePatch: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
pr.AddToTaskQueue()
|
||||
}
|
||||
}
|
||||
|
||||
// AddTestPullRequestTask adds new test tasks by given head/base repository and head/base branch,
|
||||
// and generate new patch for testing as needed.
|
||||
func AddTestPullRequestTask(repoID int64, branch string) {
|
||||
log.Trace("AddTestPullRequestTask[head_repo_id: %d, head_branch: %s]: finding pull requests", repoID, branch)
|
||||
prs, err := GetUnmergedPullRequestsByHeadInfo(repoID, branch)
|
||||
if err != nil {
|
||||
log.Error(4, "Find pull requests[head_repo_id: %d, head_branch: %s]: %v", repoID, branch, err)
|
||||
return
|
||||
}
|
||||
addHeadRepoTasks(prs)
|
||||
|
||||
log.Trace("AddTestPullRequestTask[base_repo_id: %d, base_branch: %s]: finding pull requests", repoID, branch)
|
||||
prs, err = GetUnmergedPullRequestsByBaseInfo(repoID, branch)
|
||||
if err != nil {
|
||||
log.Error(4, "Find pull requests[base_repo_id: %d, base_branch: %s]: %v", repoID, branch, err)
|
||||
return
|
||||
}
|
||||
for _, pr := range prs {
|
||||
pr.AddToTaskQueue()
|
||||
}
|
||||
}
|
||||
|
||||
// checkAndUpdateStatus checks if pull request is possible to levaing checking status,
|
||||
// and set to be either conflict or mergeable.
|
||||
func (pr *PullRequest) checkAndUpdateStatus() {
|
||||
// Status is not changed to conflict means mergeable.
|
||||
if pr.Status == PULL_REQUEST_STATUS_CHECKING {
|
||||
pr.Status = PULL_REQUEST_STATUS_MERGEABLE
|
||||
}
|
||||
|
||||
// Make sure there is no waiting test to process before levaing the checking status.
|
||||
if !PullRequestQueue.Exist(pr.ID) {
|
||||
if err := pr.UpdateCols("status"); err != nil {
|
||||
log.Error(4, "Update[%d]: %v", pr.ID, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestPullRequests checks and tests untested patches of pull requests.
|
||||
// TODO: test more pull requests at same time.
|
||||
func TestPullRequests() {
|
||||
prs := make([]*PullRequest, 0, 10)
|
||||
x.Iterate(PullRequest{
|
||||
Status: PULL_REQUEST_STATUS_CHECKING,
|
||||
},
|
||||
func(idx int, bean interface{}) error {
|
||||
pr := bean.(*PullRequest)
|
||||
|
||||
if err := pr.GetBaseRepo(); err != nil {
|
||||
log.Error(3, "GetBaseRepo: %v", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := pr.testPatch(); err != nil {
|
||||
log.Error(3, "testPatch: %v", err)
|
||||
return nil
|
||||
}
|
||||
prs = append(prs, pr)
|
||||
return nil
|
||||
})
|
||||
|
||||
// Update pull request status.
|
||||
for _, pr := range prs {
|
||||
pr.checkAndUpdateStatus()
|
||||
}
|
||||
|
||||
// Start listening on new test requests.
|
||||
for prID := range PullRequestQueue.Queue() {
|
||||
log.Trace("TestPullRequests[%v]: processing test task", prID)
|
||||
PullRequestQueue.Remove(prID)
|
||||
|
||||
pr, err := GetPullRequestByID(com.StrTo(prID).MustInt64())
|
||||
if err != nil {
|
||||
log.Error(4, "GetPullRequestByID[%d]: %v", prID, err)
|
||||
continue
|
||||
} else if err = pr.testPatch(); err != nil {
|
||||
log.Error(4, "testPatch[%d]: %v", pr.ID, err)
|
||||
continue
|
||||
}
|
||||
|
||||
pr.checkAndUpdateStatus()
|
||||
}
|
||||
}
|
||||
|
||||
func InitTestPullRequests() {
|
||||
go TestPullRequests()
|
||||
}
|
||||
@@ -5,7 +5,7 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -13,18 +13,14 @@ import (
|
||||
"github.com/go-xorm/xorm"
|
||||
|
||||
"github.com/gogits/gogs/modules/git"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrReleaseAlreadyExist = errors.New("Release already exist")
|
||||
ErrReleaseNotExist = errors.New("Release does not exist")
|
||||
"github.com/gogits/gogs/modules/process"
|
||||
)
|
||||
|
||||
// Release represents a release of repository.
|
||||
type Release struct {
|
||||
Id int64
|
||||
RepoId int64
|
||||
PublisherId int64
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
RepoID int64
|
||||
PublisherID int64
|
||||
Publisher *User `xorm:"-"`
|
||||
TagName string
|
||||
LowerTagName string
|
||||
@@ -47,12 +43,16 @@ func (r *Release) AfterSet(colName string, _ xorm.Cell) {
|
||||
}
|
||||
|
||||
// IsReleaseExist returns true if release with given tag name already exists.
|
||||
func IsReleaseExist(repoId int64, tagName string) (bool, error) {
|
||||
func IsReleaseExist(repoID int64, tagName string) (bool, error) {
|
||||
if len(tagName) == 0 {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return x.Get(&Release{RepoId: repoId, LowerTagName: strings.ToLower(tagName)})
|
||||
return x.Get(&Release{RepoID: repoID, LowerTagName: strings.ToLower(tagName)})
|
||||
}
|
||||
|
||||
func init() {
|
||||
git.GetVersion()
|
||||
}
|
||||
|
||||
func createTag(gitRepo *git.Repository, rel *Release) error {
|
||||
@@ -64,7 +64,7 @@ func createTag(gitRepo *git.Repository, rel *Release) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = gitRepo.CreateTag(rel.TagName, commit.Id.String()); err != nil {
|
||||
if err = gitRepo.CreateTag(rel.TagName, commit.ID.String()); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
@@ -84,11 +84,11 @@ func createTag(gitRepo *git.Repository, rel *Release) error {
|
||||
|
||||
// CreateRelease creates a new release of repository.
|
||||
func CreateRelease(gitRepo *git.Repository, rel *Release) error {
|
||||
isExist, err := IsReleaseExist(rel.RepoId, rel.TagName)
|
||||
isExist, err := IsReleaseExist(rel.RepoID, rel.TagName)
|
||||
if err != nil {
|
||||
return err
|
||||
} else if isExist {
|
||||
return ErrReleaseAlreadyExist
|
||||
return ErrReleaseAlreadyExist{rel.TagName}
|
||||
}
|
||||
|
||||
if err = createTag(gitRepo, rel); err != nil {
|
||||
@@ -100,22 +100,35 @@ func CreateRelease(gitRepo *git.Repository, rel *Release) error {
|
||||
}
|
||||
|
||||
// GetRelease returns release by given ID.
|
||||
func GetRelease(repoId int64, tagName string) (*Release, error) {
|
||||
isExist, err := IsReleaseExist(repoId, tagName)
|
||||
func GetRelease(repoID int64, tagName string) (*Release, error) {
|
||||
isExist, err := IsReleaseExist(repoID, tagName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !isExist {
|
||||
return nil, ErrReleaseNotExist
|
||||
return nil, ErrReleaseNotExist{0, tagName}
|
||||
}
|
||||
|
||||
rel := &Release{RepoId: repoId, LowerTagName: strings.ToLower(tagName)}
|
||||
rel := &Release{RepoID: repoID, LowerTagName: strings.ToLower(tagName)}
|
||||
_, err = x.Get(rel)
|
||||
return rel, err
|
||||
}
|
||||
|
||||
// GetReleasesByRepoId returns a list of releases of repository.
|
||||
func GetReleasesByRepoId(repoId int64) (rels []*Release, err error) {
|
||||
err = x.Desc("created").Find(&rels, Release{RepoId: repoId})
|
||||
// GetReleaseByID returns release with given ID.
|
||||
func GetReleaseByID(id int64) (*Release, error) {
|
||||
rel := new(Release)
|
||||
has, err := x.Id(id).Get(rel)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !has {
|
||||
return nil, ErrReleaseNotExist{id, ""}
|
||||
}
|
||||
|
||||
return rel, nil
|
||||
}
|
||||
|
||||
// GetReleasesByRepoID returns a list of releases of repository.
|
||||
func GetReleasesByRepoID(repoID int64) (rels []*Release, err error) {
|
||||
err = x.Desc("created").Find(&rels, Release{RepoID: repoID})
|
||||
return rels, err
|
||||
}
|
||||
|
||||
@@ -150,6 +163,32 @@ func UpdateRelease(gitRepo *git.Repository, rel *Release) (err error) {
|
||||
if err = createTag(gitRepo, rel); err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = x.Id(rel.Id).AllCols().Update(rel)
|
||||
_, err = x.Id(rel.ID).AllCols().Update(rel)
|
||||
return err
|
||||
}
|
||||
|
||||
// DeleteReleaseByID deletes a release and corresponding Git tag by given ID.
|
||||
func DeleteReleaseByID(id int64) error {
|
||||
rel, err := GetReleaseByID(id)
|
||||
if err != nil {
|
||||
return fmt.Errorf("GetReleaseByID: %v", err)
|
||||
}
|
||||
|
||||
repo, err := GetRepositoryByID(rel.RepoID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("GetRepositoryByID: %v", err)
|
||||
}
|
||||
|
||||
_, stderr, err := process.ExecDir(-1, repo.RepoPath(),
|
||||
fmt.Sprintf("DeleteReleaseByID (git tag -d): %d", rel.ID),
|
||||
"git", "tag", "-d", rel.TagName)
|
||||
if err != nil && !strings.Contains(stderr, "not found") {
|
||||
return fmt.Errorf("git tag -d: %v - %s", err, stderr)
|
||||
}
|
||||
|
||||
if _, err = x.Id(rel.ID).Delete(new(Release)); err != nil {
|
||||
return fmt.Errorf("Delete: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
588
models/repo.go
588
models/repo.go
@@ -17,16 +17,21 @@ import (
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/Unknwon/cae/zip"
|
||||
"github.com/Unknwon/com"
|
||||
"github.com/go-xorm/xorm"
|
||||
"github.com/mcuadros/go-version"
|
||||
"gopkg.in/ini.v1"
|
||||
|
||||
"github.com/gogits/git-shell"
|
||||
api "github.com/gogits/go-gogs-client"
|
||||
|
||||
"github.com/gogits/gogs/modules/base"
|
||||
"github.com/gogits/gogs/modules/bindata"
|
||||
"github.com/gogits/gogs/modules/git"
|
||||
"github.com/gogits/gogs/modules/log"
|
||||
"github.com/gogits/gogs/modules/process"
|
||||
"github.com/gogits/gogs/modules/setting"
|
||||
@@ -46,6 +51,9 @@ var (
|
||||
|
||||
var (
|
||||
Gitignores, Licenses, Readmes []string
|
||||
|
||||
// Maximum items per page in forks, watchers and stars of a repo
|
||||
ItemsPerPage = 40
|
||||
)
|
||||
|
||||
func LoadRepoConfig() {
|
||||
@@ -90,16 +98,13 @@ func NewRepoContext() {
|
||||
}
|
||||
|
||||
// Check Git version.
|
||||
ver, err := git.GetVersion()
|
||||
gitVer, err := git.Version()
|
||||
if err != nil {
|
||||
log.Fatal(4, "Fail to get Git version: %v", err)
|
||||
}
|
||||
|
||||
reqVer, err := git.ParseVersion("1.7.1")
|
||||
if err != nil {
|
||||
log.Fatal(4, "Fail to parse required Git version: %v", err)
|
||||
}
|
||||
if ver.LessThan(reqVer) {
|
||||
log.Info("Git Version: %s", gitVer)
|
||||
if version.Compare("1.7.1", gitVer, ">") {
|
||||
log.Fatal(4, "Gogs requires Git version greater or equal to 1.7.1")
|
||||
}
|
||||
|
||||
@@ -157,6 +162,14 @@ type Repository struct {
|
||||
IsMirror bool
|
||||
*Mirror `xorm:"-"`
|
||||
|
||||
// Advanced settings
|
||||
EnableWiki bool `xorm:"NOT NULL DEFAULT true"`
|
||||
EnableIssues bool `xorm:"NOT NULL DEFAULT true"`
|
||||
EnableExternalTracker bool
|
||||
ExternalTrackerFormat string
|
||||
ExternalMetas map[string]string `xorm:"-"`
|
||||
EnablePulls bool `xorm:"NOT NULL DEFAULT true"`
|
||||
|
||||
IsFork bool `xorm:"NOT NULL DEFAULT false"`
|
||||
ForkID int64
|
||||
BaseRepo *Repository `xorm:"-"`
|
||||
@@ -179,9 +192,11 @@ func (repo *Repository) AfterSet(colName string, _ xorm.Cell) {
|
||||
}
|
||||
|
||||
func (repo *Repository) getOwner(e Engine) (err error) {
|
||||
if repo.Owner == nil {
|
||||
repo.Owner, err = getUserByID(e, repo.OwnerID)
|
||||
if repo.Owner != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
repo.Owner, err = getUserByID(e, repo.OwnerID)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -189,6 +204,39 @@ func (repo *Repository) GetOwner() error {
|
||||
return repo.getOwner(x)
|
||||
}
|
||||
|
||||
func (repo *Repository) mustOwner(e Engine) *User {
|
||||
if err := repo.getOwner(e); err != nil {
|
||||
return &User{
|
||||
Name: "error",
|
||||
FullName: err.Error(),
|
||||
}
|
||||
}
|
||||
|
||||
return repo.Owner
|
||||
}
|
||||
|
||||
// MustOwner always returns a valid *User object to avoid
|
||||
// conceptually impossible error handling.
|
||||
// It creates a fake object that contains error deftail
|
||||
// when error occurs.
|
||||
func (repo *Repository) MustOwner() *User {
|
||||
return repo.mustOwner(x)
|
||||
}
|
||||
|
||||
// ComposeMetas composes a map of metas for rendering external issue tracker URL.
|
||||
func (repo *Repository) ComposeMetas() map[string]string {
|
||||
if !repo.EnableExternalTracker {
|
||||
return nil
|
||||
} else if repo.ExternalMetas == nil {
|
||||
repo.ExternalMetas = map[string]string{
|
||||
"format": repo.ExternalTrackerFormat,
|
||||
"user": repo.MustOwner().Name,
|
||||
"repo": repo.Name,
|
||||
}
|
||||
}
|
||||
return repo.ExternalMetas
|
||||
}
|
||||
|
||||
// GetAssignees returns all users that have write access of repository.
|
||||
func (repo *Repository) GetAssignees() (_ []*User, err error) {
|
||||
if err = repo.GetOwner(); err != nil {
|
||||
@@ -245,22 +293,16 @@ func (repo *Repository) GetBaseRepo() (err error) {
|
||||
return err
|
||||
}
|
||||
|
||||
func (repo *Repository) repoPath(e Engine) (string, error) {
|
||||
if err := repo.getOwner(e); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return RepoPath(repo.Owner.Name, repo.Name), nil
|
||||
func (repo *Repository) repoPath(e Engine) string {
|
||||
return RepoPath(repo.mustOwner(e).Name, repo.Name)
|
||||
}
|
||||
|
||||
func (repo *Repository) RepoPath() (string, error) {
|
||||
func (repo *Repository) RepoPath() string {
|
||||
return repo.repoPath(x)
|
||||
}
|
||||
|
||||
func (repo *Repository) RepoLink() (string, error) {
|
||||
if err := repo.GetOwner(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return setting.AppSubUrl + "/" + repo.Owner.Name + "/" + repo.Name, nil
|
||||
func (repo *Repository) RepoLink() string {
|
||||
return setting.AppSubUrl + "/" + repo.MustOwner().Name + "/" + repo.Name
|
||||
}
|
||||
|
||||
func (repo *Repository) HasAccess(u *User) bool {
|
||||
@@ -272,6 +314,11 @@ func (repo *Repository) IsOwnedBy(userID int64) bool {
|
||||
return repo.OwnerID == userID
|
||||
}
|
||||
|
||||
// CanBeForked returns true if repository meets the requirements of being forked.
|
||||
func (repo *Repository) CanBeForked() bool {
|
||||
return !repo.IsBare && !repo.IsMirror
|
||||
}
|
||||
|
||||
func (repo *Repository) NextIssueIndex() int64 {
|
||||
return int64(repo.NumIssues+repo.NumPulls) + 1
|
||||
}
|
||||
@@ -288,6 +335,73 @@ func (repo *Repository) DescriptionHtml() template.HTML {
|
||||
return template.HTML(DescPattern.ReplaceAllStringFunc(base.Sanitizer.Sanitize(repo.Description), sanitize))
|
||||
}
|
||||
|
||||
func (repo *Repository) LocalCopyPath() string {
|
||||
return path.Join(setting.AppDataPath, "tmp/local", com.ToStr(repo.ID))
|
||||
}
|
||||
|
||||
func updateLocalCopy(repoPath, localPath string) error {
|
||||
if !com.IsExist(localPath) {
|
||||
if err := git.Clone(repoPath, localPath); err != nil {
|
||||
return fmt.Errorf("Clone: %v", err)
|
||||
}
|
||||
} else {
|
||||
if err := git.Pull(localPath, true); err != nil {
|
||||
return fmt.Errorf("Pull: %v", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateLocalCopy makes sure the local copy of repository is up-to-date.
|
||||
func (repo *Repository) UpdateLocalCopy() error {
|
||||
return updateLocalCopy(repo.RepoPath(), repo.LocalCopyPath())
|
||||
}
|
||||
|
||||
// PatchPath returns corresponding patch file path of repository by given issue ID.
|
||||
func (repo *Repository) PatchPath(index int64) (string, error) {
|
||||
if err := repo.GetOwner(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return filepath.Join(RepoPath(repo.Owner.Name, repo.Name), "pulls", com.ToStr(index)+".patch"), nil
|
||||
}
|
||||
|
||||
// SavePatch saves patch data to corresponding location by given issue ID.
|
||||
func (repo *Repository) SavePatch(index int64, patch []byte) error {
|
||||
patchPath, err := repo.PatchPath(index)
|
||||
if err != nil {
|
||||
return fmt.Errorf("PatchPath: %v", err)
|
||||
}
|
||||
|
||||
os.MkdirAll(filepath.Dir(patchPath), os.ModePerm)
|
||||
if err = ioutil.WriteFile(patchPath, patch, 0644); err != nil {
|
||||
return fmt.Errorf("WriteFile: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ComposePayload composes and returns *api.PayloadRepo corresponding to the repository.
|
||||
func (repo *Repository) ComposePayload() *api.PayloadRepo {
|
||||
cl := repo.CloneLink()
|
||||
return &api.PayloadRepo{
|
||||
ID: repo.ID,
|
||||
Name: repo.LowerName,
|
||||
URL: repo.RepoLink(),
|
||||
SSHURL: cl.SSH,
|
||||
CloneURL: cl.HTTPS,
|
||||
Description: repo.Description,
|
||||
Website: repo.Website,
|
||||
Watchers: repo.NumWatches,
|
||||
Owner: &api.PayloadAuthor{
|
||||
Name: repo.MustOwner().DisplayName(),
|
||||
Email: repo.MustOwner().Email,
|
||||
UserName: repo.MustOwner().Name,
|
||||
},
|
||||
Private: repo.IsPrivate,
|
||||
}
|
||||
}
|
||||
|
||||
func isRepositoryExist(e Engine, u *User, repoName string) (bool, error) {
|
||||
has, err := e.Get(&Repository{
|
||||
OwnerID: u.Id,
|
||||
@@ -308,24 +422,31 @@ type CloneLink struct {
|
||||
Git string
|
||||
}
|
||||
|
||||
// CloneLink returns clone URLs of repository.
|
||||
func (repo *Repository) CloneLink() (cl CloneLink, err error) {
|
||||
if err = repo.GetOwner(); err != nil {
|
||||
return cl, err
|
||||
func (repo *Repository) cloneLink(isWiki bool) *CloneLink {
|
||||
repoName := repo.Name
|
||||
if isWiki {
|
||||
repoName += ".wiki"
|
||||
}
|
||||
|
||||
repo.Owner = repo.MustOwner()
|
||||
cl := new(CloneLink)
|
||||
if setting.SSHPort != 22 {
|
||||
cl.SSH = fmt.Sprintf("ssh://%s@%s:%d/%s/%s.git", setting.RunUser, setting.SSHDomain, setting.SSHPort, repo.Owner.LowerName, repo.LowerName)
|
||||
cl.SSH = fmt.Sprintf("ssh://%s@%s:%d/%s/%s.git", setting.RunUser, setting.SSHDomain, setting.SSHPort, repo.Owner.Name, repoName)
|
||||
} else {
|
||||
cl.SSH = fmt.Sprintf("%s@%s:%s/%s.git", setting.RunUser, setting.SSHDomain, repo.Owner.LowerName, repo.LowerName)
|
||||
cl.SSH = fmt.Sprintf("%s@%s:%s/%s.git", setting.RunUser, setting.SSHDomain, repo.Owner.Name, repoName)
|
||||
}
|
||||
cl.HTTPS = fmt.Sprintf("%s%s/%s.git", setting.AppUrl, repo.Owner.LowerName, repo.LowerName)
|
||||
return cl, nil
|
||||
cl.HTTPS = fmt.Sprintf("%s%s/%s.git", setting.AppUrl, repo.Owner.Name, repoName)
|
||||
return cl
|
||||
}
|
||||
|
||||
// CloneLink returns clone URLs of repository.
|
||||
func (repo *Repository) CloneLink() (cl *CloneLink) {
|
||||
return repo.cloneLink(false)
|
||||
}
|
||||
|
||||
var (
|
||||
reservedNames = []string{"debug", "raw", "install", "api", "avatar", "user", "org", "help", "stars", "issues", "pulls", "commits", "repo", "template", "admin", "new"}
|
||||
reservedPatterns = []string{"*.git", "*.keys"}
|
||||
reservedPatterns = []string{"*.git", "*.keys", "*.wiki"}
|
||||
)
|
||||
|
||||
// IsUsableName checks if name is reserved or pattern of name is not allowed.
|
||||
@@ -397,6 +518,11 @@ func UpdateMirror(m *Mirror) error {
|
||||
return updateMirror(x, m)
|
||||
}
|
||||
|
||||
func createUpdateHook(repoPath string) error {
|
||||
return git.SetUpdateHook(repoPath,
|
||||
fmt.Sprintf(_TPL_UPDATE_HOOK, setting.ScriptType, "\""+setting.AppPath+"\"", setting.CustomConf))
|
||||
}
|
||||
|
||||
// MirrorRepository creates a mirror repository from source.
|
||||
func MirrorRepository(repoId int64, userName, repoName, repoPath, url string) error {
|
||||
_, stderr, err := process.ExecTimeout(10*time.Minute,
|
||||
@@ -416,13 +542,21 @@ func MirrorRepository(repoId int64, userName, repoName, repoPath, url string) er
|
||||
return nil
|
||||
}
|
||||
|
||||
type MigrateRepoOptions struct {
|
||||
Name string
|
||||
Description string
|
||||
IsPrivate bool
|
||||
IsMirror bool
|
||||
RemoteAddr string
|
||||
}
|
||||
|
||||
// MigrateRepository migrates a existing repository from other project hosting.
|
||||
func MigrateRepository(u *User, name, desc string, private, mirror bool, url string) (*Repository, error) {
|
||||
func MigrateRepository(u *User, opts MigrateRepoOptions) (*Repository, error) {
|
||||
repo, err := CreateRepository(u, CreateRepoOptions{
|
||||
Name: name,
|
||||
Description: desc,
|
||||
IsPrivate: private,
|
||||
IsMirror: mirror,
|
||||
Name: opts.Name,
|
||||
Description: opts.Description,
|
||||
IsPrivate: opts.IsPrivate,
|
||||
IsMirror: opts.IsMirror,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -432,7 +566,7 @@ func MigrateRepository(u *User, name, desc string, private, mirror bool, url str
|
||||
tmpDir := filepath.Join(os.TempDir(), fmt.Sprintf("%d", time.Now().Nanosecond()))
|
||||
os.MkdirAll(tmpDir, os.ModePerm)
|
||||
|
||||
repoPath := RepoPath(u.Name, name)
|
||||
repoPath := RepoPath(u.Name, opts.Name)
|
||||
|
||||
if u.IsOrganization() {
|
||||
t, err := u.GetOwnerTeam()
|
||||
@@ -445,8 +579,8 @@ func MigrateRepository(u *User, name, desc string, private, mirror bool, url str
|
||||
}
|
||||
|
||||
repo.IsBare = false
|
||||
if mirror {
|
||||
if err = MirrorRepository(repo.ID, u.Name, repo.Name, repoPath, url); err != nil {
|
||||
if opts.IsMirror {
|
||||
if err = MirrorRepository(repo.ID, u.Name, repo.Name, repoPath, opts.RemoteAddr); err != nil {
|
||||
return repo, err
|
||||
}
|
||||
repo.IsMirror = true
|
||||
@@ -458,20 +592,47 @@ func MigrateRepository(u *User, name, desc string, private, mirror bool, url str
|
||||
// FIXME: this command could for both migrate and mirror
|
||||
_, stderr, err := process.ExecTimeout(10*time.Minute,
|
||||
fmt.Sprintf("MigrateRepository: %s", repoPath),
|
||||
"git", "clone", "--mirror", "--bare", "--quiet", url, repoPath)
|
||||
"git", "clone", "--mirror", "--bare", "--quiet", opts.RemoteAddr, repoPath)
|
||||
if err != nil {
|
||||
return repo, fmt.Errorf("git clone --mirror --bare --quiet: %v", stderr)
|
||||
} else if err = createUpdateHook(repoPath); err != nil {
|
||||
return repo, fmt.Errorf("create update hook: %v", err)
|
||||
}
|
||||
|
||||
// Check if repository has master branch, if so set it to default branch.
|
||||
// Clean up mirror info which prevents "push --all".
|
||||
configPath := filepath.Join(repoPath, "/config")
|
||||
cfg, err := ini.Load(configPath)
|
||||
if err != nil {
|
||||
return repo, fmt.Errorf("open config file: %v", err)
|
||||
}
|
||||
cfg.DeleteSection("remote \"origin\"")
|
||||
if err = cfg.SaveToIndent(configPath, "\t"); err != nil {
|
||||
return repo, fmt.Errorf("save config file: %v", err)
|
||||
}
|
||||
|
||||
// Check if repository is empty.
|
||||
_, stderr, err = com.ExecCmdDir(repoPath, "git", "log", "-1")
|
||||
if err != nil {
|
||||
if strings.Contains(stderr, "fatal: bad default revision 'HEAD'") {
|
||||
repo.IsBare = true
|
||||
} else {
|
||||
return repo, fmt.Errorf("check bare: %v - %s", err, stderr)
|
||||
}
|
||||
}
|
||||
|
||||
// Try to get HEAD branch and set it as default branch.
|
||||
gitRepo, err := git.OpenRepository(repoPath)
|
||||
if err != nil {
|
||||
return repo, fmt.Errorf("open git repository: %v", err)
|
||||
log.Error(4, "OpenRepository: %v", err)
|
||||
return repo, nil
|
||||
}
|
||||
if gitRepo.IsBranchExist("master") {
|
||||
repo.DefaultBranch = "master"
|
||||
headBranch, err := gitRepo.GetHEADBranch()
|
||||
if err != nil {
|
||||
log.Error(4, "GetHEADBranch: %v", err)
|
||||
return repo, nil
|
||||
}
|
||||
if headBranch != nil {
|
||||
repo.DefaultBranch = headBranch.Name
|
||||
}
|
||||
|
||||
return repo, UpdateRepository(repo, false)
|
||||
@@ -481,33 +642,26 @@ func MigrateRepository(u *User, name, desc string, private, mirror bool, url str
|
||||
func initRepoCommit(tmpPath string, sig *git.Signature) (err error) {
|
||||
var stderr string
|
||||
if _, stderr, err = process.ExecDir(-1,
|
||||
tmpPath, fmt.Sprintf("initRepoCommit(git add): %s", tmpPath),
|
||||
tmpPath, fmt.Sprintf("initRepoCommit (git add): %s", tmpPath),
|
||||
"git", "add", "--all"); err != nil {
|
||||
return fmt.Errorf("git add: %s", stderr)
|
||||
}
|
||||
|
||||
if _, stderr, err = process.ExecDir(-1,
|
||||
tmpPath, fmt.Sprintf("initRepoCommit(git commit): %s", tmpPath),
|
||||
tmpPath, fmt.Sprintf("initRepoCommit (git commit): %s", tmpPath),
|
||||
"git", "commit", fmt.Sprintf("--author='%s <%s>'", sig.Name, sig.Email),
|
||||
"-m", "initial commit"); err != nil {
|
||||
return fmt.Errorf("git commit: %s", stderr)
|
||||
}
|
||||
|
||||
if _, stderr, err = process.ExecDir(-1,
|
||||
tmpPath, fmt.Sprintf("initRepoCommit(git push): %s", tmpPath),
|
||||
tmpPath, fmt.Sprintf("initRepoCommit (git push): %s", tmpPath),
|
||||
"git", "push", "origin", "master"); err != nil {
|
||||
return fmt.Errorf("git push: %s", stderr)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func createUpdateHook(repoPath string) error {
|
||||
hookPath := path.Join(repoPath, "hooks/update")
|
||||
os.MkdirAll(path.Dir(hookPath), os.ModePerm)
|
||||
return ioutil.WriteFile(hookPath,
|
||||
[]byte(fmt.Sprintf(_TPL_UPDATE_HOOK, setting.ScriptType, "\""+appPath+"\"", setting.CustomConf)), 0777)
|
||||
}
|
||||
|
||||
type CreateRepoOptions struct {
|
||||
Name string
|
||||
Description string
|
||||
@@ -544,10 +698,7 @@ func prepareRepoCommit(repo *Repository, tmpDir, repoPath string, opts CreateRep
|
||||
return fmt.Errorf("getRepoInitFile[%s]: %v", opts.Readme, err)
|
||||
}
|
||||
|
||||
cloneLink, err := repo.CloneLink()
|
||||
if err != nil {
|
||||
return fmt.Errorf("CloneLink: %v", err)
|
||||
}
|
||||
cloneLink := repo.CloneLink()
|
||||
match := map[string]string{
|
||||
"Name": repo.Name,
|
||||
"Description": repo.Description,
|
||||
@@ -596,26 +747,21 @@ func prepareRepoCommit(repo *Repository, tmpDir, repoPath string, opts CreateRep
|
||||
}
|
||||
|
||||
// InitRepository initializes README and .gitignore if needed.
|
||||
func initRepository(e Engine, repoPath string, u *User, repo *Repository, opts CreateRepoOptions) error {
|
||||
func initRepository(e Engine, repoPath string, u *User, repo *Repository, opts CreateRepoOptions) (err error) {
|
||||
// Somehow the directory could exist.
|
||||
if com.IsExist(repoPath) {
|
||||
return fmt.Errorf("initRepository: path already exists: %s", repoPath)
|
||||
}
|
||||
|
||||
// Init bare new repository.
|
||||
os.MkdirAll(repoPath, os.ModePerm)
|
||||
_, stderr, err := process.ExecDir(-1, repoPath,
|
||||
fmt.Sprintf("initRepository(git init --bare): %s", repoPath), "git", "init", "--bare")
|
||||
if err != nil {
|
||||
return fmt.Errorf("git init --bare: %v - %s", err, stderr)
|
||||
}
|
||||
|
||||
if err := createUpdateHook(repoPath); err != nil {
|
||||
return err
|
||||
if err = git.InitRepository(repoPath, true); err != nil {
|
||||
return fmt.Errorf("InitRepository: %v", err)
|
||||
} else if err = createUpdateHook(repoPath); err != nil {
|
||||
return fmt.Errorf("createUpdateHook: %v", err)
|
||||
}
|
||||
|
||||
tmpDir := filepath.Join(os.TempDir(), "gogs-"+repo.Name+"-"+com.ToStr(time.Now().Nanosecond()))
|
||||
fmt.Println(tmpDir)
|
||||
|
||||
// Initialize repository according to user's choice.
|
||||
if opts.AutoInit {
|
||||
os.MkdirAll(tmpDir, os.ModePerm)
|
||||
@@ -699,12 +845,15 @@ func createRepository(e *xorm.Session, u *User, repo *Repository) (err error) {
|
||||
// CreateRepository creates a repository for given user or organization.
|
||||
func CreateRepository(u *User, opts CreateRepoOptions) (_ *Repository, err error) {
|
||||
repo := &Repository{
|
||||
OwnerID: u.Id,
|
||||
Owner: u,
|
||||
Name: opts.Name,
|
||||
LowerName: strings.ToLower(opts.Name),
|
||||
Description: opts.Description,
|
||||
IsPrivate: opts.IsPrivate,
|
||||
OwnerID: u.Id,
|
||||
Owner: u,
|
||||
Name: opts.Name,
|
||||
LowerName: strings.ToLower(opts.Name),
|
||||
Description: opts.Description,
|
||||
IsPrivate: opts.IsPrivate,
|
||||
EnableWiki: true,
|
||||
EnableIssues: true,
|
||||
EnablePulls: true,
|
||||
}
|
||||
|
||||
sess := x.NewSession()
|
||||
@@ -761,21 +910,16 @@ func CountPublicRepositories() int64 {
|
||||
return countRepositories(false)
|
||||
}
|
||||
|
||||
// GetRepositoriesWithUsers returns given number of repository objects with offset.
|
||||
// It also auto-gets corresponding users.
|
||||
func GetRepositoriesWithUsers(num, offset int) ([]*Repository, error) {
|
||||
repos := make([]*Repository, 0, num)
|
||||
if err := x.Limit(num, offset).Asc("id").Find(&repos); err != nil {
|
||||
// RepositoriesWithUsers returns number of repos in given page.
|
||||
func RepositoriesWithUsers(page, pageSize int) (_ []*Repository, err error) {
|
||||
repos := make([]*Repository, 0, pageSize)
|
||||
if err = x.Limit(pageSize, (page-1)*pageSize).Asc("id").Find(&repos); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, repo := range repos {
|
||||
repo.Owner = &User{Id: repo.OwnerID}
|
||||
has, err := x.Get(repo.Owner)
|
||||
if err != nil {
|
||||
for i := range repos {
|
||||
if err = repos[i].GetOwner(); err != nil {
|
||||
return nil, err
|
||||
} else if !has {
|
||||
return nil, ErrUserNotExist{repo.OwnerID, ""}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -821,9 +965,9 @@ func TransferOwnership(u *User, newOwnerName string, repo *Repository) error {
|
||||
}
|
||||
|
||||
// Remove redundant collaborators.
|
||||
collaborators, err := repo.GetCollaborators()
|
||||
collaborators, err := repo.getCollaborators(sess)
|
||||
if err != nil {
|
||||
return fmt.Errorf("GetCollaborators: %v", err)
|
||||
return fmt.Errorf("getCollaborators: %v", err)
|
||||
}
|
||||
|
||||
// Dummy object.
|
||||
@@ -859,9 +1003,9 @@ func TransferOwnership(u *User, newOwnerName string, repo *Repository) error {
|
||||
}
|
||||
|
||||
if newOwner.IsOrganization() {
|
||||
t, err := newOwner.GetOwnerTeam()
|
||||
t, err := newOwner.getOwnerTeam(sess)
|
||||
if err != nil {
|
||||
return fmt.Errorf("GetOwnerTeam: %v", err)
|
||||
return fmt.Errorf("getOwnerTeam: %v", err)
|
||||
} else if err = t.addRepository(sess, repo); err != nil {
|
||||
return fmt.Errorf("add to owner team: %v", err)
|
||||
}
|
||||
@@ -887,7 +1031,14 @@ func TransferOwnership(u *User, newOwnerName string, repo *Repository) error {
|
||||
|
||||
// Change repository directory name.
|
||||
if err = os.Rename(RepoPath(owner.Name, repo.Name), RepoPath(newOwner.Name, repo.Name)); err != nil {
|
||||
return fmt.Errorf("rename directory: %v", err)
|
||||
return fmt.Errorf("rename repository directory: %v", err)
|
||||
}
|
||||
|
||||
wikiPath := WikiPath(owner.Name, repo.Name)
|
||||
if com.IsExist(wikiPath) {
|
||||
if err = os.Rename(wikiPath, WikiPath(newOwner.Name, repo.Name)); err != nil {
|
||||
return fmt.Errorf("rename repository wiki: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
return sess.Commit()
|
||||
@@ -909,7 +1060,18 @@ func ChangeRepositoryName(u *User, oldRepoName, newRepoName string) (err error)
|
||||
}
|
||||
|
||||
// Change repository directory name.
|
||||
return os.Rename(RepoPath(u.LowerName, oldRepoName), RepoPath(u.LowerName, newRepoName))
|
||||
if err = os.Rename(RepoPath(u.Name, oldRepoName), RepoPath(u.Name, newRepoName)); err != nil {
|
||||
return fmt.Errorf("rename repository directory: %v", err)
|
||||
}
|
||||
|
||||
wikiPath := WikiPath(u.Name, oldRepoName)
|
||||
if com.IsExist(wikiPath) {
|
||||
if err = os.Rename(wikiPath, WikiPath(u.Name, newRepoName)); err != nil {
|
||||
return fmt.Errorf("rename repository wiki: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getRepositoriesByForkID(e Engine, forkID int64) ([]*Repository, error) {
|
||||
@@ -940,13 +1102,11 @@ func updateRepository(e Engine, repo *Repository, visibilityChanged bool) (err e
|
||||
if err = repo.getOwner(e); err != nil {
|
||||
return fmt.Errorf("getOwner: %v", err)
|
||||
}
|
||||
if !repo.Owner.IsOrganization() {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Organization repository need to recalculate access table when visivility is changed.
|
||||
if err = repo.recalculateTeamAccesses(e, 0); err != nil {
|
||||
return fmt.Errorf("recalculateTeamAccesses: %v", err)
|
||||
if repo.Owner.IsOrganization() {
|
||||
// Organization repository need to recalculate access table when visivility is changed.
|
||||
if err = repo.recalculateTeamAccesses(e, 0); err != nil {
|
||||
return fmt.Errorf("recalculateTeamAccesses: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
forkRepos, err := getRepositoriesByForkID(e, repo.ID)
|
||||
@@ -1015,26 +1175,20 @@ func DeleteRepository(uid, repoID int64) error {
|
||||
}
|
||||
}
|
||||
|
||||
if _, err = sess.Delete(&Repository{ID: repoID}); err != nil {
|
||||
return err
|
||||
} else if _, err = sess.Delete(&Access{RepoID: repo.ID}); err != nil {
|
||||
return err
|
||||
} else if _, err = sess.Delete(&Action{RepoID: repo.ID}); err != nil {
|
||||
return err
|
||||
} else if _, err = sess.Delete(&Watch{RepoID: repoID}); err != nil {
|
||||
return err
|
||||
} else if _, err = sess.Delete(&Mirror{RepoID: repoID}); err != nil {
|
||||
return err
|
||||
} else if _, err = sess.Delete(&IssueUser{RepoID: repoID}); err != nil {
|
||||
return err
|
||||
} else if _, err = sess.Delete(&Milestone{RepoID: repoID}); err != nil {
|
||||
return err
|
||||
} else if _, err = sess.Delete(&Release{RepoId: repoID}); err != nil {
|
||||
return err
|
||||
} else if _, err = sess.Delete(&Collaboration{RepoID: repoID}); err != nil {
|
||||
return err
|
||||
} else if _, err = sess.Delete(&PullRequest{BaseRepoID: repoID}); err != nil {
|
||||
return err
|
||||
if err = deleteBeans(sess,
|
||||
&Repository{ID: repoID},
|
||||
&Access{RepoID: repo.ID},
|
||||
&Action{RepoID: repo.ID},
|
||||
&Watch{RepoID: repoID},
|
||||
&Star{RepoID: repoID},
|
||||
&Mirror{RepoID: repoID},
|
||||
&IssueUser{RepoID: repoID},
|
||||
&Milestone{RepoID: repoID},
|
||||
&Release{RepoID: repoID},
|
||||
&Collaboration{RepoID: repoID},
|
||||
&PullRequest{BaseRepoID: repoID},
|
||||
); err != nil {
|
||||
return fmt.Errorf("deleteBeans: %v", err)
|
||||
}
|
||||
|
||||
// Delete comments and attachments.
|
||||
@@ -1076,15 +1230,21 @@ func DeleteRepository(uid, repoID int64) error {
|
||||
}
|
||||
|
||||
// Remove repository files.
|
||||
repoPath, err := repo.repoPath(sess)
|
||||
if err != nil {
|
||||
return fmt.Errorf("RepoPath: %v", err)
|
||||
}
|
||||
repoPath := repo.repoPath(sess)
|
||||
if err = os.RemoveAll(repoPath); err != nil {
|
||||
desc := fmt.Sprintf("delete repository files[%s]: %v", repoPath, err)
|
||||
desc := fmt.Sprintf("delete repository files [%s]: %v", repoPath, err)
|
||||
log.Warn(desc)
|
||||
if err = CreateRepositoryNotice(desc); err != nil {
|
||||
log.Error(4, "add notice: %v", err)
|
||||
log.Error(4, "CreateRepositoryNotice: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
wikiPath := repo.WikiPath()
|
||||
if err = os.RemoveAll(wikiPath); err != nil {
|
||||
desc := fmt.Sprintf("delete repository wiki [%s]: %v", wikiPath, err)
|
||||
log.Warn(desc)
|
||||
if err = CreateRepositoryNotice(desc); err != nil {
|
||||
log.Error(4, "CreateRepositoryNotice: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1185,9 +1345,13 @@ func GetRecentUpdatedRepositories(page int) (repos []*Repository, err error) {
|
||||
Where("is_private=?", false).Limit(setting.ExplorePagingNum).Desc("updated").Find(&repos)
|
||||
}
|
||||
|
||||
func getRepositoryCount(e Engine, u *User) (int64, error) {
|
||||
return x.Count(&Repository{OwnerID: u.Id})
|
||||
}
|
||||
|
||||
// GetRepositoryCount returns the total number of repositories of user.
|
||||
func GetRepositoryCount(u *User) (int64, error) {
|
||||
return x.Count(&Repository{OwnerID: u.Id})
|
||||
return getRepositoryCount(x, u)
|
||||
}
|
||||
|
||||
type SearchOption struct {
|
||||
@@ -1212,7 +1376,7 @@ func SearchRepositoryByName(opt SearchOption) (repos []*Repository, err error) {
|
||||
sess.Where("owner_id=?", opt.Uid)
|
||||
}
|
||||
if !opt.Private {
|
||||
sess.And("is_private=false")
|
||||
sess.And("is_private=?", false)
|
||||
}
|
||||
sess.And("lower_name like ?", "%"+opt.Keyword+"%").Find(&repos)
|
||||
return repos, err
|
||||
@@ -1223,39 +1387,99 @@ func DeleteRepositoryArchives() error {
|
||||
return x.Where("id > 0").Iterate(new(Repository),
|
||||
func(idx int, bean interface{}) error {
|
||||
repo := bean.(*Repository)
|
||||
if err := repo.GetOwner(); err != nil {
|
||||
return err
|
||||
}
|
||||
return os.RemoveAll(filepath.Join(RepoPath(repo.Owner.Name, repo.Name), "archives"))
|
||||
return os.RemoveAll(filepath.Join(repo.RepoPath(), "archives"))
|
||||
})
|
||||
}
|
||||
|
||||
// DeleteMissingRepositories deletes all repository records that lost Git files.
|
||||
func DeleteMissingRepositories() error {
|
||||
repos := make([]*Repository, 0, 5)
|
||||
if err := x.Where("id > 0").Iterate(new(Repository),
|
||||
func(idx int, bean interface{}) error {
|
||||
repo := bean.(*Repository)
|
||||
if !com.IsDir(repo.RepoPath()) {
|
||||
repos = append(repos, repo)
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
if err2 := CreateRepositoryNotice(fmt.Sprintf("DeleteMissingRepositories: %v", err)); err2 != nil {
|
||||
log.Error(4, "CreateRepositoryNotice: %v", err2)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(repos) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, repo := range repos {
|
||||
log.Trace("Deleting %d/%d...", repo.OwnerID, repo.ID)
|
||||
if err := DeleteRepository(repo.OwnerID, repo.ID); err != nil {
|
||||
if err2 := CreateRepositoryNotice(fmt.Sprintf("DeleteRepository [%d]: %v", repo.ID, err)); err2 != nil {
|
||||
log.Error(4, "CreateRepositoryNotice: %v", err2)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RewriteRepositoryUpdateHook rewrites all repositories' update hook.
|
||||
func RewriteRepositoryUpdateHook() error {
|
||||
return x.Where("id > 0").Iterate(new(Repository),
|
||||
func(idx int, bean interface{}) error {
|
||||
repo := bean.(*Repository)
|
||||
if err := repo.GetOwner(); err != nil {
|
||||
return err
|
||||
}
|
||||
return createUpdateHook(RepoPath(repo.Owner.Name, repo.Name))
|
||||
return createUpdateHook(repo.RepoPath())
|
||||
})
|
||||
}
|
||||
|
||||
var (
|
||||
// Prevent duplicate running tasks.
|
||||
isMirrorUpdating = false
|
||||
isGitFscking = false
|
||||
isCheckingRepos = false
|
||||
// statusPool represents a pool of status with true/false.
|
||||
type statusPool struct {
|
||||
lock sync.RWMutex
|
||||
pool map[string]bool
|
||||
}
|
||||
|
||||
// Start sets value of given name to true in the pool.
|
||||
func (p *statusPool) Start(name string) {
|
||||
p.lock.Lock()
|
||||
defer p.lock.Unlock()
|
||||
|
||||
p.pool[name] = true
|
||||
}
|
||||
|
||||
// Stop sets value of given name to false in the pool.
|
||||
func (p *statusPool) Stop(name string) {
|
||||
p.lock.Lock()
|
||||
defer p.lock.Unlock()
|
||||
|
||||
p.pool[name] = false
|
||||
}
|
||||
|
||||
// IsRunning checks if value of given name is set to true in the pool.
|
||||
func (p *statusPool) IsRunning(name string) bool {
|
||||
p.lock.RLock()
|
||||
defer p.lock.RUnlock()
|
||||
|
||||
return p.pool[name]
|
||||
}
|
||||
|
||||
// Prevent duplicate running tasks.
|
||||
var taskStatusPool = &statusPool{
|
||||
pool: make(map[string]bool),
|
||||
}
|
||||
|
||||
const (
|
||||
_MIRROR_UPDATE = "mirror_update"
|
||||
_GIT_FSCK = "git_fsck"
|
||||
_CHECK_REPOs = "check_repos"
|
||||
)
|
||||
|
||||
// MirrorUpdate checks and updates mirror repositories.
|
||||
func MirrorUpdate() {
|
||||
if isMirrorUpdating {
|
||||
if taskStatusPool.IsRunning(_MIRROR_UPDATE) {
|
||||
return
|
||||
}
|
||||
isMirrorUpdating = true
|
||||
defer func() { isMirrorUpdating = false }()
|
||||
taskStatusPool.Start(_MIRROR_UPDATE)
|
||||
defer taskStatusPool.Stop(_MIRROR_UPDATE)
|
||||
|
||||
log.Trace("Doing: MirrorUpdate")
|
||||
|
||||
@@ -1271,11 +1495,7 @@ func MirrorUpdate() {
|
||||
return nil
|
||||
}
|
||||
|
||||
repoPath, err := m.Repo.RepoPath()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Repo.RepoPath: %v", err)
|
||||
}
|
||||
|
||||
repoPath := m.Repo.RepoPath()
|
||||
if _, stderr, err := process.ExecDir(10*time.Minute,
|
||||
repoPath, fmt.Sprintf("MirrorUpdate: %s", repoPath),
|
||||
"git", "remote", "update", "--prune"); err != nil {
|
||||
@@ -1303,11 +1523,11 @@ func MirrorUpdate() {
|
||||
|
||||
// GitFsck calls 'git fsck' to check repository health.
|
||||
func GitFsck() {
|
||||
if isGitFscking {
|
||||
if taskStatusPool.IsRunning(_GIT_FSCK) {
|
||||
return
|
||||
}
|
||||
isGitFscking = true
|
||||
defer func() { isGitFscking = false }()
|
||||
taskStatusPool.Start(_GIT_FSCK)
|
||||
defer taskStatusPool.Stop(_GIT_FSCK)
|
||||
|
||||
log.Trace("Doing: GitFsck")
|
||||
|
||||
@@ -1315,12 +1535,8 @@ func GitFsck() {
|
||||
if err := x.Where("id>0").Iterate(new(Repository),
|
||||
func(idx int, bean interface{}) error {
|
||||
repo := bean.(*Repository)
|
||||
repoPath, err := repo.RepoPath()
|
||||
if err != nil {
|
||||
return fmt.Errorf("RepoPath: %v", err)
|
||||
}
|
||||
|
||||
_, _, err = process.ExecDir(-1, repoPath, "Repository health check", "git", args...)
|
||||
repoPath := repo.RepoPath()
|
||||
_, _, err := process.ExecDir(-1, repoPath, "Repository health check", "git", args...)
|
||||
if err != nil {
|
||||
desc := fmt.Sprintf("Fail to health check repository(%s)", repoPath)
|
||||
log.Warn(desc)
|
||||
@@ -1372,11 +1588,11 @@ func repoStatsCheck(checker *repoChecker) {
|
||||
}
|
||||
|
||||
func CheckRepoStats() {
|
||||
if isCheckingRepos {
|
||||
if taskStatusPool.IsRunning(_CHECK_REPOs) {
|
||||
return
|
||||
}
|
||||
isCheckingRepos = true
|
||||
defer func() { isCheckingRepos = false }()
|
||||
taskStatusPool.Start(_CHECK_REPOs)
|
||||
defer taskStatusPool.Stop(_CHECK_REPOs)
|
||||
|
||||
log.Trace("Doing: CheckRepoStats")
|
||||
|
||||
@@ -1405,12 +1621,18 @@ func CheckRepoStats() {
|
||||
"UPDATE `user` SET num_repos=(SELECT COUNT(*) FROM `repository` WHERE owner_id=?) WHERE id=?",
|
||||
"user count 'num_repos'",
|
||||
},
|
||||
// Issue.NumComments
|
||||
{
|
||||
"SELECT `issue`.id FROM `issue` WHERE `issue`.num_comments!=(SELECT COUNT(*) FROM `comment` WHERE issue_id=`issue`.id AND type=0)",
|
||||
"UPDATE `issue` SET num_comments=(SELECT COUNT(*) FROM `comment` WHERE issue_id=? AND type=0) WHERE id=?",
|
||||
"issue count 'num_comments'",
|
||||
},
|
||||
}
|
||||
for i := range checkers {
|
||||
repoStatsCheck(checkers[i])
|
||||
}
|
||||
|
||||
// FIXME: use checker when v0.8, stop supporting old fork repo format.
|
||||
// FIXME: use checker when v0.9, stop supporting old fork repo format.
|
||||
// ***** START: Repository.NumForks *****
|
||||
results, err := x.Query("SELECT repo.id FROM `repository` repo WHERE repo.num_forks!=(SELECT COUNT(*) FROM `repository` WHERE fork_id=repo.id)")
|
||||
if err != nil {
|
||||
@@ -1555,15 +1777,19 @@ type Watch struct {
|
||||
RepoID int64 `xorm:"UNIQUE(watch)"`
|
||||
}
|
||||
|
||||
func isWatching(e Engine, uid, repoId int64) bool {
|
||||
has, _ := e.Get(&Watch{0, uid, repoId})
|
||||
return has
|
||||
}
|
||||
|
||||
// IsWatching checks if user has watched given repository.
|
||||
func IsWatching(uid, repoId int64) bool {
|
||||
has, _ := x.Get(&Watch{0, uid, repoId})
|
||||
return has
|
||||
return isWatching(x, uid, repoId)
|
||||
}
|
||||
|
||||
func watchRepo(e Engine, uid, repoId int64, watch bool) (err error) {
|
||||
if watch {
|
||||
if IsWatching(uid, repoId) {
|
||||
if isWatching(e, uid, repoId) {
|
||||
return nil
|
||||
}
|
||||
if _, err = e.Insert(&Watch{RepoID: repoId, UserID: uid}); err != nil {
|
||||
@@ -1571,7 +1797,7 @@ func watchRepo(e Engine, uid, repoId int64, watch bool) (err error) {
|
||||
}
|
||||
_, err = e.Exec("UPDATE `repository` SET num_watches = num_watches + 1 WHERE id = ?", repoId)
|
||||
} else {
|
||||
if !IsWatching(uid, repoId) {
|
||||
if !isWatching(e, uid, repoId) {
|
||||
return nil
|
||||
}
|
||||
if _, err = e.Delete(&Watch{0, uid, repoId}); err != nil {
|
||||
@@ -1587,15 +1813,21 @@ func WatchRepo(uid, repoId int64, watch bool) (err error) {
|
||||
return watchRepo(x, uid, repoId, watch)
|
||||
}
|
||||
|
||||
func getWatchers(e Engine, rid int64) ([]*Watch, error) {
|
||||
func getWatchers(e Engine, repoID int64) ([]*Watch, error) {
|
||||
watches := make([]*Watch, 0, 10)
|
||||
err := e.Find(&watches, &Watch{RepoID: rid})
|
||||
return watches, err
|
||||
return watches, e.Find(&watches, &Watch{RepoID: repoID})
|
||||
}
|
||||
|
||||
// GetWatchers returns all watchers of given repository.
|
||||
func GetWatchers(rid int64) ([]*Watch, error) {
|
||||
return getWatchers(x, rid)
|
||||
func GetWatchers(repoID int64) ([]*Watch, error) {
|
||||
return getWatchers(x, repoID)
|
||||
}
|
||||
|
||||
// Repository.GetWatchers returns range of users watching given repository.
|
||||
func (repo *Repository) GetWatchers(page int) ([]*User, error) {
|
||||
users := make([]*User, 0, ItemsPerPage)
|
||||
return users, x.Limit(ItemsPerPage, (page-1)*ItemsPerPage).
|
||||
Where("repo_id=?", repo.ID).Join("LEFT", "watch", "user.id=watch.user_id").Find(&users)
|
||||
}
|
||||
|
||||
func notifyWatchers(e Engine, act *Action) error {
|
||||
@@ -1639,7 +1871,7 @@ func NotifyWatchers(act *Action) error {
|
||||
|
||||
type Star struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
UID int64 `xorm:"uid UNIQUE(s)"`
|
||||
UID int64 `xorm:"UNIQUE(s)"`
|
||||
RepoID int64 `xorm:"UNIQUE(s)"`
|
||||
}
|
||||
|
||||
@@ -1675,6 +1907,12 @@ func IsStaring(uid, repoId int64) bool {
|
||||
return has
|
||||
}
|
||||
|
||||
func (repo *Repository) GetStargazers(page int) ([]*User, error) {
|
||||
users := make([]*User, 0, ItemsPerPage)
|
||||
return users, x.Limit(ItemsPerPage, (page-1)*ItemsPerPage).
|
||||
Where("repo_id=?", repo.ID).Join("LEFT", "star", "user.id=star.uid").Find(&users)
|
||||
}
|
||||
|
||||
// ___________ __
|
||||
// \_ _____/__________| | __
|
||||
// | __)/ _ \_ __ \ |/ /
|
||||
@@ -1716,15 +1954,10 @@ func ForkRepository(u *User, oldRepo *Repository, name, desc string) (_ *Reposit
|
||||
return nil, err
|
||||
}
|
||||
|
||||
oldRepoPath, err := oldRepo.RepoPath()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get old repository path: %v", err)
|
||||
}
|
||||
|
||||
repoPath := RepoPath(u.Name, repo.Name)
|
||||
_, stderr, err := process.ExecTimeout(10*time.Minute,
|
||||
fmt.Sprintf("ForkRepository(git clone): %s/%s", u.Name, repo.Name),
|
||||
"git", "clone", "--bare", oldRepoPath, repoPath)
|
||||
"git", "clone", "--bare", oldRepo.RepoPath(), repoPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("git clone: %v", stderr)
|
||||
}
|
||||
@@ -1742,3 +1975,8 @@ func ForkRepository(u *User, oldRepo *Repository, name, desc string) (_ *Reposit
|
||||
|
||||
return repo, sess.Commit()
|
||||
}
|
||||
|
||||
func (repo *Repository) GetForks() ([]*Repository, error) {
|
||||
forks := make([]*Repository, 0, repo.NumForks)
|
||||
return forks, x.Find(&forks, &Repository{ForkID: repo.ID})
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ import (
|
||||
// AccessToken represents a personal access token.
|
||||
type AccessToken struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
UID int64 `xorm:"uid INDEX"`
|
||||
UID int64 `xorm:"INDEX"`
|
||||
Name string
|
||||
Sha1 string `xorm:"UNIQUE VARCHAR(40)"`
|
||||
Created time.Time `xorm:"CREATED"`
|
||||
|
||||
@@ -10,49 +10,46 @@ import (
|
||||
"os/exec"
|
||||
"strings"
|
||||
|
||||
"github.com/gogits/gogs/modules/base"
|
||||
"github.com/gogits/gogs/modules/git"
|
||||
"github.com/gogits/gogs/modules/log"
|
||||
)
|
||||
|
||||
type UpdateTask struct {
|
||||
Id int64
|
||||
Uuid string `xorm:"index"`
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
UUID string `xorm:"index"`
|
||||
RefName string
|
||||
OldCommitId string
|
||||
NewCommitId string
|
||||
OldCommitID string
|
||||
NewCommitID string
|
||||
}
|
||||
|
||||
const (
|
||||
MAX_COMMITS int = 5
|
||||
)
|
||||
|
||||
func AddUpdateTask(task *UpdateTask) error {
|
||||
_, err := x.Insert(task)
|
||||
return err
|
||||
}
|
||||
|
||||
func GetUpdateTasksByUuid(uuid string) ([]*UpdateTask, error) {
|
||||
// GetUpdateTaskByUUID returns update task by given UUID.
|
||||
func GetUpdateTaskByUUID(uuid string) (*UpdateTask, error) {
|
||||
task := &UpdateTask{
|
||||
Uuid: uuid,
|
||||
UUID: uuid,
|
||||
}
|
||||
tasks := make([]*UpdateTask, 0)
|
||||
err := x.Find(&tasks, task)
|
||||
has, err := x.Get(task)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !has {
|
||||
return nil, ErrUpdateTaskNotExist{uuid}
|
||||
}
|
||||
return tasks, nil
|
||||
return task, nil
|
||||
}
|
||||
|
||||
func DelUpdateTasksByUuid(uuid string) error {
|
||||
_, err := x.Delete(&UpdateTask{Uuid: uuid})
|
||||
func DeleteUpdateTaskByUUID(uuid string) error {
|
||||
_, err := x.Delete(&UpdateTask{UUID: uuid})
|
||||
return err
|
||||
}
|
||||
|
||||
func Update(refName, oldCommitId, newCommitId, userName, repoUserName, repoName string, userId int64) error {
|
||||
isNew := strings.HasPrefix(oldCommitId, "0000000")
|
||||
func Update(refName, oldCommitID, newCommitID, userName, repoUserName, repoName string, userID int64) error {
|
||||
isNew := strings.HasPrefix(oldCommitID, "0000000")
|
||||
if isNew &&
|
||||
strings.HasPrefix(newCommitId, "0000000") {
|
||||
strings.HasPrefix(newCommitID, "0000000") {
|
||||
return fmt.Errorf("old rev and new rev both 000000")
|
||||
}
|
||||
|
||||
@@ -62,23 +59,23 @@ func Update(refName, oldCommitId, newCommitId, userName, repoUserName, repoName
|
||||
gitUpdate.Dir = f
|
||||
gitUpdate.Run()
|
||||
|
||||
isDel := strings.HasPrefix(newCommitId, "0000000")
|
||||
isDel := strings.HasPrefix(newCommitID, "0000000")
|
||||
if isDel {
|
||||
log.GitLogger.Info("del rev", refName, "from", userName+"/"+repoName+".git", "by", userId)
|
||||
log.GitLogger.Info("del rev", refName, "from", userName+"/"+repoName+".git", "by", userID)
|
||||
return nil
|
||||
}
|
||||
|
||||
repo, err := git.OpenRepository(f)
|
||||
gitRepo, err := git.OpenRepository(f)
|
||||
if err != nil {
|
||||
return fmt.Errorf("runUpdate.Open repoId: %v", err)
|
||||
}
|
||||
|
||||
ru, err := GetUserByName(repoUserName)
|
||||
user, err := GetUserByName(repoUserName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("runUpdate.GetUserByName: %v", err)
|
||||
}
|
||||
|
||||
repos, err := GetRepositoryByName(ru.Id, repoName)
|
||||
repo, err := GetRepositoryByName(user.Id, repoName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("runUpdate.GetRepositoryByName userId: %v", err)
|
||||
}
|
||||
@@ -86,7 +83,7 @@ func Update(refName, oldCommitId, newCommitId, userName, repoUserName, repoName
|
||||
// Push tags.
|
||||
if strings.HasPrefix(refName, "refs/tags/") {
|
||||
tagName := git.RefEndName(refName)
|
||||
tag, err := repo.GetTag(tagName)
|
||||
tag, err := gitRepo.GetTag(tagName)
|
||||
if err != nil {
|
||||
log.GitLogger.Fatal(4, "runUpdate.GetTag: %v", err)
|
||||
}
|
||||
@@ -102,16 +99,16 @@ func Update(refName, oldCommitId, newCommitId, userName, repoUserName, repoName
|
||||
actEmail = cmt.Committer.Email
|
||||
}
|
||||
|
||||
commit := &base.PushCommits{}
|
||||
commit := &PushCommits{}
|
||||
|
||||
if err = CommitRepoAction(userId, ru.Id, userName, actEmail,
|
||||
repos.ID, repoUserName, repoName, refName, commit, oldCommitId, newCommitId); err != nil {
|
||||
if err = CommitRepoAction(userID, user.Id, userName, actEmail,
|
||||
repo.ID, repoUserName, repoName, refName, commit, oldCommitID, newCommitID); err != nil {
|
||||
log.GitLogger.Fatal(4, "CommitRepoAction: %s/%s:%v", repoUserName, repoName, err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
newCommit, err := repo.GetCommit(newCommitId)
|
||||
newCommit, err := gitRepo.GetCommit(newCommitID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("runUpdate GetCommit of newCommitId: %v", err)
|
||||
}
|
||||
@@ -121,12 +118,12 @@ func Update(refName, oldCommitId, newCommitId, userName, repoUserName, repoName
|
||||
if isNew {
|
||||
l, err = newCommit.CommitsBefore()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Find CommitsBefore erro: %v", err)
|
||||
return fmt.Errorf("CommitsBefore: %v", err)
|
||||
}
|
||||
} else {
|
||||
l, err = newCommit.CommitsBeforeUntil(oldCommitId)
|
||||
l, err = newCommit.CommitsBeforeUntil(oldCommitID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Find CommitsBeforeUntil erro: %v", err)
|
||||
return fmt.Errorf("CommitsBeforeUntil: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,26 +132,23 @@ func Update(refName, oldCommitId, newCommitId, userName, repoUserName, repoName
|
||||
}
|
||||
|
||||
// Push commits.
|
||||
commits := make([]*base.PushCommit, 0)
|
||||
commits := make([]*PushCommit, 0)
|
||||
var actEmail string
|
||||
for e := l.Front(); e != nil; e = e.Next() {
|
||||
commit := e.Value.(*git.Commit)
|
||||
|
||||
if actEmail == "" {
|
||||
actEmail = commit.Committer.Email
|
||||
}
|
||||
commits = append(commits,
|
||||
&base.PushCommit{commit.Id.String(),
|
||||
&PushCommit{commit.ID.String(),
|
||||
commit.Message(),
|
||||
commit.Author.Email,
|
||||
commit.Author.Name})
|
||||
if len(commits) >= MAX_COMMITS {
|
||||
break
|
||||
}
|
||||
commit.Author.Name,
|
||||
})
|
||||
}
|
||||
|
||||
if err = CommitRepoAction(userId, ru.Id, userName, actEmail,
|
||||
repos.ID, repoUserName, repoName, refName, &base.PushCommits{l.Len(), commits, ""}, oldCommitId, newCommitId); err != nil {
|
||||
if err = CommitRepoAction(userID, user.Id, userName, actEmail,
|
||||
repo.ID, repoUserName, repoName, refName, &PushCommits{l.Len(), commits, "", nil}, oldCommitID, newCommitID); err != nil {
|
||||
return fmt.Errorf("runUpdate.models.CommitRepoAction: %s/%s:%v", repoUserName, repoName, err)
|
||||
}
|
||||
return nil
|
||||
|
||||
297
models/user.go
297
models/user.go
@@ -14,6 +14,7 @@ import (
|
||||
"image"
|
||||
"image/jpeg"
|
||||
_ "image/jpeg"
|
||||
"image/png"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
@@ -24,9 +25,11 @@ import (
|
||||
"github.com/go-xorm/xorm"
|
||||
"github.com/nfnt/resize"
|
||||
|
||||
"github.com/gogits/git-shell"
|
||||
|
||||
"github.com/gogits/gogs/modules/avatar"
|
||||
"github.com/gogits/gogs/modules/base"
|
||||
"github.com/gogits/gogs/modules/git"
|
||||
oldgit "github.com/gogits/gogs/modules/git"
|
||||
"github.com/gogits/gogs/modules/log"
|
||||
"github.com/gogits/gogs/modules/setting"
|
||||
)
|
||||
@@ -55,12 +58,13 @@ type User struct {
|
||||
Name string `xorm:"UNIQUE NOT NULL"`
|
||||
FullName string
|
||||
// Email is the primary email address (to be used for communication).
|
||||
Email string `xorm:"UNIQUE(s) NOT NULL"`
|
||||
Email string `xorm:"NOT NULL"`
|
||||
Passwd string `xorm:"NOT NULL"`
|
||||
LoginType LoginType
|
||||
LoginSource int64 `xorm:"NOT NULL DEFAULT 0"`
|
||||
LoginName string
|
||||
Type UserType `xorm:"UNIQUE(s)"`
|
||||
Type UserType
|
||||
OwnedOrgs []*User `xorm:"-"`
|
||||
Orgs []*User `xorm:"-"`
|
||||
Repos []*Repository `xorm:"-"`
|
||||
Location string
|
||||
@@ -70,13 +74,14 @@ type User struct {
|
||||
Created time.Time `xorm:"CREATED"`
|
||||
Updated time.Time `xorm:"UPDATED"`
|
||||
|
||||
// Remember visibility choice for convenience.
|
||||
// Remember visibility choice for convenience, true for private
|
||||
LastRepoVisibility bool
|
||||
|
||||
// Permissions.
|
||||
IsActive bool
|
||||
IsAdmin bool
|
||||
AllowGitHook bool
|
||||
IsActive bool
|
||||
IsAdmin bool
|
||||
AllowGitHook bool
|
||||
AllowImportLocal bool // Allow migrate repository by local path
|
||||
|
||||
// Avatar.
|
||||
Avatar string `xorm:"VARCHAR(2048) NOT NULL"`
|
||||
@@ -106,11 +111,27 @@ func (u *User) AfterSet(colName string, _ xorm.Cell) {
|
||||
}
|
||||
}
|
||||
|
||||
// HasForkedRepo checks if user has already forked a repository with given ID.
|
||||
func (u *User) HasForkedRepo(repoID int64) bool {
|
||||
_, has := HasForkedRepo(u.Id, repoID)
|
||||
return has
|
||||
}
|
||||
|
||||
// CanEditGitHook returns true if user can edit Git hooks.
|
||||
func (u *User) CanEditGitHook() bool {
|
||||
return u.IsAdmin || u.AllowGitHook
|
||||
}
|
||||
|
||||
// CanImportLocal returns true if user can migrate repository by local path.
|
||||
func (u *User) CanImportLocal() bool {
|
||||
return u.IsAdmin || u.AllowImportLocal
|
||||
}
|
||||
|
||||
// EmailAdresses is the list of all email addresses of a user. Can contain the
|
||||
// primary email address, but is not obligatory
|
||||
type EmailAddress struct {
|
||||
Id int64
|
||||
Uid int64 `xorm:"INDEX NOT NULL"`
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
UID int64 `xorm:"INDEX NOT NULL"`
|
||||
Email string `xorm:"UNIQUE NOT NULL"`
|
||||
IsActivated bool
|
||||
IsPrimary bool `xorm:"-"`
|
||||
@@ -126,48 +147,75 @@ func (u *User) DashboardLink() string {
|
||||
|
||||
// HomeLink returns the user or organization home page link.
|
||||
func (u *User) HomeLink() string {
|
||||
if u.IsOrganization() {
|
||||
return setting.AppSubUrl + "/org/" + u.Name
|
||||
}
|
||||
return setting.AppSubUrl + "/" + u.Name
|
||||
}
|
||||
|
||||
// GenerateEmailActivateCode generates an activate code based on user information and given e-mail.
|
||||
func (u *User) GenerateEmailActivateCode(email string) string {
|
||||
code := base.CreateTimeLimitCode(
|
||||
com.ToStr(u.Id)+email+u.LowerName+u.Passwd+u.Rands,
|
||||
setting.Service.ActiveCodeLives, nil)
|
||||
|
||||
// Add tail hex username
|
||||
code += hex.EncodeToString([]byte(u.LowerName))
|
||||
return code
|
||||
}
|
||||
|
||||
// GenerateActivateCode generates an activate code based on user information.
|
||||
func (u *User) GenerateActivateCode() string {
|
||||
return u.GenerateEmailActivateCode(u.Email)
|
||||
}
|
||||
|
||||
// CustomAvatarPath returns user custom avatar file path.
|
||||
func (u *User) CustomAvatarPath() string {
|
||||
return filepath.Join(setting.AvatarUploadPath, com.ToStr(u.Id))
|
||||
}
|
||||
|
||||
// GenerateRandomAvatar generates a random avatar for user.
|
||||
func (u *User) GenerateRandomAvatar() error {
|
||||
seed := u.Email
|
||||
if len(seed) == 0 {
|
||||
seed = u.Name
|
||||
}
|
||||
|
||||
img, err := avatar.RandomImage([]byte(seed))
|
||||
if err != nil {
|
||||
return fmt.Errorf("RandomImage: %v", err)
|
||||
}
|
||||
if err = os.MkdirAll(path.Dir(u.CustomAvatarPath()), os.ModePerm); err != nil {
|
||||
return fmt.Errorf("MkdirAll: %v", err)
|
||||
}
|
||||
fw, err := os.Create(u.CustomAvatarPath())
|
||||
if err != nil {
|
||||
return fmt.Errorf("Create: %v", err)
|
||||
}
|
||||
defer fw.Close()
|
||||
|
||||
if err = jpeg.Encode(fw, img, nil); err != nil {
|
||||
return fmt.Errorf("Encode: %v", err)
|
||||
}
|
||||
|
||||
log.Info("New random avatar created: %d", u.Id)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *User) RelAvatarLink() string {
|
||||
defaultImgUrl := "/img/avatar_default.jpg"
|
||||
if u.Id == -1 {
|
||||
return defaultImgUrl
|
||||
}
|
||||
|
||||
imgPath := path.Join(setting.AvatarUploadPath, com.ToStr(u.Id))
|
||||
switch {
|
||||
case u.UseCustomAvatar:
|
||||
if !com.IsExist(imgPath) {
|
||||
if !com.IsExist(u.CustomAvatarPath()) {
|
||||
return defaultImgUrl
|
||||
}
|
||||
return "/avatars/" + com.ToStr(u.Id)
|
||||
case setting.DisableGravatar, setting.OfflineMode:
|
||||
if !com.IsExist(imgPath) {
|
||||
img, err := avatar.RandomImage([]byte(u.Email))
|
||||
if err != nil {
|
||||
log.Error(3, "RandomImage: %v", err)
|
||||
return defaultImgUrl
|
||||
if !com.IsExist(u.CustomAvatarPath()) {
|
||||
if err := u.GenerateRandomAvatar(); err != nil {
|
||||
log.Error(3, "GenerateRandomAvatar: %v", err)
|
||||
}
|
||||
if err = os.MkdirAll(path.Dir(imgPath), os.ModePerm); err != nil {
|
||||
log.Error(3, "MkdirAll: %v", err)
|
||||
return defaultImgUrl
|
||||
}
|
||||
fw, err := os.Create(imgPath)
|
||||
if err != nil {
|
||||
log.Error(3, "Create: %v", err)
|
||||
return defaultImgUrl
|
||||
}
|
||||
defer fw.Close()
|
||||
|
||||
if err = jpeg.Encode(fw, img, nil); err != nil {
|
||||
log.Error(3, "Encode: %v", err)
|
||||
return defaultImgUrl
|
||||
}
|
||||
log.Info("New random avatar created: %d", u.Id)
|
||||
}
|
||||
|
||||
return "/avatars/" + com.ToStr(u.Id)
|
||||
@@ -208,43 +256,36 @@ func (u *User) ValidatePassword(passwd string) bool {
|
||||
return u.Passwd == newUser.Passwd
|
||||
}
|
||||
|
||||
// CustomAvatarPath returns user custom avatar file path.
|
||||
func (u *User) CustomAvatarPath() string {
|
||||
return filepath.Join(setting.AvatarUploadPath, com.ToStr(u.Id))
|
||||
}
|
||||
|
||||
// UploadAvatar saves custom avatar for user.
|
||||
// FIXME: split uploads to different subdirs in case we have massive users.
|
||||
func (u *User) UploadAvatar(data []byte) error {
|
||||
u.UseCustomAvatar = true
|
||||
|
||||
img, _, err := image.Decode(bytes.NewReader(data))
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("Decode: %v", err)
|
||||
}
|
||||
m := resize.Resize(234, 234, img, resize.NearestNeighbor)
|
||||
|
||||
m := resize.Resize(290, 290, img, resize.NearestNeighbor)
|
||||
|
||||
sess := x.NewSession()
|
||||
defer sess.Close()
|
||||
defer sessionRelease(sess)
|
||||
if err = sess.Begin(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err = sess.Id(u.Id).AllCols().Update(u); err != nil {
|
||||
sess.Rollback()
|
||||
return err
|
||||
u.UseCustomAvatar = true
|
||||
if err = updateUser(sess, u); err != nil {
|
||||
return fmt.Errorf("updateUser: %v", err)
|
||||
}
|
||||
|
||||
os.MkdirAll(setting.AvatarUploadPath, os.ModePerm)
|
||||
fw, err := os.Create(u.CustomAvatarPath())
|
||||
if err != nil {
|
||||
sess.Rollback()
|
||||
return err
|
||||
return fmt.Errorf("Create: %v", err)
|
||||
}
|
||||
defer fw.Close()
|
||||
if err = jpeg.Encode(fw, m, nil); err != nil {
|
||||
sess.Rollback()
|
||||
return err
|
||||
|
||||
if err = png.Encode(fw, m); err != nil {
|
||||
return fmt.Errorf("Encode: %v", err)
|
||||
}
|
||||
|
||||
return sess.Commit()
|
||||
@@ -284,9 +325,13 @@ func (u *User) IsPublicMember(orgId int64) bool {
|
||||
return IsPublicMembership(orgId, u.Id)
|
||||
}
|
||||
|
||||
func (u *User) getOrganizationCount(e Engine) (int64, error) {
|
||||
return e.Where("uid=?", u.Id).Count(new(OrgUser))
|
||||
}
|
||||
|
||||
// GetOrganizationCount returns count of membership of organization of user.
|
||||
func (u *User) GetOrganizationCount() (int64, error) {
|
||||
return x.Where("uid=?", u.Id).Count(new(OrgUser))
|
||||
return u.getOrganizationCount(x)
|
||||
}
|
||||
|
||||
// GetRepositories returns all repositories that user owns, including private repositories.
|
||||
@@ -295,6 +340,12 @@ func (u *User) GetRepositories() (err error) {
|
||||
return err
|
||||
}
|
||||
|
||||
// GetOwnedOrganizations returns all organizations that user owns.
|
||||
func (u *User) GetOwnedOrganizations() (err error) {
|
||||
u.OwnedOrgs, err = GetOwnedOrgsByUserID(u.Id)
|
||||
return err
|
||||
}
|
||||
|
||||
// GetOrganizations returns all organizations that user belongs to.
|
||||
func (u *User) GetOrganizations() error {
|
||||
ous, err := GetOrgUsersByUserId(u.Id)
|
||||
@@ -321,6 +372,15 @@ func (u *User) DisplayName() string {
|
||||
return u.Name
|
||||
}
|
||||
|
||||
// ShortName returns shorted user name with given maximum length,
|
||||
// it adds "..." at the end if user name has more length than maximum.
|
||||
func (u *User) ShortName(length int) string {
|
||||
if len(u.Name) < length {
|
||||
return u.Name
|
||||
}
|
||||
return u.Name[:length] + "..."
|
||||
}
|
||||
|
||||
// IsUserExist checks if given user name exist,
|
||||
// the user name should be noncased unique.
|
||||
// If uid is presented, then check will rule out that one,
|
||||
@@ -372,6 +432,7 @@ func CreateUser(u *User) (err error) {
|
||||
return ErrUserAlreadyExist{u.Name}
|
||||
}
|
||||
|
||||
u.Email = strings.ToLower(u.Email)
|
||||
isExist, err = IsEmailUsed(u.Email)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -413,11 +474,10 @@ func CountUsers() int64 {
|
||||
return countUsers(x)
|
||||
}
|
||||
|
||||
// GetUsers returns given number of user objects with offset.
|
||||
func GetUsers(num, offset int) ([]*User, error) {
|
||||
users := make([]*User, 0, num)
|
||||
err := x.Limit(num, offset).Where("type=0").Asc("id").Find(&users)
|
||||
return users, err
|
||||
// Users returns number of users in given page.
|
||||
func Users(page, pageSize int) ([]*User, error) {
|
||||
users := make([]*User, 0, pageSize)
|
||||
return users, x.Limit(pageSize, (page-1)*pageSize).Where("type=0").Asc("id").Find(&users)
|
||||
}
|
||||
|
||||
// get user by erify code
|
||||
@@ -490,12 +550,20 @@ func ChangeUserName(u *User, newUserName string) (err error) {
|
||||
}
|
||||
|
||||
func updateUser(e Engine, u *User) error {
|
||||
u.Email = strings.ToLower(u.Email)
|
||||
has, err := e.Where("id!=?", u.Id).And("type=?", u.Type).And("email=?", u.Email).Get(new(User))
|
||||
if err != nil {
|
||||
return err
|
||||
} else if has {
|
||||
return ErrEmailAlreadyUsed{u.Email}
|
||||
// Organization does not need e-mail.
|
||||
if !u.IsOrganization() {
|
||||
u.Email = strings.ToLower(u.Email)
|
||||
has, err := e.Where("id!=?", u.Id).And("type=?", u.Type).And("email=?", u.Email).Get(new(User))
|
||||
if err != nil {
|
||||
return err
|
||||
} else if has {
|
||||
return ErrEmailAlreadyUsed{u.Email}
|
||||
}
|
||||
|
||||
if len(u.AvatarEmail) == 0 {
|
||||
u.AvatarEmail = u.Email
|
||||
}
|
||||
u.Avatar = avatar.HashEmail(u.AvatarEmail)
|
||||
}
|
||||
|
||||
u.LowerName = strings.ToLower(u.Name)
|
||||
@@ -510,13 +578,8 @@ func updateUser(e Engine, u *User) error {
|
||||
u.Description = u.Description[:255]
|
||||
}
|
||||
|
||||
if u.AvatarEmail == "" {
|
||||
u.AvatarEmail = u.Email
|
||||
}
|
||||
u.Avatar = avatar.HashEmail(u.AvatarEmail)
|
||||
|
||||
u.FullName = base.Sanitizer.Sanitize(u.FullName)
|
||||
_, err = e.Id(u.Id).AllCols().Update(u)
|
||||
_, err := e.Id(u.Id).AllCols().Update(u)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -525,8 +588,8 @@ func UpdateUser(u *User) error {
|
||||
return updateUser(x, u)
|
||||
}
|
||||
|
||||
// DeleteBeans deletes all given beans, beans should contain delete conditions.
|
||||
func DeleteBeans(e Engine, beans ...interface{}) (err error) {
|
||||
// deleteBeans deletes all given beans, beans should contain delete conditions.
|
||||
func deleteBeans(e Engine, beans ...interface{}) (err error) {
|
||||
for i := range beans {
|
||||
if _, err = e.Delete(beans[i]); err != nil {
|
||||
return err
|
||||
@@ -536,14 +599,12 @@ func DeleteBeans(e Engine, beans ...interface{}) (err error) {
|
||||
}
|
||||
|
||||
// FIXME: need some kind of mechanism to record failure. HINT: system notice
|
||||
// DeleteUser completely and permanently deletes everything of a user,
|
||||
// but issues/comments/pulls will be kept and shown as someone has been deleted.
|
||||
func DeleteUser(u *User) error {
|
||||
func deleteUser(e *xorm.Session, u *User) error {
|
||||
// Note: A user owns any repository or belongs to any organization
|
||||
// cannot perform delete operation.
|
||||
|
||||
// Check ownership of repository.
|
||||
count, err := GetRepositoryCount(u)
|
||||
count, err := getRepositoryCount(e, u)
|
||||
if err != nil {
|
||||
return fmt.Errorf("GetRepositoryCount: %v", err)
|
||||
} else if count > 0 {
|
||||
@@ -551,26 +612,20 @@ func DeleteUser(u *User) error {
|
||||
}
|
||||
|
||||
// Check membership of organization.
|
||||
count, err = u.GetOrganizationCount()
|
||||
count, err = u.getOrganizationCount(e)
|
||||
if err != nil {
|
||||
return fmt.Errorf("GetOrganizationCount: %v", err)
|
||||
} else if count > 0 {
|
||||
return ErrUserHasOrgs{UID: u.Id}
|
||||
}
|
||||
|
||||
sess := x.NewSession()
|
||||
defer sessionRelease(sess)
|
||||
if err = sess.Begin(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// ***** START: Watch *****
|
||||
watches := make([]*Watch, 0, 10)
|
||||
if err = x.Find(&watches, &Watch{UserID: u.Id}); err != nil {
|
||||
if err = e.Find(&watches, &Watch{UserID: u.Id}); err != nil {
|
||||
return fmt.Errorf("get all watches: %v", err)
|
||||
}
|
||||
for i := range watches {
|
||||
if _, err = sess.Exec("UPDATE `repository` SET num_watches=num_watches-1 WHERE id=?", watches[i].RepoID); err != nil {
|
||||
if _, err = e.Exec("UPDATE `repository` SET num_watches=num_watches-1 WHERE id=?", watches[i].RepoID); err != nil {
|
||||
return fmt.Errorf("decrease repository watch number[%d]: %v", watches[i].RepoID, err)
|
||||
}
|
||||
}
|
||||
@@ -578,11 +633,11 @@ func DeleteUser(u *User) error {
|
||||
|
||||
// ***** START: Star *****
|
||||
stars := make([]*Star, 0, 10)
|
||||
if err = x.Find(&stars, &Star{UID: u.Id}); err != nil {
|
||||
if err = e.Find(&stars, &Star{UID: u.Id}); err != nil {
|
||||
return fmt.Errorf("get all stars: %v", err)
|
||||
}
|
||||
for i := range stars {
|
||||
if _, err = sess.Exec("UPDATE `repository` SET num_stars=num_stars-1 WHERE id=?", stars[i].RepoID); err != nil {
|
||||
if _, err = e.Exec("UPDATE `repository` SET num_stars=num_stars-1 WHERE id=?", stars[i].RepoID); err != nil {
|
||||
return fmt.Errorf("decrease repository star number[%d]: %v", stars[i].RepoID, err)
|
||||
}
|
||||
}
|
||||
@@ -590,18 +645,17 @@ func DeleteUser(u *User) error {
|
||||
|
||||
// ***** START: Follow *****
|
||||
followers := make([]*Follow, 0, 10)
|
||||
if err = x.Find(&followers, &Follow{UserID: u.Id}); err != nil {
|
||||
if err = e.Find(&followers, &Follow{UserID: u.Id}); err != nil {
|
||||
return fmt.Errorf("get all followers: %v", err)
|
||||
}
|
||||
for i := range followers {
|
||||
if _, err = sess.Exec("UPDATE `user` SET num_followers=num_followers-1 WHERE id=?", followers[i].UserID); err != nil {
|
||||
if _, err = e.Exec("UPDATE `user` SET num_followers=num_followers-1 WHERE id=?", followers[i].UserID); err != nil {
|
||||
return fmt.Errorf("decrease user follower number[%d]: %v", followers[i].UserID, err)
|
||||
}
|
||||
}
|
||||
// ***** END: Follow *****
|
||||
|
||||
if err = DeleteBeans(sess,
|
||||
&Oauth2{Uid: u.Id},
|
||||
if err = deleteBeans(e,
|
||||
&AccessToken{UID: u.Id},
|
||||
&Collaboration{UserID: u.Id},
|
||||
&Access{UserID: u.Id},
|
||||
@@ -610,36 +664,32 @@ func DeleteUser(u *User) error {
|
||||
&Follow{FollowID: u.Id},
|
||||
&Action{UserID: u.Id},
|
||||
&IssueUser{UID: u.Id},
|
||||
&EmailAddress{Uid: u.Id},
|
||||
&EmailAddress{UID: u.Id},
|
||||
); err != nil {
|
||||
return fmt.Errorf("DeleteBeans: %v", err)
|
||||
return fmt.Errorf("deleteBeans: %v", err)
|
||||
}
|
||||
|
||||
// ***** START: PublicKey *****
|
||||
keys := make([]*PublicKey, 0, 10)
|
||||
if err = sess.Find(&keys, &PublicKey{OwnerID: u.Id}); err != nil {
|
||||
if err = e.Find(&keys, &PublicKey{OwnerID: u.Id}); err != nil {
|
||||
return fmt.Errorf("get all public keys: %v", err)
|
||||
}
|
||||
for _, key := range keys {
|
||||
if err = deletePublicKey(sess, key.ID); err != nil {
|
||||
if err = deletePublicKey(e, key.ID); err != nil {
|
||||
return fmt.Errorf("deletePublicKey: %v", err)
|
||||
}
|
||||
}
|
||||
// ***** END: PublicKey *****
|
||||
|
||||
// Clear assignee.
|
||||
if _, err = sess.Exec("UPDATE `issue` SET assignee_id=0 WHERE assignee_id=?", u.Id); err != nil {
|
||||
if _, err = e.Exec("UPDATE `issue` SET assignee_id=0 WHERE assignee_id=?", u.Id); err != nil {
|
||||
return fmt.Errorf("clear assignee: %v", err)
|
||||
}
|
||||
|
||||
if _, err = sess.Id(u.Id).Delete(new(User)); err != nil {
|
||||
if _, err = e.Id(u.Id).Delete(new(User)); err != nil {
|
||||
return fmt.Errorf("Delete: %v", err)
|
||||
}
|
||||
|
||||
if err = sess.Commit(); err != nil {
|
||||
return fmt.Errorf("Commit: %v", err)
|
||||
}
|
||||
|
||||
// FIXME: system notice
|
||||
// Note: There are something just cannot be roll back,
|
||||
// so just keep error logs of those operations.
|
||||
@@ -651,6 +701,23 @@ func DeleteUser(u *User) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteUser completely and permanently deletes everything of a user,
|
||||
// but issues/comments/pulls will be kept and shown as someone has been deleted.
|
||||
func DeleteUser(u *User) (err error) {
|
||||
sess := x.NewSession()
|
||||
defer sessionRelease(sess)
|
||||
if err = sess.Begin(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = deleteUser(sess, u); err != nil {
|
||||
// Note: don't wrapper error here.
|
||||
return err
|
||||
}
|
||||
|
||||
return sess.Commit()
|
||||
}
|
||||
|
||||
// DeleteInactivateUsers deletes all inactivate users and email addresses.
|
||||
func DeleteInactivateUsers() (err error) {
|
||||
users := make([]*User, 0, 10)
|
||||
@@ -676,9 +743,9 @@ func UserPath(userName string) string {
|
||||
return filepath.Join(setting.RepoRootPath, strings.ToLower(userName))
|
||||
}
|
||||
|
||||
func GetUserByKeyId(keyId int64) (*User, error) {
|
||||
func GetUserByKeyID(keyID int64) (*User, error) {
|
||||
user := new(User)
|
||||
has, err := x.Sql("SELECT a.* FROM `user` AS a, public_key AS b WHERE a.id = b.owner_id AND b.id=?", keyId).Get(user)
|
||||
has, err := x.Sql("SELECT a.* FROM `user` AS a, public_key AS b WHERE a.id = b.owner_id AND b.id=?", keyID).Get(user)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !has {
|
||||
@@ -805,11 +872,11 @@ func AddEmailAddress(email *EmailAddress) error {
|
||||
|
||||
func (email *EmailAddress) Activate() error {
|
||||
email.IsActivated = true
|
||||
if _, err := x.Id(email.Id).AllCols().Update(email); err != nil {
|
||||
if _, err := x.Id(email.ID).AllCols().Update(email); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if user, err := GetUserByID(email.Uid); err != nil {
|
||||
if user, err := GetUserByID(email.UID); err != nil {
|
||||
return err
|
||||
} else {
|
||||
user.Rands = GetUserSalt()
|
||||
@@ -825,7 +892,7 @@ func DeleteEmailAddress(email *EmailAddress) error {
|
||||
return ErrEmailNotExist
|
||||
}
|
||||
|
||||
if _, err = x.Id(email.Id).Delete(email); err != nil {
|
||||
if _, err = x.Id(email.ID).Delete(email); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -845,12 +912,12 @@ func MakeEmailPrimary(email *EmailAddress) error {
|
||||
return ErrEmailNotActivated
|
||||
}
|
||||
|
||||
user := &User{Id: email.Uid}
|
||||
user := &User{Id: email.UID}
|
||||
has, err = x.Get(user)
|
||||
if err != nil {
|
||||
return err
|
||||
} else if !has {
|
||||
return ErrUserNotExist{email.Uid, ""}
|
||||
return ErrUserNotExist{email.UID, ""}
|
||||
}
|
||||
|
||||
// Make sure the former primary email doesn't disappear
|
||||
@@ -859,7 +926,7 @@ func MakeEmailPrimary(email *EmailAddress) error {
|
||||
if err != nil {
|
||||
return err
|
||||
} else if !has {
|
||||
former_primary_email.Uid = user.Id
|
||||
former_primary_email.UID = user.Id
|
||||
former_primary_email.IsActivated = user.IsActive
|
||||
x.Insert(former_primary_email)
|
||||
}
|
||||
@@ -873,11 +940,11 @@ func MakeEmailPrimary(email *EmailAddress) error {
|
||||
// UserCommit represents a commit with validation of user.
|
||||
type UserCommit struct {
|
||||
User *User
|
||||
*git.Commit
|
||||
*oldgit.Commit
|
||||
}
|
||||
|
||||
// ValidateCommitWithEmail chceck if author's e-mail of commit is corresponsind to a user.
|
||||
func ValidateCommitWithEmail(c *git.Commit) *User {
|
||||
func ValidateCommitWithEmail(c *oldgit.Commit) *User {
|
||||
u, err := GetUserByEmail(c.Author.Email)
|
||||
if err != nil {
|
||||
return nil
|
||||
@@ -894,7 +961,7 @@ func ValidateCommitsWithEmails(oldCommits *list.List) *list.List {
|
||||
e = oldCommits.Front()
|
||||
)
|
||||
for e != nil {
|
||||
c := e.Value.(*git.Commit)
|
||||
c := e.Value.(*oldgit.Commit)
|
||||
|
||||
if v, ok := emails[c.Author.Email]; !ok {
|
||||
u, _ = GetUserByEmail(c.Author.Email)
|
||||
@@ -936,10 +1003,10 @@ func GetUserByEmail(email string) (*User, error) {
|
||||
return nil, err
|
||||
}
|
||||
if has {
|
||||
return GetUserByID(emailAddress.Uid)
|
||||
return GetUserByID(emailAddress.UID)
|
||||
}
|
||||
|
||||
return nil, ErrUserNotExist{0, "email"}
|
||||
return nil, ErrUserNotExist{0, email}
|
||||
}
|
||||
|
||||
// SearchUserByName returns given number of users whose name contains keyword.
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/Unknwon/com"
|
||||
"github.com/go-xorm/xorm"
|
||||
|
||||
api "github.com/gogits/go-gogs-client"
|
||||
@@ -177,8 +178,8 @@ func GetActiveWebhooksByRepoID(repoID int64) (ws []*Webhook, err error) {
|
||||
return ws, err
|
||||
}
|
||||
|
||||
// GetWebhooksByRepoId returns all webhooks of repository.
|
||||
func GetWebhooksByRepoId(repoID int64) (ws []*Webhook, err error) {
|
||||
// GetWebhooksByRepoID returns all webhooks of repository.
|
||||
func GetWebhooksByRepoID(repoID int64) (ws []*Webhook, err error) {
|
||||
err = x.Find(&ws, &Webhook{RepoID: repoID})
|
||||
return ws, err
|
||||
}
|
||||
@@ -334,7 +335,7 @@ func (t *HookTask) AfterSet(colName string, _ xorm.Cell) {
|
||||
|
||||
t.ResponseInfo = &HookResponse{}
|
||||
if err = json.Unmarshal([]byte(t.ResponseContent), t.ResponseInfo); err != nil {
|
||||
log.Error(3, "Unmarshal[%d]: %v", t.ID, err)
|
||||
log.Error(3, "Unmarshal [%d]: %v", t.ID, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -342,7 +343,7 @@ func (t *HookTask) AfterSet(colName string, _ xorm.Cell) {
|
||||
func (t *HookTask) MarshalJSON(v interface{}) string {
|
||||
p, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
log.Error(3, "Marshal[%d]: %v", t.ID, err)
|
||||
log.Error(3, "Marshal [%d]: %v", t.ID, err)
|
||||
}
|
||||
return string(p)
|
||||
}
|
||||
@@ -435,39 +436,65 @@ func PrepareWebhooks(repo *Repository, event HookEventType, p api.Payloader) err
|
||||
return nil
|
||||
}
|
||||
|
||||
type hookQueue struct {
|
||||
// Make sure one repository only occur once in the queue.
|
||||
lock sync.Mutex
|
||||
repoIDs map[int64]bool
|
||||
// UniqueQueue represents a queue that guarantees only one instance of same ID is in the line.
|
||||
type UniqueQueue struct {
|
||||
lock sync.Mutex
|
||||
ids map[string]bool
|
||||
|
||||
queue chan int64
|
||||
queue chan string
|
||||
}
|
||||
|
||||
func (q *hookQueue) removeRepoID(id int64) {
|
||||
func (q *UniqueQueue) Queue() <-chan string {
|
||||
return q.queue
|
||||
}
|
||||
|
||||
func NewUniqueQueue(queueLength int) *UniqueQueue {
|
||||
if queueLength <= 0 {
|
||||
queueLength = 100
|
||||
}
|
||||
|
||||
return &UniqueQueue{
|
||||
ids: make(map[string]bool),
|
||||
queue: make(chan string, queueLength),
|
||||
}
|
||||
}
|
||||
|
||||
func (q *UniqueQueue) Remove(id interface{}) {
|
||||
q.lock.Lock()
|
||||
defer q.lock.Unlock()
|
||||
delete(q.repoIDs, id)
|
||||
delete(q.ids, com.ToStr(id))
|
||||
}
|
||||
|
||||
func (q *hookQueue) addRepoID(id int64) {
|
||||
q.lock.Lock()
|
||||
if q.repoIDs[id] {
|
||||
q.lock.Unlock()
|
||||
func (q *UniqueQueue) AddFunc(id interface{}, fn func()) {
|
||||
newid := com.ToStr(id)
|
||||
|
||||
if q.Exist(id) {
|
||||
return
|
||||
}
|
||||
q.repoIDs[id] = true
|
||||
|
||||
q.lock.Lock()
|
||||
q.ids[newid] = true
|
||||
if fn != nil {
|
||||
fn()
|
||||
}
|
||||
q.lock.Unlock()
|
||||
q.queue <- id
|
||||
q.queue <- newid
|
||||
}
|
||||
|
||||
// AddRepoID adds repository ID to hook delivery queue.
|
||||
func (q *hookQueue) AddRepoID(id int64) {
|
||||
go q.addRepoID(id)
|
||||
func (q *UniqueQueue) Add(id interface{}) {
|
||||
q.AddFunc(id, nil)
|
||||
}
|
||||
|
||||
var HookQueue *hookQueue
|
||||
func (q *UniqueQueue) Exist(id interface{}) bool {
|
||||
q.lock.Lock()
|
||||
defer q.lock.Unlock()
|
||||
|
||||
func deliverHook(t *HookTask) {
|
||||
return q.ids[com.ToStr(id)]
|
||||
}
|
||||
|
||||
var HookQueue = NewUniqueQueue(setting.Webhook.QueueLength)
|
||||
|
||||
func (t *HookTask) deliver() {
|
||||
t.IsDelivered = true
|
||||
|
||||
timeout := time.Duration(setting.Webhook.DeliverTimeout) * time.Second
|
||||
@@ -499,6 +526,8 @@ func deliverHook(t *HookTask) {
|
||||
t.Delivered = time.Now().UTC().UnixNano()
|
||||
if t.IsSucceed {
|
||||
log.Trace("Hook delivered: %s", t.UUID)
|
||||
} else {
|
||||
log.Trace("Hook delivery failed: %s", t.UUID)
|
||||
}
|
||||
|
||||
// Update webhook last delivery status.
|
||||
@@ -549,12 +578,13 @@ func deliverHook(t *HookTask) {
|
||||
}
|
||||
|
||||
// DeliverHooks checks and delivers undelivered hooks.
|
||||
// TODO: shoot more hooks at same time.
|
||||
func DeliverHooks() {
|
||||
tasks := make([]*HookTask, 0, 10)
|
||||
x.Where("is_delivered=?", false).Iterate(new(HookTask),
|
||||
func(idx int, bean interface{}) error {
|
||||
t := bean.(*HookTask)
|
||||
deliverHook(t)
|
||||
t.deliver()
|
||||
tasks = append(tasks, t)
|
||||
return nil
|
||||
})
|
||||
@@ -562,29 +592,25 @@ func DeliverHooks() {
|
||||
// Update hook task status.
|
||||
for _, t := range tasks {
|
||||
if err := UpdateHookTask(t); err != nil {
|
||||
log.Error(4, "UpdateHookTask(%d): %v", t.ID, err)
|
||||
log.Error(4, "UpdateHookTask [%d]: %v", t.ID, err)
|
||||
}
|
||||
}
|
||||
|
||||
HookQueue = &hookQueue{
|
||||
lock: sync.Mutex{},
|
||||
repoIDs: make(map[int64]bool),
|
||||
queue: make(chan int64, setting.Webhook.QueueLength),
|
||||
}
|
||||
|
||||
// Start listening on new hook requests.
|
||||
for repoID := range HookQueue.queue {
|
||||
HookQueue.removeRepoID(repoID)
|
||||
for repoID := range HookQueue.Queue() {
|
||||
log.Trace("DeliverHooks [%v]: processing delivery hooks", repoID)
|
||||
HookQueue.Remove(repoID)
|
||||
|
||||
tasks = make([]*HookTask, 0, 5)
|
||||
if err := x.Where("repo_id=? AND is_delivered=?", repoID, false).Find(&tasks); err != nil {
|
||||
log.Error(4, "Get repository(%d) hook tasks: %v", repoID, err)
|
||||
log.Error(4, "Get repository [%d] hook tasks: %v", repoID, err)
|
||||
continue
|
||||
}
|
||||
for _, t := range tasks {
|
||||
deliverHook(t)
|
||||
t.deliver()
|
||||
if err := UpdateHookTask(t); err != nil {
|
||||
log.Error(4, "UpdateHookTask(%d): %v", t.ID, err)
|
||||
log.Error(4, "UpdateHookTask [%d]: %v", t.ID, err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,8 +33,9 @@ type SlackPayload struct {
|
||||
}
|
||||
|
||||
type SlackAttachment struct {
|
||||
Color string `json:"color"`
|
||||
Text string `json:"text"`
|
||||
Fallback string `json:"fallback"`
|
||||
Color string `json:"color"`
|
||||
Text string `json:"text"`
|
||||
}
|
||||
|
||||
func (p *SlackPayload) SetSecret(_ string) {}
|
||||
@@ -82,19 +83,19 @@ func getSlackPushPayload(p *api.PushPayload, slack *SlackMeta) (*SlackPayload, e
|
||||
// n new commits
|
||||
var (
|
||||
branchName = git.RefEndName(p.Ref)
|
||||
commitDesc string
|
||||
commitString string
|
||||
)
|
||||
|
||||
if len(p.Commits) == 1 {
|
||||
commitString = "1 new commit"
|
||||
if len(p.CompareUrl) > 0 {
|
||||
commitString = SlackLinkFormatter(p.CompareUrl, commitString)
|
||||
}
|
||||
commitDesc = "1 new commit"
|
||||
} else {
|
||||
commitString = fmt.Sprintf("%d new commits", len(p.Commits))
|
||||
if p.CompareUrl != "" {
|
||||
commitString = SlackLinkFormatter(p.CompareUrl, commitString)
|
||||
}
|
||||
commitDesc = fmt.Sprintf("%d new commits", len(p.Commits))
|
||||
}
|
||||
if len(p.CompareUrl) > 0 {
|
||||
commitString = SlackLinkFormatter(p.CompareUrl, commitDesc)
|
||||
} else {
|
||||
commitString = commitDesc
|
||||
}
|
||||
|
||||
repoLink := SlackLinkFormatter(p.Repo.URL, p.Repo.Name)
|
||||
@@ -111,7 +112,10 @@ func getSlackPushPayload(p *api.PushPayload, slack *SlackMeta) (*SlackPayload, e
|
||||
}
|
||||
}
|
||||
|
||||
slackAttachments := []SlackAttachment{{Color: slack.Color, Text: attachmentText}}
|
||||
slackAttachments := []SlackAttachment{{
|
||||
Color: slack.Color,
|
||||
Text: attachmentText,
|
||||
}}
|
||||
|
||||
return &SlackPayload{
|
||||
Channel: slack.Channel,
|
||||
|
||||
178
models/wiki.go
Normal file
178
models/wiki.go
Normal file
@@ -0,0 +1,178 @@
|
||||
// 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 models
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/Unknwon/com"
|
||||
|
||||
"github.com/gogits/git-shell"
|
||||
|
||||
"github.com/gogits/gogs/modules/setting"
|
||||
)
|
||||
|
||||
// workingPool represents a pool of working status which makes sure
|
||||
// that only one instance of same task is performing at a time.
|
||||
// However, different type of tasks can performing at the same time.
|
||||
type workingPool struct {
|
||||
lock sync.Mutex
|
||||
pool map[string]*sync.Mutex
|
||||
count map[string]int
|
||||
}
|
||||
|
||||
// CheckIn checks in a task and waits if others are running.
|
||||
func (p *workingPool) CheckIn(name string) {
|
||||
p.lock.Lock()
|
||||
|
||||
lock, has := p.pool[name]
|
||||
if !has {
|
||||
lock = &sync.Mutex{}
|
||||
p.pool[name] = lock
|
||||
}
|
||||
p.count[name]++
|
||||
|
||||
p.lock.Unlock()
|
||||
lock.Lock()
|
||||
}
|
||||
|
||||
// CheckOut checks out a task to let other tasks run.
|
||||
func (p *workingPool) CheckOut(name string) {
|
||||
p.lock.Lock()
|
||||
defer p.lock.Unlock()
|
||||
|
||||
p.pool[name].Unlock()
|
||||
if p.count[name] == 1 {
|
||||
delete(p.pool, name)
|
||||
delete(p.count, name)
|
||||
} else {
|
||||
p.count[name]--
|
||||
}
|
||||
}
|
||||
|
||||
var wikiWorkingPool = &workingPool{
|
||||
pool: make(map[string]*sync.Mutex),
|
||||
count: make(map[string]int),
|
||||
}
|
||||
|
||||
// ToWikiPageURL formats a string to corresponding wiki URL name.
|
||||
func ToWikiPageURL(name string) string {
|
||||
return strings.Replace(name, " ", "-", -1)
|
||||
}
|
||||
|
||||
// ToWikiPageName formats a URL back to corresponding wiki page name.
|
||||
func ToWikiPageName(name string) string {
|
||||
return strings.Replace(name, "-", " ", -1)
|
||||
}
|
||||
|
||||
// WikiCloneLink returns clone URLs of repository wiki.
|
||||
func (repo *Repository) WikiCloneLink() (cl *CloneLink) {
|
||||
return repo.cloneLink(true)
|
||||
}
|
||||
|
||||
// WikiPath returns wiki data path by given user and repository name.
|
||||
func WikiPath(userName, repoName string) string {
|
||||
return filepath.Join(UserPath(userName), strings.ToLower(repoName)+".wiki.git")
|
||||
}
|
||||
|
||||
func (repo *Repository) WikiPath() string {
|
||||
return WikiPath(repo.MustOwner().Name, repo.Name)
|
||||
}
|
||||
|
||||
// HasWiki returns true if repository has wiki.
|
||||
func (repo *Repository) HasWiki() bool {
|
||||
return com.IsDir(repo.WikiPath())
|
||||
}
|
||||
|
||||
// InitWiki initializes a wiki for repository,
|
||||
// it does nothing when repository already has wiki.
|
||||
func (repo *Repository) InitWiki() error {
|
||||
if repo.HasWiki() {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := git.InitRepository(repo.WikiPath(), true); err != nil {
|
||||
return fmt.Errorf("InitRepository: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (repo *Repository) LocalWikiPath() string {
|
||||
return path.Join(setting.AppDataPath, "tmp/local-wiki", com.ToStr(repo.ID))
|
||||
}
|
||||
|
||||
// UpdateLocalWiki makes sure the local copy of repository wiki is up-to-date.
|
||||
func (repo *Repository) UpdateLocalWiki() error {
|
||||
return updateLocalCopy(repo.WikiPath(), repo.LocalWikiPath())
|
||||
}
|
||||
|
||||
// updateWikiPage adds new page to repository wiki.
|
||||
func (repo *Repository) updateWikiPage(doer *User, oldTitle, title, content, message string, isNew bool) (err error) {
|
||||
wikiWorkingPool.CheckIn(com.ToStr(repo.ID))
|
||||
defer wikiWorkingPool.CheckOut(com.ToStr(repo.ID))
|
||||
|
||||
if err = repo.InitWiki(); err != nil {
|
||||
return fmt.Errorf("InitWiki: %v", err)
|
||||
}
|
||||
|
||||
localPath := repo.LocalWikiPath()
|
||||
|
||||
// Discard local commits make sure even to remote when local copy exists.
|
||||
if com.IsExist(localPath) {
|
||||
// No need to check if nothing in the repository.
|
||||
if git.IsBranchExist(localPath, "master") {
|
||||
if err = git.ResetHEAD(localPath, true, "origin/master"); err != nil {
|
||||
return fmt.Errorf("Reset: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err = repo.UpdateLocalWiki(); err != nil {
|
||||
return fmt.Errorf("UpdateLocalWiki: %v", err)
|
||||
}
|
||||
|
||||
title = ToWikiPageName(strings.Replace(title, "/", " ", -1))
|
||||
filename := path.Join(localPath, title+".md")
|
||||
|
||||
// If not a new file, show perform update not create.
|
||||
if isNew {
|
||||
if com.IsExist(filename) {
|
||||
return ErrWikiAlreadyExist{filename}
|
||||
}
|
||||
} else {
|
||||
os.Remove(path.Join(localPath, oldTitle+".md"))
|
||||
}
|
||||
|
||||
if err = ioutil.WriteFile(filename, []byte(content), 0666); err != nil {
|
||||
return fmt.Errorf("WriteFile: %v", err)
|
||||
}
|
||||
|
||||
if len(message) == 0 {
|
||||
message = "Update page '" + title + "'"
|
||||
}
|
||||
if err = git.AddChanges(localPath, true); err != nil {
|
||||
return fmt.Errorf("AddChanges: %v", err)
|
||||
} else if err = git.CommitChanges(localPath, message, doer.NewGitSig()); err != nil {
|
||||
return fmt.Errorf("CommitChanges: %v", err)
|
||||
} else if err = git.Push(localPath, "origin", "master"); err != nil {
|
||||
return fmt.Errorf("Push: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (repo *Repository) AddWikiPage(doer *User, title, content, message string) error {
|
||||
return repo.updateWikiPage(doer, "", title, content, message, true)
|
||||
}
|
||||
|
||||
func (repo *Repository) EditWikiPage(doer *User, oldTitle, title, content, message string) error {
|
||||
return repo.updateWikiPage(doer, oldTitle, title, content, message, false)
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
Copyright (c) 2012 The Go Authors. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
@@ -1,11 +0,0 @@
|
||||
# Copyright 2009 The Go Authors. All rights reserved.
|
||||
# Use of this source code is governed by a BSD-style
|
||||
# license that can be found in the LICENSE file.
|
||||
|
||||
include $(GOROOT)/src/Make.inc
|
||||
|
||||
TARG=github.com/mmitton/asn1-ber
|
||||
GOFILES=\
|
||||
ber.go\
|
||||
|
||||
include $(GOROOT)/src/Make.pkg
|
||||
@@ -1,14 +0,0 @@
|
||||
ASN1 BER Encoding / Decoding Library for the GO programming language.
|
||||
|
||||
Required Librarys:
|
||||
None
|
||||
|
||||
Working:
|
||||
Very basic encoding / decoding needed for LDAP protocol
|
||||
|
||||
Tests Implemented:
|
||||
None
|
||||
|
||||
TODO:
|
||||
Fix all encoding / decoding to conform to ASN1 BER spec
|
||||
Implement Tests / Benchmarks
|
||||
@@ -1,497 +0,0 @@
|
||||
package ber
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
"errors"
|
||||
)
|
||||
|
||||
type Packet struct {
|
||||
ClassType uint8
|
||||
TagType uint8
|
||||
Tag uint8
|
||||
Value interface{}
|
||||
ByteValue []byte
|
||||
Data *bytes.Buffer
|
||||
Children []*Packet
|
||||
Description string
|
||||
}
|
||||
|
||||
const (
|
||||
TagEOC = 0x00
|
||||
TagBoolean = 0x01
|
||||
TagInteger = 0x02
|
||||
TagBitString = 0x03
|
||||
TagOctetString = 0x04
|
||||
TagNULL = 0x05
|
||||
TagObjectIdentifier = 0x06
|
||||
TagObjectDescriptor = 0x07
|
||||
TagExternal = 0x08
|
||||
TagRealFloat = 0x09
|
||||
TagEnumerated = 0x0a
|
||||
TagEmbeddedPDV = 0x0b
|
||||
TagUTF8String = 0x0c
|
||||
TagRelativeOID = 0x0d
|
||||
TagSequence = 0x10
|
||||
TagSet = 0x11
|
||||
TagNumericString = 0x12
|
||||
TagPrintableString = 0x13
|
||||
TagT61String = 0x14
|
||||
TagVideotexString = 0x15
|
||||
TagIA5String = 0x16
|
||||
TagUTCTime = 0x17
|
||||
TagGeneralizedTime = 0x18
|
||||
TagGraphicString = 0x19
|
||||
TagVisibleString = 0x1a
|
||||
TagGeneralString = 0x1b
|
||||
TagUniversalString = 0x1c
|
||||
TagCharacterString = 0x1d
|
||||
TagBMPString = 0x1e
|
||||
TagBitmask = 0x1f // xxx11111b
|
||||
)
|
||||
|
||||
var TagMap = map[uint8]string{
|
||||
TagEOC: "EOC (End-of-Content)",
|
||||
TagBoolean: "Boolean",
|
||||
TagInteger: "Integer",
|
||||
TagBitString: "Bit String",
|
||||
TagOctetString: "Octet String",
|
||||
TagNULL: "NULL",
|
||||
TagObjectIdentifier: "Object Identifier",
|
||||
TagObjectDescriptor: "Object Descriptor",
|
||||
TagExternal: "External",
|
||||
TagRealFloat: "Real (float)",
|
||||
TagEnumerated: "Enumerated",
|
||||
TagEmbeddedPDV: "Embedded PDV",
|
||||
TagUTF8String: "UTF8 String",
|
||||
TagRelativeOID: "Relative-OID",
|
||||
TagSequence: "Sequence and Sequence of",
|
||||
TagSet: "Set and Set OF",
|
||||
TagNumericString: "Numeric String",
|
||||
TagPrintableString: "Printable String",
|
||||
TagT61String: "T61 String",
|
||||
TagVideotexString: "Videotex String",
|
||||
TagIA5String: "IA5 String",
|
||||
TagUTCTime: "UTC Time",
|
||||
TagGeneralizedTime: "Generalized Time",
|
||||
TagGraphicString: "Graphic String",
|
||||
TagVisibleString: "Visible String",
|
||||
TagGeneralString: "General String",
|
||||
TagUniversalString: "Universal String",
|
||||
TagCharacterString: "Character String",
|
||||
TagBMPString: "BMP String",
|
||||
}
|
||||
|
||||
const (
|
||||
ClassUniversal = 0 // 00xxxxxxb
|
||||
ClassApplication = 64 // 01xxxxxxb
|
||||
ClassContext = 128 // 10xxxxxxb
|
||||
ClassPrivate = 192 // 11xxxxxxb
|
||||
ClassBitmask = 192 // 11xxxxxxb
|
||||
)
|
||||
|
||||
var ClassMap = map[uint8]string{
|
||||
ClassUniversal: "Universal",
|
||||
ClassApplication: "Application",
|
||||
ClassContext: "Context",
|
||||
ClassPrivate: "Private",
|
||||
}
|
||||
|
||||
const (
|
||||
TypePrimitive = 0 // xx0xxxxxb
|
||||
TypeConstructed = 32 // xx1xxxxxb
|
||||
TypeBitmask = 32 // xx1xxxxxb
|
||||
)
|
||||
|
||||
var TypeMap = map[uint8]string{
|
||||
TypePrimitive: "Primative",
|
||||
TypeConstructed: "Constructed",
|
||||
}
|
||||
|
||||
var Debug bool = false
|
||||
|
||||
func PrintBytes(buf []byte, indent string) {
|
||||
data_lines := make([]string, (len(buf)/30)+1)
|
||||
num_lines := make([]string, (len(buf)/30)+1)
|
||||
|
||||
for i, b := range buf {
|
||||
data_lines[i/30] += fmt.Sprintf("%02x ", b)
|
||||
num_lines[i/30] += fmt.Sprintf("%02d ", (i+1)%100)
|
||||
}
|
||||
|
||||
for i := 0; i < len(data_lines); i++ {
|
||||
fmt.Print(indent + data_lines[i] + "\n")
|
||||
fmt.Print(indent + num_lines[i] + "\n\n")
|
||||
}
|
||||
}
|
||||
|
||||
func PrintPacket(p *Packet) {
|
||||
printPacket(p, 0, false)
|
||||
}
|
||||
|
||||
func printPacket(p *Packet, indent int, printBytes bool) {
|
||||
indent_str := ""
|
||||
|
||||
for len(indent_str) != indent {
|
||||
indent_str += " "
|
||||
}
|
||||
|
||||
class_str := ClassMap[p.ClassType]
|
||||
|
||||
tagtype_str := TypeMap[p.TagType]
|
||||
|
||||
tag_str := fmt.Sprintf("0x%02X", p.Tag)
|
||||
|
||||
if p.ClassType == ClassUniversal {
|
||||
tag_str = TagMap[p.Tag]
|
||||
}
|
||||
|
||||
value := fmt.Sprint(p.Value)
|
||||
description := ""
|
||||
|
||||
if p.Description != "" {
|
||||
description = p.Description + ": "
|
||||
}
|
||||
|
||||
fmt.Printf("%s%s(%s, %s, %s) Len=%d %q\n", indent_str, description, class_str, tagtype_str, tag_str, p.Data.Len(), value)
|
||||
|
||||
if printBytes {
|
||||
PrintBytes(p.Bytes(), indent_str)
|
||||
}
|
||||
|
||||
for _, child := range p.Children {
|
||||
printPacket(child, indent+1, printBytes)
|
||||
}
|
||||
}
|
||||
|
||||
func resizeBuffer(in []byte, new_size uint64) (out []byte) {
|
||||
out = make([]byte, new_size)
|
||||
|
||||
copy(out, in)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func readBytes(reader io.Reader, buf []byte) error {
|
||||
idx := 0
|
||||
buflen := len(buf)
|
||||
|
||||
if reader == nil {
|
||||
return errors.New("reader was nil, aborting")
|
||||
}
|
||||
|
||||
for idx < buflen {
|
||||
n, err := reader.Read(buf[idx:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
idx += n
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func ReadPacket(reader io.Reader) (*Packet, error) {
|
||||
buf := make([]byte, 2)
|
||||
|
||||
err := readBytes(reader, buf)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
idx := uint64(2)
|
||||
datalen := uint64(buf[1])
|
||||
|
||||
if Debug {
|
||||
fmt.Printf("Read: datalen = %d len(buf) = %d ", datalen, len(buf))
|
||||
|
||||
for _, b := range buf {
|
||||
fmt.Printf("%02X ", b)
|
||||
}
|
||||
|
||||
fmt.Printf("\n")
|
||||
}
|
||||
|
||||
if datalen&128 != 0 {
|
||||
a := datalen - 128
|
||||
|
||||
idx += a
|
||||
buf = resizeBuffer(buf, 2+a)
|
||||
|
||||
err := readBytes(reader, buf[2:])
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
datalen = DecodeInteger(buf[2 : 2+a])
|
||||
|
||||
if Debug {
|
||||
fmt.Printf("Read: a = %d idx = %d datalen = %d len(buf) = %d", a, idx, datalen, len(buf))
|
||||
|
||||
for _, b := range buf {
|
||||
fmt.Printf("%02X ", b)
|
||||
}
|
||||
|
||||
fmt.Printf("\n")
|
||||
}
|
||||
}
|
||||
|
||||
buf = resizeBuffer(buf, idx+datalen)
|
||||
err = readBytes(reader, buf[idx:])
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if Debug {
|
||||
fmt.Printf("Read: len( buf ) = %d idx=%d datalen=%d idx+datalen=%d\n", len(buf), idx, datalen, idx+datalen)
|
||||
|
||||
for _, b := range buf {
|
||||
fmt.Printf("%02X ", b)
|
||||
}
|
||||
}
|
||||
|
||||
p := DecodePacket(buf)
|
||||
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func DecodeString(data []byte) (ret string) {
|
||||
// for _, c := range data {
|
||||
// ret += fmt.Sprintf("%c", c)
|
||||
// }
|
||||
|
||||
return string(data)
|
||||
}
|
||||
|
||||
func DecodeInteger(data []byte) (ret uint64) {
|
||||
for _, i := range data {
|
||||
ret = ret * 256
|
||||
ret = ret + uint64(i)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func EncodeInteger(val uint64) []byte {
|
||||
var out bytes.Buffer
|
||||
|
||||
found := false
|
||||
|
||||
shift := uint(56)
|
||||
|
||||
mask := uint64(0xFF00000000000000)
|
||||
|
||||
for mask > 0 {
|
||||
if !found && (val&mask != 0) {
|
||||
found = true
|
||||
}
|
||||
|
||||
if found || (shift == 0) {
|
||||
out.Write([]byte{byte((val & mask) >> shift)})
|
||||
}
|
||||
|
||||
shift -= 8
|
||||
mask = mask >> 8
|
||||
}
|
||||
|
||||
return out.Bytes()
|
||||
}
|
||||
|
||||
func DecodePacket(data []byte) *Packet {
|
||||
p, _ := decodePacket(data)
|
||||
|
||||
return p
|
||||
}
|
||||
|
||||
func decodePacket(data []byte) (*Packet, []byte) {
|
||||
if Debug {
|
||||
fmt.Printf("decodePacket: enter %d\n", len(data))
|
||||
}
|
||||
|
||||
p := new(Packet)
|
||||
|
||||
p.ClassType = data[0] & ClassBitmask
|
||||
p.TagType = data[0] & TypeBitmask
|
||||
p.Tag = data[0] & TagBitmask
|
||||
|
||||
datalen := DecodeInteger(data[1:2])
|
||||
datapos := uint64(2)
|
||||
|
||||
if datalen&128 != 0 {
|
||||
datalen -= 128
|
||||
datapos += datalen
|
||||
datalen = DecodeInteger(data[2 : 2+datalen])
|
||||
}
|
||||
|
||||
p.Data = new(bytes.Buffer)
|
||||
|
||||
p.Children = make([]*Packet, 0, 2)
|
||||
|
||||
p.Value = nil
|
||||
|
||||
value_data := data[datapos : datapos+datalen]
|
||||
|
||||
if p.TagType == TypeConstructed {
|
||||
for len(value_data) != 0 {
|
||||
var child *Packet
|
||||
|
||||
child, value_data = decodePacket(value_data)
|
||||
p.AppendChild(child)
|
||||
}
|
||||
} else if p.ClassType == ClassUniversal {
|
||||
p.Data.Write(data[datapos : datapos+datalen])
|
||||
p.ByteValue = value_data
|
||||
|
||||
switch p.Tag {
|
||||
case TagEOC:
|
||||
case TagBoolean:
|
||||
val := DecodeInteger(value_data)
|
||||
|
||||
p.Value = val != 0
|
||||
case TagInteger:
|
||||
p.Value = DecodeInteger(value_data)
|
||||
case TagBitString:
|
||||
case TagOctetString:
|
||||
p.Value = DecodeString(value_data)
|
||||
case TagNULL:
|
||||
case TagObjectIdentifier:
|
||||
case TagObjectDescriptor:
|
||||
case TagExternal:
|
||||
case TagRealFloat:
|
||||
case TagEnumerated:
|
||||
p.Value = DecodeInteger(value_data)
|
||||
case TagEmbeddedPDV:
|
||||
case TagUTF8String:
|
||||
case TagRelativeOID:
|
||||
case TagSequence:
|
||||
case TagSet:
|
||||
case TagNumericString:
|
||||
case TagPrintableString:
|
||||
p.Value = DecodeString(value_data)
|
||||
case TagT61String:
|
||||
case TagVideotexString:
|
||||
case TagIA5String:
|
||||
case TagUTCTime:
|
||||
case TagGeneralizedTime:
|
||||
case TagGraphicString:
|
||||
case TagVisibleString:
|
||||
case TagGeneralString:
|
||||
case TagUniversalString:
|
||||
case TagCharacterString:
|
||||
case TagBMPString:
|
||||
}
|
||||
} else {
|
||||
p.Data.Write(data[datapos : datapos+datalen])
|
||||
}
|
||||
|
||||
return p, data[datapos+datalen:]
|
||||
}
|
||||
|
||||
func (p *Packet) DataLength() uint64 {
|
||||
return uint64(p.Data.Len())
|
||||
}
|
||||
|
||||
func (p *Packet) Bytes() []byte {
|
||||
var out bytes.Buffer
|
||||
|
||||
out.Write([]byte{p.ClassType | p.TagType | p.Tag})
|
||||
packet_length := EncodeInteger(p.DataLength())
|
||||
|
||||
if p.DataLength() > 127 || len(packet_length) > 1 {
|
||||
out.Write([]byte{byte(len(packet_length) | 128)})
|
||||
out.Write(packet_length)
|
||||
} else {
|
||||
out.Write(packet_length)
|
||||
}
|
||||
|
||||
out.Write(p.Data.Bytes())
|
||||
|
||||
return out.Bytes()
|
||||
}
|
||||
|
||||
func (p *Packet) AppendChild(child *Packet) {
|
||||
p.Data.Write(child.Bytes())
|
||||
|
||||
if len(p.Children) == cap(p.Children) {
|
||||
newChildren := make([]*Packet, cap(p.Children)*2)
|
||||
|
||||
copy(newChildren, p.Children)
|
||||
p.Children = newChildren[0:len(p.Children)]
|
||||
}
|
||||
|
||||
p.Children = p.Children[0 : len(p.Children)+1]
|
||||
p.Children[len(p.Children)-1] = child
|
||||
}
|
||||
|
||||
func Encode(ClassType, TagType, Tag uint8, Value interface{}, Description string) *Packet {
|
||||
p := new(Packet)
|
||||
|
||||
p.ClassType = ClassType
|
||||
p.TagType = TagType
|
||||
p.Tag = Tag
|
||||
p.Data = new(bytes.Buffer)
|
||||
|
||||
p.Children = make([]*Packet, 0, 2)
|
||||
|
||||
p.Value = Value
|
||||
p.Description = Description
|
||||
|
||||
if Value != nil {
|
||||
v := reflect.ValueOf(Value)
|
||||
|
||||
if ClassType == ClassUniversal {
|
||||
switch Tag {
|
||||
case TagOctetString:
|
||||
sv, ok := v.Interface().(string)
|
||||
|
||||
if ok {
|
||||
p.Data.Write([]byte(sv))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return p
|
||||
}
|
||||
|
||||
func NewSequence(Description string) *Packet {
|
||||
return Encode(ClassUniversal, TypePrimitive, TagSequence, nil, Description)
|
||||
}
|
||||
|
||||
func NewBoolean(ClassType, TagType, Tag uint8, Value bool, Description string) *Packet {
|
||||
intValue := 0
|
||||
|
||||
if Value {
|
||||
intValue = 1
|
||||
}
|
||||
|
||||
p := Encode(ClassType, TagType, Tag, nil, Description)
|
||||
|
||||
p.Value = Value
|
||||
p.Data.Write(EncodeInteger(uint64(intValue)))
|
||||
|
||||
return p
|
||||
}
|
||||
|
||||
func NewInteger(ClassType, TagType, Tag uint8, Value uint64, Description string) *Packet {
|
||||
p := Encode(ClassType, TagType, Tag, nil, Description)
|
||||
|
||||
p.Value = Value
|
||||
p.Data.Write(EncodeInteger(Value))
|
||||
|
||||
return p
|
||||
}
|
||||
|
||||
func NewString(ClassType, TagType, Tag uint8, Value, Description string) *Packet {
|
||||
p := Encode(ClassType, TagType, Tag, nil, Description)
|
||||
|
||||
p.Value = Value
|
||||
p.Data.Write([]byte(Value))
|
||||
|
||||
return p
|
||||
}
|
||||
@@ -5,22 +5,36 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"github.com/Unknwon/macaron"
|
||||
"gopkg.in/macaron.v1"
|
||||
|
||||
"github.com/macaron-contrib/binding"
|
||||
"github.com/go-macaron/binding"
|
||||
)
|
||||
|
||||
type AdminCrateUserForm struct {
|
||||
LoginType string `binding:"Required"`
|
||||
LoginName string
|
||||
UserName string `binding:"Required;AlphaDashDot;MaxSize(35)"`
|
||||
Email string `binding:"Required;Email;MaxSize(254)"`
|
||||
Password string `binding:"MaxSize(255)"`
|
||||
SendNotify bool
|
||||
}
|
||||
|
||||
func (f *AdminCrateUserForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
|
||||
return validate(errs, ctx.Data, f, ctx.Locale)
|
||||
}
|
||||
|
||||
type AdminEditUserForm struct {
|
||||
FullName string `form:"fullname" binding:"MaxSize(100)"`
|
||||
Email string `binding:"Required;Email;MaxSize(254)"`
|
||||
Password string `binding:"OmitEmpty;MinSize(6);MaxSize(255)"`
|
||||
Website string `binding:"MaxSize(50)"`
|
||||
Location string `binding:"MaxSize(50)"`
|
||||
Avatar string `binding:"Required;Email;MaxSize(50)"`
|
||||
Active bool
|
||||
Admin bool
|
||||
AllowGitHook bool
|
||||
LoginType int
|
||||
LoginType string `binding:"Required"`
|
||||
LoginName string
|
||||
FullName string `binding:"MaxSize(100)"`
|
||||
Email string `binding:"Required;Email;MaxSize(254)"`
|
||||
Password string `binding:"MaxSize(255)"`
|
||||
Website string `binding:"MaxSize(50)"`
|
||||
Location string `binding:"MaxSize(50)"`
|
||||
Active bool
|
||||
Admin bool
|
||||
AllowGitHook bool
|
||||
AllowImportLocal bool
|
||||
}
|
||||
|
||||
func (f *AdminEditUserForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
|
||||
|
||||
@@ -1,73 +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 apiv1
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
|
||||
"github.com/Unknwon/macaron"
|
||||
"github.com/macaron-contrib/binding"
|
||||
|
||||
"github.com/gogits/gogs/modules/auth"
|
||||
)
|
||||
|
||||
type MarkdownForm struct {
|
||||
Text string
|
||||
Mode string
|
||||
Context string
|
||||
}
|
||||
|
||||
func (f *MarkdownForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
|
||||
return validateApiReq(errs, ctx.Data, f)
|
||||
}
|
||||
|
||||
func validateApiReq(errs binding.Errors, data map[string]interface{}, f auth.Form) binding.Errors {
|
||||
if errs.Len() == 0 {
|
||||
return errs
|
||||
}
|
||||
|
||||
data["HasError"] = true
|
||||
|
||||
typ := reflect.TypeOf(f)
|
||||
val := reflect.ValueOf(f)
|
||||
|
||||
if typ.Kind() == reflect.Ptr {
|
||||
typ = typ.Elem()
|
||||
val = val.Elem()
|
||||
}
|
||||
|
||||
for i := 0; i < typ.NumField(); i++ {
|
||||
field := typ.Field(i)
|
||||
|
||||
fieldName := field.Tag.Get("form")
|
||||
// Allow ignored fields in the struct
|
||||
if fieldName == "-" {
|
||||
continue
|
||||
}
|
||||
|
||||
if errs[0].FieldNames[0] == field.Name {
|
||||
switch errs[0].Classification {
|
||||
case binding.ERR_REQUIRED:
|
||||
data["ErrorMsg"] = fieldName + " cannot be empty"
|
||||
case binding.ERR_ALPHA_DASH:
|
||||
data["ErrorMsg"] = fieldName + " must be valid alpha or numeric or dash(-_) characters"
|
||||
case binding.ERR_ALPHA_DASH_DOT:
|
||||
data["ErrorMsg"] = fieldName + " must be valid alpha or numeric or dash(-_) or dot characters"
|
||||
case binding.ERR_MIN_SIZE:
|
||||
data["ErrorMsg"] = fieldName + " must contain at least " + auth.GetMinSize(field) + " characters"
|
||||
case binding.ERR_MAX_SIZE:
|
||||
data["ErrorMsg"] = fieldName + " must contain at most " + auth.GetMaxSize(field) + " characters"
|
||||
case binding.ERR_EMAIL:
|
||||
data["ErrorMsg"] = fieldName + " is not a valid e-mail address"
|
||||
case binding.ERR_URL:
|
||||
data["ErrorMsg"] = fieldName + " is not a valid URL"
|
||||
default:
|
||||
data["ErrorMsg"] = "Unknown error: " + errs[0].Classification
|
||||
}
|
||||
return errs
|
||||
}
|
||||
}
|
||||
return errs
|
||||
}
|
||||
@@ -10,9 +10,9 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/Unknwon/com"
|
||||
"github.com/Unknwon/macaron"
|
||||
"github.com/macaron-contrib/binding"
|
||||
"github.com/macaron-contrib/session"
|
||||
"github.com/go-macaron/binding"
|
||||
"github.com/go-macaron/session"
|
||||
"gopkg.in/macaron.v1"
|
||||
|
||||
"github.com/gogits/gogs/models"
|
||||
"github.com/gogits/gogs/modules/base"
|
||||
@@ -181,7 +181,7 @@ func AssignForm(form interface{}, data map[string]interface{}) {
|
||||
}
|
||||
}
|
||||
|
||||
func getSize(field reflect.StructField, prefix string) string {
|
||||
func getRuleBody(field reflect.StructField, prefix string) string {
|
||||
for _, rule := range strings.Split(field.Tag.Get("binding"), ";") {
|
||||
if strings.HasPrefix(rule, prefix) {
|
||||
return rule[len(prefix) : len(rule)-1]
|
||||
@@ -191,15 +191,19 @@ func getSize(field reflect.StructField, prefix string) string {
|
||||
}
|
||||
|
||||
func GetSize(field reflect.StructField) string {
|
||||
return getSize(field, "Size(")
|
||||
return getRuleBody(field, "Size(")
|
||||
}
|
||||
|
||||
func GetMinSize(field reflect.StructField) string {
|
||||
return getSize(field, "MinSize(")
|
||||
return getRuleBody(field, "MinSize(")
|
||||
}
|
||||
|
||||
func GetMaxSize(field reflect.StructField) string {
|
||||
return getSize(field, "MaxSize(")
|
||||
return getRuleBody(field, "MaxSize(")
|
||||
}
|
||||
|
||||
func GetInclude(field reflect.StructField) string {
|
||||
return getRuleBody(field, "Include(")
|
||||
}
|
||||
|
||||
// FIXME: struct contains a struct
|
||||
@@ -260,6 +264,8 @@ func validate(errs binding.Errors, data map[string]interface{}, f Form, l macaro
|
||||
data["ErrorMsg"] = trName + l.Tr("form.email_error")
|
||||
case binding.ERR_URL:
|
||||
data["ErrorMsg"] = trName + l.Tr("form.url_error")
|
||||
case binding.ERR_INCLUDE:
|
||||
data["ErrorMsg"] = trName + l.Tr("form.include_error", GetInclude(field))
|
||||
default:
|
||||
data["ErrorMsg"] = l.Tr("form.unknown_error") + " " + errs[0].Classification
|
||||
}
|
||||
|
||||
@@ -5,32 +5,33 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"github.com/Unknwon/macaron"
|
||||
"github.com/macaron-contrib/binding"
|
||||
"github.com/go-macaron/binding"
|
||||
"gopkg.in/macaron.v1"
|
||||
)
|
||||
|
||||
type AuthenticationForm struct {
|
||||
ID int64 `form:"id"`
|
||||
Type int
|
||||
Name string `binding:"Required;MaxSize(50)"`
|
||||
ID int64
|
||||
Type int `binding:"Range(2,5)"`
|
||||
Name string `binding:"Required;MaxSize(30)"`
|
||||
Host string
|
||||
Port int
|
||||
UseSSL bool `form:"use_ssl"`
|
||||
BindDN string `form:"bind_dn"`
|
||||
BindDN string
|
||||
BindPassword string
|
||||
UserBase string
|
||||
UserDN string
|
||||
AttributeUsername string
|
||||
AttributeName string
|
||||
AttributeSurname string
|
||||
AttributeMail string
|
||||
Filter string
|
||||
AdminFilter string
|
||||
IsActived bool
|
||||
SMTPAuth string `form:"smtp_auth"`
|
||||
SMTPHost string `form:"smtp_host"`
|
||||
SMTPPort int `form:"smtp_port"`
|
||||
TLS bool `form:"tls"`
|
||||
IsActive bool
|
||||
SMTPAuth string
|
||||
SMTPHost string
|
||||
SMTPPort int
|
||||
AllowedDomains string
|
||||
TLS bool
|
||||
SkipVerify bool
|
||||
AllowAutoRegister bool `form:"allowautoregister"`
|
||||
PAMServiceName string
|
||||
}
|
||||
|
||||
|
||||
@@ -4,61 +4,98 @@ Gogs LDAP Authentication Module
|
||||
## About
|
||||
|
||||
This authentication module attempts to authorize and authenticate a user
|
||||
against an LDAP server. Like most LDAP authentication systems, this module does
|
||||
this in two steps. First, it queries the LDAP server using a Bind DN and
|
||||
searches for the user that is attempting to sign in. If the user is found, the
|
||||
module attempts to bind to the server using the user's supplied credentials. If
|
||||
this succeeds, the user has been authenticated, and his account information is
|
||||
retrieved and passed to the Gogs login infrastructure.
|
||||
against an LDAP server. It provides two methods of authentication: LDAP via
|
||||
BindDN, and LDAP simple authentication.
|
||||
|
||||
LDAP via BindDN functions like most LDAP authentication systems. First, it
|
||||
queries the LDAP server using a Bind DN and searches for the user that is
|
||||
attempting to sign in. If the user is found, the module attempts to bind to the
|
||||
server using the user's supplied credentials. If this succeeds, the user has
|
||||
been authenticated, and his account information is retrieved and passed to the
|
||||
Gogs login infrastructure.
|
||||
|
||||
LDAP simple authentication does not utilize a Bind DN. Instead, it binds
|
||||
directly with the LDAP server using the user's supplied credentials. If the bind
|
||||
succeeds and no filter rules out the user, the user is authenticated.
|
||||
|
||||
LDAP via BindDN is recommended for most users. By using a Bind DN, the server
|
||||
can perform authorization by restricting which entries the Bind DN account can
|
||||
read. Further, using a Bind DN with reduced permissions can reduce security risk
|
||||
in the face of application bugs.
|
||||
|
||||
## Usage
|
||||
|
||||
To use this module, add an LDAP authentication source via the Authentications
|
||||
section in the admin panel. The fields should be set as follows:
|
||||
section in the admin panel. Both the LDAP via BindDN and the simple auth LDAP
|
||||
share the following fields:
|
||||
|
||||
* Authorization Name **(required)**
|
||||
* A name to assign to the new method of authorization.
|
||||
* A name to assign to the new method of authorization.
|
||||
|
||||
* Host **(required)**
|
||||
* The address where the LDAP server can be reached.
|
||||
* Example: mydomain.com
|
||||
* The address where the LDAP server can be reached.
|
||||
* Example: mydomain.com
|
||||
|
||||
* Port **(required)**
|
||||
* The port to use when connecting to the server.
|
||||
* Example: 636
|
||||
* The port to use when connecting to the server.
|
||||
* Example: 636
|
||||
|
||||
* Enable TLS Encryption (optional)
|
||||
* Whether to use TLS when connecting to the LDAP server.
|
||||
* Whether to use TLS when connecting to the LDAP server.
|
||||
|
||||
* Bind DN (optional)
|
||||
* The DN to bind to the LDAP server with when searching for the user.
|
||||
This may be left blank to perform an anonymous search.
|
||||
* Example: cn=Search,dc=mydomain,dc=com
|
||||
|
||||
* Bind Password (optional)
|
||||
* The password for the Bind DN specified above, if any.
|
||||
|
||||
* User Search Base **(required)**
|
||||
* The LDAP base at which user accounts will be searched for.
|
||||
* Example: ou=Users,dc=mydomain,dc=com
|
||||
|
||||
* User Filter **(required)**
|
||||
* An LDAP filter declaring how to find the user record that is attempting
|
||||
to authenticate. The '%s' matching parameter will be substituted with
|
||||
the user's username.
|
||||
* Example: (&(objectClass=posixAccount)(uid=%s))
|
||||
* Admin Filter (optional)
|
||||
* An LDAP filter specifying if a user should be given administrator
|
||||
privileges. If a user accounts passes the filter, the user will be
|
||||
privileged as an administrator.
|
||||
* Example: (objectClass=adminAccount)
|
||||
|
||||
* First name attribute (optional)
|
||||
* The attribute of the user's LDAP record containing the user's first
|
||||
name. This will be used to populate their account information.
|
||||
* Example: givenName
|
||||
* The attribute of the user's LDAP record containing the user's first name.
|
||||
This will be used to populate their account information.
|
||||
* Example: givenName
|
||||
|
||||
* Surname name attribute (optional)
|
||||
* The attribute of the user's LDAP record containing the user's surname
|
||||
This will be used to populate their account information.
|
||||
* Example: sn
|
||||
* Surname attribute (optional)
|
||||
* The attribute of the user's LDAP record containing the user's surname This
|
||||
will be used to populate their account information.
|
||||
* Example: sn
|
||||
|
||||
* E-mail attribute **(required)**
|
||||
* The attribute of the user's LDAP record containing the user's email
|
||||
address. This will be used to populate their account information.
|
||||
* Example: mail
|
||||
* The attribute of the user's LDAP record containing the user's email
|
||||
address. This will be used to populate their account information.
|
||||
* Example: mail
|
||||
|
||||
**LDAP via BindDN** adds the following fields:
|
||||
|
||||
* Bind DN (optional)
|
||||
* The DN to bind to the LDAP server with when searching for the user. This
|
||||
may be left blank to perform an anonymous search.
|
||||
* Example: cn=Search,dc=mydomain,dc=com
|
||||
|
||||
* Bind Password (optional)
|
||||
* The password for the Bind DN specified above, if any. _Note: The password
|
||||
is stored in plaintext at the server. As such, ensure that your Bind DN
|
||||
has as few privileges as possible._
|
||||
|
||||
* User Search Base **(required)**
|
||||
* The LDAP base at which user accounts will be searched for.
|
||||
* Example: ou=Users,dc=mydomain,dc=com
|
||||
|
||||
* User Filter **(required)**
|
||||
* An LDAP filter declaring how to find the user record that is attempting to
|
||||
authenticate. The '%s' matching parameter will be substituted with the
|
||||
user's username.
|
||||
* Example: (&(objectClass=posixAccount)(uid=%s))
|
||||
|
||||
**LDAP using simple auth** adds the following fields:
|
||||
|
||||
* User DN **(required)**
|
||||
* A template to use as the user's DN. The `%s` matching parameter will be
|
||||
substituted with the user's username.
|
||||
* Example: cn=%s,ou=Users,dc=mydomain,dc=com
|
||||
* Example: uid=%s,ou=Users,dc=mydomain,dc=com
|
||||
|
||||
* User Filter **(required)**
|
||||
* An LDAP filter declaring when a user should be allowed to log in. The `%s`
|
||||
matching parameter will be substituted with the user's username.
|
||||
* Example: (&(objectClass=posixAccount)(cn=%s))
|
||||
* Example: (&(objectClass=posixAccount)(uid=%s))
|
||||
|
||||
@@ -7,30 +7,58 @@
|
||||
package ldap
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"gopkg.in/ldap.v2"
|
||||
|
||||
"github.com/gogits/gogs/modules/ldap"
|
||||
"github.com/gogits/gogs/modules/log"
|
||||
)
|
||||
|
||||
// Basic LDAP authentication service
|
||||
type Ldapsource struct {
|
||||
Name string // canonical name (ie. corporate.ad)
|
||||
Host string // LDAP host
|
||||
Port int // port number
|
||||
UseSSL bool // Use SSL
|
||||
BindDN string // DN to bind with
|
||||
BindPassword string // Bind DN password
|
||||
UserBase string // Base search path for users
|
||||
AttributeName string // First name attribute
|
||||
AttributeSurname string // Surname attribute
|
||||
AttributeMail string // E-mail attribute
|
||||
Filter string // Query filter to validate entry
|
||||
AdminFilter string // Query filter to check if user is admin
|
||||
Enabled bool // if this source is disabled
|
||||
type Source struct {
|
||||
Name string // canonical name (ie. corporate.ad)
|
||||
Host string // LDAP host
|
||||
Port int // port number
|
||||
UseSSL bool // Use SSL
|
||||
SkipVerify bool
|
||||
BindDN string // DN to bind with
|
||||
BindPassword string // Bind DN password
|
||||
UserBase string // Base search path for users
|
||||
UserDN string // Template for the DN of the user for simple auth
|
||||
AttributeUsername string // Username attribute
|
||||
AttributeName string // First name attribute
|
||||
AttributeSurname string // Surname attribute
|
||||
AttributeMail string // E-mail attribute
|
||||
Filter string // Query filter to validate entry
|
||||
AdminFilter string // Query filter to check if user is admin
|
||||
Enabled bool // if this source is disabled
|
||||
}
|
||||
|
||||
func (ls Ldapsource) FindUserDN(name string) (string, bool) {
|
||||
func (ls *Source) sanitizedUserQuery(username string) (string, bool) {
|
||||
// See http://tools.ietf.org/search/rfc4515
|
||||
badCharacters := "\x00()*\\"
|
||||
if strings.ContainsAny(username, badCharacters) {
|
||||
log.Debug("'%s' contains invalid query characters. Aborting.", username)
|
||||
return "", false
|
||||
}
|
||||
|
||||
return fmt.Sprintf(ls.Filter, username), true
|
||||
}
|
||||
|
||||
func (ls *Source) sanitizedUserDN(username string) (string, bool) {
|
||||
// See http://tools.ietf.org/search/rfc4514: "special characters"
|
||||
badCharacters := "\x00()*\\,='\"#+;<> "
|
||||
if strings.ContainsAny(username, badCharacters) {
|
||||
log.Debug("'%s' contains invalid DN characters. Aborting.", username)
|
||||
return "", false
|
||||
}
|
||||
|
||||
return fmt.Sprintf(ls.UserDN, username), true
|
||||
}
|
||||
|
||||
func (ls *Source) FindUserDN(name string) (string, bool) {
|
||||
l, err := ldapDial(ls)
|
||||
if err != nil {
|
||||
log.Error(4, "LDAP Connect error, %s:%v", ls.Host, err)
|
||||
@@ -52,7 +80,11 @@ func (ls Ldapsource) FindUserDN(name string) (string, bool) {
|
||||
}
|
||||
|
||||
// A search for the user.
|
||||
userFilter := fmt.Sprintf(ls.Filter, name)
|
||||
userFilter, ok := ls.sanitizedUserQuery(name)
|
||||
if !ok {
|
||||
return "", false
|
||||
}
|
||||
|
||||
log.Trace("Searching using filter %s", userFilter)
|
||||
search := ldap.NewSearchRequest(
|
||||
ls.UserBase, ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0,
|
||||
@@ -70,7 +102,7 @@ func (ls Ldapsource) FindUserDN(name string) (string, bool) {
|
||||
|
||||
userDN := sr.Entries[0].DN
|
||||
if userDN == "" {
|
||||
log.Error(4, "LDAP search was succesful, but found no DN!")
|
||||
log.Error(4, "LDAP search was successful, but found no DN!")
|
||||
return "", false
|
||||
}
|
||||
|
||||
@@ -78,30 +110,47 @@ func (ls Ldapsource) FindUserDN(name string) (string, bool) {
|
||||
}
|
||||
|
||||
// searchEntry : search an LDAP source if an entry (name, passwd) is valid and in the specific filter
|
||||
func (ls Ldapsource) SearchEntry(name, passwd string) (string, string, string, bool, bool) {
|
||||
userDN, found := ls.FindUserDN(name)
|
||||
if !found {
|
||||
return "", "", "", false, false
|
||||
func (ls *Source) SearchEntry(name, passwd string, directBind bool) (string, string, string, string, bool, bool) {
|
||||
var userDN string
|
||||
if directBind {
|
||||
log.Trace("LDAP will bind directly via UserDN template: %s", ls.UserDN)
|
||||
|
||||
var ok bool
|
||||
userDN, ok = ls.sanitizedUserDN(name)
|
||||
if !ok {
|
||||
return "", "", "", "", false, false
|
||||
}
|
||||
} else {
|
||||
log.Trace("LDAP will use BindDN.")
|
||||
|
||||
var found bool
|
||||
userDN, found = ls.FindUserDN(name)
|
||||
if !found {
|
||||
return "", "", "", "", false, false
|
||||
}
|
||||
}
|
||||
|
||||
l, err := ldapDial(ls)
|
||||
if err != nil {
|
||||
log.Error(4, "LDAP Connect error, %s:%v", ls.Host, err)
|
||||
log.Error(4, "LDAP Connect error (%s): %v", ls.Host, err)
|
||||
ls.Enabled = false
|
||||
return "", "", "", false, false
|
||||
return "", "", "", "", false, false
|
||||
}
|
||||
|
||||
defer l.Close()
|
||||
|
||||
log.Trace("Binding with userDN: %s", userDN)
|
||||
err = l.Bind(userDN, passwd)
|
||||
if err != nil {
|
||||
log.Debug("LDAP auth. failed for %s, reason: %v", userDN, err)
|
||||
return "", "", "", false, false
|
||||
return "", "", "", "", false, false
|
||||
}
|
||||
|
||||
log.Trace("Bound successfully with userDN: %s", userDN)
|
||||
userFilter := fmt.Sprintf(ls.Filter, name)
|
||||
userFilter, ok := ls.sanitizedUserQuery(name)
|
||||
if !ok {
|
||||
return "", "", "", "", false, false
|
||||
}
|
||||
|
||||
search := ldap.NewSearchRequest(
|
||||
userDN, ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, userFilter,
|
||||
[]string{ls.AttributeName, ls.AttributeSurname, ls.AttributeMail},
|
||||
@@ -110,12 +159,18 @@ func (ls Ldapsource) SearchEntry(name, passwd string) (string, string, string, b
|
||||
sr, err := l.Search(search)
|
||||
if err != nil {
|
||||
log.Error(4, "LDAP Search failed unexpectedly! (%v)", err)
|
||||
return "", "", "", false, false
|
||||
return "", "", "", "", false, false
|
||||
} else if len(sr.Entries) < 1 {
|
||||
log.Error(4, "LDAP Search failed unexpectedly! (0 entries)")
|
||||
return "", "", "", false, false
|
||||
if directBind {
|
||||
log.Error(4, "User filter inhibited user login.")
|
||||
} else {
|
||||
log.Error(4, "LDAP Search failed unexpectedly! (0 entries)")
|
||||
}
|
||||
|
||||
return "", "", "", "", false, false
|
||||
}
|
||||
|
||||
username_attr := sr.Entries[0].GetAttributeValue(ls.AttributeUsername)
|
||||
name_attr := sr.Entries[0].GetAttributeValue(ls.AttributeName)
|
||||
sn_attr := sr.Entries[0].GetAttributeValue(ls.AttributeSurname)
|
||||
mail_attr := sr.Entries[0].GetAttributeValue(ls.AttributeMail)
|
||||
@@ -137,13 +192,15 @@ func (ls Ldapsource) SearchEntry(name, passwd string) (string, string, string, b
|
||||
}
|
||||
}
|
||||
|
||||
return name_attr, sn_attr, mail_attr, admin_attr, true
|
||||
return username_attr, name_attr, sn_attr, mail_attr, admin_attr, true
|
||||
}
|
||||
|
||||
func ldapDial(ls Ldapsource) (*ldap.Conn, error) {
|
||||
func ldapDial(ls *Source) (*ldap.Conn, error) {
|
||||
if ls.UseSSL {
|
||||
log.Debug("Using TLS for LDAP")
|
||||
return ldap.DialTLS("tcp", fmt.Sprintf("%s:%d", ls.Host, ls.Port), nil)
|
||||
log.Debug("Using TLS for LDAP without verifying: %v", ls.SkipVerify)
|
||||
return ldap.DialTLS("tcp", fmt.Sprintf("%s:%d", ls.Host, ls.Port), &tls.Config{
|
||||
InsecureSkipVerify: ls.SkipVerify,
|
||||
})
|
||||
} else {
|
||||
return ldap.Dial("tcp", fmt.Sprintf("%s:%d", ls.Host, ls.Port))
|
||||
}
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"github.com/Unknwon/macaron"
|
||||
"github.com/macaron-contrib/binding"
|
||||
"github.com/go-macaron/binding"
|
||||
"gopkg.in/macaron.v1"
|
||||
)
|
||||
|
||||
// ________ .__ __ .__
|
||||
@@ -17,8 +17,7 @@ import (
|
||||
// \/ /_____/ \/ \/ \/ \/ \/
|
||||
|
||||
type CreateOrgForm struct {
|
||||
OrgName string `form:"org_name" binding:"Required;AlphaDashDot;MaxSize(30)"`
|
||||
Email string `form:"email" binding:"Required;Email;MaxSize(50)"`
|
||||
OrgName string `binding:"Required;AlphaDashDot;MaxSize(35)" locale:"org.org_name_holder"`
|
||||
}
|
||||
|
||||
func (f *CreateOrgForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
|
||||
@@ -26,13 +25,11 @@ func (f *CreateOrgForm) Validate(ctx *macaron.Context, errs binding.Errors) bind
|
||||
}
|
||||
|
||||
type UpdateOrgSettingForm struct {
|
||||
OrgUserName string `form:"uname" binding:"Required;AlphaDashDot;MaxSize(30)" locale:"org.org_name_holder"`
|
||||
OrgFullName string `form:"fullname" binding:"MaxSize(100)"`
|
||||
Email string `form:"email" binding:"Required;Email;MaxSize(50)"`
|
||||
Description string `form:"desc" binding:"MaxSize(255)"`
|
||||
Website string `form:"website" binding:"Url;MaxSize(100)"`
|
||||
Location string `form:"location" binding:"MaxSize(50)"`
|
||||
Avatar string `form:"avatar" binding:"Required;Email;MaxSize(50)"`
|
||||
Name string `binding:"Required;AlphaDashDot;MaxSize(35)" locale:"org.org_name_holder"`
|
||||
FullName string `binding:"MaxSize(100)"`
|
||||
Description string `binding:"MaxSize(255)"`
|
||||
Website string `binding:"Url;MaxSize(100)"`
|
||||
Location string `binding:"MaxSize(50)"`
|
||||
}
|
||||
|
||||
func (f *UpdateOrgSettingForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
|
||||
|
||||
@@ -5,8 +5,14 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"github.com/Unknwon/macaron"
|
||||
"github.com/macaron-contrib/binding"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/Unknwon/com"
|
||||
"github.com/go-macaron/binding"
|
||||
"gopkg.in/macaron.v1"
|
||||
|
||||
"github.com/gogits/gogs/models"
|
||||
)
|
||||
|
||||
// _______________________________________ _________.______________________ _______________.___.
|
||||
@@ -37,8 +43,8 @@ type MigrateRepoForm struct {
|
||||
AuthPassword string `json:"auth_password"`
|
||||
Uid int64 `json:"uid" binding:"Required"`
|
||||
RepoName string `json:"repo_name" binding:"Required;AlphaDashDot;MaxSize(100)"`
|
||||
Private bool `json:"mirror"`
|
||||
Mirror bool `json:"private"`
|
||||
Mirror bool `json:"mirror"`
|
||||
Private bool `json:"private"`
|
||||
Description string `json:"description" binding:"MaxSize(255)"`
|
||||
}
|
||||
|
||||
@@ -46,6 +52,34 @@ func (f *MigrateRepoForm) Validate(ctx *macaron.Context, errs binding.Errors) bi
|
||||
return validate(errs, ctx.Data, f, ctx.Locale)
|
||||
}
|
||||
|
||||
// ParseRemoteAddr checks if given remote address is valid,
|
||||
// and returns composed URL with needed username and passowrd.
|
||||
// It also checks if given user has permission when remote address
|
||||
// is actually a local path.
|
||||
func (f MigrateRepoForm) ParseRemoteAddr(user *models.User) (string, error) {
|
||||
remoteAddr := f.CloneAddr
|
||||
|
||||
// Remote address can be HTTP/HTTPS/Git URL or local path.
|
||||
if strings.HasPrefix(remoteAddr, "http://") ||
|
||||
strings.HasPrefix(remoteAddr, "https://") ||
|
||||
strings.HasPrefix(remoteAddr, "git://") {
|
||||
u, err := url.Parse(remoteAddr)
|
||||
if err != nil {
|
||||
return "", models.ErrInvalidCloneAddr{IsURLError: true}
|
||||
}
|
||||
if len(f.AuthUsername)+len(f.AuthPassword) > 0 {
|
||||
u.User = url.UserPassword(f.AuthUsername, f.AuthPassword)
|
||||
}
|
||||
remoteAddr = u.String()
|
||||
} else if !user.CanImportLocal() {
|
||||
return "", models.ErrInvalidCloneAddr{IsPermissionDenied: true}
|
||||
} else if !com.IsDir(remoteAddr) {
|
||||
return "", models.ErrInvalidCloneAddr{IsInvalidPath: true}
|
||||
}
|
||||
|
||||
return remoteAddr, nil
|
||||
}
|
||||
|
||||
type RepoSettingForm struct {
|
||||
RepoName string `binding:"Required;AlphaDashDot;MaxSize(100)"`
|
||||
Description string `binding:"MaxSize(255)"`
|
||||
@@ -53,6 +87,13 @@ type RepoSettingForm struct {
|
||||
Branch string
|
||||
Interval int
|
||||
Private bool
|
||||
|
||||
// Advanced settings
|
||||
EnableWiki bool
|
||||
EnableIssues bool
|
||||
EnableExternalTracker bool
|
||||
TrackerURLFormat string
|
||||
EnablePulls bool
|
||||
}
|
||||
|
||||
func (f *RepoSettingForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
|
||||
@@ -181,12 +222,12 @@ func (f *CreateLabelForm) Validate(ctx *macaron.Context, errs binding.Errors) bi
|
||||
// \/ \/ \/ \/ \/ \/
|
||||
|
||||
type NewReleaseForm struct {
|
||||
TagName string `form:"tag_name" binding:"Required"`
|
||||
TagName string `binding:"Required"`
|
||||
Target string `form:"tag_target" binding:"Required"`
|
||||
Title string `form:"title" binding:"Required"`
|
||||
Content string `form:"content" binding:"Required"`
|
||||
Draft string `form:"draft"`
|
||||
Prerelease bool `form:"prerelease"`
|
||||
Title string `binding:"Required"`
|
||||
Content string
|
||||
Draft string
|
||||
Prerelease bool
|
||||
}
|
||||
|
||||
func (f *NewReleaseForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
|
||||
@@ -203,3 +244,22 @@ type EditReleaseForm struct {
|
||||
func (f *EditReleaseForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
|
||||
return validate(errs, ctx.Data, f, ctx.Locale)
|
||||
}
|
||||
|
||||
// __ __.__ __ .__
|
||||
// / \ / \__| | _|__|
|
||||
// \ \/\/ / | |/ / |
|
||||
// \ /| | <| |
|
||||
// \__/\ / |__|__|_ \__|
|
||||
// \/ \/
|
||||
|
||||
type NewWikiForm struct {
|
||||
OldTitle string
|
||||
Title string `binding:"Required"`
|
||||
Content string `binding:"Required"`
|
||||
Message string
|
||||
}
|
||||
|
||||
// FIXME: use code generation to generate this method.
|
||||
func (f *NewWikiForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
|
||||
return validate(errs, ctx.Data, f, ctx.Locale)
|
||||
}
|
||||
|
||||
@@ -7,8 +7,8 @@ package auth
|
||||
import (
|
||||
"mime/multipart"
|
||||
|
||||
"github.com/Unknwon/macaron"
|
||||
"github.com/macaron-contrib/binding"
|
||||
"github.com/go-macaron/binding"
|
||||
"gopkg.in/macaron.v1"
|
||||
)
|
||||
|
||||
type InstallForm struct {
|
||||
@@ -30,7 +30,7 @@ type InstallForm struct {
|
||||
|
||||
SMTPHost string
|
||||
SMTPFrom string
|
||||
SMTPEmail string `binding:"OmitEmpty;Email;MaxSize(50)" locale:"install.mailer_user"`
|
||||
SMTPEmail string `binding:"OmitEmpty;Email;MaxSize(254)" locale:"install.mailer_user"`
|
||||
SMTPPasswd string
|
||||
RegisterConfirm bool
|
||||
MailNotify bool
|
||||
@@ -38,12 +38,13 @@ type InstallForm struct {
|
||||
OfflineMode bool
|
||||
DisableGravatar bool
|
||||
DisableRegistration bool
|
||||
EnableCaptcha bool
|
||||
RequireSignInView bool
|
||||
|
||||
AdminName string `binding:"OmitEmpty;AlphaDashDot;MaxSize(30)" locale:"install.admin_name"`
|
||||
AdminPasswd string `binding:"OmitEmpty;MaxSize(255)" locale:"install.admin_password"`
|
||||
AdminConfirmPasswd string
|
||||
AdminEmail string `binding:"OmitEmpty;Email;MaxSize(50)" locale:"install.admin_email"`
|
||||
AdminEmail string `binding:"OmitEmpty;MinSize(3);MaxSize(254);Include(@)" locale:"install.admin_email"`
|
||||
}
|
||||
|
||||
func (f *InstallForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
|
||||
@@ -58,12 +59,10 @@ func (f *InstallForm) Validate(ctx *macaron.Context, errs binding.Errors) bindin
|
||||
// \/ \/
|
||||
|
||||
type RegisterForm struct {
|
||||
UserName string `form:"uname" binding:"Required;AlphaDashDot;MaxSize(35)"`
|
||||
Email string `form:"email" binding:"Required;Email;MaxSize(254)"`
|
||||
Password string `form:"password" binding:"Required;MaxSize(255)"`
|
||||
Retype string `form:"retype"`
|
||||
LoginType string `form:"logintype"`
|
||||
LoginName string `form:"loginname"`
|
||||
UserName string `binding:"Required;AlphaDashDot;MaxSize(35)"`
|
||||
Email string `binding:"Required;Email;MaxSize(254)"`
|
||||
Password string `binding:"Required;MaxSize(255)"`
|
||||
Retype string
|
||||
}
|
||||
|
||||
func (f *RegisterForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
|
||||
@@ -71,9 +70,9 @@ func (f *RegisterForm) Validate(ctx *macaron.Context, errs binding.Errors) bindi
|
||||
}
|
||||
|
||||
type SignInForm struct {
|
||||
UserName string `form:"uname" binding:"Required;MaxSize(254)"`
|
||||
Password string `form:"password" binding:"Required;MaxSize(255)"`
|
||||
Remember bool `form:"remember"`
|
||||
UserName string `binding:"Required;MaxSize(254)"`
|
||||
Password string `binding:"Required;MaxSize(255)"`
|
||||
Remember bool
|
||||
}
|
||||
|
||||
func (f *SignInForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
|
||||
@@ -88,12 +87,12 @@ func (f *SignInForm) Validate(ctx *macaron.Context, errs binding.Errors) binding
|
||||
// \/ \/ \/ \/ \/
|
||||
|
||||
type UpdateProfileForm struct {
|
||||
UserName string `form:"uname" binding:"Required;MaxSize(35)"`
|
||||
FullName string `form:"fullname" binding:"MaxSize(100)"`
|
||||
Email string `form:"email" binding:"Required;Email;MaxSize(254)"`
|
||||
Website string `form:"website" binding:"Url;MaxSize(100)"`
|
||||
Location string `form:"location" binding:"MaxSize(50)"`
|
||||
Avatar string `form:"avatar" binding:"Required;Email;MaxSize(254)"`
|
||||
Name string `binding:"Required;MaxSize(35)"`
|
||||
FullName string `binding:"MaxSize(100)"`
|
||||
Email string `binding:"Required;Email;MaxSize(254)"`
|
||||
Website string `binding:"Url;MaxSize(100)"`
|
||||
Location string `binding:"MaxSize(50)"`
|
||||
Gravatar string `binding:"Required;Email;MaxSize(254)"`
|
||||
}
|
||||
|
||||
func (f *UpdateProfileForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
|
||||
@@ -101,8 +100,8 @@ func (f *UpdateProfileForm) Validate(ctx *macaron.Context, errs binding.Errors)
|
||||
}
|
||||
|
||||
type UploadAvatarForm struct {
|
||||
Enable bool `form:"enable"`
|
||||
Avatar *multipart.FileHeader `form:"avatar"`
|
||||
Enable bool
|
||||
Avatar *multipart.FileHeader
|
||||
}
|
||||
|
||||
func (f *UploadAvatarForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
|
||||
@@ -110,7 +109,7 @@ func (f *UploadAvatarForm) Validate(ctx *macaron.Context, errs binding.Errors) b
|
||||
}
|
||||
|
||||
type AddEmailForm struct {
|
||||
Email string `binding:"Required;Email;MaxSize(50)"`
|
||||
Email string `binding:"Required;Email;MaxSize(254)"`
|
||||
}
|
||||
|
||||
func (f *AddEmailForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors {
|
||||
|
||||
@@ -32,13 +32,15 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/issue9/identicon"
|
||||
"github.com/nfnt/resize"
|
||||
|
||||
"github.com/gogits/gogs/modules/identicon"
|
||||
"github.com/gogits/gogs/modules/log"
|
||||
"github.com/gogits/gogs/modules/setting"
|
||||
)
|
||||
|
||||
//FIXME: remove cache module
|
||||
|
||||
var gravatarSource string
|
||||
|
||||
func UpdateGravatarSource() {
|
||||
@@ -102,7 +104,7 @@ func New(hash string, cacheDir string) *Avatar {
|
||||
expireDuration: time.Minute * 10,
|
||||
reqParams: url.Values{
|
||||
"d": {"retro"},
|
||||
"size": {"200"},
|
||||
"size": {"290"},
|
||||
"r": {"pg"}}.Encode(),
|
||||
imagePath: filepath.Join(cacheDir, hash+".image"), //maybe png or jpeg
|
||||
}
|
||||
@@ -153,7 +155,7 @@ func (this *Avatar) Encode(wr io.Writer, size int) (err error) {
|
||||
if img, err = decodeImageFile(imgPath); err != nil {
|
||||
return
|
||||
}
|
||||
m := resize.Resize(uint(size), 0, img, resize.NearestNeighbor)
|
||||
m := resize.Resize(uint(size), 0, img, resize.Lanczos3)
|
||||
return jpeg.Encode(wr, m, nil)
|
||||
}
|
||||
|
||||
@@ -192,7 +194,7 @@ func (this *service) mustInt(r *http.Request, defaultValue int, keys ...string)
|
||||
func (this *service) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
urlPath := r.URL.Path
|
||||
hash := urlPath[strings.LastIndex(urlPath, "/")+1:]
|
||||
size := this.mustInt(r, 80, "s", "size") // default size = 80*80
|
||||
size := this.mustInt(r, 290, "s", "size") // default size = 290*290
|
||||
|
||||
avatar := New(hash, this.cacheDir)
|
||||
avatar.AlterImage = this.altImage
|
||||
|
||||
@@ -4,15 +4,29 @@
|
||||
|
||||
package base
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
const DOC_URL = "https://github.com/gogits/go-gogs-client/wiki"
|
||||
|
||||
type (
|
||||
TplName string
|
||||
|
||||
ApiJsonErr struct {
|
||||
Message string `json:"message"`
|
||||
DocUrl string `json:"url"`
|
||||
}
|
||||
)
|
||||
|
||||
var GoGetMetas = make(map[string]bool)
|
||||
|
||||
// ExecPath returns the executable path.
|
||||
func ExecPath() (string, error) {
|
||||
file, err := exec.LookPath(os.Args[0])
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
p, err := filepath.Abs(file)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return p, nil
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/Unknwon/com"
|
||||
"github.com/russross/blackfriday"
|
||||
"golang.org/x/net/html"
|
||||
|
||||
@@ -99,12 +100,33 @@ func (options *CustomRender) Link(out *bytes.Buffer, link []byte, title []byte,
|
||||
options.Renderer.Link(out, link, title, content)
|
||||
}
|
||||
|
||||
var (
|
||||
svgSuffix = []byte(".svg")
|
||||
svgSuffixWithMark = []byte(".svg?")
|
||||
)
|
||||
|
||||
func (options *CustomRender) Image(out *bytes.Buffer, link []byte, title []byte, alt []byte) {
|
||||
if len(link) > 0 && !isLink(link) {
|
||||
link = []byte(path.Join(strings.Replace(options.urlPrefix, "/src/", "/raw/", 1), string(link)))
|
||||
prefix := strings.Replace(options.urlPrefix, "/src/", "/raw/", 1)
|
||||
if len(link) > 0 {
|
||||
if isLink(link) {
|
||||
// External link with .svg suffix usually means CI status.
|
||||
if bytes.HasSuffix(link, svgSuffix) || bytes.Contains(link, svgSuffixWithMark) {
|
||||
options.Renderer.Image(out, link, title, alt)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
if link[0] != '/' {
|
||||
prefix += "/"
|
||||
}
|
||||
link = []byte(prefix + string(link))
|
||||
}
|
||||
}
|
||||
|
||||
out.WriteString(`<a href="`)
|
||||
out.Write(link)
|
||||
out.WriteString(`">`)
|
||||
options.Renderer.Image(out, link, title, alt)
|
||||
out.WriteString("</a>")
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -115,7 +137,43 @@ var (
|
||||
sha1CurrentPattern = regexp.MustCompile(`\b[0-9a-f]{40}\b`)
|
||||
)
|
||||
|
||||
func RenderSpecialLink(rawBytes []byte, urlPrefix string) []byte {
|
||||
func cutoutVerbosePrefix(prefix string) string {
|
||||
count := 0
|
||||
for i := 0; i < len(prefix); i++ {
|
||||
if prefix[i] == '/' {
|
||||
count++
|
||||
}
|
||||
if count >= 3 {
|
||||
return prefix[:i]
|
||||
}
|
||||
}
|
||||
return prefix
|
||||
}
|
||||
|
||||
func RenderIssueIndexPattern(rawBytes []byte, urlPrefix string, metas map[string]string) []byte {
|
||||
urlPrefix = cutoutVerbosePrefix(urlPrefix)
|
||||
ms := issueIndexPattern.FindAll(rawBytes, -1)
|
||||
for _, m := range ms {
|
||||
var space string
|
||||
m2 := m
|
||||
if m2[0] == ' ' {
|
||||
space = " "
|
||||
m2 = m2[1:]
|
||||
}
|
||||
if metas == nil {
|
||||
rawBytes = bytes.Replace(rawBytes, m, []byte(fmt.Sprintf(`%s<a href="%s/issues/%s">%s</a>`,
|
||||
space, urlPrefix, m2[1:], m2)), 1)
|
||||
} else {
|
||||
// Support for external issue tracker
|
||||
metas["index"] = string(m2[1:])
|
||||
rawBytes = bytes.Replace(rawBytes, m, []byte(fmt.Sprintf(`%s<a href="%s">%s</a>`,
|
||||
space, com.Expand(metas["format"], metas), m2)), 1)
|
||||
}
|
||||
}
|
||||
return rawBytes
|
||||
}
|
||||
|
||||
func RenderSpecialLink(rawBytes []byte, urlPrefix string, metas map[string]string) []byte {
|
||||
ms := MentionPattern.FindAll(rawBytes, -1)
|
||||
for _, m := range ms {
|
||||
m = bytes.TrimSpace(m)
|
||||
@@ -123,29 +181,7 @@ func RenderSpecialLink(rawBytes []byte, urlPrefix string) []byte {
|
||||
[]byte(fmt.Sprintf(`<a href="%s/%s">%s</a>`, setting.AppSubUrl, m[1:], m)), -1)
|
||||
}
|
||||
|
||||
ms = commitPattern.FindAll(rawBytes, -1)
|
||||
for _, m := range ms {
|
||||
m = bytes.TrimSpace(m)
|
||||
i := strings.Index(string(m), "commit/")
|
||||
j := strings.Index(string(m), "#")
|
||||
if j == -1 {
|
||||
j = len(m)
|
||||
}
|
||||
rawBytes = bytes.Replace(rawBytes, m, []byte(fmt.Sprintf(
|
||||
` <code><a href="%s">%s</a></code>`, m, ShortSha(string(m[i+7:j])))), -1)
|
||||
}
|
||||
ms = issueFullPattern.FindAll(rawBytes, -1)
|
||||
for _, m := range ms {
|
||||
m = bytes.TrimSpace(m)
|
||||
i := strings.Index(string(m), "issues/")
|
||||
j := strings.Index(string(m), "#")
|
||||
if j == -1 {
|
||||
j = len(m)
|
||||
}
|
||||
rawBytes = bytes.Replace(rawBytes, m, []byte(fmt.Sprintf(
|
||||
` <a href="%s">#%s</a>`, m, ShortSha(string(m[i+7:j])))), -1)
|
||||
}
|
||||
rawBytes = RenderIssueIndexPattern(rawBytes, urlPrefix)
|
||||
rawBytes = RenderIssueIndexPattern(rawBytes, urlPrefix, metas)
|
||||
rawBytes = RenderSha1CurrentPattern(rawBytes, urlPrefix)
|
||||
return rawBytes
|
||||
}
|
||||
@@ -159,33 +195,10 @@ func RenderSha1CurrentPattern(rawBytes []byte, urlPrefix string) []byte {
|
||||
return rawBytes
|
||||
}
|
||||
|
||||
func RenderIssueIndexPattern(rawBytes []byte, urlPrefix string) []byte {
|
||||
ms := issueIndexPattern.FindAll(rawBytes, -1)
|
||||
for _, m := range ms {
|
||||
var space string
|
||||
m2 := m
|
||||
if m2[0] == ' ' {
|
||||
space = " "
|
||||
m2 = m2[1:]
|
||||
}
|
||||
rawBytes = bytes.Replace(rawBytes, m, []byte(fmt.Sprintf(`%s<a href="%s/issues/%s">%s</a>`,
|
||||
space, urlPrefix, m2[1:], m2)), 1)
|
||||
}
|
||||
return rawBytes
|
||||
}
|
||||
|
||||
func RenderRawMarkdown(body []byte, urlPrefix string) []byte {
|
||||
htmlFlags := 0
|
||||
// htmlFlags |= blackfriday.HTML_USE_XHTML
|
||||
// htmlFlags |= blackfriday.HTML_USE_SMARTYPANTS
|
||||
// htmlFlags |= blackfriday.HTML_SMARTYPANTS_FRACTIONS
|
||||
// htmlFlags |= blackfriday.HTML_SMARTYPANTS_LATEX_DASHES
|
||||
// htmlFlags |= blackfriday.HTML_SKIP_HTML
|
||||
htmlFlags |= blackfriday.HTML_SKIP_STYLE
|
||||
// htmlFlags |= blackfriday.HTML_SKIP_SCRIPT
|
||||
// htmlFlags |= blackfriday.HTML_GITHUB_BLOCKCODE
|
||||
htmlFlags |= blackfriday.HTML_OMIT_CONTENTS
|
||||
// htmlFlags |= blackfriday.HTML_COMPLETE_PAGE
|
||||
renderer := &CustomRender{
|
||||
Renderer: blackfriday.HtmlRenderer(htmlFlags, "", ""),
|
||||
urlPrefix: urlPrefix,
|
||||
@@ -209,33 +222,96 @@ func RenderRawMarkdown(body []byte, urlPrefix string) []byte {
|
||||
return body
|
||||
}
|
||||
|
||||
var (
|
||||
leftAngleBracket = []byte("</")
|
||||
rightAngleBracket = []byte(">")
|
||||
)
|
||||
|
||||
var noEndTags = []string{"img", "input", "br", "hr"}
|
||||
|
||||
// PreProcessMarkdown renders full links of commits, issues and pulls to shorter version.
|
||||
func PreProcessMarkdown(rawHTML []byte, urlPrefix string) []byte {
|
||||
ms := commitPattern.FindAll(rawHTML, -1)
|
||||
for _, m := range ms {
|
||||
m = bytes.TrimSpace(m)
|
||||
i := strings.Index(string(m), "commit/")
|
||||
j := strings.Index(string(m), "#")
|
||||
if j == -1 {
|
||||
j = len(m)
|
||||
}
|
||||
rawHTML = bytes.Replace(rawHTML, m, []byte(fmt.Sprintf(
|
||||
` <code><a href="%s">%s</a></code>`, m, ShortSha(string(m[i+7:j])))), -1)
|
||||
}
|
||||
ms = issueFullPattern.FindAll(rawHTML, -1)
|
||||
for _, m := range ms {
|
||||
m = bytes.TrimSpace(m)
|
||||
i := strings.Index(string(m), "issues/")
|
||||
j := strings.Index(string(m), "#")
|
||||
if j == -1 {
|
||||
j = len(m)
|
||||
}
|
||||
rawHTML = bytes.Replace(rawHTML, m, []byte(fmt.Sprintf(
|
||||
` <a href="%s">#%s</a>`, m, ShortSha(string(m[i+7:j])))), -1)
|
||||
}
|
||||
return rawHTML
|
||||
}
|
||||
|
||||
// PostProcessMarkdown treats different types of HTML differently,
|
||||
// and only renders special links for plain text blocks.
|
||||
func PostProcessMarkdown(rawHtml []byte, urlPrefix string) []byte {
|
||||
func PostProcessMarkdown(rawHtml []byte, urlPrefix string, metas map[string]string) []byte {
|
||||
startTags := make([]string, 0, 5)
|
||||
var buf bytes.Buffer
|
||||
tokenizer := html.NewTokenizer(bytes.NewReader(rawHtml))
|
||||
|
||||
OUTER_LOOP:
|
||||
for html.ErrorToken != tokenizer.Next() {
|
||||
token := tokenizer.Token()
|
||||
switch token.Type {
|
||||
case html.TextToken:
|
||||
buf.Write(RenderSpecialLink([]byte(token.String()), urlPrefix))
|
||||
buf.Write(RenderSpecialLink([]byte(token.String()), urlPrefix, metas))
|
||||
|
||||
case html.StartTagToken:
|
||||
buf.WriteString(token.String())
|
||||
tagName := token.Data
|
||||
// If this is an excluded tag, we skip processing all output until a close tag is encountered.
|
||||
if strings.EqualFold("a", tagName) || strings.EqualFold("code", tagName) || strings.EqualFold("pre", tagName) {
|
||||
stackNum := 1
|
||||
for html.ErrorToken != tokenizer.Next() {
|
||||
token = tokenizer.Token()
|
||||
|
||||
// Copy the token to the output verbatim
|
||||
buf.WriteString(token.String())
|
||||
// If this is the close tag, we are done
|
||||
if html.EndTagToken == token.Type && strings.EqualFold(tagName, token.Data) {
|
||||
break
|
||||
|
||||
if token.Type == html.StartTagToken {
|
||||
stackNum++
|
||||
}
|
||||
|
||||
// If this is the close tag to the outer-most, we are done
|
||||
if token.Type == html.EndTagToken && strings.EqualFold(tagName, token.Data) {
|
||||
stackNum--
|
||||
|
||||
if stackNum == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
continue OUTER_LOOP
|
||||
}
|
||||
|
||||
if !com.IsSliceContainsStr(noEndTags, token.Data) {
|
||||
startTags = append(startTags, token.Data)
|
||||
}
|
||||
|
||||
case html.EndTagToken:
|
||||
if len(startTags) == 0 {
|
||||
buf.WriteString(token.String())
|
||||
break
|
||||
}
|
||||
|
||||
buf.Write(leftAngleBracket)
|
||||
buf.WriteString(startTags[len(startTags)-1])
|
||||
buf.Write(rightAngleBracket)
|
||||
startTags = startTags[:len(startTags)-1]
|
||||
default:
|
||||
buf.WriteString(token.String())
|
||||
}
|
||||
@@ -250,13 +326,14 @@ func PostProcessMarkdown(rawHtml []byte, urlPrefix string) []byte {
|
||||
return rawHtml
|
||||
}
|
||||
|
||||
func RenderMarkdown(rawBytes []byte, urlPrefix string) []byte {
|
||||
result := RenderRawMarkdown(rawBytes, urlPrefix)
|
||||
result = PostProcessMarkdown(result, urlPrefix)
|
||||
func RenderMarkdown(rawBytes []byte, urlPrefix string, metas map[string]string) []byte {
|
||||
result := PreProcessMarkdown(rawBytes, urlPrefix)
|
||||
result = RenderRawMarkdown(result, urlPrefix)
|
||||
result = PostProcessMarkdown(result, urlPrefix, metas)
|
||||
result = Sanitizer.SanitizeBytes(result)
|
||||
return result
|
||||
}
|
||||
|
||||
func RenderMarkdownString(raw, urlPrefix string) string {
|
||||
return string(RenderMarkdown([]byte(raw), urlPrefix))
|
||||
func RenderMarkdownString(raw, urlPrefix string, metas map[string]string) string {
|
||||
return string(RenderMarkdown([]byte(raw), urlPrefix, metas))
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
"hash"
|
||||
"html/template"
|
||||
"math"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -22,14 +23,16 @@ import (
|
||||
"github.com/Unknwon/i18n"
|
||||
"github.com/microcosm-cc/bluemonday"
|
||||
|
||||
"github.com/gogits/chardet"
|
||||
|
||||
"github.com/gogits/gogs/modules/avatar"
|
||||
"github.com/gogits/gogs/modules/setting"
|
||||
)
|
||||
|
||||
var Sanitizer = bluemonday.UGCPolicy()
|
||||
var Sanitizer = bluemonday.UGCPolicy().AllowAttrs("class").Matching(regexp.MustCompile(`[\p{L}\p{N}\s\-_',:\[\]!\./\\\(\)&]*`)).OnElements("code")
|
||||
|
||||
// Encode string to md5 hex value.
|
||||
func EncodeMd5(str string) string {
|
||||
// EncodeMD5 encodes string to md5 hex value.
|
||||
func EncodeMD5(str string) string {
|
||||
m := md5.New()
|
||||
m.Write([]byte(str))
|
||||
return hex.EncodeToString(m.Sum(nil))
|
||||
@@ -42,6 +45,22 @@ func EncodeSha1(str string) string {
|
||||
return hex.EncodeToString(h.Sum(nil))
|
||||
}
|
||||
|
||||
func ShortSha(sha1 string) string {
|
||||
if len(sha1) == 40 {
|
||||
return sha1[:10]
|
||||
}
|
||||
return sha1
|
||||
}
|
||||
|
||||
func DetectEncoding(content []byte) (string, error) {
|
||||
detector := chardet.NewTextDetector()
|
||||
result, err := detector.DetectBest(content)
|
||||
if result.Charset != "UTF-8" && len(setting.Repository.AnsiCharset) > 0 {
|
||||
return setting.Repository.AnsiCharset, err
|
||||
}
|
||||
return result.Charset, err
|
||||
}
|
||||
|
||||
func BasicAuthDecode(encoded string) (string, string, error) {
|
||||
s, err := base64.StdEncoding.DecodeString(encoded)
|
||||
if err != nil {
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -111,7 +111,7 @@ func TestSpecSchedule(t *testing.T) {
|
||||
t.Error(err)
|
||||
}
|
||||
if !reflect.DeepEqual(actual, c.expected) {
|
||||
t.Errorf("%s => (expected) %b != %b (actual)", c.expr, c.expected, actual)
|
||||
t.Errorf("%s => (expected) %v != %v (actual)", c.expr, c.expected, actual)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ type Blob struct {
|
||||
}
|
||||
|
||||
func (b *Blob) Data() (io.Reader, error) {
|
||||
stdout, stderr, err := com.ExecCmdDirBytes(b.repo.Path, "git", "show", b.Id.String())
|
||||
stdout, stderr, err := com.ExecCmdDirBytes(b.repo.Path, "git", "show", b.ID.String())
|
||||
if err != nil {
|
||||
return nil, errors.New(string(stderr))
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ import (
|
||||
// Commit represents a git commit.
|
||||
type Commit struct {
|
||||
Tree
|
||||
Id sha1 // The id of this commit object
|
||||
ID sha1 // The id of this commit object
|
||||
Author *Signature
|
||||
Committer *Signature
|
||||
CommitMessage string
|
||||
@@ -35,7 +35,7 @@ func (c *Commit) Summary() string {
|
||||
// Return oid of the parent number n (0-based index). Return nil if no such parent exists.
|
||||
func (c *Commit) ParentId(n int) (id sha1, err error) {
|
||||
if n >= len(c.parents) {
|
||||
err = IdNotExist
|
||||
err = IDNotExist
|
||||
return
|
||||
}
|
||||
return c.parents[n], nil
|
||||
@@ -61,7 +61,7 @@ func (c *Commit) ParentCount() int {
|
||||
}
|
||||
|
||||
func (c *Commit) CommitsBefore() (*list.List, error) {
|
||||
return c.repo.getCommitsBefore(c.Id)
|
||||
return c.repo.getCommitsBefore(c.ID)
|
||||
}
|
||||
|
||||
func (c *Commit) CommitsBeforeUntil(commitId string) (*list.List, error) {
|
||||
@@ -73,19 +73,19 @@ func (c *Commit) CommitsBeforeUntil(commitId string) (*list.List, error) {
|
||||
}
|
||||
|
||||
func (c *Commit) CommitsCount() (int, error) {
|
||||
return c.repo.commitsCount(c.Id)
|
||||
return c.repo.commitsCount(c.ID)
|
||||
}
|
||||
|
||||
func (c *Commit) SearchCommits(keyword string) (*list.List, error) {
|
||||
return c.repo.searchCommits(c.Id, keyword)
|
||||
return c.repo.searchCommits(c.ID, keyword)
|
||||
}
|
||||
|
||||
func (c *Commit) CommitsByRange(page int) (*list.List, error) {
|
||||
return c.repo.commitsByRange(c.Id, page)
|
||||
return c.repo.commitsByRange(c.ID, page)
|
||||
}
|
||||
|
||||
func (c *Commit) GetCommitOfRelPath(relPath string) (*Commit, error) {
|
||||
return c.repo.getCommitOfRelPath(c.Id, relPath)
|
||||
return c.repo.getCommitOfRelPath(c.ID, relPath)
|
||||
}
|
||||
|
||||
func (c *Commit) GetSubModule(entryname string) (*SubModule, error) {
|
||||
|
||||
@@ -28,7 +28,7 @@ func (c *Commit) CreateArchive(path string, archiveType ArchiveType) error {
|
||||
return fmt.Errorf("unknown format: %v", archiveType)
|
||||
}
|
||||
|
||||
_, stderr, err := com.ExecCmdDir(c.repo.Path, "git", "archive", "--format="+format, "-o", path, c.Id.String())
|
||||
_, stderr, err := com.ExecCmdDir(c.repo.Path, "git", "archive", "--format="+format, "-o", path, c.ID.String())
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s", stderr)
|
||||
}
|
||||
|
||||
22
modules/git/error.go
Normal file
22
modules/git/error.go
Normal file
@@ -0,0 +1,22 @@
|
||||
// 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 git
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type ErrUnsupportedVersion struct {
|
||||
Required string
|
||||
}
|
||||
|
||||
func IsErrUnsupportedVersion(err error) bool {
|
||||
_, ok := err.(ErrUnsupportedVersion)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrUnsupportedVersion) Error() string {
|
||||
return fmt.Sprintf("Operation requires higher version [required: %s]", err.Required)
|
||||
}
|
||||
@@ -16,15 +16,23 @@ import (
|
||||
|
||||
// hookNames is a list of Git hooks' name that are supported.
|
||||
var hookNames = []string{
|
||||
"pre-applypatch",
|
||||
"applypatch-msg",
|
||||
"pre-applypatch",
|
||||
"post-applypatch",
|
||||
"pre-commit",
|
||||
"prepare-commit-msg",
|
||||
"commit-msg",
|
||||
"pre-commit",
|
||||
"pre-rebase",
|
||||
"post-commit",
|
||||
"pre-rebase",
|
||||
"post-checkout",
|
||||
"post-merge",
|
||||
"pre-push",
|
||||
// "update",
|
||||
"post-receive",
|
||||
"post-update",
|
||||
"push-to-checkout",
|
||||
"pre-auto-gc",
|
||||
"post-rewrite",
|
||||
}
|
||||
|
||||
var (
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
package git
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
@@ -21,6 +22,8 @@ func OpenRepository(repoPath string) (*Repository, error) {
|
||||
repoPath, err := filepath.Abs(repoPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !isDir(repoPath) {
|
||||
return nil, errors.New("no such file or directory")
|
||||
}
|
||||
|
||||
return &Repository{Path: repoPath}, nil
|
||||
|
||||
@@ -35,3 +35,16 @@ func (repo *Repository) GetBranches() ([]string, error) {
|
||||
}
|
||||
return branches, nil
|
||||
}
|
||||
|
||||
// SetDefaultBranch sets default branch of repository.
|
||||
func (repo *Repository) SetDefaultBranch(branchName string) error {
|
||||
if gitVer.LessThan(MustParseVersion("1.7.10")) {
|
||||
return ErrUnsupportedVersion{"1.7.10"}
|
||||
}
|
||||
|
||||
_, stderr, err := com.ExecCmdDir(repo.Path, "git", "symbolic-ref", "HEAD", "refs/heads/"+branchName)
|
||||
if err != nil {
|
||||
return concatenateError(err, stderr)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@ l:
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
commit.Tree.Id = id
|
||||
commit.Tree.ID = id
|
||||
case "parent":
|
||||
// A commit can have one or more parents
|
||||
oid, err := NewIdFromString(string(line[spacepos+1:]))
|
||||
@@ -111,9 +111,9 @@ func (repo *Repository) getCommit(id sha1) (*Commit, error) {
|
||||
repo.commitCache = make(map[sha1]*Commit, 10)
|
||||
}
|
||||
|
||||
data, bytErr, err := com.ExecCmdDirBytes(repo.Path, "git", "cat-file", "-p", id.String())
|
||||
data, stderr, err := com.ExecCmdDirBytes(repo.Path, "git", "cat-file", "-p", id.String())
|
||||
if err != nil {
|
||||
return nil, errors.New(err.Error() + ": " + string(bytErr))
|
||||
return nil, concatenateError(err, string(stderr))
|
||||
}
|
||||
|
||||
commit, err := parseCommitData(data)
|
||||
@@ -121,7 +121,7 @@ func (repo *Repository) getCommit(id sha1) (*Commit, error) {
|
||||
return nil, err
|
||||
}
|
||||
commit.repo = repo
|
||||
commit.Id = id
|
||||
commit.ID = id
|
||||
|
||||
repo.commitCache[id] = commit
|
||||
return commit, nil
|
||||
@@ -211,7 +211,7 @@ func (repo *Repository) CommitsBetween(last *Commit, before *Commit) (*list.List
|
||||
var err error
|
||||
cur := last
|
||||
for {
|
||||
if cur.Id.Equal(before.Id) {
|
||||
if cur.ID.Equal(before.ID) {
|
||||
break
|
||||
}
|
||||
l.PushBack(cur)
|
||||
@@ -229,7 +229,7 @@ func (repo *Repository) CommitsBetween(last *Commit, before *Commit) (*list.List
|
||||
func (repo *Repository) commitsBefore(lock *sync.Mutex, l *list.List, parent *list.Element, id sha1, limit int) error {
|
||||
commit, err := repo.getCommit(id)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("getCommit: %v", err)
|
||||
}
|
||||
|
||||
var e *list.Element
|
||||
@@ -240,7 +240,7 @@ func (repo *Repository) commitsBefore(lock *sync.Mutex, l *list.List, parent *li
|
||||
for {
|
||||
if in == nil {
|
||||
break
|
||||
} else if in.Value.(*Commit).Id.Equal(commit.Id) {
|
||||
} else if in.Value.(*Commit).ID.Equal(commit.ID) {
|
||||
return nil
|
||||
} else {
|
||||
if in.Next() == nil {
|
||||
@@ -301,8 +301,7 @@ func (repo *Repository) CommitsByFileAndRange(branch, file string, page int) (*l
|
||||
func (repo *Repository) getCommitsBefore(id sha1) (*list.List, error) {
|
||||
l := list.New()
|
||||
lock := new(sync.Mutex)
|
||||
err := repo.commitsBefore(lock, l, nil, id, 0)
|
||||
return l, err
|
||||
return l, repo.commitsBefore(lock, l, nil, id, 0)
|
||||
}
|
||||
|
||||
func (repo *Repository) searchCommits(id sha1, keyword string) (*list.List, error) {
|
||||
|
||||
@@ -20,31 +20,55 @@ type PullRequestInfo struct {
|
||||
NumFiles int
|
||||
}
|
||||
|
||||
// GetMergeBase checks and returns merge base of two branches.
|
||||
func (repo *Repository) GetMergeBase(remoteBranch, headBranch string) (string, error) {
|
||||
// Get merge base commit.
|
||||
stdout, stderr, err := com.ExecCmdDir(repo.Path, "git", "merge-base", remoteBranch, headBranch)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("get merge base: %v", concatenateError(err, stderr))
|
||||
}
|
||||
return strings.TrimSpace(stdout), nil
|
||||
}
|
||||
|
||||
// AddRemote adds a remote to repository.
|
||||
func (repo *Repository) AddRemote(name, path string) error {
|
||||
_, stderr, err := com.ExecCmdDir(repo.Path, "git", "remote", "add", "-f", name, path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("add remote(%s - %s): %v", name, path, concatenateError(err, stderr))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemoveRemote removes a remote from repository.
|
||||
func (repo *Repository) RemoveRemote(name string) error {
|
||||
_, stderr, err := com.ExecCmdDir(repo.Path, "git", "remote", "remove", name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("remove remote(%s): %v", name, concatenateError(err, stderr))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetPullRequestInfo generates and returns pull request information
|
||||
// between base and head branches of repositories.
|
||||
func (repo *Repository) GetPullRequestInfo(basePath, baseBranch, headBranch string) (*PullRequestInfo, error) {
|
||||
func (repo *Repository) GetPullRequestInfo(basePath, baseBranch, headBranch string) (_ *PullRequestInfo, err error) {
|
||||
// Add a temporary remote.
|
||||
tmpRemote := com.ToStr(time.Now().UnixNano())
|
||||
_, stderr, err := com.ExecCmdDir(repo.Path, "git", "remote", "add", "-f", tmpRemote, basePath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("add base as remote: %v", concatenateError(err, stderr))
|
||||
if err = repo.AddRemote(tmpRemote, basePath); err != nil {
|
||||
return nil, fmt.Errorf("AddRemote: %v", err)
|
||||
}
|
||||
defer func() {
|
||||
com.ExecCmdDir(repo.Path, "git", "remote", "remove", tmpRemote)
|
||||
repo.RemoveRemote(tmpRemote)
|
||||
}()
|
||||
|
||||
prInfo := new(PullRequestInfo)
|
||||
|
||||
var stdout string
|
||||
remoteBranch := "remotes/" + tmpRemote + "/" + baseBranch
|
||||
// Get merge base commit.
|
||||
stdout, stderr, err = com.ExecCmdDir(repo.Path, "git", "merge-base", remoteBranch, headBranch)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get merge base: %v", concatenateError(err, stderr))
|
||||
}
|
||||
prInfo.MergeBase = strings.TrimSpace(stdout)
|
||||
|
||||
stdout, stderr, err = com.ExecCmdDir(repo.Path, "git", "log", remoteBranch+"..."+headBranch, prettyLogFormat)
|
||||
prInfo := new(PullRequestInfo)
|
||||
prInfo.MergeBase, err = repo.GetMergeBase(remoteBranch, headBranch)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("GetMergeBase: %v", err)
|
||||
}
|
||||
|
||||
stdout, stderr, err := com.ExecCmdDir(repo.Path, "git", "log", prInfo.MergeBase+"..."+headBranch, prettyLogFormat)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("list diff logs: %v", concatenateError(err, stderr))
|
||||
}
|
||||
@@ -64,20 +88,8 @@ func (repo *Repository) GetPullRequestInfo(basePath, baseBranch, headBranch stri
|
||||
}
|
||||
|
||||
// GetPatch generates and returns patch data between given branches.
|
||||
func (repo *Repository) GetPatch(basePath, baseBranch, headBranch string) ([]byte, error) {
|
||||
// Add a temporary remote.
|
||||
tmpRemote := com.ToStr(time.Now().UnixNano())
|
||||
_, stderr, err := com.ExecCmdDirBytes(repo.Path, "git", "remote", "add", "-f", tmpRemote, basePath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("add base as remote: %v", concatenateError(err, string(stderr)))
|
||||
}
|
||||
defer func() {
|
||||
com.ExecCmdDir(repo.Path, "git", "remote", "remove", tmpRemote)
|
||||
}()
|
||||
|
||||
var stdout []byte
|
||||
remoteBranch := "remotes/" + tmpRemote + "/" + baseBranch
|
||||
stdout, stderr, err = com.ExecCmdDirBytes(repo.Path, "git", "diff", "-p", remoteBranch, headBranch)
|
||||
func (repo *Repository) GetPatch(mergeBase, headBranch string) ([]byte, error) {
|
||||
stdout, stderr, err := com.ExecCmdDirBytes(repo.Path, "git", "diff", "-p", "--binary", mergeBase, headBranch)
|
||||
if err != nil {
|
||||
return nil, concatenateError(err, string(stderr))
|
||||
}
|
||||
|
||||
@@ -69,7 +69,7 @@ func (repo *Repository) getTag(id sha1) (*Tag, error) {
|
||||
// Tag is a commit.
|
||||
if ObjectType(tp) == COMMIT {
|
||||
tag := &Tag{
|
||||
Id: id,
|
||||
ID: id,
|
||||
Object: id,
|
||||
Type: string(COMMIT),
|
||||
repo: repo,
|
||||
@@ -89,7 +89,7 @@ func (repo *Repository) getTag(id sha1) (*Tag, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tag.Id = id
|
||||
tag.ID = id
|
||||
tag.repo = repo
|
||||
|
||||
repo.tagCache[id] = tag
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user