Compare commits

...

512 Commits

Author SHA1 Message Date
无闻
a0318db2f9 Merge pull request #239 from baijum/docker_command
use 'docker.io' command instead of 'docker'
2014-06-06 05:12:56 -04:00
Baiju Muthukadan
c6058e5ea8 locate docker command during running time 2014-06-06 14:22:54 +05:30
Unknown
4f2f3c2857 Code convention 2014-06-05 22:07:35 -04:00
Baiju Muthukadan
925ccc2608 use 'docker.io' command instead of 'docker' 2014-06-05 21:46:46 +05:30
Unknown
11f9d738e8 Fix #237 2014-06-03 21:51:25 -04:00
Unknown
63baf76ab2 Fix #214 2014-06-03 12:51:53 -04:00
Unknown
9c3aa6936a Fix #223 2014-06-03 12:16:44 -04:00
Unknown
32c5fa514c Fix #230 2014-06-02 23:17:21 -04:00
Unknown
eaa7d71fc7 Fix #232 2014-06-02 21:59:56 -04:00
Unknown
e1332c5239 Fix #230 2014-06-01 21:47:48 -04:00
Unknown
afc6cbc479 Update tag 2014-06-01 11:06:03 -04:00
Unknown
1f059502dc Merge branch 'master' of github.com:gogits/gogs into dev 2014-06-01 10:54:15 -04:00
Unknown
01a516b584 Fix #229 2014-06-01 10:53:57 -04:00
无闻
48c98c92c8 Merge pull request #228 from janoliver/patch-1
Fixed directory permissions for the zip download router
2014-06-01 10:42:47 -04:00
Jan Oliver Oelerich
6fe43eb8d4 Fixed directory permissions for the zip download router
If the archives/ directory is created with 0655, it results in a permission error.
2014-06-01 15:22:13 +02:00
Unknown
2657f88d9a Fix #222 2014-05-31 17:15:04 -04:00
Unknown
ddb7f55035 Prepare for v0.4.0 release 2014-05-30 19:19:30 -04:00
Unknown
fbf274b751 Fix #220 2014-05-30 17:57:38 -04:00
Unknown
422043f422 Fix #219 2014-05-30 16:35:35 -04:00
Unknown
617bbe3fee Fix #218 2014-05-30 06:34:24 -04:00
Unknown
9085dfa426 Working on #211 2014-05-29 22:09:50 -04:00
Unknown
6696610aea Fix zombie 2014-05-28 22:15:15 -04:00
slene
e323604d78 display large file 2014-05-28 14:01:41 +08:00
slene
4ee6bc4fca fix for new git api 2014-05-28 14:01:41 +08:00
Unknown
ab13a29cb5 Fix #209 2014-05-28 01:53:06 -04:00
Unknown
ff690fd976 Fix #200, add VERSION to template files 2014-05-28 00:06:31 -04:00
无闻
bcfa78b8b5 Merge pull request #210 from brunoqc/typo
Typo
2014-05-26 12:15:15 -04:00
Bruno Bigras
6227b59b1a Registeration -> Registration 2014-05-26 12:00:36 -04:00
FuXiaoHei
e0a6e6dd1a issue ui update 2014-05-26 21:21:30 +08:00
Unknown
bf5fcfb49c Add * to selected label 2014-05-25 23:46:45 -04:00
Unknown
ff48aeddef Little bug fix 2014-05-25 20:57:01 -04:00
Unknown
d72fdc9900 Change version to v0.3.6 2014-05-25 20:44:58 -04:00
Unknown
5543a0b6dc 增加版本标识确保二进制和模板文件处于同个版本 2014-05-25 20:33:30 -04:00
Unknown
2f820e01d8 Fixed #209 2014-05-25 20:17:26 -04:00
Unknown
688ec6ecbd Fixed #209 2014-05-25 20:11:25 -04:00
Unknown
87854c95a9 Fixed #200 2014-05-24 15:34:02 -04:00
Unknown
80055bde86 Code convention 2014-05-24 15:28:31 -04:00
Unknown
e33a104448 Almost done issue label #200 2014-05-24 03:11:53 -04:00
Unknown
dce17c86ac Almost done issue label #200 2014-05-24 03:05:41 -04:00
Unknown
b1bdbd7f94 Almost done issue label #200 2014-05-24 02:31:58 -04:00
FuXiaoHei
50ba08e2c6 fix issue label ajax 2014-05-24 13:39:12 +08:00
Unknown
5e9a45f74a Code convention 2014-05-21 21:37:13 -04:00
FuXiaoHei
1331134316 finish issue edit content preview 2014-05-21 20:49:47 +08:00
FuXiaoHei
4e8a1bf9c9 fix firefox zclip bug 2014-05-21 20:03:45 +08:00
FuXiaoHei
c82807a713 add issue label attach-detach ui 2014-05-19 23:47:11 +08:00
FuXiaoHei
342baf1dda add label edit and manage ui 2014-05-19 21:55:25 +08:00
Lunny Xiao
fdc6b64f15 Merge pull request #203 from DerDackel/nopasswords
Remove password from log message upon login failure
2014-05-19 16:16:07 +08:00
Sebastian Jackel
02fb088b80 Remove password from log message on failed login 2014-05-19 09:16:56 +02:00
Unknown
5660570d92 Add MustValueRange 2014-05-18 22:05:35 -04:00
Unknown
93f8f92523 Finish create new label 2014-05-18 18:07:04 -04:00
无闻
a4c3ab48a5 Merge pull request #201 from j20/patch-1
Add dropdown-menu-right class to the repo-watching dropdown
2014-05-18 16:58:58 -04:00
Jason
7fd22bea1e Add dropdown-menu-right class to the repo-watching dropdown
The dropdown menu was dropping down to the right and appearing outside of the page container. This made it not visible at smaller browser widths.

This commit makes it drop down into the page container, instead of out, and keeps it visible at all screen sizes.
2014-05-18 15:57:02 -05:00
Unknown
04890904f9 fix #195 2014-05-16 16:24:26 -04:00
Unknown
a0a95d797e Fix ui 2014-05-16 16:21:31 -04:00
Unknown
956f011dd3 Fix #186 2014-05-16 13:41:08 -04:00
FuXiaoHei
16e162b669 issue label ui design 2014-05-16 16:00:09 +08:00
FuXiaoHei
61641d9c63 issue label ui design 2014-05-16 15:47:39 +08:00
Unknown
44f1ef2493 Merge branch 'dev' of github.com:gogits/gogs into dev 2014-05-15 23:03:31 -04:00
Unknown
be82716b66 Clean old LDAP code 2014-05-15 23:03:26 -04:00
Lunny Xiao
57ad8d50b7 Merge branch 'dev' of github.com:gogits/gogs into dev 2014-05-16 10:31:54 +08:00
Lunny Xiao
f6c94c29d5 implicated error for ldap dial 2014-05-16 10:31:39 +08:00
Unknown
4744996f9a Make gmail auth work 2014-05-15 14:46:04 -04:00
Unknown
db6b71ad03 Update issue-user when change milestone 2014-05-15 12:08:53 -04:00
Lunny Xiao
f4486f3eec bug fixed #193 2014-05-15 22:34:36 +08:00
Lunny Xiao
4f042d12bd Merge branch 'dev' of github.com:gogits/gogs into dev 2014-05-15 20:58:20 +08:00
Lunny Xiao
7869cfccb9 Merge pull request #192 from DerDackel/ldapssl
Add LDAP over SSL support
2014-05-15 20:52:05 +08:00
Sebastian Jackel
eb264a112b Add LDAP over SSL support 2014-05-15 14:27:16 +02:00
Lunny Xiao
839a9bb054 Merge branch 'dev' of github.com:gogits/gogs into dev 2014-05-15 09:55:16 +08:00
Lunny Xiao
9d5e827a1e Merge branch 'master' of github.com:gogits/gogs into dev 2014-05-15 09:54:59 +08:00
Unknown
b70db61854 Fix #185 2014-05-14 13:04:57 -04:00
Unknown
16bddd593a Finish change issue’s milestone 2014-05-14 11:14:51 -04:00
Unknown
e880a2fa48 Merge branch 'dev' of github.com:gogits/gogs into dev 2014-05-14 10:55:41 -04:00
Unknown
46fc36c3a6 Finish change issue’s milestone 2014-05-14 10:55:36 -04:00
FuXiaoHei
a1bb3741d5 add milestone in new-issue page 2014-05-14 22:01:20 +08:00
FuXiaoHei
99f2400e3b Merge remote-tracking branch 'origin/dev' into dev 2014-05-14 21:31:29 +08:00
FuXiaoHei
4a4392192b add milestone issue view ui 2014-05-14 21:29:54 +08:00
Unknown
e8dd480f10 Litte bug fix 2014-05-14 09:23:33 -04:00
Unknown
9100786beb Fix #185 2014-05-14 08:51:04 -04:00
Unknown
b0084b1adc Litte bug fix 2014-05-14 08:30:35 -04:00
Unknown
5ed5aa5228 Finish milestone 2014-05-13 19:46:48 -04:00
Unknown
33ec0632ff Fix #183 2014-05-13 19:26:13 -04:00
无闻
f276b37bbb Merge pull request #184 from mlitbk/issue-link
Fix issue link in issues page
2014-05-13 17:53:25 -04:00
Michael Litvak
cca2a53d6f Fix issue link in issues page
The link assumes that the issue belongs to the signed in
user, which is not necessarily the case
I changed it to use repository owner name instead
2014-05-13 23:37:09 +03:00
Unknown
5d5c4535cb Fix #181 2014-05-13 14:49:20 -04:00
Unknown
2eee1e9bc2 Finish edit a milestone 2014-05-13 13:28:21 -04:00
Unknown
98dbbae2ef Fix #166 2014-05-13 12:40:32 -04:00
Unknown
c117f9e73f Fix #166 2014-05-12 20:22:35 -04:00
Unknown
f979d0d6b9 Fix #138 2014-05-12 19:32:35 -04:00
Unknown
a913aff1d0 Show collaborative repositories in dashboard 2014-05-12 15:14:22 -04:00
Unknown
54e95fa367 Finish add new milestone 2014-05-12 14:06:42 -04:00
Unknown
f1130ce5e9 Fix edit auth page bug 2014-05-12 11:02:36 -04:00
无闻
f5b2e5f836 Merge pull request #177 from jacksonpan/master
modify the file path
2014-05-12 10:32:07 -04:00
FuXiaoHei
1769bb2f26 add milestone edit ui 2014-05-12 21:51:22 +08:00
jack
83a10ce880 add tip to modify the command path, add check the log folder, if not then create it. 2014-05-12 21:50:04 +08:00
jack
7ec3f1b2d8 modify the file path
move etc/supervisord.conf to conf/etc/supervisord;
remove tmp folder, then all logs move to log folder;
move the supervisor.pid to /tmp folder.
2014-05-12 21:13:05 +08:00
FuXiaoHei
3fe87cba85 fix preview js 2014-05-12 20:35:26 +08:00
无闻
ca0b6dfa38 Merge pull request #175 from jacksonpan/master
add supervisor support and script, easy to use.
2014-05-12 07:51:13 -04:00
jack
1482bd8fb4 add supervisor support and script, easy to use.
just like ./gogs_supervisord.sh start
./gogs_supervisord.sh stop
./gogs_supervisord.sh restart
and logs all saved tmp folder
2014-05-12 13:20:13 +08:00
Unknown
0970d6cc38 Log to different adapter according to level 2014-05-11 14:37:12 -04:00
Unknown
98eeec4cbb Fix #174 2014-05-11 14:03:51 -04:00
Unknown
68fb62347f Finish issue assignee 2014-05-11 13:46:36 -04:00
Unknown
a196245a87 little fix 2014-05-11 12:20:41 -04:00
Unknown
605a38759f Fix #94 and fix #169 2014-05-11 12:17:10 -04:00
Unknown
fd4b123e88 Fix #94 and fix #169 2014-05-11 11:18:10 -04:00
Unknown
d122aa6d88 Merge branch 'dev' of github.com:gogits/gogs into dev 2014-05-11 10:37:36 -04:00
Unknown
2c73ced0db Fix #173 2014-05-11 10:37:31 -04:00
FuXiaoHei
fd7b0a2ba4 add user new js 2014-05-11 21:27:06 +08:00
FuXiaoHei
27f9b7a144 add user new js 2014-05-11 21:26:47 +08:00
Lunny Xiao
f68e279150 spell bug fixed 2014-05-11 20:18:57 +08:00
Lunny Xiao
70398ed01a Merge branch 'dev' of github.com:gogits/gogs into dev 2014-05-11 20:04:54 +08:00
Lunny Xiao
d2231bb54c smtp login bug fixed 2014-05-11 20:04:28 +08:00
Unknown
65e628d1f4 ignore broken tests 2014-05-11 07:50:05 -04:00
Unknown
c5dbc24ca4 UI fix 2014-05-11 07:43:57 -04:00
Lunny Xiao
bf58679390 add support for smtp authentication 2014-05-11 18:10:37 +08:00
FuXiaoHei
cdc87623dc add auth new js 2014-05-11 16:46:38 +08:00
Lunny Xiao
b33f255c40 add smtp authentication 2014-05-11 15:49:36 +08:00
Lunny Xiao
26c0113ea0 delete unused codes 2014-05-11 14:13:49 +08:00
Lunny Xiao
55019bfbc5 merge all login methods 2014-05-11 14:12:45 +08:00
Lunny Xiao
4d6de6c7b9 add login name for auth type 2014-05-11 11:56:04 +08:00
slene
7d84cc96e8 update with git api 2014-05-10 11:14:24 +08:00
FuXiaoHei
2eabeba6b7 finish milestone add page 2014-05-09 22:33:07 +08:00
FuXiaoHei
045c21de4f assignee ui in issue view 2014-05-09 20:44:08 +08:00
Unknown
4ef9494637 Fix panic when view issue without login 2014-05-09 06:37:32 -04:00
无闻
43ffacd05b Merge pull request #168 from jiangjianxiao/dev
fix news feed repo link
2014-05-09 05:15:28 -04:00
jiangjianxiao
501f70e248 fix news feed repo link 2014-05-09 14:42:50 +08:00
Unknown
09dba7d63e Clean names 2014-05-08 22:12:05 -04:00
Unknown
3f4c040f3f Update import path 2014-05-08 22:06:06 -04:00
Unknown
830494a8aa Remove old files 2014-05-08 22:03:34 -04:00
Unknown
25713ab209 Fix #167 2014-05-08 20:00:07 -04:00
Unknown
914ffa496f Show private repository activities in dashboard if has access 2014-05-08 19:17:43 -04:00
Unknown
a742ee543e Add change assignee back end 2014-05-08 17:17:45 -04:00
Unknown
e867283406 Assignee back end 2014-05-08 12:24:11 -04:00
FuXiaoHei
a03f380fa8 assignee ui in issue view 2014-05-08 21:49:05 +08:00
FuXiaoHei
8eb15815f1 Merge remote-tracking branch 'origin/dev' into dev 2014-05-08 21:02:25 +08:00
FuXiaoHei
11ca10ab2f issue assignee ui 2014-05-08 21:01:47 +08:00
Unknown
23a857d107 Add repo info to web hook post 2014-05-08 08:18:03 -04:00
Unknown
495d939ca5 Waiting for UI 2014-05-07 20:36:00 -04:00
Unknown
7d89e765ab Bug fix 2014-05-07 18:57:00 -04:00
Unknown
31fd45ba02 Bug fix 2014-05-07 17:38:03 -04:00
Unknown
abd8c2a7ca Bug fix 2014-05-07 17:37:09 -04:00
Unknown
e05b1385fb Bug fix 2014-05-07 17:23:02 -04:00
Unknown
8b6766ecbe Bug fix 2014-05-07 17:10:46 -04:00
Unknown
269281ab76 Bug fix 2014-05-07 17:04:32 -04:00
Unknown
4b08d3aacf travis file 2014-05-07 16:57:25 -04:00
Unknown
33d32585b1 Add mention, read/unread support of issue tracker 2014-05-07 16:51:14 -04:00
Unknown
6fb7229bea Merge branch 'dev' of github.com:gogits/gogs into dev 2014-05-07 12:09:35 -04:00
Unknown
7407f9caf3 Finish issue design 2014-05-07 12:09:30 -04:00
FuXiaoHei
d76772adb9 add milestone list page 2014-05-07 21:01:00 +08:00
Unknown
8ca14e2109 Improve delete SSH key 2014-05-06 16:28:52 -04:00
Unknown
7cb5a15c9b Batch of mirror fixes 2014-05-06 13:47:47 -04:00
Unknown
e573855a4f Fix #98, support web hook 2014-05-06 11:50:31 -04:00
Unknown
94bccbb148 Finish edit and remove web hook 2014-05-05 21:36:08 -04:00
Unknown
24f614f6db Finish add web hook 2014-05-05 20:52:25 -04:00
Unknown
cb505b22ec Fix #164 2014-05-05 20:09:38 -04:00
Unknown
6e3dba2cc5 Clean repo code 2014-05-05 19:58:13 -04:00
Unknown
bbdfe25769 User code clean and ui improve 2014-05-05 16:21:43 -04:00
Unknown
c1eb4d894a Clean api code 2014-05-05 13:08:01 -04:00
codeskyblue
5f653898f3 add excludes to gobuild.yml 2014-05-06 00:27:09 +08:00
FuXiaoHei
d189b83ac1 some ui fix 2014-05-05 20:50:59 +08:00
FuXiaoHei
d34c3bb751 finish webhook ui,
some ui fix
2014-05-05 20:16:24 +08:00
Unknown
3b7465f817 fix code 2014-05-05 05:32:47 -04:00
Lunny Xiao
d8136c9c3c Merge branch 'dev-ldap' into dev 2014-05-05 16:42:15 +08:00
Lunny Xiao
1652dd5068 basic authentications 2014-05-05 16:40:25 +08:00
Unknown
02687cbdf3 Add mail notify for new collaborator 2014-05-05 04:27:28 -04:00
Unknown
07c3d497a7 Fix #145 2014-05-05 02:49:33 -04:00
Unknown
816c0ed5e7 Fix import path 2014-05-05 02:42:52 -04:00
Unknown
a641854cad command dump 2014-05-05 00:55:17 -04:00
Unknown
bb0bc0a240 Merge branch 'dev' of github.com:gogits/gogs 2014-05-03 01:38:22 -04:00
Unknown
7b60756f2c Fix Collaborators cannot commit 2014-05-03 01:37:49 -04:00
Lunny Xiao
163fcec59f resolved #155 2014-05-03 11:12:15 +08:00
Lunny Xiao
79ea34e70e ldap support 2014-05-03 10:48:14 +08:00
Unknown
e10096ee2e Mirror fix on UI 2014-05-02 19:23:06 -04:00
Unknown
a9e6d49dc6 Fix #159 2014-05-02 17:30:59 -04:00
FuXiaoHei
3e856928a2 add webhooks page ui 2014-05-02 22:07:34 +08:00
Unknown
8bbaf9550a Merge branch 'master' of github.com:gogits/gogs
Conflicts:
	models/action.go
2014-05-01 21:31:16 -04:00
Unknown
e7d8fadb08 Add disable gravatar option 2014-05-01 21:30:04 -04:00
Unknown
3bd5fc6d6f Add command dump and move to cmd did 2014-05-01 21:21:46 -04:00
Unknown
cd2020429a Fix #153 2014-05-01 20:29:51 -04:00
Unknown
ff9872104e Fix #153 2014-05-01 20:26:40 -04:00
Lunny Xiao
a3f807106a add default for Action's IsPrivate 2014-05-02 08:18:44 +08:00
Unknown
100cd181bc Add router log config option 2014-05-01 18:53:41 -04:00
Unknown
03c2468c2f Merge branch 'master' of github.com:gogits/gogs into dev 2014-05-01 12:16:04 -04:00
无闻
fa17989763 Merge pull request #152 from jcfrank/master
Add 'IF EXISTS', otherwise an error would show on MySQL that has no 'gogs' created before.
2014-05-01 12:15:31 -04:00
Unknown
75109bbd65 Fix panic when no master branch 2014-05-01 12:03:10 -04:00
Chen Chao Shih
6ce8fa49ea Add 'IF EXISTS', otherwise an error would show on MySQL that has no
'gogs' created before.
2014-05-01 23:58:48 +08:00
Unknown
0a187dbef5 Merge branch 'dev' of github.com:gogits/gogs into dev 2014-05-01 11:32:25 -04:00
Unknown
f6c4fbeb37 Collaborator 2014-05-01 11:32:12 -04:00
FuXiaoHei
ad71775ae8 fix collaboration js bug 2014-05-01 23:31:35 +08:00
FuXiaoHei
0dfb5560cd Merge remote-tracking branch 'origin/dev' into dev 2014-05-01 21:28:47 +08:00
FuXiaoHei
1c88a541cb fix profile ui 2014-05-01 21:27:12 +08:00
Unknown
a7584d16e4 Fix #150 2014-05-01 09:09:37 -04:00
Unknown
480a4ae8c5 Fix #150 2014-05-01 09:00:30 -04:00
FuXiaoHei
cc1eb5643e Merge branch 'dev' of https://github.com/gogits/gogs into dev 2014-05-01 20:57:11 +08:00
Unknown
a2333d95d5 Fix #148 2014-05-01 08:35:05 -04:00
Unknown
89c99167b2 Fix #149 2014-05-01 08:26:41 -04:00
Unknown
d0ea4c7b68 Fix #149 2014-05-01 07:59:09 -04:00
Unknown
2a1dc0085b fix #91 2014-05-01 07:35:21 -04:00
Unknown
46af92c57e Clean files 2014-05-01 06:55:39 -04:00
FuXiaoHei
52fbb9788a add collaboration page ui 2014-05-01 17:44:22 +08:00
无闻
00ebb5019f Merge pull request #147 from dockboard/dev
Remove 'ENV DEBIAN_FRONTEND noninteractive'
2014-05-01 03:40:29 -04:00
Meaglith Ma
dc5546633f Remove 'ENV DEBIAN_FRONTEND noninteractive' 2014-05-01 15:37:24 +08:00
Unknown
eb463000a9 Clean files 2014-05-01 03:27:26 -04:00
Unknown
49dc57e336 Add /api/v1/users/search 2014-04-30 23:48:01 -04:00
Unknown
de46c06d2e Merge branch 'master' of github.com:gogits/gogs into dev 2014-04-30 03:44:42 -04:00
Unknown
b36448a537 Merge branch 'dev' of github.com:gogits/gogs into dev 2014-04-30 03:44:32 -04:00
Unknown
48bfbb7ddf Mirror changes 2014-04-30 03:44:28 -04:00
Lunny Xiao
1e9f376d3d bug fixed 2014-04-30 11:12:03 +08:00
Lunny Xiao
a85f242030 Merge branch 'dev' of github.com:gogits/gogs into dev 2014-04-30 10:24:00 +08:00
Lunny Xiao
cdc843f06b add fix command for upgrade 2014-04-30 10:23:43 +08:00
无闻
0d6856dbe7 Merge pull request #143 from tsigo/rs-install-default-hosts
Install: Set the default host string based on database type
2014-04-29 17:47:16 -04:00
Robert Speicher
494e5fd40c Install: Set the default host string based on database type 2014-04-29 16:35:25 -04:00
Unknown
2401e68d7e Prepare for v0.3.1 hotfix 2014-04-28 21:53:40 -04:00
无闻
31805e2bbe Merge pull request #135 from clee/show-parents
Show parents in commit diff page
2014-04-27 19:46:38 -04:00
Chris Lee
6700257558 Show parents in commit diff page 2014-04-27 23:43:14 +00:00
Unknown
41b0a7b97c Mirror fix on public key 2014-04-27 17:01:39 -04:00
Unknown
62240b6bc1 Add login by email 2014-04-27 01:54:08 -06:00
Unknown
ce05a8d7b6 Merge branch 'dev' of github.com:gogits/gogs into dev
Conflicts:
	templates/repo/single_list.tmpl
2014-04-27 01:06:57 -06:00
Unknown
62d23e9154 HTTP no follow and offline mode 2014-04-27 01:05:13 -06:00
无闻
9fdf4bc277 Merge pull request #134 from clee/master
Use commit summaries instead of full messages
2014-04-27 00:56:06 -06:00
Chris Lee
8a8f84d245 Use commit summaries instead of full messages 2014-04-27 06:46:03 +00:00
Unknown
59d0e73c35 Batch mirror fix 2014-04-26 22:34:48 -06:00
Unknown
1badb2bbcc Fix #107 2014-04-26 19:06:59 -06:00
Unknown
89b68bdd45 Merge branch 'master' of github.com:gogits/gogs
Conflicts:
	modules/base/conf.go
2014-04-26 18:34:58 -06:00
无闻
597387ad40 Merge pull request #132 from fundon/fix_codename_match
Fixed codename match issue, when using `FROM ubuntu` wil be pulled latest image
2014-04-26 14:18:29 -06:00
fundon
cfa0968191 Fixed codename match issue, when using FROM ubuntu wil be pulled latest image 2014-04-27 01:55:22 +08:00
Unknown
9dfb7de371 Fix #102 2014-04-26 01:22:22 -06:00
Lunny Xiao
1b734501bd bug fixed 2014-04-26 14:25:51 +08:00
Unknown
861a20f464 Merge branch 'dev' of github.com:gogits/gogs into dev 2014-04-26 00:21:52 -06:00
Lunny Xiao
8bab21d795 add login.go 2014-04-26 14:21:04 +08:00
Lunny Xiao
0da329462e bug fixed 2014-04-26 14:14:48 +08:00
Unknown
3684e70dc4 Merge branch 'master' of github.com:gogits/gogs into dev 2014-04-25 23:09:20 -06:00
无闻
83578cff65 Merge pull request #129 from shabbychef/patch-1
explicitly install sudo
2014-04-25 18:40:57 -06:00
无闻
3f2f648035 Merge pull request #124 from fundon/delete-superfluous-variable
delete superfluous environment variables
2014-04-25 17:56:39 -06:00
Steven Pav
d0f887a1ed explicitly install sudo
```deploy.sh``` in the docker container requires ```sudo```; this should resolve #127.
2014-04-25 15:21:48 -07:00
fundon
35a86d04c0 delete superfluous environment variables 2014-04-25 17:31:57 +08:00
无闻
37cbfc032a Merge pull request #112 from juju2013/master
basic support for LDAP/Microsoft Active Directory authentication
2014-04-24 14:25:56 -06:00
Unknown
5898d56205 Merge branch 'dev' of github.com:gogits/gogs into dev 2014-04-24 14:20:27 -06:00
无闻
cf7901fe6a Merge pull request #120 from james-anderson/master
fixed some broken forms in user settings
2014-04-24 14:11:59 -06:00
James Anderson
e5af34a078 fixed some broken forms that were introduced in james-anderson@521c5f0e10 2014-04-24 14:50:24 -04:00
无闻
51550e5b2e Merge pull request #114 from deringer/dev
Missed one typo while fixing another... on the same line. Deplorable eff...
2014-04-23 17:48:44 -06:00
Michael Dyrynda
eb4691cb2f Missed one typo while fixing another... on the same line. Deplorable effort on my part! @bradleyfalzon 2014-04-24 09:12:34 +09:30
无闻
912481019f Merge pull request #113 from deringer/dev
fixed typo
2014-04-23 16:54:14 -06:00
Michael Dyrynda
184f1ae135 fixed typo 2014-04-24 08:20:23 +09:30
juju2013
efc05ea1de initial support for LDAP authentication/MSAD 2014-04-23 23:07:54 +02:00
无闻
521c5f0e10 Merge pull request #111 from james-anderson/master
Changed 'setting' url to settings, and a small typo fix
2014-04-23 16:02:05 -04:00
James Anderson
32ae6896fa Close to Closed 2014-04-23 15:39:37 -04:00
James Anderson
4b58c01603 Changed setting url to settings 2014-04-23 15:30:18 -04:00
Lunny Xiao
ff690840d4 doc improvements 2014-04-23 19:24:26 +08:00
Lunny Xiao
0c5e50a888 Merge branch 'master' of github.com:gogits/gogs 2014-04-23 19:22:04 +08:00
无闻
7ded30ba5b Merge pull request #109 from laofo/master
Create the installation guide for gogs on ubuntu (src & binary)
2014-04-23 05:20:51 -04:00
Lunny Xiao
683e58878a bug fixed 2014-04-23 17:12:50 +08:00
laofo
649d0e1681 format the doc and add comments 2014-04-23 16:33:03 +08:00
laofo
d7956b3fb8 format the doc 2014-04-23 16:30:05 +08:00
laofo
2a95bc1395 use the dedicated user, git, to run gogs 2014-04-23 16:20:53 +08:00
laofo
e554e49c16 add the installation guide on ubuntu, including binary install and source install 2014-04-23 16:18:46 +08:00
Unknown
9abb37b45e Merge branch 'master' of github.com:gogits/gogs into dev 2014-04-23 00:33:52 -04:00
无闻
22f8536577 Merge pull request #105 from dockboard/master
Add Memcached and Redis Docker supported with MySQL and PostGreSQL
2014-04-23 00:33:04 -04:00
Meaglith Ma
8952eb1ce0 Sync with gigs master branch. 2014-04-23 12:30:18 +08:00
Meaglith Ma
ee7bfe2ebe Add memcached and redis Docker supported 2014-04-23 12:29:53 +08:00
crystaldust
b270b34c98 Merge branch 'feature/docker'
Add the sciprt for deplying gogs in docker, support the user to choose database(MySQL or PostgreSQL) and memory cache(redis or memcached).
2014-04-23 03:29:43 +00:00
crystaldust
b01e967a9f Add the configs comments, and update the README.md file 2014-04-23 03:29:17 +00:00
crystaldust
baacba96ca Remove the dependency of Unkwon/com, since gogs has already integrate the lateset version of Unkwon/com. 2014-04-23 03:16:09 +00:00
Unknown
8bc502a1ea New err check style 2014-04-22 22:34:49 -04:00
crystaldust
e5aaf23bb2 Update the githu.com/Unknwon/com module before getting gogs. 2014-04-23 02:26:56 +00:00
crystaldust
a90a014033 Change the image naming, start with "gogits/" 2014-04-23 00:05:14 +00:00
Unknown
88072a1e9b Merge branch 'master' of github.com:gogits/gogs into dev 2014-04-22 16:10:27 -04:00
无闻
f0cdf30134 Merge pull request #100 from gramakri/grammar-fix
Fix grammar
2014-04-22 16:07:49 -04:00
Girish Ramakrishnan
22bb5104c8 Fix grammar 2014-04-22 12:49:22 -07:00
crystaldust
1560abe553 Add the MEM db config replacement script in the deploy.sh. 2014-04-22 10:20:27 +00:00
crystaldust
65ad26feba Checkout to the latest dev branch when building the gogs docker image. 2014-04-22 10:19:23 +00:00
crystaldust
b60d5ecc3e Add the redis Dockerfile 2014-04-22 10:15:12 +00:00
crystaldust
8a6119551b Add the memcache(d) Dockerfile 2014-04-22 10:14:10 +00:00
crystaldust
4b8d72dec2 Change the MySQL password placeholder to THE_DB_PASSWORD. 2014-04-22 09:49:10 +00:00
Unknown
5435b259cc Merge branch 'master' of github.com:gogits/gogs into dev 2014-04-22 05:30:53 -04:00
无闻
dbdaf934e1 Merge pull request #87 from bjohnso5/master
Minor grammatical fix for Disable Registration feature
2014-04-22 05:30:36 -04:00
无闻
d6ff275c58 Merge pull request #95 from octplane/patch-1
Typos
2014-04-22 05:13:11 -04:00
Pierre Baillet
b32407456a Typos
- Very small typo fixes
2014-04-22 11:11:11 +02:00
Unknown
2a9da4b8e5 Fix #92 2014-04-22 03:41:47 -04:00
无闻
9a5e49cccb Merge pull request #90 from cburgdorf/patch-1
fixed typo
2014-04-21 20:16:22 -04:00
Christoph Burgdorf
338fc122fa fixed typo 2014-04-22 00:53:04 +02:00
Bryan Johnson
34d18a19a3 Minor grammatical fix for Disable Registration feature 2014-04-21 18:03:04 -04:00
Unknown
de01f81489 Prepare for v0.3.0 release 2014-04-21 06:54:07 -04:00
Unknown
660b47373d Prepare for release v0.3.0 2014-04-21 06:37:57 -04:00
Unknown
11a28428de Merge branch 'master' of github.com:gogits/gogs 2014-04-21 02:24:39 -04:00
Unknown
ea29a9b846 API fix 2014-04-21 02:24:35 -04:00
无闻
7b91dfeb01 Update reset_passwd.tmpl 2014-04-21 00:27:40 -04:00
Unknown
006c45e21d Mirror change 2014-04-20 20:52:00 -04:00
无闻
e55d1ea3d6 Merge pull request #85 from twitchyliquid64/master
Corrected grammar and spelling mistakes
2014-04-20 14:05:51 -04:00
twitchyliquid64
d304a23787 Corrected grammar and spelling mistakes in templates. Normalizing to American English. 2014-04-20 16:54:26 +10:00
Unknown
5fdfc2e223 Add debug log 2014-04-19 23:37:04 -04:00
Unknown
8bed017557 Add build tag enable prompt 2014-04-19 22:13:22 -04:00
Unknown
0e41726ece API change 2014-04-19 06:15:47 -04:00
Unknown
eda3f8b3b3 Mirror updates 2014-04-19 06:00:08 -04:00
Unknown
b32e223db5 Mirror fix 2014-04-18 21:17:43 -04:00
Unknown
bda8fdc5f4 fix bug when logout without activate 2014-04-18 21:14:35 -04:00
Unknown
aa58320a1b Add 2 more gitignore 2014-04-18 12:17:28 -04:00
Unknown
8719c1b8c5 Mirror bug fix 2014-04-18 12:12:10 -04:00
Unknown
642687d08d Merge branch 'dev' of github.com:gogits/gogs into dev 2014-04-18 09:35:14 -04:00
Unknown
49c01c0b57 Fix import path 2014-04-18 09:35:09 -04:00
skyblue
55af04d801 Merge branch 'master' into dev 2014-04-18 21:16:39 +08:00
skyblue
96f7af1d70 add supersor sample 2014-04-18 21:15:53 +08:00
Unknown
f8571f4db1 Bug fix on build tag 2014-04-17 03:31:21 -04:00
Unknown
7d2632784f Mirror fix 2014-04-17 02:20:24 -04:00
无闻
e44655f722 Merge pull request #83 from dockboard/master
Add postgresql support in docker deployed.
2014-04-16 14:04:07 -04:00
Unknown
1bfe4ee665 Merge branch 'master' of github.com:gogits/gogs 2014-04-16 07:13:20 -04:00
Unknown
2dc0329c5f Fix auth issue on #80 2014-04-16 04:45:02 -04:00
Unknown
6277f8497c Auth problem related #80 2014-04-16 04:37:07 -04:00
Unknown
7ad05ce210 Fix #80 2014-04-16 04:12:31 -04:00
crystaldust
066d91e113 Allow the user to config the apt source in build.sh 2014-04-15 23:48:33 -04:00
crystaldust
bb84bb8ac3 Add two apt source:aliyun and nchc, make a '#sourcename#' before the RUN command to make the apt source configurable in build.sh 2014-04-15 23:41:46 -04:00
Lunny Xiao
9e047cdbbb Merge pull request #79 from compressed/null_handling
swap \n for \000, update len check, when reading receive-pack input
2014-04-16 11:38:11 +08:00
crystaldust
213b366959 Remove the installation of PostgreSQL, which not needed actually. 2014-04-15 23:34:58 -04:00
Christopher Brickley
4c5a6e4d87 swap \n for \000, update len check, when reading receive-pack input 2014-04-15 22:07:28 -04:00
Unknown
c3a52f7dd0 Mirror bug fix on downloading zip 2014-04-15 20:01:20 -04:00
crystaldust
dfba49b062 Update README.md
Add the instruction of installing gogs with PostgreSQL
2014-04-16 07:50:58 +08:00
Unknown
67426534ef Add view by tag support 2014-04-15 17:43:25 -04:00
Unknown
7d656ee2e3 Update docs 2014-04-15 15:34:26 -04:00
Lance Ju
83283dfe37 Merge remote-tracking branch 'origin/master'
Add the support of postgres as the database.
(Config the db in the build.sh file, make the DB_TYPE to 'postgres').
2014-04-16 00:58:09 +08:00
Lance Ju
f456f964ec Add the apt source when installing potgres.
Replace the host with $DB_PORT, which is dependent to the database imagae's exposed port.
2014-04-16 00:55:39 +08:00
Lance Ju
1f989d0b98 Replace the 'mysql' options with 'db' options in build.sh 2014-04-16 00:54:02 +08:00
slene
d3fc1da8c2 download link 2014-04-16 00:46:57 +08:00
slene
4fafc76052 zip archive download 2014-04-16 00:29:03 +08:00
Lance Ju
8080beea85 Add the line to replace the database type.
Replace the 'mysql' options with 'db' options, makeing the config according to the database type.
2014-04-16 00:17:52 +08:00
Lance Ju
1d3c7693b7 Build the database image by the variable DB_TYPE, but not mysql. 2014-04-16 00:16:32 +08:00
Lance Ju
6cc914b090 Rename the posgresql to postgres, add the Dockerfile for postgres image. 2014-04-16 00:14:17 +08:00
Unknown
5378bb326b Merge branch 'master' of github.com:gogits/gogs into dev 2014-04-14 22:48:01 -04:00
shengxiang[skyblue]
df0c61cc62 Merge pull request #78 from compressed/master
change gravatar image to match URL scheme
2014-04-15 10:16:55 +08:00
Christopher Brickley
b9a1d13c29 change gravatar image to match URL scheme 2014-04-14 22:01:24 -04:00
Lunny Xiao
d9f2878db4 Merge pull request #77 from compressed/postgres
update SetEngine for postgresql #76
2014-04-15 09:34:43 +08:00
Christopher Brickley
caeddb79a3 update SetEngine for postgresql #76 2014-04-14 21:07:21 -04:00
Lunny Xiao
4210f56dbc bug fixed for http clone 2014-04-14 23:22:00 +08:00
skyblue
6cb636f8a0 fix title extra quoted 2014-04-14 22:42:17 +08:00
FuXiaoHei
8549b47e04 fix repo-new ui, fix top-nav search-bar ui 2014-04-14 20:40:22 +08:00
Unknown
337eef2ee5 Weibo oauth 2014-04-14 06:29:47 -04:00
Unknown
516baa4531 Weibo oauth 2014-04-14 06:07:40 -04:00
Unknown
a1ab3cad3e Weibo oauth 2014-04-14 05:37:39 -04:00
Unknown
3c3cda7326 Weibo oauth 2014-04-14 05:07:19 -04:00
Unknown
c3c2cfebaa Merge branch 'dev' of github.com:gogits/gogs into dev 2014-04-14 04:11:41 -04:00
Unknown
c36e7d322e Mirror updates 2014-04-14 04:11:33 -04:00
Lunny Xiao
38c069308e Merge branch 'dev' of github.com:gogits/gogs into dev 2014-04-14 14:51:51 +08:00
Lunny Xiao
8283e16ef7 Merge branch 'dev' of github.com:gogits/gogs into dev 2014-04-14 14:50:37 +08:00
Unknown
57f84fb051 UPDATE DOCS 2014-04-14 02:50:13 -04:00
Lunny Xiao
6cee65db5a bug fixed #76 2014-04-14 14:49:50 +08:00
Unknown
f644cefa86 Finish release 2014-04-14 01:57:25 -04:00
Unknown
190b83e05e push tag support 2014-04-13 22:20:28 -04:00
Unknown
d2b53dd43b Add weibo oauth 2014-04-13 21:00:12 -04:00
Unknown
4b9b8024ba Clean oauth code 2014-04-13 18:12:07 -04:00
Unknown
8c266f2df5 go get 2014-04-13 05:04:11 -04:00
Unknown
4c1452574a go get 2014-04-13 05:02:11 -04:00
Unknown
ea74be2f2e go get 2014-04-13 04:27:29 -04:00
Unknown
d26a333dfb go get 2014-04-13 04:08:25 -04:00
Unknown
9d983f27d6 go vet 2014-04-13 03:14:43 -04:00
Unknown
5c2da610a2 Move binding as subrepo 2014-04-13 01:57:42 -04:00
Unknown
33f2d33a46 UPDATE README 2014-04-12 23:46:14 -04:00
Unknown
c9a1eb4789 Able to change mirror interval now 2014-04-12 22:30:00 -04:00
slene
52b4ab2aa5 update with new git 2014-04-13 09:35:36 +08:00
Unknown
9ffa8a4083 finish mirror fix #63 2014-04-12 21:30:09 -04:00
Unknown
90f6aa8cd1 Add repo mirror and import 2014-04-12 20:35:35 -04:00
Unknown
23bba7633b Mirror fix on sqlite3 tag 2014-04-12 16:24:09 -04:00
Unknown
d60f7b85e5 Merge branch 'dev' of github.com:gogits/gogs into dev 2014-04-12 16:19:16 -04:00
Unknown
47f3dd43a5 Merge branch 'master' of github.com:gogits/gogs into dev 2014-04-12 16:19:10 -04:00
无闻
25ecf56285 Merge pull request #75 from crosbymichael/sqlite-build-tag
Add sqlite build tag
2014-04-12 15:21:46 -04:00
Michael Crosby
25fd495b2e Add sqlite build tag
This adds a sqlite build tag so that you don't have to have the sqlite
import commented out in code and users can run:
    go build -tags sqlite
if they want to have sqlite support enabled.  It is disabled by default
so nothing changes with the default go get or build commands.
2014-04-12 12:12:48 -07:00
skyblue
75d2affcbf add oauth2 qq support 2014-04-13 01:15:19 +08:00
skyblue
f92851e347 Merge branch 'dev' of github.com:gogits/gogs into dev 2014-04-12 23:19:22 +08:00
skyblue
2ce0c3befe add google oauth2 support 2014-04-12 23:19:17 +08:00
Unknown
802a110e42 Merge branch 'dev' of github.com:gogits/gogs into dev 2014-04-12 02:08:16 -04:00
Unknown
559a57330e Mirror fix 2014-04-12 02:08:12 -04:00
FuXiaoHei
fe4750ebe2 Merge remote-tracking branch 'origin/dev' into dev 2014-04-12 13:51:09 +08:00
FuXiaoHei
5f3a6a9d74 social login button ui 2014-04-12 13:50:27 +08:00
Unknown
794b457252 Mirror fix 2014-04-12 01:47:59 -04:00
Unknown
790a2c1099 Merge branch 'dev' of github.com:gogits/gogs into dev 2014-04-12 01:45:47 -04:00
Unknown
31d613c01d Mirror fix 2014-04-12 01:45:43 -04:00
FuXiaoHei
b057cffd85 fix logo ui, add following ui in user-profile page 2014-04-12 12:47:35 +08:00
Unknown
d305448fa8 Mirror bug fix 2014-04-11 23:52:08 -04:00
无闻
3df8b2bef0 Merge pull request #73 from zhsso/dev
combine getCommit ById and ByBranch in routers/repo/commit.go
2014-04-11 23:14:28 -04:00
zhsso
2e6d50addc combine getCommit ById and ByBranch in routers/repo/commit.go 2014-04-11 22:57:25 -04:00
Unknown
5d30bfc8ba Fix #65 fix #57 2014-04-11 21:55:22 -04:00
Unknown
4fc5dcc764 Merge branch 'dev' of github.com:gogits/gogs into dev 2014-04-11 21:47:43 -04:00
Unknown
33aa4f7438 Support private repo 2014-04-11 21:47:39 -04:00
skyblue
f8e97b75fb Merge branch 'dev' of github.com:gogits/gogs into dev 2014-04-12 09:42:15 +08:00
skyblue
5c1312f38e clean oauth2 code 2014-04-12 09:42:09 +08:00
Unknown
d6dac160df Pages in commits list page 2014-04-11 20:23:34 -04:00
Unknown
47aa53bd36 Add search commits 2014-04-11 19:44:13 -04:00
Unknown
7d07b58114 UPDATE README 2014-04-11 13:24:19 -04:00
skyblue
4c6e0e9499 Merge branch 'dev' of github.com:gogits/gogs into dev 2014-04-12 01:01:40 +08:00
skyblue
dd815ae7b5 finish github oauth2 support 2014-04-12 01:01:30 +08:00
Unknown
a5fcaae5ee UPDATE README 2014-04-11 12:26:23 -04:00
Unknown
b26df29865 Merge branch 'master' of github.com:gogits/gogs into dev 2014-04-11 12:16:17 -04:00
Unknown
23e318f85a Merge branch 'dev' of github.com:gogits/gogs into dev 2014-04-11 12:14:16 -04:00
Unknown
11f54b1426 Mirror bug fix 2014-04-11 12:14:11 -04:00
无闻
0650c3c9f6 Merge pull request #72 from dockboard/master
Add Docker Deploy Support
2014-04-11 12:10:40 -04:00
FuXiaoHei
555b6976a3 add search form in top navbar 2014-04-11 22:41:41 +08:00
FuXiaoHei
df000245d1 add pager in commit-page, finish commit search form 2014-04-11 21:56:40 +08:00
FuXiaoHei
668007592a upgrade bootstrap to 3.1.1, upgrade todc bootstrap theme 2014-04-11 21:37:42 +08:00
crystaldust
a4f070b828 Update README.md
Update the guide of how to deploy gogs in docker with the scripts.
2014-04-11 16:10:52 +08:00
Lance Ju
b1a3ba9932 Fix the path problem, which will build the mysql image as the gogits image 2014-04-11 15:32:57 +08:00
Lance Ju
2315dc39b6 Add the auto build scripts for deplying gogs with Docker. 2014-04-11 14:55:43 +08:00
Unknown
65c2c62982 Bug fix 2014-04-11 00:01:38 -04:00
Unknown
f8e370f8ca Bug fix 2014-04-10 23:49:53 -04:00
Unknown
ed546912e5 fix #52 fix #34 2014-04-10 23:41:10 -04:00
Lunny Xiao
ee2d8d2469 remove trace 2014-04-11 10:44:09 +08:00
Lunny Xiao
8cfa2be433 Merge branch 'dev' of github.com:gogits/gogs into dev 2014-04-11 10:28:23 +08:00
Lunny Xiao
db39e58a13 add actions for http push 2014-04-11 10:27:13 +08:00
Unknown
306aa5bffe Add support default branch 2014-04-10 22:03:31 -04:00
Unknown
8980675a9f Fix #69 2014-04-10 18:09:57 -04:00
Unknown
45462662e9 Add flash 2014-04-10 16:36:50 -04:00
Unknown
459223cf01 Merge branch 'master' of github.com:gogits/gogs into dev 2014-04-10 14:39:37 -04:00
无闻
8faa0dbcd7 Merge pull request #70 from zhsso/git
Git
2014-04-10 14:38:48 -04:00
Unknown
29e255422e Merge branch 'dev' of github.com:gogits/gogs into dev 2014-04-10 14:37:51 -04:00
Unknown
af552596cf Work on form resubmit 2014-04-10 14:37:43 -04:00
zhsso
a4cbe79567 fix 2014-04-10 14:20:58 -04:00
zhsso
f3ed11d177 mistakes 2014-04-10 14:20:01 -04:00
Gogs
fde5b16332 Fix wrong path name 2014-04-10 12:59:20 -04:00
Gogs
7811e58726 speedup models.getReposFiles, using os.Exec
but the results may different with before
2014-04-10 12:44:05 -04:00
Lunny Xiao
a24c0b92e4 bug fixed 2014-04-10 23:02:08 +08:00
Lunny Xiao
88d873c67f Merge branch 'dev' of github.com:gogits/gogs into dev 2014-04-10 22:22:18 +08:00
Lunny Xiao
94c7278194 Merge branch 'master' of github.com:gogits/gogs into dev
Conflicts:
	web.go
2014-04-10 22:21:12 +08:00
Lunny Xiao
16b6e5d50b bug fixed 2014-04-10 22:12:32 +08:00
FuXiaoHei
6b30d9b0f2 fix mirror typo 2014-04-10 22:06:52 +08:00
FuXiaoHei
a354f33ac2 new-repo dropdown in top navbar 2014-04-10 22:00:32 +08:00
crystaldust
5966dd78e6 Add the scirpts for auto deploy. 2014-04-10 19:56:19 +08:00
Unknown
b3f1ae1ba6 Merge branch 'dev' of github.com:gogits/gogs into dev 2014-04-09 21:42:30 -04:00
Unknown
5d4025cb5a Add go get meta support 2014-04-09 21:42:25 -04:00
slene
0f9e07de2d fix render bug 2014-04-10 08:34:19 +08:00
slene
3487f17285 markdown render html 2014-04-10 08:21:51 +08:00
Unknown
9f7bd5007b Work on mirror repo 2014-04-09 14:20:02 -04:00
Unknown
5f6bd323f5 Merge branch 'dev' of github.com:gogits/gogs into dev
Conflicts:
	models/oauth2.go
2014-04-09 14:15:09 -04:00
Unknown
a773cf1d87 Mirror fix 2014-04-09 14:11:24 -04:00
skyblue
8683d2f857 remove martini oauth2 depend 2014-04-10 00:07:57 +08:00
FuXiaoHei
c72e1b5c2a repo-import page ui 2014-04-09 21:28:00 +08:00
FuXiaoHei
721834a267 change new-repo button to dropdown menu in dashboard page 2014-04-09 20:10:56 +08:00
Meaglith Ma
4efb56af03 Update README.md 2014-04-09 10:48:05 +08:00
Meaglith Ma
c8b81d8339 Create README.md 2014-04-09 10:46:05 +08:00
Unknown
a991ebf5d0 Fix #54 2014-04-08 15:27:35 -04:00
Unknown
b506429803 Merge branch 'dev' of github.com:gogits/gogs into dev 2014-04-08 12:41:37 -04:00
Unknown
115a349131 Fix #67 2014-04-08 12:41:33 -04:00
skyblue
24d0ca4aa0 clean tail 2014-04-09 00:31:09 +08:00
skyblue
d4565483e6 add id for oauth2 2014-04-09 00:26:12 +08:00
无闻
2577940c30 Merge pull request #68 from gogits/dev
Dev
2014-04-07 15:47:26 -04:00
Unknown
22feddf804 Fix #66 2014-04-07 14:24:58 -04:00
Unknown
7776f407b6 Fix issue with log in with GitHub but need more error handle after 2014-04-07 13:00:59 -04:00
Unknown
9ea9818d32 Fix issue with log in with GitHub but need more error handle after 2014-04-07 12:56:40 -04:00
skyblue
05fb34eacd first works oauth2(github). need to login with /user/login/github 2014-04-07 18:01:25 +08:00
skyblue
a92d67fa01 Merge branch 'dev' of github.com:gogits/gogs into dev 2014-04-07 14:14:43 +08:00
skyblue
125c87a405 update fswatch.json 2014-04-07 14:14:17 +08:00
Unknown
8c9a0494ec Add @ # commit link detect on all markdown render 2014-04-07 01:55:22 -04:00
Unknown
e2fe220905 Work on comment 2014-04-07 00:47:19 -04:00
Unknown
e7c8a3cb8d Add salt for every single user 2014-04-06 16:10:57 -04:00
Unknown
db1fe3483e Fix bug related to log 2014-04-06 13:54:48 -04:00
Unknown
6a16866f4e Fix bug related to log 2014-04-06 13:41:58 -04:00
Unknown
794cd27db3 Fix bug related to log 2014-04-06 13:07:34 -04:00
Unknown
cd5b800a21 Merge branch 'dev' of github.com:gogits/gogs into dev 2014-04-06 13:00:24 -04:00
Unknown
2846ff7d31 Fix bug related to log 2014-04-06 13:00:20 -04:00
skyblue
583f11f27c better looks on github 2014-04-07 00:19:59 +08:00
skyblue
7a4c6c22ce fix start.sh in crontab run failed problem 2014-04-07 00:11:18 +08:00
skyblue
5e534bf2a5 Merge branch 'dev' of github.com:gogits/gogs into dev 2014-04-06 23:12:29 +08:00
skyblue
a04918e36d add fswatch.json for hot compile 2014-04-06 23:12:19 +08:00
Meaglith Ma
5f4b5c1557 Temp commit 2014-04-06 17:31:42 +08:00
Meaglith Ma
2e8d5c2eb3 Merge remote-tracking branch 'upstream/master' 2014-04-06 17:28:33 +08:00
FuXiaoHei
d3a987eded username & repo-name changing help message 2014-04-06 16:48:02 +08:00
FuXiaoHei
1b0142513e release-new page ui 2014-04-06 16:29:45 +08:00
FuXiaoHei
98f918ed28 Merge branch 'dev' of https://github.com/gogits/gogs into dev 2014-04-06 14:54:39 +08:00
FuXiaoHei
3ede496383 add release-new route 2014-04-06 14:54:28 +08:00
Unknown
b7c3b0cc73 Add reset password, fix #58 2014-04-05 12:32:34 -04:00
Unknown
3ebc9b991a Use gogits/session for oauth2 2014-04-05 11:22:14 -04:00
skyblue
1336d8d54d Merge branches 'dev' and 'dev' of github.com:gogits/gogs into dev 2014-04-05 22:53:17 +08:00
skyblue
c22d3503fd add oauth2 table init 2014-04-05 22:52:24 +08:00
skyblue
ce350a737a update models, add licence in start.sh 2014-04-05 22:46:32 +08:00
Lunny Xiao
9791e70da6 bug fixed 2014-04-05 22:24:10 +08:00
Lunny Xiao
a92826a8fe Merge branch 'dev' of github.com:gogits/gogs into dev
Conflicts:
	routers/repo/repo.go
2014-04-05 15:30:49 +08:00
Lunny Xiao
493b0c5ac2 add ssl support for web 2014-04-05 15:17:57 +08:00
Unknown
e41ab839c7 Use session for rolling back 2014-04-04 18:55:17 -04:00
无闻
ef6b978496 Merge pull request #62 from gogits/dev
Add transfer repository
2014-04-04 18:39:21 -04:00
Unknown
eb803ec5eb Add transfer repository 2014-04-04 18:31:09 -04:00
Unknown
ca5dcea7d1 Merge branch 'master' of github.com:gogits/gogs into dev 2014-04-04 10:41:45 -04:00
无闻
3a23476dbe Merge pull request #61 from gogits/dev
Dev
2014-04-04 10:40:14 -04:00
Unknown
4a4997a3c7 Merge branch 'master' of github.com:gogits/gogs into dev 2014-04-04 10:34:18 -04:00
无闻
48d3a1fef1 Merge pull request #60 from Wind0r/master
Add missing deps
2014-04-04 09:44:52 -04:00
FuXiaoHei
75db79b4b6 release list page ui 2014-04-04 20:54:00 +08:00
Wind0r
fa34656b5d Add missing deps 2014-04-04 12:25:14 +02:00
Lunny Xiao
e0d64d3168 Merge branch 'master' of github.com:gogits/gogs 2014-04-04 17:27:02 +08:00
Lunny Xiao
6b9f7fd758 basiclly http push 2014-04-04 17:26:46 +08:00
Unknown
e9c4156c87 Add: rename user 2014-04-03 16:33:27 -04:00
Unknown
79a610592e Add: rename repository 2014-04-03 15:50:55 -04:00
skyblue
bbadbbdf68 Merge branch 'dev' of github.com:gogits/gogs into dev 2014-04-03 11:13:44 +08:00
skyblue
bfdadaa13c add oauth2 models 2014-04-03 11:13:35 +08:00
无闻
b9a7dd72f8 Merge pull request #55 from MartinGarton/master
fix unique
2014-04-02 16:17:25 -04:00
Martin Garton
4586c386e7 fix unique 2014-04-02 20:35:29 +01:00
FuXiaoHei
1757a59a99 js clipboard 2014-04-03 00:56:26 +08:00
Unknown
db66b8da72 Add release tmpl 2014-04-02 12:43:31 -04:00
Unknown
97b133bbee Merge branch 'dev' of github.com:gogits/gogs into dev
Conflicts:
	routers/user/user.go
2014-04-02 10:45:01 -04:00
Unknown
d9005ee970 Improve issue mail content 2014-04-02 10:38:30 -04:00
skyblue
adf42a5b54 change oauth2 to modules 2014-04-02 22:27:54 +08:00
skyblue
93f6e98047 change oauth2 path 2014-04-02 22:23:45 +08:00
skyblue
5180506678 split into social.go 2014-04-02 10:39:04 +08:00
Unknown
b0e7dd6864 Update README 2014-04-01 21:19:19 -04:00
Unknown
13824af291 Merge branch 'master' of github.com:gogits/gogs into dev 2014-04-01 21:08:02 -04:00
skyblue
272c27c8f2 add github social login, first step 2014-04-02 08:14:56 +08:00
Meaglith Ma
a044c24c3d Update the Golang Dockerfile and add mysql Dockerfile 2014-04-01 14:27:21 +08:00
Unknown
91e6db1bae Fix log bug 2014-04-01 00:07:25 -04:00
无闻
67bd2daa02 Merge pull request #51 from crackcomm/master
My mistake in #50
2014-03-31 23:54:35 -04:00
无闻
9ef100bbf0 Merge pull request #50 from crackcomm/master
Description list instead of div's in admin Dashboard & Configuration.
2014-03-31 23:47:01 -04:00
crackcomm
128b7c790e My mistake... 2014-04-01 05:46:59 +02:00
crackcomm
5a07b5430e .admin-dl-horizontal 2014-04-01 05:44:30 +02:00
crackcomm
868d921f7b Description list instead of div's in admin Dashboard & Configuration. 2014-04-01 05:21:59 +02:00
Meaglith Ma
f9ba8f01d1 Merge branch 'master' of https://github.com/gogits/gogs 2014-04-01 11:13:14 +08:00
Meaglith Ma
5a01a0f668 Init dockerfiles folder and base gogs Dockerfile 2014-04-01 01:47:32 +08:00
无闻
1240041b4d Merge pull request #48 from gogits/dev
Huge bug fix
2014-03-31 10:13:11 -04:00
Unknown
10ba28deba Huge bug fix 2014-03-31 10:12:36 -04:00
Lunny Xiao
a187ba9651 improved log locations 2014-03-31 21:26:15 +08:00
Meaglith Ma
1a247340db Merge pull request #1 from gogits/master
Sync to lastest
2014-03-31 17:23:37 +08:00
253 changed files with 20945 additions and 4059 deletions

12
.fswatch.json Normal file
View 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
View File

@@ -12,6 +12,7 @@ public/img/avatar/
*.o
*.a
*.so
dev
# Folders
_obj
@@ -33,3 +34,4 @@ _testmain.go
*.exe~
gogs
__pycache__
*.pem

View File

@@ -2,9 +2,10 @@ filesets:
includes:
- templates
- public
- conf
- LICENSE
- README.md
- README_ZH.md
- start.bat
- start.sh
excludes:
- \.git

View File

@@ -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
View File

@@ -0,0 +1,5 @@
language: go
go:
- 1.2
- tip

View File

@@ -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.

View File

@@ -5,9 +5,12 @@ Gogs(Go Git Service) is a Self Hosted Git Service in the Go Programming Language
![Demo](http://gowalker.org/public/gogs_demo.gif)
##### 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.
[![Clone in Koding](http://learn.koding.com/btn/clone_d.png)][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.

View File

@@ -5,7 +5,7 @@ Gogs(Go Git Service) 是一个由 Go 语言编写的自助 Git 托管服务。
![Demo](http://gowalker.org/public/gogs_demo.gif)
##### 当前版本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) 获取完整的贡献者列表。
[![Clone in Koding](http://learn.koding.com/btn/clone_d.png)][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) 文件中。

View File

@@ -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
View 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
View 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
View 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
View 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
View 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
View 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/...
```

View File

@@ -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
View 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
View 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
View 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*

View 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/

View File

@@ -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
View 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
View 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
View 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"

View 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

View 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

View File

View 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

View File

View 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"]

View File

View 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"]

View File

View 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
View 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
View File

@@ -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
View 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

View File

@@ -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
}

View File

@@ -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
View File

@@ -0,0 +1,6 @@
package models
func Fix() error {
_, err := orm.Exec("alter table repository drop column num_releases")
return err
}

View File

@@ -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
View 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)
}

View File

@@ -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
View 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)
}

View File

@@ -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
View 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
}

View File

@@ -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
View 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
}

View File

@@ -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
View 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
}

View File

@@ -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
View 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)
}
}

View File

@@ -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
View 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
}

View File

@@ -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)
}

View 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
}
}
}

View File

@@ -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)
}

View 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)
}

View File

@@ -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)
}

View 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
View 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))
}
}

View 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)
// }

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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")

View File

@@ -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)

View File

@@ -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()
}

View File

@@ -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))
}

View File

@@ -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 ""
}

View File

@@ -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

File diff suppressed because it is too large Load Diff

17
modules/cron/cron.go Normal file
View 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
View 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
View 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
View 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
View 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")
}
}

View File

@@ -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)
}

View File

@@ -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
}

View File

@@ -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 {

View File

@@ -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
}
}

View 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\-\.,@?^=%&amp;:/~\+#]*[\w\-\@?^=%&amp;/~\+#])?`)
)
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"
)

View File

@@ -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.

View File

@@ -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)

View File

@@ -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)))

View File

@@ -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
View 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()
}

View 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
}

View 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
View 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
View 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
}

View 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

File diff suppressed because one or more lines are too long

790
public/css/datepicker3.css Normal file
View 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;
}

View File

@@ -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

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

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