Compare commits

..

1 Commits

Author SHA1 Message Date
takezoe
753403b4c8 Implement /meta API endpoint 2022-10-30 21:02:23 +09:00
99 changed files with 651 additions and 1341 deletions

View File

@@ -4,12 +4,8 @@
- [ ] searched for similar already existing issue
- [ ] read the documentation and [wiki](https://github.com/gitbucket/gitbucket/wiki)
<!--
*(if you have performed all the above, remove the paragraph and continue describing the issue with template below)*
-->
## Issue
**Impacted version**: xxxx

View File

@@ -8,9 +8,9 @@ jobs:
timeout-minutes: 30
strategy:
matrix:
java: [11, 21]
java: [8, 11, 17]
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v3
- name: Cache
uses: actions/cache@v3
env:
@@ -27,9 +27,9 @@ jobs:
java-version: ${{ matrix.java }}
distribution: adopt
- name: Run tests
run: sbt scalafmtSbtCheck scalafmtCheckAll test
run: sbt scalafmtSbtCheck scalafmtCheck test:scalafmtCheck test
- name: Scala 3
run: sbt '++ 3.x' update # TODO
run: sbt '++ 3.1.2!' update # TODO
- name: Build executable
run: sbt executable
- name: Upload artifacts

1
.gitignore vendored
View File

@@ -4,7 +4,6 @@
.ensime_cache
.DS_Store
.java-version
.tmp
# sbt specific
dist/*

View File

@@ -3,6 +3,7 @@ updates.limit = 3
updates.includeScala = true
updates.pin = [
{ groupId = "org.eclipse.jetty", version = "10." }
{ groupId = "org.mariadb.jdbc", version = "2." }
{ groupId = "org.eclipse.jetty", version = "9." }
{ groupId = "org.eclipse.jgit", version = "5." }
{ groupId = "com.zaxxer", version = "4." }
]

View File

@@ -1,22 +1,6 @@
# Changelog
All changes to the project will be documented in this file.
## 4.40.0 - 22 Oct 2023
- Drop Java 8 support
- Improve git push performance
- Show activities of all visible repositories as news feed
- Support custom fields of issues and pull requests in search condition
- Configurable default branch name
## 4.39.0 - 29 Apr 2023
- Support enum type in custom fields of Issues and Pull requests
- Hide large diffs by default
- Add new options to make it possible to run GitBucket using multiple machines
- Fix many API issues
## 4.38.4 - 2 Nov 2022
- Downgrade MariaDB JDBC drive to avoid unknown error
## 4.38.3 - 30 Oct 2022
- Fix several issues around multiple assignees in issues and pull requests
- Fix IllegalStateException when returning unknown avatar image

View File

@@ -59,13 +59,29 @@ Support
- If you can't find same question and report, send it to our [Gitter room](https://gitter.im/gitbucket/gitbucket) before raising an issue.
- The highest priority of GitBucket is the ease of installation and API compatibility with GitHub, so your feature request might be rejected if they go against those principles.
What's New in 4.40.x
What's New in 4.38.x
-------------
## 4.40.0 - 22 Oct 2023
- Drop Java 8 support
- Improve git push performance
- Show activities of all visible repositories as news feed
- Support custom fields of issues and pull requests in search condition
- Configurable default branch name
## 4.38.3 - 30 Oct 2022
- Fix several issues around multiple assignees in issues and pull requests
- Fix IllegalStateException when returning unknown avatar image
## 4.38.2 - 20 Sep 2022
- Resurrect assignee icons on the issue list
## 4.38.1 - 10 Sep 2022
- Fix comment diff in Chrome 105
- Fix Markdown table CSS
- Fix HTML rendering of multiple asignees
## 4.38.0 - 3 Sep 2022
- Support multiple assignees for Issues and Pull requests
- Custom fields for issues and pull requests
- Reset password by users
- Allow to configure Jetty idle timeout in standalone mode
- Horizontal scroll for too wide tables in Markdown
- Hide header content on signin and register page
- Fix the default charset of the online editor in the repository viewer
- Fix the milestone count
- Some improvements and bugfixes for WebAPI and WebHook
See the [change log](CHANGELOG.md) for all of the updates.

108
build.sbt
View File

@@ -1,12 +1,12 @@
import sbtlicensereport.license.{DepModuleInfo, LicenseInfo}
import com.typesafe.sbt.license.{DepModuleInfo, LicenseInfo}
import com.jsuereth.sbtpgp.PgpKeys._
val Organization = "io.github.gitbucket"
val Name = "gitbucket"
val GitBucketVersion = "4.40.0"
val ScalatraVersion = "3.0.0"
val JettyVersion = "10.0.17"
val JgitVersion = "6.7.0.202309050840-r"
val GitBucketVersion = "4.38.3"
val ScalatraVersion = "2.8.4"
val JettyVersion = "9.4.49.v20220914"
val JgitVersion = "5.13.1.202206130422-r"
lazy val root = (project in file("."))
.enablePlugins(SbtTwirl, ScalatraPlugin)
@@ -15,9 +15,7 @@ sourcesInBase := false
organization := Organization
name := Name
version := GitBucketVersion
scalaVersion := "2.13.12"
crossScalaVersions += "3.3.1"
scalaVersion := "2.13.10"
// scalafmtOnCompile := true
@@ -32,47 +30,56 @@ resolvers ++= Seq(
libraryDependencies ++= Seq(
"org.eclipse.jgit" % "org.eclipse.jgit.http.server" % JgitVersion,
"org.eclipse.jgit" % "org.eclipse.jgit.archive" % JgitVersion,
"org.scalatra" %% "scalatra-javax" % ScalatraVersion,
"org.scalatra" %% "scalatra-json-javax" % ScalatraVersion,
"org.scalatra" %% "scalatra-forms-javax" % ScalatraVersion,
"org.json4s" %% "json4s-jackson" % "4.0.6",
"commons-io" % "commons-io" % "2.14.0",
"org.scalatra" %% "scalatra" % ScalatraVersion cross CrossVersion.for3Use2_13,
"org.scalatra" %% "scalatra-json" % ScalatraVersion cross CrossVersion.for3Use2_13,
"org.scalatra" %% "scalatra-forms" % ScalatraVersion cross CrossVersion.for3Use2_13,
"org.json4s" %% "json4s-jackson" % "4.0.6" cross CrossVersion.for3Use2_13,
"commons-io" % "commons-io" % "2.11.0",
"io.github.gitbucket" % "solidbase" % "1.0.5",
"io.github.gitbucket" % "markedj" % "1.0.18",
"org.apache.commons" % "commons-compress" % "1.24.0",
"io.github.gitbucket" % "markedj" % "1.0.17",
"org.apache.commons" % "commons-compress" % "1.21",
"org.apache.commons" % "commons-email" % "1.5",
"commons-net" % "commons-net" % "3.10.0",
"org.apache.httpcomponents" % "httpclient" % "4.5.14",
"org.apache.sshd" % "apache-sshd" % "2.11.0" exclude ("org.slf4j", "slf4j-jdk14") exclude ("org.apache.sshd", "sshd-mina") exclude ("org.apache.sshd", "sshd-netty"),
"org.apache.tika" % "tika-core" % "2.9.1",
"com.github.takezoe" %% "blocking-slick" % "0.0.14",
"commons-net" % "commons-net" % "3.8.0",
"org.apache.httpcomponents" % "httpclient" % "4.5.13",
"org.apache.sshd" % "apache-sshd" % "2.9.1" exclude ("org.slf4j", "slf4j-jdk14") exclude ("org.apache.sshd", "sshd-mina") exclude ("org.apache.sshd", "sshd-netty"),
"org.apache.tika" % "tika-core" % "2.5.0",
"com.github.takezoe" %% "blocking-slick-32" % "0.0.12" cross CrossVersion.for3Use2_13,
"com.novell.ldap" % "jldap" % "2009-10-07",
"com.h2database" % "h2" % "1.4.199",
"org.mariadb.jdbc" % "mariadb-java-client" % "2.7.6",
"org.postgresql" % "postgresql" % "42.6.0",
"ch.qos.logback" % "logback-classic" % "1.4.11",
"org.mariadb.jdbc" % "mariadb-java-client" % "3.0.8",
"org.postgresql" % "postgresql" % "42.5.0",
"ch.qos.logback" % "logback-classic" % "1.2.11",
"com.zaxxer" % "HikariCP" % "4.0.3" exclude ("org.slf4j", "slf4j-api"),
"com.typesafe" % "config" % "1.4.3",
"com.typesafe" % "config" % "1.4.2",
"fr.brouillard.oss.security.xhub" % "xhub4j-core" % "1.1.0",
"io.github.java-diff-utils" % "java-diff-utils" % "4.12",
"org.cache2k" % "cache2k-all" % "1.6.0.Final",
"net.coobird" % "thumbnailator" % "0.4.20",
"net.coobird" % "thumbnailator" % "0.4.18",
"com.github.zafarkhaja" % "java-semver" % "0.9.0",
"com.nimbusds" % "oauth2-oidc-sdk" % "11.4",
"com.nimbusds" % "oauth2-oidc-sdk" % "10.1",
"org.eclipse.jetty" % "jetty-webapp" % JettyVersion % "provided",
"javax.servlet" % "javax.servlet-api" % "3.1.0" % "provided",
"junit" % "junit" % "4.13.2" % "test",
"org.scalatra" %% "scalatra-scalatest-javax" % ScalatraVersion % "test",
"org.mockito" % "mockito-core" % "5.6.0" % "test",
"com.dimafeng" %% "testcontainers-scala" % "0.41.0" % "test",
"org.testcontainers" % "mysql" % "1.19.1" % "test",
"org.testcontainers" % "postgresql" % "1.19.1" % "test",
"org.scalatra" %% "scalatra-scalatest" % ScalatraVersion % "test" cross CrossVersion.for3Use2_13,
"org.mockito" % "mockito-core" % "4.8.1" % "test",
"com.dimafeng" %% "testcontainers-scala" % "0.40.11" % "test",
"org.testcontainers" % "mysql" % "1.17.5" % "test",
"org.testcontainers" % "postgresql" % "1.17.5" % "test",
"net.i2p.crypto" % "eddsa" % "0.3.0",
"is.tagomor.woothee" % "woothee-java" % "1.11.0",
"org.ec4j.core" % "ec4j-core" % "0.3.0",
"org.kohsuke" % "github-api" % "1.317" % "test"
"org.kohsuke" % "github-api" % "1.313" % "test"
)
libraryDependencies ~= {
_.map {
case x if x.name == "twirl-api" =>
x cross CrossVersion.for3Use2_13
case x =>
x
}
}
// Compiler settings
scalacOptions := Seq(
"-deprecation",
@@ -82,15 +89,7 @@ scalacOptions := Seq(
"-Wunused:imports",
"-Wconf:cat=unused&src=twirl/.*:s,cat=unused&src=scala/gitbucket/core/model/[^/]+\\.scala:s"
)
scalacOptions ++= {
scalaBinaryVersion.value match {
case "2.13" =>
Seq("-Xsource:3")
case _ =>
Nil
}
}
compile / javacOptions ++= Seq("-target", "11", "-source", "11")
compile / javacOptions ++= Seq("-target", "8", "-source", "8")
Jetty / javaOptions += "-Dlogback.configurationFile=/logback-dev.xml"
// Test settings
@@ -124,14 +123,15 @@ signedArtifacts := {
val ExecutableConfig = config("executable").hide
Keys.ivyConfigurations += ExecutableConfig
libraryDependencies ++= Seq(
"org.eclipse.jetty" % "jetty-security" % JettyVersion % "executable",
"org.eclipse.jetty" % "jetty-webapp" % JettyVersion % "executable",
"org.eclipse.jetty" % "jetty-server" % JettyVersion % "executable",
"org.eclipse.jetty" % "jetty-xml" % JettyVersion % "executable",
"org.eclipse.jetty" % "jetty-http" % JettyVersion % "executable",
"org.eclipse.jetty" % "jetty-servlet" % JettyVersion % "executable",
"org.eclipse.jetty" % "jetty-io" % JettyVersion % "executable",
"org.eclipse.jetty" % "jetty-util" % JettyVersion % "executable"
"org.eclipse.jetty" % "jetty-security" % JettyVersion % "executable",
"org.eclipse.jetty" % "jetty-webapp" % JettyVersion % "executable",
"org.eclipse.jetty" % "jetty-continuation" % JettyVersion % "executable",
"org.eclipse.jetty" % "jetty-server" % JettyVersion % "executable",
"org.eclipse.jetty" % "jetty-xml" % JettyVersion % "executable",
"org.eclipse.jetty" % "jetty-http" % JettyVersion % "executable",
"org.eclipse.jetty" % "jetty-servlet" % JettyVersion % "executable",
"org.eclipse.jetty" % "jetty-io" % JettyVersion % "executable",
"org.eclipse.jetty" % "jetty-util" % JettyVersion % "executable"
)
// Run package task before test to generate target/webapp for integration test
@@ -159,7 +159,8 @@ executableKey := {
val jettyJars = Keys.update.value select configurationFilter(name = ExecutableConfig.name)
jettyJars foreach { jar =>
IO unzip (jar, temp, (name: String) =>
(name startsWith "javax/") || (name startsWith "org/") || (name startsWith "META-INF/services/"))
(name startsWith "javax/") ||
(name startsWith "org/"))
}
// include original war file
@@ -184,7 +185,7 @@ executableKey := {
val url = "https://github.com/" +
s"gitbucket/gitbucket-${pluginId}-plugin/releases/download/${pluginVersion}/gitbucket-${pluginId}-plugin-${pluginVersion}.jar"
log info s"Download: ${url}"
IO transfer (new java.net.URI(url).toURL.openStream, pluginsDir / url.substring(url.lastIndexOf("/") + 1))
IO transfer (new java.net.URL(url).openStream, pluginsDir / url.substring(url.lastIndexOf("/") + 1))
case _ => ()
}
}
@@ -285,9 +286,6 @@ Test / testOptions ++= {
}
Jetty / javaOptions ++= Seq(
"-Dlogback.configurationFile=/logback-dev.xml",
"-Xdebug",
"-Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=8000",
"-Dorg.eclipse.jetty.annotations.AnnotationParser.LEVEL=OFF",
//"-Ddev-features=keep-session"
"-Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=8000"
)

View File

@@ -1,7 +1,7 @@
How to build and run from the source tree
========
First of all, Install [sbt](https://www.scala-sbt.org/index.html).
First of all, Install [sbt](http://www.scala-sbt.org/index.html).
```shell
$ brew install sbt
@@ -18,21 +18,7 @@ $ sbt ~jetty:start
Then access `http://localhost:8080/` in your browser. The default administrator account is `root` and password is `root`.
Source code modifications are detected and a reloading happens automatically.
You can modify the logging configuration by editing `src/main/resources/logback-dev.xml`.
Note that HttpSession is cleared when auto-reloading happened.
This is a bit annoying when developing features that requires sign-in.
You can keep HttpSession even if GitBucket is restarted by enabling this configuration in `build.sbt`:
https://github.com/gitbucket/gitbucket/blob/d5c083b70f7f3748d080166252e9a3dcaf579648/build.sbt#L292
Or by launching GitBucket with the following command:
```shell
sbt '; set Jetty/javaOptions += "-Ddev-features=keep-session" ; ~jetty:start'
```
Note that this feature serializes HttpSession on the local disk and assigns all requests to the same session
which means you cannot test multi users behavior in this mode.
Source code modifications are detected and a reloading happens automatically. You can modify the logging configuration by editing `src/main/resources/logback-dev.xml`.
Build war file
--------
@@ -51,8 +37,7 @@ To build an executable war file, run
$ sbt executable
```
at the top of the source tree. It generates executable `gitbucket.war` into `target/executable`.
We release this war file as release artifact.
at the top of the source tree. It generates executable `gitbucket.war` into `target/executable`. We release this war file as release artifact.
Run tests spec
---------

View File

@@ -5,17 +5,13 @@ GitBucket persists all data into __HOME/.gitbucket__ in default (In 1.9 or befor
This directory has following structure:
```
* /HOME/.gitbucket
* gitbucket.conf
* database.conf
* activity.log
* data.mv.db, data.trace.db (H2 data files if the default embed H2 is used)
* /HOME/gitbucket
* /repositories
* /USER_NAME
* /REPO_NAME.git (substance of repository. GitServlet sees this directory)
* /REPO_NAME.wiki.git (wiki repository)
* /REPO_NAME
* /issues (files attached to issue)
* /issues (files which are attached to issue)
* /lfs (LFS managed files)
* /data
* /USER_NAME
@@ -24,8 +20,6 @@ This directory has following structure:
* /plugins
* plugin.jar
* /.installed (copied available plugins from the parent directory automatically)
* /sessions
* HTTP session data created (if '--save_sessions' option is used in the standalone mode)
* /tmp
* /_upload
* /SESSION_ID (removed at session timeout)

View File

@@ -1,6 +1,6 @@
Mapping and Validation
========
GitBucket uses [scalatra-forms](https://scalatra.org/guides/2.8/formats/forms.html) to validate request parameters and map them to the scala object. This is inspired by Play2 form mapping / validation.
GitBucket uses [scalatra-forms](http://scalatra.org/guides/2.6/formats/forms.html) to validate request parameters and map them to the scala object. This is inspired by Play2 form mapping / validation.
At first, define the mapping as following:

View File

@@ -1 +1 @@
sbt.version=1.9.6
sbt.version=1.7.2

View File

@@ -1,11 +1,11 @@
scalacOptions ++= Seq("-unchecked", "-deprecation", "-feature")
addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.5.2")
addSbtPlugin("com.typesafe.play" % "sbt-twirl" % "1.6.1")
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "2.1.3")
addSbtPlugin("org.scalatra.sbt" % "sbt-scalatra" % "1.0.4")
addSbtPlugin("com.github.sbt" % "sbt-pgp" % "2.2.1")
addSbtPlugin("com.github.sbt" % "sbt-license-report" % "1.6.1")
addSbtPlugin("org.scoverage" % "sbt-scoverage" % "2.0.9")
addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.6")
addSbtPlugin("com.typesafe.sbt" % "sbt-twirl" % "1.5.1")
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "2.0.0")
addSbtPlugin("org.scalatra.sbt" % "sbt-scalatra" % "1.0.4")
addSbtPlugin("com.github.sbt" % "sbt-pgp" % "2.1.2")
addSbtPlugin("com.typesafe.sbt" % "sbt-license-report" % "1.2.0")
addSbtPlugin("org.scoverage" % "sbt-scoverage" % "2.0.0")
addDependencyTreePlugin

View File

@@ -65,15 +65,9 @@ public class JettyLauncher {
boolean saveSessions = false;
for(String arg: args) {
if (arg.equals("--save_sessions")) {
if(arg.equals("--save_sessions")) {
saveSessions = true;
}
if (arg.equals("--disable_news_feed")) {
System.setProperty("gitbucket.disableNewsFeed", "true");
}
if (arg.equals("--disable_cache")) {
System.setProperty("gitbucket.disableCache", "true");
}
if(arg.startsWith("--") && arg.contains("=")) {
String[] dim = arg.split("=", 2);
if(dim.length == 2) {
@@ -155,7 +149,7 @@ public class JettyLauncher {
}
if (connectorsSet.contains(Connectors.HTTPS)) {
final SslContextFactory.Server sslContextFactory = new SslContextFactory.Server();
final SslContextFactory sslContextFactory = new SslContextFactory.Server();
sslContextFactory.setKeyStorePath(requireNonNull(keyStorePath,
"You must specify a path to an SSL keystore via the --key_store_path command line argument" +

View File

@@ -1,4 +1,4 @@
notifications:1.11.0
gist:4.23.0
gist:4.22.0
emoji:4.6.0
pages:1.10.0

View File

@@ -7,7 +7,7 @@
</appender>
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<file>.tmp/gitbucket.log</file>
<file>gitbucket.log</file>
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
@@ -23,4 +23,4 @@
<logger name="scala.slick.jdbc.JdbcBackend.statement" level="DEBUG" />
-->
</configuration>
</configuration>

View File

@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<changeSet>
<addColumn tableName="CUSTOM_FIELD">
<column name="CONSTRAINTS" type="varchar(200)" nullable="true"/>
</addColumn>
</changeSet>

View File

@@ -114,8 +114,5 @@ object GitBucketCoreModule
new Version("4.38.0", new LiquibaseMigration("update/gitbucket-core_4.38.xml")),
new Version("4.38.1"),
new Version("4.38.2"),
new Version("4.38.3"),
new Version("4.38.4"),
new Version("4.39.0", new LiquibaseMigration("update/gitbucket-core_4.39.xml")),
new Version("4.40.0")
new Version("4.38.3")
)

View File

@@ -1,6 +0,0 @@
package gitbucket.core.api
/**
* https://developer.github.com/v3/issues/labels/#add-labels-to-an-issue
*/
case class AddLabelsToAnIssue(labels: Seq[String])

View File

@@ -61,7 +61,7 @@ object ApiRepository {
watchers = 0,
forks = 0,
`private` = false,
default_branch = "main",
default_branch = "master",
owner = owner,
has_issues = true
)

View File

@@ -185,6 +185,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
initOption: String,
sourceUrl: Option[String]
)
case class ForkRepositoryForm(owner: String, name: String)
val newRepositoryForm = mapping(
"owner" -> trim(label("Owner", text(required, maxlength(100), identifier, existsAccount))),
@@ -195,6 +196,11 @@ trait AccountControllerBase extends AccountManagementControllerBase {
"sourceUrl" -> trim(label("Source URL", optionalRequired(_.value("initOption") == "COPY", text())))
)(RepositoryCreationForm.apply)
val forkRepositoryForm = mapping(
"owner" -> trim(label("Repository owner", text(required))),
"name" -> trim(label("Repository name", text(required)))
)(ForkRepositoryForm.apply)
case class AccountForm(accountName: String)
val accountForm = mapping(
@@ -262,7 +268,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
gitbucket.core.account.html.activity(
account,
if (account.isGroupAccount) Nil else getGroupsByUserName(userName),
getActivitiesByUser(userName, publicOnly = true),
getActivitiesByUser(userName, true),
extraMailAddresses
)
@@ -807,8 +813,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
form.description,
form.isPrivate,
form.initOption,
form.sourceUrl,
context.settings.defaultBranch
form.sourceUrl
)
// redirect to the repository
redirect(s"/${form.owner}/${form.name}")

View File

@@ -100,4 +100,16 @@ trait ApiControllerBase extends ControllerBase {
get("/api/v3/gitbucket/plugins") {
PluginRegistry().getPlugins().map { ApiPlugin(_) }
}
/**
* https://docs.github.com/en/enterprise-server@2.21/rest/reference/meta#get-github-enterprise-server-meta-information
*/
get("/api/v3/meta") {
JsonFormat(
Map(
"https://api.github.com/meta" -> context.loginAccount.isDefined,
"installed_version" -> "2.21.0"
)
)
}
}

View File

@@ -1,6 +1,7 @@
package gitbucket.core.controller
import java.io.{File, FileInputStream, FileOutputStream}
import java.io.{File, FileInputStream}
import gitbucket.core.api.{ApiError, JsonFormat}
import gitbucket.core.model.Account
import gitbucket.core.service.{AccountService, RepositoryService, SystemSettingsService}
@@ -13,9 +14,9 @@ import org.scalatra._
import org.scalatra.i18n._
import org.scalatra.json._
import org.scalatra.forms._
import javax.servlet.http.{HttpServletRequest, HttpServletResponse}
import javax.servlet.{FilterChain, ServletRequest, ServletResponse}
import is.tagomor.woothee.Classifier
import scala.util.Try
@@ -28,9 +29,6 @@ import org.eclipse.jgit.treewalk._
import org.apache.commons.io.IOUtils
import org.slf4j.LoggerFactory
import org.json4s.Formats
import org.json4s.jackson.Serialization
import java.nio.charset.StandardCharsets
/**
* Provides generic features for controller implementations.
@@ -95,16 +93,8 @@ abstract class ControllerBase
}
}
private def LoginAccount: Option[Account] = {
request
.getAs[Account](Keys.Session.LoginAccount)
.orElse(session.getAs[Account](Keys.Session.LoginAccount))
.orElse {
if (isDevFeatureEnabled(DevFeatures.KeepSession)) {
getLoginAccountFromLocalFile()
} else None
}
}
private def LoginAccount: Option[Account] =
request.getAs[Account](Keys.Session.LoginAccount).orElse(session.getAs[Account](Keys.Session.LoginAccount))
def ajaxGet(path: String)(action: => Any): Route =
super.get(path) {
@@ -287,47 +277,6 @@ abstract class ControllerBase
}
}
}
protected object DevFeatures {
val KeepSession = "keep-session"
}
private val loginAccountFile = new File(".tmp/login_account.json")
protected def isDevFeatureEnabled(feature: String): Boolean = {
Option(System.getProperty("dev-features")).getOrElse("").split(",").map(_.trim).contains(feature)
}
protected def getLoginAccountFromLocalFile(): Option[Account] = {
if (isDevFeatureEnabled(DevFeatures.KeepSession)) {
if (loginAccountFile.exists()) {
Using.resource(new FileInputStream(loginAccountFile)) { in =>
val json = IOUtils.toString(in, StandardCharsets.UTF_8)
val account = parse(json).extract[Account]
session.setAttribute(Keys.Session.LoginAccount, account)
Some(parse(json).extract[Account])
}
} else None
} else None
}
protected def saveLoginAccountToLocalFile(account: Account): Unit = {
if (isDevFeatureEnabled(DevFeatures.KeepSession)) {
if (!loginAccountFile.getParentFile.exists()) {
loginAccountFile.getParentFile.mkdirs()
}
Using.resource(new FileOutputStream(loginAccountFile)) { in =>
in.write(Serialization.write(account).getBytes(StandardCharsets.UTF_8))
}
}
}
protected def deleteLoginAccountFromLocalFile(): Unit = {
if (isDevFeatureEnabled(DevFeatures.KeepSession)) {
loginAccountFile.delete()
}
}
}
/**

View File

@@ -6,7 +6,6 @@ import gitbucket.core.service._
import gitbucket.core.util.{Keys, UsersAuthenticator}
import gitbucket.core.util.Implicits._
import gitbucket.core.service.IssuesService._
import gitbucket.core.service.ActivityService._
class DashboardController
extends DashboardControllerBase
@@ -43,7 +42,7 @@ trait DashboardControllerBase extends ControllerBase {
withoutPhysicalInfo = true,
limit = context.settings.basicBehavior.limitVisibleRepositories
)
html.repos(getGroupNames(loginAccount.userName), repos, repos, isNewsFeedEnabled)
html.repos(getGroupNames(loginAccount.userName), repos, repos)
}
})
@@ -131,8 +130,7 @@ trait DashboardControllerBase extends ControllerBase {
None,
withoutPhysicalInfo = true,
limit = context.settings.basicBehavior.limitVisibleRepositories
),
isNewsFeedEnabled
)
)
}
@@ -174,8 +172,7 @@ trait DashboardControllerBase extends ControllerBase {
None,
withoutPhysicalInfo = true,
limit = context.settings.basicBehavior.limitVisibleRepositories
),
isNewsFeedEnabled
)
)
}

View File

@@ -1,8 +1,7 @@
package gitbucket.core.controller
import com.nimbusds.jwt.JWT
import java.net.URI
import com.nimbusds.oauth2.sdk.id.State
import com.nimbusds.openid.connect.sdk.Nonce
import gitbucket.core.helper.xml
@@ -14,8 +13,6 @@ import gitbucket.core.view.helpers._
import org.scalatra.Ok
import org.scalatra.forms._
import gitbucket.core.service.ActivityService._
class IndexController
extends IndexControllerBase
with RepositoryService
@@ -60,41 +57,30 @@ trait IndexControllerBase extends ControllerBase {
//
// case class SearchForm(query: String, owner: String, repository: String)
case class OidcAuthContext(state: State, nonce: Nonce, redirectBackURI: String)
case class OidcSessionContext(token: JWT)
case class OidcContext(state: State, nonce: Nonce, redirectBackURI: String)
get("/") {
context.loginAccount
.map { account =>
val visibleOwnerSet: Set[String] = Set(account.userName) ++ getGroupsByUserName(account.userName)
if (!isNewsFeedEnabled) {
redirect("/dashboard/repos")
} else {
val repos = getVisibleRepositories(
gitbucket.core.html.index(
getRecentActivitiesByOwners(visibleOwnerSet),
getVisibleRepositories(
Some(account),
None,
withoutPhysicalInfo = true,
limit = false
limit = context.settings.basicBehavior.limitVisibleRepositories
),
showBannerToCreatePersonalAccessToken = hasAccountFederation(account.userName) && !hasAccessToken(
account.userName
)
gitbucket.core.html.index(
activities = getRecentActivitiesByRepos(repos.map(x => (x.owner, x.name)).toSet),
recentRepositories = if (context.settings.basicBehavior.limitVisibleRepositories) {
repos.filter(x => x.owner == account.userName)
} else repos,
showBannerToCreatePersonalAccessToken = hasAccountFederation(account.userName) && !hasAccessToken(
account.userName
),
enableNewsFeed = isNewsFeedEnabled
)
}
)
}
.getOrElse {
gitbucket.core.html.index(
activities = getRecentPublicActivities(),
recentRepositories = getVisibleRepositories(None, withoutPhysicalInfo = true),
showBannerToCreatePersonalAccessToken = false,
enableNewsFeed = isNewsFeedEnabled
getRecentPublicActivities(),
getVisibleRepositories(None, withoutPhysicalInfo = true),
showBannerToCreatePersonalAccessToken = false
)
}
}
@@ -134,8 +120,8 @@ trait IndexControllerBase extends ControllerBase {
case _ => "/"
}
session.setAttribute(
Keys.Session.OidcAuthContext,
OidcAuthContext(authenticationRequest.getState, authenticationRequest.getNonce, redirectBackURI)
Keys.Session.OidcContext,
OidcContext(authenticationRequest.getState, authenticationRequest.getNonce, redirectBackURI)
)
redirect(authenticationRequest.toURI.toString)
} getOrElse {
@@ -149,12 +135,10 @@ trait IndexControllerBase extends ControllerBase {
get("/signin/oidc") {
context.settings.oidc.map { oidc =>
val redirectURI = new URI(s"$baseUrl/signin/oidc")
session.get(Keys.Session.OidcAuthContext) match {
case Some(context: OidcAuthContext) =>
authenticate(params.toMap, redirectURI, context.state, context.nonce, oidc).map {
case (jwt, account) =>
session.setAttribute(Keys.Session.OidcSessionContext, OidcSessionContext(jwt))
signin(account, context.redirectBackURI)
session.get(Keys.Session.OidcContext) match {
case Some(context: OidcContext) =>
authenticate(params.toMap, redirectURI, context.state, context.nonce, oidc).map { account =>
signin(account, context.redirectBackURI)
} orElse {
flash.update("error", "Sorry, authentication failed. Please try again.")
session.invalidate()
@@ -171,19 +155,7 @@ trait IndexControllerBase extends ControllerBase {
}
get("/signout") {
context.settings.oidc.foreach { oidc =>
session.get(Keys.Session.OidcSessionContext).foreach {
case context: OidcSessionContext =>
val redirectURI = new URI(baseUrl)
val authenticationRequest = createOIDLogoutRequest(oidc.issuer, oidc.clientID, redirectURI, context.token)
session.invalidate()
redirect(authenticationRequest.toURI.toString)
}
}
session.invalidate()
if (isDevFeatureEnabled(DevFeatures.KeepSession)) {
deleteLoginAccountFromLocalFile()
}
session.invalidate
redirect("/")
}
@@ -206,9 +178,6 @@ trait IndexControllerBase extends ControllerBase {
*/
private def signin(account: Account, redirectUrl: String = "/") = {
session.setAttribute(Keys.Session.LoginAccount, account)
if (isDevFeatureEnabled(DevFeatures.KeepSession)) {
saveLoginAccountToLocalFile(account)
}
updateLastLoginDate(account.userName)
if (LDAPUtil.isDummyMailAddress(account)) {
@@ -232,7 +201,7 @@ trait IndexControllerBase extends ControllerBase {
org.json4s.jackson.Serialization.write(
Map(
"options" -> (
getAllUsers(includeRemoved = false)
getAllUsers(false)
.withFilter { t =>
(user, group) match {
case (true, true) => true
@@ -265,7 +234,7 @@ trait IndexControllerBase extends ControllerBase {
} getOrElse ""
})
// TODO Move to RepositoryViewerController?
// TODO Move to RepositoryViewrController?
get("/:owner/:repository/search")(referrersOnly { repository =>
val query = params.getOrElse("q", "").trim
val target = params.getOrElse("type", "code")
@@ -279,8 +248,8 @@ trait IndexControllerBase extends ControllerBase {
target.toLowerCase match {
case "issues" =>
gitbucket.core.search.html.issues(
if (query.nonEmpty) searchIssues(repository.owner, repository.name, query, pullRequest = false) else Nil,
pullRequest = false,
if (query.nonEmpty) searchIssues(repository.owner, repository.name, query, false) else Nil,
false,
query,
page,
repository
@@ -288,8 +257,8 @@ trait IndexControllerBase extends ControllerBase {
case "pulls" =>
gitbucket.core.search.html.issues(
if (query.nonEmpty) searchIssues(repository.owner, repository.name, query, pullRequest = true) else Nil,
pullRequest = true,
if (query.nonEmpty) searchIssues(repository.owner, repository.name, query, true) else Nil,
true,
query,
page,
repository
@@ -324,15 +293,15 @@ trait IndexControllerBase extends ControllerBase {
)
val repositories = {
if (context.settings.basicBehavior.limitVisibleRepositories) {
getVisibleRepositories(
context.loginAccount,
None,
withoutPhysicalInfo = true,
limit = false
)
} else {
visibleRepositories
context.settings.basicBehavior.limitVisibleRepositories match {
case true =>
getVisibleRepositories(
context.loginAccount,
None,
withoutPhysicalInfo = true,
limit = false
)
case false => visibleRepositories
}
}.filter { repository =>
repository.name.toLowerCase.indexOf(query) >= 0 || repository.owner.toLowerCase.indexOf(query) >= 0

View File

@@ -89,13 +89,10 @@ trait IssuesControllerBase extends ControllerBase {
get("/:owner/:repository/issues")(referrersOnly { repository =>
val q = request.getParameter("q")
Option(q) match {
case Some(filter) if filter.contains("is:pr") =>
redirect(s"/${repository.owner}/${repository.name}/pulls?q=${StringUtil.urlEncode(q)}")
case Some(filter) =>
searchIssues(repository, IssueSearchCondition(filter), IssueSearchCondition.page(request))
case None =>
searchIssues(repository, IssueSearchCondition(request), IssueSearchCondition.page(request))
if (Option(q).exists(_.contains("is:pr"))) {
redirect(s"/${repository.owner}/${repository.name}/pulls?q=${StringUtil.urlEncode(q)}")
} else {
searchIssues(repository)
}
})
@@ -534,7 +531,10 @@ trait IssuesControllerBase extends ControllerBase {
}
}
private def searchIssues(repository: RepositoryService.RepositoryInfo, condition: IssueSearchCondition, page: Int) = {
private def searchIssues(repository: RepositoryService.RepositoryInfo) = {
val page = IssueSearchCondition.page(request)
// retrieve search condition
val condition = IssueSearchCondition(request)
// search issues
val issues =
searchIssue(

View File

@@ -102,13 +102,10 @@ trait PullRequestsControllerBase extends ControllerBase {
get("/:owner/:repository/pulls")(referrersOnly { repository =>
val q = request.getParameter("q")
Option(q) match {
case Some(filter) if filter.contains("is:issue") =>
redirect(s"/${repository.owner}/${repository.name}/issues?q=${StringUtil.urlEncode(q)}")
case Some(filter) =>
searchPullRequests(repository, IssueSearchCondition(filter), IssueSearchCondition.page(request))
case None =>
searchPullRequests(repository, IssueSearchCondition(request), IssueSearchCondition.page(request))
if (Option(q).exists(_.contains("is:issue"))) {
redirect(s"/${repository.owner}/${repository.name}/issues?q=" + StringUtil.urlEncode(q))
} else {
searchPullRequests(None, repository)
}
})
@@ -693,11 +690,10 @@ trait PullRequestsControllerBase extends ControllerBase {
html.proposals(proposedBranches, targetRepository, repository)
})
private def searchPullRequests(
repository: RepositoryService.RepositoryInfo,
condition: IssueSearchCondition,
page: Int
) = {
private def searchPullRequests(userName: Option[String], repository: RepositoryService.RepositoryInfo) = {
val page = IssueSearchCondition.page(request)
// retrieve search condition
val condition = IssueSearchCondition(request)
// search issues
val issues = searchIssue(
condition,

View File

@@ -126,7 +126,6 @@ trait RepositorySettingsControllerBase extends ControllerBase {
case class CustomFieldForm(
fieldName: String,
fieldType: String,
constraints: Option[String],
enableForIssues: Boolean,
enableForPullRequests: Boolean
)
@@ -134,7 +133,6 @@ trait RepositorySettingsControllerBase extends ControllerBase {
val customFieldForm = mapping(
"fieldName" -> trim(label("Field name", text(required, maxlength(100)))),
"fieldType" -> trim(label("Field type", text(required))),
"constraints" -> trim(label("Constraints", optional(text()))),
"enableForIssues" -> trim(label("Enable for issues", boolean(required))),
"enableForPullRequests" -> trim(label("Enable for pull requests", boolean(required))),
)(CustomFieldForm.apply)
@@ -513,7 +511,6 @@ trait RepositorySettingsControllerBase extends ControllerBase {
repository.name,
form.fieldName,
form.fieldType,
if (form.fieldType == "enum") form.constraints else None,
form.enableForIssues,
form.enableForPullRequests
)
@@ -536,7 +533,6 @@ trait RepositorySettingsControllerBase extends ControllerBase {
params("fieldId").toInt,
form.fieldName,
form.fieldType,
if (form.fieldType == "enum") form.constraints else None,
form.enableForIssues,
form.enableForPullRequests
)

View File

@@ -124,8 +124,7 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
)(Upload.apply),
"repositoryViewer" -> mapping(
"maxFiles" -> trim(label("Max files", number(required)))
)(RepositoryViewerSettings.apply),
"defaultBranch" -> trim(label("Default branch", text(required)))
)(RepositoryViewerSettings.apply)
)(SystemSettings.apply).verifying { settings =>
Vector(
if (settings.ssh.enabled && settings.baseUrl.isEmpty) {

View File

@@ -46,7 +46,7 @@ trait WikiControllerBase extends ControllerBase {
)
val newForm = mapping(
"pageName" -> trim(label("Page name", text(required, maxlength(40), pageName, unique))),
"pageName" -> trim(label("Page name", text(required, maxlength(40), pagename, unique))),
"content" -> trim(label("Content", text(required, conflictForNew))),
"message" -> trim(label("Message", optional(text()))),
"currentPageName" -> trim(label("Current page name", text())),
@@ -54,7 +54,7 @@ trait WikiControllerBase extends ControllerBase {
)(WikiPageEditForm.apply)
val editForm = mapping(
"pageName" -> trim(label("Page name", text(required, maxlength(40), pageName))),
"pageName" -> trim(label("Page name", text(required, maxlength(40), pagename))),
"content" -> trim(label("Content", text(required, conflictForEdit))),
"message" -> trim(label("Message", optional(text()))),
"currentPageName" -> trim(label("Current page name", text(required))),
@@ -62,56 +62,46 @@ trait WikiControllerBase extends ControllerBase {
)(WikiPageEditForm.apply)
get("/:owner/:repository/wiki")(referrersOnly { repository =>
val branch = getWikiBranch(repository.owner, repository.name)
getWikiPage(repository.owner, repository.name, "Home", branch).map { page =>
getWikiPage(repository.owner, repository.name, "Home").map { page =>
html.page(
"Home",
page,
getWikiPageList(repository.owner, repository.name, branch),
getWikiPageList(repository.owner, repository.name),
repository,
isEditable(repository),
getWikiPage(repository.owner, repository.name, "_Sidebar", branch),
getWikiPage(repository.owner, repository.name, "_Footer", branch)
getWikiPage(repository.owner, repository.name, "_Sidebar"),
getWikiPage(repository.owner, repository.name, "_Footer")
)
} getOrElse redirect(s"/${repository.owner}/${repository.name}/wiki/Home/_edit")
})
get("/:owner/:repository/wiki/:page")(referrersOnly { repository =>
val pageName = StringUtil.urlDecode(params("page"))
val branch = getWikiBranch(repository.owner, repository.name)
getWikiPage(repository.owner, repository.name, pageName, branch).map { page =>
getWikiPage(repository.owner, repository.name, pageName).map { page =>
html.page(
pageName,
page,
getWikiPageList(repository.owner, repository.name, branch),
getWikiPageList(repository.owner, repository.name),
repository,
isEditable(repository),
getWikiPage(repository.owner, repository.name, "_Sidebar", branch),
getWikiPage(repository.owner, repository.name, "_Footer", branch)
getWikiPage(repository.owner, repository.name, "_Sidebar"),
getWikiPage(repository.owner, repository.name, "_Footer")
)
} getOrElse redirect(s"/${repository.owner}/${repository.name}/wiki/${StringUtil.urlEncode(pageName)}/_edit")
})
get("/:owner/:repository/wiki/:page/_history")(referrersOnly { repository =>
val pageName = StringUtil.urlDecode(params("page"))
val branch = getWikiBranch(repository.owner, repository.name)
Using.resource(Git.open(getWikiRepositoryDir(repository.owner, repository.name))) { git =>
JGitUtil.getCommitLog(git, branch, path = pageName + ".md") match {
JGitUtil.getCommitLog(git, "master", path = pageName + ".md") match {
case Right((logs, hasNext)) => html.history(Some(pageName), logs, repository, isEditable(repository))
case Left(_) => NotFound()
}
}
})
private def getWikiBranch(owner: String, repository: String): String = {
Using.resource(Git.open(Directory.getWikiRepositoryDir(owner, repository))) { git =>
git.getRepository.getBranch
}
}
get("/:owner/:repository/wiki/:page/_compare/:commitId")(referrersOnly { repository =>
val pageName = StringUtil.urlDecode(params("page"))
val Array(from, to) = params("commitId").split("\\.\\.\\.")
@@ -151,9 +141,8 @@ trait WikiControllerBase extends ControllerBase {
if (isEditable(repository)) {
val pageName = StringUtil.urlDecode(params("page"))
val Array(from, to) = params("commitId").split("\\.\\.\\.")
val branch = getWikiBranch(repository.owner, repository.name)
if (revertWikiPage(repository.owner, repository.name, from, to, loginAccount, Some(pageName), branch)) {
if (revertWikiPage(repository.owner, repository.name, from, to, loginAccount, Some(pageName))) {
redirect(s"/${repository.owner}/${repository.name}/wiki/${StringUtil.urlEncode(pageName)}")
} else {
flash.update("info", "This patch was not able to be reversed.")
@@ -170,9 +159,8 @@ trait WikiControllerBase extends ControllerBase {
loginAccount =>
if (isEditable(repository)) {
val Array(from, to) = params("commitId").split("\\.\\.\\.")
val branch = getWikiBranch(repository.owner, repository.name)
if (revertWikiPage(repository.owner, repository.name, from, to, loginAccount, None, branch)) {
if (revertWikiPage(repository.owner, repository.name, from, to, loginAccount, None)) {
redirect(s"/${repository.owner}/${repository.name}/wiki")
} else {
flash.update("info", "This patch was not able to be reversed.")
@@ -185,9 +173,7 @@ trait WikiControllerBase extends ControllerBase {
get("/:owner/:repository/wiki/:page/_edit")(readableUsersOnly { repository =>
if (isEditable(repository)) {
val pageName = StringUtil.urlDecode(params("page"))
val branch = getWikiBranch(repository.owner, repository.name)
html.edit(pageName, getWikiPage(repository.owner, repository.name, pageName, branch), repository)
html.edit(pageName, getWikiPage(repository.owner, repository.name, pageName), repository)
} else Unauthorized()
})
@@ -294,8 +280,7 @@ trait WikiControllerBase extends ControllerBase {
})
get("/:owner/:repository/wiki/_pages")(referrersOnly { repository =>
val branch = getWikiBranch(repository.owner, repository.name)
html.pages(getWikiPageList(repository.owner, repository.name, branch), repository, isEditable(repository))
html.pages(getWikiPageList(repository.owner, repository.name), repository, isEditable(repository))
})
get("/:owner/:repository/wiki/_history")(referrersOnly { repository =>
@@ -324,18 +309,13 @@ trait WikiControllerBase extends ControllerBase {
value: String,
params: Map[String, Seq[String]],
messages: Messages
): Option[String] = {
val owner = params.value("owner")
val repository = params.value("repository")
val branch = getWikiBranch(owner, repository)
getWikiPageList(owner, repository, branch)
): Option[String] =
getWikiPageList(params.value("owner"), params.value("repository"))
.find(_ == value)
.map(_ => "Page already exists.")
}
}
private def pageName: Constraint = new Constraint() {
private def pagename: Constraint = new Constraint() {
override def validate(name: String, value: String, messages: Messages): Option[String] =
if (value.exists("\\/:*?\"<>|".contains(_))) {
Some(s"${name} contains invalid character.")
@@ -346,7 +326,7 @@ trait WikiControllerBase extends ControllerBase {
}
}
private def notReservedPageName(value: String): Boolean = !(Array[String]("_Sidebar", "_Footer") contains value)
private def notReservedPageName(value: String) = !(Array[String]("_Sidebar", "_Footer") contains value)
private def conflictForNew: Constraint = new Constraint() {
override def validate(name: String, value: String, messages: Messages): Option[String] = {
@@ -364,13 +344,7 @@ trait WikiControllerBase extends ControllerBase {
}
}
private def targetWikiPage: Option[WikiService.WikiPageInfo] = {
val owner = params("owner")
val repository = params("repository")
val pageName = params("pageName")
val branch = getWikiBranch(owner, repository)
getWikiPage(owner, repository, pageName, branch)
}
private def targetWikiPage = getWikiPage(params("owner"), params("repository"), params("pageName"))
private def isEditable(repository: RepositoryInfo)(implicit context: Context): Boolean = {
repository.repository.options.wikiOption match {

View File

@@ -57,27 +57,22 @@ trait ApiGitReferenceControllerBase extends ControllerBase {
* iii. Create a reference
* https://docs.github.com/en/free-pro-team@latest/rest/reference/git#create-a-reference
*/
post("/api/v3/repos/:owner/:repository/git/refs")(writableUsersOnly { repository =>
post("/api/v3/repos/:owner/:repository/git/refs")(referrersOnly { repository =>
extractFromJsonBody[CreateARef].map {
data =>
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) {
git =>
val ref = git.getRepository.findRef(data.ref)
if (ref == null) {
val update = git.getRepository.updateRef(data.ref)
update.setNewObjectId(ObjectId.fromString(data.sha))
val result = update.update()
result match {
case Result.NEW =>
JsonFormat(
ApiRef
.fromRef(RepositoryName(repository.owner, repository.name), git.getRepository.findRef(data.ref))
)
case _ => UnprocessableEntity(result.name())
}
} else {
UnprocessableEntity("Ref already exists.")
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.owner))) { git =>
val ref = git.getRepository.findRef(data.ref)
if (ref == null) {
val update = git.getRepository.updateRef(data.ref)
update.setNewObjectId(ObjectId.fromString(data.sha))
val result = update.update()
result match {
case Result.NEW => JsonFormat(ApiRef.fromRef(RepositoryName(repository.owner, repository.name), ref))
case _ => UnprocessableEntity(result.name())
}
} else {
UnprocessableEntity("Ref already exists.")
}
}
} getOrElse BadRequest()
})
@@ -90,7 +85,7 @@ trait ApiGitReferenceControllerBase extends ControllerBase {
val refName = multiParams("splat").mkString("/")
extractFromJsonBody[UpdateARef].map {
data =>
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.owner))) { git =>
val ref = git.getRepository.findRef(refName)
if (ref == null) {
UnprocessableEntity("Ref does not exist.")
@@ -101,7 +96,7 @@ trait ApiGitReferenceControllerBase extends ControllerBase {
val result = update.update()
result match {
case Result.FORCED | Result.FAST_FORWARD | Result.NO_CHANGE =>
JsonFormat(ApiRef.fromRef(RepositoryName(repository), git.getRepository.findRef(refName)))
JsonFormat(ApiRef.fromRef(RepositoryName(repository), update.getRef))
case _ => UnprocessableEntity(result.name())
}
}

View File

@@ -85,7 +85,7 @@ trait ApiIssueCommentControllerBase extends ControllerBase {
* iv. Delete a comment
* https://docs.github.com/en/rest/reference/issues#delete-an-issue-comment
*/
delete("/api/v3/repos/:owner/:repository/issues/comments/:id")(readableUsersOnly { repository =>
delete("/api/v3/repos/:owner/:repo/issues/comments/:id")(readableUsersOnly { repository =>
val maybeDeleteResponse: Option[Either[ActionResult, Option[Int]]] =
for {
commentId <- params("id").toIntOpt

View File

@@ -1,5 +1,5 @@
package gitbucket.core.controller.api
import gitbucket.core.api.{AddLabelsToAnIssue, ApiError, ApiLabel, CreateALabel, JsonFormat}
import gitbucket.core.api.{ApiError, ApiLabel, CreateALabel, JsonFormat}
import gitbucket.core.controller.ControllerBase
import gitbucket.core.service._
import gitbucket.core.util.Implicits._
@@ -121,10 +121,10 @@ trait ApiIssueLabelControllerBase extends ControllerBase {
*/
post("/api/v3/repos/:owner/:repository/issues/:id/labels")(writableUsersOnly { repository =>
JsonFormat(for {
data <- extractFromJsonBody[AddLabelsToAnIssue]
data <- extractFromJsonBody[Seq[String]]
issueId <- params("id").toIntOpt
} yield {
data.labels.map { labelName =>
data.map { labelName =>
val label = getLabel(repository.owner, repository.name, labelName).getOrElse(
getLabel(
repository.owner,
@@ -160,11 +160,11 @@ trait ApiIssueLabelControllerBase extends ControllerBase {
*/
put("/api/v3/repos/:owner/:repository/issues/:id/labels")(writableUsersOnly { repository =>
JsonFormat(for {
data <- extractFromJsonBody[AddLabelsToAnIssue]
data <- extractFromJsonBody[Seq[String]]
issueId <- params("id").toIntOpt
} yield {
deleteAllIssueLabels(repository.owner, repository.name, issueId, true)
data.labels.map { labelName =>
data.map { labelName =>
val label = getLabel(repository.owner, repository.name, labelName).getOrElse(
getLabel(
repository.owner,

View File

@@ -93,8 +93,7 @@ trait ApiRepositoryControllerBase extends ControllerBase {
data.name,
data.description,
data.`private`,
data.auto_init,
context.settings.defaultBranch
data.auto_init
)
Await.result(f, Duration.Inf)
@@ -131,8 +130,7 @@ trait ApiRepositoryControllerBase extends ControllerBase {
data.name,
data.description,
data.`private`,
data.auto_init,
context.settings.defaultBranch
data.auto_init
)
Await.result(f, Duration.Inf)
val repository = Database() withTransaction { session =>

View File

@@ -10,7 +10,7 @@ trait AccessTokenComponent { self: Profile =>
val userName = column[String]("USER_NAME")
val tokenHash = column[String]("TOKEN_HASH")
val note = column[String]("NOTE")
def * = (accessTokenId, userName, tokenHash, note).mapTo[AccessToken]
def * = (accessTokenId, userName, tokenHash, note).<>(AccessToken.tupled, AccessToken.unapply)
}
}
case class AccessToken(

View File

@@ -35,7 +35,7 @@ trait AccountComponent { self: Profile =>
groupAccount,
removed,
description.?
).mapTo[Account]
).<>(Account.tupled, Account.unapply)
}
}

View File

@@ -9,7 +9,7 @@ trait AccountExtraMailAddressComponent { self: Profile =>
val userName = column[String]("USER_NAME", O PrimaryKey)
val extraMailAddress = column[String]("EXTRA_MAIL_ADDRESS", O PrimaryKey)
def * =
(userName, extraMailAddress).mapTo[AccountExtraMailAddress]
(userName, extraMailAddress).<>(AccountExtraMailAddress.tupled, AccountExtraMailAddress.unapply)
}
}

View File

@@ -9,7 +9,7 @@ trait AccountFederationComponent { self: Profile =>
val issuer = column[String]("ISSUER")
val subject = column[String]("SUBJECT")
val userName = column[String]("USER_NAME")
def * = (issuer, subject, userName).mapTo[AccountFederation]
def * = (issuer, subject, userName).<>(AccountFederation.tupled, AccountFederation.unapply)
def byPrimaryKey(issuer: String, subject: String): Rep[Boolean] =
(this.issuer === issuer.bind) && (this.subject === subject.bind)

View File

@@ -9,7 +9,7 @@ trait AccountPreferenceComponent { self: Profile =>
val userName = column[String]("USER_NAME", O PrimaryKey)
val highlighterTheme = column[String]("HIGHLIGHTER_THEME")
def * =
(userName, highlighterTheme).mapTo[AccountPreference]
(userName, highlighterTheme).<>(AccountPreference.tupled, AccountPreference.unapply)
def byPrimaryKey(userName: String): Rep[Boolean] = this.userName === userName.bind
}

View File

@@ -12,7 +12,7 @@ trait AccountWebHookComponent extends TemplateComponent { self: Profile =>
val url = column[String]("URL")
val token = column[Option[String]]("TOKEN")
val ctype = column[WebHookContentType]("CTYPE")
def * = (userName, url, ctype, token).mapTo[AccountWebHook]
def * = (userName, url, ctype, token).<>((AccountWebHook.apply _).tupled, AccountWebHook.unapply)
def byPrimaryKey(userName: String, url: String) = (this.userName === userName.bind) && (this.url === url.bind)
}

View File

@@ -12,7 +12,7 @@ trait AccountWebHookEventComponent extends TemplateComponent { self: Profile =>
val url = column[String]("URL")
val event = column[WebHook.Event]("EVENT")
def * = (userName, url, event).mapTo[AccountWebHookEvent]
def * = (userName, url, event).<>((AccountWebHookEvent.apply _).tupled, AccountWebHookEvent.unapply)
def byAccountWebHook(userName: String, url: String) = (this.userName === userName.bind) && (this.url === url.bind)

View File

@@ -11,7 +11,7 @@ trait ActivityComponent extends TemplateComponent { self: Profile =>
lazy val Activities = TableQuery[Activities]
class Activities(tag: Tag) extends Table[Activity](tag, "ACTIVITY") with BasicTemplate {
def * : slick.lifted.ProvenShape[Activity] = ???
def * = ???
}
}

View File

@@ -8,7 +8,7 @@ trait CollaboratorComponent extends TemplateComponent { self: Profile =>
class Collaborators(tag: Tag) extends Table[Collaborator](tag, "COLLABORATOR") with BasicTemplate {
val collaboratorName = column[String]("COLLABORATOR_NAME")
val role = column[String]("ROLE")
def * = (userName, repositoryName, collaboratorName, role).mapTo[Collaborator]
def * = (userName, repositoryName, collaboratorName, role).<>(Collaborator.tupled, Collaborator.unapply)
def byPrimaryKey(owner: String, repository: String, collaborator: String) =
byRepository(owner, repository) && (collaboratorName === collaborator.bind)

View File

@@ -21,7 +21,7 @@ trait IssueCommentComponent extends TemplateComponent { self: Profile =>
val updatedDate = column[java.util.Date]("UPDATED_DATE")
def * =
(userName, repositoryName, issueId, commentId, action, commentedUserName, content, registeredDate, updatedDate)
.mapTo[IssueComment]
.<>(IssueComment.tupled, IssueComment.unapply)
def byPrimaryKey(commentId: Int) = this.commentId === commentId.bind
}
@@ -75,7 +75,7 @@ trait CommitCommentComponent extends TemplateComponent { self: Profile =>
originalCommitId,
originalOldLine,
originalNewLine
).mapTo[CommitComment]
).<>(CommitComment.tupled, CommitComment.unapply)
def byPrimaryKey(commentId: Int) = this.commentId === commentId.bind
}

View File

@@ -30,7 +30,7 @@ trait CommitStatusComponent extends TemplateComponent { self: Profile =>
creator,
registeredDate,
updatedDate
).mapTo[CommitStatus]
).<>((CommitStatus.apply _).tupled, CommitStatus.unapply)
def byPrimaryKey(id: Int) = commitStatusId === id.bind
}
}

View File

@@ -5,7 +5,6 @@ import gitbucket.core.service.RepositoryService.RepositoryInfo
import gitbucket.core.util.StringUtil
import gitbucket.core.view.helpers
import org.scalatra.i18n.Messages
import play.twirl.api.Html
trait CustomFieldComponent extends TemplateComponent { self: Profile =>
import profile.api._
@@ -16,12 +15,11 @@ trait CustomFieldComponent extends TemplateComponent { self: Profile =>
val fieldId = column[Int]("FIELD_ID", O AutoInc)
val fieldName = column[String]("FIELD_NAME")
val fieldType = column[String]("FIELD_TYPE")
val constraints = column[Option[String]]("CONSTRAINTS")
val enableForIssues = column[Boolean]("ENABLE_FOR_ISSUES")
val enableForPullRequests = column[Boolean]("ENABLE_FOR_PULL_REQUESTS")
def * =
(userName, repositoryName, fieldId, fieldName, fieldType, constraints, enableForIssues, enableForPullRequests)
.mapTo[CustomField]
(userName, repositoryName, fieldId, fieldName, fieldType, enableForIssues, enableForPullRequests)
.<>(CustomField.tupled, CustomField.unapply)
def byPrimaryKey(userName: String, repositoryName: String, fieldId: Int) =
(this.userName === userName.bind) && (this.repositoryName === repositoryName.bind) && (this.fieldId === fieldId.bind)
@@ -33,28 +31,17 @@ case class CustomField(
repositoryName: String,
fieldId: Int = 0,
fieldName: String,
fieldType: String, // long, double, string, date, or enum
constraints: Option[String],
fieldType: String, // long, double, string, or date
enableForIssues: Boolean,
enableForPullRequests: Boolean
)
trait CustomFieldBehavior {
def createHtml(repository: RepositoryInfo, fieldId: Int, fieldName: String, constraints: Option[String])(
def createHtml(repository: RepositoryInfo, fieldId: Int)(implicit conext: Context): String
def fieldHtml(repository: RepositoryInfo, issueId: Int, fieldId: Int, value: String, editable: Boolean)(
implicit context: Context
): String
def fieldHtml(
repository: RepositoryInfo,
issueId: Int,
fieldId: Int,
fieldName: String,
constraints: Option[String],
value: String,
editable: Boolean
)(
implicit context: Context
): String
def validate(name: String, constraints: Option[String], value: String, messages: Messages): Option[String]
def validate(name: String, value: String, messages: Messages): Option[String]
}
object CustomFieldBehavior {
@@ -62,7 +49,7 @@ object CustomFieldBehavior {
if (value.isEmpty) None
else {
CustomFieldBehavior(field.fieldType).flatMap { behavior =>
behavior.validate(field.fieldName, field.constraints, value, messages)
behavior.validate(field.fieldName, value, messages)
}
}
}
@@ -73,18 +60,12 @@ object CustomFieldBehavior {
case "double" => Some(DoubleFieldBehavior)
case "string" => Some(StringFieldBehavior)
case "date" => Some(DateFieldBehavior)
case "enum" => Some(EnumFieldBehavior)
case _ => None
}
}
case object LongFieldBehavior extends TextFieldBehavior {
override def validate(
name: String,
constraints: Option[String],
value: String,
messages: Messages
): Option[String] = {
override def validate(name: String, value: String, messages: Messages): Option[String] = {
try {
value.toLong
None
@@ -94,12 +75,7 @@ object CustomFieldBehavior {
}
}
case object DoubleFieldBehavior extends TextFieldBehavior {
override def validate(
name: String,
constraints: Option[String],
value: String,
messages: Messages
): Option[String] = {
override def validate(name: String, value: String, messages: Messages): Option[String] = {
try {
value.toDouble
None
@@ -113,12 +89,7 @@ object CustomFieldBehavior {
private val pattern = "yyyy-MM-dd"
override protected val fieldType: String = "date"
override def validate(
name: String,
constraints: Option[String],
value: String,
messages: Messages
): Option[String] = {
override def validate(name: String, value: String, messages: Messages): Option[String] = {
try {
new java.text.SimpleDateFormat(pattern).parse(value)
None
@@ -129,142 +100,10 @@ object CustomFieldBehavior {
}
}
case object EnumFieldBehavior extends CustomFieldBehavior {
override def createHtml(repository: RepositoryInfo, fieldId: Int, fieldName: String, constraints: Option[String])(
implicit context: Context
): String = {
createPulldownHtml(repository, fieldId, fieldName, constraints, None, None)
}
override def fieldHtml(
repository: RepositoryInfo,
issueId: Int,
fieldId: Int,
fieldName: String,
constraints: Option[String],
value: String,
editable: Boolean
)(implicit context: Context): String = {
if (!editable) {
val sb = new StringBuilder
sb.append("""</div>""")
sb.append("""<div>""")
if (value == "") {
sb.append(s"""<span id="label-custom-field-$fieldId"><span class="muted small">No ${StringUtil.escapeHtml(
fieldName
)}</span></span>""")
} else {
sb.append(s"""<span id="label-custom-field-$fieldId"><span class="muted small">${StringUtil
.escapeHtml(value)}</span></span>""")
}
sb.toString()
} else {
createPulldownHtml(repository, fieldId, fieldName, constraints, Some(issueId), Some(value))
}
}
private def createPulldownHtml(
repository: RepositoryInfo,
fieldId: Int,
fieldName: String,
constraints: Option[String],
issueId: Option[Int],
value: Option[String]
)(implicit context: Context): String = {
val sb = new StringBuilder
sb.append("""<div class="pull-right">""")
sb.append(
gitbucket.core.helper.html
.dropdown("Edit", right = true, filter = (fieldName, s"Filter $fieldName")) {
val options = new StringBuilder()
options.append(
s"""<li><a href="javascript:void(0);" class="custom-field-option-$fieldId" data-value=""><i class="octicon octicon-x"></i> Clear ${StringUtil
.escapeHtml(fieldName)}</a></li>"""
)
constraints.foreach {
x =>
x.split(",").map(_.trim).foreach {
item =>
options.append(s"""<li>
| <a href="javascript:void(0);" class="custom-field-option-$fieldId" data-value="${StringUtil
.escapeHtml(item)}">
| ${gitbucket.core.helper.html.checkicon(value.contains(item))}
| ${StringUtil.escapeHtml(item)}
| </a>
|</li>
|""".stripMargin)
}
}
Html(options.toString())
}
.toString()
)
sb.append("""</div>""")
sb.append("""</div>""")
sb.append("""<div>""")
value match {
case None =>
sb.append(s"""<span id="label-custom-field-$fieldId"><span class="muted small">No ${StringUtil.escapeHtml(
fieldName
)}</span></span>""")
case Some(value) =>
sb.append(s"""<span id="label-custom-field-$fieldId"><span class="muted small">${StringUtil
.escapeHtml(value)}</span></span>""")
}
if (value.isEmpty || issueId.isEmpty) {
sb.append(s"""<input type="hidden" id="custom-field-$fieldId" name="custom-field-$fieldId" value=""/>""")
sb.append(s"""<script>
|$$('a.custom-field-option-$fieldId').click(function(){
| const value = $$(this).data('value');
| $$('a.custom-field-option-$fieldId i.octicon-check').removeClass('octicon-check');
| $$('#custom-field-$fieldId').val(value);
| if (value == '') {
| $$('#label-custom-field-$fieldId').html($$('<span class="muted small">').text('No ${StringUtil
.escapeHtml(fieldName)}'));
| } else {
| $$('#label-custom-field-$fieldId').html($$('<span class="muted small">').text(value));
| $$('a.custom-field-option-$fieldId[data-value=' + value + '] i').addClass('octicon-check');
| }
|});
|</script>""".stripMargin)
} else {
sb.append(s"""<script>
|$$('a.custom-field-option-$fieldId').click(function(){
| const value = $$(this).data('value');
| $$.post('${helpers.url(repository)}/issues/${issueId.get}/customfield/$fieldId',
| { value: value },
| function(data){
| $$('a.custom-field-option-$fieldId i.octicon-check').removeClass('octicon-check');
| if (value == '') {
| $$('#label-custom-field-$fieldId').html($$('<span class="muted small">').text('No ${StringUtil
.escapeHtml(fieldName)}'));
| } else {
| $$('#label-custom-field-$fieldId').html($$('<span class="muted small">').text(value));
| $$('a.custom-field-option-$fieldId[data-value=' + value + '] i').addClass('octicon-check');
| }
| }
| );
|});
|</script>
|""".stripMargin)
}
sb.toString()
}
override def validate(
name: String,
constraints: Option[String],
value: String,
messages: Messages
): Option[String] = None
}
trait TextFieldBehavior extends CustomFieldBehavior {
protected val fieldType = "text"
override def createHtml(repository: RepositoryInfo, fieldId: Int, fieldName: String, constraints: Option[String])(
implicit context: Context
): String = {
def createHtml(repository: RepositoryInfo, fieldId: Int)(implicit context: Context): String = {
val sb = new StringBuilder
sb.append(
s"""<input type="$fieldType" class="form-control input-sm" id="custom-field-$fieldId" name="custom-field-$fieldId" data-field-id="$fieldId" style="width: 120px;"/>"""
@@ -272,7 +111,8 @@ object CustomFieldBehavior {
sb.append(s"""<script>
|$$('#custom-field-$fieldId').focusout(function(){
| const $$this = $$(this);
| $$.post('${helpers.url(repository)}/issues/customfield_validation/$fieldId',
| const fieldId = $$this.data('field-id');
| $$.post('${helpers.url(repository)}/issues/customfield_validation/' + fieldId,
| { value: $$this.val() },
| function(data){
| if (data != '') {
@@ -288,34 +128,14 @@ object CustomFieldBehavior {
sb.toString()
}
override def fieldHtml(
repository: RepositoryInfo,
issueId: Int,
fieldId: Int,
fieldName: String,
constraints: Option[String],
value: String,
editable: Boolean
)(
def fieldHtml(repository: RepositoryInfo, issueId: Int, fieldId: Int, value: String, editable: Boolean)(
implicit context: Context
): String = {
val sb = new StringBuilder
if (value.nonEmpty) {
sb.append(
s"""<span id="custom-field-$fieldId-label" class="custom-field-label">${StringUtil
.escapeHtml(value)}</span>"""
)
} else {
if (editable) {
sb.append(
s"""<span id="custom-field-$fieldId-label" class="custom-field-label"><i class="octicon octicon-pencil" style="cursor: pointer;"></i></span>"""
)
} else {
sb.append(
s"""<span id="custom-field-$fieldId-label" class="custom-field-label">N/A</span>"""
)
}
}
sb.append(
s"""<span id="custom-field-$fieldId-label" class="custom-field-label">${StringUtil
.escapeHtml(value)}</span>""".stripMargin
)
if (editable) {
sb.append(
s"""<input type="$fieldType" id="custom-field-$fieldId-editor" class="form-control input-sm custom-field-editor" data-field-id="$fieldId" style="width: 120px; display: none;"/>"""
@@ -329,22 +149,19 @@ object CustomFieldBehavior {
|
|$$('#custom-field-$fieldId-editor').focusout(function(){
| const $$this = $$(this);
| $$.post('${helpers.url(repository)}/issues/customfield_validation/$fieldId',
| const fieldId = $$this.data('field-id');
| $$.post('${helpers.url(repository)}/issues/customfield_validation/' + fieldId,
| { value: $$this.val() },
| function(data){
| if (data != '') {
| $$('#custom-field-$fieldId-error').text(data);
| } else {
| $$('#custom-field-$fieldId-error').text('');
| $$.post('${helpers.url(repository)}/issues/$issueId/customfield/$fieldId',
| $$.post('${helpers.url(repository)}/issues/$issueId/customfield/' + fieldId,
| { value: $$this.val() },
| function(data){
| $$this.hide();
| if (data == '') {
| $$this.prev().html('<i class="octicon octicon-pencil" style="cursor: pointer;">').show();
| } else {
| $$this.prev().text(data).show();
| }
| $$this.prev().text(data).show();
| }
| );
| }
@@ -369,11 +186,6 @@ object CustomFieldBehavior {
sb.toString()
}
override def validate(
name: String,
constraints: Option[String],
value: String,
messages: Messages
): Option[String] = None
def validate(name: String, value: String, messages: Messages): Option[String] = None
}
}

View File

@@ -11,7 +11,7 @@ trait DeployKeyComponent extends TemplateComponent { self: Profile =>
val publicKey = column[String]("PUBLIC_KEY")
val allowWrite = column[Boolean]("ALLOW_WRITE")
def * =
(userName, repositoryName, deployKeyId, title, publicKey, allowWrite).mapTo[DeployKey]
(userName, repositoryName, deployKeyId, title, publicKey, allowWrite).<>(DeployKey.tupled, DeployKey.unapply)
def byPrimaryKey(userName: String, repositoryName: String, deployKeyId: Int) =
(this.userName === userName.bind) && (this.repositoryName === repositoryName.bind) && (this.deployKeyId === deployKeyId.bind)

View File

@@ -11,7 +11,7 @@ trait GpgKeyComponent { self: Profile =>
val gpgKeyId = column[Long]("GPG_KEY_ID")
val title = column[String]("TITLE")
val publicKey = column[String]("PUBLIC_KEY")
def * = (userName, keyId, gpgKeyId, title, publicKey).mapTo[GpgKey]
def * = (userName, keyId, gpgKeyId, title, publicKey).<>(GpgKey.tupled, GpgKey.unapply)
def byPrimaryKey(userName: String, keyId: Int) =
(this.userName === userName.bind) && (this.keyId === keyId.bind)

View File

@@ -9,7 +9,7 @@ trait GroupMemberComponent { self: Profile =>
val groupName = column[String]("GROUP_NAME", O PrimaryKey)
val userName = column[String]("USER_NAME", O PrimaryKey)
val isManager = column[Boolean]("MANAGER")
def * = (groupName, userName, isManager).mapTo[GroupMember]
def * = (groupName, userName, isManager).<>(GroupMember.tupled, GroupMember.unapply)
}
}

View File

@@ -47,7 +47,7 @@ trait IssueComponent extends TemplateComponent { self: Profile =>
registeredDate,
updatedDate,
pullRequest
).mapTo[Issue]
).<>(Issue.tupled, Issue.unapply)
def byPrimaryKey(owner: String, repository: String, issueId: Int) = byIssue(owner, repository, issueId)
}

View File

@@ -9,7 +9,8 @@ trait IssueAssigneeComponent extends TemplateComponent { self: Profile =>
class IssueAssignees(tag: Tag) extends Table[IssueAssignee](tag, "ISSUE_ASSIGNEE") with IssueTemplate {
val assigneeUserName = column[String]("ASSIGNEE_USER_NAME")
def * =
(userName, repositoryName, issueId, assigneeUserName).mapTo[IssueAssignee]
(userName, repositoryName, issueId, assigneeUserName)
.<>(IssueAssignee.tupled, IssueAssignee.unapply)
def byPrimaryKey(owner: String, repository: String, issueId: Int, assigneeUserName: String) = {
byIssue(owner, repository, issueId) && this.assigneeUserName === assigneeUserName.bind

View File

@@ -13,7 +13,8 @@ trait IssueCustomFieldComponent extends TemplateComponent { self: Profile =>
val fieldId = column[Int]("FIELD_ID", O.PrimaryKey)
val value = column[String]("VALUE")
def * =
(userName, repositoryName, issueId, fieldId, value).mapTo[IssueCustomField]
(userName, repositoryName, issueId, fieldId, value)
.<>(IssueCustomField.tupled, IssueCustomField.unapply)
def byPrimaryKey(owner: String, repository: String, issueId: Int, fieldId: Int) = {
this.userName === owner.bind && this.repositoryName === repository.bind && this.issueId === issueId.bind && this.fieldId === fieldId.bind

View File

@@ -6,7 +6,7 @@ trait IssueLabelComponent extends TemplateComponent { self: Profile =>
lazy val IssueLabels = TableQuery[IssueLabels]
class IssueLabels(tag: Tag) extends Table[IssueLabel](tag, "ISSUE_LABEL") with IssueTemplate with LabelTemplate {
def * = (userName, repositoryName, issueId, labelId).mapTo[IssueLabel]
def * = (userName, repositoryName, issueId, labelId).<>(IssueLabel.tupled, IssueLabel.unapply)
def byPrimaryKey(owner: String, repository: String, issueId: Int, labelId: Int) =
byIssue(owner, repository, issueId) && (this.labelId === labelId.bind)
}

View File

@@ -9,7 +9,7 @@ trait LabelComponent extends TemplateComponent { self: Profile =>
override val labelId = column[Int]("LABEL_ID", O AutoInc)
override val labelName = column[String]("LABEL_NAME")
val color = column[String]("COLOR")
def * = (userName, repositoryName, labelId, labelName, color).mapTo[Label]
def * = (userName, repositoryName, labelId, labelName, color).<>(Label.tupled, Label.unapply)
def byPrimaryKey(owner: String, repository: String, labelId: Int) = byLabel(owner, repository, labelId)
def byPrimaryKey(userName: Rep[String], repositoryName: Rep[String], labelId: Rep[Int]) =

View File

@@ -13,7 +13,8 @@ trait MilestoneComponent extends TemplateComponent { self: Profile =>
val dueDate = column[Option[java.util.Date]]("DUE_DATE")
val closedDate = column[Option[java.util.Date]]("CLOSED_DATE")
def * =
(userName, repositoryName, milestoneId, title, description, dueDate, closedDate).mapTo[Milestone]
(userName, repositoryName, milestoneId, title, description, dueDate, closedDate)
.<>(Milestone.tupled, Milestone.unapply)
def byPrimaryKey(owner: String, repository: String, milestoneId: Int) = byMilestone(owner, repository, milestoneId)
def byPrimaryKey(userName: Rep[String], repositoryName: Rep[String], milestoneId: Rep[Int]) =

View File

@@ -13,7 +13,8 @@ trait PriorityComponent extends TemplateComponent { self: Profile =>
val isDefault = column[Boolean]("IS_DEFAULT")
val color = column[String]("COLOR")
def * =
(userName, repositoryName, priorityId, priorityName, description.?, isDefault, ordering, color).mapTo[Priority]
(userName, repositoryName, priorityId, priorityName, description.?, isDefault, ordering, color)
.<>(Priority.tupled, Priority.unapply)
def byPrimaryKey(owner: String, repository: String, priorityId: Int) = byPriority(owner, repository, priorityId)
def byPrimaryKey(userName: Rep[String], repositoryName: Rep[String], priorityId: Rep[Int]) =

View File

@@ -7,7 +7,7 @@ trait ProtectedBranchComponent extends TemplateComponent { self: Profile =>
lazy val ProtectedBranches = TableQuery[ProtectedBranches]
class ProtectedBranches(tag: Tag) extends Table[ProtectedBranch](tag, "PROTECTED_BRANCH") with BranchTemplate {
val statusCheckAdmin = column[Boolean]("STATUS_CHECK_ADMIN")
def * = (userName, repositoryName, branch, statusCheckAdmin).mapTo[ProtectedBranch]
def * = (userName, repositoryName, branch, statusCheckAdmin).<>(ProtectedBranch.tupled, ProtectedBranch.unapply)
def byPrimaryKey(userName: String, repositoryName: String, branch: String) =
byBranch(userName, repositoryName, branch)
def byPrimaryKey(userName: Rep[String], repositoryName: Rep[String], branch: Rep[String]) =
@@ -20,7 +20,7 @@ trait ProtectedBranchComponent extends TemplateComponent { self: Profile =>
with BranchTemplate {
val context = column[String]("CONTEXT")
def * =
(userName, repositoryName, branch, context).mapTo[ProtectedBranchContext]
(userName, repositoryName, branch, context).<>(ProtectedBranchContext.tupled, ProtectedBranchContext.unapply)
}
}

View File

@@ -25,7 +25,7 @@ trait PullRequestComponent extends TemplateComponent { self: Profile =>
commitIdFrom,
commitIdTo,
isDraft
).mapTo[PullRequest]
).<>(PullRequest.tupled, PullRequest.unapply)
def byPrimaryKey(userName: String, repositoryName: String, issueId: Int) =
byIssue(userName, repositoryName, issueId)

View File

@@ -20,7 +20,7 @@ trait ReleaseAssetComponent extends TemplateComponent { self: Profile =>
def * =
(userName, repositoryName, tag, releaseAssetId, fileName, label, size, uploader, registeredDate, updatedDate)
.mapTo[ReleaseAsset]
.<>(ReleaseAsset.tupled, ReleaseAsset.unapply)
def byPrimaryKey(owner: String, repository: String, tag: String, fileName: String) =
byTag(owner, repository, tag) && (this.fileName === fileName.bind)
def byTag(owner: String, repository: String, tag: String) =

View File

@@ -15,7 +15,8 @@ trait ReleaseTagComponent extends TemplateComponent { self: Profile =>
val updatedDate = column[java.util.Date]("UPDATED_DATE")
def * =
(userName, repositoryName, name, tag, author, content, registeredDate, updatedDate).mapTo[ReleaseTag]
(userName, repositoryName, name, tag, author, content, registeredDate, updatedDate)
.<>(ReleaseTag.tupled, ReleaseTag.unapply)
def byPrimaryKey(owner: String, repository: String, tag: String) = byTag(owner, repository, tag)
def byTag(owner: String, repository: String, tag: String) =
byRepository(owner, repository) && (this.tag === tag.bind)

View File

@@ -14,7 +14,8 @@ trait RepositoryWebHookComponent extends TemplateComponent { self: Profile =>
val token = column[Option[String]]("TOKEN")
val ctype = column[WebHookContentType]("CTYPE")
def * =
(userName, repositoryName, hookId, url, ctype, token).mapTo[RepositoryWebHook]
(userName, repositoryName, hookId, url, ctype, token)
.<>((RepositoryWebHook.apply _).tupled, RepositoryWebHook.unapply)
def byRepositoryUrl(owner: String, repository: String, url: String) =
byRepository(owner, repository) && (this.url === url.bind)

View File

@@ -12,7 +12,7 @@ trait RepositoryWebHookEventComponent extends TemplateComponent { self: Profile
val url = column[String]("URL")
val event = column[WebHook.Event]("EVENT")
def * =
(userName, repositoryName, url, event).mapTo[RepositoryWebHookEvent]
(userName, repositoryName, url, event).<>((RepositoryWebHookEvent.apply _).tupled, RepositoryWebHookEvent.unapply)
def byRepositoryWebHook(owner: String, repository: String, url: String) =
byRepository(owner, repository) && (this.url === url.bind)

View File

@@ -10,7 +10,7 @@ trait SshKeyComponent { self: Profile =>
val sshKeyId = column[Int]("SSH_KEY_ID", O AutoInc)
val title = column[String]("TITLE")
val publicKey = column[String]("PUBLIC_KEY")
def * = (userName, sshKeyId, title, publicKey).mapTo[SshKey]
def * = (userName, sshKeyId, title, publicKey).<>(SshKey.tupled, SshKey.unapply)
def byPrimaryKey(userName: String, sshKeyId: Int) =
(this.userName === userName.bind) && (this.sshKeyId === sshKeyId.bind)

View File

@@ -9,10 +9,9 @@ import org.json4s.jackson.Serialization.{read, write}
import scala.util.Using
import java.io.FileOutputStream
import java.nio.charset.StandardCharsets
import gitbucket.core.controller.Context
import gitbucket.core.util.ConfigUtil
import org.apache.commons.io.input.ReversedLinesFileReader
import ActivityService._
import scala.collection.mutable.ListBuffer
@@ -27,52 +26,40 @@ trait ActivityService {
}
}
def getActivitiesByUser(activityUserName: String, publicOnly: Boolean)(implicit context: Context): List[Activity] = {
getActivities(includePublic = false) { activity =>
if (activity.activityUserName == activityUserName) {
!publicOnly || isPublicActivity(activity)
} else false
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).forall(_.isPrivate)) {
list += activity
}
}
}
}
}
list.toList
}
}
def getRecentPublicActivities()(implicit context: Context): List[Activity] = {
getActivities(includePublic = true) { _ =>
false
}
}
def getRecentActivitiesByRepos(repos: Set[(String, String)])(implicit context: Context): List[Activity] = {
getActivities(includePublic = true) { activity =>
repos.exists {
case (userName, repositoryName) =>
activity.userName == userName && activity.repositoryName == repositoryName
}
}
}
private def getActivities(
includePublic: Boolean
)(filter: Activity => Boolean)(implicit context: Context): List[Activity] = {
if (!isNewsFeedEnabled || !ActivityLog.exists()) {
if (!ActivityLog.exists()) {
List.empty
} else {
val list = new ListBuffer[Activity]
Using.resource(
ReversedLinesFileReader
.builder()
.setFile(ActivityLog)
.setCharset(StandardCharsets.UTF_8)
.get()
) { reader =>
Using.resource(new ReversedLinesFileReader(ActivityLog, StandardCharsets.UTF_8)) { reader =>
var json: String = null
while (list.length < 50 && {
json = reader.readLine();
json
} != null) {
while (list.length < 50 && { json = reader.readLine(); json } != null) {
val activity = read[Activity](json)
if (filter(activity)) {
list += activity
} else if (includePublic && isPublicActivity(activity)) {
if (!getRepositoryInfoFromCache(activity.userName, activity.repositoryName).forall(_.isPrivate)) {
list += activity
}
}
@@ -81,8 +68,24 @@ trait ActivityService {
}
}
private def isPublicActivity(activity: Activity)(implicit context: Context): Boolean = {
!getRepositoryInfoFromCache(activity.userName, activity.repositoryName).forall(_.isPrivate)
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).forall(_.isPrivate)) {
list += activity
}
}
}
list.toList
}
}
def recordActivity[T <: { def toActivity: Activity }](info: T): Unit = {
@@ -90,8 +93,3 @@ trait ActivityService {
writeLog(info.toActivity)
}
}
object ActivityService {
def isNewsFeedEnabled: Boolean =
!ConfigUtil.getConfigValue[Boolean]("gitbucket.disableNewsFeed").getOrElse(false)
}

View File

@@ -28,7 +28,6 @@ trait CustomFieldsService {
repository: String,
fieldName: String,
fieldType: String,
constraints: Option[String],
enableForIssues: Boolean,
enableForPullRequests: Boolean
)(implicit s: Session): Int = {
@@ -37,7 +36,6 @@ trait CustomFieldsService {
repositoryName = repository,
fieldName = fieldName,
fieldType = fieldType,
constraints = constraints,
enableForIssues = enableForIssues,
enableForPullRequests = enableForPullRequests
)
@@ -49,7 +47,6 @@ trait CustomFieldsService {
fieldId: Int,
fieldName: String,
fieldType: String,
constraints: Option[String],
enableForIssues: Boolean,
enableForPullRequests: Boolean
)(
@@ -57,8 +54,8 @@ trait CustomFieldsService {
): Unit =
CustomFields
.filter(_.byPrimaryKey(owner, repository, fieldId))
.map(t => (t.fieldName, t.fieldType, t.constraints, t.enableForIssues, t.enableForPullRequests))
.update((fieldName, fieldType, constraints, enableForIssues, enableForPullRequests))
.map(t => (t.fieldName, t.fieldType, t.enableForIssues, t.enableForPullRequests))
.update((fieldName, fieldType, enableForIssues, enableForPullRequests))
def deleteCustomField(owner: String, repository: String, fieldId: Int)(implicit s: Session): Unit = {
IssueCustomFields

View File

@@ -12,7 +12,6 @@ import gitbucket.core.model.{
IssueComment,
IssueLabel,
Label,
Profile,
PullRequest,
Repository,
Role
@@ -23,8 +22,6 @@ import gitbucket.core.model.Profile.profile.blockingApi._
import gitbucket.core.model.Profile.dateColumnType
import gitbucket.core.plugin.PluginRegistry
import scala.jdk.CollectionConverters._
trait IssuesService {
self: AccountService with RepositoryService with LabelsService with PrioritiesService with MilestonesService =>
import IssuesService._
@@ -382,8 +379,8 @@ trait IssuesService {
searchOption: IssueSearchOption
)(
implicit s: Session
) = {
val query = Issues filter { t1 =>
) =
Issues filter { t1 =>
(if (repos.sizeIs == 1) {
t1.byRepository(repos.head._1, repos.head._2)
} else {
@@ -393,8 +390,8 @@ trait IssuesService {
case "open" => t1.closed === false
case "closed" => t1.closed === true
case _ => t1.closed === true || t1.closed === false
}).&&(t1.milestoneId.? isEmpty, condition.milestone.contains(None))
.&&(t1.priorityId.? isEmpty, condition.priority.contains(None))
}).&&(t1.milestoneId.? isEmpty, condition.milestone == Some(None))
.&&(t1.priorityId.? isEmpty, condition.priority == Some(None))
//.&&(t1.assignedUserName.? isEmpty, condition.assigned == Some(None))
.&&(t1.openedUserName === condition.author.get.bind, condition.author.isDefined) &&
(searchOption match {
@@ -442,7 +439,7 @@ trait IssuesService {
.&&(
Repositories filter { t2 =>
(t2.byRepository(t1.userName, t1.repositoryName)) &&
(t2.isPrivate === condition.visibility.contains("private").bind)
(t2.isPrivate === (condition.visibility == Some("private")).bind)
} exists,
condition.visibility.nonEmpty
)
@@ -460,34 +457,6 @@ trait IssuesService {
)
}
condition.others.foldLeft(query) {
case (query, cond) =>
def condQuery(f: Rep[String] => Rep[Boolean]): Query[Profile.Issues, Issue, Seq] = {
query.filter { t1 =>
IssueCustomFields
.join(CustomFields)
.on { (t2, t3) =>
t2.userName === t3.userName && t2.repositoryName === t3.repositoryName && t2.fieldId === t3.fieldId
}
.filter {
case (t2, t3) =>
t1.byIssue(t2.userName, t2.repositoryName, t2.issueId) && t3.fieldName === cond.name.bind && f(
t2.value
)
} exists
}
}
cond.operator match {
case "eq" => condQuery(_ === cond.value.bind)
case "lt" => condQuery(_ < cond.value.bind)
case "gt" => condQuery(_ > cond.value.bind)
case "lte" => condQuery(_ <= cond.value.bind)
case "gte" => condQuery(_ >= cond.value.bind)
case _ => throw new IllegalArgumentException("Unsupported operator")
}
}
}
def insertIssue(
owner: String,
repository: String,
@@ -615,7 +584,7 @@ trait IssuesService {
.update(title, content, currentDate)
}
def changeIssueToPullRequest(owner: String, repository: String, issueId: Int)(implicit s: Session): Int = {
def changeIssueToPullRequest(owner: String, repository: String, issueId: Int)(implicit s: Session) = {
Issues
.filter(_.byPrimaryKey(owner, repository, issueId))
.map { t =>
@@ -974,8 +943,6 @@ object IssuesService {
val IssueLimit = 25
case class CustomFieldCondition(name: String, value: String, operator: String)
case class IssueSearchCondition(
labels: Set[String] = Set.empty,
milestone: Option[Option[String]] = None,
@@ -987,8 +954,7 @@ object IssuesService {
sort: String = "created",
direction: String = "desc",
visibility: Option[String] = None,
groups: Set[String] = Set.empty,
others: Seq[CustomFieldCondition] = Nil
groups: Set[String] = Set.empty
) {
def isEmpty: Boolean = {
@@ -1027,148 +993,48 @@ object IssuesService {
case ("priority", "asc") => Some("sort:priority-asc")
case x => throw new MatchError(x)
},
visibility.map(visibility => s"visibility:${visibility}"),
visibility.map(visibility => s"visibility:${visibility}")
).flatten ++
others.map { cond =>
cond.operator match {
case "eq" => s"custom.${cond.name}:${cond.value}"
case "lt" => s"custom.${cond.name}<${cond.value}"
case "lte" => s"custom.${cond.name}<=${cond.value}"
case "gt" => s"custom.${cond.name}>${cond.value}"
case "gte" => s"custom.${cond.name}>=${cond.value}"
}
} ++
groups.map(group => s"group:${group}")
).mkString(" ")
def toURL: String = {
"?" + (Seq(
def toURL: String =
"?" + List(
if (labels.isEmpty) None else Some("labels=" + urlEncode(labels.mkString(","))),
milestone.map {
case Some(x) => s"milestone=${urlEncode(x)}"
case Some(x) => "milestone=" + urlEncode(x)
case None => "milestone=none"
},
priority.map {
case Some(x) => s"priority=${urlEncode(x)}"
case Some(x) => "priority=" + urlEncode(x)
case None => "priority=none"
},
author.map(x => s"author=${urlEncode(x)}"),
author.map(x => "author=" + urlEncode(x)),
assigned.map {
case Some(x) => s"assigned=${urlEncode(x)}"
case Some(x) => "assigned=" + urlEncode(x)
case None => "assigned=none"
},
mentioned.map(x => s"mentioned=${urlEncode(x)}"),
Some(s"state=${urlEncode(state)}"),
Some(s"sort=${urlEncode(sort)}"),
Some(s"direction=${urlEncode(direction)}"),
visibility.map(x => s"visibility=${urlEncode(x)}"),
if (groups.isEmpty) None else Some(s"groups=${urlEncode(groups.mkString(","))}")
).flatten ++ others.map { x =>
s"custom.${urlEncode(x.name)}=${urlEncode(x.operator)}:${urlEncode(x.value)}"
}).mkString("&")
}
mentioned.map(x => "mentioned=" + urlEncode(x)),
Some("state=" + urlEncode(state)),
Some("sort=" + urlEncode(sort)),
Some("direction=" + urlEncode(direction)),
visibility.map(x => "visibility=" + urlEncode(x)),
if (groups.isEmpty) None else Some("groups=" + urlEncode(groups.mkString(",")))
).flatten.mkString("&")
}
object IssueSearchCondition {
private val SupportedOperators = Seq("eq", "lt", "gt", "lte", "gte")
private def param(request: HttpServletRequest, name: String, allow: Seq[String] = Nil): Option[String] = {
val value = request.getParameter(name)
if (value == null || value.isEmpty || (allow.nonEmpty && !allow.contains(value))) None else Some(value)
}
/**
* Restores IssueSearchCondition instance from filter query.
*/
def apply(filter: String): IssueSearchCondition = {
val conditions = filter
.split("[  \t]+")
.collect {
case x if !x.startsWith("custom.") && x.indexOf(":") > 0 =>
val dim = x.split(":")
dim(0) -> dim(1)
}
.groupBy(_._1)
.map {
case (key, values) =>
key -> values.map(_._2).toSeq
}
val (sort, direction) = conditions.get("sort").flatMap(_.headOption).getOrElse("created-desc") match {
case "created-asc" => ("created", "asc")
case "comments-desc" => ("comments", "desc")
case "comments-asc" => ("comments", "asc")
case "updated-desc" => ("comments", "desc")
case "updated-asc" => ("comments", "asc")
case _ => ("created", "desc")
}
val others = filter
.split("[  \t]+")
.collect {
case x if x.startsWith("custom.") && x.indexOf(":") > 0 =>
val dim = x.split(":")
dim(0) -> ("eq", dim(1))
case x if x.startsWith("custom.") && x.indexOf("<=") > 0 =>
val dim = x.split("<=")
dim(0) -> ("lte", dim(1))
case x if x.startsWith("custom.") && x.indexOf("<") > 0 =>
val dim = x.split("<")
dim(0) -> ("lt", dim(1))
case x if x.startsWith("custom.") && x.indexOf(">=") > 0 =>
val dim = x.split(">=")
dim(0) -> ("gte", dim(1))
case x if x.startsWith("custom.") && x.indexOf(">") > 0 =>
val dim = x.split(">")
dim(0) -> ("gt", dim(1))
}
.map {
case (key, (operator, value)) =>
CustomFieldCondition(key.stripPrefix("custom."), value, operator)
}
.toSeq
IssueSearchCondition(
conditions.get("label").map(_.toSet).getOrElse(Set.empty),
conditions.get("milestone").flatMap(_.headOption) match {
case None => None
case Some("none") => Some(None)
case Some(x) => Some(Some(x)) //milestones.get(x).map(x => Some(x))
},
conditions.get("priority").map(_.headOption), // TODO
conditions.get("author").flatMap(_.headOption),
conditions.get("assignee").map(_.headOption), // TODO
conditions.get("mentions").flatMap(_.headOption),
conditions.get("is").getOrElse(Seq.empty).find(x => x == "open" || x == "closed").getOrElse("open"),
sort,
direction,
conditions.get("visibility").flatMap(_.headOption),
conditions.get("group").map(_.toSet).getOrElse(Set.empty),
others
)
}
/**
* Restores IssueSearchCondition instance from request parameters.
*/
def apply(request: HttpServletRequest): IssueSearchCondition = {
val others = request.getParameterMap.asScala
.collect {
// custom.<field_name> = <operator>:<value>
case (key, values) if key.startsWith("custom.") && values.nonEmpty && values.head.indexOf(":") > 0 =>
val name = key.stripPrefix("custom.")
val Array(operator, value) = values.head.split(":")
CustomFieldCondition(name, value, operator)
case (key, values) if key.startsWith("custom.") && values.nonEmpty =>
val name = key.stripPrefix("custom.")
CustomFieldCondition(name, values.head, "eq")
}
.filter { x =>
SupportedOperators.contains(x.operator)
}
.toSeq
def apply(request: HttpServletRequest): IssueSearchCondition =
IssueSearchCondition(
param(request, "labels").map(_.split(",").toSet).getOrElse(Set.empty),
param(request, "milestone").map {
@@ -1189,16 +1055,31 @@ object IssuesService {
param(request, "sort", Seq("created", "comments", "updated", "priority")).getOrElse("created"),
param(request, "direction", Seq("asc", "desc")).getOrElse("desc"),
param(request, "visibility"),
param(request, "groups").map(_.split(",").toSet).getOrElse(Set.empty),
others
param(request, "groups").map(_.split(",").toSet).getOrElse(Set.empty)
)
}
def apply(request: HttpServletRequest, milestone: String): IssueSearchCondition = {
apply(request).copy(milestone = Some(Some(milestone)))
}
def apply(request: HttpServletRequest, milestone: String): IssueSearchCondition =
IssueSearchCondition(
param(request, "labels").map(_.split(",").toSet).getOrElse(Set.empty),
Some(Some(milestone)),
param(request, "priority").map {
case "none" => None
case x => Some(x)
},
param(request, "author"),
param(request, "assigned").map {
case "none" => None
case x => Some(x)
},
param(request, "mentioned"),
param(request, "state", Seq("open", "closed", "all")).getOrElse("open"),
param(request, "sort", Seq("created", "comments", "updated", "priority")).getOrElse("created"),
param(request, "direction", Seq("asc", "desc")).getOrElse("desc"),
param(request, "visibility"),
param(request, "groups").map(_.split(",").toSet).getOrElse(Set.empty)
)
def page(request: HttpServletRequest): Int = {
def page(request: HttpServletRequest) = {
PaginationHelper.page(param(request, "page"))
}
}

View File

@@ -1,11 +1,11 @@
package gitbucket.core.service
import java.net.URI
import com.nimbusds.jose.JWSAlgorithm.Family
import com.nimbusds.jose.proc.BadJOSEException
import com.nimbusds.jose.util.DefaultResourceRetriever
import com.nimbusds.jose.{JOSEException, JWSAlgorithm}
import com.nimbusds.jwt.JWT
import com.nimbusds.oauth2.sdk._
import com.nimbusds.oauth2.sdk.auth.ClientSecretBasic
import com.nimbusds.oauth2.sdk.id.{ClientID, Issuer, State}
@@ -52,11 +52,6 @@ trait OpenIDConnectService {
)
}
def createOIDLogoutRequest(issuer: Issuer, clientID: ClientID, redirectURI: URI, token: JWT): LogoutRequest = {
val metadata = OIDCProviderMetadata.resolve(issuer)
new LogoutRequest(metadata.getEndSessionEndpointURI, token, null, clientID, redirectURI, null, null)
}
/**
* Proceed the OpenID Connect authentication.
*
@@ -65,7 +60,7 @@ trait OpenIDConnectService {
* @param state State saved in the session
* @param nonce Nonce saved in the session
* @param oidc OIDC settings
* @return (ID token, GitBucket account)
* @return ID token
*/
def authenticate(
params: Map[String, String],
@@ -73,25 +68,22 @@ trait OpenIDConnectService {
state: State,
nonce: Nonce,
oidc: SystemSettingsService.OIDC
)(implicit s: Session): Option[(JWT, Account)] =
)(implicit s: Session): Option[Account] =
validateOIDCAuthenticationResponse(params, state, redirectURI) flatMap { authenticationResponse =>
obtainOIDCToken(authenticationResponse.getAuthorizationCode, nonce, redirectURI, oidc) flatMap {
case (jwt, claims) =>
Seq("email", "preferred_username", "name").map(k => Option(claims.getStringClaim(k))) match {
case Seq(Some(email), preferredUsername, name) =>
getOrCreateFederatedUser(
claims.getIssuer.getValue,
claims.getSubject.getValue,
email,
preferredUsername,
name
).map { account =>
(jwt, account)
}
case _ =>
logger.info(s"OIDC ID token must have an email claim: claims=${claims.toJSONObject}")
None
}
obtainOIDCToken(authenticationResponse.getAuthorizationCode, nonce, redirectURI, oidc) flatMap { claims =>
Seq("email", "preferred_username", "name").map(k => Option(claims.getStringClaim(k))) match {
case Seq(Some(email), preferredUsername, name) =>
getOrCreateFederatedUser(
claims.getIssuer.getValue,
claims.getSubject.getValue,
email,
preferredUsername,
name
)
case _ =>
logger.info(s"OIDC ID token must have an email claim: claims=${claims.toJSONObject}")
None
}
}
}
@@ -144,7 +136,7 @@ trait OpenIDConnectService {
nonce: Nonce,
redirectURI: URI,
oidc: SystemSettingsService.OIDC
): Option[(JWT, IDTokenClaimsSet)] = {
): Option[IDTokenClaimsSet] = {
val metadata = OIDCProviderMetadata.resolve(oidc.issuer)
val tokenRequest = new TokenRequest(
metadata.getTokenEndpointURI,
@@ -181,7 +173,7 @@ trait OpenIDConnectService {
metadata: OIDCProviderMetadata,
nonce: Nonce,
oidc: SystemSettingsService.OIDC
): Option[(JWT, IDTokenClaimsSet)] =
): Option[IDTokenClaimsSet] =
Option(response.getOIDCTokens.getIDToken) match {
case Some(jwt) =>
val validator = oidc.jwsAlgorithm map { jwsAlgorithm =>
@@ -196,7 +188,7 @@ trait OpenIDConnectService {
new IDTokenValidator(metadata.getIssuer, oidc.clientID)
}
try {
Some((jwt, validator.validate(jwt, nonce)))
Some(validator.validate(jwt, nonce))
} catch {
case e @ (_: BadJOSEException | _: JOSEException) =>
logger.info(s"OIDC ID token has error: $e")

View File

@@ -64,19 +64,9 @@ trait RepositoryCreationService {
name: String,
description: Option[String],
isPrivate: Boolean,
createReadme: Boolean,
defaultBranch: String
createReadme: Boolean
): Future[Unit] = {
createRepository(
loginAccount,
owner,
name,
description,
isPrivate,
if (createReadme) "README" else "EMPTY",
None,
defaultBranch
)
createRepository(loginAccount, owner, name, description, isPrivate, if (createReadme) "README" else "EMPTY", None)
}
def createRepository(
@@ -86,8 +76,7 @@ trait RepositoryCreationService {
description: Option[String],
isPrivate: Boolean,
initOption: String,
sourceUrl: Option[String],
defaultBranch: String
sourceUrl: Option[String]
): Future[Unit] = Future {
RepositoryCreationService.startCreation(owner, name)
try {
@@ -104,7 +93,7 @@ trait RepositoryCreationService {
} else None
// Insert to the database at first
insertRepository(name, owner, description, isPrivate, defaultBranch)
insertRepository(name, owner, description, isPrivate)
// // Add collaborators for group repository
// if(ownerAccount.isGroupAccount){
@@ -121,7 +110,7 @@ trait RepositoryCreationService {
// Create the actual repository
val gitdir = getRepositoryDir(owner, name)
JGitUtil.initRepository(gitdir, defaultBranch)
JGitUtil.initRepository(gitdir)
if (initOption == "README" || initOption == "EMPTY_COMMIT") {
Using.resource(Git.open(gitdir)) { git =>
@@ -174,7 +163,7 @@ trait RepositoryCreationService {
}
// Create Wiki repository
createWikiRepository(loginAccount, owner, name, defaultBranch)
createWikiRepository(loginAccount, owner, name)
// Record activity
recordActivity(CreateRepositoryInfo(owner, name, loginUserName))

View File

@@ -34,7 +34,7 @@ trait RepositoryService {
userName: String,
description: Option[String],
isPrivate: Boolean,
defaultBranch: String,
defaultBranch: String = "master",
originRepositoryName: Option[String] = None,
originUserName: Option[String] = None,
parentRepositoryName: Option[String] = None,
@@ -254,7 +254,6 @@ trait RepositoryService {
Labels.filter(_.byRepository(userName, repositoryName)).delete
IssueComments.filter(_.byRepository(userName, repositoryName)).delete
PullRequests.filter(_.byRepository(userName, repositoryName)).delete
IssueAssignees.filter(_.byRepository(userName, repositoryName)).delete
Issues.filter(_.byRepository(userName, repositoryName)).delete
Priorities.filter(_.byRepository(userName, repositoryName)).delete
IssueId.filter(_.byRepository(userName, repositoryName)).delete

View File

@@ -90,7 +90,6 @@ trait SystemSettingsService {
props.setProperty(UploadLargeMaxFileSize, settings.upload.largeMaxFileSize.toString)
props.setProperty(UploadLargeTimeout, settings.upload.largeTimeout.toString)
props.setProperty(RepositoryViewerMaxFiles, settings.repositoryViewer.maxFiles.toString)
props.setProperty(DefaultBranch, settings.defaultBranch)
Using.resource(new java.io.FileOutputStream(GitBucketConf)) { out =>
props.store(out, null)
@@ -206,8 +205,7 @@ trait SystemSettingsService {
),
RepositoryViewerSettings(
getValue(props, RepositoryViewerMaxFiles, 0)
),
getValue(props, DefaultBranch, "main")
)
)
}
}
@@ -233,8 +231,7 @@ object SystemSettingsService {
showMailAddress: Boolean,
webHook: WebHook,
upload: Upload,
repositoryViewer: RepositoryViewerSettings,
defaultBranch: String
repositoryViewer: RepositoryViewerSettings
) {
def baseUrl(request: HttpServletRequest): String =
baseUrl.getOrElse(parseBaseUrl(request)).stripSuffix("/")
@@ -405,6 +402,7 @@ object SystemSettingsService {
private val RepositoryOperationFork = "repository_operation_fork"
private val Gravatar = "gravatar"
private val Notification = "notification"
private val ActivityLogLimit = "activity_log_limit"
private val LimitVisibleRepositories = "limitVisibleRepositories"
private val SshEnabled = "ssh"
private val SshHost = "ssh.host"
@@ -450,7 +448,6 @@ object SystemSettingsService {
private val UploadLargeMaxFileSize = "upload.largeMaxFileSize"
private val UploadLargeTimeout = "upload.largeTimeout"
private val RepositoryViewerMaxFiles = "repository_viewer_max_files"
private val DefaultBranch = "default_branch"
private def getValue[A: ClassTag](props: java.util.Properties, key: String, default: A): A = {
getConfigValue(key).getOrElse {

View File

@@ -51,11 +51,11 @@ object WikiService {
trait WikiService {
import WikiService._
def createWikiRepository(loginAccount: Account, owner: String, repository: String, defaultBranch: String): Unit =
def createWikiRepository(loginAccount: Account, owner: String, repository: String): Unit =
LockUtil.lock(s"${owner}/${repository}/wiki") {
val dir = Directory.getWikiRepositoryDir(owner, repository)
if (!dir.exists) {
JGitUtil.initRepository(dir, defaultBranch)
JGitUtil.initRepository(dir)
saveWikiPage(
owner,
repository,
@@ -72,11 +72,11 @@ trait WikiService {
/**
* Returns the wiki page.
*/
def getWikiPage(owner: String, repository: String, pageName: String, branch: String): Option[WikiPageInfo] = {
def getWikiPage(owner: String, repository: String, pageName: String): Option[WikiPageInfo] = {
Using.resource(Git.open(Directory.getWikiRepositoryDir(owner, repository))) { git =>
if (!JGitUtil.isEmpty(git)) {
val fileName = pageName + ".md"
JGitUtil.getLatestCommitFromPath(git, fileName, branch).map { latestCommit =>
JGitUtil.getLatestCommitFromPath(git, fileName, "master").map { latestCommit =>
val content = JGitUtil.getContentFromPath(git, latestCommit.getTree, fileName, true)
WikiPageInfo(
fileName,
@@ -93,10 +93,10 @@ trait WikiService {
/**
* Returns the list of wiki page names.
*/
def getWikiPageList(owner: String, repository: String, branch: String): List[String] = {
def getWikiPageList(owner: String, repository: String): List[String] = {
Using.resource(Git.open(Directory.getWikiRepositoryDir(owner, repository))) { git =>
JGitUtil
.getFileList(git, branch, ".")
.getFileList(git, "master", ".")
.filter(_.name.endsWith(".md"))
.filterNot(_.name.startsWith("_"))
.map(_.name.stripSuffix(".md"))
@@ -113,8 +113,7 @@ trait WikiService {
from: String,
to: String,
committer: Account,
pageName: Option[String],
branch: String
pageName: Option[String]
): Boolean = {
case class RevertInfo(operation: String, filePath: String, source: String)
@@ -152,7 +151,7 @@ trait WikiService {
fh.getChangeType match {
case DiffEntry.ChangeType.MODIFY => {
val source =
getWikiPage(owner, repository, fh.getNewPath.stripSuffix(".md"), branch).map(_.content).getOrElse("")
getWikiPage(owner, repository, fh.getNewPath.stripSuffix(".md")).map(_.content).getOrElse("")
val applied = PatchUtil.apply(source, patch, fh)
if (applied != null) {
Seq(RevertInfo("ADD", fh.getNewPath, applied))

View File

@@ -245,7 +245,7 @@ class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl:
with RequestCache {
private val logger = LoggerFactory.getLogger(classOf[CommitLogHook])
private var newCommitIds: Seq[String] = Nil
private var existIds: Seq[String] = Nil
def onPreReceive(receivePack: ReceivePack, commands: java.util.Collection[ReceiveCommand]): Unit = {
Database() withTransaction { implicit session =>
@@ -260,43 +260,13 @@ class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl:
}
}
Using.resource(Git.open(Directory.getRepositoryDir(owner, repository))) { git =>
commands.asScala.foreach { command =>
val commits = getCommits(git, command)
if (commits.size < 100) {
newCommitIds = commits
.takeWhile { commit =>
!existCommit(git, commit)
}
.map(_.id)
} else {
val allCommits = JGitUtil.getAllCommitIds(git)
newCommitIds = commits.collect {
case commit if !allCommits.contains(commit.id) =>
commit.id
}
}
}
existIds = JGitUtil.getAllCommitIds(git)
}
} catch {
case ex: Exception =>
case ex: Exception => {
logger.error(ex.toString, ex)
throw ex
}
}
}
private def existCommit(git: Git, commit: CommitInfo): Boolean = {
JGitUtil.getBranchesOfCommit(git, commit.id).nonEmpty
}
private def getCommits(git: Git, command: ReceiveCommand): Seq[CommitInfo] = {
val refName = command.getRefName.split("/")
if (refName(1) == "tags") {
Nil
} else {
command.getType match {
case ReceiveCommand.Type.DELETE => Nil
case _ => JGitUtil.getCommitLog(git, command.getOldId.name, command.getNewId.name)
}
}
}
}
@@ -309,12 +279,20 @@ class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl:
Using.resource(Git.open(Directory.getRepositoryDir(owner, repository))) { git =>
JGitUtil.removeCache(git)
val pushedIds = scala.collection.mutable.Set[String]()
commands.asScala.foreach { command =>
logger.debug(s"commandType: ${command.getType}, refName: ${command.getRefName}")
implicit val apiContext: Context = api.JsonFormat.Context(baseUrl, sshUrl)
val refName = command.getRefName.split("/")
val branchName = refName.drop(2).mkString("/")
val commits = getCommits(git, command)
val commits = if (refName(1) == "tags") {
Nil
} else {
command.getType match {
case ReceiveCommand.Type.DELETE => Nil
case _ => JGitUtil.getCommitLog(git, command.getOldId.name, command.getNewId.name)
}
}
val repositoryInfo = getRepository(owner, repository).get
@@ -334,9 +312,8 @@ class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl:
// Extract new commit and apply issue comment
val defaultBranch = repositoryInfo.repository.defaultBranch
val pushedIds = scala.collection.mutable.Set[String]()
val newCommits = commits.collect {
case commit if newCommitIds.contains(commit.id) && !pushedIds.contains(commit.id) =>
val newCommits = commits.flatMap { commit =>
if (!existIds.contains(commit.id) && !pushedIds.contains(commit.id)) {
if (issueCount > 0) {
pushedIds.add(commit.id)
createIssueComment(owner, repository, commit)
@@ -356,8 +333,9 @@ class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl:
}
}
}
commit
}.toList
Some(commit)
} else None
}
// set PR as merged
val pulls = getPullRequestsByBranch(owner, repository, branchName, Some(false))
@@ -453,9 +431,10 @@ class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl:
// update repository last modified time.
updateLastActivityDate(owner, repository)
} catch {
case ex: Exception =>
case ex: Exception => {
logger.error(ex.toString, ex)
throw ex
}
}
}
}
@@ -527,9 +506,10 @@ class WikiCommitHook(owner: String, repository: String, pusher: String, baseUrl:
}
}
} catch {
case ex: Exception =>
case ex: Exception => {
logger.error(ex.toString, ex)
throw ex
}
}
}
}

View File

@@ -99,26 +99,26 @@ object DatabaseType {
object H2 extends DatabaseType {
val jdbcDriver = "org.h2.Driver"
val slickDriver: BlockingJdbcProfile = BlockingH2Driver
val liquiDriver: AbstractJdbcDatabase = new H2Database()
val slickDriver = BlockingH2Driver
val liquiDriver = new H2Database()
}
object MySQL extends DatabaseType {
val jdbcDriver = "org.mariadb.jdbc.Driver"
val slickDriver: BlockingJdbcProfile = BlockingMySQLDriver
val liquiDriver: AbstractJdbcDatabase = new MySQLDatabase()
val slickDriver = BlockingMySQLDriver
val liquiDriver = new MySQLDatabase()
}
object MariaDb extends DatabaseType {
val jdbcDriver = "org.mariadb.jdbc.Driver"
val slickDriver: BlockingJdbcProfile = BlockingMySQLDriver
val liquiDriver: AbstractJdbcDatabase = new MariaDBDatabase()
val slickDriver = BlockingMySQLDriver
val liquiDriver = new MariaDBDatabase()
}
object PostgreSQL extends DatabaseType {
val jdbcDriver = "org.postgresql.Driver2"
val slickDriver: BlockingJdbcProfile = BlockingPostgresDriver
val liquiDriver: AbstractJdbcDatabase = new PostgresDatabase()
val slickDriver = BlockingPostgresDriver
val liquiDriver = new PostgresDatabase()
}
object BlockingPostgresDriver extends slick.jdbc.PostgresProfile with BlockingJdbcProfile {

View File

@@ -1,7 +1,8 @@
package gitbucket.core.util
import java.io.ByteArrayInputStream
import scala.jdk.CollectionConverters._
import gitbucket.core.model.Profile._
import gitbucket.core.model.Profile.profile.blockingApi._
import org.bouncycastle.bcpg.ArmoredInputStream
@@ -33,33 +34,29 @@ object GpgUtil {
}
def verifySign(signInfo: JGitUtil.GpgSignInfo)(implicit s: Session): Option[JGitUtil.GpgVerifyInfo] = {
try {
new BcPGPObjectFactory(new ArmoredInputStream(new ByteArrayInputStream(signInfo.signArmored)))
.iterator()
.asScala
.flatMap {
case signList: PGPSignatureList =>
signList
.iterator()
.asScala
.flatMap { sign =>
getGpgKey(sign.getKeyID)
.map { pubKey =>
sign.init(new BcPGPContentVerifierBuilderProvider, pubKey)
sign.update(signInfo.target)
(sign, pubKey)
}
.collect {
case (sign, pubKey) if sign.verify() =>
JGitUtil.GpgVerifyInfo(pubKey.getUserIDs.next, pubKey.getKeyID.toHexString.toUpperCase)
}
}
}
.toList
.headOption
} catch {
case _: Throwable => None
}
new BcPGPObjectFactory(new ArmoredInputStream(new ByteArrayInputStream(signInfo.signArmored)))
.iterator()
.asScala
.flatMap {
case signList: PGPSignatureList =>
signList
.iterator()
.asScala
.flatMap { sign =>
getGpgKey(sign.getKeyID)
.map { pubKey =>
sign.init(new BcPGPContentVerifierBuilderProvider, pubKey)
sign.update(signInfo.target)
(sign, pubKey)
}
.collect {
case (sign, pubKey) if sign.verify() =>
JGitUtil.GpgVerifyInfo(pubKey.getUserIDs.next, pubKey.getKeyID.toHexString.toUpperCase)
}
}
}
.toList
.headOption
}
}

View File

@@ -1,6 +1,7 @@
package gitbucket.core.util
import java.io._
import gitbucket.core.service.RepositoryService
import org.eclipse.jgit.api.Git
import Directory._
@@ -17,10 +18,10 @@ import org.eclipse.jgit.treewalk.filter._
import org.eclipse.jgit.diff.DiffEntry.ChangeType
import org.eclipse.jgit.errors.{ConfigInvalidException, IncorrectObjectTypeException, MissingObjectException}
import org.eclipse.jgit.transport.RefSpec
import java.util.Date
import java.util.concurrent.TimeUnit
import org.cache2k.{Cache, Cache2kBuilder}
import org.cache2k.Cache2kBuilder
import org.eclipse.jgit.api.errors._
import org.eclipse.jgit.diff.{DiffEntry, DiffFormatter, RawTextComparator}
import org.eclipse.jgit.dircache.DirCacheEntry
@@ -39,9 +40,6 @@ object JGitUtil {
private implicit val objectDatabaseReleasable: Releasable[ObjectDatabase] =
_.close()
private def isCacheEnabled(): Boolean =
!ConfigUtil.getConfigValue[Boolean]("gitbucket.disableCache").getOrElse(false)
/**
* The repository data.
*
@@ -286,34 +284,26 @@ object JGitUtil {
revCommit
}
private val cache: Cache[String, Int] = if (isCacheEnabled()) {
Cache2kBuilder
.of(classOf[String], classOf[Int])
.name("commit-count")
.expireAfterWrite(24, TimeUnit.HOURS)
.entryCapacity(10000)
.build()
} else null
private val cache = new Cache2kBuilder[String, Int]() {}
.name("commit-count")
.expireAfterWrite(24, TimeUnit.HOURS)
.entryCapacity(10000)
.build()
private val objectCommitCache: Cache[ObjectId, RevCommit] = if (isCacheEnabled()) {
Cache2kBuilder
.of(classOf[ObjectId], classOf[RevCommit])
.name("object-commit")
.entryCapacity(10000)
.build()
} else null
private val objectCommitCache = new Cache2kBuilder[ObjectId, RevCommit]() {}
.name("object-commit")
.entryCapacity(10000)
.build()
def removeCache(git: Git): Unit = {
if (isCacheEnabled()) {
val dir = git.getRepository.getDirectory
val keyPrefix = dir.getAbsolutePath + "@"
val dir = git.getRepository.getDirectory
val keyPrefix = dir.getAbsolutePath + "@"
cache.keys.forEach(key => {
if (key.startsWith(keyPrefix)) {
cache.remove(key)
}
})
}
cache.keys.forEach(key => {
if (key.startsWith(keyPrefix)) {
cache.remove(key)
}
})
}
/**
@@ -322,23 +312,16 @@ object JGitUtil {
*/
def getCommitCount(git: Git, branch: String, max: Int = 10001): Int = {
val dir = git.getRepository.getDirectory
val key = dir.getAbsolutePath + "@" + branch
val entry = cache.getEntry(key)
if (isCacheEnabled()) {
val key = dir.getAbsolutePath + "@" + branch
val entry = cache.getEntry(key)
if (entry == null) {
val commitId = git.getRepository.resolve(branch)
val commitCount = git.log.add(commitId).call.iterator.asScala.take(max).size
cache.put(key, commitCount)
commitCount
} else {
entry.getValue
}
} else {
if (entry == null) {
val commitId = git.getRepository.resolve(branch)
val commitCount = git.log.add(commitId).call.iterator.asScala.take(max).size
cache.put(key, commitCount)
commitCount
} else {
entry.getValue
}
}
@@ -461,7 +444,7 @@ object JGitUtil {
(id, mode, name, path, opt, None)
} else if (commitCount < 10000) {
(id, mode, name, path, opt, Some(getCommit(path)))
} else if (isCacheEnabled()) {
} else {
// Use in-memory cache if the commit count is too big.
val cached = objectCommitCache.getEntry(id)
if (cached == null) {
@@ -471,9 +454,6 @@ object JGitUtil {
} else {
(id, mode, name, path, opt, Some(cached.getValue))
}
} else {
val commit = getCommit(path)
(id, mode, name, path, opt, Some(commit))
}
}
}
@@ -892,11 +872,10 @@ object JGitUtil {
.reverse
}
def initRepository(dir: java.io.File, defaultBranch: String): Unit =
Using.resource(new RepositoryBuilder().setGitDir(dir).setBare().setInitialBranch(defaultBranch).build) {
repository =>
repository.create(true)
setReceivePack(repository)
def initRepository(dir: java.io.File): Unit =
Using.resource(new RepositoryBuilder().setGitDir(dir).setBare.build) { repository =>
repository.create(true)
setReceivePack(repository)
}
def cloneRepository(from: java.io.File, to: java.io.File): Unit =

View File

@@ -28,12 +28,7 @@ object Keys {
/**
* Session key for the OpenID Connect authentication.
*/
val OidcAuthContext = "oidcAuthContext"
/**
* Session key for the OpenID Connect token.
*/
val OidcSessionContext = "oidcSessionContext"
val OidcContext = "oidcContext"
/**
* Generate session key for the issue search condition.

View File

@@ -56,15 +56,6 @@
<textarea id="information" name="information" class="form-control" style="height: 100px;">@context.settings.information</textarea>
</fieldset>
<!--====================================================================-->
<!-- Default branch -->
<!--====================================================================-->
<hr>
<label for="defaultBranch"><span class="strong">Default branch</span></label>
<fieldset>
<input type="text" name="defaultBranch" id="defaultBranch" class="form-control" value="@context.settings.defaultBranch"/>
<span id="error-defaultBranch" class="error"></span>
</fieldset>
<!--====================================================================-->
<!-- AdminLTE SkinName -->
<!--====================================================================-->
<hr>

View File

@@ -5,11 +5,10 @@
condition: gitbucket.core.service.IssuesService.IssueSearchCondition,
filter: String,
groups: List[String],
recentRepositories: List[gitbucket.core.service.RepositoryService.RepositoryInfo],
enableNewsFeed: Boolean)(implicit context: gitbucket.core.controller.Context)
recentRepositories: List[gitbucket.core.service.RepositoryService.RepositoryInfo])(implicit context: gitbucket.core.controller.Context)
@gitbucket.core.html.main("Issues"){
@gitbucket.core.dashboard.html.sidebar(recentRepositories){
@gitbucket.core.dashboard.html.tab(enableNewsFeed, "issues")
@gitbucket.core.dashboard.html.tab("issues")
<div class="container">
@gitbucket.core.dashboard.html.issuesnavi("issues", filter, openCount, closedCount, condition)
@gitbucket.core.dashboard.html.issueslist(issues, page, openCount, closedCount, condition, filter, groups)

View File

@@ -5,11 +5,10 @@
condition: gitbucket.core.service.IssuesService.IssueSearchCondition,
filter: String,
groups: List[String],
recentRepositories: List[gitbucket.core.service.RepositoryService.RepositoryInfo],
enableNewsFeed: Boolean)(implicit context: gitbucket.core.controller.Context)
recentRepositories: List[gitbucket.core.service.RepositoryService.RepositoryInfo])(implicit context: gitbucket.core.controller.Context)
@gitbucket.core.html.main("Pull requests"){
@gitbucket.core.dashboard.html.sidebar(recentRepositories){
@gitbucket.core.dashboard.html.tab(enableNewsFeed, "pulls")
@gitbucket.core.dashboard.html.tab("pulls")
<div class="container">
@gitbucket.core.dashboard.html.issuesnavi("pulls", filter, openCount, closedCount, condition)
@gitbucket.core.dashboard.html.issueslist(issues, page, openCount, closedCount, condition, filter, groups)

View File

@@ -1,11 +1,10 @@
@(groups: List[String],
visibleRepositories: List[gitbucket.core.service.RepositoryService.RepositoryInfo],
recentRepositories: List[gitbucket.core.service.RepositoryService.RepositoryInfo],
enableNewsFeed: Boolean)(implicit context: gitbucket.core.controller.Context)
recentRepositories: List[gitbucket.core.service.RepositoryService.RepositoryInfo])(implicit context: gitbucket.core.controller.Context)
@import gitbucket.core.view.helpers
@gitbucket.core.html.main("Repositories"){
@gitbucket.core.dashboard.html.sidebar(recentRepositories){
@gitbucket.core.dashboard.html.tab(enableNewsFeed, "repos")
@gitbucket.core.dashboard.html.tab("repos")
<div class="container">
<div class="btn-group" id="owner-dropdown">
<button class="dropdown-toggle btn btn-default" data-toggle="dropdown" aria-expanded="false">

View File

@@ -1,18 +1,14 @@
@(enableNewsFeed: Boolean, active: String = "")(implicit context: gitbucket.core.controller.Context)
@if(enableNewsFeed || context.loginAccount.isDefined) {
<ul class="nav nav-tabs" style="margin-bottom: 20px;">
@if(enableNewsFeed) {
<li @if(active == "") {class="active"}><a href="@context.path/">News feed</a></li>
}
@if(context.loginAccount.isDefined) {
<li @if(active == "repos") {class="active"}><a href="@context.path/dashboard/repos">Repositories</a></li>
<li @if(active == "pulls") {class="active"}><a href="@context.path/dashboard/pulls">Pull requests</a></li>
<li @if(active == "issues") {class="active"}><a href="@context.path/dashboard/issues">Issues</a></li>
@gitbucket.core.plugin.PluginRegistry().getDashboardTabs.map { tab =>
@tab(context).map { link =>
<li @if(active == link.id) {class="active"}><a href="@context.path/@link.path">@link.label</a></li>
}
@(active: String = "")(implicit context: gitbucket.core.controller.Context)
<ul class="nav nav-tabs" style="margin-bottom: 20px;">
<li @if(active == ""){ class="active"}><a href="@context.path/">News feed</a></li>
@if(context.loginAccount.isDefined){
<li @if(active == "repos" ){ class="active"}><a href="@context.path/dashboard/repos">Repositories</a></li>
<li @if(active == "pulls" ){ class="active"}><a href="@context.path/dashboard/pulls">Pull requests</a></li>
<li @if(active == "issues"){ class="active"}><a href="@context.path/dashboard/issues">Issues</a></li>
@gitbucket.core.plugin.PluginRegistry().getDashboardTabs.map { tab =>
@tab(context).map { link =>
<li @if(active == link.id){ class="active"}><a href="@context.path/@link.path">@link.label</a></li>
}
}
</ul>
}
}
</ul>

View File

@@ -1,7 +1,6 @@
@(activities: List[gitbucket.core.model.Activity],
recentRepositories: List[gitbucket.core.service.RepositoryService.RepositoryInfo],
showBannerToCreatePersonalAccessToken: Boolean,
enableNewsFeed: Boolean)(implicit context: gitbucket.core.controller.Context)
showBannerToCreatePersonalAccessToken: Boolean)(implicit context: gitbucket.core.controller.Context)
@import gitbucket.core.view.helpers
@gitbucket.core.html.main("GitBucket"){
@gitbucket.core.dashboard.html.sidebar(recentRepositories){
@@ -20,26 +19,12 @@
</a> and use it in place of a password on the <code>git</code> command line.
</div>
}
@gitbucket.core.dashboard.html.tab(enableNewsFeed)
@gitbucket.core.dashboard.html.tab()
<div class="container">
@if(enableNewsFeed) {
<div class="pull-right">
<a href="@context.path/activities.atom"><img src="@helpers.assets("/common/images/feed.png")" alt="activities"></a>
</div>
@gitbucket.core.helper.html.activities(activities)
} else {
<div class="signin-form">
@if(context.settings.basicBehavior.allowAnonymousAccess){
@context.settings.information.map { information =>
<div class="alert alert-info" style="background-color: white; color: #555; border-color: #4183c4; font-size: small; line-height: 120%;">
<button type="button" class="close" data-dismiss="alert">&times;</button>
@Html(information)
</div>
}
}
@gitbucket.core.html.signinform(context.settings)
</div>
}
<div class="pull-right">
<a href="@context.path/activities.atom"><img src="@helpers.assets("/common/images/feed.png")" alt="activities"></a>
</div>
@gitbucket.core.helper.html.activities(activities)
</div>
}
}

View File

@@ -140,8 +140,8 @@
}
</div>
<span id="label-assigned">
@issueAssignees.map { assignee =>
<div>@helpers.avatarLink(assignee.assigneeUserName, 20) @helpers.user(assignee.assigneeUserName, styleClass="username strong small")</div>
@issueAssignees.map { asignee =>
<div>@helpers.avatarLink(asignee.assigneeUserName, 20) @helpers.user(asignee.assigneeUserName, styleClass="username strong small")</div>
}
@if(issueAssignees.isEmpty) {
<span class="muted small">No one assigned</span>
@@ -158,10 +158,10 @@
<div class="pull-right">
@gitbucket.core.model.CustomFieldBehavior(field.fieldType).map { behavior =>
@if(issue.nonEmpty) {
@Html(behavior.fieldHtml(repository, issue.get.issueId, field.fieldId, field.fieldName, field.constraints, value.map(_.value).getOrElse(""), isManageable))
@Html(behavior.fieldHtml(repository, issue.get.issueId, field.fieldId, value.map(_.value).getOrElse(""), isManageable))
}
@if(issue.isEmpty) {
@Html(behavior.createHtml(repository, field.fieldId, field.fieldName, field.constraints))
@Html(behavior.createHtml(repository, field.fieldId))
}
}
</div>

View File

@@ -24,9 +24,9 @@
<a href="@condition.copy(state = "closed").toURL">Closed <span class="badge">@closedCount</span></a>
</li>
</ul>
<form method="GET" action="@helpers.url(repository)/@target" id="search-filter-form" class="form-inline pull-right" autocomplete="off">
<form method="GET" action="@helpers.url(repository)/search" id="search-filter-form" class="form-inline pull-right" autocomplete="off">
<div class="input-group">
<input type="text" class="form-control" name="q" placeholder="Search..." aria-label="Search all issues" value="@condition.toFilterString" style="width: 300px;"/>
<input type="text" class="form-control" name="q" placeholder="Search..." aria-label="Search all issues"/>
<input type="hidden" name="type" value="@target"/>
<span class="input-group-btn">
<button type="submit" id="search-btn" class="btn btn-default" aria-label="Search all issues"><i class="fa fa-search"></i></button>

View File

@@ -46,7 +46,7 @@
</td>
<td class="text-right">
<div class="branch-action">
@if(repository.repository.defaultBranch != branch.name || repository.repository.originUserName.isDefined){
@if(repository.repository.defaultBranch != branch.name){
@branch.mergeInfo.map{ info =>
@prs.map{ case (pull, issue) =>
<a href="@helpers.url(repository)/pull/@issue.issueId" title="@issue.title">#@issue.issueId</a>
@@ -76,14 +76,13 @@
}
@if(hasWritePermission){
<span style="margin-left: 8px;">
@if(prs.exists(!_._2.closed)){
@if(prs.map(!_._2.closed).getOrElse(false)){
<a class="disabled" data-toggle="tooltip" title="You cant delete this branch because it has an open pull request"><i class="octicon octicon-trashcan"></i></a>
} else {
@if(isProtected || repository.repository.defaultBranch == branch.name) {
@if(isProtected){
<a class="disabled" data-toggle="tooltip" title="You cant delete a protected branch."><i class="octicon octicon-trashcan"></i></a>
} else {
<a href="@helpers.url(repository)/delete/@helpers.encodeRefName(branch.name)" class="delete-branch" data-name="@branch.name" @if(info.isMerged) {
data-toggle="tooltip" title="this branch is merged" }><i class="octicon octicon-trashcan @if(info.isMerged) {warning} else {danger}"></i></a>
<a href="@helpers.url(repository)/delete/@helpers.encodeRefName(branch.name)" class="delete-branch" data-name="@branch.name" @if(info.isMerged){ data-toggle="tooltip" title="this branch is merged" }><i class="octicon octicon-trashcan @if(info.isMerged){warning} else {danger}"></i></a>
}
}
</span>
@@ -100,7 +99,7 @@
<script>
$(function(){
$('.delete-branch').click(function(e){
const branchName = $(e.target).closest('a').data('name');
var branchName = $(e.target).closest('a').data('name');
return confirm('Are you sure you want to remove the ' + branchName + ' branch?');
});
$('*[data-toggle=tooltip]').tooltip().css("white-space","nowrap");

View File

@@ -21,12 +21,12 @@
git add README.md
git commit -m "first commit"
git remote add origin <span class="live-clone-url">@repository.httpUrl</span>
git push -u origin @context.settings.defaultBranch
git push -u origin master
}
<h3 style="margin-top: 30px;">Push an existing repository from the command line</h3>
@helpers.pre {
git remote add origin <span class="live-clone-url">@repository.httpUrl</span>
git push -u origin @context.settings.defaultBranch
git push -u origin master
}
<script>
$(function(){
@@ -38,4 +38,4 @@
</script>
}
}
}
}

View File

@@ -7,9 +7,6 @@
</div>
<div class="col-md-4">
@customField.fieldType
@customField.constraints.map { constraints =>
(@constraints)
}
</div>
<div class="col-md-2">
@if(customField.enableForIssues) {

View File

@@ -10,9 +10,7 @@
<option value="double" @if(field.map(_.fieldType == "double").getOrElse(false)){selected}>Double</option>
<option value="string" @if(field.map(_.fieldType == "string").getOrElse(false)){selected}>String</option>
<option value="date" @if(field.map(_.fieldType == "date").getOrElse(false)){selected}>Date</option>
<option value="enum" @if(field.map(_.fieldType == "enum").getOrElse(false)){selected}>Enum</option>
</select>
<input type="text" id="constraints-@fieldId" style="width: 300px; @if(!field.exists(_.fieldType == "enum")){display: none;}" class="form-control input-sm" value="@field.map(_.constraints)" placeholder="Comma-separated enum values">
<label for="enableForIssues-@fieldId" class="normal" style="margin-left: 4px;">
<input type="checkbox" id="enableForIssues-@fieldId" @if(field.map(_.enableForIssues).getOrElse(false)){checked}> Issues
</label>
@@ -32,7 +30,6 @@
$.post('@helpers.url(repository)/settings/issues/fields/@{if(fieldId == "new") "new" else s"$fieldId/edit"}', {
'fieldName' : $('#fieldName-@fieldId').val(),
'fieldType': $('#fieldType-@fieldId option:selected').val(),
'constraints': $('#constraints-@fieldId').val(),
'enableForIssues': $('#enableForIssues-@fieldId').prop('checked'),
'enableForPullRequests': $('#enableForPullRequests-@fieldId').prop('checked')
}, function(data, status){
@@ -64,14 +61,6 @@
$('#field-@fieldId').show();
}
});
$('#fieldType-@fieldId').change(function(){
if($(this).val() == 'enum') {
$('#constraints-@fieldId').show();
} else {
$('#constraints-@fieldId').hide();
}
});
});
</script>
}

View File

@@ -24,16 +24,15 @@ class GitBucketCoreModuleSpec extends AnyFunSuite {
)
}
implicit private val suiteDescription: Description = Description.createSuiteDescription(getClass)
implicit private val suiteDescription = Description.createSuiteDescription(getClass)
Seq("8.0", "5.7").foreach { tag =>
test(s"Migration MySQL $tag", ExternalDBTest) {
val container = new MySQLContainer() {
override val container: org.testcontainers.containers.MySQLContainer[_] =
new org.testcontainers.containers.MySQLContainer(s"mysql:$tag") {
override def getDriverClassName = "org.mariadb.jdbc.Driver"
override def getJdbcUrl: String = super.getJdbcUrl + "?permitMysqlScheme"
}
override val container = new org.testcontainers.containers.MySQLContainer(s"mysql:$tag") {
override def getDriverClassName = "org.mariadb.jdbc.Driver"
override def getJdbcUrl: String = super.getJdbcUrl + "?permitMysqlScheme"
}
// TODO https://jira.mariadb.org/browse/CONJ-663
container.withCommand("mysqld --default-authentication-plugin=mysql_native_password")
}

View File

@@ -9,19 +9,12 @@ import scala.util.Using
import org.kohsuke.github.GHCommitState
import java.io.File
import java.util.logging.{Level, Logger}
/**
* Need to run `sbt package` before running this test.
*/
class ApiIntegrationTest extends AnyFunSuite {
// Suppress warning logs caused by liquibase
private val liquibaseResourceLogger = Logger.getLogger("liquibase.resource")
liquibaseResourceLogger.setLevel(Level.SEVERE)
private val liquibaseParserLogger = Logger.getLogger("liquibase.parser")
liquibaseParserLogger.setLevel(Level.SEVERE)
test("create repository") {
Using.resource(new TestingGitBucketServer(19999)) { server =>
val github = server.client("root", "root")
@@ -36,7 +29,7 @@ class ApiIntegrationTest extends AnyFunSuite {
assert(repository.getName == "test")
assert(repository.getDescription == "test repository")
assert(repository.getDefaultBranch == "main")
assert(repository.getDefaultBranch == "master")
assert(repository.getWatchers == 0)
assert(repository.getWatchersCount == 0)
assert(repository.getForks == 0)
@@ -55,7 +48,7 @@ class ApiIntegrationTest extends AnyFunSuite {
val repository = repositories.get(0)
assert(repository.getName == "test")
assert(repository.getDescription == "test repository")
assert(repository.getDefaultBranch == "main")
assert(repository.getDefaultBranch == "master")
assert(repository.getWatchers == 0)
assert(repository.getWatchersCount == 0)
assert(repository.getForks == 0)
@@ -75,7 +68,7 @@ class ApiIntegrationTest extends AnyFunSuite {
val github = server.client("root", "root")
val repo = github.createRepository("create_status_test").autoInit(true).create()
val sha1 = repo.getBranch("main").getSHA1
val sha1 = repo.getBranch("master").getSHA1
{
val status = repo.getLastCommitStatus(sha1)
@@ -147,10 +140,10 @@ class ApiIntegrationTest extends AnyFunSuite {
// get master ref
{
val ref = repo.getRef("heads/main")
assert(ref.getRef == "refs/heads/main")
val ref = repo.getRef("heads/master")
assert(ref.getRef == "refs/heads/master")
assert(
ref.getUrl.toString == "http://localhost:19999/api/v3/repos/root/create_status_test/git/refs/heads/main"
ref.getUrl.toString == "http://localhost:19999/api/v3/repos/root/create_status_test/git/refs/heads/master"
)
assert(ref.getObject.getType == "commit")
}
@@ -176,7 +169,7 @@ class ApiIntegrationTest extends AnyFunSuite {
val createResult =
repo
.createContent()
.branch("main")
.branch("master")
.content("create")
.message("Create content")
.path("README.md")
@@ -193,7 +186,7 @@ class ApiIntegrationTest extends AnyFunSuite {
val updateResult =
repo
.createContent()
.branch("main")
.branch("master")
.content("update")
.message("Update content")
.path("README.md")
@@ -211,100 +204,4 @@ class ApiIntegrationTest extends AnyFunSuite {
}
}
test("issue labels") {
Using.resource(new TestingGitBucketServer(19999)) { server =>
val github = server.client("root", "root")
val repo = github.createRepository("issue_label_test").autoInit(true).create()
val issue = repo.createIssue("test").create()
// Initial label state
{
val labels = repo.getIssue(issue.getNumber).getLabels
assert(labels.size() == 0)
}
// Add labels
{
issue.addLabels("bug", "duplicate")
val labels = repo.getIssue(issue.getNumber).getLabels
assert(labels.size() == 2)
val i = labels.iterator()
val label1 = i.next()
assert(label1.getName == "bug")
assert(label1.getColor == "fc2929")
assert(label1.getUrl == "http://localhost:19999/api/v3/repos/root/issue_label_test/labels/bug")
val label2 = i.next()
assert(label2.getName == "duplicate")
assert(label2.getColor == "cccccc")
assert(label2.getUrl == "http://localhost:19999/api/v3/repos/root/issue_label_test/labels/duplicate")
}
// Remove a label
{
issue.removeLabel("duplicate")
val labels = repo.getIssue(issue.getNumber).getLabels
assert(labels.size() == 1)
val i = labels.iterator()
val label1 = i.next()
assert(label1.getName == "bug")
assert(label1.getColor == "fc2929")
assert(label1.getUrl == "http://localhost:19999/api/v3/repos/root/issue_label_test/labels/bug")
}
// Replace labels (Cannot test because GHLabel.setLabels() doesn't use the replace endpoint)
// {
// issue.setLabels("enhancement", "invalid", "question")
//
// val labels = repo.getIssue(issue.getNumber).getLabels
// assert(labels.size() == 3)
//
// val i = labels.iterator()
// val label1 = i.next()
// assert(label1.getName == "enhancement")
// assert(label1.getColor == "84b6eb")
// assert(label1.getUrl == "http://localhost:19999/api/v3/repos/root/issue_label_test/labels/enhancement")
//
// val label2 = i.next()
// assert(label2.getName == "invalid")
// assert(label2.getColor == "e6e6e6")
// assert(label2.getUrl == "http://localhost:19999/api/v3/repos/root/issue_label_test/labels/invalid")
//
// val label3 = i.next()
// assert(label3.getName == "question")
// assert(label3.getColor == "cc317c")
// assert(label3.getUrl == "http://localhost:19999/api/v3/repos/root/issue_label_test/labels/question")
// }
}
}
test("Git refs APIs") {
Using.resource(new TestingGitBucketServer(19999)) { server =>
val github = server.client("root", "root")
val repo = github.createRepository("git_refs_test").autoInit(true).create()
val sha1 = repo.getBranch("main").getSHA1
val refs1 = repo.listRefs().toList
assert(refs1.size() == 1)
assert(refs1.get(0).getRef == "refs/heads/main")
assert(refs1.get(0).getObject.getSha == sha1)
val ref = repo.createRef("refs/heads/testref", sha1)
assert(ref.getRef == "refs/heads/testref")
assert(ref.getObject.getSha == sha1)
val refs2 = repo.listRefs().toList
assert(refs2.size() == 2)
assert(refs2.get(0).getRef == "refs/heads/main")
assert(refs2.get(0).getObject.getSha == sha1)
assert(refs2.get(1).getRef == "refs/heads/testref")
assert(refs2.get(1).getObject.getSha == sha1)
}
}
}

View File

@@ -13,7 +13,7 @@ import org.eclipse.jgit.lib.ObjectId
object ApiSpecModels {
implicit val context: JsonFormat.Context = JsonFormat.Context("http://gitbucket.exmple.com", None)
implicit val context = JsonFormat.Context("http://gitbucket.exmple.com", None)
val date1 = {
val d = Calendar.getInstance(TimeZone.getTimeZone("UTC"))
@@ -53,7 +53,7 @@ object ApiSpecModels {
repositoryName = repo1Name.name,
isPrivate = false,
description = Some("This your first repo!"),
defaultBranch = "main",
defaultBranch = "master",
registeredDate = date1,
updatedDate = date1,
lastActivityDate = date1,
@@ -81,7 +81,7 @@ object ApiSpecModels {
pullCount = 1,
forkedCount = 1,
milestoneCount = 1,
branchList = Seq("main", "develop"),
branchList = Seq("master", "develop"),
tags = Seq(
TagInfo(
name = "v1.0",
@@ -147,7 +147,7 @@ object ApiSpecModels {
userName = repo1Name.owner,
repositoryName = repo1Name.name,
issueId = issuePR.issueId,
branch = "main",
branch = "master",
requestUserName = "bear",
requestRepositoryName = repo1Name.name,
requestBranch = "new-topic",
@@ -363,7 +363,7 @@ object ApiSpecModels {
info = ProtectedBranchInfo(
owner = repo1Name.owner,
repository = repo1Name.name,
branch = "main",
branch = "master",
enabled = true,
contexts = Seq("continuous-integration/travis-ci"),
includeAdministrators = true
@@ -384,7 +384,7 @@ object ApiSpecModels {
)
val apiBranch = ApiBranch(
name = "main",
name = "master",
commit = ApiBranchCommit(sha1),
protection = apiBranchProtectionOutput
)(
@@ -392,7 +392,7 @@ object ApiSpecModels {
)
val apiBranchForList = ApiBranchForList(
name = "main",
name = "master",
commit = ApiBranchCommit(sha1)
)
@@ -447,8 +447,8 @@ object ApiSpecModels {
val gitHubContext = JsonFormat.Context("https://api.github.com", Some("https://api.github.com"))
val apiRefHeadsMaster = ApiRef(
ref = "refs/heads/main",
url = ApiPath("/repos/gitbucket/gitbucket/git/refs/heads/main"),
ref = "refs/heads/master",
url = ApiPath("/repos/gitbucket/gitbucket/git/refs/heads/master"),
node_id = "MDM6UmVmOTM1MDc0NjpyZWZzL2hlYWRzL21hc3Rlcg==",
`object` = ApiRefCommit(
sha = "6b2d124d092402f2c2b7131caada05ead9e7de6d",
@@ -508,7 +508,7 @@ object ApiSpecModels {
|"watchers":0,
|"forks":1,
|"private":false,
|"default_branch":"main",
|"default_branch":"master",
|"owner":$jsonUser,
|"has_issues":true,
|"id":0,
@@ -594,7 +594,7 @@ object ApiSpecModels {
|"updated_at":"2011-04-14T16:00:49Z",
|"created_at":"2011-04-14T16:00:49Z",
|"head":{"sha":"6dcb09b5b57875f334f61aebed695e2e4193db5e","ref":"new-topic","repo":$jsonRepository,"label":"new-topic","user":$jsonUser},
|"base":{"sha":"6dcb09b5b57875f334f61aebed695e2e4193db5e","ref":"main","repo":$jsonRepository,"label":"main","user":$jsonUser},
|"base":{"sha":"6dcb09b5b57875f334f61aebed695e2e4193db5e","ref":"master","repo":$jsonRepository,"label":"master","user":$jsonUser},
|"merged":true,
|"merged_at":"2011-04-14T16:00:49Z",
|"merged_by":$jsonUser,
@@ -730,13 +730,13 @@ object ApiSpecModels {
val jsonBranchProtectionOutput =
"""{
|"url":"http://gitbucket.exmple.com/api/v3/repos/octocat/Hello-World/branches/main/protection",
|"url":"http://gitbucket.exmple.com/api/v3/repos/octocat/Hello-World/branches/master/protection",
|"enabled":true,
|"required_status_checks":{
|"url":"http://gitbucket.exmple.com/api/v3/repos/octocat/Hello-World/branches/main/protection/required_status_checks",
|"url":"http://gitbucket.exmple.com/api/v3/repos/octocat/Hello-World/branches/master/protection/required_status_checks",
|"enforcement_level":"everyone",
|"contexts":["continuous-integration/travis-ci"],
|"contexts_url":"http://gitbucket.exmple.com/api/v3/repos/octocat/Hello-World/branches/main/protection/required_status_checks/contexts"}
|"contexts_url":"http://gitbucket.exmple.com/api/v3/repos/octocat/Hello-World/branches/master/protection/required_status_checks/contexts"}
|}""".stripMargin
val jsonBranchProtectionInput =
@@ -749,15 +749,15 @@ object ApiSpecModels {
|}""".stripMargin
val jsonBranch = s"""{
|"name":"main",
|"name":"master",
|"commit":{"sha":"6dcb09b5b57875f334f61aebed695e2e4193db5e"},
|"protection":$jsonBranchProtectionOutput,
|"_links":{
|"self":"http://gitbucket.exmple.com/api/v3/repos/octocat/Hello-World/branches/main",
|"html":"http://gitbucket.exmple.com/octocat/Hello-World/tree/main"}
|"self":"http://gitbucket.exmple.com/api/v3/repos/octocat/Hello-World/branches/master",
|"html":"http://gitbucket.exmple.com/octocat/Hello-World/tree/master"}
|}""".stripMargin
val jsonBranchForList = """{"name":"main","commit":{"sha":"6dcb09b5b57875f334f61aebed695e2e4193db5e"}}"""
val jsonBranchForList = """{"name":"master","commit":{"sha":"6dcb09b5b57875f334f61aebed695e2e4193db5e"}}"""
val jsonContents =
"""{
@@ -800,11 +800,11 @@ object ApiSpecModels {
//I checked all refs in gitbucket repo, and there appears to be only type "commit" and type "tag"
val jsonRef = """{"ref":"refs/heads/featureA","object":{"sha":"6dcb09b5b57875f334f61aebed695e2e4193db5e"}}"""
val jsonRefHeadsMain =
val jsonRefHeadsMaster =
"""{
|"ref": "refs/heads/main",
|"ref": "refs/heads/master",
|"node_id": "MDM6UmVmOTM1MDc0NjpyZWZzL2hlYWRzL21hc3Rlcg==",
|"url": "https://api.github.com/repos/gitbucket/gitbucket/git/refs/heads/main",
|"url": "https://api.github.com/repos/gitbucket/gitbucket/git/refs/heads/master",
|"object": {
|"sha": "6b2d124d092402f2c2b7131caada05ead9e7de6d",
|"type": "commit",

View File

@@ -1,12 +1,11 @@
package gitbucket.core.api
import org.json4s.Formats
import org.json4s.jackson.JsonMethods
import org.scalatest.funsuite.AnyFunSuite
class JsonFormatSpec extends AnyFunSuite {
import ApiSpecModels._
implicit val format: Formats = JsonFormat.jsonFormats
implicit val format = JsonFormat.jsonFormats
private def expected(json: String) = json.replaceAll("\n", "")
def normalizeJson(json: String) = {
@@ -84,7 +83,7 @@ class JsonFormatSpec extends AnyFunSuite {
assert(JsonFormat(apiPusher) == expected(jsonPusher))
}
test("apiRefHead") {
assertEqualJson(JsonFormat(apiRefHeadsMaster)(gitHubContext), jsonRefHeadsMain)
assertEqualJson(JsonFormat(apiRefHeadsMaster)(gitHubContext), jsonRefHeadsMaster)
}
test("apiRefTag") {
assertEqualJson(JsonFormat(apiRefTag)(gitHubContext), jsonRefTag)

View File

@@ -40,7 +40,7 @@ class CommitStatusServiceSpec
test("createCommitState can insert and update") {
withTestDB { implicit session =>
val tester = generateNewAccount(fixture1.creator)
insertRepository(fixture1.repositoryName, fixture1.userName, None, false, "main")
insertRepository(fixture1.repositoryName, fixture1.userName, None, false)
val id = generateFixture1(tester: Account)
assert(
getCommitStatus(fixture1.userName, fixture1.repositoryName, id) == Some(fixture1.copy(commitStatusId = id))
@@ -77,7 +77,7 @@ class CommitStatusServiceSpec
test("getCommitStatus can find by commitId and context") {
withTestDB { implicit session =>
val tester = generateNewAccount(fixture1.creator)
insertRepository(fixture1.repositoryName, fixture1.userName, None, false, "main")
insertRepository(fixture1.repositoryName, fixture1.userName, None, false)
val id = generateFixture1(tester: Account)
assert(
getCommitStatus(fixture1.userName, fixture1.repositoryName, fixture1.commitId, fixture1.context) == Some(
@@ -90,7 +90,7 @@ class CommitStatusServiceSpec
test("getCommitStatus can find by commitStatusId") {
withTestDB { implicit session =>
val tester = generateNewAccount(fixture1.creator)
insertRepository(fixture1.repositoryName, fixture1.userName, None, false, "main")
insertRepository(fixture1.repositoryName, fixture1.userName, None, false)
val id = generateFixture1(tester: Account)
assert(
getCommitStatus(fixture1.userName, fixture1.repositoryName, id) == Some(fixture1.copy(commitStatusId = id))

View File

@@ -7,7 +7,7 @@ class RepositoryServiceSpec extends AnyFunSuite with ServiceSpecBase with Reposi
test("renameRepository can rename CommitState, ProtectedBranches") {
withTestDB { implicit session =>
val tester = generateNewAccount("tester")
insertRepository("repo", "root", None, false, "main")
insertRepository("repo", "root", None, false)
val service = new CommitStatusService with ProtectedBranchService {}
val id = service.createCommitStatus(
userName = "root",

View File

@@ -81,8 +81,7 @@ trait ServiceSpecBase {
),
repositoryViewer = RepositoryViewerSettings(
maxFiles = 0
),
defaultBranch = "main"
)
)
def withTestDB[A](action: (Session) => A): A = {
@@ -128,8 +127,8 @@ trait ServiceSpecBase {
if (dir.exists()) {
FileUtils.deleteQuietly(dir)
}
JGitUtil.initRepository(dir, "main")
dummyService.insertRepository(repositoryName, userName, None, false, "main")
JGitUtil.initRepository(dir)
dummyService.insertRepository(repositoryName, userName, None, false)
ac
}

View File

@@ -26,7 +26,7 @@ class WebHookJsonFormatSpec extends AnyFunSuite {
|"description":"This your first repo!",
|"ref":"v1.0",
|"ref_type":"tag",
|"master_branch":"main",
|"master_branch":"master",
|"repository":$jsonRepository,
|"pusher_type":"user"
|}""".stripMargin
@@ -41,12 +41,12 @@ class WebHookJsonFormatSpec extends AnyFunSuite {
createFile(git, Constants.HEAD, "README.md", "body1", message = "initial")
createFile(git, Constants.HEAD, "README.md", "body1\nbody2", message = "modified")
val branchId = git.getRepository.resolve("main")
val branchId = git.getRepository.resolve("master")
val payload = WebHookPushPayload(
git = git,
sender = account,
refName = "refs/heads/main",
refName = "refs/heads/master",
repositoryInfo = repositoryInfo,
commits = List(commitInfo(branchId.name)),
repositoryOwner = account,
@@ -56,7 +56,7 @@ class WebHookJsonFormatSpec extends AnyFunSuite {
val expected = s"""{
|"pusher":{"name":"octocat","email":"octocat@example.com"},
|"sender":$jsonUser,
|"ref":"refs/heads/main",
|"ref":"refs/heads/master",
|"before":"6dcb09b5b57875f334f61aebed695e2e4193db5e",
|"after":"6dcb09b5b57875f334f61aebed695e2e4193db5e",
|"commits":[${jsonCommit(branchId.name)}],

View File

@@ -34,7 +34,7 @@ object GitSpecUtil {
RepositoryCache.clear()
FileUtils.deleteQuietly(dir)
Files.createDirectories(dir.toPath())
JGitUtil.initRepository(dir, "main")
JGitUtil.initRepository(dir)
dir
}

View File

@@ -23,14 +23,14 @@ class JGitUtilSpec extends AnyFunSuite {
withTestRepository { git =>
createFile(git, Constants.HEAD, "README.md", "body1", message = "commit1")
val branchId = git.getRepository.resolve("main")
val branchId = git.getRepository.resolve("master")
val commit = JGitUtil.getRevCommitFromId(git, branchId)
createFile(git, Constants.HEAD, "LICENSE", "Apache License", message = "commit1")
createFile(git, Constants.HEAD, "README.md", "body1\nbody2", message = "commit1")
// latest commit
val diff1 = JGitUtil.getDiffs(git, None, "main", false, true)
val diff1 = JGitUtil.getDiffs(git, None, "master", false, true)
assert(diff1.size == 1)
assert(diff1(0).changeType == ChangeType.MODIFY)
assert(diff1(0).oldPath == "README.md")
@@ -44,7 +44,7 @@ class JGitUtilSpec extends AnyFunSuite {
|\ No newline at end of file""".stripMargin))
// from specified commit
val diff2 = JGitUtil.getDiffs(git, Some(commit.getName), "main", false, true)
val diff2 = JGitUtil.getDiffs(git, Some(commit.getName), "master", false, true)
assert(diff2.size == 2)
assert(diff2(0).changeType == ChangeType.ADD)
assert(diff2(0).oldPath == "/dev/null")
@@ -73,7 +73,7 @@ class JGitUtilSpec extends AnyFunSuite {
createFile(git, Constants.HEAD, "README.md", "body1", message = "commit1")
// branch name
val branchId = git.getRepository.resolve("main")
val branchId = git.getRepository.resolve("master")
val commit1 = JGitUtil.getRevCommitFromId(git, branchId)
// commit id
@@ -97,19 +97,19 @@ class JGitUtilSpec extends AnyFunSuite {
withTestRepository { git =>
// getCommitCount
createFile(git, Constants.HEAD, "README.md", "body1", message = "commit1")
assert(JGitUtil.getCommitCount(git, "main") == 1)
assert(JGitUtil.getCommitCount(git, "master") == 1)
createFile(git, Constants.HEAD, "README.md", "body2", message = "commit2")
assert(JGitUtil.getCommitCount(git, "main") == 2)
assert(JGitUtil.getCommitCount(git, "master") == 2)
// maximum limit
(3 to 10).foreach { i =>
createFile(git, Constants.HEAD, "README.md", "body" + i, message = "commit" + i)
}
assert(JGitUtil.getCommitCount(git, "main", 5) == 5)
assert(JGitUtil.getCommitCount(git, "master", 5) == 5)
// actual commit count
val gitLog = git.log.add(git.getRepository.resolve("main")).all
val gitLog = git.log.add(git.getRepository.resolve("master")).all
assert(gitLog.call.asScala.toSeq.size == 10)
// getAllCommitIds
@@ -123,22 +123,22 @@ class JGitUtilSpec extends AnyFunSuite {
createFile(git, Constants.HEAD, "README.md", "body1", message = "commit1")
// createBranch
assert(JGitUtil.createBranch(git, "main", "test1") == Right("Branch created."))
assert(JGitUtil.createBranch(git, "main", "test2") == Right("Branch created."))
assert(JGitUtil.createBranch(git, "main", "test2") == Left("Sorry, that branch already exists."))
assert(JGitUtil.createBranch(git, "master", "test1") == Right("Branch created."))
assert(JGitUtil.createBranch(git, "master", "test2") == Right("Branch created."))
assert(JGitUtil.createBranch(git, "master", "test2") == Left("Sorry, that branch already exists."))
// verify
val branches = git.branchList.call()
assert(branches.size == 3)
assert(branches.get(0).getName == "refs/heads/main")
assert(branches.get(0).getName == "refs/heads/master")
assert(branches.get(1).getName == "refs/heads/test1")
assert(branches.get(2).getName == "refs/heads/test2")
// getBranchesOfCommit
val commit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve("main"))
val commit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve("master"))
val branchesOfCommit = JGitUtil.getBranchesOfCommit(git, commit.getName)
assert(branchesOfCommit.size == 3)
assert(branchesOfCommit(0) == "main")
assert(branchesOfCommit(0) == "master")
assert(branchesOfCommit(1) == "test1")
assert(branchesOfCommit(2) == "test2")
}
@@ -147,16 +147,16 @@ class JGitUtilSpec extends AnyFunSuite {
test("getBranches") {
withTestRepository { git =>
createFile(git, Constants.HEAD, "README.md", "body1", message = "commit1")
JGitUtil.createBranch(git, "main", "test1")
JGitUtil.createBranch(git, "master", "test1")
createFile(git, Constants.HEAD, "README.md", "body2", message = "commit2")
JGitUtil.createBranch(git, "main", "test2")
JGitUtil.createBranch(git, "master", "test2")
// getBranches
val branches = JGitUtil.getBranches(git, "main", true)
val branches = JGitUtil.getBranches(git, "master", true)
assert(branches.size == 3)
assert(branches(0).name == "main")
assert(branches(0).name == "master")
assert(branches(0).committerName == "dummy")
assert(branches(0).committerEmailAddress == "dummy@example.com")
@@ -178,17 +178,17 @@ class JGitUtilSpec extends AnyFunSuite {
createFile(git, Constants.HEAD, "README.md", "body1", message = "commit1")
// createTag
assert(JGitUtil.createTag(git, "1.0", Some("test1"), "main") == Right("Tag added."))
assert(JGitUtil.createTag(git, "1.0", Some("test1"), "master") == Right("Tag added."))
assert(
JGitUtil.createTag(git, "1.0", Some("test2"), "main") == Left("Sorry, some Git operation error occurs.")
JGitUtil.createTag(git, "1.0", Some("test2"), "master") == Left("Sorry, some Git operation error occurs.")
)
// record current commit
val commit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve("main"))
val commit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve("master"))
// createTag
createFile(git, Constants.HEAD, "LICENSE", "Apache License", message = "commit2")
assert(JGitUtil.createTag(git, "1.1", Some("test3"), "main") == Right("Tag added."))
assert(JGitUtil.createTag(git, "1.1", Some("test3"), "master") == Right("Tag added."))
// verify
val allTags = git.tagList().call().asScala
@@ -203,7 +203,7 @@ class JGitUtilSpec extends AnyFunSuite {
assert(tagsOfCommit(1) == "1.0")
// getTagsOnCommit
val tagsOnCommit = JGitUtil.getTagsOnCommit(git, "main")
val tagsOnCommit = JGitUtil.getTagsOnCommit(git, "master")
assert(tagsOnCommit.size == 1)
assert(tagsOnCommit(0) == "1.1")
}
@@ -214,7 +214,7 @@ class JGitUtilSpec extends AnyFunSuite {
createFile(git, Constants.HEAD, "README.md", "body1", message = "commit1")
createFile(git, Constants.HEAD, "LICENSE", "Apache License", message = "commit2")
val objectId = git.getRepository.resolve("main")
val objectId = git.getRepository.resolve("master")
val commit = JGitUtil.getRevCommitFromId(git, objectId)
// Since Non-LFS file doesn't need RepositoryInfo give null
@@ -233,7 +233,7 @@ class JGitUtilSpec extends AnyFunSuite {
createFile(git, Constants.HEAD, "README.md", "body1", message = "commit1")
createFile(git, Constants.HEAD, "LARGE_FILE", "body1" * 1000000, message = "commit1")
val objectId = git.getRepository.resolve("main")
val objectId = git.getRepository.resolve("master")
val commit = JGitUtil.getRevCommitFromId(git, objectId)
val content1 = JGitUtil.getContentFromPath(git, commit.getTree, "README.md", true)
@@ -252,7 +252,7 @@ class JGitUtilSpec extends AnyFunSuite {
createFile(git, Constants.HEAD, "README.md", "body1\nbody2\nbody3", message = "commit1")
createFile(git, Constants.HEAD, "README.md", "body0\nbody2\nbody3", message = "commit2")
val blames = JGitUtil.getBlame(git, "main", "README.md").toSeq
val blames = JGitUtil.getBlame(git, "master", "README.md").toSeq
assert(blames.size == 2)
assert(blames(0).message == "commit2")
@@ -266,75 +266,75 @@ class JGitUtilSpec extends AnyFunSuite {
withTestRepository { git =>
def list(branch: String, path: String) =
JGitUtil.getFileList(git, branch, path).map(finfo => (finfo.name, finfo.message, finfo.isDirectory))
assert(list("main", ".") == Nil)
assert(list("main", "dir/subdir") == Nil)
assert(list("master", ".") == Nil)
assert(list("master", "dir/subdir") == Nil)
assert(list("branch", ".") == Nil)
assert(list("branch", "dir/subdir") == Nil)
createFile(git, "main", "README.md", "body1", message = "commit1")
createFile(git, "master", "README.md", "body1", message = "commit1")
assert(list("main", ".") == List(("README.md", "commit1", false)))
assert(list("main", "dir/subdir") == Nil)
assert(list("master", ".") == List(("README.md", "commit1", false)))
assert(list("master", "dir/subdir") == Nil)
assert(list("branch", ".") == Nil)
assert(list("branch", "dir/subdir") == Nil)
createFile(git, "main", "README.md", "body2", message = "commit2")
createFile(git, "master", "README.md", "body2", message = "commit2")
assert(list("main", ".") == List(("README.md", "commit2", false)))
assert(list("main", "dir/subdir") == Nil)
assert(list("master", ".") == List(("README.md", "commit2", false)))
assert(list("master", "dir/subdir") == Nil)
assert(list("branch", ".") == Nil)
assert(list("branch", "dir/subdir") == Nil)
createFile(git, "main", "dir/subdir/File3.md", "body3", message = "commit3")
createFile(git, "master", "dir/subdir/File3.md", "body3", message = "commit3")
assert(list("main", ".") == List(("dir/subdir", "commit3", true), ("README.md", "commit2", false)))
assert(list("main", "dir/subdir") == List(("File3.md", "commit3", false)))
assert(list("master", ".") == List(("dir/subdir", "commit3", true), ("README.md", "commit2", false)))
assert(list("master", "dir/subdir") == List(("File3.md", "commit3", false)))
assert(list("branch", ".") == Nil)
assert(list("branch", "dir/subdir") == Nil)
createFile(git, "main", "dir/subdir/File4.md", "body4", message = "commit4")
createFile(git, "master", "dir/subdir/File4.md", "body4", message = "commit4")
assert(list("main", ".") == List(("dir/subdir", "commit4", true), ("README.md", "commit2", false)))
assert(list("main", "dir/subdir") == List(("File3.md", "commit3", false), ("File4.md", "commit4", false)))
assert(list("master", ".") == List(("dir/subdir", "commit4", true), ("README.md", "commit2", false)))
assert(list("master", "dir/subdir") == List(("File3.md", "commit3", false), ("File4.md", "commit4", false)))
assert(list("branch", ".") == Nil)
assert(list("branch", "dir/subdir") == Nil)
createFile(git, "main", "README5.md", "body5", message = "commit5")
createFile(git, "master", "README5.md", "body5", message = "commit5")
assert(
list("main", ".") == List(
list("master", ".") == List(
("dir/subdir", "commit4", true),
("README.md", "commit2", false),
("README5.md", "commit5", false)
)
)
assert(list("main", "dir/subdir") == List(("File3.md", "commit3", false), ("File4.md", "commit4", false)))
assert(list("master", "dir/subdir") == List(("File3.md", "commit3", false), ("File4.md", "commit4", false)))
assert(list("branch", ".") == Nil)
assert(list("branch", "dir/subdir") == Nil)
createFile(git, "main", "README.md", "body6", message = "commit6")
createFile(git, "master", "README.md", "body6", message = "commit6")
assert(
list("main", ".") == List(
list("master", ".") == List(
("dir/subdir", "commit4", true),
("README.md", "commit6", false),
("README5.md", "commit5", false)
)
)
assert(list("main", "dir/subdir") == List(("File3.md", "commit3", false), ("File4.md", "commit4", false)))
assert(list("master", "dir/subdir") == List(("File3.md", "commit3", false), ("File4.md", "commit4", false)))
assert(list("branch", ".") == Nil)
assert(list("branch", "dir/subdir") == Nil)
git.branchCreate().setName("branch").setStartPoint("main").call()
git.branchCreate().setName("branch").setStartPoint("master").call()
assert(
list("main", ".") == List(
list("master", ".") == List(
("dir/subdir", "commit4", true),
("README.md", "commit6", false),
("README5.md", "commit5", false)
)
)
assert(list("main", "dir/subdir") == List(("File3.md", "commit3", false), ("File4.md", "commit4", false)))
assert(list("master", "dir/subdir") == List(("File3.md", "commit3", false), ("File4.md", "commit4", false)))
assert(
list("branch", ".") == List(
("dir/subdir", "commit4", true),
@@ -347,13 +347,13 @@ class JGitUtilSpec extends AnyFunSuite {
createFile(git, "branch", "dir/subdir/File3.md", "body7", message = "commit7")
assert(
list("main", ".") == List(
list("master", ".") == List(
("dir/subdir", "commit4", true),
("README.md", "commit6", false),
("README5.md", "commit5", false)
)
)
assert(list("main", "dir/subdir") == List(("File3.md", "commit3", false), ("File4.md", "commit4", false)))
assert(list("master", "dir/subdir") == List(("File3.md", "commit3", false), ("File4.md", "commit4", false)))
assert(
list("branch", ".") == List(
("dir/subdir", "commit7", true),
@@ -363,17 +363,17 @@ class JGitUtilSpec extends AnyFunSuite {
)
assert(list("branch", "dir/subdir") == List(("File3.md", "commit7", false), ("File4.md", "commit4", false)))
createFile(git, "main", "dir8/File8.md", "body8", message = "commit8")
createFile(git, "master", "dir8/File8.md", "body8", message = "commit8")
assert(
list("main", ".") == List(
list("master", ".") == List(
("dir/subdir", "commit4", true),
("dir8", "commit8", true),
("README.md", "commit6", false),
("README5.md", "commit5", false)
)
)
assert(list("main", "dir/subdir") == List(("File3.md", "commit3", false), ("File4.md", "commit4", false)))
assert(list("master", "dir/subdir") == List(("File3.md", "commit3", false), ("File4.md", "commit4", false)))
assert(
list("branch", ".") == List(
("dir/subdir", "commit7", true),
@@ -386,14 +386,14 @@ class JGitUtilSpec extends AnyFunSuite {
createFile(git, "branch", "dir/subdir9/File9.md", "body9", message = "commit9")
assert(
list("main", ".") == List(
list("master", ".") == List(
("dir/subdir", "commit4", true),
("dir8", "commit8", true),
("README.md", "commit6", false),
("README5.md", "commit5", false)
)
)
assert(list("main", "dir/subdir") == List(("File3.md", "commit3", false), ("File4.md", "commit4", false)))
assert(list("master", "dir/subdir") == List(("File3.md", "commit3", false), ("File4.md", "commit4", false)))
assert(
list("branch", ".") == List(
("dir", "commit9", true),
@@ -403,17 +403,17 @@ class JGitUtilSpec extends AnyFunSuite {
)
assert(list("branch", "dir/subdir") == List(("File3.md", "commit7", false), ("File4.md", "commit4", false)))
mergeAndCommit(git, "main", "branch", message = "merge10")
mergeAndCommit(git, "master", "branch", message = "merge10")
assert(
list("main", ".") == List(
list("master", ".") == List(
("dir", "commit9", true),
("dir8", "commit8", true),
("README.md", "commit6", false),
("README5.md", "commit5", false)
)
)
assert(list("main", "dir/subdir") == List(("File3.md", "commit7", false), ("File4.md", "commit4", false)))
assert(list("master", "dir/subdir") == List(("File3.md", "commit7", false), ("File4.md", "commit4", false)))
}
}
@@ -421,10 +421,10 @@ class JGitUtilSpec extends AnyFunSuite {
withTestRepository { git =>
def list(branch: String, path: String) =
JGitUtil.getFileList(git, branch, path).map(finfo => (finfo.name, finfo.message, finfo.isDirectory))
createFile(git, "main", "README.md", "body1", message = "commit1")
createFile(git, "master", "README.md", "body1", message = "commit1")
createFile(git, "branch", "test/text2.txt", "body2", message = "commit2")
mergeAndCommit(git, "main", "branch", message = "merge3")
assert(list("main", "test") == List(("text2.txt", "commit2", false)))
mergeAndCommit(git, "master", "branch", message = "merge3")
assert(list("master", "test") == List(("text2.txt", "commit2", false)))
}
}

View File

@@ -193,8 +193,7 @@ class AvatarImageProviderSpec extends AnyFunSpec {
),
repositoryViewer = RepositoryViewerSettings(
maxFiles = 0
),
"main"
)
)
/**

View File

@@ -9,7 +9,7 @@ import org.mockito.Mockito._
class HelpersSpec extends AnyFunSpec {
private implicit val context: Context = mock(classOf[Context])
private implicit val context = mock(classOf[Context])
private val repository = mock(classOf[RepositoryInfo])
import helpers._