Compare commits

..

130 Commits
4.2 ... 4.5

Author SHA1 Message Date
Naoki Takezoe
c65599d995 Update README.md 2016-09-29 10:40:21 +09:00
Naoki Takezoe
22ae1df4b1 Update README.md 2016-09-29 10:30:34 +09:00
Naoki Takezoe
6b22342166 Merge pull request #1299 from gitbucket/release/gitbucket-4.5
GitBucket 4.5 release
2016-09-29 10:29:07 +09:00
Naoki Takezoe
53f6190267 Scalaz's <| is deprecated 2016-09-28 14:05:40 +09:00
Naoki Takezoe
f73daaef44 (refs #954)Cut commit id in Markdown with 7 letters 2016-09-28 13:28:50 +09:00
Naoki Takezoe
d99e382dfe (refs #1206)Display commit count on the history button 2016-09-28 10:11:55 +09:00
Naoki Takezoe
aefbee2093 (refs #1206)Display find and history icon in mobile view 2016-09-28 09:58:32 +09:00
Naoki Takezoe
11fb0a7edf (refs #1214)Gravater is disable in default 2016-09-28 09:33:16 +09:00
Naoki Takezoe
fe959aecff (refs #1298)Append raw=true only if the given url does not have it. 2016-09-25 17:42:50 +09:00
Naoki Takezoe
9b33655bd4 Bump to sbt 0.13.12. 2016-09-25 17:09:29 +09:00
Naoki Takezoe
33acad85db Merge pull request #1301 from conradlink/master
Fix host command line argument
2016-09-23 16:00:28 +09:00
conradlink
6bfe3ea760 Merge remote-tracking branch 'upstream/master' 2016-09-23 00:32:35 -04:00
Naoki Takezoe
1532fd71d0 Remove files for publishing jars to the maven repository because already it's possible by sbt. 2016-09-22 11:43:21 +09:00
Naoki Takezoe
c14a732e2a Update publishing jar operation 2016-09-22 11:41:26 +09:00
Naoki Takezoe
a1372034ed Ready for GitBucket 4.5 release 2016-09-22 11:37:37 +09:00
conradlink
98914269b7 Fix to make the --host argument work again. 2016-09-21 08:43:07 -04:00
Naoki Takezoe
d5e455336b (refs #1293)Restore dashboard issues / pull requests switcher 2016-09-20 16:41:20 +09:00
Naoki Takezoe
7b84f25c56 (refs #1297)Bugfix 2016-09-20 15:45:21 +09:00
Naoki Takezoe
2ca20af502 (refs #1297)Allow to configure HikariCP in database.conf 2016-09-19 23:13:52 +09:00
Naoki Takezoe
78df2accfc (refs #1290)Always show “Download ZIP” button 2016-09-18 21:14:00 +09:00
Naoki Takezoe
7a282fb67e (refs #1291)Add secure attribute to JSESSIONID cookie when baseUrl starts with "https://" 2016-09-12 15:06:59 +09:00
Naoki Takezoe
db679967af (refs #1291)Add http-only attribute to JSESSIONID cookie 2016-09-12 14:59:43 +09:00
Naoki Takezoe
9e98d30612 (refs #1288)Dropping files is also available in textarea, not only in the bottom bar. 2016-09-06 02:05:23 +09:00
Naoki Takezoe
a47065e4a9 (refs #1277)Don't make search filter and sorting sticky 2016-09-05 11:55:44 +09:00
Naoki Takezoe
94d18c471c Remove unnecessary style class 2016-09-04 02:59:06 +09:00
Naoki Takezoe
f8f3019228 Tweak branch list presentation 2016-09-04 02:54:54 +09:00
Naoki Takezoe
c3d90b8593 (refs #1239)Add toggle sidebar button 2016-09-04 02:12:56 +09:00
Naoki Takezoe
62c1299f29 (refs #1275, #1239)Fix CSS 2016-09-04 00:51:08 +09:00
Naoki Takezoe
b75db98cad (refs #1278) Bump to AdminLTE 2.3.6 2016-08-31 01:16:52 +09:00
Naoki Takezoe
3592b3d13c Merge pull request #1280 from kw-udon/fix-path-param-contents-api
fix get-contents API's format
2016-08-31 01:11:18 +09:00
Keiichi Watanabe
ca814e2c08 fix path parameter in get-contents API 2016-08-30 19:00:10 +09:00
Naoki Takezoe
48b6a590bf 4.4.0 release 2016-08-28 11:51:59 +09:00
Naoki Takezoe
285ef02a17 Merge pull request #1270 from S-YOU/patch-1
add copyright holder name in license
2016-08-28 11:10:42 +09:00
YOU
18375c741e Update LICENSE 2016-08-26 08:05:40 +00:00
Naoki Takezoe
21030344cc Merge pull request #1273 from kw-udon/fix-contents-api-default-ref-param
Set default value of parameter in Get-Contents API
2016-08-22 23:55:18 +09:00
Keiichi Watanabe
a494027217 Set default value to ref param in contents-API 2016-08-22 18:58:59 +09:00
Naoki Takezoe
7bca01af59 (refs #1230) Apply table-hover class for the commit history 2016-08-18 11:48:47 +09:00
YOU
acf3fa9980 add copyright holder name in license 2016-08-15 23:32:31 +09:00
Naoki Takezoe
c0ce0f8d19 (refs #1259) Add SQL import capability and remove XML export / import 2016-08-14 01:55:19 +09:00
Naoki Takezoe
56e7168461 Merge pull request #1268 from haru/FixgetGroupNames
Fix AccountService#getGroupNames returns duplicated group name .
2016-08-12 00:08:48 +09:00
Haruyuki Iida
c2d0d94f05 Fix AccountService#getGroupNames returns duplicated group name . 2016-08-11 15:28:29 +09:00
Naoki Takezoe
fc22cfbbdd Merge branch 'master' of https://github.com/gitbucket/gitbucket 2016-08-11 14:40:14 +09:00
Naoki Takezoe
d62adbf649 (refs #769) Output go-import meta tag in all repositories for go get 2016-08-11 14:40:06 +09:00
Naoki Takezoe
dba5539e3e (refs #1267) Graceful shutdown for the embedded jetty 2016-08-10 20:57:44 +09:00
Naoki Takezoe
f0a8b3bb17 (refs #1264) BugFix: File attachment does not work on the issue comment 2016-08-08 21:57:26 +09:00
Naoki Takezoe
f52e7e1bdd (refs #1255) Display the newest version as plugin version because if migration was failed, plugin is not registered. 2016-08-07 14:10:57 +09:00
Naoki Takezoe
58ba26f21e (refs #1255) Also check plugin version 2016-08-07 13:58:51 +09:00
Naoki Takezoe
bf7b30630c (refs #1255) Check whether database version is same as GitBucket version in startup 2016-08-07 13:42:42 +09:00
Naoki Takezoe
b5cac0308e Merge branch 'mcveat-217-sort-milestones' 2016-08-06 10:04:18 +09:00
Naoki Takezoe
373ea39048 (refs #219) Add milestoneId as a lowest priority sort column 2016-08-06 10:03:59 +09:00
Naoki Takezoe
427f5eec5f Merge branch '217-sort-milestones' of https://github.com/mcveat/gitbucket into mcveat-217-sort-milestones
# Conflicts:
#	src/main/scala/service/MilestonesService.scala
2016-08-06 09:56:56 +09:00
Naoki Takezoe
a4e9903e00 Merge pull request #1261 from UprootStaging/sshdUpdate
Sshd update
2016-08-04 15:51:03 +09:00
Naoki Takezoe
0d900a892c Merge pull request #1260 from team-lab/fix-blame-admin-lte
(fix #1246) cannot see the blame
2016-08-04 15:28:23 +09:00
hrj
dc6fdaf482 Fix for test compilation 2016-08-04 10:03:35 +05:30
hrj
b79498ed9f Update apache-sshd to latest version 2016-08-04 09:54:07 +05:30
nazoking
69e8f628df (fix #1246) cannot see the blame 2016-08-04 12:46:02 +09:00
Naoki Takezoe
d3d8e3ce5f Merge pull request #1256 from team-lab/git-is-reserved-user-name
(refs #1251) git is reserved user name. add validation.
2016-08-03 15:42:13 +09:00
nazoking
0499c47f4b (refs #1251, #1256) add admin, upload and api to reserved. 2016-08-03 12:14:32 +09:00
nazoking
7fd0cdd7d8 (refs #1251) git is reserved user name 2016-08-02 18:00:40 +09:00
Naoki Takezoe
49eaf79e01 Merge pull request #1253 from gitbucket/publish-maven-central
(refs #935) Update project configuration for deploying artifacts to Maven central
2016-07-31 21:45:04 +09:00
Naoki Takezoe
3a96c30aa8 (refs #935) Reoveride artifact to remove war from published artifacts 2016-07-31 21:21:27 +09:00
Naoki Takezoe
6d550fa485 (refs #935) Revert version to 4.3.0 2016-07-31 20:49:16 +09:00
Naoki Takezoe
7f9d69bb51 (refs #935) Update project configuration for deploying artifacts to Maven central 2016-07-31 18:08:47 +09:00
Naoki Takezoe
709fab9ccc GitBucket 4.3 release 2016-07-30 10:34:40 +09:00
Naoki Takezoe
fd13a2db79 Update README.md for 4.3 release 2016-07-30 10:21:01 +09:00
Naoki Takezoe
840d81f7bd (refs #1250) Bump markedj 2016-07-30 10:14:05 +09:00
Naoki Takezoe
5d08f4d339 Merge pull request #1249 from shiena/patch/fix-git-repo-path
fix: can't resolve the git repository path provided by the plugin
2016-07-29 01:26:31 +09:00
Naoki Takezoe
ef48b2d5ef (refs #1248) Move splitPath() to RepositoryInfo 2016-07-28 17:37:35 +09:00
Naoki Takezoe
f54e4f337f Merge pull request #1248 from kounoike/PR-api-for-ghbs
add some API. required by Jenkins GitHub Branch Source plugin
2016-07-28 17:28:53 +09:00
Naoki Takezoe
743965d3b8 (refs #1247) cleanup 2016-07-27 02:36:30 +09:00
Naoki Takezoe
0e787eddfd Merge pull request #1247 from kounoike/PR-api-basicauth
Add Basic Authentication support for API access
2016-07-27 02:32:20 +09:00
Mitsuhiro Koga
442c0d575e Modify contextPath to the literal pattern
Because contextPath can contain some special chars.
2016-07-26 20:50:40 +09:00
Mitsuhiro Koga
485516be2e fix: can't resolve the git repository path provided by the plugin
If the contextPath is not equals to `/`, gitRepositoryPath contains
the contextPath + `/git`.  Therefore, gitbucket can not resolve
the git repository path provided by the plugin.

For example, you can not clone the snippets repository of gist-plugin.

To fix this, also deletes contextPath from requestURI.
2016-07-26 01:01:57 +09:00
KOUNOIKE Yuusuke
6b2fbb3bf0 add some API. required by Jenkins GitHub Branch Source plugin 2016-07-25 23:52:08 +09:00
KOUNOIKE Yuusuke
e510b1c26b Add Basic Authentication support for API access 2016-07-25 23:43:35 +09:00
Naoki Takezoe
8d35494169 Fix message of plugin version 2016-07-20 11:45:16 +09:00
Naoki Takezoe
cf1504bae7 (refs #1245) Display migrated plugin version if migration is failing 2016-07-20 10:58:26 +09:00
Naoki Takezoe
9bb4e473b9 Merge pull request #1236 from mrkm4ntr/fix-plugin-versions
Fix plugin versions in installed plugins page
2016-07-19 09:27:38 +09:00
Naoki Takezoe
d67afebadc Rename CompletionProposalProvider to SuggestionProvider 2016-07-16 12:29:42 +09:00
Naoki Takezoe
417886161c (refs #1242) Bugfix in branch protection for branches which contain / 2016-07-16 11:00:29 +09:00
Naoki Takezoe
1b85d511e9 Purge Emoji support because it will be provided as plugin 2016-07-14 10:35:16 +09:00
Naoki Takezoe
45d84f63c1 Merge remote-tracking branch 'origin/master' 2016-07-14 02:21:42 +09:00
Naoki Takezoe
fff60b2704 Remove head / from resource path 2016-07-14 02:21:27 +09:00
Naoki Takezoe
c9339aec9e Fix error in creating and merging pull request 2016-07-13 21:06:38 +09:00
Naoki Takezoe
7c98ae1341 (refs #1241) Update CompletionProposalProvider interface 2016-07-13 02:33:58 +09:00
Naoki Takezoe
01c2291715 Fix testcase 2016-07-13 02:14:44 +09:00
Naoki Takezoe
2e03f081d9 (refs #1241) Filter CompletionProposalProvider by the completion context 2016-07-13 01:56:43 +09:00
Naoki Takezoe
0cbafdd884 Update TextDecorator interface 2016-07-13 01:43:35 +09:00
Naoki Takezoe
d5a9c2c15d (refs #1241) Add new extension point to add completion proposals provider for the textarea 2016-07-12 19:38:42 +09:00
Naoki Takezoe
1496591244 (refs #1240) Add new extension point to add text decorators 2016-07-12 15:21:40 +09:00
Naoki Takezoe
f5acce3901 Decorate only text node 2016-07-12 01:25:02 +09:00
Shintaro Murakami
5568a0ad8e Fix plugin versions in installed plugins page 2016-07-11 20:46:48 +09:00
Naoki Takezoe
26a18287c7 (refs #1238) Add new extension point to supply assets by plugin 2016-07-11 18:14:43 +09:00
Naoki Takezoe
b0f819b9bd Remove unused import statements 2016-07-11 13:53:07 +09:00
Naoki Takezoe
ebff7baf07 Fix WebHook message garbling:
76079aa1e8
2016-07-11 13:31:06 +09:00
Naoki Takezoe
cf9a55d896 (refs #1237) Fix broken layout 2016-07-10 22:55:34 +09:00
Naoki Takezoe
72f7b659f4 Remove unused import statements 2016-07-10 12:15:17 +09:00
Naoki Takezoe
87192d025b Remove Jsoup dependency 2016-07-10 11:35:03 +09:00
Naoki Takezoe
fd181b9a0c Fix emoji conversion 2016-07-10 02:03:25 +09:00
Naoki Takezoe
9c4cc12a02 Change import to resolve resolving error in IntelliJ 2016-07-09 18:09:54 +09:00
Naoki Takezoe
44497b559e Change import to resolve resolving error in IntelliJ 2016-07-09 18:09:31 +09:00
Naoki Takezoe
09c50a149b Change import to resolve resolving error in IntelliJ 2016-07-09 16:07:32 +09:00
Naoki Takezoe
88beb68e01 Change import to resolve resolving error in IntelliJ 2016-07-09 15:27:04 +09:00
Naoki Takezoe
0da358311b Change import to resolve resolving error in IntelliJ 2016-07-09 14:50:17 +09:00
Naoki Takezoe
cf97b63dab Change import to resolve resolving error in IntelliJ 2016-07-09 14:22:23 +09:00
Naoki Takezoe
4b5f22144e Change import to resolve resolving error in IntelliJ 2016-07-09 14:22:06 +09:00
Naoki Takezoe
0d342a6863 Use completion is disabled in the Wiki editor 2016-07-09 13:46:13 +09:00
Naoki Takezoe
458820a09d Add user name completion in the textarea 2016-07-09 11:47:41 +09:00
Naoki Takezoe
135c34ef0f Remove extension point to add text decorators. We need more consideration. 2016-07-09 11:47:22 +09:00
Naoki Takezoe
8187c5a013 Add new extension point to add TextDecorator. 2016-07-08 22:50:01 +09:00
Naoki Takezoe
6ff48c8130 Clean up 2016-07-08 19:58:22 +09:00
Naoki Takezoe
d37c70cd8d Emoji completion in textarea 2016-07-08 19:51:28 +09:00
Naoki Takezoe
8abf357405 Convert emoji in commit message 2016-07-07 19:53:23 +09:00
Naoki Takezoe
c93ac71634 Merge branch 'rlazoti-emoji-support' 2016-07-07 19:46:30 +09:00
Naoki Takezoe
408180f071 Change EmojiConverter to EmojiUtil 2016-07-07 19:39:30 +09:00
Naoki Takezoe
4e98abfe5c Remove unnecessary self typing 2016-07-07 19:30:47 +09:00
Naoki Takezoe
efbb404bd4 Fix file attachement area in Wiki page editing form 2016-07-05 17:48:53 +09:00
Naoki Takezoe
66f409bfad Merge branch 'emoji-support' of https://github.com/rlazoti/gitbucket into rlazoti-emoji-support
# Conflicts:
#	src/main/scala/view/Markdown.scala
2016-07-05 17:21:10 +09:00
Naoki Takezoe
44ec64fb4b Merge pull request #1232 from shiena/patch/fix-user-profile
fix: can't show the user profile when joining any groups
2016-07-05 01:57:24 +09:00
Mitsuhiro Koga
fb27bd29e8 Add a missing ul tag 2016-07-05 01:47:20 +09:00
Mitsuhiro Koga
c26ca9d463 fix: can't show the user profile when joining any groups 2016-07-04 21:01:47 +09:00
Naoki Takezoe
8c36ba33f4 Update README.md 2016-07-04 08:39:50 +09:00
Naoki Takezoe
fe1e18b495 Bugfix for new installation 2016-07-04 08:33:02 +09:00
Naoki Takezoe
29e632af04 Update README.md 2016-07-03 01:10:17 +09:00
Naoki Takezoe
2b9daae62b Update README.md 2016-07-03 00:52:20 +09:00
Naoki Takezoe
8a11f85dd1 4.2.1 release 2016-07-03 00:46:54 +09:00
Naoki Takezoe
b09c72b106 (refs #1227)Fix migration from 3.14 to 4.0.0 2016-07-03 00:39:38 +09:00
Naoki Takezoe
43456e817a Fix badge position in the sidebar 2016-07-02 21:13:14 +09:00
Rodrigo Lazoti
41e49423b2 Add emoji support for markdown 2015-01-21 22:07:14 -02:00
Piotr Adamski
3cc7bd3cdb #217 open milestones sorted by due date ascending, closed milestones sorted by close date descending 2013-12-01 14:09:25 +01:00
230 changed files with 6393 additions and 2075 deletions

View File

@@ -187,7 +187,7 @@
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Copyright 2013-2016 GitBucket Team
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

View File

@@ -50,6 +50,7 @@ GitBucket has the plug-in system to extend GitBucket from outside of GitBucket.
- [gitbucket-commitgraphs-plugin](https://github.com/yoshiyoshifujii/gitbucket-commitgraphs-plugin)
- [gitbucket-asciidoctor-plugin](https://github.com/lefou/gitbucket-asciidoctor-plugin)
- [gitbucket-network-plugin](https://github.com/mrkm4ntr/gitbucket-network-plugin)
- [gitbucket-emoji-plugin](https://github.com/gitbucket/gitbucket-emoji-plugin)
You can find community plugins other than them at [gitbucket community plugins](http://gitbucket-plugins.github.io/).
@@ -64,21 +65,55 @@ Support
Release Notes
-------------
### 4.2 - 2 Jul 2016
### 4.5 - 29 Sep 2016
- Attach files by dropping into textarea
- Issues / Pull requests switcher in dashboard
- HikariCP could be configured in `GITBUCKET_HOME/database.conf`
- Improve Cookie security
- Display commit count on the history button
- Improve mobile view
### 4.4 - 28 Aug 2016
- Import a SQL dump file to the database
- `go get` support in private repositories
- Sort milestones by due date
- apache-sshd has been updated to 1.2.0
### 4.3 - 30 Jul 2016
- Emoji support by [gitbucket-emoji-plugin](https://github.com/gitbucket/gitbucket-emoji-plugin)
- User name suggestion
- Add new web APIs and basic authentication support for API access
- Root Endpoint
- [List endpoints](https://developer.github.com/v3/#root-endpoint)
- [List Branches](https://developer.github.com/v3/repos/branches/#list-branches)
- [Get contents](https://developer.github.com/v3/repos/contents/#get-contents)
- [Get a Reference](https://developer.github.com/v3/git/refs/#get-a-reference)
- [List Collaborators](https://developer.github.com/v3/repos/collaborators/#list-collaborators)
- [List user repositories](https://developer.github.com/v3/repos/#list-user-repositories)
- [Get a group](https://developer.github.com/v3/orgs/#get-an-organization)
- [List group repositories](https://developer.github.com/v3/repos/#list-organization-repositories)
- Add new extension points
- `assetsMapping` : Supplies resources in plugin classpath as web assets
- `suggestionProvider` : Provides suggestion in the Markdown editing textarea
- `textDecorator` : Decorate text nodes in HTML which is converted from Markdown
### 4.2.1 - 3 Jul 2016
- Fix migration bug
This is hotfix for a critical bug in migration. If you are new installation, use 4.2.0. But if you have an exisiting installation and it had been updated to 4.0 from 3.x, you must update to 4.2.1.
### 4.2 - 2 Jul 2016
- New UI based on [AdminLTE](https://github.com/almasaeed2010/AdminLTE)
- git gc
- Issues and Wiki have been possible to be disabled
- SMTP configuration test mail
### 4.1 - 4 Jun 2016
- Generic ssh user
- Improve branch protection UI
- Default value of pull request title
### 4.0 - 30 Apr 2016
- MySQL and PostgreSQL support
- Data export and import
- Migration system has been switched to [solidbase](https://github.com/gitbucket/solidbase)
@@ -86,7 +121,6 @@ Release Notes
**Note:** You can upgrade to GitBucket 4.0 from 3.14. If your GitBucket is 3.13 or before, you have to upgrade 3.14 at first.
### 3.14 - 30 Apr 2016
- File attachment and search for wiki pages
- New extension points to add menus
- Content-Type of webhooks has been choosable

View File

@@ -1,6 +1,6 @@
val Organization = "gitbucket"
val Organization = "io.github.gitbucket"
val Name = "gitbucket"
val GitBucketVersion = "4.2.0"
val GitBucketVersion = "4.5.0"
val ScalatraVersion = "2.4.1"
val JettyVersion = "9.3.9.v20160517"
@@ -29,11 +29,11 @@ libraryDependencies ++= Seq(
"io.github.gitbucket" %% "scalatra-forms" % "1.0.0",
"commons-io" % "commons-io" % "2.4",
"io.github.gitbucket" % "solidbase" % "1.0.0",
"io.github.gitbucket" % "markedj" % "1.0.9-SNAPSHOT",
"io.github.gitbucket" % "markedj" % "1.0.9",
"org.apache.commons" % "commons-compress" % "1.11",
"org.apache.commons" % "commons-email" % "1.4",
"org.apache.httpcomponents" % "httpclient" % "4.5.1",
"org.apache.sshd" % "apache-sshd" % "1.0.0",
"org.apache.sshd" % "apache-sshd" % "1.2.0",
"org.apache.tika" % "tika-core" % "1.13",
"com.typesafe.slick" %% "slick" % "2.1.0",
"com.novell.ldap" % "jldap" % "2009-10-07",
@@ -55,9 +55,6 @@ libraryDependencies ++= Seq(
"ru.yandex.qatools.embed" % "postgresql-embedded" % "1.14" % "test"
)
// Twirl settings
play.twirl.sbt.Import.TwirlKeys.templateImports += "gitbucket.core._"
// Compiler settings
scalacOptions := Seq("-deprecation", "-language:postfixOps", "-Ybackend:GenBCode", "-Ydelambdafy:method", "-target:jvm-1.8")
javacOptions in compile ++= Seq("-target", "8", "-source", "8")
@@ -173,3 +170,56 @@ Keys.artifact in (Compile, executableKey) ~= {
}
addArtifact(Keys.artifact in (Compile, executableKey), executableKey)
*/
publishTo <<= version { (v: String) =>
val nexus = "https://oss.sonatype.org/"
if (v.trim.endsWith("SNAPSHOT")) Some("snapshots" at nexus + "content/repositories/snapshots")
else Some("releases" at nexus + "service/local/staging/deploy/maven2")
}
publishMavenStyle := true
pomIncludeRepository := { _ => false }
artifact in Keys.`package` := Artifact(moduleName.value)
pomExtra := (
<url>https://github.com/gitbucket/gitbucket</url>
<licenses>
<license>
<name>The Apache Software License, Version 2.0</name>
<url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
</license>
</licenses>
<scm>
<url>https://github.com/gitbucket/gitbucket</url>
<connection>scm:git:https://github.com/gitbucket/gitbucket.git</connection>
</scm>
<developers>
<developer>
<id>takezoe</id>
<name>Naoki Takezoe</name>
<url>https://github.com/takezoe</url>
</developer>
<developer>
<id>shimamoto</id>
<name>Takako Shimamoto</name>
<url>https://github.com/shimamoto</url>
</developer>
<developer>
<id>tanacasino</id>
<name>Tomofumi Tanaka</name>
<url>https://github.com/tanacasino</url>
</developer>
<developer>
<id>mrkm4ntr</id>
<name>Shintaro Murakami</name>
<url>https://github.com/mrkm4ntr</url>
</developer>
<developer>
<id>nazoking</id>
<name>nazoking</name>
<url>https://github.com/nazoking</url>
</developer>
<developer>
<id>McFoggy</id>
<name>Matthieu Brouillard</name>
<url>https://github.com/McFoggy</url>
</developer>
</developers>
)

View File

@@ -46,9 +46,10 @@ $ sbt executable
### Deploy assembly jar file
For plug-in development, we have to publish the assembly jar file to the public Maven repository by `release/deploy-assembly-jar.sh`.
For plug-in development, we have to publish the GitBucket jar file to the Maven central repository as well. At first, hit following command to publish artifacts to the sonatype OSS repository:
```bash
$ cd release/
$ ./deploy-assembly-jar.sh
$ sbt publish-signed
```
Then operate release sequence at https://oss.sonatype.org/.

View File

@@ -1 +1 @@
sbt.version=0.13.11
sbt.version=0.13.12

View File

@@ -1,6 +1,7 @@
scalacOptions ++= Seq("-unchecked", "-deprecation", "-feature")
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")
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")
addSbtPlugin("fi.gekkio.sbtplugins" % "sbt-jrebel-plugin" % "0.10.0")
addSbtPlugin("com.typesafe.sbt" % "sbt-pgp" % "0.8.3")

View File

@@ -1,24 +0,0 @@
#!/bin/sh
. ./env.sh
cd ../
./sbt.sh clean assembly
cd release
if [[ "$GITBUCKET_VERSION" =~ -SNAPSHOT$ ]]; then
MVN_DEPLOY_PATH=mvn-snapshot
else
MVN_DEPLOY_PATH=mvn
fi
echo $MVN_DEPLOY_PATH
mvn deploy:deploy-file \
-DgroupId=gitbucket\
-DartifactId=gitbucket-assembly\
-Dversion=$GITBUCKET_VERSION\
-Dpackaging=jar\
-Dfile=../target/scala-2.11/gitbucket-assembly-$GITBUCKET_VERSION.jar\
-DrepositoryId=sourceforge.jp\
-Durl=scp://shell.sourceforge.jp/home/groups/a/am/amateras/htdocs/$MVN_DEPLOY_PATH/

View File

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

View File

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

View File

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

2
sbt.sh
View File

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

View File

@@ -3,12 +3,14 @@ import org.eclipse.jetty.webapp.WebAppContext;
import java.io.File;
import java.net.URL;
import java.net.InetSocketAddress;
import java.security.ProtectionDomain;
public class JettyLauncher {
public static void main(String[] args) throws Exception {
String host = null;
int port = 8080;
InetSocketAddress address = null;
String contextPath = "/";
boolean forceHttps = false;
@@ -29,7 +31,13 @@ public class JettyLauncher {
}
}
Server server = new Server(port);
if(host != null) {
address = new InetSocketAddress(host, port);
} else {
address = new InetSocketAddress(port);
}
Server server = new Server(address);
// SelectChannelConnector connector = new SelectChannelConnector();
// if(host != null) {
@@ -60,6 +68,8 @@ public class JettyLauncher {
}
server.setHandler(context);
server.setStopAtShutdown(true);
server.setStopTimeout(7_000);
server.start();
server.join();
}

View File

@@ -1,24 +1,30 @@
import gitbucket.core.controller._
import gitbucket.core.plugin.PluginRegistry
import gitbucket.core.servlet.{AccessTokenAuthenticationFilter, BasicAuthenticationFilter, Database, TransactionFilter}
import gitbucket.core.servlet.{ApiAuthenticationFilter, Database, GitAuthenticationFilter, TransactionFilter}
import gitbucket.core.util.Directory
import java.util.EnumSet
import javax.servlet._
import gitbucket.core.service.SystemSettingsService
import org.scalatra._
class ScalatraBootstrap extends LifeCycle {
class ScalatraBootstrap extends LifeCycle with SystemSettingsService {
override def init(context: ServletContext) {
val settings = loadSystemSettings()
if(settings.baseUrl.exists(_.startsWith("https://"))) {
context.getSessionCookieConfig.setSecure(true)
}
// Register TransactionFilter and BasicAuthenticationFilter at first
context.addFilter("transactionFilter", new TransactionFilter)
context.getFilterRegistration("transactionFilter").addMappingForUrlPatterns(EnumSet.allOf(classOf[DispatcherType]), true, "/*")
context.addFilter("basicAuthenticationFilter", new BasicAuthenticationFilter)
context.getFilterRegistration("basicAuthenticationFilter").addMappingForUrlPatterns(EnumSet.allOf(classOf[DispatcherType]), true, "/git/*")
context.addFilter("accessTokenAuthenticationFilter", new AccessTokenAuthenticationFilter)
context.getFilterRegistration("accessTokenAuthenticationFilter").addMappingForUrlPatterns(EnumSet.allOf(classOf[DispatcherType]), true, "/api/v3/*")
context.addFilter("gitAuthenticationFilter", new GitAuthenticationFilter)
context.getFilterRegistration("gitAuthenticationFilter").addMappingForUrlPatterns(EnumSet.allOf(classOf[DispatcherType]), true, "/git/*")
context.addFilter("apiAuthenticationFilter", new ApiAuthenticationFilter)
context.getFilterRegistration("apiAuthenticationFilter").addMappingForUrlPatterns(EnumSet.allOf(classOf[DispatcherType]), true, "/api/v3/*")
// Register controllers
context.mount(new AnonymousAccessController, "/*")

View File

@@ -11,5 +11,9 @@ object GitBucketCoreModule extends Module("gitbucket-core",
new Version("4.1.0"),
new Version("4.2.0",
new LiquibaseMigration("update/gitbucket-core_4.2.xml")
)
),
new Version("4.2.1"),
new Version("4.3.0"),
new Version("4.4.0"),
new Version("4.5.0")
)

View File

@@ -14,3 +14,10 @@ case class ApiBranch(
"self" -> ApiPath(s"/api/v3/repos/${repositoryName.fullName}/branches/${name}"),
"html" -> ApiPath(s"/${repositoryName.fullName}/tree/${name}"))
}
case class ApiBranchCommit(sha: String)
case class ApiBranchForList(
name: String,
commit: ApiBranchCommit
)

View File

@@ -0,0 +1,11 @@
package gitbucket.core.api
import gitbucket.core.util.JGitUtil.FileInfo
case class ApiContents(`type`: String, name: String)
object ApiContents{
def apply(fileInfo: FileInfo): ApiContents =
if(fileInfo.isDirectory) ApiContents("dir", fileInfo.name)
else ApiContents("file", fileInfo.name)
}

View File

@@ -0,0 +1,3 @@
package gitbucket.core.api
case class ApiEndPoint(rate_limit_url: ApiPath = ApiPath("/api/v3/rate_limit"))

View File

@@ -0,0 +1,5 @@
package gitbucket.core.api
case class ApiObject(sha: String)
case class ApiRef(ref: String, `object`: ApiObject)

View File

@@ -13,6 +13,7 @@ case class ApiUser(
created_at: Date) {
val url = ApiPath(s"/api/v3/users/${login}")
val html_url = ApiPath(s"/${login}")
val avatar_url = ApiPath(s"/${login}/_avatar")
// val followers_url = ApiPath(s"/api/v3/users/${login}/followers")
// val following_url = ApiPath(s"/api/v3/users/${login}/following{/other_user}")
// val gists_url = ApiPath(s"/api/v3/users/${login}/gists{/gist_id}")

View File

@@ -38,7 +38,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
case class PersonalTokenForm(note: String)
val newForm = mapping(
"userName" -> trim(label("User name" , text(required, maxlength(100), identifier, uniqueUserName))),
"userName" -> trim(label("User name" , text(required, maxlength(100), identifier, uniqueUserName, reservedNames))),
"password" -> trim(label("Password" , text(required, maxlength(20)))),
"fullName" -> trim(label("Full Name" , text(required, maxlength(100)))),
"mailAddress" -> trim(label("Mail Address" , text(required, maxlength(100), uniqueMailAddress()))),
@@ -68,7 +68,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
case class EditGroupForm(groupName: String, url: Option[String], fileId: Option[String], members: String, clearImage: Boolean)
val newGroupForm = mapping(
"groupName" -> trim(label("Group name" ,text(required, maxlength(100), identifier, uniqueUserName))),
"groupName" -> trim(label("Group name" ,text(required, maxlength(100), identifier, uniqueUserName, reservedNames))),
"url" -> trim(label("URL" ,optional(text(maxlength(200))))),
"fileId" -> trim(label("File ID" ,optional(text()))),
"members" -> trim(label("Members" ,text(required, members)))

View File

@@ -7,7 +7,7 @@ import gitbucket.core.service.PullRequestService._
import gitbucket.core.service._
import gitbucket.core.util.ControlUtil._
import gitbucket.core.util.Directory._
import gitbucket.core.util.JGitUtil.CommitInfo
import gitbucket.core.util.JGitUtil.{CommitInfo, getFileList, getBranches, getDefaultBranch}
import gitbucket.core.util._
import gitbucket.core.util.Implicits._
import org.eclipse.jgit.api.Git
@@ -54,14 +54,91 @@ trait ApiControllerBase extends ControllerBase {
with CollaboratorsAuthenticator =>
/**
* https://developer.github.com/v3/users/#get-a-single-user
*/
get("/api/v3/users/:userName") {
getAccountByUserName(params("userName")).map { account =>
* https://developer.github.com/v3/#root-endpoint
*/
get("/api/v3/") {
JsonFormat(ApiEndPoint())
}
/**
* https://developer.github.com/v3/orgs/#get-an-organization
*/
get("/api/v3/orgs/:groupName") {
getAccountByUserName(params("groupName")).filter(account => account.isGroupAccount).map { account =>
JsonFormat(ApiUser(account))
} getOrElse NotFound
}
/**
* https://developer.github.com/v3/users/#get-a-single-user
*/
get("/api/v3/users/:userName") {
getAccountByUserName(params("userName")).filterNot(account => account.isGroupAccount).map { account =>
JsonFormat(ApiUser(account))
} getOrElse NotFound
}
/**
* https://developer.github.com/v3/repos/#list-organization-repositories
*/
get("/api/v3/orgs/:orgName/repos") {
JsonFormat(getVisibleRepositories(context.loginAccount, Some(params("orgName"))).map{ r => ApiRepository(r, getAccountByUserName(r.owner).get)})
}
/**
* https://developer.github.com/v3/repos/#list-user-repositories
*/
get("/api/v3/users/:userName/repos") {
JsonFormat(getVisibleRepositories(context.loginAccount, Some(params("userName"))).map{ r => ApiRepository(r, getAccountByUserName(r.owner).get)})
}
/*
* https://developer.github.com/v3/repos/branches/#list-branches
*/
get ("/api/v3/repos/:owner/:repo/branches")(referrersOnly { repository =>
JsonFormat(JGitUtil.getBranches(
owner = repository.owner,
name = repository.name,
defaultBranch = repository.repository.defaultBranch,
origin = repository.repository.originUserName.isEmpty
).map { br =>
ApiBranchForList(br.name, ApiBranchCommit(br.commitId))
})
})
/*
* https://developer.github.com/v3/repos/contents/#get-contents
*/
get("/api/v3/repos/:owner/:repo/contents/*")(referrersOnly { repository =>
val path = multiParams("splat").head match {
case s if s.isEmpty => "."
case s => s
}
val refStr = params.getOrElse("ref", repository.repository.defaultBranch)
using(Git.open(getRepositoryDir(params("owner"), params("repo")))){ git =>
JsonFormat(getFileList(git, refStr, path).map{f => ApiContents(f)})
}
})
/*
* https://developer.github.com/v3/git/refs/#get-a-reference
*/
get("/api/v3/repos/:owner/:repo/git/*") (referrersOnly { repository =>
val revstr = multiParams("splat").head
using(Git.open(getRepositoryDir(params("owner"), params("repo")))) { git =>
//JsonFormat( (revstr, git.getRepository().resolve(revstr)) )
// getRef is deprecated by jgit-4.2. use exactRef() or findRef()
val sha = git.getRepository().getRef(revstr).getObjectId().name()
JsonFormat(ApiRef(revstr, ApiObject(sha)))
}
})
/**
* https://developer.github.com/v3/repos/collaborators/#list-collaborators
*/
get("/api/v3/repos/:owner/:repo/collaborators") (referrersOnly { repository =>
JsonFormat(getCollaborators(params("owner"), params("repo")).map(u => ApiUser(getAccountByUserName(u).get)))
})
/**
* https://developer.github.com/v3/users/#get-the-authenticated-user
*/
@@ -71,6 +148,16 @@ trait ApiControllerBase extends ControllerBase {
} getOrElse Unauthorized
}
/**
* List user's own repository
* https://developer.github.com/v3/repos/#list-your-repositories
*/
get("/api/v3/user/repos")(usersOnly{
JsonFormat(getVisibleRepositories(context.loginAccount, Option(context.loginAccount.get.userName)).map{
r => ApiRepository(r, getAccountByUserName(r.owner).get)
})
})
/**
* Create user repository
* https://developer.github.com/v3/repos/#create

View File

@@ -244,4 +244,13 @@ trait AccountManagementControllerBase extends ControllerBase {
.map { _ => "Mail address is already registered." }
}
val allReservedNames = Set("git", "admin", "upload", "api")
protected def reservedNames(): Constraint = new Constraint(){
override def validate(name: String, value: String, messages: Messages): Option[String] = if(allReservedNames.contains(value)){
Some(s"${value} is reserved")
}else{
None
}
}
}

View File

@@ -15,20 +15,7 @@ trait DashboardControllerBase extends ControllerBase {
with UsersAuthenticator =>
get("/dashboard/issues")(usersOnly {
val q = request.getParameter("q")
val account = context.loginAccount.get
Option(q).map { q =>
val condition = IssueSearchCondition(q, Map[String, Int]())
q match {
case q if(q.contains("is:pr")) => redirect(s"/dashboard/pulls?q=${StringUtil.urlEncode(q)}")
case q if(q.contains(s"author:${account.userName}")) => redirect(s"/dashboard/issues/created_by${condition.toURL}")
case q if(q.contains(s"assignee:${account.userName}")) => redirect(s"/dashboard/issues/assigned${condition.toURL}")
case q if(q.contains(s"mentions:${account.userName}")) => redirect(s"/dashboard/issues/mentioned${condition.toURL}")
case _ => searchIssues("created_by")
}
} getOrElse {
searchIssues("created_by")
}
searchIssues("created_by")
})
get("/dashboard/issues/assigned")(usersOnly {
@@ -44,20 +31,7 @@ trait DashboardControllerBase extends ControllerBase {
})
get("/dashboard/pulls")(usersOnly {
val q = request.getParameter("q")
val account = context.loginAccount.get
Option(q).map { q =>
val condition = IssueSearchCondition(q, Map[String, Int]())
q match {
case q if(q.contains("is:issue")) => redirect(s"/dashboard/issues?q=${StringUtil.urlEncode(q)}")
case q if(q.contains(s"author:${account.userName}")) => redirect(s"/dashboard/pulls/created_by${condition.toURL}")
case q if(q.contains(s"assignee:${account.userName}")) => redirect(s"/dashboard/pulls/assigned${condition.toURL}")
case q if(q.contains(s"mentions:${account.userName}")) => redirect(s"/dashboard/pulls/mentioned${condition.toURL}")
case _ => searchPullRequests("created_by")
}
} getOrElse {
searchPullRequests("created_by")
}
searchPullRequests("created_by")
})
get("/dashboard/pulls/created_by")(usersOnly {
@@ -73,14 +47,7 @@ trait DashboardControllerBase extends ControllerBase {
})
private def getOrCreateCondition(key: String, filter: String, userName: String) = {
val condition = session.putAndGet(key, if(request.hasQueryString){
val q = request.getParameter("q")
if(q == null){
IssueSearchCondition(request)
} else {
IssueSearchCondition(q, Map[String, Int]())
}
} else session.getAs[IssueSearchCondition](key).getOrElse(IssueSearchCondition()))
val condition = IssueSearchCondition(request)
filter match {
case "assigned" => condition.copy(assigned = Some(Some(userName)), author = None, mentioned = None)

View File

@@ -79,15 +79,10 @@ class FileUploadController extends ScalatraServlet with FileUploadSupport with R
}
post("/import") {
import JDBCUtil._
session.get(Keys.Session.LoginAccount).collect { case loginAccount: Account if loginAccount.isAdmin =>
execute({ (file, fileId) =>
if(file.getName.endsWith(".xml")){
import JDBCUtil._
val conn = request2Session(request).conn
conn.importAsXML(file.getInputStream)
} else {
throw new RuntimeException("Import is available for only the XML file.")
}
request2Session(request).conn.importAsSQL(file.getInputStream)
}, _ => true)
}
redirect("/admin/data")

View File

@@ -204,8 +204,7 @@ trait IssuesControllerBase extends ControllerBase {
getIssue(repository.owner, repository.name, params("id")) map { x =>
if(isEditable(x.userName, x.repositoryName, x.openedUserName)){
params.get("dataType") collect {
case t if t == "html" => html.editissue(
x.content, x.issueId, x.userName, x.repositoryName)
case t if t == "html" => html.editissue(x.content, x.issueId, repository)
} getOrElse {
contentType = formats("json")
org.json4s.jackson.Serialization.write(
@@ -232,8 +231,7 @@ trait IssuesControllerBase extends ControllerBase {
getComment(repository.owner, repository.name, params("id")) map { x =>
if(isEditable(x.userName, x.repositoryName, x.commentedUserName)){
params.get("dataType") collect {
case t if t == "html" => html.editcomment(
x.content, x.commentId, x.userName, x.repositoryName)
case t if t == "html" => html.editcomment(x.content, x.commentId, repository)
} getOrElse {
contentType = formats("json")
org.json4s.jackson.Serialization.write(
@@ -365,16 +363,7 @@ trait IssuesControllerBase extends ControllerBase {
val sessionKey = Keys.Session.Issues(owner, repoName)
// retrieve search condition
val condition = session.putAndGet(sessionKey,
if(request.hasQueryString){
val q = request.getParameter("q")
if(q == null || q.trim.isEmpty){
IssueSearchCondition(request)
} else {
IssueSearchCondition(q, getMilestones(owner, repoName).map(x => (x.title, x.milestoneId)).toMap)
}
} else session.getAs[IssueSearchCondition](sessionKey).getOrElse(IssueSearchCondition())
)
val condition = IssueSearchCondition(request)
html.list(
"issues",

View File

@@ -520,10 +520,7 @@ trait PullRequestsControllerBase extends ControllerBase {
val sessionKey = Keys.Session.Pulls(owner, repoName)
// retrieve search condition
val condition = session.putAndGet(sessionKey,
if(request.hasQueryString) IssueSearchCondition(request)
else session.getAs[IssueSearchCondition](sessionKey).getOrElse(IssueSearchCondition())
)
val condition = IssueSearchCondition(request)
gitbucket.core.issues.html.list(
"pulls",

View File

@@ -117,15 +117,20 @@ trait RepositoryViewerControllerBase extends ControllerBase {
/**
* Displays the file list of the repository root and the default branch.
*/
get("/:owner/:repository")(referrersOnly {
fileList(_)
})
get("/:owner/:repository") {
params.get("go-get") match {
case Some("1") => defining(request.paths){ paths =>
getRepository(paths(0), paths(1)).map(gitbucket.core.html.goget(_))getOrElse NotFound()
}
case _ => referrersOnly(fileList(_))
}
}
/**
* Displays the file list of the specified path and branch.
*/
get("/:owner/:repository/tree/*")(referrersOnly { repository =>
val (id, path) = splitPath(repository, multiParams("splat").head)
val (id, path) = repository.splitPath(multiParams("splat").head)
if(path.isEmpty){
fileList(repository, id)
} else {
@@ -137,7 +142,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
* Displays the commit list of the specified resource.
*/
get("/:owner/:repository/commits/*")(referrersOnly { repository =>
val (branchName, path) = splitPath(repository, multiParams("splat").head)
val (branchName, path) = repository.splitPath(multiParams("splat").head)
val page = params.get("page").flatMap(_.toIntOpt).getOrElse(1)
using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git =>
@@ -153,7 +158,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
})
get("/:owner/:repository/new/*")(collaboratorsOnly { repository =>
val (branch, path) = splitPath(repository, multiParams("splat").head)
val (branch, path) = repository.splitPath(multiParams("splat").head)
val protectedBranch = getProtectedBranchInfo(repository.owner, repository.name, branch).needStatusCheck(context.loginAccount.get.userName)
html.editor(branch, repository, if(path.length == 0) Nil else path.split("/").toList,
None, JGitUtil.ContentInfo("text", None, Some("UTF-8")),
@@ -161,7 +166,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
})
get("/:owner/:repository/edit/*")(collaboratorsOnly { repository =>
val (branch, path) = splitPath(repository, multiParams("splat").head)
val (branch, path) = repository.splitPath(multiParams("splat").head)
val protectedBranch = getProtectedBranchInfo(repository.owner, repository.name, branch).needStatusCheck(context.loginAccount.get.userName)
using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git =>
@@ -177,7 +182,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
})
get("/:owner/:repository/remove/*")(collaboratorsOnly { repository =>
val (branch, path) = splitPath(repository, multiParams("splat").head)
val (branch, path) = repository.splitPath(multiParams("splat").head)
using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git =>
val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(branch))
@@ -235,7 +240,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
})
get("/:owner/:repository/raw/*")(referrersOnly { repository =>
val (id, path) = splitPath(repository, multiParams("splat").head)
val (id, path) = repository.splitPath(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 =>
@@ -253,7 +258,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
* Displays the file content of the specified branch or commit.
*/
val blobRoute = get("/:owner/:repository/blob/*")(referrersOnly { repository =>
val (id, path) = splitPath(repository, multiParams("splat").head)
val (id, path) = repository.splitPath(multiParams("splat").head)
val raw = params.get("raw").getOrElse("false").toBoolean
using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git =>
val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(id))
@@ -285,7 +290,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
* Blame data.
*/
ajaxGet("/:owner/:repository/get-blame/*")(referrersOnly { repository =>
val (id, path) = splitPath(repository, multiParams("splat").head)
val (id, path) = repository.splitPath(multiParams("splat").head)
contentType = formats("json")
using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git =>
val last = git.log.add(git.getRepository.resolve(id)).addPath(path).setMaxCount(1).call.iterator.next.name
@@ -376,8 +381,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
getCommitComment(repository.owner, repository.name, params("id")) map { x =>
if(isEditable(x.userName, x.repositoryName, x.commentedUserName)){
params.get("dataType") collect {
case t if t == "html" => html.editcomment(
x.content, x.commentId, x.userName, x.repositoryName)
case t if t == "html" => html.editcomment(x.content, x.commentId, repository)
} getOrElse {
contentType = formats("json")
org.json4s.jackson.Serialization.write(
@@ -527,17 +531,6 @@ trait RepositoryViewerControllerBase extends ControllerBase {
}
})
private def splitPath(repository: RepositoryService.RepositoryInfo, path: String): (String, String) = {
val id = repository.branchList.collectFirst {
case branch if(path == branch || path.startsWith(branch + "/")) => branch
} orElse repository.tags.collectFirst {
case tag if(path == tag.name || path.startsWith(tag.name + "/")) => tag.name
} getOrElse path.split("/")(0)
(id, path.substring(id.length).stripPrefix("/"))
}
private val readmeFiles = PluginRegistry().renderableExtensions.map { extension =>
s"readme.${extension}"
} ++ Seq("readme.txt", "readme")

View File

@@ -14,7 +14,6 @@ import gitbucket.core.util.Directory._
import gitbucket.core.util.StringUtil._
import io.github.gitbucket.scalatra.forms._
import org.apache.commons.io.{FileUtils, IOUtils}
import org.apache.commons.mail.{DefaultAuthenticator, HtmlEmail}
import org.scalatra.i18n.Messages
class SystemSettingsController extends SystemSettingsControllerBase
@@ -104,7 +103,7 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
val newUserForm = mapping(
"userName" -> trim(label("Username" ,text(required, maxlength(100), identifier, uniqueUserName))),
"userName" -> trim(label("Username" ,text(required, maxlength(100), identifier, uniqueUserName, reservedNames))),
"password" -> trim(label("Password" ,text(required, maxlength(20)))),
"fullName" -> trim(label("Full Name" ,text(required, maxlength(100)))),
"mailAddress" -> trim(label("Mail Address" ,text(required, maxlength(100), uniqueMailAddress()))),
@@ -126,7 +125,7 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
)(EditUserForm.apply)
val newGroupForm = mapping(
"groupName" -> trim(label("Group name" ,text(required, maxlength(100), identifier, uniqueUserName))),
"groupName" -> trim(label("Group name" ,text(required, maxlength(100), identifier, uniqueUserName, reservedNames))),
"url" -> trim(label("URL" ,optional(text(maxlength(200))))),
"fileId" -> trim(label("File ID" ,optional(text()))),
"members" -> trim(label("Members" ,text(required, members)))
@@ -304,12 +303,7 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
post("/admin/export")(adminOnly {
import gitbucket.core.util.JDBCUtil._
val session = request2Session(request)
val file = if(params("type") == "sql"){
session.conn.exportAsSQL(request.getParameterValues("tableNames").toSeq)
} else {
session.conn.exportAsXML(request.getParameterValues("tableNames").toSeq)
}
val file = request2Session(request).conn.exportAsSQL(request.getParameterValues("tableNames").toSeq)
contentType = "application/octet-stream"
response.setHeader("Content-Disposition", "attachment; filename=" + file.getName)

View File

@@ -149,6 +149,36 @@ abstract class Plugin {
*/
def dashboardTabs(registry: PluginRegistry, context: ServletContext, settings: SystemSettings): Seq[(Context) => Option[Link]] = Nil
/**
* Override to add assets mappings.
*/
val assetsMappings: Seq[(String, String)] = Nil
/**
* Override to add assets mappings.
*/
def assetsMappings(registry: PluginRegistry, context: ServletContext, settings: SystemSettings): Seq[(String, String)] = Nil
/**
* Override to add text decorators.
*/
val textDecorators: Seq[TextDecorator] = Nil
/**
* Override to add text decorators.
*/
def textDecorators(registry: PluginRegistry, context: ServletContext, settings: SystemSettings): Seq[TextDecorator] = Nil
/**
* Override to add suggestion provider.
*/
val suggestionProviders: Seq[SuggestionProvider] = Nil
/**
* Override to add suggestion provider.
*/
def suggestionProviders(registry: PluginRegistry, context: ServletContext, settings: SystemSettings): Seq[SuggestionProvider] = Nil
/**
* This method is invoked in initialization of plugin system.
* Register plugin functionality to PluginRegistry.
@@ -193,6 +223,15 @@ abstract class Plugin {
(dashboardTabs ++ dashboardTabs(registry, context, settings)).foreach { dashboardTab =>
registry.addDashboardTab(dashboardTab)
}
(assetsMappings ++ assetsMappings(registry, context, settings)).foreach { assetMapping =>
registry.addAssetsMapping((assetMapping._1, assetMapping._2, getClass.getClassLoader))
}
(textDecorators ++ textDecorators(registry, context, settings)).foreach { textDecorator =>
registry.addTextDecorator(textDecorator)
}
(suggestionProviders ++ suggestionProviders(registry, context, settings)).foreach { suggestionProvider =>
registry.addSuggestionProvider(suggestionProvider)
}
}
/**

View File

@@ -13,8 +13,8 @@ import gitbucket.core.util.ControlUtil._
import gitbucket.core.util.DatabaseConfig
import gitbucket.core.util.Directory._
import io.github.gitbucket.solidbase.Solidbase
import io.github.gitbucket.solidbase.manager.JDBCVersionManager
import io.github.gitbucket.solidbase.model.Module
import liquibase.database.core.H2Database
import org.apache.commons.codec.binary.{Base64, StringUtils}
import org.slf4j.LoggerFactory
@@ -42,10 +42,13 @@ class PluginRegistry {
private val systemSettingMenus = new ListBuffer[(Context) => Option[Link]]
private val accountSettingMenus = new ListBuffer[(Context) => Option[Link]]
private val dashboardTabs = new ListBuffer[(Context) => Option[Link]]
private val assetsMappings = new ListBuffer[(String, String, ClassLoader)]
private val textDecorators = new ListBuffer[TextDecorator]
def addPlugin(pluginInfo: PluginInfo): Unit = {
plugins += pluginInfo
}
private val suggestionProviders = new ListBuffer[SuggestionProvider]
suggestionProviders += new UserNameSuggestionProvider()
def addPlugin(pluginInfo: PluginInfo): Unit = plugins += pluginInfo
def getPlugins(): List[PluginInfo] = plugins.toList
@@ -66,42 +69,26 @@ class PluginRegistry {
def getImage(id: String): String = images(id)
def addController(path: String, controller: ControllerBase): Unit = {
controllers += ((controller, path))
}
def addController(path: String, controller: ControllerBase): Unit = controllers += ((controller, path))
@deprecated("Use addController(path: String, controller: ControllerBase) instead", "3.4.0")
def addController(controller: ControllerBase, path: String): Unit = {
addController(path, controller)
}
def addController(controller: ControllerBase, path: String): Unit = addController(path, controller)
def getControllers(): Seq[(ControllerBase, String)] = controllers.toSeq
def addJavaScript(path: String, script: String): Unit = {
javaScripts += ((path, script))
}
def addJavaScript(path: String, script: String): Unit = javaScripts += ((path, script))
def getJavaScript(currentPath: String): List[String] = {
javaScripts.filter(x => currentPath.matches(x._1)).toList.map(_._2)
}
def getJavaScript(currentPath: String): List[String] = javaScripts.filter(x => currentPath.matches(x._1)).toList.map(_._2)
def addRenderer(extension: String, renderer: Renderer): Unit = {
renderers += ((extension, renderer))
}
def addRenderer(extension: String, renderer: Renderer): Unit = renderers += ((extension, renderer))
def getRenderer(extension: String): Renderer = {
renderers.get(extension).getOrElse(DefaultRenderer)
}
def getRenderer(extension: String): Renderer = renderers.get(extension).getOrElse(DefaultRenderer)
def renderableExtensions: Seq[String] = renderers.keys.toSeq
def addRepositoryRouting(routing: GitRepositoryRouting): Unit = {
repositoryRoutings += routing
}
def addRepositoryRouting(routing: GitRepositoryRouting): Unit = repositoryRoutings += routing
def getRepositoryRoutings(): Seq[GitRepositoryRouting] = {
repositoryRoutings.toSeq
}
def getRepositoryRoutings(): Seq[GitRepositoryRouting] = repositoryRoutings.toSeq
def getRepositoryRouting(repositoryPath: String): Option[GitRepositoryRouting] = {
PluginRegistry().getRepositoryRoutings().find {
@@ -111,54 +98,49 @@ class PluginRegistry {
}
}
def addReceiveHook(commitHook: ReceiveHook): Unit = {
receiveHooks += commitHook
}
def addReceiveHook(commitHook: ReceiveHook): Unit = receiveHooks += commitHook
def getReceiveHooks: Seq[ReceiveHook] = receiveHooks.toSeq
def addGlobalMenu(globalMenu: (Context) => Option[Link]): Unit = {
globalMenus += globalMenu
}
def addGlobalMenu(globalMenu: (Context) => Option[Link]): Unit = globalMenus += globalMenu
def getGlobalMenus: Seq[(Context) => Option[Link]] = globalMenus.toSeq
def addRepositoryMenu(repositoryMenu: (RepositoryInfo, Context) => Option[Link]): Unit = {
repositoryMenus += repositoryMenu
}
def addRepositoryMenu(repositoryMenu: (RepositoryInfo, Context) => Option[Link]): Unit = repositoryMenus += repositoryMenu
def getRepositoryMenus: Seq[(RepositoryInfo, Context) => Option[Link]] = repositoryMenus.toSeq
def addRepositorySettingTab(repositorySettingTab: (RepositoryInfo, Context) => Option[Link]): Unit = {
repositorySettingTabs += repositorySettingTab
}
def addRepositorySettingTab(repositorySettingTab: (RepositoryInfo, Context) => Option[Link]): Unit = repositorySettingTabs += repositorySettingTab
def getRepositorySettingTabs: Seq[(RepositoryInfo, Context) => Option[Link]] = repositorySettingTabs.toSeq
def addProfileTab(profileTab: (Account, Context) => Option[Link]): Unit = {
profileTabs += profileTab
}
def addProfileTab(profileTab: (Account, Context) => Option[Link]): Unit = profileTabs += profileTab
def getProfileTabs: Seq[(Account, Context) => Option[Link]] = profileTabs.toSeq
def addSystemSettingMenu(systemSettingMenu: (Context) => Option[Link]): Unit = {
systemSettingMenus += systemSettingMenu
}
def addSystemSettingMenu(systemSettingMenu: (Context) => Option[Link]): Unit = systemSettingMenus += systemSettingMenu
def getSystemSettingMenus: Seq[(Context) => Option[Link]] = systemSettingMenus.toSeq
def addAccountSettingMenu(accountSettingMenu: (Context) => Option[Link]): Unit = {
accountSettingMenus += accountSettingMenu
}
def addAccountSettingMenu(accountSettingMenu: (Context) => Option[Link]): Unit = accountSettingMenus += accountSettingMenu
def getAccountSettingMenus: Seq[(Context) => Option[Link]] = accountSettingMenus.toSeq
def addDashboardTab(dashboardTab: (Context) => Option[Link]): Unit = {
dashboardTabs += dashboardTab
}
def addDashboardTab(dashboardTab: (Context) => Option[Link]): Unit = dashboardTabs += dashboardTab
def getDashboardTabs: Seq[(Context) => Option[Link]] = dashboardTabs.toSeq
def addAssetsMapping(assetsMapping: (String, String, ClassLoader)): Unit = assetsMappings += assetsMapping
def getAssetsMappings: Seq[(String, String, ClassLoader)] = assetsMappings.toSeq
def addTextDecorator(textDecorator: TextDecorator): Unit = textDecorators += textDecorator
def getTextDecorators: Seq[TextDecorator] = textDecorators.toSeq
def addSuggestionProvider(suggestionProvider: SuggestionProvider): Unit = suggestionProviders += suggestionProvider
def getSuggestionProviders: Seq[SuggestionProvider] = suggestionProviders.toSeq
}
/**
@@ -180,6 +162,8 @@ object PluginRegistry {
*/
def initialize(context: ServletContext, settings: SystemSettings, conn: java.sql.Connection): Unit = {
val pluginDir = new File(PluginHome)
val manager = new JDBCVersionManager(conn)
if(pluginDir.exists && pluginDir.isDirectory){
pluginDir.listFiles(new FilenameFilter {
override def accept(dir: File, name: String): Boolean = name.endsWith(".jar")
@@ -192,19 +176,26 @@ object PluginRegistry {
val solidbase = new Solidbase()
solidbase.migrate(conn, classLoader, DatabaseConfig.liquiDriver, new Module(plugin.pluginId, plugin.versions: _*))
// Check version
val databaseVersion = manager.getCurrentVersion(plugin.pluginId)
val pluginVersion = plugin.versions.last.getVersion
if(databaseVersion != pluginVersion){
throw new IllegalStateException(s"Plugin version is ${pluginVersion}, but database version is ${databaseVersion}")
}
// Initialize
plugin.initialize(instance, context, settings)
instance.addPlugin(PluginInfo(
pluginId = plugin.pluginId,
pluginName = plugin.pluginName,
version = plugin.versions.head.getVersion,
description = plugin.description,
pluginClass = plugin
pluginId = plugin.pluginId,
pluginName = plugin.pluginName,
pluginVersion = plugin.versions.last.getVersion,
description = plugin.description,
pluginClass = plugin
))
} catch {
case e: Throwable => {
logger.error(s"Error during plugin initialization", e)
logger.error(s"Error during plugin initialization: ${pluginJar.getAbsolutePath}", e)
}
}
}
@@ -231,7 +222,7 @@ case class Link(id: String, label: String, path: String, icon: Option[String] =
case class PluginInfo(
pluginId: String,
pluginName: String,
version: String,
pluginVersion: String,
description: String,
pluginClass: Plugin
)

View File

@@ -0,0 +1,27 @@
package gitbucket.core.plugin
import gitbucket.core.controller.Context
import gitbucket.core.service.RepositoryService.RepositoryInfo
trait SuggestionProvider {
val id: String
val prefix: String
val suffix: String = " "
val context: Seq[String]
def values(repository: RepositoryInfo): Seq[String]
def template(implicit context: Context): String = "value"
def additionalScript(implicit context: Context): String = ""
}
class UserNameSuggestionProvider extends SuggestionProvider {
override val id: String = "user"
override val prefix: String = "@"
override val context: Seq[String] = Seq("issues")
override def values(repository: RepositoryInfo): Seq[String] = Nil
override def template(implicit context: Context): String = "'@' + value"
override def additionalScript(implicit context: Context): String =
s"""$$.get('${context.path}/_user/proposals', { query: '' }, function (data) { user = data.options; });"""
}

View File

@@ -0,0 +1,10 @@
package gitbucket.core.plugin
import gitbucket.core.controller.Context
import gitbucket.core.service.RepositoryService.RepositoryInfo
trait TextDecorator {
def decorate(text: String, repository: RepositoryInfo)(implicit context: Context): String
}

View File

@@ -186,7 +186,7 @@ trait AccountService {
def getGroupNames(userName: String)(implicit s: Session): List[String] = {
List(userName) ++
Collaborators.filter(_.collaboratorName === userName.bind).sortBy(_.userName).map(_.userName).list
Collaborators.filter(_.collaboratorName === userName.bind).sortBy(_.userName).map(_.userName).list.distinct
}
}

View File

@@ -518,50 +518,6 @@ object IssuesService {
if(value == null || value.isEmpty || (allow.nonEmpty && !allow.contains(value))) None else Some(value)
}
/**
* Restores IssueSearchCondition instance from filter query.
*/
def apply(filter: String, milestones: Map[String, Int]): IssueSearchCondition = {
val conditions = filter.split("[  \t]+").flatMap { x =>
x.split(":") match {
case Array(key, value) => Some((key, value))
case _ => None
}
}.groupBy(_._1).map { case (key, values) =>
key -> values.map(_._2).toSeq
}
val (sort, direction) = conditions.get("sort").flatMap(_.headOption).getOrElse("created-desc") match {
case "created-asc" => ("created" , "asc" )
case "comments-desc" => ("comments", "desc")
case "comments-asc" => ("comments", "asc" )
case "updated-desc" => ("comments", "desc")
case "updated-asc" => ("comments", "asc" )
case _ => ("created" , "desc")
}
IssueSearchCondition(
conditions.get("label").map(_.toSet).getOrElse(Set.empty),
conditions.get("milestone").flatMap(_.headOption) match {
case None => None
case Some("none") => Some(None)
case Some(x) => Some(Some(x))
},
conditions.get("author").flatMap(_.headOption),
conditions.get("assignee").flatMap(_.headOption) match {
case None => None
case Some("none") => Some(None)
case Some(x) => Some(Some(x))
},
conditions.get("mentions").flatMap(_.headOption),
conditions.get("is").getOrElse(Seq.empty).find(x => x == "open" || x == "closed").getOrElse("open"),
sort,
direction,
conditions.get("visibility").flatMap(_.headOption),
conditions.get("group").map(_.toSet).getOrElse(Set.empty)
)
}
/**
* Restores IssueSearchCondition instance from request parameters.
*/

View File

@@ -41,7 +41,7 @@ trait MilestonesService {
def getMilestonesWithIssueCount(owner: String, repository: String)(implicit s: Session): List[(Milestone, Int, Int)] = {
val counts = Issues
.filter { t => (t.byRepository(owner, repository)) && (t.milestoneId.? isDefined) }
.filter { t => t.byRepository(owner, repository) && (t.milestoneId.? isDefined) }
.groupBy { t => t.milestoneId -> t.closed }
.map { case (t1, t2) => t1._1 -> t1._2 -> t2.length }
.toMap
@@ -52,6 +52,6 @@ trait MilestonesService {
}
def getMilestones(owner: String, repository: String)(implicit s: Session): List[Milestone] =
Milestones.filter(_.byRepository(owner, repository)).sortBy(_.milestoneId asc).list
Milestones.filter(_.byRepository(owner, repository)).sortBy(t => (t.dueDate.asc, t.closedDate.desc, t.milestoneId.desc)).list
}

View File

@@ -420,6 +420,17 @@ object RepositoryService {
def httpUrl(implicit context: Context): String = RepositoryService.httpUrl(owner, name)
def sshUrl(implicit context: Context): Option[String] = RepositoryService.sshUrl(owner, name)
def splitPath(path: String): (String, String) = {
val id = branchList.collectFirst {
case branch if(path == branch || path.startsWith(branch + "/")) => branch
} orElse tags.collectFirst {
case tag if(path == tag.name || path.startsWith(tag.name + "/")) => tag.name
} getOrElse path.split("/")(0)
(id, path.substring(id.length).stripPrefix("/"))
}
}
def httpUrl(owner: String, name: String)(implicit context: Context): String = s"${context.baseUrl}/git/${owner}/${name}.git"

View File

@@ -73,7 +73,7 @@ trait SystemSettingsService {
getValue(props, AllowAccountRegistration, false),
getValue(props, AllowAnonymousAccess, true),
getValue(props, IsCreateRepoOptionPublic, true),
getValue(props, Gravatar, true),
getValue(props, Gravatar, false),
getValue(props, Notification, false),
getOptionValue[Int](props, ActivityLogLimit, None),
getValue(props, Ssh, false),

View File

@@ -1,6 +1,5 @@
package gitbucket.core.service
import java.io.ByteArrayInputStream
import fr.brouillard.oss.security.xhub.XHub
import fr.brouillard.oss.security.xhub.XHub.{XHubDigest, XHubConverter}
import gitbucket.core.api._
@@ -22,6 +21,7 @@ import org.apache.http.HttpRequest
import org.apache.http.HttpResponse
import gitbucket.core.model.WebHookContentType
import org.apache.http.client.entity.EntityBuilder
import org.apache.http.entity.ContentType
trait WebHookService {
@@ -118,7 +118,7 @@ trait WebHookService {
}
}
case WebHookContentType.JSON => {
httpPost.setEntity(EntityBuilder.create().setText(json).build())
httpPost.setEntity(EntityBuilder.create().setContentType(ContentType.APPLICATION_JSON).setText(json).build())
if (webHook.token.exists(_.trim.nonEmpty)) {
httpPost.addHeader("X-Hub-Signature", XHub.generateHeaderXHubToken(XHubConverter.HEXA_LOWERCASE, XHubDigest.SHA1, webHook.token.orNull, json.getBytes("UTF-8")))
}

View File

@@ -4,15 +4,14 @@ import javax.servlet._
import javax.servlet.http.{HttpServletRequest, HttpServletResponse}
import gitbucket.core.model.Account
import gitbucket.core.service.AccessTokenService
import gitbucket.core.util.Keys
import gitbucket.core.service.SystemSettingsService.SystemSettings
import gitbucket.core.service.{AccessTokenService, AccountService, SystemSettingsService}
import gitbucket.core.util.{AuthUtil, Keys}
import org.scalatra.servlet.ServletApiImplicits._
import org.scalatra._
class AccessTokenAuthenticationFilter extends Filter with AccessTokenService {
private val tokenHeaderPrefix = "token "
class ApiAuthenticationFilter extends Filter with AccessTokenService with AccountService with SystemSettingsService {
override def init(filterConfig: FilterConfig): Unit = {}
@@ -23,9 +22,9 @@ class AccessTokenAuthenticationFilter extends Filter with AccessTokenService {
implicit val session = req.getAttribute(Keys.Request.DBSession).asInstanceOf[slick.jdbc.JdbcBackend#Session]
val response = res.asInstanceOf[HttpServletResponse]
Option(request.getHeader("Authorization")).map{
case auth if auth.startsWith("token ") => AccessTokenService.getAccountByAccessToken(auth.substring(6).trim).toRight(Unit)
// TODO Basic Authentication Support
case _ => Left(Unit)
case auth if auth.startsWith("token ") => AccessTokenService.getAccountByAccessToken(auth.substring(6).trim).toRight(())
case auth if auth.startsWith("Basic ") => doBasicAuth(auth, loadSystemSettings(), request).toRight(())
case _ => Left(())
}.orElse{
Option(request.getSession.getAttribute(Keys.Session.LoginAccount).asInstanceOf[Account]).map(Right(_))
} match {
@@ -40,4 +39,10 @@ class AccessTokenAuthenticationFilter extends Filter with AccessTokenService {
}
}
}
def doBasicAuth(auth: String, settings: SystemSettings, request: HttpServletRequest): Option[Account] = {
implicit val session = request.getAttribute(Keys.Request.DBSession).asInstanceOf[slick.jdbc.JdbcBackend#Session]
val Array(username, password) = AuthUtil.decodeAuthHeader(auth).split(":", 2)
authenticate(settings, username, password)
}
}

View File

@@ -5,16 +5,16 @@ import javax.servlet.http._
import gitbucket.core.plugin.{GitRepositoryFilter, GitRepositoryRouting, PluginRegistry}
import gitbucket.core.service.SystemSettingsService.SystemSettings
import gitbucket.core.service.{RepositoryService, AccountService, SystemSettingsService}
import gitbucket.core.util.{Keys, Implicits}
import gitbucket.core.util.{Keys, Implicits, AuthUtil}
import org.slf4j.LoggerFactory
import Implicits._
/**
* Provides BASIC Authentication for [[GitRepositoryServlet]].
*/
class BasicAuthenticationFilter extends Filter with RepositoryService with AccountService with SystemSettingsService {
class GitAuthenticationFilter extends Filter with RepositoryService with AccountService with SystemSettingsService {
private val logger = LoggerFactory.getLogger(classOf[BasicAuthenticationFilter])
private val logger = LoggerFactory.getLogger(classOf[GitAuthenticationFilter])
def init(config: FilterConfig) = {}
@@ -43,7 +43,7 @@ class BasicAuthenticationFilter extends Filter with RepositoryService with Accou
} catch {
case ex: Exception => {
logger.error("error", ex)
requireAuth(response)
AuthUtil.requireAuth(response)
}
}
}
@@ -54,7 +54,7 @@ class BasicAuthenticationFilter extends Filter with RepositoryService with Accou
val account = for {
auth <- Option(request.getHeader("Authorization"))
Array(username, password) = decodeAuthHeader(auth).split(":", 2)
Array(username, password) = AuthUtil.decodeAuthHeader(auth).split(":", 2)
account <- authenticate(settings, username, password)
} yield {
request.setAttribute(Keys.Request.UserName, account.userName)
@@ -64,7 +64,7 @@ class BasicAuthenticationFilter extends Filter with RepositoryService with Accou
if(filter.filter(request.gitRepositoryPath, account.map(_.userName), settings, isUpdating)){
chain.doFilter(request, response)
} else {
requireAuth(response)
AuthUtil.requireAuth(response)
}
}
@@ -81,7 +81,7 @@ class BasicAuthenticationFilter extends Filter with RepositoryService with Accou
} else {
val passed = for {
auth <- Option(request.getHeader("Authorization"))
Array(username, password) = decodeAuthHeader(auth).split(":", 2)
Array(username, password) = AuthUtil.decodeAuthHeader(auth).split(":", 2)
account <- authenticate(settings, username, password)
} yield if(isUpdating || repository.repository.isPrivate){
if(hasWritePermission(repository.owner, repository.name, Some(account))){
@@ -93,7 +93,7 @@ class BasicAuthenticationFilter extends Filter with RepositoryService with Accou
if(passed.getOrElse(false)){
chain.doFilter(request, response)
} else {
requireAuth(response)
AuthUtil.requireAuth(response)
}
}
}
@@ -108,17 +108,4 @@ class BasicAuthenticationFilter extends Filter with RepositoryService with Accou
}
}
}
private def requireAuth(response: HttpServletResponse): Unit = {
response.setHeader("WWW-Authenticate", "BASIC realm=\"GitBucket\"")
response.sendError(HttpServletResponse.SC_UNAUTHORIZED)
}
private def decodeAuthHeader(header: String): String = {
try {
new String(new sun.misc.BASE64Decoder().decodeBuffer(header.substring(6)))
} catch {
case _: Throwable => ""
}
}
}

View File

@@ -27,7 +27,7 @@ import javax.servlet.http.{HttpServletResponse, HttpServletRequest}
* Provides Git repository via HTTP.
*
* This servlet provides only Git repository functionality.
* Authentication is provided by [[BasicAuthenticationFilter]].
* Authentication is provided by [[GitAuthenticationFilter]].
*/
class GitRepositoryServlet extends GitServlet with SystemSettingsService {

View File

@@ -17,6 +17,7 @@ import org.apache.commons.io.FileUtils
import org.slf4j.LoggerFactory
import akka.actor.{Actor, Props, ActorSystem}
import com.typesafe.akka.extension.quartz.QuartzSchedulerExtension
import scala.collection.JavaConverters._
/**
* Initialize GitBucket system.
@@ -35,6 +36,7 @@ class InitializeListener extends ServletContextListener with SystemSettingsServi
Database() withTransaction { session =>
val conn = session.conn
val manager = new JDBCVersionManager(conn)
// Check version
val versionFile = new File(GitBucketHome, "version")
@@ -56,9 +58,8 @@ class InitializeListener extends ServletContextListener with SystemSettingsServi
}
// Change form
val manager = new JDBCVersionManager(conn)
manager.initialize()
manager.updateVersion(GitBucketCoreModule.getModuleId, "4.0")
manager.updateVersion(GitBucketCoreModule.getModuleId, "4.0.0")
conn.select("SELECT PLUGIN_ID, VERSION FROM PLUGIN"){ rs =>
manager.updateVersion(rs.getString("PLUGIN_ID"), rs.getString("VERSION"))
}
@@ -77,6 +78,19 @@ class InitializeListener extends ServletContextListener with SystemSettingsServi
val solidbase = new Solidbase()
solidbase.migrate(conn, Thread.currentThread.getContextClassLoader, DatabaseConfig.liquiDriver, GitBucketCoreModule)
// Rescue code for users who updated from 3.14 to 4.0.0
// https://github.com/gitbucket/gitbucket/issues/1227
val currentVersion = manager.getCurrentVersion(GitBucketCoreModule.getModuleId)
val databaseVersion = if(currentVersion == "4.0"){
manager.updateVersion(GitBucketCoreModule.getModuleId, "4.0.0")
"4.0.0"
} else currentVersion
val gitbucketVersion = GitBucketCoreModule.getVersions.asScala.last.getVersion
if(databaseVersion != gitbucketVersion){
throw new IllegalStateException(s"Initialization failed. GitBucket version is ${gitbucketVersion}, but database version is ${databaseVersion}.")
}
// Load plugins
logger.info("Initialize plugins")
PluginRegistry.initialize(event.getServletContext, loadSystemSettings(), conn)

View File

@@ -0,0 +1,39 @@
package gitbucket.core.servlet
import javax.servlet.http.{HttpServlet, HttpServletRequest, HttpServletResponse}
import gitbucket.core.plugin.PluginRegistry
import gitbucket.core.util.FileUtil
import org.apache.commons.io.IOUtils
/**
* Supply assets which are provided by plugins.
*/
class PluginAssetsServlet extends HttpServlet {
override def doGet(req: HttpServletRequest, resp: HttpServletResponse): Unit = {
val assetsMappings = PluginRegistry().getAssetsMappings
val path = req.getRequestURI.substring(req.getContextPath.length)
assetsMappings
.find { case (prefix, _, _) => path.startsWith("/plugin-assets" + prefix) }
.flatMap { case (prefix, resourcePath, classLoader) =>
val resourceName = path.substring(("/plugin-assets" + prefix).length)
Option(classLoader.getResourceAsStream(resourcePath.replaceFirst("^/", "") + resourceName))
}
.map { in =>
try {
val bytes = IOUtils.toByteArray(in)
resp.setContentLength(bytes.length)
resp.setContentType(FileUtil.getContentType(path, bytes))
resp.getOutputStream.write(bytes)
} finally {
in.close()
}
}
.getOrElse {
resp.setStatus(404)
}
}
}

View File

@@ -52,6 +52,13 @@ object Database {
config.setJdbcUrl(DatabaseConfig.url)
config.setUsername(DatabaseConfig.user)
config.setPassword(DatabaseConfig.password)
config.setAutoCommit(false)
DatabaseConfig.connectionTimeout.foreach(config.setConnectionTimeout)
DatabaseConfig.idleTimeout.foreach(config.setIdleTimeout)
DatabaseConfig.maxLifetime.foreach(config.setMaxLifetime)
DatabaseConfig.minimumIdle.foreach(config.setMinimumIdle)
DatabaseConfig.maximumPoolSize.foreach(config.setMaximumPoolSize)
logger.debug("load database connection pool")
new HikariDataSource(config)
}

View File

@@ -13,7 +13,7 @@ import ControlUtil._
import org.eclipse.jgit.api.Git
import Directory._
import org.eclipse.jgit.transport.{ReceivePack, UploadPack}
import org.apache.sshd.server.command.UnknownCommand
import org.apache.sshd.server.scp.UnknownCommand
import org.eclipse.jgit.errors.RepositoryNotFoundException
object GitCommand {

View File

@@ -7,12 +7,12 @@ import gitbucket.core.service.SshKeyService
import gitbucket.core.servlet.Database
import org.apache.sshd.server.auth.pubkey.PublickeyAuthenticator
import org.apache.sshd.server.session.ServerSession
import org.apache.sshd.common.session.Session
import org.apache.sshd.common.AttributeStore
import org.slf4j.LoggerFactory
object PublicKeyAuthenticator {
// put in the ServerSession here to be read by GitCommand later
private val userNameSessionKey = new Session.AttributeKey[String]
private val userNameSessionKey = new AttributeStore.AttributeKey[String]
def putUserName(serverSession:ServerSession, userName:String):Unit =
serverSession.setAttribute(userNameSessionKey, userName)

View File

@@ -0,0 +1,21 @@
package gitbucket.core.util
import javax.servlet.http.HttpServletResponse
/**
* Provides HTTP (Basic) Authentication related functions.
*/
object AuthUtil {
def requireAuth(response: HttpServletResponse): Unit = {
response.setHeader("WWW-Authenticate", "BASIC realm=\"GitBucket\"")
response.sendError(HttpServletResponse.SC_UNAUTHORIZED)
}
def decodeAuthHeader(header: String): String = {
try {
new String(new sun.misc.BASE64Decoder().decodeBuffer(header.substring(6)))
} catch {
case _: Throwable => ""
}
}
}

View File

@@ -17,6 +17,11 @@ object DatabaseConfig {
| url = "jdbc:h2:${DatabaseHome};MVCC=true"
| user = "sa"
| password = "sa"
|# connectionTimeout = 30000
|# idleTimeout = 600000
|# maxLifetime = 1800000
|# minimumIdle = 10
|# maximumPoolSize = 10
|}
|""".stripMargin, "UTF-8")
}
@@ -28,12 +33,21 @@ object DatabaseConfig {
def url(directory: Option[String]): String =
dbUrl.replace("${DatabaseHome}", directory.getOrElse(DatabaseHome))
lazy val url: String = url(None)
lazy val user: String = config.getString("db.user")
lazy val password: String = config.getString("db.password")
lazy val jdbcDriver: String = DatabaseType(url).jdbcDriver
lazy val slickDriver: slick.driver.JdbcProfile = DatabaseType(url).slickDriver
lazy val liquiDriver: AbstractJdbcDatabase = DatabaseType(url).liquiDriver
lazy val url : String = url(None)
lazy val user : String = config.getString("db.user")
lazy val password : String = config.getString("db.password")
lazy val jdbcDriver : String = DatabaseType(url).jdbcDriver
lazy val slickDriver : slick.driver.JdbcProfile = DatabaseType(url).slickDriver
lazy val liquiDriver : AbstractJdbcDatabase = DatabaseType(url).liquiDriver
lazy val connectionTimeout : Option[Long] = getOptionValue("db.connectionTimeout", config.getLong)
lazy val idleTimeout : Option[Long] = getOptionValue("db.idleTimeout" , config.getLong)
lazy val maxLifetime : Option[Long] = getOptionValue("db.maxLifetime" , config.getLong)
lazy val minimumIdle : Option[Int] = getOptionValue("db.minimumIdle" , config.getInt)
lazy val maximumPoolSize : Option[Int] = getOptionValue("db.maximumPoolSize" , config.getInt)
private def getOptionValue[T](path: String, f: String => T): Option[T] = {
if(config.hasPath(path)) Some(f(path)) else None
}
}

View File

@@ -4,6 +4,8 @@ import gitbucket.core.api.JsonFormat
import gitbucket.core.controller.Context
import gitbucket.core.servlet.Database
import java.util.regex.Pattern.quote
import javax.servlet.http.{HttpSession, HttpServletRequest}
import scala.util.matching.Regex
@@ -73,7 +75,7 @@ object Implicits {
def hasAttribute(name: String): Boolean = request.getAttribute(name) != null
def gitRepositoryPath: String = request.getRequestURI.replaceFirst("^/git/", "/")
def gitRepositoryPath: String = request.getRequestURI.replaceFirst("^" + quote(request.getContextPath) + "/git/", "/")
def baseUrl:String = {
val url = request.getRequestURL.toString
@@ -83,12 +85,6 @@ object Implicits {
}
implicit class RichSession(session: HttpSession){
def putAndGet[T](key: String, value: T): T = {
session.setAttribute(key, value)
value
}
def getAndRemove[T](key: String): Option[T] = {
val value = session.getAttribute(key).asInstanceOf[T]
if(value == null){

View File

@@ -3,11 +3,8 @@ package gitbucket.core.util
import java.io._
import java.sql._
import java.text.SimpleDateFormat
import javax.xml.stream.{XMLStreamConstants, XMLInputFactory, XMLOutputFactory}
import ControlUtil._
import scala.StringBuilder
import scala.annotation.tailrec
import scala.collection.mutable
import scala.collection.mutable.ListBuffer
/**
@@ -64,65 +61,34 @@ object JDBCUtil {
}
}
def importAsXML(in: InputStream): Unit = {
def importAsSQL(in: InputStream): Unit = {
conn.setAutoCommit(false)
try {
val factory = XMLInputFactory.newInstance()
using(factory.createXMLStreamReader(in, "UTF-8")){ reader =>
// stateful objects
var elementName = ""
var insertTable = ""
var insertColumns = Map.empty[String, (String, String)]
using(in){ in =>
var out = new ByteArrayOutputStream()
while(reader.hasNext){
reader.next()
var length = 0
val bytes = new scala.Array[Byte](1024 * 8)
var stringLiteral = false
reader.getEventType match {
case XMLStreamConstants.START_ELEMENT =>
elementName = reader.getName.getLocalPart
if(elementName == "insert"){
insertTable = reader.getAttributeValue(null, "table")
} else if(elementName == "delete"){
val tableName = reader.getAttributeValue(null, "table")
conn.update(s"DELETE FROM ${tableName}")
} else if(elementName == "column"){
val columnName = reader.getAttributeValue(null, "name")
val columnType = reader.getAttributeValue(null, "type")
val columnValue = reader.getElementText
insertColumns = insertColumns + (columnName -> (columnType, columnValue))
}
case XMLStreamConstants.END_ELEMENT =>
// Execute insert statement
reader.getName.getLocalPart match {
case "insert" => {
val sb = new StringBuilder()
sb.append(s"INSERT INTO ${insertTable} (")
sb.append(insertColumns.map { case (columnName, _) => columnName }.mkString(", "))
sb.append(") VALUES (")
sb.append(insertColumns.map { case (_, (columnType, columnValue)) =>
if(columnType == null || columnValue == null){
"NULL"
} else if(columnType == "string"){
"'" + columnValue.replace("'", "''") + "'"
} else if(columnType == "timestamp"){
"'" + columnValue + "'"
} else {
columnValue.toString
}
}.mkString(", "))
sb.append(")")
var count = 0
conn.update(sb.toString)
insertColumns = Map.empty[String, (String, String)] // Clear column information
}
case _ => // Nothing to do
}
case _ => // Nothing to do
while({ length = in.read(bytes); length != -1 }){
for(i <- 0 to length - 1){
val c = bytes(i)
if(c == '\''){
stringLiteral = !stringLiteral
}
if(c == ';' && !stringLiteral){
val sql = new String(out.toByteArray, "UTF-8")
conn.update(sql)
out = new ByteArrayOutputStream()
} else {
out.write(c)
}
}
}
}
conn.commit()
} catch {
@@ -133,68 +99,6 @@ object JDBCUtil {
}
}
def exportAsXML(targetTables: Seq[String]): File = {
val dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss")
val file = File.createTempFile("gitbucket-export-", ".xml")
val factory = XMLOutputFactory.newInstance()
using(factory.createXMLStreamWriter(new FileOutputStream(file), "UTF-8")){ writer =>
val dbMeta = conn.getMetaData
val allTablesInDatabase = allTablesOrderByDependencies(dbMeta)
writer.writeStartDocument("UTF-8", "1.0")
writer.writeStartElement("tables")
println(allTablesInDatabase.mkString(", "))
allTablesInDatabase.reverse.foreach { tableName =>
if (targetTables.contains(tableName)) {
writer.writeStartElement("delete")
writer.writeAttribute("table", tableName)
writer.writeEndElement()
}
}
allTablesInDatabase.foreach { tableName =>
if (targetTables.contains(tableName)) {
select(s"SELECT * FROM ${tableName}") { rs =>
writer.writeStartElement("insert")
writer.writeAttribute("table", tableName)
val rsMeta = rs.getMetaData
(1 to rsMeta.getColumnCount).foreach { i =>
val columnName = rsMeta.getColumnName(i)
val (columnType, columnValue) = if(rs.getObject(columnName) == null){
(null, null)
} else {
rsMeta.getColumnType(i) match {
case Types.BOOLEAN | Types.BIT => ("boolean", rs.getBoolean(columnName))
case Types.VARCHAR | Types.CLOB | Types.CHAR | Types.LONGVARCHAR => ("string", rs.getString(columnName))
case Types.INTEGER => ("int", rs.getInt(columnName))
case Types.TIMESTAMP => ("timestamp", dateFormat.format(rs.getTimestamp(columnName)))
}
}
writer.writeStartElement("column")
writer.writeAttribute("name", columnName)
if(columnType != null){
writer.writeAttribute("type", columnType)
}
if(columnValue != null){
writer.writeCharacters(columnValue.toString)
}
writer.writeEndElement()
}
writer.writeEndElement()
}
}
}
writer.writeEndElement()
writer.writeEndDocument()
}
file
}
def exportAsSQL(targetTables: Seq[String]): File = {
val dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss")
val file = File.createTempFile("gitbucket-export-", ".sql")

View File

@@ -37,7 +37,7 @@ trait LinkConverter { self: RequestCache =>
// convert username/project@SHA to link
.replaceBy("(?<=(^|\\W))([a-zA-Z0-9\\-_]+)/([a-zA-Z0-9\\-_\\.]+)@([a-f0-9]{40})(?=(\\W|$))".r){ m =>
getAccountByUserName(m.group(2)).map { _ =>
s"""<a href="${context.path}/${m.group(2)}/${m.group(3)}/commit/${m.group(4)}">${m.group(2)}/${m.group(3)}@${m.group(4).substring(0, 7)}</a>"""
s"""<code><a href="${context.path}/${m.group(2)}/${m.group(3)}/commit/${m.group(4)}">${m.group(2)}/${m.group(3)}@${m.group(4).substring(0, 7)}</a></code>"""
}
}
@@ -56,7 +56,7 @@ trait LinkConverter { self: RequestCache =>
// convert username@SHA to link
.replaceBy( ("(?<=(^|\\W))([a-zA-Z0-9\\-_]+)@([a-f0-9]{40})(?=(\\W|$))").r ) { m =>
getAccountByUserName(m.group(2)).map { _ =>
s"""<a href="${context.path}/${m.group(2)}/${repository.name}/commit/${m.group(3)}">${m.group(2)}@${m.group(3).substring(0, 7)}</a>"""
s"""<code><a href="${context.path}/${m.group(2)}/${repository.name}/commit/${m.group(3)}">${m.group(2)}@${m.group(3).substring(0, 7)}</a></code>"""
}
}
@@ -93,6 +93,8 @@ trait LinkConverter { self: RequestCache =>
}
// convert commit id to link
.replaceAll("(?<=(^|[^\\w/@]))([a-f0-9]{40})(?=(\\W|$))", s"""<a href="${context.path}/${repository.owner}/${repository.name}/commit/$$2">$$2</a>""")
.replaceBy("(?<=(^|[^\\w/@]))([a-f0-9]{40})(?=(\\W|$))".r){ m =>
Some(s"""<code><a href="${context.path}/${repository.owner}/${repository.name}/commit/${m.group(2)}">${m.group(2).substring(0, 7)}</a></code>""")
}
}
}

View File

@@ -44,7 +44,7 @@ object Markdown {
val renderer = new GitBucketMarkedRenderer(options, repository,
enableWikiLink, enableRefsLink, enableAnchor, enableTaskList, hasWritePermission, pages)
Marked.marked(source, options, renderer)
helpers.decorateHtml(Marked.marked(source, options, renderer), repository)
}
/**
@@ -147,21 +147,23 @@ object Markdown {
}
private def fixUrl(url: String, isImage: Boolean = false): String = {
lazy val urlWithRawParam: String = url + (if(isImage && !url.endsWith("?raw=true")) "?raw=true" else "")
if(url.startsWith("http://") || url.startsWith("https://") || url.startsWith("/")){
url
} else if(url.startsWith("#")){
("#" + generateAnchorName(url.substring(1)))
} else if(!enableWikiLink){
if(context.currentPath.contains("/blob/")){
url + (if(isImage) "?raw=true" else "")
urlWithRawParam
} else if(context.currentPath.contains("/tree/")){
val paths = context.currentPath.split("/")
val branch = if(paths.length > 3) paths.drop(4).mkString("/") else repository.repository.defaultBranch
repository.httpUrl.replaceFirst("/git/", "/").stripSuffix(".git") + "/blob/" + branch + "/" + url + (if(isImage) "?raw=true" else "")
repository.httpUrl.replaceFirst("/git/", "/").stripSuffix(".git") + "/blob/" + branch + "/" + urlWithRawParam
} else {
val paths = context.currentPath.split("/")
val branch = if(paths.length > 3) paths.last else repository.repository.defaultBranch
repository.httpUrl.replaceFirst("/git/", "/").stripSuffix(".git") + "/blob/" + branch + "/" + url + (if(isImage) "?raw=true" else "")
repository.httpUrl.replaceFirst("/git/", "/").stripSuffix(".git") + "/blob/" + branch + "/" + urlWithRawParam
}
} else {
repository.httpUrl.replaceFirst("/git/", "/").stripSuffix(".git") + "/wiki/_blob/" + url

View File

@@ -5,10 +5,10 @@ import java.util.{Date, Locale, TimeZone}
import gitbucket.core.controller.Context
import gitbucket.core.model.CommitState
import gitbucket.core.plugin.{RenderRequest, PluginRegistry}
import gitbucket.core.plugin.{PluginRegistry, RenderRequest}
import gitbucket.core.service.RepositoryService.RepositoryInfo
import gitbucket.core.service.{RepositoryService, RequestCache}
import gitbucket.core.util.{FileUtil, JGitUtil, StringUtil}
import play.twirl.api.{Html, HtmlFormat}
/**
@@ -151,7 +151,7 @@ object helpers extends AvatarImageProvider with LinkConverter with RequestCache
* Converts commit id, issue id and username to the link.
*/
def link(value: String, repository: RepositoryService.RepositoryInfo)(implicit context: Context): Html =
Html(convertRefsLinks(value, repository))
Html(decorateHtml(convertRefsLinks(value, repository), repository))
def cut(value: String, length: Int): String =
if(value.length > length){
@@ -316,10 +316,18 @@ object helpers extends AvatarImageProvider with LinkConverter with RequestCache
case CommitState.FAILURE => "Failed"
}
/**
* Render a given object as the JSON string.
*/
def json(obj: AnyRef): String = {
implicit val formats = org.json4s.DefaultFormats
org.json4s.jackson.Serialization.write(obj)
}
// 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 = {
def detectAndRenderLinks(text: String, repository: RepositoryInfo)(implicit context: Context): String = {
val matches = detectAndRenderLinksRegex.findAllMatchIn(text).toSeq
val (x, pos) = matches.foldLeft((collection.immutable.Seq.empty[Html], 0)){ case ((x, pos), m) =>
@@ -333,6 +341,43 @@ object helpers extends AvatarImageProvider with LinkConverter with RequestCache
// append rest fragment
val out = if (pos < text.length) x :+ HtmlFormat.escape(text.substring(pos)) else x
HtmlFormat.fill(out)
decorateHtml(HtmlFormat.fill(out).toString, repository)
}
def decorateHtml(html: String, repository: RepositoryInfo)(implicit context: Context): String = {
PluginRegistry().getTextDecorators.foldLeft(html){ case (html, decorator) =>
val text = new StringBuilder()
val result = new StringBuilder()
var tag = false
html.foreach { c =>
c match {
case '<' if tag == false => {
tag = true
if(text.nonEmpty){
result.append(decorator.decorate(text.toString, repository))
text.setLength(0)
}
result.append(c)
}
case '>' if tag == true => {
tag = false
result.append(c)
}
case _ if tag == false => {
text.append(c)
}
case _ if tag == true => {
result.append(c)
}
}
}
if(text.nonEmpty){
result.append(decorator.decorate(text.toString, repository))
}
result.toString
}
}
}

View File

@@ -1,11 +1,10 @@
@(account: gitbucket.core.model.Account,
groupNames: List[String],
activities: List[gitbucket.core.model.Activity])(implicit context: gitbucket.core.controller.Context)
@import context._
@import gitbucket.core.view.helpers._
@main(account, groupNames, "activity"){
@import gitbucket.core.view.helpers
@gitbucket.core.account.html.main(account, groupNames, "activity"){
<div class="pull-right">
<a href="@path/@{account.userName}.atom"><img src="@assets/common/images/feed.png" alt="activities"></a>
<a href="@context.path/@{account.userName}.atom"><img src="@{helpers.assets}/common/images/feed.png" alt="activities"></a>
</div>
@helper.html.activities(activities)
@gitbucket.core.helper.html.activities(activities)
}

View File

@@ -1,11 +1,9 @@
@(account: gitbucket.core.model.Account,
personalTokens: List[gitbucket.core.model.AccessToken],
gneratedToken: Option[(gitbucket.core.model.AccessToken, String)])(implicit context: gitbucket.core.controller.Context)
@import context._
@import gitbucket.core.view.helpers._
@html.main("Applications"){
@gitbucket.core.html.main("Applications"){
<div class="container body">
@menu("application", settings.ssh){
@gitbucket.core.account.html.menu("application", context.settings.ssh){
<div class="panel panel-default">
<div class="panel-heading strong">Personal access tokens</div>
<div class="panel-body">
@@ -15,13 +13,13 @@
Tokens you have generated that can be used to access the GitBucket API.
<hr style="margin-top: 10px;">
}
@gneratedToken.map{ case (token, tokenString) =>
@gneratedToken.map { case (token, tokenString) =>
<div class="alert alert-info">
Make sure to copy your new personal access token now. You won't be able to see it again!
</div>
<a href="@path/@account.userName/_personalToken/delete/@token.accessTokenId" class="btn btn-sm btn-danger pull-right">Delete</a>
<a href="@context.path/@account.userName/_personalToken/delete/@token.accessTokenId" class="btn btn-sm btn-danger pull-right">Delete</a>
<div style="width: 50%;">
@helper.html.copy("generated-token-copy", tokenString){
@gitbucket.core.helper.html.copy("generated-token-copy", tokenString){
<input type="text" value="@tokenString" class="form-control input-sm" readonly>
}
</div>
@@ -32,11 +30,11 @@
<hr>
}
<strong style="line-height: 30px;">@token.note</strong>
<a href="@path/@account.userName/_personalToken/delete/@token.accessTokenId" class="btn btn-sm btn-danger pull-right">Delete</a>
<a href="@context.path/@account.userName/_personalToken/delete/@token.accessTokenId" class="btn btn-sm btn-danger pull-right">Delete</a>
}
</div>
</div>
<form method="POST" action="@path/@account.userName/_personalToken" validate="true">
<form method="POST" action="@context.path/@account.userName/_personalToken" validate="true">
<div class="panel panel-default">
<div class="panel-heading strong">Generate new token</div>
<div class="panel-body">

View File

@@ -1,14 +1,13 @@
@(account: gitbucket.core.model.Account, info: Option[Any], error: Option[Any])(implicit context: gitbucket.core.controller.Context)
@import gitbucket.core.util.LDAPUtil
@import context._
@import gitbucket.core.view.helpers._
@html.main("Edit your profile"){
@import gitbucket.core.view.helpers
@gitbucket.core.html.main("Edit your profile"){
<div class="container body">
@menu("profile", settings.ssh){
@helper.html.information(info)
@helper.html.error(error)
@gitbucket.core.account.html.menu("profile", context.settings.ssh){
@gitbucket.core.helper.html.information(info)
@gitbucket.core.helper.html.error(error)
@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">
<form action="@helpers.url(account.userName)/_edit" method="POST" validate="true">
<div class="panel panel-default">
<div class="panel-heading strong">Profile</div>
<div class="panel-body">
@@ -42,7 +41,7 @@
<div class="col-md-6">
<fieldset class="form-group">
<label for="avatar" class="strong">Image (optional):</label>
@helper.html.uploadavatar(Some(account))
@gitbucket.core.helper.html.uploadavatar(Some(account))
</fieldset>
</div>
</div>
@@ -50,10 +49,10 @@
</div>
<div>
<div class="pull-right">
<a href="@path/@account.userName/_delete" class="btn btn-danger" id="delete">Delete account</a>
<a href="@context.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 btn-default">Cancel</a>}
@if(!LDAPUtil.isDummyMailAddress(account)){<a href="@helpers.url(account.userName)" class="btn btn-default">Cancel</a>}
</div>
</form>
}

View File

@@ -1,11 +1,10 @@
@(account: Option[gitbucket.core.model.Account], members: List[gitbucket.core.model.GroupMember])(implicit context: gitbucket.core.controller.Context)
@import context._
@import gitbucket.core.view.helpers._
@html.main(if(account.isEmpty) "Create group" else "Edit group"){
@import gitbucket.core.view.helpers
@gitbucket.core.html.main(if(account.isEmpty) "Create group" else "Edit group"){
<div class="content-wrapper main-center">
<div class="content body">
<h2>@{if(account.isEmpty) "Create group" else "Edit group"}</h2>
<form id="form" method="post" action="@if(account.isEmpty){@path/groups/new} else {@path/@account.get.userName/_editgroup}" validate="true">
<form id="form" method="post" action="@if(account.isEmpty){@context.path/groups/new} else {@context.path/@account.get.userName/_editgroup}" validate="true">
<div class="row">
<div class="col-md-5">
<fieldset class="form-group">
@@ -24,7 +23,7 @@
</fieldset>
<fieldset class="form-group">
<label for="avatar" class="strong">Image (Optional)</label>
@helper.html.uploadavatar(account)
@gitbucket.core.helper.html.uploadavatar(account)
</fieldset>
</div>
<div class="col-md-7">
@@ -32,7 +31,7 @@
<label class="strong">Members</label>
<ul id="member-list" class="collaborator">
</ul>
@helper.html.account("memberName", 200)
@gitbucket.core.helper.html.account("memberName", 200)
<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>
@@ -44,12 +43,12 @@
<fieldset class="border-top">
@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="@helpers.url(account.get.userName)/_deletegroup" id="delete" class="btn btn-danger">Delete Group</a>
</div>
}
<input type="submit" class="btn btn-success" value="@if(account.isEmpty){Create Group} else {Update Group}"/>
@if(account.isDefined){
<a href="@url(account.get.userName)" class="btn btn-default">Cancel</a>
<a href="@helpers.url(account.get.userName)" class="btn btn-default">Cancel</a>
}
</fieldset>
</form>
@@ -81,7 +80,7 @@ $(function(){
}
// check existence
$.post('@path/_user/existence', {
$.post('@context.path/_user/existence', {
'userName': userName
}, function(data, status){
if(data == 'true'){
@@ -125,7 +124,7 @@ $(function(){
.append(memberButton)
.append(managerButton))
.append(' ')
.append($('<a>').attr('href', '@path/' + userName).text(userName))
.append($('<a>').attr('href', '@context.path/' + userName).text(userName))
.append(' ')
.append($('<a href="#" class="remove pull-right">(remove)</a>')));
}

View File

@@ -1,12 +1,11 @@
@(account: gitbucket.core.model.Account, groupNames: List[String], active: String,
isGroupManager: Boolean = false)(body: Html)(implicit context: gitbucket.core.controller.Context)
@import context._
@import gitbucket.core.view.helpers._
@html.main(account.userName){
@import gitbucket.core.view.helpers
@gitbucket.core.html.main(account.userName){
<div class="main-sidebar">
<div class="sidebar">
<div class="user-panel">
<div class="pull-left image">@avatar(account.userName, 40)</div>
<div class="pull-left image">@helpers.avatar(account.userName, 40)</div>
<div class="pull-left info">
<p>@account.userName</p>
@account.fullName
@@ -19,44 +18,44 @@
</p>
}
<p style="color: white;">
<i class="octicon octicon-clock"></i> Joined on @date(account.registeredDate)
<i class="octicon octicon-clock"></i> Joined on @helpers.date(account.registeredDate)
</p>
</div>
@if(groupNames.nonEmpty){
<ul class="sidebar-menu">
<li class="header">Groups</li>
@groupNames.map { groupName =>
<li>@avatarLink(groupName, 20, tooltip = true, label = true)</li>
<li>@helpers.avatarLink(groupName, 20, tooltip = true, label = true)</li>
}
</div>
</ul>
}
</div>
</div>
<div class="content-wrapper">
<div class="content body">
<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>
<li@if(active == "repositories"){ class="active"}><a href="@helpers.url(account.userName)?tab=repositories">Repositories</a></li>
@if(account.isGroupAccount){
<li@if(active == "members"){ class="active"}><a href="@url(account.userName)?tab=members">Members</a></li>
<li@if(active == "members"){ class="active"}><a href="@helpers.url(account.userName)?tab=members">Members</a></li>
} else {
<li@if(active == "activity"){ class="active"}><a href="@url(account.userName)?tab=activity">Public Activity</a></li>
<li@if(active == "activity"){ class="active"}><a href="@helpers.url(account.userName)?tab=activity">Public Activity</a></li>
}
@gitbucket.core.plugin.PluginRegistry().getProfileTabs.map { tab =>
@tab(account, context).map { link =>
<li@if(active == link.id){ class="active"}><a href="@path/@link.path">@link.label</a></li>
<li@if(active == link.id){ class="active"}><a href="@context.path/@link.path">@link.label</a></li>
}
}
@if(loginAccount.isDefined && loginAccount.get.userName == account.userName){
@if(context.loginAccount.isDefined && context.loginAccount.get.userName == account.userName){
<li class="pull-right">
<div class="button-group">
<a href="@url(account.userName)/_edit" class="btn btn-default">Edit Your Profile</a>
<a href="@helpers.url(account.userName)/_edit" class="btn btn-default">Edit Your Profile</a>
</div>
</li>
}
@if(loginAccount.isDefined && account.isGroupAccount && isGroupManager){
@if(context.loginAccount.isDefined && account.isGroupAccount && isGroupManager){
<li class="pull-right">
<div class="button-group">
<a href="@url(account.userName)/_editgroup" class="btn btn-default">Edit Group</a>
<a href="@helpers.url(account.userName)/_editgroup" class="btn btn-default">Edit Group</a>
</div>
</li>
}

View File

@@ -1,14 +1,13 @@
@(account: gitbucket.core.model.Account, members: List[String], isGroupManager: Boolean)(implicit context: gitbucket.core.controller.Context)
@import context._
@import gitbucket.core.view.helpers._
@main(account, Nil, "members", isGroupManager){
@import gitbucket.core.view.helpers
@gitbucket.core.account.html.main(account, Nil, "members", isGroupManager){
@if(members.isEmpty){
No members
} else {
@members.map { userName =>
<div class="block">
<div class="block-header">
@avatar(userName, 20) <a href="@url(userName)">@userName</a>
@helpers.avatar(userName, 20) <a href="@helpers.url(userName)">@userName</a>
</div>
</div>
}

View File

@@ -1,23 +1,22 @@
@(active: String, ssh: Boolean)(body: Html)(implicit context: gitbucket.core.controller.Context)
@import context._
<div class="main-sidebar">
<div class="sidebar">
<ul class="sidebar-menu">
<li@if(active=="profile"){ class="active"}>
<a href="@path/@loginAccount.get.userName/_edit">Profile</a>
<a href="@context.path/@context.loginAccount.get.userName/_edit">Profile</a>
</li>
@if(ssh){
<li@if(active=="ssh"){ class="active"}>
<a href="@path/@loginAccount.get.userName/_ssh">SSH Keys</a>
<a href="@context.path/@context.loginAccount.get.userName/_ssh">SSH Keys</a>
</li>
}
<li@if(active=="application"){ class="active"}>
<a href="@path/@loginAccount.get.userName/_application">Applications</a>
<a href="@context.path/@context.loginAccount.get.userName/_application">Applications</a>
</li>
@gitbucket.core.plugin.PluginRegistry().getAccountSettingMenus.map { menu =>
@menu(context).map { link =>
<li@if(active==link.id){ class="active"}>
<a href="@path/@link.path">@link.label</a>
<a href="@context.path/@link.path">@link.label</a>
</li>
}
}

View File

@@ -1,31 +1,30 @@
@(groupNames: List[String],
isCreateRepoOptionPublic: Boolean)(implicit context: gitbucket.core.controller.Context)
@import context._
@import gitbucket.core.view.helpers._
@html.main("Create a New Repository"){
@import gitbucket.core.view.helpers
@gitbucket.core.html.main("Create a New Repository"){
<div class="content-wrapper main-center">
<div class="content body">
<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">
<form id="form" method="post" action="@context.path/new" validate="true">
<fieldset class="border-top 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="strong">@helpers.avatar(context.loginAccount.get.userName, 20) @context.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>
<li><a href="javascript:void(0);" data-name="@context.loginAccount.get.userName"><i class="octicon octicon-check"></i> <span>@helpers.avatar(context.loginAccount.get.userName, 20) @context.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>
<li><a href="javascript:void(0);" data-name="@groupName"><i class="octicon"></i> <span>@helpers.avatar(groupName, 20) @groupName</span></a></li>
}
</ul>
<input type="hidden" name="owner" id="owner" value="@loginAccount.get.userName"/>
<input type="hidden" name="owner" id="owner" value="@context.loginAccount.get.userName"/>
</div>
</dd>
</dl>

View File

@@ -1,11 +1,9 @@
@()(implicit context: gitbucket.core.controller.Context)
@import context._
@import gitbucket.core.view.helpers._
@html.main("Create your account"){
@gitbucket.core.html.main("Create your account"){
<div class="content-wrapper main-center">
<div class="content body">
<h2>Create your account</h2>
<form action="@path/register" method="POST" validate="true">
<form action="@context.path/register" method="POST" validate="true">
<div class="row">
<div class="col-md-6">
<fieldset>
@@ -39,7 +37,7 @@
<div class="col-md-6">
<fieldset>
<label for="avatar" class="strong">Image (optional):</label>
@helper.html.uploadavatar(None)
@gitbucket.core.helper.html.uploadavatar(None)
</fieldset>
</div>
</div>

View File

@@ -1,31 +1,30 @@
@(account: gitbucket.core.model.Account, groupNames: List[String],
repositories: List[gitbucket.core.service.RepositoryService.RepositoryInfo],
isGroupManager: Boolean)(implicit context: gitbucket.core.controller.Context)
@import context._
@import gitbucket.core.view.helpers._
@main(account, groupNames, "repositories", isGroupManager){
@import gitbucket.core.view.helpers
@gitbucket.core.account.html.main(account, groupNames, "repositories", isGroupManager){
@if(repositories.isEmpty){
No repositories
} else {
@repositories.map { repository =>
<div class="block">
<div class="repository-icon">
@helper.html.repositoryicon(repository, true)
@gitbucket.core.helper.html.repositoryicon(repository, true)
</div>
<div class="repository-content">
<div class="block-header">
<a href="@url(repository)">@repository.name</a>
<a href="@helpers.url(repository)">@repository.name</a>
@if(repository.repository.isPrivate){
<i class="octicon octicon-lock"></i>
}
</div>
@if(repository.repository.originUserName.isDefined){
<div class="small muted">forked from <a href="@path/@repository.repository.parentUserName/@repository.repository.parentRepositoryName">@repository.repository.parentUserName/@repository.repository.parentRepositoryName</a></div>
<div class="small muted">forked from <a href="@context.path/@repository.repository.parentUserName/@repository.repository.parentRepositoryName">@repository.repository.parentUserName/@repository.repository.parentRepositoryName</a></div>
}
@if(repository.repository.description.isDefined){
<div>@repository.repository.description</div>
}
<div><span class="muted small">Updated @helper.html.datetimeago(repository.repository.lastActivityDate)</span></div>
<div><span class="muted small">Updated @gitbucket.core.helper.html.datetimeago(repository.repository.lastActivityDate)</span></div>
</div>
</div>
}

View File

@@ -1,10 +1,8 @@
@(account: gitbucket.core.model.Account, sshKeys: List[gitbucket.core.model.SshKey])(implicit context: gitbucket.core.controller.Context)
@import gitbucket.core.ssh.SshUtil
@import context._
@import gitbucket.core.view.helpers._
@html.main("SSH Keys"){
@gitbucket.core.html.main("SSH Keys"){
<div class="container body">
@menu("ssh", settings.ssh){
@gitbucket.core.account.html.menu("ssh", context.settings.ssh){
<div class="panel panel-default">
<div class="panel-heading strong">SSH Keys</div>
<div class="panel-body">
@@ -16,11 +14,11 @@
<hr>
}
<strong style="line-height: 30px;">@key.title</strong> (@SshUtil.fingerPrint(key.publicKey).getOrElse("Key is invalid."))
<a href="@path/@account.userName/_ssh/delete/@key.sshKeyId" class="btn btn-sm btn-danger pull-right">Delete</a>
<a href="@context.path/@account.userName/_ssh/delete/@key.sshKeyId" class="btn btn-sm btn-danger pull-right">Delete</a>
}
</div>
</div>
<form method="POST" action="@path/@account.userName/_ssh" validate="true">
<form method="POST" action="@context.path/@account.userName/_ssh" validate="true">
<div class="panel panel-default">
<div class="panel-heading strong">Add an SSH Key</div>
<div class="panel-body">

View File

@@ -1,12 +1,10 @@
@(tableNames: Seq[String])(implicit context: gitbucket.core.controller.Context)
@import context._
@import gitbucket.core.view.helpers._
@html.main("Data export / import"){
@admin.html.menu("data") {
@gitbucket.core.html.main("Data export / import"){
@gitbucket.core.admin.html.menu("data") {
<div class="panel panel-default">
<div class="panel-heading strong">Export</div>
<div class="panel-body">
<form class="form form-horizontal" action="@path/admin/export" method="POST">
<form class="form form-horizontal" action="@context.path/admin/export" method="POST">
@tableNames.map { tableName =>
<div class="checkbox">
<label>
@@ -15,24 +13,14 @@
</div>
}
<input type="submit" class="btn btn-success pull-right" value="Export">
<div class="radio pull-right" style="margin-right: 10px;">
<label>
<input type="radio" name="type" value="sql">SQL
</label>
</div>
<div class="radio pull-right" style="margin-right: 10px;">
<label>
<input type="radio" name="type" value="xml" checked>XML
</label>
</div>
</form>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading strong">Import (only XML)</div>
<div class="panel-heading strong">Import</div>
<div class="panel-body">
<form class="form form-horizontal" action="@path/upload/import" method="POST" enctype="multipart/form-data" id="import-form">
<form class="form form-horizontal" action="@context.path/upload/import" method="POST" enctype="multipart/form-data" id="import-form">
<input type="file" name="file" id="file">
<input type="submit" class="btn btn-success pull-right" value="Import" id="import">
</form>
@@ -44,10 +32,10 @@
$(function(){
$('#import-form').submit(function(){
if($('#file').val() == ''){
alert('Choose an import XML file.');
alert('Choose an import SQL file.');
return false;
} else if(!$('#file').val().endsWith(".xml")){
alert('Import is available for only the XML file.');
} else if(!$('#file').val().endsWith(".sql")){
alert('Import is available for only the SQL file.');
return false;
}
return confirm('All existing data is deleted before importing.\nAre you sure?');

View File

@@ -1,27 +1,26 @@
@(active: String)(body: Html)(implicit context: gitbucket.core.controller.Context)
@import context._
<div class="main-sidebar">
<div class="sidebar">
<ul class="sidebar-menu" id="system-admin-menu-container">
<li@if(active=="users"){ class="active"}>
<a href="@path/admin/users">User Management</a>
<a href="@context.path/admin/users">User Management</a>
</li>
<li@if(active=="system"){ class="active"}>
<a href="@path/admin/system">System Settings</a>
<a href="@context.path/admin/system">System Settings</a>
</li>
<li@if(active=="plugins"){ class="active"}>
<a href="@path/admin/plugins">Plugins</a>
<a href="@context.path/admin/plugins">Plugins</a>
</li>
<li@if(active=="data"){ class="active"}>
<a href="@path/admin/data">Data export / import</a>
<a href="@context.path/admin/data">Data export / import</a>
</li>
<li>
<a href="@path/console/login.jsp" target="_blank">H2 Console</a>
<a href="@context.path/console/login.jsp" target="_blank">H2 Console</a>
</li>
@gitbucket.core.plugin.PluginRegistry().getSystemSettingMenus.map { menu =>
@menu(context).map { link =>
<li@if(active==link.id){ class="active"}>
<a href="@path/@link.path">@link.label</a>
<a href="@context.path/@link.path">@link.label</a>
</li>
}
}

View File

@@ -1,18 +1,16 @@
@(plugins: List[gitbucket.core.plugin.PluginInfo])(implicit context: gitbucket.core.controller.Context)
@import context._
@import gitbucket.core.view.helpers._
@html.main("Plugins"){
@admin.html.menu("plugins") {
@gitbucket.core.html.main("Plugins"){
@gitbucket.core.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>
@plugins.map { plugin =>
<li><a href="#@plugin.pluginId">@plugin.pluginId:@plugin.pluginVersion</a></li>
}
</ul>
@plugins.map {plugin =>
@plugins.map { plugin =>
<div class="panel panel-default">
<div class="panel-heading strong">@plugin.pluginName</div>
<div class="panel-body">
@@ -22,7 +20,7 @@
</div>
<div class="row">
<label class="col-md-2">Version</label>
<span class="col-md-10">@plugin.version</span>
<span class="col-md-10">@plugin.pluginVersion</span>
</div>
<div class="row">
<label class="col-md-2">Name</label>

View File

@@ -1,11 +1,8 @@
@(info: Option[Any])(implicit context: gitbucket.core.controller.Context)
@import context._
@import gitbucket.core.view.helpers._
@import gitbucket.core.util.Directory._
@html.main("System Settings"){
@menu("system"){
@helper.html.information(info)
<form action="@path/admin/system" method="POST" validate="true" class="form-horizontal">
@gitbucket.core.html.main("System Settings"){
@gitbucket.core.admin.html.menu("system"){
@gitbucket.core.helper.html.information(info)
<form action="@context.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">
@@ -19,7 +16,7 @@
</tr>
<tr>
<td>GITBUCKET_HOME</td>
<td>@GitBucketHome</td>
<td>@gitbucket.core.util.Directory.GitBucketHome</td>
</tr>
<tr>
<td>DATABASE_URL</td>
@@ -33,7 +30,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" class="form-control" value="@settings.baseUrl"/>
<input type="text" name="baseUrl" id="baseUrl" class="form-control" value="@context.settings.baseUrl"/>
<span id="error-baseUrl" class="error"></span>
</div>
</fieldset>
@@ -48,7 +45,7 @@
<hr>
<label><span class="strong">Information</span> (HTML is available)</label>
<fieldset>
<textarea name="information" class="form-control" style="height: 100px;">@settings.information</textarea>
<textarea name="information" class="form-control" style="height: 100px;">@context.settings.information</textarea>
</fieldset>
<!--====================================================================-->
<!-- Account registration -->
@@ -57,11 +54,11 @@
<label class="strong">Account registration</label>
<fieldset>
<label class="radio">
<input type="radio" name="allowAccountRegistration" value="true"@if(settings.allowAccountRegistration){ checked}>
<input type="radio" name="allowAccountRegistration" value="true"@if(context.settings.allowAccountRegistration){ checked}>
<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}>
<input type="radio" name="allowAccountRegistration" value="false"@if(!context.settings.allowAccountRegistration){ checked}>
<span class="strong">Deny</span> - <span class="normal">Only administrators can create accounts.</span>
</label>
</fieldset>
@@ -69,11 +66,11 @@
<label class="strong">Default option to create a new repository</label>
<fieldset>
<label class="radio">
<input type="radio" name="isCreateRepoOptionPublic" value="true"@if(settings.isCreateRepoOptionPublic){ checked}>
<input type="radio" name="isCreateRepoOptionPublic" value="true"@if(context.settings.isCreateRepoOptionPublic){ checked}>
<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}>
<input type="radio" name="isCreateRepoOptionPublic" value="false"@if(!context.settings.isCreateRepoOptionPublic){ checked}>
<span class="strong">Private</span> <span class="normal">- Only collaborators can read that repository.</span>
</label>
</fieldset>
@@ -84,11 +81,11 @@
<label class="strong">Anonymous access</label>
<fieldset>
<label class="radio">
<input type="radio" name="allowAnonymousAccess" value="true"@if(settings.allowAnonymousAccess){ checked}>
<input type="radio" name="allowAnonymousAccess" value="true"@if(context.settings.allowAnonymousAccess){ checked}>
<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}>
<input type="radio" name="allowAnonymousAccess" value="false"@if(!context.settings.allowAnonymousAccess){ checked}>
<span class="strong">Deny</span> <span class="normal">- Users must authenticate before viewing any information.</span>
</label>
</fieldset>
@@ -101,7 +98,7 @@
<div class="form-group">
<label class="control-label col-md-3" for="activityLogLimit">Limit</label>
<div class="col-md-9">
<input type="text" id="activityLogLimit" name="activityLogLimit" class="form-control input-mini" value="@settings.activityLogLimit"/>
<input type="text" id="activityLogLimit" name="activityLogLimit" class="form-control input-mini" value="@context.settings.activityLogLimit"/>
<span id="error-activityLogLimit" class="error"></span>
</div>
</div>
@@ -113,7 +110,7 @@
<label class="strong">Services</label>
<fieldset>
<label class="checkbox">
<input type="checkbox" name="gravatar"@if(settings.gravatar){ checked}/>
<input type="checkbox" name="gravatar"@if(context.settings.gravatar){ checked}/>
Use Gravatar for Profile-Images
</label>
</fieldset>
@@ -124,7 +121,7 @@
<label class="strong">SSH access</label>
<fieldset>
<label class="checkbox">
<input type="checkbox" id="ssh" name="ssh"@if(settings.ssh){ checked}/>
<input type="checkbox" id="ssh" name="ssh"@if(context.settings.ssh){ checked}/>
Enable SSH access to git repository
</label>
</fieldset>
@@ -132,14 +129,14 @@
<div class="form-group">
<label class="control-label col-md-3" for="sshHost">SSH Host</label>
<div class="col-md-9">
<input type="text" id="sshHost" name="sshHost" class="form-control" value="@settings.sshHost"/>
<input type="text" id="sshHost" name="sshHost" class="form-control" value="@context.settings.sshHost"/>
<span id="error-sshHost" class="error"></span>
</div>
</div>
<div class="form-group">
<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"/>
<input type="text" id="sshPort" name="sshPort" class="form-control" value="@context.settings.sshPort"/>
<span id="error-sshPort" class="error"></span>
</div>
</div>
@@ -154,7 +151,7 @@
<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(context.settings.ldap){ checked} />
LDAP
</label>
</fieldset>
@@ -162,82 +159,82 @@
<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)"/>
<input type="text" id="ldapHost" name="ldap.host" class="form-control" value="@context.settings.ldap.map(_.host)"/>
<span id="error-ldap_host" class="error"></span>
</div>
</div>
<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)"/>
<input type="text" id="ldapPort" name="ldap.port" class="form-control input-mini" value="@context.settings.ldap.map(_.port)"/>
<span id="error-ldap_port" class="error"></span>
</div>
</div>
<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)"/>
<input type="text" id="ldapBindDN" name="ldap.bindDN" class="form-control" value="@context.settings.ldap.map(_.bindDN)"/>
<span id="error-ldap_bindDN" class="error"></span>
</div>
</div>
<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)"/>
<input type="password" id="ldapBindPassword" name="ldap.bindPassword" class="form-control" value="@context.settings.ldap.map(_.bindPassword)"/>
<span id="error-ldap_bindPassword" class="error"></span>
</div>
</div>
<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)"/>
<input type="text" id="ldapBaseDN" name="ldap.baseDN" class="form-control" value="@context.settings.ldap.map(_.baseDN)"/>
<span id="error-ldap_baseDN" class="error"></span>
</div>
</div>
<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)"/>
<input type="text" id="ldapUserNameAttribute" name="ldap.userNameAttribute" class="form-control" value="@context.settings.ldap.map(_.userNameAttribute)"/>
<span id="error-ldap_userNameAttribute" class="error"></span>
</div>
</div>
<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)"/>
<input type="text" id="ldapAdditionalFilterCondition" name="ldap.additionalFilterCondition" class="form-control" value="@context.settings.ldap.map(_.additionalFilterCondition)"/>
<span id="error-ldap_additionalFilterCondition" class="error"></span>
</div>
</div>
<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)"/>
<input type="text" id="ldapFullNameAttribute" name="ldap.fullNameAttribute" class="form-control" value="@context.settings.ldap.map(_.fullNameAttribute)"/>
<span id="error-ldap_fullNameAttribute" class="error"></span>
</div>
</div>
<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)"/>
<input type="text" id="ldapMailAttribute" name="ldap.mailAttribute" class="form-control" value="@context.settings.ldap.map(_.mailAttribute)"/>
<span id="error-ldap_mailAttribute" class="error"></span>
</div>
</div>
<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}/>
<input type="checkbox" name="ldap.tls"@if(context.settings.ldap.flatMap(_.tls).getOrElse(false)){ checked}/>
</div>
</div>
<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}/>
<input type="checkbox" name="ldap.ssl"@if(context.settings.ldap.flatMap(_.ssl).getOrElse(false)){ checked}/>
</div>
</div>
<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)"/>
<input type="text" id="ldapKeystore" name="ldap.keystore" class="form-control" value="@context.settings.ldap.map(_.keystore)"/>
<span id="error-ldap_keystore" class="error"></span>
</div>
</div>
@@ -249,7 +246,7 @@
<label class="strong">Notifications</label>
<fieldset>
<label class="checkbox">
<input type="checkbox" id="notification" name="notification"@if(settings.notification){ checked}/>
<input type="checkbox" id="notification" name="notification"@if(context.settings.notification){ checked}/>
Send notifications
</label>
</fieldset>
@@ -260,7 +257,7 @@
<label class="strong">Communication</label>
<fieldset>
<label class="checkbox">
<input type="checkbox" id="useSMTP" name="useSMTP" @if(settings.useSMTP){ checked}/>
<input type="checkbox" id="useSMTP" name="useSMTP" @if(context.settings.useSMTP){ checked}/>
SMTP
</label>
</fieldset>
@@ -268,45 +265,45 @@
<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)"/>
<input type="text" id="smtpHost" name="smtp.host" class="form-control" value="@context.settings.smtp.map(_.host)"/>
<span id="error-smtp_host" class="error"></span>
</div>
</div>
<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)"/>
<input type="text" id="smtpPort" name="smtp.port" class="form-control input-mini" value="@context.settings.smtp.map(_.port)"/>
<span id="error-smtp_port" class="error"></span>
</div>
</div>
<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)"/>
<input type="text" id="smtpUser" name="smtp.user" class="form-control" value="@context.settings.smtp.map(_.user)"/>
</div>
</div>
<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)"/>
<input type="password" id="smtpPassword" name="smtp.password" class="form-control" value="@context.settings.smtp.map(_.password)"/>
</div>
</div>
<div class="form-group">
<label class="control-label col-md-3" for="smtpPassword">Enable SSL</label>
<div class="col-md-9">
<input type="checkbox" id="smtpSsl" name="smtp.ssl"@if(settings.smtp.flatMap(_.ssl).getOrElse(false)){ checked}/>
<input type="checkbox" id="smtpSsl" name="smtp.ssl"@if(context.settings.smtp.flatMap(_.ssl).getOrElse(false)){ checked}/>
</div>
</div>
<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)"/>
<input type="text" id="fromAddress" name="smtp.fromAddress" class="form-control" value="@context.settings.smtp.map(_.fromAddress)"/>
</div>
</div>
<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)"/>
<input type="text" id="fromName" name="smtp.fromName" class="form-control" value="@context.settings.smtp.map(_.fromName)"/>
</div>
</div>
<div class="text-right">
@@ -346,7 +343,7 @@ $(function(){
alert('Destination is required.');
$('#testAddress').focus();
} else {
$.post('@path/admin/system/sendmail', {
$.post('@context.path/admin/system/sendmail', {
'smtp.host': host,
'smtp.port': port,
'smtp.user': user,

View File

@@ -1,9 +1,8 @@
@(account: Option[gitbucket.core.model.Account], error: Option[Any] = None)(implicit context: gitbucket.core.controller.Context)
@import context._
@html.main(if(account.isEmpty) "New User" else "Update User"){
@admin.html.menu("users"){
@helper.html.error(error)
<form method="POST" action="@if(account.isEmpty){@path/admin/users/_newuser} else {@path/admin/users/@account.get.userName/_edituser}" validate="true">
@gitbucket.core.html.main(if(account.isEmpty) "New User" else "Update User"){
@gitbucket.core.admin.html.menu("users"){
@gitbucket.core.helper.html.error(error)
<form method="POST" action="@if(account.isEmpty){@context.path/admin/users/_newuser} else {@context.path/admin/users/@account.get.userName/_edituser}" validate="true">
<div class="row">
<div class="col-md-6">
<fieldset class="form-group">
@@ -71,13 +70,13 @@
<div class="col-md-6">
<fieldset class="form-group">
<label for="avatar" class="strong">Image (Optional)</label>
@helper.html.uploadavatar(account)
@gitbucket.core.helper.html.uploadavatar(account)
</fieldset>
</div>
</div>
<fieldset class="border-top">
<input type="submit" class="btn btn-success" value="@if(account.isEmpty){Create User} else {Update User}"/>
<a href="@path/admin/users" class="btn btn-default">Cancel</a>
<a href="@context.path/admin/users" class="btn btn-default">Cancel</a>
</fieldset>
</form>
}

View File

@@ -1,9 +1,7 @@
@(account: Option[gitbucket.core.model.Account], members: List[gitbucket.core.model.GroupMember])(implicit context: gitbucket.core.controller.Context)
@import context._
@import gitbucket.core.view.helpers._
@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">
@gitbucket.core.html.main(if(account.isEmpty) "New Group" else "Update Group"){
@gitbucket.core.admin.html.menu("users"){
<form method="POST" action="@if(account.isEmpty){@context.path/admin/users/_newgroup} else {@context.path/admin/users/@account.get.userName/_editgroup}" validate="true">
<div class="row">
<div class="col-md-6">
<fieldset class="form-group">
@@ -28,7 +26,7 @@
</fieldset>
<fieldset class="form-group">
<label for="avatar" class="strong">Image (Optional)</label>
@helper.html.uploadavatar(account)
@gitbucket.core.helper.html.uploadavatar(account)
</fieldset>
</div>
<div class="col-md-6">
@@ -36,7 +34,7 @@
<label class="strong">Members</label>
<ul id="member-list" class="collaborator">
</ul>
@helper.html.account("memberName", 200)
@gitbucket.core.helper.html.account("memberName", 200)
<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>
@@ -47,7 +45,7 @@
</div>
<fieldset class="border-top">
<input type="submit" class="btn btn-success" value="@if(account.isEmpty){Create Group} else {Update Group}"/>
<a href="@path/admin/users" class="btn btn-default">Cancel</a>
<a href="@context.path/admin/users" class="btn btn-default">Cancel</a>
</fieldset>
</form>
}
@@ -77,7 +75,7 @@ $(function(){
}
// check existence
$.post('@path/_user/existence', {
$.post('@context.path/_user/existence', {
'userName': userName,
'userOnly': true
}, function(data, status){
@@ -118,7 +116,7 @@ $(function(){
.append(memberButton)
.append(managerButton))
.append(' ')
.append($('<a>').attr('href', '@path/' + userName).text(userName))
.append($('<a>').attr('href', '@context.path/' + userName).text(userName))
.append(' ')
.append($('<a href="#" class="remove pull-right">(remove)</a>')));
}

View File

@@ -1,11 +1,10 @@
@(users: List[gitbucket.core.model.Account], members: Map[String, List[String]], includeRemoved: Boolean)(implicit context: gitbucket.core.controller.Context)
@import context._
@import gitbucket.core.view.helpers._
@html.main("Manage Users"){
@admin.html.menu("users"){
@import gitbucket.core.view.helpers
@gitbucket.core.html.main("Manage Users"){
@gitbucket.core.admin.html.menu("users"){
<div class="pull-right" style="margin-bottom: 4px;">
<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>
<a href="@context.path/admin/users/_newuser" class="btn btn-default">New User</a>
<a href="@context.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}/>
@@ -17,14 +16,14 @@
<td @if(account.isRemoved){style="background-color: #dddddd;"}>
<div class="pull-right">
@if(account.isGroupAccount){
<a href="@path/admin/users/@account.userName/_editgroup">Edit</a>
<a href="@context.path/admin/users/@account.userName/_editgroup">Edit</a>
} else {
<a href="@path/admin/users/@account.userName/_edituser">Edit</a>
<a href="@context.path/admin/users/@account.userName/_edituser">Edit</a>
}
</div>
<div class="strong">
@avatar(account.userName, 20)
<a href="@url(account.userName)">@account.userName</a>
@helpers.avatar(account.userName, 20)
<a href="@helpers.url(account.userName)">@account.userName</a>
@if(account.isGroupAccount){
(Group)
} else {
@@ -36,7 +35,7 @@
}
@if(account.isGroupAccount){
@members(account.userName).map { userName =>
@avatar(userName, 20, tooltip = true)
@helpers.avatar(userName, 20, tooltip = true)
}
}
</div>
@@ -50,10 +49,10 @@
}
</div>
<div>
<span class="muted">Registered:</span> @datetime(account.registeredDate)
<span class="muted">Updated:</span> @datetime(account.updatedDate)
<span class="muted">Registered:</span> @helpers.datetime(account.registeredDate)
<span class="muted">Updated:</span> @helpers.datetime(account.updatedDate)
@if(!account.isGroupAccount){
<span class="muted">Last Login:</span> @account.lastLoginDate.map(datetime)
<span class="muted">Last Login:</span> @account.lastLoginDate.map(helpers.datetime)
}
</div>
</td>
@@ -65,7 +64,7 @@
<script>
$(function(){
$('#includeRemoved').click(function(){
location.href = '@path/admin/users?includeRemoved=' + this.checked;
location.href = '@context.path/admin/users?includeRemoved=' + this.checked;
});
});
</script>

View File

@@ -2,62 +2,61 @@
closedCount: Int,
condition: gitbucket.core.service.IssuesService.IssueSearchCondition,
groups: List[String])(implicit context: gitbucket.core.controller.Context)
@import context._
@import gitbucket.core.view.helpers._
@import gitbucket.core.view.helpers
<div id="table-issues-control">
@helper.html.dropdown("Visibility"){
@gitbucket.core.helper.html.dropdown("Visibility"){
<li>
<a href="@(condition.copy(visibility = (if(condition.visibility == Some("private")) None else Some("private"))).toURL)">
@helper.html.checkicon(condition.visibility == Some("private"))
@gitbucket.core.helper.html.checkicon(condition.visibility == Some("private"))
Private repository only
</a>
</li>
<li>
<a href="@(condition.copy(visibility = (if(condition.visibility == Some("public")) None else Some("public"))).toURL)">
@helper.html.checkicon(condition.visibility == Some("public"))
@gitbucket.core.helper.html.checkicon(condition.visibility == Some("public"))
Public repository only
</a>
</li>
}
@helper.html.dropdown("Organization"){
@gitbucket.core.helper.html.dropdown("Organization"){
@groups.map { group =>
<li>
<a href="@((if(condition.groups.contains(group)) condition.copy(groups = condition.groups - group) else condition.copy(groups = condition.groups + group)).toURL)">
@helper.html.checkicon(condition.groups.contains(group))
@avatar(group, 20) @group
@gitbucket.core.helper.html.checkicon(condition.groups.contains(group))
@helpers.avatar(group, 20) @group
</a>
</li>
}
}
@helper.html.dropdown("Sort"){
@gitbucket.core.helper.html.dropdown("Sort"){
<li>
<a href="@condition.copy(sort="created", direction="desc").toURL">
@helper.html.checkicon(condition.sort == "created" && condition.direction == "desc") Newest
@gitbucket.core.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
@gitbucket.core.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
@gitbucket.core.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
@gitbucket.core.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
@gitbucket.core.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
@gitbucket.core.helper.html.checkicon(condition.sort == "updated" && condition.direction == "asc") Least recently updated
</a>
</li>
}

View File

@@ -7,14 +7,12 @@
groups: List[String],
recentRepositories: List[gitbucket.core.service.RepositoryService.RepositoryInfo],
userRepositories: List[gitbucket.core.service.RepositoryService.RepositoryInfo])(implicit context: gitbucket.core.controller.Context)
@import context._
@import gitbucket.core.view.helpers._
@html.main("Issues"){
@sidebar(recentRepositories, userRepositories){
@dashboard.html.tab("issues")
@gitbucket.core.html.main("Issues"){
@gitbucket.core.dashboard.html.sidebar(recentRepositories, userRepositories){
@gitbucket.core.dashboard.html.tab("issues")
<div class="container">
@issuesnavi(filter, openCount, closedCount, condition)
@issueslist(issues, page, openCount, closedCount, condition, filter, groups)
@gitbucket.core.dashboard.html.issuesnavi("issues", filter, openCount, closedCount, condition)
@gitbucket.core.dashboard.html.issueslist(issues, page, openCount, closedCount, condition, filter, groups)
</div>
}
}

View File

@@ -5,15 +5,14 @@
condition: gitbucket.core.service.IssuesService.IssueSearchCondition,
filter: String,
groups: List[String])(implicit context: gitbucket.core.controller.Context)
@import context._
@import gitbucket.core.view.helpers._
@import gitbucket.core.view.helpers
@import gitbucket.core.service.IssuesService
@import gitbucket.core.service.IssuesService.IssueInfo
<table class="table table-bordered table-hover table-issues">
<thead>
<tr>
<th style="background-color: #eee;">
@dashboard.html.header(openCount, closedCount, condition, groups)
@gitbucket.core.dashboard.html.header(openCount, closedCount, condition, groups)
</th>
</tr>
</thead>
@@ -21,11 +20,11 @@
@issues.map { case IssueInfo(issue, labels, milestone, commentCount, commitStatus) =>
<tr>
<td style="padding-top: 12px; padding-bottom: 12px;">
<a href="@path/@issue.userName/@issue.repositoryName">@issue.userName/@issue.repositoryName</a>&nbsp;&#xFF65;
<a href="@context.path/@issue.userName/@issue.repositoryName">@issue.userName/@issue.repositoryName</a>&nbsp;&#xFF65;
@if(issue.isPullRequest){
<a href="@path/@issue.userName/@issue.repositoryName/pull/@issue.issueId" class="issue-title">@issue.title</a>
<a href="@context.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>
<a href="@context.path/@issue.userName/@issue.repositoryName/issues/@issue.issueId" class="issue-title">@issue.title</a>
}
@gitbucket.core.issues.html.commitstatus(issue, commitStatus)
@labels.map { label =>
@@ -33,20 +32,20 @@
}
<span class="pull-right muted">
@issue.assignedUserName.map { userName =>
@avatar(userName, 20, tooltip = true)
@helpers.avatar(userName, 20, tooltip = true)
}
@if(commentCount > 0){
<a href="@path/@issue.userName/@issue.repositoryName/issues/@issue.issueId" class="issue-comment-count">
<a href="@context.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;">
<a href="@context.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-top: 2px;">
#@issue.issueId opened by @user(issue.openedUserName, styleClass="username") @datetime(issue.registeredDate)
#@issue.issueId opened by @helpers.user(issue.openedUserName, styleClass="username") @helpers.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>
}
@@ -64,5 +63,5 @@
</tbody>
</table>
<div class="pull-right">
@helper.html.paginator(page, (if(condition.state == "open") openCount else closedCount), IssuesService.IssueLimit, 10, condition.toURL)
@gitbucket.core.helper.html.paginator(page, (if(condition.state == "open") openCount else closedCount), IssuesService.IssueLimit, 10, condition.toURL)
</div>

View File

@@ -1,9 +1,8 @@
@(filter: String,
@(active: String,
filter: String,
openCount: Int,
closedCount: Int,
condition: gitbucket.core.service.IssuesService.IssueSearchCondition)(implicit context: gitbucket.core.controller.Context)
@import context._
@import gitbucket.core.view.helpers._
<ul class="nav nav-pills pull-left" style="line-height: 14px; margin-bottom: 10px;">
<li class="@(if(condition.state == "open"){"active"})">
<a href="@condition.copy(state = "open").toURL">Open <span class="badge">@openCount</span></a>
@@ -11,15 +10,18 @@
<li class="@(if(condition.state == "closed"){"active"})">
<a href="@condition.copy(state = "closed").toURL">Closed <span class="badge">@closedCount</span></a>
</li>
@*
<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}">
<a href="@path/dashboard/@active/mentioned@condition.copy(author = None, assigned = None).toURL">Mentioned</a>
</li>
*@
</ul>
<div class="btn-group pull-right" data-toggle="buttons">
<a class="switch btn btn-default @if(filter == "created_by"){active}" href="@context.path/dashboard/@active/created_by@condition.copy(author = None, assigned = None).toURL">Created</a>
<a class="switch btn btn-default @if(filter == "assigned" ){active}" href="@context.path/dashboard/@active/assigned@condition.copy(author = None, assigned = None).toURL">Assigned</a>
<a class="switch btn btn-default @if(filter == "mentioned" ){active}" href="@context.path/dashboard/@active/mentioned@condition.copy(author = None, assigned = None).toURL">Mentioned</a>
</div>
<script>
$(function(){
$('a.switch').click(function(){
location.href = $(this).attr('href');
});
})
</script>

View File

@@ -7,14 +7,12 @@
groups: List[String],
recentRepositories: List[gitbucket.core.service.RepositoryService.RepositoryInfo],
userRepositories: List[gitbucket.core.service.RepositoryService.RepositoryInfo])(implicit context: gitbucket.core.controller.Context)
@import context._
@import gitbucket.core.view.helpers._
@html.main("Pull Requests"){
@sidebar(recentRepositories, userRepositories){
@dashboard.html.tab("pulls")
@gitbucket.core.html.main("Pull Requests"){
@gitbucket.core.dashboard.html.sidebar(recentRepositories, userRepositories){
@gitbucket.core.dashboard.html.tab("pulls")
<div class="container">
@issuesnavi(filter, openCount, closedCount, condition)
@issueslist(issues, page, openCount, closedCount, condition, filter, groups)
@gitbucket.core.dashboard.html.issuesnavi("pulls", filter, openCount, closedCount, condition)
@gitbucket.core.dashboard.html.issueslist(issues, page, openCount, closedCount, condition, filter, groups)
</div>
}
}

View File

@@ -1,22 +1,24 @@
@(recentRepositories: List[gitbucket.core.service.RepositoryService.RepositoryInfo],
userRepositories: List[gitbucket.core.service.RepositoryService.RepositoryInfo])(body: Html)(implicit context: gitbucket.core.controller.Context)
@import context._
@import gitbucket.core.view.helpers._
@import gitbucket.core.view.helpers
<div class="main-sidebar">
<div class="sidebar">
<ul class="nav sidebar-menu">
@if(loginAccount.isDefined){
<li class="header">Your repositories <span class="label label-primary pull-right">@userRepositories.size</span></li>
@if(context.loginAccount.isDefined){
<li class="header">
<span class="label label-primary pull-right">@userRepositories.size</span>
Your repositories
</li>
@if(userRepositories.isEmpty){
<li>No repositories</li>
} else {
@defining(10){ max =>
@userRepositories.zipWithIndex.map { case (repository, i) =>
<li class="repo-link" style="@if(i > max - 1){display:none;}">
@if(repository.owner == loginAccount.get.userName){
<a href="@url(repository)">@helper.html.repositoryicon(repository, false) <span class="strong">@repository.name</span></a>
@if(repository.owner == context.loginAccount.get.userName){
<a href="@helpers.url(repository)">@gitbucket.core.helper.html.repositoryicon(repository, false) <span class="strong">@repository.name</span></a>
} else {
<a href="@url(repository)">@helper.html.repositoryicon(repository, false) @repository.owner/<span class="strong">@repository.name</span></a>
<a href="@helpers.url(repository)">@gitbucket.core.helper.html.repositoryicon(repository, false) @repository.owner/<span class="strong">@repository.name</span></a>
}
</li>
}
@@ -35,7 +37,7 @@
@defining(10){ max =>
@recentRepositories.zipWithIndex.map { case (repository, i) =>
<li class="repo-link" style="@if(i > max - 1){display:none;}">
<a href="@url(repository)">@helper.html.repositoryicon(repository, false) @repository.owner/<span class="strong">@repository.name</span></a>
<a href="@helpers.url(repository)">@gitbucket.core.helper.html.repositoryicon(repository, false) @repository.owner/<span class="strong">@repository.name</span></a>
</li>
}
@if(recentRepositories.size > max){

View File

@@ -1,14 +1,12 @@
@(active: String = "")(implicit context: gitbucket.core.controller.Context)
@import context._
@import gitbucket.core.view.helpers._
<ul class="nav nav-tabs" style="margin-bottom: 20px;">
<li @if(active == ""){ class="active"}><a href="@path/">News Feed</a></li>
@if(loginAccount.isDefined){
<li @if(active == "pulls" ){ class="active"}><a href="@path/dashboard/pulls">Pull Requests</a></li>
<li @if(active == "issues"){ class="active"}><a href="@path/dashboard/issues">Issues</a></li>
<li @if(active == ""){ class="active"}><a href="@context.path/">News Feed</a></li>
@if(context.loginAccount.isDefined){
<li @if(active == "pulls" ){ class="active"}><a href="@context.path/dashboard/pulls">Pull Requests</a></li>
<li @if(active == "issues"){ class="active"}><a href="@context.path/dashboard/issues">Issues</a></li>
@gitbucket.core.plugin.PluginRegistry().getDashboardTabs.map { tab =>
@tab(context).map { link =>
<li @if(active == link.id){ class="active"}><a href="@path/@link.path">@link.label</a></li>
<li @if(active == link.id){ class="active"}><a href="@context.path/@link.path">@link.label</a></li>
}
}
}

View File

@@ -1,4 +1,4 @@
@(title: String)(implicit context: gitbucket.core.controller.Context)
@main("Error"){
@gitbucket.core.html.main("Error"){
<h1>@title</h1>
}

View File

@@ -0,0 +1,7 @@
@(repository: gitbucket.core.service.RepositoryService.RepositoryInfo)(implicit context: gitbucket.core.controller.Context)
<!DOCTYPE html>
<html>
<head>
<meta name="go-import" content="@context.baseUrl.replaceFirst("^https?://", "")/@repository.owner/@repository.name git @repository.httpUrl" />
</head>
</html>

View File

@@ -1,5 +1,4 @@
@(id: String, width: Int)(implicit context: gitbucket.core.controller.Context)
@import context._
<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>
@@ -7,7 +6,7 @@
$(function(){
$('#@id').typeahead({
source: function (query, process) {
return $.get('@path/_user/proposals', { query: query },
return $.get('@context.path/_user/proposals', { query: query },
function (data) {
return process(data.options);
});

View File

@@ -1,6 +1,5 @@
@(activities: List[gitbucket.core.model.Activity])(implicit context: gitbucket.core.controller.Context)
@import context._
@import gitbucket.core.view.helpers._
@import gitbucket.core.view.helpers
@if(activities.isEmpty){
No activity
@@ -29,7 +28,7 @@
} else {
if(commit.nonEmpty){
<div>
<a href={s"${path}/${activity.userName}/${activity.repositoryName}/commit/${commit. substring(0, 40)}"} class="monospace">{commit.substring(0, 7)}</a>
<a href={s"${context.path}/${activity.userName}/${activity.repositoryName}/commit/${commit. substring(0, 40)}"} class="monospace">{commit.substring(0, 7)}</a>
<span>{commit.substring(41)}</span>
</div>
}
@@ -39,19 +38,19 @@
}
case "create_wiki" => customActivity(activity, "book"){
<div class="small activity-message">
Created <a href={s"${path}/${activity.userName}/${activity.repositoryName}/wiki/${activity.additionalInfo.get}"}>{activity.additionalInfo.get}</a>.
Created <a href={s"${context.path}/${activity.userName}/${activity.repositoryName}/wiki/${activity.additionalInfo.get}"}>{activity.additionalInfo.get}</a>.
</div>
}
case "edit_wiki" => customActivity(activity, "book"){
activity.additionalInfo.get.split(":") match {
case Array(pageName, commitId) =>
<div class="small activity-message">
Edited <a href={s"${path}/${activity.userName}/${activity.repositoryName}/wiki/${pageName}"}>{pageName}</a>.
<a href={s"${path}/${activity.userName}/${activity.repositoryName}/wiki/${pageName}/_compare/${commitId.substring(0, 7)}^...${commitId.substring(0, 7)}"}>View the diff »</a>
Edited <a href={s"${context.path}/${activity.userName}/${activity.repositoryName}/wiki/${pageName}"}>{pageName}</a>.
<a href={s"${context.path}/${activity.userName}/${activity.repositoryName}/wiki/${pageName}/_compare/${commitId.substring(0, 7)}^...${commitId.substring(0, 7)}"}>View the diff »</a>
</div>
case Array(pageName) =>
<div class="small activity-message">
Edited <a href={s"${path}/${activity.userName}/${activity.repositoryName}/wiki/${pageName}"}>{pageName}</a>.
Edited <a href={s"${context.path}/${activity.userName}/${activity.repositoryName}/wiki/${pageName}"}>{pageName}</a>.
</div>
}
}
@@ -65,10 +64,10 @@
<div class="activity-icon-large"><i class="mega-octicon octicon-@image"></i></div>
*@
<div>
<div class="muted small">@helper.html.datetimeago(activity.activityDate)</div>
<div class="muted small">@gitbucket.core.helper.html.datetimeago(activity.activityDate)</div>
<div class="strong">
@avatar(activity.activityUserName, 16)
@activityMessage(activity.message)
@helpers.avatar(activity.activityUserName, 16)
@helpers.activityMessage(activity.message)
</div>
@activity.additionalInfo.map { additionalInfo =>
<div class=" activity-message">@additionalInfo</div>
@@ -81,10 +80,10 @@
<div class="activity-icon-large"><i class="mega-octicon octicon-@image"></i></div>
*@
<div>
<div class="muted small">@helper.html.datetimeago(activity.activityDate)</div>
<div class="muted small">@gitbucket.core.helper.html.datetimeago(activity.activityDate)</div>
<div class="strong">
@avatar(activity.activityUserName, 16)
@activityMessage(activity.message)
@helpers.avatar(activity.activityUserName, 16)
@helpers.activityMessage(activity.message)
</div>
@additionalInfo
</div>
@@ -95,10 +94,10 @@
<div class="activity-icon-small"><i class="octicon octicon-@image"></i></div>
*@
<div>
<span class="muted small">@helper.html.datetimeago(activity.activityDate)</span>
<span class="muted small">@gitbucket.core.helper.html.datetimeago(activity.activityDate)</span>
<div>
@avatar(activity.activityUserName, 16)
@activityMessage(activity.message)
@helpers.avatar(activity.activityUserName, 16)
@helpers.activityMessage(activity.message)
</div>
</div>
}

View File

@@ -1,6 +1,7 @@
@(owner: String, repository: String)(textarea: Html)(implicit context: gitbucket.core.controller.Context)
@import context._
@(repository: gitbucket.core.service.RepositoryService.RepositoryInfo,
completionContext: String, generateScript: Boolean = true)(textarea: Html)(implicit context: gitbucket.core.controller.Context)
@import gitbucket.core.util.FileUtil
@import gitbucket.core.view.helpers
<div class="muted attachable">
@textarea
<div class="clickable">Attach images or documents by dragging &amp; dropping, or selecting them.</div>
@@ -8,25 +9,69 @@
@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/file/@owner/@repository',
maxFilesize: 10,
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 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();
@gitbucket.core.plugin.PluginRegistry().getSuggestionProviders.map { provider =>
@if(provider.context.contains(completionContext)){
var @provider.id = @Html(helpers.json(provider.values(repository)));
@Html(provider.additionalScript)
}
}
$('#@textareaId').textcomplete([
@gitbucket.core.plugin.PluginRegistry().getSuggestionProviders.map { provider =>
@if(provider.context.contains(completionContext)){
{
id: '@{provider.id}',
match: /\B@{provider.prefix}([\-+\w]*)$/,
search: function (term, callback) {
callback($.map(@{provider.id}, function (proposal) {
return proposal.indexOf(term) === 0 ? proposal : null;
}));
},
template: function (value) {
return @{Html(provider.template)};
},
replace: function (value) {
return '@{provider.prefix}' + value + '@{provider.suffix}';
},
index: 1
},
}
}
], {
onKeydown: function (e, commands) {
if (e.ctrlKey && e.keyCode === 74) { // CTRL-J
return commands.KEY_ENTER;
}
}
});
@if(generateScript){
try {
$([$('#@textareaId')[0]]).dropzone({
@dropzone(false, textareaId)
});
$([$('#@textareaId').closest('div')[0], $('#@textareaId').next('div')[0]]).dropzone({
@dropzone(true, textareaId)
});
} catch(e) {
if (e.message !== "Dropzone already attached.") {
throw e;
}
});
} catch(e) {
if (e.message !== "Dropzone already attached.") {
throw e;
}
}
});
</script>
}
@dropzone(clickable: Boolean, textareaId: Option[String]) = {
url: '@context.path/upload/file/@repository.owner/@repository.name',
maxFilesize: 10,
clickable: false,
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 attachFile = (file.type.match(/image\/.*/) ? '\n![' + file.name.split('.')[0] : '\n[' + file.name) +
'](@context.baseUrl/@repository.owner/@repository.name/_attached/' + id + ')';
$('#@textareaId').val($('#@textareaId').val() + attachFile);
$(file.previewElement).prevAll('div.dz-preview').addBack().remove();
}
}

View File

@@ -1,11 +1,8 @@
@(branch: String = "",
repository: gitbucket.core.service.RepositoryService.RepositoryInfo,
hasWritePermission: Boolean)(body: Html)(implicit context: gitbucket.core.controller.Context)
@import gitbucket.core.service.RepositoryService
@import context._
@import gitbucket.core._
@import gitbucket.core.view.helpers._
@helper.html.dropdown(
@import gitbucket.core.view.helpers
@gitbucket.core.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"
) {
@@ -14,7 +11,7 @@
@body
@if(hasWritePermission) {
<li id="create-branch" style="display: none;">
<a><form action="@url(repository)/branches" method="post" style="margin: 0;">
<a><form action="@helpers.url(repository)/branches" method="post" style="margin: 0;">
<span class="new-branch-name">Create branch:&nbsp;<span class="new-branch"></span></span>
<br><span style="padding-left: 17px;">from&nbsp;'@branch'</span>
<input type="hidden" name="new">

View File

@@ -2,18 +2,16 @@
hasWritePermission: Boolean,
repository: gitbucket.core.service.RepositoryService.RepositoryInfo,
latestCommitId: Option[String] = None)(implicit context: gitbucket.core.controller.Context)
@import context._
@import gitbucket.core._
@import gitbucket.core.view.helpers._
@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">@avatarLink(comment.commentedUserName, 48)</div>
<div class="issue-avatar-image">@helpers.avatarLink(comment.commentedUserName, 48)</div>
<div class="panel panel-default commit-comment-box commit-comment-@comment.commentId">
<div class="panel-heading">
@user(comment.commentedUserName, styleClass="username strong")
@helpers.user(comment.commentedUserName, styleClass="username strong")
<span class="muted">
commented
@if(comment.issueId.isDefined){
@@ -22,19 +20,19 @@
@if(comment.fileName.isDefined){
on @comment.fileName.get
}
in <a href="@path/@repository.owner/@repository.name/commit/@comment.commitId">@comment.commitId.substring(0, 7)</a>
in <a href="@context.path/@repository.owner/@repository.name/commit/@comment.commitId">@comment.commitId.substring(0, 7)</a>
}
@helper.html.datetimeago(comment.registeredDate)
@gitbucket.core.helper.html.datetimeago(comment.registeredDate)
</span>
<span class="pull-right">
@if(hasWritePermission || loginAccount.map(_.userName == comment.commentedUserName).getOrElse(false)){
@if(hasWritePermission || context.loginAccount.map(_.userName == comment.commentedUserName).getOrElse(false)){
<a href="#" data-comment-id="@comment.commentId"><i class="octicon octicon-pencil"></i></a>&nbsp;
<a href="#" data-comment-id="@comment.commentId"><i class="octicon octicon-x"></i></a>
}
</span>
</div>
<div class="panel-body issue-content commit-commentContent-@comment.commentId markdown-body">
@markdown(
@helpers.markdown(
markdown = comment.content,
repository = repository,
enableWikiLink = false,

View File

@@ -1,10 +1,10 @@
@(latestUpdatedDate: java.util.Date,
recentOnly: Boolean = true)
@import gitbucket.core.view.helpers._
<span data-toggle="tooltip" title="@datetime(latestUpdatedDate)">
@import gitbucket.core.view.helpers
<span data-toggle="tooltip" title="@helpers.datetime(latestUpdatedDate)">
@if(recentOnly){
@datetimeAgoRecentOnly(latestUpdatedDate)
}else{
@datetimeAgo(latestUpdatedDate)
@helpers.datetimeAgoRecentOnly(latestUpdatedDate)
} else {
@helpers.datetimeAgo(latestUpdatedDate)
}
</span>

View File

@@ -6,8 +6,7 @@
issueId: Option[Int],
hasWritePermission: Boolean,
showLineNotes: Boolean)(implicit context: gitbucket.core.controller.Context)
@import context._
@import gitbucket.core.view.helpers._
@import gitbucket.core.view.helpers
@import org.eclipse.jgit.diff.DiffEntry.ChangeType
@if(showIndex){
<div class="pull-right" style="margin-bottom: 10px;">
@@ -16,7 +15,7 @@
<input type="button" id="btn-split" class="btn btn-default btn-small" value="Split">
</div>
</div>
Showing <a href="javascript:void(0);" id="toggle-file-list">@diffs.size changed @plural(diffs.size, "file")</a>
Showing <a href="javascript:void(0);" id="toggle-file-list">@diffs.size changed @helpers.plural(diffs.size, "file")</a>
<ul id="commit-file-list" style="display: none;">
@diffs.zipWithIndex.map { case (diff, i) =>
<li@if(i > 0){ class="border"}>
@@ -49,7 +48,7 @@
<div class="pull-right align-right">
<label class="no-margin"><input type="checkbox" class="ignore-whitespace" value="1"/> Ignore Space</label>
<label class="no-margin"><input type="checkbox" class="toggle-notes" checked> Show notes</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>
<a href="@helpers.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"><i class="octicon octicon-diff-renamed"></i></span>
@@ -60,13 +59,13 @@
<div class="pull-right align-right">
<label class="no-margin"><input type="checkbox" class="ignore-whitespace" value="1"/> Ignore Space</label>
<label class="no-margin"><input type="checkbox" class="toggle-notes" checked> Show notes</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>
<a href="@helpers.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">
@if(diff.changeType == ChangeType.ADD){
<i class="octicon octicon-diff-added"></i>
}else{
} else {
<i class="octicon octicon-diff-modified"></i>
}
</span>
@@ -76,7 +75,7 @@
@if(oldCommitId.isDefined){
<div class="pull-right align-right">
<label class="no-margin"><input type="checkbox" class="toggle-notes" checked> Show notes</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>
<a href="@helpers.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"><i class="octicon octicon-diff-removed"></i></span>
@@ -104,14 +103,14 @@
@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)/raw/@oldCommitId.get/@diff.oldPath" class="diff-image" onload="onLoadedDiffImages(this)" style="display:none" /></div>
<div class="diff-image-frame diff-old"><img src="@helpers.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>
<div class="diff-image-frame diff-new"><img src="@helpers.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>
@@ -131,8 +130,8 @@
</tr>
</table>
}
<script type="text/javascript" src="@assets/vendors/jsdifflib/difflib.js"></script>
<link href="@assets/vendors/jsdifflib/diffview.css" type="text/css" rel="stylesheet" />
<script type="text/javascript" src="@helpers.assets/vendors/jsdifflib/difflib.js"></script>
<link href="@helpers.assets/vendors/jsdifflib/diffview.css" type="text/css" rel="stylesheet" />
<script>
$(function(){
@if(showIndex){
@@ -203,7 +202,7 @@ $(function(){
var commitId = $this.closest('.table-bordered').attr('commitId'),
fileName = $this.closest('.table-bordered').attr('fileName'),
oldLineNumber, newLineNumber,
url = '@url(repository)/commit/' + commitId + '/comment/_form?fileName=' + fileName@issueId.map { id => + '&issueId=@id' };
url = '@helpers.url(repository)/commit/' + commitId + '/comment/_form?fileName=' + fileName@issueId.map { id => + '&issueId=@id' };
if (viewType == 0) {
oldLineNumber = $this.parent().prev('.oldline').attr('line-number');
newLineNumber = $this.parent().prev('.newline').attr('line-number');

View File

@@ -1,6 +1,5 @@
@(activities: List[gitbucket.core.model.Activity])(implicit context: gitbucket.core.controller.Context)<?xml version="1.0" encoding="UTF-8"?>
@import context._
@import gitbucket.core.view.helpers._
@import gitbucket.core.view.helpers
<feed xmlns="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" xml:lang="en-US">
<id>tag:@context.host,2013:gitbucket</id>
<title>Gitbucket's activities</title>
@@ -9,19 +8,19 @@
<name>Gitbucket</name>
<uri>@context.baseUrl</uri>
</author>
<updated>@datetimeRFC3339(if(activities.isEmpty) new java.util.Date else activities.map(_.activityDate).max)</updated>
<updated>@helpers.datetimeRFC3339(if(activities.isEmpty) new java.util.Date else activities.map(_.activityDate).max)</updated>
@activities.map { activity =>
<entry>
<id>tag:@context.host,@date(activity.activityDate):activity:@activity.activityId</id>
<published>@datetimeRFC3339(activity.activityDate)</published>
<updated>@datetimeRFC3339(activity.activityDate)</updated>
<id>tag:@context.host,@helpers.date(activity.activityDate):activity:@activity.activityId</id>
<published>@helpers.datetimeRFC3339(activity.activityDate)</published>
<updated>@helpers.datetimeRFC3339(activity.activityDate)</updated>
<link type="text/html" rel="alternate" href="@context.baseUrl/@activity.userName/@activity.repositoryName" />
<title type="html">@removeHtml(activityMessage(activity.message))</title>
<title type="html">@helpers.removeHtml(helpers.activityMessage(activity.message))</title>
<author>
<name>@activity.activityUserName</name>
<uri>@url(activity.activityUserName)</uri>
<uri>@helpers.url(activity.activityUserName)</uri>
</author>
<content type="html">@activityMessage(activity.message)</content>
<content type="html">@helpers.activityMessage(activity.message)</content>
</entry>
}
</feed>

View File

@@ -1,16 +1,15 @@
@(repository: gitbucket.core.service.RepositoryService.RepositoryInfo,
groupAndPerm: Map[String, Boolean])(implicit context: gitbucket.core.controller.Context)
@import context._
@import gitbucket.core.view.helpers._
@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">
<form action="@helpers.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>
<div class="owner-select-target js-fork-owner-select-target enabled">@helpers.avatar(context.loginAccount.get.userName, 100)<span class="owner css-truncate" title="@@@context.loginAccount.get.userName">@@@context.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">@helpers.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">@helpers.avatar(groupName, 100)<span class="owner css-truncate" title="@@@groupName">@@@groupName</span></div>
}
}
</div>

View File

@@ -5,15 +5,14 @@
enableLineBreaks: Boolean,
enableTaskList: Boolean,
hasWritePermission: Boolean,
completionContext: String,
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._
@import gitbucket.core.view.helpers
<div class="tabbable">
<ul class="nav nav-tabs fill-width" style="margin-bottom: 10px;">
<li class="active"><a href="#tab@uid" data-toggle="tab">Write</a></li>
@@ -28,11 +27,11 @@
@if(style.nonEmpty){ style="@style"}
@if(styleClass.nonEmpty){ class="@styleClass" }>@content</textarea>
}
@if(enableWikiLink){
@textarea
} else {
@helper.html.attached(repository.owner, repository.name)(textarea)
}
@gitbucket.core.helper.html.attached(
repository = repository,
completionContext = completionContext,
generateScript = !enableWikiLink
)(textarea)
</div>
<div class="tab-pane" id="tab@(uid+1)">
<div class="markdown-body" id="preview-area@uid">
@@ -40,8 +39,8 @@
</div>
</div>
</div>
<link href="@assets/vendors/google-code-prettify/prettify.css" type="text/css" rel="stylesheet"/>
<script src="@assets/vendors/google-code-prettify/prettify.js"></script>
<link href="@helpers.assets/vendors/google-code-prettify/prettify.css" type="text/css" rel="stylesheet"/>
<script src="@helpers.assets/vendors/google-code-prettify/prettify.js"></script>
<script>
$(function(){
@if(elastic){
@@ -49,8 +48,8 @@ $(function(){
}
$('#preview@uid').click(function(){
$('#preview-area@uid').html('<img src="@assets/common/images/indicator.gif"> Previewing...');
$.post('@url(repository)/_preview', {
$('#preview-area@uid').html('<img src="@helpers.assets/common/images/indicator.gif"> Previewing...');
$.post('@helpers.url(repository)/_preview', {
content : $('#content@uid').val(),
enableWikiLink : @enableWikiLink,
enableRefsLink : @enableRefsLink,

View File

@@ -1,7 +1,4 @@
@(repository: gitbucket.core.service.RepositoryService.RepositoryInfo, large: Boolean)(implicit context: gitbucket.core.controller.Context)
@import gitbucket.core.service.RepositoryService
@import context._
@import gitbucket.core.view.helpers._
@if(repository.repository.isPrivate){
<i class="@{if(large){"mega-"}}octicon octicon-lock"></i>
} else {

View File

@@ -1,8 +1,7 @@
@(account: Option[gitbucket.core.model.Account])(implicit context: gitbucket.core.controller.Context)
@import context._
<div id="avatar" class="muted">
@if(account.nonEmpty && account.get.image.nonEmpty){
<img src="@path/@account.get.userName/_avatar" style="with: 120px; height: 120px;"/>
<img src="@context.path/@account.get.userName/_avatar" style="with: 120px; height: 120px;"/>
} else {
<div id="clickable">Upload Image</div>
}
@@ -17,7 +16,7 @@
<script>
$(function(){
var dropzone = new Dropzone('div#clickable', {
url: '@path/upload/image',
url: '@context.path/upload/image',
previewsContainer: 'div#avatar',
parallelUploads: 1,
thumbnailWidth: 120,

View File

@@ -1,22 +1,21 @@
@(activities: List[gitbucket.core.model.Activity],
recentRepositories: List[gitbucket.core.service.RepositoryService.RepositoryInfo],
userRepositories: List[gitbucket.core.service.RepositoryService.RepositoryInfo])(implicit context: gitbucket.core.controller.Context)
@import context._
@import gitbucket.core.view.helpers._
@main("GitBucket"){
@dashboard.html.sidebar(recentRepositories, userRepositories){
@settings.information.map { information =>
@import gitbucket.core.view.helpers
@gitbucket.core.html.main("GitBucket"){
@gitbucket.core.dashboard.html.sidebar(recentRepositories, userRepositories){
@context.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">&times;</button>
@Html(information)
</div>
}
@dashboard.html.tab()
@gitbucket.core.dashboard.html.tab()
<div class="container">
<div class="pull-right">
<a href="@path/activities.atom"><img src="@assets/common/images/feed.png" alt="activities"></a>
<a href="@context.path/activities.atom"><img src="@helpers.assets/common/images/feed.png" alt="activities"></a>
</div>
@helper.html.activities(activities)
@gitbucket.core.helper.html.activities(activities)
</div>
}
}

View File

@@ -2,15 +2,14 @@
reopenable: Boolean,
hasWritePermission: Boolean,
repository: gitbucket.core.service.RepositoryService.RepositoryInfo)(implicit context: gitbucket.core.controller.Context)
@import context._
@import gitbucket.core.view.helpers._
@if(loginAccount.isDefined){
@import gitbucket.core.view.helpers
@if(context.loginAccount.isDefined){
<hr/><br/>
<form method="POST" validate="true">
<div class="issue-avatar-image">@avatarLink(loginAccount.get.userName, 48)</div>
<div class="issue-avatar-image">@helpers.avatarLink(context.loginAccount.get.userName, 48)</div>
<div class="panel panel-default issue-comment-box">
<div class="panel-body">
@helper.html.preview(
@gitbucket.core.helper.html.preview(
repository = repository,
content = "",
enableWikiLink = false,
@@ -18,16 +17,17 @@
enableLineBreaks = true,
enableTaskList = true,
hasWritePermission = hasWritePermission,
completionContext = "issues",
style = "",
elastic = true,
tabIndex = 1
)
<div class="text-right">
<input type="hidden" name="issueId" value="@issue.issueId"/>
@if((reopenable || !issue.closed) && (hasWritePermission || issue.openedUserName == loginAccount.get.userName)){
<input type="submit" class="btn btn-default" tabindex="3" formaction="@url(repository)/issue_comments/state" value="@{if(issue.closed) "Reopen" else "Close"}" id="action"/>
@if((reopenable || !issue.closed) && (hasWritePermission || issue.openedUserName == context.loginAccount.get.userName)){
<input type="submit" class="btn btn-default" tabindex="3" formaction="@helpers.url(repository)/issue_comments/state" value="@{if(issue.closed) "Reopen" else "Close"}" id="action"/>
}
<input type="submit" class="btn btn-success" tabindex="2" formaction="@url(repository)/issue_comments/new" value="Comment"/>
<input type="submit" class="btn btn-success" tabindex="2" formaction="@helpers.url(repository)/issue_comments/new" value="Comment"/>
</div>
</div>
</div>

View File

@@ -3,22 +3,21 @@
hasWritePermission: Boolean,
repository: gitbucket.core.service.RepositoryService.RepositoryInfo,
pullreq: Option[gitbucket.core.model.PullRequest] = None)(implicit context: gitbucket.core.controller.Context)
@import context._
@import gitbucket.core.view.helpers._
@import gitbucket.core.view.helpers
@import gitbucket.core.model.CommitComment
@if(issue.isDefined){
<div class="issue-avatar-image">@avatarLink(issue.get.openedUserName, 48)</div>
<div class="issue-avatar-image">@helpers.avatarLink(issue.get.openedUserName, 48)</div>
<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>
@helpers.user(issue.get.openedUserName, styleClass="username strong") <span class="muted">commented @gitbucket.core.helper.html.datetimeago(issue.get.registeredDate)</span>
<span class="pull-right">
@if(hasWritePermission || loginAccount.map(_.userName == issue.get.openedUserName).getOrElse(false)){
@if(hasWritePermission || context.loginAccount.map(_.userName == issue.get.openedUserName).getOrElse(false)){
<a href="#" data-issue-id="@issue.get.issueId"><i class="octicon octicon-pencil" aria-label="Edit"></i></a>
}
</span>
</div>
<div class="panel-body issue-content markdown-body" id="issueContent">
@markdown(
@helpers.markdown(
markdown = issue.get.content getOrElse "No description provided.",
repository = repository,
enableWikiLink = false,
@@ -36,20 +35,20 @@
case comment: gitbucket.core.model.IssueComment => {
@if(comment.action != "close" && comment.action != "reopen" && comment.action != "delete_branch"
&& comment.action != "commit" && comment.action != "refer"){
<div class="issue-avatar-image">@avatarLink(comment.commentedUserName, 48)</div>
<div class="issue-avatar-image">@helpers.avatarLink(comment.commentedUserName, 48)</div>
<div class="panel panel-default issue-comment-box" id="comment-@comment.commentId">
<div class="panel-heading">
@user(comment.commentedUserName, styleClass="username strong")
@helpers.user(comment.commentedUserName, styleClass="username strong")
<span class="muted">
@if(comment.action == "comment"){
commented
} else {
referenced the @issueOrPullRequest()
}
@helper.html.datetimeago(comment.registeredDate)
@gitbucket.core.helper.html.datetimeago(comment.registeredDate)
</span>
@if(comment.action != "commit" && comment.action != "merge" && comment.action != "refer"
&& (hasWritePermission || loginAccount.map(_.userName == comment.commentedUserName).getOrElse(false))){
&& (hasWritePermission || context.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>&nbsp;
<a href="#" data-comment-id="@comment.commentId"><i class="octicon octicon-x" aria-label="Remove"></i></a>
@@ -57,7 +56,7 @@
}
</div>
<div class="panel-body issue-content markdown-body" id="commentContent-@comment.commentId">
@markdown(
@helpers.markdown(
markdown = comment.content,
repository = repository,
enableWikiLink = false,
@@ -80,20 +79,20 @@
<div class="discussion-item discussion-item-commit">
<div class="discussion-item-header">
<span class="discussion-item-icon"><i class="octicon octicon-bookmark"></i></span>
@avatar(comment.commentedUserName, 16)
@user(comment.commentedUserName, styleClass="username strong")
@helpers.avatar(comment.commentedUserName, 16)
@helpers.user(comment.commentedUserName, styleClass="username strong")
added a commit that referenced this @issueOrPullRequest()
@helper.html.datetimeago(comment.registeredDate)
@gitbucket.core.helper.html.datetimeago(comment.registeredDate)
</div>
<div style="discussion-item-content">
@commitId.map{ id =>
<span class="pull-right"><a href="@path/@repository.owner/@repository.name/commit/@id" class="monospace">@id.substring(0, 7)</a></span>
<span class="pull-right"><a href="@context.path/@repository.owner/@repository.name/commit/@id" class="monospace">@id.substring(0, 7)</a></span>
}
<span class="discussion-item-content-head"><i class="octicon octicon-git-commit"></i></span>
@link(head, repository)
@helpers.link(head, repository)
@rest.map{ content =>
<a href="javascript:void(0)" class="omit" onclick="$(this.parentNode).find('.discussion-item-content-text').toggle()">...</a>
<pre class="reset discussion-item-content-text" style="display:none">@link(content, repository)</pre>
<pre class="reset discussion-item-content-text" style="display:none">@helpers.link(content, repository)</pre>
}
</div>
</div>
@@ -103,14 +102,14 @@
<div class="discussion-item discussion-item-refer">
<div class="discussion-item-header">
<span class="discussion-item-icon"><i class="octicon octicon-bookmark"></i></span>
@avatar(comment.commentedUserName, 16)
@user(comment.commentedUserName, styleClass="username strong")
@helpers.avatar(comment.commentedUserName, 16)
@helpers.user(comment.commentedUserName, styleClass="username strong")
referenced the @issueOrPullRequest()
@helper.html.datetimeago(comment.registeredDate)
@gitbucket.core.helper.html.datetimeago(comment.registeredDate)
</div>
<div style="discussion-item-content">
@defining(comment.content.split(":")){ case Array(issueId, rest @ _*) =>
<strong>@issueLink(repository, issueId.toInt): @rest.mkString(":")</strong>
<strong>@helpers.issueLink(repository, issueId.toInt): @rest.mkString(":")</strong>
}
</div>
</div>
@@ -119,8 +118,8 @@
<div class="discussion-item discussion-item-merge">
<div class="discussion-item-header">
<span class="discussion-item-icon"><i class="octicon octicon-git-merge"></i></span>
@avatar(comment.commentedUserName, 16)
@user(comment.commentedUserName, styleClass="username strong")
@helpers.avatar(comment.commentedUserName, 16)
@helpers.user(comment.commentedUserName, styleClass="username strong")
merged commit
<code>@pullreq.map(_.commitIdTo.substring(0, 7))</code> into
@if(pullreq.get.requestUserName == repository.owner){
@@ -128,7 +127,7 @@
} else {
<code>@pullreq.map(_.userName):@pullreq.map(_.branch)</code> from <code>@pullreq.map(_.requestUserName):@pullreq.map(_.requestBranch)</code>
}
@helper.html.datetimeago(comment.registeredDate)
@gitbucket.core.helper.html.datetimeago(comment.registeredDate)
</div>
</div>
}
@@ -136,10 +135,10 @@
<div class="discussion-item discussion-item-close">
<div class="discussion-item-header">
<span class="discussion-item-icon"><i class="octicon octicon-circle-slash"></i></span>
@avatar(comment.commentedUserName, 16)
@user(comment.commentedUserName, styleClass="username strong")
@helpers.avatar(comment.commentedUserName, 16)
@helpers.user(comment.commentedUserName, styleClass="username strong")
closed this @issueOrPullRequest()
@helper.html.datetimeago(comment.registeredDate)
@gitbucket.core.helper.html.datetimeago(comment.registeredDate)
</div>
</div>
}
@@ -147,10 +146,10 @@
<div class="discussion-item discussion-item-reopen">
<div class="discussion-item-header">
<span class="discussion-item-icon"><i class="octicon octicon-primitive-dot"></i></span>
@avatar(comment.commentedUserName, 16)
@user(comment.commentedUserName, styleClass="username strong")
@helpers.avatar(comment.commentedUserName, 16)
@helpers.user(comment.commentedUserName, styleClass="username strong")
reopened the @issueOrPullRequest()
@helper.html.datetimeago(comment.registeredDate)
@gitbucket.core.helper.html.datetimeago(comment.registeredDate)
</div>
</div>
}
@@ -158,16 +157,16 @@
<div class="discussion-item discussion-item-delete_branch">
<div class="discussion-item-header">
<span class="discussion-item-icon"><i class="octicon octicon-git-branch"></i></span>
@avatar(comment.commentedUserName, 16)
@user(comment.commentedUserName, styleClass="username strong")
@helpers.avatar(comment.commentedUserName, 16)
@helpers.user(comment.commentedUserName, styleClass="username strong")
deleted the <code>@pullreq.map(_.requestBranch)</code> branch
@helper.html.datetimeago(comment.registeredDate)
@gitbucket.core.helper.html.datetimeago(comment.registeredDate)
</div>
</div>
}
}
case comment: CommitComment => {
@helper.html.commitcomment(comment, hasWritePermission, repository, pullreq.map(_.commitIdTo))
@gitbucket.core.helper.html.commitcomment(comment, hasWritePermission, repository, pullreq.map(_.commitIdTo))
}
}
<script>
@@ -175,12 +174,12 @@ $(function(){
@if(issue.isDefined){
$('.issue-comment-box i.octicon-pencil').click(function(){
var id = $(this).closest('a').data('comment-id');
var url = '@url(repository)/issue_comments/_data/' + id;
var url = '@helpers.url(repository)/issue_comments/_data/' + id;
var $content = $('#commentContent-' + id);
if(!id){
id = $(this).closest('a').data('issue-id');
url = '@url(repository)/issues/_data/' + id;
url = '@helpers.url(repository)/issues/_data/' + id;
$content = $('#issueContent');
}
@@ -196,7 +195,7 @@ $(function(){
$('.issue-comment-box i.octicon-x').click(function(){
if(confirm('Are you sure you want to delete this?')) {
var id = $(this).closest('a').data('comment-id');
$.post('@url(repository)/issue_comments/delete/' + id,
$.post('@helpers.url(repository)/issue_comments/delete/' + id,
function(data){
if(data > 0) {
$('#comment-' + id).prev('div.issue-avatar-image').remove();
@@ -209,7 +208,7 @@ $(function(){
}
$(document).on('click', '.commit-comment-box i.octicon-pencil', function(){
var id = $(this).closest('a').data('comment-id');
var url = '@url(repository)/commit_comments/_data/' + id;
var url = '@helpers.url(repository)/commit_comments/_data/' + id;
var $content = $('.commit-commentContent-' + id, $(this).closest('.commit-comment-box'));
$.get(url,
@@ -224,7 +223,7 @@ $(function(){
$(document).on('click', '.commit-comment-box i.octicon-x', function(){
if(confirm('Are you sure you want to delete this?')) {
var id = $(this).closest('a').data('comment-id');
$.post('@url(repository)/commit_comments/delete/' + id,
$.post('@helpers.url(repository)/commit_comments/delete/' + id,
function(data){
if(data > 0) {
$('.commit-comment-' + id).closest('.not-diff').remove();
@@ -260,13 +259,13 @@ $(function(){
var $commentContent = $(ev.target).parents('div[class*=commit-commentContent-]'),
commentId = $commentContent.attr('class').match(/commit-commentContent-.+/)[0].replace(/commit-commentContent-/, ''),
checkboxes = $commentContent.find(':checkbox');
$.get('@url(repository)/commit_comments/_data/' + commentId,
$.get('@helpers.url(repository)/commit_comments/_data/' + commentId,
{
dataType : 'html'
},
function(responseContent){
$.ajax({
url: '@url(repository)/commit_comments/edit/' + commentId,
url: '@helpers.url(repository)/commit_comments/edit/' + commentId,
type: 'POST',
data: {
issueId : 0,
@@ -283,13 +282,13 @@ $(function(){
@if(issue.isDefined){
$('#issueContent').on('click', ':checkbox', function(ev){
var checkboxes = $('#issueContent :checkbox');
$.get('@url(repository)/issues/_data/@issue.get.issueId',
$.get('@helpers.url(repository)/issues/_data/@issue.get.issueId',
{
dataType : 'html'
},
function(responseContent){
$.ajax({
url: '@url(repository)/issues/edit/@issue.get.issueId',
url: '@helpers.url(repository)/issues/edit/@issue.get.issueId',
type: 'POST',
data: {
title : $('#issueTitle').text(),
@@ -304,13 +303,13 @@ $(function(){
var $commentContent = $(ev.target).parents('div[id^=commentContent-]'),
commentId = $commentContent.attr('id').replace(/commentContent-/, ''),
checkboxes = $commentContent.find(':checkbox');
$.get('@url(repository)/issue_comments/_data/' + commentId,
$.get('@helpers.url(repository)/issue_comments/_data/' + commentId,
{
dataType : 'html'
},
function(responseContent){
$.ajax({
url: '@url(repository)/issue_comments/edit/' + commentId,
url: '@helpers.url(repository)/issue_comments/edit/' + commentId,
type: 'POST',
data: {
issueId : 0,

View File

@@ -1,11 +1,11 @@
@(issue: gitbucket.core.model.Issue, statusInfo: Option[gitbucket.core.service.IssuesService.CommitStatusInfo])(implicit context: gitbucket.core.controller.Context)
@import gitbucket.core.view.helpers._
@import gitbucket.core.view.helpers
@statusInfo.map{ status =>
@if(status.count==1 && status.state.isDefined){
@if(status.targetUrl.isDefined){
<a href="@status.targetUrl.get" class="text-@status.state.get.name" data-toggle="tooltip" title="@status.state.get.name : @status.description.getOrElse(status.context.get)">@commitStateIcon(status.state.get)</a>
<a href="@status.targetUrl.get" class="text-@status.state.get.name" data-toggle="tooltip" title="@status.state.get.name : @status.description.getOrElse(status.context.get)">@helpers.commitStateIcon(status.state.get)</a>
}else{
<span class="text-@status.state.get.name">@commitStateIcon(status.state.get)
<span class="text-@status.state.get.name">@helpers.commitStateIcon(status.state.get)
}
}else{
@defining(status.count==status.successCount){ isSuccess =>

View File

@@ -3,16 +3,15 @@
labels: List[gitbucket.core.model.Label],
hasWritePermission: Boolean,
repository: gitbucket.core.service.RepositoryService.RepositoryInfo)(implicit context: gitbucket.core.controller.Context)
@import context._
@import gitbucket.core.view.helpers._
@html.main(s"New Issue - ${repository.owner}/${repository.name}", Some(repository)){
@html.menu("issues", repository){
<form action="@url(repository)/issues/new" method="POST" validate="true" class="form-group">
@import gitbucket.core.view.helpers
@gitbucket.core.html.main(s"New Issue - ${repository.owner}/${repository.name}", Some(repository)){
@gitbucket.core.html.menu("issues", repository){
<form action="@helpers.url(repository)/issues/new" method="POST" validate="true" class="form-group">
<div class="row-fluid">
<div class="col-md-9">
<span id="error-title" class="error"></span>
<input type="text" id="issue-title" name="title" class="form-control" value="" placeholder="Title" style="margin-bottom: 6px;" autofocus/>
@helper.html.preview(
@gitbucket.core.helper.html.preview(
repository = repository,
content = "",
enableWikiLink = false,
@@ -20,6 +19,7 @@
enableLineBreaks = true,
enableTaskList = true,
hasWritePermission = hasWritePermission,
completionContext = "issues",
style = "height: 200px; max-height: 250px;",
elastic = true
)
@@ -28,7 +28,7 @@
</div>
</div>
<div class="col-md-3">
@issueinfo(None, Nil, Nil, collaborators, milestones.map(x => (x, 0, 0)), labels, hasWritePermission, repository)
@gitbucket.core.issues.html.issueinfo(None, Nil, Nil, collaborators, milestones.map(x => (x, 0, 0)), labels, hasWritePermission, repository)
</div>
</div>
</form>

View File

@@ -1,7 +1,7 @@
@(content: String, commentId: Int, owner: String, repository: String)(implicit context: gitbucket.core.controller.Context)
@import context._
@(content: String, commentId: Int,
repository: gitbucket.core.service.RepositoryService.RepositoryInfo)(implicit context: gitbucket.core.controller.Context)
<span id="error-edit-content-@commentId" class="error"></span>
@helper.html.attached(owner, repository){
@gitbucket.core.helper.html.attached(repository, "issues"){
<textarea id="edit-content-@commentId" class="form-control">@content</textarea>
}
<div>
@@ -19,7 +19,7 @@ $(function(){
$('#update-comment-@commentId').click(function(){
$('#update-comment-@commentId, #cancel-comment-@commentId').attr('disabled', 'disabled');
$.ajax({
url: '@path/@owner/@repository/issue_comments/edit/@commentId',
url: '@context.path/@repository.owner/@repository.name/issue_comments/edit/@commentId',
type: 'POST',
data: {
issueId : 0, // TODO
@@ -35,7 +35,7 @@ $(function(){
$('#cancel-comment-@commentId').click(function(){
$('#update-comment-@commentId, #cancel-comment-@commentId').attr('disabled', 'disabled');
$.get('@path/@owner/@repository/issue_comments/_data/@commentId', callback);
$.get('@context.path/@repository.owner/@repository.name/issue_comments/_data/@commentId', callback);
return false;
});
});

View File

@@ -1,6 +1,6 @@
@(content: Option[String], issueId: Int, owner: String, repository: String)(implicit context: gitbucket.core.controller.Context)
@import context._
@helper.html.attached(owner, repository){
@(content: Option[String], issueId: Int,
repository: gitbucket.core.service.RepositoryService.RepositoryInfo)(implicit context: gitbucket.core.controller.Context)
@gitbucket.core.helper.html.attached(repository, "issues"){
<textarea id="edit-content" class="form-control">@content.getOrElse("")</textarea>
}
<div>
@@ -17,7 +17,7 @@ $(function(){
$('#update-issue').click(function(){
$('#update, #cancel').attr('disabled', 'disabled');
$.ajax({
url: '@path/@owner/@repository/issues/edit/@issueId',
url: '@context.path/@repository.owner/@repository.name/issues/edit/@issueId',
type: 'POST',
data: {
content : $('#edit-content').val()
@@ -31,7 +31,7 @@ $(function(){
$('#cancel-issue').click(function(){
$('#update, #cancel').attr('disabled', 'disabled');
$.get('@path/@owner/@repository/issues/_data/@issueId', callback);
$.get('@context.path/@repository.owner/@repository.name/issues/_data/@issueId', callback);
return false;
});
});

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