mirror of
https://github.com/gitbucket/gitbucket.git
synced 2026-05-08 12:27:33 +02:00
Compare commits
61 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3134bb0428 | ||
|
|
134b5df6a2 | ||
|
|
aab84d9275 | ||
|
|
5209c29d0f | ||
|
|
cc975f8ffa | ||
|
|
125040f90b | ||
|
|
7009aaeb24 | ||
|
|
a135c6977f | ||
|
|
55991a6f17 | ||
|
|
17e1de6174 | ||
|
|
12a58f393e | ||
|
|
ebb57a80e3 | ||
|
|
7a53bd8766 | ||
|
|
d8b46b194d | ||
|
|
5db7d863ff | ||
|
|
7d7c11aa1a | ||
|
|
95748a2f2f | ||
|
|
e77045abe3 | ||
|
|
d73ddbdbcb | ||
|
|
8893a4a456 | ||
|
|
608112ce22 | ||
|
|
2e9155fbcc | ||
|
|
f33a30c697 | ||
|
|
e208dd5966 | ||
|
|
a6cb71f9c3 | ||
|
|
91deeb969b | ||
|
|
040d812f2a | ||
|
|
772ac80764 | ||
|
|
ef67c94272 | ||
|
|
fdb4a6bdc6 | ||
|
|
57902af87c | ||
|
|
92fea3ff01 | ||
|
|
cbd16169e4 | ||
|
|
299df34bf4 | ||
|
|
48a92df719 | ||
|
|
b806439e2f | ||
|
|
1db586c0bd | ||
|
|
26e2bfbf43 | ||
|
|
28c4ac6a19 | ||
|
|
c0d9d68fca | ||
|
|
122ed1dd0f | ||
|
|
f18b83999a | ||
|
|
fc28aacb52 | ||
|
|
2872da4f94 | ||
|
|
5d3c5e7f3c | ||
|
|
a86fb480b2 | ||
|
|
7daf33c149 | ||
|
|
0d9afdc939 | ||
|
|
a9a26193cd | ||
|
|
684973ea85 | ||
|
|
0149593272 | ||
|
|
1ae3df76bd | ||
|
|
d8e0a06d93 | ||
|
|
b2d842ddd0 | ||
|
|
580374208f | ||
|
|
8ab96f09cb | ||
|
|
f5b6728358 | ||
|
|
c7d46ee18f | ||
|
|
f073112814 | ||
|
|
9ee71e9f25 | ||
|
|
68d090f81a |
11
.github/CONTRIBUTING.md
vendored
11
.github/CONTRIBUTING.md
vendored
@@ -1,7 +1,8 @@
|
||||
# Guideline for Issues
|
||||
# The guidelines for contributing
|
||||
|
||||
- At first, see [Wiki](https://github.com/gitbucket/gitbucket/wiki) and check issues whether there is a same question or request in the past.
|
||||
- If you can't find same question and report, send it to [gitter room](https://gitter.im/gitbucket/gitbucket) before raising an issue.
|
||||
- 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.
|
||||
- We can also support in Japanese other than English at [gitter room for Japanese](https://gitter.im/gitbucket/gitbucket_ja).
|
||||
- Write an issue in English. At least, write subject in English.
|
||||
- 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.
|
||||
- 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.
|
||||
|
||||
4
.github/ISSUE_TEMPLATE.md
vendored
4
.github/ISSUE_TEMPLATE.md
vendored
@@ -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*
|
||||
|
||||
2
.github/PULL_REQUEST_TEMPLATE.md
vendored
2
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -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
Normal file
6
.github/SUPPORT.md
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
# The support guidelines
|
||||
|
||||
- At first, see [Wiki](https://github.com/gitbucket/gitbucket/wiki) and check issues whether there is a same question or request in the past.
|
||||
- If you can't find same question and report, send it to [gitter room](https://gitter.im/gitbucket/gitbucket) before raising an issue.
|
||||
- We can also support in Japanese other than English at [gitter room for Japanese](https://gitter.im/gitbucket/gitbucket_ja).
|
||||
- Write an issue in English. Since we can't support issues written in other languages, we close them forcibly.
|
||||
39
.travis.yml
39
.travis.yml
@@ -1,5 +1,7 @@
|
||||
language: scala
|
||||
sudo: true
|
||||
jdk:
|
||||
- oraclejdk8
|
||||
script:
|
||||
- sbt test
|
||||
before_script:
|
||||
@@ -14,40 +16,3 @@ 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
|
||||
before_install:
|
||||
- cd ~
|
||||
- JDK9_URL=`curl http://jdk.java.net/9/ | grep "lin64JDK" | grep "tar.gz\"" | sed -e "s/\"/ /g" | awk '{print $5}'`
|
||||
- wget -O jdk-9_linux-x64_bin.tar.gz $JDK9_URL
|
||||
- tar -xzf jdk-9_linux-x64_bin.tar.gz
|
||||
- cd -
|
||||
script:
|
||||
# https://github.com/sbt/sbt/pull/2951
|
||||
- git clone https://github.com/retronym/java9-rt-export
|
||||
- cd java9-rt-export/
|
||||
- git checkout 1019a2873d057dd7214f4135e84283695728395d
|
||||
- jdk_switcher use oraclejdk8
|
||||
- sbt package
|
||||
# - jdk_switcher use oraclejdk9
|
||||
- export JAVA_HOME=~/jdk-9
|
||||
- PATH=$JAVA_HOME/bin:$PATH
|
||||
- java -version
|
||||
- mkdir -p $HOME/.sbt/0.13/java9-rt-ext; java -jar target/java9-rt-export-*.jar $HOME/.sbt/0.13/java9-rt-ext/rt.jar
|
||||
- jar tf $HOME/.sbt/0.13/java9-rt-ext/rt.jar | grep java/lang/Object
|
||||
- cd ..
|
||||
- wget https://raw.githubusercontent.com/paulp/sbt-extras/9ade5fa54914ca8aded44105bf4b9a60966f3ccd/sbt && chmod +x ./sbt
|
||||
- ./sbt -Dscala.ext.dirs=$HOME/.sbt/0.13/java9-rt-ext test
|
||||
|
||||
@@ -59,7 +59,7 @@ GitBucket has a plug-in system that allows extra functionality. Officially the f
|
||||
- [gitbucket-pages-plugin](https://github.com/gitbucket/gitbucket-pages-plugin)
|
||||
- [gitbucket-notifications-plugin](https://github.com/gitbucket/gitbucket-notifications-plugin)
|
||||
|
||||
You can find more plugins made by the community at [GitBucket community plugins](http://gitbucket-plugins.github.io/).
|
||||
You can find more plugins made by the community at [GitBucket community plugins](https://gitbucket-plugins.github.io/).
|
||||
|
||||
Support
|
||||
--------
|
||||
@@ -72,6 +72,11 @@ 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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
val Organization = "io.github.gitbucket"
|
||||
val Name = "gitbucket"
|
||||
val GitBucketVersion = "4.15.0"
|
||||
val GitBucketVersion = "4.16.0"
|
||||
val ScalatraVersion = "2.5.0"
|
||||
val JettyVersion = "9.3.19.v20170502"
|
||||
|
||||
@@ -29,13 +29,13 @@ libraryDependencies ++= Seq(
|
||||
"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.13",
|
||||
"io.github.gitbucket" % "markedj" % "1.0.14",
|
||||
"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.9",
|
||||
"com.github.takezoe" %% "blocking-slick-32" % "0.0.10",
|
||||
"joda-time" % "joda-time" % "2.9.9",
|
||||
"com.novell.ldap" % "jldap" % "2009-10-07",
|
||||
"com.h2database" % "h2" % "1.4.195",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
JRebel integration (optional)
|
||||
=============================
|
||||
|
||||
[JRebel](http://zeroturnaround.com/software/jrebel/) is a JVM plugin that makes developing web apps much faster.
|
||||
[JRebel](https://zeroturnaround.com/software/jrebel/) is a JVM plugin that makes developing web apps much faster.
|
||||
JRebel is generally able to eliminate the need for the following slow "app restart" in sbt following a code change:
|
||||
|
||||
```
|
||||
@@ -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](http://zeroturnaround.com/software/jrebel/download/prev-releases/).
|
||||
Download the most recent ["nosetup" JRebel zip](https://zeroturnaround.com/software/jrebel/download/prev-releases/).
|
||||
Next, unzip the downloaded file.
|
||||
|
||||
## 3. Activate
|
||||
|
||||
Follow the [instructions on the JRebel website](http://zeroturnaround.com/software/jrebel/download/prev-releases/) to activate your downloaded JRebel.
|
||||
Follow the [instructions on the JRebel website](https://zeroturnaround.com/software/jrebel/download/prev-releases/) to activate your downloaded JRebel.
|
||||
|
||||
You can use the default settings for all the configurations.
|
||||
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
"description": "Provides notifications feature on GitBucket.",
|
||||
"versions": [
|
||||
{
|
||||
"version": "1.0.0",
|
||||
"range": ">=4.15.0",
|
||||
"file": "gitbucket-notifications-plugin_2.12-1.0.0.jar"
|
||||
"version": "1.1.0",
|
||||
"range": ">=4.16.0",
|
||||
"file": "gitbucket-notifications-plugin_2.12-1.1.0.jar"
|
||||
}
|
||||
],
|
||||
"default": true
|
||||
|
||||
@@ -51,6 +51,9 @@ public class JettyLauncher {
|
||||
case "--plugin_dir":
|
||||
System.setProperty("gitbucket.pluginDir", dim[1]);
|
||||
break;
|
||||
case "--validate_password":
|
||||
System.setProperty("gitbucket.validate.password", dim[1]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,11 +25,9 @@ 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 AnonymousAccessController, "/*")
|
||||
context.mount(new PreProcessController, "/*")
|
||||
|
||||
context.addFilter("pluginControllerFilter", new PluginControllerFilter)
|
||||
context.getFilterRegistration("pluginControllerFilter").addMappingForUrlPatterns(EnumSet.allOf(classOf[DispatcherType]), true, "/*")
|
||||
|
||||
@@ -40,5 +40,6 @@ object GitBucketCoreModule extends Module("gitbucket-core",
|
||||
new SqlMigration("update/gitbucket-core_4.14.sql")
|
||||
),
|
||||
new Version("4.14.1"),
|
||||
new Version("4.15.0")
|
||||
new Version("4.15.0"),
|
||||
new Version("4.16.0")
|
||||
)
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
package gitbucket.core.controller
|
||||
|
||||
class AnonymousAccessController extends AnonymousAccessControllerBase
|
||||
|
||||
trait AnonymousAccessControllerBase extends ControllerBase {
|
||||
get(!context.settings.allowAnonymousAccess, context.loginAccount.isEmpty) {
|
||||
if(!context.currentPath.startsWith("/assets") && !context.currentPath.startsWith("/signin") &&
|
||||
!context.currentPath.startsWith("/register")) {
|
||||
Unauthorized()
|
||||
} else {
|
||||
pass()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,14 +5,15 @@ import gitbucket.core.model._
|
||||
import gitbucket.core.service.IssuesService.IssueSearchCondition
|
||||
import gitbucket.core.service.PullRequestService._
|
||||
import gitbucket.core.service._
|
||||
import gitbucket.core.util.SyntaxSugars._
|
||||
import gitbucket.core.util.Directory._
|
||||
import gitbucket.core.util.JGitUtil._
|
||||
import gitbucket.core.util._
|
||||
import gitbucket.core.util.Implicits._
|
||||
import gitbucket.core.view.helpers.{renderMarkup, isRenderable}
|
||||
import gitbucket.core.util.JGitUtil._
|
||||
import gitbucket.core.util.SyntaxSugars._
|
||||
import gitbucket.core.util._
|
||||
import gitbucket.core.view.helpers.{isRenderable, renderMarkup}
|
||||
import org.eclipse.jgit.api.Git
|
||||
import org.scalatra.{NoContent, UnprocessableEntity, Created}
|
||||
import org.scalatra.{Created, NoContent, UnprocessableEntity}
|
||||
|
||||
import scala.collection.JavaConverters._
|
||||
|
||||
class ApiController extends ApiControllerBase
|
||||
@@ -124,10 +125,10 @@ trait ApiControllerBase extends ControllerBase {
|
||||
/**
|
||||
* https://developer.github.com/v3/repos/branches/#get-branch
|
||||
*/
|
||||
get ("/api/v3/repos/:owner/:repo/branches/:branch")(referrersOnly { repository =>
|
||||
get ("/api/v3/repos/:owner/:repo/branches/*")(referrersOnly { repository =>
|
||||
//import gitbucket.core.api._
|
||||
(for{
|
||||
branch <- params.get("branch") if repository.branchList.contains(branch)
|
||||
branch <- params.get("splat") if repository.branchList.contains(branch)
|
||||
br <- getBranches(repository.owner, repository.name, repository.repository.defaultBranch, repository.repository.originUserName.isEmpty).find(_.name == branch)
|
||||
} yield {
|
||||
val protection = getProtectedBranchInfo(repository.owner, repository.name, branch)
|
||||
@@ -286,10 +287,10 @@ trait ApiControllerBase extends ControllerBase {
|
||||
/**
|
||||
* https://developer.github.com/v3/repos/#enabling-and-disabling-branch-protection
|
||||
*/
|
||||
patch("/api/v3/repos/:owner/:repo/branches/:branch")(ownerOnly { repository =>
|
||||
patch("/api/v3/repos/:owner/:repo/branches/*")(ownerOnly { repository =>
|
||||
import gitbucket.core.api._
|
||||
(for{
|
||||
branch <- params.get("branch") if repository.branchList.contains(branch)
|
||||
branch <- params.get("splat") if repository.branchList.contains(branch)
|
||||
protection <- extractFromJsonBody[ApiBranchProtection.EnablingAndDisabling].map(_.protection)
|
||||
br <- getBranches(repository.owner, repository.name, repository.repository.defaultBranch, repository.repository.originUserName.isEmpty).find(_.name == branch)
|
||||
} yield {
|
||||
@@ -381,7 +382,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.toInt)
|
||||
comments = getCommentsForApi(repository.owner, repository.name, issueId)
|
||||
} yield {
|
||||
JsonFormat(comments.map{ case (issueComment, user, issue) => ApiComment(issueComment, RepositoryName(repository), issueId, ApiUser(user), issue.isPullRequest) })
|
||||
}) getOrElse NotFound()
|
||||
|
||||
@@ -26,6 +26,7 @@ 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.
|
||||
@@ -34,6 +35,8 @@ 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/*") {
|
||||
@@ -147,6 +150,20 @@ abstract class ControllerBase extends ScalatraFilter
|
||||
}
|
||||
}
|
||||
|
||||
error{
|
||||
case e => {
|
||||
logger.error(s"Catch unhandled error in request: ${request}", e)
|
||||
if(request.hasAttribute(Keys.Request.Ajax)){
|
||||
org.scalatra.InternalServerError()
|
||||
} else if(request.hasAttribute(Keys.Request.APIv3)){
|
||||
contentType = formats("json")
|
||||
org.scalatra.InternalServerError(ApiError("Internal Server Error"))
|
||||
} else {
|
||||
org.scalatra.InternalServerError(gitbucket.core.html.error("Internal Server Error", Some(e)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override def url(path: String, params: Iterable[(String, Any)] = Iterable.empty,
|
||||
includeContextPath: Boolean = true, includeServletPath: Boolean = true,
|
||||
absolutize: Boolean = true, withSessionId: Boolean = true)
|
||||
@@ -163,7 +180,7 @@ abstract class ControllerBase extends ScalatraFilter
|
||||
override def validate(name: String, value: String, params: Map[String, String], messages: Messages): Seq[(String, String)] =
|
||||
valueType.validate(name, trim(value), params, messages)
|
||||
|
||||
private def trim(value: String): String = if(value == null) null else value.replaceAll("\r\n", "").trim
|
||||
private def trim(value: String): String = if(value == null) null else value.replace("\r\n", "").trim
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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.commentId, form.content)
|
||||
updateComment(comment.issueId, 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.commentId))
|
||||
Ok(deleteComment(comment.issueId, comment.commentId))
|
||||
} else Unauthorized()
|
||||
} getOrElse NotFound()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
package gitbucket.core.controller
|
||||
|
||||
import org.scalatra.MovedPermanently
|
||||
|
||||
class PreProcessController extends PreProcessControllerBase
|
||||
|
||||
trait PreProcessControllerBase extends ControllerBase {
|
||||
|
||||
/**
|
||||
* Provides GitHub compatible URLs for Git client.
|
||||
*
|
||||
* <ul>
|
||||
* <li>git clone http://localhost:8080/owner/repo</li>
|
||||
* <li>git clone http://localhost:8080/owner/repo.git</li>
|
||||
* </ul>
|
||||
*
|
||||
* @see https://git-scm.com/book/en/v2/Git-Internals-Transfer-Protocols
|
||||
*/
|
||||
get("/*/*/info/refs") {
|
||||
val query = Option(request.getQueryString).map("?" + _).getOrElse("")
|
||||
halt(MovedPermanently(baseUrl + "/git" + request.getRequestURI + query))
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter requests from anonymous users.
|
||||
*
|
||||
* If anonymous access is allowed, pass all requests.
|
||||
* But if it's not allowed, demands authentication except some paths.
|
||||
*/
|
||||
get(!context.settings.allowAnonymousAccess, context.loginAccount.isEmpty) {
|
||||
if(!context.currentPath.startsWith("/assets") && !context.currentPath.startsWith("/signin") &&
|
||||
!context.currentPath.startsWith("/register")) {
|
||||
Unauthorized()
|
||||
} else {
|
||||
pass()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -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
|
||||
createComment(owner, name, loginAccount.userName, issueId, form.message, "merge")
|
||||
val commentId = createComment(owner, name, loginAccount.userName, issueId, form.message, "merge")
|
||||
createComment(owner, name, loginAccount.userName, issueId, "Close", "close")
|
||||
updateClosed(owner, name, issueId, true)
|
||||
|
||||
@@ -282,7 +282,10 @@ trait PullRequestsControllerBase extends ControllerBase {
|
||||
callPullRequestWebHook("closed", repository, issueId, context.baseUrl, context.loginAccount.get)
|
||||
|
||||
// call hooks
|
||||
PluginRegistry().getPullRequestHooks.foreach(_.merged(issue, repository))
|
||||
PluginRegistry().getPullRequestHooks.foreach{ h =>
|
||||
h.addedComment(commentId, form.message, issue, repository)
|
||||
h.merged(issue, repository)
|
||||
}
|
||||
|
||||
redirect(s"/${owner}/${name}/pull/${issueId}")
|
||||
}
|
||||
|
||||
@@ -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, WebHook}
|
||||
import gitbucket.core.model.{Account, CommitState, CommitStatus, WebHook}
|
||||
import gitbucket.core.service.WebHookService._
|
||||
import gitbucket.core.view
|
||||
import gitbucket.core.view.helpers
|
||||
@@ -174,13 +174,24 @@ 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))
|
||||
}, page, hasNext, hasDeveloperRole(repository.owner, repository.name, context.loginAccount), getStatuses, getSummary)
|
||||
case Left(_) => NotFound()
|
||||
}
|
||||
}
|
||||
@@ -213,7 +224,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)
|
||||
}
|
||||
|
||||
@@ -222,7 +233,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
branch = form.branch,
|
||||
path = form.path,
|
||||
files = files,
|
||||
message = form.message.getOrElse(s"Add files via upload")
|
||||
message = form.message.getOrElse("Add files via upload")
|
||||
)
|
||||
|
||||
if(form.path.length == 0){
|
||||
@@ -630,8 +641,8 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
}
|
||||
})
|
||||
|
||||
case class UploadFiles(branch: String, path: String, fileIds : Map[String,String], message: String) {
|
||||
lazy val isValid: Boolean = fileIds.size > 0
|
||||
case class UploadFiles(branch: String, path: String, fileIds: Map[String,String], message: String) {
|
||||
lazy val isValid: Boolean = fileIds.nonEmpty
|
||||
}
|
||||
|
||||
case class CommitFile(id: String, name: String)
|
||||
|
||||
@@ -63,7 +63,8 @@ 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))
|
||||
)(Ldap.apply)),
|
||||
"skinName" -> trim(label("AdminLTE skin name", text(required)))
|
||||
)(SystemSettings.apply).verifying { settings =>
|
||||
Vector(
|
||||
if(settings.ssh && settings.baseUrl.isEmpty){
|
||||
@@ -174,8 +175,9 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
|
||||
post("/admin/system/sendmail", sendMailForm)(adminOnly { form =>
|
||||
try {
|
||||
new Mailer(form.smtp).send(form.testAddress,
|
||||
"Test message from GitBucket", "This is a test message from GitBucket.",
|
||||
context.loginAccount.get)
|
||||
"Test message from GitBucket", context.loginAccount.get,
|
||||
"This is a test message from GitBucket.", None
|
||||
)
|
||||
|
||||
"Test mail has been sent to: " + form.testAddress
|
||||
|
||||
|
||||
@@ -4,6 +4,9 @@ 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}
|
||||
@@ -22,51 +25,49 @@ import org.apache.commons.io.FileUtils
|
||||
import org.slf4j.LoggerFactory
|
||||
import play.twirl.api.Html
|
||||
|
||||
import scala.collection.mutable
|
||||
import scala.collection.mutable.ListBuffer
|
||||
import scala.collection.JavaConverters._
|
||||
|
||||
class PluginRegistry {
|
||||
|
||||
private val plugins = new ListBuffer[PluginInfo]
|
||||
private val javaScripts = new ListBuffer[(String, String)]
|
||||
private val controllers = new ListBuffer[(ControllerBase, String)]
|
||||
private val images = mutable.Map[String, String]()
|
||||
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 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 repositoryHooks = new ListBuffer[RepositoryHook]
|
||||
private val issueHooks = new ListBuffer[IssueHook]
|
||||
private val repositoryHooks = new ConcurrentLinkedQueue[RepositoryHook]
|
||||
private val issueHooks = new ConcurrentLinkedQueue[IssueHook]
|
||||
|
||||
private val pullRequestHooks = new ListBuffer[PullRequestHook]
|
||||
private val pullRequestHooks = new ConcurrentLinkedQueue[PullRequestHook]
|
||||
|
||||
private val repositoryHeaders = new ListBuffer[(RepositoryInfo, Context) => Option[Html]]
|
||||
private val 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 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 suggestionProviders = new ListBuffer[SuggestionProvider]
|
||||
suggestionProviders += new UserNameSuggestionProvider()
|
||||
private val suggestionProviders = new ConcurrentLinkedQueue[SuggestionProvider]
|
||||
suggestionProviders.add(new UserNameSuggestionProvider())
|
||||
|
||||
def addPlugin(pluginInfo: PluginInfo): Unit = plugins += pluginInfo
|
||||
def addPlugin(pluginInfo: PluginInfo): Unit = plugins.add(pluginInfo)
|
||||
|
||||
def getPlugins(): List[PluginInfo] = plugins.toList
|
||||
def getPlugins(): List[PluginInfo] = plugins.asScala.toList
|
||||
|
||||
def addImage(id: String, bytes: Array[Byte]): Unit = {
|
||||
val encoded = Base64.getEncoder.encodeToString(bytes)
|
||||
images += ((id, encoded))
|
||||
images.put(id, encoded)
|
||||
}
|
||||
|
||||
@deprecated("Use addImage(id: String, bytes: Array[Byte]) instead", "3.4.0")
|
||||
@@ -79,28 +80,28 @@ class PluginRegistry {
|
||||
addImage(id, bytes)
|
||||
}
|
||||
|
||||
def getImage(id: String): String = images(id)
|
||||
def getImage(id: String): String = images.get(id)
|
||||
|
||||
def addController(path: String, controller: ControllerBase): Unit = controllers += ((controller, path))
|
||||
def addController(path: String, controller: ControllerBase): Unit = controllers.add((controller, path))
|
||||
|
||||
@deprecated("Use addController(path: String, controller: ControllerBase) instead", "3.4.0")
|
||||
def addController(controller: ControllerBase, path: String): Unit = addController(path, controller)
|
||||
|
||||
def getControllers(): Seq[(ControllerBase, String)] = controllers.toSeq
|
||||
def getControllers(): Seq[(ControllerBase, String)] = controllers.asScala.toSeq
|
||||
|
||||
def addJavaScript(path: String, script: String): Unit = javaScripts += ((path, script))
|
||||
def addJavaScript(path: String, script: String): Unit = javaScripts.add((path, script)) //javaScripts += ((path, script))
|
||||
|
||||
def getJavaScript(currentPath: String): List[String] = javaScripts.filter(x => currentPath.matches(x._1)).toList.map(_._2)
|
||||
def getJavaScript(currentPath: String): List[String] = javaScripts.asScala.filter(x => currentPath.matches(x._1)).toList.map(_._2)
|
||||
|
||||
def addRenderer(extension: String, renderer: Renderer): Unit = renderers += ((extension, renderer))
|
||||
def addRenderer(extension: String, renderer: Renderer): Unit = renderers.put(extension, renderer)
|
||||
|
||||
def getRenderer(extension: String): Renderer = renderers.getOrElse(extension, DefaultRenderer)
|
||||
def getRenderer(extension: String): Renderer = renderers.asScala.getOrElse(extension, DefaultRenderer)
|
||||
|
||||
def renderableExtensions: Seq[String] = renderers.keys.toSeq
|
||||
def renderableExtensions: Seq[String] = renderers.keys.asScala.toSeq
|
||||
|
||||
def addRepositoryRouting(routing: GitRepositoryRouting): Unit = repositoryRoutings += routing
|
||||
def addRepositoryRouting(routing: GitRepositoryRouting): Unit = repositoryRoutings.add(routing)
|
||||
|
||||
def getRepositoryRoutings(): Seq[GitRepositoryRouting] = repositoryRoutings.toSeq
|
||||
def getRepositoryRoutings(): Seq[GitRepositoryRouting] = repositoryRoutings.asScala.toSeq
|
||||
|
||||
def getRepositoryRouting(repositoryPath: String): Option[GitRepositoryRouting] = {
|
||||
PluginRegistry().getRepositoryRoutings().find {
|
||||
@@ -110,73 +111,73 @@ class PluginRegistry {
|
||||
}
|
||||
}
|
||||
|
||||
def addAccountHook(accountHook: AccountHook): Unit = accountHooks += accountHook
|
||||
def addAccountHook(accountHook: AccountHook): Unit = accountHooks.add(accountHook)
|
||||
|
||||
def getAccountHooks: Seq[AccountHook] = accountHooks.toSeq
|
||||
def getAccountHooks: Seq[AccountHook] = accountHooks.asScala.toSeq
|
||||
|
||||
def addReceiveHook(commitHook: ReceiveHook): Unit = receiveHooks += commitHook
|
||||
def addReceiveHook(commitHook: ReceiveHook): Unit = receiveHooks.add(commitHook)
|
||||
|
||||
def getReceiveHooks: Seq[ReceiveHook] = receiveHooks.toSeq
|
||||
def getReceiveHooks: Seq[ReceiveHook] = receiveHooks.asScala.toSeq
|
||||
|
||||
def addRepositoryHook(repositoryHook: RepositoryHook): Unit = repositoryHooks += repositoryHook
|
||||
def addRepositoryHook(repositoryHook: RepositoryHook): Unit = repositoryHooks.add(repositoryHook)
|
||||
|
||||
def getRepositoryHooks: Seq[RepositoryHook] = repositoryHooks.toSeq
|
||||
def getRepositoryHooks: Seq[RepositoryHook] = repositoryHooks.asScala.toSeq
|
||||
|
||||
def addIssueHook(issueHook: IssueHook): Unit = issueHooks += issueHook
|
||||
def addIssueHook(issueHook: IssueHook): Unit = issueHooks.add(issueHook)
|
||||
|
||||
def getIssueHooks: Seq[IssueHook] = issueHooks.toSeq
|
||||
def getIssueHooks: Seq[IssueHook] = issueHooks.asScala.toSeq
|
||||
|
||||
def addPullRequestHook(pullRequestHook: PullRequestHook): Unit = pullRequestHooks += pullRequestHook
|
||||
def addPullRequestHook(pullRequestHook: PullRequestHook): Unit = pullRequestHooks.add(pullRequestHook)
|
||||
|
||||
def getPullRequestHooks: Seq[PullRequestHook] = pullRequestHooks.toSeq
|
||||
def getPullRequestHooks: Seq[PullRequestHook] = pullRequestHooks.asScala.toSeq
|
||||
|
||||
def addRepositoryHeader(repositoryHeader: (RepositoryInfo, Context) => Option[Html]): Unit = repositoryHeaders += repositoryHeader
|
||||
def addRepositoryHeader(repositoryHeader: (RepositoryInfo, Context) => Option[Html]): Unit = repositoryHeaders.add(repositoryHeader)
|
||||
|
||||
def getRepositoryHeaders: Seq[(RepositoryInfo, Context) => Option[Html]] = repositoryHeaders.toSeq
|
||||
def getRepositoryHeaders: Seq[(RepositoryInfo, Context) => Option[Html]] = repositoryHeaders.asScala.toSeq
|
||||
|
||||
def addGlobalMenu(globalMenu: (Context) => Option[Link]): Unit = globalMenus += globalMenu
|
||||
def addGlobalMenu(globalMenu: (Context) => Option[Link]): Unit = globalMenus.add(globalMenu)
|
||||
|
||||
def getGlobalMenus: Seq[(Context) => Option[Link]] = globalMenus.toSeq
|
||||
def getGlobalMenus: Seq[(Context) => Option[Link]] = globalMenus.asScala.toSeq
|
||||
|
||||
def addRepositoryMenu(repositoryMenu: (RepositoryInfo, Context) => Option[Link]): Unit = repositoryMenus += repositoryMenu
|
||||
def addRepositoryMenu(repositoryMenu: (RepositoryInfo, Context) => Option[Link]): Unit = repositoryMenus.add(repositoryMenu)
|
||||
|
||||
def getRepositoryMenus: Seq[(RepositoryInfo, Context) => Option[Link]] = repositoryMenus.toSeq
|
||||
def getRepositoryMenus: Seq[(RepositoryInfo, Context) => Option[Link]] = repositoryMenus.asScala.toSeq
|
||||
|
||||
def addRepositorySettingTab(repositorySettingTab: (RepositoryInfo, Context) => Option[Link]): Unit = repositorySettingTabs += repositorySettingTab
|
||||
def addRepositorySettingTab(repositorySettingTab: (RepositoryInfo, Context) => Option[Link]): Unit = repositorySettingTabs.add(repositorySettingTab)
|
||||
|
||||
def getRepositorySettingTabs: Seq[(RepositoryInfo, Context) => Option[Link]] = repositorySettingTabs.toSeq
|
||||
def getRepositorySettingTabs: Seq[(RepositoryInfo, Context) => Option[Link]] = repositorySettingTabs.asScala.toSeq
|
||||
|
||||
def addProfileTab(profileTab: (Account, Context) => Option[Link]): Unit = profileTabs += profileTab
|
||||
def addProfileTab(profileTab: (Account, Context) => Option[Link]): Unit = profileTabs.add(profileTab)
|
||||
|
||||
def getProfileTabs: Seq[(Account, Context) => Option[Link]] = profileTabs.toSeq
|
||||
def getProfileTabs: Seq[(Account, Context) => Option[Link]] = profileTabs.asScala.toSeq
|
||||
|
||||
def addSystemSettingMenu(systemSettingMenu: (Context) => Option[Link]): Unit = systemSettingMenus += systemSettingMenu
|
||||
def addSystemSettingMenu(systemSettingMenu: (Context) => Option[Link]): Unit = systemSettingMenus.add(systemSettingMenu)
|
||||
|
||||
def getSystemSettingMenus: Seq[(Context) => Option[Link]] = systemSettingMenus.toSeq
|
||||
def getSystemSettingMenus: Seq[(Context) => Option[Link]] = systemSettingMenus.asScala.toSeq
|
||||
|
||||
def addAccountSettingMenu(accountSettingMenu: (Context) => Option[Link]): Unit = accountSettingMenus += accountSettingMenu
|
||||
def addAccountSettingMenu(accountSettingMenu: (Context) => Option[Link]): Unit = accountSettingMenus.add(accountSettingMenu)
|
||||
|
||||
def getAccountSettingMenus: Seq[(Context) => Option[Link]] = accountSettingMenus.toSeq
|
||||
def getAccountSettingMenus: Seq[(Context) => Option[Link]] = accountSettingMenus.asScala.toSeq
|
||||
|
||||
def addDashboardTab(dashboardTab: (Context) => Option[Link]): Unit = dashboardTabs += dashboardTab
|
||||
def addDashboardTab(dashboardTab: (Context) => Option[Link]): Unit = dashboardTabs.add(dashboardTab)
|
||||
|
||||
def getDashboardTabs: Seq[(Context) => Option[Link]] = dashboardTabs.toSeq
|
||||
def getDashboardTabs: Seq[(Context) => Option[Link]] = dashboardTabs.asScala.toSeq
|
||||
|
||||
def addIssueSidebar(issueSidebar: (Issue, RepositoryInfo, Context) => Option[Html]): Unit = issueSidebars += issueSidebar
|
||||
def addIssueSidebar(issueSidebar: (Issue, RepositoryInfo, Context) => Option[Html]): Unit = issueSidebars.add(issueSidebar)
|
||||
|
||||
def getIssueSidebars: Seq[(Issue, RepositoryInfo, Context) => Option[Html]] = issueSidebars.toSeq
|
||||
def getIssueSidebars: Seq[(Issue, RepositoryInfo, Context) => Option[Html]] = issueSidebars.asScala.toSeq
|
||||
|
||||
def addAssetsMapping(assetsMapping: (String, String, ClassLoader)): Unit = assetsMappings += assetsMapping
|
||||
def addAssetsMapping(assetsMapping: (String, String, ClassLoader)): Unit = assetsMappings.add(assetsMapping)
|
||||
|
||||
def getAssetsMappings: Seq[(String, String, ClassLoader)] = assetsMappings.toSeq
|
||||
def getAssetsMappings: Seq[(String, String, ClassLoader)] = assetsMappings.asScala.toSeq
|
||||
|
||||
def addTextDecorator(textDecorator: TextDecorator): Unit = textDecorators += textDecorator
|
||||
def addTextDecorator(textDecorator: TextDecorator): Unit = textDecorators.add(textDecorator)
|
||||
|
||||
def getTextDecorators: Seq[TextDecorator] = textDecorators.toSeq
|
||||
def getTextDecorators: Seq[TextDecorator] = textDecorators.asScala.toSeq
|
||||
|
||||
def addSuggestionProvider(suggestionProvider: SuggestionProvider): Unit = suggestionProviders += suggestionProvider
|
||||
def addSuggestionProvider(suggestionProvider: SuggestionProvider): Unit = suggestionProviders.add(suggestionProvider)
|
||||
|
||||
def getSuggestionProviders: Seq[SuggestionProvider] = suggestionProviders.toSeq
|
||||
def getSuggestionProviders: Seq[SuggestionProvider] = suggestionProviders.asScala.toSeq
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -190,6 +191,7 @@ object PluginRegistry {
|
||||
|
||||
private var watcher: PluginWatchThread = null
|
||||
private var extraWatcher: PluginWatchThread = null
|
||||
private val initializing = new AtomicBoolean(false)
|
||||
|
||||
/**
|
||||
* Returns the PluginRegistry singleton instance.
|
||||
@@ -229,9 +231,8 @@ object PluginRegistry {
|
||||
* Install a plugin from a specified jar file.
|
||||
*/
|
||||
def install(file: File, context: ServletContext, settings: SystemSettings, conn: java.sql.Connection): Unit = synchronized {
|
||||
FileUtils.copyFile(file, new File(PluginHome, file.getName))
|
||||
|
||||
shutdown(context, settings)
|
||||
FileUtils.copyFile(file, new File(PluginHome, file.getName))
|
||||
instance = new PluginRegistry()
|
||||
initialize(context, settings, conn)
|
||||
}
|
||||
@@ -323,6 +324,14 @@ object PluginRegistry {
|
||||
instance.getPlugins().foreach { plugin =>
|
||||
try {
|
||||
plugin.pluginClass.shutdown(instance, context, settings)
|
||||
if(watcher != null){
|
||||
watcher.interrupt()
|
||||
watcher = null
|
||||
}
|
||||
if(extraWatcher != null){
|
||||
extraWatcher.interrupt()
|
||||
extraWatcher = null
|
||||
}
|
||||
} catch {
|
||||
case e: Exception => {
|
||||
logger.error(s"Error during plugin shutdown: ${plugin.pluginJar.getName}", e)
|
||||
|
||||
@@ -50,7 +50,7 @@ trait HandleCommentService {
|
||||
id
|
||||
}
|
||||
|
||||
actionActivity foreach ( _ (owner, name, userName, issue.issueId, issue.title) )
|
||||
actionActivity.foreach { f => f(owner, name, userName, issue.issueId, issue.title) }
|
||||
|
||||
// call web hooks
|
||||
action match {
|
||||
|
||||
@@ -32,8 +32,11 @@ trait IssuesService {
|
||||
.list
|
||||
|
||||
def getMergedComment(owner: String, repository: String, issueId: Int)(implicit s: Session): Option[(IssueComment, Account)] = {
|
||||
getCommentsForApi(owner, repository, issueId)
|
||||
.collectFirst { case (comment, account, _) if comment.action == "merged" => (comment, 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
|
||||
}
|
||||
|
||||
def getComment(owner: String, repository: String, commentId: String)(implicit s: Session): Option[IssueComment] = {
|
||||
@@ -327,6 +330,7 @@ trait IssuesService {
|
||||
|
||||
def createComment(owner: String, repository: String, loginUser: String,
|
||||
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,
|
||||
@@ -342,31 +346,33 @@ 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(_.assignedUserName?).update (assignedUserName)
|
||||
Issues.filter(_.byPrimaryKey(owner, repository, issueId)).map(t => (t.assignedUserName?, t.updatedDate)).update(assignedUserName, currentDate)
|
||||
}
|
||||
|
||||
def updateMilestoneId(owner: String, repository: String, issueId: Int, milestoneId: Option[Int])(implicit s: Session): Int = {
|
||||
Issues.filter (_.byPrimaryKey(owner, repository, issueId)).map(_.milestoneId?).update (milestoneId)
|
||||
Issues.filter(_.byPrimaryKey(owner, repository, issueId)).map(t => (t.milestoneId?, t.updatedDate)).update(milestoneId, currentDate)
|
||||
}
|
||||
|
||||
def updatePriorityId(owner: String, repository: String, issueId: Int, priorityId: Option[Int])(implicit s: Session): Int = {
|
||||
Issues.filter (_.byPrimaryKey(owner, repository, issueId)).map(_.priorityId?).update (priorityId)
|
||||
Issues.filter(_.byPrimaryKey(owner, repository, issueId)).map(t => (t.priorityId?, t.updatedDate)).update(priorityId, currentDate)
|
||||
}
|
||||
|
||||
def updateComment(commentId: Int, content: String)(implicit s: Session): Int = {
|
||||
IssueComments.filter (_.byPrimaryKey(commentId)).map(t => (t.content, t.updatedDate)).update(content, currentDate)
|
||||
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 deleteComment(commentId: Int)(implicit s: Session): Int = {
|
||||
IssueComments filter (_.byPrimaryKey(commentId)) delete
|
||||
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 updateClosed(owner: String, repository: String, issueId: Int, closed: Boolean)(implicit s: Session): Int = {
|
||||
(Issues filter (_.byPrimaryKey(owner, repository, issueId)) map(t => (t.closed, t.updatedDate))).update((closed, currentDate))
|
||||
Issues.filter(_.byPrimaryKey(owner, repository, issueId)).map(t => (t.closed, t.updatedDate)).update(closed, currentDate)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -497,14 +503,14 @@ object IssuesService {
|
||||
).flatten ++
|
||||
labels.map(label => s"label:${label}") ++
|
||||
List(
|
||||
milestone.map { _ match {
|
||||
milestone.map {
|
||||
case Some(x) => s"milestone:${x}"
|
||||
case None => "no:milestone"
|
||||
}},
|
||||
priority.map { _ match {
|
||||
},
|
||||
priority.map {
|
||||
case Some(x) => s"priority:${x}"
|
||||
case None => "no:priority"
|
||||
}},
|
||||
},
|
||||
(sort, direction) match {
|
||||
case ("created" , "desc") => None
|
||||
case ("created" , "asc" ) => Some("sort:created-asc")
|
||||
|
||||
@@ -18,10 +18,11 @@ 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))
|
||||
|
||||
@@ -94,9 +94,9 @@ trait PullRequestService { self: IssuesService with CommitsService =>
|
||||
|
||||
/**
|
||||
* for repository viewer.
|
||||
* 1. find pull request from from `branch` to othre branch on same repository
|
||||
* 1. find pull request from `branch` to other branch on same repository
|
||||
* 1. return if exists pull request to `defaultBranch`
|
||||
* 2. return if exists pull request to othre branch
|
||||
* 2. return if exists pull request to other 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.isEmpty && CommitState.combine(statuses.map(_.state).toSet) != CommitState.SUCCESS)
|
||||
val hasProblem = hasRequiredStatusProblem || hasConflict || (statuses.nonEmpty && CommitState.combine(statuses.map(_.state).toSet) != CommitState.SUCCESS)
|
||||
val canUpdate = branchIsOutOfDate && !hasConflict
|
||||
val canMerge = hasMergePermission && !hasConflict && !hasRequiredStatusProblem
|
||||
lazy val commitStateSummary:(CommitState, String) = {
|
||||
|
||||
@@ -67,7 +67,7 @@ trait RepositorySearchService { self: IssuesService =>
|
||||
files.map { case (path, text) =>
|
||||
val (highlightText, lineNumber) = getHighlightText(text, query)
|
||||
FileSearchResult(
|
||||
path.replaceFirst("\\.md$", ""),
|
||||
path.stripSuffix(".md"),
|
||||
commits(path).getCommitterIdent.getWhen,
|
||||
highlightText,
|
||||
lineNumber)
|
||||
|
||||
@@ -135,7 +135,7 @@ trait RepositoryService { self: AccountService =>
|
||||
repositoryName = newRepositoryName
|
||||
)) :_*)
|
||||
|
||||
// TODO Drop transfered owner from collaborators?
|
||||
// TODO Drop transferred owner from collaborators?
|
||||
Collaborators.insertAll(collaborators.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)) :_*)
|
||||
|
||||
// Update activity messages
|
||||
|
||||
@@ -54,6 +54,7 @@ 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)
|
||||
}
|
||||
@@ -111,7 +112,8 @@ trait SystemSettingsService {
|
||||
getOptionValue(props, LdapKeystore, None)))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
},
|
||||
getValue(props, SkinName, "skin-blue")
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -136,7 +138,8 @@ object SystemSettingsService {
|
||||
useSMTP: Boolean,
|
||||
smtp: Option[Smtp],
|
||||
ldapAuthentication: Boolean,
|
||||
ldap: Option[Ldap]){
|
||||
ldap: Option[Ldap],
|
||||
skinName: String){
|
||||
def baseUrl(request: HttpServletRequest): String = baseUrl.fold(request.baseUrl)(_.stripSuffix("/"))
|
||||
|
||||
def sshAddress:Option[SshAddress] =
|
||||
@@ -219,6 +222,7 @@ 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 {
|
||||
|
||||
@@ -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-initalied repository
|
||||
case 0 => ApiPath(s"/${repository.full_name}") // maybe test hook on un-initialized repository
|
||||
case 1 => ApiPath(s"/${repository.full_name}/commit/${after}")
|
||||
case _ if before.filterNot(_=='0').isEmpty => ApiPath(s"/${repository.full_name}/compare/${commits.head.id}^...${after}")
|
||||
case _ if before.forall(_=='0') => ApiPath(s"/${repository.full_name}/compare/${commits.head.id}^...${after}")
|
||||
case _ => ApiPath(s"/${repository.full_name}/compare/${before}...${after}")
|
||||
}
|
||||
val head_commit = commits.lastOption
|
||||
|
||||
@@ -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.length == 0) {
|
||||
if(message.trim.isEmpty) {
|
||||
if(removed){
|
||||
s"Rename ${currentPageName} to ${newPageName}"
|
||||
} else if(created){
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
package gitbucket.core.servlet
|
||||
|
||||
import javax.servlet._
|
||||
import javax.servlet.http.{HttpServletRequest, HttpServletResponse}
|
||||
|
||||
import gitbucket.core.service.SystemSettingsService
|
||||
|
||||
/**
|
||||
* A controller to provide GitHub compatible URL for Git clients.
|
||||
*/
|
||||
class GHCompatRepositoryAccessFilter extends Filter with SystemSettingsService {
|
||||
|
||||
/**
|
||||
* Pattern of GitHub compatible repository URL.
|
||||
* <code>/:user/:repo.git/</code>
|
||||
*/
|
||||
private val githubRepositoryPattern = """^/[^/]+/[^/]+\.git/.*""".r
|
||||
|
||||
override def init(filterConfig: FilterConfig) = {}
|
||||
|
||||
override def doFilter(req: ServletRequest, res: ServletResponse, chain: FilterChain) = {
|
||||
implicit val request = req.asInstanceOf[HttpServletRequest]
|
||||
val agent = request.getHeader("USER-AGENT")
|
||||
val response = res.asInstanceOf[HttpServletResponse]
|
||||
val requestPath = request.getRequestURI.substring(request.getContextPath.length)
|
||||
|
||||
requestPath match {
|
||||
case githubRepositoryPattern() if agent != null && agent.toLowerCase.indexOf("git") >= 0 =>
|
||||
response.sendRedirect(baseUrl + "/git" + requestPath)
|
||||
case _ =>
|
||||
chain.doFilter(req, res)
|
||||
}
|
||||
}
|
||||
|
||||
override def destroy() = {}
|
||||
|
||||
}
|
||||
@@ -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$|\\.git$", "")) match {
|
||||
getRepository(repositoryOwner, repositoryName.replaceFirst("(\\.wiki)?\\.git$", "")) match {
|
||||
case Some(repository) => {
|
||||
val execute = if (!isUpdating && !repository.repository.isPrivate && settings.allowAnonymousAccess) {
|
||||
// Authentication is not required
|
||||
|
||||
@@ -166,7 +166,7 @@ class GitBucketReceivePackFactory extends ReceivePackFactory[HttpServletRequest]
|
||||
|
||||
if(repository.endsWith(".wiki")){
|
||||
defining(request) { implicit r =>
|
||||
receivePack.setPostReceiveHook(new WikiCommitHook(owner, repository.replaceFirst("\\.wiki$", ""), pusher, baseUrl))
|
||||
receivePack.setPostReceiveHook(new WikiCommitHook(owner, repository.stripSuffix(".wiki"), pusher, baseUrl))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.replaceFirst("^/", "") + resourceName))
|
||||
Option(classLoader.getResourceAsStream(resourcePath.stripPrefix("/") + resourceName))
|
||||
}
|
||||
.map { in =>
|
||||
try {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -75,7 +75,7 @@ object JDBCUtil {
|
||||
var stringLiteral = false
|
||||
|
||||
while({ length = in.read(bytes); length != -1 }){
|
||||
for(i <- 0 to length - 1){
|
||||
for(i <- 0 until length){
|
||||
val c = bytes(i)
|
||||
if(c == '\''){
|
||||
stringLiteral = !stringLiteral
|
||||
@@ -146,13 +146,11 @@ object JDBCUtil {
|
||||
}
|
||||
}
|
||||
|
||||
val columnValues = values.map { value =>
|
||||
value match {
|
||||
case x: String => "'" + x.replace("'", "''") + "'"
|
||||
case x: Timestamp => "'" + dateFormat.format(x) + "'"
|
||||
case null => "NULL"
|
||||
case x => x
|
||||
}
|
||||
val columnValues = values.map {
|
||||
case x: String => "'" + x.replace("'", "''") + "'"
|
||||
case x: Timestamp => "'" + dateFormat.format(x) + "'"
|
||||
case null => "NULL"
|
||||
case x => x
|
||||
}
|
||||
sb.append(columnValues.mkString(", "))
|
||||
sb.append(");\n")
|
||||
|
||||
@@ -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
|
||||
@@ -293,7 +293,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 +364,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 +374,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
|
||||
}
|
||||
|
||||
@@ -19,8 +19,12 @@ 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, msg: String)
|
||||
def toNotify(subject: String, textMsg: String, htmlMsg: Option[String])
|
||||
(recipients: Account => Session => Seq[String])(implicit context: Context): Unit
|
||||
|
||||
}
|
||||
@@ -35,7 +39,7 @@ object Notifier {
|
||||
class Mailer(private val smtp: Smtp) extends Notifier {
|
||||
private val logger = LoggerFactory.getLogger(classOf[Mailer])
|
||||
|
||||
def toNotify(subject: String, msg: String)
|
||||
def toNotify(subject: String, textMsg: String, htmlMsg: Option[String] = None)
|
||||
(recipients: Account => Session => Seq[String])(implicit context: Context): Unit = {
|
||||
context.loginAccount.foreach { loginAccount =>
|
||||
val database = Database()
|
||||
@@ -43,7 +47,7 @@ class Mailer(private val smtp: Smtp) extends Notifier {
|
||||
val f = Future {
|
||||
database withSession { session =>
|
||||
recipients(loginAccount)(session) foreach { to =>
|
||||
send(to, subject, msg, loginAccount)
|
||||
send(to, subject, loginAccount, textMsg, htmlMsg)
|
||||
}
|
||||
}
|
||||
"Notifications Successful."
|
||||
@@ -55,7 +59,7 @@ class Mailer(private val smtp: Smtp) extends Notifier {
|
||||
}
|
||||
}
|
||||
|
||||
def send(to: String, subject: String, msg: String, loginAccount: Account): Unit = {
|
||||
def send(to: String, subject: String, loginAccount: Account, textMsg: String, htmlMsg: Option[String] = None): Unit = {
|
||||
val email = new HtmlEmail
|
||||
email.setHostName(smtp.host)
|
||||
email.setSmtpPort(smtp.port.get)
|
||||
@@ -80,13 +84,16 @@ class Mailer(private val smtp: Smtp) extends Notifier {
|
||||
}
|
||||
email.setCharset("UTF-8")
|
||||
email.setSubject(subject)
|
||||
email.setHtmlMsg(msg)
|
||||
email.setTextMsg(textMsg)
|
||||
htmlMsg.foreach { msg =>
|
||||
email.setHtmlMsg(msg)
|
||||
}
|
||||
|
||||
email.addTo(to).send
|
||||
}
|
||||
|
||||
}
|
||||
class MockMailer extends Notifier {
|
||||
def toNotify(subject: String, msg: String)
|
||||
def toNotify(subject: String, textMsg: String, htmlMsg: Option[String] = None)
|
||||
(recipients: Account => Session => Seq[String])(implicit context: Context): Unit = ()
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ trait Validations {
|
||||
*/
|
||||
def password: Constraint = new Constraint(){
|
||||
override def validate(name: String, value: String, messages: Messages): Option[String] =
|
||||
if(!value.matches("[a-zA-Z0-9\\-_.]+")){
|
||||
if(System.getProperty("gitbucket.validate.password") != "false" && !value.matches("[a-zA-Z0-9\\-_.]+")){
|
||||
Some(s"${name} contains invalid character.")
|
||||
} else {
|
||||
None
|
||||
|
||||
@@ -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.reverse.head.toLowerCase
|
||||
val fileName = filePath.last.toLowerCase
|
||||
val extension = FileUtil.getExtension(fileName)
|
||||
val renderer = PluginRegistry().getRenderer(extension)
|
||||
renderer.render(RenderRequest(filePath, fileContent, branch, repository, enableWikiLink, enableRefsLink, enableAnchor, context))
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
@(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)
|
||||
@@ -344,6 +345,36 @@
|
||||
</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;">
|
||||
|
||||
@@ -1,8 +1,22 @@
|
||||
@(title: String)(implicit context: gitbucket.core.controller.Context)
|
||||
@(title: String, e: Option[Throwable]=None)(implicit context: gitbucket.core.controller.Context)
|
||||
@gitbucket.core.html.main("Error"){
|
||||
<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>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 displayd because tooptip width is narrow. *@
|
||||
@* if default container is used then 2 lines tooltip text is displayed because tooptip width is narrow. *@
|
||||
container: 'body'
|
||||
});
|
||||
$('#@copyButtonId').on('click', function() {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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("/vendors/AdminLTE-2.3.11/css/skins/skin-blue.min.css")" rel="stylesheet">
|
||||
<link href="@helpers.assets(s"/vendors/AdminLTE-2.3.11/css/skins/${context.settings.skinName}.min.css")" rel="stylesheet">
|
||||
<link href="@helpers.assets("/vendors/font-awesome-4.6.3/css/font-awesome.min.css")" rel="stylesheet">
|
||||
<link href="@helpers.assets("/vendors/jquery-ui/jquery-ui.min.css")" rel="stylesheet">
|
||||
<link href="@helpers.assets("/vendors/jquery-ui/jquery-ui.structure.min.css")" rel="stylesheet">
|
||||
@@ -42,7 +42,7 @@
|
||||
}
|
||||
<script src="@helpers.assets("/vendors/AdminLTE-2.3.11/js/app.js")" type="text/javascript"></script>
|
||||
</head>
|
||||
<body class="skin-blue page-load @if(body.toString.contains("menu-item-hover")){sidebar-mini} @if(context.sidebarCollapse){sidebar-collapse}">
|
||||
<body class="@context.settings.skinName page-load @if(body.toString.contains("menu-item-hover")){sidebar-mini} @if(context.sidebarCollapse){sidebar-collapse}">
|
||||
<div class="wrapper">
|
||||
<header class="main-header">
|
||||
<a href="@context.path/" class="logo">
|
||||
|
||||
@@ -71,7 +71,7 @@
|
||||
</div>
|
||||
</div>
|
||||
@if(content.viewType == "text"){
|
||||
@defining(helpers.isRenderable(pathList.reverse.head)){ isRenderable =>
|
||||
@defining(helpers.isRenderable(pathList.last)){ 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)
|
||||
|
||||
@@ -4,7 +4,9 @@
|
||||
commits: Seq[Seq[gitbucket.core.util.JGitUtil.CommitInfo]],
|
||||
page: Int,
|
||||
hasNext: Boolean,
|
||||
hasWritePermission: Boolean)(implicit context: gitbucket.core.controller.Context)
|
||||
hasWritePermission: Boolean,
|
||||
getStatuses: String => List[gitbucket.core.model.CommitStatus],
|
||||
getSummary: List[gitbucket.core.model.CommitStatus] => (gitbucket.core.model.CommitState, String))(implicit context: gitbucket.core.controller.Context)
|
||||
@import gitbucket.core.view.helpers
|
||||
@gitbucket.core.html.main(s"${repository.owner}/${repository.name}", Some(repository)) {
|
||||
@gitbucket.core.html.menu("files", repository){
|
||||
@@ -63,6 +65,30 @@
|
||||
}
|
||||
@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>
|
||||
@@ -86,5 +112,19 @@
|
||||
}
|
||||
</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>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -175,7 +175,7 @@
|
||||
</table>
|
||||
@readme.map { case(filePath, content) =>
|
||||
<div id="readme" class="panel panel-default">
|
||||
<div class="panel-heading strong">@filePath.reverse.head</div>
|
||||
<div class="panel-heading strong">@filePath.last</div>
|
||||
<div class="panel-body markdown-body" style="padding-left: 20px; padding-right: 20px;">@helpers.renderMarkup(filePath, content, branch, repository, false, false, true)</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@repository.tags.reverse.map { tag =>
|
||||
@repository.tags.reverseMap { 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>
|
||||
|
||||
@@ -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[Interger] matchng positions"}]}
|
||||
* @return {Array[{score:"float matching score", string:"string target string", matchingPositions:"Array[Integer] matching positions"}]}
|
||||
*/
|
||||
function string_score_sort(word, strings, limit){
|
||||
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[Interger] matchng positions"}
|
||||
* @param score {string:"string target string", matchingPositions:"Array[Integer] matching positions"}
|
||||
* @param highlight tag ex: '<b>'
|
||||
* @return array of highlighted html elements.
|
||||
*/
|
||||
|
||||
@@ -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, s"refs/heads/master", "test.txt", "hoge" )
|
||||
createFile(git, "refs/heads/master", "test.txt", "hoge" )
|
||||
git.branchCreate().setStartPoint(s"refs/heads/master").setName(s"refs/pull/${issueId}/head").call()
|
||||
}
|
||||
dir
|
||||
|
||||
@@ -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 othre branch and not closed.
|
||||
|return pull request if exists pull request from `branch` to other branch and not closed.
|
||||
|return None if all pull request is closed""".stripMargin.trim) { withTestDB { implicit se =>
|
||||
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") // othre user
|
||||
generateNewPullRequest("user1/repo1/master", "user1/repo2/head1") // othre repository
|
||||
generateNewPullRequest("user1/repo1/master", "user2/repo1/head1") // other user
|
||||
generateNewPullRequest("user1/repo1/master", "user1/repo2/head1") // other 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)
|
||||
} }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,7 +118,10 @@ class AvatarImageProviderSpec extends FunSpec with MockitoSugar {
|
||||
useSMTP = false,
|
||||
smtp = None,
|
||||
ldapAuthentication = false,
|
||||
ldap = None)
|
||||
ldap = None,
|
||||
skinName = "skin-blue",
|
||||
debug = false
|
||||
)
|
||||
|
||||
/**
|
||||
* Adapter to test AvatarImageProviderImpl.
|
||||
|
||||
@@ -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 mulitple links within text") {
|
||||
it("should convert a multiple links within text") {
|
||||
val before = "Example Project. http://example.com. (See also https://github.com/)"
|
||||
val 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>)""")
|
||||
|
||||
Reference in New Issue
Block a user