mirror of
https://github.com/gitbucket/gitbucket.git
synced 2026-05-08 14:16:56 +02:00
Compare commits
190 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8ab6b386a3 | ||
|
|
2dd2c4f568 | ||
|
|
36f7011ebf | ||
|
|
fbcf962630 | ||
|
|
3b9b261878 | ||
|
|
b334809e3e | ||
|
|
9a1324a870 | ||
|
|
b7c39e90d2 | ||
|
|
63b4f25687 | ||
|
|
b582af4469 | ||
|
|
ecd3f5b4eb | ||
|
|
7be433d331 | ||
|
|
154be0f425 | ||
|
|
9ddd2065b7 | ||
|
|
6917cbf224 | ||
|
|
637b033782 | ||
|
|
7aee451a55 | ||
|
|
e1e369d653 | ||
|
|
c2ad66438c | ||
|
|
c88e5adac2 | ||
|
|
703fb4a650 | ||
|
|
465282580f | ||
|
|
e041f8ffa0 | ||
|
|
094386bb65 | ||
|
|
b9e830f06e | ||
|
|
bdbfbc62a9 | ||
|
|
5787a02daa | ||
|
|
0d6e5af8d7 | ||
|
|
88a8973685 | ||
|
|
e640cec323 | ||
|
|
5a4226db1d | ||
|
|
fb48c2a874 | ||
|
|
28da703052 | ||
|
|
2568171083 | ||
|
|
2dff7f1b9e | ||
|
|
8a79de8b50 | ||
|
|
1b763c753c | ||
|
|
3bb1b1269c | ||
|
|
dbbaf3cc9c | ||
|
|
4465a62b5f | ||
|
|
fb48e75ecf | ||
|
|
a79142074f | ||
|
|
9f58c6dce7 | ||
|
|
6fe903afab | ||
|
|
0d94865633 | ||
|
|
2fbdead64b | ||
|
|
72a354931e | ||
|
|
cb8affcd0d | ||
|
|
a5a997eb40 | ||
|
|
b4220aab68 | ||
|
|
c8a4798f86 | ||
|
|
6b8e9e8892 | ||
|
|
9ab9363d0b | ||
|
|
1edb18a147 | ||
|
|
d8b4bf3033 | ||
|
|
6597c4490b | ||
|
|
daaf1696ad | ||
|
|
53a1ca7874 | ||
|
|
f045fa4c6a | ||
|
|
53a7c5adf8 | ||
|
|
b142eca9a5 | ||
|
|
ba753a373b | ||
|
|
95ceca75a4 | ||
|
|
f16cc117a9 | ||
|
|
af66f8f746 | ||
|
|
d9cc57e8e0 | ||
|
|
72b6dad3a2 | ||
|
|
18f396b4a2 | ||
|
|
9f3fde8de2 | ||
|
|
dfd6f80b63 | ||
|
|
119d91210c | ||
|
|
75ef30ee03 | ||
|
|
d1cf9dd600 | ||
|
|
9c9fea908c | ||
|
|
1145c4d0f6 | ||
|
|
d847fc6e0f | ||
|
|
bb9585f7a6 | ||
|
|
e91411fa45 | ||
|
|
adbc065a6f | ||
|
|
862283b729 | ||
|
|
217df7012c | ||
|
|
e672d41e77 | ||
|
|
d975700bd4 | ||
|
|
f65e41561a | ||
|
|
dab4f33ed9 | ||
|
|
5ff45ef5ae | ||
|
|
af7c622647 | ||
|
|
f7027e57df | ||
|
|
6a4719469d | ||
|
|
6ebc865ba5 | ||
|
|
0c1e8b932b | ||
|
|
fda67a32e2 | ||
|
|
14d7e9ee90 | ||
|
|
b7b7322cce | ||
|
|
5eb44398d0 | ||
|
|
be7bb255c3 | ||
|
|
cb9522d416 | ||
|
|
d80afb473b | ||
|
|
607d85c661 | ||
|
|
a5fab3bc96 | ||
|
|
eb403ada58 | ||
|
|
911c102f39 | ||
|
|
bf23e854f8 | ||
|
|
52427c0a1e | ||
|
|
d8e5ac585c | ||
|
|
2fbeef73b0 | ||
|
|
15e39572dd | ||
|
|
9eac4f42c5 | ||
|
|
01d18bb5c3 | ||
|
|
00258e9125 | ||
|
|
046b337337 | ||
|
|
46cc7b6fd3 | ||
|
|
59344b4f05 | ||
|
|
c4d8af02b2 | ||
|
|
a10bc3687a | ||
|
|
b0d21dee42 | ||
|
|
13ea0e7507 | ||
|
|
d66fdaede5 | ||
|
|
d6217d89eb | ||
|
|
c99ff1cf0f | ||
|
|
001b9ae2ae | ||
|
|
f63493f1c0 | ||
|
|
c9095722f8 | ||
|
|
b9d2efa582 | ||
|
|
9c2e09020a | ||
|
|
1ffcf8c1e9 | ||
|
|
0124091840 | ||
|
|
2a28a7b35b | ||
|
|
2a68ffc8dc | ||
|
|
6b47c49cdd | ||
|
|
b634967776 | ||
|
|
172bed760d | ||
|
|
1b7eb69083 | ||
|
|
185c01db99 | ||
|
|
ab548d8c25 | ||
|
|
983975620b | ||
|
|
b512b08256 | ||
|
|
a54fb4960f | ||
|
|
93de53d717 | ||
|
|
f262c0a9eb | ||
|
|
e5d15569df | ||
|
|
369b08eae3 | ||
|
|
8e6fcb022b | ||
|
|
456b1f6571 | ||
|
|
6e459ad225 | ||
|
|
cece4c1c7d | ||
|
|
42e7a9fa9f | ||
|
|
f9510aba8e | ||
|
|
8a7d719025 | ||
|
|
1293a21450 | ||
|
|
65dd597ab7 | ||
|
|
e145b5151e | ||
|
|
b9684c277b | ||
|
|
4accb77533 | ||
|
|
9eef961025 | ||
|
|
546b40cdd1 | ||
|
|
274a08c14c | ||
|
|
eed4b51189 | ||
|
|
5c2f84367b | ||
|
|
d5b625e43f | ||
|
|
27f9e3dec9 | ||
|
|
00ef4db9a7 | ||
|
|
bf4f814389 | ||
|
|
23e45afd7f | ||
|
|
bfb02eef62 | ||
|
|
c129aae73a | ||
|
|
a955856cef | ||
|
|
a43a3fa55c | ||
|
|
a6254ab955 | ||
|
|
b505c3dc12 | ||
|
|
9d69b9e980 | ||
|
|
44b2320644 | ||
|
|
8fb9643ea5 | ||
|
|
10ea988298 | ||
|
|
7896945519 | ||
|
|
210342d2bc | ||
|
|
fdacea858b | ||
|
|
6825028d37 | ||
|
|
2089882d41 | ||
|
|
8e1d938155 | ||
|
|
39eb4cef04 | ||
|
|
d4e3adafa6 | ||
|
|
a7b8326499 | ||
|
|
249f8738d3 | ||
|
|
fefe6ef74f | ||
|
|
9ca6cd1d90 | ||
|
|
bff7b7c460 | ||
|
|
cfff79758b | ||
|
|
ded8ceb2c6 | ||
|
|
c502ebfc16 |
@@ -3,3 +3,6 @@
|
||||
|
||||
# Scala Steward: Reformat with scalafmt 3.8.2
|
||||
f1360f44c61f8e12666965c10e79f11cd75d6d30
|
||||
|
||||
# Scala Steward: Reformat with scalafmt 3.9.7
|
||||
a54fb4960ff0762738f4895cdc29bf2715a57f87
|
||||
|
||||
1
.github/CODEOWNERS
vendored
1
.github/CODEOWNERS
vendored
@@ -3,3 +3,4 @@
|
||||
build.sbt @xuwei-k
|
||||
project/* @xuwei-k
|
||||
.github/workflows/* @xuwei-k
|
||||
.scalafmt.conf @xuwei-k
|
||||
|
||||
22
.github/ISSUE_TEMPLATE.md
vendored
22
.github/ISSUE_TEMPLATE.md
vendored
@@ -1,22 +0,0 @@
|
||||
### Before submitting an issue to GitBucket I have first:
|
||||
|
||||
- [ ] read the [contribution guidelines](https://github.com/gitbucket/gitbucket/blob/master/.github/CONTRIBUTING.md)
|
||||
- [ ] searched for similar already existing issue
|
||||
- [ ] read the documentation and [wiki](https://github.com/gitbucket/gitbucket/wiki)
|
||||
|
||||
<!--
|
||||
|
||||
*(if you have performed all the above, remove the paragraph and continue describing the issue with template below)*
|
||||
|
||||
-->
|
||||
|
||||
## Issue
|
||||
**Impacted version**: xxxx
|
||||
|
||||
**Deployment mode**: *explain here how you use GitBucket : standalone app, under webcontainer (which one), with an http frontend (nginx, httpd, ...)*
|
||||
|
||||
**Problem description**:
|
||||
- *be as explicit as you can*
|
||||
- *describe the problem and its symptoms*
|
||||
- *explain how to reproduce*
|
||||
- *attach whatever information that can help understanding the context (screen capture, log files)*
|
||||
1
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
1
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@@ -0,0 +1 @@
|
||||
blank_issues_enabled: false
|
||||
41
.github/ISSUE_TEMPLATE/issue_template.yml
vendored
Normal file
41
.github/ISSUE_TEMPLATE/issue_template.yml
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
name: Report issue
|
||||
description: Report a problem or feature request with GitBucket
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
### Before submitting an issue to GitBucket, please ensure you have:
|
||||
- Read the [contribution guidelines](https://github.com/gitbucket/gitbucket/blob/master/.github/CONTRIBUTING.md)
|
||||
- Searched for similar existing issues
|
||||
- Read the documentation and [wiki](https://github.com/gitbucket/gitbucket/wiki)
|
||||
- You can use [Gitter chat room](https://gitter.im/gitbucket/gitbucket) instead of GitHub Issues for casual discussion or inquiry
|
||||
|
||||
- type: checkboxes
|
||||
id: prerequisites
|
||||
attributes:
|
||||
label: Prerequisites
|
||||
options:
|
||||
- label: I have read the contribution guidelines
|
||||
- label: I have searched for similar issues
|
||||
- label: I have read the documentation and wiki
|
||||
|
||||
- type: input
|
||||
id: impacted_version
|
||||
attributes:
|
||||
label: Impacted version
|
||||
description: Which version of GitBucket is affected?
|
||||
placeholder: e.g. 4.37.0
|
||||
|
||||
- type: input
|
||||
id: deployment_mode
|
||||
attributes:
|
||||
label: Deployment mode
|
||||
description: How do you use GitBucket? (standalone app, under webcontainer, with an HTTP frontend, etc.)
|
||||
placeholder: e.g. Standalone app, Tomcat, nginx
|
||||
|
||||
- type: textarea
|
||||
id: problem_description
|
||||
attributes:
|
||||
label: Problem description
|
||||
description: Be as explicit as you can. Describe the problem, its symptoms, how to reproduce, and attach any relevant information (screenshots, logs, etc.)
|
||||
placeholder: Describe the problem and how to reproduce it
|
||||
10
.github/workflows/build.yml
vendored
10
.github/workflows/build.yml
vendored
@@ -9,11 +9,11 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
java: [17, 21]
|
||||
java: [17, 25]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- name: Cache
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v5
|
||||
env:
|
||||
cache-name: cache-sbt-libs
|
||||
with:
|
||||
@@ -23,7 +23,7 @@ jobs:
|
||||
~/.cache/coursier/v1
|
||||
key: build-${{ env.cache-name }}-${{ hashFiles('build.sbt') }}
|
||||
- name: Set up JDK
|
||||
uses: actions/setup-java@v4
|
||||
uses: actions/setup-java@v5
|
||||
with:
|
||||
java-version: ${{ matrix.java }}
|
||||
distribution: adopt
|
||||
@@ -36,7 +36,7 @@ jobs:
|
||||
- name: Build executable
|
||||
run: sbt executable
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: gitbucket-java${{ matrix.java }}-${{ github.sha }}
|
||||
path: ./target/executable/gitbucket.*
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
version = "3.8.5"
|
||||
version = "3.10.3"
|
||||
project.git = true
|
||||
|
||||
maxColumn = 120
|
||||
|
||||
35
CHANGELOG.md
35
CHANGELOG.md
@@ -1,6 +1,41 @@
|
||||
# Changelog
|
||||
All changes to the project will be documented in this file.
|
||||
|
||||
## 4.45.0 - 11 Jan 2026
|
||||
- Add new option to show full username on UI
|
||||
- Support render plugin in issues, pull requests, wiki and commit comments
|
||||
- Support link to other pages from Wiki page using Wiki link syntax
|
||||
|
||||
## 4.44.0 - 23 Sep 2025
|
||||
- Enhanced branch protection which supports the following settings:
|
||||
- Prevent pushes from non-allowed users
|
||||
- Whether to apply restrictions to administrator users as well
|
||||
- Improve logging for initialization errors
|
||||
|
||||
## 4.43.0 - 29 Jun 2025
|
||||
- Upgrade H2 database from 1.x to 2.x
|
||||
|
||||
Note that upgrading from h2 1.x to 2.x requires data file migration: https://www.h2database.com/html/migration-to-v2.html
|
||||
|
||||
It can't be done automatically using GitBucket's auto migration mechanism because it relies on database itself. So, users who use h2 will have to dump and recreate their database manually with the following steps:
|
||||
```bash
|
||||
# Export database using the current version of H2
|
||||
$ curl -O https://repo1.maven.org/maven2/com/h2database/h2/1.4.199/h2-1.4.199.jar
|
||||
$ java -cp h2-1.4.199.jar org.h2.tools.Script -url "jdbc:h2:~/.gitbucket/data" -user sa -password sa -script dump.sql
|
||||
|
||||
# Recreate database using the new version of H2
|
||||
$ curl -O https://repo1.maven.org/maven2/com/h2database/h2/2.3.232/h2-2.3.232.jar
|
||||
$ java -cp h2-2.3.232.jar org.h2.tools.RunScript -url "jdbc:h2:~/.gitbucket/data" -user sa -password sa -script dump.sql
|
||||
```
|
||||
|
||||
In addition, if `~/.gitbucket/database.conf` has the following configuration, remove `;MVCC=true` from `url`.
|
||||
```
|
||||
db {
|
||||
url = "jdbc:h2:${DatabaseHome};MVCC=true" // => "jdbc:h2:${DatabaseHome}"
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
## 4.42.1 - 20 Jan 2025
|
||||
- Fix LDAP issue with SSL
|
||||
|
||||
|
||||
40
README.md
40
README.md
@@ -44,7 +44,7 @@ GitBucket has a plug-in system that allows extra functionality. Officially the f
|
||||
- [gitbucket-pages-plugin](https://github.com/gitbucket/gitbucket-pages-plugin)
|
||||
- [gitbucket-notifications-plugin](https://github.com/gitbucket/gitbucket-notifications-plugin)
|
||||
|
||||
You can find more plugins made by the community at [GitBucket community plugins](https://gitbucket-plugins.github.io/).
|
||||
You can find more plugins made by the community at [GitBucket community plugins](https://github.com/gitbucket/gitbucket/wiki/Community-Plugins).
|
||||
|
||||
Building and Development
|
||||
-----------
|
||||
@@ -56,19 +56,35 @@ Support
|
||||
--------
|
||||
|
||||
- If you have any questions about GitBucket, see [Wiki](https://github.com/gitbucket/gitbucket/wiki) and check issues whether there is a same question or request in the past.
|
||||
- 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 chat room](https://gitter.im/gitbucket/gitbucket) before raising an issue.
|
||||
- The highest priority of GitBucket is the ease of installation and API compatibility with GitHub, so your feature request might be rejected if they go against those principles.
|
||||
|
||||
What's New in 4.42.x
|
||||
What's New in 4.45.x
|
||||
-------------
|
||||
## 4.42.1 - 20 Jan 2025
|
||||
- Fix LDAP issue with SSL
|
||||
## 4.45.0 - 11 Jan 2026
|
||||
- Add new option to show full username on UI
|
||||
- Support render plugin in issues, pull requests, wiki and commit comments
|
||||
- Support link to other pages from Wiki page using Wiki link syntax
|
||||
|
||||
## 4.42.0 - 30 Dec 2024
|
||||
- Increase max branch name length 100 -> 255
|
||||
- Fix some GitHub incompatible Web APIs
|
||||
- Apply user-defined CSS after all plugins
|
||||
- Improve performance of listing commit logs
|
||||
- Drop Java 11 support. Java 17 is now required
|
||||
Note that you have to migrate h2 database file if you will upgrade GitBucket from 4.42 or before to 4.43 or later and you are using the default h2 database because h2 1.x and h2.x don't have compatibility: https://www.h2database.com/html/migration-to-v2.html
|
||||
|
||||
See the [change log](CHANGELOG.md) for all of the updates.
|
||||
It can't be done automatically using GitBucket's auto migration mechanism because it relies on database itself. So, users who use h2 will have to dump and recreate their database manually with the following steps:
|
||||
```bash
|
||||
# Export database using the current version of H2
|
||||
$ curl -O https://repo1.maven.org/maven2/com/h2database/h2/1.4.199/h2-1.4.199.jar
|
||||
$ java -cp h2-1.4.199.jar org.h2.tools.Script -url "jdbc:h2:~/.gitbucket/data" -user sa -password sa -script dump.sql
|
||||
|
||||
# Recreate database using the new version of H2
|
||||
$ curl -O https://repo1.maven.org/maven2/com/h2database/h2/2.3.232/h2-2.3.232.jar
|
||||
$ java -cp h2-2.3.232.jar org.h2.tools.RunScript -url "jdbc:h2:~/.gitbucket/data" -user sa -password sa -script dump.sql
|
||||
```
|
||||
|
||||
In addition, if `~/.gitbucket/database.conf` has the following configuration, remove `;MVCC=true` from `url`.
|
||||
```
|
||||
db {
|
||||
url = "jdbc:h2:${DatabaseHome};MVCC=true" // => "jdbc:h2:${DatabaseHome}"
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
See the [change log](CHANGELOG.md) for all the past updates.
|
||||
|
||||
86
build.sbt
86
build.sbt
@@ -2,10 +2,10 @@ import com.jsuereth.sbtpgp.PgpKeys._
|
||||
|
||||
val Organization = "io.github.gitbucket"
|
||||
val Name = "gitbucket"
|
||||
val GitBucketVersion = "4.42.1"
|
||||
val ScalatraVersion = "3.1.1"
|
||||
val JettyVersion = "10.0.24"
|
||||
val JgitVersion = "6.10.0.202406032230-r"
|
||||
val GitBucketVersion = "4.45.0"
|
||||
val ScalatraVersion = "3.1.2"
|
||||
val JettyVersion = "10.0.26"
|
||||
val JgitVersion = "6.10.1.202505221210-r"
|
||||
|
||||
lazy val root = (project in file("."))
|
||||
.enablePlugins(SbtTwirl, ContainerPlugin)
|
||||
@@ -14,9 +14,9 @@ sourcesInBase := false
|
||||
organization := Organization
|
||||
name := Name
|
||||
version := GitBucketVersion
|
||||
scalaVersion := "2.13.16"
|
||||
scalaVersion := "2.13.18"
|
||||
|
||||
crossScalaVersions += "3.6.3"
|
||||
crossScalaVersions += "3.7.4"
|
||||
|
||||
// scalafmtOnCompile := true
|
||||
|
||||
@@ -29,45 +29,45 @@ libraryDependencies ++= Seq(
|
||||
"org.scalatra" %% "scalatra-json-javax" % ScalatraVersion,
|
||||
"org.scalatra" %% "scalatra-forms-javax" % ScalatraVersion,
|
||||
"org.json4s" %% "json4s-jackson" % "4.1.0-M8",
|
||||
"commons-io" % "commons-io" % "2.18.0",
|
||||
"commons-io" % "commons-io" % "2.21.0",
|
||||
"io.github.gitbucket" % "solidbase" % "1.1.0",
|
||||
"io.github.gitbucket" % "markedj" % "1.0.20",
|
||||
"org.tukaani" % "xz" % "1.10",
|
||||
"org.apache.commons" % "commons-compress" % "1.27.1",
|
||||
"org.tukaani" % "xz" % "1.11",
|
||||
"org.apache.commons" % "commons-compress" % "1.28.0",
|
||||
"org.apache.commons" % "commons-email" % "1.6.0",
|
||||
"commons-net" % "commons-net" % "3.11.1",
|
||||
"commons-net" % "commons-net" % "3.12.0",
|
||||
"org.apache.httpcomponents" % "httpclient" % "4.5.14",
|
||||
"org.apache.sshd" % "apache-sshd" % "2.14.0" exclude ("org.slf4j", "slf4j-jdk14") exclude (
|
||||
"org.apache.sshd" % "apache-sshd" % "2.16.0" exclude ("org.slf4j", "slf4j-jdk14") exclude (
|
||||
"org.apache.sshd",
|
||||
"sshd-mina"
|
||||
) exclude ("org.apache.sshd", "sshd-netty"),
|
||||
"org.apache.tika" % "tika-core" % "3.0.0",
|
||||
"com.github.takezoe" %% "blocking-slick" % "0.0.14",
|
||||
"com.novell.ldap" % "jldap" % "2009-10-07",
|
||||
"com.h2database" % "h2" % "1.4.199",
|
||||
"org.mariadb.jdbc" % "mariadb-java-client" % "2.7.12",
|
||||
"org.postgresql" % "postgresql" % "42.7.5",
|
||||
"ch.qos.logback" % "logback-classic" % "1.5.16",
|
||||
"com.zaxxer" % "HikariCP" % "6.2.1" exclude ("org.slf4j", "slf4j-api"),
|
||||
"com.typesafe" % "config" % "1.4.3",
|
||||
"fr.brouillard.oss.security.xhub" % "xhub4j-core" % "1.1.0",
|
||||
"io.github.java-diff-utils" % "java-diff-utils" % "4.15",
|
||||
"org.cache2k" % "cache2k-all" % "1.6.0.Final",
|
||||
"net.coobird" % "thumbnailator" % "0.4.20",
|
||||
"com.github.zafarkhaja" % "java-semver" % "0.10.2",
|
||||
"com.nimbusds" % "oauth2-oidc-sdk" % "11.21",
|
||||
"org.eclipse.jetty" % "jetty-webapp" % JettyVersion % "provided",
|
||||
"javax.servlet" % "javax.servlet-api" % "3.1.0" % "provided",
|
||||
"junit" % "junit" % "4.13.2" % "test",
|
||||
"org.scalatra" %% "scalatra-scalatest-javax" % ScalatraVersion % "test",
|
||||
"org.mockito" % "mockito-core" % "5.15.2" % "test",
|
||||
"com.dimafeng" %% "testcontainers-scala" % "0.41.5" % "test",
|
||||
"org.testcontainers" % "mysql" % "1.20.4" % "test",
|
||||
"org.testcontainers" % "postgresql" % "1.20.4" % "test",
|
||||
"net.i2p.crypto" % "eddsa" % "0.3.0",
|
||||
"is.tagomor.woothee" % "woothee-java" % "1.11.0",
|
||||
"org.ec4j.core" % "ec4j-core" % "1.1.0",
|
||||
"org.kohsuke" % "github-api" % "1.326" % "test"
|
||||
) exclude ("org.apache.sshd", "sshd-netty")
|
||||
exclude ("org.apache.sshd", "sshd-spring-sftp"),
|
||||
"org.apache.tika" % "tika-core" % "3.2.3",
|
||||
"com.github.takezoe" %% "blocking-slick" % "0.0.14",
|
||||
"com.novell.ldap" % "jldap" % "2009-10-07",
|
||||
"com.h2database" % "h2" % "2.4.240",
|
||||
"org.mariadb.jdbc" % "mariadb-java-client" % "2.7.13",
|
||||
"org.postgresql" % "postgresql" % "42.7.8",
|
||||
"ch.qos.logback" % "logback-classic" % "1.5.24",
|
||||
"com.zaxxer" % "HikariCP" % "7.0.2" exclude ("org.slf4j", "slf4j-api"),
|
||||
"com.typesafe" % "config" % "1.4.5",
|
||||
"fr.brouillard.oss.security.xhub" % "xhub4j-core" % "1.1.0",
|
||||
"io.github.java-diff-utils" % "java-diff-utils" % "4.16",
|
||||
"org.cache2k" % "cache2k-all" % "1.6.0.Final",
|
||||
"net.coobird" % "thumbnailator" % "0.4.21",
|
||||
"com.github.zafarkhaja" % "java-semver" % "0.10.2",
|
||||
"com.nimbusds" % "oauth2-oidc-sdk" % "11.31",
|
||||
"org.eclipse.jetty" % "jetty-webapp" % JettyVersion % "provided",
|
||||
"javax.servlet" % "javax.servlet-api" % "3.1.0" % "provided",
|
||||
"junit" % "junit" % "4.13.2" % "test",
|
||||
"org.scalatra" %% "scalatra-scalatest-javax" % ScalatraVersion % "test",
|
||||
"org.mockito" % "mockito-core" % "5.21.0" % "test",
|
||||
"org.testcontainers" % "testcontainers-mysql" % "2.0.3" % "test",
|
||||
"org.testcontainers" % "testcontainers-postgresql" % "2.0.3" % "test",
|
||||
"net.i2p.crypto" % "eddsa" % "0.3.0",
|
||||
"is.tagomor.woothee" % "woothee-java" % "1.11.0",
|
||||
"org.ec4j.core" % "ec4j-core" % "1.2.0",
|
||||
"org.kohsuke" % "github-api" % "1.330" % "test"
|
||||
)
|
||||
|
||||
// Compiler settings
|
||||
@@ -191,7 +191,7 @@ executableKey := {
|
||||
|
||||
// zip it up
|
||||
IO delete (temp / "META-INF" / "MANIFEST.MF")
|
||||
val contentMappings = (temp.allPaths --- PathFinder(temp)).get pair { file =>
|
||||
val contentMappings = (temp.allPaths --- PathFinder(temp)).get() pair { file =>
|
||||
IO.relativizeFile(temp, file)
|
||||
}
|
||||
val manifest = new JarManifest
|
||||
@@ -215,9 +215,9 @@ executableKey := {
|
||||
outputFile
|
||||
}
|
||||
publishTo := {
|
||||
val nexus = "https://oss.sonatype.org/"
|
||||
if (version.value.trim.endsWith("SNAPSHOT")) Some("snapshots" at nexus + "content/repositories/snapshots")
|
||||
else Some("releases" at nexus + "service/local/staging/deploy/maven2")
|
||||
val centralSnapshots = "https://central.sonatype.com/repository/maven-snapshots/"
|
||||
if (isSnapshot.value) Some("central-snapshots" at centralSnapshots)
|
||||
else localStaging.value
|
||||
}
|
||||
publishMavenStyle := true
|
||||
pomIncludeRepository := { _ =>
|
||||
|
||||
@@ -12,7 +12,7 @@ javaOptions in Jetty ++= Seq(
|
||||
Run GitBucket:
|
||||
|
||||
```shell
|
||||
$ sbt ~jetty:start
|
||||
$ sbt ~container:start
|
||||
```
|
||||
|
||||
In IntelliJ, create remote debug configuration as follows. Make sure port number is same as above configuration.
|
||||
|
||||
@@ -38,15 +38,26 @@ Generate release files
|
||||
|
||||
For plug-in development, we have to publish the GitBucket jar file to the Maven central repository before release GitBucket itself.
|
||||
|
||||
First, hit following command to publish artifacts to the sonatype OSS repository:
|
||||
First, stage artifacts on your machine:
|
||||
|
||||
```bash
|
||||
$ sbt publishSigned
|
||||
```
|
||||
|
||||
Then logged-in to https://oss.sonatype.org/, close and release the repository.
|
||||
Next, upload artifacts to Sonatype's Central Portal with the following command:
|
||||
|
||||
You need to wait up to a day until [gitbucket-notification-plugin](https://plugins.gitbucket-community.org/) which is default bundled plugin is built for new version of GitBucket.
|
||||
```bash
|
||||
$ sbt sonaUpload
|
||||
```
|
||||
|
||||
Then logged-in to https://central.sonatype.com/ and publish the deployment.
|
||||
|
||||
You need to wait up to a day until default bundled plugins:
|
||||
|
||||
- https://github.com/gitbucket/gitbucket-notifications-plugin
|
||||
- https://github.com/gitbucket/gitbucket-gist-plugin
|
||||
- https://github.com/gitbucket/gitbucket-pages-plugin
|
||||
- https://github.com/gitbucket/gitbucket-emoji-plugin
|
||||
|
||||
### Make release war file
|
||||
|
||||
@@ -55,5 +66,4 @@ Run `sbt executable`. The release war file and fingerprint are generated into `t
|
||||
```bash
|
||||
$ sbt executable
|
||||
```
|
||||
|
||||
Create new release from the corresponded tag on GitHub, then upload generated jar file and fingerprints to the release.
|
||||
|
||||
@@ -1 +1 @@
|
||||
sbt.version=1.10.7
|
||||
sbt.version=1.12.0
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
scalacOptions ++= Seq("-unchecked", "-deprecation", "-feature")
|
||||
|
||||
addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.5.4")
|
||||
addSbtPlugin("org.playframework.twirl" % "sbt-twirl" % "2.0.7")
|
||||
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "2.3.0")
|
||||
addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.5.6")
|
||||
addSbtPlugin("org.playframework.twirl" % "sbt-twirl" % "2.0.9")
|
||||
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "2.3.1")
|
||||
addSbtPlugin("org.scalatra.sbt" % "sbt-scalatra" % "1.0.4")
|
||||
addSbtPlugin("com.github.sbt" % "sbt-pgp" % "2.3.1")
|
||||
addSbtPlugin("com.github.sbt" % "sbt-license-report" % "1.7.0")
|
||||
addSbtPlugin("org.scoverage" % "sbt-scoverage" % "2.3.0")
|
||||
addSbtPlugin("com.github.sbt" % "sbt-license-report" % "1.9.0")
|
||||
addSbtPlugin("org.scoverage" % "sbt-scoverage" % "2.4.3")
|
||||
|
||||
addDependencyTreePlugin
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
notifications:1.11.0
|
||||
gist:4.23.0
|
||||
gist:4.24.0
|
||||
emoji:4.6.0
|
||||
pages:1.10.0
|
||||
|
||||
32
src/main/resources/update/gitbucket-core_4.44.xml
Normal file
32
src/main/resources/update/gitbucket-core_4.44.xml
Normal file
@@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<changeSet>
|
||||
<!--================================================================================================-->
|
||||
<!-- PROTECTED_BRANCH -->
|
||||
<!--================================================================================================-->
|
||||
<addColumn tableName="PROTECTED_BRANCH">
|
||||
<column name="REQUIRED_STATUS_CHECK" type="boolean" nullable="false" defaultValue="false"/>
|
||||
<column name="RESTRICTIONS" type="boolean" nullable="false" defaultValue="false"/>
|
||||
</addColumn>
|
||||
|
||||
<sql>
|
||||
UPDATE PROTECTED_BRANCH SET REQUIRED_STATUS_CHECK = TRUE
|
||||
WHERE EXISTS (SELECT * FROM PROTECTED_BRANCH_REQUIRE_CONTEXT
|
||||
WHERE PROTECTED_BRANCH.USER_NAME = PROTECTED_BRANCH_REQUIRE_CONTEXT.USER_NAME
|
||||
AND PROTECTED_BRANCH.REPOSITORY_NAME = PROTECTED_BRANCH_REQUIRE_CONTEXT.REPOSITORY_NAME
|
||||
AND PROTECTED_BRANCH.BRANCH = PROTECTED_BRANCH_REQUIRE_CONTEXT.BRANCH)
|
||||
</sql>
|
||||
|
||||
<!--================================================================================================-->
|
||||
<!-- PROTECTED_BRANCH_RESTRICTIONS_USER -->
|
||||
<!--================================================================================================-->
|
||||
<createTable tableName="PROTECTED_BRANCH_RESTRICTION">
|
||||
<column name="USER_NAME" type="varchar(100)" nullable="false"/>
|
||||
<column name="REPOSITORY_NAME" type="varchar(100)" nullable="false"/>
|
||||
<column name="BRANCH" type="varchar(100)" nullable="false"/>
|
||||
<column name="ALLOWED_USER" type="varchar(255)" nullable="false"/>
|
||||
</createTable>
|
||||
|
||||
<addPrimaryKey constraintName="IDX_PROTECTED_BRANCH_RESTRICTION_PK" tableName="PROTECTED_BRANCH_RESTRICTION" columnNames="USER_NAME, REPOSITORY_NAME, BRANCH, ALLOWED_USER"/>
|
||||
<addForeignKeyConstraint constraintName="IDX_PROTECTED_BRANCH_RESTRICTION_FK0" baseTableName="PROTECTED_BRANCH_RESTRICTION" baseColumnNames="USER_NAME, REPOSITORY_NAME, BRANCH" referencedTableName="PROTECTED_BRANCH" referencedColumnNames="USER_NAME, REPOSITORY_NAME, BRANCH" onDelete="CASCADE" onUpdate="CASCADE"/>
|
||||
<addForeignKeyConstraint constraintName="IDX_PROTECTED_BRANCH_RESTRICTION_FK1" baseTableName="PROTECTED_BRANCH_RESTRICTION" baseColumnNames="ALLOWED_USER" referencedTableName="ACCOUNT" referencedColumnNames="USER_NAME"/>
|
||||
</changeSet>
|
||||
@@ -119,7 +119,10 @@ object GitBucketCoreModule
|
||||
new Version("4.40.0"),
|
||||
new Version("4.41.0"),
|
||||
new Version("4.42.0", new LiquibaseMigration("update/gitbucket-core_4.42.xml")),
|
||||
new Version("4.42.1")
|
||||
new Version("4.42.1"),
|
||||
new Version("4.43.0"),
|
||||
new Version("4.44.0", new LiquibaseMigration("update/gitbucket-core_4.44.xml")),
|
||||
new Version("4.45.0")
|
||||
) {
|
||||
java.util.logging.Logger.getLogger("liquibase").setLevel(Level.SEVERE)
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import gitbucket.core.util.RepositoryName
|
||||
* https://developer.github.com/v3/repos/#get-branch
|
||||
* https://developer.github.com/v3/repos/#enabling-and-disabling-branch-protection
|
||||
*/
|
||||
case class ApiBranch(name: String, commit: ApiBranchCommit, protection: ApiBranchProtection)(
|
||||
case class ApiBranch(name: String, commit: ApiBranchCommit, protection: ApiBranchProtectionResponse)(
|
||||
repositoryName: RepositoryName
|
||||
) extends FieldSerializable {
|
||||
val _links =
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
package gitbucket.core.api
|
||||
|
||||
/** https://developer.github.com/v3/repos/#enabling-and-disabling-branch-protection */
|
||||
case class ApiBranchProtectionRequest(
|
||||
enabled: Boolean,
|
||||
required_status_checks: Option[ApiBranchProtectionRequest.Status],
|
||||
restrictions: Option[ApiBranchProtectionRequest.Restrictions],
|
||||
enforce_admins: Option[Boolean]
|
||||
)
|
||||
|
||||
object ApiBranchProtectionRequest {
|
||||
|
||||
/** form for enabling-and-disabling-branch-protection */
|
||||
case class EnablingAndDisabling(protection: ApiBranchProtectionRequest)
|
||||
|
||||
case class Status(
|
||||
contexts: Seq[String]
|
||||
)
|
||||
|
||||
case class Restrictions(users: Seq[String])
|
||||
}
|
||||
@@ -4,55 +4,68 @@ import gitbucket.core.service.ProtectedBranchService
|
||||
import org.json4s._
|
||||
|
||||
/** https://developer.github.com/v3/repos/#enabling-and-disabling-branch-protection */
|
||||
case class ApiBranchProtection(
|
||||
case class ApiBranchProtectionResponse(
|
||||
url: Option[ApiPath], // for output
|
||||
enabled: Boolean,
|
||||
required_status_checks: Option[ApiBranchProtection.Status]
|
||||
required_status_checks: Option[ApiBranchProtectionResponse.Status],
|
||||
restrictions: Option[ApiBranchProtectionResponse.Restrictions],
|
||||
enforce_admins: Option[ApiBranchProtectionResponse.EnforceAdmins]
|
||||
) {
|
||||
def status: ApiBranchProtection.Status = required_status_checks.getOrElse(ApiBranchProtection.statusNone)
|
||||
def status: ApiBranchProtectionResponse.Status =
|
||||
required_status_checks.getOrElse(ApiBranchProtectionResponse.statusNone)
|
||||
}
|
||||
|
||||
object ApiBranchProtection {
|
||||
object ApiBranchProtectionResponse {
|
||||
|
||||
/** form for enabling-and-disabling-branch-protection */
|
||||
case class EnablingAndDisabling(protection: ApiBranchProtection)
|
||||
case class EnforceAdmins(enabled: Boolean)
|
||||
|
||||
def apply(info: ProtectedBranchService.ProtectedBranchInfo): ApiBranchProtection =
|
||||
ApiBranchProtection(
|
||||
// /** form for enabling-and-disabling-branch-protection */
|
||||
// case class EnablingAndDisabling(protection: ApiBranchProtectionResponse)
|
||||
|
||||
def apply(info: ProtectedBranchService.ProtectedBranchInfo): ApiBranchProtectionResponse =
|
||||
ApiBranchProtectionResponse(
|
||||
url = Some(
|
||||
ApiPath(
|
||||
s"/api/v3/repos/${info.owner}/${info.repository}/branches/${info.branch}/protection"
|
||||
)
|
||||
),
|
||||
enabled = info.enabled,
|
||||
required_status_checks = Some(
|
||||
required_status_checks = info.contexts.map { contexts =>
|
||||
Status(
|
||||
Some(
|
||||
ApiPath(
|
||||
s"/api/v3/repos/${info.owner}/${info.repository}/branches/${info.branch}/protection/required_status_checks"
|
||||
)
|
||||
),
|
||||
EnforcementLevel(info.enabled && info.contexts.nonEmpty, info.includeAdministrators),
|
||||
info.contexts,
|
||||
EnforcementLevel(info.enabled && info.contexts.nonEmpty, info.enforceAdmins),
|
||||
contexts,
|
||||
Some(
|
||||
ApiPath(
|
||||
s"/api/v3/repos/${info.owner}/${info.repository}/branches/${info.branch}/protection/required_status_checks/contexts"
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
},
|
||||
restrictions = info.restrictionsUsers.map { restrictionsUsers =>
|
||||
Restrictions(restrictionsUsers)
|
||||
},
|
||||
enforce_admins = if (info.enabled) Some(EnforceAdmins(info.enforceAdmins)) else None
|
||||
)
|
||||
val statusNone = Status(None, Off, Seq.empty, None)
|
||||
|
||||
val statusNone: Status = Status(None, Off, Seq.empty, None)
|
||||
|
||||
case class Status(
|
||||
url: Option[ApiPath], // for output
|
||||
enforcement_level: EnforcementLevel,
|
||||
contexts: Seq[String],
|
||||
contexts_url: Option[ApiPath] // for output
|
||||
)
|
||||
|
||||
sealed class EnforcementLevel(val name: String)
|
||||
case object Off extends EnforcementLevel("off")
|
||||
case object NonAdmins extends EnforcementLevel("non_admins")
|
||||
case object Everyone extends EnforcementLevel("everyone")
|
||||
|
||||
object EnforcementLevel {
|
||||
def apply(enabled: Boolean, includeAdministrators: Boolean): EnforcementLevel =
|
||||
if (enabled) {
|
||||
@@ -66,6 +79,8 @@ object ApiBranchProtection {
|
||||
}
|
||||
}
|
||||
|
||||
case class Restrictions(users: Seq[String])
|
||||
|
||||
implicit val enforcementLevelSerializer: CustomSerializer[EnforcementLevel] =
|
||||
new CustomSerializer[EnforcementLevel](format =>
|
||||
(
|
||||
@@ -44,7 +44,7 @@ object JsonFormat {
|
||||
FieldSerializer[ApiCommits.File]() +
|
||||
FieldSerializer[ApiRelease]() +
|
||||
FieldSerializer[ApiReleaseAsset]() +
|
||||
ApiBranchProtection.enforcementLevelSerializer
|
||||
ApiBranchProtectionResponse.enforcementLevelSerializer
|
||||
|
||||
def apiPathSerializer(c: Context) =
|
||||
new CustomSerializer[ApiPath](_ =>
|
||||
|
||||
@@ -351,7 +351,7 @@ case class Context(
|
||||
val path: String = settings.baseUrl.getOrElse(request.getContextPath)
|
||||
val currentPath: String = request.getRequestURI.substring(request.getContextPath.length)
|
||||
val baseUrl: String = settings.baseUrl(request)
|
||||
val host: String = new java.net.URL(baseUrl).getHost
|
||||
val host: String = new java.net.URI(baseUrl).toURL.getHost
|
||||
val platform: String = request.getHeader("User-Agent") match {
|
||||
case null => null
|
||||
case agent if agent.contains("Mac") => "mac"
|
||||
|
||||
@@ -13,8 +13,8 @@ import gitbucket.core.util.*
|
||||
import gitbucket.core.view.helpers.*
|
||||
import org.scalatra.Ok
|
||||
import org.scalatra.forms.*
|
||||
|
||||
import gitbucket.core.service.ActivityService.*
|
||||
import gitbucket.core.view.helpers
|
||||
|
||||
class IndexController
|
||||
extends IndexControllerBase
|
||||
@@ -92,10 +92,18 @@ trait IndexControllerBase extends ControllerBase {
|
||||
}
|
||||
}
|
||||
|
||||
get("/_is_renderable") {
|
||||
helpers.isRenderable(params("filename"))
|
||||
}
|
||||
|
||||
get("/signin") {
|
||||
val redirect = params.get("redirect")
|
||||
if (redirect.isDefined && redirect.get.startsWith("/")) {
|
||||
flash.update(Keys.Flash.Redirect, redirect.get)
|
||||
if (context.loginAccount.nonEmpty) {
|
||||
redirect("/")
|
||||
}
|
||||
params.get("redirect").foreach { redirect =>
|
||||
if (redirect.startsWith("/")) {
|
||||
flash.update(Keys.Flash.Redirect, redirect)
|
||||
}
|
||||
}
|
||||
gitbucket.core.html.signin(flash.get("userName"), flash.get("password"), flash.get("error"))
|
||||
}
|
||||
@@ -243,13 +251,22 @@ trait IndexControllerBase extends ControllerBase {
|
||||
}
|
||||
}
|
||||
.map { t =>
|
||||
Map(
|
||||
"label" -> s"${avatar(t.userName, 16)}<b>@${StringUtil.escapeHtml(
|
||||
StringUtil.cutTail(t.userName, 25, "...")
|
||||
)}</b> ${StringUtil
|
||||
.escapeHtml(StringUtil.cutTail(t.fullName, 25, "..."))}",
|
||||
"value" -> t.userName
|
||||
)
|
||||
if (t.isGroupAccount) {
|
||||
Map(
|
||||
"label" -> s"${avatar(t.userName, 16)} <b>@${StringUtil.escapeHtml(
|
||||
StringUtil.cutTail(t.userName, 25, "...")
|
||||
)}</b>",
|
||||
"value" -> t.userName
|
||||
)
|
||||
} else {
|
||||
Map(
|
||||
"label" -> s"${avatar(t.userName, 16)} <b>@${StringUtil.escapeHtml(
|
||||
StringUtil.cutTail(t.userName, 25, "...")
|
||||
)}</b> (${StringUtil
|
||||
.escapeHtml(StringUtil.cutTail(t.fullName, 25, "..."))})",
|
||||
"value" -> t.userName
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
)
|
||||
@@ -257,11 +274,22 @@ trait IndexControllerBase extends ControllerBase {
|
||||
|
||||
/**
|
||||
* JSON API for checking user or group existence.
|
||||
*
|
||||
* Returns a single string which is any of "group", "user" or "".
|
||||
* Additionally, check whether the user is writable to the repository
|
||||
* if "owner" and "repository" are given,
|
||||
*/
|
||||
post("/_user/existence")(usersOnly {
|
||||
getAccountByUserNameIgnoreCase(params("userName")).map { account =>
|
||||
if (account.isGroupAccount) "group" else "user"
|
||||
if (!account.isGroupAccount && params.get("repository").isDefined && params.get("owner").isDefined) {
|
||||
getRepository(params("owner"), params("repository"))
|
||||
.collect {
|
||||
case repository if isWritable(repository.repository, Some(account)) => "user"
|
||||
}
|
||||
.getOrElse("")
|
||||
} else {
|
||||
if (account.isGroupAccount) "group" else "user"
|
||||
}
|
||||
} getOrElse ""
|
||||
})
|
||||
|
||||
|
||||
@@ -6,8 +6,7 @@ import gitbucket.core.service.IssuesService.*
|
||||
import gitbucket.core.service.*
|
||||
import gitbucket.core.util.Implicits.*
|
||||
import gitbucket.core.util.*
|
||||
import gitbucket.core.view
|
||||
import gitbucket.core.view.Markdown
|
||||
import gitbucket.core.view.helpers
|
||||
import org.scalatra.forms.*
|
||||
import org.scalatra.{BadRequest, Ok}
|
||||
|
||||
@@ -271,20 +270,24 @@ trait IssuesControllerBase extends ControllerBase {
|
||||
case t if t == "html" => html.editissue(x.content, x.issueId, repository)
|
||||
} getOrElse {
|
||||
contentType = formats("json")
|
||||
val content = helpers
|
||||
.renderMarkup(
|
||||
filePath = List("temporary.md"),
|
||||
fileContent = x.content getOrElse "No description given.",
|
||||
branch = repository.repository.defaultBranch,
|
||||
repository = repository,
|
||||
enableWikiLink = false,
|
||||
enableRefsLink = true,
|
||||
enableAnchor = true,
|
||||
enableLineBreaks = true,
|
||||
enableTaskList = true,
|
||||
hasWritePermission = true
|
||||
)
|
||||
.toString()
|
||||
org.json4s.jackson.Serialization.write(
|
||||
Map(
|
||||
"title" -> x.title,
|
||||
"content" -> Markdown.toHtml(
|
||||
markdown = x.content getOrElse "No description given.",
|
||||
repository = repository,
|
||||
branch = repository.repository.defaultBranch,
|
||||
enableWikiLink = false,
|
||||
enableRefsLink = true,
|
||||
enableAnchor = true,
|
||||
enableLineBreaks = true,
|
||||
enableTaskList = true,
|
||||
hasWritePermission = true
|
||||
)
|
||||
"content" -> content
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -301,19 +304,23 @@ trait IssuesControllerBase extends ControllerBase {
|
||||
case t if t == "html" => html.editcomment(x.content, x.commentId, repository)
|
||||
} getOrElse {
|
||||
contentType = formats("json")
|
||||
val content = helpers
|
||||
.renderMarkup(
|
||||
filePath = List("temporary.md"),
|
||||
fileContent = x.content,
|
||||
branch = repository.repository.defaultBranch,
|
||||
repository = repository,
|
||||
enableWikiLink = false,
|
||||
enableRefsLink = true,
|
||||
enableAnchor = true,
|
||||
enableLineBreaks = true,
|
||||
enableTaskList = true,
|
||||
hasWritePermission = true
|
||||
)
|
||||
.toString()
|
||||
org.json4s.jackson.Serialization.write(
|
||||
Map(
|
||||
"content" -> view.Markdown.toHtml(
|
||||
markdown = x.content,
|
||||
repository = repository,
|
||||
branch = repository.repository.defaultBranch,
|
||||
enableWikiLink = false,
|
||||
enableRefsLink = true,
|
||||
enableAnchor = true,
|
||||
enableLineBreaks = true,
|
||||
enableTaskList = true,
|
||||
hasWritePermission = true
|
||||
)
|
||||
"content" -> content
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ trait PreProcessControllerBase extends ControllerBase {
|
||||
if (
|
||||
!context.currentPath.startsWith("/assets") && !context.currentPath.startsWith("/signin") &&
|
||||
!context.currentPath.startsWith("/register") && !context.currentPath.endsWith("/info/refs") &&
|
||||
!context.currentPath.startsWith("/plugin-assets") &&
|
||||
!context.currentPath.startsWith("/plugin-assets") && !context.currentPath.equals("/user.css") &&
|
||||
!PluginRegistry().getAnonymousAccessiblePaths().exists { path =>
|
||||
context.currentPath.startsWith(path)
|
||||
}
|
||||
|
||||
@@ -203,7 +203,7 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
||||
if (!repository.branchList.contains(branch)) {
|
||||
redirect(s"/${repository.owner}/${repository.name}/settings/branches")
|
||||
} else {
|
||||
val protection = ApiBranchProtection(getProtectedBranchInfo(repository.owner, repository.name, branch))
|
||||
val protection = ApiBranchProtectionResponse(getProtectedBranchInfo(repository.owner, repository.name, branch))
|
||||
val lastWeeks = getRecentStatusContexts(
|
||||
repository.owner,
|
||||
repository.name,
|
||||
@@ -628,7 +628,7 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
||||
new Constraint() {
|
||||
override def validate(name: String, value: String, messages: Messages): Option[String] =
|
||||
getAccountByUserName(value) match {
|
||||
case None => Some("User does not exist.")
|
||||
case None => Some("User does not exist.")
|
||||
case Some(x) =>
|
||||
if (x.userName == params("owner")) {
|
||||
Some("This is current repository owner.")
|
||||
|
||||
@@ -23,8 +23,7 @@ import org.apache.commons.compress.archivers.zip.{ZipArchiveEntry, ZipArchiveOut
|
||||
import org.apache.commons.compress.compressors.bzip2.BZip2CompressorOutputStream
|
||||
import org.apache.commons.compress.compressors.gzip.GzipCompressorOutputStream
|
||||
import org.apache.commons.compress.compressors.xz.XZCompressorOutputStream
|
||||
import org.apache.commons.compress.utils.IOUtils
|
||||
import org.apache.commons.io.FileUtils
|
||||
import org.apache.commons.io.{FileUtils, IOUtils}
|
||||
import org.scalatra.forms.*
|
||||
import org.eclipse.jgit.api.{ArchiveCommand, Git}
|
||||
import org.eclipse.jgit.archive.{TgzFormat, ZipFormat}
|
||||
@@ -35,7 +34,7 @@ import org.eclipse.jgit.treewalk.TreeWalk.OperationType
|
||||
import org.eclipse.jgit.treewalk.filter.PathFilter
|
||||
import org.eclipse.jgit.util.io.EolStreamTypeUtil
|
||||
import org.json4s.jackson.Serialization
|
||||
import org.scalatra._
|
||||
import org.scalatra.*
|
||||
import org.scalatra.i18n.Messages
|
||||
|
||||
class RepositoryViewerController
|
||||
@@ -170,31 +169,19 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
*/
|
||||
post("/:owner/:repository/_preview")(referrersOnly { repository =>
|
||||
contentType = "text/html"
|
||||
val filename = params.get("filename")
|
||||
filename match {
|
||||
case Some(f) =>
|
||||
helpers.renderMarkup(
|
||||
filePath = List(f),
|
||||
fileContent = params("content"),
|
||||
branch = repository.repository.defaultBranch,
|
||||
repository = repository,
|
||||
enableWikiLink = params("enableWikiLink").toBoolean,
|
||||
enableRefsLink = params("enableRefsLink").toBoolean,
|
||||
enableAnchor = false
|
||||
)
|
||||
case None =>
|
||||
helpers.markdown(
|
||||
markdown = params("content"),
|
||||
repository = repository,
|
||||
branch = repository.repository.defaultBranch,
|
||||
enableWikiLink = params("enableWikiLink").toBoolean,
|
||||
enableRefsLink = params("enableRefsLink").toBoolean,
|
||||
enableLineBreaks = params("enableLineBreaks").toBoolean,
|
||||
enableTaskList = params("enableTaskList").toBoolean,
|
||||
enableAnchor = false,
|
||||
hasWritePermission = hasDeveloperRole(repository.owner, repository.name, context.loginAccount)
|
||||
)
|
||||
}
|
||||
val filename = params.get("filename").getOrElse("temporary.md")
|
||||
helpers.renderMarkup(
|
||||
filePath = filename.split("/").toList,
|
||||
fileContent = params("content"),
|
||||
branch = repository.repository.defaultBranch,
|
||||
repository = repository,
|
||||
enableWikiLink = params("enableWikiLink").toBoolean,
|
||||
enableRefsLink = params("enableRefsLink").toBoolean,
|
||||
enableLineBreaks = params("enableLineBreaks").toBoolean,
|
||||
enableTaskList = params("enableTaskList").toBoolean,
|
||||
enableAnchor = false,
|
||||
hasWritePermission = hasDeveloperRole(repository.owner, repository.name, context.loginAccount)
|
||||
)
|
||||
})
|
||||
|
||||
/**
|
||||
@@ -897,19 +884,23 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
case t if t == "html" => html.editcomment(x.content, x.commentId, repository)
|
||||
} getOrElse {
|
||||
contentType = formats("json")
|
||||
val content = helpers
|
||||
.renderMarkup(
|
||||
filePath = List("temporary.md"),
|
||||
fileContent = x.content,
|
||||
branch = repository.repository.defaultBranch,
|
||||
repository = repository,
|
||||
enableWikiLink = false,
|
||||
enableRefsLink = true,
|
||||
enableAnchor = true,
|
||||
enableLineBreaks = true,
|
||||
enableTaskList = true,
|
||||
hasWritePermission = true
|
||||
)
|
||||
.toString()
|
||||
org.json4s.jackson.Serialization.write(
|
||||
Map(
|
||||
"content" -> view.Markdown.toHtml(
|
||||
markdown = x.content,
|
||||
repository = repository,
|
||||
branch = repository.repository.defaultBranch,
|
||||
enableWikiLink = false,
|
||||
enableRefsLink = true,
|
||||
enableAnchor = true,
|
||||
enableLineBreaks = true,
|
||||
enableTaskList = true,
|
||||
hasWritePermission = true
|
||||
)
|
||||
"content" -> content
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -1076,14 +1067,9 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
redirect(s"${repository.owner}/${repository.name}/releases")
|
||||
})
|
||||
|
||||
get("/:owner/:repository/archive/:name")(referrersOnly { repository =>
|
||||
val name = params("name")
|
||||
archiveRepository(name, repository, "")
|
||||
})
|
||||
|
||||
get("/:owner/:repository/archive/*/:name")(referrersOnly { repository =>
|
||||
val name = params("name")
|
||||
val path = multiParams("splat").head
|
||||
get("/:owner/:repository/archive/*")(referrersOnly { repository =>
|
||||
val name = multiParams("splat").mkString("/")
|
||||
val path = params.get("path").getOrElse("")
|
||||
archiveRepository(name, repository, path)
|
||||
})
|
||||
|
||||
|
||||
@@ -125,7 +125,8 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
|
||||
"maxDiffFiles" -> trim(label("Max diff files", number(required))),
|
||||
"maxDiffLines" -> trim(label("Max diff lines", number(required)))
|
||||
)(RepositoryViewerSettings.apply),
|
||||
"defaultBranch" -> trim(label("Default branch", text(required)))
|
||||
"defaultBranch" -> trim(label("Default branch", text(required))),
|
||||
"showFullName" -> trim(label("Show full name", boolean()))
|
||||
)(SystemSettings.apply).verifying { settings =>
|
||||
Vector(
|
||||
if (settings.ssh.enabled && settings.baseUrl.isEmpty) {
|
||||
|
||||
@@ -306,7 +306,8 @@ trait WikiControllerBase extends ControllerBase {
|
||||
|
||||
get("/:owner/:repository/wiki/_history")(referrersOnly { repository =>
|
||||
Using.resource(Git.open(getWikiRepositoryDir(repository.owner, repository.name))) { git =>
|
||||
JGitUtil.getCommitLog(git, "master") match {
|
||||
val branch = getWikiBranch(repository.owner, repository.name)
|
||||
JGitUtil.getCommitLog(git, branch) match {
|
||||
case Right((logs, hasNext)) => html.history(None, logs, repository, isEditable(repository))
|
||||
case Left(_) => NotFound()
|
||||
}
|
||||
@@ -316,7 +317,8 @@ trait WikiControllerBase extends ControllerBase {
|
||||
get("/:owner/:repository/wiki/_blob/*")(referrersOnly { repository =>
|
||||
val path = multiParams("splat").head
|
||||
Using.resource(Git.open(getWikiRepositoryDir(repository.owner, repository.name))) { git =>
|
||||
val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve("master"))
|
||||
val branch = getWikiBranch(repository.owner, repository.name)
|
||||
val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(branch))
|
||||
|
||||
getPathObjectId(git, path, revCommit).map { objectId =>
|
||||
responseRawFile(git, objectId, path, repository)
|
||||
|
||||
@@ -138,7 +138,7 @@ trait ApiGitReferenceControllerBase extends ControllerBase {
|
||||
|
||||
val name = RepositoryName(repository)
|
||||
val result = JsonFormat(revstr match {
|
||||
case "tags" => repository.tags.map(ApiRef.fromTag(name, _))
|
||||
case "tags" => repository.tags.map(ApiRef.fromTag(name, _))
|
||||
case x if x.startsWith("tags/") =>
|
||||
val tagName = x.substring("tags/".length)
|
||||
repository.tags.find(_.name == tagName) match {
|
||||
|
||||
@@ -29,7 +29,7 @@ trait ApiIssueMilestoneControllerBase extends ControllerBase {
|
||||
)
|
||||
}).reverse
|
||||
state match {
|
||||
case "all" => JsonFormat(apiMilestones)
|
||||
case "all" => JsonFormat(apiMilestones)
|
||||
case "open" | "closed" =>
|
||||
JsonFormat(
|
||||
apiMilestones.filter(p => p.state == state)
|
||||
|
||||
@@ -43,7 +43,9 @@ trait ApiRepositoryBranchControllerBase extends ControllerBase {
|
||||
} yield {
|
||||
val protection = getProtectedBranchInfo(repository.owner, repository.name, branch)
|
||||
JsonFormat(
|
||||
ApiBranch(branch, ApiBranchCommit(br.commitId), ApiBranchProtection(protection))(RepositoryName(repository))
|
||||
ApiBranch(branch, ApiBranchCommit(br.commitId), ApiBranchProtectionResponse(protection))(
|
||||
RepositoryName(repository)
|
||||
)
|
||||
)
|
||||
}) getOrElse NotFound()
|
||||
}
|
||||
@@ -58,7 +60,7 @@ trait ApiRepositoryBranchControllerBase extends ControllerBase {
|
||||
if (repository.branchList.contains(branch)) {
|
||||
val protection = getProtectedBranchInfo(repository.owner, repository.name, branch)
|
||||
JsonFormat(
|
||||
ApiBranchProtection(protection)
|
||||
ApiBranchProtectionResponse(protection)
|
||||
)
|
||||
} else { NotFound() }
|
||||
})
|
||||
@@ -138,7 +140,7 @@ trait ApiRepositoryBranchControllerBase extends ControllerBase {
|
||||
if (repository.branchList.contains(branch)) {
|
||||
val protection = getProtectedBranchInfo(repository.owner, repository.name, branch)
|
||||
JsonFormat(
|
||||
ApiBranchProtection(protection).required_status_checks
|
||||
ApiBranchProtectionResponse(protection).required_status_checks
|
||||
)
|
||||
} else { NotFound() }
|
||||
})
|
||||
@@ -262,7 +264,7 @@ trait ApiRepositoryBranchControllerBase extends ControllerBase {
|
||||
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
|
||||
(for {
|
||||
branch <- params.get("splat") if repository.branchList.contains(branch)
|
||||
protection <- extractFromJsonBody[ApiBranchProtection.EnablingAndDisabling].map(_.protection)
|
||||
protection <- extractFromJsonBody[ApiBranchProtectionRequest.EnablingAndDisabling].map(_.protection)
|
||||
br <- getBranchesNoMergeInfo(git).find(_.name == branch)
|
||||
} yield {
|
||||
if (protection.enabled) {
|
||||
@@ -270,13 +272,17 @@ trait ApiRepositoryBranchControllerBase extends ControllerBase {
|
||||
repository.owner,
|
||||
repository.name,
|
||||
branch,
|
||||
protection.status.enforcement_level == ApiBranchProtection.Everyone,
|
||||
protection.status.contexts
|
||||
protection.enforce_admins.getOrElse(false),
|
||||
protection.required_status_checks.isDefined,
|
||||
protection.required_status_checks.map(_.contexts).getOrElse(Nil),
|
||||
protection.restrictions.isDefined,
|
||||
protection.restrictions.map(_.users).getOrElse(Nil)
|
||||
)
|
||||
} else {
|
||||
disableBranchProtection(repository.owner, repository.name, branch)
|
||||
}
|
||||
JsonFormat(ApiBranch(branch, ApiBranchCommit(br.commitId), protection)(RepositoryName(repository)))
|
||||
val response = ApiBranchProtectionResponse(getProtectedBranchInfo(repository.owner, repository.name, branch))
|
||||
JsonFormat(ApiBranch(branch, ApiBranchCommit(br.commitId), response)(RepositoryName(repository)))
|
||||
}) getOrElse NotFound()
|
||||
}
|
||||
})
|
||||
|
||||
@@ -90,7 +90,17 @@ trait ApiRepositoryContentsControllerBase extends ControllerBase {
|
||||
path,
|
||||
"\" id=\"file\">",
|
||||
"<article>",
|
||||
renderMarkup(path.split("/").toList, new String(c), refStr, repository, false, false, true).body,
|
||||
renderMarkup(
|
||||
filePath = path.split("/").toList,
|
||||
fileContent = new String(c),
|
||||
branch = refStr,
|
||||
repository = repository,
|
||||
enableWikiLink = false,
|
||||
enableRefsLink = false,
|
||||
enableAnchor = false,
|
||||
enableLineBreaks = true,
|
||||
enableTaskList = true
|
||||
).body,
|
||||
"</article>",
|
||||
"</div>"
|
||||
).mkString
|
||||
@@ -142,10 +152,11 @@ trait ApiRepositoryContentsControllerBase extends ControllerBase {
|
||||
val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(branch))
|
||||
revCommit.name
|
||||
}
|
||||
val paths = multiParams("splat").head.split("/")
|
||||
val fullPath = multiParams("splat").head
|
||||
val paths = fullPath.split("/")
|
||||
val path = paths.take(paths.size - 1).toList.mkString("/")
|
||||
Using.resource(Git.open(getRepositoryDir(params("owner"), params("repository")))) { git =>
|
||||
val fileInfo = getFileInfo(git, commit, path, false)
|
||||
val fileInfo = getFileInfo(git, commit, fullPath, ignoreCase = false)
|
||||
|
||||
fileInfo match {
|
||||
case Some(f) if !data.sha.contains(f.id.getName) =>
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
package gitbucket.core.model
|
||||
|
||||
trait ProtectedBranchComponent extends TemplateComponent { self: Profile =>
|
||||
import profile.api._
|
||||
import self._
|
||||
import profile.api.*
|
||||
|
||||
lazy val ProtectedBranches = TableQuery[ProtectedBranches]
|
||||
class ProtectedBranches(tag: Tag) extends Table[ProtectedBranch](tag, "PROTECTED_BRANCH") with BranchTemplate {
|
||||
val statusCheckAdmin = column[Boolean]("STATUS_CHECK_ADMIN")
|
||||
def * = (userName, repositoryName, branch, statusCheckAdmin).mapTo[ProtectedBranch]
|
||||
def byPrimaryKey(userName: String, repositoryName: String, branch: String) =
|
||||
val statusCheckAdmin = column[Boolean]("STATUS_CHECK_ADMIN") // enforceAdmins
|
||||
val requiredStatusCheck = column[Boolean]("REQUIRED_STATUS_CHECK")
|
||||
val restrictions = column[Boolean]("RESTRICTIONS")
|
||||
def * =
|
||||
(userName, repositoryName, branch, statusCheckAdmin, requiredStatusCheck, restrictions).mapTo[ProtectedBranch]
|
||||
def byPrimaryKey(userName: String, repositoryName: String, branch: String): Rep[Boolean] =
|
||||
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]): Rep[Boolean] =
|
||||
byBranch(userName, repositoryName, branch)
|
||||
}
|
||||
|
||||
@@ -22,8 +24,27 @@ trait ProtectedBranchComponent extends TemplateComponent { self: Profile =>
|
||||
def * =
|
||||
(userName, repositoryName, branch, context).mapTo[ProtectedBranchContext]
|
||||
}
|
||||
|
||||
lazy val ProtectedBranchRestrictions = TableQuery[ProtectedBranchRestrictions]
|
||||
class ProtectedBranchRestrictions(tag: Tag)
|
||||
extends Table[ProtectedBranchRestriction](tag, "PROTECTED_BRANCH_RESTRICTION")
|
||||
with BranchTemplate {
|
||||
val allowedUser = column[String]("ALLOWED_USER")
|
||||
def * = (userName, repositoryName, branch, allowedUser).mapTo[ProtectedBranchRestriction]
|
||||
def byPrimaryKey(userName: String, repositoryName: String, branch: String, allowedUser: String): Rep[Boolean] =
|
||||
this.userName === userName.bind && this.repositoryName === repositoryName.bind && this.branch === branch.bind && this.allowedUser === allowedUser.bind
|
||||
}
|
||||
}
|
||||
|
||||
case class ProtectedBranch(userName: String, repositoryName: String, branch: String, statusCheckAdmin: Boolean)
|
||||
case class ProtectedBranch(
|
||||
userName: String,
|
||||
repositoryName: String,
|
||||
branch: String,
|
||||
enforceAdmins: Boolean,
|
||||
requiredStatusCheck: Boolean,
|
||||
restrictions: Boolean
|
||||
)
|
||||
|
||||
case class ProtectedBranchContext(userName: String, repositoryName: String, branch: String, context: String)
|
||||
|
||||
case class ProtectedBranchRestriction(userName: String, repositoryName: String, branch: String, allowedUser: String)
|
||||
|
||||
@@ -15,7 +15,7 @@ import gitbucket.core.service.RepositoryService.RepositoryInfo
|
||||
import gitbucket.core.service.SystemSettingsService
|
||||
import gitbucket.core.service.SystemSettingsService.SystemSettings
|
||||
import gitbucket.core.util.{ConfigUtil, DatabaseConfig}
|
||||
import gitbucket.core.util.Directory._
|
||||
import gitbucket.core.util.Directory.*
|
||||
import io.github.gitbucket.solidbase.Solidbase
|
||||
import io.github.gitbucket.solidbase.manager.JDBCVersionManager
|
||||
import io.github.gitbucket.solidbase.model.Module
|
||||
@@ -25,7 +25,7 @@ import org.apache.sshd.server.command.Command
|
||||
import org.slf4j.LoggerFactory
|
||||
import play.twirl.api.Html
|
||||
|
||||
import scala.jdk.CollectionConverters._
|
||||
import scala.jdk.CollectionConverters.*
|
||||
|
||||
class PluginRegistry {
|
||||
|
||||
@@ -233,29 +233,29 @@ object PluginRegistry {
|
||||
override def accept(dir: File, name: String): Boolean = name.endsWith(".jar")
|
||||
})
|
||||
.toSeq
|
||||
.sortBy(x => Version.valueOf(getPluginVersion(x.getName)))
|
||||
.sortBy(x => Version.parse(getPluginVersion(x.getName)))
|
||||
.reverse
|
||||
}
|
||||
|
||||
lazy val extraPluginDir: Option[String] = ConfigUtil.getConfigValue[String]("gitbucket.pluginDir")
|
||||
private lazy val extraPluginDir: Option[String] = ConfigUtil.getConfigValue[String]("gitbucket.pluginDir")
|
||||
|
||||
def getGitBucketVersion(pluginJarFileName: String): Option[String] = {
|
||||
val regex = ".+-gitbucket\\_(\\d+\\.\\d+\\.\\d+(-SNAPSHOT)?)-.+".r
|
||||
private def getGitBucketVersion(pluginJarFileName: String): Option[String] = {
|
||||
val regex = ".+-gitbucket_(\\d+\\.\\d+\\.\\d+(-SNAPSHOT)?)-.+".r
|
||||
pluginJarFileName match {
|
||||
case regex(all, _) => Some(all)
|
||||
case _ => None
|
||||
}
|
||||
}
|
||||
|
||||
def getPluginVersion(pluginJarFileName: String): String = {
|
||||
private def getPluginVersion(pluginJarFileName: String): String = {
|
||||
val regex = ".+-((\\d+)\\.(\\d+)(\\.(\\d+))?(-SNAPSHOT)?)\\.jar$".r
|
||||
pluginJarFileName match {
|
||||
case regex(all, major, minor, _, patch, modifier) => {
|
||||
if (patch != null) all
|
||||
else {
|
||||
case regex(all, major, minor, _, patch, modifier) =>
|
||||
if (patch != null) {
|
||||
all
|
||||
} else {
|
||||
s"${major}.${minor}.0" + (if (modifier == null) "" else modifier)
|
||||
}
|
||||
}
|
||||
case _ => "0.0.0"
|
||||
}
|
||||
}
|
||||
@@ -295,11 +295,10 @@ object PluginRegistry {
|
||||
|
||||
// Check duplication
|
||||
instance.getPlugins().find(_.pluginId == pluginId) match {
|
||||
case Some(x) => {
|
||||
case Some(x) =>
|
||||
logger.warn(s"Plugin ${pluginId} is duplicated. ${x.pluginJar.getName} is available.")
|
||||
classLoader.close()
|
||||
}
|
||||
case None => {
|
||||
case None =>
|
||||
// Migration
|
||||
val solidbase = new Solidbase()
|
||||
solidbase
|
||||
@@ -334,7 +333,6 @@ object PluginRegistry {
|
||||
classLoader = classLoader
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
case e: Throwable =>
|
||||
@@ -369,9 +367,8 @@ object PluginRegistry {
|
||||
extraWatcher = null
|
||||
}
|
||||
} catch {
|
||||
case e: Exception => {
|
||||
case e: Exception =>
|
||||
logger.error(s"Error during plugin shutdown: ${plugin.pluginJar.getName}", e)
|
||||
}
|
||||
} finally {
|
||||
plugin.classLoader.close()
|
||||
}
|
||||
@@ -437,7 +434,7 @@ class PluginWatchThread(context: ServletContext, dir: String) extends Thread wit
|
||||
logger.info("Start PluginWatchThread: " + path)
|
||||
|
||||
try {
|
||||
while (watchKey.isValid()) {
|
||||
while (watchKey.isValid) {
|
||||
val detectedWatchKey = watcher.take()
|
||||
val events = detectedWatchKey.pollEvents.asScala.filter { e =>
|
||||
e.context.toString != ".installed" && !e.context.toString.endsWith(".bak")
|
||||
|
||||
@@ -20,7 +20,7 @@ trait Renderer {
|
||||
|
||||
object MarkdownRenderer extends Renderer {
|
||||
override def render(request: RenderRequest): Html = {
|
||||
import request._
|
||||
import request.*
|
||||
Html(
|
||||
Markdown.toHtml(
|
||||
markdown = fileContent,
|
||||
@@ -29,9 +29,9 @@ object MarkdownRenderer extends Renderer {
|
||||
enableWikiLink = enableWikiLink,
|
||||
enableRefsLink = enableRefsLink,
|
||||
enableAnchor = enableAnchor,
|
||||
enableLineBreaks = false,
|
||||
enableTaskList = true,
|
||||
hasWritePermission = false
|
||||
enableLineBreaks = enableLineBreaks,
|
||||
enableTaskList = enableTaskList,
|
||||
hasWritePermission = hasWritePermission
|
||||
)(context)
|
||||
)
|
||||
}
|
||||
@@ -51,5 +51,8 @@ case class RenderRequest(
|
||||
enableWikiLink: Boolean,
|
||||
enableRefsLink: Boolean,
|
||||
enableAnchor: Boolean,
|
||||
enableLineBreaks: Boolean,
|
||||
enableTaskList: Boolean,
|
||||
hasWritePermission: Boolean,
|
||||
context: Context
|
||||
)
|
||||
|
||||
@@ -59,7 +59,7 @@ trait AccountFederationService {
|
||||
.orElse(extractSafeStringForUserName(mailAddress)) match {
|
||||
case Some(safeUserName) =>
|
||||
getAccountByUserName(safeUserName, includeRemoved = true) match {
|
||||
case None => Some(safeUserName)
|
||||
case None => Some(safeUserName)
|
||||
case Some(_) =>
|
||||
logger.info(
|
||||
s"User ($safeUserName) already exists. preferredUserName=$preferredUserName, mailAddress=$mailAddress"
|
||||
|
||||
@@ -46,7 +46,7 @@ trait AccountService {
|
||||
case account if !account.isGroupAccount =>
|
||||
account.password match {
|
||||
case pbkdf2re(iter, salt, hash) if (pbkdf2_sha256(iter.toInt, salt, password) == hash) => Some(account)
|
||||
case p if p == sha1(password) =>
|
||||
case p if p == sha1(password) =>
|
||||
updateAccount(account.copy(password = pbkdf2_sha256(password)))
|
||||
Some(account)
|
||||
case _ => None
|
||||
|
||||
@@ -62,7 +62,7 @@ trait HandleCommentService {
|
||||
.getOrElse(None -> None)
|
||||
|
||||
val commentId = (content, action) match {
|
||||
case (None, None) => None
|
||||
case (None, None) => None
|
||||
case (None, Some(action)) =>
|
||||
Some(createComment(owner, name, userName, issue.issueId, action.capitalize, action))
|
||||
case (Some(content), _) =>
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
package gitbucket.core.service
|
||||
|
||||
import gitbucket.core.model.{Session => _, _}
|
||||
import gitbucket.core.plugin.ReceiveHook
|
||||
import gitbucket.core.model.Profile._
|
||||
import gitbucket.core.model.Profile.profile.blockingApi._
|
||||
import gitbucket.core.model.Profile.*
|
||||
import gitbucket.core.model.Profile.profile.blockingApi.*
|
||||
import gitbucket.core.model.{CommitState, ProtectedBranch, ProtectedBranchContext, ProtectedBranchRestriction, Role}
|
||||
import gitbucket.core.util.SyntaxSugars.*
|
||||
import org.eclipse.jgit.transport.{ReceiveCommand, ReceivePack}
|
||||
|
||||
trait ProtectedBranchService {
|
||||
@@ -13,17 +14,27 @@ trait ProtectedBranchService {
|
||||
): Option[ProtectedBranchInfo] =
|
||||
ProtectedBranches
|
||||
.joinLeft(ProtectedBranchContexts)
|
||||
.on { case (pb, c) => pb.byBranch(c.userName, c.repositoryName, c.branch) }
|
||||
.map { case (pb, c) => pb -> c.map(_.context) }
|
||||
.on { case pb ~ c => pb.byBranch(c.userName, c.repositoryName, c.branch) }
|
||||
.joinLeft(ProtectedBranchRestrictions)
|
||||
.on { case pb ~ c ~ r => pb.byBranch(r.userName, r.repositoryName, r.branch) }
|
||||
.map { case pb ~ c ~ r => pb -> (c.map(_.context), r.map(_.allowedUser)) }
|
||||
.filter(_._1.byPrimaryKey(owner, repository, branch))
|
||||
.list
|
||||
.groupBy(_._1)
|
||||
.headOption
|
||||
.map { p =>
|
||||
p._1 -> p._2.flatMap(_._2)
|
||||
.map { (p: (ProtectedBranch, List[(ProtectedBranch, (Option[String], Option[String]))])) =>
|
||||
p._1 -> (p._2.flatMap(_._2._1), p._2.flatMap(_._2._2))
|
||||
}
|
||||
.map { case (t1, contexts) =>
|
||||
new ProtectedBranchInfo(t1.userName, t1.repositoryName, t1.branch, true, contexts, t1.statusCheckAdmin)
|
||||
.map { case (t1, (contexts, users)) =>
|
||||
new ProtectedBranchInfo(
|
||||
t1.userName,
|
||||
t1.repositoryName,
|
||||
t1.branch,
|
||||
true,
|
||||
if (t1.requiredStatusCheck) Some(contexts) else None,
|
||||
t1.enforceAdmins,
|
||||
if (t1.restrictions) Some(users) else None
|
||||
)
|
||||
}
|
||||
|
||||
def getProtectedBranchInfo(owner: String, repository: String, branch: String)(implicit
|
||||
@@ -40,19 +51,32 @@ trait ProtectedBranchService {
|
||||
owner: String,
|
||||
repository: String,
|
||||
branch: String,
|
||||
includeAdministrators: Boolean,
|
||||
contexts: Seq[String]
|
||||
enforceAdmins: Boolean,
|
||||
requiredStatusCheck: Boolean,
|
||||
contexts: Seq[String],
|
||||
restrictions: Boolean,
|
||||
restrictionsUsers: Seq[String]
|
||||
)(implicit session: Session): Unit = {
|
||||
disableBranchProtection(owner, repository, branch)
|
||||
ProtectedBranches.insert(new ProtectedBranch(owner, repository, branch, includeAdministrators && contexts.nonEmpty))
|
||||
contexts.map { context =>
|
||||
ProtectedBranchContexts.insert(new ProtectedBranchContext(owner, repository, branch, context))
|
||||
ProtectedBranches.insert(
|
||||
ProtectedBranch(owner, repository, branch, enforceAdmins, requiredStatusCheck, restrictions)
|
||||
)
|
||||
|
||||
if (restrictions) {
|
||||
restrictionsUsers.foreach { user =>
|
||||
ProtectedBranchRestrictions.insert(ProtectedBranchRestriction(owner, repository, branch, user))
|
||||
}
|
||||
}
|
||||
|
||||
if (requiredStatusCheck) {
|
||||
contexts.foreach { context =>
|
||||
ProtectedBranchContexts.insert(ProtectedBranchContext(owner, repository, branch, context))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def disableBranchProtection(owner: String, repository: String, branch: String)(implicit session: Session): Unit =
|
||||
ProtectedBranches.filter(_.byPrimaryKey(owner, repository, branch)).delete
|
||||
|
||||
}
|
||||
|
||||
object ProtectedBranchService {
|
||||
@@ -101,6 +125,7 @@ object ProtectedBranchService {
|
||||
)
|
||||
}
|
||||
} else {
|
||||
println("-> else")
|
||||
None
|
||||
}
|
||||
}
|
||||
@@ -117,12 +142,16 @@ object ProtectedBranchService {
|
||||
* When enabled, commits must first be pushed to another branch,
|
||||
* then merged or pushed directly to test after status checks have passed.
|
||||
*/
|
||||
contexts: Seq[String],
|
||||
contexts: Option[Seq[String]],
|
||||
/**
|
||||
* Include administrators
|
||||
* Enforce required status checks for repository administrators.
|
||||
*/
|
||||
includeAdministrators: Boolean
|
||||
enforceAdmins: Boolean,
|
||||
/**
|
||||
* Users who can push to the branch.
|
||||
*/
|
||||
restrictionsUsers: Option[Seq[String]]
|
||||
) extends AccountService
|
||||
with RepositoryService
|
||||
with CommitStatusService {
|
||||
@@ -148,42 +177,66 @@ object ProtectedBranchService {
|
||||
session: Session
|
||||
): Option[String] = {
|
||||
if (enabled) {
|
||||
command.getType() match {
|
||||
command.getType match {
|
||||
case ReceiveCommand.Type.UPDATE_NONFASTFORWARD if isAllowNonFastForwards =>
|
||||
Some("Cannot force-push to a protected branch")
|
||||
case ReceiveCommand.Type.UPDATE | ReceiveCommand.Type.UPDATE_NONFASTFORWARD if !isPushAllowed(pusher) =>
|
||||
Some("You do not have permission to push to this branch")
|
||||
case ReceiveCommand.Type.UPDATE | ReceiveCommand.Type.UPDATE_NONFASTFORWARD if needStatusCheck(pusher) =>
|
||||
unSuccessedContexts(command.getNewId.name) match {
|
||||
case s if s.sizeIs == 1 => Some(s"""Required status check "${s.toSeq(0)}" is expected""")
|
||||
case s if s.sizeIs >= 1 => Some(s"${s.size} of ${contexts.size} required status checks are expected")
|
||||
case _ => None
|
||||
case s if s.sizeIs == 1 => Some(s"""Required status check "${s.head}" is expected""")
|
||||
case s if s.sizeIs >= 1 =>
|
||||
Some(s"${s.size} of ${contexts.map(_.size).getOrElse(0)} required status checks are expected")
|
||||
case _ => None
|
||||
}
|
||||
case ReceiveCommand.Type.DELETE =>
|
||||
Some("Cannot delete a protected branch")
|
||||
Some("You do not have permission to push to this branch")
|
||||
case _ => None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
def unSuccessedContexts(sha1: String)(implicit session: Session): Set[String] =
|
||||
if (contexts.isEmpty) {
|
||||
Set.empty
|
||||
} else {
|
||||
contexts.toSet -- getCommitStatuses(owner, repository, sha1)
|
||||
.filter(_.state == CommitState.SUCCESS)
|
||||
.map(_.context)
|
||||
.toSet
|
||||
|
||||
def unSuccessedContexts(sha1: String)(implicit session: Session): Set[String] = {
|
||||
contexts match {
|
||||
case None => Set.empty
|
||||
case Some(x) if x.isEmpty => Set.empty
|
||||
case Some(x) =>
|
||||
x.toSet -- getCommitStatuses(owner, repository, sha1)
|
||||
.filter(_.state == CommitState.SUCCESS)
|
||||
.map(_.context)
|
||||
.toSet
|
||||
}
|
||||
}
|
||||
|
||||
def needStatusCheck(pusher: String)(implicit session: Session): Boolean = pusher match {
|
||||
case _ if !enabled => false
|
||||
case _ if contexts.isEmpty => false
|
||||
case _ if includeAdministrators => true
|
||||
case p if isAdministrator(p) => false
|
||||
case _ => true
|
||||
case _ if !enabled => false
|
||||
case _ if contexts.isEmpty => false
|
||||
case _ if enforceAdmins => true
|
||||
case p if isAdministrator(p) => false
|
||||
case _ => true
|
||||
}
|
||||
|
||||
def isPushAllowed(pusher: String)(implicit session: Session): Boolean = pusher match {
|
||||
case _ if !enabled || restrictionsUsers.isEmpty => true
|
||||
case _ if restrictionsUsers.get.contains(pusher) => true
|
||||
case p if isAdministrator(p) && enforceAdmins => false
|
||||
case _ => false
|
||||
}
|
||||
}
|
||||
|
||||
object ProtectedBranchInfo {
|
||||
def disabled(owner: String, repository: String, branch: String): ProtectedBranchInfo =
|
||||
ProtectedBranchInfo(owner, repository, branch, false, Nil, false)
|
||||
def disabled(owner: String, repository: String, branch: String): ProtectedBranchInfo = {
|
||||
ProtectedBranchInfo(
|
||||
owner,
|
||||
repository,
|
||||
branch,
|
||||
enabled = false,
|
||||
contexts = None,
|
||||
enforceAdmins = false,
|
||||
restrictionsUsers = None
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -427,7 +427,7 @@ trait PullRequestService {
|
||||
case Some(patch) =>
|
||||
file -> comments.foreach { case (commentId, lineNumber) =>
|
||||
lineNumber match {
|
||||
case Left(oldLine) => updateCommitCommentPosition(commentId, newCommitId, Some(oldLine), None)
|
||||
case Left(oldLine) => updateCommitCommentPosition(commentId, newCommitId, Some(oldLine), None)
|
||||
case Right(newLine) =>
|
||||
var counter = newLine
|
||||
patch.getDeltas.asScala.filter(_.getSource.getPosition < newLine).foreach { delta =>
|
||||
@@ -653,18 +653,18 @@ object PullRequestService {
|
||||
commitIdTo: String
|
||||
) {
|
||||
|
||||
val hasConflict = conflictMessage.isDefined
|
||||
val hasConflict: Boolean = conflictMessage.isDefined
|
||||
val statuses: List[CommitStatus] =
|
||||
commitStatuses ++ (branchProtection.contexts.toSet -- commitStatuses.map(_.context).toSet)
|
||||
commitStatuses ++ (branchProtection.contexts.getOrElse(Nil).toSet -- commitStatuses.map(_.context).toSet)
|
||||
.map(CommitStatus.pending(branchProtection.owner, branchProtection.repository, _))
|
||||
val hasRequiredStatusProblem = needStatusCheck && branchProtection.contexts.exists(context =>
|
||||
statuses.find(_.context == context).map(_.state) != Some(CommitState.SUCCESS)
|
||||
)
|
||||
val hasProblem = hasRequiredStatusProblem || hasConflict || (statuses.nonEmpty && CommitState.combine(
|
||||
val hasRequiredStatusProblem: Boolean = needStatusCheck && branchProtection.contexts
|
||||
.getOrElse(Nil)
|
||||
.exists(context => !statuses.find(_.context == context).map(_.state).contains(CommitState.SUCCESS))
|
||||
val hasProblem: Boolean = hasRequiredStatusProblem || hasConflict || (statuses.nonEmpty && CommitState.combine(
|
||||
statuses.map(_.state).toSet
|
||||
) != CommitState.SUCCESS)
|
||||
val canUpdate = branchIsOutOfDate && !hasConflict
|
||||
val canMerge = hasMergePermission && !hasConflict && !hasRequiredStatusProblem
|
||||
val canUpdate: Boolean = branchIsOutOfDate && !hasConflict
|
||||
val canMerge: Boolean = hasMergePermission && !hasConflict && !hasRequiredStatusProblem
|
||||
lazy val commitStateSummary: (CommitState, String) = {
|
||||
val stateMap = statuses.groupBy(_.state)
|
||||
val state = CommitState.combine(stateMap.keySet)
|
||||
@@ -672,8 +672,8 @@ object PullRequestService {
|
||||
state -> summary
|
||||
}
|
||||
lazy val statusesAndRequired: List[(CommitStatus, Boolean)] = statuses.map { s =>
|
||||
s -> branchProtection.contexts.contains(s.context)
|
||||
s -> branchProtection.contexts.getOrElse(Nil).contains(s.context)
|
||||
}
|
||||
lazy val isAllSuccess = commitStateSummary._1 == CommitState.SUCCESS
|
||||
lazy val isAllSuccess: Boolean = commitStateSummary._1 == CommitState.SUCCESS
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package gitbucket.core.service
|
||||
import gitbucket.core.api.JsonFormat
|
||||
import gitbucket.core.model.{Account, WebHook}
|
||||
import gitbucket.core.model.Profile.profile.blockingApi._
|
||||
import gitbucket.core.model.Profile.profile.blockingApi.*
|
||||
import gitbucket.core.model.activity.{CloseIssueInfo, PushInfo}
|
||||
import gitbucket.core.plugin.PluginRegistry
|
||||
import gitbucket.core.service.SystemSettingsService.SystemSettings
|
||||
@@ -11,14 +11,14 @@ import gitbucket.core.util.JGitUtil.CommitInfo
|
||||
import gitbucket.core.util.{JGitUtil, LockUtil}
|
||||
import org.eclipse.jgit.api.Git
|
||||
import org.eclipse.jgit.dircache.{DirCache, DirCacheBuilder}
|
||||
import org.eclipse.jgit.lib._
|
||||
import org.eclipse.jgit.lib.*
|
||||
import org.eclipse.jgit.transport.{ReceiveCommand, ReceivePack}
|
||||
|
||||
import scala.util.Using
|
||||
|
||||
trait RepositoryCommitFileService {
|
||||
self: AccountService & ActivityService & IssuesService & PullRequestService & WebHookPullRequestService &
|
||||
RepositoryService =>
|
||||
RepositoryService & ProtectedBranchService =>
|
||||
|
||||
/**
|
||||
* Create multiple files by callback function.
|
||||
@@ -92,10 +92,10 @@ trait RepositoryCommitFileService {
|
||||
)(implicit s: Session, c: JsonFormat.Context): Either[String, (ObjectId, Option[ObjectId])] = {
|
||||
|
||||
val newPath = newFileName.map { newFileName =>
|
||||
if (path.length == 0) newFileName else s"${path}/${newFileName}"
|
||||
if (path.isEmpty) newFileName else s"${path}/${newFileName}"
|
||||
}
|
||||
val oldPath = oldFileName.map { oldFileName =>
|
||||
if (path.length == 0) oldFileName else s"${path}/${oldFileName}"
|
||||
if (path.isEmpty) oldFileName else s"${path}/${oldFileName}"
|
||||
}
|
||||
|
||||
_createFiles(repository, branch, message, pusherAccount, committerName, committerMailAddress, settings) {
|
||||
@@ -139,7 +139,6 @@ trait RepositoryCommitFileService {
|
||||
)(
|
||||
f: (Git, ObjectId, DirCacheBuilder, ObjectInserter) => R
|
||||
)(implicit s: Session, c: JsonFormat.Context): Either[String, (ObjectId, R)] = {
|
||||
|
||||
LockUtil.lock(s"${repository.owner}/${repository.name}") {
|
||||
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
|
||||
val builder = DirCache.newInCore.builder()
|
||||
@@ -168,7 +167,14 @@ trait RepositoryCommitFileService {
|
||||
|
||||
// call pre-commit hook
|
||||
val error = PluginRegistry().getReceiveHooks.flatMap { hook =>
|
||||
hook.preReceive(repository.owner, repository.name, receivePack, receiveCommand, pusherAccount.userName, false)
|
||||
hook.preReceive(
|
||||
repository.owner,
|
||||
repository.name,
|
||||
receivePack,
|
||||
receiveCommand,
|
||||
pusherAccount.userName,
|
||||
mergePullRequest = false
|
||||
)
|
||||
}.headOption
|
||||
|
||||
error match {
|
||||
@@ -194,7 +200,8 @@ trait RepositoryCommitFileService {
|
||||
// record activity
|
||||
updateLastActivityDate(repository.owner, repository.name)
|
||||
val commitInfo = new CommitInfo(JGitUtil.getRevCommitFromId(git, commitId))
|
||||
val pushInfo = PushInfo(repository.owner, repository.name, pusherAccount.userName, branch, List(commitInfo))
|
||||
val pushInfo =
|
||||
PushInfo(repository.owner, repository.name, pusherAccount.userName, branch, List(commitInfo))
|
||||
recordActivity(pushInfo)
|
||||
|
||||
// create issue comment by commit message
|
||||
@@ -221,7 +228,14 @@ trait RepositoryCommitFileService {
|
||||
|
||||
// call post-commit hook
|
||||
PluginRegistry().getReceiveHooks.foreach { hook =>
|
||||
hook.postReceive(repository.owner, repository.name, receivePack, receiveCommand, committerName, false)
|
||||
hook.postReceive(
|
||||
repository.owner,
|
||||
repository.name,
|
||||
receivePack,
|
||||
receiveCommand,
|
||||
committerName,
|
||||
mergePullRequest = false
|
||||
)
|
||||
}
|
||||
|
||||
val commit = new JGitUtil.CommitInfo(JGitUtil.getRevCommitFromId(git, commitId))
|
||||
|
||||
@@ -654,11 +654,11 @@ trait RepositoryService {
|
||||
|
||||
def hasOwnerRole(owner: String, repository: String, loginAccount: Option[Account])(implicit s: Session): Boolean = {
|
||||
loginAccount match {
|
||||
case Some(a) if (a.isAdmin) => true
|
||||
case Some(a) if (a.userName == owner) => true
|
||||
case Some(a) if (getGroupMembers(owner).exists(_.userName == a.userName)) => true
|
||||
case Some(a) if (getCollaboratorUserNames(owner, repository, Seq(Role.ADMIN)).contains(a.userName)) => true
|
||||
case _ => false
|
||||
case Some(a) if a.isAdmin => true
|
||||
case Some(a) if a.userName == owner => true
|
||||
case Some(a) if getGroupMembers(owner).exists(_.userName == a.userName) => true
|
||||
case Some(a) if getCollaboratorUserNames(owner, repository, Seq(Role.ADMIN)).contains(a.userName) => true
|
||||
case _ => false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -666,11 +666,11 @@ trait RepositoryService {
|
||||
s: Session
|
||||
): Boolean = {
|
||||
loginAccount match {
|
||||
case Some(a) if (a.isAdmin) => true
|
||||
case Some(a) if (a.userName == owner) => true
|
||||
case Some(a) if (getGroupMembers(owner).exists(_.userName == a.userName)) => true
|
||||
case Some(a) if a.isAdmin => true
|
||||
case Some(a) if a.userName == owner => true
|
||||
case Some(a) if getGroupMembers(owner).exists(_.userName == a.userName) => true
|
||||
case Some(a)
|
||||
if (getCollaboratorUserNames(owner, repository, Seq(Role.ADMIN, Role.DEVELOPER)).contains(a.userName)) =>
|
||||
if getCollaboratorUserNames(owner, repository, Seq(Role.ADMIN, Role.DEVELOPER)).contains(a.userName) =>
|
||||
true
|
||||
case _ => false
|
||||
}
|
||||
@@ -678,12 +678,12 @@ trait RepositoryService {
|
||||
|
||||
def hasGuestRole(owner: String, repository: String, loginAccount: Option[Account])(implicit s: Session): Boolean = {
|
||||
loginAccount match {
|
||||
case Some(a) if (a.isAdmin) => true
|
||||
case Some(a) if (a.userName == owner) => true
|
||||
case Some(a) if (getGroupMembers(owner).exists(_.userName == a.userName)) => true
|
||||
case Some(a) if a.isAdmin => true
|
||||
case Some(a) if a.userName == owner => true
|
||||
case Some(a) if getGroupMembers(owner).exists(_.userName == a.userName) => true
|
||||
case Some(a)
|
||||
if (getCollaboratorUserNames(owner, repository, Seq(Role.ADMIN, Role.DEVELOPER, Role.GUEST))
|
||||
.contains(a.userName)) =>
|
||||
if getCollaboratorUserNames(owner, repository, Seq(Role.ADMIN, Role.DEVELOPER, Role.GUEST))
|
||||
.contains(a.userName) =>
|
||||
true
|
||||
case _ => false
|
||||
}
|
||||
@@ -694,17 +694,29 @@ trait RepositoryService {
|
||||
true
|
||||
} else {
|
||||
loginAccount match {
|
||||
case Some(x) if (x.isAdmin) => true
|
||||
case Some(x) if (repository.userName == x.userName) => true
|
||||
case Some(x) if (getGroupMembers(repository.userName).exists(_.userName == x.userName)) => true
|
||||
case Some(x)
|
||||
if (getCollaboratorUserNames(repository.userName, repository.repositoryName).contains(x.userName)) =>
|
||||
case Some(x) if x.isAdmin => true
|
||||
case Some(x) if repository.userName == x.userName => true
|
||||
case Some(x) if getGroupMembers(repository.userName).exists(_.userName == x.userName) => true
|
||||
case Some(x) if getCollaboratorUserNames(repository.userName, repository.repositoryName).contains(x.userName) =>
|
||||
true
|
||||
case _ => false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def isWritable(repository: Repository, loginAccount: Option[Account])(implicit s: Session): Boolean = {
|
||||
loginAccount match {
|
||||
case Some(x) if x.isAdmin => true
|
||||
case Some(x) if repository.userName == x.userName => true
|
||||
case Some(x) if getGroupMembers(repository.userName).exists(_.userName == x.userName) => true
|
||||
case Some(x)
|
||||
if getCollaboratorUserNames(repository.userName, repository.repositoryName, Seq(Role.ADMIN, Role.DEVELOPER))
|
||||
.contains(x.userName) =>
|
||||
true
|
||||
case _ => false
|
||||
}
|
||||
}
|
||||
|
||||
private def getForkedCount(userName: String, repositoryName: String)(implicit s: Session): Int =
|
||||
Query(Repositories.filter { t =>
|
||||
(t.originUserName === userName.bind) && (t.originRepositoryName === repositoryName.bind)
|
||||
|
||||
@@ -93,6 +93,7 @@ trait SystemSettingsService {
|
||||
props.setProperty(RepositoryViewerMaxDiffFiles, settings.repositoryViewer.maxDiffFiles.toString)
|
||||
props.setProperty(RepositoryViewerMaxDiffLines, settings.repositoryViewer.maxDiffLines.toString)
|
||||
props.setProperty(DefaultBranch, settings.defaultBranch)
|
||||
props.setProperty(ShowFullName, settings.showFullName.toString)
|
||||
|
||||
Using.resource(new java.io.FileOutputStream(GitBucketConf)) { out =>
|
||||
props.store(out, null)
|
||||
@@ -211,7 +212,8 @@ trait SystemSettingsService {
|
||||
getValue(props, RepositoryViewerMaxDiffFiles, 100),
|
||||
getValue(props, RepositoryViewerMaxDiffLines, 1000)
|
||||
),
|
||||
getValue(props, DefaultBranch, "main")
|
||||
getValue(props, DefaultBranch, "main"),
|
||||
getValue(props, ShowFullName, false)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -238,7 +240,8 @@ object SystemSettingsService {
|
||||
webHook: WebHook,
|
||||
upload: Upload,
|
||||
repositoryViewer: RepositoryViewerSettings,
|
||||
defaultBranch: String
|
||||
defaultBranch: String,
|
||||
showFullName: Boolean
|
||||
) {
|
||||
def baseUrl(request: HttpServletRequest): String =
|
||||
baseUrl.getOrElse(parseBaseUrl(request)).stripSuffix("/")
|
||||
@@ -457,6 +460,7 @@ object SystemSettingsService {
|
||||
private val RepositoryViewerMaxDiffFiles = "repository_viewer_max_diff_files"
|
||||
private val RepositoryViewerMaxDiffLines = "repository_viewer_max_diff_lines"
|
||||
private val DefaultBranch = "default_branch"
|
||||
private val ShowFullName = "show_full_name"
|
||||
|
||||
private def getValue[A: ClassTag](props: java.util.Properties, key: String, default: A): A = {
|
||||
getConfigValue(key).getOrElse {
|
||||
|
||||
@@ -265,7 +265,7 @@ trait WebHookService {
|
||||
}
|
||||
|
||||
private def validateTargetAddress(settings: SystemSettings, url: String): Boolean = {
|
||||
val host = new java.net.URL(url).getHost
|
||||
val host = new java.net.URI(url).toURL.getHost
|
||||
|
||||
!settings.webHook.blockPrivateAddress ||
|
||||
!HttpClientUtil.isPrivateAddress(host) ||
|
||||
|
||||
@@ -25,8 +25,8 @@ class ApiAuthenticationFilter extends Filter with AccessTokenService with Accoun
|
||||
val response = res.asInstanceOf[HttpServletResponse]
|
||||
Option(request.getHeader("Authorization"))
|
||||
.map {
|
||||
case auth if auth.toLowerCase().startsWith("token ") =>
|
||||
AccessTokenService.getAccountByAccessToken(auth.substring(6).trim).toRight(())
|
||||
case auth if auth.toLowerCase().startsWith("token ") || auth.toLowerCase().startsWith("bearer ") =>
|
||||
AccessTokenService.getAccountByAccessToken(auth.substring(auth.indexOf(" ") + 1).trim).toRight(())
|
||||
case auth if auth.startsWith("Basic ") => doBasicAuth(auth, loadSystemSettings(), request).toRight(())
|
||||
case _ => Left(())
|
||||
}
|
||||
@@ -42,7 +42,7 @@ class ApiAuthenticationFilter extends Filter with AccessTokenService with Accoun
|
||||
updateLastLoginDate(account.userName)
|
||||
}
|
||||
chain.doFilter(req, res)
|
||||
case None => chain.doFilter(req, res)
|
||||
case None => chain.doFilter(req, res)
|
||||
case Some(Left(_)) => {
|
||||
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED)
|
||||
response.setContentType("application/json; charset=utf-8")
|
||||
|
||||
@@ -9,11 +9,11 @@ import gitbucket.core.api.JsonFormat.Context
|
||||
import gitbucket.core.model.WebHook
|
||||
import gitbucket.core.plugin.{GitRepositoryRouting, PluginRegistry}
|
||||
import gitbucket.core.service.IssuesService.IssueSearchCondition
|
||||
import gitbucket.core.service.WebHookService._
|
||||
import gitbucket.core.service._
|
||||
import gitbucket.core.util.Implicits._
|
||||
import gitbucket.core.util._
|
||||
import gitbucket.core.model.Profile.profile.blockingApi._
|
||||
import gitbucket.core.service.WebHookService.*
|
||||
import gitbucket.core.service.*
|
||||
import gitbucket.core.util.Implicits.*
|
||||
import gitbucket.core.util.*
|
||||
import gitbucket.core.model.Profile.profile.blockingApi.*
|
||||
import gitbucket.core.model.activity.{
|
||||
BaseActivityInfo,
|
||||
CloseIssueInfo,
|
||||
@@ -33,9 +33,9 @@ import gitbucket.core.servlet.Database
|
||||
|
||||
import org.eclipse.jgit.api.Git
|
||||
import org.eclipse.jgit.http.server.GitServlet
|
||||
import org.eclipse.jgit.lib._
|
||||
import org.eclipse.jgit.transport._
|
||||
import org.eclipse.jgit.transport.resolver._
|
||||
import org.eclipse.jgit.lib.*
|
||||
import org.eclipse.jgit.transport.*
|
||||
import org.eclipse.jgit.transport.resolver.*
|
||||
import org.slf4j.LoggerFactory
|
||||
import javax.servlet.ServletConfig
|
||||
import javax.servlet.http.{HttpServletRequest, HttpServletResponse}
|
||||
@@ -43,7 +43,7 @@ import org.eclipse.jgit.diff.DiffEntry.ChangeType
|
||||
import org.eclipse.jgit.internal.storage.file.FileRepository
|
||||
import org.json4s.Formats
|
||||
import org.json4s.convertToJsonInput
|
||||
import org.json4s.jackson.Serialization._
|
||||
import org.json4s.jackson.Serialization.*
|
||||
|
||||
/**
|
||||
* Provides Git repository via HTTP.
|
||||
@@ -117,7 +117,7 @@ class GitRepositoryServlet extends GitServlet with SystemSettingsService {
|
||||
GitLfs.BatchResponseObject(
|
||||
requestObject.oid,
|
||||
requestObject.size,
|
||||
true,
|
||||
authenticated = true,
|
||||
GitLfs.Actions(
|
||||
upload = Some(
|
||||
GitLfs.Action(
|
||||
@@ -138,7 +138,7 @@ class GitRepositoryServlet extends GitServlet with SystemSettingsService {
|
||||
GitLfs.BatchResponseObject(
|
||||
requestObject.oid,
|
||||
requestObject.size,
|
||||
true,
|
||||
authenticated = true,
|
||||
GitLfs.Actions(
|
||||
download = Some(
|
||||
GitLfs.Action(
|
||||
@@ -223,7 +223,7 @@ class GitBucketReceivePackFactory extends ReceivePackFactory[HttpServletRequest]
|
||||
}
|
||||
}
|
||||
|
||||
import scala.jdk.CollectionConverters._
|
||||
import scala.jdk.CollectionConverters.*
|
||||
|
||||
class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl: String, sshUrl: Option[String])
|
||||
extends PostReceiveHook
|
||||
@@ -242,6 +242,7 @@ class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl:
|
||||
with WebHookPullRequestReviewCommentService
|
||||
with CommitsService
|
||||
with SystemSettingsService
|
||||
with ProtectedBranchService
|
||||
with RequestCache {
|
||||
|
||||
private val logger = LoggerFactory.getLogger(classOf[CommitLogHook])
|
||||
@@ -253,7 +254,7 @@ class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl:
|
||||
commands.asScala.foreach { command =>
|
||||
// call pre-commit hook
|
||||
PluginRegistry().getReceiveHooks
|
||||
.flatMap(_.preReceive(owner, repository, receivePack, command, pusher, false))
|
||||
.flatMap(_.preReceive(owner, repository, receivePack, command, pusher, mergePullRequest = false))
|
||||
.headOption
|
||||
.foreach { error =>
|
||||
command.setResult(ReceiveCommand.Result.REJECTED_OTHER_REASON, error)
|
||||
@@ -428,8 +429,8 @@ class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl:
|
||||
repositoryInfo,
|
||||
newCommits,
|
||||
ownerAccount,
|
||||
newId = command.getNewId(),
|
||||
oldId = command.getOldId()
|
||||
newId = command.getNewId,
|
||||
oldId = command.getOldId
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -453,7 +454,7 @@ class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl:
|
||||
|
||||
// call post-commit hook
|
||||
PluginRegistry().getReceiveHooks
|
||||
.foreach(_.postReceive(owner, repository, receivePack, command, pusher, false))
|
||||
.foreach(_.postReceive(owner, repository, receivePack, command, pusher, mergePullRequest = false))
|
||||
}
|
||||
}
|
||||
// update repository last modified time.
|
||||
@@ -543,7 +544,7 @@ class WikiCommitHook(owner: String, repository: String, pusher: String, baseUrl:
|
||||
case ChangeType.ADD | ChangeType.RENAME => "created"
|
||||
case ChangeType.MODIFY => "edited"
|
||||
case ChangeType.DELETE => "deleted"
|
||||
case other =>
|
||||
case other =>
|
||||
logger.error(s"Unsupported Wiki action: $other")
|
||||
"unsupported action"
|
||||
}
|
||||
@@ -560,7 +561,7 @@ class WikiCommitHook(owner: String, repository: String, pusher: String, baseUrl:
|
||||
case "created" => Some(CreateWikiPageInfo(owner, repo, commit.committerName, pageName))
|
||||
case "edited" => Some(EditWikiPageInfo(owner, repo, commit.committerName, pageName, commit.id))
|
||||
case "deleted" => Some(DeleteWikiInfo(owner, repo, commit.committerName, pageName))
|
||||
case other =>
|
||||
case other =>
|
||||
logger.info(s"Attempted to build wiki record for unsupported action: $other")
|
||||
None
|
||||
}
|
||||
|
||||
@@ -6,9 +6,9 @@ import gitbucket.core.GitBucketCoreModule
|
||||
import gitbucket.core.plugin.PluginRegistry
|
||||
import gitbucket.core.service.SystemSettingsService
|
||||
import gitbucket.core.util.DatabaseConfig
|
||||
import gitbucket.core.util.Directory._
|
||||
import gitbucket.core.util.JDBCUtil._
|
||||
import gitbucket.core.model.Profile.profile.blockingApi._
|
||||
import gitbucket.core.util.Directory.*
|
||||
import gitbucket.core.util.JDBCUtil.*
|
||||
import gitbucket.core.model.Profile.profile.blockingApi.*
|
||||
// Imported names have higher precedence than names, defined in other files.
|
||||
// If Database is not bound by explicit import, then "Database" refers to the Database introduced by the wildcard import above.
|
||||
import gitbucket.core.servlet.Database
|
||||
@@ -20,7 +20,7 @@ import javax.servlet.{ServletContextEvent, ServletContextListener}
|
||||
import org.apache.commons.io.{FileUtils, IOUtils}
|
||||
import org.slf4j.LoggerFactory
|
||||
|
||||
import scala.jdk.CollectionConverters._
|
||||
import scala.jdk.CollectionConverters.*
|
||||
import scala.util.Using
|
||||
|
||||
/**
|
||||
@@ -50,51 +50,57 @@ class InitializeListener extends ServletContextListener with SystemSettingsServi
|
||||
// )
|
||||
|
||||
override def contextInitialized(event: ServletContextEvent): Unit = {
|
||||
val dataDir = event.getServletContext.getInitParameter("gitbucket.home")
|
||||
if (dataDir != null) {
|
||||
System.setProperty("gitbucket.home", dataDir)
|
||||
}
|
||||
org.h2.Driver.load()
|
||||
try {
|
||||
val dataDir = event.getServletContext.getInitParameter("gitbucket.home")
|
||||
if (dataDir != null) {
|
||||
System.setProperty("gitbucket.home", dataDir)
|
||||
}
|
||||
org.h2.Driver.load()
|
||||
|
||||
Database() withTransaction { session =>
|
||||
val conn = session.conn
|
||||
val manager = new JDBCVersionManager(conn)
|
||||
Database() withTransaction { session =>
|
||||
val conn = session.conn
|
||||
val manager = new JDBCVersionManager(conn)
|
||||
|
||||
// Check version
|
||||
checkVersion(manager, conn)
|
||||
// Check version
|
||||
checkVersion(manager, conn)
|
||||
|
||||
// Run normal migration
|
||||
logger.info("Start schema update")
|
||||
new Solidbase()
|
||||
.migrate(conn, Thread.currentThread.getContextClassLoader, DatabaseConfig.liquiDriver, GitBucketCoreModule)
|
||||
// Run normal migration
|
||||
logger.info("Start schema update")
|
||||
new Solidbase()
|
||||
.migrate(conn, Thread.currentThread.getContextClassLoader, DatabaseConfig.liquiDriver, GitBucketCoreModule)
|
||||
|
||||
// Rescue code for users who updated from 3.14 to 4.0.0
|
||||
// https://github.com/gitbucket/gitbucket/issues/1227
|
||||
val currentVersion = manager.getCurrentVersion(GitBucketCoreModule.getModuleId)
|
||||
val databaseVersion = if (currentVersion == "4.0") {
|
||||
manager.updateVersion(GitBucketCoreModule.getModuleId, "4.0.0")
|
||||
"4.0.0"
|
||||
} else currentVersion
|
||||
// Rescue code for users who updated from 3.14 to 4.0.0
|
||||
// https://github.com/gitbucket/gitbucket/issues/1227
|
||||
val currentVersion = manager.getCurrentVersion(GitBucketCoreModule.getModuleId)
|
||||
val databaseVersion = if (currentVersion == "4.0") {
|
||||
manager.updateVersion(GitBucketCoreModule.getModuleId, "4.0.0")
|
||||
"4.0.0"
|
||||
} else currentVersion
|
||||
|
||||
val gitbucketVersion = GitBucketCoreModule.getVersions.asScala.last.getVersion
|
||||
if (databaseVersion != gitbucketVersion) {
|
||||
throw new IllegalStateException(
|
||||
s"Initialization failed. GitBucket version is ${gitbucketVersion}, but database version is ${databaseVersion}."
|
||||
)
|
||||
val gitbucketVersion = GitBucketCoreModule.getVersions.asScala.last.getVersion
|
||||
if (databaseVersion != gitbucketVersion) {
|
||||
throw new IllegalStateException(
|
||||
s"Initialization failed. GitBucket version is ${gitbucketVersion}, but database version is ${databaseVersion}."
|
||||
)
|
||||
}
|
||||
|
||||
// Install bundled plugins
|
||||
extractBundledPlugins()
|
||||
|
||||
// Load plugins
|
||||
logger.info("Initialize plugins")
|
||||
PluginRegistry.initialize(event.getServletContext, loadSystemSettings(), conn)
|
||||
}
|
||||
|
||||
// Install bundled plugins
|
||||
extractBundledPlugins()
|
||||
|
||||
// Load plugins
|
||||
logger.info("Initialize plugins")
|
||||
PluginRegistry.initialize(event.getServletContext, loadSystemSettings(), conn)
|
||||
// // Start Quartz scheduler
|
||||
// val scheduler = QuartzSchedulerExtension(system)
|
||||
//
|
||||
// scheduler.schedule("Daily", system.actorOf(Props[DeleteOldActivityActor]), "DeleteOldActivity")
|
||||
} catch {
|
||||
case e: Exception =>
|
||||
logger.error(e.getMessage, e)
|
||||
throw e
|
||||
}
|
||||
|
||||
// // Start Quartz scheduler
|
||||
// val scheduler = QuartzSchedulerExtension(system)
|
||||
//
|
||||
// scheduler.schedule("Daily", system.actorOf(Props[DeleteOldActivityActor]), "DeleteOldActivity")
|
||||
}
|
||||
|
||||
private def checkVersion(manager: JDBCVersionManager, conn: java.sql.Connection): Unit = {
|
||||
@@ -141,7 +147,7 @@ class InitializeListener extends ServletContextListener with SystemSettingsServi
|
||||
Using.resource(cl.getResourceAsStream("bundle-plugins.txt")) { pluginsFile =>
|
||||
if (pluginsFile != null) {
|
||||
val plugins = IOUtils.readLines(pluginsFile, "UTF-8")
|
||||
val gitbucketVersion = GitBucketCoreModule.getVersions.asScala.last.getVersion
|
||||
// val gitbucketVersion = GitBucketCoreModule.getVersions.asScala.last.getVersion
|
||||
|
||||
plugins.asScala.foreach { plugin =>
|
||||
plugin.trim.split(":") match {
|
||||
|
||||
@@ -15,9 +15,9 @@ trait OneselfAuthenticator { self: ControllerBase =>
|
||||
|
||||
private def authenticate(action: => Any) = {
|
||||
context.loginAccount match {
|
||||
case Some(x) if (x.isAdmin) => action
|
||||
case Some(x) if (request.paths(0) == x.userName) => action
|
||||
case _ => Unauthorized()
|
||||
case Some(x) if x.isAdmin => action
|
||||
case Some(x) if request.paths(0) == x.userName => action
|
||||
case _ => Unauthorized()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -26,7 +26,7 @@ trait OneselfAuthenticator { self: ControllerBase =>
|
||||
* Allows only the repository owner and administrators.
|
||||
*/
|
||||
trait OwnerAuthenticator { self: ControllerBase & RepositoryService & AccountService =>
|
||||
protected def ownerOnly(action: (RepositoryInfo) => Any) = { authenticate(action) }
|
||||
protected def ownerOnly(action: RepositoryInfo => Any) = { authenticate(action) }
|
||||
protected def ownerOnly[T](action: (T, RepositoryInfo) => Any) = (form: T) => { authenticate(action(form, _)) }
|
||||
|
||||
private def authenticate(action: (RepositoryInfo) => Any) = {
|
||||
@@ -34,14 +34,14 @@ trait OwnerAuthenticator { self: ControllerBase & RepositoryService & AccountSer
|
||||
val repoName = params("repository")
|
||||
getRepository(userName, repoName).map { repository =>
|
||||
context.loginAccount match {
|
||||
case Some(x) if (x.isAdmin) => action(repository)
|
||||
case Some(x) if (repository.owner == x.userName) => action(repository)
|
||||
case Some(x) if x.isAdmin => action(repository)
|
||||
case Some(x) if repository.owner == x.userName => action(repository)
|
||||
// TODO Repository management is allowed for only group managers?
|
||||
case Some(x) if (getGroupMembers(repository.owner).exists { m =>
|
||||
case Some(x) if getGroupMembers(repository.owner).exists { m =>
|
||||
m.userName == x.userName && m.isManager
|
||||
}) =>
|
||||
} =>
|
||||
action(repository)
|
||||
case Some(x) if (getCollaboratorUserNames(userName, repoName, Seq(Role.ADMIN)).contains(x.userName)) =>
|
||||
case Some(x) if getCollaboratorUserNames(userName, repoName, Seq(Role.ADMIN)).contains(x.userName) =>
|
||||
action(repository)
|
||||
case _ => Unauthorized()
|
||||
}
|
||||
@@ -83,10 +83,10 @@ trait AdminAuthenticator { self: ControllerBase =>
|
||||
* Allows only guests and signed in users who can access the repository.
|
||||
*/
|
||||
trait ReferrerAuthenticator { self: ControllerBase & RepositoryService & AccountService =>
|
||||
protected def referrersOnly(action: (RepositoryInfo) => Any) = { authenticate(action) }
|
||||
protected def referrersOnly(action: RepositoryInfo => Any) = { authenticate(action) }
|
||||
protected def referrersOnly[T](action: (T, RepositoryInfo) => Any) = (form: T) => { authenticate(action(form, _)) }
|
||||
|
||||
private def authenticate(action: (RepositoryInfo) => Any) = {
|
||||
private def authenticate(action: RepositoryInfo => Any) = {
|
||||
val userName = params("owner")
|
||||
val repoName = params("repository")
|
||||
getRepository(userName, repoName).map { repository =>
|
||||
@@ -103,12 +103,12 @@ trait ReferrerAuthenticator { self: ControllerBase & RepositoryService & Account
|
||||
* Allows only signed in users who have read permission for the repository.
|
||||
*/
|
||||
trait ReadableUsersAuthenticator { self: ControllerBase & RepositoryService & AccountService =>
|
||||
protected def readableUsersOnly(action: (RepositoryInfo) => Any) = { authenticate(action) }
|
||||
protected def readableUsersOnly(action: RepositoryInfo => Any) = { authenticate(action) }
|
||||
protected def readableUsersOnly[T](action: (T, RepositoryInfo) => Any) = (form: T) => {
|
||||
authenticate(action(form, _))
|
||||
}
|
||||
|
||||
private def authenticate(action: (RepositoryInfo) => Any) = {
|
||||
private def authenticate(action: RepositoryInfo => Any) = {
|
||||
val userName = params("owner")
|
||||
val repoName = params("repository")
|
||||
getRepository(userName, repoName).map { repository =>
|
||||
@@ -125,24 +125,19 @@ trait ReadableUsersAuthenticator { self: ControllerBase & RepositoryService & Ac
|
||||
* Allows only signed in users who have write permission for the repository.
|
||||
*/
|
||||
trait WritableUsersAuthenticator { self: ControllerBase & RepositoryService & AccountService =>
|
||||
protected def writableUsersOnly(action: (RepositoryInfo) => Any) = { authenticate(action) }
|
||||
protected def writableUsersOnly(action: RepositoryInfo => Any) = { authenticate(action) }
|
||||
protected def writableUsersOnly[T](action: (T, RepositoryInfo) => Any) = (form: T) => {
|
||||
authenticate(action(form, _))
|
||||
}
|
||||
|
||||
private def authenticate(action: (RepositoryInfo) => Any) = {
|
||||
private def authenticate(action: RepositoryInfo => Any) = {
|
||||
val userName = params("owner")
|
||||
val repoName = params("repository")
|
||||
getRepository(userName, repoName).map { repository =>
|
||||
context.loginAccount match {
|
||||
case Some(x) if (x.isAdmin) => action(repository)
|
||||
case Some(x) if (userName == x.userName) => action(repository)
|
||||
case Some(x) if (getGroupMembers(repository.owner).exists(_.userName == x.userName)) => action(repository)
|
||||
case Some(x)
|
||||
if (getCollaboratorUserNames(userName, repoName, Seq(Role.ADMIN, Role.DEVELOPER))
|
||||
.contains(x.userName)) =>
|
||||
action(repository)
|
||||
case _ => Unauthorized()
|
||||
if (isWritable(repository.repository, context.loginAccount)) {
|
||||
action(repository)
|
||||
} else {
|
||||
Unauthorized()
|
||||
}
|
||||
} getOrElse NotFound()
|
||||
}
|
||||
@@ -159,9 +154,9 @@ trait GroupManagerAuthenticator { self: ControllerBase & AccountService =>
|
||||
context.loginAccount match {
|
||||
case Some(x) if x.isAdmin => action
|
||||
case Some(x) if x.userName == request.paths(0) => action
|
||||
case Some(x) if (getGroupMembers(request.paths(0)).exists { member =>
|
||||
case Some(x) if getGroupMembers(request.paths(0)).exists { member =>
|
||||
member.userName == x.userName && member.isManager
|
||||
}) =>
|
||||
} =>
|
||||
action
|
||||
case _ => Unauthorized()
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ object DatabaseConfig {
|
||||
FileUtils.write(
|
||||
file,
|
||||
"""db {
|
||||
| url = "jdbc:h2:${DatabaseHome};MVCC=true"
|
||||
| url = "jdbc:h2:${DatabaseHome}"
|
||||
| user = "sa"
|
||||
| password = "sa"
|
||||
|# connectionTimeout = 30000
|
||||
|
||||
@@ -10,7 +10,7 @@ object Directory {
|
||||
val GitBucketHome = (System.getProperty("gitbucket.home") match {
|
||||
// -Dgitbucket.home=<path>
|
||||
case path if (path != null) => new File(path)
|
||||
case _ =>
|
||||
case _ =>
|
||||
scala.util.Properties.envOrNone("GITBUCKET_HOME") match {
|
||||
// environment variable GITBUCKET_HOME
|
||||
case Some(env) => new File(env)
|
||||
|
||||
@@ -669,7 +669,7 @@ object JGitUtil {
|
||||
val objectId = git.getRepository.resolve(name)
|
||||
git.getRepository.open(objectId).getType match {
|
||||
case Constants.OBJ_COMMIT => objectId
|
||||
case Constants.OBJ_TAG =>
|
||||
case Constants.OBJ_TAG =>
|
||||
val ref = git.getRepository.getRefDatabase.findRef(name)
|
||||
git.getRepository.getRefDatabase.peel(ref).getPeeledObjectId
|
||||
case _ => ObjectId.zeroId()
|
||||
|
||||
@@ -47,18 +47,28 @@ trait AvatarImageProvider { self: RequestCache =>
|
||||
}
|
||||
}
|
||||
|
||||
val displayName = if (!context.settings.showFullName) {
|
||||
s"@$userName"
|
||||
} else {
|
||||
if (mailAddress.isEmpty) {
|
||||
getAccountByUserNameFromCache(userName).map(_.fullName).getOrElse(s"@$userName")
|
||||
} else {
|
||||
getAccountByMailAddressFromCache(mailAddress).map(_.fullName).getOrElse(s"@$userName")
|
||||
}
|
||||
}
|
||||
|
||||
if (tooltip) {
|
||||
Html(
|
||||
s"""<img src="${src}" class="${if (size > 20) { "avatar" }
|
||||
else { "avatar-mini" }}" style="width: ${size}px; height: ${size}px;"
|
||||
| alt="@${StringUtil.escapeHtml(userName)}"
|
||||
| data-toggle="tooltip" title="${StringUtil.escapeHtml(userName)}" />""".stripMargin
|
||||
| alt="${StringUtil.escapeHtml(displayName)}"
|
||||
| data-toggle="tooltip" title="${StringUtil.escapeHtml(displayName)}" />""".stripMargin
|
||||
)
|
||||
} else {
|
||||
Html(
|
||||
s"""<img src="${src}" class="${if (size > 20) { "avatar" }
|
||||
else { "avatar-mini" }}" style="width: ${size}px; height: ${size}px;"
|
||||
| alt="@${StringUtil.escapeHtml(userName)}" />""".stripMargin
|
||||
| alt="${StringUtil.escapeHtml(displayName)}" />""".stripMargin
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import gitbucket.core.service.{RepositoryService, RequestCache}
|
||||
import gitbucket.core.util.StringUtil
|
||||
import io.github.gitbucket.markedj._
|
||||
import io.github.gitbucket.markedj.Utils._
|
||||
import gitbucket.core.service.WikiService
|
||||
|
||||
object Markdown {
|
||||
|
||||
@@ -75,7 +76,8 @@ object Markdown {
|
||||
)(implicit val context: Context)
|
||||
extends Renderer(options)
|
||||
with LinkConverter
|
||||
with RequestCache {
|
||||
with RequestCache
|
||||
with WikiService {
|
||||
|
||||
override def heading(text: String, level: Int, raw: String): String = {
|
||||
val id = generateAnchorName(text)
|
||||
@@ -192,7 +194,16 @@ object Markdown {
|
||||
.stripSuffix(".git") + "/blob/" + branch + "/" + urlWithRawParam
|
||||
}
|
||||
} else {
|
||||
repository.httpUrl.replaceFirst("/git/", "/").stripSuffix(".git") + "/wiki/_blob/" + url
|
||||
// URL is being modified to link to the image file on the repository, but users may want to link to the page if the page name is a link.
|
||||
// If the wiki page cannot be retrieved from the url, the blob address is returned; otherwise, the page address is returned.
|
||||
val pathElems = context.currentPath.split("/")
|
||||
val owner = pathElems(1)
|
||||
val repos = pathElems(2)
|
||||
if (getWikiPage(owner, repos, url, branch) == None) {
|
||||
repository.httpUrl.replaceFirst("/git/", "/").stripSuffix(".git") + "/wiki/_blob/" + url
|
||||
} else {
|
||||
repository.httpUrl.replaceFirst("/git/", "/").stripSuffix(".git") + "/wiki/" + url
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,8 +53,8 @@ object helpers extends AvatarImageProvider with LinkConverter with RequestCache
|
||||
def datetimeAgoRecentOnly(date: Date): String = {
|
||||
val duration = new Date().getTime - date.getTime
|
||||
timeUnits.find(tuple => duration / tuple._1 > 0) match {
|
||||
case Some((_, "month")) => s"on ${new SimpleDateFormat("d MMM", Locale.ENGLISH).format(date)}"
|
||||
case Some((_, "year")) => s"on ${new SimpleDateFormat("d MMM yyyy", Locale.ENGLISH).format(date)}"
|
||||
case Some((_, "month")) => s"on ${new SimpleDateFormat("d MMM", Locale.ENGLISH).format(date)}"
|
||||
case Some((_, "year")) => s"on ${new SimpleDateFormat("d MMM yyyy", Locale.ENGLISH).format(date)}"
|
||||
case Some((unitValue, unitString)) =>
|
||||
val value = duration / unitValue
|
||||
s"${value} ${unitString}${if (value > 1) "s" else ""} ago"
|
||||
@@ -101,6 +101,7 @@ object helpers extends AvatarImageProvider with LinkConverter with RequestCache
|
||||
/**
|
||||
* Converts Markdown of Wiki pages to HTML.
|
||||
*/
|
||||
@deprecated("This doesn't apply render plugins. Should use renderMarkup() instead.", "4.45.0")
|
||||
def markdown(
|
||||
markdown: String,
|
||||
repository: RepositoryService.RepositoryInfo,
|
||||
@@ -139,14 +140,29 @@ object helpers extends AvatarImageProvider with LinkConverter with RequestCache
|
||||
repository: RepositoryService.RepositoryInfo,
|
||||
enableWikiLink: Boolean,
|
||||
enableRefsLink: Boolean,
|
||||
enableAnchor: Boolean
|
||||
enableAnchor: Boolean,
|
||||
enableLineBreaks: Boolean,
|
||||
enableTaskList: Boolean,
|
||||
hasWritePermission: Boolean = false
|
||||
)(implicit context: Context): Html = {
|
||||
|
||||
val fileName = filePath.last.toLowerCase
|
||||
val extension = FileUtil.getExtension(fileName)
|
||||
val renderer = PluginRegistry().getRenderer(extension)
|
||||
renderer.render(
|
||||
RenderRequest(filePath, fileContent, branch, repository, enableWikiLink, enableRefsLink, enableAnchor, context)
|
||||
RenderRequest(
|
||||
filePath,
|
||||
fileContent,
|
||||
branch,
|
||||
repository,
|
||||
enableWikiLink,
|
||||
enableRefsLink,
|
||||
enableAnchor,
|
||||
enableLineBreaks,
|
||||
enableTaskList,
|
||||
hasWritePermission,
|
||||
context
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -316,12 +332,30 @@ object helpers extends AvatarImageProvider with LinkConverter with RequestCache
|
||||
*/
|
||||
def assets(path: String)(implicit context: Context): String = s"${context.path}/assets${path}?${hashQuery}"
|
||||
|
||||
def displayUserName(userName: String, mailAddress: String = "")(implicit context: Context): String = {
|
||||
val displayName = if (!context.settings.showFullName) {
|
||||
userName
|
||||
} else {
|
||||
if (mailAddress.isEmpty) {
|
||||
getAccountByUserNameFromCache(userName).map(_.fullName).getOrElse(userName)
|
||||
} else {
|
||||
getAccountByMailAddressFromCache(mailAddress).map(_.fullName).getOrElse(userName)
|
||||
}
|
||||
}
|
||||
if (userName == displayName) {
|
||||
userName
|
||||
} else {
|
||||
s"$userName ($displayName)"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Generates the text link to the account page.
|
||||
* If user does not exist or disabled, this method returns user name as text without link.
|
||||
*/
|
||||
def user(userName: String, mailAddress: String = "", styleClass: String = "")(implicit context: Context): Html =
|
||||
userWithContent(userName, mailAddress, styleClass)(Html(StringUtil.escapeHtml(userName)))
|
||||
userWithContent(userName, mailAddress, styleClass)(Html(StringUtil.escapeHtml(displayUserName(userName, mailAddress))))
|
||||
|
||||
/**
|
||||
* Generates the avatar link to the account page.
|
||||
@@ -511,6 +545,18 @@ object helpers extends AvatarImageProvider with LinkConverter with RequestCache
|
||||
Html(sb.toString())
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility method to enable checkboxes
|
||||
*/
|
||||
def enableCheckbox(html: Html, enable: Boolean): Html = {
|
||||
if (enable) {
|
||||
val re = "(<input\\s+[^<>]*type=\"checkbox\"\\s+[^<>]*)\\s+disabled[^<>]*>".r
|
||||
Html(re.replaceAllIn(html.toString(), "$1>"))
|
||||
} else {
|
||||
html
|
||||
}
|
||||
}
|
||||
|
||||
case class CommentDiffLine(newLine: Option[String], oldLine: Option[String], `type`: String, text: String)
|
||||
|
||||
def appendQueryString(baseUrl: String, queryString: String): String = {
|
||||
|
||||
@@ -56,16 +56,16 @@ $(function(){
|
||||
|
||||
$('#addMember').click(function(){
|
||||
$('#error-members').text('');
|
||||
var userName = $('#memberName').val();
|
||||
const userName = $('#memberName').val();
|
||||
|
||||
// check empty
|
||||
if($.trim(userName) == ''){
|
||||
if($.trim(userName) === ''){
|
||||
return false;
|
||||
}
|
||||
|
||||
// check duplication
|
||||
var exists = $('#member-list li').filter(function(){
|
||||
return $(this).data('name') == userName;
|
||||
const exists = $('#member-list li').filter(function(){
|
||||
return $(this).data('name') === userName;
|
||||
}).length > 0;
|
||||
if(exists){
|
||||
$('#error-members').text('User has been already added.');
|
||||
@@ -75,7 +75,7 @@ $(function(){
|
||||
// check existence
|
||||
$.post('@context.path/_user/existence', { 'userName': userName },
|
||||
function(data, status){
|
||||
if(data == 'user'){
|
||||
if(data === 'user'){
|
||||
addMemberHTML(userName, false);
|
||||
$('#memberName').val('');
|
||||
} else {
|
||||
@@ -90,7 +90,7 @@ $(function(){
|
||||
|
||||
// Don't submit form by ENTER key
|
||||
$('#memberName').keypress(function(e){
|
||||
return !(e.keyCode == 13);
|
||||
return !(e.keyCode === 13);
|
||||
});
|
||||
|
||||
$('#delete').click(function(){
|
||||
@@ -102,11 +102,11 @@ $(function(){
|
||||
}
|
||||
|
||||
function addMemberHTML(userName, isManager){
|
||||
var memberButton = $('<label class="btn btn-default btn-mini"><input type="radio" value="false" name="' + userName + '">Member</label>');
|
||||
const memberButton = $('<label class="btn btn-default btn-mini"><input type="radio" value="false" name="' + userName + '">Member</label>');
|
||||
if(!isManager){
|
||||
memberButton.addClass('active');
|
||||
}
|
||||
var managerButton = $('<label class="btn btn-default btn-mini"><input type="radio" value="true" name="' + userName + '">Manager</label>');
|
||||
const managerButton = $('<label class="btn btn-default btn-mini"><input type="radio" value="true" name="' + userName + '">Manager</label>');
|
||||
if(isManager){
|
||||
managerButton.addClass('active');
|
||||
}
|
||||
@@ -123,8 +123,8 @@ $(function(){
|
||||
}
|
||||
|
||||
function updateMembers(){
|
||||
var members = $('#member-list li').map(function(i, e){
|
||||
var userName = $(e).data('name');
|
||||
const members = $('#member-list li').map(function(i, e){
|
||||
const userName = $(e).data('name');
|
||||
return userName + ':' + $(e).find('label.active input[type=radio]').attr('value');
|
||||
}).get().join(',');
|
||||
$('#members').val(members);
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
</li>
|
||||
@gitbucket.core.plugin.PluginRegistry().getSystemSettingMenus.map { menu =>
|
||||
@menu(context).map { link =>
|
||||
<li@if(active==link.id){ class="active"}>
|
||||
<li class="menu-item-hover @if(active==link.id){active}">
|
||||
<a href="@context.path/@link.path">
|
||||
<i class="menu-icon octicon octicon-@link.icon.getOrElse("plug")"></i>
|
||||
<span>@link.label</span>
|
||||
|
||||
@@ -253,6 +253,21 @@
|
||||
<input type="radio" name="showMailAddress" value="false"@if(!context.settings.showMailAddress){ checked}>
|
||||
<span class="strong">Hide</span> <span class="normal">- Hide mail address in user's profile page.</span>
|
||||
</label>
|
||||
</fieldset>
|
||||
<!--====================================================================-->
|
||||
<!-- Show full name -->
|
||||
<!--====================================================================-->
|
||||
<hr>
|
||||
<label class="strong">Username display on UI</label>
|
||||
<fieldset>
|
||||
<label class="radio">
|
||||
<input type="radio" name="showFullName" value="false"@if(!context.settings.showFullName){ checked}>
|
||||
<span class="strong">User name</span> <span class="normal">- Username is primarily displayed on UI.</span>
|
||||
</label>
|
||||
<label class="radio">
|
||||
<input type="radio" name="showFullName" value="true"@if(context.settings.showFullName){ checked}>
|
||||
<span class="strong">Full name</span> <span class="normal">- Fullname is primarily displayed instead of username on UI.</span>
|
||||
</label>
|
||||
</fieldset>
|
||||
<!--====================================================================-->
|
||||
<!-- File upload -->
|
||||
|
||||
@@ -25,20 +25,23 @@
|
||||
<a href="@context.path/admin/users/@account.userName/_edituser">Edit</a>
|
||||
}
|
||||
</div>
|
||||
<div class="strong">
|
||||
@helpers.avatarLink(account.userName, 20)
|
||||
@if(account.isRemoved){
|
||||
@account.userName
|
||||
} else {
|
||||
<a href="@helpers.url(account.userName)">@account.userName</a>
|
||||
}
|
||||
@if(account.isGroupAccount){
|
||||
(Group)
|
||||
} else {
|
||||
@if(account.isAdmin){
|
||||
(Administrator)
|
||||
<div>
|
||||
<span class="strong">
|
||||
@helpers.avatarLink(account.userName, 20)
|
||||
@if(account.isRemoved){
|
||||
@account.userName
|
||||
} else {
|
||||
(Normal)
|
||||
<a href="@helpers.url(account.userName)">@account.userName</a>
|
||||
}
|
||||
</span>
|
||||
@if(account.isGroupAccount){
|
||||
- Group
|
||||
} else {
|
||||
(@account.fullName)
|
||||
@if(account.isAdmin){
|
||||
- Administrator
|
||||
} else {
|
||||
- Normal
|
||||
}
|
||||
}
|
||||
@if(account.isGroupAccount){
|
||||
@@ -50,10 +53,10 @@
|
||||
<div>
|
||||
<hr>
|
||||
@if(!account.isGroupAccount){
|
||||
<i class="octicon octicon-mail"></i> @account.mailAddress
|
||||
<i class="octicon octicon-mail"></i>@account.mailAddress
|
||||
}
|
||||
@account.url.map { url =>
|
||||
<i class="octicon octicon-home"></i> @url
|
||||
<i class="octicon octicon-home"></i>@url
|
||||
}
|
||||
</div>
|
||||
<div>
|
||||
|
||||
@@ -67,7 +67,7 @@
|
||||
});
|
||||
|
||||
function updateBranchControlList(active) {
|
||||
if (active == 'branches') {
|
||||
if (active === 'branches') {
|
||||
$('li#branch-control-tab-branches').addClass('active');
|
||||
$('li#branch-control-tab-tags').removeClass('active');
|
||||
|
||||
@@ -81,7 +81,7 @@
|
||||
} else {
|
||||
$('#branch-control-input').attr('placeholder', 'Find branch ...');
|
||||
}
|
||||
} else if (active == 'tags') {
|
||||
} else if (active === 'tags') {
|
||||
$('li#branch-control-tab-branches').removeClass('active');
|
||||
$('li#branch-control-tab-tags').addClass('active');
|
||||
|
||||
|
||||
@@ -21,16 +21,18 @@
|
||||
</span>
|
||||
</div>
|
||||
<div class="commit-commentContent-@comment.commentId">
|
||||
@helpers.markdown(
|
||||
markdown = comment.content,
|
||||
repository = repository,
|
||||
branch = repository.repository.defaultBranch,
|
||||
enableWikiLink = false,
|
||||
enableRefsLink = true,
|
||||
enableLineBreaks = true,
|
||||
enableTaskList = true,
|
||||
hasWritePermission = hasWritePermission
|
||||
)
|
||||
@helpers.renderMarkup(
|
||||
filePath = List("temporary.md"),
|
||||
fileContent = comment.content,
|
||||
branch = repository.repository.defaultBranch,
|
||||
repository = repository,
|
||||
enableWikiLink = false,
|
||||
enableRefsLink = true,
|
||||
enableAnchor = true,
|
||||
enableLineBreaks = true,
|
||||
enableTaskList = true,
|
||||
hasWritePermission = hasWritePermission
|
||||
)
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -57,6 +57,7 @@ $(function(){
|
||||
$('#preview@uid').click(function(){
|
||||
$('#preview-area@uid').html('<img src="@helpers.assets("/common/images/indicator.gif")"> Previewing...');
|
||||
$.post('@helpers.url(repository)/_preview', {
|
||||
filename : "temporary.md",
|
||||
content : $('#content@uid').val(),
|
||||
enableWikiLink : @enableWikiLink,
|
||||
enableRefsLink : @enableRefsLink,
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
</a>
|
||||
</span>
|
||||
@if(comment.action != "commit" && comment.action != "merge" && comment.action != "refer"
|
||||
&& (isManageable || context.loginAccount.map(_.userName == comment.commentedUserName).getOrElse(false))){
|
||||
&& (isManageable || context.loginAccount.exists(_.userName == comment.commentedUserName))){
|
||||
<span class="pull-right">
|
||||
<a href="#" data-comment-id="@comment.commentId"><i class="octicon octicon-pencil" aria-label="Edit"></i></a>
|
||||
<a href="#" data-comment-id="@comment.commentId"><i class="octicon octicon-x" aria-label="Remove"></i></a>
|
||||
@@ -32,12 +32,14 @@
|
||||
}
|
||||
</div>
|
||||
<div class="panel-body markdown-body" id="commentContent-@comment.commentId">
|
||||
@helpers.markdown(
|
||||
markdown = comment.content,
|
||||
repository = repository,
|
||||
@helpers.renderMarkup(
|
||||
filePath = List("temporary.md"),
|
||||
fileContent = comment.content,
|
||||
branch = repository.repository.defaultBranch,
|
||||
repository = repository,
|
||||
enableWikiLink = false,
|
||||
enableRefsLink = true,
|
||||
enableAnchor = true,
|
||||
enableLineBreaks = true,
|
||||
enableTaskList = true,
|
||||
hasWritePermission = isManageable
|
||||
@@ -52,18 +54,20 @@
|
||||
@helpers.user(issue.get.openedUserName, styleClass="username strong")
|
||||
<span class="muted">commented @gitbucket.core.helper.html.datetimeago(issue.get.registeredDate)</span>
|
||||
<span class="pull-right">
|
||||
@if(isManageable || context.loginAccount.map(_.userName == issue.get.openedUserName).getOrElse(false)){
|
||||
@if(isManageable || context.loginAccount.exists(_.userName == issue.get.openedUserName)){
|
||||
<a href="#" data-issue-id="@issue.get.issueId"><i class="octicon octicon-pencil" aria-label="Edit"></i></a>
|
||||
}
|
||||
</span>
|
||||
</div>
|
||||
<div class="panel-body markdown-body" id="issueContent">
|
||||
@helpers.markdown(
|
||||
markdown = issue.get.content getOrElse "No description provided.",
|
||||
repository = repository,
|
||||
@helpers.renderMarkup(
|
||||
filePath = List("temporary.md"),
|
||||
fileContent = issue.get.content getOrElse "No description provided.",
|
||||
branch = repository.repository.defaultBranch,
|
||||
repository = repository,
|
||||
enableWikiLink = false,
|
||||
enableRefsLink = true,
|
||||
enableAnchor = true,
|
||||
enableLineBreaks = true,
|
||||
enableTaskList = true,
|
||||
hasWritePermission = isManageable
|
||||
@@ -331,12 +335,14 @@
|
||||
}
|
||||
</div>
|
||||
<div class="panel-body markdown-body commit-commentContent-@comment.commentId">
|
||||
@helpers.markdown(
|
||||
markdown = comment.content,
|
||||
repository = repository,
|
||||
@helpers.renderMarkup(
|
||||
filePath = List("temporary.md"),
|
||||
fileContent = comment.content,
|
||||
branch = repository.repository.defaultBranch,
|
||||
repository = repository,
|
||||
enableWikiLink = false,
|
||||
enableRefsLink = true,
|
||||
enableAnchor = true,
|
||||
enableLineBreaks = true,
|
||||
enableTaskList = true,
|
||||
hasWritePermission = isManageable
|
||||
@@ -400,7 +406,7 @@ $(function(){
|
||||
// diff view
|
||||
const tr = comment.closest('.not-diff');
|
||||
if(tr.length > 0){
|
||||
if(tr.prev('.not-diff').length == 0){
|
||||
if(tr.prev('.not-diff').length === 0){
|
||||
tr.next('.not-diff:has(.reply-comment)').remove();
|
||||
}
|
||||
tr.remove();
|
||||
@@ -410,7 +416,7 @@ $(function(){
|
||||
const panel = comment.closest('div.panel:has(.commit-comment-box)');
|
||||
if(panel.length > 0){
|
||||
comment.parent('.commit-comment-box').remove();
|
||||
if(panel.has('.commit-comment-box').length == 0){
|
||||
if(panel.has('.commit-comment-box').length === 0){
|
||||
panel.remove();
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
@gitbucket.core.html.menu("issues", repository){
|
||||
<div>
|
||||
<div class="show-title pull-right">
|
||||
@if(isManageable || context.loginAccount.map(_.userName == issue.openedUserName).getOrElse(false)){
|
||||
@if(isManageable || context.loginAccount.exists(_.userName == issue.openedUserName)){
|
||||
<a class="btn btn-default" href="#" id="edit">Edit</a>
|
||||
}
|
||||
@if(isEditable){
|
||||
|
||||
@@ -131,7 +131,7 @@
|
||||
@collaborators.map { collaborator =>
|
||||
<li>
|
||||
<a href="javascript:void(0);" class="toggle-assign" data-name="@collaborator">
|
||||
@gitbucket.core.helper.html.checkicon(issueAssignees.exists(_.assigneeUserName == collaborator))@helpers.avatar(collaborator, 20) @collaborator
|
||||
@gitbucket.core.helper.html.checkicon(issueAssignees.exists(_.assigneeUserName == collaborator))@helpers.avatar(collaborator, 20) @helpers.displayUserName(collaborator)
|
||||
</a>
|
||||
</li>
|
||||
}
|
||||
|
||||
@@ -37,8 +37,8 @@
|
||||
</div>
|
||||
}
|
||||
@if(isManageableForkedRepository && issue.closed && merged &&
|
||||
forkedRepository.map(r => (r.branchList.contains(pullreq.requestBranch) && r.repository.defaultBranch != pullreq.requestBranch)).getOrElse(false)){
|
||||
<div class="issue-comment-box" style="background-color: #d0eeff;">
|
||||
forkedRepository.exists(r => r.branchList.contains(pullreq.requestBranch) && r.repository.defaultBranch != pullreq.requestBranch)){
|
||||
<div class="issue-comment-box" style="background-color: #d0eeff; margin-bottom: 20px;">
|
||||
<div class="box-content" style="border: 1px solid #87a8c9; padding: 10px;">
|
||||
<a href="@helpers.url(repository)/pull/@issue.issueId/delete_branch" class="btn btn-info pull-right delete-branch" data-name="@pullreq.requestBranch">Delete branch</a>
|
||||
<div>
|
||||
|
||||
@@ -40,7 +40,10 @@
|
||||
<div id="branchCtrlWrapper" style="display:inline;">
|
||||
@gitbucket.core.helper.html.branchcontrol(branch, repository, hasWritePermission){
|
||||
@repository.branchList.map { x =>
|
||||
<li><a href="@helpers.url(repository)/blob/@helpers.encodeRefName((x :: pathList).mkString("/"))">@gitbucket.core.helper.html.checkicon(x == branch) @x</a></li>
|
||||
<li class="branch-control-item-branch"><a href="@helpers.url(repository)/blob/@helpers.encodeRefName((x :: pathList).mkString("/"))">@gitbucket.core.helper.html.checkicon(x == branch) @x</a></li>
|
||||
}
|
||||
@repository.tags.map { x =>
|
||||
<li class="branch-control-item-tag"><a href="@helpers.url(repository)/blob/@helpers.encodeRefName((x.name :: pathList).mkString("/"))">@gitbucket.core.helper.html.checkicon(x.name == branch) @x.name</a></li>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
@@ -80,7 +83,18 @@
|
||||
@defining(helpers.isRenderable(pathList.last)){ isRenderable =>
|
||||
@if(!isBlame && isRenderable) {
|
||||
<div class="box-content-bottom @if(content.viewType == "text"){ markdown-body } " style="padding-left: 20px; padding-right: 20px;">
|
||||
@helpers.renderMarkup(pathList, content.content.getOrElse(""), branch, repository, false, false, true)
|
||||
@helpers.renderMarkup(
|
||||
filePath = pathList,
|
||||
fileContent = content.content.getOrElse(""),
|
||||
branch = branch,
|
||||
repository = repository,
|
||||
enableWikiLink = false,
|
||||
enableRefsLink = false,
|
||||
enableAnchor = true,
|
||||
enableLineBreaks = false,
|
||||
enableTaskList = true,
|
||||
hasWritePermission = hasWritePermission
|
||||
)
|
||||
</div>
|
||||
}else{
|
||||
@if(content.viewType == "text"){
|
||||
|
||||
@@ -12,7 +12,10 @@
|
||||
@if(pathList.isEmpty){
|
||||
@gitbucket.core.helper.html.branchcontrol(branch, repository, hasWritePermission){
|
||||
@repository.branchList.map { x =>
|
||||
<li><a href="@helpers.url(repository)/commits/@helpers.encodeRefName(x)">@gitbucket.core.helper.html.checkicon(x == branch) @x</a></li>
|
||||
<li class="branch-control-item-branch"><a href="@helpers.url(repository)/commits/@helpers.encodeRefName(x)">@gitbucket.core.helper.html.checkicon(x == branch) @x</a></li>
|
||||
}
|
||||
@repository.tags.map { x =>
|
||||
<li class="branch-control-item-tag"><a href="@helpers.url(repository)/commits/@helpers.encodeRefName(x.name)">@gitbucket.core.helper.html.checkicon(x.name == branch) @x.name</a></li>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -208,29 +208,31 @@ $(function(){
|
||||
$('#preview').show();
|
||||
$('#btn-code').removeClass('active');
|
||||
|
||||
@if(fileName.map(helpers.isRenderable _).getOrElse(false)) {
|
||||
// update preview
|
||||
$('#preview').html('<img src="@helpers.assets("/common/images/indicator.gif")"> Previewing...');
|
||||
$.post('@helpers.url(repository)/_preview', {
|
||||
content : editor.getValue(),
|
||||
enableWikiLink : false,
|
||||
filename : $('#newFileName').val(),
|
||||
enableRefsLink : false,
|
||||
enableLineBreaks : false,
|
||||
enableTaskList : false
|
||||
}, function(data){
|
||||
$('#preview').empty().append(
|
||||
$('<div class="markdown-body" style="padding-left: 20px; padding-right: 20px;">').html(data));
|
||||
prettyPrint();
|
||||
});
|
||||
} else {
|
||||
// Show diff
|
||||
$('#preview').empty()
|
||||
.append($('<div id="diffText">'))
|
||||
.append($('<textarea id="newText" style="display: none;">').data('file-name',$("#newFileName").val()).data('val', editor.getValue()))
|
||||
.append($('<textarea id="oldText" style="display: none;">').data('file-name',$("#oldFileName").val()).data('val', $('#initial').val()));
|
||||
diffUsingJS('oldText', 'newText', 'diffText', 1);
|
||||
}
|
||||
$.get("@context.baseUrl/_is_renderable?filename=" + encodeURIComponent($('#newFileName').val()), function(data) {
|
||||
if (data === 'true') {
|
||||
// update preview
|
||||
$('#preview').html('<img src="@helpers.assets("/common/images/indicator.gif")"> Previewing...');
|
||||
$.post('@helpers.url(repository)/_preview', {
|
||||
content : editor.getValue(),
|
||||
enableWikiLink : false,
|
||||
filename : $('#path').val() + '/' + $('#newFileName').val(),
|
||||
enableRefsLink : false,
|
||||
enableLineBreaks : false,
|
||||
enableTaskList : false
|
||||
}, function(data){
|
||||
$('#preview').empty().append(
|
||||
$('<div class="markdown-body" style="padding-left: 20px; padding-right: 20px;">').html(data));
|
||||
prettyPrint();
|
||||
});
|
||||
} else {
|
||||
// Show diff
|
||||
$('#preview').empty()
|
||||
.append($('<div id="diffText">'))
|
||||
.append($('<textarea id="newText" style="display: none;">').data('file-name',$("#newFileName").val()).data('val', editor.getValue()))
|
||||
.append($('<textarea id="oldText" style="display: none;">').data('file-name',$("#oldFileName").val()).data('val', $('#initial').val()));
|
||||
diffUsingJS('oldText', 'newText', 'diffText', 1);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
<div class="head" style="height: 24px;">
|
||||
<div class="pull-right">
|
||||
<div class="btn-group">
|
||||
<a href="@{helpers.url(repository)}/archive@if(pathList.length > 0){/@pathList.map(helpers.urlEncode).mkString("/")}/@{helpers.urlEncode(branch)}.zip" class="btn btn-sm btn-default pc"><i class="octicon octicon-cloud-download"></i> Download ZIP</a>
|
||||
<a href="@{helpers.url(repository)}/archive/@{helpers.urlEncode(branch)}.zip@if(pathList.nonEmpty){?path=@helpers.urlEncode(pathList.mkString("/"))}" class="btn btn-sm btn-default pc"><i class="octicon octicon-cloud-download"></i> Download ZIP</a>
|
||||
<a href="@helpers.url(repository)/find/@helpers.encodeRefName(branch)" class="btn btn-sm btn-default" data-hotkey="t" title="Search files"><i class="octicon octicon-search" aria-label="Search files"></i></a>
|
||||
<a href="@helpers.url(repository)/commits/@helpers.encodeRefName((branch :: pathList).mkString("/"))" class="btn btn-sm btn-default"><i class="octicon octicon-history"></i> @if(commitCount > 10000){10000+} else {@commitCount} @helpers.plural(commitCount, "commit")</a>
|
||||
</div>
|
||||
@@ -220,7 +220,7 @@
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="box-content-bottom markdown-body" style="padding-left: 20px; padding-right: 20px;">@helpers.renderMarkup(filePath, content, branch, repository, false, false, true)</div>
|
||||
<div class="box-content-bottom markdown-body" style="padding-left: 20px; padding-right: 20px;">@helpers.renderMarkup(filePath, content, branch, repository, false, false, false, true, true)</div>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
@(repository: gitbucket.core.service.RepositoryService.RepositoryInfo,
|
||||
branch: String,
|
||||
protection: gitbucket.core.api.ApiBranchProtection,
|
||||
protection: gitbucket.core.api.ApiBranchProtectionResponse,
|
||||
knownContexts: Seq[String],
|
||||
info: Option[Any])(implicit context: gitbucket.core.controller.Context)
|
||||
@import gitbucket.core.view.helpers
|
||||
@@ -22,9 +22,44 @@
|
||||
</label>
|
||||
<p class="help-block">Disables force-pushes to this branch and prevents it from being deleted.</p>
|
||||
</div>
|
||||
|
||||
<!--====================================================================-->
|
||||
<!-- Enforce administrators -->
|
||||
<!--====================================================================-->
|
||||
<div class="checkbox js-enabled" style="display:none">
|
||||
<label>
|
||||
<input type="checkbox" name="has_required_statuses" onclick="update()" @check(protection.status.enforcement_level.name!="off") @if(knownContexts.isEmpty){disabled }>
|
||||
<input type="checkbox" name="enforce_for_admins" onclick="update()" @check(protection.enforce_admins.exists(_.enabled))>
|
||||
<span class="strong">Include administrators</span>
|
||||
</label>
|
||||
<p class="help-block">Enforce restrictions even for repository administrators.</p>
|
||||
</div>
|
||||
|
||||
<!--====================================================================-->
|
||||
<!-- Push restrictions -->
|
||||
<!--====================================================================-->
|
||||
<div class="checkbox js-enabled" style="display:none">
|
||||
<label>
|
||||
<input type="checkbox" name="restrictions" onclick="update()" @check(protection.restrictions.isDefined)>
|
||||
<span class="strong">Restrict users for push</span>
|
||||
</label>
|
||||
<p class="help-block">Restrict users who can push to this branch</p>
|
||||
<div class="js-restrictions_enabled" style="display: none;">
|
||||
<ul id="restrictions-user-list">
|
||||
</ul>
|
||||
@gitbucket.core.helper.html.account("userName-restrictions-user", 200, true, false)
|
||||
<input type="button" class="btn btn-default add-restrictions-user" value="Add"/>
|
||||
<div>
|
||||
<span class="error" id="error-restrictions-user"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!--====================================================================-->
|
||||
<!-- Status check -->
|
||||
<!--====================================================================-->
|
||||
<div class="checkbox js-enabled" style="display:none">
|
||||
<label>
|
||||
<input type="checkbox" name="has_required_statuses" onclick="update()" @check(protection.required_status_checks.isDefined) @if(knownContexts.isEmpty){disabled }>
|
||||
<span class="strong">Require status checks to pass before merging</span>
|
||||
</label>
|
||||
<p class="help-block">When enabled, commits must first be pushed to another branch, then merged or pushed directly to <b>@branch</b> after status checks have passed.</p>
|
||||
@@ -48,14 +83,6 @@
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" name="enforce_for_admins" onclick="update()" @check(protection.status.enforcement_level.name=="everyone")>
|
||||
<span class="strong">Include administrators</span>
|
||||
</label>
|
||||
<p class="help-block">Enforce required status checks for repository administrators.</p>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
@@ -68,59 +95,114 @@
|
||||
}
|
||||
<script>
|
||||
function getValue(){
|
||||
var v = {}, contexts=[];
|
||||
const v = {}, contexts = [];
|
||||
let restrictions = undefined;
|
||||
$("input[type=checkbox]:checked").each(function(){
|
||||
if(this.name === 'contexts'){
|
||||
if(this.name === 'contexts') {
|
||||
contexts.push(this.value);
|
||||
} else if (this.name === 'restrictions') {
|
||||
restrictions = $('#restrictions-user-list li').map(function(i, e){
|
||||
return $(e).data('name');
|
||||
}).get();
|
||||
} else {
|
||||
v[this.name] = true;
|
||||
}
|
||||
});
|
||||
|
||||
if(v.enabled){
|
||||
return {
|
||||
enabled: true,
|
||||
required_status_checks: {
|
||||
enforcement_level: v.has_required_statuses ? ((v.enforce_for_admins ? 'everyone' : 'non_admins')) : 'off',
|
||||
contexts: v.has_required_statuses ? contexts : []
|
||||
}
|
||||
};
|
||||
enforce_admins: v.enforce_for_admins,
|
||||
required_status_checks: v.has_required_statuses ? { contexts: contexts } : undefined,
|
||||
restrictions: restrictions ? { users: restrictions } : undefined
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
enabled: false,
|
||||
required_status_checks: {
|
||||
enforcement_level: "off",
|
||||
contexts: []
|
||||
}
|
||||
enabled: false
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function updateView(protection){
|
||||
$('.js-enabled').toggle(protection.enabled);
|
||||
$('.js-has_required_statuses').toggle(protection.required_status_checks.enforcement_level != 'off');
|
||||
$('.js-submit-btn').attr('disabled',protection.required_status_checks.enforcement_level != 'off' && protection.required_status_checks.contexts.length == 0);
|
||||
$('.js-restrictions_enabled').toggle(protection.restrictions !== undefined);
|
||||
$('.js-has_required_statuses').toggle(protection.required_status_checks !== undefined);
|
||||
}
|
||||
|
||||
function update(){
|
||||
var protection = getValue();
|
||||
const protection = getValue();
|
||||
updateView(protection);
|
||||
}
|
||||
$(update);
|
||||
|
||||
function submitForm(e){
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
var protection = getValue();
|
||||
const protection = getValue();
|
||||
$.ajax({
|
||||
method:'PATCH',
|
||||
url:'@context.path/api/v3/repos/@repository.owner/@repository.name/branches/@helpers.urlEncode(branch)',
|
||||
method: 'PATCH',
|
||||
url: '@context.path/api/v3/repos/@repository.owner/@repository.name/branches/@helpers.urlEncode(branch)',
|
||||
contentType: 'application/json',
|
||||
dataType: 'json',
|
||||
data:JSON.stringify({protection:protection}),
|
||||
success:function(r){
|
||||
data: JSON.stringify({protection: protection}),
|
||||
success: function(r){
|
||||
$('#saved-info').show();
|
||||
},
|
||||
error:function(err){
|
||||
error: function(err){
|
||||
console.log(err);
|
||||
alert('update error');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function addUserToListHTML(userName, id){
|
||||
$(id).append($('<li>').data('name', userName)
|
||||
.append(' ')
|
||||
.append(userName)
|
||||
.append($('<a href="#" onclick="$(this).parent().remove();" class="remove">(remove)</a>')));
|
||||
}
|
||||
|
||||
$(function() {
|
||||
// Initialize
|
||||
update();
|
||||
|
||||
@protection.restrictions.map(_.users).map { users =>
|
||||
@users.map { user =>
|
||||
addUserToListHTML('@user', '#restrictions-user-list');
|
||||
}
|
||||
}
|
||||
|
||||
$('.add-restrictions-user').click(function(){
|
||||
$('#error-restrictions-user').text('');
|
||||
const userName = $('#userName-restrictions-user').val();
|
||||
|
||||
// check empty
|
||||
if($.trim(userName) === ''){
|
||||
return false;
|
||||
}
|
||||
|
||||
// check duplication
|
||||
const exists = $('#restrictions-user-list li').filter(function(){
|
||||
return $(this).data('name') === userName;
|
||||
}).length > 0;
|
||||
if(exists){
|
||||
$('#error-restrictions-user').text('User has been already added.');
|
||||
return false;
|
||||
}
|
||||
|
||||
// check existence
|
||||
$.post('@context.path/_user/existence', {
|
||||
'userName': userName,
|
||||
'owner': '@repository.owner',
|
||||
'repository': '@repository.name'
|
||||
},
|
||||
function(data, status){
|
||||
if(data !== ''){
|
||||
addUserToListHTML(userName, '#restrictions-user-list');
|
||||
$('#userName-restrictions-user').val('');
|
||||
} else {
|
||||
$('#error-restrictions-user').text("User does not exist or isn't writable to this repository.");
|
||||
}
|
||||
});
|
||||
});
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -48,25 +48,25 @@ $(function(){
|
||||
});
|
||||
|
||||
$('.add').click(function(){
|
||||
var id = $(this).attr('id') == 'addCollaborator' ? 'collaborator' : 'group';
|
||||
const id = $(this).attr('id') === 'addCollaborator' ? 'collaborator' : 'group';
|
||||
|
||||
$('#error-' + id).text('');
|
||||
var userName = $('#userName-' + id).val();
|
||||
const userName = $('#userName-' + id).val();
|
||||
|
||||
// check empty
|
||||
if($.trim(userName) == ''){
|
||||
if($.trim(userName) === ''){
|
||||
return false;
|
||||
}
|
||||
|
||||
// check owner
|
||||
var owner = '@repository.owner' == userName
|
||||
const owner = '@repository.owner' === userName
|
||||
if(owner){
|
||||
$('#error-' + id).text('User is owner of this repository.');
|
||||
return false;
|
||||
}
|
||||
// check duplication
|
||||
var exists = $('#' + id + '-list li').filter(function(){
|
||||
return $(this).data('name') == userName;
|
||||
const exists = $('#' + id + '-list li').filter(function(){
|
||||
return $(this).data('name') === userName;
|
||||
}).length > 0;
|
||||
if(exists){
|
||||
$('#error-' + id).text('User has been already added.');
|
||||
@@ -76,7 +76,7 @@ $(function(){
|
||||
// check existence
|
||||
$.post('@context.path/_user/existence', { 'userName': userName },
|
||||
function(data, status){
|
||||
if(data != ''){
|
||||
if(data !== ''){
|
||||
addListHTML(userName, '@Role.ADMIN.name', '#' + id + '-list');
|
||||
$('#userName-' + id).val('');
|
||||
} else {
|
||||
@@ -91,7 +91,7 @@ $(function(){
|
||||
|
||||
// Don't submit form by ENTER key
|
||||
$('#userName-collaborator, #userName-group').keypress(function(e){
|
||||
return !(e.keyCode == 13);
|
||||
return !(e.keyCode === 13);
|
||||
});
|
||||
|
||||
@collaborators.map { case (collaborator, isGroup) =>
|
||||
@@ -99,16 +99,16 @@ $(function(){
|
||||
}
|
||||
|
||||
function addListHTML(userName, role, id){
|
||||
var adminButton = $('<label class="btn btn-default btn-mini"><input type="radio" value="@Role.ADMIN.name" name="' + userName + '">Admin</label>');
|
||||
if(role == '@Role.ADMIN.name'){
|
||||
const adminButton = $('<label class="btn btn-default btn-mini"><input type="radio" value="@Role.ADMIN.name" name="' + userName + '">Admin</label>');
|
||||
if(role === '@Role.ADMIN.name'){
|
||||
adminButton.addClass('active');
|
||||
}
|
||||
var writeButton = $('<label class="btn btn-default btn-mini"><input type="radio" value="@Role.DEVELOPER.name" name="' + userName + '">Developer</label>');
|
||||
if(role == '@Role.DEVELOPER.name'){
|
||||
const writeButton = $('<label class="btn btn-default btn-mini"><input type="radio" value="@Role.DEVELOPER.name" name="' + userName + '">Developer</label>');
|
||||
if(role === '@Role.DEVELOPER.name'){
|
||||
writeButton.addClass('active');
|
||||
}
|
||||
var readButton = $('<label class="btn btn-default btn-mini"><input type="radio" value="@Role.GUEST.name" name="' + userName + '">Guest</label>');
|
||||
if(role == '@Role.GUEST.name'){
|
||||
const readButton = $('<label class="btn btn-default btn-mini"><input type="radio" value="@Role.GUEST.name" name="' + userName + '">Guest</label>');
|
||||
if(role === '@Role.GUEST.name'){
|
||||
readButton.addClass('active');
|
||||
}
|
||||
|
||||
@@ -124,13 +124,13 @@ $(function(){
|
||||
}
|
||||
|
||||
function updateValues(){
|
||||
var collaborators = $('#collaborator-list li').map(function(i, e){
|
||||
var userName = $(e).data('name');
|
||||
const collaborators = $('#collaborator-list li').map(function(i, e){
|
||||
const userName = $(e).data('name');
|
||||
return userName + ':' + $(e).find('label.active input[type=radio]').attr('value');
|
||||
}).get().join(',');
|
||||
|
||||
var groups = $('#group-list li').map(function(i, e){
|
||||
var userName = $(e).data('name');
|
||||
const groups = $('#group-list li').map(function(i, e){
|
||||
const userName = $(e).data('name');
|
||||
return userName + ':' + $(e).find('label.active input[type=radio]').attr('value');
|
||||
}).get().join(',');
|
||||
|
||||
|
||||
@@ -59,7 +59,17 @@
|
||||
@if(isEditable){
|
||||
<a href="@helpers.url(repository)/wiki/_Sidebar/_edit" style="text-decoration: none;"><span class="octicon octicon-pencil pull-right"></span></a>
|
||||
}
|
||||
@helpers.markdown(sidebarPage.content, repository, "master", true, false, false, false, pages)
|
||||
@helpers.renderMarkup(
|
||||
filePath = sidebarPage.name.split("/").toList,
|
||||
fileContent = sidebarPage.content,
|
||||
branch = "master",
|
||||
repository = repository,
|
||||
enableWikiLink = true,
|
||||
enableRefsLink = false,
|
||||
enableAnchor = false,
|
||||
enableLineBreaks = false,
|
||||
enableTaskList = false
|
||||
)
|
||||
</div>
|
||||
}.getOrElse{
|
||||
@if(isEditable){
|
||||
@@ -82,16 +92,16 @@
|
||||
</div>
|
||||
<div class="wiki-main">
|
||||
<div class="markdown-body">
|
||||
@helpers.markdown(
|
||||
markdown = page.content,
|
||||
repository = repository,
|
||||
@helpers.renderMarkup(
|
||||
filePath = page.name.split("/").toList,
|
||||
fileContent = page.content,
|
||||
branch = "master",
|
||||
repository = repository,
|
||||
enableWikiLink = true,
|
||||
enableRefsLink = false,
|
||||
enableAnchor = true,
|
||||
enableLineBreaks = false,
|
||||
enableTaskList = false,
|
||||
hasWritePermission = false,
|
||||
pages = pages
|
||||
enableTaskList = false
|
||||
)
|
||||
</div>
|
||||
@footer.map { footerPage =>
|
||||
@@ -99,15 +109,16 @@
|
||||
@if(isEditable){
|
||||
<a href="@helpers.url(repository)/wiki/_Footer/_edit" style="text-decoration: none;"><span class="octicon octicon-pencil pull-right"></span></a>
|
||||
}
|
||||
@helpers.markdown(
|
||||
markdown = footerPage.content,
|
||||
repository = repository,
|
||||
branch = "master",
|
||||
enableWikiLink = true,
|
||||
enableRefsLink = false,
|
||||
@helpers.renderMarkup(
|
||||
filePath = footerPage.name.split("/").toList,
|
||||
fileContent = footerPage.content,
|
||||
branch = "master",
|
||||
repository = repository,
|
||||
enableWikiLink = true,
|
||||
enableRefsLink = false,
|
||||
enableAnchor = false,
|
||||
enableLineBreaks = false,
|
||||
enableAnchor = false,
|
||||
pages = pages
|
||||
enableTaskList = false
|
||||
)
|
||||
</div>
|
||||
}.getOrElse{
|
||||
|
||||
@@ -1536,7 +1536,6 @@ div.markdown-body table th,
|
||||
div.markdown-body table td {
|
||||
padding: 8px;
|
||||
line-height: 20px;
|
||||
text-align: left;
|
||||
vertical-align: top;
|
||||
border-top: 1px solid #dddddd;
|
||||
}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
package gitbucket.core
|
||||
|
||||
import java.sql.DriverManager
|
||||
|
||||
import com.dimafeng.testcontainers.{MySQLContainer, PostgreSQLContainer}
|
||||
import io.github.gitbucket.solidbase.Solidbase
|
||||
import io.github.gitbucket.solidbase.model.Module
|
||||
import liquibase.database.core.{H2Database, MySQLDatabase, PostgresDatabase}
|
||||
import org.junit.runner.Description
|
||||
import org.scalatest.funsuite.AnyFunSuite
|
||||
import org.scalatest.Tag
|
||||
import org.testcontainers.postgresql.PostgreSQLContainer
|
||||
import org.testcontainers.mysql.MySQLContainer
|
||||
import org.testcontainers.utility.DockerImageName
|
||||
|
||||
object ExternalDBTest extends Tag("ExternalDBTest")
|
||||
@@ -26,24 +26,19 @@ class GitBucketCoreModuleSpec extends AnyFunSuite {
|
||||
|
||||
implicit private val suiteDescription: Description = Description.createSuiteDescription(getClass)
|
||||
|
||||
Seq("8.0", "5.7").foreach { tag =>
|
||||
Seq("8.4", "5.7").foreach { tag =>
|
||||
test(s"Migration MySQL $tag", ExternalDBTest) {
|
||||
val container = new MySQLContainer() {
|
||||
override val container: org.testcontainers.containers.MySQLContainer[?] =
|
||||
new org.testcontainers.containers.MySQLContainer(s"mysql:$tag") {
|
||||
override def getDriverClassName = "org.mariadb.jdbc.Driver"
|
||||
override def getJdbcUrl: String = super.getJdbcUrl + "?permitMysqlScheme"
|
||||
}
|
||||
// TODO https://jira.mariadb.org/browse/CONJ-663
|
||||
container.withCommand("mysqld --default-authentication-plugin=mysql_native_password")
|
||||
val container = new MySQLContainer(s"mysql:$tag") {
|
||||
override def getDriverClassName = "org.mariadb.jdbc.Driver"
|
||||
override def getJdbcUrl: String = super.getJdbcUrl + "?permitMysqlScheme"
|
||||
}
|
||||
container.start()
|
||||
try {
|
||||
new Solidbase().migrate(
|
||||
DriverManager.getConnection(
|
||||
container.jdbcUrl,
|
||||
container.username,
|
||||
container.password
|
||||
container.getJdbcUrl,
|
||||
container.getUsername,
|
||||
container.getPassword
|
||||
),
|
||||
Thread.currentThread().getContextClassLoader(),
|
||||
new MySQLDatabase(),
|
||||
@@ -57,12 +52,12 @@ class GitBucketCoreModuleSpec extends AnyFunSuite {
|
||||
|
||||
Seq("11", "10").foreach { tag =>
|
||||
test(s"Migration PostgreSQL $tag", ExternalDBTest) {
|
||||
val container = PostgreSQLContainer(DockerImageName.parse(s"postgres:$tag"))
|
||||
val container = new PostgreSQLContainer(DockerImageName.parse(s"postgres:$tag"))
|
||||
|
||||
container.start()
|
||||
try {
|
||||
new Solidbase().migrate(
|
||||
DriverManager.getConnection(container.jdbcUrl, container.username, container.password),
|
||||
DriverManager.getConnection(container.getJdbcUrl, container.getUsername, container.getPassword),
|
||||
Thread.currentThread().getContextClassLoader(),
|
||||
new PostgresDatabase(),
|
||||
new Module(GitBucketCoreModule.getModuleId, GitBucketCoreModule.getVersions)
|
||||
|
||||
@@ -183,14 +183,14 @@ class ApiIntegrationTest extends AnyFunSuite {
|
||||
.branch("main")
|
||||
.content("create")
|
||||
.message("Create content")
|
||||
.path("README.md")
|
||||
.path("test.txt")
|
||||
.commit()
|
||||
|
||||
assert(createResult.getContent.isFile == true)
|
||||
assert(createResult.getContent.isFile)
|
||||
assert(IOUtils.toString(createResult.getContent.read(), "UTF-8") == "create")
|
||||
|
||||
val content1 = repo.getFileContent("README.md")
|
||||
assert(content1.isFile == true)
|
||||
val content1 = repo.getFileContent("test.txt")
|
||||
assert(content1.isFile)
|
||||
assert(IOUtils.toString(content1.read(), "UTF-8") == "create")
|
||||
assert(content1.getSha == createResult.getContent.getSha)
|
||||
|
||||
@@ -200,14 +200,14 @@ class ApiIntegrationTest extends AnyFunSuite {
|
||||
.branch("main")
|
||||
.content("update")
|
||||
.message("Update content")
|
||||
.path("README.md")
|
||||
.path("test.txt")
|
||||
.sha(content1.getSha)
|
||||
.commit()
|
||||
|
||||
assert(updateResult.getContent.isFile == true)
|
||||
assert(updateResult.getContent.isFile)
|
||||
assert(IOUtils.toString(updateResult.getContent.read(), "UTF-8") == "update")
|
||||
|
||||
val content2 = repo.getFileContent("README.md")
|
||||
val content2 = repo.getFileContent("test.txt")
|
||||
assert(content2.isFile == true)
|
||||
assert(IOUtils.toString(content2.read(), "UTF-8") == "update")
|
||||
assert(content2.getSha == updateResult.getContent.getSha)
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
package gitbucket.core.api
|
||||
|
||||
import java.util.{Calendar, Date, TimeZone}
|
||||
import gitbucket.core.api.ApiBranchProtectionResponse.Restrictions
|
||||
|
||||
import gitbucket.core.model._
|
||||
import java.util.{Calendar, Date, TimeZone}
|
||||
import gitbucket.core.model.*
|
||||
import gitbucket.core.plugin.PluginInfo
|
||||
import gitbucket.core.service.ProtectedBranchService.ProtectedBranchInfo
|
||||
import gitbucket.core.service.RepositoryService.RepositoryInfo
|
||||
@@ -15,7 +16,7 @@ object ApiSpecModels {
|
||||
|
||||
implicit val context: JsonFormat.Context = JsonFormat.Context("http://gitbucket.exmple.com", None)
|
||||
|
||||
val date1 = {
|
||||
val date1: Date = {
|
||||
val d = Calendar.getInstance(TimeZone.getTimeZone("UTC"))
|
||||
d.set(2011, 3, 14, 16, 0, 49)
|
||||
d.getTime
|
||||
@@ -29,7 +30,7 @@ object ApiSpecModels {
|
||||
|
||||
// Models
|
||||
|
||||
val account = Account(
|
||||
val account: Account = Account(
|
||||
userName = "octocat",
|
||||
fullName = "octocat",
|
||||
mailAddress = "octocat@example.com",
|
||||
@@ -45,10 +46,10 @@ object ApiSpecModels {
|
||||
description = None
|
||||
)
|
||||
|
||||
val sha1 = "6dcb09b5b57875f334f61aebed695e2e4193db5e"
|
||||
val repo1Name = RepositoryName("octocat/Hello-World")
|
||||
val sha1: String = "6dcb09b5b57875f334f61aebed695e2e4193db5e"
|
||||
val repo1Name: RepositoryName = RepositoryName("octocat/Hello-World")
|
||||
|
||||
val repository = Repository(
|
||||
val repository: Repository = Repository(
|
||||
userName = repo1Name.owner,
|
||||
repositoryName = repo1Name.name,
|
||||
isPrivate = false,
|
||||
@@ -73,7 +74,7 @@ object ApiSpecModels {
|
||||
)
|
||||
)
|
||||
|
||||
val repositoryInfo = RepositoryInfo(
|
||||
val repositoryInfo: RepositoryInfo = RepositoryInfo(
|
||||
owner = repo1Name.owner,
|
||||
name = repo1Name.name,
|
||||
repository = repository,
|
||||
@@ -101,7 +102,7 @@ object ApiSpecModels {
|
||||
managers = Seq("myboss")
|
||||
)
|
||||
|
||||
val label = Label(
|
||||
val label: Label = Label(
|
||||
userName = repo1Name.owner,
|
||||
repositoryName = repo1Name.name,
|
||||
labelId = 10,
|
||||
@@ -109,7 +110,7 @@ object ApiSpecModels {
|
||||
color = "f29513"
|
||||
)
|
||||
|
||||
val issue = Issue(
|
||||
val issue: Issue = Issue(
|
||||
userName = repo1Name.owner,
|
||||
repositoryName = repo1Name.name,
|
||||
issueId = 1347,
|
||||
@@ -124,14 +125,14 @@ object ApiSpecModels {
|
||||
isPullRequest = false
|
||||
)
|
||||
|
||||
val issuePR = issue.copy(
|
||||
val issuePR: Issue = issue.copy(
|
||||
title = "new-feature",
|
||||
content = Some("Please pull these awesome changes"),
|
||||
closed = true,
|
||||
isPullRequest = true
|
||||
)
|
||||
|
||||
val issueComment = IssueComment(
|
||||
val issueComment: IssueComment = IssueComment(
|
||||
userName = repo1Name.owner,
|
||||
repositoryName = repo1Name.name,
|
||||
issueId = issue.issueId,
|
||||
@@ -143,7 +144,7 @@ object ApiSpecModels {
|
||||
updatedDate = date1
|
||||
)
|
||||
|
||||
val pullRequest = PullRequest(
|
||||
val pullRequest: PullRequest = PullRequest(
|
||||
userName = repo1Name.owner,
|
||||
repositoryName = repo1Name.name,
|
||||
issueId = issuePR.issueId,
|
||||
@@ -156,7 +157,7 @@ object ApiSpecModels {
|
||||
isDraft = true
|
||||
)
|
||||
|
||||
val commitComment = CommitComment(
|
||||
val commitComment: CommitComment = CommitComment(
|
||||
userName = repo1Name.owner,
|
||||
repositoryName = repo1Name.name,
|
||||
commitId = sha1,
|
||||
@@ -174,7 +175,7 @@ object ApiSpecModels {
|
||||
originalNewLine = None
|
||||
)
|
||||
|
||||
val commitStatus = CommitStatus(
|
||||
val commitStatus: CommitStatus = CommitStatus(
|
||||
commitStatusId = 1,
|
||||
userName = repo1Name.owner,
|
||||
repositoryName = repo1Name.name,
|
||||
@@ -188,7 +189,7 @@ object ApiSpecModels {
|
||||
updatedDate = date1
|
||||
)
|
||||
|
||||
val milestone = Milestone(
|
||||
val milestone: Milestone = Milestone(
|
||||
userName = repo1Name.owner,
|
||||
repositoryName = repo1Name.name,
|
||||
milestoneId = 1,
|
||||
@@ -200,28 +201,28 @@ object ApiSpecModels {
|
||||
|
||||
// APIs
|
||||
|
||||
val apiUser = ApiUser(account)
|
||||
val apiUser: ApiUser = ApiUser(account)
|
||||
|
||||
val apiRepository = ApiRepository(
|
||||
val apiRepository: ApiRepository = ApiRepository(
|
||||
repository = repository,
|
||||
owner = apiUser,
|
||||
forkedCount = repositoryInfo.forkedCount,
|
||||
watchers = 0
|
||||
)
|
||||
|
||||
val apiLabel = ApiLabel(
|
||||
val apiLabel: ApiLabel = ApiLabel(
|
||||
label = label,
|
||||
repositoryName = repo1Name
|
||||
)
|
||||
|
||||
val apiMilestone = ApiMilestone(
|
||||
val apiMilestone: ApiMilestone = ApiMilestone(
|
||||
repository = repository,
|
||||
milestone = milestone,
|
||||
open_issue_count = 1,
|
||||
closed_issue_count = 1
|
||||
)
|
||||
|
||||
val apiIssue = ApiIssue(
|
||||
val apiIssue: ApiIssue = ApiIssue(
|
||||
issue = issue,
|
||||
repositoryName = repo1Name,
|
||||
user = apiUser,
|
||||
@@ -230,7 +231,7 @@ object ApiSpecModels {
|
||||
milestone = Some(apiMilestone)
|
||||
)
|
||||
|
||||
val apiNotAssignedIssue = ApiIssue(
|
||||
val apiNotAssignedIssue: ApiIssue = ApiIssue(
|
||||
issue = issue,
|
||||
repositoryName = repo1Name,
|
||||
user = apiUser,
|
||||
@@ -239,7 +240,7 @@ object ApiSpecModels {
|
||||
milestone = Some(apiMilestone)
|
||||
)
|
||||
|
||||
val apiIssuePR = ApiIssue(
|
||||
val apiIssuePR: ApiIssue = ApiIssue(
|
||||
issue = issuePR,
|
||||
repositoryName = repo1Name,
|
||||
user = apiUser,
|
||||
@@ -248,7 +249,7 @@ object ApiSpecModels {
|
||||
milestone = Some(apiMilestone)
|
||||
)
|
||||
|
||||
val apiComment = ApiComment(
|
||||
val apiComment: ApiComment = ApiComment(
|
||||
comment = issueComment,
|
||||
repositoryName = repo1Name,
|
||||
issueId = issueComment.issueId,
|
||||
@@ -256,7 +257,7 @@ object ApiSpecModels {
|
||||
isPullRequest = false
|
||||
)
|
||||
|
||||
val apiCommentPR = ApiComment(
|
||||
val apiCommentPR: ApiComment = ApiComment(
|
||||
comment = issueComment,
|
||||
repositoryName = repo1Name,
|
||||
issueId = issueComment.issueId,
|
||||
@@ -264,7 +265,7 @@ object ApiSpecModels {
|
||||
isPullRequest = true
|
||||
)
|
||||
|
||||
val apiPullRequest = ApiPullRequest(
|
||||
val apiPullRequest: ApiPullRequest = ApiPullRequest(
|
||||
issue = issuePR,
|
||||
pullRequest = pullRequest,
|
||||
headRepo = apiRepository,
|
||||
@@ -275,15 +276,14 @@ object ApiSpecModels {
|
||||
mergedComment = Some((issueComment, account))
|
||||
)
|
||||
|
||||
// https://developer.github.com/v3/activity/events/types/#pullrequestreviewcommentevent
|
||||
val apiPullRequestReviewComment = ApiPullRequestReviewComment(
|
||||
val apiPullRequestReviewComment: ApiPullRequestReviewComment = ApiPullRequestReviewComment(
|
||||
comment = commitComment,
|
||||
commentedUser = apiUser,
|
||||
repositoryName = repo1Name,
|
||||
issueId = commitComment.issueId.get
|
||||
)
|
||||
|
||||
val commitInfo = (id: String) =>
|
||||
val commitInfo: String => CommitInfo = (id: String) =>
|
||||
CommitInfo(
|
||||
id = id,
|
||||
shortMessage = "short message",
|
||||
@@ -299,12 +299,12 @@ object ApiSpecModels {
|
||||
None
|
||||
)
|
||||
|
||||
val apiCommitListItem = ApiCommitListItem(
|
||||
val apiCommitListItem: ApiCommitListItem = ApiCommitListItem(
|
||||
commit = commitInfo(sha1),
|
||||
repositoryName = repo1Name
|
||||
)
|
||||
|
||||
val apiCommit = {
|
||||
val apiCommit: ApiCommit = {
|
||||
val commit = commitInfo(sha1)
|
||||
ApiCommit(
|
||||
id = commit.id,
|
||||
@@ -318,7 +318,7 @@ object ApiSpecModels {
|
||||
)(repo1Name)
|
||||
}
|
||||
|
||||
val apiCommits = ApiCommits(
|
||||
val apiCommits: ApiCommits = ApiCommits(
|
||||
repositoryName = repo1Name,
|
||||
commitInfo = commitInfo(sha1),
|
||||
diffs = Seq(
|
||||
@@ -348,42 +348,45 @@ object ApiSpecModels {
|
||||
commentCount = 2
|
||||
)
|
||||
|
||||
val apiCommitStatus = ApiCommitStatus(
|
||||
val apiCommitStatus: ApiCommitStatus = ApiCommitStatus(
|
||||
status = commitStatus,
|
||||
creator = apiUser
|
||||
)
|
||||
|
||||
val apiCombinedCommitStatus = ApiCombinedCommitStatus(
|
||||
val apiCombinedCommitStatus: ApiCombinedCommitStatus = ApiCombinedCommitStatus(
|
||||
sha = sha1,
|
||||
statuses = Iterable((commitStatus, account)),
|
||||
repository = apiRepository
|
||||
)
|
||||
|
||||
val apiBranchProtectionOutput = ApiBranchProtection(
|
||||
val apiBranchProtectionOutput: ApiBranchProtectionResponse = ApiBranchProtectionResponse(
|
||||
info = ProtectedBranchInfo(
|
||||
owner = repo1Name.owner,
|
||||
repository = repo1Name.name,
|
||||
branch = "main",
|
||||
enabled = true,
|
||||
contexts = Seq("continuous-integration/travis-ci"),
|
||||
includeAdministrators = true
|
||||
contexts = Some(Seq("continuous-integration/travis-ci")),
|
||||
enforceAdmins = true,
|
||||
restrictionsUsers = Some(Seq("admin"))
|
||||
)
|
||||
)
|
||||
|
||||
val apiBranchProtectionInput = new ApiBranchProtection(
|
||||
val apiBranchProtectionInput: ApiBranchProtectionResponse = new ApiBranchProtectionResponse(
|
||||
url = None,
|
||||
enabled = true,
|
||||
required_status_checks = Some(
|
||||
ApiBranchProtection.Status(
|
||||
ApiBranchProtectionResponse.Status(
|
||||
url = None,
|
||||
enforcement_level = ApiBranchProtection.Everyone,
|
||||
enforcement_level = ApiBranchProtectionResponse.Everyone,
|
||||
contexts = Seq("continuous-integration/travis-ci"),
|
||||
contexts_url = None
|
||||
)
|
||||
)
|
||||
),
|
||||
restrictions = Some(Restrictions(users = Seq("admin"))),
|
||||
enforce_admins = None
|
||||
)
|
||||
|
||||
val apiBranch = ApiBranch(
|
||||
val apiBranch: ApiBranch = ApiBranch(
|
||||
name = "main",
|
||||
commit = ApiBranchCommit(sha1),
|
||||
protection = apiBranchProtectionOutput
|
||||
@@ -391,12 +394,12 @@ object ApiSpecModels {
|
||||
repositoryName = repo1Name
|
||||
)
|
||||
|
||||
val apiBranchForList = ApiBranchForList(
|
||||
val apiBranchForList: ApiBranchForList = ApiBranchForList(
|
||||
name = "main",
|
||||
commit = ApiBranchCommit(sha1)
|
||||
)
|
||||
|
||||
val apiContents = ApiContents(
|
||||
val apiContents: ApiContents = ApiContents(
|
||||
fileInfo = FileInfo(
|
||||
id = ObjectId.fromString(sha1),
|
||||
isDirectory = false,
|
||||
@@ -413,14 +416,14 @@ object ApiSpecModels {
|
||||
content = Some("README".getBytes("UTF-8"))
|
||||
)
|
||||
|
||||
val apiEndPoint = ApiEndPoint()
|
||||
val apiEndPoint: ApiEndPoint = ApiEndPoint()
|
||||
|
||||
val apiError = ApiError(
|
||||
val apiError: ApiError = ApiError(
|
||||
message = "A repository with this name already exists on this account",
|
||||
documentation_url = Some("https://developer.github.com/v3/repos/#create")
|
||||
)
|
||||
|
||||
val apiGroup = ApiGroup(
|
||||
val apiGroup: ApiGroup = ApiGroup(
|
||||
account.copy(
|
||||
isAdmin = true,
|
||||
isGroupAccount = true,
|
||||
@@ -428,7 +431,7 @@ object ApiSpecModels {
|
||||
)
|
||||
)
|
||||
|
||||
val apiPlugin = ApiPlugin(
|
||||
val apiPlugin: ApiPlugin = ApiPlugin(
|
||||
plugin = PluginInfo(
|
||||
pluginId = "gist",
|
||||
pluginName = "Gist Plugin",
|
||||
@@ -441,12 +444,12 @@ object ApiSpecModels {
|
||||
)
|
||||
)
|
||||
|
||||
val apiPusher = ApiPusher(account)
|
||||
val apiPusher: ApiPusher = ApiPusher(account)
|
||||
|
||||
// have both urls as https, as the expected samples are using https
|
||||
val gitHubContext = JsonFormat.Context("https://api.github.com", Some("https://api.github.com"))
|
||||
val gitHubContext: JsonFormat.Context = JsonFormat.Context("https://api.github.com", Some("https://api.github.com"))
|
||||
|
||||
val apiRefHeadsMaster = ApiRef(
|
||||
val apiRefHeadsMaster: ApiRef = ApiRef(
|
||||
ref = "refs/heads/main",
|
||||
url = ApiPath("/repos/gitbucket/gitbucket/git/refs/heads/main"),
|
||||
node_id = "MDM6UmVmOTM1MDc0NjpyZWZzL2hlYWRzL21hc3Rlcg==",
|
||||
@@ -457,7 +460,7 @@ object ApiSpecModels {
|
||||
)
|
||||
)
|
||||
|
||||
val apiRefTag = ApiRef(
|
||||
val apiRefTag: ApiRef = ApiRef(
|
||||
ref = "refs/tags/1.0",
|
||||
url = ApiPath("/repos/gitbucket/gitbucket/git/refs/tags/1.0"),
|
||||
node_id = "MDM6UmVmOTM1MDc0NjpyZWZzL3RhZ3MvMS4w",
|
||||
@@ -468,9 +471,9 @@ object ApiSpecModels {
|
||||
)
|
||||
)
|
||||
|
||||
val assetFileName = "010203040a0b0c0d"
|
||||
val assetFileName: String = "010203040a0b0c0d"
|
||||
|
||||
val apiReleaseAsset = ApiReleaseAsset(
|
||||
val apiReleaseAsset: ApiReleaseAsset = ApiReleaseAsset(
|
||||
name = "release.zip",
|
||||
size = 100
|
||||
)(
|
||||
@@ -479,7 +482,7 @@ object ApiSpecModels {
|
||||
repositoryName = repo1Name
|
||||
)
|
||||
|
||||
val apiRelease = ApiRelease(
|
||||
val apiRelease: ApiRelease = ApiRelease(
|
||||
name = "release1",
|
||||
tag_name = "tag1",
|
||||
body = Some("content"),
|
||||
@@ -489,7 +492,7 @@ object ApiSpecModels {
|
||||
|
||||
// JSON String for APIs
|
||||
|
||||
val jsonUser = """{
|
||||
val jsonUser: String = """{
|
||||
|"login":"octocat",
|
||||
|"email":"octocat@example.com",
|
||||
|"type":"User",
|
||||
@@ -501,7 +504,7 @@ object ApiSpecModels {
|
||||
|"avatar_url":"http://gitbucket.exmple.com/octocat/_avatar"
|
||||
|}""".stripMargin
|
||||
|
||||
val jsonRepository = s"""{
|
||||
val jsonRepository: String = s"""{
|
||||
|"name":"Hello-World",
|
||||
|"full_name":"octocat/Hello-World",
|
||||
|"description":"This your first repo!",
|
||||
@@ -519,10 +522,10 @@ object ApiSpecModels {
|
||||
|"html_url":"http://gitbucket.exmple.com/octocat/Hello-World"
|
||||
|}""".stripMargin
|
||||
|
||||
val jsonLabel =
|
||||
val jsonLabel: String =
|
||||
"""{"name":"bug","color":"f29513","url":"http://gitbucket.exmple.com/api/v3/repos/octocat/Hello-World/labels/bug"}"""
|
||||
|
||||
val jsonMilestone = """{
|
||||
val jsonMilestone: String = """{
|
||||
|"url":"http://gitbucket.exmple.com/api/v3/repos/octocat/Hello-World/milestones/1",
|
||||
|"html_url":"http://gitbucket.exmple.com/octocat/Hello-World/milestone/1",
|
||||
|"id":1,
|
||||
@@ -535,7 +538,7 @@ object ApiSpecModels {
|
||||
|"due_on":"2011-04-14T16:00:49Z"
|
||||
|}""".stripMargin
|
||||
|
||||
val jsonIssue = s"""{
|
||||
val jsonIssue: String = s"""{
|
||||
|"number":1347,
|
||||
|"title":"Found a bug",
|
||||
|"user":$jsonUser,
|
||||
@@ -552,7 +555,7 @@ object ApiSpecModels {
|
||||
|"html_url":"http://gitbucket.exmple.com/octocat/Hello-World/issues/1347"
|
||||
|}""".stripMargin
|
||||
|
||||
val jsonNotAssignedIssue = s"""{
|
||||
val jsonNotAssignedIssue: String = s"""{
|
||||
|"number":1347,
|
||||
|"title":"Found a bug",
|
||||
|"user":$jsonUser,
|
||||
@@ -568,7 +571,7 @@ object ApiSpecModels {
|
||||
|"html_url":"http://gitbucket.exmple.com/octocat/Hello-World/issues/1347"
|
||||
|}""".stripMargin
|
||||
|
||||
val jsonIssuePR = s"""{
|
||||
val jsonIssuePR: String = s"""{
|
||||
|"number":1347,
|
||||
|"title":"new-feature",
|
||||
|"user":$jsonUser,
|
||||
@@ -588,7 +591,7 @@ object ApiSpecModels {
|
||||
|"html_url":"http://gitbucket.exmple.com/octocat/Hello-World/pull/1347"}
|
||||
|}""".stripMargin
|
||||
|
||||
val jsonPullRequest = s"""{
|
||||
val jsonPullRequest: String = s"""{
|
||||
|"number":1347,
|
||||
|"state":"closed",
|
||||
|"updated_at":"2011-04-14T16:00:49Z",
|
||||
@@ -615,7 +618,7 @@ object ApiSpecModels {
|
||||
|"statuses_url":"http://gitbucket.exmple.com/api/v3/repos/octocat/Hello-World/statuses/6dcb09b5b57875f334f61aebed695e2e4193db5e"
|
||||
|}""".stripMargin
|
||||
|
||||
val jsonPullRequestReviewComment = s"""{
|
||||
val jsonPullRequestReviewComment: String = s"""{
|
||||
|"id":29724692,
|
||||
|"path":"README.md",
|
||||
|"commit_id":"6dcb09b5b57875f334f61aebed695e2e4193db5e",
|
||||
@@ -632,7 +635,7 @@ object ApiSpecModels {
|
||||
|"pull_request":{"href":"http://gitbucket.exmple.com/api/v3/repos/octocat/Hello-World/pulls/1347"}}
|
||||
|}""".stripMargin
|
||||
|
||||
val jsonComment = s"""{
|
||||
val jsonComment: String = s"""{
|
||||
|"id":1,
|
||||
|"user":$jsonUser,
|
||||
|"body":"Me too",
|
||||
@@ -641,7 +644,7 @@ object ApiSpecModels {
|
||||
|"html_url":"http://gitbucket.exmple.com/octocat/Hello-World/issues/1347#comment-1"
|
||||
|}""".stripMargin
|
||||
|
||||
val jsonCommentPR = s"""{
|
||||
val jsonCommentPR: String = s"""{
|
||||
|"id":1,
|
||||
|"user":$jsonUser,
|
||||
|"body":"Me too",
|
||||
@@ -650,7 +653,7 @@ object ApiSpecModels {
|
||||
|"html_url":"http://gitbucket.exmple.com/octocat/Hello-World/pull/1347#comment-1"
|
||||
|}""".stripMargin
|
||||
|
||||
val jsonCommitListItem = s"""{
|
||||
val jsonCommitListItem: String = s"""{
|
||||
|"sha":"6dcb09b5b57875f334f61aebed695e2e4193db5e",
|
||||
|"commit":{
|
||||
|"message":"full message",
|
||||
@@ -664,7 +667,7 @@ object ApiSpecModels {
|
||||
|"url":"http://gitbucket.exmple.com/api/v3/repos/octocat/Hello-World/commits/6dcb09b5b57875f334f61aebed695e2e4193db5e"
|
||||
|}""".stripMargin
|
||||
|
||||
val jsonCommit = (id: String) => s"""{
|
||||
val jsonCommit: String => String = (id: String) => s"""{
|
||||
|"id":"$id",
|
||||
|"message":"full message",
|
||||
|"timestamp":"2011-04-14T16:00:49Z",
|
||||
@@ -677,7 +680,7 @@ object ApiSpecModels {
|
||||
|"html_url":"http://gitbucket.exmple.com/octocat/Hello-World/commit/$id"
|
||||
|}""".stripMargin
|
||||
|
||||
val jsonCommits = s"""{
|
||||
val jsonCommits: String = s"""{
|
||||
|"url":"http://gitbucket.exmple.com/api/v3/repos/octocat/Hello-World/commits/6dcb09b5b57875f334f61aebed695e2e4193db5e",
|
||||
|"sha":"6dcb09b5b57875f334f61aebed695e2e4193db5e",
|
||||
|"html_url":"http://gitbucket.exmple.com/octocat/Hello-World/commit/6dcb09b5b57875f334f61aebed695e2e4193db5e",
|
||||
@@ -707,7 +710,7 @@ object ApiSpecModels {
|
||||
|"patch":"@@ -1 +1,2 @@\\n-body1\\n\\\\ No newline at end of file\\n+body1\\n+body2\\n\\\\ No newline at end of file"}]
|
||||
|}""".stripMargin
|
||||
|
||||
val jsonCommitStatus = s"""{
|
||||
val jsonCommitStatus: String = s"""{
|
||||
|"created_at":"2011-04-14T16:00:49Z",
|
||||
|"updated_at":"2011-04-14T16:00:49Z",
|
||||
|"state":"success",
|
||||
@@ -719,7 +722,7 @@ object ApiSpecModels {
|
||||
|"url":"http://gitbucket.exmple.com/api/v3/repos/octocat/Hello-World/commits/6dcb09b5b57875f334f61aebed695e2e4193db5e/statuses"
|
||||
|}""".stripMargin
|
||||
|
||||
val jsonCombinedCommitStatus = s"""{
|
||||
val jsonCombinedCommitStatus: String = s"""{
|
||||
|"state":"success",
|
||||
|"sha":"6dcb09b5b57875f334f61aebed695e2e4193db5e",
|
||||
|"total_count":1,
|
||||
@@ -728,7 +731,7 @@ object ApiSpecModels {
|
||||
|"url":"http://gitbucket.exmple.com/api/v3/repos/octocat/Hello-World/commits/6dcb09b5b57875f334f61aebed695e2e4193db5e/status"
|
||||
|}""".stripMargin
|
||||
|
||||
val jsonBranchProtectionOutput =
|
||||
val jsonBranchProtectionOutput: String =
|
||||
"""{
|
||||
|"url":"http://gitbucket.exmple.com/api/v3/repos/octocat/Hello-World/branches/main/protection",
|
||||
|"enabled":true,
|
||||
@@ -736,15 +739,25 @@ object ApiSpecModels {
|
||||
|"url":"http://gitbucket.exmple.com/api/v3/repos/octocat/Hello-World/branches/main/protection/required_status_checks",
|
||||
|"enforcement_level":"everyone",
|
||||
|"contexts":["continuous-integration/travis-ci"],
|
||||
|"contexts_url":"http://gitbucket.exmple.com/api/v3/repos/octocat/Hello-World/branches/main/protection/required_status_checks/contexts"}
|
||||
|"contexts_url":"http://gitbucket.exmple.com/api/v3/repos/octocat/Hello-World/branches/main/protection/required_status_checks/contexts"
|
||||
|},
|
||||
|"restrictions":{
|
||||
|"users":["admin"]
|
||||
|},
|
||||
|"enforce_admins":{
|
||||
|"enabled":true
|
||||
|}
|
||||
|}""".stripMargin
|
||||
|
||||
val jsonBranchProtectionInput =
|
||||
val jsonBranchProtectionInput: String =
|
||||
"""{
|
||||
|"enabled":true,
|
||||
|"required_status_checks":{
|
||||
|"enforcement_level":"everyone",
|
||||
|"contexts":["continuous-integration/travis-ci"]
|
||||
|},
|
||||
|"restrictions":{
|
||||
|"users":["admin"]
|
||||
|}
|
||||
|}""".stripMargin
|
||||
|
||||
@@ -757,9 +770,9 @@ object ApiSpecModels {
|
||||
|"html":"http://gitbucket.exmple.com/octocat/Hello-World/tree/main"}
|
||||
|}""".stripMargin
|
||||
|
||||
val jsonBranchForList = """{"name":"main","commit":{"sha":"6dcb09b5b57875f334f61aebed695e2e4193db5e"}}"""
|
||||
val jsonBranchForList: String = """{"name":"main","commit":{"sha":"6dcb09b5b57875f334f61aebed695e2e4193db5e"}}"""
|
||||
|
||||
val jsonContents =
|
||||
val jsonContents: String =
|
||||
"""{
|
||||
|"type":"file",
|
||||
|"name":"README.md",
|
||||
@@ -770,14 +783,14 @@ object ApiSpecModels {
|
||||
|"download_url":"http://gitbucket.exmple.com/api/v3/repos/octocat/Hello-World/raw/6dcb09b5b57875f334f61aebed695e2e4193db5e/doc/README.md"
|
||||
|}""".stripMargin
|
||||
|
||||
val jsonEndPoint = """{"rate_limit_url":"http://gitbucket.exmple.com/api/v3/rate_limit"}"""
|
||||
val jsonEndPoint: String = """{"rate_limit_url":"http://gitbucket.exmple.com/api/v3/rate_limit"}"""
|
||||
|
||||
val jsonError = """{
|
||||
val jsonError: String = """{
|
||||
|"message":"A repository with this name already exists on this account",
|
||||
|"documentation_url":"https://developer.github.com/v3/repos/#create"
|
||||
|}""".stripMargin
|
||||
|
||||
val jsonGroup = """{
|
||||
val jsonGroup: String = """{
|
||||
|"login":"octocat",
|
||||
|"description":"Admin group",
|
||||
|"created_at":"2011-04-14T16:00:49Z",
|
||||
@@ -787,7 +800,7 @@ object ApiSpecModels {
|
||||
|"avatar_url":"http://gitbucket.exmple.com/octocat/_avatar"
|
||||
|}""".stripMargin
|
||||
|
||||
val jsonPlugin = """{
|
||||
val jsonPlugin: String = """{
|
||||
|"id":"gist",
|
||||
|"name":"Gist Plugin",
|
||||
|"version":"4.16.0",
|
||||
@@ -795,12 +808,12 @@ object ApiSpecModels {
|
||||
|"jarFileName":"gitbucket-gist-plugin-gitbucket_4.30.0-SNAPSHOT-4.17.0.jar"
|
||||
|}""".stripMargin
|
||||
|
||||
val jsonPusher = """{"name":"octocat","email":"octocat@example.com"}"""
|
||||
val jsonPusher: String = """{"name":"octocat","email":"octocat@example.com"}"""
|
||||
|
||||
// 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: String = """{"ref":"refs/heads/featureA","object":{"sha":"6dcb09b5b57875f334f61aebed695e2e4193db5e"}}"""
|
||||
|
||||
val jsonRefHeadsMain =
|
||||
val jsonRefHeadsMain: String =
|
||||
"""{
|
||||
|"ref": "refs/heads/main",
|
||||
|"node_id": "MDM6UmVmOTM1MDc0NjpyZWZzL2hlYWRzL21hc3Rlcg==",
|
||||
@@ -812,7 +825,7 @@ object ApiSpecModels {
|
||||
|}
|
||||
|}""".stripMargin
|
||||
|
||||
val jsonRefTag =
|
||||
val jsonRefTag: String =
|
||||
"""{
|
||||
|"ref": "refs/tags/1.0",
|
||||
|"node_id": "MDM6UmVmOTM1MDc0NjpyZWZzL3RhZ3MvMS4w",
|
||||
@@ -824,7 +837,7 @@ object ApiSpecModels {
|
||||
|}
|
||||
|}""".stripMargin
|
||||
|
||||
val jsonReleaseAsset =
|
||||
val jsonReleaseAsset: String =
|
||||
s"""{
|
||||
|"name":"release.zip",
|
||||
|"size":100,
|
||||
@@ -833,7 +846,7 @@ object ApiSpecModels {
|
||||
|"browser_download_url":"http://gitbucket.exmple.com/octocat/Hello-World/releases/tag1/assets/${assetFileName}"
|
||||
|}""".stripMargin
|
||||
|
||||
val jsonRelease =
|
||||
val jsonRelease: String =
|
||||
s"""{
|
||||
|"name":"release1",
|
||||
|"tag_name":"tag1",
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
package gitbucket.core.api
|
||||
|
||||
import org.json4s.Formats
|
||||
import org.json4s.{Formats, JValue, jvalue2extractable}
|
||||
import org.json4s.jackson.JsonMethods
|
||||
import org.json4s.jvalue2extractable
|
||||
import org.scalatest.Assertion
|
||||
import org.scalatest.funsuite.AnyFunSuite
|
||||
|
||||
class JsonFormatSpec extends AnyFunSuite {
|
||||
import ApiSpecModels._
|
||||
import ApiSpecModels.*
|
||||
implicit val format: Formats = JsonFormat.jsonFormats
|
||||
|
||||
private def expected(json: String) = json.replaceAll("\n", "")
|
||||
def normalizeJson(json: String) = {
|
||||
def normalizeJson(json: String): JValue = {
|
||||
org.json4s.jackson.parseJson(json)
|
||||
}
|
||||
def assertEqualJson(actual: String, expected: String) = {
|
||||
def assertEqualJson(actual: String, expected: String): Assertion = {
|
||||
assert(normalizeJson(actual) == normalizeJson(expected))
|
||||
}
|
||||
|
||||
@@ -57,7 +57,9 @@ class JsonFormatSpec extends AnyFunSuite {
|
||||
assert(JsonFormat(apiBranchProtectionOutput) == expected(jsonBranchProtectionOutput))
|
||||
}
|
||||
test("deserialize apiBranchProtection") {
|
||||
assert(JsonMethods.parse(jsonBranchProtectionInput).extract[ApiBranchProtection] == apiBranchProtectionInput)
|
||||
assert(
|
||||
JsonMethods.parse(jsonBranchProtectionInput).extract[ApiBranchProtectionResponse] == apiBranchProtectionInput
|
||||
)
|
||||
}
|
||||
test("apiBranch") {
|
||||
assert(JsonFormat(apiBranch) == expected(jsonBranch))
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package gitbucket.core.service
|
||||
|
||||
import gitbucket.core.model._
|
||||
import gitbucket.core.model.*
|
||||
import org.scalatest.funsuite.AnyFunSuite
|
||||
import gitbucket.core.model.Profile._
|
||||
import gitbucket.core.model.Profile.profile.blockingApi._
|
||||
import gitbucket.core.model.Profile.*
|
||||
import gitbucket.core.model.Profile.profile.blockingApi.*
|
||||
|
||||
class AccessTokenServiceSpec extends AnyFunSuite with ServiceSpecBase {
|
||||
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
package gitbucket.core.service
|
||||
|
||||
import gitbucket.core.model._
|
||||
import gitbucket.core.model.Profile._
|
||||
import gitbucket.core.model.Profile.profile.blockingApi._
|
||||
import gitbucket.core.model.*
|
||||
import gitbucket.core.model.Profile.*
|
||||
import gitbucket.core.model.Profile.profile.blockingApi.*
|
||||
import gitbucket.core.model.Session
|
||||
import org.scalatest.funsuite.AnyFunSuite
|
||||
|
||||
class CommitStatusServiceSpec
|
||||
@@ -12,7 +13,7 @@ class CommitStatusServiceSpec
|
||||
with RepositoryService
|
||||
with AccountService {
|
||||
val now = new java.util.Date()
|
||||
val fixture1 = CommitStatus(
|
||||
val fixture1: CommitStatus = CommitStatus(
|
||||
userName = "root",
|
||||
repositoryName = "repo",
|
||||
commitId = "0e97b8f59f7cdd709418bb59de53f741fd1c1bd7",
|
||||
@@ -24,8 +25,9 @@ class CommitStatusServiceSpec
|
||||
updatedDate = now,
|
||||
registeredDate = now
|
||||
)
|
||||
def findById(id: Int)(implicit s: Session) = CommitStatuses.filter(_.byPrimaryKey(id)).firstOption
|
||||
def generateFixture1(tester: Account)(implicit s: Session) =
|
||||
def findById(id: Int)(implicit s: Session): Option[CommitStatus] =
|
||||
CommitStatuses.filter(_.byPrimaryKey(id)).firstOption
|
||||
def generateFixture1(tester: Account)(implicit s: Session): Int =
|
||||
createCommitStatus(
|
||||
userName = fixture1.userName,
|
||||
repositoryName = fixture1.repositoryName,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package gitbucket.core.service
|
||||
|
||||
import gitbucket.core.model._
|
||||
import gitbucket.core.model.*
|
||||
import org.scalatest.funspec.AnyFunSpec
|
||||
|
||||
class LabelsServiceSpec extends AnyFunSpec with ServiceSpecBase {
|
||||
|
||||
@@ -1,31 +1,32 @@
|
||||
package gitbucket.core.service
|
||||
|
||||
import org.eclipse.jgit.api.Git
|
||||
import org.eclipse.jgit.revwalk._
|
||||
import org.eclipse.jgit.revwalk.*
|
||||
import org.eclipse.jetty.server.handler.AbstractHandler
|
||||
import org.scalatest.funspec.AnyFunSpec
|
||||
|
||||
import java.io.File
|
||||
import java.util.Date
|
||||
import java.net.InetSocketAddress
|
||||
import java.nio.charset.StandardCharsets
|
||||
|
||||
import javax.servlet.http.{HttpServletRequest, HttpServletResponse}
|
||||
|
||||
import scala.util.Using
|
||||
import scala.jdk.CollectionConverters._
|
||||
import scala.jdk.CollectionConverters.*
|
||||
import gitbucket.core.controller.Context
|
||||
import gitbucket.core.plugin.ReceiveHook
|
||||
import gitbucket.core.util.Directory._
|
||||
import gitbucket.core.util.GitSpecUtil._
|
||||
import gitbucket.core.util.Directory.*
|
||||
import gitbucket.core.util.GitSpecUtil.*
|
||||
import gitbucket.core.service.RepositoryService.RepositoryInfo
|
||||
import gitbucket.core.model._
|
||||
import gitbucket.core.model.Profile._
|
||||
import gitbucket.core.model.Profile.profile.blockingApi._
|
||||
import gitbucket.core.model.*
|
||||
import gitbucket.core.model.Profile.*
|
||||
import gitbucket.core.model.Profile.profile.blockingApi.*
|
||||
import gitbucket.core.model.Session
|
||||
import org.eclipse.jetty.webapp.WebAppContext
|
||||
import org.eclipse.jetty.server.{Request, Server}
|
||||
import org.json4s.jackson.JsonMethods._
|
||||
import org.json4s.jvalue2monadic
|
||||
import MergeServiceSpec._
|
||||
import org.json4s.jackson.JsonMethods.*
|
||||
import org.json4s.{Formats, jvalue2monadic}
|
||||
import MergeServiceSpec.*
|
||||
import org.eclipse.jgit.lib.ObjectId
|
||||
import org.json4s.JsonAST.{JArray, JString}
|
||||
|
||||
class MergeServiceSpec extends AnyFunSpec with ServiceSpecBase {
|
||||
@@ -54,7 +55,7 @@ class MergeServiceSpec extends AnyFunSpec with ServiceSpecBase {
|
||||
}
|
||||
dir
|
||||
}
|
||||
def createConfrict(git: Git) = {
|
||||
def createConfrict(git: Git): ObjectId = {
|
||||
createFile(git, s"refs/heads/${branch}", "test.txt", "hoge2")
|
||||
createFile(git, s"refs/pull/${issueId}/head", "test.txt", "hoge4")
|
||||
}
|
||||
@@ -141,14 +142,14 @@ class MergeServiceSpec extends AnyFunSpec with ServiceSpecBase {
|
||||
}
|
||||
describe("mergePullRequest") {
|
||||
it("can merge") {
|
||||
implicit val jsonFormats = gitbucket.core.api.JsonFormat.jsonFormats
|
||||
import gitbucket.core.util.Implicits._
|
||||
implicit val jsonFormats: Formats = gitbucket.core.api.JsonFormat.jsonFormats
|
||||
import gitbucket.core.util.Implicits.*
|
||||
|
||||
withTestDB { implicit session =>
|
||||
generateNewUserWithDBRepository("user1", "repo8")
|
||||
initRepository("user1", "repo8")
|
||||
|
||||
implicit val context = Context(
|
||||
implicit val context: Context = Context(
|
||||
createSystemSettings(),
|
||||
Some(createAccount("dummy2", "dummy2-fullname", "dummy2@example.com")),
|
||||
request
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package gitbucket.core.service
|
||||
|
||||
import gitbucket.core.util.GitSpecUtil._
|
||||
import gitbucket.core.util.GitSpecUtil.*
|
||||
import org.eclipse.jgit.transport.{ReceivePack, ReceiveCommand}
|
||||
import org.eclipse.jgit.lib.ObjectId
|
||||
import gitbucket.core.model.CommitState
|
||||
@@ -29,26 +29,28 @@ class ProtectedBranchServiceSpec
|
||||
it("should enable and update and disable") {
|
||||
withTestDB { implicit session =>
|
||||
generateNewUserWithDBRepository("user1", "repo1")
|
||||
enableBranchProtection("user1", "repo1", "branch", false, Nil)
|
||||
enableBranchProtection("user1", "repo1", "branch", false, false, Nil, false, Nil)
|
||||
assert(
|
||||
getProtectedBranchInfo("user1", "repo1", "branch") == ProtectedBranchInfo(
|
||||
"user1",
|
||||
"repo1",
|
||||
"branch",
|
||||
true,
|
||||
Nil,
|
||||
false
|
||||
None,
|
||||
false,
|
||||
None
|
||||
)
|
||||
)
|
||||
enableBranchProtection("user1", "repo1", "branch", true, Seq("hoge", "huge"))
|
||||
enableBranchProtection("user1", "repo1", "branch", true, true, Seq("hoge", "huge"), false, Nil)
|
||||
assert(
|
||||
getProtectedBranchInfo("user1", "repo1", "branch") == ProtectedBranchInfo(
|
||||
"user1",
|
||||
"repo1",
|
||||
"branch",
|
||||
true,
|
||||
Seq("hoge", "huge"),
|
||||
true
|
||||
enabled = true,
|
||||
contexts = Some(Seq("hoge", "huge")),
|
||||
enforceAdmins = true,
|
||||
restrictionsUsers = None
|
||||
)
|
||||
)
|
||||
disableBranchProtection("user1", "repo1", "branch")
|
||||
@@ -57,21 +59,21 @@ class ProtectedBranchServiceSpec
|
||||
)
|
||||
}
|
||||
}
|
||||
it("should empty contexts is no-include-administrators") {
|
||||
it("should empty contexts is include-administrators") {
|
||||
withTestDB { implicit session =>
|
||||
generateNewUserWithDBRepository("user1", "repo1")
|
||||
enableBranchProtection("user1", "repo1", "branch", false, Nil)
|
||||
assert(getProtectedBranchInfo("user1", "repo1", "branch").includeAdministrators == false)
|
||||
enableBranchProtection("user1", "repo1", "branch", true, Nil)
|
||||
assert(getProtectedBranchInfo("user1", "repo1", "branch").includeAdministrators == false)
|
||||
enableBranchProtection("user1", "repo1", "branch", false, false, Nil, false, Nil)
|
||||
assert(!getProtectedBranchInfo("user1", "repo1", "branch").enforceAdmins)
|
||||
enableBranchProtection("user1", "repo1", "branch", true, false, Nil, false, Nil)
|
||||
assert(getProtectedBranchInfo("user1", "repo1", "branch").enforceAdmins)
|
||||
}
|
||||
}
|
||||
it("getProtectedBranchList") {
|
||||
withTestDB { implicit session =>
|
||||
generateNewUserWithDBRepository("user1", "repo1")
|
||||
enableBranchProtection("user1", "repo1", "branch", false, Nil)
|
||||
enableBranchProtection("user1", "repo1", "branch2", false, Seq("fuga"))
|
||||
enableBranchProtection("user1", "repo1", "branch3", true, Seq("hoge"))
|
||||
enableBranchProtection("user1", "repo1", "branch", false, false, Nil, false, Nil)
|
||||
enableBranchProtection("user1", "repo1", "branch2", false, false, Seq("fuga"), false, Nil)
|
||||
enableBranchProtection("user1", "repo1", "branch3", true, false, Seq("hoge"), false, Nil)
|
||||
assert(getProtectedBranchList("user1", "repo1").toSet == Set("branch", "branch2", "branch3"))
|
||||
}
|
||||
}
|
||||
@@ -87,12 +89,12 @@ class ProtectedBranchServiceSpec
|
||||
ReceiveCommand.Type.UPDATE_NONFASTFORWARD
|
||||
)
|
||||
generateNewUserWithDBRepository("user1", "repo1")
|
||||
assert(receiveHook.preReceive("user1", "repo1", rp, rc, "user1", false) == None)
|
||||
enableBranchProtection("user1", "repo1", "branch", false, Nil)
|
||||
assert(receiveHook.preReceive("user1", "repo1", rp, rc, "user1", false).isEmpty)
|
||||
enableBranchProtection("user1", "repo1", "branch", false, false, Nil, false, Nil)
|
||||
assert(
|
||||
receiveHook.preReceive("user1", "repo1", rp, rc, "user1", false) == Some(
|
||||
"Cannot force-push to a protected branch"
|
||||
)
|
||||
receiveHook
|
||||
.preReceive("user1", "repo1", rp, rc, "user1", false)
|
||||
.contains("Cannot force-push to a protected branch")
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -109,12 +111,12 @@ class ProtectedBranchServiceSpec
|
||||
ReceiveCommand.Type.UPDATE_NONFASTFORWARD
|
||||
)
|
||||
generateNewUserWithDBRepository("user1", "repo1")
|
||||
assert(receiveHook.preReceive("user1", "repo1", rp, rc, "user2", false) == None)
|
||||
enableBranchProtection("user1", "repo1", "branch", false, Nil)
|
||||
assert(receiveHook.preReceive("user1", "repo1", rp, rc, "user2", false).isEmpty)
|
||||
enableBranchProtection("user1", "repo1", "branch", false, false, Nil, false, Nil)
|
||||
assert(
|
||||
receiveHook.preReceive("user1", "repo1", rp, rc, "user2", false) == Some(
|
||||
"Cannot force-push to a protected branch"
|
||||
)
|
||||
receiveHook
|
||||
.preReceive("user1", "repo1", rp, rc, "user2", false)
|
||||
.contains("Cannot force-push to a protected branch")
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -131,33 +133,33 @@ class ProtectedBranchServiceSpec
|
||||
ReceiveCommand.Type.UPDATE
|
||||
)
|
||||
val user1 = generateNewUserWithDBRepository("user1", "repo1")
|
||||
assert(receiveHook.preReceive("user1", "repo1", rp, rc, "user2", false) == None)
|
||||
enableBranchProtection("user1", "repo1", "branch", false, Seq("must"))
|
||||
assert(receiveHook.preReceive("user1", "repo1", rp, rc, "user2", false).isEmpty)
|
||||
enableBranchProtection("user1", "repo1", "branch", false, true, Seq("must"), false, Nil)
|
||||
assert(
|
||||
receiveHook.preReceive("user1", "repo1", rp, rc, "user2", false) == Some(
|
||||
"Required status check \"must\" is expected"
|
||||
)
|
||||
receiveHook
|
||||
.preReceive("user1", "repo1", rp, rc, "user2", false)
|
||||
.contains("Required status check \"must\" is expected")
|
||||
)
|
||||
enableBranchProtection("user1", "repo1", "branch", false, Seq("must", "must2"))
|
||||
enableBranchProtection("user1", "repo1", "branch", false, true, Seq("must", "must2"), false, Nil)
|
||||
assert(
|
||||
receiveHook.preReceive("user1", "repo1", rp, rc, "user2", false) == Some(
|
||||
"2 of 2 required status checks are expected"
|
||||
)
|
||||
receiveHook
|
||||
.preReceive("user1", "repo1", rp, rc, "user2", false)
|
||||
.contains("2 of 2 required status checks are expected")
|
||||
)
|
||||
createCommitStatus("user1", "repo1", sha2, "context", CommitState.SUCCESS, None, None, now, user1)
|
||||
assert(
|
||||
receiveHook.preReceive("user1", "repo1", rp, rc, "user2", false) == Some(
|
||||
"2 of 2 required status checks are expected"
|
||||
)
|
||||
receiveHook
|
||||
.preReceive("user1", "repo1", rp, rc, "user2", false)
|
||||
.contains("2 of 2 required status checks are expected")
|
||||
)
|
||||
createCommitStatus("user1", "repo1", sha2, "must", CommitState.SUCCESS, None, None, now, user1)
|
||||
assert(
|
||||
receiveHook.preReceive("user1", "repo1", rp, rc, "user2", false) == Some(
|
||||
"Required status check \"must2\" is expected"
|
||||
)
|
||||
receiveHook
|
||||
.preReceive("user1", "repo1", rp, rc, "user2", false)
|
||||
.contains("Required status check \"must2\" is expected")
|
||||
)
|
||||
createCommitStatus("user1", "repo1", sha2, "must2", CommitState.SUCCESS, None, None, now, user1)
|
||||
assert(receiveHook.preReceive("user1", "repo1", rp, rc, "user2", false) == None)
|
||||
assert(receiveHook.preReceive("user1", "repo1", rp, rc, "user2", false).isEmpty)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -173,37 +175,60 @@ class ProtectedBranchServiceSpec
|
||||
ReceiveCommand.Type.UPDATE
|
||||
)
|
||||
val user1 = generateNewUserWithDBRepository("user1", "repo1")
|
||||
assert(receiveHook.preReceive("user1", "repo1", rp, rc, "user1", false) == None)
|
||||
enableBranchProtection("user1", "repo1", "branch", false, Seq("must"))
|
||||
assert(receiveHook.preReceive("user1", "repo1", rp, rc, "user1", false) == None)
|
||||
enableBranchProtection("user1", "repo1", "branch", true, Seq("must"))
|
||||
assert(receiveHook.preReceive("user1", "repo1", rp, rc, "user1", false).isEmpty)
|
||||
enableBranchProtection("user1", "repo1", "branch", false, true, Seq("must"), false, Nil)
|
||||
assert(receiveHook.preReceive("user1", "repo1", rp, rc, "user1", false).isEmpty)
|
||||
enableBranchProtection("user1", "repo1", "branch", true, true, Seq("must"), false, Nil)
|
||||
assert(
|
||||
receiveHook.preReceive("user1", "repo1", rp, rc, "user1", false) == Some(
|
||||
"Required status check \"must\" is expected"
|
||||
)
|
||||
receiveHook
|
||||
.preReceive("user1", "repo1", rp, rc, "user1", false)
|
||||
.contains("Required status check \"must\" is expected")
|
||||
)
|
||||
enableBranchProtection("user1", "repo1", "branch", false, Seq("must", "must2"))
|
||||
assert(receiveHook.preReceive("user1", "repo1", rp, rc, "user1", false) == None)
|
||||
enableBranchProtection("user1", "repo1", "branch", true, Seq("must", "must2"))
|
||||
enableBranchProtection("user1", "repo1", "branch", false, true, Seq("must", "must2"), false, Nil)
|
||||
assert(receiveHook.preReceive("user1", "repo1", rp, rc, "user1", false).isEmpty)
|
||||
enableBranchProtection("user1", "repo1", "branch", true, true, Seq("must", "must2"), false, Nil)
|
||||
assert(
|
||||
receiveHook.preReceive("user1", "repo1", rp, rc, "user1", false) == Some(
|
||||
"2 of 2 required status checks are expected"
|
||||
)
|
||||
receiveHook
|
||||
.preReceive("user1", "repo1", rp, rc, "user1", false)
|
||||
.contains("2 of 2 required status checks are expected")
|
||||
)
|
||||
createCommitStatus("user1", "repo1", sha2, "context", CommitState.SUCCESS, None, None, now, user1)
|
||||
assert(
|
||||
receiveHook.preReceive("user1", "repo1", rp, rc, "user1", false) == Some(
|
||||
"2 of 2 required status checks are expected"
|
||||
)
|
||||
receiveHook
|
||||
.preReceive("user1", "repo1", rp, rc, "user1", false)
|
||||
.contains("2 of 2 required status checks are expected")
|
||||
)
|
||||
createCommitStatus("user1", "repo1", sha2, "must", CommitState.SUCCESS, None, None, now, user1)
|
||||
assert(
|
||||
receiveHook.preReceive("user1", "repo1", rp, rc, "user1", false) == Some(
|
||||
"Required status check \"must2\" is expected"
|
||||
)
|
||||
receiveHook
|
||||
.preReceive("user1", "repo1", rp, rc, "user1", false)
|
||||
.contains("Required status check \"must2\" is expected")
|
||||
)
|
||||
createCommitStatus("user1", "repo1", sha2, "must2", CommitState.SUCCESS, None, None, now, user1)
|
||||
assert(receiveHook.preReceive("user1", "repo1", rp, rc, "user1", false) == None)
|
||||
assert(receiveHook.preReceive("user1", "repo1", rp, rc, "user1", false).isEmpty)
|
||||
}
|
||||
}
|
||||
}
|
||||
it("should restrict push to allowed users only") {
|
||||
withTestDB { implicit session =>
|
||||
withTestRepository { git =>
|
||||
val rp = new ReceivePack(git.getRepository)
|
||||
rp.setAllowNonFastForwards(true)
|
||||
val rc = new ReceiveCommand(
|
||||
ObjectId.fromString(sha),
|
||||
ObjectId.fromString(sha2),
|
||||
"refs/heads/branch",
|
||||
ReceiveCommand.Type.UPDATE
|
||||
)
|
||||
generateNewUserWithDBRepository("user1", "repo1")
|
||||
generateNewAccount("user2")
|
||||
enableBranchProtection("user1", "repo1", "branch", false, false, Nil, true, Seq("user2"))
|
||||
assert(receiveHook.preReceive("user1", "repo1", rp, rc, "user2", false).isEmpty)
|
||||
assert(
|
||||
receiveHook
|
||||
.preReceive("user1", "repo1", rp, rc, "user3", false)
|
||||
.contains("You do not have permission to push to this branch")
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -212,29 +237,53 @@ class ProtectedBranchServiceSpec
|
||||
it("administrator is owner") {
|
||||
withTestDB { implicit session =>
|
||||
generateNewUserWithDBRepository("user1", "repo1")
|
||||
val x = ProtectedBranchInfo("user1", "repo1", "branch", true, Nil, false)
|
||||
assert(x.isAdministrator("user1") == true)
|
||||
assert(x.isAdministrator("user2") == false)
|
||||
val x = ProtectedBranchInfo(
|
||||
"user1",
|
||||
"repo1",
|
||||
"branch",
|
||||
enabled = true,
|
||||
contexts = Some(Nil),
|
||||
enforceAdmins = false,
|
||||
restrictionsUsers = None
|
||||
)
|
||||
assert(x.isAdministrator("user1"))
|
||||
assert(!x.isAdministrator("user2"))
|
||||
}
|
||||
}
|
||||
it("administrator is manager") {
|
||||
withTestDB { implicit session =>
|
||||
val x = ProtectedBranchInfo("grp1", "repo1", "branch", true, Nil, false)
|
||||
val x = ProtectedBranchInfo(
|
||||
"grp1",
|
||||
"repo1",
|
||||
"branch",
|
||||
enabled = true,
|
||||
contexts = Some(Nil),
|
||||
enforceAdmins = false,
|
||||
restrictionsUsers = None
|
||||
)
|
||||
x.createGroup("grp1", None, None)
|
||||
generateNewAccount("user1")
|
||||
generateNewAccount("user2")
|
||||
generateNewAccount("user3")
|
||||
|
||||
x.updateGroupMembers("grp1", List("user1" -> true, "user2" -> false))
|
||||
assert(x.isAdministrator("user1") == true)
|
||||
assert(x.isAdministrator("user2") == false)
|
||||
assert(x.isAdministrator("user3") == false)
|
||||
assert(x.isAdministrator("user1"))
|
||||
assert(!x.isAdministrator("user2"))
|
||||
assert(!x.isAdministrator("user3"))
|
||||
}
|
||||
}
|
||||
it("unSuccessedContexts") {
|
||||
withTestDB { implicit session =>
|
||||
val user1 = generateNewUserWithDBRepository("user1", "repo1")
|
||||
val x = ProtectedBranchInfo("user1", "repo1", "branch", true, List("must"), false)
|
||||
val x = ProtectedBranchInfo(
|
||||
"user1",
|
||||
"repo1",
|
||||
"branch",
|
||||
enabled = true,
|
||||
contexts = Some(List("must")),
|
||||
enforceAdmins = false,
|
||||
restrictionsUsers = None
|
||||
)
|
||||
assert(x.unSuccessedContexts(sha) == Set("must"))
|
||||
createCommitStatus("user1", "repo1", sha, "context", CommitState.SUCCESS, None, None, now, user1)
|
||||
assert(x.unSuccessedContexts(sha) == Set("must"))
|
||||
@@ -251,7 +300,15 @@ class ProtectedBranchServiceSpec
|
||||
it("unSuccessedContexts when empty") {
|
||||
withTestDB { implicit session =>
|
||||
val user1 = generateNewUserWithDBRepository("user1", "repo1")
|
||||
val x = ProtectedBranchInfo("user1", "repo1", "branch", true, Nil, false)
|
||||
val x = ProtectedBranchInfo(
|
||||
"user1",
|
||||
"repo1",
|
||||
"branch",
|
||||
enabled = true,
|
||||
contexts = Some(Nil),
|
||||
enforceAdmins = false,
|
||||
restrictionsUsers = None
|
||||
)
|
||||
val sha = "0c77148632618b59b6f70004e3084002be2b8804"
|
||||
assert(x.unSuccessedContexts(sha) == Set())
|
||||
createCommitStatus("user1", "repo1", sha, "context", CommitState.SUCCESS, None, None, now, user1)
|
||||
@@ -261,23 +318,63 @@ class ProtectedBranchServiceSpec
|
||||
it("if disabled, needStatusCheck is false") {
|
||||
withTestDB { implicit session =>
|
||||
assert(
|
||||
ProtectedBranchInfo("user1", "repo1", "branch", false, Seq("must"), true).needStatusCheck("user1") == false
|
||||
!ProtectedBranchInfo(
|
||||
"user1",
|
||||
"repo1",
|
||||
"branch",
|
||||
enabled = false,
|
||||
contexts = Some(Seq("must")),
|
||||
enforceAdmins = true,
|
||||
restrictionsUsers = None
|
||||
).needStatusCheck("user1")
|
||||
)
|
||||
}
|
||||
}
|
||||
it("needStatusCheck includeAdministrators") {
|
||||
withTestDB { implicit session =>
|
||||
assert(
|
||||
ProtectedBranchInfo("user1", "repo1", "branch", true, Seq("must"), false).needStatusCheck("user2") == true
|
||||
ProtectedBranchInfo(
|
||||
"user1",
|
||||
"repo1",
|
||||
"branch",
|
||||
enabled = true,
|
||||
contexts = Some(Seq("must")),
|
||||
enforceAdmins = false,
|
||||
restrictionsUsers = None
|
||||
).needStatusCheck("user2")
|
||||
)
|
||||
assert(
|
||||
ProtectedBranchInfo("user1", "repo1", "branch", true, Seq("must"), false).needStatusCheck("user1") == false
|
||||
!ProtectedBranchInfo(
|
||||
"user1",
|
||||
"repo1",
|
||||
"branch",
|
||||
enabled = true,
|
||||
contexts = Some(Seq("must")),
|
||||
enforceAdmins = false,
|
||||
restrictionsUsers = None
|
||||
).needStatusCheck("user1")
|
||||
)
|
||||
assert(
|
||||
ProtectedBranchInfo("user1", "repo1", "branch", true, Seq("must"), true).needStatusCheck("user2") == true
|
||||
ProtectedBranchInfo(
|
||||
"user1",
|
||||
"repo1",
|
||||
"branch",
|
||||
enabled = true,
|
||||
contexts = Some(Seq("must")),
|
||||
enforceAdmins = true,
|
||||
restrictionsUsers = None
|
||||
).needStatusCheck("user2")
|
||||
)
|
||||
assert(
|
||||
ProtectedBranchInfo("user1", "repo1", "branch", true, Seq("must"), true).needStatusCheck("user1") == true
|
||||
ProtectedBranchInfo(
|
||||
"user1",
|
||||
"repo1",
|
||||
"branch",
|
||||
enabled = true,
|
||||
contexts = Some(Seq("must")),
|
||||
enforceAdmins = true,
|
||||
restrictionsUsers = None
|
||||
).needStatusCheck("user1")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package gitbucket.core.service
|
||||
|
||||
import gitbucket.core.model._
|
||||
import gitbucket.core.model.*
|
||||
import org.scalatest.funspec.AnyFunSpec
|
||||
|
||||
class PullRequestServiceSpec
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package gitbucket.core.service
|
||||
|
||||
import gitbucket.core.model._
|
||||
import gitbucket.core.model.*
|
||||
import org.scalatest.funsuite.AnyFunSuite
|
||||
|
||||
class RepositoryServiceSpec extends AnyFunSuite with ServiceSpecBase with RepositoryService with AccountService {
|
||||
@@ -21,7 +21,7 @@ class RepositoryServiceSpec extends AnyFunSuite with ServiceSpecBase with Reposi
|
||||
now = new java.util.Date
|
||||
)
|
||||
|
||||
service.enableBranchProtection("root", "repo", "branch", true, Seq("must1", "must2"))
|
||||
service.enableBranchProtection("root", "repo", "branch", true, true, Seq("must1", "must2"), false, Nil)
|
||||
|
||||
val orgPbi = service.getProtectedBranchInfo("root", "repo", "branch")
|
||||
val org = service.getCommitStatus("root", "repo", id).get
|
||||
|
||||
@@ -5,8 +5,9 @@ import gitbucket.core.util.{DatabaseConfig, Directory, FileUtil, JGitUtil}
|
||||
import io.github.gitbucket.solidbase.Solidbase
|
||||
import liquibase.database.core.H2Database
|
||||
import liquibase.database.jvm.JdbcConnection
|
||||
import gitbucket.core.model._
|
||||
import gitbucket.core.model.Profile.profile.blockingApi._
|
||||
import gitbucket.core.model.*
|
||||
import gitbucket.core.model.Profile.profile.blockingApi.*
|
||||
import gitbucket.core.model.Session
|
||||
import org.apache.commons.io.FileUtils
|
||||
|
||||
import java.sql.DriverManager
|
||||
@@ -21,21 +22,21 @@ import gitbucket.core.service.SystemSettingsService.{
|
||||
}
|
||||
|
||||
import javax.servlet.http.{HttpServletRequest, HttpSession}
|
||||
import org.mockito.Mockito._
|
||||
import org.mockito.Mockito.*
|
||||
|
||||
import scala.util.Random
|
||||
import scala.util.Using
|
||||
|
||||
trait ServiceSpecBase {
|
||||
|
||||
val request = mock(classOf[HttpServletRequest])
|
||||
val session = mock(classOf[HttpSession])
|
||||
val request: HttpServletRequest = mock(classOf[HttpServletRequest])
|
||||
val session: HttpSession = mock(classOf[HttpSession])
|
||||
when(request.getRequestURL).thenReturn(new StringBuffer("http://localhost:8080/path.html"))
|
||||
when(request.getRequestURI).thenReturn("/path.html")
|
||||
when(request.getContextPath).thenReturn("")
|
||||
when(request.getSession).thenReturn(session)
|
||||
|
||||
def createSystemSettings() =
|
||||
def createSystemSettings(): SystemSettings =
|
||||
SystemSettings(
|
||||
baseUrl = None,
|
||||
information = None,
|
||||
@@ -84,7 +85,8 @@ trait ServiceSpecBase {
|
||||
maxDiffFiles = 100,
|
||||
maxDiffLines = 1000
|
||||
),
|
||||
defaultBranch = "main"
|
||||
defaultBranch = "main",
|
||||
showFullName = false
|
||||
)
|
||||
|
||||
def withTestDB[A](action: (Session) => A): A = {
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
package gitbucket.core.service
|
||||
|
||||
import gitbucket.core.api.JsonFormat
|
||||
import gitbucket.core.service.WebHookService._
|
||||
import gitbucket.core.service.WebHookService.*
|
||||
import org.scalatest.Assertion
|
||||
import org.scalatest.funsuite.AnyFunSuite
|
||||
|
||||
class WebHookJsonFormatSpec extends AnyFunSuite {
|
||||
import gitbucket.core.api.ApiSpecModels._
|
||||
import gitbucket.core.api.ApiSpecModels.*
|
||||
|
||||
private def assert(payload: WebHookPayload, expected: String): Assertion = {
|
||||
val json = JsonFormat(payload)
|
||||
@@ -34,7 +34,7 @@ class WebHookJsonFormatSpec extends AnyFunSuite {
|
||||
}
|
||||
|
||||
test("WebHookPushPayload") {
|
||||
import gitbucket.core.util.GitSpecUtil._
|
||||
import gitbucket.core.util.GitSpecUtil.*
|
||||
import org.eclipse.jgit.lib.{Constants, ObjectId}
|
||||
|
||||
withTestRepository { git =>
|
||||
|
||||
@@ -107,7 +107,7 @@ class AvatarImageProviderSpec extends AnyFunSpec {
|
||||
provider.toHtml("user", 20, "hoge@hoge.com", true).toString ==
|
||||
"""<img src="/_unknown/_avatar" class="avatar-mini" style="width: 20px; height: 20px;"
|
||||
| alt="@user"
|
||||
| data-toggle="tooltip" title="user" />""".stripMargin
|
||||
| data-toggle="tooltip" title="@user" />""".stripMargin
|
||||
)
|
||||
}
|
||||
|
||||
@@ -125,7 +125,7 @@ class AvatarImageProviderSpec extends AnyFunSpec {
|
||||
provider.toHtml("""<user>"<name>""", 20, "hoge@hoge.com", true).toString ==
|
||||
"""<img src="/_unknown/_avatar" class="avatar-mini" style="width: 20px; height: 20px;"
|
||||
| alt="@<user>"<name>"
|
||||
| data-toggle="tooltip" title="<user>"<name>" />""".stripMargin
|
||||
| data-toggle="tooltip" title="@<user>"<name>" />""".stripMargin
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -196,7 +196,8 @@ class AvatarImageProviderSpec extends AnyFunSpec {
|
||||
maxDiffFiles = 100,
|
||||
maxDiffLines = 1000
|
||||
),
|
||||
"main"
|
||||
defaultBranch = "main",
|
||||
showFullName = false
|
||||
)
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user