mirror of
https://github.com/gitbucket/gitbucket.git
synced 2026-05-08 01:26:34 +02:00
Compare commits
187 Commits
3.7
...
3.10.1_h2-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a7afc3e8f9 | ||
|
|
8e8468b5b1 | ||
|
|
8665a6ee44 | ||
|
|
f4932ab88e | ||
|
|
c077dd0046 | ||
|
|
9cc6beead7 | ||
|
|
92f6792ad9 | ||
|
|
25031eadfb | ||
|
|
a7024615e1 | ||
|
|
8191a4bedb | ||
|
|
7e1f5aa353 | ||
|
|
44e161f6ec | ||
|
|
7bd17ed038 | ||
|
|
a47404130b | ||
|
|
669949aa8c | ||
|
|
c773bb90f6 | ||
|
|
18f525523b | ||
|
|
3bfd228df1 | ||
|
|
c072ab2dc5 | ||
|
|
53e06c5e86 | ||
|
|
b0a749bdc1 | ||
|
|
809443a46f | ||
|
|
59ef44dd71 | ||
|
|
d77973a29e | ||
|
|
be6bc16d3d | ||
|
|
407e926abe | ||
|
|
117655b011 | ||
|
|
2ea4a5a7ef | ||
|
|
30b0279efe | ||
|
|
7511701bb2 | ||
|
|
a919ae3430 | ||
|
|
c30ee24f8d | ||
|
|
10b5de571e | ||
|
|
c6f4ec7250 | ||
|
|
b229058726 | ||
|
|
21363794b6 | ||
|
|
1754d37ac0 | ||
|
|
1c36b185ec | ||
|
|
1a4b94e5df | ||
|
|
31bd8e1741 | ||
|
|
e8824ad1ac | ||
|
|
17636a5ecf | ||
|
|
8f36ff60de | ||
|
|
7ad605afba | ||
|
|
f080e8693e | ||
|
|
e27bf7868f | ||
|
|
db55fe9ff3 | ||
|
|
de4db0764c | ||
|
|
9d644b4625 | ||
|
|
354dc27e84 | ||
|
|
1c639474d3 | ||
|
|
4d8b47732e | ||
|
|
8650abf679 | ||
|
|
27dcc2ef48 | ||
|
|
94a12cd28c | ||
|
|
a982b64fd4 | ||
|
|
ee5b4bbf2e | ||
|
|
d09ddd8d07 | ||
|
|
c7bf47820c | ||
|
|
4dfb01d59e | ||
|
|
54bef6505f | ||
|
|
2aa71ed217 | ||
|
|
d90938421f | ||
|
|
e4992bbd17 | ||
|
|
7853b22522 | ||
|
|
9475215fa6 | ||
|
|
8622bbd2ac | ||
|
|
0cdcc252e0 | ||
|
|
9477cca8e8 | ||
|
|
176e427058 | ||
|
|
1bf26cacb9 | ||
|
|
38e8247483 | ||
|
|
44aa12108c | ||
|
|
dd73405aa9 | ||
|
|
6710393a53 | ||
|
|
711aee1c8b | ||
|
|
51be1048d5 | ||
|
|
50166f04d8 | ||
|
|
7eb6fea08b | ||
|
|
8e3c054da4 | ||
|
|
5249b67df1 | ||
|
|
583830c1c2 | ||
|
|
bb75f1c034 | ||
|
|
b7a9236283 | ||
|
|
7e0e172d37 | ||
|
|
ea37a34476 | ||
|
|
fb5564de07 | ||
|
|
cead7e9e4e | ||
|
|
7cf23b6c56 | ||
|
|
1080821862 | ||
|
|
0bf843ce2a | ||
|
|
b8bd8f5512 | ||
|
|
12fba30ef8 | ||
|
|
dc5cb74f4d | ||
|
|
446a524b5a | ||
|
|
121110aede | ||
|
|
c76a8562ea | ||
|
|
495d8069f5 | ||
|
|
70af6d0c35 | ||
|
|
abfc6eea1e | ||
|
|
fd2f3e252c | ||
|
|
3b6d0065a2 | ||
|
|
7ffe2ab864 | ||
|
|
2f6febb748 | ||
|
|
092c897150 | ||
|
|
30f072f925 | ||
|
|
72d0282613 | ||
|
|
a56f766497 | ||
|
|
31b5583896 | ||
|
|
7c95bfc77e | ||
|
|
c6804fe589 | ||
|
|
fc41e282ce | ||
|
|
d3b21a4e39 | ||
|
|
7b6d9850a2 | ||
|
|
84d97817e3 | ||
|
|
2c6d2176bb | ||
|
|
7840d28ed2 | ||
|
|
d4fd2e6cd6 | ||
|
|
70e8385246 | ||
|
|
3fd23f1749 | ||
|
|
a3e3aea4e0 | ||
|
|
dcfb8ad8eb | ||
|
|
35f82e91bc | ||
|
|
f1533cb168 | ||
|
|
763fbec0ca | ||
|
|
111c893d94 | ||
|
|
80c38a45c5 | ||
|
|
548860607b | ||
|
|
66b5fe7337 | ||
|
|
2b2a117912 | ||
|
|
39a10fd167 | ||
|
|
93f8dbd42a | ||
|
|
4f280d0df8 | ||
|
|
5e9ff3278a | ||
|
|
b4b68f0e17 | ||
|
|
97bd324cb0 | ||
|
|
2738406710 | ||
|
|
8e78345cbe | ||
|
|
bd9e064137 | ||
|
|
eb49365bcb | ||
|
|
de92e28c7a | ||
|
|
c18f8fd87b | ||
|
|
34272cb4c4 | ||
|
|
b3f8a02494 | ||
|
|
f767a55350 | ||
|
|
9b2c3848d9 | ||
|
|
e34d016581 | ||
|
|
b64b447b42 | ||
|
|
c6a4c13394 | ||
|
|
c8822cb4ca | ||
|
|
c05e7218f3 | ||
|
|
01872d3440 | ||
|
|
91bbb3e4dc | ||
|
|
80ebd9fb0e | ||
|
|
2ab217251a | ||
|
|
2b20f6c74c | ||
|
|
042f855cd5 | ||
|
|
720ab7e0a3 | ||
|
|
4b1b100aa5 | ||
|
|
541b7e2a79 | ||
|
|
00cd3adc7b | ||
|
|
5a97a518a6 | ||
|
|
d7219068cd | ||
|
|
a79c07f095 | ||
|
|
9f27f70c87 | ||
|
|
8fa79db368 | ||
|
|
a1efa60741 | ||
|
|
a08a212fdc | ||
|
|
6166eb3743 | ||
|
|
5194fc5f15 | ||
|
|
1a97beb8cf | ||
|
|
99d23398ad | ||
|
|
6accdefb8c | ||
|
|
98fc64deaa | ||
|
|
30a8cefc37 | ||
|
|
9d47c3ccb3 | ||
|
|
5a1ab8d485 | ||
|
|
5d526f243e | ||
|
|
d13bb47ee7 | ||
|
|
8b8c6ee861 | ||
|
|
0a2d95e434 | ||
|
|
fdd119c477 | ||
|
|
4f94ca1384 | ||
|
|
ed21ee8bdb | ||
|
|
efd257dee0 | ||
|
|
bacf391a39 | ||
|
|
1fddc01f6e |
@@ -1,5 +1,7 @@
|
||||
# Guideline for Issues
|
||||
|
||||
- If you have any question about GitBucket, send it to [gitter room](https://gitter.im/takezoe/gitbucket) before raise an issue.
|
||||
- If you have any question about GitBucket, send it to [gitter room](https://gitter.im/gitbucket/gitbucket) before raise an issue.
|
||||
- Make sure check whether there is a same question or request in the past.
|
||||
- When raise a new issue, write subject in **English** at least.
|
||||
- We can also support in Japaneses other than English at [gitter room for Japanese](https://gitter.im/takezoe/gitbucket_ja).
|
||||
- We can also support in Japaneses other than English at [gitter room for Japanese](https://gitter.im/gitbucket/gitbucket_ja).
|
||||
- First priority of GitBucket is easy installation and reproduce GitHub behavior, so we might reject if your request is against it.
|
||||
|
||||
42
README.md
42
README.md
@@ -1,8 +1,7 @@
|
||||
GitBucket [](https://gitter.im/takezoe/gitbucket) [](https://travis-ci.org/takezoe/gitbucket)
|
||||
GitBucket [](https://gitter.im/gitbucket/gitbucket) [](https://travis-ci.org/gitbucket/gitbucket)
|
||||
=========
|
||||
|
||||
GitBucket is the easily installable GitHub clone powered by Scala.
|
||||
|
||||
GitBucket is a GitHub clone powered by Scala which has easy installation and high extensibility.
|
||||
|
||||
Features
|
||||
--------
|
||||
@@ -20,12 +19,12 @@ The current version of GitBucket provides a basic features below:
|
||||
- Gravatar support
|
||||
- Plug-in system
|
||||
|
||||
If you want to try the development version of GitBucket, see the documentation for developers at [Wiki](https://github.com/takezoe/gitbucket/wiki).
|
||||
If you want to try the development version of GitBucket, see the documentation for developers at [Wiki](https://github.com/gitbucket/gitbucket/wiki).
|
||||
|
||||
Installation
|
||||
--------
|
||||
|
||||
1. Download latest **gitbucket.war** from [the release page](https://github.com/takezoe/gitbucket/releases).
|
||||
1. Download latest **gitbucket.war** from [the release page](https://github.com/gitbucket/gitbucket/releases).
|
||||
2. Deploy it to the Servlet 3.0 container such as Tomcat 7.x, Jetty 8.x, GlassFish 3.x or higher.
|
||||
3. Access **http://[hostname]:[port]/gitbucket/** using your web browser.
|
||||
|
||||
@@ -40,9 +39,9 @@ or you can start GitBucket by `java -jar gitbucket.war` without servlet containe
|
||||
- --host=[HOSTNAME]
|
||||
- --gitbucket.home=[DATA_DIR]
|
||||
|
||||
To upgrade GitBucket, only replace gitbucket.war. All GitBucket data is stored in HOME/.gitbucket. So if you want to back up GitBucket data, copy this directory to the other disk.
|
||||
To upgrade GitBucket, only replace gitbucket.war after stop GitBucket. All GitBucket data is stored in HOME/.gitbucket. So if you want to back up GitBucket data, copy this directory to the other disk.
|
||||
|
||||
For Installation on Windows Server with IIS see [this wiki page](https://github.com/takezoe/gitbucket/wiki/Installation-on-IIS-and-Helicontech-Zoo)
|
||||
For Installation on Windows Server with IIS see [this wiki page](https://github.com/gitbucket/gitbucket/wiki/Installation-on-IIS-and-Helicontech-Zoo)
|
||||
|
||||
### Mac OS X
|
||||
#### Installing Via Homebrew
|
||||
@@ -65,7 +64,7 @@ Or, if you don't want/need launchctl, you can just run:
|
||||
```
|
||||
|
||||
#### Manual Installation
|
||||
On OS X, copy the [gitbucket.plist](https://raw.github.com/takezoe/gitbucket/master/contrib/macosx/gitbucket.plist) file to `~/Library/LaunchAgents/`
|
||||
On OS X, generate `gitbucket.plist` by [this script](https://raw.githubusercontent.com/gitbucket/gitbucket/master/contrib/macosx/makePlist) and copy it to `~/Library/LaunchAgents/`
|
||||
|
||||
Run the following commands in `Terminal` to
|
||||
|
||||
@@ -80,21 +79,44 @@ GitBucket has the plug-in system to extend GitBucket from outside of GitBucket.
|
||||
- [gitbucket-announce-plugin](https://github.com/gitbucket-plugins/gitbucket-announce-plugin)
|
||||
- [gitbucket-h2-backup-plugin](https://github.com/gitbucket-plugins/gitbucket-h2-backup-plugin)
|
||||
- [gitbucket-desktopnotify-plugin](https://github.com/yoshiyoshifujii/gitbucket-desktopnotify-plugin)
|
||||
- [gitbucket-commitgraphs-plugin](https://github.com/yoshiyoshifujii/gitbucket-commitgraphs-plugin)
|
||||
- [gitbucket-asciidoctor-plugin](https://github.com/lefou/gitbucket-asciidoctor-plugin)
|
||||
|
||||
You can find community plugins other than them at [gitbucket community plugins](http://gitbucket-plugins.github.io/).
|
||||
|
||||
Support
|
||||
--------
|
||||
|
||||
- If you have any question about GitBucket, send it to [gitter room](https://gitter.im/takezoe/gitbucket) before raise an issue.
|
||||
- If you have any question about GitBucket, send it to [gitter room](https://gitter.im/gitbucket/gitbucket) before raise an issue.
|
||||
- Make sure check whether there is a same question or request in the past.
|
||||
- When raise a new issue, write subject in **English** at least.
|
||||
- We can also support in Japaneses other than English at [gitter room for Japanese](https://gitter.im/takezoe/gitbucket_ja).
|
||||
- We can also support in Japaneses other than English at [gitter room for Japanese](https://gitter.im/gitbucket/gitbucket_ja).
|
||||
- First priority of GitBucket is easy installation and reproduce GitHub behavior, so we might reject if your request is against it.
|
||||
|
||||
Release Notes
|
||||
--------
|
||||
### 3.10 - 30 Dec 2015
|
||||
- Move to Bootstrap3
|
||||
- New URL for raw contents (`raw/master/doc/activity.md` instead of `blob/master/doc/activity.md?raw=true`)
|
||||
- Update xsbt-web-pligin
|
||||
- Update H2 database
|
||||
|
||||
### 3.9 - 5 Dec 2015
|
||||
- GFM inline breaks support in Markdown
|
||||
- WebHook on create review comment is available
|
||||
- WebHook event trigger is selectable
|
||||
|
||||
### 3.8 - 31 Oct 2015
|
||||
- Moved to GitHub organization
|
||||
- Omit diff view for large differences
|
||||
- Repository creation API
|
||||
- Render url as link in repository description
|
||||
- Expand attachable file types
|
||||
|
||||
### 3.7 - 3 Oct 2015
|
||||
- Markdown processor has been switched to [markedj](https://github.com/gitbucket/markedj) from pegdown
|
||||
- Clone in desktop button
|
||||
- Providing MD5 and SHA-1 checksum for `gitbucket.war` has started
|
||||
|
||||
### 3.6 - 30 Aug 2015
|
||||
- User interface Improvements: Especially, commit list, issues and pull request have been updated largely.
|
||||
|
||||
@@ -38,7 +38,7 @@ createDir "$GITBUCKET_DIR"
|
||||
createDir "$GITBUCKET_LOG_DIR"
|
||||
|
||||
echo "Fetching GitBucket v$GITBUCKET_VERSION and saving as $GITBUCKET_WAR_FILE"
|
||||
sudo wget -qO "$GITBUCKET_WAR_FILE" https://github.com/takezoe/gitbucket/releases/download/$GITBUCKET_VERSION/gitbucket.war
|
||||
sudo wget -qO "$GITBUCKET_WAR_FILE" https://github.com/gitbucket/gitbucket/releases/download/$GITBUCKET_VERSION/gitbucket.war
|
||||
|
||||
sudo rm -f "$GITBUCKET_LOG_DIR/run.log"
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ Summary: GitHub clone written with Scala.
|
||||
Version: 2.6
|
||||
Release: 1%{?dist}
|
||||
License: Apache
|
||||
URL: https://github.com/takezoe/gitbucket
|
||||
URL: https://github.com/gitbucket/gitbucket
|
||||
Group: System/Servers
|
||||
Source0: %{name}.war
|
||||
Source1: %{name}.init
|
||||
|
||||
60
doc/authenticator.md
Normal file
60
doc/authenticator.md
Normal file
@@ -0,0 +1,60 @@
|
||||
Authentication in Controller
|
||||
========
|
||||
GitBucket provides many [authenticators](https://github.com/gitbucket/gitbucket/blob/master/src/main/scala/gitbucket/core/util/Authenticator.scala) to access controlling in the controller.
|
||||
|
||||
For example, in the case of `RepositoryViwerController`,
|
||||
it references three authenticators: `ReadableUsersAuthenticator`, `ReferrerAuthenticator` and `CollaboratorsAuthenticator`.
|
||||
|
||||
```scala
|
||||
class RepositoryViewerController extends RepositoryViewerControllerBase
|
||||
with RepositoryService with AccountService with ActivityService with IssuesService with WebHookService with CommitsService
|
||||
with ReadableUsersAuthenticator with ReferrerAuthenticator with CollaboratorsAuthenticator with PullRequestService with CommitStatusService
|
||||
with WebHookPullRequestService with WebHookPullRequestReviewCommentService
|
||||
|
||||
trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
self: RepositoryService with AccountService with ActivityService with IssuesService with WebHookService with CommitsService
|
||||
with ReadableUsersAuthenticator with ReferrerAuthenticator with CollaboratorsAuthenticator with PullRequestService with CommitStatusService
|
||||
with WebHookPullRequestService with WebHookPullRequestReviewCommentService =>
|
||||
|
||||
...
|
||||
```
|
||||
|
||||
Authenticators provides a method to add guard to actions in the controller:
|
||||
|
||||
- `ReadableUsersAuthenticator` provides `readableUsersOnly` method
|
||||
- `ReferrerAuthenticator` provides `referrersOnly` method
|
||||
- `CollaboratorsAuthenticator` provides `collaboratorsOnly` method
|
||||
|
||||
These methods are available in each actions as below:
|
||||
|
||||
```scala
|
||||
// Allows only the repository owner (or manager for group repository) and administrators.
|
||||
get("/:owner/:repository/tree/*")(referrersOnly { repository =>
|
||||
...
|
||||
})
|
||||
|
||||
// Allows only collaborators and administrators.
|
||||
get("/:owner/:repository/new/*")(collaboratorsOnly { repository =>
|
||||
...
|
||||
})
|
||||
|
||||
// Allows only signed in users which can access the repository.
|
||||
post("/:owner/:repository/commit/:id/comment/new", commentForm)(readableUsersOnly { (form, repository) =>
|
||||
...
|
||||
})
|
||||
```
|
||||
|
||||
Currently, GitBucket provides below authenticators:
|
||||
|
||||
|Trait | Method | Description |
|
||||
|--------------------------|-----------------|--------------------------------------------------------------------------------------|
|
||||
|OneselfAuthenticator |oneselfOnly |Allows only oneself and administrators. |
|
||||
|OwnerAuthenticator |ownerOnly |Allows only the repository owner and administrators. |
|
||||
|UsersAuthenticator |usersOnly |Allows only signed in users. |
|
||||
|AdminAuthenticator |adminOnly |Allows only administrators. |
|
||||
|CollaboratorsAuthenticator|collaboratorsOnly|Allows only collaborators and administrators. |
|
||||
|ReferrerAuthenticator |referrersOnly |Allows only the repository owner (or manager for group repository) and administrators.|
|
||||
|ReadableUsersAuthenticator|readableUsersOnly|Allows only signed in users which can access the repository. |
|
||||
|GroupManagerAuthenticator |managersOnly |Allows only the group managers. |
|
||||
|
||||
Of course, if you make a new plugin, you can define a your own authenticator according to requirement in your plugin.
|
||||
@@ -2,7 +2,7 @@ Automatic Schema Updating
|
||||
========
|
||||
GitBucket uses H2 database to manage project and account data. GitBucket updates database schema automatically in the first run after the upgrading.
|
||||
|
||||
To release a new version of GitBucket, add the version definition to the [gitbucket.core.servlet.AutoUpdate](https://github.com/takezoe/gitbucket/blob/master/src/main/scala/gitbucket/core/servlet/AutoUpdate.scala) at first.
|
||||
To release a new version of GitBucket, add the version definition to the [gitbucket.core.servlet.AutoUpdate](https://github.com/gitbucket/gitbucket/blob/master/src/main/scala/gitbucket/core/servlet/AutoUpdate.scala) at first.
|
||||
|
||||
```scala
|
||||
object AutoUpdate {
|
||||
@@ -16,7 +16,7 @@ object AutoUpdate {
|
||||
...
|
||||
```
|
||||
|
||||
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```.
|
||||
Next, add a SQL file which updates database schema into [/src/main/resources/update/](https://github.com/gitbucket/gitbucket/tree/master/src/main/resources/update) as ```MAJOR_MINOR.sql```.
|
||||
|
||||
GitBucket stores the current version to ```GITBUCKET_HOME/version``` and checks it at start-up. If the stored version differs from the actual version, it executes differences of SQL files between the stored version and the actual version. And ```GITBUCKET_HOME/version``` is updated by the actual version.
|
||||
|
||||
|
||||
@@ -1,48 +1,56 @@
|
||||
About Action in Issue Comment
|
||||
========
|
||||
After the issue creation at GitBucket, users can add comments or close it.
|
||||
The details are saved at ```ISSUE_COMMENT``` table.
|
||||
The details are saved at `ISSUE_COMMENT` table.
|
||||
|
||||
To determine if it was any operation, you see the ```ACTION``` column.
|
||||
To determine if it was any operation, you see the `ACTION` column.
|
||||
And in the case of some actions, `CONTENT` column value contains additional information.
|
||||
|
||||
|ACTION|
|
||||
|--------|
|
||||
|comment|
|
||||
|close_comment|
|
||||
|reopen_comment|
|
||||
|close|
|
||||
|reopen|
|
||||
|commit|
|
||||
|merge|
|
||||
|delete_branch|
|
||||
|refer|
|
||||
|ACTION |CONTENT |
|
||||
|---------------|-----------------|
|
||||
|comment |comment |
|
||||
|close_comment |comment |
|
||||
|reopen_comment |comment |
|
||||
|close |"Close" |
|
||||
|reopen |"Reopen" |
|
||||
|commit |comment commitId |
|
||||
|merge |comment |
|
||||
|delete_branch |branchName |
|
||||
|refer |issueId:title |
|
||||
|
||||
### comment
|
||||
|
||||
#####comment
|
||||
This value is saved when users have made a normal comment.
|
||||
|
||||
#####close_comment, reopen_comment
|
||||
### close_comment, reopen_comment
|
||||
|
||||
These values are saved when users have reopened or closed the issue with comments.
|
||||
|
||||
#####close, reopen
|
||||
### 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.
|
||||
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.
|
||||
### 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
|
||||
### merge
|
||||
|
||||
This value is saved when users have merged the pull request.
|
||||
At the same time, store the message to the ```CONTENT``` column.
|
||||
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
|
||||
### 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.
|
||||
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```.
|
||||
### 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`.
|
||||
|
||||
@@ -1,56 +1,26 @@
|
||||
How to run from the source tree
|
||||
========
|
||||
|
||||
for Testers
|
||||
Run for Development
|
||||
--------
|
||||
|
||||
If you want to test GitBucket, input following command at the root directory of the source tree.
|
||||
|
||||
```
|
||||
C:\gitbucket> sbt ~container:start
|
||||
$ sbt ~jetty: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:
|
||||
|
||||
Windows:
|
||||
|
||||
```
|
||||
C:\gitbucket> sbt
|
||||
...
|
||||
> container:start
|
||||
...
|
||||
> ~ ;copy-resources;aux-compile
|
||||
```
|
||||
|
||||
Linux:
|
||||
|
||||
```
|
||||
~/gitbucket$ ./sbt.sh
|
||||
...
|
||||
> container:start
|
||||
...
|
||||
> ~ ;copy-resources;aux-compile
|
||||
```
|
||||
Source code modification is detected and reloaded automatically. You can modify logging configuration by editing `src/main/resources/logback-dev.xml`.
|
||||
|
||||
Build war file
|
||||
--------
|
||||
|
||||
To build war file, run the following command:
|
||||
|
||||
Windows:
|
||||
|
||||
```
|
||||
C:\gitbucket> sbt package
|
||||
```
|
||||
|
||||
Linux:
|
||||
|
||||
```
|
||||
~/gitbucket$ ./sbt.sh package
|
||||
$ sbt package
|
||||
```
|
||||
|
||||
`gitbucket_2.11-x.x.x.war` is generated into `target/scala-2.11`.
|
||||
|
||||
@@ -3,7 +3,7 @@ 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)
|
||||
* [Authentication in Controller](authenticator.md)
|
||||
* [About Action in Issue Comment](comment_action.md)
|
||||
* [Activity Types](activity.md)
|
||||
* [Notification Email](notification.md)
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
import sbt._
|
||||
import Keys._
|
||||
import org.scalatra.sbt._
|
||||
import com.typesafe.sbteclipse.plugin.EclipsePlugin.EclipseKeys
|
||||
import com.earldouglas.xwp.JettyPlugin
|
||||
import play.twirl.sbt.SbtTwirl
|
||||
import play.twirl.sbt.Import.TwirlKeys._
|
||||
import sbtassembly._
|
||||
import sbt.Keys._
|
||||
import sbt._
|
||||
import sbtassembly.AssemblyKeys._
|
||||
import sbtassembly._
|
||||
import JettyPlugin.autoImport._
|
||||
|
||||
object MyBuild extends Build {
|
||||
val Organization = "gitbucket"
|
||||
val Name = "gitbucket"
|
||||
val Version = "3.7.0"
|
||||
val Version = "3.10.1"
|
||||
val ScalaVersion = "2.11.6"
|
||||
val ScalatraVersion = "2.3.1"
|
||||
|
||||
@@ -18,7 +17,7 @@ object MyBuild extends Build {
|
||||
"gitbucket",
|
||||
file(".")
|
||||
)
|
||||
.settings(ScalatraPlugin.scalatraWithJRebel: _*)
|
||||
// .settings(ScalatraPlugin.scalatraWithJRebel: _*)
|
||||
.settings(
|
||||
test in assembly := {},
|
||||
assemblyMergeStrategy in assembly := {
|
||||
@@ -49,32 +48,33 @@ object MyBuild extends Build {
|
||||
"org.scalatra" %% "scalatra-specs2" % ScalatraVersion % "test",
|
||||
"org.scalatra" %% "scalatra-json" % ScalatraVersion,
|
||||
"org.json4s" %% "json4s-jackson" % "3.2.11",
|
||||
"jp.sf.amateras" %% "scalatra-forms" % "0.1.0",
|
||||
"jp.sf.amateras" %% "scalatra-forms" % "0.2.0",
|
||||
"commons-io" % "commons-io" % "2.4",
|
||||
"io.github.gitbucket" % "markedj" % "1.0.3",
|
||||
"io.github.gitbucket" % "markedj" % "1.0.6-SNAPSHOT",
|
||||
"org.apache.commons" % "commons-compress" % "1.9",
|
||||
"org.apache.commons" % "commons-email" % "1.3.3",
|
||||
"org.apache.httpcomponents" % "httpclient" % "4.3.6",
|
||||
"org.apache.sshd" % "apache-sshd" % "0.11.0",
|
||||
"org.apache.sshd" % "apache-sshd" % "1.0.0",
|
||||
"org.apache.tika" % "tika-core" % "1.10",
|
||||
"com.typesafe.slick" %% "slick" % "2.1.0",
|
||||
"com.novell.ldap" % "jldap" % "2009-10-07",
|
||||
"com.h2database" % "h2" % "1.4.180",
|
||||
// "ch.qos.logback" % "logback-classic" % "1.0.13" % "runtime",
|
||||
"org.eclipse.jetty" % "jetty-webapp" % "8.1.16.v20140903" % "container;provided",
|
||||
"org.eclipse.jetty.orbit" % "javax.servlet" % "3.0.0.v201112011016" % "container;provided;test" artifacts Artifact("javax.servlet", "jar", "jar"),
|
||||
"ch.qos.logback" % "logback-classic" % "1.1.1",
|
||||
"org.eclipse.jetty" % "jetty-webapp" % "8.1.16.v20140903" % "provided",
|
||||
"javax.servlet" % "javax.servlet-api" % "3.0.1" % "provided",
|
||||
"junit" % "junit" % "4.12" % "test",
|
||||
"com.mchange" % "c3p0" % "0.9.5",
|
||||
"com.mchange" % "c3p0" % "0.9.5.2",
|
||||
"com.typesafe" % "config" % "1.2.1",
|
||||
"com.typesafe.akka" %% "akka-actor" % "2.3.10",
|
||||
"com.enragedginger" %% "akka-quartz-scheduler" % "1.3.0-akka-2.3.x" exclude("c3p0","c3p0")
|
||||
),
|
||||
play.twirl.sbt.Import.TwirlKeys.templateImports += "gitbucket.core._",
|
||||
EclipseKeys.withSource := true,
|
||||
javacOptions in compile ++= Seq("-target", "7", "-source", "7"),
|
||||
javaOptions in Jetty += "-Dlogback.configurationFile=/logback-dev.xml",
|
||||
testOptions in Test += Tests.Argument(TestFrameworks.Specs2, "junitxml", "console"),
|
||||
javaOptions in Test += "-Dgitbucket.home=target/gitbucket_home_for_test",
|
||||
testOptions in Test += Tests.Setup( () => new java.io.File("target/gitbucket_home_for_test").mkdir() ),
|
||||
fork in Test := true,
|
||||
packageOptions += Package.MainClass("JettyLauncher")
|
||||
).enablePlugins(SbtTwirl)
|
||||
).enablePlugins(SbtTwirl, JettyPlugin)
|
||||
}
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
scalacOptions ++= Seq("-unchecked", "-deprecation", "-feature")
|
||||
|
||||
addSbtPlugin("com.typesafe.sbteclipse" % "sbteclipse-plugin" % "2.4.0")
|
||||
addSbtPlugin("com.github.mpeltonen" % "sbt-idea" % "1.6.0")
|
||||
addSbtPlugin("org.scalatra.sbt" % "scalatra-sbt" % "0.3.5")
|
||||
addSbtPlugin("com.typesafe.sbt" % "sbt-twirl" % "1.0.4")
|
||||
addSbtPlugin("com.timushev.sbt" % "sbt-updates" % "0.1.8")
|
||||
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.12.0")
|
||||
addSbtPlugin("com.typesafe.sbt" % "sbt-twirl" % "1.0.4")
|
||||
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.12.0")
|
||||
addSbtPlugin("com.earldouglas" % "xsbt-web-plugin" % "2.1.0")
|
||||
@@ -55,7 +55,12 @@
|
||||
tofile="${target.dir}/scala-${scala.version}/gitbucket.war"/>
|
||||
</target>
|
||||
|
||||
<target name="all" depends="rename">
|
||||
<target name="checksum" depends="rename">
|
||||
<checksum file="${target.dir}/scala-${scala.version}/gitbucket.war" algorithm="MD5" format="MD5SUM" forceOverwrite="yes" fileext=".md5"/>
|
||||
<checksum file="${target.dir}/scala-${scala.version}/gitbucket.war" algorithm="SHA" format="MD5SUM" forceOverwrite="yes" fileext=".sha1"/>
|
||||
</target>
|
||||
|
||||
<target name="all" depends="checksum">
|
||||
</target>
|
||||
|
||||
|
||||
|
||||
26
src/main/resources/logback-dev.xml
Normal file
26
src/main/resources/logback-dev.xml
Normal file
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<configuration>
|
||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
|
||||
<file>gitbucket.log</file>
|
||||
<encoder>
|
||||
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<root level="INFO">
|
||||
<appender-ref ref="STDOUT" />
|
||||
</root>
|
||||
|
||||
<!--
|
||||
<logger name="service.WebHookService" level="DEBUG" />
|
||||
<logger name="servlet" level="DEBUG" />
|
||||
-->
|
||||
<logger name="scala.slick.jdbc.JdbcBackend.statement" level="DEBUG" />
|
||||
|
||||
</configuration>
|
||||
@@ -6,12 +6,23 @@
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<!--
|
||||
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
|
||||
<file>gitbucket.log</file>
|
||||
<encoder>
|
||||
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
-->
|
||||
|
||||
<root level="INFO">
|
||||
<appender-ref ref="STDOUT" />
|
||||
</root>
|
||||
|
||||
|
||||
<!--
|
||||
<logger name="service.WebHookService" level="DEBUG" />
|
||||
<logger name="servlet" level="DEBUG" />
|
||||
<logger name="scala.slick.jdbc.JdbcBackend.statement" level="DEBUG" />
|
||||
-->
|
||||
|
||||
</configuration>
|
||||
5
src/main/resources/reference.conf
Normal file
5
src/main/resources/reference.conf
Normal file
@@ -0,0 +1,5 @@
|
||||
c3p0 {
|
||||
privilegeSpawnedThreads=true
|
||||
contextClassLoaderSource=library
|
||||
}
|
||||
|
||||
@@ -128,7 +128,7 @@ INSERT INTO ACCOUNT (
|
||||
'root@localhost',
|
||||
'dc76e9f0c0006e8f919e0c515c66dbba3982f785',
|
||||
true,
|
||||
'https://github.com/takezoe/gitbucket',
|
||||
'https://github.com/gitbucket/gitbucket',
|
||||
SYSDATE,
|
||||
SYSDATE,
|
||||
NULL
|
||||
|
||||
55
src/main/resources/update/3_9.sql
Normal file
55
src/main/resources/update/3_9.sql
Normal file
@@ -0,0 +1,55 @@
|
||||
DROP TABLE IF EXISTS WEB_HOOK_EVENT;
|
||||
|
||||
CREATE TABLE WEB_HOOK_EVENT(
|
||||
USER_NAME VARCHAR(100) NOT NULL,
|
||||
REPOSITORY_NAME VARCHAR(100) NOT NULL,
|
||||
URL VARCHAR(200) NOT NULL,
|
||||
EVENT VARCHAR(30) NOT NULL
|
||||
);
|
||||
|
||||
ALTER TABLE WEB_HOOK_EVENT ADD CONSTRAINT IDX_WEB_HOOK_EVENT_PK PRIMARY KEY (USER_NAME, REPOSITORY_NAME, URL, EVENT);
|
||||
ALTER TABLE WEB_HOOK_EVENT ADD CONSTRAINT IDX_WEB_HOOK_EVENT_FK0 FOREIGN KEY (USER_NAME, REPOSITORY_NAME, URL) REFERENCES WEB_HOOK (USER_NAME, REPOSITORY_NAME, URL)
|
||||
ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
CREATE TEMPORARY TABLE TMP_EVENTS (EVENT VARCHAR(30));
|
||||
|
||||
INSERT INTO TMP_EVENTS VALUES ('push'),('issue_comment'),('issues'),('pull_request');
|
||||
|
||||
INSERT INTO WEB_HOOK_EVENT (USER_NAME, REPOSITORY_NAME, URL, EVENT)
|
||||
SELECT USER_NAME, REPOSITORY_NAME, URL, EVENT
|
||||
FROM WEB_HOOK, TMP_EVENTS;
|
||||
|
||||
DROP TABLE TMP_EVENTS;
|
||||
|
||||
ALTER TABLE COMMIT_COMMENT ADD COLUMN ISSUE_ID INT;
|
||||
|
||||
CREATE OR REPLACE VIEW ISSUE_OUTLINE_VIEW AS
|
||||
SELECT
|
||||
A.USER_NAME,
|
||||
A.REPOSITORY_NAME,
|
||||
A.ISSUE_ID,
|
||||
NVL(B.COMMENT_COUNT, 0) + NVL(C.COMMENT_COUNT, 0) AS COMMENT_COUNT
|
||||
FROM ISSUE A
|
||||
LEFT OUTER JOIN (
|
||||
SELECT USER_NAME, REPOSITORY_NAME, ISSUE_ID, COUNT(COMMENT_ID) AS COMMENT_COUNT FROM ISSUE_COMMENT
|
||||
WHERE ACTION IN ('comment', 'close_comment', 'reopen_comment')
|
||||
GROUP BY USER_NAME, REPOSITORY_NAME, ISSUE_ID
|
||||
) B
|
||||
ON (A.USER_NAME = B.USER_NAME AND A.REPOSITORY_NAME = B.REPOSITORY_NAME AND A.ISSUE_ID = B.ISSUE_ID)
|
||||
LEFT OUTER JOIN (
|
||||
SELECT USER_NAME, REPOSITORY_NAME, ISSUE_ID, COUNT(COMMENT_ID) AS COMMENT_COUNT FROM COMMIT_COMMENT
|
||||
GROUP BY USER_NAME, REPOSITORY_NAME, ISSUE_ID
|
||||
) C
|
||||
ON (A.USER_NAME = C.USER_NAME AND A.REPOSITORY_NAME = C.REPOSITORY_NAME AND A.ISSUE_ID = C.ISSUE_ID);
|
||||
|
||||
|
||||
UPDATE COMMIT_COMMENT C SET (ISSUE_ID) = (
|
||||
SELECT MAX(P.ISSUE_ID)
|
||||
FROM PULL_REQUEST P
|
||||
WHERE
|
||||
C.USER_NAME = P.USER_NAME AND
|
||||
C.REPOSITORY_NAME = P.REPOSITORY_NAME AND
|
||||
C.COMMIT_ID = P.COMMIT_ID_TO
|
||||
);
|
||||
|
||||
ALTER TABLE COMMIT_COMMENT DROP COLUMN PULL_REQUEST;
|
||||
@@ -20,13 +20,21 @@ case class ApiCommit(
|
||||
removed: List[String],
|
||||
modified: List[String],
|
||||
author: ApiPersonIdent,
|
||||
committer: ApiPersonIdent)(repositoryName:RepositoryName) extends FieldSerializable{
|
||||
val url = ApiPath(s"/api/v3/${repositoryName.fullName}/commits/${id}")
|
||||
val html_url = ApiPath(s"/${repositoryName.fullName}/commit/${id}")
|
||||
committer: ApiPersonIdent)(repositoryName:RepositoryName, urlIsHtmlUrl: Boolean) extends FieldSerializable{
|
||||
val url = if(urlIsHtmlUrl){
|
||||
ApiPath(s"/${repositoryName.fullName}/commit/${id}")
|
||||
}else{
|
||||
ApiPath(s"/api/v3/${repositoryName.fullName}/commits/${id}")
|
||||
}
|
||||
val html_url = if(urlIsHtmlUrl){
|
||||
None
|
||||
}else{
|
||||
Some(ApiPath(s"/${repositoryName.fullName}/commit/${id}"))
|
||||
}
|
||||
}
|
||||
|
||||
object ApiCommit{
|
||||
def apply(git: Git, repositoryName: RepositoryName, commit: CommitInfo): ApiCommit = {
|
||||
def apply(git: Git, repositoryName: RepositoryName, commit: CommitInfo, urlIsHtmlUrl: Boolean = false): ApiCommit = {
|
||||
val diffs = JGitUtil.getDiffs(git, commit.id, false)
|
||||
ApiCommit(
|
||||
id = commit.id,
|
||||
@@ -43,6 +51,7 @@ object ApiCommit{
|
||||
},
|
||||
author = ApiPersonIdent.author(commit),
|
||||
committer = ApiPersonIdent.committer(commit)
|
||||
)(repositoryName)
|
||||
)(repositoryName, urlIsHtmlUrl)
|
||||
}
|
||||
def forPushPayload(git: Git, repositoryName: RepositoryName, commit: CommitInfo): ApiCommit = apply(git, repositoryName, commit, true)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
package gitbucket.core.api
|
||||
|
||||
import gitbucket.core.util.RepositoryName
|
||||
import gitbucket.core.model.CommitComment
|
||||
|
||||
import java.util.Date
|
||||
|
||||
/**
|
||||
* https://developer.github.com/v3/activity/events/types/#pullrequestreviewcommentevent
|
||||
*/
|
||||
case class ApiPullRequestReviewComment(
|
||||
id: Int, // 29724692
|
||||
// "diff_hunk": "@@ -1 +1 @@\n-# public-repo",
|
||||
path: String, // "README.md",
|
||||
// "position": 1,
|
||||
// "original_position": 1,
|
||||
commit_id: String, // "0d1a26e67d8f5eaf1f6ba5c57fc3c7d91ac0fd1c",
|
||||
// "original_commit_id": "0d1a26e67d8f5eaf1f6ba5c57fc3c7d91ac0fd1c",
|
||||
user: ApiUser,
|
||||
body: String, // "Maybe you should use more emojji on this line.",
|
||||
created_at: Date, // "2015-05-05T23:40:27Z",
|
||||
updated_at: Date // "2015-05-05T23:40:27Z",
|
||||
)(repositoryName:RepositoryName, issueId: Int) extends FieldSerializable {
|
||||
// "url": "https://api.github.com/repos/baxterthehacker/public-repo/pulls/comments/29724692",
|
||||
val url = ApiPath(s"/api/v3/repos/${repositoryName.fullName}/pulls/comments/${id}")
|
||||
// "html_url": "https://github.com/baxterthehacker/public-repo/pull/1#discussion_r29724692",
|
||||
val html_url = ApiPath(s"/${repositoryName.fullName}/pull/${issueId}#discussion_r${id}")
|
||||
// "pull_request_url": "https://api.github.com/repos/baxterthehacker/public-repo/pulls/1",
|
||||
val pull_request_url = ApiPath(s"/api/v3/repos/${repositoryName.fullName}/pulls/${issueId}")
|
||||
|
||||
/*
|
||||
"_links": {
|
||||
"self": {
|
||||
"href": "https://api.github.com/repos/baxterthehacker/public-repo/pulls/comments/29724692"
|
||||
},
|
||||
"html": {
|
||||
"href": "https://github.com/baxterthehacker/public-repo/pull/1#discussion_r29724692"
|
||||
},
|
||||
"pull_request": {
|
||||
"href": "https://api.github.com/repos/baxterthehacker/public-repo/pulls/1"
|
||||
}
|
||||
}
|
||||
*/
|
||||
val _links = Map(
|
||||
"self" -> Map("href" -> url),
|
||||
"html" -> Map("href" -> html_url),
|
||||
"pull_request" -> Map("href" -> pull_request_url))
|
||||
}
|
||||
|
||||
object ApiPullRequestReviewComment{
|
||||
def apply(comment: CommitComment, commentedUser: ApiUser, repositoryName: RepositoryName, issueId: Int): ApiPullRequestReviewComment =
|
||||
new ApiPullRequestReviewComment(
|
||||
id = comment.commentId,
|
||||
path = comment.fileName.getOrElse(""),
|
||||
commit_id = comment.commitId,
|
||||
user = commentedUser,
|
||||
body = comment.content,
|
||||
created_at = comment.registeredDate,
|
||||
updated_at = comment.updatedDate
|
||||
)(repositoryName, issueId)
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
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/activity/events/types/#pushevent
|
||||
*/
|
||||
case class ApiPushCommit(
|
||||
id: String,
|
||||
message: String,
|
||||
timestamp: Date,
|
||||
added: List[String],
|
||||
removed: List[String],
|
||||
modified: List[String],
|
||||
author: ApiPersonIdent,
|
||||
committer: ApiPersonIdent)(repositoryName:RepositoryName) extends FieldSerializable {
|
||||
val url = ApiPath(s"/${repositoryName.fullName}/commit/${id}")
|
||||
}
|
||||
|
||||
object ApiPushCommit{
|
||||
def apply(commit: ApiCommit, repositoryName: RepositoryName): ApiPushCommit = ApiPushCommit(
|
||||
id = commit.id,
|
||||
message = commit.message,
|
||||
timestamp = commit.timestamp,
|
||||
added = commit.added,
|
||||
removed = commit.removed,
|
||||
modified = commit.modified,
|
||||
author = commit.author,
|
||||
committer = commit.committer)(repositoryName)
|
||||
def apply(git: Git, repositoryName: RepositoryName, commit: CommitInfo): ApiPushCommit =
|
||||
ApiPushCommit(ApiCommit(git, repositoryName, commit), repositoryName)
|
||||
}
|
||||
11
src/main/scala/gitbucket/core/api/ApiPusher.scala
Normal file
11
src/main/scala/gitbucket/core/api/ApiPusher.scala
Normal file
@@ -0,0 +1,11 @@
|
||||
package gitbucket.core.api
|
||||
|
||||
import gitbucket.core.model.Account
|
||||
|
||||
case class ApiPusher(name: String, email: String)
|
||||
|
||||
object ApiPusher {
|
||||
def apply(user: Account): ApiPusher = ApiPusher(
|
||||
name = user.userName,
|
||||
email = user.mailAddress)
|
||||
}
|
||||
@@ -13,10 +13,14 @@ case class ApiRepository(
|
||||
forks: Int,
|
||||
`private`: Boolean,
|
||||
default_branch: String,
|
||||
owner: ApiUser) {
|
||||
owner: ApiUser)(urlIsHtmlUrl: Boolean) {
|
||||
val forks_count = forks
|
||||
val watchers_count = watchers
|
||||
val url = ApiPath(s"/api/v3/repos/${full_name}")
|
||||
val url = if(urlIsHtmlUrl){
|
||||
ApiPath(s"/${full_name}")
|
||||
}else{
|
||||
ApiPath(s"/api/v3/repos/${full_name}")
|
||||
}
|
||||
val http_url = ApiPath(s"/git/${full_name}.git")
|
||||
val clone_url = ApiPath(s"/git/${full_name}.git")
|
||||
val html_url = ApiPath(s"/${full_name}")
|
||||
@@ -27,7 +31,8 @@ object ApiRepository{
|
||||
repository: Repository,
|
||||
owner: ApiUser,
|
||||
forkedCount: Int =0,
|
||||
watchers: Int = 0): ApiRepository =
|
||||
watchers: Int = 0,
|
||||
urlIsHtmlUrl: Boolean = false): ApiRepository =
|
||||
ApiRepository(
|
||||
name = repository.repositoryName,
|
||||
full_name = s"${repository.userName}/${repository.repositoryName}",
|
||||
@@ -37,7 +42,7 @@ object ApiRepository{
|
||||
`private` = repository.isPrivate,
|
||||
default_branch = repository.defaultBranch,
|
||||
owner = owner
|
||||
)
|
||||
)(urlIsHtmlUrl)
|
||||
|
||||
def apply(repositoryInfo: RepositoryInfo, owner: ApiUser): ApiRepository =
|
||||
ApiRepository(repositoryInfo.repository, owner, forkedCount=repositoryInfo.forkedCount)
|
||||
@@ -45,4 +50,7 @@ object ApiRepository{
|
||||
def apply(repositoryInfo: RepositoryInfo, owner: Account): ApiRepository =
|
||||
this(repositoryInfo.repository, ApiUser(owner))
|
||||
|
||||
def forPushPayload(repositoryInfo: RepositoryInfo, owner: ApiUser): ApiRepository =
|
||||
ApiRepository(repositoryInfo.repository, owner, forkedCount=repositoryInfo.forkedCount, urlIsHtmlUrl=true)
|
||||
|
||||
}
|
||||
|
||||
19
src/main/scala/gitbucket/core/api/CreateARepository.scala
Normal file
19
src/main/scala/gitbucket/core/api/CreateARepository.scala
Normal file
@@ -0,0 +1,19 @@
|
||||
package gitbucket.core.api
|
||||
|
||||
/**
|
||||
* https://developer.github.com/v3/repos/#create
|
||||
* api form
|
||||
*/
|
||||
case class CreateARepository(
|
||||
name: String,
|
||||
description: Option[String],
|
||||
`private`: Boolean = false,
|
||||
auto_init: Boolean = false
|
||||
) {
|
||||
def isValid: Boolean = {
|
||||
name.length<=40 &&
|
||||
name.matches("[a-zA-Z0-9\\-\\+_.]+") &&
|
||||
!name.startsWith("_") &&
|
||||
!name.startsWith("-")
|
||||
}
|
||||
}
|
||||
@@ -5,40 +5,48 @@ 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)
|
||||
|
||||
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 JString(s) => Try(parserISO.parseDateTime(s)).toOption.map(_.toDate).getOrElse(throw new MappingException("Can't convert " + s + " to Date")) },
|
||||
{ case x: Date => JString(parserISO.print(new DateTime(x).withZone(DateTimeZone.UTC))) }
|
||||
)
|
||||
) + FieldSerializer[ApiUser]() + FieldSerializer[ApiPullRequest]() + FieldSerializer[ApiRepository]() +
|
||||
FieldSerializer[ApiCommitListItem.Parent]() + FieldSerializer[ApiCommitListItem]() + FieldSerializer[ApiCommitListItem.Commit]() +
|
||||
FieldSerializer[ApiCommitStatus]() + FieldSerializer[FieldSerializable]() + FieldSerializer[ApiCombinedCommitStatus]() +
|
||||
FieldSerializer[ApiPullRequest.Commit]() + FieldSerializer[ApiIssue]() + FieldSerializer[ApiComment]()
|
||||
|
||||
) + FieldSerializer[ApiUser]() +
|
||||
FieldSerializer[ApiPullRequest]() +
|
||||
FieldSerializer[ApiRepository]() +
|
||||
FieldSerializer[ApiCommitListItem.Parent]() +
|
||||
FieldSerializer[ApiCommitListItem]() +
|
||||
FieldSerializer[ApiCommitListItem.Commit]() +
|
||||
FieldSerializer[ApiCommitStatus]() +
|
||||
FieldSerializer[FieldSerializable]() +
|
||||
FieldSerializer[ApiCombinedCommitStatus]() +
|
||||
FieldSerializer[ApiPullRequest.Commit]() +
|
||||
FieldSerializer[ApiIssue]() +
|
||||
FieldSerializer[ApiComment]()
|
||||
|
||||
def apiPathSerializer(c: Context) = new CustomSerializer[ApiPath](format =>
|
||||
(
|
||||
{
|
||||
case JString(s) if s.startsWith(c.baseUrl) => ApiPath(s.substring(c.baseUrl.length))
|
||||
case JString(s) => throw new MappingException("Can't convert " + s + " to ApiPath")
|
||||
},
|
||||
{
|
||||
case ApiPath(path) => JString(c.baseUrl+path)
|
||||
}
|
||||
)
|
||||
(
|
||||
{
|
||||
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))
|
||||
|
||||
}
|
||||
|
||||
@@ -90,8 +90,8 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
case class ForkRepositoryForm(owner: String, name: String)
|
||||
|
||||
val newRepositoryForm = mapping(
|
||||
"owner" -> trim(label("Owner" , text(required, maxlength(40), identifier, existsAccount))),
|
||||
"name" -> trim(label("Repository name", text(required, maxlength(40), repository, uniqueRepository))),
|
||||
"owner" -> trim(label("Owner" , text(required, maxlength(100), identifier, existsAccount))),
|
||||
"name" -> trim(label("Repository name", text(required, maxlength(100), repository, uniqueRepository))),
|
||||
"description" -> trim(label("Description" , optional(text()))),
|
||||
"isPrivate" -> trim(label("Repository Type", boolean())),
|
||||
"createReadme" -> trim(label("Create README" , boolean()))
|
||||
@@ -212,6 +212,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
// // Remove from GROUP_MEMBER, COLLABORATOR and REPOSITORY
|
||||
// removeUserRelatedData(userName)
|
||||
|
||||
removeUserRelatedData(userName)
|
||||
updateAccount(account.copy(isRemoved = true))
|
||||
}
|
||||
|
||||
@@ -366,56 +367,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
post("/new", newRepositoryForm)(usersOnly { form =>
|
||||
LockUtil.lock(s"${form.owner}/${form.name}"){
|
||||
if(getRepository(form.owner, form.name, context.baseUrl).isEmpty){
|
||||
val ownerAccount = getAccountByUserName(form.owner).get
|
||||
val loginAccount = context.loginAccount.get
|
||||
val loginUserName = loginAccount.userName
|
||||
|
||||
// Insert to the database at first
|
||||
createRepository(form.name, form.owner, form.description, form.isPrivate)
|
||||
|
||||
// Add collaborators for group repository
|
||||
if(ownerAccount.isGroupAccount){
|
||||
getGroupMembers(form.owner).foreach { member =>
|
||||
addCollaborator(form.owner, form.name, member.userName)
|
||||
}
|
||||
}
|
||||
|
||||
// Insert default labels
|
||||
insertDefaultLabels(form.owner, form.name)
|
||||
|
||||
// Create the actual repository
|
||||
val gitdir = getRepositoryDir(form.owner, form.name)
|
||||
JGitUtil.initRepository(gitdir)
|
||||
|
||||
if(form.createReadme){
|
||||
using(Git.open(gitdir)){ git =>
|
||||
val builder = DirCache.newInCore.builder()
|
||||
val inserter = git.getRepository.newObjectInserter()
|
||||
val headId = git.getRepository.resolve(Constants.HEAD + "^{commit}")
|
||||
val content = if(form.description.nonEmpty){
|
||||
form.name + "\n" +
|
||||
"===============\n" +
|
||||
"\n" +
|
||||
form.description.get
|
||||
} else {
|
||||
form.name + "\n" +
|
||||
"===============\n"
|
||||
}
|
||||
|
||||
builder.add(JGitUtil.createDirCacheEntry("README.md", FileMode.REGULAR_FILE,
|
||||
inserter.insert(Constants.OBJ_BLOB, content.getBytes("UTF-8"))))
|
||||
builder.finish()
|
||||
|
||||
JGitUtil.createNewCommit(git, inserter, headId, builder.getDirCache.writeTree(inserter),
|
||||
Constants.HEAD, loginAccount.fullName, loginAccount.mailAddress, "Initial commit")
|
||||
}
|
||||
}
|
||||
|
||||
// Create Wiki repository
|
||||
createWikiRepository(loginAccount, form.owner, form.name)
|
||||
|
||||
// Record activity
|
||||
recordCreateRepositoryActivity(form.owner, form.name, loginUserName)
|
||||
createRepository(form.owner, form.name, form.description, form.isPrivate, form.createReadme)
|
||||
}
|
||||
|
||||
// redirect to the repository
|
||||
@@ -423,6 +375,54 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* Create user repository
|
||||
* https://developer.github.com/v3/repos/#create
|
||||
*/
|
||||
post("/api/v3/user/repos")(usersOnly {
|
||||
val owner = context.loginAccount.get.userName
|
||||
(for {
|
||||
data <- extractFromJsonBody[CreateARepository] if data.isValid
|
||||
} yield {
|
||||
LockUtil.lock(s"${owner}/${data.name}") {
|
||||
if(getRepository(owner, data.name, context.baseUrl).isEmpty){
|
||||
createRepository(owner, data.name, data.description, data.`private`, data.auto_init)
|
||||
val repository = getRepository(owner, data.name, context.baseUrl).get
|
||||
JsonFormat(ApiRepository(repository, ApiUser(getAccountByUserName(owner).get)))
|
||||
} else {
|
||||
ApiError(
|
||||
"A repository with this name already exists on this account",
|
||||
Some("https://developer.github.com/v3/repos/#create")
|
||||
)
|
||||
}
|
||||
}
|
||||
}) getOrElse NotFound
|
||||
})
|
||||
|
||||
/**
|
||||
* Create group repository
|
||||
* https://developer.github.com/v3/repos/#create
|
||||
*/
|
||||
post("/api/v3/orgs/:org/repos")(managersOnly {
|
||||
val groupName = params("org")
|
||||
(for {
|
||||
data <- extractFromJsonBody[CreateARepository] if data.isValid
|
||||
} yield {
|
||||
LockUtil.lock(s"${groupName}/${data.name}") {
|
||||
if(getRepository(groupName, data.name, context.baseUrl).isEmpty){
|
||||
createRepository(groupName, data.name, data.description, data.`private`, data.auto_init)
|
||||
val repository = getRepository(groupName, data.name, context.baseUrl).get
|
||||
JsonFormat(ApiRepository(repository, ApiUser(getAccountByUserName(groupName).get)))
|
||||
} else {
|
||||
ApiError(
|
||||
"A repository with this name already exists for this group",
|
||||
Some("https://developer.github.com/v3/repos/#create")
|
||||
)
|
||||
}
|
||||
}
|
||||
}) getOrElse NotFound
|
||||
})
|
||||
|
||||
get("/:owner/:repository/fork")(readableUsersOnly { repository =>
|
||||
val loginAccount = context.loginAccount.get
|
||||
val loginUserName = loginAccount.userName
|
||||
@@ -496,6 +496,59 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
}
|
||||
})
|
||||
|
||||
private def createRepository(owner: String, name: String, description: Option[String], isPrivate: Boolean, createReadme: Boolean) {
|
||||
val ownerAccount = getAccountByUserName(owner).get
|
||||
val loginAccount = context.loginAccount.get
|
||||
val loginUserName = loginAccount.userName
|
||||
|
||||
// Insert to the database at first
|
||||
createRepository(name, owner, description, isPrivate)
|
||||
|
||||
// Add collaborators for group repository
|
||||
if(ownerAccount.isGroupAccount){
|
||||
getGroupMembers(owner).foreach { member =>
|
||||
addCollaborator(owner, name, member.userName)
|
||||
}
|
||||
}
|
||||
|
||||
// Insert default labels
|
||||
insertDefaultLabels(owner, name)
|
||||
|
||||
// Create the actual repository
|
||||
val gitdir = getRepositoryDir(owner, name)
|
||||
JGitUtil.initRepository(gitdir)
|
||||
|
||||
if(createReadme){
|
||||
using(Git.open(gitdir)){ git =>
|
||||
val builder = DirCache.newInCore.builder()
|
||||
val inserter = git.getRepository.newObjectInserter()
|
||||
val headId = git.getRepository.resolve(Constants.HEAD + "^{commit}")
|
||||
val content = if(description.nonEmpty){
|
||||
name + "\n" +
|
||||
"===============\n" +
|
||||
"\n" +
|
||||
description.get
|
||||
} else {
|
||||
name + "\n" +
|
||||
"===============\n"
|
||||
}
|
||||
|
||||
builder.add(JGitUtil.createDirCacheEntry("README.md", FileMode.REGULAR_FILE,
|
||||
inserter.insert(Constants.OBJ_BLOB, content.getBytes("UTF-8"))))
|
||||
builder.finish()
|
||||
|
||||
JGitUtil.createNewCommit(git, inserter, headId, builder.getDirCache.writeTree(inserter),
|
||||
Constants.HEAD, loginAccount.fullName, loginAccount.mailAddress, "Initial commit")
|
||||
}
|
||||
}
|
||||
|
||||
// Create Wiki repository
|
||||
createWikiRepository(loginAccount, owner, name)
|
||||
|
||||
// Record activity
|
||||
recordCreateRepositoryActivity(owner, name, loginUserName)
|
||||
}
|
||||
|
||||
private def insertDefaultLabels(userName: String, repositoryName: String): Unit = {
|
||||
createLabel(userName, repositoryName, "bug", "fc2929")
|
||||
createLabel(userName, repositoryName, "duplicate", "cccccc")
|
||||
|
||||
@@ -17,22 +17,22 @@ class FileUploadController extends ScalatraServlet with FileUploadSupport {
|
||||
configureMultipartHandling(MultipartConfig(maxFileSize = Some(3 * 1024 * 1024)))
|
||||
|
||||
post("/image"){
|
||||
execute { (file, fileId) =>
|
||||
execute({ (file, fileId) =>
|
||||
FileUtils.writeByteArrayToFile(new java.io.File(getTemporaryDir(session.getId), fileId), file.get)
|
||||
session += Keys.Session.Upload(fileId) -> file.name
|
||||
}
|
||||
}, FileUtil.isImage)
|
||||
}
|
||||
|
||||
post("/image/:owner/:repository"){
|
||||
execute { (file, fileId) =>
|
||||
post("/file/:owner/:repository"){
|
||||
execute({ (file, fileId) =>
|
||||
FileUtils.writeByteArrayToFile(new java.io.File(
|
||||
getAttachedDir(params("owner"), params("repository")),
|
||||
fileId + "." + FileUtil.getExtension(file.getName)), file.get)
|
||||
}
|
||||
}, FileUtil.isUploadableType)
|
||||
}
|
||||
|
||||
private def execute(f: (FileItem, String) => Unit) = fileParams.get("file") match {
|
||||
case Some(file) if(FileUtil.isImage(file.name)) =>
|
||||
private def execute(f: (FileItem, String) => Unit, mimeTypeChcker: (String) => Boolean) = fileParams.get("file") match {
|
||||
case Some(file) if(mimeTypeChcker(file.name)) =>
|
||||
defining(FileUtil.generateFileId){ fileId =>
|
||||
f(file, fileId)
|
||||
|
||||
|
||||
@@ -104,7 +104,7 @@ trait IndexControllerBase extends ControllerBase {
|
||||
})
|
||||
|
||||
/**
|
||||
* JSON APU for checking user existence.
|
||||
* JSON API for checking user existence.
|
||||
*/
|
||||
post("/_user/existence")(usersOnly {
|
||||
getAccountByUserName(params("userName")).isDefined
|
||||
|
||||
@@ -231,10 +231,20 @@ trait IssuesControllerBase extends ControllerBase {
|
||||
} getOrElse {
|
||||
contentType = formats("json")
|
||||
org.json4s.jackson.Serialization.write(
|
||||
Map("title" -> x.title,
|
||||
"content" -> Markdown.toHtml(x.content getOrElse "No description given.",
|
||||
repository, false, true, true, true, isEditable(x.userName, x.repositoryName, x.openedUserName))
|
||||
))
|
||||
Map(
|
||||
"title" -> x.title,
|
||||
"content" -> Markdown.toHtml(
|
||||
markdown = x.content getOrElse "No description given.",
|
||||
repository = repository,
|
||||
enableWikiLink = false,
|
||||
enableRefsLink = true,
|
||||
enableAnchor = true,
|
||||
enableLineBreaks = true,
|
||||
enableTaskList = true,
|
||||
hasWritePermission = isEditable(x.userName, x.repositoryName, x.openedUserName)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
} else Unauthorized
|
||||
} getOrElse NotFound
|
||||
@@ -249,9 +259,19 @@ trait IssuesControllerBase extends ControllerBase {
|
||||
} getOrElse {
|
||||
contentType = formats("json")
|
||||
org.json4s.jackson.Serialization.write(
|
||||
Map("content" -> view.Markdown.toHtml(x.content,
|
||||
repository, false, true, true, isEditable(x.userName, x.repositoryName, x.commentedUserName))
|
||||
))
|
||||
Map(
|
||||
"content" -> view.Markdown.toHtml(
|
||||
markdown = x.content,
|
||||
repository = repository,
|
||||
enableWikiLink = false,
|
||||
enableRefsLink = true,
|
||||
enableAnchor = true,
|
||||
enableLineBreaks = true,
|
||||
enableTaskList = true,
|
||||
hasWritePermission = isEditable(x.userName, x.repositoryName, x.commentedUserName)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
} else Unauthorized
|
||||
} getOrElse NotFound
|
||||
@@ -332,6 +352,7 @@ trait IssuesControllerBase extends ControllerBase {
|
||||
(Directory.getAttachedDir(repository.owner, repository.name) match {
|
||||
case dir if(dir.exists && dir.isDirectory) =>
|
||||
dir.listFiles.find(_.getName.startsWith(params("file") + ".")).map { file =>
|
||||
response.setHeader("Content-Disposition", f"""inline; filename=${file.getName}""")
|
||||
RawData(FileUtil.getMimeType(file.getName), file)
|
||||
}
|
||||
case _ => None
|
||||
@@ -352,6 +373,7 @@ trait IssuesControllerBase extends ControllerBase {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO Same method exists in PullRequestController. Should it moved to IssueService?
|
||||
private def createReferComment(owner: String, repository: String, fromIssue: Issue, message: String) = {
|
||||
StringUtil.extractIssueId(message).foreach { issueId =>
|
||||
val content = fromIssue.issueId + ":" + fromIssue.title
|
||||
|
||||
@@ -294,6 +294,9 @@ trait PullRequestsControllerBase extends ControllerBase {
|
||||
originRepositoryName <- if(originOwner == forkedOwner) {
|
||||
// Self repository
|
||||
Some(forkedRepository.name)
|
||||
} else if(forkedRepository.repository.originUserName.isEmpty){
|
||||
// when ForkedRepository is the original repository
|
||||
getForkedRepositories(forkedRepository.owner, forkedRepository.name).find(_._1 == originOwner).map(_._2)
|
||||
} else if(Some(originOwner) == forkedRepository.repository.originUserName){
|
||||
// Original repository
|
||||
forkedRepository.repository.originRepositoryName
|
||||
@@ -436,8 +439,11 @@ trait PullRequestsControllerBase extends ControllerBase {
|
||||
// call web hook
|
||||
callPullRequestWebHook("opened", repository, issueId, context.baseUrl, context.loginAccount.get)
|
||||
|
||||
// notifications
|
||||
getIssue(owner, name, issueId.toString) foreach { issue =>
|
||||
// extract references and create refer comment
|
||||
createReferComment(owner, name, issue, form.title + " " + form.content.getOrElse(""))
|
||||
|
||||
// notifications
|
||||
Notifier().toNotify(repository, issue, form.content.getOrElse("")){
|
||||
Notifier.msgPullRequest(s"${context.baseUrl}/${owner}/${name}/pull/${issueId}")
|
||||
}
|
||||
@@ -447,6 +453,19 @@ trait PullRequestsControllerBase extends ControllerBase {
|
||||
}
|
||||
})
|
||||
|
||||
// TODO Same method exists in IssueController. Should it moved to IssueService?
|
||||
private def createReferComment(owner: String, repository: String, fromIssue: Issue, message: String) = {
|
||||
StringUtil.extractIssueId(message).foreach { issueId =>
|
||||
val content = fromIssue.issueId + ":" + fromIssue.title
|
||||
if(getIssue(owner, repository, issueId).isDefined){
|
||||
// Not add if refer comment already exist.
|
||||
if(!getComments(owner, repository, issueId.toInt).exists { x => x.action == "refer" && x.content == content }) {
|
||||
createComment(owner, repository, context.loginAccount.get.userName, issueId.toInt, content, "refer")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses branch identifier and extracts owner and branch name as tuple.
|
||||
*
|
||||
|
||||
@@ -14,6 +14,7 @@ import org.apache.commons.io.FileUtils
|
||||
import org.scalatra.i18n.Messages
|
||||
import org.eclipse.jgit.api.Git
|
||||
import org.eclipse.jgit.lib.Constants
|
||||
import org.eclipse.jgit.lib.ObjectId
|
||||
|
||||
|
||||
class RepositorySettingsController extends RepositorySettingsControllerBase
|
||||
@@ -28,7 +29,7 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
||||
case class OptionsForm(repositoryName: String, description: Option[String], defaultBranch: String, isPrivate: Boolean)
|
||||
|
||||
val optionsForm = mapping(
|
||||
"repositoryName" -> trim(label("Description" , text(required, maxlength(40), identifier, renameRepositoryName))),
|
||||
"repositoryName" -> trim(label("Repository Name", text(required, maxlength(40), identifier, renameRepositoryName))),
|
||||
"description" -> trim(label("Description" , optional(text()))),
|
||||
"defaultBranch" -> trim(label("Default Branch" , text(required, maxlength(100)))),
|
||||
"isPrivate" -> trim(label("Repository Type", boolean()))
|
||||
@@ -42,10 +43,11 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
||||
)(CollaboratorForm.apply)
|
||||
|
||||
// for web hook url addition
|
||||
case class WebHookForm(url: String)
|
||||
case class WebHookForm(url: String, events: Set[WebHook.Event])
|
||||
|
||||
val webHookForm = mapping(
|
||||
"url" -> trim(label("url", text(required, webHook)))
|
||||
def webHookForm(update:Boolean) = mapping(
|
||||
"url" -> trim(label("url", text(required, webHook(update)))),
|
||||
"events" -> webhookEvents
|
||||
)(WebHookForm.apply)
|
||||
|
||||
// for transfer ownership
|
||||
@@ -138,14 +140,23 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
||||
* Display the web hook page.
|
||||
*/
|
||||
get("/:owner/:repository/settings/hooks")(ownerOnly { repository =>
|
||||
html.hooks(getWebHookURLs(repository.owner, repository.name), flash.get("url"), repository, flash.get("info"))
|
||||
html.hooks(getWebHooks(repository.owner, repository.name), repository, flash.get("info"))
|
||||
})
|
||||
|
||||
/**
|
||||
* Display the web hook edit page.
|
||||
*/
|
||||
get("/:owner/:repository/settings/hooks/new")(ownerOnly { repository =>
|
||||
val webhook = WebHook(repository.owner, repository.name, "")
|
||||
html.edithooks(webhook, Set(WebHook.Push), repository, flash.get("info"), true)
|
||||
})
|
||||
|
||||
/**
|
||||
* Add the web hook URL.
|
||||
*/
|
||||
post("/:owner/:repository/settings/hooks/add", webHookForm)(ownerOnly { (form, repository) =>
|
||||
addWebHookURL(repository.owner, repository.name, form.url)
|
||||
post("/:owner/:repository/settings/hooks/new", webHookForm(false))(ownerOnly { (form, repository) =>
|
||||
addWebHook(repository.owner, repository.name, form.url, form.events)
|
||||
flash += "info" -> s"Webhook ${form.url} created"
|
||||
redirect(s"/${repository.owner}/${repository.name}/settings/hooks")
|
||||
})
|
||||
|
||||
@@ -153,30 +164,87 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
||||
* Delete the web hook URL.
|
||||
*/
|
||||
get("/:owner/:repository/settings/hooks/delete")(ownerOnly { repository =>
|
||||
deleteWebHookURL(repository.owner, repository.name, params("url"))
|
||||
deleteWebHook(repository.owner, repository.name, params("url"))
|
||||
flash += "info" -> s"Webhook ${params("url")} deleted"
|
||||
redirect(s"/${repository.owner}/${repository.name}/settings/hooks")
|
||||
})
|
||||
|
||||
/**
|
||||
* Send the test request to registered web hook URLs.
|
||||
*/
|
||||
post("/:owner/:repository/settings/hooks/test", webHookForm)(ownerOnly { (form, repository) =>
|
||||
ajaxPost("/:owner/:repository/settings/hooks/test")(ownerOnly { repository =>
|
||||
def _headers(h: Array[org.apache.http.Header]): Array[Array[String]] = h.map { h => Array(h.getName, h.getValue) }
|
||||
|
||||
using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git =>
|
||||
import scala.collection.JavaConverters._
|
||||
val commits = if(repository.commitCount == 0) List.empty else git.log
|
||||
.add(git.getRepository.resolve(repository.repository.defaultBranch))
|
||||
.setMaxCount(3)
|
||||
.call.iterator.asScala.map(new CommitInfo(_))
|
||||
import scala.concurrent.duration._
|
||||
import scala.concurrent._
|
||||
import scala.util.control.NonFatal
|
||||
import org.apache.http.util.EntityUtils
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
|
||||
getAccountByUserName(repository.owner).foreach { ownerAccount =>
|
||||
callWebHook("push",
|
||||
List(WebHook(repository.owner, repository.name, form.url)),
|
||||
WebHookPushPayload(git, ownerAccount, "refs/heads/" + repository.repository.defaultBranch, repository, commits.toList, ownerAccount)
|
||||
val url = params("url")
|
||||
val dummyWebHookInfo = WebHook(repository.owner, repository.name, url)
|
||||
val dummyPayload = {
|
||||
val ownerAccount = getAccountByUserName(repository.owner).get
|
||||
val commits = if(repository.commitCount == 0) List.empty else git.log
|
||||
.add(git.getRepository.resolve(repository.repository.defaultBranch))
|
||||
.setMaxCount(4)
|
||||
.call.iterator.asScala.map(new CommitInfo(_)).toList
|
||||
val pushedCommit = commits.drop(1)
|
||||
|
||||
WebHookPushPayload(
|
||||
git = git,
|
||||
sender = ownerAccount,
|
||||
refName = "refs/heads/" + repository.repository.defaultBranch,
|
||||
repositoryInfo = repository,
|
||||
commits = pushedCommit,
|
||||
repositoryOwner = ownerAccount,
|
||||
oldId = commits.lastOption.map(_.id).map(ObjectId.fromString).getOrElse(ObjectId.zeroId()),
|
||||
newId = commits.headOption.map(_.id).map(ObjectId.fromString).getOrElse(ObjectId.zeroId())
|
||||
)
|
||||
}
|
||||
flash += "url" -> form.url
|
||||
flash += "info" -> "Test payload deployed!"
|
||||
|
||||
val (webHook, json, reqFuture, resFuture) = callWebHook(WebHook.Push, List(dummyWebHookInfo), dummyPayload).head
|
||||
|
||||
val toErrorMap: PartialFunction[Throwable, Map[String,String]] = {
|
||||
case e: java.net.UnknownHostException => Map("error"-> ("Unknown host " + e.getMessage))
|
||||
case e: java.lang.IllegalArgumentException => Map("error"-> ("invalid url"))
|
||||
case e: org.apache.http.client.ClientProtocolException => Map("error"-> ("invalid url"))
|
||||
case NonFatal(e) => Map("error"-> (e.getClass + " "+ e.getMessage))
|
||||
}
|
||||
|
||||
contentType = formats("json")
|
||||
org.json4s.jackson.Serialization.write(Map(
|
||||
"url" -> url,
|
||||
"request" -> Await.result(reqFuture.map(req => Map(
|
||||
"headers" -> _headers(req.getAllHeaders),
|
||||
"payload" -> json
|
||||
)).recover(toErrorMap), 20 seconds),
|
||||
"responce" -> Await.result(resFuture.map(res => Map(
|
||||
"status" -> res.getStatusLine(),
|
||||
"body" -> EntityUtils.toString(res.getEntity()),
|
||||
"headers" -> _headers(res.getAllHeaders())
|
||||
)).recover(toErrorMap), 20 seconds)
|
||||
))
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* Display the web hook edit page.
|
||||
*/
|
||||
get("/:owner/:repository/settings/hooks/edit")(ownerOnly { repository =>
|
||||
getWebHook(repository.owner, repository.name, params("url")).map{ case (webhook, events) =>
|
||||
html.edithooks(webhook, events, repository, flash.get("info"), false)
|
||||
} getOrElse NotFound
|
||||
})
|
||||
|
||||
/**
|
||||
* Update web hook settings.
|
||||
*/
|
||||
post("/:owner/:repository/settings/hooks/edit", webHookForm(true))(ownerOnly { (form, repository) =>
|
||||
updateWebHook(repository.owner, repository.name, form.url, form.events)
|
||||
flash += "info" -> s"webhook ${form.url} updated"
|
||||
redirect(s"/${repository.owner}/${repository.name}/settings/hooks")
|
||||
})
|
||||
|
||||
@@ -226,9 +294,30 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
||||
/**
|
||||
* Provides duplication check for web hook url.
|
||||
*/
|
||||
private def webHook: Constraint = new Constraint(){
|
||||
private def webHook(needExists: Boolean): Constraint = new Constraint(){
|
||||
override def validate(name: String, value: String, messages: Messages): Option[String] =
|
||||
getWebHookURLs(params("owner"), params("repository")).map(_.url).find(_ == value).map(_ => "URL had been registered already.")
|
||||
if(getWebHook(params("owner"), params("repository"), value).isDefined != needExists){
|
||||
Some(if(needExists){
|
||||
"URL had not been registered yet."
|
||||
} else {
|
||||
"URL had been registered already."
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
private def webhookEvents = new ValueType[Set[WebHook.Event]]{
|
||||
def convert(name: String, params: Map[String, String], messages: Messages): Set[WebHook.Event] = {
|
||||
WebHook.Event.values.flatMap { t =>
|
||||
params.get(name + "." + t.name).map(_ => t)
|
||||
}.toSet
|
||||
}
|
||||
def validate(name: String, params: Map[String, String], messages: Messages): Seq[(String, String)] = if(convert(name,params,messages).isEmpty){
|
||||
Seq(name -> messages("error.required").format(name))
|
||||
} else {
|
||||
Nil
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package gitbucket.core.controller
|
||||
|
||||
import javax.servlet.http.{HttpServletResponse, HttpServletRequest}
|
||||
|
||||
import gitbucket.core.api._
|
||||
import gitbucket.core.plugin.PluginRegistry
|
||||
import gitbucket.core.repo.html
|
||||
@@ -11,7 +13,7 @@ import gitbucket.core.util.StringUtil._
|
||||
import gitbucket.core.util.ControlUtil._
|
||||
import gitbucket.core.util.Implicits._
|
||||
import gitbucket.core.util.Directory._
|
||||
import gitbucket.core.model.{Account, CommitState}
|
||||
import gitbucket.core.model.{Account, CommitState, WebHook}
|
||||
import gitbucket.core.service.CommitStatusService
|
||||
import gitbucket.core.service.WebHookService._
|
||||
import gitbucket.core.view
|
||||
@@ -32,7 +34,7 @@ import org.scalatra._
|
||||
class RepositoryViewerController extends RepositoryViewerControllerBase
|
||||
with RepositoryService with AccountService with ActivityService with IssuesService with WebHookService with CommitsService
|
||||
with ReadableUsersAuthenticator with ReferrerAuthenticator with CollaboratorsAuthenticator with PullRequestService with CommitStatusService
|
||||
with WebHookPullRequestService
|
||||
with WebHookPullRequestService with WebHookPullRequestReviewCommentService
|
||||
|
||||
/**
|
||||
* The repository viewer.
|
||||
@@ -40,7 +42,7 @@ class RepositoryViewerController extends RepositoryViewerControllerBase
|
||||
trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
self: RepositoryService with AccountService with ActivityService with IssuesService with WebHookService with CommitsService
|
||||
with ReadableUsersAuthenticator with ReferrerAuthenticator with CollaboratorsAuthenticator with PullRequestService with CommitStatusService
|
||||
with WebHookPullRequestService =>
|
||||
with WebHookPullRequestService with WebHookPullRequestReviewCommentService =>
|
||||
|
||||
ArchiveCommand.registerFormat("zip", new ZipFormat)
|
||||
ArchiveCommand.registerFormat("tar.gz", new TgzFormat)
|
||||
@@ -102,11 +104,15 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
*/
|
||||
post("/:owner/:repository/_preview")(referrersOnly { repository =>
|
||||
contentType = "text/html"
|
||||
helpers.markdown(params("content"), repository,
|
||||
params("enableWikiLink").toBoolean,
|
||||
params("enableRefsLink").toBoolean,
|
||||
params("enableTaskList").toBoolean,
|
||||
hasWritePermission(repository.owner, repository.name, context.loginAccount))
|
||||
helpers.markdown(
|
||||
markdown = params("content"),
|
||||
repository = repository,
|
||||
enableWikiLink = params("enableWikiLink").toBoolean,
|
||||
enableRefsLink = params("enableRefsLink").toBoolean,
|
||||
enableLineBreaks = params("enableLineBreaks").toBoolean,
|
||||
enableTaskList = params("enableTaskList").toBoolean,
|
||||
hasWritePermission = hasWritePermission(repository.owner, repository.name, context.loginAccount)
|
||||
)
|
||||
})
|
||||
|
||||
/**
|
||||
@@ -177,7 +183,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
*
|
||||
* 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 =>
|
||||
val listStatusesRoute = get("/api/v3/repos/:owner/:repo/commits/:ref/statuses")(referrersOnly { repository =>
|
||||
(for{
|
||||
ref <- params.get("ref")
|
||||
sha <- JGitUtil.getShaByRef(repository.owner, repository.name, ref)
|
||||
@@ -188,6 +194,15 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
}) getOrElse NotFound
|
||||
})
|
||||
|
||||
/**
|
||||
* https://developer.github.com/v3/repos/statuses/#list-statuses-for-a-specific-ref
|
||||
*
|
||||
* legacy route
|
||||
*/
|
||||
get("/api/v3/repos/:owner/:repo/statuses/:ref"){
|
||||
listStatusesRoute.action()
|
||||
}
|
||||
|
||||
/**
|
||||
* https://developer.github.com/v3/repos/statuses/#get-the-combined-status-for-a-specific-ref
|
||||
*
|
||||
@@ -282,6 +297,21 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
redirect(s"/${repository.owner}/${repository.name}/tree/${form.branch}${if(form.path.length == 0) "" else form.path}")
|
||||
})
|
||||
|
||||
get("/:owner/:repository/raw/*")(referrersOnly { repository =>
|
||||
val (id, path) = splitPath(repository, multiParams("splat").head)
|
||||
using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git =>
|
||||
val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(id))
|
||||
getPathObjectId(git, path, revCommit).flatMap { objectId =>
|
||||
JGitUtil.getObjectLoaderFromId(git, objectId){ loader =>
|
||||
contentType = "application/octet-stream"
|
||||
response.setContentLength(loader.getSize.toInt)
|
||||
loader.copyTo(response.outputStream)
|
||||
()
|
||||
}
|
||||
} getOrElse NotFound
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* Displays the file content of the specified branch or commit.
|
||||
*/
|
||||
@@ -292,9 +322,12 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(id))
|
||||
getPathObjectId(git, path, revCommit).map { objectId =>
|
||||
if(raw){
|
||||
// Download
|
||||
JGitUtil.getContentFromId(git, objectId, true).map { bytes =>
|
||||
RawData("application/octet-stream", bytes)
|
||||
// Download (This route is left for backword compatibility)
|
||||
JGitUtil.getObjectLoaderFromId(git, objectId){ loader =>
|
||||
contentType = "application/octet-stream"
|
||||
response.setContentLength(loader.getSize.toInt)
|
||||
loader.copyTo(response.outputStream)
|
||||
()
|
||||
} getOrElse NotFound
|
||||
} else {
|
||||
html.blob(id, repository, path.split("/").toList,
|
||||
@@ -366,7 +399,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
post("/:owner/:repository/commit/:id/comment/new", commentForm)(readableUsersOnly { (form, repository) =>
|
||||
val id = params("id")
|
||||
createCommitComment(repository.owner, repository.name, id, context.loginAccount.get.userName, form.content,
|
||||
form.fileName, form.oldLineNumber, form.newLineNumber, form.issueId.isDefined)
|
||||
form.fileName, form.oldLineNumber, form.newLineNumber, form.issueId)
|
||||
form.issueId match {
|
||||
case Some(issueId) => recordCommentPullRequestActivity(repository.owner, repository.name, context.loginAccount.get.userName, issueId, form.content)
|
||||
case None => recordCommentCommitActivity(repository.owner, repository.name, context.loginAccount.get.userName, id, form.content)
|
||||
@@ -391,13 +424,15 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
ajaxPost("/:owner/:repository/commit/:id/comment/_data/new", commentForm)(readableUsersOnly { (form, repository) =>
|
||||
val id = params("id")
|
||||
val commentId = createCommitComment(repository.owner, repository.name, id, context.loginAccount.get.userName,
|
||||
form.content, form.fileName, form.oldLineNumber, form.newLineNumber, form.issueId.isDefined)
|
||||
form.content, form.fileName, form.oldLineNumber, form.newLineNumber, form.issueId)
|
||||
val comment = getCommitComment(repository.owner, repository.name, commentId.toString).get
|
||||
form.issueId match {
|
||||
case Some(issueId) => recordCommentPullRequestActivity(repository.owner, repository.name, context.loginAccount.get.userName, issueId, form.content)
|
||||
case Some(issueId) =>
|
||||
recordCommentPullRequestActivity(repository.owner, repository.name, context.loginAccount.get.userName, issueId, form.content)
|
||||
callPullRequestReviewCommentWebHook("create", comment, repository, issueId, context.baseUrl, context.loginAccount.get)
|
||||
case None => recordCommentCommitActivity(repository.owner, repository.name, context.loginAccount.get.userName, id, form.content)
|
||||
}
|
||||
helper.html.commitcomment(getCommitComment(repository.owner, repository.name, commentId.toString).get,
|
||||
hasWritePermission(repository.owner, repository.name, context.loginAccount), repository)
|
||||
helper.html.commitcomment(comment, hasWritePermission(repository.owner, repository.name, context.loginAccount), repository)
|
||||
})
|
||||
|
||||
ajaxGet("/:owner/:repository/commit_comments/_data/:id")(readableUsersOnly { repository =>
|
||||
@@ -409,8 +444,16 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
} getOrElse {
|
||||
contentType = formats("json")
|
||||
org.json4s.jackson.Serialization.write(
|
||||
Map("content" -> view.Markdown.toHtml(x.content,
|
||||
repository, false, true, true, isEditable(x.userName, x.repositoryName, x.commentedUserName))
|
||||
Map(
|
||||
"content" -> view.Markdown.toHtml(
|
||||
markdown = x.content,
|
||||
repository = repository,
|
||||
enableWikiLink = false,
|
||||
enableRefsLink = true,
|
||||
enableAnchor = true,
|
||||
enableLineBreaks = true,
|
||||
hasWritePermission = isEditable(x.userName, x.repositoryName, x.commentedUserName)
|
||||
)
|
||||
))
|
||||
}
|
||||
} else Unauthorized
|
||||
@@ -663,9 +706,10 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
// call web hook
|
||||
callPullRequestWebHookByRequestBranch("synchronize", repository, branch, context.baseUrl, loginAccount)
|
||||
val commit = new JGitUtil.CommitInfo(JGitUtil.getRevCommitFromId(git, commitId))
|
||||
callWebHookOf(repository.owner, repository.name, "push") {
|
||||
callWebHookOf(repository.owner, repository.name, WebHook.Push) {
|
||||
getAccountByUserName(repository.owner).map{ ownerAccount =>
|
||||
WebHookPushPayload(git, loginAccount, headName, repository, List(commit), ownerAccount)
|
||||
WebHookPushPayload(git, loginAccount, headName, repository, List(commit), ownerAccount,
|
||||
oldId = headTip, newId = commitId)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -717,4 +761,11 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
|
||||
private def isEditable(owner: String, repository: String, author: String)(implicit context: Context): Boolean =
|
||||
hasWritePermission(owner, repository, context.loginAccount) || author == context.loginAccount.get.userName
|
||||
|
||||
|
||||
|
||||
override protected def renderUncaughtException(e: Throwable)(implicit request: HttpServletRequest, response: HttpServletResponse): Unit = {
|
||||
e.printStackTrace()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -100,12 +100,12 @@ trait UserManagementControllerBase extends AccountManagementControllerBase {
|
||||
|
||||
if(form.isRemoved){
|
||||
// Remove repositories
|
||||
getRepositoryNamesOfUser(userName).foreach { repositoryName =>
|
||||
deleteRepository(userName, repositoryName)
|
||||
FileUtils.deleteDirectory(getRepositoryDir(userName, repositoryName))
|
||||
FileUtils.deleteDirectory(getWikiRepositoryDir(userName, repositoryName))
|
||||
FileUtils.deleteDirectory(getTemporaryDir(userName, repositoryName))
|
||||
}
|
||||
// getRepositoryNamesOfUser(userName).foreach { repositoryName =>
|
||||
// deleteRepository(userName, repositoryName)
|
||||
// FileUtils.deleteDirectory(getRepositoryDir(userName, repositoryName))
|
||||
// FileUtils.deleteDirectory(getWikiRepositoryDir(userName, repositoryName))
|
||||
// FileUtils.deleteDirectory(getTemporaryDir(userName, repositoryName))
|
||||
// }
|
||||
// Remove from GROUP_MEMBER, COLLABORATOR and REPOSITORY
|
||||
removeUserRelatedData(userName)
|
||||
}
|
||||
|
||||
@@ -55,8 +55,8 @@ trait CommitCommentComponent extends TemplateComponent { self: Profile =>
|
||||
val newLine = column[Option[Int]]("NEW_LINE_NUMBER")
|
||||
val registeredDate = column[java.util.Date]("REGISTERED_DATE")
|
||||
val updatedDate = column[java.util.Date]("UPDATED_DATE")
|
||||
val pullRequest = column[Boolean]("PULL_REQUEST")
|
||||
def * = (userName, repositoryName, commitId, commentId, commentedUserName, content, fileName, oldLine, newLine, registeredDate, updatedDate, pullRequest) <> (CommitComment.tupled, CommitComment.unapply)
|
||||
val issueId = column[Option[Int]]("ISSUE_ID")
|
||||
def * = (userName, repositoryName, commitId, commentId, commentedUserName, content, fileName, oldLine, newLine, registeredDate, updatedDate, issueId) <> (CommitComment.tupled, CommitComment.unapply)
|
||||
|
||||
def byPrimaryKey(commentId: Int) = this.commentId === commentId.bind
|
||||
}
|
||||
@@ -74,5 +74,5 @@ case class CommitComment(
|
||||
newLine: Option[Int],
|
||||
registeredDate: java.util.Date,
|
||||
updatedDate: java.util.Date,
|
||||
pullRequest: Boolean
|
||||
issueId: Option[Int]
|
||||
) extends Comment
|
||||
|
||||
@@ -48,6 +48,7 @@ trait CoreProfile extends ProfileProvider with Profile
|
||||
with RepositoryComponent
|
||||
with SshKeyComponent
|
||||
with WebHookComponent
|
||||
with WebHookEventComponent
|
||||
with PluginComponent
|
||||
|
||||
object Profile extends CoreProfile
|
||||
|
||||
@@ -7,7 +7,7 @@ trait WebHookComponent extends TemplateComponent { self: Profile =>
|
||||
|
||||
class WebHooks(tag: Tag) extends Table[WebHook](tag, "WEB_HOOK") with BasicTemplate {
|
||||
val url = column[String]("URL")
|
||||
def * = (userName, repositoryName, url) <> (WebHook.tupled, WebHook.unapply)
|
||||
def * = (userName, repositoryName, url) <> ((WebHook.apply _).tupled, WebHook.unapply)
|
||||
|
||||
def byPrimaryKey(owner: String, repository: String, url: String) = byRepository(owner, repository) && (this.url === url.bind)
|
||||
}
|
||||
@@ -18,3 +18,32 @@ case class WebHook(
|
||||
repositoryName: String,
|
||||
url: String
|
||||
)
|
||||
|
||||
object WebHook {
|
||||
sealed class Event(var name: String)
|
||||
case object CommitComment extends Event("commit_comment")
|
||||
case object Create extends Event("create")
|
||||
case object Delete extends Event("delete")
|
||||
case object Deployment extends Event("deployment")
|
||||
case object DeploymentStatus extends Event("deployment_status")
|
||||
case object Fork extends Event("fork")
|
||||
case object Gollum extends Event("gollum")
|
||||
case object IssueComment extends Event("issue_comment")
|
||||
case object Issues extends Event("issues")
|
||||
case object Member extends Event("member")
|
||||
case object PageBuild extends Event("page_build")
|
||||
case object Public extends Event("public")
|
||||
case object PullRequest extends Event("pull_request")
|
||||
case object PullRequestReviewComment extends Event("pull_request_review_comment")
|
||||
case object Push extends Event("push")
|
||||
case object Release extends Event("release")
|
||||
case object Status extends Event("status")
|
||||
case object TeamAdd extends Event("team_add")
|
||||
case object Watch extends Event("watch")
|
||||
object Event{
|
||||
val values = List(CommitComment,Create,Delete,Deployment,DeploymentStatus,Fork,Gollum,IssueComment,Issues,Member,PageBuild,Public,PullRequest,PullRequestReviewComment,Push,Release,Status,TeamAdd,Watch)
|
||||
private val map:Map[String,Event] = values.map(e => e.name -> e).toMap
|
||||
def valueOf(name: String): Event = map(name)
|
||||
def valueOpt(name: String): Option[Event] = map.get(name)
|
||||
}
|
||||
}
|
||||
|
||||
30
src/main/scala/gitbucket/core/model/WebHookEvent.scala
Normal file
30
src/main/scala/gitbucket/core/model/WebHookEvent.scala
Normal file
@@ -0,0 +1,30 @@
|
||||
package gitbucket.core.model
|
||||
|
||||
trait WebHookEventComponent extends TemplateComponent { self: Profile =>
|
||||
import profile.simple._
|
||||
import gitbucket.core.model.Profile.WebHooks
|
||||
|
||||
lazy val WebHookEvents = TableQuery[WebHookEvents]
|
||||
|
||||
implicit val typedType = MappedColumnType.base[WebHook.Event, String](_.name, WebHook.Event.valueOf(_))
|
||||
|
||||
class WebHookEvents(tag: Tag) extends Table[WebHookEvent](tag, "WEB_HOOK_EVENT") with BasicTemplate {
|
||||
val url = column[String]("URL")
|
||||
val event = column[WebHook.Event]("EVENT")
|
||||
def * = (userName, repositoryName, url, event) <> ((WebHookEvent.apply _).tupled, WebHookEvent.unapply)
|
||||
|
||||
def byWebHook(owner: String, repository: String, url: String) = byRepository(owner, repository) && (this.url === url.bind)
|
||||
def byWebHook(owner: Column[String], repository: Column[String], url: Column[String]) =
|
||||
byRepository(userName, repositoryName) && (this.url === url)
|
||||
def byWebHook(webhook: WebHooks) =
|
||||
byRepository(webhook.userName, webhook.repositoryName) && (this.url === webhook.url)
|
||||
def byPrimaryKey(owner: String, repository: String, url: String, event: WebHook.Event) = byWebHook(owner, repository, url) && (this.event === event.bind)
|
||||
}
|
||||
}
|
||||
|
||||
case class WebHookEvent(
|
||||
userName: String,
|
||||
repositoryName: String,
|
||||
url: String,
|
||||
event: WebHook.Event
|
||||
)
|
||||
@@ -20,7 +20,14 @@ trait Renderer {
|
||||
object MarkdownRenderer extends Renderer {
|
||||
override def render(request: RenderRequest): Html = {
|
||||
import request._
|
||||
Html(Markdown.toHtml(fileContent, repository, enableWikiLink, enableRefsLink, enableAnchor)(context))
|
||||
Html(Markdown.toHtml(
|
||||
markdown = fileContent,
|
||||
repository = repository,
|
||||
enableWikiLink = enableWikiLink,
|
||||
enableRefsLink = enableRefsLink,
|
||||
enableAnchor = enableAnchor,
|
||||
enableLineBreaks = false
|
||||
)(context))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,11 +42,13 @@ object DefaultRenderer extends Renderer {
|
||||
}
|
||||
}
|
||||
|
||||
case class RenderRequest(filePath: List[String],
|
||||
fileContent: String,
|
||||
branch: String,
|
||||
repository: RepositoryService.RepositoryInfo,
|
||||
enableWikiLink: Boolean,
|
||||
enableRefsLink: Boolean,
|
||||
enableAnchor: Boolean,
|
||||
context: Context)
|
||||
case class RenderRequest(
|
||||
filePath: List[String],
|
||||
fileContent: String,
|
||||
branch: String,
|
||||
repository: RepositoryService.RepositoryInfo,
|
||||
enableWikiLink: Boolean,
|
||||
enableRefsLink: Boolean,
|
||||
enableAnchor: Boolean,
|
||||
context: Context
|
||||
)
|
||||
@@ -13,9 +13,9 @@ import StringUtil._
|
||||
|
||||
trait CommitsService {
|
||||
|
||||
def getCommitComments(owner: String, repository: String, commitId: String, pullRequest: Boolean)(implicit s: Session) =
|
||||
def getCommitComments(owner: String, repository: String, commitId: String, includePullRequest: Boolean)(implicit s: Session) =
|
||||
CommitComments filter {
|
||||
t => t.byCommit(owner, repository, commitId) && (t.pullRequest === pullRequest || pullRequest)
|
||||
t => t.byCommit(owner, repository, commitId) && (t.issueId.isEmpty || includePullRequest)
|
||||
} list
|
||||
|
||||
def getCommitComment(owner: String, repository: String, commentId: String)(implicit s: Session) =
|
||||
@@ -27,7 +27,8 @@ trait CommitsService {
|
||||
None
|
||||
|
||||
def createCommitComment(owner: String, repository: String, commitId: String, loginUser: String,
|
||||
content: String, fileName: Option[String], oldLine: Option[Int], newLine: Option[Int], pullRequest: Boolean)(implicit s: Session): Int =
|
||||
content: String, fileName: Option[String], oldLine: Option[Int], newLine: Option[Int],
|
||||
issueId: Option[Int])(implicit s: Session): Int =
|
||||
CommitComments.autoInc insert CommitComment(
|
||||
userName = owner,
|
||||
repositoryName = repository,
|
||||
@@ -39,7 +40,7 @@ trait CommitsService {
|
||||
newLine = newLine,
|
||||
registeredDate = currentDate,
|
||||
updatedDate = currentDate,
|
||||
pullRequest = pullRequest)
|
||||
issueId = issueId)
|
||||
|
||||
def updateCommitComment(commentId: Int, content: String)(implicit s: Session) =
|
||||
CommitComments
|
||||
|
||||
@@ -92,7 +92,7 @@ trait IssuesService {
|
||||
def getCommitStatues(issueList:Seq[(String, String, Int)])(implicit s: Session) :Map[(String, String, Int), CommitStatusInfo] ={
|
||||
if(issueList.isEmpty){
|
||||
Map.empty
|
||||
}else{
|
||||
} else {
|
||||
import scala.slick.jdbc._
|
||||
val issueIdQuery = issueList.map(i => "(PR.USER_NAME=? AND PR.REPOSITORY_NAME=? AND PR.ISSUE_ID=?)").mkString(" OR ")
|
||||
implicit val qset = SetParameter[Seq[(String, String, Int)]] {
|
||||
|
||||
@@ -47,6 +47,7 @@ trait RepositoryService { self: AccountService =>
|
||||
Repositories insert repository.copy(userName = newUserName, repositoryName = newRepositoryName)
|
||||
|
||||
val webHooks = WebHooks .filter(_.byRepository(oldUserName, oldRepositoryName)).list
|
||||
val webHookEvents = WebHookEvents .filter(_.byRepository(oldUserName, oldRepositoryName)).list
|
||||
val milestones = Milestones .filter(_.byRepository(oldUserName, oldRepositoryName)).list
|
||||
val issueId = IssueId .filter(_.byRepository(oldUserName, oldRepositoryName)).list
|
||||
val issues = Issues .filter(_.byRepository(oldUserName, oldRepositoryName)).list
|
||||
@@ -66,10 +67,6 @@ trait RepositoryService { self: AccountService =>
|
||||
(t.parentUserName === oldUserName.bind) && (t.parentRepositoryName === oldRepositoryName.bind)
|
||||
}.map { t => t.originUserName -> t.originRepositoryName }.update(newUserName, newRepositoryName)
|
||||
|
||||
PullRequests.filter { t =>
|
||||
t.requestRepositoryName === oldRepositoryName.bind
|
||||
}.map { t => t.requestUserName -> t.requestRepositoryName }.update(newUserName, newRepositoryName)
|
||||
|
||||
// Updates activity fk before deleting repository because activity is sorted by activityId
|
||||
// and it can't be changed by deleting-and-inserting record.
|
||||
Activities.filter(_.byRepository(oldUserName, oldRepositoryName)).list.foreach { activity =>
|
||||
@@ -79,9 +76,10 @@ trait RepositoryService { self: AccountService =>
|
||||
|
||||
deleteRepository(oldUserName, oldRepositoryName)
|
||||
|
||||
WebHooks .insertAll(webHooks .map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*)
|
||||
Milestones.insertAll(milestones .map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*)
|
||||
IssueId .insertAll(issueId .map(_.copy(_1 = newUserName, _2 = newRepositoryName)) :_*)
|
||||
WebHooks .insertAll(webHooks .map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*)
|
||||
WebHookEvents.insertAll(webHookEvents .map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*)
|
||||
Milestones .insertAll(milestones .map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*)
|
||||
IssueId .insertAll(issueId .map(_.copy(_1 = newUserName, _2 = newRepositoryName)) :_*)
|
||||
|
||||
val newMilestones = Milestones.filter(_.byRepository(newUserName, newRepositoryName)).list
|
||||
Issues.insertAll(issues.map { x => x.copy(
|
||||
@@ -98,6 +96,11 @@ trait RepositoryService { self: AccountService =>
|
||||
CommitComments.insertAll(commitComments.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*)
|
||||
CommitStatuses.insertAll(commitStatuses.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*)
|
||||
|
||||
// Update source repository of pull requests
|
||||
PullRequests.filter { t =>
|
||||
(t.requestUserName === oldUserName.bind) && (t.requestRepositoryName === oldRepositoryName.bind)
|
||||
}.map { t => t.requestUserName -> t.requestRepositoryName }.update(newUserName, newRepositoryName)
|
||||
|
||||
// Convert labelId
|
||||
val oldLabelMap = labels.map(x => (x.labelId, x.labelName)).toMap
|
||||
val newLabelMap = Labels.filter(_.byRepository(newUserName, newRepositoryName)).map(x => (x.labelName, x.labelId)).list.toMap
|
||||
@@ -145,6 +148,7 @@ trait RepositoryService { self: AccountService =>
|
||||
IssueId .filter(_.byRepository(userName, repositoryName)).delete
|
||||
Milestones .filter(_.byRepository(userName, repositoryName)).delete
|
||||
WebHooks .filter(_.byRepository(userName, repositoryName)).delete
|
||||
WebHookEvents .filter(_.byRepository(userName, repositoryName)).delete
|
||||
Repositories .filter(_.byRepository(userName, repositoryName)).delete
|
||||
|
||||
// Update ORIGIN_USER_NAME and ORIGIN_REPOSITORY_NAME
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package gitbucket.core.service
|
||||
|
||||
import gitbucket.core.api._
|
||||
import gitbucket.core.model.{WebHook, Account, Issue, PullRequest, IssueComment}
|
||||
import gitbucket.core.model.{WebHook, Account, Issue, PullRequest, IssueComment, WebHookEvent, CommitComment}
|
||||
import gitbucket.core.model.Profile._
|
||||
import profile.simple._
|
||||
import gitbucket.core.util.JGitUtil.CommitInfo
|
||||
@@ -12,7 +12,11 @@ import org.apache.http.NameValuePair
|
||||
import org.apache.http.client.entity.UrlEncodedFormEntity
|
||||
import org.apache.http.message.BasicNameValuePair
|
||||
import org.eclipse.jgit.api.Git
|
||||
import org.eclipse.jgit.lib.ObjectId
|
||||
import org.slf4j.LoggerFactory
|
||||
import scala.concurrent._
|
||||
import org.apache.http.HttpRequest
|
||||
import org.apache.http.HttpResponse
|
||||
|
||||
|
||||
trait WebHookService {
|
||||
@@ -20,46 +24,93 @@ trait WebHookService {
|
||||
|
||||
private val logger = LoggerFactory.getLogger(classOf[WebHookService])
|
||||
|
||||
def getWebHookURLs(owner: String, repository: String)(implicit s: Session): List[WebHook] =
|
||||
WebHooks.filter(_.byRepository(owner, repository)).sortBy(_.url).list
|
||||
/** get All WebHook informations of repository */
|
||||
def getWebHooks(owner: String, repository: String)(implicit s: Session): List[(WebHook, Set[WebHook.Event])] =
|
||||
WebHooks.filter(_.byRepository(owner, repository))
|
||||
.innerJoin(WebHookEvents).on { (w, t) => t.byWebHook(w) }
|
||||
.map{ case (w,t) => w -> t.event }
|
||||
.list.groupBy(_._1).mapValues(_.map(_._2).toSet).toList.sortBy(_._1.url)
|
||||
|
||||
def addWebHookURL(owner: String, repository: String, url :String)(implicit s: Session): Unit =
|
||||
/** get All WebHook informations of repository event */
|
||||
def getWebHooksByEvent(owner: String, repository: String, event: WebHook.Event)(implicit s: Session): List[WebHook] =
|
||||
WebHookEvents.filter(t => t.byRepository(owner, repository) && t.event === event.bind)
|
||||
.list.map(t => WebHook(t.userName, t.repositoryName, t.url))
|
||||
|
||||
/** get All WebHook information from repository to url */
|
||||
def getWebHook(owner: String, repository: String, url: String)(implicit s: Session): Option[(WebHook, Set[WebHook.Event])] =
|
||||
WebHooks
|
||||
.filter(_.byPrimaryKey(owner, repository, url))
|
||||
.innerJoin(WebHookEvents).on { (w, t) => t.byWebHook(w) }
|
||||
.map{ case (w,t) => w -> t.event }
|
||||
.list.groupBy(_._1).mapValues(_.map(_._2).toSet).headOption
|
||||
|
||||
def addWebHook(owner: String, repository: String, url :String, events: Set[WebHook.Event])(implicit s: Session): Unit = {
|
||||
WebHooks insert WebHook(owner, repository, url)
|
||||
|
||||
def deleteWebHookURL(owner: String, repository: String, url :String)(implicit s: Session): Unit =
|
||||
WebHooks.filter(_.byPrimaryKey(owner, repository, url)).delete
|
||||
|
||||
def callWebHookOf(owner: String, repository: String, eventName: String)(makePayload: => Option[WebHookPayload])(implicit s: Session, c: JsonFormat.Context): Unit = {
|
||||
val webHookURLs = getWebHookURLs(owner, repository)
|
||||
if(webHookURLs.nonEmpty){
|
||||
makePayload.map(callWebHook(eventName, webHookURLs, _))
|
||||
events.toSet.map{ event: WebHook.Event =>
|
||||
WebHookEvents insert WebHookEvent(owner, repository, url, event)
|
||||
}
|
||||
}
|
||||
|
||||
def callWebHook(eventName: String, webHookURLs: List[WebHook], payload: WebHookPayload)(implicit c: JsonFormat.Context): Unit = {
|
||||
import org.apache.http.client.methods.HttpPost
|
||||
def updateWebHook(owner: String, repository: String, url :String, events: Set[WebHook.Event])(implicit s: Session): Unit = {
|
||||
WebHookEvents.filter(_.byWebHook(owner, repository, url)).delete
|
||||
events.toSet.map{ event: WebHook.Event =>
|
||||
WebHookEvents insert WebHookEvent(owner, repository, url, event)
|
||||
}
|
||||
}
|
||||
|
||||
def deleteWebHook(owner: String, repository: String, url :String)(implicit s: Session): Unit =
|
||||
WebHooks.filter(_.byPrimaryKey(owner, repository, url)).delete
|
||||
|
||||
def callWebHookOf(owner: String, repository: String, event: WebHook.Event)(makePayload: => Option[WebHookPayload])
|
||||
(implicit s: Session, c: JsonFormat.Context): Unit = {
|
||||
val webHooks = getWebHooksByEvent(owner, repository, event)
|
||||
if(webHooks.nonEmpty){
|
||||
makePayload.map(callWebHook(event, webHooks, _))
|
||||
}
|
||||
}
|
||||
|
||||
def callWebHook(event: WebHook.Event, webHookURLs: List[WebHook], payload: WebHookPayload)
|
||||
(implicit c: JsonFormat.Context): List[(WebHook, String, Future[HttpRequest], Future[HttpResponse])] = {
|
||||
import org.apache.http.impl.client.HttpClientBuilder
|
||||
import scala.concurrent._
|
||||
import ExecutionContext.Implicits.global
|
||||
import org.apache.http.protocol.HttpContext
|
||||
import org.apache.http.client.methods.HttpPost
|
||||
|
||||
if(webHookURLs.nonEmpty){
|
||||
val json = JsonFormat(payload)
|
||||
val httpClient = HttpClientBuilder.create.build
|
||||
|
||||
webHookURLs.foreach { webHookUrl =>
|
||||
webHookURLs.map { webHookUrl =>
|
||||
val reqPromise = Promise[HttpRequest]
|
||||
val f = Future {
|
||||
logger.debug(s"start web hook invocation for ${webHookUrl}")
|
||||
val httpPost = new HttpPost(webHookUrl.url)
|
||||
httpPost.addHeader("Content-Type", "application/x-www-form-urlencoded")
|
||||
httpPost.addHeader("X-Github-Event", eventName)
|
||||
val itcp = new org.apache.http.HttpRequestInterceptor{
|
||||
def process(res: HttpRequest, ctx: HttpContext): Unit = {
|
||||
reqPromise.success(res)
|
||||
}
|
||||
}
|
||||
try{
|
||||
val httpClient = HttpClientBuilder.create.addInterceptorLast(itcp).build
|
||||
logger.debug(s"start web hook invocation for ${webHookUrl.url}")
|
||||
val httpPost = new HttpPost(webHookUrl.url)
|
||||
httpPost.addHeader("Content-Type", "application/x-www-form-urlencoded")
|
||||
httpPost.addHeader("X-Github-Event", event.name)
|
||||
httpPost.addHeader("X-Github-Delivery", java.util.UUID.randomUUID().toString)
|
||||
|
||||
val params: java.util.List[NameValuePair] = new java.util.ArrayList()
|
||||
params.add(new BasicNameValuePair("payload", json))
|
||||
httpPost.setEntity(new UrlEncodedFormEntity(params, "UTF-8"))
|
||||
val params: java.util.List[NameValuePair] = new java.util.ArrayList()
|
||||
params.add(new BasicNameValuePair("payload", json))
|
||||
httpPost.setEntity(new UrlEncodedFormEntity(params, "UTF-8"))
|
||||
|
||||
httpClient.execute(httpPost)
|
||||
httpPost.releaseConnection()
|
||||
logger.debug(s"end web hook invocation for ${webHookUrl}")
|
||||
val res = httpClient.execute(httpPost)
|
||||
httpPost.releaseConnection()
|
||||
logger.debug(s"end web hook invocation for ${webHookUrl}")
|
||||
res
|
||||
}catch{
|
||||
case e:Throwable => {
|
||||
if(!reqPromise.isCompleted){
|
||||
reqPromise.failure(e)
|
||||
}
|
||||
throw e
|
||||
}
|
||||
}
|
||||
}
|
||||
f.onSuccess {
|
||||
case s => logger.debug(s"Success: web hook request to ${webHookUrl.url}")
|
||||
@@ -67,9 +118,12 @@ trait WebHookService {
|
||||
f.onFailure {
|
||||
case t => logger.error(s"Failed: web hook request to ${webHookUrl.url}", t)
|
||||
}
|
||||
(webHookUrl, json, reqPromise.future, f)
|
||||
}
|
||||
} else {
|
||||
Nil
|
||||
}
|
||||
logger.debug("end callWebHook")
|
||||
// logger.debug("end callWebHook")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,8 +133,9 @@ trait WebHookPullRequestService extends WebHookService {
|
||||
|
||||
import WebHookService._
|
||||
// https://developer.github.com/v3/activity/events/types/#issuesevent
|
||||
def callIssuesWebHook(action: String, repository: RepositoryService.RepositoryInfo, issue: Issue, baseUrl: String, sender: Account)(implicit s: Session, context:JsonFormat.Context): Unit = {
|
||||
callWebHookOf(repository.owner, repository.name, "issues"){
|
||||
def callIssuesWebHook(action: String, repository: RepositoryService.RepositoryInfo, issue: Issue, baseUrl: String, sender: Account)
|
||||
(implicit s: Session, context:JsonFormat.Context): Unit = {
|
||||
callWebHookOf(repository.owner, repository.name, WebHook.Issues){
|
||||
val users = getAccountsByUserNames(Set(repository.owner, issue.openedUserName), Set(sender))
|
||||
for{
|
||||
repoOwner <- users.get(repository.owner)
|
||||
@@ -96,9 +151,10 @@ trait WebHookPullRequestService extends WebHookService {
|
||||
}
|
||||
}
|
||||
|
||||
def callPullRequestWebHook(action: String, repository: RepositoryService.RepositoryInfo, issueId: Int, baseUrl: String, sender: Account)(implicit s: Session, context:JsonFormat.Context): Unit = {
|
||||
def callPullRequestWebHook(action: String, repository: RepositoryService.RepositoryInfo, issueId: Int, baseUrl: String, sender: Account)
|
||||
(implicit s: Session, context:JsonFormat.Context): Unit = {
|
||||
import WebHookService._
|
||||
callWebHookOf(repository.owner, repository.name, "pull_request"){
|
||||
callWebHookOf(repository.owner, repository.name, WebHook.PullRequest){
|
||||
for{
|
||||
(issue, pullRequest) <- getPullRequest(repository.owner, repository.name, issueId)
|
||||
users = getAccountsByUserNames(Set(repository.owner, pullRequest.requestUserName, issue.openedUserName), Set(sender))
|
||||
@@ -134,11 +190,13 @@ trait WebHookPullRequestService extends WebHookService {
|
||||
ru <- Accounts if ru.userName === pr.requestUserName
|
||||
iu <- Accounts if iu.userName === is.openedUserName
|
||||
wh <- WebHooks if wh.byRepository(is.userName , is.repositoryName)
|
||||
wht <- WebHookEvents if wht.event === WebHook.PullRequest.asInstanceOf[WebHook.Event].bind && wht.byWebHook(wh)
|
||||
} yield {
|
||||
((is, iu, pr, bu, ru), wh)
|
||||
}).list.groupBy(_._1).mapValues(_.map(_._2))
|
||||
|
||||
def callPullRequestWebHookByRequestBranch(action: String, requestRepository: RepositoryService.RepositoryInfo, requestBranch: String, baseUrl: String, sender: Account)(implicit s: Session, context:JsonFormat.Context): Unit = {
|
||||
def callPullRequestWebHookByRequestBranch(action: String, requestRepository: RepositoryService.RepositoryInfo, requestBranch: String, baseUrl: String, sender: Account)
|
||||
(implicit s: Session, context:JsonFormat.Context): Unit = {
|
||||
import WebHookService._
|
||||
for{
|
||||
((issue, issueUser, pullRequest, baseOwner, headOwner), webHooks) <- getPullRequestsByRequestForWebhook(requestRepository.owner, requestRepository.name, requestBranch)
|
||||
@@ -154,7 +212,37 @@ trait WebHookPullRequestService extends WebHookService {
|
||||
baseRepository = baseRepo,
|
||||
baseOwner = baseOwner,
|
||||
sender = sender)
|
||||
callWebHook("pull_request", webHooks, payload)
|
||||
callWebHook(WebHook.PullRequest, webHooks, payload)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
trait WebHookPullRequestReviewCommentService extends WebHookService {
|
||||
self: AccountService with RepositoryService with PullRequestService with IssuesService with CommitsService =>
|
||||
def callPullRequestReviewCommentWebHook(action: String, comment: CommitComment, repository: RepositoryService.RepositoryInfo, issueId: Int, baseUrl: String, sender: Account)
|
||||
(implicit s: Session, context:JsonFormat.Context): Unit = {
|
||||
import WebHookService._
|
||||
callWebHookOf(repository.owner, repository.name, WebHook.PullRequestReviewComment){
|
||||
for{
|
||||
(issue, pullRequest) <- getPullRequest(repository.owner, repository.name, issueId)
|
||||
users = getAccountsByUserNames(Set(repository.owner, pullRequest.requestUserName, issue.openedUserName), Set(sender))
|
||||
baseOwner <- users.get(repository.owner)
|
||||
headOwner <- users.get(pullRequest.requestUserName)
|
||||
issueUser <- users.get(issue.openedUserName)
|
||||
headRepo <- getRepository(pullRequest.requestUserName, pullRequest.requestRepositoryName, baseUrl)
|
||||
} yield {
|
||||
WebHookPullRequestReviewCommentPayload(
|
||||
action = action,
|
||||
comment = comment,
|
||||
issue = issue,
|
||||
issueUser = issueUser,
|
||||
pullRequest = pullRequest,
|
||||
headRepository = headRepo,
|
||||
headOwner = headOwner,
|
||||
baseRepository = repository,
|
||||
baseOwner = baseOwner,
|
||||
sender = sender)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -163,8 +251,9 @@ trait WebHookIssueCommentService extends WebHookPullRequestService {
|
||||
self: AccountService with RepositoryService with PullRequestService with IssuesService =>
|
||||
|
||||
import WebHookService._
|
||||
def callIssueCommentWebHook(repository: RepositoryService.RepositoryInfo, issue: Issue, issueCommentId: Int, sender: Account)(implicit s: Session, context:JsonFormat.Context): Unit = {
|
||||
callWebHookOf(repository.owner, repository.name, "issue_comment"){
|
||||
def callIssueCommentWebHook(repository: RepositoryService.RepositoryInfo, issue: Issue, issueCommentId: Int, sender: Account)
|
||||
(implicit s: Session, context:JsonFormat.Context): Unit = {
|
||||
callWebHookOf(repository.owner, repository.name, WebHook.IssueComment){
|
||||
for{
|
||||
issueComment <- getComment(repository.owner, repository.name, issueCommentId.toString())
|
||||
users = getAccountsByUserNames(Set(issue.openedUserName, repository.owner, issueComment.commentedUserName), Set(sender))
|
||||
@@ -190,23 +279,37 @@ object WebHookService {
|
||||
|
||||
// https://developer.github.com/v3/activity/events/types/#pushevent
|
||||
case class WebHookPushPayload(
|
||||
pusher: ApiUser,
|
||||
pusher: ApiPusher,
|
||||
sender: ApiUser,
|
||||
ref: String,
|
||||
commits: List[ApiPushCommit],
|
||||
before: String,
|
||||
after: String,
|
||||
commits: List[ApiCommit],
|
||||
repository: ApiRepository
|
||||
) extends WebHookPayload
|
||||
) extends FieldSerializable with WebHookPayload {
|
||||
val compare = commits.size match {
|
||||
case 0 => ApiPath(s"/${repository.full_name}") // maybe test hook on un-initalied repository
|
||||
case 1 => ApiPath(s"/${repository.full_name}/commit/${after}")
|
||||
case _ if before.filterNot(_=='0').isEmpty => ApiPath(s"/${repository.full_name}/compare/${commits.head.id}^...${after}")
|
||||
case _ => ApiPath(s"/${repository.full_name}/compare/${before}...${after}")
|
||||
}
|
||||
val head_commit = commits.lastOption
|
||||
}
|
||||
|
||||
object WebHookPushPayload {
|
||||
def apply(git: Git, pusher: Account, refName: String, repositoryInfo: RepositoryInfo,
|
||||
commits: List[CommitInfo], repositoryOwner: Account): WebHookPushPayload =
|
||||
def apply(git: Git, sender: Account, refName: String, repositoryInfo: RepositoryInfo,
|
||||
commits: List[CommitInfo], repositoryOwner: Account,
|
||||
newId: ObjectId, oldId: ObjectId): WebHookPushPayload =
|
||||
WebHookPushPayload(
|
||||
ApiUser(pusher),
|
||||
refName,
|
||||
commits.map{ commit => ApiPushCommit(git, RepositoryName(repositoryInfo), commit) },
|
||||
ApiRepository(
|
||||
pusher = ApiPusher(sender),
|
||||
sender = ApiUser(sender),
|
||||
ref = refName,
|
||||
before = ObjectId.toString(oldId),
|
||||
after = ObjectId.toString(newId),
|
||||
commits = commits.map{ commit => ApiCommit.forPushPayload(git, RepositoryName(repositoryInfo), commit) },
|
||||
repository = ApiRepository.forPushPayload(
|
||||
repositoryInfo,
|
||||
owner= ApiUser(repositoryOwner)
|
||||
)
|
||||
owner= ApiUser(repositoryOwner))
|
||||
)
|
||||
}
|
||||
|
||||
@@ -276,4 +379,38 @@ object WebHookService {
|
||||
comment = ApiComment(comment, RepositoryName(repository), issue.issueId, ApiUser(commentUser), issue.isPullRequest),
|
||||
sender = ApiUser(sender))
|
||||
}
|
||||
|
||||
// https://developer.github.com/v3/activity/events/types/#pullrequestreviewcommentevent
|
||||
case class WebHookPullRequestReviewCommentPayload(
|
||||
action: String,
|
||||
comment: ApiPullRequestReviewComment,
|
||||
pull_request: ApiPullRequest,
|
||||
repository: ApiRepository,
|
||||
sender: ApiUser
|
||||
) extends WebHookPayload
|
||||
|
||||
object WebHookPullRequestReviewCommentPayload{
|
||||
def apply(
|
||||
action: String,
|
||||
comment: CommitComment,
|
||||
issue: Issue,
|
||||
issueUser: Account,
|
||||
pullRequest: PullRequest,
|
||||
headRepository: RepositoryInfo,
|
||||
headOwner: Account,
|
||||
baseRepository: RepositoryInfo,
|
||||
baseOwner: Account,
|
||||
sender: Account
|
||||
) : WebHookPullRequestReviewCommentPayload = {
|
||||
val headRepoPayload = ApiRepository(headRepository, headOwner)
|
||||
val baseRepoPayload = ApiRepository(baseRepository, baseOwner)
|
||||
val senderPayload = ApiUser(sender)
|
||||
WebHookPullRequestReviewCommentPayload(
|
||||
action = action,
|
||||
comment = ApiPullRequestReviewComment(comment, senderPayload, RepositoryName(baseRepository), issue.issueId),
|
||||
pull_request = ApiPullRequest(issue, pullRequest, headRepoPayload, baseRepoPayload, ApiUser(issueUser)),
|
||||
repository = baseRepoPayload,
|
||||
sender = senderPayload)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,9 @@ object AutoUpdate {
|
||||
* The history of versions. A head of this sequence is the current BitBucket version.
|
||||
*/
|
||||
val versions = Seq(
|
||||
new Version(3, 10),
|
||||
new Version(3, 9),
|
||||
new Version(3, 8),
|
||||
new Version(3, 7) with SystemSettingsService {
|
||||
override def update(conn: Connection, cl: ClassLoader): Unit = {
|
||||
super.update(conn, cl)
|
||||
|
||||
@@ -3,7 +3,7 @@ package gitbucket.core.servlet
|
||||
import java.io.File
|
||||
|
||||
import gitbucket.core.api
|
||||
import gitbucket.core.model.Session
|
||||
import gitbucket.core.model.{Session, WebHook}
|
||||
import gitbucket.core.plugin.{GitRepositoryRouting, PluginRegistry}
|
||||
import gitbucket.core.service.IssuesService.IssueSearchCondition
|
||||
import gitbucket.core.service.WebHookService._
|
||||
@@ -200,10 +200,11 @@ class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl:
|
||||
}
|
||||
|
||||
// call web hook
|
||||
callWebHookOf(owner, repository, "push"){
|
||||
callWebHookOf(owner, repository, WebHook.Push){
|
||||
for(pusherAccount <- getAccountByUserName(pusher);
|
||||
ownerAccount <- getAccountByUserName(owner)) yield {
|
||||
WebHookPushPayload(git, pusherAccount, command.getRefName, repositoryInfo, newCommits, ownerAccount)
|
||||
WebHookPushPayload(git, pusherAccount, command.getRefName, repositoryInfo, newCommits, ownerAccount,
|
||||
newId = command.getNewId(), oldId = command.getOldId())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
package gitbucket.core.ssh
|
||||
|
||||
import java.security.PublicKey
|
||||
|
||||
import gitbucket.core.service.SshKeyService
|
||||
import gitbucket.core.servlet.Database
|
||||
import org.apache.sshd.server.PublickeyAuthenticator
|
||||
import org.apache.sshd.server.auth.pubkey.PublickeyAuthenticator
|
||||
import org.apache.sshd.server.session.ServerSession
|
||||
import java.security.PublicKey
|
||||
|
||||
class PublicKeyAuthenticator extends PublickeyAuthenticator with SshKeyService {
|
||||
|
||||
|
||||
@@ -1,20 +1,25 @@
|
||||
package gitbucket.core.ssh
|
||||
|
||||
import java.io.File
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
import javax.servlet.{ServletContextEvent, ServletContextListener}
|
||||
|
||||
import gitbucket.core.service.SystemSettingsService
|
||||
import gitbucket.core.util.Directory
|
||||
import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
|
||||
object SshServer {
|
||||
private val logger = LoggerFactory.getLogger(SshServer.getClass)
|
||||
private val server = org.apache.sshd.SshServer.setUpDefaultServer()
|
||||
private val server = org.apache.sshd.server.SshServer.setUpDefaultServer()
|
||||
private val active = new AtomicBoolean(false)
|
||||
|
||||
private def configure(port: Int, baseUrl: String) = {
|
||||
server.setPort(port)
|
||||
server.setKeyPairProvider(new SimpleGeneratorHostKeyProvider(s"${Directory.GitBucketHome}/gitbucket.ser"))
|
||||
val provider = new SimpleGeneratorHostKeyProvider(new File(s"${Directory.GitBucketHome}/gitbucket.ser"))
|
||||
provider.setAlgorithm("RSA")
|
||||
provider.setOverwriteAllowed(false)
|
||||
server.setKeyPairProvider(provider)
|
||||
server.setPublickeyAuthenticator(new PublicKeyAuthenticator)
|
||||
server.setCommandFactory(new GitCommandFactory(baseUrl))
|
||||
server.setShellFactory(new NoShell)
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
package gitbucket.core.ssh
|
||||
|
||||
import java.security.PublicKey
|
||||
import org.slf4j.LoggerFactory
|
||||
|
||||
import org.apache.commons.codec.binary.Base64
|
||||
import org.apache.sshd.common.config.keys.KeyUtils
|
||||
import org.apache.sshd.common.util.buffer.ByteArrayBuffer
|
||||
import org.eclipse.jgit.lib.Constants
|
||||
import org.apache.sshd.common.util.{KeyUtils, Buffer}
|
||||
import org.slf4j.LoggerFactory
|
||||
|
||||
|
||||
object SshUtil {
|
||||
|
||||
@@ -20,7 +23,7 @@ object SshUtil {
|
||||
try {
|
||||
val encodedKey = parts(1)
|
||||
val decode = Base64.decodeBase64(Constants.encodeASCII(encodedKey))
|
||||
Some(new Buffer(decode).getRawPublicKey)
|
||||
Some(new ByteArrayBuffer(decode).getRawPublicKey)
|
||||
} catch {
|
||||
case e: Throwable =>
|
||||
logger.debug(e.getMessage, e)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package gitbucket.core.util
|
||||
|
||||
import org.apache.commons.io.FileUtils
|
||||
import java.net.URLConnection
|
||||
import org.apache.tika.Tika
|
||||
import java.io.File
|
||||
import ControlUtil._
|
||||
import scala.util.Random
|
||||
@@ -9,8 +9,8 @@ import scala.util.Random
|
||||
object FileUtil {
|
||||
|
||||
def getMimeType(name: String): String =
|
||||
defining(URLConnection.getFileNameMap()){ fileNameMap =>
|
||||
fileNameMap.getContentTypeFor(name) match {
|
||||
defining(new Tika()){ tika =>
|
||||
tika.detect(name) match {
|
||||
case null => "application/octet-stream"
|
||||
case mimeType => mimeType
|
||||
}
|
||||
@@ -28,6 +28,8 @@ object FileUtil {
|
||||
|
||||
def isImage(name: String): Boolean = getMimeType(name).startsWith("image/")
|
||||
|
||||
def isUploadableType(name: String): Boolean = mimeTypeWhiteList contains getMimeType(name)
|
||||
|
||||
def isLarge(size: Long): Boolean = (size > 1024 * 1000)
|
||||
|
||||
def isText(content: Array[Byte]): Boolean = !content.contains(0)
|
||||
@@ -50,4 +52,14 @@ object FileUtil {
|
||||
FileUtils.deleteDirectory(dir)
|
||||
}
|
||||
}
|
||||
|
||||
val mimeTypeWhiteList: Array[String] = Array(
|
||||
"application/pdf",
|
||||
"application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
||||
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||||
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
||||
"image/gif",
|
||||
"image/jpeg",
|
||||
"image/png",
|
||||
"text/plain")
|
||||
}
|
||||
|
||||
@@ -65,6 +65,7 @@ object Implicits {
|
||||
|
||||
def paths: Array[String] = (request.getRequestURI.substring(request.getContextPath.length + 1) match{
|
||||
case path if path.startsWith("api/v3/repos/") => path.substring(13/* "/api/v3/repos".length */)
|
||||
case path if path.startsWith("api/v3/orgs/") => path.substring(12/* "/api/v3/orgs".length */)
|
||||
case path => path
|
||||
}).split("/")
|
||||
|
||||
|
||||
@@ -100,8 +100,18 @@ object JGitUtil {
|
||||
def isDifferentFromAuthor: Boolean = authorName != committerName || authorEmailAddress != committerEmailAddress
|
||||
}
|
||||
|
||||
case class DiffInfo(changeType: ChangeType, oldPath: String, newPath: String, oldContent: Option[String], newContent: Option[String],
|
||||
oldIsImage: Boolean, newIsImage: Boolean, oldObjectId: Option[String], newObjectId: Option[String])
|
||||
case class DiffInfo(
|
||||
changeType: ChangeType,
|
||||
oldPath: String,
|
||||
newPath: String,
|
||||
oldContent: Option[String],
|
||||
newContent: Option[String],
|
||||
oldIsImage: Boolean,
|
||||
newIsImage: Boolean,
|
||||
oldObjectId: Option[String],
|
||||
newObjectId: Option[String],
|
||||
tooLarge: Boolean
|
||||
)
|
||||
|
||||
/**
|
||||
* The file content data for the file content view of the repository viewer.
|
||||
@@ -495,11 +505,31 @@ object JGitUtil {
|
||||
while(treeWalk.next){
|
||||
val newIsImage = FileUtil.isImage(treeWalk.getPathString)
|
||||
buffer.append((if(!fetchContent){
|
||||
DiffInfo(ChangeType.ADD, null, treeWalk.getPathString, None, None, false, newIsImage, None, Option(treeWalk.getObjectId(0)).map(_.name))
|
||||
DiffInfo(
|
||||
changeType = ChangeType.ADD,
|
||||
oldPath = null,
|
||||
newPath = treeWalk.getPathString,
|
||||
oldContent = None,
|
||||
newContent = None,
|
||||
oldIsImage = false,
|
||||
newIsImage = newIsImage,
|
||||
oldObjectId = None,
|
||||
newObjectId = Option(treeWalk.getObjectId(0)).map(_.name),
|
||||
tooLarge = false
|
||||
)
|
||||
} else {
|
||||
DiffInfo(ChangeType.ADD, null, treeWalk.getPathString, None,
|
||||
JGitUtil.getContentFromId(git, treeWalk.getObjectId(0), false).filter(FileUtil.isText).map(convertFromByteArray),
|
||||
false, newIsImage, None, Option(treeWalk.getObjectId(0)).map(_.name))
|
||||
DiffInfo(
|
||||
changeType = ChangeType.ADD,
|
||||
oldPath = null,
|
||||
newPath = treeWalk.getPathString,
|
||||
oldContent = None,
|
||||
newContent = JGitUtil.getContentFromId(git, treeWalk.getObjectId(0), false).filter(FileUtil.isText).map(convertFromByteArray),
|
||||
oldIsImage = false,
|
||||
newIsImage = newIsImage,
|
||||
oldObjectId = None,
|
||||
newObjectId = Option(treeWalk.getObjectId(0)).map(_.name),
|
||||
tooLarge = false
|
||||
)
|
||||
}))
|
||||
}
|
||||
(buffer.toList, None)
|
||||
@@ -518,16 +548,52 @@ object JGitUtil {
|
||||
|
||||
import scala.collection.JavaConverters._
|
||||
git.getRepository.getConfig.setString("diff", null, "renames", "copies")
|
||||
git.diff.setNewTree(newTreeIter).setOldTree(oldTreeIter).call.asScala.map { diff =>
|
||||
val oldIsImage = FileUtil.isImage(diff.getOldPath)
|
||||
val newIsImage = FileUtil.isImage(diff.getNewPath)
|
||||
if(!fetchContent || oldIsImage || newIsImage){
|
||||
DiffInfo(diff.getChangeType, diff.getOldPath, diff.getNewPath, None, None, oldIsImage, newIsImage, Option(diff.getOldId).map(_.name), Option(diff.getNewId).map(_.name))
|
||||
|
||||
val diffs = git.diff.setNewTree(newTreeIter).setOldTree(oldTreeIter).call.asScala
|
||||
diffs.map { diff =>
|
||||
if(diffs.size > 100){
|
||||
DiffInfo(
|
||||
changeType = diff.getChangeType,
|
||||
oldPath = diff.getOldPath,
|
||||
newPath = diff.getNewPath,
|
||||
oldContent = None,
|
||||
newContent = None,
|
||||
oldIsImage = false,
|
||||
newIsImage = false,
|
||||
oldObjectId = Option(diff.getOldId).map(_.name),
|
||||
newObjectId = Option(diff.getNewId).map(_.name),
|
||||
tooLarge = true
|
||||
)
|
||||
} else {
|
||||
DiffInfo(diff.getChangeType, diff.getOldPath, diff.getNewPath,
|
||||
JGitUtil.getContentFromId(git, diff.getOldId.toObjectId, false).filter(FileUtil.isText).map(convertFromByteArray),
|
||||
JGitUtil.getContentFromId(git, diff.getNewId.toObjectId, false).filter(FileUtil.isText).map(convertFromByteArray),
|
||||
oldIsImage, newIsImage, Option(diff.getOldId).map(_.name), Option(diff.getNewId).map(_.name))
|
||||
val oldIsImage = FileUtil.isImage(diff.getOldPath)
|
||||
val newIsImage = FileUtil.isImage(diff.getNewPath)
|
||||
if(!fetchContent || oldIsImage || newIsImage){
|
||||
DiffInfo(
|
||||
changeType = diff.getChangeType,
|
||||
oldPath = diff.getOldPath,
|
||||
newPath = diff.getNewPath,
|
||||
oldContent = None,
|
||||
newContent = None,
|
||||
oldIsImage = oldIsImage,
|
||||
newIsImage = newIsImage,
|
||||
oldObjectId = Option(diff.getOldId).map(_.name),
|
||||
newObjectId = Option(diff.getNewId).map(_.name),
|
||||
tooLarge = false
|
||||
)
|
||||
} else {
|
||||
DiffInfo(
|
||||
changeType = diff.getChangeType,
|
||||
oldPath = diff.getOldPath,
|
||||
newPath = diff.getNewPath,
|
||||
oldContent = JGitUtil.getContentFromId(git, diff.getOldId.toObjectId, false).filter(FileUtil.isText).map(convertFromByteArray),
|
||||
newContent = JGitUtil.getContentFromId(git, diff.getNewId.toObjectId, false).filter(FileUtil.isText).map(convertFromByteArray),
|
||||
oldIsImage = oldIsImage,
|
||||
newIsImage = newIsImage,
|
||||
oldObjectId = Option(diff.getOldId).map(_.name),
|
||||
newObjectId = Option(diff.getNewId).map(_.name),
|
||||
tooLarge = false
|
||||
)
|
||||
}
|
||||
}
|
||||
}.toList
|
||||
}
|
||||
@@ -713,7 +779,7 @@ object JGitUtil {
|
||||
def getContentFromId(git: Git, id: ObjectId, fetchLargeFile: Boolean): Option[Array[Byte]] = try {
|
||||
using(git.getRepository.getObjectDatabase){ db =>
|
||||
val loader = db.open(id)
|
||||
if(fetchLargeFile == false && FileUtil.isLarge(loader.getSize)){
|
||||
if(loader.isLarge || (fetchLargeFile == false && FileUtil.isLarge(loader.getSize))){
|
||||
None
|
||||
} else {
|
||||
Some(loader.getBytes)
|
||||
@@ -723,6 +789,22 @@ object JGitUtil {
|
||||
case e: MissingObjectException => None
|
||||
}
|
||||
|
||||
/**
|
||||
* Get objectLoader of the given object id from the Git repository.
|
||||
*
|
||||
* @param git the Git object
|
||||
* @param id the object id
|
||||
* @param f the function process ObjectLoader
|
||||
* @return None if object does not exist
|
||||
*/
|
||||
def getObjectLoaderFromId[A](git: Git, id: ObjectId)(f: ObjectLoader => A):Option[A] = try {
|
||||
using(git.getRepository.getObjectDatabase){ db =>
|
||||
Some(f(db.open(id)))
|
||||
}
|
||||
} catch {
|
||||
case e: MissingObjectException => None
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all commit id in the specified repository.
|
||||
*/
|
||||
|
||||
@@ -75,7 +75,14 @@ class Mailer(private val smtp: Smtp) extends Notifier {
|
||||
database withSession { implicit session =>
|
||||
defining(
|
||||
s"[${r.name}] ${issue.title} (#${issue.issueId})" ->
|
||||
msg(Markdown.toHtml(content, r, false, true, false))) { case (subject, msg) =>
|
||||
msg(Markdown.toHtml(
|
||||
markdown = content,
|
||||
repository = r,
|
||||
enableWikiLink = false,
|
||||
enableRefsLink = true,
|
||||
enableAnchor = false,
|
||||
enableLineBreaks = false
|
||||
))) { case (subject, msg) =>
|
||||
recipients(issue) { to =>
|
||||
val email = new HtmlEmail
|
||||
email.setHostName(smtp.host)
|
||||
|
||||
@@ -7,13 +7,31 @@ import gitbucket.core.util.Implicits.RichString
|
||||
trait LinkConverter { self: RequestCache =>
|
||||
|
||||
/**
|
||||
* Converts issue id, username and commit id to link.
|
||||
* Creates a link to the issue or the pull request from the issue id.
|
||||
*/
|
||||
protected def convertRefsLinks(value: String, repository: RepositoryService.RepositoryInfo,
|
||||
protected def createIssueLink(repository: RepositoryService.RepositoryInfo, issueId: Int)(implicit context: Context): String = {
|
||||
val userName = repository.repository.userName
|
||||
val repositoryName = repository.repository.repositoryName
|
||||
|
||||
getIssue(userName, repositoryName, issueId.toString) match {
|
||||
case Some(issue) if (issue.isPullRequest) =>
|
||||
s"""<a href="${context.path}/${userName}/${repositoryName}/pull/${issueId}">Pull #${issueId}</a>"""
|
||||
case Some(_) =>
|
||||
s"""<a href="${context.path}/${userName}/${repositoryName}/issues/${issueId}">Issue #${issueId}</a>"""
|
||||
case None =>
|
||||
s"Unknown #${issueId}"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Converts issue id, username and commit id to link in the given text.
|
||||
*/
|
||||
protected def convertRefsLinks(text: String, repository: RepositoryService.RepositoryInfo,
|
||||
issueIdPrefix: String = "#", escapeHtml: Boolean = true)(implicit context: Context): String = {
|
||||
|
||||
// escape HTML tags
|
||||
val escaped = if(escapeHtml) value.replace("&", "&").replace("<", "<").replace(">", ">").replace("\"", """) else value
|
||||
val escaped = if(escapeHtml) text.replace("&", "&").replace("<", "<").replace(">", ">").replace("\"", """) else text
|
||||
|
||||
escaped
|
||||
// convert username/project@SHA to link
|
||||
@@ -26,10 +44,12 @@ trait LinkConverter { self: RequestCache =>
|
||||
// convert username/project#Num to link
|
||||
.replaceBy( ("(?<=(^|\\W))([a-zA-Z0-9\\-_]+)/([a-zA-Z0-9\\-_\\.]+)" + issueIdPrefix + "([0-9]+)(?=(\\W|$))").r){ m =>
|
||||
getIssue(m.group(2), m.group(3), m.group(4)) match {
|
||||
case Some(issue) if (issue.isPullRequest)
|
||||
=> Some( s"""<a href="${context.path}/${m.group(2)}/${m.group(3)}/pull/${m.group(4)}">${m.group(2)}/${m.group(3)}#${m.group(4)}</a>""")
|
||||
case Some(_) => Some( s"""<a href="${context.path}/${m.group(2)}/${m.group(3)}/issues/${m.group(4)}">${m.group(2)}/${m.group(3)}#${m.group(4)}</a>""")
|
||||
case None => Some( s"""${m.group(2)}/${m.group(3)}#${m.group(4)}""")
|
||||
case Some(issue) if (issue.isPullRequest) =>
|
||||
Some(s"""<a href="${context.path}/${m.group(2)}/${m.group(3)}/pull/${m.group(4)}">${m.group(2)}/${m.group(3)}#${m.group(4)}</a>""")
|
||||
case Some(_) =>
|
||||
Some(s"""<a href="${context.path}/${m.group(2)}/${m.group(3)}/issues/${m.group(4)}">${m.group(2)}/${m.group(3)}#${m.group(4)}</a>""")
|
||||
case None =>
|
||||
Some(s"""${m.group(2)}/${m.group(3)}#${m.group(4)}""")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,10 +63,12 @@ trait LinkConverter { self: RequestCache =>
|
||||
// convert username#Num to link
|
||||
.replaceBy( ("(?<=(^|\\W))([a-zA-Z0-9\\-_]+)" + issueIdPrefix + "([0-9]+)(?=(\\W|$))").r ) { m =>
|
||||
getIssue(m.group(2), repository.name, m.group(3)) match {
|
||||
case Some(issue) if(issue.isPullRequest)
|
||||
=> Some(s"""<a href="${context.path}/${m.group(2)}/${repository.name}/pull/${m.group(3)}">${m.group(2)}#${m.group(3)}</a>""")
|
||||
case Some(_) => Some(s"""<a href="${context.path}/${m.group(2)}/${repository.name}/issues/${m.group(3)}">${m.group(2)}#${m.group(3)}</a>""")
|
||||
case None => Some(s"""${m.group(2)}#${m.group(3)}""")
|
||||
case Some(issue) if(issue.isPullRequest) =>
|
||||
Some(s"""<a href="${context.path}/${m.group(2)}/${repository.name}/pull/${m.group(3)}">${m.group(2)}#${m.group(3)}</a>""")
|
||||
case Some(_) =>
|
||||
Some(s"""<a href="${context.path}/${m.group(2)}/${repository.name}/issues/${m.group(3)}">${m.group(2)}#${m.group(3)}</a>""")
|
||||
case None =>
|
||||
Some(s"""${m.group(2)}#${m.group(3)}""")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,10 +76,12 @@ trait LinkConverter { self: RequestCache =>
|
||||
.replaceBy(("(?<=(^|\\W))(GH-|" + issueIdPrefix + ")([0-9]+)(?=(\\W|$))").r){ m =>
|
||||
val prefix = if(m.group(2) == "issue:") "#" else m.group(2)
|
||||
getIssue(repository.owner, repository.name, m.group(3)) match {
|
||||
case Some(issue) if(issue.isPullRequest)
|
||||
=> Some(s"""<a href="${context.path}/${repository.owner}/${repository.name}/pull/${m.group(3)}">${prefix}${m.group(3)}</a>""")
|
||||
case Some(_) => Some(s"""<a href="${context.path}/${repository.owner}/${repository.name}/issues/${m.group(3)}">${prefix}${m.group(3)}</a>""")
|
||||
case None => Some(s"""${m.group(2)}${m.group(3)}""")
|
||||
case Some(issue) if(issue.isPullRequest) =>
|
||||
Some(s"""<a href="${context.path}/${repository.owner}/${repository.name}/pull/${m.group(3)}">${prefix}${m.group(3)}</a>""")
|
||||
case Some(_) =>
|
||||
Some(s"""<a href="${context.path}/${repository.owner}/${repository.name}/issues/${m.group(3)}">${prefix}${m.group(3)}</a>""")
|
||||
case None =>
|
||||
Some(s"""${m.group(2)}${m.group(3)}""")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ object Markdown {
|
||||
* @param enableWikiLink if true then wiki style link is available in markdown
|
||||
* @param enableRefsLink if true then issue reference (e.g. #123) is rendered as link
|
||||
* @param enableAnchor if true then anchor for headline is generated
|
||||
* @param enableLineBreaks if true then render line breaks as <br>
|
||||
* @param enableTaskList if true then task list syntax is available
|
||||
* @param hasWritePermission true if user has writable to ths given repository
|
||||
* @param pages the list of existing Wiki pages
|
||||
@@ -28,6 +29,7 @@ object Markdown {
|
||||
enableWikiLink: Boolean,
|
||||
enableRefsLink: Boolean,
|
||||
enableAnchor: Boolean,
|
||||
enableLineBreaks: Boolean,
|
||||
enableTaskList: Boolean = false,
|
||||
hasWritePermission: Boolean = false,
|
||||
pages: List[String] = Nil)(implicit context: Context): String = {
|
||||
@@ -43,6 +45,7 @@ object Markdown {
|
||||
|
||||
val options = new Options()
|
||||
options.setSanitize(true)
|
||||
options.setBreaks(enableLineBreaks)
|
||||
val renderer = new GitBucketMarkedRenderer(options, repository, enableWikiLink, enableRefsLink, enableAnchor, enableTaskList, hasWritePermission, pages)
|
||||
Marked.marked(source, options, renderer)
|
||||
}
|
||||
@@ -110,7 +113,7 @@ object Markdown {
|
||||
}
|
||||
|
||||
override def link(href: String, title: String, text: String): String = {
|
||||
super.link(fixUrl(href, true), title, text)
|
||||
super.link(fixUrl(href, false), title, text)
|
||||
}
|
||||
|
||||
override def image(href: String, title: String, text: String): String = {
|
||||
|
||||
@@ -84,15 +84,30 @@ object helpers extends AvatarImageProvider with LinkConverter with RequestCache
|
||||
/**
|
||||
* Converts Markdown of Wiki pages to HTML.
|
||||
*/
|
||||
def markdown(value: String,
|
||||
def markdown(markdown: String,
|
||||
repository: RepositoryService.RepositoryInfo,
|
||||
enableWikiLink: Boolean,
|
||||
enableRefsLink: Boolean,
|
||||
enableLineBreaks: Boolean,
|
||||
enableTaskList: Boolean = false,
|
||||
hasWritePermission: Boolean = false,
|
||||
pages: List[String] = Nil)(implicit context: Context): Html =
|
||||
Html(Markdown.toHtml(value, repository, enableWikiLink, enableRefsLink, true, enableTaskList, hasWritePermission, pages))
|
||||
Html(Markdown.toHtml(
|
||||
markdown = markdown,
|
||||
repository = repository,
|
||||
enableWikiLink = enableWikiLink,
|
||||
enableRefsLink = enableRefsLink,
|
||||
enableAnchor = true,
|
||||
enableLineBreaks = enableLineBreaks,
|
||||
enableTaskList = enableTaskList,
|
||||
hasWritePermission = hasWritePermission,
|
||||
pages = pages
|
||||
))
|
||||
|
||||
/**
|
||||
* Render the given source (only markdown is supported in default) as HTML.
|
||||
* You can test if a file is renderable in this method by [[isRenderable()]].
|
||||
*/
|
||||
def renderMarkup(filePath: List[String], fileContent: String, branch: String,
|
||||
repository: RepositoryService.RepositoryInfo,
|
||||
enableWikiLink: Boolean, enableRefsLink: Boolean, enableAnchor: Boolean)(implicit context: Context): Html = {
|
||||
@@ -103,10 +118,20 @@ object helpers extends AvatarImageProvider with LinkConverter with RequestCache
|
||||
renderer.render(RenderRequest(filePath, fileContent, branch, repository, enableWikiLink, enableRefsLink, enableAnchor, context))
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests whether the given file is renderable. It's tested by the file extension.
|
||||
*/
|
||||
def isRenderable(fileName: String): Boolean = {
|
||||
PluginRegistry().renderableExtensions.exists(extension => fileName.toLowerCase.endsWith("." + extension))
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a link to the issue or the pull request from the issue id.
|
||||
*/
|
||||
def issueLink(repository: RepositoryService.RepositoryInfo, issueId: Int)(implicit context: Context): Html = {
|
||||
Html(createIssueLink(repository, issueId))
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns <img> which displays the avatar icon for the given user name.
|
||||
* This method looks up Gravatar if avatar icon has not been configured in user settings.
|
||||
@@ -275,4 +300,11 @@ object helpers extends AvatarImageProvider with LinkConverter with RequestCache
|
||||
case CommitState.ERROR => "Failed"
|
||||
case CommitState.FAILURE => "Failed"
|
||||
}
|
||||
|
||||
// This pattern comes from: http://stackoverflow.com/a/4390768/1771641 (extract-url-from-string)
|
||||
private[this] val detectAndRenderLinksRegex = """(?i)\b((?:https?://|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,13}/)(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:'".,<>?«»“”‘’]))""".r
|
||||
|
||||
def detectAndRenderLinks(text: String): Html = {
|
||||
Html(detectAndRenderLinksRegex.replaceAllIn(text, m => s"""<a href="${m.group(0)}">${m.group(0)}</a>"""))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,17 +5,17 @@
|
||||
@import gitbucket.core.view.helpers._
|
||||
@html.main("Applications"){
|
||||
<div class="container">
|
||||
<div class="row-fluid">
|
||||
<div class="span3">
|
||||
<div class="row">
|
||||
<div class="col-md-3">
|
||||
@menu("application", settings.ssh)
|
||||
</div>
|
||||
<div class="span9">
|
||||
<div class="box">
|
||||
<div class="box-header">Personal access tokens</div>
|
||||
<div class="box-content">
|
||||
<div class="col-md-9">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading strong">Personal access tokens</div>
|
||||
<div class="panel-body">
|
||||
@if(personalTokens.isEmpty && gneratedToken.isEmpty){
|
||||
No tokens.
|
||||
}else{
|
||||
} else {
|
||||
Tokens you have generated that can be used to access the GitBucket API.<hr>
|
||||
}
|
||||
@gneratedToken.map{ case (token, tokenString) =>
|
||||
@@ -38,13 +38,13 @@
|
||||
</div>
|
||||
</div>
|
||||
<form method="POST" action="@path/@account.userName/_personalToken" validate="true">
|
||||
<div class="box">
|
||||
<div class="box-header">Generate new token</div>
|
||||
<div class="box-content">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading strong">Generate new token</div>
|
||||
<div class="panel-body">
|
||||
<fieldset>
|
||||
<label for="note" class="strong">Token description</label>
|
||||
<div><span id="error-note" class="error"></span></div>
|
||||
<input type="text" name="note" id="note" style="width: 400px;"/>
|
||||
<input type="text" name="note" id="note" class="form-control" style="width: 400px;"/>
|
||||
<p class="muted">What's this token for?</p>
|
||||
</fieldset>
|
||||
<input type="submit" class="btn btn-success" value="Generate token"/>
|
||||
|
||||
@@ -4,46 +4,46 @@
|
||||
@import gitbucket.core.view.helpers._
|
||||
@html.main("Edit your profile"){
|
||||
<div class="container">
|
||||
<div class="row-fluid">
|
||||
<div class="span3">
|
||||
<div class="row">
|
||||
<div class="col-md-3">
|
||||
@menu("profile", settings.ssh)
|
||||
</div>
|
||||
<div class="span9">
|
||||
<div class="col-md-9">
|
||||
@helper.html.information(info)
|
||||
@if(LDAPUtil.isDummyMailAddress(account)){<div class="alert alert-danger">Please register your mail address.</div>}
|
||||
<form action="@url(account.userName)/_edit" method="POST" validate="true">
|
||||
<div class="box">
|
||||
<div class="box-header">Profile</div>
|
||||
<div class="box-content">
|
||||
<div class="row-fluid">
|
||||
<div class="span6">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading strong">Profile</div>
|
||||
<div class="panel-body">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
@if(account.password.nonEmpty){
|
||||
<fieldset>
|
||||
<fieldset class="form-group">
|
||||
<label for="password" class="strong">
|
||||
Password (input to change password):
|
||||
</label>
|
||||
<input type="password" name="password" id="password" value="" autocomplete="off"/>
|
||||
<input type="password" name="password" id="password" class="form-control" value="" autocomplete="off"/>
|
||||
<span id="error-password" class="error"></span>
|
||||
</fieldset>
|
||||
}
|
||||
<fieldset>
|
||||
<fieldset class="form-group">
|
||||
<label for="fullName" class="strong">Full Name:</label>
|
||||
<input type="text" name="fullName" id="fullName" value="@account.fullName"/>
|
||||
<input type="text" name="fullName" id="fullName" class="form-control" value="@account.fullName"/>
|
||||
<span id="error-fullName" class="error"></span>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<fieldset class="form-group">
|
||||
<label for="mailAddress" class="strong">Mail Address:</label>
|
||||
<input type="text" name="mailAddress" id="mailAddress" value="@if(!LDAPUtil.isDummyMailAddress(account)){@account.mailAddress}"/>
|
||||
<input type="text" name="mailAddress" id="mailAddress" class="form-control" value="@if(!LDAPUtil.isDummyMailAddress(account)){@account.mailAddress}"/>
|
||||
<span id="error-mailAddress" class="error"></span>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<fieldset class="form-group">
|
||||
<label for="url" class="strong">URL (optional):</label>
|
||||
<input type="text" name="url" id="url" style="width: 300px;" value="@account.url"/>
|
||||
<input type="text" name="url" id="url" class="form-control" value="@account.url"/>
|
||||
<span id="error-url" class="error"></span>
|
||||
</fieldset>
|
||||
</div>
|
||||
<div class="span6">
|
||||
<fieldset>
|
||||
<div class="col-md-6">
|
||||
<fieldset class="form-group">
|
||||
<label for="avatar" class="strong">Image (optional):</label>
|
||||
@helper.html.uploadavatar(Some(account))
|
||||
</fieldset>
|
||||
@@ -54,7 +54,7 @@
|
||||
<a href="@path/@account.userName/_delete" class="btn btn-danger" id="delete">Delete account</a>
|
||||
</div>
|
||||
<input type="submit" class="btn btn-success" value="Save"/>
|
||||
@if(!LDAPUtil.isDummyMailAddress(account)){<a href="@url(account.userName)" class="btn">Cancel</a>}
|
||||
@if(!LDAPUtil.isDummyMailAddress(account)){<a href="@url(account.userName)" class="btn btn-default">Cancel</a>}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -4,34 +4,34 @@
|
||||
@html.main(if(account.isEmpty) "Create group" else "Edit group"){
|
||||
<div class="container">
|
||||
<form id="form" method="post" action="@if(account.isEmpty){@path/groups/new} else {@path/@account.get.userName/_editgroup}" validate="true">
|
||||
<div class="row-fluid">
|
||||
<div class="span5">
|
||||
<fieldset>
|
||||
<div class="row">
|
||||
<div class="col-md-5">
|
||||
<fieldset class="form-group">
|
||||
<label for="groupName" class="strong">Group name</label>
|
||||
<div>
|
||||
<span id="error-groupName" class="error"></span>
|
||||
</div>
|
||||
<input type="text" name="groupName" id="groupName" value="@account.map(_.userName)"@if(account.isDefined){ readonly}/>
|
||||
<input type="text" name="groupName" id="groupName" class="form-control" value="@account.map(_.userName)"@if(account.isDefined){ readonly}/>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<fieldset class="form-group">
|
||||
<label class="strong">URL (Optional)</label>
|
||||
<div>
|
||||
<span id="error-url" class="error"></span>
|
||||
</div>
|
||||
<input type="text" name="url" id="url" style="width: 300px;" value="@account.map(_.url)"/>
|
||||
<input type="text" name="url" id="url" class="form-control" value="@account.map(_.url)"/>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<fieldset class="form-group">
|
||||
<label for="avatar" class="strong">Image (Optional)</label>
|
||||
@helper.html.uploadavatar(account)
|
||||
</fieldset>
|
||||
</div>
|
||||
<div class="span7">
|
||||
<fieldset>
|
||||
<div class="col-md-7">
|
||||
<fieldset class="form-group">
|
||||
<label class="strong">Members</label>
|
||||
<ul id="member-list" class="collaborator">
|
||||
</ul>
|
||||
@helper.html.account("memberName", 200)
|
||||
<input type="button" class="btn" value="Add" id="addMember"/>
|
||||
<input type="button" class="btn btn-default" value="Add" id="addMember"/>
|
||||
<input type="hidden" id="members" name="members" value="@members.map(member => member.userName + ":" + member.isManager).mkString(",")"/>
|
||||
<div>
|
||||
<span class="error" id="error-members"></span>
|
||||
@@ -42,12 +42,12 @@
|
||||
<fieldset class="margin">
|
||||
@if(account.isDefined){
|
||||
<div class="pull-right">
|
||||
<a href="@url(account.get.userName)/_deletegroup" id="delete" class="btn btn-danger">Delete Group</a>
|
||||
<a href="@url(account.get.userName)/_deletegroup" id="delete" class="btn btn-danger btn-lg">Delete Group</a>
|
||||
</div>
|
||||
}
|
||||
<input type="submit" class="btn btn-success" value="@if(account.isEmpty){Create Group} else {Update Group}"/>
|
||||
<input type="submit" class="btn btn-success btn-lg" value="@if(account.isEmpty){Create Group} else {Update Group}"/>
|
||||
@if(account.isDefined){
|
||||
<a href="@url(account.get.userName)" class="btn">Cancel</a>
|
||||
<a href="@url(account.get.userName)" class="btn btn-default btn-lg">Cancel</a>
|
||||
}
|
||||
</fieldset>
|
||||
</form>
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
@html.main(account.userName){
|
||||
<div class="container">
|
||||
<div class="container-fluid">
|
||||
<div class="row-fluid">
|
||||
<div class="span4">
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<div class="block">
|
||||
<div class="account-image">@avatar(account.userName, 270)</div>
|
||||
<div class="account-fullname">@account.fullName</div>
|
||||
@@ -28,7 +28,7 @@
|
||||
}
|
||||
|
||||
</div>
|
||||
<div class="span8">
|
||||
<div class="col-md-8">
|
||||
<ul class="nav nav-tabs" style="margin-bottom: 5px;">
|
||||
<li@if(active == "repositories"){ class="active"}><a href="@url(account.userName)?tab=repositories">Repositories</a></li>
|
||||
@if(account.isGroupAccount){
|
||||
@@ -39,14 +39,14 @@
|
||||
@if(loginAccount.isDefined && loginAccount.get.userName == account.userName){
|
||||
<li class="pull-right">
|
||||
<div class="button-group">
|
||||
<a href="@url(account.userName)/_edit" class="btn">Edit Your Profile</a>
|
||||
<a href="@url(account.userName)/_edit" class="btn btn-default">Edit Your Profile</a>
|
||||
</div>
|
||||
</li>
|
||||
}
|
||||
@if(loginAccount.isDefined && account.isGroupAccount && isGroupManager){
|
||||
<li class="pull-right">
|
||||
<div class="button-group">
|
||||
<a href="@url(account.userName)/_editgroup" class="btn">Edit Group</a>
|
||||
<a href="@url(account.userName)/_editgroup" class="btn btn-default">Edit Group</a>
|
||||
</div>
|
||||
</li>
|
||||
}
|
||||
|
||||
@@ -4,45 +4,56 @@ isCreateRepoOptionPublic: Boolean)(implicit context: gitbucket.core.controller.C
|
||||
@import gitbucket.core.view.helpers._
|
||||
@html.main("Create a New Repository"){
|
||||
<div style="width: 600px; margin: 10px auto;">
|
||||
<h2>Create a new repository</h2>
|
||||
<p class="muted">
|
||||
A repository contains all the files for your project, including the revision history.
|
||||
</p>
|
||||
<form id="form" method="post" action="@path/new" validate="true">
|
||||
<fieldset>
|
||||
<label for="name" class="strong">Repository name:</label>
|
||||
<div class="btn-group" style="margin-bottom: 10px;" id="owner-dropdown">
|
||||
<button class="btn dropdown-toggle" data-toggle="dropdown">
|
||||
<span class="strong">@avatar(loginAccount.get.userName, 20) @loginAccount.get.userName</span>
|
||||
<span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a href="javascript:void(0);" data-name="@loginAccount.get.userName"><i class="octicon octicon-check"></i> <span>@avatar(loginAccount.get.userName, 20) @loginAccount.get.userName</span></a></li>
|
||||
@groupNames.map { groupName =>
|
||||
<li><a href="javascript:void(0);" data-name="@groupName"><i class="icon-white"></i> <span>@avatar(groupName, 20) @groupName</span></a></li>
|
||||
}
|
||||
</ul>
|
||||
<input type="hidden" name="owner" id="owner" value="@loginAccount.get.userName"/>
|
||||
</div>
|
||||
<span class="slash">/</span>
|
||||
<input type="text" name="name" id="name" autofocus />
|
||||
<span id="error-name" class="error"></span>
|
||||
<fieldset class="margin form-group">
|
||||
<dl style="float: left;">
|
||||
<dt>Owner</dt>
|
||||
<dd style="margin-left: 0px;">
|
||||
<div class="btn-group" id="owner-dropdown">
|
||||
<button class="btn dropdown-toggle btn-default" data-toggle="dropdown">
|
||||
<span class="strong">@avatar(loginAccount.get.userName, 20) @loginAccount.get.userName</span>
|
||||
<span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a href="javascript:void(0);" data-name="@loginAccount.get.userName"><i class="octicon octicon-check"></i> <span>@avatar(loginAccount.get.userName, 20) @loginAccount.get.userName</span></a></li>
|
||||
@groupNames.map { groupName =>
|
||||
<li><a href="javascript:void(0);" data-name="@groupName"><i class="octicon"></i> <span>@avatar(groupName, 20) @groupName</span></a></li>
|
||||
}
|
||||
</ul>
|
||||
<input type="hidden" name="owner" id="owner" value="@loginAccount.get.userName"/>
|
||||
</div>
|
||||
</dd>
|
||||
</dl>
|
||||
<span class="slash" style="float: left; margin-left: 10px; margin-right: 10px; margin-top: 15px;">/</span>
|
||||
<dl>
|
||||
<dt>Repository name</dt>
|
||||
<dd style="margin-left: 0px;">
|
||||
<input type="text" name="name" id="name" class="form-control" style="width: 200px;" autofocus />
|
||||
<span id="error-name" class="error"></span>
|
||||
</dd>
|
||||
</dl>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<fieldset class="form-group">
|
||||
<label for="description" class="strong">Description (optional):</label>
|
||||
<input type="text" name="description" id="description" style="width: 95%;"/>
|
||||
<input type="text" name="description" id="description" class="form-control" style="width: 95%;"/>
|
||||
</fieldset>
|
||||
<fieldset class="margin">
|
||||
<label class="radio">
|
||||
<input type="radio" name="isPrivate" value="false" @if(isCreateRepoOptionPublic){checked}>
|
||||
<span class="strong"><i class="octicon octicon-repo"></i> </i> Public</span><br>
|
||||
<div>
|
||||
<span>All users and guests can read this repository.</span>
|
||||
<span class="strong"><i class="octicon octicon-repo"></i> </i> Public</span>
|
||||
<div class="normal muted">
|
||||
Anyone can see this repository. You choose who can commit.
|
||||
</div>
|
||||
</label>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<label class="radio">
|
||||
<input type="radio" name="isPrivate" value="true" @if(!isCreateRepoOptionPublic){checked}>
|
||||
<span class="strong"><i class="octicon octicon-lock"></i> </i> Private</span><br>
|
||||
<div>
|
||||
<span>Only collaborators can read this repository.</span>
|
||||
<span class="strong"><i class="octicon octicon-lock"></i> </i> Private</span>
|
||||
<div class="normal muted">
|
||||
You choose who can see and commit to this repository.
|
||||
</div>
|
||||
</label>
|
||||
</fieldset>
|
||||
@@ -50,13 +61,13 @@ isCreateRepoOptionPublic: Boolean)(implicit context: gitbucket.core.controller.C
|
||||
<label for="createReadme" class="checkbox">
|
||||
<input type="checkbox" name="createReadme" id="createReadme"/>
|
||||
<span class="strong">Initialize this repository with a README</span>
|
||||
<div>
|
||||
<span>This will allow you to <code>git clone</code> the repository immediately.</span>
|
||||
<div class="normal muted">
|
||||
This will let you immediately clone the repository to your computer. Skip this step if you’re importing an existing repository.
|
||||
</div>
|
||||
</label>
|
||||
</fieldset>
|
||||
<fieldset class="margin">
|
||||
<input type="submit" class="btn btn-success" value="Create repository"/>
|
||||
<fieldset class="margin form-actions">
|
||||
<input type="submit" class="btn btn-success btn-lg" value="Create repository"/>
|
||||
</fieldset>
|
||||
</form>
|
||||
</div>
|
||||
@@ -66,8 +77,8 @@ $('#owner-dropdown a').click(function(){
|
||||
var userName = $(this).data('name');
|
||||
$('#owner').val(userName);
|
||||
|
||||
$('#owner-dropdown i').attr('class', 'icon-white');
|
||||
$(this).find('i').attr('class', 'icon-ok');
|
||||
$('#owner-dropdown i').attr('class', 'octicon');
|
||||
$(this).find('i').attr('class', 'octicon octicon-check');
|
||||
|
||||
$('#owner-dropdown span.strong').html($(this).find('span').html());
|
||||
});
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
<div class="container">
|
||||
<h3>Create your account</h3>
|
||||
<form action="@path/register" method="POST" validate="true">
|
||||
<div class="row-fluid">
|
||||
<div class="span6">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<fieldset>
|
||||
<label for="userName" class="strong">Username:</label>
|
||||
<input type="text" name="userName" id="userName" value="" autofocus/>
|
||||
@@ -35,7 +35,7 @@
|
||||
<span id="error-url" class="error"></span>
|
||||
</fieldset>
|
||||
</div>
|
||||
<div class="span6">
|
||||
<div class="col-md-6">
|
||||
<fieldset>
|
||||
<label for="avatar" class="strong">Image (optional):</label>
|
||||
@helper.html.uploadavatar(None)
|
||||
|
||||
@@ -4,14 +4,14 @@
|
||||
@import gitbucket.core.view.helpers._
|
||||
@html.main("SSH Keys"){
|
||||
<div class="container">
|
||||
<div class="row-fluid">
|
||||
<div class="span3">
|
||||
<div class="row">
|
||||
<div class="col-md-3">
|
||||
@menu("ssh", settings.ssh)
|
||||
</div>
|
||||
<div class="span9">
|
||||
<div class="box">
|
||||
<div class="box-header">SSH Keys</div>
|
||||
<div class="box-content">
|
||||
<div class="col-md-9">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading strong">SSH Keys</div>
|
||||
<div class="panel-body">
|
||||
@if(sshKeys.isEmpty){
|
||||
No keys
|
||||
}
|
||||
@@ -25,18 +25,18 @@
|
||||
</div>
|
||||
</div>
|
||||
<form method="POST" action="@path/@account.userName/_ssh" validate="true">
|
||||
<div class="box">
|
||||
<div class="box-header">Add an SSH Key</div>
|
||||
<div class="box-content">
|
||||
<fieldset>
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading strong">Add an SSH Key</div>
|
||||
<div class="panel-body">
|
||||
<fieldset class="form-group">
|
||||
<label for="title" class="strong">Title</label>
|
||||
<div><span id="error-title" class="error"></span></div>
|
||||
<input type="text" name="title" id="title" style="width: 400px;"/>
|
||||
<input type="text" name="title" id="title" class="form-control" style="width: 400px;"/>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<fieldset class="form-group">
|
||||
<label for="publicKey" class="strong">Key</label>
|
||||
<div><span id="error-publicKey" class="error"></span></div>
|
||||
<textarea name="publicKey" id="publicKey" style="width: 600px; height: 250px;"></textarea>
|
||||
<textarea name="publicKey" id="publicKey" class="form-control" style="width: 600px; height: 250px;"></textarea>
|
||||
</fieldset>
|
||||
<input type="submit" class="btn btn-success" value="Add"/>
|
||||
</div>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
@(active: String)(body: Html)(implicit context: gitbucket.core.controller.Context)
|
||||
@import context._
|
||||
<div class="container">
|
||||
<div class="row-fluid">
|
||||
<div class="span3">
|
||||
<div class="container body">
|
||||
<div class="row">
|
||||
<div class="col-md-3">
|
||||
<ul class="nav nav-tabs nav-stacked side-menu" id="system-admin-menu-container">
|
||||
<li@if(active=="users"){ class="active"}>
|
||||
<a href="@path/admin/users">User Management</a>
|
||||
@@ -18,7 +18,7 @@
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="span9">
|
||||
<div class="col-md-9">
|
||||
@body
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2,29 +2,41 @@
|
||||
@import context._
|
||||
@import gitbucket.core.view.helpers._
|
||||
@html.main("Plugins"){
|
||||
@admin.html.menu("plugins") {
|
||||
<h1>Installed plugins</h1>
|
||||
@admin.html.menu("plugins") {
|
||||
<h1>Installed plugins</h1>
|
||||
|
||||
@if(plugins.size > 0) {
|
||||
<ul>
|
||||
@plugins.map {plugin =>
|
||||
<li><a href="#@plugin.pluginId">@plugin.pluginId:@plugin.version</a></li>
|
||||
}
|
||||
</ul>
|
||||
@if(plugins.size > 0) {
|
||||
<ul>
|
||||
@plugins.map {plugin =>
|
||||
<li><a href="#@plugin.pluginId">@plugin.pluginId:@plugin.version</a></li>
|
||||
}
|
||||
</ul>
|
||||
|
||||
@plugins.map {plugin =>
|
||||
<div class="box">
|
||||
<div class="box-header">@plugin.pluginName</div>
|
||||
<div class="box-content">
|
||||
<p><span class="strong">Id: </span>@plugin.pluginId</p>
|
||||
<p><span class="strong">Version: </span>@plugin.version</p>
|
||||
<p><span class="strong">Name: </span>@plugin.pluginName</p>
|
||||
<p class="muted">@plugin.description</p>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
} else {
|
||||
<p>No plugin detected on your gitbucket installation.</p>
|
||||
}
|
||||
@plugins.map {plugin =>
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading strong">@plugin.pluginName</div>
|
||||
<div class="panel-body">
|
||||
<div class="row">
|
||||
<label class="col-md-2">Id</label>
|
||||
<span class="col-md-8">@plugin.pluginId</span>
|
||||
</div>
|
||||
<div class="row">
|
||||
<label class="col-md-2">Version</label>
|
||||
<span class="col-md-8">@plugin.version</span>
|
||||
</div>
|
||||
<div class="row">
|
||||
<label class="col-md-2">Name</label>
|
||||
<span class="col-md-8">@plugin.pluginName</span>
|
||||
</div>
|
||||
<div class="row">
|
||||
<label class="col-md-2">Description</label>
|
||||
<span class="col-md-8 muted">@plugin.description</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
} else {
|
||||
<p>No plugin detected on your gitbucket installation.</p>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,10 +5,10 @@
|
||||
@html.main("System Settings"){
|
||||
@menu("system"){
|
||||
@helper.html.information(info)
|
||||
<form action="@path/admin/system" method="POST" validate="true">
|
||||
<div class="box">
|
||||
<div class="box-header">System Settings</div>
|
||||
<div class="box-content">
|
||||
<form action="@path/admin/system" method="POST" validate="true" class="form-horizontal">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading strong">System Settings</div>
|
||||
<div class="panel-body">
|
||||
<!--====================================================================-->
|
||||
<!-- GITBUCKET_HOME -->
|
||||
<!--====================================================================-->
|
||||
@@ -21,7 +21,7 @@
|
||||
<label><span class="strong">Base URL</span> (e.g. <code>http://example.com/gitbucket</code>)</label>
|
||||
<fieldset>
|
||||
<div class="controls">
|
||||
<input type="text" name="baseUrl" id="baseUrl" style="width: 400px" value="@settings.baseUrl"/>
|
||||
<input type="text" name="baseUrl" id="baseUrl" class="form-control" value="@settings.baseUrl"/>
|
||||
<span id="error-baseUrl" class="error"></span>
|
||||
</div>
|
||||
</fieldset>
|
||||
@@ -36,7 +36,7 @@
|
||||
<hr>
|
||||
<label><span class="strong">Information</span> (HTML is available)</label>
|
||||
<fieldset>
|
||||
<textarea name="information" style="width: 600px; height: 100px;">@settings.information</textarea>
|
||||
<textarea name="information" class="form-control" style="height: 100px;">@settings.information</textarea>
|
||||
</fieldset>
|
||||
<!--====================================================================-->
|
||||
<!-- Account registration -->
|
||||
@@ -46,11 +46,11 @@
|
||||
<fieldset>
|
||||
<label class="radio">
|
||||
<input type="radio" name="allowAccountRegistration" value="true"@if(settings.allowAccountRegistration){ checked}>
|
||||
<span class="strong">Allow</span> - Users can create accounts by themselves.
|
||||
<span class="strong">Allow</span> <span class="normal">- Users can create accounts by themselves.</span>
|
||||
</label>
|
||||
<label class="radio">
|
||||
<input type="radio" name="allowAccountRegistration" value="false"@if(!settings.allowAccountRegistration){ checked}>
|
||||
<span class="strong">Deny</span> - Only administrators can create accounts.
|
||||
<span class="strong">Deny</span> - <span class="normal">Only administrators can create accounts.</span>
|
||||
</label>
|
||||
</fieldset>
|
||||
<hr>
|
||||
@@ -58,11 +58,11 @@
|
||||
<fieldset>
|
||||
<label class="radio">
|
||||
<input type="radio" name="isCreateRepoOptionPublic" value="true"@if(settings.isCreateRepoOptionPublic){ checked}>
|
||||
<span class="strong">Public</span> - All users and guests can read that repository.
|
||||
<span class="strong">Public</span> <span class="normal">- All users and guests can read that repository.</span>
|
||||
</label>
|
||||
<label class="radio">
|
||||
<input type="radio" name="isCreateRepoOptionPublic" value="false"@if(!settings.isCreateRepoOptionPublic){ checked}>
|
||||
<span class="strong">Private</span> - Only collaborators can read that repository.
|
||||
<span class="strong">Private</span> <span class="normal">- Only collaborators can read that repository.</span>
|
||||
</label>
|
||||
</fieldset>
|
||||
<!--====================================================================-->
|
||||
@@ -73,11 +73,11 @@
|
||||
<fieldset>
|
||||
<label class="radio">
|
||||
<input type="radio" name="allowAnonymousAccess" value="true"@if(settings.allowAnonymousAccess){ checked}>
|
||||
<span class="strong">Allow</span> - Anyone can view public repositories, user/group profiles.
|
||||
<span class="strong">Allow</span> <span class="normal">- Anyone can view public repositories, user/group profiles.</span>
|
||||
</label>
|
||||
<label class="radio">
|
||||
<input type="radio" name="allowAnonymousAccess" value="false"@if(!settings.allowAnonymousAccess){ checked}>
|
||||
<span class="strong">Deny</span> - Users must authenticate before viewing any information
|
||||
<span class="strong">Deny</span> <span class="normal">- Users must authenticate before viewing any information.</span>
|
||||
</label>
|
||||
</fieldset>
|
||||
<!--====================================================================-->
|
||||
@@ -86,7 +86,7 @@
|
||||
<hr>
|
||||
<label><span class="strong">Limit of activity logs</span> (Unlimited if it's not specified or zero)</label>
|
||||
<div class="controls">
|
||||
<input type="text" id="activityLogLimit" name="activityLogLimit" class="input-mini" value="@settings.activityLogLimit"/>
|
||||
<input type="text" id="activityLogLimit" name="activityLogLimit" class="form-control input-mini" value="@settings.activityLogLimit"/>
|
||||
<span id="error-activityLogLimit" class="error"></span>
|
||||
</div>
|
||||
<!--====================================================================-->
|
||||
@@ -111,13 +111,11 @@
|
||||
Enable SSH access to git repository
|
||||
</label>
|
||||
</fieldset>
|
||||
<div class="form-horizontal ssh">
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="sshPort">SSH Port</label>
|
||||
<div class="controls">
|
||||
<input type="text" id="sshPort" name="sshPort" value="@settings.sshPort"/>
|
||||
<span id="error-sshPort" class="error"></span>
|
||||
</div>
|
||||
<div class="form-group ssh">
|
||||
<label class="control-label col-md-3" for="sshPort">SSH Port</label>
|
||||
<div class="col-md-9">
|
||||
<input type="text" id="sshPort" name="sshPort" class="form-control" value="@settings.sshPort"/>
|
||||
<span id="error-sshPort" class="error"></span>
|
||||
</div>
|
||||
</div>
|
||||
<p class="muted">
|
||||
@@ -130,92 +128,90 @@
|
||||
<label class="strong">Authentication</label>
|
||||
<fieldset>
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" id="ldapAuthentication" name="ldapAuthentication"@if(settings.ldap){ checked}/>
|
||||
<input type="checkbox" id="ldapAuthentication" name="ldapAuthentication"@if(settings.ldap){ checked} />
|
||||
LDAP
|
||||
</label>
|
||||
</fieldset>
|
||||
<div class="form-horizontal ldap">
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="ldapHost">LDAP Host</label>
|
||||
<div class="controls">
|
||||
<input type="text" id="ldapHost" name="ldap.host" value="@settings.ldap.map(_.host)"/>
|
||||
<div class="ldap">
|
||||
<div class="form-group">
|
||||
<label class="control-label col-md-3" for="ldapHost">LDAP Host</label>
|
||||
<div class="col-md-9">
|
||||
<input type="text" id="ldapHost" name="ldap.host" class="form-control" value="@settings.ldap.map(_.host)"/>
|
||||
<span id="error-ldap_host" class="error"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="ldapPort">LDAP Port</label>
|
||||
<div class="controls">
|
||||
<input type="text" id="ldapPort" name="ldap.port" class="input-mini" value="@settings.ldap.map(_.port)"/>
|
||||
<div class="form-group">
|
||||
<label class="control-label col-md-3" for="ldapPort">LDAP Port</label>
|
||||
<div class="col-md-9">
|
||||
<input type="text" id="ldapPort" name="ldap.port" class="form-control input-mini" value="@settings.ldap.map(_.port)"/>
|
||||
<span id="error-ldap_port" class="error"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="ldapBindDN">Bind DN</label>
|
||||
<div class="controls">
|
||||
<input type="text" id="ldapBindDN" name="ldap.bindDN" value="@settings.ldap.map(_.bindDN)"/>
|
||||
<div class="form-group">
|
||||
<label class="control-label col-md-3" for="ldapBindDN">Bind DN</label>
|
||||
<div class="col-md-9">
|
||||
<input type="text" id="ldapBindDN" name="ldap.bindDN" class="form-control" value="@settings.ldap.map(_.bindDN)"/>
|
||||
<span id="error-ldap_bindDN" class="error"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="ldapBindPassword">Bind Password</label>
|
||||
<div class="controls">
|
||||
<input type="password" id="ldapBindPassword" name="ldap.bindPassword" value="@settings.ldap.map(_.bindPassword)"/>
|
||||
<div class="form-group">
|
||||
<label class="control-label col-md-3" for="ldapBindPassword">Bind Password</label>
|
||||
<div class="col-md-9">
|
||||
<input type="password" id="ldapBindPassword" name="ldap.bindPassword" class="form-control" value="@settings.ldap.map(_.bindPassword)"/>
|
||||
<span id="error-ldap_bindPassword" class="error"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="ldapBaseDN">Base DN</label>
|
||||
<div class="controls">
|
||||
<input type="text" id="ldapBaseDN" name="ldap.baseDN" value="@settings.ldap.map(_.baseDN)"/>
|
||||
<div class="form-group">
|
||||
<label class="control-label col-md-3" for="ldapBaseDN">Base DN</label>
|
||||
<div class="col-md-9">
|
||||
<input type="text" id="ldapBaseDN" name="ldap.baseDN" class="form-control" value="@settings.ldap.map(_.baseDN)"/>
|
||||
<span id="error-ldap_baseDN" class="error"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="ldapUserNameAttribute">User name attribute</label>
|
||||
<div class="controls">
|
||||
<input type="text" id="ldapUserNameAttribute" name="ldap.userNameAttribute" value="@settings.ldap.map(_.userNameAttribute)"/>
|
||||
<div class="form-group">
|
||||
<label class="control-label col-md-3" for="ldapUserNameAttribute">User name attribute</label>
|
||||
<div class="col-md-9">
|
||||
<input type="text" id="ldapUserNameAttribute" name="ldap.userNameAttribute" class="form-control" value="@settings.ldap.map(_.userNameAttribute)"/>
|
||||
<span id="error-ldap_userNameAttribute" class="error"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="ldapAdditionalFilterCondition">Additional filter condition</label>
|
||||
<div class="controls">
|
||||
<input type="text" id="ldapAdditionalFilterCondition" name="ldap.additionalFilterCondition" value="@settings.ldap.map(_.additionalFilterCondition)"/>
|
||||
<div class="form-group">
|
||||
<label class="control-label col-md-3" for="ldapAdditionalFilterCondition">Additional filter condition</label>
|
||||
<div class="col-md-9">
|
||||
<input type="text" id="ldapAdditionalFilterCondition" name="ldap.additionalFilterCondition" class="form-control" value="@settings.ldap.map(_.additionalFilterCondition)"/>
|
||||
<span id="error-ldap_additionalFilterCondition" class="error"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="ldapFullNameAttribute">Full name attribute</label>
|
||||
<div class="controls">
|
||||
<input type="text" id="ldapFullNameAttribute" name="ldap.fullNameAttribute" value="@settings.ldap.map(_.fullNameAttribute)"/>
|
||||
<div class="form-group">
|
||||
<label class="control-label col-md-3" for="ldapFullNameAttribute">Full name attribute</label>
|
||||
<div class="col-md-9">
|
||||
<input type="text" id="ldapFullNameAttribute" name="ldap.fullNameAttribute" class="form-control" value="@settings.ldap.map(_.fullNameAttribute)"/>
|
||||
<span id="error-ldap_fullNameAttribute" class="error"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="ldapMailAttribute">Mail address attribute</label>
|
||||
<div class="controls">
|
||||
<input type="text" id="ldapMailAttribute" name="ldap.mailAttribute" value="@settings.ldap.map(_.mailAttribute)"/>
|
||||
<div class="form-group">
|
||||
<label class="control-label col-md-3" for="ldapMailAttribute">Mail address attribute</label>
|
||||
<div class="col-md-9">
|
||||
<input type="text" id="ldapMailAttribute" name="ldap.mailAttribute" class="form-control" value="@settings.ldap.map(_.mailAttribute)"/>
|
||||
<span id="error-ldap_mailAttribute" class="error"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<div class="controls">
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" name="ldap.tls"@if(settings.ldap.flatMap(_.tls).getOrElse(false)){ checked}/> Enable TLS
|
||||
</label>
|
||||
<div class="form-group">
|
||||
<label class="control-label col-md-3">Enable TLS</label>
|
||||
<div class="col-md-9">
|
||||
<input type="checkbox" name="ldap.tls"@if(settings.ldap.flatMap(_.tls).getOrElse(false)){ checked}/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<div class="controls">
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" name="ldap.ssl"@if(settings.ldap.flatMap(_.ssl).getOrElse(false)){ checked}/> Enable SSL
|
||||
</label>
|
||||
<div class="form-group">
|
||||
<label class="control-label col-md-3">Enable SSL</label>
|
||||
<div class="col-md-9">
|
||||
<input type="checkbox" name="ldap.ssl"@if(settings.ldap.flatMap(_.ssl).getOrElse(false)){ checked}/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="ldapBindDN">Keystore</label>
|
||||
<div class="controls">
|
||||
<input type="text" id="ldapKeystore" name="ldap.keystore" value="@settings.ldap.map(_.keystore)"/>
|
||||
<div class="form-group">
|
||||
<label class="control-label col-md-3" for="ldapBindDN">Keystore</label>
|
||||
<div class="col-md-9">
|
||||
<input type="text" id="ldapKeystore" name="ldap.keystore" class="form-control" value="@settings.ldap.map(_.keystore)"/>
|
||||
<span id="error-ldap_keystore" class="error"></span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -242,54 +238,53 @@
|
||||
SMTP
|
||||
</label>
|
||||
</fieldset>
|
||||
<div class="form-horizontal useSMTP">
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="smtpHost">SMTP Host</label>
|
||||
<div class="controls">
|
||||
<input type="text" id="smtpHost" name="smtp.host" value="@settings.smtp.map(_.host)"/>
|
||||
<div class="useSMTP">
|
||||
<div class="form-group">
|
||||
<label class="control-label col-md-3" for="smtpHost">SMTP Host</label>
|
||||
<div class="col-md-9">
|
||||
<input type="text" id="smtpHost" name="smtp.host" class="form-control" value="@settings.smtp.map(_.host)"/>
|
||||
<span id="error-smtp_host" class="error"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="smtpPort">SMTP Port</label>
|
||||
<div class="controls">
|
||||
<input type="text" id="smtpPort" name="smtp.port" class="input-mini" value="@settings.smtp.map(_.port)"/>
|
||||
<div class="form-group">
|
||||
<label class="control-label col-md-3" for="smtpPort">SMTP Port</label>
|
||||
<div class="col-md-9">
|
||||
<input type="text" id="smtpPort" name="smtp.port" class="form-control input-mini" value="@settings.smtp.map(_.port)"/>
|
||||
<span id="error-smtp_port" class="error"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="smtpUser">SMTP User</label>
|
||||
<div class="controls">
|
||||
<input type="text" id="smtpUser" name="smtp.user" value="@settings.smtp.map(_.user)"/>
|
||||
<div class="form-group">
|
||||
<label class="control-label col-md-3" for="smtpUser">SMTP User</label>
|
||||
<div class="col-md-9">
|
||||
<input type="text" id="smtpUser" name="smtp.user" class="form-control" value="@settings.smtp.map(_.user)"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="smtpPassword">SMTP Password</label>
|
||||
<div class="controls">
|
||||
<input type="password" id="smtpPassword" name="smtp.password" value="@settings.smtp.map(_.password)"/>
|
||||
<div class="form-group">
|
||||
<label class="control-label col-md-3" for="smtpPassword">SMTP Password</label>
|
||||
<div class="col-md-9">
|
||||
<input type="password" id="smtpPassword" name="smtp.password" class="form-control" value="@settings.smtp.map(_.password)"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<div class="controls">
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" name="smtp.ssl"@if(settings.smtp.flatMap(_.ssl).getOrElse(false)){ checked}/> Enable SSL
|
||||
</label>
|
||||
<div class="form-group">
|
||||
<label class="control-label col-md-3" for="smtpPassword">Enable SSL</label>
|
||||
<div class="col-md-9">
|
||||
<input type="checkbox" name="smtp.ssl"@if(settings.smtp.flatMap(_.ssl).getOrElse(false)){ checked}/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="fromAddress">FROM Address</label>
|
||||
<div class="controls">
|
||||
<input type="text" id="fromAddress" name="smtp.fromAddress" value="@settings.smtp.map(_.fromAddress)"/>
|
||||
<div class="form-group">
|
||||
<label class="control-label col-md-3" for="fromAddress">FROM Address</label>
|
||||
<div class="col-md-9">
|
||||
<input type="text" id="fromAddress" name="smtp.fromAddress" class="form-control" value="@settings.smtp.map(_.fromAddress)"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label class="control-label" for="fromName">FROM Name</label>
|
||||
<div class="controls">
|
||||
<input type="text" id="fromName" name="smtp.fromName" value="@settings.smtp.map(_.fromName)"/>
|
||||
<div class="form-group">
|
||||
<label class="control-label col-md-3" for="fromName">FROM Name</label>
|
||||
<div class="col-md-9">
|
||||
<input type="text" id="fromName" name="smtp.fromName" class="form-control" value="@settings.smtp.map(_.fromName)"/>
|
||||
</div>
|
||||
</div>
|
||||
<p class="muted">
|
||||
Enable notification not only SMTP configuration if you want to send nitification email.
|
||||
Enable notification not only SMTP configuration if you want to send notification email.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -322,4 +317,4 @@ $(function(){
|
||||
$('.ldap input').prop('disabled', !$(this).prop('checked'));
|
||||
}).change();
|
||||
});
|
||||
</script>
|
||||
</script>
|
||||
|
||||
@@ -4,14 +4,14 @@
|
||||
@html.main(if(account.isEmpty) "New Group" else "Update Group"){
|
||||
@admin.html.menu("users"){
|
||||
<form method="POST" action="@if(account.isEmpty){@path/admin/users/_newgroup} else {@path/admin/users/@account.get.userName/_editgroup}" validate="true">
|
||||
<div class="row-fluid">
|
||||
<div class="span5">
|
||||
<fieldset>
|
||||
<div class="row">
|
||||
<div class="col-md-5">
|
||||
<fieldset class="form-group">
|
||||
<label for="groupName" class="strong">Group name</label>
|
||||
<div>
|
||||
<span id="error-groupName" class="error"></span>
|
||||
</div>
|
||||
<input type="text" name="groupName" id="groupName" value="@account.map(_.userName)"@if(account.isDefined){ readonly}/>
|
||||
<input type="text" name="groupName" id="groupName" class="form-control" value="@account.map(_.userName)"@if(account.isDefined){ readonly}/>
|
||||
@if(account.isDefined){
|
||||
<label for="removed">
|
||||
<input type="checkbox" name="removed" id="removed" value="true" @if(account.get.isRemoved){checked}/>
|
||||
@@ -19,25 +19,25 @@
|
||||
</label>
|
||||
}
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<fieldset class="form-group">
|
||||
<label class="strong">URL (Optional)</label>
|
||||
<div>
|
||||
<span id="error-url" class="error"></span>
|
||||
</div>
|
||||
<input type="text" name="url" id="url" value="@account.map(_.url)"/>
|
||||
<input type="text" name="url" id="url" class="form-control" value="@account.map(_.url)"/>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<fieldset class="form-group">
|
||||
<label for="avatar" class="strong">Image (Optional)</label>
|
||||
@helper.html.uploadavatar(account)
|
||||
</fieldset>
|
||||
</div>
|
||||
<div class="span7">
|
||||
<fieldset>
|
||||
<div class="col-md-7">
|
||||
<fieldset class="form-group">
|
||||
<label class="strong">Members</label>
|
||||
<ul id="member-list" class="collaborator">
|
||||
</ul>
|
||||
@helper.html.account("memberName", 200)
|
||||
<input type="button" class="btn" value="Add" id="addMember"/>
|
||||
<input type="button" class="btn btn-default" value="Add" id="addMember"/>
|
||||
<input type="hidden" id="members" name="members" value="@members.map(member => member.userName + ":" + member.isManager).mkString(",")"/>
|
||||
<div>
|
||||
<span class="error" id="error-members"></span>
|
||||
@@ -47,7 +47,7 @@
|
||||
</div>
|
||||
<fieldset class="margin">
|
||||
<input type="submit" class="btn btn-success" value="@if(account.isEmpty){Create Group} else {Update Group}"/>
|
||||
<a href="@path/admin/users" class="btn">Cancel</a>
|
||||
<a href="@path/admin/users" class="btn btn-default">Cancel</a>
|
||||
</fieldset>
|
||||
</form>
|
||||
}
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
@html.main("Manage Users"){
|
||||
@admin.html.menu("users"){
|
||||
<div class="pull-right" style="margin-bottom: 4px;">
|
||||
<a href="@path/admin/users/_newuser" class="btn">New User</a>
|
||||
<a href="@path/admin/users/_newgroup" class="btn">New Group</a>
|
||||
<a href="@path/admin/users/_newuser" class="btn btn-default">New User</a>
|
||||
<a href="@path/admin/users/_newgroup" class="btn btn-default">New Group</a>
|
||||
</div>
|
||||
<label for="includeRemoved">
|
||||
<input type="checkbox" id="includeRemoved" name="includeRemoved" @if(includeRemoved){checked}/>
|
||||
|
||||
@@ -3,14 +3,14 @@
|
||||
@html.main(if(account.isEmpty) "New User" else "Update User"){
|
||||
@admin.html.menu("users"){
|
||||
<form method="POST" action="@if(account.isEmpty){@path/admin/users/_newuser} else {@path/admin/users/@account.get.userName/_edituser}" validate="true">
|
||||
<div class="row-fluid">
|
||||
<div class="span6">
|
||||
<fieldset>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<fieldset class="form-group">
|
||||
<label for="userName" class="strong">Username:</label>
|
||||
<div>
|
||||
<span id="error-userName" class="error"></span>
|
||||
</div>
|
||||
<input type="text" name="userName" id="userName" value="@account.map(_.userName)"@if(account.isDefined){ readonly}/>
|
||||
<input type="text" name="userName" id="userName" class="form-control" value="@account.map(_.userName)"@if(account.isDefined){ readonly}/>
|
||||
@if(account.isDefined){
|
||||
<label for="removed">
|
||||
<input type="checkbox" name="removed" id="removed" value="true" @if(account.get.isRemoved){checked}/>
|
||||
@@ -22,7 +22,7 @@
|
||||
}
|
||||
</fieldset>
|
||||
@if(account.map(_.password.nonEmpty).getOrElse(true)){
|
||||
<fieldset>
|
||||
<fieldset class="form-group">
|
||||
<label for="password" class="strong">
|
||||
Password
|
||||
@if(account.isDefined){
|
||||
@@ -33,24 +33,24 @@
|
||||
<div>
|
||||
<span id="error-password" class="error"></span>
|
||||
</div>
|
||||
<input type="password" name="password" id="password" value="" autocomplete="off"/>
|
||||
<input type="password" name="password" id="password" class="form-control" value="" autocomplete="off"/>
|
||||
</fieldset>
|
||||
}
|
||||
<fieldset>
|
||||
<fieldset class="form-group">
|
||||
<label for="fullName" class="strong">Full Name:</label>
|
||||
<div>
|
||||
<span id="error-fullName" class="error"></span>
|
||||
</div>
|
||||
<input type="text" name="fullName" id="fullName" value="@account.map(_.fullName)"/>
|
||||
<input type="text" name="fullName" id="fullName" class="form-control" value="@account.map(_.fullName)"/>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<fieldset class="form-group">
|
||||
<label for="mailAddress" class="strong">Mail Address:</label>
|
||||
<div>
|
||||
<span id="error-mailAddress" class="error"></span>
|
||||
</div>
|
||||
<input type="text" name="mailAddress" id="mailAddress" value="@account.map(_.mailAddress)"/>
|
||||
<input type="text" name="mailAddress" id="mailAddress" class="form-control" value="@account.map(_.mailAddress)"/>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<fieldset class="form-group">
|
||||
<label class="strong">User Type:</label>
|
||||
<label class="radio" for="userType_Normal">
|
||||
<input type="radio" name="isAdmin" id="userType_Normal" value="false"@if(account.isEmpty || !account.get.isAdmin){ checked}/> Normal
|
||||
@@ -59,16 +59,16 @@
|
||||
<input type="radio" name="isAdmin" id="userType_Admin" value="true"@if(account.isDefined && account.get.isAdmin){ checked}/> Administrator
|
||||
</label>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<fieldset class="form-group">
|
||||
<label class="strong">URL (Optional):</label>
|
||||
<div>
|
||||
<span id="error-url" class="error"></span>
|
||||
</div>
|
||||
<input type="text" name="url" id="url" style="width: 400px;" value="@account.map(_.url)"/>
|
||||
<input type="text" name="url" id="url" class="form-control" value="@account.map(_.url)"/>
|
||||
</fieldset>
|
||||
</div>
|
||||
<div class="span6">
|
||||
<fieldset>
|
||||
<div class="col-md-6">
|
||||
<fieldset class="form-group">
|
||||
<label for="avatar" class="strong">Image (Optional)</label>
|
||||
@helper.html.uploadavatar(account)
|
||||
</fieldset>
|
||||
@@ -76,7 +76,7 @@
|
||||
</div>
|
||||
<fieldset class="margin">
|
||||
<input type="submit" class="btn btn-success" value="@if(account.isEmpty){Create User} else {Update User}"/>
|
||||
<a href="@path/admin/users" class="btn">Cancel</a>
|
||||
<a href="@path/admin/users" class="btn btn-default">Cancel</a>
|
||||
</fieldset>
|
||||
</form>
|
||||
}
|
||||
|
||||
@@ -10,53 +10,63 @@
|
||||
@import gitbucket.core.service.IssuesService
|
||||
@import gitbucket.core.service.IssuesService.IssueInfo
|
||||
<table class="table table-bordered table-hover table-issues">
|
||||
<tr>
|
||||
<th style="background-color: #eee;">
|
||||
@dashboard.html.header(openCount, closedCount, condition, groups)
|
||||
</th>
|
||||
</tr>
|
||||
@issues.map { case IssueInfo(issue, labels, milestone, commentCount, commitStatus) =>
|
||||
<tr>
|
||||
<td style="padding-top: 15px; padding-bottom: 15px;">
|
||||
@if(issue.isPullRequest){
|
||||
<i class="octicon octicon-git-pull-request @(if(issue.closed) "closed" else "open")"></i>
|
||||
} else {
|
||||
<i class="octicon octicon-issue-@(if(issue.closed) "closed" else "opened")"></i>
|
||||
}
|
||||
<a href="@path/@issue.userName/@issue.repositoryName">@issue.userName/@issue.repositoryName</a> ・
|
||||
@if(issue.isPullRequest){
|
||||
<a href="@path/@issue.userName/@issue.repositoryName/pull/@issue.issueId" class="issue-title">@issue.title</a>
|
||||
} else {
|
||||
<a href="@path/@issue.userName/@issue.repositoryName/issues/@issue.issueId" class="issue-title">@issue.title</a>
|
||||
}
|
||||
@gitbucket.core.issues.html.commitstatus(issue, commitStatus)
|
||||
@labels.map { label =>
|
||||
<span class="label-color small" style="background-color: #@label.color; color: #@label.fontColor; padding-left: 4px; padding-right: 4px">@label.labelName</span>
|
||||
}
|
||||
<span class="pull-right muted">
|
||||
@issue.assignedUserName.map { userName =>
|
||||
@avatar(userName, 20, tooltip = true)
|
||||
}
|
||||
@if(commentCount > 0){
|
||||
<a href="@path/@issue.userName/@issue.repositoryName/issues/@issue.issueId" class="issue-comment-count">
|
||||
<i class="octicon octicon-comment active"></i> @commentCount
|
||||
</a>
|
||||
} else {
|
||||
<a href="@path/@issue.userName/@issue.repositoryName/issues/@issue.issueId" class="issue-comment-count" style="color: silver;">
|
||||
<i class="octicon octicon-comment"></i> @commentCount
|
||||
</a>
|
||||
}
|
||||
</span>
|
||||
<div class="small muted" style="margin-left: 20px; margin-top: 5px;">
|
||||
#@issue.issueId opened by @user(issue.openedUserName, styleClass="username") @datetime(issue.registeredDate)
|
||||
@milestone.map { milestone =>
|
||||
<span style="margin: 20px;"><a href="@condition.copy(milestone = Some(Some(milestone))).toURL" class="username"><i class="octicon octicon-milestone"></i>
|
||||
@milestone</a></span>
|
||||
}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="background-color: #eee;">
|
||||
@dashboard.html.header(openCount, closedCount, condition, groups)
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@issues.map { case IssueInfo(issue, labels, milestone, commentCount, commitStatus) =>
|
||||
<tr>
|
||||
<td style="padding-top: 12px; padding-bottom: 12px;">
|
||||
@if(issue.isPullRequest){
|
||||
<i class="octicon octicon-git-pull-request @(if(issue.closed) "closed" else "open")"></i>
|
||||
} else {
|
||||
<i class="octicon octicon-issue-@(if(issue.closed) "closed" else "opened")"></i>
|
||||
}
|
||||
<a href="@path/@issue.userName/@issue.repositoryName">@issue.userName/@issue.repositoryName</a> ・
|
||||
@if(issue.isPullRequest){
|
||||
<a href="@path/@issue.userName/@issue.repositoryName/pull/@issue.issueId" class="issue-title">@issue.title</a>
|
||||
} else {
|
||||
<a href="@path/@issue.userName/@issue.repositoryName/issues/@issue.issueId" class="issue-title">@issue.title</a>
|
||||
}
|
||||
@gitbucket.core.issues.html.commitstatus(issue, commitStatus)
|
||||
@labels.map { label =>
|
||||
<span class="label-color small" style="background-color: #@label.color; color: #@label.fontColor; padding-left: 4px; padding-right: 4px">@label.labelName</span>
|
||||
}
|
||||
<span class="pull-right muted">
|
||||
@issue.assignedUserName.map { userName =>
|
||||
@avatar(userName, 20, tooltip = true)
|
||||
}
|
||||
@if(commentCount > 0){
|
||||
<a href="@path/@issue.userName/@issue.repositoryName/issues/@issue.issueId" class="issue-comment-count">
|
||||
<i class="octicon octicon-comment active"></i> @commentCount
|
||||
</a>
|
||||
} else {
|
||||
<a href="@path/@issue.userName/@issue.repositoryName/issues/@issue.issueId" class="issue-comment-count" style="color: silver;">
|
||||
<i class="octicon octicon-comment"></i> @commentCount
|
||||
</a>
|
||||
}
|
||||
</span>
|
||||
<div class="small muted" style="margin-left: 20px; margin-top: 2px;">
|
||||
#@issue.issueId opened by @user(issue.openedUserName, styleClass="username") @datetime(issue.registeredDate)
|
||||
@milestone.map { milestone =>
|
||||
<span style="margin: 20px;"><a href="@condition.copy(milestone = Some(Some(milestone))).toURL" class="username"><i class="octicon octicon-milestone"></i> @milestone</a></span>
|
||||
}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
@if(issues.isEmpty){
|
||||
<tr>
|
||||
<td style="padding: 20px; background-color: #eee; text-align: center;">
|
||||
No results matched your search.
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="pull-right">
|
||||
@helper.html.paginator(page, (if(condition.state == "open") openCount else closedCount), IssuesService.IssueLimit, 10, condition.toURL)
|
||||
|
||||
@@ -3,20 +3,18 @@
|
||||
condition: gitbucket.core.service.IssuesService.IssueSearchCondition)(implicit context: gitbucket.core.controller.Context)
|
||||
@import context._
|
||||
@import gitbucket.core.view.helpers._
|
||||
<ul class="nav nav-pills-group pull-left fill-width">
|
||||
<li class="@if(filter == "created_by"){active} first">
|
||||
<ul class="nav nav-pills pull-left" style="line-height: 14px;">
|
||||
<li class="@if(filter == "created_by"){active}">
|
||||
<a href="@path/dashboard/@active/created_by@condition.copy(author = None, assigned = None).toURL">Created</a>
|
||||
</li>
|
||||
<li class="@if(filter == "assigned"){active}">
|
||||
<a href="@path/dashboard/@active/assigned@condition.copy(author = None, assigned = None).toURL">Assigned</a>
|
||||
</li>
|
||||
<li class="@if(filter == "mentioned"){active} last">
|
||||
<li class="@if(filter == "mentioned"){active}">
|
||||
<a href="@path/dashboard/@active/mentioned@condition.copy(author = None, assigned = None).toURL">Mentioned</a>
|
||||
</li>
|
||||
<li class="pull-right">
|
||||
<form method="GET" id="search-filter-form" action="@path/dashboard/@active" style="margin-bottom: 0px;">
|
||||
<input type="text" id="search-filter-box" class="input-xlarge" name="q" style="height: 24px; width: 400px;"
|
||||
value="is:@{if(active == "issues") "issue" else "pr"} @condition.toFilterString"/>
|
||||
</form>
|
||||
</li>
|
||||
</ul>
|
||||
<form method="GET" id="search-filter-form" action="@path/dashboard/@active" class="pull-right">
|
||||
<input type="text" id="search-filter-box" class="form-control input-lg" name="q" style="width: 400px;"
|
||||
value="is:@{if(active == "issues") "issue" else "pr"} @condition.toFilterString"/>
|
||||
</form>
|
||||
|
||||
@@ -4,17 +4,14 @@
|
||||
<div class="dashboard-nav">
|
||||
<div class="container">
|
||||
<a href="@path/" @if(active == ""){ class="active"}>
|
||||
<i class="octicon octicon-rss"></i>
|
||||
News Feed
|
||||
<i class="octicon octicon-rss"></i> News Feed
|
||||
</a>
|
||||
@if(loginAccount.isDefined){
|
||||
<a href="@path/dashboard/pulls" @if(active == "pulls" ){ class="active"}>
|
||||
<i class="octicon octicon-git-pull-request"></i>
|
||||
Pull Requests
|
||||
<i class="octicon octicon-git-pull-request"></i> Pull Requests
|
||||
</a>
|
||||
<a href="@path/dashboard/issues" @if(active == "issues"){ class="active"}>
|
||||
<i class="octicon octicon-issue-opened"></i>
|
||||
Issues
|
||||
<i class="octicon octicon-issue-opened"></i> Issues
|
||||
</a>
|
||||
}
|
||||
</div>
|
||||
@@ -27,8 +24,6 @@ div.dashboard-nav {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
|
||||
|
||||
div.dashboard-nav a {
|
||||
line-height: 10px;
|
||||
margin-left: 20px;
|
||||
@@ -38,7 +33,6 @@ div.dashboard-nav a {
|
||||
color: #888;
|
||||
}
|
||||
|
||||
|
||||
div.dashboard-nav .octicon{
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
@(id: String, width: Int)(implicit context: gitbucket.core.controller.Context)
|
||||
@import context._
|
||||
<input type="text" name="@id" id="@id" style="width: @{width}px; margin-bottom: 0px;"/>
|
||||
<span style="margin-right: 0px;">
|
||||
<input type="text" name="@id" id="@id" class="form-control" autocomplete="off" style="width: @{width}px; margin-bottom: 0px; display: inline; vertical-align: middle;"/>
|
||||
</span>
|
||||
<script>
|
||||
$(function(){
|
||||
$('#@id').typeahead({
|
||||
|
||||
@@ -1,22 +1,24 @@
|
||||
@(owner: String, repository: String)(textarea: Html)(implicit context: gitbucket.core.controller.Context)
|
||||
@import context._
|
||||
@import gitbucket.core.util.FileUtil
|
||||
<div class="muted attachable">
|
||||
@textarea
|
||||
<div class="clickable">Attach images by dragging & dropping, or selecting them.</div>
|
||||
<div class="clickable">Attach images or documents by dragging & dropping, or selecting them.</div>
|
||||
</div>
|
||||
@defining("(id=\")([\\w\\-]*)(\")".r.findFirstMatchIn(textarea.body).map(_.group(2))){ textareaId =>
|
||||
<script>
|
||||
$(function(){
|
||||
try {
|
||||
$([$('#@textareaId').closest('div')[0], $('#@textareaId').next('div')[0]]).dropzone({
|
||||
url: '@path/upload/image/@owner/@repository',
|
||||
url: '@path/upload/file/@owner/@repository',
|
||||
maxFilesize: 10,
|
||||
acceptedFiles: 'image/*',
|
||||
dictInvalidFileType: 'Unfortunately, we don\'t support that file type. Try again with a PNG, GIF, or JPG.',
|
||||
previewTemplate: "<div class=\"dz-preview\">\n <div class=\"dz-progress\"><span class=\"dz-upload\" data-dz-uploadprogress>Uploading your images...</span></div>\n <div class=\"dz-error-message\"><span data-dz-errormessage></span></div>\n</div>",
|
||||
acceptedFiles: @Html(FileUtil.mimeTypeWhiteList.mkString("'", ",", "'")),
|
||||
dictInvalidFileType: 'Unfortunately, we don\'t support that file type. Try again with a PNG, GIF, JPG, DOCX, PPTX, XLSX, TXT, or PDF.',
|
||||
previewTemplate: "<div class=\"dz-preview\">\n <div class=\"dz-progress\"><span class=\"dz-upload\" data-dz-uploadprogress>Uploading your files...</span></div>\n <div class=\"dz-error-message\"><span data-dz-errormessage></span></div>\n</div>",
|
||||
success: function(file, id) {
|
||||
var images = '\n![' + file.name.split('.')[0] + '](@baseUrl/@owner/@repository/_attached/' + id + ')';
|
||||
$('#@textareaId').val($('#@textareaId').val() + images);
|
||||
var attachFile = (file.type.match(/image\/.*/) ? '\n![' + file.name.split('.')[0] : '\n[' + file.name) +
|
||||
'](@baseUrl/@owner/@repository/_attached/' + id + ')';
|
||||
$('#@textareaId').val($('#@textareaId').val() + attachFile);
|
||||
$(file.previewElement).prevAll('div.dz-preview').addBack().remove();
|
||||
}
|
||||
});
|
||||
@@ -27,7 +29,10 @@ $(function(){
|
||||
}
|
||||
|
||||
// Adjust clickable area width
|
||||
$('#@textareaId').next('div.clickable').css('width', ($('#@textareaId').width() + 8) + 'px');
|
||||
$('#@textareaId').next('div.clickable').css({
|
||||
'width': ($('#@textareaId').width() + 18) + 'px',
|
||||
'font-size': '13px'
|
||||
});
|
||||
});
|
||||
</script>
|
||||
}
|
||||
|
||||
@@ -7,11 +7,10 @@
|
||||
@import gitbucket.core.view.helpers._
|
||||
@helper.html.dropdown(
|
||||
value = if(branch.length == 40) branch.substring(0, 10) else branch,
|
||||
prefix = if(branch.length == 40) "tree" else if(repository.branchList.contains(branch)) "branch" else "tree",
|
||||
mini = false
|
||||
prefix = if(branch.length == 40) "tree" else if(repository.branchList.contains(branch)) "branch" else "tree"
|
||||
) {
|
||||
<li><div id="branch-control-title">Switch branches<button id="branch-control-close" class="pull-right">×</button></div></li>
|
||||
<li><input id="branch-control-input" type="text" placeholder="Find or create branch ..."/></li>
|
||||
<li><input id="branch-control-input" type="text" class="form-control input-sm" placeholder="Find or create branch ..."/></li>
|
||||
@body
|
||||
@if(hasWritePermission) {
|
||||
<li id="create-branch" style="display: none;">
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
@if(condition){
|
||||
<i class="octicon octicon-check"></i>
|
||||
} else {
|
||||
<i class="icon-white"></i>
|
||||
<i class="octicon"></i>
|
||||
}
|
||||
@@ -6,18 +6,19 @@
|
||||
@import gitbucket.core._
|
||||
@import gitbucket.core.view.helpers._
|
||||
<div class="@if(comment.fileName.isDefined && (!latestCommitId.isDefined || latestCommitId.get == comment.commitId)){inline-comment}"
|
||||
id="discussion_r@comment.commentId"
|
||||
@if(comment.fileName.isDefined){filename="@comment.fileName.get"}
|
||||
@if(comment.newLine.isDefined){newline="@comment.newLine.get"}
|
||||
@if(comment.oldLine.isDefined){oldline="@comment.oldLine.get"}>
|
||||
<div class="issue-avatar-image">@avatar(comment.commentedUserName, 48)</div>
|
||||
<div class="box commit-comment-box commit-comment-@comment.commentId">
|
||||
<div class="box-header">
|
||||
<div class="panel- panel-default commit-comment-box commit-comment-@comment.commentId">
|
||||
<div class="panel-heading">
|
||||
@user(comment.commentedUserName, styleClass="username strong")
|
||||
<span class="muted">
|
||||
commented
|
||||
@if(comment.pullRequest){
|
||||
@if(comment.issueId.isDefined){
|
||||
on this Pull Request
|
||||
}else{
|
||||
} else {
|
||||
@if(comment.fileName.isDefined){
|
||||
on @comment.fileName.get
|
||||
}
|
||||
@@ -32,8 +33,16 @@
|
||||
}
|
||||
</span>
|
||||
</div>
|
||||
<div class="box-content-bottom commit-commentContent-@comment.commentId markdown-body">
|
||||
@markdown(comment.content, repository, false, true, true, hasWritePermission)
|
||||
<div class="panel-body issue-content commit-commentContent-@comment.commentId markdown-body">
|
||||
@markdown(
|
||||
markdown = comment.content,
|
||||
repository = repository,
|
||||
enableWikiLink = false,
|
||||
enableRefsLink = true,
|
||||
enableLineBreaks = true,
|
||||
enableTaskList = false,
|
||||
hasWritePermission = hasWritePermission
|
||||
)
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
@(id: String, value: String, prepend: Boolean = false)(html: Html)
|
||||
<div class="input-append @if(prepend){input-prepend}" style="margin-bottom: 0px;">
|
||||
<div class="input-group @if(prepend){input-prepend}" style="margin-bottom: 0px;">
|
||||
@html
|
||||
<span id="@id" class="add-on btn" data-clipboard-text="@value" data-placement="bottom" title="copy to clipboard"><i class="octicon octicon-clippy"></i></span>
|
||||
<span class="input-group-btn"><span id="@id" class="btn btn-sm btn-default" data-clipboard-text="@value" data-placement="bottom" title="copy to clipboard"><i class="octicon octicon-clippy"></i></span></span>
|
||||
</div>
|
||||
<script>
|
||||
// copy to clipboard
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
@(name: String, value: Option[java.util.Date])
|
||||
@import gitbucket.core.view.helpers
|
||||
<div id="@name" class="input-append date" data-date-format="yyyy-mm-dd" data-date="@value.map(helpers.date)">
|
||||
<input class="span2" name="@name" type="text" readonly="" value="@value.map(helpers.date)" size="16"/>
|
||||
<span class="add-on"><i class="octicon octicon-calendar"></i></span>
|
||||
<div id="@name" class="input-group date" data-date="@value.map(helpers.date)" style="width: 180px;">
|
||||
<input class="form-control" name="@name" type="text" value="@value.map(helpers.date)"/>
|
||||
<span class="input-group-addon"><i class="octicon octicon-calendar"></i></span>
|
||||
</div>
|
||||
<script>
|
||||
$(function(){
|
||||
$('#@name').datepicker();
|
||||
$('#@name').datetimepicker({
|
||||
format: 'YYYY-MM-DD'
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -45,13 +45,13 @@
|
||||
<a name="diff-@i"></a>
|
||||
<table class="table table-bordered diff-outside" commitId="@newCommitId" fileName="@diff.newPath" data-diff-id="@i">
|
||||
<tr>
|
||||
<th style="font-weight: normal; line-height: 27px;" class="box-header">
|
||||
<th style="font-weight: normal;" class="box-header">
|
||||
@if(diff.changeType == ChangeType.COPY || diff.changeType == ChangeType.RENAME){
|
||||
@if(newCommitId.isDefined){
|
||||
<div class="pull-right align-right">
|
||||
<label class="checkbox" style="display: inline-block;"><input type="checkbox" class="ignore-whitespace" value="1"/>Ignore Space</label>
|
||||
<label class="checkbox" style="display: inline-block;"><input type="checkbox" class="toggle-notes" checked><span>Show notes</span></label>
|
||||
<a href="@url(repository)/blob/@newCommitId.get/@diff.newPath" class="btn btn-small" title="View the whole file at version @newCommitId.get.substring(0, 10)" data-toggle="tooltip">View</a>
|
||||
<label class="checkbox no-margin" style="display: inline-block;"><input type="checkbox" class="ignore-whitespace" value="1"/>Ignore Space</label>
|
||||
<label class="checkbox no-margin" style="display: inline-block;"><input type="checkbox" class="toggle-notes" checked><span>Show notes</span></label>
|
||||
<a href="@url(repository)/blob/@newCommitId.get/@diff.newPath" class="btn btn-default btn-sm" title="View the whole file at version @newCommitId.get.substring(0, 10)" data-toggle="tooltip">View</a>
|
||||
</div>
|
||||
}
|
||||
<span class="diffstat">
|
||||
@@ -61,9 +61,9 @@
|
||||
@if(diff.changeType == ChangeType.ADD || diff.changeType == ChangeType.MODIFY){
|
||||
@if(newCommitId.isDefined){
|
||||
<div class="pull-right align-right">
|
||||
<label class="checkbox" style="display: inline-block;"><input type="checkbox" class="ignore-whitespace" value="1"/>Ignore Space</label>
|
||||
<label class="checkbox" style="display: inline-block;"><input type="checkbox" class="toggle-notes" checked><span>Show notes</span></label>
|
||||
<a href="@url(repository)/blob/@newCommitId.get/@diff.newPath" class="btn btn-small" title="View the whole file at version @newCommitId.get.substring(0, 10)" data-toggle="tooltip">View</a>
|
||||
<label class="checkbox no-margin" style="display: inline-block;"><input type="checkbox" class="ignore-whitespace" value="1"/>Ignore Space</label>
|
||||
<label class="checkbox no-margin" style="display: inline-block;"><input type="checkbox" class="toggle-notes" checked><span>Show notes</span></label>
|
||||
<a href="@url(repository)/blob/@newCommitId.get/@diff.newPath" class="btn btn-default btn-sm" title="View the whole file at version @newCommitId.get.substring(0, 10)" data-toggle="tooltip">View</a>
|
||||
</div>
|
||||
}
|
||||
<span class="diffstat">
|
||||
@@ -78,8 +78,8 @@
|
||||
@if(diff.changeType == ChangeType.DELETE){
|
||||
@if(oldCommitId.isDefined){
|
||||
<div class="pull-right align-right">
|
||||
<label class="checkbox" style="display: inline-block;"><input type="checkbox" class="toggle-notes" checked><span>Show notes</span></label>
|
||||
<a href="@url(repository)/blob/@oldCommitId.get/@diff.oldPath" class="btn btn-small" title="View the whole file at version @oldCommitId.get.substring(0, 10)" data-toggle="tooltip">View</a>
|
||||
<label class="checkbox no-margin" style="display: inline-block;"><input type="checkbox" class="toggle-notes" checked><span>Show notes</span></label>
|
||||
<a href="@url(repository)/blob/@oldCommitId.get/@diff.oldPath" class="btn btn-default btn-sm" title="View the whole file at version @oldCommitId.get.substring(0, 10)" data-toggle="tooltip">View</a>
|
||||
</div>
|
||||
}
|
||||
<span class="diffstat">
|
||||
@@ -92,30 +92,38 @@
|
||||
<td style="padding: 0;">
|
||||
@if(diff.oldObjectId == diff.newObjectId){
|
||||
<div class="diff-same">File renamed without changes</div>
|
||||
} else { @if(diff.newContent != None || diff.oldContent != None){
|
||||
<div id="diffText-@i" class="diffText"></div>
|
||||
<textarea id="newText-@i" style="display: none;" data-file-name="@diff.oldPath">@diff.newContent.getOrElse("")</textarea>
|
||||
<textarea id="oldText-@i" style="display: none;" data-file-name="@diff.newPath">@diff.oldContent.getOrElse("")</textarea>
|
||||
} else { @if(diff.newIsImage || diff.oldIsImage){
|
||||
<div class="diff-image-render diff2up">
|
||||
@if(oldCommitId.isDefined && diff.oldIsImage){
|
||||
<div class="diff-image-frame diff-old"><img src="@url(repository)/blob/@oldCommitId.get/@diff.oldPath?raw=true" class="diff-image" onload="onLoadedDiffImages(this)" style="display:none" /></div>
|
||||
} else {
|
||||
@if(diff.changeType != ChangeType.ADD){
|
||||
Not supported
|
||||
}
|
||||
}
|
||||
@if(newCommitId.isDefined && diff.newIsImage){
|
||||
<div class="diff-image-frame diff-new"><img src="@url(repository)/blob/@newCommitId.get/@diff.newPath?raw=true" class="diff-image" onload="onLoadedDiffImages(this)" style="display:none" /></div>
|
||||
} else {
|
||||
@if(diff.changeType != ChangeType.DELETE){
|
||||
Not supported
|
||||
}
|
||||
}
|
||||
</div>
|
||||
} else {
|
||||
Not supported
|
||||
} } }
|
||||
@if(diff.newContent != None || diff.oldContent != None){
|
||||
<div id="diffText-@i" class="diffText"></div>
|
||||
<textarea id="newText-@i" style="display: none;" data-file-name="@diff.oldPath">@diff.newContent.getOrElse("")</textarea>
|
||||
<textarea id="oldText-@i" style="display: none;" data-file-name="@diff.newPath">@diff.oldContent.getOrElse("")</textarea>
|
||||
} else {
|
||||
@if(diff.newIsImage || diff.oldIsImage){
|
||||
<div class="diff-image-render diff2up">@diff.oldIsImage @diff.newIsImage
|
||||
@if(oldCommitId.isDefined && diff.oldIsImage){
|
||||
<div class="diff-image-frame diff-old"><img src="@url(repository)/raw/@oldCommitId.get/@diff.oldPath" class="diff-image" onload="onLoadedDiffImages(this)" style="display:none" /></div>
|
||||
} else {
|
||||
@if(diff.changeType != ChangeType.ADD){
|
||||
<div style="padding: 12px;">Not supported</div>
|
||||
}
|
||||
}
|
||||
@if(newCommitId.isDefined && diff.newIsImage){
|
||||
<div class="diff-image-frame diff-new"><img src="@url(repository)/raw/@newCommitId.get/@diff.newPath" class="diff-image" onload="onLoadedDiffImages(this)" style="display:none" /></div>
|
||||
} else {
|
||||
@if(diff.changeType != ChangeType.DELETE){
|
||||
<div style="padding: 12px;">Not supported</div>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
} else {
|
||||
@if(diff.tooLarge){
|
||||
<div style="padding: 12px;">Too large</div>
|
||||
} else {
|
||||
<div style="padding: 12px;">Not supported</div>
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
@@ -212,7 +220,6 @@ $(function(){
|
||||
dataType : 'html'
|
||||
},
|
||||
function(responseContent) {
|
||||
$this.hide();
|
||||
var tmp;
|
||||
if (!isNaN(oldLineNumber) && oldLineNumber) {
|
||||
if (!isNaN(newLineNumber) && newLineNumber) {
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
@(value : String = "",
|
||||
prefix: String = "",
|
||||
mini : Boolean = true,
|
||||
style : String = "",
|
||||
right : Boolean = false,
|
||||
flat : Boolean = false)(body: Html)
|
||||
<div class="btn-group" @if(style.nonEmpty){style="@style"}>
|
||||
<button
|
||||
@if(flat){style="border: none; background-color: #eee;"}
|
||||
class="dropdown-toggle @if(!flat){btn} else {flat} @if(mini){btn-mini} else {btn-small}" data-toggle="dropdown">
|
||||
class="dropdown-toggle @if(!flat){btn btn-default} else {flat} btn-sm" data-toggle="dropdown">
|
||||
@if(value.isEmpty){
|
||||
<i class="octicon octicon-gear"></i>
|
||||
} else {
|
||||
|
||||
@@ -2,17 +2,18 @@
|
||||
groupAndPerm: Map[String, Boolean])(implicit context: gitbucket.core.controller.Context)
|
||||
@import context._
|
||||
@import gitbucket.core.view.helpers._
|
||||
<h2 class="facebox-header">Where should we fork this repository?</h2>
|
||||
<form action="@url(repository)/fork" id="fork" method="post">
|
||||
<div class="owner-select-grid">
|
||||
<div class="owner-select-target js-fork-owner-select-target enabled">@avatar(loginAccount.get.userName, 100)<span class="owner css-truncate" title="@@@loginAccount.get.userName">@@@loginAccount.get.userName</span></div>
|
||||
<h2 class="facebox-header">Where should we fork this repository?</h2>
|
||||
<form action="@url(repository)/fork" id="fork" method="post">
|
||||
<div class="owner-select-grid">
|
||||
<div class="owner-select-target js-fork-owner-select-target enabled">@avatar(loginAccount.get.userName, 100)<span class="owner css-truncate" title="@@@loginAccount.get.userName">@@@loginAccount.get.userName</span></div>
|
||||
@for((groupName, isManager) <- groupAndPerm) {
|
||||
@if(isManager) {
|
||||
<div class="owner-select-target js-fork-owner-select-target enabled">@avatar(groupName, 100)<span class="owner css-truncate" title="@@@groupName">@@@groupName</span></div>
|
||||
<div class="owner-select-target js-fork-owner-select-target enabled">@avatar(groupName, 100)<span class="owner css-truncate" title="@@@groupName">@@@groupName</span></div>
|
||||
} else {
|
||||
<div title="You don't have permission to fork here." class="owner-select-target js-fork-owner-select-target disabled">@avatar(groupName, 100)<span class="owner css-truncate" title="@@@groupName">@@@groupName</span></div>
|
||||
<div title="You don't have permission to fork here." class="owner-select-target js-fork-owner-select-target disabled">@avatar(groupName, 100)<span class="owner css-truncate" title="@@@groupName">@@@groupName</span></div>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
<input id="account" name="account" type="hidden" />
|
||||
</div>
|
||||
<input id="account" name="account" type="hidden" />
|
||||
</form>
|
||||
@@ -2,26 +2,29 @@
|
||||
content: String,
|
||||
enableWikiLink: Boolean,
|
||||
enableRefsLink: Boolean,
|
||||
enableLineBreaks: Boolean,
|
||||
enableTaskList: Boolean,
|
||||
hasWritePermission: Boolean,
|
||||
style: String = "",
|
||||
styleClass: String = "",
|
||||
placeholder: String = "Leave a comment",
|
||||
elastic: Boolean = false,
|
||||
tabIndex: Int = -2,
|
||||
uid: Long = new java.util.Date().getTime())(implicit context: gitbucket.core.controller.Context)
|
||||
@import context._
|
||||
@import gitbucket.core._
|
||||
@import gitbucket.core.view.helpers._
|
||||
<div class="tabbable">
|
||||
<ul class="nav nav-tabs" style="height: 37px;">
|
||||
<ul class="nav nav-tabs fill-width pull-left" style="@*margin-top: 12px; *@margin-bottom: 10px;">
|
||||
<li class="active"><a href="#tab@uid" data-toggle="tab">Write</a></li>
|
||||
<li><a href="#tab@(uid+1)" data-toggle="tab" id="preview@uid">Preview</a></li>
|
||||
</ul>
|
||||
<div class="tab-content">
|
||||
<div class="tab-pane active" id="tab@uid">
|
||||
<div class="tab-pane active" style="margin-top: 4px;" id="tab@uid">
|
||||
<span id="error-content" class="error"></span>
|
||||
@textarea = {
|
||||
<textarea id="content@uid" name="content" placeholder="@placeholder"
|
||||
<textarea id="content@uid" class="form-control" name="content" placeholder="@placeholder"
|
||||
@if(tabIndex > -2){ tabindex="@tabIndex"}
|
||||
@if(style.nonEmpty){ style="@style"}
|
||||
@if(styleClass.nonEmpty){ class="@styleClass" }>@content</textarea>
|
||||
}
|
||||
@@ -48,10 +51,11 @@ $(function(){
|
||||
$('#preview@uid').click(function(){
|
||||
$('#preview-area@uid').html('<img src="@assets/common/images/indicator.gif"> Previewing...');
|
||||
$.post('@url(repository)/_preview', {
|
||||
content : $('#content@uid').val(),
|
||||
enableWikiLink : @enableWikiLink,
|
||||
enableRefsLink : @enableRefsLink,
|
||||
enableTaskList : @enableTaskList
|
||||
content : $('#content@uid').val(),
|
||||
enableWikiLink : @enableWikiLink,
|
||||
enableRefsLink : @enableRefsLink,
|
||||
enableLineBreaks : @enableLineBreaks,
|
||||
enableTaskList : @enableTaskList
|
||||
}, function(data){
|
||||
$('#preview-area@uid').html(data);
|
||||
prettyPrint();
|
||||
|
||||
@@ -6,15 +6,15 @@
|
||||
@main("GitBucket"){
|
||||
@dashboard.html.tab()
|
||||
<div class="container">
|
||||
<div class="row-fluid">
|
||||
<div class="span8">
|
||||
<div class="row">
|
||||
<div class="col-md-8">
|
||||
<div class="pull-right">
|
||||
<a href="@path/activities.atom"><img src="@assets/common/images/feed.png" alt="activities"></a>
|
||||
</div>
|
||||
@helper.html.activities(activities)
|
||||
</div>
|
||||
|
||||
<div class="span4">
|
||||
<div class="col-md-4">
|
||||
@settings.information.map { information =>
|
||||
<div class="alert alert-info" style="background-color: white; color: #555; border-color: #4183c4; font-size: small; line-height: 120%;">
|
||||
<button type="button" class="close" data-dismiss="alert">×</button>
|
||||
@@ -24,55 +24,53 @@
|
||||
@if(loginAccount.isEmpty){
|
||||
@signinform(settings)
|
||||
} else {
|
||||
<div class="box-header">
|
||||
<div class="pull-right">
|
||||
<a href="@path/new" class="btn btn-success btn-mini">New repository</a>
|
||||
</div>
|
||||
<span class="strong">Your repositories</span> <span class="label">@userRepositories.size</span>
|
||||
</div>
|
||||
@if(userRepositories.isEmpty){
|
||||
<div class="box-content-bottom">
|
||||
No repositories
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading strong">
|
||||
<div class="pull-right">
|
||||
<a href="@path/new" class="btn btn-success btn-sm">New repository</a>
|
||||
</div>
|
||||
} else {
|
||||
<div class="box-content-bottom" style="padding: 0px;">
|
||||
@defining(3){ max =>
|
||||
Your repositories <span class="badge">@userRepositories.size</span>
|
||||
</div>
|
||||
<ul class="list-group list-group-flush">
|
||||
@if(userRepositories.isEmpty){
|
||||
<li class="list-group-item">No repositories</li>
|
||||
} else {
|
||||
@defining(20){ max =>
|
||||
@userRepositories.zipWithIndex.map { case (repository, i) =>
|
||||
<div class="box-content-row repo-link" style="@if(i > max - 1){display:none;}">
|
||||
<li class="list-group-item repo-link" style="@if(i > max - 1){display:none;}">
|
||||
@helper.html.repositoryicon(repository, false)
|
||||
@if(repository.owner == loginAccount.get.userName){
|
||||
<a href="@url(repository)"><span class="strong">@repository.name</span></a>
|
||||
} else {
|
||||
<a href="@url(repository)">@repository.owner/<span class="strong">@repository.name</span></a>
|
||||
}
|
||||
</div>
|
||||
</li>
|
||||
}
|
||||
@if(userRepositories.size > max){
|
||||
<div class="box-content-row show-more">
|
||||
<li class="list-group-item show-more">
|
||||
<a href="javascript:void(0);" id="show-more-repos">Show more @{userRepositories.size - max} pages...</a>
|
||||
</div>
|
||||
</li>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
}
|
||||
<div class="box-header">
|
||||
<span class="strong">Recent updated repositories</span>
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading strong">Recent updated repositories</div>
|
||||
<ul class="list-group list-group-flush">
|
||||
@if(recentRepositories.isEmpty){
|
||||
<li class="list-group-item">No repositories</li>
|
||||
} else {
|
||||
@recentRepositories.map { repository =>
|
||||
<li class="list-group-item repo-link">
|
||||
@helper.html.repositoryicon(repository, false)
|
||||
<a href="@url(repository)">@repository.owner/<span class="strong">@repository.name</span></a>
|
||||
</li>
|
||||
}
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
@if(recentRepositories.isEmpty){
|
||||
<div class="box-content-bottom">
|
||||
No repositories
|
||||
</div>
|
||||
} else {
|
||||
<div class="box-content-bottom" style="padding: 0px;">
|
||||
@recentRepositories.map { repository =>
|
||||
<div class="box-content-row repo-link">
|
||||
@helper.html.repositoryicon(repository, false)
|
||||
<a href="@url(repository)">@repository.owner/<span class="strong">@repository.name</span></a>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -80,8 +78,8 @@
|
||||
<script>
|
||||
$(function(){
|
||||
$('#show-more-repos').click(function(e){
|
||||
$(e.target).parents('div.box-content-bottom').find('div.repo-link').show();
|
||||
$(e.target).parents('div.show-more').remove();
|
||||
$(e.target).parents('ul.list-group').find('li.repo-link').show();
|
||||
$(e.target).parents('li.show-more').remove();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</script>
|
||||
|
||||
@@ -8,24 +8,26 @@
|
||||
<hr/><br/>
|
||||
<form method="POST" validate="true">
|
||||
<div class="issue-avatar-image">@avatar(loginAccount.get.userName, 48)</div>
|
||||
<div class="issue-comment-box">
|
||||
<div class="box-content">
|
||||
<div class="panel panel-default issue-comment-box">
|
||||
<div class="panel-body">
|
||||
@helper.html.preview(
|
||||
repository = repository,
|
||||
content = "",
|
||||
enableWikiLink = false,
|
||||
enableRefsLink = true,
|
||||
enableLineBreaks = true,
|
||||
enableTaskList = true,
|
||||
hasWritePermission = hasWritePermission,
|
||||
style = "",
|
||||
elastic = true
|
||||
elastic = true,
|
||||
tabIndex = 1
|
||||
)
|
||||
<div style="text-align: right;">
|
||||
<div class="text-right">
|
||||
<input type="hidden" name="issueId" value="@issue.issueId"/>
|
||||
<input type="submit" class="btn btn-success" formaction="@url(repository)/issue_comments/new" value="Comment"/>
|
||||
@if((reopenable || !issue.closed) && (hasWritePermission || issue.openedUserName == loginAccount.get.userName)){
|
||||
<input type="submit" class="btn" formaction="@url(repository)/issue_comments/state" value="@{if(issue.closed) "Reopen" else "Close"}" id="action"/>
|
||||
<input type="submit" class="btn btn-lg btn-default" tabindex="3" formaction="@url(repository)/issue_comments/state" value="@{if(issue.closed) "Reopen" else "Close"}" id="action"/>
|
||||
}
|
||||
<input type="submit" class="btn btn-lg btn-success" tabindex="2" formaction="@url(repository)/issue_comments/new" value="Comment"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
@import gitbucket.core.model.CommitComment
|
||||
@if(issue.isDefined){
|
||||
<div class="issue-avatar-image">@avatar(issue.get.openedUserName, 48)</div>
|
||||
<div class="issue-comment-box">
|
||||
<div class="box-header">
|
||||
<div class="panel panel-default issue-comment-box">
|
||||
<div class="panel-heading">
|
||||
@user(issue.get.openedUserName, styleClass="username strong") <span class="muted">commented @helper.html.datetimeago(issue.get.registeredDate)</span>
|
||||
<span class="pull-right">
|
||||
@if(hasWritePermission || loginAccount.map(_.userName == issue.get.openedUserName).getOrElse(false)){
|
||||
@@ -17,8 +17,16 @@
|
||||
}
|
||||
</span>
|
||||
</div>
|
||||
<div class="box-content-bottom issue-content markdown-body" id="issueContent">
|
||||
@markdown(issue.get.content getOrElse "No description provided.", repository, false, true, true, hasWritePermission)
|
||||
<div class="panel-body issue-content markdown-body" id="issueContent">
|
||||
@markdown(
|
||||
markdown = issue.get.content getOrElse "No description provided.",
|
||||
repository = repository,
|
||||
enableWikiLink = false,
|
||||
enableRefsLink = true,
|
||||
enableLineBreaks = true,
|
||||
enableTaskList = true,
|
||||
hasWritePermission = hasWritePermission
|
||||
)
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@@ -27,8 +35,8 @@
|
||||
case comment: gitbucket.core.model.IssueComment => {
|
||||
@if(comment.action != "close" && comment.action != "reopen" && comment.action != "delete_branch"){
|
||||
<div class="issue-avatar-image">@avatar(comment.commentedUserName, 48)</div>
|
||||
<div class="box issue-comment-box" id="comment-@comment.commentId">
|
||||
<div class="box-header">
|
||||
<div class="panel panel-default issue-comment-box" id="comment-@comment.commentId">
|
||||
<div class="panel-heading">
|
||||
@user(comment.commentedUserName, styleClass="username strong")
|
||||
<span class="muted">
|
||||
@if(comment.action == "comment"){
|
||||
@@ -38,27 +46,45 @@
|
||||
}
|
||||
@helper.html.datetimeago(comment.registeredDate)
|
||||
</span>
|
||||
<span class="pull-right">
|
||||
@if(comment.action != "commit" && comment.action != "merge" && comment.action != "refer"
|
||||
&& (hasWritePermission || loginAccount.map(_.userName == comment.commentedUserName).getOrElse(false))){
|
||||
@if(comment.action != "commit" && comment.action != "merge" && comment.action != "refer"
|
||||
&& (hasWritePermission || loginAccount.map(_.userName == comment.commentedUserName).getOrElse(false))){
|
||||
<span class="pull-right">
|
||||
<a href="#" data-comment-id="@comment.commentId"><i class="octicon octicon-pencil" aria-label="Edit"></i></a>
|
||||
<a href="#" data-comment-id="@comment.commentId"><i class="octicon octicon-x" aria-label="Remove"></i></a>
|
||||
}
|
||||
</span>
|
||||
</div>
|
||||
<div class="box-content-bottom issue-content" id="commentContent-@comment.commentId">
|
||||
</span>
|
||||
}
|
||||
@if(comment.action == "commit" && comment.content.split(" ").last.matches("[a-f0-9]{40}")){
|
||||
@defining(comment.content.substring(comment.content.length - 40)){ id =>
|
||||
<div class="pull-right"><a href="@path/@repository.owner/@repository.name/commit/@id" class="monospace">@id.substring(0, 7)</a></div>
|
||||
<div class="markdown-body">@markdown(comment.content.substring(0, comment.content.length - 41), repository, false, true, true, hasWritePermission)</div>
|
||||
<span class="pull-right"><a href="@path/@repository.owner/@repository.name/commit/@id" class="monospace">@id.substring(0, 7)</a></span>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
<div class="panel-body issue-content markdown-body" id="commentContent-@comment.commentId">
|
||||
@if(comment.action == "commit" && comment.content.split(" ").last.matches("[a-f0-9]{40}")){
|
||||
@markdown(
|
||||
markdown = comment.content.substring(0, comment.content.length - 41),
|
||||
repository = repository,
|
||||
enableWikiLink = false,
|
||||
enableRefsLink = true,
|
||||
enableLineBreaks = true,
|
||||
enableTaskList = true,
|
||||
hasWritePermission = hasWritePermission
|
||||
)
|
||||
} else {
|
||||
@if(comment.action == "refer"){
|
||||
@defining(comment.content.split(":")){ case Array(issueId, rest @ _*) =>
|
||||
<strong><a href="@path/@repository.owner/@repository.name/issues/@issueId">Issue #@issueId</a>: @rest.mkString(":")</strong>
|
||||
<strong>@issueLink(repository, issueId.toInt): @rest.mkString(":")</strong>
|
||||
}
|
||||
} else {
|
||||
<div class="markdown-body">@markdown(comment.content, repository, false, true, true, hasWritePermission)</div>
|
||||
@markdown(
|
||||
markdown = comment.content,
|
||||
repository = repository,
|
||||
enableWikiLink = false,
|
||||
enableRefsLink = true,
|
||||
enableLineBreaks = true,
|
||||
enableTaskList = true,
|
||||
hasWritePermission = hasWritePermission
|
||||
)
|
||||
}
|
||||
}
|
||||
</div>
|
||||
|
||||
@@ -7,33 +7,32 @@
|
||||
@import gitbucket.core.view.helpers._
|
||||
@html.main(s"New Issue - ${repository.owner}/${repository.name}", Some(repository)){
|
||||
@html.menu("issues", repository){
|
||||
@navigation("issues", false, repository)
|
||||
<br/><br/><hr style="margin-bottom: 10px;">
|
||||
<form action="@url(repository)/issues/new" method="POST" validate="true">
|
||||
<div class="row-fluid">
|
||||
<div class="span10">
|
||||
<form action="@url(repository)/issues/new" method="POST" validate="true" class="form-group">
|
||||
<div class="row">
|
||||
<div class="col-md-10">
|
||||
<div class="issue-avatar-image">@avatar(loginAccount.get.userName, 48)</div>
|
||||
<div class="box issue-box">
|
||||
<div class="box-content">
|
||||
<div class="panel panel-default issue-box">
|
||||
<div class="panel-body">
|
||||
<span id="error-title" class="error"></span>
|
||||
<input type="text" name="title" value="" placeholder="Title" style="width: 690px;" autofocus/>
|
||||
<input type="text" name="title" class="form-control input-lg" value="" placeholder="Title" style="width: 680px;" autofocus/>
|
||||
@helper.html.preview(
|
||||
repository = repository,
|
||||
content = "",
|
||||
enableWikiLink = false,
|
||||
enableRefsLink = true,
|
||||
enableLineBreaks = true,
|
||||
enableTaskList = true,
|
||||
hasWritePermission = hasWritePermission,
|
||||
style = "width: 690px; height: 200px; max-height: 250px;",
|
||||
style = "width: 680px; height: 200px; max-height: 250px;",
|
||||
elastic = true
|
||||
)
|
||||
<div class="align-right">
|
||||
<input type="submit" class="btn btn-success" value="Submit new issue"/>
|
||||
<input type="submit" class="btn btn-lg btn-success" value="Submit new issue"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="span2">
|
||||
<div class="col-md-2">
|
||||
@issueinfo(None, Nil, Nil, collaborators, milestones.map(x => (x, 0, 0)), labels, hasWritePermission, repository)
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
@import context._
|
||||
<span id="error-edit-content-@commentId" class="error"></span>
|
||||
@helper.html.attached(owner, repository){
|
||||
<textarea id="edit-content-@commentId">@content</textarea>
|
||||
<textarea id="edit-content-@commentId" class="form-control">@content</textarea>
|
||||
}
|
||||
<div>
|
||||
<input type="button" id="cancel-comment-@commentId" class="btn btn-small btn-danger" value="Cancel"/>
|
||||
<input type="button" id="update-comment-@commentId" class="btn btn-small pull-right" value="Update comment"/>
|
||||
<input type="button" id="cancel-comment-@commentId" class="btn btn-lg btn-danger" value="Cancel"/>
|
||||
<input type="button" id="update-comment-@commentId" class="btn btn-lg btn-default pull-right" value="Update comment"/>
|
||||
</div>
|
||||
<script>
|
||||
$(function(){
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
@(content: Option[String], issueId: Int, owner: String, repository: String)(implicit context: gitbucket.core.controller.Context)
|
||||
@import context._
|
||||
@helper.html.attached(owner, repository){
|
||||
<textarea id="edit-content">@content.getOrElse("")</textarea>
|
||||
<textarea id="edit-content" class="form-control">@content.getOrElse("")</textarea>
|
||||
}
|
||||
<div>
|
||||
<input type="button" id="cancel-issue" class="btn btn-small btn-danger" value="Cancel"/>
|
||||
<input type="button" id="update-issue" class="btn btn-small pull-right" value="Update comment"/>
|
||||
<input type="button" id="cancel-issue" class="btn btn-lg btn-danger" value="Cancel"/>
|
||||
<input type="button" id="update-issue" class="btn btn-lg btn-default pull-right" value="Update comment"/>
|
||||
</div>
|
||||
<script>
|
||||
$(function(){
|
||||
|
||||
@@ -13,12 +13,12 @@
|
||||
<div>
|
||||
<div class="show-title pull-right">
|
||||
@if(hasWritePermission || loginAccount.map(_.userName == issue.openedUserName).getOrElse(false)){
|
||||
<a class="btn" href="#" id="edit">Edit</a>
|
||||
<a class="btn btn-default" href="#" id="edit">Edit</a>
|
||||
}
|
||||
<a class="btn btn-success" href="@url(repository)/issues/new">New issue</a>
|
||||
</div>
|
||||
<div class="edit-title pull-right" style="display: none;">
|
||||
<a class="btn" href="#" id="update">Save</a> <a href="#" id="cancel">Cancel</a>
|
||||
<a class="btn btn-default" href="#" id="update">Save</a> <a href="#" id="cancel">Cancel</a>
|
||||
</div>
|
||||
<h1>
|
||||
<span class="show-title">
|
||||
@@ -27,31 +27,31 @@
|
||||
</span>
|
||||
<span class="edit-title" style="display: none;">
|
||||
<span id="error-edit-title" class="error"></span>
|
||||
<input type="text" style="width: 700px;" id="edit-title" value="@issue.title"/>
|
||||
<input type="text" class="form-control" style="width: 700px;" id="edit-title" value="@issue.title"/>
|
||||
</span>
|
||||
</h1>
|
||||
</div>
|
||||
@if(issue.closed) {
|
||||
<span class="label label-important issue-status">Closed</span>
|
||||
} else {
|
||||
<span class="label label-success issue-status">Open</span>
|
||||
}
|
||||
<span class="muted">
|
||||
@user(issue.openedUserName, styleClass="username strong") opened this issue @helper.html.datetimeago(issue.registeredDate) - @defining(
|
||||
comments.count( _.action.contains("comment") )
|
||||
){ count =>
|
||||
@count @plural(count, "comment")
|
||||
<div style="margin-bottom: 15px">
|
||||
@if(issue.closed) {
|
||||
<span class="label label-important issue-status">Closed</span>
|
||||
} else {
|
||||
<span class="label label-success issue-status">Open</span>
|
||||
}
|
||||
</span>
|
||||
<br/><br/>
|
||||
<span class="muted">
|
||||
@user(issue.openedUserName, styleClass="username strong") opened this issue @helper.html.datetimeago(issue.registeredDate) - @defining(
|
||||
comments.count( _.action.contains("comment") )
|
||||
){ count =>
|
||||
@count @plural(count, "comment")
|
||||
}
|
||||
</span>
|
||||
</div>
|
||||
<hr>
|
||||
<br/>
|
||||
<div class="row-fluid">
|
||||
<div class="span10">
|
||||
<div class="row" style="margin-top: 15px;">
|
||||
<div class="col-md-10">
|
||||
@commentlist(Some(issue), comments, hasWritePermission, repository)
|
||||
@commentform(issue, true, hasWritePermission, repository)
|
||||
</div>
|
||||
<div class="span2">
|
||||
<div class="col-md-2">
|
||||
@issueinfo(Some(issue), comments, issueLabels, collaborators, milestones, labels, hasWritePermission, repository)
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -93,7 +93,7 @@
|
||||
@collaborators.map { collaborator =>
|
||||
<li>
|
||||
<a href="javascript:void(0);" class="assign" data-name="@collaborator">
|
||||
@helper.html.checkicon(issue.exists(_.assignedUserName == collaborator))@avatar(collaborator, 20) @collaborator
|
||||
@helper.html.checkicon(issue.exists(_.assignedUserName.exists(_ == collaborator)))@avatar(collaborator, 20) @collaborator
|
||||
</a>
|
||||
</li>
|
||||
}
|
||||
@@ -116,11 +116,13 @@
|
||||
<div style="margin-bottom: 8px;">
|
||||
@defining((issue.openedUserName :: comments.map(_.commentedUserName)).distinct){ participants =>
|
||||
<div class="muted small strong">@participants.size @plural(participants.size, "participant")</div>
|
||||
@participants.map { participant =>
|
||||
@avatarLink(participant, 20, tooltip = true)
|
||||
}
|
||||
}
|
||||
</div>
|
||||
@defining((issue.openedUserName :: comments.map(_.commentedUserName)).distinct){ participants =>
|
||||
@participants.map { participant =>
|
||||
@avatarLink(participant, 20, tooltip = true)
|
||||
}
|
||||
}
|
||||
}
|
||||
<script>
|
||||
$(function(){
|
||||
@@ -162,7 +164,7 @@ $(function(){
|
||||
switchLabel($(this));
|
||||
var labelNames = Array();
|
||||
$('a.toggle-label').each(function(i, e){
|
||||
if($(e).children('i').hasClass('icon-ok') == true){
|
||||
if($(e).children('i').hasClass('octicon-check') == true){
|
||||
labelNames.push($(e).text().trim());
|
||||
}
|
||||
});
|
||||
@@ -193,17 +195,17 @@ $(function(){
|
||||
|
||||
function switchLabel($this){
|
||||
var i = $this.children('i');
|
||||
if(i.hasClass('icon-ok')){
|
||||
i.removeClass().addClass('icon-white');
|
||||
if(i.hasClass('octicon-check')){
|
||||
i.removeClass('octicon-check');
|
||||
return 'delete';
|
||||
} else {
|
||||
i.removeClass().addClass('icon-ok');
|
||||
i.addClass('octicon-check');
|
||||
return 'new';
|
||||
}
|
||||
}
|
||||
|
||||
function displayMilestone(title, milestoneId, progress){
|
||||
$('a.milestone i.icon-ok').attr('class', 'icon-white');
|
||||
$('a.milestone i.octicon-check').removeClass('octicon-check');
|
||||
if(milestoneId == ''){
|
||||
$('#label-milestone').html($('<span class="muted small">').text('No milestone'));
|
||||
$('#milestone-progress-area').empty();
|
||||
@@ -212,19 +214,19 @@ $(function(){
|
||||
if(progress){
|
||||
$('#milestone-progress-area').html(progress);
|
||||
}
|
||||
$('a.milestone[data-id=' + milestoneId + '] i').attr('class', 'icon-ok');
|
||||
$('a.milestone[data-id=' + milestoneId + '] i').addClass('octicon-check');
|
||||
}
|
||||
}
|
||||
|
||||
function displayAssignee($this, userName){
|
||||
$('a.assign i.icon-ok').attr('class', 'icon-white');
|
||||
$('a.assign i.octicon-check').removeClass('octicon-check');
|
||||
if(userName == ''){
|
||||
$('#label-assigned').html($('<span class="muted small">').text('No one'));
|
||||
} else {
|
||||
$('#label-assigned').empty()
|
||||
.append($this.find('img.avatar-mini').clone(false)).append(' ')
|
||||
.append($('<a class="username strong small">').attr('href', '@context.path/' + userName).text(userName));
|
||||
$('a.assign[data-name=' + jqSelectorEscape(userName) + '] i').attr('class', 'icon-ok');
|
||||
$('a.assign[data-name=' + jqSelectorEscape(userName) + '] i').addClass('octicon-check');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -4,18 +4,18 @@
|
||||
@import gitbucket.core.view.helpers._
|
||||
@defining(label.map(_.labelId).getOrElse("new")){ labelId =>
|
||||
<div id="edit-label-area-@labelId">
|
||||
<form style="margin-bottom: 0px;">
|
||||
<input type="text" id="labelName-@labelId" style="width: 300px; margin-bottom: 0px;" value="@label.map(_.labelName)"@if(labelId == "new"){ placeholder="New label name"}/>
|
||||
<div id="label-color-@labelId" class="input-append color bscp" data-color="#@label.map(_.color).getOrElse("888888")" data-color-format="hex" style="width: 100px; margin-bottom: 0px;">
|
||||
<input type="text" class="span3" id="labelColor-@labelId" value="#@label.map(_.color).getOrElse("888888")" readonly style="width: 100px;">
|
||||
<span class="add-on"><i style="background-color: #@label.map(_.color).getOrElse("888888");"></i></span>
|
||||
<form class="form-inline">
|
||||
<input type="text" id="labelName-@labelId" style="width: 300px; float: left; margin-right: 4px;" class="form-control" value="@label.map(_.labelName)"@if(labelId == "new"){ placeholder="New label name"}/>
|
||||
<div id="label-color-@labelId" class="input-group color bscp" data-color="#@label.map(_.color).getOrElse("888888")" data-color-format="hex" style="width: 100px; float: left;">
|
||||
<input type="text" class="form-control" id="labelColor-@labelId" value="#@label.map(_.color).getOrElse("888888")" readonly style="width: 100px;">
|
||||
<span class="input-group-addon"><i style="background-color: #@label.map(_.color).getOrElse("888888");"></i></span>
|
||||
</div>
|
||||
<script>
|
||||
$('div#label-color-@labelId').colorpicker();
|
||||
</script>
|
||||
<span class="pull-right">
|
||||
<span id="label-error-@labelId" class="error"></span>
|
||||
<input type="button" id="cancel-@labelId" class="btn label-edit-cancel" value="Cancel">
|
||||
<input type="button" id="cancel-@labelId" class="btn btn-default label-edit-cancel" value="Cancel">
|
||||
<input type="button" id="submit-@labelId" class="btn btn-success" style="margin-bottom: 0px;" value="@(if(labelId == "new") "Create label" else "Save changes")"/>
|
||||
</span>
|
||||
</form>
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
@import gitbucket.core.view.helpers._
|
||||
<tr id="label-row-@label.labelId">
|
||||
<td style="padding-top: 15px; padding-bottom: 15px;">
|
||||
<div class="milestone row-fluid" id="label-@label.labelId">
|
||||
<div class="span8">
|
||||
<div class="milestone row" id="label-@label.labelId">
|
||||
<div class="col-md-8">
|
||||
<div style="margin-top: 6px">
|
||||
<a href="@url(repository)/issues?labels=@urlEncode(label.labelName)" id="label-row-content-@label.labelId">
|
||||
<span style="background-color: #@label.color; color: #@label.fontColor; padding: 8px; font-size: 120%; border-radius: 4px;">
|
||||
@@ -17,13 +17,13 @@
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="@if(hasWritePermission){span2} else {span4}">
|
||||
<div class="@if(hasWritePermission){col-md-2} else {col-md-4}">
|
||||
<div class="pull-right">
|
||||
<span class="muted">@counts.get(label.labelName).getOrElse(0) open issues</span>
|
||||
</div>
|
||||
</div>
|
||||
@if(hasWritePermission){
|
||||
<div class="span2">
|
||||
<div class="col-md-2">
|
||||
<div class="pull-right">
|
||||
<a href="javascript:void(0);" onclick="editLabel(@label.labelId)">Edit</a>
|
||||
|
||||
|
||||
@@ -12,24 +12,28 @@
|
||||
<tr><td></td></tr>
|
||||
</table>
|
||||
<table class="table table-bordered table-hover table-issues">
|
||||
<tr id="label-row-header">
|
||||
<th style="background-color: #eee;">
|
||||
<span class="small">@labels.size labels</span>
|
||||
</th>
|
||||
</tr>
|
||||
@labels.map { label =>
|
||||
@gitbucket.core.issues.labels.html.label(label, counts, repository, hasWritePermission)
|
||||
}
|
||||
@if(labels.isEmpty){
|
||||
<tr>
|
||||
<td style="padding: 20px; background-color: #eee; text-align: center;">
|
||||
No labels to show.
|
||||
@if(hasWritePermission){
|
||||
<a href="@url(repository)/issues/labels/new">Create a new label.</a>
|
||||
}
|
||||
</td>
|
||||
<thead>
|
||||
<tr id="label-row-header">
|
||||
<th style="background-color: #eee;">
|
||||
<span class="small">@labels.size labels</span>
|
||||
</th>
|
||||
</tr>
|
||||
}
|
||||
</thead>
|
||||
<tbody>
|
||||
@labels.map { label =>
|
||||
@gitbucket.core.issues.labels.html.label(label, counts, repository, hasWritePermission)
|
||||
}
|
||||
@if(labels.isEmpty){
|
||||
<tr>
|
||||
<td style="padding: 20px; background-color: #eee; text-align: center;">
|
||||
No labels to show.
|
||||
@if(hasWritePermission){
|
||||
<a href="@url(repository)/issues/labels/new">Create a new label.</a>
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,9 +12,8 @@
|
||||
@import context._
|
||||
@import gitbucket.core.view.helpers._
|
||||
@import gitbucket.core.service.IssuesService.IssueInfo
|
||||
<br>
|
||||
@if(condition.nonEmpty){
|
||||
<div>
|
||||
<div style="width: 100%; display: inline-block;">
|
||||
<a href="@gitbucket.core.service.IssuesService.IssueSearchCondition().toURL" class="header-link">
|
||||
<i class="octicon octicon-x" ></i>
|
||||
<span class="strong">Clear current search query, filters, and sorts</span>
|
||||
@@ -22,196 +21,199 @@
|
||||
</div>
|
||||
}
|
||||
<table class="table table-bordered table-hover table-issues">
|
||||
<tr>
|
||||
<th style="background-color: #eee;">
|
||||
<input type="checkbox"/>
|
||||
<span class="small">
|
||||
<a class="button-link@if(condition.state == "open"){ selected}" href="@condition.copy(state = "open").toURL">
|
||||
<i class="octicon octicon-issue-opened @(if(condition.state == "open"){"active"})"></i>
|
||||
@openCount Open
|
||||
</a>
|
||||
<a class="button-link@if(condition.state == "closed"){ selected}" href="@condition.copy(state = "closed").toURL">
|
||||
<i class="octicon octicon-check @(if(condition.state == "closed"){"active"})"></i>
|
||||
@closedCount Closed
|
||||
</a>
|
||||
</span>
|
||||
<div class="pull-right" id="table-issues-control">
|
||||
@helper.html.dropdown("Author", flat = true) {
|
||||
@collaborators.map { collaborator =>
|
||||
<li>
|
||||
<a href="@condition.copy(author = (if(condition.author == Some(collaborator)) None else Some(collaborator))).toURL">
|
||||
@helper.html.checkicon(condition.author == Some(collaborator))
|
||||
@avatar(collaborator, 20) @collaborator
|
||||
</a>
|
||||
</li>
|
||||
}
|
||||
}
|
||||
@helper.html.dropdown("Label", flat = true) {
|
||||
@labels.map { label =>
|
||||
<li>
|
||||
<a href="@condition.copy(labels = (if(condition.labels.contains(label.labelName)) condition.labels - label.labelName else condition.labels + label.labelName)).toURL">
|
||||
@helper.html.checkicon(condition.labels.contains(label.labelName))
|
||||
<span style="background-color: #@label.color;" class="label-color"> </span>
|
||||
@label.labelName
|
||||
</a>
|
||||
</li>
|
||||
}
|
||||
}
|
||||
@helper.html.dropdown("Milestone", flat = true) {
|
||||
<li>
|
||||
<a href="@condition.copy(milestone = Some(None)).toURL">
|
||||
@helper.html.checkicon(condition.milestone == Some(None)) Issues with no milestone
|
||||
</a>
|
||||
</li>
|
||||
@milestones.filter(_.closedDate.isEmpty).map { milestone =>
|
||||
<li>
|
||||
<a href="@condition.copy(milestone = Some(Some(milestone.title))).toURL">
|
||||
@helper.html.checkicon(condition.milestone == Some(Some(milestone.title))) @milestone.title
|
||||
</a>
|
||||
</li>
|
||||
}
|
||||
}
|
||||
@helper.html.dropdown("Assignee", flat = true) {
|
||||
@collaborators.map { collaborator =>
|
||||
<li>
|
||||
<a href="@condition.copy(assigned = Some(collaborator)).toURL">
|
||||
@helper.html.checkicon(condition.assigned == Some(collaborator))
|
||||
@avatar(collaborator, 20) @collaborator
|
||||
</a>
|
||||
</li>
|
||||
}
|
||||
}
|
||||
@helper.html.dropdown("Sort", flat = true){
|
||||
<li>
|
||||
<a href="@condition.copy(sort="created", direction="desc").toURL">
|
||||
@helper.html.checkicon(condition.sort == "created" && condition.direction == "desc") Newest
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="@condition.copy(sort="created", direction="asc" ).toURL">
|
||||
@helper.html.checkicon(condition.sort == "created" && condition.direction == "asc") Oldest
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="@condition.copy(sort="comments", direction="desc").toURL">
|
||||
@helper.html.checkicon(condition.sort == "comments" && condition.direction == "desc") Most commented
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="@condition.copy(sort="comments", direction="asc" ).toURL">
|
||||
@helper.html.checkicon(condition.sort == "comments" && condition.direction == "asc") Least commented
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="@condition.copy(sort="updated", direction="desc").toURL">
|
||||
@helper.html.checkicon(condition.sort == "updated" && condition.direction == "desc") Recently updated
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="@condition.copy(sort="updated", direction="asc" ).toURL">
|
||||
@helper.html.checkicon(condition.sort == "updated" && condition.direction == "asc") Least recently updated
|
||||
</a>
|
||||
</li>
|
||||
}
|
||||
</div>
|
||||
@if(hasWritePermission){
|
||||
<div class="pull-right" id="table-issues-batchedit">
|
||||
@helper.html.dropdown("Mark as", flat = true) {
|
||||
<li><a href="javascript:void(0);" class="toggle-state" data-id="open">Open</a></li>
|
||||
<li><a href="javascript:void(0);" class="toggle-state" data-id="close">Close</a></li>
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="background-color: #eee;">
|
||||
<input type="checkbox"/>
|
||||
<span class="small">
|
||||
<a class="button-link@if(condition.state == "open"){ selected}" href="@condition.copy(state = "open").toURL">
|
||||
<i class="octicon octicon-issue-opened @(if(condition.state == "open"){"active"})"></i>
|
||||
@openCount Open
|
||||
</a>
|
||||
<a class="button-link@if(condition.state == "closed"){ selected}" href="@condition.copy(state = "closed").toURL">
|
||||
<i class="octicon octicon-check @(if(condition.state == "closed"){"active"})"></i>
|
||||
@closedCount Closed
|
||||
</a>
|
||||
</span>
|
||||
<div class="pull-right" id="table-issues-control">
|
||||
@helper.html.dropdown("Author", flat = true) {
|
||||
@collaborators.map { collaborator =>
|
||||
<li>
|
||||
<a href="@condition.copy(author = (if(condition.author == Some(collaborator)) None else Some(collaborator))).toURL">
|
||||
@helper.html.checkicon(condition.author == Some(collaborator))
|
||||
@avatar(collaborator, 20) @collaborator
|
||||
</a>
|
||||
</li>
|
||||
}
|
||||
}
|
||||
@helper.html.dropdown("Label", flat = true) {
|
||||
@labels.map { label =>
|
||||
<li>
|
||||
<a href="javascript:void(0);" class="toggle-label" data-id="@label.labelId">
|
||||
<i class="icon-white"></i>
|
||||
<span class="label" style="background-color: #@label.color;"> </span>
|
||||
<a href="@condition.copy(labels = (if(condition.labels.contains(label.labelName)) condition.labels - label.labelName else condition.labels + label.labelName)).toURL">
|
||||
@helper.html.checkicon(condition.labels.contains(label.labelName))
|
||||
<span style="background-color: #@label.color;" class="label-color"> </span>
|
||||
@label.labelName
|
||||
</a>
|
||||
</li>
|
||||
}
|
||||
}
|
||||
@helper.html.dropdown("Milestone", flat = true) {
|
||||
<li><a href="javascript:void(0);" class="toggle-milestone" data-id="">No milestone</a></li>
|
||||
<li>
|
||||
<a href="@condition.copy(milestone = Some(None)).toURL">
|
||||
@helper.html.checkicon(condition.milestone == Some(None)) Issues with no milestone
|
||||
</a>
|
||||
</li>
|
||||
@milestones.filter(_.closedDate.isEmpty).map { milestone =>
|
||||
<li><a href="javascript:void(0);" class="toggle-milestone" data-id="@milestone.milestoneId">@milestone.title</a></li>
|
||||
<li>
|
||||
<a href="@condition.copy(milestone = Some(Some(milestone.title))).toURL">
|
||||
@helper.html.checkicon(condition.milestone == Some(Some(milestone.title))) @milestone.title
|
||||
</a>
|
||||
</li>
|
||||
}
|
||||
}
|
||||
@helper.html.dropdown("Assignee", flat = true) {
|
||||
<li><a href="javascript:void(0);" class="toggle-assign" data-name=""><i class="octicon octicon-x"></i> Clear assignee</a></li>
|
||||
@collaborators.map { collaborator =>
|
||||
<li><a href="javascript:void(0);" class="toggle-assign" data-name="@collaborator"><i class="icon-white"></i>@avatar(collaborator, 20) @collaborator</a></li>
|
||||
<li>
|
||||
<a href="@condition.copy(assigned = Some(collaborator)).toURL">
|
||||
@helper.html.checkicon(condition.assigned == Some(collaborator))
|
||||
@avatar(collaborator, 20) @collaborator
|
||||
</a>
|
||||
</li>
|
||||
}
|
||||
}
|
||||
@helper.html.dropdown("Sort", flat = true){
|
||||
<li>
|
||||
<a href="@condition.copy(sort="created", direction="desc").toURL">
|
||||
@helper.html.checkicon(condition.sort == "created" && condition.direction == "desc") Newest
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="@condition.copy(sort="created", direction="asc" ).toURL">
|
||||
@helper.html.checkicon(condition.sort == "created" && condition.direction == "asc") Oldest
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="@condition.copy(sort="comments", direction="desc").toURL">
|
||||
@helper.html.checkicon(condition.sort == "comments" && condition.direction == "desc") Most commented
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="@condition.copy(sort="comments", direction="asc" ).toURL">
|
||||
@helper.html.checkicon(condition.sort == "comments" && condition.direction == "asc") Least commented
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="@condition.copy(sort="updated", direction="desc").toURL">
|
||||
@helper.html.checkicon(condition.sort == "updated" && condition.direction == "desc") Recently updated
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="@condition.copy(sort="updated", direction="asc" ).toURL">
|
||||
@helper.html.checkicon(condition.sort == "updated" && condition.direction == "asc") Least recently updated
|
||||
</a>
|
||||
</li>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</th>
|
||||
</tr>
|
||||
@if(issues.isEmpty){
|
||||
<tr>
|
||||
<td style="padding: 20px; background-color: #eee; text-align: center;">
|
||||
@if(target == "issues"){
|
||||
No issues to show.
|
||||
} else {
|
||||
No pull requests to show.
|
||||
}
|
||||
@if(condition.labels.nonEmpty || condition.milestone.isDefined){
|
||||
<a href="@condition.copy(labels = Set.empty, milestone = None).toURL">Clear active filters.</a>
|
||||
} else {
|
||||
@if(repository.isDefined){
|
||||
@if(target == "issues"){
|
||||
<a href="@url(repository.get)/issues/new">Create a new issue.</a>
|
||||
} else {
|
||||
<a href="@url(repository.get)/compare">Create a new pull request.</a>
|
||||
}
|
||||
}
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
@issues.map { case IssueInfo(issue, labels, milestone, commentCount, commitStatus) =>
|
||||
<tr>
|
||||
<td style="padding-top: 15px; padding-bottom: 15px;">
|
||||
@if(hasWritePermission){
|
||||
<input type="checkbox" value="@issue.issueId"/>
|
||||
<div class="pull-right" id="table-issues-batchedit">
|
||||
@helper.html.dropdown("Mark as", flat = true) {
|
||||
<li><a href="javascript:void(0);" class="toggle-state" data-id="open">Open</a></li>
|
||||
<li><a href="javascript:void(0);" class="toggle-state" data-id="close">Close</a></li>
|
||||
}
|
||||
@helper.html.dropdown("Label", flat = true) {
|
||||
@labels.map { label =>
|
||||
<li>
|
||||
<a href="javascript:void(0);" class="toggle-label" data-id="@label.labelId">
|
||||
<i class="octicon"></i>
|
||||
<span class="label" style="background-color: #@label.color;"> </span>
|
||||
@label.labelName
|
||||
</a>
|
||||
</li>
|
||||
}
|
||||
}
|
||||
@helper.html.dropdown("Milestone", flat = true) {
|
||||
<li><a href="javascript:void(0);" class="toggle-milestone" data-id="">No milestone</a></li>
|
||||
@milestones.filter(_.closedDate.isEmpty).map { milestone =>
|
||||
<li><a href="javascript:void(0);" class="toggle-milestone" data-id="@milestone.milestoneId">@milestone.title</a></li>
|
||||
}
|
||||
}
|
||||
@helper.html.dropdown("Assignee", flat = true) {
|
||||
<li><a href="javascript:void(0);" class="toggle-assign" data-name=""><i class="octicon octicon-x"></i> Clear assignee</a></li>
|
||||
@collaborators.map { collaborator =>
|
||||
<li><a href="javascript:void(0);" class="toggle-assign" data-name="@collaborator"><i class="octicon"></i>@avatar(collaborator, 20) @collaborator</a></li>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
}
|
||||
<i class="octicon octicon-issue-@(if(issue.closed) "closed" else "opened")" style="margin-right: 3px;"></i>
|
||||
@if(repository.isEmpty){
|
||||
<a href="@path/@issue.userName/@issue.repositoryName">@issue.repositoryName</a> ・
|
||||
}
|
||||
@if(target == "issues"){
|
||||
<a href="@path/@issue.userName/@issue.repositoryName/issues/@issue.issueId" class="issue-title">@issue.title</a>
|
||||
} else {
|
||||
<a href="@path/@issue.userName/@issue.repositoryName/pull/@issue.issueId" class="issue-title">@issue.title</a>
|
||||
}
|
||||
@commitstatus(issue, commitStatus)
|
||||
@labels.map { label =>
|
||||
<span class="label-color small" style="background-color: #@label.color; color: #@label.fontColor; padding-left: 4px; padding-right: 4px">@label.labelName</span>
|
||||
}
|
||||
<span class="pull-right small">
|
||||
@issue.assignedUserName.map { userName =>
|
||||
@avatar(userName, 20, tooltip = true)
|
||||
}
|
||||
@if(commentCount > 0){
|
||||
<a href="@path/@issue.userName/@issue.repositoryName/issues/@issue.issueId" class="issue-comment-count">
|
||||
<i class="octicon octicon-comment active"></i> @commentCount
|
||||
</a>
|
||||
} else {
|
||||
<a href="@path/@issue.userName/@issue.repositoryName/issues/@issue.issueId" class="issue-comment-count" style="color: silver;">
|
||||
<i class="octicon octicon-comment"></i> @commentCount
|
||||
</a>
|
||||
}
|
||||
</span>
|
||||
<div class="small muted" style="margin-left: 40px; margin-top: 5px;">
|
||||
#@issue.issueId opened @helper.html.datetimeago(issue.registeredDate) by @user(issue.openedUserName, styleClass="username")
|
||||
@milestone.map { milestone =>
|
||||
<span style="margin: 20px;"><a href="@condition.copy(milestone = Some(Some(milestone))).toURL" class="username"><i class="octicon octicon-milestone"></i>
|
||||
@milestone</a></span>
|
||||
}
|
||||
</div>
|
||||
</td>
|
||||
</th>
|
||||
</tr>
|
||||
}
|
||||
</thead>
|
||||
<tbody>
|
||||
@if(issues.isEmpty){
|
||||
<tr>
|
||||
<td style="padding: 20px; background-color: #eee; text-align: center;">
|
||||
@if(target == "issues"){
|
||||
No issues to show.
|
||||
} else {
|
||||
No pull requests to show.
|
||||
}
|
||||
@if(condition.labels.nonEmpty || condition.milestone.isDefined){
|
||||
<a href="@condition.copy(labels = Set.empty, milestone = None).toURL">Clear active filters.</a>
|
||||
} else {
|
||||
@if(repository.isDefined){
|
||||
@if(target == "issues"){
|
||||
<a href="@url(repository.get)/issues/new">Create a new issue.</a>
|
||||
} else {
|
||||
<a href="@url(repository.get)/compare">Create a new pull request.</a>
|
||||
}
|
||||
}
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
@issues.map { case IssueInfo(issue, labels, milestone, commentCount, commitStatus) =>
|
||||
<tr>
|
||||
<td style="padding-top: 12px; padding-bottom: 12px;">
|
||||
@if(hasWritePermission){
|
||||
<input type="checkbox" value="@issue.issueId"/>
|
||||
}
|
||||
<i class="octicon octicon-issue-@(if(issue.closed) "closed" else "opened")" style="margin-right: 3px;"></i>
|
||||
@if(repository.isEmpty){
|
||||
<a href="@path/@issue.userName/@issue.repositoryName">@issue.repositoryName</a> ・
|
||||
}
|
||||
@if(target == "issues"){
|
||||
<a href="@path/@issue.userName/@issue.repositoryName/issues/@issue.issueId" class="issue-title">@issue.title</a>
|
||||
} else {
|
||||
<a href="@path/@issue.userName/@issue.repositoryName/pull/@issue.issueId" class="issue-title">@issue.title</a>
|
||||
}
|
||||
@commitstatus(issue, commitStatus)
|
||||
@labels.map { label =>
|
||||
<span class="label-color small" style="background-color: #@label.color; color: #@label.fontColor; padding-left: 4px; padding-right: 4px">@label.labelName</span>
|
||||
}
|
||||
<span class="pull-right small">
|
||||
@issue.assignedUserName.map { userName =>
|
||||
@avatar(userName, 20, tooltip = true)
|
||||
}
|
||||
@if(commentCount > 0){
|
||||
<a href="@path/@issue.userName/@issue.repositoryName/issues/@issue.issueId" class="issue-comment-count">
|
||||
<i class="octicon octicon-comment active"></i> @commentCount
|
||||
</a>
|
||||
} else {
|
||||
<a href="@path/@issue.userName/@issue.repositoryName/issues/@issue.issueId" class="issue-comment-count" style="color: silver;">
|
||||
<i class="octicon octicon-comment"></i> @commentCount
|
||||
</a>
|
||||
}
|
||||
</span>
|
||||
<div class="small muted" style="margin-left: 40px; margin-top: 2px;">
|
||||
#@issue.issueId opened @helper.html.datetimeago(issue.registeredDate) by @user(issue.openedUserName, styleClass="username")
|
||||
@milestone.map { milestone =>
|
||||
<span style="margin: 20px;"><a href="@condition.copy(milestone = Some(Some(milestone))).toURL" class="username"><i class="octicon octicon-milestone"></i> @milestone</a></span>
|
||||
}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="pull-right">
|
||||
@helper.html.paginator(page, (if(condition.state == "open") openCount else closedCount), gitbucket.core.service.IssuesService.IssueLimit, 10, condition.toURL)
|
||||
|
||||
@@ -13,16 +13,16 @@
|
||||
}
|
||||
<hr style="margin-top: 12px; margin-bottom: 18px;" class="fill-width"/>
|
||||
<form method="POST" action="@url(repository)/issues/milestones/@if(milestone.isEmpty){new}else{@milestone.get.milestoneId/edit}" validate="true">
|
||||
<fieldset>
|
||||
<input type="text" id="title" name="title" style="width: 500px;" value="@milestone.map(_.title)" placeholder="Title"/>
|
||||
<fieldset class="form-group">
|
||||
<input type="text" id="title" name="title" class="form-control" style="width: 500px;" value="@milestone.map(_.title)" placeholder="Title"/>
|
||||
<span id="error-title" class="error"></span>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<fieldset class="form-group">
|
||||
<label for="description" class="strong">Description</label>
|
||||
<textarea id="description" name="description" style="width: 500px; height: 150px;">@milestone.map(_.description)</textarea>
|
||||
<textarea id="description" name="description" class="form-control" style="width: 500px; height: 150px;">@milestone.map(_.description)</textarea>
|
||||
<span id="error-description" class="error"></span>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<fieldset class="form-group">
|
||||
<label for="dueDate" class="strong">Due Date</label>
|
||||
@helper.html.datepicker("dueDate", milestone.flatMap(_.dueDate))
|
||||
<span id="error-dueDate" class="error"></span>
|
||||
@@ -30,16 +30,16 @@
|
||||
<hr>
|
||||
<div class="pull-right">
|
||||
@if(milestone.isEmpty){
|
||||
<input type="submit" class="btn" value="Create milestone"/>
|
||||
<input type="submit" class="btn btn-default" value="Create milestone"/>
|
||||
} else {
|
||||
@if(milestone.get.closedDate.isDefined){
|
||||
<input type="button" class="btn" value="Open" id="open"
|
||||
<input type="button" class="btn btn-default" value="Open" id="open"
|
||||
onclick="location.href='@url(repository)/issues/milestones/@milestone.get.milestoneId/close';"/>
|
||||
} else {
|
||||
<input type="button" class="btn" value="Close" id="close"
|
||||
<input type="button" class="btn btn-default" value="Close" id="close"
|
||||
onclick="location.href='@url(repository)/issues/milestones/@milestone.get.milestoneId/open';"/>
|
||||
}
|
||||
<input type="submit" class="btn" value="Update milestone"/>
|
||||
<input type="submit" class="btn btn-default" value="Update milestone"/>
|
||||
}
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -9,89 +9,99 @@
|
||||
@issues.html.navigation("milestones", hasWritePermission, repository)
|
||||
<br>
|
||||
<table class="table table-bordered table-hover table-issues">
|
||||
<tr>
|
||||
<th style="background-color: #eee;">
|
||||
<span class="small">
|
||||
<a class="button-link@if(state == "open"){ selected}" href="?state=open">
|
||||
<i class="octicon octicon-milestone @(if(state == "open"){"active"})"></i>
|
||||
@milestones.count(_._1.closedDate.isEmpty) Open
|
||||
</a>
|
||||
<a class="button-link@if(state == "closed"){ selected}" href="?state=closed">
|
||||
<i class="octicon octicon-milestone @(if(state == "closed"){"active"})"></i>
|
||||
@milestones.count(_._1.closedDate.isDefined) Closed
|
||||
</a>
|
||||
</span>
|
||||
</th>
|
||||
</tr>
|
||||
@defining(milestones.filter { case (milestone, _, _) =>
|
||||
milestone.closedDate.map(_ => state == "closed").getOrElse(state == "open")
|
||||
}){ milestones =>
|
||||
@milestones.map { case (milestone, openCount, closedCount) =>
|
||||
<thead>
|
||||
<tr>
|
||||
<td style="padding-top: 15px; padding-bottom: 15px;">
|
||||
<div class="milestone row-fluid">
|
||||
<div class="span4">
|
||||
<a href="@url(repository)/issues?milestone=@milestone.title&state=open" class="milestone-title">@milestone.title</a>
|
||||
<div style="margin-top: 6px">
|
||||
@if(milestone.closedDate.isDefined){
|
||||
<span class="muted">Closed @helper.html.datetimeago(milestone.closedDate.get)</span>
|
||||
} else {
|
||||
@milestone.dueDate.map { dueDate =>
|
||||
@if(isPast(dueDate)){
|
||||
<i class="octicon octicon-alert" style="color:#BD2C00;"></i><span class="muted milestone-alert">Due by @date(dueDate)</span>
|
||||
} else {
|
||||
<span class="muted">Due by @date(dueDate)</span>
|
||||
}
|
||||
}.getOrElse {
|
||||
<span class="muted">No due date</span>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="span8">
|
||||
@progress(openCount + closedCount, closedCount)
|
||||
<div>
|
||||
<th style="background-color: #eee;">
|
||||
<span class="small">
|
||||
<a class="button-link@if(state == "open"){ selected}" href="?state=open">
|
||||
<i class="octicon octicon-milestone @(if(state == "open"){"active"})"></i>
|
||||
@milestones.count(_._1.closedDate.isEmpty) Open
|
||||
</a>
|
||||
<a class="button-link@if(state == "closed"){ selected}" href="?state=closed">
|
||||
<i class="octicon octicon-milestone @(if(state == "closed"){"active"})"></i>
|
||||
@milestones.count(_._1.closedDate.isDefined) Closed
|
||||
</a>
|
||||
</span>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@defining(milestones.filter { case (milestone, _, _) =>
|
||||
milestone.closedDate.map(_ => state == "closed").getOrElse(state == "open")
|
||||
}){ milestones =>
|
||||
@milestones.map { case (milestone, openCount, closedCount) =>
|
||||
<tr>
|
||||
<td style="padding-top: 15px; padding-bottom: 15px;">
|
||||
<div class="milestone row">
|
||||
<div class="col-md-4">
|
||||
<a href="@url(repository)/issues?milestone=@milestone.title&state=open" class="milestone-title">@milestone.title</a>
|
||||
<div>
|
||||
@if(closedCount == 0){
|
||||
0%
|
||||
@if(milestone.closedDate.isDefined){
|
||||
<span class="muted">Closed @helper.html.datetimeago(milestone.closedDate.get)</span>
|
||||
} else {
|
||||
@((closedCount.toDouble / (openCount + closedCount).toDouble * 100).toInt)%
|
||||
} <span class="muted">complete</span>
|
||||
@openCount <span class="muted">open</span>
|
||||
@closedCount <span class="muted">closed</span>
|
||||
</div>
|
||||
<div class="milestone-menu">
|
||||
@if(hasWritePermission){
|
||||
<a href="@url(repository)/issues/milestones/@milestone.milestoneId/edit">Edit</a>
|
||||
@if(milestone.closedDate.isDefined){
|
||||
<a href="@url(repository)/issues/milestones/@milestone.milestoneId/open">Open</a>
|
||||
} else {
|
||||
<a href="@url(repository)/issues/milestones/@milestone.milestoneId/close">Close</a>
|
||||
@milestone.dueDate.map { dueDate =>
|
||||
@if(isPast(dueDate)){
|
||||
<i class="octicon octicon-alert" style="color:#BD2C00;"></i><span class="muted milestone-alert">Due by @date(dueDate)</span>
|
||||
} else {
|
||||
<span class="muted">Due by @date(dueDate)</span>
|
||||
}
|
||||
}.getOrElse {
|
||||
<span class="muted">No due date</span>
|
||||
}
|
||||
<a href="@url(repository)/issues/milestones/@milestone.milestoneId/delete" class="delete">Delete</a>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
@progress(openCount + closedCount, closedCount)
|
||||
<div>
|
||||
<div>
|
||||
@if(closedCount == 0){
|
||||
0%
|
||||
} else {
|
||||
@((closedCount.toDouble / (openCount + closedCount).toDouble * 100).toInt)%
|
||||
} <span class="muted">complete</span>
|
||||
@openCount <span class="muted">open</span>
|
||||
@closedCount <span class="muted">closed</span>
|
||||
</div>
|
||||
<div class="milestone-menu">
|
||||
@if(hasWritePermission){
|
||||
<a href="@url(repository)/issues/milestones/@milestone.milestoneId/edit">Edit</a>
|
||||
@if(milestone.closedDate.isDefined){
|
||||
<a href="@url(repository)/issues/milestones/@milestone.milestoneId/open">Open</a>
|
||||
} else {
|
||||
<a href="@url(repository)/issues/milestones/@milestone.milestoneId/close">Close</a>
|
||||
}
|
||||
<a href="@url(repository)/issues/milestones/@milestone.milestoneId/delete" class="delete">Delete</a>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@if(milestone.description.isDefined){
|
||||
<div class="milestone-description markdown-body">
|
||||
@markdown(milestone.description.get, repository, false, false)
|
||||
</div>
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
@if(milestones.isEmpty){
|
||||
<tr>
|
||||
<td style="padding: 20px; background-color: #eee; text-align: center;">
|
||||
No milestones to show.
|
||||
@if(hasWritePermission){
|
||||
<a href="@url(repository)/issues/milestones/new">Create a new milestone.</a>
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
@if(milestone.description.isDefined){
|
||||
<div class="milestone-description markdown-body">
|
||||
@markdown(
|
||||
markdown = milestone.description.get,
|
||||
repository = repository,
|
||||
enableWikiLink = false,
|
||||
enableRefsLink = false,
|
||||
enableLineBreaks = true
|
||||
)
|
||||
</div>
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
@if(milestones.isEmpty){
|
||||
<tr>
|
||||
<td style="padding: 20px; background-color: #eee; text-align: center;">
|
||||
No milestones to show.
|
||||
@if(hasWritePermission){
|
||||
<a href="@url(repository)/issues/milestones/new">Create a new milestone.</a>
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
}
|
||||
</table>
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
@(total: Int, progress: Int)
|
||||
<div class="milestone-progress">
|
||||
@if(progress > 0){
|
||||
<span class="milestone-progress" style="width: @((progress.toDouble / total.toDouble * 100).toInt)%;"></span>
|
||||
}
|
||||
<div class="progress" style="height: 12px; margin-bottom: 8px;">
|
||||
<div class="progress-bar progress-bar-success" role="progressbar" aria-valuenow="@((progress.toDouble / total.toDouble * 100).toInt)" aria-valuemin="0" aria-valuemax="100" style="width: @((progress.toDouble / total.toDouble * 100).toInt)%">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -4,55 +4,53 @@
|
||||
condition: Option[gitbucket.core.service.IssuesService.IssueSearchCondition] = None)(implicit context: gitbucket.core.controller.Context)
|
||||
@import context._
|
||||
@import gitbucket.core.view.helpers._
|
||||
<ul class="nav nav-pills-group pull-left fill-width">
|
||||
<li class="@if(active == "issues" ){active} first"><a href="@url(repository)/issues">Issues</a></li>
|
||||
<ul class="nav nav-pills pull-left" style="line-height: 14px;">
|
||||
<li class="@if(active == "issues" ){active}"><a href="@url(repository)/issues">Issues</a></li>
|
||||
<li class="@if(active == "pulls" ){active}"><a href="@url(repository)/pulls">Pull requests</a></li>
|
||||
<li class="@if(active == "labels" ){active}"><a href="@url(repository)/issues/labels">Labels</a></li>
|
||||
<li class="@if(active == "milestones"){active} last"><a href="@url(repository)/issues/milestones">Milestones</a></li>
|
||||
<li class="pull-right">
|
||||
<form method="GET" id="search-filter-form" style="margin-bottom: 0px;">
|
||||
@condition.map { condition =>
|
||||
@if(loginAccount.isDefined){
|
||||
<div class="input-prepend" style="margin-bottom: 0px;">
|
||||
<div class="btn-group">
|
||||
<button type="button" class="btn dropdown-toggle" data-toggle="dropdown" style="height: 34px;">
|
||||
Filter
|
||||
<span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a href="?q=is:open">Open issues and pull requests</a></li>
|
||||
<li><a href="?q=is:open+is:issue+author:@urlEncode(loginAccount.get.userName)">Your issues</a></li>
|
||||
<li><a href="?q=is:open+is:pr+author:@urlEncode(loginAccount.get.userName)">Your pull requests</a></li>
|
||||
<li><a href="?q=is:open+assignee:@urlEncode(loginAccount.get.userName)">Everything assigned to you</a></li>
|
||||
@*
|
||||
<li><a href="?q=is:open+mentions:@urlEncode(loginAccount.get.userName)">Everything mentioning you</a></li>
|
||||
*@
|
||||
</ul>
|
||||
</div>
|
||||
<input type="text" id="search-filter-box" class="input-xlarge" name="q" style="height: 24px;" value="is:@{if(active == "issues") "issue" else "pr"} @condition.toFilterString"/>
|
||||
</div>
|
||||
} else {
|
||||
<input type="text" id="search-filter-box" class="input-xlarge" name="q" style="height: 24px;" value="is:@{if(active == "issues") "issue" else "pr"} @condition.toFilterString"/>
|
||||
}
|
||||
}
|
||||
@if(loginAccount.isDefined){
|
||||
<div class="btn-group">
|
||||
@if(newButton){
|
||||
@if(active == "issues"){
|
||||
<a class="btn btn-success" href="@url(repository)/issues/new" style="height: 24px;">New issue</a>
|
||||
}
|
||||
@if(active == "pulls"){
|
||||
<a class="btn btn-success" href="@url(repository)/compare" style="height: 24px;">New pull request</a>
|
||||
}
|
||||
@if(active == "labels"){
|
||||
<a class="btn btn-success" href="javascript:void(0);" id="new-label-button" style="height: 24px;">New label</a>
|
||||
}
|
||||
@if(active == "milestones"){
|
||||
<a class="btn btn-success" href="@url(repository)/issues/milestones/new" style="height: 24px;">New milestone</a>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</form>
|
||||
</li>
|
||||
<li class="@if(active == "milestones"){active}"><a href="@url(repository)/issues/milestones">Milestones</a></li>
|
||||
</ul>
|
||||
<form method="GET" id="search-filter-form" style="margin-bottom: 0px;" class="form-inline pull-right">
|
||||
@condition.map { condition =>
|
||||
@if(loginAccount.isDefined){
|
||||
<div class="form-group" style="width: 300px">
|
||||
<div class="input-group" style="margin-bottom: 0px;">
|
||||
<div class="input-group-btn">
|
||||
<button type="button" class="btn btn-default btn-lg dropdown-toggle" data-toggle="dropdown">
|
||||
Filter
|
||||
<span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a href="?q=is:open">Open issues and pull requests</a></li>
|
||||
<li><a href="?q=is:open+is:issue+author:@urlEncode(loginAccount.get.userName)">Your issues</a></li>
|
||||
<li><a href="?q=is:open+is:pr+author:@urlEncode(loginAccount.get.userName)">Your pull requests</a></li>
|
||||
<li><a href="?q=is:open+assignee:@urlEncode(loginAccount.get.userName)">Everything assigned to you</a></li>
|
||||
@*
|
||||
<li><a href="?q=is:open+mentions:@urlEncode(loginAccount.get.userName)">Everything mentioning you</a></li>
|
||||
*@
|
||||
</ul>
|
||||
</div>
|
||||
<input type="text" id="search-filter-box" class="form-control input-lg" size="40" name="q" value="is:@{if(active == "issues") "issue" else "pr"} @condition.toFilterString"/>
|
||||
</div>
|
||||
</div>
|
||||
} else {
|
||||
<input type="text" id="search-filter-box" class="form-control input-lg" size="40" name="q" value="is:@{if(active == "issues") "issue" else "pr"} @condition.toFilterString"/>
|
||||
}
|
||||
}
|
||||
@if(loginAccount.isDefined){
|
||||
@if(newButton){
|
||||
@if(active == "issues"){
|
||||
<a class="btn btn-success btn-lg" href="@url(repository)/issues/new">New issue</a>
|
||||
}
|
||||
@if(active == "pulls"){
|
||||
<a class="btn btn-success btn-lg" href="@url(repository)/compare">New pull request</a>
|
||||
}
|
||||
@if(active == "labels"){
|
||||
<a class="btn btn-success btn-lg" href="javascript:void(0);" id="new-label-button">New label</a>
|
||||
}
|
||||
@if(active == "milestones"){
|
||||
<a class="btn btn-success btn-lg" href="@url(repository)/issues/milestones/new">New milestone</a>
|
||||
}
|
||||
}
|
||||
}
|
||||
</form>
|
||||
|
||||
@@ -11,10 +11,12 @@
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<link rel="icon" href="@assets/common/images/gitbucket.png" type="image/vnd.microsoft.icon" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link href="@assets/vendors/bootstrap/css/bootstrap.css" rel="stylesheet">
|
||||
<link href="@assets/vendors/bootstrap/css/bootstrap-responsive.css" rel="stylesheet">
|
||||
<link href="@assets/vendors/bootstrap-theme-github-3.1.1/css/bootstrap.css" rel="stylesheet">
|
||||
@*
|
||||
<link href="@assets/vendors/bootstrap-theme-github-3.1.1/css/bootstrap-responsive.css" rel="stylesheet">
|
||||
*@
|
||||
<link href="@assets/vendors/octicons/octicons.css" rel="stylesheet">
|
||||
<link href="@assets/vendors/datepicker/css/datepicker.css" rel="stylesheet">
|
||||
<link href="@assets/vendors/datepicker/css/bootstrap-datetimepicker.min.css" rel="stylesheet">
|
||||
<link href="@assets/vendors/colorpicker/css/bootstrap-colorpicker.css" rel="stylesheet">
|
||||
<link href="@assets/vendors/google-code-prettify/prettify.css" type="text/css" rel="stylesheet"/>
|
||||
<link href="@assets/vendors/facebox/facebox.css" rel="stylesheet"/>
|
||||
@@ -23,8 +25,10 @@
|
||||
<script src="@assets/vendors/dropzone/dropzone.js"></script>
|
||||
<script src="@assets/common/js/validation.js"></script>
|
||||
<script src="@assets/common/js/gitbucket.js"></script>
|
||||
<script src="@assets/vendors/bootstrap/js/bootstrap.js"></script>
|
||||
<script src="@assets/vendors/datepicker/js/bootstrap-datepicker.js"></script>
|
||||
<script src="@assets/vendors/bootstrap-theme-github-3.1.1/js/bootstrap.js"></script>
|
||||
<script src="@assets/vendors/bootstrap3-typeahead/bootstrap3-typeahead.js"></script>
|
||||
<script src="@assets/vendors/datepicker/js/moment.js"></script>
|
||||
<script src="@assets/vendors/datepicker/js/bootstrap-datetimepicker.min.js"></script>
|
||||
<script src="@assets/vendors/colorpicker/js/bootstrap-colorpicker.js"></script>
|
||||
<script src="@assets/vendors/google-code-prettify/prettify.js"></script>
|
||||
<script src="@assets/vendors/zclip/ZeroClipboard.min.js"></script>
|
||||
@@ -38,58 +42,69 @@
|
||||
}
|
||||
</head>
|
||||
<body>
|
||||
<form id="search" action="@path/search" method="POST">
|
||||
<div class="navbar">
|
||||
<div class="navbar-inner">
|
||||
<div class="container" style="width: 980px;">
|
||||
<button type="button" class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse">
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
</button>
|
||||
<a class="brand" href="@path/">
|
||||
<img src="@assets/common/images/gitbucket.png" style="width: 24px; height: 24px;"/>GitBucket
|
||||
@defining(AutoUpdate.getCurrentVersion){ version =>
|
||||
<span class="header-version">@version.majorVersion.@version.minorVersion</span>
|
||||
}
|
||||
|
||||
</a>
|
||||
<form id="search" action="@path/search" method="POST" class="form-inline">
|
||||
<nav class="navbar navbar-default">
|
||||
<div class="container" style="width: 980px;">
|
||||
@* TODO: for plugi-ins?
|
||||
<button type="button" class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse">
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
</button>
|
||||
*@
|
||||
<a class="navbar-brand" href="@path/">
|
||||
<img src="@assets/common/images/gitbucket.png" style="width: 24px; height: 24px;"/>GitBucket
|
||||
@defining(AutoUpdate.getCurrentVersion){ version =>
|
||||
<span class="header-version">@version.majorVersion.@version.minorVersion</span>
|
||||
}
|
||||
</a>
|
||||
@if(loginAccount.isDefined){
|
||||
@repository.map { repository =>
|
||||
<input type="text" name="query" style="width: 200px; margin-top: 5px; margin-bottom: 0px;" placeholder="Search this repository"/>
|
||||
<input type="text" name="query" class="form-control" style="width: 200px; margin-bottom: 0px;" placeholder="Search this repository"/>
|
||||
<input type="hidden" name="owner" value="@repository.owner"/>
|
||||
<input type="hidden" name="repository" value="@repository.name"/>
|
||||
}
|
||||
@if(loginAccount.isDefined){
|
||||
<a href="@path/dashboard/pulls" class="global-header-menu">Pull requests</a>
|
||||
<a href="@path/dashboard/issues" class="global-header-menu">Issues</a>
|
||||
<a href="@path/dashboard/pulls" class="global-header-menu">Pull requests</a>
|
||||
<a href="@path/dashboard/issues" class="global-header-menu">Issues</a>
|
||||
} else {
|
||||
@* TODO: merge with below *@
|
||||
@repository.map { repository =>
|
||||
<input type="text" name="query" class="form-control" style="width: 200px; margin-top: 6px; margin-bottom: 0px;" placeholder="Search this repository"/>
|
||||
<input type="hidden" name="owner" value="@repository.owner"/>
|
||||
<input type="hidden" name="repository" value="@repository.name"/>
|
||||
}
|
||||
<div class="nav-collapse collapse pull-right header-menu">
|
||||
@if(loginAccount.isDefined){
|
||||
<div class="btn-group" style="margin-top: 0px;">
|
||||
<a class="dropdown-toggle menu" data-toggle="dropdown" href="#"><i class="octicon octicon-plus" style="font-size: 20px; vertical-align: middle;height:20px !important;"></i><span class="caret" style="vertical-align: middle;"></span></a>
|
||||
<ul class="dropdown-menu pull-right">
|
||||
<li><a href="@path/new">New repository</a></li>
|
||||
<li><a href="@path/groups/new">New group</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="btn-group" style="margin-top: 0px;">
|
||||
<a class="dropdown-toggle menu" data-toggle="dropdown" href="#" data-toggle="tooltip" data-placement="bottom" title="Signed is as @loginAccount.get.userName">@avatar(loginAccount.get.userName, 16)<span class="caret" style="vertical-align: middle;"></span></a>
|
||||
<ul class="dropdown-menu pull-right">
|
||||
<li><a href="@url(loginAccount.get.userName)">Your profile</a></li>
|
||||
<li><a href="@url(loginAccount.get.userName)/_edit">Account settings</a></li>
|
||||
@if(loginAccount.get.isAdmin){
|
||||
<li><a href="@path/admin/users">System administration</a></li>
|
||||
}
|
||||
<li><a href="@path/signout">Sign out</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
} else {
|
||||
<a href="@path/signin?redirect=@urlEncode(currentPath)" class="btn" id="signin">Sign in</a>
|
||||
}
|
||||
</div><!--/.nav-collapse -->
|
||||
</div>
|
||||
}
|
||||
@if(loginAccount.isDefined){
|
||||
<div class="pull-right" style="margin-top: 6px;">
|
||||
<div class="btn-group" style="margin-right: 8px;">
|
||||
<a class="dropdown-toggle menu" data-toggle="dropdown" href="#">
|
||||
<i class="octicon octicon-plus" style="color: black; font-size: 20px; vertical-align: middle;height:20px !important;"></i>
|
||||
<span class="caret" style="color: black; vertical-align: middle;"></span>
|
||||
</a>
|
||||
<ul class="dropdown-menu pull-right">
|
||||
<li><a href="@path/new">New repository</a></li>
|
||||
<li><a href="@path/groups/new">New group</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="btn-group">
|
||||
<a class="dropdown-toggle menu" data-toggle="dropdown" href="#" data-toggle="tooltip" data-placement="bottom" title="Signed is as @loginAccount.get.userName">@avatar(loginAccount.get.userName, 16)
|
||||
<span class="caret" style="color: black; vertical-align: middle;"></span>
|
||||
</a>
|
||||
<ul class="dropdown-menu pull-right">
|
||||
<li><a href="@url(loginAccount.get.userName)">Your profile</a></li>
|
||||
<li><a href="@url(loginAccount.get.userName)/_edit">Account settings</a></li>
|
||||
@if(loginAccount.get.isAdmin){
|
||||
<li><a href="@path/admin/users">System administration</a></li>
|
||||
}
|
||||
<li><a href="@path/signout">Sign out</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
} else {
|
||||
<a href="@path/signin?redirect=@urlEncode(currentPath)" class="btn btn-default pull-right" style="margin-top: 6px;" id="signin">Sign in</a>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
</form>
|
||||
@body
|
||||
<script>
|
||||
|
||||
@@ -24,38 +24,42 @@
|
||||
<div class="container">
|
||||
@helper.html.information(info)
|
||||
@helper.html.error(error)
|
||||
@if(repository.commitCount > 0){
|
||||
<div class="pull-right">
|
||||
<div class="input-prepend">
|
||||
@if(loginAccount.isEmpty){
|
||||
<a title="You must be signed in to fork a repository" href="@path/signin?redirect=@urlEncode(s"${path}/${repository.owner}/${repository.name}")" class="btn btn-small" style="margin-bottom: 10px;">Fork</a>
|
||||
} else {
|
||||
@if(isNoGroup) {
|
||||
<a id="fork-link" href="javascript:void(0);" class="btn btn-small" style="margin-bottom: 10px;">Fork</a>
|
||||
} else {
|
||||
<a href="@path/@repository.owner/@repository.name/fork" class="btn btn-small" rel="facebox" style="margin-bottom: 10px;">Fork</a>
|
||||
}
|
||||
<div class="row">
|
||||
<div class="head">
|
||||
@if(repository.commitCount > 0){
|
||||
<div class="input-group pull-right">
|
||||
<span class="fork">
|
||||
<span class="input-group-btn">
|
||||
@if(loginAccount.isEmpty){
|
||||
<a title="You must be signed in to fork a repository" href="@path/signin?redirect=@urlEncode(s"${path}/${repository.owner}/${repository.name}")" class="btn btn-default">Fork</a>
|
||||
} else {
|
||||
@if(isNoGroup) {
|
||||
<a id="fork-link" href="javascript:void(0);" class="btn btn-default">Fork</a>
|
||||
} else {
|
||||
<a href="@path/@repository.owner/@repository.name/fork" class="btn btn-default" rel="facebox">Fork</a>
|
||||
}
|
||||
}
|
||||
</span>
|
||||
<span class="count"><a href="@url(repository)/network/members">@repository.forkedCount</a></span>
|
||||
</span>
|
||||
</div>
|
||||
@if(loginAccount.isDefined && isNoGroup){
|
||||
<form id="fork-form" method="post" action="@path/@repository.owner/@repository.name/fork" style="display: none;">
|
||||
<input type="hidden" name="account" value="@loginAccount.get.userName"/>
|
||||
</form>
|
||||
}
|
||||
}
|
||||
@helper.html.repositoryicon(repository, true)
|
||||
<a href="@url(repository.owner)">@repository.owner</a> / <a href="@url(repository)" class="strong">@repository.name</a>
|
||||
|
||||
@defining(repository.repository){ x =>
|
||||
@if(repository.repository.originRepositoryName.isDefined){
|
||||
<div class="forked">
|
||||
forked from <a href="@path/@x.parentUserName/@x.parentRepositoryName">@x.parentUserName/@x.parentRepositoryName</a>
|
||||
</div>
|
||||
}
|
||||
<span class="add-on count"><a href="@url(repository)/network/members">@repository.forkedCount</a></span>
|
||||
</div>
|
||||
@if(loginAccount.isDefined && isNoGroup){
|
||||
<form id="fork-form" method="post" action="@path/@repository.owner/@repository.name/fork" style="display: none;">
|
||||
<input type="hidden" name="account" value="@loginAccount.get.userName"/>
|
||||
</form>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
<div class="head">
|
||||
@helper.html.repositoryicon(repository, true)
|
||||
<a href="@url(repository.owner)">@repository.owner</a> / <a href="@url(repository)" class="strong">@repository.name</a>
|
||||
|
||||
@defining(repository.repository){ x =>
|
||||
@if(repository.repository.originRepositoryName.isDefined){
|
||||
<div class="forked">
|
||||
forked from <a href="@path/@x.parentUserName/@x.parentRepositoryName">@x.parentUserName/@x.parentRepositoryName</a>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<hr style="margin-bottom: 20px;"/>
|
||||
@@ -77,7 +81,7 @@
|
||||
<strong id="repository-url-proto">HTTP</strong> <span class="mute">clone URL</span>
|
||||
</div>
|
||||
@helper.html.copy("repository-url-copy", repository.httpUrl){
|
||||
<input type="text" value="@repository.httpUrl" id="repository-url" readonly>
|
||||
<input type="text" value="@repository.httpUrl" id="repository-url" class="form-control input-sm" readonly>
|
||||
}
|
||||
@if(settings.ssh && loginAccount.isDefined){
|
||||
<div class="small">
|
||||
@@ -87,26 +91,26 @@
|
||||
@id.map { id =>
|
||||
@if(context.platform != "linux" && context.platform != null){
|
||||
<div style="margin-top: 10px;">
|
||||
<a href="@repository.httpOpenRepoUrl(context.platform)" id="repository-clone-url" class="btn btn-small" style="width: 147px;font-weight: bold;"><i class="octicon octicon-desktop-download"></i> Clone in Desktop</a>
|
||||
<a href="@repository.httpOpenRepoUrl(context.platform)" id="repository-clone-url" class="btn btn-sm btn-default btn-block"><i class="octicon octicon-desktop-download"></i> Clone in Desktop</a>
|
||||
</div>
|
||||
}
|
||||
<div style="margin-top: 10px;">
|
||||
<a href="@{url(repository)}/archive/@{encodeRefName(id)}.zip" class="btn btn-small" style="width: 147px;font-weight: bold;"><i class="octicon octicon-cloud-download"></i>Download ZIP</a>
|
||||
<a href="@{url(repository)}/archive/@{encodeRefName(id)}.zip" class="btn btn-sm btn-default btn-block"><i class="octicon octicon-cloud-download"></i>Download ZIP</a>
|
||||
</div>
|
||||
@*
|
||||
<div style="margin-top: 10px;">
|
||||
<a href="@{url(repository)}/archive/@{encodeRefName(id)}.tar.gz" class="btn btn-small" style="width: 147px;font-weight: bold;"><i class="octicon octicon-cloud-download"></i>Download TAR.GZ</a>
|
||||
<a href="@{url(repository)}/archive/@{encodeRefName(id)}.tar.gz" class="btn btn-sm btn-default btn-block "><i class="octicon octicon-cloud-download"></i>Download TAR.GZ</a>
|
||||
</div>
|
||||
*@
|
||||
}
|
||||
}
|
||||
</div>
|
||||
<div style="margin-right: @if(expand){180px} else {50px};">
|
||||
<div style="margin-right: @if(expand){180px} else {55px};">
|
||||
@if(expand){
|
||||
@repository.repository.description.map { description =>
|
||||
<p class="description">@description</p>
|
||||
<p class="description">@detectAndRenderLinks(description)</p>
|
||||
}
|
||||
<div style="margin-bottom: 10px;" class="box-content">
|
||||
<div style="margin-bottom: 10px; padding: 4px;" class="panel panel-default">
|
||||
<table class="fill-width">
|
||||
<tr>
|
||||
<td style="width: 33%; text-align: center;">
|
||||
|
||||
@@ -9,37 +9,37 @@
|
||||
<div class="muted" style="background-color: white;">
|
||||
<i class="octicon octicon-git-commit"></i> Commits on @date(day.head.commitTime)
|
||||
</div>
|
||||
<div class="box-commits">
|
||||
@day.map { commit =>
|
||||
<div class="box-content-row" style="padding: 8px;">
|
||||
<ul class="nav nav-pills-group pull-right" style="margin-top: 2px; margin-bottom: 0px; margin-right: 4px;">
|
||||
<li class="first"><a href="@url(repository)/commit/@commit.id" class="link monospace">@commit.id.substring(0, 7)</a></li>
|
||||
<li class="last"><a href="@url(repository)/tree/@commit.id" style="padding-top: 9px; padding-bottom: 10px;"><i class="octicon octicon-code link"></i></a></li>
|
||||
</ul>
|
||||
<div>
|
||||
<div class="commit-avatar-image">@avatar(commit, 40)</div>
|
||||
<div class="commit-message-box">
|
||||
<a href="@url(repository)/commit/@commit.id" class="commit-message" style="font-weight: bold;">@link(commit.summary, repository)</a>
|
||||
@if(commit.description.isDefined){
|
||||
<a href="javascript:void(0)" onclick="$('#description-@commit.id').toggle();" class="omit">...</a>
|
||||
}
|
||||
<br>
|
||||
@if(commit.description.isDefined){
|
||||
<pre id="description-@commit.id" style="display: none;" class="commit-description">@link(commit.description.get, repository)</pre>
|
||||
}
|
||||
<div class="small">
|
||||
@user(commit.authorName, commit.authorEmailAddress, "username")
|
||||
<span class="muted">authored @helper.html.datetimeago(commit.authorTime)</span>
|
||||
@if(commit.isDifferentFromAuthor) {
|
||||
<span class="octicon octicon-arrow-right" style="margin-top : -2px;"></span>
|
||||
@user(commit.committerName, commit.committerEmailAddress, "username")
|
||||
<span class="muted">committed @helper.html.datetimeago(commit.authorTime)</span>
|
||||
<div class="list-group box-commits">
|
||||
@day.map { commit =>
|
||||
<div class="list-group-item">
|
||||
<ul class="nav nav-pills pull-right">
|
||||
<li><a href="@url(repository)/commit/@commit.id" class="link monospace" style="line-height: 16px;">@commit.id.substring(0, 7)</a></li>
|
||||
<li><a href="@url(repository)/tree/@commit.id" style="line-height: 16px;"><i class="octicon octicon-code link"></i></a></li>
|
||||
</ul>
|
||||
<div>
|
||||
<div class="commit-avatar-image">@avatar(commit, 40)</div>
|
||||
<div>
|
||||
<a href="@url(repository)/commit/@commit.id" class="commit-message" style="font-weight: bold;">@link(commit.summary, repository)</a>
|
||||
@if(commit.description.isDefined){
|
||||
<a href="javascript:void(0)" onclick="$('#description-@commit.id').toggle();" class="omit">...</a>
|
||||
}
|
||||
<br>
|
||||
@if(commit.description.isDefined){
|
||||
<pre id="description-@commit.id" style="display: none;" class="commit-description">@link(commit.description.get, repository)</pre>
|
||||
}
|
||||
<div style="margin-top: 2px;">
|
||||
@user(commit.authorName, commit.authorEmailAddress, "username")
|
||||
<span class="muted">authored @helper.html.datetimeago(commit.authorTime)</span>
|
||||
@if(commit.isDifferentFromAuthor) {
|
||||
<span class="octicon octicon-arrow-right" style="margin-top : -2px;"></span>
|
||||
@user(commit.committerName, commit.committerEmailAddress, "username")
|
||||
<span class="muted">committed @helper.html.datetimeago(commit.authorTime)</span>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
@@ -53,21 +53,22 @@
|
||||
</div>
|
||||
<div id="pull-request-form" @*class="box"*@ style="display: none; margin-bottom: 20px;">
|
||||
<form method="POST" action="@path/@originRepository.owner/@originRepository.name/pulls/new" validate="true">
|
||||
<div class="row-fluid">
|
||||
<div class="span10">
|
||||
<div class="row">
|
||||
<div class="col-md-10">
|
||||
<div class="issue-avatar-image">@avatar(loginAccount.get.userName, 48)</div>
|
||||
<div class="box issue-box">
|
||||
<div class="box-content">
|
||||
<div class="panel panel-default issue-box">
|
||||
<div class="panel-body">
|
||||
<span class="error" id="error-title"></span>
|
||||
<input type="text" name="title" style="width: 690px" placeholder="Title"/>
|
||||
<input type="text" name="title" class="form-control input-lg" style="width: 680px" placeholder="Title"/>
|
||||
@helper.html.preview(
|
||||
repository = repository,
|
||||
content = "",
|
||||
enableWikiLink = false,
|
||||
enableRefsLink = true,
|
||||
enableLineBreaks = true,
|
||||
enableTaskList = true,
|
||||
hasWritePermission = true,
|
||||
style = "width: 690px; height: 200px;"
|
||||
style = "width: 680px; height: 200px;"
|
||||
)
|
||||
<input type="hidden" name="targetUserName" value="@originRepository.owner"/>
|
||||
<input type="hidden" name="targetBranch" value="@originId"/>
|
||||
@@ -77,12 +78,12 @@
|
||||
<input type="hidden" name="commitIdFrom" value="@sourceId"/>
|
||||
<input type="hidden" name="commitIdTo" value="@commitId"/>
|
||||
<div class="align-right">
|
||||
<input type="submit" class="btn btn-success" value="Create pull request"/>
|
||||
<input type="submit" class="btn btn-lg btn-success" value="Create pull request"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="span2">
|
||||
<div class="col-md-2">
|
||||
@issues.html.issueinfo(None, Nil, Nil, collaborators, milestones.map((_, 0, 0)), labels, hasOriginWritePermission, repository)
|
||||
</div>
|
||||
</div>
|
||||
@@ -90,6 +91,11 @@
|
||||
</div>
|
||||
}
|
||||
@if(commits.isEmpty){
|
||||
<div class="panel panel-default" style="padding: 20px; background-color: #eee; text-align: center;">
|
||||
<h4>There isn't anything to compare.</h4>
|
||||
<span class="strong">@originRepository.owner:@originId</span> and <span class="strong">@forkedRepository.owner:@forkedId</span> are identical.
|
||||
</div>
|
||||
@*
|
||||
<table class="table table-bordered table-hover table-issues">
|
||||
<tr>
|
||||
<td style="padding: 20px; background-color: #eee; text-align: center;">
|
||||
@@ -98,8 +104,9 @@
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
*@
|
||||
} else {
|
||||
<div style="margin-bottom: 10px;" class="box-content">
|
||||
<div style="margin-bottom: 10px; padding: 4px;" class="panel panel-default">
|
||||
<table class="fill-width">
|
||||
<tr>
|
||||
<td style="width: 25%; text-align: center;">
|
||||
@@ -167,8 +174,8 @@
|
||||
$(function(){
|
||||
$('a.origin-owner, a.forked-owner, a.origin-branch, a.forked-branch').click(function(){
|
||||
var e = $(this);
|
||||
e.parents('ul').find('i').attr('class', 'icon-white');
|
||||
e.find('i').attr('class', 'octicon-check');
|
||||
e.parents('ul').find('i').attr('class', 'octicon');
|
||||
e.find('i').addClass('octicon-check');
|
||||
e.parents('div.btn-group').find('button span.strong').text(e.text());
|
||||
|
||||
@if(members.isEmpty){
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user