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]
steps:
- 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
uses: actions/setup-java@v1
with:

View File

@@ -1,22 +1,6 @@
# Changelog
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
- All CLI options are configurable by environment variables

View File

@@ -57,20 +57,11 @@ Support
What's New in 4.33.x
-------------
### 4.34.0 - 26 Jul 2020
### 4.33.0 - 31 Dec 2019
- 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
- All CLI options are configurable by environment variables
- Folding pull request files
- WebHook security options
- Add assignee and assignees properties to some Web APIs' response
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 Name = "gitbucket"
val GitBucketVersion = "4.34.0"
val GitBucketVersion = "4.33.0"
val ScalatraVersion = "2.7.0-RC1"
val JettyVersion = "9.4.30.v20200611"
val JgitVersion = "5.8.0.202006091008-r"
@@ -54,9 +54,11 @@ libraryDependencies ++= Seq(
"ch.qos.logback" % "logback-classic" % "1.2.3",
"com.zaxxer" % "HikariCP" % "3.4.5",
"com.typesafe" % "config" % "1.4.0",
"com.typesafe.akka" %% "akka-actor" % "2.5.27",
"fr.brouillard.oss.security.xhub" % "xhub4j-core" % "1.1.0",
"com.github.bkromhout" % "java-diff-utils" % "2.1.1",
"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",
"com.github.zafarkhaja" % "java-semver" % "0.9.0",
"com.nimbusds" % "oauth2-oidc-sdk" % "5.64.4",

View File

@@ -1,4 +1,4 @@
notifications:1.9.0
gist:4.19.0
notifications:1.8.0
gist:4.18.0
emoji:4.6.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
import java.io.FileOutputStream
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
import io.github.gitbucket.solidbase.migration.{SqlMigration, LiquibaseMigration}
import io.github.gitbucket.solidbase.model.{Version, Module}
object GitBucketCoreModule
extends Module(
@@ -80,38 +65,5 @@ object GitBucketCoreModule
new Version("4.31.1"),
new Version("4.31.2"),
new Version("4.32.0", new LiquibaseMigration("update/gitbucket-core_4.32.xml")),
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")
)
new Version("4.33.0")
)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -30,10 +30,8 @@ class RepositorySettingsController
with ProtectedBranchService
with CommitStatusService
with DeployKeyService
with ActivityService
with OwnerAuthenticator
with UsersAuthenticator
with RequestCache
trait RepositorySettingsControllerBase extends ControllerBase {
self: RepositoryService
@@ -42,7 +40,6 @@ trait RepositorySettingsControllerBase extends ControllerBase {
with ProtectedBranchService
with CommitStatusService
with DeployKeyService
with ActivityService
with OwnerAuthenticator
with UsersAuthenticator =>
@@ -100,7 +97,9 @@ trait RepositorySettingsControllerBase extends ControllerBase {
"events" -> webhookEvents,
"ctype" -> label("ctype", text()),
"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
case class RenameRepositoryForm(repositoryName: String)
@@ -252,10 +251,9 @@ trait RepositorySettingsControllerBase extends ControllerBase {
* Send the test request to registered web hook URLs.
*/
ajaxPost("/:owner/:repository/settings/hooks/test")(ownerOnly { repository =>
def _headers(h: Array[org.apache.http.Header]): Array[Array[String]] =
h.map { h =>
Array(h.getName, h.getValue)
}
def _headers(h: Array[org.apache.http.Header]): Array[Array[String]] = h.map { h =>
Array(h.getName, h.getValue)
}
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) {
git =>
@@ -373,15 +371,7 @@ trait RepositorySettingsControllerBase extends ControllerBase {
post("/:owner/:repository/settings/rename", renameForm)(ownerOnly { (form, repository) =>
if (context.settings.repositoryOperation.rename || context.loginAccount.get.isAdmin) {
if (repository.name != form.repositoryName) {
// Update database and move git repository
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}")
} else Forbidden()
@@ -394,15 +384,7 @@ trait RepositorySettingsControllerBase extends ControllerBase {
if (context.settings.repositoryOperation.transfer || context.loginAccount.get.isAdmin) {
// Change repository owner
if (repository.owner != form.newOwner) {
// Update database and move git repository
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}")
} else Forbidden()
@@ -453,34 +435,32 @@ trait RepositorySettingsControllerBase extends ControllerBase {
/**
* Provides duplication check for web hook url.
*/
private def webHook(needExists: Boolean): Constraint =
new Constraint() {
override def validate(name: String, value: String, messages: Messages): Option[String] =
if (getWebHook(params("owner"), params("repository"), value).isDefined != needExists) {
Some(if (needExists) {
"URL had not been registered yet."
} else {
"URL had been registered already."
})
private def webHook(needExists: Boolean): Constraint = new Constraint() {
override def validate(name: String, value: String, messages: Messages): Option[String] =
if (getWebHook(params("owner"), params("repository"), value).isDefined != needExists) {
Some(if (needExists) {
"URL had not been registered yet."
} 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
"URL had been registered already."
})
} else {
None
}
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
}
}
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))
} else {
Nil
}
}
// /**
// * Provides Constraint to validate the collaborator name.
@@ -500,77 +480,70 @@ trait RepositorySettingsControllerBase extends ControllerBase {
/**
* Duplicate check for the rename repository name.
*/
private def renameRepositoryName: Constraint =
new Constraint() {
override def validate(
name: String,
value: String,
params: Map[String, Seq[String]],
messages: Messages
): Option[String] = {
for {
repoName <- params.optionValue("repository") if repoName != value
userName <- params.optionValue("owner")
_ <- getRepositoryNamesOfUser(userName).find(_ == value)
} yield {
"Repository already exists."
}
private def renameRepositoryName: Constraint = new Constraint() {
override def validate(
name: String,
value: String,
params: Map[String, Seq[String]],
messages: Messages
): Option[String] = {
for {
repoName <- params.optionValue("repository") if repoName != value
userName <- params.optionValue("owner")
_ <- getRepositoryNamesOfUser(userName).find(_ == value)
} yield {
"Repository already exists."
}
}
}
/**
*
*/
private def featureOption: Constraint =
new Constraint() {
override def validate(
name: String,
value: String,
params: Map[String, Seq[String]],
messages: Messages
): Option[String] =
if (Seq("DISABLE", "PRIVATE", "PUBLIC", "ALL").contains(value)) None else Some("Option is invalid.")
}
private def featureOption: Constraint = new Constraint() {
override def validate(
name: String,
value: String,
params: Map[String, Seq[String]],
messages: Messages
): Option[String] =
if (Seq("DISABLE", "PRIVATE", "PUBLIC", "ALL").contains(value)) None else Some("Option is invalid.")
}
/**
* Provides Constraint to validate the repository transfer user.
*/
private def transferUser: Constraint =
new Constraint() {
override def validate(name: String, value: String, messages: Messages): Option[String] =
getAccountByUserName(value) match {
case None => Some("User does not exist.")
case Some(x) =>
if (x.userName == params("owner")) {
Some("This is current repository owner.")
} else {
params.get("repository").flatMap { repositoryName =>
getRepositoryNamesOfUser(x.userName).find(_ == repositoryName).map { _ =>
"User already has same repository."
}
private def transferUser: Constraint = new Constraint() {
override def validate(name: String, value: String, messages: Messages): Option[String] =
getAccountByUserName(value) match {
case None => Some("User does not exist.")
case Some(x) =>
if (x.userName == params("owner")) {
Some("This is current repository owner.")
} else {
params.get("repository").flatMap { repositoryName =>
getRepositoryNamesOfUser(x.userName).find(_ == repositoryName).map { _ =>
"User already has same repository."
}
}
}
}
}
}
}
private def mergeOptions =
new ValueType[Seq[String]] {
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)
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
}
private def mergeOptions = new ValueType[Seq[String]] {
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)
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,7 +60,6 @@ class RepositoryViewerController
with WebHookPullRequestService
with WebHookPullRequestReviewCommentService
with ProtectedBranchService
with RequestCache
/**
* The repository viewer.

View File

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

View File

@@ -1,9 +1,5 @@
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 =>
import profile.api._
import self._
@@ -11,7 +7,14 @@ trait ActivityComponent extends TemplateComponent { self: Profile =>
lazy val Activities = TableQuery[Activities]
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,
additionalInfo: Option[String],
activityDate: java.util.Date,
activityId: String
activityId: Int = 0
)

View File

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

View File

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

View File

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

View File

@@ -1,5 +1,6 @@
package gitbucket.core.service
import gitbucket.core.api.JsonFormat
import gitbucket.core.controller.Context
import gitbucket.core.util._
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.dateColumnType
import gitbucket.core.plugin.PluginRegistry
import gitbucket.core.service.WebHookService.WebHookPushPayload
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.eclipse.jgit.api.Git
import org.eclipse.jgit.dircache.{DirCache, DirCacheBuilder}
import org.eclipse.jgit.lib.{Repository => _, _}
import org.eclipse.jgit.transport.{ReceiveCommand, ReceivePack}
import scala.util.Using
trait RepositoryService {
@@ -115,6 +119,15 @@ trait RepositoryService {
}
.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)
RepositoryWebHooks.insertAll(
@@ -200,6 +213,50 @@ trait RepositoryService {
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
defining(getRepositoryDir(oldUserName, oldRepositoryName)) { dir =>
if (dir.isDirectory) {
@@ -247,7 +304,7 @@ trait RepositoryService {
}
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
CommitComments.filter(_.byRepository(userName, repositoryName)).delete
IssueLabels.filter(_.byRepository(userName, repositoryName)).delete

View File

@@ -1,11 +1,9 @@
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.controller.Context
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.
@@ -25,41 +23,21 @@ trait RequestCache
private implicit def context2Session(implicit context: Context): Session =
request2Session(context.request)
def getIssueFromCache(userName: String, repositoryName: String, issueId: String)(
implicit context: Context
): Option[Issue] = {
def getIssue(userName: String, repositoryName: String, issueId: String)(implicit context: Context): Option[Issue] = {
context.cache(s"issue.${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}") {
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}") {
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 WebHookPullRequestReviewCommentService
with CommitsService
with SystemSettingsService
with RequestCache {
with SystemSettingsService {
private val logger = LoggerFactory.getLogger(classOf[CommitLogHook])
private var existIds: Seq[String] = Nil

View File

@@ -2,10 +2,11 @@ package gitbucket.core.servlet
import java.io.{File, FileOutputStream}
import akka.event.Logging
import com.typesafe.config.ConfigFactory
import gitbucket.core.GitBucketCoreModule
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.Directory._
import gitbucket.core.util.JDBCUtil._
@@ -20,6 +21,8 @@ import javax.servlet.{ServletContextEvent, ServletContextListener}
import org.apache.commons.io.{FileUtils, IOUtils}
import org.slf4j.LoggerFactory
import akka.actor.{Actor, ActorSystem, Props}
import com.typesafe.akka.extension.quartz.QuartzSchedulerExtension
import scala.jdk.CollectionConverters._
import scala.util.Using
@@ -32,23 +35,23 @@ class InitializeListener extends ServletContextListener with SystemSettingsServi
private val logger = LoggerFactory.getLogger(classOf[InitializeListener])
// // ActorSystem for Quartz scheduler
// private val system = ActorSystem(
// "job",
// ConfigFactory.parseString("""
// |akka {
// | daemonic = on
// | coordinated-shutdown.run-by-jvm-shutdown-hook = off
// | quartz {
// | schedules {
// | Daily {
// | expression = "0 0 0 * * ?"
// | }
// | }
// | }
// |}
// """.stripMargin)
// )
// ActorSystem for Quartz scheduler
private val system = ActorSystem(
"job",
ConfigFactory.parseString("""
|akka {
| daemonic = on
| coordinated-shutdown.run-by-jvm-shutdown-hook = off
| quartz {
| schedules {
| Daily {
| expression = "0 0 0 * * ?"
| }
| }
| }
|}
""".stripMargin)
)
override def contextInitialized(event: ServletContextEvent): Unit = {
val dataDir = event.getServletContext.getInitParameter("gitbucket.home")
@@ -92,10 +95,10 @@ class InitializeListener extends ServletContextListener with SystemSettingsServi
PluginRegistry.initialize(event.getServletContext, loadSystemSettings(), conn)
}
// // Start Quartz scheduler
// val scheduler = QuartzSchedulerExtension(system)
//
// scheduler.schedule("Daily", system.actorOf(Props[DeleteOldActivityActor]), "DeleteOldActivity")
// Start Quartz scheduler
val scheduler = QuartzSchedulerExtension(system)
scheduler.schedule("Daily", system.actorOf(Props[DeleteOldActivityActor]), "DeleteOldActivity")
}
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 = {
// // Shutdown Quartz scheduler
// system.terminate()
// Shutdown Quartz scheduler
system.terminate()
// Shutdown plugins
PluginRegistry.shutdown(event.getServletContext, loadSystemSettings())
// 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 ActivityLog = new File(GitBucketHome, "activity.log")
val RepositoryHome = s"${GitBucketHome}/repositories"
val DatabaseHome = s"${GitBucketHome}/data"

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -18,8 +18,7 @@ class PullRequestServiceSpec
with PrioritiesService
with WebHookService
with WebHookPullRequestService
with WebHookPullRequestReviewCommentService
with RequestCache {
with WebHookPullRequestReviewCommentService {
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
with MergeService with PullRequestService with CommitsService with CommitStatusService with LabelsService
with MilestonesService with PrioritiesService with WebHookService with WebHookPullRequestService
with WebHookPullRequestReviewCommentService with RequestCache {
with WebHookPullRequestReviewCommentService {
override def fetchAsPullRequest(
userName: String,
repositoryName: String,

View File

@@ -7,7 +7,7 @@ import gitbucket.core.model.WebHookContentType
class WebHookServiceSpec extends FunSuite with ServiceSpecBase {
lazy val service = new WebHookPullRequestService with AccountService with ActivityService with RepositoryService
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") {
withTestDB { implicit session =>

View File

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