mirror of
https://github.com/gitbucket/gitbucket.git
synced 2025-10-29 17:46:27 +01:00
Compare commits
250 Commits
4.30.1
...
disable-gi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f16f395539 | ||
|
|
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 | ||
|
|
a6e7761141 | ||
|
|
eb053a66d7 | ||
|
|
5257c83563 | ||
|
|
04bc92001f | ||
|
|
d939082e1f | ||
|
|
a943a5985d | ||
|
|
9e19821256 | ||
|
|
455183a13e | ||
|
|
c241c08904 | ||
|
|
08389cb1a0 | ||
|
|
94f9d42fc4 | ||
|
|
f35ecce3c7 | ||
|
|
2837bb40b0 | ||
|
|
54331f976d | ||
|
|
b975e74de3 | ||
|
|
c45ab34f43 | ||
|
|
4ee442f697 | ||
|
|
3c25d322f2 | ||
|
|
265c6b3e0f | ||
|
|
fad4503aec | ||
|
|
d8f70bfde3 | ||
|
|
d7dfb44efc | ||
|
|
409330a9fb | ||
|
|
0fd7e07831 | ||
|
|
91bd26d2a9 | ||
|
|
e5c4cf3298 | ||
|
|
c874d3fd84 | ||
|
|
c06f95256e | ||
|
|
2f1e05833e | ||
|
|
719cad00d6 | ||
|
|
b372c71fbf | ||
|
|
6b252a7018 | ||
|
|
6575258b6c | ||
|
|
d33280f9af | ||
|
|
863bb80ad1 | ||
|
|
e4266f31a6 | ||
|
|
0405fccb69 | ||
|
|
9a41adcec8 | ||
|
|
1d54920165 | ||
|
|
7ace37cd07 | ||
|
|
eb6398654d | ||
|
|
10a4c3e2a4 | ||
|
|
d494014011 | ||
|
|
91bf562b91 | ||
|
|
ec3961555f | ||
|
|
33b46869b6 | ||
|
|
88db21ef07 | ||
|
|
d53948f4a9 | ||
|
|
f97992a776 | ||
|
|
f9d99703cb | ||
|
|
b015cdde74 | ||
|
|
92b35bd458 | ||
|
|
3c8026f135 | ||
|
|
6a3f51a784 | ||
|
|
160c4a8a72 | ||
|
|
c4ff760bda | ||
|
|
aaed8f595a | ||
|
|
3c0a2e8385 | ||
|
|
0eef0f9aa5 | ||
|
|
169e2f16fb | ||
|
|
3800391a0e | ||
|
|
1f564808d5 | ||
|
|
433639dd04 | ||
|
|
f1e4116672 | ||
|
|
6cf00c5c66 | ||
|
|
71248cd9b7 | ||
|
|
841e6d110c | ||
|
|
f7defffeab | ||
|
|
13e6f5f6cf | ||
|
|
130cbf0b24 | ||
|
|
642d85b6bf | ||
|
|
8fe7f85e1a | ||
|
|
d1fb794783 | ||
|
|
e7aedb405a | ||
|
|
9dc148dace | ||
|
|
8ad0b25023 | ||
|
|
f648d60abb | ||
|
|
bf90d2842b | ||
|
|
7004c4f44c | ||
|
|
76eeb3d0f7 | ||
|
|
279caca502 | ||
|
|
1c6bdc7369 | ||
|
|
8705d3450a | ||
|
|
33277bf25f | ||
|
|
cbd2342208 | ||
|
|
831f87f62e | ||
|
|
06c9609587 | ||
|
|
9b43d31b75 | ||
|
|
23a3c7f960 | ||
|
|
81acfaa424 | ||
|
|
9de40292c4 | ||
|
|
c489a7ed75 | ||
|
|
6c26eb8333 | ||
|
|
6a57c5ed74 | ||
|
|
dce33aaabc | ||
|
|
b1db0ff498 | ||
|
|
a348b483c3 | ||
|
|
d7ce99526c | ||
|
|
90f0cb862a | ||
|
|
ff0c7f6a50 | ||
|
|
d608b171de | ||
|
|
5e76488276 | ||
|
|
bc265c09ff | ||
|
|
c7fe828252 | ||
|
|
9fbf67d451 | ||
|
|
e5572d5833 | ||
|
|
6b647e4cf3 | ||
|
|
4661dc3124 | ||
|
|
e428346d3b | ||
|
|
59af264463 | ||
|
|
01c60a2faa | ||
|
|
0f880143e3 | ||
|
|
bbba8b4b30 | ||
|
|
3a3a864bcb | ||
|
|
2bc0b3716a | ||
|
|
1e89e70e57 | ||
|
|
c160e7a945 | ||
|
|
d6f6938465 | ||
|
|
0d2a154622 | ||
|
|
142ea20eaf | ||
|
|
c9b910937c | ||
|
|
70c863c80c | ||
|
|
b90d206514 | ||
|
|
29ea484a26 | ||
|
|
8aa6e83673 | ||
|
|
aef8e32da3 | ||
|
|
be0f64a6ad | ||
|
|
3c88fabab3 | ||
|
|
ee85ee0660 | ||
|
|
f639cf1134 | ||
|
|
65549d4456 | ||
|
|
d194681981 | ||
|
|
5b5ddb251b | ||
|
|
5ce72e2056 | ||
|
|
ef2218a3d8 | ||
|
|
2745a3bfaa | ||
|
|
dd3fc3b0be | ||
|
|
957dfaef52 | ||
|
|
9e8015f475 | ||
|
|
38c8977dab | ||
|
|
fdf2bc6adf | ||
|
|
89344f92b3 | ||
|
|
8e8eeaf6c8 | ||
|
|
dcf2f1dfdf | ||
|
|
a7f183d40d | ||
|
|
114de52434 | ||
|
|
468cab6982 | ||
|
|
02f12d40f0 | ||
|
|
1da452aa92 | ||
|
|
2cf2adafd3 | ||
|
|
261e72cae4 | ||
|
|
a6d682fdee | ||
|
|
f8013c0ec0 | ||
|
|
d6fff29a72 | ||
|
|
dba5a44c60 |
6
.github/ISSUE_TEMPLATE.md
vendored
6
.github/ISSUE_TEMPLATE.md
vendored
@@ -1,8 +1,8 @@
|
||||
### Before submitting an issue to GitBucket I have first:
|
||||
|
||||
- [] read the [contribution guidelines](https://github.com/gitbucket/gitbucket/blob/master/.github/CONTRIBUTING.md)
|
||||
- [] searched for similar already existing issue
|
||||
- [] read the documentation and [wiki](https://github.com/gitbucket/gitbucket/wiki)
|
||||
- [ ] read the [contribution guidelines](https://github.com/gitbucket/gitbucket/blob/master/.github/CONTRIBUTING.md)
|
||||
- [ ] searched for similar already existing issue
|
||||
- [ ] read the documentation and [wiki](https://github.com/gitbucket/gitbucket/wiki)
|
||||
|
||||
*(if you have performed all the above, remove the paragraph and continue describing the issue with template below)*
|
||||
|
||||
|
||||
12
.github/PULL_REQUEST_TEMPLATE.md
vendored
12
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -1,8 +1,8 @@
|
||||
### Before submitting a pull-request to GitBucket I have first:
|
||||
|
||||
- [] read the [contribution guidelines](https://github.com/gitbucket/gitbucket/blob/master/.github/CONTRIBUTING.md)
|
||||
- [] rebased my branch over master
|
||||
- [] verified that project is compiling
|
||||
- [] verified that tests are passing
|
||||
- [] squashed my commits as appropriate *(keep several commits if it is relevant to understand the PR)*
|
||||
- [] [marked as closed using commit message](https://help.github.com/articles/closing-issues-via-commit-messages/) all issue ID that this PR should correct
|
||||
- [ ] read the [contribution guidelines](https://github.com/gitbucket/gitbucket/blob/master/.github/CONTRIBUTING.md)
|
||||
- [ ] rebased my branch over master
|
||||
- [ ] verified that project is compiling
|
||||
- [ ] verified that tests are passing
|
||||
- [ ] squashed my commits as appropriate *(keep several commits if it is relevant to understand the PR)*
|
||||
- [ ] [marked as closed using commit message](https://help.github.com/articles/closing-issues-via-commit-messages/) all issue ID that this PR should correct
|
||||
|
||||
29
.github/workflows/build.yml
vendored
Normal file
29
.github/workflows/build.yml
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
name: build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
java: [8, 11]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- 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.*
|
||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -24,3 +24,8 @@ project/plugins/project/
|
||||
.idea/
|
||||
.idea_modules/
|
||||
*.iml
|
||||
|
||||
# Metals specific
|
||||
.metals
|
||||
.bloop
|
||||
project/metals.sbt
|
||||
@@ -1,3 +1,4 @@
|
||||
version = "1.5.1"
|
||||
project.git = true
|
||||
|
||||
maxColumn = 120
|
||||
|
||||
21
.travis.yml
21
.travis.yml
@@ -1,21 +0,0 @@
|
||||
language: scala
|
||||
sudo: true
|
||||
jdk:
|
||||
- oraclejdk8
|
||||
- oraclejdk11
|
||||
- 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
|
||||
- $HOME/.embedmysql
|
||||
- $HOME/.embedpostgresql
|
||||
29
CHANGELOG.md
29
CHANGELOG.md
@@ -1,6 +1,35 @@
|
||||
# Changelog
|
||||
All changes to the project will be documented in this file.
|
||||
|
||||
### 4.33.0 - 31 Dec 2019
|
||||
|
||||
- All CLI options are configurable by environment variables
|
||||
- Folding pull request files
|
||||
- WebHook security options
|
||||
- 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
|
||||
- Compare view works for commit id
|
||||
- Apply default priority to pull requests
|
||||
- Focus title after clicking issue / pull request edit button
|
||||
|
||||
### 4.31.2 - 7 Apr 2019
|
||||
- Bug and security fix
|
||||
|
||||
### 4.31.1 - 17 Mar 2019
|
||||
- Bug fix
|
||||
|
||||
### 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
|
||||
- Bug fix for several WebHooks and Web API
|
||||
|
||||
|
||||
33
README.md
33
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.12) [](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:
|
||||
@@ -22,7 +22,7 @@ The current version of GitBucket provides many features such as:
|
||||
- Account and group management with LDAP integration
|
||||
- a Plug-in system
|
||||
|
||||
If you want to try the development version of GitBucket, see the [Developer's Guide](https://github.com/gitbucket/gitbucket/blob/master/doc/how_to_run.md).
|
||||
If you want to try the development version of GitBucket, see the [Developer's Guide](https://github.com/gitbucket/gitbucket/blob/master/doc/readme.md).
|
||||
|
||||
Installation
|
||||
--------
|
||||
@@ -31,19 +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]`
|
||||
|
||||
`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 for upload files.
|
||||
|
||||
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).
|
||||
@@ -68,17 +55,13 @@ 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.30.x
|
||||
What's New in 4.33.x
|
||||
-------------
|
||||
### 4.30.1 - 22 Dec 2018
|
||||
- Bug fix for several WebHooks and Web API
|
||||
### 4.33.0 - 31 Dec 2019
|
||||
|
||||
### 4.30.0 - 15 Dec 2018
|
||||
- Automatic ChangeLog Summary generation for new Releases
|
||||
- A lot of GitBucket Web API updates to increase compatibility with the GitHub API.
|
||||
- Display of checkboxes in Markdown files in Git repositories
|
||||
- A new extension point for plugins: anonymousAccessiblePaths
|
||||
- Group support in the Gist Plugin
|
||||
- Allow redirection to the Release Page from the Activity Timeline Page
|
||||
- All CLI options are configurable by environment variables
|
||||
- Folding pull request files
|
||||
- WebHook security options
|
||||
- Add assignee and assignees properties to some Web APIs' response
|
||||
|
||||
See the [change log](CHANGELOG.md) for all of the updates.
|
||||
|
||||
82
build.sbt
82
build.sbt
@@ -3,10 +3,10 @@ import com.typesafe.sbt.pgp.PgpKeys._
|
||||
|
||||
val Organization = "io.github.gitbucket"
|
||||
val Name = "gitbucket"
|
||||
val GitBucketVersion = "4.30.1"
|
||||
val ScalatraVersion = "2.6.3"
|
||||
val JettyVersion = "9.4.14.v20181114"
|
||||
val JgitVersion = "5.1.3.201810200350-r"
|
||||
val GitBucketVersion = "4.33.0"
|
||||
val ScalatraVersion = "2.7.0-RC1"
|
||||
val JettyVersion = "9.4.30.v20200611"
|
||||
val JgitVersion = "5.8.0.202006091008-r"
|
||||
|
||||
lazy val root = (project in file("."))
|
||||
.enablePlugins(SbtTwirl, ScalatraPlugin)
|
||||
@@ -17,17 +17,17 @@ sourcesInBase := false
|
||||
organization := Organization
|
||||
name := Name
|
||||
version := GitBucketVersion
|
||||
scalaVersion := "2.12.8"
|
||||
scalaVersion := "2.13.1"
|
||||
|
||||
scalafmtOnCompile := true
|
||||
|
||||
coverageExcludedPackages := ".*\\.html\\..*"
|
||||
|
||||
// dependency settings
|
||||
resolvers ++= Seq(
|
||||
Classpaths.typesafeReleases,
|
||||
Resolver.jcenterRepo,
|
||||
"amateras" at "http://amateras.sourceforge.jp/mvn/",
|
||||
"sonatype-snapshot" at "https://oss.sonatype.org/content/repositories/snapshots/",
|
||||
"amateras-snapshot" at "http://amateras.sourceforge.jp/mvn-snapshot/"
|
||||
"sonatype-snapshot" at "https://oss.sonatype.org/content/repositories/snapshots/"
|
||||
)
|
||||
|
||||
libraryDependencies ++= Seq(
|
||||
@@ -36,45 +36,47 @@ libraryDependencies ++= Seq(
|
||||
"org.scalatra" %% "scalatra" % ScalatraVersion,
|
||||
"org.scalatra" %% "scalatra-json" % ScalatraVersion,
|
||||
"org.scalatra" %% "scalatra-forms" % ScalatraVersion,
|
||||
"org.json4s" %% "json4s-jackson" % "3.5.2",
|
||||
"commons-io" % "commons-io" % "2.6",
|
||||
"org.json4s" %% "json4s-jackson" % "3.6.9",
|
||||
"commons-io" % "commons-io" % "2.7",
|
||||
"io.github.gitbucket" % "solidbase" % "1.0.3",
|
||||
"io.github.gitbucket" % "markedj" % "1.0.15",
|
||||
"org.apache.commons" % "commons-compress" % "1.18",
|
||||
"io.github.gitbucket" % "markedj" % "1.0.16",
|
||||
"org.apache.commons" % "commons-compress" % "1.20",
|
||||
"org.apache.commons" % "commons-email" % "1.5",
|
||||
"org.apache.httpcomponents" % "httpclient" % "4.5.6",
|
||||
"commons-net" % "commons-net" % "3.6",
|
||||
"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.19.1",
|
||||
"com.github.takezoe" %% "blocking-slick-32" % "0.0.11",
|
||||
"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.197",
|
||||
"org.mariadb.jdbc" % "mariadb-java-client" % "2.3.0",
|
||||
"org.postgresql" % "postgresql" % "42.2.5",
|
||||
"com.h2database" % "h2" % "1.4.199",
|
||||
"org.mariadb.jdbc" % "mariadb-java-client" % "2.6.0",
|
||||
"org.postgresql" % "postgresql" % "42.2.6",
|
||||
"ch.qos.logback" % "logback-classic" % "1.2.3",
|
||||
"com.zaxxer" % "HikariCP" % "3.2.0",
|
||||
"com.typesafe" % "config" % "1.3.3",
|
||||
"com.typesafe.akka" %% "akka-actor" % "2.5.18",
|
||||
"fr.brouillard.oss.security.xhub" % "xhub4j-core" % "1.0.0",
|
||||
"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.0.Final",
|
||||
"com.enragedginger" %% "akka-quartz-scheduler" % "1.7.0-akka-2.5.x" exclude ("c3p0", "c3p0") exclude ("com.zaxxer", "HikariCP-java6"),
|
||||
"net.coobird" % "thumbnailator" % "0.4.8",
|
||||
"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.11",
|
||||
"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" % "2.23.4" % "test",
|
||||
"com.wix" % "wix-embedded-mysql" % "4.2.0" % "test",
|
||||
"ru.yandex.qatools.embed" % "postgresql-embedded" % "2.10" % "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.8.0",
|
||||
"is.tagomor.woothee" % "woothee-java" % "1.11.0",
|
||||
"org.ec4j.core" % "ec4j-core" % "0.0.3"
|
||||
)
|
||||
|
||||
// Compiler settings
|
||||
scalacOptions := Seq("-deprecation", "-language:postfixOps", "-opt:l:method", "-Xfuture")
|
||||
scalacOptions := Seq("-deprecation", "-language:postfixOps", "-opt:l:method")
|
||||
javacOptions in compile ++= Seq("-target", "8", "-source", "8")
|
||||
javaOptions in Jetty += "-Dlogback.configurationFile=/logback-dev.xml"
|
||||
|
||||
@@ -162,8 +164,8 @@ executableKey := {
|
||||
plugins.foreach { plugin =>
|
||||
plugin.trim.split(":") match {
|
||||
case Array(pluginId, pluginVersion) =>
|
||||
val url = "https://plugins.gitbucket-community.org/releases/" +
|
||||
s"gitbucket-${pluginId}-plugin/gitbucket-${pluginId}-plugin-gitbucket_${version.value}-${pluginVersion}.jar"
|
||||
val url = "https://github.com/" +
|
||||
s"gitbucket/gitbucket-${pluginId}-plugin/releases/download/${pluginVersion}/gitbucket-${pluginId}-plugin-${pluginVersion}.jar"
|
||||
log info s"Download: ${url}"
|
||||
IO transfer (new java.net.URL(url).openStream, pluginsDir / url.substring(url.lastIndexOf("/") + 1))
|
||||
case _ => ()
|
||||
@@ -255,3 +257,17 @@ licenseOverrides := {
|
||||
case DepModuleInfo("com.github.bkromhout", "java-diff-utils", _) =>
|
||||
LicenseInfo(LicenseCategory.Apache, "Apache-2.0", "http://www.apache.org/licenses/LICENSE-2.0")
|
||||
}
|
||||
|
||||
testOptions in Test ++= {
|
||||
if (scala.util.Properties.isWin) {
|
||||
Seq(
|
||||
Tests.Exclude(
|
||||
Set(
|
||||
"gitbucket.core.GitBucketCoreModuleSpec"
|
||||
)
|
||||
)
|
||||
)
|
||||
} else {
|
||||
Nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
How to run from the source tree
|
||||
How to build and run from the source tree
|
||||
========
|
||||
|
||||
Install [sbt](http://www.scala-sbt.org/index.html) at first.
|
||||
First of all, Install [sbt](http://www.scala-sbt.org/index.html).
|
||||
|
||||
```
|
||||
```shell
|
||||
$ brew install sbt
|
||||
```
|
||||
|
||||
@@ -12,7 +12,7 @@ Run for Development
|
||||
|
||||
If you want to test GitBucket, type the following command in the root directory of the source tree.
|
||||
|
||||
```
|
||||
```shell
|
||||
$ sbt ~jetty:start
|
||||
```
|
||||
|
||||
@@ -25,15 +25,15 @@ Build war file
|
||||
|
||||
To build war file, run the following command:
|
||||
|
||||
```
|
||||
```shell
|
||||
$ sbt package
|
||||
```
|
||||
|
||||
`gitbucket_2.12-x.x.x.war` is generated into `target/scala-2.12`.
|
||||
`gitbucket_2.13-x.x.x.war` is generated into `target/scala-2.13`.
|
||||
|
||||
To build an executable war file, run
|
||||
|
||||
```
|
||||
```shell
|
||||
$ sbt executable
|
||||
```
|
||||
|
||||
@@ -41,8 +41,21 @@ at the top of the source tree. It generates executable `gitbucket.war` into `tar
|
||||
|
||||
Run tests spec
|
||||
---------
|
||||
Before running tests, you need to install docker.
|
||||
|
||||
```shell
|
||||
$ brew cask install docker # Install Docker
|
||||
$ open /Applications/Docker.app # Start Docker
|
||||
```
|
||||
|
||||
To run the full series of tests, run the following command:
|
||||
|
||||
```
|
||||
```shell
|
||||
$ sbt test
|
||||
```
|
||||
|
||||
If you don't have docker, you can skip docker tests which require docker as follows:
|
||||
|
||||
```shell
|
||||
$ sbt "testOnly * -- -l ExternalDBTest"
|
||||
```
|
||||
22
doc/debug.md
Normal file
22
doc/debug.md
Normal file
@@ -0,0 +1,22 @@
|
||||
Debug GitBucket on IntelliJ
|
||||
========
|
||||
Add following configuration for allowing remote debugging to `buils.sbt`:
|
||||
|
||||
```scala
|
||||
javaOptions in Jetty ++= Seq(
|
||||
"-Xdebug",
|
||||
"-Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=8000"
|
||||
)
|
||||
```
|
||||
|
||||
Run GitBucket:
|
||||
|
||||
```shell
|
||||
$ sbt ~jetty:start
|
||||
```
|
||||
|
||||
In IntelliJ, create remote debug configuration as follows. Make sure port number is same as above configuration.
|
||||
|
||||

|
||||
|
||||
Then you can start debugging on IntelliJ!
|
||||
@@ -9,16 +9,17 @@ This directory has following structure:
|
||||
* /repositories
|
||||
* /USER_NAME
|
||||
* /REPO_NAME.git (substance of repository. GitServlet sees this directory)
|
||||
* /REPO_NAME.wiki.git (wiki repository)
|
||||
* /REPO_NAME
|
||||
* /issues (files which are attached to issue)
|
||||
* /REPO_NAME.wiki.git (wiki repository)
|
||||
* /lfs (LFS managed files)
|
||||
* /data
|
||||
* /USER_NAME
|
||||
* /files
|
||||
* avatar.xxx (image file of user avatar)
|
||||
* /plugins
|
||||
* /PLUGIN_NAME
|
||||
* plugin.js
|
||||
* plugin.jar
|
||||
* /.installed (copied available plugins from the parent directory automatically)
|
||||
* /tmp
|
||||
* /_upload
|
||||
* /SESSION_ID (removed at session timeout)
|
||||
|
||||
172
doc/licenses.md
172
doc/licenses.md
@@ -4,98 +4,128 @@ Category | License | Dependency | Notes
|
||||
--- | --- | --- | ---
|
||||
Apache | [ Apache License, Version 2.0 ]( http://opensource.org/licenses/apache2.0.php ) | org.osgi # org.osgi.core # 4.3.1 | <notextile></notextile>
|
||||
Apache | [Apache 2](http://www.apache.org/licenses/LICENSE-2.0.txt) | com.googlecode.javaewah # JavaEWAH # 1.1.6 | <notextile></notextile>
|
||||
Apache | [Apache 2](http://www.apache.org/licenses/LICENSE-2.0) | org.cache2k # cache2k-all # 1.0.0.CR1 | <notextile></notextile>
|
||||
Apache | [Apache 2](http://www.apache.org/licenses/LICENSE-2.0.txt) | org.objenesis # objenesis # 2.5 | <notextile></notextile>
|
||||
Apache | [Apache 2.0 License](http://www.apache.org/licenses/LICENSE-2.0) | org.apache.sshd # apache-sshd # 1.4.0 | <notextile></notextile>
|
||||
Apache | [Apache 2.0 License](http://www.apache.org/licenses/LICENSE-2.0) | org.apache.sshd # sshd-core # 1.4.0 | <notextile></notextile>
|
||||
Apache | [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) | com.typesafe # config # 1.3.1 | <notextile></notextile>
|
||||
Apache | [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) | com.typesafe.akka # akka-actor_2.12 # 2.5.0 | <notextile></notextile>
|
||||
Apache | [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | commons-io # commons-io # 2.5 | <notextile></notextile>
|
||||
Apache | [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | fr.brouillard.oss.security.xhub # xhub4j-core # 1.0.0 | <notextile></notextile>
|
||||
Apache | [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) | org.apache.commons # commons-compress # 1.13 | <notextile></notextile>
|
||||
Apache | [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | org.apache.commons # commons-email # 1.4 | <notextile></notextile>
|
||||
Apache | [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) | org.apache.commons # commons-lang3 # 3.5 | <notextile></notextile>
|
||||
Apache | [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | org.apache.httpcomponents # httpclient # 4.5.3 | <notextile></notextile>
|
||||
Apache | [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | org.apache.httpcomponents # httpcore # 4.4.6 | <notextile></notextile>
|
||||
Apache | [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | org.apache.httpcomponents # httpmime # 4.5.2 | <notextile></notextile>
|
||||
Apache | [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | org.apache.tika # tika-core # 1.14 | <notextile></notextile>
|
||||
Apache | [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) | org.liquibase # liquibase-core # 3.4.1 | <notextile></notextile>
|
||||
Apache | [Apache Software License - Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) | org.eclipse.jetty # jetty-http # 9.2.19.v20160908 | <notextile></notextile>
|
||||
Apache | [Apache Software License - Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) | org.eclipse.jetty # jetty-io # 9.2.19.v20160908 | <notextile></notextile>
|
||||
Apache | [Apache Software License - Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) | org.eclipse.jetty # jetty-security # 9.2.19.v20160908 | <notextile></notextile>
|
||||
Apache | [Apache Software License - Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) | org.eclipse.jetty # jetty-server # 9.2.19.v20160908 | <notextile></notextile>
|
||||
Apache | [Apache Software License - Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) | org.eclipse.jetty # jetty-servlet # 9.2.19.v20160908 | <notextile></notextile>
|
||||
Apache | [Apache Software License - Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) | org.eclipse.jetty # jetty-util # 9.2.19.v20160908 | <notextile></notextile>
|
||||
Apache | [Apache Software License - Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) | org.eclipse.jetty # jetty-webapp # 9.2.19.v20160908 | <notextile></notextile>
|
||||
Apache | [Apache Software License - Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) | org.eclipse.jetty # jetty-xml # 9.2.19.v20160908 | <notextile></notextile>
|
||||
Apache | [Apache Software License, Version 1.1](http://www.apache.org/licenses/LICENSE-1.1) | org.bouncycastle # bcpg-jdk15on # 1.56 | <notextile></notextile>
|
||||
Apache | [Apache 2](http://www.apache.org/licenses/LICENSE-2.0) | org.cache2k # cache2k-all # 1.2.0.Final | <notextile></notextile>
|
||||
Apache | [Apache 2](http://www.apache.org/licenses/LICENSE-2.0) | org.cache2k # cache2k-api # 1.2.0.Final | <notextile></notextile>
|
||||
Apache | [Apache 2](http://www.apache.org/licenses/LICENSE-2.0) | org.cache2k # cache2k-core # 1.2.0.Final | <notextile></notextile>
|
||||
Apache | [Apache 2](http://www.apache.org/licenses/LICENSE-2.0.txt) | org.objenesis # objenesis # 2.6 | <notextile></notextile>
|
||||
Apache | [Apache 2.0 License](http://www.apache.org/licenses/LICENSE-2.0) | org.apache.sshd # apache-sshd # 2.1.0 | <notextile></notextile>
|
||||
Apache | [Apache 2.0 License](http://www.apache.org/licenses/LICENSE-2.0) | org.apache.sshd # sshd-cli # 2.1.0 | <notextile></notextile>
|
||||
Apache | [Apache 2.0 License](http://www.apache.org/licenses/LICENSE-2.0) | org.apache.sshd # sshd-common # 2.1.0 | <notextile></notextile>
|
||||
Apache | [Apache 2.0 License](http://www.apache.org/licenses/LICENSE-2.0) | org.apache.sshd # sshd-core # 2.1.0 | <notextile></notextile>
|
||||
Apache | [Apache 2.0 License](http://www.apache.org/licenses/LICENSE-2.0) | org.apache.sshd # sshd-putty # 2.1.0 | <notextile></notextile>
|
||||
Apache | [Apache 2.0 License](http://www.apache.org/licenses/LICENSE-2.0) | org.apache.sshd # sshd-scp # 2.1.0 | <notextile></notextile>
|
||||
Apache | [Apache 2.0 License](http://www.apache.org/licenses/LICENSE-2.0) | org.apache.sshd # sshd-sftp # 2.1.0 | <notextile></notextile>
|
||||
Apache | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | net.java.dev.jna # jna # 4.5.1 | <notextile></notextile>
|
||||
Apache | [Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | net.java.dev.jna # jna-platform # 4.5.1 | <notextile></notextile>
|
||||
Apache | [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | com.github.stephenc.jcip # jcip-annotations # 1.0-1 | <notextile></notextile>
|
||||
Apache | [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | com.kohlschutter.junixsocket # junixsocket-common # 2.0.4 | <notextile></notextile>
|
||||
Apache | [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | com.kohlschutter.junixsocket # junixsocket-native-common # 2.0.4 | <notextile></notextile>
|
||||
Apache | [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0) | com.typesafe # config # 1.3.3 | <notextile></notextile>
|
||||
Apache | [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) | com.typesafe.akka # akka-actor_2.12 # 2.5.18 | <notextile></notextile>
|
||||
Apache | [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) | commons-io # commons-io # 2.6 | <notextile></notextile>
|
||||
Apache | [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | is.tagomor.woothee # woothee-java # 1.8.0 | <notextile></notextile>
|
||||
Apache | [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) | org.apache.commons # commons-compress # 1.18 | <notextile></notextile>
|
||||
Apache | [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | org.apache.commons # commons-email # 1.5 | <notextile></notextile>
|
||||
Apache | [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt) | org.apache.commons # commons-lang3 # 3.6 | <notextile></notextile>
|
||||
Apache | [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | org.apache.httpcomponents # httpclient # 4.5.6 | <notextile></notextile>
|
||||
Apache | [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | org.apache.httpcomponents # httpcore # 4.4.10 | <notextile></notextile>
|
||||
Apache | [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | org.apache.httpcomponents # httpmime # 4.5.3 | <notextile></notextile>
|
||||
Apache | [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | org.apache.tika # tika-core # 1.19.1 | <notextile></notextile>
|
||||
Apache | [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | org.ec4j.core # ec4j-core # 0.0.3 | <notextile></notextile>
|
||||
Apache | [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) | org.liquibase # liquibase-core # 3.6.2 | <notextile></notextile>
|
||||
Apache | [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | org.yaml # snakeyaml # 1.18 | <notextile></notextile>
|
||||
Apache | [Apache License, version 2.0](http://www.apache.org/licenses/LICENSE-2.0.html) | com.nimbusds # oauth2-oidc-sdk # 5.64.4 | <notextile></notextile>
|
||||
Apache | [Apache Software License - Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) | org.eclipse.jetty # jetty-http # 9.4.6.v20170531 | <notextile></notextile>
|
||||
Apache | [Apache Software License - Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) | org.eclipse.jetty # jetty-io # 9.4.6.v20170531 | <notextile></notextile>
|
||||
Apache | [Apache Software License - Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) | org.eclipse.jetty # jetty-security # 9.4.6.v20170531 | <notextile></notextile>
|
||||
Apache | [Apache Software License - Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) | org.eclipse.jetty # jetty-server # 9.4.6.v20170531 | <notextile></notextile>
|
||||
Apache | [Apache Software License - Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) | org.eclipse.jetty # jetty-servlet # 9.4.6.v20170531 | <notextile></notextile>
|
||||
Apache | [Apache Software License - Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) | org.eclipse.jetty # jetty-util # 9.4.6.v20170531 | <notextile></notextile>
|
||||
Apache | [Apache Software License - Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) | org.eclipse.jetty # jetty-webapp # 9.4.6.v20170531 | <notextile></notextile>
|
||||
Apache | [Apache Software License - Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) | org.eclipse.jetty # jetty-xml # 9.4.6.v20170531 | <notextile></notextile>
|
||||
Apache | [Apache Software License, Version 1.1](http://www.apache.org/licenses/LICENSE-1.1) | org.bouncycastle # bcpg-jdk15on # 1.60 | <notextile></notextile>
|
||||
Apache | [Apache-2.0](http://www.apache.org/licenses/LICENSE-2.0) | com.github.bkromhout # java-diff-utils # 2.1.1 | <notextile></notextile>
|
||||
Apache | [Apache-2.0](http://www.apache.org/licenses/LICENSE-2.0.html) | com.typesafe.play # twirl-api_2.12 # 1.3.7 | <notextile></notextile>
|
||||
Apache | [Apache-2.0](http://www.apache.org/licenses/LICENSE-2.0) | org.json4s # json4s-ast_2.12 # 3.5.1 | <notextile></notextile>
|
||||
Apache | [Apache-2.0](http://www.apache.org/licenses/LICENSE-2.0) | org.json4s # json4s-core_2.12 # 3.5.1 | <notextile></notextile>
|
||||
Apache | [Apache-2.0](http://www.apache.org/licenses/LICENSE-2.0) | org.json4s # json4s-jackson_2.12 # 3.5.1 | <notextile></notextile>
|
||||
Apache | [Apache-2.0](http://www.apache.org/licenses/LICENSE-2.0) | org.json4s # json4s-scalap_2.12 # 3.5.1 | <notextile></notextile>
|
||||
Apache | [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | com.enragedginger # akka-quartz-scheduler_2.12 # 1.6.0-akka-2.4.x | <notextile></notextile>
|
||||
Apache | [Apache-2.0](http://www.apache.org/licenses/LICENSE-2.0.html) | com.typesafe.play # twirl-api_2.12 # 1.3.15 | <notextile></notextile>
|
||||
Apache | [Apache-2.0](http://www.apache.org/licenses/LICENSE-2.0) | org.json4s # json4s-ast_2.12 # 3.5.2 | <notextile></notextile>
|
||||
Apache | [Apache-2.0](http://www.apache.org/licenses/LICENSE-2.0) | org.json4s # json4s-core_2.12 # 3.5.2 | <notextile></notextile>
|
||||
Apache | [Apache-2.0](http://www.apache.org/licenses/LICENSE-2.0) | org.json4s # json4s-jackson_2.12 # 3.5.2 | <notextile></notextile>
|
||||
Apache | [Apache-2.0](http://www.apache.org/licenses/LICENSE-2.0) | org.json4s # json4s-scalap_2.12 # 3.5.2 | <notextile></notextile>
|
||||
Apache | [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0) | org.scala-lang # scala-library # 2.12.8 | <notextile></notextile>
|
||||
Apache | [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0) | org.scala-lang # scala-reflect # 2.12.8 | <notextile></notextile>
|
||||
Apache | [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | com.enragedginger # akka-quartz-scheduler_2.12 # 1.7.0-akka-2.5.x | <notextile></notextile>
|
||||
Apache | [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | com.fasterxml.jackson.core # jackson-annotations # 2.8.0 | <notextile></notextile>
|
||||
Apache | [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | com.fasterxml.jackson.core # jackson-core # 2.8.4 | <notextile></notextile>
|
||||
Apache | [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | com.fasterxml.jackson.core # jackson-databind # 2.8.4 | <notextile></notextile>
|
||||
Apache | [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | com.github.takezoe # blocking-slick-32_2.12 # 0.0.10 | <notextile></notextile>
|
||||
Apache | [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | com.github.takezoe # blocking-slick-32_2.12 # 0.0.11 | <notextile></notextile>
|
||||
Apache | [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | com.google.code.findbugs # jsr305 # 3.0.0 | <notextile></notextile>
|
||||
Apache | [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | com.zaxxer # HikariCP # 2.6.1 | <notextile></notextile>
|
||||
Apache | [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | commons-codec # commons-codec # 1.9 | <notextile></notextile>
|
||||
Apache | [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | commons-logging # commons-logging # 1.2 | <notextile></notextile>
|
||||
Apache | [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | de.flapdoodle.embed # de.flapdoodle.embed.process # 2.0.1 | <notextile></notextile>
|
||||
Apache | [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | com.nimbusds # lang-tag # 1.4.3 | <notextile></notextile>
|
||||
Apache | [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | com.nimbusds # nimbus-jose-jwt # 5.5 | <notextile></notextile>
|
||||
Apache | [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | com.zaxxer # HikariCP # 3.2.0 | <notextile></notextile>
|
||||
Apache | [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | eu.medsea.mimeutil # mime-util # 2.1.3 | <notextile></notextile>
|
||||
Apache | [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | io.github.gitbucket # markedj # 1.0.15 | <notextile></notextile>
|
||||
Apache | [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | io.github.gitbucket # scalatra-forms_2.12 # 1.1.0 | <notextile></notextile>
|
||||
Apache | [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | io.github.gitbucket # solidbase # 1.0.2 | <notextile></notextile>
|
||||
Apache | [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | net.bytebuddy # byte-buddy # 1.6.11 | <notextile></notextile>
|
||||
Apache | [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | net.bytebuddy # byte-buddy-agent # 1.6.11 | <notextile></notextile>
|
||||
Apache | [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | org.quartz-scheduler # quartz # 2.2.3 | <notextile></notextile>
|
||||
Apache | [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | ru.yandex.qatools.embed # postgresql-embedded # 2.0 | <notextile></notextile>
|
||||
Apache | [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | tomcat # tomcat-apr # 5.5.23 | <notextile></notextile>
|
||||
Apache | [the Apache License, ASL Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) | org.scalactic # scalactic_2.12 # 3.0.0 | <notextile></notextile>
|
||||
Apache | [the Apache License, ASL Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) | org.scalatest # scalatest_2.12 # 3.0.0 | <notextile></notextile>
|
||||
BSD | [BSD](LICENSE.txt) | com.thoughtworks.paranamer # paranamer # 2.8 | <notextile></notextile>
|
||||
BSD | [BSD](http://software.clapper.org/grizzled-slf4j/license.html) | org.clapper # grizzled-slf4j_2.12 # 1.3.0 | <notextile></notextile>
|
||||
BSD | [BSD](http://github.com/scalatra/scalatra/raw/HEAD/LICENSE) | org.scalatra # scalatra-common_2.12 # 2.5.0 | <notextile></notextile>
|
||||
BSD | [BSD](http://github.com/scalatra/scalatra/raw/HEAD/LICENSE) | org.scalatra # scalatra-json_2.12 # 2.5.0 | <notextile></notextile>
|
||||
BSD | [BSD](http://github.com/scalatra/scalatra/raw/HEAD/LICENSE) | org.scalatra # scalatra-scalatest_2.12 # 2.5.0 | <notextile></notextile>
|
||||
BSD | [BSD](http://github.com/scalatra/scalatra/raw/HEAD/LICENSE) | org.scalatra # scalatra-test_2.12 # 2.5.0 | <notextile></notextile>
|
||||
BSD | [BSD](http://github.com/scalatra/scalatra/raw/HEAD/LICENSE) | org.scalatra # scalatra_2.12 # 2.5.0 | <notextile></notextile>
|
||||
BSD | [BSD 3-Clause](http://www.scala-lang.org/license.html) | org.scala-lang # scala-library # 2.12.3 | <notextile></notextile>
|
||||
BSD | [BSD 3-Clause](http://www.scala-lang.org/license.html) | org.scala-lang # scala-reflect # 2.12.3 | <notextile></notextile>
|
||||
Apache | [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | io.github.gitbucket # solidbase # 1.0.3 | <notextile></notextile>
|
||||
Apache | [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | net.bytebuddy # byte-buddy # 1.9.3 | <notextile></notextile>
|
||||
Apache | [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | net.bytebuddy # byte-buddy-agent # 1.9.3 | <notextile></notextile>
|
||||
Apache | [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | net.minidev # accessors-smart # 1.2 | <notextile></notextile>
|
||||
Apache | [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | net.minidev # json-smart # 2.3 | <notextile></notextile>
|
||||
Apache | [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | org.jetbrains # annotations # 15.0 | <notextile></notextile>
|
||||
Apache | [The Apache Software License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.txt) | org.quartz-scheduler # quartz # 2.3.0 | <notextile></notextile>
|
||||
Apache | [the Apache License, ASL Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) | org.scalactic # scalactic_2.12 # 3.0.3 | <notextile></notextile>
|
||||
Apache | [the Apache License, ASL Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) | org.scalatest # scalatest_2.12 # 3.0.3 | <notextile></notextile>
|
||||
BSD | [BSD](http://github.com/scalatra/scalatra/raw/HEAD/LICENSE) | org.scalatra # scalatra-common_2.12 # 2.6.3 | <notextile></notextile>
|
||||
BSD | [BSD](http://github.com/scalatra/scalatra/raw/HEAD/LICENSE) | org.scalatra # scalatra-forms_2.12 # 2.6.3 | <notextile></notextile>
|
||||
BSD | [BSD](http://github.com/scalatra/scalatra/raw/HEAD/LICENSE) | org.scalatra # scalatra-json_2.12 # 2.6.3 | <notextile></notextile>
|
||||
BSD | [BSD](http://github.com/scalatra/scalatra/raw/HEAD/LICENSE) | org.scalatra # scalatra-scalatest_2.12 # 2.6.3 | <notextile></notextile>
|
||||
BSD | [BSD](http://github.com/scalatra/scalatra/raw/HEAD/LICENSE) | org.scalatra # scalatra-test_2.12 # 2.6.3 | <notextile></notextile>
|
||||
BSD | [BSD](http://github.com/scalatra/scalatra/raw/HEAD/LICENSE) | org.scalatra # scalatra_2.12 # 2.6.3 | <notextile></notextile>
|
||||
BSD | [BSD 3-clause](http://opensource.org/licenses/BSD-3-Clause) | org.scala-lang.modules # scala-java8-compat_2.12 # 0.8.0 | <notextile></notextile>
|
||||
BSD | [BSD 3-clause](http://opensource.org/licenses/BSD-3-Clause) | org.scala-lang.modules # scala-parser-combinators_2.12 # 1.0.4 | <notextile></notextile>
|
||||
BSD | [BSD 3-clause](http://opensource.org/licenses/BSD-3-Clause) | org.scala-lang.modules # scala-parser-combinators_2.12 # 1.0.6 | <notextile></notextile>
|
||||
BSD | [BSD 3-clause](http://opensource.org/licenses/BSD-3-Clause) | org.scala-lang.modules # scala-xml_2.12 # 1.0.6 | <notextile></notextile>
|
||||
BSD | [BSD License](http://www.opensource.org/licenses/bsd-license.php) | com.wix # wix-embedded-mysql # 2.1.4 | <notextile></notextile>
|
||||
BSD | [BSD-2-Clause](https://jdbc.postgresql.org/about/license.html) | org.postgresql # postgresql # 42.0.0 | <notextile></notextile>
|
||||
BSD | [Eclipse Distribution License (New BSD License)](null) | org.eclipse.jgit # org.eclipse.jgit # 4.8.0.201706111038-r | <notextile></notextile>
|
||||
BSD | [Eclipse Distribution License (New BSD License)](null) | org.eclipse.jgit # org.eclipse.jgit.archive # 4.8.0.201706111038-r | <notextile></notextile>
|
||||
BSD | [Eclipse Distribution License (New BSD License)](null) | org.eclipse.jgit # org.eclipse.jgit.http.server # 4.8.0.201706111038-r | <notextile></notextile>
|
||||
BSD | [BSD-2-Clause](https://jdbc.postgresql.org/about/license.html) | org.postgresql # postgresql # 42.2.5 | <notextile></notextile>
|
||||
BSD | [Eclipse Distribution License (New BSD License)](null) | org.eclipse.jgit # org.eclipse.jgit # 5.2.0.201812061821-r | <notextile></notextile>
|
||||
BSD | [Eclipse Distribution License (New BSD License)](null) | org.eclipse.jgit # org.eclipse.jgit.archive # 5.2.0.201812061821-r | <notextile></notextile>
|
||||
BSD | [Eclipse Distribution License (New BSD License)](null) | org.eclipse.jgit # org.eclipse.jgit.http.server # 5.2.0.201812061821-r | <notextile></notextile>
|
||||
BSD | [New BSD License](http://www.opensource.org/licenses/bsd-license.php) | org.hamcrest # hamcrest-core # 1.3 | <notextile></notextile>
|
||||
BSD | [Revised BSD](http://www.jcraft.com/jsch/LICENSE.txt) | com.jcraft # jsch # 0.1.54 | <notextile></notextile>
|
||||
BSD | [Revised BSD](http://www.jcraft.com/jzlib/LICENSE.txt) | com.jcraft # jzlib # 1.1.1 | <notextile></notextile>
|
||||
BSD | [Two-clause BSD-style license](http://github.com/slick/slick/blob/master/LICENSE.txt) | com.typesafe.slick # slick_2.12 # 3.2.1 | <notextile></notextile>
|
||||
CC0 | [CC0](http://creativecommons.org/publicdomain/zero/1.0/) | org.reactivestreams # reactive-streams # 1.0.0 | <notextile></notextile>
|
||||
CDDL | [COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) Version 1.0](https://glassfish.dev.java.net/public/CDDLv1.0.html) | javax.activation # activation # 1.1.1 | <notextile></notextile>
|
||||
GPL | [CDDL/GPLv2+CE](https://glassfish.java.net/public/CDDL+GPL_1_1.html) | com.sun.mail # javax.mail # 1.5.2 | <notextile></notextile>
|
||||
CC0 | [CC0 1.0 Universal](https://creativecommons.org/publicdomain/zero/1.0/) | net.i2p.crypto # eddsa # 0.3.0 | <notextile></notextile>
|
||||
CC0 | [CC0 1.0 Universal License](http://creativecommons.org/publicdomain/zero/1.0/) | org.scijava # native-lib-loader # 2.0.2 | <notextile></notextile>
|
||||
CDDL | [Common Development and Distribution License (CDDL) v1.0](https://glassfish.dev.java.net/public/CDDLv1.0.html) | javax.activation # activation # 1.1 | <notextile></notextile>
|
||||
GPL | [CDDL/GPLv2+CE](https://javaee.github.io/javamail/LICENSE) | com.sun.mail # javax.mail # 1.6.1 | <notextile></notextile>
|
||||
GPL | [GPL2 w/ CPE](https://oss.oracle.com/licenses/CDDL+GPL-1.1) | javax.xml.bind # jaxb-api # 2.3.0 | <notextile></notextile>
|
||||
GPL with Classpath Extension | [CDDL + GPLv2 with classpath exception](https://github.com/javaee/javax.annotation/blob/master/LICENSE) | javax.annotation # javax.annotation-api # 1.3.1 | <notextile></notextile>
|
||||
GPL with Classpath Extension | [CDDL + GPLv2 with classpath exception](https://glassfish.dev.java.net/nonav/public/CDDL+GPL.html) | javax.servlet # javax.servlet-api # 3.1.0 | <notextile></notextile>
|
||||
LGPL | [GNU Lesser General Public License](http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html) | ch.qos.logback # logback-classic # 1.2.3 | <notextile></notextile>
|
||||
LGPL | [GNU Lesser General Public License](http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html) | ch.qos.logback # logback-core # 1.2.3 | <notextile></notextile>
|
||||
LGPL | [LGPL, version 2.1](http://www.gnu.org/licenses/licenses.html) | net.java.dev.jna # jna # 4.0.0 | <notextile></notextile>
|
||||
LGPL | [LGPL, version 2.1](http://www.gnu.org/licenses/licenses.html) | net.java.dev.jna # jna-platform # 4.0.0 | <notextile></notextile>
|
||||
LGPL | [LGPL-2.1](null) | org.mariadb.jdbc # mariadb-java-client # 2.0.3 | <notextile></notextile>
|
||||
LGPL | [GNU Lesser General Public License, Version 2.1](http://www.gnu.org/licenses/lgpl-2.1.html) | com.mchange # c3p0 # 0.9.5.2 | <notextile></notextile>
|
||||
LGPL | [GNU Lesser General Public License, Version 2.1](http://www.gnu.org/licenses/lgpl-2.1.html) | com.mchange # mchange-commons-java # 0.2.11 | <notextile></notextile>
|
||||
LGPL | [LGPL-2.1](null) | org.mariadb.jdbc # mariadb-java-client # 2.3.0 | <notextile></notextile>
|
||||
MIT | [MIT](http://opensource.org/licenses/MIT) | org.rnorth # tcp-unix-socket-proxy # 1.0.2 | <notextile></notextile>
|
||||
MIT | [MIT](http://opensource.org/licenses/MIT) | org.rnorth.duct-tape # duct-tape # 1.0.7 | <notextile></notextile>
|
||||
MIT | [MIT](http://opensource.org/licenses/MIT) | org.rnorth.visible-assertions # visible-assertions # 2.1.1 | <notextile></notextile>
|
||||
MIT | [MIT](http://opensource.org/licenses/MIT) | org.testcontainers # database-commons # 1.10.3 | <notextile></notextile>
|
||||
MIT | [MIT](http://opensource.org/licenses/MIT) | org.testcontainers # jdbc # 1.10.3 | <notextile></notextile>
|
||||
MIT | [MIT](http://opensource.org/licenses/MIT) | org.testcontainers # mysql # 1.10.3 | <notextile></notextile>
|
||||
MIT | [MIT](http://opensource.org/licenses/MIT) | org.testcontainers # postgresql # 1.10.3 | <notextile></notextile>
|
||||
MIT | [MIT](http://opensource.org/licenses/MIT) | org.testcontainers # testcontainers # 1.10.3 | <notextile></notextile>
|
||||
MIT | [MIT License](http://www.opensource.org/licenses/mit-license.php) | org.slf4j # slf4j-api # 1.7.25 | <notextile></notextile>
|
||||
MIT | [The MIT License](http://www.opensource.org/licenses/mit-license.php) | com.github.zafarkhaja # java-semver # 0.9.0 | <notextile></notextile>
|
||||
MIT | [The MIT License](https://jsoup.org/license) | org.jsoup # jsoup # 1.10.2 | <notextile></notextile>
|
||||
MIT | [The MIT License](http://github.com/mockito/mockito/blob/master/LICENSE) | org.mockito # mockito-all # 1.10.19 | <notextile></notextile>
|
||||
MIT | [The MIT License](http://github.com/mockito/mockito/blob/master/LICENSE) | org.mockito # mockito-core # 2.7.22 | <notextile></notextile>
|
||||
MIT | [The MIT License](https://github.com/mockito/mockito/blob/master/LICENSE) | org.mockito # mockito-core # 2.23.4 | <notextile></notextile>
|
||||
MIT | [The MIT License (MIT)](https://opensource.org/licenses/MIT) | com.dimafeng # testcontainers-scala_2.12 # 0.22.0 | <notextile></notextile>
|
||||
MIT | [The MIT License (MIT)](http://www.opensource.org/licenses/mit-license.html) | net.coobird # thumbnailator # 0.4.8 | <notextile></notextile>
|
||||
Mozilla | [MPL 2.0 or EPL 1.0](http://h2database.com/html/license.html) | com.h2database # h2 # 1.4.195 | <notextile></notextile>
|
||||
Mozilla | [MPL 2.0 or EPL 1.0](http://h2database.com/html/license.html) | com.h2database # h2 # 1.4.197 | <notextile></notextile>
|
||||
Mozilla | [Mozilla Public License 1.1 (MPL 1.1)](http://www.mozilla.org/MPL/MPL-1.1.html) | com.googlecode.juniversalchardet # juniversalchardet # 1.0.3 | <notextile></notextile>
|
||||
Public Domain | [Public Domain](http://en.wikipedia.org/wiki/Public_domain) | net.i2p.crypto # eddsa # 0.1.0 | <notextile></notextile>
|
||||
unrecognized | [Bouncy Castle Licence](http://www.bouncycastle.org/licence.html) | org.bouncycastle # bcpkix-jdk15on # 1.56 | <notextile></notextile>
|
||||
unrecognized | [Bouncy Castle Licence](http://www.bouncycastle.org/licence.html) | org.bouncycastle # bcprov-jdk15on # 1.56 | <notextile></notextile>
|
||||
unrecognized | [Bouncy Castle Licence](http://www.bouncycastle.org/licence.html) | org.bouncycastle # bcpkix-jdk15on # 1.60 | <notextile></notextile>
|
||||
unrecognized | [Bouncy Castle Licence](http://www.bouncycastle.org/licence.html) | org.bouncycastle # bcprov-jdk15on # 1.60 | <notextile></notextile>
|
||||
unrecognized | [Eclipse Public License 1.0](http://www.eclipse.org/legal/epl-v10.html) | junit # junit # 4.12 | <notextile></notextile>
|
||||
unrecognized | [The OpenLDAP Public License](http://www.openldap.org/software/release/license.html) | com.novell.ldap # jldap # 2009-10-07 | <notextile></notextile>
|
||||
unrecognized | [none specified](none specified) | com.thoughtworks.paranamer # paranamer # 2.8 | <notextile></notextile>
|
||||
unrecognized | [none specified](none specified) | commons-codec # commons-codec # 1.10 | <notextile></notextile>
|
||||
unrecognized | [none specified](none specified) | commons-logging # commons-logging # 1.2 | <notextile></notextile>
|
||||
unrecognized | [none specified](none specified) | fr.brouillard.oss.security.xhub # xhub4j-core # 1.0.0 | <notextile></notextile>
|
||||
unrecognized | [none specified](none specified) | org.ow2.asm # asm # 5.0.4 | <notextile></notextile>
|
||||
unrecognized | [none specified](none specified) | tomcat # tomcat-apr # 5.5.23 | <notextile></notextile>
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
Developer's Guide
|
||||
========
|
||||
* [How to run from source tree](how_to_run.md)
|
||||
* [Build from source tree](build.md)
|
||||
* [Debug on IntelliJ](debug.md)
|
||||
* [Directory Structure](directory.md)
|
||||
* [Mapping and Validation](validation.md)
|
||||
* [Authentication in Controller](authenticator.md)
|
||||
|
||||
@@ -34,6 +34,20 @@ object GitBucketCoreModule extends Module("gitbucket-core",
|
||||
Generate release files
|
||||
--------
|
||||
|
||||
### Deploy assembly jar file
|
||||
|
||||
For plug-in development, we have to publish the GitBucket jar file to the Maven central repository before release GitBucket itself.
|
||||
|
||||
First, hit following command to publish artifacts to the sonatype OSS repository:
|
||||
|
||||
```bash
|
||||
$ sbt publishSigned
|
||||
```
|
||||
|
||||
Then logged-in to https://oss.sonatype.org/, close and release the repository.
|
||||
|
||||
You need to wait up to a day until [gitbucket-notification-plugin](https://plugins.gitbucket-community.org/) which is default bundled plugin is built for new version of GitBucket.
|
||||
|
||||
### Make release war file
|
||||
|
||||
Run `sbt executable`. The release war file and fingerprint are generated into `target/executable/gitbucket.war`.
|
||||
@@ -42,20 +56,4 @@ Run `sbt executable`. The release war file and fingerprint are generated into `t
|
||||
$ sbt executable
|
||||
```
|
||||
|
||||
### Deploy assembly jar file
|
||||
|
||||
For plug-in development, we have to publish the GitBucket jar file to the Maven central repository as well. At first, hit following command to publish artifacts to the sonatype OSS repository:
|
||||
|
||||
```bash
|
||||
$ sbt publishSigned
|
||||
```
|
||||
|
||||
Then logged-in https://oss.sonatype.org/ and delete following files from the staging repository:
|
||||
|
||||
- gitbucket_2.12-x.x.x.war
|
||||
- gitbucket_2.12-x.x.x.war.asc
|
||||
- gitbucket_2.12-x.x.x.war.asc.md5
|
||||
- gitbucket_2.12-x.x.x.war.asc.sha1
|
||||
- gitbucket_2.12-x.x.x.war.md5
|
||||
|
||||
At last, close and release the repository.
|
||||
Create new release from the corresponded tag on GitHub, then upload generated jar file and fingerprints to the release.
|
||||
|
||||
BIN
doc/remote_debug.png
Normal file
BIN
doc/remote_debug.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 203 KiB |
@@ -1,11 +1,11 @@
|
||||
Mapping and Validation
|
||||
========
|
||||
GitBucket uses [scalatra-forms](https://github.com/takezoe/scalatra-forms) to validate request parameters and map them to the scala object. This is inspired by Play2 form mapping / validation.
|
||||
GitBucket uses [scalatra-forms](http://scalatra.org/guides/2.6/formats/forms.html) to validate request parameters and map them to the scala object. This is inspired by Play2 form mapping / validation.
|
||||
|
||||
At first, define the mapping as following:
|
||||
|
||||
```scala
|
||||
import jp.sf.amateras.scalatra.forms._
|
||||
import org.scalatra.forms._
|
||||
|
||||
case class RegisterForm(name: String, description: String)
|
||||
|
||||
@@ -15,17 +15,17 @@ val form = mapping(
|
||||
)(RegisterForm.apply)
|
||||
```
|
||||
|
||||
The servlet have to mixed in ```jp.sf.amateras.scalatra.forms.ClientSideValidationFormSupport``` to validate request parameters and take mapped object. It validates request parameters before action. If any errors are detected, it throws an exception.
|
||||
The servlet have to mixed in `gitbucket.core.controller.ValidationFormSupport` to validate request parameters and take mapped object. It validates request parameters before action. If any errors are detected, it throws an exception.
|
||||
|
||||
```scala
|
||||
class RegisterServlet extends ScalatraServlet with ClientSideValidationFormSupport {
|
||||
class RegisterServlet extends ScalatraServlet with ValidationFormSupport {
|
||||
post("/register", form) { form: RegisterForm =>
|
||||
...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
In the view template, you can add client-side validation by adding ```validate="true"``` to your form. Error messages are set to ```span#error-<fieldname>```.
|
||||
In the view template, you can add client-side validation by adding `validate="true"` to your form. Error messages are set to `span#error-<fieldname>`.
|
||||
|
||||
```html
|
||||
<form method="POST" action="/register" validate="true">
|
||||
@@ -39,9 +39,9 @@ In the view template, you can add client-side validation by adding ```validate="
|
||||
</form>
|
||||
```
|
||||
|
||||
Client-side validation calls ```<form-action>/validate``` to validate form contents. It returns a validation result as JSON. In this case, form action is ```/register```, so ```/register/validate``` is called before submitting a form. ```ClientSideValidationFormSupport``` adds this JSON API automatically.
|
||||
Client-side validation calls `<form-action>/validate` to validate form contents. It returns a validation result as JSON. In this case, form action is `/register`, so `/register/validate` is called before submitting a form. `ValidationFormSupport` adds this JSON API automatically.
|
||||
|
||||
For Ajax request, you have to use '''ajaxGet''' or '''ajaxPost''' to define action. It almost same as '''get''' or '''post'''. You can implement actions which handle Ajax request as same as normal actions.
|
||||
For Ajax request, you have to use `ajaxGet` or `ajaxPost` to define action. It almost same as '''get''' or '''post'''. You can implement actions which handle Ajax request as same as normal actions.
|
||||
Small difference is they return validation errors as JSON.
|
||||
|
||||
```scala
|
||||
|
||||
@@ -1 +1 @@
|
||||
sbt.version=1.2.6
|
||||
sbt.version=1.3.12
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
scalacOptions ++= Seq("-unchecked", "-deprecation", "-feature")
|
||||
|
||||
addSbtPlugin("com.geirsson" % "sbt-scalafmt" % "1.5.0")
|
||||
addSbtPlugin("com.typesafe.sbt" % "sbt-twirl" % "1.3.15")
|
||||
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.9")
|
||||
addSbtPlugin("org.scalatra.sbt" % "sbt-scalatra" % "1.0.3")
|
||||
addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.1.2")
|
||||
addSbtCoursier
|
||||
addSbtPlugin("com.geirsson" % "sbt-scalafmt" % "1.5.1")
|
||||
addSbtPlugin("com.typesafe.sbt" % "sbt-twirl" % "1.5.0")
|
||||
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")
|
||||
addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.6.1")
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
addSbtPlugin("io.get-coursier" % "sbt-coursier" % "1.0.0")
|
||||
@@ -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;
|
||||
@@ -16,13 +20,22 @@ public class JettyLauncher {
|
||||
System.setProperty("java.awt.headless", "true");
|
||||
|
||||
String host = null;
|
||||
int port = 8080;
|
||||
String port = null;
|
||||
InetSocketAddress address = null;
|
||||
String contextPath = "/";
|
||||
String tmpDirPath="";
|
||||
boolean forceHttps = false;
|
||||
boolean saveSessions = false;
|
||||
|
||||
host = getEnvironmentVariable("gitbucket.host");
|
||||
port = getEnvironmentVariable("gitbucket.port");
|
||||
contextPath = getEnvironmentVariable("gitbucket.prefix");
|
||||
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) {
|
||||
@@ -31,16 +44,10 @@ public class JettyLauncher {
|
||||
host = dim[1];
|
||||
break;
|
||||
case "--port":
|
||||
port = Integer.parseInt(dim[1]);
|
||||
port = dim[1];
|
||||
break;
|
||||
case "--prefix":
|
||||
contextPath = dim[1];
|
||||
if (!contextPath.startsWith("/")) {
|
||||
contextPath = "/" + contextPath;
|
||||
}
|
||||
break;
|
||||
case "--max_file_size":
|
||||
System.setProperty("gitbucket.maxFileSize", dim[1]);
|
||||
break;
|
||||
case "--gitbucket.home":
|
||||
System.setProperty("gitbucket.home", dim[1]);
|
||||
@@ -51,18 +58,19 @@ public class JettyLauncher {
|
||||
case "--plugin_dir":
|
||||
System.setProperty("gitbucket.pluginDir", dim[1]);
|
||||
break;
|
||||
case "--validate_password":
|
||||
System.setProperty("gitbucket.validate.password", dim[1]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (contextPath != null && !contextPath.startsWith("/")) {
|
||||
contextPath = "/" + contextPath;
|
||||
}
|
||||
|
||||
if(host != null) {
|
||||
address = new InetSocketAddress(host, port);
|
||||
address = new InetSocketAddress(host, getPort(port));
|
||||
} else {
|
||||
address = new InetSocketAddress(port);
|
||||
address = new InetSocketAddress(getPort(port));
|
||||
}
|
||||
|
||||
Server server = new Server(address);
|
||||
@@ -87,8 +95,21 @@ 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.equals("")){
|
||||
if(tmpDirPath == null || tmpDirPath.equals("")){
|
||||
tmpDir = new File(getGitBucketHome(), "tmp");
|
||||
if(!tmpDir.exists()){
|
||||
tmpDir.mkdirs();
|
||||
@@ -111,7 +132,7 @@ public class JettyLauncher {
|
||||
ProtectionDomain domain = JettyLauncher.class.getProtectionDomain();
|
||||
URL location = domain.getCodeSource().getLocation();
|
||||
|
||||
context.setContextPath(contextPath);
|
||||
context.setContextPath(contextPath == null ? "" : contextPath);
|
||||
context.setDescriptor(location.toExternalForm() + "/WEB-INF/web.xml");
|
||||
context.setServer(server);
|
||||
context.setWar(location.toExternalForm());
|
||||
@@ -140,6 +161,23 @@ public class JettyLauncher {
|
||||
return new File(System.getProperty("user.home"), ".gitbucket");
|
||||
}
|
||||
|
||||
private static String getEnvironmentVariable(String key){
|
||||
String value = System.getenv(key.toUpperCase().replace('.', '_'));
|
||||
if (value != null && value.length() == 0){
|
||||
return null;
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
private static int getPort(String port){
|
||||
if(port == null) {
|
||||
return 8080;
|
||||
} else {
|
||||
return Integer.parseInt(port);
|
||||
}
|
||||
}
|
||||
|
||||
private static Handler addStatisticsHandler(Handler handler) {
|
||||
// The graceful shutdown is implemented via the statistics handler.
|
||||
// See the following: https://bugs.eclipse.org/bugs/show_bug.cgi?id=420142
|
||||
|
||||
@@ -1 +1,4 @@
|
||||
notifications:1.7.0
|
||||
notifications:1.8.0
|
||||
gist:4.18.0
|
||||
emoji:4.6.0
|
||||
pages:1.8.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"/>
|
||||
@@ -206,7 +202,7 @@
|
||||
<column name="PULL_REQUEST" type="boolean" nullable="false"/>
|
||||
</createTable>
|
||||
|
||||
<addPrimaryKey constraintName="IDX_ISSUE_PK" tableName="ISSUE" columnNames="ISSUE_ID, USER_NAME, REPOSITORY_NAME"/>
|
||||
<addPrimaryKey constraintName="IDX_ISSUE_PK" tableName="ISSUE" columnNames="USER_NAME, REPOSITORY_NAME, ISSUE_ID"/>
|
||||
<addForeignKeyConstraint constraintName="IDX_ISSUE_FK0" baseTableName="ISSUE" baseColumnNames="USER_NAME, REPOSITORY_NAME" referencedTableName="REPOSITORY" referencedColumnNames="USER_NAME, REPOSITORY_NAME"/>
|
||||
<addForeignKeyConstraint constraintName="IDX_ISSUE_FK2" baseTableName="ISSUE" baseColumnNames="MILESTONE_ID" referencedTableName="MILESTONE" referencedColumnNames="MILESTONE_ID"/>
|
||||
<addForeignKeyConstraint constraintName="IDX_ISSUE_FK1" baseTableName="ISSUE" baseColumnNames="OPENED_USER_NAME" referencedTableName="ACCOUNT" referencedColumnNames="USER_NAME"/>
|
||||
@@ -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"/>
|
||||
|
||||
<!--================================================================================================-->
|
||||
@@ -348,4 +343,4 @@
|
||||
<addPrimaryKey constraintName="IDX_PROTECTED_BRANCH_REQUIRE_CONTEXT_PK" tableName="PROTECTED_BRANCH_REQUIRE_CONTEXT" columnNames="USER_NAME, REPOSITORY_NAME, BRANCH, CONTEXT"/>
|
||||
<addForeignKeyConstraint constraintName="IDX_PROTECTED_BRANCH_REQUIRE_CONTEXT_FK0" baseTableName="PROTECTED_BRANCH_REQUIRE_CONTEXT" baseColumnNames="USER_NAME, REPOSITORY_NAME, BRANCH" referencedTableName="PROTECTED_BRANCH" referencedColumnNames="USER_NAME, REPOSITORY_NAME, BRANCH" onDelete="CASCADE" onUpdate="CASCADE"/>
|
||||
|
||||
</changeSet>
|
||||
</changeSet>
|
||||
|
||||
16
src/main/resources/update/gitbucket-core_4.31.xml
Normal file
16
src/main/resources/update/gitbucket-core_4.31.xml
Normal file
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<changeSet>
|
||||
<!--================================================================================================-->
|
||||
<!-- SSH_KEY -->
|
||||
<!--================================================================================================-->
|
||||
<createTable tableName="GPG_KEY">
|
||||
<column name="USER_NAME" type="varchar(100)" nullable="false"/>
|
||||
<column name="KEY_ID" type="int" nullable="false" autoIncrement="true" unique="true"/>
|
||||
<column name="GPG_KEY_ID" type="bigint" nullable="false"/>
|
||||
<column name="TITLE" type="varchar(100)" nullable="false"/>
|
||||
<column name="PUBLIC_KEY" type="text" nullable="false"/>
|
||||
</createTable>
|
||||
|
||||
<addPrimaryKey constraintName="IDX_GPG_KEY_PK" tableName="GPG_KEY" columnNames="USER_NAME, GPG_KEY_ID"/>
|
||||
<addForeignKeyConstraint constraintName="IDX_GPG_KEY_FK0" baseTableName="GPG_KEY" baseColumnNames="USER_NAME" referencedTableName="ACCOUNT" referencedColumnNames="USER_NAME"/>
|
||||
</changeSet>
|
||||
6
src/main/resources/update/gitbucket-core_4.32.xml
Normal file
6
src/main/resources/update/gitbucket-core_4.32.xml
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<changeSet>
|
||||
<addColumn tableName="PULL_REQUEST">
|
||||
<column name="IS_DRAFT" type="boolean" nullable="false" defaultValueBoolean="false" />
|
||||
</addColumn>
|
||||
</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")
|
||||
|
||||
@@ -60,5 +60,10 @@ object GitBucketCoreModule
|
||||
new Version("4.28.0"),
|
||||
new Version("4.29.0"),
|
||||
new Version("4.30.0"),
|
||||
new Version("4.30.1")
|
||||
new Version("4.30.1"),
|
||||
new Version("4.31.0", new LiquibaseMigration("update/gitbucket-core_4.31.xml")),
|
||||
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")
|
||||
)
|
||||
|
||||
@@ -9,7 +9,7 @@ import gitbucket.core.util.RepositoryName
|
||||
case class ApiBranch(name: String, commit: ApiBranchCommit, protection: ApiBranchProtection)(
|
||||
repositoryName: RepositoryName
|
||||
) extends FieldSerializable {
|
||||
def _links =
|
||||
val _links =
|
||||
Map(
|
||||
"self" -> ApiPath(s"/api/v3/repos/${repositoryName.fullName}/branches/${name}"),
|
||||
"html" -> ApiPath(s"/${repositoryName.fullName}/tree/${name}")
|
||||
|
||||
@@ -21,22 +21,14 @@ case class ApiCommit(
|
||||
modified: List[String],
|
||||
author: ApiPersonIdent,
|
||||
committer: ApiPersonIdent
|
||||
)(repositoryName: RepositoryName, urlIsHtmlUrl: Boolean)
|
||||
)(repositoryName: RepositoryName)
|
||||
extends FieldSerializable {
|
||||
val url = if (urlIsHtmlUrl) {
|
||||
ApiPath(s"/${repositoryName.fullName}/commit/${id}")
|
||||
} else {
|
||||
ApiPath(s"/api/v3/${repositoryName.fullName}/commits/${id}")
|
||||
}
|
||||
val html_url = if (urlIsHtmlUrl) {
|
||||
None
|
||||
} else {
|
||||
Some(ApiPath(s"/${repositoryName.fullName}/commit/${id}"))
|
||||
}
|
||||
val url = ApiPath(s"/api/v3/${repositoryName.fullName}/commits/${id}")
|
||||
val html_url = ApiPath(s"/${repositoryName.fullName}/commit/${id}")
|
||||
}
|
||||
|
||||
object ApiCommit {
|
||||
def apply(git: Git, repositoryName: RepositoryName, commit: CommitInfo, urlIsHtmlUrl: Boolean = false): ApiCommit = {
|
||||
def apply(git: Git, repositoryName: RepositoryName, commit: CommitInfo): ApiCommit = {
|
||||
val diffs = JGitUtil.getDiffs(git, None, commit.id, false, false)
|
||||
ApiCommit(
|
||||
id = commit.id,
|
||||
@@ -53,8 +45,6 @@ object ApiCommit {
|
||||
},
|
||||
author = ApiPersonIdent.author(commit),
|
||||
committer = ApiPersonIdent.committer(commit)
|
||||
)(repositoryName, urlIsHtmlUrl)
|
||||
)(repositoryName)
|
||||
}
|
||||
def forWebhookPayload(git: Git, repositoryName: RepositoryName, commit: CommitInfo): ApiCommit =
|
||||
apply(git, repositoryName, commit, true)
|
||||
}
|
||||
|
||||
@@ -98,7 +98,7 @@ object ApiCommits {
|
||||
url = ApiPath(s"/api/v3/repos/${repositoryName.fullName}/commits/${commitInfo.id}"),
|
||||
sha = commitInfo.id,
|
||||
html_url = ApiPath(s"${repositoryName.fullName}/commit/${commitInfo.id}"),
|
||||
comment_url = ApiPath(""),
|
||||
comment_url = ApiPath(""), // TODO no API for commit comment
|
||||
commit = Commit(
|
||||
url = ApiPath(s"/api/v3/repos/${repositoryName.fullName}/commits/${commitInfo.id}"),
|
||||
author = ApiPersonIdent.author(commitInfo),
|
||||
|
||||
@@ -12,6 +12,7 @@ case class ApiIssue(
|
||||
number: Int,
|
||||
title: String,
|
||||
user: ApiUser,
|
||||
assignee: Option[ApiUser],
|
||||
labels: List[ApiLabel],
|
||||
state: String,
|
||||
created_at: Date,
|
||||
@@ -19,6 +20,7 @@ case class ApiIssue(
|
||||
body: String
|
||||
)(repositoryName: RepositoryName, isPullRequest: Boolean) {
|
||||
val id = 0 // dummy id
|
||||
val assignees = List(assignee).flatten
|
||||
val comments_url = ApiPath(s"/api/v3/repos/${repositoryName.fullName}/issues/${number}/comments")
|
||||
val html_url = ApiPath(s"/${repositoryName.fullName}/${if (isPullRequest) { "pull" } else { "issues" }}/${number}")
|
||||
val pull_request = if (isPullRequest) {
|
||||
@@ -36,11 +38,18 @@ case class ApiIssue(
|
||||
}
|
||||
|
||||
object ApiIssue {
|
||||
def apply(issue: Issue, repositoryName: RepositoryName, user: ApiUser, labels: List[ApiLabel]): ApiIssue =
|
||||
def apply(
|
||||
issue: Issue,
|
||||
repositoryName: RepositoryName,
|
||||
user: ApiUser,
|
||||
assignee: Option[ApiUser],
|
||||
labels: List[ApiLabel]
|
||||
): ApiIssue =
|
||||
ApiIssue(
|
||||
number = issue.issueId,
|
||||
title = issue.title,
|
||||
user = user,
|
||||
assignee = assignee,
|
||||
labels = labels,
|
||||
state = if (issue.closed) { "closed" } else { "open" },
|
||||
body = issue.content.getOrElse(""),
|
||||
|
||||
@@ -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) {
|
||||
|
||||
42
src/main/scala/gitbucket/core/api/ApiRelease.scala
Normal file
42
src/main/scala/gitbucket/core/api/ApiRelease.scala
Normal file
@@ -0,0 +1,42 @@
|
||||
package gitbucket.core.api
|
||||
import gitbucket.core.model.{Account, ReleaseAsset, ReleaseTag}
|
||||
import gitbucket.core.util.RepositoryName
|
||||
|
||||
case class ApiReleaseAsset(name: String, size: Long)(tag: String, fileName: String, repositoryName: RepositoryName) {
|
||||
val label = name
|
||||
val file_id = fileName
|
||||
val browser_download_url = ApiPath(
|
||||
s"/${repositoryName.fullName}/releases/${tag}/assets/${fileName}"
|
||||
)
|
||||
}
|
||||
|
||||
object ApiReleaseAsset {
|
||||
def apply(asset: ReleaseAsset, repositoryName: RepositoryName): ApiReleaseAsset =
|
||||
ApiReleaseAsset(asset.label, asset.size)(asset.tag, asset.fileName, repositoryName)
|
||||
}
|
||||
|
||||
case class ApiRelease(
|
||||
name: String,
|
||||
tag_name: String,
|
||||
body: Option[String],
|
||||
author: ApiUser,
|
||||
assets: Seq[ApiReleaseAsset]
|
||||
)
|
||||
|
||||
object ApiRelease {
|
||||
def apply(
|
||||
release: ReleaseTag,
|
||||
assets: Seq[ReleaseAsset],
|
||||
author: Account,
|
||||
repositoryName: RepositoryName
|
||||
): ApiRelease =
|
||||
ApiRelease(
|
||||
release.name,
|
||||
release.tag,
|
||||
release.content,
|
||||
ApiUser(author),
|
||||
assets.map { asset =>
|
||||
ApiReleaseAsset(asset, repositoryName)
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -13,15 +13,11 @@ case class ApiRepository(
|
||||
`private`: Boolean,
|
||||
default_branch: String,
|
||||
owner: ApiUser
|
||||
)(urlIsHtmlUrl: Boolean) {
|
||||
) {
|
||||
val id = 0 // dummy id
|
||||
val forks_count = forks
|
||||
val watchers_count = watchers
|
||||
val url = if (urlIsHtmlUrl) {
|
||||
ApiPath(s"/${full_name}")
|
||||
} else {
|
||||
ApiPath(s"/api/v3/repos/${full_name}")
|
||||
}
|
||||
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}")
|
||||
@@ -33,8 +29,7 @@ object ApiRepository {
|
||||
repository: Repository,
|
||||
owner: ApiUser,
|
||||
forkedCount: Int = 0,
|
||||
watchers: Int = 0,
|
||||
urlIsHtmlUrl: Boolean = false
|
||||
watchers: Int = 0
|
||||
): ApiRepository =
|
||||
ApiRepository(
|
||||
name = repository.repositoryName,
|
||||
@@ -45,16 +40,13 @@ object ApiRepository {
|
||||
`private` = repository.isPrivate,
|
||||
default_branch = repository.defaultBranch,
|
||||
owner = owner
|
||||
)(urlIsHtmlUrl)
|
||||
)
|
||||
|
||||
def apply(repositoryInfo: RepositoryInfo, owner: ApiUser): ApiRepository =
|
||||
ApiRepository(repositoryInfo.repository, owner, forkedCount = repositoryInfo.forkedCount)
|
||||
|
||||
def apply(repositoryInfo: RepositoryInfo, owner: Account): ApiRepository =
|
||||
this(repositoryInfo.repository, ApiUser(owner))
|
||||
|
||||
def forWebhookPayload(repositoryInfo: RepositoryInfo, owner: ApiUser): ApiRepository =
|
||||
ApiRepository(repositoryInfo.repository, owner, forkedCount = repositoryInfo.forkedCount, urlIsHtmlUrl = true)
|
||||
this(repositoryInfo, ApiUser(owner))
|
||||
|
||||
def forDummyPayload(owner: ApiUser): ApiRepository =
|
||||
ApiRepository(
|
||||
@@ -66,5 +58,5 @@ object ApiRepository {
|
||||
`private` = false,
|
||||
default_branch = "master",
|
||||
owner = owner
|
||||
)(true)
|
||||
)
|
||||
}
|
||||
|
||||
13
src/main/scala/gitbucket/core/api/CreateAFile.scala
Normal file
13
src/main/scala/gitbucket/core/api/CreateAFile.scala
Normal file
@@ -0,0 +1,13 @@
|
||||
package gitbucket.core.api
|
||||
|
||||
/**
|
||||
* https://developer.github.com/v3/repos/contents/#create-a-file
|
||||
*/
|
||||
case class CreateAFile(
|
||||
message: String,
|
||||
content: String,
|
||||
sha: Option[String],
|
||||
branch: Option[String],
|
||||
committer: Option[ApiPusher],
|
||||
author: Option[ApiPusher]
|
||||
)
|
||||
8
src/main/scala/gitbucket/core/api/CreateAGroup.scala
Normal file
8
src/main/scala/gitbucket/core/api/CreateAGroup.scala
Normal file
@@ -0,0 +1,8 @@
|
||||
package gitbucket.core.api
|
||||
|
||||
case class CreateAGroup(
|
||||
login: String,
|
||||
admin: String,
|
||||
profile_name: Option[String],
|
||||
url: Option[String]
|
||||
)
|
||||
@@ -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(
|
||||
|
||||
10
src/main/scala/gitbucket/core/api/CreateARelease.scala
Normal file
10
src/main/scala/gitbucket/core/api/CreateARelease.scala
Normal file
@@ -0,0 +1,10 @@
|
||||
package gitbucket.core.api
|
||||
|
||||
case class CreateARelease(
|
||||
tag_name: String,
|
||||
target_commitish: Option[String],
|
||||
name: Option[String],
|
||||
body: Option[String],
|
||||
draft: Option[Boolean],
|
||||
prerelease: Option[Boolean]
|
||||
)
|
||||
@@ -43,6 +43,8 @@ object JsonFormat {
|
||||
FieldSerializer[ApiCommits.Tree]() +
|
||||
FieldSerializer[ApiCommits.Stats]() +
|
||||
FieldSerializer[ApiCommits.File]() +
|
||||
FieldSerializer[ApiRelease]() +
|
||||
FieldSerializer[ApiReleaseAsset]() +
|
||||
ApiBranchProtection.enforcementLevelSerializer
|
||||
|
||||
def apiPathSerializer(c: Context) =
|
||||
|
||||
@@ -5,7 +5,6 @@ import java.io.File
|
||||
import gitbucket.core.account.html
|
||||
import gitbucket.core.helper
|
||||
import gitbucket.core.model._
|
||||
import gitbucket.core.plugin.PluginRegistry
|
||||
import gitbucket.core.service._
|
||||
import gitbucket.core.service.WebHookService._
|
||||
import gitbucket.core.ssh.SshUtil
|
||||
@@ -17,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
|
||||
@@ -26,6 +26,7 @@ class AccountController
|
||||
with WikiService
|
||||
with LabelsService
|
||||
with SshKeyService
|
||||
with GpgKeyService
|
||||
with OneselfAuthenticator
|
||||
with UsersAuthenticator
|
||||
with GroupManagerAuthenticator
|
||||
@@ -42,6 +43,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
with WikiService
|
||||
with LabelsService
|
||||
with SshKeyService
|
||||
with GpgKeyService
|
||||
with OneselfAuthenticator
|
||||
with UsersAuthenticator
|
||||
with GroupManagerAuthenticator
|
||||
@@ -75,11 +77,13 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
|
||||
case class SshKeyForm(title: String, publicKey: String)
|
||||
|
||||
case class GpgKeyForm(title: String, publicKey: String)
|
||||
|
||||
case class PersonalTokenForm(note: 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(
|
||||
@@ -91,7 +95,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(
|
||||
@@ -108,6 +112,11 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
"publicKey" -> trim2(label("Key", text(required, validPublicKey)))
|
||||
)(SshKeyForm.apply)
|
||||
|
||||
val gpgKeyForm = mapping(
|
||||
"title" -> trim(label("Title", text(required, maxlength(100)))),
|
||||
"publicKey" -> label("Key", text(required, validGpgPublicKey))
|
||||
)(GpgKeyForm.apply)
|
||||
|
||||
val personalTokenForm = mapping(
|
||||
"note" -> trim(label("Token", text(required, maxlength(100))))
|
||||
)(PersonalTokenForm.apply)
|
||||
@@ -247,12 +256,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
account,
|
||||
members,
|
||||
extraMailAddresses,
|
||||
context.loginAccount.exists(
|
||||
x =>
|
||||
members.exists { member =>
|
||||
member.userName == x.userName && member.isManager
|
||||
}
|
||||
)
|
||||
isGroupManager(context.loginAccount, members)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -264,12 +268,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
if (account.isGroupAccount) Nil else getGroupsByUserName(userName),
|
||||
getVisibleRepositories(context.loginAccount, Some(userName)),
|
||||
extraMailAddresses,
|
||||
context.loginAccount.exists(
|
||||
x =>
|
||||
members.exists { member =>
|
||||
member.userName == x.userName && member.isManager
|
||||
}
|
||||
)
|
||||
isGroupManager(context.loginAccount, members)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -338,7 +337,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
|
||||
updateImage(userName, form.fileId, form.clearImage)
|
||||
updateAccountExtraMailAddresses(userName, form.extraMailAddresses.filter(_ != ""))
|
||||
flash += "info" -> "Account information has been updated."
|
||||
flash.update("info", "Account information has been updated.")
|
||||
redirect(s"/${userName}/_edit")
|
||||
|
||||
} getOrElse NotFound()
|
||||
@@ -350,7 +349,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
getAccountByUserName(userName, true).map {
|
||||
account =>
|
||||
if (isLastAdministrator(account)) {
|
||||
flash += "error" -> "Account can't be removed because this is last one administrator."
|
||||
flash.update("error", "Account can't be removed because this is last one administrator.")
|
||||
redirect(s"/${userName}/_edit")
|
||||
} else {
|
||||
// // Remove repositories
|
||||
@@ -387,6 +386,27 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
redirect(s"/${userName}/_ssh")
|
||||
})
|
||||
|
||||
get("/:userName/_gpg")(oneselfOnly {
|
||||
val userName = params("userName")
|
||||
getAccountByUserName(userName).map { x =>
|
||||
//html.ssh(x, getPublicKeys(x.userName))
|
||||
html.gpg(x, getGpgPublicKeys(x.userName))
|
||||
} getOrElse NotFound()
|
||||
})
|
||||
|
||||
post("/:userName/_gpg", gpgKeyForm)(oneselfOnly { form =>
|
||||
val userName = params("userName")
|
||||
addGpgPublicKey(userName, form.title, form.publicKey)
|
||||
redirect(s"/${userName}/_gpg")
|
||||
})
|
||||
|
||||
get("/:userName/_gpg/delete/:id")(oneselfOnly {
|
||||
val userName = params("userName")
|
||||
val keyId = params("id").toInt
|
||||
deleteGpgPublicKey(userName, keyId)
|
||||
redirect(s"/${userName}/_gpg")
|
||||
})
|
||||
|
||||
get("/:userName/_application")(oneselfOnly {
|
||||
val userName = params("userName")
|
||||
getAccountByUserName(userName).map { x =>
|
||||
@@ -409,7 +429,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
val userName = params("userName")
|
||||
getAccountByUserName(userName).map { x =>
|
||||
val (tokenId, token) = generateAccessToken(userName, form.note)
|
||||
flash += "generatedToken" -> (tokenId, token)
|
||||
flash.update("generatedToken", (tokenId, token))
|
||||
}
|
||||
redirect(s"/${userName}/_application")
|
||||
})
|
||||
@@ -445,7 +465,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
post("/:userName/_hooks/new", accountWebHookForm(false))(managersOnly { form =>
|
||||
val userName = params("userName")
|
||||
addAccountWebHook(userName, form.url, form.events, form.ctype, form.token)
|
||||
flash += "info" -> s"Webhook ${form.url} created"
|
||||
flash.update("info", s"Webhook ${form.url} created")
|
||||
redirect(s"/${userName}/_hooks")
|
||||
})
|
||||
|
||||
@@ -455,7 +475,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
get("/:userName/_hooks/delete")(managersOnly {
|
||||
val userName = params("userName")
|
||||
deleteAccountWebHook(userName, params("url"))
|
||||
flash += "info" -> s"Webhook ${params("url")} deleted"
|
||||
flash.update("info", s"Webhook ${params("url")} deleted")
|
||||
redirect(s"/${userName}/_hooks")
|
||||
})
|
||||
|
||||
@@ -478,7 +498,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
post("/:userName/_hooks/edit", accountWebHookForm(true))(managersOnly { form =>
|
||||
val userName = params("userName")
|
||||
updateAccountWebHook(userName, form.url, form.events, form.ctype, form.token)
|
||||
flash += "info" -> s"webhook ${form.url} updated"
|
||||
flash.update("info", s"webhook ${form.url} updated")
|
||||
redirect(s"/${userName}/_hooks")
|
||||
})
|
||||
|
||||
@@ -507,13 +527,14 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
WebHookPushPayload.createDummyPayload(ownerAccount)
|
||||
}
|
||||
|
||||
val (webHook, json, reqFuture, resFuture) = callWebHook(WebHook.Push, List(dummyWebHookInfo), dummyPayload).head
|
||||
val (webHook, json, reqFuture, resFuture) =
|
||||
callWebHook(WebHook.Push, List(dummyWebHookInfo), dummyPayload, context.settings).head
|
||||
|
||||
val toErrorMap: PartialFunction[Throwable, Map[String, String]] = {
|
||||
case e: java.net.UnknownHostException => Map("error" -> ("Unknown host " + e.getMessage))
|
||||
case e: java.lang.IllegalArgumentException => Map("error" -> ("invalid url"))
|
||||
case e: org.apache.http.client.ClientProtocolException => Map("error" -> ("invalid url"))
|
||||
case NonFatal(e) => Map("error" -> (e.getClass + " " + e.getMessage))
|
||||
case NonFatal(e) => Map("error" -> (s"${e.getClass} ${e.getMessage}"))
|
||||
}
|
||||
|
||||
contentType = formats("json")
|
||||
@@ -653,7 +674,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
|
||||
updateImage(form.groupName, form.fileId, form.clearImage)
|
||||
|
||||
flash += "info" -> "Account information has been updated."
|
||||
flash.update("info", "Account information has been updated.")
|
||||
redirect(s"/${groupName}/_editgroup")
|
||||
|
||||
} getOrElse NotFound()
|
||||
@@ -671,27 +692,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 {
|
||||
@@ -715,8 +737,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
|
||||
|
||||
@@ -730,7 +752,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
// redirect to the repository
|
||||
redirect(s"/${accountName}/${repository.name}")
|
||||
}
|
||||
} else BadRequest()
|
||||
} else Forbidden()
|
||||
})
|
||||
|
||||
private def existsAccount: Constraint = new Constraint() {
|
||||
@@ -771,6 +793,20 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
}
|
||||
}
|
||||
|
||||
private def validGpgPublicKey: Constraint = new Constraint() {
|
||||
override def validate(name: String, value: String, messages: Messages): Option[String] = {
|
||||
GpgUtil.str2GpgKeyId(value) match {
|
||||
case Some(s) if GpgUtil.getGpgKey(s).isEmpty =>
|
||||
None
|
||||
case Some(_) =>
|
||||
Some("GPG key is duplicated.")
|
||||
case None =>
|
||||
Some("GPG key is invalid.")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private def validAccountName: Constraint = new Constraint() {
|
||||
override def validate(name: String, value: String, messages: Messages): Option[String] = {
|
||||
getAccountByUserName(value) match {
|
||||
@@ -779,4 +815,13 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private def isGroupManager(account: Option[Account], members: Seq[GroupMember]): Boolean = {
|
||||
account.exists { account =>
|
||||
account.isAdmin || members.exists { member =>
|
||||
member.userName == account.userName && member.isManager
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ class ApiController
|
||||
with ApiIssueLabelControllerBase
|
||||
with ApiOrganizationControllerBase
|
||||
with ApiPullRequestControllerBase
|
||||
with ApiReleaseControllerBase
|
||||
with ApiRepositoryBranchControllerBase
|
||||
with ApiRepositoryCollaboratorControllerBase
|
||||
with ApiRepositoryCommitControllerBase
|
||||
@@ -31,7 +32,9 @@ class ApiController
|
||||
with PullRequestService
|
||||
with CommitsService
|
||||
with CommitStatusService
|
||||
with ReleaseService
|
||||
with RepositoryCreationService
|
||||
with RepositoryCommitFileService
|
||||
with IssueCreationService
|
||||
with HandleCommentService
|
||||
with MergeService
|
||||
|
||||
@@ -20,6 +20,7 @@ import javax.servlet.{FilterChain, ServletRequest, ServletResponse}
|
||||
import is.tagomor.woothee.Classifier
|
||||
|
||||
import scala.util.Try
|
||||
import scala.util.Using
|
||||
import net.coobird.thumbnailator.Thumbnails
|
||||
import org.eclipse.jgit.api.Git
|
||||
import org.eclipse.jgit.lib.ObjectId
|
||||
@@ -240,7 +241,7 @@ abstract class ControllerBase
|
||||
case false => None
|
||||
}
|
||||
|
||||
using(new TreeWalk(git.getRepository)) { treeWalk =>
|
||||
Using.resource(new TreeWalk(git.getRepository)) { treeWalk =>
|
||||
treeWalk.addTree(revCommit.getTree)
|
||||
treeWalk.setRecursive(true)
|
||||
_getPathObjectId(path, treeWalk)
|
||||
@@ -268,7 +269,7 @@ abstract class ControllerBase
|
||||
response.setContentLength(attrs("size").toInt)
|
||||
val oid = attrs("oid").split(":")(1)
|
||||
|
||||
using(new FileInputStream(FileUtil.getLfsFilePath(repository.owner, repository.name, oid))) { in =>
|
||||
Using.resource(new FileInputStream(FileUtil.getLfsFilePath(repository.owner, repository.name, oid))) { in =>
|
||||
IOUtils.copy(in, response.getOutputStream)
|
||||
}
|
||||
} else {
|
||||
@@ -324,6 +325,8 @@ case class Context(
|
||||
trait AccountManagementControllerBase extends ControllerBase {
|
||||
self: AccountService =>
|
||||
|
||||
private val logger = LoggerFactory.getLogger(getClass)
|
||||
|
||||
protected def updateImage(userName: String, fileId: Option[String], clearImage: Boolean): Unit =
|
||||
if (clearImage) {
|
||||
getAccountByUserName(userName).flatMap(_.image).foreach { image =>
|
||||
@@ -331,17 +334,21 @@ trait AccountManagementControllerBase extends ControllerBase {
|
||||
updateAvatarImage(userName, None)
|
||||
}
|
||||
} else {
|
||||
fileId.foreach { fileId =>
|
||||
val filename = "avatar." + FileUtil.getExtension(session.getAndRemove(Keys.Session.Upload(fileId)).get)
|
||||
val uploadDir = getUserUploadDir(userName)
|
||||
if (!uploadDir.exists) {
|
||||
uploadDir.mkdirs()
|
||||
try {
|
||||
fileId.foreach { fileId =>
|
||||
val filename = "avatar." + FileUtil.getExtension(session.getAndRemove(Keys.Session.Upload(fileId)).get)
|
||||
val uploadDir = getUserUploadDir(userName)
|
||||
if (!uploadDir.exists) {
|
||||
uploadDir.mkdirs()
|
||||
}
|
||||
Thumbnails
|
||||
.of(new File(getTemporaryDir(session.getId), FileUtil.checkFilename(fileId)))
|
||||
.size(324, 324)
|
||||
.toFile(new File(uploadDir, FileUtil.checkFilename(filename)))
|
||||
updateAvatarImage(userName, Some(filename))
|
||||
}
|
||||
Thumbnails
|
||||
.of(new File(getTemporaryDir(session.getId), FileUtil.checkFilename(fileId)))
|
||||
.size(324, 324)
|
||||
.toFile(new File(uploadDir, FileUtil.checkFilename(filename)))
|
||||
updateAvatarImage(userName, Some(filename))
|
||||
} catch {
|
||||
case e: Exception => logger.info("Error while updateImage" + e.getMessage)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -359,7 +366,7 @@ trait AccountManagementControllerBase extends ControllerBase {
|
||||
params: Map[String, Seq[String]],
|
||||
messages: Messages
|
||||
): Option[String] = {
|
||||
val extraMailAddresses = params.filterKeys(k => k.startsWith("extraMailAddresses"))
|
||||
val extraMailAddresses = params.view.filterKeys(k => k.startsWith("extraMailAddresses"))
|
||||
if (extraMailAddresses.exists {
|
||||
case (k, v) =>
|
||||
v.contains(value)
|
||||
@@ -382,7 +389,7 @@ trait AccountManagementControllerBase extends ControllerBase {
|
||||
params: Map[String, Seq[String]],
|
||||
messages: Messages
|
||||
): Option[String] = {
|
||||
val extraMailAddresses = params.filterKeys(k => k.startsWith("extraMailAddresses"))
|
||||
val extraMailAddresses = params.view.filterKeys(k => k.startsWith("extraMailAddresses"))
|
||||
if (Some(value) == params.optionValue("mailAddress") || extraMailAddresses.count {
|
||||
case (k, v) =>
|
||||
v.contains(value)
|
||||
|
||||
@@ -9,6 +9,7 @@ import gitbucket.core.service.IssuesService._
|
||||
class DashboardController
|
||||
extends DashboardControllerBase
|
||||
with IssuesService
|
||||
with MergeService
|
||||
with PullRequestService
|
||||
with RepositoryService
|
||||
with AccountService
|
||||
@@ -26,7 +27,12 @@ trait DashboardControllerBase extends ControllerBase {
|
||||
self: IssuesService with PullRequestService with RepositoryService with AccountService 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)
|
||||
})
|
||||
|
||||
@@ -92,7 +98,12 @@ trait DashboardControllerBase extends ControllerBase {
|
||||
},
|
||||
filter,
|
||||
getGroupNames(userName),
|
||||
getVisibleRepositories(context.loginAccount, withoutPhysicalInfo = true)
|
||||
getVisibleRepositories(
|
||||
context.loginAccount,
|
||||
None,
|
||||
withoutPhysicalInfo = true,
|
||||
limit = context.settings.limitVisibleRepositories
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -117,7 +128,12 @@ trait DashboardControllerBase extends ControllerBase {
|
||||
},
|
||||
filter,
|
||||
getGroupNames(userName),
|
||||
getVisibleRepositories(context.loginAccount, withoutPhysicalInfo = true)
|
||||
getVisibleRepositories(
|
||||
context.loginAccount,
|
||||
None,
|
||||
withoutPhysicalInfo = true,
|
||||
limit = context.settings.limitVisibleRepositories
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,9 @@ import org.scalatra._
|
||||
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.
|
||||
*
|
||||
@@ -26,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
|
||||
@@ -42,6 +45,7 @@ class FileUploadController
|
||||
}
|
||||
|
||||
post("/tmp") {
|
||||
setMultipartConfig()
|
||||
execute(
|
||||
{ (file, fileId) =>
|
||||
FileUtils
|
||||
@@ -53,6 +57,7 @@ class FileUploadController
|
||||
}
|
||||
|
||||
post("/file/:owner/:repository") {
|
||||
setMultipartConfig()
|
||||
execute(
|
||||
{ (file, fileId) =>
|
||||
FileUtils.writeByteArrayToFile(
|
||||
@@ -68,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 =>
|
||||
@@ -80,7 +86,7 @@ class FileUploadController
|
||||
{ (file, fileId) =>
|
||||
val fileName = file.getName
|
||||
LockUtil.lock(s"${owner}/${repository}/wiki") {
|
||||
using(Git.open(Directory.getWikiRepositoryDir(owner, repository))) {
|
||||
Using.resource(Git.open(Directory.getWikiRepositoryDir(owner, repository))) {
|
||||
git =>
|
||||
val builder = DirCache.newInCore.builder()
|
||||
val inserter = git.getRepository.newObjectInserter()
|
||||
@@ -126,6 +132,7 @@ class FileUploadController
|
||||
}
|
||||
|
||||
post("/release/:owner/:repository/:tag") {
|
||||
setMultipartConfigForLargeFile()
|
||||
session
|
||||
.get(Keys.Session.LoginAccount)
|
||||
.collect {
|
||||
@@ -148,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) =>
|
||||
@@ -157,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 {
|
||||
|
||||
@@ -10,6 +10,7 @@ import gitbucket.core.service._
|
||||
import gitbucket.core.util.Implicits._
|
||||
import gitbucket.core.util.SyntaxSugars._
|
||||
import gitbucket.core.util._
|
||||
import gitbucket.core.view.helpers._
|
||||
import org.scalatra.Ok
|
||||
import org.scalatra.forms._
|
||||
|
||||
@@ -64,7 +65,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
|
||||
)
|
||||
@@ -82,7 +88,7 @@ trait IndexControllerBase extends ControllerBase {
|
||||
get("/signin") {
|
||||
val redirect = params.get("redirect")
|
||||
if (redirect.isDefined && redirect.get.startsWith("/")) {
|
||||
flash += Keys.Flash.Redirect -> redirect.get
|
||||
flash.update(Keys.Flash.Redirect, redirect.get)
|
||||
}
|
||||
gitbucket.core.html.signin(flash.get("userName"), flash.get("password"), flash.get("error"))
|
||||
}
|
||||
@@ -95,9 +101,9 @@ trait IndexControllerBase extends ControllerBase {
|
||||
case _ => signin(account)
|
||||
}
|
||||
case None =>
|
||||
flash += "userName" -> form.userName
|
||||
flash += "password" -> form.password
|
||||
flash += "error" -> "Sorry, your Username and/or Password is incorrect. Please try again."
|
||||
flash.update("userName", form.userName)
|
||||
flash.update("password", form.password)
|
||||
flash.update("error", "Sorry, your Username and/or Password is incorrect. Please try again.")
|
||||
redirect("/signin")
|
||||
}
|
||||
}
|
||||
@@ -131,15 +137,15 @@ trait IndexControllerBase extends ControllerBase {
|
||||
val redirectURI = new URI(s"$baseUrl/signin/oidc")
|
||||
session.get(Keys.Session.OidcContext) match {
|
||||
case Some(context: OidcContext) =>
|
||||
authenticate(params, redirectURI, context.state, context.nonce, oidc) map { account =>
|
||||
authenticate(params.toMap, redirectURI, context.state, context.nonce, oidc).map { account =>
|
||||
signin(account, context.redirectBackURI)
|
||||
} orElse {
|
||||
flash += "error" -> "Sorry, authentication failed. Please try again."
|
||||
flash.update("error", "Sorry, authentication failed. Please try again.")
|
||||
session.invalidate()
|
||||
redirect("/signin")
|
||||
}
|
||||
case _ =>
|
||||
flash += "error" -> "Sorry, something wrong. Please try again."
|
||||
flash.update("error", "Sorry, something wrong. Please try again.")
|
||||
session.invalidate()
|
||||
redirect("/signin")
|
||||
}
|
||||
@@ -206,7 +212,8 @@ trait IndexControllerBase extends ControllerBase {
|
||||
}
|
||||
.map { t =>
|
||||
Map(
|
||||
"label" -> s"<b>@${StringUtil.escapeHtml(t.userName)}</b> ${StringUtil.escapeHtml(t.fullName)}",
|
||||
"label" -> s"${avatar(t.userName, 16)}<b>@${StringUtil.escapeHtml(t.userName)}</b> ${StringUtil
|
||||
.escapeHtml(t.fullName)}",
|
||||
"value" -> t.userName
|
||||
)
|
||||
}
|
||||
@@ -225,7 +232,7 @@ trait IndexControllerBase extends ControllerBase {
|
||||
} getOrElse ""
|
||||
})
|
||||
|
||||
// TODO Move to RepositoryViwerController?
|
||||
// TODO Move to RepositoryViewrController?
|
||||
get("/:owner/:repository/search")(referrersOnly { repository =>
|
||||
defining(params.getOrElse("q", "").trim, params.getOrElse("type", "code")) {
|
||||
case (query, target) =>
|
||||
@@ -277,11 +284,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)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ class IssuesController
|
||||
with ReadableUsersAuthenticator
|
||||
with ReferrerAuthenticator
|
||||
with WritableUsersAuthenticator
|
||||
with MergeService
|
||||
with PullRequestService
|
||||
with WebHookIssueCommentService
|
||||
with WebHookPullRequestReviewCommentService
|
||||
@@ -144,7 +145,7 @@ trait IssuesControllerBase extends ControllerBase {
|
||||
form.assignedUserName,
|
||||
form.milestoneId,
|
||||
form.priorityId,
|
||||
form.labelNames.toArray.flatMap(_.split(",")),
|
||||
form.labelNames.toSeq.flatMap(_.split(",")),
|
||||
context.loginAccount.get
|
||||
)
|
||||
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
package gitbucket.core.controller
|
||||
|
||||
import gitbucket.core.issues.milestones.html
|
||||
import gitbucket.core.service.{RepositoryService, MilestonesService, AccountService}
|
||||
import gitbucket.core.util.{ReferrerAuthenticator, WritableUsersAuthenticator}
|
||||
import gitbucket.core.service.{AccountService, MilestonesService, RepositoryService}
|
||||
import gitbucket.core.util.Implicits._
|
||||
import gitbucket.core.util.{ReferrerAuthenticator, WritableUsersAuthenticator}
|
||||
import gitbucket.core.util.SyntaxSugars._
|
||||
import org.scalatra.forms._
|
||||
import org.scalatra.i18n.Messages
|
||||
|
||||
class MilestonesController
|
||||
extends MilestonesControllerBase
|
||||
@@ -20,7 +22,7 @@ trait MilestonesControllerBase extends ControllerBase {
|
||||
case class MilestoneForm(title: String, description: Option[String], dueDate: Option[java.util.Date])
|
||||
|
||||
val milestoneForm = mapping(
|
||||
"title" -> trim(label("Title", text(required, maxlength(100)))),
|
||||
"title" -> trim(label("Title", text(required, maxlength(100), uniqueMilestone))),
|
||||
"description" -> trim(label("Description", optional(text()))),
|
||||
"dueDate" -> trim(label("Due Date", optional(date())))
|
||||
)(MilestoneForm.apply)
|
||||
@@ -86,4 +88,29 @@ trait MilestonesControllerBase extends ControllerBase {
|
||||
} getOrElse NotFound()
|
||||
})
|
||||
|
||||
private def uniqueMilestone: Constraint = new Constraint() {
|
||||
override def validate(
|
||||
name: String,
|
||||
value: String,
|
||||
params: Map[String, Seq[String]],
|
||||
messages: Messages
|
||||
): Option[String] = {
|
||||
for {
|
||||
owner <- params.optionValue("owner")
|
||||
repository <- params.optionValue("repository")
|
||||
_ <- params.optionValue("milestoneId") match {
|
||||
// existing milestone
|
||||
case Some(id) =>
|
||||
getMilestones(owner, repository)
|
||||
.find(m => m.title.equalsIgnoreCase(value) && m.milestoneId.toString != id)
|
||||
// new milestone
|
||||
case None =>
|
||||
getMilestones(owner, repository)
|
||||
.find(m => m.title.equalsIgnoreCase(value))
|
||||
}
|
||||
} yield {
|
||||
"Milestone already exists."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
package gitbucket.core.controller
|
||||
|
||||
import gitbucket.core.model.{CommitComment, CommitComments, IssueComment, WebHook}
|
||||
import gitbucket.core.plugin.PluginRegistry
|
||||
import gitbucket.core.pulls.html
|
||||
import gitbucket.core.service.CommitStatusService
|
||||
import gitbucket.core.service.MergeService
|
||||
@@ -15,11 +13,9 @@ import gitbucket.core.util.Implicits._
|
||||
import gitbucket.core.util._
|
||||
import org.scalatra.forms._
|
||||
import org.eclipse.jgit.api.Git
|
||||
import org.eclipse.jgit.lib.{ObjectId, PersonIdent}
|
||||
import org.eclipse.jgit.revwalk.RevWalk
|
||||
import org.scalatra.BadRequest
|
||||
|
||||
import scala.collection.JavaConverters._
|
||||
import scala.util.Using
|
||||
|
||||
class PullRequestsController
|
||||
extends PullRequestsControllerBase
|
||||
@@ -69,6 +65,7 @@ trait PullRequestsControllerBase extends ControllerBase {
|
||||
"requestBranch" -> trim(text(required, maxlength(100))),
|
||||
"commitIdFrom" -> trim(text(required, maxlength(40))),
|
||||
"commitIdTo" -> trim(text(required, maxlength(40))),
|
||||
"isDraft" -> trim(boolean(required)),
|
||||
"assignedUserName" -> trim(optional(text())),
|
||||
"milestoneId" -> trim(optional(number())),
|
||||
"priorityId" -> trim(optional(number())),
|
||||
@@ -77,7 +74,8 @@ trait PullRequestsControllerBase extends ControllerBase {
|
||||
|
||||
val mergeForm = mapping(
|
||||
"message" -> trim(label("Message", text(required))),
|
||||
"strategy" -> trim(label("Strategy", text(required)))
|
||||
"strategy" -> trim(label("Strategy", text(required))),
|
||||
"isDraft" -> trim(boolean(required))
|
||||
)(MergeForm.apply)
|
||||
|
||||
case class PullRequestForm(
|
||||
@@ -90,13 +88,14 @@ trait PullRequestsControllerBase extends ControllerBase {
|
||||
requestBranch: String,
|
||||
commitIdFrom: String,
|
||||
commitIdTo: String,
|
||||
isDraft: Boolean,
|
||||
assignedUserName: Option[String],
|
||||
milestoneId: Option[Int],
|
||||
priorityId: Option[Int],
|
||||
labelNames: Option[String]
|
||||
)
|
||||
|
||||
case class MergeForm(message: String, strategy: String)
|
||||
case class MergeForm(message: String, strategy: String, isDraft: Boolean)
|
||||
|
||||
get("/:owner/:repository/pulls")(referrersOnly { repository =>
|
||||
val q = request.getParameter("q")
|
||||
@@ -133,7 +132,7 @@ trait PullRequestsControllerBase extends ControllerBase {
|
||||
hasDeveloperRole(pullreq.requestUserName, pullreq.requestRepositoryName, context.loginAccount),
|
||||
repository,
|
||||
getRepository(pullreq.requestUserName, pullreq.requestRepositoryName),
|
||||
flash.toMap.map(f => f._1 -> f._2.toString)
|
||||
flash.iterator.map(f => f._1 -> f._2.toString).toMap
|
||||
)
|
||||
|
||||
// html.pullreq(
|
||||
@@ -266,11 +265,11 @@ trait PullRequestsControllerBase extends ControllerBase {
|
||||
val repository = getRepository(owner, name).get
|
||||
val branchProtection = getProtectedBranchInfo(owner, name, pullreq.requestBranch)
|
||||
if (branchProtection.enabled) {
|
||||
flash += "error" -> s"branch ${pullreq.requestBranch} is protected."
|
||||
flash.update("error", s"branch ${pullreq.requestBranch} is protected.")
|
||||
} else {
|
||||
if (repository.repository.defaultBranch != pullreq.requestBranch) {
|
||||
val userName = context.loginAccount.get.userName
|
||||
using(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
|
||||
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)
|
||||
}
|
||||
@@ -283,9 +282,10 @@ trait PullRequestsControllerBase extends ControllerBase {
|
||||
"delete_branch"
|
||||
)
|
||||
} else {
|
||||
flash += "error" -> s"""Can't delete the default branch "${pullreq.requestBranch}"."""
|
||||
flash.update("error", s"""Can't delete the default branch "${pullreq.requestBranch}".""")
|
||||
}
|
||||
}
|
||||
|
||||
redirect(s"/${baseRepository.owner}/${baseRepository.name}/pull/${issueId}")
|
||||
}) getOrElse NotFound()
|
||||
})
|
||||
@@ -303,7 +303,7 @@ trait PullRequestsControllerBase extends ControllerBase {
|
||||
} yield {
|
||||
val branchProtection = getProtectedBranchInfo(owner, name, pullreq.requestBranch)
|
||||
if (branchProtection.needStatusCheck(loginAccount.userName)) {
|
||||
flash += "error" -> s"branch ${pullreq.requestBranch} is protected need status check."
|
||||
flash.update("error", s"branch ${pullreq.requestBranch} is protected need status check.")
|
||||
} else {
|
||||
LockUtil.lock(s"${owner}/${name}") {
|
||||
val alias =
|
||||
@@ -312,9 +312,11 @@ trait PullRequestsControllerBase extends ControllerBase {
|
||||
} else {
|
||||
s"${pullreq.userName}:${pullreq.branch}"
|
||||
}
|
||||
val existIds = using(Git.open(Directory.getRepositoryDir(owner, name))) { git =>
|
||||
JGitUtil.getAllCommitIds(git)
|
||||
}.toSet
|
||||
val existIds = Using
|
||||
.resource(Git.open(Directory.getRepositoryDir(owner, name))) { git =>
|
||||
JGitUtil.getAllCommitIds(git)
|
||||
}
|
||||
.toSet
|
||||
pullRemote(
|
||||
repository,
|
||||
pullreq.requestBranch,
|
||||
@@ -322,14 +324,15 @@ trait PullRequestsControllerBase extends ControllerBase {
|
||||
pullreq.branch,
|
||||
loginAccount,
|
||||
s"Merge branch '${alias}' into ${pullreq.requestBranch}",
|
||||
Some(pullreq)
|
||||
Some(pullreq),
|
||||
context.settings
|
||||
) match {
|
||||
case None => // conflict
|
||||
flash += "error" -> s"Can't automatic merging branch '${alias}' into ${pullreq.requestBranch}."
|
||||
flash.update("error", s"Can't automatic merging branch '${alias}' into ${pullreq.requestBranch}.")
|
||||
case Some(oldId) =>
|
||||
// update pull request
|
||||
updatePullRequests(owner, name, pullreq.requestBranch, loginAccount, "synchronize")
|
||||
flash += "info" -> s"Merge branch '${alias}' into ${pullreq.requestBranch}"
|
||||
updatePullRequests(owner, name, pullreq.requestBranch, loginAccount, "synchronize", context.settings)
|
||||
flash.update("info", s"Merge branch '${alias}' into ${pullreq.requestBranch}")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -338,14 +341,34 @@ trait PullRequestsControllerBase extends ControllerBase {
|
||||
}) getOrElse NotFound()
|
||||
})
|
||||
|
||||
post("/:owner/:repository/pull/:id/update_draft")(readableUsersOnly { baseRepository =>
|
||||
(for {
|
||||
issueId <- params("id").toIntOpt
|
||||
(_, pullreq) <- getPullRequest(baseRepository.owner, baseRepository.name, issueId)
|
||||
owner = pullreq.requestUserName
|
||||
name = pullreq.requestRepositoryName
|
||||
if hasDeveloperRole(owner, name, context.loginAccount)
|
||||
} yield {
|
||||
updateDraftToPullRequest(baseRepository.owner, baseRepository.name, issueId)
|
||||
}) getOrElse NotFound()
|
||||
})
|
||||
|
||||
post("/:owner/:repository/pull/:id/merge", mergeForm)(writableUsersOnly { (form, repository) =>
|
||||
params("id").toIntOpt.flatMap { issueId =>
|
||||
val owner = repository.owner
|
||||
val name = repository.name
|
||||
|
||||
mergePullRequest(repository, issueId, context.loginAccount.get, form.message, form.strategy) match {
|
||||
mergePullRequest(
|
||||
repository,
|
||||
issueId,
|
||||
context.loginAccount.get,
|
||||
form.message,
|
||||
form.strategy,
|
||||
form.isDraft,
|
||||
context.settings
|
||||
) match {
|
||||
case Right(objectId) => redirect(s"/${owner}/${name}/pull/${issueId}")
|
||||
case Left(message) => Some(BadRequest())
|
||||
case Left(message) => Some(BadRequest(message))
|
||||
}
|
||||
} getOrElse NotFound()
|
||||
})
|
||||
@@ -356,7 +379,7 @@ trait PullRequestsControllerBase extends ControllerBase {
|
||||
case (Some(originUserName), Some(originRepositoryName)) => {
|
||||
getRepository(originUserName, originRepositoryName).map {
|
||||
originRepository =>
|
||||
using(
|
||||
Using.resources(
|
||||
Git.open(getRepositoryDir(originUserName, originRepositoryName)),
|
||||
Git.open(getRepositoryDir(forkedRepository.owner, forkedRepository.name))
|
||||
) { (oldGit, newGit) =>
|
||||
@@ -372,7 +395,7 @@ trait PullRequestsControllerBase extends ControllerBase {
|
||||
} getOrElse NotFound()
|
||||
}
|
||||
case _ => {
|
||||
using(Git.open(getRepositoryDir(forkedRepository.owner, forkedRepository.name))) { git =>
|
||||
Using.resource(Git.open(getRepositoryDir(forkedRepository.owner, forkedRepository.name))) { git =>
|
||||
JGitUtil.getDefaultBranch(git, forkedRepository).map {
|
||||
case (_, defaultBranch) =>
|
||||
redirect(
|
||||
@@ -464,6 +487,7 @@ trait PullRequestsControllerBase extends ControllerBase {
|
||||
getAssignableUserNames(originRepository.owner, originRepository.name),
|
||||
getMilestones(originRepository.owner, originRepository.name),
|
||||
getPriorities(originRepository.owner, originRepository.name),
|
||||
getDefaultPriority(originRepository.owner, originRepository.name),
|
||||
getLabels(originRepository.owner, originRepository.name)
|
||||
)
|
||||
}
|
||||
@@ -493,7 +517,7 @@ trait PullRequestsControllerBase extends ControllerBase {
|
||||
}
|
||||
};
|
||||
originRepository <- getRepository(originOwner, originRepositoryName)) yield {
|
||||
using(
|
||||
Using.resources(
|
||||
Git.open(getRepositoryDir(originRepository.owner, originRepository.name)),
|
||||
Git.open(getRepositoryDir(forkedRepository.owner, forkedRepository.name))
|
||||
) {
|
||||
@@ -534,15 +558,17 @@ trait PullRequestsControllerBase extends ControllerBase {
|
||||
)
|
||||
|
||||
createPullRequest(
|
||||
originUserName = repository.owner,
|
||||
originRepositoryName = repository.name,
|
||||
originRepository = repository,
|
||||
issueId = issueId,
|
||||
originBranch = form.targetBranch,
|
||||
requestUserName = form.requestUserName,
|
||||
requestRepositoryName = form.requestRepositoryName,
|
||||
requestBranch = form.requestBranch,
|
||||
commitIdFrom = form.commitIdFrom,
|
||||
commitIdTo = form.commitIdTo
|
||||
commitIdTo = form.commitIdTo,
|
||||
isDraft = form.isDraft,
|
||||
loginAccount = context.loginAccount.get,
|
||||
settings = context.settings
|
||||
)
|
||||
|
||||
// insert labels
|
||||
@@ -557,29 +583,6 @@ trait PullRequestsControllerBase extends ControllerBase {
|
||||
}
|
||||
}
|
||||
|
||||
// fetch requested branch
|
||||
fetchAsPullRequest(owner, name, form.requestUserName, form.requestRepositoryName, form.requestBranch, issueId)
|
||||
|
||||
// record activity
|
||||
recordPullRequestActivity(owner, name, loginUserName, issueId, form.title)
|
||||
|
||||
// call web hook
|
||||
callPullRequestWebHook("opened", repository, issueId, context.loginAccount.get)
|
||||
|
||||
getIssue(owner, name, issueId.toString) foreach { issue =>
|
||||
// extract references and create refer comment
|
||||
createReferComment(
|
||||
owner,
|
||||
name,
|
||||
issue,
|
||||
form.title + " " + form.content.getOrElse(""),
|
||||
context.loginAccount.get
|
||||
)
|
||||
|
||||
// call hooks
|
||||
PluginRegistry().getPullRequestHooks.foreach(_.created(issue, repository))
|
||||
}
|
||||
|
||||
redirect(s"/${owner}/${name}/pull/${issueId}")
|
||||
}
|
||||
})
|
||||
@@ -589,23 +592,26 @@ trait PullRequestsControllerBase extends ControllerBase {
|
||||
val mailAddresses =
|
||||
context.loginAccount.map(x => Seq(x.mailAddress) ++ getAccountExtraMailAddresses(x.userName)).getOrElse(Nil)
|
||||
|
||||
val branches = JGitUtil
|
||||
.getBranches(
|
||||
owner = repository.owner,
|
||||
name = repository.name,
|
||||
defaultBranch = repository.repository.defaultBranch,
|
||||
origin = repository.repository.originUserName.isEmpty
|
||||
)
|
||||
.filter { x =>
|
||||
x.mergeInfo.map(_.ahead).getOrElse(0) > 0 && x.mergeInfo.map(_.behind).getOrElse(0) == 0 &&
|
||||
x.commitTime.getTime > thresholdTime &&
|
||||
mailAddresses.contains(x.committerEmailAddress)
|
||||
val branches =
|
||||
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) {
|
||||
git =>
|
||||
JGitUtil
|
||||
.getBranches(
|
||||
git = git,
|
||||
defaultBranch = repository.repository.defaultBranch,
|
||||
origin = repository.repository.originUserName.isEmpty
|
||||
)
|
||||
.filter { x =>
|
||||
x.mergeInfo.map(_.ahead).getOrElse(0) > 0 && x.mergeInfo.map(_.behind).getOrElse(0) == 0 &&
|
||||
x.commitTime.getTime > thresholdTime &&
|
||||
mailAddresses.contains(x.committerEmailAddress)
|
||||
}
|
||||
.sortBy { br =>
|
||||
(br.mergeInfo.isEmpty, br.commitTime)
|
||||
}
|
||||
.map(_.name)
|
||||
.reverse
|
||||
}
|
||||
.sortBy { br =>
|
||||
(br.mergeInfo.isEmpty, br.commitTime)
|
||||
}
|
||||
.map(_.name)
|
||||
.reverse
|
||||
|
||||
val targetRepository = (for {
|
||||
parentUserName <- repository.repository.parentUserName
|
||||
|
||||
@@ -2,16 +2,17 @@ package gitbucket.core.controller
|
||||
|
||||
import java.io.File
|
||||
|
||||
import gitbucket.core.service.{AccountService, ActivityService, ReleaseService, RepositoryService}
|
||||
import gitbucket.core.service.{AccountService, ActivityService, PaginationHelper, ReleaseService, RepositoryService}
|
||||
import gitbucket.core.util._
|
||||
import gitbucket.core.util.Directory._
|
||||
import gitbucket.core.util.Implicits._
|
||||
import org.scalatra.forms._
|
||||
import gitbucket.core.releases.html
|
||||
import gitbucket.core.util.SyntaxSugars.using
|
||||
import org.apache.commons.io.FileUtils
|
||||
import org.eclipse.jgit.api.Git
|
||||
|
||||
import scala.util.Using
|
||||
|
||||
class ReleaseController
|
||||
extends ReleaseControllerBase
|
||||
with RepositoryService
|
||||
@@ -42,17 +43,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
|
||||
)
|
||||
})
|
||||
|
||||
@@ -106,7 +104,7 @@ trait ReleaseControllerBase extends ControllerBase {
|
||||
createRelease(repository.owner, repository.name, form.name, form.content, tagName, loginAccount)
|
||||
|
||||
// Insert into RELEASE_ASSET
|
||||
val files = params.collect {
|
||||
val files = params.toMap.collect {
|
||||
case (name, value) if name.startsWith("file:") =>
|
||||
val Array(_, fileId) = name.split(":")
|
||||
(fileId, value)
|
||||
@@ -121,7 +119,7 @@ trait ReleaseControllerBase extends ControllerBase {
|
||||
createReleaseAsset(repository.owner, repository.name, tagName, fileId, fileName, size, loginAccount)
|
||||
}
|
||||
|
||||
recordReleaseActivity(repository.owner, repository.name, loginAccount.userName, form.name)
|
||||
recordReleaseActivity(repository.owner, repository.name, loginAccount.userName, form.name, tagName)
|
||||
|
||||
redirect(s"/${repository.owner}/${repository.name}/releases/${tagName}")
|
||||
})
|
||||
@@ -130,7 +128,7 @@ trait ReleaseControllerBase extends ControllerBase {
|
||||
val Seq(previousTag, currentTag) = multiParams("splat")
|
||||
val previousTagId = repository.tags.collectFirst { case x if x.name == previousTag => x.id }.getOrElse("")
|
||||
|
||||
val commitLog = using(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
|
||||
val commitLog = Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
|
||||
val commits = JGitUtil.getCommitLog(git, previousTagId, currentTag).reverse
|
||||
commits
|
||||
.map { commit =>
|
||||
@@ -174,7 +172,7 @@ trait ReleaseControllerBase extends ControllerBase {
|
||||
val assets = getReleaseAssets(repository.owner, repository.name, tagName)
|
||||
deleteReleaseAssets(repository.owner, repository.name, tagName)
|
||||
|
||||
val files = params.collect {
|
||||
val files = params.toMap.collect {
|
||||
case (name, value) if name.startsWith("file:") =>
|
||||
val Array(_, fileId) = name.split(":")
|
||||
(fileId, value)
|
||||
@@ -215,4 +213,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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,12 +12,15 @@ import gitbucket.core.util.JGitUtil._
|
||||
import gitbucket.core.util.SyntaxSugars._
|
||||
import gitbucket.core.util.Implicits._
|
||||
import gitbucket.core.util.Directory._
|
||||
import gitbucket.core.model.WebHookContentType
|
||||
import org.scalatra.forms._
|
||||
import org.scalatra.i18n.Messages
|
||||
import org.eclipse.jgit.api.Git
|
||||
import org.eclipse.jgit.lib.Constants
|
||||
import org.eclipse.jgit.lib.ObjectId
|
||||
import gitbucket.core.model.WebHookContentType
|
||||
|
||||
import scala.util.Using
|
||||
import org.scalatra.Forbidden
|
||||
|
||||
class RepositorySettingsController
|
||||
extends RepositorySettingsControllerBase
|
||||
@@ -42,7 +45,6 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
||||
|
||||
// for repository options
|
||||
case class OptionsForm(
|
||||
repositoryName: String,
|
||||
description: Option[String],
|
||||
isPrivate: Boolean,
|
||||
issuesOption: String,
|
||||
@@ -55,9 +57,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))),
|
||||
@@ -102,6 +101,15 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
||||
(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)
|
||||
|
||||
@@ -142,13 +150,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 += "info" -> "Repository settings has been updated."
|
||||
redirect(s"/${repository.owner}/${form.repositoryName}/settings/options")
|
||||
flash.update("info", "Repository settings has been updated.")
|
||||
redirect(s"/${repository.owner}/${repository.name}/settings/options")
|
||||
})
|
||||
|
||||
/** branch settings */
|
||||
@@ -164,10 +167,10 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
||||
} else {
|
||||
saveRepositoryDefaultBranch(repository.owner, repository.name, form.defaultBranch)
|
||||
// Change repository HEAD
|
||||
using(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
|
||||
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
|
||||
git.getRepository.updateRef(Constants.HEAD, true).link(Constants.R_HEADS + form.defaultBranch)
|
||||
}
|
||||
flash += "info" -> "Repository default branch has been updated."
|
||||
flash.update("info", "Repository default branch has been updated.")
|
||||
redirect(s"/${repository.owner}/${repository.name}/settings/branches")
|
||||
}
|
||||
})
|
||||
@@ -231,7 +234,7 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
||||
*/
|
||||
post("/:owner/:repository/settings/hooks/new", webHookForm(false))(ownerOnly { (form, repository) =>
|
||||
addWebHook(repository.owner, repository.name, form.url, form.events, form.ctype, form.token)
|
||||
flash += "info" -> s"Webhook ${form.url} created"
|
||||
flash.update("info", s"Webhook ${form.url} created")
|
||||
redirect(s"/${repository.owner}/${repository.name}/settings/hooks")
|
||||
})
|
||||
|
||||
@@ -240,7 +243,7 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
||||
*/
|
||||
get("/:owner/:repository/settings/hooks/delete")(ownerOnly { repository =>
|
||||
deleteWebHook(repository.owner, repository.name, params("url"))
|
||||
flash += "info" -> s"Webhook ${params("url")} deleted"
|
||||
flash.update("info", s"Webhook ${params("url")} deleted")
|
||||
redirect(s"/${repository.owner}/${repository.name}/settings/hooks")
|
||||
})
|
||||
|
||||
@@ -252,11 +255,11 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
||||
Array(h.getName, h.getValue)
|
||||
}
|
||||
|
||||
using(Git.open(getRepositoryDir(repository.owner, repository.name))) {
|
||||
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) {
|
||||
git =>
|
||||
import scala.collection.JavaConverters._
|
||||
import scala.concurrent.duration._
|
||||
import scala.concurrent._
|
||||
import scala.jdk.CollectionConverters._
|
||||
import scala.util.control.NonFatal
|
||||
import org.apache.http.util.EntityUtils
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
@@ -292,13 +295,14 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
||||
)
|
||||
}
|
||||
|
||||
val (webHook, json, reqFuture, resFuture) = callWebHook(WebHook.Push, List(dummyWebHookInfo), dummyPayload).head
|
||||
val (webHook, json, reqFuture, resFuture) =
|
||||
callWebHook(WebHook.Push, List(dummyWebHookInfo), dummyPayload, context.settings).head
|
||||
|
||||
val toErrorMap: PartialFunction[Throwable, Map[String, String]] = {
|
||||
case e: java.net.UnknownHostException => Map("error" -> ("Unknown host " + e.getMessage))
|
||||
case e: java.lang.IllegalArgumentException => Map("error" -> ("invalid url"))
|
||||
case e: org.apache.http.client.ClientProtocolException => Map("error" -> ("invalid url"))
|
||||
case NonFatal(e) => Map("error" -> (e.getClass + " " + e.getMessage))
|
||||
case NonFatal(e) => Map("error" -> (s"${e.getClass} ${e.getMessage}"))
|
||||
}
|
||||
|
||||
contentType = formats("json")
|
||||
@@ -350,7 +354,7 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
||||
*/
|
||||
post("/:owner/:repository/settings/hooks/edit", webHookForm(true))(ownerOnly { (form, repository) =>
|
||||
updateWebHook(repository.owner, repository.name, form.url, form.events, form.ctype, form.token)
|
||||
flash += "info" -> s"webhook ${form.url} updated"
|
||||
flash.update("info", s"webhook ${form.url} updated")
|
||||
redirect(s"/${repository.owner}/${repository.name}/settings/hooks")
|
||||
})
|
||||
|
||||
@@ -361,24 +365,40 @@ 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) {
|
||||
renameRepository(repository.owner, repository.name, repository.owner, form.repositoryName)
|
||||
}
|
||||
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) {
|
||||
renameRepository(repository.owner, repository.name, form.newOwner, repository.name)
|
||||
}
|
||||
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()
|
||||
})
|
||||
|
||||
/**
|
||||
@@ -386,11 +406,11 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
||||
*/
|
||||
post("/:owner/:repository/settings/gc")(ownerOnly { repository =>
|
||||
LockUtil.lock(s"${repository.owner}/${repository.name}") {
|
||||
using(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
|
||||
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
|
||||
git.gc().call()
|
||||
}
|
||||
}
|
||||
flash += "info" -> "Garbage collection has been executed."
|
||||
flash.update("info", "Garbage collection has been executed.")
|
||||
redirect(s"/${repository.owner}/${repository.name}/settings/danger")
|
||||
})
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
package gitbucket.core.controller
|
||||
|
||||
import java.io.File
|
||||
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
|
||||
@@ -14,6 +15,7 @@ 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.util.JGitUtil.CommitInfo
|
||||
import gitbucket.core.view
|
||||
import gitbucket.core.view.helpers
|
||||
import org.apache.commons.compress.archivers.{ArchiveEntry, ArchiveOutputStream}
|
||||
@@ -29,8 +31,10 @@ import org.eclipse.jgit.api.{ArchiveCommand, Git}
|
||||
import org.eclipse.jgit.archive.{TgzFormat, ZipFormat}
|
||||
import org.eclipse.jgit.errors.MissingObjectException
|
||||
import org.eclipse.jgit.lib._
|
||||
import org.eclipse.jgit.treewalk.TreeWalk
|
||||
import org.eclipse.jgit.treewalk.{TreeWalk, WorkingTreeOptions}
|
||||
import org.eclipse.jgit.treewalk.TreeWalk.OperationType
|
||||
import org.eclipse.jgit.treewalk.filter.PathFilter
|
||||
import org.eclipse.jgit.util.io.EolStreamTypeUtil
|
||||
import org.json4s.jackson.Serialization
|
||||
import org.scalatra._
|
||||
import org.scalatra.i18n.Messages
|
||||
@@ -50,6 +54,7 @@ class RepositoryViewerController
|
||||
with ReadableUsersAuthenticator
|
||||
with ReferrerAuthenticator
|
||||
with WritableUsersAuthenticator
|
||||
with MergeService
|
||||
with PullRequestService
|
||||
with CommitStatusService
|
||||
with WebHookPullRequestService
|
||||
@@ -254,11 +259,11 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
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) => states.size + " " + keyState.name }.mkString(", ")
|
||||
val summary = stateMap.map { case (keyState, states) => s"${states.size} ${keyState.name}" }.mkString(", ")
|
||||
state -> summary
|
||||
}
|
||||
|
||||
using(Git.open(getRepositoryDir(repository.owner, repository.name))) {
|
||||
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) {
|
||||
git =>
|
||||
def getTags(sha: String): List[String] = {
|
||||
JGitUtil.getTagsOnCommit(git, sha)
|
||||
@@ -270,9 +275,30 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
if (path.isEmpty) Nil else path.split("/").toList,
|
||||
branchName,
|
||||
repository,
|
||||
logs.splitWith { (commit1, commit2) =>
|
||||
view.helpers.date(commit1.commitTime) == view.helpers.date(commit2.commitTime)
|
||||
},
|
||||
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)
|
||||
}
|
||||
)
|
||||
}
|
||||
.splitWith { (commit1, commit2) =>
|
||||
view.helpers.date(commit1.commitTime) == view.helpers.date(commit2.commitTime)
|
||||
},
|
||||
page,
|
||||
hasNext,
|
||||
hasDeveloperRole(repository.owner, repository.name, context.loginAccount),
|
||||
@@ -290,7 +316,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
val protectedBranch = getProtectedBranchInfo(repository.owner, repository.name, branch)
|
||||
.needStatusCheck(context.loginAccount.get.userName)
|
||||
|
||||
using(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
|
||||
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
|
||||
val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(branch))
|
||||
|
||||
html.editor(
|
||||
@@ -326,9 +352,10 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
repository = repository,
|
||||
branch = form.branch,
|
||||
path = form.path,
|
||||
files = files,
|
||||
files = files.toIndexedSeq,
|
||||
message = form.message.getOrElse("Add files via upload"),
|
||||
loginAccount = context.loginAccount.get
|
||||
loginAccount = context.loginAccount.get,
|
||||
settings = context.settings
|
||||
) {
|
||||
case (git, headTip, builder, inserter) =>
|
||||
JGitUtil.processTree(git, headTip) { (path, tree) =>
|
||||
@@ -359,7 +386,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
val protectedBranch = getProtectedBranchInfo(repository.owner, repository.name, branch)
|
||||
.needStatusCheck(context.loginAccount.get.userName)
|
||||
|
||||
using(Git.open(getRepositoryDir(repository.owner, repository.name))) {
|
||||
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) {
|
||||
git =>
|
||||
val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(branch))
|
||||
|
||||
@@ -386,7 +413,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
|
||||
get("/:owner/:repository/remove/*")(writableUsersOnly { repository =>
|
||||
val (branch, path) = repository.splitPath(multiParams("splat").head)
|
||||
using(Git.open(getRepositoryDir(repository.owner, repository.name))) {
|
||||
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) {
|
||||
git =>
|
||||
val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(branch))
|
||||
|
||||
@@ -415,7 +442,8 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
charset = form.charset,
|
||||
message = form.message.getOrElse(s"Create ${form.newFileName}"),
|
||||
commit = form.commit,
|
||||
loginAccount = context.loginAccount.get
|
||||
loginAccount = context.loginAccount.get,
|
||||
settings = context.settings
|
||||
)
|
||||
|
||||
redirect(
|
||||
@@ -439,7 +467,8 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
form.message.getOrElse(s"Rename ${form.oldFileName.get} to ${form.newFileName}")
|
||||
},
|
||||
commit = form.commit,
|
||||
loginAccount = context.loginAccount.get
|
||||
loginAccount = context.loginAccount.get,
|
||||
settings = context.settings
|
||||
)
|
||||
|
||||
redirect(
|
||||
@@ -459,11 +488,10 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
charset = "",
|
||||
message = form.message.getOrElse(s"Delete ${form.fileName}"),
|
||||
commit = form.commit,
|
||||
loginAccount = context.loginAccount.get
|
||||
loginAccount = context.loginAccount.get,
|
||||
settings = context.settings
|
||||
)
|
||||
|
||||
println(form.path)
|
||||
|
||||
redirect(
|
||||
s"/${repository.owner}/${repository.name}/tree/${form.branch}${if (form.path.length == 0) "" else "/" + form.path}"
|
||||
)
|
||||
@@ -471,7 +499,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
|
||||
get("/:owner/:repository/raw/*")(referrersOnly { repository =>
|
||||
val (id, path) = repository.splitPath(multiParams("splat").head)
|
||||
using(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
|
||||
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
|
||||
val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(id))
|
||||
|
||||
getPathObjectId(git, path, revCommit).map { objectId =>
|
||||
@@ -486,7 +514,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
|
||||
using(Git.open(getRepositoryDir(repository.owner, repository.name))) {
|
||||
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) {
|
||||
git =>
|
||||
val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(id))
|
||||
getPathObjectId(git, path, revCommit).map {
|
||||
@@ -526,7 +554,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
ajaxGet("/:owner/:repository/get-blame/*")(referrersOnly { repository =>
|
||||
val (id, path) = repository.splitPath(multiParams("splat").head)
|
||||
contentType = formats("json")
|
||||
using(Git.open(getRepositoryDir(repository.owner, repository.name))) {
|
||||
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) {
|
||||
git =>
|
||||
val last = git.log.add(git.getRepository.resolve(id)).addPath(path).setMaxCount(1).call.iterator.next.name
|
||||
Serialization.write(
|
||||
@@ -561,7 +589,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
val id = params("id")
|
||||
|
||||
try {
|
||||
using(Git.open(getRepositoryDir(repository.owner, repository.name))) {
|
||||
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) {
|
||||
git =>
|
||||
defining(JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(id))) {
|
||||
revCommit =>
|
||||
@@ -590,7 +618,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
|
||||
get("/:owner/:repository/patch/:id")(referrersOnly { repository =>
|
||||
try {
|
||||
using(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
|
||||
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
|
||||
val diff = JGitUtil.getPatch(git, None, params("id"))
|
||||
contentType = formats("txt")
|
||||
diff
|
||||
@@ -603,7 +631,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
get("/:owner/:repository/patch/*...*")(referrersOnly { repository =>
|
||||
try {
|
||||
val Seq(fromId, toId) = multiParams("splat")
|
||||
using(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
|
||||
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
|
||||
val diff = JGitUtil.getPatch(git, Some(fromId), toId)
|
||||
contentType = formats("txt")
|
||||
diff
|
||||
@@ -723,29 +751,31 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
*/
|
||||
get("/:owner/:repository/branches")(referrersOnly { repository =>
|
||||
val protectedBranches = getProtectedBranchList(repository.owner, repository.name).toSet
|
||||
val branches = JGitUtil
|
||||
.getBranches(
|
||||
owner = repository.owner,
|
||||
name = repository.name,
|
||||
defaultBranch = repository.repository.defaultBranch,
|
||||
origin = repository.repository.originUserName.isEmpty
|
||||
)
|
||||
.sortBy(br => (br.mergeInfo.isEmpty, br.commitTime))
|
||||
.map(
|
||||
br =>
|
||||
(
|
||||
br,
|
||||
getPullRequestByRequestCommit(
|
||||
repository.owner,
|
||||
repository.name,
|
||||
repository.repository.defaultBranch,
|
||||
br.name,
|
||||
br.commitId
|
||||
),
|
||||
protectedBranches.contains(br.name)
|
||||
)
|
||||
)
|
||||
.reverse
|
||||
val branches = Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) {
|
||||
git =>
|
||||
JGitUtil
|
||||
.getBranches(
|
||||
git = git,
|
||||
defaultBranch = repository.repository.defaultBranch,
|
||||
origin = repository.repository.originUserName.isEmpty
|
||||
)
|
||||
.sortBy(br => (br.mergeInfo.isEmpty, br.commitTime))
|
||||
.map(
|
||||
br =>
|
||||
(
|
||||
br,
|
||||
getPullRequestByRequestCommit(
|
||||
repository.owner,
|
||||
repository.name,
|
||||
repository.repository.defaultBranch,
|
||||
br.name,
|
||||
br.commitId
|
||||
),
|
||||
protectedBranches.contains(br.name)
|
||||
)
|
||||
)
|
||||
.reverse
|
||||
}
|
||||
|
||||
html.branches(branches, hasDeveloperRole(repository.owner, repository.name, context.loginAccount), repository)
|
||||
})
|
||||
@@ -761,14 +791,14 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
* Creates a tag.
|
||||
*/
|
||||
post("/:owner/:repository/tag", tagForm)(writableUsersOnly { (form, repository) =>
|
||||
using(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
|
||||
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
|
||||
JGitUtil.createTag(git, form.tagName, form.message, form.commitId)
|
||||
} match {
|
||||
case Right(message) =>
|
||||
flash += "info" -> message
|
||||
flash.update("info", message)
|
||||
redirect(s"/${repository.owner}/${repository.name}/commit/${form.commitId}")
|
||||
case Left(message) =>
|
||||
flash += "error" -> message
|
||||
flash.update("error", message)
|
||||
redirect(s"/${repository.owner}/${repository.name}/commit/${form.commitId}")
|
||||
}
|
||||
})
|
||||
@@ -779,16 +809,16 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
post("/:owner/:repository/branches")(writableUsersOnly { repository =>
|
||||
val newBranchName = params.getOrElse("new", halt(400))
|
||||
val fromBranchName = params.getOrElse("from", halt(400))
|
||||
using(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
|
||||
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
|
||||
JGitUtil.createBranch(git, fromBranchName, newBranchName)
|
||||
} match {
|
||||
case Right(message) =>
|
||||
flash += "info" -> message
|
||||
flash.update("info", message)
|
||||
redirect(
|
||||
s"/${repository.owner}/${repository.name}/tree/${StringUtil.urlEncode(newBranchName).replace("%2F", "/")}"
|
||||
)
|
||||
case Left(message) =>
|
||||
flash += "error" -> message
|
||||
flash.update("error", message)
|
||||
redirect(s"/${repository.owner}/${repository.name}/tree/${fromBranchName}")
|
||||
}
|
||||
})
|
||||
@@ -800,7 +830,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
val branchName = multiParams("splat").head
|
||||
val userName = context.loginAccount.get.userName
|
||||
if (repository.repository.defaultBranch != branchName) {
|
||||
using(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
|
||||
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
|
||||
git.branchDelete().setForce(true).setBranchNames(branchName).call()
|
||||
recordDeleteBranchActivity(repository.owner, repository.name, userName, branchName)
|
||||
}
|
||||
@@ -852,7 +882,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
* Displays the file find of branch.
|
||||
*/
|
||||
get("/:owner/:repository/find/*")(referrersOnly { repository =>
|
||||
using(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
|
||||
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
|
||||
val ref = multiParams("splat").head
|
||||
JGitUtil.getTreeId(git, ref).map { treeId =>
|
||||
html.find(ref, treeId, repository)
|
||||
@@ -864,7 +894,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
* Get all file list of branch.
|
||||
*/
|
||||
ajaxGet("/:owner/:repository/tree-list/:tree")(referrersOnly { repository =>
|
||||
using(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
|
||||
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
|
||||
val treeId = params("tree")
|
||||
contentType = formats("json")
|
||||
Map("paths" -> JGitUtil.getAllFileListByTreeId(git, treeId))
|
||||
@@ -888,7 +918,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
* @return HTML of the file list
|
||||
*/
|
||||
private def fileList(repository: RepositoryService.RepositoryInfo, revstr: String = "", path: String = ".") = {
|
||||
using(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
|
||||
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
|
||||
if (JGitUtil.isEmpty(git)) {
|
||||
html.guide(repository, hasDeveloperRole(repository.owner, repository.name, context.loginAccount))
|
||||
} else {
|
||||
@@ -898,8 +928,9 @@ 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)
|
||||
val parentPath = if (path == ".") Nil else path.split("/").toList
|
||||
// process README.md or README.markdown
|
||||
val readme = files
|
||||
@@ -920,7 +951,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
repository,
|
||||
if (path == ".") Nil else path.split("/").toList, // current path
|
||||
new JGitUtil.CommitInfo(lastModifiedCommit), // last modified commit
|
||||
JGitUtil.getCommitCount(repository.owner, repository.name, lastModifiedCommit.getName),
|
||||
commitCount,
|
||||
files,
|
||||
readme,
|
||||
hasDeveloperRole(repository.owner, repository.name, context.loginAccount),
|
||||
@@ -947,16 +978,16 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
def archive(revision: String, archiveFormat: String, archive: ArchiveOutputStream)(
|
||||
entryCreator: (String, Long, java.util.Date, Int) => ArchiveEntry
|
||||
): Unit = {
|
||||
using(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
|
||||
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
|
||||
val oid = git.getRepository.resolve(revision)
|
||||
val commit = JGitUtil.getRevCommitFromId(git, oid)
|
||||
val date = commit.getCommitterIdent.getWhen
|
||||
val sha1 = oid.getName()
|
||||
val repositorySuffix = (if (sha1.startsWith(revision)) sha1 else revision).replace('/', '-')
|
||||
val pathSuffix = if (path.isEmpty) "" else '-' + path.replace('/', '-')
|
||||
val pathSuffix = if (path.isEmpty) "" else s"-${path.replace('/', '-')}"
|
||||
val baseName = repository.name + "-" + repositorySuffix + pathSuffix
|
||||
|
||||
using(new TreeWalk(git.getRepository)) { treeWalk =>
|
||||
Using.resource(new TreeWalk(git.getRepository)) { treeWalk =>
|
||||
treeWalk.addTree(commit.getTree)
|
||||
treeWalk.setRecursive(true)
|
||||
if (!path.isEmpty) {
|
||||
@@ -967,13 +998,31 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
val entryPath =
|
||||
if (path.isEmpty) baseName + "/" + treeWalk.getPathString
|
||||
else path.split("/").last + treeWalk.getPathString.substring(path.length)
|
||||
val size = JGitUtil.getFileSize(git, repository, treeWalk)
|
||||
val mode = treeWalk.getFileMode.getBits
|
||||
val entry: ArchiveEntry = entryCreator(entryPath, size, date, mode)
|
||||
JGitUtil.openFile(git, repository, commit.getTree, treeWalk.getPathString) { in =>
|
||||
val tempFile = File.createTempFile("gitbucket", ".archive")
|
||||
val size = Using.resource(new FileOutputStream(tempFile)) { out =>
|
||||
IOUtils.copy(
|
||||
EolStreamTypeUtil.wrapInputStream(
|
||||
in,
|
||||
EolStreamTypeUtil
|
||||
.detectStreamType(
|
||||
OperationType.CHECKOUT_OP,
|
||||
git.getRepository.getConfig.get(WorkingTreeOptions.KEY),
|
||||
treeWalk.getAttributes
|
||||
)
|
||||
),
|
||||
out
|
||||
)
|
||||
}
|
||||
|
||||
val entry: ArchiveEntry = entryCreator(entryPath, size, date, mode)
|
||||
archive.putArchiveEntry(entry)
|
||||
IOUtils.copy(in, archive)
|
||||
Using.resource(new FileInputStream(tempFile)) { in =>
|
||||
IOUtils.copy(in, archive)
|
||||
}
|
||||
archive.closeArchiveEntry()
|
||||
tempFile.delete()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -994,7 +1043,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
)
|
||||
contentType = "application/octet-stream"
|
||||
response.setBufferSize(1024 * 1024)
|
||||
using(new ZipArchiveOutputStream(response.getOutputStream)) { zip =>
|
||||
Using.resource(new ZipArchiveOutputStream(response.getOutputStream)) { zip =>
|
||||
archive(revision, ".zip", zip) { (path, size, date, mode) =>
|
||||
val entry = new ZipArchiveEntry(path)
|
||||
entry.setSize(size)
|
||||
@@ -1011,12 +1060,12 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
)
|
||||
contentType = "application/octet-stream"
|
||||
response.setBufferSize(1024 * 1024)
|
||||
using(compressor match {
|
||||
Using.resource(compressor match {
|
||||
case "gz" => new GzipCompressorOutputStream(response.getOutputStream)
|
||||
case "bz2" => new BZip2CompressorOutputStream(response.getOutputStream)
|
||||
case "xz" => new XZCompressorOutputStream(response.getOutputStream)
|
||||
}) { compressorOutputStream =>
|
||||
using(new TarArchiveOutputStream(compressorOutputStream)) { tar =>
|
||||
Using.resource(new TarArchiveOutputStream(compressorOutputStream)) { tar =>
|
||||
tar.setBigNumberMode(TarArchiveOutputStream.BIGNUMBER_STAR)
|
||||
tar.setLongFileMode(TarArchiveOutputStream.LONGFILE_GNU)
|
||||
tar.setAddPaxHeadersForNonAsciiNames(true)
|
||||
@@ -1045,7 +1094,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
val branch = params("branch")
|
||||
|
||||
LockUtil.lock(s"${owner}/${repository}") {
|
||||
using(Git.open(getRepositoryDir(owner, repository))) { git =>
|
||||
Using.resource(Git.open(getRepositoryDir(owner, repository))) { git =>
|
||||
val headName = s"refs/heads/${branch}"
|
||||
val headTip = git.getRepository.resolve(headName)
|
||||
if (headTip.getName != value) {
|
||||
|
||||
@@ -2,14 +2,11 @@ package gitbucket.core.controller
|
||||
|
||||
import java.io.FileInputStream
|
||||
|
||||
import com.github.zafarkhaja.semver.{Version => Semver}
|
||||
import gitbucket.core.GitBucketCoreModule
|
||||
import gitbucket.core.admin.html
|
||||
import gitbucket.core.plugin.{PluginInfoBase, PluginRegistry, PluginRepository}
|
||||
import gitbucket.core.plugin.PluginRegistry
|
||||
import gitbucket.core.service.SystemSettingsService._
|
||||
import gitbucket.core.service.{AccountService, RepositoryService}
|
||||
import gitbucket.core.ssh.SshServer
|
||||
import gitbucket.core.util.Directory._
|
||||
import gitbucket.core.util.Implicits._
|
||||
import gitbucket.core.util.StringUtil._
|
||||
import gitbucket.core.util.SyntaxSugars._
|
||||
@@ -21,8 +18,8 @@ import org.scalatra._
|
||||
import org.scalatra.forms._
|
||||
import org.scalatra.i18n.Messages
|
||||
|
||||
import scala.collection.JavaConverters._
|
||||
import scala.collection.mutable.ListBuffer
|
||||
import scala.util.Using
|
||||
|
||||
class SystemSettingsController
|
||||
extends SystemSettingsControllerBase
|
||||
@@ -41,14 +38,22 @@ 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(
|
||||
@@ -93,17 +98,18 @@ 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())),
|
||||
"pluginNetworkInstall" -> trim(label("Network plugin installation", boolean())),
|
||||
"proxy" -> optionalIfNotChecked(
|
||||
"useProxy",
|
||||
mapping(
|
||||
"host" -> trim(label("Proxy host", text(required))),
|
||||
"port" -> trim(label("Proxy port", number())),
|
||||
"user" -> trim(label("Keystore", optional(text()))),
|
||||
"password" -> trim(label("Keystore", optional(text())))
|
||||
)(Proxy.apply)
|
||||
)
|
||||
"webhook" -> mapping(
|
||||
"blockPrivateAddress" -> trim(label("Block private address", boolean())),
|
||||
"whitelist" -> trim(label("Whitelist", multiLineText()))
|
||||
)(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)
|
||||
)(SystemSettings.apply).verifying { settings =>
|
||||
Vector(
|
||||
if (settings.ssh.enabled && settings.baseUrl.isEmpty) {
|
||||
@@ -179,7 +185,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(
|
||||
@@ -193,7 +199,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(
|
||||
@@ -229,30 +235,30 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
|
||||
val conn = request2Session(request).conn
|
||||
val meta = conn.getMetaData
|
||||
val tables = ListBuffer[Table]()
|
||||
using(meta.getTables(null, "%", "%", Array("TABLE", "VIEW"))) {
|
||||
Using.resource(meta.getTables(null, "%", "%", Array("TABLE", "VIEW"))) {
|
||||
rs =>
|
||||
while (rs.next()) {
|
||||
val tableName = rs.getString("TABLE_NAME")
|
||||
|
||||
val pkColumns = ListBuffer[String]()
|
||||
using(meta.getPrimaryKeys(null, null, tableName)) { rs =>
|
||||
Using.resource(meta.getPrimaryKeys(null, null, tableName)) { rs =>
|
||||
while (rs.next()) {
|
||||
pkColumns += rs.getString("COLUMN_NAME").toUpperCase
|
||||
}
|
||||
}
|
||||
|
||||
val columns = ListBuffer[Column]()
|
||||
using(meta.getColumns(null, "%", tableName, "%")) { rs =>
|
||||
Using.resource(meta.getColumns(null, "%", tableName, "%")) { rs =>
|
||||
while (rs.next()) {
|
||||
val columnName = rs.getString("COLUMN_NAME").toUpperCase
|
||||
columns += Column(columnName, pkColumns.contains(columnName))
|
||||
}
|
||||
}
|
||||
|
||||
tables += Table(tableName.toUpperCase, columns)
|
||||
tables += Table(tableName.toUpperCase, columns.toSeq)
|
||||
}
|
||||
}
|
||||
html.dbviewer(tables)
|
||||
html.dbviewer(tables.toSeq)
|
||||
})
|
||||
|
||||
post("/admin/dbviewer/_query")(adminOnly {
|
||||
@@ -263,10 +269,10 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
|
||||
if (trimmedQuery.nonEmpty) {
|
||||
try {
|
||||
val conn = request2Session(request).conn
|
||||
using(conn.prepareStatement(query)) {
|
||||
Using.resource(conn.prepareStatement(query)) {
|
||||
stmt =>
|
||||
if (trimmedQuery.toUpperCase.startsWith("SELECT")) {
|
||||
using(stmt.executeQuery()) {
|
||||
Using.resource(stmt.executeQuery()) {
|
||||
rs =>
|
||||
val meta = rs.getMetaData
|
||||
val columns = for (i <- 1 to meta.getColumnCount) yield {
|
||||
@@ -309,7 +315,7 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
|
||||
} SshServer.start(sshAddress, baseUrl)
|
||||
}
|
||||
|
||||
flash += "info" -> "System settings has been updated."
|
||||
flash.update("info", "System settings has been updated.")
|
||||
redirect("/admin/system")
|
||||
})
|
||||
|
||||
@@ -332,63 +338,12 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
|
||||
})
|
||||
|
||||
get("/admin/plugins")(adminOnly {
|
||||
// Installed plugins
|
||||
val enabledPlugins = PluginRegistry().getPlugins()
|
||||
val gitbucketVersion = GitBucketCoreModule.getVersions.asScala.last.getVersion
|
||||
val gitbucketSemver = Semver.valueOf(gitbucketVersion)
|
||||
|
||||
// Plugins in the remote repository
|
||||
val repositoryPlugins = if (context.settings.pluginNetworkInstall) {
|
||||
PluginRepository
|
||||
.getPlugins()
|
||||
.map {
|
||||
meta =>
|
||||
(meta, meta.versions.reverse.find {
|
||||
version =>
|
||||
val semver = Semver.valueOf(version.version)
|
||||
gitbucketVersion == version.gitbucketVersion && !enabledPlugins.exists { plugin =>
|
||||
if (plugin.pluginId == meta.id) {
|
||||
Semver.valueOf(plugin.pluginVersion) match {
|
||||
case x if x.greaterThan(semver) => true
|
||||
case x if x.equals(semver) =>
|
||||
plugin.gitbucketVersion match {
|
||||
case None => true
|
||||
case Some(x) => Semver.valueOf(x).greaterThanOrEqualTo(gitbucketSemver)
|
||||
}
|
||||
case _ => false
|
||||
}
|
||||
} else false
|
||||
}
|
||||
})
|
||||
}
|
||||
.collect {
|
||||
case (meta, Some(version)) =>
|
||||
new PluginInfoBase(
|
||||
pluginId = meta.id,
|
||||
pluginName = meta.name,
|
||||
pluginVersion = version.version,
|
||||
gitbucketVersion = Some(version.gitbucketVersion),
|
||||
description = meta.description
|
||||
)
|
||||
}
|
||||
} else Nil
|
||||
|
||||
// Merge
|
||||
val plugins = (enabledPlugins.map((_, true)) ++ repositoryPlugins.map((_, false)))
|
||||
.groupBy(_._1.pluginId)
|
||||
.map {
|
||||
case (pluginId, plugins) =>
|
||||
val (plugin, enabled) = plugins.head
|
||||
(plugin, enabled, if (plugins.length > 1) plugins.last._1.pluginVersion else "")
|
||||
}
|
||||
.toList
|
||||
|
||||
html.plugins(plugins, flash.get("info"))
|
||||
html.plugins(PluginRegistry().getPlugins(), flash.get("info"))
|
||||
})
|
||||
|
||||
post("/admin/plugins/_reload")(adminOnly {
|
||||
PluginRegistry.reload(request.getServletContext(), loadSystemSettings(), request2Session(request).conn)
|
||||
flash += "info" -> "All plugins were reloaded."
|
||||
flash.update("info", "All plugins were reloaded.")
|
||||
redirect("/admin/plugins")
|
||||
})
|
||||
|
||||
@@ -398,37 +353,7 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
|
||||
if (PluginRegistry().getPlugins().exists(_.pluginId == pluginId)) {
|
||||
PluginRegistry
|
||||
.uninstall(pluginId, request.getServletContext, loadSystemSettings(), request2Session(request).conn)
|
||||
flash += "info" -> s"${pluginId} was uninstalled."
|
||||
}
|
||||
|
||||
redirect("/admin/plugins")
|
||||
})
|
||||
|
||||
post("/admin/plugins/:pluginId/:version/_install")(adminOnly {
|
||||
if (context.settings.pluginNetworkInstall) {
|
||||
val pluginId = params("pluginId")
|
||||
val version = params("version")
|
||||
val gitbucketVersion = GitBucketCoreModule.getVersions.asScala.last.getVersion
|
||||
|
||||
PluginRepository
|
||||
.getPlugins()
|
||||
.collectFirst {
|
||||
case meta if meta.id == pluginId =>
|
||||
(meta, meta.versions.find(x => x.gitbucketVersion == gitbucketVersion && x.version == version))
|
||||
}
|
||||
.foreach {
|
||||
case (meta, version) =>
|
||||
version.foreach { version =>
|
||||
PluginRegistry.install(
|
||||
pluginId,
|
||||
new java.net.URL(version.url),
|
||||
request.getServletContext,
|
||||
loadSystemSettings(),
|
||||
request2Session(request).conn
|
||||
)
|
||||
flash += "info" -> s"${pluginId}:${version.version} was installed."
|
||||
}
|
||||
}
|
||||
flash.update("info", s"${pluginId} was uninstalled.")
|
||||
}
|
||||
|
||||
redirect("/admin/plugins")
|
||||
@@ -476,7 +401,7 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
|
||||
getAccountByUserName(userName, true).map {
|
||||
account =>
|
||||
if (account.isAdmin && (form.isRemoved || !form.isAdmin) && isLastAdministrator(account)) {
|
||||
flash += "error" -> "Account can't be turned off because this is last one administrator."
|
||||
flash.update("error", "Account can't be turned off because this is last one administrator.")
|
||||
redirect(s"/admin/users/${userName}/_edituser")
|
||||
} else {
|
||||
if (form.isRemoved) {
|
||||
@@ -601,31 +526,44 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
|
||||
response.setHeader("Content-Disposition", "attachment; filename=" + file.getName)
|
||||
response.setContentLength(file.length.toInt)
|
||||
|
||||
using(new FileInputStream(file)) { in =>
|
||||
Using.resource(new FileInputStream(file)) { in =>
|
||||
IOUtils.copy(in, response.outputStream)
|
||||
}
|
||||
|
||||
()
|
||||
})
|
||||
|
||||
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 multiLineText(constraints: Constraint*): SingleValueType[Seq[String]] =
|
||||
new SingleValueType[Seq[String]](constraints: _*) {
|
||||
def convert(value: String, messages: Messages): Seq[String] = {
|
||||
if (value == null) {
|
||||
Nil
|
||||
} else {
|
||||
value.split("\n").toIndexedSeq.map(_.trim)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,6 +13,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
|
||||
extends WikiControllerBase
|
||||
@@ -90,7 +91,7 @@ trait WikiControllerBase extends ControllerBase {
|
||||
get("/:owner/:repository/wiki/:page/_history")(referrersOnly { repository =>
|
||||
val pageName = StringUtil.urlDecode(params("page"))
|
||||
|
||||
using(Git.open(getWikiRepositoryDir(repository.owner, repository.name))) { git =>
|
||||
Using.resource(Git.open(getWikiRepositoryDir(repository.owner, repository.name))) { git =>
|
||||
JGitUtil.getCommitLog(git, "master", path = pageName + ".md") match {
|
||||
case Right((logs, hasNext)) => html.history(Some(pageName), logs, repository, isEditable(repository))
|
||||
case Left(_) => NotFound()
|
||||
@@ -102,7 +103,7 @@ trait WikiControllerBase extends ControllerBase {
|
||||
val pageName = StringUtil.urlDecode(params("page"))
|
||||
val Array(from, to) = params("commitId").split("\\.\\.\\.")
|
||||
|
||||
using(Git.open(getWikiRepositoryDir(repository.owner, repository.name))) { git =>
|
||||
Using.resource(Git.open(getWikiRepositoryDir(repository.owner, repository.name))) { git =>
|
||||
html.compare(
|
||||
Some(pageName),
|
||||
from,
|
||||
@@ -118,7 +119,7 @@ trait WikiControllerBase extends ControllerBase {
|
||||
get("/:owner/:repository/wiki/_compare/:commitId")(referrersOnly { repository =>
|
||||
val Array(from, to) = params("commitId").split("\\.\\.\\.")
|
||||
|
||||
using(Git.open(getWikiRepositoryDir(repository.owner, repository.name))) { git =>
|
||||
Using.resource(Git.open(getWikiRepositoryDir(repository.owner, repository.name))) { git =>
|
||||
html.compare(
|
||||
None,
|
||||
from,
|
||||
@@ -139,7 +140,7 @@ trait WikiControllerBase extends ControllerBase {
|
||||
if (revertWikiPage(repository.owner, repository.name, from, to, context.loginAccount.get, Some(pageName))) {
|
||||
redirect(s"/${repository.owner}/${repository.name}/wiki/${StringUtil.urlEncode(pageName)}")
|
||||
} else {
|
||||
flash += "info" -> "This patch was not able to be reversed."
|
||||
flash.update("info", "This patch was not able to be reversed.")
|
||||
redirect(
|
||||
s"/${repository.owner}/${repository.name}/wiki/${StringUtil.urlEncode(pageName)}/_compare/${from}...${to}"
|
||||
)
|
||||
@@ -154,7 +155,7 @@ trait WikiControllerBase extends ControllerBase {
|
||||
if (revertWikiPage(repository.owner, repository.name, from, to, context.loginAccount.get, None)) {
|
||||
redirect(s"/${repository.owner}/${repository.name}/wiki")
|
||||
} else {
|
||||
flash += "info" -> "This patch was not able to be reversed."
|
||||
flash.update("info", "This patch was not able to be reversed.")
|
||||
redirect(s"/${repository.owner}/${repository.name}/wiki/_compare/${from}...${to}")
|
||||
}
|
||||
} else Unauthorized()
|
||||
@@ -190,7 +191,7 @@ trait WikiControllerBase extends ControllerBase {
|
||||
form.pageName,
|
||||
commitId
|
||||
)
|
||||
callWebHookOf(repository.owner, repository.name, WebHook.Gollum) {
|
||||
callWebHookOf(repository.owner, repository.name, WebHook.Gollum, context.settings) {
|
||||
getAccountByUserName(repository.owner).map { repositoryUser =>
|
||||
WebHookGollumPayload("edited", form.pageName, commitId, repository, repositoryUser, loginAccount)
|
||||
}
|
||||
@@ -228,7 +229,7 @@ trait WikiControllerBase extends ControllerBase {
|
||||
commitId =>
|
||||
updateLastActivityDate(repository.owner, repository.name)
|
||||
recordCreateWikiPageActivity(repository.owner, repository.name, loginAccount.userName, form.pageName)
|
||||
callWebHookOf(repository.owner, repository.name, WebHook.Gollum) {
|
||||
callWebHookOf(repository.owner, repository.name, WebHook.Gollum, context.settings) {
|
||||
getAccountByUserName(repository.owner).map { repositoryUser =>
|
||||
WebHookGollumPayload("created", form.pageName, commitId, repository, repositoryUser, loginAccount)
|
||||
}
|
||||
@@ -269,7 +270,7 @@ trait WikiControllerBase extends ControllerBase {
|
||||
})
|
||||
|
||||
get("/:owner/:repository/wiki/_history")(referrersOnly { repository =>
|
||||
using(Git.open(getWikiRepositoryDir(repository.owner, repository.name))) { git =>
|
||||
Using.resource(Git.open(getWikiRepositoryDir(repository.owner, repository.name))) { git =>
|
||||
JGitUtil.getCommitLog(git, "master") match {
|
||||
case Right((logs, hasNext)) => html.history(None, logs, repository, isEditable(repository))
|
||||
case Left(_) => NotFound()
|
||||
@@ -279,7 +280,7 @@ trait WikiControllerBase extends ControllerBase {
|
||||
|
||||
get("/:owner/:repository/wiki/_blob/*")(referrersOnly { repository =>
|
||||
val path = multiParams("splat").head
|
||||
using(Git.open(getWikiRepositoryDir(repository.owner, repository.name))) { git =>
|
||||
Using.resource(Git.open(getWikiRepositoryDir(repository.owner, repository.name))) { git =>
|
||||
val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve("master"))
|
||||
|
||||
getPathObjectId(git, path, revCommit).map { objectId =>
|
||||
|
||||
@@ -3,21 +3,35 @@ import gitbucket.core.api.{ApiObject, ApiRef, JsonFormat}
|
||||
import gitbucket.core.controller.ControllerBase
|
||||
import gitbucket.core.util.Directory.getRepositoryDir
|
||||
import gitbucket.core.util.ReferrerAuthenticator
|
||||
import gitbucket.core.util.SyntaxSugars.using
|
||||
import gitbucket.core.util.Implicits._
|
||||
import org.eclipse.jgit.api.Git
|
||||
import scala.collection.JavaConverters._
|
||||
import org.slf4j.LoggerFactory
|
||||
|
||||
import scala.jdk.CollectionConverters._
|
||||
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
|
||||
*/
|
||||
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(Git.open(getRepositoryDir(params("owner"), params("repository")))) { git =>
|
||||
Using.resource(Git.open(getRepositoryDir(params("owner"), params("repository")))) { git =>
|
||||
val ref = git.getRepository().findRef(revstr)
|
||||
|
||||
if (ref != null) {
|
||||
@@ -37,7 +51,8 @@ trait ApiGitReferenceControllerBase extends ControllerBase {
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/*
|
||||
* ii. Get all references
|
||||
* https://developer.github.com/v3/git/refs/#get-all-references
|
||||
|
||||
@@ -31,7 +31,7 @@ trait ApiIssueControllerBase extends ControllerBase {
|
||||
val condition = IssueSearchCondition(request)
|
||||
val baseOwner = getAccountByUserName(repository.owner).get
|
||||
|
||||
val issues: List[(Issue, Account)] =
|
||||
val issues: List[(Issue, Account, Option[Account])] =
|
||||
searchIssueByApi(
|
||||
condition = condition,
|
||||
offset = (page - 1) * PullRequestLimit,
|
||||
@@ -40,11 +40,12 @@ trait ApiIssueControllerBase extends ControllerBase {
|
||||
)
|
||||
|
||||
JsonFormat(issues.map {
|
||||
case (issue, issueUser) =>
|
||||
case (issue, issueUser, assignedUser) =>
|
||||
ApiIssue(
|
||||
issue = issue,
|
||||
repositoryName = RepositoryName(repository),
|
||||
user = ApiUser(issueUser),
|
||||
assignee = assignedUser.map(ApiUser(_)),
|
||||
labels = getIssueLabels(repository.owner, repository.name, issue.issueId)
|
||||
.map(ApiLabel(_, RepositoryName(repository)))
|
||||
)
|
||||
@@ -59,13 +60,15 @@ trait ApiIssueControllerBase extends ControllerBase {
|
||||
(for {
|
||||
issueId <- params("id").toIntOpt
|
||||
issue <- getIssue(repository.owner, repository.name, issueId.toString)
|
||||
openedUser <- getAccountByUserName(issue.openedUserName)
|
||||
users = getAccountsByUserNames(Set(issue.openedUserName) ++ issue.assignedUserName, Set())
|
||||
openedUser <- users.get(issue.openedUserName)
|
||||
} yield {
|
||||
JsonFormat(
|
||||
ApiIssue(
|
||||
issue,
|
||||
RepositoryName(repository),
|
||||
ApiUser(openedUser),
|
||||
issue.assignedUserName.flatMap(users.get(_)).map(ApiUser(_)),
|
||||
getIssueLabels(repository.owner, repository.name, issue.issueId).map(ApiLabel(_, RepositoryName(repository)))
|
||||
)
|
||||
)
|
||||
@@ -98,6 +101,7 @@ trait ApiIssueControllerBase extends ControllerBase {
|
||||
issue,
|
||||
RepositoryName(repository),
|
||||
ApiUser(loginAccount),
|
||||
issue.assignedUserName.flatMap(getAccountByUserName(_)).map(ApiUser(_)),
|
||||
getIssueLabels(repository.owner, repository.name, issue.issueId)
|
||||
.map(ApiLabel(_, RepositoryName(repository)))
|
||||
)
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
package gitbucket.core.controller.api
|
||||
import gitbucket.core.api.{ApiGroup, ApiRepository, ApiUser, JsonFormat}
|
||||
import gitbucket.core.api.{ApiGroup, CreateAGroup, ApiRepository, ApiUser, JsonFormat}
|
||||
import gitbucket.core.controller.ControllerBase
|
||||
import gitbucket.core.service.{AccountService, RepositoryService}
|
||||
import gitbucket.core.util.Implicits._
|
||||
import gitbucket.core.util.UsersAuthenticator
|
||||
import gitbucket.core.util.{AdminAuthenticator, UsersAuthenticator}
|
||||
|
||||
trait ApiOrganizationControllerBase extends ControllerBase {
|
||||
self: RepositoryService with AccountService with UsersAuthenticator =>
|
||||
self: RepositoryService with AccountService with AdminAuthenticator with UsersAuthenticator =>
|
||||
|
||||
/*
|
||||
* i. List your organizations
|
||||
@@ -51,6 +51,19 @@ trait ApiOrganizationControllerBase extends ControllerBase {
|
||||
* ghe: i. Create an organization
|
||||
* https://developer.github.com/enterprise/2.14/v3/enterprise-admin/orgs/#create-an-organization
|
||||
*/
|
||||
post("/api/v3/admin/organizations")(adminOnly {
|
||||
for {
|
||||
data <- extractFromJsonBody[CreateAGroup]
|
||||
} yield {
|
||||
val group = createGroup(
|
||||
data.login,
|
||||
data.profile_name,
|
||||
data.url
|
||||
)
|
||||
updateGroupMembers(data.login, List(data.admin -> true))
|
||||
JsonFormat(ApiGroup(group))
|
||||
}
|
||||
})
|
||||
|
||||
/*
|
||||
* ghe: ii. Rename an organization
|
||||
|
||||
@@ -8,12 +8,12 @@ import gitbucket.core.service.PullRequestService.PullRequestLimit
|
||||
import gitbucket.core.util.Directory.getRepositoryDir
|
||||
import gitbucket.core.util.Implicits._
|
||||
import gitbucket.core.util.JGitUtil.CommitInfo
|
||||
import gitbucket.core.util.SyntaxSugars.using
|
||||
import gitbucket.core.util._
|
||||
import org.eclipse.jgit.api.Git
|
||||
import org.scalatra.NoContent
|
||||
import scala.util.Using
|
||||
|
||||
import scala.collection.JavaConverters._
|
||||
import scala.jdk.CollectionConverters._
|
||||
|
||||
trait ApiPullRequestControllerBase extends ControllerBase {
|
||||
self: AccountService
|
||||
@@ -106,15 +106,17 @@ trait ApiPullRequestControllerBase extends ControllerBase {
|
||||
)
|
||||
|
||||
createPullRequest(
|
||||
originUserName = repository.owner,
|
||||
originRepositoryName = repository.name,
|
||||
originRepository = repository,
|
||||
issueId = issueId,
|
||||
originBranch = createPullReq.base,
|
||||
requestUserName = reqOwner,
|
||||
requestRepositoryName = repository.name,
|
||||
requestBranch = reqBranch,
|
||||
commitIdFrom = commitIdFrom.getName,
|
||||
commitIdTo = commitIdTo.getName
|
||||
commitIdTo = commitIdTo.getName,
|
||||
isDraft = createPullReq.draft.getOrElse(false),
|
||||
loginAccount = context.loginAccount.get,
|
||||
settings = context.settings
|
||||
)
|
||||
getApiPullRequest(repository, issueId).map(JsonFormat(_))
|
||||
case _ =>
|
||||
@@ -133,15 +135,17 @@ trait ApiPullRequestControllerBase extends ControllerBase {
|
||||
case (Some(commitIdFrom), Some(commitIdTo)) =>
|
||||
changeIssueToPullRequest(repository.owner, repository.name, createPullReqAlt.issue)
|
||||
createPullRequest(
|
||||
originUserName = repository.owner,
|
||||
originRepositoryName = repository.name,
|
||||
originRepository = repository,
|
||||
issueId = createPullReqAlt.issue,
|
||||
originBranch = createPullReqAlt.base,
|
||||
requestUserName = reqOwner,
|
||||
requestRepositoryName = repository.name,
|
||||
requestBranch = reqBranch,
|
||||
commitIdFrom = commitIdFrom.getName,
|
||||
commitIdTo = commitIdTo.getName
|
||||
commitIdTo = commitIdTo.getName,
|
||||
isDraft = false,
|
||||
loginAccount = context.loginAccount.get,
|
||||
settings = context.settings
|
||||
)
|
||||
getApiPullRequest(repository, createPullReqAlt.issue).map(JsonFormat(_))
|
||||
case _ =>
|
||||
@@ -171,7 +175,7 @@ trait ApiPullRequestControllerBase extends ControllerBase {
|
||||
issueId =>
|
||||
getPullRequest(owner, name, issueId) map {
|
||||
case (issue, pullreq) =>
|
||||
using(Git.open(getRepositoryDir(owner, name))) { git =>
|
||||
Using.resource(Git.open(getRepositoryDir(owner, name))) { git =>
|
||||
val oldId = git.getRepository.resolve(pullreq.commitIdFrom)
|
||||
val newId = git.getRepository.resolve(pullreq.commitIdTo)
|
||||
val repoFullName = RepositoryName(repository)
|
||||
|
||||
@@ -0,0 +1,184 @@
|
||||
package gitbucket.core.controller.api
|
||||
import java.io.{ByteArrayInputStream, File}
|
||||
|
||||
import gitbucket.core.api._
|
||||
import gitbucket.core.controller.ControllerBase
|
||||
import gitbucket.core.service.{AccountService, ReleaseService}
|
||||
import gitbucket.core.util.Directory.getReleaseFilesDir
|
||||
import gitbucket.core.util.{FileUtil, ReferrerAuthenticator, RepositoryName, WritableUsersAuthenticator}
|
||||
import gitbucket.core.util.Implicits._
|
||||
import gitbucket.core.util.SyntaxSugars.defining
|
||||
import org.apache.commons.io.FileUtils
|
||||
import org.scalatra.{Created, NoContent}
|
||||
|
||||
trait ApiReleaseControllerBase extends ControllerBase {
|
||||
self: AccountService with ReleaseService with ReferrerAuthenticator with WritableUsersAuthenticator =>
|
||||
|
||||
/**
|
||||
* i. List releases for a repository
|
||||
* https://developer.github.com/v3/repos/releases/#list-releases-for-a-repository
|
||||
*/
|
||||
get("/api/v3/repos/:owner/:repository/releases")(referrersOnly { repository =>
|
||||
val releases = getReleases(repository.owner, repository.name)
|
||||
JsonFormat(releases.map { rel =>
|
||||
val assets = getReleaseAssets(repository.owner, repository.name, rel.tag)
|
||||
ApiRelease(rel, assets, getAccountByUserName(rel.author).get, RepositoryName(repository))
|
||||
})
|
||||
})
|
||||
|
||||
/**
|
||||
* ii. Get a single release
|
||||
* https://developer.github.com/v3/repos/releases/#get-a-single-release
|
||||
* GitBucket doesn't have release id
|
||||
*/
|
||||
/**
|
||||
* iii. Get the latest release
|
||||
* https://developer.github.com/v3/repos/releases/#get-the-latest-release
|
||||
*/
|
||||
get("/api/v3/repos/:owner/:repository/releases/latest")(referrersOnly { repository =>
|
||||
getReleases(repository.owner, repository.name).lastOption
|
||||
.map { release =>
|
||||
val assets = getReleaseAssets(repository.owner, repository.name, release.tag)
|
||||
JsonFormat(ApiRelease(release, assets, getAccountByUserName(release.author).get, RepositoryName(repository)))
|
||||
}
|
||||
.getOrElse {
|
||||
NotFound()
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* iv. Get a release by tag name
|
||||
* https://developer.github.com/v3/repos/releases/#get-a-release-by-tag-name
|
||||
*/
|
||||
get("/api/v3/repos/:owner/:repository/releases/tags/:tag")(referrersOnly { repository =>
|
||||
val tag = params("tag")
|
||||
getRelease(repository.owner, repository.name, tag)
|
||||
.map { release =>
|
||||
val assets = getReleaseAssets(repository.owner, repository.name, tag)
|
||||
JsonFormat(ApiRelease(release, assets, getAccountByUserName(release.author).get, RepositoryName(repository)))
|
||||
}
|
||||
.getOrElse {
|
||||
NotFound()
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* v. Create a release
|
||||
* https://developer.github.com/v3/repos/releases/#create-a-release
|
||||
*/
|
||||
post("/api/v3/repos/:owner/:repository/releases")(writableUsersOnly { repository =>
|
||||
(for {
|
||||
data <- extractFromJsonBody[CreateARelease]
|
||||
} yield {
|
||||
createRelease(
|
||||
repository.owner,
|
||||
repository.name,
|
||||
data.name.getOrElse(data.tag_name),
|
||||
data.body,
|
||||
data.tag_name,
|
||||
context.loginAccount.get
|
||||
)
|
||||
val release = getRelease(repository.owner, repository.name, data.tag_name).get
|
||||
val assets = getReleaseAssets(repository.owner, repository.name, data.tag_name)
|
||||
JsonFormat(ApiRelease(release, assets, context.loginAccount.get, RepositoryName(repository)))
|
||||
})
|
||||
})
|
||||
|
||||
/**
|
||||
* vi. Edit a release
|
||||
* https://developer.github.com/v3/repos/releases/#edit-a-release
|
||||
* Incompatiblity info: GitHub API requires :release_id, but GitBucket API requires :tag_name
|
||||
*/
|
||||
patch("/api/v3/repos/:owner/:repository/releases/:tag")(writableUsersOnly { repository =>
|
||||
(for {
|
||||
data <- extractFromJsonBody[CreateARelease]
|
||||
} yield {
|
||||
val tag = params("tag")
|
||||
updateRelease(repository.owner, repository.name, tag, data.name.getOrElse(data.tag_name), data.body)
|
||||
val release = getRelease(repository.owner, repository.name, data.tag_name).get
|
||||
val assets = getReleaseAssets(repository.owner, repository.name, data.tag_name)
|
||||
JsonFormat(ApiRelease(release, assets, context.loginAccount.get, RepositoryName(repository)))
|
||||
})
|
||||
})
|
||||
|
||||
/**
|
||||
* vii. Delete a release
|
||||
* https://developer.github.com/v3/repos/releases/#delete-a-release
|
||||
* Incompatiblity info: GitHub API requires :release_id, but GitBucket API requires :tag_name
|
||||
*/
|
||||
delete("/api/v3/repos/:owner/:repository/releases/:tag")(writableUsersOnly { repository =>
|
||||
val tag = params("tag")
|
||||
deleteRelease(repository.owner, repository.name, tag)
|
||||
NoContent()
|
||||
})
|
||||
|
||||
/**
|
||||
* viii. List assets for a release
|
||||
* https://developer.github.com/v3/repos/releases/#list-assets-for-a-release
|
||||
*/
|
||||
/**
|
||||
* ix. Upload a release asset
|
||||
* https://developer.github.com/v3/repos/releases/#upload-a-release-asset
|
||||
*/
|
||||
post("/api/v3/repos/:owner/:repository/releases/:tag/assets")(writableUsersOnly { repository =>
|
||||
val name = params("name")
|
||||
val tag = params("tag")
|
||||
getRelease(repository.owner, repository.name, tag)
|
||||
.map {
|
||||
release =>
|
||||
defining(FileUtil.generateFileId) { fileId =>
|
||||
val buf = new Array[Byte](request.inputStream.available())
|
||||
request.inputStream.read(buf)
|
||||
FileUtils.writeByteArrayToFile(
|
||||
new File(
|
||||
getReleaseFilesDir(repository.owner, repository.name),
|
||||
FileUtil.checkFilename(tag + "/" + fileId)
|
||||
),
|
||||
buf
|
||||
)
|
||||
createReleaseAsset(
|
||||
repository.owner,
|
||||
repository.name,
|
||||
tag,
|
||||
fileId,
|
||||
name,
|
||||
request.contentLength.getOrElse(0),
|
||||
context.loginAccount.get
|
||||
)
|
||||
getReleaseAsset(repository.owner, repository.name, tag, fileId)
|
||||
.map { asset =>
|
||||
JsonFormat(ApiReleaseAsset(asset, RepositoryName(repository)))
|
||||
}
|
||||
.getOrElse {
|
||||
ApiError("Unknown error")
|
||||
}
|
||||
}
|
||||
}
|
||||
.getOrElse(NotFound())
|
||||
})
|
||||
|
||||
/**
|
||||
* x. Get a single release asset
|
||||
* https://developer.github.com/v3/repos/releases/#get-a-single-release-asset
|
||||
* Incompatibility info: GitHub requires only asset_id, but GitBucket requires tag and fileId(file_id).
|
||||
*/
|
||||
get("/api/v3/repos/:owner/:repository/releases/:tag/assets/:fileId")(referrersOnly { repository =>
|
||||
val tag = params("tag")
|
||||
val fileId = params("fileId")
|
||||
getReleaseAsset(repository.owner, repository.name, tag, fileId)
|
||||
.map { asset =>
|
||||
JsonFormat(ApiReleaseAsset(asset, RepositoryName(repository)))
|
||||
}
|
||||
.getOrElse(NotFound())
|
||||
})
|
||||
|
||||
/*
|
||||
* xi. Edit a release asset
|
||||
* https://developer.github.com/v3/repos/releases/#edit-a-release-asset
|
||||
*/
|
||||
|
||||
/*
|
||||
* xii. Delete a release asset
|
||||
* https://developer.github.com/v3/repos/releases/#edit-a-release-asset
|
||||
*/
|
||||
}
|
||||
@@ -3,8 +3,11 @@ import gitbucket.core.api._
|
||||
import gitbucket.core.controller.ControllerBase
|
||||
import gitbucket.core.service.{AccountService, ProtectedBranchService, RepositoryService}
|
||||
import gitbucket.core.util._
|
||||
import gitbucket.core.util.Directory._
|
||||
import gitbucket.core.util.Implicits._
|
||||
import gitbucket.core.util.JGitUtil.getBranches
|
||||
import org.eclipse.jgit.api.Git
|
||||
import scala.util.Using
|
||||
|
||||
trait ApiRepositoryBranchControllerBase extends ControllerBase {
|
||||
self: RepositoryService
|
||||
@@ -22,18 +25,19 @@ trait ApiRepositoryBranchControllerBase extends ControllerBase {
|
||||
* https://developer.github.com/v3/repos/branches/#list-branches
|
||||
*/
|
||||
get("/api/v3/repos/:owner/:repository/branches")(referrersOnly { repository =>
|
||||
JsonFormat(
|
||||
JGitUtil
|
||||
.getBranches(
|
||||
owner = repository.owner,
|
||||
name = repository.name,
|
||||
defaultBranch = repository.repository.defaultBranch,
|
||||
origin = repository.repository.originUserName.isEmpty
|
||||
)
|
||||
.map { br =>
|
||||
ApiBranchForList(br.name, ApiBranchCommit(br.commitId))
|
||||
}
|
||||
)
|
||||
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
|
||||
JsonFormat(
|
||||
JGitUtil
|
||||
.getBranches(
|
||||
git = git,
|
||||
defaultBranch = repository.repository.defaultBranch,
|
||||
origin = repository.repository.originUserName.isEmpty
|
||||
)
|
||||
.map { br =>
|
||||
ApiBranchForList(br.name, ApiBranchCommit(br.commitId))
|
||||
}
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
@@ -41,21 +45,22 @@ trait ApiRepositoryBranchControllerBase extends ControllerBase {
|
||||
* https://developer.github.com/v3/repos/branches/#get-branch
|
||||
*/
|
||||
get("/api/v3/repos/:owner/:repository/branches/*")(referrersOnly { repository =>
|
||||
//import gitbucket.core.api._
|
||||
(for {
|
||||
branch <- params.get("splat") if repository.branchList.contains(branch)
|
||||
br <- getBranches(
|
||||
repository.owner,
|
||||
repository.name,
|
||||
repository.repository.defaultBranch,
|
||||
repository.repository.originUserName.isEmpty
|
||||
).find(_.name == branch)
|
||||
} yield {
|
||||
val protection = getProtectedBranchInfo(repository.owner, repository.name, branch)
|
||||
JsonFormat(
|
||||
ApiBranch(branch, ApiBranchCommit(br.commitId), ApiBranchProtection(protection))(RepositoryName(repository))
|
||||
)
|
||||
}) getOrElse NotFound()
|
||||
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) {
|
||||
git =>
|
||||
(for {
|
||||
branch <- params.get("splat") if repository.branchList.contains(branch)
|
||||
br <- getBranches(
|
||||
git,
|
||||
repository.repository.defaultBranch,
|
||||
repository.repository.originUserName.isEmpty
|
||||
).find(_.name == branch)
|
||||
} yield {
|
||||
val protection = getProtectedBranchInfo(repository.owner, repository.name, branch)
|
||||
JsonFormat(
|
||||
ApiBranch(branch, ApiBranchCommit(br.commitId), ApiBranchProtection(protection))(RepositoryName(repository))
|
||||
)
|
||||
}) getOrElse NotFound()
|
||||
}
|
||||
})
|
||||
|
||||
/*
|
||||
@@ -209,28 +214,30 @@ trait ApiRepositoryBranchControllerBase extends ControllerBase {
|
||||
*/
|
||||
patch("/api/v3/repos/:owner/:repository/branches/*")(ownerOnly { repository =>
|
||||
import gitbucket.core.api._
|
||||
(for {
|
||||
branch <- params.get("splat") if repository.branchList.contains(branch)
|
||||
protection <- extractFromJsonBody[ApiBranchProtection.EnablingAndDisabling].map(_.protection)
|
||||
br <- getBranches(
|
||||
repository.owner,
|
||||
repository.name,
|
||||
repository.repository.defaultBranch,
|
||||
repository.repository.originUserName.isEmpty
|
||||
).find(_.name == branch)
|
||||
} yield {
|
||||
if (protection.enabled) {
|
||||
enableBranchProtection(
|
||||
repository.owner,
|
||||
repository.name,
|
||||
branch,
|
||||
protection.status.enforcement_level == ApiBranchProtection.Everyone,
|
||||
protection.status.contexts
|
||||
)
|
||||
} else {
|
||||
disableBranchProtection(repository.owner, repository.name, branch)
|
||||
}
|
||||
JsonFormat(ApiBranch(branch, ApiBranchCommit(br.commitId), protection)(RepositoryName(repository)))
|
||||
}) getOrElse NotFound()
|
||||
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) {
|
||||
git =>
|
||||
(for {
|
||||
branch <- params.get("splat") if repository.branchList.contains(branch)
|
||||
protection <- extractFromJsonBody[ApiBranchProtection.EnablingAndDisabling].map(_.protection)
|
||||
br <- getBranches(
|
||||
git,
|
||||
repository.repository.defaultBranch,
|
||||
repository.repository.originUserName.isEmpty
|
||||
).find(_.name == branch)
|
||||
} yield {
|
||||
if (protection.enabled) {
|
||||
enableBranchProtection(
|
||||
repository.owner,
|
||||
repository.name,
|
||||
branch,
|
||||
protection.status.enforcement_level == ApiBranchProtection.Everyone,
|
||||
protection.status.contexts
|
||||
)
|
||||
} else {
|
||||
disableBranchProtection(repository.owner, repository.name, branch)
|
||||
}
|
||||
JsonFormat(ApiBranch(branch, ApiBranchCommit(br.commitId), protection)(RepositoryName(repository)))
|
||||
}) getOrElse NotFound()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -7,9 +7,10 @@ import gitbucket.core.util.Directory.getRepositoryDir
|
||||
import gitbucket.core.util.Implicits._
|
||||
import gitbucket.core.util.JGitUtil.CommitInfo
|
||||
import gitbucket.core.util.{JGitUtil, ReferrerAuthenticator, RepositoryName}
|
||||
import gitbucket.core.util.SyntaxSugars.using
|
||||
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 =>
|
||||
@@ -17,6 +18,33 @@ trait ApiRepositoryCommitControllerBase extends ControllerBase {
|
||||
* 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 = if (request.body.nonEmpty) (parse(request.body) \ "sha").extract[String] else "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
|
||||
@@ -27,11 +55,11 @@ trait ApiRepositoryCommitControllerBase extends ControllerBase {
|
||||
val name = repository.name
|
||||
val sha = params("sha")
|
||||
|
||||
using(Git.open(getRepositoryDir(owner, name))) {
|
||||
Using.resource(Git.open(getRepositoryDir(owner, name))) {
|
||||
git =>
|
||||
val repo = git.getRepository
|
||||
val objectId = repo.resolve(sha)
|
||||
val commitInfo = using(new RevWalk(repo)) { revWalk =>
|
||||
val commitInfo = Using.resource(new RevWalk(repo)) { revWalk =>
|
||||
new CommitInfo(revWalk.parseCommit(objectId))
|
||||
}
|
||||
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
package gitbucket.core.controller.api
|
||||
import gitbucket.core.api.{ApiContents, JsonFormat}
|
||||
import gitbucket.core.api.{ApiContents, ApiError, CreateAFile, JsonFormat}
|
||||
import gitbucket.core.controller.ControllerBase
|
||||
import gitbucket.core.service.{AccountService, ProtectedBranchService, RepositoryService}
|
||||
import gitbucket.core.service.{RepositoryCommitFileService, RepositoryService}
|
||||
import gitbucket.core.util.Directory.getRepositoryDir
|
||||
import gitbucket.core.util.JGitUtil.{FileInfo, getContentFromId, getFileList}
|
||||
import gitbucket.core.util._
|
||||
import gitbucket.core.util.SyntaxSugars.using
|
||||
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 =>
|
||||
self: ReferrerAuthenticator with WritableUsersAuthenticator with RepositoryCommitFileService =>
|
||||
|
||||
/*
|
||||
* i. Get the README
|
||||
@@ -45,7 +45,7 @@ trait ApiRepositoryContentsControllerBase extends ControllerBase {
|
||||
getFileList(git, revision, dirName).find(f => f.name.equals(fileName))
|
||||
}
|
||||
|
||||
using(Git.open(getRepositoryDir(params("owner"), params("repository")))) { git =>
|
||||
Using.resource(Git.open(getRepositoryDir(params("owner"), params("repository")))) { git =>
|
||||
val fileList = getFileList(git, refStr, path)
|
||||
if (fileList.isEmpty) { // file or NotFound
|
||||
getFileInfo(git, refStr, path)
|
||||
@@ -101,16 +101,45 @@ trait ApiRepositoryContentsControllerBase extends ControllerBase {
|
||||
}
|
||||
}
|
||||
/*
|
||||
* iii. Create a file
|
||||
* iii. Create a file or iv. Update a file
|
||||
* https://developer.github.com/v3/repos/contents/#create-a-file
|
||||
* https://developer.github.com/v3/repos/contents/#update-a-file
|
||||
* if sha is presented, update a file else create a file.
|
||||
* requested #2112
|
||||
*/
|
||||
|
||||
/*
|
||||
* iv. Update a file
|
||||
* https://developer.github.com/v3/repos/contents/#update-a-file
|
||||
* requested #2112
|
||||
*/
|
||||
put("/api/v3/repos/:owner/:repository/contents/*")(writableUsersOnly { repository =>
|
||||
JsonFormat(for {
|
||||
data <- extractFromJsonBody[CreateAFile]
|
||||
} yield {
|
||||
val branch = data.branch.getOrElse(repository.repository.defaultBranch)
|
||||
val commit = Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
|
||||
val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(branch))
|
||||
revCommit.name
|
||||
}
|
||||
val paths = multiParams("splat").head.split("/")
|
||||
val path = paths.take(paths.size - 1).toList.mkString("/")
|
||||
if (data.sha.isDefined && data.sha.get != commit) {
|
||||
ApiError("The blob SHA is not matched.", Some("https://developer.github.com/v3/repos/contents/#update-a-file"))
|
||||
} else {
|
||||
val objectId = commitFile(
|
||||
repository,
|
||||
branch,
|
||||
path,
|
||||
Some(paths.last),
|
||||
data.sha.map(_ => paths.last),
|
||||
StringUtil.base64Decode(data.content),
|
||||
data.message,
|
||||
commit,
|
||||
context.loginAccount.get,
|
||||
data.committer.map(_.name).getOrElse(context.loginAccount.get.fullName),
|
||||
data.committer.map(_.email).getOrElse(context.loginAccount.get.mailAddress),
|
||||
context.settings
|
||||
)
|
||||
ApiContents("file", paths.last, path, objectId.name, None, None)(RepositoryName(repository))
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
/*
|
||||
* v. Delete a file
|
||||
|
||||
@@ -6,12 +6,12 @@ import gitbucket.core.servlet.Database
|
||||
import gitbucket.core.util.Directory.getRepositoryDir
|
||||
import gitbucket.core.util._
|
||||
import gitbucket.core.util.Implicits._
|
||||
import gitbucket.core.util.SyntaxSugars.using
|
||||
import gitbucket.core.model.Profile.profile.blockingApi._
|
||||
import org.eclipse.jgit.api.Git
|
||||
|
||||
import scala.concurrent.Await
|
||||
import scala.concurrent.duration.Duration
|
||||
import scala.util.Using
|
||||
|
||||
trait ApiRepositoryControllerBase extends ControllerBase {
|
||||
self: RepositoryService
|
||||
@@ -193,7 +193,7 @@ trait ApiRepositoryControllerBase extends ControllerBase {
|
||||
*/
|
||||
get("/api/v3/repos/:owner/:repository/raw/*")(referrersOnly { repository =>
|
||||
val (id, path) = repository.splitPath(multiParams("splat").head)
|
||||
using(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
|
||||
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
|
||||
val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(id))
|
||||
|
||||
getPathObjectId(git, path, revCommit).map { objectId =>
|
||||
|
||||
@@ -4,6 +4,7 @@ import gitbucket.core.controller.ControllerBase
|
||||
import gitbucket.core.service.{AccountService, RepositoryService}
|
||||
import gitbucket.core.util.{AdminAuthenticator, UsersAuthenticator}
|
||||
import gitbucket.core.util.Implicits._
|
||||
import gitbucket.core.util.StringUtil._
|
||||
import org.scalatra.NoContent
|
||||
|
||||
trait ApiUserControllerBase extends ControllerBase {
|
||||
@@ -70,7 +71,7 @@ trait ApiUserControllerBase extends ControllerBase {
|
||||
} yield {
|
||||
val user = createAccount(
|
||||
data.login,
|
||||
data.password,
|
||||
pbkdf2_sha256(data.password),
|
||||
data.fullName.getOrElse(data.login),
|
||||
data.email,
|
||||
data.isAdmin.getOrElse(false),
|
||||
|
||||
29
src/main/scala/gitbucket/core/model/GpgKey.scala
Normal file
29
src/main/scala/gitbucket/core/model/GpgKey.scala
Normal file
@@ -0,0 +1,29 @@
|
||||
package gitbucket.core.model
|
||||
|
||||
trait GpgKeyComponent { self: Profile =>
|
||||
import profile.api._
|
||||
|
||||
lazy val GpgKeys = TableQuery[GpgKeys]
|
||||
|
||||
class GpgKeys(tag: Tag) extends Table[GpgKey](tag, "GPG_KEY") {
|
||||
val userName = column[String]("USER_NAME")
|
||||
val keyId = column[Int]("KEY_ID", O AutoInc)
|
||||
val gpgKeyId = column[Long]("GPG_KEY_ID")
|
||||
val title = column[String]("TITLE")
|
||||
val publicKey = column[String]("PUBLIC_KEY")
|
||||
def * = (userName, keyId, gpgKeyId, title, publicKey) <> (GpgKey.tupled, GpgKey.unapply)
|
||||
|
||||
def byPrimaryKey(userName: String, keyId: Int) =
|
||||
(this.userName === userName.bind) && (this.keyId === keyId.bind)
|
||||
def byGpgKeyId(gpgKeyId: Long) =
|
||||
this.gpgKeyId === gpgKeyId.bind
|
||||
}
|
||||
}
|
||||
|
||||
case class GpgKey(
|
||||
userName: String,
|
||||
keyId: Int = 0,
|
||||
gpgKeyId: Long,
|
||||
title: String,
|
||||
publicKey: String
|
||||
)
|
||||
@@ -59,6 +59,7 @@ trait CoreProfile
|
||||
with PullRequestComponent
|
||||
with RepositoryComponent
|
||||
with SshKeyComponent
|
||||
with GpgKeyComponent
|
||||
with RepositoryWebHookComponent
|
||||
with RepositoryWebHookEventComponent
|
||||
with AccountWebHookComponent
|
||||
|
||||
@@ -12,6 +12,7 @@ trait PullRequestComponent extends TemplateComponent { self: Profile =>
|
||||
val requestBranch = column[String]("REQUEST_BRANCH")
|
||||
val commitIdFrom = column[String]("COMMIT_ID_FROM")
|
||||
val commitIdTo = column[String]("COMMIT_ID_TO")
|
||||
val isDraft = column[Boolean]("IS_DRAFT")
|
||||
def * =
|
||||
(
|
||||
userName,
|
||||
@@ -22,7 +23,8 @@ trait PullRequestComponent extends TemplateComponent { self: Profile =>
|
||||
requestRepositoryName,
|
||||
requestBranch,
|
||||
commitIdFrom,
|
||||
commitIdTo
|
||||
commitIdTo,
|
||||
isDraft
|
||||
) <> (PullRequest.tupled, PullRequest.unapply)
|
||||
|
||||
def byPrimaryKey(userName: String, repositoryName: String, issueId: Int) =
|
||||
@@ -41,5 +43,6 @@ case class PullRequest(
|
||||
requestRepositoryName: String,
|
||||
requestBranch: String,
|
||||
commitIdFrom: String,
|
||||
commitIdTo: String
|
||||
commitIdTo: String,
|
||||
isDraft: Boolean
|
||||
)
|
||||
|
||||
@@ -6,10 +6,10 @@ import gitbucket.core.controller.{Context, ControllerBase}
|
||||
import gitbucket.core.model.{Account, Issue}
|
||||
import gitbucket.core.service.RepositoryService.RepositoryInfo
|
||||
import gitbucket.core.service.SystemSettingsService.SystemSettings
|
||||
import gitbucket.core.util.SyntaxSugars._
|
||||
import io.github.gitbucket.solidbase.model.Version
|
||||
import org.apache.sshd.server.command.Command
|
||||
import play.twirl.api.Html
|
||||
import scala.util.Using
|
||||
|
||||
/**
|
||||
* Trait for define plugin interface.
|
||||
@@ -434,7 +434,7 @@ abstract class Plugin {
|
||||
* Helper method to get a resource from classpath.
|
||||
*/
|
||||
protected def fromClassPath(path: String): Array[Byte] =
|
||||
using(getClass.getClassLoader.getResourceAsStream(path)) { in =>
|
||||
Using.resource(getClass.getClassLoader.getResourceAsStream(path)) { in =>
|
||||
val bytes = new Array[Byte](in.available)
|
||||
in.read(bytes)
|
||||
bytes
|
||||
|
||||
@@ -15,19 +15,17 @@ import gitbucket.core.service.ProtectedBranchService.ProtectedBranchReceiveHook
|
||||
import gitbucket.core.service.RepositoryService.RepositoryInfo
|
||||
import gitbucket.core.service.SystemSettingsService
|
||||
import gitbucket.core.service.SystemSettingsService.SystemSettings
|
||||
import gitbucket.core.util.DatabaseConfig
|
||||
import gitbucket.core.util.{ConfigUtil, DatabaseConfig}
|
||||
import gitbucket.core.util.Directory._
|
||||
import gitbucket.core.util.HttpClientUtil._
|
||||
import io.github.gitbucket.solidbase.Solidbase
|
||||
import io.github.gitbucket.solidbase.manager.JDBCVersionManager
|
||||
import io.github.gitbucket.solidbase.model.Module
|
||||
import org.apache.commons.io.FileUtils
|
||||
import org.apache.http.client.methods.HttpGet
|
||||
import org.apache.sshd.server.command.Command
|
||||
import org.slf4j.LoggerFactory
|
||||
import play.twirl.api.Html
|
||||
|
||||
import scala.collection.JavaConverters._
|
||||
import scala.jdk.CollectionConverters._
|
||||
|
||||
class PluginRegistry {
|
||||
|
||||
@@ -227,40 +225,6 @@ object PluginRegistry {
|
||||
initialize(context, settings, conn)
|
||||
}
|
||||
|
||||
/**
|
||||
* Install a plugin from a specified jar file.
|
||||
*/
|
||||
def install(
|
||||
pluginId: String,
|
||||
url: java.net.URL,
|
||||
context: ServletContext,
|
||||
settings: SystemSettings,
|
||||
conn: java.sql.Connection
|
||||
): Unit =
|
||||
synchronized {
|
||||
shutdown(context, settings)
|
||||
|
||||
new File(PluginHome)
|
||||
.listFiles((_: File, name: String) => {
|
||||
name.startsWith(s"gitbucket-${pluginId}-plugin") && name.endsWith(".jar")
|
||||
})
|
||||
.foreach(_.delete())
|
||||
|
||||
withHttpClient(settings.pluginProxy) { httpClient =>
|
||||
val httpGet = new HttpGet(url.toString)
|
||||
try {
|
||||
val response = httpClient.execute(httpGet)
|
||||
val in = response.getEntity.getContent
|
||||
FileUtils.copyToFile(in, new File(PluginHome, new File(url.getFile).getName))
|
||||
} finally {
|
||||
httpGet.releaseConnection()
|
||||
}
|
||||
}
|
||||
|
||||
instance = new PluginRegistry()
|
||||
initialize(context, settings, conn)
|
||||
}
|
||||
|
||||
private def listPluginJars(dir: File): Seq[File] = {
|
||||
dir
|
||||
.listFiles(new FilenameFilter {
|
||||
@@ -271,7 +235,7 @@ object PluginRegistry {
|
||||
.reverse
|
||||
}
|
||||
|
||||
lazy val extraPluginDir: Option[String] = Option(System.getProperty("gitbucket.pluginDir"))
|
||||
lazy val extraPluginDir: Option[String] = ConfigUtil.getConfigValue[String]("gitbucket.pluginDir")
|
||||
|
||||
def getGitBucketVersion(pluginJarFileName: String): Option[String] = {
|
||||
val regex = ".+-gitbucket\\_(\\d+\\.\\d+\\.\\d+(-SNAPSHOT)?)-.+".r
|
||||
@@ -331,6 +295,7 @@ object PluginRegistry {
|
||||
instance.getPlugins().find(_.pluginId == pluginId) match {
|
||||
case Some(x) => {
|
||||
logger.warn(s"Plugin ${pluginId} is duplicated. ${x.pluginJar.getName} is available.")
|
||||
classLoader.close()
|
||||
}
|
||||
case None => {
|
||||
// Migration
|
||||
@@ -370,7 +335,9 @@ object PluginRegistry {
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
case e: Throwable => logger.error(s"Error during plugin initialization: ${pluginJar.getName}", e)
|
||||
case e: Throwable =>
|
||||
logger.error(s"Error during plugin initialization: ${pluginJar.getName}", e)
|
||||
classLoader.close()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -409,6 +376,13 @@ object PluginRegistry {
|
||||
}
|
||||
}
|
||||
|
||||
def getPluginInfoFromClassLoader(classLoader: ClassLoader): Option[PluginInfo] = {
|
||||
instance
|
||||
.getPlugins()
|
||||
.find { info =>
|
||||
info.classLoader.equals(classLoader)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case class Link(
|
||||
@@ -439,7 +413,6 @@ case class PluginInfo(
|
||||
|
||||
class PluginWatchThread(context: ServletContext, dir: String) extends Thread with SystemSettingsService {
|
||||
import gitbucket.core.model.Profile.profile.blockingApi._
|
||||
import scala.collection.JavaConverters._
|
||||
|
||||
private val logger = LoggerFactory.getLogger(classOf[PluginWatchThread])
|
||||
|
||||
@@ -469,7 +442,7 @@ class PluginWatchThread(context: ServletContext, dir: String) extends Thread wit
|
||||
}
|
||||
if (events.nonEmpty) {
|
||||
events.foreach { event =>
|
||||
logger.info(event.kind + ": " + event.context)
|
||||
logger.info(s"${event.kind}: ${event.context}")
|
||||
}
|
||||
new Thread {
|
||||
override def run(): Unit = {
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
package gitbucket.core.plugin
|
||||
|
||||
import gitbucket.core.controller.Context
|
||||
import gitbucket.core.util.SyntaxSugars.using
|
||||
import gitbucket.core.util.HttpClientUtil._
|
||||
import org.json4s._
|
||||
import org.apache.commons.io.IOUtils
|
||||
|
||||
import org.apache.http.client.methods.HttpGet
|
||||
import org.slf4j.LoggerFactory
|
||||
|
||||
object PluginRepository {
|
||||
private val logger = LoggerFactory.getLogger(getClass)
|
||||
implicit val formats = DefaultFormats
|
||||
|
||||
def parsePluginJson(json: String): Seq[PluginMetadata] = {
|
||||
org.json4s.jackson.JsonMethods.parse(json).extract[Seq[PluginMetadata]]
|
||||
}
|
||||
|
||||
def getPlugins()(implicit context: Context): Seq[PluginMetadata] = {
|
||||
try {
|
||||
val url = new java.net.URL("https://plugins.gitbucket-community.org/releases/plugins.json")
|
||||
|
||||
withHttpClient(context.settings.pluginProxy) { httpClient =>
|
||||
val httpGet = new HttpGet(url.toString)
|
||||
try {
|
||||
val response = httpClient.execute(httpGet)
|
||||
using(response.getEntity.getContent) { in =>
|
||||
val str = IOUtils.toString(in, "UTF-8")
|
||||
parsePluginJson(str)
|
||||
}
|
||||
} finally {
|
||||
httpGet.releaseConnection()
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
case t: Throwable =>
|
||||
logger.warn("Failed to access to the plugin repository: " + t.toString)
|
||||
Nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Mapped from plugins.json
|
||||
case class PluginMetadata(
|
||||
id: String,
|
||||
name: String,
|
||||
description: String,
|
||||
versions: Seq[VersionDef],
|
||||
default: Boolean = false
|
||||
) {
|
||||
lazy val latestVersion: VersionDef = versions.last
|
||||
}
|
||||
|
||||
case class VersionDef(
|
||||
version: String,
|
||||
url: String,
|
||||
gitbucketVersion: String
|
||||
)
|
||||
@@ -240,8 +240,8 @@ trait AccountService {
|
||||
def updateLastLoginDate(userName: String)(implicit s: Session): Unit =
|
||||
Accounts.filter(_.userName === userName.bind).map(_.lastLoginDate).update(currentDate)
|
||||
|
||||
def createGroup(groupName: String, description: Option[String], url: Option[String])(implicit s: Session): Unit =
|
||||
Accounts insert Account(
|
||||
def createGroup(groupName: String, description: Option[String], url: Option[String])(implicit s: Session): Account = {
|
||||
val group = Account(
|
||||
userName = groupName,
|
||||
password = "",
|
||||
fullName = groupName,
|
||||
@@ -256,6 +256,9 @@ trait AccountService {
|
||||
isRemoved = false,
|
||||
description = description
|
||||
)
|
||||
Accounts insert group
|
||||
group
|
||||
}
|
||||
|
||||
def updateGroup(groupName: String, description: Option[String], url: Option[String], removed: Boolean)(
|
||||
implicit s: Session
|
||||
|
||||
@@ -131,6 +131,23 @@ trait ActivityService {
|
||||
currentDate
|
||||
)
|
||||
|
||||
def recordReopenPullRequestActivity(
|
||||
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 pull request [issue:${userName}/${repositoryName}#${issueId}]",
|
||||
Some(title),
|
||||
currentDate
|
||||
)
|
||||
|
||||
def recordCommentIssueActivity(
|
||||
userName: String,
|
||||
repositoryName: String,
|
||||
@@ -352,15 +369,19 @@ trait ActivityService {
|
||||
currentDate
|
||||
)
|
||||
|
||||
def recordReleaseActivity(userName: String, repositoryName: String, activityUserName: String, name: String)(
|
||||
implicit s: Session
|
||||
): Unit =
|
||||
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}/${name}] at [repo:${userName}/${repositoryName}]",
|
||||
s"[user:${activityUserName}] released [release:${userName}/${repositoryName}/${tagName}:${releaseName}] at [repo:${userName}/${repositoryName}]",
|
||||
None,
|
||||
currentDate
|
||||
)
|
||||
|
||||
@@ -94,7 +94,8 @@ trait CommitsService {
|
||||
repository,
|
||||
issue,
|
||||
pullRequest,
|
||||
loginAccount
|
||||
loginAccount,
|
||||
context.settings
|
||||
)
|
||||
}
|
||||
case None =>
|
||||
|
||||
29
src/main/scala/gitbucket/core/service/GpgKeyService.scala
Normal file
29
src/main/scala/gitbucket/core/service/GpgKeyService.scala
Normal file
@@ -0,0 +1,29 @@
|
||||
package gitbucket.core.service
|
||||
|
||||
import java.io.ByteArrayInputStream
|
||||
|
||||
import gitbucket.core.model.GpgKey
|
||||
import gitbucket.core.model.Profile._
|
||||
import gitbucket.core.model.Profile.profile.blockingApi._
|
||||
import org.bouncycastle.bcpg.ArmoredInputStream
|
||||
import org.bouncycastle.openpgp.PGPPublicKeyRing
|
||||
import org.bouncycastle.openpgp.bc.BcPGPObjectFactory
|
||||
|
||||
import scala.jdk.CollectionConverters._
|
||||
|
||||
trait GpgKeyService {
|
||||
def getGpgPublicKeys(userName: String)(implicit s: Session): List[GpgKey] =
|
||||
GpgKeys.filter(_.userName === userName.bind).sortBy(_.gpgKeyId).list
|
||||
|
||||
def addGpgPublicKey(userName: String, title: String, publicKey: String)(implicit s: Session): Unit = {
|
||||
val pubKeyOf = new BcPGPObjectFactory(new ArmoredInputStream(new ByteArrayInputStream(publicKey.getBytes)))
|
||||
pubKeyOf.iterator().asScala.foreach {
|
||||
case keyRing: PGPPublicKeyRing =>
|
||||
val key = keyRing.getPublicKey()
|
||||
GpgKeys.insert(GpgKey(userName = userName, gpgKeyId = key.getKeyID, title = title, publicKey = publicKey))
|
||||
}
|
||||
}
|
||||
|
||||
def deleteGpgPublicKey(userName: String, keyId: Int)(implicit s: Session): Unit =
|
||||
GpgKeys.filter(_.byPrimaryKey(userName, keyId)).delete
|
||||
}
|
||||
@@ -39,7 +39,10 @@ trait HandleCommentService {
|
||||
))
|
||||
case "reopen" if (issue.closed) =>
|
||||
false ->
|
||||
(Some("reopen") -> Some(recordReopenIssueActivity _))
|
||||
(Some("reopen") -> Some(
|
||||
if (issue.isPullRequest) recordReopenPullRequestActivity _
|
||||
else recordReopenIssueActivity _
|
||||
))
|
||||
}
|
||||
.map {
|
||||
case (closed, t) =>
|
||||
@@ -80,16 +83,17 @@ trait HandleCommentService {
|
||||
|
||||
// call web hooks
|
||||
action match {
|
||||
case None => commentId foreach (callIssueCommentWebHook(repository, issue, _, loginAccount))
|
||||
case None =>
|
||||
commentId foreach (callIssueCommentWebHook(repository, issue, _, loginAccount, context.settings))
|
||||
case Some(act) =>
|
||||
val webHookAction = act match {
|
||||
case "close" => "closed"
|
||||
case "reopen" => "reopened"
|
||||
}
|
||||
if (issue.isPullRequest)
|
||||
callPullRequestWebHook(webHookAction, repository, issue.issueId, loginAccount)
|
||||
callPullRequestWebHook(webHookAction, repository, issue.issueId, loginAccount, context.settings)
|
||||
else
|
||||
callIssuesWebHook(webHookAction, repository, issue, loginAccount)
|
||||
callIssuesWebHook(webHookAction, repository, issue, loginAccount, context.settings)
|
||||
}
|
||||
|
||||
// call hooks
|
||||
|
||||
@@ -57,7 +57,7 @@ trait IssueCreationService {
|
||||
createReferComment(owner, name, issue, title + " " + body.getOrElse(""), loginAccount)
|
||||
|
||||
// call web hooks
|
||||
callIssuesWebHook("opened", repository, issue, loginAccount)
|
||||
callIssuesWebHook("opened", repository, issue, loginAccount, context.settings)
|
||||
|
||||
// call hooks
|
||||
PluginRegistry().getIssueHooks.foreach(_.created(issue, repository))
|
||||
|
||||
@@ -203,7 +203,7 @@ trait IssuesService {
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
@@ -265,17 +265,19 @@ trait IssuesService {
|
||||
}
|
||||
|
||||
/** for api
|
||||
* @return (issue, issueUser, commentCount)
|
||||
* @return (issue, issueUser, assignedUser)
|
||||
*/
|
||||
def searchIssueByApi(condition: IssueSearchCondition, offset: Int, limit: Int, repos: (String, String)*)(
|
||||
implicit s: Session
|
||||
): List[(Issue, Account)] = {
|
||||
): List[(Issue, Account, Option[Account])] = {
|
||||
// get issues and comment count and labels
|
||||
searchIssueQueryBase(condition, false, offset, limit, repos)
|
||||
.join(Accounts)
|
||||
.on { case t1 ~ t2 ~ i ~ t3 => t3.userName === t1.openedUserName }
|
||||
.sortBy { case t1 ~ t2 ~ i ~ t3 => i asc }
|
||||
.map { case t1 ~ t2 ~ i ~ t3 => (t1, t3) }
|
||||
.joinLeft(Accounts)
|
||||
.on { case t1 ~ t2 ~ i ~ t3 ~ t4 => t4.userName === t1.assignedUserName }
|
||||
.sortBy { case t1 ~ t2 ~ i ~ t3 ~ t4 => i asc }
|
||||
.map { case t1 ~ t2 ~ i ~ t3 ~ t4 => (t1, t3, t4) }
|
||||
.list
|
||||
}
|
||||
|
||||
@@ -752,7 +754,7 @@ trait IssuesService {
|
||||
implicit s: Session
|
||||
): Unit = {
|
||||
extractIssueId(message).foreach { issueId =>
|
||||
val content = fromIssue.issueId + ":" + fromIssue.title
|
||||
val content = s"${fromIssue.issueId}:${fromIssue.title}"
|
||||
if (getIssue(owner, repository, issueId).isDefined) {
|
||||
// Not add if refer comment already exist.
|
||||
if (!getComments(owner, repository, issueId.toInt).exists { x =>
|
||||
@@ -908,13 +910,9 @@ 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 page(request: HttpServletRequest) = {
|
||||
PaginationHelper.page(param(request, "page"))
|
||||
}
|
||||
}
|
||||
|
||||
case class CommitStatusInfo(
|
||||
|
||||
@@ -7,10 +7,8 @@ import gitbucket.core.plugin.PluginRegistry
|
||||
import gitbucket.core.service.RepositoryService.RepositoryInfo
|
||||
import gitbucket.core.util.Directory._
|
||||
import gitbucket.core.util.{JGitUtil, LockUtil}
|
||||
import gitbucket.core.util.SyntaxSugars._
|
||||
import gitbucket.core.model.Profile._
|
||||
import gitbucket.core.model.Profile.profile._
|
||||
import gitbucket.core.model.Profile.profile.blockingApi._
|
||||
import gitbucket.core.service.SystemSettingsService.SystemSettings
|
||||
import org.eclipse.jgit.merge.{MergeStrategy, Merger, RecursiveMerger}
|
||||
import org.eclipse.jgit.api.Git
|
||||
import org.eclipse.jgit.transport.RefSpec
|
||||
@@ -18,7 +16,8 @@ import org.eclipse.jgit.errors.NoMergeBaseException
|
||||
import org.eclipse.jgit.lib.{CommitBuilder, ObjectId, PersonIdent, Repository}
|
||||
import org.eclipse.jgit.revwalk.{RevCommit, RevWalk}
|
||||
|
||||
import scala.collection.JavaConverters._
|
||||
import scala.jdk.CollectionConverters._
|
||||
import scala.util.Using
|
||||
|
||||
trait MergeService {
|
||||
self: AccountService
|
||||
@@ -35,7 +34,7 @@ trait MergeService {
|
||||
* Returns true if conflict will be caused.
|
||||
*/
|
||||
def checkConflict(userName: String, repositoryName: String, branch: String, issueId: Int): Option[String] = {
|
||||
using(Git.open(getRepositoryDir(userName, repositoryName))) { git =>
|
||||
Using.resource(Git.open(getRepositoryDir(userName, repositoryName))) { git =>
|
||||
new MergeCacheInfo(git, branch, issueId).checkConflict()
|
||||
}
|
||||
}
|
||||
@@ -52,7 +51,7 @@ trait MergeService {
|
||||
branch: String,
|
||||
issueId: Int
|
||||
): Option[Option[String]] = {
|
||||
using(Git.open(getRepositoryDir(userName, repositoryName))) { git =>
|
||||
Using.resource(Git.open(getRepositoryDir(userName, repositoryName))) { git =>
|
||||
new MergeCacheInfo(git, branch, issueId).checkConflictCache()
|
||||
}
|
||||
}
|
||||
@@ -99,7 +98,7 @@ trait MergeService {
|
||||
requestBranch: String,
|
||||
issueId: Int
|
||||
): Unit = {
|
||||
using(Git.open(getRepositoryDir(userName, repositoryName))) { git =>
|
||||
Using.resource(Git.open(getRepositoryDir(userName, repositoryName))) { git =>
|
||||
git.fetch
|
||||
.setRemote(getRepositoryDir(requestUserName, requestRepositoryName).toURI.toString)
|
||||
.setRefSpecs(new RefSpec(s"refs/heads/${requestBranch}:refs/pull/${issueId}/head"))
|
||||
@@ -118,7 +117,7 @@ trait MergeService {
|
||||
remoteRepositoryName: String,
|
||||
remoteBranch: String
|
||||
): Either[String, (ObjectId, ObjectId, ObjectId)] = {
|
||||
using(Git.open(getRepositoryDir(localUserName, localRepositoryName))) { git =>
|
||||
Using.resource(Git.open(getRepositoryDir(localUserName, localRepositoryName))) { git =>
|
||||
val remoteRefName = s"refs/heads/${remoteBranch}"
|
||||
val tmpRefName = s"refs/remote-temp/${remoteUserName}/${remoteRepositoryName}/${remoteBranch}"
|
||||
val refSpec = new RefSpec(s"${remoteRefName}:${tmpRefName}").setForceUpdate(true)
|
||||
@@ -169,7 +168,8 @@ trait MergeService {
|
||||
remoteBranch: String,
|
||||
loginAccount: Account,
|
||||
message: String,
|
||||
pullreq: Option[PullRequest]
|
||||
pullreq: Option[PullRequest],
|
||||
settings: SystemSettings
|
||||
)(implicit s: Session, c: JsonFormat.Context): Option[ObjectId] = {
|
||||
val localUserName = localRepository.owner
|
||||
val localRepositoryName = localRepository.name
|
||||
@@ -177,7 +177,7 @@ trait MergeService {
|
||||
val remoteRepositoryName = remoteRepository.name
|
||||
tryMergeRemote(localUserName, localRepositoryName, localBranch, remoteUserName, remoteRepositoryName, remoteBranch).map {
|
||||
case (newTreeId, oldBaseId, oldHeadId) =>
|
||||
using(Git.open(getRepositoryDir(localUserName, localRepositoryName))) { git =>
|
||||
Using.resource(Git.open(getRepositoryDir(localUserName, localRepositoryName))) { git =>
|
||||
val existIds = JGitUtil.getAllCommitIds(git).toSet
|
||||
|
||||
val committer = new PersonIdent(loginAccount.fullName, loginAccount.mailAddress)
|
||||
@@ -214,7 +214,7 @@ trait MergeService {
|
||||
closeIssuesFromMessage(commit.fullMessage, loginAccount.userName, localUserName, localRepositoryName)
|
||||
.foreach { issueId =>
|
||||
getIssue(localRepository.owner, localRepository.name, issueId.toString).foreach { issue =>
|
||||
callIssuesWebHook("closed", localRepository, issue, loginAccount)
|
||||
callIssuesWebHook("closed", localRepository, issue, loginAccount, settings)
|
||||
PluginRegistry().getIssueHooks
|
||||
.foreach(
|
||||
_.closedByCommitComment(issue, localRepository, commit.fullMessage, loginAccount)
|
||||
@@ -225,7 +225,7 @@ trait MergeService {
|
||||
}
|
||||
|
||||
pullreq.foreach { pullreq =>
|
||||
callWebHookOf(localRepository.owner, localRepository.name, WebHook.Push) {
|
||||
callWebHookOf(localRepository.owner, localRepository.name, WebHook.Push, settings) {
|
||||
for {
|
||||
ownerAccount <- getAccountByUserName(localRepository.owner)
|
||||
} yield {
|
||||
@@ -252,133 +252,147 @@ trait MergeService {
|
||||
issueId: Int,
|
||||
loginAccount: Account,
|
||||
message: String,
|
||||
strategy: String
|
||||
strategy: String,
|
||||
isDraft: Boolean,
|
||||
settings: SystemSettings
|
||||
)(implicit s: Session, c: JsonFormat.Context, context: Context): Either[String, ObjectId] = {
|
||||
if (repository.repository.options.mergeOptions.split(",").contains(strategy)) {
|
||||
LockUtil.lock(s"${repository.owner}/${repository.name}") {
|
||||
getPullRequest(repository.owner, repository.name, issueId)
|
||||
.map {
|
||||
case (issue, pullreq) =>
|
||||
using(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)
|
||||
if (!isDraft) {
|
||||
if (repository.repository.options.mergeOptions.split(",").contains(strategy)) {
|
||||
LockUtil.lock(s"${repository.owner}/${repository.name}") {
|
||||
getPullRequest(repository.owner, repository.name, issueId)
|
||||
.map {
|
||||
case (issue, pullreq) =>
|
||||
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)
|
||||
// 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
|
||||
)
|
||||
val (commits, _) = getRequestCompareInfo(
|
||||
repository.owner,
|
||||
repository.name,
|
||||
pullreq.commitIdFrom,
|
||||
pullreq.requestUserName,
|
||||
pullreq.requestRepositoryName,
|
||||
pullreq.commitIdTo
|
||||
)
|
||||
|
||||
val revCommits = using(new RevWalk(git.getRepository)) { revWalk =>
|
||||
commits.flatten.map { commit =>
|
||||
revWalk.parseCommit(git.getRepository.resolve(commit.id))
|
||||
}
|
||||
}.reverse
|
||||
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)
|
||||
// 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 "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 "squash" =>
|
||||
Some(
|
||||
squashPullRequest(
|
||||
git,
|
||||
pullreq.branch,
|
||||
issueId,
|
||||
s"${issue.title} (#${issueId})\n\n" + message,
|
||||
new PersonIdent(loginAccount.fullName, loginAccount.mailAddress)
|
||||
)
|
||||
)
|
||||
)
|
||||
case _ =>
|
||||
None
|
||||
}) match {
|
||||
case Some(newCommitId) =>
|
||||
// close issue by content of pull request
|
||||
val defaultBranch = getRepository(repository.owner, repository.name).get.repository.defaultBranch
|
||||
if (pullreq.branch == defaultBranch) {
|
||||
commits.flatten.foreach { commit =>
|
||||
case _ =>
|
||||
None
|
||||
}) match {
|
||||
case Some(newCommitId) =>
|
||||
// close issue by content of pull request
|
||||
val defaultBranch = getRepository(repository.owner, repository.name).get.repository.defaultBranch
|
||||
if (pullreq.branch == defaultBranch) {
|
||||
commits.flatten.foreach { commit =>
|
||||
closeIssuesFromMessage(
|
||||
commit.fullMessage,
|
||||
loginAccount.userName,
|
||||
repository.owner,
|
||||
repository.name
|
||||
).foreach { issueId =>
|
||||
getIssue(repository.owner, repository.name, issueId.toString).foreach { issue =>
|
||||
callIssuesWebHook("closed", repository, issue, loginAccount, context.settings)
|
||||
PluginRegistry().getIssueHooks
|
||||
.foreach(_.closedByCommitComment(issue, repository, commit.fullMessage, loginAccount))
|
||||
}
|
||||
}
|
||||
}
|
||||
val issueContent = issue.title + " " + issue.content.getOrElse("")
|
||||
closeIssuesFromMessage(
|
||||
commit.fullMessage,
|
||||
issueContent,
|
||||
loginAccount.userName,
|
||||
repository.owner,
|
||||
repository.name
|
||||
).foreach { issueId =>
|
||||
getIssue(repository.owner, repository.name, issueId.toString).foreach { issue =>
|
||||
callIssuesWebHook("closed", repository, issue, loginAccount)
|
||||
PluginRegistry().getIssueHooks
|
||||
.foreach(_.closedByCommitComment(issue, repository, commit.fullMessage, loginAccount))
|
||||
}
|
||||
}
|
||||
}
|
||||
val issueContent = issue.title + " " + issue.content.getOrElse("")
|
||||
closeIssuesFromMessage(
|
||||
issueContent,
|
||||
loginAccount.userName,
|
||||
repository.owner,
|
||||
repository.name
|
||||
).foreach { issueId =>
|
||||
getIssue(repository.owner, repository.name, issueId.toString).foreach { issue =>
|
||||
callIssuesWebHook("closed", repository, issue, loginAccount)
|
||||
PluginRegistry().getIssueHooks
|
||||
.foreach(_.closedByCommitComment(issue, repository, issueContent, loginAccount))
|
||||
}
|
||||
}
|
||||
closeIssuesFromMessage(message, loginAccount.userName, repository.owner, repository.name)
|
||||
.foreach { issueId =>
|
||||
getIssue(repository.owner, repository.name, issueId.toString).foreach { issue =>
|
||||
callIssuesWebHook("closed", repository, issue, loginAccount)
|
||||
callIssuesWebHook("closed", repository, issue, loginAccount, context.settings)
|
||||
PluginRegistry().getIssueHooks
|
||||
.foreach(_.closedByCommitComment(issue, repository, issueContent, loginAccount))
|
||||
}
|
||||
}
|
||||
}
|
||||
closeIssuesFromMessage(message, loginAccount.userName, repository.owner, repository.name)
|
||||
.foreach { issueId =>
|
||||
getIssue(repository.owner, repository.name, issueId.toString).foreach { issue =>
|
||||
callIssuesWebHook("closed", repository, issue, loginAccount, context.settings)
|
||||
PluginRegistry().getIssueHooks
|
||||
.foreach(_.closedByCommitComment(issue, repository, issueContent, loginAccount))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
updatePullRequests(repository.owner, repository.name, pullreq.branch, loginAccount, "closed")
|
||||
callPullRequestWebHook("closed", repository, issueId, context.loginAccount.get, context.settings)
|
||||
|
||||
// call hooks
|
||||
PluginRegistry().getPullRequestHooks.foreach { h =>
|
||||
h.addedComment(commentId, message, issue, repository)
|
||||
h.merged(issue, repository)
|
||||
}
|
||||
updatePullRequests(
|
||||
repository.owner,
|
||||
repository.name,
|
||||
pullreq.branch,
|
||||
loginAccount,
|
||||
"closed",
|
||||
settings
|
||||
)
|
||||
|
||||
Right(newCommitId)
|
||||
case None =>
|
||||
Left("Unknown strategy")
|
||||
// call hooks
|
||||
PluginRegistry().getPullRequestHooks.foreach { h =>
|
||||
h.addedComment(commentId, message, issue, repository)
|
||||
h.merged(issue, repository)
|
||||
}
|
||||
|
||||
Right(newCommitId)
|
||||
case None =>
|
||||
Left("Unknown strategy")
|
||||
}
|
||||
}
|
||||
}
|
||||
case _ => Left("Unknown error")
|
||||
}
|
||||
.getOrElse(Left("Pull request not found"))
|
||||
}
|
||||
} else Left("Strategy not allowed")
|
||||
|
||||
case _ => Left("Unknown error")
|
||||
}
|
||||
.getOrElse(Left("Pull request not found"))
|
||||
}
|
||||
} else Left("Strategy not allowed")
|
||||
} else Left("Draft pull requests cannot be merged")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -400,7 +414,7 @@ object MergeService {
|
||||
mergeCommit.setCommitter(committer)
|
||||
mergeCommit.setMessage(message)
|
||||
// insertObject and got mergeCommit Object Id
|
||||
using(repository.newObjectInserter) { inserter =>
|
||||
Using.resource(repository.newObjectInserter) { inserter =>
|
||||
val mergeCommitId = inserter.insert(mergeCommit)
|
||||
inserter.flush()
|
||||
mergeCommitId
|
||||
@@ -468,7 +482,7 @@ object MergeService {
|
||||
} catch {
|
||||
case e: NoMergeBaseException => true
|
||||
}
|
||||
val mergeTipCommit = using(new RevWalk(repository))(_.parseCommit(mergeTip))
|
||||
val mergeTipCommit = Using.resource(new RevWalk(repository))(_.parseCommit(mergeTip))
|
||||
val committer = mergeTipCommit.getCommitterIdent
|
||||
|
||||
def _updateBranch(treeId: ObjectId, message: String, branchName: String): Unit = {
|
||||
@@ -521,10 +535,10 @@ object MergeService {
|
||||
newCommit
|
||||
}
|
||||
|
||||
val mergeBaseTipCommit = using(new RevWalk(repository))(_.parseCommit(mergeBaseTip))
|
||||
val mergeBaseTipCommit = Using.resource(new RevWalk(repository))(_.parseCommit(mergeBaseTip))
|
||||
var previousId = mergeBaseTipCommit.getId
|
||||
|
||||
using(repository.newObjectInserter) { inserter =>
|
||||
Using.resource(repository.newObjectInserter) { inserter =>
|
||||
commits.foreach { commit =>
|
||||
val nextCommit = _cloneCommit(commit, previousId, mergeBaseTipCommit.getId)
|
||||
previousId = inserter.insert(nextCommit)
|
||||
@@ -540,8 +554,9 @@ object MergeService {
|
||||
throw new RuntimeException("This pull request can't merge automatically.")
|
||||
}
|
||||
|
||||
val mergeBaseTipCommit = using(new RevWalk(repository))(_.parseCommit(mergeBaseTip))
|
||||
val mergeBranchHeadCommit = using(new RevWalk(repository))(_.parseCommit(repository.resolve(mergedBranchName)))
|
||||
val mergeBaseTipCommit = Using.resource(new RevWalk(repository))(_.parseCommit(mergeBaseTip))
|
||||
val mergeBranchHeadCommit =
|
||||
Using.resource(new RevWalk(repository))(_.parseCommit(repository.resolve(mergedBranchName)))
|
||||
|
||||
// Create squash commit
|
||||
val mergeCommit = new CommitBuilder()
|
||||
@@ -552,7 +567,7 @@ object MergeService {
|
||||
mergeCommit.setMessage(message)
|
||||
|
||||
// insertObject and got squash commit Object Id
|
||||
val newCommitId = using(repository.newObjectInserter) { inserter =>
|
||||
val newCommitId = Using.resource(repository.newObjectInserter) { inserter =>
|
||||
val newCommitId = inserter.insert(mergeCommit)
|
||||
inserter.flush()
|
||||
newCommitId
|
||||
@@ -575,7 +590,7 @@ object MergeService {
|
||||
private def createMergeCommit(treeId: ObjectId, committer: PersonIdent, message: String) =
|
||||
Util.createMergeCommit(repository, treeId, committer, message, Seq[ObjectId](mergeBaseTip, mergeTip))
|
||||
|
||||
private def parseCommit(id: ObjectId) = using(new RevWalk(repository))(_.parseCommit(id))
|
||||
private def parseCommit(id: ObjectId) = Using.resource(new RevWalk(repository))(_.parseCommit(id))
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ import gitbucket.core.model.Account
|
||||
import gitbucket.core.model.Profile.profile.blockingApi._
|
||||
import org.slf4j.LoggerFactory
|
||||
|
||||
import scala.collection.JavaConverters.{asScalaSet, mapAsJavaMap}
|
||||
import scala.jdk.CollectionConverters._
|
||||
|
||||
/**
|
||||
* Service class for the OpenID Connect authentication.
|
||||
@@ -101,7 +101,7 @@ trait OpenIDConnectService {
|
||||
redirectURI: URI
|
||||
): Option[AuthenticationSuccessResponse] =
|
||||
try {
|
||||
AuthenticationResponseParser.parse(redirectURI, mapAsJavaMap(params)) match {
|
||||
AuthenticationResponseParser.parse(redirectURI, params.asJava) match {
|
||||
case response: AuthenticationSuccessResponse =>
|
||||
if (response.getState == state) {
|
||||
Some(response)
|
||||
@@ -207,5 +207,5 @@ object OpenIDConnectService {
|
||||
"RSA" -> Family.RSA,
|
||||
"ECDSA" -> Family.EC,
|
||||
"EdDSA" -> Family.ED
|
||||
).toMap.map { case (name, family) => (name, asScalaSet(family).toSet) }
|
||||
).toMap.map { case (name, family) => (name, family.asScala.toSet) }
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -6,7 +6,9 @@ import gitbucket.core.model.Profile.profile.blockingApi._
|
||||
import difflib.{Delta, DiffUtils}
|
||||
import gitbucket.core.service.RepositoryService.RepositoryInfo
|
||||
import gitbucket.core.api.JsonFormat
|
||||
import gitbucket.core.util.SyntaxSugars._
|
||||
import gitbucket.core.controller.Context
|
||||
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
|
||||
@@ -17,10 +19,17 @@ import gitbucket.core.view.helpers
|
||||
import org.eclipse.jgit.api.Git
|
||||
import org.eclipse.jgit.lib.ObjectId
|
||||
|
||||
import scala.collection.JavaConverters._
|
||||
import scala.jdk.CollectionConverters._
|
||||
import scala.util.Using
|
||||
|
||||
trait PullRequestService {
|
||||
self: IssuesService with CommitsService with WebHookService with WebHookPullRequestService with RepositoryService =>
|
||||
self: IssuesService
|
||||
with CommitsService
|
||||
with WebHookService
|
||||
with WebHookPullRequestService
|
||||
with RepositoryService
|
||||
with MergeService
|
||||
with ActivityService =>
|
||||
import PullRequestService._
|
||||
|
||||
def getPullRequest(owner: String, repository: String, issueId: Int)(
|
||||
@@ -40,6 +49,14 @@ trait PullRequestService {
|
||||
.map(pr => pr.commitIdTo -> pr.commitIdFrom)
|
||||
.update((commitIdTo, commitIdFrom))
|
||||
|
||||
def updateDraftToPullRequest(owner: String, repository: String, issueId: Int)(
|
||||
implicit s: Session
|
||||
): Unit =
|
||||
PullRequests
|
||||
.filter(_.byPrimaryKey(owner, repository, issueId))
|
||||
.map(pr => pr.isDraft)
|
||||
.update(false)
|
||||
|
||||
def getPullRequestCountGroupByUser(closed: Boolean, owner: Option[String], repository: Option[String])(
|
||||
implicit s: Session
|
||||
): List[PullRequestCount] =
|
||||
@@ -81,27 +98,69 @@ trait PullRequestService {
|
||||
// .map { x => PullRequestCount(x._1, x._2) }
|
||||
|
||||
def createPullRequest(
|
||||
originUserName: String,
|
||||
originRepositoryName: String,
|
||||
originRepository: RepositoryInfo,
|
||||
issueId: Int,
|
||||
originBranch: String,
|
||||
requestUserName: String,
|
||||
requestRepositoryName: String,
|
||||
requestBranch: String,
|
||||
commitIdFrom: String,
|
||||
commitIdTo: String
|
||||
)(implicit s: Session): Unit =
|
||||
PullRequests insert PullRequest(
|
||||
originUserName,
|
||||
originRepositoryName,
|
||||
issueId,
|
||||
originBranch,
|
||||
requestUserName,
|
||||
requestRepositoryName,
|
||||
requestBranch,
|
||||
commitIdFrom,
|
||||
commitIdTo
|
||||
)
|
||||
commitIdTo: String,
|
||||
isDraft: Boolean,
|
||||
loginAccount: Account,
|
||||
settings: SystemSettings
|
||||
)(implicit s: Session, context: Context): Unit = {
|
||||
getIssue(originRepository.owner, originRepository.name, issueId.toString).foreach { baseIssue =>
|
||||
PullRequests insert PullRequest(
|
||||
originRepository.owner,
|
||||
originRepository.name,
|
||||
issueId,
|
||||
originBranch,
|
||||
requestUserName,
|
||||
requestRepositoryName,
|
||||
requestBranch,
|
||||
commitIdFrom,
|
||||
commitIdTo,
|
||||
isDraft
|
||||
)
|
||||
|
||||
// fetch requested branch
|
||||
fetchAsPullRequest(
|
||||
originRepository.owner,
|
||||
originRepository.name,
|
||||
requestUserName,
|
||||
requestRepositoryName,
|
||||
requestBranch,
|
||||
issueId
|
||||
)
|
||||
|
||||
// record activity
|
||||
recordPullRequestActivity(
|
||||
originRepository.owner,
|
||||
originRepository.name,
|
||||
loginAccount.userName,
|
||||
issueId,
|
||||
baseIssue.title
|
||||
)
|
||||
|
||||
// call web hook
|
||||
callPullRequestWebHook("opened", originRepository, issueId, loginAccount, settings)
|
||||
|
||||
getIssue(originRepository.owner, originRepository.name, issueId.toString) foreach { issue =>
|
||||
// extract references and create refer comment
|
||||
createReferComment(
|
||||
originRepository.owner,
|
||||
originRepository.name,
|
||||
issue,
|
||||
baseIssue.title + " " + baseIssue.content,
|
||||
loginAccount
|
||||
)
|
||||
|
||||
// call hooks
|
||||
PluginRegistry().getPullRequestHooks.foreach(_.created(issue, originRepository))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def getPullRequestsByRequest(userName: String, repositoryName: String, branch: String, closed: Option[Boolean])(
|
||||
implicit s: Session
|
||||
@@ -169,7 +228,14 @@ trait PullRequestService {
|
||||
/**
|
||||
* Fetch pull request contents into refs/pull/${issueId}/head and update pull request table.
|
||||
*/
|
||||
def updatePullRequests(owner: String, repository: String, branch: String, loginAccount: Account, action: String)(
|
||||
def updatePullRequests(
|
||||
owner: String,
|
||||
repository: String,
|
||||
branch: String,
|
||||
loginAccount: Account,
|
||||
action: String,
|
||||
settings: SystemSettings
|
||||
)(
|
||||
implicit s: Session,
|
||||
c: JsonFormat.Context
|
||||
): Unit = {
|
||||
@@ -218,7 +284,8 @@ trait PullRequestService {
|
||||
action,
|
||||
getRepository(owner, repository).get,
|
||||
pullreq.requestBranch,
|
||||
loginAccount
|
||||
loginAccount,
|
||||
settings
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -330,7 +397,7 @@ trait PullRequestService {
|
||||
requestRepositoryName: String,
|
||||
requestCommitId: String
|
||||
): (Seq[Seq[CommitInfo]], Seq[DiffInfo]) =
|
||||
using(
|
||||
Using.resources(
|
||||
Git.open(getRepositoryDir(userName, repositoryName)),
|
||||
Git.open(getRepositoryDir(requestUserName, requestRepositoryName))
|
||||
) { (oldGit, newGit) =>
|
||||
@@ -421,7 +488,7 @@ trait PullRequestService {
|
||||
originId: String,
|
||||
forkedId: String
|
||||
): (Option[ObjectId], Option[ObjectId]) = {
|
||||
using(
|
||||
Using.resources(
|
||||
Git.open(getRepositoryDir(originRepository.owner, originRepository.name)),
|
||||
Git.open(getRepositoryDir(forkedRepository.owner, forkedRepository.name))
|
||||
) {
|
||||
@@ -487,7 +554,7 @@ object PullRequestService {
|
||||
lazy val commitStateSummary: (CommitState, String) = {
|
||||
val stateMap = statuses.groupBy(_.state)
|
||||
val state = CommitState.combine(stateMap.keySet)
|
||||
val summary = stateMap.map { case (keyState, states) => states.size + " " + keyState.name }.mkString(", ")
|
||||
val summary = stateMap.map { case (keyState, states) => s"${states.size} ${keyState.name}" }.mkString(", ")
|
||||
state -> summary
|
||||
}
|
||||
lazy val statusesAndRequired: List[(CommitStatus, Boolean)] = statuses.map { s =>
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -73,24 +73,22 @@ trait ReleaseService {
|
||||
}
|
||||
|
||||
def getReleases(owner: String, repository: String)(implicit s: Session): Seq[ReleaseTag] = {
|
||||
ReleaseTags.filter(x => x.byRepository(owner, repository)).list
|
||||
ReleaseTags.filter(x => x.byRepository(owner, repository)).sortBy(x => x.updatedDate).list
|
||||
}
|
||||
|
||||
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] = {
|
||||
//Releases filter (_.byPrimaryKey(owner, repository, releaseId)) firstOption
|
||||
ReleaseTags filter (_.byTag(owner, repository, tag)) firstOption
|
||||
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
|
||||
): Int = {
|
||||
@@ -107,3 +105,9 @@ trait ReleaseService {
|
||||
ReleaseTags filter (_.byPrimaryKey(owner, repository, tag)) delete
|
||||
}
|
||||
}
|
||||
|
||||
object ReleaseService {
|
||||
|
||||
val ReleaseLimit = 10
|
||||
|
||||
}
|
||||
|
||||
@@ -4,16 +4,18 @@ import gitbucket.core.model.{Account, WebHook}
|
||||
import gitbucket.core.model.Profile._
|
||||
import gitbucket.core.model.Profile.profile.blockingApi._
|
||||
import gitbucket.core.plugin.PluginRegistry
|
||||
import gitbucket.core.service.SystemSettingsService.SystemSettings
|
||||
import gitbucket.core.service.WebHookService.WebHookPushPayload
|
||||
import gitbucket.core.util.Directory.getRepositoryDir
|
||||
import gitbucket.core.util.JGitUtil.CommitInfo
|
||||
import gitbucket.core.util.{JGitUtil, LockUtil}
|
||||
import gitbucket.core.util.SyntaxSugars.using
|
||||
import org.eclipse.jgit.api.Git
|
||||
import org.eclipse.jgit.dircache.{DirCache, DirCacheBuilder}
|
||||
import org.eclipse.jgit.lib._
|
||||
import org.eclipse.jgit.transport.{ReceiveCommand, ReceivePack}
|
||||
|
||||
import scala.util.Using
|
||||
|
||||
trait RepositoryCommitFileService {
|
||||
self: AccountService with ActivityService with IssuesService with PullRequestService with WebHookPullRequestService =>
|
||||
import RepositoryCommitFileService._
|
||||
@@ -24,12 +26,13 @@ trait RepositoryCommitFileService {
|
||||
branch: String,
|
||||
path: String,
|
||||
message: String,
|
||||
loginAccount: Account
|
||||
loginAccount: Account,
|
||||
settings: SystemSettings
|
||||
)(
|
||||
f: (Git, ObjectId, DirCacheBuilder, ObjectInserter) => Unit
|
||||
)(implicit s: Session, c: JsonFormat.Context) = {
|
||||
// prepend path to the filename
|
||||
_commitFile(repository, branch, message, loginAccount)(f)
|
||||
_commitFile(repository, branch, message, loginAccount, loginAccount.fullName, loginAccount.mailAddress, settings)(f)
|
||||
}
|
||||
|
||||
def commitFile(
|
||||
@@ -42,8 +45,39 @@ trait RepositoryCommitFileService {
|
||||
charset: String,
|
||||
message: String,
|
||||
commit: String,
|
||||
loginAccount: Account
|
||||
)(implicit s: Session, c: JsonFormat.Context) = {
|
||||
loginAccount: Account,
|
||||
settings: SystemSettings
|
||||
)(implicit s: Session, c: JsonFormat.Context): ObjectId = {
|
||||
commitFile(
|
||||
repository,
|
||||
branch,
|
||||
path,
|
||||
newFileName,
|
||||
oldFileName,
|
||||
if (content.nonEmpty) { content.getBytes(charset) } else { Array.emptyByteArray },
|
||||
message,
|
||||
commit,
|
||||
loginAccount,
|
||||
loginAccount.fullName,
|
||||
loginAccount.mailAddress,
|
||||
settings
|
||||
)
|
||||
}
|
||||
|
||||
def commitFile(
|
||||
repository: RepositoryService.RepositoryInfo,
|
||||
branch: String,
|
||||
path: String,
|
||||
newFileName: Option[String],
|
||||
oldFileName: Option[String],
|
||||
content: Array[Byte],
|
||||
message: String,
|
||||
commit: String,
|
||||
loginAccount: Account,
|
||||
fullName: String,
|
||||
mailAddress: String,
|
||||
settings: SystemSettings
|
||||
)(implicit s: Session, c: JsonFormat.Context): ObjectId = {
|
||||
|
||||
val newPath = newFileName.map { newFileName =>
|
||||
if (path.length == 0) newFileName else s"${path}/${newFileName}"
|
||||
@@ -52,7 +86,7 @@ trait RepositoryCommitFileService {
|
||||
if (path.length == 0) oldFileName else s"${path}/${oldFileName}"
|
||||
}
|
||||
|
||||
_commitFile(repository, branch, message, loginAccount) {
|
||||
_commitFile(repository, branch, message, loginAccount, fullName, mailAddress, settings) {
|
||||
case (git, headTip, builder, inserter) =>
|
||||
if (headTip.getName == commit) {
|
||||
val permission = JGitUtil
|
||||
@@ -70,7 +104,7 @@ trait RepositoryCommitFileService {
|
||||
newPath.foreach { newPath =>
|
||||
builder.add(JGitUtil.createDirCacheEntry(newPath, permission.map { bits =>
|
||||
FileMode.fromBits(bits)
|
||||
} getOrElse FileMode.REGULAR_FILE, inserter.insert(Constants.OBJ_BLOB, content.getBytes(charset))))
|
||||
} getOrElse FileMode.REGULAR_FILE, inserter.insert(Constants.OBJ_BLOB, content)))
|
||||
}
|
||||
builder.finish()
|
||||
}
|
||||
@@ -81,13 +115,16 @@ trait RepositoryCommitFileService {
|
||||
repository: RepositoryService.RepositoryInfo,
|
||||
branch: String,
|
||||
message: String,
|
||||
loginAccount: Account
|
||||
loginAccount: Account,
|
||||
committerName: String,
|
||||
committerMailAddress: String,
|
||||
settings: SystemSettings
|
||||
)(
|
||||
f: (Git, ObjectId, DirCacheBuilder, ObjectInserter) => Unit
|
||||
)(implicit s: Session, c: JsonFormat.Context) = {
|
||||
)(implicit s: Session, c: JsonFormat.Context): ObjectId = {
|
||||
|
||||
LockUtil.lock(s"${repository.owner}/${repository.name}") {
|
||||
using(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
|
||||
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
|
||||
val builder = DirCache.newInCore.builder()
|
||||
val inserter = git.getRepository.newObjectInserter()
|
||||
val headName = s"refs/heads/${branch}"
|
||||
@@ -101,8 +138,8 @@ trait RepositoryCommitFileService {
|
||||
headTip,
|
||||
builder.getDirCache.writeTree(inserter),
|
||||
headName,
|
||||
loginAccount.fullName,
|
||||
loginAccount.mailAddress,
|
||||
committerName,
|
||||
committerMailAddress,
|
||||
message
|
||||
)
|
||||
|
||||
@@ -114,7 +151,7 @@ trait RepositoryCommitFileService {
|
||||
|
||||
// call post commit hook
|
||||
val error = PluginRegistry().getReceiveHooks.flatMap { hook =>
|
||||
hook.preReceive(repository.owner, repository.name, receivePack, receiveCommand, loginAccount.userName)
|
||||
hook.preReceive(repository.owner, repository.name, receivePack, receiveCommand, committerName)
|
||||
}.headOption
|
||||
|
||||
error match {
|
||||
@@ -131,11 +168,11 @@ trait RepositoryCommitFileService {
|
||||
val refUpdate = git.getRepository.updateRef(headName)
|
||||
refUpdate.setNewObjectId(commitId)
|
||||
refUpdate.setForceUpdate(false)
|
||||
refUpdate.setRefLogIdent(new PersonIdent(loginAccount.fullName, loginAccount.mailAddress))
|
||||
refUpdate.setRefLogIdent(new PersonIdent(committerName, committerMailAddress))
|
||||
refUpdate.update()
|
||||
|
||||
// update pull request
|
||||
updatePullRequests(repository.owner, repository.name, branch, loginAccount, "synchronize")
|
||||
updatePullRequests(repository.owner, repository.name, branch, loginAccount, "synchronize", settings)
|
||||
|
||||
// record activity
|
||||
val commitInfo = new CommitInfo(JGitUtil.getRevCommitFromId(git, commitId))
|
||||
@@ -146,23 +183,22 @@ trait RepositoryCommitFileService {
|
||||
|
||||
// close issue by commit message
|
||||
if (branch == repository.repository.defaultBranch) {
|
||||
closeIssuesFromMessage(message, loginAccount.userName, repository.owner, repository.name).foreach {
|
||||
issueId =>
|
||||
getIssue(repository.owner, repository.name, issueId.toString).foreach { issue =>
|
||||
callIssuesWebHook("closed", repository, issue, loginAccount)
|
||||
PluginRegistry().getIssueHooks
|
||||
.foreach(_.closedByCommitComment(issue, repository, message, loginAccount))
|
||||
}
|
||||
closeIssuesFromMessage(message, committerName, repository.owner, repository.name).foreach { issueId =>
|
||||
getIssue(repository.owner, repository.name, issueId.toString).foreach { issue =>
|
||||
callIssuesWebHook("closed", repository, issue, loginAccount, settings)
|
||||
PluginRegistry().getIssueHooks
|
||||
.foreach(_.closedByCommitComment(issue, repository, message, loginAccount))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// call post commit hook
|
||||
PluginRegistry().getReceiveHooks.foreach { hook =>
|
||||
hook.postReceive(repository.owner, repository.name, receivePack, receiveCommand, loginAccount.userName)
|
||||
hook.postReceive(repository.owner, repository.name, receivePack, receiveCommand, committerName)
|
||||
}
|
||||
|
||||
val commit = new JGitUtil.CommitInfo(JGitUtil.getRevCommitFromId(git, commitId))
|
||||
callWebHookOf(repository.owner, repository.name, WebHook.Push) {
|
||||
callWebHookOf(repository.owner, repository.name, WebHook.Push, settings) {
|
||||
getAccountByUserName(repository.owner).map { ownerAccount =>
|
||||
WebHookPushPayload(
|
||||
git,
|
||||
@@ -177,6 +213,7 @@ trait RepositoryCommitFileService {
|
||||
}
|
||||
}
|
||||
}
|
||||
commitId
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import java.nio.file.Files
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
import gitbucket.core.model.Profile.profile.blockingApi._
|
||||
import gitbucket.core.util.SyntaxSugars._
|
||||
import gitbucket.core.util.Directory._
|
||||
import gitbucket.core.util.{FileUtil, JGitUtil, LockUtil}
|
||||
import gitbucket.core.model.{Account, Role}
|
||||
@@ -18,6 +17,7 @@ import org.eclipse.jgit.lib.{Constants, FileMode}
|
||||
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
import scala.concurrent.Future
|
||||
import scala.util.Using
|
||||
|
||||
object RepositoryCreationService {
|
||||
|
||||
@@ -107,7 +107,7 @@ trait RepositoryCreationService {
|
||||
JGitUtil.initRepository(gitdir)
|
||||
|
||||
if (initOption == "README" || initOption == "EMPTY_COMMIT") {
|
||||
using(Git.open(gitdir)) { git =>
|
||||
Using.resource(Git.open(gitdir)) { git =>
|
||||
val builder = DirCache.newInCore.builder()
|
||||
val inserter = git.getRepository.newObjectInserter()
|
||||
val headId = git.getRepository.resolve(Constants.HEAD + "^{commit}")
|
||||
@@ -148,7 +148,7 @@ trait RepositoryCreationService {
|
||||
|
||||
copyRepositoryDir.foreach { dir =>
|
||||
try {
|
||||
using(Git.open(dir)) { git =>
|
||||
Using.resource(Git.open(dir)) { git =>
|
||||
git.push().setRemote(gitdir.toURI.toString).setPushAll().setPushTags().call()
|
||||
}
|
||||
} finally {
|
||||
|
||||
@@ -4,12 +4,12 @@ import gitbucket.core.model.Issue
|
||||
import gitbucket.core.util._
|
||||
import gitbucket.core.util.StringUtil
|
||||
import Directory._
|
||||
import SyntaxSugars._
|
||||
import org.eclipse.jgit.revwalk.RevWalk
|
||||
import org.eclipse.jgit.treewalk.TreeWalk
|
||||
import org.eclipse.jgit.lib.FileMode
|
||||
import org.eclipse.jgit.api.Git
|
||||
import gitbucket.core.model.Profile.profile.blockingApi._
|
||||
import scala.util.Using
|
||||
|
||||
trait RepositorySearchService { self: IssuesService =>
|
||||
import RepositorySearchService._
|
||||
@@ -37,12 +37,12 @@ trait RepositorySearchService { self: IssuesService =>
|
||||
}
|
||||
|
||||
def countFiles(owner: String, repository: String, query: String): Int =
|
||||
using(Git.open(getRepositoryDir(owner, repository))) { git =>
|
||||
Using.resource(Git.open(getRepositoryDir(owner, repository))) { git =>
|
||||
if (JGitUtil.isEmpty(git)) 0 else searchRepositoryFiles(git, query).length
|
||||
}
|
||||
|
||||
def searchFiles(owner: String, repository: String, query: String): List[FileSearchResult] =
|
||||
using(Git.open(getRepositoryDir(owner, repository))) { git =>
|
||||
Using.resource(Git.open(getRepositoryDir(owner, repository))) { git =>
|
||||
if (JGitUtil.isEmpty(git)) {
|
||||
Nil
|
||||
} else {
|
||||
@@ -57,12 +57,12 @@ trait RepositorySearchService { self: IssuesService =>
|
||||
}
|
||||
|
||||
def countWikiPages(owner: String, repository: String, query: String): Int =
|
||||
using(Git.open(Directory.getWikiRepositoryDir(owner, repository))) { git =>
|
||||
Using.resource(Git.open(Directory.getWikiRepositoryDir(owner, repository))) { git =>
|
||||
if (JGitUtil.isEmpty(git)) 0 else searchRepositoryFiles(git, query).length
|
||||
}
|
||||
|
||||
def searchWikiPages(owner: String, repository: String, query: String): List[FileSearchResult] =
|
||||
using(Git.open(Directory.getWikiRepositoryDir(owner, repository))) { git =>
|
||||
Using.resource(Git.open(Directory.getWikiRepositoryDir(owner, repository))) { git =>
|
||||
if (JGitUtil.isEmpty(git)) {
|
||||
Nil
|
||||
} else {
|
||||
|
||||
@@ -17,6 +17,7 @@ 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 {
|
||||
self: AccountService =>
|
||||
@@ -458,6 +459,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)
|
||||
@@ -469,23 +471,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 associated with the login account.
|
||||
* @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) &&
|
||||
@@ -763,7 +784,7 @@ trait RepositoryService {
|
||||
}
|
||||
|
||||
// Get template file from project root. When didn't find, will lookup default folder.
|
||||
using(Git.open(Directory.getRepositoryDir(repository.owner, repository.name))) { git =>
|
||||
Using.resource(Git.open(Directory.getRepositoryDir(repository.owner, repository.name))) { git =>
|
||||
choiceTemplate(JGitUtil.getFileList(git, repository.repository.defaultBranch, "."))
|
||||
.orElse {
|
||||
choiceTemplate(JGitUtil.getFileList(git, repository.repository.defaultBranch, ".gitbucket"))
|
||||
|
||||
@@ -8,6 +8,7 @@ import gitbucket.core.service.SystemSettingsService._
|
||||
import gitbucket.core.util.ConfigUtil._
|
||||
import gitbucket.core.util.Directory._
|
||||
import gitbucket.core.util.SyntaxSugars._
|
||||
import scala.util.Using
|
||||
|
||||
trait SystemSettingsService {
|
||||
|
||||
@@ -20,9 +21,15 @@ 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))
|
||||
@@ -68,20 +75,16 @@ trait SystemSettingsService {
|
||||
}
|
||||
}
|
||||
props.setProperty(SkinName, settings.skinName.toString)
|
||||
settings.userDefinedCss.foreach(x => props.setProperty(UserDefinedCss, x))
|
||||
props.setProperty(ShowMailAddress, settings.showMailAddress.toString)
|
||||
props.setProperty(PluginNetworkInstall, settings.pluginNetworkInstall.toString)
|
||||
settings.pluginProxy.foreach { proxy =>
|
||||
props.setProperty(PluginProxyHost, proxy.host)
|
||||
props.setProperty(PluginProxyPort, proxy.port.toString)
|
||||
proxy.user.foreach { user =>
|
||||
props.setProperty(PluginProxyUser, user)
|
||||
}
|
||||
proxy.password.foreach { password =>
|
||||
props.setProperty(PluginProxyPassword, password)
|
||||
}
|
||||
}
|
||||
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)
|
||||
|
||||
using(new java.io.FileOutputStream(GitBucketConf)) { out =>
|
||||
Using.resource(new java.io.FileOutputStream(GitBucketConf)) { out =>
|
||||
props.store(out, null)
|
||||
}
|
||||
}
|
||||
@@ -90,7 +93,7 @@ trait SystemSettingsService {
|
||||
def loadSystemSettings(): SystemSettings = {
|
||||
defining(new java.util.Properties()) { props =>
|
||||
if (GitBucketConf.exists) {
|
||||
using(new java.io.FileInputStream(GitBucketConf)) { in =>
|
||||
Using.resource(new java.io.FileInputStream(GitBucketConf)) { in =>
|
||||
props.load(in)
|
||||
}
|
||||
}
|
||||
@@ -100,15 +103,27 @@ 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(
|
||||
@@ -156,18 +171,15 @@ trait SystemSettingsService {
|
||||
None
|
||||
},
|
||||
getValue(props, SkinName, "skin-blue"),
|
||||
getOptionValue(props, UserDefinedCss, None),
|
||||
getValue(props, ShowMailAddress, false),
|
||||
getValue(props, PluginNetworkInstall, false),
|
||||
if (getValue(props, PluginProxyHost, "").nonEmpty) {
|
||||
Some(
|
||||
Proxy(
|
||||
getValue(props, PluginProxyHost, ""),
|
||||
getValue(props, PluginProxyPort, 8080),
|
||||
getOptionValue(props, PluginProxyUser, None),
|
||||
getOptionValue(props, PluginProxyPassword, None)
|
||||
)
|
||||
)
|
||||
} else None
|
||||
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)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -185,9 +197,11 @@ 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],
|
||||
@@ -196,9 +210,10 @@ object SystemSettingsService {
|
||||
oidcAuthentication: Boolean,
|
||||
oidc: Option[OIDC],
|
||||
skinName: String,
|
||||
userDefinedCss: Option[String],
|
||||
showMailAddress: Boolean,
|
||||
pluginNetworkInstall: Boolean,
|
||||
pluginProxy: Option[Proxy]
|
||||
webHook: WebHook,
|
||||
upload: Upload
|
||||
) {
|
||||
|
||||
def baseUrl(request: HttpServletRequest): String =
|
||||
@@ -217,12 +232,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],
|
||||
@@ -270,12 +294,14 @@ 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 Lfs(serverUrl: Option[String])
|
||||
case class WebHook(blockPrivateAddress: Boolean, whitelist: Seq[String])
|
||||
|
||||
case class Upload(maxFileSize: Long, timeout: Long, largeMaxFileSize: Long, largeTimeout: Long)
|
||||
|
||||
val DefaultSshPort = 29418
|
||||
val DefaultSmtpPort = 25
|
||||
@@ -286,9 +312,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"
|
||||
@@ -320,15 +352,17 @@ 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 def getValue[A: ClassTag](props: java.util.Properties, key: String, default: A): A = {
|
||||
getSystemProperty(key).getOrElse(getEnvironmentVariable(key).getOrElse {
|
||||
getConfigValue(key).getOrElse {
|
||||
defining(props.getProperty(key)) { value =>
|
||||
if (value == null || value.isEmpty) {
|
||||
default
|
||||
@@ -336,11 +370,21 @@ object SystemSettingsService {
|
||||
convertType(value).asInstanceOf[A]
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private def getSeqValue[A: ClassTag](props: java.util.Properties, key: String, default: A): Seq[A] = {
|
||||
getValue[String](props, key, "").split("\n").toIndexedSeq.map { value =>
|
||||
if (value == null || value.isEmpty) {
|
||||
default
|
||||
} else {
|
||||
convertType(value).asInstanceOf[A]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private def getOptionValue[A: ClassTag](props: java.util.Properties, key: String, default: Option[A]): Option[A] = {
|
||||
getSystemProperty(key).orElse(getEnvironmentVariable(key).orElse {
|
||||
getConfigValue(key).orElse {
|
||||
defining(props.getProperty(key)) { value =>
|
||||
if (value == null || value.isEmpty) {
|
||||
default
|
||||
@@ -348,7 +392,7 @@ object SystemSettingsService {
|
||||
Some(convertType(value)).asInstanceOf[Option[A]]
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -3,24 +3,26 @@ package gitbucket.core.service
|
||||
import fr.brouillard.oss.security.xhub.XHub
|
||||
import fr.brouillard.oss.security.xhub.XHub.{XHubConverter, XHubDigest}
|
||||
import gitbucket.core.api._
|
||||
import gitbucket.core.controller.Context
|
||||
import gitbucket.core.model.{
|
||||
Account,
|
||||
AccountWebHook,
|
||||
AccountWebHookEvent,
|
||||
CommitComment,
|
||||
Issue,
|
||||
IssueComment,
|
||||
Label,
|
||||
PullRequest,
|
||||
WebHook,
|
||||
RepositoryWebHook,
|
||||
RepositoryWebHookEvent,
|
||||
AccountWebHook,
|
||||
AccountWebHookEvent
|
||||
WebHook
|
||||
}
|
||||
import gitbucket.core.model.Profile._
|
||||
import gitbucket.core.model.Profile.profile.blockingApi._
|
||||
import org.apache.http.client.utils.URLEncodedUtils
|
||||
import gitbucket.core.util.JGitUtil.CommitInfo
|
||||
import gitbucket.core.util.{RepositoryName, StringUtil}
|
||||
import gitbucket.core.util.Implicits._
|
||||
import gitbucket.core.util.{HttpClientUtil, RepositoryName, StringUtil}
|
||||
import gitbucket.core.service.RepositoryService.RepositoryInfo
|
||||
import org.apache.http.NameValuePair
|
||||
import org.apache.http.client.entity.UrlEncodedFormEntity
|
||||
@@ -34,6 +36,7 @@ import scala.util.{Failure, Success}
|
||||
import org.apache.http.HttpRequest
|
||||
import org.apache.http.HttpResponse
|
||||
import gitbucket.core.model.WebHookContentType
|
||||
import gitbucket.core.service.SystemSettingsService.SystemSettings
|
||||
import org.apache.http.client.entity.EntityBuilder
|
||||
import org.apache.http.entity.ContentType
|
||||
|
||||
@@ -55,6 +58,7 @@ trait WebHookService {
|
||||
.map { case (w, t) => w -> t.event }
|
||||
.list
|
||||
.groupBy(_._1)
|
||||
.view
|
||||
.mapValues(_.map(_._2).toSet)
|
||||
.toList
|
||||
.sortBy(_._1.url)
|
||||
@@ -87,6 +91,7 @@ trait WebHookService {
|
||||
.map { case (w, t) => w -> t.event }
|
||||
.list
|
||||
.groupBy(_._1)
|
||||
.view
|
||||
.mapValues(_.map(_._2).toSet)
|
||||
.headOption
|
||||
|
||||
@@ -136,6 +141,7 @@ trait WebHookService {
|
||||
.map { case (w, t) => w -> t.event }
|
||||
.list
|
||||
.groupBy(_._1)
|
||||
.view
|
||||
.mapValues(_.map(_._2).toSet)
|
||||
.toList
|
||||
.sortBy(_._1.url)
|
||||
@@ -164,6 +170,7 @@ trait WebHookService {
|
||||
.map { case (w, t) => w -> t.event }
|
||||
.list
|
||||
.groupBy(_._1)
|
||||
.view
|
||||
.mapValues(_.map(_._2).toSet)
|
||||
.headOption
|
||||
|
||||
@@ -197,20 +204,28 @@ trait WebHookService {
|
||||
def deleteAccountWebHook(owner: String, url: String)(implicit s: Session): Unit =
|
||||
AccountWebHooks.filter(_.byPrimaryKey(owner, url)).delete
|
||||
|
||||
def callWebHookOf(owner: String, repository: String, event: WebHook.Event)(
|
||||
def callWebHookOf(owner: String, repository: String, event: WebHook.Event, settings: SystemSettings)(
|
||||
makePayload: => Option[WebHookPayload]
|
||||
)(implicit s: Session, c: JsonFormat.Context): Unit = {
|
||||
val webHooks = getWebHooksByEvent(owner, repository, event)
|
||||
if (webHooks.nonEmpty) {
|
||||
makePayload.map(callWebHook(event, webHooks, _))
|
||||
makePayload.map(callWebHook(event, webHooks, _, settings))
|
||||
}
|
||||
val accountWebHooks = getAccountWebHooksByEvent(owner, event)
|
||||
if (accountWebHooks.nonEmpty) {
|
||||
makePayload.map(callWebHook(event, accountWebHooks, _))
|
||||
makePayload.map(callWebHook(event, accountWebHooks, _, settings))
|
||||
}
|
||||
}
|
||||
|
||||
def callWebHook(event: WebHook.Event, webHooks: List[WebHook], payload: WebHookPayload)(
|
||||
private def validateTargetAddress(settings: SystemSettings, url: String): Boolean = {
|
||||
val host = new java.net.URL(url).getHost
|
||||
|
||||
!settings.webHook.blockPrivateAddress ||
|
||||
!HttpClientUtil.isPrivateAddress(host) ||
|
||||
settings.webHook.whitelist.exists(range => HttpClientUtil.inIpRange(range, host))
|
||||
}
|
||||
|
||||
def callWebHook(event: WebHook.Event, webHooks: List[WebHook], payload: WebHookPayload, settings: SystemSettings)(
|
||||
implicit c: JsonFormat.Context
|
||||
): List[(WebHook, String, Future[HttpRequest], Future[HttpResponse])] = {
|
||||
import org.apache.http.impl.client.HttpClientBuilder
|
||||
@@ -230,10 +245,13 @@ trait WebHookService {
|
||||
}
|
||||
}
|
||||
try {
|
||||
if (!validateTargetAddress(settings, webHook.url)) {
|
||||
throw new IllegalArgumentException(s"Illegal address: ${webHook.url}")
|
||||
}
|
||||
val httpClient = HttpClientBuilder.create.useSystemProperties.addInterceptorLast(itcp).build
|
||||
logger.debug(s"start web hook invocation for ${webHook.url}")
|
||||
val httpPost = new HttpPost(webHook.url)
|
||||
logger.info(s"Content-Type: ${webHook.ctype.ctype}")
|
||||
logger.debug(s"Content-Type: ${webHook.ctype.ctype}")
|
||||
httpPost.addHeader("Content-Type", webHook.ctype.ctype)
|
||||
httpPost.addHeader("X-Github-Event", event.name)
|
||||
httpPost.addHeader("X-Github-Delivery", java.util.UUID.randomUUID().toString)
|
||||
@@ -298,7 +316,6 @@ trait WebHookService {
|
||||
} else {
|
||||
Nil
|
||||
}
|
||||
// logger.debug("end callWebHook")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -311,10 +328,12 @@ trait WebHookPullRequestService extends WebHookService {
|
||||
action: String,
|
||||
repository: RepositoryService.RepositoryInfo,
|
||||
issue: Issue,
|
||||
sender: Account
|
||||
sender: Account,
|
||||
settings: SystemSettings
|
||||
)(implicit s: Session, context: JsonFormat.Context): Unit = {
|
||||
callWebHookOf(repository.owner, repository.name, WebHook.Issues) {
|
||||
val users = getAccountsByUserNames(Set(repository.owner, issue.openedUserName), Set(sender))
|
||||
callWebHookOf(repository.owner, repository.name, WebHook.Issues, settings) {
|
||||
val users =
|
||||
getAccountsByUserNames(Set(repository.owner, issue.openedUserName) ++ issue.assignedUserName, Set(sender))
|
||||
for {
|
||||
repoOwner <- users.get(repository.owner)
|
||||
issueUser <- users.get(issue.openedUserName)
|
||||
@@ -327,6 +346,7 @@ trait WebHookPullRequestService extends WebHookService {
|
||||
issue,
|
||||
RepositoryName(repository),
|
||||
ApiUser(issueUser),
|
||||
issue.assignedUserName.flatMap(users.get(_)).map(ApiUser(_)),
|
||||
getIssueLabels(repository.owner, repository.name, issue.issueId)
|
||||
.map(ApiLabel(_, RepositoryName(repository)))
|
||||
),
|
||||
@@ -340,10 +360,11 @@ trait WebHookPullRequestService extends WebHookService {
|
||||
action: String,
|
||||
repository: RepositoryService.RepositoryInfo,
|
||||
issueId: Int,
|
||||
sender: Account
|
||||
sender: Account,
|
||||
settings: SystemSettings
|
||||
)(implicit s: Session, c: JsonFormat.Context): Unit = {
|
||||
import WebHookService._
|
||||
callWebHookOf(repository.owner, repository.name, WebHook.PullRequest) {
|
||||
callWebHookOf(repository.owner, repository.name, WebHook.PullRequest, settings) {
|
||||
for {
|
||||
(issue, pullRequest) <- getPullRequest(repository.owner, repository.name, issueId)
|
||||
users = getAccountsByUserNames(
|
||||
@@ -396,13 +417,14 @@ trait WebHookPullRequestService extends WebHookService {
|
||||
if wht.event === WebHook.PullRequest.asInstanceOf[WebHook.Event].bind && wht.byRepositoryWebHook(wh)
|
||||
} yield {
|
||||
((is, iu, pr, bu, ru), wh)
|
||||
}).list.groupBy(_._1).mapValues(_.map(_._2))
|
||||
}).list.groupBy(_._1).map { case (k, v) => (k, v.map(_._2)) }
|
||||
|
||||
def callPullRequestWebHookByRequestBranch(
|
||||
action: String,
|
||||
requestRepository: RepositoryService.RepositoryInfo,
|
||||
requestBranch: String,
|
||||
sender: Account
|
||||
sender: Account,
|
||||
settings: SystemSettings
|
||||
)(implicit s: Session, c: JsonFormat.Context): Unit = {
|
||||
import WebHookService._
|
||||
for {
|
||||
@@ -433,7 +455,7 @@ trait WebHookPullRequestService extends WebHookService {
|
||||
mergedComment = getMergedComment(baseRepo.owner, baseRepo.name, issue.issueId)
|
||||
)
|
||||
|
||||
callWebHook(WebHook.PullRequest, webHooks, payload)
|
||||
callWebHook(WebHook.PullRequest, webHooks, payload, settings)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -447,10 +469,11 @@ trait WebHookPullRequestReviewCommentService extends WebHookService {
|
||||
repository: RepositoryService.RepositoryInfo,
|
||||
issue: Issue,
|
||||
pullRequest: PullRequest,
|
||||
sender: Account
|
||||
sender: Account,
|
||||
settings: SystemSettings
|
||||
)(implicit s: Session, c: JsonFormat.Context): Unit = {
|
||||
import WebHookService._
|
||||
callWebHookOf(repository.owner, repository.name, WebHook.PullRequestReviewComment) {
|
||||
callWebHookOf(repository.owner, repository.name, WebHook.PullRequestReviewComment, settings) {
|
||||
val users =
|
||||
getAccountsByUserNames(Set(repository.owner, pullRequest.requestUserName, issue.openedUserName), Set(sender))
|
||||
for {
|
||||
@@ -492,18 +515,20 @@ trait WebHookIssueCommentService extends WebHookPullRequestService {
|
||||
repository: RepositoryService.RepositoryInfo,
|
||||
issue: Issue,
|
||||
issueCommentId: Int,
|
||||
sender: Account
|
||||
sender: Account,
|
||||
settings: SystemSettings
|
||||
)(implicit s: Session, c: JsonFormat.Context): Unit = {
|
||||
callWebHookOf(repository.owner, repository.name, WebHook.IssueComment) {
|
||||
callWebHookOf(repository.owner, repository.name, WebHook.IssueComment, settings) {
|
||||
for {
|
||||
issueComment <- getComment(repository.owner, repository.name, issueCommentId.toString())
|
||||
users = getAccountsByUserNames(
|
||||
Set(issue.openedUserName, repository.owner, issueComment.commentedUserName),
|
||||
Set(issue.openedUserName, repository.owner, issueComment.commentedUserName) ++ issue.assignedUserName,
|
||||
Set(sender)
|
||||
)
|
||||
issueUser <- users.get(issue.openedUserName)
|
||||
repoOwner <- users.get(repository.owner)
|
||||
commenter <- users.get(issueComment.commentedUserName)
|
||||
assignedUser = issue.assignedUserName.flatMap(users.get(_))
|
||||
labels = getIssueLabels(repository.owner, repository.name, issue.issueId)
|
||||
} yield {
|
||||
WebHookIssueCommentPayload(
|
||||
@@ -513,6 +538,7 @@ trait WebHookIssueCommentService extends WebHookPullRequestService {
|
||||
commentUser = commenter,
|
||||
repository = repository,
|
||||
repositoryUser = repoOwner,
|
||||
assignedUser = assignedUser,
|
||||
sender = sender,
|
||||
labels = labels
|
||||
)
|
||||
@@ -540,11 +566,8 @@ object WebHookService {
|
||||
object WebHookCreatePayload {
|
||||
|
||||
def apply(
|
||||
git: Git,
|
||||
sender: Account,
|
||||
refName: String,
|
||||
repositoryInfo: RepositoryInfo,
|
||||
commits: List[CommitInfo],
|
||||
repositoryOwner: Account,
|
||||
ref: String,
|
||||
refType: String
|
||||
@@ -555,7 +578,7 @@ object WebHookService {
|
||||
ref_type = refType,
|
||||
description = repositoryInfo.repository.description.getOrElse(""),
|
||||
master_branch = repositoryInfo.repository.defaultBranch,
|
||||
repository = ApiRepository.forWebhookPayload(repositoryInfo, owner = ApiUser(repositoryOwner))
|
||||
repository = ApiRepository(repositoryInfo, repositoryOwner)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -597,9 +620,9 @@ object WebHookService {
|
||||
before = ObjectId.toString(oldId),
|
||||
after = ObjectId.toString(newId),
|
||||
commits = commits.map { commit =>
|
||||
ApiCommit.forWebhookPayload(git, RepositoryName(repositoryInfo), commit)
|
||||
ApiCommit(git, RepositoryName(repositoryInfo), commit)
|
||||
},
|
||||
repository = ApiRepository.forWebhookPayload(repositoryInfo, owner = ApiUser(repositoryOwner))
|
||||
repository = ApiRepository(repositoryInfo, repositoryOwner)
|
||||
)
|
||||
|
||||
def createDummyPayload(sender: Account): WebHookPushPayload =
|
||||
@@ -689,6 +712,7 @@ object WebHookService {
|
||||
commentUser: Account,
|
||||
repository: RepositoryInfo,
|
||||
repositoryUser: Account,
|
||||
assignedUser: Option[Account],
|
||||
sender: Account,
|
||||
labels: List[Label]
|
||||
): WebHookIssueCommentPayload =
|
||||
@@ -699,6 +723,7 @@ object WebHookService {
|
||||
issue,
|
||||
RepositoryName(repository),
|
||||
ApiUser(issueUser),
|
||||
assignedUser.map(ApiUser(_)),
|
||||
labels.map(ApiLabel(_, RepositoryName(repository)))
|
||||
),
|
||||
comment =
|
||||
|
||||
@@ -14,7 +14,9 @@ import org.eclipse.jgit.diff.{DiffEntry, DiffFormatter}
|
||||
import java.io.ByteArrayInputStream
|
||||
import org.eclipse.jgit.patch._
|
||||
import org.eclipse.jgit.api.errors.PatchFormatException
|
||||
import scala.collection.JavaConverters._
|
||||
|
||||
import scala.jdk.CollectionConverters._
|
||||
import scala.util.Using
|
||||
|
||||
object WikiService {
|
||||
|
||||
@@ -73,7 +75,7 @@ trait WikiService {
|
||||
* Returns the wiki page.
|
||||
*/
|
||||
def getWikiPage(owner: String, repository: String, pageName: String): Option[WikiPageInfo] = {
|
||||
using(Git.open(Directory.getWikiRepositoryDir(owner, repository))) { git =>
|
||||
Using.resource(Git.open(Directory.getWikiRepositoryDir(owner, repository))) { git =>
|
||||
if (!JGitUtil.isEmpty(git)) {
|
||||
JGitUtil.getFileList(git, "master", ".").find(_.name == pageName + ".md").map { file =>
|
||||
WikiPageInfo(
|
||||
@@ -92,7 +94,7 @@ trait WikiService {
|
||||
* Returns the list of wiki page names.
|
||||
*/
|
||||
def getWikiPageList(owner: String, repository: String): List[String] = {
|
||||
using(Git.open(Directory.getWikiRepositoryDir(owner, repository))) { git =>
|
||||
Using.resource(Git.open(Directory.getWikiRepositoryDir(owner, repository))) { git =>
|
||||
JGitUtil
|
||||
.getFileList(git, "master", ".")
|
||||
.filter(_.name.endsWith(".md"))
|
||||
@@ -118,7 +120,7 @@ trait WikiService {
|
||||
|
||||
try {
|
||||
LockUtil.lock(s"${owner}/${repository}/wiki") {
|
||||
using(Git.open(Directory.getWikiRepositoryDir(owner, repository))) { git =>
|
||||
Using.resource(Git.open(Directory.getWikiRepositoryDir(owner, repository))) { git =>
|
||||
val reader = git.getRepository.newObjectReader
|
||||
val oldTreeIter = new CanonicalTreeParser
|
||||
oldTreeIter.reset(reader, git.getRepository.resolve(from + "^{tree}"))
|
||||
@@ -133,7 +135,7 @@ trait WikiService {
|
||||
}
|
||||
}
|
||||
|
||||
val patch = using(new java.io.ByteArrayOutputStream()) { out =>
|
||||
val patch = Using.resource(new java.io.ByteArrayOutputStream()) { out =>
|
||||
val formatter = new DiffFormatter(out)
|
||||
formatter.setRepository(git.getRepository)
|
||||
formatter.format(diffs.asJava)
|
||||
@@ -237,7 +239,7 @@ trait WikiService {
|
||||
currentId: Option[String]
|
||||
): Option[String] = {
|
||||
LockUtil.lock(s"${owner}/${repository}/wiki") {
|
||||
using(Git.open(Directory.getWikiRepositoryDir(owner, repository))) { git =>
|
||||
Using.resource(Git.open(Directory.getWikiRepositoryDir(owner, repository))) { git =>
|
||||
val builder = DirCache.newInCore.builder()
|
||||
val inserter = git.getRepository.newObjectInserter()
|
||||
val headId = git.getRepository.resolve(Constants.HEAD + "^{commit}")
|
||||
@@ -309,7 +311,7 @@ trait WikiService {
|
||||
message: String
|
||||
): Unit = {
|
||||
LockUtil.lock(s"${owner}/${repository}/wiki") {
|
||||
using(Git.open(Directory.getWikiRepositoryDir(owner, repository))) { git =>
|
||||
Using.resource(Git.open(Directory.getWikiRepositoryDir(owner, repository))) { git =>
|
||||
val builder = DirCache.newInCore.builder()
|
||||
val inserter = git.getRepository.newObjectInserter()
|
||||
val headId = git.getRepository.resolve(Constants.HEAD + "^{commit}")
|
||||
|
||||
@@ -7,6 +7,10 @@ import gitbucket.core.model.Account
|
||||
import gitbucket.core.service.SystemSettingsService.SystemSettings
|
||||
import gitbucket.core.service.{AccessTokenService, AccountService, SystemSettingsService}
|
||||
import gitbucket.core.util.{AuthUtil, Keys}
|
||||
import gitbucket.core.model.Profile.profile.blockingApi._
|
||||
// Imported names have higher precedence than names, defined in other files.
|
||||
// If Database is not bound by explicit import, then "Database" refers to the Database introduced by the wildcard import above.
|
||||
import gitbucket.core.servlet.Database
|
||||
|
||||
class ApiAuthenticationFilter extends Filter with AccessTokenService with AccountService with SystemSettingsService {
|
||||
|
||||
@@ -20,16 +24,24 @@ 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(())
|
||||
}
|
||||
.orElse {
|
||||
Option(req.getParameter("access_token")).map(AccessTokenService.getAccountByAccessToken(_).toRight(()))
|
||||
}
|
||||
.orElse {
|
||||
Option(request.getSession.getAttribute(Keys.Session.LoginAccount).asInstanceOf[Account]).map(Right(_))
|
||||
} match {
|
||||
case Some(Right(account)) => request.setAttribute(Keys.Session.LoginAccount, account); chain.doFilter(req, res)
|
||||
case None => chain.doFilter(req, res)
|
||||
case Some(Right(account)) =>
|
||||
request.setAttribute(Keys.Session.LoginAccount, account)
|
||||
Database() withTransaction { implicit session =>
|
||||
updateLastLoginDate(account.userName)
|
||||
}
|
||||
chain.doFilter(req, res)
|
||||
case None => chain.doFilter(req, res)
|
||||
case Some(Left(_)) => {
|
||||
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED)
|
||||
response.setContentType("application/json; charset=utf-8")
|
||||
@@ -41,8 +53,9 @@ class ApiAuthenticationFilter extends Filter with AccessTokenService with Accoun
|
||||
}
|
||||
|
||||
def doBasicAuth(auth: String, settings: SystemSettings, request: HttpServletRequest): Option[Account] = {
|
||||
implicit val session = request.getAttribute(Keys.Request.DBSession).asInstanceOf[slick.jdbc.JdbcBackend#Session]
|
||||
val Array(username, password) = AuthUtil.decodeAuthHeader(auth).split(":", 2)
|
||||
authenticate(settings, username, password)
|
||||
Database() withTransaction { implicit session =>
|
||||
authenticate(settings, username, password)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,32 @@ import org.scalatra.ScalatraFilter
|
||||
|
||||
import scala.collection.mutable.ListBuffer
|
||||
|
||||
class CompositeScalatraFilter extends Filter {
|
||||
abstract class ControllerFilter extends Filter {
|
||||
|
||||
def process(request: ServletRequest, response: ServletResponse, checkPath: String): Boolean
|
||||
|
||||
override def doFilter(request: ServletRequest, response: ServletResponse, chain: FilterChain): Unit = {
|
||||
val contextPath = request.getServletContext.getContextPath
|
||||
val requestPath = request.asInstanceOf[HttpServletRequest].getRequestURI.substring(contextPath.length)
|
||||
val checkPath = if (requestPath.endsWith("/")) {
|
||||
requestPath
|
||||
} else {
|
||||
requestPath + "/"
|
||||
}
|
||||
|
||||
if (!checkPath.startsWith("/upload/") && !checkPath.startsWith("/git/") && !checkPath.startsWith("/git-lfs/") &&
|
||||
!checkPath.startsWith("/assets/") && !checkPath.startsWith("/plugin-assets/")) {
|
||||
val continue = process(request, response, checkPath)
|
||||
if (!continue) {
|
||||
return ()
|
||||
}
|
||||
}
|
||||
|
||||
chain.doFilter(request, response)
|
||||
}
|
||||
}
|
||||
|
||||
class CompositeScalatraFilter extends ControllerFilter {
|
||||
|
||||
private val filters = new ListBuffer[(ScalatraFilter, String)]()
|
||||
|
||||
@@ -29,34 +54,23 @@ class CompositeScalatraFilter extends Filter {
|
||||
}
|
||||
}
|
||||
|
||||
override def doFilter(request: ServletRequest, response: ServletResponse, chain: FilterChain): Unit = {
|
||||
val contextPath = request.getServletContext.getContextPath
|
||||
val requestPath = request.asInstanceOf[HttpServletRequest].getRequestURI.substring(contextPath.length)
|
||||
val checkPath = if (requestPath.endsWith("/")) {
|
||||
requestPath
|
||||
} else {
|
||||
requestPath + "/"
|
||||
}
|
||||
override def process(request: ServletRequest, response: ServletResponse, checkPath: String): Boolean = {
|
||||
filters
|
||||
.filter {
|
||||
case (_, path) =>
|
||||
val start = path.replaceFirst("/\\*$", "/")
|
||||
checkPath.startsWith(start)
|
||||
}
|
||||
.foreach {
|
||||
case (filter, _) =>
|
||||
val mockChain = new MockFilterChain()
|
||||
filter.doFilter(request, response, mockChain)
|
||||
if (mockChain.continue == false) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
if (!checkPath.startsWith("/upload/") && !checkPath.startsWith("/git/") && !checkPath.startsWith("/git-lfs/") &&
|
||||
!checkPath.startsWith("/plugin-assets/")) {
|
||||
filters
|
||||
.filter {
|
||||
case (_, path) =>
|
||||
val start = path.replaceFirst("/\\*$", "/")
|
||||
checkPath.startsWith(start)
|
||||
}
|
||||
.foreach {
|
||||
case (filter, _) =>
|
||||
val mockChain = new MockFilterChain()
|
||||
filter.doFilter(request, response, mockChain)
|
||||
if (mockChain.continue == false) {
|
||||
return ()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
chain.doFilter(request, response)
|
||||
true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -4,12 +4,16 @@ import javax.servlet._
|
||||
import javax.servlet.http._
|
||||
|
||||
import gitbucket.core.model.Account
|
||||
import gitbucket.core.model.Profile.profile.blockingApi._
|
||||
import gitbucket.core.plugin.{GitRepositoryFilter, GitRepositoryRouting, PluginRegistry}
|
||||
import gitbucket.core.service.SystemSettingsService.SystemSettings
|
||||
import gitbucket.core.service.{AccessTokenService, AccountService, RepositoryService, SystemSettingsService}
|
||||
import gitbucket.core.util.Implicits._
|
||||
import gitbucket.core.util.{AuthUtil, Implicits, Keys}
|
||||
import gitbucket.core.util.{AuthUtil, Keys}
|
||||
import gitbucket.core.model.Profile.profile.blockingApi._
|
||||
// Imported names have higher precedence than names, defined in other files.
|
||||
// If Database is not bound by explicit import, then "Database" refers to the Database introduced by the wildcard import above.
|
||||
import gitbucket.core.servlet.Database
|
||||
|
||||
import org.slf4j.LoggerFactory
|
||||
|
||||
/**
|
||||
@@ -106,6 +110,7 @@ class GitAuthenticationFilter extends Filter with RepositoryService with Account
|
||||
if (isUpdating) {
|
||||
if (hasDeveloperRole(repository.owner, repository.name, Some(account))) {
|
||||
request.setAttribute(Keys.Request.UserName, account.userName)
|
||||
request.setAttribute(Keys.Request.RepositoryLockKey, s"${repository.owner}/${repository.name}")
|
||||
true
|
||||
} else false
|
||||
} else if (repository.repository.isPrivate) {
|
||||
|
||||
@@ -8,7 +8,8 @@ import gitbucket.core.util.{FileUtil, StringUtil}
|
||||
import org.apache.commons.io.{FileUtils, IOUtils}
|
||||
import org.json4s.jackson.Serialization._
|
||||
import org.apache.http.HttpStatus
|
||||
import gitbucket.core.util.SyntaxSugars._
|
||||
|
||||
import scala.util.Using
|
||||
|
||||
/**
|
||||
* Provides GitLFS Transfer API
|
||||
@@ -28,8 +29,8 @@ class GitLfsTransferServlet extends HttpServlet {
|
||||
if (file.exists()) {
|
||||
res.setStatus(HttpStatus.SC_OK)
|
||||
res.setContentType("application/octet-stream")
|
||||
res.setContentLength(file.length.toInt)
|
||||
using(new FileInputStream(file), res.getOutputStream) { (in, out) =>
|
||||
res.setHeader("Content-Length", file.length.toString)
|
||||
Using.resources(new FileInputStream(file), res.getOutputStream) { (in, out) =>
|
||||
IOUtils.copy(in, out)
|
||||
out.flush()
|
||||
}
|
||||
@@ -45,7 +46,7 @@ class GitLfsTransferServlet extends HttpServlet {
|
||||
} yield {
|
||||
val file = new File(FileUtil.getLfsFilePath(owner, repository, oid))
|
||||
FileUtils.forceMkdir(file.getParentFile)
|
||||
using(req.getInputStream, new FileOutputStream(file)) { (in, out) =>
|
||||
Using.resources(req.getInputStream, new FileOutputStream(file)) { (in, out) =>
|
||||
IOUtils.copy(in, out)
|
||||
}
|
||||
res.setStatus(HttpStatus.SC_OK)
|
||||
@@ -71,7 +72,7 @@ class GitLfsTransferServlet extends HttpServlet {
|
||||
|
||||
private def sendError(res: HttpServletResponse, status: Int, message: String): Unit = {
|
||||
res.setStatus(status)
|
||||
using(res.getWriter()) { out =>
|
||||
Using.resource(res.getWriter()) { out =>
|
||||
out.write(write(GitLfs.Error(message)))
|
||||
out.flush()
|
||||
}
|
||||
|
||||
@@ -4,9 +4,10 @@ import java.io.File
|
||||
import java.util
|
||||
import java.util.Date
|
||||
|
||||
import scala.util.Using
|
||||
|
||||
import gitbucket.core.api
|
||||
import gitbucket.core.model.WebHook
|
||||
import gitbucket.core.model.Profile.profile.blockingApi._
|
||||
import gitbucket.core.plugin.{GitRepositoryRouting, PluginRegistry}
|
||||
import gitbucket.core.service.IssuesService.IssueSearchCondition
|
||||
import gitbucket.core.service.WebHookService._
|
||||
@@ -14,6 +15,11 @@ import gitbucket.core.service._
|
||||
import gitbucket.core.util.SyntaxSugars._
|
||||
import gitbucket.core.util.Implicits._
|
||||
import gitbucket.core.util._
|
||||
import gitbucket.core.model.Profile.profile.blockingApi._
|
||||
// Imported names have higher precedence than names, defined in other files.
|
||||
// If Database is not bound by explicit import, then "Database" refers to the Database introduced by the wildcard import above.
|
||||
import gitbucket.core.servlet.Database
|
||||
|
||||
import org.eclipse.jgit.api.Git
|
||||
import org.eclipse.jgit.http.server.GitServlet
|
||||
import org.eclipse.jgit.lib._
|
||||
@@ -22,8 +28,8 @@ import org.eclipse.jgit.transport.resolver._
|
||||
import org.slf4j.LoggerFactory
|
||||
import javax.servlet.ServletConfig
|
||||
import javax.servlet.http.{HttpServletRequest, HttpServletResponse}
|
||||
|
||||
import org.eclipse.jgit.diff.DiffEntry.ChangeType
|
||||
import org.eclipse.jgit.internal.storage.file.FileRepository
|
||||
import org.json4s.jackson.Serialization._
|
||||
|
||||
/**
|
||||
@@ -41,7 +47,7 @@ class GitRepositoryServlet extends GitServlet with SystemSettingsService {
|
||||
setReceivePackFactory(new GitBucketReceivePackFactory())
|
||||
|
||||
val root: File = new File(Directory.RepositoryHome)
|
||||
setRepositoryResolver(new GitBucketRepositoryResolver(new FileResolver[HttpServletRequest](root, true)))
|
||||
setRepositoryResolver(new GitBucketRepositoryResolver)
|
||||
|
||||
super.init(config)
|
||||
}
|
||||
@@ -55,11 +61,24 @@ class GitRepositoryServlet extends GitServlet with SystemSettingsService {
|
||||
res.sendRedirect(baseUrl(req) + "/" + paths.dropRight(1).last + "/" + paths.last)
|
||||
|
||||
} else if (req.getMethod.toUpperCase == "POST" && req.getRequestURI.endsWith("/info/lfs/objects/batch")) {
|
||||
serviceGitLfsBatchAPI(req, res)
|
||||
|
||||
withLockRepository(req) {
|
||||
serviceGitLfsBatchAPI(req, res)
|
||||
}
|
||||
} else {
|
||||
// response for git client
|
||||
super.service(req, res)
|
||||
withLockRepository(req) {
|
||||
super.service(req, res)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private def withLockRepository[T](req: HttpServletRequest)(f: => T): T = {
|
||||
if (req.hasAttribute(Keys.Request.RepositoryLockKey)) {
|
||||
LockUtil.lock(req.getAttribute(Keys.Request.RepositoryLockKey).asInstanceOf[String]) {
|
||||
f
|
||||
}
|
||||
} else {
|
||||
f
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,7 +114,7 @@ class GitRepositoryServlet extends GitServlet with SystemSettingsService {
|
||||
GitLfs.Action(
|
||||
href = baseUrl + "/git-lfs/" + owner + "/" + repository + "/" + requestObject.oid,
|
||||
header =
|
||||
Map("Authorization" -> StringUtil.encodeBlowfish(timeout + " " + requestObject.oid)),
|
||||
Map("Authorization" -> StringUtil.encodeBlowfish(s"$timeout ${requestObject.oid}")),
|
||||
expires_at = new Date(timeout)
|
||||
)
|
||||
)
|
||||
@@ -116,7 +135,7 @@ class GitRepositoryServlet extends GitServlet with SystemSettingsService {
|
||||
GitLfs.Action(
|
||||
href = baseUrl + "/git-lfs/" + owner + "/" + repository + "/" + requestObject.oid,
|
||||
header =
|
||||
Map("Authorization" -> StringUtil.encodeBlowfish(timeout + " " + requestObject.oid)),
|
||||
Map("Authorization" -> StringUtil.encodeBlowfish(s"$timeout ${requestObject.oid}")),
|
||||
expires_at = new Date(timeout)
|
||||
)
|
||||
)
|
||||
@@ -127,7 +146,7 @@ class GitRepositoryServlet extends GitServlet with SystemSettingsService {
|
||||
}
|
||||
|
||||
res.setContentType("application/vnd.git-lfs+json")
|
||||
using(res.getWriter) { out =>
|
||||
Using.resource(res.getWriter) { out =>
|
||||
out.print(write(batchResponse))
|
||||
out.flush()
|
||||
}
|
||||
@@ -138,10 +157,7 @@ class GitRepositoryServlet extends GitServlet with SystemSettingsService {
|
||||
}
|
||||
}
|
||||
|
||||
class GitBucketRepositoryResolver(parent: FileResolver[HttpServletRequest])
|
||||
extends RepositoryResolver[HttpServletRequest] {
|
||||
|
||||
private val resolver = new FileResolver[HttpServletRequest](new File(Directory.GitBucketHome), true)
|
||||
class GitBucketRepositoryResolver extends RepositoryResolver[HttpServletRequest] {
|
||||
|
||||
override def open(req: HttpServletRequest, name: String): Repository = {
|
||||
// Rewrite repository path if routing is marched
|
||||
@@ -150,10 +166,10 @@ class GitBucketRepositoryResolver(parent: FileResolver[HttpServletRequest])
|
||||
.map {
|
||||
case GitRepositoryRouting(urlPattern, localPath, _) =>
|
||||
val path = urlPattern.r.replaceFirstIn(name, localPath)
|
||||
resolver.open(req, path)
|
||||
new FileRepository(new File(Directory.GitBucketHome, path))
|
||||
}
|
||||
.getOrElse {
|
||||
parent.open(req, name)
|
||||
new FileRepository(new File(Directory.RepositoryHome, name))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -206,7 +222,7 @@ class GitBucketReceivePackFactory extends ReceivePackFactory[HttpServletRequest]
|
||||
}
|
||||
}
|
||||
|
||||
import scala.collection.JavaConverters._
|
||||
import scala.jdk.CollectionConverters._
|
||||
|
||||
class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl: String, sshUrl: Option[String])
|
||||
extends PostReceiveHook
|
||||
@@ -215,6 +231,7 @@ class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl:
|
||||
with AccountService
|
||||
with IssuesService
|
||||
with ActivityService
|
||||
with MergeService
|
||||
with PullRequestService
|
||||
with WebHookService
|
||||
with LabelsService
|
||||
@@ -222,7 +239,8 @@ class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl:
|
||||
with MilestonesService
|
||||
with WebHookPullRequestService
|
||||
with WebHookPullRequestReviewCommentService
|
||||
with CommitsService {
|
||||
with CommitsService
|
||||
with SystemSettingsService {
|
||||
|
||||
private val logger = LoggerFactory.getLogger(classOf[CommitLogHook])
|
||||
private var existIds: Seq[String] = Nil
|
||||
@@ -239,7 +257,7 @@ class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl:
|
||||
command.setResult(ReceiveCommand.Result.REJECTED_OTHER_REASON, error)
|
||||
}
|
||||
}
|
||||
using(Git.open(Directory.getRepositoryDir(owner, repository))) { git =>
|
||||
Using.resource(Git.open(Directory.getRepositoryDir(owner, repository))) { git =>
|
||||
existIds = JGitUtil.getAllCommitIds(git)
|
||||
}
|
||||
} catch {
|
||||
@@ -252,9 +270,11 @@ class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl:
|
||||
}
|
||||
|
||||
def onPostReceive(receivePack: ReceivePack, commands: java.util.Collection[ReceiveCommand]): Unit = {
|
||||
val settings = loadSystemSettings()
|
||||
|
||||
Database() withTransaction { implicit session =>
|
||||
try {
|
||||
using(Git.open(Directory.getRepositoryDir(owner, repository))) { git =>
|
||||
Using.resource(Git.open(Directory.getRepositoryDir(owner, repository))) { git =>
|
||||
JGitUtil.removeCache(git)
|
||||
|
||||
val pushedIds = scala.collection.mutable.Set[String]()
|
||||
@@ -278,7 +298,7 @@ class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl:
|
||||
if (JGitUtil.isEmpty(git) && commits.nonEmpty && branchName != repositoryInfo.repository.defaultBranch) {
|
||||
saveRepositoryDefaultBranch(owner, repository, branchName)
|
||||
// Change repository HEAD
|
||||
using(Git.open(Directory.getRepositoryDir(owner, repository))) { git =>
|
||||
Using.resource(Git.open(Directory.getRepositoryDir(owner, repository))) { git =>
|
||||
git.getRepository.updateRef(Constants.HEAD, true).link(Constants.R_HEADS + branchName)
|
||||
}
|
||||
}
|
||||
@@ -300,7 +320,7 @@ class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl:
|
||||
getAccountByUserName(pusher).foreach { pusherAccount =>
|
||||
closeIssuesFromMessage(commit.fullMessage, pusher, owner, repository).foreach { issueId =>
|
||||
getIssue(owner, repository, issueId.toString).foreach { issue =>
|
||||
callIssuesWebHook("closed", repositoryInfo, issue, pusherAccount)
|
||||
callIssuesWebHook("closed", repositoryInfo, issue, pusherAccount, settings)
|
||||
PluginRegistry().getIssueHooks
|
||||
.foreach(_.closedByCommitComment(issue, repositoryInfo, commit.fullMessage, pusherAccount))
|
||||
}
|
||||
@@ -320,7 +340,7 @@ class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl:
|
||||
}.isDefined) {
|
||||
markMergeAndClosePullRequest(pusher, owner, repository, pull)
|
||||
getAccountByUserName(pusher).foreach { pusherAccount =>
|
||||
callPullRequestWebHook("closed", repositoryInfo, pull.issueId, pusherAccount)
|
||||
callPullRequestWebHook("closed", repositoryInfo, pull.issueId, pusherAccount, settings)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -348,14 +368,14 @@ class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl:
|
||||
case ReceiveCommand.Type.CREATE | ReceiveCommand.Type.UPDATE |
|
||||
ReceiveCommand.Type.UPDATE_NONFASTFORWARD =>
|
||||
getAccountByUserName(pusher).foreach { pusherAccount =>
|
||||
updatePullRequests(owner, repository, branchName, pusherAccount, "synchronize")
|
||||
updatePullRequests(owner, repository, branchName, pusherAccount, "synchronize", settings)
|
||||
}
|
||||
case _ =>
|
||||
}
|
||||
}
|
||||
|
||||
// call web hook
|
||||
callWebHookOf(owner, repository, WebHook.Push) {
|
||||
callWebHookOf(owner, repository, WebHook.Push, settings) {
|
||||
for {
|
||||
pusherAccount <- getAccountByUserName(pusher)
|
||||
ownerAccount <- getAccountByUserName(owner)
|
||||
@@ -373,18 +393,15 @@ class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl:
|
||||
}
|
||||
}
|
||||
if (command.getType == ReceiveCommand.Type.CREATE) {
|
||||
callWebHookOf(owner, repository, WebHook.Create) {
|
||||
callWebHookOf(owner, repository, WebHook.Create, settings) {
|
||||
for {
|
||||
pusherAccount <- getAccountByUserName(pusher)
|
||||
ownerAccount <- getAccountByUserName(owner)
|
||||
} yield {
|
||||
val refType = if (refName(1) == "tags") "tag" else "branch"
|
||||
WebHookCreatePayload(
|
||||
git,
|
||||
pusherAccount,
|
||||
command.getRefName,
|
||||
repositoryInfo,
|
||||
newCommits,
|
||||
ownerAccount,
|
||||
ref = branchName,
|
||||
refType = refType
|
||||
@@ -414,11 +431,14 @@ class WikiCommitHook(owner: String, repository: String, pusher: String, baseUrl:
|
||||
extends PostReceiveHook
|
||||
with WebHookService
|
||||
with AccountService
|
||||
with RepositoryService {
|
||||
with RepositoryService
|
||||
with SystemSettingsService {
|
||||
|
||||
private val logger = LoggerFactory.getLogger(classOf[WikiCommitHook])
|
||||
|
||||
override def onPostReceive(receivePack: ReceivePack, commands: util.Collection[ReceiveCommand]): Unit = {
|
||||
val settings = loadSystemSettings()
|
||||
|
||||
Database() withTransaction { implicit session =>
|
||||
try {
|
||||
commands.asScala.headOption.foreach { command =>
|
||||
@@ -435,7 +455,7 @@ class WikiCommitHook(owner: String, repository: String, pusher: String, baseUrl:
|
||||
|
||||
commitIds.foreach {
|
||||
case (oldCommitId, newCommitId) =>
|
||||
val commits = using(Git.open(Directory.getWikiRepositoryDir(owner, repository))) { git =>
|
||||
val commits = Using.resource(Git.open(Directory.getWikiRepositoryDir(owner, repository))) { git =>
|
||||
JGitUtil.getCommitLog(git, oldCommitId, newCommitId).flatMap { commit =>
|
||||
val diffs = JGitUtil.getDiffs(git, None, commit.id, false, false)
|
||||
diffs.collect {
|
||||
@@ -454,7 +474,7 @@ class WikiCommitHook(owner: String, repository: String, pusher: String, baseUrl:
|
||||
(commits.head._1, fileName, commits.last._3)
|
||||
}
|
||||
|
||||
callWebHookOf(owner, repository, WebHook.Gollum) {
|
||||
callWebHookOf(owner, repository, WebHook.Gollum, settings) {
|
||||
for {
|
||||
pusherAccount <- getAccountByUserName(pusher)
|
||||
repositoryUser <- getAccountByUserName(owner)
|
||||
|
||||
@@ -9,9 +9,12 @@ import gitbucket.core.plugin.PluginRegistry
|
||||
import gitbucket.core.service.{ActivityService, SystemSettingsService}
|
||||
import gitbucket.core.util.DatabaseConfig
|
||||
import gitbucket.core.util.Directory._
|
||||
import gitbucket.core.util.SyntaxSugars._
|
||||
import gitbucket.core.util.JDBCUtil._
|
||||
import gitbucket.core.model.Profile.profile.blockingApi._
|
||||
// Imported names have higher precedence than names, defined in other files.
|
||||
// If Database is not bound by explicit import, then "Database" refers to the Database introduced by the wildcard import above.
|
||||
import gitbucket.core.servlet.Database
|
||||
|
||||
import io.github.gitbucket.solidbase.Solidbase
|
||||
import io.github.gitbucket.solidbase.manager.JDBCVersionManager
|
||||
import javax.servlet.{ServletContextEvent, ServletContextListener}
|
||||
@@ -21,7 +24,8 @@ import org.slf4j.LoggerFactory
|
||||
import akka.actor.{Actor, ActorSystem, Props}
|
||||
import com.typesafe.akka.extension.quartz.QuartzSchedulerExtension
|
||||
|
||||
import scala.collection.JavaConverters._
|
||||
import scala.jdk.CollectionConverters._
|
||||
import scala.util.Using
|
||||
|
||||
/**
|
||||
* Initialize GitBucket system.
|
||||
@@ -84,7 +88,7 @@ class InitializeListener extends ServletContextListener with SystemSettingsServi
|
||||
}
|
||||
|
||||
// Install bundled plugins
|
||||
extractBundledPlugins(gitbucketVersion)
|
||||
extractBundledPlugins()
|
||||
|
||||
// Load plugins
|
||||
logger.info("Initialize plugins")
|
||||
@@ -134,11 +138,11 @@ class InitializeListener extends ServletContextListener with SystemSettingsServi
|
||||
}
|
||||
}
|
||||
|
||||
private def extractBundledPlugins(gitbucketVersion: String): Unit = {
|
||||
private def extractBundledPlugins(): Unit = {
|
||||
logger.info("Extract bundled plugins...")
|
||||
val cl = Thread.currentThread.getContextClassLoader
|
||||
try {
|
||||
using(cl.getResourceAsStream("bundle-plugins.txt")) { pluginsFile =>
|
||||
Using.resource(cl.getResourceAsStream("bundle-plugins.txt")) { pluginsFile =>
|
||||
if (pluginsFile != null) {
|
||||
val plugins = IOUtils.readLines(pluginsFile, "UTF-8")
|
||||
val gitbucketVersion = GitBucketCoreModule.getVersions.asScala.last.getVersion
|
||||
@@ -146,14 +150,14 @@ class InitializeListener extends ServletContextListener with SystemSettingsServi
|
||||
plugins.asScala.foreach { plugin =>
|
||||
plugin.trim.split(":") match {
|
||||
case Array(pluginId, pluginVersion) =>
|
||||
val fileName = s"gitbucket-${pluginId}-plugin-gitbucket_${gitbucketVersion}-${pluginVersion}.jar"
|
||||
val fileName = s"gitbucket-${pluginId}-plugin-${pluginVersion}.jar"
|
||||
val in = cl.getResourceAsStream("plugins/" + fileName)
|
||||
if (in != null) {
|
||||
val file = new File(PluginHome, fileName)
|
||||
logger.info(s"Extract to ${file.getAbsolutePath}")
|
||||
|
||||
FileUtils.forceMkdirParent(file)
|
||||
using(in, new FileOutputStream(file)) {
|
||||
Using.resources(in, new FileOutputStream(file)) {
|
||||
case (in, out) => IOUtils.copy(in, out)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package gitbucket.core.servlet
|
||||
|
||||
import javax.servlet.http.{HttpServlet, HttpServletRequest, HttpServletResponse}
|
||||
|
||||
import gitbucket.core.plugin.PluginRegistry
|
||||
import gitbucket.core.util.FileUtil
|
||||
import org.apache.commons.io.IOUtils
|
||||
@@ -17,24 +16,32 @@ class PluginAssetsServlet extends HttpServlet {
|
||||
|
||||
assetsMappings
|
||||
.find { case (prefix, _, _) => path.startsWith("/plugin-assets" + prefix) }
|
||||
.flatMap {
|
||||
.foreach {
|
||||
case (prefix, resourcePath, classLoader) =>
|
||||
val resourceName = path.substring(("/plugin-assets" + prefix).length)
|
||||
Option(classLoader.getResourceAsStream(resourcePath.stripPrefix("/") + resourceName))
|
||||
}
|
||||
.map { in =>
|
||||
try {
|
||||
val bytes = IOUtils.toByteArray(in)
|
||||
resp.setContentLength(bytes.length)
|
||||
resp.setContentType(FileUtil.getMimeType(path, bytes))
|
||||
resp.setHeader("Cache-Control", "max-age=3600")
|
||||
resp.getOutputStream.write(bytes)
|
||||
} finally {
|
||||
in.close()
|
||||
}
|
||||
}
|
||||
.getOrElse {
|
||||
resp.setStatus(404)
|
||||
val ifNoneMatch = req.getHeader("If-None-Match")
|
||||
PluginRegistry.getPluginInfoFromClassLoader(classLoader).map { info =>
|
||||
val etag = s""""${info.pluginJar.lastModified}"""" // ETag must wrapped with double quote
|
||||
if (ifNoneMatch == etag) {
|
||||
resp.setStatus(304)
|
||||
} else {
|
||||
val resourceName = path.substring(("/plugin-assets" + prefix).length)
|
||||
Option(classLoader.getResourceAsStream(resourcePath.stripPrefix("/") + resourceName))
|
||||
.map { in =>
|
||||
try {
|
||||
val bytes = IOUtils.toByteArray(in)
|
||||
resp.setContentLength(bytes.length)
|
||||
resp.setContentType(FileUtil.getMimeType(path, bytes))
|
||||
resp.setHeader("ETag", etag)
|
||||
resp.getOutputStream.write(bytes)
|
||||
} finally {
|
||||
in.close()
|
||||
}
|
||||
}
|
||||
.getOrElse {
|
||||
resp.setStatus(404)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import javax.servlet.http.HttpServletRequest
|
||||
import gitbucket.core.controller.ControllerBase
|
||||
import gitbucket.core.plugin.PluginRegistry
|
||||
|
||||
class PluginControllerFilter extends Filter {
|
||||
class PluginControllerFilter extends ControllerFilter {
|
||||
|
||||
private var filterConfig: FilterConfig = null
|
||||
|
||||
@@ -21,16 +21,13 @@ class PluginControllerFilter extends Filter {
|
||||
}
|
||||
}
|
||||
|
||||
override def doFilter(request: ServletRequest, response: ServletResponse, chain: FilterChain): Unit = {
|
||||
val contextPath = request.getServletContext.getContextPath
|
||||
val requestUri = request.asInstanceOf[HttpServletRequest].getRequestURI.substring(contextPath.length)
|
||||
|
||||
override def process(request: ServletRequest, response: ServletResponse, checkPath: String): Boolean = {
|
||||
PluginRegistry()
|
||||
.getControllers()
|
||||
.filter {
|
||||
case (_, path) =>
|
||||
val start = path.replaceFirst("/\\*$", "/")
|
||||
(requestUri + "/").startsWith(start)
|
||||
checkPath.startsWith(start)
|
||||
}
|
||||
.foreach {
|
||||
case (controller, _) =>
|
||||
@@ -42,11 +39,11 @@ class PluginControllerFilter extends Filter {
|
||||
controller.doFilter(request, response, mockChain)
|
||||
|
||||
if (mockChain.continue == false) {
|
||||
return ()
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
chain.doFilter(request, response)
|
||||
true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -11,13 +11,13 @@ import org.apache.sshd.server.session.ServerSession
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.io.{File, InputStream, OutputStream}
|
||||
|
||||
import SyntaxSugars._
|
||||
import org.eclipse.jgit.api.Git
|
||||
import Directory._
|
||||
import gitbucket.core.ssh.PublicKeyAuthenticator.AuthType
|
||||
import org.eclipse.jgit.transport.{ReceivePack, UploadPack}
|
||||
import org.apache.sshd.server.shell.UnknownCommand
|
||||
import org.eclipse.jgit.errors.RepositoryNotFoundException
|
||||
import scala.util.Using
|
||||
|
||||
object GitCommand {
|
||||
val DefaultCommandRegex = """\Agit-(upload|receive)-pack '/([a-zA-Z0-9\-_.]+)/([a-zA-Z0-9\-\+_.]+).git'\Z""".r
|
||||
@@ -152,7 +152,7 @@ class DefaultGitUploadPack(owner: String, repoName: String)
|
||||
}
|
||||
|
||||
if (execute) {
|
||||
using(Git.open(getRepositoryDir(owner, repoName))) { git =>
|
||||
Using.resource(Git.open(getRepositoryDir(owner, repoName))) { git =>
|
||||
val repository = git.getRepository
|
||||
val upload = new UploadPack(repository)
|
||||
upload.upload(in, out, err)
|
||||
@@ -177,7 +177,7 @@ class DefaultGitReceivePack(owner: String, repoName: String, baseUrl: String, ss
|
||||
}
|
||||
|
||||
if (execute) {
|
||||
using(Git.open(getRepositoryDir(owner, repoName))) { git =>
|
||||
Using.resource(Git.open(getRepositoryDir(owner, repoName))) { git =>
|
||||
val repository = git.getRepository
|
||||
val receive = new ReceivePack(repository)
|
||||
if (!repoName.endsWith(".wiki")) {
|
||||
@@ -202,7 +202,7 @@ class PluginGitUploadPack(repoName: String, routing: GitRepositoryRouting)
|
||||
|
||||
if (execute) {
|
||||
val path = routing.urlPattern.r.replaceFirstIn(repoName, routing.localPath)
|
||||
using(Git.open(new File(Directory.GitBucketHome, path))) { git =>
|
||||
Using.resource(Git.open(new File(Directory.GitBucketHome, path))) { git =>
|
||||
val repository = git.getRepository
|
||||
val upload = new UploadPack(repository)
|
||||
upload.upload(in, out, err)
|
||||
@@ -222,7 +222,7 @@ class PluginGitReceivePack(repoName: String, routing: GitRepositoryRouting)
|
||||
|
||||
if (execute) {
|
||||
val path = routing.urlPattern.r.replaceFirstIn(repoName, routing.localPath)
|
||||
using(Git.open(new File(Directory.GitBucketHome, path))) { git =>
|
||||
Using.resource(Git.open(new File(Directory.GitBucketHome, path))) { git =>
|
||||
val repository = git.getRepository
|
||||
val receive = new ReceivePack(repository)
|
||||
receive.receive(in, out, err)
|
||||
|
||||
41
src/main/scala/gitbucket/core/util/ConfigUtil.scala
Normal file
41
src/main/scala/gitbucket/core/util/ConfigUtil.scala
Normal file
@@ -0,0 +1,41 @@
|
||||
package gitbucket.core.util
|
||||
|
||||
import gitbucket.core.util.SyntaxSugars.defining
|
||||
|
||||
import scala.reflect.ClassTag
|
||||
|
||||
object ConfigUtil {
|
||||
|
||||
def getConfigValue[A: ClassTag](key: String): Option[A] = {
|
||||
getSystemProperty(key).orElse(getEnvironmentVariable(key))
|
||||
}
|
||||
|
||||
def getEnvironmentVariable[A: ClassTag](key: String): Option[A] = {
|
||||
val name = (if (key.startsWith("gitbucket.")) "" else "GITBUCKET_") + key.toUpperCase.replace('.', '_')
|
||||
val value = System.getenv(name)
|
||||
if (value != null && value.nonEmpty) {
|
||||
Some(convertType(value))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
def getSystemProperty[A: ClassTag](key: String): Option[A] = {
|
||||
val name = if (key.startsWith("gitbucket.")) key else "gitbucket." + key
|
||||
val value = System.getProperty(name)
|
||||
if (value != null && value.nonEmpty) {
|
||||
Some(convertType(value))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
def convertType[A: ClassTag](value: String): A =
|
||||
defining(implicitly[ClassTag[A]].runtimeClass) { c =>
|
||||
if (c == classOf[Boolean]) value.toBoolean
|
||||
else if (c == classOf[Long]) value.toLong
|
||||
else if (c == classOf[Int]) value.toInt
|
||||
else value
|
||||
}.asInstanceOf[A]
|
||||
|
||||
}
|
||||
@@ -6,9 +6,8 @@ import java.io.File
|
||||
import Directory._
|
||||
import ConfigUtil._
|
||||
import com.github.takezoe.slick.blocking.{BlockingH2Driver, BlockingJdbcProfile, BlockingMySQLDriver}
|
||||
import gitbucket.core.util.SyntaxSugars.defining
|
||||
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
|
||||
@@ -54,16 +53,14 @@ object DatabaseConfig {
|
||||
lazy val minimumIdle: Option[Int] = getOptionValue("db.minimumIdle", config.getInt)
|
||||
lazy val maximumPoolSize: Option[Int] = getOptionValue("db.maximumPoolSize", config.getInt)
|
||||
|
||||
private def getValue[T](path: String, f: String => T): T = {
|
||||
getSystemProperty(path).getOrElse(getEnvironmentVariable(path).getOrElse {
|
||||
f(path)
|
||||
})
|
||||
private def getValue[T: ClassTag](path: String, f: String => T): T = {
|
||||
getConfigValue(path).getOrElse(f(path))
|
||||
}
|
||||
|
||||
private def getOptionValue[T](path: String, f: String => T): Option[T] = {
|
||||
getSystemProperty(path).orElse(getEnvironmentVariable(path).orElse {
|
||||
private def getOptionValue[T: ClassTag](path: String, f: String => T): Option[T] = {
|
||||
getConfigValue(path).orElse {
|
||||
if (config.hasPath(path)) Some(f(path)) else None
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -81,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 {
|
||||
@@ -100,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
|
||||
@@ -114,33 +119,3 @@ object DatabaseType {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object ConfigUtil {
|
||||
|
||||
def getEnvironmentVariable[A](key: String): Option[A] = {
|
||||
val value = System.getenv("GITBUCKET_" + key.toUpperCase.replace('.', '_'))
|
||||
if (value != null && value.nonEmpty) {
|
||||
Some(convertType(value)).asInstanceOf[Option[A]]
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
def getSystemProperty[A](key: String): Option[A] = {
|
||||
val value = System.getProperty("gitbucket." + key)
|
||||
if (value != null && value.nonEmpty) {
|
||||
Some(convertType(value)).asInstanceOf[Option[A]]
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
def convertType[A: ClassTag](value: String) =
|
||||
defining(implicitly[ClassTag[A]].runtimeClass) { c =>
|
||||
if (c == classOf[Boolean]) value.toBoolean
|
||||
else if (c == classOf[Long]) value.toLong
|
||||
else if (c == classOf[Int]) value.toInt
|
||||
else value
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -62,7 +62,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")
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user