Compare commits

...

7 Commits

Author SHA1 Message Date
Naoki Takezoe
1a4961c3e1 Merge pull request #2489 from gitbucket/release-4.34.0
GitBucket 4.34.0 release
2020-07-26 02:38:51 +09:00
Naoki Takezoe
561220237f Update README and CHANGELOG 2020-07-26 02:28:47 +09:00
Naoki Takezoe
f45b85aa71 Bump gist-plugin (refs #2443) and notifications-plugin (refs #2433) 2020-07-25 21:04:45 +09:00
Naoki Takezoe
83b3a7983e Update version to 4.34.0 2020-07-25 17:52:33 +09:00
Naoki Takezoe
63d4c5054e Deny empty password (#2488) 2020-07-25 17:49:58 +09:00
Naoki Takezoe
c8f6017be9 Migrate activity log to JSON-based file from database (#2468)
This also includes:
- Record rename and transfer repository in activity log
- Check existence of repsoitory when rendering activity logs
- Drop Akka dependency
2020-07-18 23:28:40 +09:00
Naoki Takezoe
f9fcb54861 Fix coursier cache path (#2481) 2020-07-18 17:17:41 +09:00
35 changed files with 788 additions and 545 deletions

View File

@@ -22,7 +22,7 @@ jobs:
path: | path: |
~/.ivy2/cache ~/.ivy2/cache
~/.sbt ~/.sbt
~/.coursier ~/.cache/coursier/v1
key: build-${{ env.cache-name }}-${{ hashFiles('build.sbt') }} key: build-${{ env.cache-name }}-${{ hashFiles('build.sbt') }}
- name: Set up JDK - name: Set up JDK
uses: actions/setup-java@v1 uses: actions/setup-java@v1

View File

@@ -1,6 +1,22 @@
# Changelog # Changelog
All changes to the project will be documented in this file. All changes to the project will be documented in this file.
### 4.34.0 - 26 Jul 2020
- Enhancement admin settings UI
- File upload settings
- Restrict repository operations
- User-defined CSS
- Limit the repository list in the sidebar
- Improve MariaDB support
- Improve activity logging
- CLI option to persist session on disk in the standalone mode
- Web API updates
- Add [list commits API](https://developer.github.com/v3/repos/commits/#list-commits)
- Bundled plugins updates
- [gitbucket-gist-plugin](https://github.com/gitbucket/gitbucket-gist-plugin) 4.18.0 -> 4.19.0
- [gitbucket-notifications-plugin](https://github.com/gitbucket/gitbucket-notifications-plugin) 1.8.0 -> 1.9.0
### 4.33.0 - 31 Dec 2019 ### 4.33.0 - 31 Dec 2019
- All CLI options are configurable by environment variables - All CLI options are configurable by environment variables

View File

@@ -57,11 +57,20 @@ Support
What's New in 4.33.x What's New in 4.33.x
------------- -------------
### 4.33.0 - 31 Dec 2019 ### 4.34.0 - 26 Jul 2020
- All CLI options are configurable by environment variables - Enhancement admin settings UI
- Folding pull request files - File upload settings
- WebHook security options - Restrict repository operations
- Add assignee and assignees properties to some Web APIs' response - User-defined CSS
- Limit the repository list in the sidebar
- Improve MariaDB support
- Improve activity logging
- CLI option to persist session on disk in the standalone mode
- Web API updates
- Add [list commits API](https://developer.github.com/v3/repos/commits/#list-commits)
- Bundled plugins updates
- [gitbucket-gist-plugin](https://github.com/gitbucket/gitbucket-gist-plugin) 4.18.0 -> 4.19.0
- [gitbucket-notifications-plugin](https://github.com/gitbucket/gitbucket-notifications-plugin) 1.8.0 -> 1.9.0
See the [change log](CHANGELOG.md) for all of the updates. See the [change log](CHANGELOG.md) for all of the updates.

View File

@@ -3,7 +3,7 @@ import com.typesafe.sbt.pgp.PgpKeys._
val Organization = "io.github.gitbucket" val Organization = "io.github.gitbucket"
val Name = "gitbucket" val Name = "gitbucket"
val GitBucketVersion = "4.33.0" val GitBucketVersion = "4.34.0"
val ScalatraVersion = "2.7.0-RC1" val ScalatraVersion = "2.7.0-RC1"
val JettyVersion = "9.4.30.v20200611" val JettyVersion = "9.4.30.v20200611"
val JgitVersion = "5.8.0.202006091008-r" val JgitVersion = "5.8.0.202006091008-r"
@@ -54,11 +54,9 @@ libraryDependencies ++= Seq(
"ch.qos.logback" % "logback-classic" % "1.2.3", "ch.qos.logback" % "logback-classic" % "1.2.3",
"com.zaxxer" % "HikariCP" % "3.4.5", "com.zaxxer" % "HikariCP" % "3.4.5",
"com.typesafe" % "config" % "1.4.0", "com.typesafe" % "config" % "1.4.0",
"com.typesafe.akka" %% "akka-actor" % "2.5.27",
"fr.brouillard.oss.security.xhub" % "xhub4j-core" % "1.1.0", "fr.brouillard.oss.security.xhub" % "xhub4j-core" % "1.1.0",
"com.github.bkromhout" % "java-diff-utils" % "2.1.1", "com.github.bkromhout" % "java-diff-utils" % "2.1.1",
"org.cache2k" % "cache2k-all" % "1.2.4.Final", "org.cache2k" % "cache2k-all" % "1.2.4.Final",
"com.enragedginger" %% "akka-quartz-scheduler" % "1.8.1-akka-2.5.x" exclude ("com.mchange", "c3p0") exclude ("com.zaxxer", "HikariCP-java6"),
"net.coobird" % "thumbnailator" % "0.4.11", "net.coobird" % "thumbnailator" % "0.4.11",
"com.github.zafarkhaja" % "java-semver" % "0.9.0", "com.github.zafarkhaja" % "java-semver" % "0.9.0",
"com.nimbusds" % "oauth2-oidc-sdk" % "5.64.4", "com.nimbusds" % "oauth2-oidc-sdk" % "5.64.4",

View File

@@ -1,4 +1,4 @@
notifications:1.8.0 notifications:1.9.0
gist:4.18.0 gist:4.19.0
emoji:4.6.0 emoji:4.6.0
pages:1.8.0 pages:1.8.0

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<changeSet>
<dropTable tableName="ACTIVITY" />
</changeSet>

View File

@@ -1,7 +1,22 @@
package gitbucket.core package gitbucket.core
import io.github.gitbucket.solidbase.migration.{SqlMigration, LiquibaseMigration} import java.io.FileOutputStream
import io.github.gitbucket.solidbase.model.{Version, Module} import java.nio.charset.StandardCharsets
import java.sql.Connection
import java.util
import java.util.UUID
import gitbucket.core.model.Activity
import gitbucket.core.util.Directory.ActivityLog
import gitbucket.core.util.JDBCUtil
import io.github.gitbucket.solidbase.Solidbase
import io.github.gitbucket.solidbase.migration.{LiquibaseMigration, Migration, SqlMigration}
import io.github.gitbucket.solidbase.model.{Module, Version}
import org.json4s.NoTypeHints
import org.json4s.jackson.Serialization
import org.json4s.jackson.Serialization.write
import scala.util.Using
object GitBucketCoreModule object GitBucketCoreModule
extends Module( extends Module(
@@ -65,5 +80,38 @@ object GitBucketCoreModule
new Version("4.31.1"), new Version("4.31.1"),
new Version("4.31.2"), new Version("4.31.2"),
new Version("4.32.0", new LiquibaseMigration("update/gitbucket-core_4.32.xml")), new Version("4.32.0", new LiquibaseMigration("update/gitbucket-core_4.32.xml")),
new Version("4.33.0") new Version("4.33.0"),
new Version(
"4.34.0",
new Migration() {
override def migrate(moduleId: String, version: String, context: util.Map[String, AnyRef]): Unit = {
implicit val formats = Serialization.formats(NoTypeHints)
import JDBCUtil._
val conn = context.get(Solidbase.CONNECTION).asInstanceOf[Connection]
val list = conn.select("SELECT * FROM ACTIVITY ORDER BY ACTIVITY_ID") {
rs =>
Activity(
activityId = UUID.randomUUID().toString,
userName = rs.getString("USER_NAME"),
repositoryName = rs.getString("REPOSITORY_NAME"),
activityUserName = rs.getString("ACTIVITY_USER_NAME"),
activityType = rs.getString("ACTIVITY_TYPE"),
message = rs.getString("MESSAGE"),
additionalInfo = {
val additionalInfo = rs.getString("ADDITIONAL_INFO")
if (rs.wasNull()) None else Some(additionalInfo)
},
activityDate = rs.getTimestamp("ACTIVITY_DATE")
)
}
Using.resource(new FileOutputStream(ActivityLog, true)) { out =>
list.foreach { activity =>
out.write((write(activity) + "\n").getBytes(StandardCharsets.UTF_8))
}
}
}
},
new LiquibaseMigration("update/gitbucket-core_4.34.xml")
)
) )

View File

@@ -35,6 +35,7 @@ class AccountController
with WebHookService with WebHookService
with PrioritiesService with PrioritiesService
with RepositoryCreationService with RepositoryCreationService
with RequestCache
trait AccountControllerBase extends AccountManagementControllerBase { trait AccountControllerBase extends AccountManagementControllerBase {
self: AccountService self: AccountService

View File

@@ -52,6 +52,7 @@ class ApiController
with ReferrerAuthenticator with ReferrerAuthenticator
with ReadableUsersAuthenticator with ReadableUsersAuthenticator
with WritableUsersAuthenticator with WritableUsersAuthenticator
with RequestCache
trait ApiControllerBase extends ControllerBase { trait ApiControllerBase extends ControllerBase {

View File

@@ -22,6 +22,7 @@ class DashboardController
with WebHookPullRequestReviewCommentService with WebHookPullRequestReviewCommentService
with MilestonesService with MilestonesService
with UsersAuthenticator with UsersAuthenticator
with RequestCache
trait DashboardControllerBase extends ControllerBase { trait DashboardControllerBase extends ControllerBase {
self: IssuesService with PullRequestService with RepositoryService with AccountService with UsersAuthenticator => self: IssuesService with PullRequestService with RepositoryService with AccountService with UsersAuthenticator =>

View File

@@ -29,6 +29,7 @@ class IndexController
with AccessTokenService with AccessTokenService
with AccountFederationService with AccountFederationService
with OpenIDConnectService with OpenIDConnectService
with RequestCache
trait IndexControllerBase extends ControllerBase { trait IndexControllerBase extends ControllerBase {
self: RepositoryService self: RepositoryService
@@ -78,7 +79,7 @@ trait IndexControllerBase extends ControllerBase {
} }
.getOrElse { .getOrElse {
gitbucket.core.html.index( gitbucket.core.html.index(
getRecentActivities(), getRecentPublicActivities(),
getVisibleRepositories(None, withoutPhysicalInfo = true), getVisibleRepositories(None, withoutPhysicalInfo = true),
showBannerToCreatePersonalAccessToken = false showBannerToCreatePersonalAccessToken = false
) )
@@ -161,7 +162,7 @@ trait IndexControllerBase extends ControllerBase {
get("/activities.atom") { get("/activities.atom") {
contentType = "application/atom+xml; type=feed" contentType = "application/atom+xml; type=feed"
xml.feed(getRecentActivities()) xml.feed(getRecentPublicActivities())
} }
post("/sidebar-collapse") { post("/sidebar-collapse") {

View File

@@ -30,6 +30,7 @@ class IssuesController
with WebHookPullRequestReviewCommentService with WebHookPullRequestReviewCommentService
with CommitsService with CommitsService
with PrioritiesService with PrioritiesService
with RequestCache
trait IssuesControllerBase extends ControllerBase { trait IssuesControllerBase extends ControllerBase {
self: IssuesService self: IssuesService

View File

@@ -36,6 +36,7 @@ class PullRequestsController
with MergeService with MergeService
with ProtectedBranchService with ProtectedBranchService
with PrioritiesService with PrioritiesService
with RequestCache
trait PullRequestsControllerBase extends ControllerBase { trait PullRequestsControllerBase extends ControllerBase {
self: RepositoryService self: RepositoryService

View File

@@ -2,7 +2,14 @@ package gitbucket.core.controller
import java.io.File import java.io.File
import gitbucket.core.service.{AccountService, ActivityService, PaginationHelper, ReleaseService, RepositoryService} import gitbucket.core.service.{
AccountService,
ActivityService,
PaginationHelper,
ReleaseService,
RepositoryService,
RequestCache
}
import gitbucket.core.util._ import gitbucket.core.util._
import gitbucket.core.util.Directory._ import gitbucket.core.util.Directory._
import gitbucket.core.util.Implicits._ import gitbucket.core.util.Implicits._
@@ -22,6 +29,7 @@ class ReleaseController
with ReadableUsersAuthenticator with ReadableUsersAuthenticator
with ReferrerAuthenticator with ReferrerAuthenticator
with WritableUsersAuthenticator with WritableUsersAuthenticator
with RequestCache
trait ReleaseControllerBase extends ControllerBase { trait ReleaseControllerBase extends ControllerBase {
self: RepositoryService self: RepositoryService

View File

@@ -30,8 +30,10 @@ class RepositorySettingsController
with ProtectedBranchService with ProtectedBranchService
with CommitStatusService with CommitStatusService
with DeployKeyService with DeployKeyService
with ActivityService
with OwnerAuthenticator with OwnerAuthenticator
with UsersAuthenticator with UsersAuthenticator
with RequestCache
trait RepositorySettingsControllerBase extends ControllerBase { trait RepositorySettingsControllerBase extends ControllerBase {
self: RepositoryService self: RepositoryService
@@ -40,6 +42,7 @@ trait RepositorySettingsControllerBase extends ControllerBase {
with ProtectedBranchService with ProtectedBranchService
with CommitStatusService with CommitStatusService
with DeployKeyService with DeployKeyService
with ActivityService
with OwnerAuthenticator with OwnerAuthenticator
with UsersAuthenticator => with UsersAuthenticator =>
@@ -97,9 +100,7 @@ trait RepositorySettingsControllerBase extends ControllerBase {
"events" -> webhookEvents, "events" -> webhookEvents,
"ctype" -> label("ctype", text()), "ctype" -> label("ctype", text()),
"token" -> optional(trim(label("token", text(maxlength(100))))) "token" -> optional(trim(label("token", text(maxlength(100)))))
)( )((url, events, ctype, token) => WebHookForm(url, events, WebHookContentType.valueOf(ctype), token))
(url, events, ctype, token) => WebHookForm(url, events, WebHookContentType.valueOf(ctype), token)
)
// for rename repository // for rename repository
case class RenameRepositoryForm(repositoryName: String) case class RenameRepositoryForm(repositoryName: String)
@@ -251,9 +252,10 @@ trait RepositorySettingsControllerBase extends ControllerBase {
* Send the test request to registered web hook URLs. * Send the test request to registered web hook URLs.
*/ */
ajaxPost("/:owner/:repository/settings/hooks/test")(ownerOnly { repository => ajaxPost("/:owner/:repository/settings/hooks/test")(ownerOnly { repository =>
def _headers(h: Array[org.apache.http.Header]): Array[Array[String]] = h.map { h => def _headers(h: Array[org.apache.http.Header]): Array[Array[String]] =
Array(h.getName, h.getValue) h.map { h =>
} Array(h.getName, h.getValue)
}
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) {
git => git =>
@@ -371,7 +373,15 @@ trait RepositorySettingsControllerBase extends ControllerBase {
post("/:owner/:repository/settings/rename", renameForm)(ownerOnly { (form, repository) => post("/:owner/:repository/settings/rename", renameForm)(ownerOnly { (form, repository) =>
if (context.settings.repositoryOperation.rename || context.loginAccount.get.isAdmin) { if (context.settings.repositoryOperation.rename || context.loginAccount.get.isAdmin) {
if (repository.name != form.repositoryName) { if (repository.name != form.repositoryName) {
// Update database and move git repository
renameRepository(repository.owner, repository.name, repository.owner, form.repositoryName) renameRepository(repository.owner, repository.name, repository.owner, form.repositoryName)
// Record activity log
recordRenameRepositoryActivity(
repository.owner,
form.repositoryName,
repository.name,
context.loginAccount.get.userName
)
} }
redirect(s"/${repository.owner}/${form.repositoryName}") redirect(s"/${repository.owner}/${form.repositoryName}")
} else Forbidden() } else Forbidden()
@@ -384,7 +394,15 @@ trait RepositorySettingsControllerBase extends ControllerBase {
if (context.settings.repositoryOperation.transfer || context.loginAccount.get.isAdmin) { if (context.settings.repositoryOperation.transfer || context.loginAccount.get.isAdmin) {
// Change repository owner // Change repository owner
if (repository.owner != form.newOwner) { if (repository.owner != form.newOwner) {
// Update database and move git repository
renameRepository(repository.owner, repository.name, form.newOwner, repository.name) renameRepository(repository.owner, repository.name, form.newOwner, repository.name)
// Record activity log
recordRenameRepositoryActivity(
form.newOwner,
repository.name,
repository.owner,
context.loginAccount.get.userName
)
} }
redirect(s"/${form.newOwner}/${repository.name}") redirect(s"/${form.newOwner}/${repository.name}")
} else Forbidden() } else Forbidden()
@@ -435,32 +453,34 @@ trait RepositorySettingsControllerBase extends ControllerBase {
/** /**
* Provides duplication check for web hook url. * Provides duplication check for web hook url.
*/ */
private def webHook(needExists: Boolean): Constraint = new Constraint() { private def webHook(needExists: Boolean): Constraint =
override def validate(name: String, value: String, messages: Messages): Option[String] = new Constraint() {
if (getWebHook(params("owner"), params("repository"), value).isDefined != needExists) { override def validate(name: String, value: String, messages: Messages): Option[String] =
Some(if (needExists) { if (getWebHook(params("owner"), params("repository"), value).isDefined != needExists) {
"URL had not been registered yet." Some(if (needExists) {
"URL had not been registered yet."
} else {
"URL had been registered already."
})
} else { } else {
"URL had been registered already." None
}) }
} else {
None
}
}
private def webhookEvents = new ValueType[Set[WebHook.Event]] {
def convert(name: String, params: Map[String, Seq[String]], messages: Messages): Set[WebHook.Event] = {
WebHook.Event.values.flatMap { t =>
params.get(name + "." + t.name).map(_ => t)
}.toSet
} }
def validate(name: String, params: Map[String, Seq[String]], messages: Messages): Seq[(String, String)] =
if (convert(name, params, messages).isEmpty) { private def webhookEvents =
Seq(name -> messages("error.required").format(name)) new ValueType[Set[WebHook.Event]] {
} else { def convert(name: String, params: Map[String, Seq[String]], messages: Messages): Set[WebHook.Event] = {
Nil WebHook.Event.values.flatMap { t =>
params.get(name + "." + t.name).map(_ => t)
}.toSet
} }
} def validate(name: String, params: Map[String, Seq[String]], messages: Messages): Seq[(String, String)] =
if (convert(name, params, messages).isEmpty) {
Seq(name -> messages("error.required").format(name))
} else {
Nil
}
}
// /** // /**
// * Provides Constraint to validate the collaborator name. // * Provides Constraint to validate the collaborator name.
@@ -480,70 +500,77 @@ trait RepositorySettingsControllerBase extends ControllerBase {
/** /**
* Duplicate check for the rename repository name. * Duplicate check for the rename repository name.
*/ */
private def renameRepositoryName: Constraint = new Constraint() { private def renameRepositoryName: Constraint =
override def validate( new Constraint() {
name: String, override def validate(
value: String, name: String,
params: Map[String, Seq[String]], value: String,
messages: Messages params: Map[String, Seq[String]],
): Option[String] = { messages: Messages
for { ): Option[String] = {
repoName <- params.optionValue("repository") if repoName != value for {
userName <- params.optionValue("owner") repoName <- params.optionValue("repository") if repoName != value
_ <- getRepositoryNamesOfUser(userName).find(_ == value) userName <- params.optionValue("owner")
} yield { _ <- getRepositoryNamesOfUser(userName).find(_ == value)
"Repository already exists." } yield {
"Repository already exists."
}
} }
} }
}
/** /**
*
*/ */
private def featureOption: Constraint = new Constraint() { private def featureOption: Constraint =
override def validate( new Constraint() {
name: String, override def validate(
value: String, name: String,
params: Map[String, Seq[String]], value: String,
messages: Messages params: Map[String, Seq[String]],
): Option[String] = messages: Messages
if (Seq("DISABLE", "PRIVATE", "PUBLIC", "ALL").contains(value)) None else Some("Option is invalid.") ): Option[String] =
} if (Seq("DISABLE", "PRIVATE", "PUBLIC", "ALL").contains(value)) None else Some("Option is invalid.")
}
/** /**
* Provides Constraint to validate the repository transfer user. * Provides Constraint to validate the repository transfer user.
*/ */
private def transferUser: Constraint = new Constraint() { private def transferUser: Constraint =
override def validate(name: String, value: String, messages: Messages): Option[String] = new Constraint() {
getAccountByUserName(value) match { override def validate(name: String, value: String, messages: Messages): Option[String] =
case None => Some("User does not exist.") getAccountByUserName(value) match {
case Some(x) => case None => Some("User does not exist.")
if (x.userName == params("owner")) { case Some(x) =>
Some("This is current repository owner.") if (x.userName == params("owner")) {
} else { Some("This is current repository owner.")
params.get("repository").flatMap { repositoryName => } else {
getRepositoryNamesOfUser(x.userName).find(_ == repositoryName).map { _ => params.get("repository").flatMap { repositoryName =>
"User already has same repository." getRepositoryNamesOfUser(x.userName).find(_ == repositoryName).map { _ =>
"User already has same repository."
}
} }
} }
} }
} }
}
private def mergeOptions = new ValueType[Seq[String]] { private def mergeOptions =
override def convert(name: String, params: Map[String, Seq[String]], messages: Messages): Seq[String] = { new ValueType[Seq[String]] {
params.getOrElse("mergeOptions", Nil) override def convert(name: String, params: Map[String, Seq[String]], messages: Messages): Seq[String] = {
} params.getOrElse("mergeOptions", Nil)
override def validate(name: String, params: Map[String, Seq[String]], messages: Messages): Seq[(String, String)] = { }
val mergeOptions = params.getOrElse("mergeOptions", Nil) override def validate(
if (mergeOptions.isEmpty) { name: String,
Seq("mergeOptions" -> "At least one option must be enabled.") params: Map[String, Seq[String]],
} else if (!mergeOptions.forall(x => Seq("merge-commit", "squash", "rebase").contains(x))) { messages: Messages
Seq("mergeOptions" -> "mergeOptions are invalid.") ): Seq[(String, String)] = {
} else { val mergeOptions = params.getOrElse("mergeOptions", Nil)
Nil if (mergeOptions.isEmpty) {
Seq("mergeOptions" -> "At least one option must be enabled.")
} else if (!mergeOptions.forall(x => Seq("merge-commit", "squash", "rebase").contains(x))) {
Seq("mergeOptions" -> "mergeOptions are invalid.")
} else {
Nil
}
} }
} }
}
} }

View File

@@ -60,6 +60,7 @@ class RepositoryViewerController
with WebHookPullRequestService with WebHookPullRequestService
with WebHookPullRequestReviewCommentService with WebHookPullRequestReviewCommentService
with ProtectedBranchService with ProtectedBranchService
with RequestCache
/** /**
* The repository viewer. * The repository viewer.

View File

@@ -24,6 +24,7 @@ class WikiController
with WebHookService with WebHookService
with ReadableUsersAuthenticator with ReadableUsersAuthenticator
with ReferrerAuthenticator with ReferrerAuthenticator
with RequestCache
trait WikiControllerBase extends ControllerBase { trait WikiControllerBase extends ControllerBase {
self: WikiService self: WikiService

View File

@@ -1,5 +1,9 @@
package gitbucket.core.model package gitbucket.core.model
/**
* ActivityComponent has been deprecated, but keep it for binary compatibility.
*/
@deprecated("ActivityComponent has been deprecated, but keep it for binary compatibility.", "4.34.0")
trait ActivityComponent extends TemplateComponent { self: Profile => trait ActivityComponent extends TemplateComponent { self: Profile =>
import profile.api._ import profile.api._
import self._ import self._
@@ -7,14 +11,7 @@ trait ActivityComponent extends TemplateComponent { self: Profile =>
lazy val Activities = TableQuery[Activities] lazy val Activities = TableQuery[Activities]
class Activities(tag: Tag) extends Table[Activity](tag, "ACTIVITY") with BasicTemplate { class Activities(tag: Tag) extends Table[Activity](tag, "ACTIVITY") with BasicTemplate {
val activityId = column[Int]("ACTIVITY_ID", O AutoInc) def * = ???
val activityUserName = column[String]("ACTIVITY_USER_NAME")
val activityType = column[String]("ACTIVITY_TYPE")
val message = column[String]("MESSAGE")
val additionalInfo = column[String]("ADDITIONAL_INFO")
val activityDate = column[java.util.Date]("ACTIVITY_DATE")
def * =
(userName, repositoryName, activityUserName, activityType, message, additionalInfo.?, activityDate, activityId) <> (Activity.tupled, Activity.unapply)
} }
} }
@@ -26,5 +23,5 @@ case class Activity(
message: String, message: String,
additionalInfo: Option[String], additionalInfo: Option[String],
activityDate: java.util.Date, activityDate: java.util.Date,
activityId: Int = 0 activityId: String
) )

View File

@@ -45,7 +45,7 @@ trait CoreProfile
with Profile with Profile
with AccessTokenComponent with AccessTokenComponent
with AccountComponent with AccountComponent
with ActivityComponent with ActivityComponent // ActivityComponent has been deprecated, but keep it for binary compatibility
with CollaboratorComponent with CollaboratorComponent
with CommitCommentComponent with CommitCommentComponent
with CommitStatusComponent with CommitStatusComponent

View File

@@ -17,7 +17,9 @@ trait AccountService {
def authenticate(settings: SystemSettings, userName: String, password: String)( def authenticate(settings: SystemSettings, userName: String, password: String)(
implicit s: Session implicit s: Session
): Option[Account] = { ): Option[Account] = {
val account = if (settings.ldapAuthentication) { val account = if (password.isEmpty) {
None
} else if (settings.ldapAuthentication) {
ldapAuthentication(settings, userName, password) ldapAuthentication(settings, userName, password)
} else { } else {
defaultAuthentication(userName, password) defaultAuthentication(userName, password)

View File

@@ -3,65 +3,169 @@ package gitbucket.core.service
import gitbucket.core.model.Activity import gitbucket.core.model.Activity
import gitbucket.core.util.JGitUtil import gitbucket.core.util.JGitUtil
import gitbucket.core.model.Profile._ import gitbucket.core.model.Profile._
import gitbucket.core.model.Profile.profile.blockingApi._ import gitbucket.core.util.Directory._
import org.json4s._
import org.json4s.jackson.Serialization
import org.json4s.jackson.Serialization.{read, write}
import scala.util.Using
import java.io.FileOutputStream
import java.nio.charset.StandardCharsets
import java.util.UUID
import gitbucket.core.controller.Context
import org.apache.commons.io.input.ReversedLinesFileReader
import scala.collection.mutable.ListBuffer
trait ActivityService { trait ActivityService {
self: RequestCache =>
def deleteOldActivities(limit: Int)(implicit s: Session): Int = { private implicit val formats = Serialization.formats(NoTypeHints)
Activities.map(_.activityId).sortBy(_ desc).drop(limit).firstOption.map { id =>
Activities.filter(_.activityId <= id.bind).delete private def writeLog(activity: Activity): Unit = {
} getOrElse 0 Using.resource(new FileOutputStream(ActivityLog, true)) { out =>
out.write((write(activity) + "\n").getBytes(StandardCharsets.UTF_8))
}
} }
def getActivitiesByUser(activityUserName: String, isPublic: Boolean)(implicit s: Session): List[Activity] = def getActivitiesByUser(activityUserName: String, isPublic: Boolean)(implicit context: Context): List[Activity] = {
Activities if (!ActivityLog.exists()) {
.join(Repositories) List.empty
.on((t1, t2) => t1.byRepository(t2.userName, t2.repositoryName)) } else {
.filter { val list = new ListBuffer[Activity]
case (t1, t2) => Using.resource(new ReversedLinesFileReader(ActivityLog, StandardCharsets.UTF_8)) { reader =>
if (isPublic) { var json: String = null
(t1.activityUserName === activityUserName.bind) && (t2.isPrivate === false.bind) while (list.length < 50 && { json = reader.readLine(); json } != null) {
} else { val activity = read[Activity](json)
(t1.activityUserName === activityUserName.bind) if (activity.activityUserName == activityUserName) {
if (isPublic == false) {
list += activity
} else {
if (!getRepositoryInfoFromCache(activity.userName, activity.repositoryName)
.map(_.isPrivate)
.getOrElse(true)) {
list += activity
}
}
} }
}
} }
.sortBy { case (t1, t2) => t1.activityId desc } list.toList
.map { case (t1, t2) => t1 } }
.take(30) }
.list
def getRecentActivities()(implicit s: Session): List[Activity] = def getRecentPublicActivities()(implicit context: Context): List[Activity] = {
Activities if (!ActivityLog.exists()) {
.join(Repositories) List.empty
.on((t1, t2) => t1.byRepository(t2.userName, t2.repositoryName)) } else {
.filter { case (t1, t2) => t2.isPrivate === false.bind } val list = new ListBuffer[Activity]
.sortBy { case (t1, t2) => t1.activityId desc } Using.resource(new ReversedLinesFileReader(ActivityLog, StandardCharsets.UTF_8)) { reader =>
.map { case (t1, t2) => t1 } var json: String = null
.take(30) while (list.length < 50 && { json = reader.readLine(); json } != null) {
.list val activity = read[Activity](json)
if (!getRepositoryInfoFromCache(activity.userName, activity.repositoryName)
.map(_.isPrivate)
.getOrElse(true)) {
list += activity
}
}
}
list.toList
}
}
def getRecentActivitiesByOwners(owners: Set[String])(implicit s: Session): List[Activity] = def getRecentActivitiesByOwners(owners: Set[String])(implicit context: Context): List[Activity] = {
Activities if (!ActivityLog.exists()) {
.join(Repositories) List.empty
.on((t1, t2) => t1.byRepository(t2.userName, t2.repositoryName)) } else {
.filter { case (t1, t2) => (t2.isPrivate === false.bind) || (t2.userName inSetBind owners) } val list = new ListBuffer[Activity]
.sortBy { case (t1, t2) => t1.activityId desc } Using.resource(new ReversedLinesFileReader(ActivityLog, StandardCharsets.UTF_8)) { reader =>
.map { case (t1, t2) => t1 } var json: String = null
.take(30) while (list.length < 50 && { json = reader.readLine(); json } != null) {
.list val activity = read[Activity](json)
if (owners.contains(activity.userName)) {
list += activity
} else if (!getRepositoryInfoFromCache(activity.userName, activity.repositoryName)
.map(_.isPrivate)
.getOrElse(true)) {
list += activity
}
}
}
list.toList
}
}
def recordCreateRepositoryActivity(userName: String, repositoryName: String, activityUserName: String)( def recordCreateRepositoryActivity(userName: String, repositoryName: String, activityUserName: String): Unit = {
implicit s: Session writeLog(
): Unit = Activity(
Activities insert Activity( userName,
userName, repositoryName,
repositoryName, activityUserName,
activityUserName, "create_repository",
"create_repository", s"[user:${activityUserName}] created [repo:${userName}/${repositoryName}]",
s"[user:${activityUserName}] created [repo:${userName}/${repositoryName}]", None,
None, currentDate,
currentDate UUID.randomUUID().toString
)
) )
}
def recordDeleteRepositoryActivity(userName: String, repositoryName: String, activityUserName: String): Unit = {
writeLog(
Activity(
userName,
repositoryName,
activityUserName,
"delete_repository",
s"[user:${activityUserName}] deleted [repo:${userName}/${repositoryName}]",
None,
currentDate,
UUID.randomUUID().toString
)
)
}
def recordTransferRepositoryActivity(
userName: String,
repositoryName: String,
oldUserName: String,
activityUserName: String
): Unit = {
writeLog(
Activity(
userName,
repositoryName,
activityUserName,
"transfer_repository",
s"[user:${activityUserName}] transfered [repo:${oldUserName}/${repositoryName}] to [repo:${userName}/${repositoryName}]",
None,
currentDate,
UUID.randomUUID().toString
)
)
}
def recordRenameRepositoryActivity(
userName: String,
repositoryName: String,
oldRepositoryName: String,
activityUserName: String
): Unit = {
writeLog(
Activity(
userName,
repositoryName,
activityUserName,
"rename_repository",
s"[user:${activityUserName}] renamed [repo:${userName}/${oldRepositoryName}] at [repo:${userName}/${repositoryName}]",
None,
currentDate,
UUID.randomUUID().toString
)
)
}
def recordCreateIssueActivity( def recordCreateIssueActivity(
userName: String, userName: String,
@@ -69,16 +173,20 @@ trait ActivityService {
activityUserName: String, activityUserName: String,
issueId: Int, issueId: Int,
title: String title: String
)(implicit s: Session): Unit = ): Unit = {
Activities insert Activity( writeLog(
userName, Activity(
repositoryName, userName,
activityUserName, repositoryName,
"open_issue", activityUserName,
s"[user:${activityUserName}] opened issue [issue:${userName}/${repositoryName}#${issueId}]", "open_issue",
Some(title), s"[user:${activityUserName}] opened issue [issue:${userName}/${repositoryName}#${issueId}]",
currentDate Some(title),
currentDate,
UUID.randomUUID().toString
)
) )
}
def recordCloseIssueActivity( def recordCloseIssueActivity(
userName: String, userName: String,
@@ -86,16 +194,20 @@ trait ActivityService {
activityUserName: String, activityUserName: String,
issueId: Int, issueId: Int,
title: String title: String
)(implicit s: Session): Unit = ): Unit = {
Activities insert Activity( writeLog(
userName, Activity(
repositoryName, userName,
activityUserName, repositoryName,
"close_issue", activityUserName,
s"[user:${activityUserName}] closed issue [issue:${userName}/${repositoryName}#${issueId}]", "close_issue",
Some(title), s"[user:${activityUserName}] closed issue [issue:${userName}/${repositoryName}#${issueId}]",
currentDate Some(title),
currentDate,
UUID.randomUUID().toString
)
) )
}
def recordClosePullRequestActivity( def recordClosePullRequestActivity(
userName: String, userName: String,
@@ -103,16 +215,20 @@ trait ActivityService {
activityUserName: String, activityUserName: String,
issueId: Int, issueId: Int,
title: String title: String
)(implicit s: Session): Unit = ): Unit = {
Activities insert Activity( writeLog(
userName, Activity(
repositoryName, userName,
activityUserName, repositoryName,
"close_issue", activityUserName,
s"[user:${activityUserName}] closed pull request [pullreq:${userName}/${repositoryName}#${issueId}]", "close_issue",
Some(title), s"[user:${activityUserName}] closed pull request [pullreq:${userName}/${repositoryName}#${issueId}]",
currentDate Some(title),
currentDate,
UUID.randomUUID().toString
)
) )
}
def recordReopenIssueActivity( def recordReopenIssueActivity(
userName: String, userName: String,
@@ -120,16 +236,20 @@ trait ActivityService {
activityUserName: String, activityUserName: String,
issueId: Int, issueId: Int,
title: String title: String
)(implicit s: Session): Unit = ): Unit = {
Activities insert Activity( writeLog(
userName, Activity(
repositoryName, userName,
activityUserName, repositoryName,
"reopen_issue", activityUserName,
s"[user:${activityUserName}] reopened issue [issue:${userName}/${repositoryName}#${issueId}]", "reopen_issue",
Some(title), s"[user:${activityUserName}] reopened issue [issue:${userName}/${repositoryName}#${issueId}]",
currentDate Some(title),
currentDate,
UUID.randomUUID().toString
)
) )
}
def recordReopenPullRequestActivity( def recordReopenPullRequestActivity(
userName: String, userName: String,
@@ -137,16 +257,20 @@ trait ActivityService {
activityUserName: String, activityUserName: String,
issueId: Int, issueId: Int,
title: String title: String
)(implicit s: Session): Unit = ): Unit = {
Activities insert Activity( writeLog(
userName, Activity(
repositoryName, userName,
activityUserName, repositoryName,
"reopen_issue", activityUserName,
s"[user:${activityUserName}] reopened pull request [issue:${userName}/${repositoryName}#${issueId}]", "reopen_issue",
Some(title), s"[user:${activityUserName}] reopened pull request [issue:${userName}/${repositoryName}#${issueId}]",
currentDate Some(title),
currentDate,
UUID.randomUUID().toString
)
) )
}
def recordCommentIssueActivity( def recordCommentIssueActivity(
userName: String, userName: String,
@@ -154,16 +278,20 @@ trait ActivityService {
activityUserName: String, activityUserName: String,
issueId: Int, issueId: Int,
comment: String comment: String
)(implicit s: Session): Unit = ): Unit = {
Activities insert Activity( writeLog(
userName, Activity(
repositoryName, userName,
activityUserName, repositoryName,
"comment_issue", activityUserName,
s"[user:${activityUserName}] commented on issue [issue:${userName}/${repositoryName}#${issueId}]", "comment_issue",
Some(cut(comment, 200)), s"[user:${activityUserName}] commented on issue [issue:${userName}/${repositoryName}#${issueId}]",
currentDate Some(cut(comment, 200)),
currentDate,
UUID.randomUUID().toString
)
) )
}
def recordCommentPullRequestActivity( def recordCommentPullRequestActivity(
userName: String, userName: String,
@@ -171,16 +299,20 @@ trait ActivityService {
activityUserName: String, activityUserName: String,
issueId: Int, issueId: Int,
comment: String comment: String
)(implicit s: Session): Unit = ): Unit = {
Activities insert Activity( writeLog(
userName, Activity(
repositoryName, userName,
activityUserName, repositoryName,
"comment_issue", activityUserName,
s"[user:${activityUserName}] commented on pull request [pullreq:${userName}/${repositoryName}#${issueId}]", "comment_issue",
Some(cut(comment, 200)), s"[user:${activityUserName}] commented on pull request [pullreq:${userName}/${repositoryName}#${issueId}]",
currentDate Some(cut(comment, 200)),
currentDate,
UUID.randomUUID().toString
)
) )
}
def recordCommentCommitActivity( def recordCommentCommitActivity(
userName: String, userName: String,
@@ -188,32 +320,40 @@ trait ActivityService {
activityUserName: String, activityUserName: String,
commitId: String, commitId: String,
comment: String comment: String
)(implicit s: Session): Unit = ): Unit = {
Activities insert Activity( writeLog(
userName, Activity(
repositoryName, userName,
activityUserName, repositoryName,
"comment_commit", activityUserName,
s"[user:${activityUserName}] commented on commit [commit:${userName}/${repositoryName}@${commitId}]", "comment_commit",
Some(cut(comment, 200)), s"[user:${activityUserName}] commented on commit [commit:${userName}/${repositoryName}@${commitId}]",
currentDate Some(cut(comment, 200)),
currentDate,
UUID.randomUUID().toString
)
) )
}
def recordCreateWikiPageActivity( def recordCreateWikiPageActivity(
userName: String, userName: String,
repositoryName: String, repositoryName: String,
activityUserName: String, activityUserName: String,
pageName: String pageName: String
)(implicit s: Session): Unit = ): Unit = {
Activities insert Activity( writeLog(
userName, Activity(
repositoryName, userName,
activityUserName, repositoryName,
"create_wiki", activityUserName,
s"[user:${activityUserName}] created the [repo:${userName}/${repositoryName}] wiki", "create_wiki",
Some(pageName), s"[user:${activityUserName}] created the [repo:${userName}/${repositoryName}] wiki",
currentDate Some(pageName),
currentDate,
UUID.randomUUID().toString
)
) )
}
def recordEditWikiPageActivity( def recordEditWikiPageActivity(
userName: String, userName: String,
@@ -221,16 +361,20 @@ trait ActivityService {
activityUserName: String, activityUserName: String,
pageName: String, pageName: String,
commitId: String commitId: String
)(implicit s: Session): Unit = ): Unit = {
Activities insert Activity( writeLog(
userName, Activity(
repositoryName, userName,
activityUserName, repositoryName,
"edit_wiki", activityUserName,
s"[user:${activityUserName}] edited the [repo:${userName}/${repositoryName}] wiki", "edit_wiki",
Some(pageName + ":" + commitId), s"[user:${activityUserName}] edited the [repo:${userName}/${repositoryName}] wiki",
currentDate Some(pageName + ":" + commitId),
currentDate,
UUID.randomUUID().toString
)
) )
}
def recordPushActivity( def recordPushActivity(
userName: String, userName: String,
@@ -238,23 +382,27 @@ trait ActivityService {
activityUserName: String, activityUserName: String,
branchName: String, branchName: String,
commits: List[JGitUtil.CommitInfo] commits: List[JGitUtil.CommitInfo]
)(implicit s: Session): Unit = ): Unit = {
Activities insert Activity( writeLog(
userName, Activity(
repositoryName, userName,
activityUserName, repositoryName,
"push", activityUserName,
s"[user:${activityUserName}] pushed to [branch:${userName}/${repositoryName}#${branchName}] at [repo:${userName}/${repositoryName}]", "push",
Some( s"[user:${activityUserName}] pushed to [branch:${userName}/${repositoryName}#${branchName}] at [repo:${userName}/${repositoryName}]",
commits Some(
.take(5) commits
.map { commit => .take(5)
commit.id + ":" + commit.shortMessage .map { commit =>
} commit.id + ":" + commit.shortMessage
.mkString("\n") }
), .mkString("\n")
currentDate ),
currentDate,
UUID.randomUUID().toString
)
) )
}
def recordCreateTagActivity( def recordCreateTagActivity(
userName: String, userName: String,
@@ -262,16 +410,20 @@ trait ActivityService {
activityUserName: String, activityUserName: String,
tagName: String, tagName: String,
commits: List[JGitUtil.CommitInfo] commits: List[JGitUtil.CommitInfo]
)(implicit s: Session): Unit = ): Unit = {
Activities insert Activity( writeLog(
userName, Activity(
repositoryName, userName,
activityUserName, repositoryName,
"create_tag", activityUserName,
s"[user:${activityUserName}] created tag [tag:${userName}/${repositoryName}#${tagName}] at [repo:${userName}/${repositoryName}]", "create_tag",
None, s"[user:${activityUserName}] created tag [tag:${userName}/${repositoryName}#${tagName}] at [repo:${userName}/${repositoryName}]",
currentDate None,
currentDate,
UUID.randomUUID().toString
)
) )
}
def recordDeleteTagActivity( def recordDeleteTagActivity(
userName: String, userName: String,
@@ -279,61 +431,80 @@ trait ActivityService {
activityUserName: String, activityUserName: String,
tagName: String, tagName: String,
commits: List[JGitUtil.CommitInfo] commits: List[JGitUtil.CommitInfo]
)(implicit s: Session): Unit = ): Unit = {
Activities insert Activity( writeLog(
userName, Activity(
repositoryName, userName,
activityUserName, repositoryName,
"delete_tag", activityUserName,
s"[user:${activityUserName}] deleted tag ${tagName} at [repo:${userName}/${repositoryName}]", "delete_tag",
None, s"[user:${activityUserName}] deleted tag ${tagName} at [repo:${userName}/${repositoryName}]",
currentDate None,
currentDate,
UUID.randomUUID().toString
)
) )
}
def recordCreateBranchActivity( def recordCreateBranchActivity(
userName: String, userName: String,
repositoryName: String, repositoryName: String,
activityUserName: String, activityUserName: String,
branchName: String branchName: String
)(implicit s: Session): Unit = ): Unit = {
Activities insert Activity( writeLog(
userName, Activity(
repositoryName, userName,
activityUserName, repositoryName,
"create_branch", activityUserName,
s"[user:${activityUserName}] created branch [branch:${userName}/${repositoryName}#${branchName}] at [repo:${userName}/${repositoryName}]", "create_branch",
None, s"[user:${activityUserName}] created branch [branch:${userName}/${repositoryName}#${branchName}] at [repo:${userName}/${repositoryName}]",
currentDate None,
currentDate,
UUID.randomUUID().toString
)
) )
}
def recordDeleteBranchActivity( def recordDeleteBranchActivity(
userName: String, userName: String,
repositoryName: String, repositoryName: String,
activityUserName: String, activityUserName: String,
branchName: String branchName: String
)(implicit s: Session): Unit = ): Unit = {
Activities insert Activity( writeLog(
userName, Activity(
repositoryName, userName,
activityUserName, repositoryName,
"delete_branch", activityUserName,
s"[user:${activityUserName}] deleted branch ${branchName} at [repo:${userName}/${repositoryName}]", "delete_branch",
None, s"[user:${activityUserName}] deleted branch ${branchName} at [repo:${userName}/${repositoryName}]",
currentDate None,
currentDate,
UUID.randomUUID().toString
)
) )
}
def recordForkActivity(userName: String, repositoryName: String, activityUserName: String, forkedUserName: String)( def recordForkActivity(
implicit s: Session userName: String,
): Unit = repositoryName: String,
Activities insert Activity( activityUserName: String,
userName, forkedUserName: String
repositoryName, ): Unit = {
activityUserName, writeLog(
"fork", Activity(
s"[user:${activityUserName}] forked [repo:${userName}/${repositoryName}] to [repo:${forkedUserName}/${repositoryName}]", userName,
None, repositoryName,
currentDate activityUserName,
"fork",
s"[user:${activityUserName}] forked [repo:${userName}/${repositoryName}] to [repo:${forkedUserName}/${repositoryName}]",
None,
currentDate,
UUID.randomUUID().toString
)
) )
}
def recordPullRequestActivity( def recordPullRequestActivity(
userName: String, userName: String,
@@ -341,16 +512,20 @@ trait ActivityService {
activityUserName: String, activityUserName: String,
issueId: Int, issueId: Int,
title: String title: String
)(implicit s: Session): Unit = ): Unit = {
Activities insert Activity( writeLog(
userName, Activity(
repositoryName, userName,
activityUserName, repositoryName,
"open_pullreq", activityUserName,
s"[user:${activityUserName}] opened pull request [pullreq:${userName}/${repositoryName}#${issueId}]", "open_pullreq",
Some(title), s"[user:${activityUserName}] opened pull request [pullreq:${userName}/${repositoryName}#${issueId}]",
currentDate Some(title),
currentDate,
UUID.randomUUID().toString
)
) )
}
def recordMergeActivity( def recordMergeActivity(
userName: String, userName: String,
@@ -358,16 +533,20 @@ trait ActivityService {
activityUserName: String, activityUserName: String,
issueId: Int, issueId: Int,
message: String message: String
)(implicit s: Session): Unit = ): Unit = {
Activities insert Activity( writeLog(
userName, Activity(
repositoryName, userName,
activityUserName, repositoryName,
"merge_pullreq", activityUserName,
s"[user:${activityUserName}] merged pull request [pullreq:${userName}/${repositoryName}#${issueId}]", "merge_pullreq",
Some(message), s"[user:${activityUserName}] merged pull request [pullreq:${userName}/${repositoryName}#${issueId}]",
currentDate Some(message),
currentDate,
UUID.randomUUID().toString
)
) )
}
def recordReleaseActivity( def recordReleaseActivity(
userName: String, userName: String,
@@ -375,16 +554,20 @@ trait ActivityService {
activityUserName: String, activityUserName: String,
releaseName: String, releaseName: String,
tagName: String tagName: String
)(implicit s: Session): Unit = ): Unit = {
Activities insert Activity( writeLog(
userName, Activity(
repositoryName, userName,
activityUserName, repositoryName,
"release", activityUserName,
s"[user:${activityUserName}] released [release:${userName}/${repositoryName}/${tagName}:${releaseName}] at [repo:${userName}/${repositoryName}]", "release",
None, s"[user:${activityUserName}] released [release:${userName}/${repositoryName}/${tagName}:${releaseName}] at [repo:${userName}/${repositoryName}]",
currentDate None,
currentDate,
UUID.randomUUID().toString
)
) )
}
private def cut(value: String, length: Int): String = private def cut(value: String, length: Int): String =
if (value.length > length) value.substring(0, length) + "..." else value if (value.length > length) value.substring(0, length) + "..." else value

View File

@@ -1,6 +1,5 @@
package gitbucket.core.service package gitbucket.core.service
import gitbucket.core.api.JsonFormat
import gitbucket.core.controller.Context import gitbucket.core.controller.Context
import gitbucket.core.util._ import gitbucket.core.util._
import gitbucket.core.util.SyntaxSugars._ import gitbucket.core.util.SyntaxSugars._
@@ -9,14 +8,11 @@ import gitbucket.core.model.Profile._
import gitbucket.core.model.Profile.profile.blockingApi._ import gitbucket.core.model.Profile.profile.blockingApi._
import gitbucket.core.model.Profile.dateColumnType import gitbucket.core.model.Profile.dateColumnType
import gitbucket.core.plugin.PluginRegistry import gitbucket.core.plugin.PluginRegistry
import gitbucket.core.service.WebHookService.WebHookPushPayload
import gitbucket.core.util.Directory.{getRepositoryDir, getRepositoryFilesDir, getTemporaryDir, getWikiRepositoryDir} import gitbucket.core.util.Directory.{getRepositoryDir, getRepositoryFilesDir, getTemporaryDir, getWikiRepositoryDir}
import gitbucket.core.util.JGitUtil.{CommitInfo, FileInfo} import gitbucket.core.util.JGitUtil.FileInfo
import org.apache.commons.io.FileUtils import org.apache.commons.io.FileUtils
import org.eclipse.jgit.api.Git import org.eclipse.jgit.api.Git
import org.eclipse.jgit.dircache.{DirCache, DirCacheBuilder}
import org.eclipse.jgit.lib.{Repository => _, _} import org.eclipse.jgit.lib.{Repository => _, _}
import org.eclipse.jgit.transport.{ReceiveCommand, ReceivePack}
import scala.util.Using import scala.util.Using
trait RepositoryService { trait RepositoryService {
@@ -119,15 +115,6 @@ trait RepositoryService {
} }
.update(newUserName, newRepositoryName) .update(newUserName, newRepositoryName)
// Updates activity fk before deleting repository because activity is sorted by activityId
// and it can't be changed by deleting-and-inserting record.
Activities.filter(_.byRepository(oldUserName, oldRepositoryName)).list.foreach { activity =>
Activities
.filter(_.activityId === activity.activityId.bind)
.map(x => (x.userName, x.repositoryName))
.update(newUserName, newRepositoryName)
}
deleteRepositoryOnModel(oldUserName, oldRepositoryName) deleteRepositoryOnModel(oldUserName, oldRepositoryName)
RepositoryWebHooks.insertAll( RepositoryWebHooks.insertAll(
@@ -213,50 +200,6 @@ trait RepositoryService {
collaborators.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)): _* collaborators.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)): _*
) )
// Update activity messages
Activities
.filter { t =>
(t.message like s"%:${oldUserName}/${oldRepositoryName}]%") ||
(t.message like s"%:${oldUserName}/${oldRepositoryName}#%") ||
(t.message like s"%:${oldUserName}/${oldRepositoryName}@%")
}
.map { t =>
t.activityId -> t.message
}
.list
.foreach {
case (activityId, message) =>
Activities
.filter(_.activityId === activityId.bind)
.map(_.message)
.update(
message
.replace(
s"[repo:${oldUserName}/${oldRepositoryName}]",
s"[repo:${newUserName}/${newRepositoryName}]"
)
.replace(
s"[branch:${oldUserName}/${oldRepositoryName}#",
s"[branch:${newUserName}/${newRepositoryName}#"
)
.replace(
s"[tag:${oldUserName}/${oldRepositoryName}#",
s"[tag:${newUserName}/${newRepositoryName}#"
)
.replace(
s"[pullreq:${oldUserName}/${oldRepositoryName}#",
s"[pullreq:${newUserName}/${newRepositoryName}#"
)
.replace(
s"[issue:${oldUserName}/${oldRepositoryName}#",
s"[issue:${newUserName}/${newRepositoryName}#"
)
.replace(
s"[commit:${oldUserName}/${oldRepositoryName}@",
s"[commit:${newUserName}/${newRepositoryName}@"
)
)
}
// Move git repository // Move git repository
defining(getRepositoryDir(oldUserName, oldRepositoryName)) { dir => defining(getRepositoryDir(oldUserName, oldRepositoryName)) { dir =>
if (dir.isDirectory) { if (dir.isDirectory) {
@@ -304,7 +247,7 @@ trait RepositoryService {
} }
private def deleteRepositoryOnModel(userName: String, repositoryName: String)(implicit s: Session): Unit = { private def deleteRepositoryOnModel(userName: String, repositoryName: String)(implicit s: Session): Unit = {
Activities.filter(_.byRepository(userName, repositoryName)).delete // Activities.filter(_.byRepository(userName, repositoryName)).delete
Collaborators.filter(_.byRepository(userName, repositoryName)).delete Collaborators.filter(_.byRepository(userName, repositoryName)).delete
CommitComments.filter(_.byRepository(userName, repositoryName)).delete CommitComments.filter(_.byRepository(userName, repositoryName)).delete
IssueLabels.filter(_.byRepository(userName, repositoryName)).delete IssueLabels.filter(_.byRepository(userName, repositoryName)).delete

View File

@@ -1,9 +1,11 @@
package gitbucket.core.service package gitbucket.core.service
import gitbucket.core.model.{Session, Issue, Account} import gitbucket.core.model.{Account, Issue, Repository, Session}
import gitbucket.core.util.Implicits import gitbucket.core.util.Implicits
import gitbucket.core.controller.Context import gitbucket.core.controller.Context
import Implicits.request2Session import Implicits.request2Session
import gitbucket.core.model.Profile.{Accounts, Repositories}
import gitbucket.core.model.Profile.profile.blockingApi._
/** /**
* This service is used for a view helper mainly. * This service is used for a view helper mainly.
@@ -23,21 +25,41 @@ trait RequestCache
private implicit def context2Session(implicit context: Context): Session = private implicit def context2Session(implicit context: Context): Session =
request2Session(context.request) request2Session(context.request)
def getIssue(userName: String, repositoryName: String, issueId: String)(implicit context: Context): Option[Issue] = { def getIssueFromCache(userName: String, repositoryName: String, issueId: String)(
implicit context: Context
): Option[Issue] = {
context.cache(s"issue.${userName}/${repositoryName}#${issueId}") { context.cache(s"issue.${userName}/${repositoryName}#${issueId}") {
super.getIssue(userName, repositoryName, issueId) super.getIssue(userName, repositoryName, issueId)
} }
} }
def getAccountByUserName(userName: String)(implicit context: Context): Option[Account] = { def getAccountByUserNameFromCache(userName: String)(implicit context: Context): Option[Account] = {
context.cache(s"account.${userName}") { context.cache(s"account.${userName}") {
super.getAccountByUserName(userName) super.getAccountByUserName(userName)
} }
} }
def getAccountByMailAddress(mailAddress: String)(implicit context: Context): Option[Account] = { def getAccountByMailAddressFromCache(mailAddress: String)(implicit context: Context): Option[Account] = {
context.cache(s"account.${mailAddress}") { context.cache(s"account.${mailAddress}") {
super.getAccountByMailAddress(mailAddress) super.getAccountByMailAddress(mailAddress)
} }
} }
def getRepositoryInfoFromCache(userName: String, repositoryName: String)(
implicit context: Context
): Option[Repository] = {
context.cache(s"repository.${userName}/${repositoryName}") {
Repositories
.join(Accounts)
.on(_.userName === _.userName)
.filter {
case (t1, t2) =>
t1.byRepository(userName, repositoryName) && t2.removed === false.bind
}
.map {
case (t1, t2) => t1
}
.firstOption
}
}
} }

View File

@@ -240,7 +240,8 @@ class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl:
with WebHookPullRequestService with WebHookPullRequestService
with WebHookPullRequestReviewCommentService with WebHookPullRequestReviewCommentService
with CommitsService with CommitsService
with SystemSettingsService { with SystemSettingsService
with RequestCache {
private val logger = LoggerFactory.getLogger(classOf[CommitLogHook]) private val logger = LoggerFactory.getLogger(classOf[CommitLogHook])
private var existIds: Seq[String] = Nil private var existIds: Seq[String] = Nil

View File

@@ -2,11 +2,10 @@ package gitbucket.core.servlet
import java.io.{File, FileOutputStream} import java.io.{File, FileOutputStream}
import akka.event.Logging
import com.typesafe.config.ConfigFactory import com.typesafe.config.ConfigFactory
import gitbucket.core.GitBucketCoreModule import gitbucket.core.GitBucketCoreModule
import gitbucket.core.plugin.PluginRegistry import gitbucket.core.plugin.PluginRegistry
import gitbucket.core.service.{ActivityService, SystemSettingsService} import gitbucket.core.service.SystemSettingsService
import gitbucket.core.util.DatabaseConfig import gitbucket.core.util.DatabaseConfig
import gitbucket.core.util.Directory._ import gitbucket.core.util.Directory._
import gitbucket.core.util.JDBCUtil._ import gitbucket.core.util.JDBCUtil._
@@ -21,8 +20,6 @@ import javax.servlet.{ServletContextEvent, ServletContextListener}
import org.apache.commons.io.{FileUtils, IOUtils} import org.apache.commons.io.{FileUtils, IOUtils}
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import akka.actor.{Actor, ActorSystem, Props}
import com.typesafe.akka.extension.quartz.QuartzSchedulerExtension
import scala.jdk.CollectionConverters._ import scala.jdk.CollectionConverters._
import scala.util.Using import scala.util.Using
@@ -35,23 +32,23 @@ class InitializeListener extends ServletContextListener with SystemSettingsServi
private val logger = LoggerFactory.getLogger(classOf[InitializeListener]) private val logger = LoggerFactory.getLogger(classOf[InitializeListener])
// ActorSystem for Quartz scheduler // // ActorSystem for Quartz scheduler
private val system = ActorSystem( // private val system = ActorSystem(
"job", // "job",
ConfigFactory.parseString(""" // ConfigFactory.parseString("""
|akka { // |akka {
| daemonic = on // | daemonic = on
| coordinated-shutdown.run-by-jvm-shutdown-hook = off // | coordinated-shutdown.run-by-jvm-shutdown-hook = off
| quartz { // | quartz {
| schedules { // | schedules {
| Daily { // | Daily {
| expression = "0 0 0 * * ?" // | expression = "0 0 0 * * ?"
| } // | }
| } // | }
| } // | }
|} // |}
""".stripMargin) // """.stripMargin)
) // )
override def contextInitialized(event: ServletContextEvent): Unit = { override def contextInitialized(event: ServletContextEvent): Unit = {
val dataDir = event.getServletContext.getInitParameter("gitbucket.home") val dataDir = event.getServletContext.getInitParameter("gitbucket.home")
@@ -95,10 +92,10 @@ class InitializeListener extends ServletContextListener with SystemSettingsServi
PluginRegistry.initialize(event.getServletContext, loadSystemSettings(), conn) PluginRegistry.initialize(event.getServletContext, loadSystemSettings(), conn)
} }
// Start Quartz scheduler // // Start Quartz scheduler
val scheduler = QuartzSchedulerExtension(system) // val scheduler = QuartzSchedulerExtension(system)
//
scheduler.schedule("Daily", system.actorOf(Props[DeleteOldActivityActor]), "DeleteOldActivity") // scheduler.schedule("Daily", system.actorOf(Props[DeleteOldActivityActor]), "DeleteOldActivity")
} }
private def checkVersion(manager: JDBCVersionManager, conn: java.sql.Connection): Unit = { private def checkVersion(manager: JDBCVersionManager, conn: java.sql.Connection): Unit = {
@@ -172,8 +169,8 @@ class InitializeListener extends ServletContextListener with SystemSettingsServi
} }
override def contextDestroyed(event: ServletContextEvent): Unit = { override def contextDestroyed(event: ServletContextEvent): Unit = {
// Shutdown Quartz scheduler // // Shutdown Quartz scheduler
system.terminate() // system.terminate()
// Shutdown plugins // Shutdown plugins
PluginRegistry.shutdown(event.getServletContext, loadSystemSettings()) PluginRegistry.shutdown(event.getServletContext, loadSystemSettings())
// Close datasource // Close datasource
@@ -181,21 +178,3 @@ class InitializeListener extends ServletContextListener with SystemSettingsServi
} }
} }
class DeleteOldActivityActor extends Actor with SystemSettingsService with ActivityService {
private val logger = Logging(context.system, this)
def receive = {
case s: String => {
loadSystemSettings().activityLogLimit.foreach { limit =>
if (limit > 0) {
Database() withTransaction { implicit session =>
val rows = deleteOldActivities(limit)
logger.info(s"Deleted ${rows} activity logs")
}
}
}
}
}
}

View File

@@ -29,6 +29,8 @@ object Directory {
val GitBucketConf = new File(GitBucketHome, "gitbucket.conf") val GitBucketConf = new File(GitBucketHome, "gitbucket.conf")
val ActivityLog = new File(GitBucketHome, "activity.log")
val RepositoryHome = s"${GitBucketHome}/repositories" val RepositoryHome = s"${GitBucketHome}/repositories"
val DatabaseHome = s"${GitBucketHome}/data" val DatabaseHome = s"${GitBucketHome}/data"

View File

@@ -17,7 +17,7 @@ trait AvatarImageProvider { self: RequestCache =>
val src = if (mailAddress.isEmpty) { val src = if (mailAddress.isEmpty) {
// by user name // by user name
getAccountByUserName(userName).map { account => getAccountByUserNameFromCache(userName).map { account =>
if (account.image.isEmpty && context.settings.gravatar) { if (account.image.isEmpty && context.settings.gravatar) {
s"""https://www.gravatar.com/avatar/${StringUtil.md5(account.mailAddress.toLowerCase)}?s=${size}&d=retro&r=g""" s"""https://www.gravatar.com/avatar/${StringUtil.md5(account.mailAddress.toLowerCase)}?s=${size}&d=retro&r=g"""
} else { } else {
@@ -28,7 +28,7 @@ trait AvatarImageProvider { self: RequestCache =>
} }
} else { } else {
// by mail address // by mail address
getAccountByMailAddress(mailAddress).map { account => getAccountByMailAddressFromCache(mailAddress).map { account =>
if (account.image.isEmpty && context.settings.gravatar) { if (account.image.isEmpty && context.settings.gravatar) {
s"""https://www.gravatar.com/avatar/${StringUtil.md5(account.mailAddress.toLowerCase)}?s=${size}&d=retro&r=g""" s"""https://www.gravatar.com/avatar/${StringUtil.md5(account.mailAddress.toLowerCase)}?s=${size}&d=retro&r=g"""
} else { } else {

View File

@@ -16,7 +16,7 @@ trait LinkConverter { self: RequestCache =>
val userName = repository.repository.userName val userName = repository.repository.userName
val repositoryName = repository.repository.repositoryName val repositoryName = repository.repository.repositoryName
getIssue(userName, repositoryName, issueId.toString) match { getIssueFromCache(userName, repositoryName, issueId.toString) match {
case Some(issue) => case Some(issue) =>
s"""<a href="${context.path}/${userName}/${repositoryName}/${if (issue.isPullRequest) "pull" else "issues"}/${issueId}"><strong>${StringUtil s"""<a href="${context.path}/${userName}/${repositoryName}/${if (issue.isPullRequest) "pull" else "issues"}/${issueId}"><strong>${StringUtil
.escapeHtml(title)}</strong> #${issueId}</a>""" .escapeHtml(title)}</strong> #${issueId}</a>"""
@@ -43,7 +43,7 @@ trait LinkConverter { self: RequestCache =>
escaped escaped
// convert username/project@SHA to link // convert username/project@SHA to link
.replaceBy("(?<=(^|\\W))([a-zA-Z0-9\\-_]+)/([a-zA-Z0-9\\-_\\.]+)@([a-f0-9]{40})(?=(\\W|$))".r) { m => .replaceBy("(?<=(^|\\W))([a-zA-Z0-9\\-_]+)/([a-zA-Z0-9\\-_\\.]+)@([a-f0-9]{40})(?=(\\W|$))".r) { m =>
getAccountByUserName(m.group(2)).map { _ => getAccountByUserNameFromCache(m.group(2)).map { _ =>
s"""<code><a href="${context.path}/${m.group(2)}/${m.group(3)}/commit/${m.group(4)}">${m.group(2)}/${m.group( s"""<code><a href="${context.path}/${m.group(2)}/${m.group(3)}/commit/${m.group(4)}">${m.group(2)}/${m.group(
3 3
)}@${m.group(4).substring(0, 7)}</a></code>""" )}@${m.group(4).substring(0, 7)}</a></code>"""
@@ -53,7 +53,7 @@ trait LinkConverter { self: RequestCache =>
// convert username/project#Num to link // convert username/project#Num to link
.replaceBy(("(?<=(^|\\W))([a-zA-Z0-9\\-_]+)/([a-zA-Z0-9\\-_\\.]+)" + issueIdPrefix + "([0-9]+)(?=(\\W|$))").r) { .replaceBy(("(?<=(^|\\W))([a-zA-Z0-9\\-_]+)/([a-zA-Z0-9\\-_\\.]+)" + issueIdPrefix + "([0-9]+)(?=(\\W|$))").r) {
m => m =>
getIssue(m.group(2), m.group(3), m.group(4)) match { getIssueFromCache(m.group(2), m.group(3), m.group(4)) match {
case Some(issue) if (issue.isPullRequest) => case Some(issue) if (issue.isPullRequest) =>
Some(s"""<a href="${context.path}/${m.group(2)}/${m.group(3)}/pull/${m.group(4)}">${m.group(2)}/${m.group( Some(s"""<a href="${context.path}/${m.group(2)}/${m.group(3)}/pull/${m.group(4)}">${m.group(2)}/${m.group(
3 3
@@ -68,7 +68,7 @@ trait LinkConverter { self: RequestCache =>
// convert username@SHA to link // convert username@SHA to link
.replaceBy(("(?<=(^|\\W))([a-zA-Z0-9\\-_]+)@([a-f0-9]{40})(?=(\\W|$))").r) { m => .replaceBy(("(?<=(^|\\W))([a-zA-Z0-9\\-_]+)@([a-f0-9]{40})(?=(\\W|$))").r) { m =>
getAccountByUserName(m.group(2)).map { _ => getAccountByUserNameFromCache(m.group(2)).map { _ =>
s"""<code><a href="${context.path}/${m.group(2)}/${repository.name}/commit/${m.group(3)}">${m.group(2)}@${m s"""<code><a href="${context.path}/${m.group(2)}/${repository.name}/commit/${m.group(3)}">${m.group(2)}@${m
.group(3) .group(3)
.substring(0, 7)}</a></code>""" .substring(0, 7)}</a></code>"""
@@ -77,7 +77,7 @@ trait LinkConverter { self: RequestCache =>
// convert username#Num to link // convert username#Num to link
.replaceBy(("(?<=(^|\\W))([a-zA-Z0-9\\-_]+)" + issueIdPrefix + "([0-9]+)(?=(\\W|$))").r) { m => .replaceBy(("(?<=(^|\\W))([a-zA-Z0-9\\-_]+)" + issueIdPrefix + "([0-9]+)(?=(\\W|$))").r) { m =>
getIssue(m.group(2), repository.name, m.group(3)) match { getIssueFromCache(m.group(2), repository.name, m.group(3)) match {
case Some(issue) if (issue.isPullRequest) => case Some(issue) if (issue.isPullRequest) =>
Some(s"""<a href="${context.path}/${m.group(2)}/${repository.name}/pull/${m.group(3)}">${m.group(2)}#${m Some(s"""<a href="${context.path}/${m.group(2)}/${repository.name}/pull/${m.group(3)}">${m.group(2)}#${m
.group(3)}</a>""") .group(3)}</a>""")
@@ -92,7 +92,7 @@ trait LinkConverter { self: RequestCache =>
// convert issue id to link // convert issue id to link
.replaceBy(("(?<=(^|\\W))(GH-|(?<!&)" + issueIdPrefix + ")([0-9]+)(?=(\\W|$))").r) { m => .replaceBy(("(?<=(^|\\W))(GH-|(?<!&)" + issueIdPrefix + ")([0-9]+)(?=(\\W|$))").r) { m =>
val prefix = if (m.group(2) == "issue:") "#" else m.group(2) val prefix = if (m.group(2) == "issue:") "#" else m.group(2)
getIssue(repository.owner, repository.name, m.group(3)) match { getIssueFromCache(repository.owner, repository.name, m.group(3)) match {
case Some(issue) if (issue.isPullRequest) => case Some(issue) if (issue.isPullRequest) =>
Some(s"""<a href="${context.path}/${repository.owner}/${repository.name}/pull/${m.group(3)}">${prefix}${m Some(s"""<a href="${context.path}/${repository.owner}/${repository.name}/pull/${m.group(3)}">${prefix}${m
.group(3)}</a>""") .group(3)}</a>""")
@@ -106,7 +106,7 @@ trait LinkConverter { self: RequestCache =>
// convert @username to link // convert @username to link
.replaceBy("(?<=(^|\\W))@([a-zA-Z0-9\\-_\\.]+)(?=(\\W|$))".r) { m => .replaceBy("(?<=(^|\\W))@([a-zA-Z0-9\\-_\\.]+)(?=(\\W|$))".r) { m =>
getAccountByUserName(m.group(2)).map { _ => getAccountByUserNameFromCache(m.group(2)).map { _ =>
s"""<a href="${context.path}/${m.group(2)}">@${m.group(2)}</a>""" s"""<a href="${context.path}/${m.group(2)}">@${m.group(2)}</a>"""
} }
} }

View File

@@ -3,7 +3,6 @@ package gitbucket.core.view
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.{Date, Locale, TimeZone} import java.util.{Date, Locale, TimeZone}
import com.nimbusds.jose.util.JSONObjectUtils
import gitbucket.core.controller.Context import gitbucket.core.controller.Context
import gitbucket.core.model.CommitState import gitbucket.core.model.CommitState
import gitbucket.core.model.PullRequest import gitbucket.core.model.PullRequest
@@ -196,7 +195,7 @@ object helpers extends AvatarImageProvider with LinkConverter with RequestCache
import scala.util.matching.Regex._ import scala.util.matching.Regex._
implicit class RegexReplaceString(private val s: String) extends AnyVal { implicit class RegexReplaceString(private val s: String) extends AnyVal {
def replaceAll(pattern: String, replacer: (Match) => String): String = { def replaceAll(pattern: String)(replacer: Match => String): String = {
pattern.r.replaceAllIn(s, (m: Match) => replacer(m).replace("$", "\\$")) pattern.r.replaceAllIn(s, (m: Match) => replacer(m).replace("$", "\\$"))
} }
} }
@@ -204,50 +203,64 @@ object helpers extends AvatarImageProvider with LinkConverter with RequestCache
/** /**
* Convert link notations in the activity message. * Convert link notations in the activity message.
*/ */
// format: off
def activityMessage(message: String)(implicit context: Context): Html = def activityMessage(message: String)(implicit context: Context): Html =
Html( Html(
message message
.replaceAll( .replaceAll("\\[issue:([^\\s]+?)/([^\\s]+?)#((\\d+))\\]"){ m =>
"\\[issue:([^\\s]+?)/([^\\s]+?)#((\\d+))\\]", if (getRepositoryInfoFromCache(m.group(1), m.group(2)).isDefined) {
s"""<a href="${context.path}/$$1/$$2/issues/$$3">$$1/$$2#$$3</a>""" s"""<a href="${context.path}/${m.group(1)}/${m.group(2)}/issues/${m.group(3)}">${m.group(1)}/${m.group(2)}#${m.group(3)}</a>"""
) } else {
.replaceAll( s"${m.group(1)}/${m.group(2)}#${m.group(3)}"
"\\[pullreq:([^\\s]+?)/([^\\s]+?)#((\\d+))\\]", }
s"""<a href="${context.path}/$$1/$$2/pull/$$3">$$1/$$2#$$3</a>""" }
) .replaceAll("\\[pullreq:([^\\s]+?)/([^\\s]+?)#((\\d+))\\]"){ m =>
.replaceAll("\\[repo:([^\\s]+?)/([^\\s]+?)\\]", s"""<a href="${context.path}/$$1/$$2\">$$1/$$2</a>""") if (getRepositoryInfoFromCache(m.group(1), m.group(2)).isDefined) {
.replaceAll( s"""<a href="${context.path}/${m.group(1)}/${m.group(2)}/pull/${m.group(3)}">${m.group(1)}/${m.group(2)}#${m.group(3)}</a>"""
"\\[branch:([^\\s]+?)/([^\\s]+?)#([^\\s]+?)\\]", } else {
(m: Match) => s"${m.group(1)}/${m.group(2)}#${m.group(3)}"
s"""<a href="${context.path}/${m.group(1)}/${m.group(2)}/tree/${encodeRefName(m.group(3))}">${StringUtil }
.escapeHtml( }
m.group(3) .replaceAll("\\[repo:([^\\s]+?)/([^\\s]+?)\\]") { m =>
)}</a>""" if (getRepositoryInfoFromCache(m.group(1), m.group(2)).isDefined) {
) s"""<a href="${context.path}/${m.group(1)}/${m.group(2)}">${m.group(1)}/${m.group(2)}</a>"""
.replaceAll( } else {
"\\[tag:([^\\s]+?)/([^\\s]+?)#([^\\s]+?)\\]", s"${m.group(1)}/${m.group(2)}"
(m: Match) => }
s"""<a href="${context.path}/${m.group(1)}/${m.group(2)}/tree/${encodeRefName(m.group(3))}">${StringUtil }
.escapeHtml( .replaceAll("\\[branch:([^\\s]+?)/([^\\s]+?)#([^\\s]+?)\\]") { m =>
m.group(3) if (getRepositoryInfoFromCache(m.group(1), m.group(2)).isDefined) {
)}</a>""" s"""<a href="${context.path}/${m.group(1)}/${m.group(2)}/tree/${encodeRefName(m.group(3))}">${StringUtil.escapeHtml(m.group(3))}</a>"""
) } else {
.replaceAll("\\[user:([^\\s]+?)\\]", (m: Match) => user(m.group(1)).body) StringUtil.escapeHtml(m.group(3))
.replaceAll( }
"\\[commit:([^\\s]+?)/([^\\s]+?)\\@([^\\s]+?)\\]", }
(m: Match) => .replaceAll("\\[tag:([^\\s]+?)/([^\\s]+?)#([^\\s]+?)\\]") { m =>
s"""<a href="${context.path}/${m.group(1)}/${m.group(2)}/commit/${m.group(3)}">${m.group(1)}/${m if (getRepositoryInfoFromCache(m.group(1), m.group(2)).isDefined) {
.group(2)}@${m.group(3).substring(0, 7)}</a>""" s"""<a href="${context.path}/${m.group(1)}/${m.group(2)}/tree/${encodeRefName(m.group(3))}">${StringUtil.escapeHtml(m.group(3))}</a>"""
) } else {
.replaceAll( StringUtil.escapeHtml(m.group(3))
"\\[release:([^\\s]+?)/([^\\s]+?)/([^\\s]+?):(.+)\\]", }
(m: Match) => }
s"""<a href="${context.path}/${m.group(1)}/${m.group(2)}/releases/${encodeRefName(m.group(3))}">${StringUtil .replaceAll("\\[user:([^\\s]+?)\\]") { m =>
.escapeHtml( user(m.group(1)).body
m.group(4) }
)}</a>""" .replaceAll("\\[commit:([^\\s]+?)/([^\\s]+?)\\@([^\\s]+?)\\]") { m =>
) if (getRepositoryInfoFromCache(m.group(1), m.group(2)).isDefined) {
s"""<a href="${context.path}/${m.group(1)}/${m.group(2)}/commit/${m.group(3)}">${m.group(1)}/${m.group(2)}@${m.group(3).substring(0, 7)}</a>"""
} else {
s"${m.group(1)}/${m.group(2)}@${m.group(3).substring(0, 7)}"
}
}
.replaceAll("\\[release:([^\\s]+?)/([^\\s]+?)/([^\\s]+?):(.+)\\]") { m =>
if (getRepositoryInfoFromCache(m.group(1), m.group(2)).isDefined) {
s"""<a href="${context.path}/${m.group(1)}/${m.group(2)}/releases/${encodeRefName(m.group(3))}">${StringUtil.escapeHtml(m.group(4))}</a>"""
} else {
StringUtil.escapeHtml(m.group(4))
}
}
) )
// format: off
/** /**
* Remove html tags from the given Html instance. * Remove html tags from the given Html instance.
@@ -333,9 +346,9 @@ object helpers extends AvatarImageProvider with LinkConverter with RequestCache
content: Html content: Html
)(implicit context: Context): Html = )(implicit context: Context): Html =
(if (mailAddress.isEmpty) { (if (mailAddress.isEmpty) {
getAccountByUserName(userName) getAccountByUserNameFromCache(userName)
} else { } else {
getAccountByMailAddress(mailAddress) getAccountByMailAddressFromCache(mailAddress)
}).map { account => }).map { account =>
Html(s"""<a href="${url(account.userName)}" class="${styleClass}">${content}</a>""") Html(s"""<a href="${url(account.userName)}" class="${styleClass}">${content}</a>""")
} getOrElse content } getOrElse content

View File

@@ -7,21 +7,24 @@
@activities.map { activity => @activities.map { activity =>
<div class="block"> <div class="block">
@(activity.activityType match { @(activity.activityType match {
case "open_issue" => detailActivity(activity, "issue-opened") case "open_issue" => simpleActivity(activity)
case "comment_issue" => detailActivity(activity, "comment-discussion") case "comment_issue" => simpleActivity(activity)
case "comment_commit" => detailActivity(activity, "comment-discussion") case "comment_commit" => simpleActivity(activity)
case "close_issue" => detailActivity(activity, "issue-closed") case "close_issue" => simpleActivity(activity)
case "reopen_issue" => detailActivity(activity, "issue-reopened") case "reopen_issue" => simpleActivity(activity)
case "open_pullreq" => detailActivity(activity, "git-pull-request") case "open_pullreq" => simpleActivity(activity)
case "merge_pullreq" => detailActivity(activity, "git-merge") case "merge_pullreq" => simpleActivity(activity)
case "release" => detailActivity(activity, "package") case "release" => simpleActivity(activity)
case "create_repository" => simpleActivity(activity, "repo") case "create_repository" => simpleActivity(activity)
case "create_branch" => simpleActivity(activity, "git-branch") case "delete_repository" => simpleActivity(activity)
case "delete_branch" => simpleActivity(activity, "circle-slash") case "rename_repository" => simpleActivity(activity)
case "create_tag" => simpleActivity(activity, "tag") case "transfer_repository" => simpleActivity(activity)
case "delete_tag" => simpleActivity(activity, "circle-slash") case "create_branch" => simpleActivity(activity)
case "fork" => simpleActivity(activity, "repo-forked") case "delete_branch" => simpleActivity(activity)
case "push" => customActivity(activity, "git-commit"){ case "create_tag" => simpleActivity(activity)
case "delete_tag" => simpleActivity(activity)
case "fork" => simpleActivity(activity)
case "push" => customActivity(activity){
<div class="small activity-message"> <div class="small activity-message">
{activity.additionalInfo.get.split("\n").reverse.take(4).zipWithIndex.map{ case (commit, i) => {activity.additionalInfo.get.split("\n").reverse.take(4).zipWithIndex.map{ case (commit, i) =>
if(i == 3){ if(i == 3){
@@ -37,12 +40,12 @@
}} }}
</div> </div>
} }
case "create_wiki" => customActivity(activity, "book"){ case "create_wiki" => customActivity(activity){
<div class="small activity-message"> <div class="small activity-message">
Created <a href={s"${context.path}/${activity.userName}/${activity.repositoryName}/wiki/${activity.additionalInfo.get}"}>{activity.additionalInfo.get}</a>. Created <a href={s"${context.path}/${activity.userName}/${activity.repositoryName}/wiki/${activity.additionalInfo.get}"}>{activity.additionalInfo.get}</a>.
</div> </div>
} }
case "edit_wiki" => customActivity(activity, "book"){ case "edit_wiki" => customActivity(activity){
activity.additionalInfo.get.split(":") match { activity.additionalInfo.get.split(":") match {
case Array(pageName, commitId) => case Array(pageName, commitId) =>
<div class="small activity-message"> <div class="small activity-message">
@@ -60,26 +63,7 @@
} }
} }
@detailActivity(activity: gitbucket.core.model.Activity, image: String) = { @customActivity(activity: gitbucket.core.model.Activity)(additionalInfo: Any) = {
@*
<div class="activity-icon-large"><i class="mega-octicon octicon-@image"></i></div>
*@
<div>
<div class="muted small">@gitbucket.core.helper.html.datetimeago(activity.activityDate)</div>
<div class="strong">
@helpers.avatarLink(activity.activityUserName, 16)
@helpers.activityMessage(activity.message)
</div>
@activity.additionalInfo.map { additionalInfo =>
<div class=" activity-message">@additionalInfo</div>
}
</div>
}
@customActivity(activity: gitbucket.core.model.Activity, image: String)(additionalInfo: Any) = {
@*
<div class="activity-icon-large"><i class="mega-octicon octicon-@image"></i></div>
*@
<div> <div>
<div class="muted small">@gitbucket.core.helper.html.datetimeago(activity.activityDate)</div> <div class="muted small">@gitbucket.core.helper.html.datetimeago(activity.activityDate)</div>
<div class="strong"> <div class="strong">
@@ -90,10 +74,7 @@
</div> </div>
} }
@simpleActivity(activity: gitbucket.core.model.Activity, image: String) = { @simpleActivity(activity: gitbucket.core.model.Activity) = {
@*
<div class="activity-icon-small"><i class="octicon octicon-@image"></i></div>
*@
<div> <div>
<span class="muted small">@gitbucket.core.helper.html.datetimeago(activity.activityDate)</span> <span class="muted small">@gitbucket.core.helper.html.datetimeago(activity.activityDate)</span>
<div> <div>

View File

@@ -14,7 +14,7 @@ import scala.util.Using
class MergeServiceSpec extends FunSpec { class MergeServiceSpec extends FunSpec {
val service = new MergeService with AccountService with ActivityService with IssuesService with LabelsService val service = new MergeService with AccountService with ActivityService with IssuesService with LabelsService
with MilestonesService with RepositoryService with PrioritiesService with PullRequestService with CommitsService with MilestonesService with RepositoryService with PrioritiesService with PullRequestService with CommitsService
with WebHookPullRequestService with WebHookPullRequestReviewCommentService {} with WebHookPullRequestService with WebHookPullRequestReviewCommentService with RequestCache {}
val branch = "master" val branch = "master"
val issueId = 10 val issueId = 10
def initRepository(owner: String, name: String): File = { def initRepository(owner: String, name: String): File = {

View File

@@ -18,7 +18,8 @@ class PullRequestServiceSpec
with PrioritiesService with PrioritiesService
with WebHookService with WebHookService
with WebHookPullRequestService with WebHookPullRequestService
with WebHookPullRequestReviewCommentService { with WebHookPullRequestReviewCommentService
with RequestCache {
def swap(r: (Issue, PullRequest)) = (r._2 -> r._1) def swap(r: (Issue, PullRequest)) = (r._2 -> r._1)

View File

@@ -99,7 +99,7 @@ trait ServiceSpecBase extends MockitoSugar {
lazy val dummyService = new RepositoryService with AccountService with ActivityService with IssuesService lazy val dummyService = new RepositoryService with AccountService with ActivityService with IssuesService
with MergeService with PullRequestService with CommitsService with CommitStatusService with LabelsService with MergeService with PullRequestService with CommitsService with CommitStatusService with LabelsService
with MilestonesService with PrioritiesService with WebHookService with WebHookPullRequestService with MilestonesService with PrioritiesService with WebHookService with WebHookPullRequestService
with WebHookPullRequestReviewCommentService { with WebHookPullRequestReviewCommentService with RequestCache {
override def fetchAsPullRequest( override def fetchAsPullRequest(
userName: String, userName: String,
repositoryName: String, repositoryName: String,

View File

@@ -7,7 +7,7 @@ import gitbucket.core.model.WebHookContentType
class WebHookServiceSpec extends FunSuite with ServiceSpecBase { class WebHookServiceSpec extends FunSuite with ServiceSpecBase {
lazy val service = new WebHookPullRequestService with AccountService with ActivityService with RepositoryService lazy val service = new WebHookPullRequestService with AccountService with ActivityService with RepositoryService
with MergeService with PullRequestService with IssuesService with CommitsService with LabelsService with MergeService with PullRequestService with IssuesService with CommitsService with LabelsService
with MilestonesService with PrioritiesService with WebHookPullRequestReviewCommentService with MilestonesService with PrioritiesService with WebHookPullRequestReviewCommentService with RequestCache
test("WebHookPullRequestService.getPullRequestsByRequestForWebhook") { test("WebHookPullRequestService.getPullRequestsByRequestForWebhook") {
withTestDB { implicit session => withTestDB { implicit session =>

View File

@@ -169,8 +169,9 @@ class AvatarImageProviderSpec extends FunSpec with MockitoSugar {
context: Context context: Context
): Html = getAvatarImageHtml(userName, size, mailAddress, tooltip) ): Html = getAvatarImageHtml(userName, size, mailAddress, tooltip)
override def getAccountByMailAddress(mailAddress: String)(implicit context: Context): Option[Account] = account override def getAccountByMailAddressFromCache(mailAddress: String)(implicit context: Context): Option[Account] =
override def getAccountByUserName(userName: String)(implicit context: Context): Option[Account] = account account
override def getAccountByUserNameFromCache(userName: String)(implicit context: Context): Option[Account] = account
} }
} }