Compare commits

..

228 Commits

Author SHA1 Message Date
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
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
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
137 changed files with 7118 additions and 2112 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"
}
}

1
.gitignore vendored
View File

@@ -33,3 +33,4 @@ _testmain.go
*.exe~
gogs
__pycache__
*.pem

View File

@@ -1,25 +1,25 @@
[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 =
github.com/Unknwon/com =
github.com/Unknwon/goconfig =
github.com/codegangsta/cli =
github.com/go-martini/martini =
github.com/go-sql-driver/mysql =
github.com/go-xorm/xorm =
github.com/gogits/cache =
github.com/gogits/gfm =
github.com/gogits/git =
github.com/gogits/logs =
github.com/gogits/oauth2 =
github.com/gogits/session =
github.com/lib/pq =
github.com/nfnt/resize =
github.com/qiniu/log =
github.com/robfig/cron =
[res]
include=templates|public|conf
include = templates|public|conf

View File

@@ -2,7 +2,7 @@
> 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.**
**This document is pre^2 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.

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.3.0 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,7 +24,7 @@ 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 [Wiki](https://github.com/gogits/gogs/wiki) 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).
@@ -29,37 +32,42 @@ More importantly, Gogs only needs one binary to setup your own project hosting o
## Features
- Activity timeline
- SSH/HTTPS(Clone only) protocol support.
- Register/delete account.
- Create/delete/watch public repository.
- User profile page.
- Repository viewer.
- SSH/HTTP(S) protocol support.
- Register/delete/rename account.
- Create/migrate/mirror/delete/watch/rename/transfer public/private repository.
- Repository viewer/release/issue tracker.
- Gravatar and cache support.
- Mail service(register, issue).
- Administration panel.
- Supports MySQL, PostgreSQL and SQLite3(binary release only).
- Supports MySQL, PostgreSQL and SQLite3.
- Social account login(GitHub, Google, QQ, Weibo)
## Installation
Make sure you install [Prerequirements](https://github.com/gogits/gogs/wiki/Prerequirements) first.
There are two ways to install Gogs:
There are 3 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 binary](https://github.com/gogits/gogs/wiki/Install-from-binary): **STRONGLY RECOMMENDED**
- [Install from source](https://github.com/gogits/gogs/wiki/Install-from-source)
- [Ship with Docker](https://github.com/gogits/gogs/tree/master/dockerfiles)
## 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/) 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.
This project was launched by [Unknown](https://github.com/Unknwon) and [lunny](https://github.com/lunny); [fuxiaohei](https://github.com/fuxiaohei), [slene](https://github.com/slene) and [codeskyblue](https://github.com/codeskyblue) joined the team soon after. 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

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.3.0 Alpha
## 开发目的
@@ -15,7 +15,7 @@ Gogs 完全使用 Go 语言来实现对 Git 数据的操作,实现 **零** 依
## 项目概览
- 有关项目设计、已知问题变更日志和路线图,请通过 [Wiki](https://github.com/gogits/gogs/wiki) 查看。
- 有关项目设计、已知问题变更日志,请通过 [Wiki](https://github.com/gogits/gogs/wiki) 查看。
- 您可以到 [Trello Board](https://trello.com/b/uxAoeLUl/gogs-go-git-service) 跟随开发团队的脚步。
- 想要先睹为快?通过 [在线体验](http://try.gogits.org/Unknown/gogs) 或查看 **安装部署 -> 二进制安装** 小节。
- 使用过程中遇到问题?尝试从 [故障排查](https://github.com/gogits/gogs/wiki/Troubleshooting) 页面获取帮助。
@@ -23,37 +23,42 @@ Gogs 完全使用 Go 语言来实现对 Git 数据的操作,实现 **零** 依
## 功能特性
- 活动时间线
- SSH/HTTPS仅限 Clone 协议支持
- 注册/删除用户
- 创建/删除/关注公开仓库
- 用户个人信息页面
- 仓库浏览器
- SSH/HTTP(S) 协议支持
- 注册/删除/重命名用户
- 创建/迁移/镜像/删除/关注/重命名/转移 公开/私有 仓库
- 仓库 浏览器/发布/缺陷追踪
- Gravatar 以及缓存支持
- 邮件服务注册、Issue
- 管理员面板
- 支持 MySQL、PostgreSQL 以及 SQLite3(仅限二进制版本)
- 支持 MySQL、PostgreSQL 以及 SQLite3 数据库
- 社交帐号登录GitHub、Google、QQ、微博
## 安装部署
在安装 Gogs 之前,您需要先安装 [基本环境](https://github.com/gogits/gogs/wiki/Prerequirements)。
然后,您可以通过以下种方式来安装 Gogs
然后,您可以通过以下 3 种方式来安装 Gogs
- [二进制安装](https://github.com/gogits/gogs/wiki/Install-from-binary): **强烈推荐** 适合体验者和实际部署
- [二进制安装](https://github.com/gogits/gogs/wiki/Install-from-binary): **强烈推荐**
- [源码安装](https://github.com/gogits/gogs/wiki/Install-from-source)
- [采用 Docker 部署](https://github.com/gogits/gogs/tree/master/dockerfiles)
## 特别鸣谢
- 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/) 设计的 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) 获取完整的贡献者列表。
本项目最初由 [Unknown](https://github.com/Unknwon) 和 [lunny](https://github.com/lunny) 发起,随后 [fuxiaohei](https://github.com/fuxiaohei)[slene](https://github.com/slene) 以及 [codeskyblue](https://github.com/codeskyblue) 加入到开发团队。您可以通过查看 [贡献者页面](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
## 授权许可

View File

@@ -12,7 +12,7 @@
"models": "",
"others": [
"modules",
"$GOPATH/src/github.com/gogits/binding",
"$GOPATH/src/github.com/gogits/logs",
"$GOPATH/src/github.com/gogits/git",
"$GOPATH/src/github.com/gogits/gfm"
]

View File

@@ -8,17 +8,24 @@ RUN_MODE = dev
[repository]
ROOT =
LANG_IGNS = Google Go|C|C++|Python|Ruby|C Sharp
SCRIPT_TYPE = bash
LANG_IGNS = Google Go|C|C++|Python|Ruby|C Sharp|Java|Objective-C|Android
LICENSES = Apache v2 License|GPL v2|MIT License|Affero GPL|Artistic License 2.0|BSD (3-Clause) License
[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
; 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
[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
@@ -62,6 +69,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 +77,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

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/

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"

59
dockerfiles/README.md Normal file
View File

@@ -0,0 +1,59 @@
### Gogs Install With Docker
#### Gogs With MySQL
Deply 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 confis:
```
MYSQL_PASSWORD="YOUR_MYSQL_PASSWORD"
MYSQL_RUN_NAME="YOUR_MYSQL_RUN_NAME"
HOST_PORT="YOUR_HOST_PORT"
```
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 gogs_mysql:db -p 3333:3000 gogs/gogits
```
Just follow the message, run:
```
docker run -i -t --link gogs_mysql:db -p 3333:3000 gogs/gogits
```
Now we have gogs running! Open the browser and navigate to:
```
http://YOUR_HOST_IP:YOUR_HOST_PORT
```
Let's 'gogs'!
#### Gogs With PostgreSQL
Installing Gogs with PostgreSQL is nearly the same with installing it with MySQL. What you do is just change the DB_TYPE in build.sh to 'postgres'.
#### Gogs, MySQL With Redis
#### Gogs, MySQL With Memcached
#### Gogs, PostgreSQL With Redis
#### Gogs, PostgreSQL With Memcached

29
dockerfiles/build.sh Executable file
View File

@@ -0,0 +1,29 @@
# Configs of the docker images, you might have specify your own configs here.
# type of database, support 'mysql' and 'postgres'
DB_TYPE="postgres"
DB_PASSWORD="YOUR_DB_PASSWORD"
DB_RUN_NAME="YOUR_DB_RUN_NAME"
HOST_PORT="YOUR_HOST_PORT"
# 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 database type in gogits image deploy.sh file.
sed -i "s/THE_DB_TYPE/$DB_TYPE/g" images/gogits/deploy.sh
# Build the database image
cd images/$DB_TYPE
docker build -t gogs/$DB_TYPE .
#
## Build the gogits image
cd ../gogits
docker build -t gogs/gogits .
#
## Run MySQL image with name
docker run -d --name $DB_RUN_NAME gogs/$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 run -i -t --link $DB_RUN_NAME:db -p $HOST_PORT:3000 gogs/gogits \033[0m"

29
dockerfiles/build.sh.bak Executable file
View File

@@ -0,0 +1,29 @@
# Configs of the docker images, you might have specify your own configs here.
# type of database, support 'mysql' and 'postgres'
DB_TYPE="postgres"
DB_PASSWORD="YOUR_DB_PASSWORD"
DB_RUN_NAME="YOUR_DB_RUN_NAME"
HOST_PORT="YOUR_HOST_PORT"
# 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 database type in gogits image deploy.sh file.
sed -i "s/THE_DB_TYPE/$DB_TYPE/g" images/gogits/deploy.sh
# Build the database image
cd images/$DB_TYPE
docker build -t gogs/$DB_TYPE .
#
## Build the gogits image
cd ../gogits
docker build -t gogs/gogits .
#
## Run MySQL image with name
docker run -d --name $DB_RUN_NAME gogs/$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 run -i -t --link $DB_RUN_NAME:db -p $HOST_PORT:3000 gogs/gogits \033[0m"

View File

@@ -0,0 +1,33 @@
FROM stackbrew/ubuntu:13.10
MAINTAINER Meaglith Ma <genedna@gmail.com> (@genedna)
ENV DEBIAN_FRONTEND noninteractive
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
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 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
# You may need a proxy, if github is very slow.
#RUN http_proxy=106.187.38.45:3128 go get -u github.com/gogits/gogs
RUN go get -u github.com/gogits/gogs
RUN cd $GOPATH/src/github.com/gogits/gogs && go build
# 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,35 @@
# 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
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 [ $DB_TYPE = "postgres" ]
then
# Add the postgres in gogs image.
apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys B97B0AFCAA1A47F044F244A07FCC7D46ACCC4CF8
echo "deb http://apt.postgresql.org/pub/repos/apt/ precise-pgdg main" > /etc/apt/sources.list.d/pgdg.list
apt-get update
apt-get -y -q install python-software-properties software-properties-common
apt-get -y -q install postgresql-9.3 postgresql-client-9.3 postgresql-contrib-9.3
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

View File

@@ -0,0 +1,37 @@
#FROM stackbrew/ubuntu:13.10
#FROM stackbrew/ubuntu
FROM stackbrew/ubuntu:saucy
MAINTAINER Meaglith Ma <genedna@gmail.com> (@genedna)
ENV DEBIAN_FRONTEND noninteractive
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_MYSQL_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,49 @@
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``.
RUN echo "deb http://apt.postgresql.org/pub/repos/apt/ precise-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

13
dockerfiles/run.sh Executable file
View File

@@ -0,0 +1,13 @@
# Configs
MYSQL_PASSWORD="kuajie8402"
MYSQL_RUN_NAME="gogs_mysql"
typeset -u MYSQL_ALIAS
MYSQL_ALIAS="db"
HOST_PORT="3000"
## Run MySQL image with name
docker run -d --name $MYSQL_RUN_NAME gogs/mysql
#
## Run gogits image and link it to the MySQL image
docker run --link $MYSQL_RUN_NAME:$MYSQL_ALIAS -p $HOST_PORT:3000 gogs/gogits

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.
@@ -14,12 +16,10 @@ import (
"github.com/gogits/gogs/modules/base"
)
// +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.3.0.0421 Alpha"
func init() {
base.AppVer = APP_VER

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,36 @@ func AddAccess(access *Access) error {
return err
}
// 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
}
// 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.
func HasAccess(userName, repoName string, mode int) (bool, error) {
return orm.Get(&Access{
Id: 0,
access := &Access{
UserName: strings.ToLower(userName),
RepoName: strings.ToLower(repoName),
Mode: mode,
})
}
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,8 +6,11 @@ package models
import (
"encoding/json"
"strings"
"time"
"github.com/gogits/git"
"github.com/gogits/gogs/modules/base"
"github.com/gogits/gogs/modules/log"
)
@@ -21,6 +24,8 @@ const (
OP_COMMIT_REPO
OP_CREATE_ISSUE
OP_PULL_REQUEST
OP_TRANSFER_REPO
OP_PUSH_TAG
)
// Action represents user operation type and other information to repository.,
@@ -66,7 +71,16 @@ 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)
// log.Trace("action.CommitRepoAction(start): %d/%s", userId, repoName)
opType := OP_COMMIT_REPO
// Check it's tag push or branch.
if strings.HasPrefix(refName, "refs/tags/") {
opType = OP_PUSH_TAG
commit = &base.PushCommits{}
}
refName = git.RefEndName(refName)
bs, err := json.Marshal(commit)
if err != nil {
@@ -75,7 +89,7 @@ func CommitRepoAction(userId int64, userName, actEmail string,
}
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 {
OpType: opType, Content: string(bs), RepoId: repoId, RepoName: repoName, RefName: refName}); err != nil {
log.Error("action.CommitRepoAction(notify watchers): %d/%s", userId, repoName)
return err
}
@@ -108,6 +122,18 @@ 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}); 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)

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

212
models/git_diff.go Normal file
View File

@@ -0,0 +1,212 @@
// 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
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(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:],
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() {
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
cmd.Run()
wr.Close()
}()
defer rd.Close()
return ParsePatch(rd)
}

View File

@@ -8,26 +8,35 @@ 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"
)
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))
}
func LoadModelsConfig() {
DbCfg.Type = base.Cfg.MustValue("database", "DB_TYPE")
if DbCfg.Type == "sqlite3" {
@@ -47,20 +56,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 {
host = fields[0]
}
if len(fields) > 1 {
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 +89,49 @@ 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 {
host = fields[0]
}
if len(fields) > 1 {
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.
execDir, _ := base.ExecDir()
logPath := execDir + "/log/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,10 @@ 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 int64
}
}
@@ -118,5 +153,10 @@ 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))
return
}

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
}

76
models/oauth2.go Normal file
View File

@@ -0,0 +1,76 @@
// 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
}

View File

@@ -77,8 +77,8 @@ func init() {
// PublicKey represents a SSH key of user.
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"`

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(repoPath string, rel *Release, gitRepo *git.Repository) error {
isExist, err := IsReleaseExist(rel.RepoId, rel.TagName)
if err != nil {
return err
} else if isExist {
return ErrReleaseAlreadyExist
}
if !git.IsTagExist(repoPath, rel.TagName) {
_, stderr, err := com.ExecCmdDir(repoPath, "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

@@ -30,7 +30,8 @@ var (
ErrRepoNotExist = errors.New("Repository does not exist")
ErrRepoFileNotExist = errors.New("Target Repo file does not exist")
ErrRepoNameIllegal = errors.New("Repository name contains illegal characters")
ErrRepoFileNotLoaded = fmt.Errorf("repo file not loaded")
ErrRepoFileNotLoaded = errors.New("repo file not loaded")
ErrMirrorNotExist = errors.New("Mirror does not exist")
)
var (
@@ -65,6 +66,7 @@ func NewRepoContext() {
type Repository struct {
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"`
@@ -76,8 +78,12 @@ type Repository struct {
NumIssues int
NumClosedIssues int
NumOpenIssues 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"`
}
@@ -116,13 +122,133 @@ 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(base.RepoRootPath, m.RepoName+".git")
_, stderr, err := com.ExecCmdDir(repoPath, "git", "remote", "update")
if err != nil {
return err
} else if strings.Contains(stderr, "fatal:") {
return errors.New(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 err
} else if strings.Contains(stderr, "fatal:") {
return errors.New(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, err
} else if strings.Contains(stderr, "fatal:") {
return repo, errors.New("git clone: " + stderr)
}
// Pull data from source.
_, stderr, err = com.ExecCmdDir(tmpDir, "git", "pull", url)
if err != nil {
return repo, err
} else if strings.Contains(stderr, "fatal:") {
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, err
} else if strings.Contains(stderr, "fatal:") {
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 {
@@ -130,82 +256,95 @@ func CreateRepository(user *User, repoName, desc, repoLang, license string, priv
}
repo := &Repository{
OwnerId: user.Id,
Name: repoName,
LowerName: strings.ToLower(repoName),
Description: desc,
IsPrivate: private,
IsBare: repoLang == "" && license == "" && !initReadme,
OwnerId: user.Id,
Name: name,
LowerName: strings.ToLower(name),
Description: desc,
IsPrivate: private,
IsBare: lang == "" && license == "" && !initReadme,
DefaultBranch: "master",
}
repoPath := RepoPath(user.Name, repo.Name)
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()
sess := orm.NewSession()
defer sess.Close()
sess.Begin()
if _, err = session.Insert(repo); err != nil {
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 !repo.IsPrivate {
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)
}
// 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
}
@@ -226,24 +365,21 @@ 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
}
if len(stderr) > 0 {
log.Trace("stderr(1): %s", stderr)
} else if strings.Contains(stderr, "fatal:") {
return errors.New("git add: " + 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)
} else if strings.Contains(stderr, "fatal:") {
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)
} else if strings.Contains(stderr, "fatal:") {
return errors.New("git push: " + stderr)
}
return nil
}
@@ -259,6 +395,13 @@ func createHookUpdate(hookPath, content string) error {
return err
}
// SetRepoEnvs sets environment variables for command update.
func SetRepoEnvs(userId int64, userName, repoName string) {
os.Setenv("userId", base.ToStr(userId))
os.Setenv("userName", userName)
os.Setenv("repoName", repoName)
}
// 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)
@@ -270,7 +413,7 @@ func initRepository(f string, user *User, repo *Repository, initReadme bool, rep
// hook/post-update
if err := createHookUpdate(filepath.Join(repoPath, "hooks", "update"),
fmt.Sprintf("#!/usr/bin/env bash\n%s update $1 $2 $3\n",
fmt.Sprintf("#!/usr/bin/env %s\n%s update $1 $2 $3\n", base.ScriptType,
strings.Replace(appPath, "\\", "/", -1))); err != nil {
return err
}
@@ -291,8 +434,11 @@ func initRepository(f string, user *User, repo *Repository, initReadme bool, rep
tmpDir := filepath.Join(os.TempDir(), fmt.Sprintf("%d", time.Now().Nanosecond()))
os.MkdirAll(tmpDir, os.ModePerm)
if _, _, err := com.ExecCmd("git", "clone", repoPath, tmpDir); err != nil {
_, stderr, err := com.ExecCmd("git", "clone", repoPath, tmpDir)
if err != nil {
return err
} else if strings.Contains(stderr, "fatal:") {
return errors.New("git clone: " + stderr)
}
// README
@@ -309,7 +455,7 @@ func initRepository(f string, user *User, repo *Repository, initReadme bool, rep
if repoLang != "" {
filePath := "conf/gitignore/" + repoLang
if com.IsFile(filePath) {
if _, err := com.Copy(filePath,
if err := com.Copy(filePath,
filepath.Join(tmpDir, fileName["gitign"])); err != nil {
return err
}
@@ -320,7 +466,7 @@ func initRepository(f string, user *User, repo *Repository, initReadme bool, rep
if license != "" {
filePath := "conf/license/" + license
if com.IsFile(filePath) {
if _, err := com.Copy(filePath,
if err := com.Copy(filePath,
filepath.Join(tmpDir, fileName["license"])); err != nil {
return err
}
@@ -331,6 +477,8 @@ func initRepository(f string, user *User, repo *Repository, initReadme bool, rep
return nil
}
SetRepoEnvs(user.Id, user.Name, repo.Name)
// Apply changes and commit.
return initRepoCommit(tmpDir, user.NewGitSig())
}
@@ -364,18 +512,120 @@ func GetRepos(num, offset int) ([]UserRepo, error) {
return urepos, 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 +640,39 @@ 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
}
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 {
@@ -451,12 +711,24 @@ func GetRepositoryById(id int64) (*Repository, error) {
}
// GetRepositories returns the list of repositories of given user.
func GetRepositories(user *User) ([]Repository, error) {
func GetRepositories(user *User, private bool) ([]Repository, error) {
repos := make([]Repository, 0, 10)
err := orm.Desc("updated").Find(&repos, &Repository{OwnerId: user.Id})
sess := orm.Desc("updated")
if !private {
sess.Where("is_private=?", false)
}
err := sess.Find(&repos, &Repository{OwnerId: user.Id})
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})
}
@@ -513,6 +785,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())

84
models/update.go Normal file
View File

@@ -0,0 +1,84 @@
package models
import (
"container/list"
"os/exec"
"strings"
"github.com/gogits/git"
"github.com/gogits/gogs/modules/base"
qlog "github.com/qiniu/log"
)
func Update(refName, oldCommitId, newCommitId, userName, 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(userName, repoName)
gitUpdate := exec.Command("git", "update-server-info")
gitUpdate.Dir = f
gitUpdate.Run()
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)
}
repos, err := GetRepositoryByName(userId, 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, userName, actEmail,
repos.Id, repoName, refName, &base.PushCommits{l.Len(), commits}); err != nil {
qlog.Fatalf("runUpdate.models.CommitRepoAction: %v", err)
}
}

View File

@@ -5,6 +5,7 @@
package models
import (
"crypto/sha256"
"encoding/hex"
"errors"
"fmt"
@@ -13,8 +14,6 @@ import (
"strings"
"time"
"github.com/dchest/scrypt"
"github.com/gogits/git"
"github.com/gogits/gogs/modules/base"
@@ -62,6 +61,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"`
}
@@ -76,7 +76,7 @@ func (user *User) AvatarLink() string {
if base.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 +89,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,11 +104,17 @@ 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})
}
@@ -142,9 +147,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 {
@@ -203,8 +208,64 @@ func VerifyUserActiveCode(code string) (user *User) {
return nil
}
// 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
}
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, 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].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(user *User) (err error) {
user.LowerName = strings.ToLower(user.Name)
if len(user.Location) > 255 {
user.Location = user.Location[:255]
}
@@ -228,11 +289,31 @@ 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)
if err = orm.Find(&keys, &PublicKey{OwnerId: user.Id}); err != nil {
@@ -250,7 +331,6 @@ func DeleteUser(user *User) error {
}
_, err = orm.Delete(user)
// TODO: delete and update follower information.
return err
}
@@ -300,20 +380,50 @@ func GetUserByName(name string) (*User, error) {
return user, nil
}
// 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
}
// 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 {
return nil, ErrUserNotExist
}
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
}
user := User{LowerName: strings.ToLower(name)}
has, err := orm.Get(&user)
if err != nil {
return nil, err
} else if !has {
err = ErrUserNotExist
return nil, ErrUserNotExist
}
return &user, err
newUser := &User{Passwd: passwd, Salt: user.Salt}
newUser.EncodePasswd()
if user.Passwd != newUser.Passwd {
return nil, ErrUserNotExist
}
return &user, nil
}
// Follow is connection request for receiving user notifycation.

View File

@@ -10,8 +10,6 @@ import (
"github.com/go-martini/martini"
"github.com/gogits/binding"
"github.com/gogits/gogs/modules/base"
"github.com/gogits/gogs/modules/log"
)
@@ -35,7 +33,7 @@ func (f *AdminEditUserForm) Name(field string) string {
return names[field]
}
func (f *AdminEditUserForm) Validate(errors *binding.Errors, req *http.Request, context martini.Context) {
func (f *AdminEditUserForm) Validate(errors *base.BindingErrors, req *http.Request, context martini.Context) {
if req.Method == "GET" || errors.Count() == 0 {
return
}

View File

@@ -11,8 +11,6 @@ import (
"github.com/go-martini/martini"
"github.com/gogits/binding"
"github.com/gogits/gogs/modules/base"
"github.com/gogits/gogs/modules/log"
)
@@ -39,7 +37,7 @@ func (f *RegisterForm) Name(field string) string {
return names[field]
}
func (f *RegisterForm) Validate(errors *binding.Errors, req *http.Request, context martini.Context) {
func (f *RegisterForm) Validate(errors *base.BindingErrors, req *http.Request, context martini.Context) {
if req.Method == "GET" || errors.Count() == 0 {
return
}
@@ -72,7 +70,7 @@ func (f *LogInForm) Name(field string) string {
return names[field]
}
func (f *LogInForm) Validate(errors *binding.Errors, req *http.Request, context martini.Context) {
func (f *LogInForm) Validate(errors *base.BindingErrors, req *http.Request, context martini.Context) {
if req.Method == "GET" || errors.Count() == 0 {
return
}
@@ -100,7 +98,7 @@ func getMinMaxSize(field reflect.StructField) string {
return ""
}
func validate(errors *binding.Errors, data base.TmplData, form Form) {
func validate(errors *base.BindingErrors, data base.TmplData, form Form) {
typ := reflect.TypeOf(form)
val := reflect.ValueOf(form)
@@ -121,16 +119,18 @@ func validate(errors *binding.Errors, data base.TmplData, form Form) {
if err, ok := errors.Fields[field.Name]; ok {
data["Err_"+field.Name] = true
switch err {
case binding.RequireError:
case base.BindingRequireError:
data["ErrorMsg"] = form.Name(field.Name) + " cannot be empty"
case binding.AlphaDashError:
case base.BindingAlphaDashError:
data["ErrorMsg"] = form.Name(field.Name) + " must be valid alpha or numeric or dash(-_) characters"
case binding.MinSizeError:
case base.BindingMinSizeError:
data["ErrorMsg"] = form.Name(field.Name) + " must contain at least " + getMinMaxSize(field) + " characters"
case binding.MaxSizeError:
case base.BindingMaxSizeError:
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 base.BindingEmailError:
data["ErrorMsg"] = form.Name(field.Name) + " is not a valid e-mail address"
case base.BindingUrlError:
data["ErrorMsg"] = form.Name(field.Name) + " is not a valid URL"
default:
data["ErrorMsg"] = "Unknown error: " + err
}
@@ -194,7 +194,7 @@ func (f *InstallForm) Name(field string) string {
return names[field]
}
func (f *InstallForm) Validate(errors *binding.Errors, req *http.Request, context martini.Context) {
func (f *InstallForm) Validate(errors *base.BindingErrors, req *http.Request, context martini.Context) {
if req.Method == "GET" || errors.Count() == 0 {
return
}

View File

@@ -10,8 +10,6 @@ import (
"github.com/go-martini/martini"
"github.com/gogits/binding"
"github.com/gogits/gogs/modules/base"
"github.com/gogits/gogs/modules/log"
)
@@ -31,7 +29,7 @@ func (f *CreateIssueForm) Name(field string) string {
return names[field]
}
func (f *CreateIssueForm) Validate(errors *binding.Errors, req *http.Request, context martini.Context) {
func (f *CreateIssueForm) Validate(errors *base.BindingErrors, req *http.Request, context martini.Context) {
if req.Method == "GET" || errors.Count() == 0 {
return
}

50
modules/auth/release.go Normal file
View File

@@ -0,0 +1,50 @@
// 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/log"
)
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 *base.BindingErrors, 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("NewReleaseForm.Validate: %v", err)
}
return
}
validate(errors, data, f)
}

View File

@@ -10,19 +10,17 @@ import (
"github.com/go-martini/martini"
"github.com/gogits/binding"
"github.com/gogits/gogs/modules/base"
"github.com/gogits/gogs/modules/log"
)
type CreateRepoForm struct {
RepoName string `form:"repo" binding:"Required;AlphaDash"`
Visibility string `form:"visibility"`
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 {
@@ -33,7 +31,7 @@ func (f *CreateRepoForm) Name(field string) string {
return names[field]
}
func (f *CreateRepoForm) Validate(errors *binding.Errors, req *http.Request, context martini.Context) {
func (f *CreateRepoForm) Validate(errors *base.BindingErrors, req *http.Request, context martini.Context) {
if req.Method == "GET" || errors.Count() == 0 {
return
}
@@ -51,3 +49,41 @@ func (f *CreateRepoForm) Validate(errors *binding.Errors, req *http.Request, con
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"`
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 *base.BindingErrors, 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("MigrateRepoForm.Validate: %v", err)
}
return
}
validate(errors, data, f)
}

View File

@@ -11,8 +11,6 @@ import (
"github.com/go-martini/martini"
"github.com/gogits/binding"
"github.com/gogits/gogs/modules/base"
"github.com/gogits/gogs/modules/log"
)
@@ -30,7 +28,7 @@ func (f *AddSSHKeyForm) Name(field string) string {
return names[field]
}
func (f *AddSSHKeyForm) Validate(errors *binding.Errors, req *http.Request, context martini.Context) {
func (f *AddSSHKeyForm) Validate(errors *base.BindingErrors, req *http.Request, context martini.Context) {
data := context.Get(reflect.TypeOf(base.TmplData{})).Interface().(base.TmplData)
AssignForm(f, data)

View File

@@ -10,7 +10,6 @@ import (
"github.com/go-martini/martini"
"github.com/gogits/binding"
"github.com/gogits/session"
"github.com/gogits/gogs/models"
@@ -75,6 +74,7 @@ type FeedsForm struct {
}
type UpdateProfileForm struct {
UserName string `form:"username" binding:"Required;AlphaDash;MaxSize(30)"`
Email string `form:"email" binding:"Required;Email;MaxSize(50)"`
Website string `form:"website" binding:"MaxSize(50)"`
Location string `form:"location" binding:"MaxSize(50)"`
@@ -83,6 +83,7 @@ type UpdateProfileForm struct {
func (f *UpdateProfileForm) Name(field string) string {
names := map[string]string{
"UserName": "Username",
"Email": "E-mail address",
"Website": "Website",
"Location": "Location",
@@ -91,7 +92,7 @@ func (f *UpdateProfileForm) Name(field string) string {
return names[field]
}
func (f *UpdateProfileForm) Validate(errors *binding.Errors, req *http.Request, context martini.Context) {
func (f *UpdateProfileForm) Validate(errors *base.BindingErrors, req *http.Request, context martini.Context) {
if req.Method == "GET" || errors.Count() == 0 {
return
}
@@ -124,7 +125,7 @@ func (f *UpdatePasswdForm) Name(field string) string {
return names[field]
}
func (f *UpdatePasswdForm) Validate(errors *binding.Errors, req *http.Request, context martini.Context) {
func (f *UpdatePasswdForm) Validate(errors *base.BindingErrors, req *http.Request, context martini.Context) {
if req.Method == "GET" || errors.Count() == 0 {
return
}

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

@@ -8,3 +8,51 @@ type (
// Type TmplData represents data in the templates.
TmplData map[string]interface{}
)
// __________.__ .___.__
// \______ \__| ____ __| _/|__| ____ ____
// | | _/ |/ \ / __ | | |/ \ / ___\
// | | \ | | \/ /_/ | | | | \/ /_/ >
// |______ /__|___| /\____ | |__|___| /\___ /
// \/ \/ \/ \//_____/
// Errors represents the contract of the response body when the
// binding step fails before getting to the application.
type BindingErrors 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 BindingErrors) Count() int {
return len(err.Overall) + len(err.Fields)
}
func (this *BindingErrors) Combine(other BindingErrors) {
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"
BindingMinSizeError string = "MinSize"
BindingMaxSizeError string = "MaxSize"
BindingEmailError string = "Email"
BindingUrlError string = "Url"
BindingDeserializationError string = "DeserializationError"
BindingIntegerTypeError string = "IntegerTypeError"
BindingBooleanTypeError string = "BooleanTypeError"
BindingFloatTypeError string = "FloatTypeError"
)
var GoGetMetas = make(map[string]bool)

View File

@@ -0,0 +1,11 @@
// +build memcache
package base
import (
_ "github.com/gogits/cache/memcache"
)
func init() {
EnableMemcache = true
}

View File

@@ -0,0 +1,11 @@
// +build redis
package base
import (
_ "github.com/gogits/cache/redis"
)
func init() {
EnableRedis = true
}

View File

@@ -14,6 +14,7 @@ import (
"github.com/Unknwon/com"
"github.com/Unknwon/goconfig"
qlog "github.com/qiniu/log"
"github.com/gogits/cache"
"github.com/gogits/session"
@@ -21,22 +22,38 @@ import (
"github.com/gogits/gogs/modules/log"
)
// Mailer represents a mail service.
// Mailer represents mail service.
type Mailer struct {
Name string
Host 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 (
AppVer string
AppName string
AppLogo string
AppUrl string
Domain string
SecretKey string
RunUser string
AppVer string
AppName string
AppLogo string
AppUrl string
IsProdMode bool
Domain string
SecretKey string
RunUser string
RepoRootPath string
ScriptType string
InstallLock bool
@@ -44,8 +61,9 @@ var (
CookieUserName string
CookieRememberName string
Cfg *goconfig.ConfigFile
MailService *Mailer
Cfg *goconfig.ConfigFile
MailService *Mailer
OauthService *Oauther
LogMode string
LogConfig string
@@ -59,6 +77,9 @@ var (
SessionManager *session.Manager
PictureService string
EnableRedis bool
EnableMemcache bool
)
var Service struct {
@@ -105,16 +126,14 @@ func newLogService() {
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)
qlog.Fatalf("Unknown log mode: %s\n", LogMode)
}
// 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)
qlog.Fatalf("Unknown log level: %s\n", levelName)
}
// Generate log configuration.
@@ -151,12 +170,19 @@ func newLogService() {
Cfg.MustValue(modeSec, "CONN"))
}
log.Info("%s %s", AppName, AppVer)
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")
if EnableRedis {
log.Info("Redis Enabled")
}
if EnableMemcache {
log.Info("Memcache Enabled")
}
switch CacheAdapter {
case "memory":
@@ -164,16 +190,14 @@ func newCacheService() {
case "redis", "memcache":
CacheConfig = fmt.Sprintf(`{"conn":"%s"}`, Cfg.MustValue("cache", "HOST"))
default:
fmt.Printf("Unknown cache adapter: %s\n", CacheAdapter)
os.Exit(2)
qlog.Fatalf("Unknown cache adapter: %s\n", CacheAdapter)
}
var err error
Cache, err = cache.NewCache(CacheAdapter, CacheConfig)
if err != nil {
fmt.Printf("Init cache system failed, adapter: %s, config: %s, %v\n",
qlog.Fatalf("Init cache system failed, adapter: %s, config: %s, %v\n",
CacheAdapter, CacheConfig, err)
os.Exit(2)
}
log.Info("Cache Service Enabled")
@@ -199,9 +223,8 @@ func newSessionService() {
var err error
SessionManager, err = session.NewManager(SessionProvider, *SessionConfig)
if err != nil {
fmt.Printf("Init session system failed, provider: %s, %v\n",
qlog.Fatalf("Init session system failed, provider: %s, %v\n",
SessionProvider, err)
os.Exit(2)
}
log.Info("Session Service Enabled")
@@ -209,15 +232,17 @@ func newSessionService() {
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")
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"),
}
log.Info("Mail Service Enabled")
}
func newRegisterMailService() {
@@ -246,23 +271,20 @@ func NewConfigContext() {
//var err error
workDir, err := ExecDir()
if err != nil {
fmt.Printf("Fail to get work directory: %s\n", err)
os.Exit(2)
qlog.Fatalf("Fail to get work directory: %s\n", err)
}
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)
qlog.Fatalf("Cannot load config file(%s): %v\n", cfgPath, err)
}
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)
qlog.Fatalf("Cannot load config file(%s): %v\n", cfgPath, err)
}
}
@@ -275,14 +297,13 @@ func NewConfigContext() {
InstallLock = Cfg.MustBool("security", "INSTALL_LOCK", false)
RunUser = Cfg.MustValue("", "RUN_USER")
curUser := os.Getenv("USERNAME")
curUser := os.Getenv("USER")
if len(curUser) == 0 {
curUser = os.Getenv("USER")
curUser = os.Getenv("USERNAME")
}
// 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)
qlog.Fatalf("Expect user(%s) but current user is: %s\n", RunUser, curUser)
}
LogInRememberDays = Cfg.MustInt("security", "LOGIN_REMEMBER_DAYS")
@@ -294,17 +315,16 @@ func NewConfigContext() {
// 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)
qlog.Fatalf("Fail to get home directory): %v\n", err)
}
RepoRootPath = Cfg.MustValue("repository", "ROOT", filepath.Join(homeDir, "git/gogs-repositories"))
RepoRootPath = Cfg.MustValue("repository", "ROOT", filepath.Join(homeDir, "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)
qlog.Fatalf("Fail to create RepoRootPath(%s): %v\n", RepoRootPath, err)
}
ScriptType = Cfg.MustValue("repository", "SCRIPT_TYPE", "bash")
}
func NewServices() {
func NewBaseServices() {
newService()
newLogService()
newCacheService()

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,58 @@ func (options *CustomRender) Link(out *bytes.Buffer, link []byte, title []byte,
options.Renderer.Link(out, link, title, content)
}
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 {
ms := MentionPattern.FindAll(rawBytes, -1)
for _, m := range ms {
rawBytes = bytes.Replace(rawBytes, m,
[]byte(fmt.Sprintf(`<a href="/user/%s">%s</a>`, m[1:], m)), -1)
}
ms = commitPattern.FindAll(rawBytes, -1)
for _, m := range ms {
m = bytes.TrimSpace(m)
i := strings.Index(string(m), "commit/")
j := strings.Index(string(m), "#")
if j == -1 {
j = len(m)
}
rawBytes = bytes.Replace(rawBytes, m, []byte(fmt.Sprintf(
` <code><a href="%s">%s</a></code>`, m, ShortSha(string(m[i+7:j])))), -1)
}
ms = issueFullPattern.FindAll(rawBytes, -1)
for _, m := range ms {
m = bytes.TrimSpace(m)
i := strings.Index(string(m), "issues/")
j := strings.Index(string(m), "#")
if j == -1 {
j = len(m)
}
rawBytes = bytes.Replace(rawBytes, m, []byte(fmt.Sprintf(
` <a href="%s">#%s</a>`, m, ShortSha(string(m[i+7:j])))), -1)
}
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 RenderMarkdown(rawBytes []byte, urlPrefix string) []byte {
body := RenderSpecialLink(rawBytes, urlPrefix)
// fmt.Println(string(body))
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 +162,11 @@ 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)
// fmt.Println(string(body))
return body
}
func RenderMarkdownString(raw, urlPrefix string) string {
return string(RenderMarkdown([]byte(raw), urlPrefix))
}

View File

@@ -5,7 +5,9 @@
package base
import (
"bytes"
"container/list"
"encoding/json"
"fmt"
"html/template"
"strings"
@@ -54,6 +56,9 @@ var TemplateFuncs template.FuncMap = map[string]interface{}{
"AppDomain": func() string {
return Domain
},
"IsProdMode": func() bool {
return IsProdMode
},
"LoadTimes": func(startTime time.Time) string {
return fmt.Sprint(time.Since(startTime).Nanoseconds()/1e6) + "ms"
},
@@ -62,11 +67,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 +92,128 @@ var TemplateFuncs template.FuncMap = map[string]interface{}{
"DiffTypeToStr": DiffTypeToStr,
"DiffLineTypeToStr": DiffLineTypeToStr,
"ShortSha": ShortSha,
"Oauth2Icon": Oauth2Icon,
}
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, 9: // Commit repository.
return "arrow-circle-o-right"
case 6: // Create issue.
return "exclamation-circle"
case 8: // Transfer repository.
return "share"
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>`
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">%s</a> at <a href="/%s">%s</a>`
)
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])
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)
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 ""
}

View File

@@ -5,13 +5,13 @@
package base
import (
"bytes"
"crypto/hmac"
"crypto/md5"
"crypto/rand"
"crypto/sha1"
"encoding/hex"
"encoding/json"
"fmt"
"hash"
"math"
"strconv"
"strings"
@@ -40,6 +40,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 {
@@ -105,7 +143,7 @@ func AvatarLink(email string) string {
if 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 +284,6 @@ func TimeSince(then time.Time) string {
default:
return fmt.Sprintf("%d years %s", diff/Year, lbl)
}
return then.String()
}
const (
@@ -474,101 +511,3 @@ func (a argInt) Get(i int, args ...int) (r int) {
}
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"
}

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

View File

@@ -15,14 +15,13 @@ var (
)
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.SetLogFuncCallDepth(3)
logger.SetLogger(mode, config)
}

View File

@@ -86,37 +86,77 @@ func SendActiveMail(r *middleware.Render, user *models.User) {
}
msg := NewMailMessage([]string{user.Email}, subject, body)
msg.Info = fmt.Sprintf("UID: %d, send email verify mail", user.Id)
msg.Info = fmt.Sprintf("UID: %d, send active mail", user.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, user *models.User) {
code := CreateUserActiveCode(user, nil)
subject := "Reset your password"
data := GetMailTmplData(user)
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
}
msg := NewMailMessage([]string{user.Email}, subject, body)
msg.Info = fmt.Sprintf("UID: %d, send reset password email", user.Id)
SendAsync(&msg)
}
// SendIssueNotifyMail sends mail notification of all watchers of repository.
func SendIssueNotifyMail(user, owner *models.User, repo *models.Repository, issue *models.Issue) ([]string, error) {
watches, err := models.GetWatches(repo.Id)
if err != nil {
return nil, errors.New("mail.NotifyWatchers(get watches): " + err.Error())
}
tos := make([]string, 0, len(watches))
for i := range watches {
uid := watches[i].UserId
if userId == uid {
if user.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(get user): " + err.Error())
}
tos = append(tos, u.Email)
}
if len(tos) == 0 {
return tos, nil
}
subject := fmt.Sprintf("[%s] %s", repo.Name, issue.Name)
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),
base.AppUrl, owner.Name, repo.Name, issue.Index)
msg := NewMailMessageFrom(tos, user.Name, 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(user, 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)
issueLink := fmt.Sprintf("%s%s/%s/issues/%d", base.AppUrl, owner.Name, repo.Name, issue.Index)
body := fmt.Sprintf(`%s mentioned you.`, user.Name)
subject := fmt.Sprintf("[%s] %s", repo.Name, issue.Name)
content := fmt.Sprintf("%s<br>-<br> <a href=\"%s\">View it on Gogs</a>.", body, issueLink)
msg := NewMailMessageFrom(tos, user.Name, subject, content)
msg.Info = fmt.Sprintf("Subject: %s, send issue mention emails", subject)
SendAsync(&msg)
return nil
}

View File

@@ -47,7 +47,7 @@ func Toggle(options *ToggleOptions) martini.Handler {
return
} else if !ctx.User.IsActive && base.Service.RegisterEmailConfirm {
ctx.Data["Title"] = "Activate Your Account"
ctx.HTML(200, "user/active")
ctx.HTML(200, "user/activate")
return
}
}

View File

@@ -0,0 +1,426 @@
// 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 middleware
import (
"encoding/json"
"fmt"
"io"
"net/http"
"reflect"
"regexp"
"strconv"
"strings"
"unicode/utf8"
"github.com/go-martini/martini"
"github.com/gogits/gogs/modules/base"
)
/*
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[base.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[base.BindingDeserializationError] = err.Error()
} else {
form, parseErr := multipartReader.ReadForm(MaxMemory)
if parseErr != nil {
errors.Overall[base.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[base.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-_]")
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 *base.BindingErrors, 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] = base.BindingRequireError
break
}
case rule == "AlphaDash":
if alphaDashPattern.MatchString(fmt.Sprintf("%v", fieldValue)) {
errors.Fields[field.Name] = base.BindingAlphaDashError
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] = base.BindingMinSizeError
break
}
v := reflect.ValueOf(fieldValue)
if v.Kind() == reflect.Slice && v.Len() < min {
errors.Fields[field.Name] = base.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] = base.BindingMaxSizeError
break
}
v := reflect.ValueOf(fieldValue)
if v.Kind() == reflect.Slice && v.Len() > max {
errors.Fields[field.Name] = base.BindingMinSizeError
break
}
case rule == "Email":
if !emailPattern.MatchString(fmt.Sprintf("%v", fieldValue)) {
errors.Fields[field.Name] = base.BindingEmailError
break
}
case rule == "Url":
if !urlPattern.MatchString(fmt.Sprintf("%v", fieldValue)) {
errors.Fields[field.Name] = base.BindingUrlError
break
}
}
}
}
}
func mapForm(formStruct reflect.Value, form map[string][]string, errors *base.BindingErrors) {
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 base.BindingErrors, resp http.ResponseWriter) {
if errs.Count() > 0 {
resp.Header().Set("Content-Type", "application/json; charset=utf-8")
if _, ok := errs.Overall[base.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 *base.BindingErrors) {
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] = base.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] = base.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] = base.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] = base.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 *base.BindingErrors, 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() *base.BindingErrors {
return &base.BindingErrors{make(map[string]string), make(map[string]string)}
}
func getErrors(context martini.Context) base.BindingErrors {
return context.Get(reflect.TypeOf(base.BindingErrors{})).Interface().(base.BindingErrors)
}
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(*base.BindingErrors, *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)
)

View File

@@ -0,0 +1,701 @@
// 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 middleware
import (
"bytes"
"mime/multipart"
"net/http"
"net/http/httptest"
"strconv"
"strings"
"testing"
"github.com/codegangsta/martini"
)
func TestBind(t *testing.T) {
testBind(t, false)
}
func TestBindWithInterface(t *testing.T) {
testBind(t, true)
}
func TestMultipartBind(t *testing.T) {
index := 0
for test, expectStatus := range bindMultipartTests {
handler := func(post BlogPost, errors Errors) {
handle(test, t, index, post, errors)
}
recorder := testMultipart(t, test, Bind(BlogPost{}), handler, index)
if recorder.Code != expectStatus {
t.Errorf("On test case %v, got status code %d but expected %d", test, recorder.Code, expectStatus)
}
index++
}
}
func TestForm(t *testing.T) {
testForm(t, false)
}
func TestFormWithInterface(t *testing.T) {
testForm(t, true)
}
func TestEmptyForm(t *testing.T) {
testEmptyForm(t)
}
func TestMultipartForm(t *testing.T) {
for index, test := range multipartformTests {
handler := func(post BlogPost, errors Errors) {
handle(test, t, index, post, errors)
}
testMultipart(t, test, MultipartForm(BlogPost{}), handler, index)
}
}
func TestMultipartFormWithInterface(t *testing.T) {
for index, test := range multipartformTests {
handler := func(post Modeler, errors Errors) {
post.Create(test, t, index)
}
testMultipart(t, test, MultipartForm(BlogPost{}, (*Modeler)(nil)), handler, index)
}
}
func TestJson(t *testing.T) {
testJson(t, false)
}
func TestJsonWithInterface(t *testing.T) {
testJson(t, true)
}
func TestEmptyJson(t *testing.T) {
testEmptyJson(t)
}
func TestValidate(t *testing.T) {
handlerMustErr := func(errors Errors) {
if errors.Count() == 0 {
t.Error("Expected at least one error, got 0")
}
}
handlerNoErr := func(errors Errors) {
if errors.Count() > 0 {
t.Error("Expected no errors, got", errors.Count())
}
}
performValidationTest(&BlogPost{"", "...", 0, 0, []int{}}, handlerMustErr, t)
performValidationTest(&BlogPost{"Good Title", "Good content", 0, 0, []int{}}, handlerNoErr, t)
performValidationTest(&User{Name: "Jim", Home: Address{"", ""}}, handlerMustErr, t)
performValidationTest(&User{Name: "Jim", Home: Address{"required", ""}}, handlerNoErr, t)
}
func handle(test testCase, t *testing.T, index int, post BlogPost, errors Errors) {
assertEqualField(t, "Title", index, test.ref.Title, post.Title)
assertEqualField(t, "Content", index, test.ref.Content, post.Content)
assertEqualField(t, "Views", index, test.ref.Views, post.Views)
for i := range test.ref.Multiple {
if i >= len(post.Multiple) {
t.Errorf("Expected: %v (size %d) to have same size as: %v (size %d)", post.Multiple, len(post.Multiple), test.ref.Multiple, len(test.ref.Multiple))
break
}
if test.ref.Multiple[i] != post.Multiple[i] {
t.Errorf("Expected: %v to deep equal: %v", post.Multiple, test.ref.Multiple)
break
}
}
if test.ok && errors.Count() > 0 {
t.Errorf("%+v should be OK (0 errors), but had errors: %+v", test, errors)
} else if !test.ok && errors.Count() == 0 {
t.Errorf("%+v should have errors, but was OK (0 errors)", test)
}
}
func handleEmpty(test emptyPayloadTestCase, t *testing.T, index int, section BlogSection, errors Errors) {
assertEqualField(t, "Title", index, test.ref.Title, section.Title)
assertEqualField(t, "Content", index, test.ref.Content, section.Content)
if test.ok && errors.Count() > 0 {
t.Errorf("%+v should be OK (0 errors), but had errors: %+v", test, errors)
} else if !test.ok && errors.Count() == 0 {
t.Errorf("%+v should have errors, but was OK (0 errors)", test)
}
}
func testBind(t *testing.T, withInterface bool) {
index := 0
for test, expectStatus := range bindTests {
m := martini.Classic()
recorder := httptest.NewRecorder()
handler := func(post BlogPost, errors Errors) { handle(test, t, index, post, errors) }
binding := Bind(BlogPost{})
if withInterface {
handler = func(post BlogPost, errors Errors) {
post.Create(test, t, index)
}
binding = Bind(BlogPost{}, (*Modeler)(nil))
}
switch test.method {
case "GET":
m.Get(route, binding, handler)
case "POST":
m.Post(route, binding, handler)
}
req, err := http.NewRequest(test.method, test.path, strings.NewReader(test.payload))
req.Header.Add("Content-Type", test.contentType)
if err != nil {
t.Error(err)
}
m.ServeHTTP(recorder, req)
if recorder.Code != expectStatus {
t.Errorf("On test case %v, got status code %d but expected %d", test, recorder.Code, expectStatus)
}
index++
}
}
func testJson(t *testing.T, withInterface bool) {
for index, test := range jsonTests {
recorder := httptest.NewRecorder()
handler := func(post BlogPost, errors Errors) { handle(test, t, index, post, errors) }
binding := Json(BlogPost{})
if withInterface {
handler = func(post BlogPost, errors Errors) {
post.Create(test, t, index)
}
binding = Bind(BlogPost{}, (*Modeler)(nil))
}
m := martini.Classic()
switch test.method {
case "GET":
m.Get(route, binding, handler)
case "POST":
m.Post(route, binding, handler)
case "PUT":
m.Put(route, binding, handler)
case "DELETE":
m.Delete(route, binding, handler)
}
req, err := http.NewRequest(test.method, route, strings.NewReader(test.payload))
if err != nil {
t.Error(err)
}
m.ServeHTTP(recorder, req)
}
}
func testEmptyJson(t *testing.T) {
for index, test := range emptyPayloadTests {
recorder := httptest.NewRecorder()
handler := func(section BlogSection, errors Errors) { handleEmpty(test, t, index, section, errors) }
binding := Json(BlogSection{})
m := martini.Classic()
switch test.method {
case "GET":
m.Get(route, binding, handler)
case "POST":
m.Post(route, binding, handler)
case "PUT":
m.Put(route, binding, handler)
case "DELETE":
m.Delete(route, binding, handler)
}
req, err := http.NewRequest(test.method, route, strings.NewReader(test.payload))
if err != nil {
t.Error(err)
}
m.ServeHTTP(recorder, req)
}
}
func testForm(t *testing.T, withInterface bool) {
for index, test := range formTests {
recorder := httptest.NewRecorder()
handler := func(post BlogPost, errors Errors) { handle(test, t, index, post, errors) }
binding := Form(BlogPost{})
if withInterface {
handler = func(post BlogPost, errors Errors) {
post.Create(test, t, index)
}
binding = Form(BlogPost{}, (*Modeler)(nil))
}
m := martini.Classic()
switch test.method {
case "GET":
m.Get(route, binding, handler)
case "POST":
m.Post(route, binding, handler)
}
req, err := http.NewRequest(test.method, test.path, nil)
if err != nil {
t.Error(err)
}
m.ServeHTTP(recorder, req)
}
}
func testEmptyForm(t *testing.T) {
for index, test := range emptyPayloadTests {
recorder := httptest.NewRecorder()
handler := func(section BlogSection, errors Errors) { handleEmpty(test, t, index, section, errors) }
binding := Form(BlogSection{})
m := martini.Classic()
switch test.method {
case "GET":
m.Get(route, binding, handler)
case "POST":
m.Post(route, binding, handler)
}
req, err := http.NewRequest(test.method, test.path, nil)
if err != nil {
t.Error(err)
}
m.ServeHTTP(recorder, req)
}
}
func testMultipart(t *testing.T, test testCase, middleware martini.Handler, handler martini.Handler, index int) *httptest.ResponseRecorder {
recorder := httptest.NewRecorder()
m := martini.Classic()
m.Post(route, middleware, handler)
body := &bytes.Buffer{}
writer := multipart.NewWriter(body)
writer.WriteField("title", test.ref.Title)
writer.WriteField("content", test.ref.Content)
writer.WriteField("views", strconv.Itoa(test.ref.Views))
if len(test.ref.Multiple) != 0 {
for _, value := range test.ref.Multiple {
writer.WriteField("multiple", strconv.Itoa(value))
}
}
req, err := http.NewRequest(test.method, test.path, body)
req.Header.Add("Content-Type", writer.FormDataContentType())
if err != nil {
t.Error(err)
}
err = writer.Close()
if err != nil {
t.Error(err)
}
m.ServeHTTP(recorder, req)
return recorder
}
func assertEqualField(t *testing.T, fieldname string, testcasenumber int, expected interface{}, got interface{}) {
if expected != got {
t.Errorf("%s: expected=%s, got=%s in test case %d\n", fieldname, expected, got, testcasenumber)
}
}
func performValidationTest(data interface{}, handler func(Errors), t *testing.T) {
recorder := httptest.NewRecorder()
m := martini.Classic()
m.Get(route, Validate(data), handler)
req, err := http.NewRequest("GET", route, nil)
if err != nil {
t.Error("HTTP error:", err)
}
m.ServeHTTP(recorder, req)
}
func (self BlogPost) Validate(errors *Errors, req *http.Request) {
if len(self.Title) < 4 {
errors.Fields["Title"] = "Too short; minimum 4 characters"
}
if len(self.Content) > 1024 {
errors.Fields["Content"] = "Too long; maximum 1024 characters"
}
if len(self.Content) < 5 {
errors.Fields["Content"] = "Too short; minimum 5 characters"
}
}
func (self BlogPost) Create(test testCase, t *testing.T, index int) {
assertEqualField(t, "Title", index, test.ref.Title, self.Title)
assertEqualField(t, "Content", index, test.ref.Content, self.Content)
assertEqualField(t, "Views", index, test.ref.Views, self.Views)
for i := range test.ref.Multiple {
if i >= len(self.Multiple) {
t.Errorf("Expected: %v (size %d) to have same size as: %v (size %d)", self.Multiple, len(self.Multiple), test.ref.Multiple, len(test.ref.Multiple))
break
}
if test.ref.Multiple[i] != self.Multiple[i] {
t.Errorf("Expected: %v to deep equal: %v", self.Multiple, test.ref.Multiple)
break
}
}
}
func (self BlogSection) Create(test emptyPayloadTestCase, t *testing.T, index int) {
// intentionally left empty
}
type (
testCase struct {
method string
path string
payload string
contentType string
ok bool
ref *BlogPost
}
emptyPayloadTestCase struct {
method string
path string
payload string
contentType string
ok bool
ref *BlogSection
}
Modeler interface {
Create(test testCase, t *testing.T, index int)
}
BlogPost struct {
Title string `form:"title" json:"title" binding:"required"`
Content string `form:"content" json:"content"`
Views int `form:"views" json:"views"`
internal int `form:"-"`
Multiple []int `form:"multiple"`
}
BlogSection struct {
Title string `form:"title" json:"title"`
Content string `form:"content" json:"content"`
}
User struct {
Name string `json:"name" binding:"required"`
Home Address `json:"address" binding:"required"`
}
Address struct {
Street1 string `json:"street1" binding:"required"`
Street2 string `json:"street2"`
}
)
var (
bindTests = map[testCase]int{
// These should bail at the deserialization/binding phase
testCase{
"POST",
path,
`{ bad JSON `,
"application/json",
false,
new(BlogPost),
}: http.StatusBadRequest,
testCase{
"POST",
path,
`not multipart but has content-type`,
"multipart/form-data",
false,
new(BlogPost),
}: http.StatusBadRequest,
testCase{
"POST",
path,
`no content-type and not URL-encoded or JSON"`,
"",
false,
new(BlogPost),
}: http.StatusBadRequest,
// These should deserialize, then bail at the validation phase
testCase{
"POST",
path + "?title= This is wrong ",
`not URL-encoded but has content-type`,
"x-www-form-urlencoded",
false,
new(BlogPost),
}: 422, // according to comments in Form() -> although the request is not url encoded, ParseForm does not complain
testCase{
"GET",
path + "?content=This+is+the+content",
``,
"x-www-form-urlencoded",
false,
&BlogPost{Title: "", Content: "This is the content"},
}: 422,
testCase{
"GET",
path + "",
`{"content":"", "title":"Blog Post Title"}`,
"application/json",
false,
&BlogPost{Title: "Blog Post Title", Content: ""},
}: 422,
// These should succeed
testCase{
"GET",
path + "",
`{"content":"This is the content", "title":"Blog Post Title"}`,
"application/json",
true,
&BlogPost{Title: "Blog Post Title", Content: "This is the content"},
}: http.StatusOK,
testCase{
"GET",
path + "?content=This+is+the+content&title=Blog+Post+Title",
``,
"",
true,
&BlogPost{Title: "Blog Post Title", Content: "This is the content"},
}: http.StatusOK,
testCase{
"GET",
path + "?content=This is the content&title=Blog+Post+Title",
`{"content":"This is the content", "title":"Blog Post Title"}`,
"",
true,
&BlogPost{Title: "Blog Post Title", Content: "This is the content"},
}: http.StatusOK,
testCase{
"GET",
path + "",
`{"content":"This is the content", "title":"Blog Post Title"}`,
"",
true,
&BlogPost{Title: "Blog Post Title", Content: "This is the content"},
}: http.StatusOK,
}
bindMultipartTests = map[testCase]int{
// This should deserialize, then bail at the validation phase
testCase{
"POST",
path,
"",
"multipart/form-data",
false,
&BlogPost{Title: "", Content: "This is the content"},
}: 422,
// This should succeed
testCase{
"POST",
path,
"",
"multipart/form-data",
true,
&BlogPost{Title: "This is the Title", Content: "This is the content"},
}: http.StatusOK,
}
formTests = []testCase{
{
"GET",
path + "?content=This is the content",
"",
"",
false,
&BlogPost{Title: "", Content: "This is the content"},
},
{
"POST",
path + "?content=This+is+the+content&title=Blog+Post+Title&views=3",
"",
"",
false, // false because POST requests should have a body, not just a query string
&BlogPost{Title: "Blog Post Title", Content: "This is the content", Views: 3},
},
{
"GET",
path + "?content=This+is+the+content&title=Blog+Post+Title&views=3&multiple=5&multiple=10&multiple=15&multiple=20",
"",
"",
true,
&BlogPost{Title: "Blog Post Title", Content: "This is the content", Views: 3, Multiple: []int{5, 10, 15, 20}},
},
}
multipartformTests = []testCase{
{
"POST",
path,
"",
"multipart/form-data",
false,
&BlogPost{Title: "", Content: "This is the content"},
},
{
"POST",
path,
"",
"multipart/form-data",
false,
&BlogPost{Title: "Blog Post Title", Views: 3},
},
{
"POST",
path,
"",
"multipart/form-data",
true,
&BlogPost{Title: "Blog Post Title", Content: "This is the content", Views: 3, Multiple: []int{5, 10, 15, 20}},
},
}
emptyPayloadTests = []emptyPayloadTestCase{
{
"GET",
"",
"",
"",
true,
&BlogSection{},
},
{
"POST",
"",
"",
"",
true,
&BlogSection{},
},
{
"PUT",
"",
"",
"",
true,
&BlogSection{},
},
{
"DELETE",
"",
"",
"",
true,
&BlogSection{},
},
}
jsonTests = []testCase{
// bad requests
{
"GET",
"",
`{blah blah blah}`,
"",
false,
&BlogPost{},
},
{
"POST",
"",
`{asdf}`,
"",
false,
&BlogPost{},
},
{
"PUT",
"",
`{blah blah blah}`,
"",
false,
&BlogPost{},
},
{
"DELETE",
"",
`{;sdf _SDf- }`,
"",
false,
&BlogPost{},
},
// Valid-JSON requests
{
"GET",
"",
`{"content":"This is the content"}`,
"",
false,
&BlogPost{Title: "", Content: "This is the content"},
},
{
"POST",
"",
`{}`,
"application/json",
false,
&BlogPost{Title: "", Content: ""},
},
{
"POST",
"",
`{"content":"This is the content", "title":"Blog Post Title"}`,
"",
true,
&BlogPost{Title: "Blog Post Title", Content: "This is the content"},
},
{
"PUT",
"",
`{"content":"This is the content", "title":"Blog Post Title"}`,
"",
true,
&BlogPost{Title: "Blog Post Title", Content: "This is the content"},
},
{
"DELETE",
"",
`{"content":"This is the content", "title":"Blog Post Title"}`,
"",
true,
&BlogPost{Title: "Blog Post Title", Content: "This is the content"},
},
}
)
const (
route = "/blogposts/create"
path = "http://localhost:3000" + route
)

View File

@@ -10,7 +10,10 @@ import (
"encoding/base64"
"fmt"
"html/template"
"io"
"net/http"
"net/url"
"path/filepath"
"strconv"
"strings"
"time"
@@ -34,6 +37,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 +51,7 @@ type Context struct {
IsBranch bool
IsTag bool
IsCommit bool
HasAccess bool
Repository *models.Repository
Owner *models.User
Commit *git.Commit
@@ -59,6 +64,7 @@ type Context struct {
HTTPS string
Git string
}
Mirror *models.Mirror
}
}
@@ -78,6 +84,8 @@ func (ctx *Context) HasError() bool {
if !ok {
return false
}
ctx.Flash.ErrorMsg = ctx.Data["ErrorMsg"].(string)
ctx.Data["Flash"] = ctx.Flash
return hasErr.(bool)
}
@@ -88,21 +96,21 @@ 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 martini.Dev != martini.Prod {
ctx.Data["ErrorMsg"] = err
}
ctx.Data["ErrorMsg"] = err
ctx.HTML(status, fmt.Sprintf("status/%d", status))
}
@@ -237,6 +245,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) {
@@ -254,9 +312,27 @@ func InitContext() martini.Handler {
// start session
ctx.Session = base.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

@@ -146,7 +146,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

@@ -15,6 +15,7 @@ import (
"github.com/gogits/gogs/models"
"github.com/gogits/gogs/modules/base"
"github.com/gogits/gogs/modules/log"
)
func RepoAssignment(redirect bool, args ...bool) martini.Handler {
@@ -39,7 +40,7 @@ func RepoAssignment(redirect bool, args ...bool) martini.Handler {
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)
@@ -66,33 +67,69 @@ func RepoAssignment(redirect bool, args ...bool) martini.Handler {
ctx.Handle(200, "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 access.
if repo.IsPrivate {
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
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
@@ -104,29 +141,43 @@ func RepoAssignment(redirect bool, args ...bool) martini.Handler {
ctx.Repo.CloneLink.HTTPS = fmt.Sprintf("%s%s/%s.git", base.AppUrl, user.LowerName, repo.LowerName)
ctx.Data["CloneLink"] = ctx.Repo.CloneLink
if ctx.Repo.Repository.IsGoget {
ctx.Data["GoGetLink"] = fmt.Sprintf("%s%s/%s", base.AppUrl, user.LowerName, repo.LowerName)
ctx.Data["GoGetImport"] = fmt.Sprintf("%s/%s/%s", base.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,16 +188,23 @@ func RepoAssignment(redirect bool, args ...bool) martini.Handler {
}
} else {
branchName = "master"
refName = ctx.Repo.Repository.DefaultBranch
if len(refName) == 0 {
refName = "master"
}
goto detect
}
ctx.Data["IsBranch"] = ctx.Repo.IsBranch
ctx.Data["IsCommit"] = ctx.Repo.IsCommit
log.Debug("Repo.Commit: %v", ctx.Repo.Commit)
}
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,6 +214,11 @@ 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
}

396
modules/social/social.go Normal file
View File

@@ -0,0 +1,396 @@
// 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/base"
"github.com/gogits/gogs/modules/log"
)
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 !base.Cfg.MustBool("oauth", "ENABLED") {
return
}
base.OauthService = &base.Oauther{}
base.OauthService.OauthInfos = make(map[string]*base.OauthInfo)
socialConfigs := make(map[string]*oauth.Config)
allOauthes := []string{"github", "google", "qq", "twitter", "weibo"}
// Load all OAuth config data.
for _, name := range allOauthes {
base.OauthService.OauthInfos[name] = &base.OauthInfo{
ClientId: base.Cfg.MustValue("oauth."+name, "CLIENT_ID"),
ClientSecret: base.Cfg.MustValue("oauth."+name, "CLIENT_SECRET"),
Scopes: base.Cfg.MustValue("oauth."+name, "SCOPES"),
AuthUrl: base.Cfg.MustValue("oauth."+name, "AUTH_URL"),
TokenUrl: base.Cfg.MustValue("oauth."+name, "TOKEN_URL"),
}
socialConfigs[name] = &oauth.Config{
ClientId: base.OauthService.OauthInfos[name].ClientId,
ClientSecret: base.OauthService.OauthInfos[name].ClientSecret,
RedirectURL: strings.TrimSuffix(base.AppUrl, "/") + SocialBaseUrl + name,
Scope: base.OauthService.OauthInfos[name].Scopes,
AuthURL: base.OauthService.OauthInfos[name].AuthUrl,
TokenURL: base.OauthService.OauthInfos[name].TokenUrl,
}
}
enabledOauths := make([]string, 0, 10)
// GitHub.
if base.Cfg.MustBool("oauth.github", "ENABLED") {
base.OauthService.GitHub = true
newGitHubOauth(socialConfigs["github"])
enabledOauths = append(enabledOauths, "GitHub")
}
// Google.
if base.Cfg.MustBool("oauth.google", "ENABLED") {
base.OauthService.Google = true
newGoogleOauth(socialConfigs["google"])
enabledOauths = append(enabledOauths, "Google")
}
// QQ.
if base.Cfg.MustBool("oauth.qq", "ENABLED") {
base.OauthService.Tencent = true
newTencentOauth(socialConfigs["qq"])
enabledOauths = append(enabledOauths, "QQ")
}
// Twitter.
if base.Cfg.MustBool("oauth.twitter", "ENABLED") {
base.OauthService.Twitter = true
newTwitterOauth(socialConfigs["twitter"])
enabledOauths = append(enabledOauths, "Twitter")
}
// Weibo.
if base.Cfg.MustBool("oauth.weibo", "ENABLED") {
base.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
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

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 */
@@ -291,6 +339,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,6 +373,18 @@ 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 > h4, #user-setting-container > h4, #user-setting-container > div > h4,
@@ -444,6 +520,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 {
@@ -614,6 +727,10 @@ html, body {
margin-top: -20px;
}
#commits-pager {
margin-top: 0;
}
#source .source-toolbar:after {
clear: both;
}
@@ -831,6 +948,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 +959,6 @@ html, body {
vertical-align: top;
}
.commit-box .search {
margin-top: 3px;
}
.commit-box td {
background-color: #FFF;
}
@@ -1166,7 +1283,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 +1291,7 @@ html, body {
border-color: #CCC;
}
#issue .issue-is-closed .issue-line{
#issue .issue-is-closed .issue-line {
display: none;
}
@@ -1193,7 +1310,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,7 +1320,7 @@ 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;
}
@@ -1231,3 +1348,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.

Before

Width:  |  Height:  |  Size: 7.1 KiB

After

Width:  |  Height:  |  Size: 16 KiB

BIN
public/js/ZeroClipboard.swf Normal file

Binary file not shown.

View File

@@ -159,6 +159,7 @@ var Gogits = {
$tabs.tab("show");
$tabs.find("li:eq(0) a").tab("show");
};
// fix dropdown inside click
Gogits.initDropDown = function () {
$('.dropdown-menu.no-propagation').on('click', function (e) {
@@ -166,6 +167,7 @@ var Gogits = {
});
};
// render markdown
Gogits.renderMarkdown = function () {
var $md = $('.markdown');
@@ -192,6 +194,7 @@ var Gogits = {
});
};
// render code view
Gogits.renderCodeView = function () {
function selectRange($list, $select, $from) {
$list.removeClass('active');
@@ -255,6 +258,43 @@ var Gogits = {
}).trigger('hashchange');
};
// copy utils
Gogits.bindCopy = function (selector) {
if ($(selector).hasClass('js-copy-bind')) {
return;
}
$(selector).zclip({
path: "/js/ZeroClipboard.swf",
copy: function () {
var t = $(this).data("copy-val");
var to = $($(this).data("copy-from"));
var str = "";
if (t == "txt") {
str = to.text();
}
if (t == 'val') {
str = to.val();
}
if (t == 'html') {
str = to.html();
}
return str;
},
afterCopy: function () {
var $this = $(this);
$this.tooltip('hide')
.attr('data-original-title', 'Copied OK');
setTimeout(function () {
$this.tooltip("show");
}, 200);
setTimeout(function () {
$this.tooltip('hide')
.attr('data-original-title', 'Copy to Clipboard');
}, 3000);
}
}).addClass("js-copy-bind");
}
})(jQuery);
// ajax utils
@@ -314,6 +354,7 @@ function initRegister() {
}
function initUserSetting() {
// ssh confirmation
$('#ssh-keys .delete').confirmation({
singleton: true,
onConfirm: function (e, $this) {
@@ -326,6 +367,18 @@ function initUserSetting() {
});
}
});
// profile form
(function () {
$('#user-setting-username').on("keyup", function () {
var $this = $(this);
if ($this.val() != $this.attr('title')) {
$this.next('.help-block').toggleShow();
} else {
$this.next('.help-block').toggleHide();
}
});
}())
}
function initRepository() {
@@ -343,7 +396,10 @@ function initRepository() {
$clone.find('span.clone-url').text($this.data('link'));
}
}).eq(0).trigger("click");
// todo copy to clipboard
$("#repo-clone").on("shown.bs.dropdown", function () {
Gogits.bindCopy("[data-init=copy]");
});
Gogits.bindCopy("[data-init=copy]:visible");
}
})();
@@ -395,6 +451,18 @@ function initRepository() {
$item.find(".bar .add").css("width", addPercent + "%");
});
}());
// repo setting form
(function () {
$('#repo-setting-name').on("keyup", function () {
var $this = $(this);
if ($this.val() != $this.attr('title')) {
$this.next('.help-block').toggleShow();
} else {
$this.next('.help-block').toggleHide();
}
});
}())
}
function initInstall() {
@@ -477,6 +545,31 @@ function initIssue() {
}
function initRelease() {
// release new ajax preview
(function () {
$('[data-ajax-name=release-preview]').on("click", function () {
var $this = $(this);
$this.toggleAjax(function (json) {
if (json.ok) {
$($this.data("preview")).html(json.content);
}
})
});
$('.release-write a[data-toggle]').on("click", function () {
$('.release-preview-content').html("loading...");
});
}());
// release new target selection
(function () {
$('#release-new-target-branch-list').on('click', 'a', function () {
$('#tag-target').val($(this).text());
$('#release-new-target-name').text(" " + $(this).text());
});
}());
}
(function ($) {
$(function () {
initCore();
@@ -496,5 +589,8 @@ function initIssue() {
if ($('#issue').length) {
initIssue();
}
if ($('#release').length) {
initRelease();
}
});
})(jQuery);

File diff suppressed because one or more lines are too long

View File

@@ -153,6 +153,12 @@ func Config(ctx *middleware.Context) {
ctx.Data["Mailer"] = base.MailService
}
ctx.Data["OauthEnabled"] = false
if base.OauthService != nil {
ctx.Data["OauthEnabled"] = true
ctx.Data["Oauther"] = base.OauthService
}
ctx.Data["CacheAdapter"] = base.CacheAdapter
ctx.Data["CacheConfig"] = base.CacheConfig

View File

@@ -16,14 +16,15 @@ import (
"github.com/gogits/gogs/modules/middleware"
)
func NewUser(ctx *middleware.Context, form auth.RegisterForm) {
func NewUser(ctx *middleware.Context) {
ctx.Data["Title"] = "New Account"
ctx.Data["PageIsUsers"] = true
ctx.HTML(200, "admin/users/new")
}
if ctx.Req.Method == "GET" {
ctx.HTML(200, "admin/users/new")
return
}
func NewUserPost(ctx *middleware.Context, form auth.RegisterForm) {
ctx.Data["Title"] = "New Account"
ctx.Data["PageIsUsers"] = true
if form.Password != form.RetypePasswd {
ctx.Data["HasError"] = true
@@ -55,7 +56,7 @@ func NewUser(ctx *middleware.Context, form auth.RegisterForm) {
case models.ErrUserNameIllegal:
ctx.RenderWithErr(models.ErrRepoNameIllegal.Error(), "admin/users/new", &form)
default:
ctx.Handle(200, "admin.user.NewUser", err)
ctx.Handle(500, "admin.user.NewUser", err)
}
return
}
@@ -66,25 +67,39 @@ func NewUser(ctx *middleware.Context, form auth.RegisterForm) {
ctx.Redirect("/admin/users")
}
func EditUser(ctx *middleware.Context, params martini.Params, form auth.AdminEditUserForm) {
func EditUser(ctx *middleware.Context, params martini.Params) {
ctx.Data["Title"] = "Edit Account"
ctx.Data["PageIsUsers"] = true
uid, err := base.StrTo(params["userid"]).Int()
if err != nil {
ctx.Handle(200, "admin.user.EditUser", err)
ctx.Handle(404, "admin.user.EditUser", err)
return
}
u, err := models.GetUserById(int64(uid))
if err != nil {
ctx.Handle(200, "admin.user.EditUser", err)
ctx.Handle(500, "admin.user.EditUser", err)
return
}
if ctx.Req.Method == "GET" {
ctx.Data["User"] = u
ctx.HTML(200, "admin/users/edit")
ctx.Data["User"] = u
ctx.HTML(200, "admin/users/edit")
}
func EditUserPost(ctx *middleware.Context, params martini.Params, form auth.AdminEditUserForm) {
ctx.Data["Title"] = "Edit Account"
ctx.Data["PageIsUsers"] = true
uid, err := base.StrTo(params["userid"]).Int()
if err != nil {
ctx.Handle(404, "admin.user.EditUser", err)
return
}
u, err := models.GetUserById(int64(uid))
if err != nil {
ctx.Handle(500, "admin.user.EditUser", err)
return
}
@@ -96,47 +111,44 @@ func EditUser(ctx *middleware.Context, params martini.Params, form auth.AdminEdi
u.IsActive = form.Active == "on"
u.IsAdmin = form.Admin == "on"
if err := models.UpdateUser(u); err != nil {
ctx.Handle(200, "admin.user.EditUser", err)
ctx.Handle(500, "admin.user.EditUser", err)
return
}
ctx.Data["IsSuccess"] = true
ctx.Data["User"] = u
ctx.HTML(200, "admin/users/edit")
log.Trace("%s User profile updated by admin(%s): %s", ctx.Req.RequestURI,
ctx.User.LowerName, ctx.User.LowerName)
ctx.Data["User"] = u
ctx.Flash.Success("Account profile has been successfully updated.")
ctx.Redirect("/admin/users/" + params["userid"])
}
func DeleteUser(ctx *middleware.Context, params martini.Params) {
ctx.Data["Title"] = "Edit Account"
ctx.Data["Title"] = "Delete Account"
ctx.Data["PageIsUsers"] = true
log.Info("delete")
uid, err := base.StrTo(params["userid"]).Int()
if err != nil {
ctx.Handle(200, "admin.user.EditUser", err)
ctx.Handle(404, "admin.user.EditUser", err)
return
}
u, err := models.GetUserById(int64(uid))
if err != nil {
ctx.Handle(200, "admin.user.EditUser", err)
ctx.Handle(500, "admin.user.EditUser", err)
return
}
if err = models.DeleteUser(u); err != nil {
ctx.Data["HasError"] = true
switch err {
case models.ErrUserOwnRepos:
ctx.Data["ErrorMsg"] = "This account still has ownership of repository, owner has to delete or transfer them first."
ctx.Data["User"] = u
ctx.HTML(200, "admin/users/edit")
ctx.Flash.Error("This account still has ownership of repository, owner has to delete or transfer them first.")
ctx.Redirect("/admin/users/" + params["userid"])
default:
ctx.Handle(200, "admin.user.DeleteUser", err)
ctx.Handle(500, "admin.user.DeleteUser", err)
}
return
}
log.Trace("%s User deleted by admin(%s): %s", ctx.Req.RequestURI,
ctx.User.LowerName, ctx.User.LowerName)

View File

@@ -13,6 +13,6 @@ func Markdown(ctx *middleware.Context) {
content := ctx.Query("content")
ctx.Render.JSON(200, map[string]interface{}{
"ok": true,
"content": string(base.RenderMarkdown([]byte(content), "")),
"content": string(base.RenderMarkdown([]byte(content), ctx.Query("repoLink"))),
})
}

View File

@@ -5,6 +5,7 @@
package routers
import (
"github.com/gogits/gogs/models"
"github.com/gogits/gogs/modules/base"
"github.com/gogits/gogs/modules/middleware"
"github.com/gogits/gogs/routers/user"
@@ -23,6 +24,11 @@ func Home(ctx *middleware.Context) {
return
}
repos, _ := models.GetRecentUpdatedRepositories()
for _, repo := range repos {
repo.Owner, _ = models.GetUserById(repo.OwnerId)
}
ctx.Data["Repos"] = repos
ctx.Data["PageIsHome"] = true
ctx.HTML(200, "home")
}

View File

@@ -7,18 +7,22 @@ package routers
import (
"errors"
"os"
"os/exec"
"strings"
"github.com/Unknwon/goconfig"
"github.com/go-martini/martini"
"github.com/lunny/xorm"
"github.com/go-xorm/xorm"
qlog "github.com/qiniu/log"
"github.com/gogits/gogs/models"
"github.com/gogits/gogs/modules/auth"
"github.com/gogits/gogs/modules/base"
"github.com/gogits/gogs/modules/cron"
"github.com/gogits/gogs/modules/log"
"github.com/gogits/gogs/modules/mailer"
"github.com/gogits/gogs/modules/middleware"
"github.com/gogits/gogs/modules/social"
)
// Check run mode(Default of martini is Dev).
@@ -26,12 +30,18 @@ func checkRunMode() {
switch base.Cfg.MustValue("", "RUN_MODE") {
case "prod":
martini.Env = martini.Prod
base.IsProdMode = true
case "test":
martini.Env = martini.Test
}
log.Info("Run Mode: %s", strings.Title(martini.Env))
}
func NewServices() {
base.NewBaseServices()
social.NewOauthService()
}
// GlobalInit is for global configuration reload-able.
func GlobalInit() {
base.NewConfigContext()
@@ -39,16 +49,19 @@ func GlobalInit() {
models.LoadModelsConfig()
models.LoadRepoConfig()
models.NewRepoContext()
NewServices()
if base.InstallLock {
if err := models.NewEngine(); err != nil {
log.Error("%v", err)
os.Exit(2)
qlog.Fatal(err)
}
models.HasEngine = true
if models.EnableSQLite3 {
log.Info("SQLite3 Enabled")
}
cron.NewCronContext()
}
base.NewServices()
checkRunMode()
}
@@ -61,44 +74,56 @@ func Install(ctx *middleware.Context, form auth.InstallForm) {
ctx.Data["Title"] = "Install"
ctx.Data["PageIsInstall"] = true
if ctx.Req.Method == "GET" {
// Get and assign value to install form.
if len(form.Host) == 0 {
form.Host = models.DbCfg.Host
}
if len(form.User) == 0 {
form.User = models.DbCfg.User
}
if len(form.Passwd) == 0 {
form.Passwd = models.DbCfg.Pwd
}
if len(form.DatabaseName) == 0 {
form.DatabaseName = models.DbCfg.Name
}
if len(form.DatabasePath) == 0 {
form.DatabasePath = models.DbCfg.Path
}
// Get and assign value to install form.
if len(form.Host) == 0 {
form.Host = models.DbCfg.Host
}
if len(form.User) == 0 {
form.User = models.DbCfg.User
}
if len(form.Passwd) == 0 {
form.Passwd = models.DbCfg.Pwd
}
if len(form.DatabaseName) == 0 {
form.DatabaseName = models.DbCfg.Name
}
if len(form.DatabasePath) == 0 {
form.DatabasePath = models.DbCfg.Path
}
if len(form.RepoRootPath) == 0 {
form.RepoRootPath = base.RepoRootPath
}
if len(form.RunUser) == 0 {
form.RunUser = base.RunUser
}
if len(form.Domain) == 0 {
form.Domain = base.Domain
}
if len(form.AppUrl) == 0 {
form.AppUrl = base.AppUrl
}
if len(form.RepoRootPath) == 0 {
form.RepoRootPath = base.RepoRootPath
}
if len(form.RunUser) == 0 {
form.RunUser = base.RunUser
}
if len(form.Domain) == 0 {
form.Domain = base.Domain
}
if len(form.AppUrl) == 0 {
form.AppUrl = base.AppUrl
}
auth.AssignForm(form, ctx.Data)
auth.AssignForm(form, ctx.Data)
ctx.HTML(200, "install")
}
func InstallPost(ctx *middleware.Context, form auth.InstallForm) {
if base.InstallLock {
ctx.Handle(404, "install.Install", errors.New("Installation is prohibited"))
return
}
ctx.Data["Title"] = "Install"
ctx.Data["PageIsInstall"] = true
if ctx.HasError() {
ctx.HTML(200, "install")
return
}
if ctx.HasError() {
ctx.HTML(200, "install")
if _, err := exec.LookPath("git"); err != nil {
ctx.RenderWithErr("Fail to test 'git' command: "+err.Error(), "install", &form)
return
}
@@ -132,9 +157,9 @@ func Install(ctx *middleware.Context, form auth.InstallForm) {
}
// Check run user.
curUser := os.Getenv("USERNAME")
curUser := os.Getenv("USER")
if len(curUser) == 0 {
curUser = os.Getenv("USER")
curUser = os.Getenv("USERNAME")
}
// Does not check run user when the install lock is off.
if form.RunUser != curUser {
@@ -182,6 +207,7 @@ func Install(ctx *middleware.Context, form auth.InstallForm) {
if _, err := models.RegisterUser(&models.User{Name: form.AdminName, Email: form.AdminEmail, Passwd: form.AdminPasswd,
IsAdmin: true, IsActive: true}); err != nil {
if err != models.ErrUserAlreadyExist {
base.InstallLock = false
ctx.RenderWithErr("Admin account setting is invalid: "+err.Error(), "install", &form)
return
}
@@ -189,5 +215,6 @@ func Install(ctx *middleware.Context, form auth.InstallForm) {
}
log.Info("First-time run install finished!")
ctx.Flash.Success("Welcome! We're glad that you choose Gogs, have fun and take care.")
ctx.Redirect("/user/login")
}

View File

@@ -7,12 +7,11 @@ package repo
import (
"github.com/go-martini/martini"
"github.com/gogits/gogs/models"
"github.com/gogits/gogs/modules/middleware"
)
func Branches(ctx *middleware.Context, params martini.Params) {
brs, err := models.GetBranches(ctx.Repo.Owner.Name, ctx.Repo.Repository.Name)
brs, err := ctx.Repo.GitRepo.GetBranches()
if err != nil {
ctx.Handle(404, "repo.Branches", err)
return

View File

@@ -5,7 +5,6 @@
package repo
import (
"container/list"
"path"
"github.com/go-martini/martini"
@@ -16,35 +15,51 @@ import (
)
func Commits(ctx *middleware.Context, params martini.Params) {
userName := params["username"]
repoName := params["reponame"]
branchName := params["branchname"]
userName := ctx.Repo.Owner.Name
repoName := ctx.Repo.Repository.Name
brs, err := models.GetBranches(userName, repoName)
brs, err := ctx.Repo.GitRepo.GetBranches()
if err != nil {
ctx.Handle(200, "repo.Commits", err)
ctx.Handle(500, "repo.Commits", err)
return
} else if len(brs) == 0 {
ctx.Handle(404, "repo.Commits", nil)
return
}
var commits *list.List
if models.IsBranchExist(userName, repoName, branchName) {
commits, err = models.GetCommitsByBranch(userName, repoName, branchName)
} else {
commits, err = models.GetCommitsByCommitId(userName, repoName, branchName)
commitsCount, err := ctx.Repo.Commit.CommitsCount()
if err != nil {
ctx.Handle(500, "repo.Commits(GetCommitsCount)", err)
return
}
// Calculate and validate page number.
page, _ := base.StrTo(ctx.Query("p")).Int()
if page < 1 {
page = 1
}
lastPage := page - 1
if lastPage < 0 {
lastPage = 0
}
nextPage := page + 1
if nextPage*50 > commitsCount {
nextPage = 0
}
//both `git log branchName` and `git log commitId` work
commits, err := ctx.Repo.Commit.CommitsByRange(page)
if err != nil {
ctx.Handle(404, "repo.Commits", err)
ctx.Handle(500, "repo.Commits(get commits)", err)
return
}
ctx.Data["Username"] = userName
ctx.Data["Reponame"] = repoName
ctx.Data["CommitCount"] = commits.Len()
ctx.Data["CommitCount"] = commitsCount
ctx.Data["Commits"] = commits
ctx.Data["LastPageNum"] = lastPage
ctx.Data["NextPageNum"] = nextPage
ctx.Data["IsRepoToolbarCommits"] = true
ctx.HTML(200, "repo/commits")
}
@@ -52,7 +67,6 @@ func Commits(ctx *middleware.Context, params martini.Params) {
func Diff(ctx *middleware.Context, params martini.Params) {
userName := ctx.Repo.Owner.Name
repoName := ctx.Repo.Repository.Name
branchName := ctx.Repo.BranchName
commitId := ctx.Repo.CommitId
commit := ctx.Repo.Commit
@@ -64,19 +78,15 @@ func Diff(ctx *middleware.Context, params martini.Params) {
}
isImageFile := func(name string) bool {
repoFile, err := models.GetTargetFile(userName, repoName,
branchName, commitId, name)
blob, err := ctx.Repo.Commit.GetBlobByPath(name)
if err != nil {
return false
}
blob, err := repoFile.LookupBlob()
data, err := blob.Data()
if err != nil {
return false
}
data := blob.Contents()
_, isImage := base.IsImageFile(data)
return isImage
}
@@ -85,8 +95,44 @@ func Diff(ctx *middleware.Context, params martini.Params) {
ctx.Data["Title"] = commit.Message() + " · " + base.ShortSha(commitId)
ctx.Data["Commit"] = commit
ctx.Data["Diff"] = diff
ctx.Data["DiffNotAvailable"] = diff.NumFiles() == 0
ctx.Data["IsRepoToolbarCommits"] = true
ctx.Data["SourcePath"] = "/" + path.Join(userName, repoName, "src", commitId)
ctx.Data["RawPath"] = "/" + path.Join(userName, repoName, "raw", commitId)
ctx.HTML(200, "repo/diff")
}
func SearchCommits(ctx *middleware.Context, params martini.Params) {
keyword := ctx.Query("q")
if len(keyword) == 0 {
ctx.Redirect(ctx.Repo.RepoLink + "/commits/" + ctx.Repo.BranchName)
return
}
userName := params["username"]
repoName := params["reponame"]
brs, err := ctx.Repo.GitRepo.GetBranches()
if err != nil {
ctx.Handle(500, "repo.SearchCommits(GetBranches)", err)
return
} else if len(brs) == 0 {
ctx.Handle(404, "repo.SearchCommits(GetBranches)", nil)
return
}
commits, err := ctx.Repo.Commit.SearchCommits(keyword)
if err != nil {
ctx.Handle(500, "repo.SearchCommits(SearchCommits)", err)
return
}
ctx.Data["Keyword"] = keyword
ctx.Data["Username"] = userName
ctx.Data["Reponame"] = repoName
ctx.Data["CommitCount"] = commits.Len()
ctx.Data["Commits"] = commits
ctx.Data["IsSearchPage"] = true
ctx.Data["IsRepoToolbarCommits"] = true
ctx.HTML(200, "repo/commits")
}

68
routers/repo/download.go Normal file
View File

@@ -0,0 +1,68 @@
// 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 repo
import (
"os"
"path/filepath"
"github.com/Unknwon/com"
"github.com/go-martini/martini"
"github.com/gogits/gogs/modules/base"
"github.com/gogits/gogs/modules/middleware"
)
func SingleDownload(ctx *middleware.Context, params martini.Params) {
// Get tree path
treename := params["_1"]
blob, err := ctx.Repo.Commit.GetBlobByPath(treename)
if err != nil {
ctx.Handle(404, "repo.SingleDownload(GetBlobByPath)", err)
return
}
data, err := blob.Data()
if err != nil {
ctx.Handle(404, "repo.SingleDownload(Data)", err)
return
}
contentType, isTextFile := base.IsTextFile(data)
_, isImageFile := base.IsImageFile(data)
ctx.Res.Header().Set("Content-Type", contentType)
if !isTextFile && !isImageFile {
ctx.Res.Header().Set("Content-Disposition", "attachment; filename="+filepath.Base(treename))
ctx.Res.Header().Set("Content-Transfer-Encoding", "binary")
}
ctx.Res.Write(data)
}
func ZipDownload(ctx *middleware.Context, params martini.Params) {
commitId := ctx.Repo.CommitId
archivesPath := filepath.Join(ctx.Repo.GitRepo.Path, "archives")
if !com.IsDir(archivesPath) {
if err := os.Mkdir(archivesPath, 0755); err != nil {
ctx.Handle(404, "ZipDownload -> os.Mkdir(archivesPath)", err)
return
}
}
zipPath := filepath.Join(archivesPath, commitId+".zip")
if com.IsFile(zipPath) {
ctx.ServeFile(zipPath, ctx.Repo.Repository.Name+".zip")
return
}
err := ctx.Repo.Commit.CreateArchive(zipPath)
if err != nil {
ctx.Handle(404, "ZipDownload -> CreateArchive "+zipPath, err)
return
}
ctx.ServeFile(zipPath, ctx.Repo.Repository.Name+".zip")
}

55
routers/repo/git.go Normal file
View File

@@ -0,0 +1,55 @@
package repo
import (
"fmt"
"strings"
)
const advertise_refs = "--advertise-refs"
func command(cmd string, opts ...string) string {
return fmt.Sprintf("git %s %s", cmd, strings.Join(opts, " "))
}
/*func upload_pack(repository_path string, opts ...string) string {
cmd = "upload-pack"
opts = append(opts, "--stateless-rpc", repository_path)
return command(cmd, opts...)
}
func receive_pack(repository_path string, opts ...string) string {
cmd = "receive-pack"
opts = append(opts, "--stateless-rpc", repository_path)
return command(cmd, opts...)
}*/
/*func update_server_info(repository_path, opts = {}, &block)
cmd = "update-server-info"
args = []
opts.each {|k,v| args << command_options[k] if command_options.has_key?(k) }
opts[:args] = args
Dir.chdir(repository_path) do # "git update-server-info" does not take a parameter to specify the repository, so set the working directory to the repository
self.command(cmd, opts, &block)
end
end
def get_config_setting(repository_path, key)
path = get_config_location(repository_path)
raise "Config file could not be found for repository in #{repository_path}." unless path
self.command("config", {:args => ["-f #{path}", key]}).chomp
end
def get_config_location(repository_path)
non_bare = File.join(repository_path,'.git') # This is where the config file will be if the repository is non-bare
if File.exists?(non_bare) then # The repository is non-bare
non_bare_config = File.join(non_bare, 'config')
return non_bare_config if File.exists?(non_bare_config)
else # We are dealing with a bare repository
bare_config = File.join(repository_path, "config")
return bare_config if File.exists?(bare_config)
end
return nil
end
end
*/

496
routers/repo/http.go Normal file
View File

@@ -0,0 +1,496 @@
// 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 repo
import (
"bytes"
"fmt"
"io"
"io/ioutil"
"log"
"net/http"
"os"
"os/exec"
"path"
"path/filepath"
"regexp"
"strconv"
"strings"
"time"
"github.com/go-martini/martini"
"github.com/gogits/gogs/models"
"github.com/gogits/gogs/modules/base"
"github.com/gogits/gogs/modules/middleware"
)
func Http(ctx *middleware.Context, params martini.Params) {
username := params["username"]
reponame := params["reponame"]
if strings.HasSuffix(reponame, ".git") {
reponame = reponame[:len(reponame)-4]
}
var isPull bool
service := ctx.Query("service")
if service == "git-receive-pack" ||
strings.HasSuffix(ctx.Req.URL.Path, "git-receive-pack") {
isPull = false
} else if service == "git-upload-pack" ||
strings.HasSuffix(ctx.Req.URL.Path, "git-upload-pack") {
isPull = true
} else {
isPull = (ctx.Req.Method == "GET")
}
repoUser, err := models.GetUserByName(username)
if err != nil {
ctx.Handle(500, "repo.GetUserByName", nil)
return
}
repo, err := models.GetRepositoryByName(repoUser.Id, reponame)
if err != nil {
ctx.Handle(500, "repo.GetRepositoryByName", nil)
return
}
// only public pull don't need auth
isPublicPull := !repo.IsPrivate && isPull
var askAuth = !isPublicPull || base.Service.RequireSignInView
var authUser *models.User
// check access
if askAuth {
baHead := ctx.Req.Header.Get("Authorization")
if baHead == "" {
// ask auth
authRequired(ctx)
return
}
auths := strings.Fields(baHead)
// currently check basic auth
// TODO: support digit auth
if len(auths) != 2 || auths[0] != "Basic" {
ctx.Handle(401, "no basic auth and digit auth", nil)
return
}
authUsername, passwd, err := basicDecode(auths[1])
if err != nil {
ctx.Handle(401, "no basic auth and digit auth", nil)
return
}
authUser, err = models.GetUserByName(authUsername)
if err != nil {
ctx.Handle(401, "no basic auth and digit auth", nil)
return
}
newUser := &models.User{Passwd: passwd, Salt: authUser.Salt}
newUser.EncodePasswd()
if authUser.Passwd != newUser.Passwd {
ctx.Handle(401, "no basic auth and digit auth", nil)
return
}
if !isPublicPull {
var tp = models.AU_WRITABLE
if isPull {
tp = models.AU_READABLE
}
has, err := models.HasAccess(authUsername, username+"/"+reponame, tp)
if err != nil {
ctx.Handle(401, "no basic auth and digit auth", nil)
return
} else if !has {
if tp == models.AU_READABLE {
has, err = models.HasAccess(authUsername, username+"/"+reponame, models.AU_WRITABLE)
if err != nil || !has {
ctx.Handle(401, "no basic auth and digit auth", nil)
return
}
} else {
ctx.Handle(401, "no basic auth and digit auth", nil)
return
}
}
}
}
config := Config{base.RepoRootPath, "git", true, true, func(rpc string, input []byte) {
if rpc == "receive-pack" {
firstLine := bytes.IndexRune(input, '\000')
if firstLine > -1 {
fields := strings.Fields(string(input[:firstLine]))
if len(fields) == 3 {
oldCommitId := fields[0][4:]
newCommitId := fields[1]
refName := fields[2]
models.Update(refName, oldCommitId, newCommitId, username, reponame, authUser.Id)
}
}
}
}}
handler := HttpBackend(&config)
handler(ctx.ResponseWriter, ctx.Req)
/* Webdav
dir := models.RepoPath(username, reponame)
prefix := path.Join("/", username, params["reponame"])
server := webdav.NewServer(
dir, prefix, true)
server.ServeHTTP(ctx.ResponseWriter, ctx.Req)
*/
}
type route struct {
cr *regexp.Regexp
method string
handler func(handler)
}
type Config struct {
ReposRoot string
GitBinPath string
UploadPack bool
ReceivePack bool
OnSucceed func(rpc string, input []byte)
}
type handler struct {
*Config
w http.ResponseWriter
r *http.Request
Dir string
File string
}
var routes = []route{
{regexp.MustCompile("(.*?)/git-upload-pack$"), "POST", serviceUploadPack},
{regexp.MustCompile("(.*?)/git-receive-pack$"), "POST", serviceReceivePack},
{regexp.MustCompile("(.*?)/info/refs$"), "GET", getInfoRefs},
{regexp.MustCompile("(.*?)/HEAD$"), "GET", getTextFile},
{regexp.MustCompile("(.*?)/objects/info/alternates$"), "GET", getTextFile},
{regexp.MustCompile("(.*?)/objects/info/http-alternates$"), "GET", getTextFile},
{regexp.MustCompile("(.*?)/objects/info/packs$"), "GET", getInfoPacks},
{regexp.MustCompile("(.*?)/objects/info/[^/]*$"), "GET", getTextFile},
{regexp.MustCompile("(.*?)/objects/[0-9a-f]{2}/[0-9a-f]{38}$"), "GET", getLooseObject},
{regexp.MustCompile("(.*?)/objects/pack/pack-[0-9a-f]{40}\\.pack$"), "GET", getPackFile},
{regexp.MustCompile("(.*?)/objects/pack/pack-[0-9a-f]{40}\\.idx$"), "GET", getIdxFile},
}
// Request handling function
func HttpBackend(config *Config) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
//log.Printf("%s %s %s %s", r.RemoteAddr, r.Method, r.URL.Path, r.Proto)
for _, route := range routes {
if m := route.cr.FindStringSubmatch(r.URL.Path); m != nil {
if route.method != r.Method {
renderMethodNotAllowed(w, r)
return
}
file := strings.Replace(r.URL.Path, m[1]+"/", "", 1)
dir, err := getGitDir(config, m[1])
if err != nil {
log.Print(err)
renderNotFound(w)
return
}
hr := handler{config, w, r, dir, file}
route.handler(hr)
return
}
}
renderNotFound(w)
return
}
}
// Actual command handling functions
func serviceUploadPack(hr handler) {
serviceRpc("upload-pack", hr)
}
func serviceReceivePack(hr handler) {
serviceRpc("receive-pack", hr)
}
func serviceRpc(rpc string, hr handler) {
w, r, dir := hr.w, hr.r, hr.Dir
access := hasAccess(r, hr.Config, dir, rpc, true)
if access == false {
renderNoAccess(w)
return
}
input, _ := ioutil.ReadAll(r.Body)
w.Header().Set("Content-Type", fmt.Sprintf("application/x-git-%s-result", rpc))
w.WriteHeader(http.StatusOK)
args := []string{rpc, "--stateless-rpc", dir}
cmd := exec.Command(hr.Config.GitBinPath, args...)
cmd.Dir = dir
in, err := cmd.StdinPipe()
if err != nil {
log.Print(err)
return
}
stdout, err := cmd.StdoutPipe()
if err != nil {
log.Print(err)
return
}
err = cmd.Start()
if err != nil {
log.Print(err)
return
}
in.Write(input)
io.Copy(w, stdout)
cmd.Wait()
if hr.Config.OnSucceed != nil {
hr.Config.OnSucceed(rpc, input)
}
}
func getInfoRefs(hr handler) {
w, r, dir := hr.w, hr.r, hr.Dir
serviceName := getServiceType(r)
access := hasAccess(r, hr.Config, dir, serviceName, false)
if access {
args := []string{serviceName, "--stateless-rpc", "--advertise-refs", "."}
refs := gitCommand(hr.Config.GitBinPath, dir, args...)
hdrNocache(w)
w.Header().Set("Content-Type", fmt.Sprintf("application/x-git-%s-advertisement", serviceName))
w.WriteHeader(http.StatusOK)
w.Write(packetWrite("# service=git-" + serviceName + "\n"))
w.Write(packetFlush())
w.Write(refs)
} else {
updateServerInfo(hr.Config.GitBinPath, dir)
hdrNocache(w)
sendFile("text/plain; charset=utf-8", hr)
}
}
func getInfoPacks(hr handler) {
hdrCacheForever(hr.w)
sendFile("text/plain; charset=utf-8", hr)
}
func getLooseObject(hr handler) {
hdrCacheForever(hr.w)
sendFile("application/x-git-loose-object", hr)
}
func getPackFile(hr handler) {
hdrCacheForever(hr.w)
sendFile("application/x-git-packed-objects", hr)
}
func getIdxFile(hr handler) {
hdrCacheForever(hr.w)
sendFile("application/x-git-packed-objects-toc", hr)
}
func getTextFile(hr handler) {
hdrNocache(hr.w)
sendFile("text/plain", hr)
}
// Logic helping functions
func sendFile(contentType string, hr handler) {
w, r := hr.w, hr.r
reqFile := path.Join(hr.Dir, hr.File)
//fmt.Println("sendFile:", reqFile)
f, err := os.Stat(reqFile)
if os.IsNotExist(err) {
renderNotFound(w)
return
}
w.Header().Set("Content-Type", contentType)
w.Header().Set("Content-Length", fmt.Sprintf("%d", f.Size()))
w.Header().Set("Last-Modified", f.ModTime().Format(http.TimeFormat))
http.ServeFile(w, r, reqFile)
}
func getGitDir(config *Config, fPath string) (string, error) {
root := config.ReposRoot
if root == "" {
cwd, err := os.Getwd()
if err != nil {
log.Print(err)
return "", err
}
root = cwd
}
if !strings.HasSuffix(fPath, ".git") {
fPath = fPath + ".git"
}
f := filepath.Join(root, fPath)
if _, err := os.Stat(f); os.IsNotExist(err) {
return "", err
}
return f, nil
}
func getServiceType(r *http.Request) string {
serviceType := r.FormValue("service")
if s := strings.HasPrefix(serviceType, "git-"); !s {
return ""
}
return strings.Replace(serviceType, "git-", "", 1)
}
func hasAccess(r *http.Request, config *Config, dir string, rpc string, checkContentType bool) bool {
if checkContentType {
if r.Header.Get("Content-Type") != fmt.Sprintf("application/x-git-%s-request", rpc) {
return false
}
}
if !(rpc == "upload-pack" || rpc == "receive-pack") {
return false
}
if rpc == "receive-pack" {
return config.ReceivePack
}
if rpc == "upload-pack" {
return config.UploadPack
}
return getConfigSetting(config.GitBinPath, rpc, dir)
}
func getConfigSetting(gitBinPath, serviceName string, dir string) bool {
serviceName = strings.Replace(serviceName, "-", "", -1)
setting := getGitConfig(gitBinPath, "http."+serviceName, dir)
if serviceName == "uploadpack" {
return setting != "false"
}
return setting == "true"
}
func getGitConfig(gitBinPath, configName string, dir string) string {
args := []string{"config", configName}
out := string(gitCommand(gitBinPath, dir, args...))
return out[0 : len(out)-1]
}
func updateServerInfo(gitBinPath, dir string) []byte {
args := []string{"update-server-info"}
return gitCommand(gitBinPath, dir, args...)
}
func gitCommand(gitBinPath, dir string, args ...string) []byte {
command := exec.Command(gitBinPath, args...)
command.Dir = dir
out, err := command.Output()
if err != nil {
log.Print(err)
}
return out
}
// HTTP error response handling functions
func renderMethodNotAllowed(w http.ResponseWriter, r *http.Request) {
if r.Proto == "HTTP/1.1" {
w.WriteHeader(http.StatusMethodNotAllowed)
w.Write([]byte("Method Not Allowed"))
} else {
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte("Bad Request"))
}
}
func renderNotFound(w http.ResponseWriter) {
w.WriteHeader(http.StatusNotFound)
w.Write([]byte("Not Found"))
}
func renderNoAccess(w http.ResponseWriter) {
w.WriteHeader(http.StatusForbidden)
w.Write([]byte("Forbidden"))
}
// Packet-line handling function
func packetFlush() []byte {
return []byte("0000")
}
func packetWrite(str string) []byte {
s := strconv.FormatInt(int64(len(str)+4), 16)
if len(s)%4 != 0 {
s = strings.Repeat("0", 4-len(s)%4) + s
}
return []byte(s + str)
}
// Header writing functions
func hdrNocache(w http.ResponseWriter) {
w.Header().Set("Expires", "Fri, 01 Jan 1980 00:00:00 GMT")
w.Header().Set("Pragma", "no-cache")
w.Header().Set("Cache-Control", "no-cache, max-age=0, must-revalidate")
}
func hdrCacheForever(w http.ResponseWriter) {
now := time.Now().Unix()
expires := now + 31536000
w.Header().Set("Date", fmt.Sprintf("%d", now))
w.Header().Set("Expires", fmt.Sprintf("%d", expires))
w.Header().Set("Cache-Control", "public, max-age=31536000")
}
// Main
/*
func main() {
http.HandleFunc("/", requestHandler())
err := http.ListenAndServe(":8080", nil)
if err != nil {
log.Fatal("ListenAndServe: ", err)
}
}*/

View File

@@ -9,6 +9,7 @@ import (
"net/url"
"strings"
"github.com/Unknwon/com"
"github.com/go-martini/martini"
"github.com/gogits/gogs/models"
@@ -31,7 +32,8 @@ func Issues(ctx *middleware.Context) {
ctx.Data["IssueCreatedCount"] = 0
var posterId int64 = 0
if ctx.Query("type") == "created_by" {
isCreatedBy := ctx.Query("type") == "created_by"
if isCreatedBy {
if !ctx.IsSigned {
ctx.SetCookie("redirect_to", "/"+url.QueryEscape(ctx.Req.RequestURI))
ctx.Redirect("/user/login/", 302)
@@ -53,6 +55,7 @@ func Issues(ctx *middleware.Context) {
}
var createdByCount int
showIssues := make([]models.Issue, 0, len(issues))
// Get posters.
for i := range issues {
u, err := models.GetUserById(issues[i].PosterId)
@@ -60,30 +63,36 @@ func Issues(ctx *middleware.Context) {
ctx.Handle(200, "issue.Issues(get poster): %v", err)
return
}
issues[i].Poster = u
if isCreatedBy && u.Id != posterId {
continue
}
if u.Id == posterId {
createdByCount++
}
issues[i].Poster = u
showIssues = append(showIssues, issues[i])
}
ctx.Data["Issues"] = issues
ctx.Data["Issues"] = showIssues
ctx.Data["IssueCount"] = ctx.Repo.Repository.NumIssues
ctx.Data["OpenCount"] = ctx.Repo.Repository.NumIssues - ctx.Repo.Repository.NumClosedIssues
ctx.Data["OpenCount"] = ctx.Repo.Repository.NumOpenIssues
ctx.Data["ClosedCount"] = ctx.Repo.Repository.NumClosedIssues
ctx.Data["IssueCreatedCount"] = createdByCount
ctx.Data["IsShowClosed"] = ctx.Query("state") == "closed"
ctx.HTML(200, "issue/list")
}
func CreateIssue(ctx *middleware.Context, params martini.Params, form auth.CreateIssueForm) {
func CreateIssue(ctx *middleware.Context, params martini.Params) {
ctx.Data["Title"] = "Create issue"
ctx.Data["IsRepoToolbarIssues"] = true
ctx.Data["IsRepoToolbarIssuesList"] = false
ctx.HTML(200, "issue/create")
}
if ctx.Req.Method == "GET" {
ctx.HTML(200, "issue/create")
return
}
func CreateIssuePost(ctx *middleware.Context, params martini.Params, form auth.CreateIssueForm) {
ctx.Data["Title"] = "Create issue"
ctx.Data["IsRepoToolbarIssues"] = true
ctx.Data["IsRepoToolbarIssuesList"] = false
if ctx.HasError() {
ctx.HTML(200, "issue/create")
@@ -93,7 +102,7 @@ func CreateIssue(ctx *middleware.Context, params martini.Params, form auth.Creat
issue, err := models.CreateIssue(ctx.User.Id, ctx.Repo.Repository.Id, form.MilestoneId, form.AssigneeId,
ctx.Repo.Repository.NumIssues, form.IssueName, form.Labels, form.Content, false)
if err != nil {
ctx.Handle(200, "issue.CreateIssue", err)
ctx.Handle(500, "issue.CreateIssue(CreateIssue)", err)
return
}
@@ -101,19 +110,36 @@ func CreateIssue(ctx *middleware.Context, params martini.Params, form auth.Creat
if err = models.NotifyWatchers(&models.Action{ActUserId: ctx.User.Id, ActUserName: ctx.User.Name, ActEmail: ctx.User.Email,
OpType: models.OP_CREATE_ISSUE, Content: fmt.Sprintf("%d|%s", issue.Index, issue.Name),
RepoId: ctx.Repo.Repository.Id, RepoName: ctx.Repo.Repository.Name, RefName: ""}); err != nil {
ctx.Handle(200, "issue.CreateIssue", err)
ctx.Handle(500, "issue.CreateIssue(NotifyWatchers)", err)
return
}
// Mail watchers.
// Mail watchers and mentions.
if base.Service.NotifyMail {
if err = mailer.SendNotifyMail(ctx.User.Id, ctx.Repo.Repository.Id, ctx.User.Name, ctx.Repo.Repository.Name, issue.Name, issue.Content); err != nil {
ctx.Handle(200, "issue.CreateIssue", err)
tos, err := mailer.SendIssueNotifyMail(ctx.User, ctx.Repo.Owner, ctx.Repo.Repository, issue)
if err != nil {
ctx.Handle(500, "issue.CreateIssue(SendIssueNotifyMail)", err)
return
}
tos = append(tos, ctx.User.LowerName)
ms := base.MentionPattern.FindAllString(issue.Content, -1)
newTos := make([]string, 0, len(ms))
for _, m := range ms {
if com.IsSliceContainsStr(tos, m[1:]) {
continue
}
newTos = append(newTos, m[1:])
}
if err = mailer.SendIssueMentionMail(ctx.User, ctx.Repo.Owner, ctx.Repo.Repository,
issue, models.GetUserEmailsByNames(newTos)); err != nil {
ctx.Handle(500, "issue.CreateIssue(SendIssueMentionMail)", err)
return
}
}
log.Trace("%d Issue created: %d", ctx.Repo.Repository.Id, issue.Id)
ctx.Redirect(fmt.Sprintf("/%s/%s/issues/%d", params["username"], params["reponame"], issue.Index))
}
@@ -141,7 +167,7 @@ func ViewIssue(ctx *middleware.Context, params martini.Params) {
return
}
issue.Poster = u
issue.RenderedContent = string(base.RenderMarkdown([]byte(issue.Content), ""))
issue.RenderedContent = string(base.RenderMarkdown([]byte(issue.Content), ctx.Repo.RepoLink))
// Get comments.
comments, err := models.GetIssueComments(issue.Id)
@@ -158,7 +184,7 @@ func ViewIssue(ctx *middleware.Context, params martini.Params) {
return
}
comments[i].Poster = u
comments[i].Content = string(base.RenderMarkdown([]byte(comments[i].Content), ""))
comments[i].Content = string(base.RenderMarkdown([]byte(comments[i].Content), ctx.Repo.RepoLink))
}
ctx.Data["Title"] = issue.Name
@@ -187,7 +213,7 @@ func UpdateIssue(ctx *middleware.Context, params martini.Params, form auth.Creat
return
}
if ctx.User.Id != issue.PosterId {
if ctx.User.Id != issue.PosterId && !ctx.Repo.IsOwner {
ctx.Handle(404, "issue.UpdateIssue", nil)
return
}
@@ -205,7 +231,7 @@ func UpdateIssue(ctx *middleware.Context, params martini.Params, form auth.Creat
ctx.JSON(200, map[string]interface{}{
"ok": true,
"title": issue.Name,
"content": string(base.RenderMarkdown([]byte(issue.Content), "")),
"content": string(base.RenderMarkdown([]byte(issue.Content), ctx.Repo.RepoLink)),
})
}

156
routers/repo/release.go Normal file
View File

@@ -0,0 +1,156 @@
// 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 repo
import (
"sort"
"github.com/gogits/gogs/models"
"github.com/gogits/gogs/modules/auth"
"github.com/gogits/gogs/modules/base"
"github.com/gogits/gogs/modules/log"
"github.com/gogits/gogs/modules/middleware"
)
type ReleaseSorter struct {
rels []*models.Release
}
func (rs *ReleaseSorter) Len() int {
return len(rs.rels)
}
func (rs *ReleaseSorter) Less(i, j int) bool {
return rs.rels[i].NumCommits > rs.rels[j].NumCommits
}
func (rs *ReleaseSorter) Swap(i, j int) {
rs.rels[i], rs.rels[j] = rs.rels[j], rs.rels[i]
}
func Releases(ctx *middleware.Context) {
ctx.Data["Title"] = "Releases"
ctx.Data["IsRepoToolbarReleases"] = true
ctx.Data["IsRepoReleaseNew"] = false
rawTags, err := ctx.Repo.GitRepo.GetTags()
if err != nil {
ctx.Handle(500, "release.Releases(GetTags)", err)
return
}
rels, err := models.GetReleasesByRepoId(ctx.Repo.Repository.Id)
if err != nil {
ctx.Handle(500, "release.Releases(GetReleasesByRepoId)", err)
return
}
commitsCount, err := ctx.Repo.Commit.CommitsCount()
if err != nil {
ctx.Handle(500, "release.Releases(CommitsCount)", err)
return
}
var tags ReleaseSorter
tags.rels = make([]*models.Release, len(rawTags))
for i, rawTag := range rawTags {
for _, rel := range rels {
if rel.TagName == rawTag {
rel.Publisher, err = models.GetUserById(rel.PublisherId)
if err != nil {
ctx.Handle(500, "release.Releases(GetUserById)", err)
return
}
rel.NumCommitsBehind = commitsCount - rel.NumCommits
rel.Note = base.RenderMarkdownString(rel.Note, ctx.Repo.RepoLink)
tags.rels[i] = rel
break
}
}
if tags.rels[i] == nil {
commit, err := ctx.Repo.GitRepo.GetCommitOfTag(rawTag)
if err != nil {
ctx.Handle(500, "release.Releases(GetCommitOfTag)", err)
return
}
tags.rels[i] = &models.Release{
Title: rawTag,
TagName: rawTag,
SHA1: commit.Id.String(),
}
tags.rels[i].NumCommits, err = ctx.Repo.GitRepo.CommitsCount(commit.Id.String())
if err != nil {
ctx.Handle(500, "release.Releases(CommitsCount)", err)
return
}
tags.rels[i].NumCommitsBehind = commitsCount - tags.rels[i].NumCommits
tags.rels[i].Created = commit.Author.When
}
}
sort.Sort(&tags)
ctx.Data["Releases"] = tags.rels
ctx.HTML(200, "release/list")
}
func ReleasesNew(ctx *middleware.Context) {
if !ctx.Repo.IsOwner {
ctx.Handle(404, "release.ReleasesNew", nil)
return
}
ctx.Data["Title"] = "New Release"
ctx.Data["IsRepoToolbarReleases"] = true
ctx.Data["IsRepoReleaseNew"] = true
ctx.HTML(200, "release/new")
}
func ReleasesNewPost(ctx *middleware.Context, form auth.NewReleaseForm) {
if !ctx.Repo.IsOwner {
ctx.Handle(404, "release.ReleasesNew", nil)
return
}
ctx.Data["Title"] = "New Release"
ctx.Data["IsRepoToolbarReleases"] = true
ctx.Data["IsRepoReleaseNew"] = true
if ctx.HasError() {
ctx.HTML(200, "release/new")
return
}
commitsCount, err := ctx.Repo.Commit.CommitsCount()
if err != nil {
ctx.Handle(500, "release.ReleasesNewPost(CommitsCount)", err)
return
}
rel := &models.Release{
RepoId: ctx.Repo.Repository.Id,
PublisherId: ctx.User.Id,
Title: form.Title,
TagName: form.TagName,
SHA1: ctx.Repo.Commit.Id.String(),
NumCommits: commitsCount,
Note: form.Content,
IsPrerelease: form.Prerelease,
}
if err = models.CreateRelease(models.RepoPath(ctx.User.Name, ctx.Repo.Repository.Name),
rel, ctx.Repo.GitRepo); err != nil {
if err == models.ErrReleaseAlreadyExist {
ctx.RenderWithErr("Release with this tag name has already existed", "release/new", &form)
} else {
ctx.Handle(500, "release.ReleasesNewPost(IsReleaseExist)", err)
}
return
}
log.Trace("%s Release created: %s/%s:%s", ctx.Req.RequestURI, ctx.User.LowerName, ctx.Repo.Repository.Name, form.TagName)
ctx.Redirect(ctx.Repo.RepoLink + "/releases")
}

View File

@@ -5,14 +5,16 @@
package repo
import (
"encoding/base64"
"errors"
"fmt"
"github.com/gogits/git"
"path"
"path/filepath"
"strings"
"github.com/go-martini/martini"
"github.com/gogits/webdav"
"github.com/gogits/gogs/models"
"github.com/gogits/gogs/modules/auth"
"github.com/gogits/gogs/modules/base"
@@ -20,24 +22,27 @@ import (
"github.com/gogits/gogs/modules/middleware"
)
func Create(ctx *middleware.Context, form auth.CreateRepoForm) {
func Create(ctx *middleware.Context) {
ctx.Data["Title"] = "Create repository"
ctx.Data["PageIsNewRepo"] = true // For navbar arrow.
ctx.Data["PageIsNewRepo"] = true
ctx.Data["LanguageIgns"] = models.LanguageIgns
ctx.Data["Licenses"] = models.Licenses
ctx.HTML(200, "repo/create")
}
if ctx.Req.Method == "GET" {
ctx.HTML(200, "repo/create")
return
}
func CreatePost(ctx *middleware.Context, form auth.CreateRepoForm) {
ctx.Data["Title"] = "Create repository"
ctx.Data["PageIsNewRepo"] = true
ctx.Data["LanguageIgns"] = models.LanguageIgns
ctx.Data["Licenses"] = models.Licenses
if ctx.HasError() {
ctx.HTML(200, "repo/create")
return
}
_, err := models.CreateRepository(ctx.User, form.RepoName, form.Description,
form.Language, form.License, form.Visibility == "private", form.InitReadme == "on")
repo, err := models.CreateRepository(ctx.User, form.RepoName, form.Description,
form.Language, form.License, form.Private, false, form.InitReadme)
if err == nil {
log.Trace("%s Repository created: %s/%s", ctx.Req.RequestURI, ctx.User.LowerName, form.RepoName)
ctx.Redirect("/" + ctx.User.Name + "/" + form.RepoName)
@@ -49,12 +54,60 @@ func Create(ctx *middleware.Context, form auth.CreateRepoForm) {
ctx.RenderWithErr(models.ErrRepoNameIllegal.Error(), "repo/create", &form)
return
}
ctx.Handle(200, "repo.Create", err)
if repo != nil {
if errDelete := models.DeleteRepository(ctx.User.Id, repo.Id, ctx.User.Name); errDelete != nil {
log.Error("repo.MigratePost(CreatePost): %v", errDelete)
}
}
ctx.Handle(500, "repo.Create", err)
}
func Migrate(ctx *middleware.Context) {
ctx.Data["Title"] = "Migrate repository"
ctx.Data["PageIsNewRepo"] = true
ctx.HTML(200, "repo/migrate")
}
func MigratePost(ctx *middleware.Context, form auth.MigrateRepoForm) {
ctx.Data["Title"] = "Migrate repository"
ctx.Data["PageIsNewRepo"] = true
if ctx.HasError() {
ctx.HTML(200, "repo/migrate")
return
}
url := strings.Replace(form.Url, "://", fmt.Sprintf("://%s:%s@", form.AuthUserName, form.AuthPasswd), 1)
repo, err := models.MigrateRepository(ctx.User, form.RepoName, form.Description, form.Private,
form.Mirror, url)
if err == nil {
log.Trace("%s Repository migrated: %s/%s", ctx.Req.RequestURI, ctx.User.LowerName, form.RepoName)
ctx.Redirect("/" + ctx.User.Name + "/" + form.RepoName)
return
} else if err == models.ErrRepoAlreadyExist {
ctx.RenderWithErr("Repository name has already been used", "repo/migrate", &form)
return
} else if err == models.ErrRepoNameIllegal {
ctx.RenderWithErr(models.ErrRepoNameIllegal.Error(), "repo/migrate", &form)
return
}
if repo != nil {
if errDelete := models.DeleteRepository(ctx.User.Id, repo.Id, ctx.User.Name); errDelete != nil {
log.Error("repo.MigratePost(DeleteRepository): %v", errDelete)
}
}
if strings.Contains(err.Error(), "Authentication failed") {
ctx.RenderWithErr(err.Error(), "repo/migrate", &form)
return
}
ctx.Handle(500, "repo.Migrate", err)
}
func Single(ctx *middleware.Context, params martini.Params) {
branchName := ctx.Repo.BranchName
commitId := ctx.Repo.CommitId
userName := ctx.Repo.Owner.Name
repoName := ctx.Repo.Repository.Name
@@ -72,46 +125,42 @@ func Single(ctx *middleware.Context, params martini.Params) {
ctx.Data["IsRepoToolbarSource"] = true
// Branches.
brs, err := models.GetBranches(userName, repoName)
if err != nil {
ctx.Handle(404, "repo.Single(GetBranches)", err)
return
}
ctx.Data["Branches"] = brs
isViewBranch := ctx.Repo.IsBranch
ctx.Data["IsViewBranch"] = isViewBranch
repoFile, err := models.GetTargetFile(userName, repoName,
branchName, commitId, treename)
treePath := treename
if len(treePath) != 0 {
treePath = treePath + "/"
}
if err != nil && err != models.ErrRepoFileNotExist {
ctx.Handle(404, "repo.Single(GetTargetFile)", err)
entry, err := ctx.Repo.Commit.GetTreeEntryByPath(treename)
if err != nil && err != git.ErrNotExist {
ctx.Handle(404, "repo.Single(GetTreeEntryByPath)", err)
return
}
if len(treename) != 0 && repoFile == nil {
if len(treename) != 0 && entry == nil {
ctx.Handle(404, "repo.Single", nil)
return
}
if repoFile != nil && repoFile.IsFile() {
if blob, err := repoFile.LookupBlob(); err != nil {
ctx.Handle(404, "repo.Single(repoFile.LookupBlob)", err)
if entry != nil && !entry.IsDir() {
blob := entry.Blob()
if data, err := blob.Data(); err != nil {
ctx.Handle(404, "repo.Single(blob.Data)", err)
} else {
ctx.Data["FileSize"] = repoFile.Size
ctx.Data["FileSize"] = blob.Size()
ctx.Data["IsFile"] = true
ctx.Data["FileName"] = repoFile.Name
ext := path.Ext(repoFile.Name)
ctx.Data["FileName"] = blob.Name()
ext := path.Ext(blob.Name())
if len(ext) > 0 {
ext = ext[1:]
}
ctx.Data["FileExt"] = ext
ctx.Data["FileLink"] = rawLink + "/" + treename
data := blob.Contents()
_, isTextFile := base.IsTextFile(data)
_, isImageFile := base.IsImageFile(data)
ctx.Data["FileIsText"] = isTextFile
@@ -119,7 +168,7 @@ func Single(ctx *middleware.Context, params martini.Params) {
if isImageFile {
ctx.Data["IsImageFile"] = true
} else {
readmeExist := base.IsMarkdownFile(repoFile.Name) || base.IsReadmeFile(repoFile.Name)
readmeExist := base.IsMarkdownFile(blob.Name()) || base.IsReadmeFile(blob.Name())
ctx.Data["ReadmeExist"] = readmeExist
if readmeExist {
ctx.Data["FileContent"] = string(base.RenderMarkdown(data, ""))
@@ -133,21 +182,35 @@ func Single(ctx *middleware.Context, params martini.Params) {
} else {
// Directory and file list.
files, err := models.GetReposFiles(userName, repoName, ctx.Repo.CommitId, treename)
tree, err := ctx.Repo.Commit.SubTree(treename)
if err != nil {
ctx.Handle(404, "repo.Single(GetReposFiles)", err)
ctx.Handle(404, "repo.Single(SubTree)", err)
return
}
entries := tree.ListEntries()
entries.Sort()
files := make([][]interface{}, 0, len(entries))
for _, te := range entries {
c, err := ctx.Repo.Commit.GetCommitOfRelPath(filepath.Join(treePath, te.Name()))
if err != nil {
ctx.Handle(404, "repo.Single(SubTree)", err)
return
}
files = append(files, []interface{}{te, c})
}
ctx.Data["Files"] = files
var readmeFile *models.RepoFile
var readmeFile *git.Blob
for _, f := range files {
if !f.IsFile() || !base.IsReadmeFile(f.Name) {
for _, f := range entries {
if f.IsDir() || !base.IsReadmeFile(f.Name()) {
continue
} else {
readmeFile = f
readmeFile = f.Blob()
break
}
}
@@ -155,16 +218,15 @@ func Single(ctx *middleware.Context, params martini.Params) {
if readmeFile != nil {
ctx.Data["ReadmeInSingle"] = true
ctx.Data["ReadmeExist"] = true
if blob, err := readmeFile.LookupBlob(); err != nil {
if data, err := readmeFile.Data(); err != nil {
ctx.Handle(404, "repo.Single(readmeFile.LookupBlob)", err)
return
} else {
ctx.Data["FileSize"] = readmeFile.Size
ctx.Data["FileLink"] = rawLink + "/" + treename
data := blob.Contents()
_, isTextFile := base.IsTextFile(data)
ctx.Data["FileIsText"] = isTextFile
ctx.Data["FileName"] = readmeFile.Name
ctx.Data["FileName"] = readmeFile.Name()
if isTextFile {
ctx.Data["FileContent"] = string(base.RenderMarkdown(data, branchLink))
}
@@ -193,64 +255,36 @@ func Single(ctx *middleware.Context, params martini.Params) {
ctx.Data["LastCommit"] = ctx.Repo.Commit
ctx.Data["Paths"] = Paths
ctx.Data["Treenames"] = treenames
ctx.Data["TreePath"] = treePath
ctx.Data["BranchLink"] = branchLink
ctx.HTML(200, "repo/single")
}
func SingleDownload(ctx *middleware.Context, params martini.Params) {
// Get tree path
treename := params["_1"]
branchName := params["branchname"]
userName := params["username"]
repoName := params["reponame"]
var commitId string
if !models.IsBranchExist(userName, repoName, branchName) {
commitId = branchName
branchName = ""
}
repoFile, err := models.GetTargetFile(userName, repoName,
branchName, commitId, treename)
if err != nil {
ctx.Handle(404, "repo.SingleDownload(GetTargetFile)", err)
return
}
blob, err := repoFile.LookupBlob()
if err != nil {
ctx.Handle(404, "repo.SingleDownload(LookupBlob)", err)
return
}
data := blob.Contents()
contentType, isTextFile := base.IsTextFile(data)
_, isImageFile := base.IsImageFile(data)
ctx.Res.Header().Set("Content-Type", contentType)
if !isTextFile && !isImageFile {
ctx.Res.Header().Set("Content-Disposition", "attachment; filename="+filepath.Base(treename))
ctx.Res.Header().Set("Content-Transfer-Encoding", "binary")
}
ctx.Res.Write(data)
func basicEncode(username, password string) string {
auth := username + ":" + password
return base64.StdEncoding.EncodeToString([]byte(auth))
}
func Http(ctx *middleware.Context, params martini.Params) {
// TODO: access check
username := params["username"]
reponame := params["reponame"]
if strings.HasSuffix(reponame, ".git") {
reponame = reponame[:len(reponame)-4]
func basicDecode(encoded string) (user string, name string, err error) {
var s []byte
s, err = base64.StdEncoding.DecodeString(encoded)
if err != nil {
return
}
prefix := path.Join("/", username, params["reponame"])
server := webdav.NewServer(
models.RepoPath(username, reponame),
prefix, true)
a := strings.Split(string(s), ":")
if len(a) == 2 {
user, name = a[0], a[1]
} else {
err = errors.New("decode failed")
}
return
}
server.ServeHTTP(ctx.ResponseWriter, ctx.Req)
func authRequired(ctx *middleware.Context) {
ctx.ResponseWriter.Header().Set("WWW-Authenticate", "Basic realm=\".\"")
ctx.Data["ErrorMsg"] = "no basic auth and digit auth"
ctx.HTML(401, fmt.Sprintf("status/401"))
}
func Setting(ctx *middleware.Context, params martini.Params) {
@@ -276,30 +310,92 @@ func SettingPost(ctx *middleware.Context) {
return
}
ctx.Data["IsRepoToolbarSetting"] = true
switch ctx.Query("action") {
case "update":
newRepoName := ctx.Query("name")
// Check if repository name has been changed.
if ctx.Repo.Repository.Name != newRepoName {
isExist, err := models.IsRepositoryExist(ctx.Repo.Owner, newRepoName)
if err != nil {
ctx.Handle(500, "repo.SettingPost(update: check existence)", err)
return
} else if isExist {
ctx.RenderWithErr("Repository name has been taken in your repositories.", "repo/setting", nil)
return
} else if err = models.ChangeRepositoryName(ctx.Repo.Owner.Name, ctx.Repo.Repository.Name, newRepoName); err != nil {
ctx.Handle(500, "repo.SettingPost(change repository name)", err)
return
}
log.Trace("%s Repository name changed: %s/%s -> %s", ctx.Req.RequestURI, ctx.User.Name, ctx.Repo.Repository.Name, newRepoName)
ctx.Repo.Repository.Name = newRepoName
}
br := ctx.Query("branch")
if git.IsBranchExist(models.RepoPath(ctx.User.Name, ctx.Repo.Repository.Name), br) {
ctx.Repo.Repository.DefaultBranch = br
}
ctx.Repo.Repository.Description = ctx.Query("desc")
ctx.Repo.Repository.Website = ctx.Query("site")
ctx.Repo.Repository.IsPrivate = ctx.Query("private") == "on"
ctx.Repo.Repository.IsGoget = ctx.Query("goget") == "on"
if err := models.UpdateRepository(ctx.Repo.Repository); err != nil {
ctx.Handle(404, "repo.SettingPost(update)", err)
return
}
ctx.Data["IsSuccess"] = true
ctx.HTML(200, "repo/setting")
log.Trace("%s Repository updated: %s/%s", ctx.Req.RequestURI, ctx.User.LowerName, ctx.Repo.Repository.LowerName)
log.Trace("%s Repository updated: %s/%s", ctx.Req.RequestURI, ctx.Repo.Owner.Name, ctx.Repo.Repository.Name)
if ctx.Repo.Repository.IsMirror {
if len(ctx.Query("interval")) > 0 {
var err error
ctx.Repo.Mirror.Interval, err = base.StrTo(ctx.Query("interval")).Int()
if err != nil {
log.Error("repo.SettingPost(get mirror interval): %v", err)
} else if err = models.UpdateMirror(ctx.Repo.Mirror); err != nil {
log.Error("repo.SettingPost(UpdateMirror): %v", err)
}
}
}
ctx.Flash.Success("Repository options has been successfully updated.")
ctx.Redirect(fmt.Sprintf("/%s/%s/settings", ctx.Repo.Owner.Name, ctx.Repo.Repository.Name))
case "transfer":
if len(ctx.Repo.Repository.Name) == 0 || ctx.Repo.Repository.Name != ctx.Query("repository") {
ctx.RenderWithErr("Please make sure you entered repository name is correct.", "repo/setting", nil)
return
}
newOwner := ctx.Query("owner")
// Check if new owner exists.
isExist, err := models.IsUserExist(newOwner)
if err != nil {
ctx.Handle(500, "repo.SettingPost(transfer: check existence)", err)
return
} else if !isExist {
ctx.RenderWithErr("Please make sure you entered owner name is correct.", "repo/setting", nil)
return
} else if err = models.TransferOwnership(ctx.User, newOwner, ctx.Repo.Repository); err != nil {
ctx.Handle(500, "repo.SettingPost(transfer repository)", err)
return
}
log.Trace("%s Repository transfered: %s/%s -> %s", ctx.Req.RequestURI, ctx.User.Name, ctx.Repo.Repository.Name, newOwner)
ctx.Redirect("/")
case "delete":
if len(ctx.Repo.Repository.Name) == 0 || ctx.Repo.Repository.Name != ctx.Query("repository") {
ctx.Data["ErrorMsg"] = "Please make sure you entered repository name is correct."
ctx.HTML(200, "repo/setting")
ctx.RenderWithErr("Please make sure you entered repository name is correct.", "repo/setting", nil)
return
}
if err := models.DeleteRepository(ctx.User.Id, ctx.Repo.Repository.Id, ctx.User.LowerName); err != nil {
ctx.Handle(200, "repo.Delete", err)
ctx.Handle(500, "repo.Delete", err)
return
}
log.Trace("%s Repository deleted: %s/%s", ctx.Req.RequestURI, ctx.User.LowerName, ctx.Repo.Repository.LowerName)
ctx.Redirect("/")
}
}

196
routers/user/home.go Normal file
View File

@@ -0,0 +1,196 @@
// 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 user
import (
"fmt"
"github.com/go-martini/martini"
"github.com/gogits/gogs/models"
"github.com/gogits/gogs/modules/auth"
"github.com/gogits/gogs/modules/base"
"github.com/gogits/gogs/modules/middleware"
)
func Dashboard(ctx *middleware.Context) {
ctx.Data["Title"] = "Dashboard"
ctx.Data["PageIsUserDashboard"] = true
repos, err := models.GetRepositories(&models.User{Id: ctx.User.Id}, true)
if err != nil {
ctx.Handle(500, "user.Dashboard", err)
return
}
ctx.Data["MyRepos"] = repos
feeds, err := models.GetFeeds(ctx.User.Id, 0, false)
if err != nil {
ctx.Handle(500, "user.Dashboard", err)
return
}
ctx.Data["Feeds"] = feeds
ctx.HTML(200, "user/dashboard")
}
func Profile(ctx *middleware.Context, params martini.Params) {
ctx.Data["Title"] = "Profile"
// TODO: Need to check view self or others.
user, err := models.GetUserByName(params["username"])
if err != nil {
ctx.Handle(500, "user.Profile", err)
return
}
ctx.Data["Owner"] = user
tab := ctx.Query("tab")
ctx.Data["TabName"] = tab
switch tab {
case "activity":
feeds, err := models.GetFeeds(user.Id, 0, true)
if err != nil {
ctx.Handle(500, "user.Profile", err)
return
}
ctx.Data["Feeds"] = feeds
default:
repos, err := models.GetRepositories(user, ctx.IsSigned && ctx.User.Id == user.Id)
if err != nil {
ctx.Handle(500, "user.Profile", err)
return
}
ctx.Data["Repos"] = repos
}
ctx.Data["PageIsUserProfile"] = true
ctx.HTML(200, "user/profile")
}
func Email2User(ctx *middleware.Context) {
u, err := models.GetUserByEmail(ctx.Query("email"))
if err != nil {
if err == models.ErrUserNotExist {
ctx.Handle(404, "user.Email2User", err)
} else {
ctx.Handle(500, "user.Email2User(GetUserByEmail)", err)
}
return
}
ctx.Redirect("/user/" + u.Name)
}
const (
TPL_FEED = `<i class="icon fa fa-%s"></i>
<div class="info"><span class="meta">%s</span><br>%s</div>`
)
func Feeds(ctx *middleware.Context, form auth.FeedsForm) {
actions, err := models.GetFeeds(form.UserId, form.Page*20, false)
if err != nil {
ctx.JSON(500, err)
}
feeds := make([]string, len(actions))
for i := range actions {
feeds[i] = fmt.Sprintf(TPL_FEED, base.ActionIcon(actions[i].OpType),
base.TimeSince(actions[i].Created), base.ActionDesc(actions[i]))
}
ctx.JSON(200, &feeds)
}
func Issues(ctx *middleware.Context) {
ctx.Data["Title"] = "Your Issues"
ctx.Data["ViewType"] = "all"
page, _ := base.StrTo(ctx.Query("page")).Int()
repoId, _ := base.StrTo(ctx.Query("repoid")).Int64()
ctx.Data["RepoId"] = repoId
var posterId int64 = 0
if ctx.Query("type") == "created_by" {
posterId = ctx.User.Id
ctx.Data["ViewType"] = "created_by"
}
// Get all repositories.
repos, err := models.GetRepositories(ctx.User, true)
if err != nil {
ctx.Handle(200, "user.Issues(get repositories)", err)
return
}
showRepos := make([]models.Repository, 0, len(repos))
isShowClosed := ctx.Query("state") == "closed"
var closedIssueCount, createdByCount, allIssueCount int
// Get all issues.
allIssues := make([]models.Issue, 0, 5*len(repos))
for i, repo := range repos {
issues, err := models.GetIssues(0, repo.Id, posterId, 0, page, isShowClosed, false, "", "")
if err != nil {
ctx.Handle(200, "user.Issues(get issues)", err)
return
}
allIssueCount += repo.NumIssues
closedIssueCount += repo.NumClosedIssues
// Set repository information to issues.
for j := range issues {
issues[j].Repo = &repos[i]
}
allIssues = append(allIssues, issues...)
repos[i].NumOpenIssues = repo.NumIssues - repo.NumClosedIssues
if repos[i].NumOpenIssues > 0 {
showRepos = append(showRepos, repos[i])
}
}
showIssues := make([]models.Issue, 0, len(allIssues))
ctx.Data["IsShowClosed"] = isShowClosed
// Get posters and filter issues.
for i := range allIssues {
u, err := models.GetUserById(allIssues[i].PosterId)
if err != nil {
ctx.Handle(200, "user.Issues(get poster): %v", err)
return
}
allIssues[i].Poster = u
if u.Id == ctx.User.Id {
createdByCount++
}
if repoId > 0 && repoId != allIssues[i].Repo.Id {
continue
}
if isShowClosed == allIssues[i].IsClosed {
showIssues = append(showIssues, allIssues[i])
}
}
ctx.Data["Repos"] = showRepos
ctx.Data["Issues"] = showIssues
ctx.Data["AllIssueCount"] = allIssueCount
ctx.Data["ClosedIssueCount"] = closedIssueCount
ctx.Data["OpenIssueCount"] = allIssueCount - closedIssueCount
ctx.Data["CreatedByCount"] = createdByCount
ctx.HTML(200, "issue/user")
}
func Pulls(ctx *middleware.Context) {
ctx.HTML(200, "user/pulls")
}
func Stars(ctx *middleware.Context) {
ctx.HTML(200, "user/stars")
}

View File

@@ -14,8 +14,16 @@ import (
"github.com/gogits/gogs/modules/middleware"
)
func Setting(ctx *middleware.Context) {
ctx.Data["Title"] = "Setting"
ctx.Data["PageIsUserSetting"] = true
ctx.Data["IsUserPageSetting"] = true
ctx.Data["Owner"] = ctx.User
ctx.HTML(200, "user/setting")
}
// Render user setting page (email, website modify)
func Setting(ctx *middleware.Context, form auth.UpdateProfileForm) {
func SettingPost(ctx *middleware.Context, form auth.UpdateProfileForm) {
ctx.Data["Title"] = "Setting"
ctx.Data["PageIsUserSetting"] = true // For navbar arrow.
ctx.Data["IsUserPageSetting"] = true // For setting nav highlight.
@@ -23,15 +31,27 @@ func Setting(ctx *middleware.Context, form auth.UpdateProfileForm) {
user := ctx.User
ctx.Data["Owner"] = user
if ctx.Req.Method == "GET" {
if ctx.HasError() {
ctx.HTML(200, "user/setting")
return
}
// below is for POST requests
if hasErr, ok := ctx.Data["HasError"]; ok && hasErr.(bool) {
ctx.HTML(200, "user/setting")
return
// Check if user name has been changed.
if user.Name != form.UserName {
isExist, err := models.IsUserExist(form.UserName)
if err != nil {
ctx.Handle(500, "user.Setting(update: check existence)", err)
return
} else if isExist {
ctx.RenderWithErr("User name has been taken.", "user/setting", &form)
return
} else if err = models.ChangeUserName(user, form.UserName); err != nil {
ctx.Handle(500, "user.Setting(change user name)", err)
return
}
log.Trace("%s User name changed: %s -> %s", ctx.Req.RequestURI, user.Name, form.UserName)
user.Name = form.UserName
}
user.Email = form.Email
@@ -40,51 +60,69 @@ func Setting(ctx *middleware.Context, form auth.UpdateProfileForm) {
user.Avatar = base.EncodeMd5(form.Avatar)
user.AvatarEmail = form.Avatar
if err := models.UpdateUser(user); err != nil {
ctx.Handle(200, "setting.Setting", err)
ctx.Handle(500, "setting.Setting", err)
return
}
log.Trace("%s User setting updated: %s", ctx.Req.RequestURI, ctx.User.LowerName)
ctx.Flash.Success("Your profile has been successfully updated.")
ctx.Redirect("/user/setting")
}
func SettingSocial(ctx *middleware.Context) {
ctx.Data["Title"] = "Social Account"
ctx.Data["PageIsUserSetting"] = true
ctx.Data["IsUserPageSettingSocial"] = true
socials, err := models.GetOauthByUserId(ctx.User.Id)
if err != nil {
ctx.Handle(500, "user.SettingSocial", err)
return
}
ctx.Data["IsSuccess"] = true
ctx.HTML(200, "user/setting")
log.Trace("%s User setting updated: %s", ctx.Req.RequestURI, ctx.User.LowerName)
ctx.Data["Socials"] = socials
ctx.HTML(200, "user/social")
}
func SettingPassword(ctx *middleware.Context, form auth.UpdatePasswdForm) {
func SettingPassword(ctx *middleware.Context) {
ctx.Data["Title"] = "Password"
ctx.Data["PageIsUserSetting"] = true
ctx.Data["IsUserPageSettingPasswd"] = true
ctx.HTML(200, "user/password")
}
func SettingPasswordPost(ctx *middleware.Context, form auth.UpdatePasswdForm) {
ctx.Data["Title"] = "Password"
ctx.Data["PageIsUserSetting"] = true
ctx.Data["IsUserPageSettingPasswd"] = true
if ctx.Req.Method == "GET" {
if ctx.HasError() {
ctx.HTML(200, "user/password")
return
}
user := ctx.User
newUser := &models.User{Passwd: form.NewPasswd}
if err := newUser.EncodePasswd(); err != nil {
ctx.Handle(200, "setting.SettingPassword", err)
return
tmpUser := &models.User{
Passwd: form.OldPasswd,
Salt: user.Salt,
}
if user.Passwd != newUser.Passwd {
ctx.Data["HasError"] = true
ctx.Data["ErrorMsg"] = "Old password is not correct"
tmpUser.EncodePasswd()
if user.Passwd != tmpUser.Passwd {
ctx.Flash.Error("Old password is not correct")
} else if form.NewPasswd != form.RetypePasswd {
ctx.Data["HasError"] = true
ctx.Data["ErrorMsg"] = "New password and re-type password are not same"
ctx.Flash.Error("New password and re-type password are not same")
} else {
user.Passwd = newUser.Passwd
user.Passwd = form.NewPasswd
user.Salt = models.GetUserSalt()
user.EncodePasswd()
if err := models.UpdateUser(user); err != nil {
ctx.Handle(200, "setting.SettingPassword", err)
return
}
ctx.Data["IsSuccess"] = true
log.Trace("%s User password updated: %s", ctx.Req.RequestURI, ctx.User.LowerName)
ctx.Flash.Success("Password is changed successfully. You can now sign in via new password.")
}
ctx.Data["Owner"] = user
ctx.HTML(200, "user/password")
log.Trace("%s User password updated: %s", ctx.Req.RequestURI, ctx.User.LowerName)
ctx.Redirect("/user/setting/password")
}
func SettingSSHKeys(ctx *middleware.Context, form auth.AddSSHKeyForm) {
@@ -123,7 +161,7 @@ func SettingSSHKeys(ctx *middleware.Context, form auth.AddSSHKeyForm) {
// Add new SSH key.
if ctx.Req.Method == "POST" {
if hasErr, ok := ctx.Data["HasError"]; ok && hasErr.(bool) {
if ctx.HasError() {
ctx.HTML(200, "user/publickey")
return
}
@@ -138,11 +176,13 @@ func SettingSSHKeys(ctx *middleware.Context, form auth.AddSSHKeyForm) {
ctx.RenderWithErr("Public key name has been used", "user/publickey", &form)
return
}
ctx.Handle(200, "ssh.AddPublicKey", err)
log.Trace("%s User SSH key added: %s", ctx.Req.RequestURI, ctx.User.LowerName)
ctx.Handle(500, "ssh.AddPublicKey", err)
return
} else {
ctx.Data["AddSSHKeySuccess"] = true
log.Trace("%s User SSH key added: %s", ctx.Req.RequestURI, ctx.User.LowerName)
ctx.Flash.Success("New SSH Key has been added!")
ctx.Redirect("/user/setting/ssh")
return
}
}

99
routers/user/social.go Normal file
View File

@@ -0,0 +1,99 @@
// 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 user
import (
"encoding/json"
"errors"
"fmt"
"net/url"
"strings"
"github.com/go-martini/martini"
"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/social"
)
func extractPath(next string) string {
n, err := url.Parse(next)
if err != nil {
return "/"
}
return n.Path
}
func SocialSignIn(ctx *middleware.Context, params martini.Params) {
if base.OauthService == nil {
ctx.Handle(404, "social.SocialSignIn(oauth service not enabled)", nil)
return
}
next := extractPath(ctx.Query("next"))
name := params["name"]
connect, ok := social.SocialMap[name]
if !ok {
ctx.Handle(404, "social.SocialSignIn(social login not enabled)", errors.New(name))
return
}
code := ctx.Query("code")
if code == "" {
// redirect to social login page
connect.SetRedirectUrl(strings.TrimSuffix(base.AppUrl, "/") + ctx.Req.URL.Path)
ctx.Redirect(connect.AuthCodeURL(next))
return
}
// handle call back
tk, err := connect.Exchange(code)
if err != nil {
ctx.Handle(500, "social.SocialSignIn(Exchange)", err)
return
}
next = extractPath(ctx.Query("state"))
log.Trace("social.SocialSignIn(Got token)")
ui, err := connect.UserInfo(tk, ctx.Req.URL)
if err != nil {
ctx.Handle(500, fmt.Sprintf("social.SocialSignIn(get info from %s)", name), err)
return
}
log.Info("social.SocialSignIn(social login): %s", ui)
oa, err := models.GetOauth2(ui.Identity)
switch err {
case nil:
ctx.Session.Set("userId", oa.User.Id)
ctx.Session.Set("userName", oa.User.Name)
case models.ErrOauth2RecordNotExist:
raw, _ := json.Marshal(tk)
oa = &models.Oauth2{
Uid: -1,
Type: connect.Type(),
Identity: ui.Identity,
Token: string(raw),
}
log.Trace("social.SocialSignIn(oa): %v", oa)
if err = models.AddOauth2(oa); err != nil {
log.Error("social.SocialSignIn(add oauth2): %v", err) // 501
return
}
case models.ErrOauth2NotAssociated:
next = "/user/sign_up"
default:
ctx.Handle(500, "social.SocialSignIn(GetOauth2)", err)
return
}
ctx.Session.Set("socialId", oa.Id)
ctx.Session.Set("socialName", ui.Name)
ctx.Session.Set("socialEmail", ui.Email)
log.Trace("social.SocialSignIn(social ID): %v", oa.Id)
ctx.Redirect(next)
}

View File

@@ -5,12 +5,9 @@
package user
import (
"fmt"
"net/url"
"strings"
"github.com/go-martini/martini"
"github.com/gogits/gogs/models"
"github.com/gogits/gogs/modules/auth"
"github.com/gogits/gogs/modules/base"
@@ -19,107 +16,74 @@ import (
"github.com/gogits/gogs/modules/middleware"
)
func Dashboard(ctx *middleware.Context) {
ctx.Data["Title"] = "Dashboard"
ctx.Data["PageIsUserDashboard"] = true
repos, err := models.GetRepositories(&models.User{Id: ctx.User.Id})
if err != nil {
ctx.Handle(200, "user.Dashboard", err)
return
}
ctx.Data["MyRepos"] = repos
feeds, err := models.GetFeeds(ctx.User.Id, 0, false)
if err != nil {
ctx.Handle(200, "user.Dashboard", err)
return
}
ctx.Data["Feeds"] = feeds
ctx.HTML(200, "user/dashboard")
}
func Profile(ctx *middleware.Context, params martini.Params) {
ctx.Data["Title"] = "Profile"
// TODO: Need to check view self or others.
user, err := models.GetUserByName(params["username"])
if err != nil {
ctx.Handle(200, "user.Profile", err)
return
}
ctx.Data["Owner"] = user
tab := ctx.Query("tab")
ctx.Data["TabName"] = tab
switch tab {
case "activity":
feeds, err := models.GetFeeds(user.Id, 0, true)
if err != nil {
ctx.Handle(200, "user.Profile", err)
return
}
ctx.Data["Feeds"] = feeds
default:
repos, err := models.GetRepositories(user)
if err != nil {
ctx.Handle(200, "user.Profile", err)
return
}
ctx.Data["Repos"] = repos
}
ctx.Data["PageIsUserProfile"] = true
ctx.HTML(200, "user/profile")
}
func SignIn(ctx *middleware.Context, form auth.LogInForm) {
func SignIn(ctx *middleware.Context) {
ctx.Data["Title"] = "Log In"
if ctx.Req.Method == "GET" {
// Check auto-login.
userName := ctx.GetCookie(base.CookieUserName)
if len(userName) == 0 {
ctx.HTML(200, "user/signin")
return
}
isSucceed := false
defer func() {
if !isSucceed {
log.Trace("%s auto-login cookie cleared: %s", ctx.Req.RequestURI, userName)
ctx.SetCookie(base.CookieUserName, "", -1)
ctx.SetCookie(base.CookieRememberName, "", -1)
}
}()
user, err := models.GetUserByName(userName)
if err != nil {
ctx.HTML(200, "user/signin")
return
}
secret := base.EncodeMd5(user.Rands + user.Passwd)
value, _ := ctx.GetSecureCookie(secret, base.CookieRememberName)
if value != user.Name {
ctx.HTML(200, "user/signin")
return
}
isSucceed = true
ctx.Session.Set("userId", user.Id)
ctx.Session.Set("userName", user.Name)
redirectTo, _ := url.QueryUnescape(ctx.GetCookie("redirect_to"))
if len(redirectTo) > 0 {
ctx.SetCookie("redirect_to", "", -1)
ctx.Redirect(redirectTo)
} else {
ctx.Redirect("/")
}
if _, ok := ctx.Session.Get("socialId").(int64); ok {
ctx.Data["IsSocialLogin"] = true
ctx.HTML(200, "user/signin")
return
}
if base.OauthService != nil {
ctx.Data["OauthEnabled"] = true
ctx.Data["OauthService"] = base.OauthService
}
// Check auto-login.
userName := ctx.GetCookie(base.CookieUserName)
if len(userName) == 0 {
ctx.HTML(200, "user/signin")
return
}
isSucceed := false
defer func() {
if !isSucceed {
log.Trace("user.SignIn(auto-login cookie cleared): %s", userName)
ctx.SetCookie(base.CookieUserName, "", -1)
ctx.SetCookie(base.CookieRememberName, "", -1)
return
}
}()
user, err := models.GetUserByName(userName)
if err != nil {
ctx.HTML(500, "user/signin")
return
}
secret := base.EncodeMd5(user.Rands + user.Passwd)
value, _ := ctx.GetSecureCookie(secret, base.CookieRememberName)
if value != user.Name {
ctx.HTML(500, "user/signin")
return
}
isSucceed = true
ctx.Session.Set("userId", user.Id)
ctx.Session.Set("userName", user.Name)
if redirectTo, _ := url.QueryUnescape(ctx.GetCookie("redirect_to")); len(redirectTo) > 0 {
ctx.SetCookie("redirect_to", "", -1)
ctx.Redirect(redirectTo)
return
}
ctx.Redirect("/")
}
func SignInPost(ctx *middleware.Context, form auth.LogInForm) {
ctx.Data["Title"] = "Log In"
sid, isOauth := ctx.Session.Get("socialId").(int64)
if isOauth {
ctx.Data["IsSocialLogin"] = true
} else if base.OauthService != nil {
ctx.Data["OauthEnabled"] = true
ctx.Data["OauthService"] = base.OauthService
}
if ctx.HasError() {
ctx.HTML(200, "user/signin")
return
@@ -133,7 +97,7 @@ func SignIn(ctx *middleware.Context, form auth.LogInForm) {
return
}
ctx.Handle(200, "user.SignIn", err)
ctx.Handle(500, "user.SignIn", err)
return
}
@@ -144,26 +108,64 @@ func SignIn(ctx *middleware.Context, form auth.LogInForm) {
ctx.SetSecureCookie(secret, base.CookieRememberName, user.Name, days)
}
// Bind with social account.
if isOauth {
if err = models.BindUserOauth2(user.Id, sid); err != nil {
if err == models.ErrOauth2RecordNotExist {
ctx.Handle(404, "user.SignInPost(GetOauth2ById)", err)
} else {
ctx.Handle(500, "user.SignInPost(GetOauth2ById)", err)
}
return
}
ctx.Session.Delete("socialId")
log.Trace("%s OAuth binded: %s -> %d", ctx.Req.RequestURI, form.UserName, sid)
}
ctx.Session.Set("userId", user.Id)
ctx.Session.Set("userName", user.Name)
redirectTo, _ := url.QueryUnescape(ctx.GetCookie("redirect_to"))
if len(redirectTo) > 0 {
if redirectTo, _ := url.QueryUnescape(ctx.GetCookie("redirect_to")); len(redirectTo) > 0 {
ctx.SetCookie("redirect_to", "", -1)
ctx.Redirect(redirectTo)
} else {
ctx.Redirect("/")
return
}
ctx.Redirect("/")
}
func oauthSignInPost(ctx *middleware.Context, sid int64) {
ctx.Data["Title"] = "OAuth Sign Up"
ctx.Data["PageIsSignUp"] = true
if _, err := models.GetOauth2ById(sid); err != nil {
if err == models.ErrOauth2RecordNotExist {
ctx.Handle(404, "user.oauthSignUp(GetOauth2ById)", err)
} else {
ctx.Handle(500, "user.oauthSignUp(GetOauth2ById)", err)
}
return
}
ctx.Data["IsSocialLogin"] = true
ctx.Data["username"] = ctx.Session.Get("socialName")
ctx.Data["email"] = ctx.Session.Get("socialEmail")
log.Trace("user.oauthSignUp(social ID): %v", ctx.Session.Get("socialId"))
ctx.HTML(200, "user/signup")
}
func SignOut(ctx *middleware.Context) {
ctx.Session.Delete("userId")
ctx.Session.Delete("userName")
ctx.Session.Delete("socialId")
ctx.Session.Delete("socialName")
ctx.Session.Delete("socialEmail")
ctx.SetCookie(base.CookieUserName, "", -1)
ctx.SetCookie(base.CookieRememberName, "", -1)
ctx.Redirect("/")
}
func SignUp(ctx *middleware.Context, form auth.RegisterForm) {
func SignUp(ctx *middleware.Context) {
ctx.Data["Title"] = "Sign Up"
ctx.Data["PageIsSignUp"] = true
@@ -173,11 +175,49 @@ func SignUp(ctx *middleware.Context, form auth.RegisterForm) {
return
}
if ctx.Req.Method == "GET" {
ctx.HTML(200, "user/signup")
if sid, ok := ctx.Session.Get("socialId").(int64); ok {
oauthSignUp(ctx, sid)
return
}
ctx.HTML(200, "user/signup")
}
func oauthSignUp(ctx *middleware.Context, sid int64) {
ctx.Data["Title"] = "OAuth Sign Up"
ctx.Data["PageIsSignUp"] = true
if _, err := models.GetOauth2ById(sid); err != nil {
if err == models.ErrOauth2RecordNotExist {
ctx.Handle(404, "user.oauthSignUp(GetOauth2ById)", err)
} else {
ctx.Handle(500, "user.oauthSignUp(GetOauth2ById)", err)
}
return
}
ctx.Data["IsSocialLogin"] = true
ctx.Data["username"] = strings.Replace(ctx.Session.Get("socialName").(string), " ", "", -1)
ctx.Data["email"] = ctx.Session.Get("socialEmail")
log.Trace("user.oauthSignUp(social ID): %v", ctx.Session.Get("socialId"))
ctx.HTML(200, "user/signup")
}
func SignUpPost(ctx *middleware.Context, form auth.RegisterForm) {
ctx.Data["Title"] = "Sign Up"
ctx.Data["PageIsSignUp"] = true
if base.Service.DisenableRegisteration {
ctx.Handle(403, "user.SignUpPost", nil)
return
}
sid, isOauth := ctx.Session.Get("socialId").(int64)
if isOauth {
ctx.Data["IsSocialLogin"] = true
}
if form.Password != form.RetypePasswd {
ctx.Data["HasError"] = true
ctx.Data["Err_Password"] = true
@@ -195,7 +235,7 @@ func SignUp(ctx *middleware.Context, form auth.RegisterForm) {
Name: form.UserName,
Email: form.Email,
Passwd: form.Password,
IsActive: !base.Service.RegisterEmailConfirm,
IsActive: !base.Service.RegisterEmailConfirm || isOauth,
}
var err error
@@ -208,20 +248,30 @@ func SignUp(ctx *middleware.Context, form auth.RegisterForm) {
case models.ErrUserNameIllegal:
ctx.RenderWithErr(models.ErrRepoNameIllegal.Error(), "user/signup", &form)
default:
ctx.Handle(200, "user.SignUp", err)
ctx.Handle(500, "user.SignUp(RegisterUser)", err)
}
return
}
log.Trace("%s User created: %s", ctx.Req.RequestURI, strings.ToLower(form.UserName))
log.Trace("%s User created: %s", ctx.Req.RequestURI, form.UserName)
// Send confirmation e-mail.
if base.Service.RegisterEmailConfirm && u.Id > 1 {
// Bind social account.
if isOauth {
if err = models.BindUserOauth2(u.Id, sid); err != nil {
ctx.Handle(500, "user.SignUp(BindUserOauth2)", err)
return
}
ctx.Session.Delete("socialId")
log.Trace("%s OAuth binded: %s -> %d", ctx.Req.RequestURI, form.UserName, sid)
}
// Send confirmation e-mail, no need for social account.
if !isOauth && base.Service.RegisterEmailConfirm && u.Id > 1 {
mailer.SendRegisterMail(ctx.Render, u)
ctx.Data["IsSendRegisterMail"] = true
ctx.Data["Email"] = u.Email
ctx.Data["Hours"] = base.Service.ActiveCodeLives / 60
ctx.HTML(200, "user/active")
ctx.HTML(200, "user/activate")
if err = ctx.Cache.Put("MailResendLimit_"+u.LowerName, u.LowerName, 180); err != nil {
log.Error("Set cache(MailResendLimit) fail: %v", err)
@@ -235,25 +285,28 @@ func Delete(ctx *middleware.Context) {
ctx.Data["Title"] = "Delete Account"
ctx.Data["PageIsUserSetting"] = true
ctx.Data["IsUserPageSettingDelete"] = true
ctx.HTML(200, "user/delete")
}
if ctx.Req.Method == "GET" {
ctx.HTML(200, "user/delete")
return
func DeletePost(ctx *middleware.Context) {
ctx.Data["Title"] = "Delete Account"
ctx.Data["PageIsUserSetting"] = true
ctx.Data["IsUserPageSettingDelete"] = true
tmpUser := models.User{
Passwd: ctx.Query("password"),
Salt: ctx.User.Salt,
}
tmpUser := models.User{Passwd: ctx.Query("password")}
tmpUser.EncodePasswd()
if len(tmpUser.Passwd) == 0 || tmpUser.Passwd != ctx.User.Passwd {
ctx.Data["HasError"] = true
ctx.Data["ErrorMsg"] = "Password is not correct. Make sure you are owner of this account."
if tmpUser.Passwd != ctx.User.Passwd {
ctx.Flash.Error("Password is not correct. Make sure you are owner of this account.")
} else {
if err := models.DeleteUser(ctx.User); err != nil {
ctx.Data["HasError"] = true
switch err {
case models.ErrUserOwnRepos:
ctx.Data["ErrorMsg"] = "Your account still have ownership of repository, you have to delete or transfer them first."
ctx.Flash.Error("Your account still have ownership of repository, you have to delete or transfer them first.")
default:
ctx.Handle(200, "user.Delete", err)
ctx.Handle(500, "user.Delete", err)
return
}
} else {
@@ -262,118 +315,7 @@ func Delete(ctx *middleware.Context) {
}
}
ctx.HTML(200, "user/delete")
}
const (
TPL_FEED = `<i class="icon fa fa-%s"></i>
<div class="info"><span class="meta">%s</span><br>%s</div>`
)
func Feeds(ctx *middleware.Context, form auth.FeedsForm) {
actions, err := models.GetFeeds(form.UserId, form.Page*20, false)
if err != nil {
ctx.JSON(500, err)
}
feeds := make([]string, len(actions))
for i := range actions {
feeds[i] = fmt.Sprintf(TPL_FEED, base.ActionIcon(actions[i].OpType),
base.TimeSince(actions[i].Created), base.ActionDesc(actions[i]))
}
ctx.JSON(200, &feeds)
}
func Issues(ctx *middleware.Context) {
ctx.Data["Title"] = "Your Issues"
ctx.Data["ViewType"] = "all"
page, _ := base.StrTo(ctx.Query("page")).Int()
repoId, _ := base.StrTo(ctx.Query("repoid")).Int64()
ctx.Data["RepoId"] = repoId
var posterId int64 = 0
if ctx.Query("type") == "created_by" {
posterId = ctx.User.Id
ctx.Data["ViewType"] = "created_by"
}
// Get all repositories.
repos, err := models.GetRepositories(ctx.User)
if err != nil {
ctx.Handle(200, "user.Issues(get repositories)", err)
return
}
showRepos := make([]models.Repository, 0, len(repos))
isShowClosed := ctx.Query("state") == "closed"
var closedIssueCount, createdByCount, allIssueCount int
// Get all issues.
allIssues := make([]models.Issue, 0, 5*len(repos))
for i, repo := range repos {
issues, err := models.GetIssues(0, repo.Id, posterId, 0, page, isShowClosed, false, "", "")
if err != nil {
ctx.Handle(200, "user.Issues(get issues)", err)
return
}
allIssueCount += repo.NumIssues
closedIssueCount += repo.NumClosedIssues
// Set repository information to issues.
for j := range issues {
issues[j].Repo = &repos[i]
}
allIssues = append(allIssues, issues...)
repos[i].NumOpenIssues = repo.NumIssues - repo.NumClosedIssues
if repos[i].NumOpenIssues > 0 {
showRepos = append(showRepos, repos[i])
}
}
showIssues := make([]models.Issue, 0, len(allIssues))
ctx.Data["IsShowClosed"] = isShowClosed
// Get posters and filter issues.
for i := range allIssues {
u, err := models.GetUserById(allIssues[i].PosterId)
if err != nil {
ctx.Handle(200, "user.Issues(get poster): %v", err)
return
}
allIssues[i].Poster = u
if u.Id == ctx.User.Id {
createdByCount++
}
if repoId > 0 && repoId != allIssues[i].Repo.Id {
continue
}
if isShowClosed == allIssues[i].IsClosed {
showIssues = append(showIssues, allIssues[i])
}
}
ctx.Data["Repos"] = showRepos
ctx.Data["Issues"] = showIssues
ctx.Data["AllIssueCount"] = allIssueCount
ctx.Data["ClosedIssueCount"] = closedIssueCount
ctx.Data["OpenIssueCount"] = allIssueCount - closedIssueCount
ctx.Data["CreatedByCount"] = createdByCount
ctx.HTML(200, "issue/user")
}
func Pulls(ctx *middleware.Context) {
ctx.HTML(200, "user/pulls")
}
func Stars(ctx *middleware.Context) {
ctx.HTML(200, "user/stars")
ctx.Redirect("/user/delete")
}
func Activate(ctx *middleware.Context) {
@@ -391,11 +333,15 @@ func Activate(ctx *middleware.Context) {
} else {
ctx.Data["Hours"] = base.Service.ActiveCodeLives / 60
mailer.SendActiveMail(ctx.Render, ctx.User)
if err := ctx.Cache.Put("MailResendLimit_"+ctx.User.LowerName, ctx.User.LowerName, 180); err != nil {
log.Error("Set cache(MailResendLimit) fail: %v", err)
}
}
} else {
ctx.Data["ServiceNotEnabled"] = true
}
ctx.HTML(200, "user/active")
ctx.HTML(200, "user/activate")
return
}
@@ -403,9 +349,12 @@ func Activate(ctx *middleware.Context) {
if user := models.VerifyUserActiveCode(code); user != nil {
user.IsActive = true
user.Rands = models.GetUserSalt()
models.UpdateUser(user)
if err := models.UpdateUser(user); err != nil {
ctx.Handle(404, "user.Activate", err)
return
}
log.Trace("%s User activated: %s", ctx.Req.RequestURI, user.LowerName)
log.Trace("%s User activated: %s", ctx.Req.RequestURI, user.Name)
ctx.Session.Set("userId", user.Id)
ctx.Session.Set("userName", user.Name)
@@ -414,5 +363,106 @@ func Activate(ctx *middleware.Context) {
}
ctx.Data["IsActivateFailed"] = true
ctx.HTML(200, "user/active")
ctx.HTML(200, "user/activate")
}
func ForgotPasswd(ctx *middleware.Context) {
ctx.Data["Title"] = "Forgot Password"
if base.MailService == nil {
ctx.Data["IsResetDisable"] = true
ctx.HTML(200, "user/forgot_passwd")
return
}
ctx.Data["IsResetRequest"] = true
ctx.HTML(200, "user/forgot_passwd")
}
func ForgotPasswdPost(ctx *middleware.Context) {
ctx.Data["Title"] = "Forgot Password"
if base.MailService == nil {
ctx.Handle(403, "user.ForgotPasswdPost", nil)
return
}
ctx.Data["IsResetRequest"] = true
email := ctx.Query("email")
u, err := models.GetUserByEmail(email)
if err != nil {
if err == models.ErrUserNotExist {
ctx.RenderWithErr("This e-mail address does not associate to any account.", "user/forgot_passwd", nil)
} else {
ctx.Handle(500, "user.ResetPasswd(check existence)", err)
}
return
}
if ctx.Cache.IsExist("MailResendLimit_" + u.LowerName) {
ctx.Data["ResendLimited"] = true
ctx.HTML(200, "user/forgot_passwd")
return
}
mailer.SendResetPasswdMail(ctx.Render, u)
if err = ctx.Cache.Put("MailResendLimit_"+u.LowerName, u.LowerName, 180); err != nil {
log.Error("Set cache(MailResendLimit) fail: %v", err)
}
ctx.Data["Email"] = email
ctx.Data["Hours"] = base.Service.ActiveCodeLives / 60
ctx.Data["IsResetSent"] = true
ctx.HTML(200, "user/forgot_passwd")
}
func ResetPasswd(ctx *middleware.Context) {
ctx.Data["Title"] = "Reset Password"
code := ctx.Query("code")
if len(code) == 0 {
ctx.Error(404)
return
}
ctx.Data["Code"] = code
ctx.Data["IsResetForm"] = true
ctx.HTML(200, "user/reset_passwd")
}
func ResetPasswdPost(ctx *middleware.Context) {
ctx.Data["Title"] = "Reset Password"
code := ctx.Query("code")
if len(code) == 0 {
ctx.Error(404)
return
}
ctx.Data["Code"] = code
if u := models.VerifyUserActiveCode(code); u != nil {
// Validate password length.
passwd := ctx.Query("passwd")
if len(passwd) < 6 || len(passwd) > 30 {
ctx.Data["IsResetForm"] = true
ctx.RenderWithErr("Password length should be in 6 and 30.", "user/reset_passwd", nil)
return
}
u.Passwd = passwd
u.Rands = models.GetUserSalt()
u.Salt = models.GetUserSalt()
u.EncodePasswd()
if err := models.UpdateUser(u); err != nil {
ctx.Handle(500, "user.ResetPasswd(UpdateUser)", err)
return
}
log.Trace("%s User password reset: %s", ctx.Req.RequestURI, u.Name)
ctx.Redirect("/user/login")
return
}
ctx.Data["IsResetFailed"] = true
ctx.HTML(200, "user/reset_passwd")
}

View File

@@ -14,7 +14,7 @@ import (
"strings"
"github.com/codegangsta/cli"
"github.com/gogits/gogs/modules/log"
qlog "github.com/qiniu/log"
//"github.com/gogits/git"
"github.com/gogits/gogs/models"
@@ -44,11 +44,16 @@ gogs serv provide access auth for repositories`,
}
func newLogger(execDir string) {
level := "0"
logPath := execDir + "/log/serv.log"
os.MkdirAll(path.Dir(logPath), os.ModePerm)
log.NewLogger(0, "file", fmt.Sprintf(`{"level":%s,"filename":"%s"}`, level, logPath))
log.Trace("start logging...")
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.Info("Start logging serv...")
}
func parseCmd(cmd string) (string, string) {
@@ -87,21 +92,18 @@ func runServ(k *cli.Context) {
keys := strings.Split(os.Args[2], "-")
if len(keys) != 2 {
println("auth file format error")
log.Error("auth file format error")
return
qlog.Fatal("auth file format error")
}
keyId, err := strconv.ParseInt(keys[1], 10, 64)
if err != nil {
println("auth file format error")
log.Error("auth file format error", err)
return
qlog.Fatal("auth file format error", err)
}
user, err := models.GetUserByKeyId(keyId)
if err != nil {
println("You have no right to access")
log.Error("SSH visit error: %v", err)
return
qlog.Fatalf("SSH visit error: %v", err)
}
cmd := os.Getenv("SSH_ORIGINAL_COMMAND")
@@ -115,23 +117,18 @@ func runServ(k *cli.Context) {
rr := strings.SplitN(repoPath, "/", 2)
if len(rr) != 2 {
println("Unavilable repository", args)
log.Error("Unavilable repository %v", args)
return
qlog.Fatalf("Unavilable repository %v", args)
}
repoUserName := rr[0]
repoName := rr[1]
if strings.HasSuffix(repoName, ".git") {
repoName = repoName[:len(repoName)-4]
}
repoName := strings.TrimSuffix(rr[1], ".git")
isWrite := In(verb, COMMANDS_WRITE)
isRead := In(verb, COMMANDS_READONLY)
repoUser, err := models.GetUserByName(repoUserName)
if err != nil {
fmt.Println("You have no right to access")
log.Error("Get user failed", err)
return
println("You have no right to access")
qlog.Fatal("Get user failed", err)
}
// access check
@@ -140,54 +137,44 @@ func runServ(k *cli.Context) {
has, err := models.HasAccess(user.LowerName, path.Join(repoUserName, repoName), models.AU_WRITABLE)
if err != nil {
println("Inernel error:", err)
log.Error(err.Error())
return
qlog.Fatal(err)
} else if !has {
println("You have no right to write this repository")
log.Error("User %s has no right to write repository %s", user.Name, repoPath)
return
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 {
println("Get repository error:", err)
log.Error("Get repository error: " + err.Error())
return
qlog.Fatal("Get repository error: " + err.Error())
}
if !repo.IsPrivate {
break
}
has, err := models.HasAccess(user.Name, repoPath, models.AU_READABLE)
has, err := models.HasAccess(user.Name, path.Join(repoUserName, repoName), models.AU_READABLE)
if err != nil {
println("Inernel error")
log.Error(err.Error())
return
qlog.Fatal(err)
}
if !has {
has, err = models.HasAccess(user.Name, repoPath, models.AU_WRITABLE)
if err != nil {
println("Inernel error")
log.Error(err.Error())
return
qlog.Fatal(err)
}
}
if !has {
println("You have no right to access this repository")
log.Error("You have no right to access this repository")
return
qlog.Fatal("You have no right to access this repository")
}
default:
println("Unknown command")
log.Error("Unknown command")
return
qlog.Fatal("Unknown command")
}
// for update use
os.Setenv("userName", user.Name)
os.Setenv("userId", strconv.Itoa(int(user.Id)))
os.Setenv("repoName", repoName)
models.SetRepoEnvs(user.Id, user.Name, repoName)
gitcmd := exec.Command(verb, repoPath)
gitcmd.Dir = base.RepoRootPath
@@ -197,7 +184,15 @@ func runServ(k *cli.Context) {
if err = gitcmd.Run(); err != nil {
println("execute command error:", err.Error())
log.Error("execute command error: " + err.Error())
return
qlog.Fatal("execute command error: " + err.Error())
}
//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)
}

View File

@@ -1,6 +1,15 @@
#!/bin/bash -
#!/bin/sh -
# 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.
#
# start gogs web
#
cd "$(dirname $0)"
./gogs web
IFS='
'
PATH=/bin:/usr/bin:/usr/local/bin
HOME=${HOME:?"need \$HOME variable"}
USER=$(whoami)
export USER HOME PATH
cd "$(dirname $0)" && exec ./gogs web

View File

@@ -2,22 +2,31 @@
{{template "base/navbar" .}}
<div id="body" class="container" data-page="admin">
{{template "admin/nav" .}}
<div id="admin-container" class="col-md-9">
<div id="admin-container" class="col-md-10">
<div class="panel panel-default">
<div class="panel-heading">
Server Configuration
</div>
<div class="panel-body">
<div><b>Application Name:</b> {{AppName}}</div>
<div><b>Application Version:</b> {{AppVer}}</div>
<div><b>Application URL:</b> {{.AppUrl}}</div>
<div><b>Domain:</b> {{.Domain}}</div>
<hr/>
<div><b>Run User:</b> {{.RunUser}}</div>
<div><b>Run Mode:</b> {{.RunMode}}</div>
<hr/>
<div><b>Repository Root Path:</b> {{.RepoRootPath}}</div>
<dl class="dl-horizontal admin-dl-horizontal">
<dt>Application Name</dt>
<dd>{{AppName}}</dd>
<dt>Application Version</dt>
<dd>{{AppVer}}</dd>
<dt>Application URL</dt>
<dd>{{.AppUrl}}</dd>
<dt>Domain</dt>
<dd>{{.Domain}}</dd>
<hr/>
<dt>Run User</dt>
<dd>{{.RunUser}}</dd>
<dt>Run Mode</dt>
<dd>{{.RunMode}}</dd>
<hr/>
<dt>Repository Root Path</dt>
<dd>{{.RepoRootPath}}</dd>
</dl>
</div>
</div>
@@ -27,12 +36,20 @@
</div>
<div class="panel-body">
<div><b>Type:</b> {{.DbCfg.Type}}</div>
<div><b>Host:</b> {{.DbCfg.Host}}</div>
<div><b>Name:</b> {{.DbCfg.Name}}</div>
<div><b>User:</b> {{.DbCfg.User}}</div>
<div><b>SslMode:</b> {{.DbCfg.SslMode}} (for "postgres" only)</div>
<div><b>Path:</b> {{.DbCfg.Path}} (for "sqlite3" only)</div>
<dl class="dl-horizontal admin-dl-horizontal">
<dt>Type</dt>
<dd>{{.DbCfg.Type}}</dd>
<dt>Host</dt>
<dd>{{.DbCfg.Host}}</dd>
<dt>Name</dt>
<dd>{{.DbCfg.Name}}</dd>
<dt>User</dt>
<dd>{{.DbCfg.User}}</dd>
<dt>SslMode</dt>
<dd>{{.DbCfg.SslMode}} (for "postgres" only)</dd>
<dt>Path</dt>
<dd>{{.DbCfg.Path}} (for "sqlite3" only)</dd>
</dl>
</div>
</div>
@@ -42,14 +59,23 @@
</div>
<div class="panel-body">
<div><b>Register Email Confirmation:</b> <i class="fa fa{{if .Service.RegisterEmailConfirm}}-check{{end}}-square-o"></i></div>
<div><b>Disenable Registeration:</b> <i class="fa fa{{if .Service.DisenableRegisteration}}-check{{end}}-square-o"></i></div>
<div><b>Require Sign In View:</b> <i class="fa fa{{if .Service.RequireSignInView}}-check{{end}}-square-o"></i></div>
<div><b>Mail Notification:</b> <i class="fa fa{{if .Service.NotifyMail}}-check{{end}}-square-o"></i></div>
<div><b>Enable Cache Avatar:</b> <i class="fa fa{{if .Service.EnableCacheAvatar}}-check{{end}}-square-o"></i></div>
<hr/>
<div><b>Active Code Lives:</b> {{.Service.ActiveCodeLives}} minutes</div>
<div><b>Reset Password Code Lives:</b> {{.Service.ResetPwdCodeLives}} minutes</div>
<dl class="dl-horizontal admin-dl-horizontal">
<dt>Register Email Confirmation</dt>
<dd><i class="fa fa{{if .Service.RegisterEmailConfirm}}-check{{end}}-square-o"></i></dd>
<dt>Disenable Registeration</dt>
<dd><i class="fa fa{{if .Service.DisenableRegisteration}}-check{{end}}-square-o"></i></dd>
<dt>Require Sign In View</dt>
<dd><i class="fa fa{{if .Service.RequireSignInView}}-check{{end}}-square-o"></i></dd>
<dt>Mail Notification</dt>
<dd><i class="fa fa{{if .Service.NotifyMail}}-check{{end}}-square-o"></i></dd>
<dt>Enable Cache Avatar</dt>
<dd><i class="fa fa{{if .Service.EnableCacheAvatar}}-check{{end}}-square-o"></i></dd>
<hr/>
<dt>Active Code Lives</dt>
<dd>{{.Service.ActiveCodeLives}} minutes</dd>
<dt>Reset Password Code Lives</dt>
<dd>{{.Service.ResetPwdCodeLives}} minutes</dd>
</dl>
</div>
</div>
@@ -59,10 +85,38 @@
</div>
<div class="panel-body">
<div><b>Enabled:</b> <i class="fa fa{{if .MailerEnabled}}-check{{end}}-square-o"></i></div>
<div><b>Name:</b> {{.Mailer.Name}}</div>
<div><b>Host:</b> {{.Mailer.Host}}</div>
<div><b>User:</b> {{.Mailer.User}}</div>
<dl class="dl-horizontal admin-dl-horizontal">
<dt>Enabled</dt>
<dd><i class="fa fa{{if .MailerEnabled}}-check{{end}}-square-o"></i></dd>
{{if .MailerEnabled}}<dt>Name</dt>
<dd>{{.Mailer.Name}}</dd>
<dt>Host</dt>
<dd>{{.Mailer.Host}}</dd>
<dt>User</dt>
<dd>{{.Mailer.User}}</dd>{{end}}
</dl>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">
OAuth Configuration
</div>
<div class="panel-body">
<dl class="dl-horizontal admin-dl-horizontal">
<dt>Enabled</dt>
<dd><i class="fa fa{{if .OauthEnabled}}-check{{end}}-square-o"></i></dd>
{{if .OauthEnabled}}<dt>GitHub</dt>
<dd><i class="fa fa{{if .Oauther.GitHub}}-check{{end}}-square-o"></i></dd>
<dt>Google</dt>
<dd><i class="fa fa{{if .Oauther.Google}}-check{{end}}-square-o"></i></dd>
<dt>Tencent QQ</dt>
<dd><i class="fa fa{{if .Oauther.Tencent}}-check{{end}}-square-o"></i></dd>
<dt>Weibo</dt>
<dd><i class="fa fa{{if .Oauther.Weibo}}-check{{end}}-square-o"></i></dd>
{{end}}
</dl>
</div>
</div>
@@ -72,9 +126,12 @@
</div>
<div class="panel-body">
<div><b>Cache Adapter:</b> {{.CacheAdapter}}</div>
<div><b>Cache Config:</b></div>
<div style="padding-top: 5px;"><pre>{{.CacheConfig}}</pre></div>
<dl class="dl-horizontal admin-dl-horizontal">
<dt>Cache Adapter</dt>
<dd>{{.CacheAdapter}}</dd>
<dt>Cache Config</dt>
<dd><div style="padding-top: 5px;"><pre>{{.CacheConfig}}</pre></div></dd>
</dl>
</div>
</div>
@@ -84,16 +141,28 @@
</div>
<div class="panel-body">
<div><b>Session Provider:</b> {{.SessionProvider}}</div>
<div><b>Cookie Name:</b> {{.SessionConfig.CookieName}}</div>
<div><b>Enable Set Cookie:</b> <i class="fa fa{{if .SessionConfig.EnableSetCookie}}-check{{end}}-square-o"></i></div>
<div><b>GC Interval Time:</b> {{.SessionConfig.GcIntervalTime}} seconds</div>
<div><b>Session Life Time:</b> {{.SessionConfig.SessionLifeTime}} seconds</div>
<div><b>HTTPS Only:</b> <i class="fa fa{{if .SessionConfig.CookieSecure}}-check{{end}}-square-o"></i></div>
<div><b>Cookie Life Time:</b> {{.SessionConfig.CookieLifeTime}} seconds</div>
<div><b>Session ID Hash Function:</b> {{.SessionConfig.SessionIDHashFunc}}</div>
<div><b>Session ID Hash Key:</b> {{.SessionConfig.SessionIDHashKey}}</div>
<div><b>Provider Config:</b> {{.SessionConfig.ProviderConfig}}</div>
<dl class="dl-horizontal admin-dl-horizontal">
<dt>Session Provider</dt>
<dd>{{.SessionProvider}}</dd>
<dt>Cookie Name</dt>
<dd>{{.SessionConfig.CookieName}}</dd>
<dt>Enable Set Cookie</dt>
<dd><i class="fa fa{{if .SessionConfig.EnableSetCookie}}-check{{end}}-square-o"></i></dd>
<dt>GC Interval Time</dt>
<dd>{{.SessionConfig.GcIntervalTime}} seconds</dd>
<dt>Session Life Time</dt>
<dd>{{.SessionConfig.SessionLifeTime}} seconds</dd>
<dt>HTTPS Only</dt>
<dd><i class="fa fa{{if .SessionConfig.CookieSecure}}-check{{end}}-square-o"></i></dd>
<dt>Cookie Life Time</dt>
<dd>{{.SessionConfig.CookieLifeTime}} seconds</dd>
<dt>Session ID Hash Function</dt>
<dd>{{.SessionConfig.SessionIDHashFunc}}</dd>
<dt>Session ID Hash Key</dt>
<dd>{{.SessionConfig.SessionIDHashKey}}</dd>
<dt>Provider Config</dt>
<dd>{{.SessionConfig.ProviderConfig}}</dd>
</dl>
</div>
</div>
@@ -103,7 +172,10 @@
</div>
<div class="panel-body">
<div><b>Picture Service:</b> {{.PictureService}}</div>
<dl class="dl-horizontal admin-dl-horizontal">
<dt>Picture Service</dt>
<dd>{{.PictureService}}</dd>
</dl>
</div>
</div>
@@ -113,9 +185,14 @@
</div>
<div class="panel-body">
<div><b>Log Mode:</b> {{.LogMode}}</div>
<div><b>Log Config:</b></div>
<div style="padding-top: 5px;"><pre>{{.LogConfig}}</pre></div>
<dl class="dl-horizontal admin-dl-horizontal">
<dt>Log Mode</dt>
<dd>{{.LogMode}}</dd>
<dt>Log Config</dt>
<dd>
<div style="padding-top: 5px;"><pre>{{.LogConfig}}</pre></div>
</dd>
</dl>
</div>
</div>

View File

@@ -2,14 +2,14 @@
{{template "base/navbar" .}}
<div id="body" class="container" data-page="admin">
{{template "admin/nav" .}}
<div id="admin-container" class="col-md-9">
<div id="admin-container" class="col-md-10">
<div class="panel panel-default">
<div class="panel-heading">
Statistic
</div>
<div class="panel-body">
Gogs database has <b>{{.Stats.Counter.User}}</b> users, <b>{{.Stats.Counter.PublicKey}}</b> SSH keys, <b>{{.Stats.Counter.Repo}}</b> repositories, <b>{{.Stats.Counter.Watch}}</b> watches, <b>{{.Stats.Counter.Action}}</b> actions, and <b>{{.Stats.Counter.Access}}</b> accesses.
Gogs database has <b>{{.Stats.Counter.User}}</b> users, <b>{{.Stats.Counter.PublicKey}}</b> SSH keys, <b>{{.Stats.Counter.Repo}}</b> repositories, <b>{{.Stats.Counter.Watch}}</b> watches, <b>{{.Stats.Counter.Action}}</b> actions, <b>{{.Stats.Counter.Access}}</b> accesses, <b>{{.Stats.Counter.Issue}}</b> issues, <b>{{.Stats.Counter.Comment}}</b> comments, <b>{{.Stats.Counter.Mirror}}</b> mirrors, <b>{{.Stats.Counter.Oauth}}</b> oauthes, <b>{{.Stats.Counter.Release}}</b> releases.
</div>
</div>
@@ -19,38 +19,95 @@
</div>
<div class="panel-body">
<div>Server Uptime: <b>{{.SysStatus.Uptime}}</b></div>
<div>Current Goroutines: <b>{{.SysStatus.NumGoroutine}}</b></div>
<hr/>
<div>Current Memory Usage: <b>{{.SysStatus.MemAllocated}}</b></div>
<div>Total Memory Allocated: <b>{{.SysStatus.MemTotal}}</b></div>
<div>Memory Obtained: <b>{{.SysStatus.MemSys}}</b></div>
<div>Pointer Lookup Times: <b>{{.SysStatus.Lookups}}</b></div>
<div>Memory Allocate Times: <b>{{.SysStatus.MemMallocs}}</b></div>
<div>Memory Free Times: <b>{{.SysStatus.MemFrees}}</b></div>
<hr/>
<div>Current Heap Usage: <b>{{.SysStatus.HeapAlloc}}</b></div>
<div>Heap Memory Obtained: <b>{{.SysStatus.HeapSys}}</b></div>
<div>Heap Memory Idle: <b>{{.SysStatus.HeapIdle}}</b></div>
<div>Heap Memory In Use: <b>{{.SysStatus.HeapInuse}}</b></div>
<div>Heap Memory Released: <b>{{.SysStatus.HeapReleased}}</b></div>
<div>Heap Objects: <b>{{.SysStatus.HeapObjects}}</b></div>
<hr/>
<div>Bootstrap Stack Usage: <b>{{.SysStatus.StackInuse}}</b></div>
<div>Stack Memory Obtained: <b>{{.SysStatus.StackSys}}</b></div>
<div>MSpan Structures Usage: <b>{{.SysStatus.MSpanInuse}}</b></div>
<div>MSpan Structures Obtained: <b>{{.SysStatus.HeapSys}}</b></div>
<div>MCache Structures Usage: <b>{{.SysStatus.MCacheInuse}}</b></div>
<div>MCache Structures Obtained: <b>{{.SysStatus.MCacheSys}}</b></div>
<div>Profiling Bucket Hash Table Obtained: <b>{{.SysStatus.BuckHashSys}}</b></div>
<div>GC Metadada Obtained: <b>{{.SysStatus.GCSys}}</b></div>
<div>Other System Allocation Obtained: <b>{{.SysStatus.OtherSys}}</b></div>
<hr/>
<div>Next GC Recycle: <b>{{.SysStatus.NextGC}}</b></div>
<div>Last GC Time: <b>{{.SysStatus.LastGC}} ago</b></div>
<div>Total GC Pause: <b>{{.SysStatus.PauseTotalNs}}</b></div>
<div>Last GC Pause: <b>{{.SysStatus.PauseNs}}</b></div>
<div>GC Times: <b>{{.SysStatus.NumGC}}</b></div>
<dl class="dl-horizontal admin-dl-horizontal">
<dt>Server Uptime</dt>
<dd>{{.SysStatus.Uptime}}</dd>
<dt>Current Goroutines</dt>
<dd>{{.SysStatus.NumGoroutine}}</dd>
<hr/>
<dt>Current Memory Usage</dt>
<dd>{{.SysStatus.MemAllocated}}</dd>
<dt>Total Memory Allocated</dt>
<dd>{{.SysStatus.MemTotal}}</dd>
<dt>Memory Obtained</dt>
<dd>{{.SysStatus.MemSys}}</dd>
<dt>Pointer Lookup Times</dt>
<dd>{{.SysStatus.Lookups}}</dd>
<dt>Memory Allocate Times</dt>
<dd>{{.SysStatus.MemMallocs}}</dd>
<dt>Memory Free Times</dt>
<dd>{{.SysStatus.MemFrees}}</dd>
<hr/>
<dt>Current Heap Usage</dt>
<dd>{{.SysStatus.HeapAlloc}}</dd>
<dt>Heap Memory Obtained</dt>
<dd>{{.SysStatus.HeapSys}}</dd>
<dt>Heap Memory Idle</dt>
<dd>{{.SysStatus.HeapIdle}}</dd>
<dt>Heap Memory In Use</dt>
<dd>{{.SysStatus.HeapInuse}}</dd>
<dt>Heap Memory Released</dt>
<dd>{{.SysStatus.HeapReleased}}</dd>
<dt>Heap Objects</dt>
<dd>{{.SysStatus.HeapObjects}}</dd>
<hr/>
<dt>Bootstrap Stack Usage</dt>
<dd>{{.SysStatus.StackInuse}}</dd>
<dt>Stack Memory Obtained</dt>
<dd>{{.SysStatus.StackSys}}</dd>
<dt>MSpan Structures Usage</dt>
<dd>{{.SysStatus.MSpanInuse}}</dd>
<dt>MSpan Structures Obtained</dt>
<dd>{{.SysStatus.HeapSys}}</dd>
<dt>MCache Structures Usage</dt>
<dd>{{.SysStatus.MCacheInuse}}</dd>
<dt>MCache Structures Obtained</dt>
<dd>{{.SysStatus.MCacheSys}}</dd>
<dt>Profiling Bucket Hash Table Obtained</dt>
<dd>{{.SysStatus.BuckHashSys}}</dd>
<dt>GC Metadada Obtained</dt>
<dd>{{.SysStatus.GCSys}}</dd>
<dt>Other System Allocation Obtained</dt>
<dd>{{.SysStatus.OtherSys}}</dd>
<hr/>
<dt>Next GC Recycle</dt>
<dd>{{.SysStatus.NextGC}}</dd>
<dt>Last GC Time</dt>
<dd>{{.SysStatus.LastGC}} ago</dd>
<dt>Total GC Pause</dt>
<dd>{{.SysStatus.PauseTotalNs}}</dd>
<dt>Last GC Pause</dt>
<dd>{{.SysStatus.PauseNs}}</dd>
<dt>GC Times</dt>
<dd>{{.SysStatus.NumGC}}</dd>
</dl>
</div>
</div>
</div>

View File

@@ -1,4 +1,4 @@
<div id="user-setting-nav" class="col-md-3 admin-nav">
<div id="user-setting-nav" class="col-md-2 admin-nav">
<ul class="list-group">
<li class="list-group-item{{if .PageIsDashboard}} active{{end}}"><a href="/admin"><i class="fa fa-tachometer fa-lg"></i> Dashboard</a></li>
<li class="list-group-item{{if .PageIsUsers}} active{{end}}"><a href="/admin/users"><i class="fa fa-users fa-lg"></i> Users</a></li>

View File

@@ -2,7 +2,7 @@
{{template "base/navbar" .}}
<div id="body" class="container" data-page="admin">
{{template "admin/nav" .}}
<div id="admin-container" class="col-md-9">
<div id="admin-container" class="col-md-10">
<div class="panel panel-default">
<div class="panel-heading">
Repository Management

View File

@@ -2,7 +2,7 @@
{{template "base/navbar" .}}
<div id="body" class="container" data-page="admin">
{{template "admin/nav" .}}
<div id="admin-container" class="col-md-9">
<div id="admin-container" class="col-md-10">
<div class="panel panel-default">
<div class="panel-heading">
User Management

View File

@@ -11,8 +11,8 @@
<div class="panel-body">
<br/>
<form action="/admin/users/{{.User.Id}}" method="post" class="form-horizontal">
{{if .IsSuccess}}<p class="alert alert-success">Account profile has been successfully updated.</p>{{else if .HasError}}<p class="alert alert-danger form-error">{{.ErrorMsg}}</p>{{end}}
{{.CsrfTokenHtml}}
{{template "base/alert" .}}
<input type="hidden" value="{{.User.Id}}" name="userId"/>
<div class="form-group">
<label class="col-md-3 control-label">Username: </label>

View File

@@ -12,7 +12,7 @@
<br/>
<form action="/admin/users/new" method="post" class="form-horizontal">
{{.CsrfTokenHtml}}
<div class="alert alert-danger form-error{{if .HasError}}{{else}} hidden{{end}}">{{.ErrorMsg}}</div>
{{template "base/alert" .}}
<div class="form-group {{if .Err_UserName}}has-error has-feedback{{end}}">
<label class="col-md-3 control-label">Username: </label>
<div class="col-md-7">

View File

@@ -0,0 +1,2 @@
{{if .Flash.ErrorMsg}}<div class="alert alert-danger form-error">{{.Flash.ErrorMsg}}</div>{{end}}
{{if .Flash.SuccessMsg}}<div class="alert alert-success">{{.Flash.SuccessMsg}}</div>{{end}}

View File

@@ -9,16 +9,27 @@
<meta name="description" content="Gogs(Go Git Service) is a GitHub-like clone in the Go Programming Language" />
<meta name="keywords" content="go, git">
<meta name="_csrf" content="{{.CsrfToken}}" />
{{if .Repository.IsGoget}}<meta name="go-import" content="{{.GoGetImport}} git {{.CloneLink.HTTPS}}">{{end}}
<!-- Stylesheets -->
{{if IsProdMode}}
<link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css">
<link href="//netdna.bootstrapcdn.com/font-awesome/4.0.3/css/font-awesome.css" rel="stylesheet">
<script src="//code.jquery.com/jquery-1.11.0.min.js"></script>
<script src="//netdna.bootstrapcdn.com/bootstrap/3.1.1/js/bootstrap.min.js"></script>
{{else}}
<link href="/css/bootstrap.min.css" rel="stylesheet" />
<link href="/css/todc-bootstrap.min.css" rel="stylesheet" />
<link href="/css/font-awesome.min.css" rel="stylesheet" />
<link href="/css/markdown.css" rel="stylesheet" />
<link href="/css/gogs.css" rel="stylesheet" />
<script src="/js/jquery-1.10.1.min.js"></script>
<script src="/js/bootstrap.min.js"></script>
{{end}}
<link href="/css/todc-bootstrap.min.css" rel="stylesheet" />
<link href="/css/markdown.css" rel="stylesheet" />
<link href="/css/gogs.css" rel="stylesheet" />
<script src="/js/lib.js"></script>
<script src="/js/app.js"></script>
<title>{{if .Title}}{{.Title}} - {{end}}{{AppName}}</title>

View File

@@ -1,16 +1,38 @@
<div class="masthead navbar" id="masthead">
<div class="container">
<nav class="nav">
<a id="nav-logo" class="nav-item{{if .PageIsHome}} active{{end}}" href="/"><img src="/img/favicon.png" alt="Gogs Logo" id="logo"></a>
<a class="nav-item{{if .PageIsUserDashboard}} active{{end}}" href="/">Dashboard</a>
<a class="nav-item{{if .PageIsHelp}} active{{end}}" href="https://github.com/gogits/gogs/wiki">Help</a>{{if .IsSigned}}
<a id="nav-logo" class="nav-item pull-left{{if .PageIsHome}} active{{end}}" href="/"><img src="/img/favicon.png" alt="Gogs Logo" id="logo"></a>
<a class="nav-item pull-left{{if .PageIsUserDashboard}} active{{end}}" href="/">Dashboard</a>
<a class="nav-item pull-left{{if .PageIsHelp}} active{{end}}" href="https://github.com/gogits/gogs/wiki">Help</a>{{if .IsSigned}}
{{if .HasAccess}}<!-- <form class="nav-item pull-left{{if .PageIsNewRepo}} active{{end}}" id="nav-search-form">
<div class="input-group">
<div class="input-group-btn">
<button type="button" class="btn btn-sm btn-default dropdown-toggle" data-toggle="dropdown">{{if .Repository}}This Repository{{else}}All Repositories{{end}} <span class="caret"></span></button>
<ul class="dropdown-menu">
{{if .Repository}}<li><a href="#">This Repository</a></li>
<li class="divider"></li>{{end}}
<li><a href="#">All Repositories</a></li>
</ul>
</div>
<input type="search" class="form-control input-sm" name="q" placeholder="search code, commits and issues"/>
</div>
</form> -->{{end}}
<a id="nav-out" class="nav-item navbar-right navbar-btn btn btn-danger" href="/user/logout/"><i class="fa fa-power-off fa-lg"></i></a>
<a id="nav-avatar" class="nav-item navbar-right{{if .PageIsUserProfile}} active{{end}}" href="{{.SignedUser.HomeLink}}" data-toggle="tooltip" data-placement="bottom" title="{{.SignedUserName}}">
<img src="{{.SignedUser.AvatarLink}}?s=28" alt="user-avatar" title="username"/>
</a>
<a class="navbar-right nav-item{{if .PageIsNewRepo}} active{{end}}" href="/repo/create" data-toggle="tooltip" data-placement="bottom" title="New Repository"><i class="fa fa-plus fa-lg"></i></a>
<a class="navbar-right nav-item{{if .PageIsUserSetting}} active{{end}}" href="/user/setting" data-toggle="tooltip" data-placement="bottom" title="Setting"><i class="fa fa-cogs fa-lg"></i></a>
{{if .IsAdmin}}<a class="navbar-right nav-item{{if .PageIsAdmin}} active{{end}}" href="/admin" data-toggle="tooltip" data-placement="bottom" title="Admin"><i class="fa fa-gear fa-lg"></i></a>{{end}}
<div class="navbar-right nav-item pull-right{{if .PageIsNewRepo}} active{{end}}" id="nav-repo-new" data-toggle="tooltip" data-placement="bottom" title="New Repo">
<button type="button" class="dropdown-toggle" data-toggle="dropdown"><i class="fa fa-plus-square fa-lg"></i></button>
<div class="dropdown-menu">
<ul class="list-unstyled">
<li><a href="/repo/create"><i class="fa fa-book"></i>Repository</a></li>
<li><a href="/repo/migrate"><i class="fa fa-clipboard"></i>Migration</a></li>
<!-- <li><a href="#"><i class="fa fa-users"></i>Organization</a></li> -->
</ul>
</div>
</div>
{{else}}<a id="nav-signin" class="nav-item navbar-right navbar-btn btn btn-danger" href="/user/login/">Sign In</a>
<a id="nav-signup" class="nav-item navbar-right" href="/user/sign_up/">Sign Up</a>{{end}}
</nav>

Some files were not shown because too many files have changed in this diff Show More