Compare commits

..

3 Commits

Author SHA1 Message Date
Naoki Takezoe
47ec2d4712 Copy only LFS dir and insert default priorities in forking repository 2017-07-05 00:21:15 +09:00
Naoki Takezoe
31160f42cb Release 4.14.1 2017-07-05 00:01:33 +09:00
Naoki Takezoe
4f11cbaa77 (refs #1631)Copy repository files directory only if it exists 2017-07-04 23:57:18 +09:00
82 changed files with 586 additions and 1136 deletions

View File

@@ -1,8 +1,7 @@
# The guidelines for contributing
# Guideline for Issues
- 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.
- 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.
- 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).
- 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.
- 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.
- Write an issue in English. At least, write subject in English.
- 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.

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)
- [] searched for similar already existing issue
@@ -9,7 +9,7 @@
## Issue
**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**:
- *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)
- [] rebased my branch over master

6
.github/SUPPORT.md vendored
View File

@@ -1,6 +0,0 @@
# 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,7 +1,5 @@
language: scala
sudo: true
jdk:
- oraclejdk8
script:
- sbt test
before_script:
@@ -16,3 +14,33 @@ cache:
- $HOME/.coursier
- $HOME/.embedmysql
- $HOME/.embedpostgresql
matrix:
include:
- jdk: oraclejdk8
addons:
apt:
packages:
- libaio1
- dist: trusty
group: edge
sudo: required
jdk: oraclejdk9
addons:
apt:
packages:
- libaio1
- oracle-java9-installer
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
- 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

@@ -57,9 +57,8 @@ GitBucket has a plug-in system that allows extra functionality. Officially the f
- [gitbucket-gist-plugin](https://github.com/gitbucket/gitbucket-gist-plugin)
- [gitbucket-emoji-plugin](https://github.com/gitbucket/gitbucket-emoji-plugin)
- [gitbucket-pages-plugin](https://github.com/gitbucket/gitbucket-pages-plugin)
- [gitbucket-notifications-plugin](https://github.com/gitbucket/gitbucket-notifications-plugin)
You can find more plugins made by the community at [GitBucket community plugins](https://gitbucket-plugins.github.io/).
You can find more plugins made by the community at [GitBucket community plugins](http://gitbucket-plugins.github.io/).
Support
--------
@@ -72,19 +71,6 @@ Support
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
- Bundle GitBucket organization plugins
- Notifications plugin
- Plugin hot deployment
- Update Slick to 3.2.1 from 3.2.0
- Support ed25519 keys for SSH
- Markdown preview in comment editing forms
### 4.14.1 - 4 Jul 2017
- Bug fix: Possibility of error in forking repository

View File

@@ -1,6 +1,6 @@
val Organization = "io.github.gitbucket"
val Name = "gitbucket"
val GitBucketVersion = "4.16.0"
val GitBucketVersion = "4.14.1"
val ScalatraVersion = "2.5.0"
val JettyVersion = "9.3.19.v20170502"
@@ -10,7 +10,7 @@ sourcesInBase := false
organization := Organization
name := Name
version := GitBucketVersion
scalaVersion := "2.12.3"
scalaVersion := "2.12.2"
// dependency settings
resolvers ++= Seq(
@@ -21,25 +21,25 @@ resolvers ++= Seq(
"amateras-snapshot" at "http://amateras.sourceforge.jp/mvn-snapshot/"
)
libraryDependencies ++= Seq(
"org.eclipse.jgit" % "org.eclipse.jgit.http.server" % "4.8.0.201706111038-r",
"org.eclipse.jgit" % "org.eclipse.jgit.archive" % "4.8.0.201706111038-r",
"org.eclipse.jgit" % "org.eclipse.jgit.http.server" % "4.7.0.201704051617-r",
"org.eclipse.jgit" % "org.eclipse.jgit.archive" % "4.7.0.201704051617-r",
"org.scalatra" %% "scalatra" % ScalatraVersion,
"org.scalatra" %% "scalatra-json" % ScalatraVersion,
"org.json4s" %% "json4s-jackson" % "3.5.1",
"io.github.gitbucket" %% "scalatra-forms" % "1.1.0",
"commons-io" % "commons-io" % "2.5",
"io.github.gitbucket" % "solidbase" % "1.0.2",
"io.github.gitbucket" % "markedj" % "1.0.14",
"io.github.gitbucket" % "markedj" % "1.0.12",
"org.apache.commons" % "commons-compress" % "1.13",
"org.apache.commons" % "commons-email" % "1.4",
"org.apache.httpcomponents" % "httpclient" % "4.5.3",
"org.apache.sshd" % "apache-sshd" % "1.4.0" exclude("org.slf4j","slf4j-jdk14"),
"org.apache.tika" % "tika-core" % "1.14",
"com.github.takezoe" %% "blocking-slick-32" % "0.0.10",
"com.github.takezoe" %% "blocking-slick-32" % "0.0.8",
"joda-time" % "joda-time" % "2.9.9",
"com.novell.ldap" % "jldap" % "2009-10-07",
"com.h2database" % "h2" % "1.4.195",
"org.mariadb.jdbc" % "mariadb-java-client" % "2.0.3",
"mysql" % "mysql-connector-java" % "6.0.6",
"org.postgresql" % "postgresql" % "42.0.0",
"ch.qos.logback" % "logback-classic" % "1.2.3",
"com.zaxxer" % "HikariCP" % "2.6.1",
@@ -50,15 +50,13 @@ libraryDependencies ++= Seq(
"org.cache2k" % "cache2k-all" % "1.0.0.CR1",
"com.enragedginger" %% "akka-quartz-scheduler" % "1.6.0-akka-2.4.x" exclude("c3p0","c3p0"),
"net.coobird" % "thumbnailator" % "0.4.8",
"com.github.zafarkhaja" % "java-semver" % "0.9.0",
"org.eclipse.jetty" % "jetty-webapp" % JettyVersion % "provided",
"javax.servlet" % "javax.servlet-api" % "3.1.0" % "provided",
"junit" % "junit" % "4.12" % "test",
"org.scalatra" %% "scalatra-scalatest" % ScalatraVersion % "test",
"org.mockito" % "mockito-core" % "2.7.22" % "test",
"com.wix" % "wix-embedded-mysql" % "2.1.4" % "test",
"ru.yandex.qatools.embed" % "postgresql-embedded" % "2.0" % "test",
"net.i2p.crypto" % "eddsa" % "0.1.0"
"ru.yandex.qatools.embed" % "postgresql-embedded" % "2.0" % "test"
)
// Compiler settings
@@ -145,28 +143,12 @@ executableKey := {
IO copyFile (classDir / name, temp / name)
}
// include plugins
val pluginsDir = temp / "WEB-INF" / "classes" / "plugins"
IO createDirectory (pluginsDir)
IO copyFile(Keys.baseDirectory.value / "plugins.json", pluginsDir / "plugins.json")
val json = IO read(Keys.baseDirectory.value / "plugins.json")
PluginsJson.parse(json).foreach { case (plugin, version) =>
val url = if(plugin == "gitbucket-pages-plugin"){
s"https://github.com/gitbucket/${plugin}/releases/download/v${version}/${plugin}_${scalaBinaryVersion.value}-${version}.jar"
} else {
s"https://github.com/gitbucket/${plugin}/releases/download/${version}/${plugin}_${scalaBinaryVersion.value}-${version}.jar"
}
log info s"Download: ${url}"
IO download(new java.net.URL(url), pluginsDir / s"${plugin}_${scalaBinaryVersion.value}-${version}.jar")
}
// zip it up
IO delete (temp / "META-INF" / "MANIFEST.MF")
val contentMappings = (temp.*** --- PathFinder(temp)).get pair relativeTo(temp)
val manifest = new JarManifest
manifest.getMainAttributes put (AttrName.MANIFEST_VERSION, "1.0")
manifest.getMainAttributes put (AttrName.MAIN_CLASS, "JettyLauncher")
manifest.getMainAttributes put (AttrName.MANIFEST_VERSION, "1.0")
manifest.getMainAttributes put (AttrName.MAIN_CLASS, "JettyLauncher")
val outputFile = workDir / warName
IO jar (contentMappings, outputFile, manifest)

View File

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

24
doc/notification.md Normal file
View File

@@ -0,0 +1,24 @@
Notification Email
========
GitBucket can send email notification to users if this feature is enabled by an administrator.
The timing of the notification are as follows:
##### at the issue registration (new issue, new pull request)
When a record is saved into the ```ISSUE``` table, GitBucket does the notification.
##### at the comment registration
Among the records in the ```ISSUE_COMMENT``` table, them to be counted as a comment (i.e. the record ```ACTION``` column value is "comment" or "close_comment" or "reopen_comment") are saved, GitBucket does the notification.
##### at the status update (close, reopen, merge)
When the ```CLOSED``` column value is updated, GitBucket does the notification.
Notified users are as follows:
* individual repository's owner
* group members of group repository
* collaborators
* participants
However, the person performing the operation is excluded from the notification.

View File

@@ -6,6 +6,7 @@ Developer's Guide
* [Authentication in Controller](authenticator.md)
* [About Action in Issue Comment](comment_action.md)
* [Activity Types](activity.md)
* [Notification Email](notification.md)
* [Automatic Schema Updating](auto_update.md)
* [Release Operation](release.md)
* [JRebel integration (optional)](jrebel.md)

View File

@@ -1,54 +0,0 @@
[
{
"id": "notifications",
"name": "Notifications Plugin",
"description": "Provides notifications feature on GitBucket.",
"versions": [
{
"version": "1.1.0",
"range": ">=4.16.0",
"file": "gitbucket-notifications-plugin_2.12-1.1.0.jar"
}
],
"default": true
},
{
"id": "emoji",
"name": "Emoji Plugin",
"description": "Provides Emoji support for GitBucket.",
"versions": [
{
"version": "4.4.0",
"range": ">=4.10.0",
"file": "gitbucket-emoji-plugin_2.12-4.4.0.jar"
}
],
"default": false
},
{
"id": "gist",
"name": "Gist Plugin",
"description": "Provides Gist feature on GitBucket.",
"versions": [
{
"version": "4.10.0",
"range": ">=4.15.0",
"file": "gitbucket-gist-plugin_2.12-4.10.0.jar"
}
],
"default": false
},
{
"id": "pages",
"name": "Pages Plugin",
"description": "Project pages for gitbucket",
"versions": [
{
"version": "1.5.0",
"range": ">=4.15.0",
"file": "gitbucket-pages-plugin_2.12-1.5.0.jar"
}
],
"default": false
}
]

View File

@@ -1,6 +1,7 @@
import java.security.MessageDigest
import java.security.MessageDigest;
import scala.annotation._
import sbt._
import sbt.Using._
object Checksums {
private val bufferSize = 2048

View File

@@ -1,17 +0,0 @@
import com.eclipsesource.json.Json
import scala.collection.JavaConverters._
object PluginsJson {
def parse(json: String): Seq[(String, String)] = {
val value = Json.parse(json)
value.asArray.values.asScala.map { plugin =>
val obj = plugin.asObject.get("versions").asArray.asScala.head.asObject
val pluginName = obj.get("file").asString.split("_2.12-").head
val version = obj.get("version").asString
(pluginName, version)
}
}
}

View File

@@ -1 +0,0 @@
libraryDependencies += "com.eclipsesource.minimal-json" % "minimal-json" % "0.9.4"

View File

@@ -40,7 +40,7 @@ public class JettyLauncher {
}
break;
case "--max_file_size":
System.setProperty("gitbucket.maxFileSize", dim[1]);
System.setProperty("gitbucket.maxFileSize", dim[2]);
break;
case "--gitbucket.home":
System.setProperty("gitbucket.home", dim[1]);
@@ -48,12 +48,6 @@ public class JettyLauncher {
case "--temp_dir":
tmpDirPath = dim[1];
break;
case "--plugin_dir":
System.setProperty("gitbucket.pluginDir", dim[1]);
break;
case "--validate_password":
System.setProperty("gitbucket.validate.password", dim[1]);
break;
}
}
}

View File

@@ -25,12 +25,15 @@ class ScalatraBootstrap extends LifeCycle with SystemSettingsService {
context.getFilterRegistration("gitAuthenticationFilter").addMappingForUrlPatterns(EnumSet.allOf(classOf[DispatcherType]), true, "/git/*")
context.addFilter("apiAuthenticationFilter", new ApiAuthenticationFilter)
context.getFilterRegistration("apiAuthenticationFilter").addMappingForUrlPatterns(EnumSet.allOf(classOf[DispatcherType]), true, "/api/v3/*")
context.addFilter("ghCompatRepositoryAccessFilter", new GHCompatRepositoryAccessFilter)
context.getFilterRegistration("ghCompatRepositoryAccessFilter").addMappingForUrlPatterns(EnumSet.allOf(classOf[DispatcherType]), true, "/*")
// Register controllers
context.mount(new PreProcessController, "/*")
context.mount(new AnonymousAccessController, "/*")
context.addFilter("pluginControllerFilter", new PluginControllerFilter)
context.getFilterRegistration("pluginControllerFilter").addMappingForUrlPatterns(EnumSet.allOf(classOf[DispatcherType]), true, "/*")
PluginRegistry().getControllers.foreach { case (controller, path) =>
context.mount(controller, path)
}
context.mount(new IndexController, "/")
context.mount(new ApiController, "/api/v3")

View File

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

View File

@@ -232,10 +232,6 @@ trait AccountControllerBase extends AccountManagementControllerBase {
} getOrElse NotFound()
})
get("/captures/(.*)".r) {
multiParams("captures").head
}
get("/:userName/_delete")(oneselfOnly {
val userName = params("userName")

View File

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

View File

@@ -26,7 +26,6 @@ import org.eclipse.jgit.lib.ObjectId
import org.eclipse.jgit.revwalk.RevCommit
import org.eclipse.jgit.treewalk._
import org.apache.commons.io.IOUtils
import org.slf4j.LoggerFactory
/**
* Provides generic features for controller implementations.
@@ -35,8 +34,6 @@ abstract class ControllerBase extends ScalatraFilter
with ClientSideValidationFormSupport with JacksonJsonSupport with I18nSupport with FlashMapSupport with Validations
with SystemSettingsService {
private val logger = LoggerFactory.getLogger(getClass)
implicit val jsonFormats = gitbucket.core.api.JsonFormat.jsonFormats
before("/api/v3/*") {
@@ -150,20 +147,6 @@ 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,
includeContextPath: Boolean = true, includeServletPath: Boolean = true,
absolutize: Boolean = true, withSessionId: Boolean = true)
@@ -180,7 +163,7 @@ abstract class ControllerBase extends ScalatraFilter
override def validate(name: String, value: String, params: Map[String, String], messages: Messages): Seq[(String, String)] =
valueType.validate(name, trim(value), params, messages)
private def trim(value: String): String = if(value == null) null else value.replace("\r\n", "").trim
private def trim(value: String): String = if(value == null) null else value.replaceAll("\r\n", "").trim
}
/**

View File

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

View File

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

View File

@@ -150,7 +150,7 @@ trait RepositorySettingsControllerBase extends ControllerBase {
get("/:owner/:repository/settings/branches")(ownerOnly { repository =>
val protecteions = getProtectedBranchList(repository.owner, repository.name)
html.branches(repository, protecteions, flash.get("info"))
})
});
/** Update default branch */
post("/:owner/:repository/settings/update_default_branch", defaultBranchForm)(ownerOnly { (form, repository) =>
@@ -355,7 +355,7 @@ trait RepositorySettingsControllerBase extends ControllerBase {
FileUtils.moveDirectory(dir, getAttachedDir(form.newOwner, repository.name))
}
}
// Delete parent directory
// Delere parent directory
FileUtil.deleteDirectoryIfEmpty(getRepositoryFilesDir(repository.owner, repository.name))
// Call hooks
@@ -393,7 +393,7 @@ trait RepositorySettingsControllerBase extends ControllerBase {
post("/:owner/:repository/settings/gc")(ownerOnly { repository =>
LockUtil.lock(s"${repository.owner}/${repository.name}") {
using(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
git.gc()
git.gc();
}
}
flash += "info" -> "Garbage collection has been executed."

View File

@@ -13,7 +13,7 @@ import gitbucket.core.util.StringUtil._
import gitbucket.core.util.SyntaxSugars._
import gitbucket.core.util.Implicits._
import gitbucket.core.util.Directory._
import gitbucket.core.model.{Account, CommitState, CommitStatus, WebHook}
import gitbucket.core.model.{Account, WebHook}
import gitbucket.core.service.WebHookService._
import gitbucket.core.view
import gitbucket.core.view.helpers
@@ -174,24 +174,13 @@ trait RepositoryViewerControllerBase extends ControllerBase {
val (branchName, path) = repository.splitPath(multiParams("splat").head)
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 =>
JGitUtil.getCommitLog(git, branchName, page, 30, path) match {
case Right((logs, hasNext)) =>
html.commits(if(path.isEmpty) Nil else path.split("/").toList, branchName, repository,
logs.splitWith{ (commit1, commit2) =>
view.helpers.date(commit1.commitTime) == view.helpers.date(commit2.commitTime)
}, page, hasNext, hasDeveloperRole(repository.owner, repository.name, context.loginAccount), getStatuses, getSummary)
}, page, hasNext, hasDeveloperRole(repository.owner, repository.name, context.loginAccount))
case Left(_) => NotFound()
}
}
@@ -224,7 +213,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
post("/:owner/:repository/upload", uploadForm)(writableUsersOnly { (form, repository) =>
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)
}
@@ -233,7 +222,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
branch = form.branch,
path = form.path,
files = files,
message = form.message.getOrElse("Add files via upload")
message = form.message.getOrElse(s"Add files via upload")
)
if(form.path.length == 0){
@@ -641,8 +630,8 @@ trait RepositoryViewerControllerBase extends ControllerBase {
}
})
case class UploadFiles(branch: String, path: String, fileIds: Map[String,String], message: String) {
lazy val isValid: Boolean = fileIds.nonEmpty
case class UploadFiles(branch: String, path: String, fileIds : Map[String,String], message: String) {
lazy val isValid: Boolean = fileIds.size > 0
}
case class CommitFile(id: String, name: String)

View File

@@ -6,7 +6,7 @@ import gitbucket.core.admin.html
import gitbucket.core.service.{AccountService, RepositoryService, SystemSettingsService}
import gitbucket.core.util.{AdminAuthenticator, Mailer}
import gitbucket.core.ssh.SshServer
import gitbucket.core.plugin.{PluginInfoBase, PluginRegistry, PluginRepository}
import gitbucket.core.plugin.PluginRegistry
import SystemSettingsService._
import gitbucket.core.util.Implicits._
import gitbucket.core.util.SyntaxSugars._
@@ -15,10 +15,6 @@ import gitbucket.core.util.StringUtil._
import io.github.gitbucket.scalatra.forms._
import org.apache.commons.io.{FileUtils, IOUtils}
import org.scalatra.i18n.Messages
import com.github.zafarkhaja.semver.{Version => Semver}
import gitbucket.core.GitBucketCoreModule
import scala.collection.JavaConverters._
class SystemSettingsController extends SystemSettingsControllerBase
with AccountService with RepositoryService with AdminAuthenticator
@@ -63,8 +59,7 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
"tls" -> trim(label("Enable TLS", optional(boolean()))),
"ssl" -> trim(label("Enable SSL", optional(boolean()))),
"keystore" -> trim(label("Keystore", optional(text())))
)(Ldap.apply)),
"skinName" -> trim(label("AdminLTE skin name", text(required)))
)(Ldap.apply))
)(SystemSettings.apply).verifying { settings =>
Vector(
if(settings.ssh && settings.baseUrl.isEmpty){
@@ -175,9 +170,8 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
post("/admin/system/sendmail", sendMailForm)(adminOnly { form =>
try {
new Mailer(form.smtp).send(form.testAddress,
"Test message from GitBucket", context.loginAccount.get,
"This is a test message from GitBucket.", None
)
"Test message from GitBucket", "This is a test message from GitBucket.",
context.loginAccount.get)
"Test mail has been sent to: " + form.testAddress
@@ -187,71 +181,7 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
})
get("/admin/plugins")(adminOnly {
// Installed plugins
val enabledPlugins = PluginRegistry().getPlugins()
val gitbucketVersion = Semver.valueOf(GitBucketCoreModule.getVersions.asScala.last.getVersion)
// Plugins in the local repository
val repositoryPlugins = PluginRepository.getPlugins()
.filterNot { meta =>
enabledPlugins.exists { plugin => plugin.pluginId == meta.id &&
Semver.valueOf(plugin.pluginVersion).greaterThanOrEqualTo(Semver.valueOf(meta.latestVersion.version))
}
}.map { meta =>
(meta, meta.versions.reverse.find { version => gitbucketVersion.satisfies(version.range) })
}.collect { case (meta, Some(version)) =>
new PluginInfoBase(
pluginId = meta.id,
pluginName = meta.name,
pluginVersion = version.version,
description = meta.description
)
}
// Merge
val plugins = enabledPlugins.map((_, true)) ++ repositoryPlugins.map((_, false))
html.plugins(plugins, flash.get("info"))
})
post("/admin/plugins/_reload")(adminOnly {
PluginRegistry.reload(request.getServletContext(), loadSystemSettings(), request2Session(request).conn)
flash += "info" -> "All plugins were reloaded."
redirect("/admin/plugins")
})
post("/admin/plugins/:pluginId/:version/_uninstall")(adminOnly {
val pluginId = params("pluginId")
val version = params("version")
PluginRegistry().getPlugins()
.collect { case plugin if (plugin.pluginId == pluginId && plugin.pluginVersion == version) => plugin }
.foreach { _ =>
PluginRegistry.uninstall(pluginId, request.getServletContext, loadSystemSettings(), request2Session(request).conn)
flash += "info" -> s"${pluginId} was uninstalled."
}
redirect("/admin/plugins")
})
post("/admin/plugins/:pluginId/:version/_install")(adminOnly {
val pluginId = params("pluginId")
val version = params("version")
/// TODO!!!!
PluginRepository.getPlugins()
.collect { case meta if meta.id == pluginId => (meta, meta.versions.find(_.version == version) )}
.foreach { case (meta, version) =>
version.foreach { version =>
// TODO Install version!
PluginRegistry.install(
new java.io.File(PluginHome, s".repository/${version.file}"),
request.getServletContext,
loadSystemSettings(),
request2Session(request).conn
)
flash += "info" -> s"${pluginId} was installed."
}
}
redirect("/admin/plugins")
html.plugins(PluginRegistry().getPlugins())
})

View File

@@ -121,16 +121,6 @@ abstract class Plugin {
*/
def pullRequestHooks(registry: PluginRegistry, context: ServletContext, settings: SystemSettings): Seq[PullRequestHook] = Nil
/**
* Override to add repository headers.
*/
val repositoryHeaders: Seq[(RepositoryInfo, Context) => Option[Html]] = Nil
/**
* Override to add repository headers.
*/
def repositoryHeaders(registry: PluginRegistry, context: ServletContext, settings: SystemSettings): Seq[(RepositoryInfo, Context) => Option[Html]] = Nil
/**
* Override to add global menus.
*/
@@ -276,9 +266,6 @@ abstract class Plugin {
(pullRequestHooks ++ pullRequestHooks(registry, context, settings)).foreach { pullRequestHook =>
registry.addPullRequestHook(pullRequestHook)
}
(repositoryHeaders ++ repositoryHeaders(registry, context, settings)).foreach { repositoryHeader =>
registry.addRepositoryHeader(repositoryHeader)
}
(globalMenus ++ globalMenus(registry, context, settings)).foreach { globalMenu =>
registry.addGlobalMenu(globalMenu)
}
@@ -300,8 +287,8 @@ abstract class Plugin {
(dashboardTabs ++ dashboardTabs(registry, context, settings)).foreach { dashboardTab =>
registry.addDashboardTab(dashboardTab)
}
(issueSidebars ++ issueSidebars(registry, context, settings)).foreach { issueSidebarComponent =>
registry.addIssueSidebar(issueSidebarComponent)
(issueSidebars ++ issueSidebars(registry, context, settings)).foreach { issueSidebar =>
registry.addIssueSidebar(issueSidebar)
}
(assetsMappings ++ assetsMappings(registry, context, settings)).foreach { assetMapping =>
registry.addAssetsMapping((assetMapping._1, assetMapping._2, getClass.getClassLoader))
@@ -315,17 +302,11 @@ abstract class Plugin {
}
/**
* This method is invoked when the plugin system is shutting down.
* This method is invoked in shutdown of plugin system.
* If the plugin has any resources, release them in this method.
*/
def shutdown(registry: PluginRegistry, context: ServletContext, settings: SystemSettings): Unit = {}
// /**
// * This method is invoked when this plugin is uninstalled.
// * Cleanup database or any other resources in this method if necessary.
// */
// def uninstall(registry: PluginRegistry, context: ServletContext, settings: SystemSettings): Unit = {}
/**
* Helper method to get a resource from classpath.
*/

View File

@@ -2,18 +2,13 @@ package gitbucket.core.plugin
import java.io.{File, FilenameFilter, InputStream}
import java.net.URLClassLoader
import java.nio.file.{Files, Paths, StandardWatchEventKinds}
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 gitbucket.core.controller.{Context, ControllerBase}
import gitbucket.core.model.{Account, Issue}
import gitbucket.core.service.ProtectedBranchService.ProtectedBranchReceiveHook
import gitbucket.core.service.RepositoryService.RepositoryInfo
import gitbucket.core.service.SystemSettingsService
import gitbucket.core.service.SystemSettingsService.SystemSettings
import gitbucket.core.util.SyntaxSugars._
import gitbucket.core.util.DatabaseConfig
@@ -21,53 +16,55 @@ import gitbucket.core.util.Directory._
import io.github.gitbucket.solidbase.Solidbase
import io.github.gitbucket.solidbase.manager.JDBCVersionManager
import io.github.gitbucket.solidbase.model.Module
import org.apache.commons.io.FileUtils
import org.slf4j.LoggerFactory
import play.twirl.api.Html
import scala.collection.JavaConverters._
import scala.collection.mutable
import scala.collection.mutable.ListBuffer
class PluginRegistry {
private val plugins = new ConcurrentLinkedQueue[PluginInfo]
private val javaScripts = new ConcurrentLinkedQueue[(String, String)]
private val controllers = new ConcurrentLinkedQueue[(ControllerBase, String)]
private val images = new ConcurrentHashMap[String, String]
private val renderers = new ConcurrentHashMap[String, Renderer]
renderers.put("md", MarkdownRenderer)
renderers.put("markdown", MarkdownRenderer)
private val repositoryRoutings = new ConcurrentLinkedQueue[GitRepositoryRouting]
private val accountHooks = new ConcurrentLinkedQueue[AccountHook]
private val receiveHooks = new ConcurrentLinkedQueue[ReceiveHook]
receiveHooks.add(new ProtectedBranchReceiveHook())
private val plugins = new ListBuffer[PluginInfo]
private val javaScripts = new ListBuffer[(String, String)]
private val controllers = new ListBuffer[(ControllerBase, String)]
private val images = mutable.Map[String, String]()
private val renderers = mutable.Map[String, Renderer]()
renderers ++= Seq(
"md" -> MarkdownRenderer, "markdown" -> MarkdownRenderer
)
private val repositoryRoutings = new ListBuffer[GitRepositoryRouting]
private val accountHooks = new ListBuffer[AccountHook]
private val receiveHooks = new ListBuffer[ReceiveHook]
receiveHooks += new ProtectedBranchReceiveHook()
private val repositoryHooks = new ConcurrentLinkedQueue[RepositoryHook]
private val issueHooks = new ConcurrentLinkedQueue[IssueHook]
private val repositoryHooks = new ListBuffer[RepositoryHook]
private val issueHooks = new ListBuffer[IssueHook]
issueHooks += new gitbucket.core.util.Notifier.IssueHook()
private val pullRequestHooks = new ConcurrentLinkedQueue[PullRequestHook]
private val pullRequestHooks = new ListBuffer[PullRequestHook]
pullRequestHooks += new gitbucket.core.util.Notifier.PullRequestHook()
private val repositoryHeaders = new ConcurrentLinkedQueue[(RepositoryInfo, Context) => Option[Html]]
private val globalMenus = new ConcurrentLinkedQueue[(Context) => Option[Link]]
private val repositoryMenus = new ConcurrentLinkedQueue[(RepositoryInfo, Context) => Option[Link]]
private val repositorySettingTabs = new ConcurrentLinkedQueue[(RepositoryInfo, Context) => Option[Link]]
private val profileTabs = new ConcurrentLinkedQueue[(Account, Context) => Option[Link]]
private val systemSettingMenus = new ConcurrentLinkedQueue[(Context) => Option[Link]]
private val accountSettingMenus = new ConcurrentLinkedQueue[(Context) => Option[Link]]
private val dashboardTabs = new ConcurrentLinkedQueue[(Context) => Option[Link]]
private val issueSidebars = new ConcurrentLinkedQueue[(Issue, RepositoryInfo, Context) => Option[Html]]
private val assetsMappings = new ConcurrentLinkedQueue[(String, String, ClassLoader)]
private val textDecorators = new ConcurrentLinkedQueue[TextDecorator]
private val globalMenus = new ListBuffer[(Context) => Option[Link]]
private val repositoryMenus = new ListBuffer[(RepositoryInfo, Context) => Option[Link]]
private val repositorySettingTabs = new ListBuffer[(RepositoryInfo, Context) => Option[Link]]
private val profileTabs = new ListBuffer[(Account, Context) => Option[Link]]
private val systemSettingMenus = new ListBuffer[(Context) => Option[Link]]
private val accountSettingMenus = new ListBuffer[(Context) => Option[Link]]
private val dashboardTabs = new ListBuffer[(Context) => Option[Link]]
private val issueSidebars = new ListBuffer[(Issue, RepositoryInfo, Context) => Option[Html]]
private val assetsMappings = new ListBuffer[(String, String, ClassLoader)]
private val textDecorators = new ListBuffer[TextDecorator]
private val suggestionProviders = new ConcurrentLinkedQueue[SuggestionProvider]
suggestionProviders.add(new UserNameSuggestionProvider())
private val suggestionProviders = new ListBuffer[SuggestionProvider]
suggestionProviders += new UserNameSuggestionProvider()
def addPlugin(pluginInfo: PluginInfo): Unit = plugins.add(pluginInfo)
def addPlugin(pluginInfo: PluginInfo): Unit = plugins += pluginInfo
def getPlugins(): List[PluginInfo] = plugins.asScala.toList
def getPlugins(): List[PluginInfo] = plugins.toList
def addImage(id: String, bytes: Array[Byte]): Unit = {
val encoded = Base64.getEncoder.encodeToString(bytes)
images.put(id, encoded)
images += ((id, encoded))
}
@deprecated("Use addImage(id: String, bytes: Array[Byte]) instead", "3.4.0")
@@ -80,28 +77,28 @@ class PluginRegistry {
addImage(id, bytes)
}
def getImage(id: String): String = images.get(id)
def getImage(id: String): String = images(id)
def addController(path: String, controller: ControllerBase): Unit = controllers.add((controller, path))
def addController(path: String, controller: ControllerBase): Unit = controllers += ((controller, path))
@deprecated("Use addController(path: String, controller: ControllerBase) instead", "3.4.0")
def addController(controller: ControllerBase, path: String): Unit = addController(path, controller)
def getControllers(): Seq[(ControllerBase, String)] = controllers.asScala.toSeq
def getControllers(): Seq[(ControllerBase, String)] = controllers.toSeq
def addJavaScript(path: String, script: String): Unit = javaScripts.add((path, script)) //javaScripts += ((path, script))
def addJavaScript(path: String, script: String): Unit = javaScripts += ((path, script))
def getJavaScript(currentPath: String): List[String] = javaScripts.asScala.filter(x => currentPath.matches(x._1)).toList.map(_._2)
def getJavaScript(currentPath: String): List[String] = javaScripts.filter(x => currentPath.matches(x._1)).toList.map(_._2)
def addRenderer(extension: String, renderer: Renderer): Unit = renderers.put(extension, renderer)
def addRenderer(extension: String, renderer: Renderer): Unit = renderers += ((extension, renderer))
def getRenderer(extension: String): Renderer = renderers.asScala.getOrElse(extension, DefaultRenderer)
def getRenderer(extension: String): Renderer = renderers.getOrElse(extension, DefaultRenderer)
def renderableExtensions: Seq[String] = renderers.keys.asScala.toSeq
def renderableExtensions: Seq[String] = renderers.keys.toSeq
def addRepositoryRouting(routing: GitRepositoryRouting): Unit = repositoryRoutings.add(routing)
def addRepositoryRouting(routing: GitRepositoryRouting): Unit = repositoryRoutings += routing
def getRepositoryRoutings(): Seq[GitRepositoryRouting] = repositoryRoutings.asScala.toSeq
def getRepositoryRoutings(): Seq[GitRepositoryRouting] = repositoryRoutings.toSeq
def getRepositoryRouting(repositoryPath: String): Option[GitRepositoryRouting] = {
PluginRegistry().getRepositoryRoutings().find {
@@ -111,73 +108,69 @@ class PluginRegistry {
}
}
def addAccountHook(accountHook: AccountHook): Unit = accountHooks.add(accountHook)
def addAccountHook(accountHook: AccountHook): Unit = accountHooks += accountHook
def getAccountHooks: Seq[AccountHook] = accountHooks.asScala.toSeq
def getAccountHooks: Seq[AccountHook] = accountHooks.toSeq
def addReceiveHook(commitHook: ReceiveHook): Unit = receiveHooks.add(commitHook)
def addReceiveHook(commitHook: ReceiveHook): Unit = receiveHooks += commitHook
def getReceiveHooks: Seq[ReceiveHook] = receiveHooks.asScala.toSeq
def getReceiveHooks: Seq[ReceiveHook] = receiveHooks.toSeq
def addRepositoryHook(repositoryHook: RepositoryHook): Unit = repositoryHooks.add(repositoryHook)
def addRepositoryHook(repositoryHook: RepositoryHook): Unit = repositoryHooks += repositoryHook
def getRepositoryHooks: Seq[RepositoryHook] = repositoryHooks.asScala.toSeq
def getRepositoryHooks: Seq[RepositoryHook] = repositoryHooks.toSeq
def addIssueHook(issueHook: IssueHook): Unit = issueHooks.add(issueHook)
def addIssueHook(issueHook: IssueHook): Unit = issueHooks += issueHook
def getIssueHooks: Seq[IssueHook] = issueHooks.asScala.toSeq
def getIssueHooks: Seq[IssueHook] = issueHooks.toSeq
def addPullRequestHook(pullRequestHook: PullRequestHook): Unit = pullRequestHooks.add(pullRequestHook)
def addPullRequestHook(pullRequestHook: PullRequestHook): Unit = pullRequestHooks += pullRequestHook
def getPullRequestHooks: Seq[PullRequestHook] = pullRequestHooks.asScala.toSeq
def getPullRequestHooks: Seq[PullRequestHook] = pullRequestHooks.toSeq
def addRepositoryHeader(repositoryHeader: (RepositoryInfo, Context) => Option[Html]): Unit = repositoryHeaders.add(repositoryHeader)
def addGlobalMenu(globalMenu: (Context) => Option[Link]): Unit = globalMenus += globalMenu
def getRepositoryHeaders: Seq[(RepositoryInfo, Context) => Option[Html]] = repositoryHeaders.asScala.toSeq
def getGlobalMenus: Seq[(Context) => Option[Link]] = globalMenus.toSeq
def addGlobalMenu(globalMenu: (Context) => Option[Link]): Unit = globalMenus.add(globalMenu)
def addRepositoryMenu(repositoryMenu: (RepositoryInfo, Context) => Option[Link]): Unit = repositoryMenus += repositoryMenu
def getGlobalMenus: Seq[(Context) => Option[Link]] = globalMenus.asScala.toSeq
def getRepositoryMenus: Seq[(RepositoryInfo, Context) => Option[Link]] = repositoryMenus.toSeq
def addRepositoryMenu(repositoryMenu: (RepositoryInfo, Context) => Option[Link]): Unit = repositoryMenus.add(repositoryMenu)
def addRepositorySettingTab(repositorySettingTab: (RepositoryInfo, Context) => Option[Link]): Unit = repositorySettingTabs += repositorySettingTab
def getRepositoryMenus: Seq[(RepositoryInfo, Context) => Option[Link]] = repositoryMenus.asScala.toSeq
def getRepositorySettingTabs: Seq[(RepositoryInfo, Context) => Option[Link]] = repositorySettingTabs.toSeq
def addRepositorySettingTab(repositorySettingTab: (RepositoryInfo, Context) => Option[Link]): Unit = repositorySettingTabs.add(repositorySettingTab)
def addProfileTab(profileTab: (Account, Context) => Option[Link]): Unit = profileTabs += profileTab
def getRepositorySettingTabs: Seq[(RepositoryInfo, Context) => Option[Link]] = repositorySettingTabs.asScala.toSeq
def getProfileTabs: Seq[(Account, Context) => Option[Link]] = profileTabs.toSeq
def addProfileTab(profileTab: (Account, Context) => Option[Link]): Unit = profileTabs.add(profileTab)
def addSystemSettingMenu(systemSettingMenu: (Context) => Option[Link]): Unit = systemSettingMenus += systemSettingMenu
def getProfileTabs: Seq[(Account, Context) => Option[Link]] = profileTabs.asScala.toSeq
def getSystemSettingMenus: Seq[(Context) => Option[Link]] = systemSettingMenus.toSeq
def addSystemSettingMenu(systemSettingMenu: (Context) => Option[Link]): Unit = systemSettingMenus.add(systemSettingMenu)
def addAccountSettingMenu(accountSettingMenu: (Context) => Option[Link]): Unit = accountSettingMenus += accountSettingMenu
def getSystemSettingMenus: Seq[(Context) => Option[Link]] = systemSettingMenus.asScala.toSeq
def getAccountSettingMenus: Seq[(Context) => Option[Link]] = accountSettingMenus.toSeq
def addAccountSettingMenu(accountSettingMenu: (Context) => Option[Link]): Unit = accountSettingMenus.add(accountSettingMenu)
def addDashboardTab(dashboardTab: (Context) => Option[Link]): Unit = dashboardTabs += dashboardTab
def getAccountSettingMenus: Seq[(Context) => Option[Link]] = accountSettingMenus.asScala.toSeq
def getDashboardTabs: Seq[(Context) => Option[Link]] = dashboardTabs.toSeq
def addDashboardTab(dashboardTab: (Context) => Option[Link]): Unit = dashboardTabs.add(dashboardTab)
def addIssueSidebar(issueSidebar: (Issue, RepositoryInfo, Context) => Option[Html]): Unit = issueSidebars += issueSidebar
def getDashboardTabs: Seq[(Context) => Option[Link]] = dashboardTabs.asScala.toSeq
def getIssueSidebars: Seq[(Issue, RepositoryInfo, Context) => Option[Html]] = issueSidebars.toSeq
def addIssueSidebar(issueSidebar: (Issue, RepositoryInfo, Context) => Option[Html]): Unit = issueSidebars.add(issueSidebar)
def addAssetsMapping(assetsMapping: (String, String, ClassLoader)): Unit = assetsMappings += assetsMapping
def getIssueSidebars: Seq[(Issue, RepositoryInfo, Context) => Option[Html]] = issueSidebars.asScala.toSeq
def getAssetsMappings: Seq[(String, String, ClassLoader)] = assetsMappings.toSeq
def addAssetsMapping(assetsMapping: (String, String, ClassLoader)): Unit = assetsMappings.add(assetsMapping)
def addTextDecorator(textDecorator: TextDecorator): Unit = textDecorators += textDecorator
def getAssetsMappings: Seq[(String, String, ClassLoader)] = assetsMappings.asScala.toSeq
def getTextDecorators: Seq[TextDecorator] = textDecorators.toSeq
def addTextDecorator(textDecorator: TextDecorator): Unit = textDecorators.add(textDecorator)
def addSuggestionProvider(suggestionProvider: SuggestionProvider): Unit = suggestionProviders += suggestionProvider
def getTextDecorators: Seq[TextDecorator] = textDecorators.asScala.toSeq
def addSuggestionProvider(suggestionProvider: SuggestionProvider): Unit = suggestionProviders.add(suggestionProvider)
def getSuggestionProviders: Seq[SuggestionProvider] = suggestionProviders.asScala.toSeq
def getSuggestionProviders: Seq[SuggestionProvider] = suggestionProviders.toSeq
}
/**
@@ -187,233 +180,79 @@ object PluginRegistry {
private val logger = LoggerFactory.getLogger(classOf[PluginRegistry])
private var instance = new PluginRegistry()
private var watcher: PluginWatchThread = null
private var extraWatcher: PluginWatchThread = null
private val initializing = new AtomicBoolean(false)
private val instance = new PluginRegistry()
/**
* Returns the PluginRegistry singleton instance.
*/
def apply(): PluginRegistry = instance
/**
* Reload all plugins.
*/
def reload(context: ServletContext, settings: SystemSettings, conn: java.sql.Connection): Unit = synchronized {
shutdown(context, settings)
instance = new PluginRegistry()
initialize(context, settings, conn)
}
/**
* Uninstall a specified plugin.
*/
def uninstall(pluginId: String, context: ServletContext, settings: SystemSettings, conn: java.sql.Connection): Unit = synchronized {
instance.getPlugins()
.collect { case plugin if plugin.pluginId == pluginId => plugin }
.foreach { plugin =>
// try {
// plugin.pluginClass.uninstall(instance, context, settings)
// } catch {
// case e: Exception =>
// logger.error(s"Error during uninstalling plugin: ${plugin.pluginJar.getName}", e)
// }
shutdown(context, settings)
plugin.pluginJar.delete()
instance = new PluginRegistry()
initialize(context, settings, conn)
}
}
/**
* Install a plugin from a specified jar file.
*/
def install(file: File, context: ServletContext, settings: SystemSettings, conn: java.sql.Connection): Unit = synchronized {
shutdown(context, settings)
FileUtils.copyFile(file, new File(PluginHome, file.getName))
instance = new PluginRegistry()
initialize(context, settings, conn)
}
private def listPluginJars(dir: File): Seq[File] = {
dir.listFiles(new FilenameFilter {
override def accept(dir: File, name: String): Boolean = name.endsWith(".jar")
}).toSeq.sortBy(_.getName).reverse
}
lazy val extraPluginDir: Option[String] = Option(System.getProperty("gitbucket.pluginDir"))
/**
* Initializes all installed plugins.
*/
def initialize(context: ServletContext, settings: SystemSettings, conn: java.sql.Connection): Unit = synchronized {
def initialize(context: ServletContext, settings: SystemSettings, conn: java.sql.Connection): Unit = {
val pluginDir = new File(PluginHome)
val manager = new JDBCVersionManager(conn)
// Clean installed directory
val installedDir = new File(PluginHome, ".installed")
if(installedDir.exists){
FileUtils.deleteDirectory(installedDir)
}
installedDir.mkdir()
if(pluginDir.exists && pluginDir.isDirectory){
pluginDir.listFiles(new FilenameFilter {
override def accept(dir: File, name: String): Boolean = name.endsWith(".jar")
}).sortBy(_.getName).foreach { pluginJar =>
val classLoader = new URLClassLoader(Array(pluginJar.toURI.toURL), Thread.currentThread.getContextClassLoader)
try {
val plugin = classLoader.loadClass("Plugin").getDeclaredConstructor().newInstance().asInstanceOf[Plugin]
val pluginJars = listPluginJars(pluginDir)
val extraJars = extraPluginDir.map { extraDir => listPluginJars(new File(extraDir)) }.getOrElse(Nil)
// Migration
val solidbase = new Solidbase()
solidbase.migrate(conn, classLoader, DatabaseConfig.liquiDriver, new Module(plugin.pluginId, plugin.versions: _*))
(extraJars ++ pluginJars).foreach { pluginJar =>
val installedJar = new File(installedDir, pluginJar.getName)
FileUtils.copyFile(pluginJar, installedJar)
logger.info(s"Initialize ${pluginJar.getName}")
val classLoader = new URLClassLoader(Array(installedJar.toURI.toURL), Thread.currentThread.getContextClassLoader)
try {
val plugin = classLoader.loadClass("Plugin").getDeclaredConstructor().newInstance().asInstanceOf[Plugin]
val pluginId = plugin.pluginId
// Check duplication
instance.getPlugins().find(_.pluginId == pluginId) match {
case Some(x) => {
logger.warn(s"Plugin ${pluginId} is duplicated. ${x.pluginJar.getName} is available.")
// Check version
val databaseVersion = manager.getCurrentVersion(plugin.pluginId)
val pluginVersion = plugin.versions.last.getVersion
if(databaseVersion != pluginVersion){
throw new IllegalStateException(s"Plugin version is ${pluginVersion}, but database version is ${databaseVersion}")
}
case None => {
// Migration
val solidbase = new Solidbase()
solidbase.migrate(conn, classLoader, DatabaseConfig.liquiDriver, new Module(plugin.pluginId, plugin.versions: _*))
// Check database version
val databaseVersion = manager.getCurrentVersion(plugin.pluginId)
val pluginVersion = plugin.versions.last.getVersion
if (databaseVersion != pluginVersion) {
throw new IllegalStateException(s"Plugin version is ${pluginVersion}, but database version is ${databaseVersion}")
}
// Initialize
plugin.initialize(instance, context, settings)
instance.addPlugin(PluginInfo(
pluginId = plugin.pluginId,
pluginName = plugin.pluginName,
pluginVersion = plugin.versions.last.getVersion,
description = plugin.description,
pluginClass = plugin
))
// Initialize
plugin.initialize(instance, context, settings)
instance.addPlugin(PluginInfo(
pluginId = plugin.pluginId,
pluginName = plugin.pluginName,
pluginVersion = plugin.versions.last.getVersion,
description = plugin.description,
pluginClass = plugin,
pluginJar = pluginJar,
classLoader = classLoader
))
} catch {
case e: Throwable => {
logger.error(s"Error during plugin initialization: ${pluginJar.getAbsolutePath}", e)
}
}
} catch {
case e: Throwable => logger.error(s"Error during plugin initialization: ${pluginJar.getName}", e)
}
}
if(watcher == null){
watcher = new PluginWatchThread(context, PluginHome)
watcher.start()
}
extraPluginDir.foreach { extraDir =>
if(extraWatcher == null){
extraWatcher = new PluginWatchThread(context, extraDir)
extraWatcher.start()
}
}
}
def shutdown(context: ServletContext, settings: SystemSettings): Unit = synchronized {
instance.getPlugins().foreach { plugin =>
def shutdown(context: ServletContext, settings: SystemSettings): Unit = {
instance.getPlugins().foreach { pluginInfo =>
try {
plugin.pluginClass.shutdown(instance, context, settings)
if(watcher != null){
watcher.interrupt()
watcher = null
}
if(extraWatcher != null){
extraWatcher.interrupt()
extraWatcher = null
}
pluginInfo.pluginClass.shutdown(instance, context, settings)
} catch {
case e: Exception => {
logger.error(s"Error during plugin shutdown: ${plugin.pluginJar.getName}", e)
logger.error(s"Error during plugin shutdown", e)
}
} finally {
plugin.classLoader.close()
}
}
}
}
case class Link(
id: String,
label: String,
path: String,
icon: Option[String] = None
)
class PluginInfoBase(
val pluginId: String,
val pluginName: String,
val pluginVersion: String,
val description: String
)
case class Link(id: String, label: String, path: String, icon: Option[String] = None)
case class PluginInfo(
override val pluginId: String,
override val pluginName: String,
override val pluginVersion: String,
override val description: String,
pluginClass: Plugin,
pluginJar: File,
classLoader: URLClassLoader
) extends PluginInfoBase(pluginId, pluginName, pluginVersion, description)
class PluginWatchThread(context: ServletContext, dir: String) extends Thread with SystemSettingsService {
import gitbucket.core.model.Profile.profile.blockingApi._
import scala.collection.JavaConverters._
private val logger = LoggerFactory.getLogger(classOf[PluginWatchThread])
override def run(): Unit = {
val path = Paths.get(dir)
if(!Files.exists(path)){
Files.createDirectories(path)
}
val fs = path.getFileSystem
val watcher = fs.newWatchService
val watchKey = path.register(watcher,
StandardWatchEventKinds.ENTRY_CREATE,
StandardWatchEventKinds.ENTRY_MODIFY,
StandardWatchEventKinds.ENTRY_DELETE,
StandardWatchEventKinds.OVERFLOW)
logger.info("Start PluginWatchThread: " + path)
try {
while (watchKey.isValid()) {
val detectedWatchKey = watcher.take()
val events = detectedWatchKey.pollEvents.asScala.filter { e =>
e.context.toString != ".installed" && !e.context.toString.endsWith(".bak")
}
if(events.nonEmpty){
events.foreach { event =>
logger.info(event.kind + ": " + event.context)
}
gitbucket.core.servlet.Database() withTransaction { session =>
logger.info("Reloading plugins...")
PluginRegistry.reload(context, loadSystemSettings(), session.conn)
logger.info("Reloading finished.")
}
}
detectedWatchKey.reset()
}
} catch {
case _: InterruptedException => watchKey.cancel()
}
logger.info("Shutdown PluginWatchThread")
}
}
pluginId: String,
pluginName: String,
pluginVersion: String,
description: String,
pluginClass: Plugin
)

View File

@@ -1,41 +0,0 @@
package gitbucket.core.plugin
import org.json4s._
import gitbucket.core.util.Directory._
import org.apache.commons.io.FileUtils
object PluginRepository {
implicit val formats = DefaultFormats
def parsePluginJson(json: String): Seq[PluginMetadata] = {
org.json4s.jackson.JsonMethods.parse(json).extract[Seq[PluginMetadata]]
}
lazy val LocalRepositoryDir = new java.io.File(PluginHome, ".repository")
lazy val LocalRepositoryIndexFile = new java.io.File(LocalRepositoryDir, "plugins.json")
def getPlugins(): Seq[PluginMetadata] = {
if(LocalRepositoryIndexFile.exists){
parsePluginJson(FileUtils.readFileToString(LocalRepositoryIndexFile, "UTF-8"))
} else Nil
}
}
// Mapped from plugins.json
case class PluginMetadata(
id: String,
name: String,
description: String,
versions: Seq[VersionDef],
default: Boolean = false
){
lazy val latestVersion: VersionDef = versions.last
}
case class VersionDef(
version: String,
file: String,
range: String
)

View File

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

View File

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

View File

@@ -163,7 +163,7 @@ object MergeService{
case e: NoMergeBaseException => true
}
val mergeTipCommit = using(new RevWalk( repository ))(_.parseCommit( mergeTip ))
val committer = mergeTipCommit.getCommitterIdent
val committer = mergeTipCommit.getCommitterIdent;
def updateBranch(treeId:ObjectId, message:String, branchName:String){
// creates merge commit
val mergeCommitId = createMergeCommit(treeId, committer, message)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,37 @@
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 {
case Array(_, repositoryOwner, repositoryName, _*) =>
Database() withSession { implicit session =>
getRepository(repositoryOwner, repositoryName.replaceFirst("(\\.wiki)?\\.git$", "")) match {
getRepository(repositoryOwner, repositoryName.replaceFirst("\\.wiki\\.git$|\\.git$", "")) match {
case Some(repository) => {
val execute = if (!isUpdating && !repository.repository.isPrivate && settings.allowAnonymousAccess) {
// Authentication is not required

View File

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

View File

@@ -1,30 +1,25 @@
package gitbucket.core.servlet
import java.io.{File, FileOutputStream}
import java.io.File
import akka.event.Logging
import com.typesafe.config.ConfigFactory
import gitbucket.core.GitBucketCoreModule
import gitbucket.core.plugin.{PluginRegistry, PluginRepository}
import gitbucket.core.plugin.PluginRegistry
import gitbucket.core.service.{ActivityService, SystemSettingsService}
import gitbucket.core.util.DatabaseConfig
import gitbucket.core.util.Directory._
import gitbucket.core.util.SyntaxSugars._
import gitbucket.core.util.JDBCUtil._
import gitbucket.core.model.Profile.profile.blockingApi._
import io.github.gitbucket.solidbase.Solidbase
import io.github.gitbucket.solidbase.manager.JDBCVersionManager
import javax.servlet.{ServletContextEvent, ServletContextListener}
import org.apache.commons.io.{FileUtils, IOUtils}
import javax.servlet.{ServletContextListener, ServletContextEvent}
import org.apache.commons.io.FileUtils
import org.slf4j.LoggerFactory
import akka.actor.{Actor, ActorSystem, Props}
import akka.actor.{Actor, Props, ActorSystem}
import com.typesafe.akka.extension.quartz.QuartzSchedulerExtension
import com.github.zafarkhaja.semver.{Version => Semver}
import scala.collection.JavaConverters._
/**
* Initialize GitBucket system.
* Update database schema and load plug-ins automatically in the context initializing.
@@ -59,11 +54,44 @@ class InitializeListener extends ServletContextListener with SystemSettingsServi
val manager = new JDBCVersionManager(conn)
// Check version
checkVersion(manager, conn)
val versionFile = new File(GitBucketHome, "version")
if(versionFile.exists()){
val version = FileUtils.readFileToString(versionFile, "UTF-8")
if(version == "3.14"){
// Initialization for GitBucket 3.14
logger.info("Migration to GitBucket 4.x start")
// Backup current data
val dataMvFile = new File(GitBucketHome, "data.mv.db")
if(dataMvFile.exists) {
FileUtils.copyFile(dataMvFile, new File(GitBucketHome, "data.mv.db_3.14"))
}
val dataTraceFile = new File(GitBucketHome, "data.trace.db")
if(dataTraceFile.exists) {
FileUtils.copyFile(dataTraceFile, new File(GitBucketHome, "data.trace.db_3.14"))
}
// Change form
manager.initialize()
manager.updateVersion(GitBucketCoreModule.getModuleId, "4.0.0")
conn.select("SELECT PLUGIN_ID, VERSION FROM PLUGIN"){ rs =>
manager.updateVersion(rs.getString("PLUGIN_ID"), rs.getString("VERSION"))
}
conn.update("DROP TABLE PLUGIN")
versionFile.delete()
logger.info("Migration to GitBucket 4.x completed")
} else {
throw new Exception("GitBucket can't migrate from this version. Please update to 3.14 at first.")
}
}
// Run normal migration
logger.info("Start schema update")
new Solidbase().migrate(conn, Thread.currentThread.getContextClassLoader, DatabaseConfig.liquiDriver, GitBucketCoreModule)
val solidbase = new Solidbase()
solidbase.migrate(conn, Thread.currentThread.getContextClassLoader, DatabaseConfig.liquiDriver, GitBucketCoreModule)
// Rescue code for users who updated from 3.14 to 4.0.0
// https://github.com/gitbucket/gitbucket/issues/1227
@@ -78,9 +106,6 @@ class InitializeListener extends ServletContextListener with SystemSettingsServi
throw new IllegalStateException(s"Initialization failed. GitBucket version is ${gitbucketVersion}, but database version is ${databaseVersion}.")
}
// Install bundled plugins
extractBundledPlugins(gitbucketVersion)
// Load plugins
logger.info("Initialize plugins")
PluginRegistry.initialize(event.getServletContext, loadSystemSettings(), conn)
@@ -92,76 +117,7 @@ class InitializeListener extends ServletContextListener with SystemSettingsServi
scheduler.schedule("Daily", system.actorOf(Props[DeleteOldActivityActor]), "DeleteOldActivity")
}
private def checkVersion(manager: JDBCVersionManager, conn: java.sql.Connection): Unit = {
logger.info("Check version")
val versionFile = new File(GitBucketHome, "version")
if(versionFile.exists()){
val version = FileUtils.readFileToString(versionFile, "UTF-8")
if(version == "3.14"){
// Initialization for GitBucket 3.14
logger.info("Migration to GitBucket 4.x start")
// Backup current data
val dataMvFile = new File(GitBucketHome, "data.mv.db")
if(dataMvFile.exists) {
FileUtils.copyFile(dataMvFile, new File(GitBucketHome, "data.mv.db_3.14"))
}
val dataTraceFile = new File(GitBucketHome, "data.trace.db")
if(dataTraceFile.exists) {
FileUtils.copyFile(dataTraceFile, new File(GitBucketHome, "data.trace.db_3.14"))
}
// Change form
manager.initialize()
manager.updateVersion(GitBucketCoreModule.getModuleId, "4.0.0")
conn.select("SELECT PLUGIN_ID, VERSION FROM PLUGIN"){ rs =>
manager.updateVersion(rs.getString("PLUGIN_ID"), rs.getString("VERSION"))
}
conn.update("DROP TABLE PLUGIN")
versionFile.delete()
logger.info("Migration to GitBucket 4.x completed")
} else {
throw new Exception("GitBucket can't migrate from this version. Please update to 3.14 at first.")
}
}
}
private def extractBundledPlugins(gitbucketVersion: String): Unit = {
logger.info("Extract bundled plugins")
val cl = Thread.currentThread.getContextClassLoader
try {
using(cl.getResourceAsStream("plugins/plugins.json")){ pluginsFile =>
if(pluginsFile != null){
val pluginsJson = IOUtils.toString(pluginsFile, "UTF-8")
FileUtils.forceMkdir(PluginRepository.LocalRepositoryDir)
FileUtils.write(PluginRepository.LocalRepositoryIndexFile, pluginsJson, "UTF-8")
val plugins = PluginRepository.parsePluginJson(pluginsJson)
plugins.foreach { plugin =>
plugin.versions.sortBy { x => Semver.valueOf(x.version) }.reverse.zipWithIndex.foreach { case (version, i) =>
val file = new File(PluginRepository.LocalRepositoryDir, version.file)
if(!file.exists) {
logger.info(s"Copy ${plugin} to ${file.getAbsolutePath}")
FileUtils.forceMkdirParent(file)
using(cl.getResourceAsStream("plugins/" + version.file), new FileOutputStream(file)){ case (in, out) => IOUtils.copy(in, out) }
if(plugin.default && i == 0){
logger.info(s"Enable ${file.getName} in default")
FileUtils.copyFile(file, new File(PluginHome, version.file))
}
}
}
}
}
}
} catch {
case e: Exception => logger.error("Error in extracting bundled plugin", e)
}
}
override def contextDestroyed(event: ServletContextEvent): Unit = {
// Shutdown Quartz scheduler
@@ -190,4 +146,4 @@ class DeleteOldActivityActor extends Actor with SystemSettingsService with Activ
}
}
}
}
}

View File

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

View File

@@ -1,45 +0,0 @@
package gitbucket.core.servlet
import javax.servlet._
import javax.servlet.http.HttpServletRequest
import gitbucket.core.controller.ControllerBase
import gitbucket.core.plugin.PluginRegistry
class PluginControllerFilter extends Filter {
private var filterConfig: FilterConfig = null
override def init(filterConfig: FilterConfig): Unit = {
this.filterConfig = filterConfig
}
override def destroy(): Unit = {
PluginRegistry().getControllers().foreach { case (controller, _) =>
controller.destroy()
}
}
override def doFilter(request: ServletRequest, response: ServletResponse, chain: FilterChain): Unit = {
val controller = PluginRegistry().getControllers().filter { case (_, path) =>
val requestUri = request.asInstanceOf[HttpServletRequest].getRequestURI
val start = path.replaceFirst("/\\*$", "/")
path.endsWith("/*") && (requestUri + "/").startsWith(start)
}
val filterChainWrapper = controller.foldLeft(chain){ case (chain, (controller, _)) =>
new FilterChainWrapper(controller, chain)
}
filterChainWrapper.doFilter(request, response)
}
class FilterChainWrapper(controller: ControllerBase, chain: FilterChain) extends FilterChain {
override def doFilter(request: ServletRequest, response: ServletResponse): Unit = {
if(controller.config == null){
controller.init(filterConfig)
}
controller.doFilter(request, response, chain)
}
}
}

View File

@@ -19,7 +19,7 @@ import org.apache.sshd.server.scp.UnknownCommand
import org.eclipse.jgit.errors.RepositoryNotFoundException
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
}

View File

@@ -6,7 +6,7 @@ import javax.servlet.{ServletContextEvent, ServletContextListener}
import gitbucket.core.service.SystemSettingsService
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.slf4j.LoggerFactory

View File

@@ -92,7 +92,7 @@ object DatabaseType {
}
object MySQL extends DatabaseType {
val jdbcDriver = "org.mariadb.jdbc.Driver"
val jdbcDriver = "com.mysql.jdbc.Driver"
val slickDriver = BlockingMySQLDriver
val liquiDriver = new MySQLDatabase()
}

View File

@@ -90,4 +90,4 @@ object Directory {
def getWikiRepositoryDir(owner: String, repository: String): File =
new File(s"${RepositoryHome}/${owner}/${repository}.wiki.git")
}
}

View File

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

View File

@@ -14,7 +14,7 @@ import org.eclipse.jgit.revwalk.filter._
import org.eclipse.jgit.treewalk._
import org.eclipse.jgit.treewalk.filter._
import org.eclipse.jgit.diff.DiffEntry.ChangeType
import org.eclipse.jgit.errors.{ConfigInvalidException, IncorrectObjectTypeException, MissingObjectException}
import org.eclipse.jgit.errors.{ConfigInvalidException, MissingObjectException}
import org.eclipse.jgit.transport.RefSpec
import java.util.Date
import java.util.concurrent.TimeUnit
@@ -93,7 +93,7 @@ object JGitUtil {
val summary = getSummaryMessage(fullMessage, shortMessage)
val description = defining(fullMessage.trim.indexOf('\n')){ i =>
val description = defining(fullMessage.trim.indexOf("\n")){ i =>
if(i >= 0){
Some(fullMessage.trim.substring(i).trim)
} else None
@@ -226,14 +226,9 @@ object JGitUtil {
ref.getName.stripPrefix("refs/heads/")
}.toList,
// tags
git.tagList.call.asScala.flatMap { ref =>
try {
val revCommit = getRevCommitFromId(git, ref.getObjectId)
Some(TagInfo(ref.getName.stripPrefix("refs/tags/"), revCommit.getCommitterIdent.getWhen, revCommit.getName))
} catch {
case _: IncorrectObjectTypeException =>
None
}
git.tagList.call.asScala.map { ref =>
val revCommit = getRevCommitFromId(git, ref.getObjectId)
TagInfo(ref.getName.stripPrefix("refs/tags/"), revCommit.getCommitterIdent.getWhen, revCommit.getName)
}.sortBy(_.time).toList
)
} catch {
@@ -293,7 +288,7 @@ object JGitUtil {
@tailrec
def findLastCommits(result:List[(ObjectId, FileMode, String, String, Option[String], 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){
result
} else if(!revIterator.hasNext){ // maybe, revCommit has only 1 log. other case, restList be empty
@@ -364,9 +359,9 @@ object JGitUtil {
(file1.isDirectory, file2.isDirectory) match {
case (true , false) => true
case (false, true ) => false
case _ => file1.name.compareTo(file2.name) < 0
case _ => file1.name.compareTo(file2.name) < 0
}
}
}.toList
}
}
@@ -374,7 +369,7 @@ object JGitUtil {
* Returns the first line of the commit message.
*/
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 =>
if(firstLine.length > shortMessage.length) shortMessage else firstLine
}
@@ -999,13 +994,13 @@ object JGitUtil {
def getBlame(git: Git, id: String, path: String): Iterable[BlameInfo] = {
Option(git.getRepository.resolve(id)).map{ commitId =>
val blamer = new org.eclipse.jgit.api.BlameCommand(git.getRepository)
val blamer = new org.eclipse.jgit.api.BlameCommand(git.getRepository);
blamer.setStartCommit(commitId)
blamer.setFilePath(path)
val blame = blamer.call()
var blameMap = Map[String, JGitUtil.BlameInfo]()
var idLine = List[(String, Int)]()
val commits = 0.to(blame.getResultContents().size() - 1).map{ i =>
val commits = 0.to(blame.getResultContents().size()-1).map{ i =>
val c = blame.getSourceCommit(i)
if(!blameMap.contains(c.name)){
blameMap += c.name -> JGitUtil.BlameInfo(
@@ -1015,7 +1010,7 @@ object JGitUtil {
c.getAuthorIdent.getWhen,
Option(git.log.add(c).addPath(blame.getSourcePath(i)).setSkip(1).setMaxCount(2).call.iterator.next)
.map(_.name),
if(blame.getSourcePath(i)==path){ None } else { Some(blame.getSourcePath(i)) },
if(blame.getSourcePath(i)==path){ None }else{ Some(blame.getSourcePath(i)) },
c.getCommitterIdent.getWhen,
c.getShortMessage,
Set.empty)

View File

@@ -1,9 +1,10 @@
package gitbucket.core.util
import gitbucket.core.model.{Session, Account}
import gitbucket.core.model.{Session, Issue, Account}
import gitbucket.core.model.Profile.profile.blockingApi._
import gitbucket.core.service.SystemSettingsService
import gitbucket.core.service.{RepositoryService, AccountService, IssuesService, SystemSettingsService}
import gitbucket.core.servlet.Database
import gitbucket.core.view.Markdown
import scala.concurrent._
import scala.util.{Success, Failure}
@@ -19,12 +20,8 @@ import SystemSettingsService.Smtp
* Please see the plugin for details.
*/
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, textMsg: String, htmlMsg: Option[String])
def toNotify(subject: String, msg: String)
(recipients: Account => Session => Seq[String])(implicit context: Context): Unit
}
@@ -34,12 +31,131 @@ object Notifier {
case settings if (settings.notification && settings.useSMTP) => new Mailer(settings.smtp.get)
case _ => new MockMailer
}
// TODO This class is temporary keeping the current feature until Notifications Plugin is available.
class IssueHook extends gitbucket.core.plugin.IssueHook
with RepositoryService with AccountService with IssuesService {
override def created(issue: Issue, r: RepositoryService.RepositoryInfo)(implicit context: Context): Unit = {
Notifier().toNotify(
subject(issue, r),
message(issue.content getOrElse "", r)(content => s"""
|$content<br/>
|--<br/>
|<a href="${s"${context.baseUrl}/${r.owner}/${r.name}/issues/${issue.issueId}"}">View it on GitBucket</a>
""".stripMargin)
)(recipients(issue))
}
override def addedComment(commentId: Int, content: String, issue: Issue, r: RepositoryService.RepositoryInfo)(implicit context: Context): Unit = {
Notifier().toNotify(
subject(issue, r),
message(content, r)(content => s"""
|$content<br/>
|--<br/>
|<a href="${s"${context.baseUrl}/${r.owner}/${r.name}/issues/${issue.issueId}#comment-$commentId"}">View it on GitBucket</a>
""".stripMargin)
)(recipients(issue))
}
override def closed(issue: Issue, r: RepositoryService.RepositoryInfo)(implicit context: Context): Unit = {
Notifier().toNotify(
subject(issue, r),
message("close", r)(content => s"""
|$content <a href="${s"${context.baseUrl}/${r.owner}/${r.name}/issues/${issue.issueId}"}">#${issue.issueId}</a>
""".stripMargin)
)(recipients(issue))
}
override def reopened(issue: Issue, r: RepositoryService.RepositoryInfo)(implicit context: Context): Unit = {
Notifier().toNotify(
subject(issue, r),
message("reopen", r)(content => s"""
|$content <a href="${s"${context.baseUrl}/${r.owner}/${r.name}/issues/${issue.issueId}"}">#${issue.issueId}</a>
""".stripMargin)
)(recipients(issue))
}
protected def subject(issue: Issue, r: RepositoryService.RepositoryInfo): String =
s"[${r.owner}/${r.name}] ${issue.title} (#${issue.issueId})"
protected def message(content: String, r: RepositoryService.RepositoryInfo)(msg: String => String)(implicit context: Context): String =
msg(Markdown.toHtml(
markdown = content,
repository = r,
enableWikiLink = false,
enableRefsLink = true,
enableAnchor = false,
enableLineBreaks = false
))
protected val recipients: Issue => Account => Session => Seq[String] = {
issue => loginAccount => implicit session =>
(
// individual repository's owner
issue.userName ::
// group members of group repository
getGroupMembers(issue.userName).map(_.userName) :::
// collaborators
getCollaboratorUserNames(issue.userName, issue.repositoryName) :::
// participants
issue.openedUserName ::
getComments(issue.userName, issue.repositoryName, issue.issueId).map(_.commentedUserName)
)
.distinct
.withFilter ( _ != loginAccount.userName ) // the operation in person is excluded
.flatMap (
getAccountByUserName(_)
.filterNot (_.isGroupAccount)
.filterNot (LDAPUtil.isDummyMailAddress)
.map (_.mailAddress)
)
}
}
// TODO This class is temporary keeping the current feature until Notifications Plugin is available.
class PullRequestHook extends IssueHook with gitbucket.core.plugin.PullRequestHook {
override def created(issue: Issue, r: RepositoryService.RepositoryInfo)(implicit context: Context): Unit = {
val url = s"${context.baseUrl}/${r.owner}/${r.name}/pull/${issue.issueId}"
Notifier().toNotify(
subject(issue, r),
message(issue.content getOrElse "", r)(content => s"""
|$content<hr/>
|View, comment on, or merge it at:<br/>
|<a href="$url">$url</a>
""".stripMargin)
)(recipients(issue))
}
override def addedComment(commentId: Int, content: String, issue: Issue, r: RepositoryService.RepositoryInfo)(implicit context: Context): Unit = {
Notifier().toNotify(
subject(issue, r),
message(content, r)(content => s"""
|$content<br/>
|--<br/>
|<a href="${s"${context.baseUrl}/${r.owner}/${r.name}/pull/${issue.issueId}#comment-$commentId"}">View it on GitBucket</a>
""".stripMargin)
)(recipients(issue))
}
override def merged(issue: Issue, r: RepositoryService.RepositoryInfo)(implicit context: Context): Unit = {
Notifier().toNotify(
subject(issue, r),
message("merge", r)(content => s"""
|$content <a href="${s"${context.baseUrl}/${r.owner}/${r.name}/pull/${issue.issueId}"}">#${issue.issueId}</a>
""".stripMargin)
)(recipients(issue))
}
}
}
class Mailer(private val smtp: Smtp) extends Notifier {
private val logger = LoggerFactory.getLogger(classOf[Mailer])
def toNotify(subject: String, textMsg: String, htmlMsg: Option[String] = None)
def toNotify(subject: String, msg: String)
(recipients: Account => Session => Seq[String])(implicit context: Context): Unit = {
context.loginAccount.foreach { loginAccount =>
val database = Database()
@@ -47,7 +163,7 @@ class Mailer(private val smtp: Smtp) extends Notifier {
val f = Future {
database withSession { session =>
recipients(loginAccount)(session) foreach { to =>
send(to, subject, loginAccount, textMsg, htmlMsg)
send(to, subject, msg, loginAccount)
}
}
"Notifications Successful."
@@ -59,7 +175,7 @@ class Mailer(private val smtp: Smtp) extends Notifier {
}
}
def send(to: String, subject: String, loginAccount: Account, textMsg: String, htmlMsg: Option[String] = None): Unit = {
def send(to: String, subject: String, msg: String, loginAccount: Account): Unit = {
val email = new HtmlEmail
email.setHostName(smtp.host)
email.setSmtpPort(smtp.port.get)
@@ -84,16 +200,13 @@ class Mailer(private val smtp: Smtp) extends Notifier {
}
email.setCharset("UTF-8")
email.setSubject(subject)
email.setTextMsg(textMsg)
htmlMsg.foreach { msg =>
email.setHtmlMsg(msg)
}
email.setHtmlMsg(msg)
email.addTo(to).send
}
}
class MockMailer extends Notifier {
def toNotify(subject: String, textMsg: String, htmlMsg: Option[String] = None)
def toNotify(subject: String, msg: String)
(recipients: Account => Session => Seq[String])(implicit context: Context): Unit = ()
}

View File

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

View File

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

View File

@@ -1,32 +1,18 @@
@(plugins: List[(gitbucket.core.plugin.PluginInfoBase, Boolean)], info: Option[Any])(implicit context: gitbucket.core.controller.Context)
@(plugins: List[gitbucket.core.plugin.PluginInfo])(implicit context: gitbucket.core.controller.Context)
@gitbucket.core.html.main("Plugins"){
@gitbucket.core.admin.html.menu("plugins") {
@gitbucket.core.helper.html.information(info)
<form action="@context.path/admin/plugins/_reload" method="POST" class="pull-right">
<input type="submit" value="Reload plugins" class="btn btn-default">
</form>
<h1>Plugins</h1>
<h1>Installed plugins</h1>
@if(plugins.size > 0) {
<ul>
@plugins.map { case (plugin, enabled) =>
@plugins.map { plugin =>
<li><a href="#@plugin.pluginId">@plugin.pluginId:@plugin.pluginVersion</a></li>
}
</ul>
@plugins.map { case (plugin, enabled) =>
@plugins.map { plugin =>
<div class="panel panel-default">
<div class="panel-heading strong" id="@plugin.pluginId">
@if(enabled){
<form action="@{context.path}/admin/plugins/@{plugin.pluginId}/@{plugin.pluginVersion}/_uninstall" method="POST" class="pull-right uninstall-form">
<input type="submit" value="Uninstall" class="btn btn-danger btn-sm" style="position: relative; top: -5px; left: 10px;" data-name="@plugin.pluginName">
</form>
} else {
<form action="@{context.path}/admin/plugins/@{plugin.pluginId}/@{plugin.pluginVersion}/_install" method="POST" class="pull-right install-form">
<input type="submit" value="Install" class="btn btn-success btn-sm" style="position: relative; top: -5px; left: 10px;" data-name="@plugin.pluginName">
</form>
}
@plugin.pluginName
</div>
<div class="panel-heading strong" id="@plugin.pluginId">@plugin.pluginName</div>
<div class="panel-body">
<div class="row">
<label class="col-md-2">Id</label>
@@ -52,16 +38,3 @@
}
}
}
<script>
$(function(){
$('.uninstall-form').click(function(e){
var name = $(e.target).data('name');
return confirm('Uninstall ' + name + '. Are you sure?');
});
$('.install-form').click(function(e){
var name = $(e.target).data('name');
return confirm('Install ' + name + '. Are you sure?');
});
});
</script>

View File

@@ -1,6 +1,5 @@
@(info: Option[Any])(implicit context: gitbucket.core.controller.Context)
@import gitbucket.core.util.DatabaseConfig
@import gitbucket.core.view.helpers
@gitbucket.core.html.main("System settings"){
@gitbucket.core.admin.html.menu("system"){
@gitbucket.core.helper.html.information(info)
@@ -345,36 +344,6 @@
</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 class="align-right" style="margin-top: 20px;">

View File

@@ -14,7 +14,7 @@
} else {
<li><form class="sidebar-form"><input type="text" id="filter-box" class="form-control input-sm" placeholder="Find repository"/></form></li>
@userRepositories.zipWithIndex.map { case (repository, i) =>
<li class="repo-link menu-item-hover">
<li class="menu-item-hover">
@if(repository.owner == context.loginAccount.get.userName){
<a href="@helpers.url(repository)">@gitbucket.core.helper.html.repositoryicon(repository, false) <span class="strong">@repository.name</span></a>
} else {
@@ -30,7 +30,7 @@
} else {
<li><form class="sidebar-form"><input type="text" id="filter-box" class="form-control input-sm" placeholder="Find repository"/></form></li>
@recentRepositories.zipWithIndex.map { case (repository, i) =>
<li class="repo-link menu-item-hover">
<li class="menu-item-hover">
<a href="@helpers.url(repository)">@gitbucket.core.helper.html.repositoryicon(repository, false) <span>@repository.owner/<span class="strong">@repository.name</span></span></a>
</li>
}

View File

@@ -1,22 +1,8 @@
@(title: String, e: Option[Throwable]=None)(implicit context: gitbucket.core.controller.Context)
@(title: String)(implicit context: gitbucket.core.controller.Context)
@gitbucket.core.html.main("Error"){
<div class="content-wrapper main-center">
<div class="content body">
<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>
}
}

View File

@@ -3,12 +3,12 @@
<div class="input-group" style="margin-bottom: 0px;">
@html
<span class="input-group-btn">
<span id="@copyButtonId" class="btn btn-sm btn-default" @if(style.nonEmpty){style="@style"}
<span id="@copyButtonId" class="btn btn-default" @if(style.nonEmpty){style="@style"}
data-clipboard-text="@value" data-placement="bottom" title="copy to clipboard"><i class="octicon octicon-clippy"></i></span>
</span>
</div>
} else {
<span id="@copyButtonId" class="btn btn-sm btn-default" @if(style.nonEmpty){style="@style"}
<span id="@copyButtonId" class="btn btn-default" @if(style.nonEmpty){style="@style"}
data-clipboard-text="@value" data-placement="bottom" title="copy to clipboard"><i class="octicon octicon-clippy"></i></span>
}
<script>
@@ -18,7 +18,7 @@
if (document.queryCommandSupported('copy')) {
var title = $('#@copyButtonId').attr('title');
$('#@copyButtonId').tooltip({
@* if default container is used then 2 lines tooltip text is displayed because tooptip width is narrow. *@
@* if default container is used then 2 lines tooltip text is displayd because tooptip width is narrow. *@
container: 'body'
});
$('#@copyButtonId').on('click', function() {

View File

@@ -2,10 +2,10 @@
@import gitbucket.core.view.helpers
<feed xmlns="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" xml:lang="en-US">
<id>tag:@context.host,2013:gitbucket</id>
<title>GitBucket's activities</title>
<title>Gitbucket's activities</title>
<link type="application/atom+xml" rel="self" href="@context.baseUrl/activities.atom"/>
<author>
<name>GitBucket</name>
<name>Gitbucket</name>
<uri>@context.baseUrl</uri>
</author>
<updated>@helpers.datetimeRFC3339(if(activities.isEmpty) new java.util.Date else activities.map(_.activityDate).max)</updated>

View File

@@ -16,7 +16,7 @@
<div class="tabbable">
<ul class="nav nav-tabs fill-width" style="margin-bottom: 10px;">
<li class="active"><a href="#tab@uid" data-toggle="tab">Write</a></li>
<li><a href="#tab@(uid + 1)" data-toggle="tab" id="preview@uid">Preview</a></li>
<li><a href="#tab@(uid+1)" data-toggle="tab" id="preview@uid">Preview</a></li>
</ul>
<div class="tab-content">
<div class="tab-pane active" style="margin-top: 4px;" id="tab@uid">
@@ -32,7 +32,7 @@
generateScript = !enableWikiLink
)(textarea)
</div>
<div class="tab-pane" id="tab@(uid + 1)">
<div class="tab-pane" id="tab@(uid+1)">
<div class="markdown-body" id="preview-area@uid">
</div>
</div>

View File

@@ -1,22 +1,12 @@
@(content: String, commentId: Int,
repository: gitbucket.core.service.RepositoryService.RepositoryInfo)(implicit context: gitbucket.core.controller.Context)
<span id="error-edit-content-@commentId" class="error"></span>
@gitbucket.core.helper.html.preview(
repository = repository,
content = content,
enableWikiLink = false,
enableRefsLink = true,
enableLineBreaks = true,
enableTaskList = true,
hasWritePermission = true,
completionContext = "issues",
style = "",
elastic = true,
tabIndex = 1
)
<div class="pull-right">
<input type="button" id="cancel-comment-@commentId" class="btn btn-default" value="Cancel"/>
<input type="button" id="update-comment-@commentId" class="btn btn-success" value="Update comment"/>
@gitbucket.core.helper.html.attached(repository, "issues"){
<textarea id="edit-content-@commentId" class="form-control">@content</textarea>
}
<div>
<input type="button" id="cancel-comment-@commentId" class="btn btn-danger" value="Cancel"/>
<input type="button" id="update-comment-@commentId" class="btn btn-default pull-right" value="Update comment"/>
</div>
<script>
$(function(){
@@ -27,14 +17,13 @@ $(function(){
};
$('#update-comment-@commentId').click(function(){
var content = $(this).parent().parent().find('textarea[name=content]').val();
$('#update-comment-@commentId, #cancel-comment-@commentId').attr('disabled', 'disabled');
$.ajax({
url: '@context.path/@repository.owner/@repository.name/issue_comments/edit/@commentId',
type: 'POST',
data: {
issueId : 0, // TODO
content : content
content : $('#edit-content-@commentId').val()
}
}).done(
callback

View File

@@ -1,37 +1,27 @@
@(content: Option[String], issueId: Int,
repository: gitbucket.core.service.RepositoryService.RepositoryInfo)(implicit context: gitbucket.core.controller.Context)
@gitbucket.core.helper.html.preview(
repository = repository,
content = content.getOrElse(""),
enableWikiLink = false,
enableRefsLink = true,
enableLineBreaks = true,
enableTaskList = true,
hasWritePermission = true,
completionContext = "issues",
style = "",
elastic = true,
tabIndex = 1
)
<div class="pull-right">
<input type="button" id="cancel-issue" class="btn btn-default" value="Cancel"/>
<input type="button" id="update-issue" class="btn btn-success" value="Update comment"/>
@gitbucket.core.helper.html.attached(repository, "issues"){
<textarea id="edit-content" class="form-control">@content.getOrElse("")</textarea>
}
<div>
<input type="button" id="cancel-issue" class="btn btn-danger" value="Cancel"/>
<input type="button" id="update-issue" class="btn btn-default pull-right" value="Update comment"/>
</div>
<script>
$(function(){
var callback = function(data){
$('#update, #cancel').removeAttr('disabled');
$('#issueContent').empty().html(data.content);
prettyPrint();
};
$('#update-issue').click(function(){
var content = $(this).parent().parent().find('textarea[name=content]').val();
$('#update, #cancel').attr('disabled', 'disabled');
$.ajax({
url: '@context.path/@repository.owner/@repository.name/issues/edit/@issueId',
type: 'POST',
data: { content : content }
data: {
content : $('#edit-content').val()
}
}).done(
callback
).fail(function(req) {

View File

@@ -148,8 +148,8 @@
<input type="hidden" name="assignedUserName" value=""/>
}
@issue.map { issue =>
@gitbucket.core.plugin.PluginRegistry().getIssueSidebars.map { sidebarComponent =>
@sidebarComponent(issue, repository, context)
@gitbucket.core.plugin.PluginRegistry().getIssueSidebars.map { sidebar =>
@sidebar(issue, repository, context)
}
<hr/>
<div style="margin-bottom: 14px;">

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/facebox/facebox.css")" rel="stylesheet"/>
<link href="@helpers.assets("/vendors/AdminLTE-2.3.11/css/AdminLTE.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/AdminLTE-2.3.11/css/skins/skin-blue.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.structure.min.css")" rel="stylesheet">
@@ -42,11 +42,11 @@
}
<script src="@helpers.assets("/vendors/AdminLTE-2.3.11/js/app.js")" type="text/javascript"></script>
</head>
<body class="@context.settings.skinName page-load @if(body.toString.contains("menu-item-hover")){sidebar-mini} @if(context.sidebarCollapse){sidebar-collapse}">
<body class="skin-blue page-load @if(body.toString.contains("menu-item-hover")){sidebar-mini} @if(context.sidebarCollapse){sidebar-collapse}">
<div class="wrapper">
<header class="main-header">
<a href="@context.path/" class="logo">
<img src="@helpers.assets("/common/images/gitbucket.svg")" style="width: 24px; height: 24px; display: inline;"/>
<img src="@helpers.assets("/common/images/gitbucket.png")" style="width: 24px; height: 24px; display: inline;"/>
GitBucket
<span class="header-version">@gitbucket.core.GitBucketCoreModule.getVersions.last.getVersion</span>
</a>

View File

@@ -74,11 +74,6 @@
@gitbucket.core.helper.html.information(info)
@gitbucket.core.helper.html.error(error)
<div class="head">
<div class="pull-right">
@gitbucket.core.plugin.PluginRegistry().getRepositoryHeaders.map { repositoryHeaderComponent =>
@repositoryHeaderComponent(repository, context)
}
</div>
@gitbucket.core.helper.html.repositoryicon(repository, true)
<a href="@helpers.url(repository.owner)">@repository.owner</a> / <a href="@helpers.url(repository)" class="strong">@repository.name</a>

View File

@@ -20,7 +20,7 @@
@status.statusesAndRequired.map { case (status, required) =>
<div class="build-status-item">
<div class="pull-right">
@if(required){ <span class="label label-success">Required</span> }
@if(required){ <span class="label">Required</span> }
@status.targetUrl.map { url => <a href="@url">Details</a> }
</div>
<span class="build-status-icon text-@{status.state.name}">@helpers.commitStateIcon(status.state)</span>

View File

@@ -71,7 +71,7 @@
</div>
</div>
@if(content.viewType == "text"){
@defining(helpers.isRenderable(pathList.last)){ isRenderable =>
@defining(helpers.isRenderable(pathList.reverse.head)){ isRenderable =>
@if(!isBlame && isRenderable) {
<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)

View File

@@ -4,9 +4,7 @@
commits: Seq[Seq[gitbucket.core.util.JGitUtil.CommitInfo]],
page: Int,
hasNext: Boolean,
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)
hasWritePermission: Boolean)(implicit context: gitbucket.core.controller.Context)
@import gitbucket.core.view.helpers
@gitbucket.core.html.main(s"${repository.owner}/${repository.name}", Some(repository)) {
@gitbucket.core.html.menu("files", repository){
@@ -65,30 +63,6 @@
}
@helpers.user(commit.committerName, commit.committerEmailAddress, "username")
<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>
@@ -112,19 +86,5 @@
}
</ul>
</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

@@ -1,22 +1,12 @@
@(content: String, commentId: Int,
repository: gitbucket.core.service.RepositoryService.RepositoryInfo)(implicit context: gitbucket.core.controller.Context)
<span class="error-edit-content-@commentId error"></span>
@gitbucket.core.helper.html.preview(
repository = repository,
content = content,
enableWikiLink = false,
enableRefsLink = true,
enableLineBreaks = true,
enableTaskList = true,
hasWritePermission = true,
completionContext = "issues",
style = "",
elastic = true,
tabIndex = 1
)
<div class="pull-right">
<input type="button" class="cancel-comment-@commentId btn btn-small btn-default" value="Cancel"/>
<input type="button" class="update-comment-@commentId btn btn-small btn-success" value="Update comment"/>
@gitbucket.core.helper.html.attached(repository, "issues"){
<textarea style="height: 100px;" id="edit-content-@commentId" class="form-control">@content</textarea>
}
<div>
<input type="button" class="cancel-comment-@commentId btn btn-small btn-danger" value="Cancel"/>
<input type="button" class="update-comment-@commentId btn btn-small btn-default pull-right" value="Update comment"/>
</div>
<script>
$(function(){
@@ -29,14 +19,13 @@ $(function(){
}
$(document).on('click', '.update-comment-@commentId', function(){
var content = $(this).parent().parent().find('textarea[name=content]').val();
$box = $(this).closest('.commit-comment-box');
$('.update-comment-@commentId, .cancel-comment-@commentId', $box).attr('disabled', 'disabled');
$.ajax({
url: '@context.path/@repository.owner/@repository.name/commit_comments/edit/@commentId',
type: 'POST',
data: {
content : content
content : $('#edit-content-@commentId', $box).val()
}
}).done(
curriedCallback($box)

View File

@@ -175,7 +175,7 @@
</table>
@readme.map { case(filePath, content) =>
<div id="readme" class="panel panel-default">
<div class="panel-heading strong">@filePath.last</div>
<div class="panel-heading strong">@filePath.reverse.head</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>
}

View File

@@ -12,7 +12,7 @@
</tr>
</thead>
<tbody>
@repository.tags.reverseMap { tag =>
@repository.tags.reverse.map { tag =>
<tr>
<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>

View File

@@ -1,8 +1,6 @@
/****************************************************************************/
/* Common */
/****************************************************************************/
.wrapper { position: static; }
body {
color: #333;
}
@@ -564,6 +562,17 @@ pre.commit-description {
font-family: monospace;
}
#repository-url-copy {
/*height: 18px;*/
padding-top: 2px;
}
#repository-url-copy > i {
margin-left: -4px;
margin-right: -4px;
}
ul#commit-file-list {
list-style-type: none;
padding-left: 0px;

View File

@@ -1,39 +0,0 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
width="316.000000pt" height="329.000000pt" viewBox="0 0 316.000000 329.000000"
preserveAspectRatio="xMidYMid meet">
<metadata>
Created by potrace 1.14, written by Peter Selinger 2001-2017
</metadata>
<g transform="translate(0.000000,329.000000) scale(0.100000,-0.100000)"
fill="#303030" stroke="none">
<path d="M1230 3079 c-448 -28 -840 -128 -971 -246 -50 -45 -50 -71 0 -116
158 -142 707 -256 1241 -257 l106 0 54 56 c118 121 213 124 326 12 l52 -50
133 17 c338 42 615 127 718 220 53 48 53 72 0 120 -101 91 -391 181 -704 219
-283 34 -656 44 -955 25z"/>
<path d="M232 2484 c4 -16 70 -429 148 -919 79 -490 147 -895 152 -902 17 -21
258 -114 416 -159 130 -37 351 -84 400 -84 6 0 12 6 12 13 0 6 -174 185 -386
397 -395 394 -424 428 -424 500 0 72 30 107 383 459 301 300 348 343 366 335
11 -5 87 -74 170 -152 l151 -144 0 -59 c0 -73 24 -124 77 -167 l38 -30 0 -309
0 -309 -38 -34 c-102 -89 -102 -229 -1 -307 31 -23 49 -28 108 -31 82 -4 115
11 162 73 22 30 29 51 32 100 4 65 -2 83 -53 154 l-25 34 0 266 c0 267 3 311
23 311 5 0 54 -44 109 -97 96 -95 99 -100 108 -157 17 -103 89 -166 190 -166
76 0 135 39 174 117 32 64 16 143 -43 206 -41 44 -73 57 -149 57 l-68 0 -127
121 c-107 101 -127 126 -132 157 -12 72 -16 82 -47 117 -39 45 -77 65 -122 65
-19 0 -45 4 -58 9 -13 5 -99 82 -193 170 l-170 160 -71 1 c-106 0 -360 27
-524 55 -228 40 -385 86 -579 172 -11 5 -13 0 -9 -23z"/>
<path d="M2785 2454 c-86 -36 -280 -88 -425 -114 -69 -12 -129 -26 -135 -31
-6 -6 93 -112 275 -294 156 -156 285 -283 287 -281 2 2 19 158 38 347 19 189
37 356 40 372 6 34 -2 34 -80 1z"/>
<path d="M2552 697 c-78 -78 -142 -146 -142 -150 0 -4 7 -7 16 -7 22 0 230 89
242 103 9 11 35 187 29 193 -2 2 -67 -61 -145 -139z"/>
<path d="M560 455 c0 -34 40 -95 84 -129 61 -46 197 -104 317 -135 169 -43
321 -62 534 -68 l190 -5 -70 71 c-68 69 -71 71 -120 71 -189 0 -551 79 -806
176 -64 24 -119 44 -123 44 -3 0 -6 -11 -6 -25z"/>
<path d="M2620 456 c-53 -28 -250 -97 -360 -126 l-114 -29 -78 -78 c-42 -42
-76 -79 -74 -81 9 -8 244 41 334 69 164 53 267 114 308 185 24 40 31 74 17 74
-5 -1 -19 -7 -33 -14z"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.2 KiB

View File

@@ -443,7 +443,7 @@ function string_score(string, word) {
* @param word {String} search word
* @param strings {Array[String]} search targets
* @param limit {Integer} result limit
* @return {Array[{score:"float matching score", string:"string target string", matchingPositions:"Array[Integer] matching positions"}]}
* @return {Array[{score:"float matching score", string:"string target string", matchingPositions:"Array[Interger] matchng positions"}]}
*/
function string_score_sort(word, strings, limit){
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.
* @param score {string:"string target string", matchingPositions:"Array[Integer] matching positions"}
* @param score {string:"string target string", matchingPositions:"Array[Interger] matchng positions"}
* @param highlight tag ex: '<b>'
* @return array of highlighted html elements.
*/

View File

@@ -39,7 +39,7 @@ class CommitStatusServiceSpec extends FunSuite with ServiceSpecBase with CommitS
assert(getCommitStatus(fixture1.userName, fixture1.repositoryName, id) == Some(fixture1.copy(commitStatusId=id)))
// other one can update
val tester2 = generateNewAccount("tester2")
val time2 = new java.util.Date()
val time2 = new java.util.Date();
val id2 = createCommitStatus(
userName = fixture1.userName,
repositoryName = fixture1.repositoryName,
@@ -72,4 +72,4 @@ class CommitStatusServiceSpec extends FunSuite with ServiceSpecBase with CommitS
val id = generateFixture1(tester:Account)
assert(getCommitStatus(fixture1.userName, fixture1.repositoryName, id) == Some(fixture1.copy(commitStatusId=id)))
}}
}
}

View File

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

View File

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

View File

@@ -84,7 +84,7 @@ object GitSpecUtil {
throw new RuntimeException("conflict!")
}
val mergeTipCommit = using(new RevWalk( repository ))(_.parseCommit( mergeTip ))
val committer = mergeTipCommit.getCommitterIdent
val committer = mergeTipCommit.getCommitterIdent;
// creates merge commit
val mergeCommit = new CommitBuilder()
mergeCommit.setTreeId(merger.getResultTreeId)

View File

@@ -118,10 +118,7 @@ class AvatarImageProviderSpec extends FunSpec with MockitoSugar {
useSMTP = false,
smtp = None,
ldapAuthentication = false,
ldap = None,
skinName = "skin-blue",
debug = false
)
ldap = None)
/**
* 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>""")
}
it("should convert a multiple links within text") {
it("should convert a mulitple links within text") {
val before = "Example Project. http://example.com. (See also https://github.com/)"
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>)""")