Compare commits

..

1 Commits

Author SHA1 Message Date
Naoki Takezoe
f16f395539 Disable GitHub Actions cache 2020-07-06 09:59:59 +09:00
35 changed files with 544 additions and 797 deletions

View File

@@ -14,16 +14,6 @@ jobs:
java: [8, 11] java: [8, 11]
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Cache
uses: actions/cache@v2
env:
cache-name: cache-sbt-libs
with:
path: |
~/.ivy2/cache
~/.sbt
~/.cache/coursier/v1
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
with: with:

View File

@@ -1,22 +1,6 @@
# 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,20 +57,11 @@ Support
What's New in 4.33.x What's New in 4.33.x
------------- -------------
### 4.34.0 - 26 Jul 2020 ### 4.33.0 - 31 Dec 2019
- Enhancement admin settings UI - All CLI options are configurable by environment variables
- File upload settings - Folding pull request files
- Restrict repository operations - WebHook security options
- User-defined CSS - Add assignee and assignees properties to some Web APIs' response
- 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.34.0" val GitBucketVersion = "4.33.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,9 +54,11 @@ 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.9.0 notifications:1.8.0
gist:4.19.0 gist:4.18.0
emoji:4.6.0 emoji:4.6.0
pages:1.8.0 pages:1.8.0

View File

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

View File

@@ -1,22 +1,7 @@
package gitbucket.core package gitbucket.core
import java.io.FileOutputStream import io.github.gitbucket.solidbase.migration.{SqlMigration, LiquibaseMigration}
import java.nio.charset.StandardCharsets import io.github.gitbucket.solidbase.model.{Version, Module}
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(
@@ -80,38 +65,5 @@ 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,7 +35,6 @@ 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,7 +52,6 @@ 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,7 +22,6 @@ 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,7 +29,6 @@ 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
@@ -79,7 +78,7 @@ trait IndexControllerBase extends ControllerBase {
} }
.getOrElse { .getOrElse {
gitbucket.core.html.index( gitbucket.core.html.index(
getRecentPublicActivities(), getRecentActivities(),
getVisibleRepositories(None, withoutPhysicalInfo = true), getVisibleRepositories(None, withoutPhysicalInfo = true),
showBannerToCreatePersonalAccessToken = false showBannerToCreatePersonalAccessToken = false
) )
@@ -162,7 +161,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(getRecentPublicActivities()) xml.feed(getRecentActivities())
} }
post("/sidebar-collapse") { post("/sidebar-collapse") {

View File

@@ -30,7 +30,6 @@ 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,7 +36,6 @@ 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,14 +2,7 @@ package gitbucket.core.controller
import java.io.File import java.io.File
import gitbucket.core.service.{ import gitbucket.core.service.{AccountService, ActivityService, PaginationHelper, ReleaseService, RepositoryService}
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._
@@ -29,7 +22,6 @@ 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,10 +30,8 @@ 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
@@ -42,7 +40,6 @@ 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 =>
@@ -100,7 +97,9 @@ 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)
@@ -252,10 +251,9 @@ 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]] = def _headers(h: Array[org.apache.http.Header]): Array[Array[String]] = h.map { h =>
h.map { h => Array(h.getName, h.getValue)
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 =>
@@ -373,15 +371,7 @@ 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()
@@ -394,15 +384,7 @@ 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()
@@ -453,34 +435,32 @@ trait RepositorySettingsControllerBase extends ControllerBase {
/** /**
* Provides duplication check for web hook url. * Provides duplication check for web hook url.
*/ */
private def webHook(needExists: Boolean): Constraint = private def webHook(needExists: Boolean): Constraint = new Constraint() {
new Constraint() { override def validate(name: String, value: String, messages: Messages): Option[String] =
override def validate(name: String, value: String, messages: Messages): Option[String] = if (getWebHook(params("owner"), params("repository"), value).isDefined != needExists) {
if (getWebHook(params("owner"), params("repository"), value).isDefined != needExists) { Some(if (needExists) {
Some(if (needExists) { "URL had not been registered yet."
"URL had not been registered yet."
} else {
"URL had been registered already."
})
} else { } else {
None "URL had been registered already."
} })
} } 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) {
Seq(name -> messages("error.required").format(name)) private def webhookEvents = 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.
@@ -500,77 +480,70 @@ trait RepositorySettingsControllerBase extends ControllerBase {
/** /**
* Duplicate check for the rename repository name. * Duplicate check for the rename repository name.
*/ */
private def renameRepositoryName: Constraint = private def renameRepositoryName: Constraint = new Constraint() {
new Constraint() { override def validate(
override def validate( name: String,
name: String, value: String,
value: String, params: Map[String, Seq[String]],
params: Map[String, Seq[String]], messages: Messages
messages: Messages ): Option[String] = {
): Option[String] = { for {
for { repoName <- params.optionValue("repository") if repoName != value
repoName <- params.optionValue("repository") if repoName != value userName <- params.optionValue("owner")
userName <- params.optionValue("owner") _ <- getRepositoryNamesOfUser(userName).find(_ == value)
_ <- getRepositoryNamesOfUser(userName).find(_ == value) } yield {
} yield { "Repository already exists."
"Repository already exists."
}
} }
} }
}
/** /**
*
*/ */
private def featureOption: Constraint = private def featureOption: Constraint = new Constraint() {
new Constraint() { override def validate(
override def validate( name: String,
name: String, value: String,
value: String, params: Map[String, Seq[String]],
params: Map[String, Seq[String]], messages: Messages
messages: Messages ): Option[String] =
): Option[String] = if (Seq("DISABLE", "PRIVATE", "PUBLIC", "ALL").contains(value)) None else Some("Option is invalid.")
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 = private def transferUser: Constraint = new Constraint() {
new Constraint() { override def validate(name: String, value: String, messages: Messages): Option[String] =
override def validate(name: String, value: String, messages: Messages): Option[String] = getAccountByUserName(value) match {
getAccountByUserName(value) match { case None => Some("User does not exist.")
case None => Some("User does not exist.") case Some(x) =>
case Some(x) => if (x.userName == params("owner")) {
if (x.userName == params("owner")) { Some("This is current repository owner.")
Some("This is current repository owner.") } else {
} else { params.get("repository").flatMap { repositoryName =>
params.get("repository").flatMap { repositoryName => getRepositoryNamesOfUser(x.userName).find(_ == repositoryName).map { _ =>
getRepositoryNamesOfUser(x.userName).find(_ == repositoryName).map { _ => "User already has same repository."
"User already has same repository."
}
} }
} }
} }
} }
}
private def mergeOptions = private def mergeOptions = new ValueType[Seq[String]] {
new ValueType[Seq[String]] { override def convert(name: String, params: Map[String, Seq[String]], messages: Messages): Seq[String] = {
override def convert(name: String, params: Map[String, Seq[String]], messages: Messages): Seq[String] = { params.getOrElse("mergeOptions", Nil)
params.getOrElse("mergeOptions", Nil) }
} override def validate(name: String, params: Map[String, Seq[String]], messages: Messages): Seq[(String, String)] = {
override def validate( val mergeOptions = params.getOrElse("mergeOptions", Nil)
name: String, if (mergeOptions.isEmpty) {
params: Map[String, Seq[String]], Seq("mergeOptions" -> "At least one option must be enabled.")
messages: Messages } else if (!mergeOptions.forall(x => Seq("merge-commit", "squash", "rebase").contains(x))) {
): Seq[(String, String)] = { Seq("mergeOptions" -> "mergeOptions are invalid.")
val mergeOptions = params.getOrElse("mergeOptions", Nil) } else {
if (mergeOptions.isEmpty) { Nil
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,7 +60,6 @@ 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,7 +24,6 @@ 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,9 +1,5 @@
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._
@@ -11,7 +7,14 @@ 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 {
def * = ??? val activityId = column[Int]("ACTIVITY_ID", O AutoInc)
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)
} }
} }
@@ -23,5 +26,5 @@ case class Activity(
message: String, message: String,
additionalInfo: Option[String], additionalInfo: Option[String],
activityDate: java.util.Date, activityDate: java.util.Date,
activityId: String activityId: Int = 0
) )

View File

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

View File

@@ -17,9 +17,7 @@ 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 (password.isEmpty) { val account = if (settings.ldapAuthentication) {
None
} else if (settings.ldapAuthentication) {
ldapAuthentication(settings, userName, password) ldapAuthentication(settings, userName, password)
} else { } else {
defaultAuthentication(userName, password) defaultAuthentication(userName, password)

View File

@@ -3,169 +3,65 @@ 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.util.Directory._ import gitbucket.core.model.Profile.profile.blockingApi._
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 =>
private implicit val formats = Serialization.formats(NoTypeHints) def deleteOldActivities(limit: Int)(implicit s: Session): Int = {
Activities.map(_.activityId).sortBy(_ desc).drop(limit).firstOption.map { id =>
private def writeLog(activity: Activity): Unit = { Activities.filter(_.activityId <= id.bind).delete
Using.resource(new FileOutputStream(ActivityLog, true)) { out => } getOrElse 0
out.write((write(activity) + "\n").getBytes(StandardCharsets.UTF_8))
}
} }
def getActivitiesByUser(activityUserName: String, isPublic: Boolean)(implicit context: Context): List[Activity] = { def getActivitiesByUser(activityUserName: String, isPublic: Boolean)(implicit s: Session): List[Activity] =
if (!ActivityLog.exists()) { Activities
List.empty .join(Repositories)
} else { .on((t1, t2) => t1.byRepository(t2.userName, t2.repositoryName))
val list = new ListBuffer[Activity] .filter {
Using.resource(new ReversedLinesFileReader(ActivityLog, StandardCharsets.UTF_8)) { reader => case (t1, t2) =>
var json: String = null if (isPublic) {
while (list.length < 50 && { json = reader.readLine(); json } != null) { (t1.activityUserName === activityUserName.bind) && (t2.isPrivate === false.bind)
val activity = read[Activity](json) } else {
if (activity.activityUserName == activityUserName) { (t1.activityUserName === activityUserName.bind)
if (isPublic == false) {
list += activity
} else {
if (!getRepositoryInfoFromCache(activity.userName, activity.repositoryName)
.map(_.isPrivate)
.getOrElse(true)) {
list += activity
}
}
} }
}
} }
list.toList .sortBy { case (t1, t2) => t1.activityId desc }
} .map { case (t1, t2) => t1 }
} .take(30)
.list
def getRecentPublicActivities()(implicit context: Context): List[Activity] = { def getRecentActivities()(implicit s: Session): List[Activity] =
if (!ActivityLog.exists()) { Activities
List.empty .join(Repositories)
} else { .on((t1, t2) => t1.byRepository(t2.userName, t2.repositoryName))
val list = new ListBuffer[Activity] .filter { case (t1, t2) => t2.isPrivate === false.bind }
Using.resource(new ReversedLinesFileReader(ActivityLog, StandardCharsets.UTF_8)) { reader => .sortBy { case (t1, t2) => t1.activityId desc }
var json: String = null .map { case (t1, t2) => t1 }
while (list.length < 50 && { json = reader.readLine(); json } != null) { .take(30)
val activity = read[Activity](json) .list
if (!getRepositoryInfoFromCache(activity.userName, activity.repositoryName)
.map(_.isPrivate)
.getOrElse(true)) {
list += activity
}
}
}
list.toList
}
}
def getRecentActivitiesByOwners(owners: Set[String])(implicit context: Context): List[Activity] = { def getRecentActivitiesByOwners(owners: Set[String])(implicit s: Session): List[Activity] =
if (!ActivityLog.exists()) { Activities
List.empty .join(Repositories)
} else { .on((t1, t2) => t1.byRepository(t2.userName, t2.repositoryName))
val list = new ListBuffer[Activity] .filter { case (t1, t2) => (t2.isPrivate === false.bind) || (t2.userName inSetBind owners) }
Using.resource(new ReversedLinesFileReader(ActivityLog, StandardCharsets.UTF_8)) { reader => .sortBy { case (t1, t2) => t1.activityId desc }
var json: String = null .map { case (t1, t2) => t1 }
while (list.length < 50 && { json = reader.readLine(); json } != null) { .take(30)
val activity = read[Activity](json) .list
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): Unit = { def recordCreateRepositoryActivity(userName: String, repositoryName: String, activityUserName: String)(
writeLog( implicit s: Session
Activity( ): Unit =
userName, Activities insert Activity(
repositoryName, userName,
activityUserName, repositoryName,
"create_repository", activityUserName,
s"[user:${activityUserName}] created [repo:${userName}/${repositoryName}]", "create_repository",
None, s"[user:${activityUserName}] created [repo:${userName}/${repositoryName}]",
currentDate, None,
UUID.randomUUID().toString currentDate
)
) )
}
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,
@@ -173,20 +69,16 @@ trait ActivityService {
activityUserName: String, activityUserName: String,
issueId: Int, issueId: Int,
title: String title: String
): Unit = { )(implicit s: Session): Unit =
writeLog( Activities insert Activity(
Activity( userName,
userName, repositoryName,
repositoryName, activityUserName,
activityUserName, "open_issue",
"open_issue", s"[user:${activityUserName}] opened issue [issue:${userName}/${repositoryName}#${issueId}]",
s"[user:${activityUserName}] opened issue [issue:${userName}/${repositoryName}#${issueId}]", Some(title),
Some(title), currentDate
currentDate,
UUID.randomUUID().toString
)
) )
}
def recordCloseIssueActivity( def recordCloseIssueActivity(
userName: String, userName: String,
@@ -194,20 +86,16 @@ trait ActivityService {
activityUserName: String, activityUserName: String,
issueId: Int, issueId: Int,
title: String title: String
): Unit = { )(implicit s: Session): Unit =
writeLog( Activities insert Activity(
Activity( userName,
userName, repositoryName,
repositoryName, activityUserName,
activityUserName, "close_issue",
"close_issue", s"[user:${activityUserName}] closed issue [issue:${userName}/${repositoryName}#${issueId}]",
s"[user:${activityUserName}] closed issue [issue:${userName}/${repositoryName}#${issueId}]", Some(title),
Some(title), currentDate
currentDate,
UUID.randomUUID().toString
)
) )
}
def recordClosePullRequestActivity( def recordClosePullRequestActivity(
userName: String, userName: String,
@@ -215,20 +103,16 @@ trait ActivityService {
activityUserName: String, activityUserName: String,
issueId: Int, issueId: Int,
title: String title: String
): Unit = { )(implicit s: Session): Unit =
writeLog( Activities insert Activity(
Activity( userName,
userName, repositoryName,
repositoryName, activityUserName,
activityUserName, "close_issue",
"close_issue", s"[user:${activityUserName}] closed pull request [pullreq:${userName}/${repositoryName}#${issueId}]",
s"[user:${activityUserName}] closed pull request [pullreq:${userName}/${repositoryName}#${issueId}]", Some(title),
Some(title), currentDate
currentDate,
UUID.randomUUID().toString
)
) )
}
def recordReopenIssueActivity( def recordReopenIssueActivity(
userName: String, userName: String,
@@ -236,20 +120,16 @@ trait ActivityService {
activityUserName: String, activityUserName: String,
issueId: Int, issueId: Int,
title: String title: String
): Unit = { )(implicit s: Session): Unit =
writeLog( Activities insert Activity(
Activity( userName,
userName, repositoryName,
repositoryName, activityUserName,
activityUserName, "reopen_issue",
"reopen_issue", s"[user:${activityUserName}] reopened issue [issue:${userName}/${repositoryName}#${issueId}]",
s"[user:${activityUserName}] reopened issue [issue:${userName}/${repositoryName}#${issueId}]", Some(title),
Some(title), currentDate
currentDate,
UUID.randomUUID().toString
)
) )
}
def recordReopenPullRequestActivity( def recordReopenPullRequestActivity(
userName: String, userName: String,
@@ -257,20 +137,16 @@ trait ActivityService {
activityUserName: String, activityUserName: String,
issueId: Int, issueId: Int,
title: String title: String
): Unit = { )(implicit s: Session): Unit =
writeLog( Activities insert Activity(
Activity( userName,
userName, repositoryName,
repositoryName, activityUserName,
activityUserName, "reopen_issue",
"reopen_issue", s"[user:${activityUserName}] reopened pull request [issue:${userName}/${repositoryName}#${issueId}]",
s"[user:${activityUserName}] reopened pull request [issue:${userName}/${repositoryName}#${issueId}]", Some(title),
Some(title), currentDate
currentDate,
UUID.randomUUID().toString
)
) )
}
def recordCommentIssueActivity( def recordCommentIssueActivity(
userName: String, userName: String,
@@ -278,20 +154,16 @@ trait ActivityService {
activityUserName: String, activityUserName: String,
issueId: Int, issueId: Int,
comment: String comment: String
): Unit = { )(implicit s: Session): Unit =
writeLog( Activities insert Activity(
Activity( userName,
userName, repositoryName,
repositoryName, activityUserName,
activityUserName, "comment_issue",
"comment_issue", s"[user:${activityUserName}] commented on issue [issue:${userName}/${repositoryName}#${issueId}]",
s"[user:${activityUserName}] commented on issue [issue:${userName}/${repositoryName}#${issueId}]", Some(cut(comment, 200)),
Some(cut(comment, 200)), currentDate
currentDate,
UUID.randomUUID().toString
)
) )
}
def recordCommentPullRequestActivity( def recordCommentPullRequestActivity(
userName: String, userName: String,
@@ -299,20 +171,16 @@ trait ActivityService {
activityUserName: String, activityUserName: String,
issueId: Int, issueId: Int,
comment: String comment: String
): Unit = { )(implicit s: Session): Unit =
writeLog( Activities insert Activity(
Activity( userName,
userName, repositoryName,
repositoryName, activityUserName,
activityUserName, "comment_issue",
"comment_issue", s"[user:${activityUserName}] commented on pull request [pullreq:${userName}/${repositoryName}#${issueId}]",
s"[user:${activityUserName}] commented on pull request [pullreq:${userName}/${repositoryName}#${issueId}]", Some(cut(comment, 200)),
Some(cut(comment, 200)), currentDate
currentDate,
UUID.randomUUID().toString
)
) )
}
def recordCommentCommitActivity( def recordCommentCommitActivity(
userName: String, userName: String,
@@ -320,40 +188,32 @@ trait ActivityService {
activityUserName: String, activityUserName: String,
commitId: String, commitId: String,
comment: String comment: String
): Unit = { )(implicit s: Session): Unit =
writeLog( Activities insert Activity(
Activity( userName,
userName, repositoryName,
repositoryName, activityUserName,
activityUserName, "comment_commit",
"comment_commit", s"[user:${activityUserName}] commented on commit [commit:${userName}/${repositoryName}@${commitId}]",
s"[user:${activityUserName}] commented on commit [commit:${userName}/${repositoryName}@${commitId}]", Some(cut(comment, 200)),
Some(cut(comment, 200)), currentDate
currentDate,
UUID.randomUUID().toString
)
) )
}
def recordCreateWikiPageActivity( def recordCreateWikiPageActivity(
userName: String, userName: String,
repositoryName: String, repositoryName: String,
activityUserName: String, activityUserName: String,
pageName: String pageName: String
): Unit = { )(implicit s: Session): Unit =
writeLog( Activities insert Activity(
Activity( userName,
userName, repositoryName,
repositoryName, activityUserName,
activityUserName, "create_wiki",
"create_wiki", s"[user:${activityUserName}] created the [repo:${userName}/${repositoryName}] wiki",
s"[user:${activityUserName}] created the [repo:${userName}/${repositoryName}] wiki", Some(pageName),
Some(pageName), currentDate
currentDate,
UUID.randomUUID().toString
)
) )
}
def recordEditWikiPageActivity( def recordEditWikiPageActivity(
userName: String, userName: String,
@@ -361,20 +221,16 @@ trait ActivityService {
activityUserName: String, activityUserName: String,
pageName: String, pageName: String,
commitId: String commitId: String
): Unit = { )(implicit s: Session): Unit =
writeLog( Activities insert Activity(
Activity( userName,
userName, repositoryName,
repositoryName, activityUserName,
activityUserName, "edit_wiki",
"edit_wiki", s"[user:${activityUserName}] edited the [repo:${userName}/${repositoryName}] wiki",
s"[user:${activityUserName}] edited the [repo:${userName}/${repositoryName}] wiki", Some(pageName + ":" + commitId),
Some(pageName + ":" + commitId), currentDate
currentDate,
UUID.randomUUID().toString
)
) )
}
def recordPushActivity( def recordPushActivity(
userName: String, userName: String,
@@ -382,27 +238,23 @@ trait ActivityService {
activityUserName: String, activityUserName: String,
branchName: String, branchName: String,
commits: List[JGitUtil.CommitInfo] commits: List[JGitUtil.CommitInfo]
): Unit = { )(implicit s: Session): Unit =
writeLog( Activities insert Activity(
Activity( userName,
userName, repositoryName,
repositoryName, activityUserName,
activityUserName, "push",
"push", s"[user:${activityUserName}] pushed to [branch:${userName}/${repositoryName}#${branchName}] at [repo:${userName}/${repositoryName}]",
s"[user:${activityUserName}] pushed to [branch:${userName}/${repositoryName}#${branchName}] at [repo:${userName}/${repositoryName}]", Some(
Some( commits
commits .take(5)
.take(5) .map { commit =>
.map { commit => commit.id + ":" + commit.shortMessage
commit.id + ":" + commit.shortMessage }
} .mkString("\n")
.mkString("\n") ),
), currentDate
currentDate,
UUID.randomUUID().toString
)
) )
}
def recordCreateTagActivity( def recordCreateTagActivity(
userName: String, userName: String,
@@ -410,20 +262,16 @@ trait ActivityService {
activityUserName: String, activityUserName: String,
tagName: String, tagName: String,
commits: List[JGitUtil.CommitInfo] commits: List[JGitUtil.CommitInfo]
): Unit = { )(implicit s: Session): Unit =
writeLog( Activities insert Activity(
Activity( userName,
userName, repositoryName,
repositoryName, activityUserName,
activityUserName, "create_tag",
"create_tag", s"[user:${activityUserName}] created tag [tag:${userName}/${repositoryName}#${tagName}] at [repo:${userName}/${repositoryName}]",
s"[user:${activityUserName}] created tag [tag:${userName}/${repositoryName}#${tagName}] at [repo:${userName}/${repositoryName}]", None,
None, currentDate
currentDate,
UUID.randomUUID().toString
)
) )
}
def recordDeleteTagActivity( def recordDeleteTagActivity(
userName: String, userName: String,
@@ -431,80 +279,61 @@ trait ActivityService {
activityUserName: String, activityUserName: String,
tagName: String, tagName: String,
commits: List[JGitUtil.CommitInfo] commits: List[JGitUtil.CommitInfo]
): Unit = { )(implicit s: Session): Unit =
writeLog( Activities insert Activity(
Activity( userName,
userName, repositoryName,
repositoryName, activityUserName,
activityUserName, "delete_tag",
"delete_tag", s"[user:${activityUserName}] deleted tag ${tagName} at [repo:${userName}/${repositoryName}]",
s"[user:${activityUserName}] deleted tag ${tagName} at [repo:${userName}/${repositoryName}]", None,
None, currentDate
currentDate,
UUID.randomUUID().toString
)
) )
}
def recordCreateBranchActivity( def recordCreateBranchActivity(
userName: String, userName: String,
repositoryName: String, repositoryName: String,
activityUserName: String, activityUserName: String,
branchName: String branchName: String
): Unit = { )(implicit s: Session): Unit =
writeLog( Activities insert Activity(
Activity( userName,
userName, repositoryName,
repositoryName, activityUserName,
activityUserName, "create_branch",
"create_branch", s"[user:${activityUserName}] created branch [branch:${userName}/${repositoryName}#${branchName}] at [repo:${userName}/${repositoryName}]",
s"[user:${activityUserName}] created branch [branch:${userName}/${repositoryName}#${branchName}] at [repo:${userName}/${repositoryName}]", None,
None, currentDate
currentDate,
UUID.randomUUID().toString
)
) )
}
def recordDeleteBranchActivity( def recordDeleteBranchActivity(
userName: String, userName: String,
repositoryName: String, repositoryName: String,
activityUserName: String, activityUserName: String,
branchName: String branchName: String
): Unit = { )(implicit s: Session): Unit =
writeLog( Activities insert Activity(
Activity( userName,
userName, repositoryName,
repositoryName, activityUserName,
activityUserName, "delete_branch",
"delete_branch", s"[user:${activityUserName}] deleted branch ${branchName} at [repo:${userName}/${repositoryName}]",
s"[user:${activityUserName}] deleted branch ${branchName} at [repo:${userName}/${repositoryName}]", None,
None, currentDate
currentDate,
UUID.randomUUID().toString
)
) )
}
def recordForkActivity( def recordForkActivity(userName: String, repositoryName: String, activityUserName: String, forkedUserName: String)(
userName: String, implicit s: Session
repositoryName: String, ): Unit =
activityUserName: String, Activities insert Activity(
forkedUserName: String userName,
): Unit = { repositoryName,
writeLog( activityUserName,
Activity( "fork",
userName, s"[user:${activityUserName}] forked [repo:${userName}/${repositoryName}] to [repo:${forkedUserName}/${repositoryName}]",
repositoryName, None,
activityUserName, currentDate
"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,
@@ -512,20 +341,16 @@ trait ActivityService {
activityUserName: String, activityUserName: String,
issueId: Int, issueId: Int,
title: String title: String
): Unit = { )(implicit s: Session): Unit =
writeLog( Activities insert Activity(
Activity( userName,
userName, repositoryName,
repositoryName, activityUserName,
activityUserName, "open_pullreq",
"open_pullreq", s"[user:${activityUserName}] opened pull request [pullreq:${userName}/${repositoryName}#${issueId}]",
s"[user:${activityUserName}] opened pull request [pullreq:${userName}/${repositoryName}#${issueId}]", Some(title),
Some(title), currentDate
currentDate,
UUID.randomUUID().toString
)
) )
}
def recordMergeActivity( def recordMergeActivity(
userName: String, userName: String,
@@ -533,20 +358,16 @@ trait ActivityService {
activityUserName: String, activityUserName: String,
issueId: Int, issueId: Int,
message: String message: String
): Unit = { )(implicit s: Session): Unit =
writeLog( Activities insert Activity(
Activity( userName,
userName, repositoryName,
repositoryName, activityUserName,
activityUserName, "merge_pullreq",
"merge_pullreq", s"[user:${activityUserName}] merged pull request [pullreq:${userName}/${repositoryName}#${issueId}]",
s"[user:${activityUserName}] merged pull request [pullreq:${userName}/${repositoryName}#${issueId}]", Some(message),
Some(message), currentDate
currentDate,
UUID.randomUUID().toString
)
) )
}
def recordReleaseActivity( def recordReleaseActivity(
userName: String, userName: String,
@@ -554,20 +375,16 @@ trait ActivityService {
activityUserName: String, activityUserName: String,
releaseName: String, releaseName: String,
tagName: String tagName: String
): Unit = { )(implicit s: Session): Unit =
writeLog( Activities insert Activity(
Activity( userName,
userName, repositoryName,
repositoryName, activityUserName,
activityUserName, "release",
"release", s"[user:${activityUserName}] released [release:${userName}/${repositoryName}/${tagName}:${releaseName}] at [repo:${userName}/${repositoryName}]",
s"[user:${activityUserName}] released [release:${userName}/${repositoryName}/${tagName}:${releaseName}] at [repo:${userName}/${repositoryName}]", None,
None, currentDate
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,5 +1,6 @@
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._
@@ -8,11 +9,14 @@ 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.FileInfo import gitbucket.core.util.JGitUtil.{CommitInfo, 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 {
@@ -115,6 +119,15 @@ 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(
@@ -200,6 +213,50 @@ 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) {
@@ -247,7 +304,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,11 +1,9 @@
package gitbucket.core.service package gitbucket.core.service
import gitbucket.core.model.{Account, Issue, Repository, Session} import gitbucket.core.model.{Session, Issue, Account}
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.
@@ -25,41 +23,21 @@ 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 getIssueFromCache(userName: String, repositoryName: String, issueId: String)( def getIssue(userName: String, repositoryName: String, issueId: String)(implicit context: Context): Option[Issue] = {
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 getAccountByUserNameFromCache(userName: String)(implicit context: Context): Option[Account] = { def getAccountByUserName(userName: String)(implicit context: Context): Option[Account] = {
context.cache(s"account.${userName}") { context.cache(s"account.${userName}") {
super.getAccountByUserName(userName) super.getAccountByUserName(userName)
} }
} }
def getAccountByMailAddressFromCache(mailAddress: String)(implicit context: Context): Option[Account] = { def getAccountByMailAddress(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,8 +240,7 @@ 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,10 +2,11 @@ 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.SystemSettingsService import gitbucket.core.service.{ActivityService, 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._
@@ -20,6 +21,8 @@ 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
@@ -32,23 +35,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")
@@ -92,10 +95,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 = {
@@ -169,8 +172,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
@@ -178,3 +181,21 @@ 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,8 +29,6 @@ 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
getAccountByUserNameFromCache(userName).map { account => getAccountByUserName(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
getAccountByMailAddressFromCache(mailAddress).map { account => getAccountByMailAddress(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
getIssueFromCache(userName, repositoryName, issueId.toString) match { getIssue(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 =>
getAccountByUserNameFromCache(m.group(2)).map { _ => getAccountByUserName(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 =>
getIssueFromCache(m.group(2), m.group(3), m.group(4)) match { getIssue(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 =>
getAccountByUserNameFromCache(m.group(2)).map { _ => getAccountByUserName(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 =>
getIssueFromCache(m.group(2), repository.name, m.group(3)) match { getIssue(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)
getIssueFromCache(repository.owner, repository.name, m.group(3)) match { getIssue(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 =>
getAccountByUserNameFromCache(m.group(2)).map { _ => getAccountByUserName(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,6 +3,7 @@ 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
@@ -195,7 +196,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("$", "\\$"))
} }
} }
@@ -203,64 +204,50 @@ 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("\\[issue:([^\\s]+?)/([^\\s]+?)#((\\d+))\\]"){ m => .replaceAll(
if (getRepositoryInfoFromCache(m.group(1), m.group(2)).isDefined) { "\\[issue:([^\\s]+?)/([^\\s]+?)#((\\d+))\\]",
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>""" s"""<a href="${context.path}/$$1/$$2/issues/$$3">$$1/$$2#$$3</a>"""
} else { )
s"${m.group(1)}/${m.group(2)}#${m.group(3)}" .replaceAll(
} "\\[pullreq:([^\\s]+?)/([^\\s]+?)#((\\d+))\\]",
} s"""<a href="${context.path}/$$1/$$2/pull/$$3">$$1/$$2#$$3</a>"""
.replaceAll("\\[pullreq:([^\\s]+?)/([^\\s]+?)#((\\d+))\\]"){ m => )
if (getRepositoryInfoFromCache(m.group(1), m.group(2)).isDefined) { .replaceAll("\\[repo:([^\\s]+?)/([^\\s]+?)\\]", s"""<a href="${context.path}/$$1/$$2\">$$1/$$2</a>""")
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>""" .replaceAll(
} else { "\\[branch:([^\\s]+?)/([^\\s]+?)#([^\\s]+?)\\]",
s"${m.group(1)}/${m.group(2)}#${m.group(3)}" (m: Match) =>
} s"""<a href="${context.path}/${m.group(1)}/${m.group(2)}/tree/${encodeRefName(m.group(3))}">${StringUtil
} .escapeHtml(
.replaceAll("\\[repo:([^\\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)}">${m.group(1)}/${m.group(2)}</a>""" )
} else { .replaceAll(
s"${m.group(1)}/${m.group(2)}" "\\[tag:([^\\s]+?)/([^\\s]+?)#([^\\s]+?)\\]",
} (m: Match) =>
} s"""<a href="${context.path}/${m.group(1)}/${m.group(2)}/tree/${encodeRefName(m.group(3))}">${StringUtil
.replaceAll("\\[branch:([^\\s]+?)/([^\\s]+?)#([^\\s]+?)\\]") { m => .escapeHtml(
if (getRepositoryInfoFromCache(m.group(1), m.group(2)).isDefined) { m.group(3)
s"""<a href="${context.path}/${m.group(1)}/${m.group(2)}/tree/${encodeRefName(m.group(3))}">${StringUtil.escapeHtml(m.group(3))}</a>""" )}</a>"""
} else { )
StringUtil.escapeHtml(m.group(3)) .replaceAll("\\[user:([^\\s]+?)\\]", (m: Match) => user(m.group(1)).body)
} .replaceAll(
} "\\[commit:([^\\s]+?)/([^\\s]+?)\\@([^\\s]+?)\\]",
.replaceAll("\\[tag:([^\\s]+?)/([^\\s]+?)#([^\\s]+?)\\]") { m => (m: Match) =>
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
s"""<a href="${context.path}/${m.group(1)}/${m.group(2)}/tree/${encodeRefName(m.group(3))}">${StringUtil.escapeHtml(m.group(3))}</a>""" .group(2)}@${m.group(3).substring(0, 7)}</a>"""
} else { )
StringUtil.escapeHtml(m.group(3)) .replaceAll(
} "\\[release:([^\\s]+?)/([^\\s]+?)/([^\\s]+?):(.+)\\]",
} (m: Match) =>
.replaceAll("\\[user:([^\\s]+?)\\]") { m => s"""<a href="${context.path}/${m.group(1)}/${m.group(2)}/releases/${encodeRefName(m.group(3))}">${StringUtil
user(m.group(1)).body .escapeHtml(
} m.group(4)
.replaceAll("\\[commit:([^\\s]+?)/([^\\s]+?)\\@([^\\s]+?)\\]") { m => )}</a>"""
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.
@@ -346,9 +333,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) {
getAccountByUserNameFromCache(userName) getAccountByUserName(userName)
} else { } else {
getAccountByMailAddressFromCache(mailAddress) getAccountByMailAddress(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,24 +7,21 @@
@activities.map { activity => @activities.map { activity =>
<div class="block"> <div class="block">
@(activity.activityType match { @(activity.activityType match {
case "open_issue" => simpleActivity(activity) case "open_issue" => detailActivity(activity, "issue-opened")
case "comment_issue" => simpleActivity(activity) case "comment_issue" => detailActivity(activity, "comment-discussion")
case "comment_commit" => simpleActivity(activity) case "comment_commit" => detailActivity(activity, "comment-discussion")
case "close_issue" => simpleActivity(activity) case "close_issue" => detailActivity(activity, "issue-closed")
case "reopen_issue" => simpleActivity(activity) case "reopen_issue" => detailActivity(activity, "issue-reopened")
case "open_pullreq" => simpleActivity(activity) case "open_pullreq" => detailActivity(activity, "git-pull-request")
case "merge_pullreq" => simpleActivity(activity) case "merge_pullreq" => detailActivity(activity, "git-merge")
case "release" => simpleActivity(activity) case "release" => detailActivity(activity, "package")
case "create_repository" => simpleActivity(activity) case "create_repository" => simpleActivity(activity, "repo")
case "delete_repository" => simpleActivity(activity) case "create_branch" => simpleActivity(activity, "git-branch")
case "rename_repository" => simpleActivity(activity) case "delete_branch" => simpleActivity(activity, "circle-slash")
case "transfer_repository" => simpleActivity(activity) case "create_tag" => simpleActivity(activity, "tag")
case "create_branch" => simpleActivity(activity) case "delete_tag" => simpleActivity(activity, "circle-slash")
case "delete_branch" => simpleActivity(activity) case "fork" => simpleActivity(activity, "repo-forked")
case "create_tag" => simpleActivity(activity) case "push" => customActivity(activity, "git-commit"){
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){
@@ -40,12 +37,12 @@
}} }}
</div> </div>
} }
case "create_wiki" => customActivity(activity){ case "create_wiki" => customActivity(activity, "book"){
<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){ case "edit_wiki" => customActivity(activity, "book"){
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">
@@ -63,7 +60,26 @@
} }
} }
@customActivity(activity: gitbucket.core.model.Activity)(additionalInfo: Any) = { @detailActivity(activity: gitbucket.core.model.Activity, image: String) = {
@*
<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">
@@ -74,7 +90,10 @@
</div> </div>
} }
@simpleActivity(activity: gitbucket.core.model.Activity) = { @simpleActivity(activity: gitbucket.core.model.Activity, image: String) = {
@*
<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 RequestCache {} with WebHookPullRequestService with WebHookPullRequestReviewCommentService {}
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,8 +18,7 @@ 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 RequestCache { with WebHookPullRequestReviewCommentService {
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 RequestCache with MilestonesService with PrioritiesService with WebHookPullRequestReviewCommentService
test("WebHookPullRequestService.getPullRequestsByRequestForWebhook") { test("WebHookPullRequestService.getPullRequestsByRequestForWebhook") {
withTestDB { implicit session => withTestDB { implicit session =>

View File

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