mirror of
https://github.com/gitbucket/gitbucket.git
synced 2025-10-29 17:46:27 +01:00
Compare commits
7 Commits
disable-gi
...
4.34.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1a4961c3e1 | ||
|
|
561220237f | ||
|
|
f45b85aa71 | ||
|
|
83b3a7983e | ||
|
|
63d4c5054e | ||
|
|
c8f6017be9 | ||
|
|
f9fcb54861 |
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
@@ -22,7 +22,7 @@ jobs:
|
||||
path: |
|
||||
~/.ivy2/cache
|
||||
~/.sbt
|
||||
~/.coursier
|
||||
~/.cache/coursier/v1
|
||||
key: build-${{ env.cache-name }}-${{ hashFiles('build.sbt') }}
|
||||
- name: Set up JDK
|
||||
uses: actions/setup-java@v1
|
||||
|
||||
16
CHANGELOG.md
16
CHANGELOG.md
@@ -1,6 +1,22 @@
|
||||
# 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
|
||||
|
||||
19
README.md
19
README.md
@@ -57,11 +57,20 @@ Support
|
||||
|
||||
What's New in 4.33.x
|
||||
-------------
|
||||
### 4.33.0 - 31 Dec 2019
|
||||
### 4.34.0 - 26 Jul 2020
|
||||
|
||||
- All CLI options are configurable by environment variables
|
||||
- Folding pull request files
|
||||
- WebHook security options
|
||||
- Add assignee and assignees properties to some Web APIs' response
|
||||
- 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
|
||||
|
||||
See the [change log](CHANGELOG.md) for all of the updates.
|
||||
|
||||
@@ -3,7 +3,7 @@ import com.typesafe.sbt.pgp.PgpKeys._
|
||||
|
||||
val Organization = "io.github.gitbucket"
|
||||
val Name = "gitbucket"
|
||||
val GitBucketVersion = "4.33.0"
|
||||
val GitBucketVersion = "4.34.0"
|
||||
val ScalatraVersion = "2.7.0-RC1"
|
||||
val JettyVersion = "9.4.30.v20200611"
|
||||
val JgitVersion = "5.8.0.202006091008-r"
|
||||
@@ -54,11 +54,9 @@ 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",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
notifications:1.8.0
|
||||
gist:4.18.0
|
||||
notifications:1.9.0
|
||||
gist:4.19.0
|
||||
emoji:4.6.0
|
||||
pages:1.8.0
|
||||
|
||||
4
src/main/resources/update/gitbucket-core_4.34.xml
Normal file
4
src/main/resources/update/gitbucket-core_4.34.xml
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<changeSet>
|
||||
<dropTable tableName="ACTIVITY" />
|
||||
</changeSet>
|
||||
@@ -1,7 +1,22 @@
|
||||
package gitbucket.core
|
||||
|
||||
import io.github.gitbucket.solidbase.migration.{SqlMigration, LiquibaseMigration}
|
||||
import io.github.gitbucket.solidbase.model.{Version, Module}
|
||||
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
|
||||
|
||||
object GitBucketCoreModule
|
||||
extends Module(
|
||||
@@ -65,5 +80,38 @@ 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.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")
|
||||
)
|
||||
)
|
||||
|
||||
@@ -35,6 +35,7 @@ class AccountController
|
||||
with WebHookService
|
||||
with PrioritiesService
|
||||
with RepositoryCreationService
|
||||
with RequestCache
|
||||
|
||||
trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
self: AccountService
|
||||
|
||||
@@ -52,6 +52,7 @@ class ApiController
|
||||
with ReferrerAuthenticator
|
||||
with ReadableUsersAuthenticator
|
||||
with WritableUsersAuthenticator
|
||||
with RequestCache
|
||||
|
||||
trait ApiControllerBase extends ControllerBase {
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ class DashboardController
|
||||
with WebHookPullRequestReviewCommentService
|
||||
with MilestonesService
|
||||
with UsersAuthenticator
|
||||
with RequestCache
|
||||
|
||||
trait DashboardControllerBase extends ControllerBase {
|
||||
self: IssuesService with PullRequestService with RepositoryService with AccountService with UsersAuthenticator =>
|
||||
|
||||
@@ -29,6 +29,7 @@ class IndexController
|
||||
with AccessTokenService
|
||||
with AccountFederationService
|
||||
with OpenIDConnectService
|
||||
with RequestCache
|
||||
|
||||
trait IndexControllerBase extends ControllerBase {
|
||||
self: RepositoryService
|
||||
@@ -78,7 +79,7 @@ trait IndexControllerBase extends ControllerBase {
|
||||
}
|
||||
.getOrElse {
|
||||
gitbucket.core.html.index(
|
||||
getRecentActivities(),
|
||||
getRecentPublicActivities(),
|
||||
getVisibleRepositories(None, withoutPhysicalInfo = true),
|
||||
showBannerToCreatePersonalAccessToken = false
|
||||
)
|
||||
@@ -161,7 +162,7 @@ trait IndexControllerBase extends ControllerBase {
|
||||
|
||||
get("/activities.atom") {
|
||||
contentType = "application/atom+xml; type=feed"
|
||||
xml.feed(getRecentActivities())
|
||||
xml.feed(getRecentPublicActivities())
|
||||
}
|
||||
|
||||
post("/sidebar-collapse") {
|
||||
|
||||
@@ -30,6 +30,7 @@ class IssuesController
|
||||
with WebHookPullRequestReviewCommentService
|
||||
with CommitsService
|
||||
with PrioritiesService
|
||||
with RequestCache
|
||||
|
||||
trait IssuesControllerBase extends ControllerBase {
|
||||
self: IssuesService
|
||||
|
||||
@@ -36,6 +36,7 @@ class PullRequestsController
|
||||
with MergeService
|
||||
with ProtectedBranchService
|
||||
with PrioritiesService
|
||||
with RequestCache
|
||||
|
||||
trait PullRequestsControllerBase extends ControllerBase {
|
||||
self: RepositoryService
|
||||
|
||||
@@ -2,7 +2,14 @@ package gitbucket.core.controller
|
||||
|
||||
import java.io.File
|
||||
|
||||
import gitbucket.core.service.{AccountService, ActivityService, PaginationHelper, ReleaseService, RepositoryService}
|
||||
import gitbucket.core.service.{
|
||||
AccountService,
|
||||
ActivityService,
|
||||
PaginationHelper,
|
||||
ReleaseService,
|
||||
RepositoryService,
|
||||
RequestCache
|
||||
}
|
||||
import gitbucket.core.util._
|
||||
import gitbucket.core.util.Directory._
|
||||
import gitbucket.core.util.Implicits._
|
||||
@@ -22,6 +29,7 @@ class ReleaseController
|
||||
with ReadableUsersAuthenticator
|
||||
with ReferrerAuthenticator
|
||||
with WritableUsersAuthenticator
|
||||
with RequestCache
|
||||
|
||||
trait ReleaseControllerBase extends ControllerBase {
|
||||
self: RepositoryService
|
||||
|
||||
@@ -30,8 +30,10 @@ class RepositorySettingsController
|
||||
with ProtectedBranchService
|
||||
with CommitStatusService
|
||||
with DeployKeyService
|
||||
with ActivityService
|
||||
with OwnerAuthenticator
|
||||
with UsersAuthenticator
|
||||
with RequestCache
|
||||
|
||||
trait RepositorySettingsControllerBase extends ControllerBase {
|
||||
self: RepositoryService
|
||||
@@ -40,6 +42,7 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
||||
with ProtectedBranchService
|
||||
with CommitStatusService
|
||||
with DeployKeyService
|
||||
with ActivityService
|
||||
with OwnerAuthenticator
|
||||
with UsersAuthenticator =>
|
||||
|
||||
@@ -97,9 +100,7 @@ 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)
|
||||
@@ -251,9 +252,10 @@ 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 =>
|
||||
@@ -371,7 +373,15 @@ 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()
|
||||
@@ -384,7 +394,15 @@ 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()
|
||||
@@ -435,32 +453,34 @@ 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."
|
||||
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."
|
||||
})
|
||||
} else {
|
||||
"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
|
||||
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.
|
||||
@@ -480,70 +500,77 @@ 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -60,6 +60,7 @@ class RepositoryViewerController
|
||||
with WebHookPullRequestService
|
||||
with WebHookPullRequestReviewCommentService
|
||||
with ProtectedBranchService
|
||||
with RequestCache
|
||||
|
||||
/**
|
||||
* The repository viewer.
|
||||
|
||||
@@ -24,6 +24,7 @@ class WikiController
|
||||
with WebHookService
|
||||
with ReadableUsersAuthenticator
|
||||
with ReferrerAuthenticator
|
||||
with RequestCache
|
||||
|
||||
trait WikiControllerBase extends ControllerBase {
|
||||
self: WikiService
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
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._
|
||||
@@ -7,14 +11,7 @@ trait ActivityComponent extends TemplateComponent { self: Profile =>
|
||||
lazy val Activities = TableQuery[Activities]
|
||||
|
||||
class Activities(tag: Tag) extends Table[Activity](tag, "ACTIVITY") with BasicTemplate {
|
||||
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)
|
||||
def * = ???
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,5 +23,5 @@ case class Activity(
|
||||
message: String,
|
||||
additionalInfo: Option[String],
|
||||
activityDate: java.util.Date,
|
||||
activityId: Int = 0
|
||||
activityId: String
|
||||
)
|
||||
|
||||
@@ -45,7 +45,7 @@ trait CoreProfile
|
||||
with Profile
|
||||
with AccessTokenComponent
|
||||
with AccountComponent
|
||||
with ActivityComponent
|
||||
with ActivityComponent // ActivityComponent has been deprecated, but keep it for binary compatibility
|
||||
with CollaboratorComponent
|
||||
with CommitCommentComponent
|
||||
with CommitStatusComponent
|
||||
|
||||
@@ -17,7 +17,9 @@ trait AccountService {
|
||||
def authenticate(settings: SystemSettings, userName: String, password: String)(
|
||||
implicit s: Session
|
||||
): Option[Account] = {
|
||||
val account = if (settings.ldapAuthentication) {
|
||||
val account = if (password.isEmpty) {
|
||||
None
|
||||
} else if (settings.ldapAuthentication) {
|
||||
ldapAuthentication(settings, userName, password)
|
||||
} else {
|
||||
defaultAuthentication(userName, password)
|
||||
|
||||
@@ -3,65 +3,169 @@ package gitbucket.core.service
|
||||
import gitbucket.core.model.Activity
|
||||
import gitbucket.core.util.JGitUtil
|
||||
import gitbucket.core.model.Profile._
|
||||
import gitbucket.core.model.Profile.profile.blockingApi._
|
||||
import gitbucket.core.util.Directory._
|
||||
import org.json4s._
|
||||
import org.json4s.jackson.Serialization
|
||||
import org.json4s.jackson.Serialization.{read, write}
|
||||
|
||||
import scala.util.Using
|
||||
import java.io.FileOutputStream
|
||||
import java.nio.charset.StandardCharsets
|
||||
import java.util.UUID
|
||||
|
||||
import gitbucket.core.controller.Context
|
||||
import org.apache.commons.io.input.ReversedLinesFileReader
|
||||
|
||||
import scala.collection.mutable.ListBuffer
|
||||
|
||||
trait ActivityService {
|
||||
self: RequestCache =>
|
||||
|
||||
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
|
||||
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 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)
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.sortBy { case (t1, t2) => t1.activityId desc }
|
||||
.map { case (t1, t2) => t1 }
|
||||
.take(30)
|
||||
.list
|
||||
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 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 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 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 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 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 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,
|
||||
@@ -69,16 +173,20 @@ trait ActivityService {
|
||||
activityUserName: String,
|
||||
issueId: Int,
|
||||
title: String
|
||||
)(implicit s: Session): Unit =
|
||||
Activities insert Activity(
|
||||
userName,
|
||||
repositoryName,
|
||||
activityUserName,
|
||||
"open_issue",
|
||||
s"[user:${activityUserName}] opened issue [issue:${userName}/${repositoryName}#${issueId}]",
|
||||
Some(title),
|
||||
currentDate
|
||||
): Unit = {
|
||||
writeLog(
|
||||
Activity(
|
||||
userName,
|
||||
repositoryName,
|
||||
activityUserName,
|
||||
"open_issue",
|
||||
s"[user:${activityUserName}] opened issue [issue:${userName}/${repositoryName}#${issueId}]",
|
||||
Some(title),
|
||||
currentDate,
|
||||
UUID.randomUUID().toString
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
def recordCloseIssueActivity(
|
||||
userName: String,
|
||||
@@ -86,16 +194,20 @@ trait ActivityService {
|
||||
activityUserName: String,
|
||||
issueId: Int,
|
||||
title: String
|
||||
)(implicit s: Session): Unit =
|
||||
Activities insert Activity(
|
||||
userName,
|
||||
repositoryName,
|
||||
activityUserName,
|
||||
"close_issue",
|
||||
s"[user:${activityUserName}] closed issue [issue:${userName}/${repositoryName}#${issueId}]",
|
||||
Some(title),
|
||||
currentDate
|
||||
): Unit = {
|
||||
writeLog(
|
||||
Activity(
|
||||
userName,
|
||||
repositoryName,
|
||||
activityUserName,
|
||||
"close_issue",
|
||||
s"[user:${activityUserName}] closed issue [issue:${userName}/${repositoryName}#${issueId}]",
|
||||
Some(title),
|
||||
currentDate,
|
||||
UUID.randomUUID().toString
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
def recordClosePullRequestActivity(
|
||||
userName: String,
|
||||
@@ -103,16 +215,20 @@ trait ActivityService {
|
||||
activityUserName: String,
|
||||
issueId: Int,
|
||||
title: String
|
||||
)(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
|
||||
): Unit = {
|
||||
writeLog(
|
||||
Activity(
|
||||
userName,
|
||||
repositoryName,
|
||||
activityUserName,
|
||||
"close_issue",
|
||||
s"[user:${activityUserName}] closed pull request [pullreq:${userName}/${repositoryName}#${issueId}]",
|
||||
Some(title),
|
||||
currentDate,
|
||||
UUID.randomUUID().toString
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
def recordReopenIssueActivity(
|
||||
userName: String,
|
||||
@@ -120,16 +236,20 @@ trait ActivityService {
|
||||
activityUserName: String,
|
||||
issueId: Int,
|
||||
title: String
|
||||
)(implicit s: Session): Unit =
|
||||
Activities insert Activity(
|
||||
userName,
|
||||
repositoryName,
|
||||
activityUserName,
|
||||
"reopen_issue",
|
||||
s"[user:${activityUserName}] reopened issue [issue:${userName}/${repositoryName}#${issueId}]",
|
||||
Some(title),
|
||||
currentDate
|
||||
): Unit = {
|
||||
writeLog(
|
||||
Activity(
|
||||
userName,
|
||||
repositoryName,
|
||||
activityUserName,
|
||||
"reopen_issue",
|
||||
s"[user:${activityUserName}] reopened issue [issue:${userName}/${repositoryName}#${issueId}]",
|
||||
Some(title),
|
||||
currentDate,
|
||||
UUID.randomUUID().toString
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
def recordReopenPullRequestActivity(
|
||||
userName: String,
|
||||
@@ -137,16 +257,20 @@ trait ActivityService {
|
||||
activityUserName: String,
|
||||
issueId: Int,
|
||||
title: String
|
||||
)(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
|
||||
): Unit = {
|
||||
writeLog(
|
||||
Activity(
|
||||
userName,
|
||||
repositoryName,
|
||||
activityUserName,
|
||||
"reopen_issue",
|
||||
s"[user:${activityUserName}] reopened pull request [issue:${userName}/${repositoryName}#${issueId}]",
|
||||
Some(title),
|
||||
currentDate,
|
||||
UUID.randomUUID().toString
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
def recordCommentIssueActivity(
|
||||
userName: String,
|
||||
@@ -154,16 +278,20 @@ trait ActivityService {
|
||||
activityUserName: String,
|
||||
issueId: Int,
|
||||
comment: String
|
||||
)(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
|
||||
): 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
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
def recordCommentPullRequestActivity(
|
||||
userName: String,
|
||||
@@ -171,16 +299,20 @@ trait ActivityService {
|
||||
activityUserName: String,
|
||||
issueId: Int,
|
||||
comment: String
|
||||
)(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
|
||||
): 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
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
def recordCommentCommitActivity(
|
||||
userName: String,
|
||||
@@ -188,32 +320,40 @@ trait ActivityService {
|
||||
activityUserName: String,
|
||||
commitId: String,
|
||||
comment: String
|
||||
)(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
|
||||
): 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
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
def recordCreateWikiPageActivity(
|
||||
userName: String,
|
||||
repositoryName: String,
|
||||
activityUserName: String,
|
||||
pageName: String
|
||||
)(implicit s: Session): Unit =
|
||||
Activities insert Activity(
|
||||
userName,
|
||||
repositoryName,
|
||||
activityUserName,
|
||||
"create_wiki",
|
||||
s"[user:${activityUserName}] created the [repo:${userName}/${repositoryName}] wiki",
|
||||
Some(pageName),
|
||||
currentDate
|
||||
): Unit = {
|
||||
writeLog(
|
||||
Activity(
|
||||
userName,
|
||||
repositoryName,
|
||||
activityUserName,
|
||||
"create_wiki",
|
||||
s"[user:${activityUserName}] created the [repo:${userName}/${repositoryName}] wiki",
|
||||
Some(pageName),
|
||||
currentDate,
|
||||
UUID.randomUUID().toString
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
def recordEditWikiPageActivity(
|
||||
userName: String,
|
||||
@@ -221,16 +361,20 @@ trait ActivityService {
|
||||
activityUserName: String,
|
||||
pageName: String,
|
||||
commitId: String
|
||||
)(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
|
||||
): Unit = {
|
||||
writeLog(
|
||||
Activity(
|
||||
userName,
|
||||
repositoryName,
|
||||
activityUserName,
|
||||
"edit_wiki",
|
||||
s"[user:${activityUserName}] edited the [repo:${userName}/${repositoryName}] wiki",
|
||||
Some(pageName + ":" + commitId),
|
||||
currentDate,
|
||||
UUID.randomUUID().toString
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
def recordPushActivity(
|
||||
userName: String,
|
||||
@@ -238,23 +382,27 @@ trait ActivityService {
|
||||
activityUserName: String,
|
||||
branchName: String,
|
||||
commits: List[JGitUtil.CommitInfo]
|
||||
)(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
|
||||
): 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
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
def recordCreateTagActivity(
|
||||
userName: String,
|
||||
@@ -262,16 +410,20 @@ trait ActivityService {
|
||||
activityUserName: String,
|
||||
tagName: String,
|
||||
commits: List[JGitUtil.CommitInfo]
|
||||
)(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
|
||||
): 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
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
def recordDeleteTagActivity(
|
||||
userName: String,
|
||||
@@ -279,61 +431,80 @@ trait ActivityService {
|
||||
activityUserName: String,
|
||||
tagName: String,
|
||||
commits: List[JGitUtil.CommitInfo]
|
||||
)(implicit s: Session): Unit =
|
||||
Activities insert Activity(
|
||||
userName,
|
||||
repositoryName,
|
||||
activityUserName,
|
||||
"delete_tag",
|
||||
s"[user:${activityUserName}] deleted tag ${tagName} at [repo:${userName}/${repositoryName}]",
|
||||
None,
|
||||
currentDate
|
||||
): Unit = {
|
||||
writeLog(
|
||||
Activity(
|
||||
userName,
|
||||
repositoryName,
|
||||
activityUserName,
|
||||
"delete_tag",
|
||||
s"[user:${activityUserName}] deleted tag ${tagName} at [repo:${userName}/${repositoryName}]",
|
||||
None,
|
||||
currentDate,
|
||||
UUID.randomUUID().toString
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
def recordCreateBranchActivity(
|
||||
userName: String,
|
||||
repositoryName: String,
|
||||
activityUserName: String,
|
||||
branchName: String
|
||||
)(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
|
||||
): 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
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
def recordDeleteBranchActivity(
|
||||
userName: String,
|
||||
repositoryName: String,
|
||||
activityUserName: String,
|
||||
branchName: String
|
||||
)(implicit s: Session): Unit =
|
||||
Activities insert Activity(
|
||||
userName,
|
||||
repositoryName,
|
||||
activityUserName,
|
||||
"delete_branch",
|
||||
s"[user:${activityUserName}] deleted branch ${branchName} at [repo:${userName}/${repositoryName}]",
|
||||
None,
|
||||
currentDate
|
||||
): Unit = {
|
||||
writeLog(
|
||||
Activity(
|
||||
userName,
|
||||
repositoryName,
|
||||
activityUserName,
|
||||
"delete_branch",
|
||||
s"[user:${activityUserName}] deleted branch ${branchName} at [repo:${userName}/${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 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 recordPullRequestActivity(
|
||||
userName: String,
|
||||
@@ -341,16 +512,20 @@ trait ActivityService {
|
||||
activityUserName: String,
|
||||
issueId: Int,
|
||||
title: String
|
||||
)(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
|
||||
): Unit = {
|
||||
writeLog(
|
||||
Activity(
|
||||
userName,
|
||||
repositoryName,
|
||||
activityUserName,
|
||||
"open_pullreq",
|
||||
s"[user:${activityUserName}] opened pull request [pullreq:${userName}/${repositoryName}#${issueId}]",
|
||||
Some(title),
|
||||
currentDate,
|
||||
UUID.randomUUID().toString
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
def recordMergeActivity(
|
||||
userName: String,
|
||||
@@ -358,16 +533,20 @@ trait ActivityService {
|
||||
activityUserName: String,
|
||||
issueId: Int,
|
||||
message: String
|
||||
)(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
|
||||
): Unit = {
|
||||
writeLog(
|
||||
Activity(
|
||||
userName,
|
||||
repositoryName,
|
||||
activityUserName,
|
||||
"merge_pullreq",
|
||||
s"[user:${activityUserName}] merged pull request [pullreq:${userName}/${repositoryName}#${issueId}]",
|
||||
Some(message),
|
||||
currentDate,
|
||||
UUID.randomUUID().toString
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
def recordReleaseActivity(
|
||||
userName: String,
|
||||
@@ -375,16 +554,20 @@ trait ActivityService {
|
||||
activityUserName: String,
|
||||
releaseName: String,
|
||||
tagName: String
|
||||
)(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
|
||||
): 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
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private def cut(value: String, length: Int): String =
|
||||
if (value.length > length) value.substring(0, length) + "..." else value
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package gitbucket.core.service
|
||||
|
||||
import gitbucket.core.api.JsonFormat
|
||||
import gitbucket.core.controller.Context
|
||||
import gitbucket.core.util._
|
||||
import gitbucket.core.util.SyntaxSugars._
|
||||
@@ -9,14 +8,11 @@ import gitbucket.core.model.Profile._
|
||||
import gitbucket.core.model.Profile.profile.blockingApi._
|
||||
import gitbucket.core.model.Profile.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.{CommitInfo, FileInfo}
|
||||
import gitbucket.core.util.JGitUtil.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 {
|
||||
@@ -119,15 +115,6 @@ 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(
|
||||
@@ -213,50 +200,6 @@ 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) {
|
||||
@@ -304,7 +247,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
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
package gitbucket.core.service
|
||||
|
||||
import gitbucket.core.model.{Session, Issue, Account}
|
||||
import gitbucket.core.model.{Account, Issue, Repository, Session}
|
||||
import gitbucket.core.util.Implicits
|
||||
import gitbucket.core.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.
|
||||
@@ -23,21 +25,41 @@ trait RequestCache
|
||||
private implicit def context2Session(implicit context: Context): Session =
|
||||
request2Session(context.request)
|
||||
|
||||
def getIssue(userName: String, repositoryName: String, issueId: String)(implicit context: Context): Option[Issue] = {
|
||||
def getIssueFromCache(userName: String, repositoryName: String, issueId: String)(
|
||||
implicit context: Context
|
||||
): Option[Issue] = {
|
||||
context.cache(s"issue.${userName}/${repositoryName}#${issueId}") {
|
||||
super.getIssue(userName, repositoryName, issueId)
|
||||
}
|
||||
}
|
||||
|
||||
def getAccountByUserName(userName: String)(implicit context: Context): Option[Account] = {
|
||||
def getAccountByUserNameFromCache(userName: String)(implicit context: Context): Option[Account] = {
|
||||
context.cache(s"account.${userName}") {
|
||||
super.getAccountByUserName(userName)
|
||||
}
|
||||
}
|
||||
|
||||
def getAccountByMailAddress(mailAddress: String)(implicit context: Context): Option[Account] = {
|
||||
def getAccountByMailAddressFromCache(mailAddress: String)(implicit context: Context): Option[Account] = {
|
||||
context.cache(s"account.${mailAddress}") {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -240,7 +240,8 @@ class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl:
|
||||
with WebHookPullRequestService
|
||||
with WebHookPullRequestReviewCommentService
|
||||
with CommitsService
|
||||
with SystemSettingsService {
|
||||
with SystemSettingsService
|
||||
with RequestCache {
|
||||
|
||||
private val logger = LoggerFactory.getLogger(classOf[CommitLogHook])
|
||||
private var existIds: Seq[String] = Nil
|
||||
|
||||
@@ -2,11 +2,10 @@ 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.{ActivityService, SystemSettingsService}
|
||||
import gitbucket.core.service.SystemSettingsService
|
||||
import gitbucket.core.util.DatabaseConfig
|
||||
import gitbucket.core.util.Directory._
|
||||
import gitbucket.core.util.JDBCUtil._
|
||||
@@ -21,8 +20,6 @@ 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
|
||||
@@ -35,23 +32,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")
|
||||
@@ -95,10 +92,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 = {
|
||||
@@ -172,8 +169,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
|
||||
@@ -181,21 +178,3 @@ class InitializeListener extends ServletContextListener with SystemSettingsServi
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class DeleteOldActivityActor extends Actor with SystemSettingsService with ActivityService {
|
||||
|
||||
private val logger = Logging(context.system, this)
|
||||
|
||||
def receive = {
|
||||
case s: String => {
|
||||
loadSystemSettings().activityLogLimit.foreach { limit =>
|
||||
if (limit > 0) {
|
||||
Database() withTransaction { implicit session =>
|
||||
val rows = deleteOldActivities(limit)
|
||||
logger.info(s"Deleted ${rows} activity logs")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +29,8 @@ 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"
|
||||
|
||||
@@ -17,7 +17,7 @@ trait AvatarImageProvider { self: RequestCache =>
|
||||
|
||||
val src = if (mailAddress.isEmpty) {
|
||||
// by user name
|
||||
getAccountByUserName(userName).map { account =>
|
||||
getAccountByUserNameFromCache(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
|
||||
getAccountByMailAddress(mailAddress).map { account =>
|
||||
getAccountByMailAddressFromCache(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 {
|
||||
|
||||
@@ -16,7 +16,7 @@ trait LinkConverter { self: RequestCache =>
|
||||
val userName = repository.repository.userName
|
||||
val repositoryName = repository.repository.repositoryName
|
||||
|
||||
getIssue(userName, repositoryName, issueId.toString) match {
|
||||
getIssueFromCache(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 =>
|
||||
getAccountByUserName(m.group(2)).map { _ =>
|
||||
getAccountByUserNameFromCache(m.group(2)).map { _ =>
|
||||
s"""<code><a href="${context.path}/${m.group(2)}/${m.group(3)}/commit/${m.group(4)}">${m.group(2)}/${m.group(
|
||||
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 =>
|
||||
getIssue(m.group(2), m.group(3), m.group(4)) match {
|
||||
getIssueFromCache(m.group(2), m.group(3), m.group(4)) match {
|
||||
case Some(issue) if (issue.isPullRequest) =>
|
||||
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 =>
|
||||
getAccountByUserName(m.group(2)).map { _ =>
|
||||
getAccountByUserNameFromCache(m.group(2)).map { _ =>
|
||||
s"""<code><a href="${context.path}/${m.group(2)}/${repository.name}/commit/${m.group(3)}">${m.group(2)}@${m
|
||||
.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 =>
|
||||
getIssue(m.group(2), repository.name, m.group(3)) match {
|
||||
getIssueFromCache(m.group(2), repository.name, m.group(3)) match {
|
||||
case Some(issue) if (issue.isPullRequest) =>
|
||||
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)
|
||||
getIssue(repository.owner, repository.name, m.group(3)) match {
|
||||
getIssueFromCache(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 =>
|
||||
getAccountByUserName(m.group(2)).map { _ =>
|
||||
getAccountByUserNameFromCache(m.group(2)).map { _ =>
|
||||
s"""<a href="${context.path}/${m.group(2)}">@${m.group(2)}</a>"""
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ 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
|
||||
@@ -196,7 +195,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("$", "\\$"))
|
||||
}
|
||||
}
|
||||
@@ -204,50 +203,64 @@ 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+))\\]",
|
||||
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>"""
|
||||
)
|
||||
.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))
|
||||
}
|
||||
}
|
||||
)
|
||||
// format: off
|
||||
|
||||
/**
|
||||
* Remove html tags from the given Html instance.
|
||||
@@ -333,9 +346,9 @@ object helpers extends AvatarImageProvider with LinkConverter with RequestCache
|
||||
content: Html
|
||||
)(implicit context: Context): Html =
|
||||
(if (mailAddress.isEmpty) {
|
||||
getAccountByUserName(userName)
|
||||
getAccountByUserNameFromCache(userName)
|
||||
} else {
|
||||
getAccountByMailAddress(mailAddress)
|
||||
getAccountByMailAddressFromCache(mailAddress)
|
||||
}).map { account =>
|
||||
Html(s"""<a href="${url(account.userName)}" class="${styleClass}">${content}</a>""")
|
||||
} getOrElse content
|
||||
|
||||
@@ -7,21 +7,24 @@
|
||||
@activities.map { activity =>
|
||||
<div class="block">
|
||||
@(activity.activityType match {
|
||||
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"){
|
||||
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){
|
||||
<div class="small activity-message">
|
||||
{activity.additionalInfo.get.split("\n").reverse.take(4).zipWithIndex.map{ case (commit, i) =>
|
||||
if(i == 3){
|
||||
@@ -37,12 +40,12 @@
|
||||
}}
|
||||
</div>
|
||||
}
|
||||
case "create_wiki" => customActivity(activity, "book"){
|
||||
case "create_wiki" => customActivity(activity){
|
||||
<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, "book"){
|
||||
case "edit_wiki" => customActivity(activity){
|
||||
activity.additionalInfo.get.split(":") match {
|
||||
case Array(pageName, commitId) =>
|
||||
<div class="small activity-message">
|
||||
@@ -60,26 +63,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
@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>
|
||||
*@
|
||||
@customActivity(activity: gitbucket.core.model.Activity)(additionalInfo: Any) = {
|
||||
<div>
|
||||
<div class="muted small">@gitbucket.core.helper.html.datetimeago(activity.activityDate)</div>
|
||||
<div class="strong">
|
||||
@@ -90,10 +74,7 @@
|
||||
</div>
|
||||
}
|
||||
|
||||
@simpleActivity(activity: gitbucket.core.model.Activity, image: String) = {
|
||||
@*
|
||||
<div class="activity-icon-small"><i class="octicon octicon-@image"></i></div>
|
||||
*@
|
||||
@simpleActivity(activity: gitbucket.core.model.Activity) = {
|
||||
<div>
|
||||
<span class="muted small">@gitbucket.core.helper.html.datetimeago(activity.activityDate)</span>
|
||||
<div>
|
||||
|
||||
@@ -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 WebHookPullRequestService with WebHookPullRequestReviewCommentService with RequestCache {}
|
||||
val branch = "master"
|
||||
val issueId = 10
|
||||
def initRepository(owner: String, name: String): File = {
|
||||
|
||||
@@ -18,7 +18,8 @@ class PullRequestServiceSpec
|
||||
with PrioritiesService
|
||||
with WebHookService
|
||||
with WebHookPullRequestService
|
||||
with WebHookPullRequestReviewCommentService {
|
||||
with WebHookPullRequestReviewCommentService
|
||||
with RequestCache {
|
||||
|
||||
def swap(r: (Issue, PullRequest)) = (r._2 -> r._1)
|
||||
|
||||
|
||||
@@ -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 WebHookPullRequestReviewCommentService with RequestCache {
|
||||
override def fetchAsPullRequest(
|
||||
userName: String,
|
||||
repositoryName: String,
|
||||
|
||||
@@ -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 MilestonesService with PrioritiesService with WebHookPullRequestReviewCommentService with RequestCache
|
||||
|
||||
test("WebHookPullRequestService.getPullRequestsByRequestForWebhook") {
|
||||
withTestDB { implicit session =>
|
||||
|
||||
@@ -169,8 +169,9 @@ class AvatarImageProviderSpec extends FunSpec with MockitoSugar {
|
||||
context: Context
|
||||
): Html = getAvatarImageHtml(userName, size, mailAddress, tooltip)
|
||||
|
||||
override def getAccountByMailAddress(mailAddress: String)(implicit context: Context): Option[Account] = account
|
||||
override def getAccountByUserName(userName: String)(implicit context: Context): Option[Account] = account
|
||||
override def getAccountByMailAddressFromCache(mailAddress: String)(implicit context: Context): Option[Account] =
|
||||
account
|
||||
override def getAccountByUserNameFromCache(userName: String)(implicit context: Context): Option[Account] = account
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user