mirror of
https://github.com/gitbucket/gitbucket.git
synced 2025-11-03 20:15:59 +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
|
- [ ] searched for similar already existing issue
|
||||||
- [ ] read the documentation and [wiki](https://github.com/gitbucket/gitbucket/wiki)
|
- [ ] 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)*
|
*(if you have performed all the above, remove the paragraph and continue describing the issue with template below)*
|
||||||
|
|
||||||
|
-->
|
||||||
|
|
||||||
## Issue
|
## Issue
|
||||||
**Impacted version**: xxxx
|
**Impacted version**: xxxx
|
||||||
|
|
||||||
|
|||||||
8
.github/workflows/build.yml
vendored
8
.github/workflows/build.yml
vendored
@@ -8,9 +8,9 @@ jobs:
|
|||||||
timeout-minutes: 30
|
timeout-minutes: 30
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
java: [8, 11, 17]
|
java: [11, 21]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
- name: Cache
|
- name: Cache
|
||||||
uses: actions/cache@v3
|
uses: actions/cache@v3
|
||||||
env:
|
env:
|
||||||
@@ -27,9 +27,9 @@ jobs:
|
|||||||
java-version: ${{ matrix.java }}
|
java-version: ${{ matrix.java }}
|
||||||
distribution: adopt
|
distribution: adopt
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
run: sbt scalafmtSbtCheck scalafmtCheck test:scalafmtCheck test
|
run: sbt scalafmtSbtCheck scalafmtCheckAll test
|
||||||
- name: Scala 3
|
- name: Scala 3
|
||||||
run: sbt '++ 3.1.2!' update # TODO
|
run: sbt '++ 3.x' update # TODO
|
||||||
- name: Build executable
|
- name: Build executable
|
||||||
run: sbt executable
|
run: sbt executable
|
||||||
- name: Upload artifacts
|
- name: Upload artifacts
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -4,6 +4,7 @@
|
|||||||
.ensime_cache
|
.ensime_cache
|
||||||
.DS_Store
|
.DS_Store
|
||||||
.java-version
|
.java-version
|
||||||
|
.tmp
|
||||||
|
|
||||||
# sbt specific
|
# sbt specific
|
||||||
dist/*
|
dist/*
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ updates.limit = 3
|
|||||||
updates.includeScala = true
|
updates.includeScala = true
|
||||||
|
|
||||||
updates.pin = [
|
updates.pin = [
|
||||||
{ groupId = "org.eclipse.jetty", version = "9." }
|
{ groupId = "org.eclipse.jetty", version = "10." }
|
||||||
{ groupId = "org.eclipse.jgit", version = "5." }
|
{ groupId = "org.mariadb.jdbc", version = "2." }
|
||||||
{ groupId = "com.zaxxer", version = "4." }
|
|
||||||
]
|
]
|
||||||
|
|||||||
16
CHANGELOG.md
16
CHANGELOG.md
@@ -1,6 +1,22 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
All changes to the project will be documented in this file.
|
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
|
## 4.38.3 - 30 Oct 2022
|
||||||
- Fix several issues around multiple assignees in issues and pull requests
|
- Fix several issues around multiple assignees in issues and pull requests
|
||||||
- Fix IllegalStateException when returning unknown avatar image
|
- 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.
|
- 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.
|
- 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
|
## 4.40.0 - 22 Oct 2023
|
||||||
- Fix several issues around multiple assignees in issues and pull requests
|
- Drop Java 8 support
|
||||||
- Fix IllegalStateException when returning unknown avatar image
|
- Improve git push performance
|
||||||
|
- Show activities of all visible repositories as news feed
|
||||||
## 4.38.2 - 20 Sep 2022
|
- Support custom fields of issues and pull requests in search condition
|
||||||
- Resurrect assignee icons on the issue list
|
- Configurable default branch name
|
||||||
|
|
||||||
## 4.38.1 - 10 Sep 2022
|
|
||||||
- Fix comment diff in Chrome 105
|
|
||||||
- Fix Markdown table CSS
|
|
||||||
- Fix HTML rendering of multiple asignees
|
|
||||||
|
|
||||||
## 4.38.0 - 3 Sep 2022
|
|
||||||
- Support multiple assignees for Issues and Pull requests
|
|
||||||
- Custom fields for issues and pull requests
|
|
||||||
- Reset password by users
|
|
||||||
- Allow to configure Jetty idle timeout in standalone mode
|
|
||||||
- Horizontal scroll for too wide tables in Markdown
|
|
||||||
- Hide header content on signin and register page
|
|
||||||
- Fix the default charset of the online editor in the repository viewer
|
|
||||||
- Fix the milestone count
|
|
||||||
- Some improvements and bugfixes for WebAPI and WebHook
|
|
||||||
|
|
||||||
See the [change log](CHANGELOG.md) for all of the updates.
|
See the [change log](CHANGELOG.md) for all of the updates.
|
||||||
|
|||||||
92
build.sbt
92
build.sbt
@@ -1,12 +1,12 @@
|
|||||||
import com.typesafe.sbt.license.{DepModuleInfo, LicenseInfo}
|
import sbtlicensereport.license.{DepModuleInfo, LicenseInfo}
|
||||||
import com.jsuereth.sbtpgp.PgpKeys._
|
import com.jsuereth.sbtpgp.PgpKeys._
|
||||||
|
|
||||||
val Organization = "io.github.gitbucket"
|
val Organization = "io.github.gitbucket"
|
||||||
val Name = "gitbucket"
|
val Name = "gitbucket"
|
||||||
val GitBucketVersion = "4.38.3"
|
val GitBucketVersion = "4.40.0"
|
||||||
val ScalatraVersion = "2.8.4"
|
val ScalatraVersion = "3.0.0"
|
||||||
val JettyVersion = "9.4.49.v20220914"
|
val JettyVersion = "10.0.17"
|
||||||
val JgitVersion = "5.13.1.202206130422-r"
|
val JgitVersion = "6.7.0.202309050840-r"
|
||||||
|
|
||||||
lazy val root = (project in file("."))
|
lazy val root = (project in file("."))
|
||||||
.enablePlugins(SbtTwirl, ScalatraPlugin)
|
.enablePlugins(SbtTwirl, ScalatraPlugin)
|
||||||
@@ -15,7 +15,9 @@ sourcesInBase := false
|
|||||||
organization := Organization
|
organization := Organization
|
||||||
name := Name
|
name := Name
|
||||||
version := GitBucketVersion
|
version := GitBucketVersion
|
||||||
scalaVersion := "2.13.10"
|
scalaVersion := "2.13.12"
|
||||||
|
|
||||||
|
crossScalaVersions += "3.3.1"
|
||||||
|
|
||||||
// scalafmtOnCompile := true
|
// scalafmtOnCompile := true
|
||||||
|
|
||||||
@@ -30,56 +32,47 @@ resolvers ++= Seq(
|
|||||||
libraryDependencies ++= Seq(
|
libraryDependencies ++= Seq(
|
||||||
"org.eclipse.jgit" % "org.eclipse.jgit.http.server" % JgitVersion,
|
"org.eclipse.jgit" % "org.eclipse.jgit.http.server" % JgitVersion,
|
||||||
"org.eclipse.jgit" % "org.eclipse.jgit.archive" % JgitVersion,
|
"org.eclipse.jgit" % "org.eclipse.jgit.archive" % JgitVersion,
|
||||||
"org.scalatra" %% "scalatra" % ScalatraVersion cross CrossVersion.for3Use2_13,
|
"org.scalatra" %% "scalatra-javax" % ScalatraVersion,
|
||||||
"org.scalatra" %% "scalatra-json" % ScalatraVersion cross CrossVersion.for3Use2_13,
|
"org.scalatra" %% "scalatra-json-javax" % ScalatraVersion,
|
||||||
"org.scalatra" %% "scalatra-forms" % ScalatraVersion cross CrossVersion.for3Use2_13,
|
"org.scalatra" %% "scalatra-forms-javax" % ScalatraVersion,
|
||||||
"org.json4s" %% "json4s-jackson" % "4.0.6" cross CrossVersion.for3Use2_13,
|
"org.json4s" %% "json4s-jackson" % "4.0.6",
|
||||||
"commons-io" % "commons-io" % "2.11.0",
|
"commons-io" % "commons-io" % "2.14.0",
|
||||||
"io.github.gitbucket" % "solidbase" % "1.0.5",
|
"io.github.gitbucket" % "solidbase" % "1.0.5",
|
||||||
"io.github.gitbucket" % "markedj" % "1.0.17",
|
"io.github.gitbucket" % "markedj" % "1.0.18",
|
||||||
"org.apache.commons" % "commons-compress" % "1.21",
|
"org.apache.commons" % "commons-compress" % "1.24.0",
|
||||||
"org.apache.commons" % "commons-email" % "1.5",
|
"org.apache.commons" % "commons-email" % "1.5",
|
||||||
"commons-net" % "commons-net" % "3.8.0",
|
"commons-net" % "commons-net" % "3.10.0",
|
||||||
"org.apache.httpcomponents" % "httpclient" % "4.5.13",
|
"org.apache.httpcomponents" % "httpclient" % "4.5.14",
|
||||||
"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.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.5.0",
|
"org.apache.tika" % "tika-core" % "2.9.1",
|
||||||
"com.github.takezoe" %% "blocking-slick-32" % "0.0.12" cross CrossVersion.for3Use2_13,
|
"com.github.takezoe" %% "blocking-slick" % "0.0.14",
|
||||||
"com.novell.ldap" % "jldap" % "2009-10-07",
|
"com.novell.ldap" % "jldap" % "2009-10-07",
|
||||||
"com.h2database" % "h2" % "1.4.199",
|
"com.h2database" % "h2" % "1.4.199",
|
||||||
"org.mariadb.jdbc" % "mariadb-java-client" % "3.0.8",
|
"org.mariadb.jdbc" % "mariadb-java-client" % "2.7.6",
|
||||||
"org.postgresql" % "postgresql" % "42.5.0",
|
"org.postgresql" % "postgresql" % "42.6.0",
|
||||||
"ch.qos.logback" % "logback-classic" % "1.2.11",
|
"ch.qos.logback" % "logback-classic" % "1.4.11",
|
||||||
"com.zaxxer" % "HikariCP" % "4.0.3" exclude ("org.slf4j", "slf4j-api"),
|
"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",
|
"fr.brouillard.oss.security.xhub" % "xhub4j-core" % "1.1.0",
|
||||||
"io.github.java-diff-utils" % "java-diff-utils" % "4.12",
|
"io.github.java-diff-utils" % "java-diff-utils" % "4.12",
|
||||||
"org.cache2k" % "cache2k-all" % "1.6.0.Final",
|
"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.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",
|
"org.eclipse.jetty" % "jetty-webapp" % JettyVersion % "provided",
|
||||||
"javax.servlet" % "javax.servlet-api" % "3.1.0" % "provided",
|
"javax.servlet" % "javax.servlet-api" % "3.1.0" % "provided",
|
||||||
"junit" % "junit" % "4.13.2" % "test",
|
"junit" % "junit" % "4.13.2" % "test",
|
||||||
"org.scalatra" %% "scalatra-scalatest" % ScalatraVersion % "test" cross CrossVersion.for3Use2_13,
|
"org.scalatra" %% "scalatra-scalatest-javax" % ScalatraVersion % "test",
|
||||||
"org.mockito" % "mockito-core" % "4.8.1" % "test",
|
"org.mockito" % "mockito-core" % "5.6.0" % "test",
|
||||||
"com.dimafeng" %% "testcontainers-scala" % "0.40.11" % "test",
|
"com.dimafeng" %% "testcontainers-scala" % "0.41.0" % "test",
|
||||||
"org.testcontainers" % "mysql" % "1.17.5" % "test",
|
"org.testcontainers" % "mysql" % "1.19.1" % "test",
|
||||||
"org.testcontainers" % "postgresql" % "1.17.5" % "test",
|
"org.testcontainers" % "postgresql" % "1.19.1" % "test",
|
||||||
"net.i2p.crypto" % "eddsa" % "0.3.0",
|
"net.i2p.crypto" % "eddsa" % "0.3.0",
|
||||||
"is.tagomor.woothee" % "woothee-java" % "1.11.0",
|
"is.tagomor.woothee" % "woothee-java" % "1.11.0",
|
||||||
"org.ec4j.core" % "ec4j-core" % "0.3.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
|
// Compiler settings
|
||||||
scalacOptions := Seq(
|
scalacOptions := Seq(
|
||||||
"-deprecation",
|
"-deprecation",
|
||||||
@@ -89,7 +82,15 @@ scalacOptions := Seq(
|
|||||||
"-Wunused:imports",
|
"-Wunused:imports",
|
||||||
"-Wconf:cat=unused&src=twirl/.*:s,cat=unused&src=scala/gitbucket/core/model/[^/]+\\.scala:s"
|
"-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"
|
Jetty / javaOptions += "-Dlogback.configurationFile=/logback-dev.xml"
|
||||||
|
|
||||||
// Test settings
|
// Test settings
|
||||||
@@ -125,7 +126,6 @@ Keys.ivyConfigurations += ExecutableConfig
|
|||||||
libraryDependencies ++= Seq(
|
libraryDependencies ++= Seq(
|
||||||
"org.eclipse.jetty" % "jetty-security" % JettyVersion % "executable",
|
"org.eclipse.jetty" % "jetty-security" % JettyVersion % "executable",
|
||||||
"org.eclipse.jetty" % "jetty-webapp" % 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-server" % JettyVersion % "executable",
|
||||||
"org.eclipse.jetty" % "jetty-xml" % JettyVersion % "executable",
|
"org.eclipse.jetty" % "jetty-xml" % JettyVersion % "executable",
|
||||||
"org.eclipse.jetty" % "jetty-http" % JettyVersion % "executable",
|
"org.eclipse.jetty" % "jetty-http" % JettyVersion % "executable",
|
||||||
@@ -159,8 +159,7 @@ executableKey := {
|
|||||||
val jettyJars = Keys.update.value select configurationFilter(name = ExecutableConfig.name)
|
val jettyJars = Keys.update.value select configurationFilter(name = ExecutableConfig.name)
|
||||||
jettyJars foreach { jar =>
|
jettyJars foreach { jar =>
|
||||||
IO unzip (jar, temp, (name: String) =>
|
IO unzip (jar, temp, (name: String) =>
|
||||||
(name startsWith "javax/") ||
|
(name startsWith "javax/") || (name startsWith "org/") || (name startsWith "META-INF/services/"))
|
||||||
(name startsWith "org/"))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// include original war file
|
// include original war file
|
||||||
@@ -185,7 +184,7 @@ executableKey := {
|
|||||||
val url = "https://github.com/" +
|
val url = "https://github.com/" +
|
||||||
s"gitbucket/gitbucket-${pluginId}-plugin/releases/download/${pluginVersion}/gitbucket-${pluginId}-plugin-${pluginVersion}.jar"
|
s"gitbucket/gitbucket-${pluginId}-plugin/releases/download/${pluginVersion}/gitbucket-${pluginId}-plugin-${pluginVersion}.jar"
|
||||||
log info s"Download: ${url}"
|
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 _ => ()
|
case _ => ()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -286,6 +285,9 @@ Test / testOptions ++= {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Jetty / javaOptions ++= Seq(
|
Jetty / javaOptions ++= Seq(
|
||||||
|
"-Dlogback.configurationFile=/logback-dev.xml",
|
||||||
"-Xdebug",
|
"-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
|
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
|
```shell
|
||||||
$ brew install sbt
|
$ 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`.
|
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
|
Build war file
|
||||||
--------
|
--------
|
||||||
@@ -37,7 +51,8 @@ To build an executable war file, run
|
|||||||
$ sbt executable
|
$ 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
|
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:
|
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
|
* /repositories
|
||||||
* /USER_NAME
|
* /USER_NAME
|
||||||
* /REPO_NAME.git (substance of repository. GitServlet sees this directory)
|
* /REPO_NAME.git (substance of repository. GitServlet sees this directory)
|
||||||
* /REPO_NAME.wiki.git (wiki repository)
|
* /REPO_NAME.wiki.git (wiki repository)
|
||||||
* /REPO_NAME
|
* /REPO_NAME
|
||||||
* /issues (files which are attached to issue)
|
* /issues (files attached to issue)
|
||||||
* /lfs (LFS managed files)
|
* /lfs (LFS managed files)
|
||||||
* /data
|
* /data
|
||||||
* /USER_NAME
|
* /USER_NAME
|
||||||
@@ -20,6 +24,8 @@ This directory has following structure:
|
|||||||
* /plugins
|
* /plugins
|
||||||
* plugin.jar
|
* plugin.jar
|
||||||
* /.installed (copied available plugins from the parent directory automatically)
|
* /.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
|
* /tmp
|
||||||
* /_upload
|
* /_upload
|
||||||
* /SESSION_ID (removed at session timeout)
|
* /SESSION_ID (removed at session timeout)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
Mapping and Validation
|
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:
|
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")
|
scalacOptions ++= Seq("-unchecked", "-deprecation", "-feature")
|
||||||
|
|
||||||
addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.6")
|
addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.5.2")
|
||||||
addSbtPlugin("com.typesafe.sbt" % "sbt-twirl" % "1.5.1")
|
addSbtPlugin("com.typesafe.play" % "sbt-twirl" % "1.6.1")
|
||||||
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "2.0.0")
|
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "2.1.3")
|
||||||
addSbtPlugin("org.scalatra.sbt" % "sbt-scalatra" % "1.0.4")
|
addSbtPlugin("org.scalatra.sbt" % "sbt-scalatra" % "1.0.4")
|
||||||
addSbtPlugin("com.github.sbt" % "sbt-pgp" % "2.1.2")
|
addSbtPlugin("com.github.sbt" % "sbt-pgp" % "2.2.1")
|
||||||
addSbtPlugin("com.typesafe.sbt" % "sbt-license-report" % "1.2.0")
|
addSbtPlugin("com.github.sbt" % "sbt-license-report" % "1.6.1")
|
||||||
addSbtPlugin("org.scoverage" % "sbt-scoverage" % "2.0.0")
|
addSbtPlugin("org.scoverage" % "sbt-scoverage" % "2.0.9")
|
||||||
|
|
||||||
addDependencyTreePlugin
|
addDependencyTreePlugin
|
||||||
|
|||||||
@@ -65,9 +65,15 @@ public class JettyLauncher {
|
|||||||
boolean saveSessions = false;
|
boolean saveSessions = false;
|
||||||
|
|
||||||
for(String arg: args) {
|
for(String arg: args) {
|
||||||
if(arg.equals("--save_sessions")) {
|
if (arg.equals("--save_sessions")) {
|
||||||
saveSessions = true;
|
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("=")) {
|
if(arg.startsWith("--") && arg.contains("=")) {
|
||||||
String[] dim = arg.split("=", 2);
|
String[] dim = arg.split("=", 2);
|
||||||
if(dim.length == 2) {
|
if(dim.length == 2) {
|
||||||
@@ -149,7 +155,7 @@ public class JettyLauncher {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (connectorsSet.contains(Connectors.HTTPS)) {
|
if (connectorsSet.contains(Connectors.HTTPS)) {
|
||||||
final SslContextFactory sslContextFactory = new SslContextFactory.Server();
|
final SslContextFactory.Server sslContextFactory = new SslContextFactory.Server();
|
||||||
|
|
||||||
sslContextFactory.setKeyStorePath(requireNonNull(keyStorePath,
|
sslContextFactory.setKeyStorePath(requireNonNull(keyStorePath,
|
||||||
"You must specify a path to an SSL keystore via the --key_store_path command line argument" +
|
"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
|
notifications:1.11.0
|
||||||
gist:4.22.0
|
gist:4.23.0
|
||||||
emoji:4.6.0
|
emoji:4.6.0
|
||||||
pages:1.10.0
|
pages:1.10.0
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
</appender>
|
</appender>
|
||||||
|
|
||||||
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
|
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
|
||||||
<file>gitbucket.log</file>
|
<file>.tmp/gitbucket.log</file>
|
||||||
<encoder>
|
<encoder>
|
||||||
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
|
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
|
||||||
</encoder>
|
</encoder>
|
||||||
|
|||||||
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.0", new LiquibaseMigration("update/gitbucket-core_4.38.xml")),
|
||||||
new Version("4.38.1"),
|
new Version("4.38.1"),
|
||||||
new Version("4.38.2"),
|
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,
|
watchers = 0,
|
||||||
forks = 0,
|
forks = 0,
|
||||||
`private` = false,
|
`private` = false,
|
||||||
default_branch = "master",
|
default_branch = "main",
|
||||||
owner = owner,
|
owner = owner,
|
||||||
has_issues = true
|
has_issues = true
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -185,7 +185,6 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
|||||||
initOption: String,
|
initOption: String,
|
||||||
sourceUrl: Option[String]
|
sourceUrl: Option[String]
|
||||||
)
|
)
|
||||||
case class ForkRepositoryForm(owner: String, name: String)
|
|
||||||
|
|
||||||
val newRepositoryForm = mapping(
|
val newRepositoryForm = mapping(
|
||||||
"owner" -> trim(label("Owner", text(required, maxlength(100), identifier, existsAccount))),
|
"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())))
|
"sourceUrl" -> trim(label("Source URL", optionalRequired(_.value("initOption") == "COPY", text())))
|
||||||
)(RepositoryCreationForm.apply)
|
)(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)
|
case class AccountForm(accountName: String)
|
||||||
|
|
||||||
val accountForm = mapping(
|
val accountForm = mapping(
|
||||||
@@ -268,7 +262,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
|||||||
gitbucket.core.account.html.activity(
|
gitbucket.core.account.html.activity(
|
||||||
account,
|
account,
|
||||||
if (account.isGroupAccount) Nil else getGroupsByUserName(userName),
|
if (account.isGroupAccount) Nil else getGroupsByUserName(userName),
|
||||||
getActivitiesByUser(userName, true),
|
getActivitiesByUser(userName, publicOnly = true),
|
||||||
extraMailAddresses
|
extraMailAddresses
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -813,7 +807,8 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
|||||||
form.description,
|
form.description,
|
||||||
form.isPrivate,
|
form.isPrivate,
|
||||||
form.initOption,
|
form.initOption,
|
||||||
form.sourceUrl
|
form.sourceUrl,
|
||||||
|
context.settings.defaultBranch
|
||||||
)
|
)
|
||||||
// redirect to the repository
|
// redirect to the repository
|
||||||
redirect(s"/${form.owner}/${form.name}")
|
redirect(s"/${form.owner}/${form.name}")
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package gitbucket.core.controller
|
package gitbucket.core.controller
|
||||||
|
|
||||||
import java.io.{File, FileInputStream}
|
import java.io.{File, FileInputStream, FileOutputStream}
|
||||||
|
|
||||||
import gitbucket.core.api.{ApiError, JsonFormat}
|
import gitbucket.core.api.{ApiError, JsonFormat}
|
||||||
import gitbucket.core.model.Account
|
import gitbucket.core.model.Account
|
||||||
import gitbucket.core.service.{AccountService, RepositoryService, SystemSettingsService}
|
import gitbucket.core.service.{AccountService, RepositoryService, SystemSettingsService}
|
||||||
@@ -14,9 +13,9 @@ import org.scalatra._
|
|||||||
import org.scalatra.i18n._
|
import org.scalatra.i18n._
|
||||||
import org.scalatra.json._
|
import org.scalatra.json._
|
||||||
import org.scalatra.forms._
|
import org.scalatra.forms._
|
||||||
|
|
||||||
import javax.servlet.http.{HttpServletRequest, HttpServletResponse}
|
import javax.servlet.http.{HttpServletRequest, HttpServletResponse}
|
||||||
import javax.servlet.{FilterChain, ServletRequest, ServletResponse}
|
import javax.servlet.{FilterChain, ServletRequest, ServletResponse}
|
||||||
|
|
||||||
import is.tagomor.woothee.Classifier
|
import is.tagomor.woothee.Classifier
|
||||||
|
|
||||||
import scala.util.Try
|
import scala.util.Try
|
||||||
@@ -29,6 +28,9 @@ import org.eclipse.jgit.treewalk._
|
|||||||
import org.apache.commons.io.IOUtils
|
import org.apache.commons.io.IOUtils
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
import org.json4s.Formats
|
import org.json4s.Formats
|
||||||
|
import org.json4s.jackson.Serialization
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides generic features for controller implementations.
|
* Provides generic features for controller implementations.
|
||||||
@@ -93,8 +95,16 @@ abstract class ControllerBase
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private def LoginAccount: Option[Account] =
|
private def LoginAccount: Option[Account] = {
|
||||||
request.getAs[Account](Keys.Session.LoginAccount).orElse(session.getAs[Account](Keys.Session.LoginAccount))
|
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 =
|
def ajaxGet(path: String)(action: => Any): Route =
|
||||||
super.get(path) {
|
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.{Keys, UsersAuthenticator}
|
||||||
import gitbucket.core.util.Implicits._
|
import gitbucket.core.util.Implicits._
|
||||||
import gitbucket.core.service.IssuesService._
|
import gitbucket.core.service.IssuesService._
|
||||||
|
import gitbucket.core.service.ActivityService._
|
||||||
|
|
||||||
class DashboardController
|
class DashboardController
|
||||||
extends DashboardControllerBase
|
extends DashboardControllerBase
|
||||||
@@ -42,7 +43,7 @@ trait DashboardControllerBase extends ControllerBase {
|
|||||||
withoutPhysicalInfo = true,
|
withoutPhysicalInfo = true,
|
||||||
limit = context.settings.basicBehavior.limitVisibleRepositories
|
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,
|
None,
|
||||||
withoutPhysicalInfo = true,
|
withoutPhysicalInfo = true,
|
||||||
limit = context.settings.basicBehavior.limitVisibleRepositories
|
limit = context.settings.basicBehavior.limitVisibleRepositories
|
||||||
)
|
),
|
||||||
|
isNewsFeedEnabled
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -172,7 +174,8 @@ trait DashboardControllerBase extends ControllerBase {
|
|||||||
None,
|
None,
|
||||||
withoutPhysicalInfo = true,
|
withoutPhysicalInfo = true,
|
||||||
limit = context.settings.basicBehavior.limitVisibleRepositories
|
limit = context.settings.basicBehavior.limitVisibleRepositories
|
||||||
)
|
),
|
||||||
|
isNewsFeedEnabled
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
package gitbucket.core.controller
|
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.oauth2.sdk.id.State
|
||||||
import com.nimbusds.openid.connect.sdk.Nonce
|
import com.nimbusds.openid.connect.sdk.Nonce
|
||||||
import gitbucket.core.helper.xml
|
import gitbucket.core.helper.xml
|
||||||
@@ -13,6 +14,8 @@ import gitbucket.core.view.helpers._
|
|||||||
import org.scalatra.Ok
|
import org.scalatra.Ok
|
||||||
import org.scalatra.forms._
|
import org.scalatra.forms._
|
||||||
|
|
||||||
|
import gitbucket.core.service.ActivityService._
|
||||||
|
|
||||||
class IndexController
|
class IndexController
|
||||||
extends IndexControllerBase
|
extends IndexControllerBase
|
||||||
with RepositoryService
|
with RepositoryService
|
||||||
@@ -57,30 +60,41 @@ trait IndexControllerBase extends ControllerBase {
|
|||||||
//
|
//
|
||||||
// case class SearchForm(query: String, owner: String, repository: String)
|
// 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("/") {
|
get("/") {
|
||||||
context.loginAccount
|
context.loginAccount
|
||||||
.map { account =>
|
.map { account =>
|
||||||
val visibleOwnerSet: Set[String] = Set(account.userName) ++ getGroupsByUserName(account.userName)
|
val visibleOwnerSet: Set[String] = Set(account.userName) ++ getGroupsByUserName(account.userName)
|
||||||
gitbucket.core.html.index(
|
if (!isNewsFeedEnabled) {
|
||||||
getRecentActivitiesByOwners(visibleOwnerSet),
|
redirect("/dashboard/repos")
|
||||||
getVisibleRepositories(
|
} else {
|
||||||
|
val repos = getVisibleRepositories(
|
||||||
Some(account),
|
Some(account),
|
||||||
None,
|
None,
|
||||||
withoutPhysicalInfo = true,
|
withoutPhysicalInfo = true,
|
||||||
limit = context.settings.basicBehavior.limitVisibleRepositories
|
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(
|
showBannerToCreatePersonalAccessToken = hasAccountFederation(account.userName) && !hasAccessToken(
|
||||||
account.userName
|
account.userName
|
||||||
|
),
|
||||||
|
enableNewsFeed = isNewsFeedEnabled
|
||||||
)
|
)
|
||||||
)
|
}
|
||||||
}
|
}
|
||||||
.getOrElse {
|
.getOrElse {
|
||||||
gitbucket.core.html.index(
|
gitbucket.core.html.index(
|
||||||
getRecentPublicActivities(),
|
activities = getRecentPublicActivities(),
|
||||||
getVisibleRepositories(None, withoutPhysicalInfo = true),
|
recentRepositories = getVisibleRepositories(None, withoutPhysicalInfo = true),
|
||||||
showBannerToCreatePersonalAccessToken = false
|
showBannerToCreatePersonalAccessToken = false,
|
||||||
|
enableNewsFeed = isNewsFeedEnabled
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -120,8 +134,8 @@ trait IndexControllerBase extends ControllerBase {
|
|||||||
case _ => "/"
|
case _ => "/"
|
||||||
}
|
}
|
||||||
session.setAttribute(
|
session.setAttribute(
|
||||||
Keys.Session.OidcContext,
|
Keys.Session.OidcAuthContext,
|
||||||
OidcContext(authenticationRequest.getState, authenticationRequest.getNonce, redirectBackURI)
|
OidcAuthContext(authenticationRequest.getState, authenticationRequest.getNonce, redirectBackURI)
|
||||||
)
|
)
|
||||||
redirect(authenticationRequest.toURI.toString)
|
redirect(authenticationRequest.toURI.toString)
|
||||||
} getOrElse {
|
} getOrElse {
|
||||||
@@ -135,9 +149,11 @@ trait IndexControllerBase extends ControllerBase {
|
|||||||
get("/signin/oidc") {
|
get("/signin/oidc") {
|
||||||
context.settings.oidc.map { oidc =>
|
context.settings.oidc.map { oidc =>
|
||||||
val redirectURI = new URI(s"$baseUrl/signin/oidc")
|
val redirectURI = new URI(s"$baseUrl/signin/oidc")
|
||||||
session.get(Keys.Session.OidcContext) match {
|
session.get(Keys.Session.OidcAuthContext) match {
|
||||||
case Some(context: OidcContext) =>
|
case Some(context: OidcAuthContext) =>
|
||||||
authenticate(params.toMap, redirectURI, context.state, context.nonce, oidc).map { account =>
|
authenticate(params.toMap, redirectURI, context.state, context.nonce, oidc).map {
|
||||||
|
case (jwt, account) =>
|
||||||
|
session.setAttribute(Keys.Session.OidcSessionContext, OidcSessionContext(jwt))
|
||||||
signin(account, context.redirectBackURI)
|
signin(account, context.redirectBackURI)
|
||||||
} orElse {
|
} orElse {
|
||||||
flash.update("error", "Sorry, authentication failed. Please try again.")
|
flash.update("error", "Sorry, authentication failed. Please try again.")
|
||||||
@@ -155,7 +171,19 @@ trait IndexControllerBase extends ControllerBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get("/signout") {
|
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("/")
|
redirect("/")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -178,6 +206,9 @@ trait IndexControllerBase extends ControllerBase {
|
|||||||
*/
|
*/
|
||||||
private def signin(account: Account, redirectUrl: String = "/") = {
|
private def signin(account: Account, redirectUrl: String = "/") = {
|
||||||
session.setAttribute(Keys.Session.LoginAccount, account)
|
session.setAttribute(Keys.Session.LoginAccount, account)
|
||||||
|
if (isDevFeatureEnabled(DevFeatures.KeepSession)) {
|
||||||
|
saveLoginAccountToLocalFile(account)
|
||||||
|
}
|
||||||
updateLastLoginDate(account.userName)
|
updateLastLoginDate(account.userName)
|
||||||
|
|
||||||
if (LDAPUtil.isDummyMailAddress(account)) {
|
if (LDAPUtil.isDummyMailAddress(account)) {
|
||||||
@@ -201,7 +232,7 @@ trait IndexControllerBase extends ControllerBase {
|
|||||||
org.json4s.jackson.Serialization.write(
|
org.json4s.jackson.Serialization.write(
|
||||||
Map(
|
Map(
|
||||||
"options" -> (
|
"options" -> (
|
||||||
getAllUsers(false)
|
getAllUsers(includeRemoved = false)
|
||||||
.withFilter { t =>
|
.withFilter { t =>
|
||||||
(user, group) match {
|
(user, group) match {
|
||||||
case (true, true) => true
|
case (true, true) => true
|
||||||
@@ -234,7 +265,7 @@ trait IndexControllerBase extends ControllerBase {
|
|||||||
} getOrElse ""
|
} getOrElse ""
|
||||||
})
|
})
|
||||||
|
|
||||||
// TODO Move to RepositoryViewrController?
|
// TODO Move to RepositoryViewerController?
|
||||||
get("/:owner/:repository/search")(referrersOnly { repository =>
|
get("/:owner/:repository/search")(referrersOnly { repository =>
|
||||||
val query = params.getOrElse("q", "").trim
|
val query = params.getOrElse("q", "").trim
|
||||||
val target = params.getOrElse("type", "code")
|
val target = params.getOrElse("type", "code")
|
||||||
@@ -248,8 +279,8 @@ trait IndexControllerBase extends ControllerBase {
|
|||||||
target.toLowerCase match {
|
target.toLowerCase match {
|
||||||
case "issues" =>
|
case "issues" =>
|
||||||
gitbucket.core.search.html.issues(
|
gitbucket.core.search.html.issues(
|
||||||
if (query.nonEmpty) searchIssues(repository.owner, repository.name, query, false) else Nil,
|
if (query.nonEmpty) searchIssues(repository.owner, repository.name, query, pullRequest = false) else Nil,
|
||||||
false,
|
pullRequest = false,
|
||||||
query,
|
query,
|
||||||
page,
|
page,
|
||||||
repository
|
repository
|
||||||
@@ -257,8 +288,8 @@ trait IndexControllerBase extends ControllerBase {
|
|||||||
|
|
||||||
case "pulls" =>
|
case "pulls" =>
|
||||||
gitbucket.core.search.html.issues(
|
gitbucket.core.search.html.issues(
|
||||||
if (query.nonEmpty) searchIssues(repository.owner, repository.name, query, true) else Nil,
|
if (query.nonEmpty) searchIssues(repository.owner, repository.name, query, pullRequest = true) else Nil,
|
||||||
true,
|
pullRequest = true,
|
||||||
query,
|
query,
|
||||||
page,
|
page,
|
||||||
repository
|
repository
|
||||||
@@ -293,15 +324,15 @@ trait IndexControllerBase extends ControllerBase {
|
|||||||
)
|
)
|
||||||
|
|
||||||
val repositories = {
|
val repositories = {
|
||||||
context.settings.basicBehavior.limitVisibleRepositories match {
|
if (context.settings.basicBehavior.limitVisibleRepositories) {
|
||||||
case true =>
|
|
||||||
getVisibleRepositories(
|
getVisibleRepositories(
|
||||||
context.loginAccount,
|
context.loginAccount,
|
||||||
None,
|
None,
|
||||||
withoutPhysicalInfo = true,
|
withoutPhysicalInfo = true,
|
||||||
limit = false
|
limit = false
|
||||||
)
|
)
|
||||||
case false => visibleRepositories
|
} else {
|
||||||
|
visibleRepositories
|
||||||
}
|
}
|
||||||
}.filter { repository =>
|
}.filter { repository =>
|
||||||
repository.name.toLowerCase.indexOf(query) >= 0 || repository.owner.toLowerCase.indexOf(query) >= 0
|
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 =>
|
get("/:owner/:repository/issues")(referrersOnly { repository =>
|
||||||
val q = request.getParameter("q")
|
val q = request.getParameter("q")
|
||||||
if (Option(q).exists(_.contains("is:pr"))) {
|
Option(q) match {
|
||||||
|
case Some(filter) if filter.contains("is:pr") =>
|
||||||
redirect(s"/${repository.owner}/${repository.name}/pulls?q=${StringUtil.urlEncode(q)}")
|
redirect(s"/${repository.owner}/${repository.name}/pulls?q=${StringUtil.urlEncode(q)}")
|
||||||
} else {
|
case Some(filter) =>
|
||||||
searchIssues(repository)
|
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) = {
|
private def searchIssues(repository: RepositoryService.RepositoryInfo, condition: IssueSearchCondition, page: Int) = {
|
||||||
val page = IssueSearchCondition.page(request)
|
|
||||||
// retrieve search condition
|
|
||||||
val condition = IssueSearchCondition(request)
|
|
||||||
// search issues
|
// search issues
|
||||||
val issues =
|
val issues =
|
||||||
searchIssue(
|
searchIssue(
|
||||||
|
|||||||
@@ -102,10 +102,13 @@ trait PullRequestsControllerBase extends ControllerBase {
|
|||||||
|
|
||||||
get("/:owner/:repository/pulls")(referrersOnly { repository =>
|
get("/:owner/:repository/pulls")(referrersOnly { repository =>
|
||||||
val q = request.getParameter("q")
|
val q = request.getParameter("q")
|
||||||
if (Option(q).exists(_.contains("is:issue"))) {
|
Option(q) match {
|
||||||
redirect(s"/${repository.owner}/${repository.name}/issues?q=" + StringUtil.urlEncode(q))
|
case Some(filter) if filter.contains("is:issue") =>
|
||||||
} else {
|
redirect(s"/${repository.owner}/${repository.name}/issues?q=${StringUtil.urlEncode(q)}")
|
||||||
searchPullRequests(None, repository)
|
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)
|
html.proposals(proposedBranches, targetRepository, repository)
|
||||||
})
|
})
|
||||||
|
|
||||||
private def searchPullRequests(userName: Option[String], repository: RepositoryService.RepositoryInfo) = {
|
private def searchPullRequests(
|
||||||
val page = IssueSearchCondition.page(request)
|
repository: RepositoryService.RepositoryInfo,
|
||||||
// retrieve search condition
|
condition: IssueSearchCondition,
|
||||||
val condition = IssueSearchCondition(request)
|
page: Int
|
||||||
|
) = {
|
||||||
// search issues
|
// search issues
|
||||||
val issues = searchIssue(
|
val issues = searchIssue(
|
||||||
condition,
|
condition,
|
||||||
|
|||||||
@@ -126,6 +126,7 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
|||||||
case class CustomFieldForm(
|
case class CustomFieldForm(
|
||||||
fieldName: String,
|
fieldName: String,
|
||||||
fieldType: String,
|
fieldType: String,
|
||||||
|
constraints: Option[String],
|
||||||
enableForIssues: Boolean,
|
enableForIssues: Boolean,
|
||||||
enableForPullRequests: Boolean
|
enableForPullRequests: Boolean
|
||||||
)
|
)
|
||||||
@@ -133,6 +134,7 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
|||||||
val customFieldForm = mapping(
|
val customFieldForm = mapping(
|
||||||
"fieldName" -> trim(label("Field name", text(required, maxlength(100)))),
|
"fieldName" -> trim(label("Field name", text(required, maxlength(100)))),
|
||||||
"fieldType" -> trim(label("Field type", text(required))),
|
"fieldType" -> trim(label("Field type", text(required))),
|
||||||
|
"constraints" -> trim(label("Constraints", optional(text()))),
|
||||||
"enableForIssues" -> trim(label("Enable for issues", boolean(required))),
|
"enableForIssues" -> trim(label("Enable for issues", boolean(required))),
|
||||||
"enableForPullRequests" -> trim(label("Enable for pull requests", boolean(required))),
|
"enableForPullRequests" -> trim(label("Enable for pull requests", boolean(required))),
|
||||||
)(CustomFieldForm.apply)
|
)(CustomFieldForm.apply)
|
||||||
@@ -511,6 +513,7 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
|||||||
repository.name,
|
repository.name,
|
||||||
form.fieldName,
|
form.fieldName,
|
||||||
form.fieldType,
|
form.fieldType,
|
||||||
|
if (form.fieldType == "enum") form.constraints else None,
|
||||||
form.enableForIssues,
|
form.enableForIssues,
|
||||||
form.enableForPullRequests
|
form.enableForPullRequests
|
||||||
)
|
)
|
||||||
@@ -533,6 +536,7 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
|||||||
params("fieldId").toInt,
|
params("fieldId").toInt,
|
||||||
form.fieldName,
|
form.fieldName,
|
||||||
form.fieldType,
|
form.fieldType,
|
||||||
|
if (form.fieldType == "enum") form.constraints else None,
|
||||||
form.enableForIssues,
|
form.enableForIssues,
|
||||||
form.enableForPullRequests
|
form.enableForPullRequests
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -124,7 +124,8 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
|
|||||||
)(Upload.apply),
|
)(Upload.apply),
|
||||||
"repositoryViewer" -> mapping(
|
"repositoryViewer" -> mapping(
|
||||||
"maxFiles" -> trim(label("Max files", number(required)))
|
"maxFiles" -> trim(label("Max files", number(required)))
|
||||||
)(RepositoryViewerSettings.apply)
|
)(RepositoryViewerSettings.apply),
|
||||||
|
"defaultBranch" -> trim(label("Default branch", text(required)))
|
||||||
)(SystemSettings.apply).verifying { settings =>
|
)(SystemSettings.apply).verifying { settings =>
|
||||||
Vector(
|
Vector(
|
||||||
if (settings.ssh.enabled && settings.baseUrl.isEmpty) {
|
if (settings.ssh.enabled && settings.baseUrl.isEmpty) {
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ trait WikiControllerBase extends ControllerBase {
|
|||||||
)
|
)
|
||||||
|
|
||||||
val newForm = mapping(
|
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))),
|
"content" -> trim(label("Content", text(required, conflictForNew))),
|
||||||
"message" -> trim(label("Message", optional(text()))),
|
"message" -> trim(label("Message", optional(text()))),
|
||||||
"currentPageName" -> trim(label("Current page name", text())),
|
"currentPageName" -> trim(label("Current page name", text())),
|
||||||
@@ -54,7 +54,7 @@ trait WikiControllerBase extends ControllerBase {
|
|||||||
)(WikiPageEditForm.apply)
|
)(WikiPageEditForm.apply)
|
||||||
|
|
||||||
val editForm = mapping(
|
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))),
|
"content" -> trim(label("Content", text(required, conflictForEdit))),
|
||||||
"message" -> trim(label("Message", optional(text()))),
|
"message" -> trim(label("Message", optional(text()))),
|
||||||
"currentPageName" -> trim(label("Current page name", text(required))),
|
"currentPageName" -> trim(label("Current page name", text(required))),
|
||||||
@@ -62,46 +62,56 @@ trait WikiControllerBase extends ControllerBase {
|
|||||||
)(WikiPageEditForm.apply)
|
)(WikiPageEditForm.apply)
|
||||||
|
|
||||||
get("/:owner/:repository/wiki")(referrersOnly { repository =>
|
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(
|
html.page(
|
||||||
"Home",
|
"Home",
|
||||||
page,
|
page,
|
||||||
getWikiPageList(repository.owner, repository.name),
|
getWikiPageList(repository.owner, repository.name, branch),
|
||||||
repository,
|
repository,
|
||||||
isEditable(repository),
|
isEditable(repository),
|
||||||
getWikiPage(repository.owner, repository.name, "_Sidebar"),
|
getWikiPage(repository.owner, repository.name, "_Sidebar", branch),
|
||||||
getWikiPage(repository.owner, repository.name, "_Footer")
|
getWikiPage(repository.owner, repository.name, "_Footer", branch)
|
||||||
)
|
)
|
||||||
} getOrElse redirect(s"/${repository.owner}/${repository.name}/wiki/Home/_edit")
|
} getOrElse redirect(s"/${repository.owner}/${repository.name}/wiki/Home/_edit")
|
||||||
})
|
})
|
||||||
|
|
||||||
get("/:owner/:repository/wiki/:page")(referrersOnly { repository =>
|
get("/:owner/:repository/wiki/:page")(referrersOnly { repository =>
|
||||||
val pageName = StringUtil.urlDecode(params("page"))
|
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(
|
html.page(
|
||||||
pageName,
|
pageName,
|
||||||
page,
|
page,
|
||||||
getWikiPageList(repository.owner, repository.name),
|
getWikiPageList(repository.owner, repository.name, branch),
|
||||||
repository,
|
repository,
|
||||||
isEditable(repository),
|
isEditable(repository),
|
||||||
getWikiPage(repository.owner, repository.name, "_Sidebar"),
|
getWikiPage(repository.owner, repository.name, "_Sidebar", branch),
|
||||||
getWikiPage(repository.owner, repository.name, "_Footer")
|
getWikiPage(repository.owner, repository.name, "_Footer", branch)
|
||||||
)
|
)
|
||||||
} getOrElse redirect(s"/${repository.owner}/${repository.name}/wiki/${StringUtil.urlEncode(pageName)}/_edit")
|
} getOrElse redirect(s"/${repository.owner}/${repository.name}/wiki/${StringUtil.urlEncode(pageName)}/_edit")
|
||||||
})
|
})
|
||||||
|
|
||||||
get("/:owner/:repository/wiki/:page/_history")(referrersOnly { repository =>
|
get("/:owner/:repository/wiki/:page/_history")(referrersOnly { repository =>
|
||||||
val pageName = StringUtil.urlDecode(params("page"))
|
val pageName = StringUtil.urlDecode(params("page"))
|
||||||
|
val branch = getWikiBranch(repository.owner, repository.name)
|
||||||
|
|
||||||
Using.resource(Git.open(getWikiRepositoryDir(repository.owner, repository.name))) { git =>
|
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 Right((logs, hasNext)) => html.history(Some(pageName), logs, repository, isEditable(repository))
|
||||||
case Left(_) => NotFound()
|
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 =>
|
get("/:owner/:repository/wiki/:page/_compare/:commitId")(referrersOnly { repository =>
|
||||||
val pageName = StringUtil.urlDecode(params("page"))
|
val pageName = StringUtil.urlDecode(params("page"))
|
||||||
val Array(from, to) = params("commitId").split("\\.\\.\\.")
|
val Array(from, to) = params("commitId").split("\\.\\.\\.")
|
||||||
@@ -141,8 +151,9 @@ trait WikiControllerBase extends ControllerBase {
|
|||||||
if (isEditable(repository)) {
|
if (isEditable(repository)) {
|
||||||
val pageName = StringUtil.urlDecode(params("page"))
|
val pageName = StringUtil.urlDecode(params("page"))
|
||||||
val Array(from, to) = params("commitId").split("\\.\\.\\.")
|
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)}")
|
redirect(s"/${repository.owner}/${repository.name}/wiki/${StringUtil.urlEncode(pageName)}")
|
||||||
} else {
|
} else {
|
||||||
flash.update("info", "This patch was not able to be reversed.")
|
flash.update("info", "This patch was not able to be reversed.")
|
||||||
@@ -159,8 +170,9 @@ trait WikiControllerBase extends ControllerBase {
|
|||||||
loginAccount =>
|
loginAccount =>
|
||||||
if (isEditable(repository)) {
|
if (isEditable(repository)) {
|
||||||
val Array(from, to) = params("commitId").split("\\.\\.\\.")
|
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")
|
redirect(s"/${repository.owner}/${repository.name}/wiki")
|
||||||
} else {
|
} else {
|
||||||
flash.update("info", "This patch was not able to be reversed.")
|
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 =>
|
get("/:owner/:repository/wiki/:page/_edit")(readableUsersOnly { repository =>
|
||||||
if (isEditable(repository)) {
|
if (isEditable(repository)) {
|
||||||
val pageName = StringUtil.urlDecode(params("page"))
|
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()
|
} else Unauthorized()
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -280,7 +294,8 @@ trait WikiControllerBase extends ControllerBase {
|
|||||||
})
|
})
|
||||||
|
|
||||||
get("/:owner/:repository/wiki/_pages")(referrersOnly { repository =>
|
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 =>
|
get("/:owner/:repository/wiki/_history")(referrersOnly { repository =>
|
||||||
@@ -309,13 +324,18 @@ trait WikiControllerBase extends ControllerBase {
|
|||||||
value: String,
|
value: String,
|
||||||
params: Map[String, Seq[String]],
|
params: Map[String, Seq[String]],
|
||||||
messages: Messages
|
messages: Messages
|
||||||
): Option[String] =
|
): Option[String] = {
|
||||||
getWikiPageList(params.value("owner"), params.value("repository"))
|
val owner = params.value("owner")
|
||||||
|
val repository = params.value("repository")
|
||||||
|
val branch = getWikiBranch(owner, repository)
|
||||||
|
|
||||||
|
getWikiPageList(owner, repository, branch)
|
||||||
.find(_ == value)
|
.find(_ == value)
|
||||||
.map(_ => "Page already exists.")
|
.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] =
|
override def validate(name: String, value: String, messages: Messages): Option[String] =
|
||||||
if (value.exists("\\/:*?\"<>|".contains(_))) {
|
if (value.exists("\\/:*?\"<>|".contains(_))) {
|
||||||
Some(s"${name} contains invalid character.")
|
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() {
|
private def conflictForNew: Constraint = new Constraint() {
|
||||||
override def validate(name: String, value: String, messages: Messages): Option[String] = {
|
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 = {
|
private def isEditable(repository: RepositoryInfo)(implicit context: Context): Boolean = {
|
||||||
repository.repository.options.wikiOption match {
|
repository.repository.options.wikiOption match {
|
||||||
|
|||||||
@@ -57,17 +57,22 @@ trait ApiGitReferenceControllerBase extends ControllerBase {
|
|||||||
* iii. Create a reference
|
* iii. Create a reference
|
||||||
* https://docs.github.com/en/free-pro-team@latest/rest/reference/git#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 {
|
extractFromJsonBody[CreateARef].map {
|
||||||
data =>
|
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(data.ref)
|
val ref = git.getRepository.findRef(data.ref)
|
||||||
if (ref == null) {
|
if (ref == null) {
|
||||||
val update = git.getRepository.updateRef(data.ref)
|
val update = git.getRepository.updateRef(data.ref)
|
||||||
update.setNewObjectId(ObjectId.fromString(data.sha))
|
update.setNewObjectId(ObjectId.fromString(data.sha))
|
||||||
val result = update.update()
|
val result = update.update()
|
||||||
result match {
|
result match {
|
||||||
case Result.NEW => JsonFormat(ApiRef.fromRef(RepositoryName(repository.owner, repository.name), ref))
|
case Result.NEW =>
|
||||||
|
JsonFormat(
|
||||||
|
ApiRef
|
||||||
|
.fromRef(RepositoryName(repository.owner, repository.name), git.getRepository.findRef(data.ref))
|
||||||
|
)
|
||||||
case _ => UnprocessableEntity(result.name())
|
case _ => UnprocessableEntity(result.name())
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -85,7 +90,7 @@ trait ApiGitReferenceControllerBase extends ControllerBase {
|
|||||||
val refName = multiParams("splat").mkString("/")
|
val refName = multiParams("splat").mkString("/")
|
||||||
extractFromJsonBody[UpdateARef].map {
|
extractFromJsonBody[UpdateARef].map {
|
||||||
data =>
|
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)
|
val ref = git.getRepository.findRef(refName)
|
||||||
if (ref == null) {
|
if (ref == null) {
|
||||||
UnprocessableEntity("Ref does not exist.")
|
UnprocessableEntity("Ref does not exist.")
|
||||||
@@ -96,7 +101,7 @@ trait ApiGitReferenceControllerBase extends ControllerBase {
|
|||||||
val result = update.update()
|
val result = update.update()
|
||||||
result match {
|
result match {
|
||||||
case Result.FORCED | Result.FAST_FORWARD | Result.NO_CHANGE =>
|
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())
|
case _ => UnprocessableEntity(result.name())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ trait ApiIssueCommentControllerBase extends ControllerBase {
|
|||||||
* iv. Delete a comment
|
* iv. Delete a comment
|
||||||
* https://docs.github.com/en/rest/reference/issues#delete-an-issue-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]]] =
|
val maybeDeleteResponse: Option[Either[ActionResult, Option[Int]]] =
|
||||||
for {
|
for {
|
||||||
commentId <- params("id").toIntOpt
|
commentId <- params("id").toIntOpt
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
package gitbucket.core.controller.api
|
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.controller.ControllerBase
|
||||||
import gitbucket.core.service._
|
import gitbucket.core.service._
|
||||||
import gitbucket.core.util.Implicits._
|
import gitbucket.core.util.Implicits._
|
||||||
@@ -121,10 +121,10 @@ trait ApiIssueLabelControllerBase extends ControllerBase {
|
|||||||
*/
|
*/
|
||||||
post("/api/v3/repos/:owner/:repository/issues/:id/labels")(writableUsersOnly { repository =>
|
post("/api/v3/repos/:owner/:repository/issues/:id/labels")(writableUsersOnly { repository =>
|
||||||
JsonFormat(for {
|
JsonFormat(for {
|
||||||
data <- extractFromJsonBody[Seq[String]]
|
data <- extractFromJsonBody[AddLabelsToAnIssue]
|
||||||
issueId <- params("id").toIntOpt
|
issueId <- params("id").toIntOpt
|
||||||
} yield {
|
} yield {
|
||||||
data.map { labelName =>
|
data.labels.map { labelName =>
|
||||||
val label = getLabel(repository.owner, repository.name, labelName).getOrElse(
|
val label = getLabel(repository.owner, repository.name, labelName).getOrElse(
|
||||||
getLabel(
|
getLabel(
|
||||||
repository.owner,
|
repository.owner,
|
||||||
@@ -160,11 +160,11 @@ trait ApiIssueLabelControllerBase extends ControllerBase {
|
|||||||
*/
|
*/
|
||||||
put("/api/v3/repos/:owner/:repository/issues/:id/labels")(writableUsersOnly { repository =>
|
put("/api/v3/repos/:owner/:repository/issues/:id/labels")(writableUsersOnly { repository =>
|
||||||
JsonFormat(for {
|
JsonFormat(for {
|
||||||
data <- extractFromJsonBody[Seq[String]]
|
data <- extractFromJsonBody[AddLabelsToAnIssue]
|
||||||
issueId <- params("id").toIntOpt
|
issueId <- params("id").toIntOpt
|
||||||
} yield {
|
} yield {
|
||||||
deleteAllIssueLabels(repository.owner, repository.name, issueId, true)
|
deleteAllIssueLabels(repository.owner, repository.name, issueId, true)
|
||||||
data.map { labelName =>
|
data.labels.map { labelName =>
|
||||||
val label = getLabel(repository.owner, repository.name, labelName).getOrElse(
|
val label = getLabel(repository.owner, repository.name, labelName).getOrElse(
|
||||||
getLabel(
|
getLabel(
|
||||||
repository.owner,
|
repository.owner,
|
||||||
|
|||||||
@@ -93,7 +93,8 @@ trait ApiRepositoryControllerBase extends ControllerBase {
|
|||||||
data.name,
|
data.name,
|
||||||
data.description,
|
data.description,
|
||||||
data.`private`,
|
data.`private`,
|
||||||
data.auto_init
|
data.auto_init,
|
||||||
|
context.settings.defaultBranch
|
||||||
)
|
)
|
||||||
Await.result(f, Duration.Inf)
|
Await.result(f, Duration.Inf)
|
||||||
|
|
||||||
@@ -130,7 +131,8 @@ trait ApiRepositoryControllerBase extends ControllerBase {
|
|||||||
data.name,
|
data.name,
|
||||||
data.description,
|
data.description,
|
||||||
data.`private`,
|
data.`private`,
|
||||||
data.auto_init
|
data.auto_init,
|
||||||
|
context.settings.defaultBranch
|
||||||
)
|
)
|
||||||
Await.result(f, Duration.Inf)
|
Await.result(f, Duration.Inf)
|
||||||
val repository = Database() withTransaction { session =>
|
val repository = Database() withTransaction { session =>
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ trait AccessTokenComponent { self: Profile =>
|
|||||||
val userName = column[String]("USER_NAME")
|
val userName = column[String]("USER_NAME")
|
||||||
val tokenHash = column[String]("TOKEN_HASH")
|
val tokenHash = column[String]("TOKEN_HASH")
|
||||||
val note = column[String]("NOTE")
|
val note = column[String]("NOTE")
|
||||||
def * = (accessTokenId, userName, tokenHash, note).<>(AccessToken.tupled, AccessToken.unapply)
|
def * = (accessTokenId, userName, tokenHash, note).mapTo[AccessToken]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case class AccessToken(
|
case class AccessToken(
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ trait AccountComponent { self: Profile =>
|
|||||||
groupAccount,
|
groupAccount,
|
||||||
removed,
|
removed,
|
||||||
description.?
|
description.?
|
||||||
).<>(Account.tupled, Account.unapply)
|
).mapTo[Account]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ trait AccountExtraMailAddressComponent { self: Profile =>
|
|||||||
val userName = column[String]("USER_NAME", O PrimaryKey)
|
val userName = column[String]("USER_NAME", O PrimaryKey)
|
||||||
val extraMailAddress = column[String]("EXTRA_MAIL_ADDRESS", O PrimaryKey)
|
val extraMailAddress = column[String]("EXTRA_MAIL_ADDRESS", O PrimaryKey)
|
||||||
def * =
|
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 issuer = column[String]("ISSUER")
|
||||||
val subject = column[String]("SUBJECT")
|
val subject = column[String]("SUBJECT")
|
||||||
val userName = column[String]("USER_NAME")
|
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] =
|
def byPrimaryKey(issuer: String, subject: String): Rep[Boolean] =
|
||||||
(this.issuer === issuer.bind) && (this.subject === subject.bind)
|
(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 userName = column[String]("USER_NAME", O PrimaryKey)
|
||||||
val highlighterTheme = column[String]("HIGHLIGHTER_THEME")
|
val highlighterTheme = column[String]("HIGHLIGHTER_THEME")
|
||||||
def * =
|
def * =
|
||||||
(userName, highlighterTheme).<>(AccountPreference.tupled, AccountPreference.unapply)
|
(userName, highlighterTheme).mapTo[AccountPreference]
|
||||||
|
|
||||||
def byPrimaryKey(userName: String): Rep[Boolean] = this.userName === userName.bind
|
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 url = column[String]("URL")
|
||||||
val token = column[Option[String]]("TOKEN")
|
val token = column[Option[String]]("TOKEN")
|
||||||
val ctype = column[WebHookContentType]("CTYPE")
|
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)
|
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 url = column[String]("URL")
|
||||||
val event = column[WebHook.Event]("EVENT")
|
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)
|
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]
|
lazy val Activities = TableQuery[Activities]
|
||||||
|
|
||||||
class Activities(tag: Tag) extends Table[Activity](tag, "ACTIVITY") with BasicTemplate {
|
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 {
|
class Collaborators(tag: Tag) extends Table[Collaborator](tag, "COLLABORATOR") with BasicTemplate {
|
||||||
val collaboratorName = column[String]("COLLABORATOR_NAME")
|
val collaboratorName = column[String]("COLLABORATOR_NAME")
|
||||||
val role = column[String]("ROLE")
|
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) =
|
def byPrimaryKey(owner: String, repository: String, collaborator: String) =
|
||||||
byRepository(owner, repository) && (collaboratorName === collaborator.bind)
|
byRepository(owner, repository) && (collaboratorName === collaborator.bind)
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ trait IssueCommentComponent extends TemplateComponent { self: Profile =>
|
|||||||
val updatedDate = column[java.util.Date]("UPDATED_DATE")
|
val updatedDate = column[java.util.Date]("UPDATED_DATE")
|
||||||
def * =
|
def * =
|
||||||
(userName, repositoryName, issueId, commentId, action, commentedUserName, content, registeredDate, updatedDate)
|
(userName, repositoryName, issueId, commentId, action, commentedUserName, content, registeredDate, updatedDate)
|
||||||
.<>(IssueComment.tupled, IssueComment.unapply)
|
.mapTo[IssueComment]
|
||||||
|
|
||||||
def byPrimaryKey(commentId: Int) = this.commentId === commentId.bind
|
def byPrimaryKey(commentId: Int) = this.commentId === commentId.bind
|
||||||
}
|
}
|
||||||
@@ -75,7 +75,7 @@ trait CommitCommentComponent extends TemplateComponent { self: Profile =>
|
|||||||
originalCommitId,
|
originalCommitId,
|
||||||
originalOldLine,
|
originalOldLine,
|
||||||
originalNewLine
|
originalNewLine
|
||||||
).<>(CommitComment.tupled, CommitComment.unapply)
|
).mapTo[CommitComment]
|
||||||
|
|
||||||
def byPrimaryKey(commentId: Int) = this.commentId === commentId.bind
|
def byPrimaryKey(commentId: Int) = this.commentId === commentId.bind
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ trait CommitStatusComponent extends TemplateComponent { self: Profile =>
|
|||||||
creator,
|
creator,
|
||||||
registeredDate,
|
registeredDate,
|
||||||
updatedDate
|
updatedDate
|
||||||
).<>((CommitStatus.apply _).tupled, CommitStatus.unapply)
|
).mapTo[CommitStatus]
|
||||||
def byPrimaryKey(id: Int) = commitStatusId === id.bind
|
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.util.StringUtil
|
||||||
import gitbucket.core.view.helpers
|
import gitbucket.core.view.helpers
|
||||||
import org.scalatra.i18n.Messages
|
import org.scalatra.i18n.Messages
|
||||||
|
import play.twirl.api.Html
|
||||||
|
|
||||||
trait CustomFieldComponent extends TemplateComponent { self: Profile =>
|
trait CustomFieldComponent extends TemplateComponent { self: Profile =>
|
||||||
import profile.api._
|
import profile.api._
|
||||||
@@ -15,11 +16,12 @@ trait CustomFieldComponent extends TemplateComponent { self: Profile =>
|
|||||||
val fieldId = column[Int]("FIELD_ID", O AutoInc)
|
val fieldId = column[Int]("FIELD_ID", O AutoInc)
|
||||||
val fieldName = column[String]("FIELD_NAME")
|
val fieldName = column[String]("FIELD_NAME")
|
||||||
val fieldType = column[String]("FIELD_TYPE")
|
val fieldType = column[String]("FIELD_TYPE")
|
||||||
|
val constraints = column[Option[String]]("CONSTRAINTS")
|
||||||
val enableForIssues = column[Boolean]("ENABLE_FOR_ISSUES")
|
val enableForIssues = column[Boolean]("ENABLE_FOR_ISSUES")
|
||||||
val enableForPullRequests = column[Boolean]("ENABLE_FOR_PULL_REQUESTS")
|
val enableForPullRequests = column[Boolean]("ENABLE_FOR_PULL_REQUESTS")
|
||||||
def * =
|
def * =
|
||||||
(userName, repositoryName, fieldId, fieldName, fieldType, enableForIssues, enableForPullRequests)
|
(userName, repositoryName, fieldId, fieldName, fieldType, constraints, enableForIssues, enableForPullRequests)
|
||||||
.<>(CustomField.tupled, CustomField.unapply)
|
.mapTo[CustomField]
|
||||||
|
|
||||||
def byPrimaryKey(userName: String, repositoryName: String, fieldId: Int) =
|
def byPrimaryKey(userName: String, repositoryName: String, fieldId: Int) =
|
||||||
(this.userName === userName.bind) && (this.repositoryName === repositoryName.bind) && (this.fieldId === fieldId.bind)
|
(this.userName === userName.bind) && (this.repositoryName === repositoryName.bind) && (this.fieldId === fieldId.bind)
|
||||||
@@ -31,17 +33,28 @@ case class CustomField(
|
|||||||
repositoryName: String,
|
repositoryName: String,
|
||||||
fieldId: Int = 0,
|
fieldId: Int = 0,
|
||||||
fieldName: String,
|
fieldName: String,
|
||||||
fieldType: String, // long, double, string, or date
|
fieldType: String, // long, double, string, date, or enum
|
||||||
|
constraints: Option[String],
|
||||||
enableForIssues: Boolean,
|
enableForIssues: Boolean,
|
||||||
enableForPullRequests: Boolean
|
enableForPullRequests: Boolean
|
||||||
)
|
)
|
||||||
|
|
||||||
trait CustomFieldBehavior {
|
trait CustomFieldBehavior {
|
||||||
def createHtml(repository: RepositoryInfo, fieldId: Int)(implicit conext: Context): String
|
def createHtml(repository: RepositoryInfo, fieldId: Int, fieldName: String, constraints: Option[String])(
|
||||||
def fieldHtml(repository: RepositoryInfo, issueId: Int, fieldId: Int, value: String, editable: Boolean)(
|
|
||||||
implicit context: Context
|
implicit context: Context
|
||||||
): String
|
): 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 {
|
object CustomFieldBehavior {
|
||||||
@@ -49,7 +62,7 @@ object CustomFieldBehavior {
|
|||||||
if (value.isEmpty) None
|
if (value.isEmpty) None
|
||||||
else {
|
else {
|
||||||
CustomFieldBehavior(field.fieldType).flatMap { behavior =>
|
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 "double" => Some(DoubleFieldBehavior)
|
||||||
case "string" => Some(StringFieldBehavior)
|
case "string" => Some(StringFieldBehavior)
|
||||||
case "date" => Some(DateFieldBehavior)
|
case "date" => Some(DateFieldBehavior)
|
||||||
|
case "enum" => Some(EnumFieldBehavior)
|
||||||
case _ => None
|
case _ => None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
case object LongFieldBehavior extends TextFieldBehavior {
|
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 {
|
try {
|
||||||
value.toLong
|
value.toLong
|
||||||
None
|
None
|
||||||
@@ -75,7 +94,12 @@ object CustomFieldBehavior {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
case object DoubleFieldBehavior extends TextFieldBehavior {
|
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 {
|
try {
|
||||||
value.toDouble
|
value.toDouble
|
||||||
None
|
None
|
||||||
@@ -89,7 +113,12 @@ object CustomFieldBehavior {
|
|||||||
private val pattern = "yyyy-MM-dd"
|
private val pattern = "yyyy-MM-dd"
|
||||||
override protected val fieldType: String = "date"
|
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 {
|
try {
|
||||||
new java.text.SimpleDateFormat(pattern).parse(value)
|
new java.text.SimpleDateFormat(pattern).parse(value)
|
||||||
None
|
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 {
|
trait TextFieldBehavior extends CustomFieldBehavior {
|
||||||
protected val fieldType = "text"
|
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
|
val sb = new StringBuilder
|
||||||
sb.append(
|
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;"/>"""
|
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>
|
sb.append(s"""<script>
|
||||||
|$$('#custom-field-$fieldId').focusout(function(){
|
|$$('#custom-field-$fieldId').focusout(function(){
|
||||||
| const $$this = $$(this);
|
| 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() },
|
| { value: $$this.val() },
|
||||||
| function(data){
|
| function(data){
|
||||||
| if (data != '') {
|
| if (data != '') {
|
||||||
@@ -128,14 +288,34 @@ object CustomFieldBehavior {
|
|||||||
sb.toString()
|
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
|
implicit context: Context
|
||||||
): String = {
|
): String = {
|
||||||
val sb = new StringBuilder
|
val sb = new StringBuilder
|
||||||
|
if (value.nonEmpty) {
|
||||||
sb.append(
|
sb.append(
|
||||||
s"""<span id="custom-field-$fieldId-label" class="custom-field-label">${StringUtil
|
s"""<span id="custom-field-$fieldId-label" class="custom-field-label">${StringUtil
|
||||||
.escapeHtml(value)}</span>""".stripMargin
|
.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) {
|
if (editable) {
|
||||||
sb.append(
|
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;"/>"""
|
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,20 +329,23 @@ object CustomFieldBehavior {
|
|||||||
|
|
|
|
||||||
|$$('#custom-field-$fieldId-editor').focusout(function(){
|
|$$('#custom-field-$fieldId-editor').focusout(function(){
|
||||||
| const $$this = $$(this);
|
| 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() },
|
| { value: $$this.val() },
|
||||||
| function(data){
|
| function(data){
|
||||||
| if (data != '') {
|
| if (data != '') {
|
||||||
| $$('#custom-field-$fieldId-error').text(data);
|
| $$('#custom-field-$fieldId-error').text(data);
|
||||||
| } else {
|
| } else {
|
||||||
| $$('#custom-field-$fieldId-error').text('');
|
| $$('#custom-field-$fieldId-error').text('');
|
||||||
| $$.post('${helpers.url(repository)}/issues/$issueId/customfield/' + fieldId,
|
| $$.post('${helpers.url(repository)}/issues/$issueId/customfield/$fieldId',
|
||||||
| { value: $$this.val() },
|
| { value: $$this.val() },
|
||||||
| function(data){
|
| function(data){
|
||||||
| $$this.hide();
|
| $$this.hide();
|
||||||
|
| if (data == '') {
|
||||||
|
| $$this.prev().html('<i class="octicon octicon-pencil" style="cursor: pointer;">').show();
|
||||||
|
| } else {
|
||||||
| $$this.prev().text(data).show();
|
| $$this.prev().text(data).show();
|
||||||
| }
|
| }
|
||||||
|
| }
|
||||||
| );
|
| );
|
||||||
| }
|
| }
|
||||||
| }
|
| }
|
||||||
@@ -186,6 +369,11 @@ object CustomFieldBehavior {
|
|||||||
sb.toString()
|
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 publicKey = column[String]("PUBLIC_KEY")
|
||||||
val allowWrite = column[Boolean]("ALLOW_WRITE")
|
val allowWrite = column[Boolean]("ALLOW_WRITE")
|
||||||
def * =
|
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) =
|
def byPrimaryKey(userName: String, repositoryName: String, deployKeyId: Int) =
|
||||||
(this.userName === userName.bind) && (this.repositoryName === repositoryName.bind) && (this.deployKeyId === deployKeyId.bind)
|
(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 gpgKeyId = column[Long]("GPG_KEY_ID")
|
||||||
val title = column[String]("TITLE")
|
val title = column[String]("TITLE")
|
||||||
val publicKey = column[String]("PUBLIC_KEY")
|
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) =
|
def byPrimaryKey(userName: String, keyId: Int) =
|
||||||
(this.userName === userName.bind) && (this.keyId === keyId.bind)
|
(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 groupName = column[String]("GROUP_NAME", O PrimaryKey)
|
||||||
val userName = column[String]("USER_NAME", O PrimaryKey)
|
val userName = column[String]("USER_NAME", O PrimaryKey)
|
||||||
val isManager = column[Boolean]("MANAGER")
|
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,
|
registeredDate,
|
||||||
updatedDate,
|
updatedDate,
|
||||||
pullRequest
|
pullRequest
|
||||||
).<>(Issue.tupled, Issue.unapply)
|
).mapTo[Issue]
|
||||||
|
|
||||||
def byPrimaryKey(owner: String, repository: String, issueId: Int) = byIssue(owner, repository, issueId)
|
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 {
|
class IssueAssignees(tag: Tag) extends Table[IssueAssignee](tag, "ISSUE_ASSIGNEE") with IssueTemplate {
|
||||||
val assigneeUserName = column[String]("ASSIGNEE_USER_NAME")
|
val assigneeUserName = column[String]("ASSIGNEE_USER_NAME")
|
||||||
def * =
|
def * =
|
||||||
(userName, repositoryName, issueId, assigneeUserName)
|
(userName, repositoryName, issueId, assigneeUserName).mapTo[IssueAssignee]
|
||||||
.<>(IssueAssignee.tupled, IssueAssignee.unapply)
|
|
||||||
|
|
||||||
def byPrimaryKey(owner: String, repository: String, issueId: Int, assigneeUserName: String) = {
|
def byPrimaryKey(owner: String, repository: String, issueId: Int, assigneeUserName: String) = {
|
||||||
byIssue(owner, repository, issueId) && this.assigneeUserName === assigneeUserName.bind
|
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 fieldId = column[Int]("FIELD_ID", O.PrimaryKey)
|
||||||
val value = column[String]("VALUE")
|
val value = column[String]("VALUE")
|
||||||
def * =
|
def * =
|
||||||
(userName, repositoryName, issueId, fieldId, value)
|
(userName, repositoryName, issueId, fieldId, value).mapTo[IssueCustomField]
|
||||||
.<>(IssueCustomField.tupled, IssueCustomField.unapply)
|
|
||||||
|
|
||||||
def byPrimaryKey(owner: String, repository: String, issueId: Int, fieldId: Int) = {
|
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
|
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]
|
lazy val IssueLabels = TableQuery[IssueLabels]
|
||||||
|
|
||||||
class IssueLabels(tag: Tag) extends Table[IssueLabel](tag, "ISSUE_LABEL") with IssueTemplate with LabelTemplate {
|
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) =
|
def byPrimaryKey(owner: String, repository: String, issueId: Int, labelId: Int) =
|
||||||
byIssue(owner, repository, issueId) && (this.labelId === labelId.bind)
|
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 labelId = column[Int]("LABEL_ID", O AutoInc)
|
||||||
override val labelName = column[String]("LABEL_NAME")
|
override val labelName = column[String]("LABEL_NAME")
|
||||||
val color = column[String]("COLOR")
|
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(owner: String, repository: String, labelId: Int) = byLabel(owner, repository, labelId)
|
||||||
def byPrimaryKey(userName: Rep[String], repositoryName: Rep[String], labelId: Rep[Int]) =
|
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 dueDate = column[Option[java.util.Date]]("DUE_DATE")
|
||||||
val closedDate = column[Option[java.util.Date]]("CLOSED_DATE")
|
val closedDate = column[Option[java.util.Date]]("CLOSED_DATE")
|
||||||
def * =
|
def * =
|
||||||
(userName, repositoryName, milestoneId, title, description, dueDate, closedDate)
|
(userName, repositoryName, milestoneId, title, description, dueDate, closedDate).mapTo[Milestone]
|
||||||
.<>(Milestone.tupled, Milestone.unapply)
|
|
||||||
|
|
||||||
def byPrimaryKey(owner: String, repository: String, milestoneId: Int) = byMilestone(owner, repository, milestoneId)
|
def byPrimaryKey(owner: String, repository: String, milestoneId: Int) = byMilestone(owner, repository, milestoneId)
|
||||||
def byPrimaryKey(userName: Rep[String], repositoryName: Rep[String], milestoneId: Rep[Int]) =
|
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 isDefault = column[Boolean]("IS_DEFAULT")
|
||||||
val color = column[String]("COLOR")
|
val color = column[String]("COLOR")
|
||||||
def * =
|
def * =
|
||||||
(userName, repositoryName, priorityId, priorityName, description.?, isDefault, ordering, color)
|
(userName, repositoryName, priorityId, priorityName, description.?, isDefault, ordering, color).mapTo[Priority]
|
||||||
.<>(Priority.tupled, Priority.unapply)
|
|
||||||
|
|
||||||
def byPrimaryKey(owner: String, repository: String, priorityId: Int) = byPriority(owner, repository, priorityId)
|
def byPrimaryKey(owner: String, repository: String, priorityId: Int) = byPriority(owner, repository, priorityId)
|
||||||
def byPrimaryKey(userName: Rep[String], repositoryName: Rep[String], priorityId: Rep[Int]) =
|
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]
|
lazy val ProtectedBranches = TableQuery[ProtectedBranches]
|
||||||
class ProtectedBranches(tag: Tag) extends Table[ProtectedBranch](tag, "PROTECTED_BRANCH") with BranchTemplate {
|
class ProtectedBranches(tag: Tag) extends Table[ProtectedBranch](tag, "PROTECTED_BRANCH") with BranchTemplate {
|
||||||
val statusCheckAdmin = column[Boolean]("STATUS_CHECK_ADMIN")
|
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) =
|
def byPrimaryKey(userName: String, repositoryName: String, branch: String) =
|
||||||
byBranch(userName, repositoryName, branch)
|
byBranch(userName, repositoryName, branch)
|
||||||
def byPrimaryKey(userName: Rep[String], repositoryName: Rep[String], branch: Rep[String]) =
|
def byPrimaryKey(userName: Rep[String], repositoryName: Rep[String], branch: Rep[String]) =
|
||||||
@@ -20,7 +20,7 @@ trait ProtectedBranchComponent extends TemplateComponent { self: Profile =>
|
|||||||
with BranchTemplate {
|
with BranchTemplate {
|
||||||
val context = column[String]("CONTEXT")
|
val context = column[String]("CONTEXT")
|
||||||
def * =
|
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,
|
commitIdFrom,
|
||||||
commitIdTo,
|
commitIdTo,
|
||||||
isDraft
|
isDraft
|
||||||
).<>(PullRequest.tupled, PullRequest.unapply)
|
).mapTo[PullRequest]
|
||||||
|
|
||||||
def byPrimaryKey(userName: String, repositoryName: String, issueId: Int) =
|
def byPrimaryKey(userName: String, repositoryName: String, issueId: Int) =
|
||||||
byIssue(userName, repositoryName, issueId)
|
byIssue(userName, repositoryName, issueId)
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ trait ReleaseAssetComponent extends TemplateComponent { self: Profile =>
|
|||||||
|
|
||||||
def * =
|
def * =
|
||||||
(userName, repositoryName, tag, releaseAssetId, fileName, label, size, uploader, registeredDate, updatedDate)
|
(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) =
|
def byPrimaryKey(owner: String, repository: String, tag: String, fileName: String) =
|
||||||
byTag(owner, repository, tag) && (this.fileName === fileName.bind)
|
byTag(owner, repository, tag) && (this.fileName === fileName.bind)
|
||||||
def byTag(owner: String, repository: String, tag: String) =
|
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")
|
val updatedDate = column[java.util.Date]("UPDATED_DATE")
|
||||||
|
|
||||||
def * =
|
def * =
|
||||||
(userName, repositoryName, name, tag, author, content, registeredDate, updatedDate)
|
(userName, repositoryName, name, tag, author, content, registeredDate, updatedDate).mapTo[ReleaseTag]
|
||||||
.<>(ReleaseTag.tupled, ReleaseTag.unapply)
|
|
||||||
def byPrimaryKey(owner: String, repository: String, tag: String) = byTag(owner, repository, tag)
|
def byPrimaryKey(owner: String, repository: String, tag: String) = byTag(owner, repository, tag)
|
||||||
def byTag(owner: String, repository: String, tag: String) =
|
def byTag(owner: String, repository: String, tag: String) =
|
||||||
byRepository(owner, repository) && (this.tag === tag.bind)
|
byRepository(owner, repository) && (this.tag === tag.bind)
|
||||||
|
|||||||
@@ -14,8 +14,7 @@ trait RepositoryWebHookComponent extends TemplateComponent { self: Profile =>
|
|||||||
val token = column[Option[String]]("TOKEN")
|
val token = column[Option[String]]("TOKEN")
|
||||||
val ctype = column[WebHookContentType]("CTYPE")
|
val ctype = column[WebHookContentType]("CTYPE")
|
||||||
def * =
|
def * =
|
||||||
(userName, repositoryName, hookId, url, ctype, token)
|
(userName, repositoryName, hookId, url, ctype, token).mapTo[RepositoryWebHook]
|
||||||
.<>((RepositoryWebHook.apply _).tupled, RepositoryWebHook.unapply)
|
|
||||||
|
|
||||||
def byRepositoryUrl(owner: String, repository: String, url: String) =
|
def byRepositoryUrl(owner: String, repository: String, url: String) =
|
||||||
byRepository(owner, repository) && (this.url === url.bind)
|
byRepository(owner, repository) && (this.url === url.bind)
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ trait RepositoryWebHookEventComponent extends TemplateComponent { self: Profile
|
|||||||
val url = column[String]("URL")
|
val url = column[String]("URL")
|
||||||
val event = column[WebHook.Event]("EVENT")
|
val event = column[WebHook.Event]("EVENT")
|
||||||
def * =
|
def * =
|
||||||
(userName, repositoryName, url, event).<>((RepositoryWebHookEvent.apply _).tupled, RepositoryWebHookEvent.unapply)
|
(userName, repositoryName, url, event).mapTo[RepositoryWebHookEvent]
|
||||||
|
|
||||||
def byRepositoryWebHook(owner: String, repository: String, url: String) =
|
def byRepositoryWebHook(owner: String, repository: String, url: String) =
|
||||||
byRepository(owner, repository) && (this.url === url.bind)
|
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 sshKeyId = column[Int]("SSH_KEY_ID", O AutoInc)
|
||||||
val title = column[String]("TITLE")
|
val title = column[String]("TITLE")
|
||||||
val publicKey = column[String]("PUBLIC_KEY")
|
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) =
|
def byPrimaryKey(userName: String, sshKeyId: Int) =
|
||||||
(this.userName === userName.bind) && (this.sshKeyId === sshKeyId.bind)
|
(this.userName === userName.bind) && (this.sshKeyId === sshKeyId.bind)
|
||||||
|
|||||||
@@ -9,9 +9,10 @@ import org.json4s.jackson.Serialization.{read, write}
|
|||||||
import scala.util.Using
|
import scala.util.Using
|
||||||
import java.io.FileOutputStream
|
import java.io.FileOutputStream
|
||||||
import java.nio.charset.StandardCharsets
|
import java.nio.charset.StandardCharsets
|
||||||
|
|
||||||
import gitbucket.core.controller.Context
|
import gitbucket.core.controller.Context
|
||||||
|
import gitbucket.core.util.ConfigUtil
|
||||||
import org.apache.commons.io.input.ReversedLinesFileReader
|
import org.apache.commons.io.input.ReversedLinesFileReader
|
||||||
|
import ActivityService._
|
||||||
|
|
||||||
import scala.collection.mutable.ListBuffer
|
import scala.collection.mutable.ListBuffer
|
||||||
|
|
||||||
@@ -26,40 +27,52 @@ trait ActivityService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def getActivitiesByUser(activityUserName: String, isPublic: Boolean)(implicit context: Context): List[Activity] = {
|
def getActivitiesByUser(activityUserName: String, publicOnly: Boolean)(implicit context: Context): List[Activity] = {
|
||||||
if (!ActivityLog.exists()) {
|
getActivities(includePublic = false) { activity =>
|
||||||
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 (activity.activityUserName == activityUserName) {
|
||||||
if (isPublic == false) {
|
!publicOnly || isPublicActivity(activity)
|
||||||
list += activity
|
} else false
|
||||||
} else {
|
|
||||||
if (!getRepositoryInfoFromCache(activity.userName, activity.repositoryName).forall(_.isPrivate)) {
|
|
||||||
list += activity
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
list.toList
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def getRecentPublicActivities()(implicit context: Context): List[Activity] = {
|
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
|
List.empty
|
||||||
} else {
|
} else {
|
||||||
val list = new ListBuffer[Activity]
|
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
|
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)
|
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
|
list += activity
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -68,24 +81,8 @@ trait ActivityService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def getRecentActivitiesByOwners(owners: Set[String])(implicit context: Context): List[Activity] = {
|
private def isPublicActivity(activity: Activity)(implicit context: Context): Boolean = {
|
||||||
if (!ActivityLog.exists()) {
|
!getRepositoryInfoFromCache(activity.userName, activity.repositoryName).forall(_.isPrivate)
|
||||||
List.empty
|
|
||||||
} else {
|
|
||||||
val list = new ListBuffer[Activity]
|
|
||||||
Using.resource(new ReversedLinesFileReader(ActivityLog, StandardCharsets.UTF_8)) { reader =>
|
|
||||||
var json: String = null
|
|
||||||
while (list.length < 50 && { json = reader.readLine(); json } != null) {
|
|
||||||
val activity = read[Activity](json)
|
|
||||||
if (owners.contains(activity.userName)) {
|
|
||||||
list += activity
|
|
||||||
} else if (!getRepositoryInfoFromCache(activity.userName, activity.repositoryName).forall(_.isPrivate)) {
|
|
||||||
list += activity
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
list.toList
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def recordActivity[T <: { def toActivity: Activity }](info: T): Unit = {
|
def recordActivity[T <: { def toActivity: Activity }](info: T): Unit = {
|
||||||
@@ -93,3 +90,8 @@ trait ActivityService {
|
|||||||
writeLog(info.toActivity)
|
writeLog(info.toActivity)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
object ActivityService {
|
||||||
|
def isNewsFeedEnabled: Boolean =
|
||||||
|
!ConfigUtil.getConfigValue[Boolean]("gitbucket.disableNewsFeed").getOrElse(false)
|
||||||
|
}
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ trait CustomFieldsService {
|
|||||||
repository: String,
|
repository: String,
|
||||||
fieldName: String,
|
fieldName: String,
|
||||||
fieldType: String,
|
fieldType: String,
|
||||||
|
constraints: Option[String],
|
||||||
enableForIssues: Boolean,
|
enableForIssues: Boolean,
|
||||||
enableForPullRequests: Boolean
|
enableForPullRequests: Boolean
|
||||||
)(implicit s: Session): Int = {
|
)(implicit s: Session): Int = {
|
||||||
@@ -36,6 +37,7 @@ trait CustomFieldsService {
|
|||||||
repositoryName = repository,
|
repositoryName = repository,
|
||||||
fieldName = fieldName,
|
fieldName = fieldName,
|
||||||
fieldType = fieldType,
|
fieldType = fieldType,
|
||||||
|
constraints = constraints,
|
||||||
enableForIssues = enableForIssues,
|
enableForIssues = enableForIssues,
|
||||||
enableForPullRequests = enableForPullRequests
|
enableForPullRequests = enableForPullRequests
|
||||||
)
|
)
|
||||||
@@ -47,6 +49,7 @@ trait CustomFieldsService {
|
|||||||
fieldId: Int,
|
fieldId: Int,
|
||||||
fieldName: String,
|
fieldName: String,
|
||||||
fieldType: String,
|
fieldType: String,
|
||||||
|
constraints: Option[String],
|
||||||
enableForIssues: Boolean,
|
enableForIssues: Boolean,
|
||||||
enableForPullRequests: Boolean
|
enableForPullRequests: Boolean
|
||||||
)(
|
)(
|
||||||
@@ -54,8 +57,8 @@ trait CustomFieldsService {
|
|||||||
): Unit =
|
): Unit =
|
||||||
CustomFields
|
CustomFields
|
||||||
.filter(_.byPrimaryKey(owner, repository, fieldId))
|
.filter(_.byPrimaryKey(owner, repository, fieldId))
|
||||||
.map(t => (t.fieldName, t.fieldType, t.enableForIssues, t.enableForPullRequests))
|
.map(t => (t.fieldName, t.fieldType, t.constraints, t.enableForIssues, t.enableForPullRequests))
|
||||||
.update((fieldName, fieldType, enableForIssues, enableForPullRequests))
|
.update((fieldName, fieldType, constraints, enableForIssues, enableForPullRequests))
|
||||||
|
|
||||||
def deleteCustomField(owner: String, repository: String, fieldId: Int)(implicit s: Session): Unit = {
|
def deleteCustomField(owner: String, repository: String, fieldId: Int)(implicit s: Session): Unit = {
|
||||||
IssueCustomFields
|
IssueCustomFields
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import gitbucket.core.model.{
|
|||||||
IssueComment,
|
IssueComment,
|
||||||
IssueLabel,
|
IssueLabel,
|
||||||
Label,
|
Label,
|
||||||
|
Profile,
|
||||||
PullRequest,
|
PullRequest,
|
||||||
Repository,
|
Repository,
|
||||||
Role
|
Role
|
||||||
@@ -22,6 +23,8 @@ import gitbucket.core.model.Profile.profile.blockingApi._
|
|||||||
import gitbucket.core.model.Profile.dateColumnType
|
import gitbucket.core.model.Profile.dateColumnType
|
||||||
import gitbucket.core.plugin.PluginRegistry
|
import gitbucket.core.plugin.PluginRegistry
|
||||||
|
|
||||||
|
import scala.jdk.CollectionConverters._
|
||||||
|
|
||||||
trait IssuesService {
|
trait IssuesService {
|
||||||
self: AccountService with RepositoryService with LabelsService with PrioritiesService with MilestonesService =>
|
self: AccountService with RepositoryService with LabelsService with PrioritiesService with MilestonesService =>
|
||||||
import IssuesService._
|
import IssuesService._
|
||||||
@@ -379,8 +382,8 @@ trait IssuesService {
|
|||||||
searchOption: IssueSearchOption
|
searchOption: IssueSearchOption
|
||||||
)(
|
)(
|
||||||
implicit s: Session
|
implicit s: Session
|
||||||
) =
|
) = {
|
||||||
Issues filter { t1 =>
|
val query = Issues filter { t1 =>
|
||||||
(if (repos.sizeIs == 1) {
|
(if (repos.sizeIs == 1) {
|
||||||
t1.byRepository(repos.head._1, repos.head._2)
|
t1.byRepository(repos.head._1, repos.head._2)
|
||||||
} else {
|
} else {
|
||||||
@@ -390,8 +393,8 @@ trait IssuesService {
|
|||||||
case "open" => t1.closed === false
|
case "open" => t1.closed === false
|
||||||
case "closed" => t1.closed === true
|
case "closed" => t1.closed === true
|
||||||
case _ => t1.closed === true || t1.closed === false
|
case _ => t1.closed === true || t1.closed === false
|
||||||
}).&&(t1.milestoneId.? isEmpty, condition.milestone == Some(None))
|
}).&&(t1.milestoneId.? isEmpty, condition.milestone.contains(None))
|
||||||
.&&(t1.priorityId.? isEmpty, condition.priority == Some(None))
|
.&&(t1.priorityId.? isEmpty, condition.priority.contains(None))
|
||||||
//.&&(t1.assignedUserName.? isEmpty, condition.assigned == Some(None))
|
//.&&(t1.assignedUserName.? isEmpty, condition.assigned == Some(None))
|
||||||
.&&(t1.openedUserName === condition.author.get.bind, condition.author.isDefined) &&
|
.&&(t1.openedUserName === condition.author.get.bind, condition.author.isDefined) &&
|
||||||
(searchOption match {
|
(searchOption match {
|
||||||
@@ -439,7 +442,7 @@ trait IssuesService {
|
|||||||
.&&(
|
.&&(
|
||||||
Repositories filter { t2 =>
|
Repositories filter { t2 =>
|
||||||
(t2.byRepository(t1.userName, t1.repositoryName)) &&
|
(t2.byRepository(t1.userName, t1.repositoryName)) &&
|
||||||
(t2.isPrivate === (condition.visibility == Some("private")).bind)
|
(t2.isPrivate === condition.visibility.contains("private").bind)
|
||||||
} exists,
|
} exists,
|
||||||
condition.visibility.nonEmpty
|
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(
|
def insertIssue(
|
||||||
owner: String,
|
owner: String,
|
||||||
repository: String,
|
repository: String,
|
||||||
@@ -584,7 +615,7 @@ trait IssuesService {
|
|||||||
.update(title, content, currentDate)
|
.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
|
Issues
|
||||||
.filter(_.byPrimaryKey(owner, repository, issueId))
|
.filter(_.byPrimaryKey(owner, repository, issueId))
|
||||||
.map { t =>
|
.map { t =>
|
||||||
@@ -943,6 +974,8 @@ object IssuesService {
|
|||||||
|
|
||||||
val IssueLimit = 25
|
val IssueLimit = 25
|
||||||
|
|
||||||
|
case class CustomFieldCondition(name: String, value: String, operator: String)
|
||||||
|
|
||||||
case class IssueSearchCondition(
|
case class IssueSearchCondition(
|
||||||
labels: Set[String] = Set.empty,
|
labels: Set[String] = Set.empty,
|
||||||
milestone: Option[Option[String]] = None,
|
milestone: Option[Option[String]] = None,
|
||||||
@@ -954,7 +987,8 @@ object IssuesService {
|
|||||||
sort: String = "created",
|
sort: String = "created",
|
||||||
direction: String = "desc",
|
direction: String = "desc",
|
||||||
visibility: Option[String] = None,
|
visibility: Option[String] = None,
|
||||||
groups: Set[String] = Set.empty
|
groups: Set[String] = Set.empty,
|
||||||
|
others: Seq[CustomFieldCondition] = Nil
|
||||||
) {
|
) {
|
||||||
|
|
||||||
def isEmpty: Boolean = {
|
def isEmpty: Boolean = {
|
||||||
@@ -993,48 +1027,148 @@ object IssuesService {
|
|||||||
case ("priority", "asc") => Some("sort:priority-asc")
|
case ("priority", "asc") => Some("sort:priority-asc")
|
||||||
case x => throw new MatchError(x)
|
case x => throw new MatchError(x)
|
||||||
},
|
},
|
||||||
visibility.map(visibility => s"visibility:${visibility}")
|
visibility.map(visibility => s"visibility:${visibility}"),
|
||||||
).flatten ++
|
).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}")
|
groups.map(group => s"group:${group}")
|
||||||
).mkString(" ")
|
).mkString(" ")
|
||||||
|
|
||||||
def toURL: String =
|
def toURL: String = {
|
||||||
"?" + List(
|
"?" + (Seq(
|
||||||
if (labels.isEmpty) None else Some("labels=" + urlEncode(labels.mkString(","))),
|
if (labels.isEmpty) None else Some("labels=" + urlEncode(labels.mkString(","))),
|
||||||
milestone.map {
|
milestone.map {
|
||||||
case Some(x) => "milestone=" + urlEncode(x)
|
case Some(x) => s"milestone=${urlEncode(x)}"
|
||||||
case None => "milestone=none"
|
case None => "milestone=none"
|
||||||
},
|
},
|
||||||
priority.map {
|
priority.map {
|
||||||
case Some(x) => "priority=" + urlEncode(x)
|
case Some(x) => s"priority=${urlEncode(x)}"
|
||||||
case None => "priority=none"
|
case None => "priority=none"
|
||||||
},
|
},
|
||||||
author.map(x => "author=" + urlEncode(x)),
|
author.map(x => s"author=${urlEncode(x)}"),
|
||||||
assigned.map {
|
assigned.map {
|
||||||
case Some(x) => "assigned=" + urlEncode(x)
|
case Some(x) => s"assigned=${urlEncode(x)}"
|
||||||
case None => "assigned=none"
|
case None => "assigned=none"
|
||||||
},
|
},
|
||||||
mentioned.map(x => "mentioned=" + urlEncode(x)),
|
mentioned.map(x => s"mentioned=${urlEncode(x)}"),
|
||||||
Some("state=" + urlEncode(state)),
|
Some(s"state=${urlEncode(state)}"),
|
||||||
Some("sort=" + urlEncode(sort)),
|
Some(s"sort=${urlEncode(sort)}"),
|
||||||
Some("direction=" + urlEncode(direction)),
|
Some(s"direction=${urlEncode(direction)}"),
|
||||||
visibility.map(x => "visibility=" + urlEncode(x)),
|
visibility.map(x => s"visibility=${urlEncode(x)}"),
|
||||||
if (groups.isEmpty) None else Some("groups=" + urlEncode(groups.mkString(",")))
|
if (groups.isEmpty) None else Some(s"groups=${urlEncode(groups.mkString(","))}")
|
||||||
).flatten.mkString("&")
|
).flatten ++ others.map { x =>
|
||||||
|
s"custom.${urlEncode(x.name)}=${urlEncode(x.operator)}:${urlEncode(x.value)}"
|
||||||
|
}).mkString("&")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
object IssueSearchCondition {
|
object IssueSearchCondition {
|
||||||
|
|
||||||
|
private val SupportedOperators = Seq("eq", "lt", "gt", "lte", "gte")
|
||||||
|
|
||||||
private def param(request: HttpServletRequest, name: String, allow: Seq[String] = Nil): Option[String] = {
|
private def param(request: HttpServletRequest, name: String, allow: Seq[String] = Nil): Option[String] = {
|
||||||
val value = request.getParameter(name)
|
val value = request.getParameter(name)
|
||||||
if (value == null || value.isEmpty || (allow.nonEmpty && !allow.contains(value))) None else Some(value)
|
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.
|
* 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(
|
IssueSearchCondition(
|
||||||
param(request, "labels").map(_.split(",").toSet).getOrElse(Set.empty),
|
param(request, "labels").map(_.split(",").toSet).getOrElse(Set.empty),
|
||||||
param(request, "milestone").map {
|
param(request, "milestone").map {
|
||||||
@@ -1055,31 +1189,16 @@ object IssuesService {
|
|||||||
param(request, "sort", Seq("created", "comments", "updated", "priority")).getOrElse("created"),
|
param(request, "sort", Seq("created", "comments", "updated", "priority")).getOrElse("created"),
|
||||||
param(request, "direction", Seq("asc", "desc")).getOrElse("desc"),
|
param(request, "direction", Seq("asc", "desc")).getOrElse("desc"),
|
||||||
param(request, "visibility"),
|
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 =
|
def apply(request: HttpServletRequest, milestone: String): IssueSearchCondition = {
|
||||||
IssueSearchCondition(
|
apply(request).copy(milestone = Some(Some(milestone)))
|
||||||
param(request, "labels").map(_.split(",").toSet).getOrElse(Set.empty),
|
}
|
||||||
Some(Some(milestone)),
|
|
||||||
param(request, "priority").map {
|
|
||||||
case "none" => None
|
|
||||||
case x => Some(x)
|
|
||||||
},
|
|
||||||
param(request, "author"),
|
|
||||||
param(request, "assigned").map {
|
|
||||||
case "none" => None
|
|
||||||
case x => Some(x)
|
|
||||||
},
|
|
||||||
param(request, "mentioned"),
|
|
||||||
param(request, "state", Seq("open", "closed", "all")).getOrElse("open"),
|
|
||||||
param(request, "sort", Seq("created", "comments", "updated", "priority")).getOrElse("created"),
|
|
||||||
param(request, "direction", Seq("asc", "desc")).getOrElse("desc"),
|
|
||||||
param(request, "visibility"),
|
|
||||||
param(request, "groups").map(_.split(",").toSet).getOrElse(Set.empty)
|
|
||||||
)
|
|
||||||
|
|
||||||
def page(request: HttpServletRequest) = {
|
def page(request: HttpServletRequest): Int = {
|
||||||
PaginationHelper.page(param(request, "page"))
|
PaginationHelper.page(param(request, "page"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
package gitbucket.core.service
|
package gitbucket.core.service
|
||||||
|
|
||||||
import java.net.URI
|
import java.net.URI
|
||||||
|
|
||||||
import com.nimbusds.jose.JWSAlgorithm.Family
|
import com.nimbusds.jose.JWSAlgorithm.Family
|
||||||
import com.nimbusds.jose.proc.BadJOSEException
|
import com.nimbusds.jose.proc.BadJOSEException
|
||||||
import com.nimbusds.jose.util.DefaultResourceRetriever
|
import com.nimbusds.jose.util.DefaultResourceRetriever
|
||||||
import com.nimbusds.jose.{JOSEException, JWSAlgorithm}
|
import com.nimbusds.jose.{JOSEException, JWSAlgorithm}
|
||||||
|
import com.nimbusds.jwt.JWT
|
||||||
import com.nimbusds.oauth2.sdk._
|
import com.nimbusds.oauth2.sdk._
|
||||||
import com.nimbusds.oauth2.sdk.auth.ClientSecretBasic
|
import com.nimbusds.oauth2.sdk.auth.ClientSecretBasic
|
||||||
import com.nimbusds.oauth2.sdk.id.{ClientID, Issuer, State}
|
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.
|
* Proceed the OpenID Connect authentication.
|
||||||
*
|
*
|
||||||
@@ -60,7 +65,7 @@ trait OpenIDConnectService {
|
|||||||
* @param state State saved in the session
|
* @param state State saved in the session
|
||||||
* @param nonce Nonce saved in the session
|
* @param nonce Nonce saved in the session
|
||||||
* @param oidc OIDC settings
|
* @param oidc OIDC settings
|
||||||
* @return ID token
|
* @return (ID token, GitBucket account)
|
||||||
*/
|
*/
|
||||||
def authenticate(
|
def authenticate(
|
||||||
params: Map[String, String],
|
params: Map[String, String],
|
||||||
@@ -68,9 +73,10 @@ trait OpenIDConnectService {
|
|||||||
state: State,
|
state: State,
|
||||||
nonce: Nonce,
|
nonce: Nonce,
|
||||||
oidc: SystemSettingsService.OIDC
|
oidc: SystemSettingsService.OIDC
|
||||||
)(implicit s: Session): Option[Account] =
|
)(implicit s: Session): Option[(JWT, Account)] =
|
||||||
validateOIDCAuthenticationResponse(params, state, redirectURI) flatMap { authenticationResponse =>
|
validateOIDCAuthenticationResponse(params, state, redirectURI) flatMap { authenticationResponse =>
|
||||||
obtainOIDCToken(authenticationResponse.getAuthorizationCode, nonce, redirectURI, oidc) flatMap { claims =>
|
obtainOIDCToken(authenticationResponse.getAuthorizationCode, nonce, redirectURI, oidc) flatMap {
|
||||||
|
case (jwt, claims) =>
|
||||||
Seq("email", "preferred_username", "name").map(k => Option(claims.getStringClaim(k))) match {
|
Seq("email", "preferred_username", "name").map(k => Option(claims.getStringClaim(k))) match {
|
||||||
case Seq(Some(email), preferredUsername, name) =>
|
case Seq(Some(email), preferredUsername, name) =>
|
||||||
getOrCreateFederatedUser(
|
getOrCreateFederatedUser(
|
||||||
@@ -79,7 +85,9 @@ trait OpenIDConnectService {
|
|||||||
email,
|
email,
|
||||||
preferredUsername,
|
preferredUsername,
|
||||||
name
|
name
|
||||||
)
|
).map { account =>
|
||||||
|
(jwt, account)
|
||||||
|
}
|
||||||
case _ =>
|
case _ =>
|
||||||
logger.info(s"OIDC ID token must have an email claim: claims=${claims.toJSONObject}")
|
logger.info(s"OIDC ID token must have an email claim: claims=${claims.toJSONObject}")
|
||||||
None
|
None
|
||||||
@@ -136,7 +144,7 @@ trait OpenIDConnectService {
|
|||||||
nonce: Nonce,
|
nonce: Nonce,
|
||||||
redirectURI: URI,
|
redirectURI: URI,
|
||||||
oidc: SystemSettingsService.OIDC
|
oidc: SystemSettingsService.OIDC
|
||||||
): Option[IDTokenClaimsSet] = {
|
): Option[(JWT, IDTokenClaimsSet)] = {
|
||||||
val metadata = OIDCProviderMetadata.resolve(oidc.issuer)
|
val metadata = OIDCProviderMetadata.resolve(oidc.issuer)
|
||||||
val tokenRequest = new TokenRequest(
|
val tokenRequest = new TokenRequest(
|
||||||
metadata.getTokenEndpointURI,
|
metadata.getTokenEndpointURI,
|
||||||
@@ -173,7 +181,7 @@ trait OpenIDConnectService {
|
|||||||
metadata: OIDCProviderMetadata,
|
metadata: OIDCProviderMetadata,
|
||||||
nonce: Nonce,
|
nonce: Nonce,
|
||||||
oidc: SystemSettingsService.OIDC
|
oidc: SystemSettingsService.OIDC
|
||||||
): Option[IDTokenClaimsSet] =
|
): Option[(JWT, IDTokenClaimsSet)] =
|
||||||
Option(response.getOIDCTokens.getIDToken) match {
|
Option(response.getOIDCTokens.getIDToken) match {
|
||||||
case Some(jwt) =>
|
case Some(jwt) =>
|
||||||
val validator = oidc.jwsAlgorithm map { jwsAlgorithm =>
|
val validator = oidc.jwsAlgorithm map { jwsAlgorithm =>
|
||||||
@@ -188,7 +196,7 @@ trait OpenIDConnectService {
|
|||||||
new IDTokenValidator(metadata.getIssuer, oidc.clientID)
|
new IDTokenValidator(metadata.getIssuer, oidc.clientID)
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
Some(validator.validate(jwt, nonce))
|
Some((jwt, validator.validate(jwt, nonce)))
|
||||||
} catch {
|
} catch {
|
||||||
case e @ (_: BadJOSEException | _: JOSEException) =>
|
case e @ (_: BadJOSEException | _: JOSEException) =>
|
||||||
logger.info(s"OIDC ID token has error: $e")
|
logger.info(s"OIDC ID token has error: $e")
|
||||||
|
|||||||
@@ -64,9 +64,19 @@ trait RepositoryCreationService {
|
|||||||
name: String,
|
name: String,
|
||||||
description: Option[String],
|
description: Option[String],
|
||||||
isPrivate: Boolean,
|
isPrivate: Boolean,
|
||||||
createReadme: Boolean
|
createReadme: Boolean,
|
||||||
|
defaultBranch: String
|
||||||
): Future[Unit] = {
|
): 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(
|
def createRepository(
|
||||||
@@ -76,7 +86,8 @@ trait RepositoryCreationService {
|
|||||||
description: Option[String],
|
description: Option[String],
|
||||||
isPrivate: Boolean,
|
isPrivate: Boolean,
|
||||||
initOption: String,
|
initOption: String,
|
||||||
sourceUrl: Option[String]
|
sourceUrl: Option[String],
|
||||||
|
defaultBranch: String
|
||||||
): Future[Unit] = Future {
|
): Future[Unit] = Future {
|
||||||
RepositoryCreationService.startCreation(owner, name)
|
RepositoryCreationService.startCreation(owner, name)
|
||||||
try {
|
try {
|
||||||
@@ -93,7 +104,7 @@ trait RepositoryCreationService {
|
|||||||
} else None
|
} else None
|
||||||
|
|
||||||
// Insert to the database at first
|
// Insert to the database at first
|
||||||
insertRepository(name, owner, description, isPrivate)
|
insertRepository(name, owner, description, isPrivate, defaultBranch)
|
||||||
|
|
||||||
// // Add collaborators for group repository
|
// // Add collaborators for group repository
|
||||||
// if(ownerAccount.isGroupAccount){
|
// if(ownerAccount.isGroupAccount){
|
||||||
@@ -110,7 +121,7 @@ trait RepositoryCreationService {
|
|||||||
|
|
||||||
// Create the actual repository
|
// Create the actual repository
|
||||||
val gitdir = getRepositoryDir(owner, name)
|
val gitdir = getRepositoryDir(owner, name)
|
||||||
JGitUtil.initRepository(gitdir)
|
JGitUtil.initRepository(gitdir, defaultBranch)
|
||||||
|
|
||||||
if (initOption == "README" || initOption == "EMPTY_COMMIT") {
|
if (initOption == "README" || initOption == "EMPTY_COMMIT") {
|
||||||
Using.resource(Git.open(gitdir)) { git =>
|
Using.resource(Git.open(gitdir)) { git =>
|
||||||
@@ -163,7 +174,7 @@ trait RepositoryCreationService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create Wiki repository
|
// Create Wiki repository
|
||||||
createWikiRepository(loginAccount, owner, name)
|
createWikiRepository(loginAccount, owner, name, defaultBranch)
|
||||||
|
|
||||||
// Record activity
|
// Record activity
|
||||||
recordActivity(CreateRepositoryInfo(owner, name, loginUserName))
|
recordActivity(CreateRepositoryInfo(owner, name, loginUserName))
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ trait RepositoryService {
|
|||||||
userName: String,
|
userName: String,
|
||||||
description: Option[String],
|
description: Option[String],
|
||||||
isPrivate: Boolean,
|
isPrivate: Boolean,
|
||||||
defaultBranch: String = "master",
|
defaultBranch: String,
|
||||||
originRepositoryName: Option[String] = None,
|
originRepositoryName: Option[String] = None,
|
||||||
originUserName: Option[String] = None,
|
originUserName: Option[String] = None,
|
||||||
parentRepositoryName: Option[String] = None,
|
parentRepositoryName: Option[String] = None,
|
||||||
@@ -254,6 +254,7 @@ trait RepositoryService {
|
|||||||
Labels.filter(_.byRepository(userName, repositoryName)).delete
|
Labels.filter(_.byRepository(userName, repositoryName)).delete
|
||||||
IssueComments.filter(_.byRepository(userName, repositoryName)).delete
|
IssueComments.filter(_.byRepository(userName, repositoryName)).delete
|
||||||
PullRequests.filter(_.byRepository(userName, repositoryName)).delete
|
PullRequests.filter(_.byRepository(userName, repositoryName)).delete
|
||||||
|
IssueAssignees.filter(_.byRepository(userName, repositoryName)).delete
|
||||||
Issues.filter(_.byRepository(userName, repositoryName)).delete
|
Issues.filter(_.byRepository(userName, repositoryName)).delete
|
||||||
Priorities.filter(_.byRepository(userName, repositoryName)).delete
|
Priorities.filter(_.byRepository(userName, repositoryName)).delete
|
||||||
IssueId.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(UploadLargeMaxFileSize, settings.upload.largeMaxFileSize.toString)
|
||||||
props.setProperty(UploadLargeTimeout, settings.upload.largeTimeout.toString)
|
props.setProperty(UploadLargeTimeout, settings.upload.largeTimeout.toString)
|
||||||
props.setProperty(RepositoryViewerMaxFiles, settings.repositoryViewer.maxFiles.toString)
|
props.setProperty(RepositoryViewerMaxFiles, settings.repositoryViewer.maxFiles.toString)
|
||||||
|
props.setProperty(DefaultBranch, settings.defaultBranch)
|
||||||
|
|
||||||
Using.resource(new java.io.FileOutputStream(GitBucketConf)) { out =>
|
Using.resource(new java.io.FileOutputStream(GitBucketConf)) { out =>
|
||||||
props.store(out, null)
|
props.store(out, null)
|
||||||
@@ -205,7 +206,8 @@ trait SystemSettingsService {
|
|||||||
),
|
),
|
||||||
RepositoryViewerSettings(
|
RepositoryViewerSettings(
|
||||||
getValue(props, RepositoryViewerMaxFiles, 0)
|
getValue(props, RepositoryViewerMaxFiles, 0)
|
||||||
)
|
),
|
||||||
|
getValue(props, DefaultBranch, "main")
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -231,7 +233,8 @@ object SystemSettingsService {
|
|||||||
showMailAddress: Boolean,
|
showMailAddress: Boolean,
|
||||||
webHook: WebHook,
|
webHook: WebHook,
|
||||||
upload: Upload,
|
upload: Upload,
|
||||||
repositoryViewer: RepositoryViewerSettings
|
repositoryViewer: RepositoryViewerSettings,
|
||||||
|
defaultBranch: String
|
||||||
) {
|
) {
|
||||||
def baseUrl(request: HttpServletRequest): String =
|
def baseUrl(request: HttpServletRequest): String =
|
||||||
baseUrl.getOrElse(parseBaseUrl(request)).stripSuffix("/")
|
baseUrl.getOrElse(parseBaseUrl(request)).stripSuffix("/")
|
||||||
@@ -402,7 +405,6 @@ object SystemSettingsService {
|
|||||||
private val RepositoryOperationFork = "repository_operation_fork"
|
private val RepositoryOperationFork = "repository_operation_fork"
|
||||||
private val Gravatar = "gravatar"
|
private val Gravatar = "gravatar"
|
||||||
private val Notification = "notification"
|
private val Notification = "notification"
|
||||||
private val ActivityLogLimit = "activity_log_limit"
|
|
||||||
private val LimitVisibleRepositories = "limitVisibleRepositories"
|
private val LimitVisibleRepositories = "limitVisibleRepositories"
|
||||||
private val SshEnabled = "ssh"
|
private val SshEnabled = "ssh"
|
||||||
private val SshHost = "ssh.host"
|
private val SshHost = "ssh.host"
|
||||||
@@ -448,6 +450,7 @@ object SystemSettingsService {
|
|||||||
private val UploadLargeMaxFileSize = "upload.largeMaxFileSize"
|
private val UploadLargeMaxFileSize = "upload.largeMaxFileSize"
|
||||||
private val UploadLargeTimeout = "upload.largeTimeout"
|
private val UploadLargeTimeout = "upload.largeTimeout"
|
||||||
private val RepositoryViewerMaxFiles = "repository_viewer_max_files"
|
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 = {
|
private def getValue[A: ClassTag](props: java.util.Properties, key: String, default: A): A = {
|
||||||
getConfigValue(key).getOrElse {
|
getConfigValue(key).getOrElse {
|
||||||
|
|||||||
@@ -51,11 +51,11 @@ object WikiService {
|
|||||||
trait WikiService {
|
trait WikiService {
|
||||||
import 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") {
|
LockUtil.lock(s"${owner}/${repository}/wiki") {
|
||||||
val dir = Directory.getWikiRepositoryDir(owner, repository)
|
val dir = Directory.getWikiRepositoryDir(owner, repository)
|
||||||
if (!dir.exists) {
|
if (!dir.exists) {
|
||||||
JGitUtil.initRepository(dir)
|
JGitUtil.initRepository(dir, defaultBranch)
|
||||||
saveWikiPage(
|
saveWikiPage(
|
||||||
owner,
|
owner,
|
||||||
repository,
|
repository,
|
||||||
@@ -72,11 +72,11 @@ trait WikiService {
|
|||||||
/**
|
/**
|
||||||
* Returns the wiki page.
|
* 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 =>
|
Using.resource(Git.open(Directory.getWikiRepositoryDir(owner, repository))) { git =>
|
||||||
if (!JGitUtil.isEmpty(git)) {
|
if (!JGitUtil.isEmpty(git)) {
|
||||||
val fileName = pageName + ".md"
|
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)
|
val content = JGitUtil.getContentFromPath(git, latestCommit.getTree, fileName, true)
|
||||||
WikiPageInfo(
|
WikiPageInfo(
|
||||||
fileName,
|
fileName,
|
||||||
@@ -93,10 +93,10 @@ trait WikiService {
|
|||||||
/**
|
/**
|
||||||
* Returns the list of wiki page names.
|
* 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 =>
|
Using.resource(Git.open(Directory.getWikiRepositoryDir(owner, repository))) { git =>
|
||||||
JGitUtil
|
JGitUtil
|
||||||
.getFileList(git, "master", ".")
|
.getFileList(git, branch, ".")
|
||||||
.filter(_.name.endsWith(".md"))
|
.filter(_.name.endsWith(".md"))
|
||||||
.filterNot(_.name.startsWith("_"))
|
.filterNot(_.name.startsWith("_"))
|
||||||
.map(_.name.stripSuffix(".md"))
|
.map(_.name.stripSuffix(".md"))
|
||||||
@@ -113,7 +113,8 @@ trait WikiService {
|
|||||||
from: String,
|
from: String,
|
||||||
to: String,
|
to: String,
|
||||||
committer: Account,
|
committer: Account,
|
||||||
pageName: Option[String]
|
pageName: Option[String],
|
||||||
|
branch: String
|
||||||
): Boolean = {
|
): Boolean = {
|
||||||
|
|
||||||
case class RevertInfo(operation: String, filePath: String, source: String)
|
case class RevertInfo(operation: String, filePath: String, source: String)
|
||||||
@@ -151,7 +152,7 @@ trait WikiService {
|
|||||||
fh.getChangeType match {
|
fh.getChangeType match {
|
||||||
case DiffEntry.ChangeType.MODIFY => {
|
case DiffEntry.ChangeType.MODIFY => {
|
||||||
val source =
|
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)
|
val applied = PatchUtil.apply(source, patch, fh)
|
||||||
if (applied != null) {
|
if (applied != null) {
|
||||||
Seq(RevertInfo("ADD", fh.getNewPath, applied))
|
Seq(RevertInfo("ADD", fh.getNewPath, applied))
|
||||||
|
|||||||
@@ -245,7 +245,7 @@ class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl:
|
|||||||
with RequestCache {
|
with RequestCache {
|
||||||
|
|
||||||
private val logger = LoggerFactory.getLogger(classOf[CommitLogHook])
|
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 = {
|
def onPreReceive(receivePack: ReceivePack, commands: java.util.Collection[ReceiveCommand]): Unit = {
|
||||||
Database() withTransaction { implicit session =>
|
Database() withTransaction { implicit session =>
|
||||||
@@ -260,15 +260,45 @@ class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Using.resource(Git.open(Directory.getRepositoryDir(owner, repository))) { git =>
|
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 {
|
} catch {
|
||||||
case ex: Exception => {
|
case ex: Exception =>
|
||||||
logger.error(ex.toString, ex)
|
logger.error(ex.toString, ex)
|
||||||
throw 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def onPostReceive(receivePack: ReceivePack, commands: java.util.Collection[ReceiveCommand]): Unit = {
|
def onPostReceive(receivePack: ReceivePack, commands: java.util.Collection[ReceiveCommand]): Unit = {
|
||||||
@@ -279,20 +309,12 @@ class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl:
|
|||||||
Using.resource(Git.open(Directory.getRepositoryDir(owner, repository))) { git =>
|
Using.resource(Git.open(Directory.getRepositoryDir(owner, repository))) { git =>
|
||||||
JGitUtil.removeCache(git)
|
JGitUtil.removeCache(git)
|
||||||
|
|
||||||
val pushedIds = scala.collection.mutable.Set[String]()
|
|
||||||
commands.asScala.foreach { command =>
|
commands.asScala.foreach { command =>
|
||||||
logger.debug(s"commandType: ${command.getType}, refName: ${command.getRefName}")
|
logger.debug(s"commandType: ${command.getType}, refName: ${command.getRefName}")
|
||||||
implicit val apiContext: Context = api.JsonFormat.Context(baseUrl, sshUrl)
|
implicit val apiContext: Context = api.JsonFormat.Context(baseUrl, sshUrl)
|
||||||
val refName = command.getRefName.split("/")
|
val refName = command.getRefName.split("/")
|
||||||
val branchName = refName.drop(2).mkString("/")
|
val branchName = refName.drop(2).mkString("/")
|
||||||
val commits = if (refName(1) == "tags") {
|
val commits = getCommits(git, command)
|
||||||
Nil
|
|
||||||
} else {
|
|
||||||
command.getType match {
|
|
||||||
case ReceiveCommand.Type.DELETE => Nil
|
|
||||||
case _ => JGitUtil.getCommitLog(git, command.getOldId.name, command.getNewId.name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val repositoryInfo = getRepository(owner, repository).get
|
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
|
// Extract new commit and apply issue comment
|
||||||
val defaultBranch = repositoryInfo.repository.defaultBranch
|
val defaultBranch = repositoryInfo.repository.defaultBranch
|
||||||
val newCommits = commits.flatMap { commit =>
|
val pushedIds = scala.collection.mutable.Set[String]()
|
||||||
if (!existIds.contains(commit.id) && !pushedIds.contains(commit.id)) {
|
val newCommits = commits.collect {
|
||||||
|
case commit if newCommitIds.contains(commit.id) && !pushedIds.contains(commit.id) =>
|
||||||
if (issueCount > 0) {
|
if (issueCount > 0) {
|
||||||
pushedIds.add(commit.id)
|
pushedIds.add(commit.id)
|
||||||
createIssueComment(owner, repository, commit)
|
createIssueComment(owner, repository, commit)
|
||||||
@@ -333,9 +356,8 @@ class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some(commit)
|
commit
|
||||||
} else None
|
}.toList
|
||||||
}
|
|
||||||
|
|
||||||
// set PR as merged
|
// set PR as merged
|
||||||
val pulls = getPullRequestsByBranch(owner, repository, branchName, Some(false))
|
val pulls = getPullRequestsByBranch(owner, repository, branchName, Some(false))
|
||||||
@@ -431,13 +453,12 @@ class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl:
|
|||||||
// update repository last modified time.
|
// update repository last modified time.
|
||||||
updateLastActivityDate(owner, repository)
|
updateLastActivityDate(owner, repository)
|
||||||
} catch {
|
} catch {
|
||||||
case ex: Exception => {
|
case ex: Exception =>
|
||||||
logger.error(ex.toString, ex)
|
logger.error(ex.toString, ex)
|
||||||
throw ex
|
throw ex
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -506,13 +527,12 @@ class WikiCommitHook(owner: String, repository: String, pusher: String, baseUrl:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
case ex: Exception => {
|
case ex: Exception =>
|
||||||
logger.error(ex.toString, ex)
|
logger.error(ex.toString, ex)
|
||||||
throw ex
|
throw ex
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private[this] def mapToAction(changeType: ChangeType): String = changeType match {
|
private[this] def mapToAction(changeType: ChangeType): String = changeType match {
|
||||||
case ChangeType.ADD | ChangeType.RENAME => "created"
|
case ChangeType.ADD | ChangeType.RENAME => "created"
|
||||||
|
|||||||
@@ -99,26 +99,26 @@ object DatabaseType {
|
|||||||
|
|
||||||
object H2 extends DatabaseType {
|
object H2 extends DatabaseType {
|
||||||
val jdbcDriver = "org.h2.Driver"
|
val jdbcDriver = "org.h2.Driver"
|
||||||
val slickDriver = BlockingH2Driver
|
val slickDriver: BlockingJdbcProfile = BlockingH2Driver
|
||||||
val liquiDriver = new H2Database()
|
val liquiDriver: AbstractJdbcDatabase = new H2Database()
|
||||||
}
|
}
|
||||||
|
|
||||||
object MySQL extends DatabaseType {
|
object MySQL extends DatabaseType {
|
||||||
val jdbcDriver = "org.mariadb.jdbc.Driver"
|
val jdbcDriver = "org.mariadb.jdbc.Driver"
|
||||||
val slickDriver = BlockingMySQLDriver
|
val slickDriver: BlockingJdbcProfile = BlockingMySQLDriver
|
||||||
val liquiDriver = new MySQLDatabase()
|
val liquiDriver: AbstractJdbcDatabase = new MySQLDatabase()
|
||||||
}
|
}
|
||||||
|
|
||||||
object MariaDb extends DatabaseType {
|
object MariaDb extends DatabaseType {
|
||||||
val jdbcDriver = "org.mariadb.jdbc.Driver"
|
val jdbcDriver = "org.mariadb.jdbc.Driver"
|
||||||
val slickDriver = BlockingMySQLDriver
|
val slickDriver: BlockingJdbcProfile = BlockingMySQLDriver
|
||||||
val liquiDriver = new MariaDBDatabase()
|
val liquiDriver: AbstractJdbcDatabase = new MariaDBDatabase()
|
||||||
}
|
}
|
||||||
|
|
||||||
object PostgreSQL extends DatabaseType {
|
object PostgreSQL extends DatabaseType {
|
||||||
val jdbcDriver = "org.postgresql.Driver2"
|
val jdbcDriver = "org.postgresql.Driver2"
|
||||||
val slickDriver = BlockingPostgresDriver
|
val slickDriver: BlockingJdbcProfile = BlockingPostgresDriver
|
||||||
val liquiDriver = new PostgresDatabase()
|
val liquiDriver: AbstractJdbcDatabase = new PostgresDatabase()
|
||||||
}
|
}
|
||||||
|
|
||||||
object BlockingPostgresDriver extends slick.jdbc.PostgresProfile with BlockingJdbcProfile {
|
object BlockingPostgresDriver extends slick.jdbc.PostgresProfile with BlockingJdbcProfile {
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
package gitbucket.core.util
|
package gitbucket.core.util
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream
|
import java.io.ByteArrayInputStream
|
||||||
|
|
||||||
import scala.jdk.CollectionConverters._
|
import scala.jdk.CollectionConverters._
|
||||||
|
|
||||||
import gitbucket.core.model.Profile._
|
import gitbucket.core.model.Profile._
|
||||||
import gitbucket.core.model.Profile.profile.blockingApi._
|
import gitbucket.core.model.Profile.profile.blockingApi._
|
||||||
import org.bouncycastle.bcpg.ArmoredInputStream
|
import org.bouncycastle.bcpg.ArmoredInputStream
|
||||||
@@ -34,6 +33,7 @@ object GpgUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
def verifySign(signInfo: JGitUtil.GpgSignInfo)(implicit s: Session): Option[JGitUtil.GpgVerifyInfo] = {
|
def verifySign(signInfo: JGitUtil.GpgSignInfo)(implicit s: Session): Option[JGitUtil.GpgVerifyInfo] = {
|
||||||
|
try {
|
||||||
new BcPGPObjectFactory(new ArmoredInputStream(new ByteArrayInputStream(signInfo.signArmored)))
|
new BcPGPObjectFactory(new ArmoredInputStream(new ByteArrayInputStream(signInfo.signArmored)))
|
||||||
.iterator()
|
.iterator()
|
||||||
.asScala
|
.asScala
|
||||||
@@ -54,9 +54,12 @@ object GpgUtil {
|
|||||||
JGitUtil.GpgVerifyInfo(pubKey.getUserIDs.next, pubKey.getKeyID.toHexString.toUpperCase)
|
JGitUtil.GpgVerifyInfo(pubKey.getUserIDs.next, pubKey.getKeyID.toHexString.toUpperCase)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
.toList
|
.toList
|
||||||
.headOption
|
.headOption
|
||||||
|
} catch {
|
||||||
|
case _: Throwable => None
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package gitbucket.core.util
|
package gitbucket.core.util
|
||||||
|
|
||||||
import java.io._
|
import java.io._
|
||||||
|
|
||||||
import gitbucket.core.service.RepositoryService
|
import gitbucket.core.service.RepositoryService
|
||||||
import org.eclipse.jgit.api.Git
|
import org.eclipse.jgit.api.Git
|
||||||
import Directory._
|
import Directory._
|
||||||
@@ -18,10 +17,10 @@ import org.eclipse.jgit.treewalk.filter._
|
|||||||
import org.eclipse.jgit.diff.DiffEntry.ChangeType
|
import org.eclipse.jgit.diff.DiffEntry.ChangeType
|
||||||
import org.eclipse.jgit.errors.{ConfigInvalidException, IncorrectObjectTypeException, MissingObjectException}
|
import org.eclipse.jgit.errors.{ConfigInvalidException, IncorrectObjectTypeException, MissingObjectException}
|
||||||
import org.eclipse.jgit.transport.RefSpec
|
import org.eclipse.jgit.transport.RefSpec
|
||||||
|
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
import org.cache2k.{Cache, Cache2kBuilder}
|
||||||
import org.cache2k.Cache2kBuilder
|
|
||||||
import org.eclipse.jgit.api.errors._
|
import org.eclipse.jgit.api.errors._
|
||||||
import org.eclipse.jgit.diff.{DiffEntry, DiffFormatter, RawTextComparator}
|
import org.eclipse.jgit.diff.{DiffEntry, DiffFormatter, RawTextComparator}
|
||||||
import org.eclipse.jgit.dircache.DirCacheEntry
|
import org.eclipse.jgit.dircache.DirCacheEntry
|
||||||
@@ -40,6 +39,9 @@ object JGitUtil {
|
|||||||
private implicit val objectDatabaseReleasable: Releasable[ObjectDatabase] =
|
private implicit val objectDatabaseReleasable: Releasable[ObjectDatabase] =
|
||||||
_.close()
|
_.close()
|
||||||
|
|
||||||
|
private def isCacheEnabled(): Boolean =
|
||||||
|
!ConfigUtil.getConfigValue[Boolean]("gitbucket.disableCache").getOrElse(false)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The repository data.
|
* The repository data.
|
||||||
*
|
*
|
||||||
@@ -284,18 +286,25 @@ object JGitUtil {
|
|||||||
revCommit
|
revCommit
|
||||||
}
|
}
|
||||||
|
|
||||||
private val cache = new Cache2kBuilder[String, Int]() {}
|
private val cache: Cache[String, Int] = if (isCacheEnabled()) {
|
||||||
|
Cache2kBuilder
|
||||||
|
.of(classOf[String], classOf[Int])
|
||||||
.name("commit-count")
|
.name("commit-count")
|
||||||
.expireAfterWrite(24, TimeUnit.HOURS)
|
.expireAfterWrite(24, TimeUnit.HOURS)
|
||||||
.entryCapacity(10000)
|
.entryCapacity(10000)
|
||||||
.build()
|
.build()
|
||||||
|
} else null
|
||||||
|
|
||||||
private val objectCommitCache = new Cache2kBuilder[ObjectId, RevCommit]() {}
|
private val objectCommitCache: Cache[ObjectId, RevCommit] = if (isCacheEnabled()) {
|
||||||
|
Cache2kBuilder
|
||||||
|
.of(classOf[ObjectId], classOf[RevCommit])
|
||||||
.name("object-commit")
|
.name("object-commit")
|
||||||
.entryCapacity(10000)
|
.entryCapacity(10000)
|
||||||
.build()
|
.build()
|
||||||
|
} else null
|
||||||
|
|
||||||
def removeCache(git: Git): Unit = {
|
def removeCache(git: Git): Unit = {
|
||||||
|
if (isCacheEnabled()) {
|
||||||
val dir = git.getRepository.getDirectory
|
val dir = git.getRepository.getDirectory
|
||||||
val keyPrefix = dir.getAbsolutePath + "@"
|
val keyPrefix = dir.getAbsolutePath + "@"
|
||||||
|
|
||||||
@@ -305,6 +314,7 @@ object JGitUtil {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the number of commits in the specified branch or commit.
|
* Returns the number of commits in the specified branch or commit.
|
||||||
@@ -312,6 +322,8 @@ object JGitUtil {
|
|||||||
*/
|
*/
|
||||||
def getCommitCount(git: Git, branch: String, max: Int = 10001): Int = {
|
def getCommitCount(git: Git, branch: String, max: Int = 10001): Int = {
|
||||||
val dir = git.getRepository.getDirectory
|
val dir = git.getRepository.getDirectory
|
||||||
|
|
||||||
|
if (isCacheEnabled()) {
|
||||||
val key = dir.getAbsolutePath + "@" + branch
|
val key = dir.getAbsolutePath + "@" + branch
|
||||||
val entry = cache.getEntry(key)
|
val entry = cache.getEntry(key)
|
||||||
|
|
||||||
@@ -323,6 +335,11 @@ object JGitUtil {
|
|||||||
} else {
|
} else {
|
||||||
entry.getValue
|
entry.getValue
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
val commitId = git.getRepository.resolve(branch)
|
||||||
|
val commitCount = git.log.add(commitId).call.iterator.asScala.take(max).size
|
||||||
|
commitCount
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -444,7 +461,7 @@ object JGitUtil {
|
|||||||
(id, mode, name, path, opt, None)
|
(id, mode, name, path, opt, None)
|
||||||
} else if (commitCount < 10000) {
|
} else if (commitCount < 10000) {
|
||||||
(id, mode, name, path, opt, Some(getCommit(path)))
|
(id, mode, name, path, opt, Some(getCommit(path)))
|
||||||
} else {
|
} else if (isCacheEnabled()) {
|
||||||
// Use in-memory cache if the commit count is too big.
|
// Use in-memory cache if the commit count is too big.
|
||||||
val cached = objectCommitCache.getEntry(id)
|
val cached = objectCommitCache.getEntry(id)
|
||||||
if (cached == null) {
|
if (cached == null) {
|
||||||
@@ -454,6 +471,9 @@ object JGitUtil {
|
|||||||
} else {
|
} else {
|
||||||
(id, mode, name, path, opt, Some(cached.getValue))
|
(id, mode, name, path, opt, Some(cached.getValue))
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
val commit = getCommit(path)
|
||||||
|
(id, mode, name, path, opt, Some(commit))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -872,8 +892,9 @@ object JGitUtil {
|
|||||||
.reverse
|
.reverse
|
||||||
}
|
}
|
||||||
|
|
||||||
def initRepository(dir: java.io.File): Unit =
|
def initRepository(dir: java.io.File, defaultBranch: String): Unit =
|
||||||
Using.resource(new RepositoryBuilder().setGitDir(dir).setBare.build) { repository =>
|
Using.resource(new RepositoryBuilder().setGitDir(dir).setBare().setInitialBranch(defaultBranch).build) {
|
||||||
|
repository =>
|
||||||
repository.create(true)
|
repository.create(true)
|
||||||
setReceivePack(repository)
|
setReceivePack(repository)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,12 @@ object Keys {
|
|||||||
/**
|
/**
|
||||||
* Session key for the OpenID Connect authentication.
|
* 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.
|
* 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>
|
<textarea id="information" name="information" class="form-control" style="height: 100px;">@context.settings.information</textarea>
|
||||||
</fieldset>
|
</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 -->
|
<!-- AdminLTE SkinName -->
|
||||||
<!--====================================================================-->
|
<!--====================================================================-->
|
||||||
<hr>
|
<hr>
|
||||||
|
|||||||
@@ -5,10 +5,11 @@
|
|||||||
condition: gitbucket.core.service.IssuesService.IssueSearchCondition,
|
condition: gitbucket.core.service.IssuesService.IssueSearchCondition,
|
||||||
filter: String,
|
filter: String,
|
||||||
groups: List[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.html.main("Issues"){
|
||||||
@gitbucket.core.dashboard.html.sidebar(recentRepositories){
|
@gitbucket.core.dashboard.html.sidebar(recentRepositories){
|
||||||
@gitbucket.core.dashboard.html.tab("issues")
|
@gitbucket.core.dashboard.html.tab(enableNewsFeed, "issues")
|
||||||
<div class="container">
|
<div class="container">
|
||||||
@gitbucket.core.dashboard.html.issuesnavi("issues", filter, openCount, closedCount, condition)
|
@gitbucket.core.dashboard.html.issuesnavi("issues", filter, openCount, closedCount, condition)
|
||||||
@gitbucket.core.dashboard.html.issueslist(issues, page, openCount, closedCount, condition, filter, groups)
|
@gitbucket.core.dashboard.html.issueslist(issues, page, openCount, closedCount, condition, filter, groups)
|
||||||
|
|||||||
@@ -5,10 +5,11 @@
|
|||||||
condition: gitbucket.core.service.IssuesService.IssueSearchCondition,
|
condition: gitbucket.core.service.IssuesService.IssueSearchCondition,
|
||||||
filter: String,
|
filter: String,
|
||||||
groups: List[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.html.main("Pull requests"){
|
||||||
@gitbucket.core.dashboard.html.sidebar(recentRepositories){
|
@gitbucket.core.dashboard.html.sidebar(recentRepositories){
|
||||||
@gitbucket.core.dashboard.html.tab("pulls")
|
@gitbucket.core.dashboard.html.tab(enableNewsFeed, "pulls")
|
||||||
<div class="container">
|
<div class="container">
|
||||||
@gitbucket.core.dashboard.html.issuesnavi("pulls", filter, openCount, closedCount, condition)
|
@gitbucket.core.dashboard.html.issuesnavi("pulls", filter, openCount, closedCount, condition)
|
||||||
@gitbucket.core.dashboard.html.issueslist(issues, page, openCount, closedCount, condition, filter, groups)
|
@gitbucket.core.dashboard.html.issueslist(issues, page, openCount, closedCount, condition, filter, groups)
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
@(groups: List[String],
|
@(groups: List[String],
|
||||||
visibleRepositories: List[gitbucket.core.service.RepositoryService.RepositoryInfo],
|
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
|
@import gitbucket.core.view.helpers
|
||||||
@gitbucket.core.html.main("Repositories"){
|
@gitbucket.core.html.main("Repositories"){
|
||||||
@gitbucket.core.dashboard.html.sidebar(recentRepositories){
|
@gitbucket.core.dashboard.html.sidebar(recentRepositories){
|
||||||
@gitbucket.core.dashboard.html.tab("repos")
|
@gitbucket.core.dashboard.html.tab(enableNewsFeed, "repos")
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="btn-group" id="owner-dropdown">
|
<div class="btn-group" id="owner-dropdown">
|
||||||
<button class="dropdown-toggle btn btn-default" data-toggle="dropdown" aria-expanded="false">
|
<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)
|
@(enableNewsFeed: Boolean, active: String = "")(implicit context: gitbucket.core.controller.Context)
|
||||||
<ul class="nav nav-tabs" style="margin-bottom: 20px;">
|
@if(enableNewsFeed || context.loginAccount.isDefined) {
|
||||||
<li @if(active == ""){ class="active"}><a href="@context.path/">News feed</a></li>
|
<ul class="nav nav-tabs" style="margin-bottom: 20px;">
|
||||||
@if(context.loginAccount.isDefined){
|
@if(enableNewsFeed) {
|
||||||
<li @if(active == "repos" ){ class="active"}><a href="@context.path/dashboard/repos">Repositories</a></li>
|
<li @if(active == "") {class="active"}><a href="@context.path/">News feed</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>
|
@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 =>
|
@gitbucket.core.plugin.PluginRegistry().getDashboardTabs.map { tab =>
|
||||||
@tab(context).map { link =>
|
@tab(context).map { link =>
|
||||||
<li @if(active == link.id){ class="active"}><a href="@context.path/@link.path">@link.label</a></li>
|
<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],
|
@(activities: List[gitbucket.core.model.Activity],
|
||||||
recentRepositories: List[gitbucket.core.service.RepositoryService.RepositoryInfo],
|
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
|
@import gitbucket.core.view.helpers
|
||||||
@gitbucket.core.html.main("GitBucket"){
|
@gitbucket.core.html.main("GitBucket"){
|
||||||
@gitbucket.core.dashboard.html.sidebar(recentRepositories){
|
@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.
|
</a> and use it in place of a password on the <code>git</code> command line.
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@gitbucket.core.dashboard.html.tab()
|
@gitbucket.core.dashboard.html.tab(enableNewsFeed)
|
||||||
<div class="container">
|
<div class="container">
|
||||||
|
@if(enableNewsFeed) {
|
||||||
<div class="pull-right">
|
<div class="pull-right">
|
||||||
<a href="@context.path/activities.atom"><img src="@helpers.assets("/common/images/feed.png")" alt="activities"></a>
|
<a href="@context.path/activities.atom"><img src="@helpers.assets("/common/images/feed.png")" alt="activities"></a>
|
||||||
</div>
|
</div>
|
||||||
@gitbucket.core.helper.html.activities(activities)
|
@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>
|
</div>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -140,8 +140,8 @@
|
|||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
<span id="label-assigned">
|
<span id="label-assigned">
|
||||||
@issueAssignees.map { asignee =>
|
@issueAssignees.map { assignee =>
|
||||||
<div>@helpers.avatarLink(asignee.assigneeUserName, 20) @helpers.user(asignee.assigneeUserName, styleClass="username strong small")</div>
|
<div>@helpers.avatarLink(assignee.assigneeUserName, 20) @helpers.user(assignee.assigneeUserName, styleClass="username strong small")</div>
|
||||||
}
|
}
|
||||||
@if(issueAssignees.isEmpty) {
|
@if(issueAssignees.isEmpty) {
|
||||||
<span class="muted small">No one assigned</span>
|
<span class="muted small">No one assigned</span>
|
||||||
@@ -158,10 +158,10 @@
|
|||||||
<div class="pull-right">
|
<div class="pull-right">
|
||||||
@gitbucket.core.model.CustomFieldBehavior(field.fieldType).map { behavior =>
|
@gitbucket.core.model.CustomFieldBehavior(field.fieldType).map { behavior =>
|
||||||
@if(issue.nonEmpty) {
|
@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) {
|
@if(issue.isEmpty) {
|
||||||
@Html(behavior.createHtml(repository, field.fieldId))
|
@Html(behavior.createHtml(repository, field.fieldId, field.fieldName, field.constraints))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -24,9 +24,9 @@
|
|||||||
<a href="@condition.copy(state = "closed").toURL">Closed <span class="badge">@closedCount</span></a>
|
<a href="@condition.copy(state = "closed").toURL">Closed <span class="badge">@closedCount</span></a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</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">
|
<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"/>
|
<input type="hidden" name="type" value="@target"/>
|
||||||
<span class="input-group-btn">
|
<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>
|
<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>
|
||||||
<td class="text-right">
|
<td class="text-right">
|
||||||
<div class="branch-action">
|
<div class="branch-action">
|
||||||
@if(repository.repository.defaultBranch != branch.name){
|
@if(repository.repository.defaultBranch != branch.name || repository.repository.originUserName.isDefined){
|
||||||
@branch.mergeInfo.map{ info =>
|
@branch.mergeInfo.map{ info =>
|
||||||
@prs.map{ case (pull, issue) =>
|
@prs.map{ case (pull, issue) =>
|
||||||
<a href="@helpers.url(repository)/pull/@issue.issueId" title="@issue.title">#@issue.issueId</a>
|
<a href="@helpers.url(repository)/pull/@issue.issueId" title="@issue.title">#@issue.issueId</a>
|
||||||
@@ -76,13 +76,14 @@
|
|||||||
}
|
}
|
||||||
@if(hasWritePermission){
|
@if(hasWritePermission){
|
||||||
<span style="margin-left: 8px;">
|
<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>
|
<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 {
|
} 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>
|
<a class="disabled" data-toggle="tooltip" title="You can’t delete a protected branch."><i class="octicon octicon-trashcan"></i></a>
|
||||||
} else {
|
} 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>
|
</span>
|
||||||
@@ -99,7 +100,7 @@
|
|||||||
<script>
|
<script>
|
||||||
$(function(){
|
$(function(){
|
||||||
$('.delete-branch').click(function(e){
|
$('.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?');
|
return confirm('Are you sure you want to remove the ' + branchName + ' branch?');
|
||||||
});
|
});
|
||||||
$('*[data-toggle=tooltip]').tooltip().css("white-space","nowrap");
|
$('*[data-toggle=tooltip]').tooltip().css("white-space","nowrap");
|
||||||
|
|||||||
@@ -21,12 +21,12 @@
|
|||||||
git add README.md
|
git add README.md
|
||||||
git commit -m "first commit"
|
git commit -m "first commit"
|
||||||
git remote add origin <span class="live-clone-url">@repository.httpUrl</span>
|
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>
|
<h3 style="margin-top: 30px;">Push an existing repository from the command line</h3>
|
||||||
@helpers.pre {
|
@helpers.pre {
|
||||||
git remote add origin <span class="live-clone-url">@repository.httpUrl</span>
|
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>
|
<script>
|
||||||
$(function(){
|
$(function(){
|
||||||
|
|||||||
@@ -7,6 +7,9 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="col-md-4">
|
<div class="col-md-4">
|
||||||
@customField.fieldType
|
@customField.fieldType
|
||||||
|
@customField.constraints.map { constraints =>
|
||||||
|
(@constraints)
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-2">
|
<div class="col-md-2">
|
||||||
@if(customField.enableForIssues) {
|
@if(customField.enableForIssues) {
|
||||||
|
|||||||
@@ -10,7 +10,9 @@
|
|||||||
<option value="double" @if(field.map(_.fieldType == "double").getOrElse(false)){selected}>Double</option>
|
<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="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="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>
|
</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;">
|
<label for="enableForIssues-@fieldId" class="normal" style="margin-left: 4px;">
|
||||||
<input type="checkbox" id="enableForIssues-@fieldId" @if(field.map(_.enableForIssues).getOrElse(false)){checked}> Issues
|
<input type="checkbox" id="enableForIssues-@fieldId" @if(field.map(_.enableForIssues).getOrElse(false)){checked}> Issues
|
||||||
</label>
|
</label>
|
||||||
@@ -30,6 +32,7 @@
|
|||||||
$.post('@helpers.url(repository)/settings/issues/fields/@{if(fieldId == "new") "new" else s"$fieldId/edit"}', {
|
$.post('@helpers.url(repository)/settings/issues/fields/@{if(fieldId == "new") "new" else s"$fieldId/edit"}', {
|
||||||
'fieldName' : $('#fieldName-@fieldId').val(),
|
'fieldName' : $('#fieldName-@fieldId').val(),
|
||||||
'fieldType': $('#fieldType-@fieldId option:selected').val(),
|
'fieldType': $('#fieldType-@fieldId option:selected').val(),
|
||||||
|
'constraints': $('#constraints-@fieldId').val(),
|
||||||
'enableForIssues': $('#enableForIssues-@fieldId').prop('checked'),
|
'enableForIssues': $('#enableForIssues-@fieldId').prop('checked'),
|
||||||
'enableForPullRequests': $('#enableForPullRequests-@fieldId').prop('checked')
|
'enableForPullRequests': $('#enableForPullRequests-@fieldId').prop('checked')
|
||||||
}, function(data, status){
|
}, function(data, status){
|
||||||
@@ -61,6 +64,14 @@
|
|||||||
$('#field-@fieldId').show();
|
$('#field-@fieldId').show();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$('#fieldType-@fieldId').change(function(){
|
||||||
|
if($(this).val() == 'enum') {
|
||||||
|
$('#constraints-@fieldId').show();
|
||||||
|
} else {
|
||||||
|
$('#constraints-@fieldId').hide();
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,12 +24,13 @@ 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 =>
|
Seq("8.0", "5.7").foreach { tag =>
|
||||||
test(s"Migration MySQL $tag", ExternalDBTest) {
|
test(s"Migration MySQL $tag", ExternalDBTest) {
|
||||||
val container = new MySQLContainer() {
|
val container = new MySQLContainer() {
|
||||||
override val container = new org.testcontainers.containers.MySQLContainer(s"mysql:$tag") {
|
override val container: org.testcontainers.containers.MySQLContainer[_] =
|
||||||
|
new org.testcontainers.containers.MySQLContainer(s"mysql:$tag") {
|
||||||
override def getDriverClassName = "org.mariadb.jdbc.Driver"
|
override def getDriverClassName = "org.mariadb.jdbc.Driver"
|
||||||
override def getJdbcUrl: String = super.getJdbcUrl + "?permitMysqlScheme"
|
override def getJdbcUrl: String = super.getJdbcUrl + "?permitMysqlScheme"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,12 +9,19 @@ import scala.util.Using
|
|||||||
import org.kohsuke.github.GHCommitState
|
import org.kohsuke.github.GHCommitState
|
||||||
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import java.util.logging.{Level, Logger}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Need to run `sbt package` before running this test.
|
* Need to run `sbt package` before running this test.
|
||||||
*/
|
*/
|
||||||
class ApiIntegrationTest extends AnyFunSuite {
|
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") {
|
test("create repository") {
|
||||||
Using.resource(new TestingGitBucketServer(19999)) { server =>
|
Using.resource(new TestingGitBucketServer(19999)) { server =>
|
||||||
val github = server.client("root", "root")
|
val github = server.client("root", "root")
|
||||||
@@ -29,7 +36,7 @@ class ApiIntegrationTest extends AnyFunSuite {
|
|||||||
|
|
||||||
assert(repository.getName == "test")
|
assert(repository.getName == "test")
|
||||||
assert(repository.getDescription == "test repository")
|
assert(repository.getDescription == "test repository")
|
||||||
assert(repository.getDefaultBranch == "master")
|
assert(repository.getDefaultBranch == "main")
|
||||||
assert(repository.getWatchers == 0)
|
assert(repository.getWatchers == 0)
|
||||||
assert(repository.getWatchersCount == 0)
|
assert(repository.getWatchersCount == 0)
|
||||||
assert(repository.getForks == 0)
|
assert(repository.getForks == 0)
|
||||||
@@ -48,7 +55,7 @@ class ApiIntegrationTest extends AnyFunSuite {
|
|||||||
val repository = repositories.get(0)
|
val repository = repositories.get(0)
|
||||||
assert(repository.getName == "test")
|
assert(repository.getName == "test")
|
||||||
assert(repository.getDescription == "test repository")
|
assert(repository.getDescription == "test repository")
|
||||||
assert(repository.getDefaultBranch == "master")
|
assert(repository.getDefaultBranch == "main")
|
||||||
assert(repository.getWatchers == 0)
|
assert(repository.getWatchers == 0)
|
||||||
assert(repository.getWatchersCount == 0)
|
assert(repository.getWatchersCount == 0)
|
||||||
assert(repository.getForks == 0)
|
assert(repository.getForks == 0)
|
||||||
@@ -68,7 +75,7 @@ class ApiIntegrationTest extends AnyFunSuite {
|
|||||||
val github = server.client("root", "root")
|
val github = server.client("root", "root")
|
||||||
|
|
||||||
val repo = github.createRepository("create_status_test").autoInit(true).create()
|
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)
|
val status = repo.getLastCommitStatus(sha1)
|
||||||
@@ -140,10 +147,10 @@ class ApiIntegrationTest extends AnyFunSuite {
|
|||||||
|
|
||||||
// get master ref
|
// get master ref
|
||||||
{
|
{
|
||||||
val ref = repo.getRef("heads/master")
|
val ref = repo.getRef("heads/main")
|
||||||
assert(ref.getRef == "refs/heads/master")
|
assert(ref.getRef == "refs/heads/main")
|
||||||
assert(
|
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")
|
assert(ref.getObject.getType == "commit")
|
||||||
}
|
}
|
||||||
@@ -169,7 +176,7 @@ class ApiIntegrationTest extends AnyFunSuite {
|
|||||||
val createResult =
|
val createResult =
|
||||||
repo
|
repo
|
||||||
.createContent()
|
.createContent()
|
||||||
.branch("master")
|
.branch("main")
|
||||||
.content("create")
|
.content("create")
|
||||||
.message("Create content")
|
.message("Create content")
|
||||||
.path("README.md")
|
.path("README.md")
|
||||||
@@ -186,7 +193,7 @@ class ApiIntegrationTest extends AnyFunSuite {
|
|||||||
val updateResult =
|
val updateResult =
|
||||||
repo
|
repo
|
||||||
.createContent()
|
.createContent()
|
||||||
.branch("master")
|
.branch("main")
|
||||||
.content("update")
|
.content("update")
|
||||||
.message("Update content")
|
.message("Update content")
|
||||||
.path("README.md")
|
.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 {
|
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 date1 = {
|
||||||
val d = Calendar.getInstance(TimeZone.getTimeZone("UTC"))
|
val d = Calendar.getInstance(TimeZone.getTimeZone("UTC"))
|
||||||
@@ -53,7 +53,7 @@ object ApiSpecModels {
|
|||||||
repositoryName = repo1Name.name,
|
repositoryName = repo1Name.name,
|
||||||
isPrivate = false,
|
isPrivate = false,
|
||||||
description = Some("This your first repo!"),
|
description = Some("This your first repo!"),
|
||||||
defaultBranch = "master",
|
defaultBranch = "main",
|
||||||
registeredDate = date1,
|
registeredDate = date1,
|
||||||
updatedDate = date1,
|
updatedDate = date1,
|
||||||
lastActivityDate = date1,
|
lastActivityDate = date1,
|
||||||
@@ -81,7 +81,7 @@ object ApiSpecModels {
|
|||||||
pullCount = 1,
|
pullCount = 1,
|
||||||
forkedCount = 1,
|
forkedCount = 1,
|
||||||
milestoneCount = 1,
|
milestoneCount = 1,
|
||||||
branchList = Seq("master", "develop"),
|
branchList = Seq("main", "develop"),
|
||||||
tags = Seq(
|
tags = Seq(
|
||||||
TagInfo(
|
TagInfo(
|
||||||
name = "v1.0",
|
name = "v1.0",
|
||||||
@@ -147,7 +147,7 @@ object ApiSpecModels {
|
|||||||
userName = repo1Name.owner,
|
userName = repo1Name.owner,
|
||||||
repositoryName = repo1Name.name,
|
repositoryName = repo1Name.name,
|
||||||
issueId = issuePR.issueId,
|
issueId = issuePR.issueId,
|
||||||
branch = "master",
|
branch = "main",
|
||||||
requestUserName = "bear",
|
requestUserName = "bear",
|
||||||
requestRepositoryName = repo1Name.name,
|
requestRepositoryName = repo1Name.name,
|
||||||
requestBranch = "new-topic",
|
requestBranch = "new-topic",
|
||||||
@@ -363,7 +363,7 @@ object ApiSpecModels {
|
|||||||
info = ProtectedBranchInfo(
|
info = ProtectedBranchInfo(
|
||||||
owner = repo1Name.owner,
|
owner = repo1Name.owner,
|
||||||
repository = repo1Name.name,
|
repository = repo1Name.name,
|
||||||
branch = "master",
|
branch = "main",
|
||||||
enabled = true,
|
enabled = true,
|
||||||
contexts = Seq("continuous-integration/travis-ci"),
|
contexts = Seq("continuous-integration/travis-ci"),
|
||||||
includeAdministrators = true
|
includeAdministrators = true
|
||||||
@@ -384,7 +384,7 @@ object ApiSpecModels {
|
|||||||
)
|
)
|
||||||
|
|
||||||
val apiBranch = ApiBranch(
|
val apiBranch = ApiBranch(
|
||||||
name = "master",
|
name = "main",
|
||||||
commit = ApiBranchCommit(sha1),
|
commit = ApiBranchCommit(sha1),
|
||||||
protection = apiBranchProtectionOutput
|
protection = apiBranchProtectionOutput
|
||||||
)(
|
)(
|
||||||
@@ -392,7 +392,7 @@ object ApiSpecModels {
|
|||||||
)
|
)
|
||||||
|
|
||||||
val apiBranchForList = ApiBranchForList(
|
val apiBranchForList = ApiBranchForList(
|
||||||
name = "master",
|
name = "main",
|
||||||
commit = ApiBranchCommit(sha1)
|
commit = ApiBranchCommit(sha1)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -447,8 +447,8 @@ object ApiSpecModels {
|
|||||||
val gitHubContext = JsonFormat.Context("https://api.github.com", Some("https://api.github.com"))
|
val gitHubContext = JsonFormat.Context("https://api.github.com", Some("https://api.github.com"))
|
||||||
|
|
||||||
val apiRefHeadsMaster = ApiRef(
|
val apiRefHeadsMaster = ApiRef(
|
||||||
ref = "refs/heads/master",
|
ref = "refs/heads/main",
|
||||||
url = ApiPath("/repos/gitbucket/gitbucket/git/refs/heads/master"),
|
url = ApiPath("/repos/gitbucket/gitbucket/git/refs/heads/main"),
|
||||||
node_id = "MDM6UmVmOTM1MDc0NjpyZWZzL2hlYWRzL21hc3Rlcg==",
|
node_id = "MDM6UmVmOTM1MDc0NjpyZWZzL2hlYWRzL21hc3Rlcg==",
|
||||||
`object` = ApiRefCommit(
|
`object` = ApiRefCommit(
|
||||||
sha = "6b2d124d092402f2c2b7131caada05ead9e7de6d",
|
sha = "6b2d124d092402f2c2b7131caada05ead9e7de6d",
|
||||||
@@ -508,7 +508,7 @@ object ApiSpecModels {
|
|||||||
|"watchers":0,
|
|"watchers":0,
|
||||||
|"forks":1,
|
|"forks":1,
|
||||||
|"private":false,
|
|"private":false,
|
||||||
|"default_branch":"master",
|
|"default_branch":"main",
|
||||||
|"owner":$jsonUser,
|
|"owner":$jsonUser,
|
||||||
|"has_issues":true,
|
|"has_issues":true,
|
||||||
|"id":0,
|
|"id":0,
|
||||||
@@ -594,7 +594,7 @@ object ApiSpecModels {
|
|||||||
|"updated_at":"2011-04-14T16:00:49Z",
|
|"updated_at":"2011-04-14T16:00:49Z",
|
||||||
|"created_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},
|
|"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":true,
|
||||||
|"merged_at":"2011-04-14T16:00:49Z",
|
|"merged_at":"2011-04-14T16:00:49Z",
|
||||||
|"merged_by":$jsonUser,
|
|"merged_by":$jsonUser,
|
||||||
@@ -730,13 +730,13 @@ object ApiSpecModels {
|
|||||||
|
|
||||||
val jsonBranchProtectionOutput =
|
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,
|
|"enabled":true,
|
||||||
|"required_status_checks":{
|
|"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",
|
|"enforcement_level":"everyone",
|
||||||
|"contexts":["continuous-integration/travis-ci"],
|
|"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
|
|}""".stripMargin
|
||||||
|
|
||||||
val jsonBranchProtectionInput =
|
val jsonBranchProtectionInput =
|
||||||
@@ -749,15 +749,15 @@ object ApiSpecModels {
|
|||||||
|}""".stripMargin
|
|}""".stripMargin
|
||||||
|
|
||||||
val jsonBranch = s"""{
|
val jsonBranch = s"""{
|
||||||
|"name":"master",
|
|"name":"main",
|
||||||
|"commit":{"sha":"6dcb09b5b57875f334f61aebed695e2e4193db5e"},
|
|"commit":{"sha":"6dcb09b5b57875f334f61aebed695e2e4193db5e"},
|
||||||
|"protection":$jsonBranchProtectionOutput,
|
|"protection":$jsonBranchProtectionOutput,
|
||||||
|"_links":{
|
|"_links":{
|
||||||
|"self":"http://gitbucket.exmple.com/api/v3/repos/octocat/Hello-World/branches/master",
|
|"self":"http://gitbucket.exmple.com/api/v3/repos/octocat/Hello-World/branches/main",
|
||||||
|"html":"http://gitbucket.exmple.com/octocat/Hello-World/tree/master"}
|
|"html":"http://gitbucket.exmple.com/octocat/Hello-World/tree/main"}
|
||||||
|}""".stripMargin
|
|}""".stripMargin
|
||||||
|
|
||||||
val jsonBranchForList = """{"name":"master","commit":{"sha":"6dcb09b5b57875f334f61aebed695e2e4193db5e"}}"""
|
val jsonBranchForList = """{"name":"main","commit":{"sha":"6dcb09b5b57875f334f61aebed695e2e4193db5e"}}"""
|
||||||
|
|
||||||
val jsonContents =
|
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"
|
//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 jsonRef = """{"ref":"refs/heads/featureA","object":{"sha":"6dcb09b5b57875f334f61aebed695e2e4193db5e"}}"""
|
||||||
|
|
||||||
val jsonRefHeadsMaster =
|
val jsonRefHeadsMain =
|
||||||
"""{
|
"""{
|
||||||
|"ref": "refs/heads/master",
|
|"ref": "refs/heads/main",
|
||||||
|"node_id": "MDM6UmVmOTM1MDc0NjpyZWZzL2hlYWRzL21hc3Rlcg==",
|
|"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": {
|
|"object": {
|
||||||
|"sha": "6b2d124d092402f2c2b7131caada05ead9e7de6d",
|
|"sha": "6b2d124d092402f2c2b7131caada05ead9e7de6d",
|
||||||
|"type": "commit",
|
|"type": "commit",
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
package gitbucket.core.api
|
package gitbucket.core.api
|
||||||
|
|
||||||
|
import org.json4s.Formats
|
||||||
import org.json4s.jackson.JsonMethods
|
import org.json4s.jackson.JsonMethods
|
||||||
import org.scalatest.funsuite.AnyFunSuite
|
import org.scalatest.funsuite.AnyFunSuite
|
||||||
|
|
||||||
class JsonFormatSpec extends AnyFunSuite {
|
class JsonFormatSpec extends AnyFunSuite {
|
||||||
import ApiSpecModels._
|
import ApiSpecModels._
|
||||||
implicit val format = JsonFormat.jsonFormats
|
implicit val format: Formats = JsonFormat.jsonFormats
|
||||||
|
|
||||||
private def expected(json: String) = json.replaceAll("\n", "")
|
private def expected(json: String) = json.replaceAll("\n", "")
|
||||||
def normalizeJson(json: String) = {
|
def normalizeJson(json: String) = {
|
||||||
@@ -83,7 +84,7 @@ class JsonFormatSpec extends AnyFunSuite {
|
|||||||
assert(JsonFormat(apiPusher) == expected(jsonPusher))
|
assert(JsonFormat(apiPusher) == expected(jsonPusher))
|
||||||
}
|
}
|
||||||
test("apiRefHead") {
|
test("apiRefHead") {
|
||||||
assertEqualJson(JsonFormat(apiRefHeadsMaster)(gitHubContext), jsonRefHeadsMaster)
|
assertEqualJson(JsonFormat(apiRefHeadsMaster)(gitHubContext), jsonRefHeadsMain)
|
||||||
}
|
}
|
||||||
test("apiRefTag") {
|
test("apiRefTag") {
|
||||||
assertEqualJson(JsonFormat(apiRefTag)(gitHubContext), jsonRefTag)
|
assertEqualJson(JsonFormat(apiRefTag)(gitHubContext), jsonRefTag)
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ class CommitStatusServiceSpec
|
|||||||
test("createCommitState can insert and update") {
|
test("createCommitState can insert and update") {
|
||||||
withTestDB { implicit session =>
|
withTestDB { implicit session =>
|
||||||
val tester = generateNewAccount(fixture1.creator)
|
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)
|
val id = generateFixture1(tester: Account)
|
||||||
assert(
|
assert(
|
||||||
getCommitStatus(fixture1.userName, fixture1.repositoryName, id) == Some(fixture1.copy(commitStatusId = id))
|
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") {
|
test("getCommitStatus can find by commitId and context") {
|
||||||
withTestDB { implicit session =>
|
withTestDB { implicit session =>
|
||||||
val tester = generateNewAccount(fixture1.creator)
|
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)
|
val id = generateFixture1(tester: Account)
|
||||||
assert(
|
assert(
|
||||||
getCommitStatus(fixture1.userName, fixture1.repositoryName, fixture1.commitId, fixture1.context) == Some(
|
getCommitStatus(fixture1.userName, fixture1.repositoryName, fixture1.commitId, fixture1.context) == Some(
|
||||||
@@ -90,7 +90,7 @@ class CommitStatusServiceSpec
|
|||||||
test("getCommitStatus can find by commitStatusId") {
|
test("getCommitStatus can find by commitStatusId") {
|
||||||
withTestDB { implicit session =>
|
withTestDB { implicit session =>
|
||||||
val tester = generateNewAccount(fixture1.creator)
|
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)
|
val id = generateFixture1(tester: Account)
|
||||||
assert(
|
assert(
|
||||||
getCommitStatus(fixture1.userName, fixture1.repositoryName, id) == Some(fixture1.copy(commitStatusId = id))
|
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") {
|
test("renameRepository can rename CommitState, ProtectedBranches") {
|
||||||
withTestDB { implicit session =>
|
withTestDB { implicit session =>
|
||||||
val tester = generateNewAccount("tester")
|
val tester = generateNewAccount("tester")
|
||||||
insertRepository("repo", "root", None, false)
|
insertRepository("repo", "root", None, false, "main")
|
||||||
val service = new CommitStatusService with ProtectedBranchService {}
|
val service = new CommitStatusService with ProtectedBranchService {}
|
||||||
val id = service.createCommitStatus(
|
val id = service.createCommitStatus(
|
||||||
userName = "root",
|
userName = "root",
|
||||||
|
|||||||
@@ -81,7 +81,8 @@ trait ServiceSpecBase {
|
|||||||
),
|
),
|
||||||
repositoryViewer = RepositoryViewerSettings(
|
repositoryViewer = RepositoryViewerSettings(
|
||||||
maxFiles = 0
|
maxFiles = 0
|
||||||
)
|
),
|
||||||
|
defaultBranch = "main"
|
||||||
)
|
)
|
||||||
|
|
||||||
def withTestDB[A](action: (Session) => A): A = {
|
def withTestDB[A](action: (Session) => A): A = {
|
||||||
@@ -127,8 +128,8 @@ trait ServiceSpecBase {
|
|||||||
if (dir.exists()) {
|
if (dir.exists()) {
|
||||||
FileUtils.deleteQuietly(dir)
|
FileUtils.deleteQuietly(dir)
|
||||||
}
|
}
|
||||||
JGitUtil.initRepository(dir)
|
JGitUtil.initRepository(dir, "main")
|
||||||
dummyService.insertRepository(repositoryName, userName, None, false)
|
dummyService.insertRepository(repositoryName, userName, None, false, "main")
|
||||||
ac
|
ac
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ class WebHookJsonFormatSpec extends AnyFunSuite {
|
|||||||
|"description":"This your first repo!",
|
|"description":"This your first repo!",
|
||||||
|"ref":"v1.0",
|
|"ref":"v1.0",
|
||||||
|"ref_type":"tag",
|
|"ref_type":"tag",
|
||||||
|"master_branch":"master",
|
|"master_branch":"main",
|
||||||
|"repository":$jsonRepository,
|
|"repository":$jsonRepository,
|
||||||
|"pusher_type":"user"
|
|"pusher_type":"user"
|
||||||
|}""".stripMargin
|
|}""".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", message = "initial")
|
||||||
createFile(git, Constants.HEAD, "README.md", "body1\nbody2", message = "modified")
|
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(
|
val payload = WebHookPushPayload(
|
||||||
git = git,
|
git = git,
|
||||||
sender = account,
|
sender = account,
|
||||||
refName = "refs/heads/master",
|
refName = "refs/heads/main",
|
||||||
repositoryInfo = repositoryInfo,
|
repositoryInfo = repositoryInfo,
|
||||||
commits = List(commitInfo(branchId.name)),
|
commits = List(commitInfo(branchId.name)),
|
||||||
repositoryOwner = account,
|
repositoryOwner = account,
|
||||||
@@ -56,7 +56,7 @@ class WebHookJsonFormatSpec extends AnyFunSuite {
|
|||||||
val expected = s"""{
|
val expected = s"""{
|
||||||
|"pusher":{"name":"octocat","email":"octocat@example.com"},
|
|"pusher":{"name":"octocat","email":"octocat@example.com"},
|
||||||
|"sender":$jsonUser,
|
|"sender":$jsonUser,
|
||||||
|"ref":"refs/heads/master",
|
|"ref":"refs/heads/main",
|
||||||
|"before":"6dcb09b5b57875f334f61aebed695e2e4193db5e",
|
|"before":"6dcb09b5b57875f334f61aebed695e2e4193db5e",
|
||||||
|"after":"6dcb09b5b57875f334f61aebed695e2e4193db5e",
|
|"after":"6dcb09b5b57875f334f61aebed695e2e4193db5e",
|
||||||
|"commits":[${jsonCommit(branchId.name)}],
|
|"commits":[${jsonCommit(branchId.name)}],
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ object GitSpecUtil {
|
|||||||
RepositoryCache.clear()
|
RepositoryCache.clear()
|
||||||
FileUtils.deleteQuietly(dir)
|
FileUtils.deleteQuietly(dir)
|
||||||
Files.createDirectories(dir.toPath())
|
Files.createDirectories(dir.toPath())
|
||||||
JGitUtil.initRepository(dir)
|
JGitUtil.initRepository(dir, "main")
|
||||||
dir
|
dir
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,14 +23,14 @@ class JGitUtilSpec extends AnyFunSuite {
|
|||||||
withTestRepository { git =>
|
withTestRepository { git =>
|
||||||
createFile(git, Constants.HEAD, "README.md", "body1", message = "commit1")
|
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)
|
val commit = JGitUtil.getRevCommitFromId(git, branchId)
|
||||||
|
|
||||||
createFile(git, Constants.HEAD, "LICENSE", "Apache License", message = "commit1")
|
createFile(git, Constants.HEAD, "LICENSE", "Apache License", message = "commit1")
|
||||||
createFile(git, Constants.HEAD, "README.md", "body1\nbody2", message = "commit1")
|
createFile(git, Constants.HEAD, "README.md", "body1\nbody2", message = "commit1")
|
||||||
|
|
||||||
// latest commit
|
// 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.size == 1)
|
||||||
assert(diff1(0).changeType == ChangeType.MODIFY)
|
assert(diff1(0).changeType == ChangeType.MODIFY)
|
||||||
assert(diff1(0).oldPath == "README.md")
|
assert(diff1(0).oldPath == "README.md")
|
||||||
@@ -44,7 +44,7 @@ class JGitUtilSpec extends AnyFunSuite {
|
|||||||
|\ No newline at end of file""".stripMargin))
|
|\ No newline at end of file""".stripMargin))
|
||||||
|
|
||||||
// from specified commit
|
// 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.size == 2)
|
||||||
assert(diff2(0).changeType == ChangeType.ADD)
|
assert(diff2(0).changeType == ChangeType.ADD)
|
||||||
assert(diff2(0).oldPath == "/dev/null")
|
assert(diff2(0).oldPath == "/dev/null")
|
||||||
@@ -73,7 +73,7 @@ class JGitUtilSpec extends AnyFunSuite {
|
|||||||
createFile(git, Constants.HEAD, "README.md", "body1", message = "commit1")
|
createFile(git, Constants.HEAD, "README.md", "body1", message = "commit1")
|
||||||
|
|
||||||
// branch name
|
// branch name
|
||||||
val branchId = git.getRepository.resolve("master")
|
val branchId = git.getRepository.resolve("main")
|
||||||
val commit1 = JGitUtil.getRevCommitFromId(git, branchId)
|
val commit1 = JGitUtil.getRevCommitFromId(git, branchId)
|
||||||
|
|
||||||
// commit id
|
// commit id
|
||||||
@@ -97,19 +97,19 @@ class JGitUtilSpec extends AnyFunSuite {
|
|||||||
withTestRepository { git =>
|
withTestRepository { git =>
|
||||||
// getCommitCount
|
// getCommitCount
|
||||||
createFile(git, Constants.HEAD, "README.md", "body1", message = "commit1")
|
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")
|
createFile(git, Constants.HEAD, "README.md", "body2", message = "commit2")
|
||||||
assert(JGitUtil.getCommitCount(git, "master") == 2)
|
assert(JGitUtil.getCommitCount(git, "main") == 2)
|
||||||
|
|
||||||
// maximum limit
|
// maximum limit
|
||||||
(3 to 10).foreach { i =>
|
(3 to 10).foreach { i =>
|
||||||
createFile(git, Constants.HEAD, "README.md", "body" + i, message = "commit" + 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
|
// 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)
|
assert(gitLog.call.asScala.toSeq.size == 10)
|
||||||
|
|
||||||
// getAllCommitIds
|
// getAllCommitIds
|
||||||
@@ -123,22 +123,22 @@ class JGitUtilSpec extends AnyFunSuite {
|
|||||||
createFile(git, Constants.HEAD, "README.md", "body1", message = "commit1")
|
createFile(git, Constants.HEAD, "README.md", "body1", message = "commit1")
|
||||||
|
|
||||||
// createBranch
|
// createBranch
|
||||||
assert(JGitUtil.createBranch(git, "master", "test1") == Right("Branch created."))
|
assert(JGitUtil.createBranch(git, "main", "test1") == Right("Branch created."))
|
||||||
assert(JGitUtil.createBranch(git, "master", "test2") == Right("Branch created."))
|
assert(JGitUtil.createBranch(git, "main", "test2") == Right("Branch created."))
|
||||||
assert(JGitUtil.createBranch(git, "master", "test2") == Left("Sorry, that branch already exists."))
|
assert(JGitUtil.createBranch(git, "main", "test2") == Left("Sorry, that branch already exists."))
|
||||||
|
|
||||||
// verify
|
// verify
|
||||||
val branches = git.branchList.call()
|
val branches = git.branchList.call()
|
||||||
assert(branches.size == 3)
|
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(1).getName == "refs/heads/test1")
|
||||||
assert(branches.get(2).getName == "refs/heads/test2")
|
assert(branches.get(2).getName == "refs/heads/test2")
|
||||||
|
|
||||||
// getBranchesOfCommit
|
// 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)
|
val branchesOfCommit = JGitUtil.getBranchesOfCommit(git, commit.getName)
|
||||||
assert(branchesOfCommit.size == 3)
|
assert(branchesOfCommit.size == 3)
|
||||||
assert(branchesOfCommit(0) == "master")
|
assert(branchesOfCommit(0) == "main")
|
||||||
assert(branchesOfCommit(1) == "test1")
|
assert(branchesOfCommit(1) == "test1")
|
||||||
assert(branchesOfCommit(2) == "test2")
|
assert(branchesOfCommit(2) == "test2")
|
||||||
}
|
}
|
||||||
@@ -147,16 +147,16 @@ class JGitUtilSpec extends AnyFunSuite {
|
|||||||
test("getBranches") {
|
test("getBranches") {
|
||||||
withTestRepository { git =>
|
withTestRepository { git =>
|
||||||
createFile(git, Constants.HEAD, "README.md", "body1", message = "commit1")
|
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")
|
createFile(git, Constants.HEAD, "README.md", "body2", message = "commit2")
|
||||||
JGitUtil.createBranch(git, "master", "test2")
|
JGitUtil.createBranch(git, "main", "test2")
|
||||||
|
|
||||||
// getBranches
|
// getBranches
|
||||||
val branches = JGitUtil.getBranches(git, "master", true)
|
val branches = JGitUtil.getBranches(git, "main", true)
|
||||||
assert(branches.size == 3)
|
assert(branches.size == 3)
|
||||||
|
|
||||||
assert(branches(0).name == "master")
|
assert(branches(0).name == "main")
|
||||||
assert(branches(0).committerName == "dummy")
|
assert(branches(0).committerName == "dummy")
|
||||||
assert(branches(0).committerEmailAddress == "dummy@example.com")
|
assert(branches(0).committerEmailAddress == "dummy@example.com")
|
||||||
|
|
||||||
@@ -178,17 +178,17 @@ class JGitUtilSpec extends AnyFunSuite {
|
|||||||
createFile(git, Constants.HEAD, "README.md", "body1", message = "commit1")
|
createFile(git, Constants.HEAD, "README.md", "body1", message = "commit1")
|
||||||
|
|
||||||
// createTag
|
// 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(
|
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
|
// record current commit
|
||||||
val commit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve("master"))
|
val commit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve("main"))
|
||||||
|
|
||||||
// createTag
|
// createTag
|
||||||
createFile(git, Constants.HEAD, "LICENSE", "Apache License", message = "commit2")
|
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
|
// verify
|
||||||
val allTags = git.tagList().call().asScala
|
val allTags = git.tagList().call().asScala
|
||||||
@@ -203,7 +203,7 @@ class JGitUtilSpec extends AnyFunSuite {
|
|||||||
assert(tagsOfCommit(1) == "1.0")
|
assert(tagsOfCommit(1) == "1.0")
|
||||||
|
|
||||||
// getTagsOnCommit
|
// getTagsOnCommit
|
||||||
val tagsOnCommit = JGitUtil.getTagsOnCommit(git, "master")
|
val tagsOnCommit = JGitUtil.getTagsOnCommit(git, "main")
|
||||||
assert(tagsOnCommit.size == 1)
|
assert(tagsOnCommit.size == 1)
|
||||||
assert(tagsOnCommit(0) == "1.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, "README.md", "body1", message = "commit1")
|
||||||
createFile(git, Constants.HEAD, "LICENSE", "Apache License", message = "commit2")
|
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)
|
val commit = JGitUtil.getRevCommitFromId(git, objectId)
|
||||||
|
|
||||||
// Since Non-LFS file doesn't need RepositoryInfo give null
|
// 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, "README.md", "body1", message = "commit1")
|
||||||
createFile(git, Constants.HEAD, "LARGE_FILE", "body1" * 1000000, 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 commit = JGitUtil.getRevCommitFromId(git, objectId)
|
||||||
|
|
||||||
val content1 = JGitUtil.getContentFromPath(git, commit.getTree, "README.md", true)
|
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", "body1\nbody2\nbody3", message = "commit1")
|
||||||
createFile(git, Constants.HEAD, "README.md", "body0\nbody2\nbody3", message = "commit2")
|
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.size == 2)
|
||||||
assert(blames(0).message == "commit2")
|
assert(blames(0).message == "commit2")
|
||||||
@@ -266,75 +266,75 @@ class JGitUtilSpec extends AnyFunSuite {
|
|||||||
withTestRepository { git =>
|
withTestRepository { git =>
|
||||||
def list(branch: String, path: String) =
|
def list(branch: String, path: String) =
|
||||||
JGitUtil.getFileList(git, branch, path).map(finfo => (finfo.name, finfo.message, finfo.isDirectory))
|
JGitUtil.getFileList(git, branch, path).map(finfo => (finfo.name, finfo.message, finfo.isDirectory))
|
||||||
assert(list("master", ".") == Nil)
|
assert(list("main", ".") == Nil)
|
||||||
assert(list("master", "dir/subdir") == Nil)
|
assert(list("main", "dir/subdir") == Nil)
|
||||||
assert(list("branch", ".") == Nil)
|
assert(list("branch", ".") == Nil)
|
||||||
assert(list("branch", "dir/subdir") == 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("main", ".") == List(("README.md", "commit1", false)))
|
||||||
assert(list("master", "dir/subdir") == Nil)
|
assert(list("main", "dir/subdir") == Nil)
|
||||||
assert(list("branch", ".") == Nil)
|
assert(list("branch", ".") == Nil)
|
||||||
assert(list("branch", "dir/subdir") == 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("main", ".") == List(("README.md", "commit2", false)))
|
||||||
assert(list("master", "dir/subdir") == Nil)
|
assert(list("main", "dir/subdir") == Nil)
|
||||||
assert(list("branch", ".") == Nil)
|
assert(list("branch", ".") == Nil)
|
||||||
assert(list("branch", "dir/subdir") == 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("main", ".") == List(("dir/subdir", "commit3", true), ("README.md", "commit2", false)))
|
||||||
assert(list("master", "dir/subdir") == List(("File3.md", "commit3", false)))
|
assert(list("main", "dir/subdir") == List(("File3.md", "commit3", false)))
|
||||||
assert(list("branch", ".") == Nil)
|
assert(list("branch", ".") == Nil)
|
||||||
assert(list("branch", "dir/subdir") == 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("main", ".") == 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", "dir/subdir") == List(("File3.md", "commit3", false), ("File4.md", "commit4", false)))
|
||||||
assert(list("branch", ".") == Nil)
|
assert(list("branch", ".") == Nil)
|
||||||
assert(list("branch", "dir/subdir") == Nil)
|
assert(list("branch", "dir/subdir") == Nil)
|
||||||
|
|
||||||
createFile(git, "master", "README5.md", "body5", message = "commit5")
|
createFile(git, "main", "README5.md", "body5", message = "commit5")
|
||||||
|
|
||||||
assert(
|
assert(
|
||||||
list("master", ".") == List(
|
list("main", ".") == List(
|
||||||
("dir/subdir", "commit4", true),
|
("dir/subdir", "commit4", true),
|
||||||
("README.md", "commit2", false),
|
("README.md", "commit2", false),
|
||||||
("README5.md", "commit5", 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", ".") == Nil)
|
||||||
assert(list("branch", "dir/subdir") == Nil)
|
assert(list("branch", "dir/subdir") == Nil)
|
||||||
|
|
||||||
createFile(git, "master", "README.md", "body6", message = "commit6")
|
createFile(git, "main", "README.md", "body6", message = "commit6")
|
||||||
|
|
||||||
assert(
|
assert(
|
||||||
list("master", ".") == List(
|
list("main", ".") == List(
|
||||||
("dir/subdir", "commit4", true),
|
("dir/subdir", "commit4", true),
|
||||||
("README.md", "commit6", false),
|
("README.md", "commit6", false),
|
||||||
("README5.md", "commit5", 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", ".") == Nil)
|
||||||
assert(list("branch", "dir/subdir") == Nil)
|
assert(list("branch", "dir/subdir") == Nil)
|
||||||
|
|
||||||
git.branchCreate().setName("branch").setStartPoint("master").call()
|
git.branchCreate().setName("branch").setStartPoint("main").call()
|
||||||
|
|
||||||
assert(
|
assert(
|
||||||
list("master", ".") == List(
|
list("main", ".") == List(
|
||||||
("dir/subdir", "commit4", true),
|
("dir/subdir", "commit4", true),
|
||||||
("README.md", "commit6", false),
|
("README.md", "commit6", false),
|
||||||
("README5.md", "commit5", 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(
|
assert(
|
||||||
list("branch", ".") == List(
|
list("branch", ".") == List(
|
||||||
("dir/subdir", "commit4", true),
|
("dir/subdir", "commit4", true),
|
||||||
@@ -347,13 +347,13 @@ class JGitUtilSpec extends AnyFunSuite {
|
|||||||
createFile(git, "branch", "dir/subdir/File3.md", "body7", message = "commit7")
|
createFile(git, "branch", "dir/subdir/File3.md", "body7", message = "commit7")
|
||||||
|
|
||||||
assert(
|
assert(
|
||||||
list("master", ".") == List(
|
list("main", ".") == List(
|
||||||
("dir/subdir", "commit4", true),
|
("dir/subdir", "commit4", true),
|
||||||
("README.md", "commit6", false),
|
("README.md", "commit6", false),
|
||||||
("README5.md", "commit5", 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(
|
assert(
|
||||||
list("branch", ".") == List(
|
list("branch", ".") == List(
|
||||||
("dir/subdir", "commit7", true),
|
("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)))
|
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(
|
assert(
|
||||||
list("master", ".") == List(
|
list("main", ".") == List(
|
||||||
("dir/subdir", "commit4", true),
|
("dir/subdir", "commit4", true),
|
||||||
("dir8", "commit8", true),
|
("dir8", "commit8", true),
|
||||||
("README.md", "commit6", false),
|
("README.md", "commit6", false),
|
||||||
("README5.md", "commit5", 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(
|
assert(
|
||||||
list("branch", ".") == List(
|
list("branch", ".") == List(
|
||||||
("dir/subdir", "commit7", true),
|
("dir/subdir", "commit7", true),
|
||||||
@@ -386,14 +386,14 @@ class JGitUtilSpec extends AnyFunSuite {
|
|||||||
createFile(git, "branch", "dir/subdir9/File9.md", "body9", message = "commit9")
|
createFile(git, "branch", "dir/subdir9/File9.md", "body9", message = "commit9")
|
||||||
|
|
||||||
assert(
|
assert(
|
||||||
list("master", ".") == List(
|
list("main", ".") == List(
|
||||||
("dir/subdir", "commit4", true),
|
("dir/subdir", "commit4", true),
|
||||||
("dir8", "commit8", true),
|
("dir8", "commit8", true),
|
||||||
("README.md", "commit6", false),
|
("README.md", "commit6", false),
|
||||||
("README5.md", "commit5", 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(
|
assert(
|
||||||
list("branch", ".") == List(
|
list("branch", ".") == List(
|
||||||
("dir", "commit9", true),
|
("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)))
|
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(
|
assert(
|
||||||
list("master", ".") == List(
|
list("main", ".") == List(
|
||||||
("dir", "commit9", true),
|
("dir", "commit9", true),
|
||||||
("dir8", "commit8", true),
|
("dir8", "commit8", true),
|
||||||
("README.md", "commit6", false),
|
("README.md", "commit6", false),
|
||||||
("README5.md", "commit5", 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 =>
|
withTestRepository { git =>
|
||||||
def list(branch: String, path: String) =
|
def list(branch: String, path: String) =
|
||||||
JGitUtil.getFileList(git, branch, path).map(finfo => (finfo.name, finfo.message, finfo.isDirectory))
|
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")
|
createFile(git, "branch", "test/text2.txt", "body2", message = "commit2")
|
||||||
mergeAndCommit(git, "master", "branch", message = "merge3")
|
mergeAndCommit(git, "main", "branch", message = "merge3")
|
||||||
assert(list("master", "test") == List(("text2.txt", "commit2", false)))
|
assert(list("main", "test") == List(("text2.txt", "commit2", false)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -193,7 +193,8 @@ class AvatarImageProviderSpec extends AnyFunSpec {
|
|||||||
),
|
),
|
||||||
repositoryViewer = RepositoryViewerSettings(
|
repositoryViewer = RepositoryViewerSettings(
|
||||||
maxFiles = 0
|
maxFiles = 0
|
||||||
)
|
),
|
||||||
|
"main"
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import org.mockito.Mockito._
|
|||||||
|
|
||||||
class HelpersSpec extends AnyFunSpec {
|
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])
|
private val repository = mock(classOf[RepositoryInfo])
|
||||||
|
|
||||||
import helpers._
|
import helpers._
|
||||||
|
|||||||
Reference in New Issue
Block a user