Compare commits

...

61 Commits

Author SHA1 Message Date
Naoki Takezoe
3134bb0428 (refs #1687)Bump notification plugin to 1.1.0 2017-09-02 13:43:44 +09:00
Naoki Takezoe
134b5df6a2 (refs #1675)Drop debug option because it can't be configure at settings form 2017-09-02 13:43:21 +09:00
Naoki Takezoe
aab84d9275 Update README.md 2017-09-02 01:35:13 +09:00
Naoki Takezoe
5209c29d0f Update README.md 2017-09-02 01:32:34 +09:00
Naoki Takezoe
cc975f8ffa Release 4.16.0 2017-09-02 01:30:06 +09:00
Naoki Takezoe
125040f90b (refs #1688)Fix regex which is used to extract user and repository name from URL 2017-09-01 00:58:49 +09:00
Naoki Takezoe
7009aaeb24 Merge pull request #1675 from kounoike/pr-handle-errors
Handle errors to show errors prettify and logging it.
2017-08-29 01:26:21 +09:00
Naoki Takezoe
a135c6977f Merge pull request #1683 from int128/fix-api-branch-with-slash
Fix branch API did not accept branch name with slash
2017-08-29 01:23:18 +09:00
Naoki Takezoe
55991a6f17 Merge pull request #1690 from uli-heller/blocking-slick-32-0.0.10
blocking-slick-32: 0.0.9 -> 0.0.10
2017-08-29 01:20:18 +09:00
Naoki Takezoe
17e1de6174 Bump to 4.16.0-SNAPSHOT 2017-08-28 19:24:06 +09:00
Uli Heller
12a58f393e blocking-slick-32: 0.0.9 -> 0.0.10 2017-08-25 08:18:59 +02:00
Hidetake Iwata
ebb57a80e3 Fix branch API did not accept branch name with slash 2017-08-21 17:58:22 +09:00
Naoki Takezoe
7a53bd8766 Merge pull request #1672 from kounoike/pr-support-textmsg-in-email
Add support to MIME Text part in Email notification.
2017-08-21 01:04:00 +09:00
Naoki Takezoe
d8b46b194d Merge branch 'master' into pr-support-textmsg-in-email 2017-08-20 23:39:05 +09:00
Yuusuke KOUNOIKE
5db7d863ff Merge branch 'master' into pr-handle-errors 2017-08-20 22:28:34 +09:00
Naoki Takezoe
7d7c11aa1a Merge AnonymousAccessController and GitHubCompatibleAccessController 2017-08-20 22:18:36 +09:00
Naoki Takezoe
95748a2f2f Merge pull request #1678 from int128/github-compat-url
Improve GitHub compatible URL
2017-08-20 22:02:58 +09:00
KOUNOIKE Yuusuke
e77045abe3 Merge remote-tracking branch 'upstream/master' into pr-handle-errors
# Conflicts:
#	src/main/scala/gitbucket/core/controller/SystemSettingsController.scala
#	src/main/scala/gitbucket/core/service/SystemSettingsService.scala
#	src/test/scala/gitbucket/core/view/AvatarImageProviderSpec.scala
2017-08-20 21:50:34 +09:00
Naoki Takezoe
d73ddbdbcb Merge branch 'master' into github-compat-url 2017-08-20 21:41:10 +09:00
Naoki Takezoe
8893a4a456 Merge pull request #1681 from gitbucket/drop-jdk9-build
Drop JDK9 build
2017-08-20 21:40:21 +09:00
Naoki Takezoe
608112ce22 Drop JDK9 build 2017-08-20 21:29:36 +09:00
Naoki Takezoe
2e9155fbcc Merge branch 'master' into pr-support-textmsg-in-email 2017-08-19 01:59:14 +09:00
Naoki Takezoe
f33a30c697 Merge pull request #1676 from kounoike/pr-skin-selectable
add AdminLTE skin selection feature.
2017-08-18 20:15:22 +09:00
Hidetake Iwata
e208dd5966 Implement as controller instead of filter 2017-08-18 18:56:01 +09:00
KOUNOIKE Yuusuke
a6cb71f9c3 remove helpers.skinName. (by review comment) 2017-08-18 01:55:56 +09:00
Naoki Takezoe
91deeb969b Merge branch 'master' into pr-skin-selectable 2017-08-18 01:00:20 +09:00
KOUNOIKE Yuusuke
040d812f2a add debug option to SystemSettings. and error page shows stacktrace only for admin or debug settings. 2017-08-17 17:48:53 +09:00
Hidetake Iwata
772ac80764 Improve GitHub compatible URL 2017-08-16 10:09:08 +09:00
Yuusuke KOUNOIKE
ef67c94272 Merge branch 'master' into pr-support-textmsg-in-email 2017-08-15 03:26:17 +09:00
KOUNOIKE Yuusuke
fdb4a6bdc6 change interface to textMsg: String, htmlMsg: Option[String].
SystemSettingsController calls Mailer.send directly. fix it too.
2017-08-15 03:25:27 +09:00
Naoki Takezoe
57902af87c (refs #1553)Fixup 2017-08-15 03:25:27 +09:00
KOUNOIKE Yuusuke
92fea3ff01 fix test. 2017-08-15 02:44:52 +09:00
Naoki Takezoe
cbd16169e4 (refs #1553)Fixup 2017-08-15 02:02:17 +09:00
KOUNOIKE Yuusuke
299df34bf4 add AdminLTE skin selection feature. 2017-08-14 22:12:17 +09:00
KOUNOIKE Yuusuke
48a92df719 Handle errors to show errors prettify and logging it. 2017-08-14 18:50:02 +09:00
shimamoto
b806439e2f Merge branch 'master' into pr-support-textmsg-in-email 2017-08-14 18:45:34 +09:00
Naoki Takezoe
1db586c0bd Merge pull request #1671 from kounoike/pr-send-comment-at-merged
Call addedComment hook(send notification) at PR merged.
2017-08-14 18:08:57 +09:00
KOUNOIKE Yuusuke
26e2bfbf43 Call addedComment hook(send notification) at PR merged. 2017-08-14 14:02:30 +09:00
KOUNOIKE Yuusuke
28c4ac6a19 Add support to MIME Text part. 2017-08-14 13:25:25 +09:00
Naoki Takezoe
c0d9d68fca Merge pull request #1553 from kounoike/pr-show-commitstaus-in-commits
Show CommitStatus in commits page.
2017-08-14 12:01:40 +09:00
Yuusuke KOUNOIKE
122ed1dd0f Merge branch 'master' into pr-show-commitstaus-in-commits 2017-08-14 01:14:49 +09:00
Naoki Takezoe
f18b83999a Merge remote-tracking branch 'origin/master' 2017-08-13 01:59:17 +09:00
Naoki Takezoe
fc28aacb52 (refs #1664)Bump to markedj 1.0.14 2017-08-13 01:59:00 +09:00
Naoki Takezoe
2872da4f94 Merge pull request #1669 from kounoike/pr-fix-1552
Add query string for redirect, required by git-2.11-1.
2017-08-12 20:34:03 +09:00
KOUNOIKE Yuusuke
5d3c5e7f3c Add query string for redirect, it required by git-2.12. close #1552 2017-08-12 15:16:55 +09:00
Naoki Takezoe
a86fb480b2 Merge pull request #1668 from gitbucket/thread-safe-collection
Improve concurrency of initializaton of the plugin system
2017-08-12 04:45:59 +09:00
Naoki Takezoe
7daf33c149 Merge branch 'master' into thread-safe-collection 2017-08-12 04:37:14 +09:00
Naoki Takezoe
0d9afdc939 (refs #1664)Bump to markedj 1.0.14-SNAPSHOT 2017-08-12 03:46:47 +09:00
Naoki Takezoe
a9a26193cd Improve shutdown of the plugin system 2017-08-12 03:38:07 +09:00
Naoki Takezoe
684973ea85 Merge pull request #1666 from HairyFotr/patch-8
Fix lint and typos
2017-08-09 08:18:55 +09:00
HairyFotr
0149593272 Fix lint and typos 2017-08-08 21:37:35 +02:00
Naoki Takezoe
1ae3df76bd Use thread-safe collection 2017-08-07 17:53:32 +09:00
Naoki Takezoe
d8e0a06d93 Add the support guidelines 2017-08-06 14:07:40 +09:00
Naoki Takezoe
b2d842ddd0 (refs #1654)Add --validate_password option 2017-08-06 03:49:04 +09:00
Naoki Takezoe
580374208f Merge remote-tracking branch 'origin/master' 2017-08-06 03:38:25 +09:00
Naoki Takezoe
8ab96f09cb Update ISSUES.UPDATED_DATE column when issue attriibutes are updated 2017-08-06 03:38:22 +09:00
Naoki Takezoe
f5b6728358 Merge pull request #1659 from kounoike/pr-api-pr-merged
fix getMergedComment for PR API merged field.
2017-08-06 03:20:12 +09:00
Naoki Takezoe
c7d46ee18f Merge branch 'master' into pr-api-pr-merged 2017-08-06 02:59:51 +09:00
KOUNOIKE Yuusuke
f073112814 fixup 2017-07-30 13:52:27 +09:00
KOUNOIKE Yuusuke
9ee71e9f25 fix getMergedComment for PR API merged field. 2017-07-30 11:39:36 +09:00
KOUNOIKE Yuusuke
68d090f81a Show CommitStatus in commits page. 2017-04-22 21:17:37 +09:00
55 changed files with 401 additions and 286 deletions

View File

@@ -1,7 +1,8 @@
# Guideline for Issues # The guidelines for contributing
- At first, see [Wiki](https://github.com/gitbucket/gitbucket/wiki) and check issues whether there is a same question or request in the past. - At first, see [Wiki](https://github.com/gitbucket/gitbucket/wiki) and check issues and pull requests whether there is a same request in the past.
- If you can't find same question and report, send it to [gitter room](https://gitter.im/gitbucket/gitbucket) before raising an issue. - The highest priority of GitBucket is the ease of installation and API compatibility with GitHub, so your feature request might be rejected if they go against those principles. If you don't wanna waste your time to make a pull request, ask us about your idea at [gitter room](https://gitter.im/gitbucket/gitbucket) before staring your work.
- We can also support in Japanese other than English at [gitter room for Japanese](https://gitter.im/gitbucket/gitbucket_ja). - We can also support in Japanese other than English at [gitter room for Japanese](https://gitter.im/gitbucket/gitbucket_ja).
- Write an issue in English. At least, write subject in English. - You can edit the GitBucket documentation on Wiki if you have a GitHub account. When you find any mistakes or lacks in the documentation, please update it directly.
- The highest priority of GitBucket is the ease of installation and API compatibility with GitHub, so your feature request might be rejected if they go against those principles. - Write an issue, a pull request, commit messages and comments in source code in English.
- All your contributions are handled as [Apache Software License, Version 2.0](https://github.com/gitbucket/gitbucket/blob/master/LICENSE). When you create a pull request or update the documentation, we assume you agreed this clause.

View File

@@ -1,4 +1,4 @@
### Before submitting an issue to Gitbucket I have first: ### Before submitting an issue to GitBucket I have first:
- [] read the [contribution guidelines](https://github.com/gitbucket/gitbucket/blob/master/.github/CONTRIBUTING.md) - [] read the [contribution guidelines](https://github.com/gitbucket/gitbucket/blob/master/.github/CONTRIBUTING.md)
- [] searched for similar already existing issue - [] searched for similar already existing issue
@@ -9,7 +9,7 @@
## Issue ## Issue
**Impacted version**: xxxx **Impacted version**: xxxx
**Deployment mode**: *explain here how you use gitbucket : standalone app, under webcontainer (which one), with an http frontend (nginx, httpd, ...)* **Deployment mode**: *explain here how you use GitBucket : standalone app, under webcontainer (which one), with an http frontend (nginx, httpd, ...)*
**Problem description**: **Problem description**:
- *be as explicit has you can* - *be as explicit has you can*

View File

@@ -1,4 +1,4 @@
### Before submitting a pull-request to Gitbucket I have first: ### Before submitting a pull-request to GitBucket I have first:
- [] read the [contribution guidelines](https://github.com/gitbucket/gitbucket/blob/master/.github/CONTRIBUTING.md) - [] read the [contribution guidelines](https://github.com/gitbucket/gitbucket/blob/master/.github/CONTRIBUTING.md)
- [] rebased my branch over master - [] rebased my branch over master

6
.github/SUPPORT.md vendored Normal file
View File

@@ -0,0 +1,6 @@
# The support guidelines
- At first, see [Wiki](https://github.com/gitbucket/gitbucket/wiki) and check issues whether there is a same question or request in the past.
- If you can't find same question and report, send it to [gitter room](https://gitter.im/gitbucket/gitbucket) before raising an issue.
- We can also support in Japanese other than English at [gitter room for Japanese](https://gitter.im/gitbucket/gitbucket_ja).
- Write an issue in English. Since we can't support issues written in other languages, we close them forcibly.

View File

@@ -1,5 +1,7 @@
language: scala language: scala
sudo: true sudo: true
jdk:
- oraclejdk8
script: script:
- sbt test - sbt test
before_script: before_script:
@@ -14,40 +16,3 @@ cache:
- $HOME/.coursier - $HOME/.coursier
- $HOME/.embedmysql - $HOME/.embedmysql
- $HOME/.embedpostgresql - $HOME/.embedpostgresql
matrix:
include:
- jdk: oraclejdk8
addons:
apt:
packages:
- libaio1
- dist: trusty
group: edge
sudo: required
jdk: oraclejdk9
addons:
apt:
packages:
- libaio1
before_install:
- cd ~
- JDK9_URL=`curl http://jdk.java.net/9/ | grep "lin64JDK" | grep "tar.gz\"" | sed -e "s/\"/ /g" | awk '{print $5}'`
- wget -O jdk-9_linux-x64_bin.tar.gz $JDK9_URL
- tar -xzf jdk-9_linux-x64_bin.tar.gz
- cd -
script:
# https://github.com/sbt/sbt/pull/2951
- git clone https://github.com/retronym/java9-rt-export
- cd java9-rt-export/
- git checkout 1019a2873d057dd7214f4135e84283695728395d
- jdk_switcher use oraclejdk8
- sbt package
# - jdk_switcher use oraclejdk9
- export JAVA_HOME=~/jdk-9
- PATH=$JAVA_HOME/bin:$PATH
- java -version
- mkdir -p $HOME/.sbt/0.13/java9-rt-ext; java -jar target/java9-rt-export-*.jar $HOME/.sbt/0.13/java9-rt-ext/rt.jar
- jar tf $HOME/.sbt/0.13/java9-rt-ext/rt.jar | grep java/lang/Object
- cd ..
- wget https://raw.githubusercontent.com/paulp/sbt-extras/9ade5fa54914ca8aded44105bf4b9a60966f3ccd/sbt && chmod +x ./sbt
- ./sbt -Dscala.ext.dirs=$HOME/.sbt/0.13/java9-rt-ext test

View File

@@ -59,7 +59,7 @@ GitBucket has a plug-in system that allows extra functionality. Officially the f
- [gitbucket-pages-plugin](https://github.com/gitbucket/gitbucket-pages-plugin) - [gitbucket-pages-plugin](https://github.com/gitbucket/gitbucket-pages-plugin)
- [gitbucket-notifications-plugin](https://github.com/gitbucket/gitbucket-notifications-plugin) - [gitbucket-notifications-plugin](https://github.com/gitbucket/gitbucket-notifications-plugin)
You can find more plugins made by the community at [GitBucket community plugins](http://gitbucket-plugins.github.io/). You can find more plugins made by the community at [GitBucket community plugins](https://gitbucket-plugins.github.io/).
Support Support
-------- --------
@@ -72,6 +72,11 @@ Support
Release Notes Release Notes
------------- -------------
### 4.16.0 - 2 Sep 2017
- Support AdminLTE color skin
- Improve unexpected error handling
- Show commit status on the commits list
### 4.15.0 - 5 Aug 2017 ### 4.15.0 - 5 Aug 2017
- Bundle GitBucket organization plugins - Bundle GitBucket organization plugins
- Notifications plugin - Notifications plugin

View File

@@ -1,6 +1,6 @@
val Organization = "io.github.gitbucket" val Organization = "io.github.gitbucket"
val Name = "gitbucket" val Name = "gitbucket"
val GitBucketVersion = "4.15.0" val GitBucketVersion = "4.16.0"
val ScalatraVersion = "2.5.0" val ScalatraVersion = "2.5.0"
val JettyVersion = "9.3.19.v20170502" val JettyVersion = "9.3.19.v20170502"
@@ -29,13 +29,13 @@ libraryDependencies ++= Seq(
"io.github.gitbucket" %% "scalatra-forms" % "1.1.0", "io.github.gitbucket" %% "scalatra-forms" % "1.1.0",
"commons-io" % "commons-io" % "2.5", "commons-io" % "commons-io" % "2.5",
"io.github.gitbucket" % "solidbase" % "1.0.2", "io.github.gitbucket" % "solidbase" % "1.0.2",
"io.github.gitbucket" % "markedj" % "1.0.13", "io.github.gitbucket" % "markedj" % "1.0.14",
"org.apache.commons" % "commons-compress" % "1.13", "org.apache.commons" % "commons-compress" % "1.13",
"org.apache.commons" % "commons-email" % "1.4", "org.apache.commons" % "commons-email" % "1.4",
"org.apache.httpcomponents" % "httpclient" % "4.5.3", "org.apache.httpcomponents" % "httpclient" % "4.5.3",
"org.apache.sshd" % "apache-sshd" % "1.4.0" exclude("org.slf4j","slf4j-jdk14"), "org.apache.sshd" % "apache-sshd" % "1.4.0" exclude("org.slf4j","slf4j-jdk14"),
"org.apache.tika" % "tika-core" % "1.14", "org.apache.tika" % "tika-core" % "1.14",
"com.github.takezoe" %% "blocking-slick-32" % "0.0.9", "com.github.takezoe" %% "blocking-slick-32" % "0.0.10",
"joda-time" % "joda-time" % "2.9.9", "joda-time" % "joda-time" % "2.9.9",
"com.novell.ldap" % "jldap" % "2009-10-07", "com.novell.ldap" % "jldap" % "2009-10-07",
"com.h2database" % "h2" % "1.4.195", "com.h2database" % "h2" % "1.4.195",

View File

@@ -1,7 +1,7 @@
JRebel integration (optional) JRebel integration (optional)
============================= =============================
[JRebel](http://zeroturnaround.com/software/jrebel/) is a JVM plugin that makes developing web apps much faster. [JRebel](https://zeroturnaround.com/software/jrebel/) is a JVM plugin that makes developing web apps much faster.
JRebel is generally able to eliminate the need for the following slow "app restart" in sbt following a code change: JRebel is generally able to eliminate the need for the following slow "app restart" in sbt following a code change:
``` ```
@@ -22,12 +22,12 @@ Sign up for a [usage plan](https://my.jrebel.com/). You will need to create an a
## 2. Download JRebel ## 2. Download JRebel
Download the most recent ["nosetup" JRebel zip](http://zeroturnaround.com/software/jrebel/download/prev-releases/). Download the most recent ["nosetup" JRebel zip](https://zeroturnaround.com/software/jrebel/download/prev-releases/).
Next, unzip the downloaded file. Next, unzip the downloaded file.
## 3. Activate ## 3. Activate
Follow the [instructions on the JRebel website](http://zeroturnaround.com/software/jrebel/download/prev-releases/) to activate your downloaded JRebel. Follow the [instructions on the JRebel website](https://zeroturnaround.com/software/jrebel/download/prev-releases/) to activate your downloaded JRebel.
You can use the default settings for all the configurations. You can use the default settings for all the configurations.

View File

@@ -5,9 +5,9 @@
"description": "Provides notifications feature on GitBucket.", "description": "Provides notifications feature on GitBucket.",
"versions": [ "versions": [
{ {
"version": "1.0.0", "version": "1.1.0",
"range": ">=4.15.0", "range": ">=4.16.0",
"file": "gitbucket-notifications-plugin_2.12-1.0.0.jar" "file": "gitbucket-notifications-plugin_2.12-1.1.0.jar"
} }
], ],
"default": true "default": true

View File

@@ -51,6 +51,9 @@ public class JettyLauncher {
case "--plugin_dir": case "--plugin_dir":
System.setProperty("gitbucket.pluginDir", dim[1]); System.setProperty("gitbucket.pluginDir", dim[1]);
break; break;
case "--validate_password":
System.setProperty("gitbucket.validate.password", dim[1]);
break;
} }
} }
} }

View File

@@ -25,11 +25,9 @@ class ScalatraBootstrap extends LifeCycle with SystemSettingsService {
context.getFilterRegistration("gitAuthenticationFilter").addMappingForUrlPatterns(EnumSet.allOf(classOf[DispatcherType]), true, "/git/*") context.getFilterRegistration("gitAuthenticationFilter").addMappingForUrlPatterns(EnumSet.allOf(classOf[DispatcherType]), true, "/git/*")
context.addFilter("apiAuthenticationFilter", new ApiAuthenticationFilter) context.addFilter("apiAuthenticationFilter", new ApiAuthenticationFilter)
context.getFilterRegistration("apiAuthenticationFilter").addMappingForUrlPatterns(EnumSet.allOf(classOf[DispatcherType]), true, "/api/v3/*") context.getFilterRegistration("apiAuthenticationFilter").addMappingForUrlPatterns(EnumSet.allOf(classOf[DispatcherType]), true, "/api/v3/*")
context.addFilter("ghCompatRepositoryAccessFilter", new GHCompatRepositoryAccessFilter)
context.getFilterRegistration("ghCompatRepositoryAccessFilter").addMappingForUrlPatterns(EnumSet.allOf(classOf[DispatcherType]), true, "/*")
// Register controllers // Register controllers
context.mount(new AnonymousAccessController, "/*") context.mount(new PreProcessController, "/*")
context.addFilter("pluginControllerFilter", new PluginControllerFilter) context.addFilter("pluginControllerFilter", new PluginControllerFilter)
context.getFilterRegistration("pluginControllerFilter").addMappingForUrlPatterns(EnumSet.allOf(classOf[DispatcherType]), true, "/*") context.getFilterRegistration("pluginControllerFilter").addMappingForUrlPatterns(EnumSet.allOf(classOf[DispatcherType]), true, "/*")

View File

@@ -40,5 +40,6 @@ object GitBucketCoreModule extends Module("gitbucket-core",
new SqlMigration("update/gitbucket-core_4.14.sql") new SqlMigration("update/gitbucket-core_4.14.sql")
), ),
new Version("4.14.1"), new Version("4.14.1"),
new Version("4.15.0") new Version("4.15.0"),
new Version("4.16.0")
) )

View File

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

View File

@@ -5,14 +5,15 @@ import gitbucket.core.model._
import gitbucket.core.service.IssuesService.IssueSearchCondition import gitbucket.core.service.IssuesService.IssueSearchCondition
import gitbucket.core.service.PullRequestService._ import gitbucket.core.service.PullRequestService._
import gitbucket.core.service._ import gitbucket.core.service._
import gitbucket.core.util.SyntaxSugars._
import gitbucket.core.util.Directory._ import gitbucket.core.util.Directory._
import gitbucket.core.util.JGitUtil._
import gitbucket.core.util._
import gitbucket.core.util.Implicits._ import gitbucket.core.util.Implicits._
import gitbucket.core.view.helpers.{renderMarkup, isRenderable} import gitbucket.core.util.JGitUtil._
import gitbucket.core.util.SyntaxSugars._
import gitbucket.core.util._
import gitbucket.core.view.helpers.{isRenderable, renderMarkup}
import org.eclipse.jgit.api.Git import org.eclipse.jgit.api.Git
import org.scalatra.{NoContent, UnprocessableEntity, Created} import org.scalatra.{Created, NoContent, UnprocessableEntity}
import scala.collection.JavaConverters._ import scala.collection.JavaConverters._
class ApiController extends ApiControllerBase class ApiController extends ApiControllerBase
@@ -124,10 +125,10 @@ trait ApiControllerBase extends ControllerBase {
/** /**
* https://developer.github.com/v3/repos/branches/#get-branch * https://developer.github.com/v3/repos/branches/#get-branch
*/ */
get ("/api/v3/repos/:owner/:repo/branches/:branch")(referrersOnly { repository => get ("/api/v3/repos/:owner/:repo/branches/*")(referrersOnly { repository =>
//import gitbucket.core.api._ //import gitbucket.core.api._
(for{ (for{
branch <- params.get("branch") if repository.branchList.contains(branch) branch <- params.get("splat") if repository.branchList.contains(branch)
br <- getBranches(repository.owner, repository.name, repository.repository.defaultBranch, repository.repository.originUserName.isEmpty).find(_.name == branch) br <- getBranches(repository.owner, repository.name, repository.repository.defaultBranch, repository.repository.originUserName.isEmpty).find(_.name == branch)
} yield { } yield {
val protection = getProtectedBranchInfo(repository.owner, repository.name, branch) val protection = getProtectedBranchInfo(repository.owner, repository.name, branch)
@@ -286,10 +287,10 @@ trait ApiControllerBase extends ControllerBase {
/** /**
* https://developer.github.com/v3/repos/#enabling-and-disabling-branch-protection * https://developer.github.com/v3/repos/#enabling-and-disabling-branch-protection
*/ */
patch("/api/v3/repos/:owner/:repo/branches/:branch")(ownerOnly { repository => patch("/api/v3/repos/:owner/:repo/branches/*")(ownerOnly { repository =>
import gitbucket.core.api._ import gitbucket.core.api._
(for{ (for{
branch <- params.get("branch") if repository.branchList.contains(branch) branch <- params.get("splat") if repository.branchList.contains(branch)
protection <- extractFromJsonBody[ApiBranchProtection.EnablingAndDisabling].map(_.protection) protection <- extractFromJsonBody[ApiBranchProtection.EnablingAndDisabling].map(_.protection)
br <- getBranches(repository.owner, repository.name, repository.repository.defaultBranch, repository.repository.originUserName.isEmpty).find(_.name == branch) br <- getBranches(repository.owner, repository.name, repository.repository.defaultBranch, repository.repository.originUserName.isEmpty).find(_.name == branch)
} yield { } yield {
@@ -381,7 +382,7 @@ trait ApiControllerBase extends ControllerBase {
get("/api/v3/repos/:owner/:repository/issues/:id/comments")(referrersOnly { repository => get("/api/v3/repos/:owner/:repository/issues/:id/comments")(referrersOnly { repository =>
(for{ (for{
issueId <- params("id").toIntOpt issueId <- params("id").toIntOpt
comments = getCommentsForApi(repository.owner, repository.name, issueId.toInt) comments = getCommentsForApi(repository.owner, repository.name, issueId)
} yield { } yield {
JsonFormat(comments.map{ case (issueComment, user, issue) => ApiComment(issueComment, RepositoryName(repository), issueId, ApiUser(user), issue.isPullRequest) }) JsonFormat(comments.map{ case (issueComment, user, issue) => ApiComment(issueComment, RepositoryName(repository), issueId, ApiUser(user), issue.isPullRequest) })
}) getOrElse NotFound() }) getOrElse NotFound()

View File

@@ -26,6 +26,7 @@ import org.eclipse.jgit.lib.ObjectId
import org.eclipse.jgit.revwalk.RevCommit import org.eclipse.jgit.revwalk.RevCommit
import org.eclipse.jgit.treewalk._ import org.eclipse.jgit.treewalk._
import org.apache.commons.io.IOUtils import org.apache.commons.io.IOUtils
import org.slf4j.LoggerFactory
/** /**
* Provides generic features for controller implementations. * Provides generic features for controller implementations.
@@ -34,6 +35,8 @@ abstract class ControllerBase extends ScalatraFilter
with ClientSideValidationFormSupport with JacksonJsonSupport with I18nSupport with FlashMapSupport with Validations with ClientSideValidationFormSupport with JacksonJsonSupport with I18nSupport with FlashMapSupport with Validations
with SystemSettingsService { with SystemSettingsService {
private val logger = LoggerFactory.getLogger(getClass)
implicit val jsonFormats = gitbucket.core.api.JsonFormat.jsonFormats implicit val jsonFormats = gitbucket.core.api.JsonFormat.jsonFormats
before("/api/v3/*") { before("/api/v3/*") {
@@ -147,6 +150,20 @@ abstract class ControllerBase extends ScalatraFilter
} }
} }
error{
case e => {
logger.error(s"Catch unhandled error in request: ${request}", e)
if(request.hasAttribute(Keys.Request.Ajax)){
org.scalatra.InternalServerError()
} else if(request.hasAttribute(Keys.Request.APIv3)){
contentType = formats("json")
org.scalatra.InternalServerError(ApiError("Internal Server Error"))
} else {
org.scalatra.InternalServerError(gitbucket.core.html.error("Internal Server Error", Some(e)))
}
}
}
override def url(path: String, params: Iterable[(String, Any)] = Iterable.empty, override def url(path: String, params: Iterable[(String, Any)] = Iterable.empty,
includeContextPath: Boolean = true, includeServletPath: Boolean = true, includeContextPath: Boolean = true, includeServletPath: Boolean = true,
absolutize: Boolean = true, withSessionId: Boolean = true) absolutize: Boolean = true, withSessionId: Boolean = true)
@@ -163,7 +180,7 @@ abstract class ControllerBase extends ScalatraFilter
override def validate(name: String, value: String, params: Map[String, String], messages: Messages): Seq[(String, String)] = override def validate(name: String, value: String, params: Map[String, String], messages: Messages): Seq[(String, String)] =
valueType.validate(name, trim(value), params, messages) valueType.validate(name, trim(value), params, messages)
private def trim(value: String): String = if(value == null) null else value.replaceAll("\r\n", "").trim private def trim(value: String): String = if(value == null) null else value.replace("\r\n", "").trim
} }
/** /**

View File

@@ -193,7 +193,7 @@ trait IssuesControllerBase extends ControllerBase {
defining(repository.owner, repository.name){ case (owner, name) => defining(repository.owner, repository.name){ case (owner, name) =>
getComment(owner, name, params("id")).map { comment => getComment(owner, name, params("id")).map { comment =>
if(isEditableContent(owner, name, comment.commentedUserName)){ if(isEditableContent(owner, name, comment.commentedUserName)){
updateComment(comment.commentId, form.content) updateComment(comment.issueId, comment.commentId, form.content)
redirect(s"/${owner}/${name}/issue_comments/_data/${comment.commentId}") redirect(s"/${owner}/${name}/issue_comments/_data/${comment.commentId}")
} else Unauthorized() } else Unauthorized()
} getOrElse NotFound() } getOrElse NotFound()
@@ -204,7 +204,7 @@ trait IssuesControllerBase extends ControllerBase {
defining(repository.owner, repository.name){ case (owner, name) => defining(repository.owner, repository.name){ case (owner, name) =>
getComment(owner, name, params("id")).map { comment => getComment(owner, name, params("id")).map { comment =>
if(isEditableContent(owner, name, comment.commentedUserName)){ if(isEditableContent(owner, name, comment.commentedUserName)){
Ok(deleteComment(comment.commentId)) Ok(deleteComment(comment.issueId, comment.commentId))
} else Unauthorized() } else Unauthorized()
} getOrElse NotFound() } getOrElse NotFound()
} }

View File

@@ -0,0 +1,40 @@
package gitbucket.core.controller
import org.scalatra.MovedPermanently
class PreProcessController extends PreProcessControllerBase
trait PreProcessControllerBase extends ControllerBase {
/**
* Provides GitHub compatible URLs for Git client.
*
* <ul>
* <li>git clone http://localhost:8080/owner/repo</li>
* <li>git clone http://localhost:8080/owner/repo.git</li>
* </ul>
*
* @see https://git-scm.com/book/en/v2/Git-Internals-Transfer-Protocols
*/
get("/*/*/info/refs") {
val query = Option(request.getQueryString).map("?" + _).getOrElse("")
halt(MovedPermanently(baseUrl + "/git" + request.getRequestURI + query))
}
/**
* Filter requests from anonymous users.
*
* If anonymous access is allowed, pass all requests.
* But if it's not allowed, demands authentication except some paths.
*/
get(!context.settings.allowAnonymousAccess, context.loginAccount.isEmpty) {
if(!context.currentPath.startsWith("/assets") && !context.currentPath.startsWith("/signin") &&
!context.currentPath.startsWith("/register")) {
Unauthorized()
} else {
pass()
}
}
}

View File

@@ -251,7 +251,7 @@ trait PullRequestsControllerBase extends ControllerBase {
using(Git.open(getRepositoryDir(owner, name))) { git => using(Git.open(getRepositoryDir(owner, name))) { git =>
// mark issue as merged and close. // mark issue as merged and close.
val loginAccount = context.loginAccount.get val loginAccount = context.loginAccount.get
createComment(owner, name, loginAccount.userName, issueId, form.message, "merge") val commentId = createComment(owner, name, loginAccount.userName, issueId, form.message, "merge")
createComment(owner, name, loginAccount.userName, issueId, "Close", "close") createComment(owner, name, loginAccount.userName, issueId, "Close", "close")
updateClosed(owner, name, issueId, true) updateClosed(owner, name, issueId, true)
@@ -282,7 +282,10 @@ trait PullRequestsControllerBase extends ControllerBase {
callPullRequestWebHook("closed", repository, issueId, context.baseUrl, context.loginAccount.get) callPullRequestWebHook("closed", repository, issueId, context.baseUrl, context.loginAccount.get)
// call hooks // call hooks
PluginRegistry().getPullRequestHooks.foreach(_.merged(issue, repository)) PluginRegistry().getPullRequestHooks.foreach{ h =>
h.addedComment(commentId, form.message, issue, repository)
h.merged(issue, repository)
}
redirect(s"/${owner}/${name}/pull/${issueId}") redirect(s"/${owner}/${name}/pull/${issueId}")
} }

View File

@@ -13,7 +13,7 @@ import gitbucket.core.util.StringUtil._
import gitbucket.core.util.SyntaxSugars._ import gitbucket.core.util.SyntaxSugars._
import gitbucket.core.util.Implicits._ import gitbucket.core.util.Implicits._
import gitbucket.core.util.Directory._ import gitbucket.core.util.Directory._
import gitbucket.core.model.{Account, WebHook} import gitbucket.core.model.{Account, CommitState, CommitStatus, WebHook}
import gitbucket.core.service.WebHookService._ import gitbucket.core.service.WebHookService._
import gitbucket.core.view import gitbucket.core.view
import gitbucket.core.view.helpers import gitbucket.core.view.helpers
@@ -174,13 +174,24 @@ trait RepositoryViewerControllerBase extends ControllerBase {
val (branchName, path) = repository.splitPath(multiParams("splat").head) val (branchName, path) = repository.splitPath(multiParams("splat").head)
val page = params.get("page").flatMap(_.toIntOpt).getOrElse(1) val page = params.get("page").flatMap(_.toIntOpt).getOrElse(1)
def getStatuses(sha: String): List[CommitStatus] = {
getCommitStatues(repository.owner, repository.name, sha)
}
def getSummary(statuses: List[CommitStatus]): (CommitState, String) = {
val stateMap = statuses.groupBy(_.state)
val state = CommitState.combine(stateMap.keySet)
val summary = stateMap.map{ case (keyState, states) => states.size+" "+keyState.name }.mkString(", ")
state -> summary
}
using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git => using(Git.open(getRepositoryDir(repository.owner, repository.name))){ git =>
JGitUtil.getCommitLog(git, branchName, page, 30, path) match { JGitUtil.getCommitLog(git, branchName, page, 30, path) match {
case Right((logs, hasNext)) => case Right((logs, hasNext)) =>
html.commits(if(path.isEmpty) Nil else path.split("/").toList, branchName, repository, html.commits(if(path.isEmpty) Nil else path.split("/").toList, branchName, repository,
logs.splitWith{ (commit1, commit2) => logs.splitWith{ (commit1, commit2) =>
view.helpers.date(commit1.commitTime) == view.helpers.date(commit2.commitTime) view.helpers.date(commit1.commitTime) == view.helpers.date(commit2.commitTime)
}, page, hasNext, hasDeveloperRole(repository.owner, repository.name, context.loginAccount)) }, page, hasNext, hasDeveloperRole(repository.owner, repository.name, context.loginAccount), getStatuses, getSummary)
case Left(_) => NotFound() case Left(_) => NotFound()
} }
} }
@@ -213,7 +224,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
post("/:owner/:repository/upload", uploadForm)(writableUsersOnly { (form, repository) => post("/:owner/:repository/upload", uploadForm)(writableUsersOnly { (form, repository) =>
val files = form.uploadFiles.split("\n").map { line => val files = form.uploadFiles.split("\n").map { line =>
val i = line.indexOf(":") val i = line.indexOf(':')
CommitFile(line.substring(0, i).trim, line.substring(i + 1).trim) CommitFile(line.substring(0, i).trim, line.substring(i + 1).trim)
} }
@@ -222,7 +233,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
branch = form.branch, branch = form.branch,
path = form.path, path = form.path,
files = files, files = files,
message = form.message.getOrElse(s"Add files via upload") message = form.message.getOrElse("Add files via upload")
) )
if(form.path.length == 0){ if(form.path.length == 0){
@@ -630,8 +641,8 @@ trait RepositoryViewerControllerBase extends ControllerBase {
} }
}) })
case class UploadFiles(branch: String, path: String, fileIds : Map[String,String], message: String) { case class UploadFiles(branch: String, path: String, fileIds: Map[String,String], message: String) {
lazy val isValid: Boolean = fileIds.size > 0 lazy val isValid: Boolean = fileIds.nonEmpty
} }
case class CommitFile(id: String, name: String) case class CommitFile(id: String, name: String)

View File

@@ -63,7 +63,8 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
"tls" -> trim(label("Enable TLS", optional(boolean()))), "tls" -> trim(label("Enable TLS", optional(boolean()))),
"ssl" -> trim(label("Enable SSL", optional(boolean()))), "ssl" -> trim(label("Enable SSL", optional(boolean()))),
"keystore" -> trim(label("Keystore", optional(text()))) "keystore" -> trim(label("Keystore", optional(text())))
)(Ldap.apply)) )(Ldap.apply)),
"skinName" -> trim(label("AdminLTE skin name", text(required)))
)(SystemSettings.apply).verifying { settings => )(SystemSettings.apply).verifying { settings =>
Vector( Vector(
if(settings.ssh && settings.baseUrl.isEmpty){ if(settings.ssh && settings.baseUrl.isEmpty){
@@ -174,8 +175,9 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
post("/admin/system/sendmail", sendMailForm)(adminOnly { form => post("/admin/system/sendmail", sendMailForm)(adminOnly { form =>
try { try {
new Mailer(form.smtp).send(form.testAddress, new Mailer(form.smtp).send(form.testAddress,
"Test message from GitBucket", "This is a test message from GitBucket.", "Test message from GitBucket", context.loginAccount.get,
context.loginAccount.get) "This is a test message from GitBucket.", None
)
"Test mail has been sent to: " + form.testAddress "Test mail has been sent to: " + form.testAddress

View File

@@ -4,6 +4,9 @@ import java.io.{File, FilenameFilter, InputStream}
import java.net.URLClassLoader import java.net.URLClassLoader
import java.nio.file.{Files, Paths, StandardWatchEventKinds} import java.nio.file.{Files, Paths, StandardWatchEventKinds}
import java.util.Base64 import java.util.Base64
import java.util.concurrent.atomic.AtomicBoolean
import java.util.concurrent.ConcurrentLinkedQueue
import java.util.concurrent.ConcurrentHashMap
import javax.servlet.ServletContext import javax.servlet.ServletContext
import gitbucket.core.controller.{Context, ControllerBase} import gitbucket.core.controller.{Context, ControllerBase}
@@ -22,51 +25,49 @@ import org.apache.commons.io.FileUtils
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import play.twirl.api.Html import play.twirl.api.Html
import scala.collection.mutable import scala.collection.JavaConverters._
import scala.collection.mutable.ListBuffer
class PluginRegistry { class PluginRegistry {
private val plugins = new ListBuffer[PluginInfo] private val plugins = new ConcurrentLinkedQueue[PluginInfo]
private val javaScripts = new ListBuffer[(String, String)] private val javaScripts = new ConcurrentLinkedQueue[(String, String)]
private val controllers = new ListBuffer[(ControllerBase, String)] private val controllers = new ConcurrentLinkedQueue[(ControllerBase, String)]
private val images = mutable.Map[String, String]() private val images = new ConcurrentHashMap[String, String]
private val renderers = mutable.Map[String, Renderer]() private val renderers = new ConcurrentHashMap[String, Renderer]
renderers ++= Seq( renderers.put("md", MarkdownRenderer)
"md" -> MarkdownRenderer, "markdown" -> MarkdownRenderer renderers.put("markdown", MarkdownRenderer)
) private val repositoryRoutings = new ConcurrentLinkedQueue[GitRepositoryRouting]
private val repositoryRoutings = new ListBuffer[GitRepositoryRouting] private val accountHooks = new ConcurrentLinkedQueue[AccountHook]
private val accountHooks = new ListBuffer[AccountHook] private val receiveHooks = new ConcurrentLinkedQueue[ReceiveHook]
private val receiveHooks = new ListBuffer[ReceiveHook] receiveHooks.add(new ProtectedBranchReceiveHook())
receiveHooks += new ProtectedBranchReceiveHook()
private val repositoryHooks = new ListBuffer[RepositoryHook] private val repositoryHooks = new ConcurrentLinkedQueue[RepositoryHook]
private val issueHooks = new ListBuffer[IssueHook] private val issueHooks = new ConcurrentLinkedQueue[IssueHook]
private val pullRequestHooks = new ListBuffer[PullRequestHook] private val pullRequestHooks = new ConcurrentLinkedQueue[PullRequestHook]
private val repositoryHeaders = new ListBuffer[(RepositoryInfo, Context) => Option[Html]] private val repositoryHeaders = new ConcurrentLinkedQueue[(RepositoryInfo, Context) => Option[Html]]
private val globalMenus = new ListBuffer[(Context) => Option[Link]] private val globalMenus = new ConcurrentLinkedQueue[(Context) => Option[Link]]
private val repositoryMenus = new ListBuffer[(RepositoryInfo, Context) => Option[Link]] private val repositoryMenus = new ConcurrentLinkedQueue[(RepositoryInfo, Context) => Option[Link]]
private val repositorySettingTabs = new ListBuffer[(RepositoryInfo, Context) => Option[Link]] private val repositorySettingTabs = new ConcurrentLinkedQueue[(RepositoryInfo, Context) => Option[Link]]
private val profileTabs = new ListBuffer[(Account, Context) => Option[Link]] private val profileTabs = new ConcurrentLinkedQueue[(Account, Context) => Option[Link]]
private val systemSettingMenus = new ListBuffer[(Context) => Option[Link]] private val systemSettingMenus = new ConcurrentLinkedQueue[(Context) => Option[Link]]
private val accountSettingMenus = new ListBuffer[(Context) => Option[Link]] private val accountSettingMenus = new ConcurrentLinkedQueue[(Context) => Option[Link]]
private val dashboardTabs = new ListBuffer[(Context) => Option[Link]] private val dashboardTabs = new ConcurrentLinkedQueue[(Context) => Option[Link]]
private val issueSidebars = new ListBuffer[(Issue, RepositoryInfo, Context) => Option[Html]] private val issueSidebars = new ConcurrentLinkedQueue[(Issue, RepositoryInfo, Context) => Option[Html]]
private val assetsMappings = new ListBuffer[(String, String, ClassLoader)] private val assetsMappings = new ConcurrentLinkedQueue[(String, String, ClassLoader)]
private val textDecorators = new ListBuffer[TextDecorator] private val textDecorators = new ConcurrentLinkedQueue[TextDecorator]
private val suggestionProviders = new ListBuffer[SuggestionProvider] private val suggestionProviders = new ConcurrentLinkedQueue[SuggestionProvider]
suggestionProviders += new UserNameSuggestionProvider() suggestionProviders.add(new UserNameSuggestionProvider())
def addPlugin(pluginInfo: PluginInfo): Unit = plugins += pluginInfo def addPlugin(pluginInfo: PluginInfo): Unit = plugins.add(pluginInfo)
def getPlugins(): List[PluginInfo] = plugins.toList def getPlugins(): List[PluginInfo] = plugins.asScala.toList
def addImage(id: String, bytes: Array[Byte]): Unit = { def addImage(id: String, bytes: Array[Byte]): Unit = {
val encoded = Base64.getEncoder.encodeToString(bytes) val encoded = Base64.getEncoder.encodeToString(bytes)
images += ((id, encoded)) images.put(id, encoded)
} }
@deprecated("Use addImage(id: String, bytes: Array[Byte]) instead", "3.4.0") @deprecated("Use addImage(id: String, bytes: Array[Byte]) instead", "3.4.0")
@@ -79,28 +80,28 @@ class PluginRegistry {
addImage(id, bytes) addImage(id, bytes)
} }
def getImage(id: String): String = images(id) def getImage(id: String): String = images.get(id)
def addController(path: String, controller: ControllerBase): Unit = controllers += ((controller, path)) def addController(path: String, controller: ControllerBase): Unit = controllers.add((controller, path))
@deprecated("Use addController(path: String, controller: ControllerBase) instead", "3.4.0") @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 getControllers(): Seq[(ControllerBase, String)] = controllers.asScala.toSeq
def addJavaScript(path: String, script: String): Unit = javaScripts += ((path, script)) def addJavaScript(path: String, script: String): Unit = javaScripts.add((path, script)) //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.asScala.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.put(extension, renderer)
def getRenderer(extension: String): Renderer = renderers.getOrElse(extension, DefaultRenderer) def getRenderer(extension: String): Renderer = renderers.asScala.getOrElse(extension, DefaultRenderer)
def renderableExtensions: Seq[String] = renderers.keys.toSeq def renderableExtensions: Seq[String] = renderers.keys.asScala.toSeq
def addRepositoryRouting(routing: GitRepositoryRouting): Unit = repositoryRoutings += routing def addRepositoryRouting(routing: GitRepositoryRouting): Unit = repositoryRoutings.add(routing)
def getRepositoryRoutings(): Seq[GitRepositoryRouting] = repositoryRoutings.toSeq def getRepositoryRoutings(): Seq[GitRepositoryRouting] = repositoryRoutings.asScala.toSeq
def getRepositoryRouting(repositoryPath: String): Option[GitRepositoryRouting] = { def getRepositoryRouting(repositoryPath: String): Option[GitRepositoryRouting] = {
PluginRegistry().getRepositoryRoutings().find { PluginRegistry().getRepositoryRoutings().find {
@@ -110,73 +111,73 @@ class PluginRegistry {
} }
} }
def addAccountHook(accountHook: AccountHook): Unit = accountHooks += accountHook def addAccountHook(accountHook: AccountHook): Unit = accountHooks.add(accountHook)
def getAccountHooks: Seq[AccountHook] = accountHooks.toSeq def getAccountHooks: Seq[AccountHook] = accountHooks.asScala.toSeq
def addReceiveHook(commitHook: ReceiveHook): Unit = receiveHooks += commitHook def addReceiveHook(commitHook: ReceiveHook): Unit = receiveHooks.add(commitHook)
def getReceiveHooks: Seq[ReceiveHook] = receiveHooks.toSeq def getReceiveHooks: Seq[ReceiveHook] = receiveHooks.asScala.toSeq
def addRepositoryHook(repositoryHook: RepositoryHook): Unit = repositoryHooks += repositoryHook def addRepositoryHook(repositoryHook: RepositoryHook): Unit = repositoryHooks.add(repositoryHook)
def getRepositoryHooks: Seq[RepositoryHook] = repositoryHooks.toSeq def getRepositoryHooks: Seq[RepositoryHook] = repositoryHooks.asScala.toSeq
def addIssueHook(issueHook: IssueHook): Unit = issueHooks += issueHook def addIssueHook(issueHook: IssueHook): Unit = issueHooks.add(issueHook)
def getIssueHooks: Seq[IssueHook] = issueHooks.toSeq def getIssueHooks: Seq[IssueHook] = issueHooks.asScala.toSeq
def addPullRequestHook(pullRequestHook: PullRequestHook): Unit = pullRequestHooks += pullRequestHook def addPullRequestHook(pullRequestHook: PullRequestHook): Unit = pullRequestHooks.add(pullRequestHook)
def getPullRequestHooks: Seq[PullRequestHook] = pullRequestHooks.toSeq def getPullRequestHooks: Seq[PullRequestHook] = pullRequestHooks.asScala.toSeq
def addRepositoryHeader(repositoryHeader: (RepositoryInfo, Context) => Option[Html]): Unit = repositoryHeaders += repositoryHeader def addRepositoryHeader(repositoryHeader: (RepositoryInfo, Context) => Option[Html]): Unit = repositoryHeaders.add(repositoryHeader)
def getRepositoryHeaders: Seq[(RepositoryInfo, Context) => Option[Html]] = repositoryHeaders.toSeq def getRepositoryHeaders: Seq[(RepositoryInfo, Context) => Option[Html]] = repositoryHeaders.asScala.toSeq
def addGlobalMenu(globalMenu: (Context) => Option[Link]): Unit = globalMenus += globalMenu def addGlobalMenu(globalMenu: (Context) => Option[Link]): Unit = globalMenus.add(globalMenu)
def getGlobalMenus: Seq[(Context) => Option[Link]] = globalMenus.toSeq def getGlobalMenus: Seq[(Context) => Option[Link]] = globalMenus.asScala.toSeq
def addRepositoryMenu(repositoryMenu: (RepositoryInfo, Context) => Option[Link]): Unit = repositoryMenus += repositoryMenu def addRepositoryMenu(repositoryMenu: (RepositoryInfo, Context) => Option[Link]): Unit = repositoryMenus.add(repositoryMenu)
def getRepositoryMenus: Seq[(RepositoryInfo, Context) => Option[Link]] = repositoryMenus.toSeq def getRepositoryMenus: Seq[(RepositoryInfo, Context) => Option[Link]] = repositoryMenus.asScala.toSeq
def addRepositorySettingTab(repositorySettingTab: (RepositoryInfo, Context) => Option[Link]): Unit = repositorySettingTabs += repositorySettingTab def addRepositorySettingTab(repositorySettingTab: (RepositoryInfo, Context) => Option[Link]): Unit = repositorySettingTabs.add(repositorySettingTab)
def getRepositorySettingTabs: Seq[(RepositoryInfo, Context) => Option[Link]] = repositorySettingTabs.toSeq def getRepositorySettingTabs: Seq[(RepositoryInfo, Context) => Option[Link]] = repositorySettingTabs.asScala.toSeq
def addProfileTab(profileTab: (Account, Context) => Option[Link]): Unit = profileTabs += profileTab def addProfileTab(profileTab: (Account, Context) => Option[Link]): Unit = profileTabs.add(profileTab)
def getProfileTabs: Seq[(Account, Context) => Option[Link]] = profileTabs.toSeq def getProfileTabs: Seq[(Account, Context) => Option[Link]] = profileTabs.asScala.toSeq
def addSystemSettingMenu(systemSettingMenu: (Context) => Option[Link]): Unit = systemSettingMenus += systemSettingMenu def addSystemSettingMenu(systemSettingMenu: (Context) => Option[Link]): Unit = systemSettingMenus.add(systemSettingMenu)
def getSystemSettingMenus: Seq[(Context) => Option[Link]] = systemSettingMenus.toSeq def getSystemSettingMenus: Seq[(Context) => Option[Link]] = systemSettingMenus.asScala.toSeq
def addAccountSettingMenu(accountSettingMenu: (Context) => Option[Link]): Unit = accountSettingMenus += accountSettingMenu def addAccountSettingMenu(accountSettingMenu: (Context) => Option[Link]): Unit = accountSettingMenus.add(accountSettingMenu)
def getAccountSettingMenus: Seq[(Context) => Option[Link]] = accountSettingMenus.toSeq def getAccountSettingMenus: Seq[(Context) => Option[Link]] = accountSettingMenus.asScala.toSeq
def addDashboardTab(dashboardTab: (Context) => Option[Link]): Unit = dashboardTabs += dashboardTab def addDashboardTab(dashboardTab: (Context) => Option[Link]): Unit = dashboardTabs.add(dashboardTab)
def getDashboardTabs: Seq[(Context) => Option[Link]] = dashboardTabs.toSeq def getDashboardTabs: Seq[(Context) => Option[Link]] = dashboardTabs.asScala.toSeq
def addIssueSidebar(issueSidebar: (Issue, RepositoryInfo, Context) => Option[Html]): Unit = issueSidebars += issueSidebar def addIssueSidebar(issueSidebar: (Issue, RepositoryInfo, Context) => Option[Html]): Unit = issueSidebars.add(issueSidebar)
def getIssueSidebars: Seq[(Issue, RepositoryInfo, Context) => Option[Html]] = issueSidebars.toSeq def getIssueSidebars: Seq[(Issue, RepositoryInfo, Context) => Option[Html]] = issueSidebars.asScala.toSeq
def addAssetsMapping(assetsMapping: (String, String, ClassLoader)): Unit = assetsMappings += assetsMapping def addAssetsMapping(assetsMapping: (String, String, ClassLoader)): Unit = assetsMappings.add(assetsMapping)
def getAssetsMappings: Seq[(String, String, ClassLoader)] = assetsMappings.toSeq def getAssetsMappings: Seq[(String, String, ClassLoader)] = assetsMappings.asScala.toSeq
def addTextDecorator(textDecorator: TextDecorator): Unit = textDecorators += textDecorator def addTextDecorator(textDecorator: TextDecorator): Unit = textDecorators.add(textDecorator)
def getTextDecorators: Seq[TextDecorator] = textDecorators.toSeq def getTextDecorators: Seq[TextDecorator] = textDecorators.asScala.toSeq
def addSuggestionProvider(suggestionProvider: SuggestionProvider): Unit = suggestionProviders += suggestionProvider def addSuggestionProvider(suggestionProvider: SuggestionProvider): Unit = suggestionProviders.add(suggestionProvider)
def getSuggestionProviders: Seq[SuggestionProvider] = suggestionProviders.toSeq def getSuggestionProviders: Seq[SuggestionProvider] = suggestionProviders.asScala.toSeq
} }
/** /**
@@ -190,6 +191,7 @@ object PluginRegistry {
private var watcher: PluginWatchThread = null private var watcher: PluginWatchThread = null
private var extraWatcher: PluginWatchThread = null private var extraWatcher: PluginWatchThread = null
private val initializing = new AtomicBoolean(false)
/** /**
* Returns the PluginRegistry singleton instance. * Returns the PluginRegistry singleton instance.
@@ -229,9 +231,8 @@ object PluginRegistry {
* Install a plugin from a specified jar file. * Install a plugin from a specified jar file.
*/ */
def install(file: File, context: ServletContext, settings: SystemSettings, conn: java.sql.Connection): Unit = synchronized { def install(file: File, context: ServletContext, settings: SystemSettings, conn: java.sql.Connection): Unit = synchronized {
FileUtils.copyFile(file, new File(PluginHome, file.getName))
shutdown(context, settings) shutdown(context, settings)
FileUtils.copyFile(file, new File(PluginHome, file.getName))
instance = new PluginRegistry() instance = new PluginRegistry()
initialize(context, settings, conn) initialize(context, settings, conn)
} }
@@ -323,6 +324,14 @@ object PluginRegistry {
instance.getPlugins().foreach { plugin => instance.getPlugins().foreach { plugin =>
try { try {
plugin.pluginClass.shutdown(instance, context, settings) plugin.pluginClass.shutdown(instance, context, settings)
if(watcher != null){
watcher.interrupt()
watcher = null
}
if(extraWatcher != null){
extraWatcher.interrupt()
extraWatcher = null
}
} catch { } catch {
case e: Exception => { case e: Exception => {
logger.error(s"Error during plugin shutdown: ${plugin.pluginJar.getName}", e) logger.error(s"Error during plugin shutdown: ${plugin.pluginJar.getName}", e)

View File

@@ -50,7 +50,7 @@ trait HandleCommentService {
id id
} }
actionActivity foreach ( _ (owner, name, userName, issue.issueId, issue.title) ) actionActivity.foreach { f => f(owner, name, userName, issue.issueId, issue.title) }
// call web hooks // call web hooks
action match { action match {

View File

@@ -32,8 +32,11 @@ trait IssuesService {
.list .list
def getMergedComment(owner: String, repository: String, issueId: Int)(implicit s: Session): Option[(IssueComment, Account)] = { def getMergedComment(owner: String, repository: String, issueId: Int)(implicit s: Session): Option[(IssueComment, Account)] = {
getCommentsForApi(owner, repository, issueId) IssueComments.filter(_.byIssue(owner, repository, issueId))
.collectFirst { case (comment, account, _) if comment.action == "merged" => (comment, account) } .filter(_.action === "merge".bind)
.join(Accounts).on { case t1 ~ t2 => t1.commentedUserName === t2.userName }
.map { case t1 ~ t2 => (t1, t2)}
.firstOption
} }
def getComment(owner: String, repository: String, commentId: String)(implicit s: Session): Option[IssueComment] = { def getComment(owner: String, repository: String, commentId: String)(implicit s: Session): Option[IssueComment] = {
@@ -327,6 +330,7 @@ trait IssuesService {
def createComment(owner: String, repository: String, loginUser: String, def createComment(owner: String, repository: String, loginUser: String,
issueId: Int, content: String, action: String)(implicit s: Session): Int = { issueId: Int, content: String, action: String)(implicit s: Session): Int = {
Issues.filter(_.issueId === issueId.bind).map(_.updatedDate).update(currentDate)
IssueComments returning IssueComments.map(_.commentId) insert IssueComment( IssueComments returning IssueComments.map(_.commentId) insert IssueComment(
userName = owner, userName = owner,
repositoryName = repository, repositoryName = repository,
@@ -342,31 +346,33 @@ trait IssuesService {
Issues Issues
.filter (_.byPrimaryKey(owner, repository, issueId)) .filter (_.byPrimaryKey(owner, repository, issueId))
.map { t => (t.title, t.content.?, t.updatedDate) } .map { t => (t.title, t.content.?, t.updatedDate) }
.update (title, content, currentDate) .update(title, content, currentDate)
} }
def updateAssignedUserName(owner: String, repository: String, issueId: Int, assignedUserName: Option[String])(implicit s: Session): Int = { def updateAssignedUserName(owner: String, repository: String, issueId: Int, assignedUserName: Option[String])(implicit s: Session): Int = {
Issues.filter (_.byPrimaryKey(owner, repository, issueId)).map(_.assignedUserName?).update (assignedUserName) Issues.filter(_.byPrimaryKey(owner, repository, issueId)).map(t => (t.assignedUserName?, t.updatedDate)).update(assignedUserName, currentDate)
} }
def updateMilestoneId(owner: String, repository: String, issueId: Int, milestoneId: Option[Int])(implicit s: Session): Int = { def updateMilestoneId(owner: String, repository: String, issueId: Int, milestoneId: Option[Int])(implicit s: Session): Int = {
Issues.filter (_.byPrimaryKey(owner, repository, issueId)).map(_.milestoneId?).update (milestoneId) Issues.filter(_.byPrimaryKey(owner, repository, issueId)).map(t => (t.milestoneId?, t.updatedDate)).update(milestoneId, currentDate)
} }
def updatePriorityId(owner: String, repository: String, issueId: Int, priorityId: Option[Int])(implicit s: Session): Int = { def updatePriorityId(owner: String, repository: String, issueId: Int, priorityId: Option[Int])(implicit s: Session): Int = {
Issues.filter (_.byPrimaryKey(owner, repository, issueId)).map(_.priorityId?).update (priorityId) Issues.filter(_.byPrimaryKey(owner, repository, issueId)).map(t => (t.priorityId?, t.updatedDate)).update(priorityId, currentDate)
} }
def updateComment(commentId: Int, content: String)(implicit s: Session): Int = { def updateComment(issueId: Int, commentId: Int, content: String)(implicit s: Session): Int = {
IssueComments.filter (_.byPrimaryKey(commentId)).map(t => (t.content, t.updatedDate)).update(content, currentDate) Issues.filter(_.issueId === issueId.bind).map(_.updatedDate).update(currentDate)
IssueComments.filter(_.byPrimaryKey(commentId)).map(t => (t.content, t.updatedDate)).update(content, currentDate)
} }
def deleteComment(commentId: Int)(implicit s: Session): Int = { def deleteComment(issueId: Int, commentId: Int)(implicit s: Session): Int = {
IssueComments filter (_.byPrimaryKey(commentId)) delete Issues.filter(_.issueId === issueId.bind).map(_.updatedDate).update(currentDate)
IssueComments.filter(_.byPrimaryKey(commentId)).delete
} }
def updateClosed(owner: String, repository: String, issueId: Int, closed: Boolean)(implicit s: Session): Int = { def updateClosed(owner: String, repository: String, issueId: Int, closed: Boolean)(implicit s: Session): Int = {
(Issues filter (_.byPrimaryKey(owner, repository, issueId)) map(t => (t.closed, t.updatedDate))).update((closed, currentDate)) Issues.filter(_.byPrimaryKey(owner, repository, issueId)).map(t => (t.closed, t.updatedDate)).update(closed, currentDate)
} }
/** /**
@@ -497,14 +503,14 @@ object IssuesService {
).flatten ++ ).flatten ++
labels.map(label => s"label:${label}") ++ labels.map(label => s"label:${label}") ++
List( List(
milestone.map { _ match { milestone.map {
case Some(x) => s"milestone:${x}" case Some(x) => s"milestone:${x}"
case None => "no:milestone" case None => "no:milestone"
}}, },
priority.map { _ match { priority.map {
case Some(x) => s"priority:${x}" case Some(x) => s"priority:${x}"
case None => "no:priority" case None => "no:priority"
}}, },
(sort, direction) match { (sort, direction) match {
case ("created" , "desc") => None case ("created" , "desc") => None
case ("created" , "asc" ) => Some("sort:created-asc") case ("created" , "asc" ) => Some("sort:created-asc")

View File

@@ -18,10 +18,11 @@ trait ProtectedBranchService {
.filter(_._1.byPrimaryKey(owner, repository, branch)) .filter(_._1.byPrimaryKey(owner, repository, branch))
.list .list
.groupBy(_._1) .groupBy(_._1)
.headOption
.map { p => p._1 -> p._2.flatMap(_._2) } .map { p => p._1 -> p._2.flatMap(_._2) }
.map { case (t1, contexts) => .map { case (t1, contexts) =>
new ProtectedBranchInfo(t1.userName, t1.repositoryName, true, contexts, t1.statusCheckAdmin) new ProtectedBranchInfo(t1.userName, t1.repositoryName, true, contexts, t1.statusCheckAdmin)
}.headOption }
def getProtectedBranchInfo(owner: String, repository: String, branch: String)(implicit session: Session): ProtectedBranchInfo = def getProtectedBranchInfo(owner: String, repository: String, branch: String)(implicit session: Session): ProtectedBranchInfo =
getProtectedBranchInfoOpt(owner, repository, branch).getOrElse(ProtectedBranchInfo.disabled(owner, repository)) getProtectedBranchInfoOpt(owner, repository, branch).getOrElse(ProtectedBranchInfo.disabled(owner, repository))

View File

@@ -94,9 +94,9 @@ trait PullRequestService { self: IssuesService with CommitsService =>
/** /**
* for repository viewer. * for repository viewer.
* 1. find pull request from from `branch` to othre branch on same repository * 1. find pull request from `branch` to other branch on same repository
* 1. return if exists pull request to `defaultBranch` * 1. return if exists pull request to `defaultBranch`
* 2. return if exists pull request to othre branch * 2. return if exists pull request to other branch
* 2. return None * 2. return None
*/ */
def getPullRequestFromBranch(userName: String, repositoryName: String, branch: String, defaultBranch: String) def getPullRequestFromBranch(userName: String, repositoryName: String, branch: String, defaultBranch: String)
@@ -256,7 +256,7 @@ object PullRequestService {
val statuses: List[CommitStatus] = val statuses: List[CommitStatus] =
commitStatues ++ (branchProtection.contexts.toSet -- commitStatues.map(_.context).toSet).map(CommitStatus.pending(branchProtection.owner, branchProtection.repository, _)) commitStatues ++ (branchProtection.contexts.toSet -- commitStatues.map(_.context).toSet).map(CommitStatus.pending(branchProtection.owner, branchProtection.repository, _))
val hasRequiredStatusProblem = needStatusCheck && branchProtection.contexts.exists(context => statuses.find(_.context == context).map(_.state) != Some(CommitState.SUCCESS)) val hasRequiredStatusProblem = needStatusCheck && branchProtection.contexts.exists(context => statuses.find(_.context == context).map(_.state) != Some(CommitState.SUCCESS))
val hasProblem = hasRequiredStatusProblem || hasConflict || (!statuses.isEmpty && CommitState.combine(statuses.map(_.state).toSet) != CommitState.SUCCESS) val hasProblem = hasRequiredStatusProblem || hasConflict || (statuses.nonEmpty && CommitState.combine(statuses.map(_.state).toSet) != CommitState.SUCCESS)
val canUpdate = branchIsOutOfDate && !hasConflict val canUpdate = branchIsOutOfDate && !hasConflict
val canMerge = hasMergePermission && !hasConflict && !hasRequiredStatusProblem val canMerge = hasMergePermission && !hasConflict && !hasRequiredStatusProblem
lazy val commitStateSummary:(CommitState, String) = { lazy val commitStateSummary:(CommitState, String) = {

View File

@@ -67,7 +67,7 @@ trait RepositorySearchService { self: IssuesService =>
files.map { case (path, text) => files.map { case (path, text) =>
val (highlightText, lineNumber) = getHighlightText(text, query) val (highlightText, lineNumber) = getHighlightText(text, query)
FileSearchResult( FileSearchResult(
path.replaceFirst("\\.md$", ""), path.stripSuffix(".md"),
commits(path).getCommitterIdent.getWhen, commits(path).getCommitterIdent.getWhen,
highlightText, highlightText,
lineNumber) lineNumber)

View File

@@ -135,7 +135,7 @@ trait RepositoryService { self: AccountService =>
repositoryName = newRepositoryName repositoryName = newRepositoryName
)) :_*) )) :_*)
// TODO Drop transfered owner from collaborators? // TODO Drop transferred owner from collaborators?
Collaborators.insertAll(collaborators.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*) Collaborators.insertAll(collaborators.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*)
// Update activity messages // Update activity messages

View File

@@ -54,6 +54,7 @@ trait SystemSettingsService {
ldap.keystore.foreach(x => props.setProperty(LdapKeystore, x)) ldap.keystore.foreach(x => props.setProperty(LdapKeystore, x))
} }
} }
props.setProperty(SkinName, settings.skinName.toString)
using(new java.io.FileOutputStream(GitBucketConf)){ out => using(new java.io.FileOutputStream(GitBucketConf)){ out =>
props.store(out, null) props.store(out, null)
} }
@@ -111,7 +112,8 @@ trait SystemSettingsService {
getOptionValue(props, LdapKeystore, None))) getOptionValue(props, LdapKeystore, None)))
} else { } else {
None None
} },
getValue(props, SkinName, "skin-blue")
) )
} }
} }
@@ -136,7 +138,8 @@ object SystemSettingsService {
useSMTP: Boolean, useSMTP: Boolean,
smtp: Option[Smtp], smtp: Option[Smtp],
ldapAuthentication: Boolean, ldapAuthentication: Boolean,
ldap: Option[Ldap]){ ldap: Option[Ldap],
skinName: String){
def baseUrl(request: HttpServletRequest): String = baseUrl.fold(request.baseUrl)(_.stripSuffix("/")) def baseUrl(request: HttpServletRequest): String = baseUrl.fold(request.baseUrl)(_.stripSuffix("/"))
def sshAddress:Option[SshAddress] = def sshAddress:Option[SshAddress] =
@@ -219,6 +222,7 @@ object SystemSettingsService {
private val LdapTls = "ldap.tls" private val LdapTls = "ldap.tls"
private val LdapSsl = "ldap.ssl" private val LdapSsl = "ldap.ssl"
private val LdapKeystore = "ldap.keystore" private val LdapKeystore = "ldap.keystore"
private val SkinName = "skinName"
private def getValue[A: ClassTag](props: java.util.Properties, key: String, default: A): A = { private def getValue[A: ClassTag](props: java.util.Properties, key: String, default: A): A = {
getSystemProperty(key).getOrElse(getEnvironmentVariable(key).getOrElse { getSystemProperty(key).getOrElse(getEnvironmentVariable(key).getOrElse {

View File

@@ -367,9 +367,9 @@ object WebHookService {
repository: ApiRepository repository: ApiRepository
) extends FieldSerializable with WebHookPayload { ) extends FieldSerializable with WebHookPayload {
val compare = commits.size match { val compare = commits.size match {
case 0 => ApiPath(s"/${repository.full_name}") // maybe test hook on un-initalied repository case 0 => ApiPath(s"/${repository.full_name}") // maybe test hook on un-initialized repository
case 1 => ApiPath(s"/${repository.full_name}/commit/${after}") case 1 => ApiPath(s"/${repository.full_name}/commit/${after}")
case _ if before.filterNot(_=='0').isEmpty => ApiPath(s"/${repository.full_name}/compare/${commits.head.id}^...${after}") case _ if before.forall(_=='0') => ApiPath(s"/${repository.full_name}/compare/${commits.head.id}^...${after}")
case _ => ApiPath(s"/${repository.full_name}/compare/${before}...${after}") case _ => ApiPath(s"/${repository.full_name}/compare/${before}...${after}")
} }
val head_commit = commits.lastOption val head_commit = commits.lastOption

View File

@@ -237,7 +237,7 @@ trait WikiService {
builder.finish() builder.finish()
val newHeadId = JGitUtil.createNewCommit(git, inserter, headId, builder.getDirCache.writeTree(inserter), val newHeadId = JGitUtil.createNewCommit(git, inserter, headId, builder.getDirCache.writeTree(inserter),
Constants.HEAD, committer.fullName, committer.mailAddress, Constants.HEAD, committer.fullName, committer.mailAddress,
if(message.trim.length == 0) { if(message.trim.isEmpty) {
if(removed){ if(removed){
s"Rename ${currentPageName} to ${newPageName}" s"Rename ${currentPageName} to ${newPageName}"
} else if(created){ } else if(created){

View File

@@ -1,37 +0,0 @@
package gitbucket.core.servlet
import javax.servlet._
import javax.servlet.http.{HttpServletRequest, HttpServletResponse}
import gitbucket.core.service.SystemSettingsService
/**
* A controller to provide GitHub compatible URL for Git clients.
*/
class GHCompatRepositoryAccessFilter extends Filter with SystemSettingsService {
/**
* Pattern of GitHub compatible repository URL.
* <code>/:user/:repo.git/</code>
*/
private val githubRepositoryPattern = """^/[^/]+/[^/]+\.git/.*""".r
override def init(filterConfig: FilterConfig) = {}
override def doFilter(req: ServletRequest, res: ServletResponse, chain: FilterChain) = {
implicit val request = req.asInstanceOf[HttpServletRequest]
val agent = request.getHeader("USER-AGENT")
val response = res.asInstanceOf[HttpServletResponse]
val requestPath = request.getRequestURI.substring(request.getContextPath.length)
requestPath match {
case githubRepositoryPattern() if agent != null && agent.toLowerCase.indexOf("git") >= 0 =>
response.sendRedirect(baseUrl + "/git" + requestPath)
case _ =>
chain.doFilter(req, res)
}
}
override def destroy() = {}
}

View File

@@ -74,7 +74,7 @@ class GitAuthenticationFilter extends Filter with RepositoryService with Account
val action = request.paths match { val action = request.paths match {
case Array(_, repositoryOwner, repositoryName, _*) => case Array(_, repositoryOwner, repositoryName, _*) =>
Database() withSession { implicit session => Database() withSession { implicit session =>
getRepository(repositoryOwner, repositoryName.replaceFirst("\\.wiki\\.git$|\\.git$", "")) match { getRepository(repositoryOwner, repositoryName.replaceFirst("(\\.wiki)?\\.git$", "")) match {
case Some(repository) => { case Some(repository) => {
val execute = if (!isUpdating && !repository.repository.isPrivate && settings.allowAnonymousAccess) { val execute = if (!isUpdating && !repository.repository.isPrivate && settings.allowAnonymousAccess) {
// Authentication is not required // Authentication is not required

View File

@@ -166,7 +166,7 @@ class GitBucketReceivePackFactory extends ReceivePackFactory[HttpServletRequest]
if(repository.endsWith(".wiki")){ if(repository.endsWith(".wiki")){
defining(request) { implicit r => defining(request) { implicit r =>
receivePack.setPostReceiveHook(new WikiCommitHook(owner, repository.replaceFirst("\\.wiki$", ""), pusher, baseUrl)) receivePack.setPostReceiveHook(new WikiCommitHook(owner, repository.stripSuffix(".wiki"), pusher, baseUrl))
} }
} }
} }

View File

@@ -19,7 +19,7 @@ class PluginAssetsServlet extends HttpServlet {
.find { case (prefix, _, _) => path.startsWith("/plugin-assets" + prefix) } .find { case (prefix, _, _) => path.startsWith("/plugin-assets" + prefix) }
.flatMap { case (prefix, resourcePath, classLoader) => .flatMap { case (prefix, resourcePath, classLoader) =>
val resourceName = path.substring(("/plugin-assets" + prefix).length) val resourceName = path.substring(("/plugin-assets" + prefix).length)
Option(classLoader.getResourceAsStream(resourcePath.replaceFirst("^/", "") + resourceName)) Option(classLoader.getResourceAsStream(resourcePath.stripPrefix("/") + resourceName))
} }
.map { in => .map { in =>
try { try {

View File

@@ -19,7 +19,7 @@ import org.apache.sshd.server.scp.UnknownCommand
import org.eclipse.jgit.errors.RepositoryNotFoundException import org.eclipse.jgit.errors.RepositoryNotFoundException
object GitCommand { object GitCommand {
val DefaultCommandRegex = """\Agit-(upload|receive)-pack '/([a-zA-Z0-9\-_.]+)/([a-zA-Z0-9\-_.]+).git'\Z""".r val DefaultCommandRegex = """\Agit-(upload|receive)-pack '/([a-zA-Z0-9\-_.]+)/([a-zA-Z0-9\-\+_.]+).git'\Z""".r
val SimpleCommandRegex = """\Agit-(upload|receive)-pack '/(.+\.git)'\Z""".r val SimpleCommandRegex = """\Agit-(upload|receive)-pack '/(.+\.git)'\Z""".r
} }

View File

@@ -6,7 +6,7 @@ import javax.servlet.{ServletContextEvent, ServletContextListener}
import gitbucket.core.service.SystemSettingsService import gitbucket.core.service.SystemSettingsService
import gitbucket.core.service.SystemSettingsService.SshAddress import gitbucket.core.service.SystemSettingsService.SshAddress
import gitbucket.core.util.{Directory} import gitbucket.core.util.Directory
import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory

View File

@@ -75,7 +75,7 @@ object JDBCUtil {
var stringLiteral = false var stringLiteral = false
while({ length = in.read(bytes); length != -1 }){ while({ length = in.read(bytes); length != -1 }){
for(i <- 0 to length - 1){ for(i <- 0 until length){
val c = bytes(i) val c = bytes(i)
if(c == '\''){ if(c == '\''){
stringLiteral = !stringLiteral stringLiteral = !stringLiteral
@@ -146,13 +146,11 @@ object JDBCUtil {
} }
} }
val columnValues = values.map { value => val columnValues = values.map {
value match { case x: String => "'" + x.replace("'", "''") + "'"
case x: String => "'" + x.replace("'", "''") + "'" case x: Timestamp => "'" + dateFormat.format(x) + "'"
case x: Timestamp => "'" + dateFormat.format(x) + "'" case null => "NULL"
case null => "NULL" case x => x
case x => x
}
} }
sb.append(columnValues.mkString(", ")) sb.append(columnValues.mkString(", "))
sb.append(");\n") sb.append(");\n")

View File

@@ -93,7 +93,7 @@ object JGitUtil {
val summary = getSummaryMessage(fullMessage, shortMessage) val summary = getSummaryMessage(fullMessage, shortMessage)
val description = defining(fullMessage.trim.indexOf("\n")){ i => val description = defining(fullMessage.trim.indexOf('\n')){ i =>
if(i >= 0){ if(i >= 0){
Some(fullMessage.trim.substring(i).trim) Some(fullMessage.trim.substring(i).trim)
} else None } else None
@@ -293,7 +293,7 @@ object JGitUtil {
@tailrec @tailrec
def findLastCommits(result:List[(ObjectId, FileMode, String, String, Option[String], RevCommit)], def findLastCommits(result:List[(ObjectId, FileMode, String, String, Option[String], RevCommit)],
restList:List[((ObjectId, FileMode, String, String, Option[String]), Map[RevCommit, RevCommit])], restList:List[((ObjectId, FileMode, String, String, Option[String]), Map[RevCommit, RevCommit])],
revIterator:java.util.Iterator[RevCommit]): List[(ObjectId, FileMode, String, String, Option[String], RevCommit)] ={ revIterator:java.util.Iterator[RevCommit]): List[(ObjectId, FileMode, String, String, Option[String], RevCommit)] = {
if(restList.isEmpty){ if(restList.isEmpty){
result result
} else if(!revIterator.hasNext){ // maybe, revCommit has only 1 log. other case, restList be empty } else if(!revIterator.hasNext){ // maybe, revCommit has only 1 log. other case, restList be empty
@@ -364,9 +364,9 @@ object JGitUtil {
(file1.isDirectory, file2.isDirectory) match { (file1.isDirectory, file2.isDirectory) match {
case (true , false) => true case (true , false) => true
case (false, true ) => false case (false, true ) => false
case _ => file1.name.compareTo(file2.name) < 0 case _ => file1.name.compareTo(file2.name) < 0
} }
}.toList }
} }
} }
@@ -374,7 +374,7 @@ object JGitUtil {
* Returns the first line of the commit message. * Returns the first line of the commit message.
*/ */
private def getSummaryMessage(fullMessage: String, shortMessage: String): String = { private def getSummaryMessage(fullMessage: String, shortMessage: String): String = {
defining(fullMessage.trim.indexOf("\n")){ i => defining(fullMessage.trim.indexOf('\n')){ i =>
defining(if(i >= 0) fullMessage.trim.substring(0, i).trim else fullMessage){ firstLine => defining(if(i >= 0) fullMessage.trim.substring(0, i).trim else fullMessage){ firstLine =>
if(firstLine.length > shortMessage.length) shortMessage else firstLine if(firstLine.length > shortMessage.length) shortMessage else firstLine
} }

View File

@@ -19,8 +19,12 @@ import SystemSettingsService.Smtp
* Please see the plugin for details. * Please see the plugin for details.
*/ */
trait Notifier { trait Notifier {
def toNotify(subject: String, textMsg: String)
(recipients: Account => Session => Seq[String])(implicit context: Context): Unit = {
toNotify(subject, textMsg, None)(recipients)
}
def toNotify(subject: String, msg: String) def toNotify(subject: String, textMsg: String, htmlMsg: Option[String])
(recipients: Account => Session => Seq[String])(implicit context: Context): Unit (recipients: Account => Session => Seq[String])(implicit context: Context): Unit
} }
@@ -35,7 +39,7 @@ object Notifier {
class Mailer(private val smtp: Smtp) extends Notifier { class Mailer(private val smtp: Smtp) extends Notifier {
private val logger = LoggerFactory.getLogger(classOf[Mailer]) private val logger = LoggerFactory.getLogger(classOf[Mailer])
def toNotify(subject: String, msg: String) def toNotify(subject: String, textMsg: String, htmlMsg: Option[String] = None)
(recipients: Account => Session => Seq[String])(implicit context: Context): Unit = { (recipients: Account => Session => Seq[String])(implicit context: Context): Unit = {
context.loginAccount.foreach { loginAccount => context.loginAccount.foreach { loginAccount =>
val database = Database() val database = Database()
@@ -43,7 +47,7 @@ class Mailer(private val smtp: Smtp) extends Notifier {
val f = Future { val f = Future {
database withSession { session => database withSession { session =>
recipients(loginAccount)(session) foreach { to => recipients(loginAccount)(session) foreach { to =>
send(to, subject, msg, loginAccount) send(to, subject, loginAccount, textMsg, htmlMsg)
} }
} }
"Notifications Successful." "Notifications Successful."
@@ -55,7 +59,7 @@ class Mailer(private val smtp: Smtp) extends Notifier {
} }
} }
def send(to: String, subject: String, msg: String, loginAccount: Account): Unit = { def send(to: String, subject: String, loginAccount: Account, textMsg: String, htmlMsg: Option[String] = None): Unit = {
val email = new HtmlEmail val email = new HtmlEmail
email.setHostName(smtp.host) email.setHostName(smtp.host)
email.setSmtpPort(smtp.port.get) email.setSmtpPort(smtp.port.get)
@@ -80,13 +84,16 @@ class Mailer(private val smtp: Smtp) extends Notifier {
} }
email.setCharset("UTF-8") email.setCharset("UTF-8")
email.setSubject(subject) email.setSubject(subject)
email.setHtmlMsg(msg) email.setTextMsg(textMsg)
htmlMsg.foreach { msg =>
email.setHtmlMsg(msg)
}
email.addTo(to).send email.addTo(to).send
} }
} }
class MockMailer extends Notifier { class MockMailer extends Notifier {
def toNotify(subject: String, msg: String) def toNotify(subject: String, textMsg: String, htmlMsg: Option[String] = None)
(recipients: Account => Session => Seq[String])(implicit context: Context): Unit = () (recipients: Account => Session => Seq[String])(implicit context: Context): Unit = ()
} }

View File

@@ -24,7 +24,7 @@ trait Validations {
*/ */
def password: Constraint = new Constraint(){ def password: Constraint = new Constraint(){
override def validate(name: String, value: String, messages: Messages): Option[String] = override def validate(name: String, value: String, messages: Messages): Option[String] =
if(!value.matches("[a-zA-Z0-9\\-_.]+")){ if(System.getProperty("gitbucket.validate.password") != "false" && !value.matches("[a-zA-Z0-9\\-_.]+")){
Some(s"${name} contains invalid character.") Some(s"${name} contains invalid character.")
} else { } else {
None None

View File

@@ -128,7 +128,7 @@ object helpers extends AvatarImageProvider with LinkConverter with RequestCache
repository: RepositoryService.RepositoryInfo, repository: RepositoryService.RepositoryInfo,
enableWikiLink: Boolean, enableRefsLink: Boolean, enableAnchor: Boolean)(implicit context: Context): Html = { enableWikiLink: Boolean, enableRefsLink: Boolean, enableAnchor: Boolean)(implicit context: Context): Html = {
val fileName = filePath.reverse.head.toLowerCase val fileName = filePath.last.toLowerCase
val extension = FileUtil.getExtension(fileName) val extension = FileUtil.getExtension(fileName)
val renderer = PluginRegistry().getRenderer(extension) val renderer = PluginRegistry().getRenderer(extension)
renderer.render(RenderRequest(filePath, fileContent, branch, repository, enableWikiLink, enableRefsLink, enableAnchor, context)) renderer.render(RenderRequest(filePath, fileContent, branch, repository, enableWikiLink, enableRefsLink, enableAnchor, context))

View File

@@ -1,5 +1,6 @@
@(info: Option[Any])(implicit context: gitbucket.core.controller.Context) @(info: Option[Any])(implicit context: gitbucket.core.controller.Context)
@import gitbucket.core.util.DatabaseConfig @import gitbucket.core.util.DatabaseConfig
@import gitbucket.core.view.helpers
@gitbucket.core.html.main("System settings"){ @gitbucket.core.html.main("System settings"){
@gitbucket.core.admin.html.menu("system"){ @gitbucket.core.admin.html.menu("system"){
@gitbucket.core.helper.html.information(info) @gitbucket.core.helper.html.information(info)
@@ -344,6 +345,36 @@
</div> </div>
</div> </div>
*@ *@
<!--====================================================================-->
<!-- AdminLTE SkinName -->
<!--====================================================================-->
<hr>
<label class="strong">
AdminLTE skin name
</label>
<div class="form-group">
<label class="control-label col-md-3" for="skinName">Skin name</label>
<div class="col-md-9">
<select id="skinName" name="skinName">
@Seq(
"skin-black",
"skin-black-light",
"skin-blue",
"skin-blue-light",
"skin-green",
"skin-green-light",
"skin-purple",
"skin-purple-light",
"skin-red",
"skin-red-light",
"skin-yellow",
"skin-yellow-light",
).map{ skin =>
<option @if(skin == context.settings.skinName){selected}>@skin</option>
}
</select>
</div>
</div>
</div> </div>
</div> </div>
<div class="align-right" style="margin-top: 20px;"> <div class="align-right" style="margin-top: 20px;">

View File

@@ -1,8 +1,22 @@
@(title: String)(implicit context: gitbucket.core.controller.Context) @(title: String, e: Option[Throwable]=None)(implicit context: gitbucket.core.controller.Context)
@gitbucket.core.html.main("Error"){ @gitbucket.core.html.main("Error"){
<div class="content-wrapper main-center"> <div class="content-wrapper main-center">
<div class="content body"> <div class="content body">
<h1>@title</h1> <h1>@title</h1>
@if(context.loginAccount.map{_.isAdmin}.getOrElse(false)){
@e.map { ex =>
<h2>@ex.getMessage</h2>
<table class="table table-condensed table-striped table-hover">
<tbody>
@ex.getStackTrace.map{ st =>
<tr><td>@st</td></tr>
}
</tbody>
</table>
}
} else {
<div>Please contact your administrator.</div>
}
</div> </div>
</div> </div>
} }

View File

@@ -18,7 +18,7 @@
if (document.queryCommandSupported('copy')) { if (document.queryCommandSupported('copy')) {
var title = $('#@copyButtonId').attr('title'); var title = $('#@copyButtonId').attr('title');
$('#@copyButtonId').tooltip({ $('#@copyButtonId').tooltip({
@* if default container is used then 2 lines tooltip text is displayd because tooptip width is narrow. *@ @* if default container is used then 2 lines tooltip text is displayed because tooptip width is narrow. *@
container: 'body' container: 'body'
}); });
$('#@copyButtonId').on('click', function() { $('#@copyButtonId').on('click', function() {

View File

@@ -2,10 +2,10 @@
@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"> <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> <id>tag:@context.host,2013:gitbucket</id>
<title>Gitbucket's activities</title> <title>GitBucket's activities</title>
<link type="application/atom+xml" rel="self" href="@context.baseUrl/activities.atom"/> <link type="application/atom+xml" rel="self" href="@context.baseUrl/activities.atom"/>
<author> <author>
<name>Gitbucket</name> <name>GitBucket</name>
<uri>@context.baseUrl</uri> <uri>@context.baseUrl</uri>
</author> </author>
<updated>@helpers.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>

View File

@@ -16,7 +16,7 @@
<link href="@helpers.assets("/vendors/google-code-prettify/prettify.css")" type="text/css" rel="stylesheet"/> <link href="@helpers.assets("/vendors/google-code-prettify/prettify.css")" type="text/css" rel="stylesheet"/>
<link href="@helpers.assets("/vendors/facebox/facebox.css")" rel="stylesheet"/> <link href="@helpers.assets("/vendors/facebox/facebox.css")" rel="stylesheet"/>
<link href="@helpers.assets("/vendors/AdminLTE-2.3.11/css/AdminLTE.min.css")" rel="stylesheet"> <link href="@helpers.assets("/vendors/AdminLTE-2.3.11/css/AdminLTE.min.css")" rel="stylesheet">
<link href="@helpers.assets("/vendors/AdminLTE-2.3.11/css/skins/skin-blue.min.css")" rel="stylesheet"> <link href="@helpers.assets(s"/vendors/AdminLTE-2.3.11/css/skins/${context.settings.skinName}.min.css")" rel="stylesheet">
<link href="@helpers.assets("/vendors/font-awesome-4.6.3/css/font-awesome.min.css")" rel="stylesheet"> <link href="@helpers.assets("/vendors/font-awesome-4.6.3/css/font-awesome.min.css")" rel="stylesheet">
<link href="@helpers.assets("/vendors/jquery-ui/jquery-ui.min.css")" rel="stylesheet"> <link href="@helpers.assets("/vendors/jquery-ui/jquery-ui.min.css")" rel="stylesheet">
<link href="@helpers.assets("/vendors/jquery-ui/jquery-ui.structure.min.css")" rel="stylesheet"> <link href="@helpers.assets("/vendors/jquery-ui/jquery-ui.structure.min.css")" rel="stylesheet">
@@ -42,7 +42,7 @@
} }
<script src="@helpers.assets("/vendors/AdminLTE-2.3.11/js/app.js")" type="text/javascript"></script> <script src="@helpers.assets("/vendors/AdminLTE-2.3.11/js/app.js")" type="text/javascript"></script>
</head> </head>
<body class="skin-blue page-load @if(body.toString.contains("menu-item-hover")){sidebar-mini} @if(context.sidebarCollapse){sidebar-collapse}"> <body class="@context.settings.skinName page-load @if(body.toString.contains("menu-item-hover")){sidebar-mini} @if(context.sidebarCollapse){sidebar-collapse}">
<div class="wrapper"> <div class="wrapper">
<header class="main-header"> <header class="main-header">
<a href="@context.path/" class="logo"> <a href="@context.path/" class="logo">

View File

@@ -71,7 +71,7 @@
</div> </div>
</div> </div>
@if(content.viewType == "text"){ @if(content.viewType == "text"){
@defining(helpers.isRenderable(pathList.reverse.head)){ isRenderable => @defining(helpers.isRenderable(pathList.last)){ isRenderable =>
@if(!isBlame && isRenderable) { @if(!isBlame && isRenderable) {
<div class="box-content-bottom markdown-body" style="padding-left: 20px; padding-right: 20px;"> <div class="box-content-bottom markdown-body" style="padding-left: 20px; padding-right: 20px;">
@helpers.renderMarkup(pathList, content.content.get, branch, repository, false, false, true) @helpers.renderMarkup(pathList, content.content.get, branch, repository, false, false, true)

View File

@@ -4,7 +4,9 @@
commits: Seq[Seq[gitbucket.core.util.JGitUtil.CommitInfo]], commits: Seq[Seq[gitbucket.core.util.JGitUtil.CommitInfo]],
page: Int, page: Int,
hasNext: Boolean, hasNext: Boolean,
hasWritePermission: Boolean)(implicit context: gitbucket.core.controller.Context) hasWritePermission: Boolean,
getStatuses: String => List[gitbucket.core.model.CommitStatus],
getSummary: List[gitbucket.core.model.CommitStatus] => (gitbucket.core.model.CommitState, String))(implicit context: gitbucket.core.controller.Context)
@import gitbucket.core.view.helpers @import gitbucket.core.view.helpers
@gitbucket.core.html.main(s"${repository.owner}/${repository.name}", Some(repository)) { @gitbucket.core.html.main(s"${repository.owner}/${repository.name}", Some(repository)) {
@gitbucket.core.html.menu("files", repository){ @gitbucket.core.html.menu("files", repository){
@@ -63,6 +65,30 @@
} }
@helpers.user(commit.committerName, commit.committerEmailAddress, "username") @helpers.user(commit.committerName, commit.committerEmailAddress, "username")
<span class="muted">committed @gitbucket.core.helper.html.datetimeago(commit.commitTime)</span> <span class="muted">committed @gitbucket.core.helper.html.datetimeago(commit.commitTime)</span>
@defining({
val statuses = getStatuses(commit.id)
val (summary, summaryText) = getSummary(statuses)
(statuses, summary, summaryText)
}){ case (statuses, summaryState, summaryText) =>
@if(statuses.nonEmpty){
@helpers.commitStateIcon(summaryState)
<strong class="text-@{summaryState.name}">@helpers.commitStateText(summaryState, commit.id)</strong>
<span class="text-@{summaryState.name}">- @summaryText checks</span>
<a href="#" class="toggle-check">Show all checks</a>
<div style="display: none;">
@statuses.map{ status =>
<div class="build-status-item">
<span class="build-status-icon text-@{status.state.name}">@helpers.commitStateIcon(status.state)</span>
<strong>@status.context</strong>
@status.description.map { desc => <span class="muted">- @desc</span> }
<span>
@status.targetUrl.map { url => - <a href="@url">Details</a> }
</span>
</div>
}
</div>
}
}
</div> </div>
</div> </div>
</div> </div>
@@ -86,5 +112,19 @@
} }
</ul> </ul>
</nav> </nav>
<script>
$(function () {
$('.toggle-check').click(function(){
var div = $(this).next('div');
if(div.is(':visible')){
$(this).text('Show all checks');
} else {
$(this).text('Hide all checks');
}
div.toggle();
return false;
});
})
</script>
} }
} }

View File

@@ -175,7 +175,7 @@
</table> </table>
@readme.map { case(filePath, content) => @readme.map { case(filePath, content) =>
<div id="readme" class="panel panel-default"> <div id="readme" class="panel panel-default">
<div class="panel-heading strong">@filePath.reverse.head</div> <div class="panel-heading strong">@filePath.last</div>
<div class="panel-body markdown-body" style="padding-left: 20px; padding-right: 20px;">@helpers.renderMarkup(filePath, content, branch, repository, false, false, true)</div> <div class="panel-body markdown-body" style="padding-left: 20px; padding-right: 20px;">@helpers.renderMarkup(filePath, content, branch, repository, false, false, true)</div>
</div> </div>
} }

View File

@@ -12,7 +12,7 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@repository.tags.reverse.map { tag => @repository.tags.reverseMap { tag =>
<tr> <tr>
<td><a href="@helpers.url(repository)/tree/@helpers.encodeRefName(tag.name)">@tag.name</a></td> <td><a href="@helpers.url(repository)/tree/@helpers.encodeRefName(tag.name)">@tag.name</a></td>
<td>@gitbucket.core.helper.html.datetimeago(tag.time, false)</td> <td>@gitbucket.core.helper.html.datetimeago(tag.time, false)</td>

View File

@@ -443,7 +443,7 @@ function string_score(string, word) {
* @param word {String} search word * @param word {String} search word
* @param strings {Array[String]} search targets * @param strings {Array[String]} search targets
* @param limit {Integer} result limit * @param limit {Integer} result limit
* @return {Array[{score:"float matching score", string:"string target string", matchingPositions:"Array[Interger] matchng positions"}]} * @return {Array[{score:"float matching score", string:"string target string", matchingPositions:"Array[Integer] matching positions"}]}
*/ */
function string_score_sort(word, strings, limit){ function string_score_sort(word, strings, limit){
var ret = [], i=0, l = (word==="")?Math.min(strings.length, limit):strings.length; var ret = [], i=0, l = (word==="")?Math.min(strings.length, limit):strings.length;
@@ -466,7 +466,7 @@ function string_score_sort(word, strings, limit){
} }
/** /**
* highlight by result. * highlight by result.
* @param score {string:"string target string", matchingPositions:"Array[Interger] matchng positions"} * @param score {string:"string target string", matchingPositions:"Array[Integer] matching positions"}
* @param highlight tag ex: '<b>' * @param highlight tag ex: '<b>'
* @return array of highlighted html elements. * @return array of highlighted html elements.
*/ */

View File

@@ -18,7 +18,7 @@ class MergeServiceSpec extends FunSpec {
def initRepository(owner:String, name:String): File = { def initRepository(owner:String, name:String): File = {
val dir = createTestRepository(getRepositoryDir(owner, name)) val dir = createTestRepository(getRepositoryDir(owner, name))
using(Git.open(dir)){ git => using(Git.open(dir)){ git =>
createFile(git, s"refs/heads/master", "test.txt", "hoge" ) createFile(git, "refs/heads/master", "test.txt", "hoge" )
git.branchCreate().setStartPoint(s"refs/heads/master").setName(s"refs/pull/${issueId}/head").call() git.branchCreate().setStartPoint(s"refs/heads/master").setName(s"refs/pull/${issueId}/head").call()
} }
dir dir

View File

@@ -11,15 +11,15 @@ class PullRequestServiceSpec extends FunSpec with ServiceSpecBase
describe("PullRequestService.getPullRequestFromBranch") { describe("PullRequestService.getPullRequestFromBranch") {
it("""should it("""should
|return pull request if exists pull request from `branch` to `defaultBranch` and not closed. |return pull request if exists pull request from `branch` to `defaultBranch` and not closed.
|return pull request if exists pull request from `branch` to othre branch and not closed. |return pull request if exists pull request from `branch` to other branch and not closed.
|return None if all pull request is closed""".stripMargin.trim) { withTestDB { implicit se => |return None if all pull request is closed""".stripMargin.trim) { withTestDB { implicit se =>
generateNewUserWithDBRepository("user1", "repo1") generateNewUserWithDBRepository("user1", "repo1")
generateNewUserWithDBRepository("user1", "repo2") generateNewUserWithDBRepository("user1", "repo2")
generateNewUserWithDBRepository("user2", "repo1") generateNewUserWithDBRepository("user2", "repo1")
generateNewPullRequest("user1/repo1/master", "user1/repo1/head2") // not target branch generateNewPullRequest("user1/repo1/master", "user1/repo1/head2") // not target branch
generateNewPullRequest("user1/repo1/head1", "user1/repo1/master") // not target branch ( swap from, to ) generateNewPullRequest("user1/repo1/head1", "user1/repo1/master") // not target branch ( swap from, to )
generateNewPullRequest("user1/repo1/master", "user2/repo1/head1") // othre user generateNewPullRequest("user1/repo1/master", "user2/repo1/head1") // other user
generateNewPullRequest("user1/repo1/master", "user1/repo2/head1") // othre repository generateNewPullRequest("user1/repo1/master", "user1/repo2/head1") // other repository
val r1 = swap(generateNewPullRequest("user1/repo1/master2", "user1/repo1/head1")) val r1 = swap(generateNewPullRequest("user1/repo1/master2", "user1/repo1/head1"))
val r2 = swap(generateNewPullRequest("user1/repo1/master", "user1/repo1/head1")) val r2 = swap(generateNewPullRequest("user1/repo1/master", "user1/repo1/head1"))
val r3 = swap(generateNewPullRequest("user1/repo1/master4", "user1/repo1/head1")) val r3 = swap(generateNewPullRequest("user1/repo1/master4", "user1/repo1/head1"))
@@ -31,4 +31,4 @@ class PullRequestServiceSpec extends FunSpec with ServiceSpecBase
assert(getPullRequestFromBranch("user1", "repo1", "head1", "master") == None) assert(getPullRequestFromBranch("user1", "repo1", "head1", "master") == None)
} } } }
} }
} }

View File

@@ -118,7 +118,10 @@ class AvatarImageProviderSpec extends FunSpec with MockitoSugar {
useSMTP = false, useSMTP = false,
smtp = None, smtp = None,
ldapAuthentication = false, ldapAuthentication = false,
ldap = None) ldap = None,
skinName = "skin-blue",
debug = false
)
/** /**
* Adapter to test AvatarImageProviderImpl. * Adapter to test AvatarImageProviderImpl.

View File

@@ -32,7 +32,7 @@ class HelpersSpec extends FunSpec with MockitoSugar {
assert(after == """Example Project. <a href="http://example.com">http://example.com</a>""") assert(after == """Example Project. <a href="http://example.com">http://example.com</a>""")
} }
it("should convert a mulitple links within text") { it("should convert a multiple links within text") {
val before = "Example Project. http://example.com. (See also https://github.com/)" val before = "Example Project. http://example.com. (See also https://github.com/)"
val after = detectAndRenderLinks(before, repository) val after = detectAndRenderLinks(before, repository)
assert(after == """Example Project. <a href="http://example.com">http://example.com</a>. (See also <a href="https://github.com/">https://github.com/</a>)""") assert(after == """Example Project. <a href="http://example.com">http://example.com</a>. (See also <a href="https://github.com/">https://github.com/</a>)""")