mirror of
https://github.com/gogs/gogs.git
synced 2026-02-28 09:10:57 +01:00
Compare commits
512 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a0318db2f9 | ||
|
|
c6058e5ea8 | ||
|
|
4f2f3c2857 | ||
|
|
925ccc2608 | ||
|
|
11f9d738e8 | ||
|
|
63baf76ab2 | ||
|
|
9c3aa6936a | ||
|
|
32c5fa514c | ||
|
|
eaa7d71fc7 | ||
|
|
e1332c5239 | ||
|
|
afc6cbc479 | ||
|
|
1f059502dc | ||
|
|
01a516b584 | ||
|
|
48c98c92c8 | ||
|
|
6fe43eb8d4 | ||
|
|
2657f88d9a | ||
|
|
ddb7f55035 | ||
|
|
fbf274b751 | ||
|
|
422043f422 | ||
|
|
617bbe3fee | ||
|
|
9085dfa426 | ||
|
|
6696610aea | ||
|
|
e323604d78 | ||
|
|
4ee6bc4fca | ||
|
|
ab13a29cb5 | ||
|
|
ff690fd976 | ||
|
|
bcfa78b8b5 | ||
|
|
6227b59b1a | ||
|
|
e0a6e6dd1a | ||
|
|
bf5fcfb49c | ||
|
|
ff48aeddef | ||
|
|
d72fdc9900 | ||
|
|
5543a0b6dc | ||
|
|
2f820e01d8 | ||
|
|
688ec6ecbd | ||
|
|
87854c95a9 | ||
|
|
80055bde86 | ||
|
|
e33a104448 | ||
|
|
dce17c86ac | ||
|
|
b1bdbd7f94 | ||
|
|
50ba08e2c6 | ||
|
|
5e9a45f74a | ||
|
|
1331134316 | ||
|
|
4e8a1bf9c9 | ||
|
|
c82807a713 | ||
|
|
342baf1dda | ||
|
|
fdc6b64f15 | ||
|
|
02fb088b80 | ||
|
|
5660570d92 | ||
|
|
93f8f92523 | ||
|
|
a4c3ab48a5 | ||
|
|
7fd22bea1e | ||
|
|
04890904f9 | ||
|
|
a0a95d797e | ||
|
|
956f011dd3 | ||
|
|
16e162b669 | ||
|
|
61641d9c63 | ||
|
|
44f1ef2493 | ||
|
|
be82716b66 | ||
|
|
57ad8d50b7 | ||
|
|
f6c94c29d5 | ||
|
|
4744996f9a | ||
|
|
db6b71ad03 | ||
|
|
f4486f3eec | ||
|
|
4f042d12bd | ||
|
|
7869cfccb9 | ||
|
|
eb264a112b | ||
|
|
839a9bb054 | ||
|
|
9d5e827a1e | ||
|
|
b70db61854 | ||
|
|
16bddd593a | ||
|
|
e880a2fa48 | ||
|
|
46fc36c3a6 | ||
|
|
a1bb3741d5 | ||
|
|
99f2400e3b | ||
|
|
4a4392192b | ||
|
|
e8dd480f10 | ||
|
|
9100786beb | ||
|
|
b0084b1adc | ||
|
|
5ed5aa5228 | ||
|
|
33ec0632ff | ||
|
|
f276b37bbb | ||
|
|
cca2a53d6f | ||
|
|
5d5c4535cb | ||
|
|
2eee1e9bc2 | ||
|
|
98dbbae2ef | ||
|
|
c117f9e73f | ||
|
|
f979d0d6b9 | ||
|
|
a913aff1d0 | ||
|
|
54e95fa367 | ||
|
|
f1130ce5e9 | ||
|
|
f5b2e5f836 | ||
|
|
1769bb2f26 | ||
|
|
83a10ce880 | ||
|
|
7ec3f1b2d8 | ||
|
|
3fe87cba85 | ||
|
|
ca0b6dfa38 | ||
|
|
1482bd8fb4 | ||
|
|
0970d6cc38 | ||
|
|
98eeec4cbb | ||
|
|
68fb62347f | ||
|
|
a196245a87 | ||
|
|
605a38759f | ||
|
|
fd4b123e88 | ||
|
|
d122aa6d88 | ||
|
|
2c73ced0db | ||
|
|
fd7b0a2ba4 | ||
|
|
27f9b7a144 | ||
|
|
f68e279150 | ||
|
|
70398ed01a | ||
|
|
d2231bb54c | ||
|
|
65e628d1f4 | ||
|
|
c5dbc24ca4 | ||
|
|
bf58679390 | ||
|
|
cdc87623dc | ||
|
|
b33f255c40 | ||
|
|
26c0113ea0 | ||
|
|
55019bfbc5 | ||
|
|
4d6de6c7b9 | ||
|
|
7d84cc96e8 | ||
|
|
2eabeba6b7 | ||
|
|
045c21de4f | ||
|
|
4ef9494637 | ||
|
|
43ffacd05b | ||
|
|
501f70e248 | ||
|
|
09dba7d63e | ||
|
|
3f4c040f3f | ||
|
|
830494a8aa | ||
|
|
25713ab209 | ||
|
|
914ffa496f | ||
|
|
a742ee543e | ||
|
|
e867283406 | ||
|
|
a03f380fa8 | ||
|
|
8eb15815f1 | ||
|
|
11ca10ab2f | ||
|
|
23a857d107 | ||
|
|
495d939ca5 | ||
|
|
7d89e765ab | ||
|
|
31fd45ba02 | ||
|
|
abd8c2a7ca | ||
|
|
e05b1385fb | ||
|
|
8b6766ecbe | ||
|
|
269281ab76 | ||
|
|
4b08d3aacf | ||
|
|
33d32585b1 | ||
|
|
6fb7229bea | ||
|
|
7407f9caf3 | ||
|
|
d76772adb9 | ||
|
|
8ca14e2109 | ||
|
|
7cb5a15c9b | ||
|
|
e573855a4f | ||
|
|
94bccbb148 | ||
|
|
24f614f6db | ||
|
|
cb505b22ec | ||
|
|
6e3dba2cc5 | ||
|
|
bbdfe25769 | ||
|
|
c1eb4d894a | ||
|
|
5f653898f3 | ||
|
|
d189b83ac1 | ||
|
|
d34c3bb751 | ||
|
|
3b7465f817 | ||
|
|
d8136c9c3c | ||
|
|
1652dd5068 | ||
|
|
02687cbdf3 | ||
|
|
07c3d497a7 | ||
|
|
816c0ed5e7 | ||
|
|
a641854cad | ||
|
|
bb0bc0a240 | ||
|
|
7b60756f2c | ||
|
|
163fcec59f | ||
|
|
79ea34e70e | ||
|
|
e10096ee2e | ||
|
|
a9e6d49dc6 | ||
|
|
3e856928a2 | ||
|
|
8bbaf9550a | ||
|
|
e7d8fadb08 | ||
|
|
3bd5fc6d6f | ||
|
|
cd2020429a | ||
|
|
ff9872104e | ||
|
|
a3f807106a | ||
|
|
100cd181bc | ||
|
|
03c2468c2f | ||
|
|
fa17989763 | ||
|
|
75109bbd65 | ||
|
|
6ce8fa49ea | ||
|
|
0a187dbef5 | ||
|
|
f6c4fbeb37 | ||
|
|
ad71775ae8 | ||
|
|
0dfb5560cd | ||
|
|
1c88a541cb | ||
|
|
a7584d16e4 | ||
|
|
480a4ae8c5 | ||
|
|
cc1eb5643e | ||
|
|
a2333d95d5 | ||
|
|
89c99167b2 | ||
|
|
d0ea4c7b68 | ||
|
|
2a1dc0085b | ||
|
|
46af92c57e | ||
|
|
52fbb9788a | ||
|
|
00ebb5019f | ||
|
|
dc5546633f | ||
|
|
eb463000a9 | ||
|
|
49dc57e336 | ||
|
|
de46c06d2e | ||
|
|
b36448a537 | ||
|
|
48bfbb7ddf | ||
|
|
1e9f376d3d | ||
|
|
a85f242030 | ||
|
|
cdc843f06b | ||
|
|
0d6856dbe7 | ||
|
|
494e5fd40c | ||
|
|
2401e68d7e | ||
|
|
31805e2bbe | ||
|
|
6700257558 | ||
|
|
41b0a7b97c | ||
|
|
62240b6bc1 | ||
|
|
ce05a8d7b6 | ||
|
|
62d23e9154 | ||
|
|
9fdf4bc277 | ||
|
|
8a8f84d245 | ||
|
|
59d0e73c35 | ||
|
|
1badb2bbcc | ||
|
|
89b68bdd45 | ||
|
|
597387ad40 | ||
|
|
cfa0968191 | ||
|
|
9dfb7de371 | ||
|
|
1b734501bd | ||
|
|
861a20f464 | ||
|
|
8bab21d795 | ||
|
|
0da329462e | ||
|
|
3684e70dc4 | ||
|
|
83578cff65 | ||
|
|
3f2f648035 | ||
|
|
d0f887a1ed | ||
|
|
35a86d04c0 | ||
|
|
37cbfc032a | ||
|
|
5898d56205 | ||
|
|
cf7901fe6a | ||
|
|
e5af34a078 | ||
|
|
51550e5b2e | ||
|
|
eb4691cb2f | ||
|
|
912481019f | ||
|
|
184f1ae135 | ||
|
|
efc05ea1de | ||
|
|
521c5f0e10 | ||
|
|
32ae6896fa | ||
|
|
4b58c01603 | ||
|
|
ff690840d4 | ||
|
|
0c5e50a888 | ||
|
|
7ded30ba5b | ||
|
|
683e58878a | ||
|
|
649d0e1681 | ||
|
|
d7956b3fb8 | ||
|
|
2a95bc1395 | ||
|
|
e554e49c16 | ||
|
|
9abb37b45e | ||
|
|
22f8536577 | ||
|
|
8952eb1ce0 | ||
|
|
ee7bfe2ebe | ||
|
|
b270b34c98 | ||
|
|
b01e967a9f | ||
|
|
baacba96ca | ||
|
|
8bc502a1ea | ||
|
|
e5aaf23bb2 | ||
|
|
a90a014033 | ||
|
|
88072a1e9b | ||
|
|
f0cdf30134 | ||
|
|
22bb5104c8 | ||
|
|
1560abe553 | ||
|
|
65ad26feba | ||
|
|
b60d5ecc3e | ||
|
|
8a6119551b | ||
|
|
4b8d72dec2 | ||
|
|
5435b259cc | ||
|
|
dbdaf934e1 | ||
|
|
d6ff275c58 | ||
|
|
b32407456a | ||
|
|
2a9da4b8e5 | ||
|
|
9a5e49cccb | ||
|
|
338fc122fa | ||
|
|
34d18a19a3 | ||
|
|
de01f81489 | ||
|
|
660b47373d | ||
|
|
11a28428de | ||
|
|
ea29a9b846 | ||
|
|
7b91dfeb01 | ||
|
|
006c45e21d | ||
|
|
e55d1ea3d6 | ||
|
|
d304a23787 | ||
|
|
5fdfc2e223 | ||
|
|
8bed017557 | ||
|
|
0e41726ece | ||
|
|
eda3f8b3b3 | ||
|
|
b32e223db5 | ||
|
|
bda8fdc5f4 | ||
|
|
aa58320a1b | ||
|
|
8719c1b8c5 | ||
|
|
642687d08d | ||
|
|
49c01c0b57 | ||
|
|
55af04d801 | ||
|
|
96f7af1d70 | ||
|
|
f8571f4db1 | ||
|
|
7d2632784f | ||
|
|
e44655f722 | ||
|
|
1bfe4ee665 | ||
|
|
2dc0329c5f | ||
|
|
6277f8497c | ||
|
|
7ad05ce210 | ||
|
|
066d91e113 | ||
|
|
bb84bb8ac3 | ||
|
|
9e047cdbbb | ||
|
|
213b366959 | ||
|
|
4c5a6e4d87 | ||
|
|
c3a52f7dd0 | ||
|
|
dfba49b062 | ||
|
|
67426534ef | ||
|
|
7d656ee2e3 | ||
|
|
83283dfe37 | ||
|
|
f456f964ec | ||
|
|
1f989d0b98 | ||
|
|
d3fc1da8c2 | ||
|
|
4fafc76052 | ||
|
|
8080beea85 | ||
|
|
1d3c7693b7 | ||
|
|
6cc914b090 | ||
|
|
5378bb326b | ||
|
|
df0c61cc62 | ||
|
|
b9a1d13c29 | ||
|
|
d9f2878db4 | ||
|
|
caeddb79a3 | ||
|
|
4210f56dbc | ||
|
|
6cb636f8a0 | ||
|
|
8549b47e04 | ||
|
|
337eef2ee5 | ||
|
|
516baa4531 | ||
|
|
a1ab3cad3e | ||
|
|
3c3cda7326 | ||
|
|
c3c2cfebaa | ||
|
|
c36e7d322e | ||
|
|
38c069308e | ||
|
|
8283e16ef7 | ||
|
|
57f84fb051 | ||
|
|
6cee65db5a | ||
|
|
f644cefa86 | ||
|
|
190b83e05e | ||
|
|
d2b53dd43b | ||
|
|
4b9b8024ba | ||
|
|
8c266f2df5 | ||
|
|
4c1452574a | ||
|
|
ea74be2f2e | ||
|
|
d26a333dfb | ||
|
|
9d983f27d6 | ||
|
|
5c2da610a2 | ||
|
|
33f2d33a46 | ||
|
|
c9a1eb4789 | ||
|
|
52b4ab2aa5 | ||
|
|
9ffa8a4083 | ||
|
|
90f6aa8cd1 | ||
|
|
23bba7633b | ||
|
|
d60f7b85e5 | ||
|
|
47f3dd43a5 | ||
|
|
25ecf56285 | ||
|
|
25fd495b2e | ||
|
|
75d2affcbf | ||
|
|
f92851e347 | ||
|
|
2ce0c3befe | ||
|
|
802a110e42 | ||
|
|
559a57330e | ||
|
|
fe4750ebe2 | ||
|
|
5f3a6a9d74 | ||
|
|
794b457252 | ||
|
|
790a2c1099 | ||
|
|
31d613c01d | ||
|
|
b057cffd85 | ||
|
|
d305448fa8 | ||
|
|
3df8b2bef0 | ||
|
|
2e6d50addc | ||
|
|
5d30bfc8ba | ||
|
|
4fc5dcc764 | ||
|
|
33aa4f7438 | ||
|
|
f8e97b75fb | ||
|
|
5c1312f38e | ||
|
|
d6dac160df | ||
|
|
47aa53bd36 | ||
|
|
7d07b58114 | ||
|
|
4c6e0e9499 | ||
|
|
dd815ae7b5 | ||
|
|
a5fcaae5ee | ||
|
|
b26df29865 | ||
|
|
23e318f85a | ||
|
|
11f54b1426 | ||
|
|
0650c3c9f6 | ||
|
|
555b6976a3 | ||
|
|
df000245d1 | ||
|
|
668007592a | ||
|
|
a4f070b828 | ||
|
|
b1a3ba9932 | ||
|
|
2315dc39b6 | ||
|
|
65c2c62982 | ||
|
|
f8e370f8ca | ||
|
|
ed546912e5 | ||
|
|
ee2d8d2469 | ||
|
|
8cfa2be433 | ||
|
|
db39e58a13 | ||
|
|
306aa5bffe | ||
|
|
8980675a9f | ||
|
|
45462662e9 | ||
|
|
459223cf01 | ||
|
|
8faa0dbcd7 | ||
|
|
29e255422e | ||
|
|
af552596cf | ||
|
|
a4cbe79567 | ||
|
|
f3ed11d177 | ||
|
|
fde5b16332 | ||
|
|
7811e58726 | ||
|
|
a24c0b92e4 | ||
|
|
88d873c67f | ||
|
|
94c7278194 | ||
|
|
16b6e5d50b | ||
|
|
6b30d9b0f2 | ||
|
|
a354f33ac2 | ||
|
|
5966dd78e6 | ||
|
|
b3f1ae1ba6 | ||
|
|
5d4025cb5a | ||
|
|
0f9e07de2d | ||
|
|
3487f17285 | ||
|
|
9f7bd5007b | ||
|
|
5f6bd323f5 | ||
|
|
a773cf1d87 | ||
|
|
8683d2f857 | ||
|
|
c72e1b5c2a | ||
|
|
721834a267 | ||
|
|
4efb56af03 | ||
|
|
c8b81d8339 | ||
|
|
a991ebf5d0 | ||
|
|
b506429803 | ||
|
|
115a349131 | ||
|
|
24d0ca4aa0 | ||
|
|
d4565483e6 | ||
|
|
2577940c30 | ||
|
|
22feddf804 | ||
|
|
7776f407b6 | ||
|
|
9ea9818d32 | ||
|
|
05fb34eacd | ||
|
|
a92d67fa01 | ||
|
|
125c87a405 | ||
|
|
8c9a0494ec | ||
|
|
e2fe220905 | ||
|
|
e7c8a3cb8d | ||
|
|
db1fe3483e | ||
|
|
6a16866f4e | ||
|
|
794cd27db3 | ||
|
|
cd5b800a21 | ||
|
|
2846ff7d31 | ||
|
|
583f11f27c | ||
|
|
7a4c6c22ce | ||
|
|
5e534bf2a5 | ||
|
|
a04918e36d | ||
|
|
5f4b5c1557 | ||
|
|
2e8d5c2eb3 | ||
|
|
d3a987eded | ||
|
|
1b0142513e | ||
|
|
98f918ed28 | ||
|
|
3ede496383 | ||
|
|
b7c3b0cc73 | ||
|
|
3ebc9b991a | ||
|
|
1336d8d54d | ||
|
|
c22d3503fd | ||
|
|
ce350a737a | ||
|
|
9791e70da6 | ||
|
|
a92826a8fe | ||
|
|
493b0c5ac2 | ||
|
|
e41ab839c7 | ||
|
|
ef6b978496 | ||
|
|
eb803ec5eb | ||
|
|
ca5dcea7d1 | ||
|
|
3a23476dbe | ||
|
|
4a4997a3c7 | ||
|
|
48d3a1fef1 | ||
|
|
75db79b4b6 | ||
|
|
fa34656b5d | ||
|
|
e0d64d3168 | ||
|
|
6b9f7fd758 | ||
|
|
e9c4156c87 | ||
|
|
79a610592e | ||
|
|
bbadbbdf68 | ||
|
|
bfdadaa13c | ||
|
|
b9a7dd72f8 | ||
|
|
4586c386e7 | ||
|
|
1757a59a99 | ||
|
|
db66b8da72 | ||
|
|
97b133bbee | ||
|
|
d9005ee970 | ||
|
|
adf42a5b54 | ||
|
|
93f6e98047 | ||
|
|
5180506678 | ||
|
|
b0e7dd6864 | ||
|
|
13824af291 | ||
|
|
272c27c8f2 | ||
|
|
a044c24c3d | ||
|
|
91e6db1bae | ||
|
|
67bd2daa02 | ||
|
|
9ef100bbf0 | ||
|
|
128b7c790e | ||
|
|
5a07b5430e | ||
|
|
868d921f7b | ||
|
|
f9ba8f01d1 | ||
|
|
5a01a0f668 | ||
|
|
1240041b4d | ||
|
|
10ba28deba | ||
|
|
a187ba9651 | ||
|
|
1a247340db |
12
.fswatch.json
Normal file
12
.fswatch.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"paths": ["."],
|
||||
"depth": 2,
|
||||
"exclude": [],
|
||||
"include": ["\\.go$", "\\.ini$"],
|
||||
"command": [
|
||||
"bash", "-c", "go build && ./gogs web"
|
||||
],
|
||||
"env": {
|
||||
"POWERED_BY": "github.com/shxsun/fswatch"
|
||||
}
|
||||
}
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -12,6 +12,7 @@ public/img/avatar/
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
dev
|
||||
|
||||
# Folders
|
||||
_obj
|
||||
@@ -33,3 +34,4 @@ _testmain.go
|
||||
*.exe~
|
||||
gogs
|
||||
__pycache__
|
||||
*.pem
|
||||
|
||||
@@ -2,9 +2,10 @@ filesets:
|
||||
includes:
|
||||
- templates
|
||||
- public
|
||||
- conf
|
||||
- LICENSE
|
||||
- README.md
|
||||
- README_ZH.md
|
||||
- start.bat
|
||||
- start.sh
|
||||
excludes:
|
||||
- \.git
|
||||
|
||||
40
.gopmfile
40
.gopmfile
@@ -1,25 +1,27 @@
|
||||
[target]
|
||||
path=github.com/gogits/gogs
|
||||
path = github.com/gogits/gogs
|
||||
|
||||
[deps]
|
||||
github.com/codegangsta/cli=
|
||||
github.com/go-martini/martini=
|
||||
github.com/Unknwon/com=
|
||||
github.com/Unknwon/cae=
|
||||
github.com/Unknwon/goconfig=
|
||||
github.com/dchest/scrypt=
|
||||
github.com/nfnt/resize=
|
||||
github.com/lunny/xorm=
|
||||
github.com/go-sql-driver/mysql=
|
||||
github.com/lib/pq=
|
||||
github.com/gogits/logs=
|
||||
github.com/gogits/binding=
|
||||
github.com/gogits/git=
|
||||
github.com/gogits/gfm=
|
||||
github.com/gogits/cache=
|
||||
github.com/gogits/session=
|
||||
github.com/gogits/webdav=
|
||||
github.com/Unknwon/cae = `commit:a1fa53b`
|
||||
github.com/Unknwon/com = `commit:019c36f`
|
||||
github.com/Unknwon/goconfig = `commit:c4e325f`
|
||||
github.com/codegangsta/cli = `commit:bb91895`
|
||||
github.com/go-martini/martini = `commit:49411a5`
|
||||
github.com/go-sql-driver/mysql = `commit:b44cac6`
|
||||
github.com/go-xorm/core = `commit:267e375`
|
||||
github.com/go-xorm/xorm = `commit:bd1487b`
|
||||
github.com/gogits/cache = `commit:f9bb61f`
|
||||
github.com/gogits/gfm = `commit:40f747a`
|
||||
github.com/gogits/git = `commit:3d9e771`
|
||||
github.com/gogits/logs = `commit:0a97a46`
|
||||
github.com/gogits/oauth2 = `commit:99cbec8`
|
||||
github.com/gogits/session = `commit:7ab78d4`
|
||||
github.com/juju2013/goldap = `commit:f4a7f67`
|
||||
github.com/lib/pq = `commit:529edd9`
|
||||
github.com/nfnt/resize = `commit:8aee0d9`
|
||||
github.com/qiniu/log = `commit:891d1cb`
|
||||
github.com/robfig/cron = `commit:b024fc5`
|
||||
|
||||
[res]
|
||||
include=templates|public|conf
|
||||
include = templates|public
|
||||
|
||||
|
||||
5
.travis.yml
Normal file
5
.travis.yml
Normal file
@@ -0,0 +1,5 @@
|
||||
language: go
|
||||
|
||||
go:
|
||||
- 1.2
|
||||
- tip
|
||||
@@ -2,14 +2,14 @@
|
||||
|
||||
> Thanks [drone](https://github.com/drone/drone) because this guidelines sheet is forked from its [CONTRIBUTING.md](https://github.com/drone/drone/blob/master/CONTRIBUTING.md).
|
||||
|
||||
**This document is pre^3 release, we're not ready for receiving contribution until v0.5.0 release.**
|
||||
|
||||
Want to hack on Gogs? Awesome! Here are instructions to get you started. They are probably not perfect, please let us know if anything feels wrong or incomplete.
|
||||
|
||||
## Contribution guidelines
|
||||
|
||||
### Pull requests are always welcome
|
||||
|
||||
**ALL PULL REQUESTS MUST SEND TO `DEV` BRANCH**
|
||||
|
||||
We are always thrilled to receive pull requests, and do our best to process them as fast as possible. Not sure if that typo is worth a pull request? Do it! We will appreciate it.
|
||||
|
||||
If your pull request is not accepted on the first try, don't be discouraged! If there's a problem with the implementation, hopefully you received feedback on what to improve.
|
||||
@@ -28,4 +28,4 @@ Any significant improvement should be documented as [a GitHub issue](https://git
|
||||
|
||||
### ...but check for existing issues first!
|
||||
|
||||
Please take a moment to check that an issue doesn't already exist documenting your bug report or improvement proposal. If it does, it never hurts to add a quick "+1" or "I have this problem too". This will help prioritize the most common problems and requests.
|
||||
Please take a moment to check that an issue or card on [Trello](https://trello.com/b/uxAoeLUl/gogs-go-git-service) doesn't already exist documenting your bug report or improvement proposal. If it does, it never hurts to add a quick "+1" or "I have this problem too". This will help prioritize the most common problems and requests.
|
||||
57
README.md
57
README.md
@@ -5,9 +5,12 @@ Gogs(Go Git Service) is a Self Hosted Git Service in the Go Programming Language
|
||||
|
||||

|
||||
|
||||
##### Current version: 0.2.0 Alpha
|
||||
##### Current version: 0.4.2 Alpha
|
||||
|
||||
#### Due to testing purpose, data of [try.gogits.org](http://try.gogits.org) has been reset in March 29, 2014 and will reset multiple times after. Please do NOT put your important data on the site.
|
||||
### NOTICES
|
||||
|
||||
- Due to testing purpose, data of [try.gogits.org](http://try.gogits.org) has been reset in **April 14, 2014** and will reset multiple times after. Please do **NOT** put your important data on the site.
|
||||
- Demo site [try.gogits.org](http://try.gogits.org) is running under `dev` branch.
|
||||
|
||||
#### Other language version
|
||||
|
||||
@@ -21,46 +24,60 @@ More importantly, Gogs only needs one binary to setup your own project hosting o
|
||||
|
||||
## Overview
|
||||
|
||||
- Please see [Wiki](https://github.com/gogits/gogs/wiki) for project design, known issues, change log and road map.
|
||||
- Please see [Documentation](http://gogs.io/docs/intro/) for project design, known issues, and change log.
|
||||
- See [Trello Board](https://trello.com/b/uxAoeLUl/gogs-go-git-service) to follow the develop team.
|
||||
- Try it before anything? Do it [online](http://try.gogits.org/Unknown/gogs) or go down to **Installation -> Install from binary** section!
|
||||
- Having troubles? Get help from [Troubleshooting](https://github.com/gogits/gogs/wiki/Troubleshooting).
|
||||
- Having troubles? Get help from [Troubleshooting](http://gogs.io/docs/intro/troubleshooting.md).
|
||||
|
||||
## Features
|
||||
|
||||
- Activity timeline
|
||||
- SSH/HTTPS(Clone only) protocol support.
|
||||
- Register/delete account.
|
||||
- Create/delete/watch public repository.
|
||||
- User profile page.
|
||||
- Repository viewer.
|
||||
- Gravatar and cache support.
|
||||
- Mail service(register, issue).
|
||||
- Administration panel.
|
||||
- Supports MySQL, PostgreSQL and SQLite3(binary release only).
|
||||
- SSH/HTTP(S) protocol support
|
||||
- SMTP/LDAP authentication support
|
||||
- Register/delete/rename account
|
||||
- Create/migrate/mirror/delete/watch/rename/transfer public/private repository
|
||||
- Repository viewer/release/issue tracker/webhooks
|
||||
- Add/remove repository collaborators
|
||||
- Gravatar and cache support
|
||||
- Mail service(register, issue)
|
||||
- Administration panel
|
||||
- Supports MySQL, PostgreSQL and SQLite3
|
||||
- Social account login(GitHub, Google, QQ, Weibo)
|
||||
|
||||
## System Requirements
|
||||
|
||||
- A cheap Raspberry Pi is powerful enough to match the minimal requirement.
|
||||
- 4 CPU Cores and 1GB RAM would be the baseline for teamwork.
|
||||
|
||||
## Installation
|
||||
|
||||
Make sure you install [Prerequirements](https://github.com/gogits/gogs/wiki/Prerequirements) first.
|
||||
Make sure you install [Prerequirements](http://gogs.io/docs/installation/) first.
|
||||
|
||||
There are two ways to install Gogs:
|
||||
There are 5 ways to install Gogs:
|
||||
|
||||
- [Install from binary](https://github.com/gogits/gogs/wiki/Install-from-binary): **STRONGLY RECOMMENDED** for just try and deployment!
|
||||
- [Install from source](https://github.com/gogits/gogs/wiki/Install-from-source)
|
||||
- [Install from binary](http://gogs.io/docs/installation/install_from_binary.md): **STRONGLY RECOMMENDED**
|
||||
- [Install from source](http://gogs.io/docs/installation/install_from_source.md)
|
||||
- [Install from packages](http://gogs.io/docs/installation/install_from_packages.md)
|
||||
- [Ship with Docker](https://github.com/gogits/gogs/tree/master/dockerfiles)
|
||||
- [Install with Vagrant](https://github.com/geerlingguy/ansible-vagrant-examples/tree/master/gogs)
|
||||
|
||||
## Acknowledgments
|
||||
|
||||
- Logo is inspired by [martini-contrib](https://github.com/martini-contrib).
|
||||
- Router and middleware mechanism of [martini](http://martini.codegangsta.io/).
|
||||
- Mail Service, modules design is inspired by [WeTalk](https://github.com/beego/wetalk).
|
||||
- System Monitor Status is inspired by [GoBlog](https://github.com/fuxiaohei/goblog).
|
||||
- Usage and modification from [beego](http://beego.me) modules.
|
||||
- Thanks [lavachen](http://www.lavachen.cn/) and [Rocker](http://weibo.com/rocker1989) for designing Logo.
|
||||
- Thanks [gobuild.io](http://gobuild.io) for providing binary compile and download service.
|
||||
- Great thanks to [Docker China](http://www.dockboard.org/) for providing [dockerfiles](https://github.com/gogits/gogs/tree/master/dockerfiles).
|
||||
|
||||
## Contributors
|
||||
|
||||
This project was launched by [Unknown](https://github.com/Unknwon) and [lunny](https://github.com/lunny); [fuxiaohei](https://github.com/fuxiaohei) and [slene](https://github.com/slene) joined the team soon after. See [contributors page](https://github.com/gogits/gogs/graphs/contributors) for full list of contributors.
|
||||
The [core team](http://gogs.io/team) of this project. See [contributors page](https://github.com/gogits/gogs/graphs/contributors) for full list of contributors.
|
||||
|
||||
[][koding]
|
||||
[koding]: https://koding.com/Teamwork?import=https://github.com/gogits/gogs/archive/master.zip&c=git1
|
||||
|
||||
## License
|
||||
|
||||
Gogs is under the MIT License. See the [LICENSE](https://github.com/gogits/gogs/blob/master/LICENSE) file for the full license text.
|
||||
This project is under the MIT License. See the [LICENSE](https://github.com/gogits/gogs/blob/master/LICENSE) file for the full license text.
|
||||
|
||||
46
README_ZH.md
46
README_ZH.md
@@ -5,7 +5,7 @@ Gogs(Go Git Service) 是一个由 Go 语言编写的自助 Git 托管服务。
|
||||
|
||||

|
||||
|
||||
##### 当前版本:0.2.0 Alpha
|
||||
##### 当前版本:0.4.2 Alpha
|
||||
|
||||
## 开发目的
|
||||
|
||||
@@ -15,46 +15,60 @@ Gogs 完全使用 Go 语言来实现对 Git 数据的操作,实现 **零** 依
|
||||
|
||||
## 项目概览
|
||||
|
||||
- 有关项目设计、已知问题、变更日志和路线图,请通过 [Wiki](https://github.com/gogits/gogs/wiki) 查看。
|
||||
- 有关项目设计、已知问题和变更日志,请通过 [使用手册](http://gogs.io/docs/intro/) 查看。
|
||||
- 您可以到 [Trello Board](https://trello.com/b/uxAoeLUl/gogs-go-git-service) 跟随开发团队的脚步。
|
||||
- 想要先睹为快?通过 [在线体验](http://try.gogits.org/Unknown/gogs) 或查看 **安装部署 -> 二进制安装** 小节。
|
||||
- 使用过程中遇到问题?尝试从 [故障排查](https://github.com/gogits/gogs/wiki/Troubleshooting) 页面获取帮助。
|
||||
- 使用过程中遇到问题?尝试从 [故障排查](http://gogs.io/docs/intro/troubleshooting.md) 页面获取帮助。
|
||||
|
||||
## 功能特性
|
||||
|
||||
- 活动时间线
|
||||
- SSH/HTTPS(仅限 Clone) 协议支持
|
||||
- 注册/删除用户
|
||||
- 创建/删除/关注公开仓库
|
||||
- 用户个人信息页面
|
||||
- 仓库浏览器
|
||||
- 支持 SSH/HTTP(S) 协议
|
||||
- 支持 SMTP/LDAP 用户认证
|
||||
- 注册/删除/重命名用户
|
||||
- 创建/迁移/镜像/删除/关注/重命名/转移 公开/私有 仓库
|
||||
- 仓库 浏览器/发布/缺陷管理/Web 钩子
|
||||
- 添加/删除 仓库协作者
|
||||
- Gravatar 以及缓存支持
|
||||
- 邮件服务(注册、Issue)
|
||||
- 管理员面板
|
||||
- 支持 MySQL、PostgreSQL 以及 SQLite3(仅限二进制版本)
|
||||
- 支持 MySQL、PostgreSQL 以及 SQLite3 数据库
|
||||
- 社交帐号登录(GitHub、Google、QQ、微博)
|
||||
|
||||
## 系统要求
|
||||
|
||||
- 最低的系统硬件要求为一个廉价的树莓派
|
||||
- 如果用于团队项目,建议使用 4 核 CPU 及 1GB 内存
|
||||
|
||||
## 安装部署
|
||||
|
||||
在安装 Gogs 之前,您需要先安装 [基本环境](https://github.com/gogits/gogs/wiki/Prerequirements)。
|
||||
在安装 Gogs 之前,您需要先安装 [基本环境](http://gogs.io/docs/installation/)。
|
||||
|
||||
然后,您可以通过以下两种方式来安装 Gogs:
|
||||
然后,您可以通过以下 5 种方式来安装 Gogs:
|
||||
|
||||
- [二进制安装](https://github.com/gogits/gogs/wiki/Install-from-binary): **强烈推荐** 适合体验者和实际部署
|
||||
- [源码安装](https://github.com/gogits/gogs/wiki/Install-from-source)
|
||||
- [二进制安装](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)
|
||||
- [采用 Docker 部署](https://github.com/gogits/gogs/tree/master/dockerfiles)
|
||||
- [通过 Vagrant 安装](https://github.com/geerlingguy/ansible-vagrant-examples/tree/master/gogs)
|
||||
|
||||
## 特别鸣谢
|
||||
|
||||
- Logo 基于 [martini-contrib](https://github.com/martini-contrib) 修改而来。
|
||||
- 基于 [WeTalk](https://github.com/beego/wetalk) 修改的邮件服务和模块设计。
|
||||
- 基于 [GoBlog](https://github.com/fuxiaohei/goblog) 修改的系统监视状态。
|
||||
- [beego](http://beego.me) 模块的使用与修改。
|
||||
- [martini](http://martini.codegangsta.io/) 的路由与中间件机制。
|
||||
- 感谢 [gobuild.io](http://gobuild.io) 提供二进制编译与下载服务。
|
||||
- 感谢 [lavachen](http://www.lavachen.cn/) 和 [Rocker](http://weibo.com/rocker1989) 设计的 Logo。
|
||||
- 感谢 [Docker 中文社区](http://www.dockboard.org/) 提供的 [dockerfiles](https://github.com/gogits/gogs/tree/master/dockerfiles)。
|
||||
|
||||
## 贡献成员
|
||||
|
||||
本项目最初由 [Unknown](https://github.com/Unknwon) 和 [lunny](https://github.com/lunny) 发起,随后 [fuxiaohei](https://github.com/fuxiaohei) 与 [slene](https://github.com/slene) 加入到开发团队。您可以通过查看 [贡献者页面](https://github.com/gogits/gogs/graphs/contributors) 获取完整的贡献者列表。
|
||||
本项目的 [开发团队](http://gogs.io/team)。您可以通过查看 [贡献者页面](https://github.com/gogits/gogs/graphs/contributors) 获取完整的贡献者列表。
|
||||
|
||||
[][koding]
|
||||
[koding]: https://koding.com/Teamwork?import=https://github.com/gogits/gogs/archive/master.zip&c=git1
|
||||
|
||||
## 授权许可
|
||||
|
||||
Gogs 采用 MIT 开源授权许可证,完整的授权说明已放置在 [LICENSE](https://github.com/gogits/gogs/blob/master/LICENSE) 文件中。
|
||||
本项目采用 MIT 开源授权许可证,完整的授权说明已放置在 [LICENSE](https://github.com/gogits/gogs/blob/master/LICENSE) 文件中。
|
||||
7
bee.json
7
bee.json
@@ -12,13 +12,12 @@
|
||||
"models": "",
|
||||
"others": [
|
||||
"modules",
|
||||
"$GOPATH/src/github.com/gogits/binding",
|
||||
"$GOPATH/src/github.com/gogits/git",
|
||||
"$GOPATH/src/github.com/gogits/gfm"
|
||||
"$GOPATH/src/github.com/gogits/logs",
|
||||
"$GOPATH/src/github.com/gogits/git"
|
||||
]
|
||||
},
|
||||
"cmd_args": [
|
||||
"web"
|
||||
],
|
||||
"envs": []
|
||||
}
|
||||
}
|
||||
|
||||
67
cmd/dump.go
Normal file
67
cmd/dump.go
Normal file
@@ -0,0 +1,67 @@
|
||||
// Copyright 2014 The Gogs Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
"time"
|
||||
|
||||
"github.com/Unknwon/cae/zip"
|
||||
"github.com/codegangsta/cli"
|
||||
|
||||
"github.com/gogits/gogs/models"
|
||||
"github.com/gogits/gogs/modules/setting"
|
||||
)
|
||||
|
||||
var CmdDump = cli.Command{
|
||||
Name: "dump",
|
||||
Usage: "Dump Gogs files and database",
|
||||
Description: `Dump compresses all related files and database into zip file.
|
||||
It can be used for backup and capture Gogs server image to send to maintainer`,
|
||||
Action: runDump,
|
||||
Flags: []cli.Flag{},
|
||||
}
|
||||
|
||||
func runDump(*cli.Context) {
|
||||
setting.NewConfigContext()
|
||||
models.LoadModelsConfig()
|
||||
models.SetEngine()
|
||||
|
||||
log.Printf("Dumping local repositories...%s", setting.RepoRootPath)
|
||||
zip.Verbose = false
|
||||
defer os.Remove("gogs-repo.zip")
|
||||
if err := zip.PackTo(setting.RepoRootPath, "gogs-repo.zip", 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 {
|
||||
log.Fatalf("Fail to dump database: %v", err)
|
||||
}
|
||||
|
||||
fileName := fmt.Sprintf("gogs-dump-%d.zip", time.Now().Unix())
|
||||
log.Printf("Packing dump files...")
|
||||
z, err := zip.Create(fileName)
|
||||
if err != nil {
|
||||
os.Remove(fileName)
|
||||
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.AddFile("custom/conf/app.ini", path.Join(workDir, "custom/conf/app.ini"))
|
||||
z.AddDir("log", path.Join(workDir, "log"))
|
||||
if err = z.Close(); err != nil {
|
||||
os.Remove(fileName)
|
||||
log.Fatalf("Fail to save %s: %v", fileName, err)
|
||||
}
|
||||
|
||||
log.Println("Finish dumping!")
|
||||
}
|
||||
43
cmd/fix.go
Normal file
43
cmd/fix.go
Normal file
@@ -0,0 +1,43 @@
|
||||
// Copyright 2014 The Gogs Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/gogits/gogs/models"
|
||||
"github.com/gogits/gogs/modules/setting"
|
||||
)
|
||||
|
||||
var CmdFix = cli.Command{
|
||||
Name: "fix",
|
||||
Usage: "This command for upgrade from old version",
|
||||
Description: `Fix provide upgrade from old version`,
|
||||
Action: runFix,
|
||||
Flags: []cli.Flag{},
|
||||
}
|
||||
|
||||
func runFix(k *cli.Context) {
|
||||
workDir, _ := setting.WorkDir()
|
||||
newLogger(workDir)
|
||||
|
||||
setting.NewConfigContext()
|
||||
models.LoadModelsConfig()
|
||||
|
||||
if models.UseSQLite3 {
|
||||
os.Chdir(workDir)
|
||||
}
|
||||
|
||||
models.SetEngine()
|
||||
|
||||
err := models.Fix()
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
} else {
|
||||
fmt.Println("Fix successfully!")
|
||||
}
|
||||
}
|
||||
203
cmd/serve.go
Normal file
203
cmd/serve.go
Normal file
@@ -0,0 +1,203 @@
|
||||
// Copyright 2014 The Gogs Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
qlog "github.com/qiniu/log"
|
||||
|
||||
"github.com/gogits/gogs/models"
|
||||
"github.com/gogits/gogs/modules/setting"
|
||||
)
|
||||
|
||||
var CmdServ = cli.Command{
|
||||
Name: "serv",
|
||||
Usage: "This command should only be called by SSH shell",
|
||||
Description: `Serv provide access auth for repositories`,
|
||||
Action: runServ,
|
||||
Flags: []cli.Flag{},
|
||||
}
|
||||
|
||||
func newLogger(logPath string) {
|
||||
os.MkdirAll(path.Dir(logPath), os.ModePerm)
|
||||
|
||||
f, err := os.OpenFile(logPath, os.O_WRONLY|os.O_APPEND|os.O_CREATE, os.ModePerm)
|
||||
if err != nil {
|
||||
qlog.Fatal(err)
|
||||
}
|
||||
|
||||
qlog.SetOutput(f)
|
||||
//qlog.SetOutputLevel(qlog.Ldebug)
|
||||
qlog.Info("Start logging serv...")
|
||||
}
|
||||
|
||||
func setup(logPath string) {
|
||||
workDir, _ := setting.WorkDir()
|
||||
newLogger(path.Join(workDir, logPath))
|
||||
|
||||
setting.NewConfigContext()
|
||||
models.LoadModelsConfig()
|
||||
|
||||
if models.UseSQLite3 {
|
||||
os.Chdir(workDir)
|
||||
}
|
||||
|
||||
models.SetEngine()
|
||||
}
|
||||
|
||||
func parseCmd(cmd string) (string, string) {
|
||||
ss := strings.SplitN(cmd, " ", 2)
|
||||
if len(ss) != 2 {
|
||||
return "", ""
|
||||
}
|
||||
|
||||
verb, args := ss[0], ss[1]
|
||||
if verb == "git" {
|
||||
ss = strings.SplitN(args, " ", 2)
|
||||
args = ss[1]
|
||||
verb = fmt.Sprintf("%s %s", verb, ss[0])
|
||||
}
|
||||
return verb, strings.Replace(args, "'/", "'", 1)
|
||||
}
|
||||
|
||||
var (
|
||||
COMMANDS_READONLY = map[string]int{
|
||||
"git-upload-pack": models.AU_WRITABLE,
|
||||
"git upload-pack": models.AU_WRITABLE,
|
||||
"git-upload-archive": models.AU_WRITABLE,
|
||||
}
|
||||
|
||||
COMMANDS_WRITE = map[string]int{
|
||||
"git-receive-pack": models.AU_READABLE,
|
||||
"git receive-pack": models.AU_READABLE,
|
||||
}
|
||||
)
|
||||
|
||||
func In(b string, sl map[string]int) bool {
|
||||
_, e := sl[b]
|
||||
return e
|
||||
}
|
||||
|
||||
func runServ(k *cli.Context) {
|
||||
setup(path.Join(setting.LogRootPath, "serv.log"))
|
||||
|
||||
keys := strings.Split(os.Args[2], "-")
|
||||
if len(keys) != 2 {
|
||||
println("Gogs: auth file format error")
|
||||
qlog.Fatal("Invalid auth file format: %s", os.Args[2])
|
||||
}
|
||||
|
||||
keyId, err := strconv.ParseInt(keys[1], 10, 64)
|
||||
if err != nil {
|
||||
println("Gogs: auth file format error")
|
||||
qlog.Fatalf("Invalid auth file format: %v", err)
|
||||
}
|
||||
user, err := models.GetUserByKeyId(keyId)
|
||||
if err != nil {
|
||||
if err == models.ErrUserNotKeyOwner {
|
||||
println("Gogs: you are not the owner of SSH key")
|
||||
qlog.Fatalf("Invalid owner of SSH key: %d", keyId)
|
||||
}
|
||||
println("Gogs: internal error:", err)
|
||||
qlog.Fatalf("Fail to get user by key ID(%d): %v", keyId, err)
|
||||
}
|
||||
|
||||
cmd := os.Getenv("SSH_ORIGINAL_COMMAND")
|
||||
if cmd == "" {
|
||||
println("Hi", user.Name, "! You've successfully authenticated, but Gogs does not provide shell access.")
|
||||
return
|
||||
}
|
||||
|
||||
verb, args := parseCmd(cmd)
|
||||
repoPath := strings.Trim(args, "'")
|
||||
rr := strings.SplitN(repoPath, "/", 2)
|
||||
if len(rr) != 2 {
|
||||
println("Gogs: unavailable repository", args)
|
||||
qlog.Fatalf("Unavailable repository: %v", args)
|
||||
}
|
||||
repoUserName := rr[0]
|
||||
repoName := strings.TrimSuffix(rr[1], ".git")
|
||||
|
||||
isWrite := In(verb, COMMANDS_WRITE)
|
||||
isRead := In(verb, COMMANDS_READONLY)
|
||||
|
||||
repoUser, err := models.GetUserByName(repoUserName)
|
||||
if err != nil {
|
||||
if err == models.ErrUserNotExist {
|
||||
println("Gogs: given repository owner are not registered")
|
||||
qlog.Fatalf("Unregistered owner: %s", repoUserName)
|
||||
}
|
||||
println("Gogs: internal error:", err)
|
||||
qlog.Fatalf("Fail to get repository owner(%s): %v", repoUserName, err)
|
||||
}
|
||||
|
||||
// Access check.
|
||||
switch {
|
||||
case isWrite:
|
||||
has, err := models.HasAccess(user.Name, path.Join(repoUserName, repoName), models.AU_WRITABLE)
|
||||
if err != nil {
|
||||
println("Gogs: internal error:", err)
|
||||
qlog.Fatal("Fail to check write access:", err)
|
||||
} else if !has {
|
||||
println("You have no right to write this repository")
|
||||
qlog.Fatalf("User %s has no right to write repository %s", user.Name, repoPath)
|
||||
}
|
||||
case isRead:
|
||||
repo, err := models.GetRepositoryByName(repoUser.Id, repoName)
|
||||
if err != nil {
|
||||
if err == models.ErrRepoNotExist {
|
||||
println("Gogs: given repository does not exist")
|
||||
qlog.Fatalf("Repository does not exist: %s/%s", repoUser.Name, repoName)
|
||||
}
|
||||
println("Gogs: internal error:", err)
|
||||
qlog.Fatalf("Fail to get repository: %v", err)
|
||||
}
|
||||
|
||||
if !repo.IsPrivate {
|
||||
break
|
||||
}
|
||||
|
||||
has, err := models.HasAccess(user.Name, path.Join(repoUserName, repoName), models.AU_READABLE)
|
||||
if err != nil {
|
||||
println("Gogs: internal error:", err)
|
||||
qlog.Fatal("Fail to check read access:", err)
|
||||
} else if !has {
|
||||
println("You have no right to access this repository")
|
||||
qlog.Fatalf("User %s has no right to read repository %s", user.Name, repoPath)
|
||||
}
|
||||
default:
|
||||
println("Unknown command")
|
||||
return
|
||||
}
|
||||
|
||||
models.SetRepoEnvs(user.Id, user.Name, repoName, repoUserName)
|
||||
|
||||
gitcmd := exec.Command(verb, repoPath)
|
||||
gitcmd.Dir = setting.RepoRootPath
|
||||
gitcmd.Stdout = os.Stdout
|
||||
gitcmd.Stdin = os.Stdin
|
||||
gitcmd.Stderr = os.Stderr
|
||||
|
||||
if err = gitcmd.Run(); err != nil {
|
||||
println("Gogs: internal error:", err)
|
||||
qlog.Fatalf("Fail to execute git command: %v", err)
|
||||
}
|
||||
|
||||
//refName := os.Getenv("refName")
|
||||
//oldCommitId := os.Getenv("oldCommitId")
|
||||
//newCommitId := os.Getenv("newCommitId")
|
||||
|
||||
//qlog.Error("get envs:", refName, oldCommitId, newCommitId)
|
||||
|
||||
// update
|
||||
//models.Update(refName, oldCommitId, newCommitId, repoUserName, repoName, user.Id)
|
||||
}
|
||||
58
cmd/update.go
Normal file
58
cmd/update.go
Normal file
@@ -0,0 +1,58 @@
|
||||
// Copyright 2014 The Gogs Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path"
|
||||
"strconv"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
qlog "github.com/qiniu/log"
|
||||
|
||||
"github.com/gogits/gogs/models"
|
||||
"github.com/gogits/gogs/modules/setting"
|
||||
)
|
||||
|
||||
var CmdUpdate = cli.Command{
|
||||
Name: "update",
|
||||
Usage: "This command should only be called by SSH shell",
|
||||
Description: `Update get pushed info and insert into database`,
|
||||
Action: runUpdate,
|
||||
Flags: []cli.Flag{},
|
||||
}
|
||||
|
||||
func updateEnv(refName, oldCommitId, newCommitId string) {
|
||||
os.Setenv("refName", refName)
|
||||
os.Setenv("oldCommitId", oldCommitId)
|
||||
os.Setenv("newCommitId", newCommitId)
|
||||
qlog.Info("set envs:", refName, oldCommitId, newCommitId)
|
||||
}
|
||||
|
||||
func runUpdate(c *cli.Context) {
|
||||
cmd := os.Getenv("SSH_ORIGINAL_COMMAND")
|
||||
if cmd == "" {
|
||||
return
|
||||
}
|
||||
|
||||
setup(path.Join(setting.LogRootPath, "update.log"))
|
||||
|
||||
args := c.Args()
|
||||
if len(args) != 3 {
|
||||
qlog.Fatal("received less 3 parameters")
|
||||
} else if args[0] == "" {
|
||||
qlog.Fatal("refName is empty, shouldn't use")
|
||||
}
|
||||
|
||||
//updateEnv(args[0], args[1], args[2])
|
||||
|
||||
userName := os.Getenv("userName")
|
||||
userId, _ := strconv.ParseInt(os.Getenv("userId"), 10, 64)
|
||||
//repoId := os.Getenv("repoId")
|
||||
repoUserName := os.Getenv("repoUserName")
|
||||
repoName := os.Getenv("repoName")
|
||||
|
||||
models.Update(args[0], args[1], args[2], userName, repoUserName, repoName, userId)
|
||||
}
|
||||
277
cmd/web.go
Normal file
277
cmd/web.go
Normal file
@@ -0,0 +1,277 @@
|
||||
// Copyright 2014 The Gogs Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/go-martini/martini"
|
||||
|
||||
"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/log"
|
||||
"github.com/gogits/gogs/modules/middleware"
|
||||
"github.com/gogits/gogs/modules/middleware/binding"
|
||||
"github.com/gogits/gogs/modules/setting"
|
||||
"github.com/gogits/gogs/routers"
|
||||
"github.com/gogits/gogs/routers/admin"
|
||||
"github.com/gogits/gogs/routers/api/v1"
|
||||
"github.com/gogits/gogs/routers/dev"
|
||||
"github.com/gogits/gogs/routers/repo"
|
||||
"github.com/gogits/gogs/routers/user"
|
||||
)
|
||||
|
||||
var CmdWeb = cli.Command{
|
||||
Name: "web",
|
||||
Usage: "Start Gogs web server",
|
||||
Description: `Gogs web server is the only thing you need to run,
|
||||
and it takes care of all the other things for you`,
|
||||
Action: runWeb,
|
||||
Flags: []cli.Flag{},
|
||||
}
|
||||
|
||||
// checkVersion checks if binary matches the version of temolate files.
|
||||
func checkVersion() {
|
||||
data, err := ioutil.ReadFile(path.Join(setting.StaticRootPath, "templates/VERSION"))
|
||||
if err != nil {
|
||||
log.Fatal("Fail to read 'templates/VERSION': %v", err)
|
||||
}
|
||||
if string(data) != setting.AppVer {
|
||||
log.Fatal("Binary and template file version does not match, did you forget to recompile?")
|
||||
}
|
||||
}
|
||||
|
||||
func newMartini() *martini.ClassicMartini {
|
||||
r := martini.NewRouter()
|
||||
m := martini.New()
|
||||
m.Use(middleware.Logger())
|
||||
m.Use(martini.Recovery())
|
||||
m.Use(martini.Static(path.Join(setting.StaticRootPath, "public"),
|
||||
martini.StaticOptions{SkipLogging: !setting.DisableRouterLog}))
|
||||
m.MapTo(r, (*martini.Routes)(nil))
|
||||
m.Action(r.Handle)
|
||||
return &martini.ClassicMartini{m, r}
|
||||
}
|
||||
|
||||
func runWeb(*cli.Context) {
|
||||
routers.GlobalInit()
|
||||
checkVersion()
|
||||
|
||||
m := newMartini()
|
||||
|
||||
// Middlewares.
|
||||
m.Use(middleware.Renderer(middleware.RenderOptions{
|
||||
Directory: path.Join(setting.StaticRootPath, "templates"),
|
||||
Funcs: []template.FuncMap{base.TemplateFuncs},
|
||||
IndentJSON: true,
|
||||
}))
|
||||
m.Use(middleware.InitContext())
|
||||
|
||||
reqSignIn := middleware.Toggle(&middleware.ToggleOptions{SignInRequire: true})
|
||||
ignSignIn := middleware.Toggle(&middleware.ToggleOptions{SignInRequire: setting.Service.RequireSignInView})
|
||||
ignSignInAndCsrf := middleware.Toggle(&middleware.ToggleOptions{DisableCsrf: true})
|
||||
|
||||
reqSignOut := middleware.Toggle(&middleware.ToggleOptions{SignOutRequire: true})
|
||||
|
||||
bindIgnErr := binding.BindIgnErr
|
||||
|
||||
// Routers.
|
||||
m.Get("/", ignSignIn, routers.Home)
|
||||
m.Get("/install", bindIgnErr(auth.InstallForm{}), routers.Install)
|
||||
m.Post("/install", bindIgnErr(auth.InstallForm{}), routers.InstallPost)
|
||||
m.Group("", func(r martini.Router) {
|
||||
r.Get("/issues", user.Issues)
|
||||
r.Get("/pulls", user.Pulls)
|
||||
r.Get("/stars", user.Stars)
|
||||
}, reqSignIn)
|
||||
|
||||
m.Group("/api", func(r martini.Router) {
|
||||
m.Group("/v1", func(r martini.Router) {
|
||||
// Miscellaneous.
|
||||
r.Post("/markdown", bindIgnErr(apiv1.MarkdownForm{}), v1.Markdown)
|
||||
r.Post("/markdown/raw", v1.MarkdownRaw)
|
||||
|
||||
// Users.
|
||||
r.Get("/users/search", v1.SearchUser)
|
||||
|
||||
r.Any("**", func(ctx *middleware.Context) {
|
||||
ctx.JSON(404, &base.ApiJsonErr{"Not Found", v1.DOC_URL})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
avt := avatar.CacheServer("public/img/avatar/", "public/img/avatar_default.jpg")
|
||||
os.MkdirAll("public/img/avatar/", os.ModePerm)
|
||||
m.Get("/avatar/:hash", avt.ServeHTTP)
|
||||
|
||||
m.Group("/user", func(r martini.Router) {
|
||||
r.Get("/login", user.SignIn)
|
||||
r.Post("/login", bindIgnErr(auth.LogInForm{}), user.SignInPost)
|
||||
r.Get("/login/:name", user.SocialSignIn)
|
||||
r.Get("/sign_up", user.SignUp)
|
||||
r.Post("/sign_up", bindIgnErr(auth.RegisterForm{}), user.SignUpPost)
|
||||
r.Get("/reset_password", user.ResetPasswd)
|
||||
r.Post("/reset_password", user.ResetPasswdPost)
|
||||
}, reqSignOut)
|
||||
m.Group("/user", func(r martini.Router) {
|
||||
r.Get("/delete", user.Delete)
|
||||
r.Post("/delete", user.DeletePost)
|
||||
r.Get("/settings", user.Setting)
|
||||
r.Post("/settings", bindIgnErr(auth.UpdateProfileForm{}), user.SettingPost)
|
||||
}, reqSignIn)
|
||||
m.Group("/user", func(r martini.Router) {
|
||||
r.Get("/feeds", binding.Bind(auth.FeedsForm{}), user.Feeds)
|
||||
r.Any("/activate", user.Activate)
|
||||
r.Get("/email2user", user.Email2User)
|
||||
r.Get("/forget_password", user.ForgotPasswd)
|
||||
r.Post("/forget_password", user.ForgotPasswdPost)
|
||||
r.Get("/logout", user.SignOut)
|
||||
})
|
||||
m.Group("/user/settings", func(r martini.Router) {
|
||||
r.Get("/social", user.SettingSocial)
|
||||
r.Get("/password", user.SettingPassword)
|
||||
r.Post("/password", bindIgnErr(auth.UpdatePasswdForm{}), user.SettingPasswordPost)
|
||||
r.Any("/ssh", bindIgnErr(auth.AddSSHKeyForm{}), user.SettingSSHKeys)
|
||||
r.Get("/notification", user.SettingNotification)
|
||||
r.Get("/security", user.SettingSecurity)
|
||||
}, reqSignIn)
|
||||
|
||||
m.Get("/user/:username", ignSignIn, user.Profile)
|
||||
|
||||
m.Group("/repo", func(r martini.Router) {
|
||||
r.Get("/create", repo.Create)
|
||||
r.Post("/create", bindIgnErr(auth.CreateRepoForm{}), repo.CreatePost)
|
||||
r.Get("/migrate", repo.Migrate)
|
||||
r.Post("/migrate", bindIgnErr(auth.MigrateRepoForm{}), repo.MigratePost)
|
||||
}, reqSignIn)
|
||||
|
||||
adminReq := middleware.Toggle(&middleware.ToggleOptions{SignInRequire: true, AdminRequire: true})
|
||||
|
||||
m.Get("/admin", adminReq, admin.Dashboard)
|
||||
m.Group("/admin", func(r martini.Router) {
|
||||
r.Get("/users", admin.Users)
|
||||
r.Get("/repos", admin.Repositories)
|
||||
r.Get("/config", admin.Config)
|
||||
r.Get("/auths", admin.Auths)
|
||||
}, adminReq)
|
||||
m.Group("/admin/users", func(r martini.Router) {
|
||||
r.Get("/new", admin.NewUser)
|
||||
r.Post("/new", bindIgnErr(auth.RegisterForm{}), admin.NewUserPost)
|
||||
r.Get("/:userid", admin.EditUser)
|
||||
r.Post("/:userid", bindIgnErr(auth.AdminEditUserForm{}), admin.EditUserPost)
|
||||
r.Get("/:userid/delete", admin.DeleteUser)
|
||||
}, adminReq)
|
||||
|
||||
m.Group("/admin/auths", func(r martini.Router) {
|
||||
r.Get("/new", admin.NewAuthSource)
|
||||
r.Post("/new", bindIgnErr(auth.AuthenticationForm{}), admin.NewAuthSourcePost)
|
||||
r.Get("/:authid", admin.EditAuthSource)
|
||||
r.Post("/:authid", bindIgnErr(auth.AuthenticationForm{}), admin.EditAuthSourcePost)
|
||||
r.Get("/:authid/delete", admin.DeleteAuthSource)
|
||||
}, adminReq)
|
||||
|
||||
if martini.Env == martini.Dev {
|
||||
m.Get("/template/**", dev.TemplatePreview)
|
||||
}
|
||||
|
||||
reqOwner := middleware.RequireOwner()
|
||||
|
||||
m.Group("/:username/:reponame", func(r martini.Router) {
|
||||
r.Get("/settings", repo.Setting)
|
||||
r.Post("/settings", bindIgnErr(auth.RepoSettingForm{}), repo.SettingPost)
|
||||
|
||||
m.Group("/settings", func(r martini.Router) {
|
||||
r.Get("/collaboration", repo.Collaboration)
|
||||
r.Post("/collaboration", repo.CollaborationPost)
|
||||
r.Get("/hooks", repo.WebHooks)
|
||||
r.Get("/hooks/add", repo.WebHooksAdd)
|
||||
r.Post("/hooks/add", bindIgnErr(auth.NewWebhookForm{}), repo.WebHooksAddPost)
|
||||
r.Get("/hooks/:id", repo.WebHooksEdit)
|
||||
r.Post("/hooks/:id", bindIgnErr(auth.NewWebhookForm{}), repo.WebHooksEditPost)
|
||||
})
|
||||
}, reqSignIn, middleware.RepoAssignment(true), reqOwner)
|
||||
|
||||
m.Group("/:username/:reponame", func(r martini.Router) {
|
||||
r.Get("/action/:action", repo.Action)
|
||||
|
||||
m.Group("/issues", func(r martini.Router) {
|
||||
r.Get("/new", repo.CreateIssue)
|
||||
r.Post("/new", bindIgnErr(auth.CreateIssueForm{}), repo.CreateIssuePost)
|
||||
r.Post("/:index", bindIgnErr(auth.CreateIssueForm{}), repo.UpdateIssue)
|
||||
r.Post("/:index/label", repo.UpdateIssueLabel)
|
||||
r.Post("/:index/milestone", repo.UpdateIssueMilestone)
|
||||
r.Post("/:index/assignee", repo.UpdateAssignee)
|
||||
r.Post("/labels/new", bindIgnErr(auth.CreateLabelForm{}), repo.NewLabel)
|
||||
r.Post("/labels/edit", bindIgnErr(auth.CreateLabelForm{}), repo.UpdateLabel)
|
||||
r.Post("/labels/delete", repo.DeleteLabel)
|
||||
r.Get("/milestones", repo.Milestones)
|
||||
r.Get("/milestones/new", repo.NewMilestone)
|
||||
r.Post("/milestones/new", bindIgnErr(auth.CreateMilestoneForm{}), repo.NewMilestonePost)
|
||||
r.Get("/milestones/:index/edit", repo.UpdateMilestone)
|
||||
r.Post("/milestones/:index/edit", bindIgnErr(auth.CreateMilestoneForm{}), repo.UpdateMilestonePost)
|
||||
r.Get("/milestones/:index/:action", repo.UpdateMilestone)
|
||||
})
|
||||
|
||||
r.Post("/comment/:action", repo.Comment)
|
||||
r.Get("/releases/new", repo.ReleasesNew)
|
||||
}, reqSignIn, middleware.RepoAssignment(true))
|
||||
|
||||
m.Group("/:username/:reponame", func(r martini.Router) {
|
||||
r.Post("/releases/new", bindIgnErr(auth.NewReleaseForm{}), repo.ReleasesNewPost)
|
||||
}, reqSignIn, middleware.RepoAssignment(true, true))
|
||||
|
||||
m.Group("/:username/:reponame", func(r martini.Router) {
|
||||
r.Get("/issues", repo.Issues)
|
||||
r.Get("/issues/:index", repo.ViewIssue)
|
||||
r.Get("/pulls", repo.Pulls)
|
||||
r.Get("/branches", repo.Branches)
|
||||
}, ignSignIn, middleware.RepoAssignment(true))
|
||||
|
||||
m.Group("/:username/:reponame", func(r martini.Router) {
|
||||
r.Get("/src/:branchname", repo.Single)
|
||||
r.Get("/src/:branchname/**", repo.Single)
|
||||
r.Get("/raw/:branchname/**", repo.SingleDownload)
|
||||
r.Get("/commits/:branchname", repo.Commits)
|
||||
r.Get("/commits/:branchname/search", repo.SearchCommits)
|
||||
r.Get("/commits/:branchname/**", repo.FileHistory)
|
||||
r.Get("/commit/:branchname", repo.Diff)
|
||||
r.Get("/commit/:branchname/**", repo.Diff)
|
||||
r.Get("/releases", repo.Releases)
|
||||
r.Get("/archive/:branchname/:reponame.zip", repo.ZipDownload)
|
||||
r.Get("/archive/:branchname/:reponame.tar.gz", repo.TarGzDownload)
|
||||
}, ignSignIn, middleware.RepoAssignment(true, true))
|
||||
|
||||
m.Group("/:username", func(r martini.Router) {
|
||||
r.Get("/:reponame", middleware.RepoAssignment(true, true, true), repo.Single)
|
||||
r.Any("/:reponame/**", repo.Http)
|
||||
}, ignSignInAndCsrf)
|
||||
|
||||
// Not found handler.
|
||||
m.NotFound(routers.NotFound)
|
||||
|
||||
var err error
|
||||
listenAddr := fmt.Sprintf("%s:%s", setting.HttpAddr, setting.HttpPort)
|
||||
log.Info("Listen: %v://%s", setting.Protocol, listenAddr)
|
||||
switch setting.Protocol {
|
||||
case setting.HTTP:
|
||||
err = http.ListenAndServe(listenAddr, m)
|
||||
case setting.HTTPS:
|
||||
err = http.ListenAndServeTLS(listenAddr, setting.CertFile, setting.KeyFile, m)
|
||||
default:
|
||||
log.Fatal("Invalid protocol: %s", setting.Protocol)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
log.Fatal("Fail to start server: %v", err)
|
||||
}
|
||||
}
|
||||
9
conf/README.md
Normal file
9
conf/README.md
Normal file
@@ -0,0 +1,9 @@
|
||||
## NOTICE
|
||||
|
||||
This directory only used for development, and us [go-bindata](https://github.com/jteeuwen/go-bindata) to store in memory for releases.
|
||||
|
||||
To apply any change in this directory, install [go-bindata](https://github.com/jteeuwen/go-bindata), and then execute following command in root of source directory:
|
||||
|
||||
```
|
||||
$ go-bindata -ignore="\\.DS_Store|README.md" -o modules/bin/conf.go -pkg="bin" conf/...
|
||||
```
|
||||
83
conf/app.ini
83
conf/app.ini
@@ -8,17 +8,29 @@ RUN_MODE = dev
|
||||
|
||||
[repository]
|
||||
ROOT =
|
||||
LANG_IGNS = Google Go|C|C++|Python|Ruby|C Sharp
|
||||
LICENSES = Apache v2 License|GPL v2|MIT License|Affero GPL|Artistic License 2.0|BSD (3-Clause) License
|
||||
SCRIPT_TYPE = bash
|
||||
|
||||
[server]
|
||||
PROTOCOL = http
|
||||
DOMAIN = localhost
|
||||
ROOT_URL = http://%(DOMAIN)s:%(HTTP_PORT)s/
|
||||
ROOT_URL = %(PROTOCOL)s://%(DOMAIN)s:%(HTTP_PORT)s/
|
||||
HTTP_ADDR =
|
||||
HTTP_PORT = 3000
|
||||
SSH_PORT = 22
|
||||
; Disable CDN even in "prod" mode
|
||||
OFFLINE_MODE = false
|
||||
DISABLE_ROUTER_LOG = false
|
||||
; Generate steps:
|
||||
; $ cd path/to/gogs/custom/https
|
||||
; $ go run $GOROOT/src/pkg/crypto/tls/generate_cert.go -ca=true -duration=8760h0m0s -host=myhost.example.com
|
||||
CERT_FILE = custom/https/cert.pem
|
||||
KEY_FILE = custom/https/key.pem
|
||||
; Upper level of template and static file path
|
||||
; default is the path where Gogs is executed
|
||||
STATIC_ROOT_PATH =
|
||||
|
||||
[database]
|
||||
; Either "mysql", "postgres" or "sqlite3"(binary release only), it's your choice
|
||||
; Either "mysql", "postgres" or "sqlite3", it's your choice
|
||||
DB_TYPE = mysql
|
||||
HOST = 127.0.0.1:3306
|
||||
NAME = gogs
|
||||
@@ -46,7 +58,7 @@ RESET_PASSWD_CODE_LIVE_MINUTES = 180
|
||||
; User need to confirm e-mail for registration
|
||||
REGISTER_EMAIL_CONFIRM = false
|
||||
; Does not allow register and admin create account only
|
||||
DISENABLE_REGISTERATION = false
|
||||
DISABLE_REGISTRATION = false
|
||||
; User must sign in to view anything.
|
||||
REQUIRE_SIGNIN_VIEW = false
|
||||
; Cache avatar as picture
|
||||
@@ -62,6 +74,7 @@ SEND_BUFFER_LEN = 10
|
||||
SUBJECT = %(APP_NAME)s
|
||||
; Mail server
|
||||
; Gmail: smtp.gmail.com:587
|
||||
; QQ: smtp.qq.com:25
|
||||
HOST =
|
||||
; Mail from address
|
||||
FROM =
|
||||
@@ -69,6 +82,55 @@ 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 = all
|
||||
; QQ 互联
|
||||
; AUTH_URL = https://graph.qq.com/oauth2.0/authorize
|
||||
; TOKEN_URL = https://graph.qq.com/oauth2.0/token
|
||||
; Tencent weibo
|
||||
AUTH_URL = https://open.t.qq.com/cgi-bin/oauth2/authorize
|
||||
TOKEN_URL = https://open.t.qq.com/cgi-bin/oauth2/access_token
|
||||
|
||||
[oauth.twitter]
|
||||
ENABLED = false
|
||||
CLIENT_ID =
|
||||
CLIENT_SECRET =
|
||||
SCOPES = all
|
||||
AUTH_URL = https://api.twitter.com/oauth/authorize
|
||||
TOKEN_URL = https://api.twitter.com/oauth/access_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
|
||||
@@ -84,9 +146,9 @@ HOST =
|
||||
PROVIDER = file
|
||||
; Provider config options
|
||||
; memory: not have any config yet
|
||||
; file: session file path, e.g. data/sessions
|
||||
; redis: config like redis server addr, poolSize, password, e.g. 127.0.0.1:6379,100,astaxie
|
||||
; mysql: go-sql-driver/mysql dsn config string, e.g. root:password@/session_table
|
||||
; file: session file path, e.g. "data/sessions"
|
||||
; redis: config like redis server addr, poolSize, password, e.g. "127.0.0.1:6379,100,astaxie"
|
||||
; mysql: go-sql-driver/mysql dsn config string, e.g. "root:password@/session_table"
|
||||
PROVIDER_CONFIG = data/sessions
|
||||
; Session cookie name
|
||||
COOKIE_NAME = i_like_gogits
|
||||
@@ -106,9 +168,12 @@ SESSION_ID_HASHKEY =
|
||||
[picture]
|
||||
; The place to picture data, either "server" or "qiniu", default is "server"
|
||||
SERVICE = server
|
||||
DISABLE_GRAVATAR = false
|
||||
|
||||
[log]
|
||||
ROOT_PATH =
|
||||
; Either "console", "file", "conn", "smtp" or "database", default is "console"
|
||||
; Use comma to separate multiple modes, e.g. "console, file"
|
||||
MODE = console
|
||||
; Buffer length of channel, keep it as it is if you don't know what it is.
|
||||
BUFFER_LEN = 10000
|
||||
@@ -162,5 +227,5 @@ RECEIVERS =
|
||||
; For "database" mode only
|
||||
[log.database]
|
||||
LEVEL =
|
||||
Driver =
|
||||
DRIVER =
|
||||
CONN =
|
||||
|
||||
26
conf/etc/supervisord.conf
Normal file
26
conf/etc/supervisord.conf
Normal file
@@ -0,0 +1,26 @@
|
||||
[unix_http_server]
|
||||
file=/tmp/supervisor.sock ; path to your socket file
|
||||
|
||||
[supervisord]
|
||||
logfile=log/supervisord.log ; supervisord log file
|
||||
logfile_maxbytes=50MB ; maximum size of logfile before rotation
|
||||
logfile_backups=10 ; number of backed up logfiles
|
||||
loglevel=warn ; info, debug, warn, trace
|
||||
pidfile=/tmp/supervisord.pid ; pidfile location
|
||||
nodaemon=false ; run supervisord as a daemon
|
||||
minfds=1024 ; number of startup file descriptors
|
||||
minprocs=200 ; number of process descriptors
|
||||
user=root ; default user
|
||||
childlogdir=log
|
||||
|
||||
[rpcinterface:supervisor]
|
||||
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface
|
||||
|
||||
[supervisorctl]
|
||||
serverurl=unix:///tmp/supervisor.sock ; use a unix:// URL for a unix socket
|
||||
|
||||
[program:gogs]
|
||||
command = /root/Developer/gopath/src/github.com/gogits/gogs/start.sh ; here must be the real url, not ~ or $GOROOT like
|
||||
autostart = true
|
||||
stdout_logfile = log/supervisor-gogs-stderr.log
|
||||
stderr_logfile = log/supervisor-gogs-error.log
|
||||
23
conf/gitignore/Android
Normal file
23
conf/gitignore/Android
Normal file
@@ -0,0 +1,23 @@
|
||||
# Built application files
|
||||
*.apk
|
||||
*.ap_
|
||||
|
||||
# Files for the Dalvik VM
|
||||
*.dex
|
||||
|
||||
# Java class files
|
||||
*.class
|
||||
|
||||
# Generated files
|
||||
bin/
|
||||
gen/
|
||||
|
||||
# Gradle files
|
||||
.gradle/
|
||||
build/
|
||||
|
||||
# Local configuration file (sdk path, etc)
|
||||
local.properties
|
||||
|
||||
# Proguard folder generated by Eclipse
|
||||
proguard/
|
||||
12
conf/gitignore/Java
Normal file
12
conf/gitignore/Java
Normal file
@@ -0,0 +1,12 @@
|
||||
*.class
|
||||
|
||||
# Mobile Tools for Java (J2ME)
|
||||
.mtj.tmp/
|
||||
|
||||
# Package Files #
|
||||
*.jar
|
||||
*.war
|
||||
*.ear
|
||||
|
||||
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
|
||||
hs_err_pid*
|
||||
7
conf/gitignore/Objective-C
Normal file
7
conf/gitignore/Objective-C
Normal file
@@ -0,0 +1,7 @@
|
||||
# CocoaPods
|
||||
#
|
||||
# We recommend against adding the Pods directory to your .gitignore. However
|
||||
# you should judge for yourself, the pros and cons are mentioned at:
|
||||
# http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control?
|
||||
#
|
||||
# Pods/
|
||||
@@ -1,2 +1,2 @@
|
||||
DROP DATABASE gogs;
|
||||
CREATE DATABASE IF NOT EXISTS gogs CHARACTER SET utf8 COLLATE utf8_general_ci;
|
||||
DROP DATABASE IF EXISTS gogs;
|
||||
CREATE DATABASE IF NOT EXISTS gogs CHARACTER SET utf8 COLLATE utf8_general_ci;
|
||||
|
||||
8
conf/supervisor.ini
Normal file
8
conf/supervisor.ini
Normal file
@@ -0,0 +1,8 @@
|
||||
[program:gogs]
|
||||
user=git
|
||||
command = /home/git/gogs/start.sh
|
||||
directory = /home/git/gogs
|
||||
autostart = true
|
||||
stdout_logfile = /var/gogs.log
|
||||
stderr_logfile = /var/gogs-error.log
|
||||
environment=HOME="/home/git"
|
||||
40
dockerfiles/README.md
Normal file
40
dockerfiles/README.md
Normal file
@@ -0,0 +1,40 @@
|
||||
### Install Gogs With Docker
|
||||
|
||||
Deploying gogs in [Docker](http://www.docker.io/) is just as easy as eating a pie, what you do is just open the `dockerfiles/build.sh` file, replace the configs:
|
||||
|
||||
```
|
||||
DB_TYPE="YOUR_DB_TYPE" # type of database, support 'mysql' and 'postgres'
|
||||
MEM_TYPE="YOUR_MEM_TYPE" # type of memory database, support 'redis' and 'memcache'
|
||||
DB_PASSWORD="YOUR_DB_PASSWORD" # The database password.
|
||||
DB_RUN_NAME="YOUR_DB_RUN_NAME" # The --name option value when run the database image.
|
||||
MEM_RUN_NAME="YOUR_MEM_RUN_NAME" # The --name option value when run the mem database image.
|
||||
HOST_PORT="YOUR_HOST_PORT" # The port on host, which will be redirected to the port 3000 inside gogs container.
|
||||
```
|
||||
|
||||
And run:
|
||||
```
|
||||
cd dockerfiles
|
||||
./build.sh
|
||||
```
|
||||
|
||||
The build might take some time, just be paient. After it finishes, you will receive the message:
|
||||
|
||||
```
|
||||
Now we have the MySQL image(running) and gogs image, use the follow command to start gogs service( the content might be different, according to your own configs):
|
||||
docker run -i -t --link YOUR_DB_RUN_NAME:db --link YOUR_MEM_RUN_NAME:mem -p YOUR_HOST_PORT:3000 gogits/gogs
|
||||
```
|
||||
|
||||
Just follow the message, run:
|
||||
|
||||
```
|
||||
docker run -i -t --link YOUR_DB_RUN_NAME:db --link YOUR_MEM_RUN_NAME:mem -p YOUR_HOST_PORT:3000 gogits/gogs
|
||||
```
|
||||
|
||||
Now we have gogs running! Open the browser and navigate to:
|
||||
|
||||
```
|
||||
http://YOUR_HOST_IP:YOUR_HOST_PORT
|
||||
```
|
||||
|
||||
Let's 'gogs'!
|
||||
Ouya~
|
||||
74
dockerfiles/build.sh
Executable file
74
dockerfiles/build.sh
Executable file
@@ -0,0 +1,74 @@
|
||||
# Configs of the docker images, you might have specify your own configs here.
|
||||
|
||||
DB_TYPE="YOUR_DB_TYPE" # type of database, support 'mysql' and 'postgres'
|
||||
MEM_TYPE="YOUR_MEM_TYPE" # type of memory database, support 'redis' and 'memcache'
|
||||
DB_PASSWORD="YOUR_DB_PASSWORD" # The database password.
|
||||
DB_RUN_NAME="YOUR_DB_RUN_NAME" # The --name option value when run the database image.
|
||||
MEM_RUN_NAME="YOUR_MEM_RUN_NAME" # The --name option value when run the mem database image.
|
||||
HOST_PORT="YOUR_HOST_PORT" # The port on host, which will be redirected to the port 3000 inside gogs container.
|
||||
|
||||
# apt source, you can select 'nchc'(mirror in Taiwan) or 'aliyun'(best for mainlance China users) according to your network, if you could connect to the official unbunt mirror in a fast speed, just leave it to "".
|
||||
APT_SOURCE=""
|
||||
|
||||
DOCKER_BIN=$(which docker.io || which docker)
|
||||
if [ -z "$DOCKER_BIN" ] ; then
|
||||
echo "Please install docker. You can install docker by running \"wget -qO- https://get.docker.io/ | sh\"."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Replace the database root password in database image Dockerfile.
|
||||
sed -i "s/THE_DB_PASSWORD/$DB_PASSWORD/g" images/$DB_TYPE/Dockerfile
|
||||
# Replace the database root password in gogits image deploy.sh file.
|
||||
sed -i "s/THE_DB_PASSWORD/$DB_PASSWORD/g" images/gogits/deploy.sh
|
||||
# Replace the apt source in gogits image Dockerfile.
|
||||
sed -i "s/#$APT_SOURCE#//" images/gogits/Dockerfile
|
||||
# Uncomment the installation of database lib in gogs Dockerfile
|
||||
sed -i "s/#$DB_TYPE#//" images/gogits/Dockerfile
|
||||
# Replace the database type in gogits image deploy.sh file.
|
||||
sed -i "s/THE_DB_TYPE/$DB_TYPE/g" images/gogits/deploy.sh
|
||||
|
||||
if [ $MEM_TYPE != "" ]
|
||||
then
|
||||
# Replace the mem configs in deploy.sh
|
||||
sed -i "s/THE_MEM_TYPE/$MEM_TYPE/g" images/gogits/deploy.sh
|
||||
# Uncomment the installation of go mem lib
|
||||
sed -i "s/#$MEM_TYPE#//" images/gogits/Dockerfile
|
||||
|
||||
# Add the tags when get gogs
|
||||
sed -i "s#RUN go get -u -d github.com/gogits/gogs#RUN go get -u -d -tags $MEM_TYPE github.com/gogits/gogs#g" images/gogits/Dockerfile
|
||||
# Append the tag in gogs build
|
||||
GOGS_BUILD_LINE=`awk '$0 ~ str{print NR}' str="go build" images/gogits/Dockerfile`
|
||||
# Append the build tags
|
||||
sed -i "${GOGS_BUILD_LINE}s/$/ -tags $MEM_TYPE/" images/gogits/Dockerfile
|
||||
|
||||
cd images/$MEM_TYPE
|
||||
$DOCKER_BIN build -t gogits/$MEM_TYPE .
|
||||
$DOCKER_BIN run -d --name $MEM_RUN_NAME gogits/$MEM_TYPE
|
||||
MEM_LINK=" --link $MEM_RUN_NAME:mem "
|
||||
cd ../../
|
||||
fi
|
||||
|
||||
# Build the database image
|
||||
cd images/$DB_TYPE
|
||||
$DOCKER_BIN build -t gogits/$DB_TYPE .
|
||||
#
|
||||
|
||||
|
||||
## Build the gogits image
|
||||
cd ../gogits
|
||||
|
||||
$DOCKER_BIN build -t gogits/gogs .
|
||||
|
||||
#sed -i "s#RUN go get -u -tags $MEM_TYPE github.com/gogits/gogs#RUN go get -u github.com/gogits/gogs#g" Dockerfile
|
||||
|
||||
# Remove the appended tags in go build line(if there is any)
|
||||
sed -i "s/ -tags $MEM_TYPE//" Dockerfile
|
||||
|
||||
#
|
||||
## Run MySQL image with name
|
||||
$DOCKER_BIN run -d --name $DB_RUN_NAME gogits/$DB_TYPE
|
||||
#
|
||||
## Run gogits image and link it to the database image
|
||||
echo "Now we have the $DB_TYPE image(running) and gogs image, use the follow command to start gogs service:"
|
||||
echo -e "\033[33m $DOCKER_BIN run -i -t --link $DB_RUN_NAME:db $MEM_LINK -p $HOST_PORT:3000 gogits/gogs \033[0m"
|
||||
|
||||
32
dockerfiles/images/gogits/Dockerfile
Normal file
32
dockerfiles/images/gogits/Dockerfile
Normal file
@@ -0,0 +1,32 @@
|
||||
FROM stackbrew/ubuntu:13.10
|
||||
MAINTAINER Meaglith Ma <genedna@gmail.com> (@genedna)
|
||||
|
||||
#aliyun#RUN echo "deb http://mirrors.aliyun.com/ubuntu/ saucy main restricted" > /etc/apt/sources.list && echo "deb http://mirrors.aliyun.com/ubuntu/ saucy-updates main restricted" >> /etc/apt/sources.list && echo "deb http://mirrors.aliyun.com/ubuntu/ saucy universe" >> /etc/apt/sources.list && echo "deb http://mirrors.aliyun.com/ubuntu/ saucy-updates universe" >> /etc/apt/sources.list && echo "deb http://mirrors.aliyun.com/ubuntu/ saucy multiverse" >> /etc/apt/sources.list && echo "deb http://mirrors.aliyun.com/ubuntu/ saucy-updates multiverse" >> /etc/apt/sources.list && echo "deb http://mirrors.aliyun.com/ubuntu/ saucy-backports main restricted universe multiverse" >> /etc/apt/sources.list && echo "deb http://mirrors.aliyun.com/ubuntu/ saucy-security main restricted" >> /etc/apt/sources.list && echo "deb http://mirrors.aliyun.com/ubuntu/ saucy-security universe" >> /etc/apt/sources.list && echo "deb http://mirrors.aliyun.com/ubuntu/ saucy-security multiverse" >> /etc/apt/sources.list
|
||||
|
||||
#nchc#RUN echo "deb http://free.nchc.org.tw/ubuntu/ saucy main restricted" >> /etc/apt/source.list && echo "deb-src http://free.nchc.org.tw/ubuntu/ saucy main restricted" >> /etc/apt/source.list && echo "deb http://free.nchc.org.tw/ubuntu/ saucy-updates main restricted" >> /etc/apt/source.list && echo "deb-src http://free.nchc.org.tw/ubuntu/ saucy-updates main restricted" >> /etc/apt/source.list && echo "deb http://free.nchc.org.tw/ubuntu/ saucy universe" >> /etc/apt/source.list && echo "deb-src http://free.nchc.org.tw/ubuntu/ saucy universe" >> /etc/apt/source.list && echo "deb http://free.nchc.org.tw/ubuntu/ saucy-updates universe" >> /etc/apt/source.list && echo "deb-src http://free.nchc.org.tw/ubuntu/ saucy-updates universe" >> /etc/apt/source.list && echo "deb http://free.nchc.org.tw/ubuntu/ saucy multiverse" >> /etc/apt/source.list && echo "deb-src http://free.nchc.org.tw/ubuntu/ saucy multiverse" >> /etc/apt/source.list && echo "deb http://free.nchc.org.tw/ubuntu/ saucy-updates multiverse" >> /etc/apt/source.list && echo "deb-src http://free.nchc.org.tw/ubuntu/ saucy-updates multiverse" >> /etc/apt/source.list && echo "deb http://free.nchc.org.tw/ubuntu/ saucy-backports main restricted universe multiverse" >> /etc/apt/source.list && echo "deb-src http://free.nchc.org.tw/ubuntu/ saucy-backports main restricted universe multiverse" >> /etc/apt/source.list && echo "deb http://free.nchc.org.tw/ubuntu/ saucy-security main restricted" >> /etc/apt/source.list && echo "deb-src http://free.nchc.org.tw/ubuntu/ saucy-security main restricted" >> /etc/apt/source.list && echo "deb http://free.nchc.org.tw/ubuntu/ saucy-security universe" >> /etc/apt/source.list && echo "deb-src http://free.nchc.org.tw/ubuntu/ saucy-security universe" >> /etc/apt/source.list && echo "deb http://free.nchc.org.tw/ubuntu/ saucy-security multiverse" >> /etc/apt/source.list && echo "deb-src http://free.nchc.org.tw/ubuntu/ saucy-security multiverse" >> /etc/apt/source.list && echo "deb http://extras.ubuntu.com/ubuntu saucy main" >> /etc/apt/source.list && echo "deb-src http://extras.ubuntu.com/ubuntu saucy main" >> /etc/apt/source.list
|
||||
|
||||
RUN mkdir -p /go
|
||||
ENV PATH /usr/local/go/bin:/go/bin:$PATH
|
||||
ENV GOROOT /usr/local/go
|
||||
ENV GOPATH /go
|
||||
|
||||
RUN apt-get update && apt-get install --yes --force-yes curl git mercurial zip wget ca-certificates build-essential
|
||||
RUN apt-get install -yq vim sudo
|
||||
|
||||
RUN curl -s http://docker.u.qiniudn.com/go1.2.1.src.tar.gz | tar -v -C /usr/local -xz
|
||||
RUN cd /usr/local/go/src && ./make.bash --no-clean 2>&1
|
||||
|
||||
RUN go get -u -d github.com/gogits/gogs
|
||||
RUN cd $GOPATH/src/github.com/gogits/gogs && git checkout dev && git pull origin dev && go install && go build -tags redis
|
||||
|
||||
# Clean all the unused packages
|
||||
RUN apt-get autoremove -y
|
||||
RUN apt-get clean all
|
||||
|
||||
# Add the deploy script to the docker image and assign execution permission to it.
|
||||
ADD ./deploy.sh /
|
||||
RUN chmod +x deploy.sh
|
||||
|
||||
EXPOSE 3000
|
||||
|
||||
CMD /deploy.sh
|
||||
60
dockerfiles/images/gogits/deploy.sh
Normal file
60
dockerfiles/images/gogits/deploy.sh
Normal file
@@ -0,0 +1,60 @@
|
||||
# deploy.sh in gogits image, replace the configs and run gogs
|
||||
|
||||
## Replace the database password
|
||||
DB_TYPE=THE_DB_TYPE
|
||||
DB_PASSWORD=THE_DB_PASSWORD
|
||||
DB_ALIAS=DB
|
||||
MEM_TYPE=THE_MEM_TYPE
|
||||
|
||||
DB_TYPE_LINE=`awk '$0 ~ str{print NR}' str="DB_TYPE = mysql" $GOPATH/src/github.com/gogits/gogs/conf/app.ini`
|
||||
DB_PASSWORD_LINE=`awk '$0 ~ str{print NR+1}' str="USER = root" $GOPATH/src/github.com/gogits/gogs/conf/app.ini`
|
||||
|
||||
sed -i "${DB_TYPE_LINE}s/.*$/DB_TYPE = $DB_TYPE/g" $GOPATH/src/github.com/gogits/gogs/conf/app.ini
|
||||
sed -i "${DB_PASSWORD_LINE}s/.*$/PASSWD = $DB_PASSWORD/g" $GOPATH/src/github.com/gogits/gogs/conf/app.ini
|
||||
|
||||
|
||||
|
||||
if [ $MEM_TYPE != "" ]
|
||||
then
|
||||
MEM_HOST_LINE=`awk '$0 ~ str{print NR+6}' str="ADAPTER = memory" $GOPATH/src/github.com/gogits/gogs/conf/app.ini`
|
||||
|
||||
_MEM_ADDR=`echo $MEM_PORT | cut -d '/' -f 3 | cut -d ':' -f 1`
|
||||
_MEM_PORT=`echo $MEM_PORT | cut -d '/' -f 3 | cut -d ':' -f 2`
|
||||
|
||||
# take advantage of memory db for adapter and provider
|
||||
sed -i "s/ADAPTER = memory/ADAPTER = $MEM_TYPE/g" $GOPATH/src/github.com/gogits/gogs/conf/app.ini
|
||||
# Comment the memory interval since we don't use 'memory' as adapter
|
||||
sed -i "s/INTERVAL = 60/;INTERVAL = 60/g" $GOPATH/src/github.com/gogits/gogs/conf/app.ini
|
||||
|
||||
|
||||
case $MEM_TYPE in
|
||||
"redis")
|
||||
# Modify the adapter host
|
||||
sed -i "${MEM_HOST_LINE}s/.*/HOST = $_MEM_ADDR:$_MEM_PORT/" $GOPATH/src/github.com/gogits/gogs/conf/app.ini
|
||||
sed -i "s/PROVIDER = file/PROVIDER = $MEM_TYPE/g" $GOPATH/src/github.com/gogits/gogs/conf/app.ini
|
||||
# Modify the provider config
|
||||
sed -i "s#PROVIDER_CONFIG = data/sessions#PROVIDER_CONFIG = $_MEM_ADDR:$_MEM_PORT#g" $GOPATH/src/github.com/gogits/gogs/conf/app.ini
|
||||
;;
|
||||
|
||||
"memcache")
|
||||
# Modify the adapter host
|
||||
sed -i "${MEM_HOST_LINE}s/.*/HOST = $_MEM_ADDR:$_MEM_PORT/" $GOPATH/src/github.com/gogits/gogs/conf/app.ini
|
||||
;;
|
||||
esac
|
||||
|
||||
fi
|
||||
|
||||
|
||||
## Replace the database address and port
|
||||
# When using --link in docker run, the database image's info looks like this:
|
||||
# DB_PORT=tcp://172.17.0.2:3306
|
||||
# DB_PORT_3306_TCP_PORT=3306
|
||||
# DB_PORT_3306_TCP_PROTO=tcp
|
||||
# DB_PORT_3306_TCP_ADDR=172.17.0.2
|
||||
#sed -i "/HOST = 127.0.0.1:3306/c\HOST = $DB_PORT_3306_TCP_ADDR:$DB_PORT_3306_TCP_PORT" $GOPATH/src/github.com/gogits/gogs/conf/app.ini
|
||||
sed -i "/HOST = 127.0.0.1:3306/c\HOST = `echo $DB_PORT | cut -d '/' -f 3`" $GOPATH/src/github.com/gogits/gogs/conf/app.ini
|
||||
|
||||
cd $GOPATH/src/github.com/gogits/gogs/
|
||||
|
||||
# The sudo is a must here, or the go within docker container won't get the current user by os.Getenv("USERNAME")
|
||||
sudo ./gogs web
|
||||
0
dockerfiles/images/memcache/.gitkeep
Normal file
0
dockerfiles/images/memcache/.gitkeep
Normal file
24
dockerfiles/images/memcache/Dockerfile
Normal file
24
dockerfiles/images/memcache/Dockerfile
Normal file
@@ -0,0 +1,24 @@
|
||||
FROM ubuntu
|
||||
|
||||
# Set the file maintainer (your name - the file's author)
|
||||
MAINTAINER Borja Burgos <borja@tutum.co>
|
||||
|
||||
# Update the default application repository sources list
|
||||
RUN apt-get update
|
||||
|
||||
# Install Memcached
|
||||
RUN apt-get install -y memcached
|
||||
|
||||
# Port to expose (default: 11211)
|
||||
EXPOSE 11211
|
||||
|
||||
# Default Memcached run command arguments
|
||||
# Change to limit memory when creating container in Tutum
|
||||
CMD ["-m", "64"]
|
||||
|
||||
# Set the user to run Memcached daemon
|
||||
USER daemon
|
||||
|
||||
# Set the entrypoint to memcached binary
|
||||
ENTRYPOINT memcached
|
||||
|
||||
0
dockerfiles/images/mysql/.gitkeep
Normal file
0
dockerfiles/images/mysql/.gitkeep
Normal file
35
dockerfiles/images/mysql/Dockerfile
Normal file
35
dockerfiles/images/mysql/Dockerfile
Normal file
@@ -0,0 +1,35 @@
|
||||
#FROM stackbrew/ubuntu:13.10
|
||||
#FROM stackbrew/ubuntu
|
||||
FROM stackbrew/ubuntu:saucy
|
||||
MAINTAINER Meaglith Ma <genedna@gmail.com> (@genedna)
|
||||
|
||||
RUN apt-get install -y --force-yes software-properties-common
|
||||
RUN add-apt-repository -y "deb http://archive.ubuntu.com/ubuntu $(lsb_release -sc) universe"
|
||||
RUN apt-get --yes --force-yes update
|
||||
RUN apt-get --yes --force-yes upgrade
|
||||
|
||||
ENV MYSQL_PASSWORD THE_DB_PASSWORD
|
||||
|
||||
RUN echo "mysql-server mysql-server/root_password password $MYSQL_PASSWORD" | debconf-set-selections
|
||||
RUN echo "mysql-server mysql-server/root_password_again password $MYSQL_PASSWORD" | debconf-set-selections
|
||||
|
||||
RUN apt-get update && apt-get install -y --force-yes mysql-server
|
||||
RUN sed -i -e"s/^bind-address\s*=\s*127.0.0.1/bind-address = 0.0.0.0/" /etc/mysql/my.cnf
|
||||
|
||||
RUN service mysql restart
|
||||
|
||||
RUN echo "mysql -uroot -p$MYSQL_PASSWORD -e 'drop database if exists gogs;'" >> import.sh
|
||||
RUN echo "mysql -uroot -p$MYSQL_PASSWORD -e 'create database gogs;'" >> import.sh
|
||||
RUN chmod +x import.sh
|
||||
|
||||
RUN apt-get autoremove -y
|
||||
RUN apt-get clean all
|
||||
|
||||
RUN /usr/sbin/mysqld & \
|
||||
sleep 10s &&\
|
||||
echo "GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' IDENTIFIED BY '$MYSQL_PASSWORD' WITH GRANT OPTION; FLUSH PRIVILEGES" | mysql -p$MYSQL_PASSWORD &&\
|
||||
./import.sh
|
||||
|
||||
EXPOSE 3306
|
||||
|
||||
CMD ["/usr/bin/mysqld_safe", "--skip-syslog", "--log-error=/dev/null"]
|
||||
0
dockerfiles/images/postgres/.gitkeep
Normal file
0
dockerfiles/images/postgres/.gitkeep
Normal file
50
dockerfiles/images/postgres/Dockerfile
Normal file
50
dockerfiles/images/postgres/Dockerfile
Normal file
@@ -0,0 +1,50 @@
|
||||
FROM ubuntu
|
||||
MAINTAINER SvenDowideit@docker.com
|
||||
|
||||
# Add the PostgreSQL PGP key to verify their Debian packages.
|
||||
# It should be the same key as https://www.postgresql.org/media/keys/ACCC4CF8.asc
|
||||
RUN apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys B97B0AFCAA1A47F044F244A07FCC7D46ACCC4CF8
|
||||
|
||||
# Add PostgreSQL's repository. It contains the most recent stable release
|
||||
# of PostgreSQL, ``9.3``.
|
||||
# See http://apt.postgresql.org/pub/repos/apt/README
|
||||
RUN echo "deb http://apt.postgresql.org/pub/repos/apt/ $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list
|
||||
|
||||
# Update the Ubuntu and PostgreSQL repository indexes
|
||||
RUN apt-get update
|
||||
|
||||
# Install ``python-software-properties``, ``software-properties-common`` and PostgreSQL 9.3
|
||||
# There are some warnings (in red) that show up during the build. You can hide
|
||||
# them by prefixing each apt-get statement with DEBIAN_FRONTEND=noninteractive
|
||||
RUN apt-get -y -q install python-software-properties software-properties-common
|
||||
RUN apt-get -y -q install postgresql-9.3 postgresql-client-9.3 postgresql-contrib-9.3
|
||||
|
||||
# Note: The official Debian and Ubuntu images automatically ``apt-get clean``
|
||||
# after each ``apt-get``
|
||||
|
||||
# Run the rest of the commands as the ``postgres`` user created by the ``postgres-9.3`` package when it was ``apt-get installed``
|
||||
USER postgres
|
||||
|
||||
# Create a PostgreSQL role named ``docker`` with ``docker`` as the password and
|
||||
# then create a database `docker` owned by the ``docker`` role.
|
||||
# Note: here we use ``&&\`` to run commands one after the other - the ``\``
|
||||
# allows the RUN command to span multiple lines.
|
||||
RUN /etc/init.d/postgresql start &&\
|
||||
psql --command "CREATE USER root WITH SUPERUSER PASSWORD 'THE_DB_PASSWORD';" &&\
|
||||
createdb -O root gogs
|
||||
|
||||
# Adjust PostgreSQL configuration so that remote connections to the
|
||||
# database are possible.
|
||||
RUN echo "host all all 0.0.0.0/0 md5" >> /etc/postgresql/9.3/main/pg_hba.conf
|
||||
|
||||
# And add ``listen_addresses`` to ``/etc/postgresql/9.3/main/postgresql.conf``
|
||||
RUN echo "listen_addresses='*'" >> /etc/postgresql/9.3/main/postgresql.conf
|
||||
|
||||
# Expose the PostgreSQL port
|
||||
EXPOSE 5432
|
||||
|
||||
# Add VOLUMEs to allow backup of config, logs and databases
|
||||
VOLUME ["/etc/postgresql", "/var/log/postgresql", "/var/lib/postgresql"]
|
||||
|
||||
# Set the default command to run when starting the container
|
||||
CMD ["/usr/lib/postgresql/9.3/bin/postgres", "-D", "/var/lib/postgresql/9.3/main", "-c", "config_file=/etc/postgresql/9.3/main/postgresql.conf"]
|
||||
0
dockerfiles/images/redis/.gitkeep
Normal file
0
dockerfiles/images/redis/.gitkeep
Normal file
10
dockerfiles/images/redis/Dockerfile
Normal file
10
dockerfiles/images/redis/Dockerfile
Normal file
@@ -0,0 +1,10 @@
|
||||
FROM stackbrew/ubuntu:saucy
|
||||
MAINTAINER Meaglith Ma <genedna@gmail.com> (@genedna), Lance Ju <juzhenatpku@gmail.com> (@crystaldust)
|
||||
|
||||
RUN apt-get update && apt-get install -y redis-server
|
||||
# Usually redis doesn't need a password
|
||||
#RUN sed -i "s/# requirepass foobared/requirepass THE_REDIS_PASSWORD/g" /etc/redis/redis.conf
|
||||
EXPOSE 6379
|
||||
ENTRYPOINT ["/usr/bin/redis-server"]
|
||||
CMD ["--bind", "0.0.0.0"]
|
||||
|
||||
19
dockerfiles/run.sh
Executable file
19
dockerfiles/run.sh
Executable file
@@ -0,0 +1,19 @@
|
||||
# Configs
|
||||
MYSQL_PASSWORD="kuajie8402"
|
||||
MYSQL_RUN_NAME="gogs_mysql"
|
||||
typeset -u MYSQL_ALIAS
|
||||
MYSQL_ALIAS="db"
|
||||
HOST_PORT="3000"
|
||||
|
||||
DOCKER_BIN=$(which docker.io || which docker)
|
||||
if [ -z "$DOCKER_BIN" ] ; then
|
||||
echo "Please install docker. You can install docker by running \"wget -qO- https://get.docker.io/ | sh\"."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
## Run MySQL image with name
|
||||
$DOCKER_BIN run -d --name $MYSQL_RUN_NAME gogs/mysql
|
||||
#
|
||||
## Run gogits image and link it to the MySQL image
|
||||
$DOCKER_BIN run --link $MYSQL_RUN_NAME:$MYSQL_ALIAS -p $HOST_PORT:3000 gogs/gogits
|
||||
|
||||
22
gogs.go
22
gogs.go
@@ -1,3 +1,5 @@
|
||||
// +build go1.2
|
||||
|
||||
// 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.
|
||||
@@ -11,19 +13,15 @@ import (
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
|
||||
"github.com/gogits/gogs/modules/base"
|
||||
"github.com/gogits/gogs/cmd"
|
||||
"github.com/gogits/gogs/modules/setting"
|
||||
)
|
||||
|
||||
// +build go1.2
|
||||
|
||||
// Test that go1.2 tag above is included in builds. main.go refers to this definition.
|
||||
const go12tag = true
|
||||
|
||||
const APP_VER = "0.2.0.0330 Alpha"
|
||||
const APP_VER = "0.4.2.0605 Alpha"
|
||||
|
||||
func init() {
|
||||
base.AppVer = APP_VER
|
||||
runtime.GOMAXPROCS(runtime.NumCPU())
|
||||
setting.AppVer = APP_VER
|
||||
}
|
||||
|
||||
func main() {
|
||||
@@ -32,9 +30,11 @@ func main() {
|
||||
app.Usage = "Go Git Service"
|
||||
app.Version = APP_VER
|
||||
app.Commands = []cli.Command{
|
||||
CmdWeb,
|
||||
CmdServ,
|
||||
CmdUpdate,
|
||||
cmd.CmdWeb,
|
||||
// cmd.CmdFix,
|
||||
cmd.CmdDump,
|
||||
cmd.CmdServ,
|
||||
cmd.CmdUpdate,
|
||||
}
|
||||
app.Flags = append(app.Flags, []cli.Flag{}...)
|
||||
app.Run(os.Args)
|
||||
|
||||
42
gogs_supervisord.sh
Executable file
42
gogs_supervisord.sh
Executable file
@@ -0,0 +1,42 @@
|
||||
#!/bin/sh
|
||||
|
||||
echo 'plase remember to modify the command path in etc/conf/supervisord.conf(line 23)'
|
||||
|
||||
PID="/tmp/supervisord.pid"
|
||||
CONF="conf/etc/supervisord.conf"
|
||||
|
||||
LOGDIR="log"
|
||||
if [ ! -d $LOGDIR ]; then
|
||||
mkdir $LOGDIR
|
||||
fi
|
||||
|
||||
stop() {
|
||||
if [ -f $PID ]; then
|
||||
kill `cat -- $PID`
|
||||
rm -f -- $PID
|
||||
echo "stopped"
|
||||
fi
|
||||
}
|
||||
|
||||
start() {
|
||||
echo "starting"
|
||||
if [ ! -f $PID ]; then
|
||||
supervisord -c $CONF
|
||||
echo "started"
|
||||
fi
|
||||
}
|
||||
|
||||
case "$1" in
|
||||
start)
|
||||
start
|
||||
;;
|
||||
stop)
|
||||
stop
|
||||
;;
|
||||
restart)
|
||||
stop
|
||||
start
|
||||
;;
|
||||
*)
|
||||
echo "Usage: $0 {start|stop|restart}"
|
||||
esac
|
||||
@@ -7,6 +7,8 @@ package models
|
||||
import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-xorm/xorm"
|
||||
)
|
||||
|
||||
// Access types.
|
||||
@@ -19,7 +21,7 @@ const (
|
||||
type Access struct {
|
||||
Id int64
|
||||
UserName string `xorm:"unique(s)"`
|
||||
RepoName string `xorm:"unique(s)"`
|
||||
RepoName string `xorm:"unique(s)"` // <user name>/<repo name>
|
||||
Mode int `xorm:"unique(s)"`
|
||||
Created time.Time `xorm:"created"`
|
||||
}
|
||||
@@ -32,12 +34,46 @@ func AddAccess(access *Access) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// HasAccess returns true if someone can read or write to given repository.
|
||||
func HasAccess(userName, repoName string, mode int) (bool, error) {
|
||||
return orm.Get(&Access{
|
||||
Id: 0,
|
||||
UserName: strings.ToLower(userName),
|
||||
RepoName: strings.ToLower(repoName),
|
||||
Mode: mode,
|
||||
})
|
||||
// UpdateAccess updates access information.
|
||||
func UpdateAccess(access *Access) error {
|
||||
access.UserName = strings.ToLower(access.UserName)
|
||||
access.RepoName = strings.ToLower(access.RepoName)
|
||||
_, err := orm.Id(access.Id).Update(access)
|
||||
return err
|
||||
}
|
||||
|
||||
// DeleteAccess deletes access record.
|
||||
func DeleteAccess(access *Access) error {
|
||||
_, err := orm.Delete(access)
|
||||
return err
|
||||
}
|
||||
|
||||
// UpdateAccess updates access information with session for rolling back.
|
||||
func UpdateAccessWithSession(sess *xorm.Session, access *Access) error {
|
||||
if _, err := sess.Id(access.Id).Update(access); err != nil {
|
||||
sess.Rollback()
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// HasAccess returns true if someone can read or write to given repository.
|
||||
// The repoName should be in format <username>/<reponame>.
|
||||
func HasAccess(uname, repoName string, mode int) (bool, error) {
|
||||
if len(repoName) == 0 {
|
||||
return false, nil
|
||||
}
|
||||
access := &Access{
|
||||
UserName: strings.ToLower(uname),
|
||||
RepoName: strings.ToLower(repoName),
|
||||
}
|
||||
has, err := orm.Get(access)
|
||||
if err != nil {
|
||||
return false, err
|
||||
} else if !has {
|
||||
return false, nil
|
||||
} else if mode > access.Mode {
|
||||
return false, nil
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
158
models/action.go
158
models/action.go
@@ -6,10 +6,18 @@ package models
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gogits/git"
|
||||
qlog "github.com/qiniu/log"
|
||||
|
||||
"github.com/gogits/gogs/modules/base"
|
||||
"github.com/gogits/gogs/modules/hooks"
|
||||
"github.com/gogits/gogs/modules/log"
|
||||
"github.com/gogits/gogs/modules/setting"
|
||||
)
|
||||
|
||||
// Operation types of user action.
|
||||
@@ -21,22 +29,27 @@ const (
|
||||
OP_COMMIT_REPO
|
||||
OP_CREATE_ISSUE
|
||||
OP_PULL_REQUEST
|
||||
OP_TRANSFER_REPO
|
||||
OP_PUSH_TAG
|
||||
OP_COMMENT_ISSUE
|
||||
)
|
||||
|
||||
// Action represents user operation type and other information to repository.,
|
||||
// it implemented interface base.Actioner so that can be used in template render.
|
||||
type Action struct {
|
||||
Id int64
|
||||
UserId int64 // Receiver user id.
|
||||
OpType int // Operations: CREATE DELETE STAR ...
|
||||
ActUserId int64 // Action user id.
|
||||
ActUserName string // Action user name.
|
||||
ActEmail string
|
||||
RepoId int64
|
||||
RepoName string
|
||||
RefName string
|
||||
Content string `xorm:"TEXT"`
|
||||
Created time.Time `xorm:"created"`
|
||||
Id int64
|
||||
UserId int64 // Receiver user id.
|
||||
OpType int
|
||||
ActUserId int64 // Action user id.
|
||||
ActUserName string // Action user name.
|
||||
ActEmail string
|
||||
RepoId int64
|
||||
RepoUserName string
|
||||
RepoName string
|
||||
RefName string
|
||||
IsPrivate bool `xorm:"NOT NULL DEFAULT false"`
|
||||
Content string `xorm:"TEXT"`
|
||||
Created time.Time `xorm:"created"`
|
||||
}
|
||||
|
||||
func (a Action) GetOpType() int {
|
||||
@@ -51,6 +64,10 @@ func (a Action) GetActEmail() string {
|
||||
return a.ActEmail
|
||||
}
|
||||
|
||||
func (a Action) GetRepoUserName() string {
|
||||
return a.RepoUserName
|
||||
}
|
||||
|
||||
func (a Action) GetRepoName() string {
|
||||
return a.RepoName
|
||||
}
|
||||
@@ -64,42 +81,106 @@ func (a Action) GetContent() string {
|
||||
}
|
||||
|
||||
// CommitRepoAction adds new action for committing repository.
|
||||
func CommitRepoAction(userId int64, userName, actEmail string,
|
||||
repoId int64, repoName string, refName string, commit *base.PushCommits) error {
|
||||
log.Trace("action.CommitRepoAction(start): %d/%s", userId, repoName)
|
||||
func CommitRepoAction(userId, repoUserId int64, userName, actEmail string,
|
||||
repoId int64, repoUserName, repoName string, refFullName string, commit *base.PushCommits) error {
|
||||
// log.Trace("action.CommitRepoAction(start): %d/%s", userId, repoName)
|
||||
|
||||
opType := OP_COMMIT_REPO
|
||||
// Check it's tag push or branch.
|
||||
if strings.HasPrefix(refFullName, "refs/tags/") {
|
||||
opType = OP_PUSH_TAG
|
||||
commit = &base.PushCommits{}
|
||||
}
|
||||
|
||||
refName := git.RefEndName(refFullName)
|
||||
|
||||
bs, err := json.Marshal(commit)
|
||||
if err != nil {
|
||||
log.Error("action.CommitRepoAction(json): %d/%s", userId, repoName)
|
||||
return err
|
||||
}
|
||||
|
||||
if err = NotifyWatchers(&Action{ActUserId: userId, ActUserName: userName, ActEmail: actEmail,
|
||||
OpType: OP_COMMIT_REPO, Content: string(bs), RepoId: repoId, RepoName: repoName, RefName: refName}); err != nil {
|
||||
log.Error("action.CommitRepoAction(notify watchers): %d/%s", userId, repoName)
|
||||
return err
|
||||
return errors.New("action.CommitRepoAction(json): " + err.Error())
|
||||
}
|
||||
|
||||
// Change repository bare status and update last updated time.
|
||||
repo, err := GetRepositoryByName(userId, repoName)
|
||||
repo, err := GetRepositoryByName(repoUserId, repoName)
|
||||
if err != nil {
|
||||
log.Error("action.CommitRepoAction(GetRepositoryByName): %d/%s", userId, repoName)
|
||||
return err
|
||||
return errors.New("action.CommitRepoAction(GetRepositoryByName): " + err.Error())
|
||||
}
|
||||
repo.IsBare = false
|
||||
if err = UpdateRepository(repo); err != nil {
|
||||
log.Error("action.CommitRepoAction(UpdateRepository): %d/%s", userId, repoName)
|
||||
return err
|
||||
return errors.New("action.CommitRepoAction(UpdateRepository): " + err.Error())
|
||||
}
|
||||
|
||||
log.Trace("action.CommitRepoAction(end): %d/%s", userId, repoName)
|
||||
if err = NotifyWatchers(&Action{ActUserId: userId, ActUserName: userName, ActEmail: actEmail,
|
||||
OpType: opType, Content: string(bs), RepoId: repoId, RepoUserName: repoUserName,
|
||||
RepoName: repoName, RefName: refName,
|
||||
IsPrivate: repo.IsPrivate}); err != nil {
|
||||
return errors.New("action.CommitRepoAction(NotifyWatchers): " + err.Error())
|
||||
|
||||
}
|
||||
qlog.Info("action.CommitRepoAction(end): %d/%s", repoUserId, repoName)
|
||||
|
||||
// New push event hook.
|
||||
if err := repo.GetOwner(); err != nil {
|
||||
return errors.New("action.CommitRepoAction(GetOwner): " + err.Error())
|
||||
}
|
||||
|
||||
ws, err := GetActiveWebhooksByRepoId(repoId)
|
||||
if err != nil {
|
||||
return errors.New("action.CommitRepoAction(GetWebhooksByRepoId): " + err.Error())
|
||||
} else if len(ws) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
repoLink := fmt.Sprintf("%s%s/%s", setting.AppUrl, repoUserName, repoName)
|
||||
commits := make([]*hooks.PayloadCommit, len(commit.Commits))
|
||||
for i, cmt := range commit.Commits {
|
||||
commits[i] = &hooks.PayloadCommit{
|
||||
Id: cmt.Sha1,
|
||||
Message: cmt.Message,
|
||||
Url: fmt.Sprintf("%s/commit/%s", repoLink, cmt.Sha1),
|
||||
Author: &hooks.PayloadAuthor{
|
||||
Name: cmt.AuthorName,
|
||||
Email: cmt.AuthorEmail,
|
||||
},
|
||||
}
|
||||
}
|
||||
p := &hooks.Payload{
|
||||
Ref: refFullName,
|
||||
Commits: commits,
|
||||
Repo: &hooks.PayloadRepo{
|
||||
Id: repo.Id,
|
||||
Name: repo.LowerName,
|
||||
Url: repoLink,
|
||||
Description: repo.Description,
|
||||
Website: repo.Website,
|
||||
Watchers: repo.NumWatches,
|
||||
Owner: &hooks.PayloadAuthor{
|
||||
Name: repoUserName,
|
||||
Email: actEmail,
|
||||
},
|
||||
Private: repo.IsPrivate,
|
||||
},
|
||||
Pusher: &hooks.PayloadAuthor{
|
||||
Name: repo.Owner.LowerName,
|
||||
Email: repo.Owner.Email,
|
||||
},
|
||||
}
|
||||
|
||||
for _, w := range ws {
|
||||
w.GetEvent()
|
||||
if !w.HasPushEvent() {
|
||||
continue
|
||||
}
|
||||
|
||||
p.Secret = w.Secret
|
||||
hooks.AddHookTask(&hooks.HookTask{hooks.HTT_WEBHOOK, w.Url, p, w.ContentType, w.IsSsl})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewRepoAction adds new action for creating repository.
|
||||
func NewRepoAction(user *User, repo *Repository) (err error) {
|
||||
if err = NotifyWatchers(&Action{ActUserId: user.Id, ActUserName: user.Name, ActEmail: user.Email,
|
||||
OpType: OP_CREATE_REPO, RepoId: repo.Id, RepoName: repo.Name}); err != nil {
|
||||
OpType: OP_CREATE_REPO, RepoId: repo.Id, RepoName: repo.Name, IsPrivate: repo.IsPrivate}); err != nil {
|
||||
log.Error("action.NewRepoAction(notify watchers): %d/%s", user.Id, repo.Name)
|
||||
return err
|
||||
}
|
||||
@@ -108,12 +189,25 @@ func NewRepoAction(user *User, repo *Repository) (err error) {
|
||||
return err
|
||||
}
|
||||
|
||||
// TransferRepoAction adds new action for transfering repository.
|
||||
func TransferRepoAction(user, newUser *User, repo *Repository) (err error) {
|
||||
if err = NotifyWatchers(&Action{ActUserId: user.Id, ActUserName: user.Name, ActEmail: user.Email,
|
||||
OpType: OP_TRANSFER_REPO, RepoId: repo.Id, RepoName: repo.Name, Content: newUser.Name,
|
||||
IsPrivate: repo.IsPrivate}); err != nil {
|
||||
log.Error("action.TransferRepoAction(notify watchers): %d/%s", user.Id, repo.Name)
|
||||
return err
|
||||
}
|
||||
|
||||
log.Trace("action.TransferRepoAction: %s/%s", user.LowerName, repo.LowerName)
|
||||
return err
|
||||
}
|
||||
|
||||
// GetFeeds returns action list of given user in given context.
|
||||
func GetFeeds(userid, offset int64, isProfile bool) ([]Action, error) {
|
||||
actions := make([]Action, 0, 20)
|
||||
func GetFeeds(userid, offset int64, isProfile bool) ([]*Action, error) {
|
||||
actions := make([]*Action, 0, 20)
|
||||
sess := orm.Limit(20, int(offset)).Desc("id").Where("user_id=?", userid)
|
||||
if isProfile {
|
||||
sess.And("act_user_id=?", userid)
|
||||
sess.Where("is_private=?", false).And("act_user_id=?", userid)
|
||||
} else {
|
||||
sess.And("act_user_id!=?", userid)
|
||||
}
|
||||
|
||||
6
models/fix.go
Normal file
6
models/fix.go
Normal file
@@ -0,0 +1,6 @@
|
||||
package models
|
||||
|
||||
func Fix() error {
|
||||
_, err := orm.Exec("alter table repository drop column num_releases")
|
||||
return err
|
||||
}
|
||||
445
models/git.go
445
models/git.go
@@ -1,445 +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 (
|
||||
"bufio"
|
||||
"container/list"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/gogits/git"
|
||||
|
||||
"github.com/gogits/gogs/modules/base"
|
||||
)
|
||||
|
||||
// RepoFile represents a file object in git repository.
|
||||
type RepoFile struct {
|
||||
*git.TreeEntry
|
||||
Path string
|
||||
Size int64
|
||||
Repo *git.Repository
|
||||
Commit *git.Commit
|
||||
}
|
||||
|
||||
// LookupBlob returns the content of an object.
|
||||
func (file *RepoFile) LookupBlob() (*git.Blob, error) {
|
||||
if file.Repo == nil {
|
||||
return nil, ErrRepoFileNotLoaded
|
||||
}
|
||||
|
||||
return file.Repo.LookupBlob(file.Id)
|
||||
}
|
||||
|
||||
// GetBranches returns all branches of given repository.
|
||||
func GetBranches(userName, repoName string) ([]string, error) {
|
||||
repo, err := git.OpenRepository(RepoPath(userName, repoName))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
refs, err := repo.AllReferences()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
brs := make([]string, len(refs))
|
||||
for i, ref := range refs {
|
||||
brs[i] = ref.BranchName()
|
||||
}
|
||||
return brs, nil
|
||||
}
|
||||
|
||||
func IsBranchExist(userName, repoName, branchName string) bool {
|
||||
repo, err := git.OpenRepository(RepoPath(userName, repoName))
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return repo.IsBranchExist(branchName)
|
||||
}
|
||||
|
||||
func GetTargetFile(userName, repoName, branchName, commitId, rpath string) (*RepoFile, error) {
|
||||
repo, err := git.OpenRepository(RepoPath(userName, repoName))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
commit, err := repo.GetCommitOfBranch(branchName)
|
||||
if err != nil {
|
||||
commit, err = repo.GetCommit(commitId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
parts := strings.Split(path.Clean(rpath), "/")
|
||||
|
||||
var entry *git.TreeEntry
|
||||
tree := commit.Tree
|
||||
for i, part := range parts {
|
||||
if i == len(parts)-1 {
|
||||
entry = tree.EntryByName(part)
|
||||
if entry == nil {
|
||||
return nil, ErrRepoFileNotExist
|
||||
}
|
||||
} else {
|
||||
tree, err = repo.SubTree(tree, part)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
size, err := repo.ObjectSize(entry.Id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
repoFile := &RepoFile{
|
||||
entry,
|
||||
rpath,
|
||||
size,
|
||||
repo,
|
||||
commit,
|
||||
}
|
||||
|
||||
return repoFile, nil
|
||||
}
|
||||
|
||||
// GetReposFiles returns a list of file object in given directory of repository.
|
||||
// func GetReposFilesOfBranch(userName, repoName, branchName, rpath string) ([]*RepoFile, error) {
|
||||
// return getReposFiles(userName, repoName, commitId, rpath)
|
||||
// }
|
||||
|
||||
// GetReposFiles returns a list of file object in given directory of repository.
|
||||
func GetReposFiles(userName, repoName, commitId, rpath string) ([]*RepoFile, error) {
|
||||
return getReposFiles(userName, repoName, commitId, rpath)
|
||||
}
|
||||
|
||||
func getReposFiles(userName, repoName, commitId string, rpath string) ([]*RepoFile, error) {
|
||||
repo, err := git.OpenRepository(RepoPath(userName, repoName))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
commit, err := repo.GetCommit(commitId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var repodirs []*RepoFile
|
||||
var repofiles []*RepoFile
|
||||
commit.Tree.Walk(func(dirname string, entry *git.TreeEntry) int {
|
||||
if dirname == rpath {
|
||||
// TODO: size get method shoule be improved
|
||||
size, err := repo.ObjectSize(entry.Id)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
var cm = commit
|
||||
var i int
|
||||
for {
|
||||
i = i + 1
|
||||
//fmt.Println(".....", i, cm.Id(), cm.ParentCount())
|
||||
if cm.ParentCount() == 0 {
|
||||
break
|
||||
} else if cm.ParentCount() == 1 {
|
||||
pt, _ := repo.SubTree(cm.Parent(0).Tree, dirname)
|
||||
if pt == nil {
|
||||
break
|
||||
}
|
||||
pEntry := pt.EntryByName(entry.Name)
|
||||
if pEntry == nil || !pEntry.Id.Equal(entry.Id) {
|
||||
break
|
||||
} else {
|
||||
cm = cm.Parent(0)
|
||||
}
|
||||
} else {
|
||||
var emptyCnt = 0
|
||||
var sameIdcnt = 0
|
||||
var lastSameCm *git.Commit
|
||||
//fmt.Println(".....", cm.ParentCount())
|
||||
for i := 0; i < cm.ParentCount(); i++ {
|
||||
//fmt.Println("parent", i, cm.Parent(i).Id())
|
||||
p := cm.Parent(i)
|
||||
pt, _ := repo.SubTree(p.Tree, dirname)
|
||||
var pEntry *git.TreeEntry
|
||||
if pt != nil {
|
||||
pEntry = pt.EntryByName(entry.Name)
|
||||
}
|
||||
|
||||
//fmt.Println("pEntry", pEntry)
|
||||
|
||||
if pEntry == nil {
|
||||
emptyCnt = emptyCnt + 1
|
||||
if emptyCnt+sameIdcnt == cm.ParentCount() {
|
||||
if lastSameCm == nil {
|
||||
goto loop
|
||||
} else {
|
||||
cm = lastSameCm
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
//fmt.Println(i, "pEntry", pEntry.Id, "entry", entry.Id)
|
||||
if !pEntry.Id.Equal(entry.Id) {
|
||||
goto loop
|
||||
} else {
|
||||
lastSameCm = cm.Parent(i)
|
||||
sameIdcnt = sameIdcnt + 1
|
||||
if emptyCnt+sameIdcnt == cm.ParentCount() {
|
||||
// TODO: now follow the first parent commit?
|
||||
cm = lastSameCm
|
||||
//fmt.Println("sameId...")
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
loop:
|
||||
|
||||
rp := &RepoFile{
|
||||
entry,
|
||||
path.Join(dirname, entry.Name),
|
||||
size,
|
||||
repo,
|
||||
cm,
|
||||
}
|
||||
|
||||
if entry.IsFile() {
|
||||
repofiles = append(repofiles, rp)
|
||||
} else if entry.IsDir() {
|
||||
repodirs = append(repodirs, rp)
|
||||
}
|
||||
}
|
||||
return 0
|
||||
})
|
||||
|
||||
return append(repodirs, repofiles...), nil
|
||||
}
|
||||
|
||||
func GetCommit(userName, repoName, commitId string) (*git.Commit, error) {
|
||||
repo, err := git.OpenRepository(RepoPath(userName, repoName))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return repo.GetCommit(commitId)
|
||||
}
|
||||
|
||||
// GetCommitsByBranch returns all commits of given branch of repository.
|
||||
func GetCommitsByBranch(userName, repoName, branchName string) (*list.List, error) {
|
||||
repo, err := git.OpenRepository(RepoPath(userName, repoName))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r, err := repo.LookupReference(fmt.Sprintf("refs/heads/%s", branchName))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return r.AllCommits()
|
||||
}
|
||||
|
||||
// GetCommitsByCommitId returns all commits of given commitId of repository.
|
||||
func GetCommitsByCommitId(userName, repoName, commitId string) (*list.List, error) {
|
||||
repo, err := git.OpenRepository(RepoPath(userName, repoName))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
oid, err := git.NewOidFromString(commitId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return repo.CommitsBefore(oid)
|
||||
}
|
||||
|
||||
// Diff line types.
|
||||
const (
|
||||
DIFF_LINE_PLAIN = iota + 1
|
||||
DIFF_LINE_ADD
|
||||
DIFF_LINE_DEL
|
||||
DIFF_LINE_SECTION
|
||||
)
|
||||
|
||||
const (
|
||||
DIFF_FILE_ADD = iota + 1
|
||||
DIFF_FILE_CHANGE
|
||||
DIFF_FILE_DEL
|
||||
)
|
||||
|
||||
type DiffLine struct {
|
||||
LeftIdx int
|
||||
RightIdx int
|
||||
Type int
|
||||
Content string
|
||||
}
|
||||
|
||||
func (d DiffLine) GetType() int {
|
||||
return d.Type
|
||||
}
|
||||
|
||||
type DiffSection struct {
|
||||
Name string
|
||||
Lines []*DiffLine
|
||||
}
|
||||
|
||||
type DiffFile struct {
|
||||
Name string
|
||||
Addition, Deletion int
|
||||
Type int
|
||||
Sections []*DiffSection
|
||||
}
|
||||
|
||||
type Diff struct {
|
||||
TotalAddition, TotalDeletion int
|
||||
Files []*DiffFile
|
||||
}
|
||||
|
||||
func (diff *Diff) NumFiles() int {
|
||||
return len(diff.Files)
|
||||
}
|
||||
|
||||
const DIFF_HEAD = "diff --git "
|
||||
|
||||
func ParsePatch(reader io.Reader) (*Diff, error) {
|
||||
scanner := bufio.NewScanner(reader)
|
||||
var (
|
||||
curFile *DiffFile
|
||||
curSection = &DiffSection{
|
||||
Lines: make([]*DiffLine, 0, 10),
|
||||
}
|
||||
|
||||
leftLine, rightLine int
|
||||
)
|
||||
|
||||
diff := &Diff{Files: make([]*DiffFile, 0)}
|
||||
var i int
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
// fmt.Println(i, line)
|
||||
if strings.HasPrefix(line, "+++ ") || strings.HasPrefix(line, "--- ") {
|
||||
continue
|
||||
}
|
||||
|
||||
i = i + 1
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
if line[0] == ' ' {
|
||||
diffLine := &DiffLine{Type: DIFF_LINE_PLAIN, Content: line, LeftIdx: leftLine, RightIdx: rightLine}
|
||||
leftLine++
|
||||
rightLine++
|
||||
curSection.Lines = append(curSection.Lines, diffLine)
|
||||
continue
|
||||
} else if line[0] == '@' {
|
||||
curSection = &DiffSection{}
|
||||
curFile.Sections = append(curFile.Sections, curSection)
|
||||
ss := strings.Split(line, "@@")
|
||||
diffLine := &DiffLine{Type: DIFF_LINE_SECTION, Content: line}
|
||||
curSection.Lines = append(curSection.Lines, diffLine)
|
||||
|
||||
// Parse line number.
|
||||
ranges := strings.Split(ss[len(ss)-2][1:], " ")
|
||||
leftLine, _ = base.StrTo(strings.Split(ranges[0], ",")[0][1:]).Int()
|
||||
rightLine, _ = base.StrTo(strings.Split(ranges[1], ",")[0]).Int()
|
||||
continue
|
||||
} else if line[0] == '+' {
|
||||
curFile.Addition++
|
||||
diff.TotalAddition++
|
||||
diffLine := &DiffLine{Type: DIFF_LINE_ADD, Content: line, RightIdx: rightLine}
|
||||
rightLine++
|
||||
curSection.Lines = append(curSection.Lines, diffLine)
|
||||
continue
|
||||
} else if line[0] == '-' {
|
||||
curFile.Deletion++
|
||||
diff.TotalDeletion++
|
||||
diffLine := &DiffLine{Type: DIFF_LINE_DEL, Content: line, LeftIdx: leftLine}
|
||||
if leftLine > 0 {
|
||||
leftLine++
|
||||
}
|
||||
curSection.Lines = append(curSection.Lines, diffLine)
|
||||
continue
|
||||
}
|
||||
|
||||
// Get new file.
|
||||
if strings.HasPrefix(line, DIFF_HEAD) {
|
||||
fs := strings.Split(line[len(DIFF_HEAD):], " ")
|
||||
a := fs[0]
|
||||
|
||||
curFile = &DiffFile{
|
||||
Name: a[strings.Index(a, "/")+1:],
|
||||
Type: DIFF_FILE_CHANGE,
|
||||
Sections: make([]*DiffSection, 0, 10),
|
||||
}
|
||||
diff.Files = append(diff.Files, curFile)
|
||||
|
||||
// Check file diff type.
|
||||
for scanner.Scan() {
|
||||
switch {
|
||||
case strings.HasPrefix(scanner.Text(), "new file"):
|
||||
curFile.Type = DIFF_FILE_ADD
|
||||
case strings.HasPrefix(scanner.Text(), "deleted"):
|
||||
curFile.Type = DIFF_FILE_DEL
|
||||
case strings.HasPrefix(scanner.Text(), "index"):
|
||||
curFile.Type = DIFF_FILE_CHANGE
|
||||
}
|
||||
if curFile.Type > 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return diff, nil
|
||||
}
|
||||
|
||||
func GetDiff(repoPath, commitid string) (*Diff, error) {
|
||||
repo, err := git.OpenRepository(repoPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
commit, err := repo.GetCommit(commitid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// First commit of repository.
|
||||
if commit.ParentCount() == 0 {
|
||||
rd, wr := io.Pipe()
|
||||
go func() {
|
||||
cmd := exec.Command("git", "show", commitid)
|
||||
cmd.Dir = repoPath
|
||||
cmd.Stdout = wr
|
||||
cmd.Stdin = os.Stdin
|
||||
cmd.Stderr = os.Stderr
|
||||
cmd.Run()
|
||||
wr.Close()
|
||||
}()
|
||||
defer rd.Close()
|
||||
return ParsePatch(rd)
|
||||
}
|
||||
|
||||
rd, wr := io.Pipe()
|
||||
go func() {
|
||||
cmd := exec.Command("git", "diff", commit.Parent(0).Oid.String(), commitid)
|
||||
cmd.Dir = repoPath
|
||||
cmd.Stdout = wr
|
||||
cmd.Stdin = os.Stdin
|
||||
cmd.Stderr = os.Stderr
|
||||
cmd.Run()
|
||||
wr.Close()
|
||||
}()
|
||||
defer rd.Close()
|
||||
return ParsePatch(rd)
|
||||
}
|
||||
211
models/git_diff.go
Normal file
211
models/git_diff.go
Normal file
@@ -0,0 +1,211 @@
|
||||
// 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 (
|
||||
"bufio"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
|
||||
"github.com/gogits/git"
|
||||
|
||||
"github.com/gogits/gogs/modules/base"
|
||||
"github.com/gogits/gogs/modules/log"
|
||||
)
|
||||
|
||||
// Diff line types.
|
||||
const (
|
||||
DIFF_LINE_PLAIN = iota + 1
|
||||
DIFF_LINE_ADD
|
||||
DIFF_LINE_DEL
|
||||
DIFF_LINE_SECTION
|
||||
)
|
||||
|
||||
const (
|
||||
DIFF_FILE_ADD = iota + 1
|
||||
DIFF_FILE_CHANGE
|
||||
DIFF_FILE_DEL
|
||||
)
|
||||
|
||||
type DiffLine struct {
|
||||
LeftIdx int
|
||||
RightIdx int
|
||||
Type int
|
||||
Content string
|
||||
}
|
||||
|
||||
func (d DiffLine) GetType() int {
|
||||
return d.Type
|
||||
}
|
||||
|
||||
type DiffSection struct {
|
||||
Name string
|
||||
Lines []*DiffLine
|
||||
}
|
||||
|
||||
type DiffFile struct {
|
||||
Name string
|
||||
Index int
|
||||
Addition, Deletion int
|
||||
Type int
|
||||
IsBin bool
|
||||
Sections []*DiffSection
|
||||
}
|
||||
|
||||
type Diff struct {
|
||||
TotalAddition, TotalDeletion int
|
||||
Files []*DiffFile
|
||||
}
|
||||
|
||||
func (diff *Diff) NumFiles() int {
|
||||
return len(diff.Files)
|
||||
}
|
||||
|
||||
const DIFF_HEAD = "diff --git "
|
||||
|
||||
func ParsePatch(cmd *exec.Cmd, reader io.Reader) (*Diff, error) {
|
||||
scanner := bufio.NewScanner(reader)
|
||||
var (
|
||||
curFile *DiffFile
|
||||
curSection = &DiffSection{
|
||||
Lines: make([]*DiffLine, 0, 10),
|
||||
}
|
||||
|
||||
leftLine, rightLine int
|
||||
)
|
||||
|
||||
diff := &Diff{Files: make([]*DiffFile, 0)}
|
||||
var i int
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
// fmt.Println(i, line)
|
||||
if strings.HasPrefix(line, "+++ ") || strings.HasPrefix(line, "--- ") {
|
||||
continue
|
||||
}
|
||||
|
||||
i = i + 1
|
||||
|
||||
// Diff data too large.
|
||||
if i == 5000 {
|
||||
log.Warn("Diff data too large")
|
||||
return &Diff{}, nil
|
||||
}
|
||||
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
switch {
|
||||
case line[0] == ' ':
|
||||
diffLine := &DiffLine{Type: DIFF_LINE_PLAIN, Content: line, LeftIdx: leftLine, RightIdx: rightLine}
|
||||
leftLine++
|
||||
rightLine++
|
||||
curSection.Lines = append(curSection.Lines, diffLine)
|
||||
continue
|
||||
case line[0] == '@':
|
||||
curSection = &DiffSection{}
|
||||
curFile.Sections = append(curFile.Sections, curSection)
|
||||
ss := strings.Split(line, "@@")
|
||||
diffLine := &DiffLine{Type: DIFF_LINE_SECTION, Content: line}
|
||||
curSection.Lines = append(curSection.Lines, diffLine)
|
||||
|
||||
// Parse line number.
|
||||
ranges := strings.Split(ss[len(ss)-2][1:], " ")
|
||||
leftLine, _ = base.StrTo(strings.Split(ranges[0], ",")[0][1:]).Int()
|
||||
rightLine, _ = base.StrTo(strings.Split(ranges[1], ",")[0]).Int()
|
||||
continue
|
||||
case line[0] == '+':
|
||||
curFile.Addition++
|
||||
diff.TotalAddition++
|
||||
diffLine := &DiffLine{Type: DIFF_LINE_ADD, Content: line, RightIdx: rightLine}
|
||||
rightLine++
|
||||
curSection.Lines = append(curSection.Lines, diffLine)
|
||||
continue
|
||||
case line[0] == '-':
|
||||
curFile.Deletion++
|
||||
diff.TotalDeletion++
|
||||
diffLine := &DiffLine{Type: DIFF_LINE_DEL, Content: line, LeftIdx: leftLine}
|
||||
if leftLine > 0 {
|
||||
leftLine++
|
||||
}
|
||||
curSection.Lines = append(curSection.Lines, diffLine)
|
||||
case strings.HasPrefix(line, "Binary"):
|
||||
curFile.IsBin = true
|
||||
continue
|
||||
}
|
||||
|
||||
// Get new file.
|
||||
if strings.HasPrefix(line, DIFF_HEAD) {
|
||||
fs := strings.Split(line[len(DIFF_HEAD):], " ")
|
||||
a := fs[0]
|
||||
|
||||
curFile = &DiffFile{
|
||||
Name: a[strings.Index(a, "/")+1:],
|
||||
Index: len(diff.Files) + 1,
|
||||
Type: DIFF_FILE_CHANGE,
|
||||
Sections: make([]*DiffSection, 0, 10),
|
||||
}
|
||||
diff.Files = append(diff.Files, curFile)
|
||||
|
||||
// Check file diff type.
|
||||
for scanner.Scan() {
|
||||
switch {
|
||||
case strings.HasPrefix(scanner.Text(), "new file"):
|
||||
curFile.Type = DIFF_FILE_ADD
|
||||
case strings.HasPrefix(scanner.Text(), "deleted"):
|
||||
curFile.Type = DIFF_FILE_DEL
|
||||
case strings.HasPrefix(scanner.Text(), "index"):
|
||||
curFile.Type = DIFF_FILE_CHANGE
|
||||
}
|
||||
if curFile.Type > 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// In case process became zombie.
|
||||
if !cmd.ProcessState.Exited() {
|
||||
log.Debug("git_diff.ParsePatch: process doesn't exit and now will be killed")
|
||||
if err := cmd.Process.Kill(); err != nil {
|
||||
log.Error("git_diff.ParsePatch: fail to kill zombie process: %v", err)
|
||||
}
|
||||
}
|
||||
return diff, nil
|
||||
}
|
||||
|
||||
func GetDiff(repoPath, commitid string) (*Diff, error) {
|
||||
repo, err := git.OpenRepository(repoPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
commit, err := repo.GetCommit(commitid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rd, wr := io.Pipe()
|
||||
var cmd *exec.Cmd
|
||||
// First commit of repository.
|
||||
if commit.ParentCount() == 0 {
|
||||
cmd = exec.Command("git", "show", commitid)
|
||||
} else {
|
||||
c, _ := commit.Parent(0)
|
||||
cmd = exec.Command("git", "diff", c.Id.String(), commitid)
|
||||
}
|
||||
cmd.Dir = repoPath
|
||||
cmd.Stdout = wr
|
||||
cmd.Stdin = os.Stdin
|
||||
cmd.Stderr = os.Stderr
|
||||
go func() {
|
||||
cmd.Run()
|
||||
wr.Close()
|
||||
}()
|
||||
defer rd.Close()
|
||||
return ParsePatch(cmd, rd)
|
||||
}
|
||||
725
models/issue.go
725
models/issue.go
@@ -5,82 +5,127 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-xorm/xorm"
|
||||
|
||||
"github.com/gogits/gogs/modules/base"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrIssueNotExist = errors.New("Issue does not exist")
|
||||
ErrIssueNotExist = errors.New("Issue does not exist")
|
||||
ErrLabelNotExist = errors.New("Label does not exist")
|
||||
ErrMilestoneNotExist = errors.New("Milestone does not exist")
|
||||
)
|
||||
|
||||
// Issue represents an issue or pull request of repository.
|
||||
type Issue struct {
|
||||
Id int64
|
||||
RepoId int64 `xorm:"INDEX"`
|
||||
Index int64 // Index in one repository.
|
||||
Name string
|
||||
RepoId int64 `xorm:"index"`
|
||||
Repo *Repository `xorm:"-"`
|
||||
PosterId int64
|
||||
Poster *User `xorm:"-"`
|
||||
Poster *User `xorm:"-"`
|
||||
LabelIds string `xorm:"TEXT"`
|
||||
Labels []*Label `xorm:"-"`
|
||||
MilestoneId int64
|
||||
AssigneeId int64
|
||||
IsPull bool // Indicates whether is a pull request or not.
|
||||
Assignee *User `xorm:"-"`
|
||||
IsRead bool `xorm:"-"`
|
||||
IsPull bool // Indicates whether is a pull request or not.
|
||||
IsClosed bool
|
||||
Labels string `xorm:"TEXT"`
|
||||
Mentions string `xorm:"TEXT"`
|
||||
Content string `xorm:"TEXT"`
|
||||
RenderedContent string `xorm:"-"`
|
||||
Priority int
|
||||
NumComments int
|
||||
Created time.Time `xorm:"created"`
|
||||
Updated time.Time `xorm:"updated"`
|
||||
Deadline time.Time
|
||||
Created time.Time `xorm:"CREATED"`
|
||||
Updated time.Time `xorm:"UPDATED"`
|
||||
}
|
||||
|
||||
func (i *Issue) GetPoster() (err error) {
|
||||
i.Poster, err = GetUserById(i.PosterId)
|
||||
if err == ErrUserNotExist {
|
||||
i.Poster = &User{Name: "FakeUser"}
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (i *Issue) GetLabels() error {
|
||||
if len(i.LabelIds) < 3 {
|
||||
return nil
|
||||
}
|
||||
|
||||
strIds := strings.Split(strings.TrimSuffix(i.LabelIds[1:], "|"), "|$")
|
||||
i.Labels = make([]*Label, 0, len(strIds))
|
||||
for _, strId := range strIds {
|
||||
id, _ := base.StrTo(strId).Int64()
|
||||
if id > 0 {
|
||||
l, err := GetLabelById(id)
|
||||
if err != nil {
|
||||
if err == ErrLabelNotExist {
|
||||
continue
|
||||
}
|
||||
return err
|
||||
}
|
||||
i.Labels = append(i.Labels, l)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *Issue) GetAssignee() (err error) {
|
||||
if i.AssigneeId == 0 {
|
||||
return nil
|
||||
}
|
||||
i.Assignee, err = GetUserById(i.AssigneeId)
|
||||
if err == ErrUserNotExist {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// CreateIssue creates new issue for repository.
|
||||
func CreateIssue(userId, repoId, milestoneId, assigneeId int64, issueCount int, name, labels, content string, isPull bool) (issue *Issue, err error) {
|
||||
// TODO: find out mentions
|
||||
mentions := ""
|
||||
|
||||
func NewIssue(issue *Issue) (err error) {
|
||||
sess := orm.NewSession()
|
||||
defer sess.Close()
|
||||
sess.Begin()
|
||||
|
||||
issue = &Issue{
|
||||
Index: int64(issueCount) + 1,
|
||||
Name: name,
|
||||
RepoId: repoId,
|
||||
PosterId: userId,
|
||||
MilestoneId: milestoneId,
|
||||
AssigneeId: assigneeId,
|
||||
IsPull: isPull,
|
||||
Labels: labels,
|
||||
Mentions: mentions,
|
||||
Content: content,
|
||||
if err = sess.Begin(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err = sess.Insert(issue); err != nil {
|
||||
sess.Rollback()
|
||||
return nil, err
|
||||
return err
|
||||
}
|
||||
|
||||
rawSql := "UPDATE `repository` SET num_issues = num_issues + 1 WHERE id = ?"
|
||||
if _, err = sess.Exec(rawSql, repoId); err != nil {
|
||||
if _, err = sess.Exec(rawSql, issue.RepoId); err != nil {
|
||||
sess.Rollback()
|
||||
return nil, err
|
||||
return err
|
||||
}
|
||||
return sess.Commit()
|
||||
}
|
||||
|
||||
if err = sess.Commit(); err != nil {
|
||||
sess.Rollback()
|
||||
// GetIssueByIndex returns issue by given index in repository.
|
||||
func GetIssueByIndex(rid, index int64) (*Issue, error) {
|
||||
issue := &Issue{RepoId: rid, Index: index}
|
||||
has, err := orm.Get(issue)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !has {
|
||||
return nil, ErrIssueNotExist
|
||||
}
|
||||
|
||||
return issue, nil
|
||||
}
|
||||
|
||||
// GetIssueById returns issue object by given id.
|
||||
func GetIssueByIndex(repoId, index int64) (*Issue, error) {
|
||||
issue := &Issue{RepoId: repoId, Index: index}
|
||||
// GetIssueById returns an issue by ID.
|
||||
func GetIssueById(id int64) (*Issue, error) {
|
||||
issue := &Issue{Id: id}
|
||||
has, err := orm.Get(issue)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -91,30 +136,28 @@ func GetIssueByIndex(repoId, index int64) (*Issue, error) {
|
||||
}
|
||||
|
||||
// GetIssues returns a list of issues by given conditions.
|
||||
func GetIssues(userId, repoId, posterId, milestoneId int64, page int, isClosed, isMention bool, labels, sortType string) ([]Issue, error) {
|
||||
func GetIssues(uid, rid, pid, mid int64, page int, isClosed bool, labelIds, sortType string) ([]Issue, error) {
|
||||
sess := orm.Limit(20, (page-1)*20)
|
||||
|
||||
if repoId > 0 {
|
||||
sess.Where("repo_id=?", repoId).And("is_closed=?", isClosed)
|
||||
if rid > 0 {
|
||||
sess.Where("repo_id=?", rid).And("is_closed=?", isClosed)
|
||||
} else {
|
||||
sess.Where("is_closed=?", isClosed)
|
||||
}
|
||||
|
||||
if userId > 0 {
|
||||
sess.And("assignee_id=?", userId)
|
||||
} else if posterId > 0 {
|
||||
sess.And("poster_id=?", posterId)
|
||||
} else if isMention {
|
||||
sess.And("mentions like '%$" + base.ToStr(userId) + "|%'")
|
||||
if uid > 0 {
|
||||
sess.And("assignee_id=?", uid)
|
||||
} else if pid > 0 {
|
||||
sess.And("poster_id=?", pid)
|
||||
}
|
||||
|
||||
if milestoneId > 0 {
|
||||
sess.And("milestone_id=?", milestoneId)
|
||||
if mid > 0 {
|
||||
sess.And("milestone_id=?", mid)
|
||||
}
|
||||
|
||||
if len(labels) > 0 {
|
||||
for _, label := range strings.Split(labels, ",") {
|
||||
sess.And("mentions like '%$" + label + "|%'")
|
||||
if len(labelIds) > 0 {
|
||||
for _, label := range strings.Split(labelIds, ",") {
|
||||
sess.And("label_ids like '%$" + label + "|%'")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,6 +172,8 @@ func GetIssues(userId, repoId, posterId, milestoneId int64, page int, isClosed,
|
||||
sess.Desc("num_comments")
|
||||
case "leastcomment":
|
||||
sess.Asc("num_comments")
|
||||
case "priority":
|
||||
sess.Desc("priority")
|
||||
default:
|
||||
sess.Desc("created")
|
||||
}
|
||||
@@ -138,38 +183,578 @@ func GetIssues(userId, repoId, posterId, milestoneId int64, page int, isClosed,
|
||||
return issues, err
|
||||
}
|
||||
|
||||
// GetUserIssueCount returns the number of issues that were created by given user in repository.
|
||||
func GetUserIssueCount(userId, repoId int64) int64 {
|
||||
count, _ := orm.Where("poster_id=?", userId).And("repo_id=?", repoId).Count(new(Issue))
|
||||
type IssueStatus int
|
||||
|
||||
const (
|
||||
IS_OPEN = iota + 1
|
||||
IS_CLOSE
|
||||
)
|
||||
|
||||
// GetIssuesByLabel returns a list of issues by given label and repository.
|
||||
func GetIssuesByLabel(repoId int64, label string) ([]*Issue, error) {
|
||||
issues := make([]*Issue, 0, 10)
|
||||
err := orm.Where("repo_id=?", repoId).And("label_ids like '%$" + label + "|%'").Find(&issues)
|
||||
return issues, err
|
||||
}
|
||||
|
||||
// GetIssueCountByPoster returns number of issues of repository by poster.
|
||||
func GetIssueCountByPoster(uid, rid int64, isClosed bool) int64 {
|
||||
count, _ := orm.Where("repo_id=?", rid).And("poster_id=?", uid).And("is_closed=?", isClosed).Count(new(Issue))
|
||||
return count
|
||||
}
|
||||
|
||||
// .___ ____ ___
|
||||
// | | ______ ________ __ ____ | | \______ ___________
|
||||
// | |/ ___// ___/ | \_/ __ \| | / ___// __ \_ __ \
|
||||
// | |\___ \ \___ \| | /\ ___/| | /\___ \\ ___/| | \/
|
||||
// |___/____ >____ >____/ \___ >______//____ >\___ >__|
|
||||
// \/ \/ \/ \/ \/
|
||||
|
||||
// IssueUser represents an issue-user relation.
|
||||
type IssueUser struct {
|
||||
Id int64
|
||||
Uid int64 // User ID.
|
||||
IssueId int64
|
||||
RepoId int64
|
||||
MilestoneId int64
|
||||
IsRead bool
|
||||
IsAssigned bool
|
||||
IsMentioned bool
|
||||
IsPoster bool
|
||||
IsClosed bool
|
||||
}
|
||||
|
||||
// NewIssueUserPairs adds new issue-user pairs for new issue of repository.
|
||||
func NewIssueUserPairs(rid, iid, oid, pid, aid int64, repoName string) (err error) {
|
||||
iu := &IssueUser{IssueId: iid, RepoId: rid}
|
||||
|
||||
us, err := GetCollaborators(repoName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
isNeedAddPoster := true
|
||||
for _, u := range us {
|
||||
iu.Uid = u.Id
|
||||
iu.IsPoster = iu.Uid == pid
|
||||
if isNeedAddPoster && iu.IsPoster {
|
||||
isNeedAddPoster = false
|
||||
}
|
||||
iu.IsAssigned = iu.Uid == aid
|
||||
if _, err = orm.Insert(iu); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if isNeedAddPoster {
|
||||
iu.Uid = pid
|
||||
iu.IsPoster = true
|
||||
iu.IsAssigned = iu.Uid == aid
|
||||
if _, err = orm.Insert(iu); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// PairsContains returns true when pairs list contains given issue.
|
||||
func PairsContains(ius []*IssueUser, issueId int64) int {
|
||||
for i := range ius {
|
||||
if ius[i].IssueId == issueId {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// GetIssueUserPairs returns issue-user pairs by given repository and user.
|
||||
func GetIssueUserPairs(rid, uid int64, isClosed bool) ([]*IssueUser, error) {
|
||||
ius := make([]*IssueUser, 0, 10)
|
||||
err := orm.Where("is_closed=?", isClosed).Find(&ius, &IssueUser{RepoId: rid, Uid: uid})
|
||||
return ius, err
|
||||
}
|
||||
|
||||
// GetIssueUserPairsByRepoIds returns issue-user pairs by given repository IDs.
|
||||
func GetIssueUserPairsByRepoIds(rids []int64, isClosed bool, page int) ([]*IssueUser, error) {
|
||||
buf := bytes.NewBufferString("")
|
||||
for _, rid := range rids {
|
||||
buf.WriteString("repo_id=")
|
||||
buf.WriteString(base.ToStr(rid))
|
||||
buf.WriteString(" OR ")
|
||||
}
|
||||
cond := strings.TrimSuffix(buf.String(), " OR ")
|
||||
|
||||
ius := make([]*IssueUser, 0, 10)
|
||||
sess := orm.Limit(20, (page-1)*20).Where("is_closed=?", isClosed)
|
||||
if len(cond) > 0 {
|
||||
sess.And(cond)
|
||||
}
|
||||
err := sess.Find(&ius)
|
||||
return ius, err
|
||||
}
|
||||
|
||||
// GetIssueUserPairsByMode returns issue-user pairs by given repository and user.
|
||||
func GetIssueUserPairsByMode(uid, rid int64, isClosed bool, page, filterMode int) ([]*IssueUser, error) {
|
||||
ius := make([]*IssueUser, 0, 10)
|
||||
sess := orm.Limit(20, (page-1)*20).Where("uid=?", uid).And("is_closed=?", isClosed)
|
||||
if rid > 0 {
|
||||
sess.And("repo_id=?", rid)
|
||||
}
|
||||
|
||||
switch filterMode {
|
||||
case FM_ASSIGN:
|
||||
sess.And("is_assigned=?", true)
|
||||
case FM_CREATE:
|
||||
sess.And("is_poster=?", true)
|
||||
default:
|
||||
return ius, nil
|
||||
}
|
||||
err := sess.Find(&ius)
|
||||
return ius, err
|
||||
}
|
||||
|
||||
// IssueStats represents issue statistic information.
|
||||
type IssueStats struct {
|
||||
OpenCount, ClosedCount int64
|
||||
AllCount int64
|
||||
AssignCount int64
|
||||
CreateCount int64
|
||||
MentionCount int64
|
||||
}
|
||||
|
||||
// Filter modes.
|
||||
const (
|
||||
FM_ASSIGN = iota + 1
|
||||
FM_CREATE
|
||||
FM_MENTION
|
||||
)
|
||||
|
||||
// GetIssueStats returns issue statistic information by given conditions.
|
||||
func GetIssueStats(rid, uid int64, isShowClosed bool, filterMode int) *IssueStats {
|
||||
stats := &IssueStats{}
|
||||
issue := new(Issue)
|
||||
tmpSess := &xorm.Session{}
|
||||
|
||||
sess := orm.Where("repo_id=?", rid)
|
||||
*tmpSess = *sess
|
||||
stats.OpenCount, _ = tmpSess.And("is_closed=?", false).Count(issue)
|
||||
*tmpSess = *sess
|
||||
stats.ClosedCount, _ = tmpSess.And("is_closed=?", true).Count(issue)
|
||||
if isShowClosed {
|
||||
stats.AllCount = stats.ClosedCount
|
||||
} else {
|
||||
stats.AllCount = stats.OpenCount
|
||||
}
|
||||
|
||||
if filterMode != FM_MENTION {
|
||||
sess = orm.Where("repo_id=?", rid)
|
||||
switch filterMode {
|
||||
case FM_ASSIGN:
|
||||
sess.And("assignee_id=?", uid)
|
||||
case FM_CREATE:
|
||||
sess.And("poster_id=?", uid)
|
||||
default:
|
||||
goto nofilter
|
||||
}
|
||||
*tmpSess = *sess
|
||||
stats.OpenCount, _ = tmpSess.And("is_closed=?", false).Count(issue)
|
||||
*tmpSess = *sess
|
||||
stats.ClosedCount, _ = tmpSess.And("is_closed=?", true).Count(issue)
|
||||
} else {
|
||||
sess := orm.Where("repo_id=?", rid).And("uid=?", uid).And("is_mentioned=?", true)
|
||||
*tmpSess = *sess
|
||||
stats.OpenCount, _ = tmpSess.And("is_closed=?", false).Count(new(IssueUser))
|
||||
*tmpSess = *sess
|
||||
stats.ClosedCount, _ = tmpSess.And("is_closed=?", true).Count(new(IssueUser))
|
||||
}
|
||||
nofilter:
|
||||
stats.AssignCount, _ = orm.Where("repo_id=?", rid).And("is_closed=?", isShowClosed).And("assignee_id=?", uid).Count(issue)
|
||||
stats.CreateCount, _ = orm.Where("repo_id=?", rid).And("is_closed=?", isShowClosed).And("poster_id=?", uid).Count(issue)
|
||||
stats.MentionCount, _ = orm.Where("repo_id=?", rid).And("uid=?", uid).And("is_closed=?", isShowClosed).And("is_mentioned=?", true).Count(new(IssueUser))
|
||||
return stats
|
||||
}
|
||||
|
||||
// GetUserIssueStats returns issue statistic information for dashboard by given conditions.
|
||||
func GetUserIssueStats(uid int64, filterMode int) *IssueStats {
|
||||
stats := &IssueStats{}
|
||||
issue := new(Issue)
|
||||
stats.AssignCount, _ = orm.Where("assignee_id=?", uid).And("is_closed=?", false).Count(issue)
|
||||
stats.CreateCount, _ = orm.Where("poster_id=?", uid).And("is_closed=?", false).Count(issue)
|
||||
return stats
|
||||
}
|
||||
|
||||
// UpdateIssue updates information of issue.
|
||||
func UpdateIssue(issue *Issue) error {
|
||||
_, err := orm.Id(issue.Id).AllCols().Update(issue)
|
||||
return err
|
||||
}
|
||||
|
||||
// Label represents a list of labels of repository for issues.
|
||||
type Label struct {
|
||||
Id int64
|
||||
RepoId int64 `xorm:"index"`
|
||||
Names string
|
||||
Colors string
|
||||
// UpdateIssueUserByStatus updates issue-user pairs by issue status.
|
||||
func UpdateIssueUserPairsByStatus(iid int64, isClosed bool) error {
|
||||
rawSql := "UPDATE `issue_user` SET is_closed = ? WHERE issue_id = ?"
|
||||
_, err := orm.Exec(rawSql, isClosed, iid)
|
||||
return err
|
||||
}
|
||||
|
||||
// UpdateIssueUserPairByAssignee updates issue-user pair for assigning.
|
||||
func UpdateIssueUserPairByAssignee(aid, iid int64) error {
|
||||
rawSql := "UPDATE `issue_user` SET is_assigned = ? WHERE issue_id = ?"
|
||||
if _, err := orm.Exec(rawSql, false, iid); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Assignee ID equals to 0 means clear assignee.
|
||||
if aid == 0 {
|
||||
return nil
|
||||
}
|
||||
rawSql = "UPDATE `issue_user` SET is_assigned = true WHERE uid = ? AND issue_id = ?"
|
||||
_, err := orm.Exec(rawSql, aid, iid)
|
||||
return err
|
||||
}
|
||||
|
||||
// UpdateIssueUserPairByRead updates issue-user pair for reading.
|
||||
func UpdateIssueUserPairByRead(uid, iid int64) error {
|
||||
rawSql := "UPDATE `issue_user` SET is_read = ? WHERE uid = ? AND issue_id = ?"
|
||||
_, err := orm.Exec(rawSql, true, uid, iid)
|
||||
return err
|
||||
}
|
||||
|
||||
// UpdateIssueUserPairsByMentions updates issue-user pairs by mentioning.
|
||||
func UpdateIssueUserPairsByMentions(uids []int64, iid int64) error {
|
||||
for _, uid := range uids {
|
||||
iu := &IssueUser{Uid: uid, IssueId: iid}
|
||||
has, err := orm.Get(iu)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
iu.IsMentioned = true
|
||||
if has {
|
||||
_, err = orm.Id(iu.Id).AllCols().Update(iu)
|
||||
} else {
|
||||
_, err = orm.Insert(iu)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// .____ ___. .__
|
||||
// | | _____ \_ |__ ____ | |
|
||||
// | | \__ \ | __ \_/ __ \| |
|
||||
// | |___ / __ \| \_\ \ ___/| |__
|
||||
// |_______ (____ /___ /\___ >____/
|
||||
// \/ \/ \/ \/
|
||||
|
||||
// Label represents a label of repository for issues.
|
||||
type Label struct {
|
||||
Id int64
|
||||
RepoId int64 `xorm:"INDEX"`
|
||||
Name string
|
||||
Color string `xorm:"VARCHAR(7)"`
|
||||
NumIssues int
|
||||
NumClosedIssues int
|
||||
NumOpenIssues int `xorm:"-"`
|
||||
IsChecked bool `xorm:"-"`
|
||||
}
|
||||
|
||||
// CalOpenIssues calculates the open issues of label.
|
||||
func (m *Label) CalOpenIssues() {
|
||||
m.NumOpenIssues = m.NumIssues - m.NumClosedIssues
|
||||
}
|
||||
|
||||
// NewLabel creates new label of repository.
|
||||
func NewLabel(l *Label) error {
|
||||
_, err := orm.Insert(l)
|
||||
return err
|
||||
}
|
||||
|
||||
// GetLabelById returns a label by given ID.
|
||||
func GetLabelById(id int64) (*Label, error) {
|
||||
if id <= 0 {
|
||||
return nil, ErrLabelNotExist
|
||||
}
|
||||
|
||||
l := &Label{Id: id}
|
||||
has, err := orm.Get(l)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !has {
|
||||
return nil, ErrLabelNotExist
|
||||
}
|
||||
return l, nil
|
||||
}
|
||||
|
||||
// GetLabels returns a list of labels of given repository ID.
|
||||
func GetLabels(repoId int64) ([]*Label, error) {
|
||||
labels := make([]*Label, 0, 10)
|
||||
err := orm.Where("repo_id=?", repoId).Find(&labels)
|
||||
return labels, err
|
||||
}
|
||||
|
||||
// UpdateLabel updates label information.
|
||||
func UpdateLabel(l *Label) error {
|
||||
_, err := orm.Id(l.Id).Update(l)
|
||||
return err
|
||||
}
|
||||
|
||||
// DeleteLabel delete a label of given repository.
|
||||
func DeleteLabel(repoId int64, strId string) error {
|
||||
id, _ := base.StrTo(strId).Int64()
|
||||
l, err := GetLabelById(id)
|
||||
if err != nil {
|
||||
if err == ErrLabelNotExist {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
issues, err := GetIssuesByLabel(repoId, strId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sess := orm.NewSession()
|
||||
defer sess.Close()
|
||||
if err = sess.Begin(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, issue := range issues {
|
||||
issue.LabelIds = strings.Replace(issue.LabelIds, "$"+strId+"|", "", -1)
|
||||
if _, err = sess.Id(issue.Id).AllCols().Update(issue); err != nil {
|
||||
sess.Rollback()
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if _, err = sess.Delete(l); err != nil {
|
||||
sess.Rollback()
|
||||
return err
|
||||
}
|
||||
return sess.Commit()
|
||||
}
|
||||
|
||||
// _____ .__.__ __
|
||||
// / \ |__| | ____ _______/ |_ ____ ____ ____
|
||||
// / \ / \| | | _/ __ \ / ___/\ __\/ _ \ / \_/ __ \
|
||||
// / Y \ | |_\ ___/ \___ \ | | ( <_> ) | \ ___/
|
||||
// \____|__ /__|____/\___ >____ > |__| \____/|___| /\___ >
|
||||
// \/ \/ \/ \/ \/
|
||||
|
||||
// Milestone represents a milestone of repository.
|
||||
type Milestone struct {
|
||||
Id int64
|
||||
Name string
|
||||
RepoId int64 `xorm:"index"`
|
||||
IsClosed bool
|
||||
Content string
|
||||
NumIssues int
|
||||
DueDate time.Time
|
||||
Created time.Time `xorm:"created"`
|
||||
Id int64
|
||||
RepoId int64 `xorm:"INDEX"`
|
||||
Index int64
|
||||
Name string
|
||||
Content string
|
||||
RenderedContent string `xorm:"-"`
|
||||
IsClosed bool
|
||||
NumIssues int
|
||||
NumClosedIssues int
|
||||
NumOpenIssues int `xorm:"-"`
|
||||
Completeness int // Percentage(1-100).
|
||||
Deadline time.Time
|
||||
DeadlineString string `xorm:"-"`
|
||||
ClosedDate time.Time
|
||||
}
|
||||
|
||||
// CalOpenIssues calculates the open issues of milestone.
|
||||
func (m *Milestone) CalOpenIssues() {
|
||||
m.NumOpenIssues = m.NumIssues - m.NumClosedIssues
|
||||
}
|
||||
|
||||
// NewMilestone creates new milestone of repository.
|
||||
func NewMilestone(m *Milestone) (err error) {
|
||||
sess := orm.NewSession()
|
||||
defer sess.Close()
|
||||
if err = sess.Begin(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err = sess.Insert(m); err != nil {
|
||||
sess.Rollback()
|
||||
return err
|
||||
}
|
||||
|
||||
rawSql := "UPDATE `repository` SET num_milestones = num_milestones + 1 WHERE id = ?"
|
||||
if _, err = sess.Exec(rawSql, m.RepoId); err != nil {
|
||||
sess.Rollback()
|
||||
return err
|
||||
}
|
||||
return sess.Commit()
|
||||
}
|
||||
|
||||
// GetMilestoneById returns the milestone by given ID.
|
||||
func GetMilestoneById(id int64) (*Milestone, error) {
|
||||
m := &Milestone{Id: id}
|
||||
has, err := orm.Get(m)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !has {
|
||||
return nil, ErrMilestoneNotExist
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// GetMilestoneByIndex returns the milestone of given repository and index.
|
||||
func GetMilestoneByIndex(repoId, idx int64) (*Milestone, error) {
|
||||
m := &Milestone{RepoId: repoId, Index: idx}
|
||||
has, err := orm.Get(m)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !has {
|
||||
return nil, ErrMilestoneNotExist
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// GetMilestones returns a list of milestones of given repository and status.
|
||||
func GetMilestones(repoId int64, isClosed bool) ([]*Milestone, error) {
|
||||
miles := make([]*Milestone, 0, 10)
|
||||
err := orm.Where("repo_id=?", repoId).And("is_closed=?", isClosed).Find(&miles)
|
||||
return miles, err
|
||||
}
|
||||
|
||||
// UpdateMilestone updates information of given milestone.
|
||||
func UpdateMilestone(m *Milestone) error {
|
||||
_, err := orm.Id(m.Id).Update(m)
|
||||
return err
|
||||
}
|
||||
|
||||
// ChangeMilestoneStatus changes the milestone open/closed status.
|
||||
func ChangeMilestoneStatus(m *Milestone, isClosed bool) (err error) {
|
||||
repo, err := GetRepositoryById(m.RepoId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sess := orm.NewSession()
|
||||
defer sess.Close()
|
||||
if err = sess.Begin(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
m.IsClosed = isClosed
|
||||
if _, err = sess.Id(m.Id).AllCols().Update(m); err != nil {
|
||||
sess.Rollback()
|
||||
return err
|
||||
}
|
||||
|
||||
if isClosed {
|
||||
repo.NumClosedMilestones++
|
||||
} else {
|
||||
repo.NumClosedMilestones--
|
||||
}
|
||||
if _, err = sess.Id(repo.Id).Update(repo); err != nil {
|
||||
sess.Rollback()
|
||||
return err
|
||||
}
|
||||
return sess.Commit()
|
||||
}
|
||||
|
||||
// ChangeMilestoneAssign changes assignment of milestone for issue.
|
||||
func ChangeMilestoneAssign(oldMid, mid int64, issue *Issue) (err error) {
|
||||
sess := orm.NewSession()
|
||||
defer sess.Close()
|
||||
if err = sess.Begin(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if oldMid > 0 {
|
||||
m, err := GetMilestoneById(oldMid)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
m.NumIssues--
|
||||
if issue.IsClosed {
|
||||
m.NumClosedIssues--
|
||||
}
|
||||
if m.NumIssues > 0 {
|
||||
m.Completeness = m.NumClosedIssues * 100 / m.NumIssues
|
||||
} else {
|
||||
m.Completeness = 0
|
||||
}
|
||||
if _, err = sess.Id(m.Id).Update(m); err != nil {
|
||||
sess.Rollback()
|
||||
return err
|
||||
}
|
||||
|
||||
rawSql := "UPDATE `issue_user` SET milestone_id = 0 WHERE issue_id = ?"
|
||||
if _, err = sess.Exec(rawSql, issue.Id); err != nil {
|
||||
sess.Rollback()
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if mid > 0 {
|
||||
m, err := GetMilestoneById(mid)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m.NumIssues++
|
||||
if issue.IsClosed {
|
||||
m.NumClosedIssues++
|
||||
}
|
||||
m.Completeness = m.NumClosedIssues * 100 / m.NumIssues
|
||||
if _, err = sess.Id(m.Id).Update(m); err != nil {
|
||||
sess.Rollback()
|
||||
return err
|
||||
}
|
||||
|
||||
rawSql := "UPDATE `issue_user` SET milestone_id = ? WHERE issue_id = ?"
|
||||
if _, err = sess.Exec(rawSql, m.Id, issue.Id); err != nil {
|
||||
sess.Rollback()
|
||||
return err
|
||||
}
|
||||
}
|
||||
return sess.Commit()
|
||||
}
|
||||
|
||||
// DeleteMilestone deletes a milestone.
|
||||
func DeleteMilestone(m *Milestone) (err error) {
|
||||
sess := orm.NewSession()
|
||||
defer sess.Close()
|
||||
if err = sess.Begin(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err = sess.Delete(m); err != nil {
|
||||
sess.Rollback()
|
||||
return err
|
||||
}
|
||||
|
||||
rawSql := "UPDATE `repository` SET num_milestones = num_milestones - 1 WHERE id = ?"
|
||||
if _, err = sess.Exec(rawSql, m.RepoId); err != nil {
|
||||
sess.Rollback()
|
||||
return err
|
||||
}
|
||||
|
||||
rawSql = "UPDATE `issue` SET milestone_id = 0 WHERE milestone_id = ?"
|
||||
if _, err = sess.Exec(rawSql, m.Id); err != nil {
|
||||
sess.Rollback()
|
||||
return err
|
||||
}
|
||||
|
||||
rawSql = "UPDATE `issue_user` SET milestone_id = 0 WHERE milestone_id = ?"
|
||||
if _, err = sess.Exec(rawSql, m.Id); err != nil {
|
||||
sess.Rollback()
|
||||
return err
|
||||
}
|
||||
return sess.Commit()
|
||||
}
|
||||
|
||||
// _________ __
|
||||
// \_ ___ \ ____ _____ _____ ____ _____/ |_
|
||||
// / \ \/ / _ \ / \ / \_/ __ \ / \ __\
|
||||
// \ \___( <_> ) Y Y \ Y Y \ ___/| | \ |
|
||||
// \______ /\____/|__|_| /__|_| /\___ >___| /__|
|
||||
// \/ \/ \/ \/ \/
|
||||
|
||||
// Issue types.
|
||||
const (
|
||||
IT_PLAIN = iota // Pure comment.
|
||||
@@ -186,17 +771,19 @@ type Comment struct {
|
||||
IssueId int64
|
||||
CommitId int64
|
||||
Line int64
|
||||
Content string
|
||||
Created time.Time `xorm:"created"`
|
||||
Content string `xorm:"TEXT"`
|
||||
Created time.Time `xorm:"CREATED"`
|
||||
}
|
||||
|
||||
// CreateComment creates comment of issue or commit.
|
||||
func CreateComment(userId, repoId, issueId, commitId, line int64, cmtType int, content string) error {
|
||||
sess := orm.NewSession()
|
||||
defer sess.Close()
|
||||
sess.Begin()
|
||||
if err := sess.Begin(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := orm.Insert(&Comment{PosterId: userId, Type: cmtType, IssueId: issueId,
|
||||
if _, err := sess.Insert(&Comment{PosterId: userId, Type: cmtType, IssueId: issueId,
|
||||
CommitId: commitId, Line: line, Content: content}); err != nil {
|
||||
sess.Rollback()
|
||||
return err
|
||||
|
||||
370
models/login.go
Normal file
370
models/login.go
Normal file
@@ -0,0 +1,370 @@
|
||||
// Copyright github.com/juju2013. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package models
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/smtp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-xorm/core"
|
||||
"github.com/go-xorm/xorm"
|
||||
|
||||
"github.com/gogits/gogs/modules/auth/ldap"
|
||||
"github.com/gogits/gogs/modules/log"
|
||||
)
|
||||
|
||||
// Login types.
|
||||
const (
|
||||
LT_NOTYPE = iota
|
||||
LT_PLAIN
|
||||
LT_LDAP
|
||||
LT_SMTP
|
||||
)
|
||||
|
||||
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[int]string{
|
||||
LT_LDAP: "LDAP",
|
||||
LT_SMTP: "SMTP",
|
||||
}
|
||||
|
||||
// Ensure structs implmented interface.
|
||||
var (
|
||||
_ core.Conversion = &LDAPConfig{}
|
||||
_ core.Conversion = &SMTPConfig{}
|
||||
)
|
||||
|
||||
type LDAPConfig struct {
|
||||
ldap.Ldapsource
|
||||
}
|
||||
|
||||
// implement
|
||||
func (cfg *LDAPConfig) FromDB(bs []byte) error {
|
||||
return json.Unmarshal(bs, &cfg.Ldapsource)
|
||||
}
|
||||
|
||||
func (cfg *LDAPConfig) ToDB() ([]byte, error) {
|
||||
return json.Marshal(cfg.Ldapsource)
|
||||
}
|
||||
|
||||
type SMTPConfig struct {
|
||||
Auth string
|
||||
Host string
|
||||
Port int
|
||||
TLS bool
|
||||
}
|
||||
|
||||
// implement
|
||||
func (cfg *SMTPConfig) FromDB(bs []byte) error {
|
||||
return json.Unmarshal(bs, cfg)
|
||||
}
|
||||
|
||||
func (cfg *SMTPConfig) ToDB() ([]byte, error) {
|
||||
return json.Marshal(cfg)
|
||||
}
|
||||
|
||||
type LoginSource struct {
|
||||
Id int64
|
||||
Type int
|
||||
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"`
|
||||
AllowAutoRegister bool `xorm:"not null default false"`
|
||||
}
|
||||
|
||||
func (source *LoginSource) TypeString() string {
|
||||
return LoginTypes[source.Type]
|
||||
}
|
||||
|
||||
func (source *LoginSource) LDAP() *LDAPConfig {
|
||||
return source.Cfg.(*LDAPConfig)
|
||||
}
|
||||
|
||||
func (source *LoginSource) SMTP() *SMTPConfig {
|
||||
return source.Cfg.(*SMTPConfig)
|
||||
}
|
||||
|
||||
// for xorm callback
|
||||
func (source *LoginSource) BeforeSet(colName string, val xorm.Cell) {
|
||||
if colName == "type" {
|
||||
ty := (*val).(int64)
|
||||
switch ty {
|
||||
case LT_LDAP:
|
||||
source.Cfg = new(LDAPConfig)
|
||||
case LT_SMTP:
|
||||
source.Cfg = new(SMTPConfig)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func GetAuths() ([]*LoginSource, error) {
|
||||
var auths = make([]*LoginSource, 0)
|
||||
err := orm.Find(&auths)
|
||||
return auths, err
|
||||
}
|
||||
|
||||
func GetLoginSourceById(id int64) (*LoginSource, error) {
|
||||
source := new(LoginSource)
|
||||
has, err := orm.Id(id).Get(source)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !has {
|
||||
return nil, ErrAuthenticationNotExist
|
||||
}
|
||||
return source, nil
|
||||
}
|
||||
|
||||
func AddSource(source *LoginSource) error {
|
||||
_, err := orm.Insert(source)
|
||||
return err
|
||||
}
|
||||
|
||||
func UpdateSource(source *LoginSource) error {
|
||||
_, err := orm.Id(source.Id).AllCols().Update(source)
|
||||
return err
|
||||
}
|
||||
|
||||
func DelLoginSource(source *LoginSource) error {
|
||||
cnt, err := orm.Count(&User{LoginSource: source.Id})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if cnt > 0 {
|
||||
return ErrAuthenticationUserUsed
|
||||
}
|
||||
_, err = orm.Id(source.Id).Delete(&LoginSource{})
|
||||
return err
|
||||
}
|
||||
|
||||
// UserSignIn validates user name and password.
|
||||
func UserSignIn(uname, passwd string) (*User, error) {
|
||||
var u *User
|
||||
if strings.Contains(uname, "@") {
|
||||
u = &User{Email: uname}
|
||||
} else {
|
||||
u = &User{LowerName: strings.ToLower(uname)}
|
||||
}
|
||||
|
||||
has, err := orm.Get(u)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if u.LoginType == LT_NOTYPE {
|
||||
if has {
|
||||
u.LoginType = LT_PLAIN
|
||||
}
|
||||
}
|
||||
|
||||
// for plain login, user must have existed.
|
||||
if u.LoginType == LT_PLAIN {
|
||||
if !has {
|
||||
return nil, ErrUserNotExist
|
||||
}
|
||||
|
||||
newUser := &User{Passwd: passwd, Salt: u.Salt}
|
||||
newUser.EncodePasswd()
|
||||
if u.Passwd != newUser.Passwd {
|
||||
return nil, ErrUserNotExist
|
||||
}
|
||||
return u, nil
|
||||
} else {
|
||||
if !has {
|
||||
var sources []LoginSource
|
||||
if err = orm.UseBool().Find(&sources,
|
||||
&LoginSource{IsActived: true, AllowAutoRegister: true}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, source := range sources {
|
||||
if source.Type == LT_LDAP {
|
||||
u, err := LoginUserLdapSource(nil, uname, passwd,
|
||||
source.Id, source.Cfg.(*LDAPConfig), true)
|
||||
if err == nil {
|
||||
return u, nil
|
||||
} else {
|
||||
log.Warn("Fail to login(%s) by LDAP(%s): %v", uname, source.Name, err)
|
||||
}
|
||||
} else if source.Type == LT_SMTP {
|
||||
u, err := LoginUserSMTPSource(nil, uname, passwd,
|
||||
source.Id, source.Cfg.(*SMTPConfig), true)
|
||||
if err == nil {
|
||||
return u, nil
|
||||
} else {
|
||||
log.Warn("Fail to login(%s) by SMTP(%s): %v", uname, source.Name, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil, ErrUserNotExist
|
||||
}
|
||||
|
||||
var source LoginSource
|
||||
hasSource, err := orm.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 LT_LDAP:
|
||||
return LoginUserLdapSource(u, u.LoginName, passwd,
|
||||
source.Id, source.Cfg.(*LDAPConfig), false)
|
||||
case LT_SMTP:
|
||||
return LoginUserSMTPSource(u, u.LoginName, passwd,
|
||||
source.Id, source.Cfg.(*SMTPConfig), false)
|
||||
}
|
||||
return nil, ErrUnsupportedLoginType
|
||||
}
|
||||
}
|
||||
|
||||
// Query if name/passwd can login against the LDAP direcotry pool
|
||||
// Create a local user if success
|
||||
// Return the same LoginUserPlain semantic
|
||||
func LoginUserLdapSource(user *User, name, passwd string, sourceId int64, cfg *LDAPConfig, autoRegister bool) (*User, error) {
|
||||
mail, logged := cfg.Ldapsource.SearchEntry(name, passwd)
|
||||
if !logged {
|
||||
// user not in LDAP, do nothing
|
||||
return nil, ErrUserNotExist
|
||||
}
|
||||
if !autoRegister {
|
||||
return user, nil
|
||||
}
|
||||
|
||||
// fake a local user creation
|
||||
user = &User{
|
||||
LowerName: strings.ToLower(name),
|
||||
Name: strings.ToLower(name),
|
||||
LoginType: LT_LDAP,
|
||||
LoginSource: sourceId,
|
||||
LoginName: name,
|
||||
IsActive: true,
|
||||
Passwd: passwd,
|
||||
Email: mail,
|
||||
}
|
||||
|
||||
return RegisterUser(user)
|
||||
}
|
||||
|
||||
type loginAuth struct {
|
||||
username, password string
|
||||
}
|
||||
|
||||
func LoginAuth(username, password string) smtp.Auth {
|
||||
return &loginAuth{username, password}
|
||||
}
|
||||
|
||||
func (a *loginAuth) Start(server *smtp.ServerInfo) (string, []byte, error) {
|
||||
return "LOGIN", []byte(a.username), nil
|
||||
}
|
||||
|
||||
func (a *loginAuth) Next(fromServer []byte, more bool) ([]byte, error) {
|
||||
if more {
|
||||
switch string(fromServer) {
|
||||
case "Username:":
|
||||
return []byte(a.username), nil
|
||||
case "Password:":
|
||||
return []byte(a.password), nil
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var (
|
||||
SMTP_PLAIN = "PLAIN"
|
||||
SMTP_LOGIN = "LOGIN"
|
||||
SMTPAuths = []string{SMTP_PLAIN, SMTP_LOGIN}
|
||||
)
|
||||
|
||||
func SmtpAuth(host string, port int, a smtp.Auth, useTls bool) error {
|
||||
c, err := smtp.Dial(fmt.Sprintf("%s:%d", host, port))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer c.Close()
|
||||
|
||||
if err = c.Hello("gogs"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if useTls {
|
||||
if ok, _ := c.Extension("STARTTLS"); ok {
|
||||
config := &tls.Config{ServerName: host}
|
||||
if err = c.StartTLS(config); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
return errors.New("SMTP server unsupports TLS")
|
||||
}
|
||||
}
|
||||
|
||||
if ok, _ := c.Extension("AUTH"); ok {
|
||||
if err = c.Auth(a); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
} else {
|
||||
return ErrUnsupportedLoginType
|
||||
}
|
||||
}
|
||||
|
||||
// Query if name/passwd can login against the LDAP direcotry pool
|
||||
// Create a local user if success
|
||||
// Return the same LoginUserPlain semantic
|
||||
func LoginUserSMTPSource(user *User, name, passwd string, sourceId int64, cfg *SMTPConfig, autoRegister bool) (*User, error) {
|
||||
var auth smtp.Auth
|
||||
if cfg.Auth == SMTP_PLAIN {
|
||||
auth = smtp.PlainAuth("", name, passwd, cfg.Host)
|
||||
} else if cfg.Auth == SMTP_LOGIN {
|
||||
auth = LoginAuth(name, passwd)
|
||||
} else {
|
||||
return nil, errors.New("Unsupported SMTP auth type")
|
||||
}
|
||||
|
||||
if err := SmtpAuth(cfg.Host, cfg.Port, auth, cfg.TLS); err != nil {
|
||||
if strings.Contains(err.Error(), "Username and Password not accepted") {
|
||||
return nil, ErrUserNotExist
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !autoRegister {
|
||||
return user, nil
|
||||
}
|
||||
|
||||
var loginName = name
|
||||
idx := strings.Index(name, "@")
|
||||
if idx > -1 {
|
||||
loginName = name[:idx]
|
||||
}
|
||||
// fake a local user creation
|
||||
user = &User{
|
||||
LowerName: strings.ToLower(loginName),
|
||||
Name: strings.ToLower(loginName),
|
||||
LoginType: LT_SMTP,
|
||||
LoginSource: sourceId,
|
||||
LoginName: name,
|
||||
IsActive: true,
|
||||
Passwd: passwd,
|
||||
Email: name,
|
||||
}
|
||||
|
||||
return RegisterUser(user)
|
||||
}
|
||||
115
models/models.go
115
models/models.go
@@ -8,37 +8,47 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
"github.com/go-xorm/xorm"
|
||||
_ "github.com/lib/pq"
|
||||
"github.com/lunny/xorm"
|
||||
// _ "github.com/mattn/go-sqlite3"
|
||||
|
||||
"github.com/gogits/gogs/modules/base"
|
||||
"github.com/gogits/gogs/modules/setting"
|
||||
)
|
||||
|
||||
var (
|
||||
orm *xorm.Engine
|
||||
orm *xorm.Engine
|
||||
tables []interface{}
|
||||
|
||||
HasEngine bool
|
||||
|
||||
DbCfg struct {
|
||||
Type, Host, Name, User, Pwd, Path, SslMode string
|
||||
}
|
||||
|
||||
UseSQLite3 bool
|
||||
EnableSQLite3 bool
|
||||
UseSQLite3 bool
|
||||
)
|
||||
|
||||
func init() {
|
||||
tables = append(tables, new(User), new(PublicKey), new(Repository), new(Watch),
|
||||
new(Action), new(Access), new(Issue), new(Comment), new(Oauth2), new(Follow),
|
||||
new(Mirror), new(Release), new(LoginSource), new(Webhook), new(IssueUser),
|
||||
new(Milestone), new(Label))
|
||||
}
|
||||
|
||||
func LoadModelsConfig() {
|
||||
DbCfg.Type = base.Cfg.MustValue("database", "DB_TYPE")
|
||||
DbCfg.Type = setting.Cfg.MustValue("database", "DB_TYPE")
|
||||
if DbCfg.Type == "sqlite3" {
|
||||
UseSQLite3 = true
|
||||
}
|
||||
DbCfg.Host = base.Cfg.MustValue("database", "HOST")
|
||||
DbCfg.Name = base.Cfg.MustValue("database", "NAME")
|
||||
DbCfg.User = base.Cfg.MustValue("database", "USER")
|
||||
DbCfg.Pwd = base.Cfg.MustValue("database", "PASSWD")
|
||||
DbCfg.SslMode = base.Cfg.MustValue("database", "SSL_MODE")
|
||||
DbCfg.Path = base.Cfg.MustValue("database", "PATH", "data/gogs.db")
|
||||
DbCfg.Host = setting.Cfg.MustValue("database", "HOST")
|
||||
DbCfg.Name = setting.Cfg.MustValue("database", "NAME")
|
||||
DbCfg.User = setting.Cfg.MustValue("database", "USER")
|
||||
DbCfg.Pwd = setting.Cfg.MustValue("database", "PASSWD")
|
||||
DbCfg.SslMode = setting.Cfg.MustValue("database", "SSL_MODE")
|
||||
DbCfg.Path = setting.Cfg.MustValue("database", "PATH", "data/gogs.db")
|
||||
}
|
||||
|
||||
func NewTestEngine(x *xorm.Engine) (err error) {
|
||||
@@ -47,20 +57,31 @@ func NewTestEngine(x *xorm.Engine) (err error) {
|
||||
x, err = xorm.NewEngine("mysql", fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8",
|
||||
DbCfg.User, DbCfg.Pwd, DbCfg.Host, DbCfg.Name))
|
||||
case "postgres":
|
||||
x, err = xorm.NewEngine("postgres", fmt.Sprintf("user=%s password=%s dbname=%s sslmode=%s",
|
||||
DbCfg.User, DbCfg.Pwd, DbCfg.Name, DbCfg.SslMode))
|
||||
// case "sqlite3":
|
||||
// os.MkdirAll(path.Dir(DbCfg.Path), os.ModePerm)
|
||||
// x, err = xorm.NewEngine("sqlite3", DbCfg.Path)
|
||||
var host, port = "127.0.0.1", "5432"
|
||||
fields := strings.Split(DbCfg.Host, ":")
|
||||
if len(fields) > 0 && len(strings.TrimSpace(fields[0])) > 0 {
|
||||
host = fields[0]
|
||||
}
|
||||
if len(fields) > 1 && len(strings.TrimSpace(fields[1])) > 0 {
|
||||
port = fields[1]
|
||||
}
|
||||
cnnstr := fmt.Sprintf("user=%s password=%s host=%s port=%s dbname=%s sslmode=%s",
|
||||
DbCfg.User, DbCfg.Pwd, host, port, DbCfg.Name, DbCfg.SslMode)
|
||||
//fmt.Println(cnnstr)
|
||||
x, err = xorm.NewEngine("postgres", cnnstr)
|
||||
case "sqlite3":
|
||||
if !EnableSQLite3 {
|
||||
return fmt.Errorf("Unknown database type: %s", DbCfg.Type)
|
||||
}
|
||||
os.MkdirAll(path.Dir(DbCfg.Path), os.ModePerm)
|
||||
x, err = xorm.NewEngine("sqlite3", DbCfg.Path)
|
||||
default:
|
||||
return fmt.Errorf("Unknown database type: %s\n", DbCfg.Type)
|
||||
return fmt.Errorf("Unknown database type: %s", DbCfg.Type)
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("models.init(fail to conntect database): %v\n", err)
|
||||
return fmt.Errorf("models.init(fail to conntect database): %v", err)
|
||||
}
|
||||
|
||||
return x.Sync(new(User), new(PublicKey), new(Repository), new(Watch),
|
||||
new(Action), new(Access), new(Issue), new(Comment))
|
||||
return x.Sync(tables...)
|
||||
}
|
||||
|
||||
func SetEngine() (err error) {
|
||||
@@ -69,37 +90,48 @@ func SetEngine() (err error) {
|
||||
orm, err = xorm.NewEngine("mysql", fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8",
|
||||
DbCfg.User, DbCfg.Pwd, DbCfg.Host, DbCfg.Name))
|
||||
case "postgres":
|
||||
orm, err = xorm.NewEngine("postgres", fmt.Sprintf("user=%s password=%s dbname=%s sslmode=%s",
|
||||
DbCfg.User, DbCfg.Pwd, DbCfg.Name, DbCfg.SslMode))
|
||||
var host, port = "127.0.0.1", "5432"
|
||||
fields := strings.Split(DbCfg.Host, ":")
|
||||
if len(fields) > 0 && len(strings.TrimSpace(fields[0])) > 0 {
|
||||
host = fields[0]
|
||||
}
|
||||
if len(fields) > 1 && len(strings.TrimSpace(fields[1])) > 0 {
|
||||
port = fields[1]
|
||||
}
|
||||
orm, err = xorm.NewEngine("postgres", fmt.Sprintf("user=%s password=%s host=%s port=%s dbname=%s sslmode=%s",
|
||||
DbCfg.User, DbCfg.Pwd, host, port, DbCfg.Name, DbCfg.SslMode))
|
||||
case "sqlite3":
|
||||
os.MkdirAll(path.Dir(DbCfg.Path), os.ModePerm)
|
||||
orm, err = xorm.NewEngine("sqlite3", DbCfg.Path)
|
||||
default:
|
||||
return fmt.Errorf("Unknown database type: %s\n", DbCfg.Type)
|
||||
return fmt.Errorf("Unknown database type: %s", DbCfg.Type)
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("models.init(fail to conntect database): %v\n", err)
|
||||
return fmt.Errorf("models.init(fail to conntect database): %v", err)
|
||||
}
|
||||
|
||||
// WARNNING: for serv command, MUST remove the output to os.stdout,
|
||||
// so use log file to instead print to stdout.
|
||||
logPath := path.Join(setting.LogRootPath, "xorm.log")
|
||||
os.MkdirAll(path.Dir(logPath), os.ModePerm)
|
||||
|
||||
//x.ShowDebug = true
|
||||
//orm.ShowErr = true
|
||||
f, err := os.Create("xorm.log")
|
||||
f, err := os.Create(logPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("models.init(fail to create xorm.log): %v\n", err)
|
||||
return fmt.Errorf("models.init(fail to create xorm.log): %v", err)
|
||||
}
|
||||
orm.Logger = f
|
||||
orm.Logger = xorm.NewSimpleLogger(f)
|
||||
|
||||
orm.ShowSQL = true
|
||||
orm.ShowDebug = true
|
||||
orm.ShowErr = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewEngine() (err error) {
|
||||
if err = SetEngine(); err != nil {
|
||||
return err
|
||||
} else if err = orm.Sync(new(User), new(PublicKey), new(Repository), new(Watch),
|
||||
new(Action), new(Access), new(Issue), new(Comment)); err != nil {
|
||||
}
|
||||
if err = orm.Sync(tables...); err != nil {
|
||||
return fmt.Errorf("sync database struct error: %v\n", err)
|
||||
}
|
||||
return nil
|
||||
@@ -107,7 +139,9 @@ func NewEngine() (err error) {
|
||||
|
||||
type Statistic struct {
|
||||
Counter struct {
|
||||
User, PublicKey, Repo, Watch, Action, Access int64
|
||||
User, PublicKey, Repo, Watch, Action, Access,
|
||||
Issue, Comment, Mirror, Oauth, Release,
|
||||
LoginSource, Webhook, Milestone int64
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,5 +152,18 @@ func GetStatistic() (stats Statistic) {
|
||||
stats.Counter.Watch, _ = orm.Count(new(Watch))
|
||||
stats.Counter.Action, _ = orm.Count(new(Action))
|
||||
stats.Counter.Access, _ = orm.Count(new(Access))
|
||||
stats.Counter.Issue, _ = orm.Count(new(Issue))
|
||||
stats.Counter.Comment, _ = orm.Count(new(Comment))
|
||||
stats.Counter.Mirror, _ = orm.Count(new(Mirror))
|
||||
stats.Counter.Oauth, _ = orm.Count(new(Oauth2))
|
||||
stats.Counter.Release, _ = orm.Count(new(Release))
|
||||
stats.Counter.LoginSource, _ = orm.Count(new(LoginSource))
|
||||
stats.Counter.Webhook, _ = orm.Count(new(Webhook))
|
||||
stats.Counter.Milestone, _ = orm.Count(new(Milestone))
|
||||
return
|
||||
}
|
||||
|
||||
// DumpDatabase dumps all data from database to file system.
|
||||
func DumpDatabase(filePath string) error {
|
||||
return orm.DumpAllToFile(filePath)
|
||||
}
|
||||
|
||||
15
models/models_sqlite.go
Normal file
15
models/models_sqlite.go
Normal file
@@ -0,0 +1,15 @@
|
||||
// +build sqlite
|
||||
|
||||
// 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 (
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
)
|
||||
|
||||
func init() {
|
||||
EnableSQLite3 = true
|
||||
}
|
||||
@@ -1,55 +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 (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/lunny/xorm"
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
|
||||
"github.com/gogits/gogs/modules/base"
|
||||
)
|
||||
|
||||
func init() {
|
||||
var err error
|
||||
orm, err = xorm.NewEngine("sqlite3", "./test.db")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
|
||||
orm.ShowSQL = true
|
||||
orm.ShowDebug = true
|
||||
|
||||
err = orm.Sync(&User{}, &Repository{})
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
|
||||
base.RepoRootPath = "test"
|
||||
}
|
||||
|
||||
func TestCreateRepository(t *testing.T) {
|
||||
user := User{Id: 1, Name: "foobar", Type: UT_INDIVIDUAL}
|
||||
_, err := CreateRepository(&user, "test", "", "", "test repo desc", false, false)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteRepository(t *testing.T) {
|
||||
err := DeleteRepository(1, 1, "foobar")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommitRepoAction(t *testing.T) {
|
||||
Convey("Create a commit repository action", t, func() {
|
||||
|
||||
})
|
||||
}
|
||||
88
models/oauth2.go
Normal file
88
models/oauth2.go
Normal file
@@ -0,0 +1,88 @@
|
||||
// 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"
|
||||
)
|
||||
|
||||
// OT: Oauth2 Type
|
||||
const (
|
||||
OT_GITHUB = iota + 1
|
||||
OT_GOOGLE
|
||||
OT_TWITTER
|
||||
OT_QQ
|
||||
OT_WEIBO
|
||||
OT_BITBUCKET
|
||||
OT_OSCHINA
|
||||
OT_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"`
|
||||
}
|
||||
|
||||
func BindUserOauth2(userId, oauthId int64) error {
|
||||
_, err := orm.Id(oauthId).Update(&Oauth2{Uid: userId})
|
||||
return err
|
||||
}
|
||||
|
||||
func AddOauth2(oa *Oauth2) error {
|
||||
_, err := orm.Insert(oa)
|
||||
return err
|
||||
}
|
||||
|
||||
func GetOauth2(identity string) (oa *Oauth2, err error) {
|
||||
oa = &Oauth2{Identity: identity}
|
||||
isExist, err := orm.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 := orm.Id(id).Get(oa)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !has {
|
||||
return nil, ErrOauth2RecordNotExist
|
||||
}
|
||||
return oa, nil
|
||||
}
|
||||
|
||||
// GetOauthByUserId returns list of oauthes that are releated to given user.
|
||||
func GetOauthByUserId(uid int64) (oas []*Oauth2, err error) {
|
||||
err = orm.Find(&oas, Oauth2{Uid: uid})
|
||||
return oas, err
|
||||
}
|
||||
|
||||
// DeleteOauth2ById deletes a oauth2 by ID.
|
||||
func DeleteOauth2ById(id int64) error {
|
||||
_, err := orm.Delete(&Oauth2{Id: id})
|
||||
return err
|
||||
}
|
||||
|
||||
// CleanUnbindOauth deletes all unbind OAuthes.
|
||||
func CleanUnbindOauth() error {
|
||||
_, err := orm.Delete(&Oauth2{Uid: -1})
|
||||
return err
|
||||
}
|
||||
@@ -19,24 +19,26 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/Unknwon/com"
|
||||
qlog "github.com/qiniu/log"
|
||||
|
||||
"github.com/gogits/gogs/modules/log"
|
||||
)
|
||||
|
||||
const (
|
||||
// "### autogenerated by gitgos, DO NOT EDIT\n"
|
||||
TPL_PUBLICK_KEY = `command="%s serv key-%d",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty %s`
|
||||
_TPL_PUBLICK_KEY = `command="%s serv key-%d",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty %s` + "\n"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrKeyAlreadyExist = errors.New("Public key already exist")
|
||||
ErrKeyNotExist = errors.New("Public key does not exist")
|
||||
)
|
||||
|
||||
var sshOpLocker = sync.Mutex{}
|
||||
|
||||
var (
|
||||
sshPath string
|
||||
appPath string
|
||||
sshPath string // SSH directory.
|
||||
appPath string // Execution(binary) path.
|
||||
)
|
||||
|
||||
// exePath returns the executable path.
|
||||
@@ -52,7 +54,7 @@ func exePath() (string, error) {
|
||||
func homeDir() string {
|
||||
home, err := com.HomeDir()
|
||||
if err != nil {
|
||||
return "/"
|
||||
qlog.Fatalln(err)
|
||||
}
|
||||
return home
|
||||
}
|
||||
@@ -60,39 +62,51 @@ func homeDir() string {
|
||||
func init() {
|
||||
var err error
|
||||
|
||||
appPath, err = exePath()
|
||||
if err != nil {
|
||||
fmt.Printf("publickey.init(fail to get app path): %v\n", err)
|
||||
os.Exit(2)
|
||||
if appPath, err = exePath(); err != nil {
|
||||
qlog.Fatalf("publickey.init(fail to get app path): %v\n", err)
|
||||
}
|
||||
|
||||
// Determine and create .ssh path.
|
||||
sshPath = filepath.Join(homeDir(), ".ssh")
|
||||
if err = os.MkdirAll(sshPath, os.ModePerm); err != nil {
|
||||
fmt.Printf("publickey.init(fail to create sshPath(%s)): %v\n", sshPath, err)
|
||||
os.Exit(2)
|
||||
qlog.Fatalf("publickey.init(fail to create sshPath(%s)): %v\n", sshPath, err)
|
||||
}
|
||||
}
|
||||
|
||||
// PublicKey represents a SSH key of user.
|
||||
// PublicKey represents a SSH key.
|
||||
type PublicKey struct {
|
||||
Id int64
|
||||
OwnerId int64 `xorm:"index"`
|
||||
Name string `xorm:"unique not null"`
|
||||
OwnerId int64 `xorm:"UNIQUE(s) INDEX NOT NULL"`
|
||||
Name string `xorm:"UNIQUE(s) NOT NULL"`
|
||||
Fingerprint string
|
||||
Content string `xorm:"TEXT not null"`
|
||||
Created time.Time `xorm:"created"`
|
||||
Updated time.Time `xorm:"updated"`
|
||||
Content string `xorm:"TEXT NOT NULL"`
|
||||
Created time.Time `xorm:"CREATED"`
|
||||
Updated time.Time `xorm:"UPDATED"`
|
||||
}
|
||||
|
||||
// GenAuthorizedKey returns formatted public key string.
|
||||
func GenAuthorizedKey(keyId int64, key string) string {
|
||||
return fmt.Sprintf(TPL_PUBLICK_KEY+"\n", appPath, keyId, key)
|
||||
// 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, key.Content)
|
||||
}
|
||||
|
||||
// AddPublicKey adds new public key to database and SSH key file.
|
||||
// saveAuthorizedKeyFile writes SSH key content to authorized_keys file.
|
||||
func saveAuthorizedKeyFile(key *PublicKey) error {
|
||||
sshOpLocker.Lock()
|
||||
defer sshOpLocker.Unlock()
|
||||
|
||||
fpath := filepath.Join(sshPath, "authorized_keys")
|
||||
f, err := os.OpenFile(fpath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0600)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
_, err = f.WriteString(key.GetAuthorizedString())
|
||||
return err
|
||||
}
|
||||
|
||||
// AddPublicKey adds new public key to database and authorized_keys file.
|
||||
func AddPublicKey(key *PublicKey) (err error) {
|
||||
// Check if public key name has been used.
|
||||
has, err := orm.Get(key)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -101,15 +115,15 @@ func AddPublicKey(key *PublicKey) (err error) {
|
||||
}
|
||||
|
||||
// Calculate fingerprint.
|
||||
tmpPath := strings.Replace(filepath.Join(os.TempDir(), fmt.Sprintf("%d", time.Now().Nanosecond()),
|
||||
tmpPath := strings.Replace(path.Join(os.TempDir(), fmt.Sprintf("%d", time.Now().Nanosecond()),
|
||||
"id_rsa.pub"), "\\", "/", -1)
|
||||
os.MkdirAll(path.Dir(tmpPath), os.ModePerm)
|
||||
if err = ioutil.WriteFile(tmpPath, []byte(key.Content), os.ModePerm); err != nil {
|
||||
return err
|
||||
}
|
||||
stdout, _, err := com.ExecCmd("ssh-keygen", "-l", "-f", tmpPath)
|
||||
stdout, stderr, err := com.ExecCmd("ssh-keygen", "-l", "-f", tmpPath)
|
||||
if err != nil {
|
||||
return err
|
||||
return errors.New("ssh-keygen -l -f: " + stderr)
|
||||
} else if len(stdout) < 2 {
|
||||
return errors.New("Not enough output for calculating fingerprint")
|
||||
}
|
||||
@@ -118,8 +132,8 @@ func AddPublicKey(key *PublicKey) (err error) {
|
||||
// Save SSH key.
|
||||
if _, err = orm.Insert(key); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = SaveAuthorizedKeyFile(key); err != nil {
|
||||
} else if err = saveAuthorizedKeyFile(key); err != nil {
|
||||
// Roll back.
|
||||
if _, err2 := orm.Delete(key); err2 != nil {
|
||||
return err2
|
||||
}
|
||||
@@ -129,8 +143,15 @@ func AddPublicKey(key *PublicKey) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// ListPublicKey returns a list of all public keys that user has.
|
||||
func ListPublicKey(uid int64) ([]PublicKey, error) {
|
||||
keys := make([]PublicKey, 0, 5)
|
||||
err := orm.Find(&keys, &PublicKey{OwnerId: uid})
|
||||
return keys, err
|
||||
}
|
||||
|
||||
// rewriteAuthorizedKeys finds and deletes corresponding line in authorized_keys file.
|
||||
func rewriteAuthorizedKeys(key *PublicKey, p, tmpP string) error {
|
||||
// Delete SSH key in SSH key file.
|
||||
sshOpLocker.Lock()
|
||||
defer sshOpLocker.Unlock()
|
||||
|
||||
@@ -146,6 +167,8 @@ func rewriteAuthorizedKeys(key *PublicKey, p, tmpP string) error {
|
||||
}
|
||||
defer fw.Close()
|
||||
|
||||
isFound := false
|
||||
keyword := fmt.Sprintf("key-%d", key.Id)
|
||||
buf := bufio.NewReader(fr)
|
||||
for {
|
||||
line, errRead := buf.ReadString('\n')
|
||||
@@ -164,7 +187,8 @@ func rewriteAuthorizedKeys(key *PublicKey, p, tmpP string) error {
|
||||
}
|
||||
|
||||
// Found the line and copy rest of file.
|
||||
if strings.Contains(line, fmt.Sprintf("key-%d", key.Id)) && strings.Contains(line, key.Content) {
|
||||
if !isFound && strings.Contains(line, keyword) && strings.Contains(line, key.Content) {
|
||||
isFound = true
|
||||
continue
|
||||
}
|
||||
// Still finding the line, copy the line that currently read.
|
||||
@@ -180,49 +204,26 @@ func rewriteAuthorizedKeys(key *PublicKey, p, tmpP string) error {
|
||||
}
|
||||
|
||||
// DeletePublicKey deletes SSH key information both in database and authorized_keys file.
|
||||
func DeletePublicKey(key *PublicKey) (err error) {
|
||||
// Delete SSH key in database.
|
||||
has, err := orm.Id(key.Id).Get(key)
|
||||
func DeletePublicKey(key *PublicKey) error {
|
||||
has, err := orm.Get(key)
|
||||
if err != nil {
|
||||
return err
|
||||
} else if !has {
|
||||
return errors.New("Public key does not exist")
|
||||
return ErrKeyNotExist
|
||||
}
|
||||
|
||||
if _, err = orm.Delete(key); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
p := filepath.Join(sshPath, "authorized_keys")
|
||||
tmpP := filepath.Join(sshPath, "authorized_keys.tmp")
|
||||
log.Trace("ssh.DeletePublicKey(authorized_keys): %s", p)
|
||||
fpath := filepath.Join(sshPath, "authorized_keys")
|
||||
tmpPath := filepath.Join(sshPath, "authorized_keys.tmp")
|
||||
log.Trace("publickey.DeletePublicKey(authorized_keys): %s", fpath)
|
||||
|
||||
if err = rewriteAuthorizedKeys(key, p, tmpP); err != nil {
|
||||
if err = rewriteAuthorizedKeys(key, fpath, tmpPath); err != nil {
|
||||
return err
|
||||
} else if err = os.Remove(p); err != nil {
|
||||
} else if err = os.Remove(fpath); err != nil {
|
||||
return err
|
||||
}
|
||||
return os.Rename(tmpP, p)
|
||||
}
|
||||
|
||||
// ListPublicKey returns a list of public keys that user has.
|
||||
func ListPublicKey(userId int64) ([]PublicKey, error) {
|
||||
keys := make([]PublicKey, 0)
|
||||
err := orm.Find(&keys, &PublicKey{OwnerId: userId})
|
||||
return keys, err
|
||||
}
|
||||
|
||||
// SaveAuthorizedKeyFile writes SSH key content to SSH key file.
|
||||
func SaveAuthorizedKeyFile(key *PublicKey) error {
|
||||
sshOpLocker.Lock()
|
||||
defer sshOpLocker.Unlock()
|
||||
|
||||
p := filepath.Join(sshPath, "authorized_keys")
|
||||
f, err := os.OpenFile(p, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0600)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
_, err = f.WriteString(GenAuthorizedKey(key.Id, key.Content))
|
||||
return err
|
||||
return os.Rename(tmpPath, fpath)
|
||||
}
|
||||
|
||||
83
models/release.go
Normal file
83
models/release.go
Normal file
@@ -0,0 +1,83 @@
|
||||
// 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"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Unknwon/com"
|
||||
"github.com/gogits/git"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrReleaseAlreadyExist = errors.New("Release already exist")
|
||||
)
|
||||
|
||||
// Release represents a release of repository.
|
||||
type Release struct {
|
||||
Id int64
|
||||
RepoId int64
|
||||
PublisherId int64
|
||||
Publisher *User `xorm:"-"`
|
||||
Title string
|
||||
TagName string
|
||||
LowerTagName string
|
||||
SHA1 string
|
||||
NumCommits int
|
||||
NumCommitsBehind int `xorm:"-"`
|
||||
Note string `xorm:"TEXT"`
|
||||
IsPrerelease bool
|
||||
Created time.Time `xorm:"created"`
|
||||
}
|
||||
|
||||
// GetReleasesByRepoId returns a list of releases of repository.
|
||||
func GetReleasesByRepoId(repoId int64) (rels []*Release, err error) {
|
||||
err = orm.Desc("created").Find(&rels, Release{RepoId: repoId})
|
||||
return rels, err
|
||||
}
|
||||
|
||||
// IsReleaseExist returns true if release with given tag name already exists.
|
||||
func IsReleaseExist(repoId int64, tagName string) (bool, error) {
|
||||
if len(tagName) == 0 {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return orm.Get(&Release{RepoId: repoId, LowerTagName: strings.ToLower(tagName)})
|
||||
}
|
||||
|
||||
// CreateRelease creates a new release of repository.
|
||||
func CreateRelease(gitRepo *git.Repository, rel *Release) error {
|
||||
isExist, err := IsReleaseExist(rel.RepoId, rel.TagName)
|
||||
if err != nil {
|
||||
return err
|
||||
} else if isExist {
|
||||
return ErrReleaseAlreadyExist
|
||||
}
|
||||
|
||||
if !gitRepo.IsTagExist(rel.TagName) {
|
||||
_, stderr, err := com.ExecCmdDir(gitRepo.Path, "git", "tag", rel.TagName, "-m", rel.Title)
|
||||
if err != nil {
|
||||
return err
|
||||
} else if strings.Contains(stderr, "fatal:") {
|
||||
return errors.New(stderr)
|
||||
}
|
||||
} else {
|
||||
commit, err := gitRepo.GetCommitOfTag(rel.TagName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rel.NumCommits, err = commit.CommitsCount()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
rel.LowerTagName = strings.ToLower(rel.TagName)
|
||||
_, err = orm.InsertOne(rel)
|
||||
return err
|
||||
}
|
||||
699
models/repo.go
699
models/repo.go
@@ -12,6 +12,7 @@ import (
|
||||
"os/exec"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
@@ -22,69 +23,126 @@ import (
|
||||
"github.com/gogits/git"
|
||||
|
||||
"github.com/gogits/gogs/modules/base"
|
||||
"github.com/gogits/gogs/modules/bin"
|
||||
"github.com/gogits/gogs/modules/log"
|
||||
"github.com/gogits/gogs/modules/setting"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrRepoAlreadyExist = errors.New("Repository already exist")
|
||||
ErrRepoNotExist = errors.New("Repository does not exist")
|
||||
ErrRepoFileNotExist = errors.New("Target Repo file does not exist")
|
||||
ErrRepoFileNotExist = errors.New("Repository file does not exist")
|
||||
ErrRepoNameIllegal = errors.New("Repository name contains illegal characters")
|
||||
ErrRepoFileNotLoaded = fmt.Errorf("repo file not loaded")
|
||||
ErrRepoFileNotLoaded = errors.New("Repository file not loaded")
|
||||
ErrMirrorNotExist = errors.New("Mirror does not exist")
|
||||
)
|
||||
|
||||
var (
|
||||
LanguageIgns, Licenses []string
|
||||
)
|
||||
|
||||
// getAssetList returns corresponding asset list in 'conf'.
|
||||
func getAssetList(prefix string) []string {
|
||||
assets := make([]string, 0, 15)
|
||||
for _, name := range bin.AssetNames() {
|
||||
if strings.HasPrefix(name, prefix) {
|
||||
assets = append(assets, strings.TrimPrefix(name, prefix+"/"))
|
||||
}
|
||||
}
|
||||
return assets
|
||||
}
|
||||
|
||||
func LoadRepoConfig() {
|
||||
LanguageIgns = strings.Split(base.Cfg.MustValue("repository", "LANG_IGNS"), "|")
|
||||
Licenses = strings.Split(base.Cfg.MustValue("repository", "LICENSES"), "|")
|
||||
// Load .gitignore and license files.
|
||||
types := []string{"gitignore", "license"}
|
||||
typeFiles := make([][]string, 2)
|
||||
for i, t := range types {
|
||||
files := getAssetList(path.Join("conf", t))
|
||||
customPath := path.Join(setting.CustomPath, "conf", t)
|
||||
if com.IsDir(customPath) {
|
||||
customFiles, err := com.StatDir(customPath)
|
||||
if err != nil {
|
||||
log.Fatal("Fail to get custom %s files: %v", t, err)
|
||||
}
|
||||
|
||||
for _, f := range customFiles {
|
||||
if !com.IsSliceContainsStr(files, f) {
|
||||
files = append(files, f)
|
||||
}
|
||||
}
|
||||
}
|
||||
typeFiles[i] = files
|
||||
}
|
||||
|
||||
LanguageIgns = typeFiles[0]
|
||||
Licenses = typeFiles[1]
|
||||
sort.Strings(LanguageIgns)
|
||||
sort.Strings(Licenses)
|
||||
}
|
||||
|
||||
func NewRepoContext() {
|
||||
zip.Verbose = false
|
||||
|
||||
// Check if server has basic git setting.
|
||||
stdout, _, err := com.ExecCmd("git", "config", "--get", "user.name")
|
||||
if err != nil {
|
||||
fmt.Printf("repo.init(fail to get git user.name): %v", err)
|
||||
os.Exit(2)
|
||||
} else if len(stdout) == 0 {
|
||||
if _, _, err = com.ExecCmd("git", "config", "--global", "user.email", "gogitservice@gmail.com"); err != nil {
|
||||
fmt.Printf("repo.init(fail to set git user.email): %v", err)
|
||||
os.Exit(2)
|
||||
} else if _, _, err = com.ExecCmd("git", "config", "--global", "user.name", "Gogs"); err != nil {
|
||||
fmt.Printf("repo.init(fail to set git user.name): %v", err)
|
||||
os.Exit(2)
|
||||
stdout, stderr, err := com.ExecCmd("git", "config", "--get", "user.name")
|
||||
if strings.Contains(stderr, "fatal:") {
|
||||
log.Fatal("repo.NewRepoContext(fail to get git user.name): %s", stderr)
|
||||
} else if err != nil || len(strings.TrimSpace(stdout)) == 0 {
|
||||
if _, stderr, err = com.ExecCmd("git", "config", "--global", "user.email", "gogitservice@gmail.com"); err != nil {
|
||||
log.Fatal("repo.NewRepoContext(fail to set git user.email): %s", stderr)
|
||||
} else if _, stderr, err = com.ExecCmd("git", "config", "--global", "user.name", "Gogs"); err != nil {
|
||||
log.Fatal("repo.NewRepoContext(fail to set git user.name): %s", stderr)
|
||||
}
|
||||
}
|
||||
|
||||
barePath := path.Join(setting.RepoRootPath, "git-bare.zip")
|
||||
if !com.IsExist(barePath) {
|
||||
data, err := bin.Asset("conf/content/git-bare.zip")
|
||||
if err != nil {
|
||||
log.Fatal("Fail to get asset 'git-bare.zip': %v", err)
|
||||
} else if err := ioutil.WriteFile(barePath, data, os.ModePerm); err != nil {
|
||||
log.Fatal("Fail to write asset 'git-bare.zip': %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Repository represents a git repository.
|
||||
type Repository struct {
|
||||
Id int64
|
||||
OwnerId int64 `xorm:"unique(s)"`
|
||||
ForkId int64
|
||||
LowerName string `xorm:"unique(s) index not null"`
|
||||
Name string `xorm:"index not null"`
|
||||
Description string
|
||||
Website string
|
||||
NumWatches int
|
||||
NumStars int
|
||||
NumForks int
|
||||
NumIssues int
|
||||
NumClosedIssues int
|
||||
NumOpenIssues int `xorm:"-"`
|
||||
IsPrivate bool
|
||||
IsBare bool
|
||||
Created time.Time `xorm:"created"`
|
||||
Updated time.Time `xorm:"updated"`
|
||||
Id int64
|
||||
OwnerId int64 `xorm:"unique(s)"`
|
||||
Owner *User `xorm:"-"`
|
||||
ForkId int64
|
||||
LowerName string `xorm:"unique(s) index not null"`
|
||||
Name string `xorm:"index not null"`
|
||||
Description string
|
||||
Website string
|
||||
NumWatches int
|
||||
NumStars int
|
||||
NumForks int
|
||||
NumIssues int
|
||||
NumClosedIssues int
|
||||
NumOpenIssues int `xorm:"-"`
|
||||
NumMilestones int `xorm:"NOT NULL DEFAULT 0"`
|
||||
NumClosedMilestones int `xorm:"NOT NULL DEFAULT 0"`
|
||||
NumOpenMilestones int `xorm:"-"`
|
||||
NumTags int `xorm:"-"`
|
||||
IsPrivate bool
|
||||
IsMirror bool
|
||||
IsBare bool
|
||||
IsGoget bool
|
||||
DefaultBranch string
|
||||
Created time.Time `xorm:"created"`
|
||||
Updated time.Time `xorm:"updated"`
|
||||
}
|
||||
|
||||
func (repo *Repository) GetOwner() (err error) {
|
||||
repo.Owner, err = GetUserById(repo.OwnerId)
|
||||
return err
|
||||
}
|
||||
|
||||
// IsRepositoryExist returns true if the repository with given name under user has already existed.
|
||||
func IsRepositoryExist(user *User, repoName string) (bool, error) {
|
||||
repo := Repository{OwnerId: user.Id}
|
||||
func IsRepositoryExist(u *User, repoName string) (bool, error) {
|
||||
repo := Repository{OwnerId: u.Id}
|
||||
has, err := orm.Where("lower_name = ?", strings.ToLower(repoName)).Get(&repo)
|
||||
if err != nil {
|
||||
return has, err
|
||||
@@ -92,7 +150,7 @@ func IsRepositoryExist(user *User, repoName string) (bool, error) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return com.IsDir(RepoPath(user.Name, repoName)), nil
|
||||
return com.IsDir(RepoPath(u.Name, repoName)), nil
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -116,13 +174,123 @@ func IsLegalName(repoName string) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// Mirror represents a mirror information of repository.
|
||||
type Mirror struct {
|
||||
Id int64
|
||||
RepoId int64
|
||||
RepoName string // <user name>/<repo name>
|
||||
Interval int // Hour.
|
||||
Updated time.Time `xorm:"UPDATED"`
|
||||
NextUpdate time.Time
|
||||
}
|
||||
|
||||
func GetMirror(repoId int64) (*Mirror, error) {
|
||||
m := &Mirror{RepoId: repoId}
|
||||
has, err := orm.Get(m)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !has {
|
||||
return nil, ErrMirrorNotExist
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func UpdateMirror(m *Mirror) error {
|
||||
_, err := orm.Id(m.Id).Update(m)
|
||||
return err
|
||||
}
|
||||
|
||||
// MirrorUpdate checks and updates mirror repositories.
|
||||
func MirrorUpdate() {
|
||||
if err := orm.Iterate(new(Mirror), func(idx int, bean interface{}) error {
|
||||
m := bean.(*Mirror)
|
||||
if m.NextUpdate.After(time.Now()) {
|
||||
return nil
|
||||
}
|
||||
|
||||
repoPath := filepath.Join(setting.RepoRootPath, m.RepoName+".git")
|
||||
_, stderr, err := com.ExecCmdDir(repoPath, "git", "remote", "update")
|
||||
if err != nil {
|
||||
return errors.New("git remote update: " + stderr)
|
||||
} else if err = git.UnpackRefs(repoPath); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
m.NextUpdate = time.Now().Add(time.Duration(m.Interval) * time.Hour)
|
||||
return UpdateMirror(m)
|
||||
}); err != nil {
|
||||
log.Error("repo.MirrorUpdate: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// MirrorRepository creates a mirror repository from source.
|
||||
func MirrorRepository(repoId int64, userName, repoName, repoPath, url string) error {
|
||||
_, stderr, err := com.ExecCmd("git", "clone", "--mirror", url, repoPath)
|
||||
if err != nil {
|
||||
return errors.New("git clone --mirror: " + stderr)
|
||||
}
|
||||
|
||||
if _, err = orm.InsertOne(&Mirror{
|
||||
RepoId: repoId,
|
||||
RepoName: strings.ToLower(userName + "/" + repoName),
|
||||
Interval: 24,
|
||||
NextUpdate: time.Now().Add(24 * time.Hour),
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return git.UnpackRefs(repoPath)
|
||||
}
|
||||
|
||||
// MigrateRepository migrates a existing repository from other project hosting.
|
||||
func MigrateRepository(user *User, name, desc string, private, mirror bool, url string) (*Repository, error) {
|
||||
repo, err := CreateRepository(user, name, desc, "", "", private, mirror, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Clone to temprory path and do the init commit.
|
||||
tmpDir := filepath.Join(os.TempDir(), fmt.Sprintf("%d", time.Now().Nanosecond()))
|
||||
os.MkdirAll(tmpDir, os.ModePerm)
|
||||
|
||||
repoPath := RepoPath(user.Name, name)
|
||||
|
||||
repo.IsBare = false
|
||||
if mirror {
|
||||
if err = MirrorRepository(repo.Id, user.Name, repo.Name, repoPath, url); err != nil {
|
||||
return repo, err
|
||||
}
|
||||
repo.IsMirror = true
|
||||
return repo, UpdateRepository(repo)
|
||||
}
|
||||
|
||||
// Clone from local repository.
|
||||
_, stderr, err := com.ExecCmd("git", "clone", repoPath, tmpDir)
|
||||
if err != nil {
|
||||
return repo, errors.New("git clone: " + stderr)
|
||||
}
|
||||
|
||||
// Pull data from source.
|
||||
_, stderr, err = com.ExecCmdDir(tmpDir, "git", "pull", url)
|
||||
if err != nil {
|
||||
return repo, errors.New("git pull: " + stderr)
|
||||
}
|
||||
|
||||
// Push data to local repository.
|
||||
if _, stderr, err = com.ExecCmdDir(tmpDir, "git", "push", "origin", "master"); err != nil {
|
||||
return repo, errors.New("git push: " + stderr)
|
||||
}
|
||||
|
||||
return repo, UpdateRepository(repo)
|
||||
}
|
||||
|
||||
// CreateRepository creates a repository for given user or orgnaziation.
|
||||
func CreateRepository(user *User, repoName, desc, repoLang, license string, private bool, initReadme bool) (*Repository, error) {
|
||||
if !IsLegalName(repoName) {
|
||||
func CreateRepository(user *User, name, desc, lang, license string, private, mirror, initReadme bool) (*Repository, error) {
|
||||
if !IsLegalName(name) {
|
||||
return nil, ErrRepoNameIllegal
|
||||
}
|
||||
|
||||
isExist, err := IsRepositoryExist(user, repoName)
|
||||
isExist, err := IsRepositoryExist(user, name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if isExist {
|
||||
@@ -131,89 +299,102 @@ func CreateRepository(user *User, repoName, desc, repoLang, license string, priv
|
||||
|
||||
repo := &Repository{
|
||||
OwnerId: user.Id,
|
||||
Name: repoName,
|
||||
LowerName: strings.ToLower(repoName),
|
||||
Name: name,
|
||||
LowerName: strings.ToLower(name),
|
||||
Description: desc,
|
||||
IsPrivate: private,
|
||||
IsBare: repoLang == "" && license == "" && !initReadme,
|
||||
IsBare: lang == "" && license == "" && !initReadme,
|
||||
}
|
||||
if !repo.IsBare {
|
||||
repo.DefaultBranch = "master"
|
||||
}
|
||||
|
||||
repoPath := RepoPath(user.Name, repoName)
|
||||
if err = initRepository(repoPath, user, repo, initReadme, repoLang, license); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
session := orm.NewSession()
|
||||
defer session.Close()
|
||||
session.Begin()
|
||||
repoPath := RepoPath(user.Name, repo.Name)
|
||||
|
||||
if _, err = session.Insert(repo); err != nil {
|
||||
sess := orm.NewSession()
|
||||
defer sess.Close()
|
||||
sess.Begin()
|
||||
|
||||
if _, err = sess.Insert(repo); err != nil {
|
||||
if err2 := os.RemoveAll(repoPath); err2 != nil {
|
||||
log.Error("repo.CreateRepository(repo): %v", err)
|
||||
return nil, errors.New(fmt.Sprintf(
|
||||
"delete repo directory %s/%s failed(1): %v", user.Name, repoName, err2))
|
||||
"delete repo directory %s/%s failed(1): %v", user.Name, repo.Name, err2))
|
||||
}
|
||||
session.Rollback()
|
||||
sess.Rollback()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
mode := AU_WRITABLE
|
||||
if mirror {
|
||||
mode = AU_READABLE
|
||||
}
|
||||
access := Access{
|
||||
UserName: user.LowerName,
|
||||
RepoName: strings.ToLower(path.Join(user.Name, repo.Name)),
|
||||
Mode: AU_WRITABLE,
|
||||
Mode: mode,
|
||||
}
|
||||
if _, err = session.Insert(&access); err != nil {
|
||||
session.Rollback()
|
||||
if _, err = sess.Insert(&access); err != nil {
|
||||
sess.Rollback()
|
||||
if err2 := os.RemoveAll(repoPath); err2 != nil {
|
||||
log.Error("repo.CreateRepository(access): %v", err)
|
||||
return nil, errors.New(fmt.Sprintf(
|
||||
"delete repo directory %s/%s failed(2): %v", user.Name, repoName, err2))
|
||||
"delete repo directory %s/%s failed(2): %v", user.Name, repo.Name, err2))
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rawSql := "UPDATE `user` SET num_repos = num_repos + 1 WHERE id = ?"
|
||||
if _, err = session.Exec(rawSql, user.Id); err != nil {
|
||||
session.Rollback()
|
||||
if _, err = sess.Exec(rawSql, user.Id); err != nil {
|
||||
sess.Rollback()
|
||||
if err2 := os.RemoveAll(repoPath); err2 != nil {
|
||||
log.Error("repo.CreateRepository(repo count): %v", err)
|
||||
return nil, errors.New(fmt.Sprintf(
|
||||
"delete repo directory %s/%s failed(3): %v", user.Name, repoName, err2))
|
||||
"delete repo directory %s/%s failed(3): %v", user.Name, repo.Name, err2))
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = session.Commit(); err != nil {
|
||||
session.Rollback()
|
||||
if err = sess.Commit(); err != nil {
|
||||
sess.Rollback()
|
||||
if err2 := os.RemoveAll(repoPath); err2 != nil {
|
||||
log.Error("repo.CreateRepository(commit): %v", err)
|
||||
return nil, errors.New(fmt.Sprintf(
|
||||
"delete repo directory %s/%s failed(3): %v", user.Name, repoName, err2))
|
||||
"delete repo directory %s/%s failed(3): %v", user.Name, repo.Name, err2))
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = WatchRepo(user.Id, repo.Id, true); err != nil {
|
||||
log.Error("repo.CreateRepository(WatchRepo): %v", err)
|
||||
}
|
||||
|
||||
if err = NewRepoAction(user, repo); err != nil {
|
||||
log.Error("repo.CreateRepository(NewRepoAction): %v", err)
|
||||
}
|
||||
|
||||
// No need for init for mirror.
|
||||
if mirror {
|
||||
return repo, nil
|
||||
}
|
||||
|
||||
if err = initRepository(repoPath, user, repo, initReadme, lang, license); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c := exec.Command("git", "update-server-info")
|
||||
c.Dir = repoPath
|
||||
if err = c.Run(); err != nil {
|
||||
log.Error("repo.CreateRepository(exec update-server-info): %v", err)
|
||||
}
|
||||
|
||||
if err = NewRepoAction(user, repo); err != nil {
|
||||
log.Error("repo.CreateRepository(NewRepoAction): %v", err)
|
||||
}
|
||||
|
||||
if err = WatchRepo(user.Id, repo.Id, true); err != nil {
|
||||
log.Error("repo.CreateRepository(WatchRepo): %v", err)
|
||||
}
|
||||
|
||||
return repo, nil
|
||||
}
|
||||
|
||||
// extractGitBareZip extracts git-bare.zip to repository path.
|
||||
func extractGitBareZip(repoPath string) error {
|
||||
z, err := zip.Open("conf/content/git-bare.zip")
|
||||
z, err := zip.Open(path.Join(setting.RepoRootPath, "git-bare.zip"))
|
||||
if err != nil {
|
||||
fmt.Println("shi?")
|
||||
return err
|
||||
}
|
||||
defer z.Close()
|
||||
@@ -225,25 +406,15 @@ func extractGitBareZip(repoPath string) error {
|
||||
func initRepoCommit(tmpPath string, sig *git.Signature) (err error) {
|
||||
var stderr string
|
||||
if _, stderr, err = com.ExecCmdDir(tmpPath, "git", "add", "--all"); err != nil {
|
||||
return err
|
||||
return errors.New("git add: " + stderr)
|
||||
}
|
||||
if len(stderr) > 0 {
|
||||
log.Trace("stderr(1): %s", stderr)
|
||||
}
|
||||
|
||||
if _, stderr, err = com.ExecCmdDir(tmpPath, "git", "commit", fmt.Sprintf("--author='%s <%s>'", sig.Name, sig.Email),
|
||||
"-m", "Init commit"); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(stderr) > 0 {
|
||||
log.Trace("stderr(2): %s", stderr)
|
||||
return errors.New("git commit: " + stderr)
|
||||
}
|
||||
|
||||
if _, stderr, err = com.ExecCmdDir(tmpPath, "git", "push", "origin", "master"); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(stderr) > 0 {
|
||||
log.Trace("stderr(3): %s", stderr)
|
||||
return errors.New("git push: " + stderr)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -259,6 +430,14 @@ func createHookUpdate(hookPath, content string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// SetRepoEnvs sets environment variables for command update.
|
||||
func SetRepoEnvs(userId int64, userName, repoName, repoUserName string) {
|
||||
os.Setenv("userId", base.ToStr(userId))
|
||||
os.Setenv("userName", userName)
|
||||
os.Setenv("repoName", repoName)
|
||||
os.Setenv("repoUserName", repoUserName)
|
||||
}
|
||||
|
||||
// InitRepository initializes README and .gitignore if needed.
|
||||
func initRepository(f string, user *User, repo *Repository, initReadme bool, repoLang, license string) error {
|
||||
repoPath := RepoPath(user.Name, repo.Name)
|
||||
@@ -268,10 +447,11 @@ func initRepository(f string, user *User, repo *Repository, initReadme bool, rep
|
||||
return err
|
||||
}
|
||||
|
||||
rp := strings.NewReplacer("\\", "/", " ", "\\ ")
|
||||
// hook/post-update
|
||||
if err := createHookUpdate(filepath.Join(repoPath, "hooks", "update"),
|
||||
fmt.Sprintf("#!/usr/bin/env bash\n%s update $1 $2 $3\n",
|
||||
strings.Replace(appPath, "\\", "/", -1))); err != nil {
|
||||
fmt.Sprintf("#!/usr/bin/env %s\n%s update $1 $2 $3\n", setting.ScriptType,
|
||||
rp.Replace(appPath))); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -288,11 +468,12 @@ func initRepository(f string, user *User, repo *Repository, initReadme bool, rep
|
||||
}
|
||||
|
||||
// Clone to temprory path and do the init commit.
|
||||
tmpDir := filepath.Join(os.TempDir(), fmt.Sprintf("%d", time.Now().Nanosecond()))
|
||||
tmpDir := filepath.Join(os.TempDir(), base.ToStr(time.Now().Nanosecond()))
|
||||
os.MkdirAll(tmpDir, os.ModePerm)
|
||||
|
||||
if _, _, err := com.ExecCmd("git", "clone", repoPath, tmpDir); err != nil {
|
||||
return err
|
||||
_, stderr, err := com.ExecCmd("git", "clone", repoPath, tmpDir)
|
||||
if err != nil {
|
||||
return errors.New("git clone: " + stderr)
|
||||
}
|
||||
|
||||
// README
|
||||
@@ -308,22 +489,40 @@ func initRepository(f string, user *User, repo *Repository, initReadme bool, rep
|
||||
// .gitignore
|
||||
if repoLang != "" {
|
||||
filePath := "conf/gitignore/" + repoLang
|
||||
if com.IsFile(filePath) {
|
||||
if _, err := com.Copy(filePath,
|
||||
filepath.Join(tmpDir, fileName["gitign"])); err != nil {
|
||||
targetPath := path.Join(tmpDir, fileName["gitign"])
|
||||
data, err := bin.Asset(filePath)
|
||||
if err == nil {
|
||||
if err = ioutil.WriteFile(targetPath, data, os.ModePerm); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
// Check custom files.
|
||||
filePath = path.Join(setting.CustomPath, "conf/gitignore", repoLang)
|
||||
if com.IsFile(filePath) {
|
||||
if err := com.Copy(filePath, targetPath); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// LICENSE
|
||||
if license != "" {
|
||||
filePath := "conf/license/" + license
|
||||
if com.IsFile(filePath) {
|
||||
if _, err := com.Copy(filePath,
|
||||
filepath.Join(tmpDir, fileName["license"])); err != nil {
|
||||
targetPath := path.Join(tmpDir, fileName["license"])
|
||||
data, err := bin.Asset(filePath)
|
||||
if err == nil {
|
||||
if err = ioutil.WriteFile(targetPath, data, os.ModePerm); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
// Check custom files.
|
||||
filePath = path.Join(setting.CustomPath, "conf/license", license)
|
||||
if com.IsFile(filePath) {
|
||||
if err := com.Copy(filePath, targetPath); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -331,51 +530,147 @@ func initRepository(f string, user *User, repo *Repository, initReadme bool, rep
|
||||
return nil
|
||||
}
|
||||
|
||||
SetRepoEnvs(user.Id, user.Name, repo.Name, user.Name)
|
||||
|
||||
// Apply changes and commit.
|
||||
return initRepoCommit(tmpDir, user.NewGitSig())
|
||||
}
|
||||
|
||||
// UserRepo reporesents a repository with user name.
|
||||
type UserRepo struct {
|
||||
*Repository
|
||||
UserName string
|
||||
}
|
||||
|
||||
// GetRepos returns given number of repository objects with offset.
|
||||
func GetRepos(num, offset int) ([]UserRepo, error) {
|
||||
repos := make([]Repository, 0, num)
|
||||
// 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 := orm.Limit(num, offset).Asc("id").Find(&repos); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
urepos := make([]UserRepo, len(repos))
|
||||
for i := range repos {
|
||||
urepos[i].Repository = &repos[i]
|
||||
u := new(User)
|
||||
has, err := orm.Id(urepos[i].Repository.OwnerId).Get(u)
|
||||
for _, repo := range repos {
|
||||
repo.Owner = &User{Id: repo.OwnerId}
|
||||
has, err := orm.Get(repo.Owner)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !has {
|
||||
return nil, ErrUserNotExist
|
||||
}
|
||||
urepos[i].UserName = u.Name
|
||||
}
|
||||
|
||||
return urepos, nil
|
||||
return repos, nil
|
||||
}
|
||||
|
||||
// RepoPath returns repository path by given user and repository name.
|
||||
func RepoPath(userName, repoName string) string {
|
||||
return filepath.Join(UserPath(userName), strings.ToLower(repoName)+".git")
|
||||
}
|
||||
|
||||
// TransferOwnership transfers all corresponding setting from old user to new one.
|
||||
func TransferOwnership(user *User, newOwner string, repo *Repository) (err error) {
|
||||
newUser, err := GetUserByName(newOwner)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Update accesses.
|
||||
accesses := make([]Access, 0, 10)
|
||||
if err = orm.Find(&accesses, &Access{RepoName: user.LowerName + "/" + repo.LowerName}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sess := orm.NewSession()
|
||||
defer sess.Close()
|
||||
if err = sess.Begin(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for i := range accesses {
|
||||
accesses[i].RepoName = newUser.LowerName + "/" + repo.LowerName
|
||||
if accesses[i].UserName == user.LowerName {
|
||||
accesses[i].UserName = newUser.LowerName
|
||||
}
|
||||
if err = UpdateAccessWithSession(sess, &accesses[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Update repository.
|
||||
repo.OwnerId = newUser.Id
|
||||
if _, err := sess.Id(repo.Id).Update(repo); err != nil {
|
||||
sess.Rollback()
|
||||
return err
|
||||
}
|
||||
|
||||
// Update user repository number.
|
||||
rawSql := "UPDATE `user` SET num_repos = num_repos + 1 WHERE id = ?"
|
||||
if _, err = sess.Exec(rawSql, newUser.Id); err != nil {
|
||||
sess.Rollback()
|
||||
return err
|
||||
}
|
||||
rawSql = "UPDATE `user` SET num_repos = num_repos - 1 WHERE id = ?"
|
||||
if _, err = sess.Exec(rawSql, user.Id); err != nil {
|
||||
sess.Rollback()
|
||||
return err
|
||||
}
|
||||
|
||||
// Add watch of new owner to repository.
|
||||
if !IsWatching(newUser.Id, repo.Id) {
|
||||
if err = WatchRepo(newUser.Id, repo.Id, true); err != nil {
|
||||
sess.Rollback()
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err = TransferRepoAction(user, newUser, repo); err != nil {
|
||||
sess.Rollback()
|
||||
return err
|
||||
}
|
||||
|
||||
// Change repository directory name.
|
||||
if err = os.Rename(RepoPath(user.Name, repo.Name), RepoPath(newUser.Name, repo.Name)); err != nil {
|
||||
sess.Rollback()
|
||||
return err
|
||||
}
|
||||
|
||||
return sess.Commit()
|
||||
}
|
||||
|
||||
// ChangeRepositoryName changes all corresponding setting from old repository name to new one.
|
||||
func ChangeRepositoryName(userName, oldRepoName, newRepoName string) (err error) {
|
||||
// Update accesses.
|
||||
accesses := make([]Access, 0, 10)
|
||||
if err = orm.Find(&accesses, &Access{RepoName: strings.ToLower(userName + "/" + oldRepoName)}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sess := orm.NewSession()
|
||||
defer sess.Close()
|
||||
if err = sess.Begin(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for i := range accesses {
|
||||
accesses[i].RepoName = userName + "/" + newRepoName
|
||||
if err = UpdateAccessWithSession(sess, &accesses[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Change repository directory name.
|
||||
if err = os.Rename(RepoPath(userName, oldRepoName), RepoPath(userName, newRepoName)); err != nil {
|
||||
sess.Rollback()
|
||||
return err
|
||||
}
|
||||
|
||||
return sess.Commit()
|
||||
}
|
||||
|
||||
func UpdateRepository(repo *Repository) error {
|
||||
repo.LowerName = strings.ToLower(repo.Name)
|
||||
|
||||
if len(repo.Description) > 255 {
|
||||
repo.Description = repo.Description[:255]
|
||||
}
|
||||
if len(repo.Website) > 255 {
|
||||
repo.Website = repo.Website[:255]
|
||||
}
|
||||
|
||||
_, err := orm.Id(repo.Id).AllCols().Update(repo)
|
||||
return err
|
||||
}
|
||||
@@ -390,29 +685,69 @@ func DeleteRepository(userId, repoId int64, userName string) (err error) {
|
||||
return ErrRepoNotExist
|
||||
}
|
||||
|
||||
session := orm.NewSession()
|
||||
if err = session.Begin(); err != nil {
|
||||
sess := orm.NewSession()
|
||||
defer sess.Close()
|
||||
if err = sess.Begin(); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err = session.Delete(&Repository{Id: repoId}); err != nil {
|
||||
session.Rollback()
|
||||
if _, err = sess.Delete(&Repository{Id: repoId}); err != nil {
|
||||
sess.Rollback()
|
||||
return err
|
||||
}
|
||||
if _, err := session.Delete(&Access{RepoName: strings.ToLower(path.Join(userName, repo.Name))}); err != nil {
|
||||
session.Rollback()
|
||||
if _, err := sess.Delete(&Access{RepoName: strings.ToLower(path.Join(userName, repo.Name))}); err != nil {
|
||||
sess.Rollback()
|
||||
return err
|
||||
}
|
||||
if _, err := sess.Delete(&Action{RepoId: repo.Id}); err != nil {
|
||||
sess.Rollback()
|
||||
return err
|
||||
}
|
||||
if _, err = sess.Delete(&Watch{RepoId: repoId}); err != nil {
|
||||
sess.Rollback()
|
||||
return err
|
||||
}
|
||||
if _, err = sess.Delete(&Mirror{RepoId: repoId}); err != nil {
|
||||
sess.Rollback()
|
||||
return err
|
||||
}
|
||||
if _, err = sess.Delete(&IssueUser{RepoId: repoId}); err != nil {
|
||||
sess.Rollback()
|
||||
return err
|
||||
}
|
||||
if _, err = sess.Delete(&Milestone{RepoId: repoId}); err != nil {
|
||||
sess.Rollback()
|
||||
return err
|
||||
}
|
||||
if _, err = sess.Delete(&Release{RepoId: repoId}); err != nil {
|
||||
sess.Rollback()
|
||||
return err
|
||||
}
|
||||
|
||||
// Delete comments.
|
||||
if err = orm.Iterate(&Issue{RepoId: repoId}, func(idx int, bean interface{}) error {
|
||||
issue := bean.(*Issue)
|
||||
if _, err = sess.Delete(&Comment{IssueId: issue.Id}); err != nil {
|
||||
sess.Rollback()
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
sess.Rollback()
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err = sess.Delete(&Issue{RepoId: repoId}); err != nil {
|
||||
sess.Rollback()
|
||||
return err
|
||||
}
|
||||
|
||||
rawSql := "UPDATE `user` SET num_repos = num_repos - 1 WHERE id = ?"
|
||||
if _, err = session.Exec(rawSql, userId); err != nil {
|
||||
session.Rollback()
|
||||
if _, err = sess.Exec(rawSql, userId); err != nil {
|
||||
sess.Rollback()
|
||||
return err
|
||||
}
|
||||
if _, err = session.Delete(&Watch{RepoId: repoId}); err != nil {
|
||||
session.Rollback()
|
||||
return err
|
||||
}
|
||||
if err = session.Commit(); err != nil {
|
||||
session.Rollback()
|
||||
if err = sess.Commit(); err != nil {
|
||||
sess.Rollback()
|
||||
return err
|
||||
}
|
||||
if err = os.RemoveAll(RepoPath(userName, repo.Name)); err != nil {
|
||||
@@ -447,57 +782,130 @@ func GetRepositoryById(id int64) (*Repository, error) {
|
||||
} else if !has {
|
||||
return nil, ErrRepoNotExist
|
||||
}
|
||||
return repo, err
|
||||
return repo, nil
|
||||
}
|
||||
|
||||
// GetRepositories returns the list of repositories of given user.
|
||||
func GetRepositories(user *User) ([]Repository, error) {
|
||||
repos := make([]Repository, 0, 10)
|
||||
err := orm.Desc("updated").Find(&repos, &Repository{OwnerId: user.Id})
|
||||
// GetRepositories returns a list of repositories of given user.
|
||||
func GetRepositories(uid int64, private bool) ([]*Repository, error) {
|
||||
repos := make([]*Repository, 0, 10)
|
||||
sess := orm.Desc("updated")
|
||||
if !private {
|
||||
sess.Where("is_private=?", false)
|
||||
}
|
||||
|
||||
err := sess.Find(&repos, &Repository{OwnerId: uid})
|
||||
return repos, err
|
||||
}
|
||||
|
||||
// GetRecentUpdatedRepositories returns the list of repositories that are recently updated.
|
||||
func GetRecentUpdatedRepositories() (repos []*Repository, err error) {
|
||||
err = orm.Where("is_private=?", false).Limit(5).Desc("updated").Find(&repos)
|
||||
return repos, err
|
||||
}
|
||||
|
||||
// GetRepositoryCount returns the total number of repositories of user.
|
||||
func GetRepositoryCount(user *User) (int64, error) {
|
||||
return orm.Count(&Repository{OwnerId: user.Id})
|
||||
}
|
||||
|
||||
// GetCollaboratorNames returns a list of user name of repository's collaborators.
|
||||
func GetCollaboratorNames(repoName string) ([]string, error) {
|
||||
accesses := make([]*Access, 0, 10)
|
||||
if err := orm.Find(&accesses, &Access{RepoName: strings.ToLower(repoName)}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
names := make([]string, len(accesses))
|
||||
for i := range accesses {
|
||||
names[i] = accesses[i].UserName
|
||||
}
|
||||
return names, nil
|
||||
}
|
||||
|
||||
// GetCollaborativeRepos returns a list of repositories that user is collaborator.
|
||||
func GetCollaborativeRepos(uname string) ([]*Repository, error) {
|
||||
uname = strings.ToLower(uname)
|
||||
accesses := make([]*Access, 0, 10)
|
||||
if err := orm.Find(&accesses, &Access{UserName: uname}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
repos := make([]*Repository, 0, 10)
|
||||
for _, access := range accesses {
|
||||
if strings.HasPrefix(access.RepoName, uname) {
|
||||
continue
|
||||
}
|
||||
|
||||
infos := strings.Split(access.RepoName, "/")
|
||||
u, err := GetUserByName(infos[0])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
repo, err := GetRepositoryByName(u.Id, infos[1])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
repo.Owner = u
|
||||
repos = append(repos, repo)
|
||||
}
|
||||
return repos, nil
|
||||
}
|
||||
|
||||
// GetCollaborators returns a list of users of repository's collaborators.
|
||||
func GetCollaborators(repoName string) (us []*User, err error) {
|
||||
accesses := make([]*Access, 0, 10)
|
||||
if err = orm.Find(&accesses, &Access{RepoName: strings.ToLower(repoName)}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
us = make([]*User, len(accesses))
|
||||
for i := range accesses {
|
||||
us[i], err = GetUserByName(accesses[i].UserName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return us, nil
|
||||
}
|
||||
|
||||
// Watch is connection request for receiving repository notifycation.
|
||||
type Watch struct {
|
||||
Id int64
|
||||
RepoId int64 `xorm:"UNIQUE(watch)"`
|
||||
UserId int64 `xorm:"UNIQUE(watch)"`
|
||||
RepoId int64 `xorm:"UNIQUE(watch)"`
|
||||
}
|
||||
|
||||
// Watch or unwatch repository.
|
||||
func WatchRepo(userId, repoId int64, watch bool) (err error) {
|
||||
func WatchRepo(uid, rid int64, watch bool) (err error) {
|
||||
if watch {
|
||||
if _, err = orm.Insert(&Watch{RepoId: repoId, UserId: userId}); err != nil {
|
||||
if _, err = orm.Insert(&Watch{RepoId: rid, UserId: uid}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rawSql := "UPDATE `repository` SET num_watches = num_watches + 1 WHERE id = ?"
|
||||
_, err = orm.Exec(rawSql, repoId)
|
||||
_, err = orm.Exec(rawSql, rid)
|
||||
} else {
|
||||
if _, err = orm.Delete(&Watch{0, repoId, userId}); err != nil {
|
||||
if _, err = orm.Delete(&Watch{0, uid, rid}); err != nil {
|
||||
return err
|
||||
}
|
||||
rawSql := "UPDATE `repository` SET num_watches = num_watches - 1 WHERE id = ?"
|
||||
_, err = orm.Exec(rawSql, repoId)
|
||||
_, err = orm.Exec(rawSql, rid)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// GetWatches returns all watches of given repository.
|
||||
func GetWatches(repoId int64) ([]Watch, error) {
|
||||
watches := make([]Watch, 0, 10)
|
||||
err := orm.Find(&watches, &Watch{RepoId: repoId})
|
||||
// GetWatchers returns all watchers of given repository.
|
||||
func GetWatchers(rid int64) ([]*Watch, error) {
|
||||
watches := make([]*Watch, 0, 10)
|
||||
err := orm.Find(&watches, &Watch{RepoId: rid})
|
||||
return watches, err
|
||||
}
|
||||
|
||||
// NotifyWatchers creates batch of actions for every watcher.
|
||||
func NotifyWatchers(act *Action) error {
|
||||
// Add feeds for user self and all watchers.
|
||||
watches, err := GetWatches(act.RepoId)
|
||||
watches, err := GetWatchers(act.RepoId)
|
||||
if err != nil {
|
||||
return errors.New("repo.NotifyWatchers(get watches): " + err.Error())
|
||||
}
|
||||
@@ -513,6 +921,7 @@ func NotifyWatchers(act *Action) error {
|
||||
continue
|
||||
}
|
||||
|
||||
act.Id = 0
|
||||
act.UserId = watches[i].UserId
|
||||
if _, err = orm.InsertOne(act); err != nil {
|
||||
return errors.New("repo.NotifyWatchers(create action): " + err.Error())
|
||||
@@ -522,11 +931,11 @@ func NotifyWatchers(act *Action) error {
|
||||
}
|
||||
|
||||
// IsWatching checks if user has watched given repository.
|
||||
func IsWatching(userId, repoId int64) bool {
|
||||
has, _ := orm.Get(&Watch{0, repoId, userId})
|
||||
func IsWatching(uid, rid int64) bool {
|
||||
has, _ := orm.Get(&Watch{0, uid, rid})
|
||||
return has
|
||||
}
|
||||
|
||||
func ForkRepository(reposName string, userId int64) {
|
||||
func ForkRepository(repoName string, uid int64) {
|
||||
|
||||
}
|
||||
|
||||
101
models/update.go
Normal file
101
models/update.go
Normal file
@@ -0,0 +1,101 @@
|
||||
// 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 (
|
||||
"container/list"
|
||||
"os/exec"
|
||||
"strings"
|
||||
|
||||
qlog "github.com/qiniu/log"
|
||||
|
||||
"github.com/gogits/git"
|
||||
|
||||
"github.com/gogits/gogs/modules/base"
|
||||
)
|
||||
|
||||
func Update(refName, oldCommitId, newCommitId, userName, repoUserName, repoName string, userId int64) {
|
||||
isNew := strings.HasPrefix(oldCommitId, "0000000")
|
||||
if isNew &&
|
||||
strings.HasPrefix(newCommitId, "0000000") {
|
||||
qlog.Fatal("old rev and new rev both 000000")
|
||||
}
|
||||
|
||||
f := RepoPath(repoUserName, repoName)
|
||||
|
||||
gitUpdate := exec.Command("git", "update-server-info")
|
||||
gitUpdate.Dir = f
|
||||
gitUpdate.Run()
|
||||
|
||||
isDel := strings.HasPrefix(newCommitId, "0000000")
|
||||
if isDel {
|
||||
qlog.Info("del rev", refName, "from", userName+"/"+repoName+".git", "by", userId)
|
||||
return
|
||||
}
|
||||
|
||||
repo, err := git.OpenRepository(f)
|
||||
if err != nil {
|
||||
qlog.Fatalf("runUpdate.Open repoId: %v", err)
|
||||
}
|
||||
|
||||
newCommit, err := repo.GetCommit(newCommitId)
|
||||
if err != nil {
|
||||
qlog.Fatalf("runUpdate GetCommit of newCommitId: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
var l *list.List
|
||||
// if a new branch
|
||||
if isNew {
|
||||
l, err = newCommit.CommitsBefore()
|
||||
if err != nil {
|
||||
qlog.Fatalf("Find CommitsBefore erro: %v", err)
|
||||
}
|
||||
} else {
|
||||
l, err = newCommit.CommitsBeforeUntil(oldCommitId)
|
||||
if err != nil {
|
||||
qlog.Fatalf("Find CommitsBeforeUntil erro: %v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
qlog.Fatalf("runUpdate.Commit repoId: %v", err)
|
||||
}
|
||||
|
||||
ru, err := GetUserByName(repoUserName)
|
||||
if err != nil {
|
||||
qlog.Fatalf("runUpdate.GetUserByName: %v", err)
|
||||
}
|
||||
|
||||
repos, err := GetRepositoryByName(ru.Id, repoName)
|
||||
if err != nil {
|
||||
qlog.Fatalf("runUpdate.GetRepositoryByName userId: %v", err)
|
||||
}
|
||||
|
||||
commits := make([]*base.PushCommit, 0)
|
||||
var maxCommits = 3
|
||||
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(),
|
||||
commit.Message(),
|
||||
commit.Author.Email,
|
||||
commit.Author.Name})
|
||||
if len(commits) >= maxCommits {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
//commits = append(commits, []string{lastCommit.Id().String(), lastCommit.Message()})
|
||||
if err = CommitRepoAction(userId, ru.Id, userName, actEmail,
|
||||
repos.Id, repoUserName, repoName, refName, &base.PushCommits{l.Len(), commits}); err != nil {
|
||||
qlog.Fatalf("runUpdate.models.CommitRepoAction: %s/%s:%v", repoUserName, repoName, err)
|
||||
}
|
||||
}
|
||||
233
models/user.go
233
models/user.go
@@ -5,6 +5,7 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
@@ -13,12 +14,11 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/dchest/scrypt"
|
||||
|
||||
"github.com/gogits/git"
|
||||
|
||||
"github.com/gogits/gogs/modules/base"
|
||||
"github.com/gogits/gogs/modules/log"
|
||||
"github.com/gogits/gogs/modules/setting"
|
||||
)
|
||||
|
||||
// User types.
|
||||
@@ -27,19 +27,16 @@ const (
|
||||
UT_ORGANIZATION
|
||||
)
|
||||
|
||||
// Login types.
|
||||
const (
|
||||
LT_PLAIN = iota + 1
|
||||
LT_LDAP
|
||||
)
|
||||
|
||||
var (
|
||||
ErrUserOwnRepos = errors.New("User still have ownership of repositories")
|
||||
ErrUserAlreadyExist = errors.New("User already exist")
|
||||
ErrUserNotExist = errors.New("User does not exist")
|
||||
ErrEmailAlreadyUsed = errors.New("E-mail already used")
|
||||
ErrUserNameIllegal = errors.New("User name contains illegal characters")
|
||||
ErrKeyNotExist = errors.New("Public key does not exist")
|
||||
ErrUserOwnRepos = errors.New("User still have ownership of repositories")
|
||||
ErrUserAlreadyExist = errors.New("User already exist")
|
||||
ErrUserNotExist = errors.New("User does not exist")
|
||||
ErrUserNotKeyOwner = errors.New("User does not the owner of public key")
|
||||
ErrEmailAlreadyUsed = errors.New("E-mail already used")
|
||||
ErrUserNameIllegal = errors.New("User name contains illegal characters")
|
||||
ErrLoginSourceNotExist = errors.New("Login source does not exist")
|
||||
ErrLoginSourceNotActived = errors.New("Login source is not actived")
|
||||
ErrUnsupportedLoginType = errors.New("Login source is unknown")
|
||||
)
|
||||
|
||||
// User represents the object of individual and member of organization.
|
||||
@@ -47,9 +44,12 @@ type User struct {
|
||||
Id int64
|
||||
LowerName string `xorm:"unique not null"`
|
||||
Name string `xorm:"unique not null"`
|
||||
FullName string
|
||||
Email string `xorm:"unique not null"`
|
||||
Passwd string `xorm:"not null"`
|
||||
LoginType int
|
||||
LoginSource int64 `xorm:"not null default 0"`
|
||||
LoginName string
|
||||
Type int
|
||||
NumFollowers int
|
||||
NumFollowings int
|
||||
@@ -62,6 +62,7 @@ type User struct {
|
||||
IsActive bool
|
||||
IsAdmin bool
|
||||
Rands string `xorm:"VARCHAR(10)"`
|
||||
Salt string `xorm:"VARCHAR(10)"`
|
||||
Created time.Time `xorm:"created"`
|
||||
Updated time.Time `xorm:"updated"`
|
||||
}
|
||||
@@ -71,12 +72,14 @@ func (user *User) HomeLink() string {
|
||||
return "/user/" + user.Name
|
||||
}
|
||||
|
||||
// AvatarLink returns the user gravatar link.
|
||||
// AvatarLink returns user gravatar link.
|
||||
func (user *User) AvatarLink() string {
|
||||
if base.Service.EnableCacheAvatar {
|
||||
if setting.DisableGravatar {
|
||||
return "/img/avatar_default.jpg"
|
||||
} else if setting.Service.EnableCacheAvatar {
|
||||
return "/avatar/" + user.Avatar
|
||||
}
|
||||
return "http://1.gravatar.com/avatar/" + user.Avatar
|
||||
return "//1.gravatar.com/avatar/" + user.Avatar
|
||||
}
|
||||
|
||||
// NewGitSig generates and returns the signature of given user.
|
||||
@@ -89,10 +92,9 @@ func (user *User) NewGitSig() *git.Signature {
|
||||
}
|
||||
|
||||
// EncodePasswd encodes password to safe format.
|
||||
func (user *User) EncodePasswd() error {
|
||||
newPasswd, err := scrypt.Key([]byte(user.Passwd), []byte(base.SecretKey), 16384, 8, 1, 64)
|
||||
func (user *User) EncodePasswd() {
|
||||
newPasswd := base.PBKDF2([]byte(user.Passwd), []byte(user.Salt), 10000, 50, sha256.New)
|
||||
user.Passwd = fmt.Sprintf("%x", newPasswd)
|
||||
return err
|
||||
}
|
||||
|
||||
// Member represents user is member of organization.
|
||||
@@ -105,21 +107,28 @@ type Member struct {
|
||||
// IsUserExist checks if given user name exist,
|
||||
// the user name should be noncased unique.
|
||||
func IsUserExist(name string) (bool, error) {
|
||||
if len(name) == 0 {
|
||||
return false, nil
|
||||
}
|
||||
return orm.Get(&User{LowerName: strings.ToLower(name)})
|
||||
}
|
||||
|
||||
// IsEmailUsed returns true if the e-mail has been used.
|
||||
func IsEmailUsed(email string) (bool, error) {
|
||||
if len(email) == 0 {
|
||||
return false, nil
|
||||
}
|
||||
return orm.Get(&User{Email: email})
|
||||
}
|
||||
|
||||
// return a user salt token
|
||||
// GetUserSalt returns a user salt token
|
||||
func GetUserSalt() string {
|
||||
return base.GetRandomString(10)
|
||||
}
|
||||
|
||||
// RegisterUser creates record of a new user.
|
||||
func RegisterUser(user *User) (*User, error) {
|
||||
|
||||
if !IsLegalName(user.Name) {
|
||||
return nil, ErrUserNameIllegal
|
||||
}
|
||||
@@ -142,9 +151,9 @@ func RegisterUser(user *User) (*User, error) {
|
||||
user.Avatar = base.EncodeMd5(user.Email)
|
||||
user.AvatarEmail = user.Email
|
||||
user.Rands = GetUserSalt()
|
||||
if err = user.EncodePasswd(); err != nil {
|
||||
return nil, err
|
||||
} else if _, err = orm.Insert(user); err != nil {
|
||||
user.Salt = GetUserSalt()
|
||||
user.EncodePasswd()
|
||||
if _, err = orm.Insert(user); err != nil {
|
||||
return nil, err
|
||||
} else if err = os.MkdirAll(UserPath(user.Name), os.ModePerm); err != nil {
|
||||
if _, err := orm.Id(user.Id).Delete(&User{}); err != nil {
|
||||
@@ -189,7 +198,7 @@ func getVerifyUser(code string) (user *User) {
|
||||
|
||||
// verify active code when active account
|
||||
func VerifyUserActiveCode(code string) (user *User) {
|
||||
minutes := base.Service.ActiveCodeLives
|
||||
minutes := setting.Service.ActiveCodeLives
|
||||
|
||||
if user = getVerifyUser(code); user != nil {
|
||||
// time limit code
|
||||
@@ -203,16 +212,73 @@ func VerifyUserActiveCode(code string) (user *User) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateUser updates user's information.
|
||||
func UpdateUser(user *User) (err error) {
|
||||
if len(user.Location) > 255 {
|
||||
user.Location = user.Location[:255]
|
||||
}
|
||||
if len(user.Website) > 255 {
|
||||
user.Website = user.Website[:255]
|
||||
// ChangeUserName changes all corresponding setting from old user name to new one.
|
||||
func ChangeUserName(user *User, newUserName string) (err error) {
|
||||
newUserName = strings.ToLower(newUserName)
|
||||
|
||||
// Update accesses of user.
|
||||
accesses := make([]Access, 0, 10)
|
||||
if err = orm.Find(&accesses, &Access{UserName: user.LowerName}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = orm.Id(user.Id).AllCols().Update(user)
|
||||
sess := orm.NewSession()
|
||||
defer sess.Close()
|
||||
if err = sess.Begin(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for i := range accesses {
|
||||
accesses[i].UserName = newUserName
|
||||
if strings.HasPrefix(accesses[i].RepoName, user.LowerName+"/") {
|
||||
accesses[i].RepoName = strings.Replace(accesses[i].RepoName, user.LowerName, newUserName, 1)
|
||||
}
|
||||
if err = UpdateAccessWithSession(sess, &accesses[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
repos, err := GetRepositories(user.Id, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for i := range repos {
|
||||
accesses = make([]Access, 0, 10)
|
||||
// Update accesses of user repository.
|
||||
if err = orm.Find(&accesses, &Access{RepoName: user.LowerName + "/" + repos[i].LowerName}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for j := range accesses {
|
||||
accesses[j].UserName = newUserName
|
||||
accesses[j].RepoName = newUserName + "/" + repos[i].LowerName
|
||||
if err = UpdateAccessWithSession(sess, &accesses[j]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Change user directory name.
|
||||
if err = os.Rename(UserPath(user.LowerName), UserPath(newUserName)); err != nil {
|
||||
sess.Rollback()
|
||||
return err
|
||||
}
|
||||
|
||||
return sess.Commit()
|
||||
}
|
||||
|
||||
// UpdateUser updates user's information.
|
||||
func UpdateUser(u *User) (err error) {
|
||||
u.LowerName = strings.ToLower(u.Name)
|
||||
|
||||
if len(u.Location) > 255 {
|
||||
u.Location = u.Location[:255]
|
||||
}
|
||||
if len(u.Website) > 255 {
|
||||
u.Website = u.Website[:255]
|
||||
}
|
||||
|
||||
_, err = orm.Id(u.Id).AllCols().Update(u)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -228,18 +294,38 @@ func DeleteUser(user *User) error {
|
||||
|
||||
// TODO: check issues, other repos' commits
|
||||
|
||||
// Delete all followers.
|
||||
if _, err = orm.Delete(&Follow{FollowId: user.Id}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Delete oauth2.
|
||||
if _, err = orm.Delete(&Oauth2{Uid: user.Id}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Delete all feeds.
|
||||
if _, err = orm.Delete(&Action{UserId: user.Id}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Delete all watches.
|
||||
if _, err = orm.Delete(&Watch{UserId: user.Id}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Delete all accesses.
|
||||
if _, err = orm.Delete(&Access{UserName: user.LowerName}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Delete all SSH keys.
|
||||
keys := make([]PublicKey, 0, 10)
|
||||
keys := make([]*PublicKey, 0, 10)
|
||||
if err = orm.Find(&keys, &PublicKey{OwnerId: user.Id}); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, key := range keys {
|
||||
if err = DeletePublicKey(&key); err != nil {
|
||||
if err = DeletePublicKey(key); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -250,13 +336,12 @@ func DeleteUser(user *User) error {
|
||||
}
|
||||
|
||||
_, err = orm.Delete(user)
|
||||
// TODO: delete and update follower information.
|
||||
return err
|
||||
}
|
||||
|
||||
// UserPath returns the path absolute path of user repositories.
|
||||
func UserPath(userName string) string {
|
||||
return filepath.Join(base.RepoRootPath, strings.ToLower(userName))
|
||||
return filepath.Join(setting.RepoRootPath, strings.ToLower(userName))
|
||||
}
|
||||
|
||||
func GetUserByKeyId(keyId int64) (*User, error) {
|
||||
@@ -266,23 +351,21 @@ func GetUserByKeyId(keyId int64) (*User, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !has {
|
||||
err = errors.New("not exist key owner")
|
||||
return nil, err
|
||||
return nil, ErrUserNotKeyOwner
|
||||
}
|
||||
return user, nil
|
||||
}
|
||||
|
||||
// GetUserById returns the user object by given id if exists.
|
||||
// GetUserById returns the user object by given ID if exists.
|
||||
func GetUserById(id int64) (*User, error) {
|
||||
user := new(User)
|
||||
has, err := orm.Id(id).Get(user)
|
||||
u := new(User)
|
||||
has, err := orm.Id(id).Get(u)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !has {
|
||||
} else if !has {
|
||||
return nil, ErrUserNotExist
|
||||
}
|
||||
return user, nil
|
||||
return u, nil
|
||||
}
|
||||
|
||||
// GetUserByName returns the user object by given name if exists.
|
||||
@@ -300,20 +383,64 @@ func GetUserByName(name string) (*User, error) {
|
||||
return user, nil
|
||||
}
|
||||
|
||||
// LoginUserPlain validates user by raw user name and password.
|
||||
func LoginUserPlain(name, passwd string) (*User, error) {
|
||||
user := User{LowerName: strings.ToLower(name), Passwd: passwd}
|
||||
if err := user.EncodePasswd(); err != nil {
|
||||
return nil, err
|
||||
// GetUserEmailsByNames returns a slice of e-mails corresponds to names.
|
||||
func GetUserEmailsByNames(names []string) []string {
|
||||
mails := make([]string, 0, len(names))
|
||||
for _, name := range names {
|
||||
u, err := GetUserByName(name)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
mails = append(mails, u.Email)
|
||||
}
|
||||
return mails
|
||||
}
|
||||
|
||||
has, err := orm.Get(&user)
|
||||
// GetUserIdsByNames returns a slice of ids corresponds to names.
|
||||
func GetUserIdsByNames(names []string) []int64 {
|
||||
ids := make([]int64, 0, len(names))
|
||||
for _, name := range names {
|
||||
u, err := GetUserByName(name)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
ids = append(ids, u.Id)
|
||||
}
|
||||
return ids
|
||||
}
|
||||
|
||||
// GetUserByEmail returns the user object by given e-mail if exists.
|
||||
func GetUserByEmail(email string) (*User, error) {
|
||||
if len(email) == 0 {
|
||||
return nil, ErrUserNotExist
|
||||
}
|
||||
user := &User{Email: strings.ToLower(email)}
|
||||
has, err := orm.Get(user)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !has {
|
||||
err = ErrUserNotExist
|
||||
return nil, ErrUserNotExist
|
||||
}
|
||||
return &user, err
|
||||
return user, nil
|
||||
}
|
||||
|
||||
// SearchUserByName returns given number of users whose name contains keyword.
|
||||
func SearchUserByName(key string, limit int) (us []*User, err error) {
|
||||
// Prevent SQL inject.
|
||||
key = strings.TrimSpace(key)
|
||||
if len(key) == 0 {
|
||||
return us, nil
|
||||
}
|
||||
|
||||
key = strings.Split(key, " ")[0]
|
||||
if len(key) == 0 {
|
||||
return us, nil
|
||||
}
|
||||
key = strings.ToLower(key)
|
||||
|
||||
us = make([]*User, 0, limit)
|
||||
err = orm.Limit(limit).Where("lower_name like '%" + key + "%'").Find(&us)
|
||||
return us, err
|
||||
}
|
||||
|
||||
// Follow is connection request for receiving user notifycation.
|
||||
|
||||
100
models/webhook.go
Normal file
100
models/webhook.go
Normal file
@@ -0,0 +1,100 @@
|
||||
// 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 (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
|
||||
"github.com/gogits/gogs/modules/log"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrWebhookNotExist = errors.New("Webhook does not exist")
|
||||
)
|
||||
|
||||
// Content types.
|
||||
const (
|
||||
CT_JSON = iota + 1
|
||||
CT_FORM
|
||||
)
|
||||
|
||||
type HookEvent struct {
|
||||
PushOnly bool `json:"push_only"`
|
||||
}
|
||||
|
||||
type Webhook struct {
|
||||
Id int64
|
||||
RepoId int64
|
||||
Url string `xorm:"TEXT"`
|
||||
ContentType int
|
||||
Secret string `xorm:"TEXT"`
|
||||
Events string `xorm:"TEXT"`
|
||||
*HookEvent `xorm:"-"`
|
||||
IsSsl bool
|
||||
IsActive bool
|
||||
}
|
||||
|
||||
func (w *Webhook) GetEvent() {
|
||||
w.HookEvent = &HookEvent{}
|
||||
if err := json.Unmarshal([]byte(w.Events), w.HookEvent); err != nil {
|
||||
log.Error("webhook.GetEvent(%d): %v", w.Id, err)
|
||||
}
|
||||
}
|
||||
|
||||
func (w *Webhook) SaveEvent() error {
|
||||
data, err := json.Marshal(w.HookEvent)
|
||||
w.Events = string(data)
|
||||
return err
|
||||
}
|
||||
|
||||
func (w *Webhook) HasPushEvent() bool {
|
||||
if w.PushOnly {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// CreateWebhook creates new webhook.
|
||||
func CreateWebhook(w *Webhook) error {
|
||||
_, err := orm.Insert(w)
|
||||
return err
|
||||
}
|
||||
|
||||
// UpdateWebhook updates information of webhook.
|
||||
func UpdateWebhook(w *Webhook) error {
|
||||
_, err := orm.AllCols().Update(w)
|
||||
return err
|
||||
}
|
||||
|
||||
// GetWebhookById returns webhook by given ID.
|
||||
func GetWebhookById(hookId int64) (*Webhook, error) {
|
||||
w := &Webhook{Id: hookId}
|
||||
has, err := orm.Get(w)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !has {
|
||||
return nil, ErrWebhookNotExist
|
||||
}
|
||||
return w, nil
|
||||
}
|
||||
|
||||
// GetActiveWebhooksByRepoId returns all active webhooks of repository.
|
||||
func GetActiveWebhooksByRepoId(repoId int64) (ws []*Webhook, err error) {
|
||||
err = orm.Find(&ws, &Webhook{RepoId: repoId, IsActive: true})
|
||||
return ws, err
|
||||
}
|
||||
|
||||
// GetWebhooksByRepoId returns all webhooks of repository.
|
||||
func GetWebhooksByRepoId(repoId int64) (ws []*Webhook, err error) {
|
||||
err = orm.Find(&ws, &Webhook{RepoId: repoId})
|
||||
return ws, err
|
||||
}
|
||||
|
||||
// DeleteWebhook deletes webhook of repository.
|
||||
func DeleteWebhook(hookId int64) error {
|
||||
_, err := orm.Delete(&Webhook{Id: hookId})
|
||||
return err
|
||||
}
|
||||
@@ -10,19 +10,18 @@ import (
|
||||
|
||||
"github.com/go-martini/martini"
|
||||
|
||||
"github.com/gogits/binding"
|
||||
|
||||
"github.com/gogits/gogs/modules/base"
|
||||
"github.com/gogits/gogs/modules/log"
|
||||
"github.com/gogits/gogs/modules/middleware/binding"
|
||||
)
|
||||
|
||||
type AdminEditUserForm struct {
|
||||
Email string `form:"email" binding:"Required;Email;MaxSize(50)"`
|
||||
Website string `form:"website" binding:"MaxSize(50)"`
|
||||
Location string `form:"location" binding:"MaxSize(50)"`
|
||||
Avatar string `form:"avatar" binding:"Required;Email;MaxSize(50)"`
|
||||
Active string `form:"active"`
|
||||
Admin string `form:"admin"`
|
||||
Email string `form:"email" binding:"Required;Email;MaxSize(50)"`
|
||||
Website string `form:"website" binding:"MaxSize(50)"`
|
||||
Location string `form:"location" binding:"MaxSize(50)"`
|
||||
Avatar string `form:"avatar" binding:"Required;Email;MaxSize(50)"`
|
||||
Active bool `form:"active"`
|
||||
Admin bool `form:"admin"`
|
||||
LoginType int `form:"login_type"`
|
||||
}
|
||||
|
||||
func (f *AdminEditUserForm) Name(field string) string {
|
||||
@@ -36,20 +35,6 @@ func (f *AdminEditUserForm) Name(field string) string {
|
||||
}
|
||||
|
||||
func (f *AdminEditUserForm) Validate(errors *binding.Errors, req *http.Request, context martini.Context) {
|
||||
if req.Method == "GET" || errors.Count() == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
data := context.Get(reflect.TypeOf(base.TmplData{})).Interface().(base.TmplData)
|
||||
data["HasError"] = true
|
||||
AssignForm(f, data)
|
||||
|
||||
if len(errors.Overall) > 0 {
|
||||
for _, err := range errors.Overall {
|
||||
log.Error("AdminEditUserForm.Validate: %v", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
validate(errors, data, f)
|
||||
}
|
||||
|
||||
81
modules/auth/apiv1/miscellaneous.go
Normal file
81
modules/auth/apiv1/miscellaneous.go
Normal file
@@ -0,0 +1,81 @@
|
||||
// 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 (
|
||||
"net/http"
|
||||
"reflect"
|
||||
|
||||
"github.com/go-martini/martini"
|
||||
|
||||
"github.com/gogits/gogs/modules/auth"
|
||||
"github.com/gogits/gogs/modules/base"
|
||||
"github.com/gogits/gogs/modules/log"
|
||||
"github.com/gogits/gogs/modules/middleware/binding"
|
||||
)
|
||||
|
||||
type MarkdownForm struct {
|
||||
Text string `form:"text" binding:"Required"`
|
||||
Mode string `form:"mode"`
|
||||
Context string `form:"context"`
|
||||
}
|
||||
|
||||
func (f *MarkdownForm) Validate(errs *binding.Errors, req *http.Request, ctx martini.Context) {
|
||||
data := ctx.Get(reflect.TypeOf(base.TmplData{})).Interface().(base.TmplData)
|
||||
validateApiReq(errs, data, f)
|
||||
}
|
||||
|
||||
func validateApiReq(errs *binding.Errors, data base.TmplData, f interface{}) {
|
||||
if errs.Count() == 0 {
|
||||
return
|
||||
} else if len(errs.Overall) > 0 {
|
||||
for _, err := range errs.Overall {
|
||||
log.Error("%s: %v", reflect.TypeOf(f), err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
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 err, ok := errs.Fields[field.Name]; ok {
|
||||
switch err {
|
||||
case binding.BindingRequireError:
|
||||
data["ErrorMsg"] = fieldName + " cannot be empty"
|
||||
case binding.BindingAlphaDashError:
|
||||
data["ErrorMsg"] = fieldName + " must be valid alpha or numeric or dash(-_) characters"
|
||||
case binding.BindingAlphaDashDotError:
|
||||
data["ErrorMsg"] = fieldName + " must be valid alpha or numeric or dash(-_) or dot characters"
|
||||
case binding.BindingMinSizeError:
|
||||
data["ErrorMsg"] = fieldName + " must contain at least " + auth.GetMinMaxSize(field) + " characters"
|
||||
case binding.BindingMaxSizeError:
|
||||
data["ErrorMsg"] = fieldName + " must contain at most " + auth.GetMinMaxSize(field) + " characters"
|
||||
case binding.BindingEmailError:
|
||||
data["ErrorMsg"] = fieldName + " is not a valid e-mail address"
|
||||
case binding.BindingUrlError:
|
||||
data["ErrorMsg"] = fieldName + " is not a valid URL"
|
||||
default:
|
||||
data["ErrorMsg"] = "Unknown error: " + err
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,10 +11,9 @@ import (
|
||||
|
||||
"github.com/go-martini/martini"
|
||||
|
||||
"github.com/gogits/binding"
|
||||
|
||||
"github.com/gogits/gogs/modules/base"
|
||||
"github.com/gogits/gogs/modules/log"
|
||||
"github.com/gogits/gogs/modules/middleware/binding"
|
||||
)
|
||||
|
||||
// Web form interface.
|
||||
@@ -23,10 +22,12 @@ type Form interface {
|
||||
}
|
||||
|
||||
type RegisterForm struct {
|
||||
UserName string `form:"username" binding:"Required;AlphaDash;MaxSize(30)"`
|
||||
UserName string `form:"username" binding:"Required;AlphaDashDot;MaxSize(30)"`
|
||||
Email string `form:"email" binding:"Required;Email;MaxSize(50)"`
|
||||
Password string `form:"passwd" binding:"Required;MinSize(6);MaxSize(30)"`
|
||||
RetypePasswd string `form:"retypepasswd"`
|
||||
LoginType string `form:"logintype"`
|
||||
LoginName string `form:"loginname"`
|
||||
}
|
||||
|
||||
func (f *RegisterForm) Name(field string) string {
|
||||
@@ -39,29 +40,15 @@ func (f *RegisterForm) Name(field string) string {
|
||||
return names[field]
|
||||
}
|
||||
|
||||
func (f *RegisterForm) Validate(errors *binding.Errors, req *http.Request, context martini.Context) {
|
||||
if req.Method == "GET" || errors.Count() == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
data := context.Get(reflect.TypeOf(base.TmplData{})).Interface().(base.TmplData)
|
||||
data["HasError"] = true
|
||||
AssignForm(f, data)
|
||||
|
||||
if len(errors.Overall) > 0 {
|
||||
for _, err := range errors.Overall {
|
||||
log.Error("RegisterForm.Validate: %v", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
validate(errors, data, f)
|
||||
func (f *RegisterForm) Validate(errs *binding.Errors, req *http.Request, ctx martini.Context) {
|
||||
data := ctx.Get(reflect.TypeOf(base.TmplData{})).Interface().(base.TmplData)
|
||||
validate(errs, data, f)
|
||||
}
|
||||
|
||||
type LogInForm struct {
|
||||
UserName string `form:"username" binding:"Required;AlphaDash;MaxSize(30)"`
|
||||
UserName string `form:"username" binding:"Required;MaxSize(35)"`
|
||||
Password string `form:"passwd" binding:"Required;MinSize(6);MaxSize(30)"`
|
||||
Remember string `form:"remember"`
|
||||
Remember bool `form:"remember"`
|
||||
}
|
||||
|
||||
func (f *LogInForm) Name(field string) string {
|
||||
@@ -72,26 +59,12 @@ func (f *LogInForm) Name(field string) string {
|
||||
return names[field]
|
||||
}
|
||||
|
||||
func (f *LogInForm) Validate(errors *binding.Errors, req *http.Request, context martini.Context) {
|
||||
if req.Method == "GET" || errors.Count() == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
data := context.Get(reflect.TypeOf(base.TmplData{})).Interface().(base.TmplData)
|
||||
data["HasError"] = true
|
||||
AssignForm(f, data)
|
||||
|
||||
if len(errors.Overall) > 0 {
|
||||
for _, err := range errors.Overall {
|
||||
log.Error("LogInForm.Validate: %v", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
validate(errors, data, f)
|
||||
func (f *LogInForm) Validate(errs *binding.Errors, req *http.Request, ctx martini.Context) {
|
||||
data := ctx.Get(reflect.TypeOf(base.TmplData{})).Interface().(base.TmplData)
|
||||
validate(errs, data, f)
|
||||
}
|
||||
|
||||
func getMinMaxSize(field reflect.StructField) string {
|
||||
func GetMinMaxSize(field reflect.StructField) string {
|
||||
for _, rule := range strings.Split(field.Tag.Get("binding"), ";") {
|
||||
if strings.HasPrefix(rule, "MinSize(") || strings.HasPrefix(rule, "MaxSize(") {
|
||||
return rule[8 : len(rule)-1]
|
||||
@@ -100,9 +73,21 @@ func getMinMaxSize(field reflect.StructField) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func validate(errors *binding.Errors, data base.TmplData, form Form) {
|
||||
typ := reflect.TypeOf(form)
|
||||
val := reflect.ValueOf(form)
|
||||
func validate(errs *binding.Errors, data base.TmplData, f Form) {
|
||||
if errs.Count() == 0 {
|
||||
return
|
||||
} else if len(errs.Overall) > 0 {
|
||||
for _, err := range errs.Overall {
|
||||
log.Error("%s: %v", reflect.TypeOf(f), err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
data["HasError"] = true
|
||||
AssignForm(f, data)
|
||||
|
||||
typ := reflect.TypeOf(f)
|
||||
val := reflect.ValueOf(f)
|
||||
|
||||
if typ.Kind() == reflect.Ptr {
|
||||
typ = typ.Elem()
|
||||
@@ -118,19 +103,23 @@ func validate(errors *binding.Errors, data base.TmplData, form Form) {
|
||||
continue
|
||||
}
|
||||
|
||||
if err, ok := errors.Fields[field.Name]; ok {
|
||||
if err, ok := errs.Fields[field.Name]; ok {
|
||||
data["Err_"+field.Name] = true
|
||||
switch err {
|
||||
case binding.RequireError:
|
||||
data["ErrorMsg"] = form.Name(field.Name) + " cannot be empty"
|
||||
case binding.AlphaDashError:
|
||||
data["ErrorMsg"] = form.Name(field.Name) + " must be valid alpha or numeric or dash(-_) characters"
|
||||
case binding.MinSizeError:
|
||||
data["ErrorMsg"] = form.Name(field.Name) + " must contain at least " + getMinMaxSize(field) + " characters"
|
||||
case binding.MaxSizeError:
|
||||
data["ErrorMsg"] = form.Name(field.Name) + " must contain at most " + getMinMaxSize(field) + " characters"
|
||||
case binding.EmailError:
|
||||
data["ErrorMsg"] = form.Name(field.Name) + " is not valid"
|
||||
case binding.BindingRequireError:
|
||||
data["ErrorMsg"] = f.Name(field.Name) + " cannot be empty"
|
||||
case binding.BindingAlphaDashError:
|
||||
data["ErrorMsg"] = f.Name(field.Name) + " must be valid alpha or numeric or dash(-_) characters"
|
||||
case binding.BindingAlphaDashDotError:
|
||||
data["ErrorMsg"] = f.Name(field.Name) + " must be valid alpha or numeric or dash(-_) or dot characters"
|
||||
case binding.BindingMinSizeError:
|
||||
data["ErrorMsg"] = f.Name(field.Name) + " must contain at least " + GetMinMaxSize(field) + " characters"
|
||||
case binding.BindingMaxSizeError:
|
||||
data["ErrorMsg"] = f.Name(field.Name) + " must contain at most " + GetMinMaxSize(field) + " characters"
|
||||
case binding.BindingEmailError:
|
||||
data["ErrorMsg"] = f.Name(field.Name) + " is not a valid e-mail address"
|
||||
case binding.BindingUrlError:
|
||||
data["ErrorMsg"] = f.Name(field.Name) + " is not a valid URL"
|
||||
default:
|
||||
data["ErrorMsg"] = "Unknown error: " + err
|
||||
}
|
||||
@@ -174,7 +163,7 @@ type InstallForm struct {
|
||||
RunUser string `form:"run_user"`
|
||||
Domain string `form:"domain"`
|
||||
AppUrl string `form:"app_url"`
|
||||
AdminName string `form:"admin_name" binding:"Required"`
|
||||
AdminName string `form:"admin_name" binding:"Required;AlphaDashDot;MaxSize(30)"`
|
||||
AdminPasswd string `form:"admin_pwd" binding:"Required;MinSize(6);MaxSize(30)"`
|
||||
AdminEmail string `form:"admin_email" binding:"Required;Email;MaxSize(50)"`
|
||||
SmtpHost string `form:"smtp_host"`
|
||||
@@ -195,20 +184,6 @@ func (f *InstallForm) Name(field string) string {
|
||||
}
|
||||
|
||||
func (f *InstallForm) Validate(errors *binding.Errors, req *http.Request, context martini.Context) {
|
||||
if req.Method == "GET" || errors.Count() == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
data := context.Get(reflect.TypeOf(base.TmplData{})).Interface().(base.TmplData)
|
||||
data["HasError"] = true
|
||||
AssignForm(f, data)
|
||||
|
||||
if len(errors.Overall) > 0 {
|
||||
for _, err := range errors.Overall {
|
||||
log.Error("InstallForm.Validate: %v", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
validate(errors, data, f)
|
||||
}
|
||||
|
||||
55
modules/auth/authentication.go
Normal file
55
modules/auth/authentication.go
Normal file
@@ -0,0 +1,55 @@
|
||||
// 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 auth
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"reflect"
|
||||
|
||||
"github.com/go-martini/martini"
|
||||
|
||||
"github.com/gogits/gogs/modules/base"
|
||||
"github.com/gogits/gogs/modules/middleware/binding"
|
||||
)
|
||||
|
||||
type AuthenticationForm struct {
|
||||
Id int64 `form:"id"`
|
||||
Type int `form:"type"`
|
||||
AuthName string `form:"name" binding:"Required;MaxSize(50)"`
|
||||
Domain string `form:"domain"`
|
||||
Host string `form:"host"`
|
||||
Port int `form:"port"`
|
||||
UseSSL bool `form:"usessl"`
|
||||
BaseDN string `form:"base_dn"`
|
||||
Attributes string `form:"attributes"`
|
||||
Filter string `form:"filter"`
|
||||
MsAdSA string `form:"ms_ad_sa"`
|
||||
IsActived bool `form:"is_actived"`
|
||||
SmtpAuth string `form:"smtpauth"`
|
||||
SmtpHost string `form:"smtphost"`
|
||||
SmtpPort int `form:"smtpport"`
|
||||
Tls bool `form:"tls"`
|
||||
AllowAutoRegister bool `form:"allowautoregister"`
|
||||
}
|
||||
|
||||
func (f *AuthenticationForm) Name(field string) string {
|
||||
names := map[string]string{
|
||||
"AuthName": "Authentication's name",
|
||||
"Domain": "Domain name",
|
||||
"Host": "Host address",
|
||||
"Port": "Port Number",
|
||||
"UseSSL": "Use SSL",
|
||||
"BaseDN": "Base DN",
|
||||
"Attributes": "Search attributes",
|
||||
"Filter": "Search filter",
|
||||
"MsAdSA": "Ms Ad SA",
|
||||
}
|
||||
return names[field]
|
||||
}
|
||||
|
||||
func (f *AuthenticationForm) Validate(errors *binding.Errors, req *http.Request, context martini.Context) {
|
||||
data := context.Get(reflect.TypeOf(base.TmplData{})).Interface().(base.TmplData)
|
||||
validate(errors, data, f)
|
||||
}
|
||||
@@ -1,51 +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 auth
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"reflect"
|
||||
|
||||
"github.com/go-martini/martini"
|
||||
|
||||
"github.com/gogits/binding"
|
||||
|
||||
"github.com/gogits/gogs/modules/base"
|
||||
"github.com/gogits/gogs/modules/log"
|
||||
)
|
||||
|
||||
type CreateIssueForm struct {
|
||||
IssueName string `form:"title" binding:"Required;MaxSize(50)"`
|
||||
MilestoneId int64 `form:"milestoneid"`
|
||||
AssigneeId int64 `form:"assigneeid"`
|
||||
Labels string `form:"labels"`
|
||||
Content string `form:"content"`
|
||||
}
|
||||
|
||||
func (f *CreateIssueForm) Name(field string) string {
|
||||
names := map[string]string{
|
||||
"IssueName": "Issue name",
|
||||
}
|
||||
return names[field]
|
||||
}
|
||||
|
||||
func (f *CreateIssueForm) Validate(errors *binding.Errors, req *http.Request, context martini.Context) {
|
||||
if req.Method == "GET" || errors.Count() == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
data := context.Get(reflect.TypeOf(base.TmplData{})).Interface().(base.TmplData)
|
||||
data["HasError"] = true
|
||||
AssignForm(f, data)
|
||||
|
||||
if len(errors.Overall) > 0 {
|
||||
for _, err := range errors.Overall {
|
||||
log.Error("CreateIssueForm.Validate: %v", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
validate(errors, data, f)
|
||||
}
|
||||
43
modules/auth/ldap/README.md
Normal file
43
modules/auth/ldap/README.md
Normal file
@@ -0,0 +1,43 @@
|
||||
LDAP authentication
|
||||
===================
|
||||
|
||||
## Goal
|
||||
|
||||
Authenticat user against LDAP directories
|
||||
|
||||
It will bind with the user's login/pasword and query attributs ("mail" for instance) in a pool of directory servers
|
||||
|
||||
The first OK wins.
|
||||
|
||||
If there's connection error, the server will be disabled and won't be checked again
|
||||
|
||||
## Usage
|
||||
|
||||
In the [security] section, set
|
||||
> LDAP_AUTH = true
|
||||
|
||||
then for each LDAP source, set
|
||||
|
||||
> [LdapSource-someuniquename]
|
||||
> name=canonicalName
|
||||
> host=hostname-or-ip
|
||||
> port=3268 # or regular LDAP port
|
||||
> # the following settings depend highly how you've configured your AD
|
||||
> basedn=dc=ACME,dc=COM
|
||||
> MSADSAFORMAT=%s@ACME.COM
|
||||
> filter=(&(objectClass=user)(sAMAccountName=%s))
|
||||
|
||||
### Limitation
|
||||
|
||||
Only tested on an MS 2008R2 DC, using global catalog (TCP/3268)
|
||||
|
||||
This MSAD is a mess.
|
||||
|
||||
The way how one checks the directory (CN, DN etc...) may be highly depending local custom configuration
|
||||
|
||||
### Todo
|
||||
* Define a timeout per server
|
||||
* Check servers marked as "Disabled" when they'll come back online
|
||||
* Find a more flexible way to define filter/MSADSAFORMAT/Attributes etc... maybe text/template ?
|
||||
* Check OpenLDAP server
|
||||
* SSL support ?
|
||||
96
modules/auth/ldap/ldap.go
Normal file
96
modules/auth/ldap/ldap.go
Normal file
@@ -0,0 +1,96 @@
|
||||
// Copyright github.com/juju2013. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// package ldap provide functions & structure to query a LDAP ldap directory
|
||||
// For now, it's mainly tested again an MS Active Directory service, see README.md for more information
|
||||
package ldap
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/gogits/gogs/modules/log"
|
||||
goldap "github.com/juju2013/goldap"
|
||||
)
|
||||
|
||||
// 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
|
||||
BaseDN string // Base DN
|
||||
Attributes string // Attribut to search
|
||||
Filter string // Query filter to validate entry
|
||||
MsAdSAFormat string // in the case of MS AD Simple Authen, the format to use (see: http://msdn.microsoft.com/en-us/library/cc223499.aspx)
|
||||
Enabled bool // if this source is disabled
|
||||
}
|
||||
|
||||
//Global LDAP directory pool
|
||||
var (
|
||||
Authensource []Ldapsource
|
||||
)
|
||||
|
||||
// Add a new source (LDAP directory) to the global pool
|
||||
func AddSource(name string, host string, port int, usessl bool, basedn string, attributes string, filter string, msadsaformat string) {
|
||||
ldaphost := Ldapsource{name, host, port, usessl, basedn, attributes, filter, msadsaformat, true}
|
||||
Authensource = append(Authensource, ldaphost)
|
||||
}
|
||||
|
||||
//LoginUser : try to login an user to LDAP sources, return requested (attribut,true) if ok, ("",false) other wise
|
||||
//First match wins
|
||||
//Returns first attribute if exists
|
||||
func LoginUser(name, passwd string) (a string, r bool) {
|
||||
r = false
|
||||
for _, ls := range Authensource {
|
||||
a, r = ls.SearchEntry(name, passwd)
|
||||
if r {
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// searchEntry : search an LDAP source if an entry (name, passwd) is valide and in the specific filter
|
||||
func (ls Ldapsource) SearchEntry(name, passwd string) (string, bool) {
|
||||
l, err := ldapDial(ls)
|
||||
if err != nil {
|
||||
log.Error("LDAP Connect error, %s:%v", ls.Host, err)
|
||||
ls.Enabled = false
|
||||
return "", false
|
||||
}
|
||||
defer l.Close()
|
||||
|
||||
nx := fmt.Sprintf(ls.MsAdSAFormat, name)
|
||||
err = l.Bind(nx, passwd)
|
||||
if err != nil {
|
||||
log.Debug("LDAP Authan failed for %s, reason: %s", nx, err.Error())
|
||||
return "", false
|
||||
}
|
||||
|
||||
search := goldap.NewSearchRequest(
|
||||
ls.BaseDN,
|
||||
goldap.ScopeWholeSubtree, goldap.NeverDerefAliases, 0, 0, false,
|
||||
fmt.Sprintf(ls.Filter, name),
|
||||
[]string{ls.Attributes},
|
||||
nil)
|
||||
sr, err := l.Search(search)
|
||||
if err != nil {
|
||||
log.Debug("LDAP Authen OK but not in filter %s", name)
|
||||
return "", false
|
||||
}
|
||||
log.Debug("LDAP Authen OK: %s", name)
|
||||
if len(sr.Entries) > 0 {
|
||||
r := sr.Entries[0].GetAttributeValue(ls.Attributes)
|
||||
return r, true
|
||||
}
|
||||
return "", true
|
||||
}
|
||||
|
||||
func ldapDial(ls Ldapsource) (*goldap.Conn, error) {
|
||||
if ls.UseSSL {
|
||||
return goldap.DialTLS("tcp", fmt.Sprintf("%s:%d", ls.Host, ls.Port), nil)
|
||||
} else {
|
||||
return goldap.Dial("tcp", fmt.Sprintf("%s:%d", ls.Host, ls.Port))
|
||||
}
|
||||
}
|
||||
29
modules/auth/ldap/ldap_test.go
Normal file
29
modules/auth/ldap/ldap_test.go
Normal file
@@ -0,0 +1,29 @@
|
||||
package ldap
|
||||
|
||||
// import (
|
||||
// "fmt"
|
||||
// "testing"
|
||||
// )
|
||||
|
||||
// var ldapServer = "ldap.itd.umich.edu"
|
||||
// var ldapPort = 389
|
||||
// var baseDN = "dc=umich,dc=edu"
|
||||
// var filter = []string{
|
||||
// "(cn=cis-fac)",
|
||||
// "(&(objectclass=rfc822mailgroup)(cn=*Computer*))",
|
||||
// "(&(objectclass=rfc822mailgroup)(cn=*Mathematics*))"}
|
||||
// var attributes = []string{
|
||||
// "cn",
|
||||
// "description"}
|
||||
// var msadsaformat = ""
|
||||
|
||||
// func TestLDAP(t *testing.T) {
|
||||
// AddSource("test", ldapServer, ldapPort, baseDN, attributes, filter, msadsaformat)
|
||||
// user, err := LoginUserLdap("xiaolunwen", "")
|
||||
// if err != nil {
|
||||
// t.Error(err)
|
||||
// return
|
||||
// }
|
||||
|
||||
// fmt.Println(user)
|
||||
// }
|
||||
@@ -7,14 +7,11 @@ package auth
|
||||
import (
|
||||
"net/http"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/go-martini/martini"
|
||||
|
||||
"github.com/gogits/binding"
|
||||
|
||||
"github.com/gogits/gogs/modules/base"
|
||||
"github.com/gogits/gogs/modules/log"
|
||||
"github.com/gogits/gogs/modules/middleware/binding"
|
||||
)
|
||||
|
||||
type AddSSHKeyForm struct {
|
||||
@@ -32,24 +29,5 @@ func (f *AddSSHKeyForm) Name(field string) string {
|
||||
|
||||
func (f *AddSSHKeyForm) Validate(errors *binding.Errors, req *http.Request, context martini.Context) {
|
||||
data := context.Get(reflect.TypeOf(base.TmplData{})).Interface().(base.TmplData)
|
||||
AssignForm(f, data)
|
||||
|
||||
if req.Method == "GET" || errors.Count() == 0 {
|
||||
if req.Method == "POST" &&
|
||||
(len(f.KeyContent) < 100 || !strings.HasPrefix(f.KeyContent, "ssh-rsa")) {
|
||||
data["HasError"] = true
|
||||
data["ErrorMsg"] = "SSH key content is not valid"
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
data["HasError"] = true
|
||||
if len(errors.Overall) > 0 {
|
||||
for _, err := range errors.Overall {
|
||||
log.Error("AddSSHKeyForm.Validate: %v", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
validate(errors, data, f)
|
||||
}
|
||||
@@ -10,19 +10,24 @@ import (
|
||||
|
||||
"github.com/go-martini/martini"
|
||||
|
||||
"github.com/gogits/binding"
|
||||
|
||||
"github.com/gogits/gogs/modules/base"
|
||||
"github.com/gogits/gogs/modules/log"
|
||||
"github.com/gogits/gogs/modules/middleware/binding"
|
||||
)
|
||||
|
||||
// __________ .__ __
|
||||
// \______ \ ____ ______ ____ _____|__|/ |_ ___________ ___.__.
|
||||
// | _// __ \\____ \ / _ \/ ___/ \ __\/ _ \_ __ < | |
|
||||
// | | \ ___/| |_> > <_> )___ \| || | ( <_> ) | \/\___ |
|
||||
// |____|_ /\___ > __/ \____/____ >__||__| \____/|__| / ____|
|
||||
// \/ \/|__| \/ \/
|
||||
|
||||
type CreateRepoForm struct {
|
||||
RepoName string `form:"repo" binding:"Required;AlphaDash"`
|
||||
Visibility string `form:"visibility"`
|
||||
RepoName string `form:"repo" binding:"Required;AlphaDash;MaxSize(100)"`
|
||||
Private bool `form:"private"`
|
||||
Description string `form:"desc" binding:"MaxSize(100)"`
|
||||
Language string `form:"language"`
|
||||
License string `form:"license"`
|
||||
InitReadme string `form:"initReadme"`
|
||||
InitReadme bool `form:"initReadme"`
|
||||
}
|
||||
|
||||
func (f *CreateRepoForm) Name(field string) string {
|
||||
@@ -34,20 +39,187 @@ func (f *CreateRepoForm) Name(field string) string {
|
||||
}
|
||||
|
||||
func (f *CreateRepoForm) Validate(errors *binding.Errors, req *http.Request, context martini.Context) {
|
||||
if req.Method == "GET" || errors.Count() == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
data := context.Get(reflect.TypeOf(base.TmplData{})).Interface().(base.TmplData)
|
||||
data["HasError"] = true
|
||||
AssignForm(f, data)
|
||||
|
||||
if len(errors.Overall) > 0 {
|
||||
for _, err := range errors.Overall {
|
||||
log.Error("CreateRepoForm.Validate: %v", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
validate(errors, data, f)
|
||||
}
|
||||
|
||||
type MigrateRepoForm struct {
|
||||
Url string `form:"url" binding:"Url"`
|
||||
AuthUserName string `form:"auth_username"`
|
||||
AuthPasswd string `form:"auth_password"`
|
||||
RepoName string `form:"repo" binding:"Required;AlphaDash;MaxSize(100)"`
|
||||
Mirror bool `form:"mirror"`
|
||||
Private bool `form:"private"`
|
||||
Description string `form:"desc" binding:"MaxSize(100)"`
|
||||
}
|
||||
|
||||
func (f *MigrateRepoForm) Name(field string) string {
|
||||
names := map[string]string{
|
||||
"Url": "Migration URL",
|
||||
"RepoName": "Repository name",
|
||||
"Description": "Description",
|
||||
}
|
||||
return names[field]
|
||||
}
|
||||
|
||||
func (f *MigrateRepoForm) Validate(errors *binding.Errors, req *http.Request, context martini.Context) {
|
||||
data := context.Get(reflect.TypeOf(base.TmplData{})).Interface().(base.TmplData)
|
||||
validate(errors, data, f)
|
||||
}
|
||||
|
||||
type RepoSettingForm struct {
|
||||
RepoName string `form:"name" binding:"Required;AlphaDash;MaxSize(100)"`
|
||||
Description string `form:"desc" binding:"MaxSize(100)"`
|
||||
Website string `form:"site" binding:"Url;MaxSize(100)"`
|
||||
Branch string `form:"branch"`
|
||||
Interval int `form:"interval"`
|
||||
Private bool `form:"private"`
|
||||
GoGet bool `form:"goget"`
|
||||
}
|
||||
|
||||
func (f *RepoSettingForm) Name(field string) string {
|
||||
names := map[string]string{
|
||||
"RepoName": "Repository name",
|
||||
"Description": "Description",
|
||||
"Website": "Website address",
|
||||
}
|
||||
return names[field]
|
||||
}
|
||||
|
||||
func (f *RepoSettingForm) Validate(errors *binding.Errors, req *http.Request, context martini.Context) {
|
||||
data := context.Get(reflect.TypeOf(base.TmplData{})).Interface().(base.TmplData)
|
||||
validate(errors, data, f)
|
||||
}
|
||||
|
||||
// __ __ ___. .__ .__ __
|
||||
// / \ / \ ____\_ |__ | |__ | |__ ____ | | __
|
||||
// \ \/\/ // __ \| __ \| | \| | \ / _ \| |/ /
|
||||
// \ /\ ___/| \_\ \ Y \ Y ( <_> ) <
|
||||
// \__/\ / \___ >___ /___| /___| /\____/|__|_ \
|
||||
// \/ \/ \/ \/ \/ \/
|
||||
|
||||
type NewWebhookForm struct {
|
||||
Url string `form:"url" binding:"Required;Url"`
|
||||
ContentType string `form:"content_type" binding:"Required"`
|
||||
Secret string `form:"secret""`
|
||||
PushOnly bool `form:"push_only"`
|
||||
Active bool `form:"active"`
|
||||
}
|
||||
|
||||
func (f *NewWebhookForm) Name(field string) string {
|
||||
names := map[string]string{
|
||||
"Url": "Payload URL",
|
||||
"ContentType": "Content type",
|
||||
}
|
||||
return names[field]
|
||||
}
|
||||
|
||||
func (f *NewWebhookForm) Validate(errors *binding.Errors, req *http.Request, context martini.Context) {
|
||||
data := context.Get(reflect.TypeOf(base.TmplData{})).Interface().(base.TmplData)
|
||||
validate(errors, data, f)
|
||||
}
|
||||
|
||||
// .___
|
||||
// | | ______ ________ __ ____
|
||||
// | |/ ___// ___/ | \_/ __ \
|
||||
// | |\___ \ \___ \| | /\ ___/
|
||||
// |___/____ >____ >____/ \___ >
|
||||
// \/ \/ \/
|
||||
|
||||
type CreateIssueForm struct {
|
||||
IssueName string `form:"title" binding:"Required;MaxSize(50)"`
|
||||
MilestoneId int64 `form:"milestoneid"`
|
||||
AssigneeId int64 `form:"assigneeid"`
|
||||
Labels string `form:"labels"`
|
||||
Content string `form:"content"`
|
||||
}
|
||||
|
||||
func (f *CreateIssueForm) Name(field string) string {
|
||||
names := map[string]string{
|
||||
"IssueName": "Issue name",
|
||||
}
|
||||
return names[field]
|
||||
}
|
||||
|
||||
func (f *CreateIssueForm) Validate(errors *binding.Errors, req *http.Request, context martini.Context) {
|
||||
data := context.Get(reflect.TypeOf(base.TmplData{})).Interface().(base.TmplData)
|
||||
validate(errors, data, f)
|
||||
}
|
||||
|
||||
// _____ .__.__ __
|
||||
// / \ |__| | ____ _______/ |_ ____ ____ ____
|
||||
// / \ / \| | | _/ __ \ / ___/\ __\/ _ \ / \_/ __ \
|
||||
// / Y \ | |_\ ___/ \___ \ | | ( <_> ) | \ ___/
|
||||
// \____|__ /__|____/\___ >____ > |__| \____/|___| /\___ >
|
||||
// \/ \/ \/ \/ \/
|
||||
|
||||
type CreateMilestoneForm struct {
|
||||
Title string `form:"title" binding:"Required;MaxSize(50)"`
|
||||
Content string `form:"content"`
|
||||
Deadline string `form:"due_date"`
|
||||
}
|
||||
|
||||
func (f *CreateMilestoneForm) Name(field string) string {
|
||||
names := map[string]string{
|
||||
"Title": "Milestone name",
|
||||
}
|
||||
return names[field]
|
||||
}
|
||||
|
||||
func (f *CreateMilestoneForm) Validate(errors *binding.Errors, req *http.Request, context martini.Context) {
|
||||
data := context.Get(reflect.TypeOf(base.TmplData{})).Interface().(base.TmplData)
|
||||
validate(errors, data, f)
|
||||
}
|
||||
|
||||
// .____ ___. .__
|
||||
// | | _____ \_ |__ ____ | |
|
||||
// | | \__ \ | __ \_/ __ \| |
|
||||
// | |___ / __ \| \_\ \ ___/| |__
|
||||
// |_______ (____ /___ /\___ >____/
|
||||
// \/ \/ \/ \/
|
||||
|
||||
type CreateLabelForm struct {
|
||||
Title string `form:"title" binding:"Required;MaxSize(50)"`
|
||||
Color string `form:"color" binding:"Required;Size(7)"`
|
||||
}
|
||||
|
||||
func (f *CreateLabelForm) Name(field string) string {
|
||||
names := map[string]string{
|
||||
"Title": "Label name",
|
||||
"Color": "Label color",
|
||||
}
|
||||
return names[field]
|
||||
}
|
||||
|
||||
func (f *CreateLabelForm) Validate(errors *binding.Errors, req *http.Request, context martini.Context) {
|
||||
data := context.Get(reflect.TypeOf(base.TmplData{})).Interface().(base.TmplData)
|
||||
validate(errors, data, f)
|
||||
}
|
||||
|
||||
// __________ .__
|
||||
// \______ \ ____ | | ____ _____ ______ ____
|
||||
// | _// __ \| | _/ __ \\__ \ / ___// __ \
|
||||
// | | \ ___/| |_\ ___/ / __ \_\___ \\ ___/
|
||||
// |____|_ /\___ >____/\___ >____ /____ >\___ >
|
||||
// \/ \/ \/ \/ \/ \/
|
||||
|
||||
type NewReleaseForm struct {
|
||||
TagName string `form:"tag_name" binding:"Required"`
|
||||
Title string `form:"title" binding:"Required"`
|
||||
Content string `form:"content" binding:"Required"`
|
||||
Prerelease bool `form:"prerelease"`
|
||||
}
|
||||
|
||||
func (f *NewReleaseForm) Name(field string) string {
|
||||
names := map[string]string{
|
||||
"TagName": "Tag name",
|
||||
"Title": "Release title",
|
||||
"Content": "Release content",
|
||||
}
|
||||
return names[field]
|
||||
}
|
||||
|
||||
func (f *NewReleaseForm) Validate(errors *binding.Errors, req *http.Request, context martini.Context) {
|
||||
data := context.Get(reflect.TypeOf(base.TmplData{})).Interface().(base.TmplData)
|
||||
validate(errors, data, f)
|
||||
}
|
||||
|
||||
@@ -10,63 +10,63 @@ import (
|
||||
|
||||
"github.com/go-martini/martini"
|
||||
|
||||
"github.com/gogits/binding"
|
||||
"github.com/gogits/session"
|
||||
|
||||
"github.com/gogits/gogs/models"
|
||||
"github.com/gogits/gogs/modules/base"
|
||||
"github.com/gogits/gogs/modules/log"
|
||||
"github.com/gogits/gogs/modules/middleware/binding"
|
||||
)
|
||||
|
||||
// SignedInId returns the id of signed in user.
|
||||
func SignedInId(session session.SessionStore) int64 {
|
||||
func SignedInId(sess session.SessionStore) int64 {
|
||||
if !models.HasEngine {
|
||||
return 0
|
||||
}
|
||||
|
||||
userId := session.Get("userId")
|
||||
if userId == nil {
|
||||
uid := sess.Get("userId")
|
||||
if uid == nil {
|
||||
return 0
|
||||
}
|
||||
if s, ok := userId.(int64); ok {
|
||||
if _, err := models.GetUserById(s); err != nil {
|
||||
if id, ok := uid.(int64); ok {
|
||||
if _, err := models.GetUserById(id); err != nil {
|
||||
return 0
|
||||
}
|
||||
return s
|
||||
return id
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// SignedInName returns the name of signed in user.
|
||||
func SignedInName(session session.SessionStore) string {
|
||||
userName := session.Get("userName")
|
||||
if userName == nil {
|
||||
func SignedInName(sess session.SessionStore) string {
|
||||
uname := sess.Get("userName")
|
||||
if uname == nil {
|
||||
return ""
|
||||
}
|
||||
if s, ok := userName.(string); ok {
|
||||
if s, ok := uname.(string); ok {
|
||||
return s
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// SignedInUser returns the user object of signed user.
|
||||
func SignedInUser(session session.SessionStore) *models.User {
|
||||
id := SignedInId(session)
|
||||
if id <= 0 {
|
||||
func SignedInUser(sess session.SessionStore) *models.User {
|
||||
uid := SignedInId(sess)
|
||||
if uid <= 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
user, err := models.GetUserById(id)
|
||||
u, err := models.GetUserById(uid)
|
||||
if err != nil {
|
||||
log.Error("user.SignedInUser: %v", err)
|
||||
return nil
|
||||
}
|
||||
return user
|
||||
return u
|
||||
}
|
||||
|
||||
// IsSignedIn check if any user has signed in.
|
||||
func IsSignedIn(session session.SessionStore) bool {
|
||||
return SignedInId(session) > 0
|
||||
func IsSignedIn(sess session.SessionStore) bool {
|
||||
return SignedInId(sess) > 0
|
||||
}
|
||||
|
||||
type FeedsForm struct {
|
||||
@@ -75,14 +75,17 @@ type FeedsForm struct {
|
||||
}
|
||||
|
||||
type UpdateProfileForm struct {
|
||||
UserName string `form:"username" binding:"Required;AlphaDash;MaxSize(30)"`
|
||||
FullName string `form:"fullname" binding:"MaxSize(40)"`
|
||||
Email string `form:"email" binding:"Required;Email;MaxSize(50)"`
|
||||
Website string `form:"website" binding:"MaxSize(50)"`
|
||||
Website string `form:"website" binding:"Url;MaxSize(50)"`
|
||||
Location string `form:"location" binding:"MaxSize(50)"`
|
||||
Avatar string `form:"avatar" binding:"Required;Email;MaxSize(50)"`
|
||||
}
|
||||
|
||||
func (f *UpdateProfileForm) Name(field string) string {
|
||||
names := map[string]string{
|
||||
"UserName": "Username",
|
||||
"Email": "E-mail address",
|
||||
"Website": "Website",
|
||||
"Location": "Location",
|
||||
@@ -91,22 +94,9 @@ func (f *UpdateProfileForm) Name(field string) string {
|
||||
return names[field]
|
||||
}
|
||||
|
||||
func (f *UpdateProfileForm) Validate(errors *binding.Errors, req *http.Request, context martini.Context) {
|
||||
if req.Method == "GET" || errors.Count() == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
data := context.Get(reflect.TypeOf(base.TmplData{})).Interface().(base.TmplData)
|
||||
data["HasError"] = true
|
||||
|
||||
if len(errors.Overall) > 0 {
|
||||
for _, err := range errors.Overall {
|
||||
log.Error("UpdateProfileForm.Validate: %v", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
validate(errors, data, f)
|
||||
func (f *UpdateProfileForm) Validate(errs *binding.Errors, req *http.Request, ctx martini.Context) {
|
||||
data := ctx.Get(reflect.TypeOf(base.TmplData{})).Interface().(base.TmplData)
|
||||
validate(errs, data, f)
|
||||
}
|
||||
|
||||
type UpdatePasswdForm struct {
|
||||
@@ -124,20 +114,7 @@ func (f *UpdatePasswdForm) Name(field string) string {
|
||||
return names[field]
|
||||
}
|
||||
|
||||
func (f *UpdatePasswdForm) Validate(errors *binding.Errors, req *http.Request, context martini.Context) {
|
||||
if req.Method == "GET" || errors.Count() == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
data := context.Get(reflect.TypeOf(base.TmplData{})).Interface().(base.TmplData)
|
||||
data["HasError"] = true
|
||||
|
||||
if len(errors.Overall) > 0 {
|
||||
for _, err := range errors.Overall {
|
||||
log.Error("UpdatePasswdForm.Validate: %v", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
validate(errors, data, f)
|
||||
func (f *UpdatePasswdForm) Validate(errs *binding.Errors, req *http.Request, ctx martini.Context) {
|
||||
data := ctx.Get(reflect.TypeOf(base.TmplData{})).Interface().(base.TmplData)
|
||||
validate(errs, data, f)
|
||||
}
|
||||
|
||||
@@ -157,9 +157,9 @@ func (this *service) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
avatar := New(hash, this.cacheDir)
|
||||
avatar.AlterImage = this.altImage
|
||||
if avatar.Expired() {
|
||||
err := avatar.UpdateTimeout(time.Millisecond * 500)
|
||||
if err != nil {
|
||||
if err := avatar.UpdateTimeout(time.Millisecond * 1000); err != nil {
|
||||
log.Trace("avatar update error: %v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
if modtime, err := avatar.Modtime(); err == nil {
|
||||
@@ -250,6 +250,7 @@ func (this *thunderTask) Fetch() {
|
||||
var client = &http.Client{}
|
||||
|
||||
func (this *thunderTask) fetch() error {
|
||||
log.Debug("avatar.fetch(fetch new avatar): %s", this.Url)
|
||||
req, _ := http.NewRequest("GET", this.Url, nil)
|
||||
req.Header.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/jpeg,image/png,*/*;q=0.8")
|
||||
req.Header.Set("Accept-Encoding", "deflate,sdch")
|
||||
|
||||
@@ -7,4 +7,11 @@ package base
|
||||
type (
|
||||
// Type TmplData represents data in the templates.
|
||||
TmplData map[string]interface{}
|
||||
|
||||
ApiJsonErr struct {
|
||||
Message string `json:"message"`
|
||||
DocUrl string `json:"documentation_url"`
|
||||
}
|
||||
)
|
||||
|
||||
var GoGetMetas = make(map[string]bool)
|
||||
|
||||
@@ -1,315 +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 base
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/Unknwon/com"
|
||||
"github.com/Unknwon/goconfig"
|
||||
|
||||
"github.com/gogits/cache"
|
||||
"github.com/gogits/session"
|
||||
|
||||
"github.com/gogits/gogs/modules/log"
|
||||
)
|
||||
|
||||
// Mailer represents a mail service.
|
||||
type Mailer struct {
|
||||
Name string
|
||||
Host string
|
||||
User, Passwd string
|
||||
}
|
||||
|
||||
var (
|
||||
AppVer string
|
||||
AppName string
|
||||
AppLogo string
|
||||
AppUrl string
|
||||
Domain string
|
||||
SecretKey string
|
||||
RunUser string
|
||||
RepoRootPath string
|
||||
|
||||
InstallLock bool
|
||||
|
||||
LogInRememberDays int
|
||||
CookieUserName string
|
||||
CookieRememberName string
|
||||
|
||||
Cfg *goconfig.ConfigFile
|
||||
MailService *Mailer
|
||||
|
||||
LogMode string
|
||||
LogConfig string
|
||||
|
||||
Cache cache.Cache
|
||||
CacheAdapter string
|
||||
CacheConfig string
|
||||
|
||||
SessionProvider string
|
||||
SessionConfig *session.Config
|
||||
SessionManager *session.Manager
|
||||
|
||||
PictureService string
|
||||
)
|
||||
|
||||
var Service struct {
|
||||
RegisterEmailConfirm bool
|
||||
DisenableRegisteration bool
|
||||
RequireSignInView bool
|
||||
EnableCacheAvatar bool
|
||||
NotifyMail bool
|
||||
ActiveCodeLives int
|
||||
ResetPwdCodeLives int
|
||||
}
|
||||
|
||||
func ExecDir() (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 path.Dir(strings.Replace(p, "\\", "/", -1)), nil
|
||||
}
|
||||
|
||||
var logLevels = map[string]string{
|
||||
"Trace": "0",
|
||||
"Debug": "1",
|
||||
"Info": "2",
|
||||
"Warn": "3",
|
||||
"Error": "4",
|
||||
"Critical": "5",
|
||||
}
|
||||
|
||||
func newService() {
|
||||
Service.ActiveCodeLives = Cfg.MustInt("service", "ACTIVE_CODE_LIVE_MINUTES", 180)
|
||||
Service.ResetPwdCodeLives = Cfg.MustInt("service", "RESET_PASSWD_CODE_LIVE_MINUTES", 180)
|
||||
Service.DisenableRegisteration = Cfg.MustBool("service", "DISENABLE_REGISTERATION", false)
|
||||
Service.RequireSignInView = Cfg.MustBool("service", "REQUIRE_SIGNIN_VIEW", false)
|
||||
Service.EnableCacheAvatar = Cfg.MustBool("service", "ENABLE_CACHE_AVATAR", false)
|
||||
}
|
||||
|
||||
func newLogService() {
|
||||
// Get and check log mode.
|
||||
LogMode = Cfg.MustValue("log", "MODE", "console")
|
||||
modeSec := "log." + LogMode
|
||||
if _, err := Cfg.GetSection(modeSec); err != nil {
|
||||
fmt.Printf("Unknown log mode: %s\n", LogMode)
|
||||
os.Exit(2)
|
||||
}
|
||||
|
||||
// Log level.
|
||||
levelName := Cfg.MustValue("log."+LogMode, "LEVEL", "Trace")
|
||||
level, ok := logLevels[levelName]
|
||||
if !ok {
|
||||
fmt.Printf("Unknown log level: %s\n", levelName)
|
||||
os.Exit(2)
|
||||
}
|
||||
|
||||
// Generate log configuration.
|
||||
switch LogMode {
|
||||
case "console":
|
||||
LogConfig = fmt.Sprintf(`{"level":%s}`, level)
|
||||
case "file":
|
||||
logPath := Cfg.MustValue(modeSec, "FILE_NAME", "log/gogs.log")
|
||||
os.MkdirAll(path.Dir(logPath), os.ModePerm)
|
||||
LogConfig = fmt.Sprintf(
|
||||
`{"level":%s,"filename":"%s","rotate":%v,"maxlines":%d,"maxsize":%d,"daily":%v,"maxdays":%d}`, level,
|
||||
logPath,
|
||||
Cfg.MustBool(modeSec, "LOG_ROTATE", true),
|
||||
Cfg.MustInt(modeSec, "MAX_LINES", 1000000),
|
||||
1<<uint(Cfg.MustInt(modeSec, "MAX_SIZE_SHIFT", 28)),
|
||||
Cfg.MustBool(modeSec, "DAILY_ROTATE", true),
|
||||
Cfg.MustInt(modeSec, "MAX_DAYS", 7))
|
||||
case "conn":
|
||||
LogConfig = fmt.Sprintf(`{"level":"%s","reconnectOnMsg":%v,"reconnect":%v,"net":"%s","addr":"%s"}`, level,
|
||||
Cfg.MustBool(modeSec, "RECONNECT_ON_MSG", false),
|
||||
Cfg.MustBool(modeSec, "RECONNECT", false),
|
||||
Cfg.MustValue(modeSec, "PROTOCOL", "tcp"),
|
||||
Cfg.MustValue(modeSec, "ADDR", ":7020"))
|
||||
case "smtp":
|
||||
LogConfig = fmt.Sprintf(`{"level":"%s","username":"%s","password":"%s","host":"%s","sendTos":"%s","subject":"%s"}`, level,
|
||||
Cfg.MustValue(modeSec, "USER", "example@example.com"),
|
||||
Cfg.MustValue(modeSec, "PASSWD", "******"),
|
||||
Cfg.MustValue(modeSec, "HOST", "127.0.0.1:25"),
|
||||
Cfg.MustValue(modeSec, "RECEIVERS", "[]"),
|
||||
Cfg.MustValue(modeSec, "SUBJECT", "Diagnostic message from serve"))
|
||||
case "database":
|
||||
LogConfig = fmt.Sprintf(`{"level":"%s","driver":"%s","conn":"%s"}`, level,
|
||||
Cfg.MustValue(modeSec, "Driver"),
|
||||
Cfg.MustValue(modeSec, "CONN"))
|
||||
}
|
||||
|
||||
log.NewLogger(Cfg.MustInt64("log", "BUFFER_LEN", 10000), LogMode, LogConfig)
|
||||
log.Info("Log Mode: %s(%s)", strings.Title(LogMode), levelName)
|
||||
}
|
||||
|
||||
func newCacheService() {
|
||||
CacheAdapter = Cfg.MustValue("cache", "ADAPTER", "memory")
|
||||
|
||||
switch CacheAdapter {
|
||||
case "memory":
|
||||
CacheConfig = fmt.Sprintf(`{"interval":%d}`, Cfg.MustInt("cache", "INTERVAL", 60))
|
||||
case "redis", "memcache":
|
||||
CacheConfig = fmt.Sprintf(`{"conn":"%s"}`, Cfg.MustValue("cache", "HOST"))
|
||||
default:
|
||||
fmt.Printf("Unknown cache adapter: %s\n", CacheAdapter)
|
||||
os.Exit(2)
|
||||
}
|
||||
|
||||
var err error
|
||||
Cache, err = cache.NewCache(CacheAdapter, CacheConfig)
|
||||
if err != nil {
|
||||
fmt.Printf("Init cache system failed, adapter: %s, config: %s, %v\n",
|
||||
CacheAdapter, CacheConfig, err)
|
||||
os.Exit(2)
|
||||
}
|
||||
|
||||
log.Info("Cache Service Enabled")
|
||||
}
|
||||
|
||||
func newSessionService() {
|
||||
SessionProvider = Cfg.MustValue("session", "PROVIDER", "memory")
|
||||
|
||||
SessionConfig = new(session.Config)
|
||||
SessionConfig.ProviderConfig = Cfg.MustValue("session", "PROVIDER_CONFIG")
|
||||
SessionConfig.CookieName = Cfg.MustValue("session", "COOKIE_NAME", "i_like_gogits")
|
||||
SessionConfig.CookieSecure = Cfg.MustBool("session", "COOKIE_SECURE")
|
||||
SessionConfig.EnableSetCookie = Cfg.MustBool("session", "ENABLE_SET_COOKIE", true)
|
||||
SessionConfig.GcIntervalTime = Cfg.MustInt64("session", "GC_INTERVAL_TIME", 86400)
|
||||
SessionConfig.SessionLifeTime = Cfg.MustInt64("session", "SESSION_LIFE_TIME", 86400)
|
||||
SessionConfig.SessionIDHashFunc = Cfg.MustValue("session", "SESSION_ID_HASHFUNC", "sha1")
|
||||
SessionConfig.SessionIDHashKey = Cfg.MustValue("session", "SESSION_ID_HASHKEY")
|
||||
|
||||
if SessionProvider == "file" {
|
||||
os.MkdirAll(path.Dir(SessionConfig.ProviderConfig), os.ModePerm)
|
||||
}
|
||||
|
||||
var err error
|
||||
SessionManager, err = session.NewManager(SessionProvider, *SessionConfig)
|
||||
if err != nil {
|
||||
fmt.Printf("Init session system failed, provider: %s, %v\n",
|
||||
SessionProvider, err)
|
||||
os.Exit(2)
|
||||
}
|
||||
|
||||
log.Info("Session Service Enabled")
|
||||
}
|
||||
|
||||
func newMailService() {
|
||||
// Check mailer setting.
|
||||
if Cfg.MustBool("mailer", "ENABLED") {
|
||||
MailService = &Mailer{
|
||||
Name: Cfg.MustValue("mailer", "NAME", AppName),
|
||||
Host: Cfg.MustValue("mailer", "HOST"),
|
||||
User: Cfg.MustValue("mailer", "USER"),
|
||||
Passwd: Cfg.MustValue("mailer", "PASSWD"),
|
||||
}
|
||||
log.Info("Mail Service Enabled")
|
||||
}
|
||||
}
|
||||
|
||||
func newRegisterMailService() {
|
||||
if !Cfg.MustBool("service", "REGISTER_EMAIL_CONFIRM") {
|
||||
return
|
||||
} else if MailService == nil {
|
||||
log.Warn("Register Mail Service: Mail Service is not enabled")
|
||||
return
|
||||
}
|
||||
Service.RegisterEmailConfirm = true
|
||||
log.Info("Register Mail Service Enabled")
|
||||
}
|
||||
|
||||
func newNotifyMailService() {
|
||||
if !Cfg.MustBool("service", "ENABLE_NOTIFY_MAIL") {
|
||||
return
|
||||
} else if MailService == nil {
|
||||
log.Warn("Notify Mail Service: Mail Service is not enabled")
|
||||
return
|
||||
}
|
||||
Service.NotifyMail = true
|
||||
log.Info("Notify Mail Service Enabled")
|
||||
}
|
||||
|
||||
func NewConfigContext() {
|
||||
//var err error
|
||||
workDir, err := ExecDir()
|
||||
if err != nil {
|
||||
fmt.Printf("Fail to get work directory: %s\n", err)
|
||||
os.Exit(2)
|
||||
}
|
||||
|
||||
cfgPath := filepath.Join(workDir, "conf/app.ini")
|
||||
Cfg, err = goconfig.LoadConfigFile(cfgPath)
|
||||
if err != nil {
|
||||
fmt.Printf("Cannot load config file(%s): %v\n", cfgPath, err)
|
||||
os.Exit(2)
|
||||
}
|
||||
Cfg.BlockMode = false
|
||||
|
||||
cfgPath = filepath.Join(workDir, "custom/conf/app.ini")
|
||||
if com.IsFile(cfgPath) {
|
||||
if err = Cfg.AppendFiles(cfgPath); err != nil {
|
||||
fmt.Printf("Cannot load config file(%s): %v\n", cfgPath, err)
|
||||
os.Exit(2)
|
||||
}
|
||||
}
|
||||
|
||||
AppName = Cfg.MustValue("", "APP_NAME", "Gogs: Go Git Service")
|
||||
AppLogo = Cfg.MustValue("", "APP_LOGO", "img/favicon.png")
|
||||
AppUrl = Cfg.MustValue("server", "ROOT_URL")
|
||||
Domain = Cfg.MustValue("server", "DOMAIN")
|
||||
SecretKey = Cfg.MustValue("security", "SECRET_KEY")
|
||||
|
||||
InstallLock = Cfg.MustBool("security", "INSTALL_LOCK", false)
|
||||
|
||||
RunUser = Cfg.MustValue("", "RUN_USER")
|
||||
curUser := os.Getenv("USERNAME")
|
||||
if len(curUser) == 0 {
|
||||
curUser = os.Getenv("USER")
|
||||
}
|
||||
// Does not check run user when the install lock is off.
|
||||
if InstallLock && RunUser != curUser {
|
||||
fmt.Printf("Expect user(%s) but current user is: %s\n", RunUser, curUser)
|
||||
os.Exit(2)
|
||||
}
|
||||
|
||||
LogInRememberDays = Cfg.MustInt("security", "LOGIN_REMEMBER_DAYS")
|
||||
CookieUserName = Cfg.MustValue("security", "COOKIE_USERNAME")
|
||||
CookieRememberName = Cfg.MustValue("security", "COOKIE_REMEMBER_NAME")
|
||||
|
||||
PictureService = Cfg.MustValue("picture", "SERVICE")
|
||||
|
||||
// Determine and create root git reposiroty path.
|
||||
homeDir, err := com.HomeDir()
|
||||
if err != nil {
|
||||
fmt.Printf("Fail to get home directory): %v\n", err)
|
||||
os.Exit(2)
|
||||
}
|
||||
RepoRootPath = Cfg.MustValue("repository", "ROOT", filepath.Join(homeDir, "git/gogs-repositories"))
|
||||
if err = os.MkdirAll(RepoRootPath, os.ModePerm); err != nil {
|
||||
fmt.Printf("Fail to create RepoRootPath(%s): %v\n", RepoRootPath, err)
|
||||
os.Exit(2)
|
||||
}
|
||||
}
|
||||
|
||||
func NewServices() {
|
||||
newService()
|
||||
newLogService()
|
||||
newCacheService()
|
||||
newSessionService()
|
||||
newMailService()
|
||||
newRegisterMailService()
|
||||
newNotifyMailService()
|
||||
}
|
||||
@@ -6,9 +6,11 @@ package base
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/gogits/gfm"
|
||||
@@ -87,13 +89,75 @@ func (options *CustomRender) Link(out *bytes.Buffer, link []byte, title []byte,
|
||||
options.Renderer.Link(out, link, title, content)
|
||||
}
|
||||
|
||||
func RenderMarkdown(rawBytes []byte, urlPrefix string) []byte {
|
||||
var (
|
||||
MentionPattern = regexp.MustCompile(`@[0-9a-zA-Z_]{1,}`)
|
||||
commitPattern = regexp.MustCompile(`(\s|^)https?.*commit/[0-9a-zA-Z]+(#+[0-9a-zA-Z-]*)?`)
|
||||
issueFullPattern = regexp.MustCompile(`(\s|^)https?.*issues/[0-9]+(#+[0-9a-zA-Z-]*)?`)
|
||||
issueIndexPattern = regexp.MustCompile(`#[0-9]+`)
|
||||
)
|
||||
|
||||
func RenderSpecialLink(rawBytes []byte, urlPrefix string) []byte {
|
||||
buf := bytes.NewBufferString("")
|
||||
inCodeBlock := false
|
||||
codeBlockPrefix := []byte("```")
|
||||
lineBreak := []byte("\n")
|
||||
tab := []byte("\t")
|
||||
lines := bytes.Split(rawBytes, lineBreak)
|
||||
for _, line := range lines {
|
||||
if bytes.HasPrefix(line, codeBlockPrefix) {
|
||||
inCodeBlock = !inCodeBlock
|
||||
}
|
||||
|
||||
if !inCodeBlock && !bytes.HasPrefix(line, tab) {
|
||||
ms := MentionPattern.FindAll(line, -1)
|
||||
for _, m := range ms {
|
||||
line = bytes.Replace(line, m,
|
||||
[]byte(fmt.Sprintf(`<a href="/user/%s">%s</a>`, m[1:], m)), -1)
|
||||
}
|
||||
}
|
||||
|
||||
buf.Write(line)
|
||||
buf.Write(lineBreak)
|
||||
}
|
||||
|
||||
rawBytes = buf.Bytes()
|
||||
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)
|
||||
}
|
||||
ms = issueIndexPattern.FindAll(rawBytes, -1)
|
||||
for _, m := range ms {
|
||||
rawBytes = bytes.Replace(rawBytes, m, []byte(fmt.Sprintf(
|
||||
`<a href="%s/issues/%s">%s</a>`, urlPrefix, m[1:], m)), -1)
|
||||
}
|
||||
return rawBytes
|
||||
}
|
||||
|
||||
func RenderRawMarkdown(body []byte, urlPrefix string) []byte {
|
||||
htmlFlags := 0
|
||||
// htmlFlags |= gfm.HTML_USE_XHTML
|
||||
// htmlFlags |= gfm.HTML_USE_SMARTYPANTS
|
||||
// htmlFlags |= gfm.HTML_SMARTYPANTS_FRACTIONS
|
||||
// htmlFlags |= gfm.HTML_SMARTYPANTS_LATEX_DASHES
|
||||
htmlFlags |= gfm.HTML_SKIP_HTML
|
||||
// htmlFlags |= gfm.HTML_SKIP_HTML
|
||||
htmlFlags |= gfm.HTML_SKIP_STYLE
|
||||
htmlFlags |= gfm.HTML_SKIP_SCRIPT
|
||||
htmlFlags |= gfm.HTML_GITHUB_BLOCKCODE
|
||||
@@ -115,7 +179,16 @@ func RenderMarkdown(rawBytes []byte, urlPrefix string) []byte {
|
||||
extensions |= gfm.EXTENSION_SPACE_HEADERS
|
||||
extensions |= gfm.EXTENSION_NO_EMPTY_LINE_BEFORE_BLOCK
|
||||
|
||||
body := gfm.Markdown(rawBytes, renderer, extensions)
|
||||
|
||||
body = gfm.Markdown(body, renderer, extensions)
|
||||
return body
|
||||
}
|
||||
|
||||
func RenderMarkdown(rawBytes []byte, urlPrefix string) []byte {
|
||||
body := RenderSpecialLink(rawBytes, urlPrefix)
|
||||
body = RenderRawMarkdown(body, urlPrefix)
|
||||
return body
|
||||
}
|
||||
|
||||
func RenderMarkdownString(raw, urlPrefix string) string {
|
||||
return string(RenderMarkdown([]byte(raw), urlPrefix))
|
||||
}
|
||||
|
||||
@@ -5,11 +5,16 @@
|
||||
package base
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"container/list"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gogits/gogs/modules/setting"
|
||||
)
|
||||
|
||||
func Str2html(raw string) template.HTML {
|
||||
@@ -45,14 +50,20 @@ var mailDomains = map[string]string{
|
||||
}
|
||||
|
||||
var TemplateFuncs template.FuncMap = map[string]interface{}{
|
||||
"GoVer": func() string {
|
||||
return runtime.Version()
|
||||
},
|
||||
"AppName": func() string {
|
||||
return AppName
|
||||
return setting.AppName
|
||||
},
|
||||
"AppVer": func() string {
|
||||
return AppVer
|
||||
return setting.AppVer
|
||||
},
|
||||
"AppDomain": func() string {
|
||||
return Domain
|
||||
return setting.Domain
|
||||
},
|
||||
"CdnMode": func() bool {
|
||||
return setting.ProdMode && !setting.OfflineMode
|
||||
},
|
||||
"LoadTimes": func(startTime time.Time) string {
|
||||
return fmt.Sprint(time.Since(startTime).Nanoseconds()/1e6) + "ms"
|
||||
@@ -62,11 +73,18 @@ var TemplateFuncs template.FuncMap = map[string]interface{}{
|
||||
"TimeSince": TimeSince,
|
||||
"FileSize": FileSize,
|
||||
"Subtract": Subtract,
|
||||
"Add": func(a, b int) int {
|
||||
return a + b
|
||||
},
|
||||
"ActionIcon": ActionIcon,
|
||||
"ActionDesc": ActionDesc,
|
||||
"DateFormat": DateFormat,
|
||||
"List": List,
|
||||
"Mail2Domain": func(mail string) string {
|
||||
if !strings.Contains(mail, "@") {
|
||||
return "try.gogits.org"
|
||||
}
|
||||
|
||||
suffix := strings.SplitN(mail, "@", 2)[1]
|
||||
domain, ok := mailDomains[suffix]
|
||||
if !ok {
|
||||
@@ -80,4 +98,155 @@ var TemplateFuncs template.FuncMap = map[string]interface{}{
|
||||
"DiffTypeToStr": DiffTypeToStr,
|
||||
"DiffLineTypeToStr": DiffLineTypeToStr,
|
||||
"ShortSha": ShortSha,
|
||||
"Oauth2Icon": Oauth2Icon,
|
||||
"Oauth2Name": Oauth2Name,
|
||||
}
|
||||
|
||||
type Actioner interface {
|
||||
GetOpType() int
|
||||
GetActUserName() string
|
||||
GetActEmail() string
|
||||
GetRepoUserName() string
|
||||
GetRepoName() string
|
||||
GetBranch() string
|
||||
GetContent() string
|
||||
}
|
||||
|
||||
// ActionIcon accepts a int that represents action operation type
|
||||
// and returns a icon class name.
|
||||
func ActionIcon(opType int) string {
|
||||
switch opType {
|
||||
case 1: // Create repository.
|
||||
return "plus-circle"
|
||||
case 5, 9: // Commit repository.
|
||||
return "arrow-circle-o-right"
|
||||
case 6: // Create issue.
|
||||
return "exclamation-circle"
|
||||
case 8: // Transfer repository.
|
||||
return "share"
|
||||
case 10: // Comment issue.
|
||||
return "comment"
|
||||
default:
|
||||
return "invalid type"
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
TPL_CREATE_REPO = `<a href="/user/%s">%s</a> created repository <a href="/%s">%s</a>`
|
||||
TPL_COMMIT_REPO = `<a href="/user/%s">%s</a> pushed to <a href="/%s/src/%s">%s</a> at <a href="/%s">%s</a>%s`
|
||||
TPL_COMMIT_REPO_LI = `<div><img src="%s?s=16" alt="user-avatar"/> <a href="/%s/commit/%s" rel="nofollow">%s</a> %s</div>`
|
||||
TPL_CREATE_ISSUE = `<a href="/user/%s">%s</a> opened issue <a href="/%s/issues/%s">%s#%s</a>
|
||||
<div><img src="%s?s=16" alt="user-avatar"/> %s</div>`
|
||||
TPL_TRANSFER_REPO = `<a href="/user/%s">%s</a> transfered repository <code>%s</code> to <a href="/%s">%s</a>`
|
||||
TPL_PUSH_TAG = `<a href="/user/%s">%s</a> pushed tag <a href="/%s/src/%s" rel="nofollow">%s</a> at <a href="/%s">%s</a>`
|
||||
TPL_COMMENT_ISSUE = `<a href="/user/%s">%s</a> commented on issue <a href="/%s/issues/%s">%s#%s</a>
|
||||
<div><img src="%s?s=16" alt="user-avatar"/> %s</div>`
|
||||
)
|
||||
|
||||
type PushCommit struct {
|
||||
Sha1 string
|
||||
Message string
|
||||
AuthorEmail string
|
||||
AuthorName string
|
||||
}
|
||||
|
||||
type PushCommits struct {
|
||||
Len int
|
||||
Commits []*PushCommit
|
||||
}
|
||||
|
||||
// ActionDesc accepts int that represents action operation type
|
||||
// and returns the description.
|
||||
func ActionDesc(act Actioner) string {
|
||||
actUserName := act.GetActUserName()
|
||||
email := act.GetActEmail()
|
||||
repoUserName := act.GetRepoUserName()
|
||||
repoName := act.GetRepoName()
|
||||
repoLink := repoUserName + "/" + repoName
|
||||
branch := act.GetBranch()
|
||||
content := act.GetContent()
|
||||
switch act.GetOpType() {
|
||||
case 1: // Create repository.
|
||||
return fmt.Sprintf(TPL_CREATE_REPO, actUserName, actUserName, repoLink, repoName)
|
||||
case 5: // Commit repository.
|
||||
var push *PushCommits
|
||||
if err := json.Unmarshal([]byte(content), &push); err != nil {
|
||||
return err.Error()
|
||||
}
|
||||
buf := bytes.NewBuffer([]byte("\n"))
|
||||
for _, commit := range push.Commits {
|
||||
buf.WriteString(fmt.Sprintf(TPL_COMMIT_REPO_LI, AvatarLink(commit.AuthorEmail), repoLink, commit.Sha1, commit.Sha1[:7], commit.Message) + "\n")
|
||||
}
|
||||
if push.Len > 3 {
|
||||
buf.WriteString(fmt.Sprintf(`<div><a href="/%s/%s/commits/%s" rel="nofollow">%d other commits >></a></div>`, actUserName, repoName, branch, push.Len))
|
||||
}
|
||||
return fmt.Sprintf(TPL_COMMIT_REPO, actUserName, actUserName, repoLink, branch, branch, repoLink, repoLink,
|
||||
buf.String())
|
||||
case 6: // Create issue.
|
||||
infos := strings.SplitN(content, "|", 2)
|
||||
return fmt.Sprintf(TPL_CREATE_ISSUE, actUserName, actUserName, repoLink, infos[0], repoLink, infos[0],
|
||||
AvatarLink(email), infos[1])
|
||||
case 8: // Transfer repository.
|
||||
newRepoLink := content + "/" + repoName
|
||||
return fmt.Sprintf(TPL_TRANSFER_REPO, actUserName, actUserName, repoLink, newRepoLink, newRepoLink)
|
||||
case 9: // Push tag.
|
||||
return fmt.Sprintf(TPL_PUSH_TAG, actUserName, actUserName, repoLink, branch, branch, repoLink, repoLink)
|
||||
case 10: // Comment issue.
|
||||
infos := strings.SplitN(content, "|", 2)
|
||||
return fmt.Sprintf(TPL_COMMENT_ISSUE, actUserName, actUserName, repoLink, infos[0], repoLink, infos[0],
|
||||
AvatarLink(email), infos[1])
|
||||
default:
|
||||
return "invalid type"
|
||||
}
|
||||
}
|
||||
|
||||
func DiffTypeToStr(diffType int) string {
|
||||
diffTypes := map[int]string{
|
||||
1: "add", 2: "modify", 3: "del",
|
||||
}
|
||||
return diffTypes[diffType]
|
||||
}
|
||||
|
||||
func DiffLineTypeToStr(diffType int) string {
|
||||
switch diffType {
|
||||
case 2:
|
||||
return "add"
|
||||
case 3:
|
||||
return "del"
|
||||
case 4:
|
||||
return "tag"
|
||||
}
|
||||
return "same"
|
||||
}
|
||||
|
||||
func Oauth2Icon(t int) string {
|
||||
switch t {
|
||||
case 1:
|
||||
return "fa-github-square"
|
||||
case 2:
|
||||
return "fa-google-plus-square"
|
||||
case 3:
|
||||
return "fa-twitter-square"
|
||||
case 4:
|
||||
return "fa-linux"
|
||||
case 5:
|
||||
return "fa-weibo"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func Oauth2Name(t int) string {
|
||||
switch t {
|
||||
case 1:
|
||||
return "GitHub"
|
||||
case 2:
|
||||
return "Google"
|
||||
case 3:
|
||||
return "Twitter"
|
||||
case 4:
|
||||
return "Tencent QQ"
|
||||
case 5:
|
||||
return "Weibo"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
@@ -5,17 +5,19 @@
|
||||
package base
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/hmac"
|
||||
"crypto/md5"
|
||||
"crypto/rand"
|
||||
"crypto/sha1"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"hash"
|
||||
"math"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gogits/gogs/modules/setting"
|
||||
)
|
||||
|
||||
// Encode string to md5 hex value
|
||||
@@ -40,6 +42,44 @@ func GetRandomString(n int, alphabets ...byte) string {
|
||||
return string(bytes)
|
||||
}
|
||||
|
||||
// http://code.google.com/p/go/source/browse/pbkdf2/pbkdf2.go?repo=crypto
|
||||
func PBKDF2(password, salt []byte, iter, keyLen int, h func() hash.Hash) []byte {
|
||||
prf := hmac.New(h, password)
|
||||
hashLen := prf.Size()
|
||||
numBlocks := (keyLen + hashLen - 1) / hashLen
|
||||
|
||||
var buf [4]byte
|
||||
dk := make([]byte, 0, numBlocks*hashLen)
|
||||
U := make([]byte, hashLen)
|
||||
for block := 1; block <= numBlocks; block++ {
|
||||
// N.B.: || means concatenation, ^ means XOR
|
||||
// for each block T_i = U_1 ^ U_2 ^ ... ^ U_iter
|
||||
// U_1 = PRF(password, salt || uint(i))
|
||||
prf.Reset()
|
||||
prf.Write(salt)
|
||||
buf[0] = byte(block >> 24)
|
||||
buf[1] = byte(block >> 16)
|
||||
buf[2] = byte(block >> 8)
|
||||
buf[3] = byte(block)
|
||||
prf.Write(buf[:4])
|
||||
dk = prf.Sum(dk)
|
||||
T := dk[len(dk)-hashLen:]
|
||||
copy(U, T)
|
||||
|
||||
// U_n = PRF(password, U_(n-1))
|
||||
for n := 2; n <= iter; n++ {
|
||||
prf.Reset()
|
||||
prf.Write(U)
|
||||
U = U[:0]
|
||||
U = prf.Sum(U)
|
||||
for x := range U {
|
||||
T[x] ^= U[x]
|
||||
}
|
||||
}
|
||||
}
|
||||
return dk[:keyLen]
|
||||
}
|
||||
|
||||
// verify time limit code
|
||||
func VerifyTimeLimitCode(data string, minutes int, code string) bool {
|
||||
if len(code) <= 18 {
|
||||
@@ -93,7 +133,7 @@ func CreateTimeLimitCode(data string, minutes int, startInf interface{}) string
|
||||
|
||||
// create sha1 encode string
|
||||
sh := sha1.New()
|
||||
sh.Write([]byte(data + SecretKey + startStr + endStr + ToStr(minutes)))
|
||||
sh.Write([]byte(data + setting.SecretKey + startStr + endStr + ToStr(minutes)))
|
||||
encoded := hex.EncodeToString(sh.Sum(nil))
|
||||
|
||||
code := fmt.Sprintf("%s%06d%s", startStr, minutes, encoded)
|
||||
@@ -102,10 +142,12 @@ func CreateTimeLimitCode(data string, minutes int, startInf interface{}) string
|
||||
|
||||
// AvatarLink returns avatar link by given e-mail.
|
||||
func AvatarLink(email string) string {
|
||||
if Service.EnableCacheAvatar {
|
||||
if setting.DisableGravatar {
|
||||
return "/img/avatar_default.jpg"
|
||||
} else if setting.Service.EnableCacheAvatar {
|
||||
return "/avatar/" + EncodeMd5(email)
|
||||
}
|
||||
return "http://1.gravatar.com/avatar/" + EncodeMd5(email)
|
||||
return "//1.gravatar.com/avatar/" + EncodeMd5(email)
|
||||
}
|
||||
|
||||
// Seconds-based time units
|
||||
@@ -246,7 +288,6 @@ func TimeSince(then time.Time) string {
|
||||
default:
|
||||
return fmt.Sprintf("%d years %s", diff/Year, lbl)
|
||||
}
|
||||
return then.String()
|
||||
}
|
||||
|
||||
const (
|
||||
@@ -468,107 +509,8 @@ type argInt []int
|
||||
func (a argInt) Get(i int, args ...int) (r int) {
|
||||
if i >= 0 && i < len(a) {
|
||||
r = a[i]
|
||||
}
|
||||
if len(args) > 0 {
|
||||
} else if len(args) > 0 {
|
||||
r = args[0]
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type Actioner interface {
|
||||
GetOpType() int
|
||||
GetActUserName() string
|
||||
GetActEmail() string
|
||||
GetRepoName() string
|
||||
GetBranch() string
|
||||
GetContent() string
|
||||
}
|
||||
|
||||
// ActionIcon accepts a int that represents action operation type
|
||||
// and returns a icon class name.
|
||||
func ActionIcon(opType int) string {
|
||||
switch opType {
|
||||
case 1: // Create repository.
|
||||
return "plus-circle"
|
||||
case 5: // Commit repository.
|
||||
return "arrow-circle-o-right"
|
||||
case 6: // Create issue.
|
||||
return "exclamation-circle"
|
||||
default:
|
||||
return "invalid type"
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
TPL_CREATE_REPO = `<a href="/user/%s">%s</a> created repository <a href="/%s">%s</a>`
|
||||
TPL_COMMIT_REPO = `<a href="/user/%s">%s</a> pushed to <a href="/%s/src/%s">%s</a> at <a href="/%s">%s</a>%s`
|
||||
TPL_COMMIT_REPO_LI = `<div><img src="%s?s=16" alt="user-avatar"/> <a href="/%s/commit/%s">%s</a> %s</div>`
|
||||
TPL_CREATE_Issue = `<a href="/user/%s">%s</a> opened issue <a href="/%s/issues/%s">%s#%s</a>
|
||||
<div><img src="%s?s=16" alt="user-avatar"/> %s</div>`
|
||||
)
|
||||
|
||||
type PushCommit struct {
|
||||
Sha1 string
|
||||
Message string
|
||||
AuthorEmail string
|
||||
AuthorName string
|
||||
}
|
||||
|
||||
type PushCommits struct {
|
||||
Len int
|
||||
Commits []*PushCommit
|
||||
}
|
||||
|
||||
// ActionDesc accepts int that represents action operation type
|
||||
// and returns the description.
|
||||
func ActionDesc(act Actioner) string {
|
||||
actUserName := act.GetActUserName()
|
||||
email := act.GetActEmail()
|
||||
repoName := act.GetRepoName()
|
||||
repoLink := actUserName + "/" + repoName
|
||||
branch := act.GetBranch()
|
||||
content := act.GetContent()
|
||||
switch act.GetOpType() {
|
||||
case 1: // Create repository.
|
||||
return fmt.Sprintf(TPL_CREATE_REPO, actUserName, actUserName, repoLink, repoName)
|
||||
case 5: // Commit repository.
|
||||
var push *PushCommits
|
||||
if err := json.Unmarshal([]byte(content), &push); err != nil {
|
||||
return err.Error()
|
||||
}
|
||||
buf := bytes.NewBuffer([]byte("\n"))
|
||||
for _, commit := range push.Commits {
|
||||
buf.WriteString(fmt.Sprintf(TPL_COMMIT_REPO_LI, AvatarLink(commit.AuthorEmail), repoLink, commit.Sha1, commit.Sha1[:7], commit.Message) + "\n")
|
||||
}
|
||||
if push.Len > 3 {
|
||||
buf.WriteString(fmt.Sprintf(`<div><a href="/%s/%s/commits/%s">%d other commits >></a></div>`, actUserName, repoName, branch, push.Len))
|
||||
}
|
||||
return fmt.Sprintf(TPL_COMMIT_REPO, actUserName, actUserName, repoLink, branch, branch, repoLink, repoLink,
|
||||
buf.String())
|
||||
case 6: // Create issue.
|
||||
infos := strings.SplitN(content, "|", 2)
|
||||
return fmt.Sprintf(TPL_CREATE_Issue, actUserName, actUserName, repoLink, infos[0], repoLink, infos[0],
|
||||
AvatarLink(email), infos[1])
|
||||
default:
|
||||
return "invalid type"
|
||||
}
|
||||
}
|
||||
|
||||
func DiffTypeToStr(diffType int) string {
|
||||
diffTypes := map[int]string{
|
||||
1: "add", 2: "modify", 3: "del",
|
||||
}
|
||||
return diffTypes[diffType]
|
||||
}
|
||||
|
||||
func DiffLineTypeToStr(diffType int) string {
|
||||
switch diffType {
|
||||
case 2:
|
||||
return "add"
|
||||
case 3:
|
||||
return "del"
|
||||
case 4:
|
||||
return "tag"
|
||||
}
|
||||
return "same"
|
||||
}
|
||||
|
||||
3608
modules/bin/conf.go
Normal file
3608
modules/bin/conf.go
Normal file
File diff suppressed because it is too large
Load Diff
17
modules/cron/cron.go
Normal file
17
modules/cron/cron.go
Normal file
@@ -0,0 +1,17 @@
|
||||
// Copyright 2014 The Gogs Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package cron
|
||||
|
||||
import (
|
||||
"github.com/robfig/cron"
|
||||
|
||||
"github.com/gogits/gogs/models"
|
||||
)
|
||||
|
||||
func NewCronContext() {
|
||||
c := cron.New()
|
||||
c.AddFunc("@every 1h", models.MirrorUpdate)
|
||||
c.Start()
|
||||
}
|
||||
95
modules/hooks/hooks.go
Normal file
95
modules/hooks/hooks.go
Normal file
@@ -0,0 +1,95 @@
|
||||
// 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 hooks
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"github.com/gogits/gogs/modules/httplib"
|
||||
"github.com/gogits/gogs/modules/log"
|
||||
)
|
||||
|
||||
// Hook task types.
|
||||
const (
|
||||
HTT_WEBHOOK = iota + 1
|
||||
HTT_SERVICE
|
||||
)
|
||||
|
||||
type PayloadAuthor struct {
|
||||
Name string `json:"name"`
|
||||
Email string `json:"email"`
|
||||
}
|
||||
|
||||
type PayloadCommit struct {
|
||||
Id string `json:"id"`
|
||||
Message string `json:"message"`
|
||||
Url string `json:"url"`
|
||||
Author *PayloadAuthor `json:"author"`
|
||||
}
|
||||
|
||||
type PayloadRepo struct {
|
||||
Id int64 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Url string `json:"url"`
|
||||
Description string `json:"description"`
|
||||
Website string `json:"website"`
|
||||
Watchers int `json:"watchers"`
|
||||
Owner *PayloadAuthor `json:"author"`
|
||||
Private bool `json:"private"`
|
||||
}
|
||||
|
||||
// Payload represents payload information of hook.
|
||||
type Payload struct {
|
||||
Secret string `json:"secret"`
|
||||
Ref string `json:"ref"`
|
||||
Commits []*PayloadCommit `json:"commits"`
|
||||
Repo *PayloadRepo `json:"repository"`
|
||||
Pusher *PayloadAuthor `json:"pusher"`
|
||||
}
|
||||
|
||||
// HookTask represents hook task.
|
||||
type HookTask struct {
|
||||
Type int
|
||||
Url string
|
||||
*Payload
|
||||
ContentType int
|
||||
IsSsl bool
|
||||
}
|
||||
|
||||
var (
|
||||
taskQueue = make(chan *HookTask, 1000)
|
||||
)
|
||||
|
||||
// AddHookTask adds new hook task to task queue.
|
||||
func AddHookTask(t *HookTask) {
|
||||
taskQueue <- t
|
||||
}
|
||||
|
||||
func init() {
|
||||
go handleQueue()
|
||||
}
|
||||
|
||||
func handleQueue() {
|
||||
for {
|
||||
select {
|
||||
case t := <-taskQueue:
|
||||
// Only support JSON now.
|
||||
data, err := json.MarshalIndent(t.Payload, "", "\t")
|
||||
if err != nil {
|
||||
log.Error("hooks.handleQueue(json): %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
_, err = httplib.Post(t.Url).SetTimeout(5*time.Second, 5*time.Second).
|
||||
Body(data).Response()
|
||||
if err != nil {
|
||||
log.Error("hooks.handleQueue: Fail to deliver hook: %v", err)
|
||||
continue
|
||||
}
|
||||
log.Info("Hook delivered: %s", string(data))
|
||||
}
|
||||
}
|
||||
}
|
||||
62
modules/httplib/README.md
Executable file
62
modules/httplib/README.md
Executable file
@@ -0,0 +1,62 @@
|
||||
# httplib
|
||||
httplib is an libs help you to curl remote url.
|
||||
|
||||
# How to use?
|
||||
|
||||
## GET
|
||||
you can use Get to crawl data.
|
||||
|
||||
import "httplib"
|
||||
|
||||
str, err := httplib.Get("http://beego.me/").String()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
fmt.Println(str)
|
||||
|
||||
## POST
|
||||
POST data to remote url
|
||||
|
||||
b:=httplib.Post("http://beego.me/")
|
||||
b.Param("username","astaxie")
|
||||
b.Param("password","123456")
|
||||
str, err := b.String()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
fmt.Println(str)
|
||||
|
||||
## set timeout
|
||||
you can set timeout in request.default is 60 seconds.
|
||||
|
||||
set Get timeout:
|
||||
|
||||
httplib.Get("http://beego.me/").SetTimeout(100 * time.Second, 30 * time.Second)
|
||||
|
||||
set post timeout:
|
||||
|
||||
httplib.Post("http://beego.me/").SetTimeout(100 * time.Second, 30 * time.Second)
|
||||
|
||||
- first param is connectTimeout.
|
||||
- second param is readWriteTimeout
|
||||
|
||||
## debug
|
||||
if you want to debug the request info, set the debug on
|
||||
|
||||
httplib.Get("http://beego.me/").Debug(true)
|
||||
|
||||
## support HTTPS client
|
||||
if request url is https. You can set the client support TSL:
|
||||
|
||||
httplib.SetTLSClientConfig(&tls.Config{InsecureSkipVerify: true})
|
||||
|
||||
more info about the tls.Config please visit http://golang.org/pkg/crypto/tls/#Config
|
||||
|
||||
## set cookie
|
||||
some http request need setcookie. So set it like this:
|
||||
|
||||
cookie := &http.Cookie{}
|
||||
cookie.Name = "username"
|
||||
cookie.Value = "astaxie"
|
||||
httplib.Get("http://beego.me/").SetCookie(cookie)
|
||||
|
||||
330
modules/httplib/httplib.go
Executable file
330
modules/httplib/httplib.go
Executable file
@@ -0,0 +1,330 @@
|
||||
// Copyright 2013 The Beego Authors. All rights reserved.
|
||||
// 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 httplib
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"encoding/xml"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
var defaultUserAgent = "gogsServer"
|
||||
|
||||
// Get returns *BeegoHttpRequest with GET method.
|
||||
func Get(url string) *BeegoHttpRequest {
|
||||
var req http.Request
|
||||
req.Method = "GET"
|
||||
req.Header = http.Header{}
|
||||
req.Header.Set("User-Agent", defaultUserAgent)
|
||||
return &BeegoHttpRequest{url, &req, map[string]string{}, false, 60 * time.Second, 60 * time.Second, nil, nil, nil}
|
||||
}
|
||||
|
||||
// Post returns *BeegoHttpRequest with POST method.
|
||||
func Post(url string) *BeegoHttpRequest {
|
||||
var req http.Request
|
||||
req.Method = "POST"
|
||||
req.Header = http.Header{}
|
||||
req.Header.Set("User-Agent", defaultUserAgent)
|
||||
return &BeegoHttpRequest{url, &req, map[string]string{}, false, 60 * time.Second, 60 * time.Second, nil, nil, nil}
|
||||
}
|
||||
|
||||
// Put returns *BeegoHttpRequest with PUT method.
|
||||
func Put(url string) *BeegoHttpRequest {
|
||||
var req http.Request
|
||||
req.Method = "PUT"
|
||||
req.Header = http.Header{}
|
||||
req.Header.Set("User-Agent", defaultUserAgent)
|
||||
return &BeegoHttpRequest{url, &req, map[string]string{}, false, 60 * time.Second, 60 * time.Second, nil, nil, nil}
|
||||
}
|
||||
|
||||
// Delete returns *BeegoHttpRequest DELETE GET method.
|
||||
func Delete(url string) *BeegoHttpRequest {
|
||||
var req http.Request
|
||||
req.Method = "DELETE"
|
||||
req.Header = http.Header{}
|
||||
req.Header.Set("User-Agent", defaultUserAgent)
|
||||
return &BeegoHttpRequest{url, &req, map[string]string{}, false, 60 * time.Second, 60 * time.Second, nil, nil, nil}
|
||||
}
|
||||
|
||||
// Head returns *BeegoHttpRequest with HEAD method.
|
||||
func Head(url string) *BeegoHttpRequest {
|
||||
var req http.Request
|
||||
req.Method = "HEAD"
|
||||
req.Header = http.Header{}
|
||||
req.Header.Set("User-Agent", defaultUserAgent)
|
||||
return &BeegoHttpRequest{url, &req, map[string]string{}, false, 60 * time.Second, 60 * time.Second, nil, nil, nil}
|
||||
}
|
||||
|
||||
// BeegoHttpRequest provides more useful methods for requesting one url than http.Request.
|
||||
type BeegoHttpRequest struct {
|
||||
url string
|
||||
req *http.Request
|
||||
params map[string]string
|
||||
showdebug bool
|
||||
connectTimeout time.Duration
|
||||
readWriteTimeout time.Duration
|
||||
tlsClientConfig *tls.Config
|
||||
proxy func(*http.Request) (*url.URL, error)
|
||||
transport http.RoundTripper
|
||||
}
|
||||
|
||||
// Debug sets show debug or not when executing request.
|
||||
func (b *BeegoHttpRequest) Debug(isdebug bool) *BeegoHttpRequest {
|
||||
b.showdebug = isdebug
|
||||
return b
|
||||
}
|
||||
|
||||
// SetTimeout sets connect time out and read-write time out for BeegoRequest.
|
||||
func (b *BeegoHttpRequest) SetTimeout(connectTimeout, readWriteTimeout time.Duration) *BeegoHttpRequest {
|
||||
b.connectTimeout = connectTimeout
|
||||
b.readWriteTimeout = readWriteTimeout
|
||||
return b
|
||||
}
|
||||
|
||||
// SetTLSClientConfig sets tls connection configurations if visiting https url.
|
||||
func (b *BeegoHttpRequest) SetTLSClientConfig(config *tls.Config) *BeegoHttpRequest {
|
||||
b.tlsClientConfig = config
|
||||
return b
|
||||
}
|
||||
|
||||
// Header add header item string in request.
|
||||
func (b *BeegoHttpRequest) Header(key, value string) *BeegoHttpRequest {
|
||||
b.req.Header.Set(key, value)
|
||||
return b
|
||||
}
|
||||
|
||||
// SetCookie add cookie into request.
|
||||
func (b *BeegoHttpRequest) SetCookie(cookie *http.Cookie) *BeegoHttpRequest {
|
||||
b.req.Header.Add("Cookie", cookie.String())
|
||||
return b
|
||||
}
|
||||
|
||||
// Set transport to
|
||||
func (b *BeegoHttpRequest) SetTransport(transport http.RoundTripper) *BeegoHttpRequest {
|
||||
b.transport = transport
|
||||
return b
|
||||
}
|
||||
|
||||
// Set http proxy
|
||||
// example:
|
||||
//
|
||||
// func(req *http.Request) (*url.URL, error) {
|
||||
// u, _ := url.ParseRequestURI("http://127.0.0.1:8118")
|
||||
// return u, nil
|
||||
// }
|
||||
func (b *BeegoHttpRequest) SetProxy(proxy func(*http.Request) (*url.URL, error)) *BeegoHttpRequest {
|
||||
b.proxy = proxy
|
||||
return b
|
||||
}
|
||||
|
||||
// Param adds query param in to request.
|
||||
// params build query string as ?key1=value1&key2=value2...
|
||||
func (b *BeegoHttpRequest) Param(key, value string) *BeegoHttpRequest {
|
||||
b.params[key] = value
|
||||
return b
|
||||
}
|
||||
|
||||
// Body adds request raw body.
|
||||
// it supports string and []byte.
|
||||
func (b *BeegoHttpRequest) Body(data interface{}) *BeegoHttpRequest {
|
||||
switch t := data.(type) {
|
||||
case string:
|
||||
bf := bytes.NewBufferString(t)
|
||||
b.req.Body = ioutil.NopCloser(bf)
|
||||
b.req.ContentLength = int64(len(t))
|
||||
case []byte:
|
||||
bf := bytes.NewBuffer(t)
|
||||
b.req.Body = ioutil.NopCloser(bf)
|
||||
b.req.ContentLength = int64(len(t))
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func (b *BeegoHttpRequest) getResponse() (*http.Response, error) {
|
||||
var paramBody string
|
||||
if len(b.params) > 0 {
|
||||
var buf bytes.Buffer
|
||||
for k, v := range b.params {
|
||||
buf.WriteString(url.QueryEscape(k))
|
||||
buf.WriteByte('=')
|
||||
buf.WriteString(url.QueryEscape(v))
|
||||
buf.WriteByte('&')
|
||||
}
|
||||
paramBody = buf.String()
|
||||
paramBody = paramBody[0 : len(paramBody)-1]
|
||||
}
|
||||
|
||||
if b.req.Method == "GET" && len(paramBody) > 0 {
|
||||
if strings.Index(b.url, "?") != -1 {
|
||||
b.url += "&" + paramBody
|
||||
} else {
|
||||
b.url = b.url + "?" + paramBody
|
||||
}
|
||||
} else if b.req.Method == "POST" && b.req.Body == nil && len(paramBody) > 0 {
|
||||
b.Header("Content-Type", "application/x-www-form-urlencoded")
|
||||
b.Body(paramBody)
|
||||
}
|
||||
|
||||
url, err := url.Parse(b.url)
|
||||
if url.Scheme == "" {
|
||||
b.url = "http://" + b.url
|
||||
url, err = url.Parse(b.url)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
b.req.URL = url
|
||||
if b.showdebug {
|
||||
dump, err := httputil.DumpRequest(b.req, true)
|
||||
if err != nil {
|
||||
println(err.Error())
|
||||
}
|
||||
println(string(dump))
|
||||
}
|
||||
|
||||
trans := b.transport
|
||||
|
||||
if trans == nil {
|
||||
// create default transport
|
||||
trans = &http.Transport{
|
||||
TLSClientConfig: b.tlsClientConfig,
|
||||
Proxy: b.proxy,
|
||||
Dial: TimeoutDialer(b.connectTimeout, b.readWriteTimeout),
|
||||
}
|
||||
} else {
|
||||
// if b.transport is *http.Transport then set the settings.
|
||||
if t, ok := trans.(*http.Transport); ok {
|
||||
if t.TLSClientConfig == nil {
|
||||
t.TLSClientConfig = b.tlsClientConfig
|
||||
}
|
||||
if t.Proxy == nil {
|
||||
t.Proxy = b.proxy
|
||||
}
|
||||
if t.Dial == nil {
|
||||
t.Dial = TimeoutDialer(b.connectTimeout, b.readWriteTimeout)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
client := &http.Client{
|
||||
Transport: trans,
|
||||
}
|
||||
|
||||
resp, err := client.Do(b.req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// String returns the body string in response.
|
||||
// it calls Response inner.
|
||||
func (b *BeegoHttpRequest) String() (string, error) {
|
||||
data, err := b.Bytes()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return string(data), nil
|
||||
}
|
||||
|
||||
// Bytes returns the body []byte in response.
|
||||
// it calls Response inner.
|
||||
func (b *BeegoHttpRequest) Bytes() ([]byte, error) {
|
||||
resp, err := b.getResponse()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if resp.Body == nil {
|
||||
return nil, nil
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
data, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
|
||||
// ToFile saves the body data in response to one file.
|
||||
// it calls Response inner.
|
||||
func (b *BeegoHttpRequest) ToFile(filename string) error {
|
||||
f, err := os.Create(filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
resp, err := b.getResponse()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if resp.Body == nil {
|
||||
return nil
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
_, err = io.Copy(f, resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ToJson returns the map that marshals from the body bytes as json in response .
|
||||
// it calls Response inner.
|
||||
func (b *BeegoHttpRequest) ToJson(v interface{}) error {
|
||||
data, err := b.Bytes()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = json.Unmarshal(data, v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ToXml returns the map that marshals from the body bytes as xml in response .
|
||||
// it calls Response inner.
|
||||
func (b *BeegoHttpRequest) ToXML(v interface{}) error {
|
||||
data, err := b.Bytes()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = xml.Unmarshal(data, v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Response executes request client gets response mannually.
|
||||
func (b *BeegoHttpRequest) Response() (*http.Response, error) {
|
||||
return b.getResponse()
|
||||
}
|
||||
|
||||
// TimeoutDialer returns functions of connection dialer with timeout settings for http.Transport Dial field.
|
||||
func TimeoutDialer(cTimeout time.Duration, rwTimeout time.Duration) func(net, addr string) (c net.Conn, err error) {
|
||||
return func(netw, addr string) (net.Conn, error) {
|
||||
conn, err := net.DialTimeout(netw, addr, cTimeout)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
conn.SetDeadline(time.Now().Add(rwTimeout))
|
||||
return conn, nil
|
||||
}
|
||||
}
|
||||
32
modules/httplib/httplib_test.go
Executable file
32
modules/httplib/httplib_test.go
Executable file
@@ -0,0 +1,32 @@
|
||||
package httplib
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGetUrl(t *testing.T) {
|
||||
resp, err := Get("http://beego.me/").Debug(true).Response()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if resp.Body == nil {
|
||||
t.Fatal("body is nil")
|
||||
}
|
||||
data, err := ioutil.ReadAll(resp.Body)
|
||||
defer resp.Body.Close()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(data) == 0 {
|
||||
t.Fatal("data is no")
|
||||
}
|
||||
|
||||
str, err := Get("http://beego.me/").String()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(str) == 0 {
|
||||
t.Fatal("has no info")
|
||||
}
|
||||
}
|
||||
@@ -6,46 +6,76 @@
|
||||
package log
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/gogits/logs"
|
||||
)
|
||||
|
||||
var (
|
||||
logger *logs.BeeLogger
|
||||
Mode, Config string
|
||||
loggers []*logs.BeeLogger
|
||||
)
|
||||
|
||||
func init() {
|
||||
NewLogger(10000, "console", `{"level": 0}`)
|
||||
NewLogger(0, "console", `{"level": 0}`)
|
||||
}
|
||||
|
||||
func NewLogger(bufLen int64, mode, config string) {
|
||||
Mode, Config = mode, config
|
||||
logger = logs.NewLogger(bufLen)
|
||||
logger.EnableFuncCallDepth(true)
|
||||
logger.SetLogFuncCallDepth(4)
|
||||
logger := logs.NewLogger(bufLen)
|
||||
|
||||
isExist := false
|
||||
for _, l := range loggers {
|
||||
if l.Adapter == mode {
|
||||
isExist = true
|
||||
l = logger
|
||||
}
|
||||
}
|
||||
if !isExist {
|
||||
loggers = append(loggers, logger)
|
||||
}
|
||||
logger.SetLogFuncCallDepth(3)
|
||||
logger.SetLogger(mode, config)
|
||||
}
|
||||
|
||||
func Trace(format string, v ...interface{}) {
|
||||
logger.Trace(format, v...)
|
||||
for _, logger := range loggers {
|
||||
logger.Trace(format, v...)
|
||||
}
|
||||
}
|
||||
|
||||
func Debug(format string, v ...interface{}) {
|
||||
logger.Debug(format, v...)
|
||||
for _, logger := range loggers {
|
||||
logger.Debug(format, v...)
|
||||
}
|
||||
}
|
||||
|
||||
func Info(format string, v ...interface{}) {
|
||||
logger.Info(format, v...)
|
||||
for _, logger := range loggers {
|
||||
logger.Info(format, v...)
|
||||
}
|
||||
}
|
||||
|
||||
func Error(format string, v ...interface{}) {
|
||||
logger.Error(format, v...)
|
||||
for _, logger := range loggers {
|
||||
logger.Error(format, v...)
|
||||
}
|
||||
}
|
||||
|
||||
func Warn(format string, v ...interface{}) {
|
||||
logger.Warn(format, v...)
|
||||
for _, logger := range loggers {
|
||||
logger.Warn(format, v...)
|
||||
}
|
||||
}
|
||||
|
||||
func Critical(format string, v ...interface{}) {
|
||||
logger.Critical(format, v...)
|
||||
for _, logger := range loggers {
|
||||
logger.Critical(format, v...)
|
||||
}
|
||||
}
|
||||
|
||||
func Fatal(format string, v ...interface{}) {
|
||||
Error(format, v...)
|
||||
for _, l := range loggers {
|
||||
l.Close()
|
||||
}
|
||||
os.Exit(2)
|
||||
}
|
||||
|
||||
@@ -8,56 +8,58 @@ import (
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"path"
|
||||
|
||||
"github.com/gogits/gogs/models"
|
||||
"github.com/gogits/gogs/modules/base"
|
||||
"github.com/gogits/gogs/modules/log"
|
||||
"github.com/gogits/gogs/modules/middleware"
|
||||
"github.com/gogits/gogs/modules/setting"
|
||||
)
|
||||
|
||||
// Create New mail message use MailFrom and MailUser
|
||||
func NewMailMessageFrom(To []string, from, subject, body string) Message {
|
||||
msg := NewHtmlMessage(To, from, subject, body)
|
||||
msg.User = base.MailService.User
|
||||
msg.User = setting.MailService.User
|
||||
return msg
|
||||
}
|
||||
|
||||
// Create New mail message use MailFrom and MailUser
|
||||
func NewMailMessage(To []string, subject, body string) Message {
|
||||
return NewMailMessageFrom(To, base.MailService.User, subject, body)
|
||||
return NewMailMessageFrom(To, setting.MailService.From, subject, body)
|
||||
}
|
||||
|
||||
func GetMailTmplData(user *models.User) map[interface{}]interface{} {
|
||||
func GetMailTmplData(u *models.User) map[interface{}]interface{} {
|
||||
data := make(map[interface{}]interface{}, 10)
|
||||
data["AppName"] = base.AppName
|
||||
data["AppVer"] = base.AppVer
|
||||
data["AppUrl"] = base.AppUrl
|
||||
data["AppLogo"] = base.AppLogo
|
||||
data["ActiveCodeLives"] = base.Service.ActiveCodeLives / 60
|
||||
data["ResetPwdCodeLives"] = base.Service.ResetPwdCodeLives / 60
|
||||
if user != nil {
|
||||
data["User"] = user
|
||||
data["AppName"] = setting.AppName
|
||||
data["AppVer"] = setting.AppVer
|
||||
data["AppUrl"] = setting.AppUrl
|
||||
data["AppLogo"] = setting.AppLogo
|
||||
data["ActiveCodeLives"] = setting.Service.ActiveCodeLives / 60
|
||||
data["ResetPwdCodeLives"] = setting.Service.ResetPwdCodeLives / 60
|
||||
if u != nil {
|
||||
data["User"] = u
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
// create a time limit code for user active
|
||||
func CreateUserActiveCode(user *models.User, startInf interface{}) string {
|
||||
minutes := base.Service.ActiveCodeLives
|
||||
data := base.ToStr(user.Id) + user.Email + user.LowerName + user.Passwd + user.Rands
|
||||
func CreateUserActiveCode(u *models.User, startInf interface{}) string {
|
||||
minutes := setting.Service.ActiveCodeLives
|
||||
data := base.ToStr(u.Id) + u.Email + u.LowerName + u.Passwd + u.Rands
|
||||
code := base.CreateTimeLimitCode(data, minutes, startInf)
|
||||
|
||||
// add tail hex username
|
||||
code += hex.EncodeToString([]byte(user.LowerName))
|
||||
code += hex.EncodeToString([]byte(u.LowerName))
|
||||
return code
|
||||
}
|
||||
|
||||
// Send user register mail with active code
|
||||
func SendRegisterMail(r *middleware.Render, user *models.User) {
|
||||
code := CreateUserActiveCode(user, nil)
|
||||
func SendRegisterMail(r *middleware.Render, u *models.User) {
|
||||
code := CreateUserActiveCode(u, nil)
|
||||
subject := "Register success, Welcome"
|
||||
|
||||
data := GetMailTmplData(user)
|
||||
data := GetMailTmplData(u)
|
||||
data["Code"] = code
|
||||
body, err := r.HTMLString("mail/auth/register_success", data)
|
||||
if err != nil {
|
||||
@@ -65,19 +67,19 @@ func SendRegisterMail(r *middleware.Render, user *models.User) {
|
||||
return
|
||||
}
|
||||
|
||||
msg := NewMailMessage([]string{user.Email}, subject, body)
|
||||
msg.Info = fmt.Sprintf("UID: %d, send register mail", user.Id)
|
||||
msg := NewMailMessage([]string{u.Email}, subject, body)
|
||||
msg.Info = fmt.Sprintf("UID: %d, send register mail", u.Id)
|
||||
|
||||
SendAsync(&msg)
|
||||
}
|
||||
|
||||
// Send email verify active email.
|
||||
func SendActiveMail(r *middleware.Render, user *models.User) {
|
||||
code := CreateUserActiveCode(user, nil)
|
||||
func SendActiveMail(r *middleware.Render, u *models.User) {
|
||||
code := CreateUserActiveCode(u, nil)
|
||||
|
||||
subject := "Verify your e-mail address"
|
||||
|
||||
data := GetMailTmplData(user)
|
||||
data := GetMailTmplData(u)
|
||||
data["Code"] = code
|
||||
body, err := r.HTMLString("mail/auth/active_email", data)
|
||||
if err != nil {
|
||||
@@ -85,38 +87,109 @@ func SendActiveMail(r *middleware.Render, user *models.User) {
|
||||
return
|
||||
}
|
||||
|
||||
msg := NewMailMessage([]string{user.Email}, subject, body)
|
||||
msg.Info = fmt.Sprintf("UID: %d, send email verify mail", user.Id)
|
||||
msg := NewMailMessage([]string{u.Email}, subject, body)
|
||||
msg.Info = fmt.Sprintf("UID: %d, send active mail", u.Id)
|
||||
|
||||
SendAsync(&msg)
|
||||
}
|
||||
|
||||
// SendNotifyMail sends mail notification of all watchers.
|
||||
func SendNotifyMail(userId, repoId int64, userName, repoName, subject, content string) error {
|
||||
watches, err := models.GetWatches(repoId)
|
||||
// Send reset password email.
|
||||
func SendResetPasswdMail(r *middleware.Render, u *models.User) {
|
||||
code := CreateUserActiveCode(u, nil)
|
||||
|
||||
subject := "Reset your password"
|
||||
|
||||
data := GetMailTmplData(u)
|
||||
data["Code"] = code
|
||||
body, err := r.HTMLString("mail/auth/reset_passwd", data)
|
||||
if err != nil {
|
||||
return errors.New("mail.NotifyWatchers(get watches): " + err.Error())
|
||||
log.Error("mail.SendResetPasswdMail(fail to render): %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
tos := make([]string, 0, len(watches))
|
||||
for i := range watches {
|
||||
uid := watches[i].UserId
|
||||
if userId == uid {
|
||||
msg := NewMailMessage([]string{u.Email}, subject, body)
|
||||
msg.Info = fmt.Sprintf("UID: %d, send reset password email", u.Id)
|
||||
|
||||
SendAsync(&msg)
|
||||
}
|
||||
|
||||
// SendIssueNotifyMail sends mail notification of all watchers of repository.
|
||||
func SendIssueNotifyMail(u, owner *models.User, repo *models.Repository, issue *models.Issue) ([]string, error) {
|
||||
ws, err := models.GetWatchers(repo.Id)
|
||||
if err != nil {
|
||||
return nil, errors.New("mail.NotifyWatchers(GetWatchers): " + err.Error())
|
||||
}
|
||||
|
||||
tos := make([]string, 0, len(ws))
|
||||
for i := range ws {
|
||||
uid := ws[i].UserId
|
||||
if u.Id == uid {
|
||||
continue
|
||||
}
|
||||
u, err := models.GetUserById(uid)
|
||||
if err != nil {
|
||||
return errors.New("mail.NotifyWatchers(get user): " + err.Error())
|
||||
return nil, errors.New("mail.NotifyWatchers(GetUserById): " + err.Error())
|
||||
}
|
||||
tos = append(tos, u.Email)
|
||||
}
|
||||
|
||||
if len(tos) == 0 {
|
||||
return tos, nil
|
||||
}
|
||||
|
||||
subject := fmt.Sprintf("[%s] %s(#%d)", repo.Name, issue.Name, issue.Index)
|
||||
content := fmt.Sprintf("%s<br>-<br> <a href=\"%s%s/%s/issues/%d\">View it on Gogs</a>.",
|
||||
base.RenderSpecialLink([]byte(issue.Content), owner.Name+"/"+repo.Name),
|
||||
setting.AppUrl, owner.Name, repo.Name, issue.Index)
|
||||
msg := NewMailMessageFrom(tos, u.Email, subject, content)
|
||||
msg.Info = fmt.Sprintf("Subject: %s, send issue notify emails", subject)
|
||||
SendAsync(&msg)
|
||||
return tos, nil
|
||||
}
|
||||
|
||||
// SendIssueMentionMail sends mail notification for who are mentioned in issue.
|
||||
func SendIssueMentionMail(r *middleware.Render, u, owner *models.User,
|
||||
repo *models.Repository, issue *models.Issue, tos []string) error {
|
||||
|
||||
if len(tos) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
msg := NewMailMessageFrom(tos, userName, subject, content)
|
||||
msg.Info = fmt.Sprintf("Subject: %s, send notify emails", subject)
|
||||
subject := fmt.Sprintf("[%s] %s(#%d)", repo.Name, issue.Name, issue.Index)
|
||||
|
||||
data := GetMailTmplData(nil)
|
||||
data["IssueLink"] = fmt.Sprintf("%s/%s/issues/%d", owner.Name, repo.Name, issue.Index)
|
||||
data["Subject"] = subject
|
||||
|
||||
body, err := r.HTMLString("mail/notify/mention", data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("mail.SendIssueMentionMail(fail to render): %v", err)
|
||||
}
|
||||
|
||||
msg := NewMailMessageFrom(tos, u.Email, subject, body)
|
||||
msg.Info = fmt.Sprintf("Subject: %s, send issue mention emails", subject)
|
||||
SendAsync(&msg)
|
||||
return nil
|
||||
}
|
||||
|
||||
// SendCollaboratorMail sends mail notification to new collaborator.
|
||||
func SendCollaboratorMail(r *middleware.Render, u, owner *models.User,
|
||||
repo *models.Repository) error {
|
||||
|
||||
subject := fmt.Sprintf("%s added you to %s", owner.Name, repo.Name)
|
||||
|
||||
data := GetMailTmplData(nil)
|
||||
data["RepoLink"] = path.Join(owner.Name, repo.Name)
|
||||
data["Subject"] = subject
|
||||
|
||||
body, err := r.HTMLString("mail/notify/collaborator", data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("mail.SendCollaboratorMail(fail to render): %v", err)
|
||||
}
|
||||
|
||||
msg := NewMailMessage([]string{u.Email}, subject, body)
|
||||
msg.Info = fmt.Sprintf("UID: %d, send register mail", u.Id)
|
||||
|
||||
SendAsync(&msg)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -9,8 +9,8 @@ import (
|
||||
"net/smtp"
|
||||
"strings"
|
||||
|
||||
"github.com/gogits/gogs/modules/base"
|
||||
"github.com/gogits/gogs/modules/log"
|
||||
"github.com/gogits/gogs/modules/setting"
|
||||
)
|
||||
|
||||
type Message struct {
|
||||
@@ -41,7 +41,7 @@ func (m Message) Content() string {
|
||||
var mailQueue chan *Message
|
||||
|
||||
func NewMailerContext() {
|
||||
mailQueue = make(chan *Message, base.Cfg.MustInt("mailer", "SEND_BUFFER_LEN", 10))
|
||||
mailQueue = make(chan *Message, setting.Cfg.MustInt("mailer", "SEND_BUFFER_LEN", 10))
|
||||
go processMailQueue()
|
||||
}
|
||||
|
||||
@@ -67,27 +67,25 @@ func processMailQueue() {
|
||||
// Direct Send mail message
|
||||
func Send(msg *Message) (int, error) {
|
||||
log.Trace("Sending mails to: %s", strings.Join(msg.To, "; "))
|
||||
host := strings.Split(base.MailService.Host, ":")
|
||||
host := strings.Split(setting.MailService.Host, ":")
|
||||
|
||||
// get message body
|
||||
content := msg.Content()
|
||||
|
||||
auth := smtp.PlainAuth("", base.MailService.User, base.MailService.Passwd, host[0])
|
||||
|
||||
if len(msg.To) == 0 {
|
||||
return 0, fmt.Errorf("empty receive emails")
|
||||
}
|
||||
|
||||
if len(msg.Body) == 0 {
|
||||
} else if len(msg.Body) == 0 {
|
||||
return 0, fmt.Errorf("empty email body")
|
||||
}
|
||||
|
||||
auth := smtp.PlainAuth("", setting.MailService.User, setting.MailService.Passwd, host[0])
|
||||
|
||||
if msg.Massive {
|
||||
// send mail to multiple emails one by one
|
||||
num := 0
|
||||
for _, to := range msg.To {
|
||||
body := []byte("To: " + to + "\r\n" + content)
|
||||
err := smtp.SendMail(base.MailService.Host, auth, msg.From, []string{to}, body)
|
||||
err := smtp.SendMail(setting.MailService.Host, auth, msg.From, []string{to}, body)
|
||||
if err != nil {
|
||||
return num, err
|
||||
}
|
||||
@@ -98,7 +96,7 @@ func Send(msg *Message) (int, error) {
|
||||
body := []byte("To: " + strings.Join(msg.To, ";") + "\r\n" + content)
|
||||
|
||||
// send to multiple emails in one message
|
||||
err := smtp.SendMail(base.MailService.Host, auth, msg.From, msg.To, body)
|
||||
err := smtp.SendMail(setting.MailService.Host, auth, msg.From, msg.To, body)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
} else {
|
||||
|
||||
@@ -6,10 +6,11 @@ package middleware
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/go-martini/martini"
|
||||
|
||||
"github.com/gogits/gogs/modules/base"
|
||||
"github.com/gogits/gogs/modules/setting"
|
||||
)
|
||||
|
||||
type ToggleOptions struct {
|
||||
@@ -21,33 +22,35 @@ type ToggleOptions struct {
|
||||
|
||||
func Toggle(options *ToggleOptions) martini.Handler {
|
||||
return func(ctx *Context) {
|
||||
if !base.InstallLock {
|
||||
// Cannot view any page before installation.
|
||||
if !setting.InstallLock {
|
||||
ctx.Redirect("/install")
|
||||
return
|
||||
}
|
||||
|
||||
// Redirect to dashboard if user tries to visit any non-login page.
|
||||
if options.SignOutRequire && ctx.IsSigned && ctx.Req.RequestURI != "/" {
|
||||
ctx.Redirect("/")
|
||||
return
|
||||
}
|
||||
|
||||
if !options.DisableCsrf {
|
||||
if ctx.Req.Method == "POST" {
|
||||
if !ctx.CsrfTokenValid() {
|
||||
ctx.Error(403, "CSRF token does not match")
|
||||
return
|
||||
}
|
||||
}
|
||||
if !options.DisableCsrf && ctx.Req.Method == "POST" && !ctx.CsrfTokenValid() {
|
||||
ctx.Error(403, "CSRF token does not match")
|
||||
return
|
||||
}
|
||||
|
||||
if options.SignInRequire {
|
||||
if !ctx.IsSigned {
|
||||
// Ignore watch repository operation.
|
||||
if strings.HasSuffix(ctx.Req.RequestURI, "watch") {
|
||||
return
|
||||
}
|
||||
ctx.SetCookie("redirect_to", "/"+url.QueryEscape(ctx.Req.RequestURI))
|
||||
ctx.Redirect("/user/login")
|
||||
return
|
||||
} else if !ctx.User.IsActive && base.Service.RegisterEmailConfirm {
|
||||
} else if !ctx.User.IsActive && setting.Service.RegisterEmailConfirm {
|
||||
ctx.Data["Title"] = "Activate Your Account"
|
||||
ctx.HTML(200, "user/active")
|
||||
ctx.HTML(200, "user/activate")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
473
modules/middleware/binding/binding.go
Normal file
473
modules/middleware/binding/binding.go
Normal file
@@ -0,0 +1,473 @@
|
||||
// Copyright 2013 The Martini Contrib Authors. All rights reserved.
|
||||
// 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 binding
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/go-martini/martini"
|
||||
)
|
||||
|
||||
/*
|
||||
To the land of Middle-ware Earth:
|
||||
|
||||
One func to rule them all,
|
||||
One func to find them,
|
||||
One func to bring them all,
|
||||
And in this package BIND them.
|
||||
*/
|
||||
|
||||
// Bind accepts a copy of an empty struct and populates it with
|
||||
// values from the request (if deserialization is successful). It
|
||||
// wraps up the functionality of the Form and Json middleware
|
||||
// according to the Content-Type of the request, and it guesses
|
||||
// if no Content-Type is specified. Bind invokes the ErrorHandler
|
||||
// middleware to bail out if errors occurred. If you want to perform
|
||||
// your own error handling, use Form or Json middleware directly.
|
||||
// An interface pointer can be added as a second argument in order
|
||||
// to map the struct to a specific interface.
|
||||
func Bind(obj interface{}, ifacePtr ...interface{}) martini.Handler {
|
||||
return func(context martini.Context, req *http.Request) {
|
||||
contentType := req.Header.Get("Content-Type")
|
||||
|
||||
if strings.Contains(contentType, "form-urlencoded") {
|
||||
context.Invoke(Form(obj, ifacePtr...))
|
||||
} else if strings.Contains(contentType, "multipart/form-data") {
|
||||
context.Invoke(MultipartForm(obj, ifacePtr...))
|
||||
} else if strings.Contains(contentType, "json") {
|
||||
context.Invoke(Json(obj, ifacePtr...))
|
||||
} else {
|
||||
context.Invoke(Json(obj, ifacePtr...))
|
||||
if getErrors(context).Count() > 0 {
|
||||
context.Invoke(Form(obj, ifacePtr...))
|
||||
}
|
||||
}
|
||||
|
||||
context.Invoke(ErrorHandler)
|
||||
}
|
||||
}
|
||||
|
||||
// BindIgnErr will do the exactly same thing as Bind but without any
|
||||
// error handling, which user has freedom to deal with them.
|
||||
// This allows user take advantages of validation.
|
||||
func BindIgnErr(obj interface{}, ifacePtr ...interface{}) martini.Handler {
|
||||
return func(context martini.Context, req *http.Request) {
|
||||
contentType := req.Header.Get("Content-Type")
|
||||
|
||||
if strings.Contains(contentType, "form-urlencoded") {
|
||||
context.Invoke(Form(obj, ifacePtr...))
|
||||
} else if strings.Contains(contentType, "multipart/form-data") {
|
||||
context.Invoke(MultipartForm(obj, ifacePtr...))
|
||||
} else if strings.Contains(contentType, "json") {
|
||||
context.Invoke(Json(obj, ifacePtr...))
|
||||
} else {
|
||||
context.Invoke(Json(obj, ifacePtr...))
|
||||
if getErrors(context).Count() > 0 {
|
||||
context.Invoke(Form(obj, ifacePtr...))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Form is middleware to deserialize form-urlencoded data from the request.
|
||||
// It gets data from the form-urlencoded body, if present, or from the
|
||||
// query string. It uses the http.Request.ParseForm() method
|
||||
// to perform deserialization, then reflection is used to map each field
|
||||
// into the struct with the proper type. Structs with primitive slice types
|
||||
// (bool, float, int, string) can support deserialization of repeated form
|
||||
// keys, for example: key=val1&key=val2&key=val3
|
||||
// An interface pointer can be added as a second argument in order
|
||||
// to map the struct to a specific interface.
|
||||
func Form(formStruct interface{}, ifacePtr ...interface{}) martini.Handler {
|
||||
return func(context martini.Context, req *http.Request) {
|
||||
ensureNotPointer(formStruct)
|
||||
formStruct := reflect.New(reflect.TypeOf(formStruct))
|
||||
errors := newErrors()
|
||||
parseErr := req.ParseForm()
|
||||
|
||||
// Format validation of the request body or the URL would add considerable overhead,
|
||||
// and ParseForm does not complain when URL encoding is off.
|
||||
// Because an empty request body or url can also mean absence of all needed values,
|
||||
// it is not in all cases a bad request, so let's return 422.
|
||||
if parseErr != nil {
|
||||
errors.Overall[BindingDeserializationError] = parseErr.Error()
|
||||
}
|
||||
|
||||
mapForm(formStruct, req.Form, errors)
|
||||
|
||||
validateAndMap(formStruct, context, errors, ifacePtr...)
|
||||
}
|
||||
}
|
||||
|
||||
func MultipartForm(formStruct interface{}, ifacePtr ...interface{}) martini.Handler {
|
||||
return func(context martini.Context, req *http.Request) {
|
||||
ensureNotPointer(formStruct)
|
||||
formStruct := reflect.New(reflect.TypeOf(formStruct))
|
||||
errors := newErrors()
|
||||
|
||||
// Workaround for multipart forms returning nil instead of an error
|
||||
// when content is not multipart
|
||||
// https://code.google.com/p/go/issues/detail?id=6334
|
||||
multipartReader, err := req.MultipartReader()
|
||||
if err != nil {
|
||||
errors.Overall[BindingDeserializationError] = err.Error()
|
||||
} else {
|
||||
form, parseErr := multipartReader.ReadForm(MaxMemory)
|
||||
|
||||
if parseErr != nil {
|
||||
errors.Overall[BindingDeserializationError] = parseErr.Error()
|
||||
}
|
||||
|
||||
req.MultipartForm = form
|
||||
}
|
||||
|
||||
mapForm(formStruct, req.MultipartForm.Value, errors)
|
||||
|
||||
validateAndMap(formStruct, context, errors, ifacePtr...)
|
||||
}
|
||||
}
|
||||
|
||||
// Json is middleware to deserialize a JSON payload from the request
|
||||
// into the struct that is passed in. The resulting struct is then
|
||||
// validated, but no error handling is actually performed here.
|
||||
// An interface pointer can be added as a second argument in order
|
||||
// to map the struct to a specific interface.
|
||||
func Json(jsonStruct interface{}, ifacePtr ...interface{}) martini.Handler {
|
||||
return func(context martini.Context, req *http.Request) {
|
||||
ensureNotPointer(jsonStruct)
|
||||
jsonStruct := reflect.New(reflect.TypeOf(jsonStruct))
|
||||
errors := newErrors()
|
||||
|
||||
if req.Body != nil {
|
||||
defer req.Body.Close()
|
||||
}
|
||||
|
||||
if err := json.NewDecoder(req.Body).Decode(jsonStruct.Interface()); err != nil && err != io.EOF {
|
||||
errors.Overall[BindingDeserializationError] = err.Error()
|
||||
}
|
||||
|
||||
validateAndMap(jsonStruct, context, errors, ifacePtr...)
|
||||
}
|
||||
}
|
||||
|
||||
// Validate is middleware to enforce required fields. If the struct
|
||||
// passed in is a Validator, then the user-defined Validate method
|
||||
// is executed, and its errors are mapped to the context. This middleware
|
||||
// performs no error handling: it merely detects them and maps them.
|
||||
func Validate(obj interface{}) martini.Handler {
|
||||
return func(context martini.Context, req *http.Request) {
|
||||
errors := newErrors()
|
||||
validateStruct(errors, obj)
|
||||
|
||||
if validator, ok := obj.(Validator); ok {
|
||||
validator.Validate(errors, req, context)
|
||||
}
|
||||
context.Map(*errors)
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
alphaDashPattern = regexp.MustCompile("[^\\d\\w-_]")
|
||||
alphaDashDotPattern = regexp.MustCompile("[^\\d\\w-_\\.]")
|
||||
emailPattern = regexp.MustCompile("[\\w!#$%&'*+/=?^_`{|}~-]+(?:\\.[\\w!#$%&'*+/=?^_`{|}~-]+)*@(?:[\\w](?:[\\w-]*[\\w])?\\.)+[a-zA-Z0-9](?:[\\w-]*[\\w])?")
|
||||
urlPattern = regexp.MustCompile(`(http|https):\/\/[\w\-_]+(\.[\w\-_]+)+([\w\-\.,@?^=%&:/~\+#]*[\w\-\@?^=%&/~\+#])?`)
|
||||
)
|
||||
|
||||
func validateStruct(errors *Errors, obj interface{}) {
|
||||
typ := reflect.TypeOf(obj)
|
||||
val := reflect.ValueOf(obj)
|
||||
|
||||
if typ.Kind() == reflect.Ptr {
|
||||
typ = typ.Elem()
|
||||
val = val.Elem()
|
||||
}
|
||||
|
||||
for i := 0; i < typ.NumField(); i++ {
|
||||
field := typ.Field(i)
|
||||
|
||||
// Allow ignored fields in the struct
|
||||
if field.Tag.Get("form") == "-" {
|
||||
continue
|
||||
}
|
||||
|
||||
fieldValue := val.Field(i).Interface()
|
||||
if field.Type.Kind() == reflect.Struct {
|
||||
validateStruct(errors, fieldValue)
|
||||
continue
|
||||
}
|
||||
|
||||
zero := reflect.Zero(field.Type).Interface()
|
||||
|
||||
// Match rules.
|
||||
for _, rule := range strings.Split(field.Tag.Get("binding"), ";") {
|
||||
if len(rule) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
switch {
|
||||
case rule == "Required":
|
||||
if reflect.DeepEqual(zero, fieldValue) {
|
||||
errors.Fields[field.Name] = BindingRequireError
|
||||
break
|
||||
}
|
||||
case rule == "AlphaDash":
|
||||
if alphaDashPattern.MatchString(fmt.Sprintf("%v", fieldValue)) {
|
||||
errors.Fields[field.Name] = BindingAlphaDashError
|
||||
break
|
||||
}
|
||||
case rule == "AlphaDashDot":
|
||||
if alphaDashDotPattern.MatchString(fmt.Sprintf("%v", fieldValue)) {
|
||||
errors.Fields[field.Name] = BindingAlphaDashDotError
|
||||
break
|
||||
}
|
||||
case strings.HasPrefix(rule, "MinSize("):
|
||||
min, err := strconv.Atoi(rule[8 : len(rule)-1])
|
||||
if err != nil {
|
||||
errors.Overall["MinSize"] = err.Error()
|
||||
break
|
||||
}
|
||||
if str, ok := fieldValue.(string); ok && utf8.RuneCountInString(str) < min {
|
||||
errors.Fields[field.Name] = BindingMinSizeError
|
||||
break
|
||||
}
|
||||
v := reflect.ValueOf(fieldValue)
|
||||
if v.Kind() == reflect.Slice && v.Len() < min {
|
||||
errors.Fields[field.Name] = BindingMinSizeError
|
||||
break
|
||||
}
|
||||
case strings.HasPrefix(rule, "MaxSize("):
|
||||
max, err := strconv.Atoi(rule[8 : len(rule)-1])
|
||||
if err != nil {
|
||||
errors.Overall["MaxSize"] = err.Error()
|
||||
break
|
||||
}
|
||||
if str, ok := fieldValue.(string); ok && utf8.RuneCountInString(str) > max {
|
||||
errors.Fields[field.Name] = BindingMaxSizeError
|
||||
break
|
||||
}
|
||||
v := reflect.ValueOf(fieldValue)
|
||||
if v.Kind() == reflect.Slice && v.Len() > max {
|
||||
errors.Fields[field.Name] = BindingMinSizeError
|
||||
break
|
||||
}
|
||||
case rule == "Email":
|
||||
if !emailPattern.MatchString(fmt.Sprintf("%v", fieldValue)) {
|
||||
errors.Fields[field.Name] = BindingEmailError
|
||||
break
|
||||
}
|
||||
case rule == "Url":
|
||||
str := fmt.Sprintf("%v", fieldValue)
|
||||
if len(str) == 0 {
|
||||
continue
|
||||
} else if !urlPattern.MatchString(str) {
|
||||
errors.Fields[field.Name] = BindingUrlError
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func mapForm(formStruct reflect.Value, form map[string][]string, errors *Errors) {
|
||||
typ := formStruct.Elem().Type()
|
||||
|
||||
for i := 0; i < typ.NumField(); i++ {
|
||||
typeField := typ.Field(i)
|
||||
if inputFieldName := typeField.Tag.Get("form"); inputFieldName != "" {
|
||||
structField := formStruct.Elem().Field(i)
|
||||
if !structField.CanSet() {
|
||||
continue
|
||||
}
|
||||
|
||||
inputValue, exists := form[inputFieldName]
|
||||
|
||||
if !exists {
|
||||
continue
|
||||
}
|
||||
|
||||
numElems := len(inputValue)
|
||||
if structField.Kind() == reflect.Slice && numElems > 0 {
|
||||
sliceOf := structField.Type().Elem().Kind()
|
||||
slice := reflect.MakeSlice(structField.Type(), numElems, numElems)
|
||||
for i := 0; i < numElems; i++ {
|
||||
setWithProperType(sliceOf, inputValue[i], slice.Index(i), inputFieldName, errors)
|
||||
}
|
||||
formStruct.Elem().Field(i).Set(slice)
|
||||
} else {
|
||||
setWithProperType(typeField.Type.Kind(), inputValue[0], structField, inputFieldName, errors)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ErrorHandler simply counts the number of errors in the
|
||||
// context and, if more than 0, writes a 400 Bad Request
|
||||
// response and a JSON payload describing the errors with
|
||||
// the "Content-Type" set to "application/json".
|
||||
// Middleware remaining on the stack will not even see the request
|
||||
// if, by this point, there are any errors.
|
||||
// This is a "default" handler, of sorts, and you are
|
||||
// welcome to use your own instead. The Bind middleware
|
||||
// invokes this automatically for convenience.
|
||||
func ErrorHandler(errs Errors, resp http.ResponseWriter) {
|
||||
if errs.Count() > 0 {
|
||||
resp.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
if _, ok := errs.Overall[BindingDeserializationError]; ok {
|
||||
resp.WriteHeader(http.StatusBadRequest)
|
||||
} else {
|
||||
resp.WriteHeader(422)
|
||||
}
|
||||
errOutput, _ := json.Marshal(errs)
|
||||
resp.Write(errOutput)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// This sets the value in a struct of an indeterminate type to the
|
||||
// matching value from the request (via Form middleware) in the
|
||||
// same type, so that not all deserialized values have to be strings.
|
||||
// Supported types are string, int, float, and bool.
|
||||
func setWithProperType(valueKind reflect.Kind, val string, structField reflect.Value, nameInTag string, errors *Errors) {
|
||||
switch valueKind {
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
if val == "" {
|
||||
val = "0"
|
||||
}
|
||||
intVal, err := strconv.ParseInt(val, 10, 64)
|
||||
if err != nil {
|
||||
errors.Fields[nameInTag] = BindingIntegerTypeError
|
||||
} else {
|
||||
structField.SetInt(intVal)
|
||||
}
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
if val == "" {
|
||||
val = "0"
|
||||
}
|
||||
uintVal, err := strconv.ParseUint(val, 10, 64)
|
||||
if err != nil {
|
||||
errors.Fields[nameInTag] = BindingIntegerTypeError
|
||||
} else {
|
||||
structField.SetUint(uintVal)
|
||||
}
|
||||
case reflect.Bool:
|
||||
structField.SetBool(val == "on")
|
||||
case reflect.Float32:
|
||||
if val == "" {
|
||||
val = "0.0"
|
||||
}
|
||||
floatVal, err := strconv.ParseFloat(val, 32)
|
||||
if err != nil {
|
||||
errors.Fields[nameInTag] = BindingFloatTypeError
|
||||
} else {
|
||||
structField.SetFloat(floatVal)
|
||||
}
|
||||
case reflect.Float64:
|
||||
if val == "" {
|
||||
val = "0.0"
|
||||
}
|
||||
floatVal, err := strconv.ParseFloat(val, 64)
|
||||
if err != nil {
|
||||
errors.Fields[nameInTag] = BindingFloatTypeError
|
||||
} else {
|
||||
structField.SetFloat(floatVal)
|
||||
}
|
||||
case reflect.String:
|
||||
structField.SetString(val)
|
||||
}
|
||||
}
|
||||
|
||||
// Don't pass in pointers to bind to. Can lead to bugs. See:
|
||||
// https://github.com/codegangsta/martini-contrib/issues/40
|
||||
// https://github.com/codegangsta/martini-contrib/pull/34#issuecomment-29683659
|
||||
func ensureNotPointer(obj interface{}) {
|
||||
if reflect.TypeOf(obj).Kind() == reflect.Ptr {
|
||||
panic("Pointers are not accepted as binding models")
|
||||
}
|
||||
}
|
||||
|
||||
// Performs validation and combines errors from validation
|
||||
// with errors from deserialization, then maps both the
|
||||
// resulting struct and the errors to the context.
|
||||
func validateAndMap(obj reflect.Value, context martini.Context, errors *Errors, ifacePtr ...interface{}) {
|
||||
context.Invoke(Validate(obj.Interface()))
|
||||
errors.Combine(getErrors(context))
|
||||
context.Map(*errors)
|
||||
context.Map(obj.Elem().Interface())
|
||||
if len(ifacePtr) > 0 {
|
||||
context.MapTo(obj.Elem().Interface(), ifacePtr[0])
|
||||
}
|
||||
}
|
||||
|
||||
func newErrors() *Errors {
|
||||
return &Errors{make(map[string]string), make(map[string]string)}
|
||||
}
|
||||
|
||||
func getErrors(context martini.Context) Errors {
|
||||
return context.Get(reflect.TypeOf(Errors{})).Interface().(Errors)
|
||||
}
|
||||
|
||||
type (
|
||||
// Implement the Validator interface to define your own input
|
||||
// validation before the request even gets to your application.
|
||||
// The Validate method will be executed during the validation phase.
|
||||
Validator interface {
|
||||
Validate(*Errors, *http.Request, martini.Context)
|
||||
}
|
||||
)
|
||||
|
||||
var (
|
||||
// Maximum amount of memory to use when parsing a multipart form.
|
||||
// Set this to whatever value you prefer; default is 10 MB.
|
||||
MaxMemory = int64(1024 * 1024 * 10)
|
||||
)
|
||||
|
||||
// Errors represents the contract of the response body when the
|
||||
// binding step fails before getting to the application.
|
||||
type Errors struct {
|
||||
Overall map[string]string `json:"overall"`
|
||||
Fields map[string]string `json:"fields"`
|
||||
}
|
||||
|
||||
// Total errors is the sum of errors with the request overall
|
||||
// and errors on individual fields.
|
||||
func (err Errors) Count() int {
|
||||
return len(err.Overall) + len(err.Fields)
|
||||
}
|
||||
|
||||
func (this *Errors) Combine(other Errors) {
|
||||
for key, val := range other.Fields {
|
||||
if _, exists := this.Fields[key]; !exists {
|
||||
this.Fields[key] = val
|
||||
}
|
||||
}
|
||||
for key, val := range other.Overall {
|
||||
if _, exists := this.Overall[key]; !exists {
|
||||
this.Overall[key] = val
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
BindingRequireError string = "Required"
|
||||
BindingAlphaDashError string = "AlphaDash"
|
||||
BindingAlphaDashDotError string = "AlphaDashDot"
|
||||
BindingMinSizeError string = "MinSize"
|
||||
BindingMaxSizeError string = "MaxSize"
|
||||
BindingEmailError string = "Email"
|
||||
BindingUrlError string = "Url"
|
||||
BindingDeserializationError string = "DeserializationError"
|
||||
BindingIntegerTypeError string = "IntegerTypeError"
|
||||
BindingBooleanTypeError string = "BooleanTypeError"
|
||||
BindingFloatTypeError string = "FloatTypeError"
|
||||
)
|
||||
@@ -10,7 +10,10 @@ import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -25,6 +28,7 @@ import (
|
||||
"github.com/gogits/gogs/modules/auth"
|
||||
"github.com/gogits/gogs/modules/base"
|
||||
"github.com/gogits/gogs/modules/log"
|
||||
"github.com/gogits/gogs/modules/setting"
|
||||
)
|
||||
|
||||
// Context represents context of a request.
|
||||
@@ -34,6 +38,7 @@ type Context struct {
|
||||
p martini.Params
|
||||
Req *http.Request
|
||||
Res http.ResponseWriter
|
||||
Flash *Flash
|
||||
Session session.SessionStore
|
||||
Cache cache.Cache
|
||||
User *models.User
|
||||
@@ -47,6 +52,7 @@ type Context struct {
|
||||
IsBranch bool
|
||||
IsTag bool
|
||||
IsCommit bool
|
||||
HasAccess bool
|
||||
Repository *models.Repository
|
||||
Owner *models.User
|
||||
Commit *git.Commit
|
||||
@@ -59,6 +65,7 @@ type Context struct {
|
||||
HTTPS string
|
||||
Git string
|
||||
}
|
||||
Mirror *models.Mirror
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,12 +79,27 @@ func (ctx *Context) Query(name string) string {
|
||||
// return ctx.p[name]
|
||||
// }
|
||||
|
||||
// HasError returns true if error occurs in form validation.
|
||||
func (ctx *Context) HasApiError() bool {
|
||||
hasErr, ok := ctx.Data["HasError"]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
return hasErr.(bool)
|
||||
}
|
||||
|
||||
func (ctx *Context) GetErrMsg() string {
|
||||
return ctx.Data["ErrorMsg"].(string)
|
||||
}
|
||||
|
||||
// HasError returns true if error occurs in form validation.
|
||||
func (ctx *Context) HasError() bool {
|
||||
hasErr, ok := ctx.Data["HasError"]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
ctx.Flash.ErrorMsg = ctx.Data["ErrorMsg"].(string)
|
||||
ctx.Data["Flash"] = ctx.Flash
|
||||
return hasErr.(bool)
|
||||
}
|
||||
|
||||
@@ -88,21 +110,29 @@ func (ctx *Context) HTML(status int, name string, htmlOpt ...HTMLOptions) {
|
||||
|
||||
// RenderWithErr used for page has form validation but need to prompt error to users.
|
||||
func (ctx *Context) RenderWithErr(msg, tpl string, form auth.Form) {
|
||||
ctx.Data["HasError"] = true
|
||||
ctx.Data["ErrorMsg"] = msg
|
||||
auth.AssignForm(form, ctx.Data)
|
||||
if form != nil {
|
||||
auth.AssignForm(form, ctx.Data)
|
||||
}
|
||||
ctx.Flash.ErrorMsg = msg
|
||||
ctx.Data["Flash"] = ctx.Flash
|
||||
ctx.HTML(200, tpl)
|
||||
}
|
||||
|
||||
// Handle handles and logs error by given status.
|
||||
func (ctx *Context) Handle(status int, title string, err error) {
|
||||
log.Error("%s: %v", title, err)
|
||||
if martini.Dev == martini.Prod {
|
||||
ctx.HTML(500, "status/500")
|
||||
return
|
||||
if err != nil {
|
||||
log.Error("%s: %v", title, err)
|
||||
if martini.Dev != martini.Prod {
|
||||
ctx.Data["ErrorMsg"] = err
|
||||
}
|
||||
}
|
||||
|
||||
ctx.Data["ErrorMsg"] = err
|
||||
switch status {
|
||||
case 404:
|
||||
ctx.Data["Title"] = "Page Not Found"
|
||||
case 500:
|
||||
ctx.Data["Title"] = "Internal Server Error"
|
||||
}
|
||||
ctx.HTML(status, fmt.Sprintf("status/%d", status))
|
||||
}
|
||||
|
||||
@@ -237,6 +267,56 @@ func (ctx *Context) CsrfTokenValid() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (ctx *Context) ServeFile(file string, names ...string) {
|
||||
var name string
|
||||
if len(names) > 0 {
|
||||
name = names[0]
|
||||
} else {
|
||||
name = filepath.Base(file)
|
||||
}
|
||||
ctx.Res.Header().Set("Content-Description", "File Transfer")
|
||||
ctx.Res.Header().Set("Content-Type", "application/octet-stream")
|
||||
ctx.Res.Header().Set("Content-Disposition", "attachment; filename="+name)
|
||||
ctx.Res.Header().Set("Content-Transfer-Encoding", "binary")
|
||||
ctx.Res.Header().Set("Expires", "0")
|
||||
ctx.Res.Header().Set("Cache-Control", "must-revalidate")
|
||||
ctx.Res.Header().Set("Pragma", "public")
|
||||
http.ServeFile(ctx.Res, ctx.Req, file)
|
||||
}
|
||||
|
||||
func (ctx *Context) ServeContent(name string, r io.ReadSeeker, params ...interface{}) {
|
||||
modtime := time.Now()
|
||||
for _, p := range params {
|
||||
switch v := p.(type) {
|
||||
case time.Time:
|
||||
modtime = v
|
||||
}
|
||||
}
|
||||
ctx.Res.Header().Set("Content-Description", "File Transfer")
|
||||
ctx.Res.Header().Set("Content-Type", "application/octet-stream")
|
||||
ctx.Res.Header().Set("Content-Disposition", "attachment; filename="+name)
|
||||
ctx.Res.Header().Set("Content-Transfer-Encoding", "binary")
|
||||
ctx.Res.Header().Set("Expires", "0")
|
||||
ctx.Res.Header().Set("Cache-Control", "must-revalidate")
|
||||
ctx.Res.Header().Set("Pragma", "public")
|
||||
http.ServeContent(ctx.Res, ctx.Req, name, modtime, r)
|
||||
}
|
||||
|
||||
type Flash struct {
|
||||
url.Values
|
||||
ErrorMsg, SuccessMsg string
|
||||
}
|
||||
|
||||
func (f *Flash) Error(msg string) {
|
||||
f.Set("error", msg)
|
||||
f.ErrorMsg = msg
|
||||
}
|
||||
|
||||
func (f *Flash) Success(msg string) {
|
||||
f.Set("success", msg)
|
||||
f.SuccessMsg = msg
|
||||
}
|
||||
|
||||
// InitContext initializes a classic context for a request.
|
||||
func InitContext() martini.Handler {
|
||||
return func(res http.ResponseWriter, r *http.Request, c martini.Context, rd *Render) {
|
||||
@@ -246,17 +326,35 @@ func InitContext() martini.Handler {
|
||||
// p: p,
|
||||
Req: r,
|
||||
Res: res,
|
||||
Cache: base.Cache,
|
||||
Cache: setting.Cache,
|
||||
Render: rd,
|
||||
}
|
||||
|
||||
ctx.Data["PageStartTime"] = time.Now()
|
||||
|
||||
// start session
|
||||
ctx.Session = base.SessionManager.SessionStart(res, r)
|
||||
ctx.Session = setting.SessionManager.SessionStart(res, r)
|
||||
|
||||
// Get flash.
|
||||
values, err := url.ParseQuery(ctx.GetCookie("gogs_flash"))
|
||||
if err != nil {
|
||||
log.Error("InitContext.ParseQuery(flash): %v", err)
|
||||
} else if len(values) > 0 {
|
||||
ctx.Flash = &Flash{Values: values}
|
||||
ctx.Flash.ErrorMsg = ctx.Flash.Get("error")
|
||||
ctx.Flash.SuccessMsg = ctx.Flash.Get("success")
|
||||
ctx.Data["Flash"] = ctx.Flash
|
||||
ctx.SetCookie("gogs_flash", "", -1)
|
||||
}
|
||||
ctx.Flash = &Flash{Values: url.Values{}}
|
||||
|
||||
rw := res.(martini.ResponseWriter)
|
||||
rw.Before(func(martini.ResponseWriter) {
|
||||
ctx.Session.SessionRelease(res)
|
||||
|
||||
if flash := ctx.Flash.Encode(); len(flash) > 0 {
|
||||
ctx.SetCookie("gogs_flash", ctx.Flash.Encode(), 0)
|
||||
}
|
||||
})
|
||||
|
||||
// Get user from session if logined.
|
||||
|
||||
@@ -12,6 +12,8 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/go-martini/martini"
|
||||
|
||||
"github.com/gogits/gogs/modules/setting"
|
||||
)
|
||||
|
||||
var isWindows bool
|
||||
@@ -22,6 +24,10 @@ func init() {
|
||||
|
||||
func Logger() martini.Handler {
|
||||
return func(res http.ResponseWriter, req *http.Request, ctx martini.Context, log *log.Logger) {
|
||||
if setting.DisableRouterLog {
|
||||
return
|
||||
}
|
||||
|
||||
start := time.Now()
|
||||
log.Printf("Started %s %s", req.Method, req.URL.Path)
|
||||
|
||||
|
||||
@@ -38,26 +38,18 @@ var helperFuncs = template.FuncMap{
|
||||
}
|
||||
|
||||
type Delims struct {
|
||||
Left string
|
||||
|
||||
Left string
|
||||
Right string
|
||||
}
|
||||
|
||||
type RenderOptions struct {
|
||||
Directory string
|
||||
|
||||
Layout string
|
||||
|
||||
Extensions []string
|
||||
|
||||
Funcs []template.FuncMap
|
||||
|
||||
Delims Delims
|
||||
|
||||
Charset string
|
||||
|
||||
IndentJSON bool
|
||||
|
||||
Directory string
|
||||
Layout string
|
||||
Extensions []string
|
||||
Funcs []template.FuncMap
|
||||
Delims Delims
|
||||
Charset string
|
||||
IndentJSON bool
|
||||
HTMLContentType string
|
||||
}
|
||||
|
||||
@@ -146,7 +138,7 @@ func compile(options RenderOptions) *template.Template {
|
||||
tmpl := t.New(filepath.ToSlash(name))
|
||||
|
||||
for _, funcs := range options.Funcs {
|
||||
tmpl.Funcs(funcs)
|
||||
tmpl = tmpl.Funcs(funcs)
|
||||
}
|
||||
|
||||
template.Must(tmpl.Funcs(helperFuncs).Parse(string(buf)))
|
||||
|
||||
@@ -7,6 +7,7 @@ package middleware
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/go-martini/martini"
|
||||
@@ -14,44 +15,60 @@ import (
|
||||
"github.com/gogits/git"
|
||||
|
||||
"github.com/gogits/gogs/models"
|
||||
"github.com/gogits/gogs/modules/base"
|
||||
"github.com/gogits/gogs/modules/log"
|
||||
"github.com/gogits/gogs/modules/setting"
|
||||
)
|
||||
|
||||
func RepoAssignment(redirect bool, args ...bool) martini.Handler {
|
||||
return func(ctx *Context, params martini.Params) {
|
||||
log.Trace(fmt.Sprint(args))
|
||||
// valid brachname
|
||||
var validBranch bool
|
||||
// display bare quick start if it is a bare repo
|
||||
var displayBare bool
|
||||
|
||||
if len(args) >= 1 {
|
||||
validBranch = args[0]
|
||||
// Note: argument has wrong value in Go1.3 martini.
|
||||
// validBranch = args[0]
|
||||
validBranch = true
|
||||
}
|
||||
|
||||
if len(args) >= 2 {
|
||||
displayBare = args[1]
|
||||
// displayBare = args[1]
|
||||
displayBare = true
|
||||
}
|
||||
|
||||
var (
|
||||
user *models.User
|
||||
err error
|
||||
user *models.User
|
||||
err error
|
||||
isTrueOwner bool
|
||||
)
|
||||
|
||||
userName := params["username"]
|
||||
repoName := params["reponame"]
|
||||
branchName := params["branchname"]
|
||||
refName := params["branchname"]
|
||||
|
||||
// get repository owner
|
||||
ctx.Repo.IsOwner = ctx.IsSigned && ctx.User.LowerName == strings.ToLower(userName)
|
||||
|
||||
if !ctx.Repo.IsOwner {
|
||||
user, err = models.GetUserByName(params["username"])
|
||||
// Collaborators who have write access can be seen as owners.
|
||||
if ctx.IsSigned {
|
||||
ctx.Repo.IsOwner, err = models.HasAccess(ctx.User.Name, userName+"/"+repoName, models.AU_WRITABLE)
|
||||
if err != nil {
|
||||
if redirect {
|
||||
ctx.Handle(500, "RepoAssignment(HasAccess)", err)
|
||||
return
|
||||
}
|
||||
isTrueOwner = ctx.User.LowerName == strings.ToLower(userName)
|
||||
}
|
||||
|
||||
if !isTrueOwner {
|
||||
user, err = models.GetUserByName(userName)
|
||||
if err != nil {
|
||||
if err == models.ErrUserNotExist {
|
||||
ctx.Handle(404, "RepoAssignment(GetUserByName)", err)
|
||||
return
|
||||
} else if redirect {
|
||||
ctx.Redirect("/")
|
||||
return
|
||||
}
|
||||
ctx.Handle(200, "RepoAssignment", err)
|
||||
ctx.Handle(500, "RepoAssignment(GetUserByName)", err)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
@@ -63,36 +80,78 @@ func RepoAssignment(redirect bool, args ...bool) martini.Handler {
|
||||
ctx.Redirect("/")
|
||||
return
|
||||
}
|
||||
ctx.Handle(200, "RepoAssignment", errors.New("invliad user account for single repository"))
|
||||
ctx.Handle(403, "RepoAssignment", errors.New("invliad user account for single repository"))
|
||||
return
|
||||
}
|
||||
ctx.Repo.Owner = user
|
||||
|
||||
// get repository
|
||||
repo, err := models.GetRepositoryByName(user.Id, repoName)
|
||||
if err != nil {
|
||||
if err == models.ErrRepoNotExist {
|
||||
ctx.Handle(404, "RepoAssignment", err)
|
||||
return
|
||||
} else if redirect {
|
||||
ctx.Redirect("/")
|
||||
return
|
||||
}
|
||||
ctx.Handle(404, "RepoAssignment", err)
|
||||
ctx.Handle(500, "RepoAssignment", err)
|
||||
return
|
||||
}
|
||||
ctx.Repo.Repository = repo
|
||||
|
||||
// Check if the mirror repository owner(mirror repository doesn't have access).
|
||||
if ctx.IsSigned && !ctx.Repo.IsOwner && repo.OwnerId == ctx.User.Id {
|
||||
ctx.Repo.IsOwner = true
|
||||
}
|
||||
|
||||
// Check access.
|
||||
if repo.IsPrivate && !ctx.Repo.IsOwner {
|
||||
if ctx.User == nil {
|
||||
ctx.Handle(404, "RepoAssignment(HasAccess)", nil)
|
||||
return
|
||||
}
|
||||
|
||||
hasAccess, err := models.HasAccess(ctx.User.Name, ctx.Repo.Owner.Name+"/"+repo.Name, models.AU_READABLE)
|
||||
if err != nil {
|
||||
ctx.Handle(500, "RepoAssignment(HasAccess)", err)
|
||||
return
|
||||
} else if !hasAccess {
|
||||
ctx.Handle(404, "RepoAssignment(HasAccess)", nil)
|
||||
return
|
||||
}
|
||||
}
|
||||
ctx.Repo.HasAccess = true
|
||||
ctx.Data["HasAccess"] = true
|
||||
|
||||
if repo.IsMirror {
|
||||
ctx.Repo.Mirror, err = models.GetMirror(repo.Id)
|
||||
if err != nil {
|
||||
ctx.Handle(500, "RepoAssignment(GetMirror)", err)
|
||||
return
|
||||
}
|
||||
ctx.Data["MirrorInterval"] = ctx.Repo.Mirror.Interval
|
||||
}
|
||||
|
||||
repo.NumOpenIssues = repo.NumIssues - repo.NumClosedIssues
|
||||
repo.NumOpenMilestones = repo.NumMilestones - repo.NumClosedMilestones
|
||||
ctx.Repo.Repository = repo
|
||||
ctx.Data["IsBareRepo"] = ctx.Repo.Repository.IsBare
|
||||
|
||||
gitRepo, err := git.OpenRepository(models.RepoPath(userName, repoName))
|
||||
if err != nil {
|
||||
ctx.Handle(404, "RepoAssignment Invalid repo "+models.RepoPath(userName, repoName), err)
|
||||
ctx.Handle(500, "RepoAssignment Invalid repo "+models.RepoPath(userName, repoName), err)
|
||||
return
|
||||
}
|
||||
ctx.Repo.GitRepo = gitRepo
|
||||
|
||||
ctx.Repo.Owner = user
|
||||
ctx.Repo.RepoLink = "/" + user.Name + "/" + repo.Name
|
||||
|
||||
tags, err := ctx.Repo.GitRepo.GetTags()
|
||||
if err != nil {
|
||||
ctx.Handle(500, "RepoAssignment(GetTags))", err)
|
||||
return
|
||||
}
|
||||
ctx.Repo.Repository.NumTags = len(tags)
|
||||
|
||||
ctx.Data["Title"] = user.Name + "/" + repo.Name
|
||||
ctx.Data["Repository"] = repo
|
||||
ctx.Data["Owner"] = user
|
||||
@@ -100,33 +159,51 @@ func RepoAssignment(redirect bool, args ...bool) martini.Handler {
|
||||
ctx.Data["IsRepositoryOwner"] = ctx.Repo.IsOwner
|
||||
ctx.Data["BranchName"] = ""
|
||||
|
||||
ctx.Repo.CloneLink.SSH = fmt.Sprintf("%s@%s:%s/%s.git", base.RunUser, base.Domain, user.LowerName, repo.LowerName)
|
||||
ctx.Repo.CloneLink.HTTPS = fmt.Sprintf("%s%s/%s.git", base.AppUrl, user.LowerName, repo.LowerName)
|
||||
if setting.SshPort != 22 {
|
||||
ctx.Repo.CloneLink.SSH = fmt.Sprintf("ssh://%s@%s/%s/%s.git", setting.RunUser, setting.Domain, user.LowerName, repo.LowerName)
|
||||
} else {
|
||||
ctx.Repo.CloneLink.SSH = fmt.Sprintf("%s@%s:%s/%s.git", setting.RunUser, setting.Domain, user.LowerName, repo.LowerName)
|
||||
}
|
||||
ctx.Repo.CloneLink.HTTPS = fmt.Sprintf("%s%s/%s.git", setting.AppUrl, user.LowerName, repo.LowerName)
|
||||
ctx.Data["CloneLink"] = ctx.Repo.CloneLink
|
||||
|
||||
if ctx.Repo.Repository.IsGoget {
|
||||
ctx.Data["GoGetLink"] = fmt.Sprintf("%s%s/%s", setting.AppUrl, user.LowerName, repo.LowerName)
|
||||
ctx.Data["GoGetImport"] = fmt.Sprintf("%s/%s/%s", setting.Domain, user.LowerName, repo.LowerName)
|
||||
}
|
||||
|
||||
// when repo is bare, not valid branch
|
||||
if !ctx.Repo.Repository.IsBare && validBranch {
|
||||
detect:
|
||||
if len(branchName) > 0 {
|
||||
// TODO check tag
|
||||
if models.IsBranchExist(user.Name, repoName, branchName) {
|
||||
if len(refName) > 0 {
|
||||
if gitRepo.IsBranchExist(refName) {
|
||||
ctx.Repo.IsBranch = true
|
||||
ctx.Repo.BranchName = branchName
|
||||
ctx.Repo.BranchName = refName
|
||||
|
||||
ctx.Repo.Commit, err = gitRepo.GetCommitOfBranch(branchName)
|
||||
ctx.Repo.Commit, err = gitRepo.GetCommitOfBranch(refName)
|
||||
if err != nil {
|
||||
ctx.Handle(404, "RepoAssignment invalid branch", nil)
|
||||
return
|
||||
}
|
||||
ctx.Repo.CommitId = ctx.Repo.Commit.Id.String()
|
||||
|
||||
ctx.Repo.CommitId = ctx.Repo.Commit.Oid.String()
|
||||
} else if gitRepo.IsTagExist(refName) {
|
||||
ctx.Repo.IsBranch = true
|
||||
ctx.Repo.BranchName = refName
|
||||
|
||||
} else if len(branchName) == 40 {
|
||||
ctx.Repo.Commit, err = gitRepo.GetCommitOfTag(refName)
|
||||
if err != nil {
|
||||
ctx.Handle(404, "RepoAssignment invalid tag", nil)
|
||||
return
|
||||
}
|
||||
ctx.Repo.CommitId = ctx.Repo.Commit.Id.String()
|
||||
|
||||
} else if len(refName) == 40 {
|
||||
ctx.Repo.IsCommit = true
|
||||
ctx.Repo.CommitId = branchName
|
||||
ctx.Repo.BranchName = branchName
|
||||
ctx.Repo.CommitId = refName
|
||||
ctx.Repo.BranchName = refName
|
||||
|
||||
ctx.Repo.Commit, err = gitRepo.GetCommit(branchName)
|
||||
ctx.Repo.Commit, err = gitRepo.GetCommit(refName)
|
||||
if err != nil {
|
||||
ctx.Handle(404, "RepoAssignment invalid commit", nil)
|
||||
return
|
||||
@@ -137,7 +214,18 @@ func RepoAssignment(redirect bool, args ...bool) martini.Handler {
|
||||
}
|
||||
|
||||
} else {
|
||||
branchName = "master"
|
||||
if len(refName) == 0 {
|
||||
if gitRepo.IsBranchExist(ctx.Repo.Repository.DefaultBranch) {
|
||||
refName = ctx.Repo.Repository.DefaultBranch
|
||||
} else {
|
||||
brs, err := gitRepo.GetBranches()
|
||||
if err != nil {
|
||||
ctx.Handle(500, "RepoAssignment(GetBranches))", err)
|
||||
return
|
||||
}
|
||||
refName = brs[0]
|
||||
}
|
||||
}
|
||||
goto detect
|
||||
}
|
||||
|
||||
@@ -145,8 +233,11 @@ func RepoAssignment(redirect bool, args ...bool) martini.Handler {
|
||||
ctx.Data["IsCommit"] = ctx.Repo.IsCommit
|
||||
}
|
||||
|
||||
log.Debug("displayBare: %v; IsBare: %v", displayBare, ctx.Repo.Repository.IsBare)
|
||||
|
||||
// repo is bare and display enable
|
||||
if displayBare && ctx.Repo.Repository.IsBare {
|
||||
log.Debug("Bare repository: %s", ctx.Repo.RepoLink)
|
||||
ctx.HTML(200, "repo/single_bare")
|
||||
return
|
||||
}
|
||||
@@ -156,7 +247,26 @@ func RepoAssignment(redirect bool, args ...bool) martini.Handler {
|
||||
}
|
||||
|
||||
ctx.Data["BranchName"] = ctx.Repo.BranchName
|
||||
brs, err := ctx.Repo.GitRepo.GetBranches()
|
||||
if err != nil {
|
||||
log.Error("RepoAssignment(GetBranches): %v", err)
|
||||
}
|
||||
ctx.Data["Branches"] = brs
|
||||
ctx.Data["CommitId"] = ctx.Repo.CommitId
|
||||
ctx.Data["IsRepositoryWatching"] = ctx.Repo.IsWatching
|
||||
}
|
||||
}
|
||||
|
||||
func RequireOwner() martini.Handler {
|
||||
return func(ctx *Context) {
|
||||
if !ctx.Repo.IsOwner {
|
||||
if !ctx.IsSigned {
|
||||
ctx.SetCookie("redirect_to", "/"+url.QueryEscape(ctx.Req.RequestURI))
|
||||
ctx.Redirect("/user/login")
|
||||
return
|
||||
}
|
||||
ctx.Handle(404, ctx.Req.RequestURI, nil)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
401
modules/setting/setting.go
Normal file
401
modules/setting/setting.go
Normal file
@@ -0,0 +1,401 @@
|
||||
// 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 setting
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/Unknwon/com"
|
||||
"github.com/Unknwon/goconfig"
|
||||
|
||||
"github.com/gogits/cache"
|
||||
"github.com/gogits/session"
|
||||
|
||||
"github.com/gogits/gogs/modules/bin"
|
||||
"github.com/gogits/gogs/modules/log"
|
||||
)
|
||||
|
||||
type Scheme string
|
||||
|
||||
const (
|
||||
HTTP Scheme = "http"
|
||||
HTTPS Scheme = "https"
|
||||
)
|
||||
|
||||
var (
|
||||
// App settings.
|
||||
AppVer string
|
||||
AppName string
|
||||
AppLogo string
|
||||
AppUrl string
|
||||
|
||||
// Server settings.
|
||||
Protocol Scheme
|
||||
Domain string
|
||||
HttpAddr, HttpPort string
|
||||
SshPort int
|
||||
OfflineMode bool
|
||||
DisableRouterLog bool
|
||||
CertFile, KeyFile string
|
||||
StaticRootPath string
|
||||
|
||||
// Security settings.
|
||||
InstallLock bool
|
||||
SecretKey string
|
||||
LogInRememberDays int
|
||||
CookieUserName string
|
||||
CookieRememberName string
|
||||
|
||||
// Repository settings.
|
||||
RepoRootPath string
|
||||
ScriptType string
|
||||
|
||||
// Picture settings.
|
||||
PictureService string
|
||||
DisableGravatar bool
|
||||
|
||||
// Log settings.
|
||||
LogRootPath string
|
||||
LogModes []string
|
||||
LogConfigs []string
|
||||
|
||||
// Cache settings.
|
||||
Cache cache.Cache
|
||||
CacheAdapter string
|
||||
CacheConfig string
|
||||
|
||||
EnableRedis bool
|
||||
EnableMemcache bool
|
||||
|
||||
// Session settings.
|
||||
SessionProvider string
|
||||
SessionConfig *session.Config
|
||||
SessionManager *session.Manager
|
||||
|
||||
// Global setting objects.
|
||||
Cfg *goconfig.ConfigFile
|
||||
CustomPath string // Custom directory path.
|
||||
ProdMode bool
|
||||
RunUser string
|
||||
)
|
||||
|
||||
// WorkDir returns absolute path of work directory.
|
||||
func WorkDir() (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 path.Dir(strings.Replace(p, "\\", "/", -1)), nil
|
||||
}
|
||||
|
||||
// NewConfigContext initializes configuration context.
|
||||
// NOTE: do not print any log except error.
|
||||
func NewConfigContext() {
|
||||
workDir, err := WorkDir()
|
||||
if err != nil {
|
||||
log.Fatal("Fail to get work directory: %v", err)
|
||||
}
|
||||
|
||||
data, err := bin.Asset("conf/app.ini")
|
||||
if err != nil {
|
||||
log.Fatal("Fail to read 'conf/app.ini': %v", err)
|
||||
}
|
||||
Cfg, err = goconfig.LoadFromData(data)
|
||||
if err != nil {
|
||||
log.Fatal("Fail to parse 'conf/app.ini': %v", err)
|
||||
}
|
||||
|
||||
CustomPath = os.Getenv("GOGS_CUSTOM")
|
||||
if len(CustomPath) == 0 {
|
||||
CustomPath = path.Join(workDir, "custom")
|
||||
}
|
||||
|
||||
cfgPath := path.Join(CustomPath, "conf/app.ini")
|
||||
if com.IsFile(cfgPath) {
|
||||
if err = Cfg.AppendFiles(cfgPath); err != nil {
|
||||
log.Fatal("Fail to load custom 'conf/app.ini': %v", err)
|
||||
}
|
||||
} else {
|
||||
log.Warn("No custom 'conf/app.ini' found")
|
||||
}
|
||||
|
||||
AppName = Cfg.MustValue("", "APP_NAME", "Gogs: Go Git Service")
|
||||
AppLogo = Cfg.MustValue("", "APP_LOGO", "img/favicon.png")
|
||||
AppUrl = Cfg.MustValue("server", "ROOT_URL", "http://localhost:3000")
|
||||
|
||||
Protocol = HTTP
|
||||
if Cfg.MustValue("server", "PROTOCOL") == "https" {
|
||||
Protocol = HTTPS
|
||||
CertFile = Cfg.MustValue("server", "CERT_FILE")
|
||||
KeyFile = Cfg.MustValue("server", "KEY_FILE")
|
||||
}
|
||||
Domain = Cfg.MustValue("server", "DOMAIN", "localhost")
|
||||
HttpAddr = Cfg.MustValue("server", "HTTP_ADDR", "0.0.0.0")
|
||||
HttpPort = Cfg.MustValue("server", "HTTP_PORT", "3000")
|
||||
SshPort = Cfg.MustInt("server", "SSH_PORT", 22)
|
||||
OfflineMode = Cfg.MustBool("server", "OFFLINE_MODE")
|
||||
DisableRouterLog = Cfg.MustBool("server", "DISABLE_ROUTER_LOG")
|
||||
StaticRootPath = Cfg.MustValue("server", "STATIC_ROOT_PATH", workDir)
|
||||
LogRootPath = Cfg.MustValue("log", "ROOT_PATH", path.Join(workDir, "log"))
|
||||
|
||||
InstallLock = Cfg.MustBool("security", "INSTALL_LOCK")
|
||||
SecretKey = Cfg.MustValue("security", "SECRET_KEY")
|
||||
LogInRememberDays = Cfg.MustInt("security", "LOGIN_REMEMBER_DAYS")
|
||||
CookieUserName = Cfg.MustValue("security", "COOKIE_USERNAME")
|
||||
CookieRememberName = Cfg.MustValue("security", "COOKIE_REMEMBER_NAME")
|
||||
|
||||
RunUser = Cfg.MustValue("", "RUN_USER")
|
||||
curUser := os.Getenv("USER")
|
||||
if len(curUser) == 0 {
|
||||
curUser = os.Getenv("USERNAME")
|
||||
}
|
||||
// Does not check run user when the install lock is off.
|
||||
if InstallLock && RunUser != curUser {
|
||||
log.Fatal("Expect user(%s) but current user is: %s", RunUser, curUser)
|
||||
}
|
||||
|
||||
// Determine and create root git reposiroty path.
|
||||
homeDir, err := com.HomeDir()
|
||||
if err != nil {
|
||||
log.Fatal("Fail to get home directory: %v", err)
|
||||
}
|
||||
RepoRootPath = Cfg.MustValue("repository", "ROOT", filepath.Join(homeDir, "gogs-repositories"))
|
||||
if err = os.MkdirAll(RepoRootPath, os.ModePerm); err != nil {
|
||||
log.Fatal("Fail to create repository root path(%s): %v", RepoRootPath, err)
|
||||
}
|
||||
ScriptType = Cfg.MustValue("repository", "SCRIPT_TYPE", "bash")
|
||||
|
||||
PictureService = Cfg.MustValueRange("picture", "SERVICE", "server",
|
||||
[]string{"server"})
|
||||
DisableGravatar = Cfg.MustBool("picture", "DISABLE_GRAVATAR")
|
||||
}
|
||||
|
||||
var Service struct {
|
||||
RegisterEmailConfirm bool
|
||||
DisableRegistration bool
|
||||
RequireSignInView bool
|
||||
EnableCacheAvatar bool
|
||||
NotifyMail bool
|
||||
ActiveCodeLives int
|
||||
ResetPwdCodeLives int
|
||||
LdapAuth bool
|
||||
}
|
||||
|
||||
func newService() {
|
||||
Service.ActiveCodeLives = Cfg.MustInt("service", "ACTIVE_CODE_LIVE_MINUTES", 180)
|
||||
Service.ResetPwdCodeLives = Cfg.MustInt("service", "RESET_PASSWD_CODE_LIVE_MINUTES", 180)
|
||||
Service.DisableRegistration = Cfg.MustBool("service", "DISABLE_REGISTRATION")
|
||||
Service.RequireSignInView = Cfg.MustBool("service", "REQUIRE_SIGNIN_VIEW")
|
||||
Service.EnableCacheAvatar = Cfg.MustBool("service", "ENABLE_CACHE_AVATAR")
|
||||
}
|
||||
|
||||
var logLevels = map[string]string{
|
||||
"Trace": "0",
|
||||
"Debug": "1",
|
||||
"Info": "2",
|
||||
"Warn": "3",
|
||||
"Error": "4",
|
||||
"Critical": "5",
|
||||
}
|
||||
|
||||
func newLogService() {
|
||||
log.Info("%s %s", AppName, AppVer)
|
||||
|
||||
// Get and check log mode.
|
||||
LogModes = strings.Split(Cfg.MustValue("log", "MODE", "console"), ",")
|
||||
LogConfigs = make([]string, len(LogModes))
|
||||
for i, mode := range LogModes {
|
||||
mode = strings.TrimSpace(mode)
|
||||
modeSec := "log." + mode
|
||||
if _, err := Cfg.GetSection(modeSec); err != nil {
|
||||
log.Fatal("Unknown log mode: %s", mode)
|
||||
}
|
||||
|
||||
// Log level.
|
||||
levelName := Cfg.MustValueRange("log."+mode, "LEVEL", "Trace",
|
||||
[]string{"Trace", "Debug", "Info", "Warn", "Error", "Critical"})
|
||||
level, ok := logLevels[levelName]
|
||||
if !ok {
|
||||
log.Fatal("Unknown log level: %s", levelName)
|
||||
}
|
||||
|
||||
// Generate log configuration.
|
||||
switch mode {
|
||||
case "console":
|
||||
LogConfigs[i] = fmt.Sprintf(`{"level":%s}`, level)
|
||||
case "file":
|
||||
logPath := Cfg.MustValue(modeSec, "FILE_NAME", path.Join(LogRootPath, "gogs.log"))
|
||||
os.MkdirAll(path.Dir(logPath), os.ModePerm)
|
||||
LogConfigs[i] = fmt.Sprintf(
|
||||
`{"level":%s,"filename":"%s","rotate":%v,"maxlines":%d,"maxsize":%d,"daily":%v,"maxdays":%d}`, level,
|
||||
logPath,
|
||||
Cfg.MustBool(modeSec, "LOG_ROTATE", true),
|
||||
Cfg.MustInt(modeSec, "MAX_LINES", 1000000),
|
||||
1<<uint(Cfg.MustInt(modeSec, "MAX_SIZE_SHIFT", 28)),
|
||||
Cfg.MustBool(modeSec, "DAILY_ROTATE", true),
|
||||
Cfg.MustInt(modeSec, "MAX_DAYS", 7))
|
||||
case "conn":
|
||||
LogConfigs[i] = fmt.Sprintf(`{"level":"%s","reconnectOnMsg":%v,"reconnect":%v,"net":"%s","addr":"%s"}`, level,
|
||||
Cfg.MustBool(modeSec, "RECONNECT_ON_MSG"),
|
||||
Cfg.MustBool(modeSec, "RECONNECT"),
|
||||
Cfg.MustValueRange(modeSec, "PROTOCOL", "tcp", []string{"tcp", "unix", "udp"}),
|
||||
Cfg.MustValue(modeSec, "ADDR", ":7020"))
|
||||
case "smtp":
|
||||
LogConfigs[i] = fmt.Sprintf(`{"level":"%s","username":"%s","password":"%s","host":"%s","sendTos":"%s","subject":"%s"}`, level,
|
||||
Cfg.MustValue(modeSec, "USER", "example@example.com"),
|
||||
Cfg.MustValue(modeSec, "PASSWD", "******"),
|
||||
Cfg.MustValue(modeSec, "HOST", "127.0.0.1:25"),
|
||||
Cfg.MustValue(modeSec, "RECEIVERS", "[]"),
|
||||
Cfg.MustValue(modeSec, "SUBJECT", "Diagnostic message from serve"))
|
||||
case "database":
|
||||
LogConfigs[i] = fmt.Sprintf(`{"level":"%s","driver":"%s","conn":"%s"}`, level,
|
||||
Cfg.MustValue(modeSec, "DRIVER"),
|
||||
Cfg.MustValue(modeSec, "CONN"))
|
||||
}
|
||||
|
||||
log.NewLogger(Cfg.MustInt64("log", "BUFFER_LEN", 10000), mode, LogConfigs[i])
|
||||
log.Info("Log Mode: %s(%s)", strings.Title(mode), levelName)
|
||||
}
|
||||
}
|
||||
|
||||
func newCacheService() {
|
||||
CacheAdapter = Cfg.MustValueRange("cache", "ADAPTER", "memory", []string{"memory", "redis", "memcache"})
|
||||
if EnableRedis {
|
||||
log.Info("Redis Enabled")
|
||||
}
|
||||
if EnableMemcache {
|
||||
log.Info("Memcache Enabled")
|
||||
}
|
||||
|
||||
switch CacheAdapter {
|
||||
case "memory":
|
||||
CacheConfig = fmt.Sprintf(`{"interval":%d}`, Cfg.MustInt("cache", "INTERVAL", 60))
|
||||
case "redis", "memcache":
|
||||
CacheConfig = fmt.Sprintf(`{"conn":"%s"}`, Cfg.MustValue("cache", "HOST"))
|
||||
default:
|
||||
log.Fatal("Unknown cache adapter: %s", CacheAdapter)
|
||||
}
|
||||
|
||||
var err error
|
||||
Cache, err = cache.NewCache(CacheAdapter, CacheConfig)
|
||||
if err != nil {
|
||||
log.Fatal("Init cache system failed, adapter: %s, config: %s, %v\n",
|
||||
CacheAdapter, CacheConfig, err)
|
||||
}
|
||||
|
||||
log.Info("Cache Service Enabled")
|
||||
}
|
||||
|
||||
func newSessionService() {
|
||||
SessionProvider = Cfg.MustValueRange("session", "PROVIDER", "memory",
|
||||
[]string{"memory", "file", "redis", "mysql"})
|
||||
|
||||
SessionConfig = new(session.Config)
|
||||
SessionConfig.ProviderConfig = Cfg.MustValue("session", "PROVIDER_CONFIG")
|
||||
SessionConfig.CookieName = Cfg.MustValue("session", "COOKIE_NAME", "i_like_gogits")
|
||||
SessionConfig.CookieSecure = Cfg.MustBool("session", "COOKIE_SECURE")
|
||||
SessionConfig.EnableSetCookie = Cfg.MustBool("session", "ENABLE_SET_COOKIE", true)
|
||||
SessionConfig.GcIntervalTime = Cfg.MustInt64("session", "GC_INTERVAL_TIME", 86400)
|
||||
SessionConfig.SessionLifeTime = Cfg.MustInt64("session", "SESSION_LIFE_TIME", 86400)
|
||||
SessionConfig.SessionIDHashFunc = Cfg.MustValueRange("session", "SESSION_ID_HASHFUNC",
|
||||
"sha1", []string{"sha1", "sha256", "md5"})
|
||||
SessionConfig.SessionIDHashKey = Cfg.MustValue("session", "SESSION_ID_HASHKEY")
|
||||
|
||||
if SessionProvider == "file" {
|
||||
os.MkdirAll(path.Dir(SessionConfig.ProviderConfig), os.ModePerm)
|
||||
}
|
||||
|
||||
var err error
|
||||
SessionManager, err = session.NewManager(SessionProvider, *SessionConfig)
|
||||
if err != nil {
|
||||
log.Fatal("Init session system failed, provider: %s, %v",
|
||||
SessionProvider, err)
|
||||
}
|
||||
|
||||
log.Info("Session Service Enabled")
|
||||
}
|
||||
|
||||
// Mailer represents mail service.
|
||||
type Mailer struct {
|
||||
Name string
|
||||
Host string
|
||||
From string
|
||||
User, Passwd string
|
||||
}
|
||||
|
||||
type OauthInfo struct {
|
||||
ClientId, ClientSecret string
|
||||
Scopes string
|
||||
AuthUrl, TokenUrl string
|
||||
}
|
||||
|
||||
// Oauther represents oauth service.
|
||||
type Oauther struct {
|
||||
GitHub, Google, Tencent,
|
||||
Twitter, Weibo bool
|
||||
OauthInfos map[string]*OauthInfo
|
||||
}
|
||||
|
||||
var (
|
||||
MailService *Mailer
|
||||
OauthService *Oauther
|
||||
)
|
||||
|
||||
func newMailService() {
|
||||
// Check mailer setting.
|
||||
if !Cfg.MustBool("mailer", "ENABLED") {
|
||||
return
|
||||
}
|
||||
|
||||
MailService = &Mailer{
|
||||
Name: Cfg.MustValue("mailer", "NAME", AppName),
|
||||
Host: Cfg.MustValue("mailer", "HOST"),
|
||||
User: Cfg.MustValue("mailer", "USER"),
|
||||
Passwd: Cfg.MustValue("mailer", "PASSWD"),
|
||||
}
|
||||
MailService.From = Cfg.MustValue("mailer", "FROM", MailService.User)
|
||||
log.Info("Mail Service Enabled")
|
||||
}
|
||||
|
||||
func newRegisterMailService() {
|
||||
if !Cfg.MustBool("service", "REGISTER_EMAIL_CONFIRM") {
|
||||
return
|
||||
} else if MailService == nil {
|
||||
log.Warn("Register Mail Service: Mail Service is not enabled")
|
||||
return
|
||||
}
|
||||
Service.RegisterEmailConfirm = true
|
||||
log.Info("Register Mail Service Enabled")
|
||||
}
|
||||
|
||||
func newNotifyMailService() {
|
||||
if !Cfg.MustBool("service", "ENABLE_NOTIFY_MAIL") {
|
||||
return
|
||||
} else if MailService == nil {
|
||||
log.Warn("Notify Mail Service: Mail Service is not enabled")
|
||||
return
|
||||
}
|
||||
Service.NotifyMail = true
|
||||
log.Info("Notify Mail Service Enabled")
|
||||
}
|
||||
|
||||
func NewServices() {
|
||||
newService()
|
||||
newLogService()
|
||||
newCacheService()
|
||||
newSessionService()
|
||||
newMailService()
|
||||
newRegisterMailService()
|
||||
newNotifyMailService()
|
||||
}
|
||||
15
modules/setting/setting_memcache.go
Normal file
15
modules/setting/setting_memcache.go
Normal file
@@ -0,0 +1,15 @@
|
||||
// +build memcache
|
||||
|
||||
// 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 setting
|
||||
|
||||
import (
|
||||
_ "github.com/gogits/cache/memcache"
|
||||
)
|
||||
|
||||
func init() {
|
||||
EnableMemcache = true
|
||||
}
|
||||
16
modules/setting/setting_redis.go
Normal file
16
modules/setting/setting_redis.go
Normal file
@@ -0,0 +1,16 @@
|
||||
// +build redis
|
||||
|
||||
// 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 setting
|
||||
|
||||
import (
|
||||
_ "github.com/gogits/cache/redis"
|
||||
_ "github.com/gogits/session/redis"
|
||||
)
|
||||
|
||||
func init() {
|
||||
EnableRedis = true
|
||||
}
|
||||
395
modules/social/social.go
Normal file
395
modules/social/social.go
Normal file
@@ -0,0 +1,395 @@
|
||||
// Copyright 2014 Google Inc. All Rights Reserved.
|
||||
// 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 social
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
oauth "github.com/gogits/oauth2"
|
||||
|
||||
"github.com/gogits/gogs/models"
|
||||
"github.com/gogits/gogs/modules/log"
|
||||
"github.com/gogits/gogs/modules/setting"
|
||||
)
|
||||
|
||||
type BasicUserInfo struct {
|
||||
Identity string
|
||||
Name string
|
||||
Email string
|
||||
}
|
||||
|
||||
type SocialConnector interface {
|
||||
Type() int
|
||||
SetRedirectUrl(string)
|
||||
UserInfo(*oauth.Token, *url.URL) (*BasicUserInfo, error)
|
||||
|
||||
AuthCodeURL(string) string
|
||||
Exchange(string) (*oauth.Token, error)
|
||||
}
|
||||
|
||||
var (
|
||||
SocialBaseUrl = "/user/login"
|
||||
SocialMap = make(map[string]SocialConnector)
|
||||
)
|
||||
|
||||
func NewOauthService() {
|
||||
if !setting.Cfg.MustBool("oauth", "ENABLED") {
|
||||
return
|
||||
}
|
||||
|
||||
setting.OauthService = &setting.Oauther{}
|
||||
setting.OauthService.OauthInfos = make(map[string]*setting.OauthInfo)
|
||||
|
||||
socialConfigs := make(map[string]*oauth.Config)
|
||||
allOauthes := []string{"github", "google", "qq", "twitter", "weibo"}
|
||||
// Load all OAuth config data.
|
||||
for _, name := range allOauthes {
|
||||
setting.OauthService.OauthInfos[name] = &setting.OauthInfo{
|
||||
ClientId: setting.Cfg.MustValue("oauth."+name, "CLIENT_ID"),
|
||||
ClientSecret: setting.Cfg.MustValue("oauth."+name, "CLIENT_SECRET"),
|
||||
Scopes: setting.Cfg.MustValue("oauth."+name, "SCOPES"),
|
||||
AuthUrl: setting.Cfg.MustValue("oauth."+name, "AUTH_URL"),
|
||||
TokenUrl: setting.Cfg.MustValue("oauth."+name, "TOKEN_URL"),
|
||||
}
|
||||
socialConfigs[name] = &oauth.Config{
|
||||
ClientId: setting.OauthService.OauthInfos[name].ClientId,
|
||||
ClientSecret: setting.OauthService.OauthInfos[name].ClientSecret,
|
||||
RedirectURL: strings.TrimSuffix(setting.AppUrl, "/") + SocialBaseUrl + name,
|
||||
Scope: setting.OauthService.OauthInfos[name].Scopes,
|
||||
AuthURL: setting.OauthService.OauthInfos[name].AuthUrl,
|
||||
TokenURL: setting.OauthService.OauthInfos[name].TokenUrl,
|
||||
}
|
||||
}
|
||||
enabledOauths := make([]string, 0, 10)
|
||||
|
||||
// GitHub.
|
||||
if setting.Cfg.MustBool("oauth.github", "ENABLED") {
|
||||
setting.OauthService.GitHub = true
|
||||
newGitHubOauth(socialConfigs["github"])
|
||||
enabledOauths = append(enabledOauths, "GitHub")
|
||||
}
|
||||
|
||||
// Google.
|
||||
if setting.Cfg.MustBool("oauth.google", "ENABLED") {
|
||||
setting.OauthService.Google = true
|
||||
newGoogleOauth(socialConfigs["google"])
|
||||
enabledOauths = append(enabledOauths, "Google")
|
||||
}
|
||||
|
||||
// QQ.
|
||||
if setting.Cfg.MustBool("oauth.qq", "ENABLED") {
|
||||
setting.OauthService.Tencent = true
|
||||
newTencentOauth(socialConfigs["qq"])
|
||||
enabledOauths = append(enabledOauths, "QQ")
|
||||
}
|
||||
|
||||
// Twitter.
|
||||
if setting.Cfg.MustBool("oauth.twitter", "ENABLED") {
|
||||
setting.OauthService.Twitter = true
|
||||
newTwitterOauth(socialConfigs["twitter"])
|
||||
enabledOauths = append(enabledOauths, "Twitter")
|
||||
}
|
||||
|
||||
// Weibo.
|
||||
if setting.Cfg.MustBool("oauth.weibo", "ENABLED") {
|
||||
setting.OauthService.Weibo = true
|
||||
newWeiboOauth(socialConfigs["weibo"])
|
||||
enabledOauths = append(enabledOauths, "Weibo")
|
||||
}
|
||||
|
||||
log.Info("Oauth Service Enabled %s", enabledOauths)
|
||||
}
|
||||
|
||||
// ________.__ __ ___ ___ ___.
|
||||
// / _____/|__|/ |_ / | \ __ _\_ |__
|
||||
// / \ ___| \ __\/ ~ \ | \ __ \
|
||||
// \ \_\ \ || | \ Y / | / \_\ \
|
||||
// \______ /__||__| \___|_ /|____/|___ /
|
||||
// \/ \/ \/
|
||||
|
||||
type SocialGithub struct {
|
||||
Token *oauth.Token
|
||||
*oauth.Transport
|
||||
}
|
||||
|
||||
func (s *SocialGithub) Type() int {
|
||||
return models.OT_GITHUB
|
||||
}
|
||||
|
||||
func newGitHubOauth(config *oauth.Config) {
|
||||
SocialMap["github"] = &SocialGithub{
|
||||
Transport: &oauth.Transport{
|
||||
Config: config,
|
||||
Transport: http.DefaultTransport,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (s *SocialGithub) SetRedirectUrl(url string) {
|
||||
s.Transport.Config.RedirectURL = url
|
||||
}
|
||||
|
||||
func (s *SocialGithub) UserInfo(token *oauth.Token, _ *url.URL) (*BasicUserInfo, error) {
|
||||
transport := &oauth.Transport{
|
||||
Token: token,
|
||||
}
|
||||
var data struct {
|
||||
Id int `json:"id"`
|
||||
Name string `json:"login"`
|
||||
Email string `json:"email"`
|
||||
}
|
||||
var err error
|
||||
r, err := transport.Client().Get(s.Transport.Scope)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer r.Body.Close()
|
||||
if err = json.NewDecoder(r.Body).Decode(&data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &BasicUserInfo{
|
||||
Identity: strconv.Itoa(data.Id),
|
||||
Name: data.Name,
|
||||
Email: data.Email,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ________ .__
|
||||
// / _____/ ____ ____ ____ | | ____
|
||||
// / \ ___ / _ \ / _ \ / ___\| | _/ __ \
|
||||
// \ \_\ ( <_> | <_> ) /_/ > |_\ ___/
|
||||
// \______ /\____/ \____/\___ /|____/\___ >
|
||||
// \/ /_____/ \/
|
||||
|
||||
type SocialGoogle struct {
|
||||
Token *oauth.Token
|
||||
*oauth.Transport
|
||||
}
|
||||
|
||||
func (s *SocialGoogle) Type() int {
|
||||
return models.OT_GOOGLE
|
||||
}
|
||||
|
||||
func newGoogleOauth(config *oauth.Config) {
|
||||
SocialMap["google"] = &SocialGoogle{
|
||||
Transport: &oauth.Transport{
|
||||
Config: config,
|
||||
Transport: http.DefaultTransport,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (s *SocialGoogle) SetRedirectUrl(url string) {
|
||||
s.Transport.Config.RedirectURL = url
|
||||
}
|
||||
|
||||
func (s *SocialGoogle) UserInfo(token *oauth.Token, _ *url.URL) (*BasicUserInfo, error) {
|
||||
transport := &oauth.Transport{Token: token}
|
||||
var data struct {
|
||||
Id string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Email string `json:"email"`
|
||||
}
|
||||
var err error
|
||||
|
||||
reqUrl := "https://www.googleapis.com/oauth2/v1/userinfo"
|
||||
r, err := transport.Client().Get(reqUrl)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer r.Body.Close()
|
||||
if err = json.NewDecoder(r.Body).Decode(&data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &BasicUserInfo{
|
||||
Identity: data.Id,
|
||||
Name: data.Name,
|
||||
Email: data.Email,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ________ ________
|
||||
// \_____ \ \_____ \
|
||||
// / / \ \ / / \ \
|
||||
// / \_/. \/ \_/. \
|
||||
// \_____\ \_/\_____\ \_/
|
||||
// \__> \__>
|
||||
|
||||
type SocialTencent struct {
|
||||
Token *oauth.Token
|
||||
*oauth.Transport
|
||||
reqUrl string
|
||||
}
|
||||
|
||||
func (s *SocialTencent) Type() int {
|
||||
return models.OT_QQ
|
||||
}
|
||||
|
||||
func newTencentOauth(config *oauth.Config) {
|
||||
SocialMap["qq"] = &SocialTencent{
|
||||
reqUrl: "https://open.t.qq.com/api/user/info",
|
||||
Transport: &oauth.Transport{
|
||||
Config: config,
|
||||
Transport: http.DefaultTransport,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (s *SocialTencent) SetRedirectUrl(url string) {
|
||||
s.Transport.Config.RedirectURL = url
|
||||
}
|
||||
|
||||
func (s *SocialTencent) UserInfo(token *oauth.Token, URL *url.URL) (*BasicUserInfo, error) {
|
||||
var data struct {
|
||||
Data struct {
|
||||
Id string `json:"openid"`
|
||||
Name string `json:"name"`
|
||||
Email string `json:"email"`
|
||||
} `json:"data"`
|
||||
}
|
||||
var err error
|
||||
// https://open.t.qq.com/api/user/info?
|
||||
//oauth_consumer_key=APP_KEY&
|
||||
//access_token=ACCESSTOKEN&openid=openid
|
||||
//clientip=CLIENTIP&oauth_version=2.a
|
||||
//scope=all
|
||||
var urls = url.Values{
|
||||
"oauth_consumer_key": {s.Transport.Config.ClientId},
|
||||
"access_token": {token.AccessToken},
|
||||
"openid": URL.Query()["openid"],
|
||||
"oauth_version": {"2.a"},
|
||||
"scope": {"all"},
|
||||
}
|
||||
r, err := http.Get(s.reqUrl + "?" + urls.Encode())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer r.Body.Close()
|
||||
if err = json.NewDecoder(r.Body).Decode(&data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &BasicUserInfo{
|
||||
Identity: data.Data.Id,
|
||||
Name: data.Data.Name,
|
||||
Email: data.Data.Email,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ___________ .__ __ __
|
||||
// \__ ___/_ _ _|__|/ |__/ |_ ___________
|
||||
// | | \ \/ \/ / \ __\ __\/ __ \_ __ \
|
||||
// | | \ /| || | | | \ ___/| | \/
|
||||
// |____| \/\_/ |__||__| |__| \___ >__|
|
||||
// \/
|
||||
|
||||
type SocialTwitter struct {
|
||||
Token *oauth.Token
|
||||
*oauth.Transport
|
||||
}
|
||||
|
||||
func (s *SocialTwitter) Type() int {
|
||||
return models.OT_TWITTER
|
||||
}
|
||||
|
||||
func newTwitterOauth(config *oauth.Config) {
|
||||
SocialMap["twitter"] = &SocialTwitter{
|
||||
Transport: &oauth.Transport{
|
||||
Config: config,
|
||||
Transport: http.DefaultTransport,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (s *SocialTwitter) SetRedirectUrl(url string) {
|
||||
s.Transport.Config.RedirectURL = url
|
||||
}
|
||||
|
||||
//https://github.com/mrjones/oauth
|
||||
func (s *SocialTwitter) UserInfo(token *oauth.Token, _ *url.URL) (*BasicUserInfo, error) {
|
||||
// transport := &oauth.Transport{Token: token}
|
||||
// var data struct {
|
||||
// Id string `json:"id"`
|
||||
// Name string `json:"name"`
|
||||
// Email string `json:"email"`
|
||||
// }
|
||||
// var err error
|
||||
|
||||
// reqUrl := "https://www.googleapis.com/oauth2/v1/userinfo"
|
||||
// r, err := transport.Client().Get(reqUrl)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
// defer r.Body.Close()
|
||||
// if err = json.NewDecoder(r.Body).Decode(&data); err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
// return &BasicUserInfo{
|
||||
// Identity: data.Id,
|
||||
// Name: data.Name,
|
||||
// Email: data.Email,
|
||||
// }, nil
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// __ __ ._____.
|
||||
// / \ / \ ____ |__\_ |__ ____
|
||||
// \ \/\/ // __ \| || __ \ / _ \
|
||||
// \ /\ ___/| || \_\ ( <_> )
|
||||
// \__/\ / \___ >__||___ /\____/
|
||||
// \/ \/ \/
|
||||
|
||||
type SocialWeibo struct {
|
||||
Token *oauth.Token
|
||||
*oauth.Transport
|
||||
}
|
||||
|
||||
func (s *SocialWeibo) Type() int {
|
||||
return models.OT_WEIBO
|
||||
}
|
||||
|
||||
func newWeiboOauth(config *oauth.Config) {
|
||||
SocialMap["weibo"] = &SocialWeibo{
|
||||
Transport: &oauth.Transport{
|
||||
Config: config,
|
||||
Transport: http.DefaultTransport,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (s *SocialWeibo) SetRedirectUrl(url string) {
|
||||
s.Transport.Config.RedirectURL = url
|
||||
}
|
||||
|
||||
func (s *SocialWeibo) UserInfo(token *oauth.Token, _ *url.URL) (*BasicUserInfo, error) {
|
||||
transport := &oauth.Transport{Token: token}
|
||||
var data struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
var err error
|
||||
|
||||
var urls = url.Values{
|
||||
"access_token": {token.AccessToken},
|
||||
"uid": {token.Extra["id_token"]},
|
||||
}
|
||||
reqUrl := "https://api.weibo.com/2/users/show.json"
|
||||
r, err := transport.Client().Get(reqUrl + "?" + urls.Encode())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer r.Body.Close()
|
||||
if err = json.NewDecoder(r.Body).Decode(&data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &BasicUserInfo{
|
||||
Identity: token.Extra["id_token"],
|
||||
Name: data.Name,
|
||||
}, nil
|
||||
return nil, nil
|
||||
}
|
||||
10
modules/workers/worker.go
Normal file
10
modules/workers/worker.go
Normal file
@@ -0,0 +1,10 @@
|
||||
// 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 workers
|
||||
|
||||
// Work represents a background work interface of any kind.
|
||||
type Work interface {
|
||||
Do() error
|
||||
}
|
||||
9
public/css/bootstrap-colorpicker.min.css
vendored
Normal file
9
public/css/bootstrap-colorpicker.min.css
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
/*!
|
||||
* Bootstrap Colorpicker
|
||||
* http://mjolnic.github.io/bootstrap-colorpicker/
|
||||
*
|
||||
* Originally written by (c) 2012 Stefan Petre
|
||||
* Licensed under the Apache License v2.0
|
||||
* http://www.apache.org/licenses/LICENSE-2.0.txt
|
||||
*
|
||||
*/.colorpicker-saturation{float:left;width:100px;height:100px;cursor:crosshair;background-image:url("../img/bootstrap-colorpicker/saturation.png")}.colorpicker-saturation i{position:absolute;top:0;left:0;display:block;width:5px;height:5px;margin:-4px 0 0 -4px;border:1px solid #000;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px}.colorpicker-saturation i b{display:block;width:5px;height:5px;border:1px solid #fff;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px}.colorpicker-hue,.colorpicker-alpha{float:left;width:15px;height:100px;margin-bottom:4px;margin-left:4px;cursor:row-resize}.colorpicker-hue i,.colorpicker-alpha i{position:absolute;top:0;left:0;display:block;width:100%;height:1px;margin-top:-1px;background:#000;border-top:1px solid #fff}.colorpicker-hue{background-image:url("../img/bootstrap-colorpicker/hue.png")}.colorpicker-alpha{display:none;background-image:url("../img/bootstrap-colorpicker/alpha.png")}.colorpicker{top:0;left:0;z-index:2500;min-width:130px;padding:4px;margin-top:1px;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;*zoom:1}.colorpicker:before,.colorpicker:after{display:table;line-height:0;content:""}.colorpicker:after{clear:both}.colorpicker:before{position:absolute;top:-7px;left:6px;display:inline-block;border-right:7px solid transparent;border-bottom:7px solid #ccc;border-left:7px solid transparent;border-bottom-color:rgba(0,0,0,0.2);content:''}.colorpicker:after{position:absolute;top:-6px;left:7px;display:inline-block;border-right:6px solid transparent;border-bottom:6px solid #fff;border-left:6px solid transparent;content:''}.colorpicker div{position:relative}.colorpicker.colorpicker-with-alpha{min-width:140px}.colorpicker.colorpicker-with-alpha .colorpicker-alpha{display:block}.colorpicker-color{height:10px;margin-top:5px;clear:both;background-image:url("../img/bootstrap-colorpicker/alpha.png");background-position:0 100%}.colorpicker-color div{height:10px}.colorpicker-element .input-group-addon i,.colorpicker-element .add-on i{display:inline-block;width:16px;height:16px;vertical-align:text-top;cursor:pointer}.colorpicker.colorpicker-inline{position:relative;z-index:auto;display:inline-block;float:none}.colorpicker.colorpicker-horizontal{width:110px;height:auto;min-width:110px}.colorpicker.colorpicker-horizontal .colorpicker-saturation{margin-bottom:4px}.colorpicker.colorpicker-horizontal .colorpicker-color{width:100px}.colorpicker.colorpicker-horizontal .colorpicker-hue,.colorpicker.colorpicker-horizontal .colorpicker-alpha{float:left;width:100px;height:15px;margin-bottom:4px;margin-left:0;cursor:col-resize}.colorpicker.colorpicker-horizontal .colorpicker-hue i,.colorpicker.colorpicker-horizontal .colorpicker-alpha i{position:absolute;top:0;left:0;display:block;width:1px;height:15px;margin-top:0;background:#fff;border:0}.colorpicker.colorpicker-horizontal .colorpicker-hue{background-image:url("../img/bootstrap-colorpicker/hue-horizontal.png")}.colorpicker.colorpicker-horizontal .colorpicker-alpha{background-image:url("../img/bootstrap-colorpicker/alpha-horizontal.png")}.colorpicker.colorpicker-hidden{display:none}.colorpicker.colorpicker-visible{display:block}.colorpicker-inline.colorpicker-visible{display:inline-block}
|
||||
File diff suppressed because one or more lines are too long
4
public/css/bootstrap.min.css
vendored
4
public/css/bootstrap.min.css
vendored
File diff suppressed because one or more lines are too long
790
public/css/datepicker3.css
Normal file
790
public/css/datepicker3.css
Normal file
@@ -0,0 +1,790 @@
|
||||
/*!
|
||||
* Datepicker for Bootstrap
|
||||
*
|
||||
* Copyright 2012 Stefan Petre
|
||||
* Improvements by Andrew Rowls
|
||||
* Licensed under the Apache License v2.0
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*/
|
||||
.datepicker {
|
||||
padding: 4px;
|
||||
border-radius: 4px;
|
||||
direction: ltr;
|
||||
/*.dow {
|
||||
border-top: 1px solid #ddd !important;
|
||||
}*/
|
||||
}
|
||||
.datepicker-inline {
|
||||
width: 220px;
|
||||
}
|
||||
.datepicker.datepicker-rtl {
|
||||
direction: rtl;
|
||||
}
|
||||
.datepicker.datepicker-rtl table tr td span {
|
||||
float: right;
|
||||
}
|
||||
.datepicker-dropdown {
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
.datepicker-dropdown:before {
|
||||
content: '';
|
||||
display: inline-block;
|
||||
border-left: 7px solid transparent;
|
||||
border-right: 7px solid transparent;
|
||||
border-bottom: 7px solid #ccc;
|
||||
border-top: 0;
|
||||
border-bottom-color: rgba(0, 0, 0, 0.2);
|
||||
position: absolute;
|
||||
}
|
||||
.datepicker-dropdown:after {
|
||||
content: '';
|
||||
display: inline-block;
|
||||
border-left: 6px solid transparent;
|
||||
border-right: 6px solid transparent;
|
||||
border-bottom: 6px solid #fff;
|
||||
border-top: 0;
|
||||
position: absolute;
|
||||
}
|
||||
.datepicker-dropdown.datepicker-orient-left:before {
|
||||
left: 6px;
|
||||
}
|
||||
.datepicker-dropdown.datepicker-orient-left:after {
|
||||
left: 7px;
|
||||
}
|
||||
.datepicker-dropdown.datepicker-orient-right:before {
|
||||
right: 6px;
|
||||
}
|
||||
.datepicker-dropdown.datepicker-orient-right:after {
|
||||
right: 7px;
|
||||
}
|
||||
.datepicker-dropdown.datepicker-orient-top:before {
|
||||
top: -7px;
|
||||
}
|
||||
.datepicker-dropdown.datepicker-orient-top:after {
|
||||
top: -6px;
|
||||
}
|
||||
.datepicker-dropdown.datepicker-orient-bottom:before {
|
||||
bottom: -7px;
|
||||
border-bottom: 0;
|
||||
border-top: 7px solid #999;
|
||||
}
|
||||
.datepicker-dropdown.datepicker-orient-bottom:after {
|
||||
bottom: -6px;
|
||||
border-bottom: 0;
|
||||
border-top: 6px solid #fff;
|
||||
}
|
||||
.datepicker > div {
|
||||
display: none;
|
||||
}
|
||||
.datepicker.days div.datepicker-days {
|
||||
display: block;
|
||||
}
|
||||
.datepicker.months div.datepicker-months {
|
||||
display: block;
|
||||
}
|
||||
.datepicker.years div.datepicker-years {
|
||||
display: block;
|
||||
}
|
||||
.datepicker table {
|
||||
margin: 0;
|
||||
-webkit-touch-callout: none;
|
||||
-webkit-user-select: none;
|
||||
-khtml-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
.datepicker table tr td,
|
||||
.datepicker table tr th {
|
||||
text-align: center;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
border-radius: 4px;
|
||||
border: none;
|
||||
}
|
||||
.table-striped .datepicker table tr td,
|
||||
.table-striped .datepicker table tr th {
|
||||
background-color: transparent;
|
||||
}
|
||||
.datepicker table tr td.day:hover,
|
||||
.datepicker table tr td.day.focused {
|
||||
background: #eeeeee;
|
||||
cursor: pointer;
|
||||
}
|
||||
.datepicker table tr td.old,
|
||||
.datepicker table tr td.new {
|
||||
color: #999999;
|
||||
}
|
||||
.datepicker table tr td.disabled,
|
||||
.datepicker table tr td.disabled:hover {
|
||||
background: none;
|
||||
color: #999999;
|
||||
cursor: default;
|
||||
}
|
||||
.datepicker table tr td.today,
|
||||
.datepicker table tr td.today:hover,
|
||||
.datepicker table tr td.today.disabled,
|
||||
.datepicker table tr td.today.disabled:hover {
|
||||
color: #000000;
|
||||
background-color: #ffdb99;
|
||||
border-color: #ffb733;
|
||||
}
|
||||
.datepicker table tr td.today:hover,
|
||||
.datepicker table tr td.today:hover:hover,
|
||||
.datepicker table tr td.today.disabled:hover,
|
||||
.datepicker table tr td.today.disabled:hover:hover,
|
||||
.datepicker table tr td.today:focus,
|
||||
.datepicker table tr td.today:hover:focus,
|
||||
.datepicker table tr td.today.disabled:focus,
|
||||
.datepicker table tr td.today.disabled:hover:focus,
|
||||
.datepicker table tr td.today:active,
|
||||
.datepicker table tr td.today:hover:active,
|
||||
.datepicker table tr td.today.disabled:active,
|
||||
.datepicker table tr td.today.disabled:hover:active,
|
||||
.datepicker table tr td.today.active,
|
||||
.datepicker table tr td.today:hover.active,
|
||||
.datepicker table tr td.today.disabled.active,
|
||||
.datepicker table tr td.today.disabled:hover.active,
|
||||
.open .dropdown-toggle.datepicker table tr td.today,
|
||||
.open .dropdown-toggle.datepicker table tr td.today:hover,
|
||||
.open .dropdown-toggle.datepicker table tr td.today.disabled,
|
||||
.open .dropdown-toggle.datepicker table tr td.today.disabled:hover {
|
||||
color: #000000;
|
||||
background-color: #ffcd70;
|
||||
border-color: #f59e00;
|
||||
}
|
||||
.datepicker table tr td.today:active,
|
||||
.datepicker table tr td.today:hover:active,
|
||||
.datepicker table tr td.today.disabled:active,
|
||||
.datepicker table tr td.today.disabled:hover:active,
|
||||
.datepicker table tr td.today.active,
|
||||
.datepicker table tr td.today:hover.active,
|
||||
.datepicker table tr td.today.disabled.active,
|
||||
.datepicker table tr td.today.disabled:hover.active,
|
||||
.open .dropdown-toggle.datepicker table tr td.today,
|
||||
.open .dropdown-toggle.datepicker table tr td.today:hover,
|
||||
.open .dropdown-toggle.datepicker table tr td.today.disabled,
|
||||
.open .dropdown-toggle.datepicker table tr td.today.disabled:hover {
|
||||
background-image: none;
|
||||
}
|
||||
.datepicker table tr td.today.disabled,
|
||||
.datepicker table tr td.today:hover.disabled,
|
||||
.datepicker table tr td.today.disabled.disabled,
|
||||
.datepicker table tr td.today.disabled:hover.disabled,
|
||||
.datepicker table tr td.today[disabled],
|
||||
.datepicker table tr td.today:hover[disabled],
|
||||
.datepicker table tr td.today.disabled[disabled],
|
||||
.datepicker table tr td.today.disabled:hover[disabled],
|
||||
fieldset[disabled] .datepicker table tr td.today,
|
||||
fieldset[disabled] .datepicker table tr td.today:hover,
|
||||
fieldset[disabled] .datepicker table tr td.today.disabled,
|
||||
fieldset[disabled] .datepicker table tr td.today.disabled:hover,
|
||||
.datepicker table tr td.today.disabled:hover,
|
||||
.datepicker table tr td.today:hover.disabled:hover,
|
||||
.datepicker table tr td.today.disabled.disabled:hover,
|
||||
.datepicker table tr td.today.disabled:hover.disabled:hover,
|
||||
.datepicker table tr td.today[disabled]:hover,
|
||||
.datepicker table tr td.today:hover[disabled]:hover,
|
||||
.datepicker table tr td.today.disabled[disabled]:hover,
|
||||
.datepicker table tr td.today.disabled:hover[disabled]:hover,
|
||||
fieldset[disabled] .datepicker table tr td.today:hover,
|
||||
fieldset[disabled] .datepicker table tr td.today:hover:hover,
|
||||
fieldset[disabled] .datepicker table tr td.today.disabled:hover,
|
||||
fieldset[disabled] .datepicker table tr td.today.disabled:hover:hover,
|
||||
.datepicker table tr td.today.disabled:focus,
|
||||
.datepicker table tr td.today:hover.disabled:focus,
|
||||
.datepicker table tr td.today.disabled.disabled:focus,
|
||||
.datepicker table tr td.today.disabled:hover.disabled:focus,
|
||||
.datepicker table tr td.today[disabled]:focus,
|
||||
.datepicker table tr td.today:hover[disabled]:focus,
|
||||
.datepicker table tr td.today.disabled[disabled]:focus,
|
||||
.datepicker table tr td.today.disabled:hover[disabled]:focus,
|
||||
fieldset[disabled] .datepicker table tr td.today:focus,
|
||||
fieldset[disabled] .datepicker table tr td.today:hover:focus,
|
||||
fieldset[disabled] .datepicker table tr td.today.disabled:focus,
|
||||
fieldset[disabled] .datepicker table tr td.today.disabled:hover:focus,
|
||||
.datepicker table tr td.today.disabled:active,
|
||||
.datepicker table tr td.today:hover.disabled:active,
|
||||
.datepicker table tr td.today.disabled.disabled:active,
|
||||
.datepicker table tr td.today.disabled:hover.disabled:active,
|
||||
.datepicker table tr td.today[disabled]:active,
|
||||
.datepicker table tr td.today:hover[disabled]:active,
|
||||
.datepicker table tr td.today.disabled[disabled]:active,
|
||||
.datepicker table tr td.today.disabled:hover[disabled]:active,
|
||||
fieldset[disabled] .datepicker table tr td.today:active,
|
||||
fieldset[disabled] .datepicker table tr td.today:hover:active,
|
||||
fieldset[disabled] .datepicker table tr td.today.disabled:active,
|
||||
fieldset[disabled] .datepicker table tr td.today.disabled:hover:active,
|
||||
.datepicker table tr td.today.disabled.active,
|
||||
.datepicker table tr td.today:hover.disabled.active,
|
||||
.datepicker table tr td.today.disabled.disabled.active,
|
||||
.datepicker table tr td.today.disabled:hover.disabled.active,
|
||||
.datepicker table tr td.today[disabled].active,
|
||||
.datepicker table tr td.today:hover[disabled].active,
|
||||
.datepicker table tr td.today.disabled[disabled].active,
|
||||
.datepicker table tr td.today.disabled:hover[disabled].active,
|
||||
fieldset[disabled] .datepicker table tr td.today.active,
|
||||
fieldset[disabled] .datepicker table tr td.today:hover.active,
|
||||
fieldset[disabled] .datepicker table tr td.today.disabled.active,
|
||||
fieldset[disabled] .datepicker table tr td.today.disabled:hover.active {
|
||||
background-color: #ffdb99;
|
||||
border-color: #ffb733;
|
||||
}
|
||||
.datepicker table tr td.today:hover:hover {
|
||||
color: #000;
|
||||
}
|
||||
.datepicker table tr td.today.active:hover {
|
||||
color: #fff;
|
||||
}
|
||||
.datepicker table tr td.range,
|
||||
.datepicker table tr td.range:hover,
|
||||
.datepicker table tr td.range.disabled,
|
||||
.datepicker table tr td.range.disabled:hover {
|
||||
background: #eeeeee;
|
||||
border-radius: 0;
|
||||
}
|
||||
.datepicker table tr td.range.today,
|
||||
.datepicker table tr td.range.today:hover,
|
||||
.datepicker table tr td.range.today.disabled,
|
||||
.datepicker table tr td.range.today.disabled:hover {
|
||||
color: #000000;
|
||||
background-color: #f7ca77;
|
||||
border-color: #f1a417;
|
||||
border-radius: 0;
|
||||
}
|
||||
.datepicker table tr td.range.today:hover,
|
||||
.datepicker table tr td.range.today:hover:hover,
|
||||
.datepicker table tr td.range.today.disabled:hover,
|
||||
.datepicker table tr td.range.today.disabled:hover:hover,
|
||||
.datepicker table tr td.range.today:focus,
|
||||
.datepicker table tr td.range.today:hover:focus,
|
||||
.datepicker table tr td.range.today.disabled:focus,
|
||||
.datepicker table tr td.range.today.disabled:hover:focus,
|
||||
.datepicker table tr td.range.today:active,
|
||||
.datepicker table tr td.range.today:hover:active,
|
||||
.datepicker table tr td.range.today.disabled:active,
|
||||
.datepicker table tr td.range.today.disabled:hover:active,
|
||||
.datepicker table tr td.range.today.active,
|
||||
.datepicker table tr td.range.today:hover.active,
|
||||
.datepicker table tr td.range.today.disabled.active,
|
||||
.datepicker table tr td.range.today.disabled:hover.active,
|
||||
.open .dropdown-toggle.datepicker table tr td.range.today,
|
||||
.open .dropdown-toggle.datepicker table tr td.range.today:hover,
|
||||
.open .dropdown-toggle.datepicker table tr td.range.today.disabled,
|
||||
.open .dropdown-toggle.datepicker table tr td.range.today.disabled:hover {
|
||||
color: #000000;
|
||||
background-color: #f4bb51;
|
||||
border-color: #bf800c;
|
||||
}
|
||||
.datepicker table tr td.range.today:active,
|
||||
.datepicker table tr td.range.today:hover:active,
|
||||
.datepicker table tr td.range.today.disabled:active,
|
||||
.datepicker table tr td.range.today.disabled:hover:active,
|
||||
.datepicker table tr td.range.today.active,
|
||||
.datepicker table tr td.range.today:hover.active,
|
||||
.datepicker table tr td.range.today.disabled.active,
|
||||
.datepicker table tr td.range.today.disabled:hover.active,
|
||||
.open .dropdown-toggle.datepicker table tr td.range.today,
|
||||
.open .dropdown-toggle.datepicker table tr td.range.today:hover,
|
||||
.open .dropdown-toggle.datepicker table tr td.range.today.disabled,
|
||||
.open .dropdown-toggle.datepicker table tr td.range.today.disabled:hover {
|
||||
background-image: none;
|
||||
}
|
||||
.datepicker table tr td.range.today.disabled,
|
||||
.datepicker table tr td.range.today:hover.disabled,
|
||||
.datepicker table tr td.range.today.disabled.disabled,
|
||||
.datepicker table tr td.range.today.disabled:hover.disabled,
|
||||
.datepicker table tr td.range.today[disabled],
|
||||
.datepicker table tr td.range.today:hover[disabled],
|
||||
.datepicker table tr td.range.today.disabled[disabled],
|
||||
.datepicker table tr td.range.today.disabled:hover[disabled],
|
||||
fieldset[disabled] .datepicker table tr td.range.today,
|
||||
fieldset[disabled] .datepicker table tr td.range.today:hover,
|
||||
fieldset[disabled] .datepicker table tr td.range.today.disabled,
|
||||
fieldset[disabled] .datepicker table tr td.range.today.disabled:hover,
|
||||
.datepicker table tr td.range.today.disabled:hover,
|
||||
.datepicker table tr td.range.today:hover.disabled:hover,
|
||||
.datepicker table tr td.range.today.disabled.disabled:hover,
|
||||
.datepicker table tr td.range.today.disabled:hover.disabled:hover,
|
||||
.datepicker table tr td.range.today[disabled]:hover,
|
||||
.datepicker table tr td.range.today:hover[disabled]:hover,
|
||||
.datepicker table tr td.range.today.disabled[disabled]:hover,
|
||||
.datepicker table tr td.range.today.disabled:hover[disabled]:hover,
|
||||
fieldset[disabled] .datepicker table tr td.range.today:hover,
|
||||
fieldset[disabled] .datepicker table tr td.range.today:hover:hover,
|
||||
fieldset[disabled] .datepicker table tr td.range.today.disabled:hover,
|
||||
fieldset[disabled] .datepicker table tr td.range.today.disabled:hover:hover,
|
||||
.datepicker table tr td.range.today.disabled:focus,
|
||||
.datepicker table tr td.range.today:hover.disabled:focus,
|
||||
.datepicker table tr td.range.today.disabled.disabled:focus,
|
||||
.datepicker table tr td.range.today.disabled:hover.disabled:focus,
|
||||
.datepicker table tr td.range.today[disabled]:focus,
|
||||
.datepicker table tr td.range.today:hover[disabled]:focus,
|
||||
.datepicker table tr td.range.today.disabled[disabled]:focus,
|
||||
.datepicker table tr td.range.today.disabled:hover[disabled]:focus,
|
||||
fieldset[disabled] .datepicker table tr td.range.today:focus,
|
||||
fieldset[disabled] .datepicker table tr td.range.today:hover:focus,
|
||||
fieldset[disabled] .datepicker table tr td.range.today.disabled:focus,
|
||||
fieldset[disabled] .datepicker table tr td.range.today.disabled:hover:focus,
|
||||
.datepicker table tr td.range.today.disabled:active,
|
||||
.datepicker table tr td.range.today:hover.disabled:active,
|
||||
.datepicker table tr td.range.today.disabled.disabled:active,
|
||||
.datepicker table tr td.range.today.disabled:hover.disabled:active,
|
||||
.datepicker table tr td.range.today[disabled]:active,
|
||||
.datepicker table tr td.range.today:hover[disabled]:active,
|
||||
.datepicker table tr td.range.today.disabled[disabled]:active,
|
||||
.datepicker table tr td.range.today.disabled:hover[disabled]:active,
|
||||
fieldset[disabled] .datepicker table tr td.range.today:active,
|
||||
fieldset[disabled] .datepicker table tr td.range.today:hover:active,
|
||||
fieldset[disabled] .datepicker table tr td.range.today.disabled:active,
|
||||
fieldset[disabled] .datepicker table tr td.range.today.disabled:hover:active,
|
||||
.datepicker table tr td.range.today.disabled.active,
|
||||
.datepicker table tr td.range.today:hover.disabled.active,
|
||||
.datepicker table tr td.range.today.disabled.disabled.active,
|
||||
.datepicker table tr td.range.today.disabled:hover.disabled.active,
|
||||
.datepicker table tr td.range.today[disabled].active,
|
||||
.datepicker table tr td.range.today:hover[disabled].active,
|
||||
.datepicker table tr td.range.today.disabled[disabled].active,
|
||||
.datepicker table tr td.range.today.disabled:hover[disabled].active,
|
||||
fieldset[disabled] .datepicker table tr td.range.today.active,
|
||||
fieldset[disabled] .datepicker table tr td.range.today:hover.active,
|
||||
fieldset[disabled] .datepicker table tr td.range.today.disabled.active,
|
||||
fieldset[disabled] .datepicker table tr td.range.today.disabled:hover.active {
|
||||
background-color: #f7ca77;
|
||||
border-color: #f1a417;
|
||||
}
|
||||
.datepicker table tr td.selected,
|
||||
.datepicker table tr td.selected:hover,
|
||||
.datepicker table tr td.selected.disabled,
|
||||
.datepicker table tr td.selected.disabled:hover {
|
||||
color: #ffffff;
|
||||
background-color: #999999;
|
||||
border-color: #555555;
|
||||
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
|
||||
}
|
||||
.datepicker table tr td.selected:hover,
|
||||
.datepicker table tr td.selected:hover:hover,
|
||||
.datepicker table tr td.selected.disabled:hover,
|
||||
.datepicker table tr td.selected.disabled:hover:hover,
|
||||
.datepicker table tr td.selected:focus,
|
||||
.datepicker table tr td.selected:hover:focus,
|
||||
.datepicker table tr td.selected.disabled:focus,
|
||||
.datepicker table tr td.selected.disabled:hover:focus,
|
||||
.datepicker table tr td.selected:active,
|
||||
.datepicker table tr td.selected:hover:active,
|
||||
.datepicker table tr td.selected.disabled:active,
|
||||
.datepicker table tr td.selected.disabled:hover:active,
|
||||
.datepicker table tr td.selected.active,
|
||||
.datepicker table tr td.selected:hover.active,
|
||||
.datepicker table tr td.selected.disabled.active,
|
||||
.datepicker table tr td.selected.disabled:hover.active,
|
||||
.open .dropdown-toggle.datepicker table tr td.selected,
|
||||
.open .dropdown-toggle.datepicker table tr td.selected:hover,
|
||||
.open .dropdown-toggle.datepicker table tr td.selected.disabled,
|
||||
.open .dropdown-toggle.datepicker table tr td.selected.disabled:hover {
|
||||
color: #ffffff;
|
||||
background-color: #858585;
|
||||
border-color: #373737;
|
||||
}
|
||||
.datepicker table tr td.selected:active,
|
||||
.datepicker table tr td.selected:hover:active,
|
||||
.datepicker table tr td.selected.disabled:active,
|
||||
.datepicker table tr td.selected.disabled:hover:active,
|
||||
.datepicker table tr td.selected.active,
|
||||
.datepicker table tr td.selected:hover.active,
|
||||
.datepicker table tr td.selected.disabled.active,
|
||||
.datepicker table tr td.selected.disabled:hover.active,
|
||||
.open .dropdown-toggle.datepicker table tr td.selected,
|
||||
.open .dropdown-toggle.datepicker table tr td.selected:hover,
|
||||
.open .dropdown-toggle.datepicker table tr td.selected.disabled,
|
||||
.open .dropdown-toggle.datepicker table tr td.selected.disabled:hover {
|
||||
background-image: none;
|
||||
}
|
||||
.datepicker table tr td.selected.disabled,
|
||||
.datepicker table tr td.selected:hover.disabled,
|
||||
.datepicker table tr td.selected.disabled.disabled,
|
||||
.datepicker table tr td.selected.disabled:hover.disabled,
|
||||
.datepicker table tr td.selected[disabled],
|
||||
.datepicker table tr td.selected:hover[disabled],
|
||||
.datepicker table tr td.selected.disabled[disabled],
|
||||
.datepicker table tr td.selected.disabled:hover[disabled],
|
||||
fieldset[disabled] .datepicker table tr td.selected,
|
||||
fieldset[disabled] .datepicker table tr td.selected:hover,
|
||||
fieldset[disabled] .datepicker table tr td.selected.disabled,
|
||||
fieldset[disabled] .datepicker table tr td.selected.disabled:hover,
|
||||
.datepicker table tr td.selected.disabled:hover,
|
||||
.datepicker table tr td.selected:hover.disabled:hover,
|
||||
.datepicker table tr td.selected.disabled.disabled:hover,
|
||||
.datepicker table tr td.selected.disabled:hover.disabled:hover,
|
||||
.datepicker table tr td.selected[disabled]:hover,
|
||||
.datepicker table tr td.selected:hover[disabled]:hover,
|
||||
.datepicker table tr td.selected.disabled[disabled]:hover,
|
||||
.datepicker table tr td.selected.disabled:hover[disabled]:hover,
|
||||
fieldset[disabled] .datepicker table tr td.selected:hover,
|
||||
fieldset[disabled] .datepicker table tr td.selected:hover:hover,
|
||||
fieldset[disabled] .datepicker table tr td.selected.disabled:hover,
|
||||
fieldset[disabled] .datepicker table tr td.selected.disabled:hover:hover,
|
||||
.datepicker table tr td.selected.disabled:focus,
|
||||
.datepicker table tr td.selected:hover.disabled:focus,
|
||||
.datepicker table tr td.selected.disabled.disabled:focus,
|
||||
.datepicker table tr td.selected.disabled:hover.disabled:focus,
|
||||
.datepicker table tr td.selected[disabled]:focus,
|
||||
.datepicker table tr td.selected:hover[disabled]:focus,
|
||||
.datepicker table tr td.selected.disabled[disabled]:focus,
|
||||
.datepicker table tr td.selected.disabled:hover[disabled]:focus,
|
||||
fieldset[disabled] .datepicker table tr td.selected:focus,
|
||||
fieldset[disabled] .datepicker table tr td.selected:hover:focus,
|
||||
fieldset[disabled] .datepicker table tr td.selected.disabled:focus,
|
||||
fieldset[disabled] .datepicker table tr td.selected.disabled:hover:focus,
|
||||
.datepicker table tr td.selected.disabled:active,
|
||||
.datepicker table tr td.selected:hover.disabled:active,
|
||||
.datepicker table tr td.selected.disabled.disabled:active,
|
||||
.datepicker table tr td.selected.disabled:hover.disabled:active,
|
||||
.datepicker table tr td.selected[disabled]:active,
|
||||
.datepicker table tr td.selected:hover[disabled]:active,
|
||||
.datepicker table tr td.selected.disabled[disabled]:active,
|
||||
.datepicker table tr td.selected.disabled:hover[disabled]:active,
|
||||
fieldset[disabled] .datepicker table tr td.selected:active,
|
||||
fieldset[disabled] .datepicker table tr td.selected:hover:active,
|
||||
fieldset[disabled] .datepicker table tr td.selected.disabled:active,
|
||||
fieldset[disabled] .datepicker table tr td.selected.disabled:hover:active,
|
||||
.datepicker table tr td.selected.disabled.active,
|
||||
.datepicker table tr td.selected:hover.disabled.active,
|
||||
.datepicker table tr td.selected.disabled.disabled.active,
|
||||
.datepicker table tr td.selected.disabled:hover.disabled.active,
|
||||
.datepicker table tr td.selected[disabled].active,
|
||||
.datepicker table tr td.selected:hover[disabled].active,
|
||||
.datepicker table tr td.selected.disabled[disabled].active,
|
||||
.datepicker table tr td.selected.disabled:hover[disabled].active,
|
||||
fieldset[disabled] .datepicker table tr td.selected.active,
|
||||
fieldset[disabled] .datepicker table tr td.selected:hover.active,
|
||||
fieldset[disabled] .datepicker table tr td.selected.disabled.active,
|
||||
fieldset[disabled] .datepicker table tr td.selected.disabled:hover.active {
|
||||
background-color: #999999;
|
||||
border-color: #555555;
|
||||
}
|
||||
.datepicker table tr td.active,
|
||||
.datepicker table tr td.active:hover,
|
||||
.datepicker table tr td.active.disabled,
|
||||
.datepicker table tr td.active.disabled:hover {
|
||||
color: #ffffff;
|
||||
background-color: #428bca;
|
||||
border-color: #357ebd;
|
||||
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
|
||||
}
|
||||
.datepicker table tr td.active:hover,
|
||||
.datepicker table tr td.active:hover:hover,
|
||||
.datepicker table tr td.active.disabled:hover,
|
||||
.datepicker table tr td.active.disabled:hover:hover,
|
||||
.datepicker table tr td.active:focus,
|
||||
.datepicker table tr td.active:hover:focus,
|
||||
.datepicker table tr td.active.disabled:focus,
|
||||
.datepicker table tr td.active.disabled:hover:focus,
|
||||
.datepicker table tr td.active:active,
|
||||
.datepicker table tr td.active:hover:active,
|
||||
.datepicker table tr td.active.disabled:active,
|
||||
.datepicker table tr td.active.disabled:hover:active,
|
||||
.datepicker table tr td.active.active,
|
||||
.datepicker table tr td.active:hover.active,
|
||||
.datepicker table tr td.active.disabled.active,
|
||||
.datepicker table tr td.active.disabled:hover.active,
|
||||
.open .dropdown-toggle.datepicker table tr td.active,
|
||||
.open .dropdown-toggle.datepicker table tr td.active:hover,
|
||||
.open .dropdown-toggle.datepicker table tr td.active.disabled,
|
||||
.open .dropdown-toggle.datepicker table tr td.active.disabled:hover {
|
||||
color: #ffffff;
|
||||
background-color: #3276b1;
|
||||
border-color: #285e8e;
|
||||
}
|
||||
.datepicker table tr td.active:active,
|
||||
.datepicker table tr td.active:hover:active,
|
||||
.datepicker table tr td.active.disabled:active,
|
||||
.datepicker table tr td.active.disabled:hover:active,
|
||||
.datepicker table tr td.active.active,
|
||||
.datepicker table tr td.active:hover.active,
|
||||
.datepicker table tr td.active.disabled.active,
|
||||
.datepicker table tr td.active.disabled:hover.active,
|
||||
.open .dropdown-toggle.datepicker table tr td.active,
|
||||
.open .dropdown-toggle.datepicker table tr td.active:hover,
|
||||
.open .dropdown-toggle.datepicker table tr td.active.disabled,
|
||||
.open .dropdown-toggle.datepicker table tr td.active.disabled:hover {
|
||||
background-image: none;
|
||||
}
|
||||
.datepicker table tr td.active.disabled,
|
||||
.datepicker table tr td.active:hover.disabled,
|
||||
.datepicker table tr td.active.disabled.disabled,
|
||||
.datepicker table tr td.active.disabled:hover.disabled,
|
||||
.datepicker table tr td.active[disabled],
|
||||
.datepicker table tr td.active:hover[disabled],
|
||||
.datepicker table tr td.active.disabled[disabled],
|
||||
.datepicker table tr td.active.disabled:hover[disabled],
|
||||
fieldset[disabled] .datepicker table tr td.active,
|
||||
fieldset[disabled] .datepicker table tr td.active:hover,
|
||||
fieldset[disabled] .datepicker table tr td.active.disabled,
|
||||
fieldset[disabled] .datepicker table tr td.active.disabled:hover,
|
||||
.datepicker table tr td.active.disabled:hover,
|
||||
.datepicker table tr td.active:hover.disabled:hover,
|
||||
.datepicker table tr td.active.disabled.disabled:hover,
|
||||
.datepicker table tr td.active.disabled:hover.disabled:hover,
|
||||
.datepicker table tr td.active[disabled]:hover,
|
||||
.datepicker table tr td.active:hover[disabled]:hover,
|
||||
.datepicker table tr td.active.disabled[disabled]:hover,
|
||||
.datepicker table tr td.active.disabled:hover[disabled]:hover,
|
||||
fieldset[disabled] .datepicker table tr td.active:hover,
|
||||
fieldset[disabled] .datepicker table tr td.active:hover:hover,
|
||||
fieldset[disabled] .datepicker table tr td.active.disabled:hover,
|
||||
fieldset[disabled] .datepicker table tr td.active.disabled:hover:hover,
|
||||
.datepicker table tr td.active.disabled:focus,
|
||||
.datepicker table tr td.active:hover.disabled:focus,
|
||||
.datepicker table tr td.active.disabled.disabled:focus,
|
||||
.datepicker table tr td.active.disabled:hover.disabled:focus,
|
||||
.datepicker table tr td.active[disabled]:focus,
|
||||
.datepicker table tr td.active:hover[disabled]:focus,
|
||||
.datepicker table tr td.active.disabled[disabled]:focus,
|
||||
.datepicker table tr td.active.disabled:hover[disabled]:focus,
|
||||
fieldset[disabled] .datepicker table tr td.active:focus,
|
||||
fieldset[disabled] .datepicker table tr td.active:hover:focus,
|
||||
fieldset[disabled] .datepicker table tr td.active.disabled:focus,
|
||||
fieldset[disabled] .datepicker table tr td.active.disabled:hover:focus,
|
||||
.datepicker table tr td.active.disabled:active,
|
||||
.datepicker table tr td.active:hover.disabled:active,
|
||||
.datepicker table tr td.active.disabled.disabled:active,
|
||||
.datepicker table tr td.active.disabled:hover.disabled:active,
|
||||
.datepicker table tr td.active[disabled]:active,
|
||||
.datepicker table tr td.active:hover[disabled]:active,
|
||||
.datepicker table tr td.active.disabled[disabled]:active,
|
||||
.datepicker table tr td.active.disabled:hover[disabled]:active,
|
||||
fieldset[disabled] .datepicker table tr td.active:active,
|
||||
fieldset[disabled] .datepicker table tr td.active:hover:active,
|
||||
fieldset[disabled] .datepicker table tr td.active.disabled:active,
|
||||
fieldset[disabled] .datepicker table tr td.active.disabled:hover:active,
|
||||
.datepicker table tr td.active.disabled.active,
|
||||
.datepicker table tr td.active:hover.disabled.active,
|
||||
.datepicker table tr td.active.disabled.disabled.active,
|
||||
.datepicker table tr td.active.disabled:hover.disabled.active,
|
||||
.datepicker table tr td.active[disabled].active,
|
||||
.datepicker table tr td.active:hover[disabled].active,
|
||||
.datepicker table tr td.active.disabled[disabled].active,
|
||||
.datepicker table tr td.active.disabled:hover[disabled].active,
|
||||
fieldset[disabled] .datepicker table tr td.active.active,
|
||||
fieldset[disabled] .datepicker table tr td.active:hover.active,
|
||||
fieldset[disabled] .datepicker table tr td.active.disabled.active,
|
||||
fieldset[disabled] .datepicker table tr td.active.disabled:hover.active {
|
||||
background-color: #428bca;
|
||||
border-color: #357ebd;
|
||||
}
|
||||
.datepicker table tr td span {
|
||||
display: block;
|
||||
width: 23%;
|
||||
height: 54px;
|
||||
line-height: 54px;
|
||||
float: left;
|
||||
margin: 1%;
|
||||
cursor: pointer;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.datepicker table tr td span:hover {
|
||||
background: #eeeeee;
|
||||
}
|
||||
.datepicker table tr td span.disabled,
|
||||
.datepicker table tr td span.disabled:hover {
|
||||
background: none;
|
||||
color: #999999;
|
||||
cursor: default;
|
||||
}
|
||||
.datepicker table tr td span.active,
|
||||
.datepicker table tr td span.active:hover,
|
||||
.datepicker table tr td span.active.disabled,
|
||||
.datepicker table tr td span.active.disabled:hover {
|
||||
color: #ffffff;
|
||||
background-color: #428bca;
|
||||
border-color: #357ebd;
|
||||
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
|
||||
}
|
||||
.datepicker table tr td span.active:hover,
|
||||
.datepicker table tr td span.active:hover:hover,
|
||||
.datepicker table tr td span.active.disabled:hover,
|
||||
.datepicker table tr td span.active.disabled:hover:hover,
|
||||
.datepicker table tr td span.active:focus,
|
||||
.datepicker table tr td span.active:hover:focus,
|
||||
.datepicker table tr td span.active.disabled:focus,
|
||||
.datepicker table tr td span.active.disabled:hover:focus,
|
||||
.datepicker table tr td span.active:active,
|
||||
.datepicker table tr td span.active:hover:active,
|
||||
.datepicker table tr td span.active.disabled:active,
|
||||
.datepicker table tr td span.active.disabled:hover:active,
|
||||
.datepicker table tr td span.active.active,
|
||||
.datepicker table tr td span.active:hover.active,
|
||||
.datepicker table tr td span.active.disabled.active,
|
||||
.datepicker table tr td span.active.disabled:hover.active,
|
||||
.open .dropdown-toggle.datepicker table tr td span.active,
|
||||
.open .dropdown-toggle.datepicker table tr td span.active:hover,
|
||||
.open .dropdown-toggle.datepicker table tr td span.active.disabled,
|
||||
.open .dropdown-toggle.datepicker table tr td span.active.disabled:hover {
|
||||
color: #ffffff;
|
||||
background-color: #3276b1;
|
||||
border-color: #285e8e;
|
||||
}
|
||||
.datepicker table tr td span.active:active,
|
||||
.datepicker table tr td span.active:hover:active,
|
||||
.datepicker table tr td span.active.disabled:active,
|
||||
.datepicker table tr td span.active.disabled:hover:active,
|
||||
.datepicker table tr td span.active.active,
|
||||
.datepicker table tr td span.active:hover.active,
|
||||
.datepicker table tr td span.active.disabled.active,
|
||||
.datepicker table tr td span.active.disabled:hover.active,
|
||||
.open .dropdown-toggle.datepicker table tr td span.active,
|
||||
.open .dropdown-toggle.datepicker table tr td span.active:hover,
|
||||
.open .dropdown-toggle.datepicker table tr td span.active.disabled,
|
||||
.open .dropdown-toggle.datepicker table tr td span.active.disabled:hover {
|
||||
background-image: none;
|
||||
}
|
||||
.datepicker table tr td span.active.disabled,
|
||||
.datepicker table tr td span.active:hover.disabled,
|
||||
.datepicker table tr td span.active.disabled.disabled,
|
||||
.datepicker table tr td span.active.disabled:hover.disabled,
|
||||
.datepicker table tr td span.active[disabled],
|
||||
.datepicker table tr td span.active:hover[disabled],
|
||||
.datepicker table tr td span.active.disabled[disabled],
|
||||
.datepicker table tr td span.active.disabled:hover[disabled],
|
||||
fieldset[disabled] .datepicker table tr td span.active,
|
||||
fieldset[disabled] .datepicker table tr td span.active:hover,
|
||||
fieldset[disabled] .datepicker table tr td span.active.disabled,
|
||||
fieldset[disabled] .datepicker table tr td span.active.disabled:hover,
|
||||
.datepicker table tr td span.active.disabled:hover,
|
||||
.datepicker table tr td span.active:hover.disabled:hover,
|
||||
.datepicker table tr td span.active.disabled.disabled:hover,
|
||||
.datepicker table tr td span.active.disabled:hover.disabled:hover,
|
||||
.datepicker table tr td span.active[disabled]:hover,
|
||||
.datepicker table tr td span.active:hover[disabled]:hover,
|
||||
.datepicker table tr td span.active.disabled[disabled]:hover,
|
||||
.datepicker table tr td span.active.disabled:hover[disabled]:hover,
|
||||
fieldset[disabled] .datepicker table tr td span.active:hover,
|
||||
fieldset[disabled] .datepicker table tr td span.active:hover:hover,
|
||||
fieldset[disabled] .datepicker table tr td span.active.disabled:hover,
|
||||
fieldset[disabled] .datepicker table tr td span.active.disabled:hover:hover,
|
||||
.datepicker table tr td span.active.disabled:focus,
|
||||
.datepicker table tr td span.active:hover.disabled:focus,
|
||||
.datepicker table tr td span.active.disabled.disabled:focus,
|
||||
.datepicker table tr td span.active.disabled:hover.disabled:focus,
|
||||
.datepicker table tr td span.active[disabled]:focus,
|
||||
.datepicker table tr td span.active:hover[disabled]:focus,
|
||||
.datepicker table tr td span.active.disabled[disabled]:focus,
|
||||
.datepicker table tr td span.active.disabled:hover[disabled]:focus,
|
||||
fieldset[disabled] .datepicker table tr td span.active:focus,
|
||||
fieldset[disabled] .datepicker table tr td span.active:hover:focus,
|
||||
fieldset[disabled] .datepicker table tr td span.active.disabled:focus,
|
||||
fieldset[disabled] .datepicker table tr td span.active.disabled:hover:focus,
|
||||
.datepicker table tr td span.active.disabled:active,
|
||||
.datepicker table tr td span.active:hover.disabled:active,
|
||||
.datepicker table tr td span.active.disabled.disabled:active,
|
||||
.datepicker table tr td span.active.disabled:hover.disabled:active,
|
||||
.datepicker table tr td span.active[disabled]:active,
|
||||
.datepicker table tr td span.active:hover[disabled]:active,
|
||||
.datepicker table tr td span.active.disabled[disabled]:active,
|
||||
.datepicker table tr td span.active.disabled:hover[disabled]:active,
|
||||
fieldset[disabled] .datepicker table tr td span.active:active,
|
||||
fieldset[disabled] .datepicker table tr td span.active:hover:active,
|
||||
fieldset[disabled] .datepicker table tr td span.active.disabled:active,
|
||||
fieldset[disabled] .datepicker table tr td span.active.disabled:hover:active,
|
||||
.datepicker table tr td span.active.disabled.active,
|
||||
.datepicker table tr td span.active:hover.disabled.active,
|
||||
.datepicker table tr td span.active.disabled.disabled.active,
|
||||
.datepicker table tr td span.active.disabled:hover.disabled.active,
|
||||
.datepicker table tr td span.active[disabled].active,
|
||||
.datepicker table tr td span.active:hover[disabled].active,
|
||||
.datepicker table tr td span.active.disabled[disabled].active,
|
||||
.datepicker table tr td span.active.disabled:hover[disabled].active,
|
||||
fieldset[disabled] .datepicker table tr td span.active.active,
|
||||
fieldset[disabled] .datepicker table tr td span.active:hover.active,
|
||||
fieldset[disabled] .datepicker table tr td span.active.disabled.active,
|
||||
fieldset[disabled] .datepicker table tr td span.active.disabled:hover.active {
|
||||
background-color: #428bca;
|
||||
border-color: #357ebd;
|
||||
}
|
||||
.datepicker table tr td span.old,
|
||||
.datepicker table tr td span.new {
|
||||
color: #999999;
|
||||
}
|
||||
.datepicker th.datepicker-switch {
|
||||
width: 145px;
|
||||
}
|
||||
.datepicker thead tr:first-child th,
|
||||
.datepicker tfoot tr th {
|
||||
cursor: pointer;
|
||||
}
|
||||
.datepicker thead tr:first-child th:hover,
|
||||
.datepicker tfoot tr th:hover {
|
||||
background: #eeeeee;
|
||||
}
|
||||
.datepicker .cw {
|
||||
font-size: 10px;
|
||||
width: 12px;
|
||||
padding: 0 2px 0 5px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.datepicker thead tr:first-child th.cw {
|
||||
cursor: default;
|
||||
background-color: transparent;
|
||||
}
|
||||
.input-group.date .input-group-addon i {
|
||||
cursor: pointer;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
.input-daterange input {
|
||||
text-align: center;
|
||||
}
|
||||
.input-daterange input:first-child {
|
||||
border-radius: 3px 0 0 3px;
|
||||
}
|
||||
.input-daterange input:last-child {
|
||||
border-radius: 0 3px 3px 0;
|
||||
}
|
||||
.input-daterange .input-group-addon {
|
||||
width: auto;
|
||||
min-width: 16px;
|
||||
padding: 4px 5px;
|
||||
font-weight: normal;
|
||||
line-height: 1.428571429;
|
||||
text-align: center;
|
||||
text-shadow: 0 1px 0 #fff;
|
||||
vertical-align: middle;
|
||||
background-color: #eeeeee;
|
||||
border: solid #cccccc;
|
||||
border-width: 1px 0;
|
||||
margin-left: -5px;
|
||||
margin-right: -5px;
|
||||
}
|
||||
.datepicker.dropdown-menu {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
z-index: 1000;
|
||||
float: left;
|
||||
display: none;
|
||||
min-width: 160px;
|
||||
list-style: none;
|
||||
background-color: #ffffff;
|
||||
border: 1px solid #ccc;
|
||||
border: 1px solid rgba(0, 0, 0, 0.2);
|
||||
border-radius: 5px;
|
||||
-webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
|
||||
-moz-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
|
||||
box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
|
||||
-webkit-background-clip: padding-box;
|
||||
-moz-background-clip: padding;
|
||||
background-clip: padding-box;
|
||||
*border-right-width: 2px;
|
||||
*border-bottom-width: 2px;
|
||||
color: #333333;
|
||||
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
font-size: 13px;
|
||||
line-height: 1.428571429;
|
||||
}
|
||||
.datepicker.dropdown-menu th,
|
||||
.datepicker.dropdown-menu td {
|
||||
padding: 4px 5px;
|
||||
}
|
||||
@@ -67,12 +67,14 @@ html, body {
|
||||
color: #EEE;
|
||||
font-size: 100%;
|
||||
height: 46px;
|
||||
margin-top: 3px;
|
||||
}
|
||||
|
||||
#nav-logo {
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
margin-right: 10px;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.nav-item:hover,
|
||||
@@ -81,10 +83,6 @@ html, body {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.nav-item.navbar-right {
|
||||
margin-top: 3px;
|
||||
}
|
||||
|
||||
.nav-item.navbar-btn {
|
||||
cursor: pointer;
|
||||
margin-top: 8px;
|
||||
@@ -96,6 +94,30 @@ html, body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#nav-search-form {
|
||||
width: 300px;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
#nav-search-form button {
|
||||
margin-top: 0;
|
||||
background-image: none;
|
||||
background-color: #F6F6F6;
|
||||
}
|
||||
|
||||
#nav-search-form input[type=search] {
|
||||
background-color: #F6F6F6;
|
||||
border-bottom-right-radius: 3px;
|
||||
border-top-right-radius: 3px;
|
||||
-webkit-transition: width linear .25s;
|
||||
}
|
||||
|
||||
#nav-search-form input[type=search]:focus {
|
||||
background-color: #FFF;
|
||||
border-color: #D9D9D9;
|
||||
width: 320px;
|
||||
}
|
||||
|
||||
/* gogits nav item active status */
|
||||
#masthead .nav .active {
|
||||
color: #fff;
|
||||
@@ -239,14 +261,40 @@ html, body {
|
||||
}
|
||||
|
||||
#social-login {
|
||||
margin-top: 30px;
|
||||
padding-top: 20px;
|
||||
margin-top: 40px;
|
||||
padding-top: 40px;
|
||||
border-top: 1px solid #ccc;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#social-login .btn {
|
||||
float: none;
|
||||
margin: auto;
|
||||
margin: auto 4px;
|
||||
}
|
||||
|
||||
#social-login .btn .fa {
|
||||
margin-left: 0;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
#social-login .btn span {
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
font-size: 16px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
#social-login h4 {
|
||||
position: absolute;
|
||||
top: -20px;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
#social-login h4 span {
|
||||
background-color: #FFF;
|
||||
padding: 0 12px;
|
||||
}
|
||||
|
||||
/* gogs-user-profile */
|
||||
@@ -263,11 +311,18 @@ html, body {
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
#user-name {
|
||||
margin-top: 20px;
|
||||
#user-name, #user-full-name {
|
||||
font-size: 1.6em;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#user-name {
|
||||
margin-bottom: 20px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
#user-full-name {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
#user-profile .profile-info .list-group-item {
|
||||
@@ -291,6 +346,22 @@ html, body {
|
||||
padding-right: 18px;
|
||||
}
|
||||
|
||||
#user-profile .profile-rel .col-md-6 {
|
||||
text-align: center;
|
||||
padding-bottom: 12px;
|
||||
}
|
||||
|
||||
#user-profile .profile-rel strong {
|
||||
font-size: 24px;
|
||||
color: #444;
|
||||
display: block;
|
||||
}
|
||||
|
||||
#user-profile .profile-rel p {
|
||||
margin-right: 0;
|
||||
color: #888;
|
||||
}
|
||||
|
||||
#user-activity .tab-pane {
|
||||
padding: 20px;
|
||||
}
|
||||
@@ -309,8 +380,27 @@ html, body {
|
||||
height: 8em;
|
||||
}
|
||||
|
||||
#repo-import-auth {
|
||||
width: 100%;
|
||||
margin-top: 48px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
#repo-import-auth .form-group {
|
||||
box-sizing: border-box;
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
/* gogits user setting */
|
||||
|
||||
#user-setting-nav.repo-setting-nav {
|
||||
background-color: #FFF;
|
||||
border: 1px solid #CCC;
|
||||
padding: 0;
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
#user-setting-nav > h4, #user-setting-container > h4, #user-setting-container > div > h4,
|
||||
#ssh-keys > h4, #user-delete > h4, #repo-setting-container .tab-pane > h4 {
|
||||
padding-bottom: 18px;
|
||||
@@ -320,13 +410,14 @@ html, body {
|
||||
|
||||
#user-setting-nav .list-group .list-group-item a {
|
||||
margin-left: 0;
|
||||
padding: .6em;
|
||||
padding: .6em 1.2em;
|
||||
font-size: 14px;
|
||||
color: #3B73AF;
|
||||
}
|
||||
|
||||
#user-setting-nav .list-group .list-group-item {
|
||||
background-color: transparent;
|
||||
margin-bottom: .6em;
|
||||
}
|
||||
|
||||
#user-setting-nav .list-group .list-group-item-success a {
|
||||
@@ -355,10 +446,76 @@ html, body {
|
||||
border-left: 4px solid #DD4B39;
|
||||
}
|
||||
|
||||
#repo-setting-container {
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
#repo-setting-container .form-horizontal label {
|
||||
line-height: 30px;
|
||||
}
|
||||
|
||||
#repo-collab-list li.collab {
|
||||
margin-bottom: .6em;
|
||||
}
|
||||
|
||||
#repo-collab-list .avatar {
|
||||
margin-right: 1em;
|
||||
width: 40px;
|
||||
}
|
||||
|
||||
#repo-collab-list a.member {
|
||||
color: #444;
|
||||
}
|
||||
|
||||
#repo-collab-list .remove-collab, #repo-hooks-list .remove-hook {
|
||||
color: #DD4B39;
|
||||
}
|
||||
|
||||
#repo-collab-form .dropdown-menu {
|
||||
margin-left: 15px;
|
||||
margin-top: 4px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#repo-collab-form .dropdown-menu li {
|
||||
padding: 0 1em;
|
||||
line-height: 36px;
|
||||
cursor: pointer;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#repo-collab-form .dropdown-menu li:hover {
|
||||
background-color: #e8f0ff;
|
||||
}
|
||||
|
||||
#repo-collab-form .dropdown-menu img {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
margin-right: 1em;
|
||||
vertical-align: middle;
|
||||
margin-top: -3px;
|
||||
}
|
||||
|
||||
#repo-collab-form .dropdown-menu ul {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
#repo-hooks-list li {
|
||||
line-height: 40px;
|
||||
border-top: 1px solid #DDD;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
#repo-hooks-list .link {
|
||||
display: inline-block;
|
||||
max-width: 360px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
height: 40px;
|
||||
line-height: 40px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* gogits user ssh keys */
|
||||
|
||||
#ssh-keys .list-group-item {
|
||||
@@ -444,6 +601,43 @@ html, body {
|
||||
margin-right: 1em;
|
||||
}
|
||||
|
||||
#user-dashboard-repo-new .btn-sm.dropdown-toggle {
|
||||
padding: 3px 8px;
|
||||
}
|
||||
|
||||
#user-dashboard-repo-new .dropdown-menu, #nav-repo-new .dropdown-menu {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#user-dashboard-repo-new ul, #nav-repo-new ul {
|
||||
margin: 0;
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
#user-dashboard-repo-new li a, #nav-repo-new li a {
|
||||
line-height: 36px;
|
||||
display: block;
|
||||
padding: 0 18px;
|
||||
color: #444;
|
||||
}
|
||||
|
||||
#user-dashboard-repo-new li a:hover, #nav-repo-new li a:hover {
|
||||
background: #0093c4;
|
||||
color: #FFF;
|
||||
}
|
||||
|
||||
#nav-repo-new button {
|
||||
border: none;
|
||||
background: transparent;
|
||||
padding: 0;
|
||||
width: 15px;
|
||||
}
|
||||
|
||||
#nav-repo-new li .fa {
|
||||
margin: 0 .5em;
|
||||
}
|
||||
|
||||
/* gogits repo single page */
|
||||
|
||||
#body-nav.repo-nav {
|
||||
@@ -512,6 +706,16 @@ html, body {
|
||||
height: 39px;
|
||||
}
|
||||
|
||||
#repo-toolbar .nav .tmp {
|
||||
padding: 0 6px;
|
||||
}
|
||||
|
||||
#repo-toolbar .nav .tmp a {
|
||||
display: inline-block;
|
||||
padding-left: 6px;
|
||||
padding-right: 6px;
|
||||
}
|
||||
|
||||
#repo-toolbar .nav .tmp a:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
@@ -536,6 +740,10 @@ html, body {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#repo-toolbar ul.navbar-right {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.activity-list {
|
||||
font-size: 14px;
|
||||
}
|
||||
@@ -609,11 +817,19 @@ html, body {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
#repo-clone .zclip {
|
||||
left: auto !important;
|
||||
}
|
||||
|
||||
/* #source */
|
||||
#source, #commits {
|
||||
margin-top: -20px;
|
||||
}
|
||||
|
||||
#commits-pager {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
#source .source-toolbar:after {
|
||||
clear: both;
|
||||
}
|
||||
@@ -831,6 +1047,10 @@ html, body {
|
||||
margin-left: .5em;
|
||||
}
|
||||
|
||||
#commits-search-form {
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.commit-box .avatar, .diff-head-box .avatar {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
@@ -838,10 +1058,6 @@ html, body {
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.commit-box .search {
|
||||
margin-top: 3px;
|
||||
}
|
||||
|
||||
.commit-box td {
|
||||
background-color: #FFF;
|
||||
}
|
||||
@@ -873,6 +1089,10 @@ html, body {
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.guide-box .zclip {
|
||||
left: auto !important;
|
||||
}
|
||||
|
||||
.diff-head-box h4 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
@@ -1024,7 +1244,7 @@ html, body {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
#issue-create-form .nav-tabs, #issue .issue-reply .nav-tabs {
|
||||
#issue-create-form .nav-tabs, #issue .issue-reply .nav-tabs, #issue .issue-edit-content .nav-tabs {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
@@ -1032,6 +1252,10 @@ html, body {
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
#issue .filters ul {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
#issue .filter-list a {
|
||||
padding: 6px 10px;
|
||||
font-size: 14px;
|
||||
@@ -1049,12 +1273,12 @@ html, body {
|
||||
border-color: #CCC;
|
||||
}
|
||||
|
||||
#issue .filter-list a:hover {
|
||||
#issue .filter-list li a:hover {
|
||||
background-color: #DDD;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
#issue .filter-list a.active {
|
||||
#issue .filter-list li a.active {
|
||||
background-color: #4183c4;
|
||||
color: #FFF;
|
||||
}
|
||||
@@ -1063,6 +1287,53 @@ html, body {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
#issue .filters > div {
|
||||
margin-bottom: 16px;
|
||||
padding-bottom: 16px;
|
||||
border-bottom: 1px solid #CCC;
|
||||
}
|
||||
|
||||
#issue .label-filter li {
|
||||
line-height: 24px;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
#issue .label-filter a {
|
||||
color: #666;
|
||||
font-weight: bold;
|
||||
padding: 0 4px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
#issue .label-filter li.label-item:hover {
|
||||
background-color: #FFF;
|
||||
}
|
||||
|
||||
#issue .label-filter .count {
|
||||
font-size: 12px;
|
||||
margin-right: 6px;
|
||||
color: #888;
|
||||
}
|
||||
|
||||
#issue .label-filter .color {
|
||||
float: left;
|
||||
height: 12px;
|
||||
width: 12px;
|
||||
border-radius: 2px;
|
||||
margin-right: 12px;
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
#issue .label-filter .del {
|
||||
margin-top: -24px;
|
||||
color: #888;
|
||||
display: none;
|
||||
}
|
||||
|
||||
#issue .label-filter .label-button {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
#issue .list-group .list-group-item {
|
||||
background-color: #FFF;
|
||||
}
|
||||
@@ -1084,6 +1355,10 @@ html, body {
|
||||
color: #444;
|
||||
}
|
||||
|
||||
#issue .issue-item h5 .labels .label {
|
||||
margin-left: 12px;
|
||||
}
|
||||
|
||||
#issue .issue-item .info span {
|
||||
margin-right: 12px;
|
||||
color: #888;
|
||||
@@ -1166,7 +1441,7 @@ html, body {
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
#issue .issue-child .panel-heading .user,#issue .issue-closed a.user,#issue .issue-opened a.user {
|
||||
#issue .issue-child .panel-heading .user, #issue .issue-closed a.user, #issue .issue-opened a.user {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
@@ -1174,7 +1449,7 @@ html, body {
|
||||
border-color: #CCC;
|
||||
}
|
||||
|
||||
#issue .issue-is-closed .issue-line{
|
||||
#issue .issue-is-closed .issue-line {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@@ -1193,7 +1468,7 @@ html, body {
|
||||
width: 60%;
|
||||
}
|
||||
|
||||
#issue .issue-closed .issue-content,#issue .issue-opened .issue-content{
|
||||
#issue .issue-closed .issue-content, #issue .issue-opened .issue-content {
|
||||
line-height: 42px;
|
||||
}
|
||||
|
||||
@@ -1203,10 +1478,175 @@ html, body {
|
||||
padding-bottom: 24px;
|
||||
}
|
||||
|
||||
#issue .issue-closed .label-danger,#issue .issue-opened .label-success{
|
||||
#issue .issue-closed .label-danger, #issue .issue-opened .label-success {
|
||||
margin: 0 .8em;
|
||||
}
|
||||
|
||||
#issue .milestone-item .actions {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
#issue .milestone-item .actions a {
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
#issue .milestone-item hr {
|
||||
width: 100%;
|
||||
padding-top: 8px;
|
||||
margin-top: 48px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
#issue .milestone-item .label {
|
||||
margin-top: 8px;
|
||||
float: left;
|
||||
padding: .5em;
|
||||
margin-left: .8em;
|
||||
}
|
||||
|
||||
#issue .assignee.dropdown-menu, #issue .assignee ul, #issue .milestone.dropdown-menu, #issue .milestone ul {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
min-width: 300px;
|
||||
}
|
||||
|
||||
#issue .issue-bar .assignee, #issue .issue-bar .assignee ul {
|
||||
min-width: 160px;
|
||||
}
|
||||
|
||||
#issue .issue-bar .assignee .dropdown-menu, #issue .issue-bar .milestone .dropdown-menu, #issue .issue-bar .labels .dropdown-menu {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#issue .assignee li, #issue .milestone li.clear-milestone {
|
||||
padding: 4px 12px;
|
||||
line-height: 30px;
|
||||
}
|
||||
|
||||
#issue .milestone .milestone-item {
|
||||
padding: 8px 12px;
|
||||
}
|
||||
|
||||
#issue .milestone li.milestone-item {
|
||||
border-bottom: 1px solid #CCC;
|
||||
}
|
||||
|
||||
#issue .milestone li.milestone-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
#issue .milestone .milestone-item p {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
#issue .assignee li:hover, #issue .milestone li.clear-milestone:hover, #issue .milestone li.milestone-item:hover {
|
||||
background-color: #e8f0ff;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#issue .assignee li img, #issue .issue-bar .assignee img {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
#issue .issue-bar > div {
|
||||
padding-bottom: 8px;
|
||||
margin-bottom: 40px;
|
||||
border-bottom: 1px solid #CCC;
|
||||
}
|
||||
|
||||
#issue .issue-bar .assignee {
|
||||
line-height: 30px;
|
||||
}
|
||||
|
||||
#issue .issue-bar .assignee .action, #issue .issue-bar .milestone .action, #issue .issue-bar .labels .action {
|
||||
position: relative;
|
||||
margin-top: -6px;
|
||||
}
|
||||
|
||||
#issue .issue-bar .milestone .completion {
|
||||
margin-top: 20px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
#issue .issue-bar .milestone .completion span {
|
||||
display: block;
|
||||
height: 12px;
|
||||
background-color: #77c64a;
|
||||
}
|
||||
|
||||
#issue .milestone .nav-tabs a {
|
||||
padding: 4px 8px;
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
#milestone {
|
||||
margin-left: 24px;
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
#issue .issue-bar .labels .label-item {
|
||||
padding: 2px 12px 4px 12px;
|
||||
border-radius: 2px;
|
||||
text-shadow: 0 0 2px #444;
|
||||
}
|
||||
|
||||
#issue .label-selected .count, #issue .label-selected a {
|
||||
color: #FAFAFA;
|
||||
}
|
||||
|
||||
#issue .label-selected a {
|
||||
text-shadow: 0 0 2px #444;
|
||||
}
|
||||
|
||||
#issue .issue-bar .labels .label-white {
|
||||
color: #FFF;
|
||||
}
|
||||
|
||||
#issue .issue-bar .labels .label-black {
|
||||
color: #444;
|
||||
}
|
||||
|
||||
#issue .issue-bar .labels .dropdown-menu ul {
|
||||
margin: 0;
|
||||
width: 180px;
|
||||
}
|
||||
|
||||
#issue .issue-bar .labels .dropdown-menu li {
|
||||
line-height: 30px;
|
||||
padding-left: 12px;
|
||||
border-bottom: 1px solid #DDD;
|
||||
}
|
||||
|
||||
#issue .issue-bar .labels .dropdown-menu li:hover {
|
||||
background-color: #e8f0ff;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#issue .issue-bar .labels .color {
|
||||
display: inline-block;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
vertical-align: text-top;
|
||||
margin-right: 6px;
|
||||
}
|
||||
|
||||
#issue .issue-bar .labels .no-checked .color {
|
||||
margin-left: 26px;
|
||||
}
|
||||
|
||||
#label-color-ipt2, #label-color-change-ipt2 {
|
||||
width: 120px;
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
#label-color-change-ipt2{
|
||||
margin-top: 1px;
|
||||
}
|
||||
|
||||
/* wrapper and footer */
|
||||
|
||||
#wrapper {
|
||||
@@ -1231,3 +1671,147 @@ html, body {
|
||||
#footer a {
|
||||
color: #000;
|
||||
}
|
||||
|
||||
/* admin dashboard/configuration */
|
||||
|
||||
.admin-dl-horizontal > dt {
|
||||
width: 220px;
|
||||
}
|
||||
|
||||
.admin-dl-horizontal > dd {
|
||||
margin-left: 240px;
|
||||
}
|
||||
|
||||
/* release page */
|
||||
|
||||
#release-head {
|
||||
margin-top: 0;
|
||||
padding-bottom: 30px;
|
||||
margin-bottom: 0;
|
||||
border-bottom: 1px solid #DDD;
|
||||
}
|
||||
|
||||
#release .release-item .col-md-10 {
|
||||
border-left: 1px solid #DDD;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#release .release-item .commit, #release .release-item .tag {
|
||||
display: block;
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
#release .release-item.release-tag .commit {
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
#release .release-item .title {
|
||||
line-height: 30px;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
#release .release-item .dot {
|
||||
width: 9px;
|
||||
height: 9px;
|
||||
background-color: #ccc;
|
||||
z-index: 999;
|
||||
position: absolute;
|
||||
display: block;
|
||||
left: -5px;
|
||||
top: 30px;
|
||||
border-radius: 6px;
|
||||
border: 1px solid #FFF;
|
||||
}
|
||||
|
||||
#release .release-item > div {
|
||||
padding-top: 20px;
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
|
||||
#release .release-item p.info {
|
||||
line-height: 20px;
|
||||
color: #666;
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
|
||||
#release .release-item div.desc {
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
|
||||
#release .release-item p.info > *, #release .release-item .download a {
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
#release .release-item .info .avatar {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
#release-new-form {
|
||||
margin-top: 24px;
|
||||
}
|
||||
|
||||
#release-new-form .target-at {
|
||||
margin: 0 1em;
|
||||
}
|
||||
|
||||
#release-new-form .target-text {
|
||||
color: #888;
|
||||
}
|
||||
|
||||
#release-new-target-branch-list {
|
||||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
min-width: 200px;
|
||||
}
|
||||
|
||||
#release-new-target-branch-list ul {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
#release-new-target-branch-list li {
|
||||
padding: 8px 20px;
|
||||
}
|
||||
|
||||
#release-new-target-branch-list li a {
|
||||
margin-left: 0;
|
||||
background-color: transparent;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#release-new-target-branch-list li a:hover {
|
||||
background-image: none;
|
||||
}
|
||||
|
||||
#release-new-target-branch-list li:hover {
|
||||
background-color: #0093c4;
|
||||
}
|
||||
|
||||
#release-new-target-branch-list li:hover a {
|
||||
color: #FFF;
|
||||
}
|
||||
|
||||
#release-new-title {
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
#release-new-content-div {
|
||||
margin-top: 16px;
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
#release-new-content-div .md-help {
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
#release-textarea .form-group {
|
||||
display: block;
|
||||
}
|
||||
|
||||
#release-new-content {
|
||||
width: 100%;
|
||||
margin: 16px 0;
|
||||
}
|
||||
|
||||
#release-preview {
|
||||
margin: 6px 0;
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
4
public/css/todc-bootstrap.min.css
vendored
4
public/css/todc-bootstrap.min.css
vendored
File diff suppressed because one or more lines are too long
BIN
public/img/bootstrap-colorpicker/alpha-horizontal.png
Normal file
BIN
public/img/bootstrap-colorpicker/alpha-horizontal.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.5 KiB |
BIN
public/img/bootstrap-colorpicker/alpha.png
Normal file
BIN
public/img/bootstrap-colorpicker/alpha.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.2 KiB |
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user