Compare commits

...

1185 Commits
1.5 ... 3.1

Author SHA1 Message Date
Naoki Takezoe
7d3c7a0c61 Update README.md for 3.1 release 2015-03-28 17:05:11 +09:00
Naoki Takezoe
7375ff9f97 (refs #677)Fix Fork button for non group users 2015-03-28 17:01:41 +09:00
Naoki Takezoe
5df6ec8985 Merge pull request #680 from team-lab/fix/#679-jquery-hashchange-plugin-for-jquery1.9
fix #679 jquery-hashchange-plugin for jquery 1.9
2015-03-28 03:27:09 +09:00
nazoking
9a8479ee58 fix #679 jquery-hashchange-plugin for jquery 1.9 2015-03-27 16:11:39 +09:00
Naoki Takezoe
73766f11eb (refs #664)Normalize line separator and empty line 2015-03-25 20:56:17 +09:00
Naoki Takezoe
a22878e2c5 (refs #671)Use servletPath instead of requestURI 2015-03-25 19:47:13 +09:00
Naoki Takezoe
1a2eb9d1e7 Merge pull request #676 from team-lab/#672-fix-blank-diff-output
#672 fix blank diff output
2015-03-25 13:00:20 +09:00
nazoking
277ace3c8e fix for ie 5 2015-03-25 10:21:40 +09:00
nazoking
40f376dbd9 fix for ie 7 2015-03-25 10:20:59 +09:00
nazoking
444af0935e fix regression: blank diff output #672 2015-03-25 09:36:01 +09:00
Tomofumi Tanaka
fb15fa0e43 Fix jetty.version in build.xml 2015-03-24 23:55:45 +09:00
Tomofumi Tanaka
bcd3e14870 Remove exec permission 2015-03-24 23:55:05 +09:00
Tomofumi Tanaka
c18702dcea Merge remote-tracking branch 'origin/api-support' 2015-03-24 23:40:01 +09:00
Naoki Takezoe
1341ef9c52 Merge pull request #670 from seratch/fixes-for-minor-updates
More fixes for #669
2015-03-24 15:21:39 +09:00
Kazuhiro Sera
f605a8d085 Run ./embed-jetty/update.sh 8.1.16.v20140903 2015-03-24 12:54:45 +09:00
Kazuhiro Sera
e0f7a7a3c6 Add a script to update embed-jetty jars 2015-03-24 12:54:28 +09:00
Kazuhiro Sera
1d72bed442 Bump Scala version to 2.11.6 on Travis builds 2015-03-24 12:50:28 +09:00
Naoki Takezoe
284b8e7c16 Merge pull request #669 from seratch/scalatra-2.3.1
Bump Scalatra to 2.3.1, sbt to 0.13.8
2015-03-24 11:27:43 +09:00
Kazuhiro Sera
ff9fb24094 Bump Scalatra to 2.3.1, sbt to 0.13.8 and upgrade minor version of other dependencies 2015-03-24 08:43:56 +09:00
Naoki Takezoe
fde4448dd0 Fix layout 2015-03-24 06:55:34 +09:00
Shintaro Murakami
d16ce90a3d (refs #649) Fix condition of filtering issues from milestone view. 2015-03-24 00:54:49 +09:00
Naoki Takezoe
3ed5525956 (refs #665)Fix markdown format 2015-03-23 21:42:49 +09:00
Naoki Takezoe
855d1e12aa Rename index.md to readme.md 2015-03-23 21:33:44 +09:00
Naoki Takezoe
e03797a58f Update notification.md 2015-03-23 21:33:01 +09:00
Naoki Takezoe
f0d38cf8ec Update auto_update.md 2015-03-23 21:32:26 +09:00
Naoki Takezoe
2723580e17 Update comment_action.md 2015-03-23 21:31:42 +09:00
Naoki Takezoe
1977aa481d Update mapping_and_validation.md 2015-03-23 21:30:57 +09:00
Naoki Takezoe
4b36a8f831 Update directory.md 2015-03-23 21:30:29 +09:00
Naoki Takezoe
96b56e38ba Update how_to_run.md 2015-03-23 21:29:56 +09:00
Naoki Takezoe
849d117ad3 Update index.md 2015-03-23 21:29:22 +09:00
Naoki Takezoe
8d57fca779 (refs #665)Move Mapping and Validation 2015-03-23 21:28:03 +09:00
Naoki Takezoe
0dc867306b Update index.md 2015-03-23 21:24:46 +09:00
Naoki Takezoe
eefb4c01ec (refs #665)Move developer's docs from Wiki 2015-03-23 21:21:24 +09:00
Naoki Takezoe
ccce499f7f Merge pull request #655 from hho/patch-1
Fix font in line comments
2015-03-22 08:00:56 -07:00
Naoki Takezoe
9f11eaa4d3 Merge pull request #643 from team-lab/feature/diff-ignore-space
Improvement diff
2015-03-22 05:59:16 -07:00
Naoki Takezoe
7b85c0e55f Merge pull request #656 from jparound30/feature_rename_detection
(refs #641) Enable rename detection
2015-03-22 02:23:31 -07:00
Tomofumi Tanaka
7e92f1abd5 Fix test code package name 2015-03-16 23:38:00 +09:00
Tomofumi Tanaka
825f2518e9 Rename migration sql 2015-03-16 23:23:59 +09:00
Tomofumi Tanaka
def1e877db Move api package to new rule 2015-03-16 23:22:56 +09:00
Tomofumi Tanaka
6acbd5b2cf Merge remote-tracking branch 'origin/master' into api-support
Conflicts:
	src/main/scala/ScalatraBootstrap.scala
	src/main/scala/gitbucket/core/controller/AccountController.scala
	src/main/scala/gitbucket/core/controller/ControllerBase.scala
	src/main/scala/gitbucket/core/controller/IssuesController.scala
	src/main/scala/gitbucket/core/controller/PullRequestsController.scala
	src/main/scala/gitbucket/core/controller/RepositorySettingsController.scala
	src/main/scala/gitbucket/core/controller/RepositoryViewerController.scala
	src/main/scala/gitbucket/core/model/Profile.scala
	src/main/scala/gitbucket/core/service/PullRequestService.scala
	src/main/scala/gitbucket/core/service/WebHookService.scala
	src/main/scala/gitbucket/core/servlet/InitializeListener.scala
	src/main/scala/gitbucket/core/view/helpers.scala
	src/main/twirl/gitbucket/core/pulls/conversation.scala.html
	src/main/twirl/gitbucket/core/pulls/mergeguide.scala.html
	src/main/twirl/issues/listparts.scala.html
2015-03-16 22:49:47 +09:00
tanacasino
73b7aef4a9 Merge pull request #635 from team-lab/api-support
Api support for JENKINS GitHub pull request builder plugin
2015-03-16 00:34:47 +09:00
nazoking
3d73e3922b (api-support)fix typo 2015-03-16 00:14:52 +09:00
nazoking
224e44151f (api-support)change crlf 2015-03-16 00:04:30 +09:00
Naoki Takezoe
d9c1293985 (refs #653)Sort tags by date 2015-03-15 11:38:45 +09:00
jparound30
849a40d4b5 (refs #641) Enable rename detection 2015-03-15 09:37:56 +09:00
Henning Hoefer
177387e9b0 Fix font in line comments
Line comments on a diff were rendered in the browsers default font
2015-03-13 15:04:06 +01:00
Naoki Takezoe
cacce54714 (refs #644)Fix authentication for Git access via HTTP 2015-03-13 01:58:04 +09:00
Naoki Takezoe
12082322ee (refs #646)Add autocomplete="true" to password field 2015-03-10 11:34:02 +09:00
Naoki Takezoe
7e0a5b7fec (refs #645)Fix disableByNotYourSelf validation 2015-03-10 11:31:17 +09:00
nazoking
d2cf4afc81 add diff stat bar 2015-03-10 00:54:57 +09:00
Naoki Takezoe
cce62de075 (refs #642)Fix markdown rendering style 2015-03-09 19:18:38 +09:00
nazoking
d9450df7e9 more github like style 2015-03-08 21:35:46 +09:00
nazoking
41fc81fab6 add filename for syntax highlight hint 2015-03-08 21:35:39 +09:00
nazoking
aa35498bdd Implements ignore-whitespace and hilight-syntax on diff.
Improvement experience speed
 * lazy diff rendering ( it is effective if tha diff has a lot of files ).
 * some feature implemented by javascript, to implement by css.
 * some javascript event handlers on each elements move to on parent elements.
2015-03-08 19:49:20 +09:00
Naoki Takezoe
14becd0bd6 Merge pull request #640 from rlazoti/fix-plugin
fix javascript getter for plug-in system
2015-03-05 02:00:27 +09:00
Rodrigo Lazoti
7390e21934 fix javascript getter for plug-in system 2015-03-03 18:07:25 -03:00
Naoki Takezoe
6b37967162 Add Version 3.0 to versions 2015-03-03 14:05:13 +09:00
Naoki Takezoe
a03b9584ee Update gitbucket.version to 3.0.0 2015-03-03 13:58:06 +09:00
Naoki Takezoe
34649dfeda Update group id 2015-03-03 11:16:48 +09:00
Naoki Takezoe
bc84cfc2c8 Update assembly jar deploy script for 3.0 release 2015-03-03 11:07:29 +09:00
Naoki Takezoe
bcba1f068b Update README.md for 3.0 release 2015-03-03 10:42:36 +09:00
Naoki Takezoe
e6f30ef86b Fix Profile structure 2015-03-03 10:36:14 +09:00
takezoe
9e67999ef0 Temporary fix instantiation error 2015-03-03 02:42:35 +09:00
takezoe
be75cef752 Fix testcase 2015-03-03 02:38:59 +09:00
Naoki Takezoe
19ead71b48 Bump up version number to 3.0.0 2015-03-02 22:22:52 +09:00
Naoki Takezoe
7ebe1d6c62 Merge branch 'master' into profile_generalization 2015-03-02 22:21:00 +09:00
Naoki Takezoe
2331b58b87 Merge branch 'batch-edit'
Conflicts:
	src/main/twirl/issues/list.scala.html
	src/main/twirl/issues/listparts.scala.html
2015-03-02 22:20:22 +09:00
Naoki Takezoe
d495b04d85 Change package name 2015-03-02 22:15:56 +09:00
shimamoto
751a8703ef (refs #632) Implement all the check of javascript. 2015-03-02 06:31:59 +09:00
nazoking
1e6d26221d show commit status on pull-request-list view 2015-03-02 03:12:26 +09:00
nazoking
44a8e98c7b add rete_limit api(disabled message only) 2015-03-02 03:12:25 +09:00
nazoking
415519716e show error as json 2015-03-02 03:12:24 +09:00
nazoking
597f86dc7b add webhook pull_request synchronize action 2015-03-02 03:12:23 +09:00
nazoking
579ed19949 move api classes to api package 2015-03-02 03:12:22 +09:00
nazoking
9221bfa045 create merge status cache as branch refs/pull/{issueId}/{merge,conflict} 2015-03-02 03:12:21 +09:00
nazoking
3057a31a6c add test for Diretory (set GitBucketHome when test scope) 2015-03-02 03:12:20 +09:00
nazoking
d47ccf587c Authentication move to filter 2015-03-02 03:12:19 +09:00
nazoking
3e78d423ac (refs #630) Fix bug on changing issues status. 2015-03-02 03:12:18 +09:00
nazoking
0299cee5ec add CommitStatus api and views. 2015-03-02 03:12:17 +09:00
nazoking
97ceffe689 add CommitStatus table and model and service. 2015-03-02 03:12:11 +09:00
shimamoto
9019d93449 (refs #632) Revert. And remove all checked checkbox. 2015-03-02 01:21:11 +09:00
Naoki Takezoe
32006e02c0 Fix build error 2015-03-02 00:45:28 +09:00
Naoki Takezoe
5ba0f6d51e Generalize Profile for plug-ins as ProfileBase 2015-03-02 00:32:59 +09:00
shimamoto
c004d501f6 (refs #632) Implement all the check of javascript. 2015-03-01 23:38:08 +09:00
Naoki Takezoe
6067fa0fca Fix broken layout of Wiki compare view 2015-02-28 12:47:52 +09:00
Naoki Takezoe
e2c6658e59 Fix compilation error 2015-02-28 12:47:06 +09:00
Naoki Takezoe
0a6e50cbbe Compare view supports diff for commit id, not only branch name 2015-02-28 12:43:50 +09:00
Naoki Takezoe
3732963d4b Add image API for plug-in 2015-02-28 03:40:02 +09:00
nazoking
83bcbef6ce move json extract logic to ControllerBase 2015-02-27 13:40:08 +09:00
nazoking
47cb4d1c49 add some WebAPI/v3.
* [Users](https://developer.github.com/v3/users/) get-a-single-user
 * [Issues/Comments](https://developer.github.com/v3/issues/comments/) list-comments-on-an-issue, create-a-comment
 * [Pull Requests](https://developer.github.com/v3/pulls/) list-pull-requests, get-a-single-pull-request, list-commits-on-a-pull-request
 * [Repositories](https://developer.github.com/v3/repos/) get
2015-02-27 13:40:07 +09:00
nazoking
32799cead7 more github like 2015-02-27 13:37:55 +09:00
nazoking
dbedc2166b fix typo 2015-02-27 13:37:54 +09:00
nazoking
e86b404ca2 date time format to iso-8601 ( fit The GitHub spec ) 2015-02-27 13:37:53 +09:00
nazoking
321a3a72f0 call web hook on issue or pull_request both opened or closed. and issue_comment. 2015-02-27 13:37:52 +09:00
nazoking
b512e7c390 make WebHookPullRequestPayload 2015-02-27 13:37:51 +09:00
nazoking
ae7ead6272 Add "X-Github-Event" header, and change interface. 2015-02-27 13:37:50 +09:00
nazoking
db55719a6f fix typo 2015-02-27 13:37:49 +09:00
nazoking
4e1094e75b add test 2015-02-27 13:37:48 +09:00
nazoking
3fd97662f5 Add Authorization logic to Controller 2015-02-27 13:37:47 +09:00
nazoking
d6946b93c3 Add access token table and manage ui. 2015-02-27 13:37:46 +09:00
Naoki Takezoe
20217058fe Merge branch 'feature/new-branches-ui' of https://github.com/team-lab/gitbucket into team-lab-feature/new-branches-ui
Conflicts:
	src/main/scala/service/PullRequestService.scala
2015-02-27 02:05:02 +09:00
Naoki Takezoe
e67365a19f Fix problem about milestone in issues and pull requests 2015-02-26 11:20:41 +09:00
Naoki Takezoe
c2f1817c6a Avoid exception when filter box is empty 2015-02-25 21:42:22 +09:00
Naoki Takezoe
4a948d9b01 Fix monospace style 2015-02-24 00:10:06 +09:00
Shintaro Murakami
377bc2703b (refs #630) Fix bug on changing issues status. 2015-02-23 02:41:56 +09:00
Naoki Takezoe
196890b26f Fix Test 2015-02-20 13:39:47 +09:00
Naoki Takezoe
491fc2c164 Merge branch 'new-plugin-system'
Conflicts:
	src/main/scala/servlet/InitializeListener.scala
2015-02-20 13:32:50 +09:00
Naoki Takezoe
1bed38f175 Change order of plug-in controller registration 2015-02-20 13:08:38 +09:00
Naoki Takezoe
b124c31f65 Plug-in action to be Scalatra controller 2015-02-20 09:29:44 +09:00
Naoki Takezoe
8c588cbd66 Provides Slick Session to plug-ins via ThreadLocal 2015-02-16 13:14:52 +09:00
shimamoto
334753b1ad Remove unnecessary ServletContext. 2015-02-15 21:39:31 +09:00
Naoki Takezoe
f6e7401d1b Add script to deploy assembly jar 2015-02-14 23:56:42 +09:00
Naoki Takezoe
ab564cc2d4 Merge branch 'master' into new-plugin-system
Conflicts:
	src/main/scala/servlet/AutoUpdateListener.scala
2015-02-14 23:40:43 +09:00
Naoki Takezoe
b10839a5c2 Add comment 2015-02-14 23:28:21 +09:00
shimamoto
bb3323fb0e Merge pull request #605 from rlazoti/add-connection-pool
Add Connection Pool
2015-02-14 22:56:00 +09:00
Naoki Takezoe
0b15ecbacd Add pom.xml for the assembly jar 2015-02-11 22:28:29 +09:00
Naoki Takezoe
9f6afaed07 Add Result case classes for plugin 2015-02-10 02:28:22 +09:00
Naoki Takezoe
925420734e Render Html with layout template 2015-02-08 23:51:21 +09:00
Naoki Takezoe
fb6bb12c52 Give Context to plugin actions 2015-02-08 23:09:30 +09:00
Naoki Takezoe
22d12d0488 Use util.Version for GitBucket migration 2015-02-08 22:35:58 +09:00
Naoki Takezoe
6888d959e1 Add migration system for plugins 2015-02-08 22:31:09 +09:00
Naoki Takezoe
65e66f52f6 Merge branch 'marklacroix-sidemenu-tooltips' 2015-02-07 17:58:04 +09:00
Naoki Takezoe
225abfa126 Merge branch 'sidemenu-tooltips' of https://github.com/marklacroix/gitbucket into marklacroix-sidemenu-tooltips 2015-02-07 17:54:29 +09:00
Naoki Takezoe
876757f2d4 Merge pull request #620 from nus/fix-facebox-resources
Fix facebox resource URLs.
2015-02-07 17:51:32 +09:00
Naoki Takezoe
7e77398645 Add prototype of global action support 2015-02-07 13:12:05 +09:00
Naoki Takezoe
73c76a5a88 Add plugin interfaces 2015-02-07 10:03:23 +09:00
Mark LaCroix
e5fca0d6cc Fix blinking tooltips on side menu 2015-02-06 16:48:54 -05:00
Naoki Takezoe
eed7e5177f Merge branch 'master' into new-plugin-system 2015-02-06 22:39:04 +09:00
Yota Ichino
dd54ab31cb Fix facebox resource URLs. 2015-02-05 01:50:07 +09:00
Naoki Takezoe
0085cb24ad Add description about 2.8 2015-02-01 13:00:31 +09:00
Naoki Takezoe
6a758902ef Small fix for #615 2015-02-01 12:55:37 +09:00
Naoki Takezoe
0d81a9a9b6 Merge pull request #615 from team-lab/fix/xss-by-raw-html
fix/xss by raw html
2015-02-01 12:44:15 +09:00
Naoki Takezoe
6e4f6da633 Merge pull request #612 from team-lab/fix/update-pullrequest-on-commit-by-online-editor
fix/update pullrequest when file edited by online editor
2015-01-31 18:19:46 +09:00
Naoki Takezoe
15118ca5c1 Merge pull request #614 from HairyFotr/patch-typo
Fix typo
2015-01-31 13:58:55 +09:00
HairyFotr
8161560757 Fix typo 2015-01-30 21:34:25 +01:00
nazoking
9ba564c864 test/html is cause of xss 2015-01-30 15:32:53 +09:00
nazoking
06b5b92673 update pullrequest commitId on file edited by online editor 2015-01-30 04:14:04 +09:00
nazoking
a417c373f1 'New Pull Request' button if you logined. 2015-01-30 00:55:02 +09:00
nazoking
5f5cc8d454 add action link to pull request. 2015-01-30 00:30:11 +09:00
Naoki Takezoe
b9b6589bd7 Update README.md 2015-01-29 21:47:54 +09:00
Naoki Takezoe
b79f6a5fa0 Update README.md 2015-01-29 21:47:00 +09:00
nazoking
0acbaeae86 new branches ui like GitHub. 2015-01-29 21:38:50 +09:00
Shintaro Murakami
bd046da3d0 (refs #532) Fix rendering of link over image 2015-01-28 00:09:34 +09:00
Naoki Takezoe
a889ed7c46 Merge pull request #591 from marklacroix/anon-access
(refs #274) Add option to deny anonymous (i.e. unauthorized) access
2015-01-27 10:28:11 +09:00
Naoki Takezoe
e24684cb2b Update favicon 2015-01-25 20:01:12 +09:00
Naoki Takezoe
5f939c18b4 (refs #609)Convert labelId when rename repository 2015-01-25 14:45:37 +09:00
takezoe
140f1eb31b Add sbt-assembly configuration 2015-01-25 02:31:21 +09:00
takezoe
d412dd5009 (refs #600)Fix broken layout 2015-01-25 01:16:03 +09:00
Mark LaCroix
8643bfeb37 Merge remote-tracking branch 'upstream/master' into anon-access
Conflicts:
	src/main/scala/app/SystemSettingsController.scala
	src/main/scala/service/SystemSettingsService.scala
	src/test/scala/view/AvatarImageProviderSpec.scala
2015-01-21 15:49:42 -05:00
Naoki Takezoe
31b6adf0e5 Merge pull request #606 from bati11/fix-mergeguide-text
Fix merge guide's text
2015-01-21 01:39:37 +09:00
bati11
f1ac2b3507 Change checkout branch name from "master" to ${pullreq.branch} 2015-01-20 23:32:47 +09:00
Rodrigo Lazoti
172af307a6 add connection pool to Database object 2015-01-20 12:14:28 -02:00
Naoki Takezoe
135e1ef73d Merge pull request #602 from mrkm4ntr/default-privacy-option-to-create-repo
(refs #495,#595) Add configuration to set default visibility option to create new repositories.
2015-01-20 10:59:02 +09:00
Naoki Takezoe
da55bf6af3 Apply new icon 2015-01-18 14:17:41 +09:00
Naoki Takezoe
883a9c8b17 Improve Wiki rendering performance 2015-01-18 14:06:19 +09:00
Naoki Takezoe
7da89940e3 Use the issues list template for the pull request list in the dashboard 2015-01-18 03:59:33 +09:00
Shintaro Murakami
3233b0ae3c Fix failed test. 2015-01-17 23:28:08 +09:00
Shintaro Murakami
4c2ed09915 (refs #495,#595) Add configuration to set default visibility option to create new repositories. 2015-01-17 23:04:41 +09:00
Naoki Takezoe
256b6c480f Merge pull request #546 from rlazoti/add-link-to-dashboard
Add repository's link to issue and pull request list on dashboard
2015-01-17 16:44:04 +09:00
Naoki Takezoe
dc311837f9 Merge pull request #596 from tnayuki/streaming-archive
not to make a temporary file when archive
2015-01-17 16:29:36 +09:00
Naoki Takezoe
92aec48c99 Merge pull request #547 from mslinn/master
Change Bootstrap's default color pink for code tags to match github's color
2015-01-17 16:17:43 +09:00
Naoki Takezoe
a6ada8c457 Merge pull request #582 from team-lab/add-message-to-login
Show information message on singin view.
2015-01-17 14:50:37 +09:00
Shintaro Murakami
dcc601502e (refs #589) Prevent adding event handler several times 2015-01-14 23:04:23 +09:00
Shintaro Murakami
dd58d8c804 (refs #598) Exclude count of pull requests from that of issues. 2015-01-12 22:50:12 +09:00
Shintaro Murakami
2ade54b7e3 (#refs 549) Change "…" at skipped line to pseudo element 2015-01-11 01:04:13 +09:00
Shintaro Murakami
136c5854f3 (refs #593) Expand column size of FILE_NAME in COMIT_COMMENT 2015-01-11 00:28:52 +09:00
Shintaro Murakami
c597238d9c (refs #549) Selecting lines in diff without line numbers. 2015-01-10 01:22:47 +09:00
Toru Nayuki
2552a58e08 not to make a temporary file when archive 2015-01-09 18:39:48 +09:00
nazoking
74ad5872a3 Revert "add information to singup view"
This reverts commit f7fd53bf09.
2015-01-09 14:55:28 +09:00
Shintaro Murakami
485d502bd3 (refs #584) Refactored 2015-01-09 00:16:09 +09:00
Naoki Takezoe
47bc8d030e Merge pull request #590 from ghmer/master
Allow LDAPS connections instead of only allowing TLS enabled connections
2015-01-08 02:44:36 +09:00
Mark LaCroix
48fe7133f7 Add anonymous access option to tests 2015-01-07 09:47:36 -05:00
Mark LaCroix
5d962dc5e4 Add option to deny anonymous (i.e. unauthorized) access 2015-01-07 09:17:22 -05:00
Mario Enrico Ragucci
31e8e5a951 code alignment. We want a pretty pull request! 2015-01-07 07:46:59 +01:00
Mario Enrico Ragucci
858373c628 small beautifying change to have code properly aligned 2015-01-07 07:45:18 +01:00
Mario Enrico Ragucci
7f142d2c0d Introducing "Enable SSL" option on LDAP settings 2015-01-07 07:41:41 +01:00
Shintaro Murakami
08b86232a8 Merge pull request #589 from mrkm4ntr/toggle-line-notes
Add checkbox to toggle inline notes.
2015-01-06 23:48:11 +09:00
Shintaro Murakami
6bf4f42fdb Add checkbox to toggle line notes 2015-01-06 23:27:03 +09:00
Shintaro Murakami
f3c7de36d8 Remove filter setting for old plugin 2015-01-05 22:28:30 +09:00
Naoki Takezoe
19f556de57 Merge pull request #587 from mrkm4ntr/comment-for-split-diff
(refs #564) Comment for side-by-side diff available
2015-01-04 13:46:33 +09:00
Naoki Takezoe
e4467df411 Merge pull request #586 from team-lab/feature/add-stat-icon-on-diff
add icon on each diff header
2015-01-04 13:25:15 +09:00
Shintaro Murakami
8d305a1fb1 Merge branch 'master' of https://github.com/takezoe/gitbucket into comment-for-split-diff 2015-01-04 10:47:51 +09:00
Shintaro Murakami
b47153e645 Remove old plugin test 2015-01-04 10:40:10 +09:00
Shintaro Murakami
c71766c84b (refs #564) Comment for side-by-side diff available 2015-01-03 17:33:33 +09:00
Naoki Takezoe
23e4d679ae Merge branch 'purge-old-plugin-system' 2015-01-03 04:47:10 +09:00
Naoki Takezoe
182acb2e02 Trim each lines of command guidance 2015-01-02 19:11:50 +09:00
nazoking
b255b15006 add icon on diff view 2015-01-02 17:35:53 +09:00
Naoki Takezoe
b458f88161 Remove enable.plugin flag 2015-01-02 02:27:35 +09:00
Naoki Takezoe
398d8f2f1c Merge branch 'master' into purge-old-plugin-system 2015-01-02 02:03:00 +09:00
Naoki Takezoe
85c1a56cbf Purge old plugin system 2015-01-02 01:59:21 +09:00
Shintaro Murakami
da216c6960 (refs #585) Fix issue in markdown preview 2014-12-31 16:24:30 +09:00
Naoki Takezoe
bc91b153bf Merge pull request #574 from michaeljayt/add-fork-options
Add fork to group options
2014-12-31 01:08:27 +09:00
Shintaro Murakami
bc50b47d3a (refs #584) Fix the activity of commenting to pull request. 2014-12-31 00:27:47 +09:00
michaeljayt
aed15a7f25 Skip the group popup when user has no group 2014-12-30 14:26:30 +08:00
michaeljayt
a1f09117b0 Fix security issue on fork 2014-12-30 08:50:19 +08:00
michaeljayt
0a4a4a51ca Add fork to group options 2014-12-30 08:50:19 +08:00
nazoking
f7fd53bf09 add information to singup view 2014-12-29 20:54:22 +09:00
nazoking
cbfb863a54 Add information message to singin view. 2014-12-29 19:56:52 +09:00
Naoki Takezoe
9d56d72611 Update README.md for 2.7 release 2014-12-29 11:32:23 +09:00
Naoki Takezoe
527c91ff9d Fix for #581. Column name and CSS is changed. 2014-12-29 03:24:04 +09:00
Naoki Takezoe
c58c2d6700 Merge pull request #556 from michaeljayt/add-ssh-clone-url
Add SSH clone url option when enabled SSH access
2014-12-29 02:12:18 +09:00
Naoki Takezoe
5518eca952 Merge pull request #581 from mrkm4ntr/fix-comment-in-pr
Fix bug on showing inline comments in Pull Request.
2014-12-29 02:10:57 +09:00
Shintaro Murakami
6e2b67ec0b Fix bug on showing inline comments in Pull Request. 2014-12-28 20:12:32 +09:00
Naoki Takezoe
837b1e44a7 Merge pull request #561 from torutk/rpm_nonroot
Enable contrib init file and RPM's spec file to run on RHEL 6/CentOS 6 a...
2014-12-27 20:55:26 +09:00
Naoki Takezoe
e04c230c6e Merge pull request #580 from HairyFotr/patch-lint
Fix a few issues detected by static analysis
2014-12-27 20:10:02 +09:00
HairyFotr
a01b5a4a59 Fix a few issues detected by static analysis 2014-12-26 15:40:05 +01:00
Naoki Takezoe
427b6ce846 Merge pull request #579 from banjun/fix-editor-preview-NoSuchElementException-enableTaskList
fix repo editor fails to preview
2014-12-22 23:06:46 +09:00
banjun
b7b5af2b72 add enableTaskList to post params for _preview 2014-12-22 19:36:12 +09:00
Naoki Takezoe
39fec57f72 (refs #578)Add migration for repositories which have removed parent or origin repository. 2014-12-22 01:32:15 +09:00
Naoki Takezoe
238dedb6df (refs #578)Clean original and parent repository information when it's deleted 2014-12-21 21:39:20 +09:00
Naoki Takezoe
af091117b7 (refs #577)Remove all HTML tags in Markdown 2014-12-20 01:51:15 +09:00
michaeljayt
ddea4e12f0 Add SSH clone url option when enabled SSH access 2014-12-12 21:58:21 +08:00
Naoki Takezoe
9767903252 (refs #567)Fix condition of repository search for issues. 2014-12-05 02:10:28 +09:00
takezoe
bc75f9f8a2 (refs #564)Fix for repository renaming 2014-11-28 01:42:44 +09:00
takezoe
63627fc1d0 (refs #564)Change the attached files directory to /commens from /issues 2014-11-28 00:20:46 +09:00
Naoki Takezoe
c23985c1a7 Merge pull request #564 from mrkm4ntr/coment-for-diff
(refs #9) Comments for commit and diff
2014-11-28 00:08:59 +09:00
Shintaro Murakami
af58e99dcf (refs #9) Comments for commit and diff 2014-11-26 22:59:52 +09:00
Toru Takahashi
676670e9e3 Enable contrib init file and RPM's spec file to run on RHEL 6/CentOS 6 as non root user. 2014-11-24 11:51:36 +09:00
Naoki Takezoe
823c52e941 Update README.md 2014-11-24 03:18:10 +09:00
Naoki Takezoe
7f42007648 (refs #507)Small fix about pull request UI 2014-11-23 14:59:03 +09:00
Naoki Takezoe
7214ef21d2 (refs #559)Fix merged message 2014-11-23 11:45:22 +09:00
Naoki Takezoe
18a4492975 Update README.md 2014-11-23 11:19:24 +09:00
Naoki Takezoe
99f73b1016 (refs #560)Replace build status badge with Travis 2014-11-23 01:29:38 +09:00
Naoki Takezoe
0c1ce6a088 (refs #560)Add travis configuration 2014-11-23 01:19:26 +09:00
Naoki Takezoe
ae6291ab83 Update version to 2.6 2014-11-22 22:36:27 +09:00
Naoki Takezoe
617fcf7c99 Fix compilation error in test 2014-11-22 22:34:27 +09:00
Naoki Takezoe
9df4a74837 (refs #507)Applying new UI to pull request detail page has been completed. 2014-11-22 22:30:44 +09:00
Naoki Takezoe
966d4251be (refs #507)Applying new UI to pull request detail page 2014-11-22 22:02:33 +09:00
Naoki Takezoe
84b2e9cdcd Fix compilation error 2014-11-22 21:47:34 +09:00
Naoki Takezoe
e29d63c91a Merge branch 'master' into newui-for-pullreq 2014-11-22 21:46:51 +09:00
Naoki Takezoe
805d2b8e79 (refs #530)Don't re-sort activities by repository renaming. 2014-11-22 21:40:32 +09:00
Naoki Takezoe
9983fd1292 Update README.md 2014-11-22 12:57:33 +09:00
Naoki Takezoe
1de202e927 (refs #554)Search box bug fix 2014-11-19 07:17:37 +09:00
Naoki Takezoe
4eb9f4a485 (refs #551)Adjust wiki buttons 2014-11-17 17:08:51 +09:00
Naoki Takezoe
a8801e4e41 (refs #432)Show the information message at the top page 2014-11-17 00:52:51 +09:00
Naoki Takezoe
ee1c84dbf2 (refs #508)Remove filter parameter 2014-11-16 20:22:36 +09:00
Naoki Takezoe
e40e1fa6cd (refs #508)Add search filter box to the dashboard 2014-11-16 01:38:31 +09:00
Naoki Takezoe
055f648ea2 (refs #508)Remove repository filter 2014-11-11 13:10:07 +09:00
Naoki Takezoe
37a399c3a2 (refs #508)Fix line separator 2014-11-11 12:09:48 +09:00
Naoki Takezoe
bc0b11b60a (refs #508)Basic filter box implementation 2014-11-11 12:07:22 +09:00
Naoki Takezoe
65a1ca7146 (refs #508)Start to add search filter box 2014-11-11 03:19:51 +09:00
Naoki Takezoe
2293030d4e Merge pull request #541 from rlazoti/fix-dashboard
Fix pull request's view on dashboard
2014-11-09 00:21:39 +09:00
Mike Slinn
2848f07b83 Merge remote-tracking branch 'upstream/master' 2014-11-08 04:11:55 -08:00
Mike Slinn
55224ddcd8 Changed Bootstrap's default color pink for code tags to match github's color 2014-11-08 04:07:14 -08:00
Rodrigo Lazoti
054ae75b6b Add repository's link to issues and pull request list on dashboard 2014-11-07 10:55:08 -02:00
Rodrigo Lazoti
c83fab611e Remove the 'All' tab 2014-11-06 19:01:16 -02:00
Rodrigo Lazoti
29baf1223c Fix pull request's view on dashboard 2014-11-04 18:15:07 -02:00
Naoki Takezoe
2a60f607ff Update README.md for 2.5 2014-11-04 01:38:29 +09:00
Naoki Takezoe
78f4d26aa0 Add version 2.5 2014-11-03 05:46:32 +09:00
Naoki Takezoe
f59e86f5ca (refs #529)Add icons 2014-11-03 05:25:03 +09:00
Naoki Takezoe
1c2af36c92 (refs #529)Mentioned filter 2014-11-03 04:35:41 +09:00
takezoe
badbe73f4e Fix for Firefox 2014-11-03 02:30:19 +09:00
takezoe
a9d58698cd (refs #529)Adjust bottom line 2014-11-03 01:40:47 +09:00
Naoki Takezoe
bb3f086aa6 (refs #529)Enhance dashboard header 2014-11-03 00:31:34 +09:00
Naoki Takezoe
2db674bb03 (refs #529)Organization filter 2014-11-02 13:26:46 +09:00
Naoki Takezoe
4bc4a16a80 Merge branch 'master' into newui-for-dashboard
Conflicts:
	src/main/twirl/dashboard/issueslist.scala.html
	src/main/twirl/dashboard/pullslist.scala.html
2014-11-01 03:14:19 +09:00
Naoki Takezoe
d88a105628 Merge pull request #512 from mrkm4ntr/create-branch-ui
(refs #394) Create branch from Web UI
2014-11-01 03:10:38 +09:00
Naoki Takezoe
15d0c5b506 Merge pull request #526 from mrkm4ntr/datetime-format
(ref #519) Change datetime formats
2014-11-01 03:10:18 +09:00
Naoki Takezoe
dbde79d2f2 Merge pull request #342 from bati11/feature-tasklist
Implement "Task List" in markdown
2014-11-01 03:09:24 +09:00
Naoki Takezoe
e6e3786b47 (refs #529)Visibility filter 2014-11-01 03:05:52 +09:00
Tomofumi Tanaka
4c1b8004fc (refs #533)Admin user must not disable self account yourself 2014-10-29 09:15:20 +09:00
shimamoto
ff4052f097 (refs #507) Fix the issue info of conversation page. 2014-10-19 23:39:25 +09:00
Naoki Takezoe
13c206d068 Applying new Issues UI to dashboard 2014-10-19 21:34:12 +09:00
Shintaro Murakami
5b875d7c73 Refactored, sorry. 2014-10-19 01:22:31 +09:00
Shintaro Murakami
e33dd9008b (ref #519) Change datetime formats 2014-10-18 23:21:47 +09:00
Naoki Takezoe
8764910553 (refs #504)Fix word-break of "Pages" table 2014-10-18 19:26:11 +09:00
Naoki Takezoe
4c89c40944 (refs #522)Recover user filter in dashboard 2014-10-18 19:11:30 +09:00
Shintaro Murakami
0f0986afcf (refs #394)Create branch from Web UI 2014-10-16 22:03:49 +09:00
Shintaro Murakami
5d5f1f8bdd (refs #514) Fix problems of renaming repository. 2014-10-16 22:03:49 +09:00
Naoki Takezoe
03e386b3ce Merge pull request #515 from mrkm4ntr/hotfix-514
(refs #514) Fix problems of renaming repository.
2014-10-12 19:05:54 +09:00
takezoe
435eac7ae6 (refs #511)Fix problem which is not possible to choose color at the colorpicker 2014-10-12 16:29:41 +09:00
takezoe
bd5df3977d (refs #518)Compile for Java7 2014-10-12 15:49:49 +09:00
bati11
ba218053f9 Modify to correspond to that "issuedetail.scala.html" has been deleted 2014-10-11 13:50:32 +09:00
bati11
1fe448a83b Merge branch 'master' into feature-tasklist
Conflicts:
	src/main/twirl/issues/issuedetail.scala.html
2014-10-11 12:07:14 +09:00
Shintaro Murakami
26a45d0117 (refs #514) Fix problems of renaming repository. 2014-10-09 22:05:42 +09:00
Naoki Takezoe
320585a530 Fix presentation problem on Firefox 2014-10-07 14:52:11 +09:00
Naoki Takezoe
ca0f888a99 Update for 2.4.1 2014-10-06 13:56:33 +09:00
Naoki Takezoe
3b08dc2e41 (refs #510)Fix dropdown presentation 2014-10-06 13:47:41 +09:00
Naoki Takezoe
cc128a49c1 (refs #510)Dirty fix for Firefox 2014-10-06 13:37:32 +09:00
Naoki Takezoe
e0148695f2 (refs #509)Fix link broken bug in Wiki 2014-10-06 13:22:42 +09:00
Naoki Takezoe
afe0b1dd71 Fix link bug in pull requests 2014-10-06 03:08:35 +09:00
Naoki Takezoe
353852d6da Update README.md 2014-10-06 02:14:10 +09:00
Naoki Takezoe
28585d1a3d Merge branch 'new-issue-ui' 2014-10-06 01:54:19 +09:00
shimamoto
9d69a48c65 (refs #488) Fix bug for refer comment. 2014-10-06 01:40:23 +09:00
shimamoto
2f95c76634 (refs #488) Fixed compile error. 2014-10-06 01:39:42 +09:00
shimamoto
eac9f0e6ff (refs #488) Fixed the action for assignee. 2014-10-06 01:30:30 +09:00
Naoki Takezoe
043fc21e05 Add Version 2.4 2014-10-06 01:05:40 +09:00
shimamoto
5854a75615 (refs #488) Fixed the action for label and milestone. 2014-10-06 00:52:14 +09:00
Naoki Takezoe
7b02946496 (refs #506)Fix generated URL for images 2014-10-05 23:07:15 +09:00
Naoki Takezoe
70f0ffd4f4 Fix label text color 2014-10-05 20:01:34 +09:00
Naoki Takezoe
91b82c2652 (refs #505)Disable the plugin system in default 2014-10-05 18:08:33 +09:00
shimamoto
b1017140aa (refs #488) Fixed the action for issue and comment content change. 2014-10-05 17:13:58 +09:00
takezoe
fc806b8813 Merge branch 'new-issue-ui' of https://github.com/takezoe/gitbucket into new-issue-ui 2014-10-05 15:37:22 +09:00
takezoe
836913482b (refs #488)Issue label editing is completed 2014-10-05 15:37:09 +09:00
Naoki Takezoe
b3df3f44c6 Merge pull request #500 from mrkm4ntr/split-diff-style
Modify styles of split diff.
2014-10-05 11:56:49 +09:00
shimamoto
4ffbf89e74 (refs #488) Fixed the action for issue title change. 2014-10-05 04:43:11 +09:00
shimamoto
9851c7d93d (refs #488) Changing the issue edit. 2014-10-04 23:53:37 +09:00
Mike Slinn
a10188260c Update README.md 2014-10-03 15:26:42 -07:00
Tomofumi Tanaka
2201f2b202 Simplify query 2014-10-04 00:31:23 +09:00
takezoe
c92e71bb7a Merge branch 'new-issue-ui' of https://github.com/takezoe/gitbucket into new-issue-ui 2014-10-01 02:25:43 +09:00
takezoe
d271fac350 (refs #488)Add transparent label icons 2014-10-01 02:25:19 +09:00
Naoki Takezoe
ce4522fc30 (refs #488)Remove top margin of the clear condition link 2014-09-30 02:31:11 +09:00
Tomofumi Tanaka
a178c48de6 Merge branch 'fix-498-dash-pr' 2014-09-29 20:51:32 +09:00
Tomofumi Tanaka
9d1323a044 (refs #498)Reformat counting pull request query 2014-09-29 16:38:12 +09:00
Tomofumi Tanaka
43babfed94 (refs #498) Correct pull request counts 2014-09-29 14:00:29 +09:00
Tomofumi Tanaka
6fa7ea30fb (refs #498)Returns private own repository
RepositoryService.getAllRepositories should returns
private own repository too.
2014-09-29 11:53:19 +09:00
Tomofumi Tanaka
d78315695b (refs $#498)Don't show private repo user doesn't have permission
This fix in the dashboard pull request view
But this fix still has a problem show wrong count number pull request.
2014-09-29 11:27:48 +09:00
shimamoto
16021865cb (refs #488) Fixed the screen layout. 2014-09-28 21:15:30 +09:00
Naoki Takezoe
b516be242d (refs #488)Show number of issues for each labels 2014-09-28 11:43:58 +09:00
Naoki Takezoe
0124f7cc3c (refs #488)Change permission to access to labels 2014-09-28 11:35:19 +09:00
Naoki Takezoe
f3eec35287 (refs #488)Fix nav-pills style in the header of the issues system 2014-09-28 11:24:27 +09:00
Naoki Takezoe
fb396a33b0 (refs #488)Remove unnecessary div 2014-09-28 03:05:04 +09:00
Naoki Takezoe
3370499421 (refs #488)Apply new UI to labels 2014-09-28 03:04:36 +09:00
shimamoto
d847e27cf9 (refs #488) Move Milestone and Assignee. 2014-09-27 23:48:38 +09:00
Naoki Takezoe
9684b158ce (refs #488)Apply new UI to the milestone create / edit page 2014-09-26 02:14:30 +09:00
Naoki Takezoe
8456808a8e (refs #488)Add milestone icons 2014-09-25 09:37:13 +09:00
Naoki Takezoe
9747899a19 (refs #488)Update icons 2014-09-25 09:36:48 +09:00
shimamoto
099304605e Merge branch 'new-issue-ui' of https://github.com/takezoe/gitbucket into new-issue-ui 2014-09-25 04:49:07 +09:00
shimamoto
30994d0465 (refs #488) WIP: Fixing the screen layout for the issue detail. 2014-09-25 04:48:39 +09:00
Naoki Takezoe
71fdbe7b71 (refs #488)Apply new UI to Milestones tab 2014-09-25 02:32:55 +09:00
Naoki Takezoe
86432c5ffe (refs #488)Small fix 2014-09-25 01:52:32 +09:00
takezoe
4dfa1fb0f8 Merge remote-tracking branch 'origin/new-issue-ui' into new-issue-ui 2014-09-23 23:26:54 +09:00
takezoe
db59a7652f (refs #488)Add icons for new Issues UI 2014-09-23 23:26:46 +09:00
shimamoto
417470a81c (refs #488) Fixed the screen layout for the issue creation. 2014-09-23 21:42:56 +09:00
Naoki Takezoe
cc639da17e (refs #488)Unify issues and pull requests template 2014-09-23 18:11:46 +09:00
Naoki Takezoe
f619f4a9bc (refs #488)Remove unnecessary template arguments 2014-09-23 17:50:47 +09:00
Naoki Takezoe
5dffc2a64e (refs #488)Batch edit for pull requests 2014-09-23 17:41:53 +09:00
Naoki Takezoe
bb63a8d14c (refs #488)Remove unnecessary template arguments 2014-09-23 17:23:52 +09:00
Naoki Takezoe
c1263cc16d (refs #488)Batch edit for issues 2014-09-23 14:57:29 +09:00
Shintaro Murakami
49f2e7d70f Add empty style. 2014-09-23 02:10:46 +09:00
Shintaro Murakami
f93b535f70 Modify styles of split diff. 2014-09-23 00:07:00 +09:00
shimamoto
e16d3c823b Update slick version. 2014-09-22 21:59:17 +09:00
Naoki Takezoe
7a6fdbcf50 (refs #488)Fix the "New pull request" button 2014-09-22 19:26:36 +09:00
Naoki Takezoe
46041a3762 (refs #488)Apply new UI to the pull request list 2014-09-22 19:10:39 +09:00
Naoki Takezoe
20b0553f7f (refs #488)Exclude pull requests from the issue list 2014-09-22 18:38:28 +09:00
Naoki Takezoe
5870cacf44 (refs #488)Fix presentation of issue list header 2014-09-22 18:29:30 +09:00
Naoki Takezoe
cb512cd98d (refs #488)Add link to clear search condition 2014-09-22 10:18:26 +09:00
Naoki Takezoe
90487eb7b7 (refs #488)Merge user filter into IssueSearchCondition 2014-09-22 10:08:22 +09:00
Naoki Takezoe
706fa77de3 (refs #488)Add service.IssuesService.IssueInfo 2014-09-21 02:06:38 +09:00
bati11
26b14ded58 Add nested task list support 2014-09-20 10:57:33 +09:00
Naoki Takezoe
3b1367dd8e (refs #488)Displays the milestone on the issue list. 2014-09-20 03:02:52 +09:00
bati11
e1f310317d Modify GitBucketHtmlSerializer constructor parameters
- Add to the GitBucketHtmlSerializer constructor parameter "hasWritePermission"
- Remove the call to the RepositoryService.hasWritePermission in GitBucketHtmlSerializer
2014-09-19 14:13:53 +09:00
bati11
937814ec5d Merge branch 'master' into feature-tasklist
Conflicts:
	src/main/scala/app/IssuesController.scala
	src/main/twirl/issues/create.scala.html
2014-09-19 12:45:09 +09:00
bati11
b55fc649a6 Change crlf to lf 2014-09-19 12:43:04 +09:00
Naoki Takezoe
f4e4506517 (refs #488)Start to apply new issue UI to the issue list. 2014-09-16 00:46:38 +09:00
Naoki Takezoe
287a0b6669 (refs #488)Copy listparts.scala.html from issues/pulls to dashboard. 2014-09-15 23:47:06 +09:00
Naoki Takezoe
5bddd352af (refs #453)Fix "Test Hook" behavior 2014-09-15 13:04:39 +09:00
Naoki Takezoe
9c6ea8fb9d Fix client side validation error 2014-09-15 12:39:30 +09:00
Naoki Takezoe
32e8bf46a7 Merge pull request #491 from mrkm4ntr/myBranch
Change to show a shortcut path of the directory whose ancestors have only one child directory.
2014-09-15 02:12:52 +09:00
Naoki Takezoe
d61fe1bf84 (refs #483)Fix link in markdown 2014-09-14 20:31:02 +09:00
Naoki Takezoe
47dbea947d (refs #487) split diff is available 2014-09-14 10:53:17 +09:00
Naoki Takezoe
97c6b0495e Fix Warnings 2014-09-13 19:01:49 +09:00
Tomofumi Tanaka
a602ece8e9 (refs #490)Set HEAD ref when saved default branch
GitBucket allows user to configure default branch in the repository.
But it's only affect repository viewer in the GitBucekt world.
This change set default branch in the Git world.
2014-09-12 22:01:21 +09:00
Shintaro Murakami
cf6dca84d8 Release TreeWalk in recuresive function. 2014-09-11 00:06:28 +09:00
Shintaro Murakami
79432ff8ad Like GitHub, show a shortcut path of the directory whoes ancestors have only one child directory. 2014-09-10 07:07:04 +09:00
tanacasino
b8613431de Merge pull request #484 from douglarek/patch-1
Use a shebang (#!/bin/sh) to run sbt.sh
2014-09-04 11:42:17 +09:00
Lingchao Xin
698eafa562 Use a shebang (#!/bin/sh) to run sbt.sh
If no shebang provided in a shell file, it will be executed by default shell.

However this script is not compatible with Fish Shell, so use sh to execute it.
2014-09-04 10:25:24 +08:00
Naoki Takezoe
d33886db89 Add RawData and 404 error response for plugin action 2014-09-03 02:26:06 +09:00
Naoki Takezoe
cde09d3a59 Fix plugin path problem 2014-09-03 01:46:37 +09:00
Naoki Takezoe
5674f0e980 Update README.md 2014-09-01 14:34:58 +09:00
Naoki Takezoe
b9ade60eb2 (refs #464)Improve plugin installing/updating behavior 2014-09-01 01:11:19 +09:00
Naoki Takezoe
96303723fa (refs #464)Clear plugins before upgrading to 2.3. 2014-09-01 00:29:13 +09:00
Naoki Takezoe
0f5dbc5788 Merge branch 'master' into scala-plugin
Conflicts:
	project/build.scala
2014-09-01 00:02:31 +09:00
Naoki Takezoe
8df0c3a439 (refs #476)Change Jetty temp directory to GITBUCKET_HOME/tmp 2014-08-31 23:31:59 +09:00
Tomofumi Tanaka
ca6a86816a (refs #461)Correct atom feed datetime format 2014-08-25 22:32:07 +09:00
Tomofumi Tanaka
3ea939798f Remove unused import 2014-08-24 22:18:21 +09:00
Tomofumi Tanaka
d947410e3c (refs #434)Refactor to get last modified commit 2014-08-24 22:18:21 +09:00
Tomofumi Tanaka
db59bc08ac (refs #434)Show only last modified commit 2014-08-24 22:18:14 +09:00
Naoki Takezoe
95a8649f79 (refs #464)Add Fragment rendering support for Ajax 2014-08-24 13:56:49 +09:00
Naoki Takezoe
ffd10122ed (refs #464)Some improve for plugin API
- Place holder support for db API
- Redirect support for plugin action
2014-08-23 17:57:06 +09:00
Naoki Takezoe
c4c39f36e9 (refs #464)Add db.update() to update DB from plugin 2014-08-23 03:28:13 +09:00
Naoki Takezoe
96900c3cbf (refs #464)Remove unnecessary App mix-in 2014-08-23 03:26:19 +09:00
Tomofumi Tanaka
69fa370d12 Tweak font size and family in blog/diff view 2014-08-22 00:02:58 +09:00
Tomofumi Tanaka
7496437d11 Remove unused h7 2014-08-20 22:34:48 +09:00
shimamoto
33b7d09af7 Update slick version to 2.1.0. 2014-08-17 18:25:06 +09:00
Naoki Takezoe
53d0974760 (refs #457)Fix the target of updateRef 2014-08-17 13:01:05 +09:00
Naoki Takezoe
a87399f223 (refs #464)Add a new parameter to specify request method for plugin actions 2014-08-16 16:02:02 +09:00
Naoki Takezoe
975dfb17e1 (refs #464)Twirl support for plugin 2014-08-16 02:53:43 +09:00
Naoki Takezoe
8b8bd0289b (refs #464)Fix test case 2014-08-14 22:44:04 +09:00
Naoki Takezoe
3bb69c623b (refs #464)Switch to play-twirl 2014-08-14 18:37:37 +09:00
Naoki Takezoe
dd427bdbef Merge branch 'master' of https://github.com/takezoe/gitbucket 2014-08-14 14:01:33 +09:00
Naoki Takezoe
b40657a14a (refs #467)Reverse tag table ordering 2014-08-14 14:01:11 +09:00
Tomofumi Tanaka
21ca5b2eec (refs #471)Show the copy button only when flash is available
Check flash availability before Showing the copy button.
2014-08-14 12:43:55 +09:00
Naoki Takezoe
b78d584d8a (refs #464)Add drop tables capability when plugin is uninstalled 2014-08-14 01:56:47 +09:00
Naoki Takezoe
e6b666a66a (refs #464)Implementing database migration system for plugin 2014-08-13 23:16:13 +09:00
Naoki Takezoe
bab93ea4f5 (refs #464)Fix compilation error 2014-08-13 22:06:08 +09:00
Naoki Takezoe
7fe98253ae Merge pull request #452 from mslinn/master
Enhanced install script so it works under Ubuntu and Mac OS/X
2014-08-13 15:38:44 +09:00
Naoki Takezoe
13385cbced (refs #464)Add PLUGIN table for plugin management 2014-08-13 02:23:29 +09:00
Naoki Takezoe
3f20cec7b2 (refs #464)Add Scaladoc 2014-08-12 00:37:36 +09:00
Naoki Takezoe
a0e4b020ca (refs #464)Authentication for actions which are defined by plugin is completed 2014-08-12 00:33:13 +09:00
Naoki Takezoe
ea5d898b27 (refs #464)Add Security sealed trait which is used by plugin 2014-08-12 00:02:48 +09:00
Naoki Takezoe
4e652b5ccd (refs #464)Add authentication for plugin action 2014-08-11 19:27:24 +09:00
Naoki Takezoe
dd809896c8 (refs #464)Add extension point to inject JavaScript instead of adding button 2014-08-11 00:45:58 +09:00
Naoki Takezoe
93536d3365 (refs #464)Add new extension point to add buttons 2014-08-10 05:42:06 +09:00
Naoki Takezoe
098b18fe6d (refs #464)Experimental implementation of Scala based plugin 2014-08-10 04:33:57 +09:00
tanacasino
66efdac757 Merge pull request #449 from jparound30/fix_423
Change blob view's table-layout property.
2014-08-07 23:08:54 +09:00
Tomofumi Tanaka
45545d3815 Revert "(refs #458)Skip unexpected commit message"
This reverts commit be79ac2eb2.
2014-08-05 23:02:31 +09:00
Tomofumi Tanaka
b65d41731b (refs #458)Correct commit message in activity info 2014-08-05 23:00:56 +09:00
Tomofumi Tanaka
be19e97518 Merge branch '2.2-update' 2014-08-05 08:53:51 +09:00
Naoki Takezoe
2ebf2b99bd Update README.md 2014-08-05 02:15:36 +09:00
Tomofumi Tanaka
be79ac2eb2 (refs #458)Skip unexpected commit message
This patch is temporary measures.
MUST create AutoUpdate before release 2.3.
2014-08-05 00:43:20 +09:00
Tomofumi Tanaka
05afec3236 (refs#458)Correct short and full message
Swaped short and full message in commit info by accident.
2014-08-05 00:07:21 +09:00
Naoki Takezoe
57879eb72e Update README.md 2014-08-04 00:08:05 +09:00
Naoki Takezoe
2bc915f51b Disable JavaScript console 2014-08-04 00:00:41 +09:00
Naoki Takezoe
1ca55805b5 JSON response support for plug-ins 2014-08-03 23:59:56 +09:00
Naoki Takezoe
93cc1be166 (refs #374)Fix build.xml 2014-08-03 19:26:01 +09:00
Naoki Takezoe
f88ce3f671 Update version number to 2.2 2014-08-03 19:19:15 +09:00
Naoki Takezoe
20aabfc273 Merge branch 'scala-2.11' 2014-08-03 19:13:34 +09:00
shimamoto
601f8c4249 (refs #374) Fix compile error. 2014-08-03 18:41:03 +09:00
Tomofumi Tanaka
d0ccfc52b8 (refs #421)Add tar.gz archive download link 2014-08-02 21:22:49 +09:00
Tomofumi Tanaka
c22aef8ee2 (refs #421)Add tar.gz archive download
* Update jgit version
* Add new lib org.eclipse.jgit.archive
* TODO: Add link in views
2014-08-01 23:56:02 +09:00
Tomofumi Tanaka
3807e61a48 Merge branch 'show-author' 2014-07-31 22:48:15 +09:00
Naoki Takezoe
55722f87af Fix TestCase 2014-07-31 22:04:52 +09:00
Tomofumi Tanaka
212f3725ed (refs #437)Show author at blob, commits and wiki history view 2014-07-30 22:41:04 +09:00
Mike Slinn
193a312b22 Made gitbucket run on system startup and stop on shutdown 2014-07-29 15:49:48 -07:00
Mike Slinn
6a2d2ebfd1 Added help info for user about making iptables changes persistent 2014-07-29 15:44:43 -07:00
Naoki Takezoe
82beed1f44 (refs #374)Upgrading to Scala 2.11.2 2014-07-30 07:43:32 +09:00
Naoki Takezoe
0ede7e9921 (refs #374)Upgrading to Scalatra 2.3 and Slick 2.1.
Some compilation errors in Slick code are remaining.
2014-07-30 07:36:35 +09:00
Mike Slinn
6d200aa340 Works under Ubuntu and Mac OS/X 2014-07-29 07:28:44 -07:00
Mike Slinn
a0fbb90048 Works on Mac, need to retest on Ubuntu 2014-07-29 00:18:09 -07:00
Mike Slinn
08e29e7077 Added install script, made existing RedHat init script also work with Ubuntu 2014-07-28 10:10:27 -07:00
Tomofumi Tanaka
d2317d0a97 (refs #437)Fix typo
Threre is more good function name, but i have no idea 😵
2014-07-29 01:24:09 +09:00
Tomofumi Tanaka
972628eb65 (refs #437)Show author and committer at files view 2014-07-29 01:11:27 +09:00
Tomofumi Tanaka
51a56356cb (refs #437)Show author at repo file list view 2014-07-29 00:56:34 +09:00
jparound30
3bef71f5f2 (refs #423)Change blob view's table-layout property. 2014-07-29 00:22:22 +09:00
Tomofumi Tanaka
2bb1f6168a (refs #437)Show author instead of committer
* Explicit classify committer and author
* Use author to render avatar image html
* Support commit view
2014-07-29 00:06:45 +09:00
shimamoto
b13820fc0e Improved model package. The details are as follows:
* Fix the Profiles class from package object to simple object
* Fix the row case class to model package
* Define the alias of JdbcBackend#Session
2014-07-28 04:52:56 +09:00
takezoe
723de9e81e Fix TestCase 2014-07-27 22:56:02 +09:00
takezoe
3e161353ed Merge branch 'slick2-compilation-problem' 2014-07-27 21:24:40 +09:00
takezoe
2a8706630a (refs #445)Fix yen to backslash 2014-07-27 17:11:27 +09:00
Naoki Takezoe
121b6ee641 Fix incremental compilation problem caused by Slick.
This is temporary fix to decrease compilation time in development. Therefore this fix will be reverted in the future to add multiple database support capability.
2014-07-27 03:31:45 +09:00
Naoki Takezoe
34e299bf52 (refs #445)Keep line separator in online file editing 2014-07-26 23:15:47 +09:00
Naoki Takezoe
0822b7b5f3 (refs #444)Fix pull request count in dashboard 2014-07-26 19:12:09 +09:00
Naoki Takezoe
618110327a (refs #443)Fix merge guidance 2014-07-26 04:00:15 +09:00
Naoki Takezoe
f58f476060 (refs #441)Upgrade h2 to the latest version. 2014-07-25 15:46:29 +09:00
Naoki Takezoe
f5a544603a Experimental JDBC API for JavaScript plug-ins 2014-07-24 01:39:26 +09:00
Naoki Takezoe
89515cd087 Add an argument RepositoryInfo to RepositoryAction 2014-07-24 00:57:21 +09:00
Naoki Takezoe
37731c4163 Enable plugin system 2014-07-23 19:50:38 +09:00
Naoki Takezoe
1d4720d784 Merge pull request #439 from jmu/master
Fix IE copy not working
2014-07-23 01:30:21 +09:00
jmu
a10b053489 fix ie copy not working 2014-07-22 19:50:02 +08:00
takezoe
6122c8a1e1 Fix #438 2014-07-21 03:31:52 +09:00
Tomofumi Tanaka
fa9254c240 (refs #435)Correct merge commit message
Use ${user}/${branch} instead of ${user}/{repoName}
2014-07-16 23:33:14 +09:00
Tomofumi Tanaka
10616bca7d (refs #436)Encode branch name to delete at pullreq view 2014-07-16 19:24:25 +09:00
Naoki Takezoe
307f7e15e9 (refs #431)Fix CSS layout in Wiki page 2014-07-15 07:07:52 +09:00
Naoki Takezoe
86cf97d76b Fix TODO: Close issue and send webhook from online editing 2014-07-14 01:10:33 +09:00
shimamoto
01f6590c04 Fix TODO. 2014-07-14 00:08:34 +09:00
shimamoto
8f0c22bae9 Improve slick session(because transaction for unnecessary). 2014-07-13 23:54:53 +09:00
Naoki Takezoe
652a68c5b1 Fix TODO 2014-07-13 23:20:16 +09:00
Naoki Takezoe
1f56e1360d Fix font size 2014-07-13 16:31:24 +09:00
Naoki Takezoe
38475ffefe Fix avatar image size 2014-07-13 16:26:57 +09:00
Naoki Takezoe
7a44a4d726 Merge branch '401-news-feed-of-private-repo' of https://github.com/utensil/gitbucket into utensil-401-news-feed-of-private-repo
Conflicts:
	src/main/scala/app/IndexController.scala
	src/main/scala/service/ActivityService.scala
2014-07-13 16:07:42 +09:00
Naoki Takezoe
9dbc0c3fd6 Change LDAPUtil method name 2014-07-13 15:53:12 +09:00
Naoki Takezoe
56bb43ea6b AccountUtil is merged to LDAPUtil 2014-07-13 15:13:59 +09:00
Naoki Takezoe
b287c1f60d Merge branch 'add-features-to-ldapauth' of https://github.com/yjkony/gitbucket into yjkony-add-features-to-ldapauth
Conflicts:
	src/main/scala/app/IndexController.scala
	src/main/scala/service/SystemSettingsService.scala
	src/main/scala/util/LDAPUtil.scala
	src/main/scala/util/Notifier.scala
2014-07-13 13:49:04 +09:00
Naoki Takezoe
258d53b7a6 Disable submit buttons while performing validation 2014-07-13 03:48:44 +09:00
Naoki Takezoe
2e11d6dd78 Disable submit buttons while performing validation 2014-07-13 03:47:40 +09:00
Tomofumi Tanaka
a2a2e22485 Fix compilation error service test case 2014-07-09 00:25:40 +09:00
Tomofumi Tanaka
c182cde14b Revert "Disable TestCase for Services"
This reverts commit 104c3bc89d.
2014-07-08 23:55:15 +09:00
Naoki Takezoe
104c3bc89d Disable TestCase for Services 2014-07-06 17:35:18 +09:00
Naoki Takezoe
2668977918 Convert CRLF to LF 2014-07-06 17:07:52 +09:00
Naoki Takezoe
28424c96c4 Readying 2.1 release 2014-07-06 17:06:26 +09:00
Naoki Takezoe
9cfa8c594b Merge branch 'master' into slick2
Conflicts:
	project/build.scala
	src/main/scala/app/IndexController.scala
	src/main/scala/app/RepositorySettingsController.scala
	src/main/scala/model/Account.scala
	src/main/scala/model/BasicTemplate.scala
	src/main/scala/model/Issue.scala
	src/main/scala/model/IssueComment.scala
	src/main/scala/model/package.scala
	src/main/scala/service/IssuesService.scala
	src/main/scala/service/PullRequestService.scala
	src/main/scala/service/RepositoryService.scala
	src/main/scala/service/WikiService.scala
	src/main/scala/servlet/TransactionFilter.scala
	src/main/scala/util/Notifier.scala
2014-07-06 17:02:49 +09:00
Naoki Takezoe
5c70cd654c (refs #341)Add TODO about Slick 2.0 migration 2014-07-06 16:21:25 +09:00
Naoki Takezoe
7aca24e51d (refs #341)Fix compilation error about date conversion 2014-07-06 16:09:50 +09:00
Naoki Takezoe
cce0b67871 (refs #341)Fix compilation error of delete statements 2014-07-06 15:42:45 +09:00
Naoki Takezoe
606cd83f44 Fix CRLF to LF 2014-07-06 13:19:27 +09:00
Naoki Takezoe
32897c36f9 (refs #341)Fix compilation error other than date mapping and delete statement 2014-07-06 13:07:05 +09:00
Naoki Takezoe
92e4e12655 Update README.md 2014-07-05 18:06:51 +09:00
Naoki Takezoe
c8e5b75165 Disable front-end of plugin system in GitBucket 2.1 2014-07-05 18:01:19 +09:00
Naoki Takezoe
09b9a52ad3 Merge branch 'plugin-system' 2014-07-05 17:36:02 +09:00
Naoki Takezoe
33378c6464 Merge pull request #425 from okapies/fix-ldap-filter
Filter by username explicitly
2014-07-05 11:54:08 +09:00
shimamoto
259bcfc14f (refs #341) Fix compile errors. 2014-07-01 04:08:15 +09:00
shimamoto
c361d24ba4 (refs #341) Implement a method to get the session. 2014-07-01 03:43:55 +09:00
shimamoto
d5e1b18b52 (refs #341) Sets default value. 2014-07-01 03:40:21 +09:00
tanacasino
684a17a15b Merge pull request #422 from tanacasino/improve-markdown-css
Make markdown looks like more GitHub
2014-07-01 02:44:11 +09:00
Yuta Okamoto
66b7b69d20 specify LDAP search filter explicitly 2014-06-30 23:38:43 +09:00
Tomofumi Tanaka
57254f6366 Make markdown looks like more GitHub 2014-06-29 22:17:12 +09:00
Naoki Takezoe
c64909ab1a Plugin updating is executed asynchronously by Quartz Scheduler 2014-06-29 16:09:41 +09:00
Naoki Takezoe
34dd8541f4 Disable JavaScript console 2014-06-29 14:35:49 +09:00
Naoki Takezoe
50b4fb154d Fix JavaScript path 2014-06-29 14:33:48 +09:00
Naoki Takezoe
0b3781ec8a Add plugin updating capability 2014-06-29 14:26:33 +09:00
Naoki Takezoe
0e1d184715 Remove installed plugins from available plugin list 2014-06-29 12:39:27 +09:00
Naoki Takezoe
d8c27046f6 Merge branch 'master' into plugin-system
Conflicts:
	src/main/scala/servlet/AutoUpdateListener.scala
2014-06-29 04:20:58 +09:00
Naoki Takezoe
fd09058a7d (refs #310)Convert all CRLF to LF 2014-06-29 04:16:37 +09:00
Naoki Takezoe
1c99b57709 (refs #412)Fix repository lock 2014-06-29 03:29:03 +09:00
shimamoto
9ee739d102 (refs #341) Migrate slick session. 2014-06-27 08:48:58 +09:00
Tomofumi Tanaka
e2cde81b72 (refs #417) Correct wiki repository url 2014-06-25 23:40:41 +09:00
Tomofumi Tanaka
84a4b8fd92 (refs #415) Fix bug 2014-06-25 00:19:28 +09:00
shimamoto
d2c94909cb (refs #341) Migrate service package. 2014-06-24 02:40:40 +09:00
utensil
3683a5fb7d Show newsfeed of private repo to members of owner group 2014-06-22 09:59:59 +00:00
utensil
1223bf2fd8 Show newsfeed of private repo to its owner 2014-06-22 08:10:37 +00:00
Naoki Takezoe
a9bfe0dfab (refs #411)Move thirdparty JavaScript and CSS to vendors/ 2014-06-20 11:51:27 +09:00
Naoki Takezoe
9af81c7093 Merge pull request #410 from cranst0n/ant-deprecation
Remove deprecated Ant 'rename' task.
2014-06-20 09:25:43 +09:00
cranst0n
1e8a5c3cde Remove deprecated 'rename' task. 2014-06-19 13:21:24 -04:00
Naoki Takezoe
707ad866e1 (refs #408)Performance improvement for index page. 2014-06-20 01:42:42 +09:00
Naoki Takezoe
c3a944b40e (refs #405)Fix styles 2014-06-16 10:59:21 +09:00
Naoki Takezoe
ab80cb8f60 (refs #405)Fix styles 2014-06-16 10:39:27 +09:00
Naoki Takezoe
4f45e047d2 (refs #406)Fix pull request count in the dashboard 2014-06-16 01:40:51 +09:00
shimamoto
bbe455ac49 (refs #341) Migrate model package. 2014-06-16 01:29:56 +09:00
Naoki Takezoe
b5f173fa46 (refs #32)Display plugin's status 2014-06-16 01:20:42 +09:00
Naoki Takezoe
4bd6ef143a (refs #32)Clone or pull plugin repository before displaying the available plugins page 2014-06-15 18:07:34 +09:00
Naoki Takezoe
fd4a696303 (refs #32)Add version to plugin meta information 2014-06-15 17:39:42 +09:00
Naoki Takezoe
4af4c4e7c6 (refs #32)Add plugin install tab 2014-06-15 13:11:08 +09:00
Naoki Takezoe
3b2e42fd61 (refs #32)Making plugin administration pages 2014-06-14 23:29:37 +09:00
Naoki Takezoe
b07d0b028f (refs #32)Add deleting installed plugins 2014-06-14 14:32:40 +09:00
Naoki Takezoe
f3900ca8f9 (refs #32)Add plugin system initialization 2014-06-14 14:00:21 +09:00
Naoki Takezoe
62d43f120a (refs #32)Separate Plugin interface and implementation 2014-06-14 13:26:56 +09:00
Naoki Takezoe
c4f69fbd13 (refs #32)Switch JavaScript processor to Rhino from Nashorn because GitBucket should work on both of JDK7 and JDK8 2014-06-14 10:58:58 +09:00
Naoki Takezoe
fece20ff40 (refs #32)Implementing repository action processing 2014-06-11 01:45:51 +09:00
Naoki Takezoe
bbef4b22ca (refs #32)Add plugins page into the system admin tools 2014-06-10 10:44:36 +09:00
Naoki Takezoe
481a2d213f (refs #32)Improving plug-in API 2014-06-10 07:43:52 +09:00
Tomofumi Tanaka
8ed4075f1e Change word to clear milestone the same as GitHub 2014-06-10 00:40:09 +09:00
Tomofumi Tanaka
9bf82733d1 Make icon-remove-circle of dropdown menu unclickable 2014-06-10 00:24:30 +09:00
Tomofumi Tanaka
30d66f95bc Show number of conversations 2014-06-09 22:49:41 +09:00
Naoki Takezoe
378c2c39a8 (refs #32)Add JavaScript API 2014-06-08 00:37:01 +09:00
Naoki Takezoe
daf5fc434c Merge remote-tracking branch 'origin/plugin-system' into plugin-system 2014-06-07 18:53:31 +09:00
Naoki Takezoe
e5bf90ed26 (refs #32)Provide generic layout and context to custom actions 2014-06-07 18:53:07 +09:00
Tomofumi Tanaka
1bf3146220 (refs #396)Apply syntax highlight correctly when update comment 2014-06-06 22:22:51 +09:00
Naoki Takezoe
ddd51850f0 (refs #32)Add JavaScript Console 2014-06-06 17:20:48 +09:00
Naoki Takezoe
e14a0c3770 (refs #32)Enable menu icon which is injected by plug-in 2014-06-06 16:35:54 +09:00
Naoki Takezoe
b0b318ce30 (refs #32)Example of custom action extension 2014-06-05 21:22:16 +09:00
Naoki Takezoe
6f666ca49f Merge branch 'master' into plugin-system 2014-06-05 20:54:28 +09:00
Naoki Takezoe
0cb2116bdf (refs #32)First impression of the plugin system 2014-06-05 20:52:38 +09:00
Naoki Takezoe
280113497b Merge pull request #390 from sakapoko/navform
Fix nested tags.
2014-06-05 09:37:40 +09:00
Shuji Sakagami
5f6e318329 Fix nested tags. 2014-06-04 13:11:37 +09:00
takezoe
f8921b6f10 Add a badge which shows number of pages 2014-06-03 07:28:30 +09:00
Naoki Takezoe
31a08abff2 (refs #378)"Delete branch" button is displayed for only merged pull requests 2014-06-02 21:34:03 +09:00
Naoki Takezoe
0fa1e11c5a (refs #378)Closed but not merged pull requests should be re-openable 2014-06-02 21:28:20 +09:00
yjkony
e2c99a46be Merge commit Tag 2.0 'db5395ddbc4aef485415408720dd09cfc215b527' into add-features-to-ldapauth
Conflicts:
	src/main/twirl/account/edit.scala.html
2014-06-02 17:01:22 +09:00
Naoki Takezoe
1edff41690 Fix code style 2014-06-02 16:10:03 +09:00
Naoki Takezoe
6d6f529d40 (refs #380)Close stream certainly 2014-06-02 16:04:45 +09:00
Naoki Takezoe
e2fd7d9d8e Fix "New pull request" button style 2014-06-01 23:46:28 +09:00
Naoki Takezoe
61146687b3 Merge remote-tracking branch 'origin/master' 2014-06-01 22:58:24 +09:00
Naoki Takezoe
1d1f7fa581 (refs #382)Remove unnecessary comment 2014-06-01 22:58:07 +09:00
Tomofumi Tanaka
67da88fab5 Use "Conversation" instead of "Discussion"
Github uses "Conversation" now.
2014-06-01 22:45:10 +09:00
Naoki Takezoe
fb3ed70215 (refs #382)Exclude duplicated commits from applying issue comment 2014-06-01 21:47:45 +09:00
Naoki Takezoe
2fceeeee4e Merge pull request #386 from HairyFotr/patch-4
Small cleanup using static analysis
2014-05-31 17:26:23 +09:00
Naoki Takezoe
67102822e8 Update README.md 2014-05-31 13:17:21 +09:00
Naoki Takezoe
d00a0f1571 Update README.md 2014-05-31 13:16:53 +09:00
bati11
6175eb7c08 Merge branch 'master' into feature-tasklist
Conflicts:
	src/main/twirl/issues/commentform.scala.html
	src/main/twirl/issues/create.scala.html
	src/main/twirl/pulls/compare.scala.html
	src/main/twirl/wiki/edit.scala.html
2014-05-31 12:17:30 +09:00
Naoki Takezoe
db5395ddbc Update version number to 2.0. 2014-05-31 10:38:31 +09:00
HairyFotr
7698f12112 Small cleanup using static analysis 2014-05-31 00:57:03 +02:00
Naoki Takezoe
1e8224536b (refs #383)Disable "New Issue" button in the new issue creation page. 2014-05-29 01:55:10 +09:00
Naoki Takezoe
a846c77c7e (refs #346)Add group members as collaborator when transfer repository to the group. 2014-05-25 18:14:53 +09:00
Tomofumi Tanaka
29812f4a82 (refs #375)Show merge commit diffs correctly 2014-05-20 21:26:51 +09:00
takezoe
a863951d97 Show diff for files other than markdown by "Preview" button 2014-05-19 00:29:29 +09:00
takezoe
146be677ba Hide "Edit" button if target is not head of the branch. 2014-05-19 00:03:24 +09:00
takezoe
03b5f7feb8 Fix title in file editing. 2014-05-18 23:36:13 +09:00
Tomofumi Tanaka
6d54361a6d Fix style broken in Firefox 2014-05-18 23:28:28 +09:00
Tomofumi Tanaka
f440421ed1 Repository url protocol label should be change
Like repository url.
2014-05-18 23:05:48 +09:00
takezoe
e57464fc5e Merge branch 'master' of https://github.com/takezoe/gitbucket 2014-05-18 23:03:37 +09:00
takezoe
2a4b0f5ddb (refs #372)Use shift key instead if ctrl key to select region 2014-05-18 22:58:16 +09:00
Tomofumi Tanaka
bb66e2201f Fix width styles
* issue updation title and content
* issue new comment
* pull request new comment
2014-05-18 21:44:25 +09:00
takezoe
4dc60e887f (refs #373)Preview markdown in the text file editor. 2014-05-18 21:05:51 +09:00
takezoe
f6eb2e2dc8 (refs #230)Fix markdown preview styles 2014-05-18 16:04:38 +09:00
takezoe
9ecc10ab21 (refs #372)Select lines by clicking line number in blob view 2014-05-18 15:35:50 +09:00
Naoki Takezoe
d7037a43c6 bugfix 2014-05-16 17:06:57 +09:00
Naoki Takezoe
2471b8dfe0 bugfix 2014-05-16 14:31:00 +09:00
Tomofumi Tanaka
0430cb49f9 Fix typo 2014-05-13 23:53:23 +09:00
takezoe
7811926779 (refs #367)Redirect if forked repository already exists 2014-05-12 23:56:42 +09:00
takezoe
9bb66a4297 Fix broken layout in pull request detail page 2014-05-12 23:39:32 +09:00
takezoe
70772f0d74 (refs #364)Keep links which start with '#' 2014-05-11 13:13:16 +09:00
takezoe
728b00e4c3 Fix readme styles 2014-05-11 13:08:32 +09:00
takezoe
97008ef984 Merge branch 'ui-refreshing'
Conflicts:
	src/main/twirl/index.scala.html
2014-05-11 03:04:31 +09:00
takezoe
6b86406e94 Add hover icons for header links 2014-05-11 02:58:29 +09:00
takezoe
4252c364a4 Fix commit id style in file list 2014-05-11 02:57:49 +09:00
takezoe
4f4bc0321b Fix file list style in repository viewer 2014-05-11 02:55:43 +09:00
takezoe
6ecabe4588 Fix header style 2014-05-11 02:42:03 +09:00
takezoe
93fa8484c5 Fix header button size 2014-05-11 02:23:51 +09:00
takezoe
ff2e55e82c Fix h1-h6 styles 2014-05-11 02:01:46 +09:00
takezoe
259637ce3c Fix wiki styles 2014-05-11 01:38:12 +09:00
takezoe
743b9b759a Fix button style in blob page 2014-05-10 23:17:16 +09:00
takezoe
73ba0b348b Add branch switcher 2014-05-10 23:10:36 +09:00
takezoe
e93769cc81 Fix header and side-menu styles 2014-05-10 22:51:14 +09:00
Naoki Takezoe
68f9739eed Merge pull request #365 from selesy/announce_ssh
Update the features list in README.md
2014-05-10 21:54:22 +09:00
Steve Moyer
c3d25b7a71 Update the features list in README.md
The version 1.12 announces the addition of the SSH protocol for repository access, but the feature list still states "Public / Private Git repository (http access only)".
2014-05-10 08:06:57 -04:00
takezoe
aaa582ff1a Fix header style in wiki pages 2014-05-10 20:35:49 +09:00
takezoe
debc798aec Side-menu is completed! 2014-05-10 14:44:19 +09:00
takezoe
6042f0e1e0 Add active icons for side-menu 2014-05-08 07:46:32 +09:00
takezoe
e10d02f45c Apply mouse hover style 2014-05-08 07:30:35 +09:00
takezoe
aebf4ff728 Remove invalid char 2014-05-08 02:15:30 +09:00
Naoki Takezoe
1a2e89c9ed Disable bootstrap tooltip because icon link blinks. 2014-05-07 10:52:31 +09:00
Naoki Takezoe
e10e2748b9 Fix issue and pull request creation form styles 2014-05-07 10:37:51 +09:00
takezoe
f422936e34 Add <div class="container"> to pages outside of repository. 2014-05-06 21:32:40 +09:00
takezoe
4e87f21405 Revert style for fork count 2014-05-06 12:41:45 +09:00
takezoe
dc2d79b16c Remove unnecessary parts and styles. 2014-05-06 03:21:31 +09:00
takezoe
88a3100563 Fix tab style 2014-05-06 02:28:33 +09:00
takezoe
8d3433a0e7 Add icon for repository resttings 2014-05-06 02:17:41 +09:00
takezoe
0fe30e5629 Rename header.scala.html to menu.scala.html 2014-05-06 02:17:11 +09:00
takezoe
ea1e9037c4 Folding side-menu 2014-05-06 01:01:43 +09:00
takezoe
24feeb17be Add icons for new UI 2014-05-05 22:06:18 +09:00
takezoe
6a7fc55572 Global navigation moves to side menu. 2014-05-05 22:05:41 +09:00
takezoe
cf047a8cee Migrate: add extension to files which are attached to issue 2014-05-04 18:45:58 +09:00
takezoe
896420f8dc Disable AceEditor for non text files 2014-05-04 17:58:47 +09:00
bati11
ebb9d9329a Merge branch 'master' into feature-tasklist 2014-05-03 10:51:18 +09:00
Tomofumi Tanaka
619f72d929 Add link for image file
* Render image tag with link tag on issue and wiki
* Correct response content-type of attached image on issue
2014-05-03 00:56:36 +09:00
Tomofumi Tanaka
dc21e8388e Improve update pull request query
commitIdFrom and commitIdTo columns update by one query.
2014-05-02 19:55:49 +09:00
yjkony
8c35310cd6 Merge commit Tag 1.13 ('3e82534c78a72e17dd3b79e091521d75cb4d3855') into add-features-to-ldapauth
Conflicts:
	src/main/scala/service/AccountService.scala
	src/main/scala/util/LDAPUtil.scala
2014-05-01 11:56:56 +09:00
takezoe
642e8bbb7c Fix #358 2014-05-01 01:39:19 +09:00
Naoki Takezoe
3ee4143235 Move duplicated JavaScript and CSS for diff to common files 2014-04-30 10:55:42 +09:00
Naoki Takezoe
c136823170 Fix comment 2014-04-30 10:45:23 +09:00
Naoki Takezoe
92631fbfcf Fix JavaScript and CSS for attachment area 2014-04-30 10:28:46 +09:00
Naoki Takezoe
5a1b1a4485 Update README.md 2014-04-29 18:27:51 +09:00
Naoki Takezoe
3e82534c78 Update README.md 2014-04-29 17:40:53 +09:00
Tomofumi Tanaka
dd694d27b5 (refs #324)Update commitIdFrom when pullrequest branch is updated 2014-04-29 17:24:51 +09:00
takezoe
1900aefe32 Modify message in avatar uploader 2014-04-29 17:24:16 +09:00
takezoe
2fe6b8c1e7 Fix TestCase 2014-04-29 17:02:16 +09:00
takezoe
ecfaa0247a (refs #12)Fix styles 2014-04-29 16:42:40 +09:00
takezoe
9a0cc9e043 Fix baseURL to baseUrl 2014-04-29 15:52:24 +09:00
takezoe
b0360db105 Merge branch 'master' of https://github.com/takezoe/gitbucket 2014-04-29 15:44:58 +09:00
takezoe
0f9c95c15a baseUrl calculation is concentrated to SystemSettings 2014-04-29 15:43:41 +09:00
shimamoto
8efd1da7e6 Merge branch 'fileupload' 2014-04-29 14:42:25 +09:00
shimamoto
52ebba43d5 (refs #12) Modified the response status when the file does not exist. 2014-04-29 13:54:06 +09:00
shimamoto
790eee7443 (refs #12) Add acceptedFiles options. 2014-04-29 13:41:50 +09:00
shimamoto
9f325290e8 (refs #12) Modified from path to baseURL. 2014-04-29 11:53:13 +09:00
shimamoto
93bf0a9a47 (refs #12) Modified to use the helper of the attachment. 2014-04-29 04:21:48 +09:00
shimamoto
bdd0af21a9 (refs #12) Created the helper of the attachment function. 2014-04-29 04:19:40 +09:00
Naoki Takezoe
aae5fe387b Update README.md 2014-04-29 02:52:02 +09:00
takezoe
257c5aef51 Merge branch 'ace-editor' 2014-04-29 02:43:01 +09:00
takezoe
3cae337487 (refs #13)New file icon 2014-04-29 02:41:19 +09:00
takezoe
779df30ec8 Fix #355 2014-04-29 02:03:28 +09:00
Naoki Takezoe
5609507991 (refs #13)Fix link target of cancel button 2014-04-28 10:45:13 +09:00
Naoki Takezoe
1c24090c14 (refs #13)Fix real-time validation for filename and Add line wrap mode switcher 2014-04-28 10:39:32 +09:00
takezoe
7da2c650d2 (refs #13)Bug fix 2014-04-28 01:33:13 +09:00
shimamoto
27fa9df2ee (refs #12) Implemented the process of saving image. 2014-04-27 21:08:31 +09:00
shimamoto
63c4e12259 (refs #12) Change the Markdown notation of images. 2014-04-27 19:11:14 +09:00
shimamoto
1f66670819 (refs #12) Implemented the upload area in preview.hrml. 2014-04-27 16:59:54 +09:00
shimamoto
a7b4f8de8d (refs #12) Replaced the latest version of the dropzone. 2014-04-27 16:55:48 +09:00
takezoe
ad0d57fbf9 Merge remote-tracking branch 'origin/master' 2014-04-27 02:11:39 +09:00
takezoe
cfc594805b (refs #348)Upgrade MINA to 0.11.0 2014-04-27 02:11:10 +09:00
Naoki Takezoe
52461e673c Merge pull request #351 from ysuganuma/Fix308
Fix #308
2014-04-27 02:06:27 +09:00
takezoe
a97edb7ef5 (refs #13)File editing (add, edit and delete) in repository viewer is available. 2014-04-27 02:02:36 +09:00
Yasuhito Suganuma
7a1c872861 Fix #308 2014-04-25 23:49:17 +09:00
Naoki Takezoe
0e5591017a Refactoring 2014-04-25 10:38:34 +09:00
takezoe
a104157c9a (refs #13)Disable commit button if content is not modified. 2014-04-25 00:09:34 +09:00
Naoki Takezoe
ad244adbfa (refs #13)Commit from AceEditor is available. 2014-04-24 21:49:30 +09:00
takezoe
3721b328a6 (refs #13)Implementing file editing on the repository viewer 2014-04-24 07:42:00 +09:00
takezoe
dd688f48b7 (refs #13)Implementing file editing on the repository viewer 2014-04-24 02:19:29 +09:00
shimamoto
296a0b2124 Removed FileUploadControllerBase(in the middle of improving file
upload).
2014-04-23 02:02:19 +09:00
takezoe
b9cc46e5ef (refs #13)Trying to embed ace editor into repository viewer. 2014-04-22 07:24:50 +09:00
Naoki Takezoe
375211fc30 Remove unused code 2014-04-21 16:04:25 +09:00
Naoki Takezoe
b8b59f9dcd Fix TestCase 2014-04-18 11:10:12 +09:00
Naoki Takezoe
6760ff34ef Adjust avatar icon style 2014-04-18 10:49:48 +09:00
Naoki Takezoe
c5de7811c4 Display large icons at the user repository list page 2014-04-18 10:33:45 +09:00
takezoe
82ef5457b0 Merge branch 'master' of https://github.com/takezoe/gitbucket 2014-04-18 07:54:10 +09:00
takezoe
d558476cd2 (refs #327)Fix baseURL and host in Context 2014-04-18 07:53:09 +09:00
takezoe
644701d995 (refs #327)Add atom feed of the specified user 2014-04-18 07:23:52 +09:00
takezoe
1382d59206 (refs #327)Fix icon position for global recent activities feed 2014-04-18 07:11:49 +09:00
takezoe
b60e2c07c7 (refs #327)Move feed.scala.xml to helper package because it breaks compilation by overriding xml package 2014-04-18 07:04:31 +09:00
takezoe
86f0307633 Merge branch 'feedactivities' of https://github.com/kaakaa/gitbucket into kaakaa-feedactivities 2014-04-18 06:44:27 +09:00
Naoki Takezoe
1db891a771 (refs #345)Fix an error for users who are authenticated with mail address through LDAP in push over HTTP. 2014-04-15 19:26:09 +09:00
Naoki Takezoe
c9fa3291f5 Fix TestCase 2014-04-11 13:23:36 +09:00
Naoki Takezoe
e0f1658120 Use retro as default Gravator image 2014-04-11 12:12:24 +09:00
Naoki Takezoe
da105b7180 Merge remote-tracking branch 'origin/master' 2014-04-11 11:58:51 +09:00
Naoki Takezoe
9c4f7cc530 Fix message for empty repository 2014-04-11 11:57:16 +09:00
takezoe
d7eef8bd25 (refs #343)Add drop COMMIT_LOG table statement in migration for 1.13 2014-04-11 07:40:07 +09:00
Tomofumi Tanaka
7b7c0e1eee Fix getAllcommitIds bug in empty repository 2014-04-11 01:35:31 +09:00
Naoki Takezoe
2ae7798591 (refs #343)Apply fix to ssh pushing also 2014-04-10 17:55:58 +09:00
Naoki Takezoe
3f76453f34 (refs #343)Retrieve all commit id from Git repository in pre commit hook instead of COMMIT_LOG table 2014-04-10 17:42:25 +09:00
Naoki Takezoe
8fbbe7f31e (refs #337)Fix JavaScript not found problem in JBoss/WildFly 2014-04-10 11:25:39 +09:00
takezoe
92a43b4f99 Merge branch 'remove-asciidoc' 2014-04-10 07:44:02 +09:00
bati11
843722f82e Implement the feature "Task List" 2014-04-10 02:08:45 +09:00
bati11
ce79eaada8 Add escapeTaskList method, it escapse '- [] ' characters 2014-04-10 01:21:55 +09:00
Naoki Takezoe
c128086778 (refs #335)Revert account updating in LDAP authentication 2014-04-08 16:34:01 +09:00
Naoki Takezoe
cc4fb8bf79 (refs #335)Add Scaladoc 2014-04-08 16:24:53 +09:00
Naoki Takezoe
c3ac0f3d9f (refs #335)Fix account updating in LDAP authentication 2014-04-08 16:21:10 +09:00
Naoki Takezoe
dfa4816633 (refs #288)Once remove AsciiDoc support 2014-04-08 10:13:07 +09:00
Tomofumi Tanaka
06978a4fc4 (refs #340)Add public key validation 2014-04-07 22:56:13 +09:00
Tomofumi Tanaka
3a2ecf6896 Fix bug #340 2014-04-07 22:53:31 +09:00
Naoki Takezoe
b357d52ec5 (refs #335)Fix LDAP authentication 2014-04-07 21:08:13 +09:00
Naoki Takezoe
f8b6b1ebf8 Merge pull request #288 from lefou/asciidoctorj
Support AsciiDoc markup for all files + Plain text Readme's
2014-04-04 13:09:45 +09:00
takezoe
91bd9d1111 (refs #335)Oops, fix substring condition 2014-04-03 08:55:50 +09:00
takezoe
1ec825050d (refs #335)Use string before '@' of mail address if user name is mail address in LDAP authentication. 2014-04-03 08:47:42 +09:00
takezoe
a6a08d13e9 (refs #335)Use string before '@' of mail address if user name is mail address in LDAP authentication. 2014-04-03 06:54:56 +09:00
takezoe
9a47c4a990 Fix #334 2014-04-03 00:32:39 +09:00
Naoki Takezoe
5063294177 Merge pull request #332 from mslinn/patch-1
Update README.md
2014-04-02 09:36:00 +09:00
Mike Slinn
b14917e2c6 Update README.md 2014-03-31 22:43:29 -07:00
takezoe
c1bbec2a1c Use logger instead of println 2014-04-01 04:53:32 +09:00
takezoe
6227a4643a Fix #331 2014-04-01 04:47:08 +09:00
yjkony
00af52815d Merge commit '5317ac5e031a29438657952fb882532af296135b' (tag 1.12) into add-features-to-ldapauth 2014-03-31 12:37:23 +09:00
takezoe
5d3365a944 Fix #328 2014-03-31 00:39:30 +09:00
takezoe
84ac2974fb Skip issue id extraction if the repository has no issues 2014-03-30 20:52:39 +09:00
Tobias Roeser
c9a1515d1f Merge branch 'master' into asciidoctorj
Conflicts:
	src/main/scala/app/RepositoryViewerController.scala
2014-03-29 13:53:23 +01:00
takezoe
5317ac5e03 Update README.md 2014-03-29 16:24:06 +09:00
Naoki Takezoe
9df1467ddf Merge pull request #329 from andytait/master
README typo fix.
2014-03-28 14:13:15 +09:00
Andy Tait
df79bd4515 README typo fix. 2014-03-27 10:11:56 +00:00
Naoki Takezoe
cbb14f2ba8 Update README.md 2014-03-25 05:20:03 +09:00
takezoe
1fe649e70f (refs #326)SSH clone URL includes username for logged-in users and it's hidden for non logged-in users. 2014-03-25 05:11:29 +09:00
Naoki Takezoe
0d918add28 Merge pull request #320 from kunigaku/fix_247
Fix #247 servlet.CommitLogHook.onPostReceive takes minutes
2014-03-25 04:51:04 +09:00
Naoki Takezoe
3926c98338 Merge pull request #286 from maliayas/patch-2
Improve code view
2014-03-25 02:06:20 +09:00
kaakaa
3bff6a1949 Implement atom feeds 2014-03-24 23:06:37 +09:00
takezoe
ec0c964ceb Fix #325 2014-03-20 17:50:25 +09:00
Naoki Takezoe
b4fd90c6d3 Merge pull request #323 from r0n22/patch-1
Documentation Change for Large file Sizes
2014-03-20 10:59:11 +09:00
r0n22
7dfd63cfa2 Update README.md
Ran into this issue today.  Added to documentation to let other users know about it.
2014-03-19 14:21:30 -04:00
Naoki Takezoe
a562e5ca14 Merge pull request #319 from jmu/master
Trim LF of version file
2014-03-19 04:04:31 +09:00
Naoki Takezoe
2885eef4ab Merge pull request #321 from xuwei-k/sbt0.13
sbt 0.13
2014-03-19 04:03:15 +09:00
xuwei-k
087297d14c sbt 0.13 2014-03-19 00:48:57 +09:00
kunigaku
6e0fb95ac3 Fix #247 servlet.CommitLogHook.onPostReceive takes minutes
When pushing tags, command.getOldId value is
“0000000000000000000000000000000000000000”.
So, can not get commit logs.
2014-03-18 22:51:15 +09:00
takezoe
61e28146fb Fix TODO 2014-03-16 02:10:48 +09:00
takezoe
40d3f0ef9e (refs #313)Search files which have FileMode.EXECUTABLE_FILE mode not only REGULAR_FILE 2014-03-16 01:55:26 +09:00
takezoe
99db825114 (refs #313)Add files which have FileMode.EXECUTABLE_FILE mode into zip file 2014-03-16 01:53:14 +09:00
takezoe
7341b377fe Fix #314 2014-03-15 23:10:21 +09:00
takezoe
7f78a98de0 Display the first line of commit message on the file list 2014-03-15 18:03:52 +09:00
takezoe
a64207f0ec Focus error field after validation. 2014-03-15 16:37:43 +09:00
takezoe
d86f40e3a2 Small fix 2014-03-15 16:06:49 +09:00
Jiang
b74417f393 trim LF of version file 2014-03-15 10:14:41 +08:00
takezoe
f5883abf04 Use Context#settings instead of loadSystemSettings() 2014-03-15 04:07:31 +09:00
takezoe
02a367fd99 Add SystemSettings to Context properties 2014-03-15 01:24:34 +09:00
takezoe
4870533710 Upgrade scalatra-forms to 0.0.14 2014-03-15 01:04:28 +09:00
yjkony
9175cf5c71 Merge branch 'master' into add-features-to-ldapauth
Conflicts:
	src/main/twirl/account/edit.scala.html
2014-03-14 15:56:08 +09:00
takezoe
8170a1b01d (refs #115)Display finger print on the ssh key setting page 2014-03-14 11:39:40 +09:00
takezoe
d1c6c763e2 (refs #115)Add button to toggle http/ssh for wiki repository url box 2014-03-14 10:59:42 +09:00
takezoe
0c683f7243 (refs #115)SSH access to Wiki repository is available 2014-03-14 10:44:38 +09:00
takezoe
63de780527 Remove last '/' in base url 2014-03-14 03:21:35 +09:00
takezoe
c5ccbf2d1f (refs #115)Fix TestCase 2014-03-14 02:57:31 +09:00
takezoe
8777535431 (refs #115)Base URL is required if SSH access is enabled 2014-03-14 02:07:31 +09:00
takezoe
70192ce420 (refs #115)Add new method to get sshUrl to RepositoryInfo 2014-03-13 22:26:15 +09:00
yjkony
a74bbd3eeb Merge branch 'master' into add-features-to-ldapauth 2014-03-13 18:10:41 +09:00
takezoe
02d79cb16a (refs #115)Add url switcher to the repository url box 2014-03-13 16:54:11 +09:00
Tomofumi Tanaka
78ca9b3f1a (refs #115)Use system settings port number 2014-03-13 01:47:28 +09:00
Tomofumi Tanaka
017631e337 (refs #115)Add ShellFactory 2014-03-13 00:56:33 +09:00
Tomofumi Tanaka
f9078dff2c (refs #115)Remove debug code 2014-03-13 00:45:29 +09:00
takezoe
b66381d677 Merge branch 'master' into ssh-access
Conflicts:
	src/main/scala/servlet/GitRepositoryServlet.scala
2014-03-12 22:43:32 +09:00
takezoe
49bf88f7a7 (refs #312)Fix redirection for non git client 2014-03-12 22:39:12 +09:00
Tomofumi Tanaka
f93ceaa91d (refs #115)Fix typo 2014-03-12 01:01:12 +09:00
Tomofumi Tanaka
0fe122dc63 (refs #115)Parse owner and repository correctly 2014-03-12 00:57:03 +09:00
yjkony
4e2a3fdbd0 Change trigger of "Disalbe mail resolve is enalbed" from "When system settings check-box is ON" to "When mail attribute is empty". 2014-03-11 12:56:00 +09:00
takezoe
3d251fa8ad (refs #115)Disable ssh key setting page if ssh access is disabled 2014-03-11 02:37:02 +09:00
takezoe
af0b52448a (refs #115)Add repository permission checking 2014-03-11 02:30:11 +09:00
yjkony
8d200c72d3 Merge branch 'master' into add-features-to-ldapauth 2014-03-10 11:05:02 +09:00
takezoe
f78cdb637d (refs #115)Fix TestCase 2014-03-10 01:00:10 +09:00
takezoe
845f2d6faa (refs #115)Start and stop sshd at the system settings 2014-03-10 00:44:25 +09:00
takezoe
525edbab80 (refs #115)Add TODO 2014-03-09 02:27:46 +09:00
takezoe
c422b1c9a5 (refs #115)Small fix for views 2014-03-09 02:12:32 +09:00
takezoe
1043b13228 (refs #115)Small fix for views 2014-03-09 01:48:30 +09:00
takezoe
5e6c33df6c (refs #115)Fix account registration page and add public key deletion 2014-03-09 01:38:23 +09:00
takezoe
9541771703 (refs #115)Use registered public key for authentication 2014-03-09 01:08:04 +09:00
takezoe
f99d37cfad (refs #115)Adding SSH Key form is available 2014-03-09 00:57:00 +09:00
takezoe
0cfe31ccd9 (refs #115)Fix TestCase 2014-03-08 22:00:36 +09:00
takezoe
8fc1a5473b (refs #115)Add table and model to store public keys 2014-03-08 19:26:49 +09:00
takezoe
049b12b908 Merge branch 'master' into ssh-access
Conflicts:
	src/main/scala/servlet/GitRepositoryServlet.scala
2014-03-08 18:59:13 +09:00
takezoe
45f992b2bc (refs #115)ServletContext is passed to Command 2014-03-08 14:57:17 +09:00
takezoe
9e2c66c341 (refs #115)ServletContext is passed to Command 2014-03-08 14:48:58 +09:00
takezoe
2d0f59b6f2 (refs #307)Fix deletion problem for branches which contains '/' 2014-03-08 04:22:55 +09:00
takezoe
fbba29e810 (refs #303)Submodule support 2014-03-08 02:58:49 +09:00
Tomofumi Tanaka
07a108760c (refs #115)Use regex to create git command
And add GitCommandFactory Unit Test Spec
2014-03-06 23:36:01 +09:00
takezoe
b641bfb56a (refs #241)Modify AccountService#getGroupMembers() to returns list of GroupMember instead of Tuple2 2014-03-06 16:17:41 +09:00
takezoe
c65d80bc72 (refs #241)Fix error occurring when member name contains special characters. 2014-03-06 15:51:12 +09:00
takezoe
e0d266bf16 (refs #241)Allow group manager to use repository settings 2014-03-06 15:30:58 +09:00
takezoe
b62f7c5aee Fix TestCase 2014-03-06 14:27:49 +09:00
takezoe
c89f04b926 (refs #241)Fix group editing in the administration console 2014-03-06 12:17:25 +09:00
takezoe
ff8b4b4a88 Move newrepo.scala.html to account package 2014-03-06 11:39:41 +09:00
takezoe
07d63ae63a Add caret to new icon 2014-03-06 11:39:13 +09:00
takezoe
c0f5cb1641 Move group.scala.html to account package 2014-03-06 11:26:58 +09:00
takezoe
50d84835cb Merge CreateController to AccountController 2014-03-06 11:24:28 +09:00
takezoe
8cdf4ef618 Merge branch 'improve-group' 2014-03-06 11:06:19 +09:00
takezoe
eff3a7acb4 (refs #241)Fix group creation / edition form presentation 2014-03-06 11:05:47 +09:00
yjkony
18cd967a9c Modify wrong label of "Additional filter condition" label in system settings page 2014-03-06 11:03:04 +09:00
yjkony
328d6c1d17 Merge branch 'master' into add-features-to-ldapauth 2014-03-06 10:52:16 +09:00
takezoe
716eddac7b (refs #241)Checkbox for manager is replaced with toggle buttons 2014-03-06 02:46:40 +09:00
takezoe
9b15af3bb7 Update README.md for 1.11.1 release 2014-03-05 22:16:37 +09:00
takezoe
b732e0d55a Fix error when base url is configured. 2014-03-05 22:12:34 +09:00
takezoe
d92a1cee1c Replace Context#path with the base url if it's configured. 2014-03-05 19:18:50 +09:00
Tobias Roeser
10a40bfcaf Removed commented out code. 2014-03-05 09:28:13 +01:00
Tobias Roeser
af397ba150 Fix page-relative links, e.g. in TOC. 2014-03-05 09:12:03 +01:00
takezoe
c7a2ec8290 (refs #299)Fix redirection path in PullRequestsController 2014-03-05 16:23:27 +09:00
shimamoto
145c155ba5 Remove unnecessary import. 2014-03-05 14:40:52 +09:00
shimamoto
6f9ef32d96 (refs #292) Fix to limit issue result before joins issue labels. 2014-03-05 14:35:49 +09:00
takezoe
aa5b9dbbbd Remove unnecessary code 2014-03-05 04:23:44 +09:00
takezoe
f11be44c02 (refs #330)Return NotFound if specified file does not exist 2014-03-05 04:18:45 +09:00
Tobias Roeser
4276c8f23e Support relative links in asciidoc files. 2014-03-04 16:43:56 +01:00
Tobias Roeser
9e1352c8b1 Enabled rendering of renderable files in blob view. 2014-03-04 16:43:50 +01:00
Tomofumi Tanaka
d46589ad29 (refs #115)Ensure exit status 1 on GitCommand error 2014-03-04 23:19:58 +09:00
Tomofumi Tanaka
09b7e67c52 (refs #110)Correct authentication and CommitHook 2014-03-04 22:49:25 +09:00
Tomofumi Tanaka
79e1abe624 (refs #110)Update apache sshd version
For fix authentication partial bug
2014-03-04 22:42:32 +09:00
Tobias Roeser
3db3bf1b74 Rewrite relative links to reflect the base url of the repo. 2014-03-04 11:38:56 +01:00
yjkony
a335c31385 Revert unnecessary format changes. 2014-03-04 10:54:54 +09:00
takezoe
9bd1f0a492 (refs #241)Remove unnecessary code 2014-03-04 10:53:26 +09:00
takezoe
7a2c82461e (refs #241)Group management improvement is completed in user side 2014-03-04 10:52:37 +09:00
takezoe
21f7888f55 (refs #241)Add validation for create / edit group form. 2014-03-04 10:23:44 +09:00
yjkony
97349a9bb2 Merge branch 'master' into add-features-to-ldapauth 2014-03-04 10:16:24 +09:00
yjkony
ce3b6ed7c2 Revert line separator from LF to CRLF 2014-03-04 10:16:12 +09:00
takezoe
e3fd564efd (refs #241)Work for specifying group manager 2014-03-04 04:25:44 +09:00
takezoe
5cf96134d5 (refs #296)Fix redirection path generation again 2014-03-04 03:25:18 +09:00
takezoe
607c477e7d Add options for remote debugging 2014-03-04 02:39:18 +09:00
yjkony
5e0619b500 Sync upstream/maste to master and Merge branch 'master' into add-features-to-ldapauth
Conflicts:
	src/main/scala/app/IndexController.scala
	src/main/scala/service/SystemSettingsService.scala
	src/main/twirl/admin/system.scala.html
2014-03-03 15:46:38 +09:00
takezoe
17920e1195 (refs #198)Allow group editing by group members. 2014-03-03 01:45:00 +09:00
Naoki Takezoe
721454aa90 Merge pull request #294 from eiryu/work
fix typo
2014-03-03 01:21:59 +09:00
takezoe
d870896cfb Rename CreateRepositoryController to CreateController. 2014-03-03 01:21:22 +09:00
takezoe
270eb7cf1d (refs #198)Allow create group by normal users. 2014-03-03 01:17:52 +09:00
eiryu
527fd94145 fix typo 2014-03-03 00:28:48 +09:00
takezoe
04e4572088 Merge branch '1.11-update' 2014-03-03 00:04:18 +09:00
takezoe
0961eb5976 (refs #279)Fix redirect url generation. 2014-03-02 23:55:03 +09:00
Tobias Roeser
0311359922 Merge branch 'master' into asciidoctorj 2014-03-02 09:15:57 +01:00
takezoe
ec09adf03e Merge branch 'odz-closing-issues-via-commit-messages' 2014-03-02 04:49:53 +09:00
takezoe
b031103df8 (refs #218)Separate StringUtil#extractCloseId() to add unit test. 2014-03-02 04:49:14 +09:00
takezoe
7701521a2e Fix to use Exception#ignoring to ignore Exception. 2014-03-02 04:24:24 +09:00
takezoe
0c683d4f75 Merge branch 'closing-issues-via-commit-messages' of https://github.com/odz/gitbucket into odz-closing-issues-via-commit-messages 2014-03-02 04:02:45 +09:00
Naoki Takezoe
200d095034 Merge pull request #290 from lefou/compare-email-caseinsensitive
Compare email adresses case-insensitive.
2014-03-02 01:35:00 +09:00
takezoe
94576a876a (refs #280)Commit count limitation is changed to 10000 from 1000. 2014-03-02 01:23:30 +09:00
takezoe
0fa1922bb0 Small fix to pull request #289 2014-03-02 01:05:42 +09:00
Naoki Takezoe
c557905858 Merge pull request #289 from lefou/description-in-header
Show repository description below the name on repository page.
2014-03-02 00:56:07 +09:00
takezoe
31b21d74b1 Remove --https option. 2014-03-02 00:25:56 +09:00
takezoe
153244c390 Update README.mf for GitBucket 1.11 release 2014-03-01 15:26:10 +09:00
takezoe
e97b5c3c89 (refs #279)Remove --https option because it's possible to substitute in the base url configuration. 2014-03-01 15:24:55 +09:00
takezoe
374893a5ae Apply scala.util.control.Exception to exception handling. 2014-03-01 15:22:58 +09:00
takezoe
17f581f654 (refs #279)Override ScalatraBase#fullUrl() to apply the configured base url to redirection. 2014-03-01 15:19:42 +09:00
takezoe
590b431ec1 Add FlashMapSupport to ControllerBase. 2014-03-01 13:27:36 +09:00
takezoe
98266fe0e1 (refs #279)Fix webhook URL to use the configured base URL. 2014-03-01 13:05:42 +09:00
Tomofumi Tanaka
2e236e90ba (refs #110) Add CommitHook sample
WIP: Some important functions are not implement yet.
2014-03-01 02:09:24 +09:00
Tomofumi Tanaka
c5aee0810c (refs #115) Add PublicKeyAuthenticator sample impl
Auth TODOs

* Fetch user account pubkeys from DB
2014-03-01 01:00:00 +09:00
Tobias Roeser
f13d757976 Compare email adresses case-insensitive. 2014-02-28 16:28:40 +01:00
Tobias Roeser
7a0a62af2d Show repository description below the name on repository page. 2014-02-28 15:25:32 +01:00
Tomofumi Tanaka
ceab1d2fd2 (refs #115) Reorganize ssh sources 2014-02-28 22:41:01 +09:00
yjkony
639e7e0b3f Add features (additional filter condition / disable mail resolve) to LDAP authentication. 2014-02-28 21:32:42 +09:00
Tobias Roeser
89601305f6 Merge branch 'master' into asciidoctorj
Conflicts:
	src/main/twirl/repo/files.scala.html
2014-02-28 09:42:50 +01:00
Tobias Roeser
4600b5a3bf Enabled rendering of page document title. 2014-02-28 09:36:04 +01:00
Naoki Takezoe
b620307983 Merge pull request #287 from lefou/correct-readme-name
Show the correct name of the readme file
2014-02-27 15:37:55 +09:00
Tomofumi Tanaka
891ca70ade refs #115: Support git access via ssh (1st step)
EXPERIMENTAL

TODOs

* Authentication (PublicKey)
* Publickey management in profile view
* Commit hooks integration (WebHook too)
* Parse command correctly
* Make Configurable many options(enable/disable, port, etc...)
* ShellProcessFactory
* Test, test, test...
2014-02-27 02:05:58 +09:00
Tomofumi Tanaka
9ed2a50d26 Add SSH Service Listener 2014-02-27 02:02:37 +09:00
Tobias Roeser
cbf615d699 Support plain text readme files (with .txt or no extension). 2014-02-26 16:13:39 +01:00
Tobias Roeser
97b1a0090d Initial support for rendering asciidoc files. 2014-02-26 15:14:39 +01:00
Tobias Roeser
9078aa6d08 Added asciidoctorj dependency. 2014-02-26 13:53:50 +01:00
Tobias Roeser
8677146a8d Show the correct name of the readme file (instead of showing always README.md). 2014-02-26 12:12:49 +01:00
Tobias Roeser
2c14dfb781 Show the correct name of the readme file (instead of showing always README.md). 2014-02-26 12:09:14 +01:00
Ali Ayas
057c5f073c Improve diff view 2014-02-26 12:09:25 +02:00
Ali Ayas
e902da6595 Improve code view
Now it's more similar to GitHub
2014-02-26 11:40:15 +02:00
odz
8b5414c8f7 Merge branch 'master' into closing-issues-via-commit-messages
Conflicts:
	src/main/scala/app/PullRequestsController.scala
	src/main/scala/servlet/GitRepositoryServlet.scala
2014-02-25 00:07:25 +09:00
Tomofumi Tanaka
c86ece4dc0 Add mina ssh to dependencies 2014-02-24 22:45:45 +09:00
Naoki Takezoe
1f71619b6b Merge pull request #281 from maliayas/patch-1
Apply GitHub diff colors
2014-02-24 12:15:57 +09:00
Naoki Takezoe
5b34b9c795 Update README.md 2014-02-24 02:34:13 +09:00
Naoki Takezoe
99d15899f6 Update README.md 2014-02-24 02:32:19 +09:00
takezoe
c114a8b507 (refs #279)Fix TestCase. 2014-02-24 02:16:18 +09:00
takezoe
0dd37c2481 (refs #279)Fix redirect URL generation in authentication. 2014-02-24 02:14:10 +09:00
Ali Ayas
b5d7c96bba Apply GitHub diff colors
However, jsdifflib is still not a good approach. It requires dumping all the two text to the browser and do the work there. Instead, maybe the diff should be taken from the git itself and highlighting should be applied on that.

OTOH, word level diff would be good when applicable (like GitHub does).
2014-02-23 13:11:10 +02:00
takezoe
a76792ced4 (refs #279)Add configuration to specify the base URL. But still one problem has not been resolved. 2014-02-22 14:20:51 +09:00
takezoe
39091240ff (refs #60)Mentioned issue reference from other issue or issue comment. 2014-02-22 05:13:39 +09:00
takezoe
0ccb753892 (refs #187)Add large icons for repository. 2014-02-22 02:00:49 +09:00
takezoe
63dda84c8b Add Version 1.11 2014-02-21 12:05:15 +09:00
takezoe
7ba1f85d48 (refs #187)Repository icons are updated. 2014-02-21 11:39:02 +09:00
takezoe
bb9a23fe0f Fix TestCase. 2014-02-20 03:40:06 +09:00
takezoe
8536824d7e (refs #231)Fix anchor icon style and apply URL encoding to non-ascii chars in anchor name. 2014-02-19 03:40:39 +09:00
takezoe
78073babe4 (refs #231)Add anchor icon for headlines in Markdown. 2014-02-19 03:19:34 +09:00
Naoki Takezoe
521d15219c Merge pull request #272 from jeffreyolchovy/issue/231
Fix for #231: Generate headline anchor in Wiki pages
2014-02-11 04:53:37 +09:00
Naoki Takezoe
7469a3c349 Merge pull request #270 from rndstr/patch-1
Update new data directory in README.md
2014-02-10 07:50:40 +09:00
Jeffrey Olchovy
153a32e340 (refs #231)Generate anchors for headers in wiki markup 2014-02-08 13:59:27 -05:00
Roland Schilter
f155d4f150 Update new data directory in README.md 2014-02-08 13:11:13 +01:00
takezoe
d683dd2c38 (refs #268)Fix label filter bug when label contains whitespaces. 2014-02-08 07:04:18 +09:00
takezoe
7ebba741a8 (refs #232)Highlight lines which are specified by URL hash. 2014-02-08 06:52:50 +09:00
takezoe
d10f683098 (refs #259)Add underline to h1 and h2 in div.markdown-body. 2014-02-08 05:14:26 +09:00
takezoe
0270133ecf (refs #267)Improve H2 connectivity 2014-02-08 05:06:24 +09:00
takezoe
d7b479d97d (refs #265)Fix label pulldown position. 2014-02-08 05:04:06 +09:00
takezoe
4366c512fe (refs #265)Label editing for the pull request. 2014-02-05 03:07:37 +09:00
Naoki Takezoe
229a773ed2 Merge pull request #262 from shootaroo/jump-line
Add id for line number
2014-02-04 09:41:24 -08:00
takezoe
d882f20436 (refs #254)Change comment action to "delete_branch" from "delete". 2014-02-04 17:20:48 +09:00
takezoe
9d7235af20 (refs #254)Store removed branch name into CONTENT column of COMMENT table. 2014-02-04 17:06:54 +09:00
takezoe
c2eb53d154 (refs #224)Add delete branch button to pull request from same repository. 2014-02-04 09:04:25 +09:00
takezoe
7629e347df (refs #224)Record delete branch activity 2014-02-03 08:06:04 +09:00
takezoe
2764caae29 (refs #224)Add delete branch button 2014-02-03 08:00:43 +09:00
Naoki Takezoe
a87bd2a928 Merge pull request #264 from bati11/257-https-reverse-proxy
Fix #257, "org.scalatra.ForceHttps" set to true, if --https=true
2014-02-01 21:39:01 -08:00
bati11
202c920064 Fix #257, "org.scalatra.ForceHttps" set to true, if --https=true
ScalatraBase.redirect() use "org.scalatra.ForceHttps" in servlet
context init parameter when choice 'http' or 'https'.
2014-02-02 03:17:17 +09:00
Naoki Takezoe
a08316bba0 Update README.md 2014-02-01 17:15:50 +09:00
Naoki Takezoe
520e5ebb7a Update README.md 2014-02-01 17:14:41 +09:00
Naoki Takezoe
5d5a4cacb1 Update README.md 2014-02-01 17:13:18 +09:00
takezoe
b885a1a0d4 (refs #256)If account is already registered but disabled, authentication fails. 2014-02-01 17:05:33 +09:00
takezoe
1705bd3ae9 Merge remote-tracking branch 'origin/master' 2014-02-01 07:08:39 +09:00
takezoe
e87c69f989 (refs #251)Remove BOM from UTF-8 string. 2014-02-01 07:08:03 +09:00
takezoe
1c529eea3d Disable the post commit hook for Wiki repository. 2014-02-01 07:06:17 +09:00
takezoe
738b0cfe9a Add version 1.10. 2014-02-01 06:11:18 +09:00
takezoe
913561cb2a (refs #254)Remove AUTO_SERVER=TRUE for performance issue. 2014-01-30 20:42:53 +09:00
shootaroo
05a91565dc Add id for line number 2014-01-30 15:16:29 +09:00
takezoe
79827efe9b Merge branch 'master' of https://github.com/takezoe/gitbucket 2014-01-28 03:20:25 +09:00
takezoe
8722cd89fc Merge branch 'ldap-fullname' 2014-01-28 03:19:40 +09:00
Naoki Takezoe
52fcc4ad1e Update README.md 2014-01-26 08:43:34 +09:00
takezoe
59a096bfd6 (refs #250)Include repository name in download zip filename. 2014-01-25 05:25:17 +09:00
takezoe
5a1f541e13 (refs #245)Add full name attribute for LDAP authentication. 2014-01-25 05:07:32 +09:00
takezoe
94bd1c6a93 Merge branch 'rename-repository' 2014-01-18 18:05:45 +09:00
takezoe
5b1aef5e52 (refs #101, #102)Put Repository deletion and transfer ownership together to Danger Zone. 2014-01-18 07:06:48 +09:00
takezoe
89bfcdc44e (refs #102)Add validation and auto completion to the transfer user name field 2014-01-18 06:44:39 +09:00
takezoe
fba81138ea (refs #102)Experimental implementation of transfer repository ownership 2014-01-18 04:14:32 +09:00
takezoe
d50e07265e Add unique checking before rename repository. 2014-01-18 03:54:57 +09:00
takezoe
c92891538e Fix Cancel button position in the comment editing form. 2014-01-18 01:52:59 +09:00
takezoe
ccc1e9bc8b (refs #246)Fix issue editing 2014-01-18 01:52:34 +09:00
takezoe
f33b398428 (refs #102)Change for transfer repository owner. 2014-01-16 19:39:14 +09:00
takezoe
226a8af262 Use old home if it exists. 2014-01-15 05:25:18 +09:00
takezoe
ebcc5ab4b1 (refs #101)Update links in activity message also. 2014-01-15 04:35:38 +09:00
takezoe
10e16e8379 Use old home if it exists. 2014-01-15 01:28:15 +09:00
takezoe
df1f3d8a00 (refs #101)Modification to add rename repository name. 2014-01-13 02:09:05 +09:00
takezoe
5e2dfffe25 Use FileUtils#moveDirectory() instead of File#renameTo() 2014-01-12 17:59:02 +09:00
takezoe
897f2ea6dd Update data directory checking condition. 2014-01-12 16:47:23 +09:00
takezoe
3ff39ec578 Merge SignInController into IndexController 2014-01-12 16:16:09 +09:00
takezoe
3d852a535d (refs #244)Change the default data directory to HOME/.gitbucket 2014-01-12 15:41:01 +09:00
takezoe
6f6a61f31a Fix pattern match for webhook. 2014-01-04 17:23:18 +09:00
takezoe
10f54f5790 Ignore .settings directory. 2014-01-04 04:16:51 +09:00
takezoe
0e7280585a Fix refs commit log and web hook. 2014-01-04 04:11:41 +09:00
takezoe
1da7173f27 Merge branch 'master' of https://github.com/takezoe/gitbucket 2013-12-28 02:43:57 +09:00
takezoe
1cb1e68a01 Add Version 1.9 2013-12-28 02:43:15 +09:00
Naoki Takezoe
b59c8a5512 Update README.md 2013-12-28 01:49:04 +09:00
Naoki Takezoe
fe63ad0976 Merge pull request #242 from ssogabe/pull-request-messages
Fixed pull request messages.They are a bit different from GitHub
2013-12-21 19:57:50 -08:00
ssogabe
941cb7b851 Fixed pull request messages.They are a bit different from GitHub 2013-12-22 12:28:36 +09:00
takezoe
d1cf0d9fd7 (refs #238)Enable automatic mixed mode of H2. 2013-12-22 02:06:44 +09:00
takezoe
64c2bb4d6b (refs #237)Add link to gitbucket.plist 2013-12-21 05:30:48 +09:00
takezoe
24c9f5c17e (refs #237)Move gitbucket.plist to contrib. 2013-12-21 05:29:52 +09:00
Naoki Takezoe
d368e4e80d Merge pull request #237 from hanxue/osx
Add plist launcher for OS X and installation instructions
2013-12-20 12:26:32 -08:00
Naoki Takezoe
5c0ff84fc4 Merge pull request #233 from shootaroo/fix-style-readme
Fix style markdown table on README.md
2013-12-20 11:51:08 -08:00
Lee Hanxue
502a21b6b6 Add plist launcher for OS X and installation instructions 2013-12-19 17:59:32 +08:00
takezoe
0e9bf59c0f Remove some functions from ControlUtil. 2013-12-15 04:21:39 +09:00
shootaroo
108f9fccdd Fix style markdown table on README.md 2013-12-13 19:28:10 +09:00
takezoe
ac884bd7c3 (refs #196)Fire WebHook in merging pull request from Web GUI. 2013-12-12 04:23:43 +09:00
takezoe
a4cb5c991c Upgrade scalatra-forms to 0.0.11. 2013-12-12 03:22:37 +09:00
takezoe
68f1f55f37 (refs #223)Display GITBUCKET_HOME on the system settings. 2013-12-12 03:17:42 +09:00
Naoki Takezoe
1dc779d5e8 Merge pull request #228 from mcveat/compiler-warnings
Silenced compiler warnings
2013-12-11 09:53:19 -08:00
Naoki Takezoe
f781c7a08c Merge pull request #220 from maliayas/patch-1
Turn off autocomplete on "Add collaborator" form
2013-12-03 12:13:20 -08:00
Naoki Takezoe
a8511a9f39 Merge pull request #221 from drwlrsn/master
Fixes issue #216
2013-12-03 12:08:45 -08:00
Piotr Adamski
47714eec45 Silenced compiler warnings 2013-12-03 18:41:41 +01:00
takezoe
c46e9b2f4d (refs #204)Replace order of ScalatraListener and AutoUpdateListener 2013-12-03 02:16:00 +09:00
Drew Larson
26d579f13f Fixes issue #216
Added a div element to wrap the buttons so they are vertically aligned with each other. Also converted input and a elements to button elements as Bootstrap recommends: http://getbootstrap.com/css/#buttons-tags
2013-12-01 19:56:21 -06:00
Ali Ayas
6556d26742 Turn off autocomplete on "Add collaborator" form
You have already created js autocomplete for that input, so it is good to turn off the browser autocomplete. If there are more forms that have custom autocomplete, this change should be applied to them, too.
2013-12-01 23:55:34 +02:00
Naoki Takezoe
608dce2205 Update README.md 2013-11-30 21:20:12 +09:00
Naoki Takezoe
f86e50c723 Update README.md 2013-11-30 21:04:21 +09:00
Naoki Takezoe
b60fe33886 Update README.md 2013-11-30 20:26:31 +09:00
Naoki Takezoe
5210a143fd Update README.md 2013-11-30 20:22:00 +09:00
odz
dc78dc9b0d Close issues via commit messages 2013-11-30 18:57:19 +09:00
takezoe
6b11c1a180 (refs #204)Change the option name from --data to --gitbucket.home 2013-11-30 05:22:35 +09:00
takezoe
b3669f6d66 (refs #204)Add some way to configure data directory.
1) system property of JVM (e.g. -Dgitbucket.home=PATH_TO_DATADIR)
2) java -jar gitbucket.war --data=PATH_TO_DATADIR
3) Add context parameter "gitbucket.home" to web.xml
2013-11-30 05:18:15 +09:00
takezoe
bbff75e037 (refs #211)README.md detection is case insensitive and it also detect README.markdown as same as Github. 2013-11-30 04:22:19 +09:00
takezoe
7e10618ceb Merge branch 'pullreq-update-in-push' of https://github.com/odz/gitbucket into odz-pullreq-update-in-push
Conflicts:
	src/main/scala/app/PullRequestsController.scala
2013-11-30 02:35:21 +09:00
takezoe
7f4def6b83 Ignore Exception instead of TransportException 2013-11-25 18:21:59 +09:00
takezoe
5790d246c8 Ignore TransportException if the source branch had been removed. 2013-11-25 18:16:38 +09:00
Naoki Takezoe
19dee09c86 Merge pull request #203 from olivierdagenais/FixManualMergeUrls
Remove superfluous context.path
2013-11-22 12:27:43 -08:00
Naoki Takezoe
dfe2889912 Merge pull request #202 from odz/delete-repository-with-pullreq
Resolves Error when deleting repository which has PR.
2013-11-21 06:40:19 -08:00
odz
223ba791fe Fetch pull request from source repository after updating repository. 2013-11-20 23:59:26 +09:00
odz
0d49bbe7ac Resolves error when deleting repsository with PR. 2013-11-20 01:53:18 +09:00
Olivier Dagenais
8381e8122a Remove superfluous context.path
It was generating URLs that look like
http://server.example.com/gitbucket/gitbucket/git/user/repo.git (notice
the extra "/gitbucket"?) when the WAR was deployed in Tomcat.
2013-11-19 11:48:29 -05:00
Naoki Takezoe
f38924c7fe Merge pull request #190 from jtyr/master
Adding LDAP StartTLS support
2013-11-15 09:10:38 -08:00
takezoe
43152c9341 Upgrade scalatra-forms to 0.0.8. 2013-11-14 04:04:29 +09:00
takezoe
cf84e8b7cc (refs #173)Move BasicAuthenticationFilter to ScalatraBootstrap also. 2013-11-12 00:34:11 +09:00
takezoe
2b42e73530 (refs #173)Move BasicAuthenticationFilter to ScalatraBootstrap also. 2013-11-11 03:12:41 +09:00
Naoki Takezoe
60030959f2 Merge pull request #194 from jtyr/gravatar_https
Load Gravatar images always through HTTPS
2013-11-09 23:09:18 -08:00
Jiri Tyr
7174523ac5 Load Gravatar images always through HTTPS
This patch will force to load Gravatar images always through HTTPS which
will fix the problem with mixed content when accessing the page through
HTTPS.

The problem is that if an HTTPS page includes HTTP content, the HTTP
portion can be read or modified by attackers, even though the main page
is served over HTTPS.
2013-11-10 00:42:17 +00:00
takezoe
f573fef9eb (refs #173)Move TransactionFilter to ScalatraBootstrap from web.xml to support Tomcat 7.0.29 or before. 2013-11-10 05:24:38 +09:00
takezoe
b4250d8254 Merge remote-tracking branch 'origin/master' 2013-11-10 02:46:22 +09:00
takezoe
ac4d4de3c1 Fix redirect path encoding. 2013-11-10 02:45:54 +09:00
Naoki Takezoe
05e6d008fa Merge pull request #192 from xuwei-k/issue191
add HARDWRAPS option
2013-11-09 09:17:26 -08:00
takezoe
dd4abb2073 Upgrade to scalatra-forms 0.0.6. 2013-11-08 03:24:12 +09:00
Jiri Tyr
612aba1365 Use the system keystore by default
Default system keystore is in:
$JAVA_HOME/lib/security/jssecacerts
or in:
$JAVA_HOME/lib/security/cacerts

Custom keystore can be set either in /etc/sysconfig/gitbucket by
specifying the following option:
GITBUCKET_JVM_OPTS="-Djavax.net.ssl.trustStore=/path/to/your/cacerts"
or in Gitbucket's System Settings.
2013-11-07 16:57:40 +00:00
xuwei-k
94dce09570 add HARDWRAPS option 2013-11-07 17:27:18 +09:00
Jiri Tyr
cc241c5a7b Moving keystore definition into settings 2013-11-05 15:08:03 +00:00
shimamoto
13cf9d01f0 (refs #181) Fixed bug in charset. 2013-11-04 04:04:14 +09:00
takezoe
47453fec3f (refs #189)Fix Wiki page editing via redirecting from unexisting page. 2013-11-03 18:26:06 +09:00
takezoe
641d506559 (refs #177)Fix regular expressions for issue link conversion. 2013-11-03 18:06:58 +09:00
takezoe
3dec2b8159 Fix test case. 2013-11-03 15:12:01 +09:00
takezoe
a0bd969140 (refs #114)Add functionality to remove account by themselves. 2013-11-03 14:54:28 +09:00
takezoe
b30d42a37b (refs #114)Remove unnecessary database accessing. 2013-11-03 14:51:42 +09:00
takezoe
a03acc68e7 (refs #114)Disable link for disabled users. 2013-11-03 14:32:03 +09:00
takezoe
05296473d3 (refs #114)Bug fix 2013-11-03 04:53:41 +09:00
takezoe
2118f8c764 (refs #114)Add group deletion. 2013-11-03 01:37:23 +09:00
takezoe
e366af98b5 (refs #114)Add TODO to helpers#activityMessage() 2013-11-02 14:10:05 +09:00
takezoe
81e2ac44c3 (refs #114)Remove user related data when user is removed. 2013-11-02 14:01:07 +09:00
takezoe
07bb326c06 (refs #114)Add remove option to user management. 2013-11-02 13:44:47 +09:00
takezoe
bcc2c8cc2d Fix test case. 2013-11-02 05:20:24 +09:00
takezoe
2e0e17f1aa (refs #185)Add -Dsbt.log.noformat=true option 2013-11-02 05:12:36 +09:00
takezoe
c517b44e82 Upgrade to scalatra-forms 0.0.4 2013-11-02 05:07:09 +09:00
Jiri Tyr
f311339786 Adding LDAP StartTLS support
Some LDAP server do not allow authenticate with unencrypted password.
This patch is adding the StartTLS support which takes care of the
encryption.

In order to enable the StartTLS, go to "System Settings" and select the
"Enable StartTLS" in the Authentication section. Then make sure that you
add your LDAP certificate into the Java keystore:

$ keytool -import \
          -file /etc/pki/tls/certs/cacert.pem \
          -alias myName \
          -keystore /var/lib/gitbucket/keystore

You can list all keys from the keystore like this:

$ keytool -list -keystore /var/lib/gitbucket/keystore
2013-11-01 15:44:19 +00:00
takezoe
34853d0322 Fix test case. 2013-11-01 12:42:09 +09:00
takezoe
9c60b69c88 (refs #114)Add logical remove flag to ACCOUNT. 2013-11-01 03:39:59 +09:00
takezoe
4f10bccf84 (refs #114)Add logical remove flag to ACCOUNT. 2013-11-01 03:38:33 +09:00
takezoe
c7eaebf597 (refs #186)Show private repositories in the account page. 2013-11-01 03:25:06 +09:00
takezoe
60e1052d33 (refs #179)Fetch from the source repository before pull request is referred. 2013-11-01 03:12:56 +09:00
Tomofumi Tanaka
7e77c102b0 (refs #179) Merge branch 'improve-pullreq-performance' 2013-10-31 22:15:47 +09:00
takezoe
a452c582ab Fix compilation error. 2013-10-31 03:08:28 +09:00
takezoe
0d3adb074d Release ObjectInserter after adding commit. 2013-10-31 02:18:48 +09:00
takezoe
8ec4b52dda (refs #167)Add pusher info to WebHook 2013-10-31 02:07:54 +09:00
Tomofumi Tanaka
9265c68383 (refs #179) Refactor 2013-10-31 01:38:29 +09:00
Tomofumi Tanaka
4bd2d78ecb Merge remote-tracking branch 'master' into improve-pullreq-performance 2013-10-31 00:56:18 +09:00
Tomofumi Tanaka
e7aa766d0a (refs #179) Remove refs/pull/${issueId}/merge 2013-10-31 00:50:27 +09:00
Tomofumi Tanaka
7d8300b3ce (refs #179) Fetch from fork branch before merge 2013-10-31 00:30:18 +09:00
Tomofumi Tanaka
af8a1234ed (refs #179) Improve merge performance
* merge without tmp dir repository
* remove merging ops from mergeguid
2013-10-31 00:23:09 +09:00
takezoe
bd0ecd0a9d Improve repository creation to not use the working repository. 2013-10-30 14:52:55 +09:00
takezoe
35c8f02f90 (refs #180)Fix compilation error. 2013-10-30 13:22:54 +09:00
takezoe
f160952817 Remove unused import statement. 2013-10-30 13:20:13 +09:00
takezoe
9e5a302ab1 (refs #180)Fix a problem about multi-byte characters. 2013-10-30 13:19:25 +09:00
takezoe
a1dc19fa26 (refs #180)Remove Directory#getWikiWorkDir() 2013-10-30 11:39:55 +09:00
takezoe
e79ded934f Display selected page differences only. 2013-10-30 11:37:53 +09:00
takezoe
ef3e7d9286 (refs #180)Reverting from history without working repository is completed. 2013-10-30 11:13:10 +09:00
takezoe
68b25ddbb5 (refs #180)Implementing reverting from history without ApplyCommand. 2013-10-30 08:20:17 +09:00
Tomofumi Tanaka
f96040eade Improve checkConflict
* Delete temporary RefSpec after checkConflict
* mergeguide use checkconflictInPullRequest instead of checkconflict
2013-10-30 01:33:56 +09:00
takezoe
599a808054 Fix a link to the committer page. 2013-10-29 11:53:05 +09:00
takezoe
382c5c55ec Remove unused import statement. 2013-10-29 11:52:42 +09:00
takezoe
afb2306904 (refs #180)Fix saving and deleting Wiki page. 2013-10-29 11:39:38 +09:00
takezoe
2642da3be3 (refs #180)Eliminating the working repository cloning in Wiki. 2013-10-29 09:22:37 +09:00
Tomofumi Tanaka
dcbf283c9d Improve checkConflict 2013-10-29 01:24:06 +09:00
Naoki Takezoe
f38fa0132c Merge pull request #178 from jtyr/master
Version bump in spec file
2013-10-28 07:15:07 -07:00
Jiri Tyr
569053f7e0 Version bump in spec file 2013-10-28 11:18:58 +00:00
Naoki Takezoe
037a97ff3d Update README.md 2013-10-28 11:28:08 +09:00
Naoki Takezoe
6e169ab3c2 Update README.md 2013-10-26 01:46:39 +09:00
Naoki Takezoe
6ac27e89b3 Update README.md 2013-10-26 01:45:59 +09:00
takezoe
2235dab550 (refs #174)Fix commit hook for DELETE command. 2013-10-26 01:40:26 +09:00
Naoki Takezoe
7604c2172f Update README.md 2013-10-25 04:44:03 +09:00
takezoe
1e750f4b9d (refs #171)Fix link target of pull request number. 2013-10-25 04:28:58 +09:00
takezoe
d1f0d01ae8 Small fix for #170 2013-10-25 04:05:34 +09:00
Naoki Takezoe
167a0f28b2 Merge pull request #170 from jtyr/master
Allow to force HTTPS scheme
2013-10-24 12:00:25 -07:00
takezoe
06be5266fd Fix refs message. 2013-10-24 15:04:32 +09:00
takezoe
60e7165983 (refs #104)Zip file is exported from the bare repository directly. 2013-10-24 11:29:23 +09:00
takezoe
6dbfc12896 Merge branch 'master' of https://github.com/takezoe/gitbucket 2013-10-24 11:07:26 +09:00
takezoe
6d4b3e54d0 Fix style problem in Wiki. 2013-10-24 11:03:47 +09:00
takezoe
2968b92677 Add the commit link to refs comment. 2013-10-24 04:25:50 +09:00
takezoe
0d0bf4ad3f Don't add group account as a collaborator. 2013-10-24 02:08:14 +09:00
takezoe
53fa60b0f8 Exclude owner from assigned user list in the group repository. 2013-10-24 02:00:14 +09:00
Jiri Tyr
99517fa508 Fixing command line options in init.d script
Adding missing "--host" option and fixing the declaration of other
command line options.
2013-10-21 22:47:27 +01:00
Jiri Tyr
2e239d16d4 Refactorization of the https command line option
Renaming the flag variable and the Connector class.
2013-10-21 22:45:34 +01:00
Jiri Tyr
6de5babd5b Merge remote-tracking branch 'upstream/master' 2013-10-21 17:40:42 +01:00
takezoe
f3ad1a019d Small fix for #147 2013-10-22 01:09:22 +09:00
Naoki Takezoe
90ab882e8e Merge pull request #147 from xuwei-k/AccountServiceSpec
add AccountServiceSpec
2013-10-21 09:01:11 -07:00
Jiri Tyr
53269096a6 Allow to force HTTPS scheme
If the standalone GitBucket instance runs behind HTTPS proxy, the
repository URL always shows HTTP scheme dispute the fact that the
connection is HTTPS. This patch is adding a command line option which
allows to force the HTTPS scheme.
2013-10-21 14:32:53 +01:00
shimamoto
254509f243 Fix bug. 2013-10-21 22:32:46 +09:00
Naoki Takezoe
a697f186af Merge pull request #164 from smly/feature/bugfix-redirect-encode
Fix a problem to redirect wikipage named by multi-byte characters
2013-10-20 19:33:37 -07:00
takezoe
2316a80be9 (refs #163)Remove escaping for '.' 2013-10-21 10:53:36 +09:00
shimamoto
bbcb04b263 Fix bug. 2013-10-21 05:16:55 +09:00
shimamoto
7afe7fbb5f (refs #103) Add issue comment deletion. 2013-10-21 05:11:53 +09:00
takezoe
7c7da7379d (refs #163)Fixed 2013-10-19 18:48:50 +09:00
Kohei Ozaki
37358e9c8c Fix a problem to redirect wikipage named by multi-byte characters
In some specific case, redirect path (created from route params) is incorrect.
`redirectUrl` is expected to be encoded,
but scalatra decodes route params by rl.UrlCodingUtils via ScalatraBase.UriDecoder.
To avoid this problem, I add dirty workaround to encode redirect path.
2013-10-19 14:20:35 +09:00
Naoki Takezoe
41941df87a Update README.md 2013-10-19 13:58:04 +09:00
takezoe
bf2ed81eb1 (refs #163)Allow '.' in user name. 2013-10-19 12:56:55 +09:00
Naoki Takezoe
2d85d41e9c Merge pull request #161 from jtyr/master
System support for RedHat
2013-10-17 10:36:09 -07:00
Naoki Takezoe
e5e7b2484c Describe version of Servlet container 2013-10-18 00:59:16 +09:00
Jiri Tyr
6058552654 System support for RedHat
This commit is adding init.d script and sysconfig file which allows to
run GitBucket in the standalone mode. It also adds the spec file which
allows to build RPM package.
2013-10-17 01:39:47 +01:00
takezoe
f40c7ff4fa Fix testcase. 2013-10-16 04:47:43 +09:00
takezoe
da62c6181e (refs #33)Fix avatar icon and account link in the commits page for pull request. 2013-10-16 04:10:00 +09:00
takezoe
4d066738eb Merge branch 'master' into #33_match-by-email
Conflicts:
	src/main/scala/view/AvatarImageProvider.scala
2013-10-16 02:34:12 +09:00
Naoki Takezoe
cb12d03262 Merge pull request #143 from chris-vanvranken/master
retrieve LDAP emails as lowercase to avoid confusion
2013-10-15 10:28:05 -07:00
takezoe
9a6a2d9b78 (refs #33)Improve avatar image searching behavior. 2013-10-16 02:24:40 +09:00
takezoe
ff0af477cb Merge branch 'master' into #33_match-by-email
Conflicts:
	src/main/scala/view/helpers.scala
2013-10-16 01:51:44 +09:00
Chris Van Vranken
05adf9345f retrieve LDAP emails as lowercase to avoid confusing gravatar 2013-10-14 13:00:29 -04:00
Naoki Takezoe
ba70fdda48 Merge pull request #156 from xuwei-k/deprecated
fix deprecation warning
2013-10-14 06:42:35 -07:00
Naoki Takezoe
3885fcb2ec Merge pull request #157 from ssogabe/failonerror
Abort build process if sbt reports errors
2013-10-14 06:42:18 -07:00
ssogabe
99800a27f5 Abort build process if sbt reports errors 2013-10-14 15:01:30 +09:00
takezoe
107622942b (refs #127)Fix testcase. 2013-10-14 14:54:38 +09:00
xuwei-k
9794f14a65 fix deprecation warning. use HttpClientBuilder
https://github.com/apache/httpclient/blob/4.3/httpclient/src/main/java-deprecated/org/apache/http/impl/client/DefaultHttpClient.java#L113
2013-10-14 14:52:42 +09:00
xuwei-k
af759a815f add scalacOptions 2013-10-14 14:49:52 +09:00
takezoe
0e7078c479 (refs #151)Add unique checking to group creation. 2013-10-14 14:44:26 +09:00
Naoki Takezoe
83107c7974 Merge pull request #155 from HairyFotr/patch-2
Add plugin, Update versions
2013-10-13 22:13:16 -07:00
takezoe
ff9b2dbe93 (refs #127)Display full name at the account page. 2013-10-14 14:05:25 +09:00
takezoe
ebf4e5f2e9 Merge branch 'account-full-name' of https://github.com/robinst/gitbucket into robinst-account-full-name
Conflicts:
	src/main/scala/app/PullRequestsController.scala
2013-10-14 13:40:06 +09:00
Naoki Takezoe
21c30583e5 Merge pull request #153 from xuwei-k/number-format-exception
avoid NumberFormatException
2013-10-13 21:32:52 -07:00
takezoe
d6c9ace306 Merge branch 'master' of https://github.com/takezoe/gitbucket 2013-10-14 13:25:01 +09:00
takezoe
faf1252597 (refs #154)Remove comparison for original repository which does not exist. 2013-10-14 13:24:49 +09:00
HairyFotr
7b2ee25ea2 Add plugin, Update versions 2013-10-13 21:25:44 +02:00
xuwei-k
5a3207ae42 avoid NumberFormatException 2013-10-14 03:26:47 +09:00
Naoki Takezoe
3eab4955b9 Merge pull request #152 from xuwei-k/avoid-option-get
refactoring. avoid Option#get
2013-10-13 09:41:09 -07:00
xuwei-k
d772fc3ba2 refactoring. avoid Option#get 2013-10-14 01:18:31 +09:00
Naoki Takezoe
7de0a3fd70 Fix link to IIS installation wiki page. 2013-10-13 10:47:06 +09:00
Naoki Takezoe
eb8710a336 Merge pull request #144 from chris-vanvranken/patch-1
Add note that installation on windows server IIS is possible
2013-10-12 18:45:30 -07:00
Naoki Takezoe
25c55ecbd0 Merge pull request #150 from ssogabe/specs2
configure sbt to use the junitxml option with specs2
2013-10-12 18:31:13 -07:00
ssogabe
280df2cedd configure sbt to use the junitxml option with specs2 2013-10-13 09:38:52 +09:00
Naoki Takezoe
5ba9c86bee Merge pull request #149 from jparound30/activity_problem
Fix activity message problem (related to #120)
2013-10-12 08:14:13 -07:00
jparound30
faa6591d27 Fix activity message problem (related to #120) 2013-10-12 20:50:35 +09:00
takezoe
841d442f0d (refs #131)Don't create Dropzone if image has been registered. 2013-10-12 02:54:33 +09:00
xuwei-k
3351eabc4f add AccountServiceSpec 2013-10-11 13:08:58 +09:00
takezoe
006e1bc61e Merge branch 'master' of https://github.com/takezoe/gitbucket 2013-10-11 03:10:10 +09:00
takezoe
35d1b4ea37 (refs #142)Hide "Fork" button for repositories which have no commit. 2013-10-11 03:09:25 +09:00
Naoki Takezoe
b0c5069695 Merge pull request #145 from xuwei-k/refactoring
refactoring
2013-10-10 11:01:26 -07:00
xuwei-k
dae0d0ad4b use FileUtil.withTmpDir and FileUtil.using 2013-10-11 02:44:03 +09:00
xuwei-k
79e560b7bf add FileUtil.withTmpDir 2013-10-11 02:44:03 +09:00
takezoe
cf79ac1069 (refs #142)Fix NoSuchElementException for empty repository. 2013-10-11 02:42:07 +09:00
Chris Van Vranken
8aab7a16c4 Add note that installation on windows server IIS is possible 2013-10-10 12:41:10 -04:00
shimamoto
c16b89b0be (refs #99) Improved to configure the FROM field of the email. 2013-10-10 23:57:45 +09:00
takezoe
25bbc00ff3 Display repository search field on pull request pages. 2013-10-10 19:00:56 +09:00
Tomofumi Tanaka
e667b6c139 (refs #139) Add info log for debugging LDAP Auth 2013-10-10 00:57:46 +09:00
Naoki Takezoe
195364223f Merge pull request #140 from xuwei-k/remove-unnecessary-code
remove unnecessary code
2013-10-08 21:17:50 -07:00
xuwei-k
84ce2cac8d remove unnecessary code 2013-10-09 11:09:06 +09:00
takezoe
f3507cf465 (refs #117)Add build.properties to fix sbt version. 2013-10-09 10:44:52 +09:00
Tomofumi Tanaka
f74f2c47d3 (refs #120) URL encode tag name
URL encode tag name URL like branch name.
And rename encodeBranchName to encodeRefName.
2013-10-09 09:59:30 +09:00
takezoe
72b25591a5 Resolve length issue in Slick.
https://github.com/slick/slick/issues/170
2013-10-09 03:11:27 +09:00
Naoki Takezoe
fe23a5c6da Merge pull request #136 from talios/patch-1
Change link text to 'Older'
2013-10-07 18:21:30 -07:00
Mark Derricutt
49fbc5cb62 Change link text to 'Older'
Updates the link text for previous commits to read 'Older' and not 'Order'.
2013-10-08 11:34:02 +13:00
takezoe
5a19a307a9 Merge remote-tracking branch 'origin/master' 2013-10-08 03:08:06 +09:00
takezoe
c3ec52b391 (refs #128)Add javacOptions to specify 6 as compilation target. 2013-10-08 03:07:33 +09:00
Naoki Takezoe
f2d68be0a3 Merge pull request #134 from tanacasino/fix-link-use-urlencode
(refs #120) Use encodeBranchName to tab links
2013-10-07 09:16:52 -07:00
Tomofumi Tanaka
c1f98ac481 (refs #120) Use encodeBranchName to tab links 2013-10-07 23:55:27 +09:00
Naoki Takezoe
8287c84dc7 Merge pull request #126 from robinst/readme-padding
Add padding around repository readme content
2013-10-06 18:42:39 -07:00
Robin Stocker
13bff2963e Add full name to account and use it to create commits (#125)
The Git practice is to use the full name when creating commits, not a
user name. This commit fixes that by introducing a fullName field to
Account and using it when creating commits.

For migrating from earlier versions, the user name is used as an initial
value for the full name field.
2013-10-06 23:11:09 +02:00
Robin Stocker
035f3f9e02 Add padding around repository readme content
It looks quite bad without padding.
2013-10-06 22:12:03 +02:00
takezoe
65e6de5ba4 (refs #120)URL encode branch name except '/'. 2013-10-07 02:36:35 +09:00
takezoe
82ced9233a Remove debug code. 2013-10-06 23:23:04 +09:00
takezoe
e94411ebeb (refs #121)Create WebHookPayload only when web hook has been registered. 2013-10-06 23:22:29 +09:00
takezoe
b92b429ffa (refs #121)Configure maxIdleTime and soLingerTime. 2013-10-06 21:31:55 +09:00
takezoe
e457cfb212 Fix branch name. 2013-10-06 19:49:46 +09:00
takezoe
f1476c52e6 (refs #121)Optimize push performance for a lot of commit. 2013-10-06 18:31:09 +09:00
takezoe
332246aed6 Add testcase for AvatarImageProvider. 2013-10-06 17:40:10 +09:00
takezoe
1c5201dcf1 Merge branch 'buildXML' of https://github.com/ssogabe/gitbucket 2013-10-06 16:03:25 +09:00
takezoe
36880ace27 (refs #116)Add --host option to bind Jetty connector to the specified hostname. 2013-10-06 15:56:47 +09:00
Naoki Takezoe
0d55d6ef6b Merge pull request #122 from lucas-clemente/patch-1
Fix typo when assigning issues
2013-10-05 09:51:50 -07:00
Lucas Clemente
688bf645b4 Fix typo when assigning issues 2013-10-05 14:51:26 +02:00
takezoe
d5a14482a6 Fix for pull request #119 to take some part of design fix. 2013-10-05 12:58:26 +09:00
Jan-Henrik Bruhn
cc1e0030df Did a lot of design-optimizations
mainly added icons and set correct bootstrap classes for forms, but also used some new fonts, provided via google webfonts
2013-10-05 00:21:10 +02:00
takezoe
fcadcb34a2 Add testcase for Pagination. 2013-10-05 04:31:45 +09:00
takezoe
dd8f440be0 Add testcase. 2013-10-05 03:39:46 +09:00
takezoe
17bc422e7a (refs #84)Add jquery.elastic and apply to issue and comment textarea. 2013-10-04 09:32:32 +09:00
takezoe
380cdbcf75 Add FileUtil#getContentType(). 2013-10-04 04:17:30 +09:00
takezoe
f4f2bf34fc (refs #73)Add Wiki conflict detection and some fix. 2013-10-04 03:48:51 +09:00
Naoki Takezoe
ed713d80a9 Merge pull request #110 from tanacasino/fix/migration-skip
Fix bug dot not skip migration when first init
2013-10-03 09:41:13 -07:00
Tomofumi Tanaka
c39703c61c Fix bug dot not skip migration when first init 2013-10-03 21:58:44 +09:00
takezoe
537773f975 Add testcase example. 2013-10-03 14:02:54 +09:00
takezoe
f37eca7c61 (refs #109)Change link color for absent Wiki pages. 2013-10-03 13:49:09 +09:00
takezoe
40a52d5ad5 Clone Wiki working repository if it does not exist before reverting. 2013-10-03 13:48:31 +09:00
takezoe
d95bd20cbe Fix commit message for Wiki editing. 2013-10-03 11:44:15 +09:00
takezoe
70ca98d6a2 (refs #38)Add reverting wiki from history. 2013-10-03 03:42:38 +09:00
takezoe
cf7caf55da (refs #108)Add ZIP download button to the repository viewer tab. 2013-10-03 00:57:17 +09:00
takezoe
b74bff3b2e Add org.h2.Driver.load(). 2013-10-02 11:03:30 +09:00
takezoe
b2e4853976 Merge branch 'master' of https://github.com/takezoe/gitbucket 2013-10-02 03:31:28 +09:00
takezoe
aef3c5c121 (refs #106)Don't use DbStarter because GitBucket does not use tcp server and it also create connection for each transaction. 2013-10-02 03:31:01 +09:00
takezoe
4afbfcb016 (refs #106)Skip migration if the current version is illegal. 2013-10-02 02:48:49 +09:00
Naoki Takezoe
09f8cff4c9 Update README.md 2013-10-01 03:58:40 +09:00
Naoki Takezoe
9ecd162040 GitBucket 1.6 released. 2013-10-01 03:56:48 +09:00
Naoki Takezoe
8617f02b01 Update README.md 2013-10-01 03:56:19 +09:00
Naoki Takezoe
9d71d39917 Update README.md 2013-09-29 16:31:12 +09:00
takezoe
5430564065 (refs #105)Specify suitable Content-Type header for downloaded file. 2013-09-29 16:27:40 +09:00
shimamoto
54bc8c16d8 (refs #100) Fix bug that can't get ServletContext in the future block. 2013-09-28 21:03:55 +09:00
ssogabe
9c14ddda18 allow users to build a war on Linux 2013-09-27 22:13:23 +09:00
takezoe
0affdb6ad0 Bug fix caused by path splitting. 2013-09-27 14:33:27 +09:00
takezoe
532978522a Fix logback configuration. 2013-09-27 14:07:20 +09:00
Naoki Takezoe
05a9a0b45c Update README.md 2013-09-27 11:32:15 +09:00
Naoki Takezoe
24f8ad11ad Update README.md 2013-09-27 11:28:05 +09:00
Naoki Takezoe
ce943a0e6c Update README.md 2013-09-27 11:26:06 +09:00
takezoe
204c0cd0f8 (refs #96)Add --port and --prefix option. 2013-09-27 03:05:05 +09:00
takezoe
c213008f1c (refs #96)Small fix for build.xml. 2013-09-27 02:45:00 +09:00
takezoe
e6ad069509 (refs #96)Improve Jetty embedding process. 2013-09-27 02:43:22 +09:00
takezoe
38c7e3cdf8 (refs #96)Add build.xml which makes an executable war file. 2013-09-26 20:17:33 +09:00
takezoe
2be79f6590 Replace trace log with debug log. 2013-09-26 12:02:34 +09:00
takezoe
2f7125b6c0 Add trace log to WebHookService to check future execution. 2013-09-25 13:12:35 +09:00
takezoe
bb03a6fc9b (refs #94)The merge-guide is separated as HTML fragment and retrieve them by Ajax. 2013-09-23 13:25:06 +09:00
takezoe
7b774aee1a Use helper.html.dropdown() instead of the direct Bootstrap use. 2013-09-23 03:13:21 +09:00
takezoe
d53619c247 Small fix. 2013-09-23 02:14:31 +09:00
takezoe
d34118bdfd Define request attribute keys. 2013-09-23 02:03:10 +09:00
takezoe
c57bc487a3 Define session keys. 2013-09-23 00:51:57 +09:00
takezoe
296fc9a3df Improve session handling. 2013-09-23 00:18:38 +09:00
takezoe
fd8b5780f3 Use ControlUtil. 2013-09-22 19:28:14 +09:00
takezoe
602b6c635a Add RichRequest which extends HttpServletRequest. 2013-09-22 14:25:50 +09:00
takezoe
a79180699e Generalize the account completion field. 2013-09-22 13:35:05 +09:00
takezoe
e9901a8abf Generalize the commit list in the pull request. 2013-09-22 04:19:41 +09:00
takezoe
4e63d64c13 Generalize the file index of diff. 2013-09-22 04:05:51 +09:00
takezoe
4261b7adbe Use .strong instead of <strong>. 2013-09-22 03:27:18 +09:00
takezoe
f30c9f6171 Use ControlUtil. 2013-09-22 01:24:04 +09:00
takezoe
c00b704843 Use ControlUtil#using() to handle RevWalk. 2013-09-21 22:21:59 +09:00
takezoe
e89b2020a3 Use ControlUtil. 2013-09-21 22:13:15 +09:00
Naoki Takezoe
18ca3cbd80 Update README.md 2013-09-20 13:48:37 +09:00
takezoe
062d6cd066 Add ControlUtil. 2013-09-19 18:53:50 +09:00
takezoe
b4dd067d61 Introduce ControlUtil which provides control facilities such as using() or defining(). 2013-09-19 18:53:14 +09:00
takezoe
fd22e2911a Remove debug code. 2013-09-19 13:26:33 +09:00
takezoe
73d9e69e43 (refs #74)Small fix for test hook. 2013-09-19 02:40:07 +09:00
takezoe
7e4c29f4cf (refs #74)Remove an auxiliary constructor from case class because json4s can't serialize correctly if case class have that. 2013-09-19 00:47:46 +09:00
takezoe
32672262ef Merge branch 'master' of https://github.com/takezoe/gitbucket 2013-09-18 20:13:17 +09:00
takezoe
3c865ea20b (refs #74)Remove an unnecessary action and add TODO. 2013-09-18 20:12:47 +09:00
takezoe
d8698d02b7 (refs #74)Add "Test Hook" button. 2013-09-18 20:10:53 +09:00
shimamoto
d5b47e5adb Merge branch 'master' of https://github.com/takezoe/gitbucket.git 2013-09-18 19:22:25 +09:00
shimamoto
accb1cf2ab Refactoring. 2013-09-18 19:21:56 +09:00
takezoe
aa8da1b046 Fix TODO. 2013-09-18 15:28:59 +09:00
takezoe
c52ed32949 Fix whitespaces. 2013-09-18 15:27:25 +09:00
takezoe
ec6f4ff734 Fix typo. 2013-09-18 14:30:41 +09:00
takezoe
06b0dbf2e5 Remove TODO. 2013-09-18 14:24:15 +09:00
takezoe
98d24248c2 Add ExecutionContext for Future. 2013-09-14 18:34:05 +09:00
takezoe
cec1dc98a9 (refs #74)Web hook request is sent asynchronously. 2013-09-14 17:43:06 +09:00
takezoe
36115734bb (refs #74)Web hook is completed. 2013-09-14 17:14:37 +09:00
takezoe
c1eccd391d (refs #74)JSON conversion test. 2013-09-13 21:58:50 +09:00
takezoe
7fe86fcdb2 Merge branch 'webhook' of https://github.com/takezoe/gitbucket into webhook 2013-09-13 19:07:09 +09:00
takezoe
7f81ec52c1 (refs #74)JSON conversion test. 2013-09-13 19:06:45 +09:00
takezoe
7c269de39b (refs #74)Implementing conversion of web hook payload. 2013-09-13 03:24:34 +09:00
takezoe
aa9e34e992 (refs #74)Added case classes for payload of web hook. 2013-09-12 12:57:07 +09:00
takezoe
4d0ab514fb (refs #74)Remove web hook URL is available. 2013-09-12 08:41:26 +09:00
takezoe
9d526b32e0 Delete from WEB_HOOK before deleting repository. 2013-09-12 01:38:52 +09:00
takezoe
90a83c5c64 Merge branch 'master' into webhook 2013-09-12 01:34:31 +09:00
takezoe
e6e5cc67d5 Delete from PULL_REQUEST before deleting repository. 2013-09-11 18:05:34 +09:00
takezoe
4a6eb95474 Fix redirect path to the context root. 2013-09-11 03:53:50 +09:00
takezoe
7bce8cf3b6 Fix redirect path after sign in. 2013-09-11 03:40:55 +09:00
takezoe
4d1605ded2 (refs #74)Add web hook URL addition. 2013-09-06 02:32:51 +09:00
Naoki Takezoe
2bec2cfa93 Merge pull request #95 from kaakaa/fix-activity-bug
Fix a problem in making link to commit in activities
2013-09-05 09:24:32 -07:00
kaakaa
ff07872a3d Fix a problem in making link to commit in activities 2013-09-05 22:34:51 +09:00
takezoe
35733cd82e Merge branch 'master' into webhook 2013-09-05 14:53:58 +09:00
takezoe
38df990033 Merge branch 'master' into webhook 2013-09-05 02:35:58 +09:00
takezoe
940e2f4759 (refs #74)Add WEB_HOOK table. 2013-09-02 18:17:28 +09:00
takezoe
6fe65c76b1 (refs #74)Add the web hook configuration page. 2013-08-28 13:21:51 +09:00
takezoe
c0713eaeda (refs #33)Use RequestCache instead of AccountService directly. 2013-07-18 15:55:59 +09:00
takezoe
000afa1ed6 Merge branch 'master' into #33_match-by-email
Conflicts:
	src/main/scala/util/JGitUtil.scala
	src/main/scala/view/helpers.scala
	src/main/twirl/repo/blob.scala.html
	src/main/twirl/repo/commit.scala.html
	src/main/twirl/repo/commits.scala.html
	src/main/twirl/repo/files.scala.html
2013-07-18 15:49:56 +09:00
takezoe
828688ddd0 (refs #33)Match committer by mail address. 2013-07-12 04:27:20 +09:00
665 changed files with 288264 additions and 11363 deletions

1
.gitignore vendored
View File

@@ -14,6 +14,7 @@ project/plugins/project/
.classpath
.project
.cache
.settings
# IntelliJ specific
.idea/

3
.travis.yml Normal file
View File

@@ -0,0 +1,3 @@
language: scala
scala:
- 2.11.6

268
README.md
View File

@@ -1,12 +1,15 @@
GitBucket
GitBucket [![Gitter chat](https://badges.gitter.im/takezoe/gitbucket.png)](https://gitter.im/takezoe/gitbucket) [![Build Status](https://travis-ci.org/takezoe/gitbucket.svg?branch=master)](https://travis-ci.org/takezoe/gitbucket)
=========
GitBucket is the easily installable Github clone written with Scala.
GitBucket is the easily installable GitHub clone powered by Scala.
Features
--------
The current version of GitBucket provides a basic features below:
- Public / Private Git repository (http access only)
- Repository viewer (some advanced features are not implemented)
- Public / Private Git repository (http and ssh access)
- Repository viewer and online file editing
- Repository search (Code and Issues)
- Wiki
- Issues
@@ -15,11 +18,13 @@ The current version of GitBucket provides a basic features below:
- Activity timeline
- User management (for Administrators)
- Group (like Organization in Github)
- LDAP integration
- Gravatar support
Following features are not implemented, but we will make them in the future release!
- Network graph
- Statics
- Statistics
- Watch / Star
If you want to try the development version of GitBucket, see the documentation for developers at [Wiki](https://github.com/takezoe/gitbucket/wiki).
@@ -28,55 +33,240 @@ Installation
--------
1. Download latest **gitbucket.war** from [the release page](https://github.com/takezoe/gitbucket/releases).
2. Deploy it to the servlet container such as Tomcat or Jetty.
2. Deploy it to the Servlet 3.0 container such as Tomcat 7.x, Jetty 8.x, GlassFish 3.x or higher.
3. Access **http://[hostname]:[port]/gitbucket/** using your web browser.
If you are using Gitbucket behind a webserver please make sure you have increased the **client_max_body_size** (on nignx)
The default administrator account is **root** and password is **root**.
To upgrade GitBucket, only replace gitbucket.war.
or you can start GitBucket by `java -jar gitbucket.war` without servlet container. In this case, GitBucket URL is **http://[hostname]:8080/**. You can specify following options.
- --port=[NUMBER]
- --prefix=[CONTEXTPATH]
- --host=[HOSTNAME]
- --gitbucket.home=[DATA_DIR]
To upgrade GitBucket, only replace gitbucket.war. All GitBucket data is stored in HOME/.gitbucket. So if you want to back up GitBucket data, copy this directory to the other disk.
For Installation on Windows Server with IIS see [this wiki page](https://github.com/takezoe/gitbucket/wiki/Installation-on-IIS-and-Helicontech-Zoo)
### Mac OS X
#### Installing Via Homebrew
$ brew install gitbucket
==> Downloading https://github.com/takezoe/gitbucket/releases/download/1.10/gitbucket.war
######################################################################## 100.0%
==> Caveats
Note: When using launchctl the port will be 8080.
To have launchd start gitbucket at login:
ln -sfv /usr/local/opt/gitbucket/*.plist ~/Library/LaunchAgents
Then to load gitbucket now:
launchctl load ~/Library/LaunchAgents/homebrew.mxcl.gitbucket.plist
Or, if you don't want/need launchctl, you can just run:
java -jar /usr/local/opt/gitbucket/libexec/gitbucket.war
==> Summary
/usr/local/Cellar/gitbucket/1.10: 3 files, 42M, built in 11 seconds
#### Manual Installation
On OS X, copy the [gitbucket.plist](https://raw.github.com/takezoe/gitbucket/master/contrib/macosx/gitbucket.plist) file to `~/Library/LaunchAgents/`
Run the following commands in `Terminal` to
- start gitbucket: `launchctl load ~/Library/LaunchAgents/gitbucket.plist`
- stop gitbucket: `launchctl unload ~/Library/LaunchAgents/gitbucket.plist`
Release Notes
--------
### 3.1 - 28 Mar 2015
- Web APIs for Jenkins github pull-request builder
- Improved diff view
- Bump Scalatra to 2.3.1, sbt to 0.13.8
### 3.0 - 3 Mar 2015
- New plug-in system is available
- Connection pooling by c3p0
- New branch UI
- Compare between specified commit ids
### 2.8 - 1 Feb 2015
- New logo and icons
- New system setting options to control visibility
- Comment on side-by-side diff
- Information message on sign-in page
- Fork repository by group account
### 2.7 - 29 Dec 2014
- Comment for commit and diff
- Fix security issue in markdown rendering
- Some bug fix and improvements
### 2.6 - 24 Nov 2014
- Search box at issues and pull requests
- Information from administrator
- Pull request UI has been updated
- Move to TravisCI from Buildhive
- Some bug fix and improvements
### 2.5 - 4 Nov 2014
- New Dashboard
- Change datetime format
- Create branch from Web UI
- Task list in Markdown
- Some bug fix and improvements
### 2.4.1 - 6 Oct 2014
- Bug fix
### 2.4 - 6 Oct 2014
- New UI is applied to Issues and Pull requests
- Side-by-side diff is available
- Fix relative path problem in Markdown links and images
- Plugin System is disabled in default
- Some bug fix and improvements
### 2.3 - 1 Sep 2014
- Scala based plugin system
- Embedded Jetty war extraction directory moved to `GITBUCKET_HOME/tmp`
- Some bug fix and improvements
### 2.2.1 - 5 Aug 2014
- Bug fix
### 2.2 - 4 Aug 2014
- Plug-in system is available
- Move to Scala 2.11, Scalatra 2.3 and Slick 2.1
- tar.gz export for repository contents
- LDAP authentication improvement (mail address became optional)
- Show news feed of a private repository to members
- Some bug fix and improvements
### 2.1 - 6 Jul 2014
- Upgrade to Slick 2.0 from 1.9
- Base part of the plug-in system is merged
- Many bug fix and improvements
### 2.0 - 31 May 2014
- Modern Github UI
- Preview in AceEditor
- Select lines by clicking line number in blob view
### 1.13 - 29 Apr 2014
- Direct file editing in the repository viewer using AceEditor
- File attachment for issues
- Atom feed of user activity
- Fix some bugs
### 1.12 - 29 Mar 2014
- SSH repository access is available
- Allow users can create and management their groups
- Git submodule support
- Close issues via commit messages
- Show repository description below the name on repository page
- Fix presentation of the source viewer
- Upgrade to sbt 0.13
- Fix some bugs
### 1.11.1 - 06 Mar 2014
- Bug fix
### 1.11 - 01 Mar 2014
- Base URL for redirection, notification and repository URL box is configurable
- Remove ```--https``` option because it's possible to substitute in the base url
- Headline anchor is available for Markdown contents such as Wiki page
- Improve H2 connectivity
- Label is available for pull requests not only issues
- Delete branch button is added
- Repository icons are updated
- Select lines of source code by URL hash like `#L10` or `#L10-L15` in repository viewer
- Display reference to issue from others in comment list
- Fix some bugs
### 1.10 - 01 Feb 2014
- Rename repository
- Transfer repository owner
- Change default data directory to `HOME/.gitbucket` from `HOME/gitbucket` to avoid problem like #243, but if data directory already exist at HOME/gitbucket, it continues being used.
- Add LDAP display name attribute
- Response performance improvement
- Fix some bugs
### 1.9 - 28 Dec 2013
- Display GITBUCKET_HOME on the system settings page
- Fix some bugs
### 1.8 - 30 Nov 2013
- Add user and group deletion
- Improve pull request performance
- Pull request synchronization (when source repository is updated after pull request, it's applied to the pull request)
- LDAP StartTLS support
- Enable hard wrapping in Markdown
- Add new some options to specify the data directory. See details in [Wiki](https://github.com/takezoe/gitbucket/wiki/DirectoryStructure).
- Fix some bugs
### 1.7 - 26 Oct 2013
- Support working on Java6 in embedded Jetty mode
- Add `--host` option to bind specified host name in embedded Jetty mode
- Add `--https=true` option to force https scheme when using embedded Jetty mode at the back of https proxy
- Add full name as user property
- Change link color for absent Wiki pages
- Add ZIP download button to the repository viewer tab
- Improve ZIP exporting performance
- Expand issue and comment textarea for long text automatically
- Add conflict detection in Wiki
- Add reverting wiki page from history
- Match committer to user name by email address
- Mail notification sender is customizable
- Add link to changeset in refs comment for issues
- Fix some bugs
### 1.6 - 1 Oct 2013
- Web hook
- Performance improvement for pull request
- Executable war file
- Specify suitable Content-Type for downloaded files in the repository viewer
- Fix some bugs
### 1.5 - 4 Sep 2013
- Fork and pull request.
- LDAP authentication.
- Mail notification.
- Add an option to turn off the gravatar support.
- Add the branch tab in the repository viewer.
- Encoding auto detection for the file content in the repository viewer.
- Add favicon, header logo and icons for the timeline.
- Specify data directory via environment variable GITBUCKET_HOME.
- Fixed some bugs.
- Fork and pull request
- LDAP authentication
- Mail notification
- Add an option to turn off the gravatar support
- Add the branch tab in the repository viewer
- Encoding auto detection for the file content in the repository viewer
- Add favicon, header logo and icons for the timeline
- Specify data directory via environment variable GITBUCKET_HOME
- Fix some bugs
### 1.4 - 31 Jul 2013
- Group management.
- Repository search for code and issues.
- Display user related issues on the dashboard.
- Display participants avatar of issues on the issue page.
- Performance improvement for repository viewer.
- Alert by milestone due date.
- H2 database administration console.
- Fixed some bugs.
- Group management
- Repository search for code and issues
- Display user related issues on the dashboard
- Display participants avatar of issues on the issue page
- Performance improvement for repository viewer
- Alert by milestone due date
- H2 database administration console
- Fix some bugs
### 1.3 - 18 Jul 2013
- Batch updating for issues.
- Display assigned user on issue list.
- User icon and Gravatar support.
- Convert @xxxx to link to the account page.
- Add copy to clipboard button for git clone URL.
- Allows multi-byte characters as wiki page name.
- Allows to create the empty repository.
- Fixed some bugs.
- Batch updating for issues
- Display assigned user on issue list
- User icon and Gravatar support
- Convert @xxxx to link to the account page
- Add copy to clipboard button for git clone URL
- Allow multi-byte characters as wiki page name
- Allow to create the empty repository
- Fix some bugs
### 1.2 - 09 Jul 2013
- Added activity timeline.
- Bugfix for Git 1.8.1.5 or later.
- Allows multi-byte characters as label.
- Fixed some bugs.
- Add activity timeline
- Bugfix for Git 1.8.1.5 or later
- Allow multi-byte characters as label
- Fix some bugs
### 1.1 - 05 Jul 2013
- Fixed some bugs.
- Upgrade to JGit 3.0.
- Fix some bugs
- Upgrade to JGit 3.0
### 1.0 - 04 Jul 2013
- This is a first public release.
- This is a first public release

61
build.xml Normal file
View File

@@ -0,0 +1,61 @@
<?xml version="1.0" encoding="UTF-8" ?>
<project name="gitbucket" default="all" basedir=".">
<property name="target.dir" value="target"/>
<property name="embed.classes.dir" value="${target.dir}/embed-classes"/>
<property name="jetty.dir" value="embed-jetty"/>
<property name="scala.version" value="2.11"/>
<property name="gitbucket.version" value="3.0.0"/>
<property name="jetty.version" value="8.1.16.v20140903"/>
<property name="servlet.version" value="3.0.0.v201112011016"/>
<condition property="sbt.exec" value="sbt.bat" else="sbt.sh">
<os family="windows" />
</condition>
<target name="clean">
<delete dir="${embed.classes.dir}"/>
<delete file="${target.dir}/scala-${scala.version}/gitbucket.war"/>
</target>
<target name="war" depends="clean">
<exec executable="${sbt.exec}" resolveexecutable="true" failonerror="true">
<arg line="clean compile test package" />
</exec>
</target>
<target name="embed" depends="war">
<mkdir dir="${embed.classes.dir}"/>
<unzip dest="${embed.classes.dir}" src="${jetty.dir}/javax.servlet-${servlet.version}.jar" />
<unzip dest="${embed.classes.dir}" src="${jetty.dir}/jetty-continuation-${jetty.version}.jar" />
<unzip dest="${embed.classes.dir}" src="${jetty.dir}/jetty-http-${jetty.version}.jar" />
<unzip dest="${embed.classes.dir}" src="${jetty.dir}/jetty-io-${jetty.version}.jar" />
<unzip dest="${embed.classes.dir}" src="${jetty.dir}/jetty-security-${jetty.version}.jar" />
<unzip dest="${embed.classes.dir}" src="${jetty.dir}/jetty-server-${jetty.version}.jar" />
<unzip dest="${embed.classes.dir}" src="${jetty.dir}/jetty-servlet-${jetty.version}.jar" />
<unzip dest="${embed.classes.dir}" src="${jetty.dir}/jetty-util-${jetty.version}.jar" />
<unzip dest="${embed.classes.dir}" src="${jetty.dir}/jetty-webapp-${jetty.version}.jar" />
<unzip dest="${embed.classes.dir}" src="${jetty.dir}/jetty-xml-${jetty.version}.jar" />
<zip destfile="${target.dir}/scala-${scala.version}/gitbucket_${scala.version}-${gitbucket.version}.war"
basedir="${embed.classes.dir}"
update = "true"
includes="javax/**,org/**"/>
<zip destfile="${target.dir}/scala-${scala.version}/gitbucket_${scala.version}-${gitbucket.version}.war"
basedir="${target.dir}/scala-${scala.version}/classes"
update = "true"
includes="JettyLauncher.class,HttpsSupportConnector.class"/>
</target>
<target name="rename" depends="embed">
<move file="${target.dir}/scala-${scala.version}/gitbucket_${scala.version}-${gitbucket.version}.war"
tofile="${target.dir}/scala-${scala.version}/gitbucket.war"/>
</target>
<target name="all" depends="rename">
</target>
</project>

13
contrib/README.md Normal file
View File

@@ -0,0 +1,13 @@
# Contrib Notes #
The configuration script adapts according to the OS.
The `linux` directory contains scripts for Ubuntu and RedHat.
The Mac scripts have been folded in as well.
Common scripts are in this directory.
This version of scripts has so far only been tested on Ubuntu and Mac. Someone else will have to test on RedHat.
To run:
1. Edit `gitbucket.conf` to suit.
2. Type: `install`

62
contrib/gitbucket.conf Normal file
View File

@@ -0,0 +1,62 @@
# Configuration section is below. Ignore this part
function isUbuntu {
if [ -f /etc/lsb-release ]; then
grep -i ubuntu /etc/lsb-release | head -n 1 | cut -d \ -f 1 | cut -d = -f 2
fi
}
function isRedHat {
if [ -d "/etc/rc.d/init.d" ]; then echo yes; fi
}
function isMac {
if [[ "$(uname -a | cut -d \ -f 1 )" == "Darwin" ]]; then echo yes; fi
}
#
# Configuration section start
#
# Bind host
GITBUCKET_HOST=0.0.0.0
# Other Java option
GITBUCKET_JVM_OPTS=-Dmail.smtp.starttls.enable=true
# Data directory, holds repositories
GITBUCKET_HOME=/var/lib/gitbucket
GITBUCKET_LOG_DIR=/var/log/gitbucket
# Server port
GITBUCKET_PORT=8080
# URL prefix for the GitBucket page (http://<host>:<port>/<prefix>/)
GITBUCKET_PREFIX=
# Directory where GitBucket is installed
# Configuration is stored here:
GITBUCKET_DIR=/usr/share/gitbucket
GITBUCKET_WAR_DIR=$GITBUCKET_DIR/lib
# Path to the WAR file
GITBUCKET_WAR_FILE=$GITBUCKET_WAR_DIR/gitbucket.war
# GitBucket version to fetch when installing
GITBUCKET_VERSION=2.1
#
# End of configuration section. Ignore this part
#
if [ `isUbuntu` ]; then
GITBUCKET_SERVICE=/etc/init.d/gitbucket
elif [ `isRedHat` ]; then
GITBUCKET_SERVICE=/etc/rc.d/init.d
elif [ `isMac` ]; then
GITBUCKET_SERVICE=/Library/StartupItems/GitBucket/GitBucket
else
echo "Don't know how to install onto this OS"
exit -2
fi

138
contrib/gitbucket.init Normal file
View File

@@ -0,0 +1,138 @@
#!/bin/bash
#
# RedHat: /etc/rc.d/init.d/gitbucket
# Ubuntu: /etc/init.d/gitbucket
# Mac OS/X: /Library/StartupItems/GitBucket
#
# Starts the GitBucket server
#
# chkconfig: 345 60 40
# description: Run GitBucket server
# processname: java
set -e
[ -f /etc/rc.d/init.d/functions ] && source /etc/rc.d/init.d/functions # RedHat
[ -f /etc/rc.common ] && source /etc/rc.common # Mac OS/X
# Default values
GITBUCKET_HOME=/var/lib/gitbucket
GITBUCKET_WAR_FILE=/usr/share/gitbucket/lib/gitbucket.war
# Pull in cq settings
[ -f /etc/sysconfig/gitbucket ] && source /etc/sysconfig/gitbucket # RedHat
[ -f gitbucket.conf ] && source gitbucket.conf # For all systems
# Location of the log and PID file
LOG_FILE=$GITBUCKET_LOG_DIR/run.log
PID_FILE=/var/run/gitbucket.pid
RED='\033[1m\E[37;41m'
GREEN='\033[1m\E[37;42m'
OFF='\E[0m'
if [ -z "$(which success)" ]; then
function success {
printf "%b\n" "$GREEN $* $OFF"
}
fi
if [ -z "$(which failure)" ]; then
function failure {
printf "%b\n" "$RED $* $OFF"
}
fi
RETVAL=0
start() {
echo -n $"Starting GitBucket server: "
START_OPTS=
if [ $GITBUCKET_PORT ]; then
START_OPTS="${START_OPTS} --port=${GITBUCKET_PORT}"
fi
if [ $GITBUCKET_PREFIX ]; then
START_OPTS="${START_OPTS} --prefix=${GITBUCKET_PREFIX}"
fi
if [ $GITBUCKET_HOST ]; then
START_OPTS="${START_OPTS} --host=${GITBUCKET_HOST}"
fi
GITBUCKET_HOME="${GITBUCKET_HOME}" java $GITBUCKET_JVM_OPTS -jar $GITBUCKET_WAR_FILE $START_OPTS >>$LOG_FILE 2>&1 &
RETVAL=$?
echo $! > $PID_FILE
if [ $RETVAL -eq 0 ] ; then
success "Success"
else
failure "Exit code $RETVAL"
fi
echo
return $RETVAL
}
stop() {
echo -n $"Stopping GitBucket server: "
# Run the Java process
kill $(cat $PID_FILE 2>/dev/null) >>$LOG_FILE 2>&1
RETVAL=$?
if [ $RETVAL -eq 0 ] ; then
rm -f $PID_FILE
success "GitBucket stopping"
else
failure "GitBucket stopping"
fi
echo
return $RETVAL
}
restart() {
stop
start
}
## MacOS proxies for System V service hooks:
StartService() {
start
}
StopService() {
stop
}
RestartService() {
restart
}
if [ `isMac` ]; then
RunService "$1"
else
case "$1" in
start)
start
;;
stop)
stop
;;
restart)
restart
;;
status)
status -p $PID_FILE java
RETVAL=$?
;;
*)
echo $"Usage: $0 [start|stop|restart|status]"
RETVAL=2
esac
exit $RETVAL
fi

69
contrib/install Executable file
View File

@@ -0,0 +1,69 @@
#!/bin/bash
# Only tested on Ubuntu 14.04
# Uses information stored in GitBucket git repo on GitHub as defaults.
# Edit gitbucket.conf before running this
set -e
GITBUCKET_VERSION=2.1
if [ ! -f gitbucket.conf ]; then
echo "gitbucket.conf not found, aborting"
exit -3
fi
source gitbucket.conf
function createDir {
if [ ! -d "$1" ]; then
echo "Making $1 directory."
sudo mkdir -p "$1"
fi
}
if [ "$(which iptables)" ]; then
echo "Opening port $GITBUCKET_PORT in firewall."
sudo iptables -A INPUT -p tcp --dport $GITBUCKET_PORT -j ACCEPT
echo "Please use iptables-persistent:"
echo " sudo apt-get install iptables-persistent"
echo "After installed, you can save/reload iptables rules anytime:"
echo " sudo /etc/init.d/iptables-persistent save"
echo " sudo /etc/init.d/iptables-persistent reload"
fi
createDir "$GITBUCKET_HOME"
createDir "$GITBUCKET_WAR_DIR"
createDir "$GITBUCKET_DIR"
createDir "$GITBUCKET_LOG_DIR"
echo "Fetching GitBucket v$GITBUCKET_VERSION and saving as $GITBUCKET_WAR_FILE"
sudo wget -qO "$GITBUCKET_WAR_FILE" https://github.com/takezoe/gitbucket/releases/download/$GITBUCKET_VERSION/gitbucket.war
sudo rm -f "$GITBUCKET_LOG_DIR/run.log"
echo "Copying gitbucket.conf to $GITBUCKET_DIR"
sudo cp gitbucket.conf $GITBUCKET_DIR
if [ `isUbuntu` ] || [ `isRedHat` ]; then
sudo cp gitbucket.init "$GITBUCKET_SERVICE"
# Install gitbucket as a service that starts when system boots
sudo chown root:root $GITBUCKET_SERVICE
sudo chmod 755 $GITBUCKET_SERVICE
sudo update-rc.d "$(basename $GITBUCKET_SERVICE)" defaults 98 02
echo "Starting GitBucket service"
sudo $GITBUCKET_SERVICE start
elif [ `isMac` ]; then
sudo macosx/makePlist
echo "Starting GitBucket service"
sudo cp gitbucket.conf "$GITBUCKET_SERVICE"
sudo cp gitbucket.init "$GITBUCKET_SERVICE"
sudo chmod a+x "$GITBUCKET_SERVICE"
sudo "$GITBUCKET_SERVICE" start
else
echo "Don't know how to install this OS"
exit -2
fi
if [ $? != 0 ]; then
less "$GITBUCKET_LOG_DIR/run.log"
fi

View File

@@ -0,0 +1,15 @@
# Contrib Notes #
RPM spec file and init script for Red Hat Enterprise Linux 6.x.
To create RPM:
1. Edit `../../gitbucket.conf` to suit.
2. Edit `gitbucket.init` to suit.
3. Edit `gitbucket.spec` to suit.
4. Place `gitbucket.spec` to rpm/SPECS/.
5. Place `gitbucket.init` and `gitbucket.war` to rpm/SOURCES/.
6. Execute `rpmbuild -ba rpm/SPECS/gitbucket.spec`
This rpm runs gitbucket not as root user but as gitbucket user.
This rpm creates user and group named `gitbucket` at installation.
This rpm make chkconfig of gitbucket to be on.

View File

@@ -0,0 +1,108 @@
#!/bin/bash
#
# RedHat: /etc/rc.d/init.d/gitbucket
#
# Starts the GitBucket server
#
# chkconfig: 345 60 40
# description: Run GitBucket server
# processname: java
[ -f /etc/rc.d/init.d/functions ] && source /etc/rc.d/init.d/functions # RedHat
# Default values
GITBUCKET_HOME=/var/lib/gitbucket
GITBUCKET_WAR_FILE=/usr/share/gitbucket/lib/gitbucket.war
# Pull in cq settings
[ -f /etc/sysconfig/gitbucket ] && source /etc/sysconfig/gitbucket # RedHat
[ -f gitbucket.conf ] && source gitbucket.conf # For all systems
# Location of the log and PID file
LOG_FILE=$GITBUCKET_LOG_DIR/run.log
RED='\033[1m\E[37;41m'
GREEN='\033[1m\E[37;42m'
OFF='\E[0m'
RETVAL=0
start() {
echo -n $"Starting GitBucket server: "
START_OPTS=
if [ $GITBUCKET_PORT ]; then
START_OPTS="${START_OPTS} --port=${GITBUCKET_PORT}"
fi
if [ $GITBUCKET_PREFIX ]; then
START_OPTS="${START_OPTS} --prefix=${GITBUCKET_PREFIX}"
fi
if [ $GITBUCKET_HOST ]; then
START_OPTS="${START_OPTS} --host=${GITBUCKET_HOST}"
fi
GITBUCKET_HOME="${GITBUCKET_HOME}" daemon --user=gitbucket java $GITBUCKET_JVM_OPTS -jar $GITBUCKET_WAR_FILE $START_OPTS >>$LOG_FILE 2>&1 &
sleep 3
pgrep -f $GITBUCKET_WAR_FILE >> $LOG_FILE 2>&1
RETVAL=$?
if [ $RETVAL -eq 0 ] ; then
success "Success"
else
failure "Exit code $RETVAL"
fi
echo
return $RETVAL
}
stop() {
echo -n $"Stopping GitBucket server: "
# Run the Java process
pkill -f $GITBUCKET_WAR_FILE >>$LOG_FILE 2>&1
RETVAL=$?
if [ $RETVAL -eq 0 ] ; then
success "GitBucket stopping"
else
failure "GitBucket stopping"
fi
echo
return $RETVAL
}
restart() {
stop
start
}
case "$1" in
start)
start
;;
stop)
stop
;;
restart)
restart
;;
status)
pgrep -f $GITBUCKET_WAR_FILE >> $LOG_FILE 2>&1
RETVAL=$?
if [ $RETVAL -eq 0 ]; then
echo $"GitBucket is running...."
else
echo $"GitBucket is stopped"
fi
;;
*)
echo $"Usage: $0 [start|stop|restart|status]"
RETVAL=2
esac
exit $RETVAL

View File

@@ -0,0 +1,82 @@
Name: gitbucket
Summary: GitHub clone written with Scala.
Version: 2.6
Release: 1%{?dist}
License: Apache
URL: https://github.com/takezoe/gitbucket
Group: System/Servers
Source0: %{name}.war
Source1: %{name}.init
Source2: %{name}.conf
BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root
BuildArch: noarch
Requires: java >= 1.7
%description
GitBucket is the easily installable GitHub clone written with Scala.
%install
[ "%{buildroot}" != / ] && %{__rm} -rf "%{buildroot}"
%{__mkdir_p} %{buildroot}{%{_sysconfdir}/{init.d,sysconfig},%{_datarootdir}/%{name}/lib,%{_sharedstatedir}/%{name},%{_localstatedir}/log/%{name}}
%{__install} -m 0644 %{SOURCE0} %{buildroot}%{_datarootdir}/%{name}/lib
%{__install} -m 0755 %{SOURCE1} %{buildroot}%{_sysconfdir}/init.d/%{name}
%{__install} -m 0644 %{SOURCE2} %{buildroot}%{_sysconfdir}/sysconfig/%{name}
touch %{buildroot}%{_localstatedir}/log/%{name}/run.log
%pre
/usr/sbin/groupadd -r gitbucket &> /dev/null || :
/usr/sbin/useradd -g gitbucket -s /bin/false -r -c "GitBucket GitHub clone" -d %{_sharedstatedir}/%{name} gitbucket &> /dev/null || :
%post
/sbin/chkconfig --add gitbucket
%preun
if [ "$1" = 0 ]; then
/sbin/service gitbucket stop > /dev/null 2>&1
/sbin/chkconfig --del gitbucket
fi
exit 0
%postun
if [ "$1" -ge 1 ]; then
/sbin/service gitbucket restart > /dev/null 2>&1
fi
exit 0
%clean
[ "%{buildroot}" != / ] && %{__rm} -rf "%{buildroot}"
%files
%defattr(-,root,root,-)
%{_datarootdir}/%{name}/lib/%{name}.war
%config %{_sysconfdir}/init.d/%{name}
%config(noreplace) %{_sysconfdir}/sysconfig/%{name}
%attr(0755,gitbucket,gitbucket) %{_sharedstatedir}/%{name}
%attr(0750,gitbucket,gitbucket) %{_localstatedir}/log/%{name}
%changelog
* Mon Nov 24 2014 Toru Takahashi <torutk at gmail.com>
- Version bump to v2.6
* Sun Nov 09 2014 Toru Takahashi <torutk at gmail.com>
- Version bump to v2.5
* Sun Oct 26 2014 Toru Takahashi <torutk at gmail.com>
- Version bump to v2.4.1
* Mon Jul 21 2014 Toru Takahashi <torutk at gmail.com>
- execute as gitbucket user
* Sun Jul 20 2014 Toru Takahashi <torutk at gmail.com>
- Version bump to v2.1.
* Mon Oct 28 2013 Jiri Tyr <jiri_DOT_tyr at gmail.com>
- Version bump to v1.7.
* Thu Oct 17 2013 Jiri Tyr <jiri_DOT_tyr at gmail.com>
- First build.

28
contrib/macosx/makePlist Executable file
View File

@@ -0,0 +1,28 @@
#!/bin/bash
# From http://docstore.mik.ua/orelly/unix3/mac/ch02_02.htm
source gitbucket.conf
GITBUCKET_SERVICE_DIR=`dirname "$GITBUCKET_SERVICE"`
mkdir -p "$GITBUCKET_SERVICE_DIR"
cat << EOF > "$GITBUCKET_SERVICE_DIR/gitbucket.plist"
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>gitbucket</string>
<key>ProgramArguments</key>
<array>
<string>/usr/bin/java</string>
<string>$GITBUCKET_JVM_OPTS</string>
<string>-jar</string>
<string>gitbucket.war</string>
<string>--host=$GITBUCKET_HOST</string>
<string>--port=$GITBUCKET_PORT</string>
<string>--https=true</string>
</array>
<key>RunAtLoad</key>
<true/>
</dict>
</plist>
EOF

22
doc/activity.md Normal file
View File

@@ -0,0 +1,22 @@
Activity Timeline
========
GitBucket records several types of user activity to ```ACTIVITY``` table. Activity types are shown below:
type | message | additional information
------------------|------------------------------------------------------|------------------------
create_repository |$user created $owner/$repo |-
open_issue |$user opened issue $owner/$repo#$issueId |-
close_issue |$user closed issue $owner/$repo#$issueId |-
close_issue |$user closed pull request $owner/$repo#$issueId |-
reopen_issue |$user reopened issue $owner/$repo#$issueId |-
comment_issue |$user commented on issue $owner/$repo#$issueId |-
comment_issue |$user commented on pull request $owner/$repo#$issueId |-
create_wiki |$user created the $owner/$repo wiki |$page
edit_wiki |$user edited the $owner/$repo wiki |$page<br>$page:$commitId(since 1.5)
push |$user pushed to $owner/$repo#$branch to $owner/$repo |$commitId:$shortMessage\n*
create_tag |$user created tag $tag at $owner/$repo |-
create_branch |$user created branch $branch at $owner/$repo |-
delete_branch |$user deleted branch $branch at $owner/$repo |-
fork |$user forked $owner/$repo to $owner/$repo |-
open_pullreq |$user opened pull request $owner/$repo#issueId |-
merge_pullreq |$user merge pull request $owner/$repo#issueId |-

37
doc/auto_update.md Normal file
View File

@@ -0,0 +1,37 @@
Automatic Schema Updating
========
GitBucket uses H2 database to manage project and account data. GitBucket updates database schema automatically in the first run after the upgrading.
To release a new version of GitBucket, add the version definition to the [servlet.AutoUpdate](https://github.com/takezoe/gitbucket/blob/master/src/main/scala/servlet/AutoUpdateListener.scala) at first.
```scala
object AutoUpdate {
...
/**
* The history of versions. A head of this sequence is the current BitBucket version.
*/
val versions = Seq(
Version(1, 0)
)
...
```
Next, add a SQL file which updates database schema into [/src/main/resources/update/](https://github.com/takezoe/gitbucket/tree/master/src/main/resources/update) as ```MAJOR_MINOR.sql```.
GitBucket stores the current version to ```GITBUCKET_HOME/version``` and checks it at start-up. If the stored version differs from the actual version, it executes differences of SQL files between the stored version and the actual version. And ```GITBUCKET_HOME/version``` is updated by the actual version.
We can also add any Scala code for upgrade GitBucket which modifies esources other than database. Override ```Version.update``` like below:
```scala
val versions = Seq(
new Version(1, 3){
override def update(conn: Connection): Unit = {
super.update(conn)
// Add any code here!
}
},
Version(1, 2),
Version(1, 1),
Version(1, 0)
)
```

48
doc/comment_action.md Normal file
View File

@@ -0,0 +1,48 @@
About Action in Issue Comment
========
After the issue creation at GitBucket, users can add comments or close it.
The details are saved at ```ISSUE_COMMENT``` table.
To determine if it was any operation, you see the ```ACTION``` column.
|ACTION|
|--------|
|comment|
|close_comment|
|reopen_comment|
|close|
|reopen|
|commit|
|merge|
|delete_branch|
|refer|
#####comment
This value is saved when users have made a normal comment.
#####close_comment, reopen_comment
These values are saved when users have reopened or closed the issue with comments.
#####close, reopen
These values are saved when users have reopened or closed the issue.
At the same time, store the fixed value(i.e. "Close" or "Reopen") to the ```CONTENT``` column.
Therefore, this comment is not displayed, and not counted as a comment.
#####commit
This value is saved when users have pushed including the ```#issueId``` to the commit message.
At the same time, store it to the ```CONTENT``` column with its commit id.
This comment is displayed. But it can not be edited by all users, and also not counted as a comment.
#####merge
This value is saved when users have merged the pull request.
At the same time, store the message to the ```CONTENT``` column.
This comment is displayed. But it can not be edited by all users, and also not counted as a comment.
#####delete_branch
This value is saved when users have deleted the branch. Users can delete branch after merging pull request which is requested from the same repository.
At the same time, store it to the ```CONTENT``` column with the deleted branch name.
Therefore, this comment is not displayed, and not counted as a comment.
#####refer
This value is saved when other issue or issue comment contains reference to the issue like ```#issueId```.
At the same time, store id and title of the referrer issue as ```id:title```.

44
doc/directory.md Normal file
View File

@@ -0,0 +1,44 @@
Directory Structure
========
GitBucket persists all data into __HOME/.gitbucket__ in default (In 1.9 or before, HOME/gitbucket is default).
This directory has following structure:
```
* /HOME/gitbucket
* /repositoties
* /USER_NAME
* / REPO_NAME.git (substance of repository. GitServlet sees this directory)
* / REPO_NAME
* /issues (files which are attached to issue)
* / REPO_NAME.wiki.git (wiki repository)
* /data
* /USER_NAME
* /files
* avatar.xxx (image file of user avatar)
* /plugins
* /PLUGIN_NAME
* plugin.js
* /tmp
* /_upload
* /SESSION_ID (removed at session timeout)
* current time millis + random 10 alphanumeric chars (temporary file for file uploading)
* /USER_NAME
* /init-REPO_NAME (used in repository creation and removed after it) ... unused since 1.8
* /REPO_NAME.wiki (working directory for wiki repository) ... unused since 1.8
* /REPO_NAME
* /download (temporary directories are created under this directory)
```
There are some ways to specify the data directory instead of the default location.
1. Environment variable __GITBUCKET_HOME__
2. System property __gitbucket.home__ (e.g. ```-Dgitbucket.home=PATH_TO_DATADIR```)
3. Command line option for embedded Jetty (e.g. ```java -jar gitbucket.war --data=PATH_TO_DATADIR```)
4. Context parameter __gitbucket.home__ in web.xml like below:
```xml
<context-param>
<param-name>gitbucket.home</param-name>
<param-value>PATH_TO_DATADIR</param-value>
</context-param>
```

38
doc/how_to_run.md Normal file
View File

@@ -0,0 +1,38 @@
How to run from the source tree
========
for Testers
--------
If you want to test GitBucket, input following command at the root directory of the source tree.
```
C:\gitbucket> sbt ~container:start
```
Then access to `http://localhost:8080/` by your browser. The default administrator account is `root` and password is `root`.
for Developers
--------
If you want to modify source code and confirm it, you can run GitBucket in auto reloading mode as following:
```
C:\gitbucket> sbt
...
> container:start
...
> ~ ;copy-resources;aux-compile
```
Build war file
--------
To build war file, run the following command:
```
C:\gitbucket> sbt package
```
`gitbucket_2.11-x.x.x.war` is generated into `target/scala-2.11`.
To build executable war file, run Ant at the top of the source tree. It generates executable `gitbucket.war` into `target/scala-2.11`. We release this war file as release artifact. Please note the current build.xml works on Windows only. Replace `sbt.bat` with `sbt.sh` in build.xml if you want to run it on Linux.

23
doc/notification.md Normal file
View File

@@ -0,0 +1,23 @@
Notification Email
========
GitBucket sends email to target users by enabling the notification email by an administrator.
The timing of the notification are as follows:
##### at the issue registration (new issue, new pull request)
When a record is saved into the ```ISSUE``` table, GitBucket does the notification.
##### at the comment registration
Among the records in the ```ISSUE_COMMENT``` table, them to be counted as a comment (i.e. the record ```ACTION``` column value is "comment" or "close_comment" or "reopen_comment") are saved, GitBucket does the notification.
##### at the status update (close, reopen, merge)
When the ```CLOSED``` column value is updated, GitBucket does the notification.
Notified users are as follows:
* individual repository's owner
* collaborators
* participants
However, the operation in person is excluded from the target.

10
doc/readme.md Normal file
View File

@@ -0,0 +1,10 @@
Developer's Guide
========
* [How to run from source tree](how_to_run.md)
* [Directory Structure](directory.md)
* [Mapping and Validation](validation.md)
* Authentication in Controller (not yet)
* [About Action in Issue Comment](comment_action.md)
* [Activity Types](activity.md)
* [Notification Email](notification.md)
* [Automatic Schema Updating](auto_update.md)

71
doc/validation.md Normal file
View File

@@ -0,0 +1,71 @@
Mapping and Validation
========
GitBucket uses [scalatra-forms](https://github.com/takezoe/scalatra-forms) to validate request parameters and map them to the scala object. This is inspired by Play2 form mapping / validation.
At first, define the mapping as following:
```scala
import jp.sf.amateras.scalatra.forms._
case class RegisterForm(name: String, description: String)
val form = mapping(
"name" -> text(required, maxlength(40)),
"description" -> text()
)(RegisterForm.apply)
```
The servlet have to mixed in ```jp.sf.amateras.scalatra.forms.ClientSideValidationFormSupport``` to validate request parameters and take mapped object. It validates request parameters before action. If any errors are detected, it throws an exception.
```scala
class RegisterServlet extends ScalatraServlet with ClientSideValidationFormSupport {
post("/register", form) { form: RegisterForm =>
...
}
}
```
In the view template, you can add client-side validation by adding ```validate="true"``` to your form. Error messages are set to ```span#error-<fieldname>```.
```html
<form method="POST" action="/register" validate="true">
Name: <input type="name" type="text">
<span class="error" id="error-name"></span>
<br/>
Description: <input type="description" type="text">
<span class="error" id="error-description"></span>
<br/>
<input type="submit" value="Register"/>
</form>
```
Client-side validation calls ```<form-action>/validate``` to validate form contents. It returns a validation result as JSON. In this case, form action is ```/register```, so ```/register/validate``` is called before submitting a form. ```ClientSideValidationFormSupport``` adds this JSON API automatically.
For Ajax request, you have to use '''ajaxGet''' or '''ajaxPost''' to define action. It almost same as '''get''' or '''post'''. You can implement actions which handle Ajax request as same as normal actions.
Small difference is they return validation errors as JSON.
```scala
ajaxPost("/register", form){ form =>
...
}
```
You can call these actions using jQuery as below:
```javascript
$('#register').click(function(e){
$.ajax($(this).attr('action'), {
type: 'POST',
data: {
name: $('#name').val(),
mail: $('#mail').val()
}
})
.done(function(data){
$('#result').text('Registered!');
})
.fail(function(data, status){
displayErrors($.parseJSON(data.responseText));
});
});
```

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

11
embed-jetty/update.sh Executable file
View File

@@ -0,0 +1,11 @@
#!/bin/bash
version=$1
output_dir=`dirname $0`
git rm -f ${output_dir}/jetty-*.jar
for name in 'io' 'servlet' 'xml' 'continuation' 'security' 'util' 'http' 'server' 'webapp'
do
jar_filename="jetty-${name}-${version}.jar"
wget "http://repo1.maven.org/maven2/org/eclipse/jetty/jetty-${name}/${version}/${jar_filename}" -O ${output_dir}/${jar_filename}
done
git add ${output_dir}/*.jar
git commit

9
etc/deploy-assemby-jar.sh Executable file
View File

@@ -0,0 +1,9 @@
#!/bin/sh
mvn deploy:deploy-file \
-DgroupId=gitbucket\
-DartifactId=gitbucket-assembly\
-Dversion=3.0.0\
-Dpackaging=jar\
-Dfile=../target/scala-2.11/gitbucket-assembly-3.0.0.jar\
-DrepositoryId=sourceforge.jp\
-Durl=scp://shell.sourceforge.jp/home/groups/a/am/amateras/htdocs/mvn/

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 78 KiB

17
etc/pom.xml Normal file
View File

@@ -0,0 +1,17 @@
<?xml version="1.0"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>jp.sf.amateras</groupId>
<artifactId>gitbucket-assembly</artifactId>
<version>0.0.1</version>
<build>
<extensions>
<extension>
<groupId>org.apache.maven.wagon</groupId>
<artifactId>wagon-ssh</artifactId>
<version>1.0-beta-6</version>
</extension>
</extensions>
</build>
</project>

1
project/build.properties Normal file
View File

@@ -0,0 +1 @@
sbt.version=0.13.8

View File

@@ -1,49 +1,78 @@
import sbt._
import Keys._
import org.scalatra.sbt._
import org.scalatra.sbt.PluginKeys._
import sbt.ScalaVersion
import twirl.sbt.TwirlPlugin._
import com.typesafe.sbteclipse.plugin.EclipsePlugin.EclipseKeys
import play.twirl.sbt.SbtTwirl
import play.twirl.sbt.Import.TwirlKeys._
import sbtassembly._
import sbtassembly.AssemblyKeys._
object MyBuild extends Build {
val Organization = "jp.sf.amateras"
val Organization = "gitbucket"
val Name = "gitbucket"
val Version = "0.0.1"
val ScalaVersion = "2.10.1"
val ScalatraVersion = "2.2.1"
val Version = "3.0.0"
val ScalaVersion = "2.11.6"
val ScalatraVersion = "2.3.1"
lazy val project = Project (
"gitbucket",
file("."),
settings = Defaults.defaultSettings ++ ScalatraPlugin.scalatraWithJRebel ++ Seq(
organization := Organization,
name := Name,
version := Version,
scalaVersion := ScalaVersion,
resolvers ++= Seq(
Classpaths.typesafeReleases,
"amateras-repo" at "http://amateras.sourceforge.jp/mvn/"
),
libraryDependencies ++= Seq(
"org.eclipse.jgit" % "org.eclipse.jgit.http.server" % "3.0.0.201306101825-r",
"org.scalatra" %% "scalatra" % ScalatraVersion,
"org.scalatra" %% "scalatra-specs2" % ScalatraVersion % "test",
"org.scalatra" %% "scalatra-json" % ScalatraVersion,
"org.json4s" %% "json4s-jackson" % "3.2.4",
"jp.sf.amateras" %% "scalatra-forms" % "0.0.2",
"commons-io" % "commons-io" % "2.4",
"org.pegdown" % "pegdown" % "1.3.0",
"org.apache.commons" % "commons-compress" % "1.5",
"org.apache.commons" % "commons-email" % "1.3.1",
"com.typesafe.slick" %% "slick" % "1.0.1",
"com.novell.ldap" % "jldap" % "2009-10-07",
"com.h2database" % "h2" % "1.3.171",
"ch.qos.logback" % "logback-classic" % "1.0.6" % "runtime",
"org.eclipse.jetty" % "jetty-webapp" % "8.1.8.v20121106" % "container",
"org.eclipse.jetty.orbit" % "javax.servlet" % "3.0.0.v201112011016" % "container;provided;test" artifacts (Artifact("javax.servlet", "jar", "jar"))
),
EclipseKeys.withSource := true
) ++ seq(Twirl.settings: _*)
file(".")
)
}
.settings(ScalatraPlugin.scalatraWithJRebel: _*)
.settings(
test in assembly := {},
assemblyMergeStrategy in assembly := {
case PathList("META-INF", xs @ _*) =>
(xs map {_.toLowerCase}) match {
case ("manifest.mf" :: Nil) => MergeStrategy.discard
case _ => MergeStrategy.discard
}
case x => MergeStrategy.first
}
)
.settings(
sourcesInBase := false,
organization := Organization,
name := Name,
version := Version,
scalaVersion := ScalaVersion,
resolvers ++= Seq(
Classpaths.typesafeReleases,
"amateras-repo" at "http://amateras.sourceforge.jp/mvn/"
),
scalacOptions := Seq("-deprecation", "-language:postfixOps"),
libraryDependencies ++= Seq(
"org.eclipse.jgit" % "org.eclipse.jgit.http.server" % "3.4.2.201412180340-r",
"org.eclipse.jgit" % "org.eclipse.jgit.archive" % "3.4.2.201412180340-r",
"org.scalatra" %% "scalatra" % ScalatraVersion,
"org.scalatra" %% "scalatra-specs2" % ScalatraVersion % "test",
"org.scalatra" %% "scalatra-json" % ScalatraVersion,
"org.json4s" %% "json4s-jackson" % "3.2.11",
"jp.sf.amateras" %% "scalatra-forms" % "0.1.0",
"commons-io" % "commons-io" % "2.4",
"org.pegdown" % "pegdown" % "1.4.1", // 1.4.2 has incompatible APi changes
"org.apache.commons" % "commons-compress" % "1.9",
"org.apache.commons" % "commons-email" % "1.3.3",
"org.apache.httpcomponents" % "httpclient" % "4.3.6",
"org.apache.sshd" % "apache-sshd" % "0.11.0",
"com.typesafe.slick" %% "slick" % "2.1.0",
"com.novell.ldap" % "jldap" % "2009-10-07",
"com.h2database" % "h2" % "1.4.186",
// "ch.qos.logback" % "logback-classic" % "1.0.13" % "runtime",
"org.eclipse.jetty" % "jetty-webapp" % "8.1.16.v20140903" % "container;provided",
"org.eclipse.jetty.orbit" % "javax.servlet" % "3.0.0.v201112011016" % "container;provided;test" artifacts Artifact("javax.servlet", "jar", "jar"),
"junit" % "junit" % "4.12" % "test",
"com.mchange" % "c3p0" % "0.9.5",
"com.typesafe" % "config" % "1.2.1",
"com.typesafe.play" %% "twirl-compiler" % "1.0.4"
),
play.twirl.sbt.Import.TwirlKeys.templateImports += "gitbucket.core._",
EclipseKeys.withSource := true,
javacOptions in compile ++= Seq("-target", "7", "-source", "7"),
testOptions in Test += Tests.Argument(TestFrameworks.Specs2, "junitxml", "console"),
javaOptions in Test += "-Dgitbucket.home=target/gitbucket_home_for_test",
testOptions in Test += Tests.Setup( () => new java.io.File("target/gitbucket_home_for_test").mkdir() ),
fork in Test := true,
packageOptions += Package.MainClass("JettyLauncher")
).enablePlugins(SbtTwirl)
}

View File

@@ -1,7 +1,8 @@
addSbtPlugin("com.typesafe.sbteclipse" % "sbteclipse-plugin" % "2.2.0")
scalacOptions ++= Seq("-unchecked", "-deprecation", "-feature")
addSbtPlugin("com.github.mpeltonen" % "sbt-idea" % "1.5.1")
addSbtPlugin("org.scalatra.sbt" % "scalatra-sbt" % "0.3.0")
addSbtPlugin("io.spray" % "sbt-twirl" % "0.6.1")
addSbtPlugin("com.typesafe.sbteclipse" % "sbteclipse-plugin" % "2.4.0")
addSbtPlugin("com.github.mpeltonen" % "sbt-idea" % "1.6.0")
addSbtPlugin("org.scalatra.sbt" % "scalatra-sbt" % "0.3.5")
addSbtPlugin("com.typesafe.sbt" % "sbt-twirl" % "1.0.4")
addSbtPlugin("com.timushev.sbt" % "sbt-updates" % "0.1.8")
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.12.0")

Binary file not shown.

BIN
sbt-launch-0.13.8.jar Normal file

Binary file not shown.

View File

@@ -1,2 +1,2 @@
set SCRIPT_DIR=%~dp0
java -XX:+CMSClassUnloadingEnabled -XX:MaxPermSize=256m -Xmx512M -Xss2M -jar "%SCRIPT_DIR%\sbt-launch-0.12.3.jar" %*
java -Dsbt.log.noformat=true -XX:+CMSClassUnloadingEnabled -XX:MaxPermSize=256m -Xmx512M -Xss2M -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5005 -jar "%SCRIPT_DIR%\sbt-launch-0.13.8.jar" %*

3
sbt.sh
View File

@@ -1 +1,2 @@
java -XX:+CMSClassUnloadingEnabled -XX:MaxPermSize=256m -Xmx512M -Xss2M -jar `dirname $0`/sbt-launch-0.12.3.jar "$@"
#!/bin/sh
java -Dsbt.log.noformat=true -XX:+CMSClassUnloadingEnabled -XX:MaxPermSize=256m -Xmx512M -Xss2M -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5005 -jar `dirname $0`/sbt-launch-0.13.8.jar "$@"

View File

@@ -0,0 +1,91 @@
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.nio.SelectChannelConnector;
import org.eclipse.jetty.webapp.WebAppContext;
import java.io.File;
import java.net.URL;
import java.security.ProtectionDomain;
public class JettyLauncher {
public static void main(String[] args) throws Exception {
String host = null;
int port = 8080;
String contextPath = "/";
boolean forceHttps = false;
for(String arg: args) {
if(arg.startsWith("--") && arg.contains("=")) {
String[] dim = arg.split("=");
if(dim.length >= 2) {
if(dim[0].equals("--host")) {
host = dim[1];
} else if(dim[0].equals("--port")) {
port = Integer.parseInt(dim[1]);
} else if(dim[0].equals("--prefix")) {
contextPath = dim[1];
} else if(dim[0].equals("--gitbucket.home")){
System.setProperty("gitbucket.home", dim[1]);
}
}
}
}
Server server = new Server();
SelectChannelConnector connector = new SelectChannelConnector();
if(host != null) {
connector.setHost(host);
}
connector.setMaxIdleTime(1000 * 60 * 60);
connector.setSoLingerTime(-1);
connector.setPort(port);
server.addConnector(connector);
WebAppContext context = new WebAppContext();
File tmpDir = new File(getGitBucketHome(), "tmp");
if(tmpDir.exists()){
deleteDirectory(tmpDir);
}
tmpDir.mkdirs();
context.setTempDirectory(tmpDir);
ProtectionDomain domain = JettyLauncher.class.getProtectionDomain();
URL location = domain.getCodeSource().getLocation();
context.setContextPath(contextPath);
context.setDescriptor(location.toExternalForm() + "/WEB-INF/web.xml");
context.setServer(server);
context.setWar(location.toExternalForm());
if (forceHttps) {
context.setInitParameter("org.scalatra.ForceHttps", "true");
}
server.setHandler(context);
server.start();
server.join();
}
private static File getGitBucketHome(){
String home = System.getProperty("gitbucket.home");
if(home != null && home.length() > 0){
return new File(home);
}
home = System.getenv("GITBUCKET_HOME");
if(home != null && home.length() > 0){
return new File(home);
}
return new File(System.getProperty("user.home"), ".gitbucket");
}
private static void deleteDirectory(File dir){
for(File file: dir.listFiles()){
if(file.isFile()){
file.delete();
} else if(file.isDirectory()){
deleteDirectory(file);
}
}
dir.delete();
}
}

View File

@@ -0,0 +1,93 @@
package gitbucket.core.util;
import org.eclipse.jgit.api.errors.PatchApplyException;
import org.eclipse.jgit.diff.RawText;
import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.patch.FileHeader;
import org.eclipse.jgit.patch.HunkHeader;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.List;
/**
* This class helps to apply patch. Most of these code came from {@link org.eclipse.jgit.api.ApplyCommand}.
*/
public class PatchUtil {
public static String apply(String source, String patch, FileHeader fh)
throws IOException, PatchApplyException {
RawText rt = new RawText(source.getBytes("UTF-8"));
List<String> oldLines = new ArrayList<String>(rt.size());
for (int i = 0; i < rt.size(); i++)
oldLines.add(rt.getString(i));
List<String> newLines = new ArrayList<String>(oldLines);
for (HunkHeader hh : fh.getHunks()) {
ByteArrayOutputStream out = new ByteArrayOutputStream();
out.write(patch.getBytes("UTF-8"), hh.getStartOffset(), hh.getEndOffset() - hh.getStartOffset());
RawText hrt = new RawText(out.toByteArray());
List<String> hunkLines = new ArrayList<String>(hrt.size());
for (int i = 0; i < hrt.size(); i++)
hunkLines.add(hrt.getString(i));
int pos = 0;
for (int j = 1; j < hunkLines.size(); j++) {
String hunkLine = hunkLines.get(j);
switch (hunkLine.charAt(0)) {
case ' ':
if (!newLines.get(hh.getNewStartLine() - 1 + pos).equals(
hunkLine.substring(1))) {
throw new PatchApplyException(MessageFormat.format(
JGitText.get().patchApplyException, hh));
}
pos++;
break;
case '-':
if (!newLines.get(hh.getNewStartLine() - 1 + pos).equals(
hunkLine.substring(1))) {
throw new PatchApplyException(MessageFormat.format(
JGitText.get().patchApplyException, hh));
}
newLines.remove(hh.getNewStartLine() - 1 + pos);
break;
case '+':
newLines.add(hh.getNewStartLine() - 1 + pos,
hunkLine.substring(1));
pos++;
break;
}
}
}
if (!isNoNewlineAtEndOfFile(fh))
newLines.add(""); //$NON-NLS-1$
if (!rt.isMissingNewlineAtEnd())
oldLines.add(""); //$NON-NLS-1$
if (!isChanged(oldLines, newLines))
return null; // don't touch the file
StringBuilder sb = new StringBuilder();
for (String l : newLines) {
// don't bother handling line endings - if it was windows, the \r is
// still there!
sb.append(l).append('\n');
}
sb.deleteCharAt(sb.length() - 1);
return sb.toString();
}
private static boolean isChanged(List<String> ol, List<String> nl) {
if (ol.size() != nl.size())
return true;
for (int i = 0; i < ol.size(); i++)
if (!ol.get(i).equals(nl.get(i)))
return true;
return false;
}
private static boolean isNoNewlineAtEndOfFile(FileHeader fh) {
HunkHeader lastHunk = fh.getHunks().get(fh.getHunks().size() - 1);
RawText lhrt = new RawText(lastHunk.getBuffer());
return lhrt.getString(lhrt.size() - 1).equals(
"\\ No newline at end of file"); //$NON-NLS-1$
}
}

View File

@@ -0,0 +1,6 @@
db {
driver = "org.h2.Driver"
url = "jdbc:h2:${DatabaseHome};MVCC=true"
user = "sa"
password = "sa"
}

View File

@@ -1,4 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<logger name="scala.slick" level="INFO" />
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="STDOUT" />
</root>
<!--
<logger name="service.WebHookService" level="DEBUG" />
<logger name="servlet" level="DEBUG" />
-->
</configuration>

View File

@@ -1,135 +1,135 @@
CREATE TABLE ACCOUNT(
USER_NAME VARCHAR(100) NOT NULL,
MAIL_ADDRESS VARCHAR(100) NOT NULL,
PASSWORD VARCHAR(40) NOT NULL,
ADMINISTRATOR BOOLEAN NOT NULL,
URL VARCHAR(200),
REGISTERED_DATE TIMESTAMP NOT NULL,
UPDATED_DATE TIMESTAMP NOT NULL,
LAST_LOGIN_DATE TIMESTAMP
);
CREATE TABLE REPOSITORY(
USER_NAME VARCHAR(100) NOT NULL,
REPOSITORY_NAME VARCHAR(100) NOT NULL,
PRIVATE BOOLEAN NOT NULL,
DESCRIPTION TEXT,
DEFAULT_BRANCH VARCHAR(100),
REGISTERED_DATE TIMESTAMP NOT NULL,
UPDATED_DATE TIMESTAMP NOT NULL,
LAST_ACTIVITY_DATE TIMESTAMP NOT NULL
);
CREATE TABLE COLLABORATOR(
USER_NAME VARCHAR(100) NOT NULL,
REPOSITORY_NAME VARCHAR(100) NOT NULL,
COLLABORATOR_NAME VARCHAR(100) NOT NULL
);
CREATE TABLE ISSUE(
USER_NAME VARCHAR(100) NOT NULL,
REPOSITORY_NAME VARCHAR(100) NOT NULL,
ISSUE_ID INT NOT NULL,
OPENED_USER_NAME VARCHAR(100) NOT NULL,
MILESTONE_ID INT,
ASSIGNED_USER_NAME VARCHAR(100),
TITLE TEXT NOT NULL,
CONTENT TEXT,
CLOSED BOOLEAN NOT NULL,
REGISTERED_DATE TIMESTAMP NOT NULL,
UPDATED_DATE TIMESTAMP NOT NULL
);
CREATE TABLE ISSUE_ID(
USER_NAME VARCHAR(100) NOT NULL,
REPOSITORY_NAME VARCHAR(100) NOT NULL,
ISSUE_ID INT NOT NULL
);
CREATE TABLE ISSUE_COMMENT(
USER_NAME VARCHAR(100) NOT NULL,
REPOSITORY_NAME VARCHAR(100) NOT NULL,
ISSUE_ID INT NOT NULL,
COMMENT_ID INT AUTO_INCREMENT,
ACTION VARCHAR(10),
COMMENTED_USER_NAME VARCHAR(100) NOT NULL,
CONTENT TEXT NOT NULL,
REGISTERED_DATE TIMESTAMP NOT NULL,
UPDATED_DATE TIMESTAMP NOT NULL
);
CREATE TABLE LABEL(
USER_NAME VARCHAR(100) NOT NULL,
REPOSITORY_NAME VARCHAR(100) NOT NULL,
LABEL_ID INT AUTO_INCREMENT,
LABEL_NAME VARCHAR(100) NOT NULL,
COLOR CHAR(6) NOT NULL
);
CREATE TABLE ISSUE_LABEL(
USER_NAME VARCHAR(100) NOT NULL,
REPOSITORY_NAME VARCHAR(100) NOT NULL,
ISSUE_ID INT NOT NULL,
LABEL_ID INT NOT NULL
);
CREATE TABLE MILESTONE(
USER_NAME VARCHAR(100) NOT NULL,
REPOSITORY_NAME VARCHAR(100) NOT NULL,
MILESTONE_ID INT AUTO_INCREMENT,
TITLE VARCHAR(100) NOT NULL,
DESCRIPTION TEXT,
DUE_DATE TIMESTAMP,
CLOSED_DATE TIMESTAMP
);
ALTER TABLE ACCOUNT ADD CONSTRAINT IDX_ACCOUNT_PK PRIMARY KEY (USER_NAME);
ALTER TABLE ACCOUNT ADD CONSTRAINT IDX_ACCOUNT_1 UNIQUE (MAIL_ADDRESS);
ALTER TABLE REPOSITORY ADD CONSTRAINT IDX_REPOSITORY_PK PRIMARY KEY (USER_NAME, REPOSITORY_NAME);
ALTER TABLE REPOSITORY ADD CONSTRAINT IDX_REPOSITORY_FK0 FOREIGN KEY (USER_NAME) REFERENCES ACCOUNT (USER_NAME);
ALTER TABLE COLLABORATOR ADD CONSTRAINT IDX_COLLABORATOR_PK PRIMARY KEY (USER_NAME, REPOSITORY_NAME);
ALTER TABLE COLLABORATOR ADD CONSTRAINT IDX_COLLABORATOR_FK0 FOREIGN KEY (USER_NAME, REPOSITORY_NAME) REFERENCES REPOSITORY (USER_NAME, REPOSITORY_NAME);
ALTER TABLE COLLABORATOR ADD CONSTRAINT IDX_COLLABORATOR_FK1 FOREIGN KEY (COLLABORATOR_NAME) REFERENCES ACCOUNT (USER_NAME);
ALTER TABLE ISSUE ADD CONSTRAINT IDX_ISSUE_PK PRIMARY KEY (ISSUE_ID, USER_NAME, REPOSITORY_NAME);
ALTER TABLE ISSUE ADD CONSTRAINT IDX_ISSUE_FK0 FOREIGN KEY (USER_NAME, REPOSITORY_NAME) REFERENCES REPOSITORY (USER_NAME, REPOSITORY_NAME);
ALTER TABLE ISSUE ADD CONSTRAINT IDX_ISSUE_FK1 FOREIGN KEY (OPENED_USER_NAME) REFERENCES ACCOUNT (USER_NAME);
ALTER TABLE ISSUE ADD CONSTRAINT IDX_ISSUE_FK2 FOREIGN KEY (MILESTONE_ID) REFERENCES MILESTONE (MILESTONE_ID);
ALTER TABLE ISSUE_ID ADD CONSTRAINT IDX_ISSUE_ID_PK PRIMARY KEY (USER_NAME, REPOSITORY_NAME);
ALTER TABLE ISSUE_ID ADD CONSTRAINT IDX_ISSUE_ID_FK1 FOREIGN KEY (USER_NAME, REPOSITORY_NAME) REFERENCES REPOSITORY (USER_NAME, REPOSITORY_NAME);
ALTER TABLE ISSUE_COMMENT ADD CONSTRAINT IDX_ISSUE_COMMENT_PK PRIMARY KEY (COMMENT_ID);
ALTER TABLE ISSUE_COMMENT ADD CONSTRAINT IDX_ISSUE_COMMENT_1 UNIQUE (USER_NAME, REPOSITORY_NAME, ISSUE_ID, COMMENT_ID);
ALTER TABLE ISSUE_COMMENT ADD CONSTRAINT IDX_ISSUE_COMMENT_FK0 FOREIGN KEY (USER_NAME, REPOSITORY_NAME, ISSUE_ID) REFERENCES ISSUE (USER_NAME, REPOSITORY_NAME, ISSUE_ID);
ALTER TABLE LABEL ADD CONSTRAINT IDX_LABEL_PK PRIMARY KEY (USER_NAME, REPOSITORY_NAME, LABEL_ID);
ALTER TABLE LABEL ADD CONSTRAINT IDX_LABEL_FK0 FOREIGN KEY (USER_NAME, REPOSITORY_NAME) REFERENCES REPOSITORY (USER_NAME, REPOSITORY_NAME);
ALTER TABLE ISSUE_LABEL ADD CONSTRAINT IDX_ISSUE_LABEL_PK PRIMARY KEY (USER_NAME, REPOSITORY_NAME, ISSUE_ID, LABEL_ID);
ALTER TABLE ISSUE_LABEL ADD CONSTRAINT IDX_ISSUE_LABEL_FK0 FOREIGN KEY (USER_NAME, REPOSITORY_NAME, ISSUE_ID) REFERENCES ISSUE (USER_NAME, REPOSITORY_NAME, ISSUE_ID);
ALTER TABLE MILESTONE ADD CONSTRAINT IDX_MILESTONE_PK PRIMARY KEY (USER_NAME, REPOSITORY_NAME, MILESTONE_ID);
ALTER TABLE MILESTONE ADD CONSTRAINT IDX_MILESTONE_FK0 FOREIGN KEY (USER_NAME, REPOSITORY_NAME) REFERENCES REPOSITORY (USER_NAME, REPOSITORY_NAME);
INSERT INTO ACCOUNT (
USER_NAME,
MAIL_ADDRESS,
PASSWORD,
ADMINISTRATOR,
URL,
REGISTERED_DATE,
UPDATED_DATE,
LAST_LOGIN_DATE
) VALUES (
'root',
'root@localhost',
'dc76e9f0c0006e8f919e0c515c66dbba3982f785',
true,
'https://github.com/takezoe/gitbucket',
SYSDATE,
SYSDATE,
NULL
);
CREATE TABLE ACCOUNT(
USER_NAME VARCHAR(100) NOT NULL,
MAIL_ADDRESS VARCHAR(100) NOT NULL,
PASSWORD VARCHAR(40) NOT NULL,
ADMINISTRATOR BOOLEAN NOT NULL,
URL VARCHAR(200),
REGISTERED_DATE TIMESTAMP NOT NULL,
UPDATED_DATE TIMESTAMP NOT NULL,
LAST_LOGIN_DATE TIMESTAMP
);
CREATE TABLE REPOSITORY(
USER_NAME VARCHAR(100) NOT NULL,
REPOSITORY_NAME VARCHAR(100) NOT NULL,
PRIVATE BOOLEAN NOT NULL,
DESCRIPTION TEXT,
DEFAULT_BRANCH VARCHAR(100),
REGISTERED_DATE TIMESTAMP NOT NULL,
UPDATED_DATE TIMESTAMP NOT NULL,
LAST_ACTIVITY_DATE TIMESTAMP NOT NULL
);
CREATE TABLE COLLABORATOR(
USER_NAME VARCHAR(100) NOT NULL,
REPOSITORY_NAME VARCHAR(100) NOT NULL,
COLLABORATOR_NAME VARCHAR(100) NOT NULL
);
CREATE TABLE ISSUE(
USER_NAME VARCHAR(100) NOT NULL,
REPOSITORY_NAME VARCHAR(100) NOT NULL,
ISSUE_ID INT NOT NULL,
OPENED_USER_NAME VARCHAR(100) NOT NULL,
MILESTONE_ID INT,
ASSIGNED_USER_NAME VARCHAR(100),
TITLE TEXT NOT NULL,
CONTENT TEXT,
CLOSED BOOLEAN NOT NULL,
REGISTERED_DATE TIMESTAMP NOT NULL,
UPDATED_DATE TIMESTAMP NOT NULL
);
CREATE TABLE ISSUE_ID(
USER_NAME VARCHAR(100) NOT NULL,
REPOSITORY_NAME VARCHAR(100) NOT NULL,
ISSUE_ID INT NOT NULL
);
CREATE TABLE ISSUE_COMMENT(
USER_NAME VARCHAR(100) NOT NULL,
REPOSITORY_NAME VARCHAR(100) NOT NULL,
ISSUE_ID INT NOT NULL,
COMMENT_ID INT AUTO_INCREMENT,
ACTION VARCHAR(10),
COMMENTED_USER_NAME VARCHAR(100) NOT NULL,
CONTENT TEXT NOT NULL,
REGISTERED_DATE TIMESTAMP NOT NULL,
UPDATED_DATE TIMESTAMP NOT NULL
);
CREATE TABLE LABEL(
USER_NAME VARCHAR(100) NOT NULL,
REPOSITORY_NAME VARCHAR(100) NOT NULL,
LABEL_ID INT AUTO_INCREMENT,
LABEL_NAME VARCHAR(100) NOT NULL,
COLOR CHAR(6) NOT NULL
);
CREATE TABLE ISSUE_LABEL(
USER_NAME VARCHAR(100) NOT NULL,
REPOSITORY_NAME VARCHAR(100) NOT NULL,
ISSUE_ID INT NOT NULL,
LABEL_ID INT NOT NULL
);
CREATE TABLE MILESTONE(
USER_NAME VARCHAR(100) NOT NULL,
REPOSITORY_NAME VARCHAR(100) NOT NULL,
MILESTONE_ID INT AUTO_INCREMENT,
TITLE VARCHAR(100) NOT NULL,
DESCRIPTION TEXT,
DUE_DATE TIMESTAMP,
CLOSED_DATE TIMESTAMP
);
ALTER TABLE ACCOUNT ADD CONSTRAINT IDX_ACCOUNT_PK PRIMARY KEY (USER_NAME);
ALTER TABLE ACCOUNT ADD CONSTRAINT IDX_ACCOUNT_1 UNIQUE (MAIL_ADDRESS);
ALTER TABLE REPOSITORY ADD CONSTRAINT IDX_REPOSITORY_PK PRIMARY KEY (USER_NAME, REPOSITORY_NAME);
ALTER TABLE REPOSITORY ADD CONSTRAINT IDX_REPOSITORY_FK0 FOREIGN KEY (USER_NAME) REFERENCES ACCOUNT (USER_NAME);
ALTER TABLE COLLABORATOR ADD CONSTRAINT IDX_COLLABORATOR_PK PRIMARY KEY (USER_NAME, REPOSITORY_NAME);
ALTER TABLE COLLABORATOR ADD CONSTRAINT IDX_COLLABORATOR_FK0 FOREIGN KEY (USER_NAME, REPOSITORY_NAME) REFERENCES REPOSITORY (USER_NAME, REPOSITORY_NAME);
ALTER TABLE COLLABORATOR ADD CONSTRAINT IDX_COLLABORATOR_FK1 FOREIGN KEY (COLLABORATOR_NAME) REFERENCES ACCOUNT (USER_NAME);
ALTER TABLE ISSUE ADD CONSTRAINT IDX_ISSUE_PK PRIMARY KEY (ISSUE_ID, USER_NAME, REPOSITORY_NAME);
ALTER TABLE ISSUE ADD CONSTRAINT IDX_ISSUE_FK0 FOREIGN KEY (USER_NAME, REPOSITORY_NAME) REFERENCES REPOSITORY (USER_NAME, REPOSITORY_NAME);
ALTER TABLE ISSUE ADD CONSTRAINT IDX_ISSUE_FK1 FOREIGN KEY (OPENED_USER_NAME) REFERENCES ACCOUNT (USER_NAME);
ALTER TABLE ISSUE ADD CONSTRAINT IDX_ISSUE_FK2 FOREIGN KEY (MILESTONE_ID) REFERENCES MILESTONE (MILESTONE_ID);
ALTER TABLE ISSUE_ID ADD CONSTRAINT IDX_ISSUE_ID_PK PRIMARY KEY (USER_NAME, REPOSITORY_NAME);
ALTER TABLE ISSUE_ID ADD CONSTRAINT IDX_ISSUE_ID_FK1 FOREIGN KEY (USER_NAME, REPOSITORY_NAME) REFERENCES REPOSITORY (USER_NAME, REPOSITORY_NAME);
ALTER TABLE ISSUE_COMMENT ADD CONSTRAINT IDX_ISSUE_COMMENT_PK PRIMARY KEY (COMMENT_ID);
ALTER TABLE ISSUE_COMMENT ADD CONSTRAINT IDX_ISSUE_COMMENT_1 UNIQUE (USER_NAME, REPOSITORY_NAME, ISSUE_ID, COMMENT_ID);
ALTER TABLE ISSUE_COMMENT ADD CONSTRAINT IDX_ISSUE_COMMENT_FK0 FOREIGN KEY (USER_NAME, REPOSITORY_NAME, ISSUE_ID) REFERENCES ISSUE (USER_NAME, REPOSITORY_NAME, ISSUE_ID);
ALTER TABLE LABEL ADD CONSTRAINT IDX_LABEL_PK PRIMARY KEY (USER_NAME, REPOSITORY_NAME, LABEL_ID);
ALTER TABLE LABEL ADD CONSTRAINT IDX_LABEL_FK0 FOREIGN KEY (USER_NAME, REPOSITORY_NAME) REFERENCES REPOSITORY (USER_NAME, REPOSITORY_NAME);
ALTER TABLE ISSUE_LABEL ADD CONSTRAINT IDX_ISSUE_LABEL_PK PRIMARY KEY (USER_NAME, REPOSITORY_NAME, ISSUE_ID, LABEL_ID);
ALTER TABLE ISSUE_LABEL ADD CONSTRAINT IDX_ISSUE_LABEL_FK0 FOREIGN KEY (USER_NAME, REPOSITORY_NAME, ISSUE_ID) REFERENCES ISSUE (USER_NAME, REPOSITORY_NAME, ISSUE_ID);
ALTER TABLE MILESTONE ADD CONSTRAINT IDX_MILESTONE_PK PRIMARY KEY (USER_NAME, REPOSITORY_NAME, MILESTONE_ID);
ALTER TABLE MILESTONE ADD CONSTRAINT IDX_MILESTONE_FK0 FOREIGN KEY (USER_NAME, REPOSITORY_NAME) REFERENCES REPOSITORY (USER_NAME, REPOSITORY_NAME);
INSERT INTO ACCOUNT (
USER_NAME,
MAIL_ADDRESS,
PASSWORD,
ADMINISTRATOR,
URL,
REGISTERED_DATE,
UPDATED_DATE,
LAST_LOGIN_DATE
) VALUES (
'root',
'root@localhost',
'dc76e9f0c0006e8f919e0c515c66dbba3982f785',
true,
'https://github.com/takezoe/gitbucket',
SYSDATE,
SYSDATE,
NULL
);

View File

@@ -0,0 +1,11 @@
ALTER TABLE GROUP_MEMBER ADD COLUMN MANAGER BOOLEAN DEFAULT FALSE;
CREATE TABLE SSH_KEY (
USER_NAME VARCHAR(100) NOT NULL,
SSH_KEY_ID INT AUTO_INCREMENT,
TITLE VARCHAR(100) NOT NULL,
PUBLIC_KEY TEXT NOT NULL
);
ALTER TABLE SSH_KEY ADD CONSTRAINT IDX_SSH_KEY_PK PRIMARY KEY (USER_NAME, SSH_KEY_ID);
ALTER TABLE SSH_KEY ADD CONSTRAINT IDX_SSH_KEY_FK0 FOREIGN KEY (USER_NAME) REFERENCES ACCOUNT (USER_NAME);

View File

@@ -0,0 +1 @@
DROP TABLE COMMIT_LOG;

View File

@@ -0,0 +1,8 @@
CREATE TABLE WEB_HOOK (
USER_NAME VARCHAR(100) NOT NULL,
REPOSITORY_NAME VARCHAR(100) NOT NULL,
URL VARCHAR(200) NOT NULL
);
ALTER TABLE WEB_HOOK ADD CONSTRAINT IDX_WEB_HOOK_PK PRIMARY KEY (USER_NAME, REPOSITORY_NAME, URL);
ALTER TABLE WEB_HOOK ADD CONSTRAINT IDX_WEB_HOOK_FK0 FOREIGN KEY (USER_NAME, REPOSITORY_NAME) REFERENCES REPOSITORY (USER_NAME, REPOSITORY_NAME);

View File

@@ -0,0 +1,5 @@
ALTER TABLE ACCOUNT ADD COLUMN FULL_NAME VARCHAR(100);
UPDATE ACCOUNT SET FULL_NAME = USER_NAME WHERE FULL_NAME IS NULL;
ALTER TABLE ACCOUNT ALTER COLUMN FULL_NAME SET NOT NULL;

View File

@@ -0,0 +1 @@
ALTER TABLE ACCOUNT ADD COLUMN REMOVED BOOLEAN DEFAULT FALSE;

View File

@@ -0,0 +1,6 @@
CREATE TABLE PLUGIN (
PLUGIN_ID VARCHAR(100) NOT NULL,
VERSION VARCHAR(100) NOT NULL
);
ALTER TABLE PLUGIN ADD CONSTRAINT IDX_PLUGIN_PK PRIMARY KEY (PLUGIN_ID);

View File

@@ -0,0 +1,18 @@
CREATE TABLE COMMIT_COMMENT (
USER_NAME VARCHAR(100) NOT NULL,
REPOSITORY_NAME VARCHAR(100) NOT NULL,
COMMIT_ID VARCHAR(100) NOT NULL,
COMMENT_ID INT AUTO_INCREMENT,
COMMENTED_USER_NAME VARCHAR(100) NOT NULL,
CONTENT TEXT NOT NULL,
FILE_NAME NVARCHAR(100),
OLD_LINE_NUMBER INT,
NEW_LINE_NUMBER INT,
REGISTERED_DATE TIMESTAMP NOT NULL,
UPDATED_DATE TIMESTAMP NOT NULL,
PULL_REQUEST BOOLEAN NOT NULL
);
ALTER TABLE COMMIT_COMMENT ADD CONSTRAINT IDX_COMMIT_COMMENT_PK PRIMARY KEY (COMMENT_ID);
ALTER TABLE COMMIT_COMMENT ADD CONSTRAINT IDX_COMMIT_COMMENT_FK0 FOREIGN KEY (USER_NAME, REPOSITORY_NAME) REFERENCES REPOSITORY (USER_NAME, REPOSITORY_NAME);
ALTER TABLE COMMIT_COMMENT ADD CONSTRAINT IDX_COMMIT_COMMENT_1 UNIQUE (USER_NAME, REPOSITORY_NAME, COMMIT_ID, COMMENT_ID);

View File

@@ -0,0 +1 @@
ALTER TABLE COMMIT_COMMENT ALTER COLUMN FILE_NAME NVARCHAR(260);

View File

@@ -0,0 +1,42 @@
DROP TABLE IF EXISTS ACCESS_TOKEN;
CREATE TABLE ACCESS_TOKEN (
ACCESS_TOKEN_ID INT NOT NULL AUTO_INCREMENT,
TOKEN_HASH VARCHAR(40) NOT NULL,
USER_NAME VARCHAR(100) NOT NULL,
NOTE TEXT NOT NULL
);
ALTER TABLE ACCESS_TOKEN ADD CONSTRAINT IDX_ACCESS_TOKEN_PK PRIMARY KEY (ACCESS_TOKEN_ID);
ALTER TABLE ACCESS_TOKEN ADD CONSTRAINT IDX_ACCESS_TOKEN_FK0 FOREIGN KEY (USER_NAME) REFERENCES ACCOUNT (USER_NAME)
ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE ACCESS_TOKEN ADD CONSTRAINT IDX_ACCESS_TOKEN_TOKEN_HASH UNIQUE(TOKEN_HASH);
DROP TABLE IF EXISTS COMMIT_STATUS;
CREATE TABLE COMMIT_STATUS(
COMMIT_STATUS_ID INT AUTO_INCREMENT,
USER_NAME VARCHAR(100) NOT NULL,
REPOSITORY_NAME VARCHAR(100) NOT NULL,
COMMIT_ID VARCHAR(40) NOT NULL,
CONTEXT VARCHAR(255) NOT NULL, -- context is too long (maximum is 255 characters)
STATE VARCHAR(10) NOT NULL, -- pending, success, error, or failure
TARGET_URL VARCHAR(200),
DESCRIPTION TEXT,
CREATOR VARCHAR(100) NOT NULL,
REGISTERED_DATE TIMESTAMP NOT NULL, -- CREATED_AT
UPDATED_DATE TIMESTAMP NOT NULL -- UPDATED_AT
);
ALTER TABLE COMMIT_STATUS ADD CONSTRAINT IDX_COMMIT_STATUS_PK PRIMARY KEY (COMMIT_STATUS_ID);
ALTER TABLE COMMIT_STATUS ADD CONSTRAINT IDX_COMMIT_STATUS_1
UNIQUE (USER_NAME, REPOSITORY_NAME, COMMIT_ID, CONTEXT);
ALTER TABLE COMMIT_STATUS ADD CONSTRAINT IDX_COMMIT_STATUS_FK1
FOREIGN KEY (USER_NAME, REPOSITORY_NAME)
REFERENCES REPOSITORY (USER_NAME, REPOSITORY_NAME)
ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE COMMIT_STATUS ADD CONSTRAINT IDX_COMMIT_STATUS_FK2
FOREIGN KEY (USER_NAME) REFERENCES ACCOUNT (USER_NAME)
ON DELETE CASCADE ON UPDATE CASCADE;
ALTER TABLE COMMIT_STATUS ADD CONSTRAINT IDX_COMMIT_STATUS_FK3
FOREIGN KEY (CREATOR) REFERENCES ACCOUNT (USER_NAME)
ON DELETE CASCADE ON UPDATE CASCADE;

View File

@@ -1,17 +1,37 @@
import app._
import org.scalatra._
import gitbucket.core.controller._
import gitbucket.core.plugin.PluginRegistry
import gitbucket.core.servlet.{AccessTokenAuthenticationFilter, BasicAuthenticationFilter, TransactionFilter}
import gitbucket.core.util.Directory
import java.util.EnumSet
import javax.servlet._
import org.scalatra._
class ScalatraBootstrap extends LifeCycle {
override def init(context: ServletContext) {
// Register TransactionFilter and BasicAuthenticationFilter at first
context.addFilter("transactionFilter", new TransactionFilter)
context.getFilterRegistration("transactionFilter").addMappingForUrlPatterns(EnumSet.allOf(classOf[DispatcherType]), true, "/*")
context.addFilter("basicAuthenticationFilter", new BasicAuthenticationFilter)
context.getFilterRegistration("basicAuthenticationFilter").addMappingForUrlPatterns(EnumSet.allOf(classOf[DispatcherType]), true, "/git/*")
context.addFilter("accessTokenAuthenticationFilter", new AccessTokenAuthenticationFilter)
context.getFilterRegistration("accessTokenAuthenticationFilter").addMappingForUrlPatterns(EnumSet.allOf(classOf[DispatcherType]), true, "/api/v3/*")
// Register controllers
context.mount(new AnonymousAccessController, "/*")
PluginRegistry().getControllers.foreach { case (controller, path) =>
context.mount(controller, path)
}
context.mount(new IndexController, "/")
context.mount(new SearchController, "/")
context.mount(new FileUploadController, "/upload")
context.mount(new SignInController, "/*")
context.mount(new DashboardController, "/*")
context.mount(new UserManagementController, "/*")
context.mount(new SystemSettingsController, "/*")
context.mount(new CreateRepositoryController, "/*")
context.mount(new AccountController, "/*")
context.mount(new RepositoryViewerController, "/*")
context.mount(new WikiController, "/*")
@@ -21,9 +41,10 @@ class ScalatraBootstrap extends LifeCycle {
context.mount(new PullRequestsController, "/*")
context.mount(new RepositorySettingsController, "/*")
val dir = new java.io.File(_root_.util.Directory.GitBucketHome)
// Create GITBUCKET_HOME directory if it does not exist
val dir = new java.io.File(Directory.GitBucketHome)
if(!dir.exists){
dir.mkdirs()
}
}
}
}

View File

@@ -1,115 +0,0 @@
package app
import service._
import util.{FileUtil, OneselfAuthenticator}
import util.StringUtil._
import util.Directory._
import jp.sf.amateras.scalatra.forms._
import org.scalatra.FlashMapSupport
class AccountController extends AccountControllerBase
with SystemSettingsService with AccountService with RepositoryService with ActivityService
with OneselfAuthenticator
trait AccountControllerBase extends AccountManagementControllerBase with FlashMapSupport {
self: SystemSettingsService with AccountService with RepositoryService with ActivityService
with OneselfAuthenticator =>
case class AccountNewForm(userName: String, password: String,mailAddress: String,
url: Option[String], fileId: Option[String])
case class AccountEditForm(password: Option[String], mailAddress: String,
url: Option[String], fileId: Option[String], clearImage: Boolean)
val newForm = mapping(
"userName" -> trim(label("User name" , text(required, maxlength(100), identifier, uniqueUserName))),
"password" -> trim(label("Password" , text(required, maxlength(20)))),
"mailAddress" -> trim(label("Mail Address" , text(required, maxlength(100), uniqueMailAddress()))),
"url" -> trim(label("URL" , optional(text(maxlength(200))))),
"fileId" -> trim(label("File ID" , optional(text())))
)(AccountNewForm.apply)
val editForm = mapping(
"password" -> trim(label("Password" , optional(text(maxlength(20))))),
"mailAddress" -> trim(label("Mail Address" , text(required, maxlength(100), uniqueMailAddress("userName")))),
"url" -> trim(label("URL" , optional(text(maxlength(200))))),
"fileId" -> trim(label("File ID" , optional(text()))),
"clearImage" -> trim(label("Clear image" , boolean()))
)(AccountEditForm.apply)
/**
* Displays user information.
*/
get("/:userName") {
val userName = params("userName")
getAccountByUserName(userName).map { account =>
params.getOrElse("tab", "repositories") match {
// Public Activity
case "activity" =>
_root_.account.html.activity(account,
if(account.isGroupAccount) Nil else getGroupsByUserName(userName),
getActivitiesByUser(userName, true))
// Members
case "members" if(account.isGroupAccount) =>
_root_.account.html.members(account, getGroupMembers(account.userName))
// Repositories
case _ =>
_root_.account.html.repositories(account,
if(account.isGroupAccount) Nil else getGroupsByUserName(userName),
getVisibleRepositories(context.loginAccount, baseUrl, Some(userName)))
}
} getOrElse NotFound
}
get("/:userName/_avatar"){
val userName = params("userName")
getAccountByUserName(userName).flatMap(_.image).map { image =>
contentType = FileUtil.getMimeType(image)
new java.io.File(getUserUploadDir(userName), image)
} getOrElse {
contentType = "image/png"
Thread.currentThread.getContextClassLoader.getResourceAsStream("noimage.png")
}
}
get("/:userName/_edit")(oneselfOnly {
val userName = params("userName")
getAccountByUserName(userName).map(x => account.html.edit(Some(x), flash.get("info"))) getOrElse NotFound
})
post("/:userName/_edit", editForm)(oneselfOnly { form =>
val userName = params("userName")
getAccountByUserName(userName).map { account =>
updateAccount(account.copy(
password = form.password.map(sha1).getOrElse(account.password),
mailAddress = form.mailAddress,
url = form.url))
updateImage(userName, form.fileId, form.clearImage)
flash += "info" -> "Account information has been updated."
redirect(s"/${userName}/_edit")
} getOrElse NotFound
})
get("/register"){
if(loadSystemSettings().allowAccountRegistration){
if(context.loginAccount.isDefined){
redirect("/")
} else {
account.html.edit(None, None)
}
} else NotFound
}
post("/register", newForm){ form =>
if(loadSystemSettings().allowAccountRegistration){
createAccount(form.userName, sha1(form.password), form.mailAddress, false, form.url)
updateImage(form.userName, form.fileId, false)
redirect("/signin")
} else NotFound
}
}

View File

@@ -1,226 +0,0 @@
package app
import _root_.util.Directory._
import _root_.util.{FileUtil, Validations}
import org.scalatra._
import org.scalatra.json._
import org.json4s._
import jp.sf.amateras.scalatra.forms._
import org.apache.commons.io.FileUtils
import model.Account
import scala.Some
import service.AccountService
import javax.servlet.http.{HttpServletResponse, HttpSession, HttpServletRequest}
import java.text.SimpleDateFormat
import javax.servlet.{FilterChain, ServletResponse, ServletRequest}
/**
* Provides generic features for controller implementations.
*/
abstract class ControllerBase extends ScalatraFilter
with ClientSideValidationFormSupport with JacksonJsonSupport with Validations {
implicit val jsonFormats = DefaultFormats
// Don't set content type via Accept header.
override def format(implicit request: HttpServletRequest) = ""
override def doFilter(request: ServletRequest, response: ServletResponse, chain: FilterChain) {
val httpRequest = request.asInstanceOf[HttpServletRequest]
val httpResponse = response.asInstanceOf[HttpServletResponse]
val context = request.getServletContext.getContextPath
val path = httpRequest.getRequestURI.substring(context.length)
if(path.startsWith("/console/")){
val account = httpRequest.getSession.getAttribute("LOGIN_ACCOUNT").asInstanceOf[Account]
if(account == null){
// Redirect to login form
httpResponse.sendRedirect(context + "/signin?" + path)
} else if(account.isAdmin){
// H2 Console (administrators only)
chain.doFilter(request, response)
} else {
// Redirect to dashboard
httpResponse.sendRedirect(context + "/")
}
} else if(path.startsWith("/git/")){
// Git repository
chain.doFilter(request, response)
} else {
// Scalatra actions
super.doFilter(request, response, chain)
}
}
/**
* Returns the context object for the request.
*/
implicit def context: Context = Context(servletContext.getContextPath, LoginAccount, currentURL, request)
private def currentURL: String = {
val queryString = request.getQueryString
request.getRequestURI + (if(queryString != null) "?" + queryString else "")
}
private def LoginAccount: Option[Account] = {
session.get("LOGIN_ACCOUNT") match {
case Some(x: Account) => Some(x)
case _ => None
}
}
def ajaxGet(path : String)(action : => Any) : Route = {
super.get(path){
request.setAttribute("AJAX", "true")
action
}
}
override def ajaxGet[T](path : String, form : MappingValueType[T])(action : T => Any) : Route = {
super.ajaxGet(path, form){ form =>
request.setAttribute("AJAX", "true")
action(form)
}
}
def ajaxPost(path : String)(action : => Any) : Route = {
super.post(path){
request.setAttribute("AJAX", "true")
action
}
}
override def ajaxPost[T](path : String, form : MappingValueType[T])(action : T => Any) : Route = {
super.ajaxPost(path, form){ form =>
request.setAttribute("AJAX", "true")
action(form)
}
}
protected def NotFound() = {
if(request.getAttribute("AJAX") == null){
org.scalatra.NotFound(html.error("Not Found"))
} else {
org.scalatra.NotFound()
}
}
protected def Unauthorized()(implicit context: app.Context) = {
if(request.getAttribute("AJAX") == null){
if(context.loginAccount.isDefined){
org.scalatra.Unauthorized(redirect("/"))
} else {
if(request.getMethod.toUpperCase == "POST"){
org.scalatra.Unauthorized(redirect("/signin"))
} else {
org.scalatra.Unauthorized(redirect("/signin?redirect=" + currentURL))
}
}
} else {
org.scalatra.Unauthorized()
}
}
protected def baseUrl = {
val url = request.getRequestURL.toString
url.substring(0, url.length - (request.getRequestURI.length - request.getContextPath.length))
}
}
/**
* Context object for the current request.
*/
case class Context(path: String, loginAccount: Option[Account], currentUrl: String, request: HttpServletRequest){
def redirectUrl = {
if(request.getParameter("redirect") != null){
request.getParameter("redirect")
} else {
currentUrl
}
}
/**
* Get object from cache.
*
* If object has not been cached with the specified key then retrieves by given action.
* Cached object are available during a request.
*/
def cache[A](key: String)(action: => A): A = {
Option(request.getAttribute("cache." + key).asInstanceOf[A]).getOrElse {
val newObject = action
request.setAttribute("cache." + key, newObject)
newObject
}
}
}
/**
* Base trait for controllers which manages account information.
*/
trait AccountManagementControllerBase extends ControllerBase with FileUploadControllerBase {
self: AccountService =>
protected def updateImage(userName: String, fileId: Option[String], clearImage: Boolean): Unit = {
if(clearImage){
getAccountByUserName(userName).flatMap(_.image).map { image =>
new java.io.File(getUserUploadDir(userName), image).delete()
updateAvatarImage(userName, None)
}
} else {
fileId.map { fileId =>
val filename = "avatar." + FileUtil.getExtension(getUploadedFilename(fileId).get)
FileUtils.moveFile(
getTemporaryFile(fileId),
new java.io.File(getUserUploadDir(userName), filename)
)
updateAvatarImage(userName, Some(filename))
}
}
}
protected def uniqueUserName: Constraint = new Constraint(){
override def validate(name: String, value: String): Option[String] =
getAccountByUserName(value).map { _ => "User already exists." }
}
protected def uniqueMailAddress(paramName: String = ""): Constraint = new Constraint(){
override def validate(name: String, value: String, params: Map[String, String]): Option[String] =
getAccountByMailAddress(value)
.filter { x => if(paramName.isEmpty) true else Some(x.userName) != params.get(paramName) }
.map { _ => "Mail address is already registered." }
}
}
/**
* Base trait for controllers which needs file uploading feature.
*/
trait FileUploadControllerBase {
def generateFileId: String =
new SimpleDateFormat("yyyyMMddHHmmSSsss").format(new java.util.Date(System.currentTimeMillis))
def TemporaryDir(implicit session: HttpSession): java.io.File =
new java.io.File(GitBucketHome, s"tmp/_upload/${session.getId}")
def getTemporaryFile(fileId: String)(implicit session: HttpSession): java.io.File =
new java.io.File(TemporaryDir, fileId)
// def removeTemporaryFile(fileId: String)(implicit session: HttpSession): Unit =
// getTemporaryFile(fileId).delete()
def removeTemporaryFiles()(implicit session: HttpSession): Unit =
FileUtils.deleteDirectory(TemporaryDir)
def getUploadedFilename(fileId: String)(implicit session: HttpSession): Option[String] = {
val filename = Option(session.getAttribute("upload_" + fileId).asInstanceOf[String])
if(filename.isDefined){
session.removeAttribute("upload_" + fileId)
}
filename
}
}

View File

@@ -1,197 +0,0 @@
package app
import util.Directory._
import util._
import service._
import java.io.File
import org.eclipse.jgit.api.Git
import org.apache.commons.io._
import jp.sf.amateras.scalatra.forms._
import org.eclipse.jgit.lib.PersonIdent
import scala.Some
class CreateRepositoryController extends CreateRepositoryControllerBase
with RepositoryService with AccountService with WikiService with LabelsService with ActivityService
with UsersAuthenticator with ReadableUsersAuthenticator
/**
* Creates new repository.
*/
trait CreateRepositoryControllerBase extends ControllerBase {
self: RepositoryService with AccountService with WikiService with LabelsService with ActivityService
with UsersAuthenticator with ReadableUsersAuthenticator =>
case class RepositoryCreationForm(owner: String, name: String, description: Option[String], isPrivate: Boolean, createReadme: Boolean)
case class ForkRepositoryForm(owner: String, name: String)
val newForm = mapping(
"owner" -> trim(label("Owner" , text(required, maxlength(40), identifier, existsAccount))),
"name" -> trim(label("Repository name", text(required, maxlength(40), identifier, unique))),
"description" -> trim(label("Description" , optional(text()))),
"isPrivate" -> trim(label("Repository Type", boolean())),
"createReadme" -> trim(label("Create README" , boolean()))
)(RepositoryCreationForm.apply)
val forkForm = mapping(
"owner" -> trim(label("Repository owner", text(required))),
"name" -> trim(label("Repository name", text(required)))
)(ForkRepositoryForm.apply)
/**
* Show the new repository form.
*/
get("/new")(usersOnly {
html.newrepo(getGroupsByUserName(context.loginAccount.get.userName))
})
/**
* Create new repository.
*/
post("/new", newForm)(usersOnly { form =>
LockUtil.lock(s"${form.owner}/${form.name}/create"){
if(getRepository(form.owner, form.name, baseUrl).isEmpty){
val ownerAccount = getAccountByUserName(form.owner).get
val loginAccount = context.loginAccount.get
val loginUserName = loginAccount.userName
// Insert to the database at first
createRepository(form.name, form.owner, form.description, form.isPrivate)
// Add collaborators for group repository
if(ownerAccount.isGroupAccount){
getGroupMembers(form.owner).foreach { userName =>
addCollaborator(form.owner, form.name, userName)
}
}
// Insert default labels
insertDefaultLabels(form.owner, form.name)
// Create the actual repository
val gitdir = getRepositoryDir(form.owner, form.name)
JGitUtil.initRepository(gitdir)
if(form.createReadme){
val tmpdir = getInitRepositoryDir(form.owner, form.name)
try {
// Clone the repository
Git.cloneRepository.setURI(gitdir.toURI.toString).setDirectory(tmpdir).call
// Create README.md
FileUtils.writeStringToFile(new File(tmpdir, "README.md"),
if(form.description.nonEmpty){
form.name + "\n" +
"===============\n" +
"\n" +
form.description.get
} else {
form.name + "\n" +
"===============\n"
}, "UTF-8")
val git = Git.open(tmpdir)
git.add.addFilepattern("README.md").call
git.commit
.setCommitter(new PersonIdent(loginUserName, loginAccount.mailAddress))
.setMessage("Initial commit").call
git.push.call
} finally {
FileUtils.deleteDirectory(tmpdir)
}
}
// Create Wiki repository
createWikiRepository(loginAccount, form.owner, form.name)
// Record activity
recordCreateRepositoryActivity(form.owner, form.name, loginUserName)
}
// redirect to the repository
redirect(s"/${form.owner}/${form.name}")
}
})
get("/:owner/:repository/fork")(readableUsersOnly { repository =>
val loginAccount = context.loginAccount.get
val loginUserName = loginAccount.userName
LockUtil.lock(s"${loginUserName}/${repository.name}/create"){
if(getRepository(loginUserName, repository.name, baseUrl).isEmpty){
// Insert to the database at first
val originUserName = repository.repository.originUserName.getOrElse(repository.owner)
val originRepositoryName = repository.repository.originRepositoryName.getOrElse(repository.name)
createRepository(
repositoryName = repository.name,
userName = loginUserName,
description = repository.repository.description,
isPrivate = repository.repository.isPrivate,
originRepositoryName = Some(originRepositoryName),
originUserName = Some(originUserName),
parentRepositoryName = Some(repository.name),
parentUserName = Some(repository.owner)
)
// Insert default labels
insertDefaultLabels(loginUserName, repository.name)
// clone repository actually
JGitUtil.cloneRepository(
getRepositoryDir(repository.owner, repository.name),
getRepositoryDir(loginUserName, repository.name))
// Create Wiki repository
JGitUtil.cloneRepository(
getWikiRepositoryDir(repository.owner, repository.name),
getWikiRepositoryDir(loginUserName, repository.name))
// insert commit id
JGitUtil.withGit(getRepositoryDir(loginUserName, repository.name)){ git =>
JGitUtil.getRepositoryInfo(loginUserName, repository.name, baseUrl).branchList.foreach { branch =>
JGitUtil.getCommitLog(git, branch) match {
case Right((commits, _)) => commits.foreach { commit =>
if(!existsCommitId(loginUserName, repository.name, commit.id)){
insertCommitId(loginUserName, repository.name, commit.id)
}
}
case Left(_) => ???
}
}
}
// Record activity
recordForkActivity(repository.owner, repository.name, loginUserName)
}
// redirect to the repository
redirect("/%s/%s".format(loginUserName, repository.name))
}
})
private def insertDefaultLabels(userName: String, repositoryName: String): Unit = {
createLabel(userName, repositoryName, "bug", "fc2929")
createLabel(userName, repositoryName, "duplicate", "cccccc")
createLabel(userName, repositoryName, "enhancement", "84b6eb")
createLabel(userName, repositoryName, "invalid", "e6e6e6")
createLabel(userName, repositoryName, "question", "cc317c")
createLabel(userName, repositoryName, "wontfix", "ffffff")
}
private def existsAccount: Constraint = new Constraint(){
override def validate(name: String, value: String): Option[String] =
if(getAccountByUserName(value).isEmpty) Some("User or group does not exist.") else None
}
/**
* Duplicate check for the repository name.
*/
private def unique: Constraint = new Constraint(){
override def validate(name: String, value: String, params: Map[String, String]): Option[String] =
params.get("owner").flatMap { userName =>
getRepositoryNamesOfUser(userName).find(_ == value).map(_ => "Repository already exists.")
}
}
}

View File

@@ -1,119 +0,0 @@
package app
import service._
import util.UsersAuthenticator
class DashboardController extends DashboardControllerBase
with IssuesService with PullRequestService with RepositoryService with AccountService
with UsersAuthenticator
trait DashboardControllerBase extends ControllerBase {
self: IssuesService with PullRequestService with RepositoryService with UsersAuthenticator =>
get("/dashboard/issues/repos")(usersOnly {
searchIssues("all")
})
get("/dashboard/issues/assigned")(usersOnly {
searchIssues("assigned")
})
get("/dashboard/issues/created_by")(usersOnly {
searchIssues("created_by")
})
get("/dashboard/pulls")(usersOnly {
searchPullRequests("created_by", None)
})
get("/dashboard/pulls/owned")(usersOnly {
searchPullRequests("created_by", None)
})
get("/dashboard/pulls/public")(usersOnly {
searchPullRequests("not_created_by", None)
})
get("/dashboard/pulls/for/:owner/:repository")(usersOnly {
searchPullRequests("all", Some(params("owner") + "/" + params("repository")))
})
private def searchIssues(filter: String) = {
import IssuesService._
// condition
val sessionKey = "dashboard/issues"
val condition = if(request.getQueryString == null)
session.get(sessionKey).getOrElse(IssueSearchCondition()).asInstanceOf[IssueSearchCondition]
else IssueSearchCondition(request)
session.put(sessionKey, condition)
val userName = context.loginAccount.get.userName
val repositories = getUserRepositories(userName, baseUrl).map(repo => repo.owner -> repo.name)
val filterUser = Map(filter -> userName)
val page = IssueSearchCondition.page(request)
//
dashboard.html.issues(
issues.html.listparts(
searchIssue(condition, filterUser, false, (page - 1) * IssueLimit, IssueLimit, repositories: _*),
page,
countIssue(condition.copy(state = "open"), filterUser, false, repositories: _*),
countIssue(condition.copy(state = "closed"), filterUser, false, repositories: _*),
condition),
countIssue(condition, Map.empty, false, repositories: _*),
countIssue(condition, Map("assigned" -> userName), false, repositories: _*),
countIssue(condition, Map("created_by" -> userName), false, repositories: _*),
countIssueGroupByRepository(condition, filterUser, false, repositories: _*),
condition,
filter)
}
private def searchPullRequests(filter: String, repository: Option[String]) = {
import IssuesService._
import PullRequestService._
// condition
val sessionKey = "dashboard/pulls"
val condition = {
if(request.getQueryString == null)
session.get(sessionKey).getOrElse(IssueSearchCondition()).asInstanceOf[IssueSearchCondition]
else
IssueSearchCondition(request)
}.copy(repo = repository)
session.put(sessionKey, condition)
val userName = context.loginAccount.get.userName
val repositories = getUserRepositories(userName, baseUrl).map(repo => repo.owner -> repo.name)
val filterUser = Map(filter -> userName)
val page = IssueSearchCondition.page(request)
val counts = countIssueGroupByRepository(
IssueSearchCondition().copy(state = condition.state), Map.empty, true, repositories: _*)
getRepositoryNamesOfUser(userName).map { repoName =>
(userName, repoName, counts.collectFirst { case (_, repoName, count) => count })
}
dashboard.html.pulls(
pulls.html.listparts(
searchIssue(condition, filterUser, true, (page - 1) * PullRequestLimit, PullRequestLimit, repositories: _*),
page,
countIssue(condition.copy(state = "open"), filterUser, true, repositories: _*),
countIssue(condition.copy(state = "closed"), filterUser, true, repositories: _*),
condition,
None,
false),
getPullRequestCountGroupByUser(condition.state == "closed", userName, None),
getRepositoryNamesOfUser(userName).map { RepoName =>
(userName, RepoName, counts.collectFirst { case (_, RepoName, count) => count }.getOrElse(0))
}.sortBy(_._3).reverse,
condition,
filter)
}
}

View File

@@ -1,32 +0,0 @@
package app
import util.{FileUtil}
import org.scalatra._
import org.scalatra.servlet.{MultipartConfig, FileUploadSupport}
import org.apache.commons.io.FileUtils
/**
* Provides Ajax based file upload functionality.
*
* This servlet saves uploaded file as temporary file and returns the unique id.
* You can get uploaded file using [[app.FileUploadControllerBase#getTemporaryFile()]] with this id.
*/
class FileUploadController extends ScalatraServlet
with FileUploadSupport with FlashMapSupport with FileUploadControllerBase {
configureMultipartHandling(MultipartConfig(maxFileSize = Some(3 * 1024 * 1024)))
post("/image"){
fileParams.get("file") match {
case Some(file) if(FileUtil.isImage(file.name)) => {
val fileId = generateFileId
FileUtils.writeByteArrayToFile(getTemporaryFile(fileId), file.get)
session += "upload_" + fileId -> file.name
Ok(fileId)
}
case None => BadRequest
}
}
}

View File

@@ -1,38 +0,0 @@
package app
import util._
import service._
import jp.sf.amateras.scalatra.forms._
class IndexController extends IndexControllerBase
with RepositoryService with SystemSettingsService with ActivityService with AccountService
with UsersAuthenticator
trait IndexControllerBase extends ControllerBase {
self: RepositoryService with SystemSettingsService with ActivityService with AccountService
with UsersAuthenticator =>
get("/"){
val loginAccount = context.loginAccount
html.index(getRecentActivities(),
getVisibleRepositories(loginAccount, baseUrl),
loadSystemSettings(),
loginAccount.map{ account => getUserRepositories(account.userName, baseUrl) }.getOrElse(Nil)
)
}
/**
* JSON API for collaborator completion.
*
* TODO Move to other controller?
*/
get("/_user/proposals")(usersOnly {
contentType = formats("json")
org.json4s.jackson.Serialization.write(
Map("options" -> getAllUsers.filter(!_.isGroupAccount).map(_.userName).toArray)
)
})
}

View File

@@ -1,361 +0,0 @@
package app
import jp.sf.amateras.scalatra.forms._
import service._
import IssuesService._
import util.{CollaboratorsAuthenticator, ReferrerAuthenticator, ReadableUsersAuthenticator, Notifier}
import org.scalatra.Ok
class IssuesController extends IssuesControllerBase
with IssuesService with RepositoryService with AccountService with LabelsService with MilestonesService with ActivityService
with ReadableUsersAuthenticator with ReferrerAuthenticator with CollaboratorsAuthenticator
trait IssuesControllerBase extends ControllerBase {
self: IssuesService with RepositoryService with LabelsService with MilestonesService with ActivityService
with ReadableUsersAuthenticator with ReferrerAuthenticator with CollaboratorsAuthenticator =>
case class IssueCreateForm(title: String, content: Option[String],
assignedUserName: Option[String], milestoneId: Option[Int], labelNames: Option[String])
case class IssueEditForm(title: String, content: Option[String])
case class CommentForm(issueId: Int, content: String)
case class IssueStateForm(issueId: Int, content: Option[String])
val issueCreateForm = mapping(
"title" -> trim(label("Title", text(required))),
"content" -> trim(optional(text())),
"assignedUserName" -> trim(optional(text())),
"milestoneId" -> trim(optional(number())),
"labelNames" -> trim(optional(text()))
)(IssueCreateForm.apply)
val issueEditForm = mapping(
"title" -> trim(label("Title", text(required))),
"content" -> trim(optional(text()))
)(IssueEditForm.apply)
val commentForm = mapping(
"issueId" -> label("Issue Id", number()),
"content" -> trim(label("Comment", text(required)))
)(CommentForm.apply)
val issueStateForm = mapping(
"issueId" -> label("Issue Id", number()),
"content" -> trim(optional(text()))
)(IssueStateForm.apply)
get("/:owner/:repository/issues")(referrersOnly {
searchIssues("all", _)
})
get("/:owner/:repository/issues/assigned/:userName")(referrersOnly {
searchIssues("assigned", _)
})
get("/:owner/:repository/issues/created_by/:userName")(referrersOnly {
searchIssues("created_by", _)
})
get("/:owner/:repository/issues/:id")(referrersOnly { repository =>
val owner = repository.owner
val name = repository.name
val issueId = params("id")
getIssue(owner, name, issueId) map {
issues.html.issue(
_,
getComments(owner, name, issueId.toInt),
getIssueLabels(owner, name, issueId.toInt),
(getCollaborators(owner, name) :+ owner).sorted,
getMilestonesWithIssueCount(owner, name),
getLabels(owner, name),
hasWritePermission(owner, name, context.loginAccount),
repository)
} getOrElse NotFound
})
get("/:owner/:repository/issues/new")(readableUsersOnly { repository =>
val owner = repository.owner
val name = repository.name
issues.html.create(
(getCollaborators(owner, name) :+ owner).sorted,
getMilestones(owner, name),
getLabels(owner, name),
hasWritePermission(owner, name, context.loginAccount),
repository)
})
post("/:owner/:repository/issues/new", issueCreateForm)(readableUsersOnly { (form, repository) =>
val owner = repository.owner
val name = repository.name
val writable = hasWritePermission(owner, name, context.loginAccount)
val userName = context.loginAccount.get.userName
// insert issue
val issueId = createIssue(owner, name, userName, form.title, form.content,
if(writable) form.assignedUserName else None,
if(writable) form.milestoneId else None)
// insert labels
if(writable){
form.labelNames.map { value =>
val labels = getLabels(owner, name)
value.split(",").foreach { labelName =>
labels.find(_.labelName == labelName).map { label =>
registerIssueLabel(owner, name, issueId, label.labelId)
}
}
}
}
// record activity
recordCreateIssueActivity(owner, name, userName, issueId, form.title)
// notifications
Notifier().toNotify(repository, issueId, form.content.getOrElse("")){
Notifier.msgIssue(s"${baseUrl}/${owner}/${name}/issues/${issueId}")
}
redirect(s"/${owner}/${name}/issues/${issueId}")
})
ajaxPost("/:owner/:repository/issues/edit/:id", issueEditForm)(readableUsersOnly { (form, repository) =>
val owner = repository.owner
val name = repository.name
getIssue(owner, name, params("id")).map { issue =>
if(isEditable(owner, name, issue.openedUserName)){
updateIssue(owner, name, issue.issueId, form.title, form.content)
redirect(s"/${owner}/${name}/issues/_data/${issue.issueId}")
} else Unauthorized
} getOrElse NotFound
})
post("/:owner/:repository/issue_comments/new", commentForm)(readableUsersOnly { (form, repository) =>
handleComment(form.issueId, Some(form.content), repository)() map { case (issue, id) =>
redirect(s"/${repository.owner}/${repository.name}/${
if(issue.isPullRequest) "pull" else "issues"}/${form.issueId}#comment-${id}")
} getOrElse NotFound
})
post("/:owner/:repository/issue_comments/state", issueStateForm)(readableUsersOnly { (form, repository) =>
handleComment(form.issueId, form.content, repository)() map { case (issue, id) =>
redirect(s"/${repository.owner}/${repository.name}/${
if(issue.isPullRequest) "pull" else "issues"}/${form.issueId}#comment-${id}")
} getOrElse NotFound
})
ajaxPost("/:owner/:repository/issue_comments/edit/:id", commentForm)(readableUsersOnly { (form, repository) =>
val owner = repository.owner
val name = repository.name
getComment(owner, name, params("id")).map { comment =>
if(isEditable(owner, name, comment.commentedUserName)){
updateComment(comment.commentId, form.content)
redirect(s"/${owner}/${name}/issue_comments/_data/${comment.commentId}")
} else Unauthorized
} getOrElse NotFound
})
ajaxGet("/:owner/:repository/issues/_data/:id")(readableUsersOnly { repository =>
getIssue(repository.owner, repository.name, params("id")) map { x =>
if(isEditable(x.userName, x.repositoryName, x.openedUserName)){
params.get("dataType") collect {
case t if t == "html" => issues.html.editissue(
x.title, x.content, x.issueId, x.userName, x.repositoryName)
} getOrElse {
contentType = formats("json")
org.json4s.jackson.Serialization.write(
Map("title" -> x.title,
"content" -> view.Markdown.toHtml(x.content getOrElse "No description given.",
repository, false, true)
))
}
} else Unauthorized
} getOrElse NotFound
})
ajaxGet("/:owner/:repository/issue_comments/_data/:id")(readableUsersOnly { repository =>
getComment(repository.owner, repository.name, params("id")) map { x =>
if(isEditable(x.userName, x.repositoryName, x.commentedUserName)){
params.get("dataType") collect {
case t if t == "html" => issues.html.editcomment(
x.content, x.commentId, x.userName, x.repositoryName)
} getOrElse {
contentType = formats("json")
org.json4s.jackson.Serialization.write(
Map("content" -> view.Markdown.toHtml(x.content,
repository, false, true)
))
}
} else Unauthorized
} getOrElse NotFound
})
ajaxPost("/:owner/:repository/issues/:id/label/new")(collaboratorsOnly { repository =>
val issueId = params("id").toInt
registerIssueLabel(repository.owner, repository.name, issueId, params("labelId").toInt)
issues.html.labellist(getIssueLabels(repository.owner, repository.name, issueId))
})
ajaxPost("/:owner/:repository/issues/:id/label/delete")(collaboratorsOnly { repository =>
val issueId = params("id").toInt
deleteIssueLabel(repository.owner, repository.name, issueId, params("labelId").toInt)
issues.html.labellist(getIssueLabels(repository.owner, repository.name, issueId))
})
ajaxPost("/:owner/:repository/issues/:id/assign")(collaboratorsOnly { repository =>
updateAssignedUserName(repository.owner, repository.name, params("id").toInt, assignedUserName("assignedUserName"))
Ok("updated")
})
ajaxPost("/:owner/:repository/issues/:id/milestone")(collaboratorsOnly { repository =>
updateMilestoneId(repository.owner, repository.name, params("id").toInt, milestoneId("milestoneId"))
milestoneId("milestoneId").map { milestoneId =>
getMilestonesWithIssueCount(repository.owner, repository.name)
.find(_._1.milestoneId == milestoneId).map { case (_, openCount, closeCount) =>
issues.milestones.html.progress(openCount + closeCount, closeCount, false)
} getOrElse NotFound
} getOrElse Ok()
})
post("/:owner/:repository/issues/batchedit/state")(collaboratorsOnly { repository =>
val action = params.get("value")
executeBatch(repository) {
handleComment(_, None, repository)( _ => action)
}
})
post("/:owner/:repository/issues/batchedit/label")(collaboratorsOnly { repository =>
val labelId = params("value").toInt
executeBatch(repository) { issueId =>
getIssueLabel(repository.owner, repository.name, issueId, labelId) getOrElse {
registerIssueLabel(repository.owner, repository.name, issueId, labelId)
}
}
})
post("/:owner/:repository/issues/batchedit/assign")(collaboratorsOnly { repository =>
val value = assignedUserName("value")
executeBatch(repository) {
updateAssignedUserName(repository.owner, repository.name, _, value)
}
})
post("/:owner/:repository/issues/batchedit/milestone")(collaboratorsOnly { repository =>
val value = milestoneId("value")
executeBatch(repository) {
updateMilestoneId(repository.owner, repository.name, _, value)
}
})
val assignedUserName = (key: String) => params.get(key) filter (_.trim != "")
val milestoneId = (key: String) => params.get(key) collect { case x if x.trim != "" => x.toInt }
private def isEditable(owner: String, repository: String, author: String)(implicit context: app.Context): Boolean =
hasWritePermission(owner, repository, context.loginAccount) || author == context.loginAccount.get.userName
private def executeBatch(repository: RepositoryService.RepositoryInfo)(execute: Int => Unit) = {
params("checked").split(',') map(_.toInt) foreach execute
redirect(s"/${repository.owner}/${repository.name}/issues")
}
/**
* @see [[https://github.com/takezoe/gitbucket/wiki/CommentAction]]
*/
private def handleComment(issueId: Int, content: Option[String], repository: RepositoryService.RepositoryInfo)
(getAction: model.Issue => Option[String] =
p1 => params.get("action").filter(_ => isEditable(p1.userName, p1.repositoryName, p1.openedUserName))) = {
val owner = repository.owner
val name = repository.name
val userName = context.loginAccount.get.userName
getIssue(owner, name, issueId.toString) map { issue =>
val (action, recordActivity) =
getAction(issue)
.collect {
case "close" => true -> (Some("close") ->
Some(if(issue.isPullRequest) recordClosePullRequestActivity _ else recordCloseIssueActivity _))
case "reopen" => false -> (Some("reopen") ->
Some(recordReopenIssueActivity _))
}
.map { case (closed, t) =>
updateClosed(owner, name, issueId, closed)
t
}
.getOrElse(None -> None)
val commentId = content
.map ( _ -> action.map( _ + "_comment" ).getOrElse("comment") )
.getOrElse ( action.get.capitalize -> action.get )
match {
case (content, action) => createComment(owner, name, userName, issueId, content, action)
}
// record activity
content foreach {
(if(issue.isPullRequest) recordCommentPullRequestActivity _ else recordCommentIssueActivity _)
(owner, name, userName, issueId, _)
}
recordActivity foreach ( _ (owner, name, userName, issueId, issue.title) )
// notifications
Notifier() match {
case f =>
content foreach {
f.toNotify(repository, issueId, _){
Notifier.msgComment(s"${baseUrl}/${owner}/${name}/${
if(issue.isPullRequest) "pull" else "issues"}/${issueId}#comment-${commentId}")
}
}
action foreach {
f.toNotify(repository, issueId, _){
Notifier.msgStatus(s"${baseUrl}/${owner}/${name}/issues/${issueId}")
}
}
}
issue -> commentId
}
}
private def searchIssues(filter: String, repository: RepositoryService.RepositoryInfo) = {
val owner = repository.owner
val repoName = repository.name
val filterUser = Map(filter -> params.getOrElse("userName", ""))
val page = IssueSearchCondition.page(request)
val sessionKey = s"${owner}/${repoName}/issues"
// retrieve search condition
val condition = if(request.getQueryString == null){
session.get(sessionKey).getOrElse(IssueSearchCondition()).asInstanceOf[IssueSearchCondition]
} else IssueSearchCondition(request)
session.put(sessionKey, condition)
issues.html.list(
searchIssue(condition, filterUser, false, (page - 1) * IssueLimit, IssueLimit, owner -> repoName),
page,
(getCollaborators(owner, repoName) :+ owner).sorted,
getMilestones(owner, repoName),
getLabels(owner, repoName),
countIssue(condition.copy(state = "open"), filterUser, false, owner -> repoName),
countIssue(condition.copy(state = "closed"), filterUser, false, owner -> repoName),
countIssue(condition, Map.empty, false, owner -> repoName),
context.loginAccount.map(x => countIssue(condition, Map("assigned" -> x.userName), false, owner -> repoName)),
context.loginAccount.map(x => countIssue(condition, Map("created_by" -> x.userName), false, owner -> repoName)),
countIssueGroupByLabels(owner, repoName, condition, filterUser),
condition,
filter,
repository,
hasWritePermission(owner, repoName, context.loginAccount))
}
}

View File

@@ -1,64 +0,0 @@
package app
import jp.sf.amateras.scalatra.forms._
import service._
import util.CollaboratorsAuthenticator
class LabelsController extends LabelsControllerBase
with LabelsService with RepositoryService with AccountService with CollaboratorsAuthenticator
trait LabelsControllerBase extends ControllerBase {
self: LabelsService with RepositoryService with CollaboratorsAuthenticator =>
case class LabelForm(labelName: String, color: String)
val newForm = mapping(
"newLabelName" -> trim(label("Label name", text(required, labelName, maxlength(100)))),
"newColor" -> trim(label("Color", text(required, color)))
)(LabelForm.apply)
val editForm = mapping(
"editLabelName" -> trim(label("Label name", text(required, labelName, maxlength(100)))),
"editColor" -> trim(label("Color", text(required, color)))
)(LabelForm.apply)
post("/:owner/:repository/issues/label/new", newForm)(collaboratorsOnly { (form, repository) =>
createLabel(repository.owner, repository.name, form.labelName, form.color.substring(1))
redirect(s"/${repository.owner}/${repository.name}/issues")
})
ajaxGet("/:owner/:repository/issues/label/edit")(collaboratorsOnly { repository =>
issues.labels.html.editlist(getLabels(repository.owner, repository.name), repository)
})
ajaxGet("/:owner/:repository/issues/label/:labelId/edit")(collaboratorsOnly { repository =>
getLabel(repository.owner, repository.name, params("labelId").toInt).map { label =>
issues.labels.html.edit(Some(label), repository)
} getOrElse NotFound()
})
ajaxPost("/:owner/:repository/issues/label/:labelId/edit", editForm)(collaboratorsOnly { (form, repository) =>
updateLabel(repository.owner, repository.name, params("labelId").toInt, form.labelName, form.color.substring(1))
issues.labels.html.editlist(getLabels(repository.owner, repository.name), repository)
})
ajaxGet("/:owner/:repository/issues/label/:labelId/delete")(collaboratorsOnly { repository =>
deleteLabel(repository.owner, repository.name, params("labelId").toInt)
issues.labels.html.editlist(getLabels(repository.owner, repository.name), repository)
})
/**
* Constraint for the identifier such as user name, repository name or page name.
*/
private def labelName: Constraint = new Constraint(){
override def validate(name: String, value: String): Option[String] =
if(!value.matches("^[^,]+$")){
Some(s"${name} contains invalid character.")
} else if(value.startsWith("_") || value.startsWith("-")){
Some(s"${name} starts with invalid character.")
} else {
None
}
}
}

View File

@@ -1,415 +0,0 @@
package app
import util.{LockUtil, CollaboratorsAuthenticator, JGitUtil, ReferrerAuthenticator, Notifier}
import util.Directory._
import util.Implicits._
import service._
import org.eclipse.jgit.api.Git
import jp.sf.amateras.scalatra.forms._
import org.eclipse.jgit.transport.RefSpec
import org.apache.commons.io.FileUtils
import scala.collection.JavaConverters._
import org.eclipse.jgit.lib.PersonIdent
import org.eclipse.jgit.api.MergeCommand.FastForwardMode
import service.IssuesService._
import service.PullRequestService._
import util.JGitUtil.DiffInfo
import scala.Some
import service.RepositoryService.RepositoryTreeNode
import util.JGitUtil.CommitInfo
class PullRequestsController extends PullRequestsControllerBase
with RepositoryService with AccountService with IssuesService with PullRequestService with MilestonesService with ActivityService
with ReferrerAuthenticator with CollaboratorsAuthenticator
trait PullRequestsControllerBase extends ControllerBase {
self: RepositoryService with IssuesService with MilestonesService with ActivityService with PullRequestService
with ReferrerAuthenticator with CollaboratorsAuthenticator =>
val pullRequestForm = mapping(
"title" -> trim(label("Title" , text(required, maxlength(100)))),
"content" -> trim(label("Content", optional(text()))),
"targetUserName" -> trim(text(required, maxlength(100))),
"targetBranch" -> trim(text(required, maxlength(100))),
"requestUserName" -> trim(text(required, maxlength(100))),
"requestBranch" -> trim(text(required, maxlength(100))),
"commitIdFrom" -> trim(text(required, maxlength(40))),
"commitIdTo" -> trim(text(required, maxlength(40)))
)(PullRequestForm.apply)
val mergeForm = mapping(
"message" -> trim(label("Message", text(required)))
)(MergeForm.apply)
case class PullRequestForm(
title: String,
content: Option[String],
targetUserName: String,
targetBranch: String,
requestUserName: String,
requestBranch: String,
commitIdFrom: String,
commitIdTo: String)
case class MergeForm(message: String)
get("/:owner/:repository/pulls")(referrersOnly { repository =>
searchPullRequests(None, repository)
})
get("/:owner/:repository/pulls/:userName")(referrersOnly { repository =>
searchPullRequests(Some(params("userName")), repository)
})
get("/:owner/:repository/pull/:id")(referrersOnly { repository =>
val owner = repository.owner
val name = repository.name
val issueId = params("id").toInt
getPullRequest(owner, name, issueId) map { case(issue, pullreq) =>
JGitUtil.withGit(getRepositoryDir(owner, name)){ git =>
val requestCommitId = git.getRepository.resolve(pullreq.requestBranch)
val (commits, diffs) =
getRequestCompareInfo(owner, name, pullreq.commitIdFrom, owner, name, pullreq.commitIdTo)
pulls.html.pullreq(
issue, pullreq,
getComments(owner, name, issueId.toInt),
(getCollaborators(owner, name) :+ owner).sorted,
getMilestonesWithIssueCount(owner, name),
commits,
diffs,
if(issue.closed){
false
} else {
checkConflict(owner, name, pullreq.branch, owner, name, pullreq.requestBranch)
},
hasWritePermission(owner, name, context.loginAccount),
repository,
s"${baseUrl}${context.path}/git/${pullreq.requestUserName}/${pullreq.requestRepositoryName}.git")
}
} getOrElse NotFound
})
post("/:owner/:repository/pull/:id/merge", mergeForm)(collaboratorsOnly { (form, repository) =>
LockUtil.lock(s"${repository.owner}/${repository.name}/merge"){
val issueId = params("id").toInt
getPullRequest(repository.owner, repository.name, issueId).map { case (issue, pullreq) =>
val remote = getRepositoryDir(repository.owner, repository.name)
val tmpdir = new java.io.File(getTemporaryDir(repository.owner, repository.name), s"merge-${issueId}")
val git = Git.cloneRepository.setDirectory(tmpdir).setURI(remote.toURI.toString).setBranch(pullreq.branch).call
try {
// mark issue as merged and close.
val loginAccount = context.loginAccount.get
createComment(repository.owner, repository.name, loginAccount.userName, issueId, form.message, "merge")
createComment(repository.owner, repository.name, loginAccount.userName, issueId, "Close", "close")
updateClosed(repository.owner, repository.name, issueId, true)
// record activity
recordMergeActivity(repository.owner, repository.name, loginAccount.userName, issueId, form.message)
// TODO apply ref comment
// fetch pull request to temporary working repository
val pullRequestBranchName = s"gitbucket-pullrequest-${issueId}"
git.fetch
.setRemote(getRepositoryDir(repository.owner, repository.name).toURI.toString)
.setRefSpecs(new RefSpec(s"refs/pull/${issueId}/head:refs/heads/${pullRequestBranchName}")).call
// merge pull request
git.checkout.setName(pullreq.branch).call
val result = git.merge
.include(git.getRepository.resolve(pullRequestBranchName))
.setFastForward(FastForwardMode.NO_FF)
.setCommit(false)
.call
if(result.getConflicts != null){
throw new RuntimeException("This pull request can't merge automatically.")
}
// merge commit
git.getRepository.writeMergeCommitMsg(
s"Merge pull request #${issueId} from ${pullreq.requestUserName}/${pullreq.requestRepositoryName}\n"
+ form.message)
git.commit
.setCommitter(new PersonIdent(loginAccount.userName, loginAccount.mailAddress))
.call
// push
git.push.call
val (commits, _) = getRequestCompareInfo(repository.owner, repository.name, pullreq.commitIdFrom,
pullreq.requestUserName, pullreq.requestRepositoryName, pullreq.commitIdTo)
commits.flatten.foreach { commit =>
if(!existsCommitId(repository.owner, repository.name, commit.id)){
insertCommitId(repository.owner, repository.name, commit.id)
}
}
// notifications
Notifier().toNotify(repository, issueId, "merge"){
Notifier.msgStatus(s"${baseUrl}/${repository.owner}/${repository.name}/pull/${issueId}")
}
redirect(s"/${repository.owner}/${repository.name}/pull/${issueId}")
} finally {
git.getRepository.close
FileUtils.deleteDirectory(tmpdir)
}
} getOrElse NotFound
}
})
/**
* Checks whether conflict will be caused in merging.
* Returns true if conflict will be caused.
*/
private def checkConflict(userName: String, repositoryName: String, branch: String,
requestUserName: String, requestRepositoryName: String, requestBranch: String): Boolean = {
// TODO Are there more quick way?
LockUtil.lock(s"${userName}/${repositoryName}/merge-check"){
val remote = getRepositoryDir(userName, repositoryName)
val tmpdir = new java.io.File(getTemporaryDir(userName, repositoryName), "merge-check")
if(tmpdir.exists()){
FileUtils.deleteDirectory(tmpdir)
}
val git = Git.cloneRepository.setDirectory(tmpdir).setURI(remote.toURI.toString).setBranch(branch).call
try {
git.checkout.setName(branch).call
git.fetch
.setRemote(getRepositoryDir(requestUserName, requestRepositoryName).toURI.toString)
.setRefSpecs(new RefSpec(s"refs/heads/${branch}:refs/heads/${requestBranch}")).call
val result = git.merge
.include(git.getRepository.resolve("FETCH_HEAD"))
.setCommit(false).call
result.getConflicts != null
} finally {
git.getRepository.close
FileUtils.deleteDirectory(tmpdir)
}
}
}
get("/:owner/:repository/compare")(referrersOnly { forkedRepository =>
(forkedRepository.repository.originUserName, forkedRepository.repository.originRepositoryName) match {
case (Some(originUserName), Some(originRepositoryName)) => {
getRepository(originUserName, originRepositoryName, baseUrl).map { originRepository =>
withGit(
getRepositoryDir(originUserName, originRepositoryName),
getRepositoryDir(forkedRepository.owner, forkedRepository.name)
){ (oldGit, newGit) =>
val oldBranch = JGitUtil.getDefaultBranch(oldGit, originRepository).get._2
val newBranch = JGitUtil.getDefaultBranch(newGit, forkedRepository).get._2
redirect(s"${context.path}/${forkedRepository.owner}/${forkedRepository.name}/compare/${originUserName}:${oldBranch}...${newBranch}")
}
} getOrElse NotFound
}
case _ => {
JGitUtil.withGit(getRepositoryDir(forkedRepository.owner, forkedRepository.name)){ git =>
val defaultBranch = JGitUtil.getDefaultBranch(git, forkedRepository).get._2
redirect(s"${context.path}/${forkedRepository.owner}/${forkedRepository.name}/compare/${defaultBranch}...${defaultBranch}")
}
}
}
})
get("/:owner/:repository/compare/*...*")(referrersOnly { repository =>
val Seq(origin, forked) = multiParams("splat")
val (originOwner, tmpOriginBranch) = parseCompareIdentifie(origin, repository.owner)
val (forkedOwner, tmpForkedBranch) = parseCompareIdentifie(forked, repository.owner)
(getRepository(originOwner, repository.name, baseUrl),
getRepository(forkedOwner, repository.name, baseUrl)) match {
case (Some(originRepository), Some(forkedRepository)) => {
withGit(
getRepositoryDir(originOwner, repository.name),
getRepositoryDir(forkedOwner, repository.name)
){ case (oldGit, newGit) =>
val originBranch = JGitUtil.getDefaultBranch(oldGit, originRepository, tmpOriginBranch).get._2
val forkedBranch = JGitUtil.getDefaultBranch(newGit, forkedRepository, tmpForkedBranch).get._2
val forkedId = getForkedCommitId(oldGit, newGit,
originOwner, repository.name, originBranch,
forkedOwner, repository.name, forkedBranch)
val oldId = oldGit.getRepository.resolve(forkedId)
val newId = newGit.getRepository.resolve(forkedBranch)
val (commits, diffs) = getRequestCompareInfo(
originOwner, repository.name, oldId.getName,
forkedOwner, repository.name, newId.getName)
pulls.html.compare(
commits,
diffs,
repository.repository.originUserName.map { userName =>
userName :: getForkedRepositories(userName, repository.name)
} getOrElse List(repository.owner),
originBranch,
forkedBranch,
oldId.getName,
newId.getName,
checkConflict(originOwner, repository.name, originBranch, forkedOwner, repository.name, forkedBranch),
repository,
originRepository,
forkedRepository,
hasWritePermission(repository.owner, repository.name, context.loginAccount))
}
}
case _ => NotFound
}
})
post("/:owner/:repository/pulls/new", pullRequestForm)(referrersOnly { (form, repository) =>
val loginUserName = context.loginAccount.get.userName
val issueId = createIssue(
owner = repository.owner,
repository = repository.name,
loginUser = loginUserName,
title = form.title,
content = form.content,
assignedUserName = None,
milestoneId = None,
isPullRequest = true)
createPullRequest(
originUserName = repository.owner,
originRepositoryName = repository.name,
issueId = issueId,
originBranch = form.targetBranch,
requestUserName = form.requestUserName,
requestRepositoryName = repository.name,
requestBranch = form.requestBranch,
commitIdFrom = form.commitIdFrom,
commitIdTo = form.commitIdTo)
// fetch requested branch
JGitUtil.withGit(getRepositoryDir(repository.owner, repository.name)){ git =>
git.fetch
.setRemote(getRepositoryDir(form.requestUserName, repository.name).toURI.toString)
.setRefSpecs(new RefSpec(s"refs/heads/${form.requestBranch}:refs/pull/${issueId}/head"))
.call
}
// record activity
recordPullRequestActivity(repository.owner, repository.name, loginUserName, issueId, form.title)
// notifications
Notifier().toNotify(repository, issueId, form.content.getOrElse("")){
Notifier.msgPullRequest(s"${baseUrl}/${repository.owner}/${repository.name}/pull/${issueId}")
}
redirect(s"/${repository.owner}/${repository.name}/pull/${issueId}")
})
/**
* Handles w Git object simultaneously.
*/
private def withGit[T](oldDir: java.io.File, newDir: java.io.File)(action: (Git, Git) => T): T = {
val oldGit = Git.open(oldDir)
val newGit = Git.open(newDir)
try {
action(oldGit, newGit)
} finally {
oldGit.getRepository.close
newGit.getRepository.close
}
}
/**
* Parses branch identifier and extracts owner and branch name as tuple.
*
* - "owner:branch" to ("owner", "branch")
* - "branch" to ("defaultOwner", "branch")
*/
private def parseCompareIdentifie(value: String, defaultOwner: String): (String, String) =
if(value.contains(':')){
val array = value.split(":")
(array(0), array(1))
} else {
(defaultOwner, value)
}
/**
* Extracts all repository names from [[service.RepositoryService.RepositoryTreeNode]] as flat list.
*/
private def getRepositoryNames(node: RepositoryTreeNode): List[String] =
node.owner :: node.children.map { child => getRepositoryNames(child) }.flatten
/**
* Returns the identifier of the root commit (or latest merge commit) of the specified branch.
*/
private def getForkedCommitId(oldGit: Git, newGit: Git, userName: String, repositoryName: String, branch: String,
requestUserName: String, requestRepositoryName: String, requestBranch: String): String =
JGitUtil.getCommitLogs(newGit, requestBranch, true){ commit =>
existsCommitId(userName, repositoryName, commit.getName) &&
JGitUtil.getBranchesOfCommit(oldGit, commit.getName).contains(branch)
}.head.id
private def getRequestCompareInfo(userName: String, repositoryName: String, branch: String,
requestUserName: String, requestRepositoryName: String, requestCommitId: String): (Seq[Seq[CommitInfo]], Seq[DiffInfo]) = {
withGit(
getRepositoryDir(userName, repositoryName),
getRepositoryDir(requestUserName, requestRepositoryName)
){ (oldGit, newGit) =>
val oldId = oldGit.getRepository.resolve(branch)
val newId = newGit.getRepository.resolve(requestCommitId)
val commits = newGit.log.addRange(oldId, newId).call.iterator.asScala.map { revCommit =>
new CommitInfo(revCommit)
}.toList.splitWith{ (commit1, commit2) =>
view.helpers.date(commit1.time) == view.helpers.date(commit2.time)
}
val diffs = JGitUtil.getDiffs(newGit, oldId.getName, newId.getName, true)
(commits, diffs)
}
}
private def searchPullRequests(userName: Option[String], repository: RepositoryService.RepositoryInfo) = {
val owner = repository.owner
val repoName = repository.name
val filterUser = userName.map { x => Map("created_by" -> x) } getOrElse Map("all" -> "")
val page = IssueSearchCondition.page(request)
val sessionKey = s"${owner}/${repoName}/pulls"
// retrieve search condition
val condition = if(request.getQueryString == null){
session.get(sessionKey).getOrElse(IssueSearchCondition()).asInstanceOf[IssueSearchCondition]
} else IssueSearchCondition(request)
session.put(sessionKey, condition)
pulls.html.list(
searchIssue(condition, filterUser, true, (page - 1) * PullRequestLimit, PullRequestLimit, owner -> repoName),
getPullRequestCountGroupByUser(condition.state == "closed", owner, Some(repoName)),
userName,
page,
countIssue(condition.copy(state = "open"), filterUser, true, owner -> repoName),
countIssue(condition.copy(state = "closed"), filterUser, true, owner -> repoName),
countIssue(condition, Map.empty, true, owner -> repoName),
condition,
repository,
hasWritePermission(owner, repoName, context.loginAccount))
}
}

View File

@@ -1,126 +0,0 @@
package app
import service._
import util.Directory._
import util.{UsersAuthenticator, OwnerAuthenticator}
import jp.sf.amateras.scalatra.forms._
import org.apache.commons.io.FileUtils
import org.scalatra.FlashMapSupport
class RepositorySettingsController extends RepositorySettingsControllerBase
with RepositoryService with AccountService with OwnerAuthenticator with UsersAuthenticator
trait RepositorySettingsControllerBase extends ControllerBase with FlashMapSupport {
self: RepositoryService with AccountService with OwnerAuthenticator with UsersAuthenticator =>
case class OptionsForm(description: Option[String], defaultBranch: String, isPrivate: Boolean)
val optionsForm = mapping(
"description" -> trim(label("Description" , optional(text()))),
"defaultBranch" -> trim(label("Default Branch" , text(required, maxlength(100)))),
"isPrivate" -> trim(label("Repository Type", boolean()))
)(OptionsForm.apply)
case class CollaboratorForm(userName: String)
val collaboratorForm = mapping(
"userName" -> trim(label("Username", text(required, collaborator)))
)(CollaboratorForm.apply)
/**
* Redirect to the Options page.
*/
get("/:owner/:repository/settings")(ownerOnly { repository =>
redirect(s"/${repository.owner}/${repository.name}/settings/options")
})
/**
* Display the Options page.
*/
get("/:owner/:repository/settings/options")(ownerOnly {
settings.html.options(_, flash.get("info"))
})
/**
* Save the repository options.
*/
post("/:owner/:repository/settings/options", optionsForm)(ownerOnly { (form, repository) =>
saveRepositoryOptions(
repository.owner,
repository.name,
form.description,
form.defaultBranch,
repository.repository.parentUserName.map { _ =>
repository.repository.isPrivate
} getOrElse form.isPrivate
)
flash += "info" -> "Repository settings has been updated."
redirect(s"/${repository.owner}/${repository.name}/settings/options")
})
/**
* Display the Collaborators page.
*/
get("/:owner/:repository/settings/collaborators")(ownerOnly { repository =>
settings.html.collaborators(
getCollaborators(repository.owner, repository.name),
getAccountByUserName(repository.owner).get.isGroupAccount,
repository)
})
/**
* Add the collaborator.
*/
post("/:owner/:repository/settings/collaborators/add", collaboratorForm)(ownerOnly { (form, repository) =>
if(!getAccountByUserName(repository.owner).get.isGroupAccount){
addCollaborator(repository.owner, repository.name, form.userName)
}
redirect(s"/${repository.owner}/${repository.name}/settings/collaborators")
})
/**
* Add the collaborator.
*/
get("/:owner/:repository/settings/collaborators/remove")(ownerOnly { repository =>
if(!getAccountByUserName(repository.owner).get.isGroupAccount){
removeCollaborator(repository.owner, repository.name, params("name"))
}
redirect(s"/${repository.owner}/${repository.name}/settings/collaborators")
})
/**
* Display the delete repository page.
*/
get("/:owner/:repository/settings/delete")(ownerOnly {
settings.html.delete(_)
})
/**
* Delete the repository.
*/
post("/:owner/:repository/settings/delete")(ownerOnly { repository =>
deleteRepository(repository.owner, repository.name)
FileUtils.deleteDirectory(getRepositoryDir(repository.owner, repository.name))
FileUtils.deleteDirectory(getWikiRepositoryDir(repository.owner, repository.name))
FileUtils.deleteDirectory(getTemporaryDir(repository.owner, repository.name))
redirect(s"/${repository.owner}")
})
/**
* Provides Constraint to validate the collaborator name.
*/
private def collaborator: Constraint = new Constraint(){
override def validate(name: String, value: String): Option[String] = {
val paths = request.getRequestURI.split("/")
getAccountByUserName(value) match {
case None => Some("User does not exist.")
case Some(x) if(x.userName == paths(1) || getCollaborators(paths(1), paths(2)).contains(x.userName))
=> Some("User can access this repository already.")
case _ => None
}
}
}
}

View File

@@ -1,258 +0,0 @@
package app
import util.Directory._
import util.Implicits._
import _root_.util.{ReferrerAuthenticator, JGitUtil, FileUtil, StringUtil}
import service._
import org.scalatra._
import java.io.File
import org.eclipse.jgit.api.Git
import org.eclipse.jgit.lib._
import org.apache.commons.io.FileUtils
import org.eclipse.jgit.treewalk._
class RepositoryViewerController extends RepositoryViewerControllerBase
with RepositoryService with AccountService with ReferrerAuthenticator
/**
* The repository viewer.
*/
trait RepositoryViewerControllerBase extends ControllerBase {
self: RepositoryService with AccountService with ReferrerAuthenticator =>
/**
* Returns converted HTML from Markdown for preview.
*/
post("/:owner/:repository/_preview")(referrersOnly { repository =>
contentType = "text/html"
view.helpers.markdown(params("content"), repository,
params("enableWikiLink").toBoolean,
params("enableRefsLink").toBoolean)
})
/**
* Displays the file list of the repository root and the default branch.
*/
get("/:owner/:repository")(referrersOnly {
fileList(_)
})
/**
* Displays the file list of the specified path and branch.
*/
get("/:owner/:repository/tree/*")(referrersOnly { repository =>
val (id, path) = splitPath(repository, multiParams("splat").head)
if(path.isEmpty){
fileList(repository, id)
} else {
fileList(repository, id, path)
}
})
/**
* Displays the commit list of the specified resource.
*/
get("/:owner/:repository/commits/*")(referrersOnly { repository =>
val (branchName, path) = splitPath(repository, multiParams("splat").head)
val page = params.getOrElse("page", "1").toInt
JGitUtil.withGit(getRepositoryDir(repository.owner, repository.name)){ git =>
JGitUtil.getCommitLog(git, branchName, page, 30, path) match {
case Right((logs, hasNext)) =>
repo.html.commits(if(path.isEmpty) Nil else path.split("/").toList, branchName, repository,
logs.splitWith{ (commit1, commit2) =>
view.helpers.date(commit1.time) == view.helpers.date(commit2.time)
}, page, hasNext)
case Left(_) => NotFound
}
}
})
/**
* Displays the file content of the specified branch or commit.
*/
get("/:owner/:repository/blob/*")(referrersOnly { repository =>
val (id, path) = splitPath(repository, multiParams("splat").head)
val raw = params.get("raw").getOrElse("false").toBoolean
JGitUtil.withGit(getRepositoryDir(repository.owner, repository.name)){ git =>
val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(id))
@scala.annotation.tailrec
def getPathObjectId(path: String, walk: TreeWalk): ObjectId = walk.next match {
case true if(walk.getPathString == path) => walk.getObjectId(0)
case true => getPathObjectId(path, walk)
}
val treeWalk = new TreeWalk(git.getRepository)
val objectId = try {
treeWalk.addTree(revCommit.getTree)
treeWalk.setRecursive(true)
getPathObjectId(path, treeWalk)
} finally {
treeWalk.release
}
if(raw){
// Download
contentType = "application/octet-stream"
JGitUtil.getContent(git, objectId, false).get
} else {
// Viewer
val large = FileUtil.isLarge(git.getRepository.getObjectDatabase.open(objectId).getSize)
val viewer = if(FileUtil.isImage(path)) "image" else if(large) "large" else "other"
val bytes = if(viewer == "other") JGitUtil.getContent(git, objectId, false) else None
val content = if(viewer == "other"){
if(bytes.isDefined && FileUtil.isText(bytes.get)){
// text
JGitUtil.ContentInfo("text", bytes.map(StringUtil.convertFromByteArray))
} else {
// binary
JGitUtil.ContentInfo("binary", None)
}
} else {
// image or large
JGitUtil.ContentInfo(viewer, None)
}
repo.html.blob(id, repository, path.split("/").toList, content, new JGitUtil.CommitInfo(revCommit))
}
}
})
/**
* Displays details of the specified commit.
*/
get("/:owner/:repository/commit/:id")(referrersOnly { repository =>
val id = params("id")
JGitUtil.withGit(getRepositoryDir(repository.owner, repository.name)){ git =>
val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(id))
JGitUtil.getDiffs(git, id) match { case (diffs, oldCommitId) =>
repo.html.commit(id, new JGitUtil.CommitInfo(revCommit),
JGitUtil.getBranchesOfCommit(git, revCommit.getName),
JGitUtil.getTagsOfCommit(git, revCommit.getName),
repository, diffs, oldCommitId)
}
}
})
/**
* Displays branches.
*/
get("/:owner/:repository/branches")(referrersOnly { repository =>
JGitUtil.withGit(getRepositoryDir(repository.owner, repository.name)){ git =>
// retrieve latest update date of each branch
val branchInfo = repository.branchList.map { branchName =>
val revCommit = git.log.add(git.getRepository.resolve(branchName)).setMaxCount(1).call.iterator.next
(branchName, revCommit.getCommitterIdent.getWhen)
}
repo.html.branches(branchInfo, repository)
}
})
/**
* Displays tags.
*/
get("/:owner/:repository/tags")(referrersOnly {
repo.html.tags(_)
})
/**
* Download repository contents as an archive.
*/
get("/:owner/:repository/archive/:name")(referrersOnly { repository =>
val name = params("name")
if(name.endsWith(".zip")){
val revision = name.replaceFirst("\\.zip$", "")
val workDir = getDownloadWorkDir(repository.owner, repository.name, session.getId)
if(workDir.exists){
FileUtils.deleteDirectory(workDir)
}
workDir.mkdirs
// clone the repository
val cloneDir = new File(workDir, revision)
JGitUtil.withGit(Git.cloneRepository
.setURI(getRepositoryDir(repository.owner, repository.name).toURI.toString)
.setDirectory(cloneDir)
.call){ git =>
// checkout the specified revision
git.checkout.setName(revision).call
}
// remove .git
FileUtils.deleteDirectory(new File(cloneDir, ".git"))
// create zip file
val zipFile = new File(workDir, (if(revision.length == 40) revision.substring(0, 10) else revision) + ".zip")
FileUtil.createZipFile(zipFile, cloneDir)
contentType = "application/octet-stream"
zipFile
} else {
BadRequest
}
})
get("/:owner/:repository/network/members")(referrersOnly { repository =>
repo.html.forked(
getRepository(
repository.repository.originUserName.getOrElse(repository.owner),
repository.repository.originRepositoryName.getOrElse(repository.name),
baseUrl),
getForkedRepositories(
repository.repository.originUserName.getOrElse(repository.owner),
repository.repository.originRepositoryName.getOrElse(repository.name)),
repository)
})
private def splitPath(repository: service.RepositoryService.RepositoryInfo, path: String): (String, String) = {
val id = repository.branchList.collectFirst {
case branch if(path == branch || path.startsWith(branch + "/")) => branch
} orElse repository.tags.collectFirst {
case tag if(path == tag.name || path.startsWith(tag.name + "/")) => tag.name
} orElse Some(path.split("/")(0)) get
(id, path.substring(id.length).replaceFirst("^/", ""))
}
/**
* Provides HTML of the file list.
*
* @param repository the repository information
* @param revstr the branch name or commit id(optional)
* @param path the directory path (optional)
* @return HTML of the file list
*/
private def fileList(repository: RepositoryService.RepositoryInfo, revstr: String = "", path: String = ".") = {
if(repository.commitCount == 0){
repo.html.guide(repository)
} else {
JGitUtil.withGit(getRepositoryDir(repository.owner, repository.name)){ git =>
val revisions = Seq(if(revstr.isEmpty) repository.repository.defaultBranch else revstr, repository.branchList.head)
// get specified commit
JGitUtil.getDefaultBranch(git, repository, revstr).map { case (objectId, revision) =>
val revCommit = JGitUtil.getRevCommitFromId(git, objectId)
// get files
val files = JGitUtil.getFileList(git, revision, path)
// process README.md
val readme = files.find(_.name == "README.md").map { file =>
StringUtil.convertFromByteArray(JGitUtil.getContent(Git.open(getRepositoryDir(repository.owner, repository.name)), file.id, true).get)
}
repo.html.files(revision, repository,
if(path == ".") Nil else path.split("/").toList, // current path
new JGitUtil.CommitInfo(revCommit), // latest commit
files, readme)
} getOrElse NotFound
}
}
}
}

View File

@@ -1,51 +0,0 @@
package app
import util._
import service._
import jp.sf.amateras.scalatra.forms._
class SearchController extends SearchControllerBase
with RepositoryService with AccountService with SystemSettingsService with ActivityService
with RepositorySearchService with IssuesService
with ReferrerAuthenticator
trait SearchControllerBase extends ControllerBase { self: RepositoryService
with SystemSettingsService with ActivityService with RepositorySearchService
with ReferrerAuthenticator =>
val searchForm = mapping(
"query" -> trim(text(required)),
"owner" -> trim(text(required)),
"repository" -> trim(text(required))
)(SearchForm.apply)
case class SearchForm(query: String, owner: String, repository: String)
post("/search", searchForm){ form =>
redirect(s"/${form.owner}/${form.repository}/search?q=${StringUtil.urlEncode(form.query)}")
}
get("/:owner/:repository/search")(referrersOnly { repository =>
val query = params("q").trim
val target = params.getOrElse("type", "code")
val page = try {
val i = params.getOrElse("page", "1").toInt
if(i <= 0) 1 else i
} catch {
case e: NumberFormatException => 1
}
target.toLowerCase match {
case "issue" => search.html.issues(
searchIssues(repository.owner, repository.name, query),
countFiles(repository.owner, repository.name, query),
query, page, repository)
case _ => search.html.code(
searchFiles(repository.owner, repository.name, query),
countIssues(repository.owner, repository.name, query),
query, page, repository)
}
})
}

View File

@@ -1,53 +0,0 @@
package app
import service._
import jp.sf.amateras.scalatra.forms._
class SignInController extends SignInControllerBase with SystemSettingsService with AccountService
trait SignInControllerBase extends ControllerBase { self: SystemSettingsService with AccountService =>
case class SignInForm(userName: String, password: String)
val form = mapping(
"userName" -> trim(label("Username", text(required))),
"password" -> trim(label("Password", text(required)))
)(SignInForm.apply)
get("/signin"){
val redirect = params.get("redirect")
if(redirect.isDefined && redirect.get.startsWith("/")){
session.setAttribute("REDIRECT", redirect.get)
}
html.signin(loadSystemSettings())
}
post("/signin", form){ form =>
val settings = loadSystemSettings()
authenticate(loadSystemSettings(), form.userName, form.password) match {
case Some(account) => signin(account)
case None => redirect("/signin")
}
}
get("/signout"){
session.invalidate
redirect("/")
}
/**
* Set account information into HttpSession and redirect.
*/
private def signin(account: model.Account) = {
session.setAttribute("LOGIN_ACCOUNT", account)
updateLastLoginDate(account.userName)
session.get("REDIRECT").map { redirectUrl =>
session.removeAttribute("REDIRECT")
redirect(redirectUrl.asInstanceOf[String])
}.getOrElse {
redirect("/")
}
}
}

View File

@@ -1,49 +0,0 @@
package app
import service.{AccountService, SystemSettingsService}
import SystemSettingsService._
import util.AdminAuthenticator
import jp.sf.amateras.scalatra.forms._
import org.scalatra.FlashMapSupport
class SystemSettingsController extends SystemSettingsControllerBase
with SystemSettingsService with AccountService with AdminAuthenticator
trait SystemSettingsControllerBase extends ControllerBase with FlashMapSupport {
self: SystemSettingsService with AccountService with AdminAuthenticator =>
private val form = mapping(
"allowAccountRegistration" -> trim(label("Account registration", boolean())),
"gravatar" -> trim(label("Gravatar", boolean())),
"notification" -> trim(label("Notification", boolean())),
"smtp" -> optionalIfNotChecked("notification", mapping(
"host" -> trim(label("SMTP Host", text(required))),
"port" -> trim(label("SMTP Port", optional(number()))),
"user" -> trim(label("SMTP User", optional(text()))),
"password" -> trim(label("SMTP Password", optional(text()))),
"ssl" -> trim(label("Enable SSL", optional(boolean())))
)(Smtp.apply)),
"ldapAuthentication" -> trim(label("LDAP", boolean())),
"ldap" -> optionalIfNotChecked("ldapAuthentication", mapping(
"host" -> trim(label("LDAP host", text(required))),
"port" -> trim(label("LDAP port", optional(number()))),
"bindDN" -> trim(label("Bind DN", optional(text()))),
"bindPassword" -> trim(label("Bind Password", optional(text()))),
"baseDN" -> trim(label("Base DN", text(required))),
"userNameAttribute" -> trim(label("User name attribute", text(required))),
"mailAttribute" -> trim(label("Mail address attribute", text(required)))
)(Ldap.apply))
)(SystemSettings.apply)
get("/admin/system")(adminOnly {
admin.html.system(loadSystemSettings(), flash.get("info"))
})
post("/admin/system", form)(adminOnly { form =>
saveSystemSettings(form)
flash += "info" -> "System settings has been updated."
redirect("/admin/system")
})
}

View File

@@ -1,139 +0,0 @@
package app
import service._
import util.AdminAuthenticator
import util.StringUtil._
import jp.sf.amateras.scalatra.forms._
class UserManagementController extends UserManagementControllerBase
with AccountService with RepositoryService with AdminAuthenticator
trait UserManagementControllerBase extends AccountManagementControllerBase {
self: AccountService with RepositoryService with AdminAuthenticator =>
case class NewUserForm(userName: String, password: String, mailAddress: String, isAdmin: Boolean,
url: Option[String], fileId: Option[String])
case class EditUserForm(userName: String, password: Option[String], mailAddress: String, isAdmin: Boolean,
url: Option[String], fileId: Option[String], clearImage: Boolean)
case class NewGroupForm(groupName: String, url: Option[String], fileId: Option[String],
memberNames: Option[String])
case class EditGroupForm(groupName: String, url: Option[String], fileId: Option[String],
memberNames: Option[String], clearImage: Boolean)
val newUserForm = mapping(
"userName" -> trim(label("Username" , text(required, maxlength(100), identifier, uniqueUserName))),
"password" -> trim(label("Password" , text(required, maxlength(20)))),
"mailAddress" -> trim(label("Mail Address" , text(required, maxlength(100), uniqueMailAddress()))),
"isAdmin" -> trim(label("User Type" , boolean())),
"url" -> trim(label("URL" , optional(text(maxlength(200))))),
"fileId" -> trim(label("File ID" , optional(text())))
)(NewUserForm.apply)
val editUserForm = mapping(
"userName" -> trim(label("Username" , text(required, maxlength(100), identifier))),
"password" -> trim(label("Password" , optional(text(maxlength(20))))),
"mailAddress" -> trim(label("Mail Address" , text(required, maxlength(100), uniqueMailAddress("userName")))),
"isAdmin" -> trim(label("User Type" , boolean())),
"url" -> trim(label("URL" , optional(text(maxlength(200))))),
"fileId" -> trim(label("File ID" , optional(text()))),
"clearImage" -> trim(label("Clear image" , boolean()))
)(EditUserForm.apply)
val newGroupForm = mapping(
"groupName" -> trim(label("Group name" , text(required, maxlength(100), identifier))),
"url" -> trim(label("URL" , optional(text(maxlength(200))))),
"fileId" -> trim(label("File ID" , optional(text()))),
"memberNames" -> trim(label("Member Names" , optional(text())))
)(NewGroupForm.apply)
val editGroupForm = mapping(
"groupName" -> trim(label("Group name" , text(required, maxlength(100), identifier))),
"url" -> trim(label("URL" , optional(text(maxlength(200))))),
"fileId" -> trim(label("File ID" , optional(text()))),
"memberNames" -> trim(label("Member Names" , optional(text()))),
"clearImage" -> trim(label("Clear image" , boolean()))
)(EditGroupForm.apply)
get("/admin/users")(adminOnly {
val users = getAllUsers()
val members = users.collect { case account if(account.isGroupAccount) =>
account.userName -> getGroupMembers(account.userName)
}.toMap
admin.users.html.list(users, members)
})
get("/admin/users/_newuser")(adminOnly {
admin.users.html.user(None)
})
post("/admin/users/_newuser", newUserForm)(adminOnly { form =>
createAccount(form.userName, sha1(form.password), form.mailAddress, form.isAdmin, form.url)
updateImage(form.userName, form.fileId, false)
redirect("/admin/users")
})
get("/admin/users/:userName/_edituser")(adminOnly {
val userName = params("userName")
admin.users.html.user(getAccountByUserName(userName))
})
post("/admin/users/:name/_edituser", editUserForm)(adminOnly { form =>
val userName = params("userName")
getAccountByUserName(userName).map { account =>
updateAccount(getAccountByUserName(userName).get.copy(
password = form.password.map(sha1).getOrElse(account.password),
mailAddress = form.mailAddress,
isAdmin = form.isAdmin,
url = form.url))
updateImage(userName, form.fileId, form.clearImage)
redirect("/admin/users")
} getOrElse NotFound
})
get("/admin/users/_newgroup")(adminOnly {
admin.users.html.group(None, Nil)
})
post("/admin/users/_newgroup", newGroupForm)(adminOnly { form =>
createGroup(form.groupName, form.url)
updateGroupMembers(form.groupName, form.memberNames.map(_.split(",").toList).getOrElse(Nil))
updateImage(form.groupName, form.fileId, false)
redirect("/admin/users")
})
get("/admin/users/:groupName/_editgroup")(adminOnly {
val groupName = params("groupName")
admin.users.html.group(getAccountByUserName(groupName), getGroupMembers(groupName))
})
post("/admin/users/:groupName/_editgroup", editGroupForm)(adminOnly { form =>
val groupName = params("groupName")
getAccountByUserName(groupName).map { account =>
updateGroup(groupName, form.url)
val memberNames = form.memberNames.map(_.split(",").toList).getOrElse(Nil)
updateGroupMembers(form.groupName, memberNames)
getRepositoryNamesOfUser(form.groupName).foreach { repositoryName =>
removeCollaborators(form.groupName, repositoryName)
memberNames.foreach { userName =>
addCollaborator(form.groupName, repositoryName, userName)
}
}
updateImage(form.groupName, form.fileId, form.clearImage)
redirect("/admin/users")
} getOrElse NotFound
})
post("/admin/users/_usercheck")(adminOnly {
getAccountByUserName(params("userName")).isDefined
})
}

View File

@@ -1,155 +0,0 @@
package app
import service._
import util.{CollaboratorsAuthenticator, ReferrerAuthenticator, JGitUtil, StringUtil}
import util.Directory._
import jp.sf.amateras.scalatra.forms._
class WikiController extends WikiControllerBase
with WikiService with RepositoryService with AccountService with ActivityService
with CollaboratorsAuthenticator with ReferrerAuthenticator
trait WikiControllerBase extends ControllerBase {
self: WikiService with RepositoryService with ActivityService
with CollaboratorsAuthenticator with ReferrerAuthenticator =>
case class WikiPageEditForm(pageName: String, content: String, message: Option[String], currentPageName: String)
val newForm = mapping(
"pageName" -> trim(label("Page name" , text(required, maxlength(40), pagename, unique))),
"content" -> trim(label("Content" , text(required))),
"message" -> trim(label("Message" , optional(text()))),
"currentPageName" -> trim(label("Current page name" , text()))
)(WikiPageEditForm.apply)
val editForm = mapping(
"pageName" -> trim(label("Page name" , text(required, maxlength(40), pagename))),
"content" -> trim(label("Content" , text(required))),
"message" -> trim(label("Message" , optional(text()))),
"currentPageName" -> trim(label("Current page name" , text(required)))
)(WikiPageEditForm.apply)
get("/:owner/:repository/wiki")(referrersOnly { repository =>
getWikiPage(repository.owner, repository.name, "Home").map { page =>
wiki.html.page("Home", page, repository, hasWritePermission(repository.owner, repository.name, context.loginAccount))
} getOrElse redirect(s"/${repository.owner}/${repository.name}/wiki/Home/_edit")
})
get("/:owner/:repository/wiki/:page")(referrersOnly { repository =>
val pageName = StringUtil.urlDecode(params("page"))
getWikiPage(repository.owner, repository.name, pageName).map { page =>
wiki.html.page(pageName, page, repository, hasWritePermission(repository.owner, repository.name, context.loginAccount))
} getOrElse redirect(s"/${repository.owner}/${repository.name}/wiki/${pageName}/_edit") // TODO URLEncode
})
get("/:owner/:repository/wiki/:page/_history")(referrersOnly { repository =>
val pageName = StringUtil.urlDecode(params("page"))
JGitUtil.withGit(getWikiRepositoryDir(repository.owner, repository.name)){ git =>
JGitUtil.getCommitLog(git, "master", path = pageName + ".md") match {
case Right((logs, hasNext)) => wiki.html.history(Some(pageName), logs, repository)
case Left(_) => NotFound
}
}
})
get("/:owner/:repository/wiki/:page/_compare/:commitId")(referrersOnly { repository =>
val pageName = StringUtil.urlDecode(params("page"))
val commitId = params("commitId").split("\\.\\.\\.")
JGitUtil.withGit(getWikiRepositoryDir(repository.owner, repository.name)){ git =>
wiki.html.compare(Some(pageName), JGitUtil.getDiffs(git, commitId(0), commitId(1), true), repository)
}
})
get("/:owner/:repository/wiki/_compare/:commitId")(referrersOnly { repository =>
val commitId = params("commitId").split("\\.\\.\\.")
JGitUtil.withGit(getWikiRepositoryDir(repository.owner, repository.name)){ git =>
wiki.html.compare(None, JGitUtil.getDiffs(git, commitId(0), commitId(1), true), repository)
}
})
get("/:owner/:repository/wiki/:page/_edit")(collaboratorsOnly { repository =>
val pageName = StringUtil.urlDecode(params("page"))
wiki.html.edit(pageName, getWikiPage(repository.owner, repository.name, pageName), repository)
})
post("/:owner/:repository/wiki/_edit", editForm)(collaboratorsOnly { (form, repository) =>
val loginAccount = context.loginAccount.get
saveWikiPage(repository.owner, repository.name, form.currentPageName, form.pageName,
form.content, loginAccount, form.message.getOrElse("")).map { commitId =>
updateLastActivityDate(repository.owner, repository.name)
recordEditWikiPageActivity(repository.owner, repository.name, loginAccount.userName, form.pageName, commitId)
}
redirect(s"/${repository.owner}/${repository.name}/wiki/${StringUtil.urlEncode(form.pageName)}")
})
get("/:owner/:repository/wiki/_new")(collaboratorsOnly {
wiki.html.edit("", None, _)
})
post("/:owner/:repository/wiki/_new", newForm)(collaboratorsOnly { (form, repository) =>
val loginAccount = context.loginAccount.get
saveWikiPage(repository.owner, repository.name, form.currentPageName, form.pageName,
form.content, context.loginAccount.get, form.message.getOrElse(""))
updateLastActivityDate(repository.owner, repository.name)
recordCreateWikiPageActivity(repository.owner, repository.name, loginAccount.userName, form.pageName)
redirect(s"/${repository.owner}/${repository.name}/wiki/${StringUtil.urlEncode(form.pageName)}")
})
get("/:owner/:repository/wiki/:page/_delete")(collaboratorsOnly { repository =>
val pageName = StringUtil.urlDecode(params("page"))
val account = context.loginAccount.get
deleteWikiPage(repository.owner, repository.name, pageName, account.userName, account.mailAddress, s"Delete ${pageName}")
updateLastActivityDate(repository.owner, repository.name)
redirect(s"/${repository.owner}/${repository.name}/wiki")
})
get("/:owner/:repository/wiki/_pages")(referrersOnly { repository =>
wiki.html.pages(getWikiPageList(repository.owner, repository.name), repository,
hasWritePermission(repository.owner, repository.name, context.loginAccount))
})
get("/:owner/:repository/wiki/_history")(referrersOnly { repository =>
JGitUtil.withGit(getWikiRepositoryDir(repository.owner, repository.name)){ git =>
JGitUtil.getCommitLog(git, "master") match {
case Right((logs, hasNext)) => wiki.html.history(None, logs, repository)
case Left(_) => NotFound
}
}
})
get("/:owner/:repository/wiki/_blob/*")(referrersOnly { repository =>
getFileContent(repository.owner, repository.name, multiParams("splat").head).map { content =>
contentType = "application/octet-stream"
content
} getOrElse NotFound
})
private def unique: Constraint = new Constraint(){
override def validate(name: String, value: String, params: Map[String, String]): Option[String] =
getWikiPageList(params("owner"), params("repository")).find(_ == value).map(_ => "Page already exists.")
}
private def pagename: Constraint = new Constraint(){
override def validate(name: String, value: String): Option[String] =
if(value.exists("\\/:*?\"<>|".contains(_))){
Some(s"${name} contains invalid character.")
} else if(value.startsWith("_") || value.startsWith("-")){
Some(s"${name} starts with invalid character.")
} else {
None
}
}
}

View File

@@ -0,0 +1,25 @@
package gitbucket.core.api
import gitbucket.core.model.{Account, CommitState, CommitStatus}
/**
* https://developer.github.com/v3/repos/statuses/#get-the-combined-status-for-a-specific-ref
*/
case class ApiCombinedCommitStatus(
state: String,
sha: String,
total_count: Int,
statuses: Iterable[ApiCommitStatus],
repository: ApiRepository){
// val commit_url = ApiPath(s"/api/v3/repos/${repository.full_name}/${sha}")
val url = ApiPath(s"/api/v3/repos/${repository.full_name}/commits/${sha}/status")
}
object ApiCombinedCommitStatus {
def apply(sha:String, statuses: Iterable[(CommitStatus, Account)], repository:ApiRepository): ApiCombinedCommitStatus = ApiCombinedCommitStatus(
state = CommitState.combine(statuses.map(_._1.state).toSet).name,
sha = sha,
total_count= statuses.size,
statuses = statuses.map{ case (s, a)=> ApiCommitStatus(s, ApiUser(a)) },
repository = repository)
}

View File

@@ -0,0 +1,26 @@
package gitbucket.core.api
import gitbucket.core.model.IssueComment
import java.util.Date
/**
* https://developer.github.com/v3/issues/comments/
*/
case class ApiComment(
id: Int,
user: ApiUser,
body: String,
created_at: Date,
updated_at: Date)
object ApiComment{
def apply(comment: IssueComment, user: ApiUser): ApiComment =
ApiComment(
id = comment.commentId,
user = user,
body = comment.content,
created_at = comment.registeredDate,
updated_at = comment.updatedDate)
}

View File

@@ -0,0 +1,48 @@
package gitbucket.core.api
import gitbucket.core.util.JGitUtil
import gitbucket.core.util.JGitUtil.CommitInfo
import gitbucket.core.util.RepositoryName
import org.eclipse.jgit.diff.DiffEntry
import org.eclipse.jgit.api.Git
import java.util.Date
/**
* https://developer.github.com/v3/repos/commits/
*/
case class ApiCommit(
id: String,
message: String,
timestamp: Date,
added: List[String],
removed: List[String],
modified: List[String],
author: ApiPersonIdent,
committer: ApiPersonIdent)(repositoryName:RepositoryName){
val url = ApiPath(s"/api/v3/${repositoryName.fullName}/commits/${id}")
val html_url = ApiPath(s"/${repositoryName.fullName}/commit/${id}")
}
object ApiCommit{
def apply(git: Git, repositoryName: RepositoryName, commit: CommitInfo): ApiCommit = {
val diffs = JGitUtil.getDiffs(git, commit.id, false)
ApiCommit(
id = commit.id,
message = commit.fullMessage,
timestamp = commit.commitTime,
added = diffs._1.collect {
case x if x.changeType == DiffEntry.ChangeType.ADD => x.newPath
},
removed = diffs._1.collect {
case x if x.changeType == DiffEntry.ChangeType.DELETE => x.oldPath
},
modified = diffs._1.collect {
case x if x.changeType != DiffEntry.ChangeType.ADD && x.changeType != DiffEntry.ChangeType.DELETE => x.newPath
},
author = ApiPersonIdent.author(commit),
committer = ApiPersonIdent.committer(commit)
)(repositoryName)
}
}

View File

@@ -0,0 +1,42 @@
package gitbucket.core.api
import gitbucket.core.api.ApiCommitListItem._
import gitbucket.core.util.JGitUtil.CommitInfo
import gitbucket.core.util.RepositoryName
/**
* https://developer.github.com/v3/repos/commits/
*/
case class ApiCommitListItem(
sha: String,
commit: Commit,
author: Option[ApiUser],
committer: Option[ApiUser],
parents: Seq[Parent])(repositoryName: RepositoryName) {
val url = ApiPath(s"/api/v3/repos/${repositoryName.fullName}/commits/${sha}")
}
object ApiCommitListItem {
def apply(commit: CommitInfo, repositoryName: RepositoryName): ApiCommitListItem = ApiCommitListItem(
sha = commit.id,
commit = Commit(
message = commit.fullMessage,
author = ApiPersonIdent.author(commit),
committer = ApiPersonIdent.committer(commit)
)(commit.id, repositoryName),
author = None,
committer = None,
parents = commit.parents.map(Parent(_)(repositoryName)))(repositoryName)
case class Parent(sha: String)(repositoryName: RepositoryName){
val url = ApiPath(s"/api/v3/repos/${repositoryName.fullName}/commits/${sha}")
}
case class Commit(
message: String,
author: ApiPersonIdent,
committer: ApiPersonIdent)(sha:String, repositoryName: RepositoryName) {
val url = ApiPath(s"/api/v3/repos/${repositoryName.fullName}/git/commits/${sha}")
}
}

View File

@@ -0,0 +1,38 @@
package gitbucket.core.api
import gitbucket.core.model.CommitStatus
import gitbucket.core.util.RepositoryName
import java.util.Date
/**
* https://developer.github.com/v3/repos/statuses/#create-a-status
* https://developer.github.com/v3/repos/statuses/#list-statuses-for-a-specific-ref
*/
case class ApiCommitStatus(
created_at: Date,
updated_at: Date,
state: String,
target_url: Option[String],
description: Option[String],
id: Int,
context: String,
creator: ApiUser
)(sha: String, repositoryName: RepositoryName) {
val url = ApiPath(s"/api/v3/repos/${repositoryName.fullName}/commits/${sha}/statuses")
}
object ApiCommitStatus {
def apply(status: CommitStatus, creator:ApiUser): ApiCommitStatus = ApiCommitStatus(
created_at = status.registeredDate,
updated_at = status.updatedDate,
state = status.state.name,
target_url = status.targetUrl,
description= status.description,
id = status.commitStatusId,
context = status.context,
creator = creator
)(status.commitId, RepositoryName(status))
}

View File

@@ -0,0 +1,5 @@
package gitbucket.core.api
case class ApiError(
message: String,
documentation_url: Option[String] = None)

View File

@@ -0,0 +1,31 @@
package gitbucket.core.api
import gitbucket.core.model.Issue
import java.util.Date
/**
* https://developer.github.com/v3/issues/
*/
case class ApiIssue(
number: Int,
title: String,
user: ApiUser,
// labels,
state: String,
created_at: Date,
updated_at: Date,
body: String)
object ApiIssue{
def apply(issue: Issue, user: ApiUser): ApiIssue =
ApiIssue(
number = issue.issueId,
title = issue.title,
user = user,
state = if(issue.closed){ "closed" }else{ "open" },
body = issue.content.getOrElse(""),
created_at = issue.registeredDate,
updated_at = issue.updatedDate)
}

View File

@@ -0,0 +1,6 @@
package gitbucket.core.api
/**
* path for api url. if set path '/repos/aa/bb' then, expand 'http://server:post/repos/aa/bb' when converted to json.
*/
case class ApiPath(path: String)

View File

@@ -0,0 +1,25 @@
package gitbucket.core.api
import gitbucket.core.util.JGitUtil.CommitInfo
import java.util.Date
case class ApiPersonIdent(
name: String,
email: String,
date: Date)
object ApiPersonIdent {
def author(commit: CommitInfo): ApiPersonIdent =
ApiPersonIdent(
name = commit.authorName,
email = commit.authorEmailAddress,
date = commit.authorTime)
def committer(commit: CommitInfo): ApiPersonIdent =
ApiPersonIdent(
name = commit.committerName,
email = commit.committerEmailAddress,
date = commit.commitTime)
}

View File

@@ -0,0 +1,59 @@
package gitbucket.core.api
import gitbucket.core.model.{Issue, PullRequest}
import java.util.Date
/**
* https://developer.github.com/v3/pulls/
*/
case class ApiPullRequest(
number: Int,
updated_at: Date,
created_at: Date,
head: ApiPullRequest.Commit,
base: ApiPullRequest.Commit,
mergeable: Option[Boolean],
title: String,
body: String,
user: ApiUser) {
val html_url = ApiPath(s"${base.repo.html_url.path}/pull/${number}")
//val diff_url = ApiPath(s"${base.repo.html_url.path}/pull/${number}.diff")
//val patch_url = ApiPath(s"${base.repo.html_url.path}/pull/${number}.patch")
val url = ApiPath(s"${base.repo.url.path}/pulls/${number}")
//val issue_url = ApiPath(s"${base.repo.url.path}/issues/${number}")
val commits_url = ApiPath(s"${base.repo.url.path}/pulls/${number}/commits")
val review_comments_url = ApiPath(s"${base.repo.url.path}/pulls/${number}/comments")
val review_comment_url = ApiPath(s"${base.repo.url.path}/pulls/comments/{number}")
val comments_url = ApiPath(s"${base.repo.url.path}/issues/${number}/comments")
val statuses_url = ApiPath(s"${base.repo.url.path}/statuses/${head.sha}")
}
object ApiPullRequest{
def apply(issue: Issue, pullRequest: PullRequest, headRepo: ApiRepository, baseRepo: ApiRepository, user: ApiUser): ApiPullRequest = ApiPullRequest(
number = issue.issueId,
updated_at = issue.updatedDate,
created_at = issue.registeredDate,
head = Commit(
sha = pullRequest.commitIdTo,
ref = pullRequest.requestBranch,
repo = headRepo)(issue.userName),
base = Commit(
sha = pullRequest.commitIdFrom,
ref = pullRequest.branch,
repo = baseRepo)(issue.userName),
mergeable = None, // TODO: need check mergeable.
title = issue.title,
body = issue.content.getOrElse(""),
user = user
)
case class Commit(
sha: String,
ref: String,
repo: ApiRepository)(baseOwner:String){
val label = if( baseOwner == repo.owner.login ){ ref }else{ s"${repo.owner.login}:${ref}" }
val user = repo.owner
}
}

View File

@@ -0,0 +1,48 @@
package gitbucket.core.api
import gitbucket.core.model.{Account, Repository}
import gitbucket.core.service.RepositoryService.RepositoryInfo
// https://developer.github.com/v3/repos/
case class ApiRepository(
name: String,
full_name: String,
description: String,
watchers: Int,
forks: Int,
`private`: Boolean,
default_branch: String,
owner: ApiUser) {
val forks_count = forks
val watchers_coun = watchers
val url = ApiPath(s"/api/v3/repos/${full_name}")
val http_url = ApiPath(s"/git/${full_name}.git")
val clone_url = ApiPath(s"/git/${full_name}.git")
val html_url = ApiPath(s"/${full_name}")
}
object ApiRepository{
def apply(
repository: Repository,
owner: ApiUser,
forkedCount: Int =0,
watchers: Int = 0): ApiRepository =
ApiRepository(
name = repository.repositoryName,
full_name = s"${repository.userName}/${repository.repositoryName}",
description = repository.description.getOrElse(""),
watchers = 0,
forks = forkedCount,
`private` = repository.isPrivate,
default_branch = repository.defaultBranch,
owner = owner
)
def apply(repositoryInfo: RepositoryInfo, owner: ApiUser): ApiRepository =
ApiRepository(repositoryInfo.repository, owner, forkedCount=repositoryInfo.forkedCount)
def apply(repositoryInfo: RepositoryInfo, owner: Account): ApiRepository =
this(repositoryInfo.repository, ApiUser(owner))
}

View File

@@ -0,0 +1,36 @@
package gitbucket.core.api
import gitbucket.core.model.Account
import java.util.Date
case class ApiUser(
login: String,
email: String,
`type`: String,
site_admin: Boolean,
created_at: Date) {
val url = ApiPath(s"/api/v3/users/${login}")
val html_url = ApiPath(s"/${login}")
// val followers_url = ApiPath(s"/api/v3/users/${login}/followers")
// val following_url = ApiPath(s"/api/v3/users/${login}/following{/other_user}")
// val gists_url = ApiPath(s"/api/v3/users/${login}/gists{/gist_id}")
// val starred_url = ApiPath(s"/api/v3/users/${login}/starred{/owner}{/repo}")
// val subscriptions_url = ApiPath(s"/api/v3/users/${login}/subscriptions")
// val organizations_url = ApiPath(s"/api/v3/users/${login}/orgs")
// val repos_url = ApiPath(s"/api/v3/users/${login}/repos")
// val events_url = ApiPath(s"/api/v3/users/${login}/events{/privacy}")
// val received_events_url = ApiPath(s"/api/v3/users/${login}/received_events")
}
object ApiUser{
def apply(user: Account): ApiUser = ApiUser(
login = user.fullName,
email = user.mailAddress,
`type` = if(user.isGroupAccount){ "Organization" }else{ "User" },
site_admin = user.isAdmin,
created_at = user.registeredDate
)
}

View File

@@ -0,0 +1,7 @@
package gitbucket.core.api
/**
* https://developer.github.com/v3/issues/comments/#create-a-comment
* api form
*/
case class CreateAComment(body: String)

View File

@@ -0,0 +1,26 @@
package gitbucket.core.api
import gitbucket.core.model.CommitState
/**
* https://developer.github.com/v3/repos/statuses/#create-a-status
* api form
*/
case class CreateAStatus(
/* state is Required. The state of the status. Can be one of pending, success, error, or failure. */
state: String,
/* context is a string label to differentiate this status from the status of other systems. Default: "default" */
context: Option[String],
/* The target URL to associate with this status. This URL will be linked from the GitHub UI to allow users to easily see the source of the Status. */
target_url: Option[String],
/* description is a short description of the status.*/
description: Option[String]
) {
def isValid: Boolean = {
CommitState.valueOf(state).isDefined &&
// only http
target_url.filterNot(f => "\\Ahttps?://".r.findPrefixOf(f).isDefined && f.length<255).isEmpty &&
context.filterNot(f => f.length<255).isEmpty &&
description.filterNot(f => f.length<1000).isEmpty
}
}

View File

@@ -0,0 +1,44 @@
package gitbucket.core.api
import org.joda.time.DateTime
import org.joda.time.DateTimeZone
import org.joda.time.format._
import org.json4s._
import org.json4s.jackson.Serialization
import java.util.Date
import scala.util.Try
object JsonFormat {
case class Context(baseUrl:String)
val parserISO = DateTimeFormat.forPattern("yyyy-MM-dd'T'HH:mm:ss'Z'")
val jsonFormats = Serialization.formats(NoTypeHints) + new CustomSerializer[Date](format =>
(
{ case JString(s) => Try(parserISO.parseDateTime(s)).toOption.map(_.toDate)
.getOrElse(throw new MappingException("Can't convert " + s + " to Date")) },
{ case x: Date => JString(parserISO.print(new DateTime(x).withZone(DateTimeZone.UTC))) }
)
) + FieldSerializer[ApiUser]() + FieldSerializer[ApiPullRequest]() + FieldSerializer[ApiRepository]() +
FieldSerializer[ApiCommitListItem.Parent]() + FieldSerializer[ApiCommitListItem]() + FieldSerializer[ApiCommitListItem.Commit]() +
FieldSerializer[ApiCommitStatus]() + FieldSerializer[ApiCommit]() + FieldSerializer[ApiCombinedCommitStatus]() +
FieldSerializer[ApiPullRequest.Commit]()
def apiPathSerializer(c: Context) = new CustomSerializer[ApiPath](format =>
(
{
case JString(s) if s.startsWith(c.baseUrl) => ApiPath(s.substring(c.baseUrl.length))
case JString(s) => throw new MappingException("Can't convert " + s + " to ApiPath")
},
{
case ApiPath(path) => JString(c.baseUrl+path)
}
)
)
/**
* convert object to json string
*/
def apply(obj: AnyRef)(implicit c: Context): String = Serialization.write(obj)(jsonFormats + apiPathSerializer(c))
}

View File

@@ -0,0 +1,535 @@
package gitbucket.core.controller
import gitbucket.core.account.html
import gitbucket.core.api._
import gitbucket.core.helper
import gitbucket.core.model.GroupMember
import gitbucket.core.service._
import gitbucket.core.ssh.SshUtil
import gitbucket.core.util.ControlUtil._
import gitbucket.core.util.Directory._
import gitbucket.core.util.Implicits._
import gitbucket.core.util.StringUtil._
import gitbucket.core.util._
import jp.sf.amateras.scalatra.forms._
import org.apache.commons.io.FileUtils
import org.eclipse.jgit.api.Git
import org.eclipse.jgit.dircache.DirCache
import org.eclipse.jgit.lib.{FileMode, Constants}
import org.scalatra.i18n.Messages
class AccountController extends AccountControllerBase
with AccountService with RepositoryService with ActivityService with WikiService with LabelsService with SshKeyService
with OneselfAuthenticator with UsersAuthenticator with GroupManagerAuthenticator with ReadableUsersAuthenticator
with AccessTokenService with WebHookService
trait AccountControllerBase extends AccountManagementControllerBase {
self: AccountService with RepositoryService with ActivityService with WikiService with LabelsService with SshKeyService
with OneselfAuthenticator with UsersAuthenticator with GroupManagerAuthenticator with ReadableUsersAuthenticator
with AccessTokenService with WebHookService =>
case class AccountNewForm(userName: String, password: String, fullName: String, mailAddress: String,
url: Option[String], fileId: Option[String])
case class AccountEditForm(password: Option[String], fullName: String, mailAddress: String,
url: Option[String], fileId: Option[String], clearImage: Boolean)
case class SshKeyForm(title: String, publicKey: String)
case class PersonalTokenForm(note: String)
val newForm = mapping(
"userName" -> trim(label("User name" , text(required, maxlength(100), identifier, uniqueUserName))),
"password" -> trim(label("Password" , text(required, maxlength(20)))),
"fullName" -> trim(label("Full Name" , text(required, maxlength(100)))),
"mailAddress" -> trim(label("Mail Address" , text(required, maxlength(100), uniqueMailAddress()))),
"url" -> trim(label("URL" , optional(text(maxlength(200))))),
"fileId" -> trim(label("File ID" , optional(text())))
)(AccountNewForm.apply)
val editForm = mapping(
"password" -> trim(label("Password" , optional(text(maxlength(20))))),
"fullName" -> trim(label("Full Name" , text(required, maxlength(100)))),
"mailAddress" -> trim(label("Mail Address" , text(required, maxlength(100), uniqueMailAddress("userName")))),
"url" -> trim(label("URL" , optional(text(maxlength(200))))),
"fileId" -> trim(label("File ID" , optional(text()))),
"clearImage" -> trim(label("Clear image" , boolean()))
)(AccountEditForm.apply)
val sshKeyForm = mapping(
"title" -> trim(label("Title", text(required, maxlength(100)))),
"publicKey" -> trim(label("Key" , text(required, validPublicKey)))
)(SshKeyForm.apply)
val personalTokenForm = mapping(
"note" -> trim(label("Token", text(required, maxlength(100))))
)(PersonalTokenForm.apply)
case class NewGroupForm(groupName: String, url: Option[String], fileId: Option[String], members: String)
case class EditGroupForm(groupName: String, url: Option[String], fileId: Option[String], members: String, clearImage: Boolean)
val newGroupForm = mapping(
"groupName" -> trim(label("Group name" ,text(required, maxlength(100), identifier, uniqueUserName))),
"url" -> trim(label("URL" ,optional(text(maxlength(200))))),
"fileId" -> trim(label("File ID" ,optional(text()))),
"members" -> trim(label("Members" ,text(required, members)))
)(NewGroupForm.apply)
val editGroupForm = mapping(
"groupName" -> trim(label("Group name" ,text(required, maxlength(100), identifier))),
"url" -> trim(label("URL" ,optional(text(maxlength(200))))),
"fileId" -> trim(label("File ID" ,optional(text()))),
"members" -> trim(label("Members" ,text(required, members))),
"clearImage" -> trim(label("Clear image" ,boolean()))
)(EditGroupForm.apply)
case class RepositoryCreationForm(owner: String, name: String, description: Option[String], isPrivate: Boolean, createReadme: Boolean)
case class ForkRepositoryForm(owner: String, name: String)
val newRepositoryForm = mapping(
"owner" -> trim(label("Owner" , text(required, maxlength(40), identifier, existsAccount))),
"name" -> trim(label("Repository name", text(required, maxlength(40), identifier, uniqueRepository))),
"description" -> trim(label("Description" , optional(text()))),
"isPrivate" -> trim(label("Repository Type", boolean())),
"createReadme" -> trim(label("Create README" , boolean()))
)(RepositoryCreationForm.apply)
val forkRepositoryForm = mapping(
"owner" -> trim(label("Repository owner", text(required))),
"name" -> trim(label("Repository name", text(required)))
)(ForkRepositoryForm.apply)
case class AccountForm(accountName: String)
val accountForm = mapping(
"account" -> trim(label("Group/User name", text(required, validAccountName)))
)(AccountForm.apply)
/**
* Displays user information.
*/
get("/:userName") {
val userName = params("userName")
getAccountByUserName(userName).map { account =>
params.getOrElse("tab", "repositories") match {
// Public Activity
case "activity" =>
gitbucket.core.account.html.activity(account,
if(account.isGroupAccount) Nil else getGroupsByUserName(userName),
getActivitiesByUser(userName, true))
// Members
case "members" if(account.isGroupAccount) => {
val members = getGroupMembers(account.userName)
gitbucket.core.account.html.members(account, members.map(_.userName),
context.loginAccount.exists(x => members.exists { member => member.userName == x.userName && member.isManager }))
}
// Repositories
case _ => {
val members = getGroupMembers(account.userName)
gitbucket.core.account.html.repositories(account,
if(account.isGroupAccount) Nil else getGroupsByUserName(userName),
getVisibleRepositories(context.loginAccount, context.baseUrl, Some(userName)),
context.loginAccount.exists(x => members.exists { member => member.userName == x.userName && member.isManager }))
}
}
} getOrElse NotFound
}
get("/:userName.atom") {
val userName = params("userName")
contentType = "application/atom+xml; type=feed"
helper.xml.feed(getActivitiesByUser(userName, true))
}
get("/:userName/_avatar"){
val userName = params("userName")
getAccountByUserName(userName).flatMap(_.image).map { image =>
RawData(FileUtil.getMimeType(image), new java.io.File(getUserUploadDir(userName), image))
} getOrElse {
contentType = "image/png"
Thread.currentThread.getContextClassLoader.getResourceAsStream("noimage.png")
}
}
/**
* https://developer.github.com/v3/users/#get-a-single-user
*/
get("/api/v3/users/:userName") {
getAccountByUserName(params("userName")).map { account =>
JsonFormat(ApiUser(account))
} getOrElse NotFound
}
/**
* https://developer.github.com/v3/users/#get-the-authenticated-user
*/
get("/api/v3/user") {
context.loginAccount.map { account =>
JsonFormat(ApiUser(account))
} getOrElse Unauthorized
}
get("/:userName/_edit")(oneselfOnly {
val userName = params("userName")
getAccountByUserName(userName).map { x =>
html.edit(x, flash.get("info"))
} getOrElse NotFound
})
post("/:userName/_edit", editForm)(oneselfOnly { form =>
val userName = params("userName")
getAccountByUserName(userName).map { account =>
updateAccount(account.copy(
password = form.password.map(sha1).getOrElse(account.password),
fullName = form.fullName,
mailAddress = form.mailAddress,
url = form.url))
updateImage(userName, form.fileId, form.clearImage)
flash += "info" -> "Account information has been updated."
redirect(s"/${userName}/_edit")
} getOrElse NotFound
})
get("/:userName/_delete")(oneselfOnly {
val userName = params("userName")
getAccountByUserName(userName, true).foreach { account =>
// Remove repositories
getRepositoryNamesOfUser(userName).foreach { repositoryName =>
deleteRepository(userName, repositoryName)
FileUtils.deleteDirectory(getRepositoryDir(userName, repositoryName))
FileUtils.deleteDirectory(getWikiRepositoryDir(userName, repositoryName))
FileUtils.deleteDirectory(getTemporaryDir(userName, repositoryName))
}
// Remove from GROUP_MEMBER, COLLABORATOR and REPOSITORY
removeUserRelatedData(userName)
updateAccount(account.copy(isRemoved = true))
}
session.invalidate
redirect("/")
})
get("/:userName/_ssh")(oneselfOnly {
val userName = params("userName")
getAccountByUserName(userName).map { x =>
html.ssh(x, getPublicKeys(x.userName))
} getOrElse NotFound
})
post("/:userName/_ssh", sshKeyForm)(oneselfOnly { form =>
val userName = params("userName")
addPublicKey(userName, form.title, form.publicKey)
redirect(s"/${userName}/_ssh")
})
get("/:userName/_ssh/delete/:id")(oneselfOnly {
val userName = params("userName")
val sshKeyId = params("id").toInt
deletePublicKey(userName, sshKeyId)
redirect(s"/${userName}/_ssh")
})
get("/:userName/_application")(oneselfOnly {
val userName = params("userName")
getAccountByUserName(userName).map { x =>
var tokens = getAccessTokens(x.userName)
val generatedToken = flash.get("generatedToken") match {
case Some((tokenId:Int, token:String)) => {
val gt = tokens.find(_.accessTokenId == tokenId)
gt.map{ t =>
tokens = tokens.filterNot(_ == t)
(t, token)
}
}
case _ => None
}
html.application(x, tokens, generatedToken)
} getOrElse NotFound
})
post("/:userName/_personalToken", personalTokenForm)(oneselfOnly { form =>
val userName = params("userName")
getAccountByUserName(userName).map { x =>
val (tokenId, token) = generateAccessToken(userName, form.note)
flash += "generatedToken" -> (tokenId, token)
}
redirect(s"/${userName}/_application")
})
get("/:userName/_personalToken/delete/:id")(oneselfOnly {
val userName = params("userName")
val tokenId = params("id").toInt
deleteAccessToken(userName, tokenId)
redirect(s"/${userName}/_application")
})
get("/register"){
if(context.settings.allowAccountRegistration){
if(context.loginAccount.isDefined){
redirect("/")
} else {
html.register()
}
} else NotFound
}
post("/register", newForm){ form =>
if(context.settings.allowAccountRegistration){
createAccount(form.userName, sha1(form.password), form.fullName, form.mailAddress, false, form.url)
updateImage(form.userName, form.fileId, false)
redirect("/signin")
} else NotFound
}
get("/groups/new")(usersOnly {
html.group(None, List(GroupMember("", context.loginAccount.get.userName, true)))
})
post("/groups/new", newGroupForm)(usersOnly { form =>
createGroup(form.groupName, form.url)
updateGroupMembers(form.groupName, form.members.split(",").map {
_.split(":") match {
case Array(userName, isManager) => (userName, isManager.toBoolean)
}
}.toList)
updateImage(form.groupName, form.fileId, false)
redirect(s"/${form.groupName}")
})
get("/:groupName/_editgroup")(managersOnly {
defining(params("groupName")){ groupName =>
html.group(getAccountByUserName(groupName, true), getGroupMembers(groupName))
}
})
get("/:groupName/_deletegroup")(managersOnly {
defining(params("groupName")){ groupName =>
// Remove from GROUP_MEMBER
updateGroupMembers(groupName, Nil)
// Remove repositories
getRepositoryNamesOfUser(groupName).foreach { repositoryName =>
deleteRepository(groupName, repositoryName)
FileUtils.deleteDirectory(getRepositoryDir(groupName, repositoryName))
FileUtils.deleteDirectory(getWikiRepositoryDir(groupName, repositoryName))
FileUtils.deleteDirectory(getTemporaryDir(groupName, repositoryName))
}
}
redirect("/")
})
post("/:groupName/_editgroup", editGroupForm)(managersOnly { form =>
defining(params("groupName"), form.members.split(",").map {
_.split(":") match {
case Array(userName, isManager) => (userName, isManager.toBoolean)
}
}.toList){ case (groupName, members) =>
getAccountByUserName(groupName, true).map { account =>
updateGroup(groupName, form.url, false)
// Update GROUP_MEMBER
updateGroupMembers(form.groupName, members)
// Update COLLABORATOR for group repositories
getRepositoryNamesOfUser(form.groupName).foreach { repositoryName =>
removeCollaborators(form.groupName, repositoryName)
members.foreach { case (userName, isManager) =>
addCollaborator(form.groupName, repositoryName, userName)
}
}
updateImage(form.groupName, form.fileId, form.clearImage)
redirect(s"/${form.groupName}")
} getOrElse NotFound
}
})
/**
* Show the new repository form.
*/
get("/new")(usersOnly {
html.newrepo(getGroupsByUserName(context.loginAccount.get.userName), context.settings.isCreateRepoOptionPublic)
})
/**
* Create new repository.
*/
post("/new", newRepositoryForm)(usersOnly { form =>
LockUtil.lock(s"${form.owner}/${form.name}"){
if(getRepository(form.owner, form.name, context.baseUrl).isEmpty){
val ownerAccount = getAccountByUserName(form.owner).get
val loginAccount = context.loginAccount.get
val loginUserName = loginAccount.userName
// Insert to the database at first
createRepository(form.name, form.owner, form.description, form.isPrivate)
// Add collaborators for group repository
if(ownerAccount.isGroupAccount){
getGroupMembers(form.owner).foreach { member =>
addCollaborator(form.owner, form.name, member.userName)
}
}
// Insert default labels
insertDefaultLabels(form.owner, form.name)
// Create the actual repository
val gitdir = getRepositoryDir(form.owner, form.name)
JGitUtil.initRepository(gitdir)
if(form.createReadme){
using(Git.open(gitdir)){ git =>
val builder = DirCache.newInCore.builder()
val inserter = git.getRepository.newObjectInserter()
val headId = git.getRepository.resolve(Constants.HEAD + "^{commit}")
val content = if(form.description.nonEmpty){
form.name + "\n" +
"===============\n" +
"\n" +
form.description.get
} else {
form.name + "\n" +
"===============\n"
}
builder.add(JGitUtil.createDirCacheEntry("README.md", FileMode.REGULAR_FILE,
inserter.insert(Constants.OBJ_BLOB, content.getBytes("UTF-8"))))
builder.finish()
JGitUtil.createNewCommit(git, inserter, headId, builder.getDirCache.writeTree(inserter),
Constants.HEAD, loginAccount.fullName, loginAccount.mailAddress, "Initial commit")
}
}
// Create Wiki repository
createWikiRepository(loginAccount, form.owner, form.name)
// Record activity
recordCreateRepositoryActivity(form.owner, form.name, loginUserName)
}
// redirect to the repository
redirect(s"/${form.owner}/${form.name}")
}
})
get("/:owner/:repository/fork")(readableUsersOnly { repository =>
val loginAccount = context.loginAccount.get
val loginUserName = loginAccount.userName
val groups = getGroupsByUserName(loginUserName)
groups match {
case _: List[String] =>
val managerPermissions = groups.map { group =>
val members = getGroupMembers(group)
context.loginAccount.exists(x => members.exists { member => member.userName == x.userName && member.isManager })
}
helper.html.forkrepository(
repository,
(groups zip managerPermissions).toMap
)
case _ => redirect(s"/${loginUserName}")
}
})
post("/:owner/:repository/fork", accountForm)(readableUsersOnly { (form, repository) =>
val loginAccount = context.loginAccount.get
val loginUserName = loginAccount.userName
val accountName = form.accountName
LockUtil.lock(s"${accountName}/${repository.name}"){
if(getRepository(accountName, repository.name, baseUrl).isDefined ||
(accountName != loginUserName && !getGroupsByUserName(loginUserName).contains(accountName))){
// redirect to the repository if repository already exists
redirect(s"/${accountName}/${repository.name}")
} else {
// Insert to the database at first
val originUserName = repository.repository.originUserName.getOrElse(repository.owner)
val originRepositoryName = repository.repository.originRepositoryName.getOrElse(repository.name)
createRepository(
repositoryName = repository.name,
userName = accountName,
description = repository.repository.description,
isPrivate = repository.repository.isPrivate,
originRepositoryName = Some(originRepositoryName),
originUserName = Some(originUserName),
parentRepositoryName = Some(repository.name),
parentUserName = Some(repository.owner)
)
// Insert default labels
insertDefaultLabels(accountName, repository.name)
// clone repository actually
JGitUtil.cloneRepository(
getRepositoryDir(repository.owner, repository.name),
getRepositoryDir(accountName, repository.name))
// Create Wiki repository
JGitUtil.cloneRepository(
getWikiRepositoryDir(repository.owner, repository.name),
getWikiRepositoryDir(accountName, repository.name))
// Record activity
recordForkActivity(repository.owner, repository.name, loginUserName, accountName)
// redirect to the repository
redirect(s"/${accountName}/${repository.name}")
}
}
})
private def insertDefaultLabels(userName: String, repositoryName: String): Unit = {
createLabel(userName, repositoryName, "bug", "fc2929")
createLabel(userName, repositoryName, "duplicate", "cccccc")
createLabel(userName, repositoryName, "enhancement", "84b6eb")
createLabel(userName, repositoryName, "invalid", "e6e6e6")
createLabel(userName, repositoryName, "question", "cc317c")
createLabel(userName, repositoryName, "wontfix", "ffffff")
}
private def existsAccount: Constraint = new Constraint(){
override def validate(name: String, value: String, messages: Messages): Option[String] =
if(getAccountByUserName(value).isEmpty) Some("User or group does not exist.") else None
}
private def uniqueRepository: Constraint = new Constraint(){
override def validate(name: String, value: String, params: Map[String, String], messages: Messages): Option[String] =
params.get("owner").flatMap { userName =>
getRepositoryNamesOfUser(userName).find(_ == value).map(_ => "Repository already exists.")
}
}
private def members: Constraint = new Constraint(){
override def validate(name: String, value: String, messages: Messages): Option[String] = {
if(value.split(",").exists {
_.split(":") match { case Array(userName, isManager) => isManager.toBoolean }
}) None else Some("Must select one manager at least.")
}
}
private def validPublicKey: Constraint = new Constraint(){
override def validate(name: String, value: String, messages: Messages): Option[String] = SshUtil.str2PublicKey(value) match {
case Some(_) => None
case None => Some("Key is invalid.")
}
}
private def validAccountName: Constraint = new Constraint(){
override def validate(name: String, value: String, messages: Messages): Option[String] = {
getAccountByUserName(value) match {
case Some(_) => None
case None => Some("Invalid Group/User Account.")
}
}
}
}

View File

@@ -0,0 +1,14 @@
package gitbucket.core.controller
class AnonymousAccessController extends AnonymousAccessControllerBase
trait AnonymousAccessControllerBase extends ControllerBase {
get(!context.settings.allowAnonymousAccess, context.loginAccount.isEmpty) {
if(!context.currentPath.startsWith("/assets") && !context.currentPath.startsWith("/signin") &&
!context.currentPath.startsWith("/register")) {
Unauthorized()
} else {
pass()
}
}
}

View File

@@ -0,0 +1,237 @@
package gitbucket.core.controller
import gitbucket.core.api.ApiError
import gitbucket.core.model.Account
import gitbucket.core.service.{AccountService, SystemSettingsService}
import gitbucket.core.util.ControlUtil._
import gitbucket.core.util.Directory._
import gitbucket.core.util.Implicits._
import gitbucket.core.util._
import jp.sf.amateras.scalatra.forms._
import org.apache.commons.io.FileUtils
import org.json4s._
import org.scalatra._
import org.scalatra.i18n._
import org.scalatra.json._
import javax.servlet.http.{HttpServletResponse, HttpServletRequest}
import javax.servlet.{FilterChain, ServletResponse, ServletRequest}
import scala.util.Try
/**
* Provides generic features for controller implementations.
*/
abstract class ControllerBase extends ScalatraFilter
with ClientSideValidationFormSupport with JacksonJsonSupport with I18nSupport with FlashMapSupport with Validations
with SystemSettingsService {
implicit val jsonFormats = DefaultFormats
// TODO Scala 2.11
// // Don't set content type via Accept header.
// override def format(implicit request: HttpServletRequest) = ""
override def doFilter(request: ServletRequest, response: ServletResponse, chain: FilterChain): Unit = try {
val httpRequest = request.asInstanceOf[HttpServletRequest]
val httpResponse = response.asInstanceOf[HttpServletResponse]
val context = request.getServletContext.getContextPath
val path = httpRequest.getRequestURI.substring(context.length)
if(path.startsWith("/console/")){
val account = httpRequest.getSession.getAttribute(Keys.Session.LoginAccount).asInstanceOf[Account]
val baseUrl = this.baseUrl(httpRequest)
if(account == null){
// Redirect to login form
httpResponse.sendRedirect(baseUrl + "/signin?redirect=" + StringUtil.urlEncode(path))
} else if(account.isAdmin){
// H2 Console (administrators only)
chain.doFilter(request, response)
} else {
// Redirect to dashboard
httpResponse.sendRedirect(baseUrl + "/")
}
} else if(path.startsWith("/git/")){
// Git repository
chain.doFilter(request, response)
} else {
if(path.startsWith("/api/v3/")){
httpRequest.setAttribute(Keys.Request.APIv3, true)
}
// Scalatra actions
super.doFilter(request, response, chain)
}
} finally {
contextCache.remove();
}
private val contextCache = new java.lang.ThreadLocal[Context]()
/**
* Returns the context object for the request.
*/
implicit def context: Context = {
contextCache.get match {
case null => {
val context = Context(loadSystemSettings(), LoginAccount, request)
contextCache.set(context)
context
}
case context => context
}
}
private def LoginAccount: Option[Account] = request.getAs[Account](Keys.Session.LoginAccount).orElse(session.getAs[Account](Keys.Session.LoginAccount))
def ajaxGet(path : String)(action : => Any) : Route =
super.get(path){
request.setAttribute(Keys.Request.Ajax, "true")
action
}
override def ajaxGet[T](path : String, form : ValueType[T])(action : T => Any) : Route =
super.ajaxGet(path, form){ form =>
request.setAttribute(Keys.Request.Ajax, "true")
action(form)
}
def ajaxPost(path : String)(action : => Any) : Route =
super.post(path){
request.setAttribute(Keys.Request.Ajax, "true")
action
}
override def ajaxPost[T](path : String, form : ValueType[T])(action : T => Any) : Route =
super.ajaxPost(path, form){ form =>
request.setAttribute(Keys.Request.Ajax, "true")
action(form)
}
protected def NotFound() =
if(request.hasAttribute(Keys.Request.Ajax)){
org.scalatra.NotFound()
} else if(request.hasAttribute(Keys.Request.APIv3)){
contentType = formats("json")
org.scalatra.NotFound(ApiError("Not Found"))
} else {
org.scalatra.NotFound(gitbucket.core.html.error("Not Found"))
}
protected def Unauthorized()(implicit context: Context) =
if(request.hasAttribute(Keys.Request.Ajax)){
org.scalatra.Unauthorized()
} else if(request.hasAttribute(Keys.Request.APIv3)){
contentType = formats("json")
org.scalatra.Unauthorized(ApiError("Requires authentication"))
} else {
if(context.loginAccount.isDefined){
org.scalatra.Unauthorized(redirect("/"))
} else {
if(request.getMethod.toUpperCase == "POST"){
org.scalatra.Unauthorized(redirect("/signin"))
} else {
org.scalatra.Unauthorized(redirect("/signin?redirect=" + StringUtil.urlEncode(
defining(request.getQueryString){ queryString =>
request.getRequestURI.substring(request.getContextPath.length) + (if(queryString != null) "?" + queryString else "")
}
)))
}
}
}
// TODO Scala 2.11
override def url(path: String, params: Iterable[(String, Any)] = Iterable.empty,
includeContextPath: Boolean = true, includeServletPath: Boolean = true,
absolutize: Boolean = true, withSessionId: Boolean = true)
(implicit request: HttpServletRequest, response: HttpServletResponse): String =
if (path.startsWith("http")) path
else baseUrl + super.url(path, params, false, false, false)
/**
* Use this method to response the raw data against XSS.
*/
protected def RawData[T](contentType: String, rawData: T): T = {
if(contentType.split(";").head.trim.toLowerCase.startsWith("text/html")){
this.contentType = "text/plain"
} else {
this.contentType = contentType
}
response.addHeader("X-Content-Type-Options", "nosniff")
rawData
}
// jenkins send message as 'application/x-www-form-urlencoded' but scalatra already parsed as multi-part-request.
def extractFromJsonBody[A](implicit request:HttpServletRequest, mf:Manifest[A]): Option[A] = {
(request.contentType.map(_.split(";").head.toLowerCase) match{
case Some("application/x-www-form-urlencoded") => multiParams.keys.headOption.map(parse(_))
case Some("application/json") => Some(parsedBody)
case _ => Some(parse(request.body))
}).filterNot(_ == JNothing).flatMap(j => Try(j.extract[A]).toOption)
}
}
/**
* Context object for the current request.
*/
case class Context(settings: SystemSettingsService.SystemSettings, loginAccount: Option[Account], request: HttpServletRequest){
val path = settings.baseUrl.getOrElse(request.getContextPath)
val currentPath = request.getRequestURI.substring(request.getContextPath.length)
val baseUrl = settings.baseUrl(request)
val host = new java.net.URL(baseUrl).getHost
/**
* Get object from cache.
*
* If object has not been cached with the specified key then retrieves by given action.
* Cached object are available during a request.
*/
def cache[A](key: String)(action: => A): A =
defining(Keys.Request.Cache(key)){ cacheKey =>
Option(request.getAttribute(cacheKey).asInstanceOf[A]).getOrElse {
val newObject = action
request.setAttribute(cacheKey, newObject)
newObject
}
}
}
/**
* Base trait for controllers which manages account information.
*/
trait AccountManagementControllerBase extends ControllerBase {
self: AccountService =>
protected def updateImage(userName: String, fileId: Option[String], clearImage: Boolean): Unit =
if(clearImage){
getAccountByUserName(userName).flatMap(_.image).map { image =>
new java.io.File(getUserUploadDir(userName), image).delete()
updateAvatarImage(userName, None)
}
} else {
fileId.map { fileId =>
val filename = "avatar." + FileUtil.getExtension(session.getAndRemove(Keys.Session.Upload(fileId)).get)
FileUtils.moveFile(
new java.io.File(getTemporaryDir(session.getId), fileId),
new java.io.File(getUserUploadDir(userName), filename)
)
updateAvatarImage(userName, Some(filename))
}
}
protected def uniqueUserName: Constraint = new Constraint(){
override def validate(name: String, value: String, messages: Messages): Option[String] =
getAccountByUserName(value, true).map { _ => "User already exists." }
}
protected def uniqueMailAddress(paramName: String = ""): Constraint = new Constraint(){
override def validate(name: String, value: String, params: Map[String, String], messages: Messages): Option[String] =
getAccountByMailAddress(value, true)
.filter { x => if(paramName.isEmpty) true else Some(x.userName) != params.get(paramName) }
.map { _ => "Mail address is already registered." }
}
}

View File

@@ -0,0 +1,138 @@
package gitbucket.core.controller
import gitbucket.core.dashboard.html
import gitbucket.core.service.{RepositoryService, PullRequestService, AccountService, IssuesService}
import gitbucket.core.util.{StringUtil, Keys, UsersAuthenticator}
import gitbucket.core.util.Implicits._
import gitbucket.core.service.IssuesService._
class DashboardController extends DashboardControllerBase
with IssuesService with PullRequestService with RepositoryService with AccountService
with UsersAuthenticator
trait DashboardControllerBase extends ControllerBase {
self: IssuesService with PullRequestService with RepositoryService with AccountService
with UsersAuthenticator =>
get("/dashboard/issues")(usersOnly {
val q = request.getParameter("q")
val account = context.loginAccount.get
Option(q).map { q =>
val condition = IssueSearchCondition(q, Map[String, Int]())
q match {
case q if(q.contains("is:pr")) => redirect(s"/dashboard/pulls?q=${StringUtil.urlEncode(q)}")
case q if(q.contains(s"author:${account.userName}")) => redirect(s"/dashboard/issues/created_by${condition.toURL}")
case q if(q.contains(s"assignee:${account.userName}")) => redirect(s"/dashboard/issues/assigned${condition.toURL}")
case q if(q.contains(s"mentions:${account.userName}")) => redirect(s"/dashboard/issues/mentioned${condition.toURL}")
case _ => searchIssues("created_by")
}
} getOrElse {
searchIssues("created_by")
}
})
get("/dashboard/issues/assigned")(usersOnly {
searchIssues("assigned")
})
get("/dashboard/issues/created_by")(usersOnly {
searchIssues("created_by")
})
get("/dashboard/issues/mentioned")(usersOnly {
searchIssues("mentioned")
})
get("/dashboard/pulls")(usersOnly {
val q = request.getParameter("q")
val account = context.loginAccount.get
Option(q).map { q =>
val condition = IssueSearchCondition(q, Map[String, Int]())
q match {
case q if(q.contains("is:issue")) => redirect(s"/dashboard/issues?q=${StringUtil.urlEncode(q)}")
case q if(q.contains(s"author:${account.userName}")) => redirect(s"/dashboard/pulls/created_by${condition.toURL}")
case q if(q.contains(s"assignee:${account.userName}")) => redirect(s"/dashboard/pulls/assigned${condition.toURL}")
case q if(q.contains(s"mentions:${account.userName}")) => redirect(s"/dashboard/pulls/mentioned${condition.toURL}")
case _ => searchPullRequests("created_by")
}
} getOrElse {
searchPullRequests("created_by")
}
})
get("/dashboard/pulls/created_by")(usersOnly {
searchPullRequests("created_by")
})
get("/dashboard/pulls/assigned")(usersOnly {
searchPullRequests("assigned")
})
get("/dashboard/pulls/mentioned")(usersOnly {
searchPullRequests("mentioned")
})
private def getOrCreateCondition(key: String, filter: String, userName: String) = {
val condition = session.putAndGet(key, if(request.hasQueryString){
val q = request.getParameter("q")
if(q == null){
IssueSearchCondition(request)
} else {
IssueSearchCondition(q, Map[String, Int]())
}
} else session.getAs[IssueSearchCondition](key).getOrElse(IssueSearchCondition()))
filter match {
case "assigned" => condition.copy(assigned = Some(userName), author = None , mentioned = None)
case "mentioned" => condition.copy(assigned = None , author = None , mentioned = Some(userName))
case _ => condition.copy(assigned = None , author = Some(userName), mentioned = None)
}
}
private def searchIssues(filter: String) = {
import IssuesService._
val userName = context.loginAccount.get.userName
val condition = getOrCreateCondition(Keys.Session.DashboardIssues, filter, userName)
val userRepos = getUserRepositories(userName, context.baseUrl, true).map(repo => repo.owner -> repo.name)
val page = IssueSearchCondition.page(request)
html.issues(
searchIssue(condition, false, (page - 1) * IssueLimit, IssueLimit, userRepos: _*),
page,
countIssue(condition.copy(state = "open" ), false, userRepos: _*),
countIssue(condition.copy(state = "closed"), false, userRepos: _*),
filter match {
case "assigned" => condition.copy(assigned = Some(userName))
case "mentioned" => condition.copy(mentioned = Some(userName))
case _ => condition.copy(author = Some(userName))
},
filter,
getGroupNames(userName))
}
private def searchPullRequests(filter: String) = {
import IssuesService._
import PullRequestService._
val userName = context.loginAccount.get.userName
val condition = getOrCreateCondition(Keys.Session.DashboardPulls, filter, userName)
val allRepos = getAllRepositories(userName)
val page = IssueSearchCondition.page(request)
html.pulls(
searchIssue(condition, true, (page - 1) * PullRequestLimit, PullRequestLimit, allRepos: _*),
page,
countIssue(condition.copy(state = "open" ), true, allRepos: _*),
countIssue(condition.copy(state = "closed"), true, allRepos: _*),
filter match {
case "assigned" => condition.copy(assigned = Some(userName))
case "mentioned" => condition.copy(mentioned = Some(userName))
case _ => condition.copy(author = Some(userName))
},
filter,
getGroupNames(userName))
}
}

View File

@@ -0,0 +1,44 @@
package gitbucket.core.controller
import gitbucket.core.util.{Keys, FileUtil}
import gitbucket.core.util.ControlUtil._
import gitbucket.core.util.Directory._
import org.scalatra._
import org.scalatra.servlet.{MultipartConfig, FileUploadSupport, FileItem}
import org.apache.commons.io.FileUtils
/**
* Provides Ajax based file upload functionality.
*
* This servlet saves uploaded file.
*/
class FileUploadController extends ScalatraServlet with FileUploadSupport {
configureMultipartHandling(MultipartConfig(maxFileSize = Some(3 * 1024 * 1024)))
post("/image"){
execute { (file, fileId) =>
FileUtils.writeByteArrayToFile(new java.io.File(getTemporaryDir(session.getId), fileId), file.get)
session += Keys.Session.Upload(fileId) -> file.name
}
}
post("/image/:owner/:repository"){
execute { (file, fileId) =>
FileUtils.writeByteArrayToFile(new java.io.File(
getAttachedDir(params("owner"), params("repository")),
fileId + "." + FileUtil.getExtension(file.getName)), file.get)
}
}
private def execute(f: (FileItem, String) => Unit) = fileParams.get("file") match {
case Some(file) if(FileUtil.isImage(file.name)) =>
defining(FileUtil.generateFileId){ fileId =>
f(file, fileId)
Ok(fileId)
}
case _ => BadRequest
}
}

View File

@@ -0,0 +1,122 @@
package gitbucket.core.controller
import gitbucket.core.api._
import gitbucket.core.helper.xml
import gitbucket.core.html
import gitbucket.core.model.Account
import gitbucket.core.service.{RepositoryService, ActivityService, AccountService}
import gitbucket.core.util.Implicits._
import gitbucket.core.util.{LDAPUtil, Keys, UsersAuthenticator}
import jp.sf.amateras.scalatra.forms._
class IndexController extends IndexControllerBase
with RepositoryService with ActivityService with AccountService with UsersAuthenticator
trait IndexControllerBase extends ControllerBase {
self: RepositoryService with ActivityService with AccountService with UsersAuthenticator =>
case class SignInForm(userName: String, password: String)
val form = mapping(
"userName" -> trim(label("Username", text(required))),
"password" -> trim(label("Password", text(required)))
)(SignInForm.apply)
get("/"){
val loginAccount = context.loginAccount
if(loginAccount.isEmpty) {
html.index(getRecentActivities(),
getVisibleRepositories(loginAccount, context.baseUrl, withoutPhysicalInfo = true),
loginAccount.map{ account => getUserRepositories(account.userName, context.baseUrl, withoutPhysicalInfo = true) }.getOrElse(Nil)
)
} else {
val loginUserName = loginAccount.get.userName
val loginUserGroups = getGroupsByUserName(loginUserName)
var visibleOwnerSet : Set[String] = Set(loginUserName)
visibleOwnerSet ++= loginUserGroups
html.index(getRecentActivitiesByOwners(visibleOwnerSet),
getVisibleRepositories(loginAccount, context.baseUrl, withoutPhysicalInfo = true),
loginAccount.map{ account => getUserRepositories(account.userName, context.baseUrl, withoutPhysicalInfo = true) }.getOrElse(Nil)
)
}
}
get("/signin"){
val redirect = params.get("redirect")
if(redirect.isDefined && redirect.get.startsWith("/")){
flash += Keys.Flash.Redirect -> redirect.get
}
html.signin()
}
post("/signin", form){ form =>
authenticate(context.settings, form.userName, form.password) match {
case Some(account) => signin(account)
case None => redirect("/signin")
}
}
get("/signout"){
session.invalidate
redirect("/")
}
get("/activities.atom"){
contentType = "application/atom+xml; type=feed"
xml.feed(getRecentActivities())
}
/**
* Set account information into HttpSession and redirect.
*/
private def signin(account: Account) = {
session.setAttribute(Keys.Session.LoginAccount, account)
updateLastLoginDate(account.userName)
if(LDAPUtil.isDummyMailAddress(account)) {
redirect("/" + account.userName + "/_edit")
}
flash.get(Keys.Flash.Redirect).asInstanceOf[Option[String]].map { redirectUrl =>
if(redirectUrl.stripSuffix("/") == request.getContextPath){
redirect("/")
} else {
redirect(redirectUrl)
}
}.getOrElse {
redirect("/")
}
}
/**
* JSON API for collaborator completion.
*/
get("/_user/proposals")(usersOnly {
contentType = formats("json")
org.json4s.jackson.Serialization.write(
Map("options" -> getAllUsers().filter(!_.isGroupAccount).map(_.userName).toArray)
)
})
/**
* JSON APU for checking user existence.
*/
post("/_user/existence")(usersOnly {
getAccountByUserName(params("userName")).isDefined
})
/**
* @see https://developer.github.com/v3/rate_limit/#get-your-current-rate-limit-status
* but not enabled.
*/
get("/api/v3/rate_limit"){
contentType = formats("json")
// this message is same as github enterprise...
org.scalatra.NotFound(ApiError("Rate limiting is not enabled."))
}
}

View File

@@ -0,0 +1,469 @@
package gitbucket.core.controller
import gitbucket.core.api._
import gitbucket.core.issues.html
import gitbucket.core.model.Issue
import gitbucket.core.service.IssuesService._
import gitbucket.core.service._
import gitbucket.core.util.ControlUtil._
import gitbucket.core.util.Implicits._
import gitbucket.core.util._
import gitbucket.core.view
import gitbucket.core.view.Markdown
import jp.sf.amateras.scalatra.forms._
import org.scalatra.Ok
class IssuesController extends IssuesControllerBase
with IssuesService with RepositoryService with AccountService with LabelsService with MilestonesService with ActivityService
with ReadableUsersAuthenticator with ReferrerAuthenticator with CollaboratorsAuthenticator with PullRequestService with WebHookIssueCommentService
trait IssuesControllerBase extends ControllerBase {
self: IssuesService with RepositoryService with AccountService with LabelsService with MilestonesService with ActivityService
with ReadableUsersAuthenticator with ReferrerAuthenticator with CollaboratorsAuthenticator with PullRequestService with WebHookIssueCommentService =>
case class IssueCreateForm(title: String, content: Option[String],
assignedUserName: Option[String], milestoneId: Option[Int], labelNames: Option[String])
case class CommentForm(issueId: Int, content: String)
case class IssueStateForm(issueId: Int, content: Option[String])
val issueCreateForm = mapping(
"title" -> trim(label("Title", text(required))),
"content" -> trim(optional(text())),
"assignedUserName" -> trim(optional(text())),
"milestoneId" -> trim(optional(number())),
"labelNames" -> trim(optional(text()))
)(IssueCreateForm.apply)
val issueTitleEditForm = mapping(
"title" -> trim(label("Title", text(required)))
)(x => x)
val issueEditForm = mapping(
"content" -> trim(optional(text()))
)(x => x)
val commentForm = mapping(
"issueId" -> label("Issue Id", number()),
"content" -> trim(label("Comment", text(required)))
)(CommentForm.apply)
val issueStateForm = mapping(
"issueId" -> label("Issue Id", number()),
"content" -> trim(optional(text()))
)(IssueStateForm.apply)
get("/:owner/:repository/issues")(referrersOnly { repository =>
val q = request.getParameter("q")
if(Option(q).exists(_.contains("is:pr"))){
redirect(s"/${repository.owner}/${repository.name}/pulls?q=" + StringUtil.urlEncode(q))
} else {
searchIssues(repository)
}
})
get("/:owner/:repository/issues/:id")(referrersOnly { repository =>
defining(repository.owner, repository.name, params("id")){ case (owner, name, issueId) =>
getIssue(owner, name, issueId) map {
html.issue(
_,
getComments(owner, name, issueId.toInt),
getIssueLabels(owner, name, issueId.toInt),
(getCollaborators(owner, name) ::: (if(getAccountByUserName(owner).get.isGroupAccount) Nil else List(owner))).sorted,
getMilestonesWithIssueCount(owner, name),
getLabels(owner, name),
hasWritePermission(owner, name, context.loginAccount),
repository)
} getOrElse NotFound
}
})
/**
* https://developer.github.com/v3/issues/comments/#list-comments-on-an-issue
*/
get("/api/v3/repos/:owner/:repository/issues/:id/comments")(referrersOnly { repository =>
(for{
issueId <- params("id").toIntOpt
comments = getCommentsForApi(repository.owner, repository.name, issueId.toInt)
} yield {
JsonFormat(comments.map{ case (issueComment, user) => ApiComment(issueComment, ApiUser(user)) })
}).getOrElse(NotFound)
})
get("/:owner/:repository/issues/new")(readableUsersOnly { repository =>
defining(repository.owner, repository.name){ case (owner, name) =>
html.create(
(getCollaborators(owner, name) ::: (if(getAccountByUserName(owner).get.isGroupAccount) Nil else List(owner))).sorted,
getMilestones(owner, name),
getLabels(owner, name),
hasWritePermission(owner, name, context.loginAccount),
repository)
}
})
post("/:owner/:repository/issues/new", issueCreateForm)(readableUsersOnly { (form, repository) =>
defining(repository.owner, repository.name){ case (owner, name) =>
val writable = hasWritePermission(owner, name, context.loginAccount)
val userName = context.loginAccount.get.userName
// insert issue
val issueId = createIssue(owner, name, userName, form.title, form.content,
if(writable) form.assignedUserName else None,
if(writable) form.milestoneId else None)
// insert labels
if(writable){
form.labelNames.map { value =>
val labels = getLabels(owner, name)
value.split(",").foreach { labelName =>
labels.find(_.labelName == labelName).map { label =>
registerIssueLabel(owner, name, issueId, label.labelId)
}
}
}
}
// record activity
recordCreateIssueActivity(owner, name, userName, issueId, form.title)
getIssue(owner, name, issueId.toString).foreach { issue =>
// extract references and create refer comment
createReferComment(owner, name, issue, form.title + " " + form.content.getOrElse(""))
// call web hooks
callIssuesWebHook("opened", repository, issue, context.baseUrl, context.loginAccount.get)
}
// notifications
Notifier().toNotify(repository, issueId, form.content.getOrElse("")){
Notifier.msgIssue(s"${context.baseUrl}/${owner}/${name}/issues/${issueId}")
}
redirect(s"/${owner}/${name}/issues/${issueId}")
}
})
ajaxPost("/:owner/:repository/issues/edit_title/:id", issueTitleEditForm)(readableUsersOnly { (title, repository) =>
defining(repository.owner, repository.name){ case (owner, name) =>
getIssue(owner, name, params("id")).map { issue =>
if(isEditable(owner, name, issue.openedUserName)){
// update issue
updateIssue(owner, name, issue.issueId, title, issue.content)
// extract references and create refer comment
createReferComment(owner, name, issue.copy(title = title), title)
redirect(s"/${owner}/${name}/issues/_data/${issue.issueId}")
} else Unauthorized
} getOrElse NotFound
}
})
ajaxPost("/:owner/:repository/issues/edit/:id", issueEditForm)(readableUsersOnly { (content, repository) =>
defining(repository.owner, repository.name){ case (owner, name) =>
getIssue(owner, name, params("id")).map { issue =>
if(isEditable(owner, name, issue.openedUserName)){
// update issue
updateIssue(owner, name, issue.issueId, issue.title, content)
// extract references and create refer comment
createReferComment(owner, name, issue, content.getOrElse(""))
redirect(s"/${owner}/${name}/issues/_data/${issue.issueId}")
} else Unauthorized
} getOrElse NotFound
}
})
post("/:owner/:repository/issue_comments/new", commentForm)(readableUsersOnly { (form, repository) =>
handleComment(form.issueId, Some(form.content), repository)() map { case (issue, id) =>
redirect(s"/${repository.owner}/${repository.name}/${
if(issue.isPullRequest) "pull" else "issues"}/${form.issueId}#comment-${id}")
} getOrElse NotFound
})
/**
* https://developer.github.com/v3/issues/comments/#create-a-comment
*/
post("/api/v3/repos/:owner/:repository/issues/:id/comments")(readableUsersOnly { repository =>
(for{
issueId <- params("id").toIntOpt
body <- extractFromJsonBody[CreateAComment].map(_.body) if ! body.isEmpty
(issue, id) <- handleComment(issueId, Some(body), repository)()
issueComment <- getComment(repository.owner, repository.name, id.toString())
} yield {
JsonFormat(ApiComment(issueComment, ApiUser(context.loginAccount.get)))
}) getOrElse NotFound
})
post("/:owner/:repository/issue_comments/state", issueStateForm)(readableUsersOnly { (form, repository) =>
handleComment(form.issueId, form.content, repository)() map { case (issue, id) =>
redirect(s"/${repository.owner}/${repository.name}/${
if(issue.isPullRequest) "pull" else "issues"}/${form.issueId}#comment-${id}")
} getOrElse NotFound
})
ajaxPost("/:owner/:repository/issue_comments/edit/:id", commentForm)(readableUsersOnly { (form, repository) =>
defining(repository.owner, repository.name){ case (owner, name) =>
getComment(owner, name, params("id")).map { comment =>
if(isEditable(owner, name, comment.commentedUserName)){
updateComment(comment.commentId, form.content)
redirect(s"/${owner}/${name}/issue_comments/_data/${comment.commentId}")
} else Unauthorized
} getOrElse NotFound
}
})
ajaxPost("/:owner/:repository/issue_comments/delete/:id")(readableUsersOnly { repository =>
defining(repository.owner, repository.name){ case (owner, name) =>
getComment(owner, name, params("id")).map { comment =>
if(isEditable(owner, name, comment.commentedUserName)){
Ok(deleteComment(comment.commentId))
} else Unauthorized
} getOrElse NotFound
}
})
ajaxGet("/:owner/:repository/issues/_data/:id")(readableUsersOnly { repository =>
getIssue(repository.owner, repository.name, params("id")) map { x =>
if(isEditable(x.userName, x.repositoryName, x.openedUserName)){
params.get("dataType") collect {
case t if t == "html" => html.editissue(
x.content, x.issueId, x.userName, x.repositoryName)
} getOrElse {
contentType = formats("json")
org.json4s.jackson.Serialization.write(
Map("title" -> x.title,
"content" -> Markdown.toHtml(x.content getOrElse "No description given.",
repository, false, true, true, isEditable(x.userName, x.repositoryName, x.openedUserName))
))
}
} else Unauthorized
} getOrElse NotFound
})
ajaxGet("/:owner/:repository/issue_comments/_data/:id")(readableUsersOnly { repository =>
getComment(repository.owner, repository.name, params("id")) map { x =>
if(isEditable(x.userName, x.repositoryName, x.commentedUserName)){
params.get("dataType") collect {
case t if t == "html" => html.editcomment(
x.content, x.commentId, x.userName, x.repositoryName)
} getOrElse {
contentType = formats("json")
org.json4s.jackson.Serialization.write(
Map("content" -> view.Markdown.toHtml(x.content,
repository, false, true, true, isEditable(x.userName, x.repositoryName, x.commentedUserName))
))
}
} else Unauthorized
} getOrElse NotFound
})
ajaxPost("/:owner/:repository/issues/:id/label/new")(collaboratorsOnly { repository =>
defining(params("id").toInt){ issueId =>
registerIssueLabel(repository.owner, repository.name, issueId, params("labelId").toInt)
html.labellist(getIssueLabels(repository.owner, repository.name, issueId))
}
})
ajaxPost("/:owner/:repository/issues/:id/label/delete")(collaboratorsOnly { repository =>
defining(params("id").toInt){ issueId =>
deleteIssueLabel(repository.owner, repository.name, issueId, params("labelId").toInt)
html.labellist(getIssueLabels(repository.owner, repository.name, issueId))
}
})
ajaxPost("/:owner/:repository/issues/:id/assign")(collaboratorsOnly { repository =>
updateAssignedUserName(repository.owner, repository.name, params("id").toInt, assignedUserName("assignedUserName"))
Ok("updated")
})
ajaxPost("/:owner/:repository/issues/:id/milestone")(collaboratorsOnly { repository =>
updateMilestoneId(repository.owner, repository.name, params("id").toInt, milestoneId("milestoneId"))
milestoneId("milestoneId").map { milestoneId =>
getMilestonesWithIssueCount(repository.owner, repository.name)
.find(_._1.milestoneId == milestoneId).map { case (_, openCount, closeCount) =>
gitbucket.core.issues.milestones.html.progress(openCount + closeCount, closeCount)
} getOrElse NotFound
} getOrElse Ok()
})
post("/:owner/:repository/issues/batchedit/state")(collaboratorsOnly { repository =>
defining(params.get("value")){ action =>
action match {
case Some("open") => executeBatch(repository) { handleComment(_, None, repository)( _ => Some("reopen")) }
case Some("close") => executeBatch(repository) { handleComment(_, None, repository)( _ => Some("close")) }
case _ => // TODO BadRequest
}
}
})
post("/:owner/:repository/issues/batchedit/label")(collaboratorsOnly { repository =>
params("value").toIntOpt.map{ labelId =>
executeBatch(repository) { issueId =>
getIssueLabel(repository.owner, repository.name, issueId, labelId) getOrElse {
registerIssueLabel(repository.owner, repository.name, issueId, labelId)
}
}
} getOrElse NotFound
})
post("/:owner/:repository/issues/batchedit/assign")(collaboratorsOnly { repository =>
defining(assignedUserName("value")){ value =>
executeBatch(repository) {
updateAssignedUserName(repository.owner, repository.name, _, value)
}
}
})
post("/:owner/:repository/issues/batchedit/milestone")(collaboratorsOnly { repository =>
defining(milestoneId("value")){ value =>
executeBatch(repository) {
updateMilestoneId(repository.owner, repository.name, _, value)
}
}
})
get("/:owner/:repository/_attached/:file")(referrersOnly { repository =>
(Directory.getAttachedDir(repository.owner, repository.name) match {
case dir if(dir.exists && dir.isDirectory) =>
dir.listFiles.find(_.getName.startsWith(params("file") + ".")).map { file =>
RawData(FileUtil.getMimeType(file.getName), file)
}
case _ => None
}) getOrElse NotFound
})
val assignedUserName = (key: String) => params.get(key) filter (_.trim != "")
val milestoneId: String => Option[Int] = (key: String) => params.get(key).flatMap(_.toIntOpt)
private def isEditable(owner: String, repository: String, author: String)(implicit context: Context): Boolean =
hasWritePermission(owner, repository, context.loginAccount) || author == context.loginAccount.get.userName
private def executeBatch(repository: RepositoryService.RepositoryInfo)(execute: Int => Unit) = {
params("checked").split(',') map(_.toInt) foreach execute
params("from") match {
case "issues" => redirect(s"/${repository.owner}/${repository.name}/issues")
case "pulls" => redirect(s"/${repository.owner}/${repository.name}/pulls")
}
}
private def createReferComment(owner: String, repository: String, fromIssue: Issue, message: String) = {
StringUtil.extractIssueId(message).foreach { issueId =>
if(getIssue(owner, repository, issueId).isDefined){
createComment(owner, repository, context.loginAccount.get.userName, issueId.toInt,
fromIssue.issueId + ":" + fromIssue.title, "refer")
}
}
}
/**
* @see [[https://github.com/takezoe/gitbucket/wiki/CommentAction]]
*/
private def handleComment(issueId: Int, content: Option[String], repository: RepositoryService.RepositoryInfo)
(getAction: Issue => Option[String] =
p1 => params.get("action").filter(_ => isEditable(p1.userName, p1.repositoryName, p1.openedUserName))) = {
defining(repository.owner, repository.name){ case (owner, name) =>
val userName = context.loginAccount.get.userName
getIssue(owner, name, issueId.toString) flatMap { issue =>
val (action, recordActivity) =
getAction(issue)
.collect {
case "close" if(!issue.closed) => true ->
(Some("close") -> Some(if(issue.isPullRequest) recordClosePullRequestActivity _ else recordCloseIssueActivity _))
case "reopen" if(issue.closed) => false ->
(Some("reopen") -> Some(recordReopenIssueActivity _))
}
.map { case (closed, t) =>
updateClosed(owner, name, issueId, closed)
t
}
.getOrElse(None -> None)
val commentId = (content, action) match {
case (None, None) => None
case (None, Some(action)) => Some(createComment(owner, name, userName, issueId, action.capitalize, action))
case (Some(content), _) => Some(createComment(owner, name, userName, issueId, content, action.map(_+ "_comment").getOrElse("comment")))
}
// record comment activity if comment is entered
content foreach {
(if(issue.isPullRequest) recordCommentPullRequestActivity _ else recordCommentIssueActivity _)
(owner, name, userName, issueId, _)
}
recordActivity foreach ( _ (owner, name, userName, issueId, issue.title) )
// extract references and create refer comment
content.map { content =>
createReferComment(owner, name, issue, content)
}
// call web hooks
action match {
case None => commentId.map{ commentIdSome => callIssueCommentWebHook(repository, issue, commentIdSome, context.loginAccount.get) }
case Some(act) => val webHookAction = act match {
case "open" => "opened"
case "reopen" => "reopened"
case "close" => "closed"
case _ => act
}
if(issue.isPullRequest){
callPullRequestWebHook(webHookAction, repository, issue.issueId, context.baseUrl, context.loginAccount.get)
} else {
callIssuesWebHook(webHookAction, repository, issue, context.baseUrl, context.loginAccount.get)
}
}
// notifications
Notifier() match {
case f =>
content foreach {
f.toNotify(repository, issueId, _){
Notifier.msgComment(s"${context.baseUrl}/${owner}/${name}/${
if(issue.isPullRequest) "pull" else "issues"}/${issueId}#comment-${commentId.get}")
}
}
action foreach {
f.toNotify(repository, issueId, _){
Notifier.msgStatus(s"${context.baseUrl}/${owner}/${name}/issues/${issueId}")
}
}
}
commentId.map( issue -> _ )
}
}
}
private def searchIssues(repository: RepositoryService.RepositoryInfo) = {
defining(repository.owner, repository.name){ case (owner, repoName) =>
val page = IssueSearchCondition.page(request)
val sessionKey = Keys.Session.Issues(owner, repoName)
// retrieve search condition
val condition = session.putAndGet(sessionKey,
if(request.hasQueryString){
val q = request.getParameter("q")
if(q == null || q.trim.isEmpty){
IssueSearchCondition(request)
} else {
IssueSearchCondition(q, getMilestones(owner, repoName).map(x => (x.title, x.milestoneId)).toMap)
}
} else session.getAs[IssueSearchCondition](sessionKey).getOrElse(IssueSearchCondition())
)
html.list(
"issues",
searchIssue(condition, false, (page - 1) * IssueLimit, IssueLimit, owner -> repoName),
page,
(getCollaborators(owner, repoName) :+ owner).sorted,
getMilestones(owner, repoName),
getLabels(owner, repoName),
countIssue(condition.copy(state = "open" ), false, owner -> repoName),
countIssue(condition.copy(state = "closed"), false, owner -> repoName),
condition,
repository,
hasWritePermission(owner, repoName, context.loginAccount))
}
}
}

View File

@@ -0,0 +1,83 @@
package gitbucket.core.controller
import gitbucket.core.issues.labels.html
import gitbucket.core.service.{RepositoryService, AccountService, IssuesService, LabelsService}
import gitbucket.core.util.{ReferrerAuthenticator, CollaboratorsAuthenticator}
import gitbucket.core.util.Implicits._
import jp.sf.amateras.scalatra.forms._
import org.scalatra.i18n.Messages
import org.scalatra.Ok
class LabelsController extends LabelsControllerBase
with LabelsService with IssuesService with RepositoryService with AccountService
with ReferrerAuthenticator with CollaboratorsAuthenticator
trait LabelsControllerBase extends ControllerBase {
self: LabelsService with IssuesService with RepositoryService
with ReferrerAuthenticator with CollaboratorsAuthenticator =>
case class LabelForm(labelName: String, color: String)
val labelForm = mapping(
"labelName" -> trim(label("Label name", text(required, labelName, maxlength(100)))),
"labelColor" -> trim(label("Color", text(required, color)))
)(LabelForm.apply)
get("/:owner/:repository/issues/labels")(referrersOnly { repository =>
html.list(
getLabels(repository.owner, repository.name),
countIssueGroupByLabels(repository.owner, repository.name, IssuesService.IssueSearchCondition(), Map.empty),
repository,
hasWritePermission(repository.owner, repository.name, context.loginAccount))
})
ajaxGet("/:owner/:repository/issues/labels/new")(collaboratorsOnly { repository =>
html.edit(None, repository)
})
ajaxPost("/:owner/:repository/issues/labels/new", labelForm)(collaboratorsOnly { (form, repository) =>
val labelId = createLabel(repository.owner, repository.name, form.labelName, form.color.substring(1))
html.label(
getLabel(repository.owner, repository.name, labelId).get,
// TODO futility
countIssueGroupByLabels(repository.owner, repository.name, IssuesService.IssueSearchCondition(), Map.empty),
repository,
hasWritePermission(repository.owner, repository.name, context.loginAccount))
})
ajaxGet("/:owner/:repository/issues/labels/:labelId/edit")(collaboratorsOnly { repository =>
getLabel(repository.owner, repository.name, params("labelId").toInt).map { label =>
html.edit(Some(label), repository)
} getOrElse NotFound()
})
ajaxPost("/:owner/:repository/issues/labels/:labelId/edit", labelForm)(collaboratorsOnly { (form, repository) =>
updateLabel(repository.owner, repository.name, params("labelId").toInt, form.labelName, form.color.substring(1))
html.label(
getLabel(repository.owner, repository.name, params("labelId").toInt).get,
// TODO futility
countIssueGroupByLabels(repository.owner, repository.name, IssuesService.IssueSearchCondition(), Map.empty),
repository,
hasWritePermission(repository.owner, repository.name, context.loginAccount))
})
ajaxPost("/:owner/:repository/issues/labels/:labelId/delete")(collaboratorsOnly { repository =>
deleteLabel(repository.owner, repository.name, params("labelId").toInt)
Ok()
})
/**
* Constraint for the identifier such as user name, repository name or page name.
*/
private def labelName: Constraint = new Constraint(){
override def validate(name: String, value: String, messages: Messages): Option[String] =
if(value.contains(',')){
Some(s"${name} contains invalid character.")
} else if(value.startsWith("_") || value.startsWith("-")){
Some(s"${name} starts with invalid character.")
} else {
None
}
}
}

View File

@@ -1,10 +1,11 @@
package app
package gitbucket.core.controller
import gitbucket.core.issues.milestones.html
import gitbucket.core.service.{RepositoryService, MilestonesService, AccountService}
import gitbucket.core.util.{ReferrerAuthenticator, CollaboratorsAuthenticator}
import gitbucket.core.util.Implicits._
import jp.sf.amateras.scalatra.forms._
import service._
import util.{CollaboratorsAuthenticator, ReferrerAuthenticator}
class MilestonesController extends MilestonesControllerBase
with MilestonesService with RepositoryService with AccountService
with ReferrerAuthenticator with CollaboratorsAuthenticator
@@ -22,7 +23,7 @@ trait MilestonesControllerBase extends ControllerBase {
)(MilestoneForm.apply)
get("/:owner/:repository/issues/milestones")(referrersOnly { repository =>
issues.milestones.html.list(
html.list(
params.getOrElse("state", "open"),
getMilestonesWithIssueCount(repository.owner, repository.name),
repository,
@@ -30,7 +31,7 @@ trait MilestonesControllerBase extends ControllerBase {
})
get("/:owner/:repository/issues/milestones/new")(collaboratorsOnly {
issues.milestones.html.edit(None, _)
html.edit(None, _)
})
post("/:owner/:repository/issues/milestones/new", milestoneForm)(collaboratorsOnly { (form, repository) =>
@@ -39,34 +40,44 @@ trait MilestonesControllerBase extends ControllerBase {
})
get("/:owner/:repository/issues/milestones/:milestoneId/edit")(collaboratorsOnly { repository =>
issues.milestones.html.edit(getMilestone(repository.owner, repository.name, params("milestoneId").toInt), repository)
params("milestoneId").toIntOpt.map{ milestoneId =>
html.edit(getMilestone(repository.owner, repository.name, milestoneId), repository)
} getOrElse NotFound
})
post("/:owner/:repository/issues/milestones/:milestoneId/edit", milestoneForm)(collaboratorsOnly { (form, repository) =>
getMilestone(repository.owner, repository.name, params("milestoneId").toInt).map { milestone =>
updateMilestone(milestone.copy(title = form.title, description = form.description, dueDate = form.dueDate))
redirect(s"/${repository.owner}/${repository.name}/issues/milestones")
params("milestoneId").toIntOpt.flatMap{ milestoneId =>
getMilestone(repository.owner, repository.name, milestoneId).map { milestone =>
updateMilestone(milestone.copy(title = form.title, description = form.description, dueDate = form.dueDate))
redirect(s"/${repository.owner}/${repository.name}/issues/milestones")
}
} getOrElse NotFound
})
get("/:owner/:repository/issues/milestones/:milestoneId/close")(collaboratorsOnly { repository =>
getMilestone(repository.owner, repository.name, params("milestoneId").toInt).map { milestone =>
closeMilestone(milestone)
redirect(s"/${repository.owner}/${repository.name}/issues/milestones")
params("milestoneId").toIntOpt.flatMap{ milestoneId =>
getMilestone(repository.owner, repository.name, milestoneId).map { milestone =>
closeMilestone(milestone)
redirect(s"/${repository.owner}/${repository.name}/issues/milestones")
}
} getOrElse NotFound
})
get("/:owner/:repository/issues/milestones/:milestoneId/open")(collaboratorsOnly { repository =>
getMilestone(repository.owner, repository.name, params("milestoneId").toInt).map { milestone =>
openMilestone(milestone)
redirect(s"/${repository.owner}/${repository.name}/issues/milestones")
params("milestoneId").toIntOpt.flatMap{ milestoneId =>
getMilestone(repository.owner, repository.name, milestoneId).map { milestone =>
openMilestone(milestone)
redirect(s"/${repository.owner}/${repository.name}/issues/milestones")
}
} getOrElse NotFound
})
get("/:owner/:repository/issues/milestones/:milestoneId/delete")(collaboratorsOnly { repository =>
getMilestone(repository.owner, repository.name, params("milestoneId").toInt).map { milestone =>
deleteMilestone(repository.owner, repository.name, milestone.milestoneId)
redirect(s"/${repository.owner}/${repository.name}/issues/milestones")
params("milestoneId").toIntOpt.flatMap{ milestoneId =>
getMilestone(repository.owner, repository.name, milestoneId).map { milestone =>
deleteMilestone(repository.owner, repository.name, milestone.milestoneId)
redirect(s"/${repository.owner}/${repository.name}/issues/milestones")
}
} getOrElse NotFound
})

View File

@@ -0,0 +1,462 @@
package gitbucket.core.controller
import gitbucket.core.api._
import gitbucket.core.model.{Account, CommitState, Repository, PullRequest, Issue}
import gitbucket.core.pulls.html
import gitbucket.core.service.CommitStatusService
import gitbucket.core.service.MergeService
import gitbucket.core.service.IssuesService._
import gitbucket.core.service.PullRequestService._
import gitbucket.core.service._
import gitbucket.core.util.ControlUtil._
import gitbucket.core.util.Directory._
import gitbucket.core.util.Implicits._
import gitbucket.core.util.JGitUtil._
import gitbucket.core.util._
import gitbucket.core.view
import gitbucket.core.view.helpers
import jp.sf.amateras.scalatra.forms._
import org.eclipse.jgit.api.Git
import org.eclipse.jgit.lib.PersonIdent
import org.slf4j.LoggerFactory
import scala.collection.JavaConverters._
class PullRequestsController extends PullRequestsControllerBase
with RepositoryService with AccountService with IssuesService with PullRequestService with MilestonesService with LabelsService
with CommitsService with ActivityService with WebHookPullRequestService with ReferrerAuthenticator with CollaboratorsAuthenticator
with CommitStatusService with MergeService
trait PullRequestsControllerBase extends ControllerBase {
self: RepositoryService with AccountService with IssuesService with MilestonesService with LabelsService
with CommitsService with ActivityService with PullRequestService with WebHookPullRequestService with ReferrerAuthenticator with CollaboratorsAuthenticator
with CommitStatusService with MergeService =>
private val logger = LoggerFactory.getLogger(classOf[PullRequestsControllerBase])
val pullRequestForm = mapping(
"title" -> trim(label("Title" , text(required, maxlength(100)))),
"content" -> trim(label("Content", optional(text()))),
"targetUserName" -> trim(text(required, maxlength(100))),
"targetBranch" -> trim(text(required, maxlength(100))),
"requestUserName" -> trim(text(required, maxlength(100))),
"requestRepositoryName" -> trim(text(required, maxlength(100))),
"requestBranch" -> trim(text(required, maxlength(100))),
"commitIdFrom" -> trim(text(required, maxlength(40))),
"commitIdTo" -> trim(text(required, maxlength(40)))
)(PullRequestForm.apply)
val mergeForm = mapping(
"message" -> trim(label("Message", text(required)))
)(MergeForm.apply)
case class PullRequestForm(
title: String,
content: Option[String],
targetUserName: String,
targetBranch: String,
requestUserName: String,
requestRepositoryName: String,
requestBranch: String,
commitIdFrom: String,
commitIdTo: String)
case class MergeForm(message: String)
get("/:owner/:repository/pulls")(referrersOnly { repository =>
val q = request.getParameter("q")
if(Option(q).exists(_.contains("is:issue"))){
redirect(s"/${repository.owner}/${repository.name}/issues?q=" + StringUtil.urlEncode(q))
} else {
searchPullRequests(None, repository)
}
})
/**
* https://developer.github.com/v3/pulls/#list-pull-requests
*/
get("/api/v3/repos/:owner/:repository/pulls")(referrersOnly { repository =>
val page = IssueSearchCondition.page(request)
// TODO: more api spec condition
val condition = IssueSearchCondition(request)
val baseOwner = getAccountByUserName(repository.owner).get
val issues:List[(Issue, Account, Int, PullRequest, Repository, Account)] = searchPullRequestByApi(condition, (page - 1) * PullRequestLimit, PullRequestLimit, repository.owner -> repository.name)
JsonFormat(issues.map{case (issue, issueUser, commentCount, pullRequest, headRepo, headOwner) =>
ApiPullRequest(
issue,
pullRequest,
ApiRepository(headRepo, ApiUser(headOwner)),
ApiRepository(repository, ApiUser(baseOwner)),
ApiUser(issueUser)) })
})
get("/:owner/:repository/pull/:id")(referrersOnly { repository =>
params("id").toIntOpt.flatMap{ issueId =>
val owner = repository.owner
val name = repository.name
getPullRequest(owner, name, issueId) map { case(issue, pullreq) =>
using(Git.open(getRepositoryDir(owner, name))){ git =>
val (commits, diffs) =
getRequestCompareInfo(owner, name, pullreq.commitIdFrom, owner, name, pullreq.commitIdTo)
html.pullreq(
issue, pullreq,
(commits.flatten.map(commit => getCommitComments(owner, name, commit.id, true)).flatten.toList ::: getComments(owner, name, issueId))
.sortWith((a, b) => a.registeredDate before b.registeredDate),
getIssueLabels(owner, name, issueId),
(getCollaborators(owner, name) ::: (if(getAccountByUserName(owner).get.isGroupAccount) Nil else List(owner))).sorted,
getMilestonesWithIssueCount(owner, name),
getLabels(owner, name),
commits,
diffs,
hasWritePermission(owner, name, context.loginAccount),
repository)
}
}
} getOrElse NotFound
})
/**
* https://developer.github.com/v3/pulls/#get-a-single-pull-request
*/
get("/api/v3/repos/:owner/:repository/pulls/:id")(referrersOnly { repository =>
(for{
issueId <- params("id").toIntOpt
(issue, pullRequest) <- getPullRequest(repository.owner, repository.name, issueId)
users = getAccountsByUserNames(Set(repository.owner, pullRequest.requestUserName, issue.userName), Set())
baseOwner <- users.get(repository.owner)
headOwner <- users.get(pullRequest.requestUserName)
issueUser <- users.get(issue.userName)
headRepo <- getRepository(pullRequest.requestUserName, pullRequest.requestRepositoryName, baseUrl)
} yield {
JsonFormat(ApiPullRequest(
issue,
pullRequest,
ApiRepository(headRepo, ApiUser(headOwner)),
ApiRepository(repository, ApiUser(baseOwner)),
ApiUser(issueUser)))
}).getOrElse(NotFound)
})
/**
* https://developer.github.com/v3/pulls/#list-commits-on-a-pull-request
*/
get("/api/v3/repos/:owner/:repository/pulls/:id/commits")(referrersOnly { repository =>
val owner = repository.owner
val name = repository.name
params("id").toIntOpt.flatMap{ issueId =>
getPullRequest(owner, name, issueId) map { case(issue, pullreq) =>
using(Git.open(getRepositoryDir(owner, name))){ git =>
val oldId = git.getRepository.resolve(pullreq.commitIdFrom)
val newId = git.getRepository.resolve(pullreq.commitIdTo)
val repoFullName = RepositoryName(repository)
val commits = git.log.addRange(oldId, newId).call.iterator.asScala.map(c => ApiCommitListItem(new CommitInfo(c), repoFullName)).toList
JsonFormat(commits)
}
}
} getOrElse NotFound
})
ajaxGet("/:owner/:repository/pull/:id/mergeguide")(collaboratorsOnly { repository =>
params("id").toIntOpt.flatMap{ issueId =>
val owner = repository.owner
val name = repository.name
getPullRequest(owner, name, issueId) map { case(issue, pullreq) =>
val statuses = getCommitStatues(owner, name, pullreq.commitIdTo)
val hasConfrict = LockUtil.lock(s"${owner}/${name}"){
checkConflict(owner, name, pullreq.branch, issueId)
}
val hasProblem = hasConfrict || (!statuses.isEmpty && CommitState.combine(statuses.map(_.state).toSet) != CommitState.SUCCESS)
html.mergeguide(
hasConfrict,
hasProblem,
issue,
pullreq,
statuses,
repository,
s"${context.baseUrl}/git/${pullreq.requestUserName}/${pullreq.requestRepositoryName}.git")
}
} getOrElse NotFound
})
get("/:owner/:repository/pull/:id/delete/*")(collaboratorsOnly { repository =>
params("id").toIntOpt.map { issueId =>
val branchName = multiParams("splat").head
val userName = context.loginAccount.get.userName
if(repository.repository.defaultBranch != branchName){
using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git =>
git.branchDelete().setForce(true).setBranchNames(branchName).call()
recordDeleteBranchActivity(repository.owner, repository.name, userName, branchName)
}
}
createComment(repository.owner, repository.name, userName, issueId, branchName, "delete_branch")
redirect(s"/${repository.owner}/${repository.name}/pull/${issueId}")
} getOrElse NotFound
})
post("/:owner/:repository/pull/:id/merge", mergeForm)(collaboratorsOnly { (form, repository) =>
params("id").toIntOpt.flatMap { issueId =>
val owner = repository.owner
val name = repository.name
LockUtil.lock(s"${owner}/${name}"){
getPullRequest(owner, name, issueId).map { case (issue, pullreq) =>
using(Git.open(getRepositoryDir(owner, name))) { git =>
// mark issue as merged and close.
val loginAccount = context.loginAccount.get
createComment(owner, name, loginAccount.userName, issueId, form.message, "merge")
createComment(owner, name, loginAccount.userName, issueId, "Close", "close")
updateClosed(owner, name, issueId, true)
// record activity
recordMergeActivity(owner, name, loginAccount.userName, issueId, form.message)
// merge git repository
mergePullRequest(git, pullreq.branch, issueId,
s"Merge pull request #${issueId} from ${pullreq.requestUserName}/${pullreq.requestBranch}\n\n" + form.message,
new PersonIdent(loginAccount.fullName, loginAccount.mailAddress))
val (commits, _) = getRequestCompareInfo(owner, name, pullreq.commitIdFrom,
pullreq.requestUserName, pullreq.requestRepositoryName, pullreq.commitIdTo)
// close issue by content of pull request
val defaultBranch = getRepository(owner, name, context.baseUrl).get.repository.defaultBranch
if(pullreq.branch == defaultBranch){
commits.flatten.foreach { commit =>
closeIssuesFromMessage(commit.fullMessage, loginAccount.userName, owner, name)
}
issue.content match {
case Some(content) => closeIssuesFromMessage(content, loginAccount.userName, owner, name)
case _ =>
}
closeIssuesFromMessage(form.message, loginAccount.userName, owner, name)
}
// call web hook
callPullRequestWebHook("closed", repository, issueId, context.baseUrl, context.loginAccount.get)
// notifications
Notifier().toNotify(repository, issueId, "merge"){
Notifier.msgStatus(s"${context.baseUrl}/${owner}/${name}/pull/${issueId}")
}
redirect(s"/${owner}/${name}/pull/${issueId}")
}
}
}
} getOrElse NotFound
})
get("/:owner/:repository/compare")(referrersOnly { forkedRepository =>
(forkedRepository.repository.originUserName, forkedRepository.repository.originRepositoryName) match {
case (Some(originUserName), Some(originRepositoryName)) => {
getRepository(originUserName, originRepositoryName, context.baseUrl).map { originRepository =>
using(
Git.open(getRepositoryDir(originUserName, originRepositoryName)),
Git.open(getRepositoryDir(forkedRepository.owner, forkedRepository.name))
){ (oldGit, newGit) =>
val oldBranch = JGitUtil.getDefaultBranch(oldGit, originRepository).get._2
val newBranch = JGitUtil.getDefaultBranch(newGit, forkedRepository).get._2
redirect(s"/${forkedRepository.owner}/${forkedRepository.name}/compare/${originUserName}:${oldBranch}...${newBranch}")
}
} getOrElse NotFound
}
case _ => {
using(Git.open(getRepositoryDir(forkedRepository.owner, forkedRepository.name))){ git =>
JGitUtil.getDefaultBranch(git, forkedRepository).map { case (_, defaultBranch) =>
redirect(s"/${forkedRepository.owner}/${forkedRepository.name}/compare/${defaultBranch}...${defaultBranch}")
} getOrElse {
redirect(s"/${forkedRepository.owner}/${forkedRepository.name}")
}
}
}
}
})
get("/:owner/:repository/compare/*...*")(referrersOnly { forkedRepository =>
val Seq(origin, forked) = multiParams("splat")
val (originOwner, originId) = parseCompareIdentifie(origin, forkedRepository.owner)
val (forkedOwner, forkedId) = parseCompareIdentifie(forked, forkedRepository.owner)
(for(
originRepositoryName <- if(originOwner == forkedOwner){
Some(forkedRepository.name)
} else {
forkedRepository.repository.originRepositoryName.orElse {
getForkedRepositories(forkedRepository.owner, forkedRepository.name).find(_._1 == originOwner).map(_._2)
}
};
originRepository <- getRepository(originOwner, originRepositoryName, context.baseUrl)
) yield {
using(
Git.open(getRepositoryDir(originRepository.owner, originRepository.name)),
Git.open(getRepositoryDir(forkedRepository.owner, forkedRepository.name))
){ case (oldGit, newGit) =>
val (oldId, newId) =
if(originRepository.branchList.contains(originId) && forkedRepository.branchList.contains(forkedId)){
// Branch name
val rootId = JGitUtil.getForkedCommitId(oldGit, newGit,
originRepository.owner, originRepository.name, originId,
forkedRepository.owner, forkedRepository.name, forkedId)
(oldGit.getRepository.resolve(rootId), newGit.getRepository.resolve(forkedId))
} else {
// Commit id
(oldGit.getRepository.resolve(originId), newGit.getRepository.resolve(forkedId))
}
val (commits, diffs) = getRequestCompareInfo(
originRepository.owner, originRepository.name, oldId.getName,
forkedRepository.owner, forkedRepository.name, newId.getName)
html.compare(
commits,
diffs,
(forkedRepository.repository.originUserName, forkedRepository.repository.originRepositoryName) match {
case (Some(userName), Some(repositoryName)) => (userName, repositoryName) :: getForkedRepositories(userName, repositoryName)
case _ => (forkedRepository.owner, forkedRepository.name) :: getForkedRepositories(forkedRepository.owner, forkedRepository.name)
},
commits.flatten.map(commit => getCommitComments(forkedRepository.owner, forkedRepository.name, commit.id, false)).flatten.toList,
originId,
forkedId,
oldId.getName,
newId.getName,
forkedRepository,
originRepository,
forkedRepository,
hasWritePermission(forkedRepository.owner, forkedRepository.name, context.loginAccount))
}
}) getOrElse NotFound
})
ajaxGet("/:owner/:repository/compare/*...*/mergecheck")(collaboratorsOnly { forkedRepository =>
val Seq(origin, forked) = multiParams("splat")
val (originOwner, tmpOriginBranch) = parseCompareIdentifie(origin, forkedRepository.owner)
val (forkedOwner, tmpForkedBranch) = parseCompareIdentifie(forked, forkedRepository.owner)
(for(
originRepositoryName <- if(originOwner == forkedOwner){
Some(forkedRepository.name)
} else {
forkedRepository.repository.originRepositoryName.orElse {
getForkedRepositories(forkedRepository.owner, forkedRepository.name).find(_._1 == originOwner).map(_._2)
}
};
originRepository <- getRepository(originOwner, originRepositoryName, context.baseUrl)
) yield {
using(
Git.open(getRepositoryDir(originRepository.owner, originRepository.name)),
Git.open(getRepositoryDir(forkedRepository.owner, forkedRepository.name))
){ case (oldGit, newGit) =>
val originBranch = JGitUtil.getDefaultBranch(oldGit, originRepository, tmpOriginBranch).get._2
val forkedBranch = JGitUtil.getDefaultBranch(newGit, forkedRepository, tmpForkedBranch).get._2
val conflict = LockUtil.lock(s"${originRepository.owner}/${originRepository.name}"){
checkConflict(originRepository.owner, originRepository.name, originBranch,
forkedRepository.owner, forkedRepository.name, forkedBranch)
}
html.mergecheck(conflict)
}
}) getOrElse NotFound
})
post("/:owner/:repository/pulls/new", pullRequestForm)(referrersOnly { (form, repository) =>
val loginUserName = context.loginAccount.get.userName
val issueId = createIssue(
owner = repository.owner,
repository = repository.name,
loginUser = loginUserName,
title = form.title,
content = form.content,
assignedUserName = None,
milestoneId = None,
isPullRequest = true)
createPullRequest(
originUserName = repository.owner,
originRepositoryName = repository.name,
issueId = issueId,
originBranch = form.targetBranch,
requestUserName = form.requestUserName,
requestRepositoryName = form.requestRepositoryName,
requestBranch = form.requestBranch,
commitIdFrom = form.commitIdFrom,
commitIdTo = form.commitIdTo)
// fetch requested branch
fetchAsPullRequest(repository.owner, repository.name, form.requestUserName, form.requestRepositoryName, form.requestBranch, issueId)
// record activity
recordPullRequestActivity(repository.owner, repository.name, loginUserName, issueId, form.title)
// call web hook
callPullRequestWebHook("opened", repository, issueId, context.baseUrl, context.loginAccount.get)
// notifications
Notifier().toNotify(repository, issueId, form.content.getOrElse("")){
Notifier.msgPullRequest(s"${context.baseUrl}/${repository.owner}/${repository.name}/pull/${issueId}")
}
redirect(s"/${repository.owner}/${repository.name}/pull/${issueId}")
})
/**
* Parses branch identifier and extracts owner and branch name as tuple.
*
* - "owner:branch" to ("owner", "branch")
* - "branch" to ("defaultOwner", "branch")
*/
private def parseCompareIdentifie(value: String, defaultOwner: String): (String, String) =
if(value.contains(':')){
val array = value.split(":")
(array(0), array(1))
} else {
(defaultOwner, value)
}
private def getRequestCompareInfo(userName: String, repositoryName: String, branch: String,
requestUserName: String, requestRepositoryName: String, requestCommitId: String): (Seq[Seq[CommitInfo]], Seq[DiffInfo]) =
using(
Git.open(getRepositoryDir(userName, repositoryName)),
Git.open(getRepositoryDir(requestUserName, requestRepositoryName))
){ (oldGit, newGit) =>
val oldId = oldGit.getRepository.resolve(branch)
val newId = newGit.getRepository.resolve(requestCommitId)
val commits = newGit.log.addRange(oldId, newId).call.iterator.asScala.map { revCommit =>
new CommitInfo(revCommit)
}.toList.splitWith { (commit1, commit2) =>
helpers.date(commit1.commitTime) == view.helpers.date(commit2.commitTime)
}
val diffs = JGitUtil.getDiffs(newGit, oldId.getName, newId.getName, true)
(commits, diffs)
}
private def searchPullRequests(userName: Option[String], repository: RepositoryService.RepositoryInfo) =
defining(repository.owner, repository.name){ case (owner, repoName) =>
val page = IssueSearchCondition.page(request)
val sessionKey = Keys.Session.Pulls(owner, repoName)
// retrieve search condition
val condition = session.putAndGet(sessionKey,
if(request.hasQueryString) IssueSearchCondition(request)
else session.getAs[IssueSearchCondition](sessionKey).getOrElse(IssueSearchCondition())
)
gitbucket.core.issues.html.list(
"pulls",
searchIssue(condition, true, (page - 1) * PullRequestLimit, PullRequestLimit, owner -> repoName),
page,
(getCollaborators(owner, repoName) :+ owner).sorted,
getMilestones(owner, repoName),
getLabels(owner, repoName),
countIssue(condition.copy(state = "open" ), true, owner -> repoName),
countIssue(condition.copy(state = "closed"), true, owner -> repoName),
condition,
repository,
hasWritePermission(owner, repoName, context.loginAccount))
}
}

View File

@@ -0,0 +1,277 @@
package gitbucket.core.controller
import gitbucket.core.settings.html
import gitbucket.core.model.WebHook
import gitbucket.core.service.{RepositoryService, AccountService, WebHookService}
import gitbucket.core.service.WebHookService._
import gitbucket.core.util._
import gitbucket.core.util.JGitUtil._
import gitbucket.core.util.ControlUtil._
import gitbucket.core.util.Implicits._
import gitbucket.core.util.Directory._
import jp.sf.amateras.scalatra.forms._
import org.apache.commons.io.FileUtils
import org.scalatra.i18n.Messages
import org.eclipse.jgit.api.Git
import org.eclipse.jgit.lib.Constants
class RepositorySettingsController extends RepositorySettingsControllerBase
with RepositoryService with AccountService with WebHookService
with OwnerAuthenticator with UsersAuthenticator
trait RepositorySettingsControllerBase extends ControllerBase {
self: RepositoryService with AccountService with WebHookService
with OwnerAuthenticator with UsersAuthenticator =>
// for repository options
case class OptionsForm(repositoryName: String, description: Option[String], defaultBranch: String, isPrivate: Boolean)
val optionsForm = mapping(
"repositoryName" -> trim(label("Description" , text(required, maxlength(40), identifier, renameRepositoryName))),
"description" -> trim(label("Description" , optional(text()))),
"defaultBranch" -> trim(label("Default Branch" , text(required, maxlength(100)))),
"isPrivate" -> trim(label("Repository Type", boolean()))
)(OptionsForm.apply)
// for collaborator addition
case class CollaboratorForm(userName: String)
val collaboratorForm = mapping(
"userName" -> trim(label("Username", text(required, collaborator)))
)(CollaboratorForm.apply)
// for web hook url addition
case class WebHookForm(url: String)
val webHookForm = mapping(
"url" -> trim(label("url", text(required, webHook)))
)(WebHookForm.apply)
// for transfer ownership
case class TransferOwnerShipForm(newOwner: String)
val transferForm = mapping(
"newOwner" -> trim(label("New owner", text(required, transferUser)))
)(TransferOwnerShipForm.apply)
/**
* Redirect to the Options page.
*/
get("/:owner/:repository/settings")(ownerOnly { repository =>
redirect(s"/${repository.owner}/${repository.name}/settings/options")
})
/**
* Display the Options page.
*/
get("/:owner/:repository/settings/options")(ownerOnly {
html.options(_, flash.get("info"))
})
/**
* Save the repository options.
*/
post("/:owner/:repository/settings/options", optionsForm)(ownerOnly { (form, repository) =>
val defaultBranch = if(repository.branchList.isEmpty) "master" else form.defaultBranch
saveRepositoryOptions(
repository.owner,
repository.name,
form.description,
defaultBranch,
repository.repository.parentUserName.map { _ =>
repository.repository.isPrivate
} getOrElse form.isPrivate
)
// Change repository name
if(repository.name != form.repositoryName){
// Update database
renameRepository(repository.owner, repository.name, repository.owner, form.repositoryName)
// Move git repository
defining(getRepositoryDir(repository.owner, repository.name)){ dir =>
FileUtils.moveDirectory(dir, getRepositoryDir(repository.owner, form.repositoryName))
}
// Move wiki repository
defining(getWikiRepositoryDir(repository.owner, repository.name)){ dir =>
FileUtils.moveDirectory(dir, getWikiRepositoryDir(repository.owner, form.repositoryName))
}
}
// Change repository HEAD
using(Git.open(getRepositoryDir(repository.owner, form.repositoryName))) { git =>
git.getRepository.updateRef(Constants.HEAD, true).link(Constants.R_HEADS + defaultBranch)
}
flash += "info" -> "Repository settings has been updated."
redirect(s"/${repository.owner}/${form.repositoryName}/settings/options")
})
/**
* Display the Collaborators page.
*/
get("/:owner/:repository/settings/collaborators")(ownerOnly { repository =>
html.collaborators(
getCollaborators(repository.owner, repository.name),
getAccountByUserName(repository.owner).get.isGroupAccount,
repository)
})
/**
* Add the collaborator.
*/
post("/:owner/:repository/settings/collaborators/add", collaboratorForm)(ownerOnly { (form, repository) =>
if(!getAccountByUserName(repository.owner).get.isGroupAccount){
addCollaborator(repository.owner, repository.name, form.userName)
}
redirect(s"/${repository.owner}/${repository.name}/settings/collaborators")
})
/**
* Add the collaborator.
*/
get("/:owner/:repository/settings/collaborators/remove")(ownerOnly { repository =>
if(!getAccountByUserName(repository.owner).get.isGroupAccount){
removeCollaborator(repository.owner, repository.name, params("name"))
}
redirect(s"/${repository.owner}/${repository.name}/settings/collaborators")
})
/**
* Display the web hook page.
*/
get("/:owner/:repository/settings/hooks")(ownerOnly { repository =>
html.hooks(getWebHookURLs(repository.owner, repository.name), flash.get("url"), repository, flash.get("info"))
})
/**
* Add the web hook URL.
*/
post("/:owner/:repository/settings/hooks/add", webHookForm)(ownerOnly { (form, repository) =>
addWebHookURL(repository.owner, repository.name, form.url)
redirect(s"/${repository.owner}/${repository.name}/settings/hooks")
})
/**
* Delete the web hook URL.
*/
get("/:owner/:repository/settings/hooks/delete")(ownerOnly { repository =>
deleteWebHookURL(repository.owner, repository.name, params("url"))
redirect(s"/${repository.owner}/${repository.name}/settings/hooks")
})
/**
* Send the test request to registered web hook URLs.
*/
post("/:owner/:repository/settings/hooks/test", webHookForm)(ownerOnly { (form, repository) =>
using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git =>
import scala.collection.JavaConverters._
val commits = git.log
.add(git.getRepository.resolve(repository.repository.defaultBranch))
.setMaxCount(3)
.call.iterator.asScala.map(new CommitInfo(_))
getAccountByUserName(repository.owner).foreach { ownerAccount =>
callWebHook("push",
List(WebHook(repository.owner, repository.name, form.url)),
WebHookPushPayload(git, ownerAccount, "refs/heads/" + repository.repository.defaultBranch, repository, commits.toList, ownerAccount)
)
}
flash += "url" -> form.url
flash += "info" -> "Test payload deployed!"
}
redirect(s"/${repository.owner}/${repository.name}/settings/hooks")
})
/**
* Display the danger zone.
*/
get("/:owner/:repository/settings/danger")(ownerOnly {
html.danger(_)
})
/**
* Transfer repository ownership.
*/
post("/:owner/:repository/settings/transfer", transferForm)(ownerOnly { (form, repository) =>
// Change repository owner
if(repository.owner != form.newOwner){
LockUtil.lock(s"${repository.owner}/${repository.name}"){
// Update database
renameRepository(repository.owner, repository.name, form.newOwner, repository.name)
// Move git repository
defining(getRepositoryDir(repository.owner, repository.name)){ dir =>
FileUtils.moveDirectory(dir, getRepositoryDir(form.newOwner, repository.name))
}
// Move wiki repository
defining(getWikiRepositoryDir(repository.owner, repository.name)){ dir =>
FileUtils.moveDirectory(dir, getWikiRepositoryDir(form.newOwner, repository.name))
}
}
}
redirect(s"/${form.newOwner}/${repository.name}")
})
/**
* Delete the repository.
*/
post("/:owner/:repository/settings/delete")(ownerOnly { repository =>
LockUtil.lock(s"${repository.owner}/${repository.name}"){
deleteRepository(repository.owner, repository.name)
FileUtils.deleteDirectory(getRepositoryDir(repository.owner, repository.name))
FileUtils.deleteDirectory(getWikiRepositoryDir(repository.owner, repository.name))
FileUtils.deleteDirectory(getTemporaryDir(repository.owner, repository.name))
}
redirect(s"/${repository.owner}")
})
/**
* Provides duplication check for web hook url.
*/
private def webHook: Constraint = new Constraint(){
override def validate(name: String, value: String, messages: Messages): Option[String] =
getWebHookURLs(params("owner"), params("repository")).map(_.url).find(_ == value).map(_ => "URL had been registered already.")
}
/**
* Provides Constraint to validate the collaborator name.
*/
private def collaborator: Constraint = new Constraint(){
override def validate(name: String, value: String, messages: Messages): Option[String] =
getAccountByUserName(value) match {
case None => Some("User does not exist.")
case Some(x) if(x.isGroupAccount)
=> Some("User does not exist.")
case Some(x) if(x.userName == params("owner") || getCollaborators(params("owner"), params("repository")).contains(x.userName))
=> Some("User can access this repository already.")
case _ => None
}
}
/**
* Duplicate check for the rename repository name.
*/
private def renameRepositoryName: Constraint = new Constraint(){
override def validate(name: String, value: String, params: Map[String, String], messages: Messages): Option[String] =
params.get("repository").filter(_ != value).flatMap { _ =>
params.get("owner").flatMap { userName =>
getRepositoryNamesOfUser(userName).find(_ == value).map(_ => "Repository already exists.")
}
}
}
/**
* Provides Constraint to validate the repository transfer user.
*/
private def transferUser: Constraint = new Constraint(){
override def validate(name: String, value: String, messages: Messages): Option[String] =
getAccountByUserName(value) match {
case None => Some("User does not exist.")
case Some(x) => if(x.userName == params("owner")){
Some("This is current repository owner.")
} else {
params.get("repository").flatMap { repositoryName =>
getRepositoryNamesOfUser(x.userName).find(_ == repositoryName).map{ _ => "User already has same repository." }
}
}
}
}
}

View File

@@ -0,0 +1,643 @@
package gitbucket.core.controller
import gitbucket.core.api._
import gitbucket.core.repo.html
import gitbucket.core.helper
import gitbucket.core.service._
import gitbucket.core.util._
import gitbucket.core.util.JGitUtil._
import gitbucket.core.util.StringUtil._
import gitbucket.core.util.ControlUtil._
import gitbucket.core.util.Implicits._
import gitbucket.core.util.Directory._
import gitbucket.core.model.{Account, CommitState}
import gitbucket.core.service.CommitStatusService
import gitbucket.core.service.WebHookService._
import gitbucket.core.view
import gitbucket.core.view.helpers
import jp.sf.amateras.scalatra.forms._
import org.apache.commons.io.FileUtils
import org.eclipse.jgit.api.{ArchiveCommand, Git}
import org.eclipse.jgit.archive.{TgzFormat, ZipFormat}
import org.eclipse.jgit.dircache.DirCache
import org.eclipse.jgit.lib._
import org.eclipse.jgit.revwalk.RevCommit
import org.eclipse.jgit.treewalk._
import org.scalatra._
class RepositoryViewerController extends RepositoryViewerControllerBase
with RepositoryService with AccountService with ActivityService with IssuesService with WebHookService with CommitsService
with ReadableUsersAuthenticator with ReferrerAuthenticator with CollaboratorsAuthenticator with PullRequestService with CommitStatusService
with WebHookPullRequestService
/**
* The repository viewer.
*/
trait RepositoryViewerControllerBase extends ControllerBase {
self: RepositoryService with AccountService with ActivityService with IssuesService with WebHookService with CommitsService
with ReadableUsersAuthenticator with ReferrerAuthenticator with CollaboratorsAuthenticator with PullRequestService with CommitStatusService
with WebHookPullRequestService =>
ArchiveCommand.registerFormat("zip", new ZipFormat)
ArchiveCommand.registerFormat("tar.gz", new TgzFormat)
case class EditorForm(
branch: String,
path: String,
content: String,
message: Option[String],
charset: String,
lineSeparator: String,
newFileName: String,
oldFileName: Option[String]
)
case class DeleteForm(
branch: String,
path: String,
message: Option[String],
fileName: String
)
case class CommentForm(
fileName: Option[String],
oldLineNumber: Option[Int],
newLineNumber: Option[Int],
content: String,
issueId: Option[Int]
)
val editorForm = mapping(
"branch" -> trim(label("Branch", text(required))),
"path" -> trim(label("Path", text())),
"content" -> trim(label("Content", text(required))),
"message" -> trim(label("Message", optional(text()))),
"charset" -> trim(label("Charset", text(required))),
"lineSeparator" -> trim(label("Line Separator", text(required))),
"newFileName" -> trim(label("Filename", text(required))),
"oldFileName" -> trim(label("Old filename", optional(text())))
)(EditorForm.apply)
val deleteForm = mapping(
"branch" -> trim(label("Branch", text(required))),
"path" -> trim(label("Path", text())),
"message" -> trim(label("Message", optional(text()))),
"fileName" -> trim(label("Filename", text(required)))
)(DeleteForm.apply)
val commentForm = mapping(
"fileName" -> trim(label("Filename", optional(text()))),
"oldLineNumber" -> trim(label("Old line number", optional(number()))),
"newLineNumber" -> trim(label("New line number", optional(number()))),
"content" -> trim(label("Content", text(required))),
"issueId" -> trim(label("Issue Id", optional(number())))
)(CommentForm.apply)
/**
* Returns converted HTML from Markdown for preview.
*/
post("/:owner/:repository/_preview")(referrersOnly { repository =>
contentType = "text/html"
helpers.markdown(params("content"), repository,
params("enableWikiLink").toBoolean,
params("enableRefsLink").toBoolean,
params("enableTaskList").toBoolean,
hasWritePermission(repository.owner, repository.name, context.loginAccount))
})
/**
* Displays the file list of the repository root and the default branch.
*/
get("/:owner/:repository")(referrersOnly {
fileList(_)
})
/**
* https://developer.github.com/v3/repos/#get
*/
get("/api/v3/repos/:owner/:repository")(referrersOnly { repository =>
JsonFormat(ApiRepository(repository, ApiUser(getAccountByUserName(repository.owner).get)))
})
/**
* Displays the file list of the specified path and branch.
*/
get("/:owner/:repository/tree/*")(referrersOnly { repository =>
val (id, path) = splitPath(repository, multiParams("splat").head)
if(path.isEmpty){
fileList(repository, id)
} else {
fileList(repository, id, path)
}
})
/**
* Displays the commit list of the specified resource.
*/
get("/:owner/:repository/commits/*")(referrersOnly { repository =>
val (branchName, path) = splitPath(repository, multiParams("splat").head)
val page = params.get("page").flatMap(_.toIntOpt).getOrElse(1)
using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git =>
JGitUtil.getCommitLog(git, branchName, page, 30, path) match {
case Right((logs, hasNext)) =>
html.commits(if(path.isEmpty) Nil else path.split("/").toList, branchName, repository,
logs.splitWith{ (commit1, commit2) =>
view.helpers.date(commit1.commitTime) == view.helpers.date(commit2.commitTime)
}, page, hasNext, hasWritePermission(repository.owner, repository.name, context.loginAccount))
case Left(_) => NotFound
}
}
})
/**
* https://developer.github.com/v3/repos/statuses/#create-a-status
*/
post("/api/v3/repos/:owner/:repo/statuses/:sha")(collaboratorsOnly { repository =>
(for{
ref <- params.get("sha")
sha <- JGitUtil.getShaByRef(repository.owner, repository.name, ref)
data <- extractFromJsonBody[CreateAStatus] if data.isValid
creator <- context.loginAccount
state <- CommitState.valueOf(data.state)
statusId = createCommitStatus(repository.owner, repository.name, sha, data.context.getOrElse("default"),
state, data.target_url, data.description, new java.util.Date(), creator)
status <- getCommitStatus(repository.owner, repository.name, statusId)
} yield {
JsonFormat(ApiCommitStatus(status, ApiUser(creator)))
}) getOrElse NotFound
})
/**
* https://developer.github.com/v3/repos/statuses/#list-statuses-for-a-specific-ref
*
* ref is Ref to list the statuses from. It can be a SHA, a branch name, or a tag name.
*/
get("/api/v3/repos/:owner/:repo/commits/:ref/statuses")(referrersOnly { repository =>
(for{
ref <- params.get("ref")
sha <- JGitUtil.getShaByRef(repository.owner, repository.name, ref)
} yield {
JsonFormat(getCommitStatuesWithCreator(repository.owner, repository.name, sha).map{ case(status, creator) =>
ApiCommitStatus(status, ApiUser(creator))
})
}) getOrElse NotFound
})
/**
* https://developer.github.com/v3/repos/statuses/#get-the-combined-status-for-a-specific-ref
*
* ref is Ref to list the statuses from. It can be a SHA, a branch name, or a tag name.
*/
get("/api/v3/repos/:owner/:repo/commits/:ref/status")(referrersOnly { repository =>
(for{
ref <- params.get("ref")
owner <- getAccountByUserName(repository.owner)
sha <- JGitUtil.getShaByRef(repository.owner, repository.name, ref)
} yield {
val statuses = getCommitStatuesWithCreator(repository.owner, repository.name, sha)
JsonFormat(ApiCombinedCommitStatus(sha, statuses, ApiRepository(repository, owner)))
}) getOrElse NotFound
})
get("/:owner/:repository/new/*")(collaboratorsOnly { repository =>
val (branch, path) = splitPath(repository, multiParams("splat").head)
html.editor(branch, repository, if(path.length == 0) Nil else path.split("/").toList,
None, JGitUtil.ContentInfo("text", None, Some("UTF-8")))
})
get("/:owner/:repository/edit/*")(collaboratorsOnly { repository =>
val (branch, path) = splitPath(repository, multiParams("splat").head)
using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git =>
val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(branch))
getPathObjectId(git, path, revCommit).map { objectId =>
val paths = path.split("/")
html.editor(branch, repository, paths.take(paths.size - 1).toList, Some(paths.last),
JGitUtil.getContentInfo(git, path, objectId))
} getOrElse NotFound
}
})
get("/:owner/:repository/remove/*")(collaboratorsOnly { repository =>
val (branch, path) = splitPath(repository, multiParams("splat").head)
using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git =>
val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(branch))
getPathObjectId(git, path, revCommit).map { objectId =>
val paths = path.split("/")
html.delete(branch, repository, paths.take(paths.size - 1).toList, paths.last,
JGitUtil.getContentInfo(git, path, objectId))
} getOrElse NotFound
}
})
post("/:owner/:repository/create", editorForm)(collaboratorsOnly { (form, repository) =>
commitFile(
repository = repository,
branch = form.branch,
path = form.path,
newFileName = Some(form.newFileName),
oldFileName = None,
content = appendNewLine(convertLineSeparator(form.content, form.lineSeparator), form.lineSeparator),
charset = form.charset,
message = form.message.getOrElse(s"Create ${form.newFileName}")
)
redirect(s"/${repository.owner}/${repository.name}/blob/${form.branch}/${
if(form.path.length == 0) form.newFileName else s"${form.path}/${form.newFileName}"
}")
})
post("/:owner/:repository/update", editorForm)(collaboratorsOnly { (form, repository) =>
commitFile(
repository = repository,
branch = form.branch,
path = form.path,
newFileName = Some(form.newFileName),
oldFileName = form.oldFileName,
content = appendNewLine(convertLineSeparator(form.content, form.lineSeparator), form.lineSeparator),
charset = form.charset,
message = if(form.oldFileName.exists(_ == form.newFileName)){
form.message.getOrElse(s"Update ${form.newFileName}")
} else {
form.message.getOrElse(s"Rename ${form.oldFileName.get} to ${form.newFileName}")
}
)
redirect(s"/${repository.owner}/${repository.name}/blob/${form.branch}/${
if(form.path.length == 0) form.newFileName else s"${form.path}/${form.newFileName}"
}")
})
post("/:owner/:repository/remove", deleteForm)(collaboratorsOnly { (form, repository) =>
commitFile(repository, form.branch, form.path, None, Some(form.fileName), "", "",
form.message.getOrElse(s"Delete ${form.fileName}"))
redirect(s"/${repository.owner}/${repository.name}/tree/${form.branch}${if(form.path.length == 0) "" else form.path}")
})
/**
* Displays the file content of the specified branch or commit.
*/
get("/:owner/:repository/blob/*")(referrersOnly { repository =>
val (id, path) = splitPath(repository, multiParams("splat").head)
val raw = params.get("raw").getOrElse("false").toBoolean
using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git =>
val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(id))
val lastModifiedCommit = JGitUtil.getLastModifiedCommit(git, revCommit, path)
getPathObjectId(git, path, revCommit).map { objectId =>
if(raw){
// Download
defining(JGitUtil.getContentFromId(git, objectId, false).get){ bytes =>
RawData(FileUtil.getContentType(path, bytes), bytes)
}
} else {
html.blob(id, repository, path.split("/").toList, JGitUtil.getContentInfo(git, path, objectId),
new JGitUtil.CommitInfo(lastModifiedCommit), hasWritePermission(repository.owner, repository.name, context.loginAccount))
}
} getOrElse NotFound
}
})
/**
* Displays details of the specified commit.
*/
get("/:owner/:repository/commit/:id")(referrersOnly { repository =>
val id = params("id")
using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git =>
defining(JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(id))){ revCommit =>
JGitUtil.getDiffs(git, id) match { case (diffs, oldCommitId) =>
html.commit(id, new JGitUtil.CommitInfo(revCommit),
JGitUtil.getBranchesOfCommit(git, revCommit.getName),
JGitUtil.getTagsOfCommit(git, revCommit.getName),
getCommitComments(repository.owner, repository.name, id, false),
repository, diffs, oldCommitId, hasWritePermission(repository.owner, repository.name, context.loginAccount))
}
}
}
})
post("/:owner/:repository/commit/:id/comment/new", commentForm)(readableUsersOnly { (form, repository) =>
val id = params("id")
createCommitComment(repository.owner, repository.name, id, context.loginAccount.get.userName, form.content,
form.fileName, form.oldLineNumber, form.newLineNumber, form.issueId.isDefined)
form.issueId match {
case Some(issueId) => recordCommentPullRequestActivity(repository.owner, repository.name, context.loginAccount.get.userName, issueId, form.content)
case None => recordCommentCommitActivity(repository.owner, repository.name, context.loginAccount.get.userName, id, form.content)
}
redirect(s"/${repository.owner}/${repository.name}/commit/${id}")
})
ajaxGet("/:owner/:repository/commit/:id/comment/_form")(readableUsersOnly { repository =>
val id = params("id")
val fileName = params.get("fileName")
val oldLineNumber = params.get("oldLineNumber") map (_.toInt)
val newLineNumber = params.get("newLineNumber") map (_.toInt)
val issueId = params.get("issueId") map (_.toInt)
html.commentform(
commitId = id,
fileName, oldLineNumber, newLineNumber, issueId,
hasWritePermission = hasWritePermission(repository.owner, repository.name, context.loginAccount),
repository = repository
)
})
ajaxPost("/:owner/:repository/commit/:id/comment/_data/new", commentForm)(readableUsersOnly { (form, repository) =>
val id = params("id")
val commentId = createCommitComment(repository.owner, repository.name, id, context.loginAccount.get.userName,
form.content, form.fileName, form.oldLineNumber, form.newLineNumber, form.issueId.isDefined)
form.issueId match {
case Some(issueId) => recordCommentPullRequestActivity(repository.owner, repository.name, context.loginAccount.get.userName, issueId, form.content)
case None => recordCommentCommitActivity(repository.owner, repository.name, context.loginAccount.get.userName, id, form.content)
}
helper.html.commitcomment(getCommitComment(repository.owner, repository.name, commentId.toString).get,
hasWritePermission(repository.owner, repository.name, context.loginAccount), repository)
})
ajaxGet("/:owner/:repository/commit_comments/_data/:id")(readableUsersOnly { repository =>
getCommitComment(repository.owner, repository.name, params("id")) map { x =>
if(isEditable(x.userName, x.repositoryName, x.commentedUserName)){
params.get("dataType") collect {
case t if t == "html" => html.editcomment(
x.content, x.commentId, x.userName, x.repositoryName)
} getOrElse {
contentType = formats("json")
org.json4s.jackson.Serialization.write(
Map("content" -> view.Markdown.toHtml(x.content,
repository, false, true, true, isEditable(x.userName, x.repositoryName, x.commentedUserName))
))
}
} else Unauthorized
} getOrElse NotFound
})
ajaxPost("/:owner/:repository/commit_comments/edit/:id", commentForm)(readableUsersOnly { (form, repository) =>
defining(repository.owner, repository.name){ case (owner, name) =>
getCommitComment(owner, name, params("id")).map { comment =>
if(isEditable(owner, name, comment.commentedUserName)){
updateCommitComment(comment.commentId, form.content)
redirect(s"/${owner}/${name}/commit_comments/_data/${comment.commentId}")
} else Unauthorized
} getOrElse NotFound
}
})
ajaxPost("/:owner/:repository/commit_comments/delete/:id")(readableUsersOnly { repository =>
defining(repository.owner, repository.name){ case (owner, name) =>
getCommitComment(owner, name, params("id")).map { comment =>
if(isEditable(owner, name, comment.commentedUserName)){
Ok(deleteCommitComment(comment.commentId))
} else Unauthorized
} getOrElse NotFound
}
})
/**
* Displays branches.
*/
get("/:owner/:repository/branches")(referrersOnly { repository =>
val branches = JGitUtil.getBranches(repository.owner, repository.name, repository.repository.defaultBranch)
.sortBy(br => (br.mergeInfo.isEmpty, br.commitTime))
.map(br => br -> getPullRequestByRequestCommit(repository.owner, repository.name, repository.repository.defaultBranch, br.name, br.commitId))
.reverse
html.branches(branches, hasWritePermission(repository.owner, repository.name, context.loginAccount), repository)
})
/**
* Creates a branch.
*/
post("/:owner/:repository/branches")(collaboratorsOnly { repository =>
val newBranchName = params.getOrElse("new", halt(400))
val fromBranchName = params.getOrElse("from", halt(400))
using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git =>
JGitUtil.createBranch(git, fromBranchName, newBranchName)
} match {
case Right(message) =>
flash += "info" -> message
redirect(s"/${repository.owner}/${repository.name}/tree/${StringUtil.urlEncode(newBranchName).replace("%2F", "/")}")
case Left(message) =>
flash += "error" -> message
redirect(s"/${repository.owner}/${repository.name}/tree/${fromBranchName}")
}
})
/**
* Deletes branch.
*/
get("/:owner/:repository/delete/*")(collaboratorsOnly { repository =>
val branchName = multiParams("splat").head
val userName = context.loginAccount.get.userName
if(repository.repository.defaultBranch != branchName){
using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git =>
git.branchDelete().setForce(true).setBranchNames(branchName).call()
recordDeleteBranchActivity(repository.owner, repository.name, userName, branchName)
}
}
redirect(s"/${repository.owner}/${repository.name}/branches")
})
/**
* Displays tags.
*/
get("/:owner/:repository/tags")(referrersOnly {
html.tags(_)
})
/**
* Download repository contents as an archive.
*/
get("/:owner/:repository/archive/*")(referrersOnly { repository =>
multiParams("splat").head match {
case name if name.endsWith(".zip") =>
archiveRepository(name, ".zip", repository)
case name if name.endsWith(".tar.gz") =>
archiveRepository(name, ".tar.gz", repository)
case _ => BadRequest
}
})
get("/:owner/:repository/network/members")(referrersOnly { repository =>
html.forked(
getRepository(
repository.repository.originUserName.getOrElse(repository.owner),
repository.repository.originRepositoryName.getOrElse(repository.name),
context.baseUrl),
getForkedRepositories(
repository.repository.originUserName.getOrElse(repository.owner),
repository.repository.originRepositoryName.getOrElse(repository.name)),
repository)
})
private def splitPath(repository: RepositoryService.RepositoryInfo, path: String): (String, String) = {
val id = repository.branchList.collectFirst {
case branch if(path == branch || path.startsWith(branch + "/")) => branch
} orElse repository.tags.collectFirst {
case tag if(path == tag.name || path.startsWith(tag.name + "/")) => tag.name
} getOrElse path.split("/")(0)
(id, path.substring(id.length).stripPrefix("/"))
}
private val readmeFiles = view.helpers.renderableSuffixes.map(suffix => s"readme${suffix}") ++ Seq("readme.txt", "readme")
/**
* Provides HTML of the file list.
*
* @param repository the repository information
* @param revstr the branch name or commit id(optional)
* @param path the directory path (optional)
* @return HTML of the file list
*/
private def fileList(repository: RepositoryService.RepositoryInfo, revstr: String = "", path: String = ".") = {
if(repository.commitCount == 0){
html.guide(repository, hasWritePermission(repository.owner, repository.name, context.loginAccount))
} else {
using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git =>
// get specified commit
JGitUtil.getDefaultBranch(git, repository, revstr).map { case (objectId, revision) =>
defining(JGitUtil.getRevCommitFromId(git, objectId)) { revCommit =>
val lastModifiedCommit = if(path == ".") revCommit else JGitUtil.getLastModifiedCommit(git, revCommit, path)
// get files
val files = JGitUtil.getFileList(git, revision, path)
val parentPath = if (path == ".") Nil else path.split("/").toList
// process README.md or README.markdown
val readme = files.find { file =>
readmeFiles.contains(file.name.toLowerCase)
}.map { file =>
val path = (file.name :: parentPath.reverse).reverse
path -> StringUtil.convertFromByteArray(JGitUtil.getContentFromId(
Git.open(getRepositoryDir(repository.owner, repository.name)), file.id, true).get)
}
html.files(revision, repository,
if(path == ".") Nil else path.split("/").toList, // current path
context.loginAccount match {
case None => List()
case account: Option[Account] => getGroupsByUserName(account.get.userName)
}, // groups of current user
new JGitUtil.CommitInfo(lastModifiedCommit), // last modified commit
files, readme, hasWritePermission(repository.owner, repository.name, context.loginAccount),
flash.get("info"), flash.get("error"))
}
} getOrElse NotFound
}
}
}
private def commitFile(repository: RepositoryService.RepositoryInfo,
branch: String, path: String, newFileName: Option[String], oldFileName: Option[String],
content: String, charset: String, message: String) = {
val newPath = newFileName.map { newFileName => if(path.length == 0) newFileName else s"${path}/${newFileName}" }
val oldPath = oldFileName.map { oldFileName => if(path.length == 0) oldFileName else s"${path}/${oldFileName}" }
LockUtil.lock(s"${repository.owner}/${repository.name}"){
using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git =>
val loginAccount = context.loginAccount.get
val builder = DirCache.newInCore.builder()
val inserter = git.getRepository.newObjectInserter()
val headName = s"refs/heads/${branch}"
val headTip = git.getRepository.resolve(headName)
JGitUtil.processTree(git, headTip){ (path, tree) =>
if(!newPath.exists(_ == path) && !oldPath.exists(_ == path)){
builder.add(JGitUtil.createDirCacheEntry(path, tree.getEntryFileMode, tree.getEntryObjectId))
}
}
newPath.foreach { newPath =>
builder.add(JGitUtil.createDirCacheEntry(newPath, FileMode.REGULAR_FILE,
inserter.insert(Constants.OBJ_BLOB, content.getBytes(charset))))
}
builder.finish()
val commitId = JGitUtil.createNewCommit(git, inserter, headTip, builder.getDirCache.writeTree(inserter),
headName, loginAccount.fullName, loginAccount.mailAddress, message)
inserter.flush()
inserter.release()
// update refs
val refUpdate = git.getRepository.updateRef(headName)
refUpdate.setNewObjectId(commitId)
refUpdate.setForceUpdate(false)
refUpdate.setRefLogIdent(new PersonIdent(loginAccount.fullName, loginAccount.mailAddress))
//refUpdate.setRefLogMessage("merged", true)
refUpdate.update()
// update pull request
updatePullRequests(repository.owner, repository.name, branch)
// record activity
recordPushActivity(repository.owner, repository.name, loginAccount.userName, branch,
List(new CommitInfo(JGitUtil.getRevCommitFromId(git, commitId))))
// close issue by commit message
closeIssuesFromMessage(message, loginAccount.userName, repository.owner, repository.name)
// call web hook
callPullRequestWebHookByRequestBranch("synchronize", repository, branch, context.baseUrl, loginAccount)
val commit = new JGitUtil.CommitInfo(JGitUtil.getRevCommitFromId(git, commitId))
callWebHookOf(repository.owner, repository.name, "push") {
getAccountByUserName(repository.owner).map{ ownerAccount =>
WebHookPushPayload(git, loginAccount, headName, repository, List(commit), ownerAccount)
}
}
}
}
}
private def getPathObjectId(git: Git, path: String, revCommit: RevCommit): Option[ObjectId] = {
@scala.annotation.tailrec
def _getPathObjectId(path: String, walk: TreeWalk): Option[ObjectId] = walk.next match {
case true if(walk.getPathString == path) => Some(walk.getObjectId(0))
case true => _getPathObjectId(path, walk)
case false => None
}
using(new TreeWalk(git.getRepository)){ treeWalk =>
treeWalk.addTree(revCommit.getTree)
treeWalk.setRecursive(true)
_getPathObjectId(path, treeWalk)
}
}
private def archiveRepository(name: String, suffix: String, repository: RepositoryService.RepositoryInfo): Unit = {
val revision = name.stripSuffix(suffix)
val workDir = getDownloadWorkDir(repository.owner, repository.name, session.getId)
if(workDir.exists) {
FileUtils.deleteDirectory(workDir)
}
workDir.mkdirs
val filename = repository.name + "-" +
(if(revision.length == 40) revision.substring(0, 10) else revision).replace('/', '_') + suffix
using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git =>
val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(revision))
contentType = "application/octet-stream"
response.setHeader("Content-Disposition", s"attachment; filename=${filename}")
response.setBufferSize(1024 * 1024);
git.archive
.setFormat(suffix.tail)
.setTree(revCommit.getTree)
.setOutputStream(response.getOutputStream)
.call()
Unit
}
}
private def isEditable(owner: String, repository: String, author: String)(implicit context: Context): Boolean =
hasWritePermission(owner, repository, context.loginAccount) || author == context.loginAccount.get.userName
}

View File

@@ -0,0 +1,51 @@
package gitbucket.core.controller
import gitbucket.core.search.html
import gitbucket.core.service._
import gitbucket.core.util.{StringUtil, ControlUtil, ReferrerAuthenticator, Implicits}
import ControlUtil._
import Implicits._
import jp.sf.amateras.scalatra.forms._
class SearchController extends SearchControllerBase
with RepositoryService with AccountService with ActivityService with RepositorySearchService with IssuesService with ReferrerAuthenticator
trait SearchControllerBase extends ControllerBase { self: RepositoryService
with ActivityService with RepositorySearchService with ReferrerAuthenticator =>
val searchForm = mapping(
"query" -> trim(text(required)),
"owner" -> trim(text(required)),
"repository" -> trim(text(required))
)(SearchForm.apply)
case class SearchForm(query: String, owner: String, repository: String)
post("/search", searchForm){ form =>
redirect(s"/${form.owner}/${form.repository}/search?q=${StringUtil.urlEncode(form.query)}")
}
get("/:owner/:repository/search")(referrersOnly { repository =>
defining(params("q").trim, params.getOrElse("type", "code")){ case (query, target) =>
val page = try {
val i = params.getOrElse("page", "1").toInt
if(i <= 0) 1 else i
} catch {
case e: NumberFormatException => 1
}
target.toLowerCase match {
case "issue" => html.issues(
searchIssues(repository.owner, repository.name, query),
countFiles(repository.owner, repository.name, query),
query, page, repository)
case _ => html.code(
searchFiles(repository.owner, repository.name, query),
countIssues(repository.owner, repository.name, query),
query, page, repository)
}
}
})
}

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