Compare commits

..

1 Commits

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

View File

@@ -1,2 +0,0 @@
# update scalafmt
3d5ca44d66c77a46770a65a895c9737c542690f6

1
.github/CODEOWNERS vendored
View File

@@ -2,4 +2,3 @@
build.sbt @xuwei-k
project/* @xuwei-k
.github/workflows/* @xuwei-k

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,11 +8,11 @@ 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@v4
uses: actions/cache@v3
env:
cache-name: cache-sbt-libs
with:
@@ -22,18 +22,18 @@ jobs:
~/.cache/coursier/v1
key: build-${{ env.cache-name }}-${{ hashFiles('build.sbt') }}
- name: Set up JDK
uses: actions/setup-java@v4
uses: actions/setup-java@v3
with:
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
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v3
with:
name: gitbucket-java${{ matrix.java }}-${{ github.sha }}
path: ./target/executable/gitbucket.*

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,24 +1,12 @@
version = "3.8.1"
version = "1.5.1"
project.git = true
maxColumn = 120
docstrings.style = keep
docstrings = JavaDoc
align.tokens = ["%", "%%", {code = "=>", owner = "Case"}]
align.openParenCallSite = false
align.openParenDefnSite = false
continuationIndent.callSite = 2
continuationIndent.defnSite = 2
danglingParentheses.preset = true
runner.dialect = scala213source3
rewrite.trailingCommas.style = keep
fileOverride {
"glob:**/*.sbt" {
runner.dialect = scala212
rewrite.scala3.convertToNewSyntax = false
}
}
rewrite.scala3.convertToNewSyntax = true
runner.dialectOverride.allowSignificantIndentation = false
runner.dialectOverride.allowAsForImportRename = false
runner.dialectOverride.allowStarWildcardImport = false
danglingParentheses = true

View File

@@ -1,32 +1,6 @@
# Changelog
All changes to the project will be documented in this file.
## 4.41.0 - 18 May 2024
- Simplify pull request UI
- Fix layout of branch selector
- Integrate keyword search with filtering in the search box
- Make max files and lines limit in showing diff configurable
- Adjust the default branch automatically when cloning external repo
- Performance improvement for listing branches
- Upgrade internal libraries
## 4.40.0 - 22 Oct 2023
- Configurable default branch name
- Support custom fields of issues and pull requests in search condition
- Create pull request from default branch of forked repositories
- News feed shows activities of all visible repositories
- Drop Java 8 support
- Improve git push performance
## 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

@@ -24,7 +24,7 @@ The current version of GitBucket provides many features such as:
Installation
--------
GitBucket requires **Java 11**. You have to install it, if it is not already installed.
GitBucket requires **Java8**. You have to install it, if it is not already installed.
1. Download the latest **gitbucket.war** from [the releases page](https://github.com/gitbucket/gitbucket/releases) and run it by `java -jar gitbucket.war`.
2. Go to `http://[hostname]:8080/` and log in with ID: **root** / Pass: **root**.
@@ -59,15 +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.41.x
What's New in 4.38.x
-------------
## 4.41.0 - 18 May 2024
- Simplify pull request UI
- Fix layout of branch selector
- Integrate keyword search with filtering in the search box
- Make max files and lines limit in showing diff configurable
- Adjust the default branch automatically when cloning external repo
- Performance improvement for listing branches
- Upgrade internal libraries
## 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.

161
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.41.0"
val ScalatraVersion = "3.0.0"
val JettyVersion = "10.0.21"
val JgitVersion = "6.9.0.202403050737-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,58 +15,71 @@ sourcesInBase := false
organization := Organization
name := Name
version := GitBucketVersion
scalaVersion := "2.13.14"
crossScalaVersions += "3.4.2"
scalaVersion := "2.13.10"
// scalafmtOnCompile := true
coverageExcludedPackages := ".*\\.html\\..*"
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.1.0-M5",
"commons-io" % "commons-io" % "2.16.1",
"io.github.gitbucket" % "solidbase" % "1.1.0",
"io.github.gitbucket" % "markedj" % "1.0.20",
"org.apache.commons" % "commons-compress" % "1.26.1",
"org.apache.commons" % "commons-email" % "1.6.0",
"commons-net" % "commons-net" % "3.10.0",
"org.apache.httpcomponents" % "httpclient" % "4.5.14",
"org.apache.sshd" % "apache-sshd" % "2.12.1" exclude ("org.slf4j", "slf4j-jdk14") exclude ("org.apache.sshd", "sshd-mina") exclude ("org.apache.sshd", "sshd-netty"),
"org.apache.tika" % "tika-core" % "2.9.2",
"com.github.takezoe" %% "blocking-slick" % "0.0.14",
"com.novell.ldap" % "jldap" % "2009-10-07",
"com.h2database" % "h2" % "1.4.199",
"org.mariadb.jdbc" % "mariadb-java-client" % "2.7.12",
"org.postgresql" % "postgresql" % "42.7.3",
"ch.qos.logback" % "logback-classic" % "1.5.6",
"com.zaxxer" % "HikariCP" % "5.1.0" exclude ("org.slf4j", "slf4j-api"),
"com.typesafe" % "config" % "1.4.3",
"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",
"com.github.zafarkhaja" % "java-semver" % "0.10.2",
"com.nimbusds" % "oauth2-oidc-sdk" % "11.12",
"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.12.0" % "test",
"com.dimafeng" %% "testcontainers-scala" % "0.41.3" % "test",
"org.testcontainers" % "mysql" % "1.19.8" % "test",
"org.testcontainers" % "postgresql" % "1.19.8" % "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.321" % "test"
// dependency settings
resolvers ++= Seq(
Classpaths.typesafeReleases,
"sonatype-snapshot" at "https://oss.sonatype.org/content/repositories/snapshots/"
)
libraryDependencies ++= Seq(
"org.eclipse.jgit" % "org.eclipse.jgit.http.server" % JgitVersion,
"org.eclipse.jgit" % "org.eclipse.jgit.archive" % JgitVersion,
"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.17",
"org.apache.commons" % "commons-compress" % "1.21",
"org.apache.commons" % "commons-email" % "1.5",
"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" % "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.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.18",
"com.github.zafarkhaja" % "java-semver" % "0.9.0",
"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" % 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.313" % "test"
)
libraryDependencies ~= {
_.map {
case x if x.name == "twirl-api" =>
x cross CrossVersion.for3Use2_13
case x =>
x
}
}
// Compiler settings
scalacOptions := Seq(
"-deprecation",
@@ -76,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
@@ -109,8 +114,8 @@ assembly / assemblyMergeStrategy := {
// Exclude a war file from published artifacts
signedArtifacts := {
signedArtifacts.value.filterNot { case (_, file) =>
file.getName.endsWith(".war") || file.getName.endsWith(".war.asc")
signedArtifacts.value.filterNot {
case (_, file) => file.getName.endsWith(".war") || file.getName.endsWith(".war.asc")
}
}
@@ -118,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
@@ -153,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
@@ -178,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 _ => ()
}
}
@@ -199,9 +206,10 @@ executableKey := {
"md5" -> "MD5",
"sha1" -> "SHA-1",
"sha256" -> "SHA-256"
).foreach { case (extension, algorithm) =>
val checksumFile = workDir / (warName + "." + extension)
Checksums generate (outputFile, checksumFile, algorithm)
).foreach {
case (extension, algorithm) =>
val checksumFile = workDir / (warName + "." + extension)
Checksums generate (outputFile, checksumFile, algorithm)
}
// done
@@ -278,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

@@ -1,6 +1,6 @@
Debug GitBucket on IntelliJ
========
Add following configuration for allowing remote debugging to `build.sbt`:
Add following configuration for allowing remote debugging to `buils.sbt`:
```scala
javaOptions in Jetty ++= Seq(

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.10.0
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("org.playframework.twirl" % "sbt-twirl" % "2.0.5")
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "2.2.0")
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.12")
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

@@ -76,20 +76,21 @@ object GitBucketCoreModule
import JDBCUtil._
val conn = context.get(Solidbase.CONNECTION).asInstanceOf[Connection]
val list = conn.select("SELECT * FROM ACTIVITY ORDER BY ACTIVITY_ID") { rs =>
Activity(
activityId = UUID.randomUUID().toString,
userName = rs.getString("USER_NAME"),
repositoryName = rs.getString("REPOSITORY_NAME"),
activityUserName = rs.getString("ACTIVITY_USER_NAME"),
activityType = rs.getString("ACTIVITY_TYPE"),
message = rs.getString("MESSAGE"),
additionalInfo = {
val additionalInfo = rs.getString("ADDITIONAL_INFO")
if (rs.wasNull()) None else Some(additionalInfo)
},
activityDate = rs.getTimestamp("ACTIVITY_DATE")
)
val list = conn.select("SELECT * FROM ACTIVITY ORDER BY ACTIVITY_ID") {
rs =>
Activity(
activityId = UUID.randomUUID().toString,
userName = rs.getString("USER_NAME"),
repositoryName = rs.getString("REPOSITORY_NAME"),
activityUserName = rs.getString("ACTIVITY_USER_NAME"),
activityType = rs.getString("ACTIVITY_TYPE"),
message = rs.getString("MESSAGE"),
additionalInfo = {
val additionalInfo = rs.getString("ADDITIONAL_INFO")
if (rs.wasNull()) None else Some(additionalInfo)
},
activityDate = rs.getTimestamp("ACTIVITY_DATE")
)
}
Using.resource(new FileOutputStream(ActivityLog, true)) { out =>
list.foreach { activity =>
@@ -113,9 +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.41.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

@@ -66,17 +66,16 @@ object ApiBranchProtection {
}
}
implicit val enforcementLevelSerializer: CustomSerializer[EnforcementLevel] =
new CustomSerializer[EnforcementLevel](format =>
implicit val enforcementLevelSerializer: CustomSerializer[EnforcementLevel] = new CustomSerializer[EnforcementLevel](
format =>
(
{
case JString("off") => Off
case JString("non_admins") => NonAdmins
case JString("everyone") => Everyone
},
{ case x: EnforcementLevel =>
JString(x.name)
}, {
case x: EnforcementLevel => JString(x.name)
}
)
)
)
}

View File

@@ -14,8 +14,7 @@ case class ApiComment(id: Int, user: ApiUser, body: String, created_at: Date, up
isPullRequest: Boolean
) {
val html_url = ApiPath(
s"/${repositoryName.fullName}/${if (isPullRequest) { "pull" }
else { "issues" }}/${issueId}#comment-${id}"
s"/${repositoryName.fullName}/${if (isPullRequest) { "pull" } else { "issues" }}/${issueId}#comment-${id}"
)
}

View File

@@ -29,7 +29,7 @@ case class ApiCommit(
object ApiCommit {
def apply(git: Git, repositoryName: RepositoryName, commit: CommitInfo): ApiCommit = {
val diffs = JGitUtil.getDiffs(git = git, from = None, to = commit.id, fetchContent = false, makePatch = false)
val diffs = JGitUtil.getDiffs(git, None, commit.id, false, false)
ApiCommit(
id = commit.id,
message = commit.fullMessage,

View File

@@ -69,8 +69,7 @@ object ApiCommits {
}
File(
filename = if (diff.changeType == ChangeType.DELETE) { diff.oldPath }
else { diff.newPath },
filename = if (diff.changeType == ChangeType.DELETE) { diff.oldPath } else { diff.newPath },
additions = additions,
deletions = deletions,
changes = additions + deletions,
@@ -107,9 +106,7 @@ object ApiCommits {
message = commitInfo.shortMessage,
comment_count = commentCount,
tree = Tree(
url = ApiPath(
s"/api/v3/repos/${repositoryName.fullName}/tree/${commitInfo.id}"
), // TODO This endpoint has not been implemented yet.
url = ApiPath(s"/api/v3/repos/${repositoryName.fullName}/tree/${commitInfo.id}"), // TODO This endpoint has not been implemented yet.
sha = commitInfo.id
)
),
@@ -117,9 +114,7 @@ object ApiCommits {
committer = ApiUser(committer),
parents = commitInfo.parents.map { parent =>
Tree(
url = ApiPath(
s"/api/v3/repos/${repositoryName.fullName}/tree/${parent}"
), // TODO This endpoint has not been implemented yet.
url = ApiPath(s"/api/v3/repos/${repositoryName.fullName}/tree/${parent}"), // TODO This endpoint has not been implemented yet.
sha = parent
)
},

View File

@@ -22,15 +22,16 @@ object ApiContents {
ApiContents("dir", fileInfo.name, fileInfo.path, fileInfo.commitId, None, None)(repositoryName)
} else {
content
.map(arr =>
ApiContents(
"file",
fileInfo.name,
fileInfo.path,
fileInfo.id.getName,
Some(Base64.getEncoder.encodeToString(arr)),
Some("base64")
)(repositoryName)
.map(
arr =>
ApiContents(
"file",
fileInfo.name,
fileInfo.path,
fileInfo.id.getName,
Some(Base64.getEncoder.encodeToString(arr)),
Some("base64")
)(repositoryName)
)
.getOrElse(ApiContents("file", fileInfo.name, fileInfo.path, fileInfo.commitId, None, None)(repositoryName))
}

View File

@@ -23,8 +23,7 @@ case class ApiIssue(
val id = 0 // dummy id
val assignee = assignees.headOption
val comments_url = ApiPath(s"/api/v3/repos/${repositoryName.fullName}/issues/${number}/comments")
val html_url = ApiPath(s"/${repositoryName.fullName}/${if (isPullRequest) { "pull" }
else { "issues" }}/${number}")
val html_url = ApiPath(s"/${repositoryName.fullName}/${if (isPullRequest) { "pull" } else { "issues" }}/${number}")
val pull_request = if (isPullRequest) {
Some(
Map(
@@ -55,8 +54,7 @@ object ApiIssue {
assignees = assignees,
labels = labels,
milestone = milestone,
state = if (issue.closed) { "closed" }
else { "open" },
state = if (issue.closed) { "closed" } else { "open" },
body = issue.content.getOrElse(""),
created_at = issue.registeredDate,
updated_at = issue.updatedDate

View File

@@ -27,10 +27,10 @@ case class ApiPullRequest(
val id = 0 // dummy id
val assignee = assignees.headOption
val html_url = ApiPath(s"${base.repo.html_url.path}/pull/${number}")
// val diff_url = ApiPath(s"${base.repo.html_url.path}/pull/${number}.diff")
// val patch_url = ApiPath(s"${base.repo.html_url.path}/pull/${number}.patch")
//val diff_url = ApiPath(s"${base.repo.html_url.path}/pull/${number}.diff")
//val patch_url = ApiPath(s"${base.repo.html_url.path}/pull/${number}.patch")
val url = ApiPath(s"${base.repo.url.path}/pulls/${number}")
// val issue_url = ApiPath(s"${base.repo.url.path}/issues/${number}")
//val issue_url = ApiPath(s"${base.repo.url.path}/issues/${number}")
val commits_url = ApiPath(s"${base.repo.url.path}/pulls/${number}/commits")
val review_comments_url = ApiPath(s"${base.repo.url.path}/pulls/${number}/comments")
val review_comment_url = ApiPath(s"${base.repo.url.path}/pulls/comments/{number}")
@@ -69,8 +69,7 @@ object ApiPullRequest {
)
case class Commit(sha: String, ref: String, repo: ApiRepository)(baseOwner: String) {
val label = if (baseOwner == repo.owner.login) { ref }
else { s"${repo.owner.login}:${ref}" }
val label = if (baseOwner == repo.owner.login) { ref } else { s"${repo.owner.login}:${ref}" }
val user = repo.owner
}

View File

@@ -40,12 +40,12 @@ object ApiRef {
ApiRef(
ref = s"refs/tags/${tagInfo.name}",
url = ApiPath(s"/api/v3/repos/${repositoryName.fullName}/git/refs/tags/${tagInfo.name}"),
// the GH api distinguishes between "releases" and plain git tags
// for "releases", the api returns a reference to the release object (with type `tag`)
// this would be something like s"/api/v3/repos/${repositoryName.fullName}/git/tags/<hash-of-tag>"
// with a hash for the tag, which I do not fully understand
// since this is not yet implemented in GB, we always return a link to the plain `commit` object,
// which GH does for tags that are not annotated
//the GH api distinguishes between "releases" and plain git tags
//for "releases", the api returns a reference to the release object (with type `tag`)
//this would be something like s"/api/v3/repos/${repositoryName.fullName}/git/tags/<hash-of-tag>"
//with a hash for the tag, which I do not fully understand
//since this is not yet implemented in GB, we always return a link to the plain `commit` object,
//which GH does for tags that are not annotated
`object` = ApiRefCommit(
sha = tagInfo.objectId,
url = ApiPath(s"/api/v3/repos/${repositoryName.fullName}/commits/${tagInfo.objectId}"),

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

@@ -24,8 +24,7 @@ object ApiUser {
def apply(user: Account): ApiUser = ApiUser(
login = user.userName,
email = user.mailAddress,
`type` = if (user.isGroupAccount) { "Organization" }
else { "User" },
`type` = if (user.isGroupAccount) { "Organization" } else { "User" },
site_admin = user.isAdmin,
created_at = user.registeredDate
)

View File

@@ -15,12 +15,13 @@ object JsonFormat {
val parserISO = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss'Z'")
val jsonFormats = Serialization.formats(NoTypeHints) + new CustomSerializer[Date](format =>
(
{ case JString(s) =>
Try(Date.from(Instant.parse(s))).getOrElse(throw new MappingException("Can't convert " + s + " to Date"))
},
{ case x: Date => JString(OffsetDateTime.ofInstant(x.toInstant, ZoneId.of("UTC")).format(parserISO)) }
val jsonFormats = Serialization.formats(NoTypeHints) + new CustomSerializer[Date](
format =>
(
{
case JString(s) =>
Try(Date.from(Instant.parse(s))).getOrElse(throw new MappingException("Can't convert " + s + " to Date"))
}, { case x: Date => JString(OffsetDateTime.ofInstant(x.toInstant, ZoneId.of("UTC")).format(parserISO)) }
)
) + FieldSerializer[ApiUser]() +
FieldSerializer[ApiGroup]() +
@@ -47,32 +48,29 @@ object JsonFormat {
ApiBranchProtection.enforcementLevelSerializer
def apiPathSerializer(c: Context) =
new CustomSerializer[ApiPath](_ =>
(
{
new CustomSerializer[ApiPath](
_ =>
({
case JString(s) if s.startsWith(c.baseUrl) => ApiPath(s.substring(c.baseUrl.length))
case JString(s) => throw new MappingException("Can't convert " + s + " to ApiPath")
},
{ case ApiPath(path) =>
JString(c.baseUrl + path)
}
)
}, {
case ApiPath(path) => JString(c.baseUrl + path)
})
)
def sshPathSerializer(c: Context) =
new CustomSerializer[SshPath](_ =>
(
{
new CustomSerializer[SshPath](
_ =>
({
case JString(s) if c.sshUrl.exists(sshUrl => s.startsWith(sshUrl)) =>
SshPath(s.substring(c.sshUrl.get.length))
case JString(s) => throw new MappingException("Can't convert " + s + " to ApiPath")
},
{ case SshPath(path) =>
c.sshUrl.map { sshUrl =>
JString(sshUrl + path)
} getOrElse JNothing
}
)
}, {
case SshPath(path) =>
c.sshUrl.map { sshUrl =>
JString(sshUrl + path)
} getOrElse JNothing
})
)
/**

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(
@@ -215,7 +221,9 @@ trait AccountControllerBase extends AccountManagementControllerBase {
"events" -> accountWebhookEvents,
"ctype" -> label("ctype", text()),
"token" -> optional(trim(label("token", text(maxlength(100)))))
)((url, events, ctype, token) => AccountWebHookForm(url, events, WebHookContentType.valueOf(ctype), token))
)(
(url, events, ctype, token) => AccountWebHookForm(url, events, WebHookContentType.valueOf(ctype), token)
)
/**
* Provides duplication check for web hook url. duplicated from RepositorySettingsController.scala
@@ -260,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
)
@@ -338,21 +346,22 @@ trait AccountControllerBase extends AccountManagementControllerBase {
post("/:userName/_edit", editForm)(oneselfOnly { form =>
val userName = params("userName")
getAccountByUserName(userName).map { account =>
updateAccount(
account.copy(
password = form.password.map(pbkdf2_sha256).getOrElse(account.password),
fullName = form.fullName,
mailAddress = form.mailAddress,
description = form.description,
url = form.url
getAccountByUserName(userName).map {
account =>
updateAccount(
account.copy(
password = form.password.map(pbkdf2_sha256).getOrElse(account.password),
fullName = form.fullName,
mailAddress = form.mailAddress,
description = form.description,
url = form.url
)
)
)
updateImage(userName, form.fileId, form.clearImage)
updateAccountExtraMailAddresses(userName, form.extraMailAddresses.filter(_ != ""))
flash.update("info", "Account information has been updated.")
redirect(s"/${userName}/_edit")
updateImage(userName, form.fileId, form.clearImage)
updateAccountExtraMailAddresses(userName, form.extraMailAddresses.filter(_ != ""))
flash.update("info", "Account information has been updated.")
redirect(s"/${userName}/_edit")
} getOrElse NotFound()
})
@@ -360,11 +369,12 @@ trait AccountControllerBase extends AccountManagementControllerBase {
get("/:userName/_delete")(oneselfOnly {
val userName = params("userName")
getAccountByUserName(userName, true).map { account =>
if (isLastAdministrator(account)) {
flash.update("error", "Account can't be removed because this is last one administrator.")
redirect(s"/${userName}/_edit")
} else {
getAccountByUserName(userName, true).map {
account =>
if (isLastAdministrator(account)) {
flash.update("error", "Account can't be removed because this is last one administrator.")
redirect(s"/${userName}/_edit")
} else {
// // Remove repositories
// getRepositoryNamesOfUser(userName).foreach { repositoryName =>
// deleteRepository(userName, repositoryName)
@@ -372,10 +382,10 @@ trait AccountControllerBase extends AccountManagementControllerBase {
// FileUtils.deleteDirectory(getWikiRepositoryDir(userName, repositoryName))
// FileUtils.deleteDirectory(getTemporaryDir(userName, repositoryName))
// }
suspendAccount(account)
session.invalidate
redirect("/")
}
suspendAccount(account)
session.invalidate
redirect("/")
}
} getOrElse NotFound()
})
@@ -402,7 +412,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
get("/:userName/_gpg")(oneselfOnly {
val userName = params("userName")
getAccountByUserName(userName).map { x =>
// html.ssh(x, getPublicKeys(x.userName))
//html.ssh(x, getPublicKeys(x.userName))
html.gpg(x, getGpgPublicKeys(x.userName))
} getOrElse NotFound()
})
@@ -521,8 +531,9 @@ trait AccountControllerBase extends AccountManagementControllerBase {
get("/:userName/_hooks/edit")(managersOnly {
val userName = params("userName")
getAccountByUserName(userName).flatMap { account =>
getAccountWebHook(userName, params("url")).map { case (webhook, events) =>
html.edithook(webhook, events, account, false)
getAccountWebHook(userName, params("url")).map {
case (webhook, events) =>
html.edithook(webhook, events, account, false)
}
} getOrElse NotFound()
})
@@ -579,10 +590,11 @@ trait AccountControllerBase extends AccountManagementControllerBase {
"url" -> url,
"request" -> Await.result(
reqFuture
.map(req =>
Map(
"headers" -> _headers(req.getAllHeaders),
"payload" -> json
.map(
req =>
Map(
"headers" -> _headers(req.getAllHeaders),
"payload" -> json
)
)
.recover(toErrorMap),
@@ -590,11 +602,12 @@ trait AccountControllerBase extends AccountManagementControllerBase {
),
"response" -> Await.result(
resFuture
.map(res =>
Map(
"status" -> res.getStatusLine(),
"body" -> EntityUtils.toString(res.getEntity()),
"headers" -> _headers(res.getAllHeaders())
.map(
res =>
Map(
"status" -> res.getStatusLine(),
"body" -> EntityUtils.toString(res.getEntity()),
"headers" -> _headers(res.getAllHeaders())
)
)
.recover(toErrorMap),
@@ -781,83 +794,82 @@ trait AccountControllerBase extends AccountManagementControllerBase {
* Create new repository.
*/
post("/new", newRepositoryForm)(usersOnly { form =>
context.withLoginAccount { loginAccount =>
if (context.settings.basicBehavior.repositoryOperation.create || loginAccount.isAdmin) {
LockUtil.lock(s"${form.owner}/${form.name}") {
if (getRepository(form.owner, form.name).isDefined) {
// redirect to the repository if repository already exists
redirect(s"/${form.owner}/${form.name}")
} else if (!canCreateRepository(form.owner, loginAccount)) {
// Permission error
Forbidden()
} else {
// create repository asynchronously
createRepository(
loginAccount,
form.owner,
form.name,
form.description,
form.isPrivate,
form.initOption,
form.sourceUrl,
context.settings.defaultBranch
)
// redirect to the repository
redirect(s"/${form.owner}/${form.name}")
context.withLoginAccount {
loginAccount =>
if (context.settings.basicBehavior.repositoryOperation.create || loginAccount.isAdmin) {
LockUtil.lock(s"${form.owner}/${form.name}") {
if (getRepository(form.owner, form.name).isDefined) {
// redirect to the repository if repository already exists
redirect(s"/${form.owner}/${form.name}")
} else if (!canCreateRepository(form.owner, loginAccount)) {
// Permission error
Forbidden()
} else {
// create repository asynchronously
createRepository(
loginAccount,
form.owner,
form.name,
form.description,
form.isPrivate,
form.initOption,
form.sourceUrl
)
// redirect to the repository
redirect(s"/${form.owner}/${form.name}")
}
}
}
} else Forbidden()
} else Forbidden()
}
})
get("/:owner/:repository/fork")(readableUsersOnly { repository =>
context.withLoginAccount { loginAccount =>
if (
repository.repository.options.allowFork && (context.settings.basicBehavior.repositoryOperation.fork || loginAccount.isAdmin)
) {
val loginUserName = loginAccount.userName
val groups = getGroupsByUserName(loginUserName)
groups match {
case _: List[String] =>
val managerPermissions = groups.map { group =>
val members = getGroupMembers(group)
context.loginAccount.exists(x =>
members.exists { member =>
member.userName == x.userName && member.isManager
}
context.withLoginAccount {
loginAccount =>
if (repository.repository.options.allowFork && (context.settings.basicBehavior.repositoryOperation.fork || loginAccount.isAdmin)) {
val loginUserName = loginAccount.userName
val groups = getGroupsByUserName(loginUserName)
groups match {
case _: List[String] =>
val managerPermissions = groups.map { group =>
val members = getGroupMembers(group)
context.loginAccount.exists(
x =>
members.exists { member =>
member.userName == x.userName && member.isManager
}
)
}
helper.html.forkrepository(
repository,
(groups zip managerPermissions).sortBy(_._1)
)
}
helper.html.forkrepository(
repository,
(groups zip managerPermissions).sortBy(_._1)
)
case _ => redirect(s"/${loginUserName}")
}
} else BadRequest()
case _ => redirect(s"/${loginUserName}")
}
} else BadRequest()
}
})
post("/:owner/:repository/fork", accountForm)(readableUsersOnly { (form, repository) =>
context.withLoginAccount { loginAccount =>
if (
repository.repository.options.allowFork && (context.settings.basicBehavior.repositoryOperation.fork || loginAccount.isAdmin)
) {
val loginUserName = loginAccount.userName
val accountName = form.accountName
context.withLoginAccount {
loginAccount =>
if (repository.repository.options.allowFork && (context.settings.basicBehavior.repositoryOperation.fork || loginAccount.isAdmin)) {
val loginUserName = loginAccount.userName
val accountName = form.accountName
if (getRepository(accountName, repository.name).isDefined) {
// redirect to the repository if repository already exists
redirect(s"/${accountName}/${repository.name}")
} else if (!canCreateRepository(accountName, loginAccount)) {
// Permission error
Forbidden()
} else {
// fork repository asynchronously
forkRepository(accountName, repository, loginUserName)
// redirect to the repository
redirect(s"/${accountName}/${repository.name}")
}
} else Forbidden()
if (getRepository(accountName, repository.name).isDefined) {
// redirect to the repository if repository already exists
redirect(s"/${accountName}/${repository.name}")
} else if (!canCreateRepository(accountName, loginAccount)) {
// Permission error
Forbidden()
} else {
// fork repository asynchronously
forkRepository(accountName, repository, loginUserName)
// redirect to the repository
redirect(s"/${accountName}/${repository.name}")
}
} else Forbidden()
}
})
@@ -884,11 +896,9 @@ trait AccountControllerBase extends AccountManagementControllerBase {
private def members: Constraint = new Constraint() {
override def validate(name: String, value: String, messages: Messages): Option[String] = {
if (
value.split(",").exists {
_.split(":") match { case Array(userName, isManager) => isManager.toBoolean }
}
) None
if (value.split(",").exists {
_.split(":") match { case Array(userName, isManager) => isManager.toBoolean }
}) None
else Some("Must select one manager at least.")
}
}

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()
}
}
}
/**
@@ -423,11 +372,10 @@ trait AccountManagementControllerBase extends ControllerBase {
messages: Messages
): Option[String] = {
val extraMailAddresses = params.view.filterKeys(k => k.startsWith("extraMailAddresses"))
if (
extraMailAddresses.exists { case (k, v) =>
v.contains(value)
}
) {
if (extraMailAddresses.exists {
case (k, v) =>
v.contains(value)
}) {
Some("These mail addresses are duplicated.")
} else {
getAccountByMailAddress(value, true)
@@ -447,11 +395,10 @@ trait AccountManagementControllerBase extends ControllerBase {
messages: Messages
): Option[String] = {
val extraMailAddresses = params.view.filterKeys(k => k.startsWith("extraMailAddresses"))
if (
Some(value) == params.optionValue("mailAddress") || extraMailAddresses.count { case (k, v) =>
v.contains(value)
} > 1
) {
if (Some(value) == params.optionValue("mailAddress") || extraMailAddresses.count {
case (k, v) =>
v.contains(value)
} > 1) {
Some("These mail addresses are duplicated.")
} else {
getAccountByMailAddress(value, true)

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)
}
})
@@ -112,13 +111,13 @@ trait DashboardControllerBase extends ControllerBase {
val condition = getOrCreateCondition(Keys.Session.DashboardIssues, filter, userName)
val userRepos = getUserRepositories(userName, true).map(repo => repo.owner -> repo.name)
val page = IssueSearchCondition.page(request)
val issues = searchIssue(condition, IssueSearchOption.Issues, (page - 1) * IssueLimit, IssueLimit, userRepos*)
val issues = searchIssue(condition, IssueSearchOption.Issues, (page - 1) * IssueLimit, IssueLimit, userRepos: _*)
html.issues(
issues.map(issue => (issue, None)),
page,
countIssue(condition.copy(state = "open"), IssueSearchOption.Issues, userRepos*),
countIssue(condition.copy(state = "closed"), IssueSearchOption.Issues, userRepos*),
countIssue(condition.copy(state = "open"), IssueSearchOption.Issues, userRepos: _*),
countIssue(condition.copy(state = "closed"), IssueSearchOption.Issues, userRepos: _*),
filter match {
case "assigned" => condition.copy(assigned = Some(Some(userName)))
case "mentioned" => condition.copy(mentioned = Some(userName))
@@ -131,8 +130,7 @@ trait DashboardControllerBase extends ControllerBase {
None,
withoutPhysicalInfo = true,
limit = context.settings.basicBehavior.limitVisibleRepositories
),
isNewsFeedEnabled
)
)
}
@@ -149,7 +147,7 @@ trait DashboardControllerBase extends ControllerBase {
IssueSearchOption.PullRequests,
(page - 1) * PullRequestLimit,
PullRequestLimit,
allRepos*
allRepos: _*
)
val status = issues.map { issue =>
issue.commitId.flatMap { commitId =>
@@ -160,8 +158,8 @@ trait DashboardControllerBase extends ControllerBase {
html.pulls(
issues.zip(status),
page,
countIssue(condition.copy(state = "open"), IssueSearchOption.PullRequests, allRepos*),
countIssue(condition.copy(state = "closed"), IssueSearchOption.PullRequests, allRepos*),
countIssue(condition.copy(state = "open"), IssueSearchOption.PullRequests, allRepos: _*),
countIssue(condition.copy(state = "closed"), IssueSearchOption.PullRequests, allRepos: _*),
filter match {
case "assigned" => condition.copy(assigned = Some(Some(userName)))
case "mentioned" => condition.copy(mentioned = Some(userName))
@@ -174,8 +172,7 @@ trait DashboardControllerBase extends ControllerBase {
None,
withoutPhysicalInfo = true,
limit = context.settings.basicBehavior.limitVisibleRepositories
),
isNewsFeedEnabled
)
)
}

View File

@@ -75,57 +75,59 @@ class FileUploadController
post("/wiki/:owner/:repository") {
setMultipartConfig()
// Don't accept not logged-in users
session.get(Keys.Session.LoginAccount).collect { case loginAccount: Account =>
val owner = params("owner")
val repository = params("repository")
session.get(Keys.Session.LoginAccount).collect {
case loginAccount: Account =>
val owner = params("owner")
val repository = params("repository")
// Check whether logged-in user is collaborator
onlyWikiEditable(owner, repository, loginAccount) {
execute(
{ (file, fileId) =>
val fileName = file.getName
LockUtil.lock(s"${owner}/${repository}/wiki") {
Using.resource(Git.open(Directory.getWikiRepositoryDir(owner, repository))) { git =>
val builder = DirCache.newInCore.builder()
val inserter = git.getRepository.newObjectInserter()
val headId = git.getRepository.resolve(Constants.HEAD + "^{commit}")
// Check whether logged-in user is collaborator
onlyWikiEditable(owner, repository, loginAccount) {
execute(
{ (file, fileId) =>
val fileName = file.getName
LockUtil.lock(s"${owner}/${repository}/wiki") {
Using.resource(Git.open(Directory.getWikiRepositoryDir(owner, repository))) {
git =>
val builder = DirCache.newInCore.builder()
val inserter = git.getRepository.newObjectInserter()
val headId = git.getRepository.resolve(Constants.HEAD + "^{commit}")
if (headId != null) {
JGitUtil.processTree(git, headId) { (path, tree) =>
if (path != fileName) {
builder.add(JGitUtil.createDirCacheEntry(path, tree.getEntryFileMode, tree.getEntryObjectId))
if (headId != null) {
JGitUtil.processTree(git, headId) { (path, tree) =>
if (path != fileName) {
builder.add(JGitUtil.createDirCacheEntry(path, tree.getEntryFileMode, tree.getEntryObjectId))
}
}
}
}
val bytes = IOUtils.toByteArray(file.getInputStream)
builder.add(
JGitUtil.createDirCacheEntry(
fileName,
FileMode.REGULAR_FILE,
inserter.insert(Constants.OBJ_BLOB, bytes)
)
)
builder.finish()
val newHeadId = JGitUtil.createNewCommit(
git,
inserter,
headId,
builder.getDirCache.writeTree(inserter),
Constants.HEAD,
loginAccount.fullName,
loginAccount.mailAddress,
s"Uploaded ${fileName}"
)
fileName
}
val bytes = IOUtils.toByteArray(file.getInputStream)
builder.add(
JGitUtil.createDirCacheEntry(
fileName,
FileMode.REGULAR_FILE,
inserter.insert(Constants.OBJ_BLOB, bytes)
)
)
builder.finish()
val newHeadId = JGitUtil.createNewCommit(
git,
inserter,
headId,
builder.getDirCache.writeTree(inserter),
Constants.HEAD,
loginAccount.fullName,
loginAccount.mailAddress,
s"Uploaded ${fileName}"
)
fileName
}
}
},
_ => true
)
}
},
_ => true
)
}
} getOrElse BadRequest()
}
@@ -133,19 +135,20 @@ class FileUploadController
setMultipartConfigForLargeFile()
session
.get(Keys.Session.LoginAccount)
.collect { case _: Account =>
val owner = params("owner")
val repository = params("repository")
val tag = multiParams("splat").head
execute(
{ (file, fileId) =>
FileUtils.writeByteArrayToFile(
new File(getReleaseFilesDir(owner, repository), FileUtil.checkFilename(tag + "/" + fileId)),
file.get()
)
},
_ => true
)
.collect {
case _: Account =>
val owner = params("owner")
val repository = params("repository")
val tag = multiParams("splat").head
execute(
{ (file, fileId) =>
FileUtils.writeByteArrayToFile(
new File(getReleaseFilesDir(owner, repository), FileUtil.checkFilename(tag + "/" + fileId)),
file.get()
)
},
_ => true
)
}
.getOrElse(BadRequest())
}
@@ -155,12 +158,9 @@ class FileUploadController
setMultipartConfig()
session.get(Keys.Session.LoginAccount).collect {
case loginAccount: Account if loginAccount.isAdmin =>
execute(
{ (file, fileId) =>
request2Session(request).conn.importAsSQL(file.getInputStream)
},
_ => true
)
execute({ (file, fileId) =>
request2Session(request).conn.importAsSQL(file.getInputStream)
}, _ => true)
}
redirect("/admin/data")
}

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,10 +135,9 @@ 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))
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.")
@@ -170,18 +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("/")
}
@@ -204,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)) {
@@ -230,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
@@ -242,9 +213,9 @@ trait IndexControllerBase extends ControllerBase {
.map { t =>
Map(
"label" -> s"${avatar(t.userName, 16)}<b>@${StringUtil.escapeHtml(
StringUtil.cutTail(t.userName, 25, "...")
)}</b> ${StringUtil
.escapeHtml(StringUtil.cutTail(t.fullName, 25, "..."))}",
StringUtil.cutTail(t.userName, 25, "...")
)}</b> ${StringUtil
.escapeHtml(StringUtil.cutTail(t.fullName, 25, "..."))}",
"value" -> t.userName
)
}
@@ -263,23 +234,22 @@ 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")
val page =
try {
val i = params.getOrElse("page", "1").toInt
if (i <= 0) 1 else i
} catch {
case _: NumberFormatException => 1
}
val page = try {
val i = params.getOrElse("page", "1").toInt
if (i <= 0) 1 else i
} catch {
case _: NumberFormatException => 1
}
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
@@ -287,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
@@ -323,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,44 +89,36 @@ 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) =>
val condition = IssueSearchCondition(filter)
if (condition.isEmpty) {
// Redirect to keyword search
redirect(s"/${repository.owner}/${repository.name}/search?q=${StringUtil.urlEncode(q)}&type=issues")
} else {
searchIssues(repository, condition, 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)
}
})
get("/:owner/:repository/issues/:id")(referrersOnly { repository =>
val issueId = params("id")
getIssue(repository.owner, repository.name, issueId) map { issue =>
if (issue.isPullRequest) {
redirect(s"/${repository.owner}/${repository.name}/pull/${issueId}")
} else {
html.issue(
issue,
getComments(repository.owner, repository.name, issueId.toInt),
getIssueLabels(repository.owner, repository.name, issueId.toInt),
getIssueAssignees(repository.owner, repository.name, issueId.toInt),
getAssignableUserNames(repository.owner, repository.name),
getMilestonesWithIssueCount(repository.owner, repository.name),
getPriorities(repository.owner, repository.name),
getLabels(repository.owner, repository.name),
getCustomFieldsWithValue(repository.owner, repository.name, issueId.toInt).filter(_._1.enableForIssues),
isIssueEditable(repository),
isIssueManageable(repository),
isIssueCommentManageable(repository),
repository
)
}
getIssue(repository.owner, repository.name, issueId) map {
issue =>
if (issue.isPullRequest) {
redirect(s"/${repository.owner}/${repository.name}/pull/${issueId}")
} else {
html.issue(
issue,
getComments(repository.owner, repository.name, issueId.toInt),
getIssueLabels(repository.owner, repository.name, issueId.toInt),
getIssueAssignees(repository.owner, repository.name, issueId.toInt),
getAssignableUserNames(repository.owner, repository.name),
getMilestonesWithIssueCount(repository.owner, repository.name),
getPriorities(repository.owner, repository.name),
getLabels(repository.owner, repository.name),
getCustomFieldsWithValue(repository.owner, repository.name, issueId.toInt).filter(_._1.enableForIssues),
isIssueEditable(repository),
isIssueManageable(repository),
isIssueCommentManageable(repository),
repository
)
}
} getOrElse NotFound()
})
@@ -147,120 +139,130 @@ trait IssuesControllerBase extends ControllerBase {
})
post("/:owner/:repository/issues/new", issueCreateForm)(readableUsersOnly { (form, repository) =>
context.withLoginAccount { loginAccount =>
if (isIssueEditable(repository)) { // TODO Should this check is provided by authenticator?
val issue = createIssue(
repository,
form.title,
form.content,
form.assigneeUserNames.toSeq.flatMap(_.split(",")),
form.milestoneId,
form.priorityId,
form.labelNames.toSeq.flatMap(_.split(",")),
loginAccount
)
context.withLoginAccount {
loginAccount =>
if (isIssueEditable(repository)) { // TODO Should this check is provided by authenticator?
val issue = createIssue(
repository,
form.title,
form.content,
form.assigneeUserNames.toSeq.flatMap(_.split(",")),
form.milestoneId,
form.priorityId,
form.labelNames.toSeq.flatMap(_.split(",")),
loginAccount
)
// Insert custom field values
params.toMap.foreach { case (key, value) =>
if (key.startsWith("custom-field-")) {
getCustomField(
repository.owner,
repository.name,
key.replaceFirst("^custom-field-", "").toInt
).foreach { field =>
CustomFieldBehavior.validate(field, value, messages) match {
case None =>
insertOrUpdateCustomFieldValue(field, repository.owner, repository.name, issue.issueId, value)
case Some(_) => halt(400)
// Insert custom field values
params.toMap.foreach {
case (key, value) =>
if (key.startsWith("custom-field-")) {
getCustomField(
repository.owner,
repository.name,
key.replaceFirst("^custom-field-", "").toInt
).foreach { field =>
CustomFieldBehavior.validate(field, value, messages) match {
case None =>
insertOrUpdateCustomFieldValue(field, repository.owner, repository.name, issue.issueId, value)
case Some(_) => halt(400)
}
}
}
}
}
}
redirect(s"/${issue.userName}/${issue.repositoryName}/issues/${issue.issueId}")
} else Unauthorized()
redirect(s"/${issue.userName}/${issue.repositoryName}/issues/${issue.issueId}")
} else Unauthorized()
}
})
ajaxPost("/:owner/:repository/issues/edit_title/:id", issueTitleEditForm)(readableUsersOnly { (title, repository) =>
context.withLoginAccount { loginAccount =>
getIssue(repository.owner, repository.name, params("id")).map { issue =>
if (isEditableContent(repository.owner, repository.name, issue.openedUserName, loginAccount)) {
if (issue.title != title) {
// update issue
updateIssue(repository.owner, repository.name, issue.issueId, title, issue.content)
// extract references and create refer comment
createReferComment(repository.owner, repository.name, issue.copy(title = title), title, loginAccount)
createComment(
repository.owner,
repository.name,
loginAccount.userName,
issue.issueId,
issue.title + "\r\n" + title,
"change_title"
)
}
redirect(s"/${repository.owner}/${repository.name}/issues/_data/${issue.issueId}")
} else Unauthorized()
} getOrElse NotFound()
context.withLoginAccount {
loginAccount =>
getIssue(repository.owner, repository.name, params("id")).map {
issue =>
if (isEditableContent(repository.owner, repository.name, issue.openedUserName, loginAccount)) {
if (issue.title != title) {
// update issue
updateIssue(repository.owner, repository.name, issue.issueId, title, issue.content)
// extract references and create refer comment
createReferComment(repository.owner, repository.name, issue.copy(title = title), title, loginAccount)
createComment(
repository.owner,
repository.name,
loginAccount.userName,
issue.issueId,
issue.title + "\r\n" + title,
"change_title"
)
}
redirect(s"/${repository.owner}/${repository.name}/issues/_data/${issue.issueId}")
} else Unauthorized()
} getOrElse NotFound()
}
})
ajaxPost("/:owner/:repository/issues/edit/:id", issueEditForm)(readableUsersOnly { (content, repository) =>
context.withLoginAccount { loginAccount =>
getIssue(repository.owner, repository.name, params("id")).map { issue =>
if (isEditableContent(repository.owner, repository.name, issue.openedUserName, loginAccount)) {
// update issue
updateIssue(repository.owner, repository.name, issue.issueId, issue.title, content)
// extract references and create refer comment
createReferComment(repository.owner, repository.name, issue, content.getOrElse(""), loginAccount)
context.withLoginAccount {
loginAccount =>
getIssue(repository.owner, repository.name, params("id")).map { issue =>
if (isEditableContent(repository.owner, repository.name, issue.openedUserName, loginAccount)) {
// update issue
updateIssue(repository.owner, repository.name, issue.issueId, issue.title, content)
// extract references and create refer comment
createReferComment(repository.owner, repository.name, issue, content.getOrElse(""), loginAccount)
redirect(s"/${repository.owner}/${repository.name}/issues/_data/${issue.issueId}")
} else Unauthorized()
} getOrElse NotFound()
redirect(s"/${repository.owner}/${repository.name}/issues/_data/${issue.issueId}")
} else Unauthorized()
} getOrElse NotFound()
}
})
post("/:owner/:repository/issue_comments/new", commentForm)(readableUsersOnly { (form, repository) =>
context.withLoginAccount { loginAccount =>
getIssue(repository.owner, repository.name, form.issueId.toString).flatMap { issue =>
val actionOpt =
params
.get("action")
.filter(_ => isEditableContent(issue.userName, issue.repositoryName, issue.openedUserName, loginAccount))
handleComment(issue, Some(form.content), repository, actionOpt) map { case (issue, id) =>
redirect(
s"/${repository.owner}/${repository.name}/${if (issue.isPullRequest) "pull" else "issues"}/${form.issueId}#comment-${id}"
)
}
} getOrElse NotFound()
context.withLoginAccount {
loginAccount =>
getIssue(repository.owner, repository.name, form.issueId.toString).flatMap { issue =>
val actionOpt =
params
.get("action")
.filter(_ => isEditableContent(issue.userName, issue.repositoryName, issue.openedUserName, loginAccount))
handleComment(issue, Some(form.content), repository, actionOpt) map {
case (issue, id) =>
redirect(
s"/${repository.owner}/${repository.name}/${if (issue.isPullRequest) "pull" else "issues"}/${form.issueId}#comment-${id}"
)
}
} getOrElse NotFound()
}
})
post("/:owner/:repository/issue_comments/state", issueStateForm)(readableUsersOnly { (form, repository) =>
context.withLoginAccount { loginAccount =>
getIssue(repository.owner, repository.name, form.issueId.toString).flatMap { issue =>
val actionOpt =
params
.get("action")
.filter(_ => isEditableContent(issue.userName, issue.repositoryName, issue.openedUserName, loginAccount))
handleComment(issue, form.content, repository, actionOpt) map { case (issue, id) =>
redirect(
s"/${repository.owner}/${repository.name}/${if (issue.isPullRequest) "pull" else "issues"}/${form.issueId}#comment-${id}"
)
}
} getOrElse NotFound()
context.withLoginAccount {
loginAccount =>
getIssue(repository.owner, repository.name, form.issueId.toString).flatMap { issue =>
val actionOpt =
params
.get("action")
.filter(_ => isEditableContent(issue.userName, issue.repositoryName, issue.openedUserName, loginAccount))
handleComment(issue, form.content, repository, actionOpt) map {
case (issue, id) =>
redirect(
s"/${repository.owner}/${repository.name}/${if (issue.isPullRequest) "pull" else "issues"}/${form.issueId}#comment-${id}"
)
}
} getOrElse NotFound()
}
})
ajaxPost("/:owner/:repository/issue_comments/edit/:id", commentForm)(readableUsersOnly { (form, repository) =>
context.withLoginAccount { loginAccount =>
getComment(repository.owner, repository.name, params("id")).map { comment =>
if (isEditableContent(repository.owner, repository.name, comment.commentedUserName, loginAccount)) {
updateComment(repository.owner, repository.name, comment.issueId, comment.commentId, form.content)
redirect(s"/${repository.owner}/${repository.name}/issue_comments/_data/${comment.commentId}")
} else Unauthorized()
} getOrElse NotFound()
context.withLoginAccount {
loginAccount =>
getComment(repository.owner, repository.name, params("id")).map { comment =>
if (isEditableContent(repository.owner, repository.name, comment.commentedUserName, loginAccount)) {
updateComment(repository.owner, repository.name, comment.issueId, comment.commentId, form.content)
redirect(s"/${repository.owner}/${repository.name}/issue_comments/_data/${comment.commentId}")
} else Unauthorized()
} getOrElse NotFound()
}
})
@@ -275,61 +277,65 @@ trait IssuesControllerBase extends ControllerBase {
})
ajaxGet("/:owner/:repository/issues/_data/:id")(readableUsersOnly { repository =>
context.withLoginAccount { loginAccount =>
getIssue(repository.owner, repository.name, params("id")) map { x =>
if (isEditableContent(x.userName, x.repositoryName, x.openedUserName, loginAccount)) {
params.get("dataType") collect {
case t if t == "html" => html.editissue(x.content, x.issueId, repository)
} getOrElse {
contentType = formats("json")
org.json4s.jackson.Serialization.write(
Map(
"title" -> x.title,
"content" -> Markdown.toHtml(
markdown = x.content getOrElse "No description given.",
repository = repository,
branch = repository.repository.defaultBranch,
enableWikiLink = false,
enableRefsLink = true,
enableAnchor = true,
enableLineBreaks = true,
enableTaskList = true,
hasWritePermission = true
context.withLoginAccount {
loginAccount =>
getIssue(repository.owner, repository.name, params("id")) map {
x =>
if (isEditableContent(x.userName, x.repositoryName, x.openedUserName, loginAccount)) {
params.get("dataType") collect {
case t if t == "html" => html.editissue(x.content, x.issueId, repository)
} getOrElse {
contentType = formats("json")
org.json4s.jackson.Serialization.write(
Map(
"title" -> x.title,
"content" -> Markdown.toHtml(
markdown = x.content getOrElse "No description given.",
repository = repository,
branch = repository.repository.defaultBranch,
enableWikiLink = false,
enableRefsLink = true,
enableAnchor = true,
enableLineBreaks = true,
enableTaskList = true,
hasWritePermission = true
)
)
)
)
)
}
} else Unauthorized()
} getOrElse NotFound()
}
} else Unauthorized()
} getOrElse NotFound()
}
})
ajaxGet("/:owner/:repository/issue_comments/_data/:id")(readableUsersOnly { repository =>
context.withLoginAccount { loginAccount =>
getComment(repository.owner, repository.name, params("id")) map { x =>
if (isEditableContent(x.userName, x.repositoryName, x.commentedUserName, loginAccount)) {
params.get("dataType") collect {
case t if t == "html" => html.editcomment(x.content, x.commentId, repository)
} getOrElse {
contentType = formats("json")
org.json4s.jackson.Serialization.write(
Map(
"content" -> view.Markdown.toHtml(
markdown = x.content,
repository = repository,
branch = repository.repository.defaultBranch,
enableWikiLink = false,
enableRefsLink = true,
enableAnchor = true,
enableLineBreaks = true,
enableTaskList = true,
hasWritePermission = true
context.withLoginAccount {
loginAccount =>
getComment(repository.owner, repository.name, params("id")) map {
x =>
if (isEditableContent(x.userName, x.repositoryName, x.commentedUserName, loginAccount)) {
params.get("dataType") collect {
case t if t == "html" => html.editcomment(x.content, x.commentId, repository)
} getOrElse {
contentType = formats("json")
org.json4s.jackson.Serialization.write(
Map(
"content" -> view.Markdown.toHtml(
markdown = x.content,
repository = repository,
branch = repository.repository.defaultBranch,
enableWikiLink = false,
enableRefsLink = true,
enableAnchor = true,
enableLineBreaks = true,
enableTaskList = true,
hasWritePermission = true
)
)
)
)
)
}
} else Unauthorized()
} getOrElse NotFound()
}
} else Unauthorized()
} getOrElse NotFound()
}
})
@@ -368,8 +374,9 @@ trait IssuesControllerBase extends ControllerBase {
milestoneId("milestoneId").map { milestoneId =>
getMilestonesWithIssueCount(repository.owner, repository.name)
.find(_._1.milestoneId == milestoneId)
.map { case (_, openCount, closeCount) =>
gitbucket.core.issues.milestones.html.progress(openCount + closeCount, closeCount)
.map {
case (_, openCount, closeCount) =>
gitbucket.core.issues.milestones.html.progress(openCount + closeCount, closeCount)
} getOrElse NotFound()
} getOrElse Ok()
})
@@ -450,7 +457,7 @@ trait IssuesControllerBase extends ControllerBase {
post("/:owner/:repository/issues/batchedit/assign")(writableUsersOnly { repository =>
val value = assignedUserName("value")
executeBatch(repository) {
// updateAssignedUserName(repository.owner, repository.name, _, value, true)
//updateAssignedUserName(repository.owner, repository.name, _, value, true)
value match {
case Some(assignedUserName) =>
registerIssueAssignee(repository.owner, repository.name, _, assignedUserName, true)
@@ -500,9 +507,9 @@ trait IssuesControllerBase extends ControllerBase {
.map { t =>
Map(
"label" -> s"""${if (t.isPullRequest) "<i class='octicon octicon-git-pull-request'></i>"
else "<i class='octicon octicon-issue-opened'></i>"}<b> #${StringUtil
.escapeHtml(t.issueId.toString)} ${StringUtil
.escapeHtml(StringUtil.cutTail(t.title, 50, "..."))}</b>""",
else "<i class='octicon octicon-issue-opened'></i>"}<b> #${StringUtil
.escapeHtml(t.issueId.toString)} ${StringUtil
.escapeHtml(StringUtil.cutTail(t.title, 50, "..."))}</b>""",
"value" -> t.issueId.toString
)
}
@@ -524,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(
@@ -555,8 +565,8 @@ trait IssuesControllerBase extends ControllerBase {
/**
* Tests whether an issue or a comment is editable by a logged-in user.
*/
private def isEditableContent(owner: String, repository: String, author: String, loginAccount: Account)(implicit
context: Context
private def isEditableContent(owner: String, repository: String, author: String, loginAccount: Account)(
implicit context: Context
): Boolean = {
hasDeveloperRole(owner, repository, context.loginAccount) || author == loginAccount.userName
}
@@ -564,8 +574,8 @@ trait IssuesControllerBase extends ControllerBase {
/**
* Tests whether an issue comment is deletable by a logged-in user.
*/
private def isDeletableComment(owner: String, repository: String, author: String, loginAccount: Account)(implicit
context: Context
private def isDeletableComment(owner: String, repository: String, author: String, loginAccount: Account)(
implicit context: Context
): Boolean = {
hasOwnerRole(owner, repository, context.loginAccount) || author == loginAccount.userName
}

View File

@@ -30,14 +30,12 @@ trait PreProcessControllerBase extends ControllerBase {
* But if it's not allowed, demands authentication except some paths.
*/
get(!context.settings.basicBehavior.allowAnonymousAccess, context.loginAccount.isEmpty) {
if (
!context.currentPath.startsWith("/assets") && !context.currentPath.startsWith("/signin") &&
!context.currentPath.startsWith("/register") && !context.currentPath.endsWith("/info/refs") &&
!context.currentPath.startsWith("/plugin-assets") &&
!PluginRegistry().getAnonymousAccessiblePaths().exists { path =>
context.currentPath.startsWith(path)
}
) {
if (!context.currentPath.startsWith("/assets") && !context.currentPath.startsWith("/signin") &&
!context.currentPath.startsWith("/register") && !context.currentPath.endsWith("/info/refs") &&
!context.currentPath.startsWith("/plugin-assets") &&
!PluginRegistry().getAnonymousAccessiblePaths().exists { path =>
context.currentPath.startsWith(path)
}) {
Unauthorized()
} else {
pass()

View File

@@ -102,160 +102,157 @@ 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) =>
val condition = IssueSearchCondition(filter)
if (condition.isEmpty) {
// Redirect to keyword search
redirect(s"/${repository.owner}/${repository.name}/search?q=${StringUtil.urlEncode(q)}&type=pulls")
} else {
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)
}
})
get("/:owner/:repository/pull/:id")(referrersOnly { repository =>
params("id").toIntOpt.flatMap { issueId =>
getPullRequest(repository.owner, repository.name, issueId) map { case (issue, pullreq) =>
val (commits, diffs) =
getRequestCompareInfo(
repository.owner,
repository.name,
pullreq.commitIdFrom,
repository.owner,
repository.name,
pullreq.commitIdTo,
context.settings
)
params("id").toIntOpt.flatMap {
issueId =>
getPullRequest(repository.owner, repository.name, issueId) map {
case (issue, pullreq) =>
val (commits, diffs) =
getRequestCompareInfo(
repository.owner,
repository.name,
pullreq.commitIdFrom,
repository.owner,
repository.name,
pullreq.commitIdTo
)
html.conversation(
issue,
pullreq,
commits.flatten,
getPullRequestComments(repository.owner, repository.name, issue.issueId, commits.flatten),
diffs.size,
getIssueLabels(repository.owner, repository.name, issueId),
getIssueAssignees(repository.owner, repository.name, issueId),
getAssignableUserNames(repository.owner, repository.name),
getMilestonesWithIssueCount(repository.owner, repository.name),
getPriorities(repository.owner, repository.name),
getLabels(repository.owner, repository.name),
getCustomFieldsWithValue(repository.owner, repository.name, issueId).filter(_._1.enableForPullRequests),
isEditable(repository),
isManageable(repository),
hasDeveloperRole(pullreq.requestUserName, pullreq.requestRepositoryName, context.loginAccount),
repository,
getRepository(pullreq.requestUserName, pullreq.requestRepositoryName),
flash.iterator.map(f => f._1 -> f._2.toString).toMap
)
}
html.conversation(
issue,
pullreq,
commits.flatten,
getPullRequestComments(repository.owner, repository.name, issue.issueId, commits.flatten),
diffs.size,
getIssueLabels(repository.owner, repository.name, issueId),
getIssueAssignees(repository.owner, repository.name, issueId),
getAssignableUserNames(repository.owner, repository.name),
getMilestonesWithIssueCount(repository.owner, repository.name),
getPriorities(repository.owner, repository.name),
getLabels(repository.owner, repository.name),
getCustomFieldsWithValue(repository.owner, repository.name, issueId).filter(_._1.enableForPullRequests),
isEditable(repository),
isManageable(repository),
hasDeveloperRole(pullreq.requestUserName, pullreq.requestRepositoryName, context.loginAccount),
repository,
getRepository(pullreq.requestUserName, pullreq.requestRepositoryName),
flash.iterator.map(f => f._1 -> f._2.toString).toMap
)
}
} getOrElse NotFound()
})
get("/:owner/:repository/pull/:id/commits")(referrersOnly { repository =>
params("id").toIntOpt.flatMap { issueId =>
getPullRequest(repository.owner, repository.name, issueId) map { case (issue, pullreq) =>
val (commits, diffs) =
getRequestCompareInfo(
repository.owner,
repository.name,
pullreq.commitIdFrom,
repository.owner,
repository.name,
pullreq.commitIdTo,
context.settings
)
params("id").toIntOpt.flatMap {
issueId =>
getPullRequest(repository.owner, repository.name, issueId) map {
case (issue, pullreq) =>
val (commits, diffs) =
getRequestCompareInfo(
repository.owner,
repository.name,
pullreq.commitIdFrom,
repository.owner,
repository.name,
pullreq.commitIdTo
)
val commitsWithStatus = commits.map { day =>
day.map { commit =>
(commit, getCommitStatusWithSummary(repository.owner, repository.name, commit.id))
}
val commitsWithStatus = commits.map { day =>
day.map { commit =>
(commit, getCommitStatusWithSummary(repository.owner, repository.name, commit.id))
}
}
html.commits(
issue,
pullreq,
commitsWithStatus,
getPullRequestComments(repository.owner, repository.name, issue.issueId, commits.flatten),
diffs.size,
isManageable(repository),
repository
)
}
html.commits(
issue,
pullreq,
commitsWithStatus,
getPullRequestComments(repository.owner, repository.name, issue.issueId, commits.flatten),
diffs.size,
isManageable(repository),
repository
)
}
} getOrElse NotFound()
})
get("/:owner/:repository/pull/:id/files")(referrersOnly { repository =>
params("id").toIntOpt.flatMap { issueId =>
getPullRequest(repository.owner, repository.name, issueId) map { case (issue, pullreq) =>
val (commits, diffs) =
getRequestCompareInfo(
repository.owner,
repository.name,
pullreq.commitIdFrom,
repository.owner,
repository.name,
pullreq.commitIdTo,
context.settings
)
params("id").toIntOpt.flatMap {
issueId =>
getPullRequest(repository.owner, repository.name, issueId) map {
case (issue, pullreq) =>
val (commits, diffs) =
getRequestCompareInfo(
repository.owner,
repository.name,
pullreq.commitIdFrom,
repository.owner,
repository.name,
pullreq.commitIdTo
)
html.files(
issue,
pullreq,
diffs,
commits.flatten,
getPullRequestComments(repository.owner, repository.name, issue.issueId, commits.flatten),
isManageable(repository),
repository
)
}
html.files(
issue,
pullreq,
diffs,
commits.flatten,
getPullRequestComments(repository.owner, repository.name, issue.issueId, commits.flatten),
isManageable(repository),
repository
)
}
} getOrElse NotFound()
})
ajaxGet("/:owner/:repository/pull/:id/mergeguide")(referrersOnly { repository =>
params("id").toIntOpt.flatMap { issueId =>
getPullRequest(repository.owner, repository.name, issueId) map { case (issue, pullreq) =>
val conflictMessage = LockUtil.lock(s"${repository.owner}/${repository.name}") {
checkConflict(repository.owner, repository.name, pullreq.branch, issueId)
}
val hasMergePermission = hasDeveloperRole(repository.owner, repository.name, context.loginAccount)
val branchProtection = getProtectedBranchInfo(repository.owner, repository.name, pullreq.branch)
val mergeStatus = PullRequestService.MergeStatus(
conflictMessage = conflictMessage,
commitStatuses = getCommitStatuses(repository.owner, repository.name, pullreq.commitIdTo),
branchProtection = branchProtection,
branchIsOutOfDate =
!JGitUtil.getShaByRef(repository.owner, repository.name, pullreq.branch).contains(pullreq.commitIdFrom),
needStatusCheck = context.loginAccount.forall { u =>
branchProtection.needStatusCheck(u.userName)
},
hasUpdatePermission = hasDeveloperRole(
pullreq.requestUserName,
pullreq.requestRepositoryName,
context.loginAccount
) &&
context.loginAccount.exists { u =>
!getProtectedBranchInfo(
params("id").toIntOpt.flatMap {
issueId =>
getPullRequest(repository.owner, repository.name, issueId) map {
case (issue, pullreq) =>
val conflictMessage = LockUtil.lock(s"${repository.owner}/${repository.name}") {
checkConflict(repository.owner, repository.name, pullreq.branch, issueId)
}
val hasMergePermission = hasDeveloperRole(repository.owner, repository.name, context.loginAccount)
val branchProtection = getProtectedBranchInfo(repository.owner, repository.name, pullreq.branch)
val mergeStatus = PullRequestService.MergeStatus(
conflictMessage = conflictMessage,
commitStatuses = getCommitStatuses(repository.owner, repository.name, pullreq.commitIdTo),
branchProtection = branchProtection,
branchIsOutOfDate = JGitUtil.getShaByRef(repository.owner, repository.name, pullreq.branch) != Some(
pullreq.commitIdFrom
),
needStatusCheck = context.loginAccount.forall { u =>
branchProtection.needStatusCheck(u.userName)
},
hasUpdatePermission = hasDeveloperRole(
pullreq.requestUserName,
pullreq.requestRepositoryName,
pullreq.requestBranch
).needStatusCheck(u.userName)
},
hasMergePermission = hasMergePermission,
commitIdTo = pullreq.commitIdTo
)
html.mergeguide(
mergeStatus,
issue,
pullreq,
repository,
getRepository(pullreq.requestUserName, pullreq.requestRepositoryName).get
)
}
context.loginAccount
) &&
context.loginAccount.exists { u =>
!getProtectedBranchInfo(
pullreq.requestUserName,
pullreq.requestRepositoryName,
pullreq.requestBranch
).needStatusCheck(u.userName)
},
hasMergePermission = hasMergePermission,
commitIdTo = pullreq.commitIdTo
)
html.mergeguide(
mergeStatus,
issue,
pullreq,
repository,
getRepository(pullreq.requestUserName, pullreq.requestRepositoryName).get
)
}
} getOrElse NotFound()
})
@@ -314,9 +311,7 @@ trait PullRequestsControllerBase extends ControllerBase {
} else {
LockUtil.lock(s"${owner}/${name}") {
val alias =
if (
pullreq.repositoryName == pullreq.requestRepositoryName && pullreq.userName == pullreq.requestUserName
) {
if (pullreq.repositoryName == pullreq.requestRepositoryName && pullreq.userName == pullreq.requestUserName) {
pullreq.branch
} else {
s"${pullreq.userName}:${pullreq.branch}"
@@ -385,27 +380,29 @@ trait PullRequestsControllerBase extends ControllerBase {
val headBranch = params.get("head")
(forkedRepository.repository.originUserName, forkedRepository.repository.originRepositoryName) match {
case (Some(originUserName), Some(originRepositoryName)) =>
getRepository(originUserName, originRepositoryName).map { originRepository =>
Using.resources(
Git.open(getRepositoryDir(originUserName, originRepositoryName)),
Git.open(getRepositoryDir(forkedRepository.owner, forkedRepository.name))
) { (oldGit, newGit) =>
val newBranch = headBranch.getOrElse(JGitUtil.getDefaultBranch(newGit, forkedRepository).get._2)
val oldBranch = originRepository.branchList
.find(_ == newBranch)
.getOrElse(JGitUtil.getDefaultBranch(oldGit, originRepository).get._2)
getRepository(originUserName, originRepositoryName).map {
originRepository =>
Using.resources(
Git.open(getRepositoryDir(originUserName, originRepositoryName)),
Git.open(getRepositoryDir(forkedRepository.owner, forkedRepository.name))
) { (oldGit, newGit) =>
val newBranch = headBranch.getOrElse(JGitUtil.getDefaultBranch(newGit, forkedRepository).get._2)
val oldBranch = originRepository.branchList
.find(_ == newBranch)
.getOrElse(JGitUtil.getDefaultBranch(oldGit, originRepository).get._2)
redirect(
s"/${forkedRepository.owner}/${forkedRepository.name}/compare/${originUserName}:${oldBranch}...${newBranch}"
)
}
redirect(
s"/${forkedRepository.owner}/${forkedRepository.name}/compare/${originUserName}:${oldBranch}...${newBranch}"
)
}
} getOrElse NotFound()
case _ =>
Using.resource(Git.open(getRepositoryDir(forkedRepository.owner, forkedRepository.name))) { git =>
JGitUtil.getDefaultBranch(git, forkedRepository).map { case (_, defaultBranch) =>
redirect(
s"/${forkedRepository.owner}/${forkedRepository.name}/compare/${defaultBranch}...${headBranch.getOrElse(defaultBranch)}"
)
JGitUtil.getDefaultBranch(git, forkedRepository).map {
case (_, defaultBranch) =>
redirect(
s"/${forkedRepository.owner}/${forkedRepository.name}/compare/${defaultBranch}...${headBranch.getOrElse(defaultBranch)}"
)
} getOrElse {
redirect(s"/${forkedRepository.owner}/${forkedRepository.name}")
}
@@ -445,10 +442,8 @@ trait PullRequestsControllerBase extends ControllerBase {
val (originOwner, originId) = parseCompareIdentifier(origin, forkedRepository.owner)
val (forkedOwner, forkedId) = parseCompareIdentifier(forked, forkedRepository.owner)
(for (
originRepositoryName <- getOriginRepositoryName(originOwner, forkedOwner, forkedRepository);
originRepository <- getRepository(originOwner, originRepositoryName)
) yield {
(for (originRepositoryName <- getOriginRepositoryName(originOwner, forkedOwner, forkedRepository);
originRepository <- getRepository(originOwner, originRepositoryName)) yield {
val (oldId, newId) =
getPullRequestCommitFromTo(originRepository, forkedRepository, originId, forkedId)
@@ -460,8 +455,7 @@ trait PullRequestsControllerBase extends ControllerBase {
oldId.getName,
forkedRepository.owner,
forkedRepository.name,
newId.getName,
context.settings
newId.getName
)
val title = if (commits.flatten.length == 1) {
@@ -569,88 +563,87 @@ trait PullRequestsControllerBase extends ControllerBase {
val (originOwner, tmpOriginBranch) = parseCompareIdentifier(origin, forkedRepository.owner)
val (forkedOwner, tmpForkedBranch) = parseCompareIdentifier(forked, forkedRepository.owner)
(for (
originRepositoryName <-
if (originOwner == forkedOwner) {
Some(forkedRepository.name)
} else {
forkedRepository.repository.originRepositoryName.orElse {
getForkedRepositories(forkedRepository.owner, forkedRepository.name)
.find(_.userName == originOwner)
.map(_.repositoryName)
}
};
originRepository <- getRepository(originOwner, originRepositoryName)
) yield {
(for (originRepositoryName <- if (originOwner == forkedOwner) {
Some(forkedRepository.name)
} else {
forkedRepository.repository.originRepositoryName.orElse {
getForkedRepositories(forkedRepository.owner, forkedRepository.name)
.find(_.userName == originOwner)
.map(_.repositoryName)
}
};
originRepository <- getRepository(originOwner, originRepositoryName)) yield {
Using.resources(
Git.open(getRepositoryDir(originRepository.owner, originRepository.name)),
Git.open(getRepositoryDir(forkedRepository.owner, forkedRepository.name))
) { case (oldGit, newGit) =>
val originBranch = JGitUtil.getDefaultBranch(oldGit, originRepository, tmpOriginBranch).get._2
val forkedBranch = JGitUtil.getDefaultBranch(newGit, forkedRepository, tmpForkedBranch).get._2
val conflict = LockUtil.lock(s"${originRepository.owner}/${originRepository.name}") {
checkConflict(
originRepository.owner,
originRepository.name,
originBranch,
forkedRepository.owner,
forkedRepository.name,
forkedBranch
)
}
html.mergecheck(conflict.isDefined)
) {
case (oldGit, newGit) =>
val originBranch = JGitUtil.getDefaultBranch(oldGit, originRepository, tmpOriginBranch).get._2
val forkedBranch = JGitUtil.getDefaultBranch(newGit, forkedRepository, tmpForkedBranch).get._2
val conflict = LockUtil.lock(s"${originRepository.owner}/${originRepository.name}") {
checkConflict(
originRepository.owner,
originRepository.name,
originBranch,
forkedRepository.owner,
forkedRepository.name,
forkedBranch
)
}
html.mergecheck(conflict.isDefined)
}
}) getOrElse NotFound()
})
post("/:owner/:repository/pulls/new", pullRequestForm)(readableUsersOnly { (form, repository) =>
context.withLoginAccount { loginAccount =>
val manageable = isManageable(repository)
context.withLoginAccount {
loginAccount =>
val manageable = isManageable(repository)
val issueId = insertIssue(
owner = repository.owner,
repository = repository.name,
loginUser = loginAccount.userName,
title = form.title,
content = form.content,
milestoneId = if (manageable) form.milestoneId else None,
priorityId = if (manageable) form.priorityId else None,
isPullRequest = true
)
val issueId = insertIssue(
owner = repository.owner,
repository = repository.name,
loginUser = loginAccount.userName,
title = form.title,
content = form.content,
milestoneId = if (manageable) form.milestoneId else None,
priorityId = if (manageable) form.priorityId else None,
isPullRequest = true
)
createPullRequest(
originRepository = repository,
issueId = issueId,
originBranch = form.targetBranch,
requestUserName = form.requestUserName,
requestRepositoryName = form.requestRepositoryName,
requestBranch = form.requestBranch,
commitIdFrom = form.commitIdFrom,
commitIdTo = form.commitIdTo,
isDraft = form.isDraft,
loginAccount = loginAccount,
settings = context.settings
)
createPullRequest(
originRepository = repository,
issueId = issueId,
originBranch = form.targetBranch,
requestUserName = form.requestUserName,
requestRepositoryName = form.requestRepositoryName,
requestBranch = form.requestBranch,
commitIdFrom = form.commitIdFrom,
commitIdTo = form.commitIdTo,
isDraft = form.isDraft,
loginAccount = loginAccount,
settings = context.settings
)
if (manageable) {
// insert assignees
form.assigneeUserNames.foreach { value =>
value.split(",").foreach { userName =>
registerIssueAssignee(repository.owner, repository.name, issueId, userName)
if (manageable) {
// insert assignees
form.assigneeUserNames.foreach { value =>
value.split(",").foreach { userName =>
registerIssueAssignee(repository.owner, repository.name, issueId, userName)
}
}
}
// insert labels
form.labelNames.foreach { value =>
val labels = getLabels(repository.owner, repository.name)
value.split(",").foreach { labelName =>
labels.find(_.labelName == labelName).map { label =>
registerIssueLabel(repository.owner, repository.name, issueId, label.labelId)
// insert labels
form.labelNames.foreach { value =>
val labels = getLabels(repository.owner, repository.name)
value.split(",").foreach { labelName =>
labels.find(_.labelName == labelName).map { label =>
registerIssueLabel(repository.owner, repository.name, issueId, label.labelId)
}
}
}
}
}
redirect(s"/${repository.owner}/${repository.name}/pull/${issueId}")
redirect(s"/${repository.owner}/${repository.name}/pull/${issueId}")
}
})
@@ -660,23 +653,24 @@ trait PullRequestsControllerBase extends ControllerBase {
context.loginAccount.map(x => Seq(x.mailAddress) ++ getAccountExtraMailAddresses(x.userName)).getOrElse(Nil)
val branches =
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
JGitUtil
.getBranches(
git = git,
defaultBranch = repository.repository.defaultBranch,
origin = repository.repository.originUserName.isEmpty
)
.filter { x =>
x.mergeInfo.map(_.ahead).getOrElse(0) > 0 && x.mergeInfo.map(_.behind).getOrElse(0) == 0 &&
x.commitTime.getTime > thresholdTime &&
mailAddresses.contains(x.committerEmailAddress)
}
.sortBy { br =>
(br.mergeInfo.isEmpty, br.commitTime)
}
.map(_.name)
.reverse
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) {
git =>
JGitUtil
.getBranches(
git = git,
defaultBranch = repository.repository.defaultBranch,
origin = repository.repository.originUserName.isEmpty
)
.filter { x =>
x.mergeInfo.map(_.ahead).getOrElse(0) > 0 && x.mergeInfo.map(_.behind).getOrElse(0) == 0 &&
x.commitTime.getTime > thresholdTime &&
mailAddresses.contains(x.committerEmailAddress)
}
.sortBy { br =>
(br.mergeInfo.isEmpty, br.commitTime)
}
.map(_.name)
.reverse
}
val targetRepository = (for {
@@ -696,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

@@ -106,31 +106,33 @@ trait ReleaseControllerBase extends ControllerBase {
})
post("/:owner/:repository/releases/*/create", releaseForm)(writableUsersOnly { (form, repository) =>
context.withLoginAccount { loginAccount =>
val tagName = multiParams("splat").head
context.withLoginAccount {
loginAccount =>
val tagName = multiParams("splat").head
// Insert into RELEASE
createRelease(repository.owner, repository.name, form.name, form.content, tagName, loginAccount)
// Insert into RELEASE
createRelease(repository.owner, repository.name, form.name, form.content, tagName, loginAccount)
// Insert into RELEASE_ASSET
val files = params.toMap.collect {
case (name, value) if name.startsWith("file:") =>
val Array(_, fileId) = name.split(":")
(fileId, value)
}
files.foreach { case (fileId, fileName) =>
val size =
new File(
getReleaseFilesDir(repository.owner, repository.name),
FileUtil.checkFilename(tagName + "/" + fileId)
).length
createReleaseAsset(repository.owner, repository.name, tagName, fileId, fileName, size, loginAccount)
}
// Insert into RELEASE_ASSET
val files = params.toMap.collect {
case (name, value) if name.startsWith("file:") =>
val Array(_, fileId) = name.split(":")
(fileId, value)
}
files.foreach {
case (fileId, fileName) =>
val size =
new File(
getReleaseFilesDir(repository.owner, repository.name),
FileUtil.checkFilename(tagName + "/" + fileId)
).length
createReleaseAsset(repository.owner, repository.name, tagName, fileId, fileName, size, loginAccount)
}
val releaseInfo = ReleaseInfo(repository.owner, repository.name, loginAccount.userName, form.name, tagName)
recordActivity(releaseInfo)
val releaseInfo = ReleaseInfo(repository.owner, repository.name, loginAccount.userName, form.name, tagName)
recordActivity(releaseInfo)
redirect(s"/${repository.owner}/${repository.name}/releases/${tagName}")
redirect(s"/${repository.owner}/${repository.name}/releases/${tagName}")
}
})
@@ -169,45 +171,48 @@ trait ReleaseControllerBase extends ControllerBase {
})
post("/:owner/:repository/releases/*/edit", releaseForm)(writableUsersOnly { (form, repository) =>
context.withLoginAccount { loginAccount =>
val tagName = multiParams("splat").head
context.withLoginAccount {
loginAccount =>
val tagName = multiParams("splat").head
getRelease(repository.owner, repository.name, tagName)
.map { release =>
// Update RELEASE
updateRelease(repository.owner, repository.name, tagName, form.name, form.content)
getRelease(repository.owner, repository.name, tagName)
.map {
release =>
// Update RELEASE
updateRelease(repository.owner, repository.name, tagName, form.name, form.content)
// Delete and Insert RELEASE_ASSET
val assets = getReleaseAssets(repository.owner, repository.name, tagName)
deleteReleaseAssets(repository.owner, repository.name, tagName)
// Delete and Insert RELEASE_ASSET
val assets = getReleaseAssets(repository.owner, repository.name, tagName)
deleteReleaseAssets(repository.owner, repository.name, tagName)
val files = params.toMap.collect {
case (name, value) if name.startsWith("file:") =>
val Array(_, fileId) = name.split(":")
(fileId, value)
val files = params.toMap.collect {
case (name, value) if name.startsWith("file:") =>
val Array(_, fileId) = name.split(":")
(fileId, value)
}
files.foreach {
case (fileId, fileName) =>
val size =
new File(
getReleaseFilesDir(repository.owner, repository.name),
FileUtil.checkFilename(tagName + "/" + fileId)
).length
createReleaseAsset(repository.owner, repository.name, tagName, fileId, fileName, size, loginAccount)
}
assets.foreach { asset =>
if (!files.exists { case (fileId, _) => fileId == asset.fileName }) {
val file = new File(
getReleaseFilesDir(repository.owner, repository.name),
FileUtil.checkFilename(release.tag + "/" + asset.fileName)
)
FileUtils.forceDelete(file)
}
}
redirect(s"/${release.userName}/${release.repositoryName}/releases/${tagName}")
}
files.foreach { case (fileId, fileName) =>
val size =
new File(
getReleaseFilesDir(repository.owner, repository.name),
FileUtil.checkFilename(tagName + "/" + fileId)
).length
createReleaseAsset(repository.owner, repository.name, tagName, fileId, fileName, size, loginAccount)
}
assets.foreach { asset =>
if (!files.exists { case (fileId, _) => fileId == asset.fileName }) {
val file = new File(
getReleaseFilesDir(repository.owner, repository.name),
FileUtil.checkFilename(release.tag + "/" + asset.fileName)
)
FileUtils.forceDelete(file)
}
}
redirect(s"/${release.userName}/${release.repositoryName}/releases/${tagName}")
}
.getOrElse(NotFound())
.getOrElse(NotFound())
}
})
@@ -232,12 +237,9 @@ trait ReleaseControllerBase extends ControllerBase {
val assets = getReleaseAssetsMap(repository.owner, repository.name, releases)
val tagsWithReleases = tagsToDisplay.map { tag =>
(
tag,
releases.find(_.tag == tag.name).map { release =>
(release, assets(release))
}
)
(tag, releases.find(_.tag == tag.name).map { release =>
(release, assets(release))
})
}
tagsWithReleases
}

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)
@@ -286,90 +284,93 @@ trait RepositorySettingsControllerBase extends ControllerBase {
Array(h.getName, h.getValue)
}
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
import scala.concurrent.duration._
import scala.concurrent._
import scala.jdk.CollectionConverters._
import scala.util.control.NonFatal
import org.apache.http.util.EntityUtils
import scala.concurrent.ExecutionContext.Implicits.global
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) {
git =>
import scala.concurrent.duration._
import scala.concurrent._
import scala.jdk.CollectionConverters._
import scala.util.control.NonFatal
import org.apache.http.util.EntityUtils
import scala.concurrent.ExecutionContext.Implicits.global
val url = params("url")
val token = Some(params("token"))
val ctype = WebHookContentType.valueOf(params("ctype"))
val dummyWebHookInfo = RepositoryWebHook(
userName = repository.owner,
repositoryName = repository.name,
url = url,
ctype = ctype,
token = token
)
val dummyPayload = {
val ownerAccount = getAccountByUserName(repository.owner).get
val commits =
if (JGitUtil.isEmpty(git)) List.empty
else
git.log
.add(git.getRepository.resolve(repository.repository.defaultBranch))
.setMaxCount(4)
.call
.iterator
.asScala
.map(new CommitInfo(_))
.toList
val pushedCommit = commits.drop(1)
WebHookPushPayload(
git = git,
sender = ownerAccount,
refName = "refs/heads/" + repository.repository.defaultBranch,
repositoryInfo = repository,
commits = pushedCommit,
repositoryOwner = ownerAccount,
oldId = commits.lastOption.map(_.id).map(ObjectId.fromString).getOrElse(ObjectId.zeroId()),
newId = commits.headOption.map(_.id).map(ObjectId.fromString).getOrElse(ObjectId.zeroId())
val url = params("url")
val token = Some(params("token"))
val ctype = WebHookContentType.valueOf(params("ctype"))
val dummyWebHookInfo = RepositoryWebHook(
userName = repository.owner,
repositoryName = repository.name,
url = url,
ctype = ctype,
token = token
)
}
val dummyPayload = {
val ownerAccount = getAccountByUserName(repository.owner).get
val commits =
if (JGitUtil.isEmpty(git)) List.empty
else
git.log
.add(git.getRepository.resolve(repository.repository.defaultBranch))
.setMaxCount(4)
.call
.iterator
.asScala
.map(new CommitInfo(_))
.toList
val pushedCommit = commits.drop(1)
val (webHook, json, reqFuture, resFuture) =
callWebHook(WebHook.Push, List(dummyWebHookInfo), dummyPayload, context.settings).head
WebHookPushPayload(
git = git,
sender = ownerAccount,
refName = "refs/heads/" + repository.repository.defaultBranch,
repositoryInfo = repository,
commits = pushedCommit,
repositoryOwner = ownerAccount,
oldId = commits.lastOption.map(_.id).map(ObjectId.fromString).getOrElse(ObjectId.zeroId()),
newId = commits.headOption.map(_.id).map(ObjectId.fromString).getOrElse(ObjectId.zeroId())
)
}
val toErrorMap: PartialFunction[Throwable, Map[String, String]] = {
case e: java.net.UnknownHostException => Map("error" -> ("Unknown host " + e.getMessage))
case e: java.lang.IllegalArgumentException => Map("error" -> ("invalid url"))
case e: org.apache.http.client.ClientProtocolException => Map("error" -> ("invalid url"))
case NonFatal(e) => Map("error" -> (s"${e.getClass} ${e.getMessage}"))
}
val (webHook, json, reqFuture, resFuture) =
callWebHook(WebHook.Push, List(dummyWebHookInfo), dummyPayload, context.settings).head
contentType = formats("json")
org.json4s.jackson.Serialization.write(
Map(
"url" -> url,
"request" -> Await.result(
reqFuture
.map(req =>
Map(
"headers" -> _headers(req.getAllHeaders),
"payload" -> json
val toErrorMap: PartialFunction[Throwable, Map[String, String]] = {
case e: java.net.UnknownHostException => Map("error" -> ("Unknown host " + e.getMessage))
case e: java.lang.IllegalArgumentException => Map("error" -> ("invalid url"))
case e: org.apache.http.client.ClientProtocolException => Map("error" -> ("invalid url"))
case NonFatal(e) => Map("error" -> (s"${e.getClass} ${e.getMessage}"))
}
contentType = formats("json")
org.json4s.jackson.Serialization.write(
Map(
"url" -> url,
"request" -> Await.result(
reqFuture
.map(
req =>
Map(
"headers" -> _headers(req.getAllHeaders),
"payload" -> json
)
)
)
.recover(toErrorMap),
20 seconds
),
"response" -> Await.result(
resFuture
.map(res =>
Map(
"status" -> res.getStatusLine.getStatusCode,
"body" -> EntityUtils.toString(res.getEntity()),
"headers" -> _headers(res.getAllHeaders())
.recover(toErrorMap),
20 seconds
),
"response" -> Await.result(
resFuture
.map(
res =>
Map(
"status" -> res.getStatusLine.getStatusCode,
"body" -> EntityUtils.toString(res.getEntity()),
"headers" -> _headers(res.getAllHeaders())
)
)
)
.recover(toErrorMap),
20 seconds
.recover(toErrorMap),
20 seconds
)
)
)
)
}
})
@@ -377,8 +378,9 @@ trait RepositorySettingsControllerBase extends ControllerBase {
* Display the web hook edit page.
*/
get("/:owner/:repository/settings/hooks/edit")(ownerOnly { repository =>
getWebHook(repository.owner, repository.name, params("url")).map { case (webhook, events) =>
html.edithook(webhook, events, repository, false)
getWebHook(repository.owner, repository.name, params("url")).map {
case (webhook, events) =>
html.edithook(webhook, events, repository, false)
} getOrElse NotFound()
})
@@ -402,22 +404,23 @@ trait RepositorySettingsControllerBase extends ControllerBase {
* Rename repository.
*/
post("/:owner/:repository/settings/rename", renameForm)(ownerOnly { (form, repository) =>
context.withLoginAccount { loginAccount =>
if (context.settings.basicBehavior.repositoryOperation.rename || loginAccount.isAdmin) {
if (repository.name != form.repositoryName) {
// Update database and move git repository
renameRepository(repository.owner, repository.name, repository.owner, form.repositoryName)
// Record activity log
val renameInfo = RenameRepositoryInfo(
repository.owner,
form.repositoryName,
loginAccount.userName,
repository.name
)
recordActivity(renameInfo)
}
redirect(s"/${repository.owner}/${form.repositoryName}")
} else Forbidden()
context.withLoginAccount {
loginAccount =>
if (context.settings.basicBehavior.repositoryOperation.rename || loginAccount.isAdmin) {
if (repository.name != form.repositoryName) {
// Update database and move git repository
renameRepository(repository.owner, repository.name, repository.owner, form.repositoryName)
// Record activity log
val renameInfo = RenameRepositoryInfo(
repository.owner,
form.repositoryName,
loginAccount.userName,
repository.name
)
recordActivity(renameInfo)
}
redirect(s"/${repository.owner}/${form.repositoryName}")
} else Forbidden()
}
})
@@ -425,23 +428,24 @@ trait RepositorySettingsControllerBase extends ControllerBase {
* Transfer repository ownership.
*/
post("/:owner/:repository/settings/transfer", transferForm)(ownerOnly { (form, repository) =>
context.withLoginAccount { loginAccount =>
if (context.settings.basicBehavior.repositoryOperation.transfer || loginAccount.isAdmin) {
// Change repository owner
if (repository.owner != form.newOwner) {
// Update database and move git repository
renameRepository(repository.owner, repository.name, form.newOwner, repository.name)
// Record activity log
val renameInfo = RenameRepositoryInfo(
form.newOwner,
repository.name,
loginAccount.userName,
repository.owner
)
recordActivity(renameInfo)
}
redirect(s"/${form.newOwner}/${repository.name}")
} else Forbidden()
context.withLoginAccount {
loginAccount =>
if (context.settings.basicBehavior.repositoryOperation.transfer || loginAccount.isAdmin) {
// Change repository owner
if (repository.owner != form.newOwner) {
// Update database and move git repository
renameRepository(repository.owner, repository.name, form.newOwner, repository.name)
// Record activity log
val renameInfo = RenameRepositoryInfo(
form.newOwner,
repository.name,
loginAccount.userName,
repository.owner
)
recordActivity(renameInfo)
}
redirect(s"/${form.newOwner}/${repository.name}")
} else Forbidden()
}
})
@@ -507,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
)
@@ -530,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

@@ -55,14 +55,16 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
"bindAddress" -> mapping(
"host" -> trim(label("Bind SSH host", optional(text()))),
"port" -> trim(label("Bind SSH port", optional(number()))),
)((hostOption, portOption) =>
hostOption.map(h => SshAddress(h, portOption.getOrElse(DefaultSshPort), GenericSshUser))
)(
(hostOption, portOption) =>
hostOption.map(h => SshAddress(h, portOption.getOrElse(DefaultSshPort), GenericSshUser))
),
"publicAddress" -> mapping(
"host" -> trim(label("Public SSH host", optional(text()))),
"port" -> trim(label("Public SSH port", optional(number()))),
)((hostOption, portOption) =>
hostOption.map(h => SshAddress(h, portOption.getOrElse(PublicSshPort), GenericSshUser))
)(
(hostOption, portOption) =>
hostOption.map(h => SshAddress(h, portOption.getOrElse(PublicSshPort), GenericSshUser))
),
)(Ssh.apply),
"useSMTP" -> trim(label("SMTP", boolean())),
@@ -121,11 +123,8 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
"largeTimeout" -> trim(label("Timeout for large file", long(required)))
)(Upload.apply),
"repositoryViewer" -> mapping(
"maxFiles" -> trim(label("Max files", number(required))),
"maxDiffFiles" -> trim(label("Max diff files", number(required))),
"maxDiffLines" -> trim(label("Max diff lines", number(required)))
)(RepositoryViewerSettings.apply),
"defaultBranch" -> trim(label("Default branch", text(required)))
"maxFiles" -> trim(label("Max files", number(required)))
)(RepositoryViewerSettings.apply)
)(SystemSettings.apply).verifying { settings =>
Vector(
if (settings.ssh.enabled && settings.baseUrl.isEmpty) {
@@ -251,27 +250,28 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
val conn = request2Session(request).conn
val meta = conn.getMetaData
val tables = ListBuffer[Table]()
Using.resource(meta.getTables(null, "%", "%", Array("TABLE", "VIEW"))) { rs =>
while (rs.next()) {
val tableName = rs.getString("TABLE_NAME")
Using.resource(meta.getTables(null, "%", "%", Array("TABLE", "VIEW"))) {
rs =>
while (rs.next()) {
val tableName = rs.getString("TABLE_NAME")
val pkColumns = ListBuffer[String]()
Using.resource(meta.getPrimaryKeys(null, null, tableName)) { rs =>
while (rs.next()) {
pkColumns += rs.getString("COLUMN_NAME").toUpperCase
val pkColumns = ListBuffer[String]()
Using.resource(meta.getPrimaryKeys(null, null, tableName)) { rs =>
while (rs.next()) {
pkColumns += rs.getString("COLUMN_NAME").toUpperCase
}
}
}
val columns = ListBuffer[Column]()
Using.resource(meta.getColumns(null, "%", tableName, "%")) { rs =>
while (rs.next()) {
val columnName = rs.getString("COLUMN_NAME").toUpperCase
columns += Column(columnName, pkColumns.contains(columnName))
val columns = ListBuffer[Column]()
Using.resource(meta.getColumns(null, "%", tableName, "%")) { rs =>
while (rs.next()) {
val columnName = rs.getString("COLUMN_NAME").toUpperCase
columns += Column(columnName, pkColumns.contains(columnName))
}
}
}
tables += Table(tableName.toUpperCase, columns.toSeq)
}
tables += Table(tableName.toUpperCase, columns.toSeq)
}
}
html.dbviewer(tables.toSeq)
})
@@ -284,26 +284,28 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
if (trimmedQuery.nonEmpty) {
try {
val conn = request2Session(request).conn
Using.resource(conn.prepareStatement(query)) { stmt =>
if (trimmedQuery.toUpperCase.startsWith("SELECT")) {
Using.resource(stmt.executeQuery()) { rs =>
val meta = rs.getMetaData
val columns = for (i <- 1 to meta.getColumnCount) yield {
meta.getColumnName(i)
Using.resource(conn.prepareStatement(query)) {
stmt =>
if (trimmedQuery.toUpperCase.startsWith("SELECT")) {
Using.resource(stmt.executeQuery()) {
rs =>
val meta = rs.getMetaData
val columns = for (i <- 1 to meta.getColumnCount) yield {
meta.getColumnName(i)
}
val result = ListBuffer[Map[String, String]]()
while (rs.next()) {
val row = columns.map { columnName =>
columnName -> Option(rs.getObject(columnName)).map(_.toString).getOrElse("<NULL>")
}.toMap
result += row
}
Ok(Serialization.write(Map("type" -> "query", "columns" -> columns, "rows" -> result)))
}
val result = ListBuffer[Map[String, String]]()
while (rs.next()) {
val row = columns.map { columnName =>
columnName -> Option(rs.getObject(columnName)).map(_.toString).getOrElse("<NULL>")
}.toMap
result += row
}
Ok(Serialization.write(Map("type" -> "query", "columns" -> columns, "rows" -> result)))
} else {
val rows = stmt.executeUpdate()
Ok(Serialization.write(Map("type" -> "update", "rows" -> rows)))
}
} else {
val rows = stmt.executeUpdate()
Ok(Serialization.write(Map("type" -> "update", "rows" -> rows)))
}
}
} catch {
case e: Exception =>
@@ -320,9 +322,7 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
post("/admin/system", form)(adminOnly { form =>
saveSystemSettings(form)
if (
form.ssh.bindAddress != context.settings.sshBindAddress || form.ssh.publicAddress != context.settings.sshPublicAddress
) {
if (form.ssh.bindAddress != context.settings.sshBindAddress || form.ssh.publicAddress != context.settings.sshPublicAddress) {
SshServer.stop()
for {
bindAddress <- form.ssh.bindAddress
@@ -419,43 +419,44 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
post("/admin/users/:name/_edituser", editUserForm)(adminOnly { form =>
val userName = params("userName")
getAccountByUserName(userName, true).map { account =>
if (account.isAdmin && (form.isRemoved || !form.isAdmin) && isLastAdministrator(account)) {
flash.update("error", "Account can't be turned off because this is last one administrator.")
redirect(s"/admin/users/${userName}/_edituser")
} else {
if (form.isRemoved) {
// Remove repositories
// getRepositoryNamesOfUser(userName).foreach { repositoryName =>
// deleteRepository(userName, repositoryName)
// FileUtils.deleteDirectory(getRepositoryDir(userName, repositoryName))
// FileUtils.deleteDirectory(getWikiRepositoryDir(userName, repositoryName))
// FileUtils.deleteDirectory(getTemporaryDir(userName, repositoryName))
// }
// Remove from GROUP_MEMBER and COLLABORATOR
removeUserRelatedData(userName)
}
getAccountByUserName(userName, true).map {
account =>
if (account.isAdmin && (form.isRemoved || !form.isAdmin) && isLastAdministrator(account)) {
flash.update("error", "Account can't be turned off because this is last one administrator.")
redirect(s"/admin/users/${userName}/_edituser")
} else {
if (form.isRemoved) {
// Remove repositories
// getRepositoryNamesOfUser(userName).foreach { repositoryName =>
// deleteRepository(userName, repositoryName)
// FileUtils.deleteDirectory(getRepositoryDir(userName, repositoryName))
// FileUtils.deleteDirectory(getWikiRepositoryDir(userName, repositoryName))
// FileUtils.deleteDirectory(getTemporaryDir(userName, repositoryName))
// }
// Remove from GROUP_MEMBER and COLLABORATOR
removeUserRelatedData(userName)
}
updateAccount(
account.copy(
password = form.password.map(pbkdf2_sha256).getOrElse(account.password),
fullName = form.fullName,
mailAddress = form.mailAddress,
isAdmin = form.isAdmin,
description = form.description,
url = form.url,
isRemoved = form.isRemoved
updateAccount(
account.copy(
password = form.password.map(pbkdf2_sha256).getOrElse(account.password),
fullName = form.fullName,
mailAddress = form.mailAddress,
isAdmin = form.isAdmin,
description = form.description,
url = form.url,
isRemoved = form.isRemoved
)
)
)
updateImage(userName, form.fileId, form.clearImage)
updateAccountExtraMailAddresses(userName, form.extraMailAddresses.filter(_ != ""))
updateImage(userName, form.fileId, form.clearImage)
updateAccountExtraMailAddresses(userName, form.extraMailAddresses.filter(_ != ""))
// call hooks
if (form.isRemoved) PluginRegistry().getAccountHooks.foreach(_.deleted(userName))
// call hooks
if (form.isRemoved) PluginRegistry().getAccountHooks.foreach(_.deleted(userName))
redirect("/admin/users")
}
redirect("/admin/users")
}
} getOrElse NotFound()
})
@@ -496,12 +497,13 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
}
.toList
getAccountByUserName(groupName, true).map { account =>
updateGroup(groupName, form.description, form.url, form.isRemoved)
getAccountByUserName(groupName, true).map {
account =>
updateGroup(groupName, form.description, form.url, form.isRemoved)
if (form.isRemoved) {
// Remove from GROUP_MEMBER
updateGroupMembers(form.groupName, Nil)
if (form.isRemoved) {
// Remove from GROUP_MEMBER
updateGroupMembers(form.groupName, Nil)
// // Remove repositories
// getRepositoryNamesOfUser(form.groupName).foreach { repositoryName =>
// deleteRepository(groupName, repositoryName)
@@ -509,9 +511,9 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
// FileUtils.deleteDirectory(getWikiRepositoryDir(groupName, repositoryName))
// FileUtils.deleteDirectory(getTemporaryDir(groupName, repositoryName))
// }
} else {
// Update GROUP_MEMBER
updateGroupMembers(form.groupName, members)
} else {
// Update GROUP_MEMBER
updateGroupMembers(form.groupName, members)
// // Update COLLABORATOR for group repositories
// getRepositoryNamesOfUser(form.groupName).foreach { repositoryName =>
// removeCollaborators(form.groupName, repositoryName)
@@ -519,10 +521,10 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
// addCollaborator(form.groupName, repositoryName, userName)
// }
// }
}
}
updateImage(form.groupName, form.fileId, form.clearImage)
redirect("/admin/users")
updateImage(form.groupName, form.fileId, form.clearImage)
redirect("/admin/users")
} getOrElse NotFound()
})
@@ -549,7 +551,7 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
})
private def multiLineText(constraints: Constraint*): SingleValueType[Seq[String]] =
new SingleValueType[Seq[String]](constraints*) {
new SingleValueType[Seq[String]](constraints: _*) {
def convert(value: String, messages: Messages): Seq[String] = {
if (value == null) {
Nil
@@ -562,11 +564,9 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
private def members: Constraint =
new Constraint() {
override def validate(name: String, value: String, messages: Messages): Option[String] = {
if (
value.split(",").exists {
_.split(":") match { case Array(userName, isManager) => isManager.toBoolean }
}
) None
if (value.split(",").exists {
_.split(":") match { case Array(userName, isManager) => isManager.toBoolean }
}) None
else Some("Must select one manager at least.")
}
}

View File

@@ -84,8 +84,9 @@ trait ValidationSupport extends FormSupport { self: ServletBase with JacksonJson
* Converts errors to JSON.
*/
private def toJson(errors: Seq[(String, String)]): JObject =
JObject(errors.map { case (key, value) =>
JField(key, JString(value))
JObject(errors.map {
case (key, value) =>
JField(key, JString(value))
}.toList)
}

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("\\.\\.\\.")
@@ -121,17 +111,7 @@ trait WikiControllerBase extends ControllerBase {
Some(pageName),
from,
to,
JGitUtil
.getDiffs(
git = git,
from = Some(from),
to = to,
fetchContent = true,
makePatch = false,
maxFiles = context.settings.repositoryViewer.maxDiffFiles,
maxLines = context.settings.repositoryViewer.maxDiffLines
)
.filter(_.newPath == pageName + ".md"),
JGitUtil.getDiffs(git, Some(from), to, true, false).filter(_.newPath == pageName + ".md"),
repository,
isEditable(repository),
flash.get("info")
@@ -147,15 +127,7 @@ trait WikiControllerBase extends ControllerBase {
None,
from,
to,
JGitUtil.getDiffs(
git = git,
from = Some(from),
to = to,
fetchContent = true,
makePatch = false,
maxFiles = context.settings.repositoryViewer.maxDiffFiles,
maxLines = context.settings.repositoryViewer.maxDiffLines
),
JGitUtil.getDiffs(git, Some(from), to, true, false),
repository,
isEditable(repository),
flash.get("info")
@@ -164,78 +136,78 @@ trait WikiControllerBase extends ControllerBase {
})
get("/:owner/:repository/wiki/:page/_revert/:commitId")(readableUsersOnly { repository =>
context.withLoginAccount { loginAccount =>
if (isEditable(repository)) {
val pageName = StringUtil.urlDecode(params("page"))
val Array(from, to) = params("commitId").split("\\.\\.\\.")
val branch = getWikiBranch(repository.owner, repository.name)
context.withLoginAccount {
loginAccount =>
if (isEditable(repository)) {
val pageName = StringUtil.urlDecode(params("page"))
val Array(from, to) = params("commitId").split("\\.\\.\\.")
if (revertWikiPage(repository.owner, repository.name, from, to, loginAccount, Some(pageName), branch)) {
redirect(s"/${repository.owner}/${repository.name}/wiki/${StringUtil.urlEncode(pageName)}")
} else {
flash.update("info", "This patch was not able to be reversed.")
redirect(
s"/${repository.owner}/${repository.name}/wiki/${StringUtil.urlEncode(pageName)}/_compare/${from}...${to}"
)
}
} else Unauthorized()
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.")
redirect(
s"/${repository.owner}/${repository.name}/wiki/${StringUtil.urlEncode(pageName)}/_compare/${from}...${to}"
)
}
} else Unauthorized()
}
})
get("/:owner/:repository/wiki/_revert/:commitId")(readableUsersOnly { repository =>
context.withLoginAccount { loginAccount =>
if (isEditable(repository)) {
val Array(from, to) = params("commitId").split("\\.\\.\\.")
val branch = getWikiBranch(repository.owner, repository.name)
context.withLoginAccount {
loginAccount =>
if (isEditable(repository)) {
val Array(from, to) = params("commitId").split("\\.\\.\\.")
if (revertWikiPage(repository.owner, repository.name, from, to, loginAccount, None, branch)) {
redirect(s"/${repository.owner}/${repository.name}/wiki")
} else {
flash.update("info", "This patch was not able to be reversed.")
redirect(s"/${repository.owner}/${repository.name}/wiki/_compare/${from}...${to}")
}
} else Unauthorized()
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.")
redirect(s"/${repository.owner}/${repository.name}/wiki/_compare/${from}...${to}")
}
} else Unauthorized()
}
})
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()
})
post("/:owner/:repository/wiki/_edit", editForm)(readableUsersOnly { (form, repository) =>
context.withLoginAccount { loginAccount =>
if (isEditable(repository)) {
saveWikiPage(
repository.owner,
repository.name,
form.currentPageName,
form.pageName,
appendNewLine(convertLineSeparator(form.content, "LF"), "LF"),
loginAccount,
form.message.getOrElse(""),
Some(form.id)
).foreach { commitId =>
updateLastActivityDate(repository.owner, repository.name)
val wikiEditInfo =
EditWikiPageInfo(repository.owner, repository.name, loginAccount.userName, form.pageName, commitId)
recordActivity(wikiEditInfo)
callWebHookOf(repository.owner, repository.name, WebHook.Gollum, context.settings) {
getAccountByUserName(repository.owner).map { repositoryUser =>
WebHookGollumPayload("edited", form.pageName, commitId, repository, repositoryUser, loginAccount)
}
context.withLoginAccount {
loginAccount =>
if (isEditable(repository)) {
saveWikiPage(
repository.owner,
repository.name,
form.currentPageName,
form.pageName,
appendNewLine(convertLineSeparator(form.content, "LF"), "LF"),
loginAccount,
form.message.getOrElse(""),
Some(form.id)
).foreach {
commitId =>
updateLastActivityDate(repository.owner, repository.name)
val wikiEditInfo =
EditWikiPageInfo(repository.owner, repository.name, loginAccount.userName, form.pageName, commitId)
recordActivity(wikiEditInfo)
callWebHookOf(repository.owner, repository.name, WebHook.Gollum, context.settings) {
getAccountByUserName(repository.owner).map { repositoryUser =>
WebHookGollumPayload("edited", form.pageName, commitId, repository, repositoryUser, loginAccount)
}
}
}
}
if (notReservedPageName(form.pageName)) {
redirect(s"/${repository.owner}/${repository.name}/wiki/${StringUtil.urlEncode(form.pageName)}")
} else {
redirect(s"/${repository.owner}/${repository.name}/wiki")
}
} else Unauthorized()
if (notReservedPageName(form.pageName)) {
redirect(s"/${repository.owner}/${repository.name}/wiki/${StringUtil.urlEncode(form.pageName)}")
} else {
redirect(s"/${repository.owner}/${repository.name}/wiki")
}
} else Unauthorized()
}
})
@@ -246,67 +218,69 @@ trait WikiControllerBase extends ControllerBase {
})
post("/:owner/:repository/wiki/_new", newForm)(readableUsersOnly { (form, repository) =>
context.withLoginAccount { loginAccount =>
if (isEditable(repository)) {
saveWikiPage(
repository.owner,
repository.name,
form.currentPageName,
form.pageName,
form.content,
loginAccount,
form.message.getOrElse(""),
None
).foreach { commitId =>
updateLastActivityDate(repository.owner, repository.name)
val createWikiPageInfo =
CreateWikiPageInfo(repository.owner, repository.name, loginAccount.userName, form.pageName)
recordActivity(createWikiPageInfo)
callWebHookOf(repository.owner, repository.name, WebHook.Gollum, context.settings) {
getAccountByUserName(repository.owner).map { repositoryUser =>
WebHookGollumPayload("created", form.pageName, commitId, repository, repositoryUser, loginAccount)
}
context.withLoginAccount {
loginAccount =>
if (isEditable(repository)) {
saveWikiPage(
repository.owner,
repository.name,
form.currentPageName,
form.pageName,
form.content,
loginAccount,
form.message.getOrElse(""),
None
).foreach {
commitId =>
updateLastActivityDate(repository.owner, repository.name)
val createWikiPageInfo =
CreateWikiPageInfo(repository.owner, repository.name, loginAccount.userName, form.pageName)
recordActivity(createWikiPageInfo)
callWebHookOf(repository.owner, repository.name, WebHook.Gollum, context.settings) {
getAccountByUserName(repository.owner).map { repositoryUser =>
WebHookGollumPayload("created", form.pageName, commitId, repository, repositoryUser, loginAccount)
}
}
}
}
if (notReservedPageName(form.pageName)) {
redirect(s"/${repository.owner}/${repository.name}/wiki/${StringUtil.urlEncode(form.pageName)}")
} else {
redirect(s"/${repository.owner}/${repository.name}/wiki")
}
} else Unauthorized()
if (notReservedPageName(form.pageName)) {
redirect(s"/${repository.owner}/${repository.name}/wiki/${StringUtil.urlEncode(form.pageName)}")
} else {
redirect(s"/${repository.owner}/${repository.name}/wiki")
}
} else Unauthorized()
}
})
get("/:owner/:repository/wiki/:page/_delete")(readableUsersOnly { repository =>
context.withLoginAccount { loginAccount =>
if (isEditable(repository)) {
val pageName = StringUtil.urlDecode(params("page"))
deleteWikiPage(
repository.owner,
repository.name,
pageName,
loginAccount.fullName,
loginAccount.mailAddress,
s"Destroyed ${pageName}"
)
val deleteWikiInfo = DeleteWikiInfo(
repository.owner,
repository.name,
loginAccount.userName,
pageName
)
recordActivity(deleteWikiInfo)
updateLastActivityDate(repository.owner, repository.name)
context.withLoginAccount {
loginAccount =>
if (isEditable(repository)) {
val pageName = StringUtil.urlDecode(params("page"))
deleteWikiPage(
repository.owner,
repository.name,
pageName,
loginAccount.fullName,
loginAccount.mailAddress,
s"Destroyed ${pageName}"
)
val deleteWikiInfo = DeleteWikiInfo(
repository.owner,
repository.name,
loginAccount.userName,
pageName
)
recordActivity(deleteWikiInfo)
updateLastActivityDate(repository.owner, repository.name)
redirect(s"/${repository.owner}/${repository.name}/wiki")
} else Unauthorized()
redirect(s"/${repository.owner}/${repository.name}/wiki")
} else Unauthorized()
}
})
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 =>
@@ -335,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.")
@@ -357,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] = {
@@ -375,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,26 +57,23 @@ 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 =>
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())
post("/api/v3/repos/:owner/:repository/git/refs")(referrersOnly { repository =>
extractFromJsonBody[CreateARef].map {
data =>
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.")
}
} else {
UnprocessableEntity("Ref already exists.")
}
}
} getOrElse BadRequest()
})
@@ -86,23 +83,24 @@ trait ApiGitReferenceControllerBase extends ControllerBase {
*/
patch("/api/v3/repos/:owner/:repository/git/refs/*")(writableUsersOnly { repository =>
val refName = multiParams("splat").mkString("/")
extractFromJsonBody[UpdateARef].map { data =>
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
val ref = git.getRepository.findRef(refName)
if (ref == null) {
UnprocessableEntity("Ref does not exist.")
} else {
val update = git.getRepository.updateRef(ref.getName)
update.setNewObjectId(ObjectId.fromString(data.sha))
update.setForceUpdate(data.force)
val result = update.update()
result match {
case Result.FORCED | Result.FAST_FORWARD | Result.NO_CHANGE =>
JsonFormat(ApiRef.fromRef(RepositoryName(repository), git.getRepository.findRef(refName)))
case _ => UnprocessableEntity(result.name())
extractFromJsonBody[UpdateARef].map {
data =>
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.owner))) { git =>
val ref = git.getRepository.findRef(refName)
if (ref == null) {
UnprocessableEntity("Ref does not exist.")
} else {
val update = git.getRepository.updateRef(ref.getName)
update.setNewObjectId(ObjectId.fromString(data.sha))
update.setForceUpdate(data.force)
val result = update.update()
result match {
case Result.FORCED | Result.FAST_FORWARD | Result.NO_CHANGE =>
JsonFormat(ApiRef.fromRef(RepositoryName(repository), update.getRef))
case _ => UnprocessableEntity(result.name())
}
}
}
}
} getOrElse BadRequest()
})

View File

@@ -23,8 +23,9 @@ trait ApiIssueCommentControllerBase extends ControllerBase {
issueId <- params("id").toIntOpt
comments = getCommentsForApi(repository.owner, repository.name, issueId)
} yield {
JsonFormat(comments.map { case (issueComment, user, issue) =>
ApiComment(issueComment, RepositoryName(repository), issueId, ApiUser(user), issue.isPullRequest)
JsonFormat(comments.map {
case (issueComment, user, issue) =>
ApiComment(issueComment, RepositoryName(repository), issueId, ApiUser(user), issue.isPullRequest)
})
}) getOrElse NotFound()
})
@@ -84,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

@@ -29,7 +29,7 @@ trait ApiIssueControllerBase extends ControllerBase {
val page = IssueSearchCondition.page(request)
// TODO: more api spec condition
val condition = IssueSearchCondition(request)
// val baseOwner = getAccountByUserName(repository.owner).get
//val baseOwner = getAccountByUserName(repository.owner).get
val issues: List[(Issue, Account, List[Account])] =
searchIssueByApi(
@@ -39,16 +39,17 @@ trait ApiIssueControllerBase extends ControllerBase {
repos = repository.owner -> repository.name
)
JsonFormat(issues.map { case (issue, issueUser, assigneeUsers) =>
ApiIssue(
issue = issue,
repositoryName = RepositoryName(repository),
user = ApiUser(issueUser),
assignees = assigneeUsers.map(ApiUser(_)),
labels = getIssueLabels(repository.owner, repository.name, issue.issueId)
.map(ApiLabel(_, RepositoryName(repository))),
issue.milestoneId.flatMap { getApiMilestone(repository, _) }
)
JsonFormat(issues.map {
case (issue, issueUser, assigneeUsers) =>
ApiIssue(
issue = issue,
repositoryName = RepositoryName(repository),
user = ApiUser(issueUser),
assignees = assigneeUsers.map(ApiUser(_)),
labels = getIssueLabels(repository.owner, repository.name, issue.issueId)
.map(ApiLabel(_, RepositoryName(repository))),
issue.milestoneId.flatMap { getApiMilestone(repository, _) }
)
})
})
@@ -125,7 +126,7 @@ trait ApiIssueControllerBase extends ControllerBase {
*/
/*
* vii. Unlock an issue
* https://developer.github.com/v3/issues/#unlock-an-issue
*/
* vii. Unlock an issue
* https://developer.github.com/v3/issues/#unlock-an-issue
*/
}

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._
@@ -69,24 +69,25 @@ trait ApiIssueLabelControllerBase extends ControllerBase {
data <- extractFromJsonBody[CreateALabel] if data.isValid
} yield {
LockUtil.lock(RepositoryName(repository).fullName) {
getLabel(repository.owner, repository.name, params("labelName")).map { label =>
if (getLabel(repository.owner, repository.name, data.name).isEmpty) {
updateLabel(repository.owner, repository.name, label.labelId, data.name, data.color)
JsonFormat(
ApiLabel(
getLabel(repository.owner, repository.name, label.labelId).get,
RepositoryName(repository)
getLabel(repository.owner, repository.name, params("labelName")).map {
label =>
if (getLabel(repository.owner, repository.name, data.name).isEmpty) {
updateLabel(repository.owner, repository.name, label.labelId, data.name, data.color)
JsonFormat(
ApiLabel(
getLabel(repository.owner, repository.name, label.labelId).get,
RepositoryName(repository)
)
)
)
} else {
// TODO ApiError should support errors field to enhance compatibility of GitHub API
UnprocessableEntity(
ApiError(
"Validation Failed",
Some("https://developer.github.com/v3/issues/labels/#create-a-label")
} else {
// TODO ApiError should support errors field to enhance compatibility of GitHub API
UnprocessableEntity(
ApiError(
"Validation Failed",
Some("https://developer.github.com/v3/issues/labels/#create-a-label")
)
)
)
}
}
} getOrElse NotFound()
}
}) getOrElse NotFound()
@@ -120,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,
@@ -159,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,
@@ -188,7 +189,7 @@ trait ApiIssueLabelControllerBase extends ControllerBase {
})
/*
* xi Get labels for every issue in a milestone
* https://developer.github.com/v3/issues/labels/#get-labels-for-every-issue-in-a-milestone
*/
* xi Get labels for every issue in a milestone
* https://developer.github.com/v3/issues/labels/#get-labels-for-every-issue-in-a-milestone
*/
}

View File

@@ -16,10 +16,8 @@ trait ApiIssueMilestoneControllerBase extends ControllerBase {
get("/api/v3/repos/:owner/:repository/milestones")(referrersOnly { repository =>
val state = params.getOrElse("state", "all")
// TODO "sort", "direction" params should be implemented.
val apiMilestones = (for (
milestoneWithIssue <- getMilestonesWithIssueCount(repository.owner, repository.name)
.sortBy(p => p._1.milestoneId)
)
val apiMilestones = (for (milestoneWithIssue <- getMilestonesWithIssueCount(repository.owner, repository.name)
.sortBy(p => p._1.milestoneId))
yield {
ApiMilestone(
repository.repository,

View File

@@ -71,6 +71,6 @@ trait ApiOrganizationControllerBase extends ControllerBase {
*/
/*
* should implement delete an organization API?
*/
* should implement delete an organization API?
*/
}

View File

@@ -48,18 +48,19 @@ trait ApiPullRequestControllerBase extends ControllerBase {
repos = repository.owner -> repository.name
)
JsonFormat(issues.map { case (issue, issueUser, commentCount, pullRequest, headRepo, headOwner, assignees) =>
ApiPullRequest(
issue = issue,
pullRequest = pullRequest,
headRepo = ApiRepository(headRepo, ApiUser(headOwner)),
baseRepo = ApiRepository(repository, ApiUser(baseOwner)),
user = ApiUser(issueUser),
labels = getIssueLabels(repository.owner, repository.name, issue.issueId)
.map(ApiLabel(_, RepositoryName(repository))),
assignees = assignees.map(ApiUser.apply),
mergedComment = getMergedComment(repository.owner, repository.name, issue.issueId)
)
JsonFormat(issues.map {
case (issue, issueUser, commentCount, pullRequest, headRepo, headOwner, assignees) =>
ApiPullRequest(
issue = issue,
pullRequest = pullRequest,
headRepo = ApiRepository(headRepo, ApiUser(headOwner)),
baseRepo = ApiRepository(repository, ApiUser(baseOwner)),
user = ApiUser(issueUser),
labels = getIssueLabels(repository.owner, repository.name, issue.issueId)
.map(ApiLabel(_, RepositoryName(repository))),
assignees = assignees.map(ApiUser.apply),
mergedComment = getMergedComment(repository.owner, repository.name, issue.issueId)
)
})
})
@@ -88,37 +89,38 @@ trait ApiPullRequestControllerBase extends ControllerBase {
case Left(createPullReq) =>
val (reqOwner, reqBranch) = parseCompareIdentifier(createPullReq.head, repository.owner)
getRepository(reqOwner, repository.name)
.flatMap { forkedRepository =>
getPullRequestCommitFromTo(repository, forkedRepository, createPullReq.base, reqBranch) match {
case (Some(commitIdFrom), Some(commitIdTo)) =>
val issueId = insertIssue(
owner = repository.owner,
repository = repository.name,
loginUser = context.loginAccount.get.userName,
title = createPullReq.title,
content = createPullReq.body,
milestoneId = None,
priorityId = None,
isPullRequest = true
)
.flatMap {
forkedRepository =>
getPullRequestCommitFromTo(repository, forkedRepository, createPullReq.base, reqBranch) match {
case (Some(commitIdFrom), Some(commitIdTo)) =>
val issueId = insertIssue(
owner = repository.owner,
repository = repository.name,
loginUser = context.loginAccount.get.userName,
title = createPullReq.title,
content = createPullReq.body,
milestoneId = None,
priorityId = None,
isPullRequest = true
)
createPullRequest(
originRepository = repository,
issueId = issueId,
originBranch = createPullReq.base,
requestUserName = reqOwner,
requestRepositoryName = repository.name,
requestBranch = reqBranch,
commitIdFrom = commitIdFrom.getName,
commitIdTo = commitIdTo.getName,
isDraft = createPullReq.draft.getOrElse(false),
loginAccount = context.loginAccount.get,
settings = context.settings
)
getApiPullRequest(repository, issueId).map(JsonFormat(_))
case _ =>
None
}
createPullRequest(
originRepository = repository,
issueId = issueId,
originBranch = createPullReq.base,
requestUserName = reqOwner,
requestRepositoryName = repository.name,
requestBranch = reqBranch,
commitIdFrom = commitIdFrom.getName,
commitIdTo = commitIdTo.getName,
isDraft = createPullReq.draft.getOrElse(false),
loginAccount = context.loginAccount.get,
settings = context.settings
)
getApiPullRequest(repository, issueId).map(JsonFormat(_))
case _ =>
None
}
}
.getOrElse {
NotFound()
@@ -126,27 +128,28 @@ trait ApiPullRequestControllerBase extends ControllerBase {
case Right(createPullReqAlt) =>
val (reqOwner, reqBranch) = parseCompareIdentifier(createPullReqAlt.head, repository.owner)
getRepository(reqOwner, repository.name)
.flatMap { forkedRepository =>
getPullRequestCommitFromTo(repository, forkedRepository, createPullReqAlt.base, reqBranch) match {
case (Some(commitIdFrom), Some(commitIdTo)) =>
changeIssueToPullRequest(repository.owner, repository.name, createPullReqAlt.issue)
createPullRequest(
originRepository = repository,
issueId = createPullReqAlt.issue,
originBranch = createPullReqAlt.base,
requestUserName = reqOwner,
requestRepositoryName = repository.name,
requestBranch = reqBranch,
commitIdFrom = commitIdFrom.getName,
commitIdTo = commitIdTo.getName,
isDraft = false,
loginAccount = context.loginAccount.get,
settings = context.settings
)
getApiPullRequest(repository, createPullReqAlt.issue).map(JsonFormat(_))
case _ =>
None
}
.flatMap {
forkedRepository =>
getPullRequestCommitFromTo(repository, forkedRepository, createPullReqAlt.base, reqBranch) match {
case (Some(commitIdFrom), Some(commitIdTo)) =>
changeIssueToPullRequest(repository.owner, repository.name, createPullReqAlt.issue)
createPullRequest(
originRepository = repository,
issueId = createPullReqAlt.issue,
originBranch = createPullReqAlt.base,
requestUserName = reqOwner,
requestRepositoryName = repository.name,
requestBranch = reqBranch,
commitIdFrom = commitIdFrom.getName,
commitIdTo = commitIdTo.getName,
isDraft = false,
loginAccount = context.loginAccount.get,
settings = context.settings
)
getApiPullRequest(repository, createPullReqAlt.issue).map(JsonFormat(_))
case _ =>
None
}
}
.getOrElse {
NotFound()
@@ -187,24 +190,26 @@ trait ApiPullRequestControllerBase extends ControllerBase {
get("/api/v3/repos/:owner/:repository/pulls/:id/commits")(referrersOnly { repository =>
val owner = repository.owner
val name = repository.name
params("id").toIntOpt.flatMap { issueId =>
getPullRequest(owner, name, issueId) map { case (issue, pullreq) =>
Using.resource(Git.open(getRepositoryDir(owner, name))) { git =>
val oldId = git.getRepository.resolve(pullreq.commitIdFrom)
val newId = git.getRepository.resolve(pullreq.commitIdTo)
val repoFullName = RepositoryName(repository)
val commits = git.log
.addRange(oldId, newId)
.call
.iterator
.asScala
.map { c =>
ApiCommitListItem(new CommitInfo(c), repoFullName)
params("id").toIntOpt.flatMap {
issueId =>
getPullRequest(owner, name, issueId) map {
case (issue, pullreq) =>
Using.resource(Git.open(getRepositoryDir(owner, name))) { git =>
val oldId = git.getRepository.resolve(pullreq.commitIdFrom)
val newId = git.getRepository.resolve(pullreq.commitIdTo)
val repoFullName = RepositoryName(repository)
val commits = git.log
.addRange(oldId, newId)
.call
.iterator
.asScala
.map { c =>
ApiCommitListItem(new CommitInfo(c), repoFullName)
}
.toList
JsonFormat(commits)
}
.toList
JsonFormat(commits)
}
}
} getOrElse NotFound()
})
/*
@@ -235,8 +240,8 @@ trait ApiPullRequestControllerBase extends ControllerBase {
*/
put("/api/v3/repos/:owner/:repository/pulls/:id/merge")(referrersOnly { repository =>
(for {
// TODO: crash when body is empty
// TODO: Implement sha parameter
//TODO: crash when body is empty
//TODO: Implement sha parameter
data <- extractFromJsonBody[MergeAPullRequest]
issueId <- params("id").toIntOpt
(issue, pullReq) <- getPullRequest(repository.owner, repository.name, issueId)
@@ -268,7 +273,7 @@ trait ApiPullRequestControllerBase extends ControllerBase {
repository,
issueId,
context.loginAccount.get,
data.commit_message.getOrElse(""), // TODO: Implement commit_title
data.commit_message.getOrElse(""), //TODO: Implement commit_title
strategy,
pullReq.isDraft,
context.settings

View File

@@ -119,39 +119,40 @@ trait ApiReleaseControllerBase extends ControllerBase {
* ix. Upload a release asset
* https://developer.github.com/v3/repos/releases/#upload-a-release-asset
*/
post("/api/v3/repos/:owner/:repository/releases/:tag/assets")(writableUsersOnly { repository =>
val name = params("name")
val tag = params("tag")
getRelease(repository.owner, repository.name, tag)
.map { release =>
val fileId = FileUtil.generateFileId
val buf = new Array[Byte](request.inputStream.available())
request.inputStream.read(buf)
FileUtils.writeByteArrayToFile(
new File(
getReleaseFilesDir(repository.owner, repository.name),
FileUtil.checkFilename(tag + "/" + fileId)
),
buf
)
createReleaseAsset(
repository.owner,
repository.name,
tag,
fileId,
name,
request.contentLength.getOrElse(0),
context.loginAccount.get
)
getReleaseAsset(repository.owner, repository.name, tag, fileId)
.map { asset =>
JsonFormat(ApiReleaseAsset(asset, RepositoryName(repository)))
}
.getOrElse {
ApiError("Unknown error")
}
}
.getOrElse(NotFound())
post("/api/v3/repos/:owner/:repository/releases/:tag/assets")(writableUsersOnly {
repository =>
val name = params("name")
val tag = params("tag")
getRelease(repository.owner, repository.name, tag)
.map { release =>
val fileId = FileUtil.generateFileId
val buf = new Array[Byte](request.inputStream.available())
request.inputStream.read(buf)
FileUtils.writeByteArrayToFile(
new File(
getReleaseFilesDir(repository.owner, repository.name),
FileUtil.checkFilename(tag + "/" + fileId)
),
buf
)
createReleaseAsset(
repository.owner,
repository.name,
tag,
fileId,
name,
request.contentLength.getOrElse(0),
context.loginAccount.get
)
getReleaseAsset(repository.owner, repository.name, tag, fileId)
.map { asset =>
JsonFormat(ApiReleaseAsset(asset, RepositoryName(repository)))
}
.getOrElse {
ApiError("Unknown error")
}
}
.getOrElse(NotFound())
})
/**
@@ -175,7 +176,7 @@ trait ApiReleaseControllerBase extends ControllerBase {
*/
/*
* xii. Delete a release asset
* https://developer.github.com/v3/repos/releases/#edit-a-release-asset
*/
* xii. Delete a release asset
* https://developer.github.com/v3/repos/releases/#edit-a-release-asset
*/
}

View File

@@ -1,11 +1,11 @@
package gitbucket.core.controller.api
import gitbucket.core.api.*
import gitbucket.core.api._
import gitbucket.core.controller.ControllerBase
import gitbucket.core.service.{AccountService, ProtectedBranchService, RepositoryService}
import gitbucket.core.util.*
import gitbucket.core.util.Directory.*
import gitbucket.core.util.Implicits.*
import gitbucket.core.util.JGitUtil.getBranchesNoMergeInfo
import gitbucket.core.util._
import gitbucket.core.util.Directory._
import gitbucket.core.util.Implicits._
import gitbucket.core.util.JGitUtil.getBranches
import org.eclipse.jgit.api.Git
import org.scalatra.NoContent
@@ -30,9 +30,10 @@ trait ApiRepositoryBranchControllerBase extends ControllerBase {
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
JsonFormat(
JGitUtil
.getBranchesNoMergeInfo(
.getBranches(
git = git,
defaultBranch = repository.repository.defaultBranch
defaultBranch = repository.repository.defaultBranch,
origin = repository.repository.originUserName.isEmpty
)
.map { br =>
ApiBranchForList(br.name, ApiBranchCommit(br.commitId))
@@ -46,19 +47,21 @@ trait ApiRepositoryBranchControllerBase extends ControllerBase {
* https://docs.github.com/en/rest/reference/repos#get-a-branch
*/
get("/api/v3/repos/:owner/:repository/branches/*")(referrersOnly { repository =>
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
(for {
branch <- params.get("splat") if repository.branchList.contains(branch)
br <- getBranchesNoMergeInfo(
git,
repository.repository.defaultBranch
).find(_.name == branch)
} yield {
val protection = getProtectedBranchInfo(repository.owner, repository.name, branch)
JsonFormat(
ApiBranch(branch, ApiBranchCommit(br.commitId), ApiBranchProtection(protection))(RepositoryName(repository))
)
}) getOrElse NotFound()
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) {
git =>
(for {
branch <- params.get("splat") if repository.branchList.contains(branch)
br <- getBranches(
git,
repository.repository.defaultBranch,
repository.repository.originUserName.isEmpty
).find(_.name == branch)
} yield {
val protection = getProtectedBranchInfo(repository.owner, repository.name, branch)
JsonFormat(
ApiBranch(branch, ApiBranchCommit(br.commitId), ApiBranchProtection(protection))(RepositoryName(repository))
)
}) getOrElse NotFound()
}
})
@@ -271,29 +274,31 @@ trait ApiRepositoryBranchControllerBase extends ControllerBase {
* https://developer.github.com/v3/repos/#enabling-and-disabling-branch-protection
*/
patch("/api/v3/repos/:owner/:repository/branches/*")(ownerOnly { repository =>
import gitbucket.core.api.*
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
(for {
branch <- params.get("splat") if repository.branchList.contains(branch)
protection <- extractFromJsonBody[ApiBranchProtection.EnablingAndDisabling].map(_.protection)
br <- getBranchesNoMergeInfo(
git,
repository.repository.defaultBranch
).find(_.name == branch)
} yield {
if (protection.enabled) {
enableBranchProtection(
repository.owner,
repository.name,
branch,
protection.status.enforcement_level == ApiBranchProtection.Everyone,
protection.status.contexts
)
} else {
disableBranchProtection(repository.owner, repository.name, branch)
}
JsonFormat(ApiBranch(branch, ApiBranchCommit(br.commitId), protection)(RepositoryName(repository)))
}) getOrElse NotFound()
import gitbucket.core.api._
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) {
git =>
(for {
branch <- params.get("splat") if repository.branchList.contains(branch)
protection <- extractFromJsonBody[ApiBranchProtection.EnablingAndDisabling].map(_.protection)
br <- getBranches(
git,
repository.repository.defaultBranch,
repository.repository.originUserName.isEmpty
).find(_.name == branch)
} yield {
if (protection.enabled) {
enableBranchProtection(
repository.owner,
repository.name,
branch,
protection.status.enforcement_level == ApiBranchProtection.Everyone,
protection.status.contexts
)
} else {
disableBranchProtection(repository.owner, repository.name, branch)
}
JsonFormat(ApiBranch(branch, ApiBranchCommit(br.commitId), protection)(RepositoryName(repository)))
}) getOrElse NotFound()
}
})
}

View File

@@ -4,21 +4,28 @@ import gitbucket.core.controller.ControllerBase
import gitbucket.core.model.Account
import gitbucket.core.service.{AccountService, CommitsService, ProtectedBranchService}
import gitbucket.core.util.Directory.getRepositoryDir
import gitbucket.core.util.Implicits.*
import gitbucket.core.util.JGitUtil.{CommitInfo, getBranchesNoMergeInfo, getBranchesOfCommit}
import gitbucket.core.util.Implicits._
import gitbucket.core.util.JGitUtil.{CommitInfo, getBranches, getBranchesOfCommit}
import gitbucket.core.util.{JGitUtil, ReferrerAuthenticator, RepositoryName}
import org.eclipse.jgit.api.Git
import org.eclipse.jgit.revwalk.RevWalk
import org.eclipse.jgit.revwalk.filter.*
import org.eclipse.jgit.revwalk.filter.{
AndRevFilter,
AuthorRevFilter,
CommitTimeRevFilter,
MaxCountRevFilter,
RevFilter,
SkipRevFilter
}
import org.eclipse.jgit.treewalk.filter.{AndTreeFilter, PathFilterGroup, TreeFilter}
import java.time.format.DateTimeFormatter.*
import java.time.{LocalDateTime, ZoneOffset}
import java.util.Date
import scala.collection.mutable.ListBuffer
import scala.jdk.CollectionConverters.*
import scala.math.min
import scala.jdk.CollectionConverters._
import scala.util.Using
import math.min
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter._
import java.util.Date
import java.time.ZoneOffset
trait ApiRepositoryCommitControllerBase extends ControllerBase {
self: AccountService with CommitsService with ProtectedBranchService with ReferrerAuthenticator =>
@@ -36,53 +43,56 @@ trait ApiRepositoryCommitControllerBase extends ControllerBase {
val path = params.get("path").filter(_.nonEmpty)
val since = params.get("since").filter(_.nonEmpty)
val until = params.get("until").filter(_.nonEmpty)
Using.resource(Git.open(getRepositoryDir(owner, name))) { git =>
val repo = git.getRepository
Using.resource(new RevWalk(repo)) { revWalk =>
val objectId = repo.resolve(sha)
revWalk.markStart(revWalk.parseCommit(objectId))
if (path.nonEmpty) {
revWalk.setTreeFilter(
AndTreeFilter.create(PathFilterGroup.createFromStrings(path.get), TreeFilter.ANY_DIFF)
)
Using.resource(Git.open(getRepositoryDir(owner, name))) {
git =>
val repo = git.getRepository
Using.resource(new RevWalk(repo)) {
revWalk =>
val objectId = repo.resolve(sha)
revWalk.markStart(revWalk.parseCommit(objectId))
if (path.nonEmpty) {
revWalk.setTreeFilter(
AndTreeFilter.create(PathFilterGroup.createFromStrings(path.get), TreeFilter.ANY_DIFF)
)
}
val revfilters = new ListBuffer[(RevFilter)]()
if (author.nonEmpty) {
revfilters += AuthorRevFilter.create(author.get)
}
if (since.nonEmpty) {
revfilters += CommitTimeRevFilter.after(
Date.from(LocalDateTime.parse(since.get, ISO_DATE_TIME).toInstant(ZoneOffset.UTC))
)
}
if (until.nonEmpty) {
revfilters += CommitTimeRevFilter.before(
Date.from(LocalDateTime.parse(until.get, ISO_DATE_TIME).toInstant(ZoneOffset.UTC))
)
}
if (page > 1) {
revfilters += SkipRevFilter.create(page * per_page - 2)
}
revfilters += MaxCountRevFilter.create(per_page);
revWalk.setRevFilter(
if (revfilters.size > 1) {
AndRevFilter.create(revfilters.toArray)
} else {
revfilters(0)
}
)
JsonFormat(revWalk.asScala.map {
commit =>
val commitInfo = new CommitInfo(commit)
ApiCommits(
repositoryName = RepositoryName(repository),
commitInfo = commitInfo,
diffs = JGitUtil.getDiffs(git, commitInfo.parents.headOption, commitInfo.id, false, true),
author = getAccount(commitInfo.authorName, commitInfo.authorEmailAddress),
committer = getAccount(commitInfo.committerName, commitInfo.committerEmailAddress),
commentCount = getCommitComment(repository.owner, repository.name, commitInfo.id).size
)
})
}
val revfilters = new ListBuffer[(RevFilter)]()
if (author.nonEmpty) {
revfilters += AuthorRevFilter.create(author.get)
}
if (since.nonEmpty) {
revfilters += CommitTimeRevFilter.after(
Date.from(LocalDateTime.parse(since.get, ISO_DATE_TIME).toInstant(ZoneOffset.UTC))
)
}
if (until.nonEmpty) {
revfilters += CommitTimeRevFilter.before(
Date.from(LocalDateTime.parse(until.get, ISO_DATE_TIME).toInstant(ZoneOffset.UTC))
)
}
if (page > 1) {
revfilters += SkipRevFilter.create(page * per_page - 2)
}
revfilters += MaxCountRevFilter.create(per_page);
revWalk.setRevFilter(
if (revfilters.size > 1) {
AndRevFilter.create(revfilters.toArray)
} else {
revfilters(0)
}
)
JsonFormat(revWalk.asScala.map { commit =>
val commitInfo = new CommitInfo(commit)
ApiCommits(
repositoryName = RepositoryName(repository),
commitInfo = commitInfo,
diffs = JGitUtil.getDiffs(git, commitInfo.parents.headOption, commitInfo.id, false, true),
author = getAccount(commitInfo.authorName, commitInfo.authorEmailAddress),
committer = getAccount(commitInfo.committerName, commitInfo.committerEmailAddress),
commentCount = getCommitComment(repository.owner, repository.name, commitInfo.id).size
)
})
}
}
})
@@ -95,23 +105,24 @@ trait ApiRepositoryCommitControllerBase extends ControllerBase {
val name = repository.name
val sha = params("sha")
Using.resource(Git.open(getRepositoryDir(owner, name))) { git =>
val repo = git.getRepository
val objectId = repo.resolve(sha)
val commitInfo = Using.resource(new RevWalk(repo)) { revWalk =>
new CommitInfo(revWalk.parseCommit(objectId))
}
Using.resource(Git.open(getRepositoryDir(owner, name))) {
git =>
val repo = git.getRepository
val objectId = repo.resolve(sha)
val commitInfo = Using.resource(new RevWalk(repo)) { revWalk =>
new CommitInfo(revWalk.parseCommit(objectId))
}
JsonFormat(
ApiCommits(
repositoryName = RepositoryName(repository),
commitInfo = commitInfo,
diffs = JGitUtil.getDiffs(git, commitInfo.parents.headOption, commitInfo.id, false, true),
author = getAccount(commitInfo.authorName, commitInfo.authorEmailAddress),
committer = getAccount(commitInfo.committerName, commitInfo.committerEmailAddress),
commentCount = getCommitComment(repository.owner, repository.name, sha).size
JsonFormat(
ApiCommits(
repositoryName = RepositoryName(repository),
commitInfo = commitInfo,
diffs = JGitUtil.getDiffs(git, commitInfo.parents.headOption, commitInfo.id, false, true),
author = getAccount(commitInfo.authorName, commitInfo.authorEmailAddress),
committer = getAccount(commitInfo.committerName, commitInfo.committerEmailAddress),
commentCount = getCommitComment(repository.owner, repository.name, sha).size
)
)
)
}
})
@@ -159,7 +170,7 @@ trait ApiRepositoryCommitControllerBase extends ControllerBase {
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
val apiBranchForCommits = for {
branch <- getBranchesOfCommit(git, sha)
br <- getBranchesNoMergeInfo(git, branch).find(_.name == branch)
br <- getBranches(git, branch, repository.repository.originUserName.isEmpty).find(_.name == branch)
} yield {
val protection = getProtectedBranchInfo(repository.owner, repository.name, branch)
ApiBranchForHeadCommit(branch, ApiBranchCommit(br.commitId), protection.enabled)

View File

@@ -19,16 +19,17 @@ trait ApiRepositoryContentsControllerBase extends ControllerBase {
* https://docs.github.com/en/rest/reference/repos#get-a-repository-readme
*/
get("/api/v3/repos/:owner/:repository/readme")(referrersOnly { repository =>
Using.resource(Git.open(getRepositoryDir(params("owner"), params("repository")))) { git =>
val refStr = params.getOrElse("ref", repository.repository.defaultBranch)
val files = getFileList(git, refStr, ".", maxFiles = context.settings.repositoryViewer.maxFiles)
files // files should be sorted alphabetically.
.find { file =>
!file.isDirectory && RepositoryService.readmeFiles.contains(file.name.toLowerCase)
} match {
case Some(x) => getContents(repository = repository, path = x.name, refStr = refStr, ignoreCase = true)
case _ => NotFound()
}
Using.resource(Git.open(getRepositoryDir(params("owner"), params("repository")))) {
git =>
val refStr = params.getOrElse("ref", repository.repository.defaultBranch)
val files = getFileList(git, refStr, ".", maxFiles = context.settings.repositoryViewer.maxFiles)
files // files should be sorted alphabetically.
.find { file =>
!file.isDirectory && RepositoryService.readmeFiles.contains(file.name.toLowerCase)
} match {
case Some(x) => getContents(repository = repository, path = x.name, refStr = refStr, ignoreCase = true)
case _ => NotFound()
}
}
})
@@ -133,65 +134,67 @@ trait ApiRepositoryContentsControllerBase extends ControllerBase {
* requested #2112
*/
put("/api/v3/repos/:owner/:repository/contents/*")(writableUsersOnly { repository =>
context.withLoginAccount { loginAccount =>
JsonFormat(for {
data <- extractFromJsonBody[CreateAFile]
} yield {
val branch = data.branch.getOrElse(repository.repository.defaultBranch)
val commit = Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(branch))
revCommit.name
}
val paths = multiParams("splat").head.split("/")
val path = paths.take(paths.size - 1).toList.mkString("/")
Using.resource(Git.open(getRepositoryDir(params("owner"), params("repository")))) { git =>
val fileInfo = getFileInfo(git, commit, path, false)
context.withLoginAccount {
loginAccount =>
JsonFormat(for {
data <- extractFromJsonBody[CreateAFile]
} yield {
val branch = data.branch.getOrElse(repository.repository.defaultBranch)
val commit = Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(branch))
revCommit.name
}
val paths = multiParams("splat").head.split("/")
val path = paths.take(paths.size - 1).toList.mkString("/")
Using.resource(Git.open(getRepositoryDir(params("owner"), params("repository")))) {
git =>
val fileInfo = getFileInfo(git, commit, path, false)
fileInfo match {
case Some(f) if !data.sha.contains(f.id.getName) =>
ApiError(
"The blob SHA is not matched.",
Some("https://docs.github.com/en/rest/reference/repos#create-or-update-file-contents")
)
case _ =>
commitFile(
repository,
branch,
path,
Some(paths.last),
data.sha.map(_ => paths.last),
StringUtil.base64Decode(data.content),
data.message,
commit,
loginAccount,
data.committer.map(_.name).getOrElse(loginAccount.fullName),
data.committer.map(_.email).getOrElse(loginAccount.mailAddress),
context.settings
) match {
case Left(error) =>
ApiError(s"Failed to commit a file: ${error}", None)
case Right((_, None)) =>
ApiError("Failed to commit a file.", None)
case Right((commitId, Some(blobId))) =>
Map(
"content" -> ApiContents(
"file",
paths.last,
path,
blobId.name,
Some(data.content),
Some("base64")
)(RepositoryName(repository)),
"commit" -> ApiCommit(
git,
RepositoryName(repository),
new CommitInfo(JGitUtil.getRevCommitFromId(git, commitId))
)
fileInfo match {
case Some(f) if !data.sha.contains(f.id.getName) =>
ApiError(
"The blob SHA is not matched.",
Some("https://docs.github.com/en/rest/reference/repos#create-or-update-file-contents")
)
case _ =>
commitFile(
repository,
branch,
path,
Some(paths.last),
data.sha.map(_ => paths.last),
StringUtil.base64Decode(data.content),
data.message,
commit,
loginAccount,
data.committer.map(_.name).getOrElse(loginAccount.fullName),
data.committer.map(_.email).getOrElse(loginAccount.mailAddress),
context.settings
) match {
case Left(error) =>
ApiError(s"Failed to commit a file: ${error}", None)
case Right((_, None)) =>
ApiError("Failed to commit a file.", None)
case Right((commitId, Some(blobId))) =>
Map(
"content" -> ApiContents(
"file",
paths.last,
path,
blobId.name,
Some(data.content),
Some("base64")
)(RepositoryName(repository)),
"commit" -> ApiCommit(
git,
RepositoryName(repository),
new CommitInfo(JGitUtil.getRevCommitFromId(git, commitId))
)
)
}
}
}
}
})
})
}
})
@@ -202,9 +205,9 @@ trait ApiRepositoryContentsControllerBase extends ControllerBase {
*/
/*
* vi. Download a repository archive (tar/zip)
* https://docs.github.com/en/rest/reference/repos#download-a-repository-archive-tar
* https://docs.github.com/en/rest/reference/repos#download-a-repository-archive-zip
*/
* vi. Download a repository archive (tar/zip)
* https://docs.github.com/en/rest/reference/repos#download-a-repository-archive-tar
* https://docs.github.com/en/rest/reference/repos#download-a-repository-archive-zip
*/
}

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

@@ -47,8 +47,9 @@ trait ApiRepositoryStatusControllerBase extends ControllerBase {
ref <- params.get("ref")
sha <- JGitUtil.getShaByRef(repository.owner, repository.name, ref)
} yield {
JsonFormat(getCommitStatusesWithCreator(repository.owner, repository.name, sha).map { case (status, creator) =>
ApiCommitStatus(status, ApiUser(creator))
JsonFormat(getCommitStatusesWithCreator(repository.owner, repository.name, sha).map {
case (status, creator) =>
ApiCommitStatus(status, ApiUser(creator))
})
}) getOrElse NotFound()
})

View File

@@ -113,8 +113,8 @@ trait ApiRepositoryWebhookControllerBase extends ControllerBase {
*/
/*
* vi. Test the push repository webhook
* https://docs.github.com/en/rest/reference/repos#test-the-push-repository-webhook
*/
* vi. Test the push repository webhook
* https://docs.github.com/en/rest/reference/repos#test-the-push-repository-webhook
*/
}

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

@@ -3,7 +3,7 @@ package gitbucket.core.model
protected[model] trait TemplateComponent { self: Profile =>
import profile.api._
trait BasicTemplate { self: Table[?] =>
trait BasicTemplate { self: Table[_] =>
val userName = column[String]("USER_NAME")
val repositoryName = column[String]("REPOSITORY_NAME")
@@ -18,7 +18,7 @@ protected[model] trait TemplateComponent { self: Profile =>
(this.userName === userName) && (this.repositoryName === repositoryName)
}
trait IssueTemplate extends BasicTemplate { self: Table[?] =>
trait IssueTemplate extends BasicTemplate { self: Table[_] =>
val issueId = column[Int]("ISSUE_ID")
def byIssue(owner: String, repository: String, issueId: Int) =
@@ -28,7 +28,7 @@ protected[model] trait TemplateComponent { self: Profile =>
byRepository(userName, repositoryName) && (this.issueId === issueId)
}
trait LabelTemplate extends BasicTemplate { self: Table[?] =>
trait LabelTemplate extends BasicTemplate { self: Table[_] =>
val labelId = column[Int]("LABEL_ID")
val labelName = column[String]("LABEL_NAME")
@@ -42,7 +42,7 @@ protected[model] trait TemplateComponent { self: Profile =>
byRepository(owner, repository) && (this.labelName === labelName.bind)
}
trait PriorityTemplate extends BasicTemplate { self: Table[?] =>
trait PriorityTemplate extends BasicTemplate { self: Table[_] =>
val priorityId = column[Int]("PRIORITY_ID")
val priorityName = column[String]("PRIORITY_NAME")
@@ -56,7 +56,7 @@ protected[model] trait TemplateComponent { self: Profile =>
byRepository(owner, repository) && (this.priorityName === priorityName.bind)
}
trait MilestoneTemplate extends BasicTemplate { self: Table[?] =>
trait MilestoneTemplate extends BasicTemplate { self: Table[_] =>
val milestoneId = column[Int]("MILESTONE_ID")
def byMilestone(owner: String, repository: String, milestoneId: Int) =
@@ -66,7 +66,7 @@ protected[model] trait TemplateComponent { self: Profile =>
byRepository(userName, repositoryName) && (this.milestoneId === milestoneId)
}
trait CommitTemplate extends BasicTemplate { self: Table[?] =>
trait CommitTemplate extends BasicTemplate { self: Table[_] =>
val commitId = column[String]("COMMIT_ID")
def byCommit(owner: String, repository: String, commitId: String) =
@@ -76,7 +76,7 @@ protected[model] trait TemplateComponent { self: Profile =>
byRepository(userName, repositoryName) && (this.commitId === commitId)
}
trait BranchTemplate extends BasicTemplate { self: Table[?] =>
trait BranchTemplate extends BasicTemplate { self: Table[_] =>
val branch = column[String]("BRANCH")
def byBranch(owner: String, repository: String, branchName: String) =
byRepository(owner, repository) && (branch === branchName.bind)

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])(implicit
context: Context
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,140 +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;"/>"""
@@ -270,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 != '') {
@@ -286,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
)(implicit
context: Context
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;"/>"""
@@ -327,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();
| }
| );
| }
@@ -367,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

@@ -53,24 +53,24 @@ trait RepositoryComponent extends TemplateComponent { self: Profile =>
safeMode
)
).shaped.<>(
{ case (repository, options) =>
Repository(
repository._1,
repository._2,
repository._3,
repository._4,
repository._5,
repository._6,
repository._7,
repository._8,
repository._9,
repository._10,
repository._11,
repository._12,
RepositoryOptions.tupled.apply(options)
)
},
{ (r: Repository) =>
{
case (repository, options) =>
Repository(
repository._1,
repository._2,
repository._3,
repository._4,
repository._5,
repository._6,
repository._7,
repository._8,
repository._9,
repository._10,
repository._11,
repository._12,
RepositoryOptions.tupled.apply(options)
)
}, { (r: Repository) =>
Some(
(
(

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

@@ -41,8 +41,8 @@ trait GitRepositoryFilter {
* @param session the database session
* @return true if allow accessing to repository, otherwise false.
*/
def filter(path: String, userName: Option[String], settings: SystemSettings, isUpdating: Boolean)(implicit
session: Session
def filter(path: String, userName: Option[String], settings: SystemSettings, isUpdating: Boolean)(
implicit session: Session
): Boolean
}

View File

@@ -9,16 +9,16 @@ import profile.api._
trait IssueHook {
def created(issue: Issue, repository: RepositoryInfo)(implicit session: Session, context: Context): Unit = ()
def addedComment(commentId: Int, content: String, issue: Issue, repository: RepositoryInfo)(implicit
session: Session,
def addedComment(commentId: Int, content: String, issue: Issue, repository: RepositoryInfo)(
implicit session: Session,
context: Context
): Unit = ()
def deletedComment(commentId: Int, issue: Issue, repository: RepositoryInfo)(implicit
session: Session,
def deletedComment(commentId: Int, issue: Issue, repository: RepositoryInfo)(
implicit session: Session,
context: Context
): Unit = ()
def updatedComment(commentId: Int, content: String, issue: Issue, repository: RepositoryInfo)(implicit
session: Session,
def updatedComment(commentId: Int, content: String, issue: Issue, repository: RepositoryInfo)(
implicit session: Session,
context: Context
): Unit = ()
def closed(issue: Issue, repository: RepositoryInfo)(implicit session: Session, context: Context): Unit = ()
@@ -29,12 +29,12 @@ trait IssueHook {
assigner: Option[String],
assigned: Option[String],
oldAssigned: Option[String]
)(implicit
session: Session,
)(
implicit session: Session,
context: Context
): Unit = ()
def closedByCommitComment(issue: Issue, repository: RepositoryInfo, message: String, pusher: Account)(implicit
session: Session
def closedByCommitComment(issue: Issue, repository: RepositoryInfo, message: String, pusher: Account)(
implicit session: Session
): Unit = ()
}

View File

@@ -340,20 +340,25 @@ abstract class Plugin {
* Register plugin functionality to PluginRegistry.
*/
def initialize(registry: PluginRegistry, context: ServletContext, settings: SystemSettings): Unit = {
(images ++ images(registry, context, settings)).foreach { case (id, in) =>
registry.addImage(id, in)
(images ++ images(registry, context, settings)).foreach {
case (id, in) =>
registry.addImage(id, in)
}
(controllers ++ controllers(registry, context, settings)).foreach { case (path, controller) =>
registry.addController(path, controller)
(controllers ++ controllers(registry, context, settings)).foreach {
case (path, controller) =>
registry.addController(path, controller)
}
(anonymousAccessiblePaths ++ anonymousAccessiblePaths(registry, context, settings)).foreach { case (path) =>
registry.addAnonymousAccessiblePath(path)
(anonymousAccessiblePaths ++ anonymousAccessiblePaths(registry, context, settings)).foreach {
case (path) =>
registry.addAnonymousAccessiblePath(path)
}
(javaScripts ++ javaScripts(registry, context, settings)).foreach { case (path, script) =>
registry.addJavaScript(path, script)
(javaScripts ++ javaScripts(registry, context, settings)).foreach {
case (path, script) =>
registry.addJavaScript(path, script)
}
(renderers ++ renderers(registry, context, settings)).foreach { case (extension, renderer) =>
registry.addRenderer(extension, renderer)
(renderers ++ renderers(registry, context, settings)).foreach {
case (extension, renderer) =>
registry.addRenderer(extension, renderer)
}
(repositoryRoutings ++ repositoryRoutings(registry, context, settings)).foreach { routing =>
registry.addRepositoryRouting(routing)

View File

@@ -80,7 +80,7 @@ class PluginRegistry {
def getAnonymousAccessiblePaths(): Seq[String] = anonymousAccessiblePaths.asScala.toSeq
def addJavaScript(path: String, script: String): Unit =
javaScripts.add((path, script)) // javaScripts += ((path, script))
javaScripts.add((path, script)) //javaScripts += ((path, script))
def getJavaScript(currentPath: String): List[String] =
javaScripts.asScala.filter(x => currentPath.matches(x._1)).toList.map(_._2)
@@ -307,7 +307,7 @@ object PluginRegistry {
conn,
classLoader,
DatabaseConfig.liquiDriver,
new Module(plugin.pluginId, plugin.versions*)
new Module(plugin.pluginId, plugin.versions: _*)
)
conn.commit()

View File

@@ -13,8 +13,8 @@ trait ReceiveHook {
command: ReceiveCommand,
pusher: String,
mergePullRequest: Boolean
)(implicit
session: Session
)(
implicit session: Session
): Option[String] = None
def postReceive(
@@ -24,8 +24,8 @@ trait ReceiveHook {
command: ReceiveCommand,
pusher: String,
mergePullRequest: Boolean
)(implicit
session: Session
)(
implicit session: Session
): Unit = ()
}

View File

@@ -37,8 +37,9 @@ trait AccessTokenService {
def getAccountByAccessToken(token: String)(implicit s: Session): Option[Account] =
Accounts
.join(AccessTokens)
.filter { case (ac, t) =>
(ac.userName === t.userName) && (t.tokenHash === tokenToHash(token).bind) && (ac.removed === false.bind)
.filter {
case (ac, t) =>
(ac.userName === t.userName) && (t.tokenHash === tokenToHash(token).bind) && (ac.removed === false.bind)
}
.map { case (ac, t) => ac }
.firstOption

View File

@@ -51,8 +51,8 @@ trait AccountFederationService {
* @param preferredUserName Username
* @return Available username
*/
def findAvailableUserName(preferredUserName: Option[String], mailAddress: String)(implicit
s: Session
def findAvailableUserName(preferredUserName: Option[String], mailAddress: String)(
implicit s: Session
): Option[String] = {
preferredUserName
.flatMap(n => extractSafeStringForUserName(n))

Some files were not shown because too many files have changed in this diff Show More