Compare commits

...

657 Commits
2.8 ... 3.10

Author SHA1 Message Date
Naoki Takezoe
990d082422 3.10 release 2015-12-30 01:18:13 +09:00
Naoki Takezoe
dfe3fbc02f Bump up H2 to 1.4.190 2015-12-29 01:37:52 +09:00
Naoki Takezoe
c077dd0046 Rename logback-test.xml to logback-dev.xml 2015-12-27 14:40:54 +09:00
Naoki Takezoe
9cc6beead7 (refs #990)Provides path for raw contents instead of ?raw=true 2015-12-26 22:48:28 +09:00
Naoki Takezoe
92f6792ad9 Adjust format 2015-12-26 21:20:09 +09:00
Naoki Takezoe
25031eadfb Merge pull request #1017 from swaldman/minor-c3p0-tweaks
Upgrade c3p0 and tweak default config to avoid Tomcat issues
2015-12-26 17:00:33 +09:00
Naoki Takezoe
a7024615e1 Merge pull request #1020 from P1tt187/sshd-RSA-keyprovider
update sshd dependency, rsa keyprovider as default
2015-12-26 16:59:30 +09:00
Naoki Takezoe
8191a4bedb (refs #1004)Fix edit webhook link url 2015-12-26 03:25:49 +09:00
Naoki Takezoe
7e1f5aa353 Fix branch control dropdown 2015-12-25 10:26:30 +09:00
Fabian Markert
44e161f6ec update version 2015-12-24 17:16:20 +01:00
Fabian Markert
7bd17ed038 update sshd dependency
migrate to new sshd api
change the default key provider algorithm to RSA
2015-12-24 17:11:40 +01:00
Naoki Takezoe
a47404130b Don't break lines in Wiki or markdown files on the repository viewer 2015-12-24 02:58:39 +09:00
Naoki Takezoe
669949aa8c Update docs 2015-12-23 23:30:10 +09:00
Naoki Takezoe
c773bb90f6 Update docs 2015-12-23 23:26:59 +09:00
Naoki Takezoe
18f525523b Update docs 2015-12-23 23:06:22 +09:00
Naoki Takezoe
3bfd228df1 Update docs 2015-12-23 23:03:32 +09:00
Naoki Takezoe
c072ab2dc5 Update docs 2015-12-23 22:44:20 +09:00
Naoki Takezoe
53e06c5e86 (refs #1018)Move to xsbt-web-plugin from scalatra-sbt 2015-12-23 21:37:25 +09:00
Steve Waldman
b0a749bdc1 Upgrade c3p0 and tweak default config to avoid Tomcat issues
Adds a standard typesafe-config reference.conf file that sets c3p0
config to prevents Tomcat memory leaks on hot-redeploy/shutdown.

See
   http://www.mchange.com/projects/c3p0/#configuring_to_avoid_memory_leaks_on_redeploy
   http://www.mchange.com/projects/c3p0/#tomcat-specific

This new config can be overridden in an application.conf or
c3p0.properties file.

( See http://www.mchange.com/projects/c3p0/#configuration_precedence )

Updates c3p0 version to c3p0-0.9.5.2, which fixes that leads to
unnecessary String allocation when logging via slf4j.
2015-12-21 22:51:12 -08:00
Naoki Takezoe
809443a46f Zip logback.xml into the root of gitbucket.war 2015-12-22 07:59:47 +09:00
Naoki Takezoe
59ef44dd71 Update logging configuration 2015-12-22 07:59:15 +09:00
Naoki Takezoe
d77973a29e Remove IDE plugins and add logback-classic 2015-12-22 07:58:30 +09:00
Naoki Takezoe
be6bc16d3d Fix global header style 2015-12-21 15:14:51 +09:00
Naoki Takezoe
407e926abe Fix styles in file editing form in the repository viewer 2015-12-21 15:14:21 +09:00
Naoki Takezoe
117655b011 (refs #1002)Upgrade markedj to 1.0.6-SNAPSHOT to solve IndexOutOfBoundsException in the table 2015-12-19 12:56:34 +09:00
Naoki Takezoe
2ea4a5a7ef (refs #1007)Fix max length constraint for repository owner and name at the repository creation form 2015-12-18 01:46:25 +09:00
Naoki Takezoe
30b0279efe (refs #1005)Fix typo 2015-12-18 01:40:32 +09:00
Naoki Takezoe
7511701bb2 Merge pull request #1009 from team-lab/fix/webhook-push-pusher
(fixes #1008) pusher of webhook push event is not same as github's it
2015-12-18 01:32:18 +09:00
Naoki Takezoe
a919ae3430 Merge pull request #1010 from team-lab/suppot-legacy-statuses-api
support legacy route of List Statuses for a specific Ref api
2015-12-18 01:31:28 +09:00
nazoking
c30ee24f8d support legacy route of List Statuses for a specific Ref api 2015-12-16 21:54:47 +09:00
nazoking
10b5de571e (fixes #1008) pusher of webhook push event is not same as github's it 2015-12-16 21:41:14 +09:00
Naoki Takezoe
c6f4ec7250 Fix header-link style 2015-12-15 19:51:16 +09:00
Naoki Takezoe
b229058726 Fix fork button group style 2015-12-15 19:39:18 +09:00
Naoki Takezoe
21363794b6 Merge pull request #999 from gitbucket/css_bootstrap3
Move to Bootstrap3 with GitHub theme
2015-12-15 15:58:54 +09:00
Naoki Takezoe
1754d37ac0 Fix style of circle icons 2015-12-15 15:13:54 +09:00
Naoki Takezoe
1c36b185ec Fix width and font of the dropzone 2015-12-15 14:56:24 +09:00
Naoki Takezoe
1a4b94e5df Fix modal dialog 2015-12-15 11:46:47 +09:00
Naoki Takezoe
31bd8e1741 Fix button size 2015-12-14 18:48:22 +09:00
Naoki Takezoe
e8824ad1ac Imporve styles for mobile 2015-12-14 01:56:15 +09:00
Naoki Takezoe
17636a5ecf Fixup 2015-12-14 01:11:42 +09:00
Naoki Takezoe
8f36ff60de Use bootstrap panel instead of custom box styles 2015-12-14 00:16:18 +09:00
Naoki Takezoe
7ad605afba Fix style of issue comment form and more 2015-12-13 23:59:42 +09:00
Naoki Takezoe
f080e8693e Fix right margin of account search box 2015-12-12 13:20:05 +09:00
Naoki Takezoe
e27bf7868f Tweak pagination padding 2015-12-10 17:17:18 +09:00
Naoki Takezoe
db55fe9ff3 Fixup 2015-12-10 16:02:58 +09:00
Naoki Takezoe
de4db0764c Use Bootstrap panel instead of custom CSS 2015-12-10 14:53:19 +09:00
Naoki Takezoe
9d644b4625 Fix buttons of webhook list 2015-12-10 14:04:44 +09:00
Naoki Takezoe
354dc27e84 Use Bootstrap panel instead of custom CSS 2015-12-10 12:13:22 +09:00
Naoki Takezoe
1c639474d3 Fix milestone list and progress bar 2015-12-10 12:03:22 +09:00
Naoki Takezoe
4d8b47732e Remove unused bootstrap files 2015-12-10 02:03:46 +09:00
Naoki Takezoe
8650abf679 Fix milestone creation / editing form 2015-12-10 02:00:42 +09:00
Naoki Takezoe
27dcc2ef48 Fix table vertical border 2015-12-10 01:16:17 +09:00
Naoki Takezoe
94a12cd28c Fix label editing form 2015-12-09 19:16:34 +09:00
Naoki Takezoe
a982b64fd4 Use Bootstrap panels instead of GitBucket original styles 2015-12-09 18:29:41 +09:00
Naoki Takezoe
ee5b4bbf2e Fix styles 2015-12-09 02:48:10 +09:00
Naoki Takezoe
d09ddd8d07 Fix tables and some pages 2015-12-08 19:21:08 +09:00
Naoki Takezoe
c7bf47820c Use customized Bootstrap3 theme 2015-12-08 09:33:22 +09:00
Naoki Takezoe
4dfb01d59e Fix typo 2015-12-08 02:35:52 +09:00
Naoki Takezoe
54bef6505f Fix issue search box 2015-12-08 02:26:11 +09:00
Naoki Takezoe
2aa71ed217 Fix borderd table style 2015-12-08 01:59:31 +09:00
Naoki Takezoe
d90938421f Fix ZeroClipboard button 2015-12-07 23:19:30 +09:00
Naoki Takezoe
e4992bbd17 Fix styles of repository setting pages 2015-12-07 16:56:38 +09:00
Naoki Takezoe
7853b22522 Fix sidebar style 2015-12-07 16:01:03 +09:00
Naoki Takezoe
9475215fa6 Fix styles of plugins page 2015-12-07 15:20:06 +09:00
Naoki Takezoe
8622bbd2ac Fix styles of account and group creation page 2015-12-07 14:38:18 +09:00
Naoki Takezoe
0cdcc252e0 Merge branch 'master' into css_bootstrap3
Conflicts:
	src/main/twirl/gitbucket/core/account/edit.scala.html
2015-12-07 08:33:22 +09:00
Naoki Takezoe
9477cca8e8 Fix box style 2015-12-05 19:54:32 +09:00
Naoki Takezoe
176e427058 Update README.md for 3.9 release 2015-12-05 19:48:44 +09:00
Naoki Takezoe
1bf26cacb9 Update version to 3.9.0 2015-12-05 19:46:29 +09:00
Naoki Takezoe
38e8247483 Fix styles of system administration page 2015-12-05 19:45:07 +09:00
Naoki Takezoe
44aa12108c Fix styles of the repository creation page and more 2015-12-05 14:54:50 +09:00
Naoki Takezoe
dd73405aa9 Fix styles 2015-12-05 12:41:55 +09:00
Naoki Takezoe
6710393a53 Merge branch 'master' into css_bootstrap3 2015-12-05 11:53:15 +09:00
Naoki Takezoe
711aee1c8b Fix markedj version as 1.0.5 2015-12-05 03:54:51 +09:00
Naoki Takezoe
51be1048d5 (refs #987)Remove user related data when users delete themselves 2015-12-05 03:45:26 +09:00
Naoki Takezoe
50166f04d8 Disable auto completion in the select user field 2015-12-05 03:43:57 +09:00
Naoki Takezoe
7eb6fea08b Fix typo in comment 2015-12-05 03:12:21 +09:00
Naoki Takezoe
8e3c054da4 Merge pull request #983 from team-lab/feature/webhook-review-comment
Send webhook on create pull request review comment
2015-12-05 01:14:54 +09:00
nazoking
5249b67df1 Merge branch 'master' into feature/webhook-review-comment 2015-12-03 20:09:46 +09:00
Naoki Takezoe
583830c1c2 Fix styles 2015-12-01 02:47:09 +09:00
Naoki Takezoe
bb75f1c034 Merge branch 'css_bootstrap3' of https://github.com/takezoe/gitbucket into css_bootstrap3 2015-12-01 02:12:08 +09:00
Naoki Takezoe
b7a9236283 Merge branch 'master' of https://github.com/takezoe/gitbucket into css_bootstrap3 2015-12-01 01:37:14 +09:00
Naoki Takezoe
7e0e172d37 Fix fork button 2015-11-30 23:22:16 +09:00
Naoki Takezoe
ea37a34476 Fix styles of issues and pull requests 2015-11-30 01:16:27 +09:00
Naoki Takezoe
fb5564de07 Fix diff styles 2015-11-27 12:53:10 +09:00
Naoki Takezoe
cead7e9e4e Fix commit list style 2015-11-27 12:37:26 +09:00
Naoki Takezoe
7cf23b6c56 Fix pull request creation form style 2015-11-27 12:37:16 +09:00
Naoki Takezoe
1080821862 Fix issue styles 2015-11-27 10:16:17 +09:00
Naoki Takezoe
0bf843ce2a Fix styles 2015-11-27 10:10:15 +09:00
Naoki Takezoe
b8bd8f5512 Fix branch list styles 2015-11-27 09:43:17 +09:00
Naoki Takezoe
12fba30ef8 Fix styles 2015-11-25 10:44:55 +09:00
Naoki Takezoe
dc5cb74f4d Merge branch 'master' of https://github.com/takezoe/gitbucket 2015-11-18 01:48:47 +09:00
Naoki Takezoe
446a524b5a Upgrade scalatra-forms to 0.2.0 to solve performance issue 2015-11-18 01:48:40 +09:00
Naoki Takezoe
121110aede Fix markdown header style 2015-11-17 23:19:08 +09:00
Naoki Takezoe
c76a8562ea Merge pull request #995 from uli-heller/patch-2
extensibility is probably the right word?
2015-11-17 19:28:00 +09:00
uli-heller
495d8069f5 extensibility is probably the right word? 2015-11-17 09:51:50 +01:00
Naoki Takezoe
70af6d0c35 Update README.md 2015-11-17 16:04:58 +09:00
Naoki Takezoe
abfc6eea1e Fix repository setting pages presentation 2015-11-17 01:52:14 +09:00
Naoki Takezoe
fd2f3e252c Fix webhook editing presentation and performance issue 2015-11-17 01:51:55 +09:00
Naoki Takezoe
3b6d0065a2 Once delete editHooks.scala.html to rename 2015-11-17 01:48:13 +09:00
Naoki Takezoe
7ffe2ab864 Fix global header style 2015-11-15 12:54:34 +09:00
Naoki Takezoe
2f6febb748 Adjust styles for Bootstrap3 2015-11-15 02:27:55 +09:00
Naoki Takezoe
092c897150 Fix sidebar style 2015-11-15 02:05:24 +09:00
Naoki Takezoe
30f072f925 Merge pull request #992 from team-lab/fix/lost-webhook-events-on-rename-repository
fix lost web hook events on repository renamed.
2015-11-14 15:02:41 +09:00
nazoking
72d0282613 Merge branch 'master' into feature/webhook-review-comment 2015-11-14 13:27:40 +09:00
nazoking
a56f766497 fix lost web hook events on repository renamed. 2015-11-14 13:16:13 +09:00
Naoki Takezoe
31b5583896 Fix header styles 2015-11-13 02:32:04 +09:00
Naoki Takezoe
7c95bfc77e Merge branch 'master' into css_bootstrap3 2015-11-12 03:45:00 +09:00
Naoki Takezoe
c6804fe589 Add bootstrap-theme-github 2015-11-12 03:44:37 +09:00
Naoki Takezoe
fc41e282ce Merge pull request #973 from gitbucket/commit_comment_count
Count COMMIT_COMMENT as issue comment
2015-11-10 11:48:20 +09:00
Naoki Takezoe
d3b21a4e39 Drop PULL_REQUEST column from COMMIT_COMMENT table 2015-11-10 11:35:59 +09:00
Naoki Takezoe
7b6d9850a2 Moving to Bootstrap3 2015-11-10 11:06:11 +09:00
nazoking
84d97817e3 add comment 2015-11-09 13:57:37 +09:00
nazoking
2c6d2176bb fix space 2015-11-09 13:57:26 +09:00
nazoking
7840d28ed2 add webhook pull-request-review-comment 2015-11-09 12:20:07 +09:00
Naoki Takezoe
d4fd2e6cd6 Remove bottom margin of inline comment in diff view 2015-11-09 01:20:44 +09:00
Naoki Takezoe
70e8385246 (refs #974)Not hide add-comment icon when adding comment form is active. 2015-11-09 01:15:04 +09:00
Naoki Takezoe
3fd23f1749 Fix markdown preview style 2015-11-08 22:26:43 +09:00
Naoki Takezoe
a3e3aea4e0 Fix styles 2015-11-08 19:08:05 +09:00
Naoki Takezoe
dcfb8ad8eb (refs #976)Enable GFM line breaks 2015-11-08 18:03:44 +09:00
Naoki Takezoe
35f82e91bc (refs #976)Upgrade markedj to 1.0.5-SNAPSHOT 2015-11-08 17:49:25 +09:00
Naoki Takezoe
f1533cb168 Count COMMIT_COMMENT as issue comment 2015-11-08 04:06:14 +09:00
Naoki Takezoe
763fbec0ca Update README.md 2015-11-08 02:58:54 +09:00
Naoki Takezoe
111c893d94 Merge pull request #968 from justinpitts/master
Correct typo on system administration page.
2015-11-07 03:35:39 +09:00
Naoki Takezoe
80c38a45c5 Merge pull request #941 from team-lab/feature/webhook-scope
feature/can select event trigger of webhook.
2015-11-07 03:35:09 +09:00
nazoking
548860607b update migration version to 3.9 from 3.8 2015-11-06 14:52:36 +09:00
nazoking
66b5fe7337 Merge branch 'master' into feature/webhook-scope 2015-11-06 14:48:03 +09:00
Justin Pitts
2b2a117912 Correct typo on system administration page. 2015-11-04 08:51:48 -05:00
Naoki Takezoe
39a10fd167 Update README.md 2015-11-04 02:00:14 +09:00
Naoki Takezoe
93f8dbd42a Fix markedj version 2015-11-02 08:58:29 +09:00
Naoki Takezoe
4f280d0df8 Fix blockquote font size 2015-11-02 02:33:06 +09:00
Naoki Takezoe
5e9ff3278a Fix the default number of displayed repositories 2015-11-02 00:32:46 +09:00
Naoki Takezoe
b4b68f0e17 Update README.md 2015-10-31 11:55:16 +09:00
Naoki Takezoe
97bd324cb0 Update README.md for GitBucket 3.8 release 2015-10-31 11:53:06 +09:00
Naoki Takezoe
2738406710 Update version number to 3.8 2015-10-31 11:07:02 +09:00
Naoki Takezoe
8e78345cbe Merge pull request #939 from nus/feature/upload-pdf
Support uploading PDF files
2015-10-31 11:05:41 +09:00
Naoki Takezoe
bd9e064137 Merge pull request #925 from team-lab/feature/add-compare-link-for-webhook-push
Feature/add properties for webhook push
2015-10-31 02:57:24 +09:00
Naoki Takezoe
eb49365bcb Merge pull request #960 from arteria/master
Creating repositories over API
2015-10-30 11:38:31 +09:00
Naoki Takezoe
de92e28c7a Adjust whitespace 2015-10-29 01:09:07 +09:00
Naoki Takezoe
c18f8fd87b Merge pull request #961 from superhj1987/master
Update PullRequestsController.scala
2015-10-29 01:06:06 +09:00
Naoki Takezoe
34272cb4c4 Update README.md 2015-10-29 01:00:41 +09:00
Naoki Takezoe
b3f8a02494 Update README.md 2015-10-29 00:59:47 +09:00
hangjian
f767a55350 Merge branch 'master' of github.com:gitbucket/gitbucket 2015-10-28 09:32:44 +08:00
Bryant Hang
9b2c3848d9 Update PullRequestsController.scala
when the forked repository is the original repository(forkedRepository.repository.originRepositoryName&originUserName is None),then 404 will occur,so add the if to solve it.
2015-10-27 23:09:08 +08:00
Jannis Vamvas
e34d016581 Change group repository creation API endpoint to /orgs/:org/repos 2015-10-27 10:27:47 +01:00
Jannis Vamvas
b64b447b42 Extend API to allow creating repositories 2015-10-26 14:30:49 +01:00
Yota Ichino
c6a4c13394 Change messages about file types in the comment form. 2015-10-25 22:56:31 +09:00
Yota Ichino
c8822cb4ca Write a Content-Disposition header for attach files. 2015-10-25 22:37:13 +09:00
Yota Ichino
c05e7218f3 Add document file formats for upload
The following extension is added.
- .docx
- .pptx
- .txt
- .xlsx
2015-10-25 22:03:16 +09:00
Naoki Takezoe
01872d3440 Fix order of updating pull request when repository is renamed 2015-10-24 23:31:37 +09:00
Naoki Takezoe
91bbb3e4dc Update description about support 2015-10-24 20:44:38 +09:00
Naoki Takezoe
80ebd9fb0e (refs #945)Remove ?raw=true from url other than images 2015-10-18 23:04:47 +09:00
Naoki Takezoe
2ab217251a Remove unnecessary TODO 2015-10-18 19:01:04 +09:00
Naoki Takezoe
2b20f6c74c (refs #946)Insert refer comment when pull request is created 2015-10-18 18:57:23 +09:00
Naoki Takezoe
042f855cd5 (refs #947)Fix referenced link from pull request 2015-10-18 18:06:03 +09:00
Naoki Takezoe
720ab7e0a3 Update url in docs 2015-10-17 16:17:43 +09:00
Naoki Takezoe
4b1b100aa5 Update README.md 2015-10-17 02:16:13 +09:00
Naoki Takezoe
541b7e2a79 Merge pull request #944 from lefou/t943-url-in-repo-desc
Detect links and render them as HTML links in repo description
2015-10-15 09:20:31 +09:00
Tobias Roeser
00cd3adc7b Refined regex and removed explicit TLDs
Also made regex val private.
2015-10-14 22:42:35 +02:00
Tobias Roeser
5a97a518a6 Detect links and render them as HTML links in repo description 2015-10-14 14:41:09 +02:00
Naoki Takezoe
d7219068cd (refs #907)Omit diffs if updated files are 100 over 2015-10-14 02:26:59 +09:00
Naoki Takezoe
a79c07f095 Merge branch 'master' of https://github.com/takezoe/gitbucket 2015-10-14 02:24:43 +09:00
Naoki Takezoe
9f27f70c87 (refs #907)Omit the compare view for large diffs 2015-10-14 02:24:38 +09:00
Naoki Takezoe
8fa79db368 Merge pull request #940 from team-lab/fix/933-download-large-file
(ref #933) fix/Unable to download large file
2015-10-14 00:11:29 +09:00
nazoking
a1efa60741 fix return value 2015-10-13 18:57:20 +09:00
nazoking
a08a212fdc feature/can select webhook events 2015-10-13 18:48:02 +09:00
Naoki Takezoe
6166eb3743 Merge pull request #905 from kanmi/ssh-host-key
Specify option to generate an RSA host key
2015-10-13 00:16:50 +09:00
nazoking
5194fc5f15 (ref #933) fix/Unable to download large file (fix method name) 2015-10-12 23:25:07 +09:00
nazoking
1a97beb8cf (ref #933) fix/Unable to download large file 2015-10-12 21:58:56 +09:00
Yota Ichino
99d23398ad Change the upload form for PDF files 2015-10-12 21:45:16 +09:00
Yota Ichino
6accdefb8c Change a path for uploading a file 2015-10-12 21:44:58 +09:00
Yota Ichino
98fc64deaa Enable to upload a PDF file 2015-10-12 21:44:41 +09:00
Naoki Takezoe
30a8cefc37 Merge pull request #938 from nus/change-place-of-buttons
Change place of buttons
2015-10-12 17:31:04 +09:00
Naoki Takezoe
9d47c3ccb3 Bump markedj to 1.0.4-SNAPSHOT 2015-10-12 16:52:00 +09:00
Yota Ichino
5a1ab8d485 Set tabindex for the comment form. 2015-10-12 16:41:15 +09:00
Yota Ichino
5d526f243e Change place of Comment button and Close button. 2015-10-12 15:52:47 +09:00
nazoking
d13bb47ee7 remove ApiPushCommit class (merge to ApiCommit) 2015-10-06 02:27:42 +09:00
nazoking
8b8c6ee861 fix repository.url on webhook push payload 2015-10-06 02:15:03 +09:00
nazoking
0a2d95e434 add head_commit on webhook push payload 2015-10-06 02:14:02 +09:00
nazoking
fdd119c477 add compare, after and before property on webhook push payload 2015-10-06 01:39:19 +09:00
Naoki Takezoe
4f94ca1384 Merge branch 'master' of https://github.com/takezoe/gitbucket 2015-10-04 13:17:13 +09:00
Naoki Takezoe
ed21ee8bdb Improve header anchor behavior 2015-10-04 13:17:07 +09:00
Naoki Takezoe
efd257dee0 Update README.md 2015-10-03 19:07:46 +09:00
Naoki Takezoe
bacf391a39 (refs #867)Providing checksum has started since 3.7 2015-10-03 14:26:36 +09:00
Naoki Takezoe
e8a1543466 Fix commit comment style 2015-10-03 13:28:59 +09:00
Naoki Takezoe
81c79003ec GitBucket 3.7.0 release 2015-10-03 13:13:48 +09:00
Naoki Takezoe
73cf9661ac Fix url encoding to encode whitespace to %20 2015-10-03 04:00:08 +09:00
Naoki Takezoe
6e994b0ae1 (refs #918)Fix pull request comment 2015-10-03 03:41:05 +09:00
Naoki Takezoe
c5da975cea Merge pull request #919 from team-lab/fix-push-commit-url
fix commit url in webhook `push` event.
2015-10-02 21:13:58 +09:00
Naoki Takezoe
1f3ef962e8 Merge pull request #921 from team-lab/fix-#700-update-pr-on-web-ui
(refs #700)web ui merge does not update pull request that already exist
2015-10-02 21:02:03 +09:00
Naoki Takezoe
f6066a0361 Merge pull request #920 from team-lab/fix-webhook-issue-comment-url
fix pull-request url on webhook payload
2015-10-02 20:45:23 +09:00
nazoking
ace65cf261 (refs #920) Remove IssueOrPullRequest. it is too much for this purpose 2015-10-02 11:59:25 +09:00
nazoking
1df537ce5c (refs #700)web ui merge does not update pull request that already exist 2015-10-02 00:49:11 +09:00
nazoking
bf64f6b4f4 api url is only issues 2015-10-02 00:33:23 +09:00
nazoking
0283ec574d fix pull-request url on webhook payload 2015-10-02 00:23:13 +09:00
nazoking
d566f64e8b The ApiCommit should be export url and html values in json. 2015-10-01 23:04:35 +09:00
nazoking
bb9add9da9 fix commit url in push webhook. 2015-10-01 22:56:36 +09:00
Naoki Takezoe
75d085a2c4 (refs #917)Fix incorrent HTML escape 2015-10-01 13:04:56 +09:00
Naoki Takezoe
4eab07ffaf Merge branch 'snowgooseyk-master' 2015-10-01 10:23:55 +09:00
Naoki Takezoe
cf7aaa25cd (refs #915)Fixup 2015-10-01 10:22:55 +09:00
Naoki Takezoe
8011b22de6 Merge branch 'master' of https://github.com/snowgooseyk/gitbucket into snowgooseyk-master 2015-10-01 10:16:21 +09:00
Naoki Takezoe
a108489d71 Update README.md 2015-09-30 03:25:43 +09:00
Naoki Takezoe
185c132771 Create CONTRIBUTING.md 2015-09-30 03:22:47 +09:00
Naoki Takezoe
3b11e905a1 Merge branch 'chazmuzz-master' 2015-09-30 02:30:44 +09:00
Naoki Takezoe
5a04fe7ae6 (refs #906)Shrink range which is applied style 2015-09-30 02:30:28 +09:00
Naoki Takezoe
92c73062cc Expatnd wiki page width 2015-09-29 01:20:28 +09:00
Naoki Takezoe
d07624bdc1 Sanitize in markdown 2015-09-29 01:20:10 +09:00
snowgooseyk
e1dbe80ccd Fix ArrayIndexOutOfBoundsException 2015-09-29 00:03:49 +09:00
Naoki Takezoe
6d69a52292 (refs #800)Introduce environment variable JAVA_OPTS to specify JVM options when build GitBucket 2015-09-24 13:13:36 +09:00
Naoki Takezoe
68af5479c8 (refs #823)Enable file finder for branches which contain / 2015-09-24 13:06:53 +09:00
Naoki Takezoe
3970eca8dc Merge branch 'McFoggy-issue-893' 2015-09-24 03:51:02 +09:00
Naoki Takezoe
b11d36c3a5 (refs #896)Add description and migration for separation of notification and SMTP configuration 2015-09-24 03:48:08 +09:00
Naoki Takezoe
3c53fd8618 Merge branch 'issue-893' of https://github.com/McFoggy/gitbucket into McFoggy-issue-893 2015-09-24 01:21:00 +09:00
Naoki Takezoe
4ca4c57fff Merge branch 'master' of https://github.com/chazmuzz/gitbucket into chazmuzz-master 2015-09-24 00:46:58 +09:00
Naoki Takezoe
317a6fde30 (refs #913)Remove Java8 dependency 2015-09-23 19:12:21 +09:00
Naoki Takezoe
ad08d385e6 Bump markedj 1.0.2 to Java7 support 2015-09-23 12:22:55 +09:00
Naoki Takezoe
805e12aceb Update README.md 2015-09-22 11:12:39 +09:00
Naoki Takezoe
3a45912400 Update README.md 2015-09-22 11:12:07 +09:00
Naoki Takezoe
19817e2659 Merge pull request #912 from takezoe/markedj
Switch markdown processor to markedj from pegdown
2015-09-22 11:04:14 +09:00
Naoki Takezoe
50dc205ef7 Use JDK8 for Travis build 2015-09-22 03:26:57 +09:00
Naoki Takezoe
2402a3ac72 Enable task list after update issue 2015-09-22 03:06:53 +09:00
Naoki Takezoe
e1c155d09d Fix Markdown testcase 2015-09-21 12:08:16 +09:00
Naoki Takezoe
84c3bc4ad4 Refactoring 2015-09-21 11:24:59 +09:00
Naoki Takezoe
353784c23e Restore header anchor 2015-09-21 11:01:40 +09:00
Naoki Takezoe
a359624f01 Restore task-list support 2015-09-21 10:48:44 +09:00
Naoki Takezoe
a0f684cfdf Bump markedj to 1.0.1 2015-09-20 14:39:20 +09:00
Naoki Takezoe
1ea1e74a0c Fix links 2015-09-20 12:49:49 +09:00
Naoki Takezoe
8f7c5fc922 Fix code and ordered list style 2015-09-20 04:45:46 +09:00
Naoki Takezoe
667ef680c1 Switch markdown processor to markedj from pegdown 2015-09-20 03:51:46 +09:00
Charlie Murray
972ab0df50 Truncate text inside a box-content-row div rather than flowing out of the box 2015-09-15 20:13:46 +01:00
kanmi
1fddc01f6e Specify option to generate an RSA key 2015-09-16 01:28:31 +09:00
Naoki Takezoe
bb2e77d899 Update README.md 2015-09-13 11:56:18 +09:00
Naoki Takezoe
a3daf13c15 Fix issue link in Markdown 2015-09-07 02:10:37 +09:00
Naoki Takezoe
fb2b2e37ce Remove last committer from the file list 2015-09-07 00:50:03 +09:00
Naoki Takezoe
c1381179aa Fix code style 2015-09-06 23:32:06 +09:00
Naoki Takezoe
9e2dc3f892 Merge pull request #897 from cd01/patch-1
Fix typo in README.md
2015-09-04 13:32:29 +09:00
Naoki Takezoe
5aa548d613 Update octicons 2015-09-03 22:45:52 +09:00
Naoki Takezoe
5225a95d3a Merge branch 'hikaruworld-SupportCloneInDesktop' 2015-09-03 22:31:38 +09:00
Naoki Takezoe
53b7a1fce5 Merge branch 'SupportCloneInDesktop' of https://github.com/hikaruworld/gitbucket into hikaruworld-SupportCloneInDesktop 2015-09-03 22:29:14 +09:00
Naoki Takezoe
02369a4949 (refs #895)Fix generate anchor option in Wiki 2015-09-03 21:25:56 +09:00
cd01
1ca548991b Fix typo in README.md 2015-09-03 21:20:09 +09:00
Matthieu Brouillard
0772070523 split SMTP & notification, fixes #893 2015-09-02 16:22:54 +02:00
Naoki Takezoe
4bf3848856 Merge pull request #894 from McFoggy/new-h2-backup-plugin
reference gitbucket-h2-backup-plugin in README
2015-09-02 01:56:04 +09:00
Matthieu Brouillard
512425de4c reference gitbucket-h2-backup-plugin in README 2015-09-01 14:36:59 +02:00
Naoki Takezoe
7f28bd6a26 Update README.md 2015-08-30 03:57:26 +09:00
Naoki Takezoe
4088b2c1e8 Exclude group name from issue / pull request assignees 2015-08-30 03:33:29 +09:00
Naoki Takezoe
919d55c002 Fix NullPointerException for non-existent branches 2015-08-27 11:16:33 +09:00
Naoki Takezoe
068bbd0c3b Merge branch 'officer-retry_fix_#838' 2015-08-26 22:48:03 +09:00
Naoki Takezoe
9f50528192 Merge branch 'retry_fix_#838' of https://github.com/officer/gitbucket into officer-retry_fix_#838
# Conflicts:
#	src/main/scala/gitbucket/core/view/LinkConverter.scala
2015-08-26 22:47:41 +09:00
Naoki Takezoe
4c149cf01c (refs #831)url encode filename in the redirect path 2015-08-26 22:17:21 +09:00
Naoki Takezoe
c86c706406 (refs #864)Fix blame 2015-08-24 02:40:29 +09:00
Naoki Takezoe
3b0a0f55b5 Fix broken blame 2015-08-24 02:09:50 +09:00
Naoki Takezoe
4232b8184e Change a limit of initial amount of the repositories list 2015-08-23 15:47:07 +09:00
Naoki Takezoe
e5f3dfe293 Update version to 3.6.0 2015-08-23 15:42:52 +09:00
Naoki Takezoe
22af94d36a BugFix and improvement for pull request 2015-08-23 13:44:12 +09:00
Naoki Takezoe
d6b6781861 Add show more link for repositories and wiki pages 2015-08-23 03:19:52 +09:00
Naoki Takezoe
2222299793 Fix merge checking 2015-08-23 02:17:46 +09:00
Naoki Takezoe
fdd9a184b5 Fix presentation of commit list in the pull request detail view 2015-08-22 17:20:59 +09:00
Naoki Takezoe
99492e3f8e Merge pull request #886 from noc06140728/fix-octicon
Replace some icon to octicon
2015-08-21 02:12:15 +09:00
Naoki Takezoe
a42c40bbc1 Fix merge guide to display ssh url 2015-08-21 02:11:42 +09:00
Masahiro Namba
2794f9fcfc Replace some icon to octicon
- replace some of the non-octicon to octicon.
- adjust the color of octicon on the button.

modified icon is as follows.

- .icon-home          -> .octicon-home
- .icon-time          -> .octicon-clock
- .icon-ok            -> .octicon-check
- .icon-lock          -> .octicon-lock
- .icon-envelope      -> .octicon-mail
- .icon-pencil        -> .octicon-pencil
- .icon-remove-circle -> .octicon-x
- .icon-check         -> .octicon-clippy
- .icon-calendar      -> .octicon-calendar
- .icon-cog           -> .octicon-gear
- .icon-th-list       -> .octicon-list-unordered
- .icon-trash         -> .octicon-trashcan
- .icon-arrow-right   -> .octicon-arrow-right
- .icon-retweet       -> .octicon-git-compare
- .icon-comment       -> .octicon-comment
2015-08-20 20:23:47 +09:00
Naoki Takezoe
28c0262e74 Improve issue and pull request creation form 2015-08-19 10:20:52 +09:00
Naoki Takezoe
8634191bd2 Merge pull request #884 from skx/master
Updated to fix truncated name in JSON: watchers_coun
2015-08-18 17:27:29 +09:00
Steve Kemp
f73c86d533 Updated to fix truncated name in JSON: watchers_coun
The correct field in the JSON should be `watchers_count` rather
than the truncated version `watchers_coun`.
2015-08-18 09:14:44 +03:00
Naoki Takezoe
f042d709ac Improve issue creation form 2015-08-18 10:42:16 +09:00
Naoki Takezoe
e2a6149a93 Update auto_update.md 2015-08-17 13:29:41 +09:00
Naoki Takezoe
b2a7e2c7e2 Merge pull request #882 from garygreen/shorten-commit-message
File listing and sidebar display improvements
2015-08-17 06:19:00 +09:00
Naoki Takezoe
89fc143075 Update merge guide 2015-08-16 23:43:57 +09:00
Gary Green
a754a92799 File listing and sidebar display improvements 2015-08-16 14:55:18 +01:00
Naoki Takezoe
dc26fcf609 Merge branch 'garygreen-link-width' 2015-08-16 11:07:08 +09:00
Naoki Takezoe
b9db57eeef Merge branch 'link-width' of https://github.com/garygreen/gitbucket into garygreen-link-width 2015-08-16 11:03:48 +09:00
Naoki Takezoe
9b377c727d Improve the pull request creation form 2015-08-16 02:30:13 +09:00
Naoki Takezoe
e5b8d81bb4 Remove unused code 2015-08-16 01:13:25 +09:00
Naoki Takezoe
c85b31a7d5 Improve comparing view 2015-08-16 01:12:44 +09:00
Naoki Takezoe
6580e5458a Merge branch 'master' of https://github.com/takezoe/gitbucket 2015-08-16 00:55:18 +09:00
Naoki Takezoe
4e4e65eaa6 Improve th background color 2015-08-15 14:31:00 +09:00
Naoki Takezoe
9d19aad384 Merge pull request #880 from garygreen/issues-gui
Improved design of issue
2015-08-15 11:29:56 +09:00
Naoki Takezoe
c16a9f234b (refs #878)Hide the Delete button for other than the head of branch 2015-08-15 11:21:26 +09:00
Naoki Takezoe
ace551c33d (refs #871)Make link for @mention which contains dot 2015-08-15 10:15:44 +09:00
Naoki Takezoe
1e6e686692 Merge pull request #873 from superhj1987/master
Update main.scala.html
2015-08-15 09:59:16 +09:00
Gary Green
afdcc3f7c0 Improved design of issue 2015-08-15 00:12:56 +01:00
Naoki Takezoe
00e64bc46c Remove IntelliJ specific file 2015-08-13 09:24:18 +09:00
Bryant Hang
a959e1820f Update main.scala.html
fix header plus dropdown menu display bug in safari and add 'Your profile' in user dropdown menu
2015-08-12 15:39:54 +08:00
Naoki Takezoe
3dfbdbfe51 (refs #865)Fix styles for repository viewer 2015-08-09 01:41:10 +09:00
Naoki Takezoe
5c46dc0bd3 (refs #865)Fix paginator of the commit list 2015-08-09 01:31:29 +09:00
Naoki Takezoe
db60db674f (refs #865)Update commit list presentation 2015-08-09 01:14:21 +09:00
Naoki Takezoe
687a4f14e1 (refs #865)Fix presentation of file finder and blow view 2015-08-08 21:11:34 +09:00
Naoki Takezoe
bb10365b8b (refs #865)Apply the flat style to box headers 2015-08-08 13:32:42 +09:00
Naoki Takezoe
74ed3bf6a0 Update README.md 2015-08-06 07:17:05 +09:00
Naoki Takezoe
d1d7fdc488 Merge pull request #862 from McFoggy/plugins-info
list installed plugins in the system administration menu
2015-08-06 02:20:07 +09:00
Matthieu Brouillard
67775a4c62 add a comprehensive message when no plugin is detected on the installation 2015-08-05 17:38:14 +02:00
Naoki Takezoe
317b5cb096 Merge pull request #861 from McFoggy/system-admin-extensibility
give an id to system admin menu container to allow plugin extension
2015-08-06 00:16:42 +09:00
Matthieu Brouillard
2929517d7e list installed plugins in the system administration menu 2015-08-05 16:45:00 +02:00
Matthieu Brouillard
51e788396d give an id to system admin menu container to allow plugin extension 2015-08-05 11:38:04 +02:00
Naoki Takezoe
1321653bf6 (refs #848)Additional fix for header width 2015-08-05 02:26:38 +09:00
Naoki Takezoe
3899854854 Merge pull request #848 from garygreen/css-container
Increase container width
2015-08-05 02:21:25 +09:00
Naoki Takezoe
c0ca842ba7 Merge pull request #857 from ssogabe/fixed_build_error
small fixed: env.sh has been already removed.
2015-08-04 00:40:44 +09:00
Seiji Sogabe
24b05d28db env.sh has been already removed. 2015-08-03 19:38:46 +09:00
chocolatle
f0268b105c Fix making bad link from certain references. 2015-08-03 02:14:38 +09:00
Naoki Takezoe
0a46e180a9 Merge pull request #855 from mslinn/master
Updated installation script to GitBucket 3.5
2015-08-03 01:11:42 +09:00
Mike Slinn
e6a215a9c3 Updated to GitBucket 3.5 2015-08-02 08:47:47 -07:00
Naoki Takezoe
8ca7117065 (refs #854)Backup document is moved to Wiki 2015-08-02 12:52:40 +09:00
Naoki Takezoe
ba0a07b835 Merge pull request #854 from McFoggy/backup-gitbucket
add an example of a backup script and some usage instructions
2015-08-02 12:44:22 +09:00
Naoki Takezoe
4a35b65c2c Update release scripts 2015-08-01 03:02:38 +09:00
Naoki Takezoe
836fa47812 GitBucket 3.5.0 release 2015-08-01 01:30:32 +09:00
Matthieu Brouillard
5b658ef6ff add backup script and instructions 2015-07-31 16:39:16 +02:00
Gary Green
e9ff24d9a7 Make header and sidemenu links clickable across full width. 2015-07-29 23:02:29 +01:00
Gary Green
a92051a4c3 Increase container width 2015-07-29 22:52:46 +01:00
Naoki Takezoe
77b3650580 (refs #844)Improve global header menu 2015-07-28 21:53:51 +09:00
Naoki Takezoe
67ee6857ad Remove unnecessary spec 2015-07-28 11:16:02 +09:00
Naoki Takezoe
5ab15c0a14 Merge branch 'beraboris-implement-668' 2015-07-28 11:13:51 +09:00
Naoki Takezoe
96a3f2c301 (refs #810)Some fix about pull request 2015-07-28 11:12:11 +09:00
Naoki Takezoe
85707264c4 Fix position of fork-form 2015-07-28 02:04:49 +09:00
Naoki Takezoe
ed05422ea8 Merge branch 'implement-668' of https://github.com/beraboris/gitbucket into beraboris-implement-668 2015-07-28 01:58:30 +09:00
Naoki Takezoe
8f10c8051e (refs #810)Display Compare (or Pull Request) button for the default branch of the forked repository 2015-07-28 01:57:54 +09:00
Naoki Takezoe
41fff399b5 Redirect to the repository after sign-in by clicking fork button 2015-07-26 03:17:13 +09:00
Naoki Takezoe
9e237647b0 Small fix about icons 2015-07-26 02:59:26 +09:00
Naoki Takezoe
1be53c6746 Merge branch 'sapk-master' 2015-07-23 02:11:17 +09:00
Naoki Takezoe
f2368b03c0 (refs #409)Fix header anchor name in Markdown 2015-07-23 00:53:29 +09:00
Naoki Takezoe
95284c0b36 (refs #409)Fix Markdown style 2015-07-22 02:42:30 +09:00
Naoki Takezoe
f1e21a93fb (refs #409)Fix icons 2015-07-22 02:42:30 +09:00
Naoki Takezoe
689811f659 Add forks icon 2015-07-19 23:23:00 +09:00
Naoki Takezoe
33ccb4e98c Merge branch 'uli-heller-email-sender' 2015-07-19 02:15:03 +09:00
Naoki Takezoe
29f6e98f9c Merge branch 'email-sender' of https://github.com/uli-heller/gitbucket into uli-heller-email-sender 2015-07-19 02:14:36 +09:00
Naoki Takezoe
775c8cc064 Update release operation 2015-07-19 02:04:52 +09:00
Naoki Takezoe
9a974d047c Small fix 2015-07-19 02:01:15 +09:00
Naoki Takezoe
3fd252d2db Remove unnecessary file 2015-07-19 01:47:42 +09:00
Naoki Takezoe
43edb034c8 Merge branch 'linux-ubuntu1404' of https://github.com/uli-heller/gitbucket into uli-heller-linux-ubuntu1404 2015-07-19 01:46:40 +09:00
Naoki Takezoe
e629ca391e Improve env.sh to extract GITBUCKET_VERSION from build.scala 2015-07-19 01:42:42 +09:00
Naoki Takezoe
0db7eba3f2 Merge pull request #836 from shiena/patch/fix_contenttype
Fix duplicated Content-Type
2015-07-18 23:30:27 +09:00
Naoki Takezoe
a472abc88e Merge pull request #829 from muddydixon/fix/group_image_cursor_css
fixed cursor pointer css of group image change
2015-07-18 23:25:04 +09:00
Naoki Takezoe
861c619c19 Merge branch 'master' of https://github.com/sapk/gitbucket into sapk-master 2015-07-18 22:18:02 +09:00
Naoki Takezoe
124f331963 Fix testcase 2015-07-18 22:01:45 +09:00
Mitsuhiro Koga
76fcd44191 Fix duplicated Content-Type 2015-07-16 13:05:40 +09:00
Naoki Takezoe
2124c0b88c Ignore ensime files 2015-07-14 02:49:21 +09:00
Naoki Takezoe
2f5ab8e3b9 (refs #830)Bump pegdown to 1.5.0 and support strike syntax 2015-07-14 02:47:26 +09:00
muddydixon
32c8b6914b fixed cursor pointer css of group image change 2015-07-13 16:54:12 +09:00
Naoki Takezoe
7e9d940f64 Merge branch 'beraboris-better-atom-titles' 2015-07-05 22:17:27 +09:00
Naoki Takezoe
59e826b630 (refs #811)Remove html tags from title of atom feed 2015-07-05 22:15:39 +09:00
Naoki Takezoe
88f3ee4b13 Merge branch 'better-atom-titles' of https://github.com/beraboris/gitbucket into beraboris-better-atom-titles 2015-07-05 21:37:40 +09:00
Naoki Takezoe
b7380a084e Merge pull request #814 from uli-heller/patch-1
Fixes #748 - duplicate c3p0.war
2015-07-05 21:33:36 +09:00
Naoki Takezoe
cb65e790ae (refs #812)Apply GitRepositoryFilter to SSH access too 2015-07-05 15:38:55 +09:00
Naoki Takezoe
573eabee93 (refs #812)Prepend '/' to repository name 2015-07-05 15:08:52 +09:00
Naoki Takezoe
f0d4c6546a (refs #812)SSH support for plug-in served git repository 2015-07-05 14:16:16 +09:00
uli-heller
b0943c87c8 Fixes #748 - duplicate c3p0.war 2015-07-05 06:16:42 +02:00
Boris Bera
4f1208ea98 Atom feed now puts the activity message in title
Originally it used to put the activity type in the message. This gave
no useful information. The user had to open the news item to see what
was going on. Now, the title is the activity's message rendered to html.
Note that this is the same as the content of the news item.

This fixes #481
2015-07-04 15:29:09 -04:00
Naoki Takezoe
02f16639ea Remove twirl-compiler from dependency 2015-07-05 00:50:27 +09:00
Naoki Takezoe
cd5e28c0b8 (refs #812)Add GitRepositoryFilter for authentication 2015-07-05 00:13:34 +09:00
Naoki Takezoe
6f7579f8d9 (refs #812)Add new extension point to serve Git repository by plug-in 2015-07-04 17:42:13 +09:00
Boris Bera
f1d2a71b49 Fixed url generated by pull request/compare button
The `:` character was getting escaped leading to an ugly url. This is
different from the urls generated when editing the branches in a pull
request. In that case the url did not have `:` escaped.
2015-07-03 21:23:53 -04:00
Boris Bera
fd4fe0dc0d Pullreqs in forks now use upstream master as origin
This implements #668.
2015-07-03 20:56:06 -04:00
Naoki Takezoe
3d5e4a4225 (refs #802)Allow '+' in repository name 2015-07-03 21:40:26 +09:00
Naoki Takezoe
10ffb452d0 Fix delete branch confirmation message 2015-07-03 21:19:14 +09:00
Naoki Takezoe
3218bddbdc (refs #808)Add collaborators when fork repository by group 2015-07-03 20:47:37 +09:00
Naoki Takezoe
59f78dcbcb Fix behavior when enableAnchor is false 2015-06-28 03:43:15 +09:00
Naoki Takezoe
0fe062a02f Add option to disable anchor for headline in markdown 2015-06-28 03:20:55 +09:00
Naoki Takezoe
869eaf8cfd Update version to 3.5.0-SNAPSHOT 2015-06-27 23:09:12 +09:00
Naoki Takezoe
5b445c9736 Improve declarative Plugin definition interface 2015-06-27 20:54:19 +09:00
Naoki Takezoe
6f3f3eaa99 Fix Wiki font style of Wiki editing form 2015-06-27 19:41:48 +09:00
Naoki Takezoe
a23ce92676 Add pom.xml for deploying assemble jar 2015-06-27 19:09:43 +09:00
Uli Heller
ad55d5199d Use '{loginName} {fromAddress}' for the from field within the notification emails 2015-06-27 08:45:27 +02:00
Uli Heller
bd05895761 Make the release process work on linux 2015-06-27 07:11:23 +02:00
Uli Heller
c68dd6c891 Another change to make it work on linux - now linux and windows commands are mixed up unfortunately... 2015-06-27 06:56:12 +02:00
uli-heller
daae9ae1e7 Update how_to_run.md
Once "bashism-source" is applied, the build runs smoothly on linux, no need to change anything...
2015-06-27 06:56:12 +02:00
Uli Heller
33dde75ae1 doc change: build.xml -> release/build.xml 2015-06-27 06:56:12 +02:00
Uli Heller
b2e1e1796d Fixed bashism 'source' 2015-06-27 06:16:22 +02:00
Naoki Takezoe
c4a682773c Fix warnings 2015-06-27 10:18:22 +09:00
Naoki Takezoe
07e1873cb3 Update release.md 2015-06-27 10:14:30 +09:00
Naoki Takezoe
14d0c4dc32 GitBucket 3.4 release 2015-06-27 10:12:25 +09:00
Naoki Takezoe
19f79b6e54 Merge pull request #789 from milligramme/patch-1
modify css: hide href content on printing
2015-06-26 17:47:31 +09:00
Naoki Takezoe
e0b9fc9481 (refs #788)Fix NotFound in comparing with sibling repository 2015-06-26 17:06:33 +09:00
milligramme
16c28ad938 modify css: hide href content on printing
on printing, href content appeared. 
it is caused by bootstrap.css

faae237ac5/src/main/webapp/assets/vendors/bootstrap/css/bootstrap.css (L183-L185)
2015-06-26 15:31:15 +09:00
Naoki Takezoe
0e5af9ffa9 Update README.md 2015-06-23 14:29:32 +09:00
Naoki Takezoe
a0f6f03d4e Add IntelliJ IDEA logo 2015-06-23 14:18:05 +09:00
Naoki Takezoe
f4cdfc5f32 (refs #769)go-import meta tag is generated for only public repos 2015-06-10 23:18:35 +09:00
Naoki Takezoe
b45988705b (refs #769)Add go-import meta tag 2015-06-10 23:14:12 +09:00
Naoki Takezoe
dd50239759 (refs #780)Not add an issue refer comment if it already exists 2015-06-09 10:32:58 +09:00
Naoki Takezoe
7297a07466 (refs #751)Fix issue reference in verbatim node 2015-06-09 02:24:45 +09:00
Naoki Takezoe
e5fa43f91c Merge pull request #778 from team-lab/fix/diff-ignore-space
fix diff ignore space
2015-06-08 13:22:25 +09:00
nazoking
88080c8d02 fix diff ignore space 2015-06-08 01:33:22 +09:00
Naoki Takezoe
d38dfee540 Remove empty lines 2015-06-07 22:38:03 +09:00
Naoki Takezoe
4da6a7b6f9 Merge pull request #768 from Mura-Mi/avoid-npe-masterless
Avoid NPE when master branch does not exist
2015-06-06 03:34:34 +09:00
Naoki Takezoe
957dfeed6d (refs #775)Move isRenderable() to helpers from PluginRepository 2015-06-05 01:34:23 +09:00
Naoki Takezoe
aa5a07b98e (refs #775)Add new extension point to add markup render 2015-06-05 01:30:00 +09:00
Naoki Takezoe
fd1ee07297 Provides declarative style plug-in definition. 2015-06-03 02:18:07 +09:00
Naoki Takezoe
07026125a9 Merge remote-tracking branch 'origin/master' 2015-06-02 01:06:21 +09:00
Naoki Takezoe
badb747d24 (refs #773)Fix url processing in the blame view 2015-06-02 01:06:02 +09:00
Naoki Takezoe
e27c20d600 Merge pull request #771 from bfritz/fix-release-typo
Fix typo in release.md
2015-06-01 08:27:26 +09:00
Naoki Takezoe
eb46c94c7c Merge pull request #772 from xuwei-k/fix-travis-fail
fix .travis.yml
2015-06-01 07:26:16 +09:00
xuwei-k
2e58f82a61 fix .travis.yml
```
java.lang.RuntimeException: Setting value cannot be null: {file:/home/travis/build/takezoe/gitbucket/}gitbucket/*:version
	at scala.sys.package$.error(package.scala:27)
	at sbt.EvaluateSettings$INode.setValue(INode.scala:143)
	at sbt.EvaluateSettings$MixedNode.evaluate0(INode.scala:175)
```
2015-06-01 04:04:45 +09:00
Brad Fritz
df28013b18 Fix typo in release.md 2015-05-31 11:53:08 -04:00
Naoki Takezoe
8f4e285974 Update README.md 2015-05-31 18:37:12 +09:00
Naoki Takezoe
08c4eeeefe (refs #770)Update release.md 2015-05-31 17:33:36 +09:00
Naoki Takezoe
1fdc2c629c (refs #770)Add set assembly invocation to deploy-assembly-jar.sh 2015-05-31 17:33:21 +09:00
Naoki Takezoe
a6767a1c3d (refs #770)Gather release scripts 2015-05-31 16:58:51 +09:00
Naoki Takezoe
c539d6b643 Update build.xml 2015-05-31 12:21:07 +09:00
Naoki Takezoe
80c3ffdfd7 Update release.md 2015-05-31 12:18:19 +09:00
Naoki Takezoe
f80f2106fe Fix deploy script 2015-05-31 11:58:02 +09:00
Naoki Takezoe
cbfae66882 Fix version 2015-05-31 11:52:15 +09:00
Naoki Takezoe
8a5014fcbe Update release.md 2015-05-31 11:49:35 +09:00
Naoki Takezoe
54d1bff213 Bump version to 3.3 2015-05-31 11:44:01 +09:00
Naoki Takezoe
0075664b9a Merge pull request #764 from team-lab/feature/file-finder-button-on-blob-view
add file finder button on blob view.
2015-05-31 11:26:28 +09:00
Mura-Mi
a2c26f0f2c Avoid NPE when master branch does not exist 2015-05-29 01:28:11 +09:00
nazoking
45f41a13e4 add file finder button on blob view. 2015-05-27 13:59:36 +09:00
Naoki Takezoe
faae237ac5 Update README.md 2015-05-26 01:56:57 +09:00
Naoki Takezoe
e01758e74c Update README.md 2015-05-25 23:29:20 +09:00
Naoki Takezoe
db7dd31c79 Merge branch 'sapk-fix-mobile' 2015-05-25 22:57:39 +09:00
Naoki Takezoe
7d7ac5e2be Merge branch 'fix-mobile' of https://github.com/sapk/gitbucket into sapk-fix-mobile
# Conflicts:
#	src/main/webapp/assets/common/css/gitbucket.css
2015-05-25 22:57:24 +09:00
Naoki Takezoe
577016a33f (refs #762)Add meta-tag to disable IE backward compatibility mode 2015-05-25 22:12:12 +09:00
Naoki Takezoe
fdf2102923 (refs #763)Don't remove disabled user's data and repositories. 2015-05-25 22:03:40 +09:00
Naoki Takezoe
8b47e57be0 Merge branch 'team-lab-featuer/image-on-diff' 2015-05-25 02:06:36 +09:00
Naoki Takezoe
8dad6b64b0 Merge branch 'featuer/image-on-diff' of https://github.com/team-lab/gitbucket into team-lab-featuer/image-on-diff
# Conflicts:
#	src/main/webapp/assets/common/js/gitbucket.js
2015-05-25 02:06:14 +09:00
Naoki Takezoe
05d36abdab Merge branch 'team-lab-feature/blame' 2015-05-25 02:00:49 +09:00
Naoki Takezoe
04e31c5b4f Merge branch 'feature/blame' of https://github.com/team-lab/gitbucket into team-lab-feature/blame
# Conflicts:
#	src/main/scala/gitbucket/core/controller/RepositoryViewerController.scala
#	src/main/webapp/assets/common/css/gitbucket.css
2015-05-25 02:00:27 +09:00
Naoki Takezoe
6470428a85 Merge pull request #696 from team-lab/feature/file-finder
add file finder
2015-05-25 01:49:45 +09:00
Naoki Takezoe
a7efb3989a Merge pull request #760 from sugamasao/add-pre-tag-horizontal-scroll
Add pre-tag horizontal scroll on markdown
2015-05-21 20:28:24 +09:00
sugamasao
32072d0bbf Add pre-tag horizontal scroll on markdown 2015-05-20 18:22:53 +09:00
Antoine GIRARD
78a3e4454d Fix mobile view 2015-05-18 21:37:07 +02:00
Antoine GIRARD
1ff68111b4 Merge remote-tracking branch 'upstream/master' 2015-05-18 15:51:03 +02:00
Naoki Takezoe
e576178e1e Merge pull request #758 from sugamasao/fix-height-of-diff-line
`_` is hidden by diff line
2015-05-17 00:26:19 +09:00
Naoki Takezoe
0f8bc2b03d Merge pull request #755 from tomomura/fix/api-user-login-value
fix login value for Webhook and API.
2015-05-17 00:25:32 +09:00
sugamasao
43565458d4 Fix fix height of diff line
`_` is hidden by diff line
2015-05-15 15:37:36 +09:00
tomomura
71cc3be6d5 fix login value. 2015-05-13 11:46:02 +09:00
Naoki Takezoe
9a8eef7b19 (refs #738)Set "application/octet-stream" as Content-Type to avoid automatic charset detection for text files in Scalatra 2015-05-10 17:36:26 +09:00
Naoki Takezoe
a08c4368b7 Revert dependency 2015-05-10 16:32:21 +09:00
Naoki Takezoe
3b456b2aab Move deploy-assembly-jar script 2015-05-10 15:36:27 +09:00
nazoking
31559418ba add image diff 2015-05-09 01:50:51 +09:00
Naoki Takezoe
692a6e43bc Update release.md 2015-05-07 11:30:41 +09:00
Naoki Takezoe
af4cce654c Update release.md 2015-05-07 11:29:41 +09:00
Naoki Takezoe
c63b02fd4a Move ERD and SVG file to /doc from /etc 2015-05-07 11:22:27 +09:00
Naoki Takezoe
e6974b6e51 Merge branch 'master' of https://github.com/takezoe/gitbucket 2015-05-07 11:14:25 +09:00
Naoki Takezoe
da3b7dbeff Improve assembly-jar deploying 2015-05-07 11:14:17 +09:00
Naoki Takezoe
9737bd7012 (refs #739)Update document 2015-05-07 11:08:11 +09:00
Naoki Takezoe
55fd8e5e2d (refs #739)Update document 2015-05-07 11:06:32 +09:00
Naoki Takezoe
9c4f181d93 (refs #739)Document for release operation 2015-05-07 10:55:59 +09:00
Naoki Takezoe
374342cfc1 Merge pull request #718 from team-lab/feature/api-add-issue-urls
Feature/api add issue urls
2015-05-06 21:19:59 +09:00
Naoki Takezoe
6fbdd237d1 Merge pull request #731 from ndarilek/master
Implement additional accessibility fixes
2015-05-06 10:46:13 +09:00
Naoki Takezoe
4e78f01a09 Dispose ObjectLoader certainty 2015-05-06 10:30:53 +09:00
Naoki Takezoe
8853264808 Merge pull request #744 from jochembroekhoff/master
jQuery updated to v1.11.1
2015-05-06 09:09:07 +09:00
Jochem Broekhoff
6db9b8038f jQuery updated to v1.11.1 2015-05-05 09:59:23 +02:00
Naoki Takezoe
c73e89ccd4 Update version to 3.2.0 2015-05-03 01:45:43 +09:00
Naoki Takezoe
f58a506780 Define Version 3.2 2015-05-03 01:26:35 +09:00
Naoki Takezoe
411d19e74e Update README.md 2015-05-03 01:25:55 +09:00
Naoki Takezoe
bd51ffd9d2 (refs #660)Register Scalatra error callback to rollback transaction 2015-05-03 01:14:22 +09:00
Naoki Takezoe
83980fdccd (refs #534)Allow to download large file 2015-05-02 23:09:18 +09:00
Naoki Takezoe
baab243bc8 Merge pull request #716 from expf/fallback_message
Fix style of message for browsers that do not support drag'n'drop file u...
2015-04-30 19:36:09 +09:00
Naoki Takezoe
4dd8e1dc63 (refs #707)Exclude disabled users from completion list 2015-04-30 16:19:04 +09:00
shimamoto
0b11b8b084 Merge pull request #732 from takezoe/delete_old_activity
(refs #441) Add limit of activity log configuration
2015-04-29 23:16:10 +09:00
Naoki Takezoe
704775dc60 (refs #441)Fix delete condition 2015-04-29 20:19:20 +09:00
Naoki Takezoe
c7a7be1de0 (refs #441)Fix testcase 2015-04-29 15:26:14 +09:00
Naoki Takezoe
e21a970977 (refs #441)Add limit of activity log configuration 2015-04-29 12:57:57 +09:00
Naoki Takezoe
a526dcf2dd Separate AutoUpdate from InitializeListener 2015-04-29 09:37:29 +09:00
Nolan Darilek
a7b48d63e4 Add autofocus to forms at strategic places. 2015-04-28 13:16:43 -05:00
Nolan Darilek
3677906e95 Label additional icons. 2015-04-28 13:11:50 -05:00
Naoki Takezoe
e86710fbbd Merge pull request #728 from kaakaa/testhook-npe
fix test hook error
2015-04-28 02:21:18 +09:00
kaakaa
73e850493a fix test hook error 2015-04-25 16:10:52 -04:00
Naoki Takezoe
7d735f6f8a Merge pull request #725 from sebastiancadena/patch-1
Fix typo
2015-04-24 19:41:11 +09:00
Juan Sebastian Cadena
043e99a9eb Fix typo
Repositories
2015-04-23 19:24:47 -05:00
Naoki Takezoe
65e079b1d3 Merge pull request #722 from ndarilek/master
Label unlabelled icons for accessibility.
2015-04-24 01:58:15 +09:00
Naoki Takezoe
32e6f584d8 Merge pull request #723 from team-lab/fix/#721-npe-getFileList-multi-origin-subfolder
(refs #721)NullPointerException on getFileList
2015-04-24 01:57:01 +09:00
nazoking
ebc5219ce6 (refs #721)NullPointerException on getFileList if branch has multi-origin and get sub-folder file list 2015-04-22 13:45:31 +09:00
Nolan Darilek
ad8e620bdf Label unlabelled icons for accessibility. 2015-04-21 17:38:19 -05:00
Naoki Takezoe
8590c693b9 (refs #715)Escape $ in replacement string 2015-04-22 01:20:11 +09:00
nazoking
5568acc5f3 add html_url on api issue-comment 2015-04-20 14:32:16 +09:00
nazoking
c467594199 add html_url and comments_url on api issue 2015-04-20 13:19:17 +09:00
Kohei Ichioka
8b29bf7d93 Fix style of message for browsers that do not support drag'n'drop file uploads 2015-04-20 10:24:41 +09:00
Naoki Takezoe
43b7f83082 (refs #713)Fix anchor to headline in Wiki 2015-04-20 00:24:49 +09:00
Naoki Takezoe
9ef5c981ef (refs #666)Remove html5shiv.js reference 2015-04-19 16:49:11 +09:00
Naoki Takezoe
f027ac34d4 Merge pull request #697 from banjun/fix-basicauth-for-public-repo
let non-anonymous user read access to public repos with basic auth
2015-04-16 02:13:33 +09:00
Naoki Takezoe
2ea31d6869 Revert "Fix basic auth read when anon access disabled"
This reverts commit 5f3d6242fd.
2015-04-16 02:12:40 +09:00
Naoki Takezoe
a1af7d0f9c Merge pull request #702 from marklacroix/no-anon-basic-auth-read
Fix basic auth read when anon access disabled
2015-04-16 02:05:50 +09:00
Naoki Takezoe
797bf37bfa Merge pull request #705 from team-lab/fix/word-diff-probrem
fix word-diff javascript error
2015-04-16 01:52:54 +09:00
Shintaro Murakami
7f78815c11 (refs #712) Fix problems of showing inline notes. 2015-04-13 19:31:15 +09:00
Naoki Takezoe
d8a3f308ed Merge pull request #709 from jparound30/fix_db_conn_handling
Fix db connection error in dev mode
2015-04-11 22:07:42 +09:00
jparound30
74e18a982d Fix db connection error in dev mode
Changes to close the no longer needed DB connections.
2015-04-11 13:32:29 +09:00
nazoking
e86ad423c5 fix word-diff javascript error (error occured if dictionary contains the word 'hasOwnProperty') 2015-04-09 22:18:46 +09:00
Naoki Takezoe
2f00060c57 Merge pull request #699 from rlazoti/fix-notifier
Fix issue notifications
2015-04-09 20:07:11 +09:00
Naoki Takezoe
3b7e6aa68e Merge pull request #701 from team-lab/fix/api-issue-user
fix some api user is invalid, and add content-type on webhook
2015-04-09 02:27:16 +09:00
Mark LaCroix
5f3d6242fd Fix basic auth read when anon access disabled
Basic authentication fails when not updating a public repository
with the "Disable Anonymous Access" option enabled
2015-04-08 11:46:44 -04:00
nazoking
6793a86bae fix some api user is invalid
issue.userName -> issue.openedUserName
  issueComment.userName -> issueComment.commentedUserName
2015-04-08 20:16:47 +09:00
Rodrigo Lazoti
d99ce20529 fix issue notifications 2015-04-07 10:56:52 -03:00
banjun
93e7b604cd let non-anonymous user read access to public repos with basic auth 2015-04-06 22:15:17 +09:00
nazoking
59c18056fc add file finder 2015-04-06 21:22:03 +09:00
Naoki Takezoe
5c81ce9b68 Add SystemSettings to Plugin arguments 2015-04-05 22:46:48 +09:00
Naoki Takezoe
c9cf62701f Merge pull request #690 from noc06140728/fix-basic-auth
Allow a password that contains colons
2015-04-05 17:04:43 +09:00
Naoki Takezoe
f65babca4b Merge branch 'team-lab-feature/#422-compare-link-near-branch-selector' 2015-04-05 16:53:09 +09:00
Naoki Takezoe
23c146fc5d Merge branch 'feature/#422-compare-link-near-branch-selector' of https://github.com/team-lab/gitbucket into team-lab-feature/#422-compare-link-near-branch-selector
Conflicts:
	src/main/twirl/gitbucket/core/repo/files.scala.html
2015-04-05 16:52:47 +09:00
Naoki Takezoe
e14f336142 Merge pull request #694 from team-lab/feature/#536-Commits-view-on-directory
Browse history for directory
2015-04-05 16:46:20 +09:00
nazoking
54647be5bd (#536) Browse commits for directory 2015-04-04 11:13:07 +09:00
Naoki Takezoe
9517d65646 Update README.md 2015-04-04 02:55:15 +09:00
Naoki Takezoe
0156e401fa Update README.md for 3.1.1 release 2015-04-04 02:39:25 +09:00
Naoki Takezoe
d6e2bc464d Merge branch 'master' of https://github.com/takezoe/gitbucket 2015-04-03 22:39:29 +09:00
Naoki Takezoe
5124ff593d Update version number to 3.1.1 2015-04-03 22:39:17 +09:00
Naoki Takezoe
727c90afdc Merge pull request #681 from team-lab/feature/improve-file-list-performance
Feature/improve file list performance
2015-04-03 22:32:51 +09:00
Naoki Takezoe
9ebd5e3265 Merge pull request #691 from team-lab/fix/#684-api-contextpath-support
fix/api context-path support
2015-04-03 22:31:09 +09:00
nazoking
d120142127 (ref #684)fix api context-path support 2015-04-02 16:53:30 +09:00
Naoki Takezoe
f9e4cddcaf Update version number to 3.1.1 2015-04-02 11:10:29 +09:00
Naoki Takezoe
8f64f174d9 Rolled back H2 to 1.4.180 from 1.4.186 2015-04-02 11:08:44 +09:00
Masahiro Namba
d6817796b3 Allow a password that contains colons
Allow a password that contains colons in the basic autentication.
2015-04-01 02:11:05 +09:00
nazoking
ab19d473c4 (refs #442)show compare/pull request link near branch link. 2015-03-30 21:16:42 +09:00
Naoki Takezoe
48f0116358 Fix compilation error 2015-03-29 03:37:48 +09:00
Naoki Takezoe
d8e6e97845 Pass ServletContext to Plugin 2015-03-29 03:26:06 +09:00
Naoki Takezoe
9a8920788c Update gutbucket version to 3.1.0 2015-03-29 02:27:28 +09:00
nazoking
27864a3a3c Performance Improve for getFileList 2015-03-29 01:21:10 +09:00
nazoking
b39e863591 add test for getFileList 2015-03-29 01:12:33 +09:00
nazoking
d8d18ed25c Merge branch 'master' into feature/blame
Conflicts:
	src/main/scala/gitbucket/core/util/JGitUtil.scala
2015-03-28 21:19:40 +09:00
Naoki Takezoe
7661e8cadd Update assembly jar version to 3.1.0 2015-03-28 17:24:09 +09:00
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
83fd2648f5 follow rename 2015-03-27 21:19:02 +09:00
nazoking
8e81758941 fix link 2015-03-27 21:06:12 +09:00
nazoking
41a6a29771 fix for ie 7,8,9 2015-03-27 21:03:41 +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
nazoking
3e0a50926f resolve gravatar on blame 2015-03-09 23:42:04 +09:00
Naoki Takezoe
cce62de075 (refs #642)Fix markdown rendering style 2015-03-09 19:18:38 +09:00
Antoine GIRARD
dff816324d Rebase + Remove all unused image 2015-03-08 17:15:48 +01: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
Antoine GIRARD
a33e2c6e36 Completely moved to octicon 2015-03-04 23:46:46 +01:00
Antoine GIRARD
75ef82d18a ... 2015-03-04 23:46:46 +01:00
Antoine GIRARD
eb3c522122 Moved to octicon 2015-03-04 23:41:26 +01:00
Antoine GIRARD
6c8bcfc62e Import of octicons 2015-03-04 23:10:55 +01: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
nazoking
e408eb43bb fix parent. only path exists 2015-03-04 03:37:46 +09:00
nazoking
dc0aa0851e implove source-line-num performance.
and Stop scroll when click line number.
2015-03-04 03:14:22 +09:00
nazoking
51d7c43489 add blame 2015-03-04 01:52:07 +09: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
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
nazoking
0acbaeae86 new branches ui like GitHub. 2015-01-29 21:38:50 +09:00
takezoe
140f1eb31b Add sbt-assembly configuration 2015-01-25 02:31:21 +09:00
Rodrigo Lazoti
172af307a6 add connection pool to Database object 2015-01-20 12:14:28 -02:00
hikaruworld
70c386a934 Disable icone platform is linux && null 2014-08-13 21:28:27 +09:00
hikaruworld
08eb21844a Check the null value of UserAgent 2014-08-13 18:06:56 +09:00
hikaruworld
7b37d6b571 Change to platform userAgent. 2014-08-13 18:06:35 +09:00
hikaruworld
f52bd2bcc0 support Desktop in Clone 2014-08-12 21:45:03 +09:00
451 changed files with 42418 additions and 22239 deletions

2
.gitignore vendored
View File

@@ -1,5 +1,7 @@
*.class
*.log
.ensime
.ensime_cache
# sbt specific
dist/*

View File

@@ -1,3 +1,6 @@
language: scala
scala:
- 2.11.2
sudo: false
script:
- sbt test
jdk:
- oraclejdk8

7
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,7 @@
# Guideline for Issues
- If you have any question about GitBucket, send it to [gitter room](https://gitter.im/gitbucket/gitbucket) before raise an issue.
- Make sure check whether there is a same question or request in the past.
- When raise a new issue, write subject in **English** at least.
- We can also support in Japaneses other than English at [gitter room for Japanese](https://gitter.im/gitbucket/gitbucket_ja).
- First priority of GitBucket is easy installation and reproduce GitHub behavior, so we might reject if your request is against it.

151
README.md
View File

@@ -1,8 +1,7 @@
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 [![Gitter chat](https://badges.gitter.im/gitbucket/gitbucket.png)](https://gitter.im/gitbucket/gitbucket) [![Build Status](https://travis-ci.org/gitbucket/gitbucket.svg?branch=master)](https://travis-ci.org/gitbucket/gitbucket)
=========
GitBucket is the easily installable GitHub clone powered by Scala.
GitBucket is a GitHub clone powered by Scala which has easy installation and high extensibility.
Features
--------
@@ -14,29 +13,22 @@ The current version of GitBucket provides a basic features below:
- Wiki
- Issues
- Fork / Pull request
- Mail notification
- Email notification
- Activity timeline
- User management (for Administrators)
- Group (like Organization in Github)
- LDAP integration
- Simple user and group management with LDAP integration
- Gravatar support
- Plug-in system
Following features are not implemented, but we will make them in the future release!
- Network graph
- 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).
If you want to try the development version of GitBucket, see the documentation for developers at [Wiki](https://github.com/gitbucket/gitbucket/wiki).
Installation
--------
1. Download latest **gitbucket.war** from [the release page](https://github.com/takezoe/gitbucket/releases).
1. Download latest **gitbucket.war** from [the release page](https://github.com/gitbucket/gitbucket/releases).
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)
If you are using Gitbucket behind a webserver please make sure you have increased the **client_max_body_size** (on nginx)
The default administrator account is **root** and password is **root**.
@@ -47,38 +39,129 @@ or you can start GitBucket by `java -jar gitbucket.war` without servlet containe
- --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.
To upgrade GitBucket, only replace gitbucket.war after stop GitBucket. 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)
For Installation on Windows Server with IIS see [this wiki page](https://github.com/gitbucket/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.
```
$ 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
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/`
On OS X, generate `gitbucket.plist` by [this script](https://raw.githubusercontent.com/gitbucket/gitbucket/master/contrib/macosx/makePlist) and copy it 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`
Plug-ins
--------
GitBucket has the plug-in system to extend GitBucket from outside of GitBucket. Some plug-ins are available now:
- [gitbucket-gist-plugin](https://github.com/gitbucket/gitbucket-gist-plugin)
- [gitbucket-announce-plugin](https://github.com/gitbucket-plugins/gitbucket-announce-plugin)
- [gitbucket-h2-backup-plugin](https://github.com/gitbucket-plugins/gitbucket-h2-backup-plugin)
- [gitbucket-desktopnotify-plugin](https://github.com/yoshiyoshifujii/gitbucket-desktopnotify-plugin)
- [gitbucket-commitgraphs-plugin](https://github.com/yoshiyoshifujii/gitbucket-commitgraphs-plugin)
- [gitbucket-asciidoctor-plugin](https://github.com/lefou/gitbucket-asciidoctor-plugin)
You can find community plugins other than them at [gitbucket community plugins](http://gitbucket-plugins.github.io/).
Support
--------
- If you have any question about GitBucket, send it to [gitter room](https://gitter.im/gitbucket/gitbucket) before raise an issue.
- Make sure check whether there is a same question or request in the past.
- When raise a new issue, write subject in **English** at least.
- We can also support in Japaneses other than English at [gitter room for Japanese](https://gitter.im/gitbucket/gitbucket_ja).
- First priority of GitBucket is easy installation and reproduce GitHub behavior, so we might reject if your request is against it.
Release Notes
--------
### 3.10 - 30 Dec 2015
- Move to Bootstrap3
- New URL for raw contents (`raw/master/doc/activity.md` instead of `blob/master/doc/activity.md?raw=true`)
- Update xsbt-web-pligin
- Update H2 database
### 3.9 - 5 Dec 2015
- GFM inline breaks support in Markdown
- WebHook on create review comment is available
- WebHook event trigger is selectable
### 3.8 - 31 Oct 2015
- Moved to GitHub organization
- Omit diff view for large differences
- Repository creation API
- Render url as link in repository description
- Expand attachable file types
### 3.7 - 3 Oct 2015
- Markdown processor has been switched to [markedj](https://github.com/gitbucket/markedj) from pegdown
- Clone in desktop button
- Providing MD5 and SHA-1 checksum for `gitbucket.war` has started
### 3.6 - 30 Aug 2015
- User interface Improvements: Especially, commit list, issues and pull request have been updated largely.
- Installed plugins list has been available at the system administration console.
- Pages and repository list in the sidebar have been limited and more pages and repositories link is available.
- More reference link notation in Markdown has been supported.
### 3.5 - 1 Aug 2015
- Octicons has been applied
- Global header has been enhanced. Now it's further similar to GitHub.
- Default compare / pull request target has been changed to the parent repository
- A lot of updates for [gitbucket-gist-plugin](https://github.com/gitbucket/gitbucket-gist-plugin)
### 3.4 - 27 Jun 2015
- Declarative style plug-in definition
- New extension point to add markup render
- go-import support
### 3.3 - 31 May 2015
- Rich graphical diff for images
- File finder is available in the repository viewer
- Blame is displayed at the source viewer
- Remain user data and repositories even if user is disabled
- Mobile view improvement
### 3.2 - 3 May 2015
- Directory history button
- Compare / pull request button
- Limit of activity log
### 3.1.1 - 4 Apr 2015
- Rolled back H2 version to avoid version compatibility issue
- Plug-ins became possible to access ServletContext
### 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
@@ -259,3 +342,7 @@ Release Notes
### 1.0 - 04 Jul 2013
- This is a first public release
Sponsors
--------
[![IntelliJ IDEA](https://www.jetbrains.com/idea/docs/logo_intellij_idea.png)](https://www.jetbrains.com/idea/)

View File

@@ -44,7 +44,7 @@ GITBUCKET_WAR_DIR=$GITBUCKET_DIR/lib
GITBUCKET_WAR_FILE=$GITBUCKET_WAR_DIR/gitbucket.war
# GitBucket version to fetch when installing
GITBUCKET_VERSION=2.1
GITBUCKET_VERSION=3.5
#
# End of configuration section. Ignore this part

View File

@@ -38,7 +38,7 @@ 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 wget -qO "$GITBUCKET_WAR_FILE" https://github.com/gitbucket/gitbucket/releases/download/$GITBUCKET_VERSION/gitbucket.war
sudo rm -f "$GITBUCKET_LOG_DIR/run.log"

View File

@@ -3,7 +3,7 @@ Summary: GitHub clone written with Scala.
Version: 2.6
Release: 1%{?dist}
License: Apache
URL: https://github.com/takezoe/gitbucket
URL: https://github.com/gitbucket/gitbucket
Group: System/Servers
Source0: %{name}.war
Source1: %{name}.init

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

60
doc/authenticator.md Normal file
View File

@@ -0,0 +1,60 @@
Authentication in Controller
========
GitBucket provides many [authenticators](https://github.com/gitbucket/gitbucket/blob/master/src/main/scala/gitbucket/core/util/Authenticator.scala) to access controlling in the controller.
For example, in the case of `RepositoryViwerController`,
it references three authenticators: `ReadableUsersAuthenticator`, `ReferrerAuthenticator` and `CollaboratorsAuthenticator`.
```scala
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 with WebHookPullRequestReviewCommentService
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 with WebHookPullRequestReviewCommentService =>
...
```
Authenticators provides a method to add guard to actions in the controller:
- `ReadableUsersAuthenticator` provides `readableUsersOnly` method
- `ReferrerAuthenticator` provides `referrersOnly` method
- `CollaboratorsAuthenticator` provides `collaboratorsOnly` method
These methods are available in each actions as below:
```scala
// Allows only the repository owner (or manager for group repository) and administrators.
get("/:owner/:repository/tree/*")(referrersOnly { repository =>
...
})
// Allows only collaborators and administrators.
get("/:owner/:repository/new/*")(collaboratorsOnly { repository =>
...
})
// Allows only signed in users which can access the repository.
post("/:owner/:repository/commit/:id/comment/new", commentForm)(readableUsersOnly { (form, repository) =>
...
})
```
Currently, GitBucket provides below authenticators:
|Trait | Method | Description |
|--------------------------|-----------------|--------------------------------------------------------------------------------------|
|OneselfAuthenticator |oneselfOnly |Allows only oneself and administrators. |
|OwnerAuthenticator |ownerOnly |Allows only the repository owner and administrators. |
|UsersAuthenticator |usersOnly |Allows only signed in users. |
|AdminAuthenticator |adminOnly |Allows only administrators. |
|CollaboratorsAuthenticator|collaboratorsOnly|Allows only collaborators and administrators. |
|ReferrerAuthenticator |referrersOnly |Allows only the repository owner (or manager for group repository) and administrators.|
|ReadableUsersAuthenticator|readableUsersOnly|Allows only signed in users which can access the repository. |
|GroupManagerAuthenticator |managersOnly |Allows only the group managers. |
Of course, if you make a new plugin, you can define a your own authenticator according to requirement in your plugin.

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 [gitbucket.core.servlet.AutoUpdate](https://github.com/gitbucket/gitbucket/blob/master/src/main/scala/gitbucket/core/servlet/AutoUpdate.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/gitbucket/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)
)
```

56
doc/comment_action.md Normal file
View File

@@ -0,0 +1,56 @@
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.
And in the case of some actions, `CONTENT` column value contains additional information.
|ACTION |CONTENT |
|---------------|-----------------|
|comment |comment |
|close_comment |comment |
|reopen_comment |comment |
|close |"Close" |
|reopen |"Reopen" |
|commit |comment commitId |
|merge |comment |
|delete_branch |branchName |
|refer |issueId:title |
### 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
* /repositories
* /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>
```

33
doc/how_to_run.md Normal file
View File

@@ -0,0 +1,33 @@
How to run from the source tree
========
Run for Development
--------
If you want to test GitBucket, input following command at the root directory of the source tree.
```
$ sbt ~jetty:start
```
Then access to `http://localhost:8080/` by your browser. The default administrator account is `root` and password is `root`.
Source code modification is detected and reloaded automatically. You can modify logging configuration by editing `src/main/resources/logback-dev.xml`.
Build war file
--------
To build war file, run the following command:
```
$ sbt package
```
`gitbucket_2.11-x.x.x.war` is generated into `target/scala-2.11`.
To build executable war file, run
* Windows: Not available
* Linux: `./release/make-release-war.sh`
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.

View File

@@ -379,21 +379,21 @@
<path d="M588.909,926.673 L560.094,910.545 L564.713,935.73 L588.909,926.673 z" fill-opacity="0" stroke="#B3B3B3" stroke-width="1.313" stroke-linecap="round"/>
</g>
<g id="rect3075-11">
<path d="M779.562,898.094 C779.396,912.75 779.229,927.406 779.062,942.062 C781.26,942.084 783.458,942.104 785.656,942.125 C784.104,943.688 782.552,945.25 781,946.813 C801.708,967.521 822.417,988.229 843.125,1008.938 C858.531,993.531 873.938,978.125 889.344,962.719 C868.635,942 847.927,921.281 827.219,900.563 C825.49,902.302 823.76,904.042 822.031,905.781 C822.052,903.386 822.073,900.99 822.094,898.594 C807.917,898.427 793.74,898.261 779.563,898.094 z" fill="#FFFFFF"/>
<path d="M779.562,898.094 C779.396,912.75 779.229,927.406 779.062,942.062 C781.26,942.084 783.458,942.104 785.656,942.125 C784.104,943.688 782.552,945.25 781,946.813 C801.708,967.521 822.417,988.229 843.125,1008.938 C858.531,993.531 873.938,978.125 889.344,962.719 C868.635,942 847.927,921.281 827.219,900.563 C825.49,902.302 823.76,904.042 822.031,905.781 C822.052,903.386 822.073,900.99 822.094,898.594 C807.917,898.427 793.74,898.261 779.563,898.094 z" fill-opacity="0" stroke="#B3B3B3" stroke-width="9" stroke-linecap="round"/>
<path d="M1389.845,733.625 C1389.679,748.281 1389.512,762.937 1389.345,777.593 C1391.543,777.615 1393.741,777.635 1395.939,777.656 C1394.387,779.219 1392.835,780.781 1391.283,782.344 C1411.991,803.052 1432.7,823.76 1453.408,844.469 C1468.814,829.062 1484.221,813.656 1499.627,798.25 C1478.918,777.531 1458.21,756.812 1437.502,736.094 C1435.773,737.833 1434.043,739.573 1432.314,741.312 C1432.335,738.917 1432.356,736.521 1432.377,734.125 C1418.2,733.958 1404.023,733.792 1389.846,733.625 z" fill="#FFFFFF"/>
<path d="M1389.845,733.625 C1389.679,748.281 1389.512,762.937 1389.345,777.593 C1391.543,777.615 1393.741,777.635 1395.939,777.656 C1394.387,779.219 1392.835,780.781 1391.283,782.344 C1411.991,803.052 1432.7,823.76 1453.408,844.469 C1468.814,829.062 1484.221,813.656 1499.627,798.25 C1478.918,777.531 1458.21,756.812 1437.502,736.094 C1435.773,737.833 1434.043,739.573 1432.314,741.312 C1432.335,738.917 1432.356,736.521 1432.377,734.125 C1418.2,733.958 1404.023,733.792 1389.846,733.625 z" fill-opacity="0" stroke="#B3B3B3" stroke-width="9" stroke-linecap="round"/>
</g>
<path d="M606.483,964.91 L606.483,951.243 L672.089,951.243 L672.089,964.91 z" fill="#B3B3B3" id="rect2995-0-2-8-6"/>
<g id="rect3075-11-7">
<path d="M786.383,905.075 C786.256,916.229 786.129,927.383 786.003,938.537 C787.675,938.553 789.348,938.568 791.021,938.584 C789.839,939.773 788.658,940.963 787.477,942.152 C803.237,957.911 818.997,973.671 834.756,989.431 C846.481,977.706 858.206,965.982 869.93,954.257 C854.171,938.489 838.411,922.722 822.651,906.954 C821.335,908.278 820.019,909.602 818.703,910.926 C818.719,909.102 818.734,907.279 818.751,905.456 C807.961,905.329 797.172,905.202 786.383,905.075 z" fill="#FFFFFF"/>
<path d="M786.383,905.075 C786.256,916.229 786.129,927.383 786.003,938.537 C787.675,938.553 789.348,938.568 791.021,938.584 C789.839,939.773 788.658,940.963 787.477,942.152 C803.237,957.911 818.997,973.671 834.756,989.431 C846.481,977.706 858.206,965.982 869.93,954.257 C854.171,938.489 838.411,922.722 822.651,906.954 C821.335,908.278 820.019,909.602 818.703,910.926 C818.719,909.102 818.734,907.279 818.751,905.456 C807.961,905.329 797.172,905.202 786.383,905.075 z" fill-opacity="0" stroke="#FFFFFF" stroke-width="6.849" stroke-linecap="round"/>
<path d="M1396.666,740.606 C1396.539,751.76 1396.412,762.914 1396.286,774.068 C1397.958,774.084 1399.631,774.099 1401.304,774.115 C1400.122,775.304 1398.941,776.494 1397.76,777.683 C1413.52,793.442 1429.28,809.202 1445.039,824.962 C1456.764,813.237 1468.489,801.513 1480.213,789.788 C1464.454,774.02 1448.694,758.253 1432.934,742.485 C1431.618,743.809 1430.302,745.133 1428.986,746.457 C1429.002,744.633 1429.017,742.81 1429.034,740.987 C1418.244,740.86 1407.455,740.733 1396.666,740.606 z" fill="#FFFFFF"/>
<path d="M1396.666,740.606 C1396.539,751.76 1396.412,762.914 1396.286,774.068 C1397.958,774.084 1399.631,774.099 1401.304,774.115 C1400.122,775.304 1398.941,776.494 1397.76,777.683 C1413.52,793.442 1429.28,809.202 1445.039,824.962 C1456.764,813.237 1468.489,801.513 1480.213,789.788 C1464.454,774.02 1448.694,758.253 1432.934,742.485 C1431.618,743.809 1430.302,745.133 1428.986,746.457 C1429.002,744.633 1429.017,742.81 1429.034,740.987 C1418.244,740.86 1407.455,740.733 1396.666,740.606 z" fill-opacity="0" stroke="#FFFFFF" stroke-width="6.849" stroke-linecap="round"/>
</g>
<g id="path3100-2">
<path d="M813.748,916.688 C818.255,921.195 818.255,928.501 813.748,933.008 C809.242,937.514 801.935,937.514 797.429,933.008 C792.922,928.501 792.922,921.195 797.429,916.688 C801.935,912.182 809.242,912.182 813.748,916.688 z" fill="#FFFFFF"/>
<path d="M813.748,916.688 C818.255,921.195 818.255,928.501 813.748,933.008 C809.242,937.514 801.935,937.514 797.429,933.008 C792.922,928.501 792.922,921.195 797.429,916.688 C801.935,912.182 809.242,912.182 813.748,916.688 z" fill-opacity="0" stroke="#B3B3B3" stroke-width="7.585" stroke-linecap="round"/>
<path d="M1424.031,752.219 C1428.538,756.726 1428.538,764.032 1424.031,768.539 C1419.525,773.045 1412.218,773.045 1407.712,768.539 C1403.205,764.032 1403.205,756.726 1407.712,752.219 C1412.218,747.713 1419.525,747.713 1424.031,752.219 z" fill="#FFFFFF"/>
<path d="M1424.031,752.219 C1428.538,756.726 1428.538,764.032 1424.031,768.539 C1419.525,773.045 1412.218,773.045 1407.712,768.539 C1403.205,764.032 1403.205,756.726 1407.712,752.219 C1412.218,747.713 1419.525,747.713 1424.031,752.219 z" fill-opacity="0" stroke="#B3B3B3" stroke-width="7.585" stroke-linecap="round"/>
</g>
<g id="rect4114">
<path d="M813.845,955.107 L834.888,934.064 L864.012,963.188 L842.969,984.231 z" fill="#FFFFFF"/>
<path d="M813.845,955.107 L834.888,934.064 L864.012,963.188 L842.969,984.231 z" fill-opacity="0" stroke="#B3B3B3" stroke-width="7.133"/>
<path d="M1424.128,790.638 L1445.171,769.595 L1474.295,798.719 L1453.252,819.762 z" fill="#FFFFFF"/>
<path d="M1424.128,790.638 L1445.171,769.595 L1474.295,798.719 L1453.252,819.762 z" fill-opacity="0" stroke="#B3B3B3" stroke-width="7.133"/>
</g>
<g id="path2991-7-6">
<path d="M969.889,84.636 C969.889,122.652 939.072,153.469 901.056,153.469 C863.04,153.469 832.223,122.652 832.223,84.636 C832.223,46.62 863.04,15.803 901.056,15.803 C939.072,15.803 969.889,46.62 969.889,84.636 z" fill="#A0A0A0"/>
@@ -750,5 +750,45 @@
</g>
<path d="M1396.792,592.168 C1426.908,592.168 1450.613,610.989 1450.613,639.54" fill-opacity="0" stroke="#A0A0A0" stroke-width="20" stroke-linecap="round"/>
<path d="M1397.792,545.653 C1453.613,544.493 1499.627,588.735 1499.627,636.54" fill-opacity="0" stroke="#A0A0A0" stroke-width="20" stroke-linecap="round"/>
<path d="M871.125,1039.025 C871.125,1039.025 873.794,1016.889 908.043,1011.524 C919.748,1009.691 945.861,1005.107 945.861,978.522" fill-opacity="0" stroke="#A0A0A0" stroke-width="17.059" id="path3207"/>
<g id="rect3818-4-8-4">
<path d="M869.474,950.497 L871.997,950.497 L871.997,1031.308 L869.474,1031.308 z" fill="#A0A0A0"/>
<g>
<path d="M869.474,950.497 L871.997,950.497 L871.997,1031.308 L869.474,1031.308 z" fill="#A0A0A0"/>
<path d="M869.474,950.497 L871.997,950.497 L871.997,1031.308 L869.474,1031.308 z" fill-opacity="0" stroke="#A0A0A0" stroke-width="15"/>
</g>
</g>
<g id="path3795-4-8-7-8">
<path d="M888.074,1051.656 C888.074,1060.906 880.5,1068.405 871.159,1068.405 C861.817,1068.405 854.243,1060.906 854.243,1051.656 C854.243,1042.406 861.817,1034.908 871.159,1034.908 C880.5,1034.908 888.074,1042.406 888.074,1051.656 z" fill="#FFFFFF"/>
<path d="M888.074,1051.656 C888.074,1060.906 880.5,1068.405 871.159,1068.405 C861.817,1068.405 854.243,1060.906 854.243,1051.656 C854.243,1042.406 861.817,1034.908 871.159,1034.908 C880.5,1034.908 888.074,1042.406 888.074,1051.656 z" fill-opacity="0" stroke="#A0A0A0" stroke-width="7.989"/>
</g>
<g id="path3795-8-4-8">
<path d="M886.883,935.155 C886.883,944.404 879.31,951.903 869.968,951.903 C860.626,951.903 853.054,944.404 853.054,935.155 C853.054,925.904 860.626,918.405 869.968,918.405 C879.31,918.405 886.883,925.904 886.883,935.155 z" fill="#FFFFFF"/>
<path d="M886.883,935.155 C886.883,944.404 879.31,951.903 869.968,951.903 C860.626,951.903 853.054,944.404 853.054,935.155 C853.054,925.904 860.626,918.405 869.968,918.405 C879.31,918.405 886.883,925.904 886.883,935.155 z" fill-opacity="0" stroke="#A0A0A0" stroke-width="7.989"/>
</g>
<g id="path3795-8-4-8-2">
<path d="M965.046,971.602 C965.046,980.852 957.472,988.351 948.13,988.351 C938.789,988.351 931.215,980.852 931.215,971.602 C931.215,962.352 938.789,954.854 948.13,954.854 C957.472,954.854 965.046,962.352 965.046,971.602 z" fill="#FFFFFF"/>
<path d="M965.046,971.602 C965.046,980.852 957.472,988.351 948.13,988.351 C938.789,988.351 931.215,980.852 931.215,971.602 C931.215,962.352 938.789,954.854 948.13,954.854 C957.472,954.854 965.046,962.352 965.046,971.602 z" fill-opacity="0" stroke="#A0A0A0" stroke-width="7.989"/>
</g>
<path d="M1114.353,1042.412 C1114.353,1042.412 1117.022,1020.275 1151.271,1014.91 C1162.976,1013.077 1189.089,1008.493 1189.089,981.909" fill-opacity="0" stroke="#000000" stroke-width="17.059" id="path3207"/>
<g id="rect3818-4-8-4">
<path d="M1112.701,953.884 L1115.225,953.884 L1115.225,1034.695 L1112.701,1034.695 z" fill="#A0A0A0"/>
<g>
<path d="M1112.701,953.884 L1115.225,953.884 L1115.225,1034.695 L1112.701,1034.695 z" fill="#A0A0A0"/>
<path d="M1112.701,953.884 L1115.225,953.884 L1115.225,1034.695 L1112.701,1034.695 z" fill-opacity="0" stroke="#000000" stroke-width="15"/>
</g>
</g>
<g id="path3795-4-8-7-8">
<path d="M1131.302,1055.043 C1131.302,1064.293 1123.728,1071.791 1114.386,1071.791 C1105.045,1071.791 1097.471,1064.293 1097.471,1055.043 C1097.471,1045.792 1105.045,1038.294 1114.386,1038.294 C1123.728,1038.294 1131.302,1045.792 1131.302,1055.043 z" fill="#FFFFFF"/>
<path d="M1131.302,1055.043 C1131.302,1064.293 1123.728,1071.791 1114.386,1071.791 C1105.045,1071.791 1097.471,1064.293 1097.471,1055.043 C1097.471,1045.792 1105.045,1038.294 1114.386,1038.294 C1123.728,1038.294 1131.302,1045.792 1131.302,1055.043 z" fill-opacity="0" stroke="#000000" stroke-width="7.989"/>
</g>
<g id="path3795-8-4-8">
<path d="M1130.111,938.542 C1130.111,947.791 1122.537,955.29 1113.196,955.29 C1103.854,955.29 1096.282,947.791 1096.282,938.542 C1096.282,929.291 1103.854,921.792 1113.196,921.792 C1122.537,921.792 1130.111,929.291 1130.111,938.542 z" fill="#FFFFFF"/>
<path d="M1130.111,938.542 C1130.111,947.791 1122.537,955.29 1113.196,955.29 C1103.854,955.29 1096.282,947.791 1096.282,938.542 C1096.282,929.291 1103.854,921.792 1113.196,921.792 C1122.537,921.792 1130.111,929.291 1130.111,938.542 z" fill-opacity="0" stroke="#000000" stroke-width="7.989"/>
</g>
<g id="path3795-8-4-8-2">
<path d="M1208.274,974.989 C1208.274,984.239 1200.7,991.738 1191.358,991.738 C1182.016,991.738 1174.443,984.239 1174.443,974.989 C1174.443,965.739 1182.016,958.241 1191.358,958.241 C1200.7,958.241 1208.274,965.739 1208.274,974.989 z" fill="#FFFFFF"/>
<path d="M1208.274,974.989 C1208.274,984.239 1200.7,991.738 1191.358,991.738 C1182.016,991.738 1174.443,984.239 1174.443,974.989 C1174.443,965.739 1182.016,958.241 1191.358,958.241 C1200.7,958.241 1208.274,965.739 1208.274,974.989 z" fill-opacity="0" stroke="#000000" stroke-width="7.989"/>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 78 KiB

After

Width:  |  Height:  |  Size: 82 KiB

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.

11
doc/readme.md Normal file
View File

@@ -0,0 +1,11 @@
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](authenticator.md)
* [About Action in Issue Comment](comment_action.md)
* [Activity Types](activity.md)
* [Notification Email](notification.md)
* [Automatic Schema Updating](auto_update.md)
* [Release Operation](release.md)

54
doc/release.md Normal file
View File

@@ -0,0 +1,54 @@
Release Operation
========
Update version number
--------
Note to update version number in files below:
### project/build.scala
```scala
object MyBuild extends Build {
val Organization = "gitbucket"
val Name = "gitbucket"
val Version = "3.3.0" // <---- update version!!
val ScalaVersion = "2.11.6"
val ScalatraVersion = "2.3.1"
```
### src/main/scala/gitbucket/core/servlet/AutoUpdate.scala
```scala
object AutoUpdate {
/**
* The history of versions. A head of this sequence is the current BitBucket version.
*/
val versions = Seq(
new Version(3, 3), // <---- add this line!!
new Version(3, 2),
```
Generate release files
--------
Note: Release operation requires [Ant](http://ant.apache.org/) and [Maven](https://maven.apache.org/).
### Make release war file
Run `release/make-release-war.sh`. The release war file is generated into `target/scala-2.11/gitbucket.war`.
```bash
$ cd release
$ ./make-release-war.sh
```
### Deploy assembly jar file
For plug-in development, we have to publish the assembly jar file to the public Maven repository by `release/deploy-assembly-jar.sh`.
```bash
$ cd release/
$ ./deploy-assembly-jar.sh
```

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.

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

View File

@@ -1 +1 @@
sbt.version=0.13.5
sbt.version=0.13.8

View File

@@ -1,22 +1,34 @@
import sbt._
import Keys._
import org.scalatra.sbt._
import com.typesafe.sbteclipse.plugin.EclipsePlugin.EclipseKeys
import com.earldouglas.xwp.JettyPlugin
import play.twirl.sbt.SbtTwirl
import play.twirl.sbt.Import.TwirlKeys._
import sbt.Keys._
import sbt._
import sbtassembly.AssemblyKeys._
import sbtassembly._
import JettyPlugin.autoImport._
object MyBuild extends Build {
val Organization = "jp.sf.amateras"
val Organization = "gitbucket"
val Name = "gitbucket"
val Version = "0.0.1"
val ScalaVersion = "2.11.2"
val ScalatraVersion = "2.3.0"
val Version = "3.10.0"
val ScalaVersion = "2.11.6"
val ScalatraVersion = "2.3.1"
lazy val project = Project (
"gitbucket",
file(".")
)
.settings(ScalatraPlugin.scalatraWithJRebel: _*)
// .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,
@@ -25,35 +37,44 @@ object MyBuild extends Build {
scalaVersion := ScalaVersion,
resolvers ++= Seq(
Classpaths.typesafeReleases,
"amateras-repo" at "http://amateras.sourceforge.jp/mvn/"
"amateras-repo" at "http://amateras.sourceforge.jp/mvn/",
"amateras-snapshot-repo" at "http://amateras.sourceforge.jp/mvn-snapshot/"
),
scalacOptions := Seq("-deprecation", "-language:postfixOps"),
libraryDependencies ++= Seq(
"org.eclipse.jgit" % "org.eclipse.jgit.http.server" % "3.4.1.201406201815-r",
"org.eclipse.jgit" % "org.eclipse.jgit.archive" % "3.4.1.201406201815-r",
"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.10",
"jp.sf.amateras" %% "scalatra-forms" % "0.1.0",
"org.json4s" %% "json4s-jackson" % "3.2.11",
"jp.sf.amateras" %% "scalatra-forms" % "0.2.0",
"commons-io" % "commons-io" % "2.4",
"org.pegdown" % "pegdown" % "1.4.1",
"org.apache.commons" % "commons-compress" % "1.5",
"org.apache.commons" % "commons-email" % "1.3.1",
"org.apache.httpcomponents" % "httpclient" % "4.3",
"org.apache.sshd" % "apache-sshd" % "0.11.0",
"io.github.gitbucket" % "markedj" % "1.0.6-SNAPSHOT",
"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" % "1.0.0",
"org.apache.tika" % "tika-core" % "1.10",
"com.typesafe.slick" %% "slick" % "2.1.0",
"com.novell.ldap" % "jldap" % "2009-10-07",
"com.h2database" % "h2" % "1.4.180",
"ch.qos.logback" % "logback-classic" % "1.0.13" % "runtime",
"org.eclipse.jetty" % "jetty-webapp" % "8.1.8.v20121106" % "container;provided",
"org.eclipse.jetty.orbit" % "javax.servlet" % "3.0.0.v201112011016" % "container;provided;test" artifacts Artifact("javax.servlet", "jar", "jar"),
"junit" % "junit" % "4.11" % "test",
"com.typesafe.play" %% "twirl-compiler" % "1.0.2"
"com.h2database" % "h2" % "1.4.190",
"ch.qos.logback" % "logback-classic" % "1.1.1",
"org.eclipse.jetty" % "jetty-webapp" % "8.1.16.v20140903" % "provided",
"javax.servlet" % "javax.servlet-api" % "3.0.1" % "provided",
"junit" % "junit" % "4.12" % "test",
"com.mchange" % "c3p0" % "0.9.5.2",
"com.typesafe" % "config" % "1.2.1",
"com.typesafe.akka" %% "akka-actor" % "2.3.10",
"com.enragedginger" %% "akka-quartz-scheduler" % "1.3.0-akka-2.3.x" exclude("c3p0","c3p0")
),
EclipseKeys.withSource := true,
play.twirl.sbt.Import.TwirlKeys.templateImports += "gitbucket.core._",
javacOptions in compile ++= Seq("-target", "7", "-source", "7"),
javaOptions in Jetty += "-Dlogback.configurationFile=/logback-dev.xml",
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)
).enablePlugins(SbtTwirl, JettyPlugin)
}

View File

@@ -1,9 +1,5 @@
addSbtPlugin("com.typesafe.sbteclipse" % "sbteclipse-plugin" % "2.4.0")
scalacOptions ++= Seq("-unchecked", "-deprecation", "-feature")
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.2")
addSbtPlugin("com.timushev.sbt" % "sbt-updates" % "0.1.4")
addSbtPlugin("com.typesafe.sbt" % "sbt-twirl" % "1.0.4")
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.12.0")
addSbtPlugin("com.earldouglas" % "xsbt-web-plugin" % "2.1.0")

View File

@@ -1,12 +1,13 @@
<?xml version="1.0" encoding="UTF-8" ?>
<project name="gitbucket" default="all" basedir=".">
<project name="gitbucket" default="all" basedir="..">
<property environment="env"/>
<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="0.0.1"/>
<property name="jetty.version" value="8.1.8.v20121106"/>
<property name="gitbucket.version" value="${env.GITBUCKET_VERSION}"/>
<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">
@@ -54,7 +55,12 @@
tofile="${target.dir}/scala-${scala.version}/gitbucket.war"/>
</target>
<target name="all" depends="rename">
<target name="checksum" depends="rename">
<checksum file="${target.dir}/scala-${scala.version}/gitbucket.war" algorithm="MD5" format="MD5SUM" forceOverwrite="yes" fileext=".md5"/>
<checksum file="${target.dir}/scala-${scala.version}/gitbucket.war" algorithm="SHA" format="MD5SUM" forceOverwrite="yes" fileext=".sha1"/>
</target>
<target name="all" depends="checksum">
</target>

15
release/deploy-assembly-jar.sh Executable file
View File

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

3
release/env.sh Normal file
View File

@@ -0,0 +1,3 @@
#!/bin/sh
export GITBUCKET_VERSION=`cat ../project/build.scala | grep 'val Version' | cut -d \" -f 2`
echo "GITBUCKET_VERSION: $GITBUCKET_VERSION"

15
release/make-release-war.sh Executable file
View File

@@ -0,0 +1,15 @@
#!/bin/sh
D="$(dirname "$0")"
D="$(cd "${D}"; pwd)"
DD="$(dirname "${D}")"
(
for f in "${D}/env.sh" "${D}/build.xml"; do
if [ ! -s "${f}" ]; then
echo >&2 "$0: Unable to access file '${f}'"
exit 1
fi
done
. "${D}/env.sh"
cd "${DD}"
ant -f "${D}/build.xml" all
)

17
release/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>

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 -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.5.jar" %*
java %JAVA_OPTS% -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" %*

2
sbt.sh
View File

@@ -1,2 +1,2 @@
#!/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.5.jar "$@"
java $JAVA_OPTS -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

@@ -1,4 +1,4 @@
package util;
package gitbucket.core.util;
import org.eclipse.jgit.api.errors.PatchApplyException;
import org.eclipse.jgit.diff.RawText;

View File

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

View File

@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<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>
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<file>gitbucket.log</file>
<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" />
-->
<logger name="scala.slick.jdbc.JdbcBackend.statement" level="DEBUG" />
</configuration>

View File

@@ -6,12 +6,21 @@
</encoder>
</appender>
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<file>gitbucket.log</file>
<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" />
<logger name="scala.slick.jdbc.JdbcBackend.statement" level="DEBUG" />
-->
</configuration>

View File

@@ -0,0 +1,5 @@
c3p0 {
privilegeSpawnedThreads=true
contextClassLoaderSource=library
}

View File

@@ -128,7 +128,7 @@ INSERT INTO ACCOUNT (
'root@localhost',
'dc76e9f0c0006e8f919e0c515c66dbba3982f785',
true,
'https://github.com/takezoe/gitbucket',
'https://github.com/gitbucket/gitbucket',
SYSDATE,
SYSDATE,
NULL

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

@@ -0,0 +1,55 @@
DROP TABLE IF EXISTS WEB_HOOK_EVENT;
CREATE TABLE WEB_HOOK_EVENT(
USER_NAME VARCHAR(100) NOT NULL,
REPOSITORY_NAME VARCHAR(100) NOT NULL,
URL VARCHAR(200) NOT NULL,
EVENT VARCHAR(30) NOT NULL
);
ALTER TABLE WEB_HOOK_EVENT ADD CONSTRAINT IDX_WEB_HOOK_EVENT_PK PRIMARY KEY (USER_NAME, REPOSITORY_NAME, URL, EVENT);
ALTER TABLE WEB_HOOK_EVENT ADD CONSTRAINT IDX_WEB_HOOK_EVENT_FK0 FOREIGN KEY (USER_NAME, REPOSITORY_NAME, URL) REFERENCES WEB_HOOK (USER_NAME, REPOSITORY_NAME, URL)
ON DELETE CASCADE ON UPDATE CASCADE;
CREATE TEMPORARY TABLE TMP_EVENTS (EVENT VARCHAR(30));
INSERT INTO TMP_EVENTS VALUES ('push'),('issue_comment'),('issues'),('pull_request');
INSERT INTO WEB_HOOK_EVENT (USER_NAME, REPOSITORY_NAME, URL, EVENT)
SELECT USER_NAME, REPOSITORY_NAME, URL, EVENT
FROM WEB_HOOK, TMP_EVENTS;
DROP TABLE TMP_EVENTS;
ALTER TABLE COMMIT_COMMENT ADD COLUMN ISSUE_ID INT;
CREATE OR REPLACE VIEW ISSUE_OUTLINE_VIEW AS
SELECT
A.USER_NAME,
A.REPOSITORY_NAME,
A.ISSUE_ID,
NVL(B.COMMENT_COUNT, 0) + NVL(C.COMMENT_COUNT, 0) AS COMMENT_COUNT
FROM ISSUE A
LEFT OUTER JOIN (
SELECT USER_NAME, REPOSITORY_NAME, ISSUE_ID, COUNT(COMMENT_ID) AS COMMENT_COUNT FROM ISSUE_COMMENT
WHERE ACTION IN ('comment', 'close_comment', 'reopen_comment')
GROUP BY USER_NAME, REPOSITORY_NAME, ISSUE_ID
) B
ON (A.USER_NAME = B.USER_NAME AND A.REPOSITORY_NAME = B.REPOSITORY_NAME AND A.ISSUE_ID = B.ISSUE_ID)
LEFT OUTER JOIN (
SELECT USER_NAME, REPOSITORY_NAME, ISSUE_ID, COUNT(COMMENT_ID) AS COMMENT_COUNT FROM COMMIT_COMMENT
GROUP BY USER_NAME, REPOSITORY_NAME, ISSUE_ID
) C
ON (A.USER_NAME = C.USER_NAME AND A.REPOSITORY_NAME = C.REPOSITORY_NAME AND A.ISSUE_ID = C.ISSUE_ID);
UPDATE COMMIT_COMMENT C SET (ISSUE_ID) = (
SELECT MAX(P.ISSUE_ID)
FROM PULL_REQUEST P
WHERE
C.USER_NAME = P.USER_NAME AND
C.REPOSITORY_NAME = P.REPOSITORY_NAME AND
C.COMMIT_ID = P.COMMIT_ID_TO
);
ALTER TABLE COMMIT_COMMENT DROP COLUMN PULL_REQUEST;

View File

@@ -1,9 +1,14 @@
import _root_.servlet.{BasicAuthenticationFilter, TransactionFilter}
import app._
//import jp.sf.amateras.scalatra.forms.ValidationJavaScriptProvider
import org.scalatra._
import javax.servlet._
import gitbucket.core.controller._
import gitbucket.core.plugin.PluginRegistry
import gitbucket.core.servlet.{AccessTokenAuthenticationFilter, BasicAuthenticationFilter, Database, 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) {
@@ -12,15 +17,22 @@ class ScalatraBootstrap extends LifeCycle {
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 DashboardController, "/*")
context.mount(new UserManagementController, "/*")
context.mount(new SystemSettingsController, "/*")
context.mount(new PluginsController, "/*")
context.mount(new AccountController, "/*")
context.mount(new RepositoryViewerController, "/*")
context.mount(new WikiController, "/*")
@@ -31,9 +43,13 @@ class ScalatraBootstrap extends LifeCycle {
context.mount(new RepositorySettingsController, "/*")
// Create GITBUCKET_HOME directory if it does not exist
val dir = new java.io.File(_root_.util.Directory.GitBucketHome)
val dir = new java.io.File(Directory.GitBucketHome)
if(!dir.exists){
dir.mkdirs()
}
}
}
override def destroy(context: ServletContext): Unit = {
Database.closeDataSource()
}
}

View File

@@ -1,483 +0,0 @@
package app
import util._
import util.Directory._
import util.Implicits._
import util.ControlUtil._
import service._
import org.eclipse.jgit.api.Git
import jp.sf.amateras.scalatra.forms._
import org.eclipse.jgit.transport.RefSpec
import scala.collection.JavaConverters._
import org.eclipse.jgit.lib.{ObjectId, CommitBuilder, PersonIdent}
import service.IssuesService._
import service.PullRequestService._
import org.slf4j.LoggerFactory
import org.eclipse.jgit.merge.MergeStrategy
import org.eclipse.jgit.errors.NoMergeBaseException
import service.WebHookService.WebHookPayload
import util.JGitUtil.DiffInfo
import util.JGitUtil.CommitInfo
class PullRequestsController extends PullRequestsControllerBase
with RepositoryService with AccountService with IssuesService with PullRequestService with MilestonesService with LabelsService
with CommitsService with ActivityService with WebHookService with ReferrerAuthenticator with CollaboratorsAuthenticator
trait PullRequestsControllerBase extends ControllerBase {
self: RepositoryService with AccountService with IssuesService with MilestonesService with LabelsService
with CommitsService with ActivityService with PullRequestService with WebHookService with ReferrerAuthenticator with CollaboratorsAuthenticator =>
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)
}
})
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)
pulls.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
})
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) =>
pulls.html.mergeguide(
checkConflictInPullRequest(owner, name, pullreq.branch, pullreq.requestUserName, name, pullreq.requestBranch, issueId),
pullreq,
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
val mergeBaseRefName = s"refs/heads/${pullreq.branch}"
val merger = MergeStrategy.RECURSIVE.newMerger(git.getRepository, true)
val mergeBaseTip = git.getRepository.resolve(mergeBaseRefName)
val mergeTip = git.getRepository.resolve(s"refs/pull/${issueId}/head")
val conflicted = try {
!merger.merge(mergeBaseTip, mergeTip)
} catch {
case e: NoMergeBaseException => true
}
if (conflicted) {
throw new RuntimeException("This pull request can't merge automatically.")
}
// creates merge commit
val mergeCommit = new CommitBuilder()
mergeCommit.setTreeId(merger.getResultTreeId)
mergeCommit.setParentIds(Array[ObjectId](mergeBaseTip, mergeTip): _*)
val personIdent = new PersonIdent(loginAccount.fullName, loginAccount.mailAddress)
mergeCommit.setAuthor(personIdent)
mergeCommit.setCommitter(personIdent)
mergeCommit.setMessage(s"Merge pull request #${issueId} from ${pullreq.requestUserName}/${pullreq.requestBranch}\n\n" +
form.message)
// insertObject and got mergeCommit Object Id
val inserter = git.getRepository.newObjectInserter
val mergeCommitId = inserter.insert(mergeCommit)
inserter.flush()
inserter.release()
// update refs
val refUpdate = git.getRepository.updateRef(mergeBaseRefName)
refUpdate.setNewObjectId(mergeCommitId)
refUpdate.setForceUpdate(false)
refUpdate.setRefLogIdent(personIdent)
refUpdate.setRefLogMessage("merged", true)
refUpdate.update()
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
getWebHookURLs(owner, name) match {
case webHookURLs if(webHookURLs.nonEmpty) =>
for(ownerAccount <- getAccountByUserName(owner)){
callWebHook(owner, name, webHookURLs,
WebHookPayload(git, loginAccount, mergeBaseRefName, repository, commits.flatten.toList, ownerAccount))
}
case _ =>
}
// 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, 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 forkedId = JGitUtil.getForkedCommitId(oldGit, newGit,
originRepository.owner, originRepository.name, originBranch,
forkedRepository.owner, forkedRepository.name, forkedBranch)
val oldId = oldGit.getRepository.resolve(forkedId)
val newId = newGit.getRepository.resolve(forkedBranch)
val (commits, diffs) = getRequestCompareInfo(
originRepository.owner, originRepository.name, oldId.getName,
forkedRepository.owner, forkedRepository.name, newId.getName)
pulls.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,
originBranch,
forkedBranch,
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
pulls.html.mergecheck(
checkConflict(originRepository.owner, originRepository.name, originBranch,
forkedRepository.owner, forkedRepository.name, forkedBranch))
}
}) 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
using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git =>
git.fetch
.setRemote(getRepositoryDir(form.requestUserName, form.requestRepositoryName).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"${context.baseUrl}/${repository.owner}/${repository.name}/pull/${issueId}")
}
redirect(s"/${repository.owner}/${repository.name}/pull/${issueId}")
})
/**
* 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 = {
LockUtil.lock(s"${userName}/${repositoryName}"){
using(Git.open(getRepositoryDir(requestUserName, requestRepositoryName))) { git =>
val remoteRefName = s"refs/heads/${branch}"
val tmpRefName = s"refs/merge-check/${userName}/${branch}"
val refSpec = new RefSpec(s"${remoteRefName}:${tmpRefName}").setForceUpdate(true)
try {
// fetch objects from origin repository branch
git.fetch
.setRemote(getRepositoryDir(userName, repositoryName).toURI.toString)
.setRefSpecs(refSpec)
.call
// merge conflict check
val merger = MergeStrategy.RECURSIVE.newMerger(git.getRepository, true)
val mergeBaseTip = git.getRepository.resolve(s"refs/heads/${requestBranch}")
val mergeTip = git.getRepository.resolve(tmpRefName)
try {
!merger.merge(mergeBaseTip, mergeTip)
} catch {
case e: NoMergeBaseException => true
}
} finally {
val refUpdate = git.getRepository.updateRef(refSpec.getDestination)
refUpdate.setForceUpdate(true)
refUpdate.delete()
}
}
}
}
/**
* Checks whether conflict will be caused in merging within pull request. Returns true if conflict will be caused.
*/
private def checkConflictInPullRequest(userName: String, repositoryName: String, branch: String,
requestUserName: String, requestRepositoryName: String, requestBranch: String,
issueId: Int): Boolean = {
LockUtil.lock(s"${userName}/${repositoryName}") {
using(Git.open(getRepositoryDir(userName, repositoryName))) { git =>
// merge
val merger = MergeStrategy.RECURSIVE.newMerger(git.getRepository, true)
val mergeBaseTip = git.getRepository.resolve(s"refs/heads/${branch}")
val mergeTip = git.getRepository.resolve(s"refs/pull/${issueId}/head")
try {
!merger.merge(mergeBaseTip, mergeTip)
} catch {
case e: NoMergeBaseException => true
}
}
}
}
/**
* 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) =>
view.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())
)
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,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,29 @@
package gitbucket.core.api
import gitbucket.core.model.IssueComment
import gitbucket.core.util.RepositoryName
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)(repositoryName: RepositoryName, issueId: Int, isPullRequest: Boolean){
val html_url = ApiPath(s"/${repositoryName.fullName}/${if(isPullRequest){ "pull" }else{ "issues" }}/${issueId}#comment-${id}")
}
object ApiComment{
def apply(comment: IssueComment, repositoryName: RepositoryName, issueId: Int, user: ApiUser, isPullRequest: Boolean): ApiComment =
ApiComment(
id = comment.commentId,
user = user,
body = comment.content,
created_at = comment.registeredDate,
updated_at = comment.updatedDate)(repositoryName, issueId, isPullRequest)
}

View File

@@ -0,0 +1,57 @@
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, urlIsHtmlUrl: Boolean) extends FieldSerializable{
val url = if(urlIsHtmlUrl){
ApiPath(s"/${repositoryName.fullName}/commit/${id}")
}else{
ApiPath(s"/api/v3/${repositoryName.fullName}/commits/${id}")
}
val html_url = if(urlIsHtmlUrl){
None
}else{
Some(ApiPath(s"/${repositoryName.fullName}/commit/${id}"))
}
}
object ApiCommit{
def apply(git: Git, repositoryName: RepositoryName, commit: CommitInfo, urlIsHtmlUrl: Boolean = false): 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, urlIsHtmlUrl)
}
def forPushPayload(git: Git, repositoryName: RepositoryName, commit: CommitInfo): ApiCommit = apply(git, repositoryName, commit, true)
}

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,35 @@
package gitbucket.core.api
import gitbucket.core.model.Issue
import gitbucket.core.util.RepositoryName
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)(repositoryName: RepositoryName, isPullRequest: Boolean){
val comments_url = ApiPath(s"/api/v3/repos/${repositoryName.fullName}/issues/${number}/comments")
val html_url = ApiPath(s"/${repositoryName.fullName}/${if(isPullRequest){ "pull" }else{ "issues" }}/${number}")
}
object ApiIssue{
def apply(issue: Issue, repositoryName: RepositoryName, 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)(repositoryName, issue.isPullRequest)
}

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,61 @@
package gitbucket.core.api
import gitbucket.core.util.RepositoryName
import gitbucket.core.model.CommitComment
import java.util.Date
/**
* https://developer.github.com/v3/activity/events/types/#pullrequestreviewcommentevent
*/
case class ApiPullRequestReviewComment(
id: Int, // 29724692
// "diff_hunk": "@@ -1 +1 @@\n-# public-repo",
path: String, // "README.md",
// "position": 1,
// "original_position": 1,
commit_id: String, // "0d1a26e67d8f5eaf1f6ba5c57fc3c7d91ac0fd1c",
// "original_commit_id": "0d1a26e67d8f5eaf1f6ba5c57fc3c7d91ac0fd1c",
user: ApiUser,
body: String, // "Maybe you should use more emojji on this line.",
created_at: Date, // "2015-05-05T23:40:27Z",
updated_at: Date // "2015-05-05T23:40:27Z",
)(repositoryName:RepositoryName, issueId: Int) extends FieldSerializable {
// "url": "https://api.github.com/repos/baxterthehacker/public-repo/pulls/comments/29724692",
val url = ApiPath(s"/api/v3/repos/${repositoryName.fullName}/pulls/comments/${id}")
// "html_url": "https://github.com/baxterthehacker/public-repo/pull/1#discussion_r29724692",
val html_url = ApiPath(s"/${repositoryName.fullName}/pull/${issueId}#discussion_r${id}")
// "pull_request_url": "https://api.github.com/repos/baxterthehacker/public-repo/pulls/1",
val pull_request_url = ApiPath(s"/api/v3/repos/${repositoryName.fullName}/pulls/${issueId}")
/*
"_links": {
"self": {
"href": "https://api.github.com/repos/baxterthehacker/public-repo/pulls/comments/29724692"
},
"html": {
"href": "https://github.com/baxterthehacker/public-repo/pull/1#discussion_r29724692"
},
"pull_request": {
"href": "https://api.github.com/repos/baxterthehacker/public-repo/pulls/1"
}
}
*/
val _links = Map(
"self" -> Map("href" -> url),
"html" -> Map("href" -> html_url),
"pull_request" -> Map("href" -> pull_request_url))
}
object ApiPullRequestReviewComment{
def apply(comment: CommitComment, commentedUser: ApiUser, repositoryName: RepositoryName, issueId: Int): ApiPullRequestReviewComment =
new ApiPullRequestReviewComment(
id = comment.commentId,
path = comment.fileName.getOrElse(""),
commit_id = comment.commitId,
user = commentedUser,
body = comment.content,
created_at = comment.registeredDate,
updated_at = comment.updatedDate
)(repositoryName, issueId)
}

View File

@@ -0,0 +1,11 @@
package gitbucket.core.api
import gitbucket.core.model.Account
case class ApiPusher(name: String, email: String)
object ApiPusher {
def apply(user: Account): ApiPusher = ApiPusher(
name = user.userName,
email = user.mailAddress)
}

View File

@@ -0,0 +1,56 @@
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)(urlIsHtmlUrl: Boolean) {
val forks_count = forks
val watchers_count = watchers
val url = if(urlIsHtmlUrl){
ApiPath(s"/${full_name}")
}else{
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,
urlIsHtmlUrl: Boolean = false): 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
)(urlIsHtmlUrl)
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))
def forPushPayload(repositoryInfo: RepositoryInfo, owner: ApiUser): ApiRepository =
ApiRepository(repositoryInfo.repository, owner, forkedCount=repositoryInfo.forkedCount, urlIsHtmlUrl=true)
}

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.userName,
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,19 @@
package gitbucket.core.api
/**
* https://developer.github.com/v3/repos/#create
* api form
*/
case class CreateARepository(
name: String,
description: Option[String],
`private`: Boolean = false,
auto_init: Boolean = false
) {
def isValid: Boolean = {
name.length<=40 &&
name.matches("[a-zA-Z0-9\\-\\+_.]+") &&
!name.startsWith("_") &&
!name.startsWith("-")
}
}

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,4 @@
package gitbucket.core.api
/** export fields for json */
trait FieldSerializable

View File

@@ -0,0 +1,52 @@
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[FieldSerializable]() +
FieldSerializer[ApiCombinedCommitStatus]() +
FieldSerializer[ApiPullRequest.Commit]() +
FieldSerializer[ApiIssue]() +
FieldSerializer[ApiComment]()
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

@@ -1,27 +1,35 @@
package app
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 service._
import util._
import util.StringUtil._
import util.Directory._
import util.ControlUtil._
import util.Implicits._
import ssh.SshUtil
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.{FileMode, Constants}
import org.eclipse.jgit.dircache.DirCache
import model.GroupMember
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 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])
@@ -31,6 +39,8 @@ trait AccountControllerBase extends AccountManagementControllerBase {
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)))),
@@ -54,6 +64,10 @@ trait AccountControllerBase extends AccountManagementControllerBase {
"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)
@@ -76,8 +90,8 @@ trait AccountControllerBase extends AccountManagementControllerBase {
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))),
"owner" -> trim(label("Owner" , text(required, maxlength(100), identifier, existsAccount))),
"name" -> trim(label("Repository name", text(required, maxlength(100), repository, uniqueRepository))),
"description" -> trim(label("Description" , optional(text()))),
"isPrivate" -> trim(label("Repository Type", boolean())),
"createReadme" -> trim(label("Create README" , boolean()))
@@ -103,21 +117,21 @@ trait AccountControllerBase extends AccountManagementControllerBase {
params.getOrElse("tab", "repositories") match {
// Public Activity
case "activity" =>
_root_.account.html.activity(account,
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)
_root_.account.html.members(account, members.map(_.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)
_root_.account.html.repositories(account,
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 }))
@@ -142,10 +156,29 @@ trait AccountControllerBase extends AccountManagementControllerBase {
}
}
/**
* 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 =>
account.html.edit(x, flash.get("info"))
html.edit(x, flash.get("info"))
} getOrElse NotFound
})
@@ -169,16 +202,17 @@ trait AccountControllerBase extends AccountManagementControllerBase {
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)
// // 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)
removeUserRelatedData(userName)
updateAccount(account.copy(isRemoved = true))
}
@@ -189,7 +223,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
get("/:userName/_ssh")(oneselfOnly {
val userName = params("userName")
getAccountByUserName(userName).map { x =>
account.html.ssh(x, getPublicKeys(x.userName))
html.ssh(x, getPublicKeys(x.userName))
} getOrElse NotFound
})
@@ -206,12 +240,46 @@ trait AccountControllerBase extends AccountManagementControllerBase {
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 {
account.html.register()
html.register()
}
} else NotFound
}
@@ -225,7 +293,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
}
get("/groups/new")(usersOnly {
account.html.group(None, List(GroupMember("", context.loginAccount.get.userName, true)))
html.group(None, List(GroupMember("", context.loginAccount.get.userName, true)))
})
post("/groups/new", newGroupForm)(usersOnly { form =>
@@ -241,7 +309,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
get("/:groupName/_editgroup")(managersOnly {
defining(params("groupName")){ groupName =>
account.html.group(getAccountByUserName(groupName, true), getGroupMembers(groupName))
html.group(getAccountByUserName(groupName, true), getGroupMembers(groupName))
}
})
@@ -290,7 +358,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
* Show the new repository form.
*/
get("/new")(usersOnly {
account.html.newrepo(getGroupsByUserName(context.loginAccount.get.userName), context.settings.isCreateRepoOptionPublic)
html.newrepo(getGroupsByUserName(context.loginAccount.get.userName), context.settings.isCreateRepoOptionPublic)
})
/**
@@ -299,56 +367,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
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)
createRepository(form.owner, form.name, form.description, form.isPrivate, form.createReadme)
}
// redirect to the repository
@@ -356,6 +375,54 @@ trait AccountControllerBase extends AccountManagementControllerBase {
}
})
/**
* Create user repository
* https://developer.github.com/v3/repos/#create
*/
post("/api/v3/user/repos")(usersOnly {
val owner = context.loginAccount.get.userName
(for {
data <- extractFromJsonBody[CreateARepository] if data.isValid
} yield {
LockUtil.lock(s"${owner}/${data.name}") {
if(getRepository(owner, data.name, context.baseUrl).isEmpty){
createRepository(owner, data.name, data.description, data.`private`, data.auto_init)
val repository = getRepository(owner, data.name, context.baseUrl).get
JsonFormat(ApiRepository(repository, ApiUser(getAccountByUserName(owner).get)))
} else {
ApiError(
"A repository with this name already exists on this account",
Some("https://developer.github.com/v3/repos/#create")
)
}
}
}) getOrElse NotFound
})
/**
* Create group repository
* https://developer.github.com/v3/repos/#create
*/
post("/api/v3/orgs/:org/repos")(managersOnly {
val groupName = params("org")
(for {
data <- extractFromJsonBody[CreateARepository] if data.isValid
} yield {
LockUtil.lock(s"${groupName}/${data.name}") {
if(getRepository(groupName, data.name, context.baseUrl).isEmpty){
createRepository(groupName, data.name, data.description, data.`private`, data.auto_init)
val repository = getRepository(groupName, data.name, context.baseUrl).get
JsonFormat(ApiRepository(repository, ApiUser(getAccountByUserName(groupName).get)))
} else {
ApiError(
"A repository with this name already exists for this group",
Some("https://developer.github.com/v3/repos/#create")
)
}
}
}) getOrElse NotFound
})
get("/:owner/:repository/fork")(readableUsersOnly { repository =>
val loginAccount = context.loginAccount.get
val loginUserName = loginAccount.userName
@@ -366,7 +433,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
val members = getGroupMembers(group)
context.loginAccount.exists(x => members.exists { member => member.userName == x.userName && member.isManager })
}
_root_.helper.html.forkrepository(
helper.html.forkrepository(
repository,
(groups zip managerPermissions).toMap
)
@@ -400,6 +467,14 @@ trait AccountControllerBase extends AccountManagementControllerBase {
parentUserName = Some(repository.owner)
)
// Add collaborators for group repository
val ownerAccount = getAccountByUserName(accountName).get
if(ownerAccount.isGroupAccount){
getGroupMembers(accountName).foreach { member =>
addCollaborator(accountName, repository.name, member.userName)
}
}
// Insert default labels
insertDefaultLabels(accountName, repository.name)
@@ -421,6 +496,59 @@ trait AccountControllerBase extends AccountManagementControllerBase {
}
})
private def createRepository(owner: String, name: String, description: Option[String], isPrivate: Boolean, createReadme: Boolean) {
val ownerAccount = getAccountByUserName(owner).get
val loginAccount = context.loginAccount.get
val loginUserName = loginAccount.userName
// Insert to the database at first
createRepository(name, owner, description, isPrivate)
// Add collaborators for group repository
if(ownerAccount.isGroupAccount){
getGroupMembers(owner).foreach { member =>
addCollaborator(owner, name, member.userName)
}
}
// Insert default labels
insertDefaultLabels(owner, name)
// Create the actual repository
val gitdir = getRepositoryDir(owner, name)
JGitUtil.initRepository(gitdir)
if(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(description.nonEmpty){
name + "\n" +
"===============\n" +
"\n" +
description.get
} else {
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, owner, name)
// Record activity
recordCreateRepositoryActivity(owner, name, loginUserName)
}
private def insertDefaultLabels(userName: String, repositoryName: String): Unit = {
createLabel(userName, repositoryName, "bug", "fc2929")
createLabel(userName, repositoryName, "duplicate", "cccccc")

View File

@@ -1,4 +1,4 @@
package app
package gitbucket.core.controller
class AnonymousAccessController extends AnonymousAccessControllerBase

View File

@@ -1,19 +1,25 @@
package app
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 _root_.util.Directory._
import _root_.util.Implicits._
import _root_.util.ControlUtil._
import _root_.util.{StringUtil, FileUtil, Validations, Keys}
import org.scalatra._
import org.scalatra.json._
import org.json4s._
import jp.sf.amateras.scalatra.forms._
import org.apache.commons.io.FileUtils
import model._
import service.{SystemSettingsService, AccountService}
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 org.scalatra.i18n._
import scala.util.Try
/**
* Provides generic features for controller implementations.
@@ -51,6 +57,9 @@ abstract class ControllerBase extends ScalatraFilter
// 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)
}
@@ -74,7 +83,7 @@ abstract class ControllerBase extends ScalatraFilter
}
}
private def LoginAccount: Option[Account] = session.getAs[Account](Keys.Session.LoginAccount)
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){
@@ -103,13 +112,19 @@ abstract class ControllerBase extends ScalatraFilter
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(html.error("Not Found"))
org.scalatra.NotFound(gitbucket.core.html.error("Not Found"))
}
protected def Unauthorized()(implicit context: app.Context) =
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("/"))
@@ -146,6 +161,15 @@ abstract class ControllerBase extends ScalatraFilter
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)
}
}
/**
@@ -157,6 +181,13 @@ case class Context(settings: SystemSettingsService.SystemSettings, loginAccount:
val currentPath = request.getRequestURI.substring(request.getContextPath.length)
val baseUrl = settings.baseUrl(request)
val host = new java.net.URL(baseUrl).getHost
val platform = request.getHeader("User-Agent") match {
case null => null
case agent if agent.contains("Mac") => "mac"
case agent if agent.contains("Linux") => "linux"
case agent if agent.contains("Win") => "windows"
case _ => null
}
/**
* Get object from cache.

View File

@@ -1,9 +1,10 @@
package app
package gitbucket.core.controller
import service._
import util.{StringUtil, UsersAuthenticator, Keys}
import util.Implicits._
import service.IssuesService.IssueSearchCondition
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
@@ -96,7 +97,7 @@ trait DashboardControllerBase extends ControllerBase {
val userRepos = getUserRepositories(userName, context.baseUrl, true).map(repo => repo.owner -> repo.name)
val page = IssueSearchCondition.page(request)
dashboard.html.issues(
html.issues(
searchIssue(condition, false, (page - 1) * IssueLimit, IssueLimit, userRepos: _*),
page,
countIssue(condition.copy(state = "open" ), false, userRepos: _*),
@@ -119,7 +120,7 @@ trait DashboardControllerBase extends ControllerBase {
val allRepos = getAllRepositories(userName)
val page = IssueSearchCondition.page(request)
dashboard.html.pulls(
html.pulls(
searchIssue(condition, true, (page - 1) * PullRequestLimit, PullRequestLimit, allRepos: _*),
page,
countIssue(condition.copy(state = "open" ), true, allRepos: _*),

View File

@@ -1,8 +1,8 @@
package app
package gitbucket.core.controller
import util.{Keys, FileUtil}
import util.ControlUtil._
import util.Directory._
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
@@ -17,22 +17,22 @@ class FileUploadController extends ScalatraServlet with FileUploadSupport {
configureMultipartHandling(MultipartConfig(maxFileSize = Some(3 * 1024 * 1024)))
post("/image"){
execute { (file, fileId) =>
execute({ (file, fileId) =>
FileUtils.writeByteArrayToFile(new java.io.File(getTemporaryDir(session.getId), fileId), file.get)
session += Keys.Session.Upload(fileId) -> file.name
}
}, FileUtil.isImage)
}
post("/image/:owner/:repository"){
execute { (file, fileId) =>
post("/file/:owner/:repository"){
execute({ (file, fileId) =>
FileUtils.writeByteArrayToFile(new java.io.File(
getAttachedDir(params("owner"), params("repository")),
fileId + "." + FileUtil.getExtension(file.getName)), file.get)
}
}, FileUtil.isUploadableType)
}
private def execute(f: (FileItem, String) => Unit) = fileParams.get("file") match {
case Some(file) if(FileUtil.isImage(file.name)) =>
private def execute(f: (FileItem, String) => Unit, mimeTypeChcker: (String) => Boolean) = fileParams.get("file") match {
case Some(file) if(mimeTypeChcker(file.name)) =>
defining(FileUtil.generateFileId){ fileId =>
f(file, fileId)

View File

@@ -1,13 +1,20 @@
package app
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 util._
import util.Implicits._
import service._
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 =>
@@ -61,13 +68,13 @@ trait IndexControllerBase extends ControllerBase {
get("/activities.atom"){
contentType = "application/atom+xml; type=feed"
helper.xml.feed(getRecentActivities())
xml.feed(getRecentActivities())
}
/**
* Set account information into HttpSession and redirect.
*/
private def signin(account: model.Account) = {
private def signin(account: Account) = {
session.setAttribute(Keys.Session.LoginAccount, account)
updateLastLoginDate(account.userName)
@@ -92,15 +99,24 @@ trait IndexControllerBase extends ControllerBase {
get("/_user/proposals")(usersOnly {
contentType = formats("json")
org.json4s.jackson.Serialization.write(
Map("options" -> getAllUsers().filter(!_.isGroupAccount).map(_.userName).toArray)
Map("options" -> getAllUsers(false).filter(!_.isGroupAccount).map(_.userName).toArray)
)
})
/**
* JSON APU for checking user existence.
* JSON API 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

@@ -1,22 +1,27 @@
package app
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 service._
import IssuesService._
import util._
import util.Implicits._
import util.ControlUtil._
import org.scalatra.Ok
import model.Issue
class IssuesController extends IssuesControllerBase
with IssuesService with RepositoryService with AccountService with LabelsService with MilestonesService with ActivityService
with ReadableUsersAuthenticator with ReferrerAuthenticator with CollaboratorsAuthenticator
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 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])
@@ -60,7 +65,7 @@ trait IssuesControllerBase extends ControllerBase {
get("/:owner/:repository/issues/:id")(referrersOnly { repository =>
defining(repository.owner, repository.name, params("id")){ case (owner, name, issueId) =>
getIssue(owner, name, issueId) map {
issues.html.issue(
html.issue(
_,
getComments(owner, name, issueId.toInt),
getIssueLabels(owner, name, issueId.toInt),
@@ -73,9 +78,21 @@ trait IssuesControllerBase extends ControllerBase {
}
})
/**
* 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, issue) => ApiComment(issueComment, RepositoryName(repository), issueId, ApiUser(user), issue.isPullRequest) })
}).getOrElse(NotFound)
})
get("/:owner/:repository/issues/new")(readableUsersOnly { repository =>
defining(repository.owner, repository.name){ case (owner, name) =>
issues.html.create(
html.create(
(getCollaborators(owner, name) ::: (if(getAccountByUserName(owner).get.isGroupAccount) Nil else List(owner))).sorted,
getMilestones(owner, name),
getLabels(owner, name),
@@ -109,14 +126,17 @@ trait IssuesControllerBase extends ControllerBase {
// record activity
recordCreateIssueActivity(owner, name, userName, issueId, form.title)
// extract references and create refer comment
getIssue(owner, name, issueId.toString).foreach { issue =>
// extract references and create refer comment
createReferComment(owner, name, issue, form.title + " " + form.content.getOrElse(""))
}
// notifications
Notifier().toNotify(repository, issueId, form.content.getOrElse("")){
Notifier.msgIssue(s"${context.baseUrl}/${owner}/${name}/issues/${issueId}")
// call web hooks
callIssuesWebHook("opened", repository, issue, context.baseUrl, context.loginAccount.get)
// notifications
Notifier().toNotify(repository, issue, form.content.getOrElse("")){
Notifier.msgIssue(s"${context.baseUrl}/${owner}/${name}/issues/${issueId}")
}
}
redirect(s"/${owner}/${name}/issues/${issueId}")
@@ -160,6 +180,20 @@ trait IssuesControllerBase extends ControllerBase {
} 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, RepositoryName(repository), issueId, ApiUser(context.loginAccount.get), issue.isPullRequest))
}) 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}/${
@@ -192,15 +226,25 @@ trait IssuesControllerBase extends ControllerBase {
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(
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" -> view.Markdown.toHtml(x.content getOrElse "No description given.",
repository, false, true, true, isEditable(x.userName, x.repositoryName, x.openedUserName))
))
Map(
"title" -> x.title,
"content" -> Markdown.toHtml(
markdown = x.content getOrElse "No description given.",
repository = repository,
enableWikiLink = false,
enableRefsLink = true,
enableAnchor = true,
enableLineBreaks = true,
enableTaskList = true,
hasWritePermission = isEditable(x.userName, x.repositoryName, x.openedUserName)
)
)
)
}
} else Unauthorized
} getOrElse NotFound
@@ -210,30 +254,46 @@ trait IssuesControllerBase extends ControllerBase {
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(
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))
))
Map(
"content" -> view.Markdown.toHtml(
markdown = x.content,
repository = repository,
enableWikiLink = false,
enableRefsLink = true,
enableAnchor = true,
enableLineBreaks = true,
enableTaskList = true,
hasWritePermission = isEditable(x.userName, x.repositoryName, x.commentedUserName)
)
)
)
}
} else Unauthorized
} getOrElse NotFound
})
ajaxPost("/:owner/:repository/issues/new/label")(collaboratorsOnly { repository =>
val labelNames = params("labelNames").split(",")
val labels = getLabels(repository.owner, repository.name).filter(x => labelNames.contains(x.labelName))
html.labellist(labels)
})
ajaxPost("/:owner/:repository/issues/:id/label/new")(collaboratorsOnly { repository =>
defining(params("id").toInt){ issueId =>
registerIssueLabel(repository.owner, repository.name, issueId, params("labelId").toInt)
issues.html.labellist(getIssueLabels(repository.owner, repository.name, issueId))
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)
issues.html.labellist(getIssueLabels(repository.owner, repository.name, issueId))
html.labellist(getIssueLabels(repository.owner, repository.name, issueId))
}
})
@@ -247,7 +307,7 @@ trait IssuesControllerBase extends ControllerBase {
milestoneId("milestoneId").map { milestoneId =>
getMilestonesWithIssueCount(repository.owner, repository.name)
.find(_._1.milestoneId == milestoneId).map { case (_, openCount, closeCount) =>
issues.milestones.html.progress(openCount + closeCount, closeCount)
gitbucket.core.issues.milestones.html.progress(openCount + closeCount, closeCount)
} getOrElse NotFound
} getOrElse Ok()
})
@@ -292,6 +352,7 @@ trait IssuesControllerBase extends ControllerBase {
(Directory.getAttachedDir(repository.owner, repository.name) match {
case dir if(dir.exists && dir.isDirectory) =>
dir.listFiles.find(_.getName.startsWith(params("file") + ".")).map { file =>
response.setHeader("Content-Disposition", f"""inline; filename=${file.getName}""")
RawData(FileUtil.getMimeType(file.getName), file)
}
case _ => None
@@ -301,7 +362,7 @@ trait IssuesControllerBase extends ControllerBase {
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: app.Context): Boolean =
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) = {
@@ -312,11 +373,15 @@ trait IssuesControllerBase extends ControllerBase {
}
}
// TODO Same method exists in PullRequestController. Should it moved to IssueService?
private def createReferComment(owner: String, repository: String, fromIssue: Issue, message: String) = {
StringUtil.extractIssueId(message).foreach { issueId =>
val content = fromIssue.issueId + ":" + fromIssue.title
if(getIssue(owner, repository, issueId).isDefined){
createComment(owner, repository, context.loginAccount.get.userName, issueId.toInt,
fromIssue.issueId + ":" + fromIssue.title, "refer")
// Not add if refer comment already exist.
if(!getComments(owner, repository, issueId.toInt).exists { x => x.action == "refer" && x.content == content }) {
createComment(owner, repository, context.loginAccount.get.userName, issueId.toInt, content, "refer")
}
}
}
}
@@ -325,13 +390,13 @@ trait IssuesControllerBase extends ControllerBase {
* @see [[https://github.com/takezoe/gitbucket/wiki/CommentAction]]
*/
private def handleComment(issueId: Int, content: Option[String], repository: RepositoryService.RepositoryInfo)
(getAction: model.Issue => Option[String] =
(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) map { issue =>
getIssue(owner, name, issueId.toString) flatMap { issue =>
val (action, recordActivity) =
getAction(issue)
.collect {
@@ -346,11 +411,10 @@ trait IssuesControllerBase extends ControllerBase {
}
.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)
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
@@ -365,23 +429,39 @@ trait IssuesControllerBase extends ControllerBase {
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, _){
f.toNotify(repository, issue, _){
Notifier.msgComment(s"${context.baseUrl}/${owner}/${name}/${
if(issue.isPullRequest) "pull" else "issues"}/${issueId}#comment-${commentId}")
if(issue.isPullRequest) "pull" else "issues"}/${issueId}#comment-${commentId.get}")
}
}
action foreach {
f.toNotify(repository, issueId, _){
f.toNotify(repository, issue, _){
Notifier.msgStatus(s"${context.baseUrl}/${owner}/${name}/issues/${issueId}")
}
}
}
issue -> commentId
commentId.map( issue -> _ )
}
}
}
@@ -395,7 +475,7 @@ trait IssuesControllerBase extends ControllerBase {
val condition = session.putAndGet(sessionKey,
if(request.hasQueryString){
val q = request.getParameter("q")
if(q == null){
if(q == null || q.trim.isEmpty){
IssueSearchCondition(request)
} else {
IssueSearchCondition(q, getMilestones(owner, repoName).map(x => (x.title, x.milestoneId)).toMap)
@@ -403,11 +483,15 @@ trait IssuesControllerBase extends ControllerBase {
} else session.getAs[IssueSearchCondition](sessionKey).getOrElse(IssueSearchCondition())
)
issues.html.list(
html.list(
"issues",
searchIssue(condition, false, (page - 1) * IssueLimit, IssueLimit, owner -> repoName),
page,
(getCollaborators(owner, repoName) :+ owner).sorted,
if(!getAccountByUserName(owner).exists(_.isGroupAccount)){
(getCollaborators(owner, repoName) :+ owner).sorted
} else {
getCollaborators(owner, repoName)
},
getMilestones(owner, repoName),
getLabels(owner, repoName),
countIssue(condition.copy(state = "open" ), false, owner -> repoName),
@@ -417,5 +501,4 @@ trait IssuesControllerBase extends ControllerBase {
hasWritePermission(owner, repoName, context.loginAccount))
}
}
}

View File

@@ -1,9 +1,10 @@
package app
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 service._
import util.{ReferrerAuthenticator, CollaboratorsAuthenticator}
import util.Implicits._
import org.scalatra.i18n.Messages
import org.scalatra.Ok
@@ -23,7 +24,7 @@ trait LabelsControllerBase extends ControllerBase {
)(LabelForm.apply)
get("/:owner/:repository/issues/labels")(referrersOnly { repository =>
issues.labels.html.list(
html.list(
getLabels(repository.owner, repository.name),
countIssueGroupByLabels(repository.owner, repository.name, IssuesService.IssueSearchCondition(), Map.empty),
repository,
@@ -31,12 +32,12 @@ trait LabelsControllerBase extends ControllerBase {
})
ajaxGet("/:owner/:repository/issues/labels/new")(collaboratorsOnly { repository =>
issues.labels.html.edit(None, 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))
issues.labels.html.label(
html.label(
getLabel(repository.owner, repository.name, labelId).get,
// TODO futility
countIssueGroupByLabels(repository.owner, repository.name, IssuesService.IssueSearchCondition(), Map.empty),
@@ -46,13 +47,13 @@ trait LabelsControllerBase extends ControllerBase {
ajaxGet("/:owner/:repository/issues/labels/:labelId/edit")(collaboratorsOnly { repository =>
getLabel(repository.owner, repository.name, params("labelId").toInt).map { label =>
issues.labels.html.edit(Some(label), repository)
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))
issues.labels.html.label(
html.label(
getLabel(repository.owner, repository.name, params("labelId").toInt).get,
// TODO futility
countIssueGroupByLabels(repository.owner, repository.name, IssuesService.IssueSearchCondition(), Map.empty),

View File

@@ -1,11 +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}
import util.Implicits._
class MilestonesController extends MilestonesControllerBase
with MilestonesService with RepositoryService with AccountService
with ReferrerAuthenticator with CollaboratorsAuthenticator
@@ -23,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,
@@ -31,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) =>
@@ -41,7 +41,7 @@ trait MilestonesControllerBase extends ControllerBase {
get("/:owner/:repository/issues/milestones/:milestoneId/edit")(collaboratorsOnly { repository =>
params("milestoneId").toIntOpt.map{ milestoneId =>
issues.milestones.html.edit(getMilestone(repository.owner, repository.name, milestoneId), repository)
html.edit(getMilestone(repository.owner, repository.name, milestoneId), repository)
} getOrElse NotFound
})

View File

@@ -0,0 +1,11 @@
package gitbucket.core.controller
import gitbucket.core.admin.plugins.html
import gitbucket.core.plugin.PluginRegistry
import gitbucket.core.util.AdminAuthenticator
class PluginsController extends ControllerBase with AdminAuthenticator {
get("/admin/plugins")(adminOnly {
html.plugins(PluginRegistry().getPlugins())
})
}

View File

@@ -0,0 +1,531 @@
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))),
"assignedUserName" -> trim(optional(text())),
"milestoneId" -> trim(optional(number())),
"labelNames" -> trim(optional(text()))
)(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,
assignedUserName: Option[String],
milestoneId: Option[Int],
labelNames: Option[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.openedUserName), Set())
baseOwner <- users.get(repository.owner)
headOwner <- users.get(pullRequest.requestUserName)
issueUser <- users.get(issue.openedUserName)
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,
getRepository(pullreq.requestUserName, pullreq.requestRepositoryName, context.baseUrl).get)
}
} 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)
}
updatePullRequests(owner, name, pullreq.branch)
// call web hook
callPullRequestWebHook("closed", repository, issueId, context.baseUrl, context.loginAccount.get)
// notifications
Notifier().toNotify(repository, issue, "merge"){
Notifier.msgStatus(s"${context.baseUrl}/${owner}/${name}/pull/${issueId}")
}
redirect(s"/${owner}/${name}/pull/${issueId}")
}
}
}
} getOrElse NotFound
})
get("/:owner/:repository/compare")(referrersOnly { forkedRepository =>
val headBranch:Option[String] = params.get("head")
(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 newBranch = headBranch.getOrElse(JGitUtil.getDefaultBranch(newGit, forkedRepository).get._2)
val oldBranch = originRepository.branchList.find( _ == newBranch).getOrElse(JGitUtil.getDefaultBranch(oldGit, originRepository).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}...${headBranch.getOrElse(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) {
// Self repository
Some(forkedRepository.name)
} else if(forkedRepository.repository.originUserName.isEmpty){
// when ForkedRepository is the original repository
getForkedRepositories(forkedRepository.owner, forkedRepository.name).find(_._1 == originOwner).map(_._2)
} else if(Some(originOwner) == forkedRepository.repository.originUserName){
// Original repository
forkedRepository.repository.originRepositoryName
} else {
// Sibling repository
getUserRepositories(originOwner, context.baseUrl).find { x =>
x.repository.originUserName == forkedRepository.repository.originUserName &&
x.repository.originRepositoryName == forkedRepository.repository.originRepositoryName
}.map(_.repository.repositoryName)
};
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)
(Option(oldGit.getRepository.resolve(rootId)), Option(newGit.getRepository.resolve(forkedId)))
} else {
// Commit id
(Option(oldGit.getRepository.resolve(originId)), Option(newGit.getRepository.resolve(forkedId)))
}
(oldId, newId) match {
case (Some(oldId), Some(newId)) => {
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(originRepository.owner, originRepository.name, context.loginAccount),
(getCollaborators(originRepository.owner, originRepository.name) ::: (if(getAccountByUserName(originRepository.owner).get.isGroupAccount) Nil else List(originRepository.owner))).sorted,
getMilestones(originRepository.owner, originRepository.name),
getLabels(originRepository.owner, originRepository.name)
)
}
case (oldId, newId) =>
redirect(s"/${forkedRepository.owner}/${forkedRepository.name}/compare/" +
s"${originOwner}:${oldId.map(_ => originId).getOrElse(originRepository.repository.defaultBranch)}..." +
s"${forkedOwner}:${newId.map(_ => forkedId).getOrElse(forkedRepository.repository.defaultBranch)}")
}
}
}) 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) =>
defining(repository.owner, repository.name){ case (owner, name) =>
val writable = hasWritePermission(owner, name, context.loginAccount)
val loginUserName = context.loginAccount.get.userName
val issueId = createIssue(
owner = repository.owner,
repository = repository.name,
loginUser = loginUserName,
title = form.title,
content = form.content,
assignedUserName = if(writable) form.assignedUserName else None,
milestoneId = if(writable) form.milestoneId else 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)
// insert labels
if(writable){
form.labelNames.map { value =>
val labels = getLabels(owner, name)
value.split(",").foreach { labelName =>
labels.find(_.labelName == labelName).map { label =>
registerIssueLabel(repository.owner, repository.name, issueId, label.labelId)
}
}
}
}
// fetch requested branch
fetchAsPullRequest(owner, name, form.requestUserName, form.requestRepositoryName, form.requestBranch, issueId)
// record activity
recordPullRequestActivity(owner, name, loginUserName, issueId, form.title)
// call web hook
callPullRequestWebHook("opened", repository, issueId, context.baseUrl, context.loginAccount.get)
getIssue(owner, name, issueId.toString) foreach { issue =>
// extract references and create refer comment
createReferComment(owner, name, issue, form.title + " " + form.content.getOrElse(""))
// notifications
Notifier().toNotify(repository, issue, form.content.getOrElse("")){
Notifier.msgPullRequest(s"${context.baseUrl}/${owner}/${name}/pull/${issueId}")
}
}
redirect(s"/${owner}/${name}/pull/${issueId}")
}
})
// TODO Same method exists in IssueController. Should it moved to IssueService?
private def createReferComment(owner: String, repository: String, fromIssue: Issue, message: String) = {
StringUtil.extractIssueId(message).foreach { issueId =>
val content = fromIssue.issueId + ":" + fromIssue.title
if(getIssue(owner, repository, issueId).isDefined){
// Not add if refer comment already exist.
if(!getComments(owner, repository, issueId.toInt).exists { x => x.action == "refer" && x.content == content }) {
createComment(owner, repository, context.loginAccount.get.userName, issueId.toInt, content, "refer")
}
}
}
}
/**
* 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,
if(!getAccountByUserName(owner).exists(_.isGroupAccount)){
(getCollaborators(owner, repoName) :+ owner).sorted
} else {
getCollaborators(owner, repoName)
},
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

@@ -1,17 +1,21 @@
package app
package gitbucket.core.controller
import service._
import util.Directory._
import util.Implicits._
import util.{LockUtil, UsersAuthenticator, OwnerAuthenticator}
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 service.WebHookService.WebHookPayload
import util.JGitUtil.CommitInfo
import util.ControlUtil._
import org.eclipse.jgit.api.Git
import org.eclipse.jgit.lib.Constants
import org.eclipse.jgit.lib.ObjectId
class RepositorySettingsController extends RepositorySettingsControllerBase
with RepositoryService with AccountService with WebHookService
@@ -25,7 +29,7 @@ trait RepositorySettingsControllerBase extends ControllerBase {
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))),
"repositoryName" -> trim(label("Repository Name", 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()))
@@ -39,10 +43,11 @@ trait RepositorySettingsControllerBase extends ControllerBase {
)(CollaboratorForm.apply)
// for web hook url addition
case class WebHookForm(url: String)
case class WebHookForm(url: String, events: Set[WebHook.Event])
val webHookForm = mapping(
"url" -> trim(label("url", text(required, webHook)))
def webHookForm(update:Boolean) = mapping(
"url" -> trim(label("url", text(required, webHook(update)))),
"events" -> webhookEvents
)(WebHookForm.apply)
// for transfer ownership
@@ -63,7 +68,7 @@ trait RepositorySettingsControllerBase extends ControllerBase {
* Display the Options page.
*/
get("/:owner/:repository/settings/options")(ownerOnly {
settings.html.options(_, flash.get("info"))
html.options(_, flash.get("info"))
})
/**
@@ -105,7 +110,7 @@ trait RepositorySettingsControllerBase extends ControllerBase {
* Display the Collaborators page.
*/
get("/:owner/:repository/settings/collaborators")(ownerOnly { repository =>
settings.html.collaborators(
html.collaborators(
getCollaborators(repository.owner, repository.name),
getAccountByUserName(repository.owner).get.isGroupAccount,
repository)
@@ -135,14 +140,23 @@ trait RepositorySettingsControllerBase extends ControllerBase {
* Display the web hook page.
*/
get("/:owner/:repository/settings/hooks")(ownerOnly { repository =>
settings.html.hooks(getWebHookURLs(repository.owner, repository.name), flash.get("url"), repository, flash.get("info"))
html.hooks(getWebHooks(repository.owner, repository.name), repository, flash.get("info"))
})
/**
* Display the web hook edit page.
*/
get("/:owner/:repository/settings/hooks/new")(ownerOnly { repository =>
val webhook = WebHook(repository.owner, repository.name, "")
html.edithooks(webhook, Set(WebHook.Push), repository, flash.get("info"), true)
})
/**
* Add the web hook URL.
*/
post("/:owner/:repository/settings/hooks/add", webHookForm)(ownerOnly { (form, repository) =>
addWebHookURL(repository.owner, repository.name, form.url)
post("/:owner/:repository/settings/hooks/new", webHookForm(false))(ownerOnly { (form, repository) =>
addWebHook(repository.owner, repository.name, form.url, form.events)
flash += "info" -> s"Webhook ${form.url} created"
redirect(s"/${repository.owner}/${repository.name}/settings/hooks")
})
@@ -150,30 +164,87 @@ trait RepositorySettingsControllerBase extends ControllerBase {
* Delete the web hook URL.
*/
get("/:owner/:repository/settings/hooks/delete")(ownerOnly { repository =>
deleteWebHookURL(repository.owner, repository.name, params("url"))
deleteWebHook(repository.owner, repository.name, params("url"))
flash += "info" -> s"Webhook ${params("url")} deleted"
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) =>
ajaxPost("/:owner/:repository/settings/hooks/test")(ownerOnly { repository =>
def _headers(h: Array[org.apache.http.Header]): Array[Array[String]] = h.map { h => Array(h.getName, h.getValue) }
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(_))
import scala.concurrent.duration._
import scala.concurrent._
import scala.util.control.NonFatal
import org.apache.http.util.EntityUtils
import scala.concurrent.ExecutionContext.Implicits.global
getAccountByUserName(repository.owner).foreach { ownerAccount =>
callWebHook(repository.owner, repository.name,
List(model.WebHook(repository.owner, repository.name, form.url)),
WebHookPayload(git, ownerAccount, "refs/heads/" + repository.repository.defaultBranch, repository, commits.toList, ownerAccount)
val url = params("url")
val dummyWebHookInfo = WebHook(repository.owner, repository.name, url)
val dummyPayload = {
val ownerAccount = getAccountByUserName(repository.owner).get
val commits = if(repository.commitCount == 0) List.empty else git.log
.add(git.getRepository.resolve(repository.repository.defaultBranch))
.setMaxCount(4)
.call.iterator.asScala.map(new CommitInfo(_)).toList
val pushedCommit = commits.drop(1)
WebHookPushPayload(
git = git,
sender = ownerAccount,
refName = "refs/heads/" + repository.repository.defaultBranch,
repositoryInfo = repository,
commits = pushedCommit,
repositoryOwner = ownerAccount,
oldId = commits.lastOption.map(_.id).map(ObjectId.fromString).getOrElse(ObjectId.zeroId()),
newId = commits.headOption.map(_.id).map(ObjectId.fromString).getOrElse(ObjectId.zeroId())
)
}
flash += "url" -> form.url
flash += "info" -> "Test payload deployed!"
val (webHook, json, reqFuture, resFuture) = callWebHook(WebHook.Push, List(dummyWebHookInfo), dummyPayload).head
val toErrorMap: PartialFunction[Throwable, Map[String,String]] = {
case e: java.net.UnknownHostException => Map("error"-> ("Unknown host " + e.getMessage))
case e: java.lang.IllegalArgumentException => Map("error"-> ("invalid url"))
case e: org.apache.http.client.ClientProtocolException => Map("error"-> ("invalid url"))
case NonFatal(e) => Map("error"-> (e.getClass + " "+ e.getMessage))
}
contentType = formats("json")
org.json4s.jackson.Serialization.write(Map(
"url" -> url,
"request" -> Await.result(reqFuture.map(req => Map(
"headers" -> _headers(req.getAllHeaders),
"payload" -> json
)).recover(toErrorMap), 20 seconds),
"responce" -> Await.result(resFuture.map(res => Map(
"status" -> res.getStatusLine(),
"body" -> EntityUtils.toString(res.getEntity()),
"headers" -> _headers(res.getAllHeaders())
)).recover(toErrorMap), 20 seconds)
))
}
})
/**
* Display the web hook edit page.
*/
get("/:owner/:repository/settings/hooks/edit")(ownerOnly { repository =>
getWebHook(repository.owner, repository.name, params("url")).map{ case (webhook, events) =>
html.edithooks(webhook, events, repository, flash.get("info"), false)
} getOrElse NotFound
})
/**
* Update web hook settings.
*/
post("/:owner/:repository/settings/hooks/edit", webHookForm(true))(ownerOnly { (form, repository) =>
updateWebHook(repository.owner, repository.name, form.url, form.events)
flash += "info" -> s"webhook ${form.url} updated"
redirect(s"/${repository.owner}/${repository.name}/settings/hooks")
})
@@ -181,7 +252,7 @@ trait RepositorySettingsControllerBase extends ControllerBase {
* Display the danger zone.
*/
get("/:owner/:repository/settings/danger")(ownerOnly {
settings.html.danger(_)
html.danger(_)
})
/**
@@ -223,9 +294,30 @@ trait RepositorySettingsControllerBase extends ControllerBase {
/**
* Provides duplication check for web hook url.
*/
private def webHook: Constraint = new Constraint(){
private def webHook(needExists: Boolean): 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.")
if(getWebHook(params("owner"), params("repository"), value).isDefined != needExists){
Some(if(needExists){
"URL had not been registered yet."
} else {
"URL had been registered already."
})
} else {
None
}
}
private def webhookEvents = new ValueType[Set[WebHook.Event]]{
def convert(name: String, params: Map[String, String], messages: Messages): Set[WebHook.Event] = {
WebHook.Event.values.flatMap { t =>
params.get(name + "." + t.name).map(_ => t)
}.toSet
}
def validate(name: String, params: Map[String, String], messages: Messages): Seq[(String, String)] = if(convert(name,params,messages).isEmpty){
Seq(name -> messages("error.required").format(name))
} else {
Nil
}
}
/**
@@ -271,4 +363,4 @@ trait RepositorySettingsControllerBase extends ControllerBase {
}
}
}
}
}

View File

@@ -1,35 +1,48 @@
package app
package gitbucket.core.controller
import _root_.util.JGitUtil.CommitInfo
import util.Directory._
import util.Implicits._
import _root_.util.ControlUtil._
import _root_.util._
import service._
import org.scalatra._
import java.io.File
import javax.servlet.http.{HttpServletResponse, HttpServletRequest}
import gitbucket.core.api._
import gitbucket.core.plugin.PluginRegistry
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, WebHook}
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.lib._
import org.apache.commons.io.FileUtils
import org.eclipse.jgit.treewalk._
import jp.sf.amateras.scalatra.forms._
import org.eclipse.jgit.dircache.DirCache
import org.eclipse.jgit.errors.MissingObjectException
import org.eclipse.jgit.lib._
import org.eclipse.jgit.revwalk.RevCommit
import service.WebHookService.WebHookPayload
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 ReadableUsersAuthenticator with ReferrerAuthenticator with CollaboratorsAuthenticator with PullRequestService with CommitStatusService
with WebHookPullRequestService with WebHookPullRequestReviewCommentService
/**
* 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 ReadableUsersAuthenticator with ReferrerAuthenticator with CollaboratorsAuthenticator with PullRequestService with CommitStatusService
with WebHookPullRequestService with WebHookPullRequestReviewCommentService =>
ArchiveCommand.registerFormat("zip", new ZipFormat)
ArchiveCommand.registerFormat("tar.gz", new TgzFormat)
@@ -91,11 +104,15 @@ trait RepositoryViewerControllerBase extends ControllerBase {
*/
post("/:owner/:repository/_preview")(referrersOnly { repository =>
contentType = "text/html"
view.helpers.markdown(params("content"), repository,
params("enableWikiLink").toBoolean,
params("enableRefsLink").toBoolean,
params("enableTaskList").toBoolean,
hasWritePermission(repository.owner, repository.name, context.loginAccount))
helpers.markdown(
markdown = params("content"),
repository = repository,
enableWikiLink = params("enableWikiLink").toBoolean,
enableRefsLink = params("enableRefsLink").toBoolean,
enableLineBreaks = params("enableLineBreaks").toBoolean,
enableTaskList = params("enableTaskList").toBoolean,
hasWritePermission = hasWritePermission(repository.owner, repository.name, context.loginAccount)
)
})
/**
@@ -105,6 +122,13 @@ trait RepositoryViewerControllerBase extends ControllerBase {
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.
*/
@@ -127,7 +151,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
using(Git.open(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,
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))
@@ -136,9 +160,68 @@ trait RepositoryViewerControllerBase extends ControllerBase {
}
})
/**
* 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.
*/
val listStatusesRoute = 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/#list-statuses-for-a-specific-ref
*
* legacy route
*/
get("/api/v3/repos/:owner/:repo/statuses/:ref"){
listStatusesRoute.action()
}
/**
* 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)
repo.html.editor(branch, repository, if(path.length == 0) Nil else path.split("/").toList,
html.editor(branch, repository, if(path.length == 0) Nil else path.split("/").toList,
None, JGitUtil.ContentInfo("text", None, Some("UTF-8")))
})
@@ -150,7 +233,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
getPathObjectId(git, path, revCommit).map { objectId =>
val paths = path.split("/")
repo.html.editor(branch, repository, paths.take(paths.size - 1).toList, Some(paths.last),
html.editor(branch, repository, paths.take(paths.size - 1).toList, Some(paths.last),
JGitUtil.getContentInfo(git, path, objectId))
} getOrElse NotFound
}
@@ -163,33 +246,47 @@ trait RepositoryViewerControllerBase extends ControllerBase {
getPathObjectId(git, path, revCommit).map { objectId =>
val paths = path.split("/")
repo.html.delete(branch, repository, paths.take(paths.size - 1).toList, paths.last,
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, form.branch, form.path, Some(form.newFileName), None,
StringUtil.convertLineSeparator(form.content, form.lineSeparator), form.charset,
form.message.getOrElse(s"Create ${form.newFileName}"))
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}"
if(form.path.length == 0) urlEncode(form.newFileName) else s"${form.path}/${urlEncode(form.newFileName)}"
}")
})
post("/:owner/:repository/update", editorForm)(collaboratorsOnly { (form, repository) =>
commitFile(repository, form.branch, form.path, Some(form.newFileName), form.oldFileName,
StringUtil.convertLineSeparator(form.content, form.lineSeparator), form.charset,
if(form.oldFileName.exists(_ == form.newFileName)){
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}"
if(form.path.length == 0) urlEncode(form.newFileName) else s"${form.path}/${urlEncode(form.newFileName)}"
}")
})
@@ -200,53 +297,109 @@ trait RepositoryViewerControllerBase extends ControllerBase {
redirect(s"/${repository.owner}/${repository.name}/tree/${form.branch}${if(form.path.length == 0) "" else form.path}")
})
get("/:owner/:repository/raw/*")(referrersOnly { repository =>
val (id, path) = splitPath(repository, multiParams("splat").head)
using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git =>
val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(id))
getPathObjectId(git, path, revCommit).flatMap { objectId =>
JGitUtil.getObjectLoaderFromId(git, objectId){ loader =>
contentType = "application/octet-stream"
response.setContentLength(loader.getSize.toInt)
loader.copyTo(response.outputStream)
()
}
} getOrElse NotFound
}
})
/**
* Displays the file content of the specified branch or commit.
*/
get("/:owner/:repository/blob/*")(referrersOnly { repository =>
val blobRoute = 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)
}
// Download (This route is left for backword compatibility)
JGitUtil.getObjectLoaderFromId(git, objectId){ loader =>
contentType = "application/octet-stream"
response.setContentLength(loader.getSize.toInt)
loader.copyTo(response.outputStream)
()
} getOrElse NotFound
} else {
repo.html.blob(id, repository, path.split("/").toList, JGitUtil.getContentInfo(git, path, objectId),
new JGitUtil.CommitInfo(lastModifiedCommit), hasWritePermission(repository.owner, repository.name, context.loginAccount))
html.blob(id, repository, path.split("/").toList,
JGitUtil.getContentInfo(git, path, objectId),
new JGitUtil.CommitInfo(JGitUtil.getLastModifiedCommit(git, revCommit, path)),
hasWritePermission(repository.owner, repository.name, context.loginAccount),
request.paths(2) == "blame")
}
} getOrElse NotFound
}
})
get("/:owner/:repository/blame/*"){
blobRoute.action()
}
/**
* Blame data.
*/
ajaxGet("/:owner/:repository/get-blame/*")(referrersOnly { repository =>
val (id, path) = splitPath(repository, multiParams("splat").head)
contentType = formats("json")
using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git =>
val last = git.log.add(git.getRepository.resolve(id)).addPath(path).setMaxCount(1).call.iterator.next.name
Map(
"root" -> s"${context.baseUrl}/${repository.owner}/${repository.name}",
"id" -> id,
"path" -> path,
"last" -> last,
"blame" -> JGitUtil.getBlame(git, id, path).map{ blame =>
Map(
"id" -> blame.id,
"author" -> view.helpers.user(blame.authorName, blame.authorEmailAddress).toString,
"avatar" -> view.helpers.avatarLink(blame.authorName, 32, blame.authorEmailAddress).toString,
"authed" -> helper.html.datetimeago(blame.authorTime).toString,
"prev" -> blame.prev,
"prevPath" -> blame.prevPath,
"commited" -> blame.commitTime.getTime,
"message" -> blame.message,
"lines" -> blame.lines)
})
}
})
/**
* 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) =>
repo.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))
try {
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))
}
}
}
} catch {
case e:MissingObjectException => NotFound
}
})
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.fileName, form.oldLineNumber, form.newLineNumber, form.issueId)
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)
@@ -260,7 +413,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
val oldLineNumber = params.get("oldLineNumber") map (_.toInt)
val newLineNumber = params.get("newLineNumber") map (_.toInt)
val issueId = params.get("issueId") map (_.toInt)
repo.html.commentform(
html.commentform(
commitId = id,
fileName, oldLineNumber, newLineNumber, issueId,
hasWritePermission = hasWritePermission(repository.owner, repository.name, context.loginAccount),
@@ -271,26 +424,36 @@ trait RepositoryViewerControllerBase extends ControllerBase {
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.content, form.fileName, form.oldLineNumber, form.newLineNumber, form.issueId)
val comment = getCommitComment(repository.owner, repository.name, commentId.toString).get
form.issueId match {
case Some(issueId) => recordCommentPullRequestActivity(repository.owner, repository.name, context.loginAccount.get.userName, issueId, form.content)
case Some(issueId) =>
recordCommentPullRequestActivity(repository.owner, repository.name, context.loginAccount.get.userName, issueId, form.content)
callPullRequestReviewCommentWebHook("create", comment, repository, issueId, context.baseUrl, context.loginAccount.get)
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)
helper.html.commitcomment(comment, 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" => repo.html.editcomment(
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))
Map(
"content" -> view.Markdown.toHtml(
markdown = x.content,
repository = repository,
enableWikiLink = false,
enableRefsLink = true,
enableAnchor = true,
enableLineBreaks = true,
hasWritePermission = isEditable(x.userName, x.repositoryName, x.commentedUserName)
)
))
}
} else Unauthorized
@@ -322,14 +485,17 @@ trait RepositoryViewerControllerBase extends ControllerBase {
* Displays branches.
*/
get("/:owner/:repository/branches")(referrersOnly { repository =>
using(Git.open(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, hasWritePermission(repository.owner, repository.name, context.loginAccount), repository)
}
val branches = JGitUtil.getBranches(
owner = repository.owner,
name = repository.name,
defaultBranch = repository.repository.defaultBranch,
origin = repository.repository.originUserName.isEmpty
)
.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)
})
/**
@@ -369,7 +535,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
* Displays tags.
*/
get("/:owner/:repository/tags")(referrersOnly {
repo.html.tags(_)
html.tags(_)
})
/**
@@ -386,7 +552,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
})
get("/:owner/:repository/network/members")(referrersOnly { repository =>
repo.html.forked(
html.forked(
getRepository(
repository.repository.originUserName.getOrElse(repository.owner),
repository.repository.originRepositoryName.getOrElse(repository.name),
@@ -397,7 +563,36 @@ trait RepositoryViewerControllerBase extends ControllerBase {
repository)
})
private def splitPath(repository: service.RepositoryService.RepositoryInfo, path: String): (String, String) = {
/**
* Displays the file find of branch.
*/
get("/:owner/:repository/find/*")(referrersOnly { repository =>
using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git =>
val ref = multiParams("splat").head
JGitUtil.getTreeId(git, ref).map{ treeId =>
html.find(ref,
treeId,
repository,
context.loginAccount match {
case None => List()
case account: Option[Account] => getGroupsByUserName(account.get.userName)
})
} getOrElse NotFound
}
})
/**
* Get all file list of branch.
*/
ajaxGet("/:owner/:repository/tree-list/:tree")(referrersOnly { repository =>
using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git =>
val treeId = params("tree")
contentType = formats("json")
Map("paths" -> JGitUtil.getAllFileListByTreeId(git, treeId))
}
})
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 {
@@ -408,7 +603,9 @@ trait RepositoryViewerControllerBase extends ControllerBase {
}
private val readmeFiles = view.helpers.renderableSuffixes.map(suffix => s"readme${suffix}") ++ Seq("readme.txt", "readme")
private val readmeFiles = PluginRegistry().renderableExtensions.map { extension =>
s"readme.${extension}"
} ++ Seq("readme.txt", "readme")
/**
* Provides HTML of the file list.
@@ -420,7 +617,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
*/
private def fileList(repository: RepositoryService.RepositoryInfo, revstr: String = "", path: String = ".") = {
if(repository.commitCount == 0){
repo.html.guide(repository, hasWritePermission(repository.owner, repository.name, context.loginAccount))
html.guide(repository, hasWritePermission(repository.owner, repository.name, context.loginAccount))
} else {
using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git =>
// get specified commit
@@ -439,14 +636,15 @@ trait RepositoryViewerControllerBase extends ControllerBase {
Git.open(getRepositoryDir(repository.owner, repository.name)), file.id, true).get)
}
repo.html.files(revision, repository,
html.files(revision, repository,
if(path == ".") Nil else path.split("/").toList, // current path
context.loginAccount match {
case None => List()
case account: Option[model.Account] => getGroupsByUserName(account.get.userName)
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),
getPullRequestFromBranch(repository.owner, repository.name, revstr, repository.repository.defaultBranch),
flash.get("info"), flash.get("error"))
}
} getOrElse NotFound
@@ -454,7 +652,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
}
}
private def commitFile(repository: service.RepositoryService.RepositoryInfo,
private def commitFile(repository: RepositoryService.RepositoryInfo,
branch: String, path: String, newFileName: Option[String], oldFileName: Option[String],
content: String, charset: String, message: String) = {
@@ -506,14 +704,13 @@ trait RepositoryViewerControllerBase extends ControllerBase {
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))
getWebHookURLs(repository.owner, repository.name) match {
case webHookURLs if(webHookURLs.nonEmpty) =>
for(ownerAccount <- getAccountByUserName(repository.owner)){
callWebHook(repository.owner, repository.name, webHookURLs,
WebHookPayload(git, loginAccount, headName, repository, List(commit), ownerAccount))
}
case _ =>
callWebHookOf(repository.owner, repository.name, WebHook.Push) {
getAccountByUserName(repository.owner).map{ ownerAccount =>
WebHookPushPayload(git, loginAccount, headName, repository, List(commit), ownerAccount,
oldId = headTip, newId = commitId)
}
}
}
}
@@ -562,6 +759,13 @@ trait RepositoryViewerControllerBase extends ControllerBase {
}
}
private def isEditable(owner: String, repository: String, author: String)(implicit context: app.Context): Boolean =
private def isEditable(owner: String, repository: String, author: String)(implicit context: Context): Boolean =
hasWritePermission(owner, repository, context.loginAccount) || author == context.loginAccount.get.userName
override protected def renderUncaughtException(e: Throwable)(implicit request: HttpServletRequest, response: HttpServletResponse): Unit = {
e.printStackTrace()
}
}

View File

@@ -1,9 +1,10 @@
package app
package gitbucket.core.controller
import util._
import gitbucket.core.search.html
import gitbucket.core.service._
import gitbucket.core.util.{StringUtil, ControlUtil, ReferrerAuthenticator, Implicits}
import ControlUtil._
import Implicits._
import service._
import jp.sf.amateras.scalatra.forms._
class SearchController extends SearchControllerBase
@@ -34,12 +35,12 @@ trait SearchControllerBase extends ControllerBase { self: RepositoryService
}
target.toLowerCase match {
case "issue" => search.html.issues(
case "issue" => html.issues(
searchIssues(repository.owner, repository.name, query),
countFiles(repository.owner, repository.name, query),
query, page, repository)
case _ => search.html.code(
case _ => html.code(
searchFiles(repository.owner, repository.name, query),
countIssues(repository.owner, repository.name, query),
query, page, repository)

View File

@@ -1,10 +1,11 @@
package app
package gitbucket.core.controller
import service.{AccountService, SystemSettingsService}
import gitbucket.core.admin.html
import gitbucket.core.service.{AccountService, SystemSettingsService}
import gitbucket.core.util.AdminAuthenticator
import gitbucket.core.ssh.SshServer
import SystemSettingsService._
import util.AdminAuthenticator
import jp.sf.amateras.scalatra.forms._
import ssh.SshServer
class SystemSettingsController extends SystemSettingsControllerBase
with AccountService with AdminAuthenticator
@@ -20,9 +21,11 @@ trait SystemSettingsControllerBase extends ControllerBase {
"isCreateRepoOptionPublic" -> trim(label("Default option to create a new repository", boolean())),
"gravatar" -> trim(label("Gravatar", boolean())),
"notification" -> trim(label("Notification", boolean())),
"activityLogLimit" -> trim(label("Limit of activity logs", optional(number()))),
"ssh" -> trim(label("SSH access", boolean())),
"sshPort" -> trim(label("SSH port", optional(number()))),
"smtp" -> optionalIfNotChecked("notification", mapping(
"useSMTP" -> trim(label("SMTP", boolean())),
"smtp" -> optionalIfNotChecked("useSMTP", mapping(
"host" -> trim(label("SMTP Host", text(required))),
"port" -> trim(label("SMTP Port", optional(number()))),
"user" -> trim(label("SMTP User", optional(text()))),
@@ -59,7 +62,7 @@ trait SystemSettingsControllerBase extends ControllerBase {
case class PluginForm(pluginIds: List[String])
get("/admin/system")(adminOnly {
admin.html.system(flash.get("info"))
html.system(flash.get("info"))
})
post("/admin/system", form)(adminOnly { form =>
@@ -70,7 +73,7 @@ trait SystemSettingsControllerBase extends ControllerBase {
}
if(form.ssh && !SshServer.isActive && form.baseUrl.isDefined){
SshServer.start(request.getServletContext,
SshServer.start(
form.sshPort.getOrElse(SystemSettingsService.DefaultSshPort),
form.baseUrl.get)
} else if(!form.ssh && SshServer.isActive){

View File

@@ -1,11 +1,12 @@
package app
package gitbucket.core.controller
import service._
import util.AdminAuthenticator
import util.StringUtil._
import util.ControlUtil._
import util.Directory._
import util.Implicits._
import gitbucket.core.service.{RepositoryService, AccountService}
import gitbucket.core.admin.users.html
import gitbucket.core.util._
import gitbucket.core.util.ControlUtil._
import gitbucket.core.util.StringUtil._
import gitbucket.core.util.Implicits._
import gitbucket.core.util.Directory._
import jp.sf.amateras.scalatra.forms._
import org.scalatra.i18n.Messages
import org.apache.commons.io.FileUtils
@@ -75,11 +76,11 @@ trait UserManagementControllerBase extends AccountManagementControllerBase {
account.userName -> getGroupMembers(account.userName).map(_.userName)
}.toMap
admin.users.html.list(users, members, includeRemoved)
html.list(users, members, includeRemoved)
})
get("/admin/users/_newuser")(adminOnly {
admin.users.html.user(None)
html.user(None)
})
post("/admin/users/_newuser", newUserForm)(adminOnly { form =>
@@ -90,7 +91,7 @@ trait UserManagementControllerBase extends AccountManagementControllerBase {
get("/admin/users/:userName/_edituser")(adminOnly {
val userName = params("userName")
admin.users.html.user(getAccountByUserName(userName, true))
html.user(getAccountByUserName(userName, true))
})
post("/admin/users/:name/_edituser", editUserForm)(adminOnly { form =>
@@ -99,12 +100,12 @@ trait UserManagementControllerBase extends AccountManagementControllerBase {
if(form.isRemoved){
// Remove repositories
getRepositoryNamesOfUser(userName).foreach { repositoryName =>
deleteRepository(userName, repositoryName)
FileUtils.deleteDirectory(getRepositoryDir(userName, repositoryName))
FileUtils.deleteDirectory(getWikiRepositoryDir(userName, repositoryName))
FileUtils.deleteDirectory(getTemporaryDir(userName, repositoryName))
}
// 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)
}
@@ -124,7 +125,7 @@ trait UserManagementControllerBase extends AccountManagementControllerBase {
})
get("/admin/users/_newgroup")(adminOnly {
admin.users.html.group(None, Nil)
html.group(None, Nil)
})
post("/admin/users/_newgroup", newGroupForm)(adminOnly { form =>
@@ -140,7 +141,7 @@ trait UserManagementControllerBase extends AccountManagementControllerBase {
get("/admin/users/:groupName/_editgroup")(adminOnly {
defining(params("groupName")){ groupName =>
admin.users.html.group(getAccountByUserName(groupName, true), getGroupMembers(groupName))
html.group(getAccountByUserName(groupName, true), getGroupMembers(groupName))
}
})
@@ -193,7 +194,7 @@ trait UserManagementControllerBase extends AccountManagementControllerBase {
protected def disableByNotYourself(paramName: String): Constraint = new Constraint() {
override def validate(name: String, value: String, messages: Messages): Option[String] = {
params.get(paramName).flatMap { userName =>
if(userName == context.loginAccount.get.userName)
if(userName == context.loginAccount.get.userName && params.get("removed") == Some("true"))
Some("You can't disable your account yourself")
else
None

View File

@@ -1,14 +1,15 @@
package app
package gitbucket.core.controller
import service._
import util._
import util.Directory._
import util.ControlUtil._
import util.Implicits._
import gitbucket.core.wiki.html
import gitbucket.core.service.{RepositoryService, WikiService, ActivityService, AccountService}
import gitbucket.core.util._
import gitbucket.core.util.StringUtil._
import gitbucket.core.util.ControlUtil._
import gitbucket.core.util.Implicits._
import gitbucket.core.util.Directory._
import jp.sf.amateras.scalatra.forms._
import org.eclipse.jgit.api.Git
import org.scalatra.i18n.Messages
import java.util.ResourceBundle
class WikiController extends WikiControllerBase
with WikiService with RepositoryService with AccountService with ActivityService with CollaboratorsAuthenticator with ReferrerAuthenticator
@@ -36,7 +37,7 @@ trait WikiControllerBase extends ControllerBase {
get("/:owner/:repository/wiki")(referrersOnly { repository =>
getWikiPage(repository.owner, repository.name, "Home").map { page =>
wiki.html.page("Home", page, getWikiPageList(repository.owner, repository.name),
html.page("Home", page, getWikiPageList(repository.owner, repository.name),
repository, hasWritePermission(repository.owner, repository.name, context.loginAccount))
} getOrElse redirect(s"/${repository.owner}/${repository.name}/wiki/Home/_edit")
})
@@ -45,7 +46,7 @@ trait WikiControllerBase extends ControllerBase {
val pageName = StringUtil.urlDecode(params("page"))
getWikiPage(repository.owner, repository.name, pageName).map { page =>
wiki.html.page(pageName, page, getWikiPageList(repository.owner, repository.name),
html.page(pageName, page, getWikiPageList(repository.owner, repository.name),
repository, hasWritePermission(repository.owner, repository.name, context.loginAccount))
} getOrElse redirect(s"/${repository.owner}/${repository.name}/wiki/${StringUtil.urlEncode(pageName)}/_edit")
})
@@ -55,7 +56,7 @@ trait WikiControllerBase extends ControllerBase {
using(Git.open(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 Right((logs, hasNext)) => html.history(Some(pageName), logs, repository)
case Left(_) => NotFound
}
}
@@ -66,7 +67,7 @@ trait WikiControllerBase extends ControllerBase {
val Array(from, to) = params("commitId").split("\\.\\.\\.")
using(Git.open(getWikiRepositoryDir(repository.owner, repository.name))){ git =>
wiki.html.compare(Some(pageName), from, to, JGitUtil.getDiffs(git, from, to, true).filter(_.newPath == pageName + ".md"), repository,
html.compare(Some(pageName), from, to, JGitUtil.getDiffs(git, from, to, true).filter(_.newPath == pageName + ".md"), repository,
hasWritePermission(repository.owner, repository.name, context.loginAccount), flash.get("info"))
}
})
@@ -75,7 +76,7 @@ trait WikiControllerBase extends ControllerBase {
val Array(from, to) = params("commitId").split("\\.\\.\\.")
using(Git.open(getWikiRepositoryDir(repository.owner, repository.name))){ git =>
wiki.html.compare(None, from, to, JGitUtil.getDiffs(git, from, to, true), repository,
html.compare(None, from, to, JGitUtil.getDiffs(git, from, to, true), repository,
hasWritePermission(repository.owner, repository.name, context.loginAccount), flash.get("info"))
}
})
@@ -105,13 +106,21 @@ trait WikiControllerBase extends ControllerBase {
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)
html.edit(pageName, getWikiPage(repository.owner, repository.name, pageName), repository)
})
post("/:owner/:repository/wiki/_edit", editForm)(collaboratorsOnly { (form, repository) =>
defining(context.loginAccount.get){ loginAccount =>
saveWikiPage(repository.owner, repository.name, form.currentPageName, form.pageName,
form.content, loginAccount, form.message.getOrElse(""), Some(form.id)).map { commitId =>
saveWikiPage(
repository.owner,
repository.name,
form.currentPageName,
form.pageName,
appendNewLine(convertLineSeparator(form.content, "LF"), "LF"),
loginAccount,
form.message.getOrElse(""),
Some(form.id)
).map { commitId =>
updateLastActivityDate(repository.owner, repository.name)
recordEditWikiPageActivity(repository.owner, repository.name, loginAccount.userName, form.pageName, commitId)
}
@@ -120,7 +129,7 @@ trait WikiControllerBase extends ControllerBase {
})
get("/:owner/:repository/wiki/_new")(collaboratorsOnly {
wiki.html.edit("", None, _)
html.edit("", None, _)
})
post("/:owner/:repository/wiki/_new", newForm)(collaboratorsOnly { (form, repository) =>
@@ -147,14 +156,14 @@ trait WikiControllerBase extends ControllerBase {
})
get("/:owner/:repository/wiki/_pages")(referrersOnly { repository =>
wiki.html.pages(getWikiPageList(repository.owner, repository.name), repository,
html.pages(getWikiPageList(repository.owner, repository.name), repository,
hasWritePermission(repository.owner, repository.name, context.loginAccount))
})
get("/:owner/:repository/wiki/_history")(referrersOnly { repository =>
using(Git.open(getWikiRepositoryDir(repository.owner, repository.name))){ git =>
JGitUtil.getCommitLog(git, "master") match {
case Right((logs, hasNext)) => wiki.html.history(None, logs, repository)
case Right((logs, hasNext)) => html.history(None, logs, repository)
case Left(_) => NotFound
}
}

View File

@@ -0,0 +1,21 @@
package gitbucket.core.model
trait AccessTokenComponent { self: Profile =>
import profile.simple._
lazy val AccessTokens = TableQuery[AccessTokens]
class AccessTokens(tag: Tag) extends Table[AccessToken](tag, "ACCESS_TOKEN") {
val accessTokenId = column[Int]("ACCESS_TOKEN_ID", O AutoInc)
val userName = column[String]("USER_NAME")
val tokenHash = column[String]("TOKEN_HASH")
val note = column[String]("NOTE")
def * = (accessTokenId, userName, tokenHash, note) <> (AccessToken.tupled, AccessToken.unapply)
}
}
case class AccessToken(
accessTokenId: Int = 0,
userName: String,
tokenHash: String,
note: String
)

View File

@@ -1,4 +1,4 @@
package model
package gitbucket.core.model
trait AccountComponent { self: Profile =>
import profile.simple._

View File

@@ -1,4 +1,4 @@
package model
package gitbucket.core.model
trait ActivityComponent extends TemplateComponent { self: Profile =>
import profile.simple._

View File

@@ -1,4 +1,4 @@
package model
package gitbucket.core.model
protected[model] trait TemplateComponent { self: Profile =>
import profile.simple._
@@ -49,6 +49,9 @@ protected[model] trait TemplateComponent { self: Profile =>
def byCommit(owner: String, repository: String, commitId: String) =
byRepository(owner, repository) && (this.commitId === commitId)
def byCommit(owner: Column[String], repository: Column[String], commitId: Column[String]) =
byRepository(userName, repositoryName) && (this.commitId === commitId)
}
}

View File

@@ -1,4 +1,4 @@
package model
package gitbucket.core.model
trait CollaboratorComponent extends TemplateComponent { self: Profile =>
import profile.simple._

View File

@@ -1,4 +1,4 @@
package model
package gitbucket.core.model
trait Comment {
val commentedUserName: String
@@ -55,8 +55,8 @@ trait CommitCommentComponent extends TemplateComponent { self: Profile =>
val newLine = column[Option[Int]]("NEW_LINE_NUMBER")
val registeredDate = column[java.util.Date]("REGISTERED_DATE")
val updatedDate = column[java.util.Date]("UPDATED_DATE")
val pullRequest = column[Boolean]("PULL_REQUEST")
def * = (userName, repositoryName, commitId, commentId, commentedUserName, content, fileName, oldLine, newLine, registeredDate, updatedDate, pullRequest) <> (CommitComment.tupled, CommitComment.unapply)
val issueId = column[Option[Int]]("ISSUE_ID")
def * = (userName, repositoryName, commitId, commentId, commentedUserName, content, fileName, oldLine, newLine, registeredDate, updatedDate, issueId) <> (CommitComment.tupled, CommitComment.unapply)
def byPrimaryKey(commentId: Int) = this.commentId === commentId.bind
}
@@ -74,5 +74,5 @@ case class CommitComment(
newLine: Option[Int],
registeredDate: java.util.Date,
updatedDate: java.util.Date,
pullRequest: Boolean
issueId: Option[Int]
) extends Comment

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