mirror of
https://github.com/gitbucket/gitbucket.git
synced 2025-11-02 03:26:06 +01:00
Compare commits
175 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
176deb4930 | ||
|
|
6e26d090ed | ||
|
|
070f74e7dc | ||
|
|
bcd78b3e5b | ||
|
|
be8f45ce49 | ||
|
|
baa96408a8 | ||
|
|
d2a7548f73 | ||
|
|
31e3fc64fc | ||
|
|
01a9883847 | ||
|
|
3cf2bea01e | ||
|
|
7caf11ae83 | ||
|
|
b22bc6fef0 | ||
|
|
88533c5807 | ||
|
|
53d955f198 | ||
|
|
724550ec5f | ||
|
|
1a63eff8f9 | ||
|
|
cf1b245229 | ||
|
|
c54a76c9b4 | ||
|
|
1a7213e2f0 | ||
|
|
a386cda024 | ||
|
|
f414bb4c79 | ||
|
|
6a51c7414a | ||
|
|
9718a67a74 | ||
|
|
179f86c904 | ||
|
|
e6ae6fc17d | ||
|
|
1f42b52cfd | ||
|
|
cdff342e3d | ||
|
|
daa8b6acf2 | ||
|
|
2ae72c6db4 | ||
|
|
9e3204d546 | ||
|
|
59e560b8e1 | ||
|
|
186269f8ff | ||
|
|
e441c40429 | ||
|
|
e1fa85848c | ||
|
|
8d66348634 | ||
|
|
f16b7d2c9b | ||
|
|
0ae8cc2820 | ||
|
|
ca25e7fe90 | ||
|
|
2692e29716 | ||
|
|
b71ccfbcc5 | ||
|
|
659a157cb7 | ||
|
|
f22fab32cc | ||
|
|
631e418733 | ||
|
|
0f57da14e4 | ||
|
|
424790e686 | ||
|
|
5dc43a7f75 | ||
|
|
debb131623 | ||
|
|
e3e21e9590 | ||
|
|
42fa7ea74d | ||
|
|
8ada3dde04 | ||
|
|
d43a666dfc | ||
|
|
5b266229c3 | ||
|
|
d8b0062f2b | ||
|
|
7f0859c0a3 | ||
|
|
38c7fbf589 | ||
|
|
41335964b7 | ||
|
|
c5464b2099 | ||
|
|
dc49b0dace | ||
|
|
365f30b76b | ||
|
|
ff1bc0fbca | ||
|
|
41d55372b5 | ||
|
|
ba8e5fe3cb | ||
|
|
6e692f70b7 | ||
|
|
aab979b78b | ||
|
|
9b7856464a | ||
|
|
903008b417 | ||
|
|
515cf86e4f | ||
|
|
c0b9eb8789 | ||
|
|
fefbf92f83 | ||
|
|
006fae36c9 | ||
|
|
281f7f37d8 | ||
|
|
36c6dd979f | ||
|
|
9f44015003 | ||
|
|
e719533ed9 | ||
|
|
773221d65b | ||
|
|
6beacb6af3 | ||
|
|
f11b92d391 | ||
|
|
def222d035 | ||
|
|
5dee7a9057 | ||
|
|
c20cc1271c | ||
|
|
843d429cb2 | ||
|
|
372e17ea2b | ||
|
|
5490e82411 | ||
|
|
d30a5d2f92 | ||
|
|
ef69763749 | ||
|
|
59d1250e36 | ||
|
|
33bf0b7b31 | ||
|
|
884cd498ce | ||
|
|
e0b021cb3e | ||
|
|
57edf101fa | ||
|
|
a2bf0d86d8 | ||
|
|
32e2ea5061 | ||
|
|
e28c0592f6 | ||
|
|
60627060d5 | ||
|
|
cea4d7e15f | ||
|
|
ca1670b21a | ||
|
|
c9c72faaf1 | ||
|
|
ba42538775 | ||
|
|
39141d8996 | ||
|
|
e3dd099bbb | ||
|
|
6b37d1027f | ||
|
|
b521f43c84 | ||
|
|
7c5a9df759 | ||
|
|
04762a3e53 | ||
|
|
d2ba2df2ba | ||
|
|
0e7f34f1d7 | ||
|
|
d576bbfaed | ||
|
|
2bfdca6992 | ||
|
|
d18fdb6399 | ||
|
|
fe02605544 | ||
|
|
f86ae20791 | ||
|
|
df69f88186 | ||
|
|
8d1323f354 | ||
|
|
2f598b618b | ||
|
|
baf0b0b92c | ||
|
|
27a75250a6 | ||
|
|
15f60402a5 | ||
|
|
41c6fc90b3 | ||
|
|
34356b04a8 | ||
|
|
2ca02b6539 | ||
|
|
cd0c71dffb | ||
|
|
a59120fe19 | ||
|
|
fdc35f48ed | ||
|
|
bae9b7ddc3 | ||
|
|
3dd9b7e587 | ||
|
|
44c905bdab | ||
|
|
5214040257 | ||
|
|
7ad9f901dd | ||
|
|
f472d52954 | ||
|
|
1e752af41b | ||
|
|
3ba46c3fc6 | ||
|
|
bf83da476f | ||
|
|
6b8c4cf8d0 | ||
|
|
445329c07a | ||
|
|
8f370e19c6 | ||
|
|
736dbcfb58 | ||
|
|
c1cb7f87e0 | ||
|
|
3c14fcefc9 | ||
|
|
831badf8db | ||
|
|
5f8a6e8d24 | ||
|
|
a3981493f7 | ||
|
|
6919cf5d4d | ||
|
|
f6d1e6bdd6 | ||
|
|
a13ff89acd | ||
|
|
cd5c76279a | ||
|
|
debff5e4b8 | ||
|
|
433e207ec5 | ||
|
|
3775f6a907 | ||
|
|
10d611c0eb | ||
|
|
963bc4d672 | ||
|
|
e68a21ee30 | ||
|
|
d5c083b70f | ||
|
|
2deb9cf417 | ||
|
|
fca0cfcdc7 | ||
|
|
1466e1bdb3 | ||
|
|
dd48bc443a | ||
|
|
f455738e5f | ||
|
|
85193803cd | ||
|
|
4e90a6074a | ||
|
|
ca94fa5184 | ||
|
|
f14a7c996f | ||
|
|
989d22f4d8 | ||
|
|
400a812343 | ||
|
|
97284f1ced | ||
|
|
5e6a0d7e16 | ||
|
|
599e11245f | ||
|
|
538d714c96 | ||
|
|
953915ba2a | ||
|
|
1a2f5da055 | ||
|
|
749a469d37 | ||
|
|
c7d084321a | ||
|
|
00a61cd6cf | ||
|
|
d9c6c13c62 | ||
|
|
5260c5e889 | ||
|
|
1700f96c62 |
4
.github/ISSUE_TEMPLATE.md
vendored
4
.github/ISSUE_TEMPLATE.md
vendored
@@ -4,8 +4,12 @@
|
||||
- [ ] 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
|
||||
|
||||
|
||||
8
.github/workflows/build.yml
vendored
8
.github/workflows/build.yml
vendored
@@ -8,9 +8,9 @@ jobs:
|
||||
timeout-minutes: 30
|
||||
strategy:
|
||||
matrix:
|
||||
java: [8, 11, 17]
|
||||
java: [11, 21]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Cache
|
||||
uses: actions/cache@v3
|
||||
env:
|
||||
@@ -27,9 +27,9 @@ jobs:
|
||||
java-version: ${{ matrix.java }}
|
||||
distribution: adopt
|
||||
- name: Run tests
|
||||
run: sbt scalafmtSbtCheck scalafmtCheck test:scalafmtCheck test
|
||||
run: sbt scalafmtSbtCheck scalafmtCheckAll test
|
||||
- name: Scala 3
|
||||
run: sbt '++ 3.1.2!' update # TODO
|
||||
run: sbt '++ 3.x' update # TODO
|
||||
- name: Build executable
|
||||
run: sbt executable
|
||||
- name: Upload artifacts
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -4,6 +4,7 @@
|
||||
.ensime_cache
|
||||
.DS_Store
|
||||
.java-version
|
||||
.tmp
|
||||
|
||||
# sbt specific
|
||||
dist/*
|
||||
|
||||
@@ -3,7 +3,6 @@ updates.limit = 3
|
||||
updates.includeScala = true
|
||||
|
||||
updates.pin = [
|
||||
{ groupId = "org.eclipse.jetty", version = "9." }
|
||||
{ groupId = "org.eclipse.jgit", version = "5." }
|
||||
{ groupId = "com.zaxxer", version = "4." }
|
||||
{ groupId = "org.eclipse.jetty", version = "10." }
|
||||
{ groupId = "org.mariadb.jdbc", version = "2." }
|
||||
]
|
||||
|
||||
16
CHANGELOG.md
16
CHANGELOG.md
@@ -1,6 +1,22 @@
|
||||
# Changelog
|
||||
All changes to the project will be documented in this file.
|
||||
|
||||
## 4.40.0 - 22 Oct 2023
|
||||
- Drop Java 8 support
|
||||
- Improve git push performance
|
||||
- Show activities of all visible repositories as news feed
|
||||
- Support custom fields of issues and pull requests in search condition
|
||||
- Configurable default branch name
|
||||
|
||||
## 4.39.0 - 29 Apr 2023
|
||||
- Support enum type in custom fields of Issues and Pull requests
|
||||
- Hide large diffs by default
|
||||
- Add new options to make it possible to run GitBucket using multiple machines
|
||||
- Fix many API issues
|
||||
|
||||
## 4.38.4 - 2 Nov 2022
|
||||
- Downgrade MariaDB JDBC drive to avoid unknown error
|
||||
|
||||
## 4.38.3 - 30 Oct 2022
|
||||
- Fix several issues around multiple assignees in issues and pull requests
|
||||
- Fix IllegalStateException when returning unknown avatar image
|
||||
|
||||
30
README.md
30
README.md
@@ -59,29 +59,13 @@ 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.38.x
|
||||
What's New in 4.40.x
|
||||
-------------
|
||||
## 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
|
||||
## 4.40.0 - 22 Oct 2023
|
||||
- Drop Java 8 support
|
||||
- Improve git push performance
|
||||
- Show activities of all visible repositories as news feed
|
||||
- Support custom fields of issues and pull requests in search condition
|
||||
- Configurable default branch name
|
||||
|
||||
See the [change log](CHANGELOG.md) for all of the updates.
|
||||
|
||||
108
build.sbt
108
build.sbt
@@ -1,12 +1,12 @@
|
||||
import com.typesafe.sbt.license.{DepModuleInfo, LicenseInfo}
|
||||
import sbtlicensereport.license.{DepModuleInfo, LicenseInfo}
|
||||
import com.jsuereth.sbtpgp.PgpKeys._
|
||||
|
||||
val Organization = "io.github.gitbucket"
|
||||
val Name = "gitbucket"
|
||||
val GitBucketVersion = "4.38.3"
|
||||
val ScalatraVersion = "2.8.4"
|
||||
val JettyVersion = "9.4.49.v20220914"
|
||||
val JgitVersion = "5.13.1.202206130422-r"
|
||||
val GitBucketVersion = "4.40.0"
|
||||
val ScalatraVersion = "3.0.0"
|
||||
val JettyVersion = "10.0.17"
|
||||
val JgitVersion = "6.7.0.202309050840-r"
|
||||
|
||||
lazy val root = (project in file("."))
|
||||
.enablePlugins(SbtTwirl, ScalatraPlugin)
|
||||
@@ -15,7 +15,9 @@ sourcesInBase := false
|
||||
organization := Organization
|
||||
name := Name
|
||||
version := GitBucketVersion
|
||||
scalaVersion := "2.13.10"
|
||||
scalaVersion := "2.13.12"
|
||||
|
||||
crossScalaVersions += "3.3.1"
|
||||
|
||||
// scalafmtOnCompile := true
|
||||
|
||||
@@ -30,56 +32,47 @@ resolvers ++= Seq(
|
||||
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",
|
||||
"org.scalatra" %% "scalatra-javax" % ScalatraVersion,
|
||||
"org.scalatra" %% "scalatra-json-javax" % ScalatraVersion,
|
||||
"org.scalatra" %% "scalatra-forms-javax" % ScalatraVersion,
|
||||
"org.json4s" %% "json4s-jackson" % "4.0.6",
|
||||
"commons-io" % "commons-io" % "2.14.0",
|
||||
"io.github.gitbucket" % "solidbase" % "1.0.5",
|
||||
"io.github.gitbucket" % "markedj" % "1.0.17",
|
||||
"org.apache.commons" % "commons-compress" % "1.21",
|
||||
"io.github.gitbucket" % "markedj" % "1.0.18",
|
||||
"org.apache.commons" % "commons-compress" % "1.24.0",
|
||||
"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,
|
||||
"commons-net" % "commons-net" % "3.10.0",
|
||||
"org.apache.httpcomponents" % "httpclient" % "4.5.14",
|
||||
"org.apache.sshd" % "apache-sshd" % "2.11.0" exclude ("org.slf4j", "slf4j-jdk14") exclude ("org.apache.sshd", "sshd-mina") exclude ("org.apache.sshd", "sshd-netty"),
|
||||
"org.apache.tika" % "tika-core" % "2.9.1",
|
||||
"com.github.takezoe" %% "blocking-slick" % "0.0.14",
|
||||
"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",
|
||||
"org.mariadb.jdbc" % "mariadb-java-client" % "2.7.6",
|
||||
"org.postgresql" % "postgresql" % "42.6.0",
|
||||
"ch.qos.logback" % "logback-classic" % "1.4.11",
|
||||
"com.zaxxer" % "HikariCP" % "4.0.3" exclude ("org.slf4j", "slf4j-api"),
|
||||
"com.typesafe" % "config" % "1.4.2",
|
||||
"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.18",
|
||||
"net.coobird" % "thumbnailator" % "0.4.20",
|
||||
"com.github.zafarkhaja" % "java-semver" % "0.9.0",
|
||||
"com.nimbusds" % "oauth2-oidc-sdk" % "10.1",
|
||||
"com.nimbusds" % "oauth2-oidc-sdk" % "11.4",
|
||||
"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",
|
||||
"org.scalatra" %% "scalatra-scalatest-javax" % ScalatraVersion % "test",
|
||||
"org.mockito" % "mockito-core" % "5.6.0" % "test",
|
||||
"com.dimafeng" %% "testcontainers-scala" % "0.41.0" % "test",
|
||||
"org.testcontainers" % "mysql" % "1.19.1" % "test",
|
||||
"org.testcontainers" % "postgresql" % "1.19.1" % "test",
|
||||
"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"
|
||||
"org.kohsuke" % "github-api" % "1.317" % "test"
|
||||
)
|
||||
|
||||
libraryDependencies ~= {
|
||||
_.map {
|
||||
case x if x.name == "twirl-api" =>
|
||||
x cross CrossVersion.for3Use2_13
|
||||
case x =>
|
||||
x
|
||||
}
|
||||
}
|
||||
|
||||
// Compiler settings
|
||||
scalacOptions := Seq(
|
||||
"-deprecation",
|
||||
@@ -89,7 +82,15 @@ scalacOptions := Seq(
|
||||
"-Wunused:imports",
|
||||
"-Wconf:cat=unused&src=twirl/.*:s,cat=unused&src=scala/gitbucket/core/model/[^/]+\\.scala:s"
|
||||
)
|
||||
compile / javacOptions ++= Seq("-target", "8", "-source", "8")
|
||||
scalacOptions ++= {
|
||||
scalaBinaryVersion.value match {
|
||||
case "2.13" =>
|
||||
Seq("-Xsource:3")
|
||||
case _ =>
|
||||
Nil
|
||||
}
|
||||
}
|
||||
compile / javacOptions ++= Seq("-target", "11", "-source", "11")
|
||||
Jetty / javaOptions += "-Dlogback.configurationFile=/logback-dev.xml"
|
||||
|
||||
// Test settings
|
||||
@@ -123,15 +124,14 @@ 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-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"
|
||||
"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"
|
||||
)
|
||||
|
||||
// Run package task before test to generate target/webapp for integration test
|
||||
@@ -159,8 +159,7 @@ 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 "javax/") || (name startsWith "org/") || (name startsWith "META-INF/services/"))
|
||||
}
|
||||
|
||||
// include original war file
|
||||
@@ -185,7 +184,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.URL(url).openStream, pluginsDir / url.substring(url.lastIndexOf("/") + 1))
|
||||
IO transfer (new java.net.URI(url).toURL.openStream, pluginsDir / url.substring(url.lastIndexOf("/") + 1))
|
||||
case _ => ()
|
||||
}
|
||||
}
|
||||
@@ -286,6 +285,9 @@ Test / testOptions ++= {
|
||||
}
|
||||
|
||||
Jetty / javaOptions ++= Seq(
|
||||
"-Dlogback.configurationFile=/logback-dev.xml",
|
||||
"-Xdebug",
|
||||
"-Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=8000"
|
||||
"-Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=8000",
|
||||
"-Dorg.eclipse.jetty.annotations.AnnotationParser.LEVEL=OFF",
|
||||
//"-Ddev-features=keep-session"
|
||||
)
|
||||
|
||||
21
doc/build.md
21
doc/build.md
@@ -1,7 +1,7 @@
|
||||
How to build and run from the source tree
|
||||
========
|
||||
|
||||
First of all, Install [sbt](http://www.scala-sbt.org/index.html).
|
||||
First of all, Install [sbt](https://www.scala-sbt.org/index.html).
|
||||
|
||||
```shell
|
||||
$ brew install sbt
|
||||
@@ -18,7 +18,21 @@ $ 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`.
|
||||
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.
|
||||
|
||||
Build war file
|
||||
--------
|
||||
@@ -37,7 +51,8 @@ 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
|
||||
---------
|
||||
|
||||
@@ -5,13 +5,17 @@ GitBucket persists all data into __HOME/.gitbucket__ in default (In 1.9 or befor
|
||||
This directory has following structure:
|
||||
|
||||
```
|
||||
* /HOME/gitbucket
|
||||
* /HOME/.gitbucket
|
||||
* gitbucket.conf
|
||||
* database.conf
|
||||
* activity.log
|
||||
* data.mv.db, data.trace.db (H2 data files if the default embed H2 is used)
|
||||
* /repositories
|
||||
* /USER_NAME
|
||||
* /REPO_NAME.git (substance of repository. GitServlet sees this directory)
|
||||
* /REPO_NAME.wiki.git (wiki repository)
|
||||
* /REPO_NAME
|
||||
* /issues (files which are attached to issue)
|
||||
* /issues (files attached to issue)
|
||||
* /lfs (LFS managed files)
|
||||
* /data
|
||||
* /USER_NAME
|
||||
@@ -20,6 +24,8 @@ 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)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
Mapping and 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.
|
||||
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.
|
||||
|
||||
At first, define the mapping as following:
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
sbt.version=1.7.2
|
||||
sbt.version=1.9.6
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
scalacOptions ++= Seq("-unchecked", "-deprecation", "-feature")
|
||||
|
||||
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")
|
||||
addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.5.2")
|
||||
addSbtPlugin("com.typesafe.play" % "sbt-twirl" % "1.6.1")
|
||||
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "2.1.3")
|
||||
addSbtPlugin("org.scalatra.sbt" % "sbt-scalatra" % "1.0.4")
|
||||
addSbtPlugin("com.github.sbt" % "sbt-pgp" % "2.2.1")
|
||||
addSbtPlugin("com.github.sbt" % "sbt-license-report" % "1.6.1")
|
||||
addSbtPlugin("org.scoverage" % "sbt-scoverage" % "2.0.9")
|
||||
|
||||
addDependencyTreePlugin
|
||||
|
||||
@@ -65,9 +65,15 @@ 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) {
|
||||
@@ -149,7 +155,7 @@ public class JettyLauncher {
|
||||
}
|
||||
|
||||
if (connectorsSet.contains(Connectors.HTTPS)) {
|
||||
final SslContextFactory sslContextFactory = new SslContextFactory.Server();
|
||||
final SslContextFactory.Server 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" +
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
notifications:1.11.0
|
||||
gist:4.22.0
|
||||
gist:4.23.0
|
||||
emoji:4.6.0
|
||||
pages:1.10.0
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
</appender>
|
||||
|
||||
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
|
||||
<file>gitbucket.log</file>
|
||||
<file>.tmp/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>
|
||||
|
||||
6
src/main/resources/update/gitbucket-core_4.39.xml
Normal file
6
src/main/resources/update/gitbucket-core_4.39.xml
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<changeSet>
|
||||
<addColumn tableName="CUSTOM_FIELD">
|
||||
<column name="CONSTRAINTS" type="varchar(200)" nullable="true"/>
|
||||
</addColumn>
|
||||
</changeSet>
|
||||
@@ -114,5 +114,8 @@ 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.3"),
|
||||
new Version("4.38.4"),
|
||||
new Version("4.39.0", new LiquibaseMigration("update/gitbucket-core_4.39.xml")),
|
||||
new Version("4.40.0")
|
||||
)
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
package gitbucket.core.api
|
||||
|
||||
/**
|
||||
* https://developer.github.com/v3/issues/labels/#add-labels-to-an-issue
|
||||
*/
|
||||
case class AddLabelsToAnIssue(labels: Seq[String])
|
||||
@@ -61,7 +61,7 @@ object ApiRepository {
|
||||
watchers = 0,
|
||||
forks = 0,
|
||||
`private` = false,
|
||||
default_branch = "master",
|
||||
default_branch = "main",
|
||||
owner = owner,
|
||||
has_issues = true
|
||||
)
|
||||
|
||||
@@ -185,7 +185,6 @@ 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))),
|
||||
@@ -196,11 +195,6 @@ 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(
|
||||
@@ -268,7 +262,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
gitbucket.core.account.html.activity(
|
||||
account,
|
||||
if (account.isGroupAccount) Nil else getGroupsByUserName(userName),
|
||||
getActivitiesByUser(userName, true),
|
||||
getActivitiesByUser(userName, publicOnly = true),
|
||||
extraMailAddresses
|
||||
)
|
||||
|
||||
@@ -813,7 +807,8 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
form.description,
|
||||
form.isPrivate,
|
||||
form.initOption,
|
||||
form.sourceUrl
|
||||
form.sourceUrl,
|
||||
context.settings.defaultBranch
|
||||
)
|
||||
// redirect to the repository
|
||||
redirect(s"/${form.owner}/${form.name}")
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package gitbucket.core.controller
|
||||
|
||||
import java.io.{File, FileInputStream}
|
||||
|
||||
import java.io.{File, FileInputStream, FileOutputStream}
|
||||
import gitbucket.core.api.{ApiError, JsonFormat}
|
||||
import gitbucket.core.model.Account
|
||||
import gitbucket.core.service.{AccountService, RepositoryService, SystemSettingsService}
|
||||
@@ -14,9 +13,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
|
||||
@@ -29,6 +28,9 @@ 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.
|
||||
@@ -93,8 +95,16 @@ abstract class ControllerBase
|
||||
}
|
||||
}
|
||||
|
||||
private def LoginAccount: Option[Account] =
|
||||
request.getAs[Account](Keys.Session.LoginAccount).orElse(session.getAs[Account](Keys.Session.LoginAccount))
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
def ajaxGet(path: String)(action: => Any): Route =
|
||||
super.get(path) {
|
||||
@@ -277,6 +287,47 @@ 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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -6,6 +6,7 @@ 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
|
||||
@@ -42,7 +43,7 @@ trait DashboardControllerBase extends ControllerBase {
|
||||
withoutPhysicalInfo = true,
|
||||
limit = context.settings.basicBehavior.limitVisibleRepositories
|
||||
)
|
||||
html.repos(getGroupNames(loginAccount.userName), repos, repos)
|
||||
html.repos(getGroupNames(loginAccount.userName), repos, repos, isNewsFeedEnabled)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -130,7 +131,8 @@ trait DashboardControllerBase extends ControllerBase {
|
||||
None,
|
||||
withoutPhysicalInfo = true,
|
||||
limit = context.settings.basicBehavior.limitVisibleRepositories
|
||||
)
|
||||
),
|
||||
isNewsFeedEnabled
|
||||
)
|
||||
}
|
||||
|
||||
@@ -172,7 +174,8 @@ trait DashboardControllerBase extends ControllerBase {
|
||||
None,
|
||||
withoutPhysicalInfo = true,
|
||||
limit = context.settings.basicBehavior.limitVisibleRepositories
|
||||
)
|
||||
),
|
||||
isNewsFeedEnabled
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
package gitbucket.core.controller
|
||||
|
||||
import java.net.URI
|
||||
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
|
||||
@@ -13,6 +14,8 @@ import gitbucket.core.view.helpers._
|
||||
import org.scalatra.Ok
|
||||
import org.scalatra.forms._
|
||||
|
||||
import gitbucket.core.service.ActivityService._
|
||||
|
||||
class IndexController
|
||||
extends IndexControllerBase
|
||||
with RepositoryService
|
||||
@@ -57,30 +60,41 @@ trait IndexControllerBase extends ControllerBase {
|
||||
//
|
||||
// case class SearchForm(query: String, owner: String, repository: String)
|
||||
|
||||
case class OidcContext(state: State, nonce: Nonce, redirectBackURI: String)
|
||||
case class OidcAuthContext(state: State, nonce: Nonce, redirectBackURI: String)
|
||||
case class OidcSessionContext(token: JWT)
|
||||
|
||||
get("/") {
|
||||
context.loginAccount
|
||||
.map { account =>
|
||||
val visibleOwnerSet: Set[String] = Set(account.userName) ++ getGroupsByUserName(account.userName)
|
||||
gitbucket.core.html.index(
|
||||
getRecentActivitiesByOwners(visibleOwnerSet),
|
||||
getVisibleRepositories(
|
||||
if (!isNewsFeedEnabled) {
|
||||
redirect("/dashboard/repos")
|
||||
} else {
|
||||
val repos = getVisibleRepositories(
|
||||
Some(account),
|
||||
None,
|
||||
withoutPhysicalInfo = true,
|
||||
limit = context.settings.basicBehavior.limitVisibleRepositories
|
||||
),
|
||||
showBannerToCreatePersonalAccessToken = hasAccountFederation(account.userName) && !hasAccessToken(
|
||||
account.userName
|
||||
limit = false
|
||||
)
|
||||
)
|
||||
|
||||
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(
|
||||
getRecentPublicActivities(),
|
||||
getVisibleRepositories(None, withoutPhysicalInfo = true),
|
||||
showBannerToCreatePersonalAccessToken = false
|
||||
activities = getRecentPublicActivities(),
|
||||
recentRepositories = getVisibleRepositories(None, withoutPhysicalInfo = true),
|
||||
showBannerToCreatePersonalAccessToken = false,
|
||||
enableNewsFeed = isNewsFeedEnabled
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -120,8 +134,8 @@ trait IndexControllerBase extends ControllerBase {
|
||||
case _ => "/"
|
||||
}
|
||||
session.setAttribute(
|
||||
Keys.Session.OidcContext,
|
||||
OidcContext(authenticationRequest.getState, authenticationRequest.getNonce, redirectBackURI)
|
||||
Keys.Session.OidcAuthContext,
|
||||
OidcAuthContext(authenticationRequest.getState, authenticationRequest.getNonce, redirectBackURI)
|
||||
)
|
||||
redirect(authenticationRequest.toURI.toString)
|
||||
} getOrElse {
|
||||
@@ -135,10 +149,12 @@ trait IndexControllerBase extends ControllerBase {
|
||||
get("/signin/oidc") {
|
||||
context.settings.oidc.map { oidc =>
|
||||
val redirectURI = new URI(s"$baseUrl/signin/oidc")
|
||||
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)
|
||||
session.get(Keys.Session.OidcAuthContext) match {
|
||||
case Some(context: OidcAuthContext) =>
|
||||
authenticate(params.toMap, redirectURI, context.state, context.nonce, oidc).map {
|
||||
case (jwt, account) =>
|
||||
session.setAttribute(Keys.Session.OidcSessionContext, OidcSessionContext(jwt))
|
||||
signin(account, context.redirectBackURI)
|
||||
} orElse {
|
||||
flash.update("error", "Sorry, authentication failed. Please try again.")
|
||||
session.invalidate()
|
||||
@@ -155,7 +171,19 @@ trait IndexControllerBase extends ControllerBase {
|
||||
}
|
||||
|
||||
get("/signout") {
|
||||
session.invalidate
|
||||
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()
|
||||
}
|
||||
redirect("/")
|
||||
}
|
||||
|
||||
@@ -178,6 +206,9 @@ 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)) {
|
||||
@@ -201,7 +232,7 @@ trait IndexControllerBase extends ControllerBase {
|
||||
org.json4s.jackson.Serialization.write(
|
||||
Map(
|
||||
"options" -> (
|
||||
getAllUsers(false)
|
||||
getAllUsers(includeRemoved = false)
|
||||
.withFilter { t =>
|
||||
(user, group) match {
|
||||
case (true, true) => true
|
||||
@@ -234,7 +265,7 @@ trait IndexControllerBase extends ControllerBase {
|
||||
} getOrElse ""
|
||||
})
|
||||
|
||||
// TODO Move to RepositoryViewrController?
|
||||
// TODO Move to RepositoryViewerController?
|
||||
get("/:owner/:repository/search")(referrersOnly { repository =>
|
||||
val query = params.getOrElse("q", "").trim
|
||||
val target = params.getOrElse("type", "code")
|
||||
@@ -248,8 +279,8 @@ trait IndexControllerBase extends ControllerBase {
|
||||
target.toLowerCase match {
|
||||
case "issues" =>
|
||||
gitbucket.core.search.html.issues(
|
||||
if (query.nonEmpty) searchIssues(repository.owner, repository.name, query, false) else Nil,
|
||||
false,
|
||||
if (query.nonEmpty) searchIssues(repository.owner, repository.name, query, pullRequest = false) else Nil,
|
||||
pullRequest = false,
|
||||
query,
|
||||
page,
|
||||
repository
|
||||
@@ -257,8 +288,8 @@ trait IndexControllerBase extends ControllerBase {
|
||||
|
||||
case "pulls" =>
|
||||
gitbucket.core.search.html.issues(
|
||||
if (query.nonEmpty) searchIssues(repository.owner, repository.name, query, true) else Nil,
|
||||
true,
|
||||
if (query.nonEmpty) searchIssues(repository.owner, repository.name, query, pullRequest = true) else Nil,
|
||||
pullRequest = true,
|
||||
query,
|
||||
page,
|
||||
repository
|
||||
@@ -293,15 +324,15 @@ trait IndexControllerBase extends ControllerBase {
|
||||
)
|
||||
|
||||
val repositories = {
|
||||
context.settings.basicBehavior.limitVisibleRepositories match {
|
||||
case true =>
|
||||
getVisibleRepositories(
|
||||
context.loginAccount,
|
||||
None,
|
||||
withoutPhysicalInfo = true,
|
||||
limit = false
|
||||
)
|
||||
case false => visibleRepositories
|
||||
if (context.settings.basicBehavior.limitVisibleRepositories) {
|
||||
getVisibleRepositories(
|
||||
context.loginAccount,
|
||||
None,
|
||||
withoutPhysicalInfo = true,
|
||||
limit = false
|
||||
)
|
||||
} else {
|
||||
visibleRepositories
|
||||
}
|
||||
}.filter { repository =>
|
||||
repository.name.toLowerCase.indexOf(query) >= 0 || repository.owner.toLowerCase.indexOf(query) >= 0
|
||||
|
||||
@@ -89,10 +89,13 @@ trait IssuesControllerBase extends ControllerBase {
|
||||
|
||||
get("/:owner/:repository/issues")(referrersOnly { repository =>
|
||||
val q = request.getParameter("q")
|
||||
if (Option(q).exists(_.contains("is:pr"))) {
|
||||
redirect(s"/${repository.owner}/${repository.name}/pulls?q=${StringUtil.urlEncode(q)}")
|
||||
} else {
|
||||
searchIssues(repository)
|
||||
Option(q) match {
|
||||
case Some(filter) if filter.contains("is:pr") =>
|
||||
redirect(s"/${repository.owner}/${repository.name}/pulls?q=${StringUtil.urlEncode(q)}")
|
||||
case Some(filter) =>
|
||||
searchIssues(repository, IssueSearchCondition(filter), IssueSearchCondition.page(request))
|
||||
case None =>
|
||||
searchIssues(repository, IssueSearchCondition(request), IssueSearchCondition.page(request))
|
||||
}
|
||||
})
|
||||
|
||||
@@ -531,10 +534,7 @@ trait IssuesControllerBase extends ControllerBase {
|
||||
}
|
||||
}
|
||||
|
||||
private def searchIssues(repository: RepositoryService.RepositoryInfo) = {
|
||||
val page = IssueSearchCondition.page(request)
|
||||
// retrieve search condition
|
||||
val condition = IssueSearchCondition(request)
|
||||
private def searchIssues(repository: RepositoryService.RepositoryInfo, condition: IssueSearchCondition, page: Int) = {
|
||||
// search issues
|
||||
val issues =
|
||||
searchIssue(
|
||||
|
||||
@@ -102,10 +102,13 @@ trait PullRequestsControllerBase extends ControllerBase {
|
||||
|
||||
get("/:owner/:repository/pulls")(referrersOnly { repository =>
|
||||
val q = request.getParameter("q")
|
||||
if (Option(q).exists(_.contains("is:issue"))) {
|
||||
redirect(s"/${repository.owner}/${repository.name}/issues?q=" + StringUtil.urlEncode(q))
|
||||
} else {
|
||||
searchPullRequests(None, repository)
|
||||
Option(q) match {
|
||||
case Some(filter) if filter.contains("is:issue") =>
|
||||
redirect(s"/${repository.owner}/${repository.name}/issues?q=${StringUtil.urlEncode(q)}")
|
||||
case Some(filter) =>
|
||||
searchPullRequests(repository, IssueSearchCondition(filter), IssueSearchCondition.page(request))
|
||||
case None =>
|
||||
searchPullRequests(repository, IssueSearchCondition(request), IssueSearchCondition.page(request))
|
||||
}
|
||||
})
|
||||
|
||||
@@ -690,10 +693,11 @@ trait PullRequestsControllerBase extends ControllerBase {
|
||||
html.proposals(proposedBranches, targetRepository, repository)
|
||||
})
|
||||
|
||||
private def searchPullRequests(userName: Option[String], repository: RepositoryService.RepositoryInfo) = {
|
||||
val page = IssueSearchCondition.page(request)
|
||||
// retrieve search condition
|
||||
val condition = IssueSearchCondition(request)
|
||||
private def searchPullRequests(
|
||||
repository: RepositoryService.RepositoryInfo,
|
||||
condition: IssueSearchCondition,
|
||||
page: Int
|
||||
) = {
|
||||
// search issues
|
||||
val issues = searchIssue(
|
||||
condition,
|
||||
|
||||
@@ -126,6 +126,7 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
||||
case class CustomFieldForm(
|
||||
fieldName: String,
|
||||
fieldType: String,
|
||||
constraints: Option[String],
|
||||
enableForIssues: Boolean,
|
||||
enableForPullRequests: Boolean
|
||||
)
|
||||
@@ -133,6 +134,7 @@ 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)
|
||||
@@ -511,6 +513,7 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
||||
repository.name,
|
||||
form.fieldName,
|
||||
form.fieldType,
|
||||
if (form.fieldType == "enum") form.constraints else None,
|
||||
form.enableForIssues,
|
||||
form.enableForPullRequests
|
||||
)
|
||||
@@ -533,6 +536,7 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
||||
params("fieldId").toInt,
|
||||
form.fieldName,
|
||||
form.fieldType,
|
||||
if (form.fieldType == "enum") form.constraints else None,
|
||||
form.enableForIssues,
|
||||
form.enableForPullRequests
|
||||
)
|
||||
|
||||
@@ -124,7 +124,8 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
|
||||
)(Upload.apply),
|
||||
"repositoryViewer" -> mapping(
|
||||
"maxFiles" -> trim(label("Max files", number(required)))
|
||||
)(RepositoryViewerSettings.apply)
|
||||
)(RepositoryViewerSettings.apply),
|
||||
"defaultBranch" -> trim(label("Default branch", text(required)))
|
||||
)(SystemSettings.apply).verifying { settings =>
|
||||
Vector(
|
||||
if (settings.ssh.enabled && settings.baseUrl.isEmpty) {
|
||||
|
||||
@@ -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,46 +62,56 @@ trait WikiControllerBase extends ControllerBase {
|
||||
)(WikiPageEditForm.apply)
|
||||
|
||||
get("/:owner/:repository/wiki")(referrersOnly { repository =>
|
||||
getWikiPage(repository.owner, repository.name, "Home").map { page =>
|
||||
val branch = getWikiBranch(repository.owner, repository.name)
|
||||
|
||||
getWikiPage(repository.owner, repository.name, "Home", branch).map { page =>
|
||||
html.page(
|
||||
"Home",
|
||||
page,
|
||||
getWikiPageList(repository.owner, repository.name),
|
||||
getWikiPageList(repository.owner, repository.name, branch),
|
||||
repository,
|
||||
isEditable(repository),
|
||||
getWikiPage(repository.owner, repository.name, "_Sidebar"),
|
||||
getWikiPage(repository.owner, repository.name, "_Footer")
|
||||
getWikiPage(repository.owner, repository.name, "_Sidebar", branch),
|
||||
getWikiPage(repository.owner, repository.name, "_Footer", branch)
|
||||
)
|
||||
} 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).map { page =>
|
||||
getWikiPage(repository.owner, repository.name, pageName, branch).map { page =>
|
||||
html.page(
|
||||
pageName,
|
||||
page,
|
||||
getWikiPageList(repository.owner, repository.name),
|
||||
getWikiPageList(repository.owner, repository.name, branch),
|
||||
repository,
|
||||
isEditable(repository),
|
||||
getWikiPage(repository.owner, repository.name, "_Sidebar"),
|
||||
getWikiPage(repository.owner, repository.name, "_Footer")
|
||||
getWikiPage(repository.owner, repository.name, "_Sidebar", branch),
|
||||
getWikiPage(repository.owner, repository.name, "_Footer", branch)
|
||||
)
|
||||
} 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, "master", path = pageName + ".md") match {
|
||||
JGitUtil.getCommitLog(git, branch, 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("\\.\\.\\.")
|
||||
@@ -141,8 +151,9 @@ trait WikiControllerBase extends ControllerBase {
|
||||
if (isEditable(repository)) {
|
||||
val pageName = StringUtil.urlDecode(params("page"))
|
||||
val Array(from, to) = params("commitId").split("\\.\\.\\.")
|
||||
val branch = getWikiBranch(repository.owner, repository.name)
|
||||
|
||||
if (revertWikiPage(repository.owner, repository.name, from, to, loginAccount, Some(pageName))) {
|
||||
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.")
|
||||
@@ -159,8 +170,9 @@ trait WikiControllerBase extends ControllerBase {
|
||||
loginAccount =>
|
||||
if (isEditable(repository)) {
|
||||
val Array(from, to) = params("commitId").split("\\.\\.\\.")
|
||||
val branch = getWikiBranch(repository.owner, repository.name)
|
||||
|
||||
if (revertWikiPage(repository.owner, repository.name, from, to, loginAccount, None)) {
|
||||
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.")
|
||||
@@ -173,7 +185,9 @@ trait WikiControllerBase extends ControllerBase {
|
||||
get("/:owner/:repository/wiki/:page/_edit")(readableUsersOnly { repository =>
|
||||
if (isEditable(repository)) {
|
||||
val pageName = StringUtil.urlDecode(params("page"))
|
||||
html.edit(pageName, getWikiPage(repository.owner, repository.name, pageName), repository)
|
||||
val branch = getWikiBranch(repository.owner, repository.name)
|
||||
|
||||
html.edit(pageName, getWikiPage(repository.owner, repository.name, pageName, branch), repository)
|
||||
} else Unauthorized()
|
||||
})
|
||||
|
||||
@@ -280,7 +294,8 @@ trait WikiControllerBase extends ControllerBase {
|
||||
})
|
||||
|
||||
get("/:owner/:repository/wiki/_pages")(referrersOnly { repository =>
|
||||
html.pages(getWikiPageList(repository.owner, repository.name), repository, isEditable(repository))
|
||||
val branch = getWikiBranch(repository.owner, repository.name)
|
||||
html.pages(getWikiPageList(repository.owner, repository.name, branch), repository, isEditable(repository))
|
||||
})
|
||||
|
||||
get("/:owner/:repository/wiki/_history")(referrersOnly { repository =>
|
||||
@@ -309,13 +324,18 @@ trait WikiControllerBase extends ControllerBase {
|
||||
value: String,
|
||||
params: Map[String, Seq[String]],
|
||||
messages: Messages
|
||||
): Option[String] =
|
||||
getWikiPageList(params.value("owner"), params.value("repository"))
|
||||
): Option[String] = {
|
||||
val owner = params.value("owner")
|
||||
val repository = params.value("repository")
|
||||
val branch = getWikiBranch(owner, repository)
|
||||
|
||||
getWikiPageList(owner, repository, branch)
|
||||
.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.")
|
||||
@@ -326,7 +346,7 @@ trait WikiControllerBase extends ControllerBase {
|
||||
}
|
||||
}
|
||||
|
||||
private def notReservedPageName(value: String) = !(Array[String]("_Sidebar", "_Footer") contains value)
|
||||
private def notReservedPageName(value: String): Boolean = !(Array[String]("_Sidebar", "_Footer") contains value)
|
||||
|
||||
private def conflictForNew: Constraint = new Constraint() {
|
||||
override def validate(name: String, value: String, messages: Messages): Option[String] = {
|
||||
@@ -344,7 +364,13 @@ trait WikiControllerBase extends ControllerBase {
|
||||
}
|
||||
}
|
||||
|
||||
private def targetWikiPage = getWikiPage(params("owner"), params("repository"), params("pageName"))
|
||||
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 isEditable(repository: RepositoryInfo)(implicit context: Context): Boolean = {
|
||||
repository.repository.options.wikiOption match {
|
||||
|
||||
@@ -57,22 +57,27 @@ 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")(referrersOnly { repository =>
|
||||
post("/api/v3/repos/:owner/:repository/git/refs")(writableUsersOnly { 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())
|
||||
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) {
|
||||
git =>
|
||||
val ref = git.getRepository.findRef(data.ref)
|
||||
if (ref == null) {
|
||||
val update = git.getRepository.updateRef(data.ref)
|
||||
update.setNewObjectId(ObjectId.fromString(data.sha))
|
||||
val result = update.update()
|
||||
result match {
|
||||
case Result.NEW =>
|
||||
JsonFormat(
|
||||
ApiRef
|
||||
.fromRef(RepositoryName(repository.owner, repository.name), git.getRepository.findRef(data.ref))
|
||||
)
|
||||
case _ => UnprocessableEntity(result.name())
|
||||
}
|
||||
} else {
|
||||
UnprocessableEntity("Ref already exists.")
|
||||
}
|
||||
} else {
|
||||
UnprocessableEntity("Ref already exists.")
|
||||
}
|
||||
}
|
||||
} getOrElse BadRequest()
|
||||
})
|
||||
@@ -85,7 +90,7 @@ trait ApiGitReferenceControllerBase extends ControllerBase {
|
||||
val refName = multiParams("splat").mkString("/")
|
||||
extractFromJsonBody[UpdateARef].map {
|
||||
data =>
|
||||
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.owner))) { git =>
|
||||
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
|
||||
val ref = git.getRepository.findRef(refName)
|
||||
if (ref == null) {
|
||||
UnprocessableEntity("Ref does not exist.")
|
||||
@@ -96,7 +101,7 @@ trait ApiGitReferenceControllerBase extends ControllerBase {
|
||||
val result = update.update()
|
||||
result match {
|
||||
case Result.FORCED | Result.FAST_FORWARD | Result.NO_CHANGE =>
|
||||
JsonFormat(ApiRef.fromRef(RepositoryName(repository), update.getRef))
|
||||
JsonFormat(ApiRef.fromRef(RepositoryName(repository), git.getRepository.findRef(refName)))
|
||||
case _ => UnprocessableEntity(result.name())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,7 +85,7 @@ trait ApiIssueCommentControllerBase extends ControllerBase {
|
||||
* iv. Delete a comment
|
||||
* https://docs.github.com/en/rest/reference/issues#delete-an-issue-comment
|
||||
*/
|
||||
delete("/api/v3/repos/:owner/:repo/issues/comments/:id")(readableUsersOnly { repository =>
|
||||
delete("/api/v3/repos/:owner/:repository/issues/comments/:id")(readableUsersOnly { repository =>
|
||||
val maybeDeleteResponse: Option[Either[ActionResult, Option[Int]]] =
|
||||
for {
|
||||
commentId <- params("id").toIntOpt
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
package gitbucket.core.controller.api
|
||||
import gitbucket.core.api.{ApiError, ApiLabel, CreateALabel, JsonFormat}
|
||||
import gitbucket.core.api.{AddLabelsToAnIssue, ApiError, ApiLabel, CreateALabel, JsonFormat}
|
||||
import gitbucket.core.controller.ControllerBase
|
||||
import gitbucket.core.service._
|
||||
import gitbucket.core.util.Implicits._
|
||||
@@ -121,10 +121,10 @@ trait ApiIssueLabelControllerBase extends ControllerBase {
|
||||
*/
|
||||
post("/api/v3/repos/:owner/:repository/issues/:id/labels")(writableUsersOnly { repository =>
|
||||
JsonFormat(for {
|
||||
data <- extractFromJsonBody[Seq[String]]
|
||||
data <- extractFromJsonBody[AddLabelsToAnIssue]
|
||||
issueId <- params("id").toIntOpt
|
||||
} yield {
|
||||
data.map { labelName =>
|
||||
data.labels.map { labelName =>
|
||||
val label = getLabel(repository.owner, repository.name, labelName).getOrElse(
|
||||
getLabel(
|
||||
repository.owner,
|
||||
@@ -160,11 +160,11 @@ trait ApiIssueLabelControllerBase extends ControllerBase {
|
||||
*/
|
||||
put("/api/v3/repos/:owner/:repository/issues/:id/labels")(writableUsersOnly { repository =>
|
||||
JsonFormat(for {
|
||||
data <- extractFromJsonBody[Seq[String]]
|
||||
data <- extractFromJsonBody[AddLabelsToAnIssue]
|
||||
issueId <- params("id").toIntOpt
|
||||
} yield {
|
||||
deleteAllIssueLabels(repository.owner, repository.name, issueId, true)
|
||||
data.map { labelName =>
|
||||
data.labels.map { labelName =>
|
||||
val label = getLabel(repository.owner, repository.name, labelName).getOrElse(
|
||||
getLabel(
|
||||
repository.owner,
|
||||
|
||||
@@ -93,7 +93,8 @@ trait ApiRepositoryControllerBase extends ControllerBase {
|
||||
data.name,
|
||||
data.description,
|
||||
data.`private`,
|
||||
data.auto_init
|
||||
data.auto_init,
|
||||
context.settings.defaultBranch
|
||||
)
|
||||
Await.result(f, Duration.Inf)
|
||||
|
||||
@@ -130,7 +131,8 @@ trait ApiRepositoryControllerBase extends ControllerBase {
|
||||
data.name,
|
||||
data.description,
|
||||
data.`private`,
|
||||
data.auto_init
|
||||
data.auto_init,
|
||||
context.settings.defaultBranch
|
||||
)
|
||||
Await.result(f, Duration.Inf)
|
||||
val repository = Database() withTransaction { session =>
|
||||
|
||||
@@ -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).<>(AccessToken.tupled, AccessToken.unapply)
|
||||
def * = (accessTokenId, userName, tokenHash, note).mapTo[AccessToken]
|
||||
}
|
||||
}
|
||||
case class AccessToken(
|
||||
|
||||
@@ -35,7 +35,7 @@ trait AccountComponent { self: Profile =>
|
||||
groupAccount,
|
||||
removed,
|
||||
description.?
|
||||
).<>(Account.tupled, Account.unapply)
|
||||
).mapTo[Account]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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).<>(AccountExtraMailAddress.tupled, AccountExtraMailAddress.unapply)
|
||||
(userName, extraMailAddress).mapTo[AccountExtraMailAddress]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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).<>(AccountFederation.tupled, AccountFederation.unapply)
|
||||
def * = (issuer, subject, userName).mapTo[AccountFederation]
|
||||
|
||||
def byPrimaryKey(issuer: String, subject: String): Rep[Boolean] =
|
||||
(this.issuer === issuer.bind) && (this.subject === subject.bind)
|
||||
|
||||
@@ -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).<>(AccountPreference.tupled, AccountPreference.unapply)
|
||||
(userName, highlighterTheme).mapTo[AccountPreference]
|
||||
|
||||
def byPrimaryKey(userName: String): Rep[Boolean] = this.userName === userName.bind
|
||||
}
|
||||
|
||||
@@ -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).<>((AccountWebHook.apply _).tupled, AccountWebHook.unapply)
|
||||
def * = (userName, url, ctype, token).mapTo[AccountWebHook]
|
||||
|
||||
def byPrimaryKey(userName: String, url: String) = (this.userName === userName.bind) && (this.url === url.bind)
|
||||
}
|
||||
|
||||
@@ -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).<>((AccountWebHookEvent.apply _).tupled, AccountWebHookEvent.unapply)
|
||||
def * = (userName, url, event).mapTo[AccountWebHookEvent]
|
||||
|
||||
def byAccountWebHook(userName: String, url: String) = (this.userName === userName.bind) && (this.url === url.bind)
|
||||
|
||||
|
||||
@@ -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 * = ???
|
||||
def * : slick.lifted.ProvenShape[Activity] = ???
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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).<>(Collaborator.tupled, Collaborator.unapply)
|
||||
def * = (userName, repositoryName, collaboratorName, role).mapTo[Collaborator]
|
||||
|
||||
def byPrimaryKey(owner: String, repository: String, collaborator: String) =
|
||||
byRepository(owner, repository) && (collaboratorName === collaborator.bind)
|
||||
|
||||
@@ -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)
|
||||
.<>(IssueComment.tupled, IssueComment.unapply)
|
||||
.mapTo[IssueComment]
|
||||
|
||||
def byPrimaryKey(commentId: Int) = this.commentId === commentId.bind
|
||||
}
|
||||
@@ -75,7 +75,7 @@ trait CommitCommentComponent extends TemplateComponent { self: Profile =>
|
||||
originalCommitId,
|
||||
originalOldLine,
|
||||
originalNewLine
|
||||
).<>(CommitComment.tupled, CommitComment.unapply)
|
||||
).mapTo[CommitComment]
|
||||
|
||||
def byPrimaryKey(commentId: Int) = this.commentId === commentId.bind
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ trait CommitStatusComponent extends TemplateComponent { self: Profile =>
|
||||
creator,
|
||||
registeredDate,
|
||||
updatedDate
|
||||
).<>((CommitStatus.apply _).tupled, CommitStatus.unapply)
|
||||
).mapTo[CommitStatus]
|
||||
def byPrimaryKey(id: Int) = commitStatusId === id.bind
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ 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._
|
||||
@@ -15,11 +16,12 @@ 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, enableForIssues, enableForPullRequests)
|
||||
.<>(CustomField.tupled, CustomField.unapply)
|
||||
(userName, repositoryName, fieldId, fieldName, fieldType, constraints, enableForIssues, enableForPullRequests)
|
||||
.mapTo[CustomField]
|
||||
|
||||
def byPrimaryKey(userName: String, repositoryName: String, fieldId: Int) =
|
||||
(this.userName === userName.bind) && (this.repositoryName === repositoryName.bind) && (this.fieldId === fieldId.bind)
|
||||
@@ -31,17 +33,28 @@ case class CustomField(
|
||||
repositoryName: String,
|
||||
fieldId: Int = 0,
|
||||
fieldName: String,
|
||||
fieldType: String, // long, double, string, or date
|
||||
fieldType: String, // long, double, string, date, or enum
|
||||
constraints: Option[String],
|
||||
enableForIssues: Boolean,
|
||||
enableForPullRequests: Boolean
|
||||
)
|
||||
|
||||
trait CustomFieldBehavior {
|
||||
def createHtml(repository: RepositoryInfo, fieldId: Int)(implicit conext: Context): String
|
||||
def fieldHtml(repository: RepositoryInfo, issueId: Int, fieldId: Int, value: String, editable: Boolean)(
|
||||
def createHtml(repository: RepositoryInfo, fieldId: Int, fieldName: String, constraints: Option[String])(
|
||||
implicit context: Context
|
||||
): String
|
||||
def validate(name: String, value: String, messages: Messages): Option[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]
|
||||
}
|
||||
|
||||
object CustomFieldBehavior {
|
||||
@@ -49,7 +62,7 @@ object CustomFieldBehavior {
|
||||
if (value.isEmpty) None
|
||||
else {
|
||||
CustomFieldBehavior(field.fieldType).flatMap { behavior =>
|
||||
behavior.validate(field.fieldName, value, messages)
|
||||
behavior.validate(field.fieldName, field.constraints, value, messages)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -60,12 +73,18 @@ 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, value: String, messages: Messages): Option[String] = {
|
||||
override def validate(
|
||||
name: String,
|
||||
constraints: Option[String],
|
||||
value: String,
|
||||
messages: Messages
|
||||
): Option[String] = {
|
||||
try {
|
||||
value.toLong
|
||||
None
|
||||
@@ -75,7 +94,12 @@ object CustomFieldBehavior {
|
||||
}
|
||||
}
|
||||
case object DoubleFieldBehavior extends TextFieldBehavior {
|
||||
override def validate(name: String, value: String, messages: Messages): Option[String] = {
|
||||
override def validate(
|
||||
name: String,
|
||||
constraints: Option[String],
|
||||
value: String,
|
||||
messages: Messages
|
||||
): Option[String] = {
|
||||
try {
|
||||
value.toDouble
|
||||
None
|
||||
@@ -89,7 +113,12 @@ object CustomFieldBehavior {
|
||||
private val pattern = "yyyy-MM-dd"
|
||||
override protected val fieldType: String = "date"
|
||||
|
||||
override def validate(name: String, value: String, messages: Messages): Option[String] = {
|
||||
override def validate(
|
||||
name: String,
|
||||
constraints: Option[String],
|
||||
value: String,
|
||||
messages: Messages
|
||||
): Option[String] = {
|
||||
try {
|
||||
new java.text.SimpleDateFormat(pattern).parse(value)
|
||||
None
|
||||
@@ -100,10 +129,142 @@ 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"
|
||||
|
||||
def createHtml(repository: RepositoryInfo, fieldId: Int)(implicit context: Context): String = {
|
||||
override def createHtml(repository: RepositoryInfo, fieldId: Int, fieldName: String, constraints: Option[String])(
|
||||
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;"/>"""
|
||||
@@ -111,8 +272,7 @@ object CustomFieldBehavior {
|
||||
sb.append(s"""<script>
|
||||
|$$('#custom-field-$fieldId').focusout(function(){
|
||||
| const $$this = $$(this);
|
||||
| const fieldId = $$this.data('field-id');
|
||||
| $$.post('${helpers.url(repository)}/issues/customfield_validation/' + fieldId,
|
||||
| $$.post('${helpers.url(repository)}/issues/customfield_validation/$fieldId',
|
||||
| { value: $$this.val() },
|
||||
| function(data){
|
||||
| if (data != '') {
|
||||
@@ -128,14 +288,34 @@ object CustomFieldBehavior {
|
||||
sb.toString()
|
||||
}
|
||||
|
||||
def fieldHtml(repository: RepositoryInfo, issueId: Int, fieldId: Int, value: String, editable: Boolean)(
|
||||
override def fieldHtml(
|
||||
repository: RepositoryInfo,
|
||||
issueId: Int,
|
||||
fieldId: Int,
|
||||
fieldName: String,
|
||||
constraints: Option[String],
|
||||
value: String,
|
||||
editable: Boolean
|
||||
)(
|
||||
implicit context: Context
|
||||
): String = {
|
||||
val sb = new StringBuilder
|
||||
sb.append(
|
||||
s"""<span id="custom-field-$fieldId-label" class="custom-field-label">${StringUtil
|
||||
.escapeHtml(value)}</span>""".stripMargin
|
||||
)
|
||||
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>"""
|
||||
)
|
||||
}
|
||||
}
|
||||
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;"/>"""
|
||||
@@ -149,19 +329,22 @@ object CustomFieldBehavior {
|
||||
|
|
||||
|$$('#custom-field-$fieldId-editor').focusout(function(){
|
||||
| const $$this = $$(this);
|
||||
| const fieldId = $$this.data('field-id');
|
||||
| $$.post('${helpers.url(repository)}/issues/customfield_validation/' + fieldId,
|
||||
| $$.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();
|
||||
| $$this.prev().text(data).show();
|
||||
| if (data == '') {
|
||||
| $$this.prev().html('<i class="octicon octicon-pencil" style="cursor: pointer;">').show();
|
||||
| } else {
|
||||
| $$this.prev().text(data).show();
|
||||
| }
|
||||
| }
|
||||
| );
|
||||
| }
|
||||
@@ -186,6 +369,11 @@ object CustomFieldBehavior {
|
||||
sb.toString()
|
||||
}
|
||||
|
||||
def validate(name: String, value: String, messages: Messages): Option[String] = None
|
||||
override def validate(
|
||||
name: String,
|
||||
constraints: Option[String],
|
||||
value: String,
|
||||
messages: Messages
|
||||
): Option[String] = None
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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).<>(DeployKey.tupled, DeployKey.unapply)
|
||||
(userName, repositoryName, deployKeyId, title, publicKey, allowWrite).mapTo[DeployKey]
|
||||
|
||||
def byPrimaryKey(userName: String, repositoryName: String, deployKeyId: Int) =
|
||||
(this.userName === userName.bind) && (this.repositoryName === repositoryName.bind) && (this.deployKeyId === deployKeyId.bind)
|
||||
|
||||
@@ -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).<>(GpgKey.tupled, GpgKey.unapply)
|
||||
def * = (userName, keyId, gpgKeyId, title, publicKey).mapTo[GpgKey]
|
||||
|
||||
def byPrimaryKey(userName: String, keyId: Int) =
|
||||
(this.userName === userName.bind) && (this.keyId === keyId.bind)
|
||||
|
||||
@@ -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).<>(GroupMember.tupled, GroupMember.unapply)
|
||||
def * = (groupName, userName, isManager).mapTo[GroupMember]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ trait IssueComponent extends TemplateComponent { self: Profile =>
|
||||
registeredDate,
|
||||
updatedDate,
|
||||
pullRequest
|
||||
).<>(Issue.tupled, Issue.unapply)
|
||||
).mapTo[Issue]
|
||||
|
||||
def byPrimaryKey(owner: String, repository: String, issueId: Int) = byIssue(owner, repository, issueId)
|
||||
}
|
||||
|
||||
@@ -9,8 +9,7 @@ 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)
|
||||
.<>(IssueAssignee.tupled, IssueAssignee.unapply)
|
||||
(userName, repositoryName, issueId, assigneeUserName).mapTo[IssueAssignee]
|
||||
|
||||
def byPrimaryKey(owner: String, repository: String, issueId: Int, assigneeUserName: String) = {
|
||||
byIssue(owner, repository, issueId) && this.assigneeUserName === assigneeUserName.bind
|
||||
|
||||
@@ -13,8 +13,7 @@ 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)
|
||||
.<>(IssueCustomField.tupled, IssueCustomField.unapply)
|
||||
(userName, repositoryName, issueId, fieldId, value).mapTo[IssueCustomField]
|
||||
|
||||
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
|
||||
|
||||
@@ -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).<>(IssueLabel.tupled, IssueLabel.unapply)
|
||||
def * = (userName, repositoryName, issueId, labelId).mapTo[IssueLabel]
|
||||
def byPrimaryKey(owner: String, repository: String, issueId: Int, labelId: Int) =
|
||||
byIssue(owner, repository, issueId) && (this.labelId === labelId.bind)
|
||||
}
|
||||
|
||||
@@ -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).<>(Label.tupled, Label.unapply)
|
||||
def * = (userName, repositoryName, labelId, labelName, color).mapTo[Label]
|
||||
|
||||
def byPrimaryKey(owner: String, repository: String, labelId: Int) = byLabel(owner, repository, labelId)
|
||||
def byPrimaryKey(userName: Rep[String], repositoryName: Rep[String], labelId: Rep[Int]) =
|
||||
|
||||
@@ -13,8 +13,7 @@ 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)
|
||||
.<>(Milestone.tupled, Milestone.unapply)
|
||||
(userName, repositoryName, milestoneId, title, description, dueDate, closedDate).mapTo[Milestone]
|
||||
|
||||
def byPrimaryKey(owner: String, repository: String, milestoneId: Int) = byMilestone(owner, repository, milestoneId)
|
||||
def byPrimaryKey(userName: Rep[String], repositoryName: Rep[String], milestoneId: Rep[Int]) =
|
||||
|
||||
@@ -13,8 +13,7 @@ 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)
|
||||
.<>(Priority.tupled, Priority.unapply)
|
||||
(userName, repositoryName, priorityId, priorityName, description.?, isDefault, ordering, color).mapTo[Priority]
|
||||
|
||||
def byPrimaryKey(owner: String, repository: String, priorityId: Int) = byPriority(owner, repository, priorityId)
|
||||
def byPrimaryKey(userName: Rep[String], repositoryName: Rep[String], priorityId: Rep[Int]) =
|
||||
|
||||
@@ -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).<>(ProtectedBranch.tupled, ProtectedBranch.unapply)
|
||||
def * = (userName, repositoryName, branch, statusCheckAdmin).mapTo[ProtectedBranch]
|
||||
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).<>(ProtectedBranchContext.tupled, ProtectedBranchContext.unapply)
|
||||
(userName, repositoryName, branch, context).mapTo[ProtectedBranchContext]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ trait PullRequestComponent extends TemplateComponent { self: Profile =>
|
||||
commitIdFrom,
|
||||
commitIdTo,
|
||||
isDraft
|
||||
).<>(PullRequest.tupled, PullRequest.unapply)
|
||||
).mapTo[PullRequest]
|
||||
|
||||
def byPrimaryKey(userName: String, repositoryName: String, issueId: Int) =
|
||||
byIssue(userName, repositoryName, issueId)
|
||||
|
||||
@@ -20,7 +20,7 @@ trait ReleaseAssetComponent extends TemplateComponent { self: Profile =>
|
||||
|
||||
def * =
|
||||
(userName, repositoryName, tag, releaseAssetId, fileName, label, size, uploader, registeredDate, updatedDate)
|
||||
.<>(ReleaseAsset.tupled, ReleaseAsset.unapply)
|
||||
.mapTo[ReleaseAsset]
|
||||
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) =
|
||||
|
||||
@@ -15,8 +15,7 @@ trait ReleaseTagComponent extends TemplateComponent { self: Profile =>
|
||||
val updatedDate = column[java.util.Date]("UPDATED_DATE")
|
||||
|
||||
def * =
|
||||
(userName, repositoryName, name, tag, author, content, registeredDate, updatedDate)
|
||||
.<>(ReleaseTag.tupled, ReleaseTag.unapply)
|
||||
(userName, repositoryName, name, tag, author, content, registeredDate, updatedDate).mapTo[ReleaseTag]
|
||||
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)
|
||||
|
||||
@@ -14,8 +14,7 @@ trait RepositoryWebHookComponent extends TemplateComponent { self: Profile =>
|
||||
val token = column[Option[String]]("TOKEN")
|
||||
val ctype = column[WebHookContentType]("CTYPE")
|
||||
def * =
|
||||
(userName, repositoryName, hookId, url, ctype, token)
|
||||
.<>((RepositoryWebHook.apply _).tupled, RepositoryWebHook.unapply)
|
||||
(userName, repositoryName, hookId, url, ctype, token).mapTo[RepositoryWebHook]
|
||||
|
||||
def byRepositoryUrl(owner: String, repository: String, url: String) =
|
||||
byRepository(owner, repository) && (this.url === url.bind)
|
||||
|
||||
@@ -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).<>((RepositoryWebHookEvent.apply _).tupled, RepositoryWebHookEvent.unapply)
|
||||
(userName, repositoryName, url, event).mapTo[RepositoryWebHookEvent]
|
||||
|
||||
def byRepositoryWebHook(owner: String, repository: String, url: String) =
|
||||
byRepository(owner, repository) && (this.url === url.bind)
|
||||
|
||||
@@ -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).<>(SshKey.tupled, SshKey.unapply)
|
||||
def * = (userName, sshKeyId, title, publicKey).mapTo[SshKey]
|
||||
|
||||
def byPrimaryKey(userName: String, sshKeyId: Int) =
|
||||
(this.userName === userName.bind) && (this.sshKeyId === sshKeyId.bind)
|
||||
|
||||
@@ -9,9 +9,10 @@ import org.json4s.jackson.Serialization.{read, write}
|
||||
import scala.util.Using
|
||||
import java.io.FileOutputStream
|
||||
import java.nio.charset.StandardCharsets
|
||||
|
||||
import gitbucket.core.controller.Context
|
||||
import gitbucket.core.util.ConfigUtil
|
||||
import org.apache.commons.io.input.ReversedLinesFileReader
|
||||
import ActivityService._
|
||||
|
||||
import scala.collection.mutable.ListBuffer
|
||||
|
||||
@@ -26,40 +27,52 @@ trait ActivityService {
|
||||
}
|
||||
}
|
||||
|
||||
def getActivitiesByUser(activityUserName: String, isPublic: Boolean)(implicit context: Context): List[Activity] = {
|
||||
if (!ActivityLog.exists()) {
|
||||
List.empty
|
||||
} else {
|
||||
val list = new ListBuffer[Activity]
|
||||
Using.resource(new ReversedLinesFileReader(ActivityLog, StandardCharsets.UTF_8)) { reader =>
|
||||
var json: String = null
|
||||
while (list.length < 50 && { json = reader.readLine(); json } != null) {
|
||||
val activity = read[Activity](json)
|
||||
if (activity.activityUserName == activityUserName) {
|
||||
if (isPublic == false) {
|
||||
list += activity
|
||||
} else {
|
||||
if (!getRepositoryInfoFromCache(activity.userName, activity.repositoryName).forall(_.isPrivate)) {
|
||||
list += activity
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
list.toList
|
||||
def getActivitiesByUser(activityUserName: String, publicOnly: Boolean)(implicit context: Context): List[Activity] = {
|
||||
getActivities(includePublic = false) { activity =>
|
||||
if (activity.activityUserName == activityUserName) {
|
||||
!publicOnly || isPublicActivity(activity)
|
||||
} else false
|
||||
}
|
||||
}
|
||||
|
||||
def getRecentPublicActivities()(implicit context: Context): List[Activity] = {
|
||||
if (!ActivityLog.exists()) {
|
||||
getActivities(includePublic = true) { _ =>
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
def getRecentActivitiesByRepos(repos: Set[(String, String)])(implicit context: Context): List[Activity] = {
|
||||
getActivities(includePublic = true) { activity =>
|
||||
repos.exists {
|
||||
case (userName, repositoryName) =>
|
||||
activity.userName == userName && activity.repositoryName == repositoryName
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private def getActivities(
|
||||
includePublic: Boolean
|
||||
)(filter: Activity => Boolean)(implicit context: Context): List[Activity] = {
|
||||
if (!isNewsFeedEnabled || !ActivityLog.exists()) {
|
||||
List.empty
|
||||
} else {
|
||||
val list = new ListBuffer[Activity]
|
||||
Using.resource(new ReversedLinesFileReader(ActivityLog, StandardCharsets.UTF_8)) { reader =>
|
||||
Using.resource(
|
||||
ReversedLinesFileReader
|
||||
.builder()
|
||||
.setFile(ActivityLog)
|
||||
.setCharset(StandardCharsets.UTF_8)
|
||||
.get()
|
||||
) { reader =>
|
||||
var json: String = null
|
||||
while (list.length < 50 && { json = reader.readLine(); json } != null) {
|
||||
while (list.length < 50 && {
|
||||
json = reader.readLine();
|
||||
json
|
||||
} != null) {
|
||||
val activity = read[Activity](json)
|
||||
if (!getRepositoryInfoFromCache(activity.userName, activity.repositoryName).forall(_.isPrivate)) {
|
||||
if (filter(activity)) {
|
||||
list += activity
|
||||
} else if (includePublic && isPublicActivity(activity)) {
|
||||
list += activity
|
||||
}
|
||||
}
|
||||
@@ -68,24 +81,8 @@ trait ActivityService {
|
||||
}
|
||||
}
|
||||
|
||||
def getRecentActivitiesByOwners(owners: Set[String])(implicit context: Context): List[Activity] = {
|
||||
if (!ActivityLog.exists()) {
|
||||
List.empty
|
||||
} else {
|
||||
val list = new ListBuffer[Activity]
|
||||
Using.resource(new ReversedLinesFileReader(ActivityLog, StandardCharsets.UTF_8)) { reader =>
|
||||
var json: String = null
|
||||
while (list.length < 50 && { json = reader.readLine(); json } != null) {
|
||||
val activity = read[Activity](json)
|
||||
if (owners.contains(activity.userName)) {
|
||||
list += activity
|
||||
} else if (!getRepositoryInfoFromCache(activity.userName, activity.repositoryName).forall(_.isPrivate)) {
|
||||
list += activity
|
||||
}
|
||||
}
|
||||
}
|
||||
list.toList
|
||||
}
|
||||
private def isPublicActivity(activity: Activity)(implicit context: Context): Boolean = {
|
||||
!getRepositoryInfoFromCache(activity.userName, activity.repositoryName).forall(_.isPrivate)
|
||||
}
|
||||
|
||||
def recordActivity[T <: { def toActivity: Activity }](info: T): Unit = {
|
||||
@@ -93,3 +90,8 @@ trait ActivityService {
|
||||
writeLog(info.toActivity)
|
||||
}
|
||||
}
|
||||
|
||||
object ActivityService {
|
||||
def isNewsFeedEnabled: Boolean =
|
||||
!ConfigUtil.getConfigValue[Boolean]("gitbucket.disableNewsFeed").getOrElse(false)
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ trait CustomFieldsService {
|
||||
repository: String,
|
||||
fieldName: String,
|
||||
fieldType: String,
|
||||
constraints: Option[String],
|
||||
enableForIssues: Boolean,
|
||||
enableForPullRequests: Boolean
|
||||
)(implicit s: Session): Int = {
|
||||
@@ -36,6 +37,7 @@ trait CustomFieldsService {
|
||||
repositoryName = repository,
|
||||
fieldName = fieldName,
|
||||
fieldType = fieldType,
|
||||
constraints = constraints,
|
||||
enableForIssues = enableForIssues,
|
||||
enableForPullRequests = enableForPullRequests
|
||||
)
|
||||
@@ -47,6 +49,7 @@ trait CustomFieldsService {
|
||||
fieldId: Int,
|
||||
fieldName: String,
|
||||
fieldType: String,
|
||||
constraints: Option[String],
|
||||
enableForIssues: Boolean,
|
||||
enableForPullRequests: Boolean
|
||||
)(
|
||||
@@ -54,8 +57,8 @@ trait CustomFieldsService {
|
||||
): Unit =
|
||||
CustomFields
|
||||
.filter(_.byPrimaryKey(owner, repository, fieldId))
|
||||
.map(t => (t.fieldName, t.fieldType, t.enableForIssues, t.enableForPullRequests))
|
||||
.update((fieldName, fieldType, enableForIssues, enableForPullRequests))
|
||||
.map(t => (t.fieldName, t.fieldType, t.constraints, t.enableForIssues, t.enableForPullRequests))
|
||||
.update((fieldName, fieldType, constraints, enableForIssues, enableForPullRequests))
|
||||
|
||||
def deleteCustomField(owner: String, repository: String, fieldId: Int)(implicit s: Session): Unit = {
|
||||
IssueCustomFields
|
||||
|
||||
@@ -12,6 +12,7 @@ import gitbucket.core.model.{
|
||||
IssueComment,
|
||||
IssueLabel,
|
||||
Label,
|
||||
Profile,
|
||||
PullRequest,
|
||||
Repository,
|
||||
Role
|
||||
@@ -22,6 +23,8 @@ import gitbucket.core.model.Profile.profile.blockingApi._
|
||||
import gitbucket.core.model.Profile.dateColumnType
|
||||
import gitbucket.core.plugin.PluginRegistry
|
||||
|
||||
import scala.jdk.CollectionConverters._
|
||||
|
||||
trait IssuesService {
|
||||
self: AccountService with RepositoryService with LabelsService with PrioritiesService with MilestonesService =>
|
||||
import IssuesService._
|
||||
@@ -379,8 +382,8 @@ trait IssuesService {
|
||||
searchOption: IssueSearchOption
|
||||
)(
|
||||
implicit s: Session
|
||||
) =
|
||||
Issues filter { t1 =>
|
||||
) = {
|
||||
val query = Issues filter { t1 =>
|
||||
(if (repos.sizeIs == 1) {
|
||||
t1.byRepository(repos.head._1, repos.head._2)
|
||||
} else {
|
||||
@@ -390,8 +393,8 @@ trait IssuesService {
|
||||
case "open" => t1.closed === false
|
||||
case "closed" => t1.closed === true
|
||||
case _ => t1.closed === true || t1.closed === false
|
||||
}).&&(t1.milestoneId.? isEmpty, condition.milestone == Some(None))
|
||||
.&&(t1.priorityId.? isEmpty, condition.priority == Some(None))
|
||||
}).&&(t1.milestoneId.? isEmpty, condition.milestone.contains(None))
|
||||
.&&(t1.priorityId.? isEmpty, condition.priority.contains(None))
|
||||
//.&&(t1.assignedUserName.? isEmpty, condition.assigned == Some(None))
|
||||
.&&(t1.openedUserName === condition.author.get.bind, condition.author.isDefined) &&
|
||||
(searchOption match {
|
||||
@@ -439,7 +442,7 @@ trait IssuesService {
|
||||
.&&(
|
||||
Repositories filter { t2 =>
|
||||
(t2.byRepository(t1.userName, t1.repositoryName)) &&
|
||||
(t2.isPrivate === (condition.visibility == Some("private")).bind)
|
||||
(t2.isPrivate === condition.visibility.contains("private").bind)
|
||||
} exists,
|
||||
condition.visibility.nonEmpty
|
||||
)
|
||||
@@ -457,6 +460,34 @@ trait IssuesService {
|
||||
)
|
||||
}
|
||||
|
||||
condition.others.foldLeft(query) {
|
||||
case (query, cond) =>
|
||||
def condQuery(f: Rep[String] => Rep[Boolean]): Query[Profile.Issues, Issue, Seq] = {
|
||||
query.filter { t1 =>
|
||||
IssueCustomFields
|
||||
.join(CustomFields)
|
||||
.on { (t2, t3) =>
|
||||
t2.userName === t3.userName && t2.repositoryName === t3.repositoryName && t2.fieldId === t3.fieldId
|
||||
}
|
||||
.filter {
|
||||
case (t2, t3) =>
|
||||
t1.byIssue(t2.userName, t2.repositoryName, t2.issueId) && t3.fieldName === cond.name.bind && f(
|
||||
t2.value
|
||||
)
|
||||
} exists
|
||||
}
|
||||
}
|
||||
cond.operator match {
|
||||
case "eq" => condQuery(_ === cond.value.bind)
|
||||
case "lt" => condQuery(_ < cond.value.bind)
|
||||
case "gt" => condQuery(_ > cond.value.bind)
|
||||
case "lte" => condQuery(_ <= cond.value.bind)
|
||||
case "gte" => condQuery(_ >= cond.value.bind)
|
||||
case _ => throw new IllegalArgumentException("Unsupported operator")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def insertIssue(
|
||||
owner: String,
|
||||
repository: String,
|
||||
@@ -584,7 +615,7 @@ trait IssuesService {
|
||||
.update(title, content, currentDate)
|
||||
}
|
||||
|
||||
def changeIssueToPullRequest(owner: String, repository: String, issueId: Int)(implicit s: Session) = {
|
||||
def changeIssueToPullRequest(owner: String, repository: String, issueId: Int)(implicit s: Session): Int = {
|
||||
Issues
|
||||
.filter(_.byPrimaryKey(owner, repository, issueId))
|
||||
.map { t =>
|
||||
@@ -943,6 +974,8 @@ object IssuesService {
|
||||
|
||||
val IssueLimit = 25
|
||||
|
||||
case class CustomFieldCondition(name: String, value: String, operator: String)
|
||||
|
||||
case class IssueSearchCondition(
|
||||
labels: Set[String] = Set.empty,
|
||||
milestone: Option[Option[String]] = None,
|
||||
@@ -954,7 +987,8 @@ object IssuesService {
|
||||
sort: String = "created",
|
||||
direction: String = "desc",
|
||||
visibility: Option[String] = None,
|
||||
groups: Set[String] = Set.empty
|
||||
groups: Set[String] = Set.empty,
|
||||
others: Seq[CustomFieldCondition] = Nil
|
||||
) {
|
||||
|
||||
def isEmpty: Boolean = {
|
||||
@@ -993,48 +1027,148 @@ object IssuesService {
|
||||
case ("priority", "asc") => Some("sort:priority-asc")
|
||||
case x => throw new MatchError(x)
|
||||
},
|
||||
visibility.map(visibility => s"visibility:${visibility}")
|
||||
visibility.map(visibility => s"visibility:${visibility}"),
|
||||
).flatten ++
|
||||
others.map { cond =>
|
||||
cond.operator match {
|
||||
case "eq" => s"custom.${cond.name}:${cond.value}"
|
||||
case "lt" => s"custom.${cond.name}<${cond.value}"
|
||||
case "lte" => s"custom.${cond.name}<=${cond.value}"
|
||||
case "gt" => s"custom.${cond.name}>${cond.value}"
|
||||
case "gte" => s"custom.${cond.name}>=${cond.value}"
|
||||
}
|
||||
} ++
|
||||
groups.map(group => s"group:${group}")
|
||||
).mkString(" ")
|
||||
|
||||
def toURL: String =
|
||||
"?" + List(
|
||||
def toURL: String = {
|
||||
"?" + (Seq(
|
||||
if (labels.isEmpty) None else Some("labels=" + urlEncode(labels.mkString(","))),
|
||||
milestone.map {
|
||||
case Some(x) => "milestone=" + urlEncode(x)
|
||||
case Some(x) => s"milestone=${urlEncode(x)}"
|
||||
case None => "milestone=none"
|
||||
},
|
||||
priority.map {
|
||||
case Some(x) => "priority=" + urlEncode(x)
|
||||
case Some(x) => s"priority=${urlEncode(x)}"
|
||||
case None => "priority=none"
|
||||
},
|
||||
author.map(x => "author=" + urlEncode(x)),
|
||||
author.map(x => s"author=${urlEncode(x)}"),
|
||||
assigned.map {
|
||||
case Some(x) => "assigned=" + urlEncode(x)
|
||||
case Some(x) => s"assigned=${urlEncode(x)}"
|
||||
case None => "assigned=none"
|
||||
},
|
||||
mentioned.map(x => "mentioned=" + urlEncode(x)),
|
||||
Some("state=" + urlEncode(state)),
|
||||
Some("sort=" + urlEncode(sort)),
|
||||
Some("direction=" + urlEncode(direction)),
|
||||
visibility.map(x => "visibility=" + urlEncode(x)),
|
||||
if (groups.isEmpty) None else Some("groups=" + urlEncode(groups.mkString(",")))
|
||||
).flatten.mkString("&")
|
||||
|
||||
mentioned.map(x => s"mentioned=${urlEncode(x)}"),
|
||||
Some(s"state=${urlEncode(state)}"),
|
||||
Some(s"sort=${urlEncode(sort)}"),
|
||||
Some(s"direction=${urlEncode(direction)}"),
|
||||
visibility.map(x => s"visibility=${urlEncode(x)}"),
|
||||
if (groups.isEmpty) None else Some(s"groups=${urlEncode(groups.mkString(","))}")
|
||||
).flatten ++ others.map { x =>
|
||||
s"custom.${urlEncode(x.name)}=${urlEncode(x.operator)}:${urlEncode(x.value)}"
|
||||
}).mkString("&")
|
||||
}
|
||||
}
|
||||
|
||||
object IssueSearchCondition {
|
||||
|
||||
private val SupportedOperators = Seq("eq", "lt", "gt", "lte", "gte")
|
||||
|
||||
private def param(request: HttpServletRequest, name: String, allow: Seq[String] = Nil): Option[String] = {
|
||||
val value = request.getParameter(name)
|
||||
if (value == null || value.isEmpty || (allow.nonEmpty && !allow.contains(value))) None else Some(value)
|
||||
}
|
||||
|
||||
/**
|
||||
* Restores IssueSearchCondition instance from filter query.
|
||||
*/
|
||||
def apply(filter: String): IssueSearchCondition = {
|
||||
val conditions = filter
|
||||
.split("[ \t]+")
|
||||
.collect {
|
||||
case x if !x.startsWith("custom.") && x.indexOf(":") > 0 =>
|
||||
val dim = x.split(":")
|
||||
dim(0) -> dim(1)
|
||||
}
|
||||
.groupBy(_._1)
|
||||
.map {
|
||||
case (key, values) =>
|
||||
key -> values.map(_._2).toSeq
|
||||
}
|
||||
|
||||
val (sort, direction) = conditions.get("sort").flatMap(_.headOption).getOrElse("created-desc") match {
|
||||
case "created-asc" => ("created", "asc")
|
||||
case "comments-desc" => ("comments", "desc")
|
||||
case "comments-asc" => ("comments", "asc")
|
||||
case "updated-desc" => ("comments", "desc")
|
||||
case "updated-asc" => ("comments", "asc")
|
||||
case _ => ("created", "desc")
|
||||
}
|
||||
|
||||
val others = filter
|
||||
.split("[ \t]+")
|
||||
.collect {
|
||||
case x if x.startsWith("custom.") && x.indexOf(":") > 0 =>
|
||||
val dim = x.split(":")
|
||||
dim(0) -> ("eq", dim(1))
|
||||
case x if x.startsWith("custom.") && x.indexOf("<=") > 0 =>
|
||||
val dim = x.split("<=")
|
||||
dim(0) -> ("lte", dim(1))
|
||||
case x if x.startsWith("custom.") && x.indexOf("<") > 0 =>
|
||||
val dim = x.split("<")
|
||||
dim(0) -> ("lt", dim(1))
|
||||
case x if x.startsWith("custom.") && x.indexOf(">=") > 0 =>
|
||||
val dim = x.split(">=")
|
||||
dim(0) -> ("gte", dim(1))
|
||||
case x if x.startsWith("custom.") && x.indexOf(">") > 0 =>
|
||||
val dim = x.split(">")
|
||||
dim(0) -> ("gt", dim(1))
|
||||
}
|
||||
.map {
|
||||
case (key, (operator, value)) =>
|
||||
CustomFieldCondition(key.stripPrefix("custom."), value, operator)
|
||||
}
|
||||
.toSeq
|
||||
|
||||
IssueSearchCondition(
|
||||
conditions.get("label").map(_.toSet).getOrElse(Set.empty),
|
||||
conditions.get("milestone").flatMap(_.headOption) match {
|
||||
case None => None
|
||||
case Some("none") => Some(None)
|
||||
case Some(x) => Some(Some(x)) //milestones.get(x).map(x => Some(x))
|
||||
},
|
||||
conditions.get("priority").map(_.headOption), // TODO
|
||||
conditions.get("author").flatMap(_.headOption),
|
||||
conditions.get("assignee").map(_.headOption), // TODO
|
||||
conditions.get("mentions").flatMap(_.headOption),
|
||||
conditions.get("is").getOrElse(Seq.empty).find(x => x == "open" || x == "closed").getOrElse("open"),
|
||||
sort,
|
||||
direction,
|
||||
conditions.get("visibility").flatMap(_.headOption),
|
||||
conditions.get("group").map(_.toSet).getOrElse(Set.empty),
|
||||
others
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Restores IssueSearchCondition instance from request parameters.
|
||||
*/
|
||||
def apply(request: HttpServletRequest): IssueSearchCondition =
|
||||
def apply(request: HttpServletRequest): IssueSearchCondition = {
|
||||
val others = request.getParameterMap.asScala
|
||||
.collect {
|
||||
// custom.<field_name> = <operator>:<value>
|
||||
case (key, values) if key.startsWith("custom.") && values.nonEmpty && values.head.indexOf(":") > 0 =>
|
||||
val name = key.stripPrefix("custom.")
|
||||
val Array(operator, value) = values.head.split(":")
|
||||
CustomFieldCondition(name, value, operator)
|
||||
case (key, values) if key.startsWith("custom.") && values.nonEmpty =>
|
||||
val name = key.stripPrefix("custom.")
|
||||
CustomFieldCondition(name, values.head, "eq")
|
||||
}
|
||||
.filter { x =>
|
||||
SupportedOperators.contains(x.operator)
|
||||
}
|
||||
.toSeq
|
||||
|
||||
IssueSearchCondition(
|
||||
param(request, "labels").map(_.split(",").toSet).getOrElse(Set.empty),
|
||||
param(request, "milestone").map {
|
||||
@@ -1055,31 +1189,16 @@ object IssuesService {
|
||||
param(request, "sort", Seq("created", "comments", "updated", "priority")).getOrElse("created"),
|
||||
param(request, "direction", Seq("asc", "desc")).getOrElse("desc"),
|
||||
param(request, "visibility"),
|
||||
param(request, "groups").map(_.split(",").toSet).getOrElse(Set.empty)
|
||||
param(request, "groups").map(_.split(",").toSet).getOrElse(Set.empty),
|
||||
others
|
||||
)
|
||||
}
|
||||
|
||||
def apply(request: HttpServletRequest, milestone: String): IssueSearchCondition =
|
||||
IssueSearchCondition(
|
||||
param(request, "labels").map(_.split(",").toSet).getOrElse(Set.empty),
|
||||
Some(Some(milestone)),
|
||||
param(request, "priority").map {
|
||||
case "none" => None
|
||||
case x => Some(x)
|
||||
},
|
||||
param(request, "author"),
|
||||
param(request, "assigned").map {
|
||||
case "none" => None
|
||||
case x => Some(x)
|
||||
},
|
||||
param(request, "mentioned"),
|
||||
param(request, "state", Seq("open", "closed", "all")).getOrElse("open"),
|
||||
param(request, "sort", Seq("created", "comments", "updated", "priority")).getOrElse("created"),
|
||||
param(request, "direction", Seq("asc", "desc")).getOrElse("desc"),
|
||||
param(request, "visibility"),
|
||||
param(request, "groups").map(_.split(",").toSet).getOrElse(Set.empty)
|
||||
)
|
||||
def apply(request: HttpServletRequest, milestone: String): IssueSearchCondition = {
|
||||
apply(request).copy(milestone = Some(Some(milestone)))
|
||||
}
|
||||
|
||||
def page(request: HttpServletRequest) = {
|
||||
def page(request: HttpServletRequest): Int = {
|
||||
PaginationHelper.page(param(request, "page"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
package gitbucket.core.service
|
||||
|
||||
import java.net.URI
|
||||
|
||||
import com.nimbusds.jose.JWSAlgorithm.Family
|
||||
import com.nimbusds.jose.proc.BadJOSEException
|
||||
import com.nimbusds.jose.util.DefaultResourceRetriever
|
||||
import com.nimbusds.jose.{JOSEException, JWSAlgorithm}
|
||||
import com.nimbusds.jwt.JWT
|
||||
import com.nimbusds.oauth2.sdk._
|
||||
import com.nimbusds.oauth2.sdk.auth.ClientSecretBasic
|
||||
import com.nimbusds.oauth2.sdk.id.{ClientID, Issuer, State}
|
||||
@@ -52,6 +52,11 @@ trait OpenIDConnectService {
|
||||
)
|
||||
}
|
||||
|
||||
def createOIDLogoutRequest(issuer: Issuer, clientID: ClientID, redirectURI: URI, token: JWT): LogoutRequest = {
|
||||
val metadata = OIDCProviderMetadata.resolve(issuer)
|
||||
new LogoutRequest(metadata.getEndSessionEndpointURI, token, null, clientID, redirectURI, null, null)
|
||||
}
|
||||
|
||||
/**
|
||||
* Proceed the OpenID Connect authentication.
|
||||
*
|
||||
@@ -60,7 +65,7 @@ trait OpenIDConnectService {
|
||||
* @param state State saved in the session
|
||||
* @param nonce Nonce saved in the session
|
||||
* @param oidc OIDC settings
|
||||
* @return ID token
|
||||
* @return (ID token, GitBucket account)
|
||||
*/
|
||||
def authenticate(
|
||||
params: Map[String, String],
|
||||
@@ -68,22 +73,25 @@ trait OpenIDConnectService {
|
||||
state: State,
|
||||
nonce: Nonce,
|
||||
oidc: SystemSettingsService.OIDC
|
||||
)(implicit s: Session): Option[Account] =
|
||||
)(implicit s: Session): Option[(JWT, Account)] =
|
||||
validateOIDCAuthenticationResponse(params, state, redirectURI) flatMap { authenticationResponse =>
|
||||
obtainOIDCToken(authenticationResponse.getAuthorizationCode, nonce, redirectURI, oidc) flatMap { claims =>
|
||||
Seq("email", "preferred_username", "name").map(k => Option(claims.getStringClaim(k))) match {
|
||||
case Seq(Some(email), preferredUsername, name) =>
|
||||
getOrCreateFederatedUser(
|
||||
claims.getIssuer.getValue,
|
||||
claims.getSubject.getValue,
|
||||
email,
|
||||
preferredUsername,
|
||||
name
|
||||
)
|
||||
case _ =>
|
||||
logger.info(s"OIDC ID token must have an email claim: claims=${claims.toJSONObject}")
|
||||
None
|
||||
}
|
||||
obtainOIDCToken(authenticationResponse.getAuthorizationCode, nonce, redirectURI, oidc) flatMap {
|
||||
case (jwt, claims) =>
|
||||
Seq("email", "preferred_username", "name").map(k => Option(claims.getStringClaim(k))) match {
|
||||
case Seq(Some(email), preferredUsername, name) =>
|
||||
getOrCreateFederatedUser(
|
||||
claims.getIssuer.getValue,
|
||||
claims.getSubject.getValue,
|
||||
email,
|
||||
preferredUsername,
|
||||
name
|
||||
).map { account =>
|
||||
(jwt, account)
|
||||
}
|
||||
case _ =>
|
||||
logger.info(s"OIDC ID token must have an email claim: claims=${claims.toJSONObject}")
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -136,7 +144,7 @@ trait OpenIDConnectService {
|
||||
nonce: Nonce,
|
||||
redirectURI: URI,
|
||||
oidc: SystemSettingsService.OIDC
|
||||
): Option[IDTokenClaimsSet] = {
|
||||
): Option[(JWT, IDTokenClaimsSet)] = {
|
||||
val metadata = OIDCProviderMetadata.resolve(oidc.issuer)
|
||||
val tokenRequest = new TokenRequest(
|
||||
metadata.getTokenEndpointURI,
|
||||
@@ -173,7 +181,7 @@ trait OpenIDConnectService {
|
||||
metadata: OIDCProviderMetadata,
|
||||
nonce: Nonce,
|
||||
oidc: SystemSettingsService.OIDC
|
||||
): Option[IDTokenClaimsSet] =
|
||||
): Option[(JWT, IDTokenClaimsSet)] =
|
||||
Option(response.getOIDCTokens.getIDToken) match {
|
||||
case Some(jwt) =>
|
||||
val validator = oidc.jwsAlgorithm map { jwsAlgorithm =>
|
||||
@@ -188,7 +196,7 @@ trait OpenIDConnectService {
|
||||
new IDTokenValidator(metadata.getIssuer, oidc.clientID)
|
||||
}
|
||||
try {
|
||||
Some(validator.validate(jwt, nonce))
|
||||
Some((jwt, validator.validate(jwt, nonce)))
|
||||
} catch {
|
||||
case e @ (_: BadJOSEException | _: JOSEException) =>
|
||||
logger.info(s"OIDC ID token has error: $e")
|
||||
|
||||
@@ -64,9 +64,19 @@ trait RepositoryCreationService {
|
||||
name: String,
|
||||
description: Option[String],
|
||||
isPrivate: Boolean,
|
||||
createReadme: Boolean
|
||||
createReadme: Boolean,
|
||||
defaultBranch: String
|
||||
): Future[Unit] = {
|
||||
createRepository(loginAccount, owner, name, description, isPrivate, if (createReadme) "README" else "EMPTY", None)
|
||||
createRepository(
|
||||
loginAccount,
|
||||
owner,
|
||||
name,
|
||||
description,
|
||||
isPrivate,
|
||||
if (createReadme) "README" else "EMPTY",
|
||||
None,
|
||||
defaultBranch
|
||||
)
|
||||
}
|
||||
|
||||
def createRepository(
|
||||
@@ -76,7 +86,8 @@ trait RepositoryCreationService {
|
||||
description: Option[String],
|
||||
isPrivate: Boolean,
|
||||
initOption: String,
|
||||
sourceUrl: Option[String]
|
||||
sourceUrl: Option[String],
|
||||
defaultBranch: String
|
||||
): Future[Unit] = Future {
|
||||
RepositoryCreationService.startCreation(owner, name)
|
||||
try {
|
||||
@@ -93,7 +104,7 @@ trait RepositoryCreationService {
|
||||
} else None
|
||||
|
||||
// Insert to the database at first
|
||||
insertRepository(name, owner, description, isPrivate)
|
||||
insertRepository(name, owner, description, isPrivate, defaultBranch)
|
||||
|
||||
// // Add collaborators for group repository
|
||||
// if(ownerAccount.isGroupAccount){
|
||||
@@ -110,7 +121,7 @@ trait RepositoryCreationService {
|
||||
|
||||
// Create the actual repository
|
||||
val gitdir = getRepositoryDir(owner, name)
|
||||
JGitUtil.initRepository(gitdir)
|
||||
JGitUtil.initRepository(gitdir, defaultBranch)
|
||||
|
||||
if (initOption == "README" || initOption == "EMPTY_COMMIT") {
|
||||
Using.resource(Git.open(gitdir)) { git =>
|
||||
@@ -163,7 +174,7 @@ trait RepositoryCreationService {
|
||||
}
|
||||
|
||||
// Create Wiki repository
|
||||
createWikiRepository(loginAccount, owner, name)
|
||||
createWikiRepository(loginAccount, owner, name, defaultBranch)
|
||||
|
||||
// Record activity
|
||||
recordActivity(CreateRepositoryInfo(owner, name, loginUserName))
|
||||
|
||||
@@ -34,7 +34,7 @@ trait RepositoryService {
|
||||
userName: String,
|
||||
description: Option[String],
|
||||
isPrivate: Boolean,
|
||||
defaultBranch: String = "master",
|
||||
defaultBranch: String,
|
||||
originRepositoryName: Option[String] = None,
|
||||
originUserName: Option[String] = None,
|
||||
parentRepositoryName: Option[String] = None,
|
||||
@@ -254,6 +254,7 @@ trait RepositoryService {
|
||||
Labels.filter(_.byRepository(userName, repositoryName)).delete
|
||||
IssueComments.filter(_.byRepository(userName, repositoryName)).delete
|
||||
PullRequests.filter(_.byRepository(userName, repositoryName)).delete
|
||||
IssueAssignees.filter(_.byRepository(userName, repositoryName)).delete
|
||||
Issues.filter(_.byRepository(userName, repositoryName)).delete
|
||||
Priorities.filter(_.byRepository(userName, repositoryName)).delete
|
||||
IssueId.filter(_.byRepository(userName, repositoryName)).delete
|
||||
|
||||
@@ -90,6 +90,7 @@ trait SystemSettingsService {
|
||||
props.setProperty(UploadLargeMaxFileSize, settings.upload.largeMaxFileSize.toString)
|
||||
props.setProperty(UploadLargeTimeout, settings.upload.largeTimeout.toString)
|
||||
props.setProperty(RepositoryViewerMaxFiles, settings.repositoryViewer.maxFiles.toString)
|
||||
props.setProperty(DefaultBranch, settings.defaultBranch)
|
||||
|
||||
Using.resource(new java.io.FileOutputStream(GitBucketConf)) { out =>
|
||||
props.store(out, null)
|
||||
@@ -205,7 +206,8 @@ trait SystemSettingsService {
|
||||
),
|
||||
RepositoryViewerSettings(
|
||||
getValue(props, RepositoryViewerMaxFiles, 0)
|
||||
)
|
||||
),
|
||||
getValue(props, DefaultBranch, "main")
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -231,7 +233,8 @@ object SystemSettingsService {
|
||||
showMailAddress: Boolean,
|
||||
webHook: WebHook,
|
||||
upload: Upload,
|
||||
repositoryViewer: RepositoryViewerSettings
|
||||
repositoryViewer: RepositoryViewerSettings,
|
||||
defaultBranch: String
|
||||
) {
|
||||
def baseUrl(request: HttpServletRequest): String =
|
||||
baseUrl.getOrElse(parseBaseUrl(request)).stripSuffix("/")
|
||||
@@ -402,7 +405,6 @@ object SystemSettingsService {
|
||||
private val RepositoryOperationFork = "repository_operation_fork"
|
||||
private val Gravatar = "gravatar"
|
||||
private val Notification = "notification"
|
||||
private val ActivityLogLimit = "activity_log_limit"
|
||||
private val LimitVisibleRepositories = "limitVisibleRepositories"
|
||||
private val SshEnabled = "ssh"
|
||||
private val SshHost = "ssh.host"
|
||||
@@ -448,6 +450,7 @@ object SystemSettingsService {
|
||||
private val UploadLargeMaxFileSize = "upload.largeMaxFileSize"
|
||||
private val UploadLargeTimeout = "upload.largeTimeout"
|
||||
private val RepositoryViewerMaxFiles = "repository_viewer_max_files"
|
||||
private val DefaultBranch = "default_branch"
|
||||
|
||||
private def getValue[A: ClassTag](props: java.util.Properties, key: String, default: A): A = {
|
||||
getConfigValue(key).getOrElse {
|
||||
|
||||
@@ -51,11 +51,11 @@ object WikiService {
|
||||
trait WikiService {
|
||||
import WikiService._
|
||||
|
||||
def createWikiRepository(loginAccount: Account, owner: String, repository: String): Unit =
|
||||
def createWikiRepository(loginAccount: Account, owner: String, repository: String, defaultBranch: String): Unit =
|
||||
LockUtil.lock(s"${owner}/${repository}/wiki") {
|
||||
val dir = Directory.getWikiRepositoryDir(owner, repository)
|
||||
if (!dir.exists) {
|
||||
JGitUtil.initRepository(dir)
|
||||
JGitUtil.initRepository(dir, defaultBranch)
|
||||
saveWikiPage(
|
||||
owner,
|
||||
repository,
|
||||
@@ -72,11 +72,11 @@ trait WikiService {
|
||||
/**
|
||||
* Returns the wiki page.
|
||||
*/
|
||||
def getWikiPage(owner: String, repository: String, pageName: String): Option[WikiPageInfo] = {
|
||||
def getWikiPage(owner: String, repository: String, pageName: String, branch: String): Option[WikiPageInfo] = {
|
||||
Using.resource(Git.open(Directory.getWikiRepositoryDir(owner, repository))) { git =>
|
||||
if (!JGitUtil.isEmpty(git)) {
|
||||
val fileName = pageName + ".md"
|
||||
JGitUtil.getLatestCommitFromPath(git, fileName, "master").map { latestCommit =>
|
||||
JGitUtil.getLatestCommitFromPath(git, fileName, branch).map { latestCommit =>
|
||||
val content = JGitUtil.getContentFromPath(git, latestCommit.getTree, fileName, true)
|
||||
WikiPageInfo(
|
||||
fileName,
|
||||
@@ -93,10 +93,10 @@ trait WikiService {
|
||||
/**
|
||||
* Returns the list of wiki page names.
|
||||
*/
|
||||
def getWikiPageList(owner: String, repository: String): List[String] = {
|
||||
def getWikiPageList(owner: String, repository: String, branch: String): List[String] = {
|
||||
Using.resource(Git.open(Directory.getWikiRepositoryDir(owner, repository))) { git =>
|
||||
JGitUtil
|
||||
.getFileList(git, "master", ".")
|
||||
.getFileList(git, branch, ".")
|
||||
.filter(_.name.endsWith(".md"))
|
||||
.filterNot(_.name.startsWith("_"))
|
||||
.map(_.name.stripSuffix(".md"))
|
||||
@@ -113,7 +113,8 @@ trait WikiService {
|
||||
from: String,
|
||||
to: String,
|
||||
committer: Account,
|
||||
pageName: Option[String]
|
||||
pageName: Option[String],
|
||||
branch: String
|
||||
): Boolean = {
|
||||
|
||||
case class RevertInfo(operation: String, filePath: String, source: String)
|
||||
@@ -151,7 +152,7 @@ trait WikiService {
|
||||
fh.getChangeType match {
|
||||
case DiffEntry.ChangeType.MODIFY => {
|
||||
val source =
|
||||
getWikiPage(owner, repository, fh.getNewPath.stripSuffix(".md")).map(_.content).getOrElse("")
|
||||
getWikiPage(owner, repository, fh.getNewPath.stripSuffix(".md"), branch).map(_.content).getOrElse("")
|
||||
val applied = PatchUtil.apply(source, patch, fh)
|
||||
if (applied != null) {
|
||||
Seq(RevertInfo("ADD", fh.getNewPath, applied))
|
||||
|
||||
@@ -245,7 +245,7 @@ class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl:
|
||||
with RequestCache {
|
||||
|
||||
private val logger = LoggerFactory.getLogger(classOf[CommitLogHook])
|
||||
private var existIds: Seq[String] = Nil
|
||||
private var newCommitIds: Seq[String] = Nil
|
||||
|
||||
def onPreReceive(receivePack: ReceivePack, commands: java.util.Collection[ReceiveCommand]): Unit = {
|
||||
Database() withTransaction { implicit session =>
|
||||
@@ -260,13 +260,43 @@ class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl:
|
||||
}
|
||||
}
|
||||
Using.resource(Git.open(Directory.getRepositoryDir(owner, repository))) { git =>
|
||||
existIds = JGitUtil.getAllCommitIds(git)
|
||||
commands.asScala.foreach { command =>
|
||||
val commits = getCommits(git, command)
|
||||
if (commits.size < 100) {
|
||||
newCommitIds = commits
|
||||
.takeWhile { commit =>
|
||||
!existCommit(git, commit)
|
||||
}
|
||||
.map(_.id)
|
||||
} else {
|
||||
val allCommits = JGitUtil.getAllCommitIds(git)
|
||||
newCommitIds = commits.collect {
|
||||
case commit if !allCommits.contains(commit.id) =>
|
||||
commit.id
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
case ex: Exception => {
|
||||
case ex: Exception =>
|
||||
logger.error(ex.toString, ex)
|
||||
throw ex
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private def existCommit(git: Git, commit: CommitInfo): Boolean = {
|
||||
JGitUtil.getBranchesOfCommit(git, commit.id).nonEmpty
|
||||
}
|
||||
|
||||
private def getCommits(git: Git, command: ReceiveCommand): Seq[CommitInfo] = {
|
||||
val refName = command.getRefName.split("/")
|
||||
if (refName(1) == "tags") {
|
||||
Nil
|
||||
} else {
|
||||
command.getType match {
|
||||
case ReceiveCommand.Type.DELETE => Nil
|
||||
case _ => JGitUtil.getCommitLog(git, command.getOldId.name, command.getNewId.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -279,20 +309,12 @@ class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl:
|
||||
Using.resource(Git.open(Directory.getRepositoryDir(owner, repository))) { git =>
|
||||
JGitUtil.removeCache(git)
|
||||
|
||||
val pushedIds = scala.collection.mutable.Set[String]()
|
||||
commands.asScala.foreach { command =>
|
||||
logger.debug(s"commandType: ${command.getType}, refName: ${command.getRefName}")
|
||||
implicit val apiContext: Context = api.JsonFormat.Context(baseUrl, sshUrl)
|
||||
val refName = command.getRefName.split("/")
|
||||
val branchName = refName.drop(2).mkString("/")
|
||||
val commits = if (refName(1) == "tags") {
|
||||
Nil
|
||||
} else {
|
||||
command.getType match {
|
||||
case ReceiveCommand.Type.DELETE => Nil
|
||||
case _ => JGitUtil.getCommitLog(git, command.getOldId.name, command.getNewId.name)
|
||||
}
|
||||
}
|
||||
val commits = getCommits(git, command)
|
||||
|
||||
val repositoryInfo = getRepository(owner, repository).get
|
||||
|
||||
@@ -312,8 +334,9 @@ class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl:
|
||||
|
||||
// Extract new commit and apply issue comment
|
||||
val defaultBranch = repositoryInfo.repository.defaultBranch
|
||||
val newCommits = commits.flatMap { commit =>
|
||||
if (!existIds.contains(commit.id) && !pushedIds.contains(commit.id)) {
|
||||
val pushedIds = scala.collection.mutable.Set[String]()
|
||||
val newCommits = commits.collect {
|
||||
case commit if newCommitIds.contains(commit.id) && !pushedIds.contains(commit.id) =>
|
||||
if (issueCount > 0) {
|
||||
pushedIds.add(commit.id)
|
||||
createIssueComment(owner, repository, commit)
|
||||
@@ -333,9 +356,8 @@ class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl:
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(commit)
|
||||
} else None
|
||||
}
|
||||
commit
|
||||
}.toList
|
||||
|
||||
// set PR as merged
|
||||
val pulls = getPullRequestsByBranch(owner, repository, branchName, Some(false))
|
||||
@@ -431,10 +453,9 @@ class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl:
|
||||
// update repository last modified time.
|
||||
updateLastActivityDate(owner, repository)
|
||||
} catch {
|
||||
case ex: Exception => {
|
||||
case ex: Exception =>
|
||||
logger.error(ex.toString, ex)
|
||||
throw ex
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -506,10 +527,9 @@ class WikiCommitHook(owner: String, repository: String, pusher: String, baseUrl:
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
case ex: Exception => {
|
||||
case ex: Exception =>
|
||||
logger.error(ex.toString, ex)
|
||||
throw ex
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,26 +99,26 @@ object DatabaseType {
|
||||
|
||||
object H2 extends DatabaseType {
|
||||
val jdbcDriver = "org.h2.Driver"
|
||||
val slickDriver = BlockingH2Driver
|
||||
val liquiDriver = new H2Database()
|
||||
val slickDriver: BlockingJdbcProfile = BlockingH2Driver
|
||||
val liquiDriver: AbstractJdbcDatabase = new H2Database()
|
||||
}
|
||||
|
||||
object MySQL extends DatabaseType {
|
||||
val jdbcDriver = "org.mariadb.jdbc.Driver"
|
||||
val slickDriver = BlockingMySQLDriver
|
||||
val liquiDriver = new MySQLDatabase()
|
||||
val slickDriver: BlockingJdbcProfile = BlockingMySQLDriver
|
||||
val liquiDriver: AbstractJdbcDatabase = new MySQLDatabase()
|
||||
}
|
||||
|
||||
object MariaDb extends DatabaseType {
|
||||
val jdbcDriver = "org.mariadb.jdbc.Driver"
|
||||
val slickDriver = BlockingMySQLDriver
|
||||
val liquiDriver = new MariaDBDatabase()
|
||||
val slickDriver: BlockingJdbcProfile = BlockingMySQLDriver
|
||||
val liquiDriver: AbstractJdbcDatabase = new MariaDBDatabase()
|
||||
}
|
||||
|
||||
object PostgreSQL extends DatabaseType {
|
||||
val jdbcDriver = "org.postgresql.Driver2"
|
||||
val slickDriver = BlockingPostgresDriver
|
||||
val liquiDriver = new PostgresDatabase()
|
||||
val slickDriver: BlockingJdbcProfile = BlockingPostgresDriver
|
||||
val liquiDriver: AbstractJdbcDatabase = new PostgresDatabase()
|
||||
}
|
||||
|
||||
object BlockingPostgresDriver extends slick.jdbc.PostgresProfile with BlockingJdbcProfile {
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
package gitbucket.core.util
|
||||
|
||||
import java.io.ByteArrayInputStream
|
||||
|
||||
import scala.jdk.CollectionConverters._
|
||||
|
||||
import gitbucket.core.model.Profile._
|
||||
import gitbucket.core.model.Profile.profile.blockingApi._
|
||||
import org.bouncycastle.bcpg.ArmoredInputStream
|
||||
@@ -34,29 +33,33 @@ object GpgUtil {
|
||||
}
|
||||
|
||||
def verifySign(signInfo: JGitUtil.GpgSignInfo)(implicit s: Session): Option[JGitUtil.GpgVerifyInfo] = {
|
||||
new BcPGPObjectFactory(new ArmoredInputStream(new ByteArrayInputStream(signInfo.signArmored)))
|
||||
.iterator()
|
||||
.asScala
|
||||
.flatMap {
|
||||
case signList: PGPSignatureList =>
|
||||
signList
|
||||
.iterator()
|
||||
.asScala
|
||||
.flatMap { sign =>
|
||||
getGpgKey(sign.getKeyID)
|
||||
.map { pubKey =>
|
||||
sign.init(new BcPGPContentVerifierBuilderProvider, pubKey)
|
||||
sign.update(signInfo.target)
|
||||
(sign, pubKey)
|
||||
}
|
||||
.collect {
|
||||
case (sign, pubKey) if sign.verify() =>
|
||||
JGitUtil.GpgVerifyInfo(pubKey.getUserIDs.next, pubKey.getKeyID.toHexString.toUpperCase)
|
||||
}
|
||||
}
|
||||
try {
|
||||
new BcPGPObjectFactory(new ArmoredInputStream(new ByteArrayInputStream(signInfo.signArmored)))
|
||||
.iterator()
|
||||
.asScala
|
||||
.flatMap {
|
||||
case signList: PGPSignatureList =>
|
||||
signList
|
||||
.iterator()
|
||||
.asScala
|
||||
.flatMap { sign =>
|
||||
getGpgKey(sign.getKeyID)
|
||||
.map { pubKey =>
|
||||
sign.init(new BcPGPContentVerifierBuilderProvider, pubKey)
|
||||
sign.update(signInfo.target)
|
||||
(sign, pubKey)
|
||||
}
|
||||
.collect {
|
||||
case (sign, pubKey) if sign.verify() =>
|
||||
JGitUtil.GpgVerifyInfo(pubKey.getUserIDs.next, pubKey.getKeyID.toHexString.toUpperCase)
|
||||
}
|
||||
}
|
||||
}
|
||||
.toList
|
||||
.headOption
|
||||
} catch {
|
||||
case _: Throwable => None
|
||||
}
|
||||
|
||||
}
|
||||
.toList
|
||||
.headOption
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package gitbucket.core.util
|
||||
|
||||
import java.io._
|
||||
|
||||
import gitbucket.core.service.RepositoryService
|
||||
import org.eclipse.jgit.api.Git
|
||||
import Directory._
|
||||
@@ -18,10 +17,10 @@ import org.eclipse.jgit.treewalk.filter._
|
||||
import org.eclipse.jgit.diff.DiffEntry.ChangeType
|
||||
import org.eclipse.jgit.errors.{ConfigInvalidException, IncorrectObjectTypeException, MissingObjectException}
|
||||
import org.eclipse.jgit.transport.RefSpec
|
||||
|
||||
import java.util.Date
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
import org.cache2k.Cache2kBuilder
|
||||
import org.cache2k.{Cache, Cache2kBuilder}
|
||||
import org.eclipse.jgit.api.errors._
|
||||
import org.eclipse.jgit.diff.{DiffEntry, DiffFormatter, RawTextComparator}
|
||||
import org.eclipse.jgit.dircache.DirCacheEntry
|
||||
@@ -40,6 +39,9 @@ object JGitUtil {
|
||||
private implicit val objectDatabaseReleasable: Releasable[ObjectDatabase] =
|
||||
_.close()
|
||||
|
||||
private def isCacheEnabled(): Boolean =
|
||||
!ConfigUtil.getConfigValue[Boolean]("gitbucket.disableCache").getOrElse(false)
|
||||
|
||||
/**
|
||||
* The repository data.
|
||||
*
|
||||
@@ -284,26 +286,34 @@ object JGitUtil {
|
||||
revCommit
|
||||
}
|
||||
|
||||
private val cache = new Cache2kBuilder[String, Int]() {}
|
||||
.name("commit-count")
|
||||
.expireAfterWrite(24, TimeUnit.HOURS)
|
||||
.entryCapacity(10000)
|
||||
.build()
|
||||
private val cache: Cache[String, Int] = if (isCacheEnabled()) {
|
||||
Cache2kBuilder
|
||||
.of(classOf[String], classOf[Int])
|
||||
.name("commit-count")
|
||||
.expireAfterWrite(24, TimeUnit.HOURS)
|
||||
.entryCapacity(10000)
|
||||
.build()
|
||||
} else null
|
||||
|
||||
private val objectCommitCache = new Cache2kBuilder[ObjectId, RevCommit]() {}
|
||||
.name("object-commit")
|
||||
.entryCapacity(10000)
|
||||
.build()
|
||||
private val objectCommitCache: Cache[ObjectId, RevCommit] = if (isCacheEnabled()) {
|
||||
Cache2kBuilder
|
||||
.of(classOf[ObjectId], classOf[RevCommit])
|
||||
.name("object-commit")
|
||||
.entryCapacity(10000)
|
||||
.build()
|
||||
} else null
|
||||
|
||||
def removeCache(git: Git): Unit = {
|
||||
val dir = git.getRepository.getDirectory
|
||||
val keyPrefix = dir.getAbsolutePath + "@"
|
||||
if (isCacheEnabled()) {
|
||||
val dir = git.getRepository.getDirectory
|
||||
val keyPrefix = dir.getAbsolutePath + "@"
|
||||
|
||||
cache.keys.forEach(key => {
|
||||
if (key.startsWith(keyPrefix)) {
|
||||
cache.remove(key)
|
||||
}
|
||||
})
|
||||
cache.keys.forEach(key => {
|
||||
if (key.startsWith(keyPrefix)) {
|
||||
cache.remove(key)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -312,16 +322,23 @@ object JGitUtil {
|
||||
*/
|
||||
def getCommitCount(git: Git, branch: String, max: Int = 10001): Int = {
|
||||
val dir = git.getRepository.getDirectory
|
||||
val key = dir.getAbsolutePath + "@" + branch
|
||||
val entry = cache.getEntry(key)
|
||||
|
||||
if (entry == null) {
|
||||
if (isCacheEnabled()) {
|
||||
val key = dir.getAbsolutePath + "@" + branch
|
||||
val entry = cache.getEntry(key)
|
||||
|
||||
if (entry == null) {
|
||||
val commitId = git.getRepository.resolve(branch)
|
||||
val commitCount = git.log.add(commitId).call.iterator.asScala.take(max).size
|
||||
cache.put(key, commitCount)
|
||||
commitCount
|
||||
} else {
|
||||
entry.getValue
|
||||
}
|
||||
} else {
|
||||
val commitId = git.getRepository.resolve(branch)
|
||||
val commitCount = git.log.add(commitId).call.iterator.asScala.take(max).size
|
||||
cache.put(key, commitCount)
|
||||
commitCount
|
||||
} else {
|
||||
entry.getValue
|
||||
}
|
||||
}
|
||||
|
||||
@@ -444,7 +461,7 @@ object JGitUtil {
|
||||
(id, mode, name, path, opt, None)
|
||||
} else if (commitCount < 10000) {
|
||||
(id, mode, name, path, opt, Some(getCommit(path)))
|
||||
} else {
|
||||
} else if (isCacheEnabled()) {
|
||||
// Use in-memory cache if the commit count is too big.
|
||||
val cached = objectCommitCache.getEntry(id)
|
||||
if (cached == null) {
|
||||
@@ -454,6 +471,9 @@ object JGitUtil {
|
||||
} else {
|
||||
(id, mode, name, path, opt, Some(cached.getValue))
|
||||
}
|
||||
} else {
|
||||
val commit = getCommit(path)
|
||||
(id, mode, name, path, opt, Some(commit))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -872,10 +892,11 @@ object JGitUtil {
|
||||
.reverse
|
||||
}
|
||||
|
||||
def initRepository(dir: java.io.File): Unit =
|
||||
Using.resource(new RepositoryBuilder().setGitDir(dir).setBare.build) { repository =>
|
||||
repository.create(true)
|
||||
setReceivePack(repository)
|
||||
def initRepository(dir: java.io.File, defaultBranch: String): Unit =
|
||||
Using.resource(new RepositoryBuilder().setGitDir(dir).setBare().setInitialBranch(defaultBranch).build) {
|
||||
repository =>
|
||||
repository.create(true)
|
||||
setReceivePack(repository)
|
||||
}
|
||||
|
||||
def cloneRepository(from: java.io.File, to: java.io.File): Unit =
|
||||
|
||||
@@ -28,7 +28,12 @@ object Keys {
|
||||
/**
|
||||
* Session key for the OpenID Connect authentication.
|
||||
*/
|
||||
val OidcContext = "oidcContext"
|
||||
val OidcAuthContext = "oidcAuthContext"
|
||||
|
||||
/**
|
||||
* Session key for the OpenID Connect token.
|
||||
*/
|
||||
val OidcSessionContext = "oidcSessionContext"
|
||||
|
||||
/**
|
||||
* Generate session key for the issue search condition.
|
||||
|
||||
@@ -56,6 +56,15 @@
|
||||
<textarea id="information" name="information" class="form-control" style="height: 100px;">@context.settings.information</textarea>
|
||||
</fieldset>
|
||||
<!--====================================================================-->
|
||||
<!-- Default branch -->
|
||||
<!--====================================================================-->
|
||||
<hr>
|
||||
<label for="defaultBranch"><span class="strong">Default branch</span></label>
|
||||
<fieldset>
|
||||
<input type="text" name="defaultBranch" id="defaultBranch" class="form-control" value="@context.settings.defaultBranch"/>
|
||||
<span id="error-defaultBranch" class="error"></span>
|
||||
</fieldset>
|
||||
<!--====================================================================-->
|
||||
<!-- AdminLTE SkinName -->
|
||||
<!--====================================================================-->
|
||||
<hr>
|
||||
|
||||
@@ -5,10 +5,11 @@
|
||||
condition: gitbucket.core.service.IssuesService.IssueSearchCondition,
|
||||
filter: String,
|
||||
groups: List[String],
|
||||
recentRepositories: List[gitbucket.core.service.RepositoryService.RepositoryInfo])(implicit context: gitbucket.core.controller.Context)
|
||||
recentRepositories: List[gitbucket.core.service.RepositoryService.RepositoryInfo],
|
||||
enableNewsFeed: Boolean)(implicit context: gitbucket.core.controller.Context)
|
||||
@gitbucket.core.html.main("Issues"){
|
||||
@gitbucket.core.dashboard.html.sidebar(recentRepositories){
|
||||
@gitbucket.core.dashboard.html.tab("issues")
|
||||
@gitbucket.core.dashboard.html.tab(enableNewsFeed, "issues")
|
||||
<div class="container">
|
||||
@gitbucket.core.dashboard.html.issuesnavi("issues", filter, openCount, closedCount, condition)
|
||||
@gitbucket.core.dashboard.html.issueslist(issues, page, openCount, closedCount, condition, filter, groups)
|
||||
|
||||
@@ -5,10 +5,11 @@
|
||||
condition: gitbucket.core.service.IssuesService.IssueSearchCondition,
|
||||
filter: String,
|
||||
groups: List[String],
|
||||
recentRepositories: List[gitbucket.core.service.RepositoryService.RepositoryInfo])(implicit context: gitbucket.core.controller.Context)
|
||||
recentRepositories: List[gitbucket.core.service.RepositoryService.RepositoryInfo],
|
||||
enableNewsFeed: Boolean)(implicit context: gitbucket.core.controller.Context)
|
||||
@gitbucket.core.html.main("Pull requests"){
|
||||
@gitbucket.core.dashboard.html.sidebar(recentRepositories){
|
||||
@gitbucket.core.dashboard.html.tab("pulls")
|
||||
@gitbucket.core.dashboard.html.tab(enableNewsFeed, "pulls")
|
||||
<div class="container">
|
||||
@gitbucket.core.dashboard.html.issuesnavi("pulls", filter, openCount, closedCount, condition)
|
||||
@gitbucket.core.dashboard.html.issueslist(issues, page, openCount, closedCount, condition, filter, groups)
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
@(groups: List[String],
|
||||
visibleRepositories: List[gitbucket.core.service.RepositoryService.RepositoryInfo],
|
||||
recentRepositories: List[gitbucket.core.service.RepositoryService.RepositoryInfo])(implicit context: gitbucket.core.controller.Context)
|
||||
recentRepositories: List[gitbucket.core.service.RepositoryService.RepositoryInfo],
|
||||
enableNewsFeed: Boolean)(implicit context: gitbucket.core.controller.Context)
|
||||
@import gitbucket.core.view.helpers
|
||||
@gitbucket.core.html.main("Repositories"){
|
||||
@gitbucket.core.dashboard.html.sidebar(recentRepositories){
|
||||
@gitbucket.core.dashboard.html.tab("repos")
|
||||
@gitbucket.core.dashboard.html.tab(enableNewsFeed, "repos")
|
||||
<div class="container">
|
||||
<div class="btn-group" id="owner-dropdown">
|
||||
<button class="dropdown-toggle btn btn-default" data-toggle="dropdown" aria-expanded="false">
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
@(active: String = "")(implicit context: gitbucket.core.controller.Context)
|
||||
<ul class="nav nav-tabs" style="margin-bottom: 20px;">
|
||||
<li @if(active == ""){ class="active"}><a href="@context.path/">News feed</a></li>
|
||||
@if(context.loginAccount.isDefined){
|
||||
<li @if(active == "repos" ){ class="active"}><a href="@context.path/dashboard/repos">Repositories</a></li>
|
||||
<li @if(active == "pulls" ){ class="active"}><a href="@context.path/dashboard/pulls">Pull requests</a></li>
|
||||
<li @if(active == "issues"){ class="active"}><a href="@context.path/dashboard/issues">Issues</a></li>
|
||||
@gitbucket.core.plugin.PluginRegistry().getDashboardTabs.map { tab =>
|
||||
@tab(context).map { link =>
|
||||
<li @if(active == link.id){ class="active"}><a href="@context.path/@link.path">@link.label</a></li>
|
||||
@(enableNewsFeed: Boolean, active: String = "")(implicit context: gitbucket.core.controller.Context)
|
||||
@if(enableNewsFeed || context.loginAccount.isDefined) {
|
||||
<ul class="nav nav-tabs" style="margin-bottom: 20px;">
|
||||
@if(enableNewsFeed) {
|
||||
<li @if(active == "") {class="active"}><a href="@context.path/">News feed</a></li>
|
||||
}
|
||||
@if(context.loginAccount.isDefined) {
|
||||
<li @if(active == "repos") {class="active"}><a href="@context.path/dashboard/repos">Repositories</a></li>
|
||||
<li @if(active == "pulls") {class="active"}><a href="@context.path/dashboard/pulls">Pull requests</a></li>
|
||||
<li @if(active == "issues") {class="active"}><a href="@context.path/dashboard/issues">Issues</a></li>
|
||||
@gitbucket.core.plugin.PluginRegistry().getDashboardTabs.map { tab =>
|
||||
@tab(context).map { link =>
|
||||
<li @if(active == link.id) {class="active"}><a href="@context.path/@link.path">@link.label</a></li>
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</ul>
|
||||
</ul>
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
@(activities: List[gitbucket.core.model.Activity],
|
||||
recentRepositories: List[gitbucket.core.service.RepositoryService.RepositoryInfo],
|
||||
showBannerToCreatePersonalAccessToken: Boolean)(implicit context: gitbucket.core.controller.Context)
|
||||
showBannerToCreatePersonalAccessToken: Boolean,
|
||||
enableNewsFeed: Boolean)(implicit context: gitbucket.core.controller.Context)
|
||||
@import gitbucket.core.view.helpers
|
||||
@gitbucket.core.html.main("GitBucket"){
|
||||
@gitbucket.core.dashboard.html.sidebar(recentRepositories){
|
||||
@@ -19,12 +20,26 @@
|
||||
</a> and use it in place of a password on the <code>git</code> command line.
|
||||
</div>
|
||||
}
|
||||
@gitbucket.core.dashboard.html.tab()
|
||||
@gitbucket.core.dashboard.html.tab(enableNewsFeed)
|
||||
<div class="container">
|
||||
<div class="pull-right">
|
||||
<a href="@context.path/activities.atom"><img src="@helpers.assets("/common/images/feed.png")" alt="activities"></a>
|
||||
</div>
|
||||
@gitbucket.core.helper.html.activities(activities)
|
||||
@if(enableNewsFeed) {
|
||||
<div class="pull-right">
|
||||
<a href="@context.path/activities.atom"><img src="@helpers.assets("/common/images/feed.png")" alt="activities"></a>
|
||||
</div>
|
||||
@gitbucket.core.helper.html.activities(activities)
|
||||
} else {
|
||||
<div class="signin-form">
|
||||
@if(context.settings.basicBehavior.allowAnonymousAccess){
|
||||
@context.settings.information.map { information =>
|
||||
<div class="alert alert-info" style="background-color: white; color: #555; border-color: #4183c4; font-size: small; line-height: 120%;">
|
||||
<button type="button" class="close" data-dismiss="alert">×</button>
|
||||
@Html(information)
|
||||
</div>
|
||||
}
|
||||
}
|
||||
@gitbucket.core.html.signinform(context.settings)
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,8 +140,8 @@
|
||||
}
|
||||
</div>
|
||||
<span id="label-assigned">
|
||||
@issueAssignees.map { asignee =>
|
||||
<div>@helpers.avatarLink(asignee.assigneeUserName, 20) @helpers.user(asignee.assigneeUserName, styleClass="username strong small")</div>
|
||||
@issueAssignees.map { assignee =>
|
||||
<div>@helpers.avatarLink(assignee.assigneeUserName, 20) @helpers.user(assignee.assigneeUserName, styleClass="username strong small")</div>
|
||||
}
|
||||
@if(issueAssignees.isEmpty) {
|
||||
<span class="muted small">No one assigned</span>
|
||||
@@ -158,10 +158,10 @@
|
||||
<div class="pull-right">
|
||||
@gitbucket.core.model.CustomFieldBehavior(field.fieldType).map { behavior =>
|
||||
@if(issue.nonEmpty) {
|
||||
@Html(behavior.fieldHtml(repository, issue.get.issueId, field.fieldId, value.map(_.value).getOrElse(""), isManageable))
|
||||
@Html(behavior.fieldHtml(repository, issue.get.issueId, field.fieldId, field.fieldName, field.constraints, value.map(_.value).getOrElse(""), isManageable))
|
||||
}
|
||||
@if(issue.isEmpty) {
|
||||
@Html(behavior.createHtml(repository, field.fieldId))
|
||||
@Html(behavior.createHtml(repository, field.fieldId, field.fieldName, field.constraints))
|
||||
}
|
||||
}
|
||||
</div>
|
||||
|
||||
@@ -24,9 +24,9 @@
|
||||
<a href="@condition.copy(state = "closed").toURL">Closed <span class="badge">@closedCount</span></a>
|
||||
</li>
|
||||
</ul>
|
||||
<form method="GET" action="@helpers.url(repository)/search" id="search-filter-form" class="form-inline pull-right" autocomplete="off">
|
||||
<form method="GET" action="@helpers.url(repository)/@target" id="search-filter-form" class="form-inline pull-right" autocomplete="off">
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" name="q" placeholder="Search..." aria-label="Search all issues"/>
|
||||
<input type="text" class="form-control" name="q" placeholder="Search..." aria-label="Search all issues" value="@condition.toFilterString" style="width: 300px;"/>
|
||||
<input type="hidden" name="type" value="@target"/>
|
||||
<span class="input-group-btn">
|
||||
<button type="submit" id="search-btn" class="btn btn-default" aria-label="Search all issues"><i class="fa fa-search"></i></button>
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<div class="branch-action">
|
||||
@if(repository.repository.defaultBranch != branch.name){
|
||||
@if(repository.repository.defaultBranch != branch.name || repository.repository.originUserName.isDefined){
|
||||
@branch.mergeInfo.map{ info =>
|
||||
@prs.map{ case (pull, issue) =>
|
||||
<a href="@helpers.url(repository)/pull/@issue.issueId" title="@issue.title">#@issue.issueId</a>
|
||||
@@ -76,13 +76,14 @@
|
||||
}
|
||||
@if(hasWritePermission){
|
||||
<span style="margin-left: 8px;">
|
||||
@if(prs.map(!_._2.closed).getOrElse(false)){
|
||||
@if(prs.exists(!_._2.closed)){
|
||||
<a class="disabled" data-toggle="tooltip" title="You can’t delete this branch because it has an open pull request"><i class="octicon octicon-trashcan"></i></a>
|
||||
} else {
|
||||
@if(isProtected){
|
||||
@if(isProtected || repository.repository.defaultBranch == branch.name) {
|
||||
<a class="disabled" data-toggle="tooltip" title="You can’t delete a protected branch."><i class="octicon octicon-trashcan"></i></a>
|
||||
} else {
|
||||
<a href="@helpers.url(repository)/delete/@helpers.encodeRefName(branch.name)" class="delete-branch" data-name="@branch.name" @if(info.isMerged){ data-toggle="tooltip" title="this branch is merged" }><i class="octicon octicon-trashcan @if(info.isMerged){warning} else {danger}"></i></a>
|
||||
<a href="@helpers.url(repository)/delete/@helpers.encodeRefName(branch.name)" class="delete-branch" data-name="@branch.name" @if(info.isMerged) {
|
||||
data-toggle="tooltip" title="this branch is merged" }><i class="octicon octicon-trashcan @if(info.isMerged) {warning} else {danger}"></i></a>
|
||||
}
|
||||
}
|
||||
</span>
|
||||
@@ -99,7 +100,7 @@
|
||||
<script>
|
||||
$(function(){
|
||||
$('.delete-branch').click(function(e){
|
||||
var branchName = $(e.target).closest('a').data('name');
|
||||
const branchName = $(e.target).closest('a').data('name');
|
||||
return confirm('Are you sure you want to remove the ' + branchName + ' branch?');
|
||||
});
|
||||
$('*[data-toggle=tooltip]').tooltip().css("white-space","nowrap");
|
||||
|
||||
@@ -21,12 +21,12 @@
|
||||
git add README.md
|
||||
git commit -m "first commit"
|
||||
git remote add origin <span class="live-clone-url">@repository.httpUrl</span>
|
||||
git push -u origin master
|
||||
git push -u origin @context.settings.defaultBranch
|
||||
}
|
||||
<h3 style="margin-top: 30px;">Push an existing repository from the command line</h3>
|
||||
@helpers.pre {
|
||||
git remote add origin <span class="live-clone-url">@repository.httpUrl</span>
|
||||
git push -u origin master
|
||||
git push -u origin @context.settings.defaultBranch
|
||||
}
|
||||
<script>
|
||||
$(function(){
|
||||
@@ -38,4 +38,4 @@
|
||||
</script>
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,9 @@
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
@customField.fieldType
|
||||
@customField.constraints.map { constraints =>
|
||||
(@constraints)
|
||||
}
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
@if(customField.enableForIssues) {
|
||||
|
||||
@@ -10,7 +10,9 @@
|
||||
<option value="double" @if(field.map(_.fieldType == "double").getOrElse(false)){selected}>Double</option>
|
||||
<option value="string" @if(field.map(_.fieldType == "string").getOrElse(false)){selected}>String</option>
|
||||
<option value="date" @if(field.map(_.fieldType == "date").getOrElse(false)){selected}>Date</option>
|
||||
<option value="enum" @if(field.map(_.fieldType == "enum").getOrElse(false)){selected}>Enum</option>
|
||||
</select>
|
||||
<input type="text" id="constraints-@fieldId" style="width: 300px; @if(!field.exists(_.fieldType == "enum")){display: none;}" class="form-control input-sm" value="@field.map(_.constraints)" placeholder="Comma-separated enum values">
|
||||
<label for="enableForIssues-@fieldId" class="normal" style="margin-left: 4px;">
|
||||
<input type="checkbox" id="enableForIssues-@fieldId" @if(field.map(_.enableForIssues).getOrElse(false)){checked}> Issues
|
||||
</label>
|
||||
@@ -30,6 +32,7 @@
|
||||
$.post('@helpers.url(repository)/settings/issues/fields/@{if(fieldId == "new") "new" else s"$fieldId/edit"}', {
|
||||
'fieldName' : $('#fieldName-@fieldId').val(),
|
||||
'fieldType': $('#fieldType-@fieldId option:selected').val(),
|
||||
'constraints': $('#constraints-@fieldId').val(),
|
||||
'enableForIssues': $('#enableForIssues-@fieldId').prop('checked'),
|
||||
'enableForPullRequests': $('#enableForPullRequests-@fieldId').prop('checked')
|
||||
}, function(data, status){
|
||||
@@ -61,6 +64,14 @@
|
||||
$('#field-@fieldId').show();
|
||||
}
|
||||
});
|
||||
|
||||
$('#fieldType-@fieldId').change(function(){
|
||||
if($(this).val() == 'enum') {
|
||||
$('#constraints-@fieldId').show();
|
||||
} else {
|
||||
$('#constraints-@fieldId').hide();
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
}
|
||||
|
||||
@@ -24,15 +24,16 @@ class GitBucketCoreModuleSpec extends AnyFunSuite {
|
||||
)
|
||||
}
|
||||
|
||||
implicit private val suiteDescription = Description.createSuiteDescription(getClass)
|
||||
implicit private val suiteDescription: Description = Description.createSuiteDescription(getClass)
|
||||
|
||||
Seq("8.0", "5.7").foreach { tag =>
|
||||
test(s"Migration MySQL $tag", ExternalDBTest) {
|
||||
val container = new MySQLContainer() {
|
||||
override val container = new org.testcontainers.containers.MySQLContainer(s"mysql:$tag") {
|
||||
override def getDriverClassName = "org.mariadb.jdbc.Driver"
|
||||
override def getJdbcUrl: String = super.getJdbcUrl + "?permitMysqlScheme"
|
||||
}
|
||||
override val container: org.testcontainers.containers.MySQLContainer[_] =
|
||||
new org.testcontainers.containers.MySQLContainer(s"mysql:$tag") {
|
||||
override def getDriverClassName = "org.mariadb.jdbc.Driver"
|
||||
override def getJdbcUrl: String = super.getJdbcUrl + "?permitMysqlScheme"
|
||||
}
|
||||
// TODO https://jira.mariadb.org/browse/CONJ-663
|
||||
container.withCommand("mysqld --default-authentication-plugin=mysql_native_password")
|
||||
}
|
||||
|
||||
@@ -9,12 +9,19 @@ import scala.util.Using
|
||||
import org.kohsuke.github.GHCommitState
|
||||
|
||||
import java.io.File
|
||||
import java.util.logging.{Level, Logger}
|
||||
|
||||
/**
|
||||
* Need to run `sbt package` before running this test.
|
||||
*/
|
||||
class ApiIntegrationTest extends AnyFunSuite {
|
||||
|
||||
// Suppress warning logs caused by liquibase
|
||||
private val liquibaseResourceLogger = Logger.getLogger("liquibase.resource")
|
||||
liquibaseResourceLogger.setLevel(Level.SEVERE)
|
||||
private val liquibaseParserLogger = Logger.getLogger("liquibase.parser")
|
||||
liquibaseParserLogger.setLevel(Level.SEVERE)
|
||||
|
||||
test("create repository") {
|
||||
Using.resource(new TestingGitBucketServer(19999)) { server =>
|
||||
val github = server.client("root", "root")
|
||||
@@ -29,7 +36,7 @@ class ApiIntegrationTest extends AnyFunSuite {
|
||||
|
||||
assert(repository.getName == "test")
|
||||
assert(repository.getDescription == "test repository")
|
||||
assert(repository.getDefaultBranch == "master")
|
||||
assert(repository.getDefaultBranch == "main")
|
||||
assert(repository.getWatchers == 0)
|
||||
assert(repository.getWatchersCount == 0)
|
||||
assert(repository.getForks == 0)
|
||||
@@ -48,7 +55,7 @@ class ApiIntegrationTest extends AnyFunSuite {
|
||||
val repository = repositories.get(0)
|
||||
assert(repository.getName == "test")
|
||||
assert(repository.getDescription == "test repository")
|
||||
assert(repository.getDefaultBranch == "master")
|
||||
assert(repository.getDefaultBranch == "main")
|
||||
assert(repository.getWatchers == 0)
|
||||
assert(repository.getWatchersCount == 0)
|
||||
assert(repository.getForks == 0)
|
||||
@@ -68,7 +75,7 @@ class ApiIntegrationTest extends AnyFunSuite {
|
||||
val github = server.client("root", "root")
|
||||
|
||||
val repo = github.createRepository("create_status_test").autoInit(true).create()
|
||||
val sha1 = repo.getBranch("master").getSHA1
|
||||
val sha1 = repo.getBranch("main").getSHA1
|
||||
|
||||
{
|
||||
val status = repo.getLastCommitStatus(sha1)
|
||||
@@ -140,10 +147,10 @@ class ApiIntegrationTest extends AnyFunSuite {
|
||||
|
||||
// get master ref
|
||||
{
|
||||
val ref = repo.getRef("heads/master")
|
||||
assert(ref.getRef == "refs/heads/master")
|
||||
val ref = repo.getRef("heads/main")
|
||||
assert(ref.getRef == "refs/heads/main")
|
||||
assert(
|
||||
ref.getUrl.toString == "http://localhost:19999/api/v3/repos/root/create_status_test/git/refs/heads/master"
|
||||
ref.getUrl.toString == "http://localhost:19999/api/v3/repos/root/create_status_test/git/refs/heads/main"
|
||||
)
|
||||
assert(ref.getObject.getType == "commit")
|
||||
}
|
||||
@@ -169,7 +176,7 @@ class ApiIntegrationTest extends AnyFunSuite {
|
||||
val createResult =
|
||||
repo
|
||||
.createContent()
|
||||
.branch("master")
|
||||
.branch("main")
|
||||
.content("create")
|
||||
.message("Create content")
|
||||
.path("README.md")
|
||||
@@ -186,7 +193,7 @@ class ApiIntegrationTest extends AnyFunSuite {
|
||||
val updateResult =
|
||||
repo
|
||||
.createContent()
|
||||
.branch("master")
|
||||
.branch("main")
|
||||
.content("update")
|
||||
.message("Update content")
|
||||
.path("README.md")
|
||||
@@ -204,4 +211,100 @@ class ApiIntegrationTest extends AnyFunSuite {
|
||||
}
|
||||
}
|
||||
|
||||
test("issue labels") {
|
||||
Using.resource(new TestingGitBucketServer(19999)) { server =>
|
||||
val github = server.client("root", "root")
|
||||
|
||||
val repo = github.createRepository("issue_label_test").autoInit(true).create()
|
||||
val issue = repo.createIssue("test").create()
|
||||
|
||||
// Initial label state
|
||||
{
|
||||
val labels = repo.getIssue(issue.getNumber).getLabels
|
||||
assert(labels.size() == 0)
|
||||
}
|
||||
|
||||
// Add labels
|
||||
{
|
||||
issue.addLabels("bug", "duplicate")
|
||||
|
||||
val labels = repo.getIssue(issue.getNumber).getLabels
|
||||
assert(labels.size() == 2)
|
||||
|
||||
val i = labels.iterator()
|
||||
val label1 = i.next()
|
||||
assert(label1.getName == "bug")
|
||||
assert(label1.getColor == "fc2929")
|
||||
assert(label1.getUrl == "http://localhost:19999/api/v3/repos/root/issue_label_test/labels/bug")
|
||||
|
||||
val label2 = i.next()
|
||||
assert(label2.getName == "duplicate")
|
||||
assert(label2.getColor == "cccccc")
|
||||
assert(label2.getUrl == "http://localhost:19999/api/v3/repos/root/issue_label_test/labels/duplicate")
|
||||
}
|
||||
|
||||
// Remove a label
|
||||
{
|
||||
issue.removeLabel("duplicate")
|
||||
|
||||
val labels = repo.getIssue(issue.getNumber).getLabels
|
||||
assert(labels.size() == 1)
|
||||
|
||||
val i = labels.iterator()
|
||||
val label1 = i.next()
|
||||
assert(label1.getName == "bug")
|
||||
assert(label1.getColor == "fc2929")
|
||||
assert(label1.getUrl == "http://localhost:19999/api/v3/repos/root/issue_label_test/labels/bug")
|
||||
}
|
||||
|
||||
// Replace labels (Cannot test because GHLabel.setLabels() doesn't use the replace endpoint)
|
||||
// {
|
||||
// issue.setLabels("enhancement", "invalid", "question")
|
||||
//
|
||||
// val labels = repo.getIssue(issue.getNumber).getLabels
|
||||
// assert(labels.size() == 3)
|
||||
//
|
||||
// val i = labels.iterator()
|
||||
// val label1 = i.next()
|
||||
// assert(label1.getName == "enhancement")
|
||||
// assert(label1.getColor == "84b6eb")
|
||||
// assert(label1.getUrl == "http://localhost:19999/api/v3/repos/root/issue_label_test/labels/enhancement")
|
||||
//
|
||||
// val label2 = i.next()
|
||||
// assert(label2.getName == "invalid")
|
||||
// assert(label2.getColor == "e6e6e6")
|
||||
// assert(label2.getUrl == "http://localhost:19999/api/v3/repos/root/issue_label_test/labels/invalid")
|
||||
//
|
||||
// val label3 = i.next()
|
||||
// assert(label3.getName == "question")
|
||||
// assert(label3.getColor == "cc317c")
|
||||
// assert(label3.getUrl == "http://localhost:19999/api/v3/repos/root/issue_label_test/labels/question")
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
test("Git refs APIs") {
|
||||
Using.resource(new TestingGitBucketServer(19999)) { server =>
|
||||
val github = server.client("root", "root")
|
||||
|
||||
val repo = github.createRepository("git_refs_test").autoInit(true).create()
|
||||
val sha1 = repo.getBranch("main").getSHA1
|
||||
|
||||
val refs1 = repo.listRefs().toList
|
||||
assert(refs1.size() == 1)
|
||||
assert(refs1.get(0).getRef == "refs/heads/main")
|
||||
assert(refs1.get(0).getObject.getSha == sha1)
|
||||
|
||||
val ref = repo.createRef("refs/heads/testref", sha1)
|
||||
assert(ref.getRef == "refs/heads/testref")
|
||||
assert(ref.getObject.getSha == sha1)
|
||||
|
||||
val refs2 = repo.listRefs().toList
|
||||
assert(refs2.size() == 2)
|
||||
assert(refs2.get(0).getRef == "refs/heads/main")
|
||||
assert(refs2.get(0).getObject.getSha == sha1)
|
||||
assert(refs2.get(1).getRef == "refs/heads/testref")
|
||||
assert(refs2.get(1).getObject.getSha == sha1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ import org.eclipse.jgit.lib.ObjectId
|
||||
|
||||
object ApiSpecModels {
|
||||
|
||||
implicit val context = JsonFormat.Context("http://gitbucket.exmple.com", None)
|
||||
implicit val context: JsonFormat.Context = JsonFormat.Context("http://gitbucket.exmple.com", None)
|
||||
|
||||
val date1 = {
|
||||
val d = Calendar.getInstance(TimeZone.getTimeZone("UTC"))
|
||||
@@ -53,7 +53,7 @@ object ApiSpecModels {
|
||||
repositoryName = repo1Name.name,
|
||||
isPrivate = false,
|
||||
description = Some("This your first repo!"),
|
||||
defaultBranch = "master",
|
||||
defaultBranch = "main",
|
||||
registeredDate = date1,
|
||||
updatedDate = date1,
|
||||
lastActivityDate = date1,
|
||||
@@ -81,7 +81,7 @@ object ApiSpecModels {
|
||||
pullCount = 1,
|
||||
forkedCount = 1,
|
||||
milestoneCount = 1,
|
||||
branchList = Seq("master", "develop"),
|
||||
branchList = Seq("main", "develop"),
|
||||
tags = Seq(
|
||||
TagInfo(
|
||||
name = "v1.0",
|
||||
@@ -147,7 +147,7 @@ object ApiSpecModels {
|
||||
userName = repo1Name.owner,
|
||||
repositoryName = repo1Name.name,
|
||||
issueId = issuePR.issueId,
|
||||
branch = "master",
|
||||
branch = "main",
|
||||
requestUserName = "bear",
|
||||
requestRepositoryName = repo1Name.name,
|
||||
requestBranch = "new-topic",
|
||||
@@ -363,7 +363,7 @@ object ApiSpecModels {
|
||||
info = ProtectedBranchInfo(
|
||||
owner = repo1Name.owner,
|
||||
repository = repo1Name.name,
|
||||
branch = "master",
|
||||
branch = "main",
|
||||
enabled = true,
|
||||
contexts = Seq("continuous-integration/travis-ci"),
|
||||
includeAdministrators = true
|
||||
@@ -384,7 +384,7 @@ object ApiSpecModels {
|
||||
)
|
||||
|
||||
val apiBranch = ApiBranch(
|
||||
name = "master",
|
||||
name = "main",
|
||||
commit = ApiBranchCommit(sha1),
|
||||
protection = apiBranchProtectionOutput
|
||||
)(
|
||||
@@ -392,7 +392,7 @@ object ApiSpecModels {
|
||||
)
|
||||
|
||||
val apiBranchForList = ApiBranchForList(
|
||||
name = "master",
|
||||
name = "main",
|
||||
commit = ApiBranchCommit(sha1)
|
||||
)
|
||||
|
||||
@@ -447,8 +447,8 @@ object ApiSpecModels {
|
||||
val gitHubContext = JsonFormat.Context("https://api.github.com", Some("https://api.github.com"))
|
||||
|
||||
val apiRefHeadsMaster = ApiRef(
|
||||
ref = "refs/heads/master",
|
||||
url = ApiPath("/repos/gitbucket/gitbucket/git/refs/heads/master"),
|
||||
ref = "refs/heads/main",
|
||||
url = ApiPath("/repos/gitbucket/gitbucket/git/refs/heads/main"),
|
||||
node_id = "MDM6UmVmOTM1MDc0NjpyZWZzL2hlYWRzL21hc3Rlcg==",
|
||||
`object` = ApiRefCommit(
|
||||
sha = "6b2d124d092402f2c2b7131caada05ead9e7de6d",
|
||||
@@ -508,7 +508,7 @@ object ApiSpecModels {
|
||||
|"watchers":0,
|
||||
|"forks":1,
|
||||
|"private":false,
|
||||
|"default_branch":"master",
|
||||
|"default_branch":"main",
|
||||
|"owner":$jsonUser,
|
||||
|"has_issues":true,
|
||||
|"id":0,
|
||||
@@ -594,7 +594,7 @@ object ApiSpecModels {
|
||||
|"updated_at":"2011-04-14T16:00:49Z",
|
||||
|"created_at":"2011-04-14T16:00:49Z",
|
||||
|"head":{"sha":"6dcb09b5b57875f334f61aebed695e2e4193db5e","ref":"new-topic","repo":$jsonRepository,"label":"new-topic","user":$jsonUser},
|
||||
|"base":{"sha":"6dcb09b5b57875f334f61aebed695e2e4193db5e","ref":"master","repo":$jsonRepository,"label":"master","user":$jsonUser},
|
||||
|"base":{"sha":"6dcb09b5b57875f334f61aebed695e2e4193db5e","ref":"main","repo":$jsonRepository,"label":"main","user":$jsonUser},
|
||||
|"merged":true,
|
||||
|"merged_at":"2011-04-14T16:00:49Z",
|
||||
|"merged_by":$jsonUser,
|
||||
@@ -730,13 +730,13 @@ object ApiSpecModels {
|
||||
|
||||
val jsonBranchProtectionOutput =
|
||||
"""{
|
||||
|"url":"http://gitbucket.exmple.com/api/v3/repos/octocat/Hello-World/branches/master/protection",
|
||||
|"url":"http://gitbucket.exmple.com/api/v3/repos/octocat/Hello-World/branches/main/protection",
|
||||
|"enabled":true,
|
||||
|"required_status_checks":{
|
||||
|"url":"http://gitbucket.exmple.com/api/v3/repos/octocat/Hello-World/branches/master/protection/required_status_checks",
|
||||
|"url":"http://gitbucket.exmple.com/api/v3/repos/octocat/Hello-World/branches/main/protection/required_status_checks",
|
||||
|"enforcement_level":"everyone",
|
||||
|"contexts":["continuous-integration/travis-ci"],
|
||||
|"contexts_url":"http://gitbucket.exmple.com/api/v3/repos/octocat/Hello-World/branches/master/protection/required_status_checks/contexts"}
|
||||
|"contexts_url":"http://gitbucket.exmple.com/api/v3/repos/octocat/Hello-World/branches/main/protection/required_status_checks/contexts"}
|
||||
|}""".stripMargin
|
||||
|
||||
val jsonBranchProtectionInput =
|
||||
@@ -749,15 +749,15 @@ object ApiSpecModels {
|
||||
|}""".stripMargin
|
||||
|
||||
val jsonBranch = s"""{
|
||||
|"name":"master",
|
||||
|"name":"main",
|
||||
|"commit":{"sha":"6dcb09b5b57875f334f61aebed695e2e4193db5e"},
|
||||
|"protection":$jsonBranchProtectionOutput,
|
||||
|"_links":{
|
||||
|"self":"http://gitbucket.exmple.com/api/v3/repos/octocat/Hello-World/branches/master",
|
||||
|"html":"http://gitbucket.exmple.com/octocat/Hello-World/tree/master"}
|
||||
|"self":"http://gitbucket.exmple.com/api/v3/repos/octocat/Hello-World/branches/main",
|
||||
|"html":"http://gitbucket.exmple.com/octocat/Hello-World/tree/main"}
|
||||
|}""".stripMargin
|
||||
|
||||
val jsonBranchForList = """{"name":"master","commit":{"sha":"6dcb09b5b57875f334f61aebed695e2e4193db5e"}}"""
|
||||
val jsonBranchForList = """{"name":"main","commit":{"sha":"6dcb09b5b57875f334f61aebed695e2e4193db5e"}}"""
|
||||
|
||||
val jsonContents =
|
||||
"""{
|
||||
@@ -800,11 +800,11 @@ object ApiSpecModels {
|
||||
//I checked all refs in gitbucket repo, and there appears to be only type "commit" and type "tag"
|
||||
val jsonRef = """{"ref":"refs/heads/featureA","object":{"sha":"6dcb09b5b57875f334f61aebed695e2e4193db5e"}}"""
|
||||
|
||||
val jsonRefHeadsMaster =
|
||||
val jsonRefHeadsMain =
|
||||
"""{
|
||||
|"ref": "refs/heads/master",
|
||||
|"ref": "refs/heads/main",
|
||||
|"node_id": "MDM6UmVmOTM1MDc0NjpyZWZzL2hlYWRzL21hc3Rlcg==",
|
||||
|"url": "https://api.github.com/repos/gitbucket/gitbucket/git/refs/heads/master",
|
||||
|"url": "https://api.github.com/repos/gitbucket/gitbucket/git/refs/heads/main",
|
||||
|"object": {
|
||||
|"sha": "6b2d124d092402f2c2b7131caada05ead9e7de6d",
|
||||
|"type": "commit",
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
package gitbucket.core.api
|
||||
|
||||
import org.json4s.Formats
|
||||
import org.json4s.jackson.JsonMethods
|
||||
import org.scalatest.funsuite.AnyFunSuite
|
||||
|
||||
class JsonFormatSpec extends AnyFunSuite {
|
||||
import ApiSpecModels._
|
||||
implicit val format = JsonFormat.jsonFormats
|
||||
implicit val format: Formats = JsonFormat.jsonFormats
|
||||
|
||||
private def expected(json: String) = json.replaceAll("\n", "")
|
||||
def normalizeJson(json: String) = {
|
||||
@@ -83,7 +84,7 @@ class JsonFormatSpec extends AnyFunSuite {
|
||||
assert(JsonFormat(apiPusher) == expected(jsonPusher))
|
||||
}
|
||||
test("apiRefHead") {
|
||||
assertEqualJson(JsonFormat(apiRefHeadsMaster)(gitHubContext), jsonRefHeadsMaster)
|
||||
assertEqualJson(JsonFormat(apiRefHeadsMaster)(gitHubContext), jsonRefHeadsMain)
|
||||
}
|
||||
test("apiRefTag") {
|
||||
assertEqualJson(JsonFormat(apiRefTag)(gitHubContext), jsonRefTag)
|
||||
|
||||
@@ -40,7 +40,7 @@ class CommitStatusServiceSpec
|
||||
test("createCommitState can insert and update") {
|
||||
withTestDB { implicit session =>
|
||||
val tester = generateNewAccount(fixture1.creator)
|
||||
insertRepository(fixture1.repositoryName, fixture1.userName, None, false)
|
||||
insertRepository(fixture1.repositoryName, fixture1.userName, None, false, "main")
|
||||
val id = generateFixture1(tester: Account)
|
||||
assert(
|
||||
getCommitStatus(fixture1.userName, fixture1.repositoryName, id) == Some(fixture1.copy(commitStatusId = id))
|
||||
@@ -77,7 +77,7 @@ class CommitStatusServiceSpec
|
||||
test("getCommitStatus can find by commitId and context") {
|
||||
withTestDB { implicit session =>
|
||||
val tester = generateNewAccount(fixture1.creator)
|
||||
insertRepository(fixture1.repositoryName, fixture1.userName, None, false)
|
||||
insertRepository(fixture1.repositoryName, fixture1.userName, None, false, "main")
|
||||
val id = generateFixture1(tester: Account)
|
||||
assert(
|
||||
getCommitStatus(fixture1.userName, fixture1.repositoryName, fixture1.commitId, fixture1.context) == Some(
|
||||
@@ -90,7 +90,7 @@ class CommitStatusServiceSpec
|
||||
test("getCommitStatus can find by commitStatusId") {
|
||||
withTestDB { implicit session =>
|
||||
val tester = generateNewAccount(fixture1.creator)
|
||||
insertRepository(fixture1.repositoryName, fixture1.userName, None, false)
|
||||
insertRepository(fixture1.repositoryName, fixture1.userName, None, false, "main")
|
||||
val id = generateFixture1(tester: Account)
|
||||
assert(
|
||||
getCommitStatus(fixture1.userName, fixture1.repositoryName, id) == Some(fixture1.copy(commitStatusId = id))
|
||||
|
||||
@@ -7,7 +7,7 @@ class RepositoryServiceSpec extends AnyFunSuite with ServiceSpecBase with Reposi
|
||||
test("renameRepository can rename CommitState, ProtectedBranches") {
|
||||
withTestDB { implicit session =>
|
||||
val tester = generateNewAccount("tester")
|
||||
insertRepository("repo", "root", None, false)
|
||||
insertRepository("repo", "root", None, false, "main")
|
||||
val service = new CommitStatusService with ProtectedBranchService {}
|
||||
val id = service.createCommitStatus(
|
||||
userName = "root",
|
||||
|
||||
@@ -81,7 +81,8 @@ trait ServiceSpecBase {
|
||||
),
|
||||
repositoryViewer = RepositoryViewerSettings(
|
||||
maxFiles = 0
|
||||
)
|
||||
),
|
||||
defaultBranch = "main"
|
||||
)
|
||||
|
||||
def withTestDB[A](action: (Session) => A): A = {
|
||||
@@ -127,8 +128,8 @@ trait ServiceSpecBase {
|
||||
if (dir.exists()) {
|
||||
FileUtils.deleteQuietly(dir)
|
||||
}
|
||||
JGitUtil.initRepository(dir)
|
||||
dummyService.insertRepository(repositoryName, userName, None, false)
|
||||
JGitUtil.initRepository(dir, "main")
|
||||
dummyService.insertRepository(repositoryName, userName, None, false, "main")
|
||||
ac
|
||||
}
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ class WebHookJsonFormatSpec extends AnyFunSuite {
|
||||
|"description":"This your first repo!",
|
||||
|"ref":"v1.0",
|
||||
|"ref_type":"tag",
|
||||
|"master_branch":"master",
|
||||
|"master_branch":"main",
|
||||
|"repository":$jsonRepository,
|
||||
|"pusher_type":"user"
|
||||
|}""".stripMargin
|
||||
@@ -41,12 +41,12 @@ class WebHookJsonFormatSpec extends AnyFunSuite {
|
||||
createFile(git, Constants.HEAD, "README.md", "body1", message = "initial")
|
||||
createFile(git, Constants.HEAD, "README.md", "body1\nbody2", message = "modified")
|
||||
|
||||
val branchId = git.getRepository.resolve("master")
|
||||
val branchId = git.getRepository.resolve("main")
|
||||
|
||||
val payload = WebHookPushPayload(
|
||||
git = git,
|
||||
sender = account,
|
||||
refName = "refs/heads/master",
|
||||
refName = "refs/heads/main",
|
||||
repositoryInfo = repositoryInfo,
|
||||
commits = List(commitInfo(branchId.name)),
|
||||
repositoryOwner = account,
|
||||
@@ -56,7 +56,7 @@ class WebHookJsonFormatSpec extends AnyFunSuite {
|
||||
val expected = s"""{
|
||||
|"pusher":{"name":"octocat","email":"octocat@example.com"},
|
||||
|"sender":$jsonUser,
|
||||
|"ref":"refs/heads/master",
|
||||
|"ref":"refs/heads/main",
|
||||
|"before":"6dcb09b5b57875f334f61aebed695e2e4193db5e",
|
||||
|"after":"6dcb09b5b57875f334f61aebed695e2e4193db5e",
|
||||
|"commits":[${jsonCommit(branchId.name)}],
|
||||
|
||||
@@ -34,7 +34,7 @@ object GitSpecUtil {
|
||||
RepositoryCache.clear()
|
||||
FileUtils.deleteQuietly(dir)
|
||||
Files.createDirectories(dir.toPath())
|
||||
JGitUtil.initRepository(dir)
|
||||
JGitUtil.initRepository(dir, "main")
|
||||
dir
|
||||
}
|
||||
|
||||
|
||||
@@ -23,14 +23,14 @@ class JGitUtilSpec extends AnyFunSuite {
|
||||
withTestRepository { git =>
|
||||
createFile(git, Constants.HEAD, "README.md", "body1", message = "commit1")
|
||||
|
||||
val branchId = git.getRepository.resolve("master")
|
||||
val branchId = git.getRepository.resolve("main")
|
||||
val commit = JGitUtil.getRevCommitFromId(git, branchId)
|
||||
|
||||
createFile(git, Constants.HEAD, "LICENSE", "Apache License", message = "commit1")
|
||||
createFile(git, Constants.HEAD, "README.md", "body1\nbody2", message = "commit1")
|
||||
|
||||
// latest commit
|
||||
val diff1 = JGitUtil.getDiffs(git, None, "master", false, true)
|
||||
val diff1 = JGitUtil.getDiffs(git, None, "main", false, true)
|
||||
assert(diff1.size == 1)
|
||||
assert(diff1(0).changeType == ChangeType.MODIFY)
|
||||
assert(diff1(0).oldPath == "README.md")
|
||||
@@ -44,7 +44,7 @@ class JGitUtilSpec extends AnyFunSuite {
|
||||
|\ No newline at end of file""".stripMargin))
|
||||
|
||||
// from specified commit
|
||||
val diff2 = JGitUtil.getDiffs(git, Some(commit.getName), "master", false, true)
|
||||
val diff2 = JGitUtil.getDiffs(git, Some(commit.getName), "main", false, true)
|
||||
assert(diff2.size == 2)
|
||||
assert(diff2(0).changeType == ChangeType.ADD)
|
||||
assert(diff2(0).oldPath == "/dev/null")
|
||||
@@ -73,7 +73,7 @@ class JGitUtilSpec extends AnyFunSuite {
|
||||
createFile(git, Constants.HEAD, "README.md", "body1", message = "commit1")
|
||||
|
||||
// branch name
|
||||
val branchId = git.getRepository.resolve("master")
|
||||
val branchId = git.getRepository.resolve("main")
|
||||
val commit1 = JGitUtil.getRevCommitFromId(git, branchId)
|
||||
|
||||
// commit id
|
||||
@@ -97,19 +97,19 @@ class JGitUtilSpec extends AnyFunSuite {
|
||||
withTestRepository { git =>
|
||||
// getCommitCount
|
||||
createFile(git, Constants.HEAD, "README.md", "body1", message = "commit1")
|
||||
assert(JGitUtil.getCommitCount(git, "master") == 1)
|
||||
assert(JGitUtil.getCommitCount(git, "main") == 1)
|
||||
|
||||
createFile(git, Constants.HEAD, "README.md", "body2", message = "commit2")
|
||||
assert(JGitUtil.getCommitCount(git, "master") == 2)
|
||||
assert(JGitUtil.getCommitCount(git, "main") == 2)
|
||||
|
||||
// maximum limit
|
||||
(3 to 10).foreach { i =>
|
||||
createFile(git, Constants.HEAD, "README.md", "body" + i, message = "commit" + i)
|
||||
}
|
||||
assert(JGitUtil.getCommitCount(git, "master", 5) == 5)
|
||||
assert(JGitUtil.getCommitCount(git, "main", 5) == 5)
|
||||
|
||||
// actual commit count
|
||||
val gitLog = git.log.add(git.getRepository.resolve("master")).all
|
||||
val gitLog = git.log.add(git.getRepository.resolve("main")).all
|
||||
assert(gitLog.call.asScala.toSeq.size == 10)
|
||||
|
||||
// getAllCommitIds
|
||||
@@ -123,22 +123,22 @@ class JGitUtilSpec extends AnyFunSuite {
|
||||
createFile(git, Constants.HEAD, "README.md", "body1", message = "commit1")
|
||||
|
||||
// createBranch
|
||||
assert(JGitUtil.createBranch(git, "master", "test1") == Right("Branch created."))
|
||||
assert(JGitUtil.createBranch(git, "master", "test2") == Right("Branch created."))
|
||||
assert(JGitUtil.createBranch(git, "master", "test2") == Left("Sorry, that branch already exists."))
|
||||
assert(JGitUtil.createBranch(git, "main", "test1") == Right("Branch created."))
|
||||
assert(JGitUtil.createBranch(git, "main", "test2") == Right("Branch created."))
|
||||
assert(JGitUtil.createBranch(git, "main", "test2") == Left("Sorry, that branch already exists."))
|
||||
|
||||
// verify
|
||||
val branches = git.branchList.call()
|
||||
assert(branches.size == 3)
|
||||
assert(branches.get(0).getName == "refs/heads/master")
|
||||
assert(branches.get(0).getName == "refs/heads/main")
|
||||
assert(branches.get(1).getName == "refs/heads/test1")
|
||||
assert(branches.get(2).getName == "refs/heads/test2")
|
||||
|
||||
// getBranchesOfCommit
|
||||
val commit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve("master"))
|
||||
val commit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve("main"))
|
||||
val branchesOfCommit = JGitUtil.getBranchesOfCommit(git, commit.getName)
|
||||
assert(branchesOfCommit.size == 3)
|
||||
assert(branchesOfCommit(0) == "master")
|
||||
assert(branchesOfCommit(0) == "main")
|
||||
assert(branchesOfCommit(1) == "test1")
|
||||
assert(branchesOfCommit(2) == "test2")
|
||||
}
|
||||
@@ -147,16 +147,16 @@ class JGitUtilSpec extends AnyFunSuite {
|
||||
test("getBranches") {
|
||||
withTestRepository { git =>
|
||||
createFile(git, Constants.HEAD, "README.md", "body1", message = "commit1")
|
||||
JGitUtil.createBranch(git, "master", "test1")
|
||||
JGitUtil.createBranch(git, "main", "test1")
|
||||
|
||||
createFile(git, Constants.HEAD, "README.md", "body2", message = "commit2")
|
||||
JGitUtil.createBranch(git, "master", "test2")
|
||||
JGitUtil.createBranch(git, "main", "test2")
|
||||
|
||||
// getBranches
|
||||
val branches = JGitUtil.getBranches(git, "master", true)
|
||||
val branches = JGitUtil.getBranches(git, "main", true)
|
||||
assert(branches.size == 3)
|
||||
|
||||
assert(branches(0).name == "master")
|
||||
assert(branches(0).name == "main")
|
||||
assert(branches(0).committerName == "dummy")
|
||||
assert(branches(0).committerEmailAddress == "dummy@example.com")
|
||||
|
||||
@@ -178,17 +178,17 @@ class JGitUtilSpec extends AnyFunSuite {
|
||||
createFile(git, Constants.HEAD, "README.md", "body1", message = "commit1")
|
||||
|
||||
// createTag
|
||||
assert(JGitUtil.createTag(git, "1.0", Some("test1"), "master") == Right("Tag added."))
|
||||
assert(JGitUtil.createTag(git, "1.0", Some("test1"), "main") == Right("Tag added."))
|
||||
assert(
|
||||
JGitUtil.createTag(git, "1.0", Some("test2"), "master") == Left("Sorry, some Git operation error occurs.")
|
||||
JGitUtil.createTag(git, "1.0", Some("test2"), "main") == Left("Sorry, some Git operation error occurs.")
|
||||
)
|
||||
|
||||
// record current commit
|
||||
val commit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve("master"))
|
||||
val commit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve("main"))
|
||||
|
||||
// createTag
|
||||
createFile(git, Constants.HEAD, "LICENSE", "Apache License", message = "commit2")
|
||||
assert(JGitUtil.createTag(git, "1.1", Some("test3"), "master") == Right("Tag added."))
|
||||
assert(JGitUtil.createTag(git, "1.1", Some("test3"), "main") == Right("Tag added."))
|
||||
|
||||
// verify
|
||||
val allTags = git.tagList().call().asScala
|
||||
@@ -203,7 +203,7 @@ class JGitUtilSpec extends AnyFunSuite {
|
||||
assert(tagsOfCommit(1) == "1.0")
|
||||
|
||||
// getTagsOnCommit
|
||||
val tagsOnCommit = JGitUtil.getTagsOnCommit(git, "master")
|
||||
val tagsOnCommit = JGitUtil.getTagsOnCommit(git, "main")
|
||||
assert(tagsOnCommit.size == 1)
|
||||
assert(tagsOnCommit(0) == "1.1")
|
||||
}
|
||||
@@ -214,7 +214,7 @@ class JGitUtilSpec extends AnyFunSuite {
|
||||
createFile(git, Constants.HEAD, "README.md", "body1", message = "commit1")
|
||||
createFile(git, Constants.HEAD, "LICENSE", "Apache License", message = "commit2")
|
||||
|
||||
val objectId = git.getRepository.resolve("master")
|
||||
val objectId = git.getRepository.resolve("main")
|
||||
val commit = JGitUtil.getRevCommitFromId(git, objectId)
|
||||
|
||||
// Since Non-LFS file doesn't need RepositoryInfo give null
|
||||
@@ -233,7 +233,7 @@ class JGitUtilSpec extends AnyFunSuite {
|
||||
createFile(git, Constants.HEAD, "README.md", "body1", message = "commit1")
|
||||
createFile(git, Constants.HEAD, "LARGE_FILE", "body1" * 1000000, message = "commit1")
|
||||
|
||||
val objectId = git.getRepository.resolve("master")
|
||||
val objectId = git.getRepository.resolve("main")
|
||||
val commit = JGitUtil.getRevCommitFromId(git, objectId)
|
||||
|
||||
val content1 = JGitUtil.getContentFromPath(git, commit.getTree, "README.md", true)
|
||||
@@ -252,7 +252,7 @@ class JGitUtilSpec extends AnyFunSuite {
|
||||
createFile(git, Constants.HEAD, "README.md", "body1\nbody2\nbody3", message = "commit1")
|
||||
createFile(git, Constants.HEAD, "README.md", "body0\nbody2\nbody3", message = "commit2")
|
||||
|
||||
val blames = JGitUtil.getBlame(git, "master", "README.md").toSeq
|
||||
val blames = JGitUtil.getBlame(git, "main", "README.md").toSeq
|
||||
|
||||
assert(blames.size == 2)
|
||||
assert(blames(0).message == "commit2")
|
||||
@@ -266,75 +266,75 @@ class JGitUtilSpec extends AnyFunSuite {
|
||||
withTestRepository { git =>
|
||||
def list(branch: String, path: String) =
|
||||
JGitUtil.getFileList(git, branch, path).map(finfo => (finfo.name, finfo.message, finfo.isDirectory))
|
||||
assert(list("master", ".") == Nil)
|
||||
assert(list("master", "dir/subdir") == Nil)
|
||||
assert(list("main", ".") == Nil)
|
||||
assert(list("main", "dir/subdir") == Nil)
|
||||
assert(list("branch", ".") == Nil)
|
||||
assert(list("branch", "dir/subdir") == Nil)
|
||||
|
||||
createFile(git, "master", "README.md", "body1", message = "commit1")
|
||||
createFile(git, "main", "README.md", "body1", message = "commit1")
|
||||
|
||||
assert(list("master", ".") == List(("README.md", "commit1", false)))
|
||||
assert(list("master", "dir/subdir") == Nil)
|
||||
assert(list("main", ".") == List(("README.md", "commit1", false)))
|
||||
assert(list("main", "dir/subdir") == Nil)
|
||||
assert(list("branch", ".") == Nil)
|
||||
assert(list("branch", "dir/subdir") == Nil)
|
||||
|
||||
createFile(git, "master", "README.md", "body2", message = "commit2")
|
||||
createFile(git, "main", "README.md", "body2", message = "commit2")
|
||||
|
||||
assert(list("master", ".") == List(("README.md", "commit2", false)))
|
||||
assert(list("master", "dir/subdir") == Nil)
|
||||
assert(list("main", ".") == List(("README.md", "commit2", false)))
|
||||
assert(list("main", "dir/subdir") == Nil)
|
||||
assert(list("branch", ".") == Nil)
|
||||
assert(list("branch", "dir/subdir") == Nil)
|
||||
|
||||
createFile(git, "master", "dir/subdir/File3.md", "body3", message = "commit3")
|
||||
createFile(git, "main", "dir/subdir/File3.md", "body3", message = "commit3")
|
||||
|
||||
assert(list("master", ".") == List(("dir/subdir", "commit3", true), ("README.md", "commit2", false)))
|
||||
assert(list("master", "dir/subdir") == List(("File3.md", "commit3", false)))
|
||||
assert(list("main", ".") == List(("dir/subdir", "commit3", true), ("README.md", "commit2", false)))
|
||||
assert(list("main", "dir/subdir") == List(("File3.md", "commit3", false)))
|
||||
assert(list("branch", ".") == Nil)
|
||||
assert(list("branch", "dir/subdir") == Nil)
|
||||
|
||||
createFile(git, "master", "dir/subdir/File4.md", "body4", message = "commit4")
|
||||
createFile(git, "main", "dir/subdir/File4.md", "body4", message = "commit4")
|
||||
|
||||
assert(list("master", ".") == List(("dir/subdir", "commit4", true), ("README.md", "commit2", false)))
|
||||
assert(list("master", "dir/subdir") == List(("File3.md", "commit3", false), ("File4.md", "commit4", false)))
|
||||
assert(list("main", ".") == List(("dir/subdir", "commit4", true), ("README.md", "commit2", false)))
|
||||
assert(list("main", "dir/subdir") == List(("File3.md", "commit3", false), ("File4.md", "commit4", false)))
|
||||
assert(list("branch", ".") == Nil)
|
||||
assert(list("branch", "dir/subdir") == Nil)
|
||||
|
||||
createFile(git, "master", "README5.md", "body5", message = "commit5")
|
||||
createFile(git, "main", "README5.md", "body5", message = "commit5")
|
||||
|
||||
assert(
|
||||
list("master", ".") == List(
|
||||
list("main", ".") == List(
|
||||
("dir/subdir", "commit4", true),
|
||||
("README.md", "commit2", false),
|
||||
("README5.md", "commit5", false)
|
||||
)
|
||||
)
|
||||
assert(list("master", "dir/subdir") == List(("File3.md", "commit3", false), ("File4.md", "commit4", false)))
|
||||
assert(list("main", "dir/subdir") == List(("File3.md", "commit3", false), ("File4.md", "commit4", false)))
|
||||
assert(list("branch", ".") == Nil)
|
||||
assert(list("branch", "dir/subdir") == Nil)
|
||||
|
||||
createFile(git, "master", "README.md", "body6", message = "commit6")
|
||||
createFile(git, "main", "README.md", "body6", message = "commit6")
|
||||
|
||||
assert(
|
||||
list("master", ".") == List(
|
||||
list("main", ".") == List(
|
||||
("dir/subdir", "commit4", true),
|
||||
("README.md", "commit6", false),
|
||||
("README5.md", "commit5", false)
|
||||
)
|
||||
)
|
||||
assert(list("master", "dir/subdir") == List(("File3.md", "commit3", false), ("File4.md", "commit4", false)))
|
||||
assert(list("main", "dir/subdir") == List(("File3.md", "commit3", false), ("File4.md", "commit4", false)))
|
||||
assert(list("branch", ".") == Nil)
|
||||
assert(list("branch", "dir/subdir") == Nil)
|
||||
|
||||
git.branchCreate().setName("branch").setStartPoint("master").call()
|
||||
git.branchCreate().setName("branch").setStartPoint("main").call()
|
||||
|
||||
assert(
|
||||
list("master", ".") == List(
|
||||
list("main", ".") == List(
|
||||
("dir/subdir", "commit4", true),
|
||||
("README.md", "commit6", false),
|
||||
("README5.md", "commit5", false)
|
||||
)
|
||||
)
|
||||
assert(list("master", "dir/subdir") == List(("File3.md", "commit3", false), ("File4.md", "commit4", false)))
|
||||
assert(list("main", "dir/subdir") == List(("File3.md", "commit3", false), ("File4.md", "commit4", false)))
|
||||
assert(
|
||||
list("branch", ".") == List(
|
||||
("dir/subdir", "commit4", true),
|
||||
@@ -347,13 +347,13 @@ class JGitUtilSpec extends AnyFunSuite {
|
||||
createFile(git, "branch", "dir/subdir/File3.md", "body7", message = "commit7")
|
||||
|
||||
assert(
|
||||
list("master", ".") == List(
|
||||
list("main", ".") == List(
|
||||
("dir/subdir", "commit4", true),
|
||||
("README.md", "commit6", false),
|
||||
("README5.md", "commit5", false)
|
||||
)
|
||||
)
|
||||
assert(list("master", "dir/subdir") == List(("File3.md", "commit3", false), ("File4.md", "commit4", false)))
|
||||
assert(list("main", "dir/subdir") == List(("File3.md", "commit3", false), ("File4.md", "commit4", false)))
|
||||
assert(
|
||||
list("branch", ".") == List(
|
||||
("dir/subdir", "commit7", true),
|
||||
@@ -363,17 +363,17 @@ class JGitUtilSpec extends AnyFunSuite {
|
||||
)
|
||||
assert(list("branch", "dir/subdir") == List(("File3.md", "commit7", false), ("File4.md", "commit4", false)))
|
||||
|
||||
createFile(git, "master", "dir8/File8.md", "body8", message = "commit8")
|
||||
createFile(git, "main", "dir8/File8.md", "body8", message = "commit8")
|
||||
|
||||
assert(
|
||||
list("master", ".") == List(
|
||||
list("main", ".") == List(
|
||||
("dir/subdir", "commit4", true),
|
||||
("dir8", "commit8", true),
|
||||
("README.md", "commit6", false),
|
||||
("README5.md", "commit5", false)
|
||||
)
|
||||
)
|
||||
assert(list("master", "dir/subdir") == List(("File3.md", "commit3", false), ("File4.md", "commit4", false)))
|
||||
assert(list("main", "dir/subdir") == List(("File3.md", "commit3", false), ("File4.md", "commit4", false)))
|
||||
assert(
|
||||
list("branch", ".") == List(
|
||||
("dir/subdir", "commit7", true),
|
||||
@@ -386,14 +386,14 @@ class JGitUtilSpec extends AnyFunSuite {
|
||||
createFile(git, "branch", "dir/subdir9/File9.md", "body9", message = "commit9")
|
||||
|
||||
assert(
|
||||
list("master", ".") == List(
|
||||
list("main", ".") == List(
|
||||
("dir/subdir", "commit4", true),
|
||||
("dir8", "commit8", true),
|
||||
("README.md", "commit6", false),
|
||||
("README5.md", "commit5", false)
|
||||
)
|
||||
)
|
||||
assert(list("master", "dir/subdir") == List(("File3.md", "commit3", false), ("File4.md", "commit4", false)))
|
||||
assert(list("main", "dir/subdir") == List(("File3.md", "commit3", false), ("File4.md", "commit4", false)))
|
||||
assert(
|
||||
list("branch", ".") == List(
|
||||
("dir", "commit9", true),
|
||||
@@ -403,17 +403,17 @@ class JGitUtilSpec extends AnyFunSuite {
|
||||
)
|
||||
assert(list("branch", "dir/subdir") == List(("File3.md", "commit7", false), ("File4.md", "commit4", false)))
|
||||
|
||||
mergeAndCommit(git, "master", "branch", message = "merge10")
|
||||
mergeAndCommit(git, "main", "branch", message = "merge10")
|
||||
|
||||
assert(
|
||||
list("master", ".") == List(
|
||||
list("main", ".") == List(
|
||||
("dir", "commit9", true),
|
||||
("dir8", "commit8", true),
|
||||
("README.md", "commit6", false),
|
||||
("README5.md", "commit5", false)
|
||||
)
|
||||
)
|
||||
assert(list("master", "dir/subdir") == List(("File3.md", "commit7", false), ("File4.md", "commit4", false)))
|
||||
assert(list("main", "dir/subdir") == List(("File3.md", "commit7", false), ("File4.md", "commit4", false)))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -421,10 +421,10 @@ class JGitUtilSpec extends AnyFunSuite {
|
||||
withTestRepository { git =>
|
||||
def list(branch: String, path: String) =
|
||||
JGitUtil.getFileList(git, branch, path).map(finfo => (finfo.name, finfo.message, finfo.isDirectory))
|
||||
createFile(git, "master", "README.md", "body1", message = "commit1")
|
||||
createFile(git, "main", "README.md", "body1", message = "commit1")
|
||||
createFile(git, "branch", "test/text2.txt", "body2", message = "commit2")
|
||||
mergeAndCommit(git, "master", "branch", message = "merge3")
|
||||
assert(list("master", "test") == List(("text2.txt", "commit2", false)))
|
||||
mergeAndCommit(git, "main", "branch", message = "merge3")
|
||||
assert(list("main", "test") == List(("text2.txt", "commit2", false)))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -193,7 +193,8 @@ class AvatarImageProviderSpec extends AnyFunSpec {
|
||||
),
|
||||
repositoryViewer = RepositoryViewerSettings(
|
||||
maxFiles = 0
|
||||
)
|
||||
),
|
||||
"main"
|
||||
)
|
||||
|
||||
/**
|
||||
|
||||
@@ -9,7 +9,7 @@ import org.mockito.Mockito._
|
||||
|
||||
class HelpersSpec extends AnyFunSpec {
|
||||
|
||||
private implicit val context = mock(classOf[Context])
|
||||
private implicit val context: Context = mock(classOf[Context])
|
||||
private val repository = mock(classOf[RepositoryInfo])
|
||||
|
||||
import helpers._
|
||||
|
||||
Reference in New Issue
Block a user