mirror of
https://github.com/gitbucket/gitbucket.git
synced 2026-05-09 08:07:26 +02:00
Compare commits
179 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2a3c8e0712 | ||
|
|
d3a29b3ecb | ||
|
|
7a50a15748 | ||
|
|
9a1b55b992 | ||
|
|
828b798c0e | ||
|
|
8d8845536d | ||
|
|
f20497e769 | ||
|
|
6053d9826e | ||
|
|
85263474a7 | ||
|
|
c02a722799 | ||
|
|
ce4faceccc | ||
|
|
04c8f8b864 | ||
|
|
1b32e13113 | ||
|
|
401728d47f | ||
|
|
31ace89f43 | ||
|
|
995cb86e90 | ||
|
|
e27623ca29 | ||
|
|
ea4da561c5 | ||
|
|
2e8f3efafd | ||
|
|
f25cf5781c | ||
|
|
d97f7c6025 | ||
|
|
e91d903650 | ||
|
|
4f93f06de5 | ||
|
|
08ed3c4171 | ||
|
|
5193d82980 | ||
|
|
eab82bf1be | ||
|
|
311d758910 | ||
|
|
097a2d32b8 | ||
|
|
a34766ccfd | ||
|
|
147eef9ee5 | ||
|
|
49118662b2 | ||
|
|
5989f2e2cb | ||
|
|
127f034bba | ||
|
|
e1e00c4b94 | ||
|
|
eb64cdd9cd | ||
|
|
1bfa8dffb8 | ||
|
|
33361b8015 | ||
|
|
b5ee074075 | ||
|
|
cbddc34bfa | ||
|
|
61504ae9e3 | ||
|
|
3049f6010c | ||
|
|
d4e01d631f | ||
|
|
a46aa2c61c | ||
|
|
ad147e8dd5 | ||
|
|
3555519392 | ||
|
|
f6a5def638 | ||
|
|
0590cb7048 | ||
|
|
b35d0792aa | ||
|
|
0d20bc0173 | ||
|
|
851141c2f4 | ||
|
|
31a104a697 | ||
|
|
bfc44cff98 | ||
|
|
0da781c33d | ||
|
|
8dbcbb5485 | ||
|
|
7544f64c65 | ||
|
|
73d05aefad | ||
|
|
4d70b056ad | ||
|
|
b81ce41d51 | ||
|
|
a143683d7f | ||
|
|
5ba38057dc | ||
|
|
07eb2bc41e | ||
|
|
5e4d041295 | ||
|
|
4d7fc061a4 | ||
|
|
8db98d7b16 | ||
|
|
a6063c8aa9 | ||
|
|
2cc1336e82 | ||
|
|
308bda2050 | ||
|
|
36989c38d4 | ||
|
|
29357ae170 | ||
|
|
36643bcdd0 | ||
|
|
72e40a0b12 | ||
|
|
2194ff7625 | ||
|
|
b247864bfe | ||
|
|
b1c3ae4974 | ||
|
|
81c0e2037f | ||
|
|
6224ec2a7b | ||
|
|
9ff4507fe2 | ||
|
|
67667dbff1 | ||
|
|
1a4961c3e1 | ||
|
|
561220237f | ||
|
|
f45b85aa71 | ||
|
|
83b3a7983e | ||
|
|
63d4c5054e | ||
|
|
c8f6017be9 | ||
|
|
f9fcb54861 | ||
|
|
3534b7172d | ||
|
|
42a7f974e9 | ||
|
|
b8e02d995a | ||
|
|
e0d038aa92 | ||
|
|
3d01df2bdc | ||
|
|
6f08f1fd23 | ||
|
|
3dd366b394 | ||
|
|
8cf4528959 | ||
|
|
5d77bc5d98 | ||
|
|
572c9ef558 | ||
|
|
7c736c526e | ||
|
|
696d354f3c | ||
|
|
501cbf54ab | ||
|
|
c15d69d566 | ||
|
|
83eb933230 | ||
|
|
43bec9b0df | ||
|
|
279305c202 | ||
|
|
ecbb86c006 | ||
|
|
921fb17ef0 | ||
|
|
0362de7d35 | ||
|
|
cf0d8ea2d0 | ||
|
|
0e9026447d | ||
|
|
cf4d9cb03c | ||
|
|
2a1edeaca3 | ||
|
|
eebabf9b08 | ||
|
|
d882fcad12 | ||
|
|
f976290282 | ||
|
|
f3f9d5dae2 | ||
|
|
71dffd1089 | ||
|
|
f411e98c9a | ||
|
|
1baa489bc7 | ||
|
|
f996b0fc4a | ||
|
|
eb6ba1c800 | ||
|
|
8fac1baa3c | ||
|
|
2d327543b9 | ||
|
|
8a080efe9d | ||
|
|
aaaf61e29e | ||
|
|
1294323df5 | ||
|
|
9ef4e75746 | ||
|
|
ded4ab702d | ||
|
|
f893d045c7 | ||
|
|
ef2e3adcfb | ||
|
|
a1908c5398 | ||
|
|
ec4f0d6531 | ||
|
|
9ef366237c | ||
|
|
9197ad2600 | ||
|
|
2cb7ecd851 | ||
|
|
7d1ad4ce66 | ||
|
|
c274acc8f4 | ||
|
|
7a0d48dd7a | ||
|
|
9c6f9048e1 | ||
|
|
4ff4acfd7e | ||
|
|
d8e9f07721 | ||
|
|
662c5638dd | ||
|
|
02b830d034 | ||
|
|
3734529e5c | ||
|
|
715ec24389 | ||
|
|
5615b23548 | ||
|
|
ca2eeb48cf | ||
|
|
b063c0a80c | ||
|
|
ee8b5692bd | ||
|
|
7a749dca67 | ||
|
|
0378f26ee6 | ||
|
|
4d281273c1 | ||
|
|
e23e11e1a8 | ||
|
|
772835dd22 | ||
|
|
bbf2e57548 | ||
|
|
aecda130b6 | ||
|
|
1d48030e7c | ||
|
|
929ba2fa19 | ||
|
|
6fefa947ca | ||
|
|
072cd2e846 | ||
|
|
5d20cd0365 | ||
|
|
03ec055f66 | ||
|
|
4b6a5e5d49 | ||
|
|
6d21e38159 | ||
|
|
a47c8249bf | ||
|
|
59d7a672b3 | ||
|
|
564e95d36e | ||
|
|
9aee99be74 | ||
|
|
aecd8b503d | ||
|
|
fa65eeea35 | ||
|
|
fe43584c3d | ||
|
|
c255b13dfc | ||
|
|
917b204e5b | ||
|
|
6225fd79fc | ||
|
|
cbec567ef4 | ||
|
|
84b62242d3 | ||
|
|
f17f74c30b | ||
|
|
63b04d5a27 | ||
|
|
f02073a24c | ||
|
|
cb87d126de | ||
|
|
941f8002e5 | ||
|
|
90f4f5cd89 |
@@ -5,6 +5,10 @@ trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
end_of_line = lf
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
[*.java]
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
6
.github/dependabot.yml
vendored
Normal file
6
.github/dependabot.yml
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
35
.github/workflows/build.yml
vendored
Normal file
35
.github/workflows/build.yml
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
name: build
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
java: [8, 11]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Cache
|
||||
uses: actions/cache@v2
|
||||
env:
|
||||
cache-name: cache-sbt-libs
|
||||
with:
|
||||
path: |
|
||||
~/.ivy2/cache
|
||||
~/.sbt
|
||||
~/.cache/coursier/v1
|
||||
key: build-${{ env.cache-name }}-${{ hashFiles('build.sbt') }}
|
||||
- name: Set up JDK
|
||||
uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: ${{ matrix.java }}
|
||||
- name: Run tests
|
||||
run: sbt scalafmtSbtCheck scalafmtCheck test:scalafmtCheck test
|
||||
- name: Build executable
|
||||
run: sbt executable
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: gitbucket-java${{ matrix.java }}-${{ github.sha }}
|
||||
path: ./target/executable/gitbucket.*
|
||||
8
.gitignore
vendored
8
.gitignore
vendored
@@ -10,6 +10,7 @@ lib_managed/
|
||||
src_managed/
|
||||
project/boot/
|
||||
project/plugins/project/
|
||||
.bsp/
|
||||
|
||||
# Scala-IDE specific
|
||||
.scala_dependencies
|
||||
@@ -27,4 +28,9 @@ project/plugins/project/
|
||||
|
||||
# Metals specific
|
||||
.metals
|
||||
.bloop
|
||||
.bloop
|
||||
**/metals.sbt
|
||||
|
||||
# Visual Studio Code specific
|
||||
.vscode
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
version = "1.5.1"
|
||||
project.git = true
|
||||
|
||||
maxColumn = 120
|
||||
|
||||
17
.travis.yml
17
.travis.yml
@@ -1,17 +0,0 @@
|
||||
language: scala
|
||||
sudo: true
|
||||
jdk:
|
||||
- openjdk8
|
||||
- openjdk11
|
||||
script:
|
||||
- sbt scalafmtSbtCheck scalafmtCheck test:scalafmtCheck test
|
||||
before_script:
|
||||
- sudo /etc/init.d/mysql stop
|
||||
- sudo /etc/init.d/postgresql stop
|
||||
- sudo chmod +x /usr/local/bin/sbt
|
||||
cache:
|
||||
directories:
|
||||
- $HOME/.ivy2/cache
|
||||
- $HOME/.sbt/boot
|
||||
- $HOME/.sbt/launchers
|
||||
- $HOME/.coursier
|
||||
43
CHANGELOG.md
43
CHANGELOG.md
@@ -1,15 +1,38 @@
|
||||
# Changelog
|
||||
All changes to the project will be documented in this file.
|
||||
|
||||
### 4.33.0 - 31 Dec 2019
|
||||
### 4.35.0 - 25 Dec 2020
|
||||
- Editor and source viewer color theme
|
||||
- Auto completion for issues and pull requests
|
||||
- Upload image from clipboard
|
||||
- Close multiple issues by commit comment
|
||||
- Create pull request from online editor
|
||||
- Milestone overview
|
||||
- Commit status at various places
|
||||
- WebAPI coverage improvements
|
||||
|
||||
- All CLI options are configurable by environmental variables
|
||||
## 4.34.0 - 26 Jul 2020
|
||||
- Enhancement admin settings UI
|
||||
- File upload settings
|
||||
- Restrict repository operations
|
||||
- User-defined CSS
|
||||
- Limit the repository list in the sidebar
|
||||
- Improve MariaDB support
|
||||
- Improve activity logging
|
||||
- CLI option to persist session on disk in the standalone mode
|
||||
- Web API updates
|
||||
- Add [list commits API](https://developer.github.com/v3/repos/commits/#list-commits)
|
||||
- Bundled plugins updates
|
||||
- [gitbucket-gist-plugin](https://github.com/gitbucket/gitbucket-gist-plugin) 4.18.0 -> 4.19.0
|
||||
- [gitbucket-notifications-plugin](https://github.com/gitbucket/gitbucket-notifications-plugin) 1.8.0 -> 1.9.0
|
||||
|
||||
## 4.33.0 - 31 Dec 2019
|
||||
- All CLI options are configurable by environment variables
|
||||
- Folding pull request files
|
||||
- WebHook security options
|
||||
- Add asignee and asignees properties to some Web APIs' response
|
||||
|
||||
### 4.32.0 - 7 Aug 2019
|
||||
- Add assignee and assignees properties to some Web APIs' response
|
||||
|
||||
## 4.32.0 - 7 Aug 2019
|
||||
- Bump to Scala 2.13.0 and Scalatra 2.7.0
|
||||
- Draft pull request
|
||||
- Drop network installation of plugins
|
||||
@@ -17,20 +40,20 @@ All changes to the project will be documented in this file.
|
||||
- Apply default priority to pull requests
|
||||
- Focus title after clicking issue / pull request edit button
|
||||
|
||||
### 4.31.2 - 7 Apr 2019
|
||||
## 4.31.2 - 7 Apr 2019
|
||||
- Bug and security fix
|
||||
|
||||
### 4.31.1 - 17 Mar 2019
|
||||
## 4.31.1 - 17 Mar 2019
|
||||
- Bug fix
|
||||
|
||||
### 4.31.0 - 17 Mar 2019
|
||||
## 4.31.0 - 17 Mar 2019
|
||||
- Docker support in CI plugin
|
||||
- Verify GPG key signed commit
|
||||
- OAuth2 Token (sent as a parameter) authentication support and new APIs in Web API
|
||||
- OGP (Open Graph protocol) support
|
||||
- Username completion with avatars
|
||||
|
||||
### 4.30.1 - 22 Dec 2018
|
||||
## 4.30.1 - 22 Dec 2018
|
||||
- Bug fix for several WebHooks and Web API
|
||||
|
||||
## 4.30.0 - 15 Dec 2018
|
||||
@@ -117,7 +140,7 @@ All changes to the project will be documented in this file.
|
||||
- Submodule links to web page
|
||||
- Clarify close/reopen button
|
||||
|
||||
# 4.20.0 - 23 Dec 2017
|
||||
## 4.20.0 - 23 Dec 2017
|
||||
- Squash and rebase merge strategy for pull requests
|
||||
- Quick pull request creation
|
||||
- Download patch from the diff view
|
||||
|
||||
32
README.md
32
README.md
@@ -1,4 +1,4 @@
|
||||
GitBucket [](https://gitter.im/gitbucket/gitbucket) [](https://travis-ci.org/gitbucket/gitbucket) [](https://maven-badges.herokuapp.com/maven-central/io.github.gitbucket/gitbucket_2.13) [](https://github.com/gitbucket/gitbucket/blob/master/LICENSE)
|
||||
GitBucket [](https://gitter.im/gitbucket/gitbucket) [](https://github.com/gitbucket/gitbucket/actions?query=workflow%3Abuild+branch%3Amaster) [](https://maven-badges.herokuapp.com/maven-central/io.github.gitbucket/gitbucket_2.13) [](https://github.com/gitbucket/gitbucket/blob/master/LICENSE)
|
||||
=========
|
||||
|
||||
GitBucket is a Git web platform powered by Scala offering:
|
||||
@@ -31,20 +31,6 @@ GitBucket requires **Java8**. You have to install it, if it is not already insta
|
||||
1. Download the latest **gitbucket.war** from [the releases page](https://github.com/gitbucket/gitbucket/releases) and run it by `java -jar gitbucket.war`.
|
||||
2. Go to `http://[hostname]:8080/` and log in with ID: **root** / Pass: **root**.
|
||||
|
||||
You can specify following options:
|
||||
|
||||
- `--port=[NUMBER]`
|
||||
- `--prefix=[CONTEXTPATH]`
|
||||
- `--host=[HOSTNAME]`
|
||||
- `--gitbucket.home=[DATA_DIR]`
|
||||
- `--temp_dir=[TEMP_DIR]`
|
||||
- `--max_file_size=[MAX_FILE_SIZE]`
|
||||
- `--upload_timeout=[MAX_UPLOAD_TIMEOUT]`
|
||||
|
||||
`TEMP_DIR` is used as the [temporary directory for the jetty application context](https://www.eclipse.org/jetty/documentation/9.3.x/ref-temporary-directories.html). This is the directory into which the `gitbucket.war` file is unpacked, the source files are compiled, etc. If given this parameter **must** match the path of an existing directory or the application will quit reporting an error; if not given the path used will be a `tmp` directory inside the gitbucket home.
|
||||
|
||||
`MAX_FILE_SIZE` is the max file size in bytes for upload files *( default is 3 MB -> 3 x 1024 x 1024 )*.
|
||||
|
||||
You can also deploy `gitbucket.war` to a servlet container which supports Servlet 3.0 (like Jetty, Tomcat, JBoss, etc)
|
||||
|
||||
For more information about installation on Mac or Windows Server (with IIS), or configuration of Apache or Nginx and also integration with other tools or services such as Jenkins or Slack, see [Wiki](https://github.com/gitbucket/gitbucket/wiki).
|
||||
@@ -69,13 +55,17 @@ Support
|
||||
- If you can't find same question and report, send it to [gitter room](https://gitter.im/gitbucket/gitbucket) before raising an issue.
|
||||
- The highest priority of GitBucket is the ease of installation and API compatibility with GitHub, so your feature request might be rejected if they go against those principles.
|
||||
|
||||
What's New in 4.32.x
|
||||
What's New in 4.34.x
|
||||
-------------
|
||||
### 4.33.0 - 31 Dec 2019
|
||||
### 4.35.0 - 25 Dec 2020
|
||||
|
||||
- All CLI options are configurable by environmental variables
|
||||
- Folding pull request files
|
||||
- WebHook security options
|
||||
- Add asignee and asignees properties to some Web APIs' response
|
||||
- Editor and source viewer color theme
|
||||
- Auto completion for issues and pull requests
|
||||
- Upload image from clipboard
|
||||
- Close multiple issues by commit comment
|
||||
- Create pull request from online editor
|
||||
- Milestone overview
|
||||
- Commit status at various places
|
||||
- WebAPI coverage improvements
|
||||
|
||||
See the [change log](CHANGELOG.md) for all of the updates.
|
||||
|
||||
49
build.sbt
49
build.sbt
@@ -3,10 +3,10 @@ import com.typesafe.sbt.pgp.PgpKeys._
|
||||
|
||||
val Organization = "io.github.gitbucket"
|
||||
val Name = "gitbucket"
|
||||
val GitBucketVersion = "4.33.0"
|
||||
val ScalatraVersion = "2.7.0-RC1"
|
||||
val JettyVersion = "9.4.25.v20191220"
|
||||
val JgitVersion = "5.6.0.201912101111-r"
|
||||
val GitBucketVersion = "4.35.0"
|
||||
val ScalatraVersion = "2.7.0"
|
||||
val JettyVersion = "9.4.32.v20200930"
|
||||
val JgitVersion = "5.9.0.202009080501-r"
|
||||
|
||||
lazy val root = (project in file("."))
|
||||
.enablePlugins(SbtTwirl, ScalatraPlugin)
|
||||
@@ -36,43 +36,42 @@ libraryDependencies ++= Seq(
|
||||
"org.scalatra" %% "scalatra" % ScalatraVersion,
|
||||
"org.scalatra" %% "scalatra-json" % ScalatraVersion,
|
||||
"org.scalatra" %% "scalatra-forms" % ScalatraVersion,
|
||||
"org.json4s" %% "json4s-jackson" % "3.6.7",
|
||||
"commons-io" % "commons-io" % "2.6",
|
||||
"org.json4s" %% "json4s-jackson" % "3.6.10",
|
||||
"commons-io" % "commons-io" % "2.8.0",
|
||||
"io.github.gitbucket" % "solidbase" % "1.0.3",
|
||||
"io.github.gitbucket" % "markedj" % "1.0.16",
|
||||
"org.apache.commons" % "commons-compress" % "1.19",
|
||||
"org.apache.commons" % "commons-compress" % "1.20",
|
||||
"org.apache.commons" % "commons-email" % "1.5",
|
||||
"commons-net" % "commons-net" % "3.6",
|
||||
"org.apache.httpcomponents" % "httpclient" % "4.5.10",
|
||||
"commons-net" % "commons-net" % "3.7",
|
||||
"org.apache.httpcomponents" % "httpclient" % "4.5.12",
|
||||
"org.apache.sshd" % "apache-sshd" % "2.1.0" exclude ("org.slf4j", "slf4j-jdk14") exclude ("org.apache.sshd", "sshd-mina") exclude ("org.apache.sshd", "sshd-netty"),
|
||||
"org.apache.tika" % "tika-core" % "1.23",
|
||||
"org.apache.tika" % "tika-core" % "1.24.1",
|
||||
"com.github.takezoe" %% "blocking-slick-32" % "0.0.12",
|
||||
"com.novell.ldap" % "jldap" % "2009-10-07",
|
||||
"com.h2database" % "h2" % "1.4.199",
|
||||
"org.mariadb.jdbc" % "mariadb-java-client" % "2.5.2",
|
||||
"org.mariadb.jdbc" % "mariadb-java-client" % "2.7.0",
|
||||
"org.postgresql" % "postgresql" % "42.2.6",
|
||||
"ch.qos.logback" % "logback-classic" % "1.2.3",
|
||||
"com.zaxxer" % "HikariCP" % "3.4.1",
|
||||
"com.zaxxer" % "HikariCP" % "3.4.5",
|
||||
"com.typesafe" % "config" % "1.4.0",
|
||||
"com.typesafe.akka" %% "akka-actor" % "2.5.27",
|
||||
"fr.brouillard.oss.security.xhub" % "xhub4j-core" % "1.1.0",
|
||||
"com.github.bkromhout" % "java-diff-utils" % "2.1.1",
|
||||
"org.cache2k" % "cache2k-all" % "1.2.4.Final",
|
||||
"com.enragedginger" %% "akka-quartz-scheduler" % "1.8.1-akka-2.5.x" exclude ("com.mchange", "c3p0") exclude ("com.zaxxer", "HikariCP-java6"),
|
||||
"net.coobird" % "thumbnailator" % "0.4.8",
|
||||
"net.coobird" % "thumbnailator" % "0.4.12",
|
||||
"com.github.zafarkhaja" % "java-semver" % "0.9.0",
|
||||
"com.nimbusds" % "oauth2-oidc-sdk" % "5.64.4",
|
||||
"org.eclipse.jetty" % "jetty-webapp" % JettyVersion % "provided",
|
||||
"javax.servlet" % "javax.servlet-api" % "3.1.0" % "provided",
|
||||
"junit" % "junit" % "4.12" % "test",
|
||||
"junit" % "junit" % "4.13" % "test",
|
||||
"org.scalatra" %% "scalatra-scalatest" % ScalatraVersion % "test",
|
||||
"org.mockito" % "mockito-core" % "3.2.4" % "test",
|
||||
"com.dimafeng" %% "testcontainers-scala" % "0.34.2" % "test",
|
||||
"org.testcontainers" % "mysql" % "1.12.4" % "test",
|
||||
"org.testcontainers" % "postgresql" % "1.12.4" % "test",
|
||||
"org.mockito" % "mockito-core" % "3.3.3" % "test",
|
||||
"com.dimafeng" %% "testcontainers-scala" % "0.37.0" % "test",
|
||||
"org.testcontainers" % "mysql" % "1.14.3" % "test",
|
||||
"org.testcontainers" % "postgresql" % "1.14.3" % "test",
|
||||
"net.i2p.crypto" % "eddsa" % "0.3.0",
|
||||
"is.tagomor.woothee" % "woothee-java" % "1.10.1",
|
||||
"org.ec4j.core" % "ec4j-core" % "0.0.3"
|
||||
"is.tagomor.woothee" % "woothee-java" % "1.11.0",
|
||||
"org.ec4j.core" % "ec4j-core" % "0.0.3",
|
||||
"org.kohsuke" % "github-api" % "1.116" % "test"
|
||||
)
|
||||
|
||||
// Compiler settings
|
||||
@@ -122,6 +121,12 @@ libraryDependencies ++= Seq(
|
||||
"org.eclipse.jetty" % "jetty-util" % JettyVersion % "executable"
|
||||
)
|
||||
|
||||
// Run package task before test to generate target/webapp for integration test
|
||||
test in Test := {
|
||||
_root_.sbt.Keys.`package`.value
|
||||
(test in Test).value
|
||||
}
|
||||
|
||||
val executableKey = TaskKey[File]("executable")
|
||||
executableKey := {
|
||||
import java.util.jar.Attributes.{Name => AttrName}
|
||||
|
||||
@@ -1 +1 @@
|
||||
sbt.version=1.3.5
|
||||
sbt.version=1.4.4
|
||||
|
||||
@@ -2,8 +2,8 @@ scalacOptions ++= Seq("-unchecked", "-deprecation", "-feature")
|
||||
|
||||
addSbtPlugin("com.geirsson" % "sbt-scalafmt" % "1.5.1")
|
||||
addSbtPlugin("com.typesafe.sbt" % "sbt-twirl" % "1.5.0")
|
||||
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.10")
|
||||
addSbtPlugin("org.scalatra.sbt" % "sbt-scalatra" % "1.0.3")
|
||||
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.15.0")
|
||||
addSbtPlugin("org.scalatra.sbt" % "sbt-scalatra" % "1.0.4")
|
||||
addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.1.2")
|
||||
addSbtPlugin("com.typesafe.sbt" % "sbt-license-report" % "1.2.0")
|
||||
addSbtPlugin("net.virtual-void" % "sbt-dependency-graph" % "0.9.2")
|
||||
|
||||
@@ -4,6 +4,10 @@ import org.eclipse.jetty.server.Handler;
|
||||
import org.eclipse.jetty.server.HttpConnectionFactory;
|
||||
import org.eclipse.jetty.server.Server;
|
||||
import org.eclipse.jetty.server.handler.StatisticsHandler;
|
||||
import org.eclipse.jetty.server.session.DefaultSessionCache;
|
||||
import org.eclipse.jetty.server.session.FileSessionDataStore;
|
||||
import org.eclipse.jetty.server.session.SessionCache;
|
||||
import org.eclipse.jetty.server.session.SessionHandler;
|
||||
import org.eclipse.jetty.webapp.WebAppContext;
|
||||
|
||||
import java.io.File;
|
||||
@@ -21,6 +25,7 @@ public class JettyLauncher {
|
||||
String contextPath = "/";
|
||||
String tmpDirPath="";
|
||||
boolean forceHttps = false;
|
||||
boolean saveSessions = false;
|
||||
|
||||
host = getEnvironmentVariable("gitbucket.host");
|
||||
port = getEnvironmentVariable("gitbucket.port");
|
||||
@@ -28,6 +33,9 @@ public class JettyLauncher {
|
||||
tmpDirPath = getEnvironmentVariable("gitbucket.tempDir");
|
||||
|
||||
for(String arg: args) {
|
||||
if(arg.equals("--save_sessions")) {
|
||||
saveSessions = true;
|
||||
}
|
||||
if(arg.startsWith("--") && arg.contains("=")) {
|
||||
String[] dim = arg.split("=");
|
||||
if(dim.length >= 2) {
|
||||
@@ -41,12 +49,6 @@ public class JettyLauncher {
|
||||
case "--prefix":
|
||||
contextPath = dim[1];
|
||||
break;
|
||||
case "--max_file_size":
|
||||
System.setProperty("gitbucket.maxFileSize", dim[1]);
|
||||
break;
|
||||
case "--upload_timeout":
|
||||
System.setProperty("gitbucket.UploadTimeout", dim[1]);
|
||||
break;
|
||||
case "--gitbucket.home":
|
||||
System.setProperty("gitbucket.home", dim[1]);
|
||||
break;
|
||||
@@ -56,9 +58,6 @@ public class JettyLauncher {
|
||||
case "--plugin_dir":
|
||||
System.setProperty("gitbucket.pluginDir", dim[1]);
|
||||
break;
|
||||
case "--validate_password":
|
||||
System.setProperty("gitbucket.validate.password", dim[1]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -96,6 +95,19 @@ public class JettyLauncher {
|
||||
|
||||
WebAppContext context = new WebAppContext();
|
||||
|
||||
if(saveSessions) {
|
||||
File sessDir = new File(getGitBucketHome(), "sessions");
|
||||
if(!sessDir.exists()){
|
||||
sessDir.mkdirs();
|
||||
}
|
||||
SessionHandler sessions = context.getSessionHandler();
|
||||
SessionCache cache = new DefaultSessionCache(sessions);
|
||||
FileSessionDataStore fsds = new FileSessionDataStore();
|
||||
fsds.setStoreDir(sessDir);
|
||||
cache.setSessionDataStore(fsds);
|
||||
sessions.setSessionCache(cache);
|
||||
}
|
||||
|
||||
File tmpDir;
|
||||
if(tmpDirPath == null || tmpDirPath.equals("")){
|
||||
tmpDir = new File(getGitBucketHome(), "tmp");
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
notifications:1.8.0
|
||||
gist:4.18.0
|
||||
notifications:1.9.0
|
||||
gist:4.20.0
|
||||
emoji:4.6.0
|
||||
pages:1.8.0
|
||||
pages:1.9.0
|
||||
|
||||
@@ -60,13 +60,12 @@
|
||||
<!-- ACCESS_TOKEN -->
|
||||
<!--================================================================================================-->
|
||||
<createTable tableName="ACCESS_TOKEN">
|
||||
<column name="ACCESS_TOKEN_ID" type="int" nullable="false" autoIncrement="true" unique="true"/>
|
||||
<column name="ACCESS_TOKEN_ID" type="int" nullable="false" autoIncrement="true" unique="true" primaryKeyName="IDX_ACCESS_TOKEN_PK" primaryKey="true" />
|
||||
<column name="TOKEN_HASH" type="varchar(40)" nullable="false"/>
|
||||
<column name="USER_NAME" type="varchar(100)" nullable="false"/>
|
||||
<column name="NOTE" type="text" nullable="false"/>
|
||||
</createTable>
|
||||
|
||||
<addPrimaryKey constraintName="IDX_ACCESS_TOKEN_PK" tableName="ACCESS_TOKEN" columnNames="ACCESS_TOKEN_ID"/>
|
||||
<addUniqueConstraint constraintName="IDX_ACCESS_TOKEN_TOKEN_HASH" tableName="ACCESS_TOKEN" columnNames="TOKEN_HASH"/>
|
||||
<addForeignKeyConstraint constraintName="IDX_ACCESS_TOKEN_FK0" baseTableName="ACCESS_TOKEN" baseColumnNames="USER_NAME" referencedTableName="ACCOUNT" referencedColumnNames="USER_NAME" onDelete="CASCADE" onUpdate="CASCADE"/>
|
||||
|
||||
@@ -74,7 +73,7 @@
|
||||
<!-- ACTIVITY -->
|
||||
<!--================================================================================================-->
|
||||
<createTable tableName="ACTIVITY">
|
||||
<column name="ACTIVITY_ID" type="int" nullable="false" autoIncrement="true" unique="true"/>
|
||||
<column name="ACTIVITY_ID" type="int" nullable="false" autoIncrement="true" unique="true" primaryKeyName="IDX_ACTIVITY_PK" primaryKey="true"/>
|
||||
<column name="USER_NAME" type="varchar(100)" nullable="false"/>
|
||||
<column name="REPOSITORY_NAME" type="varchar(100)" nullable="false"/>
|
||||
<column name="ACTIVITY_USER_NAME" type="varchar(100)" nullable="false"/>
|
||||
@@ -84,7 +83,6 @@
|
||||
<column name="ACTIVITY_DATE" type="datetime" nullable="false"/>
|
||||
</createTable>
|
||||
|
||||
<addPrimaryKey constraintName="IDX_ACTIVITY_PK" tableName="ACTIVITY" columnNames="ACTIVITY_ID"/>
|
||||
<addForeignKeyConstraint constraintName="IDX_ACTIVITY_FK1" baseTableName="ACTIVITY" baseColumnNames="ACTIVITY_USER_NAME" referencedTableName="ACCOUNT" referencedColumnNames="USER_NAME"/>
|
||||
<addForeignKeyConstraint constraintName="IDX_ACTIVITY_FK0" baseTableName="ACTIVITY" baseColumnNames="USER_NAME, REPOSITORY_NAME" referencedTableName="REPOSITORY" referencedColumnNames="USER_NAME, REPOSITORY_NAME"/>
|
||||
|
||||
@@ -108,7 +106,7 @@
|
||||
<column name="USER_NAME" type="varchar(100)" nullable="false"/>
|
||||
<column name="REPOSITORY_NAME" type="varchar(100)" nullable="false"/>
|
||||
<column name="COMMIT_ID" type="varchar(100)" nullable="false"/>
|
||||
<column name="COMMENT_ID" type="int" nullable="false" autoIncrement="true" unique="true"/>
|
||||
<column name="COMMENT_ID" type="int" nullable="false" autoIncrement="true" unique="true" primaryKeyName="IDX_COMMIT_COMMENT_PK" primaryKey="true" />
|
||||
<column name="COMMENTED_USER_NAME" type="varchar(100)" nullable="false"/>
|
||||
<column name="CONTENT" type="text" nullable="false"/>
|
||||
<column name="FILE_NAME" type="varchar(260)" nullable="true"/>
|
||||
@@ -119,14 +117,13 @@
|
||||
<column name="ISSUE_ID" type="int" nullable="true"/>
|
||||
</createTable>
|
||||
|
||||
<addPrimaryKey constraintName="IDX_COMMIT_COMMENT_PK" tableName="COMMIT_COMMENT" columnNames="COMMENT_ID"/>
|
||||
<addForeignKeyConstraint constraintName="IDX_COMMIT_COMMENT_FK0" baseTableName="COMMIT_COMMENT" baseColumnNames="USER_NAME, REPOSITORY_NAME" referencedTableName="REPOSITORY" referencedColumnNames="USER_NAME, REPOSITORY_NAME"/>
|
||||
|
||||
<!--================================================================================================-->
|
||||
<!-- COMMIT_STATUS -->
|
||||
<!--================================================================================================-->
|
||||
<createTable tableName="COMMIT_STATUS">
|
||||
<column name="COMMIT_STATUS_ID" type="int" nullable="false" autoIncrement="true" unique="true"/>
|
||||
<column name="COMMIT_STATUS_ID" type="int" nullable="false" autoIncrement="true" unique="true" primaryKeyName="IDX_COMMIT_STATUS_PK" primaryKey="true" />
|
||||
<column name="USER_NAME" type="varchar(100)" nullable="false"/>
|
||||
<column name="REPOSITORY_NAME" type="varchar(100)" nullable="false"/>
|
||||
<column name="COMMIT_ID" type="varchar(40)" nullable="false"/>
|
||||
@@ -139,7 +136,6 @@
|
||||
<column name="UPDATED_DATE" type="datetime" nullable="false"/>
|
||||
</createTable>
|
||||
|
||||
<addPrimaryKey constraintName="IDX_COMMIT_STATUS_PK" tableName="COMMIT_STATUS" columnNames="COMMIT_STATUS_ID"/>
|
||||
<addUniqueConstraint constraintName="IDX_COMMIT_STATUS_1" tableName="COMMIT_STATUS" columnNames="USER_NAME, REPOSITORY_NAME, COMMIT_ID, CONTEXT"/>
|
||||
<addForeignKeyConstraint constraintName="IDX_COMMIT_STATUS_FK3" baseTableName="COMMIT_STATUS" baseColumnNames="CREATOR" referencedTableName="ACCOUNT" referencedColumnNames="USER_NAME" onDelete="CASCADE" onUpdate="CASCADE"/>
|
||||
<addForeignKeyConstraint constraintName="IDX_COMMIT_STATUS_FK2" baseTableName="COMMIT_STATUS" baseColumnNames="USER_NAME" referencedTableName="ACCOUNT" referencedColumnNames="USER_NAME" onDelete="CASCADE" onUpdate="CASCADE"/>
|
||||
@@ -218,7 +214,7 @@
|
||||
<column name="USER_NAME" type="varchar(100)" nullable="false"/>
|
||||
<column name="REPOSITORY_NAME" type="varchar(100)" nullable="false"/>
|
||||
<column name="ISSUE_ID" type="int" nullable="false"/>
|
||||
<column name="COMMENT_ID" type="int" nullable="false" autoIncrement="true" unique="true"/>
|
||||
<column name="COMMENT_ID" type="int" nullable="false" autoIncrement="true" unique="true" primaryKeyName="IDX_ISSUE_COMMENT_PK" primaryKey="true" />
|
||||
<column name="ACTION" type="varchar(20)" nullable="false"/>
|
||||
<column name="COMMENTED_USER_NAME" type="varchar(100)" nullable="false"/>
|
||||
<column name="CONTENT" type="text" nullable="false"/>
|
||||
@@ -226,7 +222,6 @@
|
||||
<column name="UPDATED_DATE" type="datetime" nullable="false"/>
|
||||
</createTable>
|
||||
|
||||
<addPrimaryKey constraintName="IDX_ISSUE_COMMENT_PK" tableName="ISSUE_COMMENT" columnNames="COMMENT_ID"/>
|
||||
<addForeignKeyConstraint constraintName="IDX_ISSUE_COMMENT_FK0" baseTableName="ISSUE_COMMENT" baseColumnNames="USER_NAME, REPOSITORY_NAME, ISSUE_ID" referencedTableName="ISSUE" referencedColumnNames="USER_NAME, REPOSITORY_NAME, ISSUE_ID"/>
|
||||
|
||||
<!--================================================================================================-->
|
||||
|
||||
4
src/main/resources/update/gitbucket-core_4.34.xml
Normal file
4
src/main/resources/update/gitbucket-core_4.34.xml
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<changeSet>
|
||||
<dropTable tableName="ACTIVITY" />
|
||||
</changeSet>
|
||||
21
src/main/resources/update/gitbucket-core_4.35.xml
Normal file
21
src/main/resources/update/gitbucket-core_4.35.xml
Normal file
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<changeSet>
|
||||
<dropForeignKeyConstraint constraintName="IDX_WEB_HOOK_EVENT_FK0" baseTableName="WEB_HOOK_EVENT"/>
|
||||
<dropForeignKeyConstraint constraintName="IDX_WEB_HOOK_FK0" baseTableName="WEB_HOOK"/>
|
||||
<dropPrimaryKey tableName="WEB_HOOK" constraintName="IDX_WEB_HOOK_PK"/>
|
||||
<addColumn tableName="WEB_HOOK">
|
||||
<column name="HOOK_ID" type="int" nullable="false" unique="true"/>
|
||||
</addColumn>
|
||||
<addPrimaryKey constraintName="IDX_WEB_HOOK_PK" tableName="WEB_HOOK" columnNames="USER_NAME, REPOSITORY_NAME, URL, HOOK_ID"/>
|
||||
<addUniqueConstraint constraintName="IDX_WEB_HOOK_1" tableName="WEB_HOOK" columnNames="USER_NAME, REPOSITORY_NAME, URL"/>
|
||||
<addForeignKeyConstraint constraintName="IDX_WEB_HOOK_FK0" baseTableName="WEB_HOOK" baseColumnNames="USER_NAME, REPOSITORY_NAME" referencedTableName="REPOSITORY" referencedColumnNames="USER_NAME, REPOSITORY_NAME"/>
|
||||
<addForeignKeyConstraint constraintName="IDX_WEB_HOOK_EVENT_FK0" baseTableName="WEB_HOOK_EVENT" baseColumnNames="USER_NAME, REPOSITORY_NAME, URL" referencedTableName="WEB_HOOK" referencedColumnNames="USER_NAME, REPOSITORY_NAME, URL" onDelete="CASCADE" onUpdate="CASCADE"/>
|
||||
<addAutoIncrement columnName="HOOK_ID" columnDataType="int" tableName="WEB_HOOK"/>
|
||||
|
||||
<createTable tableName="ACCOUNT_PREFERENCE">
|
||||
<column name="USER_NAME" type="varchar(100)" nullable="false"/>
|
||||
<column name="HIGHLIGHTER_THEME" type="varchar(100)" nullable="false" defaultValue="prettify"/>
|
||||
</createTable>
|
||||
<addPrimaryKey constraintName="IDX_ACCOUNT_PREFERENCE_PK" tableName="ACCOUNT_PREFERENCE" columnNames="USER_NAME"/>
|
||||
<addForeignKeyConstraint constraintName="IDX_ACCOUNT_PREFERENCE_FK0" baseTableName="ACCOUNT_PREFERENCE" baseColumnNames="USER_NAME" referencedTableName="ACCOUNT" referencedColumnNames="USER_NAME" onDelete="CASCADE" onUpdate="CASCADE"/>
|
||||
</changeSet>
|
||||
@@ -16,7 +16,7 @@ class ScalatraBootstrap extends LifeCycle with SystemSettingsService {
|
||||
context.getSessionCookieConfig.setSecure(true)
|
||||
}
|
||||
|
||||
// Register TransactionFilter and BasicAuthenticationFilter at first
|
||||
// Register TransactionFilter at first
|
||||
context.addFilter("transactionFilter", new TransactionFilter)
|
||||
context
|
||||
.getFilterRegistration("transactionFilter")
|
||||
|
||||
@@ -1,7 +1,22 @@
|
||||
package gitbucket.core
|
||||
|
||||
import io.github.gitbucket.solidbase.migration.{SqlMigration, LiquibaseMigration}
|
||||
import io.github.gitbucket.solidbase.model.{Version, Module}
|
||||
import java.io.FileOutputStream
|
||||
import java.nio.charset.StandardCharsets
|
||||
import java.sql.Connection
|
||||
import java.util
|
||||
import java.util.UUID
|
||||
|
||||
import gitbucket.core.model.Activity
|
||||
import gitbucket.core.util.Directory.ActivityLog
|
||||
import gitbucket.core.util.JDBCUtil
|
||||
import io.github.gitbucket.solidbase.Solidbase
|
||||
import io.github.gitbucket.solidbase.migration.{LiquibaseMigration, Migration, SqlMigration}
|
||||
import io.github.gitbucket.solidbase.model.{Module, Version}
|
||||
import org.json4s.NoTypeHints
|
||||
import org.json4s.jackson.Serialization
|
||||
import org.json4s.jackson.Serialization.write
|
||||
|
||||
import scala.util.Using
|
||||
|
||||
object GitBucketCoreModule
|
||||
extends Module(
|
||||
@@ -65,5 +80,39 @@ object GitBucketCoreModule
|
||||
new Version("4.31.1"),
|
||||
new Version("4.31.2"),
|
||||
new Version("4.32.0", new LiquibaseMigration("update/gitbucket-core_4.32.xml")),
|
||||
new Version("4.33.0")
|
||||
new Version("4.33.0"),
|
||||
new Version(
|
||||
"4.34.0",
|
||||
new Migration() {
|
||||
override def migrate(moduleId: String, version: String, context: util.Map[String, AnyRef]): Unit = {
|
||||
implicit val formats = Serialization.formats(NoTypeHints)
|
||||
import JDBCUtil._
|
||||
|
||||
val conn = context.get(Solidbase.CONNECTION).asInstanceOf[Connection]
|
||||
val list = conn.select("SELECT * FROM ACTIVITY ORDER BY ACTIVITY_ID") {
|
||||
rs =>
|
||||
Activity(
|
||||
activityId = UUID.randomUUID().toString,
|
||||
userName = rs.getString("USER_NAME"),
|
||||
repositoryName = rs.getString("REPOSITORY_NAME"),
|
||||
activityUserName = rs.getString("ACTIVITY_USER_NAME"),
|
||||
activityType = rs.getString("ACTIVITY_TYPE"),
|
||||
message = rs.getString("MESSAGE"),
|
||||
additionalInfo = {
|
||||
val additionalInfo = rs.getString("ADDITIONAL_INFO")
|
||||
if (rs.wasNull()) None else Some(additionalInfo)
|
||||
},
|
||||
activityDate = rs.getTimestamp("ACTIVITY_DATE")
|
||||
)
|
||||
}
|
||||
Using.resource(new FileOutputStream(ActivityLog, true)) { out =>
|
||||
list.foreach { activity =>
|
||||
out.write((write(activity) + "\n").getBytes(StandardCharsets.UTF_8))
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
new LiquibaseMigration("update/gitbucket-core_4.34.xml")
|
||||
),
|
||||
new Version("4.35.0", new LiquibaseMigration("update/gitbucket-core_4.35.xml")),
|
||||
)
|
||||
|
||||
@@ -22,3 +22,12 @@ case class ApiBranchForList(
|
||||
name: String,
|
||||
commit: ApiBranchCommit
|
||||
)
|
||||
|
||||
/**
|
||||
* https://docs.github.com/en/rest/reference/repos#list-branches-for-head-commit
|
||||
*/
|
||||
case class ApiBranchForHeadCommit(
|
||||
name: String,
|
||||
commit: ApiBranchCommit,
|
||||
`protected`: Boolean
|
||||
)
|
||||
|
||||
@@ -4,7 +4,11 @@ import gitbucket.core.service.ProtectedBranchService
|
||||
import org.json4s._
|
||||
|
||||
/** https://developer.github.com/v3/repos/#enabling-and-disabling-branch-protection */
|
||||
case class ApiBranchProtection(enabled: Boolean, required_status_checks: Option[ApiBranchProtection.Status]) {
|
||||
case class ApiBranchProtection(
|
||||
url: Option[ApiPath], // for output
|
||||
enabled: Boolean,
|
||||
required_status_checks: Option[ApiBranchProtection.Status]
|
||||
) {
|
||||
def status: ApiBranchProtection.Status = required_status_checks.getOrElse(ApiBranchProtection.statusNone)
|
||||
}
|
||||
|
||||
@@ -15,13 +19,36 @@ object ApiBranchProtection {
|
||||
|
||||
def apply(info: ProtectedBranchService.ProtectedBranchInfo): ApiBranchProtection =
|
||||
ApiBranchProtection(
|
||||
url = Some(
|
||||
ApiPath(
|
||||
s"/api/v3/repos/${info.owner}/${info.repository}/branches/${info.branch}/protection"
|
||||
)
|
||||
),
|
||||
enabled = info.enabled,
|
||||
required_status_checks = Some(
|
||||
Status(EnforcementLevel(info.enabled && info.contexts.nonEmpty, info.includeAdministrators), info.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,
|
||||
Some(
|
||||
ApiPath(
|
||||
s"/api/v3/repos/${info.owner}/${info.repository}/branches/${info.branch}/protection/required_status_checks/contexts"
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
val statusNone = Status(Off, Seq.empty)
|
||||
case class Status(enforcement_level: EnforcementLevel, contexts: Seq[String])
|
||||
val statusNone = 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")
|
||||
|
||||
49
src/main/scala/gitbucket/core/api/ApiMilestone.scala
Normal file
49
src/main/scala/gitbucket/core/api/ApiMilestone.scala
Normal file
@@ -0,0 +1,49 @@
|
||||
package gitbucket.core.api
|
||||
|
||||
import gitbucket.core.model.{Milestone, Repository}
|
||||
import gitbucket.core.util.RepositoryName
|
||||
import java.util.Date
|
||||
|
||||
/**
|
||||
* https://docs.github.com/en/rest/reference/issues#milestones
|
||||
*/
|
||||
case class ApiMilestone(
|
||||
url: ApiPath,
|
||||
html_url: ApiPath,
|
||||
// label_url: ApiPath,
|
||||
id: Int,
|
||||
number: Int,
|
||||
state: String,
|
||||
title: String,
|
||||
description: String,
|
||||
// creator: ApiUser, // MILESTONE table does not have created user column
|
||||
open_issues: Int,
|
||||
closed_issues: Int,
|
||||
// created_at: Option[Date],
|
||||
// updated_at: Option[Date],
|
||||
closed_at: Option[Date],
|
||||
due_on: Option[Date]
|
||||
)
|
||||
|
||||
object ApiMilestone {
|
||||
def apply(
|
||||
repository: Repository,
|
||||
milestone: Milestone,
|
||||
open_issue_count: Int = 0,
|
||||
closed_issue_count: Int = 0
|
||||
): ApiMilestone =
|
||||
ApiMilestone(
|
||||
url = ApiPath(s"/api/v3/repos/${RepositoryName(repository).fullName}/milestones/${milestone.milestoneId}"),
|
||||
html_url = ApiPath(s"/${RepositoryName(repository).fullName}/milestone/${milestone.milestoneId}"),
|
||||
// label_url = ApiPath(s"/api/v3/repos/${RepositoryName(repository).fullName}/milestones/${milestone_number}/labels"),
|
||||
id = milestone.milestoneId,
|
||||
number = milestone.milestoneId, // use milestoneId as number
|
||||
state = if (milestone.closedDate.isDefined) "closed" else "open",
|
||||
title = milestone.title,
|
||||
description = milestone.description.getOrElse(""),
|
||||
open_issues = open_issue_count,
|
||||
closed_issues = closed_issue_count,
|
||||
closed_at = milestone.closedDate,
|
||||
due_on = milestone.dueDate
|
||||
)
|
||||
}
|
||||
@@ -21,7 +21,8 @@ case class ApiPullRequest(
|
||||
body: String,
|
||||
user: ApiUser,
|
||||
labels: List[ApiLabel],
|
||||
assignee: Option[ApiUser]
|
||||
assignee: Option[ApiUser],
|
||||
draft: Option[Boolean]
|
||||
) {
|
||||
val id = 0 // dummy id
|
||||
val html_url = ApiPath(s"${base.repo.html_url.path}/pull/${number}")
|
||||
@@ -62,7 +63,8 @@ object ApiPullRequest {
|
||||
body = issue.content.getOrElse(""),
|
||||
user = user,
|
||||
labels = labels,
|
||||
assignee = assignee
|
||||
assignee = assignee,
|
||||
draft = Some(pullRequest.isDraft)
|
||||
)
|
||||
|
||||
case class Commit(sha: String, ref: String, repo: ApiRepository)(baseOwner: String) {
|
||||
|
||||
@@ -6,7 +6,7 @@ case class ApiReleaseAsset(name: String, size: Long)(tag: String, fileName: Stri
|
||||
val label = name
|
||||
val file_id = fileName
|
||||
val browser_download_url = ApiPath(
|
||||
s"/api/v3/repos/${repositoryName.fullName}/releases/${tag}/assets/${fileName}"
|
||||
s"/${repositoryName.fullName}/releases/${tag}/assets/${fileName}"
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -12,13 +12,13 @@ case class ApiRepository(
|
||||
forks: Int,
|
||||
`private`: Boolean,
|
||||
default_branch: String,
|
||||
owner: ApiUser
|
||||
owner: ApiUser,
|
||||
has_issues: Boolean
|
||||
) {
|
||||
val id = 0 // dummy id
|
||||
val forks_count = forks
|
||||
val watchers_count = watchers
|
||||
val url = ApiPath(s"/api/v3/repos/${full_name}")
|
||||
val http_url = ApiPath(s"/git/${full_name}.git")
|
||||
val clone_url = ApiPath(s"/git/${full_name}.git")
|
||||
val html_url = ApiPath(s"/${full_name}")
|
||||
val ssh_url = Some(SshPath(s":${full_name}.git"))
|
||||
@@ -39,11 +39,16 @@ object ApiRepository {
|
||||
forks = forkedCount,
|
||||
`private` = repository.isPrivate,
|
||||
default_branch = repository.defaultBranch,
|
||||
owner = owner
|
||||
owner = owner,
|
||||
has_issues = if (repository.options.issuesOption == "DISABLE") false else true
|
||||
)
|
||||
|
||||
def apply(repositoryInfo: RepositoryInfo, owner: ApiUser): ApiRepository =
|
||||
ApiRepository(repositoryInfo.repository, owner, forkedCount = repositoryInfo.forkedCount)
|
||||
ApiRepository(
|
||||
repositoryInfo.repository,
|
||||
owner,
|
||||
forkedCount = repositoryInfo.forkedCount
|
||||
)
|
||||
|
||||
def apply(repositoryInfo: RepositoryInfo, owner: Account): ApiRepository =
|
||||
this(repositoryInfo, ApiUser(owner))
|
||||
@@ -57,6 +62,7 @@ object ApiRepository {
|
||||
forks = 0,
|
||||
`private` = false,
|
||||
default_branch = "master",
|
||||
owner = owner
|
||||
owner = owner,
|
||||
has_issues = true
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
package gitbucket.core.api
|
||||
|
||||
case class ApiRepositoryCollaborator(
|
||||
permission: String,
|
||||
user: ApiUser
|
||||
)
|
||||
29
src/main/scala/gitbucket/core/api/ApiTag.scala
Normal file
29
src/main/scala/gitbucket/core/api/ApiTag.scala
Normal file
@@ -0,0 +1,29 @@
|
||||
package gitbucket.core.api
|
||||
|
||||
import gitbucket.core.util.RepositoryName
|
||||
|
||||
case class ApiTagCommit(
|
||||
sha: String,
|
||||
url: ApiPath
|
||||
)
|
||||
|
||||
case class ApiTag(
|
||||
name: String,
|
||||
commit: ApiTagCommit,
|
||||
zipball_url: ApiPath,
|
||||
tarball_url: ApiPath
|
||||
)
|
||||
|
||||
object ApiTag {
|
||||
def apply(
|
||||
tagName: String,
|
||||
repositoryName: RepositoryName,
|
||||
commitId: String
|
||||
): ApiTag =
|
||||
ApiTag(
|
||||
name = tagName,
|
||||
commit = ApiTagCommit(sha = commitId, url = ApiPath(s"/${repositoryName.fullName}/commits/${commitId}")),
|
||||
zipball_url = ApiPath(s"/${repositoryName.fullName}/archive/${tagName}.zip"),
|
||||
tarball_url = ApiPath(s"/${repositoryName.fullName}/archive/${tagName}.tar.gz")
|
||||
)
|
||||
}
|
||||
46
src/main/scala/gitbucket/core/api/ApiWebhook.scala
Normal file
46
src/main/scala/gitbucket/core/api/ApiWebhook.scala
Normal file
@@ -0,0 +1,46 @@
|
||||
package gitbucket.core.api
|
||||
|
||||
import gitbucket.core.model.Profile.{RepositoryWebHookEvents, RepositoryWebHooks}
|
||||
import gitbucket.core.model.{RepositoryWebHook, WebHook}
|
||||
import gitbucket.core.util.RepositoryName
|
||||
|
||||
/**
|
||||
* https://docs.github.com/en/rest/reference/repos#webhooks
|
||||
*/
|
||||
case class ApiWebhookConfig(
|
||||
content_type: String,
|
||||
// insecure_ssl: String,
|
||||
url: String
|
||||
)
|
||||
|
||||
case class ApiWebhook(
|
||||
`type`: String,
|
||||
id: Int,
|
||||
name: String,
|
||||
active: Boolean,
|
||||
events: List[String],
|
||||
config: ApiWebhookConfig,
|
||||
// updated_at: Option[Date],
|
||||
// created_at: Option[Date],
|
||||
url: ApiPath,
|
||||
// test_url: ApiPath,
|
||||
// ping_url: ApiPath,
|
||||
// last_response: ...
|
||||
)
|
||||
|
||||
object ApiWebhook {
|
||||
def apply(
|
||||
_type: String,
|
||||
hook: RepositoryWebHook,
|
||||
hookEvents: Set[WebHook.Event]
|
||||
): ApiWebhook =
|
||||
ApiWebhook(
|
||||
`type` = _type,
|
||||
id = hook.hookId,
|
||||
name = "web", // dummy
|
||||
active = true, // dummy
|
||||
events = hookEvents.toList.map(_.name),
|
||||
config = ApiWebhookConfig(hook.ctype.code, hook.url),
|
||||
url = ApiPath(s"/api/v3/${hook.userName}/${hook.repositoryName}/hooks/${hook.hookId}")
|
||||
)
|
||||
}
|
||||
14
src/main/scala/gitbucket/core/api/CreateAMilestone.scala
Normal file
14
src/main/scala/gitbucket/core/api/CreateAMilestone.scala
Normal file
@@ -0,0 +1,14 @@
|
||||
package gitbucket.core.api
|
||||
|
||||
import java.util.Date
|
||||
|
||||
case class CreateAMilestone(
|
||||
title: String,
|
||||
state: String = "open",
|
||||
description: Option[String],
|
||||
due_on: Option[Date]
|
||||
) {
|
||||
def isValid: Boolean = {
|
||||
title.length <= 100 && title.matches("[a-zA-Z0-9\\-\\+_.]+")
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,8 @@ case class CreateAPullRequest(
|
||||
head: String,
|
||||
base: String,
|
||||
body: Option[String],
|
||||
maintainer_can_modify: Option[Boolean]
|
||||
maintainer_can_modify: Option[Boolean],
|
||||
draft: Option[Boolean]
|
||||
)
|
||||
|
||||
case class CreateAPullRequestAlt(
|
||||
@@ -14,3 +15,11 @@ case class CreateAPullRequestAlt(
|
||||
base: String,
|
||||
maintainer_can_modify: Option[Boolean]
|
||||
)
|
||||
|
||||
case class UpdateAPullRequest(
|
||||
title: Option[String],
|
||||
body: Option[String],
|
||||
state: Option[String],
|
||||
base: Option[String],
|
||||
maintainer_can_modify: Option[Boolean],
|
||||
)
|
||||
|
||||
7
src/main/scala/gitbucket/core/api/CreateARef.scala
Normal file
7
src/main/scala/gitbucket/core/api/CreateARef.scala
Normal file
@@ -0,0 +1,7 @@
|
||||
package gitbucket.core.api
|
||||
|
||||
/**
|
||||
* https://docs.github.com/en/free-pro-team@latest/rest/reference/git#create-a-reference
|
||||
* api form
|
||||
*/
|
||||
case class CreateARef(ref: String, sha: String)
|
||||
@@ -0,0 +1,35 @@
|
||||
package gitbucket.core.api
|
||||
|
||||
case class CreateARepositoryWebhookConfig(
|
||||
url: String,
|
||||
content_type: String = "form",
|
||||
insecure_ssl: String = "0",
|
||||
secret: Option[String]
|
||||
)
|
||||
|
||||
/**
|
||||
* https://docs.github.com/en/rest/reference/repos#create-a-repository-webhook
|
||||
*/
|
||||
case class CreateARepositoryWebhook(
|
||||
name: String = "web",
|
||||
config: CreateARepositoryWebhookConfig,
|
||||
events: List[String] = List("push"),
|
||||
active: Boolean = true
|
||||
) {
|
||||
def isValid: Boolean = {
|
||||
config.content_type == "form" || config.content_type == "json"
|
||||
}
|
||||
}
|
||||
|
||||
case class UpdateARepositoryWebhook(
|
||||
name: String = "web",
|
||||
config: CreateARepositoryWebhookConfig,
|
||||
events: List[String] = List("push"),
|
||||
add_events: List[String] = List(),
|
||||
remove_events: List[String] = List(),
|
||||
active: Boolean = true
|
||||
) {
|
||||
def isValid: Boolean = {
|
||||
config.content_type == "form" || config.content_type == "json"
|
||||
}
|
||||
}
|
||||
23
src/main/scala/gitbucket/core/api/MergeAPullRequest.scala
Normal file
23
src/main/scala/gitbucket/core/api/MergeAPullRequest.scala
Normal file
@@ -0,0 +1,23 @@
|
||||
package gitbucket.core.api
|
||||
|
||||
/**
|
||||
* https://docs.github.com/en/rest/reference/pulls#merge-a-pull-request
|
||||
*/
|
||||
case class MergeAPullRequest(
|
||||
commit_title: Option[String],
|
||||
commit_message: Option[String],
|
||||
/* TODO: Not Implemented
|
||||
sha: Option[String],*/
|
||||
merge_method: Option[String]
|
||||
)
|
||||
|
||||
case class SuccessToMergePrResponse(
|
||||
sha: String,
|
||||
merged: Boolean,
|
||||
message: String
|
||||
)
|
||||
|
||||
case class FailToMergePrResponse(
|
||||
documentation_url: String,
|
||||
message: String
|
||||
)
|
||||
7
src/main/scala/gitbucket/core/api/UpdateARef.scala
Normal file
7
src/main/scala/gitbucket/core/api/UpdateARef.scala
Normal file
@@ -0,0 +1,7 @@
|
||||
package gitbucket.core.api
|
||||
|
||||
/**
|
||||
* https://docs.github.com/en/free-pro-team@latest/rest/reference/git#update-a-reference
|
||||
* api form
|
||||
*/
|
||||
case class UpdateARef(sha: String, force: Boolean)
|
||||
@@ -16,6 +16,7 @@ import gitbucket.core.util._
|
||||
import org.scalatra.i18n.Messages
|
||||
import org.scalatra.BadRequest
|
||||
import org.scalatra.forms._
|
||||
import org.scalatra.Forbidden
|
||||
|
||||
class AccountController
|
||||
extends AccountControllerBase
|
||||
@@ -34,6 +35,7 @@ class AccountController
|
||||
with WebHookService
|
||||
with PrioritiesService
|
||||
with RepositoryCreationService
|
||||
with RequestCache
|
||||
|
||||
trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
self: AccountService
|
||||
@@ -80,9 +82,11 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
|
||||
case class PersonalTokenForm(note: String)
|
||||
|
||||
case class SyntaxHighlighterThemeForm(theme: String)
|
||||
|
||||
val newForm = mapping(
|
||||
"userName" -> trim(label("User name", text(required, maxlength(100), identifier, uniqueUserName, reservedNames))),
|
||||
"password" -> trim(label("Password", text(required, maxlength(20), password))),
|
||||
"password" -> trim(label("Password", text(required, maxlength(20)))),
|
||||
"fullName" -> trim(label("Full Name", text(required, maxlength(100)))),
|
||||
"mailAddress" -> trim(label("Mail Address", text(required, maxlength(100), uniqueMailAddress()))),
|
||||
"extraMailAddresses" -> list(
|
||||
@@ -94,7 +98,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
)(AccountNewForm.apply)
|
||||
|
||||
val editForm = mapping(
|
||||
"password" -> trim(label("Password", optional(text(maxlength(20), password)))),
|
||||
"password" -> trim(label("Password", optional(text(maxlength(20))))),
|
||||
"fullName" -> trim(label("Full Name", text(required, maxlength(100)))),
|
||||
"mailAddress" -> trim(label("Mail Address", text(required, maxlength(100), uniqueMailAddress("userName")))),
|
||||
"extraMailAddresses" -> list(
|
||||
@@ -120,6 +124,10 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
"note" -> trim(label("Token", text(required, maxlength(100))))
|
||||
)(PersonalTokenForm.apply)
|
||||
|
||||
val syntaxHighlighterThemeForm = mapping(
|
||||
"highlighterTheme" -> trim(label("Theme", text(required)))
|
||||
)(SyntaxHighlighterThemeForm.apply)
|
||||
|
||||
case class NewGroupForm(
|
||||
groupName: String,
|
||||
description: Option[String],
|
||||
@@ -440,6 +448,29 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
redirect(s"/${userName}/_application")
|
||||
})
|
||||
|
||||
/**
|
||||
* Display the user preference settings page
|
||||
*/
|
||||
get("/:userName/_preferences")(oneselfOnly {
|
||||
val userName = params("userName")
|
||||
val currentTheme = getAccountPreference(userName) match {
|
||||
case Some(accountHighlighter) => accountHighlighter.highlighterTheme
|
||||
case _ => "github-v2"
|
||||
}
|
||||
getAccountByUserName(userName).map { x =>
|
||||
html.preferences(x, currentTheme)
|
||||
} getOrElse NotFound()
|
||||
})
|
||||
|
||||
/**
|
||||
* Update the syntax highlighter setting of user
|
||||
*/
|
||||
post("/:userName/_preferences/highlighter", syntaxHighlighterThemeForm)(oneselfOnly { form =>
|
||||
val userName = params("userName")
|
||||
addOrUpdateAccountPreference(userName, form.theme)
|
||||
redirect(s"/${userName}/_preferences")
|
||||
})
|
||||
|
||||
get("/:userName/_hooks")(managersOnly {
|
||||
val userName = params("userName")
|
||||
getAccountByUserName(userName).map { account =>
|
||||
@@ -520,7 +551,8 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
val url = params("url")
|
||||
val token = Some(params("token"))
|
||||
val ctype = WebHookContentType.valueOf(params("ctype"))
|
||||
val dummyWebHookInfo = RepositoryWebHook(userName, "dummy", url, ctype, token)
|
||||
val dummyWebHookInfo =
|
||||
RepositoryWebHook(userName = userName, repositoryName = "dummy", url = url, ctype = ctype, token = token)
|
||||
val dummyPayload = {
|
||||
val ownerAccount = getAccountByUserName(userName).get
|
||||
WebHookPushPayload.createDummyPayload(ownerAccount)
|
||||
@@ -691,27 +723,28 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
* Create new repository.
|
||||
*/
|
||||
post("/new", newRepositoryForm)(usersOnly { form =>
|
||||
LockUtil.lock(s"${form.owner}/${form.name}") {
|
||||
if (getRepository(form.owner, form.name).isEmpty) {
|
||||
createRepository(
|
||||
context.loginAccount.get,
|
||||
form.owner,
|
||||
form.name,
|
||||
form.description,
|
||||
form.isPrivate,
|
||||
form.initOption,
|
||||
form.sourceUrl
|
||||
)
|
||||
if (context.settings.repositoryOperation.create || context.loginAccount.get.isAdmin) {
|
||||
LockUtil.lock(s"${form.owner}/${form.name}") {
|
||||
if (getRepository(form.owner, form.name).isEmpty) {
|
||||
createRepository(
|
||||
context.loginAccount.get,
|
||||
form.owner,
|
||||
form.name,
|
||||
form.description,
|
||||
form.isPrivate,
|
||||
form.initOption,
|
||||
form.sourceUrl
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// redirect to the repository
|
||||
redirect(s"/${form.owner}/${form.name}")
|
||||
// redirect to the repository
|
||||
redirect(s"/${form.owner}/${form.name}")
|
||||
} else Forbidden()
|
||||
})
|
||||
|
||||
get("/:owner/:repository/fork")(readableUsersOnly { repository =>
|
||||
if (repository.repository.options.allowFork) {
|
||||
val loginAccount = context.loginAccount.get
|
||||
val loginAccount = context.loginAccount.get
|
||||
if (repository.repository.options.allowFork && (context.settings.repositoryOperation.fork || loginAccount.isAdmin)) {
|
||||
val loginUserName = loginAccount.userName
|
||||
val groups = getGroupsByUserName(loginUserName)
|
||||
groups match {
|
||||
@@ -735,8 +768,8 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
})
|
||||
|
||||
post("/:owner/:repository/fork", accountForm)(readableUsersOnly { (form, repository) =>
|
||||
if (repository.repository.options.allowFork) {
|
||||
val loginAccount = context.loginAccount.get
|
||||
val loginAccount = context.loginAccount.get
|
||||
if (repository.repository.options.allowFork && (context.settings.repositoryOperation.fork || loginAccount.isAdmin)) {
|
||||
val loginUserName = loginAccount.userName
|
||||
val accountName = form.accountName
|
||||
|
||||
@@ -750,7 +783,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
// redirect to the repository
|
||||
redirect(s"/${accountName}/${repository.name}")
|
||||
}
|
||||
} else BadRequest()
|
||||
} else Forbidden()
|
||||
})
|
||||
|
||||
private def existsAccount: Constraint = new Constraint() {
|
||||
|
||||
@@ -13,6 +13,7 @@ class ApiController
|
||||
with ApiIssueCommentControllerBase
|
||||
with ApiIssueControllerBase
|
||||
with ApiIssueLabelControllerBase
|
||||
with ApiIssueMilestoneControllerBase
|
||||
with ApiOrganizationControllerBase
|
||||
with ApiPullRequestControllerBase
|
||||
with ApiReleaseControllerBase
|
||||
@@ -22,6 +23,7 @@ class ApiController
|
||||
with ApiRepositoryContentsControllerBase
|
||||
with ApiRepositoryControllerBase
|
||||
with ApiRepositoryStatusControllerBase
|
||||
with ApiRepositoryWebhookControllerBase
|
||||
with ApiUserControllerBase
|
||||
with RepositoryService
|
||||
with AccountService
|
||||
@@ -52,6 +54,7 @@ class ApiController
|
||||
with ReferrerAuthenticator
|
||||
with ReadableUsersAuthenticator
|
||||
with WritableUsersAuthenticator
|
||||
with RequestCache
|
||||
|
||||
trait ApiControllerBase extends ControllerBase {
|
||||
|
||||
|
||||
@@ -21,13 +21,25 @@ class DashboardController
|
||||
with WebHookPullRequestService
|
||||
with WebHookPullRequestReviewCommentService
|
||||
with MilestonesService
|
||||
with CommitStatusService
|
||||
with UsersAuthenticator
|
||||
with RequestCache
|
||||
|
||||
trait DashboardControllerBase extends ControllerBase {
|
||||
self: IssuesService with PullRequestService with RepositoryService with AccountService with UsersAuthenticator =>
|
||||
self: IssuesService
|
||||
with PullRequestService
|
||||
with RepositoryService
|
||||
with AccountService
|
||||
with CommitStatusService
|
||||
with UsersAuthenticator =>
|
||||
|
||||
get("/dashboard/repos")(usersOnly {
|
||||
val repos = getVisibleRepositories(context.loginAccount, withoutPhysicalInfo = true)
|
||||
val repos = getVisibleRepositories(
|
||||
context.loginAccount,
|
||||
None,
|
||||
withoutPhysicalInfo = true,
|
||||
limit = context.settings.limitVisibleRepositories
|
||||
)
|
||||
html.repos(getGroupNames(context.loginAccount.get.userName), repos, repos)
|
||||
})
|
||||
|
||||
@@ -80,12 +92,13 @@ trait DashboardControllerBase extends ControllerBase {
|
||||
val condition = getOrCreateCondition(Keys.Session.DashboardIssues, filter, userName)
|
||||
val userRepos = getUserRepositories(userName, true).map(repo => repo.owner -> repo.name)
|
||||
val page = IssueSearchCondition.page(request)
|
||||
val issues = searchIssue(condition, IssueSearchOption.Issues, (page - 1) * IssueLimit, IssueLimit, userRepos: _*)
|
||||
|
||||
html.issues(
|
||||
searchIssue(condition, false, (page - 1) * IssueLimit, IssueLimit, userRepos: _*),
|
||||
issues.map(issue => (issue, None)),
|
||||
page,
|
||||
countIssue(condition.copy(state = "open"), false, userRepos: _*),
|
||||
countIssue(condition.copy(state = "closed"), false, userRepos: _*),
|
||||
countIssue(condition.copy(state = "open"), IssueSearchOption.Issues, userRepos: _*),
|
||||
countIssue(condition.copy(state = "closed"), IssueSearchOption.Issues, userRepos: _*),
|
||||
filter match {
|
||||
case "assigned" => condition.copy(assigned = Some(Some(userName)))
|
||||
case "mentioned" => condition.copy(mentioned = Some(userName))
|
||||
@@ -93,7 +106,12 @@ trait DashboardControllerBase extends ControllerBase {
|
||||
},
|
||||
filter,
|
||||
getGroupNames(userName),
|
||||
getVisibleRepositories(context.loginAccount, withoutPhysicalInfo = true)
|
||||
getVisibleRepositories(
|
||||
context.loginAccount,
|
||||
None,
|
||||
withoutPhysicalInfo = true,
|
||||
limit = context.settings.limitVisibleRepositories
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -105,12 +123,24 @@ trait DashboardControllerBase extends ControllerBase {
|
||||
val condition = getOrCreateCondition(Keys.Session.DashboardPulls, filter, userName)
|
||||
val allRepos = getAllRepositories(userName)
|
||||
val page = IssueSearchCondition.page(request)
|
||||
val issues = searchIssue(
|
||||
condition,
|
||||
IssueSearchOption.PullRequests,
|
||||
(page - 1) * PullRequestLimit,
|
||||
PullRequestLimit,
|
||||
allRepos: _*
|
||||
)
|
||||
val status = issues.map { issue =>
|
||||
issue.commitId.flatMap { commitId =>
|
||||
getCommitStatusWithSummary(issue.issue.userName, issue.issue.repositoryName, commitId)
|
||||
}
|
||||
}
|
||||
|
||||
html.pulls(
|
||||
searchIssue(condition, true, (page - 1) * PullRequestLimit, PullRequestLimit, allRepos: _*),
|
||||
issues.zip(status),
|
||||
page,
|
||||
countIssue(condition.copy(state = "open"), true, allRepos: _*),
|
||||
countIssue(condition.copy(state = "closed"), true, allRepos: _*),
|
||||
countIssue(condition.copy(state = "open"), IssueSearchOption.PullRequests, allRepos: _*),
|
||||
countIssue(condition.copy(state = "closed"), IssueSearchOption.PullRequests, allRepos: _*),
|
||||
filter match {
|
||||
case "assigned" => condition.copy(assigned = Some(Some(userName)))
|
||||
case "mentioned" => condition.copy(mentioned = Some(userName))
|
||||
@@ -118,7 +148,12 @@ trait DashboardControllerBase extends ControllerBase {
|
||||
},
|
||||
filter,
|
||||
getGroupNames(userName),
|
||||
getVisibleRepositories(context.loginAccount, withoutPhysicalInfo = true)
|
||||
getVisibleRepositories(
|
||||
context.loginAccount,
|
||||
None,
|
||||
withoutPhysicalInfo = true,
|
||||
limit = context.settings.limitVisibleRepositories
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ import org.scalatra.servlet.{FileItem, FileUploadSupport, MultipartConfig}
|
||||
import org.apache.commons.io.{FileUtils, IOUtils}
|
||||
|
||||
import scala.util.Using
|
||||
import gitbucket.core.service.SystemSettingsService
|
||||
|
||||
/**
|
||||
* Provides Ajax based file upload functionality.
|
||||
@@ -28,11 +29,11 @@ class FileUploadController
|
||||
with FileUploadSupport
|
||||
with RepositoryService
|
||||
with AccountService
|
||||
with ReleaseService {
|
||||
|
||||
configureMultipartHandling(MultipartConfig(maxFileSize = Some(FileUtil.MaxFileSize)))
|
||||
with ReleaseService
|
||||
with SystemSettingsService {
|
||||
|
||||
post("/image") {
|
||||
setMultipartConfig()
|
||||
execute(
|
||||
{ (file, fileId) =>
|
||||
FileUtils
|
||||
@@ -44,6 +45,7 @@ class FileUploadController
|
||||
}
|
||||
|
||||
post("/tmp") {
|
||||
setMultipartConfig()
|
||||
execute(
|
||||
{ (file, fileId) =>
|
||||
FileUtils
|
||||
@@ -55,6 +57,7 @@ class FileUploadController
|
||||
}
|
||||
|
||||
post("/file/:owner/:repository") {
|
||||
setMultipartConfig()
|
||||
execute(
|
||||
{ (file, fileId) =>
|
||||
FileUtils.writeByteArrayToFile(
|
||||
@@ -70,6 +73,7 @@ class FileUploadController
|
||||
}
|
||||
|
||||
post("/wiki/:owner/:repository") {
|
||||
setMultipartConfig()
|
||||
// Don't accept not logged-in users
|
||||
session.get(Keys.Session.LoginAccount).collect {
|
||||
case loginAccount: Account =>
|
||||
@@ -128,6 +132,7 @@ class FileUploadController
|
||||
}
|
||||
|
||||
post("/release/:owner/:repository/:tag") {
|
||||
setMultipartConfigForLargeFile()
|
||||
session
|
||||
.get(Keys.Session.LoginAccount)
|
||||
.collect {
|
||||
@@ -150,6 +155,7 @@ class FileUploadController
|
||||
|
||||
post("/import") {
|
||||
import JDBCUtil._
|
||||
setMultipartConfig()
|
||||
session.get(Keys.Session.LoginAccount).collect {
|
||||
case loginAccount: Account if loginAccount.isAdmin =>
|
||||
execute({ (file, fileId) =>
|
||||
@@ -159,6 +165,18 @@ class FileUploadController
|
||||
redirect("/admin/data")
|
||||
}
|
||||
|
||||
private def setMultipartConfig(): Unit = {
|
||||
val settings = loadSystemSettings()
|
||||
val config = MultipartConfig(maxFileSize = Some(settings.upload.maxFileSize))
|
||||
config.apply(request.getServletContext())
|
||||
}
|
||||
|
||||
private def setMultipartConfigForLargeFile(): Unit = {
|
||||
val settings = loadSystemSettings()
|
||||
val config = MultipartConfig(maxFileSize = Some(settings.upload.largeMaxFileSize))
|
||||
config.apply(request.getServletContext())
|
||||
}
|
||||
|
||||
private def onlyWikiEditable(owner: String, repository: String, loginAccount: Account)(action: => Any): Any = {
|
||||
implicit val session = Database.getSession(request)
|
||||
getRepository(owner, repository) match {
|
||||
|
||||
@@ -29,6 +29,7 @@ class IndexController
|
||||
with AccessTokenService
|
||||
with AccountFederationService
|
||||
with OpenIDConnectService
|
||||
with RequestCache
|
||||
|
||||
trait IndexControllerBase extends ControllerBase {
|
||||
self: RepositoryService
|
||||
@@ -65,7 +66,12 @@ trait IndexControllerBase extends ControllerBase {
|
||||
val visibleOwnerSet: Set[String] = Set(account.userName) ++ getGroupsByUserName(account.userName)
|
||||
gitbucket.core.html.index(
|
||||
getRecentActivitiesByOwners(visibleOwnerSet),
|
||||
getVisibleRepositories(Some(account), withoutPhysicalInfo = true),
|
||||
getVisibleRepositories(
|
||||
Some(account),
|
||||
None,
|
||||
withoutPhysicalInfo = true,
|
||||
limit = context.settings.limitVisibleRepositories
|
||||
),
|
||||
showBannerToCreatePersonalAccessToken = hasAccountFederation(account.userName) && !hasAccessToken(
|
||||
account.userName
|
||||
)
|
||||
@@ -73,7 +79,7 @@ trait IndexControllerBase extends ControllerBase {
|
||||
}
|
||||
.getOrElse {
|
||||
gitbucket.core.html.index(
|
||||
getRecentActivities(),
|
||||
getRecentPublicActivities(),
|
||||
getVisibleRepositories(None, withoutPhysicalInfo = true),
|
||||
showBannerToCreatePersonalAccessToken = false
|
||||
)
|
||||
@@ -156,7 +162,7 @@ trait IndexControllerBase extends ControllerBase {
|
||||
|
||||
get("/activities.atom") {
|
||||
contentType = "application/atom+xml; type=feed"
|
||||
xml.feed(getRecentActivities())
|
||||
xml.feed(getRecentPublicActivities())
|
||||
}
|
||||
|
||||
post("/sidebar-collapse") {
|
||||
@@ -207,8 +213,10 @@ trait IndexControllerBase extends ControllerBase {
|
||||
}
|
||||
.map { t =>
|
||||
Map(
|
||||
"label" -> s"${avatar(t.userName, 16)}<b>@${StringUtil.escapeHtml(t.userName)}</b> ${StringUtil
|
||||
.escapeHtml(t.fullName)}",
|
||||
"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
|
||||
)
|
||||
}
|
||||
@@ -279,11 +287,28 @@ trait IndexControllerBase extends ControllerBase {
|
||||
get("/search") {
|
||||
val query = params.getOrElse("query", "").trim.toLowerCase
|
||||
val visibleRepositories =
|
||||
getVisibleRepositories(context.loginAccount, repositoryUserName = None, withoutPhysicalInfo = true)
|
||||
val repositories = visibleRepositories.filter { repository =>
|
||||
getVisibleRepositories(
|
||||
context.loginAccount,
|
||||
None,
|
||||
withoutPhysicalInfo = true,
|
||||
limit = context.settings.limitVisibleRepositories
|
||||
)
|
||||
|
||||
val repositories = {
|
||||
context.settings.limitVisibleRepositories match {
|
||||
case true =>
|
||||
getVisibleRepositories(
|
||||
context.loginAccount,
|
||||
None,
|
||||
withoutPhysicalInfo = true,
|
||||
limit = false
|
||||
)
|
||||
case false => visibleRepositories
|
||||
}
|
||||
}.filter { repository =>
|
||||
repository.name.toLowerCase.indexOf(query) >= 0 || repository.owner.toLowerCase.indexOf(query) >= 0
|
||||
}
|
||||
|
||||
gitbucket.core.search.html.repositories(query, repositories, visibleRepositories)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ class IssuesController
|
||||
with WebHookPullRequestReviewCommentService
|
||||
with CommitsService
|
||||
with PrioritiesService
|
||||
with RequestCache
|
||||
|
||||
trait IssuesControllerBase extends ControllerBase {
|
||||
self: IssuesService
|
||||
@@ -111,6 +112,7 @@ trait IssuesControllerBase extends ControllerBase {
|
||||
getLabels(owner, name),
|
||||
isIssueEditable(repository),
|
||||
isIssueManageable(repository),
|
||||
isIssueCommentManageable(repository),
|
||||
repository
|
||||
)
|
||||
}
|
||||
@@ -237,8 +239,8 @@ trait IssuesControllerBase extends ControllerBase {
|
||||
defining(repository.owner, repository.name) {
|
||||
case (owner, name) =>
|
||||
getComment(owner, name, params("id")).map { comment =>
|
||||
if (isEditableContent(owner, name, comment.commentedUserName)) {
|
||||
Ok(deleteComment(comment.issueId, comment.commentId))
|
||||
if (isDeletableComment(owner, name, comment.commentedUserName)) {
|
||||
Ok(deleteComment(repository.owner, repository.name, comment.issueId, comment.commentId))
|
||||
} else Unauthorized()
|
||||
} getOrElse NotFound()
|
||||
}
|
||||
@@ -368,6 +370,9 @@ trait IssuesControllerBase extends ControllerBase {
|
||||
}
|
||||
case _ => BadRequest()
|
||||
}
|
||||
if (params("uri").nonEmpty) {
|
||||
redirect(params("uri"))
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -376,6 +381,9 @@ trait IssuesControllerBase extends ControllerBase {
|
||||
executeBatch(repository) { issueId =>
|
||||
getIssueLabel(repository.owner, repository.name, issueId, labelId) getOrElse {
|
||||
registerIssueLabel(repository.owner, repository.name, issueId, labelId, true)
|
||||
if (params("uri").nonEmpty) {
|
||||
redirect(params("uri"))
|
||||
}
|
||||
}
|
||||
}
|
||||
} getOrElse NotFound()
|
||||
@@ -386,6 +394,9 @@ trait IssuesControllerBase extends ControllerBase {
|
||||
executeBatch(repository) {
|
||||
updateAssignedUserName(repository.owner, repository.name, _, value, true)
|
||||
}
|
||||
if (params("uri").nonEmpty) {
|
||||
redirect(params("uri"))
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -416,6 +427,29 @@ trait IssuesControllerBase extends ControllerBase {
|
||||
}) getOrElse NotFound()
|
||||
})
|
||||
|
||||
/**
|
||||
* JSON API for issue and PR completion.
|
||||
*/
|
||||
ajaxGet("/:owner/:repository/_issue/proposals")(writableUsersOnly { repository =>
|
||||
contentType = formats("json")
|
||||
org.json4s.jackson.Serialization.write(
|
||||
Map(
|
||||
"options" -> (
|
||||
getOpenIssues(repository.owner, repository.name)
|
||||
.map { t =>
|
||||
Map(
|
||||
"label" -> s"""${if (t.isPullRequest) "<i class='octicon octicon-git-pull-request'></i>"
|
||||
else "<i class='octicon octicon-issue-opened'></i>"}<b> #${StringUtil
|
||||
.escapeHtml(t.issueId.toString)} ${StringUtil
|
||||
.escapeHtml(StringUtil.cutTail(t.title, 50, "..."))}</b>""",
|
||||
"value" -> t.issueId.toString
|
||||
)
|
||||
}
|
||||
)
|
||||
)
|
||||
)
|
||||
})
|
||||
|
||||
val assignedUserName = (key: String) => params.get(key) filter (_.trim != "")
|
||||
val milestoneId: String => Option[Int] = (key: String) => params.get(key).flatMap(_.toIntOpt)
|
||||
val priorityId: String => Option[Int] = (key: String) => params.get(key).flatMap(_.toIntOpt)
|
||||
@@ -425,6 +459,7 @@ trait IssuesControllerBase extends ControllerBase {
|
||||
params("from") match {
|
||||
case "issues" => redirect(s"/${repository.owner}/${repository.name}/issues")
|
||||
case "pulls" => redirect(s"/${repository.owner}/${repository.name}/pulls")
|
||||
case _ =>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -432,20 +467,22 @@ trait IssuesControllerBase extends ControllerBase {
|
||||
defining(repository.owner, repository.name) {
|
||||
case (owner, repoName) =>
|
||||
val page = IssueSearchCondition.page(request)
|
||||
|
||||
// retrieve search condition
|
||||
val condition = IssueSearchCondition(request)
|
||||
// search issues
|
||||
val issues =
|
||||
searchIssue(condition, IssueSearchOption.Issues, (page - 1) * IssueLimit, IssueLimit, owner -> repoName)
|
||||
|
||||
html.list(
|
||||
"issues",
|
||||
searchIssue(condition, false, (page - 1) * IssueLimit, IssueLimit, owner -> repoName),
|
||||
issues.map(issue => (issue, None)),
|
||||
page,
|
||||
getAssignableUserNames(owner, repoName),
|
||||
getMilestones(owner, repoName),
|
||||
getPriorities(owner, repoName),
|
||||
getLabels(owner, repoName),
|
||||
countIssue(condition.copy(state = "open"), false, owner -> repoName),
|
||||
countIssue(condition.copy(state = "closed"), false, owner -> repoName),
|
||||
countIssue(condition.copy(state = "open"), IssueSearchOption.Issues, owner -> repoName),
|
||||
countIssue(condition.copy(state = "closed"), IssueSearchOption.Issues, owner -> repoName),
|
||||
condition,
|
||||
repository,
|
||||
isIssueEditable(repository),
|
||||
@@ -462,4 +499,13 @@ trait IssuesControllerBase extends ControllerBase {
|
||||
): Boolean = {
|
||||
hasDeveloperRole(owner, repository, context.loginAccount) || author == context.loginAccount.get.userName
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests whether an issue comment is deletable by a logged-in user.
|
||||
*/
|
||||
private def isDeletableComment(owner: String, repository: String, author: String)(
|
||||
implicit context: Context
|
||||
): Boolean = {
|
||||
hasOwnerRole(owner, repository, context.loginAccount) || author == context.loginAccount.get.userName
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,18 @@
|
||||
package gitbucket.core.controller
|
||||
|
||||
import gitbucket.core.issues.milestones.html
|
||||
import gitbucket.core.service.{AccountService, MilestonesService, RepositoryService}
|
||||
import gitbucket.core.service.IssuesService.{IssueLimit, IssueSearchCondition}
|
||||
import gitbucket.core.service.{
|
||||
AccountService,
|
||||
CommitStatusService,
|
||||
IssueSearchOption,
|
||||
MilestonesService,
|
||||
RepositoryService
|
||||
}
|
||||
import gitbucket.core.util.Implicits._
|
||||
import gitbucket.core.util.{ReferrerAuthenticator, WritableUsersAuthenticator}
|
||||
import gitbucket.core.util.SyntaxSugars._
|
||||
import gitbucket.core.view.helpers.{getAssignableUserNames, getLabels, getPriorities, searchIssue}
|
||||
import org.scalatra.forms._
|
||||
import org.scalatra.i18n.Messages
|
||||
|
||||
@@ -13,11 +21,16 @@ class MilestonesController
|
||||
with MilestonesService
|
||||
with RepositoryService
|
||||
with AccountService
|
||||
with CommitStatusService
|
||||
with ReferrerAuthenticator
|
||||
with WritableUsersAuthenticator
|
||||
|
||||
trait MilestonesControllerBase extends ControllerBase {
|
||||
self: MilestonesService with RepositoryService with ReferrerAuthenticator with WritableUsersAuthenticator =>
|
||||
self: MilestonesService
|
||||
with RepositoryService
|
||||
with CommitStatusService
|
||||
with ReferrerAuthenticator
|
||||
with WritableUsersAuthenticator =>
|
||||
|
||||
case class MilestoneForm(title: String, description: Option[String], dueDate: Option[java.util.Date])
|
||||
|
||||
@@ -36,6 +49,41 @@ trait MilestonesControllerBase extends ControllerBase {
|
||||
)
|
||||
})
|
||||
|
||||
get("/:owner/:repository/milestone/:id")(referrersOnly { repository =>
|
||||
val milestone = getMilestone(repository.owner, repository.name, params("id").toInt)
|
||||
val page = IssueSearchCondition.page(request)
|
||||
val condition = IssueSearchCondition(
|
||||
request,
|
||||
milestone.get.title
|
||||
)
|
||||
val issues = searchIssue(
|
||||
condition,
|
||||
IssueSearchOption.Both,
|
||||
(page - 1) * IssueLimit,
|
||||
IssueLimit,
|
||||
repository.owner -> repository.name
|
||||
)
|
||||
val status = issues.map { issue =>
|
||||
issue.commitId.flatMap { commitId =>
|
||||
getCommitStatusWithSummary(issue.issue.userName, issue.issue.repositoryName, commitId)
|
||||
}
|
||||
}
|
||||
|
||||
html.milestone(
|
||||
condition.state,
|
||||
issues.zip(status),
|
||||
page,
|
||||
getAssignableUserNames(repository.owner, repository.name),
|
||||
getPriorities(repository.owner, repository.name),
|
||||
getLabels(repository.owner, repository.name),
|
||||
condition,
|
||||
getMilestonesWithIssueCount(repository.owner, repository.name)
|
||||
.filter(p => p._1.milestoneId == milestone.get.milestoneId),
|
||||
repository,
|
||||
hasDeveloperRole(repository.owner, repository.name, context.loginAccount)
|
||||
)
|
||||
})
|
||||
|
||||
get("/:owner/:repository/issues/milestones/new")(writableUsersOnly {
|
||||
html.edit(None, _)
|
||||
})
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package gitbucket.core.controller
|
||||
|
||||
import gitbucket.core.model.activity.DeleteBranchInfo
|
||||
import gitbucket.core.pulls.html
|
||||
import gitbucket.core.service.CommitStatusService
|
||||
import gitbucket.core.service.MergeService
|
||||
@@ -36,6 +37,7 @@ class PullRequestsController
|
||||
with MergeService
|
||||
with ProtectedBranchService
|
||||
with PrioritiesService
|
||||
with RequestCache
|
||||
|
||||
trait PullRequestsControllerBase extends ControllerBase {
|
||||
self: RepositoryService
|
||||
@@ -217,7 +219,7 @@ trait PullRequestsControllerBase extends ControllerBase {
|
||||
val branchProtection = getProtectedBranchInfo(owner, name, pullreq.branch)
|
||||
val mergeStatus = PullRequestService.MergeStatus(
|
||||
conflictMessage = conflictMessage,
|
||||
commitStatues = getCommitStatues(owner, name, pullreq.commitIdTo),
|
||||
commitStatuses = getCommitStatuses(owner, name, pullreq.commitIdTo),
|
||||
branchProtection = branchProtection,
|
||||
branchIsOutOfDate = JGitUtil.getShaByRef(owner, name, pullreq.branch) != Some(pullreq.commitIdFrom),
|
||||
needStatusCheck = context.loginAccount
|
||||
@@ -271,7 +273,8 @@ trait PullRequestsControllerBase extends ControllerBase {
|
||||
val userName = context.loginAccount.get.userName
|
||||
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
|
||||
git.branchDelete().setForce(true).setBranchNames(pullreq.requestBranch).call()
|
||||
recordDeleteBranchActivity(repository.owner, repository.name, userName, pullreq.requestBranch)
|
||||
val deleteBranchInfo = DeleteBranchInfo(repository.owner, repository.name, userName, pullreq.requestBranch)
|
||||
recordActivity(deleteBranchInfo)
|
||||
}
|
||||
createComment(
|
||||
baseRepository.owner,
|
||||
@@ -634,20 +637,33 @@ trait PullRequestsControllerBase extends ControllerBase {
|
||||
defining(repository.owner, repository.name) {
|
||||
case (owner, repoName) =>
|
||||
val page = IssueSearchCondition.page(request)
|
||||
|
||||
// retrieve search condition
|
||||
val condition = IssueSearchCondition(request)
|
||||
// search issues
|
||||
val issues = searchIssue(
|
||||
condition,
|
||||
IssueSearchOption.PullRequests,
|
||||
(page - 1) * PullRequestLimit,
|
||||
PullRequestLimit,
|
||||
owner -> repoName
|
||||
)
|
||||
// commit status
|
||||
val status = issues.map { issue =>
|
||||
issue.commitId.flatMap { commitId =>
|
||||
getCommitStatusWithSummary(owner, repoName, commitId)
|
||||
}
|
||||
}
|
||||
|
||||
gitbucket.core.issues.html.list(
|
||||
"pulls",
|
||||
searchIssue(condition, true, (page - 1) * PullRequestLimit, PullRequestLimit, owner -> repoName),
|
||||
issues.zip(status),
|
||||
page,
|
||||
getAssignableUserNames(owner, repoName),
|
||||
getMilestones(owner, repoName),
|
||||
getPriorities(owner, repoName),
|
||||
getLabels(owner, repoName),
|
||||
countIssue(condition.copy(state = "open"), true, owner -> repoName),
|
||||
countIssue(condition.copy(state = "closed"), true, owner -> repoName),
|
||||
countIssue(condition.copy(state = "open"), IssueSearchOption.PullRequests, owner -> repoName),
|
||||
countIssue(condition.copy(state = "closed"), IssueSearchOption.PullRequests, owner -> repoName),
|
||||
condition,
|
||||
repository,
|
||||
isEditable(repository),
|
||||
|
||||
@@ -2,7 +2,15 @@ package gitbucket.core.controller
|
||||
|
||||
import java.io.File
|
||||
|
||||
import gitbucket.core.service.{AccountService, ActivityService, ReleaseService, RepositoryService}
|
||||
import gitbucket.core.model.activity.ReleaseInfo
|
||||
import gitbucket.core.service.{
|
||||
AccountService,
|
||||
ActivityService,
|
||||
PaginationHelper,
|
||||
ReleaseService,
|
||||
RepositoryService,
|
||||
RequestCache
|
||||
}
|
||||
import gitbucket.core.util._
|
||||
import gitbucket.core.util.Directory._
|
||||
import gitbucket.core.util.Implicits._
|
||||
@@ -10,6 +18,7 @@ import org.scalatra.forms._
|
||||
import gitbucket.core.releases.html
|
||||
import org.apache.commons.io.FileUtils
|
||||
import org.eclipse.jgit.api.Git
|
||||
|
||||
import scala.util.Using
|
||||
|
||||
class ReleaseController
|
||||
@@ -21,6 +30,7 @@ class ReleaseController
|
||||
with ReadableUsersAuthenticator
|
||||
with ReferrerAuthenticator
|
||||
with WritableUsersAuthenticator
|
||||
with RequestCache
|
||||
|
||||
trait ReleaseControllerBase extends ControllerBase {
|
||||
self: RepositoryService
|
||||
@@ -42,17 +52,14 @@ trait ReleaseControllerBase extends ControllerBase {
|
||||
)(ReleaseForm.apply)
|
||||
|
||||
get("/:owner/:repository/releases")(referrersOnly { repository =>
|
||||
val releases = getReleases(repository.owner, repository.name)
|
||||
val assets = getReleaseAssetsMap(repository.owner, repository.name)
|
||||
val page = PaginationHelper.page(params.get("page"))
|
||||
|
||||
html.list(
|
||||
repository,
|
||||
repository.tags.reverse.map { tag =>
|
||||
(tag, releases.find(_.tag == tag.name).map { release =>
|
||||
(release, assets(release))
|
||||
})
|
||||
},
|
||||
hasDeveloperRole(repository.owner, repository.name, context.loginAccount)
|
||||
fetchReleases(repository, page),
|
||||
hasDeveloperRole(repository.owner, repository.name, context.loginAccount),
|
||||
page,
|
||||
repository.tags.size
|
||||
)
|
||||
})
|
||||
|
||||
@@ -121,7 +128,8 @@ trait ReleaseControllerBase extends ControllerBase {
|
||||
createReleaseAsset(repository.owner, repository.name, tagName, fileId, fileName, size, loginAccount)
|
||||
}
|
||||
|
||||
recordReleaseActivity(repository.owner, repository.name, loginAccount.userName, form.name, tagName)
|
||||
val releaseInfo = ReleaseInfo(repository.owner, repository.name, loginAccount.userName, form.name, tagName)
|
||||
recordActivity(releaseInfo)
|
||||
|
||||
redirect(s"/${repository.owner}/${repository.name}/releases/${tagName}")
|
||||
})
|
||||
@@ -215,4 +223,21 @@ trait ReleaseControllerBase extends ControllerBase {
|
||||
redirect(s"/${repository.owner}/${repository.name}/releases")
|
||||
})
|
||||
|
||||
private def fetchReleases(repository: RepositoryService.RepositoryInfo, page: Int) = {
|
||||
|
||||
import gitbucket.core.service.ReleaseService._
|
||||
|
||||
val (offset, limit) = ((page - 1) * ReleaseLimit, ReleaseLimit)
|
||||
val tagsToDisplay = repository.tags.reverse.slice(offset, offset + limit)
|
||||
|
||||
val releases = getReleases(repository.owner, repository.name, tagsToDisplay)
|
||||
val assets = getReleaseAssetsMap(repository.owner, repository.name, releases)
|
||||
|
||||
val tagsWithReleases = tagsToDisplay.map { tag =>
|
||||
(tag, releases.find(_.tag == tag.name).map { release =>
|
||||
(release, assets(release))
|
||||
})
|
||||
}
|
||||
tagsWithReleases
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import gitbucket.core.util.SyntaxSugars._
|
||||
import gitbucket.core.util.Implicits._
|
||||
import gitbucket.core.util.Directory._
|
||||
import gitbucket.core.model.WebHookContentType
|
||||
import gitbucket.core.model.activity.RenameRepositoryInfo
|
||||
import org.scalatra.forms._
|
||||
import org.scalatra.i18n.Messages
|
||||
import org.eclipse.jgit.api.Git
|
||||
@@ -20,6 +21,7 @@ import org.eclipse.jgit.lib.Constants
|
||||
import org.eclipse.jgit.lib.ObjectId
|
||||
|
||||
import scala.util.Using
|
||||
import org.scalatra.Forbidden
|
||||
|
||||
class RepositorySettingsController
|
||||
extends RepositorySettingsControllerBase
|
||||
@@ -29,8 +31,10 @@ class RepositorySettingsController
|
||||
with ProtectedBranchService
|
||||
with CommitStatusService
|
||||
with DeployKeyService
|
||||
with ActivityService
|
||||
with OwnerAuthenticator
|
||||
with UsersAuthenticator
|
||||
with RequestCache
|
||||
|
||||
trait RepositorySettingsControllerBase extends ControllerBase {
|
||||
self: RepositoryService
|
||||
@@ -39,12 +43,12 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
||||
with ProtectedBranchService
|
||||
with CommitStatusService
|
||||
with DeployKeyService
|
||||
with ActivityService
|
||||
with OwnerAuthenticator
|
||||
with UsersAuthenticator =>
|
||||
|
||||
// for repository options
|
||||
case class OptionsForm(
|
||||
repositoryName: String,
|
||||
description: Option[String],
|
||||
isPrivate: Boolean,
|
||||
issuesOption: String,
|
||||
@@ -57,9 +61,6 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
||||
)
|
||||
|
||||
val optionsForm = mapping(
|
||||
"repositoryName" -> trim(
|
||||
label("Repository Name", text(required, maxlength(100), repository, renameRepositoryName))
|
||||
),
|
||||
"description" -> trim(label("Description", optional(text()))),
|
||||
"isPrivate" -> trim(label("Repository Type", boolean())),
|
||||
"issuesOption" -> trim(label("Issues Option", text(required, featureOption))),
|
||||
@@ -100,9 +101,16 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
||||
"events" -> webhookEvents,
|
||||
"ctype" -> label("ctype", text()),
|
||||
"token" -> optional(trim(label("token", text(maxlength(100)))))
|
||||
)(
|
||||
(url, events, ctype, token) => WebHookForm(url, events, WebHookContentType.valueOf(ctype), token)
|
||||
)((url, events, ctype, token) => WebHookForm(url, events, WebHookContentType.valueOf(ctype), token))
|
||||
|
||||
// for rename repository
|
||||
case class RenameRepositoryForm(repositoryName: String)
|
||||
|
||||
val renameForm = mapping(
|
||||
"repositoryName" -> trim(
|
||||
label("New repository name", text(required, maxlength(100), repository, renameRepositoryName))
|
||||
)
|
||||
)(RenameRepositoryForm.apply)
|
||||
|
||||
// for transfer ownership
|
||||
case class TransferOwnerShipForm(newOwner: String)
|
||||
@@ -144,13 +152,8 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
||||
form.mergeOptions,
|
||||
form.defaultMergeOption
|
||||
)
|
||||
// Change repository name
|
||||
if (repository.name != form.repositoryName) {
|
||||
// Update database
|
||||
renameRepository(repository.owner, repository.name, repository.owner, form.repositoryName)
|
||||
}
|
||||
flash.update("info", "Repository settings has been updated.")
|
||||
redirect(s"/${repository.owner}/${form.repositoryName}/settings/options")
|
||||
redirect(s"/${repository.owner}/${repository.name}/settings/options")
|
||||
})
|
||||
|
||||
/** branch settings */
|
||||
@@ -175,14 +178,15 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
||||
})
|
||||
|
||||
/** Branch protection for branch */
|
||||
get("/:owner/:repository/settings/branches/:branch")(ownerOnly { repository =>
|
||||
get("/:owner/:repository/settings/branches/*")(ownerOnly { repository =>
|
||||
import gitbucket.core.api._
|
||||
val branch = params("branch")
|
||||
val branch = params("splat")
|
||||
|
||||
if (!repository.branchList.contains(branch)) {
|
||||
redirect(s"/${repository.owner}/${repository.name}/settings/branches")
|
||||
} else {
|
||||
val protection = ApiBranchProtection(getProtectedBranchInfo(repository.owner, repository.name, branch))
|
||||
val lastWeeks = getRecentStatuesContexts(
|
||||
val lastWeeks = getRecentStatusContexts(
|
||||
repository.owner,
|
||||
repository.name,
|
||||
Date.from(LocalDateTime.now.minusWeeks(1).toInstant(ZoneOffset.UTC))
|
||||
@@ -224,7 +228,13 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
||||
* Display the web hook edit page.
|
||||
*/
|
||||
get("/:owner/:repository/settings/hooks/new")(ownerOnly { repository =>
|
||||
val webhook = RepositoryWebHook(repository.owner, repository.name, "", WebHookContentType.FORM, None)
|
||||
val webhook = RepositoryWebHook(
|
||||
userName = repository.owner,
|
||||
repositoryName = repository.name,
|
||||
url = "",
|
||||
ctype = WebHookContentType.FORM,
|
||||
token = None
|
||||
)
|
||||
html.edithook(webhook, Set(WebHook.Push), repository, true)
|
||||
})
|
||||
|
||||
@@ -250,9 +260,10 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
||||
* Send the test request to registered web hook URLs.
|
||||
*/
|
||||
ajaxPost("/:owner/:repository/settings/hooks/test")(ownerOnly { repository =>
|
||||
def _headers(h: Array[org.apache.http.Header]): Array[Array[String]] = h.map { h =>
|
||||
Array(h.getName, h.getValue)
|
||||
}
|
||||
def _headers(h: Array[org.apache.http.Header]): Array[Array[String]] =
|
||||
h.map { h =>
|
||||
Array(h.getName, h.getValue)
|
||||
}
|
||||
|
||||
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) {
|
||||
git =>
|
||||
@@ -266,7 +277,13 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
||||
val url = params("url")
|
||||
val token = Some(params("token"))
|
||||
val ctype = WebHookContentType.valueOf(params("ctype"))
|
||||
val dummyWebHookInfo = RepositoryWebHook(repository.owner, repository.name, url, ctype, token)
|
||||
val dummyWebHookInfo = RepositoryWebHook(
|
||||
userName = repository.owner,
|
||||
repositoryName = repository.name,
|
||||
url = url,
|
||||
ctype = ctype,
|
||||
token = token
|
||||
)
|
||||
val dummyPayload = {
|
||||
val ownerAccount = getAccountByUserName(repository.owner).get
|
||||
val commits =
|
||||
@@ -364,24 +381,58 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
||||
html.danger(_, flash.get("info"))
|
||||
})
|
||||
|
||||
/**
|
||||
* Rename repository.
|
||||
*/
|
||||
post("/:owner/:repository/settings/rename", renameForm)(ownerOnly { (form, repository) =>
|
||||
if (context.settings.repositoryOperation.rename || context.loginAccount.get.isAdmin) {
|
||||
if (repository.name != form.repositoryName) {
|
||||
// Update database and move git repository
|
||||
renameRepository(repository.owner, repository.name, repository.owner, form.repositoryName)
|
||||
// Record activity log
|
||||
val renameInfo = RenameRepositoryInfo(
|
||||
repository.owner,
|
||||
form.repositoryName,
|
||||
context.loginAccount.get.userName,
|
||||
repository.name
|
||||
)
|
||||
recordActivity(renameInfo)
|
||||
}
|
||||
redirect(s"/${repository.owner}/${form.repositoryName}")
|
||||
} else Forbidden()
|
||||
})
|
||||
|
||||
/**
|
||||
* Transfer repository ownership.
|
||||
*/
|
||||
post("/:owner/:repository/settings/transfer", transferForm)(ownerOnly { (form, repository) =>
|
||||
// Change repository owner
|
||||
if (repository.owner != form.newOwner) {
|
||||
renameRepository(repository.owner, repository.name, form.newOwner, repository.name)
|
||||
}
|
||||
redirect(s"/${form.newOwner}/${repository.name}")
|
||||
if (context.settings.repositoryOperation.transfer || context.loginAccount.get.isAdmin) {
|
||||
// Change repository owner
|
||||
if (repository.owner != form.newOwner) {
|
||||
// Update database and move git repository
|
||||
renameRepository(repository.owner, repository.name, form.newOwner, repository.name)
|
||||
// Record activity log
|
||||
val renameInfo = RenameRepositoryInfo(
|
||||
form.newOwner,
|
||||
repository.name,
|
||||
context.loginAccount.get.userName,
|
||||
repository.owner
|
||||
)
|
||||
recordActivity(renameInfo)
|
||||
}
|
||||
redirect(s"/${form.newOwner}/${repository.name}")
|
||||
} else Forbidden()
|
||||
})
|
||||
|
||||
/**
|
||||
* Delete the repository.
|
||||
*/
|
||||
post("/:owner/:repository/settings/delete")(ownerOnly { repository =>
|
||||
// Delete the repository and related files
|
||||
deleteRepository(repository.repository)
|
||||
redirect(s"/${repository.owner}")
|
||||
if (context.settings.repositoryOperation.delete || context.loginAccount.get.isAdmin) {
|
||||
// Delete the repository and related files
|
||||
deleteRepository(repository.repository)
|
||||
redirect(s"/${repository.owner}")
|
||||
} else Forbidden()
|
||||
})
|
||||
|
||||
/**
|
||||
@@ -418,32 +469,34 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
||||
/**
|
||||
* Provides duplication check for web hook url.
|
||||
*/
|
||||
private def webHook(needExists: Boolean): Constraint = new Constraint() {
|
||||
override def validate(name: String, value: String, messages: Messages): Option[String] =
|
||||
if (getWebHook(params("owner"), params("repository"), value).isDefined != needExists) {
|
||||
Some(if (needExists) {
|
||||
"URL had not been registered yet."
|
||||
private def webHook(needExists: Boolean): Constraint =
|
||||
new Constraint() {
|
||||
override def validate(name: String, value: String, messages: Messages): Option[String] =
|
||||
if (getWebHook(params("owner"), params("repository"), value).isDefined != needExists) {
|
||||
Some(if (needExists) {
|
||||
"URL had not been registered yet."
|
||||
} else {
|
||||
"URL had been registered already."
|
||||
})
|
||||
} else {
|
||||
"URL had been registered already."
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
private def webhookEvents = new ValueType[Set[WebHook.Event]] {
|
||||
def convert(name: String, params: Map[String, Seq[String]], messages: Messages): Set[WebHook.Event] = {
|
||||
WebHook.Event.values.flatMap { t =>
|
||||
params.get(name + "." + t.name).map(_ => t)
|
||||
}.toSet
|
||||
None
|
||||
}
|
||||
}
|
||||
def validate(name: String, params: Map[String, Seq[String]], messages: Messages): Seq[(String, String)] =
|
||||
if (convert(name, params, messages).isEmpty) {
|
||||
Seq(name -> messages("error.required").format(name))
|
||||
} else {
|
||||
Nil
|
||||
|
||||
private def webhookEvents =
|
||||
new ValueType[Set[WebHook.Event]] {
|
||||
def convert(name: String, params: Map[String, Seq[String]], messages: Messages): Set[WebHook.Event] = {
|
||||
WebHook.Event.values.flatMap { t =>
|
||||
params.get(name + "." + t.name).map(_ => t)
|
||||
}.toSet
|
||||
}
|
||||
}
|
||||
def validate(name: String, params: Map[String, Seq[String]], messages: Messages): Seq[(String, String)] =
|
||||
if (convert(name, params, messages).isEmpty) {
|
||||
Seq(name -> messages("error.required").format(name))
|
||||
} else {
|
||||
Nil
|
||||
}
|
||||
}
|
||||
|
||||
// /**
|
||||
// * Provides Constraint to validate the collaborator name.
|
||||
@@ -463,70 +516,77 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
||||
/**
|
||||
* Duplicate check for the rename repository name.
|
||||
*/
|
||||
private def renameRepositoryName: Constraint = new Constraint() {
|
||||
override def validate(
|
||||
name: String,
|
||||
value: String,
|
||||
params: Map[String, Seq[String]],
|
||||
messages: Messages
|
||||
): Option[String] = {
|
||||
for {
|
||||
repoName <- params.optionValue("repository") if repoName != value
|
||||
userName <- params.optionValue("owner")
|
||||
_ <- getRepositoryNamesOfUser(userName).find(_ == value)
|
||||
} yield {
|
||||
"Repository already exists."
|
||||
private def renameRepositoryName: Constraint =
|
||||
new Constraint() {
|
||||
override def validate(
|
||||
name: String,
|
||||
value: String,
|
||||
params: Map[String, Seq[String]],
|
||||
messages: Messages
|
||||
): Option[String] = {
|
||||
for {
|
||||
repoName <- params.optionValue("repository") if repoName != value
|
||||
userName <- params.optionValue("owner")
|
||||
_ <- getRepositoryNamesOfUser(userName).find(_ == value)
|
||||
} yield {
|
||||
"Repository already exists."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private def featureOption: Constraint = new Constraint() {
|
||||
override def validate(
|
||||
name: String,
|
||||
value: String,
|
||||
params: Map[String, Seq[String]],
|
||||
messages: Messages
|
||||
): Option[String] =
|
||||
if (Seq("DISABLE", "PRIVATE", "PUBLIC", "ALL").contains(value)) None else Some("Option is invalid.")
|
||||
}
|
||||
private def featureOption: Constraint =
|
||||
new Constraint() {
|
||||
override def validate(
|
||||
name: String,
|
||||
value: String,
|
||||
params: Map[String, Seq[String]],
|
||||
messages: Messages
|
||||
): Option[String] =
|
||||
if (Seq("DISABLE", "PRIVATE", "PUBLIC", "ALL").contains(value)) None else Some("Option is invalid.")
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides Constraint to validate the repository transfer user.
|
||||
*/
|
||||
private def transferUser: Constraint = new Constraint() {
|
||||
override def validate(name: String, value: String, messages: Messages): Option[String] =
|
||||
getAccountByUserName(value) match {
|
||||
case None => Some("User does not exist.")
|
||||
case Some(x) =>
|
||||
if (x.userName == params("owner")) {
|
||||
Some("This is current repository owner.")
|
||||
} else {
|
||||
params.get("repository").flatMap { repositoryName =>
|
||||
getRepositoryNamesOfUser(x.userName).find(_ == repositoryName).map { _ =>
|
||||
"User already has same repository."
|
||||
private def transferUser: Constraint =
|
||||
new Constraint() {
|
||||
override def validate(name: String, value: String, messages: Messages): Option[String] =
|
||||
getAccountByUserName(value) match {
|
||||
case None => Some("User does not exist.")
|
||||
case Some(x) =>
|
||||
if (x.userName == params("owner")) {
|
||||
Some("This is current repository owner.")
|
||||
} else {
|
||||
params.get("repository").flatMap { repositoryName =>
|
||||
getRepositoryNamesOfUser(x.userName).find(_ == repositoryName).map { _ =>
|
||||
"User already has same repository."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private def mergeOptions = new ValueType[Seq[String]] {
|
||||
override def convert(name: String, params: Map[String, Seq[String]], messages: Messages): Seq[String] = {
|
||||
params.getOrElse("mergeOptions", Nil)
|
||||
}
|
||||
override def validate(name: String, params: Map[String, Seq[String]], messages: Messages): Seq[(String, String)] = {
|
||||
val mergeOptions = params.getOrElse("mergeOptions", Nil)
|
||||
if (mergeOptions.isEmpty) {
|
||||
Seq("mergeOptions" -> "At least one option must be enabled.")
|
||||
} else if (!mergeOptions.forall(x => Seq("merge-commit", "squash", "rebase").contains(x))) {
|
||||
Seq("mergeOptions" -> "mergeOptions are invalid.")
|
||||
} else {
|
||||
Nil
|
||||
private def mergeOptions =
|
||||
new ValueType[Seq[String]] {
|
||||
override def convert(name: String, params: Map[String, Seq[String]], messages: Messages): Seq[String] = {
|
||||
params.getOrElse("mergeOptions", Nil)
|
||||
}
|
||||
override def validate(
|
||||
name: String,
|
||||
params: Map[String, Seq[String]],
|
||||
messages: Messages
|
||||
): Seq[(String, String)] = {
|
||||
val mergeOptions = params.getOrElse("mergeOptions", Nil)
|
||||
if (mergeOptions.isEmpty) {
|
||||
Seq("mergeOptions" -> "At least one option must be enabled.")
|
||||
} else if (!mergeOptions.forall(x => Seq("merge-commit", "squash", "rebase").contains(x))) {
|
||||
Seq("mergeOptions" -> "mergeOptions are invalid.")
|
||||
} else {
|
||||
Nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -4,9 +4,9 @@ import java.io.{File, FileInputStream, FileOutputStream}
|
||||
|
||||
import scala.util.Using
|
||||
import javax.servlet.http.{HttpServletRequest, HttpServletResponse}
|
||||
import gitbucket.core.plugin.PluginRegistry
|
||||
import gitbucket.core.repo.html
|
||||
import gitbucket.core.helper
|
||||
import gitbucket.core.model.activity.DeleteBranchInfo
|
||||
import gitbucket.core.service._
|
||||
import gitbucket.core.service.RepositoryCommitFileService.CommitFile
|
||||
import gitbucket.core.util._
|
||||
@@ -14,7 +14,8 @@ import gitbucket.core.util.StringUtil._
|
||||
import gitbucket.core.util.SyntaxSugars._
|
||||
import gitbucket.core.util.Implicits._
|
||||
import gitbucket.core.util.Directory._
|
||||
import gitbucket.core.model.{Account, CommitState, CommitStatus}
|
||||
import gitbucket.core.model.Account
|
||||
import gitbucket.core.service.RepositoryService.RepositoryInfo
|
||||
import gitbucket.core.util.JGitUtil.CommitInfo
|
||||
import gitbucket.core.view
|
||||
import gitbucket.core.view.helpers
|
||||
@@ -60,6 +61,7 @@ class RepositoryViewerController
|
||||
with WebHookPullRequestService
|
||||
with WebHookPullRequestReviewCommentService
|
||||
with ProtectedBranchService
|
||||
with RequestCache
|
||||
|
||||
/**
|
||||
* The repository viewer.
|
||||
@@ -88,7 +90,9 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
branch: String,
|
||||
path: String,
|
||||
uploadFiles: String,
|
||||
message: Option[String]
|
||||
message: Option[String],
|
||||
commit: String,
|
||||
newBranch: Boolean
|
||||
)
|
||||
|
||||
case class EditorForm(
|
||||
@@ -100,7 +104,8 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
lineSeparator: String,
|
||||
newFileName: String,
|
||||
oldFileName: Option[String],
|
||||
commit: String
|
||||
commit: String,
|
||||
newBranch: Boolean
|
||||
)
|
||||
|
||||
case class DeleteForm(
|
||||
@@ -108,7 +113,8 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
path: String,
|
||||
message: Option[String],
|
||||
fileName: String,
|
||||
commit: String
|
||||
commit: String,
|
||||
newBranch: Boolean
|
||||
)
|
||||
|
||||
case class CommentForm(
|
||||
@@ -131,6 +137,8 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
"path" -> trim(label("Path", text())),
|
||||
"uploadFiles" -> trim(label("Upload files", text(required))),
|
||||
"message" -> trim(label("Message", optional(text()))),
|
||||
"commit" -> trim(label("Commit", text(required, conflict))),
|
||||
"newBranch" -> trim(label("New Branch", boolean()))
|
||||
)(UploadForm.apply)
|
||||
|
||||
val editorForm = mapping(
|
||||
@@ -142,7 +150,8 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
"lineSeparator" -> trim(label("Line Separator", text(required))),
|
||||
"newFileName" -> trim(label("Filename", text(required))),
|
||||
"oldFileName" -> trim(label("Old filename", optional(text()))),
|
||||
"commit" -> trim(label("Commit", text(required, conflict)))
|
||||
"commit" -> trim(label("Commit", text(required, conflict))),
|
||||
"newBranch" -> trim(label("New Branch", boolean()))
|
||||
)(EditorForm.apply)
|
||||
|
||||
val deleteForm = mapping(
|
||||
@@ -150,7 +159,8 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
"path" -> trim(label("Path", text())),
|
||||
"message" -> trim(label("Message", optional(text()))),
|
||||
"fileName" -> trim(label("Filename", text(required))),
|
||||
"commit" -> trim(label("Commit", text(required, conflict)))
|
||||
"commit" -> trim(label("Commit", text(required, conflict))),
|
||||
"newBranch" -> trim(label("New Branch", boolean()))
|
||||
)(DeleteForm.apply)
|
||||
|
||||
val commentForm = mapping(
|
||||
@@ -252,23 +262,8 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
val (branchName, path) = repository.splitPath(multiParams("splat").head)
|
||||
val page = params.get("page").flatMap(_.toIntOpt).getOrElse(1)
|
||||
|
||||
def getStatuses(sha: String): List[CommitStatus] = {
|
||||
getCommitStatues(repository.owner, repository.name, sha)
|
||||
}
|
||||
|
||||
def getSummary(statuses: List[CommitStatus]): (CommitState, String) = {
|
||||
val stateMap = statuses.groupBy(_.state)
|
||||
val state = CommitState.combine(stateMap.keySet)
|
||||
val summary = stateMap.map { case (keyState, states) => s"${states.size} ${keyState.name}" }.mkString(", ")
|
||||
state -> summary
|
||||
}
|
||||
|
||||
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) {
|
||||
git =>
|
||||
def getTags(sha: String): List[String] = {
|
||||
JGitUtil.getTagsOnCommit(git, sha)
|
||||
}
|
||||
|
||||
JGitUtil.getCommitLog(git, branchName, page, 30, path) match {
|
||||
case Right((logs, hasNext)) =>
|
||||
html.commits(
|
||||
@@ -277,34 +272,33 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
repository,
|
||||
logs
|
||||
.map {
|
||||
c =>
|
||||
CommitInfo(
|
||||
id = c.id,
|
||||
shortMessage = c.shortMessage,
|
||||
fullMessage = c.fullMessage,
|
||||
parents = c.parents,
|
||||
authorTime = c.authorTime,
|
||||
authorName = c.authorName,
|
||||
authorEmailAddress = c.authorEmailAddress,
|
||||
commitTime = c.commitTime,
|
||||
committerName = c.committerName,
|
||||
committerEmailAddress = c.committerEmailAddress,
|
||||
commitSign = c.commitSign,
|
||||
verified = c.commitSign
|
||||
.flatMap { s =>
|
||||
GpgUtil.verifySign(s)
|
||||
}
|
||||
commit =>
|
||||
(
|
||||
CommitInfo(
|
||||
id = commit.id,
|
||||
shortMessage = commit.shortMessage,
|
||||
fullMessage = commit.fullMessage,
|
||||
parents = commit.parents,
|
||||
authorTime = commit.authorTime,
|
||||
authorName = commit.authorName,
|
||||
authorEmailAddress = commit.authorEmailAddress,
|
||||
commitTime = commit.commitTime,
|
||||
committerName = commit.committerName,
|
||||
committerEmailAddress = commit.committerEmailAddress,
|
||||
commitSign = commit.commitSign,
|
||||
verified = commit.commitSign.flatMap(GpgUtil.verifySign)
|
||||
),
|
||||
JGitUtil.getTagsOnCommit(git, commit.id),
|
||||
getCommitStatusWithSummary(repository.owner, repository.name, commit.id)
|
||||
)
|
||||
}
|
||||
.splitWith { (commit1, commit2) =>
|
||||
view.helpers.date(commit1.commitTime) == view.helpers.date(commit2.commitTime)
|
||||
.splitWith {
|
||||
case ((commit1, _, _), (commit2, _, _)) =>
|
||||
view.helpers.date(commit1.commitTime) == view.helpers.date(commit2.commitTime)
|
||||
},
|
||||
page,
|
||||
hasNext,
|
||||
hasDeveloperRole(repository.owner, repository.name, context.loginAccount),
|
||||
getStatuses,
|
||||
getSummary,
|
||||
getTags
|
||||
hasDeveloperRole(repository.owner, repository.name, context.loginAccount)
|
||||
)
|
||||
case Left(_) => NotFound()
|
||||
}
|
||||
@@ -335,7 +329,16 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
val (branch, path) = repository.splitPath(multiParams("splat").head)
|
||||
val protectedBranch = getProtectedBranchInfo(repository.owner, repository.name, branch)
|
||||
.needStatusCheck(context.loginAccount.get.userName)
|
||||
html.upload(branch, repository, if (path.length == 0) Nil else path.split("/").toList, protectedBranch)
|
||||
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
|
||||
val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(branch))
|
||||
html.upload(
|
||||
branch,
|
||||
repository,
|
||||
if (path.length == 0) Nil else path.split("/").toList,
|
||||
protectedBranch,
|
||||
revCommit.name
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
post("/:owner/:repository/upload", uploadForm)(writableUsersOnly { (form, repository) =>
|
||||
@@ -348,36 +351,47 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
file.copy(name = if (form.path.length == 0) file.name else s"${form.path}/${file.name}")
|
||||
}
|
||||
|
||||
commitFiles(
|
||||
repository = repository,
|
||||
branch = form.branch,
|
||||
path = form.path,
|
||||
files = files.toIndexedSeq,
|
||||
message = form.message.getOrElse("Add files via upload"),
|
||||
loginAccount = context.loginAccount.get,
|
||||
settings = context.settings
|
||||
) {
|
||||
case (git, headTip, builder, inserter) =>
|
||||
JGitUtil.processTree(git, headTip) { (path, tree) =>
|
||||
if (!newFiles.exists(_.name.contains(path))) {
|
||||
builder.add(JGitUtil.createDirCacheEntry(path, tree.getEntryFileMode, tree.getEntryObjectId))
|
||||
}
|
||||
}
|
||||
|
||||
newFiles.foreach { file =>
|
||||
val bytes =
|
||||
FileUtils.readFileToByteArray(new File(getTemporaryDir(session.getId), FileUtil.checkFilename(file.id)))
|
||||
builder.add(
|
||||
JGitUtil.createDirCacheEntry(file.name, FileMode.REGULAR_FILE, inserter.insert(Constants.OBJ_BLOB, bytes))
|
||||
)
|
||||
builder.finish()
|
||||
}
|
||||
if (form.newBranch) {
|
||||
val newBranchName = createNewBranchForPullRequest(repository, form.branch)
|
||||
val objectId = _commit(newBranchName)
|
||||
val issueId =
|
||||
createIssueAndPullRequest(repository, form.branch, newBranchName, form.commit, objectId.name, form.message)
|
||||
redirect(s"/${repository.owner}/${repository.name}/pull/${issueId}")
|
||||
} else {
|
||||
_commit(form.branch)
|
||||
if (form.path.length == 0) {
|
||||
redirect(s"/${repository.owner}/${repository.name}/tree/${form.branch}")
|
||||
} else {
|
||||
redirect(s"/${repository.owner}/${repository.name}/tree/${form.branch}/${form.path}")
|
||||
}
|
||||
}
|
||||
|
||||
if (form.path.length == 0) {
|
||||
redirect(s"/${repository.owner}/${repository.name}/tree/${form.branch}")
|
||||
} else {
|
||||
redirect(s"/${repository.owner}/${repository.name}/tree/${form.branch}/${form.path}")
|
||||
def _commit(branchName: String): ObjectId = {
|
||||
commitFiles(
|
||||
repository = repository,
|
||||
branch = branchName,
|
||||
path = form.path,
|
||||
files = files.toIndexedSeq,
|
||||
message = form.message.getOrElse("Add files via upload"),
|
||||
loginAccount = context.loginAccount.get,
|
||||
settings = context.settings
|
||||
) {
|
||||
case (git, headTip, builder, inserter) =>
|
||||
JGitUtil.processTree(git, headTip) { (path, tree) =>
|
||||
if (!newFiles.exists(_.name.contains(path))) {
|
||||
builder.add(JGitUtil.createDirCacheEntry(path, tree.getEntryFileMode, tree.getEntryObjectId))
|
||||
}
|
||||
}
|
||||
|
||||
newFiles.foreach { file =>
|
||||
val bytes =
|
||||
FileUtils.readFileToByteArray(new File(getTemporaryDir(session.getId), FileUtil.checkFilename(file.id)))
|
||||
builder.add(
|
||||
JGitUtil.createDirCacheEntry(file.name, FileMode.REGULAR_FILE, inserter.insert(Constants.OBJ_BLOB, bytes))
|
||||
)
|
||||
builder.finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -432,70 +446,156 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
})
|
||||
|
||||
post("/:owner/:repository/create", editorForm)(writableUsersOnly { (form, repository) =>
|
||||
commitFile(
|
||||
repository = repository,
|
||||
branch = form.branch,
|
||||
path = form.path,
|
||||
newFileName = Some(form.newFileName),
|
||||
oldFileName = None,
|
||||
content = appendNewLine(convertLineSeparator(form.content, form.lineSeparator), form.lineSeparator),
|
||||
charset = form.charset,
|
||||
message = form.message.getOrElse(s"Create ${form.newFileName}"),
|
||||
commit = form.commit,
|
||||
loginAccount = context.loginAccount.get,
|
||||
settings = context.settings
|
||||
)
|
||||
if (form.newBranch) {
|
||||
val newBranchName = createNewBranchForPullRequest(repository, form.branch)
|
||||
val objectId = _commit(newBranchName)
|
||||
val issueId =
|
||||
createIssueAndPullRequest(repository, form.branch, newBranchName, form.commit, objectId.name, form.message)
|
||||
redirect(s"/${repository.owner}/${repository.name}/pull/${issueId}")
|
||||
} else {
|
||||
_commit(form.branch)
|
||||
redirect(
|
||||
s"/${repository.owner}/${repository.name}/blob/${form.branch}/${if (form.path.length == 0) urlEncode(form.newFileName)
|
||||
else s"${form.path}/${urlEncode(form.newFileName)}"}"
|
||||
)
|
||||
}
|
||||
|
||||
redirect(
|
||||
s"/${repository.owner}/${repository.name}/blob/${form.branch}/${if (form.path.length == 0) urlEncode(form.newFileName)
|
||||
else s"${form.path}/${urlEncode(form.newFileName)}"}"
|
||||
)
|
||||
def _commit(branchName: String): ObjectId = {
|
||||
commitFile(
|
||||
repository = repository,
|
||||
branch = branchName,
|
||||
path = form.path,
|
||||
newFileName = Some(form.newFileName),
|
||||
oldFileName = None,
|
||||
content = appendNewLine(convertLineSeparator(form.content, form.lineSeparator), form.lineSeparator),
|
||||
charset = form.charset,
|
||||
message = form.message.getOrElse(s"Create ${form.newFileName}"),
|
||||
commit = form.commit,
|
||||
loginAccount = context.loginAccount.get,
|
||||
settings = context.settings
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
post("/:owner/:repository/update", editorForm)(writableUsersOnly { (form, repository) =>
|
||||
commitFile(
|
||||
repository = repository,
|
||||
branch = form.branch,
|
||||
path = form.path,
|
||||
newFileName = Some(form.newFileName),
|
||||
oldFileName = form.oldFileName,
|
||||
content = appendNewLine(convertLineSeparator(form.content, form.lineSeparator), form.lineSeparator),
|
||||
charset = form.charset,
|
||||
message = if (form.oldFileName.contains(form.newFileName)) {
|
||||
form.message.getOrElse(s"Update ${form.newFileName}")
|
||||
} else {
|
||||
form.message.getOrElse(s"Rename ${form.oldFileName.get} to ${form.newFileName}")
|
||||
},
|
||||
commit = form.commit,
|
||||
loginAccount = context.loginAccount.get,
|
||||
settings = context.settings
|
||||
)
|
||||
if (form.newBranch) {
|
||||
val newBranchName = createNewBranchForPullRequest(repository, form.branch)
|
||||
val objectId = _commit(newBranchName)
|
||||
val issueId =
|
||||
createIssueAndPullRequest(repository, form.branch, newBranchName, form.commit, objectId.name, form.message)
|
||||
redirect(s"/${repository.owner}/${repository.name}/pull/${issueId}")
|
||||
} else {
|
||||
_commit(form.branch)
|
||||
redirect(
|
||||
s"/${repository.owner}/${repository.name}/blob/${urlEncode(form.branch)}/${if (form.path.length == 0) urlEncode(form.newFileName)
|
||||
else s"${form.path}/${urlEncode(form.newFileName)}"}"
|
||||
)
|
||||
}
|
||||
|
||||
redirect(
|
||||
s"/${repository.owner}/${repository.name}/blob/${urlEncode(form.branch)}/${if (form.path.length == 0) urlEncode(form.newFileName)
|
||||
else s"${form.path}/${urlEncode(form.newFileName)}"}"
|
||||
)
|
||||
def _commit(branchName: String): ObjectId = {
|
||||
commitFile(
|
||||
repository = repository,
|
||||
branch = branchName,
|
||||
path = form.path,
|
||||
newFileName = Some(form.newFileName),
|
||||
oldFileName = form.oldFileName,
|
||||
content = appendNewLine(convertLineSeparator(form.content, form.lineSeparator), form.lineSeparator),
|
||||
charset = form.charset,
|
||||
message = if (form.oldFileName.contains(form.newFileName)) {
|
||||
form.message.getOrElse(s"Update ${form.newFileName}")
|
||||
} else {
|
||||
form.message.getOrElse(s"Rename ${form.oldFileName.get} to ${form.newFileName}")
|
||||
},
|
||||
commit = form.commit,
|
||||
loginAccount = context.loginAccount.get,
|
||||
settings = context.settings
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
post("/:owner/:repository/remove", deleteForm)(writableUsersOnly { (form, repository) =>
|
||||
commitFile(
|
||||
repository = repository,
|
||||
branch = form.branch,
|
||||
path = form.path,
|
||||
newFileName = None,
|
||||
oldFileName = Some(form.fileName),
|
||||
content = "",
|
||||
charset = "",
|
||||
message = form.message.getOrElse(s"Delete ${form.fileName}"),
|
||||
commit = form.commit,
|
||||
if (form.newBranch) {
|
||||
val newBranchName = createNewBranchForPullRequest(repository, form.branch)
|
||||
val objectId = _commit(newBranchName)
|
||||
val issueId =
|
||||
createIssueAndPullRequest(repository, form.branch, newBranchName, form.commit, objectId.name, form.message)
|
||||
redirect(s"/${repository.owner}/${repository.name}/pull/${issueId}")
|
||||
} else {
|
||||
_commit(form.branch)
|
||||
redirect(
|
||||
s"/${repository.owner}/${repository.name}/tree/${form.branch}${if (form.path.length == 0) ""
|
||||
else "/" + form.path}"
|
||||
)
|
||||
}
|
||||
|
||||
def _commit(branchName: String): ObjectId = {
|
||||
commitFile(
|
||||
repository = repository,
|
||||
branch = branchName,
|
||||
path = form.path,
|
||||
newFileName = None,
|
||||
oldFileName = Some(form.fileName),
|
||||
content = "",
|
||||
charset = "",
|
||||
message = form.message.getOrElse(s"Delete ${form.fileName}"),
|
||||
commit = form.commit,
|
||||
loginAccount = context.loginAccount.get,
|
||||
settings = context.settings
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
private def getNewBranchName(repository: RepositoryInfo): String = {
|
||||
var i = 1
|
||||
val branchNamePrefix = cutTail(context.loginAccount.get.userName.replaceAll("[^a-zA-Z0-9-_]", "-"), 25)
|
||||
while (repository.branchList.exists(p => p.contains(s"$branchNamePrefix-patch-$i"))) {
|
||||
i += 1
|
||||
}
|
||||
s"$branchNamePrefix-patch-$i"
|
||||
}
|
||||
|
||||
private def createNewBranchForPullRequest(repository: RepositoryInfo, baseBranchName: String): String = {
|
||||
val newBranchName = getNewBranchName(repository)
|
||||
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
|
||||
JGitUtil.createBranch(git, baseBranchName, newBranchName)
|
||||
}
|
||||
newBranchName
|
||||
}
|
||||
|
||||
private def createIssueAndPullRequest(
|
||||
repository: RepositoryInfo,
|
||||
baseBranch: String,
|
||||
requestBranch: String,
|
||||
commitIdFrom: String,
|
||||
commitIdTo: String,
|
||||
commitMessage: Option[String]
|
||||
): Int = {
|
||||
val issueId = insertIssue(
|
||||
owner = repository.owner,
|
||||
repository = repository.name,
|
||||
loginUser = context.loginAccount.get.userName,
|
||||
title = requestBranch,
|
||||
content = commitMessage,
|
||||
assignedUserName = None,
|
||||
milestoneId = None,
|
||||
priorityId = None,
|
||||
isPullRequest = true
|
||||
)
|
||||
createPullRequest(
|
||||
originRepository = repository,
|
||||
issueId = issueId,
|
||||
originBranch = baseBranch,
|
||||
requestUserName = repository.owner,
|
||||
requestRepositoryName = repository.name,
|
||||
requestBranch = requestBranch,
|
||||
commitIdFrom = commitIdFrom,
|
||||
commitIdTo = commitIdTo,
|
||||
isDraft = false,
|
||||
loginAccount = context.loginAccount.get,
|
||||
settings = context.settings
|
||||
)
|
||||
|
||||
redirect(
|
||||
s"/${repository.owner}/${repository.name}/tree/${form.branch}${if (form.path.length == 0) "" else "/" + form.path}"
|
||||
)
|
||||
})
|
||||
issueId
|
||||
}
|
||||
|
||||
get("/:owner/:repository/raw/*")(referrersOnly { repository =>
|
||||
val (id, path) = repository.splitPath(multiParams("splat").head)
|
||||
@@ -514,6 +614,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
val blobRoute = get("/:owner/:repository/blob/*")(referrersOnly { repository =>
|
||||
val (id, path) = repository.splitPath(multiParams("splat").head)
|
||||
val raw = params.get("raw").getOrElse("false").toBoolean
|
||||
val highlighterTheme = getSyntaxHighlighterTheme()
|
||||
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) {
|
||||
git =>
|
||||
val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(id))
|
||||
@@ -533,13 +634,25 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
hasWritePermission = hasDeveloperRole(repository.owner, repository.name, context.loginAccount),
|
||||
isBlame = request.paths(2) == "blame",
|
||||
isLfsFile = isLfsFile(git, objectId),
|
||||
tabSize = info.tabSize
|
||||
tabSize = info.tabSize,
|
||||
highlighterTheme = highlighterTheme
|
||||
)
|
||||
}
|
||||
} getOrElse NotFound()
|
||||
}
|
||||
})
|
||||
|
||||
private def getSyntaxHighlighterTheme()(implicit context: Context): String = {
|
||||
context.loginAccount match {
|
||||
case Some(account) =>
|
||||
getAccountPreference(account.userName) match {
|
||||
case Some(x) => x.highlighterTheme
|
||||
case _ => "github-v2"
|
||||
}
|
||||
case _ => "github-v2"
|
||||
}
|
||||
}
|
||||
|
||||
private def isLfsFile(git: Git, objectId: ObjectId): Boolean = {
|
||||
JGitUtil.getObjectLoaderFromId(git, objectId)(JGitUtil.isLfsPointer).getOrElse(false)
|
||||
}
|
||||
@@ -601,6 +714,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
new JGitUtil.CommitInfo(revCommit),
|
||||
JGitUtil.getBranchesOfCommit(git, revCommit.getName),
|
||||
JGitUtil.getTagsOfCommit(git, revCommit.getName),
|
||||
getCommitStatusWithSummary(repository.owner, repository.name, revCommit.getName),
|
||||
getCommitComments(repository.owner, repository.name, id, true),
|
||||
repository,
|
||||
diffs,
|
||||
@@ -759,19 +873,20 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
defaultBranch = repository.repository.defaultBranch,
|
||||
origin = repository.repository.originUserName.isEmpty
|
||||
)
|
||||
.sortBy(br => (br.mergeInfo.isEmpty, br.commitTime))
|
||||
.sortBy(branch => (branch.mergeInfo.isEmpty, branch.commitTime))
|
||||
.map(
|
||||
br =>
|
||||
branch =>
|
||||
(
|
||||
br,
|
||||
branch,
|
||||
getPullRequestByRequestCommit(
|
||||
repository.owner,
|
||||
repository.name,
|
||||
repository.repository.defaultBranch,
|
||||
br.name,
|
||||
br.commitId
|
||||
branch.name,
|
||||
branch.commitId
|
||||
),
|
||||
protectedBranches.contains(br.name)
|
||||
protectedBranches.contains(branch.name),
|
||||
getCommitStatusWithSummary(repository.owner, repository.name, branch.commitId)
|
||||
)
|
||||
)
|
||||
.reverse
|
||||
@@ -832,7 +947,8 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
if (repository.repository.defaultBranch != branchName) {
|
||||
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
|
||||
git.branchDelete().setForce(true).setBranchNames(branchName).call()
|
||||
recordDeleteBranchActivity(repository.owner, repository.name, userName, branchName)
|
||||
val deleteBranchInfo = DeleteBranchInfo(repository.owner, repository.name, userName, branchName)
|
||||
recordActivity(deleteBranchInfo)
|
||||
}
|
||||
}
|
||||
redirect(s"/${repository.owner}/${repository.name}/branches")
|
||||
@@ -905,10 +1021,6 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
lazy val isValid: Boolean = fileIds.nonEmpty
|
||||
}
|
||||
|
||||
private val readmeFiles = PluginRegistry().renderableExtensions.map { extension =>
|
||||
s"readme.${extension}"
|
||||
} ++ Seq("readme.txt", "readme")
|
||||
|
||||
/**
|
||||
* Provides HTML of the file list.
|
||||
*
|
||||
@@ -928,13 +1040,21 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
defining(JGitUtil.getRevCommitFromId(git, objectId)) { revCommit =>
|
||||
val lastModifiedCommit =
|
||||
if (path == ".") revCommit else JGitUtil.getLastModifiedCommit(git, revCommit, path)
|
||||
val commitCount = JGitUtil.getCommitCount(git, lastModifiedCommit.getName)
|
||||
// get files
|
||||
val files = JGitUtil.getFileList(git, revision, path, context.settings.baseUrl)
|
||||
val files = JGitUtil.getFileList(
|
||||
git,
|
||||
revision,
|
||||
path,
|
||||
context.settings.baseUrl,
|
||||
commitCount,
|
||||
context.settings.repositoryViewer.maxFiles
|
||||
)
|
||||
val parentPath = if (path == ".") Nil else path.split("/").toList
|
||||
// process README.md or README.markdown
|
||||
val readme = files
|
||||
// process README
|
||||
val readme = files // files should be sorted alphabetically.
|
||||
.find { file =>
|
||||
!file.isDirectory && readmeFiles.contains(file.name.toLowerCase)
|
||||
!file.isDirectory && RepositoryService.readmeFiles.contains(file.name.toLowerCase)
|
||||
}
|
||||
.map { file =>
|
||||
val path = (file.name :: parentPath.reverse).reverse
|
||||
@@ -950,7 +1070,8 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
repository,
|
||||
if (path == ".") Nil else path.split("/").toList, // current path
|
||||
new JGitUtil.CommitInfo(lastModifiedCommit), // last modified commit
|
||||
JGitUtil.getCommitCount(git, lastModifiedCommit.getName),
|
||||
getCommitStatusWithSummary(repository.owner, repository.name, lastModifiedCommit.getName),
|
||||
commitCount,
|
||||
files,
|
||||
readme,
|
||||
hasDeveloperRole(repository.owner, repository.name, context.loginAccount),
|
||||
|
||||
@@ -38,14 +38,21 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
|
||||
"information" -> trim(label("Information", optional(text()))),
|
||||
"allowAccountRegistration" -> trim(label("Account registration", boolean())),
|
||||
"allowAnonymousAccess" -> trim(label("Anonymous access", boolean())),
|
||||
"isCreateRepoOptionPublic" -> trim(label("Default option to create a new repository", boolean())),
|
||||
"isCreateRepoOptionPublic" -> trim(label("Default visibility of new repository", boolean())),
|
||||
"repositoryOperation" -> mapping(
|
||||
"create" -> trim(label("Allow all users to create repository", boolean())),
|
||||
"delete" -> trim(label("Allow all users to delete repository", boolean())),
|
||||
"rename" -> trim(label("Allow all users to rename repository", boolean())),
|
||||
"transfer" -> trim(label("Allow all users to transfer repository", boolean())),
|
||||
"fork" -> trim(label("Allow all users to fork repository", boolean()))
|
||||
)(RepositoryOperation.apply),
|
||||
"gravatar" -> trim(label("Gravatar", boolean())),
|
||||
"notification" -> trim(label("Notification", boolean())),
|
||||
"activityLogLimit" -> trim(label("Limit of activity logs", optional(number()))),
|
||||
"limitVisibleRepositories" -> trim(label("limitVisibleRepositories", boolean())),
|
||||
"ssh" -> mapping(
|
||||
"enabled" -> trim(label("SSH access", boolean())),
|
||||
"host" -> trim(label("SSH host", optional(text()))),
|
||||
"port" -> trim(label("SSH port", optional(number()))),
|
||||
"port" -> trim(label("SSH port", optional(number())))
|
||||
)(Ssh.apply),
|
||||
"useSMTP" -> trim(label("SMTP", boolean())),
|
||||
"smtp" -> optionalIfNotChecked(
|
||||
@@ -90,11 +97,21 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
|
||||
)(OIDC.apply)
|
||||
),
|
||||
"skinName" -> trim(label("AdminLTE skin name", text(required))),
|
||||
"userDefinedCss" -> trim(label("User-defined CSS", optional(text()))),
|
||||
"showMailAddress" -> trim(label("Show mail address", boolean())),
|
||||
"webhook" -> mapping(
|
||||
"blockPrivateAddress" -> trim(label("Block private address", boolean())),
|
||||
"whitelist" -> trim(label("Whitelist", multiLineText()))
|
||||
)(WebHook.apply)
|
||||
)(WebHook.apply),
|
||||
"upload" -> mapping(
|
||||
"maxFileSize" -> trim(label("Max file size", long(required))),
|
||||
"timeout" -> trim(label("Timeout", long(required))),
|
||||
"largeMaxFileSize" -> trim(label("Max file size for large file", long(required))),
|
||||
"largeTimeout" -> trim(label("Timeout for large file", long(required)))
|
||||
)(Upload.apply),
|
||||
"repositoryViewer" -> mapping(
|
||||
"maxFiles" -> trim(label("Max files", number(required)))
|
||||
)(RepositoryViewerSettings.apply)
|
||||
)(SystemSettings.apply).verifying { settings =>
|
||||
Vector(
|
||||
if (settings.ssh.enabled && settings.baseUrl.isEmpty) {
|
||||
@@ -170,7 +187,7 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
|
||||
|
||||
val newUserForm = mapping(
|
||||
"userName" -> trim(label("Username", text(required, maxlength(100), identifier, uniqueUserName, reservedNames))),
|
||||
"password" -> trim(label("Password", text(required, maxlength(20), password))),
|
||||
"password" -> trim(label("Password", text(required, maxlength(20)))),
|
||||
"fullName" -> trim(label("Full Name", text(required, maxlength(100)))),
|
||||
"mailAddress" -> trim(label("Mail Address", text(required, maxlength(100), uniqueMailAddress()))),
|
||||
"extraMailAddresses" -> list(
|
||||
@@ -184,7 +201,7 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
|
||||
|
||||
val editUserForm = mapping(
|
||||
"userName" -> trim(label("Username", text(required, maxlength(100), identifier))),
|
||||
"password" -> trim(label("Password", optional(text(maxlength(20), password)))),
|
||||
"password" -> trim(label("Password", optional(text(maxlength(20))))),
|
||||
"fullName" -> trim(label("Full Name", text(required, maxlength(100)))),
|
||||
"mailAddress" -> trim(label("Mail Address", text(required, maxlength(100), uniqueMailAddress("userName")))),
|
||||
"extraMailAddresses" -> list(
|
||||
@@ -529,24 +546,26 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
|
||||
}
|
||||
}
|
||||
|
||||
private def members: Constraint = new Constraint() {
|
||||
override def validate(name: String, value: String, messages: Messages): Option[String] = {
|
||||
if (value.split(",").exists {
|
||||
_.split(":") match { case Array(userName, isManager) => isManager.toBoolean }
|
||||
}) None
|
||||
else Some("Must select one manager at least.")
|
||||
}
|
||||
}
|
||||
|
||||
protected def disableByNotYourself(paramName: String): Constraint = new Constraint() {
|
||||
override def validate(name: String, value: String, messages: Messages): Option[String] = {
|
||||
params.get(paramName).flatMap { userName =>
|
||||
if (userName == context.loginAccount.get.userName && params.get("removed") == Some("true"))
|
||||
Some("You can't disable your account yourself")
|
||||
else
|
||||
None
|
||||
private def members: Constraint =
|
||||
new Constraint() {
|
||||
override def validate(name: String, value: String, messages: Messages): Option[String] = {
|
||||
if (value.split(",").exists {
|
||||
_.split(":") match { case Array(userName, isManager) => isManager.toBoolean }
|
||||
}) None
|
||||
else Some("Must select one manager at least.")
|
||||
}
|
||||
}
|
||||
|
||||
protected def disableByNotYourself(paramName: String): Constraint =
|
||||
new Constraint() {
|
||||
override def validate(name: String, value: String, messages: Messages): Option[String] = {
|
||||
params.get(paramName).flatMap { userName =>
|
||||
if (userName == context.loginAccount.get.userName && params.get("removed") == Some("true"))
|
||||
Some("You can't disable your account yourself")
|
||||
else
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package gitbucket.core.controller
|
||||
|
||||
import gitbucket.core.model.WebHook
|
||||
import gitbucket.core.model.activity.{CreateWikiPageInfo, DeleteWikiInfo, EditWikiPageInfo}
|
||||
import gitbucket.core.service.RepositoryService.RepositoryInfo
|
||||
import gitbucket.core.service.WebHookService.WebHookGollumPayload
|
||||
import gitbucket.core.wiki.html
|
||||
@@ -13,6 +14,7 @@ import gitbucket.core.util.Directory._
|
||||
import org.scalatra.forms._
|
||||
import org.eclipse.jgit.api.Git
|
||||
import org.scalatra.i18n.Messages
|
||||
|
||||
import scala.util.Using
|
||||
|
||||
class WikiController
|
||||
@@ -24,6 +26,7 @@ class WikiController
|
||||
with WebHookService
|
||||
with ReadableUsersAuthenticator
|
||||
with ReferrerAuthenticator
|
||||
with RequestCache
|
||||
|
||||
trait WikiControllerBase extends ControllerBase {
|
||||
self: WikiService
|
||||
@@ -184,13 +187,9 @@ trait WikiControllerBase extends ControllerBase {
|
||||
).foreach {
|
||||
commitId =>
|
||||
updateLastActivityDate(repository.owner, repository.name)
|
||||
recordEditWikiPageActivity(
|
||||
repository.owner,
|
||||
repository.name,
|
||||
loginAccount.userName,
|
||||
form.pageName,
|
||||
commitId
|
||||
)
|
||||
val wikiEditInfo =
|
||||
EditWikiPageInfo(repository.owner, repository.name, loginAccount.userName, form.pageName, commitId)
|
||||
recordActivity(wikiEditInfo)
|
||||
callWebHookOf(repository.owner, repository.name, WebHook.Gollum, context.settings) {
|
||||
getAccountByUserName(repository.owner).map { repositoryUser =>
|
||||
WebHookGollumPayload("edited", form.pageName, commitId, repository, repositoryUser, loginAccount)
|
||||
@@ -228,7 +227,9 @@ trait WikiControllerBase extends ControllerBase {
|
||||
).foreach {
|
||||
commitId =>
|
||||
updateLastActivityDate(repository.owner, repository.name)
|
||||
recordCreateWikiPageActivity(repository.owner, repository.name, loginAccount.userName, form.pageName)
|
||||
val createWikiPageInfo =
|
||||
CreateWikiPageInfo(repository.owner, repository.name, loginAccount.userName, form.pageName)
|
||||
recordActivity(createWikiPageInfo)
|
||||
callWebHookOf(repository.owner, repository.name, WebHook.Gollum, context.settings) {
|
||||
getAccountByUserName(repository.owner).map { repositoryUser =>
|
||||
WebHookGollumPayload("created", form.pageName, commitId, repository, repositoryUser, loginAccount)
|
||||
@@ -250,14 +251,13 @@ trait WikiControllerBase extends ControllerBase {
|
||||
val pageName = StringUtil.urlDecode(params("page"))
|
||||
|
||||
defining(context.loginAccount.get) { loginAccount =>
|
||||
deleteWikiPage(
|
||||
val deleteWikiInfo = DeleteWikiInfo(
|
||||
repository.owner,
|
||||
repository.name,
|
||||
pageName,
|
||||
loginAccount.fullName,
|
||||
loginAccount.mailAddress,
|
||||
s"Destroyed ${pageName}"
|
||||
loginAccount.userName,
|
||||
pageName
|
||||
)
|
||||
recordActivity(deleteWikiInfo)
|
||||
updateLastActivityDate(repository.owner, repository.name)
|
||||
|
||||
redirect(s"/${repository.owner}/${repository.name}/wiki")
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
package gitbucket.core.controller.api
|
||||
import gitbucket.core.api.{ApiObject, ApiRef, JsonFormat}
|
||||
import gitbucket.core.api.{ApiObject, ApiRef, CreateARef, JsonFormat, UpdateARef}
|
||||
import gitbucket.core.controller.ControllerBase
|
||||
import gitbucket.core.util.Directory.getRepositoryDir
|
||||
import gitbucket.core.util.ReferrerAuthenticator
|
||||
import gitbucket.core.util.Implicits._
|
||||
import org.eclipse.jgit.api.Git
|
||||
import org.eclipse.jgit.lib.ObjectId
|
||||
import org.eclipse.jgit.lib.RefUpdate.Result
|
||||
import org.scalatra.{BadRequest, NoContent, UnprocessableEntity}
|
||||
import org.slf4j.LoggerFactory
|
||||
|
||||
import scala.jdk.CollectionConverters._
|
||||
import scala.util.Using
|
||||
@@ -12,11 +16,23 @@ import scala.util.Using
|
||||
trait ApiGitReferenceControllerBase extends ControllerBase {
|
||||
self: ReferrerAuthenticator =>
|
||||
|
||||
private val logger = LoggerFactory.getLogger(classOf[ApiGitReferenceControllerBase])
|
||||
|
||||
/*
|
||||
* i. Get a reference
|
||||
* https://developer.github.com/v3/git/refs/#get-a-reference
|
||||
* https://docs.github.com/en/free-pro-team@latest/rest/reference/git#get-a-reference
|
||||
*/
|
||||
get("/api/v3/repos/:owner/:repository/git/ref/*")(referrersOnly { repository =>
|
||||
getRef()
|
||||
})
|
||||
|
||||
// Some versions of GHE support this path
|
||||
get("/api/v3/repos/:owner/:repository/git/refs/*")(referrersOnly { repository =>
|
||||
logger.warn("git/refs/ endpoint may not be compatible with GitHub API v3. Consider using git/ref/ endpoint instead")
|
||||
getRef()
|
||||
})
|
||||
|
||||
private def getRef() = {
|
||||
val revstr = multiParams("splat").head
|
||||
Using.resource(Git.open(getRepositoryDir(params("owner"), params("repository")))) { git =>
|
||||
val ref = git.getRepository().findRef(revstr)
|
||||
@@ -38,24 +54,83 @@ trait ApiGitReferenceControllerBase extends ControllerBase {
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/*
|
||||
* ii. Get all references
|
||||
* https://developer.github.com/v3/git/refs/#get-all-references
|
||||
* https://docs.github.com/en/free-pro-team@latest/rest/reference/git#list-matching-references
|
||||
*/
|
||||
|
||||
/*
|
||||
* iii. Create a reference
|
||||
* https://developer.github.com/v3/git/refs/#create-a-reference
|
||||
* https://docs.github.com/en/free-pro-team@latest/rest/reference/git#create-a-reference
|
||||
*/
|
||||
post("/api/v3/repos/:owner/:repository/git/refs")(referrersOnly { _ =>
|
||||
extractFromJsonBody[CreateARef].map {
|
||||
data =>
|
||||
Using.resource(Git.open(getRepositoryDir(params("owner"), params("repository")))) { git =>
|
||||
val ref = git.getRepository.findRef(data.ref)
|
||||
if (ref == null) {
|
||||
val update = git.getRepository.updateRef(data.ref)
|
||||
update.setNewObjectId(ObjectId.fromString(data.sha))
|
||||
val result = update.update()
|
||||
result match {
|
||||
case Result.NEW => JsonFormat(ApiRef(update.getName, ApiObject(update.getNewObjectId.getName)))
|
||||
case _ => UnprocessableEntity(result.name())
|
||||
}
|
||||
} else {
|
||||
UnprocessableEntity("Ref already exists.")
|
||||
}
|
||||
}
|
||||
} getOrElse BadRequest()
|
||||
})
|
||||
|
||||
/*
|
||||
* iv. Update a reference
|
||||
* https://developer.github.com/v3/git/refs/#update-a-reference
|
||||
* https://docs.github.com/en/free-pro-team@latest/rest/reference/git#update-a-reference
|
||||
*/
|
||||
patch("/api/v3/repos/:owner/:repository/git/refs/*")(referrersOnly { _ =>
|
||||
val refName = multiParams("splat").mkString("/")
|
||||
extractFromJsonBody[UpdateARef].map {
|
||||
data =>
|
||||
Using.resource(Git.open(getRepositoryDir(params("owner"), params("repository")))) { git =>
|
||||
val ref = git.getRepository.findRef(refName)
|
||||
if (ref == null) {
|
||||
UnprocessableEntity("Ref does not exist.")
|
||||
} else {
|
||||
val update = git.getRepository.updateRef(ref.getName)
|
||||
update.setNewObjectId(ObjectId.fromString(data.sha))
|
||||
update.setForceUpdate(data.force)
|
||||
val result = update.update()
|
||||
result match {
|
||||
case Result.FORCED | Result.FAST_FORWARD | Result.NO_CHANGE =>
|
||||
JsonFormat(ApiRef(update.getName, ApiObject(update.getNewObjectId.getName)))
|
||||
case _ => UnprocessableEntity(result.name())
|
||||
}
|
||||
}
|
||||
}
|
||||
} getOrElse BadRequest()
|
||||
})
|
||||
|
||||
/*
|
||||
* v. Delete a reference
|
||||
* https://developer.github.com/v3/git/refs/#delete-a-reference
|
||||
*/
|
||||
* v. Delete a reference
|
||||
* https://docs.github.com/en/free-pro-team@latest/rest/reference/git#delete-a-reference
|
||||
*/
|
||||
delete("/api/v3/repos/:owner/:repository/git/refs/*")(referrersOnly { _ =>
|
||||
val refName = multiParams("splat").mkString("/")
|
||||
Using.resource(Git.open(getRepositoryDir(params("owner"), params("repository")))) { git =>
|
||||
val ref = git.getRepository.findRef(refName)
|
||||
if (ref == null) {
|
||||
UnprocessableEntity("Ref does not exist.")
|
||||
} else {
|
||||
val update = git.getRepository.updateRef(ref.getName)
|
||||
update.setForceUpdate(true)
|
||||
val result = update.delete()
|
||||
result match {
|
||||
case Result.FORCED => NoContent()
|
||||
case _ => UnprocessableEntity(result.name())
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import gitbucket.core.controller.{Context, ControllerBase}
|
||||
import gitbucket.core.service._
|
||||
import gitbucket.core.util.Implicits._
|
||||
import gitbucket.core.util.{ReadableUsersAuthenticator, ReferrerAuthenticator, RepositoryName}
|
||||
import org.scalatra.{ActionResult, NoContent}
|
||||
|
||||
trait ApiIssueCommentControllerBase extends ControllerBase {
|
||||
self: AccountService
|
||||
@@ -14,8 +15,8 @@ trait ApiIssueCommentControllerBase extends ControllerBase {
|
||||
with ReadableUsersAuthenticator
|
||||
with ReferrerAuthenticator =>
|
||||
/*
|
||||
* i. List comments on an issue
|
||||
* https://developer.github.com/v3/issues/comments/#list-comments-on-an-issue
|
||||
* i. List issue comments for a repository
|
||||
* https://docs.github.com/en/rest/reference/issues#list-issue-comments-for-a-repository
|
||||
*/
|
||||
get("/api/v3/repos/:owner/:repository/issues/:id/comments")(referrersOnly { repository =>
|
||||
(for {
|
||||
@@ -30,18 +31,90 @@ trait ApiIssueCommentControllerBase extends ControllerBase {
|
||||
})
|
||||
|
||||
/*
|
||||
* ii. List comments in a repository
|
||||
* https://developer.github.com/v3/issues/comments/#list-comments-in-a-repository
|
||||
* ii. Get an issue comment
|
||||
* https://docs.github.com/en/rest/reference/issues#get-an-issue-comment
|
||||
*/
|
||||
get("/api/v3/repos/:owner/:repository/issues/comments/:id")(referrersOnly { repository =>
|
||||
val commentId = params("id").toInt
|
||||
getCommentForApi(repository.owner, repository.name, commentId) match {
|
||||
case Some((issueComment, user, issue)) =>
|
||||
JsonFormat(
|
||||
ApiComment(issueComment, RepositoryName(repository), issue.issueId, ApiUser(user), issue.isPullRequest)
|
||||
)
|
||||
case _ => NotFound()
|
||||
}
|
||||
})
|
||||
|
||||
/*
|
||||
* iii. Update an issue comment
|
||||
* https://docs.github.com/en/rest/reference/issues#update-an-issue-comment
|
||||
*/
|
||||
patch("/api/v3/repos/:owner/:repository/issues/comments/:id")(readableUsersOnly { repository =>
|
||||
val commentId = params("id")
|
||||
val result = for {
|
||||
issueComment <- getComment(repository.owner, repository.name, commentId)
|
||||
issue <- getIssue(repository.owner, repository.name, issueComment.issueId.toString)
|
||||
} yield {
|
||||
if (isEditable(repository.owner, repository.name, issueComment.commentedUserName)) {
|
||||
val body = extractFromJsonBody[CreateAComment].map(_.body)
|
||||
updateCommentByApi(repository, issue, issueComment.commentId.toString, body)
|
||||
getComment(repository.owner, repository.name, commentId) match {
|
||||
case Some(issueComment) =>
|
||||
JsonFormat(
|
||||
ApiComment(
|
||||
issueComment,
|
||||
RepositoryName(repository),
|
||||
issue.issueId,
|
||||
ApiUser(context.loginAccount.get),
|
||||
issue.isPullRequest
|
||||
)
|
||||
)
|
||||
case _ =>
|
||||
}
|
||||
} else {
|
||||
Unauthorized()
|
||||
}
|
||||
}
|
||||
result match {
|
||||
case Some(response) => response
|
||||
case None => NotFound()
|
||||
}
|
||||
})
|
||||
|
||||
/*
|
||||
* iv. Delete a comment
|
||||
* https://docs.github.com/en/rest/reference/issues#delete-an-issue-comment
|
||||
*/
|
||||
delete("/api/v3/repos/:owner/:repo/issues/comments/:id")(readableUsersOnly { repository =>
|
||||
val maybeDeleteResponse: Option[Either[ActionResult, Option[Int]]] =
|
||||
for {
|
||||
commentId <- params("id").toIntOpt
|
||||
comment <- getComment(repository.owner, repository.name, commentId.toString)
|
||||
issue <- getIssue(repository.owner, repository.name, comment.issueId.toString)
|
||||
} yield {
|
||||
if (isEditable(repository.owner, repository.name, comment.commentedUserName)) {
|
||||
val maybeDeletedComment = deleteCommentByApi(repository, comment, issue)
|
||||
Right(maybeDeletedComment.map(_.commentId))
|
||||
} else {
|
||||
Left(Unauthorized())
|
||||
}
|
||||
}
|
||||
maybeDeleteResponse
|
||||
.map {
|
||||
case Right(maybeDeletedCommentId) => maybeDeletedCommentId.getOrElse(NotFound())
|
||||
case Left(err) => err
|
||||
}
|
||||
.getOrElse(NotFound())
|
||||
})
|
||||
|
||||
/*
|
||||
* v. List issue comments
|
||||
* https://docs.github.com/en/rest/reference/issues#list-issue-comments
|
||||
*/
|
||||
|
||||
/*
|
||||
* iii. Get a single comment
|
||||
* https://developer.github.com/v3/issues/comments/#get-a-single-comment
|
||||
*/
|
||||
|
||||
/*
|
||||
* iv. Create a comment
|
||||
* https://developer.github.com/v3/issues/comments/#create-a-comment
|
||||
* vi. Create an issue comment
|
||||
* https://docs.github.com/en/rest/reference/issues#create-an-issue-comment
|
||||
*/
|
||||
post("/api/v3/repos/:owner/:repository/issues/:id/comments")(readableUsersOnly { repository =>
|
||||
(for {
|
||||
@@ -64,16 +137,6 @@ trait ApiIssueCommentControllerBase extends ControllerBase {
|
||||
}) getOrElse NotFound()
|
||||
})
|
||||
|
||||
/*
|
||||
* v. Edit a comment
|
||||
* https://developer.github.com/v3/issues/comments/#edit-a-comment
|
||||
*/
|
||||
|
||||
/*
|
||||
* vi. Delete a comment
|
||||
* https://developer.github.com/v3/issues/comments/#delete-a-comment
|
||||
*/
|
||||
|
||||
private def isEditable(owner: String, repository: String, author: String)(implicit context: Context): Boolean =
|
||||
hasDeveloperRole(owner, repository, context.loginAccount) || author == context.loginAccount.get.userName
|
||||
}
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
package gitbucket.core.controller.api
|
||||
import gitbucket.core.api._
|
||||
import gitbucket.core.controller.ControllerBase
|
||||
import gitbucket.core.service.MilestonesService
|
||||
import gitbucket.core.service.RepositoryService.RepositoryInfo
|
||||
import gitbucket.core.util.{ReferrerAuthenticator, WritableUsersAuthenticator}
|
||||
import gitbucket.core.util.Implicits._
|
||||
import org.scalatra.NoContent
|
||||
|
||||
trait ApiIssueMilestoneControllerBase extends ControllerBase {
|
||||
self: MilestonesService with WritableUsersAuthenticator with ReferrerAuthenticator =>
|
||||
|
||||
/*
|
||||
* i. List milestones
|
||||
* https://docs.github.com/en/rest/reference/issues#list-milestones
|
||||
*/
|
||||
get("/api/v3/repos/:owner/:repository/milestones")(referrersOnly { repository =>
|
||||
val state = params.getOrElse("state", "all")
|
||||
// TODO "sort", "direction" params should be implemented.
|
||||
val apiMilestones = (for (milestoneWithIssue <- getMilestonesWithIssueCount(repository.owner, repository.name)
|
||||
.sortBy(p => p._1.milestoneId))
|
||||
yield {
|
||||
ApiMilestone(
|
||||
repository.repository,
|
||||
milestoneWithIssue._1,
|
||||
milestoneWithIssue._2,
|
||||
milestoneWithIssue._3
|
||||
)
|
||||
}).reverse
|
||||
state match {
|
||||
case "all" => JsonFormat(apiMilestones)
|
||||
case "open" | "closed" =>
|
||||
JsonFormat(
|
||||
apiMilestones.filter(p => p.state == state)
|
||||
)
|
||||
case _ => NotFound()
|
||||
}
|
||||
})
|
||||
|
||||
/*
|
||||
* ii. Create a milestone
|
||||
* https://docs.github.com/en/rest/reference/issues#create-a-milestone
|
||||
*/
|
||||
post("/api/v3/repos/:owner/:repository/milestones")(writableUsersOnly { repository =>
|
||||
(for {
|
||||
data <- extractFromJsonBody[CreateAMilestone] if data.isValid
|
||||
milestoneId = createMilestone(
|
||||
repository.owner,
|
||||
repository.name,
|
||||
data.title,
|
||||
data.description,
|
||||
data.due_on
|
||||
)
|
||||
apiMilestone <- getApiMilestone(repository, milestoneId)
|
||||
} yield {
|
||||
JsonFormat(apiMilestone)
|
||||
}) getOrElse NotFound()
|
||||
})
|
||||
|
||||
/*
|
||||
* iii. Get a milestone
|
||||
* https://docs.github.com/en/rest/reference/issues#get-a-milestone
|
||||
*/
|
||||
get("/api/v3/repos/:owner/:repository/milestones/:number")(referrersOnly { repository =>
|
||||
val milestoneId = params("number").toInt // use milestoneId as number
|
||||
(for (apiMilestone <- getApiMilestone(repository, milestoneId)) yield {
|
||||
JsonFormat(apiMilestone)
|
||||
}) getOrElse NotFound()
|
||||
})
|
||||
|
||||
/*
|
||||
* iv.Update a milestone
|
||||
* https://docs.github.com/en/rest/reference/issues#update-a-milestone
|
||||
*/
|
||||
patch("/api/v3/repos/:owner/:repository/milestones/:number")(writableUsersOnly { repository =>
|
||||
val milestoneId = params("number").toInt
|
||||
(for {
|
||||
data <- extractFromJsonBody[CreateAMilestone] if data.isValid
|
||||
milestone <- getMilestone(repository.owner, repository.name, milestoneId)
|
||||
_ = (data.state, milestone.closedDate) match {
|
||||
case ("open", Some(_)) =>
|
||||
openMilestone(milestone)
|
||||
case ("closed", None) =>
|
||||
closeMilestone(milestone)
|
||||
case _ =>
|
||||
}
|
||||
milestone <- getMilestone(repository.owner, repository.name, milestoneId)
|
||||
_ = updateMilestone(milestone.copy(title = data.title, description = data.description, dueDate = data.due_on))
|
||||
apiMilestone <- getApiMilestone(repository, milestoneId)
|
||||
} yield {
|
||||
JsonFormat(apiMilestone)
|
||||
}) getOrElse NotFound()
|
||||
})
|
||||
|
||||
/*
|
||||
* v. Delete a milestone
|
||||
* https://docs.github.com/en/rest/reference/issues#delete-a-milestone
|
||||
*/
|
||||
delete("/api/v3/repos/:owner/:repository/milestones/:number")(writableUsersOnly { repository =>
|
||||
val milestoneId = params("number").toInt // use milestoneId as number
|
||||
deleteMilestone(repository.owner, repository.name, milestoneId)
|
||||
NoContent()
|
||||
})
|
||||
|
||||
private def getApiMilestone(repository: RepositoryInfo, milestoneId: Int): Option[ApiMilestone] = {
|
||||
getMilestonesWithIssueCount(repository.owner, repository.name)
|
||||
.find(p => p._1.milestoneId == milestoneId)
|
||||
.map(
|
||||
milestoneWithIssue =>
|
||||
ApiMilestone(
|
||||
repository.repository,
|
||||
milestoneWithIssue._1,
|
||||
milestoneWithIssue._2,
|
||||
milestoneWithIssue._3
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,7 @@ import gitbucket.core.util.Implicits._
|
||||
import gitbucket.core.util.JGitUtil.CommitInfo
|
||||
import gitbucket.core.util._
|
||||
import org.eclipse.jgit.api.Git
|
||||
import org.scalatra.NoContent
|
||||
import org.scalatra.{Conflict, MethodNotAllowed, NoContent, Ok}
|
||||
import scala.util.Using
|
||||
|
||||
import scala.jdk.CollectionConverters._
|
||||
@@ -114,7 +114,7 @@ trait ApiPullRequestControllerBase extends ControllerBase {
|
||||
requestBranch = reqBranch,
|
||||
commitIdFrom = commitIdFrom.getName,
|
||||
commitIdTo = commitIdTo.getName,
|
||||
isDraft = false,
|
||||
isDraft = createPullReq.draft.getOrElse(false),
|
||||
loginAccount = context.loginAccount.get,
|
||||
settings = context.settings
|
||||
)
|
||||
@@ -161,8 +161,28 @@ trait ApiPullRequestControllerBase extends ControllerBase {
|
||||
|
||||
/*
|
||||
* v. Update a pull request
|
||||
* https://developer.github.com/v3/pulls/#update-a-pull-request
|
||||
* https://docs.github.com/en/rest/reference/pulls#update-a-pull-request
|
||||
*/
|
||||
patch("/api/v3/repos/:owner/:repository/pulls/:id")(referrersOnly { repository =>
|
||||
(for {
|
||||
issueId <- params("id").toIntOpt
|
||||
account <- context.loginAccount
|
||||
settings = context.settings
|
||||
data <- extractFromJsonBody[UpdateAPullRequest]
|
||||
} yield {
|
||||
updatePullRequestsByApi(
|
||||
repository,
|
||||
issueId,
|
||||
account,
|
||||
settings,
|
||||
data.title,
|
||||
data.body,
|
||||
data.state,
|
||||
data.base
|
||||
)
|
||||
JsonFormat(getApiPullRequest(repository, issueId))
|
||||
}) getOrElse NotFound()
|
||||
})
|
||||
|
||||
/*
|
||||
* vi. List commits on a pull request
|
||||
@@ -217,8 +237,72 @@ trait ApiPullRequestControllerBase extends ControllerBase {
|
||||
|
||||
/*
|
||||
* ix. Merge a pull request (Merge Button)
|
||||
* https://developer.github.com/v3/pulls/#merge-a-pull-request-merge-button
|
||||
* https://docs.github.com/en/rest/reference/pulls#merge-a-pull-request
|
||||
*/
|
||||
put("/api/v3/repos/:owner/:repository/pulls/:id/merge")(referrersOnly { repository =>
|
||||
(for {
|
||||
//TODO: crash when body is empty
|
||||
//TODO: Implement sha parameter
|
||||
data <- extractFromJsonBody[MergeAPullRequest]
|
||||
issueId <- params("id").toIntOpt
|
||||
(issue, pullReq) <- getPullRequest(repository.owner, repository.name, issueId)
|
||||
} yield {
|
||||
if (checkConflict(repository.owner, repository.name, pullReq.branch, issueId).isDefined) {
|
||||
Conflict(
|
||||
JsonFormat(
|
||||
FailToMergePrResponse(
|
||||
message = "Head branch was modified. Review and try the merge again.",
|
||||
documentation_url = "https://docs.github.com/en/rest/reference/pulls#merge-a-pull-request",
|
||||
)
|
||||
)
|
||||
)
|
||||
} else {
|
||||
if (issue.closed) {
|
||||
MethodNotAllowed(
|
||||
JsonFormat(
|
||||
FailToMergePrResponse(
|
||||
message = "Pull Request is not mergeable, Closed",
|
||||
documentation_url = "https://docs.github.com/en/rest/reference/pulls#merge-a-pull-request",
|
||||
)
|
||||
)
|
||||
)
|
||||
} else {
|
||||
val strategy =
|
||||
if (data.merge_method.getOrElse("merge-commit") == "merge") "merge-commit"
|
||||
else data.merge_method.getOrElse("merge-commit")
|
||||
mergePullRequest(
|
||||
repository,
|
||||
issueId,
|
||||
context.loginAccount.get,
|
||||
data.commit_message.getOrElse(""), //TODO: Implement commit_title
|
||||
strategy,
|
||||
pullReq.isDraft,
|
||||
context.settings
|
||||
) match {
|
||||
case Right(objectId) =>
|
||||
Ok(
|
||||
JsonFormat(
|
||||
SuccessToMergePrResponse(
|
||||
sha = objectId.toString,
|
||||
merged = true,
|
||||
message = "Pull Request successfully merged"
|
||||
)
|
||||
)
|
||||
)
|
||||
case Left(message) =>
|
||||
MethodNotAllowed(
|
||||
JsonFormat(
|
||||
FailToMergePrResponse(
|
||||
message = "Pull Request is not mergeable",
|
||||
documentation_url = "https://docs.github.com/en/rest/reference/pulls#merge-a-pull-request",
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
/*
|
||||
* x. Labels, assignees, and milestones
|
||||
|
||||
@@ -7,6 +7,8 @@ import gitbucket.core.util.Directory._
|
||||
import gitbucket.core.util.Implicits._
|
||||
import gitbucket.core.util.JGitUtil.getBranches
|
||||
import org.eclipse.jgit.api.Git
|
||||
import org.scalatra.NoContent
|
||||
|
||||
import scala.util.Using
|
||||
|
||||
trait ApiRepositoryBranchControllerBase extends ControllerBase {
|
||||
@@ -22,7 +24,7 @@ trait ApiRepositoryBranchControllerBase extends ControllerBase {
|
||||
|
||||
/**
|
||||
* i. List branches
|
||||
* https://developer.github.com/v3/repos/branches/#list-branches
|
||||
* https://docs.github.com/en/rest/reference/repos#list-branches
|
||||
*/
|
||||
get("/api/v3/repos/:owner/:repository/branches")(referrersOnly { repository =>
|
||||
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
|
||||
@@ -41,8 +43,8 @@ trait ApiRepositoryBranchControllerBase extends ControllerBase {
|
||||
})
|
||||
|
||||
/**
|
||||
* ii. Get branch
|
||||
* https://developer.github.com/v3/repos/branches/#get-branch
|
||||
* ii. Get a branch
|
||||
* https://docs.github.com/en/rest/reference/repos#get-a-branch
|
||||
*/
|
||||
get("/api/v3/repos/:owner/:repository/branches/*")(referrersOnly { repository =>
|
||||
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) {
|
||||
@@ -65,147 +67,206 @@ trait ApiRepositoryBranchControllerBase extends ControllerBase {
|
||||
|
||||
/*
|
||||
* iii. Get branch protection
|
||||
* https://developer.github.com/v3/repos/branches/#get-branch-protection
|
||||
* https://docs.github.com/en/rest/reference/repos#get-branch-protection
|
||||
*/
|
||||
get("/api/v3/repos/:owner/:repository/branches/:branch/protection")(referrersOnly { repository =>
|
||||
val branch = params("branch")
|
||||
if (repository.branchList.contains(branch)) {
|
||||
val protection = getProtectedBranchInfo(repository.owner, repository.name, branch)
|
||||
JsonFormat(
|
||||
ApiBranchProtection(protection)
|
||||
)
|
||||
} else { NotFound() }
|
||||
})
|
||||
|
||||
/*
|
||||
* iv. Update branch protection
|
||||
* https://developer.github.com/v3/repos/branches/#update-branch-protection
|
||||
* https://docs.github.com/en/rest/reference/repos#update-branch-protection
|
||||
*/
|
||||
|
||||
/*
|
||||
* v. Remove branch protection
|
||||
* https://developer.github.com/v3/repos/branches/#remove-branch-protection
|
||||
* v. Delete branch protection
|
||||
* https://docs.github.com/en/rest/reference/repos#delete-branch-protection
|
||||
*/
|
||||
delete("/api/v3/repos/:owner/:repository/branches/:branch/protection")(writableUsersOnly { repository =>
|
||||
val branch = params("branch")
|
||||
if (repository.branchList.contains(branch)) {
|
||||
val protection = getProtectedBranchInfo(repository.owner, repository.name, branch)
|
||||
if (protection.enabled) {
|
||||
disableBranchProtection(repository.owner, repository.name, branch)
|
||||
NoContent()
|
||||
} else NotFound()
|
||||
} else NotFound()
|
||||
})
|
||||
|
||||
/*
|
||||
* vi. Get admin branch protection
|
||||
* https://docs.github.com/en/rest/reference/repos#get-admin-branch-protection
|
||||
*/
|
||||
|
||||
/*
|
||||
* vi. Get required status checks of protected branch
|
||||
* https://developer.github.com/v3/repos/branches/#get-required-status-checks-of-protected-branch
|
||||
* vii. Set admin branch protection
|
||||
* https://docs.github.com/en/rest/reference/repos#set-admin-branch-protection
|
||||
*/
|
||||
|
||||
/*
|
||||
* vii. Update required status checks of protected branch
|
||||
* https://developer.github.com/v3/repos/branches/#update-required-status-checks-of-protected-branch
|
||||
* viii. Delete admin branch protection
|
||||
* https://docs.github.com/en/rest/reference/repos#delete-admin-branch-protection
|
||||
*/
|
||||
|
||||
/*
|
||||
* viii. Remove required status checks of protected branch
|
||||
* https://developer.github.com/v3/repos/branches/#remove-required-status-checks-of-protected-branch
|
||||
* ix. Get pull request review protection
|
||||
* https://docs.github.com/en/rest/reference/repos#get-pull-request-review-protection
|
||||
*/
|
||||
|
||||
/*
|
||||
* ix. List required status checks contexts of protected branch
|
||||
* https://developer.github.com/v3/repos/branches/#list-required-status-checks-contexts-of-protected-branch
|
||||
* x. Update pull request review protection
|
||||
* https://docs.github.com/en/rest/reference/repos#update-pull-request-review-protection
|
||||
*/
|
||||
|
||||
/*
|
||||
* x. Replace required status checks contexts of protected branch
|
||||
* https://developer.github.com/v3/repos/branches/#replace-required-status-checks-contexts-of-protected-branch
|
||||
* xi. Delete pull request review protection
|
||||
* https://docs.github.com/en/rest/reference/repos#delete-pull-request-review-protection
|
||||
*/
|
||||
|
||||
/*
|
||||
* xi. Add required status checks contexts of protected branch
|
||||
* https://developer.github.com/v3/repos/branches/#remove-required-status-checks-contexts-of-protected-branch
|
||||
* xii. Get commit signature protection
|
||||
* https://docs.github.com/en/rest/reference/repos#get-commit-signature-protection
|
||||
*/
|
||||
|
||||
/*
|
||||
* xii. Remove required status checks contexts of protected branch
|
||||
* https://developer.github.com/v3/repos/branches/#remove-required-status-checks-contexts-of-protected-branch
|
||||
* xiii. Create commit signature protection
|
||||
* https://docs.github.com/en/rest/reference/repos#create-commit-signature-protection
|
||||
*/
|
||||
|
||||
/*
|
||||
* xiii. Get pull request review enforcement of protected branch
|
||||
* https://developer.github.com/v3/repos/branches/#get-pull-request-review-enforcement-of-protected-branch
|
||||
* xiv. Delete commit signature protection
|
||||
* https://docs.github.com/en/rest/reference/repos#delete-commit-signature-protection
|
||||
*/
|
||||
|
||||
/*
|
||||
* xiv. Update pull request review enforcement of protected branch
|
||||
* https://developer.github.com/v3/repos/branches/#update-pull-request-review-enforcement-of-protected-branch
|
||||
* xv. Get status checks protection
|
||||
* https://docs.github.com/en/rest/reference/repos#get-status-checks-protection
|
||||
*/
|
||||
get("/api/v3/repos/:owner/:repository/branches/:branch/protection/required_status_checks")(referrersOnly {
|
||||
repository =>
|
||||
val branch = params("branch")
|
||||
if (repository.branchList.contains(branch)) {
|
||||
val protection = getProtectedBranchInfo(repository.owner, repository.name, branch)
|
||||
JsonFormat(
|
||||
ApiBranchProtection(protection).required_status_checks
|
||||
)
|
||||
} else { NotFound() }
|
||||
})
|
||||
|
||||
/*
|
||||
* xvi. Update status check protection
|
||||
* https://docs.github.com/en/rest/reference/repos#update-status-check-protection
|
||||
*/
|
||||
|
||||
/*
|
||||
* xv. Remove pull request review enforcement of protected branch
|
||||
* https://developer.github.com/v3/repos/branches/#remove-pull-request-review-enforcement-of-protected-branch
|
||||
* xvii. Remove status check protection
|
||||
* https://docs.github.com/en/rest/reference/repos#remove-status-check-protection
|
||||
*/
|
||||
|
||||
/*
|
||||
* xvi. Get required signatures of protected branch
|
||||
* https://developer.github.com/v3/repos/branches/#get-required-signatures-of-protected-branch
|
||||
* xviii. Get all status check contexts
|
||||
* https://docs.github.com/en/free-pro-team@latest/rest/reference/repos#get-all-status-check-contexts
|
||||
*/
|
||||
get("/api/v3/repos/:owner/:repository/branches/:branch/protection/required_status_checks/contexts")(referrersOnly {
|
||||
repository =>
|
||||
val branch = params("branch")
|
||||
if (repository.branchList.contains(branch)) {
|
||||
val protection = getProtectedBranchInfo(repository.owner, repository.name, branch)
|
||||
if (protection.enabled) {
|
||||
protection.contexts.toList
|
||||
} else NotFound()
|
||||
} else NotFound()
|
||||
})
|
||||
|
||||
/*
|
||||
* xix. Add status check contexts
|
||||
* https://docs.github.com/en/rest/reference/repos#add-status-check-contexts
|
||||
*/
|
||||
|
||||
/*
|
||||
* xvii. Add required signatures of protected branch
|
||||
* https://developer.github.com/v3/repos/branches/#add-required-signatures-of-protected-branch
|
||||
* xx. Set status check contexts
|
||||
* https://docs.github.com/en/rest/reference/repos#set-status-check-contexts
|
||||
*/
|
||||
|
||||
/*
|
||||
* xviii. Remove required signatures of protected branch
|
||||
* https://developer.github.com/v3/repos/branches/#remove-required-signatures-of-protected-branch
|
||||
* xxi. Remove status check contexts
|
||||
* https://docs.github.com/en/rest/reference/repos#remove-status-check-contexts
|
||||
*/
|
||||
|
||||
/*
|
||||
* xix. Get admin enforcement of protected branch
|
||||
* https://developer.github.com/v3/repos/branches/#get-admin-enforcement-of-protected-branch
|
||||
* xxii. Get access restrictions
|
||||
* https://docs.github.com/en/rest/reference/repos#get-access-restrictions
|
||||
*/
|
||||
|
||||
/*
|
||||
* xx. Add admin enforcement of protected branch
|
||||
* https://developer.github.com/v3/repos/branches/#add-admin-enforcement-of-protected-branch
|
||||
* xxiii. Delete access restrictions
|
||||
* https://docs.github.com/en/rest/reference/repos#delete-access-restrictions
|
||||
*/
|
||||
|
||||
/*
|
||||
* xxi. Remove admin enforcement of protected branch
|
||||
* https://developer.github.com/v3/repos/branches/#remove-admin-enforcement-of-protected-branch
|
||||
* xxiv. Get apps with access to the protected branch
|
||||
* https://docs.github.com/en/rest/reference/repos#get-apps-with-access-to-the-protected-branch
|
||||
*/
|
||||
|
||||
/*
|
||||
* xxii. Get restrictions of protected branch
|
||||
* https://developer.github.com/v3/repos/branches/#get-restrictions-of-protected-branch
|
||||
* xxv. Add app access restrictions
|
||||
* https://docs.github.com/en/rest/reference/repos#add-app-access-restrictions
|
||||
*/
|
||||
|
||||
/*
|
||||
* xxiii. Remove restrictions of protected branch
|
||||
* https://developer.github.com/v3/repos/branches/#remove-restrictions-of-protected-branch
|
||||
* xxvi. Set app access restrictions
|
||||
* https://docs.github.com/en/rest/reference/repos#set-app-access-restrictions
|
||||
*/
|
||||
|
||||
/*
|
||||
* xxiv. List team restrictions of protected branch
|
||||
* https://developer.github.com/v3/repos/branches/#list-team-restrictions-of-protected-branch
|
||||
* xxvii. Remove app access restrictions
|
||||
* https://docs.github.com/en/rest/reference/repos#remove-app-access-restrictions
|
||||
*/
|
||||
|
||||
/*
|
||||
* xxv. Replace team restrictions of protected branch
|
||||
* https://developer.github.com/v3/repos/branches/#replace-team-restrictions-of-protected-branch
|
||||
* xxviii. Get teams with access to the protected branch
|
||||
* https://docs.github.com/en/rest/reference/repos#get-teams-with-access-to-the-protected-branch
|
||||
*/
|
||||
|
||||
/*
|
||||
* xxvi. Add team restrictions of protected branch
|
||||
* https://developer.github.com/v3/repos/branches/#add-team-restrictions-of-protected-branch
|
||||
* xxix. Add team access restrictions
|
||||
* https://docs.github.com/en/rest/reference/repos#add-team-access-restrictions
|
||||
*/
|
||||
|
||||
/*
|
||||
* xxvii. Remove team restrictions of protected branch
|
||||
* https://developer.github.com/v3/repos/branches/#remove-team-restrictions-of-protected-branch
|
||||
* xxx. Set team access restrictions
|
||||
* https://docs.github.com/en/rest/reference/repos#set-team-access-restrictions
|
||||
*/
|
||||
|
||||
/*
|
||||
* xxviii. List user restrictions of protected branch
|
||||
* https://developer.github.com/v3/repos/branches/#list-user-restrictions-of-protected-branch
|
||||
* xxxi. Remove team access restrictions
|
||||
* https://docs.github.com/en/rest/reference/repos#remove-team-access-restrictions
|
||||
*/
|
||||
|
||||
/*
|
||||
* xxix. Replace user restrictions of protected branch
|
||||
* https://developer.github.com/v3/repos/branches/#replace-user-restrictions-of-protected-branch
|
||||
* xxxii. Get users with access to the protected branch
|
||||
* https://docs.github.com/en/rest/reference/repos#get-users-with-access-to-the-protected-branch
|
||||
*/
|
||||
|
||||
/*
|
||||
* xxx. Add user restrictions of protected branch
|
||||
* https://developer.github.com/v3/repos/branches/#add-user-restrictions-of-protected-branch
|
||||
* xxxiii. Add user access restrictions
|
||||
* https://docs.github.com/en/rest/reference/repos#add-user-access-restrictions
|
||||
*/
|
||||
|
||||
/*
|
||||
* xxxi. Remove user restrictions of protected branch
|
||||
* https://developer.github.com/v3/repos/branches/#remove-user-restrictions-of-protected-branch
|
||||
* xxxiv. Set user access restrictions
|
||||
* https://docs.github.com/en/rest/reference/repos#set-user-access-restrictions
|
||||
*/
|
||||
|
||||
/*
|
||||
* xxxv. Remove user access restrictions
|
||||
* https://docs.github.com/en/rest/reference/repos#remove-user-access-restrictions
|
||||
*/
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
package gitbucket.core.controller.api
|
||||
import gitbucket.core.api.{AddACollaborator, ApiUser, JsonFormat}
|
||||
import gitbucket.core.api.{AddACollaborator, ApiRepositoryCollaborator, ApiUser, JsonFormat}
|
||||
import gitbucket.core.controller.ControllerBase
|
||||
import gitbucket.core.service.{AccountService, RepositoryService}
|
||||
import gitbucket.core.util.Implicits._
|
||||
@@ -10,8 +10,8 @@ trait ApiRepositoryCollaboratorControllerBase extends ControllerBase {
|
||||
self: RepositoryService with AccountService with ReferrerAuthenticator with OwnerAuthenticator =>
|
||||
|
||||
/*
|
||||
* i. List collaborators
|
||||
* https://developer.github.com/v3/repos/collaborators/#list-collaborators
|
||||
* i. List repository collaborators
|
||||
* https://docs.github.com/en/free-pro-team@latest/rest/reference/repos#list-repository-collaborators
|
||||
*/
|
||||
get("/api/v3/repos/:owner/:repository/collaborators")(referrersOnly { repository =>
|
||||
// TODO Should ApiUser take permission? getCollaboratorUserNames does not return owner group members.
|
||||
@@ -19,19 +19,40 @@ trait ApiRepositoryCollaboratorControllerBase extends ControllerBase {
|
||||
getCollaboratorUserNames(params("owner"), params("repository")).map(u => ApiUser(getAccountByUserName(u).get))
|
||||
)
|
||||
})
|
||||
|
||||
/*
|
||||
* ii. Check if a user is a collaborator
|
||||
* https://developer.github.com/v3/repos/collaborators/#check-if-a-user-is-a-collaborator
|
||||
* https://docs.github.com/en/free-pro-team@latest/rest/reference/repos#check-if-a-user-is-a-repository-collaborator
|
||||
*/
|
||||
get("/api/v3/repos/:owner/:repository/collaborators/:userName")(referrersOnly { repository =>
|
||||
(for (account <- getAccountByUserName(params("userName"))) yield {
|
||||
if (getCollaboratorUserNames(repository.owner, repository.name).contains(account.userName)) {
|
||||
NoContent()
|
||||
} else {
|
||||
NotFound()
|
||||
}
|
||||
}) getOrElse NotFound()
|
||||
})
|
||||
|
||||
/*
|
||||
* iii. Review a user's permission level
|
||||
* https://developer.github.com/v3/repos/collaborators/#review-a-users-permission-level
|
||||
* iii. Get repository permissions for a user
|
||||
* https://docs.github.com/en/free-pro-team@latest/rest/reference/repos#get-repository-permissions-for-a-user
|
||||
*/
|
||||
get("/api/v3/repos/:owner/:repository/collaborators/:userName/permission")(referrersOnly { repository =>
|
||||
(for {
|
||||
account <- getAccountByUserName(params("userName"))
|
||||
collaborator <- getCollaborators(repository.owner, repository.name)
|
||||
.find(p => p._1.collaboratorName == account.userName)
|
||||
} yield {
|
||||
JsonFormat(
|
||||
ApiRepositoryCollaborator(collaborator._1.role, ApiUser(account))
|
||||
)
|
||||
}) getOrElse NotFound()
|
||||
})
|
||||
|
||||
/*
|
||||
* iv. Add user as a collaborator
|
||||
* https://developer.github.com/v3/repos/collaborators/#add-user-as-a-collaborator
|
||||
* iv. Add a repository collaborator
|
||||
* https://docs.github.com/en/free-pro-team@latest/rest/reference/repos#add-a-repository-collaborator
|
||||
* requested #1586
|
||||
*/
|
||||
put("/api/v3/repos/:owner/:repository/collaborators/:userName")(ownerOnly { repository =>
|
||||
@@ -44,8 +65,8 @@ trait ApiRepositoryCollaboratorControllerBase extends ControllerBase {
|
||||
})
|
||||
|
||||
/*
|
||||
* v. Remove user as a collaborator
|
||||
* https://developer.github.com/v3/repos/collaborators/#remove-user-as-a-collaborator
|
||||
* v. Remove a repository collaborator
|
||||
* https://docs.github.com/en/free-pro-team@latest/rest/reference/repos#remove-a-repository-collaborator
|
||||
* requested #1586
|
||||
*/
|
||||
delete("/api/v3/repos/:owner/:repository/collaborators/:userName")(ownerOnly { repository =>
|
||||
|
||||
@@ -1,22 +1,51 @@
|
||||
package gitbucket.core.controller.api
|
||||
import gitbucket.core.api.{ApiCommits, JsonFormat}
|
||||
import gitbucket.core.api.{ApiBranchCommit, ApiBranchForHeadCommit, ApiCommits, JsonFormat}
|
||||
import gitbucket.core.controller.ControllerBase
|
||||
import gitbucket.core.model.Account
|
||||
import gitbucket.core.service.{AccountService, CommitsService}
|
||||
import gitbucket.core.service.{AccountService, CommitsService, ProtectedBranchService}
|
||||
import gitbucket.core.util.Directory.getRepositoryDir
|
||||
import gitbucket.core.util.Implicits._
|
||||
import gitbucket.core.util.JGitUtil.CommitInfo
|
||||
import gitbucket.core.util.JGitUtil.{CommitInfo, getBranches, getBranchesOfCommit}
|
||||
import gitbucket.core.util.{JGitUtil, ReferrerAuthenticator, RepositoryName}
|
||||
import org.eclipse.jgit.api.Git
|
||||
import org.eclipse.jgit.revwalk.RevWalk
|
||||
|
||||
import scala.jdk.CollectionConverters._
|
||||
import scala.util.Using
|
||||
|
||||
trait ApiRepositoryCommitControllerBase extends ControllerBase {
|
||||
self: AccountService with CommitsService with ReferrerAuthenticator =>
|
||||
self: AccountService with CommitsService with ProtectedBranchService with ReferrerAuthenticator =>
|
||||
/*
|
||||
* i. List commits on a repository
|
||||
* https://developer.github.com/v3/repos/commits/#list-commits-on-a-repository
|
||||
*/
|
||||
get("/api/v3/repos/:owner/:repository/commits")(referrersOnly { repository =>
|
||||
val owner = repository.owner
|
||||
val name = repository.name
|
||||
// TODO: The following parameters need to be implemented. [:path, :author, :since, :until]
|
||||
val sha = params.getOrElse("sha", "refs/heads/master")
|
||||
Using.resource(Git.open(getRepositoryDir(owner, name))) {
|
||||
git =>
|
||||
val repo = git.getRepository
|
||||
Using.resource(new RevWalk(repo)) {
|
||||
revWalk =>
|
||||
val objectId = repo.resolve(sha)
|
||||
revWalk.markStart(revWalk.parseCommit(objectId))
|
||||
JsonFormat(revWalk.asScala.take(30).map {
|
||||
commit =>
|
||||
val commitInfo = new CommitInfo(commit)
|
||||
ApiCommits(
|
||||
repositoryName = RepositoryName(repository),
|
||||
commitInfo = commitInfo,
|
||||
diffs = JGitUtil.getDiffs(git, commitInfo.parents.headOption, commitInfo.id, false, true),
|
||||
author = getAccount(commitInfo.authorName, commitInfo.authorEmailAddress),
|
||||
committer = getAccount(commitInfo.committerName, commitInfo.committerEmailAddress),
|
||||
commentCount = getCommitComment(repository.owner, repository.name, commitInfo.id.toString).size
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
/*
|
||||
* ii. Get a single commit
|
||||
@@ -79,7 +108,25 @@ trait ApiRepositoryCommitControllerBase extends ControllerBase {
|
||||
*/
|
||||
|
||||
/*
|
||||
* v. Commit signature verification
|
||||
* https://developer.github.com/v3/repos/commits/#commit-signature-verification
|
||||
*/
|
||||
* v. Commit signature verification
|
||||
* https://developer.github.com/v3/repos/commits/#commit-signature-verification
|
||||
*/
|
||||
|
||||
/*
|
||||
* vi. List branches for HEAD commit
|
||||
* https://docs.github.com/en/rest/reference/repos#list-branches-for-head-commit
|
||||
*/
|
||||
get("/api/v3/repos/:owner/:repository/commits/:sha/branches-where-head")(referrersOnly { repository =>
|
||||
val sha = params("sha")
|
||||
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
|
||||
val apiBranchForCommits = for {
|
||||
branch <- getBranchesOfCommit(git, sha)
|
||||
br <- getBranches(git, branch, repository.repository.originUserName.isEmpty).find(_.name == branch)
|
||||
} yield {
|
||||
val protection = getProtectedBranchInfo(repository.owner, repository.name, branch)
|
||||
ApiBranchForHeadCommit(branch, ApiBranchCommit(br.commitId), protection.enabled)
|
||||
}
|
||||
JsonFormat(apiBranchForCommits)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package gitbucket.core.controller.api
|
||||
import gitbucket.core.api.{ApiContents, ApiError, CreateAFile, JsonFormat}
|
||||
import gitbucket.core.controller.ControllerBase
|
||||
import gitbucket.core.plugin.PluginRegistry
|
||||
import gitbucket.core.service.{RepositoryCommitFileService, RepositoryService}
|
||||
import gitbucket.core.util.Directory.getRepositoryDir
|
||||
import gitbucket.core.util.JGitUtil.{FileInfo, getContentFromId, getFileList}
|
||||
@@ -8,15 +9,30 @@ import gitbucket.core.util._
|
||||
import gitbucket.core.view.helpers.{isRenderable, renderMarkup}
|
||||
import gitbucket.core.util.Implicits._
|
||||
import org.eclipse.jgit.api.Git
|
||||
|
||||
import scala.util.Using
|
||||
|
||||
trait ApiRepositoryContentsControllerBase extends ControllerBase {
|
||||
self: ReferrerAuthenticator with WritableUsersAuthenticator with RepositoryCommitFileService =>
|
||||
|
||||
/*
|
||||
* i. Get the README
|
||||
* https://developer.github.com/v3/repos/contents/#get-the-readme
|
||||
/**
|
||||
* i. Get a repository README
|
||||
* https://docs.github.com/en/rest/reference/repos#get-a-repository-readme
|
||||
*/
|
||||
get("/api/v3/repos/:owner/:repository/readme")(referrersOnly { repository =>
|
||||
Using.resource(Git.open(getRepositoryDir(params("owner"), params("repository")))) {
|
||||
git =>
|
||||
val refStr = params.getOrElse("ref", repository.repository.defaultBranch)
|
||||
val files = getFileList(git, refStr, ".", maxFiles = context.settings.repositoryViewer.maxFiles)
|
||||
files // files should be sorted alphabetically.
|
||||
.find { file =>
|
||||
!file.isDirectory && RepositoryService.readmeFiles.contains(file.name.toLowerCase)
|
||||
} match {
|
||||
case Some(x) => getContents(repository = repository, path = x.name, refStr = refStr, ignoreCase = true)
|
||||
case _ => NotFound()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* ii. Get contents
|
||||
@@ -34,21 +50,32 @@ trait ApiRepositoryContentsControllerBase extends ControllerBase {
|
||||
getContents(repository, multiParams("splat").head, params.getOrElse("ref", repository.repository.defaultBranch))
|
||||
})
|
||||
|
||||
private def getContents(repository: RepositoryService.RepositoryInfo, path: String, refStr: String) = {
|
||||
def getFileInfo(git: Git, revision: String, pathStr: String): Option[FileInfo] = {
|
||||
private def getContents(
|
||||
repository: RepositoryService.RepositoryInfo,
|
||||
path: String,
|
||||
refStr: String,
|
||||
ignoreCase: Boolean = false
|
||||
) = {
|
||||
def getFileInfo(git: Git, revision: String, pathStr: String, ignoreCase: Boolean): Option[FileInfo] = {
|
||||
val (dirName, fileName) = pathStr.lastIndexOf('/') match {
|
||||
case -1 =>
|
||||
(".", pathStr)
|
||||
case n =>
|
||||
(pathStr.take(n), pathStr.drop(n + 1))
|
||||
}
|
||||
getFileList(git, revision, dirName).find(f => f.name.equals(fileName))
|
||||
if (ignoreCase) {
|
||||
getFileList(git, revision, dirName, maxFiles = context.settings.repositoryViewer.maxFiles)
|
||||
.find(_.name.toLowerCase.equals(fileName.toLowerCase))
|
||||
} else {
|
||||
getFileList(git, revision, dirName, maxFiles = context.settings.repositoryViewer.maxFiles)
|
||||
.find(_.name.equals(fileName))
|
||||
}
|
||||
}
|
||||
|
||||
Using.resource(Git.open(getRepositoryDir(params("owner"), params("repository")))) { git =>
|
||||
val fileList = getFileList(git, refStr, path)
|
||||
val fileList = getFileList(git, refStr, path, maxFiles = context.settings.repositoryViewer.maxFiles)
|
||||
if (fileList.isEmpty) { // file or NotFound
|
||||
getFileInfo(git, refStr, path)
|
||||
getFileInfo(git, refStr, path, ignoreCase)
|
||||
.flatMap { f =>
|
||||
val largeFile = params.get("large_file").exists(s => s.equals("true"))
|
||||
val content = getContentFromId(git, f.id, largeFile)
|
||||
|
||||
@@ -54,11 +54,15 @@ trait ApiRepositoryControllerBase extends ControllerBase {
|
||||
})
|
||||
}
|
||||
|
||||
/*
|
||||
/**
|
||||
* iv. List all public repositories
|
||||
* https://developer.github.com/v3/repos/#list-all-public-repositories
|
||||
* Not implemented
|
||||
* https://developer.github.com/v3/repos/#list-public-repositories
|
||||
*/
|
||||
get("/api/v3/repositories") {
|
||||
JsonFormat(getPublicRepositories().map { r =>
|
||||
ApiRepository(r, getAccountByUserName(r.owner).get)
|
||||
})
|
||||
}
|
||||
|
||||
/*
|
||||
* v. Create
|
||||
@@ -174,9 +178,14 @@ trait ApiRepositoryControllerBase extends ControllerBase {
|
||||
*/
|
||||
|
||||
/*
|
||||
* xiii. List tags
|
||||
* https://developer.github.com/v3/repos/#list-tags
|
||||
* xiii. List repository tags
|
||||
* https://docs.github.com/en/rest/reference/repos#list-repository-tags
|
||||
*/
|
||||
get("/api/v3/repos/:owner/:repository/tags")(referrersOnly { repository =>
|
||||
JsonFormat(
|
||||
repository.tags.map(tagInfo => ApiTag(tagInfo.name, RepositoryName(repository), tagInfo.id))
|
||||
)
|
||||
})
|
||||
|
||||
/*
|
||||
* xiv. Delete a repository
|
||||
|
||||
@@ -47,7 +47,7 @@ trait ApiRepositoryStatusControllerBase extends ControllerBase {
|
||||
ref <- params.get("ref")
|
||||
sha <- JGitUtil.getShaByRef(repository.owner, repository.name, ref)
|
||||
} yield {
|
||||
JsonFormat(getCommitStatuesWithCreator(repository.owner, repository.name, sha).map {
|
||||
JsonFormat(getCommitStatusesWithCreator(repository.owner, repository.name, sha).map {
|
||||
case (status, creator) =>
|
||||
ApiCommitStatus(status, ApiUser(creator))
|
||||
})
|
||||
@@ -73,7 +73,7 @@ trait ApiRepositoryStatusControllerBase extends ControllerBase {
|
||||
owner <- getAccountByUserName(repository.owner)
|
||||
sha <- JGitUtil.getShaByRef(repository.owner, repository.name, ref)
|
||||
} yield {
|
||||
val statuses = getCommitStatuesWithCreator(repository.owner, repository.name, sha)
|
||||
val statuses = getCommitStatusesWithCreator(repository.owner, repository.name, sha)
|
||||
JsonFormat(ApiCombinedCommitStatus(sha, statuses, ApiRepository(repository, owner)))
|
||||
}) getOrElse NotFound()
|
||||
})
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
package gitbucket.core.controller.api
|
||||
import gitbucket.core.api._
|
||||
import gitbucket.core.controller.ControllerBase
|
||||
import gitbucket.core.model.{WebHook, WebHookContentType}
|
||||
import gitbucket.core.service.{RepositoryService, WebHookService}
|
||||
import gitbucket.core.util._
|
||||
import gitbucket.core.util.Implicits._
|
||||
import org.scalatra.NoContent
|
||||
|
||||
trait ApiRepositoryWebhookControllerBase extends ControllerBase {
|
||||
self: RepositoryService with WebHookService with ReferrerAuthenticator with WritableUsersAuthenticator =>
|
||||
|
||||
/*
|
||||
* i. List repository webhooks
|
||||
* https://docs.github.com/en/rest/reference/repos#list-repository-webhooks
|
||||
*/
|
||||
get("/api/v3/repos/:owner/:repository/hooks")(referrersOnly { repository =>
|
||||
val apiWebhooks = for {
|
||||
(hook, events) <- getWebHooks(repository.owner, repository.name)
|
||||
} yield {
|
||||
ApiWebhook("Repository", hook, events)
|
||||
}
|
||||
JsonFormat(apiWebhooks)
|
||||
})
|
||||
|
||||
/*
|
||||
* ii. Create a repository webhook
|
||||
* https://docs.github.com/en/rest/reference/repos#create-a-repository-webhook
|
||||
*/
|
||||
post("/api/v3/repos/:owner/:repository/hooks")(writableUsersOnly { repository =>
|
||||
(for {
|
||||
data <- extractFromJsonBody[CreateARepositoryWebhook] if data.isValid
|
||||
ctype = if (data.config.content_type == "form") WebHookContentType.FORM else WebHookContentType.JSON
|
||||
events = data.events.map(p => WebHook.Event.valueOf(p)).toSet
|
||||
} yield {
|
||||
addWebHook(
|
||||
repository.owner,
|
||||
repository.name,
|
||||
data.config.url,
|
||||
events,
|
||||
ctype,
|
||||
data.config.secret
|
||||
)
|
||||
getWebHook(repository.owner, repository.name, data.config.url) match {
|
||||
case Some(createdHook) => JsonFormat(ApiWebhook("Repository", createdHook._1, createdHook._2))
|
||||
case _ =>
|
||||
}
|
||||
}) getOrElse NotFound()
|
||||
})
|
||||
|
||||
/*
|
||||
* iii. Get a repository webhook
|
||||
* https://docs.github.com/en/rest/reference/repos#get-a-repository-webhook
|
||||
*/
|
||||
get("/api/v3/repos/:owner/:repository/hooks/:id")(referrersOnly { repository =>
|
||||
val hookId = params("id").toInt
|
||||
getWebHookById(hookId) match {
|
||||
case Some(hook) => JsonFormat(ApiWebhook("Repository", hook._1, hook._2))
|
||||
case _ => NotFound()
|
||||
}
|
||||
})
|
||||
|
||||
/*
|
||||
* iv. Update a repository webhook
|
||||
* https://docs.github.com/en/rest/reference/repos#update-a-repository-webhook
|
||||
*/
|
||||
patch("/api/v3/repos/:owner/:repository/hooks/:id")(writableUsersOnly { repository =>
|
||||
val hookId = params("id").toInt
|
||||
(for {
|
||||
data <- extractFromJsonBody[UpdateARepositoryWebhook] if data.isValid
|
||||
ctype = data.config.content_type match {
|
||||
case "json" => WebHookContentType.JSON
|
||||
case _ => WebHookContentType.FORM
|
||||
}
|
||||
} yield {
|
||||
val events = (data.events ++ data.add_events)
|
||||
.filterNot(p => data.remove_events.contains(p))
|
||||
.map(p => WebHook.Event.valueOf(p))
|
||||
.toSet
|
||||
updateWebHookByApi(
|
||||
hookId,
|
||||
repository.owner,
|
||||
repository.name,
|
||||
data.config.url,
|
||||
events,
|
||||
ctype,
|
||||
data.config.secret
|
||||
)
|
||||
getWebHookById(hookId) match {
|
||||
case Some(updatedHook) => JsonFormat(ApiWebhook("Repository", updatedHook._1, updatedHook._2))
|
||||
case _ =>
|
||||
}
|
||||
}) getOrElse NotFound()
|
||||
})
|
||||
|
||||
/*
|
||||
* v. Delete a repository webhook
|
||||
* https://docs.github.com/en/rest/reference/repos#delete-a-repository-webhook
|
||||
*/
|
||||
delete("/api/v3/repos/:owner/:repository/hooks/:id")(writableUsersOnly { repository =>
|
||||
val hookId = params("id").toInt
|
||||
getWebHookById(hookId) match {
|
||||
case Some(_) =>
|
||||
deleteWebHookById(params("id").toInt)
|
||||
NoContent()
|
||||
case _ => NotFound()
|
||||
}
|
||||
})
|
||||
|
||||
/*
|
||||
* vi. Ping a repository webhook
|
||||
* https://docs.github.com/en/rest/reference/repos#ping-a-repository-webhook
|
||||
*/
|
||||
|
||||
/*
|
||||
* vi. Test the push repository webhook
|
||||
* https://docs.github.com/en/rest/reference/repos#test-the-push-repository-webhook
|
||||
*/
|
||||
|
||||
}
|
||||
21
src/main/scala/gitbucket/core/model/AccountPreference.scala
Normal file
21
src/main/scala/gitbucket/core/model/AccountPreference.scala
Normal file
@@ -0,0 +1,21 @@
|
||||
package gitbucket.core.model
|
||||
|
||||
trait AccountPreferenceComponent { self: Profile =>
|
||||
import profile.api._
|
||||
|
||||
lazy val AccountPreferences = TableQuery[AccountPreferences]
|
||||
|
||||
class AccountPreferences(tag: Tag) extends Table[AccountPreference](tag, "ACCOUNT_PREFERENCE") {
|
||||
val userName = column[String]("USER_NAME", O PrimaryKey)
|
||||
val highlighterTheme = column[String]("HIGHLIGHTER_THEME")
|
||||
def * =
|
||||
(userName, highlighterTheme) <> (AccountPreference.tupled, AccountPreference.unapply)
|
||||
|
||||
def byPrimaryKey(userName: String): Rep[Boolean] = this.userName === userName.bind
|
||||
}
|
||||
}
|
||||
|
||||
case class AccountPreference(
|
||||
userName: String,
|
||||
highlighterTheme: String = "prettify"
|
||||
)
|
||||
@@ -1,5 +1,9 @@
|
||||
package gitbucket.core.model
|
||||
|
||||
/**
|
||||
* ActivityComponent has been deprecated, but keep it for binary compatibility.
|
||||
*/
|
||||
@deprecated("ActivityComponent has been deprecated, but keep it for binary compatibility.", "4.34.0")
|
||||
trait ActivityComponent extends TemplateComponent { self: Profile =>
|
||||
import profile.api._
|
||||
import self._
|
||||
@@ -7,14 +11,7 @@ trait ActivityComponent extends TemplateComponent { self: Profile =>
|
||||
lazy val Activities = TableQuery[Activities]
|
||||
|
||||
class Activities(tag: Tag) extends Table[Activity](tag, "ACTIVITY") with BasicTemplate {
|
||||
val activityId = column[Int]("ACTIVITY_ID", O AutoInc)
|
||||
val activityUserName = column[String]("ACTIVITY_USER_NAME")
|
||||
val activityType = column[String]("ACTIVITY_TYPE")
|
||||
val message = column[String]("MESSAGE")
|
||||
val additionalInfo = column[String]("ADDITIONAL_INFO")
|
||||
val activityDate = column[java.util.Date]("ACTIVITY_DATE")
|
||||
def * =
|
||||
(userName, repositoryName, activityUserName, activityType, message, additionalInfo.?, activityDate, activityId) <> (Activity.tupled, Activity.unapply)
|
||||
def * = ???
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,5 +23,5 @@ case class Activity(
|
||||
message: String,
|
||||
additionalInfo: Option[String],
|
||||
activityDate: java.util.Date,
|
||||
activityId: Int = 0
|
||||
activityId: String
|
||||
)
|
||||
|
||||
@@ -45,7 +45,7 @@ trait CoreProfile
|
||||
with Profile
|
||||
with AccessTokenComponent
|
||||
with AccountComponent
|
||||
with ActivityComponent
|
||||
with ActivityComponent // ActivityComponent has been deprecated, but keep it for binary compatibility
|
||||
with CollaboratorComponent
|
||||
with CommitCommentComponent
|
||||
with CommitStatusComponent
|
||||
@@ -70,5 +70,6 @@ trait CoreProfile
|
||||
with ReleaseTagComponent
|
||||
with ReleaseAssetComponent
|
||||
with AccountExtraMailAddressComponent
|
||||
with AccountPreferenceComponent
|
||||
|
||||
object Profile extends CoreProfile
|
||||
|
||||
@@ -9,20 +9,25 @@ trait RepositoryWebHookComponent extends TemplateComponent { self: Profile =>
|
||||
lazy val RepositoryWebHooks = TableQuery[RepositoryWebHooks]
|
||||
|
||||
class RepositoryWebHooks(tag: Tag) extends Table[RepositoryWebHook](tag, "WEB_HOOK") with BasicTemplate {
|
||||
val hookId = column[Int]("HOOK_ID", O AutoInc)
|
||||
val url = column[String]("URL")
|
||||
val token = column[Option[String]]("TOKEN")
|
||||
val ctype = column[WebHookContentType]("CTYPE")
|
||||
def * =
|
||||
(userName, repositoryName, url, ctype, token) <> ((RepositoryWebHook.apply _).tupled, RepositoryWebHook.unapply)
|
||||
(userName, repositoryName, hookId, url, ctype, token) <> ((RepositoryWebHook.apply _).tupled, RepositoryWebHook.unapply)
|
||||
|
||||
def byPrimaryKey(owner: String, repository: String, url: String) =
|
||||
def byRepositoryUrl(owner: String, repository: String, url: String) =
|
||||
byRepository(owner, repository) && (this.url === url.bind)
|
||||
|
||||
def byId(id: Int) =
|
||||
(this.hookId === id.bind)
|
||||
}
|
||||
}
|
||||
|
||||
case class RepositoryWebHook(
|
||||
userName: String,
|
||||
repositoryName: String,
|
||||
hookId: Int = 0,
|
||||
url: String,
|
||||
ctype: WebHookContentType,
|
||||
token: Option[String]
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
package gitbucket.core.model.activity
|
||||
|
||||
import gitbucket.core.model.Activity
|
||||
|
||||
trait BaseActivityInfo {
|
||||
|
||||
def toActivity: Activity
|
||||
|
||||
protected def trimInfoString(str: String, maxLen: Int): String =
|
||||
if (str.length > maxLen) s"${str.substring(0, maxLen)}..."
|
||||
else str
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
package gitbucket.core.model.activity
|
||||
|
||||
import java.util.UUID
|
||||
|
||||
import gitbucket.core.model.Activity
|
||||
import gitbucket.core.model.Profile.currentDate
|
||||
import gitbucket.core.util.JGitUtil.CommitInfo
|
||||
|
||||
final case class PushInfo(
|
||||
userName: String,
|
||||
repositoryName: String,
|
||||
activityUserName: String,
|
||||
branchName: String,
|
||||
commits: List[CommitInfo]
|
||||
) extends BaseActivityInfo {
|
||||
|
||||
override def toActivity: Activity =
|
||||
Activity(
|
||||
userName,
|
||||
repositoryName,
|
||||
activityUserName,
|
||||
"push",
|
||||
s"[user:$activityUserName] pushed to [branch:$userName/$repositoryName#$branchName] at [repo:$userName/$repositoryName]",
|
||||
Some(buildCommitSummary(commits)),
|
||||
currentDate,
|
||||
UUID.randomUUID().toString
|
||||
)
|
||||
|
||||
private[this] def buildCommitSummary(commits: List[CommitInfo]): String =
|
||||
commits
|
||||
.take(5)
|
||||
.map(commit => s"${commit.id}:${commit.shortMessage}")
|
||||
.mkString("\n")
|
||||
}
|
||||
|
||||
final case class CreateBranchInfo(
|
||||
userName: String,
|
||||
repositoryName: String,
|
||||
activityUserName: String,
|
||||
branchName: String
|
||||
) extends BaseActivityInfo {
|
||||
|
||||
override def toActivity: Activity =
|
||||
Activity(
|
||||
userName,
|
||||
repositoryName,
|
||||
activityUserName,
|
||||
"create_branch",
|
||||
s"[user:$activityUserName] created branch [branch:$userName/$repositoryName#$branchName] at [repo:$userName/$repositoryName]",
|
||||
None,
|
||||
currentDate,
|
||||
UUID.randomUUID().toString
|
||||
)
|
||||
}
|
||||
|
||||
final case class DeleteBranchInfo(
|
||||
userName: String,
|
||||
repositoryName: String,
|
||||
activityUserName: String,
|
||||
branchName: String
|
||||
) extends BaseActivityInfo {
|
||||
|
||||
override def toActivity: Activity =
|
||||
Activity(
|
||||
userName,
|
||||
repositoryName,
|
||||
activityUserName,
|
||||
"delete_branch",
|
||||
s"[user:$activityUserName] deleted branch $branchName at [repo:$userName/$repositoryName]",
|
||||
None,
|
||||
currentDate,
|
||||
UUID.randomUUID().toString
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package gitbucket.core.model.activity
|
||||
|
||||
import java.util.UUID
|
||||
|
||||
import gitbucket.core.model.Activity
|
||||
import gitbucket.core.model.Profile.currentDate
|
||||
|
||||
final case class IssueCommentInfo(
|
||||
userName: String,
|
||||
repositoryName: String,
|
||||
activityUserName: String,
|
||||
comment: String,
|
||||
issueId: Int
|
||||
) extends BaseActivityInfo {
|
||||
|
||||
override def toActivity: Activity =
|
||||
Activity(
|
||||
userName,
|
||||
repositoryName,
|
||||
activityUserName,
|
||||
"comment_issue",
|
||||
s"[user:${activityUserName}] commented on issue [issue:${userName}/${repositoryName}#${issueId}]",
|
||||
Some(trimInfoString(comment, 200)),
|
||||
currentDate,
|
||||
UUID.randomUUID().toString
|
||||
)
|
||||
}
|
||||
|
||||
final case class PullRequestCommentInfo(
|
||||
userName: String,
|
||||
repositoryName: String,
|
||||
activityUserName: String,
|
||||
comment: String,
|
||||
issueId: Int
|
||||
) extends BaseActivityInfo {
|
||||
|
||||
override def toActivity: Activity =
|
||||
Activity(
|
||||
userName,
|
||||
repositoryName,
|
||||
activityUserName,
|
||||
"comment_issue",
|
||||
s"[user:$activityUserName] commented on pull request [pullreq:$userName/$repositoryName#$issueId]",
|
||||
Some(trimInfoString(comment, 200)),
|
||||
currentDate,
|
||||
UUID.randomUUID().toString
|
||||
)
|
||||
}
|
||||
|
||||
final case class CommitCommentInfo(
|
||||
userName: String,
|
||||
repositoryName: String,
|
||||
activityUserName: String,
|
||||
comment: String,
|
||||
commitId: String
|
||||
) extends BaseActivityInfo {
|
||||
|
||||
override def toActivity: Activity =
|
||||
Activity(
|
||||
userName,
|
||||
repositoryName,
|
||||
activityUserName,
|
||||
"comment_commit",
|
||||
s"[user:$activityUserName] commented on commit [commit:$userName/$repositoryName@$commitId]",
|
||||
Some(trimInfoString(comment, 200)),
|
||||
currentDate,
|
||||
UUID.randomUUID().toString
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package gitbucket.core.model.activity
|
||||
|
||||
import java.util.UUID
|
||||
|
||||
import gitbucket.core.model.Activity
|
||||
import gitbucket.core.model.Profile.currentDate
|
||||
|
||||
final case class ForkInfo(
|
||||
userName: String,
|
||||
repositoryName: String,
|
||||
activityUserName: String,
|
||||
forkedUserName: String
|
||||
) extends BaseActivityInfo {
|
||||
|
||||
override def toActivity: Activity =
|
||||
Activity(
|
||||
userName,
|
||||
repositoryName,
|
||||
activityUserName,
|
||||
"fork",
|
||||
s"[user:$activityUserName] forked [repo:$userName/$repositoryName] to [repo:$forkedUserName/$repositoryName]",
|
||||
None,
|
||||
currentDate,
|
||||
UUID.randomUUID().toString
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
package gitbucket.core.model.activity
|
||||
|
||||
import java.util.UUID
|
||||
|
||||
import gitbucket.core.model.Activity
|
||||
import gitbucket.core.model.Profile.currentDate
|
||||
|
||||
final case class CreateIssueInfo(
|
||||
userName: String,
|
||||
repositoryName: String,
|
||||
activityUserName: String,
|
||||
issueId: Int,
|
||||
title: String
|
||||
) extends BaseActivityInfo {
|
||||
|
||||
override def toActivity: Activity =
|
||||
Activity(
|
||||
userName,
|
||||
repositoryName,
|
||||
activityUserName,
|
||||
"open_issue",
|
||||
s"[user:$activityUserName] opened issue [issue:$userName/$repositoryName#$issueId]",
|
||||
Some(title),
|
||||
currentDate,
|
||||
UUID.randomUUID().toString
|
||||
)
|
||||
}
|
||||
|
||||
final case class CloseIssueInfo(
|
||||
userName: String,
|
||||
repositoryName: String,
|
||||
activityUserName: String,
|
||||
issueId: Int,
|
||||
title: String
|
||||
) extends BaseActivityInfo {
|
||||
|
||||
override def toActivity: Activity =
|
||||
Activity(
|
||||
userName,
|
||||
repositoryName,
|
||||
activityUserName,
|
||||
"close_issue",
|
||||
s"[user:$activityUserName] closed issue [issue:$userName/$repositoryName#$issueId]",
|
||||
Some(title),
|
||||
currentDate,
|
||||
UUID.randomUUID().toString
|
||||
)
|
||||
}
|
||||
|
||||
final case class ReopenIssueInfo(
|
||||
userName: String,
|
||||
repositoryName: String,
|
||||
activityUserName: String,
|
||||
issueId: Int,
|
||||
title: String
|
||||
) extends BaseActivityInfo {
|
||||
|
||||
override def toActivity: Activity =
|
||||
Activity(
|
||||
userName,
|
||||
repositoryName,
|
||||
activityUserName,
|
||||
"reopen_issue",
|
||||
s"[user:$activityUserName] reopened issue [issue:$userName/$repositoryName#$issueId]",
|
||||
Some(title),
|
||||
currentDate,
|
||||
UUID.randomUUID().toString
|
||||
)
|
||||
}
|
||||
|
||||
final case class OpenPullRequestInfo(
|
||||
userName: String,
|
||||
repositoryName: String,
|
||||
activityUserName: String,
|
||||
issueId: Int,
|
||||
title: String
|
||||
) extends BaseActivityInfo {
|
||||
|
||||
override def toActivity: Activity =
|
||||
Activity(
|
||||
userName,
|
||||
repositoryName,
|
||||
activityUserName,
|
||||
"open_pullreq",
|
||||
s"[user:${activityUserName}] opened pull request [pullreq:${userName}/${repositoryName}#${issueId}]",
|
||||
Some(title),
|
||||
currentDate,
|
||||
UUID.randomUUID().toString
|
||||
)
|
||||
}
|
||||
|
||||
final case class ClosePullRequestInfo(
|
||||
userName: String,
|
||||
repositoryName: String,
|
||||
activityUserName: String,
|
||||
issueId: Int,
|
||||
title: String
|
||||
) extends BaseActivityInfo {
|
||||
|
||||
override def toActivity: Activity =
|
||||
Activity(
|
||||
userName,
|
||||
repositoryName,
|
||||
activityUserName,
|
||||
"close_issue",
|
||||
s"[user:$activityUserName] closed pull request [pullreq:$userName/$repositoryName#$issueId]",
|
||||
Some(title),
|
||||
currentDate,
|
||||
UUID.randomUUID().toString
|
||||
)
|
||||
}
|
||||
|
||||
final case class ReopenPullRequestInfo(
|
||||
userName: String,
|
||||
repositoryName: String,
|
||||
activityUserName: String,
|
||||
issueId: Int,
|
||||
title: String
|
||||
) extends BaseActivityInfo {
|
||||
|
||||
override def toActivity: Activity =
|
||||
Activity(
|
||||
userName,
|
||||
repositoryName,
|
||||
activityUserName,
|
||||
"reopen_issue",
|
||||
s"[user:$activityUserName] reopened pull request [issue:$userName/$repositoryName#$issueId]",
|
||||
Some(title),
|
||||
currentDate,
|
||||
UUID.randomUUID().toString
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package gitbucket.core.model.activity
|
||||
|
||||
import java.util.UUID
|
||||
|
||||
import gitbucket.core.model.Activity
|
||||
import gitbucket.core.model.Profile.currentDate
|
||||
|
||||
final case class MergeInfo(
|
||||
userName: String,
|
||||
repositoryName: String,
|
||||
activityUserName: String,
|
||||
issueId: Int,
|
||||
message: String
|
||||
) extends BaseActivityInfo {
|
||||
|
||||
override def toActivity: Activity =
|
||||
Activity(
|
||||
userName,
|
||||
repositoryName,
|
||||
activityUserName,
|
||||
"merge_pullreq",
|
||||
s"[user:$activityUserName] merged pull request [pullreq:$userName/$repositoryName#$issueId]",
|
||||
Some(message),
|
||||
currentDate,
|
||||
UUID.randomUUID().toString
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package gitbucket.core.model.activity
|
||||
|
||||
import java.util.UUID
|
||||
|
||||
import gitbucket.core.model.Activity
|
||||
import gitbucket.core.model.Profile.currentDate
|
||||
|
||||
final case class ReleaseInfo(
|
||||
userName: String,
|
||||
repositoryName: String,
|
||||
activityUserName: String,
|
||||
releaseName: String,
|
||||
tagName: String
|
||||
) extends BaseActivityInfo {
|
||||
|
||||
override def toActivity: Activity =
|
||||
Activity(
|
||||
userName,
|
||||
repositoryName,
|
||||
activityUserName,
|
||||
"release",
|
||||
s"[user:$activityUserName] released [release:$userName/$repositoryName/$tagName:$releaseName] at [repo:$userName/$repositoryName]",
|
||||
None,
|
||||
currentDate,
|
||||
UUID.randomUUID().toString
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
package gitbucket.core.model.activity
|
||||
|
||||
import java.util.UUID
|
||||
|
||||
import gitbucket.core.model.Activity
|
||||
import gitbucket.core.model.Profile.currentDate
|
||||
|
||||
final case class CreateRepositoryInfo(
|
||||
userName: String,
|
||||
repositoryName: String,
|
||||
activityUserName: String
|
||||
) extends BaseActivityInfo {
|
||||
|
||||
override def toActivity: Activity =
|
||||
Activity(
|
||||
userName,
|
||||
repositoryName,
|
||||
activityUserName,
|
||||
"create_repository",
|
||||
s"[user:$activityUserName] created [repo:$userName/$repositoryName]",
|
||||
None,
|
||||
currentDate,
|
||||
UUID.randomUUID().toString
|
||||
)
|
||||
}
|
||||
|
||||
final case class DeleteRepositoryInfo(
|
||||
userName: String,
|
||||
repositoryName: String,
|
||||
activityUserName: String
|
||||
) extends BaseActivityInfo {
|
||||
|
||||
override def toActivity: Activity =
|
||||
Activity(
|
||||
userName,
|
||||
repositoryName,
|
||||
activityUserName,
|
||||
"delete_repository",
|
||||
s"[user:$activityUserName] deleted [repo:$userName/$repositoryName]",
|
||||
None,
|
||||
currentDate,
|
||||
UUID.randomUUID().toString
|
||||
)
|
||||
}
|
||||
|
||||
final case class TransferRepositoryInfo(
|
||||
userName: String,
|
||||
repositoryName: String,
|
||||
activityUserName: String,
|
||||
oldUserName: String
|
||||
) extends BaseActivityInfo {
|
||||
|
||||
override def toActivity: Activity =
|
||||
Activity(
|
||||
userName,
|
||||
repositoryName,
|
||||
activityUserName,
|
||||
"transfer_repository",
|
||||
s"[user:$activityUserName] transferred [repo:$oldUserName/$repositoryName] to [repo:$userName/$repositoryName]",
|
||||
None,
|
||||
currentDate,
|
||||
UUID.randomUUID().toString
|
||||
)
|
||||
}
|
||||
|
||||
final case class RenameRepositoryInfo(
|
||||
userName: String,
|
||||
repositoryName: String,
|
||||
activityUserName: String,
|
||||
oldRepositoryName: String
|
||||
) extends BaseActivityInfo {
|
||||
|
||||
override def toActivity: Activity =
|
||||
Activity(
|
||||
userName,
|
||||
repositoryName,
|
||||
activityUserName,
|
||||
"rename_repository",
|
||||
s"[user:$activityUserName] renamed [repo:$userName/$oldRepositoryName] at [repo:$userName/$repositoryName]",
|
||||
None,
|
||||
currentDate,
|
||||
UUID.randomUUID().toString
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package gitbucket.core.model.activity
|
||||
|
||||
import java.util.UUID
|
||||
|
||||
import gitbucket.core.model.Activity
|
||||
import gitbucket.core.model.Profile.currentDate
|
||||
|
||||
final case class CreateTagInfo(
|
||||
userName: String,
|
||||
repositoryName: String,
|
||||
activityUserName: String,
|
||||
tagName: String,
|
||||
) extends BaseActivityInfo {
|
||||
|
||||
override def toActivity: Activity =
|
||||
Activity(
|
||||
userName,
|
||||
repositoryName,
|
||||
activityUserName,
|
||||
"create_tag",
|
||||
s"[user:$activityUserName] created tag [tag:$userName/$repositoryName#$tagName] at [repo:$userName/$repositoryName]",
|
||||
None,
|
||||
currentDate,
|
||||
UUID.randomUUID().toString
|
||||
)
|
||||
}
|
||||
|
||||
final case class DeleteTagInfo(
|
||||
userName: String,
|
||||
repositoryName: String,
|
||||
activityUserName: String,
|
||||
tagName: String
|
||||
) extends BaseActivityInfo {
|
||||
|
||||
override def toActivity: Activity =
|
||||
Activity(
|
||||
userName,
|
||||
repositoryName,
|
||||
activityUserName,
|
||||
"delete_tag",
|
||||
s"[user:$activityUserName] deleted tag $tagName at [repo:$userName/$repositoryName]",
|
||||
None,
|
||||
currentDate,
|
||||
UUID.randomUUID().toString
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package gitbucket.core.model.activity
|
||||
|
||||
import java.util.UUID
|
||||
|
||||
import gitbucket.core.model.Activity
|
||||
import gitbucket.core.model.Profile.currentDate
|
||||
|
||||
final case class CreateWikiPageInfo(
|
||||
userName: String,
|
||||
repositoryName: String,
|
||||
activityUserName: String,
|
||||
pageName: String
|
||||
) extends BaseActivityInfo {
|
||||
|
||||
override def toActivity: Activity =
|
||||
Activity(
|
||||
userName,
|
||||
repositoryName,
|
||||
activityUserName,
|
||||
"create_wiki",
|
||||
s"[user:$activityUserName] created the [repo:$userName/$repositoryName] wiki",
|
||||
Some(pageName),
|
||||
currentDate,
|
||||
UUID.randomUUID().toString
|
||||
)
|
||||
}
|
||||
|
||||
final case class EditWikiPageInfo(
|
||||
userName: String,
|
||||
repositoryName: String,
|
||||
activityUserName: String,
|
||||
pageName: String,
|
||||
commitId: String
|
||||
) extends BaseActivityInfo {
|
||||
|
||||
override def toActivity: Activity =
|
||||
Activity(
|
||||
userName,
|
||||
repositoryName,
|
||||
activityUserName,
|
||||
"edit_wiki",
|
||||
s"[user:$activityUserName] edited the [repo:$userName/$repositoryName] wiki",
|
||||
Some(s"$pageName:$commitId"),
|
||||
currentDate,
|
||||
UUID.randomUUID().toString
|
||||
)
|
||||
}
|
||||
|
||||
final case class DeleteWikiInfo(
|
||||
userName: String,
|
||||
repositoryName: String,
|
||||
activityUserName: String,
|
||||
pageName: String,
|
||||
) extends BaseActivityInfo {
|
||||
|
||||
override def toActivity: Activity =
|
||||
Activity(
|
||||
userName,
|
||||
repositoryName,
|
||||
activityUserName,
|
||||
"delete_wiki",
|
||||
s"[user:$activityUserName] deleted the page [$pageName] in the [repo:$userName/$repositoryName] wiki",
|
||||
additionalInfo = None,
|
||||
currentDate,
|
||||
UUID.randomUUID().toString
|
||||
)
|
||||
}
|
||||
@@ -13,6 +13,14 @@ trait IssueHook {
|
||||
implicit session: Session,
|
||||
context: Context
|
||||
): Unit = ()
|
||||
def deletedComment(commentId: Int, issue: Issue, repository: RepositoryInfo)(
|
||||
implicit session: Session,
|
||||
context: Context
|
||||
): Unit = ()
|
||||
def updatedComment(commentId: Int, content: String, issue: Issue, repository: RepositoryInfo)(
|
||||
implicit session: Session,
|
||||
context: Context
|
||||
): Unit = ()
|
||||
def closed(issue: Issue, repository: RepositoryInfo)(implicit session: Session, context: Context): Unit = ()
|
||||
def reopened(issue: Issue, repository: RepositoryInfo)(implicit session: Session, context: Context): Unit = ()
|
||||
def assigned(
|
||||
|
||||
@@ -57,6 +57,7 @@ class PluginRegistry {
|
||||
private val textDecorators = new ConcurrentLinkedQueue[TextDecorator]
|
||||
private val suggestionProviders = new ConcurrentLinkedQueue[SuggestionProvider]
|
||||
suggestionProviders.add(new UserNameSuggestionProvider())
|
||||
suggestionProviders.add(new IssueSuggestionProvider())
|
||||
private val sshCommandProviders = new ConcurrentLinkedQueue[PartialFunction[String, Command]]()
|
||||
|
||||
def addPlugin(pluginInfo: PluginInfo): Unit = plugins.add(pluginInfo)
|
||||
|
||||
@@ -6,11 +6,25 @@ import profile.api._
|
||||
|
||||
trait ReceiveHook {
|
||||
|
||||
def preReceive(owner: String, repository: String, receivePack: ReceivePack, command: ReceiveCommand, pusher: String)(
|
||||
def preReceive(
|
||||
owner: String,
|
||||
repository: String,
|
||||
receivePack: ReceivePack,
|
||||
command: ReceiveCommand,
|
||||
pusher: String,
|
||||
mergePullRequest: Boolean
|
||||
)(
|
||||
implicit session: Session
|
||||
): Option[String] = None
|
||||
|
||||
def postReceive(owner: String, repository: String, receivePack: ReceivePack, command: ReceiveCommand, pusher: String)(
|
||||
def postReceive(
|
||||
owner: String,
|
||||
repository: String,
|
||||
receivePack: ReceivePack,
|
||||
command: ReceiveCommand,
|
||||
pusher: String,
|
||||
mergePullRequest: Boolean
|
||||
)(
|
||||
implicit session: Session
|
||||
): Unit = ()
|
||||
|
||||
|
||||
@@ -91,7 +91,7 @@ trait SuggestionProvider {
|
||||
* If this suggestion provider needs some additional process to assemble the proposal list (e.g. It need to use Ajax
|
||||
* to get a proposal list from the server), then override this method and return any JavaScript code.
|
||||
*/
|
||||
def additionalScript(implicit context: Context): String = ""
|
||||
def additionalScript(repository: RepositoryInfo)(implicit context: Context): String = ""
|
||||
|
||||
}
|
||||
|
||||
@@ -99,6 +99,14 @@ class UserNameSuggestionProvider extends SuggestionProvider {
|
||||
override val id: String = "user"
|
||||
override val prefix: String = "@"
|
||||
override val context: Seq[String] = Seq("issues")
|
||||
override def additionalScript(implicit context: Context): String =
|
||||
override def additionalScript(repository: RepositoryInfo)(implicit context: Context): String =
|
||||
s"""$$.get('${context.path}/_user/proposals', { query: '', user: true, group: false }, function (data) { user = data.options; });"""
|
||||
}
|
||||
|
||||
class IssueSuggestionProvider extends SuggestionProvider {
|
||||
override val id: String = "issue"
|
||||
override val prefix: String = "#"
|
||||
override val context: Seq[String] = Seq("issues")
|
||||
override def additionalScript(repository: RepositoryInfo)(implicit context: Context): String =
|
||||
s"""$$.get('${context.path}/${repository.owner}/${repository.name}/_issue/proposals', function (data) { issue = data.options; });"""
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package gitbucket.core.service
|
||||
|
||||
import org.slf4j.LoggerFactory
|
||||
import gitbucket.core.model.{Account, AccountExtraMailAddress, GroupMember}
|
||||
import gitbucket.core.model.{Account, AccountExtraMailAddress, AccountPreference, GroupMember}
|
||||
import gitbucket.core.model.Profile._
|
||||
import gitbucket.core.model.Profile.profile.blockingApi._
|
||||
import gitbucket.core.model.Profile.dateColumnType
|
||||
@@ -17,7 +17,9 @@ trait AccountService {
|
||||
def authenticate(settings: SystemSettings, userName: String, password: String)(
|
||||
implicit s: Session
|
||||
): Option[Account] = {
|
||||
val account = if (settings.ldapAuthentication) {
|
||||
val account = if (password.isEmpty) {
|
||||
None
|
||||
} else if (settings.ldapAuthentication) {
|
||||
ldapAuthentication(settings, userName, password)
|
||||
} else {
|
||||
defaultAuthentication(userName, password)
|
||||
@@ -308,6 +310,33 @@ trait AccountService {
|
||||
Collaborators.filter(_.collaboratorName === userName.bind).sortBy(_.userName).map(_.userName).list.distinct
|
||||
}
|
||||
|
||||
/*
|
||||
* For account preference
|
||||
*/
|
||||
def getAccountPreference(userName: String)(
|
||||
implicit s: Session
|
||||
): Option[AccountPreference] = {
|
||||
AccountPreferences filter (_.byPrimaryKey(userName)) firstOption
|
||||
}
|
||||
|
||||
def addAccountPreference(userName: String, highlighterTheme: String)(implicit s: Session): Unit = {
|
||||
AccountPreferences insert AccountPreference(userName = userName, highlighterTheme = highlighterTheme)
|
||||
}
|
||||
|
||||
def updateAccountPreference(userName: String, highlighterTheme: String)(implicit s: Session): Unit = {
|
||||
AccountPreferences
|
||||
.filter(_.byPrimaryKey(userName))
|
||||
.map(t => t.highlighterTheme)
|
||||
.update(highlighterTheme)
|
||||
}
|
||||
|
||||
def addOrUpdateAccountPreference(userName: String, highlighterTheme: String)(implicit s: Session): Unit = {
|
||||
getAccountPreference(userName) match {
|
||||
case Some(_) => updateAccountPreference(userName, highlighterTheme)
|
||||
case _ => addAccountPreference(userName, highlighterTheme)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
object AccountService extends AccountService
|
||||
|
||||
@@ -3,372 +3,101 @@ package gitbucket.core.service
|
||||
import gitbucket.core.model.Activity
|
||||
import gitbucket.core.util.JGitUtil
|
||||
import gitbucket.core.model.Profile._
|
||||
import gitbucket.core.model.Profile.profile.blockingApi._
|
||||
import gitbucket.core.util.Directory._
|
||||
import org.json4s._
|
||||
import org.json4s.jackson.Serialization
|
||||
import org.json4s.jackson.Serialization.{read, write}
|
||||
|
||||
import scala.util.Using
|
||||
import java.io.FileOutputStream
|
||||
import java.nio.charset.StandardCharsets
|
||||
import java.util.UUID
|
||||
|
||||
import gitbucket.core.controller.Context
|
||||
import gitbucket.core.model.activity.BaseActivityInfo
|
||||
import org.apache.commons.io.input.ReversedLinesFileReader
|
||||
|
||||
import scala.collection.mutable.ListBuffer
|
||||
|
||||
trait ActivityService {
|
||||
self: RequestCache =>
|
||||
|
||||
def deleteOldActivities(limit: Int)(implicit s: Session): Int = {
|
||||
Activities.map(_.activityId).sortBy(_ desc).drop(limit).firstOption.map { id =>
|
||||
Activities.filter(_.activityId <= id.bind).delete
|
||||
} getOrElse 0
|
||||
private implicit val formats = Serialization.formats(NoTypeHints)
|
||||
|
||||
private def writeLog(activity: Activity): Unit = {
|
||||
Using.resource(new FileOutputStream(ActivityLog, true)) { out =>
|
||||
out.write((write(activity) + "\n").getBytes(StandardCharsets.UTF_8))
|
||||
}
|
||||
}
|
||||
|
||||
def getActivitiesByUser(activityUserName: String, isPublic: Boolean)(implicit s: Session): List[Activity] =
|
||||
Activities
|
||||
.join(Repositories)
|
||||
.on((t1, t2) => t1.byRepository(t2.userName, t2.repositoryName))
|
||||
.filter {
|
||||
case (t1, t2) =>
|
||||
if (isPublic) {
|
||||
(t1.activityUserName === activityUserName.bind) && (t2.isPrivate === false.bind)
|
||||
} else {
|
||||
(t1.activityUserName === activityUserName.bind)
|
||||
def getActivitiesByUser(activityUserName: String, isPublic: Boolean)(implicit context: Context): List[Activity] = {
|
||||
if (!ActivityLog.exists()) {
|
||||
List.empty
|
||||
} else {
|
||||
val list = new ListBuffer[Activity]
|
||||
Using.resource(new ReversedLinesFileReader(ActivityLog, StandardCharsets.UTF_8)) { reader =>
|
||||
var json: String = null
|
||||
while (list.length < 50 && { json = reader.readLine(); json } != null) {
|
||||
val activity = read[Activity](json)
|
||||
if (activity.activityUserName == activityUserName) {
|
||||
if (isPublic == false) {
|
||||
list += activity
|
||||
} else {
|
||||
if (!getRepositoryInfoFromCache(activity.userName, activity.repositoryName)
|
||||
.map(_.isPrivate)
|
||||
.getOrElse(true)) {
|
||||
list += activity
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.sortBy { case (t1, t2) => t1.activityId desc }
|
||||
.map { case (t1, t2) => t1 }
|
||||
.take(30)
|
||||
.list
|
||||
list.toList
|
||||
}
|
||||
}
|
||||
|
||||
def getRecentActivities()(implicit s: Session): List[Activity] =
|
||||
Activities
|
||||
.join(Repositories)
|
||||
.on((t1, t2) => t1.byRepository(t2.userName, t2.repositoryName))
|
||||
.filter { case (t1, t2) => t2.isPrivate === false.bind }
|
||||
.sortBy { case (t1, t2) => t1.activityId desc }
|
||||
.map { case (t1, t2) => t1 }
|
||||
.take(30)
|
||||
.list
|
||||
|
||||
def getRecentActivitiesByOwners(owners: Set[String])(implicit s: Session): List[Activity] =
|
||||
Activities
|
||||
.join(Repositories)
|
||||
.on((t1, t2) => t1.byRepository(t2.userName, t2.repositoryName))
|
||||
.filter { case (t1, t2) => (t2.isPrivate === false.bind) || (t2.userName inSetBind owners) }
|
||||
.sortBy { case (t1, t2) => t1.activityId desc }
|
||||
.map { case (t1, t2) => t1 }
|
||||
.take(30)
|
||||
.list
|
||||
|
||||
def recordCreateRepositoryActivity(userName: String, repositoryName: String, activityUserName: String)(
|
||||
implicit s: Session
|
||||
): Unit =
|
||||
Activities insert Activity(
|
||||
userName,
|
||||
repositoryName,
|
||||
activityUserName,
|
||||
"create_repository",
|
||||
s"[user:${activityUserName}] created [repo:${userName}/${repositoryName}]",
|
||||
None,
|
||||
currentDate
|
||||
)
|
||||
|
||||
def recordCreateIssueActivity(
|
||||
userName: String,
|
||||
repositoryName: String,
|
||||
activityUserName: String,
|
||||
issueId: Int,
|
||||
title: String
|
||||
)(implicit s: Session): Unit =
|
||||
Activities insert Activity(
|
||||
userName,
|
||||
repositoryName,
|
||||
activityUserName,
|
||||
"open_issue",
|
||||
s"[user:${activityUserName}] opened issue [issue:${userName}/${repositoryName}#${issueId}]",
|
||||
Some(title),
|
||||
currentDate
|
||||
)
|
||||
|
||||
def recordCloseIssueActivity(
|
||||
userName: String,
|
||||
repositoryName: String,
|
||||
activityUserName: String,
|
||||
issueId: Int,
|
||||
title: String
|
||||
)(implicit s: Session): Unit =
|
||||
Activities insert Activity(
|
||||
userName,
|
||||
repositoryName,
|
||||
activityUserName,
|
||||
"close_issue",
|
||||
s"[user:${activityUserName}] closed issue [issue:${userName}/${repositoryName}#${issueId}]",
|
||||
Some(title),
|
||||
currentDate
|
||||
)
|
||||
|
||||
def recordClosePullRequestActivity(
|
||||
userName: String,
|
||||
repositoryName: String,
|
||||
activityUserName: String,
|
||||
issueId: Int,
|
||||
title: String
|
||||
)(implicit s: Session): Unit =
|
||||
Activities insert Activity(
|
||||
userName,
|
||||
repositoryName,
|
||||
activityUserName,
|
||||
"close_issue",
|
||||
s"[user:${activityUserName}] closed pull request [pullreq:${userName}/${repositoryName}#${issueId}]",
|
||||
Some(title),
|
||||
currentDate
|
||||
)
|
||||
|
||||
def recordReopenIssueActivity(
|
||||
userName: String,
|
||||
repositoryName: String,
|
||||
activityUserName: String,
|
||||
issueId: Int,
|
||||
title: String
|
||||
)(implicit s: Session): Unit =
|
||||
Activities insert Activity(
|
||||
userName,
|
||||
repositoryName,
|
||||
activityUserName,
|
||||
"reopen_issue",
|
||||
s"[user:${activityUserName}] reopened issue [issue:${userName}/${repositoryName}#${issueId}]",
|
||||
Some(title),
|
||||
currentDate
|
||||
)
|
||||
|
||||
def recordCommentIssueActivity(
|
||||
userName: String,
|
||||
repositoryName: String,
|
||||
activityUserName: String,
|
||||
issueId: Int,
|
||||
comment: String
|
||||
)(implicit s: Session): Unit =
|
||||
Activities insert Activity(
|
||||
userName,
|
||||
repositoryName,
|
||||
activityUserName,
|
||||
"comment_issue",
|
||||
s"[user:${activityUserName}] commented on issue [issue:${userName}/${repositoryName}#${issueId}]",
|
||||
Some(cut(comment, 200)),
|
||||
currentDate
|
||||
)
|
||||
|
||||
def recordCommentPullRequestActivity(
|
||||
userName: String,
|
||||
repositoryName: String,
|
||||
activityUserName: String,
|
||||
issueId: Int,
|
||||
comment: String
|
||||
)(implicit s: Session): Unit =
|
||||
Activities insert Activity(
|
||||
userName,
|
||||
repositoryName,
|
||||
activityUserName,
|
||||
"comment_issue",
|
||||
s"[user:${activityUserName}] commented on pull request [pullreq:${userName}/${repositoryName}#${issueId}]",
|
||||
Some(cut(comment, 200)),
|
||||
currentDate
|
||||
)
|
||||
|
||||
def recordCommentCommitActivity(
|
||||
userName: String,
|
||||
repositoryName: String,
|
||||
activityUserName: String,
|
||||
commitId: String,
|
||||
comment: String
|
||||
)(implicit s: Session): Unit =
|
||||
Activities insert Activity(
|
||||
userName,
|
||||
repositoryName,
|
||||
activityUserName,
|
||||
"comment_commit",
|
||||
s"[user:${activityUserName}] commented on commit [commit:${userName}/${repositoryName}@${commitId}]",
|
||||
Some(cut(comment, 200)),
|
||||
currentDate
|
||||
)
|
||||
|
||||
def recordCreateWikiPageActivity(
|
||||
userName: String,
|
||||
repositoryName: String,
|
||||
activityUserName: String,
|
||||
pageName: String
|
||||
)(implicit s: Session): Unit =
|
||||
Activities insert Activity(
|
||||
userName,
|
||||
repositoryName,
|
||||
activityUserName,
|
||||
"create_wiki",
|
||||
s"[user:${activityUserName}] created the [repo:${userName}/${repositoryName}] wiki",
|
||||
Some(pageName),
|
||||
currentDate
|
||||
)
|
||||
|
||||
def recordEditWikiPageActivity(
|
||||
userName: String,
|
||||
repositoryName: String,
|
||||
activityUserName: String,
|
||||
pageName: String,
|
||||
commitId: String
|
||||
)(implicit s: Session): Unit =
|
||||
Activities insert Activity(
|
||||
userName,
|
||||
repositoryName,
|
||||
activityUserName,
|
||||
"edit_wiki",
|
||||
s"[user:${activityUserName}] edited the [repo:${userName}/${repositoryName}] wiki",
|
||||
Some(pageName + ":" + commitId),
|
||||
currentDate
|
||||
)
|
||||
|
||||
def recordPushActivity(
|
||||
userName: String,
|
||||
repositoryName: String,
|
||||
activityUserName: String,
|
||||
branchName: String,
|
||||
commits: List[JGitUtil.CommitInfo]
|
||||
)(implicit s: Session): Unit =
|
||||
Activities insert Activity(
|
||||
userName,
|
||||
repositoryName,
|
||||
activityUserName,
|
||||
"push",
|
||||
s"[user:${activityUserName}] pushed to [branch:${userName}/${repositoryName}#${branchName}] at [repo:${userName}/${repositoryName}]",
|
||||
Some(
|
||||
commits
|
||||
.take(5)
|
||||
.map { commit =>
|
||||
commit.id + ":" + commit.shortMessage
|
||||
def getRecentPublicActivities()(implicit context: Context): List[Activity] = {
|
||||
if (!ActivityLog.exists()) {
|
||||
List.empty
|
||||
} else {
|
||||
val list = new ListBuffer[Activity]
|
||||
Using.resource(new ReversedLinesFileReader(ActivityLog, StandardCharsets.UTF_8)) { reader =>
|
||||
var json: String = null
|
||||
while (list.length < 50 && { json = reader.readLine(); json } != null) {
|
||||
val activity = read[Activity](json)
|
||||
if (!getRepositoryInfoFromCache(activity.userName, activity.repositoryName)
|
||||
.map(_.isPrivate)
|
||||
.getOrElse(true)) {
|
||||
list += activity
|
||||
}
|
||||
.mkString("\n")
|
||||
),
|
||||
currentDate
|
||||
)
|
||||
}
|
||||
}
|
||||
list.toList
|
||||
}
|
||||
}
|
||||
|
||||
def recordCreateTagActivity(
|
||||
userName: String,
|
||||
repositoryName: String,
|
||||
activityUserName: String,
|
||||
tagName: String,
|
||||
commits: List[JGitUtil.CommitInfo]
|
||||
)(implicit s: Session): Unit =
|
||||
Activities insert Activity(
|
||||
userName,
|
||||
repositoryName,
|
||||
activityUserName,
|
||||
"create_tag",
|
||||
s"[user:${activityUserName}] created tag [tag:${userName}/${repositoryName}#${tagName}] at [repo:${userName}/${repositoryName}]",
|
||||
None,
|
||||
currentDate
|
||||
)
|
||||
def getRecentActivitiesByOwners(owners: Set[String])(implicit context: Context): List[Activity] = {
|
||||
if (!ActivityLog.exists()) {
|
||||
List.empty
|
||||
} else {
|
||||
val list = new ListBuffer[Activity]
|
||||
Using.resource(new ReversedLinesFileReader(ActivityLog, StandardCharsets.UTF_8)) { reader =>
|
||||
var json: String = null
|
||||
while (list.length < 50 && { json = reader.readLine(); json } != null) {
|
||||
val activity = read[Activity](json)
|
||||
if (owners.contains(activity.userName)) {
|
||||
list += activity
|
||||
} else if (!getRepositoryInfoFromCache(activity.userName, activity.repositoryName)
|
||||
.map(_.isPrivate)
|
||||
.getOrElse(true)) {
|
||||
list += activity
|
||||
}
|
||||
}
|
||||
}
|
||||
list.toList
|
||||
}
|
||||
}
|
||||
|
||||
def recordDeleteTagActivity(
|
||||
userName: String,
|
||||
repositoryName: String,
|
||||
activityUserName: String,
|
||||
tagName: String,
|
||||
commits: List[JGitUtil.CommitInfo]
|
||||
)(implicit s: Session): Unit =
|
||||
Activities insert Activity(
|
||||
userName,
|
||||
repositoryName,
|
||||
activityUserName,
|
||||
"delete_tag",
|
||||
s"[user:${activityUserName}] deleted tag ${tagName} at [repo:${userName}/${repositoryName}]",
|
||||
None,
|
||||
currentDate
|
||||
)
|
||||
|
||||
def recordCreateBranchActivity(
|
||||
userName: String,
|
||||
repositoryName: String,
|
||||
activityUserName: String,
|
||||
branchName: String
|
||||
)(implicit s: Session): Unit =
|
||||
Activities insert Activity(
|
||||
userName,
|
||||
repositoryName,
|
||||
activityUserName,
|
||||
"create_branch",
|
||||
s"[user:${activityUserName}] created branch [branch:${userName}/${repositoryName}#${branchName}] at [repo:${userName}/${repositoryName}]",
|
||||
None,
|
||||
currentDate
|
||||
)
|
||||
|
||||
def recordDeleteBranchActivity(
|
||||
userName: String,
|
||||
repositoryName: String,
|
||||
activityUserName: String,
|
||||
branchName: String
|
||||
)(implicit s: Session): Unit =
|
||||
Activities insert Activity(
|
||||
userName,
|
||||
repositoryName,
|
||||
activityUserName,
|
||||
"delete_branch",
|
||||
s"[user:${activityUserName}] deleted branch ${branchName} at [repo:${userName}/${repositoryName}]",
|
||||
None,
|
||||
currentDate
|
||||
)
|
||||
|
||||
def recordForkActivity(userName: String, repositoryName: String, activityUserName: String, forkedUserName: String)(
|
||||
implicit s: Session
|
||||
): Unit =
|
||||
Activities insert Activity(
|
||||
userName,
|
||||
repositoryName,
|
||||
activityUserName,
|
||||
"fork",
|
||||
s"[user:${activityUserName}] forked [repo:${userName}/${repositoryName}] to [repo:${forkedUserName}/${repositoryName}]",
|
||||
None,
|
||||
currentDate
|
||||
)
|
||||
|
||||
def recordPullRequestActivity(
|
||||
userName: String,
|
||||
repositoryName: String,
|
||||
activityUserName: String,
|
||||
issueId: Int,
|
||||
title: String
|
||||
)(implicit s: Session): Unit =
|
||||
Activities insert Activity(
|
||||
userName,
|
||||
repositoryName,
|
||||
activityUserName,
|
||||
"open_pullreq",
|
||||
s"[user:${activityUserName}] opened pull request [pullreq:${userName}/${repositoryName}#${issueId}]",
|
||||
Some(title),
|
||||
currentDate
|
||||
)
|
||||
|
||||
def recordMergeActivity(
|
||||
userName: String,
|
||||
repositoryName: String,
|
||||
activityUserName: String,
|
||||
issueId: Int,
|
||||
message: String
|
||||
)(implicit s: Session): Unit =
|
||||
Activities insert Activity(
|
||||
userName,
|
||||
repositoryName,
|
||||
activityUserName,
|
||||
"merge_pullreq",
|
||||
s"[user:${activityUserName}] merged pull request [pullreq:${userName}/${repositoryName}#${issueId}]",
|
||||
Some(message),
|
||||
currentDate
|
||||
)
|
||||
|
||||
def recordReleaseActivity(
|
||||
userName: String,
|
||||
repositoryName: String,
|
||||
activityUserName: String,
|
||||
releaseName: String,
|
||||
tagName: String
|
||||
)(implicit s: Session): Unit =
|
||||
Activities insert Activity(
|
||||
userName,
|
||||
repositoryName,
|
||||
activityUserName,
|
||||
"release",
|
||||
s"[user:${activityUserName}] released [release:${userName}/${repositoryName}/${tagName}:${releaseName}] at [repo:${userName}/${repositoryName}]",
|
||||
None,
|
||||
currentDate
|
||||
)
|
||||
|
||||
private def cut(value: String, length: Int): String =
|
||||
if (value.length > length) value.substring(0, length) + "..." else value
|
||||
def recordActivity[T <: { def toActivity: Activity }](info: T): Unit =
|
||||
writeLog(info.toActivity)
|
||||
}
|
||||
|
||||
@@ -47,6 +47,18 @@ trait CommitStatusService {
|
||||
)
|
||||
}
|
||||
|
||||
def getCommitStatusWithSummary(userName: String, repositoryName: String, sha: String)(
|
||||
implicit s: Session
|
||||
): Option[(CommitState, List[CommitStatus])] = {
|
||||
val statuses = getCommitStatuses(userName, repositoryName, sha)
|
||||
if (statuses.isEmpty) {
|
||||
None
|
||||
} else {
|
||||
val summary = CommitState.combine(statuses.groupBy(_.state).keySet)
|
||||
Some((summary, statuses))
|
||||
}
|
||||
}
|
||||
|
||||
def getCommitStatus(userName: String, repositoryName: String, id: Int)(implicit s: Session): Option[CommitStatus] =
|
||||
CommitStatuses.filter(t => t.byPrimaryKey(id) && t.byRepository(userName, repositoryName)).firstOption
|
||||
|
||||
@@ -55,10 +67,12 @@ trait CommitStatusService {
|
||||
): Option[CommitStatus] =
|
||||
CommitStatuses.filter(t => t.byCommit(userName, repositoryName, sha) && t.context === context.bind).firstOption
|
||||
|
||||
def getCommitStatues(userName: String, repositoryName: String, sha: String)(implicit s: Session): List[CommitStatus] =
|
||||
byCommitStatues(userName, repositoryName, sha).list
|
||||
def getCommitStatuses(userName: String, repositoryName: String, sha: String)(
|
||||
implicit s: Session
|
||||
): List[CommitStatus] =
|
||||
byCommitStatus(userName, repositoryName, sha).list
|
||||
|
||||
def getRecentStatuesContexts(userName: String, repositoryName: String, time: java.util.Date)(
|
||||
def getRecentStatusContexts(userName: String, repositoryName: String, time: java.util.Date)(
|
||||
implicit s: Session
|
||||
): List[String] =
|
||||
CommitStatuses
|
||||
@@ -68,15 +82,15 @@ trait CommitStatusService {
|
||||
.map(_._1)
|
||||
.list
|
||||
|
||||
def getCommitStatuesWithCreator(userName: String, repositoryName: String, sha: String)(
|
||||
def getCommitStatusesWithCreator(userName: String, repositoryName: String, sha: String)(
|
||||
implicit s: Session
|
||||
): List[(CommitStatus, Account)] =
|
||||
byCommitStatues(userName, repositoryName, sha)
|
||||
byCommitStatus(userName, repositoryName, sha)
|
||||
.join(Accounts)
|
||||
.filter { case (t, a) => t.creator === a.userName }
|
||||
.list
|
||||
|
||||
protected def byCommitStatues(userName: String, repositoryName: String, sha: String)(implicit s: Session) =
|
||||
protected def byCommitStatus(userName: String, repositoryName: String, sha: String)(implicit s: Session) =
|
||||
CommitStatuses.filter(t => t.byCommit(userName, repositoryName, sha)).sortBy(_.updatedDate desc)
|
||||
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import gitbucket.core.model.{Account, CommitComment}
|
||||
import gitbucket.core.model.Profile._
|
||||
import gitbucket.core.model.Profile.profile.blockingApi._
|
||||
import gitbucket.core.model.Profile.dateColumnType
|
||||
import gitbucket.core.model.activity.{CommitCommentInfo, PullRequestCommentInfo}
|
||||
import gitbucket.core.plugin.PluginRegistry
|
||||
import gitbucket.core.service.RepositoryService.RepositoryInfo
|
||||
import gitbucket.core.util.Directory._
|
||||
@@ -80,13 +81,9 @@ trait CommitsService {
|
||||
case Some(issueId) =>
|
||||
getPullRequest(repository.owner, repository.name, issueId).foreach {
|
||||
case (issue, pullRequest) =>
|
||||
recordCommentPullRequestActivity(
|
||||
repository.owner,
|
||||
repository.name,
|
||||
loginAccount.userName,
|
||||
issueId,
|
||||
content
|
||||
)
|
||||
val pullRequestCommentInfo =
|
||||
PullRequestCommentInfo(repository.owner, repository.name, loginAccount.userName, content, issueId)
|
||||
recordActivity(pullRequestCommentInfo)
|
||||
PluginRegistry().getPullRequestHooks.foreach(_.addedComment(commentId, content, issue, repository))
|
||||
callPullRequestReviewCommentWebHook(
|
||||
"create",
|
||||
@@ -99,13 +96,9 @@ trait CommitsService {
|
||||
)
|
||||
}
|
||||
case None =>
|
||||
recordCommentCommitActivity(
|
||||
repository.owner,
|
||||
repository.name,
|
||||
loginAccount.userName,
|
||||
commitId,
|
||||
content
|
||||
)
|
||||
val commitCommentInfo =
|
||||
CommitCommentInfo(repository.owner, repository.name, loginAccount.userName, content, commitId)
|
||||
recordActivity(commitCommentInfo)
|
||||
}
|
||||
|
||||
commentId
|
||||
|
||||
@@ -1,9 +1,18 @@
|
||||
package gitbucket.core.service
|
||||
|
||||
import gitbucket.core.controller.Context
|
||||
import gitbucket.core.model.Issue
|
||||
import gitbucket.core.model.{Issue, IssueComment}
|
||||
import gitbucket.core.model.Profile.profile.blockingApi._
|
||||
import gitbucket.core.plugin.PluginRegistry
|
||||
import gitbucket.core.model.activity.{
|
||||
CloseIssueInfo,
|
||||
ClosePullRequestInfo,
|
||||
IssueCommentInfo,
|
||||
PullRequestCommentInfo,
|
||||
ReopenIssueInfo,
|
||||
ReopenPullRequestInfo
|
||||
}
|
||||
import gitbucket.core.plugin.{IssueHook, PluginRegistry}
|
||||
import gitbucket.core.service.RepositoryService.RepositoryInfo
|
||||
import gitbucket.core.util.SyntaxSugars._
|
||||
import gitbucket.core.util.Implicits._
|
||||
|
||||
@@ -29,22 +38,31 @@ trait HandleCommentService {
|
||||
case (owner, name) =>
|
||||
val userName = loginAccount.userName
|
||||
|
||||
val (action, actionActivity) = actionOpt
|
||||
actionOpt.collect {
|
||||
case "close" if !issue.closed =>
|
||||
updateClosed(owner, name, issue.issueId, true)
|
||||
case "reopen" if issue.closed =>
|
||||
updateClosed(owner, name, issue.issueId, false)
|
||||
}
|
||||
|
||||
val (action, _) = actionOpt
|
||||
.collect {
|
||||
case "close" if (!issue.closed) =>
|
||||
true ->
|
||||
(Some("close") -> Some(
|
||||
if (issue.isPullRequest) recordClosePullRequestActivity _
|
||||
else recordCloseIssueActivity _
|
||||
))
|
||||
case "reopen" if (issue.closed) =>
|
||||
false ->
|
||||
(Some("reopen") -> Some(recordReopenIssueActivity _))
|
||||
}
|
||||
.map {
|
||||
case (closed, t) =>
|
||||
updateClosed(owner, name, issue.issueId, closed)
|
||||
t
|
||||
case "close" if !issue.closed =>
|
||||
val info = if (issue.isPullRequest) {
|
||||
ClosePullRequestInfo(owner, name, userName, issue.issueId, issue.title)
|
||||
} else {
|
||||
CloseIssueInfo(owner, name, userName, issue.issueId, issue.title)
|
||||
}
|
||||
recordActivity(info)
|
||||
Some("close") -> info
|
||||
case "reopen" if issue.closed =>
|
||||
val info = if (issue.isPullRequest) {
|
||||
ReopenPullRequestInfo(owner, name, userName, issue.issueId, issue.title)
|
||||
} else {
|
||||
ReopenIssueInfo(owner, name, userName, issue.issueId, issue.title)
|
||||
}
|
||||
recordActivity(info)
|
||||
Some("reopen") -> info
|
||||
}
|
||||
.getOrElse(None -> None)
|
||||
|
||||
@@ -65,8 +83,12 @@ trait HandleCommentService {
|
||||
)
|
||||
|
||||
// record comment activity
|
||||
if (issue.isPullRequest) recordCommentPullRequestActivity(owner, name, userName, issue.issueId, content)
|
||||
else recordCommentIssueActivity(owner, name, userName, issue.issueId, content)
|
||||
val commentInfo = if (issue.isPullRequest) {
|
||||
PullRequestCommentInfo(owner, name, userName, content, issue.issueId)
|
||||
} else {
|
||||
IssueCommentInfo(owner, name, userName, content, issue.issueId)
|
||||
}
|
||||
recordActivity(commentInfo)
|
||||
|
||||
// extract references and create refer comment
|
||||
createReferComment(owner, name, issue, content, loginAccount)
|
||||
@@ -74,10 +96,6 @@ trait HandleCommentService {
|
||||
id
|
||||
}
|
||||
|
||||
actionActivity.foreach { f =>
|
||||
f(owner, name, userName, issue.issueId, issue.title)
|
||||
}
|
||||
|
||||
// call web hooks
|
||||
action match {
|
||||
case None =>
|
||||
@@ -118,4 +136,59 @@ trait HandleCommentService {
|
||||
}
|
||||
}
|
||||
|
||||
def deleteCommentByApi(repoInfo: RepositoryInfo, comment: IssueComment, issue: Issue)(
|
||||
implicit context: Context,
|
||||
s: Session
|
||||
): Option[IssueComment] = context.loginAccount.flatMap { _ =>
|
||||
comment.action match {
|
||||
case "comment" =>
|
||||
val deleteResult = deleteComment(repoInfo.owner, repoInfo.name, comment.issueId, comment.commentId)
|
||||
val registry = PluginRegistry()
|
||||
val hooks: Seq[IssueHook] = if (issue.isPullRequest) registry.getPullRequestHooks else registry.getIssueHooks
|
||||
hooks.foreach(_.deletedComment(comment.commentId, issue, repoInfo))
|
||||
deleteResult match {
|
||||
case n if n > 0 => Some(comment)
|
||||
case _ => None
|
||||
}
|
||||
case _ => None
|
||||
}
|
||||
}
|
||||
|
||||
def updateCommentByApi(
|
||||
repository: RepositoryService.RepositoryInfo,
|
||||
issue: Issue,
|
||||
commentId: String,
|
||||
content: Option[String]
|
||||
)(implicit context: Context, s: Session): Option[(Issue, Int)] = {
|
||||
context.loginAccount.flatMap { loginAccount =>
|
||||
defining(repository.owner, repository.name) {
|
||||
case (owner, name) =>
|
||||
val userName = loginAccount.userName
|
||||
content match {
|
||||
case Some(content) =>
|
||||
// Update comment
|
||||
val _commentId = Some(updateComment(issue.issueId, commentId.toInt, content))
|
||||
// Record comment activity
|
||||
val commentInfo = if (issue.isPullRequest) {
|
||||
PullRequestCommentInfo(owner, name, userName, content, issue.issueId)
|
||||
} else {
|
||||
IssueCommentInfo(owner, name, userName, content, issue.issueId)
|
||||
}
|
||||
recordActivity(commentInfo)
|
||||
// extract references and create refer comment
|
||||
createReferComment(owner, name, issue, content, loginAccount)
|
||||
// call web hooks
|
||||
commentId foreach (callIssueCommentWebHook(repository, issue, _, loginAccount, context.settings))
|
||||
// call hooks
|
||||
if (issue.isPullRequest)
|
||||
PluginRegistry().getPullRequestHooks
|
||||
.foreach(_.updatedComment(commentId.toInt, content, issue, repository))
|
||||
else
|
||||
PluginRegistry().getIssueHooks.foreach(_.updatedComment(commentId.toInt, content, issue, repository))
|
||||
_commentId.map(issue -> _)
|
||||
case _ => None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package gitbucket.core.service
|
||||
import gitbucket.core.controller.Context
|
||||
import gitbucket.core.model.{Account, Issue}
|
||||
import gitbucket.core.model.Profile.profile.blockingApi._
|
||||
import gitbucket.core.model.activity.CreateIssueInfo
|
||||
import gitbucket.core.plugin.PluginRegistry
|
||||
import gitbucket.core.service.RepositoryService.RepositoryInfo
|
||||
import gitbucket.core.util.Implicits._
|
||||
@@ -51,7 +52,8 @@ trait IssueCreationService {
|
||||
}
|
||||
|
||||
// record activity
|
||||
recordCreateIssueActivity(owner, name, userName, issueId, title)
|
||||
val createIssueInfo = CreateIssueInfo(owner, name, userName, issueId, title)
|
||||
recordActivity(createIssueInfo)
|
||||
|
||||
// extract references and create refer comment
|
||||
createReferComment(owner, name, issue, title + " " + body.getOrElse(""), loginAccount)
|
||||
@@ -72,6 +74,13 @@ trait IssueCreationService {
|
||||
hasDeveloperRole(repository.owner, repository.name, context.loginAccount)
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests whether an logged-in user can manage issues comment.
|
||||
*/
|
||||
protected def isIssueCommentManageable(repository: RepositoryInfo)(implicit context: Context, s: Session): Boolean = {
|
||||
hasOwnerRole(repository.owner, repository.name, context.loginAccount)
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests whether an logged-in user can post issues.
|
||||
*/
|
||||
|
||||
@@ -31,6 +31,9 @@ trait IssuesService {
|
||||
Issues filter (_.byPrimaryKey(owner, repository, issueId.toInt)) firstOption
|
||||
else None
|
||||
|
||||
def getOpenIssues(owner: String, repository: String)(implicit s: Session): List[Issue] =
|
||||
Issues filter (_.byRepository(owner, repository)) filterNot (_.closed) sortBy (_.issueId desc) list
|
||||
|
||||
def getComments(owner: String, repository: String, issueId: Int)(implicit s: Session) =
|
||||
IssueComments filter (_.byIssue(owner, repository, issueId)) sortBy (_.commentId asc) list
|
||||
|
||||
@@ -68,6 +71,20 @@ trait IssuesService {
|
||||
else None
|
||||
}
|
||||
|
||||
def getCommentForApi(owner: String, repository: String, commentId: Int)(
|
||||
implicit s: Session
|
||||
): Option[(IssueComment, Account, Issue)] =
|
||||
IssueComments
|
||||
.filter(_.byRepository(owner, repository))
|
||||
.filter(_.commentId === commentId)
|
||||
.filter(_.action inSetBind Set("comment", "close_comment", "reopen_comment"))
|
||||
.join(Accounts)
|
||||
.on { case t1 ~ t2 => t1.commentedUserName === t2.userName }
|
||||
.join(Issues)
|
||||
.on { case t1 ~ t2 ~ t3 => t3.byIssue(t1.userName, t1.repositoryName, t1.issueId) }
|
||||
.map { case t1 ~ t2 ~ t3 => (t1, t2, t3) }
|
||||
.firstOption
|
||||
|
||||
def getIssueLabels(owner: String, repository: String, issueId: Int)(implicit s: Session): List[Label] = {
|
||||
IssueLabels
|
||||
.join(Labels)
|
||||
@@ -90,14 +107,14 @@ trait IssuesService {
|
||||
* Returns the count of the search result against issues.
|
||||
*
|
||||
* @param condition the search condition
|
||||
* @param onlyPullRequest if true then counts only pull request, false then counts both of issue and pull request.
|
||||
* @param searchOption if true then counts only pull request, false then counts both of issue and pull request.
|
||||
* @param repos Tuple of the repository owner and the repository name
|
||||
* @return the count of the search result
|
||||
*/
|
||||
def countIssue(condition: IssueSearchCondition, onlyPullRequest: Boolean, repos: (String, String)*)(
|
||||
def countIssue(condition: IssueSearchCondition, searchOption: IssueSearchOption, repos: (String, String)*)(
|
||||
implicit s: Session
|
||||
): Int = {
|
||||
Query(searchIssueQuery(repos, condition, onlyPullRequest).length).first
|
||||
Query(searchIssueQuery(repos, condition, searchOption).length).first
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -115,7 +132,7 @@ trait IssuesService {
|
||||
filterUser: Map[String, String]
|
||||
)(implicit s: Session): Map[String, Int] = {
|
||||
|
||||
searchIssueQuery(Seq(owner -> repository), condition.copy(labels = Set.empty), false)
|
||||
searchIssueQuery(Seq(owner -> repository), condition.copy(labels = Set.empty), IssueSearchOption.Issues)
|
||||
.join(IssueLabels)
|
||||
.on {
|
||||
case t1 ~ t2 =>
|
||||
@@ -153,7 +170,7 @@ trait IssuesService {
|
||||
filterUser: Map[String, String]
|
||||
)(implicit s: Session): Map[String, Int] = {
|
||||
|
||||
searchIssueQuery(Seq(owner -> repository), condition.copy(labels = Set.empty), false)
|
||||
searchIssueQuery(Seq(owner -> repository), condition.copy(labels = Set.empty), IssueSearchOption.Issues)
|
||||
.join(Priorities)
|
||||
.on {
|
||||
case t1 ~ t2 =>
|
||||
@@ -171,42 +188,11 @@ trait IssuesService {
|
||||
.toMap
|
||||
}
|
||||
|
||||
def getCommitStatues(userName: String, repositoryName: String, issueId: Int)(
|
||||
implicit s: Session
|
||||
): Option[CommitStatusInfo] = {
|
||||
val status = PullRequests
|
||||
.filter { pr =>
|
||||
pr.userName === userName.bind && pr.repositoryName === repositoryName.bind && pr.issueId === issueId.bind
|
||||
}
|
||||
.join(CommitStatuses)
|
||||
.on {
|
||||
case pr ~ cs =>
|
||||
pr.userName === cs.userName && pr.repositoryName === cs.repositoryName && pr.commitIdTo === cs.commitId
|
||||
}
|
||||
.list
|
||||
|
||||
if (status.nonEmpty) {
|
||||
val (_, cs) = status.head
|
||||
Some(
|
||||
CommitStatusInfo(
|
||||
count = status.length,
|
||||
successCount = status.count(_._2.state == CommitState.SUCCESS),
|
||||
context = (if (status.length == 1) Some(cs.context) else None),
|
||||
state = (if (status.length == 1) Some(cs.state) else None),
|
||||
targetUrl = (if (status.length == 1) cs.targetUrl else None),
|
||||
description = (if (status.length == 1) cs.description else None)
|
||||
)
|
||||
)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the search result against issues.
|
||||
* Returns the search result against issues.
|
||||
*
|
||||
* @param condition the search condition
|
||||
* @param pullRequest if true then returns only pull requests, false then returns only issues.
|
||||
* @param searchOption if true then returns only pull requests, false then returns only issues.
|
||||
* @param offset the offset for pagination
|
||||
* @param limit the limit for pagination
|
||||
* @param repos Tuple of the repository owner and the repository name
|
||||
@@ -214,13 +200,13 @@ trait IssuesService {
|
||||
*/
|
||||
def searchIssue(
|
||||
condition: IssueSearchCondition,
|
||||
pullRequest: Boolean,
|
||||
searchOption: IssueSearchOption,
|
||||
offset: Int,
|
||||
limit: Int,
|
||||
repos: (String, String)*
|
||||
)(implicit s: Session): List[IssueInfo] = {
|
||||
// get issues and comment count and labels
|
||||
val result = searchIssueQueryBase(condition, pullRequest, offset, limit, repos)
|
||||
val result = searchIssueQueryBase(condition, searchOption, offset, limit, repos)
|
||||
.joinLeft(IssueLabels)
|
||||
.on { case t1 ~ t2 ~ i ~ t3 => t1.byIssue(t3.userName, t3.repositoryName, t3.issueId) }
|
||||
.joinLeft(Labels)
|
||||
@@ -229,9 +215,11 @@ trait IssuesService {
|
||||
.on { case t1 ~ t2 ~ i ~ t3 ~ t4 ~ t5 => t1.byMilestone(t5.userName, t5.repositoryName, t5.milestoneId) }
|
||||
.joinLeft(Priorities)
|
||||
.on { case t1 ~ t2 ~ i ~ t3 ~ t4 ~ t5 ~ t6 => t1.byPriority(t6.userName, t6.repositoryName, t6.priorityId) }
|
||||
.sortBy { case t1 ~ t2 ~ i ~ t3 ~ t4 ~ t5 ~ t6 => i asc }
|
||||
.joinLeft(PullRequests)
|
||||
.on { case t1 ~ t2 ~ i ~ t3 ~ t4 ~ t5 ~ t6 ~ t7 => t1.byIssue(t7.userName, t7.repositoryName, t7.issueId) }
|
||||
.sortBy { case t1 ~ t2 ~ i ~ t3 ~ t4 ~ t5 ~ t6 ~ t7 => i asc }
|
||||
.map {
|
||||
case t1 ~ t2 ~ i ~ t3 ~ t4 ~ t5 ~ t6 =>
|
||||
case t1 ~ t2 ~ i ~ t3 ~ t4 ~ t5 ~ t6 ~ t7 =>
|
||||
(
|
||||
t1,
|
||||
t2.commentCount,
|
||||
@@ -239,7 +227,8 @@ trait IssuesService {
|
||||
t4.map(_.labelName),
|
||||
t4.map(_.color),
|
||||
t5.map(_.title),
|
||||
t6.map(_.priorityName)
|
||||
t6.map(_.priorityName),
|
||||
t7.map(_.commitIdTo)
|
||||
)
|
||||
}
|
||||
.list
|
||||
@@ -249,7 +238,7 @@ trait IssuesService {
|
||||
|
||||
result.map { issues =>
|
||||
issues.head match {
|
||||
case (issue, commentCount, _, _, _, milestone, priority) =>
|
||||
case (issue, commentCount, _, _, _, milestone, priority, commitId) =>
|
||||
IssueInfo(
|
||||
issue,
|
||||
issues.flatMap { t =>
|
||||
@@ -258,7 +247,7 @@ trait IssuesService {
|
||||
milestone,
|
||||
priority,
|
||||
commentCount,
|
||||
getCommitStatues(issue.userName, issue.repositoryName, issue.issueId)
|
||||
commitId
|
||||
)
|
||||
}
|
||||
} toList
|
||||
@@ -271,7 +260,7 @@ trait IssuesService {
|
||||
implicit s: Session
|
||||
): List[(Issue, Account, Option[Account])] = {
|
||||
// get issues and comment count and labels
|
||||
searchIssueQueryBase(condition, false, offset, limit, repos)
|
||||
searchIssueQueryBase(condition, IssueSearchOption.Issues, offset, limit, repos)
|
||||
.join(Accounts)
|
||||
.on { case t1 ~ t2 ~ i ~ t3 => t3.userName === t1.openedUserName }
|
||||
.joinLeft(Accounts)
|
||||
@@ -288,7 +277,7 @@ trait IssuesService {
|
||||
implicit s: Session
|
||||
): List[(Issue, Account, Int, PullRequest, Repository, Account, Option[Account])] = {
|
||||
// get issues and comment count and labels
|
||||
searchIssueQueryBase(condition, true, offset, limit, repos)
|
||||
searchIssueQueryBase(condition, IssueSearchOption.PullRequests, offset, limit, repos)
|
||||
.join(PullRequests)
|
||||
.on { case t1 ~ t2 ~ i ~ t3 => t3.byPrimaryKey(t1.userName, t1.repositoryName, t1.issueId) }
|
||||
.join(Repositories)
|
||||
@@ -306,12 +295,12 @@ trait IssuesService {
|
||||
|
||||
private def searchIssueQueryBase(
|
||||
condition: IssueSearchCondition,
|
||||
pullRequest: Boolean,
|
||||
searchOption: IssueSearchOption,
|
||||
offset: Int,
|
||||
limit: Int,
|
||||
repos: Seq[(String, String)]
|
||||
)(implicit s: Session) =
|
||||
searchIssueQuery(repos, condition, pullRequest)
|
||||
searchIssueQuery(repos, condition, searchOption)
|
||||
.join(IssueOutline)
|
||||
.on { (t1, t2) =>
|
||||
t1.byIssue(t2.userName, t2.repositoryName, t2.issueId)
|
||||
@@ -349,7 +338,11 @@ trait IssuesService {
|
||||
/**
|
||||
* Assembles query for conditional issue searching.
|
||||
*/
|
||||
private def searchIssueQuery(repos: Seq[(String, String)], condition: IssueSearchCondition, pullRequest: Boolean)(
|
||||
private def searchIssueQuery(
|
||||
repos: Seq[(String, String)],
|
||||
condition: IssueSearchCondition,
|
||||
searchOption: IssueSearchOption
|
||||
)(
|
||||
implicit s: Session
|
||||
) =
|
||||
Issues filter { t1 =>
|
||||
@@ -363,7 +356,11 @@ trait IssuesService {
|
||||
(t1.priorityId.? isEmpty, condition.priority == Some(None)) &&
|
||||
(t1.assignedUserName.? isEmpty, condition.assigned == Some(None)) &&
|
||||
(t1.openedUserName === condition.author.get.bind, condition.author.isDefined) &&
|
||||
(t1.pullRequest === pullRequest.bind) &&
|
||||
(searchOption match {
|
||||
case IssueSearchOption.Issues => t1.pullRequest === false
|
||||
case IssueSearchOption.PullRequests => t1.pullRequest === true
|
||||
case IssueSearchOption.Both => t1.pullRequest === false || t1.pullRequest === true
|
||||
}) &&
|
||||
// Milestone filter
|
||||
(Milestones filter { t2 =>
|
||||
(t2.byPrimaryKey(t1.userName, t1.repositoryName, t1.milestoneId)) &&
|
||||
@@ -635,7 +632,10 @@ trait IssuesService {
|
||||
IssueComments.filter(_.byPrimaryKey(commentId)).map(t => (t.content, t.updatedDate)).update(content, currentDate)
|
||||
}
|
||||
|
||||
def deleteComment(issueId: Int, commentId: Int)(implicit s: Session): Int = {
|
||||
def deleteComment(owner: String, repository: String, issueId: Int, commentId: Int)(
|
||||
implicit context: Context,
|
||||
s: Session
|
||||
): Int = {
|
||||
Issues.filter(_.issueId === issueId.bind).map(_.updatedDate).update(currentDate)
|
||||
IssueComments.filter(_.byPrimaryKey(commentId)).firstOption match {
|
||||
case Some(c) if c.action == "reopen_comment" =>
|
||||
@@ -644,6 +644,16 @@ trait IssuesService {
|
||||
IssueComments.filter(_.byPrimaryKey(commentId)).map(t => (t.content, t.action)).update("Close", "close")
|
||||
case Some(_) =>
|
||||
IssueComments.filter(_.byPrimaryKey(commentId)).delete
|
||||
IssueComments insert IssueComment(
|
||||
userName = owner,
|
||||
repositoryName = repository,
|
||||
issueId = issueId,
|
||||
action = "delete_comment",
|
||||
commentedUserName = context.loginAccount.map(_.userName).getOrElse("Unknown user"),
|
||||
content = s"",
|
||||
registeredDate = currentDate,
|
||||
updatedDate = currentDate
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -910,23 +920,31 @@ object IssuesService {
|
||||
param(request, "groups").map(_.split(",").toSet).getOrElse(Set.empty)
|
||||
)
|
||||
|
||||
def page(request: HttpServletRequest) =
|
||||
try {
|
||||
val i = param(request, "page").getOrElse("1").toInt
|
||||
if (i <= 0) 1 else i
|
||||
} catch {
|
||||
case e: NumberFormatException => 1
|
||||
}
|
||||
}
|
||||
def apply(request: HttpServletRequest, milestone: String): IssueSearchCondition =
|
||||
IssueSearchCondition(
|
||||
param(request, "labels").map(_.split(",").toSet).getOrElse(Set.empty),
|
||||
Some(Some(milestone)),
|
||||
param(request, "priority").map {
|
||||
case "none" => None
|
||||
case x => Some(x)
|
||||
},
|
||||
param(request, "author"),
|
||||
param(request, "assigned").map {
|
||||
case "none" => None
|
||||
case x => Some(x)
|
||||
},
|
||||
param(request, "mentioned"),
|
||||
param(request, "state", Seq("open", "closed")).getOrElse("open"),
|
||||
param(request, "sort", Seq("created", "comments", "updated", "priority")).getOrElse("created"),
|
||||
param(request, "direction", Seq("asc", "desc")).getOrElse("desc"),
|
||||
param(request, "visibility"),
|
||||
param(request, "groups").map(_.split(",").toSet).getOrElse(Set.empty)
|
||||
)
|
||||
|
||||
case class CommitStatusInfo(
|
||||
count: Int,
|
||||
successCount: Int,
|
||||
context: Option[String],
|
||||
state: Option[CommitState],
|
||||
targetUrl: Option[String],
|
||||
description: Option[String]
|
||||
)
|
||||
def page(request: HttpServletRequest) = {
|
||||
PaginationHelper.page(param(request, "page"))
|
||||
}
|
||||
}
|
||||
|
||||
case class IssueInfo(
|
||||
issue: Issue,
|
||||
@@ -934,7 +952,14 @@ object IssuesService {
|
||||
milestone: Option[String],
|
||||
priority: Option[String],
|
||||
commentCount: Int,
|
||||
status: Option[CommitStatusInfo]
|
||||
commitId: Option[String]
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
sealed trait IssueSearchOption
|
||||
|
||||
object IssueSearchOption {
|
||||
case object Issues extends IssueSearchOption
|
||||
case object PullRequests extends IssueSearchOption
|
||||
case object Both extends IssueSearchOption
|
||||
}
|
||||
|
||||
@@ -2,16 +2,18 @@ package gitbucket.core.service
|
||||
|
||||
import gitbucket.core.api.JsonFormat
|
||||
import gitbucket.core.controller.Context
|
||||
import gitbucket.core.model.{Account, PullRequest, WebHook}
|
||||
import gitbucket.core.plugin.PluginRegistry
|
||||
import gitbucket.core.model.{Account, Issue, PullRequest, WebHook}
|
||||
import gitbucket.core.plugin.{PluginRegistry, ReceiveHook}
|
||||
import gitbucket.core.service.RepositoryService.RepositoryInfo
|
||||
import gitbucket.core.util.Directory._
|
||||
import gitbucket.core.util.{JGitUtil, LockUtil}
|
||||
import gitbucket.core.model.Profile.profile.blockingApi._
|
||||
import gitbucket.core.model.activity.{CloseIssueInfo, MergeInfo, PushInfo}
|
||||
import gitbucket.core.service.SystemSettingsService.SystemSettings
|
||||
import gitbucket.core.util.JGitUtil.CommitInfo
|
||||
import org.eclipse.jgit.merge.{MergeStrategy, Merger, RecursiveMerger}
|
||||
import org.eclipse.jgit.api.Git
|
||||
import org.eclipse.jgit.transport.RefSpec
|
||||
import org.eclipse.jgit.transport.{ReceiveCommand, ReceivePack, RefSpec}
|
||||
import org.eclipse.jgit.errors.NoMergeBaseException
|
||||
import org.eclipse.jgit.lib.{CommitBuilder, ObjectId, PersonIdent, Repository}
|
||||
import org.eclipse.jgit.revwalk.{RevCommit, RevWalk}
|
||||
@@ -35,7 +37,7 @@ trait MergeService {
|
||||
*/
|
||||
def checkConflict(userName: String, repositoryName: String, branch: String, issueId: Int): Option[String] = {
|
||||
Using.resource(Git.open(getRepositoryDir(userName, repositoryName))) { git =>
|
||||
new MergeCacheInfo(git, branch, issueId).checkConflict()
|
||||
new MergeCacheInfo(git, userName, repositoryName, branch, issueId, Nil).checkConflict()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,41 +54,47 @@ trait MergeService {
|
||||
issueId: Int
|
||||
): Option[Option[String]] = {
|
||||
Using.resource(Git.open(getRepositoryDir(userName, repositoryName))) { git =>
|
||||
new MergeCacheInfo(git, branch, issueId).checkConflictCache()
|
||||
new MergeCacheInfo(git, userName, repositoryName, branch, issueId, Nil).checkConflictCache()
|
||||
}
|
||||
}
|
||||
|
||||
/** merge the pull request with a merge commit */
|
||||
def mergePullRequest(
|
||||
def mergeWithMergeCommit(
|
||||
git: Git,
|
||||
userName: String,
|
||||
repositoryName: String,
|
||||
branch: String,
|
||||
issueId: Int,
|
||||
message: String,
|
||||
committer: PersonIdent
|
||||
): ObjectId = {
|
||||
new MergeCacheInfo(git, branch, issueId).merge(message, committer)
|
||||
)(implicit s: Session): ObjectId = {
|
||||
new MergeCacheInfo(git, userName, repositoryName, branch, issueId, getReceiveHooks()).merge(message, committer)
|
||||
}
|
||||
|
||||
/** rebase to the head of the pull request branch */
|
||||
def rebasePullRequest(
|
||||
def mergeWithRebase(
|
||||
git: Git,
|
||||
userName: String,
|
||||
repositoryName: String,
|
||||
branch: String,
|
||||
issueId: Int,
|
||||
commits: Seq[RevCommit],
|
||||
committer: PersonIdent
|
||||
): ObjectId = {
|
||||
new MergeCacheInfo(git, branch, issueId).rebase(committer, commits)
|
||||
)(implicit s: Session): ObjectId = {
|
||||
new MergeCacheInfo(git, userName, repositoryName, branch, issueId, getReceiveHooks()).rebase(committer, commits)
|
||||
}
|
||||
|
||||
/** squash commits in the pull request and append it */
|
||||
def squashPullRequest(
|
||||
def mergeWithSquash(
|
||||
git: Git,
|
||||
userName: String,
|
||||
repositoryName: String,
|
||||
branch: String,
|
||||
issueId: Int,
|
||||
message: String,
|
||||
committer: PersonIdent
|
||||
): ObjectId = {
|
||||
new MergeCacheInfo(git, branch, issueId).squash(message, committer)
|
||||
)(implicit s: Session): ObjectId = {
|
||||
new MergeCacheInfo(git, userName, repositoryName, branch, issueId, getReceiveHooks()).squash(message, committer)
|
||||
}
|
||||
|
||||
/** fetch remote branch to my repository refs/pull/{issueId}/head */
|
||||
@@ -168,7 +176,7 @@ trait MergeService {
|
||||
remoteBranch: String,
|
||||
loginAccount: Account,
|
||||
message: String,
|
||||
pullreq: Option[PullRequest],
|
||||
pullRequest: Option[PullRequest],
|
||||
settings: SystemSettings
|
||||
)(implicit s: Session, c: JsonFormat.Context): Option[ObjectId] = {
|
||||
val localUserName = localRepository.owner
|
||||
@@ -200,13 +208,14 @@ trait MergeService {
|
||||
}
|
||||
|
||||
// record activity
|
||||
recordPushActivity(
|
||||
val pushInfo = PushInfo(
|
||||
localUserName,
|
||||
localRepositoryName,
|
||||
loginAccount.userName,
|
||||
localBranch,
|
||||
commits
|
||||
)
|
||||
recordActivity(pushInfo)
|
||||
|
||||
// close issue by commit message
|
||||
if (localBranch == localRepository.repository.defaultBranch) {
|
||||
@@ -215,6 +224,14 @@ trait MergeService {
|
||||
.foreach { issueId =>
|
||||
getIssue(localRepository.owner, localRepository.name, issueId.toString).foreach { issue =>
|
||||
callIssuesWebHook("closed", localRepository, issue, loginAccount, settings)
|
||||
val closeIssueInfo = CloseIssueInfo(
|
||||
localRepository.owner,
|
||||
localRepository.name,
|
||||
localUserName,
|
||||
issue.issueId,
|
||||
issue.title
|
||||
)
|
||||
recordActivity(closeIssueInfo)
|
||||
PluginRegistry().getIssueHooks
|
||||
.foreach(
|
||||
_.closedByCommitComment(issue, localRepository, commit.fullMessage, loginAccount)
|
||||
@@ -224,7 +241,7 @@ trait MergeService {
|
||||
}
|
||||
}
|
||||
|
||||
pullreq.foreach { pullreq =>
|
||||
pullRequest.foreach { pullRequest =>
|
||||
callWebHookOf(localRepository.owner, localRepository.name, WebHook.Push, settings) {
|
||||
for {
|
||||
ownerAccount <- getAccountByUserName(localRepository.owner)
|
||||
@@ -232,7 +249,7 @@ trait MergeService {
|
||||
WebHookService.WebHookPushPayload(
|
||||
git,
|
||||
loginAccount,
|
||||
pullreq.requestBranch,
|
||||
pullRequest.requestBranch,
|
||||
localRepository,
|
||||
commits,
|
||||
ownerAccount,
|
||||
@@ -247,6 +264,10 @@ trait MergeService {
|
||||
}.toOption
|
||||
}
|
||||
|
||||
protected def getReceiveHooks(): Seq[ReceiveHook] = {
|
||||
PluginRegistry().getReceiveHooks
|
||||
}
|
||||
|
||||
def mergePullRequest(
|
||||
repository: RepositoryInfo,
|
||||
issueId: Int,
|
||||
@@ -261,73 +282,52 @@ trait MergeService {
|
||||
LockUtil.lock(s"${repository.owner}/${repository.name}") {
|
||||
getPullRequest(repository.owner, repository.name, issueId)
|
||||
.map {
|
||||
case (issue, pullreq) =>
|
||||
case (issue, pullRequest) =>
|
||||
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
|
||||
// mark issue as merged and close.
|
||||
val commentId =
|
||||
createComment(repository.owner, repository.name, loginAccount.userName, issueId, message, "merge")
|
||||
createComment(repository.owner, repository.name, loginAccount.userName, issueId, "Close", "close")
|
||||
updateClosed(repository.owner, repository.name, issueId, true)
|
||||
|
||||
// record activity
|
||||
recordMergeActivity(repository.owner, repository.name, loginAccount.userName, issueId, message)
|
||||
|
||||
val (commits, _) = getRequestCompareInfo(
|
||||
repository.owner,
|
||||
repository.name,
|
||||
pullreq.commitIdFrom,
|
||||
pullreq.requestUserName,
|
||||
pullreq.requestRepositoryName,
|
||||
pullreq.commitIdTo
|
||||
pullRequest.commitIdFrom,
|
||||
pullRequest.requestUserName,
|
||||
pullRequest.requestRepositoryName,
|
||||
pullRequest.commitIdTo
|
||||
)
|
||||
|
||||
val revCommits = Using
|
||||
.resource(new RevWalk(git.getRepository)) { revWalk =>
|
||||
commits.flatten.map { commit =>
|
||||
revWalk.parseCommit(git.getRepository.resolve(commit.id))
|
||||
}
|
||||
}
|
||||
.reverse
|
||||
|
||||
// merge git repository
|
||||
(strategy match {
|
||||
case "merge-commit" =>
|
||||
Some(
|
||||
mergePullRequest(
|
||||
git,
|
||||
pullreq.branch,
|
||||
issueId,
|
||||
s"Merge pull request #${issueId} from ${pullreq.requestUserName}/${pullreq.requestBranch}\n\n" + message,
|
||||
new PersonIdent(loginAccount.fullName, loginAccount.mailAddress)
|
||||
)
|
||||
)
|
||||
case "rebase" =>
|
||||
Some(
|
||||
rebasePullRequest(
|
||||
git,
|
||||
pullreq.branch,
|
||||
issueId,
|
||||
revCommits,
|
||||
new PersonIdent(loginAccount.fullName, loginAccount.mailAddress)
|
||||
)
|
||||
)
|
||||
case "squash" =>
|
||||
Some(
|
||||
squashPullRequest(
|
||||
git,
|
||||
pullreq.branch,
|
||||
issueId,
|
||||
s"${issue.title} (#${issueId})\n\n" + message,
|
||||
new PersonIdent(loginAccount.fullName, loginAccount.mailAddress)
|
||||
)
|
||||
)
|
||||
case _ =>
|
||||
None
|
||||
}) match {
|
||||
mergeGitRepository(
|
||||
git,
|
||||
repository,
|
||||
issue,
|
||||
pullRequest,
|
||||
loginAccount,
|
||||
message,
|
||||
strategy,
|
||||
commits,
|
||||
getReceiveHooks()
|
||||
) match {
|
||||
case Some(newCommitId) =>
|
||||
// mark issue as merged and close.
|
||||
val commentId =
|
||||
createComment(
|
||||
repository.owner,
|
||||
repository.name,
|
||||
loginAccount.userName,
|
||||
issueId,
|
||||
message,
|
||||
"merge"
|
||||
)
|
||||
createComment(repository.owner, repository.name, loginAccount.userName, issueId, "Close", "close")
|
||||
updateClosed(repository.owner, repository.name, issueId, true)
|
||||
|
||||
// record activity
|
||||
val mergeInfo =
|
||||
MergeInfo(repository.owner, repository.name, loginAccount.userName, issueId, message)
|
||||
recordActivity(mergeInfo)
|
||||
updateLastActivityDate(repository.owner, repository.name)
|
||||
|
||||
// close issue by content of pull request
|
||||
val defaultBranch = getRepository(repository.owner, repository.name).get.repository.defaultBranch
|
||||
if (pullreq.branch == defaultBranch) {
|
||||
if (pullRequest.branch == defaultBranch) {
|
||||
commits.flatten.foreach { commit =>
|
||||
closeIssuesFromMessage(
|
||||
commit.fullMessage,
|
||||
@@ -337,6 +337,14 @@ trait MergeService {
|
||||
).foreach { issueId =>
|
||||
getIssue(repository.owner, repository.name, issueId.toString).foreach { issue =>
|
||||
callIssuesWebHook("closed", repository, issue, loginAccount, context.settings)
|
||||
val closeIssueInfo = CloseIssueInfo(
|
||||
repository.owner,
|
||||
repository.name,
|
||||
loginAccount.userName,
|
||||
issue.issueId,
|
||||
issue.title
|
||||
)
|
||||
recordActivity(closeIssueInfo)
|
||||
PluginRegistry().getIssueHooks
|
||||
.foreach(_.closedByCommitComment(issue, repository, commit.fullMessage, loginAccount))
|
||||
}
|
||||
@@ -351,6 +359,14 @@ trait MergeService {
|
||||
).foreach { issueId =>
|
||||
getIssue(repository.owner, repository.name, issueId.toString).foreach { issue =>
|
||||
callIssuesWebHook("closed", repository, issue, loginAccount, context.settings)
|
||||
val closeIssueInfo = CloseIssueInfo(
|
||||
repository.owner,
|
||||
repository.name,
|
||||
loginAccount.userName,
|
||||
issue.issueId,
|
||||
issue.title
|
||||
)
|
||||
recordActivity(closeIssueInfo)
|
||||
PluginRegistry().getIssueHooks
|
||||
.foreach(_.closedByCommitComment(issue, repository, issueContent, loginAccount))
|
||||
}
|
||||
@@ -359,6 +375,14 @@ trait MergeService {
|
||||
.foreach { issueId =>
|
||||
getIssue(repository.owner, repository.name, issueId.toString).foreach { issue =>
|
||||
callIssuesWebHook("closed", repository, issue, loginAccount, context.settings)
|
||||
val closeIssueInfo = CloseIssueInfo(
|
||||
repository.owner,
|
||||
repository.name,
|
||||
loginAccount.userName,
|
||||
issue.issueId,
|
||||
issue.title
|
||||
)
|
||||
recordActivity(closeIssueInfo)
|
||||
PluginRegistry().getIssueHooks
|
||||
.foreach(_.closedByCommitComment(issue, repository, issueContent, loginAccount))
|
||||
}
|
||||
@@ -370,7 +394,7 @@ trait MergeService {
|
||||
updatePullRequests(
|
||||
repository.owner,
|
||||
repository.name,
|
||||
pullreq.branch,
|
||||
pullRequest.branch,
|
||||
loginAccount,
|
||||
"closed",
|
||||
settings
|
||||
@@ -394,6 +418,67 @@ trait MergeService {
|
||||
} else Left("Strategy not allowed")
|
||||
} else Left("Draft pull requests cannot be merged")
|
||||
}
|
||||
|
||||
private def mergeGitRepository(
|
||||
git: Git,
|
||||
repository: RepositoryInfo,
|
||||
issue: Issue,
|
||||
pullRequest: PullRequest,
|
||||
loginAccount: Account,
|
||||
message: String,
|
||||
strategy: String,
|
||||
commits: Seq[Seq[CommitInfo]],
|
||||
receiveHooks: Seq[ReceiveHook]
|
||||
)(implicit s: Session): Option[ObjectId] = {
|
||||
val revCommits = Using
|
||||
.resource(new RevWalk(git.getRepository)) { revWalk =>
|
||||
commits.flatten.map { commit =>
|
||||
revWalk.parseCommit(git.getRepository.resolve(commit.id))
|
||||
}
|
||||
}
|
||||
.reverse
|
||||
|
||||
strategy match {
|
||||
case "merge-commit" =>
|
||||
Some(
|
||||
mergeWithMergeCommit(
|
||||
git,
|
||||
repository.owner,
|
||||
repository.name,
|
||||
pullRequest.branch,
|
||||
issue.issueId,
|
||||
s"Merge pull request #${issue.issueId} from ${pullRequest.requestUserName}/${pullRequest.requestBranch}\n\n" + message,
|
||||
new PersonIdent(loginAccount.fullName, loginAccount.mailAddress)
|
||||
)
|
||||
)
|
||||
case "rebase" =>
|
||||
Some(
|
||||
mergeWithRebase(
|
||||
git,
|
||||
repository.owner,
|
||||
repository.name,
|
||||
pullRequest.branch,
|
||||
issue.issueId,
|
||||
revCommits,
|
||||
new PersonIdent(loginAccount.fullName, loginAccount.mailAddress)
|
||||
)
|
||||
)
|
||||
case "squash" =>
|
||||
Some(
|
||||
mergeWithSquash(
|
||||
git,
|
||||
repository.owner,
|
||||
repository.name,
|
||||
pullRequest.branch,
|
||||
issue.issueId,
|
||||
s"${issue.title} (#${issue.issueId})\n\n" + message,
|
||||
new PersonIdent(loginAccount.fullName, loginAccount.mailAddress)
|
||||
)
|
||||
)
|
||||
case _ =>
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object MergeService {
|
||||
@@ -440,18 +525,22 @@ object MergeService {
|
||||
}
|
||||
}
|
||||
|
||||
class MergeCacheInfo(git: Git, branch: String, issueId: Int) {
|
||||
|
||||
private val repository = git.getRepository
|
||||
|
||||
class MergeCacheInfo(
|
||||
git: Git,
|
||||
userName: String,
|
||||
repositoryName: String,
|
||||
branch: String,
|
||||
issueId: Int,
|
||||
receiveHooks: Seq[ReceiveHook]
|
||||
) {
|
||||
private val mergedBranchName = s"refs/pull/${issueId}/merge"
|
||||
private val conflictedBranchName = s"refs/pull/${issueId}/conflict"
|
||||
|
||||
lazy val mergeBaseTip = repository.resolve(s"refs/heads/${branch}")
|
||||
lazy val mergeTip = repository.resolve(s"refs/pull/${issueId}/head")
|
||||
lazy val mergeBaseTip = git.getRepository.resolve(s"refs/heads/${branch}")
|
||||
lazy val mergeTip = git.getRepository.resolve(s"refs/pull/${issueId}/head")
|
||||
|
||||
def checkConflictCache(): Option[Option[String]] = {
|
||||
Option(repository.resolve(mergedBranchName))
|
||||
Option(git.getRepository.resolve(mergedBranchName))
|
||||
.flatMap { merged =>
|
||||
if (parseCommit(merged).getParents().toSet == Set(mergeBaseTip, mergeTip)) {
|
||||
// merged branch exists
|
||||
@@ -460,7 +549,7 @@ object MergeService {
|
||||
None
|
||||
}
|
||||
}
|
||||
.orElse(Option(repository.resolve(conflictedBranchName)).flatMap { conflicted =>
|
||||
.orElse(Option(git.getRepository.resolve(conflictedBranchName)).flatMap { conflicted =>
|
||||
val commit = parseCommit(conflicted)
|
||||
if (commit.getParents().toSet == Set(mergeBaseTip, mergeTip)) {
|
||||
// conflict branch exists
|
||||
@@ -476,19 +565,19 @@ object MergeService {
|
||||
}
|
||||
|
||||
def checkConflictForce(): Option[String] = {
|
||||
val merger = MergeStrategy.RECURSIVE.newMerger(repository, true)
|
||||
val merger = MergeStrategy.RECURSIVE.newMerger(git.getRepository, true)
|
||||
val conflicted = try {
|
||||
!merger.merge(mergeBaseTip, mergeTip)
|
||||
} catch {
|
||||
case e: NoMergeBaseException => true
|
||||
}
|
||||
val mergeTipCommit = Using.resource(new RevWalk(repository))(_.parseCommit(mergeTip))
|
||||
val mergeTipCommit = Using.resource(new RevWalk(git.getRepository))(_.parseCommit(mergeTip))
|
||||
val committer = mergeTipCommit.getCommitterIdent
|
||||
|
||||
def _updateBranch(treeId: ObjectId, message: String, branchName: String): Unit = {
|
||||
// creates merge commit
|
||||
val mergeCommitId = createMergeCommit(treeId, committer, message)
|
||||
Util.updateRefs(repository, branchName, mergeCommitId, true, committer)
|
||||
Util.updateRefs(git.getRepository, branchName, mergeCommitId, true, committer)
|
||||
}
|
||||
|
||||
if (!conflicted) {
|
||||
@@ -504,26 +593,48 @@ object MergeService {
|
||||
}
|
||||
|
||||
// update branch from cache
|
||||
def merge(message: String, committer: PersonIdent): ObjectId = {
|
||||
def merge(message: String, committer: PersonIdent)(implicit s: Session): ObjectId = {
|
||||
if (checkConflict().isDefined) {
|
||||
throw new RuntimeException("This pull request can't merge automatically.")
|
||||
}
|
||||
val mergeResultCommit = parseCommit(Option(repository.resolve(mergedBranchName)).getOrElse {
|
||||
val mergeResultCommit = parseCommit(Option(git.getRepository.resolve(mergedBranchName)).getOrElse {
|
||||
throw new RuntimeException(s"Not found branch ${mergedBranchName}")
|
||||
})
|
||||
// creates merge commit
|
||||
val mergeCommitId = createMergeCommit(mergeResultCommit.getTree().getId(), committer, message)
|
||||
|
||||
val refName = s"refs/heads/${branch}"
|
||||
val currentObjectId = git.getRepository.resolve(refName)
|
||||
val receivePack = new ReceivePack(git.getRepository)
|
||||
val receiveCommand = new ReceiveCommand(currentObjectId, mergeCommitId, refName)
|
||||
|
||||
// call pre-commit hooks
|
||||
val error = receiveHooks.flatMap { hook =>
|
||||
hook.preReceive(userName, repositoryName, receivePack, receiveCommand, committer.getName, true)
|
||||
}.headOption
|
||||
|
||||
error.foreach { error =>
|
||||
throw new RuntimeException(error)
|
||||
}
|
||||
|
||||
// update refs
|
||||
Util.updateRefs(repository, s"refs/heads/${branch}", mergeCommitId, false, committer, Some("merged"))
|
||||
val objectId = Util.updateRefs(git.getRepository, refName, mergeCommitId, false, committer, Some("merged"))
|
||||
|
||||
// call post-commit hook
|
||||
receiveHooks.foreach { hook =>
|
||||
hook.postReceive(userName, repositoryName, receivePack, receiveCommand, committer.getName, true)
|
||||
}
|
||||
|
||||
objectId
|
||||
}
|
||||
|
||||
def rebase(committer: PersonIdent, commits: Seq[RevCommit]): ObjectId = {
|
||||
def rebase(committer: PersonIdent, commits: Seq[RevCommit])(implicit s: Session): ObjectId = {
|
||||
if (checkConflict().isDefined) {
|
||||
throw new RuntimeException("This pull request can't merge automatically.")
|
||||
}
|
||||
|
||||
def _cloneCommit(commit: RevCommit, parentId: ObjectId, baseId: ObjectId): CommitBuilder = {
|
||||
val merger = MergeStrategy.RECURSIVE.newMerger(repository, true)
|
||||
val merger = MergeStrategy.RECURSIVE.newMerger(git.getRepository, true)
|
||||
merger.merge(commit.toObjectId, baseId)
|
||||
|
||||
val newCommit = new CommitBuilder()
|
||||
@@ -535,10 +646,10 @@ object MergeService {
|
||||
newCommit
|
||||
}
|
||||
|
||||
val mergeBaseTipCommit = Using.resource(new RevWalk(repository))(_.parseCommit(mergeBaseTip))
|
||||
val mergeBaseTipCommit = Using.resource(new RevWalk(git.getRepository))(_.parseCommit(mergeBaseTip))
|
||||
var previousId = mergeBaseTipCommit.getId
|
||||
|
||||
Using.resource(repository.newObjectInserter) { inserter =>
|
||||
Using.resource(git.getRepository.newObjectInserter) { inserter =>
|
||||
commits.foreach { commit =>
|
||||
val nextCommit = _cloneCommit(commit, previousId, mergeBaseTipCommit.getId)
|
||||
previousId = inserter.insert(nextCommit)
|
||||
@@ -546,17 +657,40 @@ object MergeService {
|
||||
inserter.flush()
|
||||
}
|
||||
|
||||
Util.updateRefs(repository, s"refs/heads/${branch}", previousId, false, committer, Some("rebased"))
|
||||
val refName = s"refs/heads/${branch}"
|
||||
val currentObjectId = git.getRepository.resolve(refName)
|
||||
val receivePack = new ReceivePack(git.getRepository)
|
||||
val receiveCommand = new ReceiveCommand(currentObjectId, previousId, refName)
|
||||
|
||||
// call pre-commit hooks
|
||||
val error = receiveHooks.flatMap { hook =>
|
||||
hook.preReceive(userName, repositoryName, receivePack, receiveCommand, committer.getName, true)
|
||||
}.headOption
|
||||
|
||||
error.foreach { error =>
|
||||
throw new RuntimeException(error)
|
||||
}
|
||||
|
||||
// update refs
|
||||
val objectId =
|
||||
Util.updateRefs(git.getRepository, s"refs/heads/${branch}", previousId, false, committer, Some("rebased"))
|
||||
|
||||
// call post-commit hook
|
||||
receiveHooks.foreach { hook =>
|
||||
hook.postReceive(userName, repositoryName, receivePack, receiveCommand, committer.getName, true)
|
||||
}
|
||||
|
||||
objectId
|
||||
}
|
||||
|
||||
def squash(message: String, committer: PersonIdent): ObjectId = {
|
||||
def squash(message: String, committer: PersonIdent)(implicit s: Session): ObjectId = {
|
||||
if (checkConflict().isDefined) {
|
||||
throw new RuntimeException("This pull request can't merge automatically.")
|
||||
}
|
||||
|
||||
val mergeBaseTipCommit = Using.resource(new RevWalk(repository))(_.parseCommit(mergeBaseTip))
|
||||
val mergeBaseTipCommit = Using.resource(new RevWalk(git.getRepository))(_.parseCommit(mergeBaseTip))
|
||||
val mergeBranchHeadCommit =
|
||||
Using.resource(new RevWalk(repository))(_.parseCommit(repository.resolve(mergedBranchName)))
|
||||
Using.resource(new RevWalk(git.getRepository))(_.parseCommit(git.getRepository.resolve(mergedBranchName)))
|
||||
|
||||
// Create squash commit
|
||||
val mergeCommit = new CommitBuilder()
|
||||
@@ -567,30 +701,52 @@ object MergeService {
|
||||
mergeCommit.setMessage(message)
|
||||
|
||||
// insertObject and got squash commit Object Id
|
||||
val newCommitId = Using.resource(repository.newObjectInserter) { inserter =>
|
||||
val newCommitId = Using.resource(git.getRepository.newObjectInserter) { inserter =>
|
||||
val newCommitId = inserter.insert(mergeCommit)
|
||||
inserter.flush()
|
||||
newCommitId
|
||||
}
|
||||
|
||||
Util.updateRefs(repository, mergedBranchName, newCommitId, true, committer)
|
||||
val refName = s"refs/heads/${branch}"
|
||||
val currentObjectId = git.getRepository.resolve(refName)
|
||||
val receivePack = new ReceivePack(git.getRepository)
|
||||
val receiveCommand = new ReceiveCommand(currentObjectId, newCommitId, refName)
|
||||
|
||||
// call pre-commit hooks
|
||||
val error = receiveHooks.flatMap { hook =>
|
||||
hook.preReceive(userName, repositoryName, receivePack, receiveCommand, committer.getName, true)
|
||||
}.headOption
|
||||
|
||||
error.foreach { error =>
|
||||
throw new RuntimeException(error)
|
||||
}
|
||||
|
||||
// update refs
|
||||
Util.updateRefs(git.getRepository, mergedBranchName, newCommitId, true, committer)
|
||||
|
||||
// rebase to squash commit
|
||||
Util.updateRefs(
|
||||
repository,
|
||||
val objectId = Util.updateRefs(
|
||||
git.getRepository,
|
||||
s"refs/heads/${branch}",
|
||||
repository.resolve(mergedBranchName),
|
||||
git.getRepository.resolve(mergedBranchName),
|
||||
false,
|
||||
committer,
|
||||
Some("squashed")
|
||||
)
|
||||
|
||||
// call post-commit hook
|
||||
receiveHooks.foreach { hook =>
|
||||
hook.postReceive(userName, repositoryName, receivePack, receiveCommand, committer.getName, true)
|
||||
}
|
||||
|
||||
objectId
|
||||
}
|
||||
|
||||
// return treeId
|
||||
private def createMergeCommit(treeId: ObjectId, committer: PersonIdent, message: String) =
|
||||
Util.createMergeCommit(repository, treeId, committer, message, Seq[ObjectId](mergeBaseTip, mergeTip))
|
||||
Util.createMergeCommit(git.getRepository, treeId, committer, message, Seq[ObjectId](mergeBaseTip, mergeTip))
|
||||
|
||||
private def parseCommit(id: ObjectId) = Using.resource(new RevWalk(repository))(_.parseCommit(id))
|
||||
private def parseCommit(id: ObjectId) = Using.resource(new RevWalk(git.getRepository))(_.parseCommit(id))
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -13,8 +13,8 @@ trait MilestonesService {
|
||||
title: String,
|
||||
description: Option[String],
|
||||
dueDate: Option[java.util.Date]
|
||||
)(implicit s: Session): Unit =
|
||||
Milestones insert Milestone(
|
||||
)(implicit s: Session): Int = {
|
||||
Milestones returning Milestones.map(_.milestoneId) insert Milestone(
|
||||
userName = owner,
|
||||
repositoryName = repository,
|
||||
title = title,
|
||||
@@ -22,6 +22,7 @@ trait MilestonesService {
|
||||
dueDate = dueDate,
|
||||
closedDate = None
|
||||
)
|
||||
}
|
||||
|
||||
def updateMilestone(milestone: Milestone)(implicit s: Session): Unit =
|
||||
Milestones
|
||||
|
||||
15
src/main/scala/gitbucket/core/service/PaginationHelper.scala
Normal file
15
src/main/scala/gitbucket/core/service/PaginationHelper.scala
Normal file
@@ -0,0 +1,15 @@
|
||||
package gitbucket.core.service
|
||||
|
||||
import scala.util.Try
|
||||
|
||||
object PaginationHelper {
|
||||
|
||||
def page(page: Option[String]) = {
|
||||
|
||||
page
|
||||
.flatMap(pageStr => Try(pageStr.toInt).toOption)
|
||||
.map(Math.max(1, _)) // remove negative pages
|
||||
.getOrElse(1)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -24,13 +24,15 @@ trait ProtectedBranchService {
|
||||
}
|
||||
.map {
|
||||
case (t1, contexts) =>
|
||||
new ProtectedBranchInfo(t1.userName, t1.repositoryName, true, contexts, t1.statusCheckAdmin)
|
||||
new ProtectedBranchInfo(t1.userName, t1.repositoryName, t1.branch, true, contexts, t1.statusCheckAdmin)
|
||||
}
|
||||
|
||||
def getProtectedBranchInfo(owner: String, repository: String, branch: String)(
|
||||
implicit session: Session
|
||||
): ProtectedBranchInfo =
|
||||
getProtectedBranchInfoOpt(owner, repository, branch).getOrElse(ProtectedBranchInfo.disabled(owner, repository))
|
||||
getProtectedBranchInfoOpt(owner, repository, branch).getOrElse(
|
||||
ProtectedBranchInfo.disabled(owner, repository, branch)
|
||||
)
|
||||
|
||||
def getProtectedBranchList(owner: String, repository: String)(implicit session: Session): List[String] =
|
||||
ProtectedBranches.filter(_.byRepository(owner, repository)).map(_.branch).list
|
||||
@@ -66,7 +68,22 @@ object ProtectedBranchService {
|
||||
repository: String,
|
||||
receivePack: ReceivePack,
|
||||
command: ReceiveCommand,
|
||||
pusher: String
|
||||
pusher: String,
|
||||
mergePullRequest: Boolean
|
||||
)(implicit session: Session): Option[String] = {
|
||||
if (mergePullRequest == true) {
|
||||
None
|
||||
} else {
|
||||
checkBranchProtection(owner, repository, receivePack, command, pusher)
|
||||
}
|
||||
}
|
||||
|
||||
private def checkBranchProtection(
|
||||
owner: String,
|
||||
repository: String,
|
||||
receivePack: ReceivePack,
|
||||
command: ReceiveCommand,
|
||||
pusher: String,
|
||||
)(implicit session: Session): Option[String] = {
|
||||
val branch = command.getRefName.stripPrefix("refs/heads/")
|
||||
if (branch != command.getRefName) {
|
||||
@@ -91,6 +108,7 @@ object ProtectedBranchService {
|
||||
case class ProtectedBranchInfo(
|
||||
owner: String,
|
||||
repository: String,
|
||||
branch: String,
|
||||
enabled: Boolean,
|
||||
/**
|
||||
* Require status checks to pass before merging
|
||||
@@ -151,7 +169,7 @@ object ProtectedBranchService {
|
||||
if (contexts.isEmpty) {
|
||||
Set.empty
|
||||
} else {
|
||||
contexts.toSet -- getCommitStatues(owner, repository, sha1)
|
||||
contexts.toSet -- getCommitStatuses(owner, repository, sha1)
|
||||
.filter(_.state == CommitState.SUCCESS)
|
||||
.map(_.context)
|
||||
.toSet
|
||||
@@ -165,7 +183,7 @@ object ProtectedBranchService {
|
||||
}
|
||||
}
|
||||
object ProtectedBranchInfo {
|
||||
def disabled(owner: String, repository: String): ProtectedBranchInfo =
|
||||
ProtectedBranchInfo(owner, repository, false, Nil, false)
|
||||
def disabled(owner: String, repository: String, branch: String): ProtectedBranchInfo =
|
||||
ProtectedBranchInfo(owner, repository, branch, false, Nil, false)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,13 +7,14 @@ import difflib.{Delta, DiffUtils}
|
||||
import gitbucket.core.service.RepositoryService.RepositoryInfo
|
||||
import gitbucket.core.api.JsonFormat
|
||||
import gitbucket.core.controller.Context
|
||||
import gitbucket.core.model.activity.OpenPullRequestInfo
|
||||
import gitbucket.core.plugin.PluginRegistry
|
||||
import gitbucket.core.service.SystemSettingsService.SystemSettings
|
||||
import gitbucket.core.util.Directory._
|
||||
import gitbucket.core.util.Implicits._
|
||||
import gitbucket.core.util.JGitUtil
|
||||
import gitbucket.core.util.StringUtil._
|
||||
import gitbucket.core.util.JGitUtil.{CommitInfo, DiffInfo}
|
||||
import gitbucket.core.util.JGitUtil.{CommitInfo, DiffInfo, getBranches}
|
||||
import gitbucket.core.view
|
||||
import gitbucket.core.view.helpers
|
||||
import org.eclipse.jgit.api.Git
|
||||
@@ -57,6 +58,15 @@ trait PullRequestService {
|
||||
.map(pr => pr.isDraft)
|
||||
.update(false)
|
||||
|
||||
def updateBaseBranch(owner: String, repository: String, issueId: Int, baseBranch: String, commitIdTo: String)(
|
||||
implicit s: Session
|
||||
): Unit = {
|
||||
PullRequests
|
||||
.filter(_.byPrimaryKey(owner, repository, issueId))
|
||||
.map(pr => pr.branch -> pr.commitIdTo)
|
||||
.update((baseBranch, commitIdTo))
|
||||
}
|
||||
|
||||
def getPullRequestCountGroupByUser(closed: Boolean, owner: Option[String], repository: Option[String])(
|
||||
implicit s: Session
|
||||
): List[PullRequestCount] =
|
||||
@@ -135,13 +145,14 @@ trait PullRequestService {
|
||||
)
|
||||
|
||||
// record activity
|
||||
recordPullRequestActivity(
|
||||
val openPullRequestInfo = OpenPullRequestInfo(
|
||||
originRepository.owner,
|
||||
originRepository.name,
|
||||
loginAccount.userName,
|
||||
issueId,
|
||||
baseIssue.title
|
||||
)
|
||||
recordActivity(openPullRequestInfo)
|
||||
|
||||
// call web hook
|
||||
callPullRequestWebHook("opened", originRepository, issueId, loginAccount, settings)
|
||||
@@ -291,6 +302,76 @@ trait PullRequestService {
|
||||
}
|
||||
}
|
||||
|
||||
def updatePullRequestsByApi(
|
||||
repository: RepositoryInfo,
|
||||
issueId: Int,
|
||||
loginAccount: Account,
|
||||
settings: SystemSettings,
|
||||
title: Option[String],
|
||||
body: Option[String],
|
||||
state: Option[String],
|
||||
base: Option[String]
|
||||
)(
|
||||
implicit s: Session,
|
||||
c: JsonFormat.Context
|
||||
): Unit = {
|
||||
getPullRequest(repository.owner, repository.name, issueId).foreach {
|
||||
case (issue, pr) =>
|
||||
if (Repositories.filter(_.byRepository(pr.userName, pr.repositoryName)).exists.run) {
|
||||
// Update base branch
|
||||
base.foreach { _base =>
|
||||
if (pr.branch != _base) {
|
||||
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
|
||||
getBranches(git, repository.repository.defaultBranch, origin = true)
|
||||
.find(_.name == _base)
|
||||
.foreach(br => updateBaseBranch(repository.owner, repository.name, issueId, br.name, br.commitId))
|
||||
}
|
||||
createComment(
|
||||
repository.owner,
|
||||
repository.name,
|
||||
loginAccount.userName,
|
||||
issue.issueId,
|
||||
pr.branch + "\r\n" + _base,
|
||||
"change_base_branch"
|
||||
)
|
||||
}
|
||||
}
|
||||
// Update title and content
|
||||
title.foreach { _title =>
|
||||
updateIssue(repository.owner, repository.name, issueId, _title, body)
|
||||
if (issue.title != _title) {
|
||||
createComment(
|
||||
repository.owner,
|
||||
repository.name,
|
||||
loginAccount.userName,
|
||||
issue.issueId,
|
||||
issue.title + "\r\n" + _title,
|
||||
"change_title"
|
||||
)
|
||||
}
|
||||
}
|
||||
// Update state
|
||||
val action = (state, issue.closed) match {
|
||||
case (Some("open"), true) =>
|
||||
updateClosed(repository.owner, repository.name, issueId, closed = false)
|
||||
"reopened"
|
||||
case (Some("closed"), false) =>
|
||||
updateClosed(repository.owner, repository.name, issueId, closed = true)
|
||||
"closed"
|
||||
case _ => "edited"
|
||||
}
|
||||
// Call web hook
|
||||
callPullRequestWebHookByRequestBranch(
|
||||
action,
|
||||
getRepository(repository.owner, repository.name).get,
|
||||
pr.requestBranch,
|
||||
loginAccount,
|
||||
settings
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def getPullRequestByRequestCommit(
|
||||
userName: String,
|
||||
repositoryName: String,
|
||||
@@ -530,7 +611,7 @@ object PullRequestService {
|
||||
|
||||
case class MergeStatus(
|
||||
conflictMessage: Option[String],
|
||||
commitStatues: List[CommitStatus],
|
||||
commitStatuses: List[CommitStatus],
|
||||
branchProtection: ProtectedBranchService.ProtectedBranchInfo,
|
||||
branchIsOutOfDate: Boolean,
|
||||
hasUpdatePermission: Boolean,
|
||||
@@ -541,7 +622,7 @@ object PullRequestService {
|
||||
|
||||
val hasConflict = conflictMessage.isDefined
|
||||
val statuses: List[CommitStatus] =
|
||||
commitStatues ++ (branchProtection.contexts.toSet -- commitStatues.map(_.context).toSet)
|
||||
commitStatuses ++ (branchProtection.contexts.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)
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
package gitbucket.core.service
|
||||
|
||||
import gitbucket.core.controller.Context
|
||||
import gitbucket.core.model.{Account, ReleaseTag, ReleaseAsset}
|
||||
import gitbucket.core.model.{Account, ReleaseAsset, ReleaseTag}
|
||||
import gitbucket.core.model.Profile.profile.blockingApi._
|
||||
import gitbucket.core.model.Profile._
|
||||
import gitbucket.core.model.Profile.dateColumnType
|
||||
import gitbucket.core.util.JGitUtil
|
||||
|
||||
trait ReleaseService {
|
||||
self: AccountService with RepositoryService =>
|
||||
@@ -35,10 +36,9 @@ trait ReleaseService {
|
||||
ReleaseAssets.filter(x => x.byTag(owner, repository, tag)).list
|
||||
}
|
||||
|
||||
def getReleaseAssetsMap(owner: String, repository: String)(
|
||||
def getReleaseAssetsMap(owner: String, repository: String, releases: Seq[ReleaseTag])(
|
||||
implicit s: Session
|
||||
): Map[ReleaseTag, Seq[ReleaseAsset]] = {
|
||||
val releases = getReleases(owner, repository)
|
||||
releases.map(rel => (rel -> getReleaseAssets(owner, repository, rel.tag))).toMap
|
||||
}
|
||||
|
||||
@@ -76,20 +76,18 @@ trait ReleaseService {
|
||||
ReleaseTags.filter(x => x.byRepository(owner, repository)).sortBy(x => x.updatedDate).list
|
||||
}
|
||||
|
||||
def getRelease(owner: String, repository: String, tag: String)(implicit s: Session): Option[ReleaseTag] = {
|
||||
//Releases filter (_.byPrimaryKey(owner, repository, releaseId)) firstOption
|
||||
ReleaseTags filter (_.byTag(owner, repository, tag)) firstOption
|
||||
def getReleases(owner: String, repository: String, tags: Seq[JGitUtil.TagInfo])(
|
||||
implicit s: Session
|
||||
): Seq[ReleaseTag] = {
|
||||
ReleaseTags
|
||||
.filter(x => x.byRepository(owner, repository))
|
||||
.filter(x => x.tag inSetBind tags.map(_.name))
|
||||
.sortBy(x => x.updatedDate)
|
||||
.list
|
||||
}
|
||||
def getRelease(owner: String, repository: String, tag: String)(implicit s: Session): Option[ReleaseTag] = {
|
||||
ReleaseTags.filter(_.byTag(owner, repository, tag)).firstOption
|
||||
}
|
||||
|
||||
// def getReleaseByTag(owner: String, repository: String, tag: String)(implicit s: Session): Option[Release] = {
|
||||
// Releases filter (_.byTag(owner, repository, tag)) firstOption
|
||||
// }
|
||||
//
|
||||
// def getRelease(owner: String, repository: String, releaseId: String)(implicit s: Session): Option[Release] = {
|
||||
// if (isInteger(releaseId))
|
||||
// getRelease(owner, repository, releaseId.toInt)
|
||||
// else None
|
||||
// }
|
||||
|
||||
def updateRelease(owner: String, repository: String, tag: String, title: String, content: Option[String])(
|
||||
implicit s: Session
|
||||
@@ -107,3 +105,9 @@ trait ReleaseService {
|
||||
ReleaseTags filter (_.byPrimaryKey(owner, repository, tag)) delete
|
||||
}
|
||||
}
|
||||
|
||||
object ReleaseService {
|
||||
|
||||
val ReleaseLimit = 10
|
||||
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package gitbucket.core.service
|
||||
import gitbucket.core.api.JsonFormat
|
||||
import gitbucket.core.model.{Account, WebHook}
|
||||
import gitbucket.core.model.Profile._
|
||||
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
|
||||
import gitbucket.core.service.WebHookService.WebHookPushPayload
|
||||
@@ -17,7 +17,12 @@ import org.eclipse.jgit.transport.{ReceiveCommand, ReceivePack}
|
||||
import scala.util.Using
|
||||
|
||||
trait RepositoryCommitFileService {
|
||||
self: AccountService with ActivityService with IssuesService with PullRequestService with WebHookPullRequestService =>
|
||||
self: AccountService
|
||||
with ActivityService
|
||||
with IssuesService
|
||||
with PullRequestService
|
||||
with WebHookPullRequestService
|
||||
with RepositoryService =>
|
||||
import RepositoryCommitFileService._
|
||||
|
||||
def commitFiles(
|
||||
@@ -149,9 +154,9 @@ trait RepositoryCommitFileService {
|
||||
val receivePack = new ReceivePack(git.getRepository)
|
||||
val receiveCommand = new ReceiveCommand(headTip, commitId, headName)
|
||||
|
||||
// call post commit hook
|
||||
// call pre-commit hook
|
||||
val error = PluginRegistry().getReceiveHooks.flatMap { hook =>
|
||||
hook.preReceive(repository.owner, repository.name, receivePack, receiveCommand, committerName)
|
||||
hook.preReceive(repository.owner, repository.name, receivePack, receiveCommand, committerName, false)
|
||||
}.headOption
|
||||
|
||||
error match {
|
||||
@@ -175,8 +180,10 @@ trait RepositoryCommitFileService {
|
||||
updatePullRequests(repository.owner, repository.name, branch, loginAccount, "synchronize", settings)
|
||||
|
||||
// record activity
|
||||
updateLastActivityDate(repository.owner, repository.name)
|
||||
val commitInfo = new CommitInfo(JGitUtil.getRevCommitFromId(git, commitId))
|
||||
recordPushActivity(repository.owner, repository.name, loginAccount.userName, branch, List(commitInfo))
|
||||
val pushInfo = PushInfo(repository.owner, repository.name, loginAccount.userName, branch, List(commitInfo))
|
||||
recordActivity(pushInfo)
|
||||
|
||||
// create issue comment by commit message
|
||||
createIssueComment(repository.owner, repository.name, commitInfo)
|
||||
@@ -186,15 +193,23 @@ trait RepositoryCommitFileService {
|
||||
closeIssuesFromMessage(message, committerName, repository.owner, repository.name).foreach { issueId =>
|
||||
getIssue(repository.owner, repository.name, issueId.toString).foreach { issue =>
|
||||
callIssuesWebHook("closed", repository, issue, loginAccount, settings)
|
||||
val closeIssueInfo = CloseIssueInfo(
|
||||
repository.owner,
|
||||
repository.name,
|
||||
loginAccount.userName,
|
||||
issue.issueId,
|
||||
issue.title
|
||||
)
|
||||
recordActivity(closeIssueInfo)
|
||||
PluginRegistry().getIssueHooks
|
||||
.foreach(_.closedByCommitComment(issue, repository, message, loginAccount))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// call post commit hook
|
||||
// call post-commit hook
|
||||
PluginRegistry().getReceiveHooks.foreach { hook =>
|
||||
hook.postReceive(repository.owner, repository.name, receivePack, receiveCommand, committerName)
|
||||
hook.postReceive(repository.owner, repository.name, receivePack, receiveCommand, committerName, false)
|
||||
}
|
||||
|
||||
val commit = new JGitUtil.CommitInfo(JGitUtil.getRevCommitFromId(git, commitId))
|
||||
|
||||
@@ -4,6 +4,7 @@ import java.nio.file.Files
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
import gitbucket.core.model.Profile.profile.blockingApi._
|
||||
import gitbucket.core.model.activity.{CreateRepositoryInfo, ForkInfo}
|
||||
import gitbucket.core.util.Directory._
|
||||
import gitbucket.core.util.{FileUtil, JGitUtil, LockUtil}
|
||||
import gitbucket.core.model.{Account, Role}
|
||||
@@ -160,7 +161,7 @@ trait RepositoryCreationService {
|
||||
createWikiRepository(loginAccount, owner, name)
|
||||
|
||||
// Record activity
|
||||
recordCreateRepositoryActivity(owner, name, loginUserName)
|
||||
recordActivity(CreateRepositoryInfo(owner, name, loginUserName))
|
||||
|
||||
// Call hooks
|
||||
PluginRegistry().getRepositoryHooks.foreach(_.created(owner, name))
|
||||
@@ -180,12 +181,14 @@ trait RepositoryCreationService {
|
||||
Database() withTransaction { implicit session =>
|
||||
val originUserName = repository.repository.originUserName.getOrElse(repository.owner)
|
||||
val originRepositoryName = repository.repository.originRepositoryName.getOrElse(repository.name)
|
||||
val originDefaultBranchName = repository.repository.defaultBranch
|
||||
|
||||
insertRepository(
|
||||
repositoryName = repository.name,
|
||||
userName = accountName,
|
||||
description = repository.repository.description,
|
||||
isPrivate = repository.repository.isPrivate,
|
||||
defaultBranch = originDefaultBranchName,
|
||||
originRepositoryName = Some(originRepositoryName),
|
||||
originUserName = Some(originUserName),
|
||||
parentRepositoryName = Some(repository.name),
|
||||
@@ -227,7 +230,8 @@ trait RepositoryCreationService {
|
||||
}
|
||||
|
||||
// Record activity
|
||||
recordForkActivity(repository.owner, repository.name, loginUserName, accountName)
|
||||
val forkInfo = ForkInfo(repository.owner, repository.name, loginUserName, accountName)
|
||||
recordActivity(forkInfo)
|
||||
|
||||
// Call hooks
|
||||
PluginRegistry().getRepositoryHooks.foreach(_.forked(repository.owner, accountName, repository.name))
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package gitbucket.core.service
|
||||
|
||||
import gitbucket.core.api.JsonFormat
|
||||
import gitbucket.core.controller.Context
|
||||
import gitbucket.core.util._
|
||||
import gitbucket.core.util.SyntaxSugars._
|
||||
@@ -9,14 +8,11 @@ import gitbucket.core.model.Profile._
|
||||
import gitbucket.core.model.Profile.profile.blockingApi._
|
||||
import gitbucket.core.model.Profile.dateColumnType
|
||||
import gitbucket.core.plugin.PluginRegistry
|
||||
import gitbucket.core.service.WebHookService.WebHookPushPayload
|
||||
import gitbucket.core.util.Directory.{getRepositoryDir, getRepositoryFilesDir, getTemporaryDir, getWikiRepositoryDir}
|
||||
import gitbucket.core.util.JGitUtil.{CommitInfo, FileInfo}
|
||||
import gitbucket.core.util.JGitUtil.FileInfo
|
||||
import org.apache.commons.io.FileUtils
|
||||
import org.eclipse.jgit.api.Git
|
||||
import org.eclipse.jgit.dircache.{DirCache, DirCacheBuilder}
|
||||
import org.eclipse.jgit.lib.{Repository => _, _}
|
||||
import org.eclipse.jgit.transport.{ReceiveCommand, ReceivePack}
|
||||
import scala.util.Using
|
||||
|
||||
trait RepositoryService {
|
||||
@@ -38,6 +34,7 @@ trait RepositoryService {
|
||||
userName: String,
|
||||
description: Option[String],
|
||||
isPrivate: Boolean,
|
||||
defaultBranch: String = "master",
|
||||
originRepositoryName: Option[String] = None,
|
||||
originUserName: Option[String] = None,
|
||||
parentRepositoryName: Option[String] = None,
|
||||
@@ -49,7 +46,7 @@ trait RepositoryService {
|
||||
repositoryName = repositoryName,
|
||||
isPrivate = isPrivate,
|
||||
description = description,
|
||||
defaultBranch = "master",
|
||||
defaultBranch = defaultBranch,
|
||||
registeredDate = currentDate,
|
||||
updatedDate = currentDate,
|
||||
lastActivityDate = currentDate,
|
||||
@@ -119,15 +116,6 @@ trait RepositoryService {
|
||||
}
|
||||
.update(newUserName, newRepositoryName)
|
||||
|
||||
// Updates activity fk before deleting repository because activity is sorted by activityId
|
||||
// and it can't be changed by deleting-and-inserting record.
|
||||
Activities.filter(_.byRepository(oldUserName, oldRepositoryName)).list.foreach { activity =>
|
||||
Activities
|
||||
.filter(_.activityId === activity.activityId.bind)
|
||||
.map(x => (x.userName, x.repositoryName))
|
||||
.update(newUserName, newRepositoryName)
|
||||
}
|
||||
|
||||
deleteRepositoryOnModel(oldUserName, oldRepositoryName)
|
||||
|
||||
RepositoryWebHooks.insertAll(
|
||||
@@ -213,50 +201,6 @@ trait RepositoryService {
|
||||
collaborators.map(_.copy(userName = newUserName, repositoryName = newRepositoryName)): _*
|
||||
)
|
||||
|
||||
// Update activity messages
|
||||
Activities
|
||||
.filter { t =>
|
||||
(t.message like s"%:${oldUserName}/${oldRepositoryName}]%") ||
|
||||
(t.message like s"%:${oldUserName}/${oldRepositoryName}#%") ||
|
||||
(t.message like s"%:${oldUserName}/${oldRepositoryName}@%")
|
||||
}
|
||||
.map { t =>
|
||||
t.activityId -> t.message
|
||||
}
|
||||
.list
|
||||
.foreach {
|
||||
case (activityId, message) =>
|
||||
Activities
|
||||
.filter(_.activityId === activityId.bind)
|
||||
.map(_.message)
|
||||
.update(
|
||||
message
|
||||
.replace(
|
||||
s"[repo:${oldUserName}/${oldRepositoryName}]",
|
||||
s"[repo:${newUserName}/${newRepositoryName}]"
|
||||
)
|
||||
.replace(
|
||||
s"[branch:${oldUserName}/${oldRepositoryName}#",
|
||||
s"[branch:${newUserName}/${newRepositoryName}#"
|
||||
)
|
||||
.replace(
|
||||
s"[tag:${oldUserName}/${oldRepositoryName}#",
|
||||
s"[tag:${newUserName}/${newRepositoryName}#"
|
||||
)
|
||||
.replace(
|
||||
s"[pullreq:${oldUserName}/${oldRepositoryName}#",
|
||||
s"[pullreq:${newUserName}/${newRepositoryName}#"
|
||||
)
|
||||
.replace(
|
||||
s"[issue:${oldUserName}/${oldRepositoryName}#",
|
||||
s"[issue:${newUserName}/${newRepositoryName}#"
|
||||
)
|
||||
.replace(
|
||||
s"[commit:${oldUserName}/${oldRepositoryName}@",
|
||||
s"[commit:${newUserName}/${newRepositoryName}@"
|
||||
)
|
||||
)
|
||||
}
|
||||
// Move git repository
|
||||
defining(getRepositoryDir(oldUserName, oldRepositoryName)) { dir =>
|
||||
if (dir.isDirectory) {
|
||||
@@ -304,7 +248,7 @@ trait RepositoryService {
|
||||
}
|
||||
|
||||
private def deleteRepositoryOnModel(userName: String, repositoryName: String)(implicit s: Session): Unit = {
|
||||
Activities.filter(_.byRepository(userName, repositoryName)).delete
|
||||
// Activities.filter(_.byRepository(userName, repositoryName)).delete
|
||||
Collaborators.filter(_.byRepository(userName, repositoryName)).delete
|
||||
CommitComments.filter(_.byRepository(userName, repositoryName)).delete
|
||||
IssueLabels.filter(_.byRepository(userName, repositoryName)).delete
|
||||
@@ -399,6 +343,10 @@ trait RepositoryService {
|
||||
repository.originUserName.getOrElse(repository.userName),
|
||||
repository.originRepositoryName.getOrElse(repository.repositoryName)
|
||||
),
|
||||
getOpenMilestones(
|
||||
repository.originUserName.getOrElse(repository.userName),
|
||||
repository.originRepositoryName.getOrElse(repository.repositoryName)
|
||||
),
|
||||
getRepositoryManagers(repository.userName, repository.repositoryName)
|
||||
)
|
||||
}
|
||||
@@ -432,6 +380,21 @@ trait RepositoryService {
|
||||
.list
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the all public repositories.
|
||||
*
|
||||
* @return the repository information list
|
||||
*/
|
||||
def getPublicRepositories(withoutPhysicalInfo: Boolean = false)(implicit s: Session): List[RepositoryInfo] = {
|
||||
Repositories
|
||||
.filter { t1 =>
|
||||
t1.isPrivate === false.bind
|
||||
}
|
||||
.sortBy(_.lastActivityDate desc)
|
||||
.list
|
||||
.map(createRepositoryInfo(_, withoutPhysicalInfo))
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of repositories which are owned by the specified user.
|
||||
* This list includes group repositories if the specified user is a member of the group.
|
||||
@@ -459,6 +422,7 @@ trait RepositoryService {
|
||||
/**
|
||||
* Returns the list of visible repositories for the specified user.
|
||||
* If repositoryUserName is given then filters results by repository owner.
|
||||
* This function is for plugin compatibility.
|
||||
*
|
||||
* @param loginAccount the logged in account
|
||||
* @param repositoryUserName the repository owner (if None then returns all repositories which are visible for logged in user)
|
||||
@@ -470,23 +434,42 @@ trait RepositoryService {
|
||||
loginAccount: Option[Account],
|
||||
repositoryUserName: Option[String] = None,
|
||||
withoutPhysicalInfo: Boolean = false
|
||||
)(implicit s: Session): List[RepositoryInfo] =
|
||||
getVisibleRepositories(loginAccount, repositoryUserName, withoutPhysicalInfo, false)
|
||||
|
||||
/**
|
||||
* Returns the list of visible repositories for the specified user.
|
||||
* If repositoryUserName is given then filters results by repository owner.
|
||||
*
|
||||
* @param loginAccount the logged in account
|
||||
* @param repositoryUserName the repository owner (if None then returns all repositories which are visible for logged in user)
|
||||
* @param withoutPhysicalInfo if true then the result does not include physical repository information such as commit count,
|
||||
* branches and tags
|
||||
* @param limit if true then result will include only repositories owned by the login account. otherwise, result will be all visible repositories.
|
||||
* @return the repository information which is sorted in descending order of lastActivityDate.
|
||||
*/
|
||||
def getVisibleRepositories(
|
||||
loginAccount: Option[Account],
|
||||
repositoryUserName: Option[String],
|
||||
withoutPhysicalInfo: Boolean,
|
||||
limit: Boolean
|
||||
)(implicit s: Session): List[RepositoryInfo] = {
|
||||
(loginAccount match {
|
||||
// for Administrators
|
||||
case Some(x) if (x.isAdmin) =>
|
||||
case Some(x) if (x.isAdmin && !limit) =>
|
||||
Repositories
|
||||
.join(Accounts)
|
||||
.on(_.userName === _.userName)
|
||||
.filter { case (t1, t2) => t2.removed === false.bind }
|
||||
.map { case (t1, t2) => t1 }
|
||||
// for Normal Users
|
||||
case Some(x) if (!x.isAdmin) =>
|
||||
case Some(x) if (!x.isAdmin || limit) =>
|
||||
Repositories
|
||||
.join(Accounts)
|
||||
.on(_.userName === _.userName)
|
||||
.filter {
|
||||
case (t1, t2) =>
|
||||
(t2.removed === false.bind) && ((t1.isPrivate === false.bind) || (t1.userName === x.userName) ||
|
||||
(t2.removed === false.bind) && ((t1.isPrivate === false.bind && !limit.bind) || (t1.userName === x.userName) ||
|
||||
(t1.userName in GroupMembers.filter(_.userName === x.userName.bind).map(_.groupName)) ||
|
||||
(Collaborators.filter { t3 =>
|
||||
t3.byRepository(t1.userName, t1.repositoryName) &&
|
||||
@@ -734,6 +717,14 @@ trait RepositoryService {
|
||||
(t.originUserName === userName.bind) && (t.originRepositoryName === repositoryName.bind)
|
||||
}.length).first
|
||||
|
||||
private def getOpenMilestones(userName: String, repositoryName: String)(implicit s: Session): Int =
|
||||
Query(
|
||||
Milestones
|
||||
.filter(_.byRepository(userName, repositoryName))
|
||||
.filter(_.closedDate.isEmpty)
|
||||
.length
|
||||
).first
|
||||
|
||||
def getForkedRepositories(userName: String, repositoryName: String)(implicit s: Session): List[Repository] =
|
||||
Repositories
|
||||
.filter { t =>
|
||||
@@ -765,9 +756,10 @@ trait RepositoryService {
|
||||
|
||||
// Get template file from project root. When didn't find, will lookup default folder.
|
||||
Using.resource(Git.open(Directory.getRepositoryDir(repository.owner, repository.name))) { git =>
|
||||
choiceTemplate(JGitUtil.getFileList(git, repository.repository.defaultBranch, "."))
|
||||
// maxFiles = 1 means not get commit info because the only objectId and filename are necessary here
|
||||
choiceTemplate(JGitUtil.getFileList(git, repository.repository.defaultBranch, ".", maxFiles = 1))
|
||||
.orElse {
|
||||
choiceTemplate(JGitUtil.getFileList(git, repository.repository.defaultBranch, ".gitbucket"))
|
||||
choiceTemplate(JGitUtil.getFileList(git, repository.repository.defaultBranch, ".gitbucket", maxFiles = 1))
|
||||
}
|
||||
.map { file =>
|
||||
JGitUtil.getContentFromId(git, file.id, true).collect {
|
||||
@@ -786,6 +778,7 @@ object RepositoryService {
|
||||
issueCount: Int,
|
||||
pullCount: Int,
|
||||
forkedCount: Int,
|
||||
milestoneCount: Int,
|
||||
branchList: Seq[String],
|
||||
tags: Seq[JGitUtil.TagInfo],
|
||||
managers: Seq[String]
|
||||
@@ -800,15 +793,27 @@ object RepositoryService {
|
||||
issueCount: Int,
|
||||
pullCount: Int,
|
||||
forkedCount: Int,
|
||||
milestoneCount: Int,
|
||||
managers: Seq[String]
|
||||
) =
|
||||
this(repo.owner, repo.name, model, issueCount, pullCount, forkedCount, repo.branchList, repo.tags, managers)
|
||||
this(
|
||||
repo.owner,
|
||||
repo.name,
|
||||
model,
|
||||
issueCount,
|
||||
pullCount,
|
||||
forkedCount,
|
||||
milestoneCount,
|
||||
repo.branchList,
|
||||
repo.tags,
|
||||
managers
|
||||
)
|
||||
|
||||
/**
|
||||
* Creates instance without issue and pull request count.
|
||||
* Creates instance without issue, pull request, and milestone count.
|
||||
*/
|
||||
def this(repo: JGitUtil.RepositoryInfo, model: Repository, forkedCount: Int, managers: Seq[String]) =
|
||||
this(repo.owner, repo.name, model, 0, 0, forkedCount, repo.branchList, repo.tags, managers)
|
||||
this(repo.owner, repo.name, model, 0, 0, forkedCount, 0, repo.branchList, repo.tags, managers)
|
||||
|
||||
def httpUrl(implicit context: Context): String = RepositoryService.httpUrl(owner, name)
|
||||
def sshUrl(implicit context: Context): Option[String] = RepositoryService.sshUrl(owner, name)
|
||||
@@ -834,4 +839,9 @@ object RepositoryService {
|
||||
} else None
|
||||
def openRepoUrl(openUrl: String)(implicit context: Context): String =
|
||||
s"github-${context.platform}://openRepo/${openUrl}"
|
||||
|
||||
def readmeFiles: Seq[String] =
|
||||
PluginRegistry().renderableExtensions.map { extension =>
|
||||
s"readme.${extension}"
|
||||
} ++ Seq("readme.txt", "readme")
|
||||
}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
package gitbucket.core.service
|
||||
|
||||
import gitbucket.core.model.{Session, Issue, Account}
|
||||
import gitbucket.core.model.{Account, Issue, Repository, Session}
|
||||
import gitbucket.core.util.Implicits
|
||||
import gitbucket.core.controller.Context
|
||||
import Implicits.request2Session
|
||||
import gitbucket.core.model.Profile.{Accounts, Repositories}
|
||||
import gitbucket.core.model.Profile.profile.blockingApi._
|
||||
|
||||
/**
|
||||
* This service is used for a view helper mainly.
|
||||
@@ -23,21 +25,41 @@ trait RequestCache
|
||||
private implicit def context2Session(implicit context: Context): Session =
|
||||
request2Session(context.request)
|
||||
|
||||
def getIssue(userName: String, repositoryName: String, issueId: String)(implicit context: Context): Option[Issue] = {
|
||||
def getIssueFromCache(userName: String, repositoryName: String, issueId: String)(
|
||||
implicit context: Context
|
||||
): Option[Issue] = {
|
||||
context.cache(s"issue.${userName}/${repositoryName}#${issueId}") {
|
||||
super.getIssue(userName, repositoryName, issueId)
|
||||
}
|
||||
}
|
||||
|
||||
def getAccountByUserName(userName: String)(implicit context: Context): Option[Account] = {
|
||||
def getAccountByUserNameFromCache(userName: String)(implicit context: Context): Option[Account] = {
|
||||
context.cache(s"account.${userName}") {
|
||||
super.getAccountByUserName(userName)
|
||||
}
|
||||
}
|
||||
|
||||
def getAccountByMailAddress(mailAddress: String)(implicit context: Context): Option[Account] = {
|
||||
def getAccountByMailAddressFromCache(mailAddress: String)(implicit context: Context): Option[Account] = {
|
||||
context.cache(s"account.${mailAddress}") {
|
||||
super.getAccountByMailAddress(mailAddress)
|
||||
}
|
||||
}
|
||||
|
||||
def getRepositoryInfoFromCache(userName: String, repositoryName: String)(
|
||||
implicit context: Context
|
||||
): Option[Repository] = {
|
||||
context.cache(s"repository.${userName}/${repositoryName}") {
|
||||
Repositories
|
||||
.join(Accounts)
|
||||
.on(_.userName === _.userName)
|
||||
.filter {
|
||||
case (t1, t2) =>
|
||||
t1.byRepository(userName, repositoryName) && t2.removed === false.bind
|
||||
}
|
||||
.map {
|
||||
case (t1, t2) => t1
|
||||
}
|
||||
.firstOption
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,9 +21,14 @@ trait SystemSettingsService {
|
||||
props.setProperty(AllowAccountRegistration, settings.allowAccountRegistration.toString)
|
||||
props.setProperty(AllowAnonymousAccess, settings.allowAnonymousAccess.toString)
|
||||
props.setProperty(IsCreateRepoOptionPublic, settings.isCreateRepoOptionPublic.toString)
|
||||
props.setProperty(RepositoryOperationCreate, settings.repositoryOperation.create.toString)
|
||||
props.setProperty(RepositoryOperationDelete, settings.repositoryOperation.delete.toString)
|
||||
props.setProperty(RepositoryOperationRename, settings.repositoryOperation.rename.toString)
|
||||
props.setProperty(RepositoryOperationTransfer, settings.repositoryOperation.transfer.toString)
|
||||
props.setProperty(RepositoryOperationFork, settings.repositoryOperation.fork.toString)
|
||||
props.setProperty(Gravatar, settings.gravatar.toString)
|
||||
props.setProperty(Notification, settings.notification.toString)
|
||||
settings.activityLogLimit.foreach(x => props.setProperty(ActivityLogLimit, x.toString))
|
||||
props.setProperty(LimitVisibleRepositories, settings.limitVisibleRepositories.toString)
|
||||
props.setProperty(SshEnabled, settings.ssh.enabled.toString)
|
||||
settings.ssh.sshHost.foreach(x => props.setProperty(SshHost, x.trim))
|
||||
settings.ssh.sshPort.foreach(x => props.setProperty(SshPort, x.toString))
|
||||
@@ -69,9 +74,15 @@ trait SystemSettingsService {
|
||||
}
|
||||
}
|
||||
props.setProperty(SkinName, settings.skinName.toString)
|
||||
settings.userDefinedCss.foreach(x => props.setProperty(UserDefinedCss, x))
|
||||
props.setProperty(ShowMailAddress, settings.showMailAddress.toString)
|
||||
props.setProperty(WebHookBlockPrivateAddress, settings.webHook.blockPrivateAddress.toString)
|
||||
props.setProperty(WebHookWhitelist, settings.webHook.whitelist.mkString("\n"))
|
||||
props.setProperty(UploadMaxFileSize, settings.upload.maxFileSize.toString)
|
||||
props.setProperty(UploadTimeout, settings.upload.timeout.toString)
|
||||
props.setProperty(UploadLargeMaxFileSize, settings.upload.largeMaxFileSize.toString)
|
||||
props.setProperty(UploadLargeTimeout, settings.upload.largeTimeout.toString)
|
||||
props.setProperty(RepositoryViewerMaxFiles, settings.repositoryViewer.maxFiles.toString)
|
||||
|
||||
Using.resource(new java.io.FileOutputStream(GitBucketConf)) { out =>
|
||||
props.store(out, null)
|
||||
@@ -92,15 +103,26 @@ trait SystemSettingsService {
|
||||
getValue(props, AllowAccountRegistration, false),
|
||||
getValue(props, AllowAnonymousAccess, true),
|
||||
getValue(props, IsCreateRepoOptionPublic, true),
|
||||
RepositoryOperation(
|
||||
create = getValue(props, RepositoryOperationCreate, true),
|
||||
delete = getValue(props, RepositoryOperationDelete, true),
|
||||
rename = getValue(props, RepositoryOperationRename, true),
|
||||
transfer = getValue(props, RepositoryOperationTransfer, true),
|
||||
fork = getValue(props, RepositoryOperationFork, true)
|
||||
),
|
||||
getValue(props, Gravatar, false),
|
||||
getValue(props, Notification, false),
|
||||
getOptionValue[Int](props, ActivityLogLimit, None),
|
||||
getValue(props, LimitVisibleRepositories, false),
|
||||
Ssh(
|
||||
getValue(props, SshEnabled, false),
|
||||
getOptionValue[String](props, SshHost, None).map(_.trim),
|
||||
getOptionValue(props, SshPort, Some(DefaultSshPort))
|
||||
),
|
||||
getValue(props, UseSMTP, getValue(props, Notification, false)), // handle migration scenario from only notification to useSMTP
|
||||
getValue(
|
||||
props,
|
||||
UseSMTP,
|
||||
getValue(props, Notification, false)
|
||||
), // handle migration scenario from only notification to useSMTP
|
||||
if (getValue(props, UseSMTP, getValue(props, Notification, false))) {
|
||||
Some(
|
||||
Smtp(
|
||||
@@ -148,8 +170,18 @@ trait SystemSettingsService {
|
||||
None
|
||||
},
|
||||
getValue(props, SkinName, "skin-blue"),
|
||||
getOptionValue(props, UserDefinedCss, None),
|
||||
getValue(props, ShowMailAddress, false),
|
||||
WebHook(getValue(props, WebHookBlockPrivateAddress, false), getSeqValue(props, WebHookWhitelist, ""))
|
||||
WebHook(getValue(props, WebHookBlockPrivateAddress, false), getSeqValue(props, WebHookWhitelist, "")),
|
||||
Upload(
|
||||
getValue(props, UploadMaxFileSize, 3 * 1024 * 1024),
|
||||
getValue(props, UploadTimeout, 3 * 10000),
|
||||
getValue(props, UploadLargeMaxFileSize, 3 * 1024 * 1024),
|
||||
getValue(props, UploadLargeTimeout, 3 * 10000)
|
||||
),
|
||||
RepositoryViewerSettings(
|
||||
getValue(props, RepositoryViewerMaxFiles, 0)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -167,9 +199,10 @@ object SystemSettingsService {
|
||||
allowAccountRegistration: Boolean,
|
||||
allowAnonymousAccess: Boolean,
|
||||
isCreateRepoOptionPublic: Boolean,
|
||||
repositoryOperation: RepositoryOperation,
|
||||
gravatar: Boolean,
|
||||
notification: Boolean,
|
||||
activityLogLimit: Option[Int],
|
||||
limitVisibleRepositories: Boolean,
|
||||
ssh: Ssh,
|
||||
useSMTP: Boolean,
|
||||
smtp: Option[Smtp],
|
||||
@@ -178,8 +211,11 @@ object SystemSettingsService {
|
||||
oidcAuthentication: Boolean,
|
||||
oidc: Option[OIDC],
|
||||
skinName: String,
|
||||
userDefinedCss: Option[String],
|
||||
showMailAddress: Boolean,
|
||||
webHook: WebHook
|
||||
webHook: WebHook,
|
||||
upload: Upload,
|
||||
repositoryViewer: RepositoryViewerSettings
|
||||
) {
|
||||
|
||||
def baseUrl(request: HttpServletRequest): String =
|
||||
@@ -198,12 +234,21 @@ object SystemSettingsService {
|
||||
.fold(base)(_ + base.dropWhile(_ != ':'))
|
||||
}
|
||||
|
||||
def sshAddress: Option[SshAddress] = ssh.sshHost.collect {
|
||||
case host if ssh.enabled =>
|
||||
SshAddress(host, ssh.sshPort.getOrElse(DefaultSshPort), "git")
|
||||
}
|
||||
def sshAddress: Option[SshAddress] =
|
||||
ssh.sshHost.collect {
|
||||
case host if ssh.enabled =>
|
||||
SshAddress(host, ssh.sshPort.getOrElse(DefaultSshPort), "git")
|
||||
}
|
||||
}
|
||||
|
||||
case class RepositoryOperation(
|
||||
create: Boolean,
|
||||
delete: Boolean,
|
||||
rename: Boolean,
|
||||
transfer: Boolean,
|
||||
fork: Boolean
|
||||
)
|
||||
|
||||
case class Ssh(
|
||||
enabled: Boolean,
|
||||
sshHost: Option[String],
|
||||
@@ -251,13 +296,17 @@ object SystemSettingsService {
|
||||
host: String,
|
||||
port: Int,
|
||||
user: Option[String],
|
||||
password: Option[String],
|
||||
password: Option[String]
|
||||
)
|
||||
|
||||
case class SshAddress(host: String, port: Int, genericUser: String)
|
||||
|
||||
case class WebHook(blockPrivateAddress: Boolean, whitelist: Seq[String])
|
||||
|
||||
case class Upload(maxFileSize: Long, timeout: Long, largeMaxFileSize: Long, largeTimeout: Long)
|
||||
|
||||
case class RepositoryViewerSettings(maxFiles: Int)
|
||||
|
||||
val DefaultSshPort = 29418
|
||||
val DefaultSmtpPort = 25
|
||||
val DefaultLdapPort = 389
|
||||
@@ -267,9 +316,15 @@ object SystemSettingsService {
|
||||
private val AllowAccountRegistration = "allow_account_registration"
|
||||
private val AllowAnonymousAccess = "allow_anonymous_access"
|
||||
private val IsCreateRepoOptionPublic = "is_create_repository_option_public"
|
||||
private val RepositoryOperationCreate = "repository_operation_create"
|
||||
private val RepositoryOperationDelete = "repository_operation_delete"
|
||||
private val RepositoryOperationRename = "repository_operation_rename"
|
||||
private val RepositoryOperationTransfer = "repository_operation_transfer"
|
||||
private val RepositoryOperationFork = "repository_operation_fork"
|
||||
private val Gravatar = "gravatar"
|
||||
private val Notification = "notification"
|
||||
private val ActivityLogLimit = "activity_log_limit"
|
||||
private val LimitVisibleRepositories = "limitVisibleRepositories"
|
||||
private val SshEnabled = "ssh"
|
||||
private val SshHost = "ssh.host"
|
||||
private val SshPort = "ssh.port"
|
||||
@@ -301,14 +356,15 @@ object SystemSettingsService {
|
||||
private val OidcClientSecret = "oidc.client_secret"
|
||||
private val OidcJwsAlgorithm = "oidc.jws_algorithm"
|
||||
private val SkinName = "skinName"
|
||||
private val UserDefinedCss = "userDefinedCss"
|
||||
private val ShowMailAddress = "showMailAddress"
|
||||
private val PluginNetworkInstall = "plugin.networkInstall"
|
||||
private val PluginProxyHost = "plugin.proxy.host"
|
||||
private val PluginProxyPort = "plugin.proxy.port"
|
||||
private val PluginProxyUser = "plugin.proxy.user"
|
||||
private val PluginProxyPassword = "plugin.proxy.password"
|
||||
private val WebHookBlockPrivateAddress = "webhook.block_private_address"
|
||||
private val WebHookWhitelist = "webhook.whitelist"
|
||||
private val UploadMaxFileSize = "upload.maxFileSize"
|
||||
private val UploadTimeout = "upload.timeout"
|
||||
private val UploadLargeMaxFileSize = "upload.largeMaxFileSize"
|
||||
private val UploadLargeTimeout = "upload.largeTimeout"
|
||||
private val RepositoryViewerMaxFiles = "repository_viewer_max_files"
|
||||
|
||||
private def getValue[A: ClassTag](props: java.util.Properties, key: String, default: A): A = {
|
||||
getConfigValue(key).getOrElse {
|
||||
|
||||
@@ -83,7 +83,7 @@ trait WebHookService {
|
||||
implicit s: Session
|
||||
): Option[(RepositoryWebHook, Set[WebHook.Event])] =
|
||||
RepositoryWebHooks
|
||||
.filter(_.byPrimaryKey(owner, repository, url))
|
||||
.filter(_.byRepositoryUrl(owner, repository, url))
|
||||
.join(RepositoryWebHookEvents)
|
||||
.on { (w, t) =>
|
||||
t.byRepositoryWebHook(w)
|
||||
@@ -95,6 +95,24 @@ trait WebHookService {
|
||||
.mapValues(_.map(_._2).toSet)
|
||||
.headOption
|
||||
|
||||
/** get All WebHook informations of repository */
|
||||
def getWebHookById(id: Int)(
|
||||
implicit s: Session
|
||||
): Option[(RepositoryWebHook, Set[WebHook.Event])] =
|
||||
RepositoryWebHooks
|
||||
.filter(_.byId(id))
|
||||
.join(RepositoryWebHookEvents)
|
||||
.on { (w, t) =>
|
||||
t.byRepositoryWebHook(w)
|
||||
}
|
||||
.map { case (w, t) => w -> t.event }
|
||||
.list
|
||||
.groupBy(_._1)
|
||||
.view
|
||||
.mapValues(_.map(_._2).toSet)
|
||||
.toList
|
||||
.headOption
|
||||
|
||||
def addWebHook(
|
||||
owner: String,
|
||||
repository: String,
|
||||
@@ -103,7 +121,13 @@ trait WebHookService {
|
||||
ctype: WebHookContentType,
|
||||
token: Option[String]
|
||||
)(implicit s: Session): Unit = {
|
||||
RepositoryWebHooks insert RepositoryWebHook(owner, repository, url, ctype, token)
|
||||
RepositoryWebHooks insert RepositoryWebHook(
|
||||
userName = owner,
|
||||
repositoryName = repository,
|
||||
url = url,
|
||||
ctype = ctype,
|
||||
token = token
|
||||
)
|
||||
events.map { event: WebHook.Event =>
|
||||
RepositoryWebHookEvents insert RepositoryWebHookEvent(owner, repository, url, event)
|
||||
}
|
||||
@@ -118,7 +142,7 @@ trait WebHookService {
|
||||
token: Option[String]
|
||||
)(implicit s: Session): Unit = {
|
||||
RepositoryWebHooks
|
||||
.filter(_.byPrimaryKey(owner, repository, url))
|
||||
.filter(_.byRepositoryUrl(owner, repository, url))
|
||||
.map(w => (w.ctype, w.token))
|
||||
.update((ctype, token))
|
||||
RepositoryWebHookEvents.filter(_.byRepositoryWebHook(owner, repository, url)).delete
|
||||
@@ -127,8 +151,30 @@ trait WebHookService {
|
||||
}
|
||||
}
|
||||
|
||||
def updateWebHookByApi(
|
||||
id: Int,
|
||||
owner: String,
|
||||
repository: String,
|
||||
url: String,
|
||||
events: Set[WebHook.Event],
|
||||
ctype: WebHookContentType,
|
||||
token: Option[String]
|
||||
)(implicit s: Session): Unit = {
|
||||
RepositoryWebHooks
|
||||
.filter(_.byId(id))
|
||||
.map(w => (w.url, w.ctype, w.token))
|
||||
.update((url, ctype, token))
|
||||
RepositoryWebHookEvents.filter(_.byRepositoryWebHook(owner, repository, url)).delete
|
||||
events.map { event: WebHook.Event =>
|
||||
RepositoryWebHookEvents insert RepositoryWebHookEvent(owner, repository, url, event)
|
||||
}
|
||||
}
|
||||
|
||||
def deleteWebHook(owner: String, repository: String, url: String)(implicit s: Session): Unit =
|
||||
RepositoryWebHooks.filter(_.byPrimaryKey(owner, repository, url)).delete
|
||||
RepositoryWebHooks.filter(_.byRepositoryUrl(owner, repository, url)).delete
|
||||
|
||||
def deleteWebHookById(id: Int)(implicit s: Session): Unit =
|
||||
RepositoryWebHooks.filter(_.byId(id)).delete
|
||||
|
||||
/** get All AccountWebHook informations of user */
|
||||
def getAccountWebHooks(owner: String)(implicit s: Session): List[(AccountWebHook, Set[WebHook.Event])] =
|
||||
|
||||
@@ -24,7 +24,7 @@ class ApiAuthenticationFilter extends Filter with AccessTokenService with Accoun
|
||||
val response = res.asInstanceOf[HttpServletResponse]
|
||||
Option(request.getHeader("Authorization"))
|
||||
.map {
|
||||
case auth if auth.startsWith("token ") =>
|
||||
case auth if auth.toLowerCase().startsWith("token ") =>
|
||||
AccessTokenService.getAccountByAccessToken(auth.substring(6).trim).toRight(())
|
||||
case auth if auth.startsWith("Basic ") => doBasicAuth(auth, loadSystemSettings(), request).toRight(())
|
||||
case _ => Left(())
|
||||
|
||||
@@ -5,7 +5,6 @@ import java.util
|
||||
import java.util.Date
|
||||
|
||||
import scala.util.Using
|
||||
|
||||
import gitbucket.core.api
|
||||
import gitbucket.core.model.WebHook
|
||||
import gitbucket.core.plugin.{GitRepositoryRouting, PluginRegistry}
|
||||
@@ -16,6 +15,19 @@ import gitbucket.core.util.SyntaxSugars._
|
||||
import gitbucket.core.util.Implicits._
|
||||
import gitbucket.core.util._
|
||||
import gitbucket.core.model.Profile.profile.blockingApi._
|
||||
import gitbucket.core.model.activity.{
|
||||
BaseActivityInfo,
|
||||
CloseIssueInfo,
|
||||
CreateBranchInfo,
|
||||
CreateTagInfo,
|
||||
CreateWikiPageInfo,
|
||||
DeleteBranchInfo,
|
||||
DeleteTagInfo,
|
||||
DeleteWikiInfo,
|
||||
EditWikiPageInfo,
|
||||
PushInfo
|
||||
}
|
||||
import gitbucket.core.util.JGitUtil.CommitInfo
|
||||
// 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
|
||||
@@ -240,7 +252,8 @@ class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl:
|
||||
with WebHookPullRequestService
|
||||
with WebHookPullRequestReviewCommentService
|
||||
with CommitsService
|
||||
with SystemSettingsService {
|
||||
with SystemSettingsService
|
||||
with RequestCache {
|
||||
|
||||
private val logger = LoggerFactory.getLogger(classOf[CommitLogHook])
|
||||
private var existIds: Seq[String] = Nil
|
||||
@@ -251,7 +264,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))
|
||||
.flatMap(_.preReceive(owner, repository, receivePack, command, pusher, false))
|
||||
.headOption
|
||||
.foreach { error =>
|
||||
command.setResult(ReceiveCommand.Result.REJECTED_OTHER_REASON, error)
|
||||
@@ -305,8 +318,8 @@ class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl:
|
||||
|
||||
// Retrieve all issue count in the repository
|
||||
val issueCount =
|
||||
countIssue(IssueSearchCondition(state = "open"), false, owner -> repository) +
|
||||
countIssue(IssueSearchCondition(state = "closed"), false, owner -> repository)
|
||||
countIssue(IssueSearchCondition(state = "open"), IssueSearchOption.Issues, owner -> repository) +
|
||||
countIssue(IssueSearchCondition(state = "closed"), IssueSearchOption.Issues, owner -> repository)
|
||||
|
||||
// Extract new commit and apply issue comment
|
||||
val defaultBranch = repositoryInfo.repository.defaultBranch
|
||||
@@ -321,6 +334,9 @@ class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl:
|
||||
closeIssuesFromMessage(commit.fullMessage, pusher, owner, repository).foreach { issueId =>
|
||||
getIssue(owner, repository, issueId.toString).foreach { issue =>
|
||||
callIssuesWebHook("closed", repositoryInfo, issue, pusherAccount, settings)
|
||||
val closeIssueInfo =
|
||||
CloseIssueInfo(owner, repository, pusherAccount.userName, issue.issueId, issue.title)
|
||||
recordActivity(closeIssueInfo)
|
||||
PluginRegistry().getIssueHooks
|
||||
.foreach(_.closedByCommitComment(issue, repositoryInfo, commit.fullMessage, pusherAccount))
|
||||
}
|
||||
@@ -348,17 +364,25 @@ class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl:
|
||||
// record activity
|
||||
if (refName(1) == "heads") {
|
||||
command.getType match {
|
||||
case ReceiveCommand.Type.CREATE => recordCreateBranchActivity(owner, repository, pusher, branchName)
|
||||
case ReceiveCommand.Type.UPDATE => recordPushActivity(owner, repository, pusher, branchName, newCommits)
|
||||
case ReceiveCommand.Type.DELETE => recordDeleteBranchActivity(owner, repository, pusher, branchName)
|
||||
case _ =>
|
||||
case ReceiveCommand.Type.CREATE =>
|
||||
val createBranchInfo = CreateBranchInfo(owner, repository, pusher, branchName)
|
||||
recordActivity(createBranchInfo)
|
||||
case ReceiveCommand.Type.UPDATE =>
|
||||
val pushInfo = PushInfo(owner, repository, pusher, branchName, newCommits)
|
||||
recordActivity(pushInfo)
|
||||
case ReceiveCommand.Type.DELETE =>
|
||||
val deleteBranchInfo = DeleteBranchInfo(owner, repository, pusher, branchName)
|
||||
recordActivity(deleteBranchInfo)
|
||||
case _ =>
|
||||
}
|
||||
} else if (refName(1) == "tags") {
|
||||
command.getType match {
|
||||
case ReceiveCommand.Type.CREATE =>
|
||||
recordCreateTagActivity(owner, repository, pusher, branchName, newCommits)
|
||||
val createTagInfo = CreateTagInfo(owner, repository, pusher, branchName)
|
||||
recordActivity(createTagInfo)
|
||||
case ReceiveCommand.Type.DELETE =>
|
||||
recordDeleteTagActivity(owner, repository, pusher, branchName, newCommits)
|
||||
val deleteTagInfo = DeleteTagInfo(owner, repository, pusher, branchName)
|
||||
recordActivity(deleteTagInfo)
|
||||
case _ =>
|
||||
}
|
||||
}
|
||||
@@ -411,7 +435,8 @@ class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl:
|
||||
}
|
||||
|
||||
// call post-commit hook
|
||||
PluginRegistry().getReceiveHooks.foreach(_.postReceive(owner, repository, receivePack, command, pusher))
|
||||
PluginRegistry().getReceiveHooks
|
||||
.foreach(_.postReceive(owner, repository, receivePack, command, pusher, false))
|
||||
}
|
||||
}
|
||||
// update repository last modified time.
|
||||
@@ -432,7 +457,9 @@ class WikiCommitHook(owner: String, repository: String, pusher: String, baseUrl:
|
||||
with WebHookService
|
||||
with AccountService
|
||||
with RepositoryService
|
||||
with SystemSettingsService {
|
||||
with ActivityService
|
||||
with SystemSettingsService
|
||||
with RequestCache {
|
||||
|
||||
private val logger = LoggerFactory.getLogger(classOf[WikiCommitHook])
|
||||
|
||||
@@ -460,18 +487,22 @@ class WikiCommitHook(owner: String, repository: String, pusher: String, baseUrl:
|
||||
val diffs = JGitUtil.getDiffs(git, None, commit.id, false, false)
|
||||
diffs.collect {
|
||||
case diff if diff.newPath.toLowerCase.endsWith(".md") =>
|
||||
val action = if (diff.changeType == ChangeType.ADD) "created" else "edited"
|
||||
val action = mapToAction(diff.changeType)
|
||||
val fileName = diff.newPath
|
||||
updateLastActivityDate(owner, repository)
|
||||
buildWikiRecord(action, owner, repository, commit, fileName).foreach(recordActivity)
|
||||
(action, fileName, commit.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val pages = commits
|
||||
.groupBy { case (action, fileName, commitId) => fileName }
|
||||
.groupBy { case (_, fileName, _) => fileName }
|
||||
.map {
|
||||
case (fileName, commits) =>
|
||||
(commits.head._1, fileName, commits.last._3)
|
||||
val (commitHeadAction, _, _) = commits.head
|
||||
val (_, _, commitLastId) = commits.last
|
||||
(commitHeadAction, fileName, commitLastId)
|
||||
}
|
||||
|
||||
callWebHookOf(owner, repository, WebHook.Gollum, settings) {
|
||||
@@ -494,6 +525,32 @@ class WikiCommitHook(owner: String, repository: String, pusher: String, baseUrl:
|
||||
}
|
||||
}
|
||||
|
||||
private[this] def mapToAction(changeType: ChangeType): String = changeType match {
|
||||
case ChangeType.ADD | ChangeType.RENAME => "created"
|
||||
case ChangeType.MODIFY => "edited"
|
||||
case ChangeType.DELETE => "deleted"
|
||||
case other =>
|
||||
logger.error(s"Unsupported Wiki action: $other")
|
||||
"unsupported action"
|
||||
}
|
||||
|
||||
private[this] def buildWikiRecord(
|
||||
action: String,
|
||||
owner: String,
|
||||
repo: String,
|
||||
commit: CommitInfo,
|
||||
fileName: String
|
||||
): Option[BaseActivityInfo] = {
|
||||
val pageName = fileName.dropRight(".md".length)
|
||||
action match {
|
||||
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 =>
|
||||
logger.info(s"Attempted to build wiki record for unsupported action: $other")
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object GitLfs {
|
||||
|
||||
@@ -2,11 +2,10 @@ package gitbucket.core.servlet
|
||||
|
||||
import java.io.{File, FileOutputStream}
|
||||
|
||||
import akka.event.Logging
|
||||
import com.typesafe.config.ConfigFactory
|
||||
import gitbucket.core.GitBucketCoreModule
|
||||
import gitbucket.core.plugin.PluginRegistry
|
||||
import gitbucket.core.service.{ActivityService, SystemSettingsService}
|
||||
import gitbucket.core.service.SystemSettingsService
|
||||
import gitbucket.core.util.DatabaseConfig
|
||||
import gitbucket.core.util.Directory._
|
||||
import gitbucket.core.util.JDBCUtil._
|
||||
@@ -21,8 +20,6 @@ import javax.servlet.{ServletContextEvent, ServletContextListener}
|
||||
|
||||
import org.apache.commons.io.{FileUtils, IOUtils}
|
||||
import org.slf4j.LoggerFactory
|
||||
import akka.actor.{Actor, ActorSystem, Props}
|
||||
import com.typesafe.akka.extension.quartz.QuartzSchedulerExtension
|
||||
|
||||
import scala.jdk.CollectionConverters._
|
||||
import scala.util.Using
|
||||
@@ -35,23 +32,23 @@ class InitializeListener extends ServletContextListener with SystemSettingsServi
|
||||
|
||||
private val logger = LoggerFactory.getLogger(classOf[InitializeListener])
|
||||
|
||||
// ActorSystem for Quartz scheduler
|
||||
private val system = ActorSystem(
|
||||
"job",
|
||||
ConfigFactory.parseString("""
|
||||
|akka {
|
||||
| daemonic = on
|
||||
| coordinated-shutdown.run-by-jvm-shutdown-hook = off
|
||||
| quartz {
|
||||
| schedules {
|
||||
| Daily {
|
||||
| expression = "0 0 0 * * ?"
|
||||
| }
|
||||
| }
|
||||
| }
|
||||
|}
|
||||
""".stripMargin)
|
||||
)
|
||||
// // ActorSystem for Quartz scheduler
|
||||
// private val system = ActorSystem(
|
||||
// "job",
|
||||
// ConfigFactory.parseString("""
|
||||
// |akka {
|
||||
// | daemonic = on
|
||||
// | coordinated-shutdown.run-by-jvm-shutdown-hook = off
|
||||
// | quartz {
|
||||
// | schedules {
|
||||
// | Daily {
|
||||
// | expression = "0 0 0 * * ?"
|
||||
// | }
|
||||
// | }
|
||||
// | }
|
||||
// |}
|
||||
// """.stripMargin)
|
||||
// )
|
||||
|
||||
override def contextInitialized(event: ServletContextEvent): Unit = {
|
||||
val dataDir = event.getServletContext.getInitParameter("gitbucket.home")
|
||||
@@ -95,10 +92,10 @@ class InitializeListener extends ServletContextListener with SystemSettingsServi
|
||||
PluginRegistry.initialize(event.getServletContext, loadSystemSettings(), conn)
|
||||
}
|
||||
|
||||
// Start Quartz scheduler
|
||||
val scheduler = QuartzSchedulerExtension(system)
|
||||
|
||||
scheduler.schedule("Daily", system.actorOf(Props[DeleteOldActivityActor]), "DeleteOldActivity")
|
||||
// // 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 = {
|
||||
@@ -172,8 +169,8 @@ class InitializeListener extends ServletContextListener with SystemSettingsServi
|
||||
}
|
||||
|
||||
override def contextDestroyed(event: ServletContextEvent): Unit = {
|
||||
// Shutdown Quartz scheduler
|
||||
system.terminate()
|
||||
// // Shutdown Quartz scheduler
|
||||
// system.terminate()
|
||||
// Shutdown plugins
|
||||
PluginRegistry.shutdown(event.getServletContext, loadSystemSettings())
|
||||
// Close datasource
|
||||
@@ -181,21 +178,3 @@ class InitializeListener extends ServletContextListener with SystemSettingsServi
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class DeleteOldActivityActor extends Actor with SystemSettingsService with ActivityService {
|
||||
|
||||
private val logger = Logging(context.system, this)
|
||||
|
||||
def receive = {
|
||||
case s: String => {
|
||||
loadSystemSettings().activityLogLimit.foreach { limit =>
|
||||
if (limit > 0) {
|
||||
Database() withTransaction { implicit session =>
|
||||
val rows = deleteOldActivities(limit)
|
||||
logger.info(s"Deleted ${rows} activity logs")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import Directory._
|
||||
import ConfigUtil._
|
||||
import com.github.takezoe.slick.blocking.{BlockingH2Driver, BlockingJdbcProfile, BlockingMySQLDriver}
|
||||
import liquibase.database.AbstractJdbcDatabase
|
||||
import liquibase.database.core.{H2Database, MySQLDatabase, PostgresDatabase}
|
||||
import liquibase.database.core.{H2Database, MariaDBDatabase, MySQLDatabase, PostgresDatabase}
|
||||
import org.apache.commons.io.FileUtils
|
||||
|
||||
import scala.reflect.ClassTag
|
||||
@@ -78,6 +78,8 @@ object DatabaseType {
|
||||
H2
|
||||
} else if (url.startsWith("jdbc:mysql:")) {
|
||||
MySQL
|
||||
} else if (url.startsWith("jdbc:mariadb:")) {
|
||||
MariaDb
|
||||
} else if (url.startsWith("jdbc:postgresql:")) {
|
||||
PostgreSQL
|
||||
} else {
|
||||
@@ -97,6 +99,12 @@ object DatabaseType {
|
||||
val liquiDriver = new MySQLDatabase()
|
||||
}
|
||||
|
||||
object MariaDb extends DatabaseType {
|
||||
val jdbcDriver = "org.mariadb.jdbc.Driver"
|
||||
val slickDriver = BlockingMySQLDriver
|
||||
val liquiDriver = new MariaDBDatabase()
|
||||
}
|
||||
|
||||
object PostgreSQL extends DatabaseType {
|
||||
val jdbcDriver = "org.postgresql.Driver2"
|
||||
val slickDriver = BlockingPostgresDriver
|
||||
|
||||
@@ -29,6 +29,8 @@ object Directory {
|
||||
|
||||
val GitBucketConf = new File(GitBucketHome, "gitbucket.conf")
|
||||
|
||||
val ActivityLog = new File(GitBucketHome, "activity.log")
|
||||
|
||||
val RepositoryHome = s"${GitBucketHome}/repositories"
|
||||
|
||||
val DatabaseHome = s"${GitBucketHome}/data"
|
||||
@@ -62,7 +64,7 @@ object Directory {
|
||||
new File(getRepositoryFilesDir(owner, repository), "releases")
|
||||
|
||||
/**
|
||||
* Directory for files which are attached to issue.
|
||||
* Directory for Git LFS files.
|
||||
*/
|
||||
def getLfsDir(owner: String, repository: String): File =
|
||||
new File(getRepositoryFilesDir(owner, repository), "lfs")
|
||||
|
||||
@@ -92,12 +92,4 @@ object FileUtil {
|
||||
name
|
||||
}
|
||||
|
||||
lazy val MaxFileSize: Long = {
|
||||
ConfigUtil.getConfigValue[Long]("gitbucket.maxFileSize").getOrElse(3 * 1024 * 1024)
|
||||
}
|
||||
|
||||
lazy val UploadTimeout: Long = {
|
||||
ConfigUtil.getConfigValue[Long]("gitbucket.UploadTimeout").getOrElse(3 * 10000)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user