mirror of
https://github.com/gitbucket/gitbucket.git
synced 2025-11-02 19:45:57 +01:00
Compare commits
195 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c73e89ccd4 | ||
|
|
f58a506780 | ||
|
|
411d19e74e | ||
|
|
bd51ffd9d2 | ||
|
|
83980fdccd | ||
|
|
baab243bc8 | ||
|
|
4dd8e1dc63 | ||
|
|
0b11b8b084 | ||
|
|
704775dc60 | ||
|
|
c7a7be1de0 | ||
|
|
e21a970977 | ||
|
|
a526dcf2dd | ||
|
|
e86710fbbd | ||
|
|
73e850493a | ||
|
|
7d735f6f8a | ||
|
|
043e99a9eb | ||
|
|
65e079b1d3 | ||
|
|
32e6f584d8 | ||
|
|
ebc5219ce6 | ||
|
|
ad8e620bdf | ||
|
|
8590c693b9 | ||
|
|
8b29bf7d93 | ||
|
|
43b7f83082 | ||
|
|
9ef5c981ef | ||
|
|
f027ac34d4 | ||
|
|
2ea31d6869 | ||
|
|
a1af7d0f9c | ||
|
|
797bf37bfa | ||
|
|
7f78815c11 | ||
|
|
d8a3f308ed | ||
|
|
74e18a982d | ||
|
|
e86ad423c5 | ||
|
|
2f00060c57 | ||
|
|
3b7e6aa68e | ||
|
|
5f3d6242fd | ||
|
|
6793a86bae | ||
|
|
d99ce20529 | ||
|
|
93e7b604cd | ||
|
|
5c81ce9b68 | ||
|
|
c9cf62701f | ||
|
|
f65babca4b | ||
|
|
23c146fc5d | ||
|
|
e14f336142 | ||
|
|
54647be5bd | ||
|
|
9517d65646 | ||
|
|
0156e401fa | ||
|
|
d6e2bc464d | ||
|
|
5124ff593d | ||
|
|
727c90afdc | ||
|
|
9ebd5e3265 | ||
|
|
d120142127 | ||
|
|
f9e4cddcaf | ||
|
|
8f64f174d9 | ||
|
|
d6817796b3 | ||
|
|
ab19d473c4 | ||
|
|
48f0116358 | ||
|
|
d8e6e97845 | ||
|
|
9a8920788c | ||
|
|
27864a3a3c | ||
|
|
b39e863591 | ||
|
|
7661e8cadd | ||
|
|
7d3c7a0c61 | ||
|
|
7375ff9f97 | ||
|
|
5df6ec8985 | ||
|
|
9a8479ee58 | ||
|
|
73766f11eb | ||
|
|
a22878e2c5 | ||
|
|
1a2eb9d1e7 | ||
|
|
277ace3c8e | ||
|
|
40f376dbd9 | ||
|
|
444af0935e | ||
|
|
fb15fa0e43 | ||
|
|
bcd3e14870 | ||
|
|
c18702dcea | ||
|
|
1341ef9c52 | ||
|
|
f605a8d085 | ||
|
|
e0f7a7a3c6 | ||
|
|
1d72bed442 | ||
|
|
284b8e7c16 | ||
|
|
ff9fb24094 | ||
|
|
fde4448dd0 | ||
|
|
d16ce90a3d | ||
|
|
3ed5525956 | ||
|
|
855d1e12aa | ||
|
|
e03797a58f | ||
|
|
f0d38cf8ec | ||
|
|
2723580e17 | ||
|
|
1977aa481d | ||
|
|
4b36a8f831 | ||
|
|
96b56e38ba | ||
|
|
849d117ad3 | ||
|
|
8d57fca779 | ||
|
|
0dc867306b | ||
|
|
eefb4c01ec | ||
|
|
ccce499f7f | ||
|
|
9f11eaa4d3 | ||
|
|
7b85c0e55f | ||
|
|
7e92f1abd5 | ||
|
|
825f2518e9 | ||
|
|
def1e877db | ||
|
|
6acbd5b2cf | ||
|
|
73b7aef4a9 | ||
|
|
3d73e3922b | ||
|
|
224e44151f | ||
|
|
d9c1293985 | ||
|
|
849a40d4b5 | ||
|
|
177387e9b0 | ||
|
|
cacce54714 | ||
|
|
12082322ee | ||
|
|
7e0a5b7fec | ||
|
|
d2cf4afc81 | ||
|
|
cce62de075 | ||
|
|
d9450df7e9 | ||
|
|
41fc81fab6 | ||
|
|
aa35498bdd | ||
|
|
14becd0bd6 | ||
|
|
7390e21934 | ||
|
|
6b37967162 | ||
|
|
a03b9584ee | ||
|
|
34649dfeda | ||
|
|
bc84cfc2c8 | ||
|
|
bcba1f068b | ||
|
|
e6f30ef86b | ||
|
|
9e67999ef0 | ||
|
|
be75cef752 | ||
|
|
19ead71b48 | ||
|
|
7ebe1d6c62 | ||
|
|
2331b58b87 | ||
|
|
d495b04d85 | ||
|
|
751a8703ef | ||
|
|
1e6d26221d | ||
|
|
44a8e98c7b | ||
|
|
415519716e | ||
|
|
597f86dc7b | ||
|
|
579ed19949 | ||
|
|
9221bfa045 | ||
|
|
3057a31a6c | ||
|
|
d47ccf587c | ||
|
|
3e78d423ac | ||
|
|
0299cee5ec | ||
|
|
97ceffe689 | ||
|
|
9019d93449 | ||
|
|
32006e02c0 | ||
|
|
5ba0f6d51e | ||
|
|
c004d501f6 | ||
|
|
6067fa0fca | ||
|
|
e2c6658e59 | ||
|
|
0a6e50cbbe | ||
|
|
3732963d4b | ||
|
|
83bcbef6ce | ||
|
|
47cb4d1c49 | ||
|
|
32799cead7 | ||
|
|
dbedc2166b | ||
|
|
e86b404ca2 | ||
|
|
321a3a72f0 | ||
|
|
b512e7c390 | ||
|
|
ae7ead6272 | ||
|
|
db55719a6f | ||
|
|
4e1094e75b | ||
|
|
3fd97662f5 | ||
|
|
d6946b93c3 | ||
|
|
20217058fe | ||
|
|
e67365a19f | ||
|
|
c2f1817c6a | ||
|
|
4a948d9b01 | ||
|
|
377bc2703b | ||
|
|
196890b26f | ||
|
|
491fc2c164 | ||
|
|
1bed38f175 | ||
|
|
b124c31f65 | ||
|
|
8c588cbd66 | ||
|
|
334753b1ad | ||
|
|
f6e7401d1b | ||
|
|
ab564cc2d4 | ||
|
|
b10839a5c2 | ||
|
|
bb3323fb0e | ||
|
|
0b15ecbacd | ||
|
|
9f6afaed07 | ||
|
|
925420734e | ||
|
|
fb6bb12c52 | ||
|
|
22d12d0488 | ||
|
|
6888d959e1 | ||
|
|
65e66f52f6 | ||
|
|
225abfa126 | ||
|
|
876757f2d4 | ||
|
|
7e77398645 | ||
|
|
73c76a5a88 | ||
|
|
e5fca0d6cc | ||
|
|
eed7e5177f | ||
|
|
dd54ab31cb | ||
|
|
a417c373f1 | ||
|
|
5f5cc8d454 | ||
|
|
0acbaeae86 | ||
|
|
140f1eb31b | ||
|
|
172af307a6 |
@@ -1,3 +1,3 @@
|
|||||||
language: scala
|
language: scala
|
||||||
scala:
|
scala:
|
||||||
- 2.11.2
|
- 2.11.6
|
||||||
|
|||||||
20
README.md
20
README.md
@@ -79,6 +79,26 @@ Run the following commands in `Terminal` to
|
|||||||
|
|
||||||
Release Notes
|
Release Notes
|
||||||
--------
|
--------
|
||||||
|
### 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
|
### 2.8 - 1 Feb 2015
|
||||||
- New logo and icons
|
- New logo and icons
|
||||||
- New system setting options to control visibility
|
- New system setting options to control visibility
|
||||||
|
|||||||
@@ -5,8 +5,8 @@
|
|||||||
<property name="embed.classes.dir" value="${target.dir}/embed-classes"/>
|
<property name="embed.classes.dir" value="${target.dir}/embed-classes"/>
|
||||||
<property name="jetty.dir" value="embed-jetty"/>
|
<property name="jetty.dir" value="embed-jetty"/>
|
||||||
<property name="scala.version" value="2.11"/>
|
<property name="scala.version" value="2.11"/>
|
||||||
<property name="gitbucket.version" value="0.0.1"/>
|
<property name="gitbucket.version" value="3.2.0"/>
|
||||||
<property name="jetty.version" value="8.1.8.v20121106"/>
|
<property name="jetty.version" value="8.1.16.v20140903"/>
|
||||||
<property name="servlet.version" value="3.0.0.v201112011016"/>
|
<property name="servlet.version" value="3.0.0.v201112011016"/>
|
||||||
|
|
||||||
<condition property="sbt.exec" value="sbt.bat" else="sbt.sh">
|
<condition property="sbt.exec" value="sbt.bat" else="sbt.sh">
|
||||||
|
|||||||
22
doc/activity.md
Normal file
22
doc/activity.md
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
Activity Timeline
|
||||||
|
========
|
||||||
|
GitBucket records several types of user activity to ```ACTIVITY``` table. Activity types are shown below:
|
||||||
|
|
||||||
|
type | message | additional information
|
||||||
|
------------------|------------------------------------------------------|------------------------
|
||||||
|
create_repository |$user created $owner/$repo |-
|
||||||
|
open_issue |$user opened issue $owner/$repo#$issueId |-
|
||||||
|
close_issue |$user closed issue $owner/$repo#$issueId |-
|
||||||
|
close_issue |$user closed pull request $owner/$repo#$issueId |-
|
||||||
|
reopen_issue |$user reopened issue $owner/$repo#$issueId |-
|
||||||
|
comment_issue |$user commented on issue $owner/$repo#$issueId |-
|
||||||
|
comment_issue |$user commented on pull request $owner/$repo#$issueId |-
|
||||||
|
create_wiki |$user created the $owner/$repo wiki |$page
|
||||||
|
edit_wiki |$user edited the $owner/$repo wiki |$page<br>$page:$commitId(since 1.5)
|
||||||
|
push |$user pushed to $owner/$repo#$branch to $owner/$repo |$commitId:$shortMessage\n*
|
||||||
|
create_tag |$user created tag $tag at $owner/$repo |-
|
||||||
|
create_branch |$user created branch $branch at $owner/$repo |-
|
||||||
|
delete_branch |$user deleted branch $branch at $owner/$repo |-
|
||||||
|
fork |$user forked $owner/$repo to $owner/$repo |-
|
||||||
|
open_pullreq |$user opened pull request $owner/$repo#issueId |-
|
||||||
|
merge_pullreq |$user merge pull request $owner/$repo#issueId |-
|
||||||
37
doc/auto_update.md
Normal file
37
doc/auto_update.md
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
Automatic Schema Updating
|
||||||
|
========
|
||||||
|
GitBucket uses H2 database to manage project and account data. GitBucket updates database schema automatically in the first run after the upgrading.
|
||||||
|
|
||||||
|
To release a new version of GitBucket, add the version definition to the [servlet.AutoUpdate](https://github.com/takezoe/gitbucket/blob/master/src/main/scala/servlet/AutoUpdateListener.scala) at first.
|
||||||
|
|
||||||
|
```scala
|
||||||
|
object AutoUpdate {
|
||||||
|
...
|
||||||
|
/**
|
||||||
|
* The history of versions. A head of this sequence is the current BitBucket version.
|
||||||
|
*/
|
||||||
|
val versions = Seq(
|
||||||
|
Version(1, 0)
|
||||||
|
)
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
Next, add a SQL file which updates database schema into [/src/main/resources/update/](https://github.com/takezoe/gitbucket/tree/master/src/main/resources/update) as ```MAJOR_MINOR.sql```.
|
||||||
|
|
||||||
|
GitBucket stores the current version to ```GITBUCKET_HOME/version``` and checks it at start-up. If the stored version differs from the actual version, it executes differences of SQL files between the stored version and the actual version. And ```GITBUCKET_HOME/version``` is updated by the actual version.
|
||||||
|
|
||||||
|
We can also add any Scala code for upgrade GitBucket which modifies resources other than database. Override ```Version.update``` like below:
|
||||||
|
|
||||||
|
```scala
|
||||||
|
val versions = Seq(
|
||||||
|
new Version(1, 3){
|
||||||
|
override def update(conn: Connection): Unit = {
|
||||||
|
super.update(conn)
|
||||||
|
// Add any code here!
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Version(1, 2),
|
||||||
|
Version(1, 1),
|
||||||
|
Version(1, 0)
|
||||||
|
)
|
||||||
|
```
|
||||||
48
doc/comment_action.md
Normal file
48
doc/comment_action.md
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
About Action in Issue Comment
|
||||||
|
========
|
||||||
|
After the issue creation at GitBucket, users can add comments or close it.
|
||||||
|
The details are saved at ```ISSUE_COMMENT``` table.
|
||||||
|
|
||||||
|
To determine if it was any operation, you see the ```ACTION``` column.
|
||||||
|
|
||||||
|
|ACTION|
|
||||||
|
|--------|
|
||||||
|
|comment|
|
||||||
|
|close_comment|
|
||||||
|
|reopen_comment|
|
||||||
|
|close|
|
||||||
|
|reopen|
|
||||||
|
|commit|
|
||||||
|
|merge|
|
||||||
|
|delete_branch|
|
||||||
|
|refer|
|
||||||
|
|
||||||
|
#####comment
|
||||||
|
This value is saved when users have made a normal comment.
|
||||||
|
|
||||||
|
#####close_comment, reopen_comment
|
||||||
|
These values are saved when users have reopened or closed the issue with comments.
|
||||||
|
|
||||||
|
#####close, reopen
|
||||||
|
These values are saved when users have reopened or closed the issue.
|
||||||
|
At the same time, store the fixed value(i.e. "Close" or "Reopen") to the ```CONTENT``` column.
|
||||||
|
Therefore, this comment is not displayed, and not counted as a comment.
|
||||||
|
|
||||||
|
#####commit
|
||||||
|
This value is saved when users have pushed including the ```#issueId``` to the commit message.
|
||||||
|
At the same time, store it to the ```CONTENT``` column with its commit id.
|
||||||
|
This comment is displayed. But it can not be edited by all users, and also not counted as a comment.
|
||||||
|
|
||||||
|
#####merge
|
||||||
|
This value is saved when users have merged the pull request.
|
||||||
|
At the same time, store the message to the ```CONTENT``` column.
|
||||||
|
This comment is displayed. But it can not be edited by all users, and also not counted as a comment.
|
||||||
|
|
||||||
|
#####delete_branch
|
||||||
|
This value is saved when users have deleted the branch. Users can delete branch after merging pull request which is requested from the same repository.
|
||||||
|
At the same time, store it to the ```CONTENT``` column with the deleted branch name.
|
||||||
|
Therefore, this comment is not displayed, and not counted as a comment.
|
||||||
|
|
||||||
|
#####refer
|
||||||
|
This value is saved when other issue or issue comment contains reference to the issue like ```#issueId```.
|
||||||
|
At the same time, store id and title of the referrer issue as ```id:title```.
|
||||||
44
doc/directory.md
Normal file
44
doc/directory.md
Normal 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>
|
||||||
|
```
|
||||||
38
doc/how_to_run.md
Normal file
38
doc/how_to_run.md
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
How to run from the source tree
|
||||||
|
========
|
||||||
|
|
||||||
|
for Testers
|
||||||
|
--------
|
||||||
|
|
||||||
|
If you want to test GitBucket, input following command at the root directory of the source tree.
|
||||||
|
|
||||||
|
```
|
||||||
|
C:\gitbucket> sbt ~container:start
|
||||||
|
```
|
||||||
|
|
||||||
|
Then access to `http://localhost:8080/` by your browser. The default administrator account is `root` and password is `root`.
|
||||||
|
|
||||||
|
for Developers
|
||||||
|
--------
|
||||||
|
If you want to modify source code and confirm it, you can run GitBucket in auto reloading mode as following:
|
||||||
|
|
||||||
|
```
|
||||||
|
C:\gitbucket> sbt
|
||||||
|
...
|
||||||
|
> container:start
|
||||||
|
...
|
||||||
|
> ~ ;copy-resources;aux-compile
|
||||||
|
```
|
||||||
|
|
||||||
|
Build war file
|
||||||
|
--------
|
||||||
|
|
||||||
|
To build war file, run the following command:
|
||||||
|
|
||||||
|
```
|
||||||
|
C:\gitbucket> sbt package
|
||||||
|
```
|
||||||
|
|
||||||
|
`gitbucket_2.11-x.x.x.war` is generated into `target/scala-2.11`.
|
||||||
|
|
||||||
|
To build executable war file, run Ant at the top of the source tree. It generates executable `gitbucket.war` into `target/scala-2.11`. We release this war file as release artifact. Please note the current build.xml works on Windows only. Replace `sbt.bat` with `sbt.sh` in build.xml if you want to run it on Linux.
|
||||||
23
doc/notification.md
Normal file
23
doc/notification.md
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
Notification Email
|
||||||
|
========
|
||||||
|
|
||||||
|
GitBucket sends email to target users by enabling the notification email by an administrator.
|
||||||
|
|
||||||
|
The timing of the notification are as follows:
|
||||||
|
|
||||||
|
##### at the issue registration (new issue, new pull request)
|
||||||
|
When a record is saved into the ```ISSUE``` table, GitBucket does the notification.
|
||||||
|
|
||||||
|
##### at the comment registration
|
||||||
|
Among the records in the ```ISSUE_COMMENT``` table, them to be counted as a comment (i.e. the record ```ACTION``` column value is "comment" or "close_comment" or "reopen_comment") are saved, GitBucket does the notification.
|
||||||
|
|
||||||
|
##### at the status update (close, reopen, merge)
|
||||||
|
When the ```CLOSED``` column value is updated, GitBucket does the notification.
|
||||||
|
|
||||||
|
Notified users are as follows:
|
||||||
|
|
||||||
|
* individual repository's owner
|
||||||
|
* collaborators
|
||||||
|
* participants
|
||||||
|
|
||||||
|
However, the operation in person is excluded from the target.
|
||||||
10
doc/readme.md
Normal file
10
doc/readme.md
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
Developer's Guide
|
||||||
|
========
|
||||||
|
* [How to run from source tree](how_to_run.md)
|
||||||
|
* [Directory Structure](directory.md)
|
||||||
|
* [Mapping and Validation](validation.md)
|
||||||
|
* Authentication in Controller (not yet)
|
||||||
|
* [About Action in Issue Comment](comment_action.md)
|
||||||
|
* [Activity Types](activity.md)
|
||||||
|
* [Notification Email](notification.md)
|
||||||
|
* [Automatic Schema Updating](auto_update.md)
|
||||||
71
doc/validation.md
Normal file
71
doc/validation.md
Normal 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.
BIN
embed-jetty/jetty-http-8.1.16.v20140903.jar
Normal file
BIN
embed-jetty/jetty-http-8.1.16.v20140903.jar
Normal file
Binary file not shown.
Binary file not shown.
BIN
embed-jetty/jetty-io-8.1.16.v20140903.jar
Normal file
BIN
embed-jetty/jetty-io-8.1.16.v20140903.jar
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
embed-jetty/jetty-server-8.1.16.v20140903.jar
Normal file
BIN
embed-jetty/jetty-server-8.1.16.v20140903.jar
Normal file
Binary file not shown.
Binary file not shown.
BIN
embed-jetty/jetty-servlet-8.1.16.v20140903.jar
Normal file
BIN
embed-jetty/jetty-servlet-8.1.16.v20140903.jar
Normal file
Binary file not shown.
Binary file not shown.
BIN
embed-jetty/jetty-util-8.1.16.v20140903.jar
Normal file
BIN
embed-jetty/jetty-util-8.1.16.v20140903.jar
Normal file
Binary file not shown.
Binary file not shown.
BIN
embed-jetty/jetty-webapp-8.1.16.v20140903.jar
Normal file
BIN
embed-jetty/jetty-webapp-8.1.16.v20140903.jar
Normal file
Binary file not shown.
Binary file not shown.
BIN
embed-jetty/jetty-xml-8.1.16.v20140903.jar
Normal file
BIN
embed-jetty/jetty-xml-8.1.16.v20140903.jar
Normal file
Binary file not shown.
Binary file not shown.
11
embed-jetty/update.sh
Executable file
11
embed-jetty/update.sh
Executable file
@@ -0,0 +1,11 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
version=$1
|
||||||
|
output_dir=`dirname $0`
|
||||||
|
git rm -f ${output_dir}/jetty-*.jar
|
||||||
|
for name in 'io' 'servlet' 'xml' 'continuation' 'security' 'util' 'http' 'server' 'webapp'
|
||||||
|
do
|
||||||
|
jar_filename="jetty-${name}-${version}.jar"
|
||||||
|
wget "http://repo1.maven.org/maven2/org/eclipse/jetty/jetty-${name}/${version}/${jar_filename}" -O ${output_dir}/${jar_filename}
|
||||||
|
done
|
||||||
|
git add ${output_dir}/*.jar
|
||||||
|
git commit
|
||||||
9
etc/deploy-assemby-jar.sh
Executable file
9
etc/deploy-assemby-jar.sh
Executable file
@@ -0,0 +1,9 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
mvn deploy:deploy-file \
|
||||||
|
-DgroupId=gitbucket\
|
||||||
|
-DartifactId=gitbucket-assembly\
|
||||||
|
-Dversion=3.1.1\
|
||||||
|
-Dpackaging=jar\
|
||||||
|
-Dfile=../target/scala-2.11/gitbucket-assembly-3.2.0.jar\
|
||||||
|
-DrepositoryId=sourceforge.jp\
|
||||||
|
-Durl=scp://shell.sourceforge.jp/home/groups/a/am/amateras/htdocs/mvn/
|
||||||
17
etc/pom.xml
Normal file
17
etc/pom.xml
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<?xml version="1.0"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<groupId>jp.sf.amateras</groupId>
|
||||||
|
<artifactId>gitbucket-assembly</artifactId>
|
||||||
|
<version>0.0.1</version>
|
||||||
|
<build>
|
||||||
|
<extensions>
|
||||||
|
<extension>
|
||||||
|
<groupId>org.apache.maven.wagon</groupId>
|
||||||
|
<artifactId>wagon-ssh</artifactId>
|
||||||
|
<version>1.0-beta-6</version>
|
||||||
|
</extension>
|
||||||
|
</extensions>
|
||||||
|
</build>
|
||||||
|
</project>
|
||||||
@@ -1 +1 @@
|
|||||||
sbt.version=0.13.5
|
sbt.version=0.13.8
|
||||||
|
|||||||
@@ -4,19 +4,32 @@ import org.scalatra.sbt._
|
|||||||
import com.typesafe.sbteclipse.plugin.EclipsePlugin.EclipseKeys
|
import com.typesafe.sbteclipse.plugin.EclipsePlugin.EclipseKeys
|
||||||
import play.twirl.sbt.SbtTwirl
|
import play.twirl.sbt.SbtTwirl
|
||||||
import play.twirl.sbt.Import.TwirlKeys._
|
import play.twirl.sbt.Import.TwirlKeys._
|
||||||
|
import sbtassembly._
|
||||||
|
import sbtassembly.AssemblyKeys._
|
||||||
|
|
||||||
object MyBuild extends Build {
|
object MyBuild extends Build {
|
||||||
val Organization = "jp.sf.amateras"
|
val Organization = "gitbucket"
|
||||||
val Name = "gitbucket"
|
val Name = "gitbucket"
|
||||||
val Version = "0.0.1"
|
val Version = "3.2.0"
|
||||||
val ScalaVersion = "2.11.2"
|
val ScalaVersion = "2.11.6"
|
||||||
val ScalatraVersion = "2.3.0"
|
val ScalatraVersion = "2.3.1"
|
||||||
|
|
||||||
lazy val project = Project (
|
lazy val project = Project (
|
||||||
"gitbucket",
|
"gitbucket",
|
||||||
file(".")
|
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(
|
.settings(
|
||||||
sourcesInBase := false,
|
sourcesInBase := false,
|
||||||
organization := Organization,
|
organization := Organization,
|
||||||
@@ -29,31 +42,39 @@ object MyBuild extends Build {
|
|||||||
),
|
),
|
||||||
scalacOptions := Seq("-deprecation", "-language:postfixOps"),
|
scalacOptions := Seq("-deprecation", "-language:postfixOps"),
|
||||||
libraryDependencies ++= Seq(
|
libraryDependencies ++= Seq(
|
||||||
"org.eclipse.jgit" % "org.eclipse.jgit.http.server" % "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.1.201406201815-r",
|
"org.eclipse.jgit" % "org.eclipse.jgit.archive" % "3.4.2.201412180340-r",
|
||||||
"org.scalatra" %% "scalatra" % ScalatraVersion,
|
"org.scalatra" %% "scalatra" % ScalatraVersion,
|
||||||
"org.scalatra" %% "scalatra-specs2" % ScalatraVersion % "test",
|
"org.scalatra" %% "scalatra-specs2" % ScalatraVersion % "test",
|
||||||
"org.scalatra" %% "scalatra-json" % ScalatraVersion,
|
"org.scalatra" %% "scalatra-json" % ScalatraVersion,
|
||||||
"org.json4s" %% "json4s-jackson" % "3.2.10",
|
"org.json4s" %% "json4s-jackson" % "3.2.11",
|
||||||
"jp.sf.amateras" %% "scalatra-forms" % "0.1.0",
|
"jp.sf.amateras" %% "scalatra-forms" % "0.1.0",
|
||||||
"commons-io" % "commons-io" % "2.4",
|
"commons-io" % "commons-io" % "2.4",
|
||||||
"org.pegdown" % "pegdown" % "1.4.1",
|
"org.pegdown" % "pegdown" % "1.4.1", // 1.4.2 has incompatible APi changes
|
||||||
"org.apache.commons" % "commons-compress" % "1.5",
|
"org.apache.commons" % "commons-compress" % "1.9",
|
||||||
"org.apache.commons" % "commons-email" % "1.3.1",
|
"org.apache.commons" % "commons-email" % "1.3.3",
|
||||||
"org.apache.httpcomponents" % "httpclient" % "4.3",
|
"org.apache.httpcomponents" % "httpclient" % "4.3.6",
|
||||||
"org.apache.sshd" % "apache-sshd" % "0.11.0",
|
"org.apache.sshd" % "apache-sshd" % "0.11.0",
|
||||||
"com.typesafe.slick" %% "slick" % "2.1.0",
|
"com.typesafe.slick" %% "slick" % "2.1.0",
|
||||||
"com.novell.ldap" % "jldap" % "2009-10-07",
|
"com.novell.ldap" % "jldap" % "2009-10-07",
|
||||||
"com.h2database" % "h2" % "1.4.180",
|
"com.h2database" % "h2" % "1.4.180",
|
||||||
"ch.qos.logback" % "logback-classic" % "1.0.13" % "runtime",
|
// "ch.qos.logback" % "logback-classic" % "1.0.13" % "runtime",
|
||||||
"org.eclipse.jetty" % "jetty-webapp" % "8.1.8.v20121106" % "container;provided",
|
"org.eclipse.jetty" % "jetty-webapp" % "8.1.16.v20140903" % "container;provided",
|
||||||
"org.eclipse.jetty.orbit" % "javax.servlet" % "3.0.0.v201112011016" % "container;provided;test" artifacts Artifact("javax.servlet", "jar", "jar"),
|
"org.eclipse.jetty.orbit" % "javax.servlet" % "3.0.0.v201112011016" % "container;provided;test" artifacts Artifact("javax.servlet", "jar", "jar"),
|
||||||
"junit" % "junit" % "4.11" % "test",
|
"junit" % "junit" % "4.12" % "test",
|
||||||
"com.typesafe.play" %% "twirl-compiler" % "1.0.2"
|
"com.mchange" % "c3p0" % "0.9.5",
|
||||||
|
"com.typesafe" % "config" % "1.2.1",
|
||||||
|
"com.typesafe.play" %% "twirl-compiler" % "1.0.4",
|
||||||
|
"com.typesafe.akka" %% "akka-actor" % "2.3.10",
|
||||||
|
"com.enragedginger" %% "akka-quartz-scheduler" % "1.3.0-akka-2.3.x"
|
||||||
),
|
),
|
||||||
|
play.twirl.sbt.Import.TwirlKeys.templateImports += "gitbucket.core._",
|
||||||
EclipseKeys.withSource := true,
|
EclipseKeys.withSource := true,
|
||||||
javacOptions in compile ++= Seq("-target", "7", "-source", "7"),
|
javacOptions in compile ++= Seq("-target", "7", "-source", "7"),
|
||||||
testOptions in Test += Tests.Argument(TestFrameworks.Specs2, "junitxml", "console"),
|
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")
|
packageOptions += Package.MainClass("JettyLauncher")
|
||||||
).enablePlugins(SbtTwirl)
|
).enablePlugins(SbtTwirl)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
|
scalacOptions ++= Seq("-unchecked", "-deprecation", "-feature")
|
||||||
|
|
||||||
addSbtPlugin("com.typesafe.sbteclipse" % "sbteclipse-plugin" % "2.4.0")
|
addSbtPlugin("com.typesafe.sbteclipse" % "sbteclipse-plugin" % "2.4.0")
|
||||||
|
addSbtPlugin("com.github.mpeltonen" % "sbt-idea" % "1.6.0")
|
||||||
addSbtPlugin("com.github.mpeltonen" % "sbt-idea" % "1.6.0")
|
addSbtPlugin("org.scalatra.sbt" % "scalatra-sbt" % "0.3.5")
|
||||||
|
addSbtPlugin("com.typesafe.sbt" % "sbt-twirl" % "1.0.4")
|
||||||
addSbtPlugin("org.scalatra.sbt" % "scalatra-sbt" % "0.3.5")
|
addSbtPlugin("com.timushev.sbt" % "sbt-updates" % "0.1.8")
|
||||||
|
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.12.0")
|
||||||
addSbtPlugin("com.typesafe.sbt" % "sbt-twirl" % "1.0.2")
|
|
||||||
|
|
||||||
addSbtPlugin("com.timushev.sbt" % "sbt-updates" % "0.1.4")
|
|
||||||
|
|||||||
Binary file not shown.
BIN
sbt-launch-0.13.8.jar
Normal file
BIN
sbt-launch-0.13.8.jar
Normal file
Binary file not shown.
2
sbt.bat
2
sbt.bat
@@ -1,2 +1,2 @@
|
|||||||
set SCRIPT_DIR=%~dp0
|
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 -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
2
sbt.sh
@@ -1,2 +1,2 @@
|
|||||||
#!/bin/sh
|
#!/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 -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 "$@"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package util;
|
package gitbucket.core.util;
|
||||||
|
|
||||||
import org.eclipse.jgit.api.errors.PatchApplyException;
|
import org.eclipse.jgit.api.errors.PatchApplyException;
|
||||||
import org.eclipse.jgit.diff.RawText;
|
import org.eclipse.jgit.diff.RawText;
|
||||||
6
src/main/resources/database.conf
Normal file
6
src/main/resources/database.conf
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
db {
|
||||||
|
driver = "org.h2.Driver"
|
||||||
|
url = "jdbc:h2:${DatabaseHome};MVCC=true"
|
||||||
|
user = "sa"
|
||||||
|
password = "sa"
|
||||||
|
}
|
||||||
42
src/main/resources/update/3_1.sql
Normal file
42
src/main/resources/update/3_1.sql
Normal 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;
|
||||||
@@ -1,9 +1,14 @@
|
|||||||
import _root_.servlet.{BasicAuthenticationFilter, TransactionFilter}
|
|
||||||
import app._
|
import gitbucket.core.controller._
|
||||||
//import jp.sf.amateras.scalatra.forms.ValidationJavaScriptProvider
|
import gitbucket.core.plugin.PluginRegistry
|
||||||
import org.scalatra._
|
import gitbucket.core.servlet.{AccessTokenAuthenticationFilter, BasicAuthenticationFilter, Database, TransactionFilter}
|
||||||
import javax.servlet._
|
import gitbucket.core.util.Directory
|
||||||
|
|
||||||
import java.util.EnumSet
|
import java.util.EnumSet
|
||||||
|
import javax.servlet._
|
||||||
|
|
||||||
|
import org.scalatra._
|
||||||
|
|
||||||
|
|
||||||
class ScalatraBootstrap extends LifeCycle {
|
class ScalatraBootstrap extends LifeCycle {
|
||||||
override def init(context: ServletContext) {
|
override def init(context: ServletContext) {
|
||||||
@@ -12,9 +17,15 @@ class ScalatraBootstrap extends LifeCycle {
|
|||||||
context.getFilterRegistration("transactionFilter").addMappingForUrlPatterns(EnumSet.allOf(classOf[DispatcherType]), true, "/*")
|
context.getFilterRegistration("transactionFilter").addMappingForUrlPatterns(EnumSet.allOf(classOf[DispatcherType]), true, "/*")
|
||||||
context.addFilter("basicAuthenticationFilter", new BasicAuthenticationFilter)
|
context.addFilter("basicAuthenticationFilter", new BasicAuthenticationFilter)
|
||||||
context.getFilterRegistration("basicAuthenticationFilter").addMappingForUrlPatterns(EnumSet.allOf(classOf[DispatcherType]), true, "/git/*")
|
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
|
// Register controllers
|
||||||
context.mount(new AnonymousAccessController, "/*")
|
context.mount(new AnonymousAccessController, "/*")
|
||||||
|
|
||||||
|
PluginRegistry().getControllers.foreach { case (controller, path) =>
|
||||||
|
context.mount(controller, path)
|
||||||
|
}
|
||||||
|
|
||||||
context.mount(new IndexController, "/")
|
context.mount(new IndexController, "/")
|
||||||
context.mount(new SearchController, "/")
|
context.mount(new SearchController, "/")
|
||||||
context.mount(new FileUploadController, "/upload")
|
context.mount(new FileUploadController, "/upload")
|
||||||
@@ -31,9 +42,13 @@ class ScalatraBootstrap extends LifeCycle {
|
|||||||
context.mount(new RepositorySettingsController, "/*")
|
context.mount(new RepositorySettingsController, "/*")
|
||||||
|
|
||||||
// Create GITBUCKET_HOME directory if it does not exist
|
// 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){
|
if(!dir.exists){
|
||||||
dir.mkdirs()
|
dir.mkdirs()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
override def destroy(context: ServletContext): Unit = {
|
||||||
|
Database.closeDataSource()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
26
src/main/scala/gitbucket/core/api/ApiComment.scala
Normal file
26
src/main/scala/gitbucket/core/api/ApiComment.scala
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
package gitbucket.core.api
|
||||||
|
|
||||||
|
import gitbucket.core.model.IssueComment
|
||||||
|
|
||||||
|
import java.util.Date
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* https://developer.github.com/v3/issues/comments/
|
||||||
|
*/
|
||||||
|
case class ApiComment(
|
||||||
|
id: Int,
|
||||||
|
user: ApiUser,
|
||||||
|
body: String,
|
||||||
|
created_at: Date,
|
||||||
|
updated_at: Date)
|
||||||
|
|
||||||
|
object ApiComment{
|
||||||
|
def apply(comment: IssueComment, user: ApiUser): ApiComment =
|
||||||
|
ApiComment(
|
||||||
|
id = comment.commentId,
|
||||||
|
user = user,
|
||||||
|
body = comment.content,
|
||||||
|
created_at = comment.registeredDate,
|
||||||
|
updated_at = comment.updatedDate)
|
||||||
|
}
|
||||||
48
src/main/scala/gitbucket/core/api/ApiCommit.scala
Normal file
48
src/main/scala/gitbucket/core/api/ApiCommit.scala
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
package gitbucket.core.api
|
||||||
|
|
||||||
|
import gitbucket.core.util.JGitUtil
|
||||||
|
import gitbucket.core.util.JGitUtil.CommitInfo
|
||||||
|
import gitbucket.core.util.RepositoryName
|
||||||
|
|
||||||
|
import org.eclipse.jgit.diff.DiffEntry
|
||||||
|
import org.eclipse.jgit.api.Git
|
||||||
|
|
||||||
|
import java.util.Date
|
||||||
|
|
||||||
|
/**
|
||||||
|
* https://developer.github.com/v3/repos/commits/
|
||||||
|
*/
|
||||||
|
case class ApiCommit(
|
||||||
|
id: String,
|
||||||
|
message: String,
|
||||||
|
timestamp: Date,
|
||||||
|
added: List[String],
|
||||||
|
removed: List[String],
|
||||||
|
modified: List[String],
|
||||||
|
author: ApiPersonIdent,
|
||||||
|
committer: ApiPersonIdent)(repositoryName:RepositoryName){
|
||||||
|
val url = ApiPath(s"/api/v3/${repositoryName.fullName}/commits/${id}")
|
||||||
|
val html_url = ApiPath(s"/${repositoryName.fullName}/commit/${id}")
|
||||||
|
}
|
||||||
|
|
||||||
|
object ApiCommit{
|
||||||
|
def apply(git: Git, repositoryName: RepositoryName, commit: CommitInfo): ApiCommit = {
|
||||||
|
val diffs = JGitUtil.getDiffs(git, commit.id, false)
|
||||||
|
ApiCommit(
|
||||||
|
id = commit.id,
|
||||||
|
message = commit.fullMessage,
|
||||||
|
timestamp = commit.commitTime,
|
||||||
|
added = diffs._1.collect {
|
||||||
|
case x if x.changeType == DiffEntry.ChangeType.ADD => x.newPath
|
||||||
|
},
|
||||||
|
removed = diffs._1.collect {
|
||||||
|
case x if x.changeType == DiffEntry.ChangeType.DELETE => x.oldPath
|
||||||
|
},
|
||||||
|
modified = diffs._1.collect {
|
||||||
|
case x if x.changeType != DiffEntry.ChangeType.ADD && x.changeType != DiffEntry.ChangeType.DELETE => x.newPath
|
||||||
|
},
|
||||||
|
author = ApiPersonIdent.author(commit),
|
||||||
|
committer = ApiPersonIdent.committer(commit)
|
||||||
|
)(repositoryName)
|
||||||
|
}
|
||||||
|
}
|
||||||
42
src/main/scala/gitbucket/core/api/ApiCommitListItem.scala
Normal file
42
src/main/scala/gitbucket/core/api/ApiCommitListItem.scala
Normal 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}")
|
||||||
|
}
|
||||||
|
}
|
||||||
38
src/main/scala/gitbucket/core/api/ApiCommitStatus.scala
Normal file
38
src/main/scala/gitbucket/core/api/ApiCommitStatus.scala
Normal 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))
|
||||||
|
}
|
||||||
5
src/main/scala/gitbucket/core/api/ApiError.scala
Normal file
5
src/main/scala/gitbucket/core/api/ApiError.scala
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
package gitbucket.core.api
|
||||||
|
|
||||||
|
case class ApiError(
|
||||||
|
message: String,
|
||||||
|
documentation_url: Option[String] = None)
|
||||||
31
src/main/scala/gitbucket/core/api/ApiIssue.scala
Normal file
31
src/main/scala/gitbucket/core/api/ApiIssue.scala
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
package gitbucket.core.api
|
||||||
|
|
||||||
|
import gitbucket.core.model.Issue
|
||||||
|
|
||||||
|
import java.util.Date
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* https://developer.github.com/v3/issues/
|
||||||
|
*/
|
||||||
|
case class ApiIssue(
|
||||||
|
number: Int,
|
||||||
|
title: String,
|
||||||
|
user: ApiUser,
|
||||||
|
// labels,
|
||||||
|
state: String,
|
||||||
|
created_at: Date,
|
||||||
|
updated_at: Date,
|
||||||
|
body: String)
|
||||||
|
|
||||||
|
object ApiIssue{
|
||||||
|
def apply(issue: Issue, user: ApiUser): ApiIssue =
|
||||||
|
ApiIssue(
|
||||||
|
number = issue.issueId,
|
||||||
|
title = issue.title,
|
||||||
|
user = user,
|
||||||
|
state = if(issue.closed){ "closed" }else{ "open" },
|
||||||
|
body = issue.content.getOrElse(""),
|
||||||
|
created_at = issue.registeredDate,
|
||||||
|
updated_at = issue.updatedDate)
|
||||||
|
}
|
||||||
6
src/main/scala/gitbucket/core/api/ApiPath.scala
Normal file
6
src/main/scala/gitbucket/core/api/ApiPath.scala
Normal 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)
|
||||||
25
src/main/scala/gitbucket/core/api/ApiPersonIdent.scala
Normal file
25
src/main/scala/gitbucket/core/api/ApiPersonIdent.scala
Normal 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)
|
||||||
|
}
|
||||||
59
src/main/scala/gitbucket/core/api/ApiPullRequest.scala
Normal file
59
src/main/scala/gitbucket/core/api/ApiPullRequest.scala
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
48
src/main/scala/gitbucket/core/api/ApiRepository.scala
Normal file
48
src/main/scala/gitbucket/core/api/ApiRepository.scala
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
package gitbucket.core.api
|
||||||
|
|
||||||
|
import gitbucket.core.model.{Account, Repository}
|
||||||
|
import gitbucket.core.service.RepositoryService.RepositoryInfo
|
||||||
|
|
||||||
|
|
||||||
|
// https://developer.github.com/v3/repos/
|
||||||
|
case class ApiRepository(
|
||||||
|
name: String,
|
||||||
|
full_name: String,
|
||||||
|
description: String,
|
||||||
|
watchers: Int,
|
||||||
|
forks: Int,
|
||||||
|
`private`: Boolean,
|
||||||
|
default_branch: String,
|
||||||
|
owner: ApiUser) {
|
||||||
|
val forks_count = forks
|
||||||
|
val watchers_coun = watchers
|
||||||
|
val url = ApiPath(s"/api/v3/repos/${full_name}")
|
||||||
|
val http_url = ApiPath(s"/git/${full_name}.git")
|
||||||
|
val clone_url = ApiPath(s"/git/${full_name}.git")
|
||||||
|
val html_url = ApiPath(s"/${full_name}")
|
||||||
|
}
|
||||||
|
|
||||||
|
object ApiRepository{
|
||||||
|
def apply(
|
||||||
|
repository: Repository,
|
||||||
|
owner: ApiUser,
|
||||||
|
forkedCount: Int =0,
|
||||||
|
watchers: Int = 0): ApiRepository =
|
||||||
|
ApiRepository(
|
||||||
|
name = repository.repositoryName,
|
||||||
|
full_name = s"${repository.userName}/${repository.repositoryName}",
|
||||||
|
description = repository.description.getOrElse(""),
|
||||||
|
watchers = 0,
|
||||||
|
forks = forkedCount,
|
||||||
|
`private` = repository.isPrivate,
|
||||||
|
default_branch = repository.defaultBranch,
|
||||||
|
owner = owner
|
||||||
|
)
|
||||||
|
|
||||||
|
def apply(repositoryInfo: RepositoryInfo, owner: ApiUser): ApiRepository =
|
||||||
|
ApiRepository(repositoryInfo.repository, owner, forkedCount=repositoryInfo.forkedCount)
|
||||||
|
|
||||||
|
def apply(repositoryInfo: RepositoryInfo, owner: Account): ApiRepository =
|
||||||
|
this(repositoryInfo.repository, ApiUser(owner))
|
||||||
|
|
||||||
|
}
|
||||||
36
src/main/scala/gitbucket/core/api/ApiUser.scala
Normal file
36
src/main/scala/gitbucket/core/api/ApiUser.scala
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
package gitbucket.core.api
|
||||||
|
|
||||||
|
import gitbucket.core.model.Account
|
||||||
|
|
||||||
|
import java.util.Date
|
||||||
|
|
||||||
|
|
||||||
|
case class ApiUser(
|
||||||
|
login: String,
|
||||||
|
email: String,
|
||||||
|
`type`: String,
|
||||||
|
site_admin: Boolean,
|
||||||
|
created_at: Date) {
|
||||||
|
val url = ApiPath(s"/api/v3/users/${login}")
|
||||||
|
val html_url = ApiPath(s"/${login}")
|
||||||
|
// val followers_url = ApiPath(s"/api/v3/users/${login}/followers")
|
||||||
|
// val following_url = ApiPath(s"/api/v3/users/${login}/following{/other_user}")
|
||||||
|
// val gists_url = ApiPath(s"/api/v3/users/${login}/gists{/gist_id}")
|
||||||
|
// val starred_url = ApiPath(s"/api/v3/users/${login}/starred{/owner}{/repo}")
|
||||||
|
// val subscriptions_url = ApiPath(s"/api/v3/users/${login}/subscriptions")
|
||||||
|
// val organizations_url = ApiPath(s"/api/v3/users/${login}/orgs")
|
||||||
|
// val repos_url = ApiPath(s"/api/v3/users/${login}/repos")
|
||||||
|
// val events_url = ApiPath(s"/api/v3/users/${login}/events{/privacy}")
|
||||||
|
// val received_events_url = ApiPath(s"/api/v3/users/${login}/received_events")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
object ApiUser{
|
||||||
|
def apply(user: Account): ApiUser = ApiUser(
|
||||||
|
login = user.fullName,
|
||||||
|
email = user.mailAddress,
|
||||||
|
`type` = if(user.isGroupAccount){ "Organization" }else{ "User" },
|
||||||
|
site_admin = user.isAdmin,
|
||||||
|
created_at = user.registeredDate
|
||||||
|
)
|
||||||
|
}
|
||||||
7
src/main/scala/gitbucket/core/api/CreateAComment.scala
Normal file
7
src/main/scala/gitbucket/core/api/CreateAComment.scala
Normal 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)
|
||||||
26
src/main/scala/gitbucket/core/api/CreateAStatus.scala
Normal file
26
src/main/scala/gitbucket/core/api/CreateAStatus.scala
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
44
src/main/scala/gitbucket/core/api/JsonFormat.scala
Normal file
44
src/main/scala/gitbucket/core/api/JsonFormat.scala
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
package gitbucket.core.api
|
||||||
|
|
||||||
|
import org.joda.time.DateTime
|
||||||
|
import org.joda.time.DateTimeZone
|
||||||
|
import org.joda.time.format._
|
||||||
|
import org.json4s._
|
||||||
|
import org.json4s.jackson.Serialization
|
||||||
|
|
||||||
|
import java.util.Date
|
||||||
|
|
||||||
|
import scala.util.Try
|
||||||
|
|
||||||
|
|
||||||
|
object JsonFormat {
|
||||||
|
case class Context(baseUrl:String)
|
||||||
|
val parserISO = DateTimeFormat.forPattern("yyyy-MM-dd'T'HH:mm:ss'Z'")
|
||||||
|
val jsonFormats = Serialization.formats(NoTypeHints) + new CustomSerializer[Date](format =>
|
||||||
|
(
|
||||||
|
{ case JString(s) => Try(parserISO.parseDateTime(s)).toOption.map(_.toDate)
|
||||||
|
.getOrElse(throw new MappingException("Can't convert " + s + " to Date")) },
|
||||||
|
{ case x: Date => JString(parserISO.print(new DateTime(x).withZone(DateTimeZone.UTC))) }
|
||||||
|
)
|
||||||
|
) + FieldSerializer[ApiUser]() + FieldSerializer[ApiPullRequest]() + FieldSerializer[ApiRepository]() +
|
||||||
|
FieldSerializer[ApiCommitListItem.Parent]() + FieldSerializer[ApiCommitListItem]() + FieldSerializer[ApiCommitListItem.Commit]() +
|
||||||
|
FieldSerializer[ApiCommitStatus]() + FieldSerializer[ApiCommit]() + FieldSerializer[ApiCombinedCommitStatus]() +
|
||||||
|
FieldSerializer[ApiPullRequest.Commit]()
|
||||||
|
|
||||||
|
|
||||||
|
def apiPathSerializer(c: Context) = new CustomSerializer[ApiPath](format =>
|
||||||
|
(
|
||||||
|
{
|
||||||
|
case JString(s) if s.startsWith(c.baseUrl) => ApiPath(s.substring(c.baseUrl.length))
|
||||||
|
case JString(s) => throw new MappingException("Can't convert " + s + " to ApiPath")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
case ApiPath(path) => JString(c.baseUrl+path)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
/**
|
||||||
|
* convert object to json string
|
||||||
|
*/
|
||||||
|
def apply(obj: AnyRef)(implicit c: Context): String = Serialization.write(obj)(jsonFormats + apiPathSerializer(c))
|
||||||
|
}
|
||||||
@@ -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 jp.sf.amateras.scalatra.forms._
|
||||||
import org.apache.commons.io.FileUtils
|
import org.apache.commons.io.FileUtils
|
||||||
import org.scalatra.i18n.Messages
|
|
||||||
import org.eclipse.jgit.api.Git
|
import org.eclipse.jgit.api.Git
|
||||||
import org.eclipse.jgit.lib.{FileMode, Constants}
|
|
||||||
import org.eclipse.jgit.dircache.DirCache
|
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
|
class AccountController extends AccountControllerBase
|
||||||
with AccountService with RepositoryService with ActivityService with WikiService with LabelsService with SshKeyService
|
with 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
|
||||||
|
|
||||||
|
|
||||||
trait AccountControllerBase extends AccountManagementControllerBase {
|
trait AccountControllerBase extends AccountManagementControllerBase {
|
||||||
self: AccountService with RepositoryService with ActivityService with WikiService with LabelsService with SshKeyService
|
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,
|
case class AccountNewForm(userName: String, password: String, fullName: String, mailAddress: String,
|
||||||
url: Option[String], fileId: Option[String])
|
url: Option[String], fileId: Option[String])
|
||||||
@@ -31,6 +39,8 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
|||||||
|
|
||||||
case class SshKeyForm(title: String, publicKey: String)
|
case class SshKeyForm(title: String, publicKey: String)
|
||||||
|
|
||||||
|
case class PersonalTokenForm(note: String)
|
||||||
|
|
||||||
val newForm = mapping(
|
val newForm = mapping(
|
||||||
"userName" -> trim(label("User name" , text(required, maxlength(100), identifier, uniqueUserName))),
|
"userName" -> trim(label("User name" , text(required, maxlength(100), identifier, uniqueUserName))),
|
||||||
"password" -> trim(label("Password" , text(required, maxlength(20)))),
|
"password" -> trim(label("Password" , text(required, maxlength(20)))),
|
||||||
@@ -54,6 +64,10 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
|||||||
"publicKey" -> trim(label("Key" , text(required, validPublicKey)))
|
"publicKey" -> trim(label("Key" , text(required, validPublicKey)))
|
||||||
)(SshKeyForm.apply)
|
)(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 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)
|
case class EditGroupForm(groupName: String, url: Option[String], fileId: Option[String], members: String, clearImage: Boolean)
|
||||||
|
|
||||||
@@ -103,21 +117,21 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
|||||||
params.getOrElse("tab", "repositories") match {
|
params.getOrElse("tab", "repositories") match {
|
||||||
// Public Activity
|
// Public Activity
|
||||||
case "activity" =>
|
case "activity" =>
|
||||||
_root_.account.html.activity(account,
|
gitbucket.core.account.html.activity(account,
|
||||||
if(account.isGroupAccount) Nil else getGroupsByUserName(userName),
|
if(account.isGroupAccount) Nil else getGroupsByUserName(userName),
|
||||||
getActivitiesByUser(userName, true))
|
getActivitiesByUser(userName, true))
|
||||||
|
|
||||||
// Members
|
// Members
|
||||||
case "members" if(account.isGroupAccount) => {
|
case "members" if(account.isGroupAccount) => {
|
||||||
val members = getGroupMembers(account.userName)
|
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 }))
|
context.loginAccount.exists(x => members.exists { member => member.userName == x.userName && member.isManager }))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Repositories
|
// Repositories
|
||||||
case _ => {
|
case _ => {
|
||||||
val members = getGroupMembers(account.userName)
|
val members = getGroupMembers(account.userName)
|
||||||
_root_.account.html.repositories(account,
|
gitbucket.core.account.html.repositories(account,
|
||||||
if(account.isGroupAccount) Nil else getGroupsByUserName(userName),
|
if(account.isGroupAccount) Nil else getGroupsByUserName(userName),
|
||||||
getVisibleRepositories(context.loginAccount, context.baseUrl, Some(userName)),
|
getVisibleRepositories(context.loginAccount, context.baseUrl, Some(userName)),
|
||||||
context.loginAccount.exists(x => members.exists { member => member.userName == x.userName && member.isManager }))
|
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 {
|
get("/:userName/_edit")(oneselfOnly {
|
||||||
val userName = params("userName")
|
val userName = params("userName")
|
||||||
getAccountByUserName(userName).map { x =>
|
getAccountByUserName(userName).map { x =>
|
||||||
account.html.edit(x, flash.get("info"))
|
html.edit(x, flash.get("info"))
|
||||||
} getOrElse NotFound
|
} getOrElse NotFound
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -189,7 +222,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
|||||||
get("/:userName/_ssh")(oneselfOnly {
|
get("/:userName/_ssh")(oneselfOnly {
|
||||||
val userName = params("userName")
|
val userName = params("userName")
|
||||||
getAccountByUserName(userName).map { x =>
|
getAccountByUserName(userName).map { x =>
|
||||||
account.html.ssh(x, getPublicKeys(x.userName))
|
html.ssh(x, getPublicKeys(x.userName))
|
||||||
} getOrElse NotFound
|
} getOrElse NotFound
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -206,12 +239,46 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
|||||||
redirect(s"/${userName}/_ssh")
|
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"){
|
get("/register"){
|
||||||
if(context.settings.allowAccountRegistration){
|
if(context.settings.allowAccountRegistration){
|
||||||
if(context.loginAccount.isDefined){
|
if(context.loginAccount.isDefined){
|
||||||
redirect("/")
|
redirect("/")
|
||||||
} else {
|
} else {
|
||||||
account.html.register()
|
html.register()
|
||||||
}
|
}
|
||||||
} else NotFound
|
} else NotFound
|
||||||
}
|
}
|
||||||
@@ -225,7 +292,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get("/groups/new")(usersOnly {
|
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 =>
|
post("/groups/new", newGroupForm)(usersOnly { form =>
|
||||||
@@ -241,7 +308,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
|||||||
|
|
||||||
get("/:groupName/_editgroup")(managersOnly {
|
get("/:groupName/_editgroup")(managersOnly {
|
||||||
defining(params("groupName")){ groupName =>
|
defining(params("groupName")){ groupName =>
|
||||||
account.html.group(getAccountByUserName(groupName, true), getGroupMembers(groupName))
|
html.group(getAccountByUserName(groupName, true), getGroupMembers(groupName))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -290,7 +357,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
|||||||
* Show the new repository form.
|
* Show the new repository form.
|
||||||
*/
|
*/
|
||||||
get("/new")(usersOnly {
|
get("/new")(usersOnly {
|
||||||
account.html.newrepo(getGroupsByUserName(context.loginAccount.get.userName), context.settings.isCreateRepoOptionPublic)
|
html.newrepo(getGroupsByUserName(context.loginAccount.get.userName), context.settings.isCreateRepoOptionPublic)
|
||||||
})
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -366,7 +433,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
|||||||
val members = getGroupMembers(group)
|
val members = getGroupMembers(group)
|
||||||
context.loginAccount.exists(x => members.exists { member => member.userName == x.userName && member.isManager })
|
context.loginAccount.exists(x => members.exists { member => member.userName == x.userName && member.isManager })
|
||||||
}
|
}
|
||||||
_root_.helper.html.forkrepository(
|
helper.html.forkrepository(
|
||||||
repository,
|
repository,
|
||||||
(groups zip managerPermissions).toMap
|
(groups zip managerPermissions).toMap
|
||||||
)
|
)
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package app
|
package gitbucket.core.controller
|
||||||
|
|
||||||
class AnonymousAccessController extends AnonymousAccessControllerBase
|
class AnonymousAccessController extends AnonymousAccessControllerBase
|
||||||
|
|
||||||
@@ -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 jp.sf.amateras.scalatra.forms._
|
||||||
import org.apache.commons.io.FileUtils
|
import org.apache.commons.io.FileUtils
|
||||||
import model._
|
import org.json4s._
|
||||||
import service.{SystemSettingsService, AccountService}
|
import org.scalatra._
|
||||||
|
import org.scalatra.i18n._
|
||||||
|
import org.scalatra.json._
|
||||||
|
|
||||||
import javax.servlet.http.{HttpServletResponse, HttpServletRequest}
|
import javax.servlet.http.{HttpServletResponse, HttpServletRequest}
|
||||||
import javax.servlet.{FilterChain, ServletResponse, ServletRequest}
|
import javax.servlet.{FilterChain, ServletResponse, ServletRequest}
|
||||||
import org.scalatra.i18n._
|
|
||||||
|
import scala.util.Try
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides generic features for controller implementations.
|
* Provides generic features for controller implementations.
|
||||||
@@ -51,6 +57,9 @@ abstract class ControllerBase extends ScalatraFilter
|
|||||||
// Git repository
|
// Git repository
|
||||||
chain.doFilter(request, response)
|
chain.doFilter(request, response)
|
||||||
} else {
|
} else {
|
||||||
|
if(path.startsWith("/api/v3/")){
|
||||||
|
httpRequest.setAttribute(Keys.Request.APIv3, true)
|
||||||
|
}
|
||||||
// Scalatra actions
|
// Scalatra actions
|
||||||
super.doFilter(request, response, chain)
|
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 =
|
def ajaxGet(path : String)(action : => Any) : Route =
|
||||||
super.get(path){
|
super.get(path){
|
||||||
@@ -103,13 +112,19 @@ abstract class ControllerBase extends ScalatraFilter
|
|||||||
protected def NotFound() =
|
protected def NotFound() =
|
||||||
if(request.hasAttribute(Keys.Request.Ajax)){
|
if(request.hasAttribute(Keys.Request.Ajax)){
|
||||||
org.scalatra.NotFound()
|
org.scalatra.NotFound()
|
||||||
|
} else if(request.hasAttribute(Keys.Request.APIv3)){
|
||||||
|
contentType = formats("json")
|
||||||
|
org.scalatra.NotFound(ApiError("Not Found"))
|
||||||
} else {
|
} 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)){
|
if(request.hasAttribute(Keys.Request.Ajax)){
|
||||||
org.scalatra.Unauthorized()
|
org.scalatra.Unauthorized()
|
||||||
|
} else if(request.hasAttribute(Keys.Request.APIv3)){
|
||||||
|
contentType = formats("json")
|
||||||
|
org.scalatra.Unauthorized(ApiError("Requires authentication"))
|
||||||
} else {
|
} else {
|
||||||
if(context.loginAccount.isDefined){
|
if(context.loginAccount.isDefined){
|
||||||
org.scalatra.Unauthorized(redirect("/"))
|
org.scalatra.Unauthorized(redirect("/"))
|
||||||
@@ -146,6 +161,15 @@ abstract class ControllerBase extends ScalatraFilter
|
|||||||
response.addHeader("X-Content-Type-Options", "nosniff")
|
response.addHeader("X-Content-Type-Options", "nosniff")
|
||||||
rawData
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1,9 +1,10 @@
|
|||||||
package app
|
package gitbucket.core.controller
|
||||||
|
|
||||||
import service._
|
import gitbucket.core.dashboard.html
|
||||||
import util.{StringUtil, UsersAuthenticator, Keys}
|
import gitbucket.core.service.{RepositoryService, PullRequestService, AccountService, IssuesService}
|
||||||
import util.Implicits._
|
import gitbucket.core.util.{StringUtil, Keys, UsersAuthenticator}
|
||||||
import service.IssuesService.IssueSearchCondition
|
import gitbucket.core.util.Implicits._
|
||||||
|
import gitbucket.core.service.IssuesService._
|
||||||
|
|
||||||
class DashboardController extends DashboardControllerBase
|
class DashboardController extends DashboardControllerBase
|
||||||
with IssuesService with PullRequestService with RepositoryService with AccountService
|
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 userRepos = getUserRepositories(userName, context.baseUrl, true).map(repo => repo.owner -> repo.name)
|
||||||
val page = IssueSearchCondition.page(request)
|
val page = IssueSearchCondition.page(request)
|
||||||
|
|
||||||
dashboard.html.issues(
|
html.issues(
|
||||||
searchIssue(condition, false, (page - 1) * IssueLimit, IssueLimit, userRepos: _*),
|
searchIssue(condition, false, (page - 1) * IssueLimit, IssueLimit, userRepos: _*),
|
||||||
page,
|
page,
|
||||||
countIssue(condition.copy(state = "open" ), false, userRepos: _*),
|
countIssue(condition.copy(state = "open" ), false, userRepos: _*),
|
||||||
@@ -119,7 +120,7 @@ trait DashboardControllerBase extends ControllerBase {
|
|||||||
val allRepos = getAllRepositories(userName)
|
val allRepos = getAllRepositories(userName)
|
||||||
val page = IssueSearchCondition.page(request)
|
val page = IssueSearchCondition.page(request)
|
||||||
|
|
||||||
dashboard.html.pulls(
|
html.pulls(
|
||||||
searchIssue(condition, true, (page - 1) * PullRequestLimit, PullRequestLimit, allRepos: _*),
|
searchIssue(condition, true, (page - 1) * PullRequestLimit, PullRequestLimit, allRepos: _*),
|
||||||
page,
|
page,
|
||||||
countIssue(condition.copy(state = "open" ), true, allRepos: _*),
|
countIssue(condition.copy(state = "open" ), true, allRepos: _*),
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
package app
|
package gitbucket.core.controller
|
||||||
|
|
||||||
import util.{Keys, FileUtil}
|
import gitbucket.core.util.{Keys, FileUtil}
|
||||||
import util.ControlUtil._
|
import gitbucket.core.util.ControlUtil._
|
||||||
import util.Directory._
|
import gitbucket.core.util.Directory._
|
||||||
import org.scalatra._
|
import org.scalatra._
|
||||||
import org.scalatra.servlet.{MultipartConfig, FileUploadSupport, FileItem}
|
import org.scalatra.servlet.{MultipartConfig, FileUploadSupport, FileItem}
|
||||||
import org.apache.commons.io.FileUtils
|
import org.apache.commons.io.FileUtils
|
||||||
@@ -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._
|
import jp.sf.amateras.scalatra.forms._
|
||||||
|
|
||||||
|
|
||||||
class IndexController extends IndexControllerBase
|
class IndexController extends IndexControllerBase
|
||||||
with RepositoryService with ActivityService with AccountService with UsersAuthenticator
|
with RepositoryService with ActivityService with AccountService with UsersAuthenticator
|
||||||
|
|
||||||
|
|
||||||
trait IndexControllerBase extends ControllerBase {
|
trait IndexControllerBase extends ControllerBase {
|
||||||
self: RepositoryService with ActivityService with AccountService with UsersAuthenticator =>
|
self: RepositoryService with ActivityService with AccountService with UsersAuthenticator =>
|
||||||
|
|
||||||
@@ -61,13 +68,13 @@ trait IndexControllerBase extends ControllerBase {
|
|||||||
|
|
||||||
get("/activities.atom"){
|
get("/activities.atom"){
|
||||||
contentType = "application/atom+xml; type=feed"
|
contentType = "application/atom+xml; type=feed"
|
||||||
helper.xml.feed(getRecentActivities())
|
xml.feed(getRecentActivities())
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set account information into HttpSession and redirect.
|
* Set account information into HttpSession and redirect.
|
||||||
*/
|
*/
|
||||||
private def signin(account: model.Account) = {
|
private def signin(account: Account) = {
|
||||||
session.setAttribute(Keys.Session.LoginAccount, account)
|
session.setAttribute(Keys.Session.LoginAccount, account)
|
||||||
updateLastLoginDate(account.userName)
|
updateLastLoginDate(account.userName)
|
||||||
|
|
||||||
@@ -92,7 +99,7 @@ trait IndexControllerBase extends ControllerBase {
|
|||||||
get("/_user/proposals")(usersOnly {
|
get("/_user/proposals")(usersOnly {
|
||||||
contentType = formats("json")
|
contentType = formats("json")
|
||||||
org.json4s.jackson.Serialization.write(
|
org.json4s.jackson.Serialization.write(
|
||||||
Map("options" -> getAllUsers().filter(!_.isGroupAccount).map(_.userName).toArray)
|
Map("options" -> getAllUsers(false).filter(!_.isGroupAccount).map(_.userName).toArray)
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -103,4 +110,13 @@ trait IndexControllerBase extends ControllerBase {
|
|||||||
getAccountByUserName(params("userName")).isDefined
|
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."))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -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 jp.sf.amateras.scalatra.forms._
|
||||||
|
|
||||||
import service._
|
|
||||||
import IssuesService._
|
|
||||||
import util._
|
|
||||||
import util.Implicits._
|
|
||||||
import util.ControlUtil._
|
|
||||||
import org.scalatra.Ok
|
import org.scalatra.Ok
|
||||||
import model.Issue
|
|
||||||
|
|
||||||
class IssuesController extends IssuesControllerBase
|
class IssuesController extends IssuesControllerBase
|
||||||
with IssuesService with RepositoryService with AccountService with LabelsService with MilestonesService with ActivityService
|
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 {
|
trait IssuesControllerBase extends ControllerBase {
|
||||||
self: IssuesService with RepositoryService with AccountService with LabelsService with MilestonesService with ActivityService
|
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],
|
case class IssueCreateForm(title: String, content: Option[String],
|
||||||
assignedUserName: Option[String], milestoneId: Option[Int], labelNames: 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 =>
|
get("/:owner/:repository/issues/:id")(referrersOnly { repository =>
|
||||||
defining(repository.owner, repository.name, params("id")){ case (owner, name, issueId) =>
|
defining(repository.owner, repository.name, params("id")){ case (owner, name, issueId) =>
|
||||||
getIssue(owner, name, issueId) map {
|
getIssue(owner, name, issueId) map {
|
||||||
issues.html.issue(
|
html.issue(
|
||||||
_,
|
_,
|
||||||
getComments(owner, name, issueId.toInt),
|
getComments(owner, name, issueId.toInt),
|
||||||
getIssueLabels(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) => ApiComment(issueComment, ApiUser(user)) })
|
||||||
|
}).getOrElse(NotFound)
|
||||||
|
})
|
||||||
|
|
||||||
get("/:owner/:repository/issues/new")(readableUsersOnly { repository =>
|
get("/:owner/:repository/issues/new")(readableUsersOnly { repository =>
|
||||||
defining(repository.owner, repository.name){ case (owner, name) =>
|
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,
|
(getCollaborators(owner, name) ::: (if(getAccountByUserName(owner).get.isGroupAccount) Nil else List(owner))).sorted,
|
||||||
getMilestones(owner, name),
|
getMilestones(owner, name),
|
||||||
getLabels(owner, name),
|
getLabels(owner, name),
|
||||||
@@ -109,14 +126,17 @@ trait IssuesControllerBase extends ControllerBase {
|
|||||||
// record activity
|
// record activity
|
||||||
recordCreateIssueActivity(owner, name, userName, issueId, form.title)
|
recordCreateIssueActivity(owner, name, userName, issueId, form.title)
|
||||||
|
|
||||||
// extract references and create refer comment
|
|
||||||
getIssue(owner, name, issueId.toString).foreach { issue =>
|
getIssue(owner, name, issueId.toString).foreach { issue =>
|
||||||
|
// extract references and create refer comment
|
||||||
createReferComment(owner, name, issue, form.title + " " + form.content.getOrElse(""))
|
createReferComment(owner, name, issue, form.title + " " + form.content.getOrElse(""))
|
||||||
}
|
|
||||||
|
|
||||||
// notifications
|
// call web hooks
|
||||||
Notifier().toNotify(repository, issueId, form.content.getOrElse("")){
|
callIssuesWebHook("opened", repository, issue, context.baseUrl, context.loginAccount.get)
|
||||||
Notifier.msgIssue(s"${context.baseUrl}/${owner}/${name}/issues/${issueId}")
|
|
||||||
|
// notifications
|
||||||
|
Notifier().toNotify(repository, issue, form.content.getOrElse("")){
|
||||||
|
Notifier.msgIssue(s"${context.baseUrl}/${owner}/${name}/issues/${issueId}")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
redirect(s"/${owner}/${name}/issues/${issueId}")
|
redirect(s"/${owner}/${name}/issues/${issueId}")
|
||||||
@@ -160,6 +180,20 @@ trait IssuesControllerBase extends ControllerBase {
|
|||||||
} getOrElse NotFound
|
} getOrElse NotFound
|
||||||
})
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* https://developer.github.com/v3/issues/comments/#create-a-comment
|
||||||
|
*/
|
||||||
|
post("/api/v3/repos/:owner/:repository/issues/:id/comments")(readableUsersOnly { repository =>
|
||||||
|
(for{
|
||||||
|
issueId <- params("id").toIntOpt
|
||||||
|
body <- extractFromJsonBody[CreateAComment].map(_.body) if ! body.isEmpty
|
||||||
|
(issue, id) <- handleComment(issueId, Some(body), repository)()
|
||||||
|
issueComment <- getComment(repository.owner, repository.name, id.toString())
|
||||||
|
} yield {
|
||||||
|
JsonFormat(ApiComment(issueComment, ApiUser(context.loginAccount.get)))
|
||||||
|
}) getOrElse NotFound
|
||||||
|
})
|
||||||
|
|
||||||
post("/:owner/:repository/issue_comments/state", issueStateForm)(readableUsersOnly { (form, repository) =>
|
post("/:owner/:repository/issue_comments/state", issueStateForm)(readableUsersOnly { (form, repository) =>
|
||||||
handleComment(form.issueId, form.content, repository)() map { case (issue, id) =>
|
handleComment(form.issueId, form.content, repository)() map { case (issue, id) =>
|
||||||
redirect(s"/${repository.owner}/${repository.name}/${
|
redirect(s"/${repository.owner}/${repository.name}/${
|
||||||
@@ -192,13 +226,13 @@ trait IssuesControllerBase extends ControllerBase {
|
|||||||
getIssue(repository.owner, repository.name, params("id")) map { x =>
|
getIssue(repository.owner, repository.name, params("id")) map { x =>
|
||||||
if(isEditable(x.userName, x.repositoryName, x.openedUserName)){
|
if(isEditable(x.userName, x.repositoryName, x.openedUserName)){
|
||||||
params.get("dataType") collect {
|
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)
|
x.content, x.issueId, x.userName, x.repositoryName)
|
||||||
} getOrElse {
|
} getOrElse {
|
||||||
contentType = formats("json")
|
contentType = formats("json")
|
||||||
org.json4s.jackson.Serialization.write(
|
org.json4s.jackson.Serialization.write(
|
||||||
Map("title" -> x.title,
|
Map("title" -> x.title,
|
||||||
"content" -> view.Markdown.toHtml(x.content getOrElse "No description given.",
|
"content" -> Markdown.toHtml(x.content getOrElse "No description given.",
|
||||||
repository, false, true, true, isEditable(x.userName, x.repositoryName, x.openedUserName))
|
repository, false, true, true, isEditable(x.userName, x.repositoryName, x.openedUserName))
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
@@ -210,7 +244,7 @@ trait IssuesControllerBase extends ControllerBase {
|
|||||||
getComment(repository.owner, repository.name, params("id")) map { x =>
|
getComment(repository.owner, repository.name, params("id")) map { x =>
|
||||||
if(isEditable(x.userName, x.repositoryName, x.commentedUserName)){
|
if(isEditable(x.userName, x.repositoryName, x.commentedUserName)){
|
||||||
params.get("dataType") collect {
|
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)
|
x.content, x.commentId, x.userName, x.repositoryName)
|
||||||
} getOrElse {
|
} getOrElse {
|
||||||
contentType = formats("json")
|
contentType = formats("json")
|
||||||
@@ -226,14 +260,14 @@ trait IssuesControllerBase extends ControllerBase {
|
|||||||
ajaxPost("/:owner/:repository/issues/:id/label/new")(collaboratorsOnly { repository =>
|
ajaxPost("/:owner/:repository/issues/:id/label/new")(collaboratorsOnly { repository =>
|
||||||
defining(params("id").toInt){ issueId =>
|
defining(params("id").toInt){ issueId =>
|
||||||
registerIssueLabel(repository.owner, repository.name, issueId, params("labelId").toInt)
|
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 =>
|
ajaxPost("/:owner/:repository/issues/:id/label/delete")(collaboratorsOnly { repository =>
|
||||||
defining(params("id").toInt){ issueId =>
|
defining(params("id").toInt){ issueId =>
|
||||||
deleteIssueLabel(repository.owner, repository.name, issueId, params("labelId").toInt)
|
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 +281,7 @@ trait IssuesControllerBase extends ControllerBase {
|
|||||||
milestoneId("milestoneId").map { milestoneId =>
|
milestoneId("milestoneId").map { milestoneId =>
|
||||||
getMilestonesWithIssueCount(repository.owner, repository.name)
|
getMilestonesWithIssueCount(repository.owner, repository.name)
|
||||||
.find(_._1.milestoneId == milestoneId).map { case (_, openCount, closeCount) =>
|
.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 NotFound
|
||||||
} getOrElse Ok()
|
} getOrElse Ok()
|
||||||
})
|
})
|
||||||
@@ -301,7 +335,7 @@ trait IssuesControllerBase extends ControllerBase {
|
|||||||
val assignedUserName = (key: String) => params.get(key) filter (_.trim != "")
|
val assignedUserName = (key: String) => params.get(key) filter (_.trim != "")
|
||||||
val milestoneId: String => Option[Int] = (key: String) => params.get(key).flatMap(_.toIntOpt)
|
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
|
hasWritePermission(owner, repository, context.loginAccount) || author == context.loginAccount.get.userName
|
||||||
|
|
||||||
private def executeBatch(repository: RepositoryService.RepositoryInfo)(execute: Int => Unit) = {
|
private def executeBatch(repository: RepositoryService.RepositoryInfo)(execute: Int => Unit) = {
|
||||||
@@ -325,13 +359,13 @@ trait IssuesControllerBase extends ControllerBase {
|
|||||||
* @see [[https://github.com/takezoe/gitbucket/wiki/CommentAction]]
|
* @see [[https://github.com/takezoe/gitbucket/wiki/CommentAction]]
|
||||||
*/
|
*/
|
||||||
private def handleComment(issueId: Int, content: Option[String], repository: RepositoryService.RepositoryInfo)
|
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))) = {
|
p1 => params.get("action").filter(_ => isEditable(p1.userName, p1.repositoryName, p1.openedUserName))) = {
|
||||||
|
|
||||||
defining(repository.owner, repository.name){ case (owner, name) =>
|
defining(repository.owner, repository.name){ case (owner, name) =>
|
||||||
val userName = context.loginAccount.get.userName
|
val userName = context.loginAccount.get.userName
|
||||||
|
|
||||||
getIssue(owner, name, issueId.toString) map { issue =>
|
getIssue(owner, name, issueId.toString) flatMap { issue =>
|
||||||
val (action, recordActivity) =
|
val (action, recordActivity) =
|
||||||
getAction(issue)
|
getAction(issue)
|
||||||
.collect {
|
.collect {
|
||||||
@@ -346,11 +380,10 @@ trait IssuesControllerBase extends ControllerBase {
|
|||||||
}
|
}
|
||||||
.getOrElse(None -> None)
|
.getOrElse(None -> None)
|
||||||
|
|
||||||
val commentId = content
|
val commentId = (content, action) match {
|
||||||
.map ( _ -> action.map( _ + "_comment" ).getOrElse("comment") )
|
case (None, None) => None
|
||||||
.getOrElse ( action.get.capitalize -> action.get )
|
case (None, Some(action)) => Some(createComment(owner, name, userName, issueId, action.capitalize, action))
|
||||||
match {
|
case (Some(content), _) => Some(createComment(owner, name, userName, issueId, content, action.map(_+ "_comment").getOrElse("comment")))
|
||||||
case (content, action) => createComment(owner, name, userName, issueId, content, action)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// record comment activity if comment is entered
|
// record comment activity if comment is entered
|
||||||
@@ -365,23 +398,39 @@ trait IssuesControllerBase extends ControllerBase {
|
|||||||
createReferComment(owner, name, issue, content)
|
createReferComment(owner, name, issue, content)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// call web hooks
|
||||||
|
action match {
|
||||||
|
case None => commentId.map{ commentIdSome => callIssueCommentWebHook(repository, issue, commentIdSome, context.loginAccount.get) }
|
||||||
|
case Some(act) => val webHookAction = act match {
|
||||||
|
case "open" => "opened"
|
||||||
|
case "reopen" => "reopened"
|
||||||
|
case "close" => "closed"
|
||||||
|
case _ => act
|
||||||
|
}
|
||||||
|
if(issue.isPullRequest){
|
||||||
|
callPullRequestWebHook(webHookAction, repository, issue.issueId, context.baseUrl, context.loginAccount.get)
|
||||||
|
} else {
|
||||||
|
callIssuesWebHook(webHookAction, repository, issue, context.baseUrl, context.loginAccount.get)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// notifications
|
// notifications
|
||||||
Notifier() match {
|
Notifier() match {
|
||||||
case f =>
|
case f =>
|
||||||
content foreach {
|
content foreach {
|
||||||
f.toNotify(repository, issueId, _){
|
f.toNotify(repository, issue, _){
|
||||||
Notifier.msgComment(s"${context.baseUrl}/${owner}/${name}/${
|
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 {
|
action foreach {
|
||||||
f.toNotify(repository, issueId, _){
|
f.toNotify(repository, issue, _){
|
||||||
Notifier.msgStatus(s"${context.baseUrl}/${owner}/${name}/issues/${issueId}")
|
Notifier.msgStatus(s"${context.baseUrl}/${owner}/${name}/issues/${issueId}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
issue -> commentId
|
commentId.map( issue -> _ )
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -395,7 +444,7 @@ trait IssuesControllerBase extends ControllerBase {
|
|||||||
val condition = session.putAndGet(sessionKey,
|
val condition = session.putAndGet(sessionKey,
|
||||||
if(request.hasQueryString){
|
if(request.hasQueryString){
|
||||||
val q = request.getParameter("q")
|
val q = request.getParameter("q")
|
||||||
if(q == null){
|
if(q == null || q.trim.isEmpty){
|
||||||
IssueSearchCondition(request)
|
IssueSearchCondition(request)
|
||||||
} else {
|
} else {
|
||||||
IssueSearchCondition(q, getMilestones(owner, repoName).map(x => (x.title, x.milestoneId)).toMap)
|
IssueSearchCondition(q, getMilestones(owner, repoName).map(x => (x.title, x.milestoneId)).toMap)
|
||||||
@@ -403,7 +452,7 @@ trait IssuesControllerBase extends ControllerBase {
|
|||||||
} else session.getAs[IssueSearchCondition](sessionKey).getOrElse(IssueSearchCondition())
|
} else session.getAs[IssueSearchCondition](sessionKey).getOrElse(IssueSearchCondition())
|
||||||
)
|
)
|
||||||
|
|
||||||
issues.html.list(
|
html.list(
|
||||||
"issues",
|
"issues",
|
||||||
searchIssue(condition, false, (page - 1) * IssueLimit, IssueLimit, owner -> repoName),
|
searchIssue(condition, false, (page - 1) * IssueLimit, IssueLimit, owner -> repoName),
|
||||||
page,
|
page,
|
||||||
@@ -417,5 +466,4 @@ trait IssuesControllerBase extends ControllerBase {
|
|||||||
hasWritePermission(owner, repoName, context.loginAccount))
|
hasWritePermission(owner, repoName, context.loginAccount))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -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 jp.sf.amateras.scalatra.forms._
|
||||||
import service._
|
|
||||||
import util.{ReferrerAuthenticator, CollaboratorsAuthenticator}
|
|
||||||
import util.Implicits._
|
|
||||||
import org.scalatra.i18n.Messages
|
import org.scalatra.i18n.Messages
|
||||||
import org.scalatra.Ok
|
import org.scalatra.Ok
|
||||||
|
|
||||||
@@ -23,7 +24,7 @@ trait LabelsControllerBase extends ControllerBase {
|
|||||||
)(LabelForm.apply)
|
)(LabelForm.apply)
|
||||||
|
|
||||||
get("/:owner/:repository/issues/labels")(referrersOnly { repository =>
|
get("/:owner/:repository/issues/labels")(referrersOnly { repository =>
|
||||||
issues.labels.html.list(
|
html.list(
|
||||||
getLabels(repository.owner, repository.name),
|
getLabels(repository.owner, repository.name),
|
||||||
countIssueGroupByLabels(repository.owner, repository.name, IssuesService.IssueSearchCondition(), Map.empty),
|
countIssueGroupByLabels(repository.owner, repository.name, IssuesService.IssueSearchCondition(), Map.empty),
|
||||||
repository,
|
repository,
|
||||||
@@ -31,12 +32,12 @@ trait LabelsControllerBase extends ControllerBase {
|
|||||||
})
|
})
|
||||||
|
|
||||||
ajaxGet("/:owner/:repository/issues/labels/new")(collaboratorsOnly { repository =>
|
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) =>
|
ajaxPost("/:owner/:repository/issues/labels/new", labelForm)(collaboratorsOnly { (form, repository) =>
|
||||||
val labelId = createLabel(repository.owner, repository.name, form.labelName, form.color.substring(1))
|
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,
|
getLabel(repository.owner, repository.name, labelId).get,
|
||||||
// TODO futility
|
// TODO futility
|
||||||
countIssueGroupByLabels(repository.owner, repository.name, IssuesService.IssueSearchCondition(), Map.empty),
|
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 =>
|
ajaxGet("/:owner/:repository/issues/labels/:labelId/edit")(collaboratorsOnly { repository =>
|
||||||
getLabel(repository.owner, repository.name, params("labelId").toInt).map { label =>
|
getLabel(repository.owner, repository.name, params("labelId").toInt).map { label =>
|
||||||
issues.labels.html.edit(Some(label), repository)
|
html.edit(Some(label), repository)
|
||||||
} getOrElse NotFound()
|
} getOrElse NotFound()
|
||||||
})
|
})
|
||||||
|
|
||||||
ajaxPost("/:owner/:repository/issues/labels/:labelId/edit", labelForm)(collaboratorsOnly { (form, repository) =>
|
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))
|
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,
|
getLabel(repository.owner, repository.name, params("labelId").toInt).get,
|
||||||
// TODO futility
|
// TODO futility
|
||||||
countIssueGroupByLabels(repository.owner, repository.name, IssuesService.IssueSearchCondition(), Map.empty),
|
countIssueGroupByLabels(repository.owner, repository.name, IssuesService.IssueSearchCondition(), Map.empty),
|
||||||
@@ -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 jp.sf.amateras.scalatra.forms._
|
||||||
|
|
||||||
import service._
|
|
||||||
import util.{CollaboratorsAuthenticator, ReferrerAuthenticator}
|
|
||||||
import util.Implicits._
|
|
||||||
|
|
||||||
class MilestonesController extends MilestonesControllerBase
|
class MilestonesController extends MilestonesControllerBase
|
||||||
with MilestonesService with RepositoryService with AccountService
|
with MilestonesService with RepositoryService with AccountService
|
||||||
with ReferrerAuthenticator with CollaboratorsAuthenticator
|
with ReferrerAuthenticator with CollaboratorsAuthenticator
|
||||||
@@ -23,7 +23,7 @@ trait MilestonesControllerBase extends ControllerBase {
|
|||||||
)(MilestoneForm.apply)
|
)(MilestoneForm.apply)
|
||||||
|
|
||||||
get("/:owner/:repository/issues/milestones")(referrersOnly { repository =>
|
get("/:owner/:repository/issues/milestones")(referrersOnly { repository =>
|
||||||
issues.milestones.html.list(
|
html.list(
|
||||||
params.getOrElse("state", "open"),
|
params.getOrElse("state", "open"),
|
||||||
getMilestonesWithIssueCount(repository.owner, repository.name),
|
getMilestonesWithIssueCount(repository.owner, repository.name),
|
||||||
repository,
|
repository,
|
||||||
@@ -31,7 +31,7 @@ trait MilestonesControllerBase extends ControllerBase {
|
|||||||
})
|
})
|
||||||
|
|
||||||
get("/:owner/:repository/issues/milestones/new")(collaboratorsOnly {
|
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) =>
|
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 =>
|
get("/:owner/:repository/issues/milestones/:milestoneId/edit")(collaboratorsOnly { repository =>
|
||||||
params("milestoneId").toIntOpt.map{ milestoneId =>
|
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
|
} getOrElse NotFound
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -1,32 +1,39 @@
|
|||||||
package app
|
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 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 jp.sf.amateras.scalatra.forms._
|
||||||
import org.eclipse.jgit.transport.RefSpec
|
import org.eclipse.jgit.api.Git
|
||||||
import scala.collection.JavaConverters._
|
import org.eclipse.jgit.lib.PersonIdent
|
||||||
import org.eclipse.jgit.lib.{ObjectId, CommitBuilder, PersonIdent}
|
|
||||||
import service.IssuesService._
|
|
||||||
import service.PullRequestService._
|
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
import org.eclipse.jgit.merge.MergeStrategy
|
|
||||||
import org.eclipse.jgit.errors.NoMergeBaseException
|
import scala.collection.JavaConverters._
|
||||||
import service.WebHookService.WebHookPayload
|
|
||||||
import util.JGitUtil.DiffInfo
|
|
||||||
import util.JGitUtil.CommitInfo
|
|
||||||
|
|
||||||
|
|
||||||
class PullRequestsController extends PullRequestsControllerBase
|
class PullRequestsController extends PullRequestsControllerBase
|
||||||
with RepositoryService with AccountService with IssuesService with PullRequestService with MilestonesService with LabelsService
|
with RepositoryService with AccountService with IssuesService with PullRequestService with MilestonesService with LabelsService
|
||||||
with CommitsService with ActivityService with WebHookService with ReferrerAuthenticator with CollaboratorsAuthenticator
|
with CommitsService with ActivityService with WebHookPullRequestService with ReferrerAuthenticator with CollaboratorsAuthenticator
|
||||||
|
with CommitStatusService with MergeService
|
||||||
|
|
||||||
|
|
||||||
trait PullRequestsControllerBase extends ControllerBase {
|
trait PullRequestsControllerBase extends ControllerBase {
|
||||||
self: RepositoryService with AccountService with IssuesService with MilestonesService with LabelsService
|
self: RepositoryService with AccountService with IssuesService with MilestonesService with LabelsService
|
||||||
with CommitsService with ActivityService with PullRequestService with WebHookService with ReferrerAuthenticator with CollaboratorsAuthenticator =>
|
with CommitsService with ActivityService with PullRequestService with WebHookPullRequestService with ReferrerAuthenticator with CollaboratorsAuthenticator
|
||||||
|
with CommitStatusService with MergeService =>
|
||||||
|
|
||||||
private val logger = LoggerFactory.getLogger(classOf[PullRequestsControllerBase])
|
private val logger = LoggerFactory.getLogger(classOf[PullRequestsControllerBase])
|
||||||
|
|
||||||
@@ -68,6 +75,24 @@ trait PullRequestsControllerBase extends ControllerBase {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 =>
|
get("/:owner/:repository/pull/:id")(referrersOnly { repository =>
|
||||||
params("id").toIntOpt.flatMap{ issueId =>
|
params("id").toIntOpt.flatMap{ issueId =>
|
||||||
val owner = repository.owner
|
val owner = repository.owner
|
||||||
@@ -76,8 +101,7 @@ trait PullRequestsControllerBase extends ControllerBase {
|
|||||||
using(Git.open(getRepositoryDir(owner, name))){ git =>
|
using(Git.open(getRepositoryDir(owner, name))){ git =>
|
||||||
val (commits, diffs) =
|
val (commits, diffs) =
|
||||||
getRequestCompareInfo(owner, name, pullreq.commitIdFrom, owner, name, pullreq.commitIdTo)
|
getRequestCompareInfo(owner, name, pullreq.commitIdFrom, owner, name, pullreq.commitIdTo)
|
||||||
|
html.pullreq(
|
||||||
pulls.html.pullreq(
|
|
||||||
issue, pullreq,
|
issue, pullreq,
|
||||||
(commits.flatten.map(commit => getCommitComments(owner, name, commit.id, true)).flatten.toList ::: getComments(owner, name, issueId))
|
(commits.flatten.map(commit => getCommitComments(owner, name, commit.id, true)).flatten.toList ::: getComments(owner, name, issueId))
|
||||||
.sortWith((a, b) => a.registeredDate before b.registeredDate),
|
.sortWith((a, b) => a.registeredDate before b.registeredDate),
|
||||||
@@ -94,14 +118,64 @@ trait PullRequestsControllerBase extends ControllerBase {
|
|||||||
} getOrElse NotFound
|
} 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 =>
|
ajaxGet("/:owner/:repository/pull/:id/mergeguide")(collaboratorsOnly { repository =>
|
||||||
params("id").toIntOpt.flatMap{ issueId =>
|
params("id").toIntOpt.flatMap{ issueId =>
|
||||||
val owner = repository.owner
|
val owner = repository.owner
|
||||||
val name = repository.name
|
val name = repository.name
|
||||||
getPullRequest(owner, name, issueId) map { case(issue, pullreq) =>
|
getPullRequest(owner, name, issueId) map { case(issue, pullreq) =>
|
||||||
pulls.html.mergeguide(
|
val statuses = getCommitStatues(owner, name, pullreq.commitIdTo)
|
||||||
checkConflictInPullRequest(owner, name, pullreq.branch, pullreq.requestUserName, name, pullreq.requestBranch, issueId),
|
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,
|
pullreq,
|
||||||
|
statuses,
|
||||||
|
repository,
|
||||||
s"${context.baseUrl}/git/${pullreq.requestUserName}/${pullreq.requestRepositoryName}.git")
|
s"${context.baseUrl}/git/${pullreq.requestUserName}/${pullreq.requestRepositoryName}.git")
|
||||||
}
|
}
|
||||||
} getOrElse NotFound
|
} getOrElse NotFound
|
||||||
@@ -138,43 +212,10 @@ trait PullRequestsControllerBase extends ControllerBase {
|
|||||||
// record activity
|
// record activity
|
||||||
recordMergeActivity(owner, name, loginAccount.userName, issueId, form.message)
|
recordMergeActivity(owner, name, loginAccount.userName, issueId, form.message)
|
||||||
|
|
||||||
// merge
|
// merge git repository
|
||||||
val mergeBaseRefName = s"refs/heads/${pullreq.branch}"
|
mergePullRequest(git, pullreq.branch, issueId,
|
||||||
val merger = MergeStrategy.RECURSIVE.newMerger(git.getRepository, true)
|
s"Merge pull request #${issueId} from ${pullreq.requestUserName}/${pullreq.requestBranch}\n\n" + form.message,
|
||||||
val mergeBaseTip = git.getRepository.resolve(mergeBaseRefName)
|
new PersonIdent(loginAccount.fullName, loginAccount.mailAddress))
|
||||||
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,
|
val (commits, _) = getRequestCompareInfo(owner, name, pullreq.commitIdFrom,
|
||||||
pullreq.requestUserName, pullreq.requestRepositoryName, pullreq.commitIdTo)
|
pullreq.requestUserName, pullreq.requestRepositoryName, pullreq.commitIdTo)
|
||||||
@@ -192,17 +233,10 @@ trait PullRequestsControllerBase extends ControllerBase {
|
|||||||
closeIssuesFromMessage(form.message, loginAccount.userName, owner, name)
|
closeIssuesFromMessage(form.message, loginAccount.userName, owner, name)
|
||||||
}
|
}
|
||||||
// call web hook
|
// call web hook
|
||||||
getWebHookURLs(owner, name) match {
|
callPullRequestWebHook("closed", repository, issueId, context.baseUrl, context.loginAccount.get)
|
||||||
case webHookURLs if(webHookURLs.nonEmpty) =>
|
|
||||||
for(ownerAccount <- getAccountByUserName(owner)){
|
|
||||||
callWebHook(owner, name, webHookURLs,
|
|
||||||
WebHookPayload(git, loginAccount, mergeBaseRefName, repository, commits.flatten.toList, ownerAccount))
|
|
||||||
}
|
|
||||||
case _ =>
|
|
||||||
}
|
|
||||||
|
|
||||||
// notifications
|
// notifications
|
||||||
Notifier().toNotify(repository, issueId, "merge"){
|
Notifier().toNotify(repository, issue, "merge"){
|
||||||
Notifier.msgStatus(s"${context.baseUrl}/${owner}/${name}/pull/${issueId}")
|
Notifier.msgStatus(s"${context.baseUrl}/${owner}/${name}/pull/${issueId}")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -214,6 +248,7 @@ trait PullRequestsControllerBase extends ControllerBase {
|
|||||||
})
|
})
|
||||||
|
|
||||||
get("/:owner/:repository/compare")(referrersOnly { forkedRepository =>
|
get("/:owner/:repository/compare")(referrersOnly { forkedRepository =>
|
||||||
|
val headBranch:Option[String] = params.get("head")
|
||||||
(forkedRepository.repository.originUserName, forkedRepository.repository.originRepositoryName) match {
|
(forkedRepository.repository.originUserName, forkedRepository.repository.originRepositoryName) match {
|
||||||
case (Some(originUserName), Some(originRepositoryName)) => {
|
case (Some(originUserName), Some(originRepositoryName)) => {
|
||||||
getRepository(originUserName, originRepositoryName, context.baseUrl).map { originRepository =>
|
getRepository(originUserName, originRepositoryName, context.baseUrl).map { originRepository =>
|
||||||
@@ -221,8 +256,8 @@ trait PullRequestsControllerBase extends ControllerBase {
|
|||||||
Git.open(getRepositoryDir(originUserName, originRepositoryName)),
|
Git.open(getRepositoryDir(originUserName, originRepositoryName)),
|
||||||
Git.open(getRepositoryDir(forkedRepository.owner, forkedRepository.name))
|
Git.open(getRepositoryDir(forkedRepository.owner, forkedRepository.name))
|
||||||
){ (oldGit, newGit) =>
|
){ (oldGit, newGit) =>
|
||||||
val oldBranch = JGitUtil.getDefaultBranch(oldGit, originRepository).get._2
|
val newBranch = headBranch.getOrElse(JGitUtil.getDefaultBranch(newGit, forkedRepository).get._2)
|
||||||
val newBranch = 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}")
|
redirect(s"/${forkedRepository.owner}/${forkedRepository.name}/compare/${originUserName}:${oldBranch}...${newBranch}")
|
||||||
}
|
}
|
||||||
@@ -231,7 +266,7 @@ trait PullRequestsControllerBase extends ControllerBase {
|
|||||||
case _ => {
|
case _ => {
|
||||||
using(Git.open(getRepositoryDir(forkedRepository.owner, forkedRepository.name))){ git =>
|
using(Git.open(getRepositoryDir(forkedRepository.owner, forkedRepository.name))){ git =>
|
||||||
JGitUtil.getDefaultBranch(git, forkedRepository).map { case (_, defaultBranch) =>
|
JGitUtil.getDefaultBranch(git, forkedRepository).map { case (_, defaultBranch) =>
|
||||||
redirect(s"/${forkedRepository.owner}/${forkedRepository.name}/compare/${defaultBranch}...${defaultBranch}")
|
redirect(s"/${forkedRepository.owner}/${forkedRepository.name}/compare/${defaultBranch}...${headBranch.getOrElse(defaultBranch)}")
|
||||||
} getOrElse {
|
} getOrElse {
|
||||||
redirect(s"/${forkedRepository.owner}/${forkedRepository.name}")
|
redirect(s"/${forkedRepository.owner}/${forkedRepository.name}")
|
||||||
}
|
}
|
||||||
@@ -242,8 +277,8 @@ trait PullRequestsControllerBase extends ControllerBase {
|
|||||||
|
|
||||||
get("/:owner/:repository/compare/*...*")(referrersOnly { forkedRepository =>
|
get("/:owner/:repository/compare/*...*")(referrersOnly { forkedRepository =>
|
||||||
val Seq(origin, forked) = multiParams("splat")
|
val Seq(origin, forked) = multiParams("splat")
|
||||||
val (originOwner, tmpOriginBranch) = parseCompareIdentifie(origin, forkedRepository.owner)
|
val (originOwner, originId) = parseCompareIdentifie(origin, forkedRepository.owner)
|
||||||
val (forkedOwner, tmpForkedBranch) = parseCompareIdentifie(forked, forkedRepository.owner)
|
val (forkedOwner, forkedId) = parseCompareIdentifie(forked, forkedRepository.owner)
|
||||||
|
|
||||||
(for(
|
(for(
|
||||||
originRepositoryName <- if(originOwner == forkedOwner){
|
originRepositoryName <- if(originOwner == forkedOwner){
|
||||||
@@ -259,21 +294,24 @@ trait PullRequestsControllerBase extends ControllerBase {
|
|||||||
Git.open(getRepositoryDir(originRepository.owner, originRepository.name)),
|
Git.open(getRepositoryDir(originRepository.owner, originRepository.name)),
|
||||||
Git.open(getRepositoryDir(forkedRepository.owner, forkedRepository.name))
|
Git.open(getRepositoryDir(forkedRepository.owner, forkedRepository.name))
|
||||||
){ case (oldGit, newGit) =>
|
){ case (oldGit, newGit) =>
|
||||||
val originBranch = JGitUtil.getDefaultBranch(oldGit, originRepository, tmpOriginBranch).get._2
|
val (oldId, newId) =
|
||||||
val forkedBranch = JGitUtil.getDefaultBranch(newGit, forkedRepository, tmpForkedBranch).get._2
|
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)
|
||||||
|
|
||||||
val forkedId = JGitUtil.getForkedCommitId(oldGit, newGit,
|
(oldGit.getRepository.resolve(rootId), newGit.getRepository.resolve(forkedId))
|
||||||
originRepository.owner, originRepository.name, originBranch,
|
} else {
|
||||||
forkedRepository.owner, forkedRepository.name, forkedBranch)
|
// Commit id
|
||||||
|
(oldGit.getRepository.resolve(originId), newGit.getRepository.resolve(forkedId))
|
||||||
val oldId = oldGit.getRepository.resolve(forkedId)
|
}
|
||||||
val newId = newGit.getRepository.resolve(forkedBranch)
|
|
||||||
|
|
||||||
val (commits, diffs) = getRequestCompareInfo(
|
val (commits, diffs) = getRequestCompareInfo(
|
||||||
originRepository.owner, originRepository.name, oldId.getName,
|
originRepository.owner, originRepository.name, oldId.getName,
|
||||||
forkedRepository.owner, forkedRepository.name, newId.getName)
|
forkedRepository.owner, forkedRepository.name, newId.getName)
|
||||||
|
|
||||||
pulls.html.compare(
|
html.compare(
|
||||||
commits,
|
commits,
|
||||||
diffs,
|
diffs,
|
||||||
(forkedRepository.repository.originUserName, forkedRepository.repository.originRepositoryName) match {
|
(forkedRepository.repository.originUserName, forkedRepository.repository.originRepositoryName) match {
|
||||||
@@ -281,8 +319,8 @@ trait PullRequestsControllerBase extends ControllerBase {
|
|||||||
case _ => (forkedRepository.owner, forkedRepository.name) :: getForkedRepositories(forkedRepository.owner, forkedRepository.name)
|
case _ => (forkedRepository.owner, forkedRepository.name) :: getForkedRepositories(forkedRepository.owner, forkedRepository.name)
|
||||||
},
|
},
|
||||||
commits.flatten.map(commit => getCommitComments(forkedRepository.owner, forkedRepository.name, commit.id, false)).flatten.toList,
|
commits.flatten.map(commit => getCommitComments(forkedRepository.owner, forkedRepository.name, commit.id, false)).flatten.toList,
|
||||||
originBranch,
|
originId,
|
||||||
forkedBranch,
|
forkedId,
|
||||||
oldId.getName,
|
oldId.getName,
|
||||||
newId.getName,
|
newId.getName,
|
||||||
forkedRepository,
|
forkedRepository,
|
||||||
@@ -314,10 +352,11 @@ trait PullRequestsControllerBase extends ControllerBase {
|
|||||||
){ case (oldGit, newGit) =>
|
){ case (oldGit, newGit) =>
|
||||||
val originBranch = JGitUtil.getDefaultBranch(oldGit, originRepository, tmpOriginBranch).get._2
|
val originBranch = JGitUtil.getDefaultBranch(oldGit, originRepository, tmpOriginBranch).get._2
|
||||||
val forkedBranch = JGitUtil.getDefaultBranch(newGit, forkedRepository, tmpForkedBranch).get._2
|
val forkedBranch = JGitUtil.getDefaultBranch(newGit, forkedRepository, tmpForkedBranch).get._2
|
||||||
|
val conflict = LockUtil.lock(s"${originRepository.owner}/${originRepository.name}"){
|
||||||
pulls.html.mergecheck(
|
|
||||||
checkConflict(originRepository.owner, originRepository.name, originBranch,
|
checkConflict(originRepository.owner, originRepository.name, originBranch,
|
||||||
forkedRepository.owner, forkedRepository.name, forkedBranch))
|
forkedRepository.owner, forkedRepository.name, forkedBranch)
|
||||||
|
}
|
||||||
|
html.mergecheck(conflict)
|
||||||
}
|
}
|
||||||
}) getOrElse NotFound
|
}) getOrElse NotFound
|
||||||
})
|
})
|
||||||
@@ -347,80 +386,24 @@ trait PullRequestsControllerBase extends ControllerBase {
|
|||||||
commitIdTo = form.commitIdTo)
|
commitIdTo = form.commitIdTo)
|
||||||
|
|
||||||
// fetch requested branch
|
// fetch requested branch
|
||||||
using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git =>
|
fetchAsPullRequest(repository.owner, repository.name, form.requestUserName, form.requestRepositoryName, form.requestBranch, issueId)
|
||||||
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
|
// record activity
|
||||||
recordPullRequestActivity(repository.owner, repository.name, loginUserName, issueId, form.title)
|
recordPullRequestActivity(repository.owner, repository.name, loginUserName, issueId, form.title)
|
||||||
|
|
||||||
|
// call web hook
|
||||||
|
callPullRequestWebHook("opened", repository, issueId, context.baseUrl, context.loginAccount.get)
|
||||||
|
|
||||||
// notifications
|
// notifications
|
||||||
Notifier().toNotify(repository, issueId, form.content.getOrElse("")){
|
getIssue(repository.owner, repository.name, issueId.toString) foreach { issue =>
|
||||||
Notifier.msgPullRequest(s"${context.baseUrl}/${repository.owner}/${repository.name}/pull/${issueId}")
|
Notifier().toNotify(repository, issue, form.content.getOrElse("")){
|
||||||
|
Notifier.msgPullRequest(s"${context.baseUrl}/${repository.owner}/${repository.name}/pull/${issueId}")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
redirect(s"/${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.
|
* Parses branch identifier and extracts owner and branch name as tuple.
|
||||||
*
|
*
|
||||||
@@ -447,7 +430,7 @@ trait PullRequestsControllerBase extends ControllerBase {
|
|||||||
val commits = newGit.log.addRange(oldId, newId).call.iterator.asScala.map { revCommit =>
|
val commits = newGit.log.addRange(oldId, newId).call.iterator.asScala.map { revCommit =>
|
||||||
new CommitInfo(revCommit)
|
new CommitInfo(revCommit)
|
||||||
}.toList.splitWith { (commit1, commit2) =>
|
}.toList.splitWith { (commit1, commit2) =>
|
||||||
view.helpers.date(commit1.commitTime) == view.helpers.date(commit2.commitTime)
|
helpers.date(commit1.commitTime) == view.helpers.date(commit2.commitTime)
|
||||||
}
|
}
|
||||||
|
|
||||||
val diffs = JGitUtil.getDiffs(newGit, oldId.getName, newId.getName, true)
|
val diffs = JGitUtil.getDiffs(newGit, oldId.getName, newId.getName, true)
|
||||||
@@ -466,7 +449,7 @@ trait PullRequestsControllerBase extends ControllerBase {
|
|||||||
else session.getAs[IssueSearchCondition](sessionKey).getOrElse(IssueSearchCondition())
|
else session.getAs[IssueSearchCondition](sessionKey).getOrElse(IssueSearchCondition())
|
||||||
)
|
)
|
||||||
|
|
||||||
issues.html.list(
|
gitbucket.core.issues.html.list(
|
||||||
"pulls",
|
"pulls",
|
||||||
searchIssue(condition, true, (page - 1) * PullRequestLimit, PullRequestLimit, owner -> repoName),
|
searchIssue(condition, true, (page - 1) * PullRequestLimit, PullRequestLimit, owner -> repoName),
|
||||||
page,
|
page,
|
||||||
@@ -479,5 +462,4 @@ trait PullRequestsControllerBase extends ControllerBase {
|
|||||||
repository,
|
repository,
|
||||||
hasWritePermission(owner, repoName, context.loginAccount))
|
hasWritePermission(owner, repoName, context.loginAccount))
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,18 +1,21 @@
|
|||||||
package app
|
package gitbucket.core.controller
|
||||||
|
|
||||||
import service._
|
import gitbucket.core.settings.html
|
||||||
import util.Directory._
|
import gitbucket.core.model.WebHook
|
||||||
import util.Implicits._
|
import gitbucket.core.service.{RepositoryService, AccountService, WebHookService}
|
||||||
import util.{LockUtil, UsersAuthenticator, OwnerAuthenticator}
|
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 jp.sf.amateras.scalatra.forms._
|
||||||
import org.apache.commons.io.FileUtils
|
import org.apache.commons.io.FileUtils
|
||||||
import org.scalatra.i18n.Messages
|
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.api.Git
|
||||||
import org.eclipse.jgit.lib.Constants
|
import org.eclipse.jgit.lib.Constants
|
||||||
|
|
||||||
|
|
||||||
class RepositorySettingsController extends RepositorySettingsControllerBase
|
class RepositorySettingsController extends RepositorySettingsControllerBase
|
||||||
with RepositoryService with AccountService with WebHookService
|
with RepositoryService with AccountService with WebHookService
|
||||||
with OwnerAuthenticator with UsersAuthenticator
|
with OwnerAuthenticator with UsersAuthenticator
|
||||||
@@ -63,7 +66,7 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
|||||||
* Display the Options page.
|
* Display the Options page.
|
||||||
*/
|
*/
|
||||||
get("/:owner/:repository/settings/options")(ownerOnly {
|
get("/:owner/:repository/settings/options")(ownerOnly {
|
||||||
settings.html.options(_, flash.get("info"))
|
html.options(_, flash.get("info"))
|
||||||
})
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -105,7 +108,7 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
|||||||
* Display the Collaborators page.
|
* Display the Collaborators page.
|
||||||
*/
|
*/
|
||||||
get("/:owner/:repository/settings/collaborators")(ownerOnly { repository =>
|
get("/:owner/:repository/settings/collaborators")(ownerOnly { repository =>
|
||||||
settings.html.collaborators(
|
html.collaborators(
|
||||||
getCollaborators(repository.owner, repository.name),
|
getCollaborators(repository.owner, repository.name),
|
||||||
getAccountByUserName(repository.owner).get.isGroupAccount,
|
getAccountByUserName(repository.owner).get.isGroupAccount,
|
||||||
repository)
|
repository)
|
||||||
@@ -135,7 +138,7 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
|||||||
* Display the web hook page.
|
* Display the web hook page.
|
||||||
*/
|
*/
|
||||||
get("/:owner/:repository/settings/hooks")(ownerOnly { repository =>
|
get("/:owner/:repository/settings/hooks")(ownerOnly { repository =>
|
||||||
settings.html.hooks(getWebHookURLs(repository.owner, repository.name), flash.get("url"), repository, flash.get("info"))
|
html.hooks(getWebHookURLs(repository.owner, repository.name), flash.get("url"), repository, flash.get("info"))
|
||||||
})
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -160,15 +163,15 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
|||||||
post("/:owner/:repository/settings/hooks/test", webHookForm)(ownerOnly { (form, repository) =>
|
post("/:owner/:repository/settings/hooks/test", webHookForm)(ownerOnly { (form, repository) =>
|
||||||
using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git =>
|
using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git =>
|
||||||
import scala.collection.JavaConverters._
|
import scala.collection.JavaConverters._
|
||||||
val commits = git.log
|
val commits = if(repository.commitCount == 0) List.empty else git.log
|
||||||
.add(git.getRepository.resolve(repository.repository.defaultBranch))
|
.add(git.getRepository.resolve(repository.repository.defaultBranch))
|
||||||
.setMaxCount(3)
|
.setMaxCount(3)
|
||||||
.call.iterator.asScala.map(new CommitInfo(_))
|
.call.iterator.asScala.map(new CommitInfo(_))
|
||||||
|
|
||||||
getAccountByUserName(repository.owner).foreach { ownerAccount =>
|
getAccountByUserName(repository.owner).foreach { ownerAccount =>
|
||||||
callWebHook(repository.owner, repository.name,
|
callWebHook("push",
|
||||||
List(model.WebHook(repository.owner, repository.name, form.url)),
|
List(WebHook(repository.owner, repository.name, form.url)),
|
||||||
WebHookPayload(git, ownerAccount, "refs/heads/" + repository.repository.defaultBranch, repository, commits.toList, ownerAccount)
|
WebHookPushPayload(git, ownerAccount, "refs/heads/" + repository.repository.defaultBranch, repository, commits.toList, ownerAccount)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
flash += "url" -> form.url
|
flash += "url" -> form.url
|
||||||
@@ -181,7 +184,7 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
|||||||
* Display the danger zone.
|
* Display the danger zone.
|
||||||
*/
|
*/
|
||||||
get("/:owner/:repository/settings/danger")(ownerOnly {
|
get("/:owner/:repository/settings/danger")(ownerOnly {
|
||||||
settings.html.danger(_)
|
html.danger(_)
|
||||||
})
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -271,4 +274,4 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,27 +1,36 @@
|
|||||||
package app
|
package gitbucket.core.controller
|
||||||
|
|
||||||
import _root_.util.JGitUtil.CommitInfo
|
import gitbucket.core.api._
|
||||||
import util.Directory._
|
import gitbucket.core.repo.html
|
||||||
import util.Implicits._
|
import gitbucket.core.helper
|
||||||
import _root_.util.ControlUtil._
|
import gitbucket.core.service._
|
||||||
import _root_.util._
|
import gitbucket.core.util._
|
||||||
import service._
|
import gitbucket.core.util.JGitUtil._
|
||||||
import org.scalatra._
|
import gitbucket.core.util.StringUtil._
|
||||||
import java.io.File
|
import gitbucket.core.util.ControlUtil._
|
||||||
|
import gitbucket.core.util.Implicits._
|
||||||
|
import gitbucket.core.util.Directory._
|
||||||
|
import gitbucket.core.model.{Account, CommitState}
|
||||||
|
import gitbucket.core.service.CommitStatusService
|
||||||
|
import gitbucket.core.service.WebHookService._
|
||||||
|
import gitbucket.core.view
|
||||||
|
import gitbucket.core.view.helpers
|
||||||
|
|
||||||
|
import jp.sf.amateras.scalatra.forms._
|
||||||
|
import org.apache.commons.io.FileUtils
|
||||||
import org.eclipse.jgit.api.{ArchiveCommand, Git}
|
import org.eclipse.jgit.api.{ArchiveCommand, Git}
|
||||||
import org.eclipse.jgit.archive.{TgzFormat, ZipFormat}
|
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.dircache.DirCache
|
||||||
|
import org.eclipse.jgit.lib._
|
||||||
import org.eclipse.jgit.revwalk.RevCommit
|
import org.eclipse.jgit.revwalk.RevCommit
|
||||||
import service.WebHookService.WebHookPayload
|
import org.eclipse.jgit.treewalk._
|
||||||
|
import org.scalatra._
|
||||||
|
|
||||||
|
|
||||||
class RepositoryViewerController extends RepositoryViewerControllerBase
|
class RepositoryViewerController extends RepositoryViewerControllerBase
|
||||||
with RepositoryService with AccountService with ActivityService with IssuesService with WebHookService with CommitsService
|
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
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -29,7 +38,8 @@ class RepositoryViewerController extends RepositoryViewerControllerBase
|
|||||||
*/
|
*/
|
||||||
trait RepositoryViewerControllerBase extends ControllerBase {
|
trait RepositoryViewerControllerBase extends ControllerBase {
|
||||||
self: RepositoryService with AccountService with ActivityService with IssuesService with WebHookService with CommitsService
|
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 =>
|
||||||
|
|
||||||
ArchiveCommand.registerFormat("zip", new ZipFormat)
|
ArchiveCommand.registerFormat("zip", new ZipFormat)
|
||||||
ArchiveCommand.registerFormat("tar.gz", new TgzFormat)
|
ArchiveCommand.registerFormat("tar.gz", new TgzFormat)
|
||||||
@@ -91,7 +101,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
|||||||
*/
|
*/
|
||||||
post("/:owner/:repository/_preview")(referrersOnly { repository =>
|
post("/:owner/:repository/_preview")(referrersOnly { repository =>
|
||||||
contentType = "text/html"
|
contentType = "text/html"
|
||||||
view.helpers.markdown(params("content"), repository,
|
helpers.markdown(params("content"), repository,
|
||||||
params("enableWikiLink").toBoolean,
|
params("enableWikiLink").toBoolean,
|
||||||
params("enableRefsLink").toBoolean,
|
params("enableRefsLink").toBoolean,
|
||||||
params("enableTaskList").toBoolean,
|
params("enableTaskList").toBoolean,
|
||||||
@@ -105,6 +115,13 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
|||||||
fileList(_)
|
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.
|
* Displays the file list of the specified path and branch.
|
||||||
*/
|
*/
|
||||||
@@ -127,7 +144,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
|||||||
using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git =>
|
using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git =>
|
||||||
JGitUtil.getCommitLog(git, branchName, page, 30, path) match {
|
JGitUtil.getCommitLog(git, branchName, page, 30, path) match {
|
||||||
case Right((logs, hasNext)) =>
|
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) =>
|
logs.splitWith{ (commit1, commit2) =>
|
||||||
view.helpers.date(commit1.commitTime) == view.helpers.date(commit2.commitTime)
|
view.helpers.date(commit1.commitTime) == view.helpers.date(commit2.commitTime)
|
||||||
}, page, hasNext, hasWritePermission(repository.owner, repository.name, context.loginAccount))
|
}, page, hasNext, hasWritePermission(repository.owner, repository.name, context.loginAccount))
|
||||||
@@ -136,9 +153,59 @@ 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.
|
||||||
|
*/
|
||||||
|
get("/api/v3/repos/:owner/:repo/commits/:ref/statuses")(referrersOnly { repository =>
|
||||||
|
(for{
|
||||||
|
ref <- params.get("ref")
|
||||||
|
sha <- JGitUtil.getShaByRef(repository.owner, repository.name, ref)
|
||||||
|
} yield {
|
||||||
|
JsonFormat(getCommitStatuesWithCreator(repository.owner, repository.name, sha).map{ case(status, creator) =>
|
||||||
|
ApiCommitStatus(status, ApiUser(creator))
|
||||||
|
})
|
||||||
|
}) getOrElse NotFound
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* https://developer.github.com/v3/repos/statuses/#get-the-combined-status-for-a-specific-ref
|
||||||
|
*
|
||||||
|
* ref is Ref to list the statuses from. It can be a SHA, a branch name, or a tag name.
|
||||||
|
*/
|
||||||
|
get("/api/v3/repos/:owner/:repo/commits/:ref/status")(referrersOnly { repository =>
|
||||||
|
(for{
|
||||||
|
ref <- params.get("ref")
|
||||||
|
owner <- getAccountByUserName(repository.owner)
|
||||||
|
sha <- JGitUtil.getShaByRef(repository.owner, repository.name, ref)
|
||||||
|
} yield {
|
||||||
|
val statuses = getCommitStatuesWithCreator(repository.owner, repository.name, sha)
|
||||||
|
JsonFormat(ApiCombinedCommitStatus(sha, statuses, ApiRepository(repository, owner)))
|
||||||
|
}) getOrElse NotFound
|
||||||
|
})
|
||||||
|
|
||||||
get("/:owner/:repository/new/*")(collaboratorsOnly { repository =>
|
get("/:owner/:repository/new/*")(collaboratorsOnly { repository =>
|
||||||
val (branch, path) = splitPath(repository, multiParams("splat").head)
|
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")))
|
None, JGitUtil.ContentInfo("text", None, Some("UTF-8")))
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -150,7 +217,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
|||||||
|
|
||||||
getPathObjectId(git, path, revCommit).map { objectId =>
|
getPathObjectId(git, path, revCommit).map { objectId =>
|
||||||
val paths = path.split("/")
|
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))
|
JGitUtil.getContentInfo(git, path, objectId))
|
||||||
} getOrElse NotFound
|
} getOrElse NotFound
|
||||||
}
|
}
|
||||||
@@ -163,16 +230,23 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
|||||||
|
|
||||||
getPathObjectId(git, path, revCommit).map { objectId =>
|
getPathObjectId(git, path, revCommit).map { objectId =>
|
||||||
val paths = path.split("/")
|
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))
|
JGitUtil.getContentInfo(git, path, objectId))
|
||||||
} getOrElse NotFound
|
} getOrElse NotFound
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
post("/:owner/:repository/create", editorForm)(collaboratorsOnly { (form, repository) =>
|
post("/:owner/:repository/create", editorForm)(collaboratorsOnly { (form, repository) =>
|
||||||
commitFile(repository, form.branch, form.path, Some(form.newFileName), None,
|
commitFile(
|
||||||
StringUtil.convertLineSeparator(form.content, form.lineSeparator), form.charset,
|
repository = repository,
|
||||||
form.message.getOrElse(s"Create ${form.newFileName}"))
|
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}/${
|
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) form.newFileName else s"${form.path}/${form.newFileName}"
|
||||||
@@ -180,13 +254,20 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
|||||||
})
|
})
|
||||||
|
|
||||||
post("/:owner/:repository/update", editorForm)(collaboratorsOnly { (form, repository) =>
|
post("/:owner/:repository/update", editorForm)(collaboratorsOnly { (form, repository) =>
|
||||||
commitFile(repository, form.branch, form.path, Some(form.newFileName), form.oldFileName,
|
commitFile(
|
||||||
StringUtil.convertLineSeparator(form.content, form.lineSeparator), form.charset,
|
repository = repository,
|
||||||
if(form.oldFileName.exists(_ == form.newFileName)){
|
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}")
|
form.message.getOrElse(s"Update ${form.newFileName}")
|
||||||
} else {
|
} else {
|
||||||
form.message.getOrElse(s"Rename ${form.oldFileName.get} to ${form.newFileName}")
|
form.message.getOrElse(s"Rename ${form.oldFileName.get} to ${form.newFileName}")
|
||||||
})
|
}
|
||||||
|
)
|
||||||
|
|
||||||
redirect(s"/${repository.owner}/${repository.name}/blob/${form.branch}/${
|
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) form.newFileName else s"${form.path}/${form.newFileName}"
|
||||||
@@ -213,11 +294,11 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
|||||||
getPathObjectId(git, path, revCommit).map { objectId =>
|
getPathObjectId(git, path, revCommit).map { objectId =>
|
||||||
if(raw){
|
if(raw){
|
||||||
// Download
|
// Download
|
||||||
defining(JGitUtil.getContentFromId(git, objectId, false).get){ bytes =>
|
JGitUtil.getContentFromId(git, objectId, true).map {bytes =>
|
||||||
RawData(FileUtil.getContentType(path, bytes), bytes)
|
RawData(FileUtil.getContentType(path, bytes), bytes)
|
||||||
}
|
} getOrElse NotFound
|
||||||
} else {
|
} else {
|
||||||
repo.html.blob(id, repository, path.split("/").toList, JGitUtil.getContentInfo(git, path, objectId),
|
html.blob(id, repository, path.split("/").toList, JGitUtil.getContentInfo(git, path, objectId),
|
||||||
new JGitUtil.CommitInfo(lastModifiedCommit), hasWritePermission(repository.owner, repository.name, context.loginAccount))
|
new JGitUtil.CommitInfo(lastModifiedCommit), hasWritePermission(repository.owner, repository.name, context.loginAccount))
|
||||||
}
|
}
|
||||||
} getOrElse NotFound
|
} getOrElse NotFound
|
||||||
@@ -233,7 +314,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
|||||||
using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git =>
|
using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git =>
|
||||||
defining(JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(id))){ revCommit =>
|
defining(JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(id))){ revCommit =>
|
||||||
JGitUtil.getDiffs(git, id) match { case (diffs, oldCommitId) =>
|
JGitUtil.getDiffs(git, id) match { case (diffs, oldCommitId) =>
|
||||||
repo.html.commit(id, new JGitUtil.CommitInfo(revCommit),
|
html.commit(id, new JGitUtil.CommitInfo(revCommit),
|
||||||
JGitUtil.getBranchesOfCommit(git, revCommit.getName),
|
JGitUtil.getBranchesOfCommit(git, revCommit.getName),
|
||||||
JGitUtil.getTagsOfCommit(git, revCommit.getName),
|
JGitUtil.getTagsOfCommit(git, revCommit.getName),
|
||||||
getCommitComments(repository.owner, repository.name, id, false),
|
getCommitComments(repository.owner, repository.name, id, false),
|
||||||
@@ -260,7 +341,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
|||||||
val oldLineNumber = params.get("oldLineNumber") map (_.toInt)
|
val oldLineNumber = params.get("oldLineNumber") map (_.toInt)
|
||||||
val newLineNumber = params.get("newLineNumber") map (_.toInt)
|
val newLineNumber = params.get("newLineNumber") map (_.toInt)
|
||||||
val issueId = params.get("issueId") map (_.toInt)
|
val issueId = params.get("issueId") map (_.toInt)
|
||||||
repo.html.commentform(
|
html.commentform(
|
||||||
commitId = id,
|
commitId = id,
|
||||||
fileName, oldLineNumber, newLineNumber, issueId,
|
fileName, oldLineNumber, newLineNumber, issueId,
|
||||||
hasWritePermission = hasWritePermission(repository.owner, repository.name, context.loginAccount),
|
hasWritePermission = hasWritePermission(repository.owner, repository.name, context.loginAccount),
|
||||||
@@ -284,7 +365,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
|||||||
getCommitComment(repository.owner, repository.name, params("id")) map { x =>
|
getCommitComment(repository.owner, repository.name, params("id")) map { x =>
|
||||||
if(isEditable(x.userName, x.repositoryName, x.commentedUserName)){
|
if(isEditable(x.userName, x.repositoryName, x.commentedUserName)){
|
||||||
params.get("dataType") collect {
|
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)
|
x.content, x.commentId, x.userName, x.repositoryName)
|
||||||
} getOrElse {
|
} getOrElse {
|
||||||
contentType = formats("json")
|
contentType = formats("json")
|
||||||
@@ -322,14 +403,11 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
|||||||
* Displays branches.
|
* Displays branches.
|
||||||
*/
|
*/
|
||||||
get("/:owner/:repository/branches")(referrersOnly { repository =>
|
get("/:owner/:repository/branches")(referrersOnly { repository =>
|
||||||
using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git =>
|
val branches = JGitUtil.getBranches(repository.owner, repository.name, repository.repository.defaultBranch)
|
||||||
// retrieve latest update date of each branch
|
.sortBy(br => (br.mergeInfo.isEmpty, br.commitTime))
|
||||||
val branchInfo = repository.branchList.map { branchName =>
|
.map(br => br -> getPullRequestByRequestCommit(repository.owner, repository.name, repository.repository.defaultBranch, br.name, br.commitId))
|
||||||
val revCommit = git.log.add(git.getRepository.resolve(branchName)).setMaxCount(1).call.iterator.next
|
.reverse
|
||||||
(branchName, revCommit.getCommitterIdent.getWhen)
|
html.branches(branches, hasWritePermission(repository.owner, repository.name, context.loginAccount), repository)
|
||||||
}
|
|
||||||
repo.html.branches(branchInfo, hasWritePermission(repository.owner, repository.name, context.loginAccount), repository)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -369,7 +447,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
|||||||
* Displays tags.
|
* Displays tags.
|
||||||
*/
|
*/
|
||||||
get("/:owner/:repository/tags")(referrersOnly {
|
get("/:owner/:repository/tags")(referrersOnly {
|
||||||
repo.html.tags(_)
|
html.tags(_)
|
||||||
})
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -386,7 +464,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
|||||||
})
|
})
|
||||||
|
|
||||||
get("/:owner/:repository/network/members")(referrersOnly { repository =>
|
get("/:owner/:repository/network/members")(referrersOnly { repository =>
|
||||||
repo.html.forked(
|
html.forked(
|
||||||
getRepository(
|
getRepository(
|
||||||
repository.repository.originUserName.getOrElse(repository.owner),
|
repository.repository.originUserName.getOrElse(repository.owner),
|
||||||
repository.repository.originRepositoryName.getOrElse(repository.name),
|
repository.repository.originRepositoryName.getOrElse(repository.name),
|
||||||
@@ -397,7 +475,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
|||||||
repository)
|
repository)
|
||||||
})
|
})
|
||||||
|
|
||||||
private def splitPath(repository: service.RepositoryService.RepositoryInfo, path: String): (String, String) = {
|
private def splitPath(repository: RepositoryService.RepositoryInfo, path: String): (String, String) = {
|
||||||
val id = repository.branchList.collectFirst {
|
val id = repository.branchList.collectFirst {
|
||||||
case branch if(path == branch || path.startsWith(branch + "/")) => branch
|
case branch if(path == branch || path.startsWith(branch + "/")) => branch
|
||||||
} orElse repository.tags.collectFirst {
|
} orElse repository.tags.collectFirst {
|
||||||
@@ -420,7 +498,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
|||||||
*/
|
*/
|
||||||
private def fileList(repository: RepositoryService.RepositoryInfo, revstr: String = "", path: String = ".") = {
|
private def fileList(repository: RepositoryService.RepositoryInfo, revstr: String = "", path: String = ".") = {
|
||||||
if(repository.commitCount == 0){
|
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 {
|
} else {
|
||||||
using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git =>
|
using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git =>
|
||||||
// get specified commit
|
// get specified commit
|
||||||
@@ -439,14 +517,15 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
|||||||
Git.open(getRepositoryDir(repository.owner, repository.name)), file.id, true).get)
|
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
|
if(path == ".") Nil else path.split("/").toList, // current path
|
||||||
context.loginAccount match {
|
context.loginAccount match {
|
||||||
case None => List()
|
case None => List()
|
||||||
case account: Option[model.Account] => getGroupsByUserName(account.get.userName)
|
case account: Option[Account] => getGroupsByUserName(account.get.userName)
|
||||||
}, // groups of current user
|
}, // groups of current user
|
||||||
new JGitUtil.CommitInfo(lastModifiedCommit), // last modified commit
|
new JGitUtil.CommitInfo(lastModifiedCommit), // last modified commit
|
||||||
files, readme, hasWritePermission(repository.owner, repository.name, context.loginAccount),
|
files, readme, hasWritePermission(repository.owner, repository.name, context.loginAccount),
|
||||||
|
getPullRequestFromBranch(repository.owner, repository.name, revstr, repository.repository.defaultBranch),
|
||||||
flash.get("info"), flash.get("error"))
|
flash.get("info"), flash.get("error"))
|
||||||
}
|
}
|
||||||
} getOrElse NotFound
|
} getOrElse NotFound
|
||||||
@@ -454,7 +533,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],
|
branch: String, path: String, newFileName: Option[String], oldFileName: Option[String],
|
||||||
content: String, charset: String, message: String) = {
|
content: String, charset: String, message: String) = {
|
||||||
|
|
||||||
@@ -506,14 +585,12 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
|||||||
closeIssuesFromMessage(message, loginAccount.userName, repository.owner, repository.name)
|
closeIssuesFromMessage(message, loginAccount.userName, repository.owner, repository.name)
|
||||||
|
|
||||||
// call web hook
|
// call web hook
|
||||||
|
callPullRequestWebHookByRequestBranch("synchronize", repository, branch, context.baseUrl, loginAccount)
|
||||||
val commit = new JGitUtil.CommitInfo(JGitUtil.getRevCommitFromId(git, commitId))
|
val commit = new JGitUtil.CommitInfo(JGitUtil.getRevCommitFromId(git, commitId))
|
||||||
getWebHookURLs(repository.owner, repository.name) match {
|
callWebHookOf(repository.owner, repository.name, "push") {
|
||||||
case webHookURLs if(webHookURLs.nonEmpty) =>
|
getAccountByUserName(repository.owner).map{ ownerAccount =>
|
||||||
for(ownerAccount <- getAccountByUserName(repository.owner)){
|
WebHookPushPayload(git, loginAccount, headName, repository, List(commit), ownerAccount)
|
||||||
callWebHook(repository.owner, repository.name, webHookURLs,
|
}
|
||||||
WebHookPayload(git, loginAccount, headName, repository, List(commit), ownerAccount))
|
|
||||||
}
|
|
||||||
case _ =>
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -562,6 +639,6 @@ 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
|
hasWritePermission(owner, repository, context.loginAccount) || author == context.loginAccount.get.userName
|
||||||
}
|
}
|
||||||
@@ -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 ControlUtil._
|
||||||
import Implicits._
|
import Implicits._
|
||||||
import service._
|
|
||||||
import jp.sf.amateras.scalatra.forms._
|
import jp.sf.amateras.scalatra.forms._
|
||||||
|
|
||||||
class SearchController extends SearchControllerBase
|
class SearchController extends SearchControllerBase
|
||||||
@@ -34,12 +35,12 @@ trait SearchControllerBase extends ControllerBase { self: RepositoryService
|
|||||||
}
|
}
|
||||||
|
|
||||||
target.toLowerCase match {
|
target.toLowerCase match {
|
||||||
case "issue" => search.html.issues(
|
case "issue" => html.issues(
|
||||||
searchIssues(repository.owner, repository.name, query),
|
searchIssues(repository.owner, repository.name, query),
|
||||||
countFiles(repository.owner, repository.name, query),
|
countFiles(repository.owner, repository.name, query),
|
||||||
query, page, repository)
|
query, page, repository)
|
||||||
|
|
||||||
case _ => search.html.code(
|
case _ => html.code(
|
||||||
searchFiles(repository.owner, repository.name, query),
|
searchFiles(repository.owner, repository.name, query),
|
||||||
countIssues(repository.owner, repository.name, query),
|
countIssues(repository.owner, repository.name, query),
|
||||||
query, page, repository)
|
query, page, repository)
|
||||||
@@ -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 SystemSettingsService._
|
||||||
import util.AdminAuthenticator
|
|
||||||
import jp.sf.amateras.scalatra.forms._
|
import jp.sf.amateras.scalatra.forms._
|
||||||
import ssh.SshServer
|
|
||||||
|
|
||||||
class SystemSettingsController extends SystemSettingsControllerBase
|
class SystemSettingsController extends SystemSettingsControllerBase
|
||||||
with AccountService with AdminAuthenticator
|
with AccountService with AdminAuthenticator
|
||||||
@@ -20,6 +21,7 @@ trait SystemSettingsControllerBase extends ControllerBase {
|
|||||||
"isCreateRepoOptionPublic" -> trim(label("Default option to create a new repository", boolean())),
|
"isCreateRepoOptionPublic" -> trim(label("Default option to create a new repository", boolean())),
|
||||||
"gravatar" -> trim(label("Gravatar", boolean())),
|
"gravatar" -> trim(label("Gravatar", boolean())),
|
||||||
"notification" -> trim(label("Notification", boolean())),
|
"notification" -> trim(label("Notification", boolean())),
|
||||||
|
"activityLogLimit" -> trim(label("Limit of activity logs", optional(number()))),
|
||||||
"ssh" -> trim(label("SSH access", boolean())),
|
"ssh" -> trim(label("SSH access", boolean())),
|
||||||
"sshPort" -> trim(label("SSH port", optional(number()))),
|
"sshPort" -> trim(label("SSH port", optional(number()))),
|
||||||
"smtp" -> optionalIfNotChecked("notification", mapping(
|
"smtp" -> optionalIfNotChecked("notification", mapping(
|
||||||
@@ -59,7 +61,7 @@ trait SystemSettingsControllerBase extends ControllerBase {
|
|||||||
case class PluginForm(pluginIds: List[String])
|
case class PluginForm(pluginIds: List[String])
|
||||||
|
|
||||||
get("/admin/system")(adminOnly {
|
get("/admin/system")(adminOnly {
|
||||||
admin.html.system(flash.get("info"))
|
html.system(flash.get("info"))
|
||||||
})
|
})
|
||||||
|
|
||||||
post("/admin/system", form)(adminOnly { form =>
|
post("/admin/system", form)(adminOnly { form =>
|
||||||
@@ -70,7 +72,7 @@ trait SystemSettingsControllerBase extends ControllerBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if(form.ssh && !SshServer.isActive && form.baseUrl.isDefined){
|
if(form.ssh && !SshServer.isActive && form.baseUrl.isDefined){
|
||||||
SshServer.start(request.getServletContext,
|
SshServer.start(
|
||||||
form.sshPort.getOrElse(SystemSettingsService.DefaultSshPort),
|
form.sshPort.getOrElse(SystemSettingsService.DefaultSshPort),
|
||||||
form.baseUrl.get)
|
form.baseUrl.get)
|
||||||
} else if(!form.ssh && SshServer.isActive){
|
} else if(!form.ssh && SshServer.isActive){
|
||||||
@@ -1,11 +1,12 @@
|
|||||||
package app
|
package gitbucket.core.controller
|
||||||
|
|
||||||
import service._
|
import gitbucket.core.service.{RepositoryService, AccountService}
|
||||||
import util.AdminAuthenticator
|
import gitbucket.core.admin.users.html
|
||||||
import util.StringUtil._
|
import gitbucket.core.util._
|
||||||
import util.ControlUtil._
|
import gitbucket.core.util.ControlUtil._
|
||||||
import util.Directory._
|
import gitbucket.core.util.StringUtil._
|
||||||
import util.Implicits._
|
import gitbucket.core.util.Implicits._
|
||||||
|
import gitbucket.core.util.Directory._
|
||||||
import jp.sf.amateras.scalatra.forms._
|
import jp.sf.amateras.scalatra.forms._
|
||||||
import org.scalatra.i18n.Messages
|
import org.scalatra.i18n.Messages
|
||||||
import org.apache.commons.io.FileUtils
|
import org.apache.commons.io.FileUtils
|
||||||
@@ -75,11 +76,11 @@ trait UserManagementControllerBase extends AccountManagementControllerBase {
|
|||||||
account.userName -> getGroupMembers(account.userName).map(_.userName)
|
account.userName -> getGroupMembers(account.userName).map(_.userName)
|
||||||
}.toMap
|
}.toMap
|
||||||
|
|
||||||
admin.users.html.list(users, members, includeRemoved)
|
html.list(users, members, includeRemoved)
|
||||||
})
|
})
|
||||||
|
|
||||||
get("/admin/users/_newuser")(adminOnly {
|
get("/admin/users/_newuser")(adminOnly {
|
||||||
admin.users.html.user(None)
|
html.user(None)
|
||||||
})
|
})
|
||||||
|
|
||||||
post("/admin/users/_newuser", newUserForm)(adminOnly { form =>
|
post("/admin/users/_newuser", newUserForm)(adminOnly { form =>
|
||||||
@@ -90,7 +91,7 @@ trait UserManagementControllerBase extends AccountManagementControllerBase {
|
|||||||
|
|
||||||
get("/admin/users/:userName/_edituser")(adminOnly {
|
get("/admin/users/:userName/_edituser")(adminOnly {
|
||||||
val userName = params("userName")
|
val userName = params("userName")
|
||||||
admin.users.html.user(getAccountByUserName(userName, true))
|
html.user(getAccountByUserName(userName, true))
|
||||||
})
|
})
|
||||||
|
|
||||||
post("/admin/users/:name/_edituser", editUserForm)(adminOnly { form =>
|
post("/admin/users/:name/_edituser", editUserForm)(adminOnly { form =>
|
||||||
@@ -124,7 +125,7 @@ trait UserManagementControllerBase extends AccountManagementControllerBase {
|
|||||||
})
|
})
|
||||||
|
|
||||||
get("/admin/users/_newgroup")(adminOnly {
|
get("/admin/users/_newgroup")(adminOnly {
|
||||||
admin.users.html.group(None, Nil)
|
html.group(None, Nil)
|
||||||
})
|
})
|
||||||
|
|
||||||
post("/admin/users/_newgroup", newGroupForm)(adminOnly { form =>
|
post("/admin/users/_newgroup", newGroupForm)(adminOnly { form =>
|
||||||
@@ -140,7 +141,7 @@ trait UserManagementControllerBase extends AccountManagementControllerBase {
|
|||||||
|
|
||||||
get("/admin/users/:groupName/_editgroup")(adminOnly {
|
get("/admin/users/:groupName/_editgroup")(adminOnly {
|
||||||
defining(params("groupName")){ groupName =>
|
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() {
|
protected def disableByNotYourself(paramName: String): Constraint = new Constraint() {
|
||||||
override def validate(name: String, value: String, messages: Messages): Option[String] = {
|
override def validate(name: String, value: String, messages: Messages): Option[String] = {
|
||||||
params.get(paramName).flatMap { userName =>
|
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")
|
Some("You can't disable your account yourself")
|
||||||
else
|
else
|
||||||
None
|
None
|
||||||
@@ -1,14 +1,15 @@
|
|||||||
package app
|
package gitbucket.core.controller
|
||||||
|
|
||||||
import service._
|
import gitbucket.core.wiki.html
|
||||||
import util._
|
import gitbucket.core.service.{RepositoryService, WikiService, ActivityService, AccountService}
|
||||||
import util.Directory._
|
import gitbucket.core.util._
|
||||||
import util.ControlUtil._
|
import gitbucket.core.util.StringUtil._
|
||||||
import util.Implicits._
|
import gitbucket.core.util.ControlUtil._
|
||||||
|
import gitbucket.core.util.Implicits._
|
||||||
|
import gitbucket.core.util.Directory._
|
||||||
import jp.sf.amateras.scalatra.forms._
|
import jp.sf.amateras.scalatra.forms._
|
||||||
import org.eclipse.jgit.api.Git
|
import org.eclipse.jgit.api.Git
|
||||||
import org.scalatra.i18n.Messages
|
import org.scalatra.i18n.Messages
|
||||||
import java.util.ResourceBundle
|
|
||||||
|
|
||||||
class WikiController extends WikiControllerBase
|
class WikiController extends WikiControllerBase
|
||||||
with WikiService with RepositoryService with AccountService with ActivityService with CollaboratorsAuthenticator with ReferrerAuthenticator
|
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 =>
|
get("/:owner/:repository/wiki")(referrersOnly { repository =>
|
||||||
getWikiPage(repository.owner, repository.name, "Home").map { page =>
|
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))
|
repository, hasWritePermission(repository.owner, repository.name, context.loginAccount))
|
||||||
} getOrElse redirect(s"/${repository.owner}/${repository.name}/wiki/Home/_edit")
|
} getOrElse redirect(s"/${repository.owner}/${repository.name}/wiki/Home/_edit")
|
||||||
})
|
})
|
||||||
@@ -45,7 +46,7 @@ trait WikiControllerBase extends ControllerBase {
|
|||||||
val pageName = StringUtil.urlDecode(params("page"))
|
val pageName = StringUtil.urlDecode(params("page"))
|
||||||
|
|
||||||
getWikiPage(repository.owner, repository.name, pageName).map { 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))
|
repository, hasWritePermission(repository.owner, repository.name, context.loginAccount))
|
||||||
} getOrElse redirect(s"/${repository.owner}/${repository.name}/wiki/${StringUtil.urlEncode(pageName)}/_edit")
|
} 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 =>
|
using(Git.open(getWikiRepositoryDir(repository.owner, repository.name))){ git =>
|
||||||
JGitUtil.getCommitLog(git, "master", path = pageName + ".md") match {
|
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
|
case Left(_) => NotFound
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -66,7 +67,7 @@ trait WikiControllerBase extends ControllerBase {
|
|||||||
val Array(from, to) = params("commitId").split("\\.\\.\\.")
|
val Array(from, to) = params("commitId").split("\\.\\.\\.")
|
||||||
|
|
||||||
using(Git.open(getWikiRepositoryDir(repository.owner, repository.name))){ git =>
|
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"))
|
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("\\.\\.\\.")
|
val Array(from, to) = params("commitId").split("\\.\\.\\.")
|
||||||
|
|
||||||
using(Git.open(getWikiRepositoryDir(repository.owner, repository.name))){ git =>
|
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"))
|
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 =>
|
get("/:owner/:repository/wiki/:page/_edit")(collaboratorsOnly { repository =>
|
||||||
val pageName = StringUtil.urlDecode(params("page"))
|
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) =>
|
post("/:owner/:repository/wiki/_edit", editForm)(collaboratorsOnly { (form, repository) =>
|
||||||
defining(context.loginAccount.get){ loginAccount =>
|
defining(context.loginAccount.get){ loginAccount =>
|
||||||
saveWikiPage(repository.owner, repository.name, form.currentPageName, form.pageName,
|
saveWikiPage(
|
||||||
form.content, loginAccount, form.message.getOrElse(""), Some(form.id)).map { commitId =>
|
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)
|
updateLastActivityDate(repository.owner, repository.name)
|
||||||
recordEditWikiPageActivity(repository.owner, repository.name, loginAccount.userName, form.pageName, commitId)
|
recordEditWikiPageActivity(repository.owner, repository.name, loginAccount.userName, form.pageName, commitId)
|
||||||
}
|
}
|
||||||
@@ -120,7 +129,7 @@ trait WikiControllerBase extends ControllerBase {
|
|||||||
})
|
})
|
||||||
|
|
||||||
get("/:owner/:repository/wiki/_new")(collaboratorsOnly {
|
get("/:owner/:repository/wiki/_new")(collaboratorsOnly {
|
||||||
wiki.html.edit("", None, _)
|
html.edit("", None, _)
|
||||||
})
|
})
|
||||||
|
|
||||||
post("/:owner/:repository/wiki/_new", newForm)(collaboratorsOnly { (form, repository) =>
|
post("/:owner/:repository/wiki/_new", newForm)(collaboratorsOnly { (form, repository) =>
|
||||||
@@ -147,14 +156,14 @@ trait WikiControllerBase extends ControllerBase {
|
|||||||
})
|
})
|
||||||
|
|
||||||
get("/:owner/:repository/wiki/_pages")(referrersOnly { repository =>
|
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))
|
hasWritePermission(repository.owner, repository.name, context.loginAccount))
|
||||||
})
|
})
|
||||||
|
|
||||||
get("/:owner/:repository/wiki/_history")(referrersOnly { repository =>
|
get("/:owner/:repository/wiki/_history")(referrersOnly { repository =>
|
||||||
using(Git.open(getWikiRepositoryDir(repository.owner, repository.name))){ git =>
|
using(Git.open(getWikiRepositoryDir(repository.owner, repository.name))){ git =>
|
||||||
JGitUtil.getCommitLog(git, "master") match {
|
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
|
case Left(_) => NotFound
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
21
src/main/scala/gitbucket/core/model/AccessToken.scala
Normal file
21
src/main/scala/gitbucket/core/model/AccessToken.scala
Normal 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
|
||||||
|
)
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package model
|
package gitbucket.core.model
|
||||||
|
|
||||||
trait AccountComponent { self: Profile =>
|
trait AccountComponent { self: Profile =>
|
||||||
import profile.simple._
|
import profile.simple._
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package model
|
package gitbucket.core.model
|
||||||
|
|
||||||
trait ActivityComponent extends TemplateComponent { self: Profile =>
|
trait ActivityComponent extends TemplateComponent { self: Profile =>
|
||||||
import profile.simple._
|
import profile.simple._
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package model
|
package gitbucket.core.model
|
||||||
|
|
||||||
protected[model] trait TemplateComponent { self: Profile =>
|
protected[model] trait TemplateComponent { self: Profile =>
|
||||||
import profile.simple._
|
import profile.simple._
|
||||||
@@ -49,6 +49,9 @@ protected[model] trait TemplateComponent { self: Profile =>
|
|||||||
|
|
||||||
def byCommit(owner: String, repository: String, commitId: String) =
|
def byCommit(owner: String, repository: String, commitId: String) =
|
||||||
byRepository(owner, repository) && (this.commitId === commitId)
|
byRepository(owner, repository) && (this.commitId === commitId)
|
||||||
|
|
||||||
|
def byCommit(owner: Column[String], repository: Column[String], commitId: Column[String]) =
|
||||||
|
byRepository(userName, repositoryName) && (this.commitId === commitId)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package model
|
package gitbucket.core.model
|
||||||
|
|
||||||
trait CollaboratorComponent extends TemplateComponent { self: Profile =>
|
trait CollaboratorComponent extends TemplateComponent { self: Profile =>
|
||||||
import profile.simple._
|
import profile.simple._
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package model
|
package gitbucket.core.model
|
||||||
|
|
||||||
trait Comment {
|
trait Comment {
|
||||||
val commentedUserName: String
|
val commentedUserName: String
|
||||||
83
src/main/scala/gitbucket/core/model/CommitStatus.scala
Normal file
83
src/main/scala/gitbucket/core/model/CommitStatus.scala
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
package gitbucket.core.model
|
||||||
|
|
||||||
|
import scala.slick.lifted.MappedTo
|
||||||
|
import scala.slick.jdbc._
|
||||||
|
|
||||||
|
trait CommitStatusComponent extends TemplateComponent { self: Profile =>
|
||||||
|
import profile.simple._
|
||||||
|
import self._
|
||||||
|
|
||||||
|
implicit val commitStateColumnType = MappedColumnType.base[CommitState, String](b => b.name , i => CommitState(i))
|
||||||
|
|
||||||
|
lazy val CommitStatuses = TableQuery[CommitStatuses]
|
||||||
|
class CommitStatuses(tag: Tag) extends Table[CommitStatus](tag, "COMMIT_STATUS") with CommitTemplate {
|
||||||
|
val commitStatusId = column[Int]("COMMIT_STATUS_ID", O AutoInc)
|
||||||
|
val context = column[String]("CONTEXT")
|
||||||
|
val state = column[CommitState]("STATE")
|
||||||
|
val targetUrl = column[Option[String]]("TARGET_URL")
|
||||||
|
val description = column[Option[String]]("DESCRIPTION")
|
||||||
|
val creator = column[String]("CREATOR")
|
||||||
|
val registeredDate = column[java.util.Date]("REGISTERED_DATE")
|
||||||
|
val updatedDate = column[java.util.Date]("UPDATED_DATE")
|
||||||
|
def * = (commitStatusId, userName, repositoryName, commitId, context, state, targetUrl, description, creator, registeredDate, updatedDate) <> (CommitStatus.tupled, CommitStatus.unapply)
|
||||||
|
def byPrimaryKey(id: Int) = commitStatusId === id.bind
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
case class CommitStatus(
|
||||||
|
commitStatusId: Int = 0,
|
||||||
|
userName: String,
|
||||||
|
repositoryName: String,
|
||||||
|
commitId: String,
|
||||||
|
context: String,
|
||||||
|
state: CommitState,
|
||||||
|
targetUrl: Option[String],
|
||||||
|
description: Option[String],
|
||||||
|
creator: String,
|
||||||
|
registeredDate: java.util.Date,
|
||||||
|
updatedDate: java.util.Date
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
sealed abstract class CommitState(val name: String)
|
||||||
|
|
||||||
|
|
||||||
|
object CommitState {
|
||||||
|
object ERROR extends CommitState("error")
|
||||||
|
|
||||||
|
object FAILURE extends CommitState("failure")
|
||||||
|
|
||||||
|
object PENDING extends CommitState("pending")
|
||||||
|
|
||||||
|
object SUCCESS extends CommitState("success")
|
||||||
|
|
||||||
|
val values: Vector[CommitState] = Vector(PENDING, SUCCESS, ERROR, FAILURE)
|
||||||
|
|
||||||
|
private val map: Map[String, CommitState] = values.map(enum => enum.name -> enum).toMap
|
||||||
|
|
||||||
|
def apply(name: String): CommitState = map(name)
|
||||||
|
|
||||||
|
def valueOf(name: String): Option[CommitState] = map.get(name)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* failure if any of the contexts report as error or failure
|
||||||
|
* pending if there are no statuses or a context is pending
|
||||||
|
* success if the latest status for all contexts is success
|
||||||
|
*/
|
||||||
|
def combine(statuses: Set[CommitState]): CommitState = {
|
||||||
|
if(statuses.isEmpty){
|
||||||
|
PENDING
|
||||||
|
} else if(statuses.contains(CommitState.ERROR) || statuses.contains(CommitState.FAILURE)) {
|
||||||
|
FAILURE
|
||||||
|
} else if(statuses.contains(CommitState.PENDING)) {
|
||||||
|
PENDING
|
||||||
|
} else {
|
||||||
|
SUCCESS
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
implicit val getResult: GetResult[CommitState] = GetResult(r => CommitState(r.<<))
|
||||||
|
implicit val getResultOpt: GetResult[Option[CommitState]] = GetResult(r => r.<<?[String].map(CommitState(_)))
|
||||||
|
}
|
||||||
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package model
|
package gitbucket.core.model
|
||||||
|
|
||||||
trait GroupMemberComponent { self: Profile =>
|
trait GroupMemberComponent { self: Profile =>
|
||||||
import profile.simple._
|
import profile.simple._
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package model
|
package gitbucket.core.model
|
||||||
|
|
||||||
trait IssueComponent extends TemplateComponent { self: Profile =>
|
trait IssueComponent extends TemplateComponent { self: Profile =>
|
||||||
import profile.simple._
|
import profile.simple._
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package model
|
package gitbucket.core.model
|
||||||
|
|
||||||
trait IssueLabelComponent extends TemplateComponent { self: Profile =>
|
trait IssueLabelComponent extends TemplateComponent { self: Profile =>
|
||||||
import profile.simple._
|
import profile.simple._
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package model
|
package gitbucket.core.model
|
||||||
|
|
||||||
trait LabelComponent extends TemplateComponent { self: Profile =>
|
trait LabelComponent extends TemplateComponent { self: Profile =>
|
||||||
import profile.simple._
|
import profile.simple._
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package model
|
package gitbucket.core.model
|
||||||
|
|
||||||
trait MilestoneComponent extends TemplateComponent { self: Profile =>
|
trait MilestoneComponent extends TemplateComponent { self: Profile =>
|
||||||
import profile.simple._
|
import profile.simple._
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package model
|
package gitbucket.core.model
|
||||||
|
|
||||||
trait PluginComponent extends TemplateComponent { self: Profile =>
|
trait PluginComponent extends TemplateComponent { self: Profile =>
|
||||||
import profile.simple._
|
import profile.simple._
|
||||||
@@ -1,28 +1,43 @@
|
|||||||
package model
|
package gitbucket.core.model
|
||||||
|
|
||||||
|
|
||||||
trait Profile {
|
trait Profile {
|
||||||
val profile: slick.driver.JdbcProfile
|
val profile: slick.driver.JdbcProfile
|
||||||
import profile.simple._
|
import profile.simple._
|
||||||
|
|
||||||
// java.util.Date Mapped Column Types
|
/**
|
||||||
|
* java.util.Date Mapped Column Types
|
||||||
|
*/
|
||||||
implicit val dateColumnType = MappedColumnType.base[java.util.Date, java.sql.Timestamp](
|
implicit val dateColumnType = MappedColumnType.base[java.util.Date, java.sql.Timestamp](
|
||||||
d => new java.sql.Timestamp(d.getTime),
|
d => new java.sql.Timestamp(d.getTime),
|
||||||
t => new java.util.Date(t.getTime)
|
t => new java.util.Date(t.getTime)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extends Column to add conditional condition
|
||||||
|
*/
|
||||||
implicit class RichColumn(c1: Column[Boolean]){
|
implicit class RichColumn(c1: Column[Boolean]){
|
||||||
def &&(c2: => Column[Boolean], guard: => Boolean): Column[Boolean] = if(guard) c1 && c2 else c1
|
def &&(c2: => Column[Boolean], guard: => Boolean): Column[Boolean] = if(guard) c1 && c2 else c1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns system date.
|
||||||
|
*/
|
||||||
|
def currentDate = new java.util.Date()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
object Profile extends {
|
trait ProfileProvider { self: Profile =>
|
||||||
val profile = slick.driver.H2Driver
|
val profile = slick.driver.H2Driver
|
||||||
|
}
|
||||||
|
|
||||||
} with AccountComponent
|
trait CoreProfile extends ProfileProvider with Profile
|
||||||
|
with AccessTokenComponent
|
||||||
|
with AccountComponent
|
||||||
with ActivityComponent
|
with ActivityComponent
|
||||||
with CollaboratorComponent
|
with CollaboratorComponent
|
||||||
with CommitCommentComponent
|
with CommitCommentComponent
|
||||||
|
with CommitStatusComponent
|
||||||
with GroupMemberComponent
|
with GroupMemberComponent
|
||||||
with IssueComponent
|
with IssueComponent
|
||||||
with IssueCommentComponent
|
with IssueCommentComponent
|
||||||
@@ -33,11 +48,6 @@ object Profile extends {
|
|||||||
with RepositoryComponent
|
with RepositoryComponent
|
||||||
with SshKeyComponent
|
with SshKeyComponent
|
||||||
with WebHookComponent
|
with WebHookComponent
|
||||||
with PluginComponent with Profile {
|
with PluginComponent
|
||||||
|
|
||||||
/**
|
object Profile extends CoreProfile
|
||||||
* Returns system date.
|
|
||||||
*/
|
|
||||||
def currentDate = new java.util.Date()
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package model
|
package gitbucket.core.model
|
||||||
|
|
||||||
trait PullRequestComponent extends TemplateComponent { self: Profile =>
|
trait PullRequestComponent extends TemplateComponent { self: Profile =>
|
||||||
import profile.simple._
|
import profile.simple._
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package model
|
package gitbucket.core.model
|
||||||
|
|
||||||
trait RepositoryComponent extends TemplateComponent { self: Profile =>
|
trait RepositoryComponent extends TemplateComponent { self: Profile =>
|
||||||
import profile.simple._
|
import profile.simple._
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package model
|
package gitbucket.core.model
|
||||||
|
|
||||||
trait SshKeyComponent { self: Profile =>
|
trait SshKeyComponent { self: Profile =>
|
||||||
import profile.simple._
|
import profile.simple._
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package model
|
package gitbucket.core.model
|
||||||
|
|
||||||
trait WebHookComponent extends TemplateComponent { self: Profile =>
|
trait WebHookComponent extends TemplateComponent { self: Profile =>
|
||||||
import profile.simple._
|
import profile.simple._
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
package gitbucket.core
|
||||||
|
|
||||||
package object model {
|
package object model {
|
||||||
type Session = slick.jdbc.JdbcBackend#Session
|
type Session = slick.jdbc.JdbcBackend#Session
|
||||||
}
|
}
|
||||||
10
src/main/scala/gitbucket/core/plugin/Images.scala
Normal file
10
src/main/scala/gitbucket/core/plugin/Images.scala
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
package gitbucket.core.plugin
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides a helper method to generate data URI of images registered by plug-in.
|
||||||
|
*/
|
||||||
|
object Images {
|
||||||
|
|
||||||
|
def dataURI(id: String) = s"data:image/png;base64,${PluginRegistry().getImage(id)}"
|
||||||
|
|
||||||
|
}
|
||||||
30
src/main/scala/gitbucket/core/plugin/Plugin.scala
Normal file
30
src/main/scala/gitbucket/core/plugin/Plugin.scala
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
package gitbucket.core.plugin
|
||||||
|
|
||||||
|
import javax.servlet.ServletContext
|
||||||
|
import gitbucket.core.service.SystemSettingsService.SystemSettings
|
||||||
|
import gitbucket.core.util.Version
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Trait for define plugin interface.
|
||||||
|
* To provide plugin, put Plugin class which mixed in this trait into the package root.
|
||||||
|
*/
|
||||||
|
trait Plugin {
|
||||||
|
|
||||||
|
val pluginId: String
|
||||||
|
val pluginName: String
|
||||||
|
val description: String
|
||||||
|
val versions: Seq[Version]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is invoked in initialization of plugin system.
|
||||||
|
* Register plugin functionality to PluginRegistry.
|
||||||
|
*/
|
||||||
|
def initialize(registry: PluginRegistry, context: ServletContext, settings: SystemSettings): Unit
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is invoked in shutdown of plugin system.
|
||||||
|
* If the plugin has any resources, release them in this method.
|
||||||
|
*/
|
||||||
|
def shutdown(registry: PluginRegistry, context: ServletContext, settings: SystemSettings): Unit
|
||||||
|
|
||||||
|
}
|
||||||
162
src/main/scala/gitbucket/core/plugin/PluginRegistory.scala
Normal file
162
src/main/scala/gitbucket/core/plugin/PluginRegistory.scala
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
package gitbucket.core.plugin
|
||||||
|
|
||||||
|
import java.io.{File, FilenameFilter, InputStream}
|
||||||
|
import java.net.URLClassLoader
|
||||||
|
import javax.servlet.ServletContext
|
||||||
|
import javax.servlet.http.{HttpServletRequest, HttpServletResponse}
|
||||||
|
|
||||||
|
import gitbucket.core.controller.{Context, ControllerBase}
|
||||||
|
import gitbucket.core.service.RepositoryService.RepositoryInfo
|
||||||
|
import gitbucket.core.service.SystemSettingsService.SystemSettings
|
||||||
|
import gitbucket.core.util.ControlUtil._
|
||||||
|
import gitbucket.core.util.Directory._
|
||||||
|
import gitbucket.core.util.JDBCUtil._
|
||||||
|
import gitbucket.core.util.{Version, Versions}
|
||||||
|
import org.apache.commons.codec.binary.{Base64, StringUtils}
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
|
|
||||||
|
import scala.collection.mutable
|
||||||
|
import scala.collection.mutable.ListBuffer
|
||||||
|
|
||||||
|
class PluginRegistry {
|
||||||
|
|
||||||
|
private val plugins = new ListBuffer[PluginInfo]
|
||||||
|
private val javaScripts = new ListBuffer[(String, String)]
|
||||||
|
private val controllers = new ListBuffer[(ControllerBase, String)]
|
||||||
|
private val images = mutable.Map[String, String]()
|
||||||
|
|
||||||
|
def addPlugin(pluginInfo: PluginInfo): Unit = {
|
||||||
|
plugins += pluginInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
def getPlugins(): List[PluginInfo] = plugins.toList
|
||||||
|
|
||||||
|
def addImage(id: String, in: InputStream): Unit = {
|
||||||
|
val bytes = using(in){ in =>
|
||||||
|
val bytes = new Array[Byte](in.available)
|
||||||
|
in.read(bytes)
|
||||||
|
bytes
|
||||||
|
}
|
||||||
|
val encoded = StringUtils.newStringUtf8(Base64.encodeBase64(bytes, false))
|
||||||
|
images += ((id, encoded))
|
||||||
|
}
|
||||||
|
|
||||||
|
def getImage(id: String): String = images(id)
|
||||||
|
|
||||||
|
def addController(controller: ControllerBase, path: String): Unit = {
|
||||||
|
controllers += ((controller, path))
|
||||||
|
}
|
||||||
|
|
||||||
|
def getControllers(): List[(ControllerBase, String)] = controllers.toList
|
||||||
|
|
||||||
|
def addJavaScript(path: String, script: String): Unit = {
|
||||||
|
javaScripts += Tuple2(path, script)
|
||||||
|
}
|
||||||
|
|
||||||
|
//def getJavaScripts(): List[(String, String)] = javaScripts.toList
|
||||||
|
|
||||||
|
def getJavaScript(currentPath: String): List[String] = {
|
||||||
|
javaScripts.filter(x => currentPath.matches(x._1)).toList.map(_._2)
|
||||||
|
}
|
||||||
|
|
||||||
|
private case class GlobalAction(
|
||||||
|
method: String,
|
||||||
|
path: String,
|
||||||
|
function: (HttpServletRequest, HttpServletResponse, Context) => Any
|
||||||
|
)
|
||||||
|
|
||||||
|
private case class RepositoryAction(
|
||||||
|
method: String,
|
||||||
|
path: String,
|
||||||
|
function: (HttpServletRequest, HttpServletResponse, Context, RepositoryInfo) => Any
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides entry point to PluginRegistry.
|
||||||
|
*/
|
||||||
|
object PluginRegistry {
|
||||||
|
|
||||||
|
private val logger = LoggerFactory.getLogger(classOf[PluginRegistry])
|
||||||
|
|
||||||
|
private val instance = new PluginRegistry()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the PluginRegistry singleton instance.
|
||||||
|
*/
|
||||||
|
def apply(): PluginRegistry = instance
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes all installed plugins.
|
||||||
|
*/
|
||||||
|
def initialize(context: ServletContext, settings: SystemSettings, conn: java.sql.Connection): Unit = {
|
||||||
|
val pluginDir = new File(PluginHome)
|
||||||
|
if(pluginDir.exists && pluginDir.isDirectory){
|
||||||
|
pluginDir.listFiles(new FilenameFilter {
|
||||||
|
override def accept(dir: File, name: String): Boolean = name.endsWith(".jar")
|
||||||
|
}).foreach { pluginJar =>
|
||||||
|
val classLoader = new URLClassLoader(Array(pluginJar.toURI.toURL), Thread.currentThread.getContextClassLoader)
|
||||||
|
try {
|
||||||
|
val plugin = classLoader.loadClass("Plugin").newInstance().asInstanceOf[Plugin]
|
||||||
|
|
||||||
|
// Migration
|
||||||
|
val headVersion = plugin.versions.head
|
||||||
|
val currentVersion = conn.find("SELECT * FROM PLUGIN WHERE PLUGIN_ID = ?", plugin.pluginId)(_.getString("VERSION")) match {
|
||||||
|
case Some(x) => {
|
||||||
|
val dim = x.split("\\.")
|
||||||
|
Version(dim(0).toInt, dim(1).toInt)
|
||||||
|
}
|
||||||
|
case None => Version(0, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
Versions.update(conn, headVersion, currentVersion, plugin.versions, new URLClassLoader(Array(pluginJar.toURI.toURL))){ conn =>
|
||||||
|
currentVersion.versionString match {
|
||||||
|
case "0.0" =>
|
||||||
|
conn.update("INSERT INTO PLUGIN (PLUGIN_ID, VERSION) VALUES (?, ?)", plugin.pluginId, headVersion.versionString)
|
||||||
|
case _ =>
|
||||||
|
conn.update("UPDATE PLUGIN SET VERSION = ? WHERE PLUGIN_ID = ?", headVersion.versionString, plugin.pluginId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize
|
||||||
|
plugin.initialize(instance, context, settings)
|
||||||
|
instance.addPlugin(PluginInfo(
|
||||||
|
pluginId = plugin.pluginId,
|
||||||
|
pluginName = plugin.pluginName,
|
||||||
|
version = plugin.versions.head.versionString,
|
||||||
|
description = plugin.description,
|
||||||
|
pluginClass = plugin
|
||||||
|
))
|
||||||
|
|
||||||
|
} catch {
|
||||||
|
case e: Exception => {
|
||||||
|
logger.error(s"Error during plugin initialization", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def shutdown(context: ServletContext, settings: SystemSettings): Unit = {
|
||||||
|
instance.getPlugins().foreach { pluginInfo =>
|
||||||
|
try {
|
||||||
|
pluginInfo.pluginClass.shutdown(instance, context, settings)
|
||||||
|
} catch {
|
||||||
|
case e: Exception => {
|
||||||
|
logger.error(s"Error during plugin shutdown", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
case class PluginInfo(
|
||||||
|
pluginId: String,
|
||||||
|
pluginName: String,
|
||||||
|
version: String,
|
||||||
|
description: String,
|
||||||
|
pluginClass: Plugin
|
||||||
|
)
|
||||||
11
src/main/scala/gitbucket/core/plugin/Results.scala
Normal file
11
src/main/scala/gitbucket/core/plugin/Results.scala
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
package gitbucket.core.plugin
|
||||||
|
|
||||||
|
import play.twirl.api.Html
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defines result case classes returned by plugin controller.
|
||||||
|
*/
|
||||||
|
object Results {
|
||||||
|
case class Redirect(path: String)
|
||||||
|
case class Fragment(html: Html)
|
||||||
|
}
|
||||||
11
src/main/scala/gitbucket/core/plugin/Sessions.scala
Normal file
11
src/main/scala/gitbucket/core/plugin/Sessions.scala
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
package gitbucket.core.plugin
|
||||||
|
|
||||||
|
import scala.slick.jdbc.JdbcBackend.Session
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides Slick Session to Plug-ins.
|
||||||
|
*/
|
||||||
|
object Sessions {
|
||||||
|
val sessions = new ThreadLocal[Session]
|
||||||
|
implicit def session: Session = sessions.get()
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
package gitbucket.core.service
|
||||||
|
|
||||||
|
import gitbucket.core.model.Profile._
|
||||||
|
import profile.simple._
|
||||||
|
|
||||||
|
import gitbucket.core.model.{Account, AccessToken}
|
||||||
|
import gitbucket.core.util.StringUtil
|
||||||
|
|
||||||
|
import scala.util.Random
|
||||||
|
|
||||||
|
|
||||||
|
trait AccessTokenService {
|
||||||
|
|
||||||
|
def makeAccessTokenString: String = {
|
||||||
|
val bytes = new Array[Byte](20)
|
||||||
|
Random.nextBytes(bytes)
|
||||||
|
bytes.map("%02x".format(_)).mkString
|
||||||
|
}
|
||||||
|
|
||||||
|
def tokenToHash(token: String): String = StringUtil.sha1(token)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @retuen (TokenId, Token)
|
||||||
|
*/
|
||||||
|
def generateAccessToken(userName: String, note: String)(implicit s: Session): (Int, String) = {
|
||||||
|
var token: String = null
|
||||||
|
var hash: String = null
|
||||||
|
do{
|
||||||
|
token = makeAccessTokenString
|
||||||
|
hash = tokenToHash(token)
|
||||||
|
}while(AccessTokens.filter(_.tokenHash === hash.bind).exists.run)
|
||||||
|
val newToken = AccessToken(
|
||||||
|
userName = userName,
|
||||||
|
note = note,
|
||||||
|
tokenHash = hash)
|
||||||
|
val tokenId = (AccessTokens returning AccessTokens.map(_.accessTokenId)) += newToken
|
||||||
|
(tokenId, token)
|
||||||
|
}
|
||||||
|
|
||||||
|
def getAccountByAccessToken(token: String)(implicit s: Session): Option[Account] =
|
||||||
|
Accounts
|
||||||
|
.innerJoin(AccessTokens)
|
||||||
|
.filter{ case (ac, t) => (ac.userName === t.userName) && (t.tokenHash === tokenToHash(token).bind) && (ac.removed === false.bind) }
|
||||||
|
.map{ case (ac, t) => ac }
|
||||||
|
.firstOption
|
||||||
|
|
||||||
|
def getAccessTokens(userName: String)(implicit s: Session): List[AccessToken] =
|
||||||
|
AccessTokens.filter(_.userName === userName.bind).sortBy(_.accessTokenId.desc).list
|
||||||
|
|
||||||
|
def deleteAccessToken(userName: String, accessTokenId: Int)(implicit s: Session): Unit =
|
||||||
|
AccessTokens filter (t => t.userName === userName.bind && t.accessTokenId === accessTokenId) delete
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
object AccessTokenService extends AccessTokenService
|
||||||
@@ -1,14 +1,14 @@
|
|||||||
package service
|
package gitbucket.core.service
|
||||||
|
|
||||||
import model.Profile._
|
import gitbucket.core.model.{GroupMember, Account}
|
||||||
|
import gitbucket.core.model.Profile._
|
||||||
|
import gitbucket.core.util.{StringUtil, LDAPUtil}
|
||||||
|
import gitbucket.core.service.SystemSettingsService.SystemSettings
|
||||||
import profile.simple._
|
import profile.simple._
|
||||||
import model.{Account, GroupMember}
|
import StringUtil._
|
||||||
// TODO [Slick 2.0]NOT import directly?
|
|
||||||
import model.Profile.dateColumnType
|
|
||||||
import service.SystemSettingsService.SystemSettings
|
|
||||||
import util.StringUtil._
|
|
||||||
import util.LDAPUtil
|
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
|
// TODO Why is direct import required?
|
||||||
|
import gitbucket.core.model.Profile.dateColumnType
|
||||||
|
|
||||||
trait AccountService {
|
trait AccountService {
|
||||||
|
|
||||||
@@ -77,6 +77,16 @@ trait AccountService {
|
|||||||
def getAccountByUserName(userName: String, includeRemoved: Boolean = false)(implicit s: Session): Option[Account] =
|
def getAccountByUserName(userName: String, includeRemoved: Boolean = false)(implicit s: Session): Option[Account] =
|
||||||
Accounts filter(t => (t.userName === userName.bind) && (t.removed === false.bind, !includeRemoved)) firstOption
|
Accounts filter(t => (t.userName === userName.bind) && (t.removed === false.bind, !includeRemoved)) firstOption
|
||||||
|
|
||||||
|
def getAccountsByUserNames(userNames: Set[String], knowns:Set[Account], includeRemoved: Boolean = false)(implicit s: Session): Map[String, Account] = {
|
||||||
|
val map = knowns.map(a => a.userName -> a).toMap
|
||||||
|
val needs = userNames -- map.keySet
|
||||||
|
if(needs.isEmpty){
|
||||||
|
map
|
||||||
|
}else{
|
||||||
|
map ++ Accounts.filter(t => (t.userName inSetBind needs) && (t.removed === false.bind, !includeRemoved)).list.map(a => a.userName -> a).toMap
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
def getAccountByMailAddress(mailAddress: String, includeRemoved: Boolean = false)(implicit s: Session): Option[Account] =
|
def getAccountByMailAddress(mailAddress: String, includeRemoved: Boolean = false)(implicit s: Session): Option[Account] =
|
||||||
Accounts filter(t => (t.mailAddress.toLowerCase === mailAddress.toLowerCase.bind) && (t.removed === false.bind, !includeRemoved)) firstOption
|
Accounts filter(t => (t.mailAddress.toLowerCase === mailAddress.toLowerCase.bind) && (t.removed === false.bind, !includeRemoved)) firstOption
|
||||||
|
|
||||||
@@ -1,11 +1,18 @@
|
|||||||
package service
|
package gitbucket.core.service
|
||||||
|
|
||||||
import model.Profile._
|
import gitbucket.core.model.Activity
|
||||||
|
import gitbucket.core.model.Profile._
|
||||||
|
import gitbucket.core.util.JGitUtil
|
||||||
import profile.simple._
|
import profile.simple._
|
||||||
import model.Activity
|
|
||||||
|
|
||||||
trait ActivityService {
|
trait ActivityService {
|
||||||
|
|
||||||
|
def deleteOldActivities(limit: Int)(implicit s: Session): Int = {
|
||||||
|
Activities.map(_.activityId).sortBy(_ desc).drop(limit).firstOption.map { id =>
|
||||||
|
Activities.filter(_.activityId <= id.bind).delete
|
||||||
|
} getOrElse 0
|
||||||
|
}
|
||||||
|
|
||||||
def getActivitiesByUser(activityUserName: String, isPublic: Boolean)(implicit s: Session): List[Activity] =
|
def getActivitiesByUser(activityUserName: String, isPublic: Boolean)(implicit s: Session): List[Activity] =
|
||||||
Activities
|
Activities
|
||||||
.innerJoin(Repositories).on((t1, t2) => t1.byRepository(t2.userName, t2.repositoryName))
|
.innerJoin(Repositories).on((t1, t2) => t1.byRepository(t2.userName, t2.repositoryName))
|
||||||
@@ -121,7 +128,7 @@ trait ActivityService {
|
|||||||
currentDate)
|
currentDate)
|
||||||
|
|
||||||
def recordPushActivity(userName: String, repositoryName: String, activityUserName: String,
|
def recordPushActivity(userName: String, repositoryName: String, activityUserName: String,
|
||||||
branchName: String, commits: List[util.JGitUtil.CommitInfo])(implicit s: Session): Unit =
|
branchName: String, commits: List[JGitUtil.CommitInfo])(implicit s: Session): Unit =
|
||||||
Activities insert Activity(userName, repositoryName, activityUserName,
|
Activities insert Activity(userName, repositoryName, activityUserName,
|
||||||
"push",
|
"push",
|
||||||
s"[user:${activityUserName}] pushed to [branch:${userName}/${repositoryName}#${branchName}] at [repo:${userName}/${repositoryName}]",
|
s"[user:${activityUserName}] pushed to [branch:${userName}/${repositoryName}#${branchName}] at [repo:${userName}/${repositoryName}]",
|
||||||
@@ -129,7 +136,7 @@ trait ActivityService {
|
|||||||
currentDate)
|
currentDate)
|
||||||
|
|
||||||
def recordCreateTagActivity(userName: String, repositoryName: String, activityUserName: String,
|
def recordCreateTagActivity(userName: String, repositoryName: String, activityUserName: String,
|
||||||
tagName: String, commits: List[util.JGitUtil.CommitInfo])(implicit s: Session): Unit =
|
tagName: String, commits: List[JGitUtil.CommitInfo])(implicit s: Session): Unit =
|
||||||
Activities insert Activity(userName, repositoryName, activityUserName,
|
Activities insert Activity(userName, repositoryName, activityUserName,
|
||||||
"create_tag",
|
"create_tag",
|
||||||
s"[user:${activityUserName}] created tag [tag:${userName}/${repositoryName}#${tagName}] at [repo:${userName}/${repositoryName}]",
|
s"[user:${activityUserName}] created tag [tag:${userName}/${repositoryName}#${tagName}] at [repo:${userName}/${repositoryName}]",
|
||||||
@@ -137,7 +144,7 @@ trait ActivityService {
|
|||||||
currentDate)
|
currentDate)
|
||||||
|
|
||||||
def recordDeleteTagActivity(userName: String, repositoryName: String, activityUserName: String,
|
def recordDeleteTagActivity(userName: String, repositoryName: String, activityUserName: String,
|
||||||
tagName: String, commits: List[util.JGitUtil.CommitInfo])(implicit s: Session): Unit =
|
tagName: String, commits: List[JGitUtil.CommitInfo])(implicit s: Session): Unit =
|
||||||
Activities insert Activity(userName, repositoryName, activityUserName,
|
Activities insert Activity(userName, repositoryName, activityUserName,
|
||||||
"delete_tag",
|
"delete_tag",
|
||||||
s"[user:${activityUserName}] deleted tag ${tagName} at [repo:${userName}/${repositoryName}]",
|
s"[user:${activityUserName}] deleted tag ${tagName} at [repo:${userName}/${repositoryName}]",
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
package gitbucket.core.service
|
||||||
|
|
||||||
|
import gitbucket.core.model.Profile._
|
||||||
|
import profile.simple._
|
||||||
|
|
||||||
|
import gitbucket.core.model.{CommitState, CommitStatus, Account}
|
||||||
|
import gitbucket.core.util.Implicits._
|
||||||
|
import gitbucket.core.util.StringUtil._
|
||||||
|
import gitbucket.core.service.RepositoryService.RepositoryInfo
|
||||||
|
|
||||||
|
|
||||||
|
trait CommitStatusService {
|
||||||
|
/** insert or update */
|
||||||
|
def createCommitStatus(userName: String, repositoryName: String, sha:String, context:String, state:CommitState, targetUrl:Option[String], description:Option[String], now:java.util.Date, creator:Account)(implicit s: Session): Int =
|
||||||
|
CommitStatuses.filter(t => t.byCommit(userName, repositoryName, sha) && t.context===context.bind )
|
||||||
|
.map(_.commitStatusId).firstOption match {
|
||||||
|
case Some(id:Int) => {
|
||||||
|
CommitStatuses.filter(_.byPrimaryKey(id)).map{
|
||||||
|
t => (t.state , t.targetUrl , t.updatedDate , t.creator, t.description)
|
||||||
|
}.update( (state, targetUrl, now, creator.userName, description) )
|
||||||
|
id
|
||||||
|
}
|
||||||
|
case None => (CommitStatuses returning CommitStatuses.map(_.commitStatusId)) += CommitStatus(
|
||||||
|
userName = userName,
|
||||||
|
repositoryName = repositoryName,
|
||||||
|
commitId = sha,
|
||||||
|
context = context,
|
||||||
|
state = state,
|
||||||
|
targetUrl = targetUrl,
|
||||||
|
description = description,
|
||||||
|
creator = creator.userName,
|
||||||
|
registeredDate = now,
|
||||||
|
updatedDate = now)
|
||||||
|
}
|
||||||
|
|
||||||
|
def getCommitStatus(userName: String, repositoryName: String, id: Int)(implicit s: Session) :Option[CommitStatus] =
|
||||||
|
CommitStatuses.filter(t => t.byPrimaryKey(id) && t.byRepository(userName, repositoryName)).firstOption
|
||||||
|
|
||||||
|
def getCommitStatus(userName: String, repositoryName: String, sha: String, context: String)(implicit s: Session) :Option[CommitStatus] =
|
||||||
|
CommitStatuses.filter(t => t.byCommit(userName, repositoryName, sha) && t.context===context.bind ).firstOption
|
||||||
|
|
||||||
|
def getCommitStatues(userName: String, repositoryName: String, sha: String)(implicit s: Session) :List[CommitStatus] =
|
||||||
|
byCommitStatues(userName, repositoryName, sha).list
|
||||||
|
|
||||||
|
def getCommitStatuesWithCreator(userName: String, repositoryName: String, sha: String)(implicit s: Session) :List[(CommitStatus, Account)] =
|
||||||
|
byCommitStatues(userName, repositoryName, sha).innerJoin(Accounts)
|
||||||
|
.filter{ case (t,a) => t.creator === a.userName }.list
|
||||||
|
|
||||||
|
protected def byCommitStatues(userName: String, repositoryName: String, sha: String)(implicit s: Session) =
|
||||||
|
CommitStatuses.filter(t => t.byCommit(userName, repositoryName, sha) ).sortBy(_.updatedDate desc)
|
||||||
|
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user