mirror of
https://github.com/gitbucket/gitbucket.git
synced 2026-05-09 12:15:35 +02:00
Compare commits
312 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b3ca4468cd | ||
|
|
452e41603a | ||
|
|
b41de4a770 | ||
|
|
3423d2f2f3 | ||
|
|
9269acc64d | ||
|
|
18b5f20f1c | ||
|
|
68d592a8ff | ||
|
|
ee18d96f98 | ||
|
|
6d3687cbdf | ||
|
|
1ef1a2983b | ||
|
|
47850fe0fa | ||
|
|
055a37cac0 | ||
|
|
333deb4756 | ||
|
|
34cde4b09a | ||
|
|
5091b4838c | ||
|
|
2e83289061 | ||
|
|
e11f651002 | ||
|
|
be684a567d | ||
|
|
97e552b10f | ||
|
|
5bb8046c32 | ||
|
|
e7192655f7 | ||
|
|
19ba09740c | ||
|
|
d169777722 | ||
|
|
ff8a5f6b77 | ||
|
|
ec953df156 | ||
|
|
d6a191d95b | ||
|
|
aba428bba1 | ||
|
|
6ab37fd596 | ||
|
|
73fc70f55b | ||
|
|
aad18b7a50 | ||
|
|
cc278be5cd | ||
|
|
d0f4f82a0f | ||
|
|
1dcbf386b1 | ||
|
|
414afd285c | ||
|
|
35b645d8b5 | ||
|
|
b3cba53866 | ||
|
|
a4773bb3ca | ||
|
|
863d8a4af5 | ||
|
|
3fccd7b53c | ||
|
|
dd2760eaf7 | ||
|
|
824bafa739 | ||
|
|
60cdaec05f | ||
|
|
c204a435b3 | ||
|
|
37accd92d6 | ||
|
|
01fd0ee1f0 | ||
|
|
fab1c74473 | ||
|
|
0d8fcfd28d | ||
|
|
b91a7c32a6 | ||
|
|
7f665c649b | ||
|
|
dcbadb4071 | ||
|
|
e3096d15ff | ||
|
|
a83c24e7b3 | ||
|
|
73457c9658 | ||
|
|
cfc8d9f3f1 | ||
|
|
8f423b83ea | ||
|
|
1e7ac532b6 | ||
|
|
0fd1db4596 | ||
|
|
0755b7ab7f | ||
|
|
052382e5c4 | ||
|
|
f44a63cec1 | ||
|
|
603d67354a | ||
|
|
aafa423b9f | ||
|
|
2da9d0a801 | ||
|
|
b60c112a74 | ||
|
|
843ed6df37 | ||
|
|
0f0a849677 | ||
|
|
682901ccbb | ||
|
|
048fdb8837 | ||
|
|
3353616789 | ||
|
|
b6cb4c865f | ||
|
|
1fcfd093f7 | ||
|
|
3f27c6e733 | ||
|
|
b6bd9bfc3b | ||
|
|
6c392f0056 | ||
|
|
9a38de9a23 | ||
|
|
8883600090 | ||
|
|
ab822a3c27 | ||
|
|
0e4d64de23 | ||
|
|
fbc6bd36bd | ||
|
|
ed90ca2dce | ||
|
|
537ef92149 | ||
|
|
d51afa7d40 | ||
|
|
975cffff48 | ||
|
|
d92e9c00e8 | ||
|
|
12d72cbb19 | ||
|
|
e7a6f0930b | ||
|
|
d8e03bed1f | ||
|
|
f48c087cd8 | ||
|
|
917663e0df | ||
|
|
556ddbc926 | ||
|
|
1c6f37b8e8 | ||
|
|
720a329a50 | ||
|
|
220a8f076a | ||
|
|
43be8333c7 | ||
|
|
08706ab4df | ||
|
|
b1196657e0 | ||
|
|
334bd0c919 | ||
|
|
cf0f896972 | ||
|
|
d21ca3ff8a | ||
|
|
83f1f16de7 | ||
|
|
0fa2ccf107 | ||
|
|
18e3dd431b | ||
|
|
f25dee2249 | ||
|
|
575ffa9580 | ||
|
|
f17af5aeb0 | ||
|
|
639f153589 | ||
|
|
fb07098c13 | ||
|
|
74fc08b039 | ||
|
|
2776e00004 | ||
|
|
5932fce303 | ||
|
|
39c9fc4261 | ||
|
|
89ea4509a3 | ||
|
|
d19d838ead | ||
|
|
633a2699a8 | ||
|
|
e79bca4a3c | ||
|
|
f8ab480d20 | ||
|
|
a14129e340 | ||
|
|
5ec39df6e0 | ||
|
|
956e0c6321 | ||
|
|
0660a9203a | ||
|
|
1b660272a1 | ||
|
|
d9ef9b874d | ||
|
|
7824f796ee | ||
|
|
838a8d4c7b | ||
|
|
8db9f77f91 | ||
|
|
4f82a469d9 | ||
|
|
0f6ae8559b | ||
|
|
3f4b2eec35 | ||
|
|
a020601d3a | ||
|
|
d511205588 | ||
|
|
fc896b2ea1 | ||
|
|
3819670535 | ||
|
|
067669a18c | ||
|
|
bef7cee8db | ||
|
|
cb9f56fc22 | ||
|
|
719a521f06 | ||
|
|
413754976f | ||
|
|
4530cfc068 | ||
|
|
fcc7d6da2d | ||
|
|
33e565d029 | ||
|
|
6a1da37e9a | ||
|
|
897cc5eea4 | ||
|
|
5cbea281af | ||
|
|
e74db57b75 | ||
|
|
6b2d124d09 | ||
|
|
7b3c77db8c | ||
|
|
1bcab5c2c7 | ||
|
|
16020e9a7d | ||
|
|
dfd3e22986 | ||
|
|
48d7a20fb7 | ||
|
|
04108a0a1a | ||
|
|
e0bab59846 | ||
|
|
1004c83bc4 | ||
|
|
502bb450e3 | ||
|
|
fb03b0bec7 | ||
|
|
2c3a1c9c92 | ||
|
|
73b7ee2d57 | ||
|
|
71df890f39 | ||
|
|
1fdac404b1 | ||
|
|
47be1e60bb | ||
|
|
1c05f50954 | ||
|
|
ce26a48fd7 | ||
|
|
26e0838ae0 | ||
|
|
6b5b0db20a | ||
|
|
5973c8cb85 | ||
|
|
f683439c73 | ||
|
|
625f3655ec | ||
|
|
4a5ab1ae71 | ||
|
|
3bcae2c6d2 | ||
|
|
4441eff657 | ||
|
|
9fed83e222 | ||
|
|
42ea9bac65 | ||
|
|
2aba827772 | ||
|
|
31e84bd543 | ||
|
|
ba471b8bf7 | ||
|
|
90ec95494d | ||
|
|
e72dbad765 | ||
|
|
ed63ea4674 | ||
|
|
559a52404f | ||
|
|
5e80848524 | ||
|
|
055a4f0798 | ||
|
|
6f432e537c | ||
|
|
a2abbbbd63 | ||
|
|
16707b99e6 | ||
|
|
80a4ed62cc | ||
|
|
b713a334f0 | ||
|
|
ec6264c04c | ||
|
|
44bb1ab565 | ||
|
|
523ee025ac | ||
|
|
d193c6399c | ||
|
|
8c9715f4d9 | ||
|
|
3bbdcd5f14 | ||
|
|
c183d3b0b5 | ||
|
|
b6f0c6bf57 | ||
|
|
8719e834f3 | ||
|
|
f23b8c6d61 | ||
|
|
d46be31ea8 | ||
|
|
9bb36e4cb2 | ||
|
|
2780fb9605 | ||
|
|
b69e2d299a | ||
|
|
a07bd7b1a4 | ||
|
|
d605455678 | ||
|
|
781b1169a2 | ||
|
|
80112d3437 | ||
|
|
131af7f4fa | ||
|
|
1ceac4ef9f | ||
|
|
b7aa3e69f4 | ||
|
|
62231c41b2 | ||
|
|
6c0a33712b | ||
|
|
e192624489 | ||
|
|
68609e44de | ||
|
|
c8e3af6072 | ||
|
|
9f7048a19c | ||
|
|
cbdc0528ca | ||
|
|
444facc95b | ||
|
|
ed40ddaa45 | ||
|
|
43c629b3e5 | ||
|
|
0d2c156579 | ||
|
|
ab218f8540 | ||
|
|
4b8a375890 | ||
|
|
9c3a5f3f0a | ||
|
|
1bcfbec29c | ||
|
|
781940436a | ||
|
|
7f226a6ab1 | ||
|
|
f538950d64 | ||
|
|
142be65893 | ||
|
|
8b01e5bc1f | ||
|
|
bde0aab207 | ||
|
|
31ecfef1c9 | ||
|
|
0fdae4c334 | ||
|
|
3ea7de974b | ||
|
|
34a7990d1d | ||
|
|
709d759d09 | ||
|
|
2d47252b27 | ||
|
|
8586e141fc | ||
|
|
e3887e6872 | ||
|
|
fd658c35ee | ||
|
|
ca4c1b237f | ||
|
|
92b17fd719 | ||
|
|
b4453531a3 | ||
|
|
de72a9223f | ||
|
|
ae26e3031e | ||
|
|
c26b41cfc2 | ||
|
|
2c4ee664ab | ||
|
|
911cd45965 | ||
|
|
320cc58c62 | ||
|
|
cfa1638e3c | ||
|
|
27264bf59a | ||
|
|
935283272e | ||
|
|
ea440294b1 | ||
|
|
df8435bf6a | ||
|
|
4fe019b11f | ||
|
|
538c79705a | ||
|
|
e94d972741 | ||
|
|
c58a9aca6a | ||
|
|
a6612e8522 | ||
|
|
bd455b9a0a | ||
|
|
122853f1f6 | ||
|
|
debbc21bf3 | ||
|
|
d991fdcbd9 | ||
|
|
78cc8f14fd | ||
|
|
09f1a06267 | ||
|
|
6fb910a939 | ||
|
|
9e891a0168 | ||
|
|
0bc77ce453 | ||
|
|
a46c821673 | ||
|
|
cae843d18f | ||
|
|
40ffc689b0 | ||
|
|
65afbd25b8 | ||
|
|
458b30610f | ||
|
|
a89608a2e2 | ||
|
|
0f45311d5e | ||
|
|
447ed6ce88 | ||
|
|
ab358a10bc | ||
|
|
5148851d49 | ||
|
|
068ecd30fe | ||
|
|
5396c0ee58 | ||
|
|
2328c1878b | ||
|
|
bc9a6f464a | ||
|
|
4613fd4cb4 | ||
|
|
ff89c8805c | ||
|
|
95d98c525b | ||
|
|
bc78a38570 | ||
|
|
8db2398dd8 | ||
|
|
e073ca74da | ||
|
|
50e899bf83 | ||
|
|
5ddd6234e3 | ||
|
|
84fa11b5df | ||
|
|
f51db60fc7 | ||
|
|
351e72e8c2 | ||
|
|
a0f54b0b8e | ||
|
|
4c491e40c7 | ||
|
|
54440d9cde | ||
|
|
ea8333844c | ||
|
|
fc34df7008 | ||
|
|
d49d62edfc | ||
|
|
246e2e2de4 | ||
|
|
83b48d26ce | ||
|
|
3d40094925 | ||
|
|
2d7e8526ed | ||
|
|
7768f4c4dd | ||
|
|
e43fabf895 | ||
|
|
7cf83979a6 | ||
|
|
1ecc30c570 | ||
|
|
ee0721f40d | ||
|
|
07836edf84 | ||
|
|
bfbe40e027 | ||
|
|
842d3b6185 | ||
|
|
086437ca4f | ||
|
|
6e48435f38 | ||
|
|
3b8e903cb5 | ||
|
|
55308f2deb |
4
.github/CODEOWNERS
vendored
Normal file
4
.github/CODEOWNERS
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
* @takezoe
|
||||||
|
|
||||||
|
build.sbt @xuwei-k
|
||||||
|
project/* @xuwei-k
|
||||||
2
.github/ISSUE_TEMPLATE.md
vendored
2
.github/ISSUE_TEMPLATE.md
vendored
@@ -12,7 +12,7 @@
|
|||||||
**Deployment mode**: *explain here how you use GitBucket : standalone app, under webcontainer (which one), with an http frontend (nginx, httpd, ...)*
|
**Deployment mode**: *explain here how you use GitBucket : standalone app, under webcontainer (which one), with an http frontend (nginx, httpd, ...)*
|
||||||
|
|
||||||
**Problem description**:
|
**Problem description**:
|
||||||
- *be as explicit has you can*
|
- *be as explicit as you can*
|
||||||
- *describe the problem and its symptoms*
|
- *describe the problem and its symptoms*
|
||||||
- *explain how to reproduce*
|
- *explain how to reproduce*
|
||||||
- *attach whatever information that can help understanding the context (screen capture, log files)*
|
- *attach whatever information that can help understanding the context (screen capture, log files)*
|
||||||
|
|||||||
10
.github/workflows/build.yml
vendored
10
.github/workflows/build.yml
vendored
@@ -5,13 +5,14 @@ on: [push, pull_request]
|
|||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 30
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
java: [8, 11]
|
java: [8, 11, 17]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Cache
|
- name: Cache
|
||||||
uses: actions/cache@v2
|
uses: actions/cache@v2.1.7
|
||||||
env:
|
env:
|
||||||
cache-name: cache-sbt-libs
|
cache-name: cache-sbt-libs
|
||||||
with:
|
with:
|
||||||
@@ -21,11 +22,14 @@ jobs:
|
|||||||
~/.cache/coursier/v1
|
~/.cache/coursier/v1
|
||||||
key: build-${{ env.cache-name }}-${{ hashFiles('build.sbt') }}
|
key: build-${{ env.cache-name }}-${{ hashFiles('build.sbt') }}
|
||||||
- name: Set up JDK
|
- name: Set up JDK
|
||||||
uses: actions/setup-java@v1
|
uses: actions/setup-java@v2
|
||||||
with:
|
with:
|
||||||
java-version: ${{ matrix.java }}
|
java-version: ${{ matrix.java }}
|
||||||
|
distribution: adopt
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
run: sbt scalafmtSbtCheck scalafmtCheck test:scalafmtCheck test
|
run: sbt scalafmtSbtCheck scalafmtCheck test:scalafmtCheck test
|
||||||
|
- name: Scala 3
|
||||||
|
run: sbt '++ 3.0.1!' update # TODO
|
||||||
- name: Build executable
|
- name: Build executable
|
||||||
run: sbt executable
|
run: sbt executable
|
||||||
- name: Upload artifacts
|
- name: Upload artifacts
|
||||||
|
|||||||
9
.scala-steward.conf
Normal file
9
.scala-steward.conf
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
updates.limit = 3
|
||||||
|
|
||||||
|
updates.includeScala = true
|
||||||
|
|
||||||
|
updates.pin = [
|
||||||
|
{ groupId = "org.eclipse.jetty", version = "9." }
|
||||||
|
{ groupId = "org.eclipse.jgit", version = "5." }
|
||||||
|
{ groupId = "com.zaxxer", version = "4." }
|
||||||
|
]
|
||||||
32
CHANGELOG.md
32
CHANGELOG.md
@@ -1,6 +1,38 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
All changes to the project will be documented in this file.
|
All changes to the project will be documented in this file.
|
||||||
|
|
||||||
|
### 4.37.2 - 16 Jan 2022
|
||||||
|
- Security fix
|
||||||
|
|
||||||
|
### 4.37.1 - 14 Dec 2021
|
||||||
|
- Update gist-plugin and notification-plugin
|
||||||
|
- Fix SSHCommand extension point for apache-sshd 2.x
|
||||||
|
|
||||||
|
### 4.37.0 - 11 Dec 2021
|
||||||
|
- Enhance Git Reference APIs
|
||||||
|
- Add milestone data to issue list API
|
||||||
|
- Support "all" in issue list API
|
||||||
|
- Support EDDSA in signed commit verification
|
||||||
|
- Support custom SSH url
|
||||||
|
- Relax max passward length limitation
|
||||||
|
- Relax max webhook url length limitation
|
||||||
|
|
||||||
|
### 4.36.2 - 16 Aug 2021
|
||||||
|
- Escape user name in avatar image tag
|
||||||
|
|
||||||
|
### 4.36.1 - 22 Jul 2021
|
||||||
|
- Bump gitbucket-gist-plugin to 4.21.0
|
||||||
|
|
||||||
|
### 4.36.0 - 17 Jul 2021
|
||||||
|
- Tag selector in the repository viewer
|
||||||
|
- Link issues/pull requests of other repositories
|
||||||
|
- Files and lines can be linked in the diff view
|
||||||
|
- Option to disable XSS protection
|
||||||
|
|
||||||
|
### 4.35.3 - 14 Jan 2021
|
||||||
|
- Fix a bug that Wiki page cannot be deleted
|
||||||
|
- Fix a deployment issue on Tomcat
|
||||||
|
|
||||||
### 4.35.2 - 30 Dec 2020
|
### 4.35.2 - 30 Dec 2020
|
||||||
- Upgrade gitbucket-notifications-plugin to 1.10.0
|
- Upgrade gitbucket-notifications-plugin to 1.10.0
|
||||||
- Upgrade oauth2-oidc-sdk to 8.29.1 to solve dependency issue
|
- Upgrade oauth2-oidc-sdk to 8.29.1 to solve dependency issue
|
||||||
|
|||||||
45
README.md
45
README.md
@@ -8,6 +8,8 @@ GitBucket is a Git web platform powered by Scala offering:
|
|||||||
- High extensibility by plugins
|
- High extensibility by plugins
|
||||||
- API compatibility with GitHub
|
- API compatibility with GitHub
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
You can try an [online demo](https://gitbucket.herokuapp.com/) *(ID: root / Pass: root)* of GitBucket, and also get the latest information at [GitBucket News](https://gitbucket.github.io/gitbucket-news/).
|
You can try an [online demo](https://gitbucket.herokuapp.com/) *(ID: root / Pass: root)* of GitBucket, and also get the latest information at [GitBucket News](https://gitbucket.github.io/gitbucket-news/).
|
||||||
|
|
||||||
Features
|
Features
|
||||||
@@ -22,8 +24,6 @@ The current version of GitBucket provides many features such as:
|
|||||||
- Account and group management with LDAP integration
|
- Account and group management with LDAP integration
|
||||||
- a Plug-in system
|
- 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/readme.md).
|
|
||||||
|
|
||||||
Installation
|
Installation
|
||||||
--------
|
--------
|
||||||
GitBucket requires **Java8**. You have to install it, if it is not already installed.
|
GitBucket requires **Java8**. You have to install it, if it is not already installed.
|
||||||
@@ -48,34 +48,35 @@ GitBucket has a plug-in system that allows extra functionality. Officially the f
|
|||||||
|
|
||||||
You can find more plugins made by the community at [GitBucket community plugins](https://gitbucket-plugins.github.io/).
|
You can find more plugins made by the community at [GitBucket community plugins](https://gitbucket-plugins.github.io/).
|
||||||
|
|
||||||
|
Building and Development
|
||||||
|
-----------
|
||||||
|
If you want to try the development version of GitBucket, or want to contribute to the project, please see the [Developer's Guide](https://github.com/gitbucket/gitbucket/blob/master/doc/readme.md).
|
||||||
|
It provides instructions on building from source and on setting up an IDE for debugging.
|
||||||
|
It also contains documentation of the core concepts used within the project.
|
||||||
|
|
||||||
Support
|
Support
|
||||||
--------
|
--------
|
||||||
|
|
||||||
- If you have any questions about GitBucket, see [Wiki](https://github.com/gitbucket/gitbucket/wiki) and check issues whether there is a same question or request in the past.
|
- If you have any questions about GitBucket, see [Wiki](https://github.com/gitbucket/gitbucket/wiki) and check issues whether there is a same question or request in the past.
|
||||||
- If you can't find same question and report, send it to [gitter room](https://gitter.im/gitbucket/gitbucket) before raising an issue.
|
- If you can't find same question and report, send it to our [Gitter room](https://gitter.im/gitbucket/gitbucket) before raising an issue.
|
||||||
- The highest priority of GitBucket is the ease of installation and API compatibility with GitHub, so your feature request might be rejected if they go against those principles.
|
- The highest priority of GitBucket is the ease of installation and API compatibility with GitHub, so your feature request might be rejected if they go against those principles.
|
||||||
|
|
||||||
What's New in 4.35.x
|
What's New in 4.37.x
|
||||||
-------------
|
-------------
|
||||||
### 4.35.2 - 30 Dec 2020
|
### 4.37.2 - 16 Jan 2022
|
||||||
- Upgrade gitbucket-notifications-plugin to 1.10.0
|
- Security fix
|
||||||
- Upgrade oauth2-oidc-sdk to 8.29.1 to solve dependency issue
|
|
||||||
|
|
||||||
### 4.35.1 - 29 Dec 2020
|
### 4.37.1 - 14 Dec 2021
|
||||||
|
- Update gist-plugin and notification-plugin
|
||||||
|
- Fix SSHCommand extension point for apache-sshd 2.x
|
||||||
|
|
||||||
- Fix database migration issue which happens if webhook is configured
|
### 4.37.0 - 11 Dec 2021
|
||||||
- Call push webhook when pull request is merged
|
- Enhance Git Reference APIs
|
||||||
- Show commit status at commits tab of pull request
|
- Add milestone data to issue list API
|
||||||
|
- Support "all" in issue list API
|
||||||
### 4.35.0 - 25 Dec 2020
|
- Support EDDSA in signed commit verification
|
||||||
|
- Support custom SSH url
|
||||||
- Editor and source viewer color theme
|
- Relax max passward length limitation
|
||||||
- Auto completion for issues and pull requests
|
- Relax max webhook url length limitation
|
||||||
- Upload image from clipboard
|
|
||||||
- Close multiple issues by commit comment
|
|
||||||
- Create pull request from online editor
|
|
||||||
- Milestone overview
|
|
||||||
- Commit status at various places
|
|
||||||
- WebAPI coverage improvements
|
|
||||||
|
|
||||||
See the [change log](CHANGELOG.md) for all of the updates.
|
See the [change log](CHANGELOG.md) for all of the updates.
|
||||||
|
|||||||
122
build.sbt
122
build.sbt
@@ -1,23 +1,21 @@
|
|||||||
import com.typesafe.sbt.license.{DepModuleInfo, LicenseInfo}
|
import com.typesafe.sbt.license.{DepModuleInfo, LicenseInfo}
|
||||||
import com.typesafe.sbt.pgp.PgpKeys._
|
import com.jsuereth.sbtpgp.PgpKeys._
|
||||||
|
|
||||||
val Organization = "io.github.gitbucket"
|
val Organization = "io.github.gitbucket"
|
||||||
val Name = "gitbucket"
|
val Name = "gitbucket"
|
||||||
val GitBucketVersion = "4.35.2"
|
val GitBucketVersion = "4.37.2"
|
||||||
val ScalatraVersion = "2.7.1"
|
val ScalatraVersion = "2.8.2"
|
||||||
val JettyVersion = "9.4.32.v20200930"
|
val JettyVersion = "9.4.44.v20210927"
|
||||||
val JgitVersion = "5.9.0.202009080501-r"
|
val JgitVersion = "5.13.0.202109080827-r"
|
||||||
|
|
||||||
lazy val root = (project in file("."))
|
lazy val root = (project in file("."))
|
||||||
.enablePlugins(SbtTwirl, ScalatraPlugin)
|
.enablePlugins(SbtTwirl, ScalatraPlugin)
|
||||||
.settings(
|
|
||||||
)
|
|
||||||
|
|
||||||
sourcesInBase := false
|
sourcesInBase := false
|
||||||
organization := Organization
|
organization := Organization
|
||||||
name := Name
|
name := Name
|
||||||
version := GitBucketVersion
|
version := GitBucketVersion
|
||||||
scalaVersion := "2.13.3"
|
scalaVersion := "2.13.8"
|
||||||
|
|
||||||
scalafmtOnCompile := true
|
scalafmtOnCompile := true
|
||||||
|
|
||||||
@@ -33,64 +31,80 @@ resolvers ++= Seq(
|
|||||||
libraryDependencies ++= Seq(
|
libraryDependencies ++= Seq(
|
||||||
"org.eclipse.jgit" % "org.eclipse.jgit.http.server" % JgitVersion,
|
"org.eclipse.jgit" % "org.eclipse.jgit.http.server" % JgitVersion,
|
||||||
"org.eclipse.jgit" % "org.eclipse.jgit.archive" % JgitVersion,
|
"org.eclipse.jgit" % "org.eclipse.jgit.archive" % JgitVersion,
|
||||||
"org.scalatra" %% "scalatra" % ScalatraVersion,
|
"org.scalatra" %% "scalatra" % ScalatraVersion cross CrossVersion.for3Use2_13,
|
||||||
"org.scalatra" %% "scalatra-json" % ScalatraVersion,
|
"org.scalatra" %% "scalatra-json" % ScalatraVersion cross CrossVersion.for3Use2_13,
|
||||||
"org.scalatra" %% "scalatra-forms" % ScalatraVersion,
|
"org.scalatra" %% "scalatra-forms" % ScalatraVersion cross CrossVersion.for3Use2_13,
|
||||||
"org.json4s" %% "json4s-jackson" % "3.6.10",
|
"org.json4s" %% "json4s-jackson" % "4.0.3" cross CrossVersion.for3Use2_13,
|
||||||
"commons-io" % "commons-io" % "2.8.0",
|
"commons-io" % "commons-io" % "2.11.0",
|
||||||
"io.github.gitbucket" % "solidbase" % "1.0.3",
|
"io.github.gitbucket" % "solidbase" % "1.0.3",
|
||||||
"io.github.gitbucket" % "markedj" % "1.0.16",
|
"io.github.gitbucket" % "markedj" % "1.0.16",
|
||||||
"org.apache.commons" % "commons-compress" % "1.20",
|
"org.apache.commons" % "commons-compress" % "1.21",
|
||||||
"org.apache.commons" % "commons-email" % "1.5",
|
"org.apache.commons" % "commons-email" % "1.5",
|
||||||
"commons-net" % "commons-net" % "3.7",
|
"commons-net" % "commons-net" % "3.8.0",
|
||||||
"org.apache.httpcomponents" % "httpclient" % "4.5.12",
|
"org.apache.httpcomponents" % "httpclient" % "4.5.13",
|
||||||
"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.sshd" % "apache-sshd" % "2.8.0" exclude ("org.slf4j", "slf4j-jdk14") exclude ("org.apache.sshd", "sshd-mina") exclude ("org.apache.sshd", "sshd-netty"),
|
||||||
"org.apache.tika" % "tika-core" % "1.24.1",
|
"org.apache.tika" % "tika-core" % "2.2.1",
|
||||||
"com.github.takezoe" %% "blocking-slick-32" % "0.0.12",
|
"com.github.takezoe" %% "blocking-slick-32" % "0.0.12" cross CrossVersion.for3Use2_13,
|
||||||
"com.novell.ldap" % "jldap" % "2009-10-07",
|
"com.novell.ldap" % "jldap" % "2009-10-07",
|
||||||
"com.h2database" % "h2" % "1.4.199",
|
"com.h2database" % "h2" % "1.4.199",
|
||||||
"org.mariadb.jdbc" % "mariadb-java-client" % "2.7.0",
|
"org.mariadb.jdbc" % "mariadb-java-client" % "2.7.4",
|
||||||
"org.postgresql" % "postgresql" % "42.2.6",
|
"org.postgresql" % "postgresql" % "42.3.1",
|
||||||
"ch.qos.logback" % "logback-classic" % "1.2.3",
|
"ch.qos.logback" % "logback-classic" % "1.2.10",
|
||||||
"com.zaxxer" % "HikariCP" % "3.4.5",
|
"com.zaxxer" % "HikariCP" % "4.0.3" exclude ("org.slf4j", "slf4j-api"),
|
||||||
"com.typesafe" % "config" % "1.4.0",
|
"com.typesafe" % "config" % "1.4.1",
|
||||||
"fr.brouillard.oss.security.xhub" % "xhub4j-core" % "1.1.0",
|
"fr.brouillard.oss.security.xhub" % "xhub4j-core" % "1.1.0",
|
||||||
"com.github.bkromhout" % "java-diff-utils" % "2.1.1",
|
"io.github.java-diff-utils" % "java-diff-utils" % "4.11",
|
||||||
"org.cache2k" % "cache2k-all" % "1.2.4.Final",
|
"org.cache2k" % "cache2k-all" % "1.6.0.Final",
|
||||||
"net.coobird" % "thumbnailator" % "0.4.12",
|
"net.coobird" % "thumbnailator" % "0.4.16",
|
||||||
"com.github.zafarkhaja" % "java-semver" % "0.9.0",
|
"com.github.zafarkhaja" % "java-semver" % "0.9.0",
|
||||||
"com.nimbusds" % "oauth2-oidc-sdk" % "8.29.1",
|
"com.nimbusds" % "oauth2-oidc-sdk" % "9.22",
|
||||||
"org.eclipse.jetty" % "jetty-webapp" % JettyVersion % "provided",
|
"org.eclipse.jetty" % "jetty-webapp" % JettyVersion % "provided",
|
||||||
"javax.servlet" % "javax.servlet-api" % "3.1.0" % "provided",
|
"javax.servlet" % "javax.servlet-api" % "3.1.0" % "provided",
|
||||||
"junit" % "junit" % "4.13" % "test",
|
"junit" % "junit" % "4.13.2" % "test",
|
||||||
"org.scalatra" %% "scalatra-scalatest" % ScalatraVersion % "test",
|
"org.scalatra" %% "scalatra-scalatest" % ScalatraVersion % "test" cross CrossVersion.for3Use2_13,
|
||||||
"org.mockito" % "mockito-core" % "3.3.3" % "test",
|
"org.mockito" % "mockito-core" % "4.2.0" % "test",
|
||||||
"com.dimafeng" %% "testcontainers-scala" % "0.37.0" % "test",
|
"com.dimafeng" %% "testcontainers-scala" % "0.39.12" % "test",
|
||||||
"org.testcontainers" % "mysql" % "1.14.3" % "test",
|
"org.testcontainers" % "mysql" % "1.16.2" % "test",
|
||||||
"org.testcontainers" % "postgresql" % "1.14.3" % "test",
|
"org.testcontainers" % "postgresql" % "1.16.2" % "test",
|
||||||
"net.i2p.crypto" % "eddsa" % "0.3.0",
|
"net.i2p.crypto" % "eddsa" % "0.3.0",
|
||||||
"is.tagomor.woothee" % "woothee-java" % "1.11.0",
|
"is.tagomor.woothee" % "woothee-java" % "1.11.0",
|
||||||
"org.ec4j.core" % "ec4j-core" % "0.0.3",
|
"org.ec4j.core" % "ec4j-core" % "0.3.0",
|
||||||
"org.kohsuke" % "github-api" % "1.116" % "test"
|
"org.kohsuke" % "github-api" % "1.301" % "test"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
libraryDependencies ~= {
|
||||||
|
_.map {
|
||||||
|
case x if x.name == "twirl-api" =>
|
||||||
|
x cross CrossVersion.for3Use2_13
|
||||||
|
case x =>
|
||||||
|
x
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Compiler settings
|
// Compiler settings
|
||||||
scalacOptions := Seq("-deprecation", "-language:postfixOps", "-opt:l:method", "-feature")
|
scalacOptions := Seq(
|
||||||
javacOptions in compile ++= Seq("-target", "8", "-source", "8")
|
"-deprecation",
|
||||||
javaOptions in Jetty += "-Dlogback.configurationFile=/logback-dev.xml"
|
"-language:postfixOps",
|
||||||
|
"-opt:l:method",
|
||||||
|
"-feature",
|
||||||
|
"-Wunused:imports",
|
||||||
|
"-Wconf:cat=unused&src=twirl/.*:s,cat=unused&src=scala/gitbucket/core/model/[^/]+\\.scala:s"
|
||||||
|
)
|
||||||
|
compile / javacOptions ++= Seq("-target", "8", "-source", "8")
|
||||||
|
Jetty / javaOptions += "-Dlogback.configurationFile=/logback-dev.xml"
|
||||||
|
|
||||||
// Test settings
|
// Test settings
|
||||||
//testOptions in Test += Tests.Argument("-l", "ExternalDBTest")
|
//testOptions in Test += Tests.Argument("-l", "ExternalDBTest")
|
||||||
javaOptions in Test += "-Dgitbucket.home=target/gitbucket_home_for_test"
|
Test / javaOptions += "-Dgitbucket.home=target/gitbucket_home_for_test"
|
||||||
testOptions in Test += Tests.Setup(() => new java.io.File("target/gitbucket_home_for_test").mkdir())
|
Test / testOptions += Tests.Setup(() => new java.io.File("target/gitbucket_home_for_test").mkdir())
|
||||||
fork in Test := true
|
Test / fork := true
|
||||||
|
|
||||||
// Packaging options
|
// Packaging options
|
||||||
packageOptions += Package.MainClass("JettyLauncher")
|
packageOptions += Package.MainClass("JettyLauncher")
|
||||||
|
|
||||||
// Assembly settings
|
// Assembly settings
|
||||||
test in assembly := {}
|
assembly / test := {}
|
||||||
assemblyMergeStrategy in assembly := {
|
assembly / assemblyMergeStrategy := {
|
||||||
case PathList("META-INF", xs @ _*) =>
|
case PathList("META-INF", xs @ _*) =>
|
||||||
(xs map { _.toLowerCase }) match {
|
(xs map { _.toLowerCase }) match {
|
||||||
case ("manifest.mf" :: Nil) => MergeStrategy.discard
|
case ("manifest.mf" :: Nil) => MergeStrategy.discard
|
||||||
@@ -122,9 +136,9 @@ libraryDependencies ++= Seq(
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Run package task before test to generate target/webapp for integration test
|
// Run package task before test to generate target/webapp for integration test
|
||||||
test in Test := {
|
Test / test := {
|
||||||
_root_.sbt.Keys.`package`.value
|
_root_.sbt.Keys.`package`.value
|
||||||
(test in Test).value
|
(Test / test).value
|
||||||
}
|
}
|
||||||
|
|
||||||
val executableKey = TaskKey[File]("executable")
|
val executableKey = TaskKey[File]("executable")
|
||||||
@@ -155,7 +169,7 @@ executableKey := {
|
|||||||
IO unzip (warFile, temp)
|
IO unzip (warFile, temp)
|
||||||
|
|
||||||
// include launcher classes
|
// include launcher classes
|
||||||
val classDir = (Keys.classDirectory in Compile).value
|
val classDir = (Compile / Keys.classDirectory).value
|
||||||
val launchClasses = Seq("JettyLauncher.class" /*, "HttpsSupportConnector.class" */ )
|
val launchClasses = Seq("JettyLauncher.class" /*, "HttpsSupportConnector.class" */ )
|
||||||
launchClasses foreach { name =>
|
launchClasses foreach { name =>
|
||||||
IO copyFile (classDir / name, temp / name)
|
IO copyFile (classDir / name, temp / name)
|
||||||
@@ -186,7 +200,7 @@ executableKey := {
|
|||||||
manifest.getMainAttributes put (AttrName.MANIFEST_VERSION, "1.0")
|
manifest.getMainAttributes put (AttrName.MANIFEST_VERSION, "1.0")
|
||||||
manifest.getMainAttributes put (AttrName.MAIN_CLASS, "JettyLauncher")
|
manifest.getMainAttributes put (AttrName.MAIN_CLASS, "JettyLauncher")
|
||||||
val outputFile = workDir / warName
|
val outputFile = workDir / warName
|
||||||
IO jar (contentMappings.map { case (file, path) => (file, path.toString) }, outputFile, manifest)
|
IO jar (contentMappings.map { case (file, path) => (file, path.toString) }, outputFile, manifest, None)
|
||||||
|
|
||||||
// generate checksums
|
// generate checksums
|
||||||
Seq(
|
Seq(
|
||||||
@@ -258,12 +272,7 @@ pomExtra := (
|
|||||||
</developers>
|
</developers>
|
||||||
)
|
)
|
||||||
|
|
||||||
licenseOverrides := {
|
Test / testOptions ++= {
|
||||||
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) {
|
if (scala.util.Properties.isWin) {
|
||||||
Seq(
|
Seq(
|
||||||
Tests.Exclude(
|
Tests.Exclude(
|
||||||
@@ -276,3 +285,8 @@ testOptions in Test ++= {
|
|||||||
Nil
|
Nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Jetty / javaOptions ++= Seq(
|
||||||
|
"-Xdebug",
|
||||||
|
"-Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=8000"
|
||||||
|
)
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ Authentication in Controller
|
|||||||
========
|
========
|
||||||
GitBucket provides many [authenticators](https://github.com/gitbucket/gitbucket/blob/master/src/main/scala/gitbucket/core/util/Authenticator.scala) to access controlling in the controller.
|
GitBucket provides many [authenticators](https://github.com/gitbucket/gitbucket/blob/master/src/main/scala/gitbucket/core/util/Authenticator.scala) to access controlling in the controller.
|
||||||
|
|
||||||
For example, in the case of `RepositoryViwerController`,
|
For example, in the case of `RepositoryViewerController`,
|
||||||
it references three authenticators: `ReadableUsersAuthenticator`, `ReferrerAuthenticator` and `CollaboratorsAuthenticator`.
|
it references three authenticators: `ReadableUsersAuthenticator`, `ReferrerAuthenticator` and `CollaboratorsAuthenticator`.
|
||||||
|
|
||||||
```scala
|
```scala
|
||||||
@@ -19,13 +19,13 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
|||||||
...
|
...
|
||||||
```
|
```
|
||||||
|
|
||||||
Authenticators provides a method to add guard to actions in the controller:
|
Authenticators provide a method to add guard to actions in the controller:
|
||||||
|
|
||||||
- `ReadableUsersAuthenticator` provides `readableUsersOnly` method
|
- `ReadableUsersAuthenticator` provides `readableUsersOnly` method
|
||||||
- `ReferrerAuthenticator` provides `referrersOnly` method
|
- `ReferrerAuthenticator` provides `referrersOnly` method
|
||||||
- `CollaboratorsAuthenticator` provides `collaboratorsOnly` method
|
- `CollaboratorsAuthenticator` provides `collaboratorsOnly` method
|
||||||
|
|
||||||
These methods are available in each actions as below:
|
These methods are available in each action as below:
|
||||||
|
|
||||||
```scala
|
```scala
|
||||||
// Allows only the repository owner (or manager for group repository) and administrators.
|
// Allows only the repository owner (or manager for group repository) and administrators.
|
||||||
@@ -38,7 +38,7 @@ get("/:owner/:repository/new/*")(collaboratorsOnly { repository =>
|
|||||||
...
|
...
|
||||||
})
|
})
|
||||||
|
|
||||||
// Allows only signed in users which can access the repository.
|
// Allows only signed-in users which can access the repository.
|
||||||
post("/:owner/:repository/commit/:id/comment/new", commentForm)(readableUsersOnly { (form, repository) =>
|
post("/:owner/:repository/commit/:id/comment/new", commentForm)(readableUsersOnly { (form, repository) =>
|
||||||
...
|
...
|
||||||
})
|
})
|
||||||
@@ -50,11 +50,11 @@ Currently, GitBucket provides below authenticators:
|
|||||||
|--------------------------|-----------------|--------------------------------------------------------------------------------------|
|
|--------------------------|-----------------|--------------------------------------------------------------------------------------|
|
||||||
|OneselfAuthenticator |oneselfOnly |Allows only oneself and administrators. |
|
|OneselfAuthenticator |oneselfOnly |Allows only oneself and administrators. |
|
||||||
|OwnerAuthenticator |ownerOnly |Allows only the repository owner and administrators. |
|
|OwnerAuthenticator |ownerOnly |Allows only the repository owner and administrators. |
|
||||||
|UsersAuthenticator |usersOnly |Allows only signed in users. |
|
|UsersAuthenticator |usersOnly |Allows only signed-in users. |
|
||||||
|AdminAuthenticator |adminOnly |Allows only administrators. |
|
|AdminAuthenticator |adminOnly |Allows only administrators. |
|
||||||
|CollaboratorsAuthenticator|collaboratorsOnly|Allows only collaborators and administrators. |
|
|CollaboratorsAuthenticator|collaboratorsOnly|Allows only collaborators and administrators. |
|
||||||
|ReferrerAuthenticator |referrersOnly |Allows only the repository owner (or manager for group repository) and administrators.|
|
|ReferrerAuthenticator |referrersOnly |Allows only the repository owner (or manager for group repository) and administrators.|
|
||||||
|ReadableUsersAuthenticator|readableUsersOnly|Allows only signed in users which can access the repository. |
|
|ReadableUsersAuthenticator|readableUsersOnly|Allows only signed-in users which can access the repository. |
|
||||||
|GroupManagerAuthenticator |managersOnly |Allows only the group managers. |
|
|GroupManagerAuthenticator |managersOnly |Allows only the group managers. |
|
||||||
|
|
||||||
Of course, if you make a new plugin, you can define a your own authenticator according to requirement in your plugin.
|
Of course, if you make a new plugin, you can implement your own authenticator according to requirement in your plugin.
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ Automatic Schema Updating
|
|||||||
========
|
========
|
||||||
GitBucket updates database schema automatically using [Solidbase](https://github.com/gitbucket/solidbase) in the first run after the upgrading.
|
GitBucket updates database schema automatically using [Solidbase](https://github.com/gitbucket/solidbase) in the first run after the upgrading.
|
||||||
|
|
||||||
To release a new version of GitBucket, add the version definition to the [gitbucket.core.GitBucketCoreModule](https://github.com/gitbucket/gitbucket/blob/master/src/main/scala/gitbucket/core/GitBucketCoreModule.scala) at first.
|
To release a new version of GitBucket, add the version definition to [gitbucket.core.GitBucketCoreModule](https://github.com/gitbucket/gitbucket/blob/master/src/main/scala/gitbucket/core/GitBucketCoreModule.scala) at first.
|
||||||
|
|
||||||
```scala
|
```scala
|
||||||
object GitBucketCoreModule extends Module("gitbucket-core",
|
object GitBucketCoreModule extends Module("gitbucket-core",
|
||||||
@@ -17,7 +17,7 @@ object GitBucketCoreModule extends Module("gitbucket-core",
|
|||||||
)
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
Next, add a XML file which updates database schema into [/src/main/resources/update/](https://github.com/gitbucket/gitbucket/tree/master/src/main/resources/update) with a filenane defined in `GitBucketCoreModule`.
|
Next, add a XML file which updates database schema into [/src/main/resources/update/](https://github.com/gitbucket/gitbucket/tree/master/src/main/resources/update) with a filename defined in `GitBucketCoreModule`.
|
||||||
|
|
||||||
```xml
|
```xml
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
@@ -31,9 +31,9 @@ Next, add a XML file which updates database schema into [/src/main/resources/upd
|
|||||||
</changeSet>
|
</changeSet>
|
||||||
```
|
```
|
||||||
|
|
||||||
Solidbase stores the current version to `VERSIONS` table and checks it at start-up. If the stored version differs from the actual version, it executes differences between the stored version and the actual version.
|
Solidbase stores the current version to `VERSIONS` table and checks it at start-up. If the stored version different from the actual version, it executes differences between the stored version and the actual version.
|
||||||
|
|
||||||
We can add the SQL file instead of the XML file using `SqlMigration`. It try to load a SQL file from classpath as following order:
|
We can add the SQL file instead of the XML file using `SqlMigration`. It tries to load a SQL file from classpath in the following order:
|
||||||
|
|
||||||
1. Specified path (if specified)
|
1. Specified path (if specified)
|
||||||
2. `${moduleId}_${version}_${database}.sql`
|
2. `${moduleId}_${version}_${database}.sql`
|
||||||
@@ -51,4 +51,4 @@ object GitBucketCoreModule extends Module("gitbucket-core",
|
|||||||
)
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
See more details [README of Solidbase](https://github.com/gitbucket/solidbase).
|
See more details at [README of Solidbase](https://github.com/gitbucket/solidbase).
|
||||||
|
|||||||
@@ -18,12 +18,12 @@ $ sbt ~jetty:start
|
|||||||
|
|
||||||
Then access `http://localhost:8080/` in your browser. The default administrator account is `root` and password is `root`.
|
Then access `http://localhost:8080/` in your browser. The default administrator account is `root` and password is `root`.
|
||||||
|
|
||||||
Source code modifications are detected and a reloaded happens automatically. You can modify the logging configuration by editing `src/main/resources/logback-dev.xml`.
|
Source code modifications are detected and a reloading happens automatically. You can modify the logging configuration by editing `src/main/resources/logback-dev.xml`.
|
||||||
|
|
||||||
Build war file
|
Build war file
|
||||||
--------
|
--------
|
||||||
|
|
||||||
To build war file, run the following command:
|
To build a war file, run the following command:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
$ sbt package
|
$ sbt package
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
sbt.version=1.4.6
|
sbt.version=1.6.1
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
scalacOptions ++= Seq("-unchecked", "-deprecation", "-feature")
|
scalacOptions ++= Seq("-unchecked", "-deprecation", "-feature")
|
||||||
|
|
||||||
addSbtPlugin("com.geirsson" % "sbt-scalafmt" % "1.5.1")
|
addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.6")
|
||||||
addSbtPlugin("com.typesafe.sbt" % "sbt-twirl" % "1.5.0")
|
addSbtPlugin("com.typesafe.sbt" % "sbt-twirl" % "1.5.1")
|
||||||
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.15.0")
|
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "1.1.0")
|
||||||
addSbtPlugin("org.scalatra.sbt" % "sbt-scalatra" % "1.0.4")
|
addSbtPlugin("org.scalatra.sbt" % "sbt-scalatra" % "1.0.4")
|
||||||
addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.1.2")
|
addSbtPlugin("com.github.sbt" % "sbt-pgp" % "2.1.2")
|
||||||
addSbtPlugin("com.typesafe.sbt" % "sbt-license-report" % "1.2.0")
|
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.9.3")
|
||||||
addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.6.1")
|
|
||||||
|
addDependencyTreePlugin
|
||||||
|
|||||||
@@ -1,51 +1,100 @@
|
|||||||
import org.eclipse.jetty.server.ConnectionFactory;
|
import org.eclipse.jetty.http.HttpVersion;
|
||||||
import org.eclipse.jetty.server.Connector;
|
|
||||||
import org.eclipse.jetty.server.Handler;
|
import org.eclipse.jetty.server.Handler;
|
||||||
|
import org.eclipse.jetty.server.HttpConfiguration;
|
||||||
import org.eclipse.jetty.server.HttpConnectionFactory;
|
import org.eclipse.jetty.server.HttpConnectionFactory;
|
||||||
|
import org.eclipse.jetty.server.SecureRequestCustomizer;
|
||||||
import org.eclipse.jetty.server.Server;
|
import org.eclipse.jetty.server.Server;
|
||||||
|
import org.eclipse.jetty.server.ServerConnector;
|
||||||
|
import org.eclipse.jetty.server.SslConnectionFactory;
|
||||||
|
import org.eclipse.jetty.server.handler.HandlerList;
|
||||||
|
import org.eclipse.jetty.server.handler.SecuredRedirectHandler;
|
||||||
import org.eclipse.jetty.server.handler.StatisticsHandler;
|
import org.eclipse.jetty.server.handler.StatisticsHandler;
|
||||||
import org.eclipse.jetty.server.session.DefaultSessionCache;
|
import org.eclipse.jetty.server.session.DefaultSessionCache;
|
||||||
import org.eclipse.jetty.server.session.FileSessionDataStore;
|
import org.eclipse.jetty.server.session.FileSessionDataStore;
|
||||||
import org.eclipse.jetty.server.session.SessionCache;
|
import org.eclipse.jetty.server.session.SessionCache;
|
||||||
import org.eclipse.jetty.server.session.SessionHandler;
|
import org.eclipse.jetty.server.session.SessionHandler;
|
||||||
|
import org.eclipse.jetty.util.ssl.SslContextFactory;
|
||||||
import org.eclipse.jetty.webapp.WebAppContext;
|
import org.eclipse.jetty.webapp.WebAppContext;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.net.InetAddress;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.net.InetSocketAddress;
|
|
||||||
import java.security.ProtectionDomain;
|
import java.security.ProtectionDomain;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import static java.util.function.Function.identity;
|
||||||
|
import static java.util.stream.Collectors.toSet;
|
||||||
|
|
||||||
public class JettyLauncher {
|
public class JettyLauncher {
|
||||||
|
|
||||||
|
private interface Defaults {
|
||||||
|
|
||||||
|
String CONNECTORS = "http";
|
||||||
|
String HOST = "0.0.0.0";
|
||||||
|
|
||||||
|
int HTTP_PORT = 8080;
|
||||||
|
int HTTPS_PORT = 8443;
|
||||||
|
|
||||||
|
boolean REDIRECT_HTTPS = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private interface Connectors {
|
||||||
|
|
||||||
|
String HTTP = "http";
|
||||||
|
String HTTPS = "https";
|
||||||
|
}
|
||||||
|
|
||||||
public static void main(String[] args) throws Exception {
|
public static void main(String[] args) throws Exception {
|
||||||
System.setProperty("java.awt.headless", "true");
|
System.setProperty("java.awt.headless", "true");
|
||||||
|
|
||||||
String host = null;
|
String connectors = getEnvironmentVariable("gitbucket.connectors");
|
||||||
String port = null;
|
String host = getEnvironmentVariable("gitbucket.host");
|
||||||
InetSocketAddress address = null;
|
String port = getEnvironmentVariable("gitbucket.port");
|
||||||
String contextPath = "/";
|
String securePort = getEnvironmentVariable("gitbucket.securePort");
|
||||||
String tmpDirPath="";
|
String keyStorePath = getEnvironmentVariable("gitbucket.keyStorePath");
|
||||||
boolean forceHttps = false;
|
String keyStorePassword = getEnvironmentVariable("gitbucket.keyStorePassword");
|
||||||
|
String keyManagerPassword = getEnvironmentVariable("gitbucket.keyManagerPassword");
|
||||||
|
String redirectHttps = getEnvironmentVariable("gitbucket.redirectHttps");
|
||||||
|
String contextPath = getEnvironmentVariable("gitbucket.prefix");
|
||||||
|
String tmpDirPath = getEnvironmentVariable("gitbucket.tempDir");
|
||||||
boolean saveSessions = false;
|
boolean saveSessions = false;
|
||||||
|
|
||||||
host = getEnvironmentVariable("gitbucket.host");
|
|
||||||
port = getEnvironmentVariable("gitbucket.port");
|
|
||||||
contextPath = getEnvironmentVariable("gitbucket.prefix");
|
|
||||||
tmpDirPath = getEnvironmentVariable("gitbucket.tempDir");
|
|
||||||
|
|
||||||
for(String arg: args) {
|
for(String arg: args) {
|
||||||
if(arg.equals("--save_sessions")) {
|
if(arg.equals("--save_sessions")) {
|
||||||
saveSessions = true;
|
saveSessions = true;
|
||||||
}
|
}
|
||||||
if(arg.startsWith("--") && arg.contains("=")) {
|
if(arg.startsWith("--") && arg.contains("=")) {
|
||||||
String[] dim = arg.split("=");
|
String[] dim = arg.split("=", 2);
|
||||||
if(dim.length >= 2) {
|
if(dim.length == 2) {
|
||||||
switch (dim[0]) {
|
switch (dim[0]) {
|
||||||
|
case "--connectors":
|
||||||
|
connectors = dim[1];
|
||||||
|
break;
|
||||||
case "--host":
|
case "--host":
|
||||||
host = dim[1];
|
host = dim[1];
|
||||||
break;
|
break;
|
||||||
case "--port":
|
case "--port":
|
||||||
port = dim[1];
|
port = dim[1];
|
||||||
break;
|
break;
|
||||||
|
case "--secure_port":
|
||||||
|
securePort = dim[1];
|
||||||
|
break;
|
||||||
|
case "--key_store_path":
|
||||||
|
keyStorePath = dim[1];
|
||||||
|
break;
|
||||||
|
case "--key_store_password":
|
||||||
|
keyStorePassword = dim[1];
|
||||||
|
break;
|
||||||
|
case "--key_manager_password":
|
||||||
|
keyManagerPassword = dim[1];
|
||||||
|
break;
|
||||||
|
case "--redirect_https":
|
||||||
|
redirectHttps = dim[1];
|
||||||
|
break;
|
||||||
case "--prefix":
|
case "--prefix":
|
||||||
contextPath = dim[1];
|
contextPath = dim[1];
|
||||||
break;
|
break;
|
||||||
@@ -67,38 +116,69 @@ public class JettyLauncher {
|
|||||||
contextPath = "/" + contextPath;
|
contextPath = "/" + contextPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(host != null) {
|
final String hostName = InetAddress.getByName(fallback(host, Defaults.HOST)).getHostName();
|
||||||
address = new InetSocketAddress(host, getPort(port));
|
|
||||||
} else {
|
final Server server = new Server();
|
||||||
address = new InetSocketAddress(getPort(port));
|
|
||||||
|
final Set<String> connectorsSet = Stream.of(fallback(connectors, Defaults.CONNECTORS)
|
||||||
|
.toLowerCase().split(",")).map(String::trim).collect(toSet());
|
||||||
|
|
||||||
|
final List<ServerConnector> connectorInstances = new ArrayList<>();
|
||||||
|
|
||||||
|
final HttpConfiguration httpConfig = new HttpConfiguration();
|
||||||
|
httpConfig.setSendServerVersion(false);
|
||||||
|
if (connectorsSet.contains(Connectors.HTTPS)) {
|
||||||
|
httpConfig.setSecurePort(fallback(securePort, Defaults.HTTPS_PORT, Integer::parseInt));
|
||||||
}
|
}
|
||||||
|
|
||||||
Server server = new Server(address);
|
if (connectorsSet.contains(Connectors.HTTP)) {
|
||||||
|
final ServerConnector connector = new ServerConnector(server, new HttpConnectionFactory(httpConfig));
|
||||||
|
connector.setHost(hostName);
|
||||||
|
connector.setPort(fallback(port, Defaults.HTTP_PORT, Integer::parseInt));
|
||||||
|
|
||||||
// SelectChannelConnector connector = new SelectChannelConnector();
|
connectorInstances.add(connector);
|
||||||
// if(host != null) {
|
|
||||||
// connector.setHost(host);
|
|
||||||
// }
|
|
||||||
// connector.setMaxIdleTime(1000 * 60 * 60);
|
|
||||||
// connector.setSoLingerTime(-1);
|
|
||||||
// connector.setPort(port);
|
|
||||||
// server.addConnector(connector);
|
|
||||||
|
|
||||||
// Disabling Server header
|
|
||||||
for (Connector connector : server.getConnectors()) {
|
|
||||||
for (ConnectionFactory factory : connector.getConnectionFactories()) {
|
|
||||||
if (factory instanceof HttpConnectionFactory) {
|
|
||||||
((HttpConnectionFactory) factory).getHttpConfiguration().setSendServerVersion(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (connectorsSet.contains(Connectors.HTTPS)) {
|
||||||
|
final SslContextFactory sslContextFactory = new SslContextFactory.Server();
|
||||||
|
|
||||||
|
sslContextFactory.setKeyStorePath(requireNonNull(keyStorePath,
|
||||||
|
"You must specify a path to an SSL keystore via the --key_store_path command line argument" +
|
||||||
|
" or GITBUCKET_KEYSTOREPATH environment variable."));
|
||||||
|
|
||||||
|
sslContextFactory.setKeyStorePassword(requireNonNull(keyStorePassword,
|
||||||
|
"You must specify a an SSL keystore password via the --key_store_password argument" +
|
||||||
|
" or GITBUCKET_KEYSTOREPASSWORD environment variable."));
|
||||||
|
|
||||||
|
sslContextFactory.setKeyManagerPassword(requireNonNull(keyManagerPassword,
|
||||||
|
"You must specify a key manager password via the --key_manager_password' argument" +
|
||||||
|
" or GITBUCKET_KEYMANAGERPASSWORD environment variable."));
|
||||||
|
|
||||||
|
final HttpConfiguration httpsConfig = new HttpConfiguration(httpConfig);
|
||||||
|
httpsConfig.addCustomizer(new SecureRequestCustomizer());
|
||||||
|
|
||||||
|
final ServerConnector connector = new ServerConnector(server,
|
||||||
|
new SslConnectionFactory(sslContextFactory, HttpVersion.HTTP_1_1.asString()),
|
||||||
|
new HttpConnectionFactory(httpsConfig));
|
||||||
|
|
||||||
|
connector.setHost(hostName);
|
||||||
|
connector.setPort(fallback(securePort, Defaults.HTTPS_PORT, Integer::parseInt));
|
||||||
|
|
||||||
|
connectorInstances.add(connector);
|
||||||
|
}
|
||||||
|
|
||||||
|
require(!connectorInstances.isEmpty(),
|
||||||
|
"No server connectors could be configured, please check your --connectors command line argument" +
|
||||||
|
" or GITBUCKET_CONNECTORS environment variable.");
|
||||||
|
|
||||||
|
server.setConnectors(connectorInstances.toArray(new ServerConnector[0]));
|
||||||
|
|
||||||
WebAppContext context = new WebAppContext();
|
WebAppContext context = new WebAppContext();
|
||||||
|
|
||||||
if(saveSessions) {
|
if(saveSessions) {
|
||||||
File sessDir = new File(getGitBucketHome(), "sessions");
|
File sessDir = new File(getGitBucketHome(), "sessions");
|
||||||
if(!sessDir.exists()){
|
if(!sessDir.exists()){
|
||||||
sessDir.mkdirs();
|
mkdir(sessDir);
|
||||||
}
|
}
|
||||||
SessionHandler sessions = context.getSessionHandler();
|
SessionHandler sessions = context.getSessionHandler();
|
||||||
SessionCache cache = new DefaultSessionCache(sessions);
|
SessionCache cache = new DefaultSessionCache(sessions);
|
||||||
@@ -112,7 +192,7 @@ public class JettyLauncher {
|
|||||||
if(tmpDirPath == null || tmpDirPath.equals("")){
|
if(tmpDirPath == null || tmpDirPath.equals("")){
|
||||||
tmpDir = new File(getGitBucketHome(), "tmp");
|
tmpDir = new File(getGitBucketHome(), "tmp");
|
||||||
if(!tmpDir.exists()){
|
if(!tmpDir.exists()){
|
||||||
tmpDir.mkdirs();
|
mkdir(tmpDir);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
tmpDir = new File(tmpDirPath);
|
tmpDir = new File(tmpDirPath);
|
||||||
@@ -136,13 +216,16 @@ public class JettyLauncher {
|
|||||||
context.setDescriptor(location.toExternalForm() + "/WEB-INF/web.xml");
|
context.setDescriptor(location.toExternalForm() + "/WEB-INF/web.xml");
|
||||||
context.setServer(server);
|
context.setServer(server);
|
||||||
context.setWar(location.toExternalForm());
|
context.setWar(location.toExternalForm());
|
||||||
if (forceHttps) {
|
|
||||||
context.setInitParameter("org.scalatra.ForceHttps", "true");
|
final HandlerList handlers = new HandlerList();
|
||||||
|
|
||||||
|
if (fallback(redirectHttps, Defaults.REDIRECT_HTTPS, Boolean::parseBoolean)) {
|
||||||
|
handlers.addHandler(new SecuredRedirectHandler());
|
||||||
}
|
}
|
||||||
|
|
||||||
Handler handler = addStatisticsHandler(context);
|
handlers.addHandler(addStatisticsHandler(context));
|
||||||
|
|
||||||
server.setHandler(handler);
|
server.setHandler(handlers);
|
||||||
server.setStopAtShutdown(true);
|
server.setStopAtShutdown(true);
|
||||||
server.setStopTimeout(7_000);
|
server.setStopTimeout(7_000);
|
||||||
server.start();
|
server.start();
|
||||||
@@ -170,11 +253,28 @@ public class JettyLauncher {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int getPort(String port){
|
private static <T, R> T fallback(R value, T defaultValue, Function<R, T> converter) {
|
||||||
if(port == null) {
|
return value == null ? defaultValue : converter.apply(value);
|
||||||
return 8080;
|
}
|
||||||
} else {
|
|
||||||
return Integer.parseInt(port);
|
private static <T> T fallback(T value, T defaultValue) {
|
||||||
|
return fallback(value, defaultValue, identity());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void require(boolean condition, String message) {
|
||||||
|
if (!condition) {
|
||||||
|
throw new IllegalArgumentException(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static <T> T requireNonNull(T value, String message) {
|
||||||
|
require(value != null, message);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void mkdir(File dir) {
|
||||||
|
if (!dir.mkdirs()) {
|
||||||
|
throw new RuntimeException("Unable to create directory: " + dir);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,15 +20,15 @@ public class PatchUtil {
|
|||||||
public static String apply(String source, String patch, FileHeader fh)
|
public static String apply(String source, String patch, FileHeader fh)
|
||||||
throws IOException, PatchApplyException {
|
throws IOException, PatchApplyException {
|
||||||
RawText rt = new RawText(source.getBytes("UTF-8"));
|
RawText rt = new RawText(source.getBytes("UTF-8"));
|
||||||
List<String> oldLines = new ArrayList<String>(rt.size());
|
List<String> oldLines = new ArrayList<>(rt.size());
|
||||||
for (int i = 0; i < rt.size(); i++)
|
for (int i = 0; i < rt.size(); i++)
|
||||||
oldLines.add(rt.getString(i));
|
oldLines.add(rt.getString(i));
|
||||||
List<String> newLines = new ArrayList<String>(oldLines);
|
List<String> newLines = new ArrayList<>(oldLines);
|
||||||
for (HunkHeader hh : fh.getHunks()) {
|
for (HunkHeader hh : fh.getHunks()) {
|
||||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||||
out.write(patch.getBytes("UTF-8"), hh.getStartOffset(), hh.getEndOffset() - hh.getStartOffset());
|
out.write(patch.getBytes("UTF-8"), hh.getStartOffset(), hh.getEndOffset() - hh.getStartOffset());
|
||||||
RawText hrt = new RawText(out.toByteArray());
|
RawText hrt = new RawText(out.toByteArray());
|
||||||
List<String> hunkLines = new ArrayList<String>(hrt.size());
|
List<String> hunkLines = new ArrayList<>(hrt.size());
|
||||||
for (int i = 0; i < hrt.size(); i++)
|
for (int i = 0; i < hrt.size(); i++)
|
||||||
hunkLines.add(hrt.getString(i));
|
hunkLines.add(hrt.getString(i));
|
||||||
int pos = 0;
|
int pos = 0;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
notifications:1.10.0
|
notifications:1.11.0
|
||||||
gist:4.20.0
|
gist:4.22.0
|
||||||
emoji:4.6.0
|
emoji:4.6.0
|
||||||
pages:1.9.0
|
pages:1.10.0
|
||||||
|
|||||||
6
src/main/resources/update/gitbucket-core_4.36.xml
Normal file
6
src/main/resources/update/gitbucket-core_4.36.xml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<changeSet>
|
||||||
|
<addColumn tableName="REPOSITORY">
|
||||||
|
<column name="SAFE_MODE" type="boolean" nullable="false" defaultValue="true"/>
|
||||||
|
</addColumn>
|
||||||
|
</changeSet>
|
||||||
7
src/main/resources/update/gitbucket-core_4.37.xml
Normal file
7
src/main/resources/update/gitbucket-core_4.37.xml
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<changeSet>
|
||||||
|
<dropForeignKeyConstraint constraintName="IDX_WEB_HOOK_EVENT_FK0" baseTableName="WEB_HOOK_EVENT"/>
|
||||||
|
<modifyDataType columnName="URL" newDataType="varchar(400)" tableName="WEB_HOOK_EVENT"/>
|
||||||
|
<modifyDataType columnName="URL" newDataType="varchar(400)" tableName="WEB_HOOK"/>
|
||||||
|
<addForeignKeyConstraint constraintName="IDX_WEB_HOOK_EVENT_FK0" baseTableName="WEB_HOOK_EVENT" baseColumnNames="USER_NAME, REPOSITORY_NAME, URL" referencedTableName="WEB_HOOK" referencedColumnNames="USER_NAME, REPOSITORY_NAME, URL" onDelete="CASCADE" onUpdate="CASCADE"/>
|
||||||
|
</changeSet>
|
||||||
@@ -2,7 +2,6 @@ import java.util.EnumSet
|
|||||||
import javax.servlet._
|
import javax.servlet._
|
||||||
|
|
||||||
import gitbucket.core.controller.{ReleaseController, _}
|
import gitbucket.core.controller.{ReleaseController, _}
|
||||||
import gitbucket.core.plugin.PluginRegistry
|
|
||||||
import gitbucket.core.service.SystemSettingsService
|
import gitbucket.core.service.SystemSettingsService
|
||||||
import gitbucket.core.servlet._
|
import gitbucket.core.servlet._
|
||||||
import gitbucket.core.util.Directory
|
import gitbucket.core.util.Directory
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package gitbucket.core
|
|||||||
import java.io.FileOutputStream
|
import java.io.FileOutputStream
|
||||||
import java.nio.charset.StandardCharsets
|
import java.nio.charset.StandardCharsets
|
||||||
import java.sql.Connection
|
import java.sql.Connection
|
||||||
import java.util
|
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
|
||||||
import gitbucket.core.model.Activity
|
import gitbucket.core.model.Activity
|
||||||
@@ -12,7 +11,7 @@ import gitbucket.core.util.JDBCUtil
|
|||||||
import io.github.gitbucket.solidbase.Solidbase
|
import io.github.gitbucket.solidbase.Solidbase
|
||||||
import io.github.gitbucket.solidbase.migration.{LiquibaseMigration, Migration, SqlMigration}
|
import io.github.gitbucket.solidbase.migration.{LiquibaseMigration, Migration, SqlMigration}
|
||||||
import io.github.gitbucket.solidbase.model.{Module, Version}
|
import io.github.gitbucket.solidbase.model.{Module, Version}
|
||||||
import org.json4s.NoTypeHints
|
import org.json4s.{Formats, NoTypeHints}
|
||||||
import org.json4s.jackson.Serialization
|
import org.json4s.jackson.Serialization
|
||||||
import org.json4s.jackson.Serialization.write
|
import org.json4s.jackson.Serialization.write
|
||||||
|
|
||||||
@@ -84,8 +83,8 @@ object GitBucketCoreModule
|
|||||||
new Version(
|
new Version(
|
||||||
"4.34.0",
|
"4.34.0",
|
||||||
new Migration() {
|
new Migration() {
|
||||||
override def migrate(moduleId: String, version: String, context: util.Map[String, AnyRef]): Unit = {
|
override def migrate(moduleId: String, version: String, context: java.util.Map[String, AnyRef]): Unit = {
|
||||||
implicit val formats = Serialization.formats(NoTypeHints)
|
implicit val formats: Formats = Serialization.formats(NoTypeHints)
|
||||||
import JDBCUtil._
|
import JDBCUtil._
|
||||||
|
|
||||||
val conn = context.get(Solidbase.CONNECTION).asInstanceOf[Connection]
|
val conn = context.get(Solidbase.CONNECTION).asInstanceOf[Connection]
|
||||||
@@ -117,4 +116,11 @@ object GitBucketCoreModule
|
|||||||
new Version("4.35.0", new LiquibaseMigration("update/gitbucket-core_4.35.xml")),
|
new Version("4.35.0", new LiquibaseMigration("update/gitbucket-core_4.35.xml")),
|
||||||
new Version("4.35.1"),
|
new Version("4.35.1"),
|
||||||
new Version("4.35.2"),
|
new Version("4.35.2"),
|
||||||
|
new Version("4.35.3"),
|
||||||
|
new Version("4.36.0", new LiquibaseMigration("update/gitbucket-core_4.36.xml")),
|
||||||
|
new Version("4.36.1"),
|
||||||
|
new Version("4.36.2"),
|
||||||
|
new Version("4.37.0", new LiquibaseMigration("update/gitbucket-core_4.37.xml")),
|
||||||
|
new Version("4.37.1"),
|
||||||
|
new Version("4.37.2")
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ object ApiBranchProtection {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
implicit val enforcementLevelSerializer = new CustomSerializer[EnforcementLevel](
|
implicit val enforcementLevelSerializer: CustomSerializer[EnforcementLevel] = new CustomSerializer[EnforcementLevel](
|
||||||
format =>
|
format =>
|
||||||
(
|
(
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ object ApiContents {
|
|||||||
"file",
|
"file",
|
||||||
fileInfo.name,
|
fileInfo.name,
|
||||||
fileInfo.path,
|
fileInfo.path,
|
||||||
fileInfo.commitId,
|
fileInfo.id.getName,
|
||||||
Some(Base64.getEncoder.encodeToString(arr)),
|
Some(Base64.getEncoder.encodeToString(arr)),
|
||||||
Some("base64")
|
Some("base64")
|
||||||
)(repositoryName)
|
)(repositoryName)
|
||||||
|
|||||||
@@ -17,7 +17,8 @@ case class ApiIssue(
|
|||||||
state: String,
|
state: String,
|
||||||
created_at: Date,
|
created_at: Date,
|
||||||
updated_at: Date,
|
updated_at: Date,
|
||||||
body: String
|
body: String,
|
||||||
|
milestone: Option[ApiMilestone]
|
||||||
)(repositoryName: RepositoryName, isPullRequest: Boolean) {
|
)(repositoryName: RepositoryName, isPullRequest: Boolean) {
|
||||||
val id = 0 // dummy id
|
val id = 0 // dummy id
|
||||||
val assignees = List(assignee).flatten
|
val assignees = List(assignee).flatten
|
||||||
@@ -43,7 +44,8 @@ object ApiIssue {
|
|||||||
repositoryName: RepositoryName,
|
repositoryName: RepositoryName,
|
||||||
user: ApiUser,
|
user: ApiUser,
|
||||||
assignee: Option[ApiUser],
|
assignee: Option[ApiUser],
|
||||||
labels: List[ApiLabel]
|
labels: List[ApiLabel],
|
||||||
|
milestone: Option[ApiMilestone]
|
||||||
): ApiIssue =
|
): ApiIssue =
|
||||||
ApiIssue(
|
ApiIssue(
|
||||||
number = issue.issueId,
|
number = issue.issueId,
|
||||||
@@ -51,6 +53,7 @@ object ApiIssue {
|
|||||||
user = user,
|
user = user,
|
||||||
assignee = assignee,
|
assignee = assignee,
|
||||||
labels = labels,
|
labels = labels,
|
||||||
|
milestone = milestone,
|
||||||
state = if (issue.closed) { "closed" } else { "open" },
|
state = if (issue.closed) { "closed" } else { "open" },
|
||||||
body = issue.content.getOrElse(""),
|
body = issue.content.getOrElse(""),
|
||||||
created_at = issue.registeredDate,
|
created_at = issue.registeredDate,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
package gitbucket.core.api
|
package gitbucket.core.api
|
||||||
|
|
||||||
import gitbucket.core.plugin.{PluginRegistry, PluginInfo}
|
import gitbucket.core.plugin.PluginInfo
|
||||||
|
|
||||||
case class ApiPlugin(
|
case class ApiPlugin(
|
||||||
id: String,
|
id: String,
|
||||||
|
|||||||
@@ -1,5 +1,49 @@
|
|||||||
package gitbucket.core.api
|
package gitbucket.core.api
|
||||||
|
|
||||||
case class ApiObject(sha: String)
|
import gitbucket.core.util.JGitUtil.TagInfo
|
||||||
|
import gitbucket.core.util.RepositoryName
|
||||||
|
import org.eclipse.jgit.lib.Ref
|
||||||
|
|
||||||
case class ApiRef(ref: String, `object`: ApiObject)
|
case class ApiRefCommit(
|
||||||
|
sha: String,
|
||||||
|
`type`: String,
|
||||||
|
url: ApiPath
|
||||||
|
)
|
||||||
|
|
||||||
|
case class ApiRef(
|
||||||
|
ref: String,
|
||||||
|
node_id: String = "",
|
||||||
|
url: ApiPath,
|
||||||
|
`object`: ApiRefCommit,
|
||||||
|
)
|
||||||
|
|
||||||
|
object ApiRef {
|
||||||
|
|
||||||
|
def fromRef(
|
||||||
|
repositoryName: RepositoryName,
|
||||||
|
ref: Ref
|
||||||
|
): ApiRef =
|
||||||
|
ApiRef(
|
||||||
|
ref = ref.getName,
|
||||||
|
url = ApiPath(s"/api/v3/repos/${repositoryName.fullName}/git/${ref.getName}"),
|
||||||
|
`object` = ApiRefCommit(
|
||||||
|
sha = ref.getObjectId.getName,
|
||||||
|
url = ApiPath(s"/api/v3/repos/${repositoryName.fullName}/git/commits/${ref.getObjectId.getName}"),
|
||||||
|
`type` = "commit"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
def fromTag(
|
||||||
|
repositoryName: RepositoryName,
|
||||||
|
tagInfo: TagInfo
|
||||||
|
): ApiRef =
|
||||||
|
ApiRef(
|
||||||
|
ref = s"refs/tags/${tagInfo.name}",
|
||||||
|
url = ApiPath(s"/api/v3/repos/${repositoryName.fullName}/git/refs/tags/${tagInfo.name}"),
|
||||||
|
`object` = ApiRefCommit(
|
||||||
|
sha = tagInfo.objectId,
|
||||||
|
url = ApiPath(s"/api/v3/repos/${repositoryName.fullName}/git/tags/${tagInfo.objectId}"), // TODO This URL is not yet available?
|
||||||
|
`type` = "tag"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,29 +0,0 @@
|
|||||||
package gitbucket.core.api
|
|
||||||
|
|
||||||
import gitbucket.core.util.RepositoryName
|
|
||||||
|
|
||||||
case class ApiTagCommit(
|
|
||||||
sha: String,
|
|
||||||
url: ApiPath
|
|
||||||
)
|
|
||||||
|
|
||||||
case class ApiTag(
|
|
||||||
name: String,
|
|
||||||
commit: ApiTagCommit,
|
|
||||||
zipball_url: ApiPath,
|
|
||||||
tarball_url: ApiPath
|
|
||||||
)
|
|
||||||
|
|
||||||
object ApiTag {
|
|
||||||
def apply(
|
|
||||||
tagName: String,
|
|
||||||
repositoryName: RepositoryName,
|
|
||||||
commitId: String
|
|
||||||
): ApiTag =
|
|
||||||
ApiTag(
|
|
||||||
name = tagName,
|
|
||||||
commit = ApiTagCommit(sha = commitId, url = ApiPath(s"/${repositoryName.fullName}/commits/${commitId}")),
|
|
||||||
zipball_url = ApiPath(s"/${repositoryName.fullName}/archive/${tagName}.zip"),
|
|
||||||
tarball_url = ApiPath(s"/${repositoryName.fullName}/archive/${tagName}.tar.gz")
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,8 +1,6 @@
|
|||||||
package gitbucket.core.api
|
package gitbucket.core.api
|
||||||
|
|
||||||
import gitbucket.core.model.Profile.{RepositoryWebHookEvents, RepositoryWebHooks}
|
|
||||||
import gitbucket.core.model.{RepositoryWebHook, WebHook}
|
import gitbucket.core.model.{RepositoryWebHook, WebHook}
|
||||||
import gitbucket.core.util.RepositoryName
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* https://docs.github.com/en/rest/reference/repos#webhooks
|
* https://docs.github.com/en/rest/reference/repos#webhooks
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
|||||||
|
|
||||||
val newForm = mapping(
|
val newForm = mapping(
|
||||||
"userName" -> trim(label("User name", text(required, maxlength(100), identifier, uniqueUserName, reservedNames))),
|
"userName" -> trim(label("User name", text(required, maxlength(100), identifier, uniqueUserName, reservedNames))),
|
||||||
"password" -> trim(label("Password", text(required, maxlength(20)))),
|
"password" -> trim(label("Password", text(required, maxlength(40)))),
|
||||||
"fullName" -> trim(label("Full Name", text(required, maxlength(100)))),
|
"fullName" -> trim(label("Full Name", text(required, maxlength(100)))),
|
||||||
"mailAddress" -> trim(label("Mail Address", text(required, maxlength(100), uniqueMailAddress()))),
|
"mailAddress" -> trim(label("Mail Address", text(required, maxlength(100), uniqueMailAddress()))),
|
||||||
"extraMailAddresses" -> list(
|
"extraMailAddresses" -> list(
|
||||||
@@ -98,7 +98,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
|||||||
)(AccountNewForm.apply)
|
)(AccountNewForm.apply)
|
||||||
|
|
||||||
val editForm = mapping(
|
val editForm = mapping(
|
||||||
"password" -> trim(label("Password", optional(text(maxlength(20))))),
|
"password" -> trim(label("Password", optional(text(maxlength(40))))),
|
||||||
"fullName" -> trim(label("Full Name", text(required, maxlength(100)))),
|
"fullName" -> trim(label("Full Name", text(required, maxlength(100)))),
|
||||||
"mailAddress" -> trim(label("Mail Address", text(required, maxlength(100), uniqueMailAddress("userName")))),
|
"mailAddress" -> trim(label("Mail Address", text(required, maxlength(100), uniqueMailAddress("userName")))),
|
||||||
"extraMailAddresses" -> list(
|
"extraMailAddresses" -> list(
|
||||||
@@ -434,7 +434,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
|||||||
|
|
||||||
post("/:userName/_personalToken", personalTokenForm)(oneselfOnly { form =>
|
post("/:userName/_personalToken", personalTokenForm)(oneselfOnly { form =>
|
||||||
val userName = params("userName")
|
val userName = params("userName")
|
||||||
getAccountByUserName(userName).map { x =>
|
getAccountByUserName(userName).foreach { x =>
|
||||||
val (tokenId, token) = generateAccessToken(userName, form.note)
|
val (tokenId, token) = generateAccessToken(userName, form.note)
|
||||||
flash.update("generatedToken", (tokenId, token))
|
flash.update("generatedToken", (tokenId, token))
|
||||||
}
|
}
|
||||||
@@ -629,7 +629,9 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get("/groups/new")(usersOnly {
|
get("/groups/new")(usersOnly {
|
||||||
html.creategroup(List(GroupMember("", context.loginAccount.get.userName, true)))
|
context.withLoginAccount { loginAccount =>
|
||||||
|
html.creategroup(List(GroupMember("", loginAccount.userName, true)))
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
post("/groups/new", newGroupForm)(usersOnly { form =>
|
post("/groups/new", newGroupForm)(usersOnly { form =>
|
||||||
@@ -650,22 +652,20 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
|||||||
})
|
})
|
||||||
|
|
||||||
get("/:groupName/_editgroup")(managersOnly {
|
get("/:groupName/_editgroup")(managersOnly {
|
||||||
defining(params("groupName")) { groupName =>
|
val groupName = params("groupName")
|
||||||
getAccountByUserName(groupName, true).map { account =>
|
getAccountByUserName(groupName, true).map { account =>
|
||||||
html.editgroup(account, getGroupMembers(groupName), flash.get("info"))
|
html.editgroup(account, getGroupMembers(groupName), flash.get("info"))
|
||||||
} getOrElse NotFound()
|
} getOrElse NotFound()
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
get("/:groupName/_deletegroup")(managersOnly {
|
get("/:groupName/_deletegroup")(managersOnly {
|
||||||
defining(params("groupName")) {
|
val groupName = params("groupName")
|
||||||
groupName =>
|
// Remove from GROUP_MEMBER
|
||||||
// Remove from GROUP_MEMBER
|
updateGroupMembers(groupName, Nil)
|
||||||
updateGroupMembers(groupName, Nil)
|
// Disable group
|
||||||
// Disable group
|
getAccountByUserName(groupName, false).foreach { account =>
|
||||||
getAccountByUserName(groupName, false).foreach { account =>
|
updateGroup(groupName, account.description, account.url, true)
|
||||||
updateGroup(groupName, account.description, account.url, true)
|
}
|
||||||
}
|
|
||||||
// // Remove repositories
|
// // Remove repositories
|
||||||
// getRepositoryNamesOfUser(groupName).foreach { repositoryName =>
|
// getRepositoryNamesOfUser(groupName).foreach { repositoryName =>
|
||||||
// deleteRepository(groupName, repositoryName)
|
// deleteRepository(groupName, repositoryName)
|
||||||
@@ -673,28 +673,25 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
|||||||
// FileUtils.deleteDirectory(getWikiRepositoryDir(groupName, repositoryName))
|
// FileUtils.deleteDirectory(getWikiRepositoryDir(groupName, repositoryName))
|
||||||
// FileUtils.deleteDirectory(getTemporaryDir(groupName, repositoryName))
|
// FileUtils.deleteDirectory(getTemporaryDir(groupName, repositoryName))
|
||||||
// }
|
// }
|
||||||
}
|
|
||||||
redirect("/")
|
redirect("/")
|
||||||
})
|
})
|
||||||
|
|
||||||
post("/:groupName/_editgroup", editGroupForm)(managersOnly { form =>
|
post("/:groupName/_editgroup", editGroupForm)(managersOnly { form =>
|
||||||
defining(
|
val groupName = params("groupName")
|
||||||
params("groupName"),
|
val members = form.members
|
||||||
form.members
|
.split(",")
|
||||||
.split(",")
|
.map {
|
||||||
.map {
|
_.split(":") match {
|
||||||
_.split(":") match {
|
case Array(userName, isManager) => (userName, isManager.toBoolean)
|
||||||
case Array(userName, isManager) => (userName, isManager.toBoolean)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
.toList
|
}
|
||||||
) {
|
.toList
|
||||||
case (groupName, members) =>
|
|
||||||
getAccountByUserName(groupName, true).map { account =>
|
|
||||||
updateGroup(groupName, form.description, form.url, false)
|
|
||||||
|
|
||||||
// Update GROUP_MEMBER
|
getAccountByUserName(groupName, true).map { account =>
|
||||||
updateGroupMembers(form.groupName, members)
|
updateGroup(groupName, form.description, form.url, false)
|
||||||
|
|
||||||
|
// Update GROUP_MEMBER
|
||||||
|
updateGroupMembers(form.groupName, members)
|
||||||
// // Update COLLABORATOR for group repositories
|
// // Update COLLABORATOR for group repositories
|
||||||
// getRepositoryNamesOfUser(form.groupName).foreach { repositoryName =>
|
// getRepositoryNamesOfUser(form.groupName).foreach { repositoryName =>
|
||||||
// removeCollaborators(form.groupName, repositoryName)
|
// removeCollaborators(form.groupName, repositoryName)
|
||||||
@@ -703,87 +700,104 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
|||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
|
||||||
updateImage(form.groupName, form.fileId, form.clearImage)
|
updateImage(form.groupName, form.fileId, form.clearImage)
|
||||||
|
|
||||||
flash.update("info", "Account information has been updated.")
|
flash.update("info", "Account information has been updated.")
|
||||||
redirect(s"/${groupName}/_editgroup")
|
redirect(s"/${groupName}/_editgroup")
|
||||||
|
|
||||||
} getOrElse NotFound()
|
} getOrElse NotFound()
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Show the new repository form.
|
* Show the new repository form.
|
||||||
*/
|
*/
|
||||||
get("/new")(usersOnly {
|
get("/new")(usersOnly {
|
||||||
html.newrepo(getGroupsByUserName(context.loginAccount.get.userName), context.settings.isCreateRepoOptionPublic)
|
context.withLoginAccount { loginAccount =>
|
||||||
|
html.newrepo(getGroupsByUserName(loginAccount.userName), context.settings.isCreateRepoOptionPublic)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create new repository.
|
* Create new repository.
|
||||||
*/
|
*/
|
||||||
post("/new", newRepositoryForm)(usersOnly { form =>
|
post("/new", newRepositoryForm)(usersOnly { form =>
|
||||||
if (context.settings.repositoryOperation.create || context.loginAccount.get.isAdmin) {
|
context.withLoginAccount {
|
||||||
LockUtil.lock(s"${form.owner}/${form.name}") {
|
loginAccount =>
|
||||||
if (getRepository(form.owner, form.name).isEmpty) {
|
if (context.settings.repositoryOperation.create || loginAccount.isAdmin) {
|
||||||
createRepository(
|
LockUtil.lock(s"${form.owner}/${form.name}") {
|
||||||
context.loginAccount.get,
|
if (getRepository(form.owner, form.name).isDefined) {
|
||||||
form.owner,
|
// redirect to the repository if repository already exists
|
||||||
form.name,
|
redirect(s"/${form.owner}/${form.name}")
|
||||||
form.description,
|
} else if (!canCreateRepository(form.owner, loginAccount)) {
|
||||||
form.isPrivate,
|
// Permission error
|
||||||
form.initOption,
|
Forbidden()
|
||||||
form.sourceUrl
|
} else {
|
||||||
)
|
// create repository asynchronously
|
||||||
}
|
createRepository(
|
||||||
}
|
loginAccount,
|
||||||
// redirect to the repository
|
form.owner,
|
||||||
redirect(s"/${form.owner}/${form.name}")
|
form.name,
|
||||||
} else Forbidden()
|
form.description,
|
||||||
|
form.isPrivate,
|
||||||
|
form.initOption,
|
||||||
|
form.sourceUrl
|
||||||
|
)
|
||||||
|
// redirect to the repository
|
||||||
|
redirect(s"/${form.owner}/${form.name}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else Forbidden()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
get("/:owner/:repository/fork")(readableUsersOnly { repository =>
|
get("/:owner/:repository/fork")(readableUsersOnly { repository =>
|
||||||
val loginAccount = context.loginAccount.get
|
context.withLoginAccount {
|
||||||
if (repository.repository.options.allowFork && (context.settings.repositoryOperation.fork || loginAccount.isAdmin)) {
|
loginAccount =>
|
||||||
val loginUserName = loginAccount.userName
|
if (repository.repository.options.allowFork && (context.settings.repositoryOperation.fork || loginAccount.isAdmin)) {
|
||||||
val groups = getGroupsByUserName(loginUserName)
|
val loginUserName = loginAccount.userName
|
||||||
groups match {
|
val groups = getGroupsByUserName(loginUserName)
|
||||||
case _: List[String] =>
|
groups match {
|
||||||
val managerPermissions = groups.map { group =>
|
case _: List[String] =>
|
||||||
val members = getGroupMembers(group)
|
val managerPermissions = groups.map { group =>
|
||||||
context.loginAccount.exists(
|
val members = getGroupMembers(group)
|
||||||
x =>
|
context.loginAccount.exists(
|
||||||
members.exists { member =>
|
x =>
|
||||||
member.userName == x.userName && member.isManager
|
members.exists { member =>
|
||||||
|
member.userName == x.userName && member.isManager
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
)
|
helper.html.forkrepository(
|
||||||
|
repository,
|
||||||
|
(groups zip managerPermissions).sortBy(_._1)
|
||||||
|
)
|
||||||
|
case _ => redirect(s"/${loginUserName}")
|
||||||
}
|
}
|
||||||
helper.html.forkrepository(
|
} else BadRequest()
|
||||||
repository,
|
}
|
||||||
(groups zip managerPermissions).sortBy(_._1)
|
|
||||||
)
|
|
||||||
case _ => redirect(s"/${loginUserName}")
|
|
||||||
}
|
|
||||||
} else BadRequest()
|
|
||||||
})
|
})
|
||||||
|
|
||||||
post("/:owner/:repository/fork", accountForm)(readableUsersOnly { (form, repository) =>
|
post("/:owner/:repository/fork", accountForm)(readableUsersOnly { (form, repository) =>
|
||||||
val loginAccount = context.loginAccount.get
|
context.withLoginAccount {
|
||||||
if (repository.repository.options.allowFork && (context.settings.repositoryOperation.fork || loginAccount.isAdmin)) {
|
loginAccount =>
|
||||||
val loginUserName = loginAccount.userName
|
if (repository.repository.options.allowFork && (context.settings.repositoryOperation.fork || loginAccount.isAdmin)) {
|
||||||
val accountName = form.accountName
|
val loginUserName = loginAccount.userName
|
||||||
|
val accountName = form.accountName
|
||||||
|
|
||||||
if (getRepository(accountName, repository.name).isDefined ||
|
if (getRepository(accountName, repository.name).isDefined) {
|
||||||
(accountName != loginUserName && !getGroupsByUserName(loginUserName).contains(accountName))) {
|
// redirect to the repository if repository already exists
|
||||||
// redirect to the repository if repository already exists
|
redirect(s"/${accountName}/${repository.name}")
|
||||||
redirect(s"/${accountName}/${repository.name}")
|
} else if (!canCreateRepository(accountName, loginAccount)) {
|
||||||
} else {
|
// Permission error
|
||||||
// fork repository asynchronously
|
Forbidden()
|
||||||
forkRepository(accountName, repository, loginUserName)
|
} else {
|
||||||
// redirect to the repository
|
// fork repository asynchronously
|
||||||
redirect(s"/${accountName}/${repository.name}")
|
forkRepository(accountName, repository, loginUserName)
|
||||||
}
|
// redirect to the repository
|
||||||
} else Forbidden()
|
redirect(s"/${accountName}/${repository.name}")
|
||||||
|
}
|
||||||
|
} else Forbidden()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
private def existsAccount: Constraint = new Constraint() {
|
private def existsAccount: Constraint = new Constraint() {
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import org.eclipse.jgit.revwalk.RevCommit
|
|||||||
import org.eclipse.jgit.treewalk._
|
import org.eclipse.jgit.treewalk._
|
||||||
import org.apache.commons.io.IOUtils
|
import org.apache.commons.io.IOUtils
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
|
import org.json4s.Formats
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides generic features for controller implementations.
|
* Provides generic features for controller implementations.
|
||||||
@@ -43,7 +44,7 @@ abstract class ControllerBase
|
|||||||
|
|
||||||
private val logger = LoggerFactory.getLogger(getClass)
|
private val logger = LoggerFactory.getLogger(getClass)
|
||||||
|
|
||||||
implicit val jsonFormats = gitbucket.core.api.JsonFormat.jsonFormats
|
implicit val jsonFormats: Formats = gitbucket.core.api.JsonFormat.jsonFormats
|
||||||
|
|
||||||
before("/api/v3/*") {
|
before("/api/v3/*") {
|
||||||
contentType = formats("json")
|
contentType = formats("json")
|
||||||
@@ -157,11 +158,8 @@ abstract class ControllerBase
|
|||||||
org.scalatra.Unauthorized(
|
org.scalatra.Unauthorized(
|
||||||
redirect(
|
redirect(
|
||||||
"/signin?redirect=" + StringUtil.urlEncode(
|
"/signin?redirect=" + StringUtil.urlEncode(
|
||||||
defining(request.getQueryString) { queryString =>
|
request.getRequestURI
|
||||||
request.getRequestURI.substring(request.getContextPath.length) + (if (queryString != null)
|
.substring(request.getContextPath.length) + Option(request.getQueryString).map("?" + _).getOrElse("")
|
||||||
"?" + queryString
|
|
||||||
else "")
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -255,7 +253,7 @@ abstract class ControllerBase
|
|||||||
repository: RepositoryService.RepositoryInfo
|
repository: RepositoryService.RepositoryInfo
|
||||||
): Unit = {
|
): Unit = {
|
||||||
JGitUtil.getObjectLoaderFromId(git, objectId) { loader =>
|
JGitUtil.getObjectLoaderFromId(git, objectId) { loader =>
|
||||||
contentType = FileUtil.getSafeMimeType(path)
|
contentType = FileUtil.getSafeMimeType(path, repository.repository.options.safeMode)
|
||||||
|
|
||||||
if (loader.isLarge) {
|
if (loader.isLarge) {
|
||||||
response.setContentLength(loader.getSize.toInt)
|
response.setContentLength(loader.getSize.toInt)
|
||||||
@@ -302,20 +300,27 @@ case class Context(
|
|||||||
}
|
}
|
||||||
val sidebarCollapse = request.getSession.getAttribute("sidebar-collapse") != null
|
val sidebarCollapse = request.getSession.getAttribute("sidebar-collapse") != null
|
||||||
|
|
||||||
|
def withLoginAccount(f: Account => Any): Any = {
|
||||||
|
loginAccount match {
|
||||||
|
case Some(loginAccount) => f(loginAccount)
|
||||||
|
case None => Unauthorized()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get object from cache.
|
* Get object from cache.
|
||||||
*
|
*
|
||||||
* If object has not been cached with the specified key then retrieves by given action.
|
* If object has not been cached with the specified key then retrieves by given action.
|
||||||
* Cached object are available during a request.
|
* Cached object are available during a request.
|
||||||
*/
|
*/
|
||||||
def cache[A](key: String)(action: => A): A =
|
def cache[A](key: String)(action: => A): A = {
|
||||||
defining(Keys.Request.Cache(key)) { cacheKey =>
|
val cacheKey = Keys.Request.Cache(key)
|
||||||
Option(request.getAttribute(cacheKey).asInstanceOf[A]).getOrElse {
|
Option(request.getAttribute(cacheKey).asInstanceOf[A]).getOrElse {
|
||||||
val newObject = action
|
val newObject = action
|
||||||
request.setAttribute(cacheKey, newObject)
|
request.setAttribute(cacheKey, newObject)
|
||||||
newObject
|
newObject
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package gitbucket.core.controller
|
package gitbucket.core.controller
|
||||||
|
|
||||||
import gitbucket.core.dashboard.html
|
import gitbucket.core.dashboard.html
|
||||||
|
import gitbucket.core.model.Account
|
||||||
import gitbucket.core.service._
|
import gitbucket.core.service._
|
||||||
import gitbucket.core.util.{Keys, UsersAuthenticator}
|
import gitbucket.core.util.{Keys, UsersAuthenticator}
|
||||||
import gitbucket.core.util.Implicits._
|
import gitbucket.core.util.Implicits._
|
||||||
@@ -34,45 +35,63 @@ trait DashboardControllerBase extends ControllerBase {
|
|||||||
with UsersAuthenticator =>
|
with UsersAuthenticator =>
|
||||||
|
|
||||||
get("/dashboard/repos")(usersOnly {
|
get("/dashboard/repos")(usersOnly {
|
||||||
val repos = getVisibleRepositories(
|
context.withLoginAccount { loginAccount =>
|
||||||
context.loginAccount,
|
val repos = getVisibleRepositories(
|
||||||
None,
|
context.loginAccount,
|
||||||
withoutPhysicalInfo = true,
|
None,
|
||||||
limit = context.settings.limitVisibleRepositories
|
withoutPhysicalInfo = true,
|
||||||
)
|
limit = context.settings.limitVisibleRepositories
|
||||||
html.repos(getGroupNames(context.loginAccount.get.userName), repos, repos)
|
)
|
||||||
|
html.repos(getGroupNames(loginAccount.userName), repos, repos)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
get("/dashboard/issues")(usersOnly {
|
get("/dashboard/issues")(usersOnly {
|
||||||
searchIssues("created_by")
|
context.withLoginAccount { loginAccount =>
|
||||||
|
searchIssues(loginAccount, "created_by")
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
get("/dashboard/issues/assigned")(usersOnly {
|
get("/dashboard/issues/assigned")(usersOnly {
|
||||||
searchIssues("assigned")
|
context.withLoginAccount { loginAccount =>
|
||||||
|
searchIssues(loginAccount, "assigned")
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
get("/dashboard/issues/created_by")(usersOnly {
|
get("/dashboard/issues/created_by")(usersOnly {
|
||||||
searchIssues("created_by")
|
context.withLoginAccount { loginAccount =>
|
||||||
|
searchIssues(loginAccount, "created_by")
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
get("/dashboard/issues/mentioned")(usersOnly {
|
get("/dashboard/issues/mentioned")(usersOnly {
|
||||||
searchIssues("mentioned")
|
context.withLoginAccount { loginAccount =>
|
||||||
|
searchIssues(loginAccount, "mentioned")
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
get("/dashboard/pulls")(usersOnly {
|
get("/dashboard/pulls")(usersOnly {
|
||||||
searchPullRequests("created_by")
|
context.withLoginAccount { loginAccount =>
|
||||||
|
searchPullRequests(loginAccount, "created_by")
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
get("/dashboard/pulls/created_by")(usersOnly {
|
get("/dashboard/pulls/created_by")(usersOnly {
|
||||||
searchPullRequests("created_by")
|
context.withLoginAccount { loginAccount =>
|
||||||
|
searchPullRequests(loginAccount, "created_by")
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
get("/dashboard/pulls/assigned")(usersOnly {
|
get("/dashboard/pulls/assigned")(usersOnly {
|
||||||
searchPullRequests("assigned")
|
context.withLoginAccount { loginAccount =>
|
||||||
|
searchPullRequests(loginAccount, "assigned")
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
get("/dashboard/pulls/mentioned")(usersOnly {
|
get("/dashboard/pulls/mentioned")(usersOnly {
|
||||||
searchPullRequests("mentioned")
|
context.withLoginAccount { loginAccount =>
|
||||||
|
searchPullRequests(loginAccount, "mentioned")
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
private def getOrCreateCondition(key: String, filter: String, userName: String) = {
|
private def getOrCreateCondition(key: String, filter: String, userName: String) = {
|
||||||
@@ -85,10 +104,10 @@ trait DashboardControllerBase extends ControllerBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private def searchIssues(filter: String) = {
|
private def searchIssues(loginAccount: Account, filter: String) = {
|
||||||
import IssuesService._
|
import IssuesService._
|
||||||
|
|
||||||
val userName = context.loginAccount.get.userName
|
val userName = loginAccount.userName
|
||||||
val condition = getOrCreateCondition(Keys.Session.DashboardIssues, filter, userName)
|
val condition = getOrCreateCondition(Keys.Session.DashboardIssues, filter, userName)
|
||||||
val userRepos = getUserRepositories(userName, true).map(repo => repo.owner -> repo.name)
|
val userRepos = getUserRepositories(userName, true).map(repo => repo.owner -> repo.name)
|
||||||
val page = IssueSearchCondition.page(request)
|
val page = IssueSearchCondition.page(request)
|
||||||
@@ -115,11 +134,11 @@ trait DashboardControllerBase extends ControllerBase {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private def searchPullRequests(filter: String) = {
|
private def searchPullRequests(loginAccount: Account, filter: String) = {
|
||||||
import IssuesService._
|
import IssuesService._
|
||||||
import PullRequestService._
|
import PullRequestService._
|
||||||
|
|
||||||
val userName = context.loginAccount.get.userName
|
val userName = loginAccount.userName
|
||||||
val condition = getOrCreateCondition(Keys.Session.DashboardPulls, filter, userName)
|
val condition = getOrCreateCondition(Keys.Session.DashboardPulls, filter, userName)
|
||||||
val allRepos = getAllRepositories(userName)
|
val allRepos = getAllRepositories(userName)
|
||||||
val page = IssueSearchCondition.page(request)
|
val page = IssueSearchCondition.page(request)
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import gitbucket.core.model.Account
|
|||||||
import gitbucket.core.service.{AccountService, ReleaseService, RepositoryService}
|
import gitbucket.core.service.{AccountService, ReleaseService, RepositoryService}
|
||||||
import gitbucket.core.servlet.Database
|
import gitbucket.core.servlet.Database
|
||||||
import gitbucket.core.util._
|
import gitbucket.core.util._
|
||||||
import gitbucket.core.util.SyntaxSugars._
|
|
||||||
import gitbucket.core.util.Directory._
|
import gitbucket.core.util.Directory._
|
||||||
import gitbucket.core.util.Implicits._
|
import gitbucket.core.util.Implicits._
|
||||||
import org.eclipse.jgit.api.Git
|
import org.eclipse.jgit.api.Git
|
||||||
@@ -18,6 +17,7 @@ import org.apache.commons.io.{FileUtils, IOUtils}
|
|||||||
|
|
||||||
import scala.util.Using
|
import scala.util.Using
|
||||||
import gitbucket.core.service.SystemSettingsService
|
import gitbucket.core.service.SystemSettingsService
|
||||||
|
import slick.jdbc.JdbcBackend.Session
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides Ajax based file upload functionality.
|
* Provides Ajax based file upload functionality.
|
||||||
@@ -40,7 +40,7 @@ class FileUploadController
|
|||||||
.writeByteArrayToFile(new File(getTemporaryDir(session.getId), FileUtil.checkFilename(fileId)), file.get())
|
.writeByteArrayToFile(new File(getTemporaryDir(session.getId), FileUtil.checkFilename(fileId)), file.get())
|
||||||
session += Keys.Session.Upload(fileId) -> file.name
|
session += Keys.Session.Upload(fileId) -> file.name
|
||||||
},
|
},
|
||||||
FileUtil.isImage
|
FileUtil.isImage(_)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -131,7 +131,7 @@ class FileUploadController
|
|||||||
} getOrElse BadRequest()
|
} getOrElse BadRequest()
|
||||||
}
|
}
|
||||||
|
|
||||||
post("/release/:owner/:repository/:tag") {
|
post("/release/:owner/:repository/*") {
|
||||||
setMultipartConfigForLargeFile()
|
setMultipartConfigForLargeFile()
|
||||||
session
|
session
|
||||||
.get(Keys.Session.LoginAccount)
|
.get(Keys.Session.LoginAccount)
|
||||||
@@ -139,7 +139,7 @@ class FileUploadController
|
|||||||
case _: Account =>
|
case _: Account =>
|
||||||
val owner = params("owner")
|
val owner = params("owner")
|
||||||
val repository = params("repository")
|
val repository = params("repository")
|
||||||
val tag = params("tag")
|
val tag = multiParams("splat").head
|
||||||
execute(
|
execute(
|
||||||
{ (file, fileId) =>
|
{ (file, fileId) =>
|
||||||
FileUtils.writeByteArrayToFile(
|
FileUtils.writeByteArrayToFile(
|
||||||
@@ -178,7 +178,7 @@ class FileUploadController
|
|||||||
}
|
}
|
||||||
|
|
||||||
private def onlyWikiEditable(owner: String, repository: String, loginAccount: Account)(action: => Any): Any = {
|
private def onlyWikiEditable(owner: String, repository: String, loginAccount: Account)(action: => Any): Any = {
|
||||||
implicit val session = Database.getSession(request)
|
implicit val session: Session = Database.getSession(request)
|
||||||
getRepository(owner, repository) match {
|
getRepository(owner, repository) match {
|
||||||
case Some(x) =>
|
case Some(x) =>
|
||||||
x.repository.options.wikiOption match {
|
x.repository.options.wikiOption match {
|
||||||
@@ -191,14 +191,13 @@ class FileUploadController
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private def execute(f: (FileItem, String) => Unit, mimeTypeChcker: (String) => Boolean) =
|
private def execute(f: (FileItem, String) => Unit, mimeTypeChecker: (String) => Boolean) =
|
||||||
fileParams.get("file") match {
|
fileParams.get("file") match {
|
||||||
case Some(file) if (mimeTypeChcker(file.name)) =>
|
case Some(file) if mimeTypeChecker(file.name) =>
|
||||||
defining(FileUtil.generateFileId) { fileId =>
|
val fileId = FileUtil.generateFileId
|
||||||
f(file, fileId)
|
f(file, fileId)
|
||||||
contentType = "text/plain"
|
contentType = "text/plain"
|
||||||
Ok(fileId)
|
Ok(fileId)
|
||||||
}
|
|
||||||
case _ => BadRequest()
|
case _ => BadRequest()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import gitbucket.core.helper.xml
|
|||||||
import gitbucket.core.model.Account
|
import gitbucket.core.model.Account
|
||||||
import gitbucket.core.service._
|
import gitbucket.core.service._
|
||||||
import gitbucket.core.util.Implicits._
|
import gitbucket.core.util.Implicits._
|
||||||
import gitbucket.core.util.SyntaxSugars._
|
|
||||||
import gitbucket.core.util._
|
import gitbucket.core.util._
|
||||||
import gitbucket.core.view.helpers._
|
import gitbucket.core.view.helpers._
|
||||||
import org.scalatra.Ok
|
import org.scalatra.Ok
|
||||||
@@ -237,50 +236,49 @@ trait IndexControllerBase extends ControllerBase {
|
|||||||
|
|
||||||
// TODO Move to RepositoryViewrController?
|
// TODO Move to RepositoryViewrController?
|
||||||
get("/:owner/:repository/search")(referrersOnly { repository =>
|
get("/:owner/:repository/search")(referrersOnly { repository =>
|
||||||
defining(params.getOrElse("q", "").trim, params.getOrElse("type", "code")) {
|
val query = params.getOrElse("q", "").trim
|
||||||
case (query, target) =>
|
val target = params.getOrElse("type", "code")
|
||||||
val page = try {
|
val page = try {
|
||||||
val i = params.getOrElse("page", "1").toInt
|
val i = params.getOrElse("page", "1").toInt
|
||||||
if (i <= 0) 1 else i
|
if (i <= 0) 1 else i
|
||||||
} catch {
|
} catch {
|
||||||
case e: NumberFormatException => 1
|
case _: NumberFormatException => 1
|
||||||
}
|
}
|
||||||
|
|
||||||
target.toLowerCase match {
|
target.toLowerCase match {
|
||||||
case "issues" =>
|
case "issues" =>
|
||||||
gitbucket.core.search.html.issues(
|
gitbucket.core.search.html.issues(
|
||||||
if (query.nonEmpty) searchIssues(repository.owner, repository.name, query, false) else Nil,
|
if (query.nonEmpty) searchIssues(repository.owner, repository.name, query, false) else Nil,
|
||||||
false,
|
false,
|
||||||
query,
|
query,
|
||||||
page,
|
page,
|
||||||
repository
|
repository
|
||||||
)
|
)
|
||||||
|
|
||||||
case "pulls" =>
|
case "pulls" =>
|
||||||
gitbucket.core.search.html.issues(
|
gitbucket.core.search.html.issues(
|
||||||
if (query.nonEmpty) searchIssues(repository.owner, repository.name, query, true) else Nil,
|
if (query.nonEmpty) searchIssues(repository.owner, repository.name, query, true) else Nil,
|
||||||
true,
|
true,
|
||||||
query,
|
query,
|
||||||
page,
|
page,
|
||||||
repository
|
repository
|
||||||
)
|
)
|
||||||
|
|
||||||
case "wiki" =>
|
case "wiki" =>
|
||||||
gitbucket.core.search.html.wiki(
|
gitbucket.core.search.html.wiki(
|
||||||
if (query.nonEmpty) searchWikiPages(repository.owner, repository.name, query) else Nil,
|
if (query.nonEmpty) searchWikiPages(repository.owner, repository.name, query) else Nil,
|
||||||
query,
|
query,
|
||||||
page,
|
page,
|
||||||
repository
|
repository
|
||||||
)
|
)
|
||||||
|
|
||||||
case _ =>
|
case _ =>
|
||||||
gitbucket.core.search.html.code(
|
gitbucket.core.search.html.code(
|
||||||
if (query.nonEmpty) searchFiles(repository.owner, repository.name, query) else Nil,
|
if (query.nonEmpty) searchFiles(repository.owner, repository.name, query) else Nil,
|
||||||
query,
|
query,
|
||||||
page,
|
page,
|
||||||
repository
|
repository
|
||||||
)
|
)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
package gitbucket.core.controller
|
package gitbucket.core.controller
|
||||||
|
|
||||||
import gitbucket.core.issues.html
|
import gitbucket.core.issues.html
|
||||||
|
import gitbucket.core.model.Account
|
||||||
import gitbucket.core.service.IssuesService._
|
import gitbucket.core.service.IssuesService._
|
||||||
import gitbucket.core.service._
|
import gitbucket.core.service._
|
||||||
import gitbucket.core.util.SyntaxSugars._
|
|
||||||
import gitbucket.core.util.Implicits._
|
import gitbucket.core.util.Implicits._
|
||||||
import gitbucket.core.util._
|
import gitbucket.core.util._
|
||||||
import gitbucket.core.view
|
import gitbucket.core.view
|
||||||
@@ -95,212 +95,224 @@ trait IssuesControllerBase extends ControllerBase {
|
|||||||
})
|
})
|
||||||
|
|
||||||
get("/:owner/:repository/issues/:id")(referrersOnly { repository =>
|
get("/:owner/:repository/issues/:id")(referrersOnly { repository =>
|
||||||
defining(repository.owner, repository.name, params("id")) {
|
val issueId = params("id")
|
||||||
case (owner, name, issueId) =>
|
getIssue(repository.owner, repository.name, issueId) map {
|
||||||
getIssue(owner, name, issueId) map {
|
issue =>
|
||||||
issue =>
|
if (issue.isPullRequest) {
|
||||||
if (issue.isPullRequest) {
|
redirect(s"/${repository.owner}/${repository.name}/pull/${issueId}")
|
||||||
redirect(s"/${repository.owner}/${repository.name}/pull/${issueId}")
|
} else {
|
||||||
} else {
|
html.issue(
|
||||||
html.issue(
|
issue,
|
||||||
issue,
|
getComments(repository.owner, repository.name, issueId.toInt),
|
||||||
getComments(owner, name, issueId.toInt),
|
getIssueLabels(repository.owner, repository.name, issueId.toInt),
|
||||||
getIssueLabels(owner, name, issueId.toInt),
|
getAssignableUserNames(repository.owner, repository.name),
|
||||||
getAssignableUserNames(owner, name),
|
getMilestonesWithIssueCount(repository.owner, repository.name),
|
||||||
getMilestonesWithIssueCount(owner, name),
|
getPriorities(repository.owner, repository.name),
|
||||||
getPriorities(owner, name),
|
getLabels(repository.owner, repository.name),
|
||||||
getLabels(owner, name),
|
isIssueEditable(repository),
|
||||||
isIssueEditable(repository),
|
isIssueManageable(repository),
|
||||||
isIssueManageable(repository),
|
isIssueCommentManageable(repository),
|
||||||
isIssueCommentManageable(repository),
|
repository
|
||||||
repository
|
)
|
||||||
)
|
}
|
||||||
}
|
} getOrElse NotFound()
|
||||||
} getOrElse NotFound()
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
get("/:owner/:repository/issues/new")(readableUsersOnly { repository =>
|
get("/:owner/:repository/issues/new")(readableUsersOnly { repository =>
|
||||||
if (isIssueEditable(repository)) { // TODO Should this check is provided by authenticator?
|
if (isIssueEditable(repository)) { // TODO Should this check is provided by authenticator?
|
||||||
defining(repository.owner, repository.name) {
|
html.create(
|
||||||
case (owner, name) =>
|
getAssignableUserNames(repository.owner, repository.name),
|
||||||
html.create(
|
getMilestones(repository.owner, repository.name),
|
||||||
getAssignableUserNames(owner, name),
|
getPriorities(repository.owner, repository.name),
|
||||||
getMilestones(owner, name),
|
getDefaultPriority(repository.owner, repository.name),
|
||||||
getPriorities(owner, name),
|
getLabels(repository.owner, repository.name),
|
||||||
getDefaultPriority(owner, name),
|
isIssueManageable(repository),
|
||||||
getLabels(owner, name),
|
getContentTemplate(repository, "ISSUE_TEMPLATE"),
|
||||||
isIssueManageable(repository),
|
repository
|
||||||
getContentTemplate(repository, "ISSUE_TEMPLATE"),
|
)
|
||||||
repository
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} else Unauthorized()
|
} else Unauthorized()
|
||||||
})
|
})
|
||||||
|
|
||||||
post("/:owner/:repository/issues/new", issueCreateForm)(readableUsersOnly { (form, repository) =>
|
post("/:owner/:repository/issues/new", issueCreateForm)(readableUsersOnly { (form, repository) =>
|
||||||
if (isIssueEditable(repository)) { // TODO Should this check is provided by authenticator?
|
context.withLoginAccount {
|
||||||
val issue = createIssue(
|
loginAccount =>
|
||||||
repository,
|
if (isIssueEditable(repository)) { // TODO Should this check is provided by authenticator?
|
||||||
form.title,
|
val issue = createIssue(
|
||||||
form.content,
|
repository,
|
||||||
form.assignedUserName,
|
form.title,
|
||||||
form.milestoneId,
|
form.content,
|
||||||
form.priorityId,
|
form.assignedUserName,
|
||||||
form.labelNames.toSeq.flatMap(_.split(",")),
|
form.milestoneId,
|
||||||
context.loginAccount.get
|
form.priorityId,
|
||||||
)
|
form.labelNames.toSeq.flatMap(_.split(",")),
|
||||||
|
loginAccount
|
||||||
redirect(s"/${issue.userName}/${issue.repositoryName}/issues/${issue.issueId}")
|
)
|
||||||
} else Unauthorized()
|
redirect(s"/${issue.userName}/${issue.repositoryName}/issues/${issue.issueId}")
|
||||||
|
} else Unauthorized()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
ajaxPost("/:owner/:repository/issues/edit_title/:id", issueTitleEditForm)(readableUsersOnly { (title, repository) =>
|
ajaxPost("/:owner/:repository/issues/edit_title/:id", issueTitleEditForm)(readableUsersOnly { (title, repository) =>
|
||||||
defining(repository.owner, repository.name) {
|
context.withLoginAccount {
|
||||||
case (owner, name) =>
|
loginAccount =>
|
||||||
getIssue(owner, name, params("id")).map {
|
getIssue(repository.owner, repository.name, params("id")).map {
|
||||||
issue =>
|
issue =>
|
||||||
if (isEditableContent(owner, name, issue.openedUserName)) {
|
if (isEditableContent(repository.owner, repository.name, issue.openedUserName, loginAccount)) {
|
||||||
if (issue.title != title) {
|
if (issue.title != title) {
|
||||||
// update issue
|
// update issue
|
||||||
updateIssue(owner, name, issue.issueId, title, issue.content)
|
updateIssue(repository.owner, repository.name, issue.issueId, title, issue.content)
|
||||||
// extract references and create refer comment
|
// extract references and create refer comment
|
||||||
createReferComment(owner, name, issue.copy(title = title), title, context.loginAccount.get)
|
createReferComment(repository.owner, repository.name, issue.copy(title = title), title, loginAccount)
|
||||||
createComment(
|
createComment(
|
||||||
owner,
|
repository.owner,
|
||||||
name,
|
repository.name,
|
||||||
context.loginAccount.get.userName,
|
loginAccount.userName,
|
||||||
issue.issueId,
|
issue.issueId,
|
||||||
issue.title + "\r\n" + title,
|
issue.title + "\r\n" + title,
|
||||||
"change_title"
|
"change_title"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
redirect(s"/${owner}/${name}/issues/_data/${issue.issueId}")
|
redirect(s"/${repository.owner}/${repository.name}/issues/_data/${issue.issueId}")
|
||||||
} else Unauthorized()
|
} else Unauthorized()
|
||||||
} getOrElse NotFound()
|
} getOrElse NotFound()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
ajaxPost("/:owner/:repository/issues/edit/:id", issueEditForm)(readableUsersOnly { (content, repository) =>
|
ajaxPost("/:owner/:repository/issues/edit/:id", issueEditForm)(readableUsersOnly { (content, repository) =>
|
||||||
defining(repository.owner, repository.name) {
|
context.withLoginAccount {
|
||||||
case (owner, name) =>
|
loginAccount =>
|
||||||
getIssue(owner, name, params("id")).map { issue =>
|
getIssue(repository.owner, repository.name, params("id")).map { issue =>
|
||||||
if (isEditableContent(owner, name, issue.openedUserName)) {
|
if (isEditableContent(repository.owner, repository.name, issue.openedUserName, loginAccount)) {
|
||||||
// update issue
|
// update issue
|
||||||
updateIssue(owner, name, issue.issueId, issue.title, content)
|
updateIssue(repository.owner, repository.name, issue.issueId, issue.title, content)
|
||||||
// extract references and create refer comment
|
// extract references and create refer comment
|
||||||
createReferComment(owner, name, issue, content.getOrElse(""), context.loginAccount.get)
|
createReferComment(repository.owner, repository.name, issue, content.getOrElse(""), loginAccount)
|
||||||
|
|
||||||
redirect(s"/${owner}/${name}/issues/_data/${issue.issueId}")
|
redirect(s"/${repository.owner}/${repository.name}/issues/_data/${issue.issueId}")
|
||||||
} else Unauthorized()
|
} else Unauthorized()
|
||||||
} getOrElse NotFound()
|
} getOrElse NotFound()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
post("/:owner/:repository/issue_comments/new", commentForm)(readableUsersOnly { (form, repository) =>
|
post("/:owner/:repository/issue_comments/new", commentForm)(readableUsersOnly { (form, repository) =>
|
||||||
getIssue(repository.owner, repository.name, form.issueId.toString).flatMap { issue =>
|
context.withLoginAccount {
|
||||||
val actionOpt =
|
loginAccount =>
|
||||||
params.get("action").filter(_ => isEditableContent(issue.userName, issue.repositoryName, issue.openedUserName))
|
getIssue(repository.owner, repository.name, form.issueId.toString).flatMap { issue =>
|
||||||
handleComment(issue, Some(form.content), repository, actionOpt) map {
|
val actionOpt =
|
||||||
case (issue, id) =>
|
params
|
||||||
redirect(
|
.get("action")
|
||||||
s"/${repository.owner}/${repository.name}/${if (issue.isPullRequest) "pull" else "issues"}/${form.issueId}#comment-${id}"
|
.filter(_ => isEditableContent(issue.userName, issue.repositoryName, issue.openedUserName, loginAccount))
|
||||||
)
|
handleComment(issue, Some(form.content), repository, actionOpt) map {
|
||||||
}
|
case (issue, id) =>
|
||||||
} getOrElse NotFound()
|
redirect(
|
||||||
|
s"/${repository.owner}/${repository.name}/${if (issue.isPullRequest) "pull" else "issues"}/${form.issueId}#comment-${id}"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} getOrElse NotFound()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
post("/:owner/:repository/issue_comments/state", issueStateForm)(readableUsersOnly { (form, repository) =>
|
post("/:owner/:repository/issue_comments/state", issueStateForm)(readableUsersOnly { (form, repository) =>
|
||||||
getIssue(repository.owner, repository.name, form.issueId.toString).flatMap { issue =>
|
context.withLoginAccount {
|
||||||
val actionOpt =
|
loginAccount =>
|
||||||
params.get("action").filter(_ => isEditableContent(issue.userName, issue.repositoryName, issue.openedUserName))
|
getIssue(repository.owner, repository.name, form.issueId.toString).flatMap { issue =>
|
||||||
handleComment(issue, form.content, repository, actionOpt) map {
|
val actionOpt =
|
||||||
case (issue, id) =>
|
params
|
||||||
redirect(
|
.get("action")
|
||||||
s"/${repository.owner}/${repository.name}/${if (issue.isPullRequest) "pull" else "issues"}/${form.issueId}#comment-${id}"
|
.filter(_ => isEditableContent(issue.userName, issue.repositoryName, issue.openedUserName, loginAccount))
|
||||||
)
|
handleComment(issue, form.content, repository, actionOpt) map {
|
||||||
}
|
case (issue, id) =>
|
||||||
} getOrElse NotFound()
|
redirect(
|
||||||
|
s"/${repository.owner}/${repository.name}/${if (issue.isPullRequest) "pull" else "issues"}/${form.issueId}#comment-${id}"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} getOrElse NotFound()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
ajaxPost("/:owner/:repository/issue_comments/edit/:id", commentForm)(readableUsersOnly { (form, repository) =>
|
ajaxPost("/:owner/:repository/issue_comments/edit/:id", commentForm)(readableUsersOnly { (form, repository) =>
|
||||||
defining(repository.owner, repository.name) {
|
context.withLoginAccount {
|
||||||
case (owner, name) =>
|
loginAccount =>
|
||||||
getComment(owner, name, params("id")).map { comment =>
|
getComment(repository.owner, repository.name, params("id")).map { comment =>
|
||||||
if (isEditableContent(owner, name, comment.commentedUserName)) {
|
if (isEditableContent(repository.owner, repository.name, comment.commentedUserName, loginAccount)) {
|
||||||
updateComment(comment.issueId, comment.commentId, form.content)
|
updateComment(comment.issueId, comment.commentId, form.content)
|
||||||
redirect(s"/${owner}/${name}/issue_comments/_data/${comment.commentId}")
|
redirect(s"/${repository.owner}/${repository.name}/issue_comments/_data/${comment.commentId}")
|
||||||
} else Unauthorized()
|
} else Unauthorized()
|
||||||
} getOrElse NotFound()
|
} getOrElse NotFound()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
ajaxPost("/:owner/:repository/issue_comments/delete/:id")(readableUsersOnly { repository =>
|
ajaxPost("/:owner/:repository/issue_comments/delete/:id")(readableUsersOnly { repository =>
|
||||||
defining(repository.owner, repository.name) {
|
context.withLoginAccount { loginAccount =>
|
||||||
case (owner, name) =>
|
getComment(repository.owner, repository.name, params("id")).map { comment =>
|
||||||
getComment(owner, name, params("id")).map { comment =>
|
if (isDeletableComment(repository.owner, repository.name, comment.commentedUserName, loginAccount)) {
|
||||||
if (isDeletableComment(owner, name, comment.commentedUserName)) {
|
Ok(deleteComment(repository.owner, repository.name, comment.issueId, comment.commentId))
|
||||||
Ok(deleteComment(repository.owner, repository.name, comment.issueId, comment.commentId))
|
} else Unauthorized()
|
||||||
} else Unauthorized()
|
} getOrElse NotFound()
|
||||||
} getOrElse NotFound()
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
ajaxGet("/:owner/:repository/issues/_data/:id")(readableUsersOnly { repository =>
|
ajaxGet("/:owner/:repository/issues/_data/:id")(readableUsersOnly { repository =>
|
||||||
getIssue(repository.owner, repository.name, params("id")) map {
|
context.withLoginAccount {
|
||||||
x =>
|
loginAccount =>
|
||||||
if (isEditableContent(x.userName, x.repositoryName, x.openedUserName)) {
|
getIssue(repository.owner, repository.name, params("id")) map {
|
||||||
params.get("dataType") collect {
|
x =>
|
||||||
case t if t == "html" => html.editissue(x.content, x.issueId, repository)
|
if (isEditableContent(x.userName, x.repositoryName, x.openedUserName, loginAccount)) {
|
||||||
} getOrElse {
|
params.get("dataType") collect {
|
||||||
contentType = formats("json")
|
case t if t == "html" => html.editissue(x.content, x.issueId, repository)
|
||||||
org.json4s.jackson.Serialization.write(
|
} getOrElse {
|
||||||
Map(
|
contentType = formats("json")
|
||||||
"title" -> x.title,
|
org.json4s.jackson.Serialization.write(
|
||||||
"content" -> Markdown.toHtml(
|
Map(
|
||||||
markdown = x.content getOrElse "No description given.",
|
"title" -> x.title,
|
||||||
repository = repository,
|
"content" -> Markdown.toHtml(
|
||||||
branch = repository.repository.defaultBranch,
|
markdown = x.content getOrElse "No description given.",
|
||||||
enableWikiLink = false,
|
repository = repository,
|
||||||
enableRefsLink = true,
|
branch = repository.repository.defaultBranch,
|
||||||
enableAnchor = true,
|
enableWikiLink = false,
|
||||||
enableLineBreaks = true,
|
enableRefsLink = true,
|
||||||
enableTaskList = true,
|
enableAnchor = true,
|
||||||
hasWritePermission = true
|
enableLineBreaks = true,
|
||||||
|
enableTaskList = true,
|
||||||
|
hasWritePermission = true
|
||||||
|
)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
}
|
||||||
)
|
} else Unauthorized()
|
||||||
}
|
} getOrElse NotFound()
|
||||||
} else Unauthorized()
|
}
|
||||||
} getOrElse NotFound()
|
|
||||||
})
|
})
|
||||||
|
|
||||||
ajaxGet("/:owner/:repository/issue_comments/_data/:id")(readableUsersOnly { repository =>
|
ajaxGet("/:owner/:repository/issue_comments/_data/:id")(readableUsersOnly { repository =>
|
||||||
getComment(repository.owner, repository.name, params("id")) map {
|
context.withLoginAccount {
|
||||||
x =>
|
loginAccount =>
|
||||||
if (isEditableContent(x.userName, x.repositoryName, x.commentedUserName)) {
|
getComment(repository.owner, repository.name, params("id")) map {
|
||||||
params.get("dataType") collect {
|
x =>
|
||||||
case t if t == "html" => html.editcomment(x.content, x.commentId, repository)
|
if (isEditableContent(x.userName, x.repositoryName, x.commentedUserName, loginAccount)) {
|
||||||
} getOrElse {
|
params.get("dataType") collect {
|
||||||
contentType = formats("json")
|
case t if t == "html" => html.editcomment(x.content, x.commentId, repository)
|
||||||
org.json4s.jackson.Serialization.write(
|
} getOrElse {
|
||||||
Map(
|
contentType = formats("json")
|
||||||
"content" -> view.Markdown.toHtml(
|
org.json4s.jackson.Serialization.write(
|
||||||
markdown = x.content,
|
Map(
|
||||||
repository = repository,
|
"content" -> view.Markdown.toHtml(
|
||||||
branch = repository.repository.defaultBranch,
|
markdown = x.content,
|
||||||
enableWikiLink = false,
|
repository = repository,
|
||||||
enableRefsLink = true,
|
branch = repository.repository.defaultBranch,
|
||||||
enableAnchor = true,
|
enableWikiLink = false,
|
||||||
enableLineBreaks = true,
|
enableRefsLink = true,
|
||||||
enableTaskList = true,
|
enableAnchor = true,
|
||||||
hasWritePermission = true
|
enableLineBreaks = true,
|
||||||
|
enableTaskList = true,
|
||||||
|
hasWritePermission = true
|
||||||
|
)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
}
|
||||||
)
|
} else Unauthorized()
|
||||||
}
|
} getOrElse NotFound()
|
||||||
} else Unauthorized()
|
}
|
||||||
} getOrElse NotFound()
|
|
||||||
})
|
})
|
||||||
|
|
||||||
ajaxPost("/:owner/:repository/issues/new/label")(writableUsersOnly { repository =>
|
ajaxPost("/:owner/:repository/issues/new/label")(writableUsersOnly { repository =>
|
||||||
@@ -310,17 +322,15 @@ trait IssuesControllerBase extends ControllerBase {
|
|||||||
})
|
})
|
||||||
|
|
||||||
ajaxPost("/:owner/:repository/issues/:id/label/new")(writableUsersOnly { repository =>
|
ajaxPost("/:owner/:repository/issues/:id/label/new")(writableUsersOnly { repository =>
|
||||||
defining(params("id").toInt) { issueId =>
|
val issueId = params("id").toInt
|
||||||
registerIssueLabel(repository.owner, repository.name, issueId, params("labelId").toInt, true)
|
registerIssueLabel(repository.owner, repository.name, issueId, params("labelId").toInt, true)
|
||||||
html.labellist(getIssueLabels(repository.owner, repository.name, issueId))
|
html.labellist(getIssueLabels(repository.owner, repository.name, issueId))
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
ajaxPost("/:owner/:repository/issues/:id/label/delete")(writableUsersOnly { repository =>
|
ajaxPost("/:owner/:repository/issues/:id/label/delete")(writableUsersOnly { repository =>
|
||||||
defining(params("id").toInt) { issueId =>
|
val issueId = params("id").toInt
|
||||||
deleteIssueLabel(repository.owner, repository.name, issueId, params("labelId").toInt, true)
|
deleteIssueLabel(repository.owner, repository.name, issueId, params("labelId").toInt, true)
|
||||||
html.labellist(getIssueLabels(repository.owner, repository.name, issueId))
|
html.labellist(getIssueLabels(repository.owner, repository.name, issueId))
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
ajaxPost("/:owner/:repository/issues/:id/assign")(writableUsersOnly { repository =>
|
ajaxPost("/:owner/:repository/issues/:id/assign")(writableUsersOnly { repository =>
|
||||||
@@ -353,26 +363,27 @@ trait IssuesControllerBase extends ControllerBase {
|
|||||||
})
|
})
|
||||||
|
|
||||||
post("/:owner/:repository/issues/batchedit/state")(writableUsersOnly { repository =>
|
post("/:owner/:repository/issues/batchedit/state")(writableUsersOnly { repository =>
|
||||||
defining(params.get("value")) {
|
val action = params.get("value")
|
||||||
action =>
|
action match {
|
||||||
action match {
|
case Some("open") =>
|
||||||
case Some("open") =>
|
executeBatch(repository) { issueId =>
|
||||||
executeBatch(repository) { issueId =>
|
getIssue(repository.owner, repository.name, issueId.toString).foreach { issue =>
|
||||||
getIssue(repository.owner, repository.name, issueId.toString).foreach { issue =>
|
handleComment(issue, None, repository, Some("reopen"))
|
||||||
handleComment(issue, None, repository, Some("reopen"))
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
case Some("close") =>
|
|
||||||
executeBatch(repository) { issueId =>
|
|
||||||
getIssue(repository.owner, repository.name, issueId.toString).foreach { issue =>
|
|
||||||
handleComment(issue, None, repository, Some("close"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case _ => BadRequest()
|
|
||||||
}
|
}
|
||||||
if (params("uri").nonEmpty) {
|
if (params("uri").nonEmpty) {
|
||||||
redirect(params("uri"))
|
redirect(params("uri"))
|
||||||
}
|
}
|
||||||
|
case Some("close") =>
|
||||||
|
executeBatch(repository) { issueId =>
|
||||||
|
getIssue(repository.owner, repository.name, issueId.toString).foreach { issue =>
|
||||||
|
handleComment(issue, None, repository, Some("close"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (params("uri").nonEmpty) {
|
||||||
|
redirect(params("uri"))
|
||||||
|
}
|
||||||
|
case _ => BadRequest()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -390,29 +401,26 @@ trait IssuesControllerBase extends ControllerBase {
|
|||||||
})
|
})
|
||||||
|
|
||||||
post("/:owner/:repository/issues/batchedit/assign")(writableUsersOnly { repository =>
|
post("/:owner/:repository/issues/batchedit/assign")(writableUsersOnly { repository =>
|
||||||
defining(assignedUserName("value")) { value =>
|
val value = assignedUserName("value")
|
||||||
executeBatch(repository) {
|
executeBatch(repository) {
|
||||||
updateAssignedUserName(repository.owner, repository.name, _, value, true)
|
updateAssignedUserName(repository.owner, repository.name, _, value, true)
|
||||||
}
|
}
|
||||||
if (params("uri").nonEmpty) {
|
if (params("uri").nonEmpty) {
|
||||||
redirect(params("uri"))
|
redirect(params("uri"))
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
post("/:owner/:repository/issues/batchedit/milestone")(writableUsersOnly { repository =>
|
post("/:owner/:repository/issues/batchedit/milestone")(writableUsersOnly { repository =>
|
||||||
defining(milestoneId("value")) { value =>
|
val value = milestoneId("value")
|
||||||
executeBatch(repository) {
|
executeBatch(repository) {
|
||||||
updateMilestoneId(repository.owner, repository.name, _, value, true)
|
updateMilestoneId(repository.owner, repository.name, _, value, true)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
post("/:owner/:repository/issues/batchedit/priority")(writableUsersOnly { repository =>
|
post("/:owner/:repository/issues/batchedit/priority")(writableUsersOnly { repository =>
|
||||||
defining(priorityId("value")) { value =>
|
val value = priorityId("value")
|
||||||
executeBatch(repository) {
|
executeBatch(repository) {
|
||||||
updatePriorityId(repository.owner, repository.name, _, value, true)
|
updatePriorityId(repository.owner, repository.name, _, value, true)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -464,48 +472,51 @@ trait IssuesControllerBase extends ControllerBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private def searchIssues(repository: RepositoryService.RepositoryInfo) = {
|
private def searchIssues(repository: RepositoryService.RepositoryInfo) = {
|
||||||
defining(repository.owner, repository.name) {
|
val page = IssueSearchCondition.page(request)
|
||||||
case (owner, repoName) =>
|
// retrieve search condition
|
||||||
val page = IssueSearchCondition.page(request)
|
val condition = IssueSearchCondition(request)
|
||||||
// retrieve search condition
|
// search issues
|
||||||
val condition = IssueSearchCondition(request)
|
val issues =
|
||||||
// search issues
|
searchIssue(
|
||||||
val issues =
|
condition,
|
||||||
searchIssue(condition, IssueSearchOption.Issues, (page - 1) * IssueLimit, IssueLimit, owner -> repoName)
|
IssueSearchOption.Issues,
|
||||||
|
(page - 1) * IssueLimit,
|
||||||
|
IssueLimit,
|
||||||
|
repository.owner -> repository.name
|
||||||
|
)
|
||||||
|
|
||||||
html.list(
|
html.list(
|
||||||
"issues",
|
"issues",
|
||||||
issues.map(issue => (issue, None)),
|
issues.map(issue => (issue, None)),
|
||||||
page,
|
page,
|
||||||
getAssignableUserNames(owner, repoName),
|
getAssignableUserNames(repository.owner, repository.name),
|
||||||
getMilestones(owner, repoName),
|
getMilestones(repository.owner, repository.name),
|
||||||
getPriorities(owner, repoName),
|
getPriorities(repository.owner, repository.name),
|
||||||
getLabels(owner, repoName),
|
getLabels(repository.owner, repository.name),
|
||||||
countIssue(condition.copy(state = "open"), IssueSearchOption.Issues, owner -> repoName),
|
countIssue(condition.copy(state = "open"), IssueSearchOption.Issues, repository.owner -> repository.name),
|
||||||
countIssue(condition.copy(state = "closed"), IssueSearchOption.Issues, owner -> repoName),
|
countIssue(condition.copy(state = "closed"), IssueSearchOption.Issues, repository.owner -> repository.name),
|
||||||
condition,
|
condition,
|
||||||
repository,
|
repository,
|
||||||
isIssueEditable(repository),
|
isIssueEditable(repository),
|
||||||
isIssueManageable(repository)
|
isIssueManageable(repository)
|
||||||
)
|
)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests whether an issue or a comment is editable by a logged-in user.
|
* Tests whether an issue or a comment is editable by a logged-in user.
|
||||||
*/
|
*/
|
||||||
private def isEditableContent(owner: String, repository: String, author: String)(
|
private def isEditableContent(owner: String, repository: String, author: String, loginAccount: Account)(
|
||||||
implicit context: Context
|
implicit context: Context
|
||||||
): Boolean = {
|
): Boolean = {
|
||||||
hasDeveloperRole(owner, repository, context.loginAccount) || author == context.loginAccount.get.userName
|
hasDeveloperRole(owner, repository, context.loginAccount) || author == loginAccount.userName
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests whether an issue comment is deletable by a logged-in user.
|
* Tests whether an issue comment is deletable by a logged-in user.
|
||||||
*/
|
*/
|
||||||
private def isDeletableComment(owner: String, repository: String, author: String)(
|
private def isDeletableComment(owner: String, repository: String, author: String, loginAccount: Account)(
|
||||||
implicit context: Context
|
implicit context: Context
|
||||||
): Boolean = {
|
): Boolean = {
|
||||||
hasOwnerRole(owner, repository, context.loginAccount) || author == context.loginAccount.get.userName
|
hasOwnerRole(owner, repository, context.loginAccount) || author == loginAccount.userName
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ trait PreProcessControllerBase extends ControllerBase {
|
|||||||
get(!context.settings.allowAnonymousAccess, context.loginAccount.isEmpty) {
|
get(!context.settings.allowAnonymousAccess, context.loginAccount.isEmpty) {
|
||||||
if (!context.currentPath.startsWith("/assets") && !context.currentPath.startsWith("/signin") &&
|
if (!context.currentPath.startsWith("/assets") && !context.currentPath.startsWith("/signin") &&
|
||||||
!context.currentPath.startsWith("/register") && !context.currentPath.endsWith("/info/refs") &&
|
!context.currentPath.startsWith("/register") && !context.currentPath.endsWith("/info/refs") &&
|
||||||
|
!context.currentPath.startsWith("/plugin-assets") &&
|
||||||
!PluginRegistry().getAnonymousAccessiblePaths().exists { path =>
|
!PluginRegistry().getAnonymousAccessiblePaths().exists { path =>
|
||||||
context.currentPath.startsWith(path)
|
context.currentPath.startsWith(path)
|
||||||
}) {
|
}) {
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import gitbucket.core.service.IssuesService._
|
|||||||
import gitbucket.core.service.PullRequestService._
|
import gitbucket.core.service.PullRequestService._
|
||||||
import gitbucket.core.service.RepositoryService.RepositoryInfo
|
import gitbucket.core.service.RepositoryService.RepositoryInfo
|
||||||
import gitbucket.core.service._
|
import gitbucket.core.service._
|
||||||
import gitbucket.core.util.SyntaxSugars._
|
|
||||||
import gitbucket.core.util.Directory._
|
import gitbucket.core.util.Directory._
|
||||||
import gitbucket.core.util.Implicits._
|
import gitbucket.core.util.Implicits._
|
||||||
import gitbucket.core.util._
|
import gitbucket.core.util._
|
||||||
@@ -58,7 +57,7 @@ trait PullRequestsControllerBase extends ControllerBase {
|
|||||||
with PrioritiesService =>
|
with PrioritiesService =>
|
||||||
|
|
||||||
val pullRequestForm = mapping(
|
val pullRequestForm = mapping(
|
||||||
"title" -> trim(label("Title", text(required, maxlength(100)))),
|
"title" -> trim(label("Title", text(required))),
|
||||||
"content" -> trim(label("Content", optional(text()))),
|
"content" -> trim(label("Content", optional(text()))),
|
||||||
"targetUserName" -> trim(text(required, maxlength(100))),
|
"targetUserName" -> trim(text(required, maxlength(100))),
|
||||||
"targetBranch" -> trim(text(required, maxlength(100))),
|
"targetBranch" -> trim(text(required, maxlength(100))),
|
||||||
@@ -111,24 +110,29 @@ trait PullRequestsControllerBase extends ControllerBase {
|
|||||||
get("/:owner/:repository/pull/:id")(referrersOnly { repository =>
|
get("/:owner/:repository/pull/:id")(referrersOnly { repository =>
|
||||||
params("id").toIntOpt.flatMap {
|
params("id").toIntOpt.flatMap {
|
||||||
issueId =>
|
issueId =>
|
||||||
val owner = repository.owner
|
getPullRequest(repository.owner, repository.name, issueId) map {
|
||||||
val name = repository.name
|
|
||||||
getPullRequest(owner, name, issueId) map {
|
|
||||||
case (issue, pullreq) =>
|
case (issue, pullreq) =>
|
||||||
val (commits, diffs) =
|
val (commits, diffs) =
|
||||||
getRequestCompareInfo(owner, name, pullreq.commitIdFrom, owner, name, pullreq.commitIdTo)
|
getRequestCompareInfo(
|
||||||
|
repository.owner,
|
||||||
|
repository.name,
|
||||||
|
pullreq.commitIdFrom,
|
||||||
|
repository.owner,
|
||||||
|
repository.name,
|
||||||
|
pullreq.commitIdTo
|
||||||
|
)
|
||||||
|
|
||||||
html.conversation(
|
html.conversation(
|
||||||
issue,
|
issue,
|
||||||
pullreq,
|
pullreq,
|
||||||
commits.flatten,
|
commits.flatten,
|
||||||
getPullRequestComments(owner, name, issue.issueId, commits.flatten),
|
getPullRequestComments(repository.owner, repository.name, issue.issueId, commits.flatten),
|
||||||
diffs.size,
|
diffs.size,
|
||||||
getIssueLabels(owner, name, issueId),
|
getIssueLabels(repository.owner, repository.name, issueId),
|
||||||
getAssignableUserNames(owner, name),
|
getAssignableUserNames(repository.owner, repository.name),
|
||||||
getMilestonesWithIssueCount(owner, name),
|
getMilestonesWithIssueCount(repository.owner, repository.name),
|
||||||
getPriorities(owner, name),
|
getPriorities(repository.owner, repository.name),
|
||||||
getLabels(owner, name),
|
getLabels(repository.owner, repository.name),
|
||||||
isEditable(repository),
|
isEditable(repository),
|
||||||
isManageable(repository),
|
isManageable(repository),
|
||||||
hasDeveloperRole(pullreq.requestUserName, pullreq.requestRepositoryName, context.loginAccount),
|
hasDeveloperRole(pullreq.requestUserName, pullreq.requestRepositoryName, context.loginAccount),
|
||||||
@@ -162,12 +166,17 @@ trait PullRequestsControllerBase extends ControllerBase {
|
|||||||
get("/:owner/:repository/pull/:id/commits")(referrersOnly { repository =>
|
get("/:owner/:repository/pull/:id/commits")(referrersOnly { repository =>
|
||||||
params("id").toIntOpt.flatMap {
|
params("id").toIntOpt.flatMap {
|
||||||
issueId =>
|
issueId =>
|
||||||
val owner = repository.owner
|
getPullRequest(repository.owner, repository.name, issueId) map {
|
||||||
val name = repository.name
|
|
||||||
getPullRequest(owner, name, issueId) map {
|
|
||||||
case (issue, pullreq) =>
|
case (issue, pullreq) =>
|
||||||
val (commits, diffs) =
|
val (commits, diffs) =
|
||||||
getRequestCompareInfo(owner, name, pullreq.commitIdFrom, owner, name, pullreq.commitIdTo)
|
getRequestCompareInfo(
|
||||||
|
repository.owner,
|
||||||
|
repository.name,
|
||||||
|
pullreq.commitIdFrom,
|
||||||
|
repository.owner,
|
||||||
|
repository.name,
|
||||||
|
pullreq.commitIdTo
|
||||||
|
)
|
||||||
|
|
||||||
val commitsWithStatus = commits.map { day =>
|
val commitsWithStatus = commits.map { day =>
|
||||||
day.map { commit =>
|
day.map { commit =>
|
||||||
@@ -179,7 +188,7 @@ trait PullRequestsControllerBase extends ControllerBase {
|
|||||||
issue,
|
issue,
|
||||||
pullreq,
|
pullreq,
|
||||||
commitsWithStatus,
|
commitsWithStatus,
|
||||||
getPullRequestComments(owner, name, issue.issueId, commits.flatten),
|
getPullRequestComments(repository.owner, repository.name, issue.issueId, commits.flatten),
|
||||||
diffs.size,
|
diffs.size,
|
||||||
isManageable(repository),
|
isManageable(repository),
|
||||||
repository
|
repository
|
||||||
@@ -191,19 +200,24 @@ trait PullRequestsControllerBase extends ControllerBase {
|
|||||||
get("/:owner/:repository/pull/:id/files")(referrersOnly { repository =>
|
get("/:owner/:repository/pull/:id/files")(referrersOnly { repository =>
|
||||||
params("id").toIntOpt.flatMap {
|
params("id").toIntOpt.flatMap {
|
||||||
issueId =>
|
issueId =>
|
||||||
val owner = repository.owner
|
getPullRequest(repository.owner, repository.name, issueId) map {
|
||||||
val name = repository.name
|
|
||||||
getPullRequest(owner, name, issueId) map {
|
|
||||||
case (issue, pullreq) =>
|
case (issue, pullreq) =>
|
||||||
val (commits, diffs) =
|
val (commits, diffs) =
|
||||||
getRequestCompareInfo(owner, name, pullreq.commitIdFrom, owner, name, pullreq.commitIdTo)
|
getRequestCompareInfo(
|
||||||
|
repository.owner,
|
||||||
|
repository.name,
|
||||||
|
pullreq.commitIdFrom,
|
||||||
|
repository.owner,
|
||||||
|
repository.name,
|
||||||
|
pullreq.commitIdTo
|
||||||
|
)
|
||||||
|
|
||||||
html.files(
|
html.files(
|
||||||
issue,
|
issue,
|
||||||
pullreq,
|
pullreq,
|
||||||
diffs,
|
diffs,
|
||||||
commits.flatten,
|
commits.flatten,
|
||||||
getPullRequestComments(owner, name, issue.issueId, commits.flatten),
|
getPullRequestComments(repository.owner, repository.name, issue.issueId, commits.flatten),
|
||||||
isManageable(repository),
|
isManageable(repository),
|
||||||
repository
|
repository
|
||||||
)
|
)
|
||||||
@@ -214,39 +228,35 @@ trait PullRequestsControllerBase extends ControllerBase {
|
|||||||
ajaxGet("/:owner/:repository/pull/:id/mergeguide")(referrersOnly { repository =>
|
ajaxGet("/:owner/:repository/pull/:id/mergeguide")(referrersOnly { repository =>
|
||||||
params("id").toIntOpt.flatMap {
|
params("id").toIntOpt.flatMap {
|
||||||
issueId =>
|
issueId =>
|
||||||
val owner = repository.owner
|
getPullRequest(repository.owner, repository.name, issueId) map {
|
||||||
val name = repository.name
|
|
||||||
getPullRequest(owner, name, issueId) map {
|
|
||||||
case (issue, pullreq) =>
|
case (issue, pullreq) =>
|
||||||
val conflictMessage = LockUtil.lock(s"${owner}/${name}") {
|
val conflictMessage = LockUtil.lock(s"${repository.owner}/${repository.name}") {
|
||||||
checkConflict(owner, name, pullreq.branch, issueId)
|
checkConflict(repository.owner, repository.name, pullreq.branch, issueId)
|
||||||
}
|
}
|
||||||
val hasMergePermission = hasDeveloperRole(owner, name, context.loginAccount)
|
val hasMergePermission = hasDeveloperRole(repository.owner, repository.name, context.loginAccount)
|
||||||
val branchProtection = getProtectedBranchInfo(owner, name, pullreq.branch)
|
val branchProtection = getProtectedBranchInfo(repository.owner, repository.name, pullreq.branch)
|
||||||
val mergeStatus = PullRequestService.MergeStatus(
|
val mergeStatus = PullRequestService.MergeStatus(
|
||||||
conflictMessage = conflictMessage,
|
conflictMessage = conflictMessage,
|
||||||
commitStatuses = getCommitStatuses(owner, name, pullreq.commitIdTo),
|
commitStatuses = getCommitStatuses(repository.owner, repository.name, pullreq.commitIdTo),
|
||||||
branchProtection = branchProtection,
|
branchProtection = branchProtection,
|
||||||
branchIsOutOfDate = JGitUtil.getShaByRef(owner, name, pullreq.branch) != Some(pullreq.commitIdFrom),
|
branchIsOutOfDate = JGitUtil.getShaByRef(repository.owner, repository.name, pullreq.branch) != Some(
|
||||||
needStatusCheck = context.loginAccount
|
pullreq.commitIdFrom
|
||||||
.map { u =>
|
),
|
||||||
branchProtection.needStatusCheck(u.userName)
|
needStatusCheck = context.loginAccount.forall { u =>
|
||||||
}
|
branchProtection.needStatusCheck(u.userName)
|
||||||
.getOrElse(true),
|
},
|
||||||
hasUpdatePermission = hasDeveloperRole(
|
hasUpdatePermission = hasDeveloperRole(
|
||||||
pullreq.requestUserName,
|
pullreq.requestUserName,
|
||||||
pullreq.requestRepositoryName,
|
pullreq.requestRepositoryName,
|
||||||
context.loginAccount
|
context.loginAccount
|
||||||
) &&
|
) &&
|
||||||
context.loginAccount
|
context.loginAccount.exists { u =>
|
||||||
.map { u =>
|
!getProtectedBranchInfo(
|
||||||
!getProtectedBranchInfo(
|
pullreq.requestUserName,
|
||||||
pullreq.requestUserName,
|
pullreq.requestRepositoryName,
|
||||||
pullreq.requestRepositoryName,
|
pullreq.requestBranch
|
||||||
pullreq.requestBranch
|
).needStatusCheck(u.userName)
|
||||||
).needStatusCheck(u.userName)
|
},
|
||||||
}
|
|
||||||
.getOrElse(false),
|
|
||||||
hasMergePermission = hasMergePermission,
|
hasMergePermission = hasMergePermission,
|
||||||
commitIdTo = pullreq.commitIdTo
|
commitIdTo = pullreq.commitIdTo
|
||||||
)
|
)
|
||||||
@@ -363,23 +373,22 @@ trait PullRequestsControllerBase extends ControllerBase {
|
|||||||
})
|
})
|
||||||
|
|
||||||
post("/:owner/:repository/pull/:id/merge", mergeForm)(writableUsersOnly { (form, repository) =>
|
post("/:owner/:repository/pull/:id/merge", mergeForm)(writableUsersOnly { (form, repository) =>
|
||||||
params("id").toIntOpt.flatMap { issueId =>
|
context.withLoginAccount { loginAccount =>
|
||||||
val owner = repository.owner
|
params("id").toIntOpt.flatMap { issueId =>
|
||||||
val name = repository.name
|
mergePullRequest(
|
||||||
|
repository,
|
||||||
mergePullRequest(
|
issueId,
|
||||||
repository,
|
loginAccount,
|
||||||
issueId,
|
form.message,
|
||||||
context.loginAccount.get,
|
form.strategy,
|
||||||
form.message,
|
form.isDraft,
|
||||||
form.strategy,
|
context.settings
|
||||||
form.isDraft,
|
) match {
|
||||||
context.settings
|
case Right(objectId) => redirect(s"/${repository.owner}/${repository.name}/pull/${issueId}")
|
||||||
) match {
|
case Left(message) => Some(BadRequest(message))
|
||||||
case Right(objectId) => redirect(s"/${owner}/${name}/pull/${issueId}")
|
}
|
||||||
case Left(message) => Some(BadRequest(message))
|
} getOrElse NotFound()
|
||||||
}
|
}
|
||||||
} getOrElse NotFound()
|
|
||||||
})
|
})
|
||||||
|
|
||||||
get("/:owner/:repository/compare")(referrersOnly { forkedRepository =>
|
get("/:owner/:repository/compare")(referrersOnly { forkedRepository =>
|
||||||
@@ -481,8 +490,7 @@ trait PullRequestsControllerBase extends ControllerBase {
|
|||||||
(repository.userName, repository.repositoryName, repository.defaultBranch)
|
(repository.userName, repository.repositoryName, repository.defaultBranch)
|
||||||
},
|
},
|
||||||
commits.flatten
|
commits.flatten
|
||||||
.map(commit => getCommitComments(forkedRepository.owner, forkedRepository.name, commit.id, false))
|
.flatMap(commit => getCommitComments(forkedRepository.owner, forkedRepository.name, commit.id, false))
|
||||||
.flatten
|
|
||||||
.toList,
|
.toList,
|
||||||
originId,
|
originId,
|
||||||
forkedId,
|
forkedId,
|
||||||
@@ -549,15 +557,14 @@ trait PullRequestsControllerBase extends ControllerBase {
|
|||||||
})
|
})
|
||||||
|
|
||||||
post("/:owner/:repository/pulls/new", pullRequestForm)(readableUsersOnly { (form, repository) =>
|
post("/:owner/:repository/pulls/new", pullRequestForm)(readableUsersOnly { (form, repository) =>
|
||||||
defining(repository.owner, repository.name) {
|
context.withLoginAccount {
|
||||||
case (owner, name) =>
|
loginAccount =>
|
||||||
val manageable = isManageable(repository)
|
val manageable = isManageable(repository)
|
||||||
val loginUserName = context.loginAccount.get.userName
|
|
||||||
|
|
||||||
val issueId = insertIssue(
|
val issueId = insertIssue(
|
||||||
owner = repository.owner,
|
owner = repository.owner,
|
||||||
repository = repository.name,
|
repository = repository.name,
|
||||||
loginUser = loginUserName,
|
loginUser = loginAccount.userName,
|
||||||
title = form.title,
|
title = form.title,
|
||||||
content = form.content,
|
content = form.content,
|
||||||
assignedUserName = if (manageable) form.assignedUserName else None,
|
assignedUserName = if (manageable) form.assignedUserName else None,
|
||||||
@@ -576,14 +583,14 @@ trait PullRequestsControllerBase extends ControllerBase {
|
|||||||
commitIdFrom = form.commitIdFrom,
|
commitIdFrom = form.commitIdFrom,
|
||||||
commitIdTo = form.commitIdTo,
|
commitIdTo = form.commitIdTo,
|
||||||
isDraft = form.isDraft,
|
isDraft = form.isDraft,
|
||||||
loginAccount = context.loginAccount.get,
|
loginAccount = loginAccount,
|
||||||
settings = context.settings
|
settings = context.settings
|
||||||
)
|
)
|
||||||
|
|
||||||
// insert labels
|
// insert labels
|
||||||
if (manageable) {
|
if (manageable) {
|
||||||
form.labelNames.foreach { value =>
|
form.labelNames.foreach { value =>
|
||||||
val labels = getLabels(owner, name)
|
val labels = getLabels(repository.owner, repository.name)
|
||||||
value.split(",").foreach { labelName =>
|
value.split(",").foreach { labelName =>
|
||||||
labels.find(_.labelName == labelName).map { label =>
|
labels.find(_.labelName == labelName).map { label =>
|
||||||
registerIssueLabel(repository.owner, repository.name, issueId, label.labelId)
|
registerIssueLabel(repository.owner, repository.name, issueId, label.labelId)
|
||||||
@@ -592,7 +599,7 @@ trait PullRequestsControllerBase extends ControllerBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
redirect(s"/${owner}/${name}/pull/${issueId}")
|
redirect(s"/${repository.owner}/${repository.name}/pull/${issueId}")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -639,44 +646,42 @@ trait PullRequestsControllerBase extends ControllerBase {
|
|||||||
html.proposals(proposedBranches, targetRepository, repository)
|
html.proposals(proposedBranches, targetRepository, repository)
|
||||||
})
|
})
|
||||||
|
|
||||||
private def searchPullRequests(userName: Option[String], repository: RepositoryService.RepositoryInfo) =
|
private def searchPullRequests(userName: Option[String], repository: RepositoryService.RepositoryInfo) = {
|
||||||
defining(repository.owner, repository.name) {
|
val page = IssueSearchCondition.page(request)
|
||||||
case (owner, repoName) =>
|
// retrieve search condition
|
||||||
val page = IssueSearchCondition.page(request)
|
val condition = IssueSearchCondition(request)
|
||||||
// retrieve search condition
|
// search issues
|
||||||
val condition = IssueSearchCondition(request)
|
val issues = searchIssue(
|
||||||
// search issues
|
condition,
|
||||||
val issues = searchIssue(
|
IssueSearchOption.PullRequests,
|
||||||
condition,
|
(page - 1) * PullRequestLimit,
|
||||||
IssueSearchOption.PullRequests,
|
PullRequestLimit,
|
||||||
(page - 1) * PullRequestLimit,
|
repository.owner -> repository.name
|
||||||
PullRequestLimit,
|
)
|
||||||
owner -> repoName
|
// commit status
|
||||||
)
|
val status = issues.map { issue =>
|
||||||
// commit status
|
issue.commitId.flatMap { commitId =>
|
||||||
val status = issues.map { issue =>
|
getCommitStatusWithSummary(repository.owner, repository.name, commitId)
|
||||||
issue.commitId.flatMap { commitId =>
|
}
|
||||||
getCommitStatusWithSummary(owner, repoName, commitId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
gitbucket.core.issues.html.list(
|
|
||||||
"pulls",
|
|
||||||
issues.zip(status),
|
|
||||||
page,
|
|
||||||
getAssignableUserNames(owner, repoName),
|
|
||||||
getMilestones(owner, repoName),
|
|
||||||
getPriorities(owner, repoName),
|
|
||||||
getLabels(owner, repoName),
|
|
||||||
countIssue(condition.copy(state = "open"), IssueSearchOption.PullRequests, owner -> repoName),
|
|
||||||
countIssue(condition.copy(state = "closed"), IssueSearchOption.PullRequests, owner -> repoName),
|
|
||||||
condition,
|
|
||||||
repository,
|
|
||||||
isEditable(repository),
|
|
||||||
isManageable(repository)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
gitbucket.core.issues.html.list(
|
||||||
|
"pulls",
|
||||||
|
issues.zip(status),
|
||||||
|
page,
|
||||||
|
getAssignableUserNames(repository.owner, repository.name),
|
||||||
|
getMilestones(repository.owner, repository.name),
|
||||||
|
getPriorities(repository.owner, repository.name),
|
||||||
|
getLabels(repository.owner, repository.name),
|
||||||
|
countIssue(condition.copy(state = "open"), IssueSearchOption.PullRequests, repository.owner -> repository.name),
|
||||||
|
countIssue(condition.copy(state = "closed"), IssueSearchOption.PullRequests, repository.owner -> repository.name),
|
||||||
|
condition,
|
||||||
|
repository,
|
||||||
|
isEditable(repository),
|
||||||
|
isManageable(repository)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests whether an logged-in user can manage pull requests.
|
* Tests whether an logged-in user can manage pull requests.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -63,8 +63,8 @@ trait ReleaseControllerBase extends ControllerBase {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
get("/:owner/:repository/releases/:tag")(referrersOnly { repository =>
|
get("/:owner/:repository/releases/*")(referrersOnly { repository =>
|
||||||
val tagName = params("tag")
|
val tagName = multiParams("splat").head
|
||||||
getRelease(repository.owner, repository.name, tagName)
|
getRelease(repository.owner, repository.name, tagName)
|
||||||
.map { release =>
|
.map { release =>
|
||||||
html.release(
|
html.release(
|
||||||
@@ -77,8 +77,8 @@ trait ReleaseControllerBase extends ControllerBase {
|
|||||||
.getOrElse(NotFound())
|
.getOrElse(NotFound())
|
||||||
})
|
})
|
||||||
|
|
||||||
get("/:owner/:repository/releases/:tag/assets/:fileId")(referrersOnly { repository =>
|
get("/:owner/:repository/releases/*/assets/:fileId")(referrersOnly { repository =>
|
||||||
val tagName = params("tag")
|
val tagName = multiParams("splat").head
|
||||||
val fileId = params("fileId")
|
val fileId = params("fileId")
|
||||||
(for {
|
(for {
|
||||||
_ <- repository.tags.find(_.name == tagName)
|
_ <- repository.tags.find(_.name == tagName)
|
||||||
@@ -93,8 +93,8 @@ trait ReleaseControllerBase extends ControllerBase {
|
|||||||
}).getOrElse(NotFound())
|
}).getOrElse(NotFound())
|
||||||
})
|
})
|
||||||
|
|
||||||
get("/:owner/:repository/releases/:tag/create")(writableUsersOnly { repository =>
|
get("/:owner/:repository/releases/*/create")(writableUsersOnly { repository =>
|
||||||
val tagName = params("tag")
|
val tagName = multiParams("splat").head
|
||||||
val previousTags = repository.tags.takeWhile(_.name != tagName).reverse
|
val previousTags = repository.tags.takeWhile(_.name != tagName).reverse
|
||||||
|
|
||||||
repository.tags
|
repository.tags
|
||||||
@@ -105,38 +105,40 @@ trait ReleaseControllerBase extends ControllerBase {
|
|||||||
.getOrElse(NotFound())
|
.getOrElse(NotFound())
|
||||||
})
|
})
|
||||||
|
|
||||||
post("/:owner/:repository/releases/:tag/create", releaseForm)(writableUsersOnly { (form, repository) =>
|
post("/:owner/:repository/releases/*/create", releaseForm)(writableUsersOnly { (form, repository) =>
|
||||||
val tagName = params("tag")
|
context.withLoginAccount {
|
||||||
val loginAccount = context.loginAccount.get
|
loginAccount =>
|
||||||
|
val tagName = multiParams("splat").head
|
||||||
|
|
||||||
// Insert into RELEASE
|
// Insert into RELEASE
|
||||||
createRelease(repository.owner, repository.name, form.name, form.content, tagName, loginAccount)
|
createRelease(repository.owner, repository.name, form.name, form.content, tagName, loginAccount)
|
||||||
|
|
||||||
// Insert into RELEASE_ASSET
|
// Insert into RELEASE_ASSET
|
||||||
val files = params.toMap.collect {
|
val files = params.toMap.collect {
|
||||||
case (name, value) if name.startsWith("file:") =>
|
case (name, value) if name.startsWith("file:") =>
|
||||||
val Array(_, fileId) = name.split(":")
|
val Array(_, fileId) = name.split(":")
|
||||||
(fileId, value)
|
(fileId, value)
|
||||||
|
}
|
||||||
|
files.foreach {
|
||||||
|
case (fileId, fileName) =>
|
||||||
|
val size =
|
||||||
|
new File(
|
||||||
|
getReleaseFilesDir(repository.owner, repository.name),
|
||||||
|
FileUtil.checkFilename(tagName + "/" + fileId)
|
||||||
|
).length
|
||||||
|
createReleaseAsset(repository.owner, repository.name, tagName, fileId, fileName, size, loginAccount)
|
||||||
|
}
|
||||||
|
|
||||||
|
val releaseInfo = ReleaseInfo(repository.owner, repository.name, loginAccount.userName, form.name, tagName)
|
||||||
|
recordActivity(releaseInfo)
|
||||||
|
|
||||||
|
redirect(s"/${repository.owner}/${repository.name}/releases/${tagName}")
|
||||||
}
|
}
|
||||||
files.foreach {
|
|
||||||
case (fileId, fileName) =>
|
|
||||||
val size =
|
|
||||||
new File(
|
|
||||||
getReleaseFilesDir(repository.owner, repository.name),
|
|
||||||
FileUtil.checkFilename(tagName + "/" + fileId)
|
|
||||||
).length
|
|
||||||
createReleaseAsset(repository.owner, repository.name, tagName, fileId, fileName, size, loginAccount)
|
|
||||||
}
|
|
||||||
|
|
||||||
val releaseInfo = ReleaseInfo(repository.owner, repository.name, loginAccount.userName, form.name, tagName)
|
|
||||||
recordActivity(releaseInfo)
|
|
||||||
|
|
||||||
redirect(s"/${repository.owner}/${repository.name}/releases/${tagName}")
|
|
||||||
})
|
})
|
||||||
|
|
||||||
get("/:owner/:repository/changelog/*...*")(writableUsersOnly { repository =>
|
get("/:owner/:repository/changelog/*...*")(writableUsersOnly { repository =>
|
||||||
val Seq(previousTag, currentTag) = multiParams("splat")
|
val Seq(previousTag, currentTag) = multiParams("splat")
|
||||||
val previousTagId = repository.tags.collectFirst { case x if x.name == previousTag => x.id }.getOrElse("")
|
val previousTagId = repository.tags.collectFirst { case x if x.name == previousTag => x.commitId }.getOrElse("")
|
||||||
|
|
||||||
val commitLog = Using.resource(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
|
val commits = JGitUtil.getCommitLog(git, previousTagId, currentTag).reverse
|
||||||
@@ -150,8 +152,8 @@ trait ReleaseControllerBase extends ControllerBase {
|
|||||||
commitLog
|
commitLog
|
||||||
})
|
})
|
||||||
|
|
||||||
get("/:owner/:repository/releases/:tag/edit")(writableUsersOnly { repository =>
|
get("/:owner/:repository/releases/*/edit")(writableUsersOnly { repository =>
|
||||||
val tagName = params("tag")
|
val tagName = multiParams("splat").head
|
||||||
val previousTags = repository.tags.takeWhile(_.name != tagName).reverse
|
val previousTags = repository.tags.takeWhile(_.name != tagName).reverse
|
||||||
|
|
||||||
(for {
|
(for {
|
||||||
@@ -168,52 +170,54 @@ trait ReleaseControllerBase extends ControllerBase {
|
|||||||
}).getOrElse(NotFound())
|
}).getOrElse(NotFound())
|
||||||
})
|
})
|
||||||
|
|
||||||
post("/:owner/:repository/releases/:tag/edit", releaseForm)(writableUsersOnly {
|
post("/:owner/:repository/releases/*/edit", releaseForm)(writableUsersOnly { (form, repository) =>
|
||||||
(form, repository) =>
|
context.withLoginAccount {
|
||||||
val tagName = params("tag")
|
loginAccount =>
|
||||||
val loginAccount = context.loginAccount.get
|
val tagName = multiParams("splat").head
|
||||||
|
|
||||||
getRelease(repository.owner, repository.name, tagName)
|
getRelease(repository.owner, repository.name, tagName)
|
||||||
.map { release =>
|
.map {
|
||||||
// Update RELEASE
|
release =>
|
||||||
updateRelease(repository.owner, repository.name, tagName, form.name, form.content)
|
// Update RELEASE
|
||||||
|
updateRelease(repository.owner, repository.name, tagName, form.name, form.content)
|
||||||
|
|
||||||
// Delete and Insert RELEASE_ASSET
|
// Delete and Insert RELEASE_ASSET
|
||||||
val assets = getReleaseAssets(repository.owner, repository.name, tagName)
|
val assets = getReleaseAssets(repository.owner, repository.name, tagName)
|
||||||
deleteReleaseAssets(repository.owner, repository.name, tagName)
|
deleteReleaseAssets(repository.owner, repository.name, tagName)
|
||||||
|
|
||||||
val files = params.toMap.collect {
|
val files = params.toMap.collect {
|
||||||
case (name, value) if name.startsWith("file:") =>
|
case (name, value) if name.startsWith("file:") =>
|
||||||
val Array(_, fileId) = name.split(":")
|
val Array(_, fileId) = name.split(":")
|
||||||
(fileId, value)
|
(fileId, value)
|
||||||
|
}
|
||||||
|
files.foreach {
|
||||||
|
case (fileId, fileName) =>
|
||||||
|
val size =
|
||||||
|
new File(
|
||||||
|
getReleaseFilesDir(repository.owner, repository.name),
|
||||||
|
FileUtil.checkFilename(tagName + "/" + fileId)
|
||||||
|
).length
|
||||||
|
createReleaseAsset(repository.owner, repository.name, tagName, fileId, fileName, size, loginAccount)
|
||||||
|
}
|
||||||
|
|
||||||
|
assets.foreach { asset =>
|
||||||
|
if (!files.exists { case (fileId, _) => fileId == asset.fileName }) {
|
||||||
|
val file = new File(
|
||||||
|
getReleaseFilesDir(repository.owner, repository.name),
|
||||||
|
FileUtil.checkFilename(release.tag + "/" + asset.fileName)
|
||||||
|
)
|
||||||
|
FileUtils.forceDelete(file)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
redirect(s"/${release.userName}/${release.repositoryName}/releases/${tagName}")
|
||||||
}
|
}
|
||||||
files.foreach {
|
.getOrElse(NotFound())
|
||||||
case (fileId, fileName) =>
|
}
|
||||||
val size =
|
|
||||||
new File(
|
|
||||||
getReleaseFilesDir(repository.owner, repository.name),
|
|
||||||
FileUtil.checkFilename(tagName + "/" + fileId)
|
|
||||||
).length
|
|
||||||
createReleaseAsset(repository.owner, repository.name, tagName, fileId, fileName, size, loginAccount)
|
|
||||||
}
|
|
||||||
|
|
||||||
assets.foreach { asset =>
|
|
||||||
if (!files.exists { case (fileId, _) => fileId == asset.fileName }) {
|
|
||||||
val file = new File(
|
|
||||||
getReleaseFilesDir(repository.owner, repository.name),
|
|
||||||
FileUtil.checkFilename(release.tag + "/" + asset.fileName)
|
|
||||||
)
|
|
||||||
FileUtils.forceDelete(file)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
redirect(s"/${release.userName}/${release.repositoryName}/releases/${tagName}")
|
|
||||||
}
|
|
||||||
.getOrElse(NotFound())
|
|
||||||
})
|
})
|
||||||
|
|
||||||
post("/:owner/:repository/releases/:tag/delete")(writableUsersOnly { repository =>
|
post("/:owner/:repository/releases/*/delete")(writableUsersOnly { repository =>
|
||||||
val tagName = params("tag")
|
val tagName = multiParams("splat").head
|
||||||
getRelease(repository.owner, repository.name, tagName).foreach { release =>
|
getRelease(repository.owner, repository.name, tagName).foreach { release =>
|
||||||
FileUtils.deleteDirectory(
|
FileUtils.deleteDirectory(
|
||||||
new File(getReleaseFilesDir(repository.owner, repository.name), FileUtil.checkFilename(release.tag))
|
new File(getReleaseFilesDir(repository.owner, repository.name), FileUtil.checkFilename(release.tag))
|
||||||
@@ -224,7 +228,6 @@ trait ReleaseControllerBase extends ControllerBase {
|
|||||||
})
|
})
|
||||||
|
|
||||||
private def fetchReleases(repository: RepositoryService.RepositoryInfo, page: Int) = {
|
private def fetchReleases(repository: RepositoryService.RepositoryInfo, page: Int) = {
|
||||||
|
|
||||||
import gitbucket.core.service.ReleaseService._
|
import gitbucket.core.service.ReleaseService._
|
||||||
|
|
||||||
val (offset, limit) = ((page - 1) * ReleaseLimit, ReleaseLimit)
|
val (offset, limit) = ((page - 1) * ReleaseLimit, ReleaseLimit)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
package gitbucket.core.controller
|
package gitbucket.core.controller
|
||||||
|
|
||||||
import java.time.{LocalDateTime, ZoneId, ZoneOffset}
|
import java.time.{LocalDateTime, ZoneOffset}
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
|
|
||||||
import gitbucket.core.settings.html
|
import gitbucket.core.settings.html
|
||||||
@@ -57,7 +57,8 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
|||||||
externalWikiUrl: Option[String],
|
externalWikiUrl: Option[String],
|
||||||
allowFork: Boolean,
|
allowFork: Boolean,
|
||||||
mergeOptions: Seq[String],
|
mergeOptions: Seq[String],
|
||||||
defaultMergeOption: String
|
defaultMergeOption: String,
|
||||||
|
safeMode: Boolean
|
||||||
)
|
)
|
||||||
|
|
||||||
val optionsForm = mapping(
|
val optionsForm = mapping(
|
||||||
@@ -69,7 +70,8 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
|||||||
"externalWikiUrl" -> trim(label("External Wiki URL", optional(text(maxlength(200))))),
|
"externalWikiUrl" -> trim(label("External Wiki URL", optional(text(maxlength(200))))),
|
||||||
"allowFork" -> trim(label("Allow Forking", boolean())),
|
"allowFork" -> trim(label("Allow Forking", boolean())),
|
||||||
"mergeOptions" -> mergeOptions,
|
"mergeOptions" -> mergeOptions,
|
||||||
"defaultMergeOption" -> trim(label("Default merge strategy", text(required)))
|
"defaultMergeOption" -> trim(label("Default merge strategy", text(required))),
|
||||||
|
"safeMode" -> trim(label("XSS protection", boolean()))
|
||||||
)(OptionsForm.apply).verifying { form =>
|
)(OptionsForm.apply).verifying { form =>
|
||||||
if (!form.mergeOptions.contains(form.defaultMergeOption)) {
|
if (!form.mergeOptions.contains(form.defaultMergeOption)) {
|
||||||
Seq("defaultMergeOption" -> s"This merge strategy isn't enabled.")
|
Seq("defaultMergeOption" -> s"This merge strategy isn't enabled.")
|
||||||
@@ -150,7 +152,8 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
|||||||
form.externalWikiUrl,
|
form.externalWikiUrl,
|
||||||
form.allowFork,
|
form.allowFork,
|
||||||
form.mergeOptions,
|
form.mergeOptions,
|
||||||
form.defaultMergeOption
|
form.defaultMergeOption,
|
||||||
|
form.safeMode
|
||||||
)
|
)
|
||||||
flash.update("info", "Repository settings has been updated.")
|
flash.update("info", "Repository settings has been updated.")
|
||||||
redirect(s"/${repository.owner}/${repository.name}/settings/options")
|
redirect(s"/${repository.owner}/${repository.name}/settings/options")
|
||||||
@@ -342,7 +345,7 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
|||||||
.map(
|
.map(
|
||||||
res =>
|
res =>
|
||||||
Map(
|
Map(
|
||||||
"status" -> res.getStatusLine(),
|
"status" -> res.getStatusLine.getStatusCode,
|
||||||
"body" -> EntityUtils.toString(res.getEntity()),
|
"body" -> EntityUtils.toString(res.getEntity()),
|
||||||
"headers" -> _headers(res.getAllHeaders())
|
"headers" -> _headers(res.getAllHeaders())
|
||||||
)
|
)
|
||||||
@@ -385,54 +388,62 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
|||||||
* Rename repository.
|
* Rename repository.
|
||||||
*/
|
*/
|
||||||
post("/:owner/:repository/settings/rename", renameForm)(ownerOnly { (form, repository) =>
|
post("/:owner/:repository/settings/rename", renameForm)(ownerOnly { (form, repository) =>
|
||||||
if (context.settings.repositoryOperation.rename || context.loginAccount.get.isAdmin) {
|
context.withLoginAccount {
|
||||||
if (repository.name != form.repositoryName) {
|
loginAccount =>
|
||||||
// Update database and move git repository
|
if (context.settings.repositoryOperation.rename || loginAccount.isAdmin) {
|
||||||
renameRepository(repository.owner, repository.name, repository.owner, form.repositoryName)
|
if (repository.name != form.repositoryName) {
|
||||||
// Record activity log
|
// Update database and move git repository
|
||||||
val renameInfo = RenameRepositoryInfo(
|
renameRepository(repository.owner, repository.name, repository.owner, form.repositoryName)
|
||||||
repository.owner,
|
// Record activity log
|
||||||
form.repositoryName,
|
val renameInfo = RenameRepositoryInfo(
|
||||||
context.loginAccount.get.userName,
|
repository.owner,
|
||||||
repository.name
|
form.repositoryName,
|
||||||
)
|
loginAccount.userName,
|
||||||
recordActivity(renameInfo)
|
repository.name
|
||||||
}
|
)
|
||||||
redirect(s"/${repository.owner}/${form.repositoryName}")
|
recordActivity(renameInfo)
|
||||||
} else Forbidden()
|
}
|
||||||
|
redirect(s"/${repository.owner}/${form.repositoryName}")
|
||||||
|
} else Forbidden()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Transfer repository ownership.
|
* Transfer repository ownership.
|
||||||
*/
|
*/
|
||||||
post("/:owner/:repository/settings/transfer", transferForm)(ownerOnly { (form, repository) =>
|
post("/:owner/:repository/settings/transfer", transferForm)(ownerOnly { (form, repository) =>
|
||||||
if (context.settings.repositoryOperation.transfer || context.loginAccount.get.isAdmin) {
|
context.withLoginAccount {
|
||||||
// Change repository owner
|
loginAccount =>
|
||||||
if (repository.owner != form.newOwner) {
|
if (context.settings.repositoryOperation.transfer || loginAccount.isAdmin) {
|
||||||
// Update database and move git repository
|
// Change repository owner
|
||||||
renameRepository(repository.owner, repository.name, form.newOwner, repository.name)
|
if (repository.owner != form.newOwner) {
|
||||||
// Record activity log
|
// Update database and move git repository
|
||||||
val renameInfo = RenameRepositoryInfo(
|
renameRepository(repository.owner, repository.name, form.newOwner, repository.name)
|
||||||
form.newOwner,
|
// Record activity log
|
||||||
repository.name,
|
val renameInfo = RenameRepositoryInfo(
|
||||||
context.loginAccount.get.userName,
|
form.newOwner,
|
||||||
repository.owner
|
repository.name,
|
||||||
)
|
loginAccount.userName,
|
||||||
recordActivity(renameInfo)
|
repository.owner
|
||||||
}
|
)
|
||||||
redirect(s"/${form.newOwner}/${repository.name}")
|
recordActivity(renameInfo)
|
||||||
} else Forbidden()
|
}
|
||||||
|
redirect(s"/${form.newOwner}/${repository.name}")
|
||||||
|
} else Forbidden()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete the repository.
|
* Delete the repository.
|
||||||
*/
|
*/
|
||||||
post("/:owner/:repository/settings/delete")(ownerOnly { repository =>
|
post("/:owner/:repository/settings/delete")(ownerOnly { repository =>
|
||||||
if (context.settings.repositoryOperation.delete || context.loginAccount.get.isAdmin) {
|
context.withLoginAccount { loginAccount =>
|
||||||
// Delete the repository and related files
|
if (context.settings.repositoryOperation.delete || loginAccount.isAdmin) {
|
||||||
deleteRepository(repository.repository)
|
// Delete the repository and related files
|
||||||
redirect(s"/${repository.owner}")
|
deleteRepository(repository.repository)
|
||||||
} else Forbidden()
|
redirect(s"/${repository.owner}")
|
||||||
|
} else Forbidden()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,6 @@
|
|||||||
package gitbucket.core.controller
|
package gitbucket.core.controller
|
||||||
|
|
||||||
import java.io.FileInputStream
|
import java.io.FileInputStream
|
||||||
|
|
||||||
import gitbucket.core.admin.html
|
import gitbucket.core.admin.html
|
||||||
import gitbucket.core.plugin.PluginRegistry
|
import gitbucket.core.plugin.PluginRegistry
|
||||||
import gitbucket.core.service.SystemSettingsService._
|
import gitbucket.core.service.SystemSettingsService._
|
||||||
@@ -9,7 +8,6 @@ import gitbucket.core.service.{AccountService, RepositoryService}
|
|||||||
import gitbucket.core.ssh.SshServer
|
import gitbucket.core.ssh.SshServer
|
||||||
import gitbucket.core.util.Implicits._
|
import gitbucket.core.util.Implicits._
|
||||||
import gitbucket.core.util.StringUtil._
|
import gitbucket.core.util.StringUtil._
|
||||||
import gitbucket.core.util.SyntaxSugars._
|
|
||||||
import gitbucket.core.util.{AdminAuthenticator, Mailer}
|
import gitbucket.core.util.{AdminAuthenticator, Mailer}
|
||||||
import org.apache.commons.io.IOUtils
|
import org.apache.commons.io.IOUtils
|
||||||
import org.apache.commons.mail.EmailException
|
import org.apache.commons.mail.EmailException
|
||||||
@@ -51,8 +49,20 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
|
|||||||
"limitVisibleRepositories" -> trim(label("limitVisibleRepositories", boolean())),
|
"limitVisibleRepositories" -> trim(label("limitVisibleRepositories", boolean())),
|
||||||
"ssh" -> mapping(
|
"ssh" -> mapping(
|
||||||
"enabled" -> trim(label("SSH access", boolean())),
|
"enabled" -> trim(label("SSH access", boolean())),
|
||||||
"host" -> trim(label("SSH host", optional(text()))),
|
"bindAddress" -> mapping(
|
||||||
"port" -> trim(label("SSH port", optional(number())))
|
"host" -> trim(label("Bind SSH host", optional(text()))),
|
||||||
|
"port" -> trim(label("Bind SSH port", optional(number()))),
|
||||||
|
)(
|
||||||
|
(hostOption, portOption) =>
|
||||||
|
hostOption.map(h => SshAddress(h, portOption.getOrElse(DefaultSshPort), GenericSshUser))
|
||||||
|
),
|
||||||
|
"publicAddress" -> mapping(
|
||||||
|
"host" -> trim(label("Public SSH host", optional(text()))),
|
||||||
|
"port" -> trim(label("Public SSH port", optional(number()))),
|
||||||
|
)(
|
||||||
|
(hostOption, portOption) =>
|
||||||
|
hostOption.map(h => SshAddress(h, portOption.getOrElse(PublicSshPort), GenericSshUser))
|
||||||
|
),
|
||||||
)(Ssh.apply),
|
)(Ssh.apply),
|
||||||
"useSMTP" -> trim(label("SMTP", boolean())),
|
"useSMTP" -> trim(label("SMTP", boolean())),
|
||||||
"smtp" -> optionalIfNotChecked(
|
"smtp" -> optionalIfNotChecked(
|
||||||
@@ -117,8 +127,8 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
|
|||||||
if (settings.ssh.enabled && settings.baseUrl.isEmpty) {
|
if (settings.ssh.enabled && settings.baseUrl.isEmpty) {
|
||||||
Some("baseUrl" -> "Base URL is required if SSH access is enabled.")
|
Some("baseUrl" -> "Base URL is required if SSH access is enabled.")
|
||||||
} else None,
|
} else None,
|
||||||
if (settings.ssh.enabled && settings.ssh.sshHost.isEmpty) {
|
if (settings.ssh.enabled && settings.ssh.bindAddress.isEmpty) {
|
||||||
Some("sshHost" -> "SSH host is required if SSH access is enabled.")
|
Some("ssh.bindAddress.host" -> "SSH bind host is required if SSH access is enabled.")
|
||||||
} else None
|
} else None
|
||||||
).flatten
|
).flatten
|
||||||
}
|
}
|
||||||
@@ -187,7 +197,7 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
|
|||||||
|
|
||||||
val newUserForm = mapping(
|
val newUserForm = mapping(
|
||||||
"userName" -> trim(label("Username", text(required, maxlength(100), identifier, uniqueUserName, reservedNames))),
|
"userName" -> trim(label("Username", text(required, maxlength(100), identifier, uniqueUserName, reservedNames))),
|
||||||
"password" -> trim(label("Password", text(required, maxlength(20)))),
|
"password" -> trim(label("Password", text(required, maxlength(40)))),
|
||||||
"fullName" -> trim(label("Full Name", text(required, maxlength(100)))),
|
"fullName" -> trim(label("Full Name", text(required, maxlength(100)))),
|
||||||
"mailAddress" -> trim(label("Mail Address", text(required, maxlength(100), uniqueMailAddress()))),
|
"mailAddress" -> trim(label("Mail Address", text(required, maxlength(100), uniqueMailAddress()))),
|
||||||
"extraMailAddresses" -> list(
|
"extraMailAddresses" -> list(
|
||||||
@@ -201,7 +211,7 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
|
|||||||
|
|
||||||
val editUserForm = mapping(
|
val editUserForm = mapping(
|
||||||
"userName" -> trim(label("Username", text(required, maxlength(100), identifier))),
|
"userName" -> trim(label("Username", text(required, maxlength(100), identifier))),
|
||||||
"password" -> trim(label("Password", optional(text(maxlength(20))))),
|
"password" -> trim(label("Password", optional(text(maxlength(40))))),
|
||||||
"fullName" -> trim(label("Full Name", text(required, maxlength(100)))),
|
"fullName" -> trim(label("Full Name", text(required, maxlength(100)))),
|
||||||
"mailAddress" -> trim(label("Mail Address", text(required, maxlength(100), uniqueMailAddress("userName")))),
|
"mailAddress" -> trim(label("Mail Address", text(required, maxlength(100), uniqueMailAddress("userName")))),
|
||||||
"extraMailAddresses" -> list(
|
"extraMailAddresses" -> list(
|
||||||
@@ -309,12 +319,13 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
|
|||||||
post("/admin/system", form)(adminOnly { form =>
|
post("/admin/system", form)(adminOnly { form =>
|
||||||
saveSystemSettings(form)
|
saveSystemSettings(form)
|
||||||
|
|
||||||
if (form.sshAddress != context.settings.sshAddress) {
|
if (form.ssh.bindAddress != context.settings.sshBindAddress || form.ssh.publicAddress != context.settings.sshPublicAddress) {
|
||||||
SshServer.stop()
|
SshServer.stop()
|
||||||
for {
|
for {
|
||||||
sshAddress <- form.sshAddress
|
bindAddress <- form.ssh.bindAddress
|
||||||
|
publicAddress <- form.ssh.publicAddress.orElse(form.ssh.bindAddress)
|
||||||
baseUrl <- form.baseUrl
|
baseUrl <- form.baseUrl
|
||||||
} SshServer.start(sshAddress, baseUrl)
|
} SshServer.start(bindAddress, publicAddress, baseUrl)
|
||||||
}
|
}
|
||||||
|
|
||||||
flash.update("info", "System settings has been updated.")
|
flash.update("info", "System settings has been updated.")
|
||||||
@@ -362,8 +373,8 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
|
|||||||
})
|
})
|
||||||
|
|
||||||
get("/admin/users")(adminOnly {
|
get("/admin/users")(adminOnly {
|
||||||
val includeRemoved = params.get("includeRemoved").map(_.toBoolean).getOrElse(false)
|
val includeRemoved = params.get("includeRemoved").exists(_.toBoolean)
|
||||||
val includeGroups = params.get("includeGroups").map(_.toBoolean).getOrElse(false)
|
val includeGroups = params.get("includeGroups").exists(_.toBoolean)
|
||||||
val users = getAllUsers(includeRemoved, includeGroups)
|
val users = getAllUsers(includeRemoved, includeGroups)
|
||||||
val members = users.collect {
|
val members = users.collect {
|
||||||
case account if (account.isGroupAccount) =>
|
case account if (account.isGroupAccount) =>
|
||||||
@@ -463,31 +474,28 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
|
|||||||
})
|
})
|
||||||
|
|
||||||
get("/admin/users/:groupName/_editgroup")(adminOnly {
|
get("/admin/users/:groupName/_editgroup")(adminOnly {
|
||||||
defining(params("groupName")) { groupName =>
|
val groupName = params("groupName")
|
||||||
html.usergroup(getAccountByUserName(groupName, true), getGroupMembers(groupName))
|
html.usergroup(getAccountByUserName(groupName, true), getGroupMembers(groupName))
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
post("/admin/users/:groupName/_editgroup", editGroupForm)(adminOnly { form =>
|
post("/admin/users/:groupName/_editgroup", editGroupForm)(adminOnly { form =>
|
||||||
defining(
|
val groupName = params("groupName")
|
||||||
params("groupName"),
|
val members = form.members
|
||||||
form.members
|
.split(",")
|
||||||
.split(",")
|
.map {
|
||||||
.map {
|
_.split(":") match {
|
||||||
_.split(":") match {
|
case Array(userName, isManager) => (userName, isManager.toBoolean)
|
||||||
case Array(userName, isManager) => (userName, isManager.toBoolean)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
.toList
|
}
|
||||||
) {
|
.toList
|
||||||
case (groupName, members) =>
|
|
||||||
getAccountByUserName(groupName, true).map {
|
|
||||||
account =>
|
|
||||||
updateGroup(groupName, form.description, form.url, form.isRemoved)
|
|
||||||
|
|
||||||
if (form.isRemoved) {
|
getAccountByUserName(groupName, true).map {
|
||||||
// Remove from GROUP_MEMBER
|
account =>
|
||||||
updateGroupMembers(form.groupName, Nil)
|
updateGroup(groupName, form.description, form.url, form.isRemoved)
|
||||||
|
|
||||||
|
if (form.isRemoved) {
|
||||||
|
// Remove from GROUP_MEMBER
|
||||||
|
updateGroupMembers(form.groupName, Nil)
|
||||||
// // Remove repositories
|
// // Remove repositories
|
||||||
// getRepositoryNamesOfUser(form.groupName).foreach { repositoryName =>
|
// getRepositoryNamesOfUser(form.groupName).foreach { repositoryName =>
|
||||||
// deleteRepository(groupName, repositoryName)
|
// deleteRepository(groupName, repositoryName)
|
||||||
@@ -495,9 +503,9 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
|
|||||||
// FileUtils.deleteDirectory(getWikiRepositoryDir(groupName, repositoryName))
|
// FileUtils.deleteDirectory(getWikiRepositoryDir(groupName, repositoryName))
|
||||||
// FileUtils.deleteDirectory(getTemporaryDir(groupName, repositoryName))
|
// FileUtils.deleteDirectory(getTemporaryDir(groupName, repositoryName))
|
||||||
// }
|
// }
|
||||||
} else {
|
} else {
|
||||||
// Update GROUP_MEMBER
|
// Update GROUP_MEMBER
|
||||||
updateGroupMembers(form.groupName, members)
|
updateGroupMembers(form.groupName, members)
|
||||||
// // Update COLLABORATOR for group repositories
|
// // Update COLLABORATOR for group repositories
|
||||||
// getRepositoryNamesOfUser(form.groupName).foreach { repositoryName =>
|
// getRepositoryNamesOfUser(form.groupName).foreach { repositoryName =>
|
||||||
// removeCollaborators(form.groupName, repositoryName)
|
// removeCollaborators(form.groupName, repositoryName)
|
||||||
@@ -505,13 +513,12 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
|
|||||||
// addCollaborator(form.groupName, repositoryName, userName)
|
// addCollaborator(form.groupName, repositoryName, userName)
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
updateImage(form.groupName, form.fileId, form.clearImage)
|
updateImage(form.groupName, form.fileId, form.clearImage)
|
||||||
redirect("/admin/users")
|
redirect("/admin/users")
|
||||||
|
|
||||||
} getOrElse NotFound()
|
} getOrElse NotFound()
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
get("/admin/data")(adminOnly {
|
get("/admin/data")(adminOnly {
|
||||||
@@ -559,12 +566,11 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
|
|||||||
protected def disableByNotYourself(paramName: String): Constraint =
|
protected def disableByNotYourself(paramName: String): Constraint =
|
||||||
new Constraint() {
|
new Constraint() {
|
||||||
override def validate(name: String, value: String, messages: Messages): Option[String] = {
|
override def validate(name: String, value: String, messages: Messages): Option[String] = {
|
||||||
params.get(paramName).flatMap { userName =>
|
for {
|
||||||
if (userName == context.loginAccount.get.userName && params.get("removed") == Some("true"))
|
userName <- params.get(paramName)
|
||||||
Some("You can't disable your account yourself")
|
loginAccount <- context.loginAccount
|
||||||
else
|
if userName == loginAccount.userName && params.get("removed") == Some("true")
|
||||||
None
|
} yield "You can't disable your account yourself"
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -136,32 +136,38 @@ trait WikiControllerBase extends ControllerBase {
|
|||||||
})
|
})
|
||||||
|
|
||||||
get("/:owner/:repository/wiki/:page/_revert/:commitId")(readableUsersOnly { repository =>
|
get("/:owner/:repository/wiki/:page/_revert/:commitId")(readableUsersOnly { repository =>
|
||||||
if (isEditable(repository)) {
|
context.withLoginAccount {
|
||||||
val pageName = StringUtil.urlDecode(params("page"))
|
loginAccount =>
|
||||||
val Array(from, to) = params("commitId").split("\\.\\.\\.")
|
if (isEditable(repository)) {
|
||||||
|
val pageName = StringUtil.urlDecode(params("page"))
|
||||||
|
val Array(from, to) = params("commitId").split("\\.\\.\\.")
|
||||||
|
|
||||||
if (revertWikiPage(repository.owner, repository.name, from, to, context.loginAccount.get, Some(pageName))) {
|
if (revertWikiPage(repository.owner, repository.name, from, to, loginAccount, Some(pageName))) {
|
||||||
redirect(s"/${repository.owner}/${repository.name}/wiki/${StringUtil.urlEncode(pageName)}")
|
redirect(s"/${repository.owner}/${repository.name}/wiki/${StringUtil.urlEncode(pageName)}")
|
||||||
} else {
|
} else {
|
||||||
flash.update("info", "This patch was not able to be reversed.")
|
flash.update("info", "This patch was not able to be reversed.")
|
||||||
redirect(
|
redirect(
|
||||||
s"/${repository.owner}/${repository.name}/wiki/${StringUtil.urlEncode(pageName)}/_compare/${from}...${to}"
|
s"/${repository.owner}/${repository.name}/wiki/${StringUtil.urlEncode(pageName)}/_compare/${from}...${to}"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else Unauthorized()
|
} else Unauthorized()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
get("/:owner/:repository/wiki/_revert/:commitId")(readableUsersOnly { repository =>
|
get("/:owner/:repository/wiki/_revert/:commitId")(readableUsersOnly { repository =>
|
||||||
if (isEditable(repository)) {
|
context.withLoginAccount {
|
||||||
val Array(from, to) = params("commitId").split("\\.\\.\\.")
|
loginAccount =>
|
||||||
|
if (isEditable(repository)) {
|
||||||
|
val Array(from, to) = params("commitId").split("\\.\\.\\.")
|
||||||
|
|
||||||
if (revertWikiPage(repository.owner, repository.name, from, to, context.loginAccount.get, None)) {
|
if (revertWikiPage(repository.owner, repository.name, from, to, loginAccount, None)) {
|
||||||
redirect(s"/${repository.owner}/${repository.name}/wiki")
|
redirect(s"/${repository.owner}/${repository.name}/wiki")
|
||||||
} else {
|
} else {
|
||||||
flash.update("info", "This patch was not able to be reversed.")
|
flash.update("info", "This patch was not able to be reversed.")
|
||||||
redirect(s"/${repository.owner}/${repository.name}/wiki/_compare/${from}...${to}")
|
redirect(s"/${repository.owner}/${repository.name}/wiki/_compare/${from}...${to}")
|
||||||
}
|
}
|
||||||
} else Unauthorized()
|
} else Unauthorized()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
get("/:owner/:repository/wiki/:page/_edit")(readableUsersOnly { repository =>
|
get("/:owner/:repository/wiki/:page/_edit")(readableUsersOnly { repository =>
|
||||||
@@ -172,9 +178,9 @@ trait WikiControllerBase extends ControllerBase {
|
|||||||
})
|
})
|
||||||
|
|
||||||
post("/:owner/:repository/wiki/_edit", editForm)(readableUsersOnly { (form, repository) =>
|
post("/:owner/:repository/wiki/_edit", editForm)(readableUsersOnly { (form, repository) =>
|
||||||
if (isEditable(repository)) {
|
context.withLoginAccount {
|
||||||
defining(context.loginAccount.get) {
|
loginAccount =>
|
||||||
loginAccount =>
|
if (isEditable(repository)) {
|
||||||
saveWikiPage(
|
saveWikiPage(
|
||||||
repository.owner,
|
repository.owner,
|
||||||
repository.name,
|
repository.name,
|
||||||
@@ -201,8 +207,8 @@ trait WikiControllerBase extends ControllerBase {
|
|||||||
} else {
|
} else {
|
||||||
redirect(s"/${repository.owner}/${repository.name}/wiki")
|
redirect(s"/${repository.owner}/${repository.name}/wiki")
|
||||||
}
|
}
|
||||||
}
|
} else Unauthorized()
|
||||||
} else Unauthorized()
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
get("/:owner/:repository/wiki/_new")(readableUsersOnly { repository =>
|
get("/:owner/:repository/wiki/_new")(readableUsersOnly { repository =>
|
||||||
@@ -212,9 +218,9 @@ trait WikiControllerBase extends ControllerBase {
|
|||||||
})
|
})
|
||||||
|
|
||||||
post("/:owner/:repository/wiki/_new", newForm)(readableUsersOnly { (form, repository) =>
|
post("/:owner/:repository/wiki/_new", newForm)(readableUsersOnly { (form, repository) =>
|
||||||
if (isEditable(repository)) {
|
context.withLoginAccount {
|
||||||
defining(context.loginAccount.get) {
|
loginAccount =>
|
||||||
loginAccount =>
|
if (isEditable(repository)) {
|
||||||
saveWikiPage(
|
saveWikiPage(
|
||||||
repository.owner,
|
repository.owner,
|
||||||
repository.name,
|
repository.name,
|
||||||
@@ -242,27 +248,35 @@ trait WikiControllerBase extends ControllerBase {
|
|||||||
} else {
|
} else {
|
||||||
redirect(s"/${repository.owner}/${repository.name}/wiki")
|
redirect(s"/${repository.owner}/${repository.name}/wiki")
|
||||||
}
|
}
|
||||||
}
|
} else Unauthorized()
|
||||||
} else Unauthorized()
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
get("/:owner/:repository/wiki/:page/_delete")(readableUsersOnly { repository =>
|
get("/:owner/:repository/wiki/:page/_delete")(readableUsersOnly { repository =>
|
||||||
if (isEditable(repository)) {
|
context.withLoginAccount {
|
||||||
val pageName = StringUtil.urlDecode(params("page"))
|
loginAccount =>
|
||||||
|
if (isEditable(repository)) {
|
||||||
|
val pageName = StringUtil.urlDecode(params("page"))
|
||||||
|
deleteWikiPage(
|
||||||
|
repository.owner,
|
||||||
|
repository.name,
|
||||||
|
pageName,
|
||||||
|
loginAccount.fullName,
|
||||||
|
loginAccount.mailAddress,
|
||||||
|
s"Destroyed ${pageName}"
|
||||||
|
)
|
||||||
|
val deleteWikiInfo = DeleteWikiInfo(
|
||||||
|
repository.owner,
|
||||||
|
repository.name,
|
||||||
|
loginAccount.userName,
|
||||||
|
pageName
|
||||||
|
)
|
||||||
|
recordActivity(deleteWikiInfo)
|
||||||
|
updateLastActivityDate(repository.owner, repository.name)
|
||||||
|
|
||||||
defining(context.loginAccount.get) { loginAccount =>
|
redirect(s"/${repository.owner}/${repository.name}/wiki")
|
||||||
val deleteWikiInfo = DeleteWikiInfo(
|
} else Unauthorized()
|
||||||
repository.owner,
|
}
|
||||||
repository.name,
|
|
||||||
loginAccount.userName,
|
|
||||||
pageName
|
|
||||||
)
|
|
||||||
recordActivity(deleteWikiInfo)
|
|
||||||
updateLastActivityDate(repository.owner, repository.name)
|
|
||||||
|
|
||||||
redirect(s"/${repository.owner}/${repository.name}/wiki")
|
|
||||||
}
|
|
||||||
} else Unauthorized()
|
|
||||||
})
|
})
|
||||||
|
|
||||||
get("/:owner/:repository/wiki/_pages")(referrersOnly { repository =>
|
get("/:owner/:repository/wiki/_pages")(referrersOnly { repository =>
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
package gitbucket.core.controller.api
|
package gitbucket.core.controller.api
|
||||||
import gitbucket.core.api.{ApiObject, ApiRef, CreateARef, JsonFormat, UpdateARef}
|
import gitbucket.core.api.{ApiError, ApiRef, CreateARef, JsonFormat, UpdateARef}
|
||||||
import gitbucket.core.controller.ControllerBase
|
import gitbucket.core.controller.ControllerBase
|
||||||
|
import gitbucket.core.service.RepositoryService.RepositoryInfo
|
||||||
import gitbucket.core.util.Directory.getRepositoryDir
|
import gitbucket.core.util.Directory.getRepositoryDir
|
||||||
import gitbucket.core.util.ReferrerAuthenticator
|
|
||||||
import gitbucket.core.util.Implicits._
|
import gitbucket.core.util.Implicits._
|
||||||
|
import gitbucket.core.util.{ReferrerAuthenticator, RepositoryName, WritableUsersAuthenticator}
|
||||||
import org.eclipse.jgit.api.Git
|
import org.eclipse.jgit.api.Git
|
||||||
import org.eclipse.jgit.lib.ObjectId
|
import org.eclipse.jgit.lib.ObjectId
|
||||||
import org.eclipse.jgit.lib.RefUpdate.Result
|
import org.eclipse.jgit.lib.RefUpdate.Result
|
||||||
@@ -14,47 +15,38 @@ import scala.jdk.CollectionConverters._
|
|||||||
import scala.util.Using
|
import scala.util.Using
|
||||||
|
|
||||||
trait ApiGitReferenceControllerBase extends ControllerBase {
|
trait ApiGitReferenceControllerBase extends ControllerBase {
|
||||||
self: ReferrerAuthenticator =>
|
self: ReferrerAuthenticator with WritableUsersAuthenticator =>
|
||||||
|
|
||||||
private val logger = LoggerFactory.getLogger(classOf[ApiGitReferenceControllerBase])
|
private val logger = LoggerFactory.getLogger(classOf[ApiGitReferenceControllerBase])
|
||||||
|
|
||||||
|
get("/api/v3/repos/:owner/:repository/git/refs")(referrersOnly { repository =>
|
||||||
|
val result = Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
|
||||||
|
val refs = git
|
||||||
|
.getRepository()
|
||||||
|
.getRefDatabase()
|
||||||
|
.getRefsByPrefix("refs")
|
||||||
|
.asScala
|
||||||
|
|
||||||
|
refs.map(ApiRef.fromRef(RepositoryName(s"${repository.owner}/${repository.name}"), _))
|
||||||
|
}
|
||||||
|
JsonFormat(result)
|
||||||
|
})
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* i. Get a reference
|
* i. Get a reference
|
||||||
* https://docs.github.com/en/free-pro-team@latest/rest/reference/git#get-a-reference
|
* https://docs.github.com/en/free-pro-team@latest/rest/reference/git#get-a-reference
|
||||||
*/
|
*/
|
||||||
get("/api/v3/repos/:owner/:repository/git/ref/*")(referrersOnly { repository =>
|
get("/api/v3/repos/:owner/:repository/git/ref/*")(referrersOnly { repository =>
|
||||||
getRef()
|
val revstr = multiParams("splat").head
|
||||||
|
getRef(revstr, repository)
|
||||||
})
|
})
|
||||||
|
|
||||||
// Some versions of GHE support this path
|
// Some versions of GHE support this path
|
||||||
get("/api/v3/repos/:owner/:repository/git/refs/*")(referrersOnly { repository =>
|
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")
|
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
|
val revstr = multiParams("splat").head
|
||||||
Using.resource(Git.open(getRepositoryDir(params("owner"), params("repository")))) { git =>
|
getRef(revstr, repository)
|
||||||
val ref = git.getRepository().findRef(revstr)
|
})
|
||||||
|
|
||||||
if (ref != null) {
|
|
||||||
val sha = ref.getObjectId().name()
|
|
||||||
JsonFormat(ApiRef(revstr, ApiObject(sha)))
|
|
||||||
|
|
||||||
} else {
|
|
||||||
val refs = git
|
|
||||||
.getRepository()
|
|
||||||
.getRefDatabase()
|
|
||||||
.getRefsByPrefix("refs/")
|
|
||||||
.asScala
|
|
||||||
|
|
||||||
JsonFormat(refs.map { ref =>
|
|
||||||
val sha = ref.getObjectId().name()
|
|
||||||
ApiRef(revstr, ApiObject(sha))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* ii. Get all references
|
* ii. Get all references
|
||||||
@@ -65,17 +57,17 @@ trait ApiGitReferenceControllerBase extends ControllerBase {
|
|||||||
* iii. Create a reference
|
* iii. Create a reference
|
||||||
* https://docs.github.com/en/free-pro-team@latest/rest/reference/git#create-a-reference
|
* https://docs.github.com/en/free-pro-team@latest/rest/reference/git#create-a-reference
|
||||||
*/
|
*/
|
||||||
post("/api/v3/repos/:owner/:repository/git/refs")(referrersOnly { _ =>
|
post("/api/v3/repos/:owner/:repository/git/refs")(referrersOnly { repository =>
|
||||||
extractFromJsonBody[CreateARef].map {
|
extractFromJsonBody[CreateARef].map {
|
||||||
data =>
|
data =>
|
||||||
Using.resource(Git.open(getRepositoryDir(params("owner"), params("repository")))) { git =>
|
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.owner))) { git =>
|
||||||
val ref = git.getRepository.findRef(data.ref)
|
val ref = git.getRepository.findRef(data.ref)
|
||||||
if (ref == null) {
|
if (ref == null) {
|
||||||
val update = git.getRepository.updateRef(data.ref)
|
val update = git.getRepository.updateRef(data.ref)
|
||||||
update.setNewObjectId(ObjectId.fromString(data.sha))
|
update.setNewObjectId(ObjectId.fromString(data.sha))
|
||||||
val result = update.update()
|
val result = update.update()
|
||||||
result match {
|
result match {
|
||||||
case Result.NEW => JsonFormat(ApiRef(update.getName, ApiObject(update.getNewObjectId.getName)))
|
case Result.NEW => JsonFormat(ApiRef.fromRef(RepositoryName(repository.owner, repository.name), ref))
|
||||||
case _ => UnprocessableEntity(result.name())
|
case _ => UnprocessableEntity(result.name())
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -89,11 +81,11 @@ trait ApiGitReferenceControllerBase extends ControllerBase {
|
|||||||
* iv. Update a reference
|
* iv. Update a reference
|
||||||
* https://docs.github.com/en/free-pro-team@latest/rest/reference/git#update-a-reference
|
* https://docs.github.com/en/free-pro-team@latest/rest/reference/git#update-a-reference
|
||||||
*/
|
*/
|
||||||
patch("/api/v3/repos/:owner/:repository/git/refs/*")(referrersOnly { _ =>
|
patch("/api/v3/repos/:owner/:repository/git/refs/*")(writableUsersOnly { repository =>
|
||||||
val refName = multiParams("splat").mkString("/")
|
val refName = multiParams("splat").mkString("/")
|
||||||
extractFromJsonBody[UpdateARef].map {
|
extractFromJsonBody[UpdateARef].map {
|
||||||
data =>
|
data =>
|
||||||
Using.resource(Git.open(getRepositoryDir(params("owner"), params("repository")))) { git =>
|
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.owner))) { git =>
|
||||||
val ref = git.getRepository.findRef(refName)
|
val ref = git.getRepository.findRef(refName)
|
||||||
if (ref == null) {
|
if (ref == null) {
|
||||||
UnprocessableEntity("Ref does not exist.")
|
UnprocessableEntity("Ref does not exist.")
|
||||||
@@ -104,7 +96,7 @@ trait ApiGitReferenceControllerBase extends ControllerBase {
|
|||||||
val result = update.update()
|
val result = update.update()
|
||||||
result match {
|
result match {
|
||||||
case Result.FORCED | Result.FAST_FORWARD | Result.NO_CHANGE =>
|
case Result.FORCED | Result.FAST_FORWARD | Result.NO_CHANGE =>
|
||||||
JsonFormat(ApiRef(update.getName, ApiObject(update.getNewObjectId.getName)))
|
JsonFormat(ApiRef.fromRef(RepositoryName(repository), update.getRef))
|
||||||
case _ => UnprocessableEntity(result.name())
|
case _ => UnprocessableEntity(result.name())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -116,7 +108,7 @@ trait ApiGitReferenceControllerBase extends ControllerBase {
|
|||||||
* v. Delete a reference
|
* v. Delete a reference
|
||||||
* https://docs.github.com/en/free-pro-team@latest/rest/reference/git#delete-a-reference
|
* https://docs.github.com/en/free-pro-team@latest/rest/reference/git#delete-a-reference
|
||||||
*/
|
*/
|
||||||
delete("/api/v3/repos/:owner/:repository/git/refs/*")(referrersOnly { _ =>
|
delete("/api/v3/repos/:owner/:repository/git/refs/*")(writableUsersOnly { _ =>
|
||||||
val refName = multiParams("splat").mkString("/")
|
val refName = multiParams("splat").mkString("/")
|
||||||
Using.resource(Git.open(getRepositoryDir(params("owner"), params("repository")))) { git =>
|
Using.resource(Git.open(getRepositoryDir(params("owner"), params("repository")))) { git =>
|
||||||
val ref = git.getRepository.findRef(refName)
|
val ref = git.getRepository.findRef(refName)
|
||||||
@@ -133,4 +125,34 @@ trait ApiGitReferenceControllerBase extends ControllerBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
private def notFound(): ApiError = {
|
||||||
|
response.setStatus(404)
|
||||||
|
ApiError("Not Found")
|
||||||
|
}
|
||||||
|
|
||||||
|
protected def getRef(revstr: String, repository: RepositoryInfo): AnyRef = {
|
||||||
|
logger.debug(s"getRef: path '${revstr}'")
|
||||||
|
|
||||||
|
val name = RepositoryName(repository)
|
||||||
|
val result = JsonFormat(revstr match {
|
||||||
|
case "tags" => repository.tags.map(ApiRef.fromTag(name, _))
|
||||||
|
case x if x.startsWith("tags/") =>
|
||||||
|
val tagName = x.substring("tags/".length)
|
||||||
|
repository.tags.find(_.name == tagName) match {
|
||||||
|
case Some(tagInfo) => ApiRef.fromTag(name, tagInfo)
|
||||||
|
case None => notFound()
|
||||||
|
}
|
||||||
|
case other =>
|
||||||
|
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
|
||||||
|
git.getRepository().findRef(other) match {
|
||||||
|
case null => notFound()
|
||||||
|
case ref => ApiRef.fromRef(name, ref)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
logger.debug(s"json result: $result")
|
||||||
|
result
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import gitbucket.core.controller.{Context, ControllerBase}
|
|||||||
import gitbucket.core.service._
|
import gitbucket.core.service._
|
||||||
import gitbucket.core.util.Implicits._
|
import gitbucket.core.util.Implicits._
|
||||||
import gitbucket.core.util.{ReadableUsersAuthenticator, ReferrerAuthenticator, RepositoryName}
|
import gitbucket.core.util.{ReadableUsersAuthenticator, ReferrerAuthenticator, RepositoryName}
|
||||||
import org.scalatra.{ActionResult, NoContent}
|
import org.scalatra.ActionResult
|
||||||
|
|
||||||
trait ApiIssueCommentControllerBase extends ControllerBase {
|
trait ApiIssueCommentControllerBase extends ControllerBase {
|
||||||
self: AccountService
|
self: AccountService
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import gitbucket.core.model.{Account, Issue}
|
|||||||
import gitbucket.core.service.{AccountService, IssueCreationService, IssuesService, MilestonesService}
|
import gitbucket.core.service.{AccountService, IssueCreationService, IssuesService, MilestonesService}
|
||||||
import gitbucket.core.service.IssuesService.IssueSearchCondition
|
import gitbucket.core.service.IssuesService.IssueSearchCondition
|
||||||
import gitbucket.core.service.PullRequestService.PullRequestLimit
|
import gitbucket.core.service.PullRequestService.PullRequestLimit
|
||||||
import gitbucket.core.util.{ReadableUsersAuthenticator, ReferrerAuthenticator, RepositoryName, UsersAuthenticator}
|
import gitbucket.core.util.{ReadableUsersAuthenticator, ReferrerAuthenticator, RepositoryName}
|
||||||
import gitbucket.core.util.Implicits._
|
import gitbucket.core.util.Implicits._
|
||||||
|
|
||||||
trait ApiIssueControllerBase extends ControllerBase {
|
trait ApiIssueControllerBase extends ControllerBase {
|
||||||
@@ -47,7 +47,8 @@ trait ApiIssueControllerBase extends ControllerBase {
|
|||||||
user = ApiUser(issueUser),
|
user = ApiUser(issueUser),
|
||||||
assignee = assignedUser.map(ApiUser(_)),
|
assignee = assignedUser.map(ApiUser(_)),
|
||||||
labels = getIssueLabels(repository.owner, repository.name, issue.issueId)
|
labels = getIssueLabels(repository.owner, repository.name, issue.issueId)
|
||||||
.map(ApiLabel(_, RepositoryName(repository)))
|
.map(ApiLabel(_, RepositoryName(repository))),
|
||||||
|
issue.milestoneId.flatMap { getApiMilestone(repository, _) }
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -69,7 +70,8 @@ trait ApiIssueControllerBase extends ControllerBase {
|
|||||||
RepositoryName(repository),
|
RepositoryName(repository),
|
||||||
ApiUser(openedUser),
|
ApiUser(openedUser),
|
||||||
issue.assignedUserName.flatMap(users.get(_)).map(ApiUser(_)),
|
issue.assignedUserName.flatMap(users.get(_)).map(ApiUser(_)),
|
||||||
getIssueLabels(repository.owner, repository.name, issue.issueId).map(ApiLabel(_, RepositoryName(repository)))
|
getIssueLabels(repository.owner, repository.name, issue.issueId).map(ApiLabel(_, RepositoryName(repository))),
|
||||||
|
issue.milestoneId.flatMap { getApiMilestone(repository, _) }
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}) getOrElse NotFound()
|
}) getOrElse NotFound()
|
||||||
@@ -103,7 +105,8 @@ trait ApiIssueControllerBase extends ControllerBase {
|
|||||||
ApiUser(loginAccount),
|
ApiUser(loginAccount),
|
||||||
issue.assignedUserName.flatMap(getAccountByUserName(_)).map(ApiUser(_)),
|
issue.assignedUserName.flatMap(getAccountByUserName(_)).map(ApiUser(_)),
|
||||||
getIssueLabels(repository.owner, repository.name, issue.issueId)
|
getIssueLabels(repository.owner, repository.name, issue.issueId)
|
||||||
.map(ApiLabel(_, RepositoryName(repository)))
|
.map(ApiLabel(_, RepositoryName(repository))),
|
||||||
|
issue.milestoneId.flatMap { getApiMilestone(repository, _) }
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}) getOrElse NotFound()
|
}) getOrElse NotFound()
|
||||||
|
|||||||
@@ -121,7 +121,7 @@ trait ApiIssueLabelControllerBase extends ControllerBase {
|
|||||||
*/
|
*/
|
||||||
post("/api/v3/repos/:owner/:repository/issues/:id/labels")(writableUsersOnly { repository =>
|
post("/api/v3/repos/:owner/:repository/issues/:id/labels")(writableUsersOnly { repository =>
|
||||||
JsonFormat(for {
|
JsonFormat(for {
|
||||||
data <- extractFromJsonBody[Seq[String]];
|
data <- extractFromJsonBody[Seq[String]]
|
||||||
issueId <- params("id").toIntOpt
|
issueId <- params("id").toIntOpt
|
||||||
} yield {
|
} yield {
|
||||||
data.map { labelName =>
|
data.map { labelName =>
|
||||||
@@ -160,7 +160,7 @@ trait ApiIssueLabelControllerBase extends ControllerBase {
|
|||||||
*/
|
*/
|
||||||
put("/api/v3/repos/:owner/:repository/issues/:id/labels")(writableUsersOnly { repository =>
|
put("/api/v3/repos/:owner/:repository/issues/:id/labels")(writableUsersOnly { repository =>
|
||||||
JsonFormat(for {
|
JsonFormat(for {
|
||||||
data <- extractFromJsonBody[Seq[String]];
|
data <- extractFromJsonBody[Seq[String]]
|
||||||
issueId <- params("id").toIntOpt
|
issueId <- params("id").toIntOpt
|
||||||
} yield {
|
} yield {
|
||||||
deleteAllIssueLabels(repository.owner, repository.name, issueId, true)
|
deleteAllIssueLabels(repository.owner, repository.name, issueId, true)
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package gitbucket.core.controller.api
|
|||||||
import gitbucket.core.api._
|
import gitbucket.core.api._
|
||||||
import gitbucket.core.controller.ControllerBase
|
import gitbucket.core.controller.ControllerBase
|
||||||
import gitbucket.core.service.MilestonesService
|
import gitbucket.core.service.MilestonesService
|
||||||
import gitbucket.core.service.RepositoryService.RepositoryInfo
|
|
||||||
import gitbucket.core.util.{ReferrerAuthenticator, WritableUsersAuthenticator}
|
import gitbucket.core.util.{ReferrerAuthenticator, WritableUsersAuthenticator}
|
||||||
import gitbucket.core.util.Implicits._
|
import gitbucket.core.util.Implicits._
|
||||||
import org.scalatra.NoContent
|
import org.scalatra.NoContent
|
||||||
@@ -102,17 +101,4 @@ trait ApiIssueMilestoneControllerBase extends ControllerBase {
|
|||||||
NoContent()
|
NoContent()
|
||||||
})
|
})
|
||||||
|
|
||||||
private def getApiMilestone(repository: RepositoryInfo, milestoneId: Int): Option[ApiMilestone] = {
|
|
||||||
getMilestonesWithIssueCount(repository.owner, repository.name)
|
|
||||||
.find(p => p._1.milestoneId == milestoneId)
|
|
||||||
.map(
|
|
||||||
milestoneWithIssue =>
|
|
||||||
ApiMilestone(
|
|
||||||
repository.repository,
|
|
||||||
milestoneWithIssue._1,
|
|
||||||
milestoneWithIssue._2,
|
|
||||||
milestoneWithIssue._3
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
package gitbucket.core.controller.api
|
package gitbucket.core.controller.api
|
||||||
import gitbucket.core.api.{ApiGroup, CreateAGroup, ApiRepository, ApiUser, JsonFormat}
|
import gitbucket.core.api.{ApiGroup, CreateAGroup, JsonFormat}
|
||||||
import gitbucket.core.controller.ControllerBase
|
import gitbucket.core.controller.ControllerBase
|
||||||
import gitbucket.core.service.{AccountService, RepositoryService}
|
import gitbucket.core.service.{AccountService, RepositoryService}
|
||||||
import gitbucket.core.util.Implicits._
|
import gitbucket.core.util.Implicits._
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
package gitbucket.core.controller.api
|
package gitbucket.core.controller.api
|
||||||
import java.io.{ByteArrayInputStream, File}
|
import java.io.File
|
||||||
|
|
||||||
import gitbucket.core.api._
|
import gitbucket.core.api._
|
||||||
import gitbucket.core.controller.ControllerBase
|
import gitbucket.core.controller.ControllerBase
|
||||||
@@ -7,9 +7,8 @@ import gitbucket.core.service.{AccountService, ReleaseService}
|
|||||||
import gitbucket.core.util.Directory.getReleaseFilesDir
|
import gitbucket.core.util.Directory.getReleaseFilesDir
|
||||||
import gitbucket.core.util.{FileUtil, ReferrerAuthenticator, RepositoryName, WritableUsersAuthenticator}
|
import gitbucket.core.util.{FileUtil, ReferrerAuthenticator, RepositoryName, WritableUsersAuthenticator}
|
||||||
import gitbucket.core.util.Implicits._
|
import gitbucket.core.util.Implicits._
|
||||||
import gitbucket.core.util.SyntaxSugars.defining
|
|
||||||
import org.apache.commons.io.FileUtils
|
import org.apache.commons.io.FileUtils
|
||||||
import org.scalatra.{Created, NoContent}
|
import org.scalatra.NoContent
|
||||||
|
|
||||||
trait ApiReleaseControllerBase extends ControllerBase {
|
trait ApiReleaseControllerBase extends ControllerBase {
|
||||||
self: AccountService with ReleaseService with ReferrerAuthenticator with WritableUsersAuthenticator =>
|
self: AccountService with ReleaseService with ReferrerAuthenticator with WritableUsersAuthenticator =>
|
||||||
@@ -87,7 +86,7 @@ trait ApiReleaseControllerBase extends ControllerBase {
|
|||||||
/**
|
/**
|
||||||
* vi. Edit a release
|
* vi. Edit a release
|
||||||
* https://developer.github.com/v3/repos/releases/#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
|
* Incompatibility info: GitHub API requires :release_id, but GitBucket API requires :tag_name
|
||||||
*/
|
*/
|
||||||
patch("/api/v3/repos/:owner/:repository/releases/:tag")(writableUsersOnly { repository =>
|
patch("/api/v3/repos/:owner/:repository/releases/:tag")(writableUsersOnly { repository =>
|
||||||
(for {
|
(for {
|
||||||
@@ -104,7 +103,7 @@ trait ApiReleaseControllerBase extends ControllerBase {
|
|||||||
/**
|
/**
|
||||||
* vii. Delete a release
|
* vii. Delete a release
|
||||||
* https://developer.github.com/v3/repos/releases/#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
|
* Incompatibility info: GitHub API requires :release_id, but GitBucket API requires :tag_name
|
||||||
*/
|
*/
|
||||||
delete("/api/v3/repos/:owner/:repository/releases/:tag")(writableUsersOnly { repository =>
|
delete("/api/v3/repos/:owner/:repository/releases/:tag")(writableUsersOnly { repository =>
|
||||||
val tag = params("tag")
|
val tag = params("tag")
|
||||||
@@ -120,41 +119,40 @@ trait ApiReleaseControllerBase extends ControllerBase {
|
|||||||
* ix. Upload a release asset
|
* ix. Upload a release asset
|
||||||
* https://developer.github.com/v3/repos/releases/#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 =>
|
post("/api/v3/repos/:owner/:repository/releases/:tag/assets")(writableUsersOnly {
|
||||||
val name = params("name")
|
repository =>
|
||||||
val tag = params("tag")
|
val name = params("name")
|
||||||
getRelease(repository.owner, repository.name, tag)
|
val tag = params("tag")
|
||||||
.map {
|
getRelease(repository.owner, repository.name, tag)
|
||||||
release =>
|
.map { release =>
|
||||||
defining(FileUtil.generateFileId) { fileId =>
|
val fileId = FileUtil.generateFileId
|
||||||
val buf = new Array[Byte](request.inputStream.available())
|
val buf = new Array[Byte](request.inputStream.available())
|
||||||
request.inputStream.read(buf)
|
request.inputStream.read(buf)
|
||||||
FileUtils.writeByteArrayToFile(
|
FileUtils.writeByteArrayToFile(
|
||||||
new File(
|
new File(
|
||||||
getReleaseFilesDir(repository.owner, repository.name),
|
getReleaseFilesDir(repository.owner, repository.name),
|
||||||
FileUtil.checkFilename(tag + "/" + fileId)
|
FileUtil.checkFilename(tag + "/" + fileId)
|
||||||
),
|
),
|
||||||
buf
|
buf
|
||||||
)
|
)
|
||||||
createReleaseAsset(
|
createReleaseAsset(
|
||||||
repository.owner,
|
repository.owner,
|
||||||
repository.name,
|
repository.name,
|
||||||
tag,
|
tag,
|
||||||
fileId,
|
fileId,
|
||||||
name,
|
name,
|
||||||
request.contentLength.getOrElse(0),
|
request.contentLength.getOrElse(0),
|
||||||
context.loginAccount.get
|
context.loginAccount.get
|
||||||
)
|
)
|
||||||
getReleaseAsset(repository.owner, repository.name, tag, fileId)
|
getReleaseAsset(repository.owner, repository.name, tag, fileId)
|
||||||
.map { asset =>
|
.map { asset =>
|
||||||
JsonFormat(ApiReleaseAsset(asset, RepositoryName(repository)))
|
JsonFormat(ApiReleaseAsset(asset, RepositoryName(repository)))
|
||||||
}
|
}
|
||||||
.getOrElse {
|
.getOrElse {
|
||||||
ApiError("Unknown error")
|
ApiError("Unknown error")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
.getOrElse(NotFound())
|
||||||
.getOrElse(NotFound())
|
|
||||||
})
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ trait ApiRepositoryCommitControllerBase extends ControllerBase {
|
|||||||
diffs = JGitUtil.getDiffs(git, commitInfo.parents.headOption, commitInfo.id, false, true),
|
diffs = JGitUtil.getDiffs(git, commitInfo.parents.headOption, commitInfo.id, false, true),
|
||||||
author = getAccount(commitInfo.authorName, commitInfo.authorEmailAddress),
|
author = getAccount(commitInfo.authorName, commitInfo.authorEmailAddress),
|
||||||
committer = getAccount(commitInfo.committerName, commitInfo.committerEmailAddress),
|
committer = getAccount(commitInfo.committerName, commitInfo.committerEmailAddress),
|
||||||
commentCount = getCommitComment(repository.owner, repository.name, commitInfo.id.toString).size
|
commentCount = getCommitComment(repository.owner, repository.name, commitInfo.id).size
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
package gitbucket.core.controller.api
|
package gitbucket.core.controller.api
|
||||||
import gitbucket.core.api.{ApiContents, ApiError, CreateAFile, JsonFormat}
|
import gitbucket.core.api.{ApiCommit, ApiContents, ApiError, CreateAFile, JsonFormat}
|
||||||
import gitbucket.core.controller.ControllerBase
|
import gitbucket.core.controller.ControllerBase
|
||||||
import gitbucket.core.plugin.PluginRegistry
|
|
||||||
import gitbucket.core.service.{RepositoryCommitFileService, RepositoryService}
|
import gitbucket.core.service.{RepositoryCommitFileService, RepositoryService}
|
||||||
import gitbucket.core.util.Directory.getRepositoryDir
|
import gitbucket.core.util.Directory.getRepositoryDir
|
||||||
import gitbucket.core.util.JGitUtil.{FileInfo, getContentFromId, getFileList}
|
import gitbucket.core.util.JGitUtil.{CommitInfo, FileInfo, getContentFromId, getFileList}
|
||||||
import gitbucket.core.util._
|
import gitbucket.core.util._
|
||||||
import gitbucket.core.view.helpers.{isRenderable, renderMarkup}
|
import gitbucket.core.view.helpers.{isRenderable, renderMarkup}
|
||||||
import gitbucket.core.util.Implicits._
|
import gitbucket.core.util.Implicits._
|
||||||
@@ -36,7 +35,7 @@ trait ApiRepositoryContentsControllerBase extends ControllerBase {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* ii. Get contents
|
* ii. Get contents
|
||||||
* https://developer.github.com/v3/repos/contents/#get-contents
|
* https://docs.github.com/en/rest/reference/repos#get-repository-content
|
||||||
*/
|
*/
|
||||||
get("/api/v3/repos/:owner/:repository/contents")(referrersOnly { repository =>
|
get("/api/v3/repos/:owner/:repository/contents")(referrersOnly { repository =>
|
||||||
getContents(repository, ".", params.getOrElse("ref", repository.repository.defaultBranch))
|
getContents(repository, ".", params.getOrElse("ref", repository.repository.defaultBranch))
|
||||||
@@ -44,34 +43,34 @@ trait ApiRepositoryContentsControllerBase extends ControllerBase {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* ii. Get contents
|
* ii. Get contents
|
||||||
* https://developer.github.com/v3/repos/contents/#get-contents
|
* https://docs.github.com/en/rest/reference/repos#get-repository-content
|
||||||
*/
|
*/
|
||||||
get("/api/v3/repos/:owner/:repository/contents/*")(referrersOnly { repository =>
|
get("/api/v3/repos/:owner/:repository/contents/*")(referrersOnly { repository =>
|
||||||
getContents(repository, multiParams("splat").head, params.getOrElse("ref", repository.repository.defaultBranch))
|
getContents(repository, multiParams("splat").head, params.getOrElse("ref", repository.repository.defaultBranch))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
private def getFileInfo(git: Git, revision: String, pathStr: String, ignoreCase: Boolean): Option[FileInfo] = {
|
||||||
|
val (dirName, fileName) = pathStr.lastIndexOf('/') match {
|
||||||
|
case -1 =>
|
||||||
|
(".", pathStr)
|
||||||
|
case n =>
|
||||||
|
(pathStr.take(n), pathStr.drop(n + 1))
|
||||||
|
}
|
||||||
|
if (ignoreCase) {
|
||||||
|
getFileList(git, revision, dirName, maxFiles = context.settings.repositoryViewer.maxFiles)
|
||||||
|
.find(_.name.toLowerCase.equals(fileName.toLowerCase))
|
||||||
|
} else {
|
||||||
|
getFileList(git, revision, dirName, maxFiles = context.settings.repositoryViewer.maxFiles)
|
||||||
|
.find(_.name.equals(fileName))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private def getContents(
|
private def getContents(
|
||||||
repository: RepositoryService.RepositoryInfo,
|
repository: RepositoryService.RepositoryInfo,
|
||||||
path: String,
|
path: String,
|
||||||
refStr: String,
|
refStr: String,
|
||||||
ignoreCase: Boolean = false
|
ignoreCase: Boolean = false
|
||||||
) = {
|
) = {
|
||||||
def getFileInfo(git: Git, revision: String, pathStr: String, ignoreCase: Boolean): Option[FileInfo] = {
|
|
||||||
val (dirName, fileName) = pathStr.lastIndexOf('/') match {
|
|
||||||
case -1 =>
|
|
||||||
(".", pathStr)
|
|
||||||
case n =>
|
|
||||||
(pathStr.take(n), pathStr.drop(n + 1))
|
|
||||||
}
|
|
||||||
if (ignoreCase) {
|
|
||||||
getFileList(git, revision, dirName, maxFiles = context.settings.repositoryViewer.maxFiles)
|
|
||||||
.find(_.name.toLowerCase.equals(fileName.toLowerCase))
|
|
||||||
} else {
|
|
||||||
getFileList(git, revision, dirName, maxFiles = context.settings.repositoryViewer.maxFiles)
|
|
||||||
.find(_.name.equals(fileName))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Using.resource(Git.open(getRepositoryDir(params("owner"), params("repository")))) { git =>
|
Using.resource(Git.open(getRepositoryDir(params("owner"), params("repository")))) { git =>
|
||||||
val fileList = getFileList(git, refStr, path, maxFiles = context.settings.repositoryViewer.maxFiles)
|
val fileList = getFileList(git, refStr, path, maxFiles = context.settings.repositoryViewer.maxFiles)
|
||||||
if (fileList.isEmpty) { // file or NotFound
|
if (fileList.isEmpty) { // file or NotFound
|
||||||
@@ -127,56 +126,88 @@ trait ApiRepositoryContentsControllerBase extends ControllerBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/*
|
|
||||||
|
/**
|
||||||
* iii. Create a file or iv. Update a file
|
* iii. Create a file or iv. Update a file
|
||||||
* https://developer.github.com/v3/repos/contents/#create-a-file
|
* https://docs.github.com/en/rest/reference/repos#create-or-update-file-contents
|
||||||
* https://developer.github.com/v3/repos/contents/#update-a-file
|
|
||||||
* if sha is presented, update a file else create a file.
|
* if sha is presented, update a file else create a file.
|
||||||
* requested #2112
|
* requested #2112
|
||||||
*/
|
*/
|
||||||
|
|
||||||
put("/api/v3/repos/:owner/:repository/contents/*")(writableUsersOnly { repository =>
|
put("/api/v3/repos/:owner/:repository/contents/*")(writableUsersOnly { repository =>
|
||||||
JsonFormat(for {
|
context.withLoginAccount {
|
||||||
data <- extractFromJsonBody[CreateAFile]
|
loginAccount =>
|
||||||
} yield {
|
JsonFormat(for {
|
||||||
val branch = data.branch.getOrElse(repository.repository.defaultBranch)
|
data <- extractFromJsonBody[CreateAFile]
|
||||||
val commit = Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
|
} yield {
|
||||||
val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(branch))
|
val branch = data.branch.getOrElse(repository.repository.defaultBranch)
|
||||||
revCommit.name
|
val commit = Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
|
||||||
}
|
val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(branch))
|
||||||
val paths = multiParams("splat").head.split("/")
|
revCommit.name
|
||||||
val path = paths.take(paths.size - 1).toList.mkString("/")
|
}
|
||||||
if (data.sha.isDefined && data.sha.get != commit) {
|
val paths = multiParams("splat").head.split("/")
|
||||||
ApiError("The blob SHA is not matched.", Some("https://developer.github.com/v3/repos/contents/#update-a-file"))
|
val path = paths.take(paths.size - 1).toList.mkString("/")
|
||||||
} else {
|
Using.resource(Git.open(getRepositoryDir(params("owner"), params("repository")))) {
|
||||||
val objectId = commitFile(
|
git =>
|
||||||
repository,
|
val fileInfo = getFileInfo(git, commit, path, false)
|
||||||
branch,
|
|
||||||
path,
|
fileInfo match {
|
||||||
Some(paths.last),
|
case Some(f) if !data.sha.contains(f.id.getName) =>
|
||||||
data.sha.map(_ => paths.last),
|
ApiError(
|
||||||
StringUtil.base64Decode(data.content),
|
"The blob SHA is not matched.",
|
||||||
data.message,
|
Some("https://docs.github.com/en/rest/reference/repos#create-or-update-file-contents")
|
||||||
commit,
|
)
|
||||||
context.loginAccount.get,
|
case _ =>
|
||||||
data.committer.map(_.name).getOrElse(context.loginAccount.get.fullName),
|
val (commitId, blobId) = commitFile(
|
||||||
data.committer.map(_.email).getOrElse(context.loginAccount.get.mailAddress),
|
repository,
|
||||||
context.settings
|
branch,
|
||||||
)
|
path,
|
||||||
ApiContents("file", paths.last, path, objectId.name, None, None)(RepositoryName(repository))
|
Some(paths.last),
|
||||||
}
|
data.sha.map(_ => paths.last),
|
||||||
})
|
StringUtil.base64Decode(data.content),
|
||||||
|
data.message,
|
||||||
|
commit,
|
||||||
|
loginAccount,
|
||||||
|
data.committer.map(_.name).getOrElse(loginAccount.fullName),
|
||||||
|
data.committer.map(_.email).getOrElse(loginAccount.mailAddress),
|
||||||
|
context.settings
|
||||||
|
)
|
||||||
|
|
||||||
|
blobId match {
|
||||||
|
case None =>
|
||||||
|
ApiError("Failed to commit a file.", None)
|
||||||
|
case Some(blobId) =>
|
||||||
|
Map(
|
||||||
|
"content" -> ApiContents(
|
||||||
|
"file",
|
||||||
|
paths.last,
|
||||||
|
path,
|
||||||
|
blobId.name,
|
||||||
|
Some(data.content),
|
||||||
|
Some("base64")
|
||||||
|
)(RepositoryName(repository)),
|
||||||
|
"commit" -> ApiCommit(
|
||||||
|
git,
|
||||||
|
RepositoryName(repository),
|
||||||
|
new CommitInfo(JGitUtil.getRevCommitFromId(git, commitId))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* v. Delete a file
|
* v. Delete a file
|
||||||
* https://developer.github.com/v3/repos/contents/#delete-a-file
|
* https://docs.github.com/en/rest/reference/repos#delete-a-file
|
||||||
* should be implemented
|
* should be implemented
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* vi. Get archive link
|
* vi. Download a repository archive (tar/zip)
|
||||||
* https://developer.github.com/v3/repos/contents/#get-archive-link
|
* https://docs.github.com/en/rest/reference/repos#download-a-repository-archive-tar
|
||||||
|
* https://docs.github.com/en/rest/reference/repos#download-a-repository-archive-zip
|
||||||
*/
|
*/
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import gitbucket.core.util._
|
|||||||
import gitbucket.core.util.Implicits._
|
import gitbucket.core.util.Implicits._
|
||||||
import gitbucket.core.model.Profile.profile.blockingApi._
|
import gitbucket.core.model.Profile.profile.blockingApi._
|
||||||
import org.eclipse.jgit.api.Git
|
import org.eclipse.jgit.api.Git
|
||||||
|
import org.scalatra.Forbidden
|
||||||
|
|
||||||
import scala.concurrent.Await
|
import scala.concurrent.Await
|
||||||
import scala.concurrent.duration.Duration
|
import scala.concurrent.duration.Duration
|
||||||
@@ -15,6 +16,7 @@ import scala.util.Using
|
|||||||
|
|
||||||
trait ApiRepositoryControllerBase extends ControllerBase {
|
trait ApiRepositoryControllerBase extends ControllerBase {
|
||||||
self: RepositoryService
|
self: RepositoryService
|
||||||
|
with ApiGitReferenceControllerBase
|
||||||
with RepositoryCreationService
|
with RepositoryCreationService
|
||||||
with AccountService
|
with AccountService
|
||||||
with OwnerAuthenticator
|
with OwnerAuthenticator
|
||||||
@@ -26,7 +28,7 @@ trait ApiRepositoryControllerBase extends ControllerBase {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* i. List your repositories
|
* i. List your repositories
|
||||||
* https://developer.github.com/v3/repos/#list-your-repositories
|
* https://docs.github.com/en/rest/reference/repos#list-repositories-for-the-authenticated-user
|
||||||
*/
|
*/
|
||||||
get("/api/v3/user/repos")(usersOnly {
|
get("/api/v3/user/repos")(usersOnly {
|
||||||
JsonFormat(getVisibleRepositories(context.loginAccount, Option(context.loginAccount.get.userName)).map { r =>
|
JsonFormat(getVisibleRepositories(context.loginAccount, Option(context.loginAccount.get.userName)).map { r =>
|
||||||
@@ -36,7 +38,7 @@ trait ApiRepositoryControllerBase extends ControllerBase {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* ii. List user repositories
|
* ii. List user repositories
|
||||||
* https://developer.github.com/v3/repos/#list-user-repositories
|
* https://docs.github.com/en/rest/reference/repos#list-repositories-for-a-user
|
||||||
*/
|
*/
|
||||||
get("/api/v3/users/:userName/repos") {
|
get("/api/v3/users/:userName/repos") {
|
||||||
JsonFormat(getVisibleRepositories(context.loginAccount, Some(params("userName"))).map { r =>
|
JsonFormat(getVisibleRepositories(context.loginAccount, Some(params("userName"))).map { r =>
|
||||||
@@ -46,7 +48,7 @@ trait ApiRepositoryControllerBase extends ControllerBase {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* iii. List organization repositories
|
* iii. List organization repositories
|
||||||
* https://developer.github.com/v3/repos/#list-organization-repositories
|
* https://docs.github.com/en/rest/reference/repos#list-organization-repositories
|
||||||
*/
|
*/
|
||||||
get("/api/v3/orgs/:orgName/repos") {
|
get("/api/v3/orgs/:orgName/repos") {
|
||||||
JsonFormat(getVisibleRepositories(context.loginAccount, Some(params("orgName"))).map { r =>
|
JsonFormat(getVisibleRepositories(context.loginAccount, Some(params("orgName"))).map { r =>
|
||||||
@@ -56,7 +58,7 @@ trait ApiRepositoryControllerBase extends ControllerBase {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* iv. List all public repositories
|
* iv. List all public repositories
|
||||||
* https://developer.github.com/v3/repos/#list-public-repositories
|
* https://docs.github.com/en/rest/reference/repos#list-public-repositories
|
||||||
*/
|
*/
|
||||||
get("/api/v3/repositories") {
|
get("/api/v3/repositories") {
|
||||||
JsonFormat(getPublicRepositories().map { r =>
|
JsonFormat(getPublicRepositories().map { r =>
|
||||||
@@ -66,13 +68,12 @@ trait ApiRepositoryControllerBase extends ControllerBase {
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
* v. Create
|
* v. Create
|
||||||
* https://developer.github.com/v3/repos/#create
|
|
||||||
* Implemented with two methods (user/orgs)
|
* Implemented with two methods (user/orgs)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create user repository
|
* Create user repository
|
||||||
* https://developer.github.com/v3/repos/#create
|
* https://docs.github.com/en/rest/reference/repos#create-a-repository-for-the-authenticated-user
|
||||||
*/
|
*/
|
||||||
post("/api/v3/user/repos")(usersOnly {
|
post("/api/v3/user/repos")(usersOnly {
|
||||||
val owner = context.loginAccount.get.userName
|
val owner = context.loginAccount.get.userName
|
||||||
@@ -80,7 +81,12 @@ trait ApiRepositoryControllerBase extends ControllerBase {
|
|||||||
data <- extractFromJsonBody[CreateARepository] if data.isValid
|
data <- extractFromJsonBody[CreateARepository] if data.isValid
|
||||||
} yield {
|
} yield {
|
||||||
LockUtil.lock(s"${owner}/${data.name}") {
|
LockUtil.lock(s"${owner}/${data.name}") {
|
||||||
if (getRepository(owner, data.name).isEmpty) {
|
if (getRepository(owner, data.name).isDefined) {
|
||||||
|
ApiError(
|
||||||
|
"A repository with this name already exists on this account",
|
||||||
|
Some("https://developer.github.com/v3/repos/#create")
|
||||||
|
)
|
||||||
|
} else {
|
||||||
val f = createRepository(
|
val f = createRepository(
|
||||||
context.loginAccount.get,
|
context.loginAccount.get,
|
||||||
owner,
|
owner,
|
||||||
@@ -95,11 +101,6 @@ trait ApiRepositoryControllerBase extends ControllerBase {
|
|||||||
getRepository(owner, data.name)(session).get
|
getRepository(owner, data.name)(session).get
|
||||||
}
|
}
|
||||||
JsonFormat(ApiRepository(repository, ApiUser(getAccountByUserName(owner).get)))
|
JsonFormat(ApiRepository(repository, ApiUser(getAccountByUserName(owner).get)))
|
||||||
} else {
|
|
||||||
ApiError(
|
|
||||||
"A repository with this name already exists on this account",
|
|
||||||
Some("https://developer.github.com/v3/repos/#create")
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}) getOrElse NotFound()
|
}) getOrElse NotFound()
|
||||||
@@ -107,15 +108,22 @@ trait ApiRepositoryControllerBase extends ControllerBase {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Create group repository
|
* Create group repository
|
||||||
* https://developer.github.com/v3/repos/#create
|
* https://docs.github.com/en/rest/reference/repos#create-an-organization-repository
|
||||||
*/
|
*/
|
||||||
post("/api/v3/orgs/:org/repos")(managersOnly {
|
post("/api/v3/orgs/:org/repos")(usersOnly {
|
||||||
val groupName = params("org")
|
val groupName = params("org")
|
||||||
(for {
|
(for {
|
||||||
data <- extractFromJsonBody[CreateARepository] if data.isValid
|
data <- extractFromJsonBody[CreateARepository] if data.isValid
|
||||||
} yield {
|
} yield {
|
||||||
LockUtil.lock(s"${groupName}/${data.name}") {
|
LockUtil.lock(s"${groupName}/${data.name}") {
|
||||||
if (getRepository(groupName, data.name).isEmpty) {
|
if (getRepository(groupName, data.name).isDefined) {
|
||||||
|
ApiError(
|
||||||
|
"A repository with this name already exists for this group",
|
||||||
|
Some("https://developer.github.com/v3/repos/#create")
|
||||||
|
)
|
||||||
|
} else if (!canCreateRepository(groupName, context.loginAccount.get)) {
|
||||||
|
Forbidden()
|
||||||
|
} else {
|
||||||
val f = createRepository(
|
val f = createRepository(
|
||||||
context.loginAccount.get,
|
context.loginAccount.get,
|
||||||
groupName,
|
groupName,
|
||||||
@@ -129,11 +137,6 @@ trait ApiRepositoryControllerBase extends ControllerBase {
|
|||||||
getRepository(groupName, data.name).get
|
getRepository(groupName, data.name).get
|
||||||
}
|
}
|
||||||
JsonFormat(ApiRepository(repository, ApiUser(getAccountByUserName(groupName).get)))
|
JsonFormat(ApiRepository(repository, ApiUser(getAccountByUserName(groupName).get)))
|
||||||
} else {
|
|
||||||
ApiError(
|
|
||||||
"A repository with this name already exists for this group",
|
|
||||||
Some("https://developer.github.com/v3/repos/#create")
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}) getOrElse NotFound()
|
}) getOrElse NotFound()
|
||||||
@@ -141,7 +144,7 @@ trait ApiRepositoryControllerBase extends ControllerBase {
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
* vi. Get
|
* vi. Get
|
||||||
* https://developer.github.com/v3/repos/#get
|
* https://docs.github.com/en/rest/reference/repos#get-a-repository
|
||||||
*/
|
*/
|
||||||
get("/api/v3/repos/:owner/:repository")(referrersOnly { repository =>
|
get("/api/v3/repos/:owner/:repository")(referrersOnly { repository =>
|
||||||
JsonFormat(ApiRepository(repository, ApiUser(getAccountByUserName(repository.owner).get)))
|
JsonFormat(ApiRepository(repository, ApiUser(getAccountByUserName(repository.owner).get)))
|
||||||
@@ -149,32 +152,32 @@ trait ApiRepositoryControllerBase extends ControllerBase {
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
* vii. Edit
|
* vii. Edit
|
||||||
* https://developer.github.com/v3/repos/#edit
|
* https://docs.github.com/en/rest/reference/repos#update-a-repository
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* viii. List all topics for a repository
|
* viii. List all topics for a repository
|
||||||
* https://developer.github.com/v3/repos/#list-all-topics-for-a-repository
|
* https://docs.github.com/en/rest/reference/repos#get-all-repository-topics
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* ix. Replace all topics for a repository
|
* ix. Replace all topics for a repository
|
||||||
* https://developer.github.com/v3/repos/#replace-all-topics-for-a-repository
|
* https://docs.github.com/en/rest/reference/repos#replace-all-repository-topics
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* x. List contributors
|
* x. List contributors
|
||||||
* https://developer.github.com/v3/repos/#list-contributors
|
* https://docs.github.com/en/rest/reference/repos#list-repository-contributors
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* xi. List languages
|
* xi. List languages
|
||||||
* https://developer.github.com/v3/repos/#list-languages
|
* https://docs.github.com/en/rest/reference/repos#list-repository-languages
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* xii. List teams
|
* xii. List teams
|
||||||
* https://developer.github.com/v3/repos/#list-teams
|
* https://docs.github.com/en/rest/reference/repos#list-repository-teams
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -182,19 +185,21 @@ trait ApiRepositoryControllerBase extends ControllerBase {
|
|||||||
* https://docs.github.com/en/rest/reference/repos#list-repository-tags
|
* https://docs.github.com/en/rest/reference/repos#list-repository-tags
|
||||||
*/
|
*/
|
||||||
get("/api/v3/repos/:owner/:repository/tags")(referrersOnly { repository =>
|
get("/api/v3/repos/:owner/:repository/tags")(referrersOnly { repository =>
|
||||||
JsonFormat(
|
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
|
||||||
repository.tags.map(tagInfo => ApiTag(tagInfo.name, RepositoryName(repository), tagInfo.id))
|
JsonFormat(
|
||||||
)
|
self.getRef("tags", repository)
|
||||||
|
)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* xiv. Delete a repository
|
* xiv. Delete a repository
|
||||||
* https://developer.github.com/v3/repos/#delete-a-repository
|
* https://docs.github.com/en/rest/reference/repos#delete-a-repository
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* xv. Transfer a repository
|
* xv. Transfer a repository
|
||||||
* https://developer.github.com/v3/repos/#transfer-a-repository
|
* https://docs.github.com/en/rest/reference/repos#transfer-a-repository
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -104,7 +104,7 @@ trait ApiUserControllerBase extends ControllerBase {
|
|||||||
*/
|
*/
|
||||||
delete("/api/v3/users/:userName/suspended")(adminOnly {
|
delete("/api/v3/users/:userName/suspended")(adminOnly {
|
||||||
val userName = params("userName")
|
val userName = params("userName")
|
||||||
getAccountByUserName(userName) match {
|
getAccountByUserName(userName, true) match {
|
||||||
case Some(targetAccount) =>
|
case Some(targetAccount) =>
|
||||||
updateAccount(targetAccount.copy(isRemoved = false))
|
updateAccount(targetAccount.copy(isRemoved = false))
|
||||||
NoContent()
|
NoContent()
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ package gitbucket.core.model
|
|||||||
trait AccountWebHookComponent extends TemplateComponent { self: Profile =>
|
trait AccountWebHookComponent extends TemplateComponent { self: Profile =>
|
||||||
import profile.api._
|
import profile.api._
|
||||||
|
|
||||||
private implicit val whContentTypeColumnType =
|
private implicit val whContentTypeColumnType: BaseColumnType[WebHookContentType] =
|
||||||
MappedColumnType.base[WebHookContentType, String](whct => whct.code, code => WebHookContentType.valueOf(code))
|
MappedColumnType.base[WebHookContentType, String](whct => whct.code, code => WebHookContentType.valueOf(code))
|
||||||
|
|
||||||
lazy val AccountWebHooks = TableQuery[AccountWebHooks]
|
lazy val AccountWebHooks = TableQuery[AccountWebHooks]
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
package gitbucket.core.model
|
package gitbucket.core.model
|
||||||
|
|
||||||
trait AccountWebHookEventComponent extends TemplateComponent {
|
trait AccountWebHookEventComponent extends TemplateComponent { self: Profile =>
|
||||||
self: Profile =>
|
|
||||||
|
|
||||||
import profile.api._
|
import profile.api._
|
||||||
import gitbucket.core.model.Profile.AccountWebHooks
|
import gitbucket.core.model.Profile.AccountWebHooks
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,8 @@ trait CommitStatusComponent extends TemplateComponent { self: Profile =>
|
|||||||
import profile.api._
|
import profile.api._
|
||||||
import self._
|
import self._
|
||||||
|
|
||||||
implicit val commitStateColumnType = MappedColumnType.base[CommitState, String](b => b.name, i => CommitState(i))
|
implicit val commitStateColumnType: BaseColumnType[CommitState] =
|
||||||
|
MappedColumnType.base[CommitState, String](b => b.name, i => CommitState(i))
|
||||||
|
|
||||||
lazy val CommitStatuses = TableQuery[CommitStatuses]
|
lazy val CommitStatuses = TableQuery[CommitStatuses]
|
||||||
class CommitStatuses(tag: Tag) extends Table[CommitStatus](tag, "COMMIT_STATUS") with CommitTemplate {
|
class CommitStatuses(tag: Tag) extends Table[CommitStatus](tag, "COMMIT_STATUS") with CommitTemplate {
|
||||||
@@ -77,7 +78,7 @@ object CommitState {
|
|||||||
|
|
||||||
val values: Vector[CommitState] = Vector(PENDING, SUCCESS, ERROR, FAILURE)
|
val values: Vector[CommitState] = Vector(PENDING, SUCCESS, ERROR, FAILURE)
|
||||||
|
|
||||||
private val map: Map[String, CommitState] = values.map(enum => enum.name -> enum).toMap
|
private val map: Map[String, CommitState] = values.map(e => e.name -> e).toMap
|
||||||
|
|
||||||
def apply(name: String): CommitState = map(name)
|
def apply(name: String): CommitState = map(name)
|
||||||
|
|
||||||
|
|||||||
@@ -10,15 +10,17 @@ trait Profile {
|
|||||||
/**
|
/**
|
||||||
* java.util.Date Mapped Column Types
|
* java.util.Date Mapped Column Types
|
||||||
*/
|
*/
|
||||||
implicit val dateColumnType = MappedColumnType.base[java.util.Date, java.sql.Timestamp](
|
implicit val dateColumnType: BaseColumnType[java.util.Date] =
|
||||||
d => new java.sql.Timestamp(d.getTime),
|
MappedColumnType.base[java.util.Date, java.sql.Timestamp](
|
||||||
t => new java.util.Date(t.getTime)
|
d => new java.sql.Timestamp(d.getTime),
|
||||||
)
|
t => new java.util.Date(t.getTime)
|
||||||
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* WebHookBase.Event Column Types
|
* WebHookBase.Event Column Types
|
||||||
*/
|
*/
|
||||||
implicit val eventColumnType = MappedColumnType.base[WebHook.Event, String](_.name, WebHook.Event.valueOf(_))
|
implicit val eventColumnType: BaseColumnType[WebHook.Event] =
|
||||||
|
MappedColumnType.base[WebHook.Event, String](_.name, WebHook.Event.valueOf(_))
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extends Column to add conditional condition
|
* Extends Column to add conditional condition
|
||||||
|
|||||||
@@ -2,9 +2,7 @@ package gitbucket.core.model
|
|||||||
|
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
|
|
||||||
trait ReleaseAssetComponent extends TemplateComponent {
|
trait ReleaseAssetComponent extends TemplateComponent { self: Profile =>
|
||||||
self: Profile =>
|
|
||||||
|
|
||||||
import profile.api._
|
import profile.api._
|
||||||
import self._
|
import self._
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
package gitbucket.core.model
|
package gitbucket.core.model
|
||||||
|
|
||||||
trait ReleaseTagComponent extends TemplateComponent {
|
trait ReleaseTagComponent extends TemplateComponent { self: Profile =>
|
||||||
self: Profile =>
|
|
||||||
|
|
||||||
import profile.api._
|
import profile.api._
|
||||||
import self._
|
import self._
|
||||||
|
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ trait RepositoryComponent extends TemplateComponent { self: Profile =>
|
|||||||
val allowFork = column[Boolean]("ALLOW_FORK")
|
val allowFork = column[Boolean]("ALLOW_FORK")
|
||||||
val mergeOptions = column[String]("MERGE_OPTIONS")
|
val mergeOptions = column[String]("MERGE_OPTIONS")
|
||||||
val defaultMergeOption = column[String]("DEFAULT_MERGE_OPTION")
|
val defaultMergeOption = column[String]("DEFAULT_MERGE_OPTION")
|
||||||
|
val safeMode = column[Boolean]("SAFE_MODE")
|
||||||
|
|
||||||
def * =
|
def * =
|
||||||
(
|
(
|
||||||
@@ -41,7 +42,16 @@ trait RepositoryComponent extends TemplateComponent { self: Profile =>
|
|||||||
parentUserName.?,
|
parentUserName.?,
|
||||||
parentRepositoryName.?
|
parentRepositoryName.?
|
||||||
),
|
),
|
||||||
(issuesOption, externalIssuesUrl.?, wikiOption, externalWikiUrl.?, allowFork, mergeOptions, defaultMergeOption)
|
(
|
||||||
|
issuesOption,
|
||||||
|
externalIssuesUrl.?,
|
||||||
|
wikiOption,
|
||||||
|
externalWikiUrl.?,
|
||||||
|
allowFork,
|
||||||
|
mergeOptions,
|
||||||
|
defaultMergeOption,
|
||||||
|
safeMode
|
||||||
|
)
|
||||||
).shaped.<>(
|
).shaped.<>(
|
||||||
{
|
{
|
||||||
case (repository, options) =>
|
case (repository, options) =>
|
||||||
@@ -112,5 +122,6 @@ case class RepositoryOptions(
|
|||||||
externalWikiUrl: Option[String],
|
externalWikiUrl: Option[String],
|
||||||
allowFork: Boolean,
|
allowFork: Boolean,
|
||||||
mergeOptions: String,
|
mergeOptions: String,
|
||||||
defaultMergeOption: String
|
defaultMergeOption: String,
|
||||||
|
safeMode: Boolean
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ package gitbucket.core.model
|
|||||||
trait RepositoryWebHookComponent extends TemplateComponent { self: Profile =>
|
trait RepositoryWebHookComponent extends TemplateComponent { self: Profile =>
|
||||||
import profile.api._
|
import profile.api._
|
||||||
|
|
||||||
implicit val whContentTypeColumnType =
|
implicit val whContentTypeColumnType: BaseColumnType[WebHookContentType] =
|
||||||
MappedColumnType.base[WebHookContentType, String](whct => whct.code, code => WebHookContentType.valueOf(code))
|
MappedColumnType.base[WebHookContentType, String](whct => whct.code, code => WebHookContentType.valueOf(code))
|
||||||
|
|
||||||
lazy val RepositoryWebHooks = TableQuery[RepositoryWebHooks]
|
lazy val RepositoryWebHooks = TableQuery[RepositoryWebHooks]
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ object WebHookContentType {
|
|||||||
|
|
||||||
val values: Vector[WebHookContentType] = Vector(JSON, FORM)
|
val values: Vector[WebHookContentType] = Vector(JSON, FORM)
|
||||||
|
|
||||||
private val map: Map[String, WebHookContentType] = values.map(enum => enum.code -> enum).toMap
|
private val map: Map[String, WebHookContentType] = values.map(e => e.code -> e).toMap
|
||||||
|
|
||||||
def apply(code: String): WebHookContentType = map(code)
|
def apply(code: String): WebHookContentType = map(code)
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,15 @@
|
|||||||
package gitbucket.core.plugin
|
package gitbucket.core.plugin
|
||||||
|
|
||||||
import javax.servlet.ServletContext
|
import javax.servlet.ServletContext
|
||||||
|
|
||||||
import gitbucket.core.controller.{Context, ControllerBase}
|
import gitbucket.core.controller.{Context, ControllerBase}
|
||||||
import gitbucket.core.model.{Account, Issue}
|
import gitbucket.core.model.{Account, Issue}
|
||||||
import gitbucket.core.service.RepositoryService.RepositoryInfo
|
import gitbucket.core.service.RepositoryService.RepositoryInfo
|
||||||
import gitbucket.core.service.SystemSettingsService.SystemSettings
|
import gitbucket.core.service.SystemSettingsService.SystemSettings
|
||||||
import io.github.gitbucket.solidbase.model.Version
|
import io.github.gitbucket.solidbase.model.Version
|
||||||
|
import org.apache.sshd.server.channel.ChannelSession
|
||||||
import org.apache.sshd.server.command.Command
|
import org.apache.sshd.server.command.Command
|
||||||
import play.twirl.api.Html
|
import play.twirl.api.Html
|
||||||
|
|
||||||
import scala.util.Using
|
import scala.util.Using
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -323,7 +324,7 @@ abstract class Plugin {
|
|||||||
/**
|
/**
|
||||||
* Override to add ssh command providers.
|
* Override to add ssh command providers.
|
||||||
*/
|
*/
|
||||||
val sshCommandProviders: Seq[PartialFunction[String, Command]] = Nil
|
val sshCommandProviders: Seq[PartialFunction[String, ChannelSession => Command]] = Nil
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Override to add ssh command providers.
|
* Override to add ssh command providers.
|
||||||
@@ -332,7 +333,7 @@ abstract class Plugin {
|
|||||||
registry: PluginRegistry,
|
registry: PluginRegistry,
|
||||||
context: ServletContext,
|
context: ServletContext,
|
||||||
settings: SystemSettings
|
settings: SystemSettings
|
||||||
): Seq[PartialFunction[String, Command]] = Nil
|
): Seq[PartialFunction[String, ChannelSession => Command]] = Nil
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method is invoked in initialization of plugin system.
|
* This method is invoked in initialization of plugin system.
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import java.nio.file.{Files, Paths, StandardWatchEventKinds}
|
|||||||
import java.util.Base64
|
import java.util.Base64
|
||||||
import java.util.concurrent.ConcurrentLinkedQueue
|
import java.util.concurrent.ConcurrentLinkedQueue
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
|
||||||
import javax.servlet.ServletContext
|
import javax.servlet.ServletContext
|
||||||
import com.github.zafarkhaja.semver.Version
|
import com.github.zafarkhaja.semver.Version
|
||||||
import gitbucket.core.controller.{Context, ControllerBase}
|
import gitbucket.core.controller.{Context, ControllerBase}
|
||||||
@@ -21,6 +20,7 @@ import io.github.gitbucket.solidbase.Solidbase
|
|||||||
import io.github.gitbucket.solidbase.manager.JDBCVersionManager
|
import io.github.gitbucket.solidbase.manager.JDBCVersionManager
|
||||||
import io.github.gitbucket.solidbase.model.Module
|
import io.github.gitbucket.solidbase.model.Module
|
||||||
import org.apache.commons.io.FileUtils
|
import org.apache.commons.io.FileUtils
|
||||||
|
import org.apache.sshd.server.channel.ChannelSession
|
||||||
import org.apache.sshd.server.command.Command
|
import org.apache.sshd.server.command.Command
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
import play.twirl.api.Html
|
import play.twirl.api.Html
|
||||||
@@ -58,7 +58,7 @@ class PluginRegistry {
|
|||||||
private val suggestionProviders = new ConcurrentLinkedQueue[SuggestionProvider]
|
private val suggestionProviders = new ConcurrentLinkedQueue[SuggestionProvider]
|
||||||
suggestionProviders.add(new UserNameSuggestionProvider())
|
suggestionProviders.add(new UserNameSuggestionProvider())
|
||||||
suggestionProviders.add(new IssueSuggestionProvider())
|
suggestionProviders.add(new IssueSuggestionProvider())
|
||||||
private val sshCommandProviders = new ConcurrentLinkedQueue[PartialFunction[String, Command]]()
|
private val sshCommandProviders = new ConcurrentLinkedQueue[PartialFunction[String, ChannelSession => Command]]()
|
||||||
|
|
||||||
def addPlugin(pluginInfo: PluginInfo): Unit = plugins.add(pluginInfo)
|
def addPlugin(pluginInfo: PluginInfo): Unit = plugins.add(pluginInfo)
|
||||||
|
|
||||||
@@ -177,10 +177,11 @@ class PluginRegistry {
|
|||||||
|
|
||||||
def getSuggestionProviders: Seq[SuggestionProvider] = suggestionProviders.asScala.toSeq
|
def getSuggestionProviders: Seq[SuggestionProvider] = suggestionProviders.asScala.toSeq
|
||||||
|
|
||||||
def addSshCommandProvider(sshCommandProvider: PartialFunction[String, Command]): Unit =
|
def addSshCommandProvider(sshCommandProvider: PartialFunction[String, ChannelSession => Command]): Unit =
|
||||||
sshCommandProviders.add(sshCommandProvider)
|
sshCommandProviders.add(sshCommandProvider)
|
||||||
|
|
||||||
def getSshCommandProviders: Seq[PartialFunction[String, Command]] = sshCommandProviders.asScala.toSeq
|
def getSshCommandProviders: Seq[PartialFunction[String, ChannelSession => Command]] =
|
||||||
|
sshCommandProviders.asScala.toSeq
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -21,13 +21,13 @@ trait AccessTokenService {
|
|||||||
* @return (TokenId, Token)
|
* @return (TokenId, Token)
|
||||||
*/
|
*/
|
||||||
def generateAccessToken(userName: String, note: String)(implicit s: Session): (Int, String) = {
|
def generateAccessToken(userName: String, note: String)(implicit s: Session): (Int, String) = {
|
||||||
var token: String = null
|
var token: String = makeAccessTokenString
|
||||||
var hash: String = null
|
var hash: String = tokenToHash(token)
|
||||||
|
|
||||||
do {
|
while (AccessTokens.filter(_.tokenHash === hash.bind).exists.run) {
|
||||||
token = makeAccessTokenString
|
token = makeAccessTokenString
|
||||||
hash = tokenToHash(token)
|
hash = tokenToHash(token)
|
||||||
} while (AccessTokens.filter(_.tokenHash === hash.bind).exists.run)
|
}
|
||||||
|
|
||||||
val newToken = AccessToken(userName = userName, note = note, tokenHash = hash)
|
val newToken = AccessToken(userName = userName, note = note, tokenHash = hash)
|
||||||
val tokenId = (AccessTokens returning AccessTokens.map(_.accessTokenId)) insert newToken
|
val tokenId = (AccessTokens returning AccessTokens.map(_.accessTokenId)) insert newToken
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ trait AccountService {
|
|||||||
case _ => None
|
case _ => None
|
||||||
}
|
}
|
||||||
case account if (!account.isGroupAccount && account.password == sha1(password)) => Some(account)
|
case account if (!account.isGroupAccount && account.password == sha1(password)) => Some(account)
|
||||||
} getOrElse None
|
}.flatten
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
package gitbucket.core.service
|
package gitbucket.core.service
|
||||||
|
|
||||||
import gitbucket.core.model.Activity
|
import gitbucket.core.model.Activity
|
||||||
import gitbucket.core.util.JGitUtil
|
|
||||||
import gitbucket.core.model.Profile._
|
|
||||||
import gitbucket.core.util.Directory._
|
import gitbucket.core.util.Directory._
|
||||||
import org.json4s._
|
import org.json4s._
|
||||||
import org.json4s.jackson.Serialization
|
import org.json4s.jackson.Serialization
|
||||||
@@ -11,10 +9,8 @@ import org.json4s.jackson.Serialization.{read, write}
|
|||||||
import scala.util.Using
|
import scala.util.Using
|
||||||
import java.io.FileOutputStream
|
import java.io.FileOutputStream
|
||||||
import java.nio.charset.StandardCharsets
|
import java.nio.charset.StandardCharsets
|
||||||
import java.util.UUID
|
|
||||||
|
|
||||||
import gitbucket.core.controller.Context
|
import gitbucket.core.controller.Context
|
||||||
import gitbucket.core.model.activity.BaseActivityInfo
|
|
||||||
import org.apache.commons.io.input.ReversedLinesFileReader
|
import org.apache.commons.io.input.ReversedLinesFileReader
|
||||||
|
|
||||||
import scala.collection.mutable.ListBuffer
|
import scala.collection.mutable.ListBuffer
|
||||||
@@ -22,7 +18,7 @@ import scala.collection.mutable.ListBuffer
|
|||||||
trait ActivityService {
|
trait ActivityService {
|
||||||
self: RequestCache =>
|
self: RequestCache =>
|
||||||
|
|
||||||
private implicit val formats = Serialization.formats(NoTypeHints)
|
private implicit val formats: Formats = Serialization.formats(NoTypeHints)
|
||||||
|
|
||||||
private def writeLog(activity: Activity): Unit = {
|
private def writeLog(activity: Activity): Unit = {
|
||||||
Using.resource(new FileOutputStream(ActivityLog, true)) { out =>
|
Using.resource(new FileOutputStream(ActivityLog, true)) { out =>
|
||||||
@@ -43,9 +39,7 @@ trait ActivityService {
|
|||||||
if (isPublic == false) {
|
if (isPublic == false) {
|
||||||
list += activity
|
list += activity
|
||||||
} else {
|
} else {
|
||||||
if (!getRepositoryInfoFromCache(activity.userName, activity.repositoryName)
|
if (!getRepositoryInfoFromCache(activity.userName, activity.repositoryName).forall(_.isPrivate)) {
|
||||||
.map(_.isPrivate)
|
|
||||||
.getOrElse(true)) {
|
|
||||||
list += activity
|
list += activity
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -65,9 +59,7 @@ trait ActivityService {
|
|||||||
var json: String = null
|
var json: String = null
|
||||||
while (list.length < 50 && { json = reader.readLine(); json } != null) {
|
while (list.length < 50 && { json = reader.readLine(); json } != null) {
|
||||||
val activity = read[Activity](json)
|
val activity = read[Activity](json)
|
||||||
if (!getRepositoryInfoFromCache(activity.userName, activity.repositoryName)
|
if (!getRepositoryInfoFromCache(activity.userName, activity.repositoryName).forall(_.isPrivate)) {
|
||||||
.map(_.isPrivate)
|
|
||||||
.getOrElse(true)) {
|
|
||||||
list += activity
|
list += activity
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -87,9 +79,7 @@ trait ActivityService {
|
|||||||
val activity = read[Activity](json)
|
val activity = read[Activity](json)
|
||||||
if (owners.contains(activity.userName)) {
|
if (owners.contains(activity.userName)) {
|
||||||
list += activity
|
list += activity
|
||||||
} else if (!getRepositoryInfoFromCache(activity.userName, activity.repositoryName)
|
} else if (!getRepositoryInfoFromCache(activity.userName, activity.repositoryName).forall(_.isPrivate)) {
|
||||||
.map(_.isPrivate)
|
|
||||||
.getOrElse(true)) {
|
|
||||||
list += activity
|
list += activity
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import gitbucket.core.model.activity.{
|
|||||||
}
|
}
|
||||||
import gitbucket.core.plugin.{IssueHook, PluginRegistry}
|
import gitbucket.core.plugin.{IssueHook, PluginRegistry}
|
||||||
import gitbucket.core.service.RepositoryService.RepositoryInfo
|
import gitbucket.core.service.RepositoryService.RepositoryInfo
|
||||||
import gitbucket.core.util.SyntaxSugars._
|
|
||||||
import gitbucket.core.util.Implicits._
|
import gitbucket.core.util.Implicits._
|
||||||
|
|
||||||
trait HandleCommentService {
|
trait HandleCommentService {
|
||||||
@@ -34,105 +33,104 @@ trait HandleCommentService {
|
|||||||
actionOpt: Option[String]
|
actionOpt: Option[String]
|
||||||
)(implicit context: Context, s: Session) = {
|
)(implicit context: Context, s: Session) = {
|
||||||
context.loginAccount.flatMap { loginAccount =>
|
context.loginAccount.flatMap { loginAccount =>
|
||||||
defining(repository.owner, repository.name) {
|
val owner = repository.owner
|
||||||
case (owner, name) =>
|
val name = repository.name
|
||||||
val userName = loginAccount.userName
|
val userName = loginAccount.userName
|
||||||
|
|
||||||
actionOpt.collect {
|
actionOpt.collect {
|
||||||
case "close" if !issue.closed =>
|
case "close" if !issue.closed =>
|
||||||
updateClosed(owner, name, issue.issueId, true)
|
updateClosed(owner, name, issue.issueId, true)
|
||||||
case "reopen" if issue.closed =>
|
case "reopen" if issue.closed =>
|
||||||
updateClosed(owner, name, issue.issueId, false)
|
updateClosed(owner, name, issue.issueId, false)
|
||||||
}
|
|
||||||
|
|
||||||
val (action, _) = actionOpt
|
|
||||||
.collect {
|
|
||||||
case "close" if !issue.closed =>
|
|
||||||
val info = if (issue.isPullRequest) {
|
|
||||||
ClosePullRequestInfo(owner, name, userName, issue.issueId, issue.title)
|
|
||||||
} else {
|
|
||||||
CloseIssueInfo(owner, name, userName, issue.issueId, issue.title)
|
|
||||||
}
|
|
||||||
recordActivity(info)
|
|
||||||
Some("close") -> info
|
|
||||||
case "reopen" if issue.closed =>
|
|
||||||
val info = if (issue.isPullRequest) {
|
|
||||||
ReopenPullRequestInfo(owner, name, userName, issue.issueId, issue.title)
|
|
||||||
} else {
|
|
||||||
ReopenIssueInfo(owner, name, userName, issue.issueId, issue.title)
|
|
||||||
}
|
|
||||||
recordActivity(info)
|
|
||||||
Some("reopen") -> info
|
|
||||||
}
|
|
||||||
.getOrElse(None -> None)
|
|
||||||
|
|
||||||
val commentId = (content, action) match {
|
|
||||||
case (None, None) => None
|
|
||||||
case (None, Some(action)) =>
|
|
||||||
Some(createComment(owner, name, userName, issue.issueId, action.capitalize, action))
|
|
||||||
case (Some(content), _) =>
|
|
||||||
val id = Some(
|
|
||||||
createComment(
|
|
||||||
owner,
|
|
||||||
name,
|
|
||||||
userName,
|
|
||||||
issue.issueId,
|
|
||||||
content,
|
|
||||||
action.map(_ + "_comment").getOrElse("comment")
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
// record comment activity
|
|
||||||
val commentInfo = if (issue.isPullRequest) {
|
|
||||||
PullRequestCommentInfo(owner, name, userName, content, issue.issueId)
|
|
||||||
} else {
|
|
||||||
IssueCommentInfo(owner, name, userName, content, issue.issueId)
|
|
||||||
}
|
|
||||||
recordActivity(commentInfo)
|
|
||||||
|
|
||||||
// extract references and create refer comment
|
|
||||||
createReferComment(owner, name, issue, content, loginAccount)
|
|
||||||
|
|
||||||
id
|
|
||||||
}
|
|
||||||
|
|
||||||
// call web hooks
|
|
||||||
action match {
|
|
||||||
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, context.settings)
|
|
||||||
else
|
|
||||||
callIssuesWebHook(webHookAction, repository, issue, loginAccount, context.settings)
|
|
||||||
}
|
|
||||||
|
|
||||||
// call hooks
|
|
||||||
content foreach { x =>
|
|
||||||
if (issue.isPullRequest)
|
|
||||||
PluginRegistry().getPullRequestHooks.foreach(_.addedComment(commentId.get, x, issue, repository))
|
|
||||||
else
|
|
||||||
PluginRegistry().getIssueHooks.foreach(_.addedComment(commentId.get, x, issue, repository))
|
|
||||||
}
|
|
||||||
action foreach {
|
|
||||||
case "close" =>
|
|
||||||
if (issue.isPullRequest)
|
|
||||||
PluginRegistry().getPullRequestHooks.foreach(_.closed(issue, repository))
|
|
||||||
else
|
|
||||||
PluginRegistry().getIssueHooks.foreach(_.closed(issue, repository))
|
|
||||||
case "reopen" =>
|
|
||||||
if (issue.isPullRequest)
|
|
||||||
PluginRegistry().getPullRequestHooks.foreach(_.reopened(issue, repository))
|
|
||||||
else
|
|
||||||
PluginRegistry().getIssueHooks.foreach(_.reopened(issue, repository))
|
|
||||||
}
|
|
||||||
|
|
||||||
commentId.map(issue -> _)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val (action, _) = actionOpt
|
||||||
|
.collect {
|
||||||
|
case "close" if !issue.closed =>
|
||||||
|
val info = if (issue.isPullRequest) {
|
||||||
|
ClosePullRequestInfo(owner, name, userName, issue.issueId, issue.title)
|
||||||
|
} else {
|
||||||
|
CloseIssueInfo(owner, name, userName, issue.issueId, issue.title)
|
||||||
|
}
|
||||||
|
recordActivity(info)
|
||||||
|
Some("close") -> info
|
||||||
|
case "reopen" if issue.closed =>
|
||||||
|
val info = if (issue.isPullRequest) {
|
||||||
|
ReopenPullRequestInfo(owner, name, userName, issue.issueId, issue.title)
|
||||||
|
} else {
|
||||||
|
ReopenIssueInfo(owner, name, userName, issue.issueId, issue.title)
|
||||||
|
}
|
||||||
|
recordActivity(info)
|
||||||
|
Some("reopen") -> info
|
||||||
|
}
|
||||||
|
.getOrElse(None -> None)
|
||||||
|
|
||||||
|
val commentId = (content, action) match {
|
||||||
|
case (None, None) => None
|
||||||
|
case (None, Some(action)) =>
|
||||||
|
Some(createComment(owner, name, userName, issue.issueId, action.capitalize, action))
|
||||||
|
case (Some(content), _) =>
|
||||||
|
val id = Some(
|
||||||
|
createComment(
|
||||||
|
owner,
|
||||||
|
name,
|
||||||
|
userName,
|
||||||
|
issue.issueId,
|
||||||
|
content,
|
||||||
|
action.map(_ + "_comment").getOrElse("comment")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
// record comment activity
|
||||||
|
val commentInfo = if (issue.isPullRequest) {
|
||||||
|
PullRequestCommentInfo(owner, name, userName, content, issue.issueId)
|
||||||
|
} else {
|
||||||
|
IssueCommentInfo(owner, name, userName, content, issue.issueId)
|
||||||
|
}
|
||||||
|
recordActivity(commentInfo)
|
||||||
|
|
||||||
|
// extract references and create refer comment
|
||||||
|
createReferComment(owner, name, issue, content, loginAccount)
|
||||||
|
|
||||||
|
id
|
||||||
|
}
|
||||||
|
|
||||||
|
// call web hooks
|
||||||
|
action match {
|
||||||
|
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, context.settings)
|
||||||
|
else
|
||||||
|
callIssuesWebHook(webHookAction, repository, issue, loginAccount, context.settings)
|
||||||
|
}
|
||||||
|
|
||||||
|
// call hooks
|
||||||
|
content foreach { x =>
|
||||||
|
if (issue.isPullRequest)
|
||||||
|
PluginRegistry().getPullRequestHooks.foreach(_.addedComment(commentId.get, x, issue, repository))
|
||||||
|
else
|
||||||
|
PluginRegistry().getIssueHooks.foreach(_.addedComment(commentId.get, x, issue, repository))
|
||||||
|
}
|
||||||
|
action foreach {
|
||||||
|
case "close" =>
|
||||||
|
if (issue.isPullRequest)
|
||||||
|
PluginRegistry().getPullRequestHooks.foreach(_.closed(issue, repository))
|
||||||
|
else
|
||||||
|
PluginRegistry().getIssueHooks.foreach(_.closed(issue, repository))
|
||||||
|
case "reopen" =>
|
||||||
|
if (issue.isPullRequest)
|
||||||
|
PluginRegistry().getPullRequestHooks.foreach(_.reopened(issue, repository))
|
||||||
|
else
|
||||||
|
PluginRegistry().getIssueHooks.foreach(_.reopened(issue, repository))
|
||||||
|
}
|
||||||
|
|
||||||
|
commentId.map(issue -> _)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -161,33 +159,32 @@ trait HandleCommentService {
|
|||||||
content: Option[String]
|
content: Option[String]
|
||||||
)(implicit context: Context, s: Session): Option[(Issue, Int)] = {
|
)(implicit context: Context, s: Session): Option[(Issue, Int)] = {
|
||||||
context.loginAccount.flatMap { loginAccount =>
|
context.loginAccount.flatMap { loginAccount =>
|
||||||
defining(repository.owner, repository.name) {
|
val owner = repository.owner
|
||||||
case (owner, name) =>
|
val name = repository.name
|
||||||
val userName = loginAccount.userName
|
val userName = loginAccount.userName
|
||||||
content match {
|
content match {
|
||||||
case Some(content) =>
|
case Some(content) =>
|
||||||
// Update comment
|
// Update comment
|
||||||
val _commentId = Some(updateComment(issue.issueId, commentId.toInt, content))
|
val _commentId = Some(updateComment(issue.issueId, commentId.toInt, content))
|
||||||
// Record comment activity
|
// Record comment activity
|
||||||
val commentInfo = if (issue.isPullRequest) {
|
val commentInfo = if (issue.isPullRequest) {
|
||||||
PullRequestCommentInfo(owner, name, userName, content, issue.issueId)
|
PullRequestCommentInfo(owner, name, userName, content, issue.issueId)
|
||||||
} else {
|
} else {
|
||||||
IssueCommentInfo(owner, name, userName, content, issue.issueId)
|
IssueCommentInfo(owner, name, userName, content, issue.issueId)
|
||||||
}
|
|
||||||
recordActivity(commentInfo)
|
|
||||||
// extract references and create refer comment
|
|
||||||
createReferComment(owner, name, issue, content, loginAccount)
|
|
||||||
// call web hooks
|
|
||||||
commentId foreach (callIssueCommentWebHook(repository, issue, _, loginAccount, context.settings))
|
|
||||||
// call hooks
|
|
||||||
if (issue.isPullRequest)
|
|
||||||
PluginRegistry().getPullRequestHooks
|
|
||||||
.foreach(_.updatedComment(commentId.toInt, content, issue, repository))
|
|
||||||
else
|
|
||||||
PluginRegistry().getIssueHooks.foreach(_.updatedComment(commentId.toInt, content, issue, repository))
|
|
||||||
_commentId.map(issue -> _)
|
|
||||||
case _ => None
|
|
||||||
}
|
}
|
||||||
|
recordActivity(commentInfo)
|
||||||
|
// extract references and create refer comment
|
||||||
|
createReferComment(owner, name, issue, content, loginAccount)
|
||||||
|
// call web hooks
|
||||||
|
commentId foreach (callIssueCommentWebHook(repository, issue, _, loginAccount, context.settings))
|
||||||
|
// call hooks
|
||||||
|
if (issue.isPullRequest)
|
||||||
|
PluginRegistry().getPullRequestHooks
|
||||||
|
.foreach(_.updatedComment(commentId.toInt, content, issue, repository))
|
||||||
|
else
|
||||||
|
PluginRegistry().getIssueHooks.foreach(_.updatedComment(commentId.toInt, content, issue, repository))
|
||||||
|
_commentId.map(issue -> _)
|
||||||
|
case _ => None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,17 +5,7 @@ import gitbucket.core.util.StringUtil._
|
|||||||
import gitbucket.core.util.Implicits._
|
import gitbucket.core.util.Implicits._
|
||||||
import gitbucket.core.util.SyntaxSugars._
|
import gitbucket.core.util.SyntaxSugars._
|
||||||
import gitbucket.core.controller.Context
|
import gitbucket.core.controller.Context
|
||||||
import gitbucket.core.model.{
|
import gitbucket.core.model.{Account, Issue, IssueComment, IssueLabel, Label, PullRequest, Repository, Role}
|
||||||
Account,
|
|
||||||
CommitState,
|
|
||||||
Issue,
|
|
||||||
IssueComment,
|
|
||||||
IssueLabel,
|
|
||||||
Label,
|
|
||||||
PullRequest,
|
|
||||||
Repository,
|
|
||||||
Role
|
|
||||||
}
|
|
||||||
import gitbucket.core.model.Profile._
|
import gitbucket.core.model.Profile._
|
||||||
import gitbucket.core.model.Profile.profile._
|
import gitbucket.core.model.Profile.profile._
|
||||||
import gitbucket.core.model.Profile.profile.blockingApi._
|
import gitbucket.core.model.Profile.profile.blockingApi._
|
||||||
@@ -346,13 +336,16 @@ trait IssuesService {
|
|||||||
implicit s: Session
|
implicit s: Session
|
||||||
) =
|
) =
|
||||||
Issues filter { t1 =>
|
Issues filter { t1 =>
|
||||||
(if (repos.size == 1) {
|
(if (repos.sizeIs == 1) {
|
||||||
t1.byRepository(repos.head._1, repos.head._2)
|
t1.byRepository(repos.head._1, repos.head._2)
|
||||||
} else {
|
} else {
|
||||||
((t1.userName ++ "/" ++ t1.repositoryName) inSetBind (repos.map { case (owner, repo) => s"$owner/$repo" }))
|
((t1.userName ++ "/" ++ t1.repositoryName) inSetBind (repos.map { case (owner, repo) => s"$owner/$repo" }))
|
||||||
}) &&
|
}) &&
|
||||||
(t1.closed === (condition.state == "closed").bind)
|
(condition.state match {
|
||||||
.&&(t1.milestoneId.? isEmpty, condition.milestone == Some(None))
|
case "open" => t1.closed === false
|
||||||
|
case "closed" => t1.closed === true
|
||||||
|
case _ => t1.closed === true || t1.closed === false
|
||||||
|
}).&&(t1.milestoneId.? isEmpty, condition.milestone == Some(None))
|
||||||
.&&(t1.priorityId.? isEmpty, condition.priority == Some(None))
|
.&&(t1.priorityId.? isEmpty, condition.priority == Some(None))
|
||||||
.&&(t1.assignedUserName.? isEmpty, condition.assigned == Some(None))
|
.&&(t1.assignedUserName.? isEmpty, condition.assigned == Some(None))
|
||||||
.&&(t1.openedUserName === condition.author.get.bind, condition.author.isDefined) &&
|
.&&(t1.openedUserName === condition.author.get.bind, condition.author.isDefined) &&
|
||||||
@@ -778,16 +771,37 @@ trait IssuesService {
|
|||||||
def createReferComment(owner: String, repository: String, fromIssue: Issue, message: String, loginAccount: Account)(
|
def createReferComment(owner: String, repository: String, fromIssue: Issue, message: String, loginAccount: Account)(
|
||||||
implicit s: Session
|
implicit s: Session
|
||||||
): Unit = {
|
): Unit = {
|
||||||
extractIssueId(message).foreach { issueId =>
|
extractGlobalIssueId(message).foreach {
|
||||||
val content = s"${fromIssue.issueId}:${fromIssue.title}"
|
case (_referredOwner, _referredRepository, referredIssueId) =>
|
||||||
if (getIssue(owner, repository, issueId).isDefined) {
|
val referredOwner = _referredOwner.getOrElse(owner)
|
||||||
// Not add if refer comment already exist.
|
val referredRepository = _referredRepository.getOrElse(repository)
|
||||||
if (!getComments(owner, repository, issueId.toInt).exists { x =>
|
getRepository(referredOwner, referredRepository).foreach { repo =>
|
||||||
x.action == "refer" && x.content == content
|
if (isReadable(repo.repository, Option(loginAccount))) {
|
||||||
}) {
|
getIssue(referredOwner, referredRepository, referredIssueId.get).foreach { _ =>
|
||||||
createComment(owner, repository, loginAccount.userName, issueId.toInt, content, "refer")
|
val (content, action) = if (owner == referredOwner && repository == referredRepository) {
|
||||||
|
(s"${fromIssue.issueId}:${fromIssue.title}", "refer")
|
||||||
|
} else {
|
||||||
|
(s"${fromIssue.issueId}:${owner}:${repository}:${fromIssue.title}", "refer_global")
|
||||||
|
}
|
||||||
|
referredIssueId.foreach(
|
||||||
|
x =>
|
||||||
|
// Not add if refer comment already exist.
|
||||||
|
if (!getComments(referredOwner, referredRepository, x.toInt).exists { x =>
|
||||||
|
(x.action == "refer" || x.action == "refer_global") && x.content == content
|
||||||
|
}) {
|
||||||
|
createComment(
|
||||||
|
referredOwner,
|
||||||
|
referredRepository,
|
||||||
|
loginAccount.userName,
|
||||||
|
x.toInt,
|
||||||
|
content,
|
||||||
|
action
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -928,7 +942,7 @@ object IssuesService {
|
|||||||
case x => Some(x)
|
case x => Some(x)
|
||||||
},
|
},
|
||||||
param(request, "mentioned"),
|
param(request, "mentioned"),
|
||||||
param(request, "state", Seq("open", "closed")).getOrElse("open"),
|
param(request, "state", Seq("open", "closed", "all")).getOrElse("open"),
|
||||||
param(request, "sort", Seq("created", "comments", "updated", "priority")).getOrElse("created"),
|
param(request, "sort", Seq("created", "comments", "updated", "priority")).getOrElse("created"),
|
||||||
param(request, "direction", Seq("asc", "desc")).getOrElse("desc"),
|
param(request, "direction", Seq("asc", "desc")).getOrElse("desc"),
|
||||||
param(request, "visibility"),
|
param(request, "visibility"),
|
||||||
@@ -949,7 +963,7 @@ object IssuesService {
|
|||||||
case x => Some(x)
|
case x => Some(x)
|
||||||
},
|
},
|
||||||
param(request, "mentioned"),
|
param(request, "mentioned"),
|
||||||
param(request, "state", Seq("open", "closed")).getOrElse("open"),
|
param(request, "state", Seq("open", "closed", "all")).getOrElse("open"),
|
||||||
param(request, "sort", Seq("created", "comments", "updated", "priority")).getOrElse("created"),
|
param(request, "sort", Seq("created", "comments", "updated", "priority")).getOrElse("created"),
|
||||||
param(request, "direction", Seq("asc", "desc")).getOrElse("desc"),
|
param(request, "direction", Seq("asc", "desc")).getOrElse("desc"),
|
||||||
param(request, "visibility"),
|
param(request, "visibility"),
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ trait MergeService {
|
|||||||
)(implicit s: Session, c: JsonFormat.Context): ObjectId = {
|
)(implicit s: Session, c: JsonFormat.Context): ObjectId = {
|
||||||
val beforeCommitId = git.getRepository.resolve(s"refs/heads/${branch}")
|
val beforeCommitId = git.getRepository.resolve(s"refs/heads/${branch}")
|
||||||
val afterCommitId = new MergeCacheInfo(git, repository.owner, repository.name, branch, issueId, getReceiveHooks())
|
val afterCommitId = new MergeCacheInfo(git, repository.owner, repository.name, branch, issueId, getReceiveHooks())
|
||||||
.merge(message, new PersonIdent(loginAccount.fullName, loginAccount.mailAddress))
|
.merge(message, new PersonIdent(loginAccount.fullName, loginAccount.mailAddress), loginAccount.userName)
|
||||||
callWebHook(git, repository, branch, beforeCommitId, afterCommitId, loginAccount, settings)
|
callWebHook(git, repository, branch, beforeCommitId, afterCommitId, loginAccount, settings)
|
||||||
afterCommitId
|
afterCommitId
|
||||||
}
|
}
|
||||||
@@ -90,7 +90,7 @@ trait MergeService {
|
|||||||
val beforeCommitId = git.getRepository.resolve(s"refs/heads/${branch}")
|
val beforeCommitId = git.getRepository.resolve(s"refs/heads/${branch}")
|
||||||
val afterCommitId =
|
val afterCommitId =
|
||||||
new MergeCacheInfo(git, repository.owner, repository.name, branch, issueId, getReceiveHooks())
|
new MergeCacheInfo(git, repository.owner, repository.name, branch, issueId, getReceiveHooks())
|
||||||
.rebase(new PersonIdent(loginAccount.fullName, loginAccount.mailAddress), commits)
|
.rebase(new PersonIdent(loginAccount.fullName, loginAccount.mailAddress), loginAccount.userName, commits)
|
||||||
callWebHook(git, repository, branch, beforeCommitId, afterCommitId, loginAccount, settings)
|
callWebHook(git, repository, branch, beforeCommitId, afterCommitId, loginAccount, settings)
|
||||||
afterCommitId
|
afterCommitId
|
||||||
}
|
}
|
||||||
@@ -108,7 +108,7 @@ trait MergeService {
|
|||||||
val beforeCommitId = git.getRepository.resolve(s"refs/heads/${branch}")
|
val beforeCommitId = git.getRepository.resolve(s"refs/heads/${branch}")
|
||||||
val afterCommitId =
|
val afterCommitId =
|
||||||
new MergeCacheInfo(git, repository.owner, repository.name, branch, issueId, getReceiveHooks())
|
new MergeCacheInfo(git, repository.owner, repository.name, branch, issueId, getReceiveHooks())
|
||||||
.squash(message, new PersonIdent(loginAccount.fullName, loginAccount.mailAddress))
|
.squash(message, new PersonIdent(loginAccount.fullName, loginAccount.mailAddress), loginAccount.userName)
|
||||||
callWebHook(git, repository, branch, beforeCommitId, afterCommitId, loginAccount, settings)
|
callWebHook(git, repository, branch, beforeCommitId, afterCommitId, loginAccount, settings)
|
||||||
afterCommitId
|
afterCommitId
|
||||||
}
|
}
|
||||||
@@ -648,7 +648,7 @@ object MergeService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// update branch from cache
|
// update branch from cache
|
||||||
def merge(message: String, committer: PersonIdent)(implicit s: Session): ObjectId = {
|
def merge(message: String, committer: PersonIdent, pusher: String)(implicit s: Session): ObjectId = {
|
||||||
if (checkConflict().isDefined) {
|
if (checkConflict().isDefined) {
|
||||||
throw new RuntimeException("This pull request can't merge automatically.")
|
throw new RuntimeException("This pull request can't merge automatically.")
|
||||||
}
|
}
|
||||||
@@ -665,7 +665,7 @@ object MergeService {
|
|||||||
|
|
||||||
// call pre-commit hooks
|
// call pre-commit hooks
|
||||||
val error = receiveHooks.flatMap { hook =>
|
val error = receiveHooks.flatMap { hook =>
|
||||||
hook.preReceive(userName, repositoryName, receivePack, receiveCommand, committer.getName, true)
|
hook.preReceive(userName, repositoryName, receivePack, receiveCommand, pusher, true)
|
||||||
}.headOption
|
}.headOption
|
||||||
|
|
||||||
error.foreach { error =>
|
error.foreach { error =>
|
||||||
@@ -683,7 +683,7 @@ object MergeService {
|
|||||||
objectId
|
objectId
|
||||||
}
|
}
|
||||||
|
|
||||||
def rebase(committer: PersonIdent, commits: Seq[RevCommit])(implicit s: Session): ObjectId = {
|
def rebase(committer: PersonIdent, pusher: String, commits: Seq[RevCommit])(implicit s: Session): ObjectId = {
|
||||||
if (checkConflict().isDefined) {
|
if (checkConflict().isDefined) {
|
||||||
throw new RuntimeException("This pull request can't merge automatically.")
|
throw new RuntimeException("This pull request can't merge automatically.")
|
||||||
}
|
}
|
||||||
@@ -719,7 +719,7 @@ object MergeService {
|
|||||||
|
|
||||||
// call pre-commit hooks
|
// call pre-commit hooks
|
||||||
val error = receiveHooks.flatMap { hook =>
|
val error = receiveHooks.flatMap { hook =>
|
||||||
hook.preReceive(userName, repositoryName, receivePack, receiveCommand, committer.getName, true)
|
hook.preReceive(userName, repositoryName, receivePack, receiveCommand, pusher, true)
|
||||||
}.headOption
|
}.headOption
|
||||||
|
|
||||||
error.foreach { error =>
|
error.foreach { error =>
|
||||||
@@ -738,7 +738,7 @@ object MergeService {
|
|||||||
objectId
|
objectId
|
||||||
}
|
}
|
||||||
|
|
||||||
def squash(message: String, committer: PersonIdent)(implicit s: Session): ObjectId = {
|
def squash(message: String, committer: PersonIdent, pusher: String)(implicit s: Session): ObjectId = {
|
||||||
if (checkConflict().isDefined) {
|
if (checkConflict().isDefined) {
|
||||||
throw new RuntimeException("This pull request can't merge automatically.")
|
throw new RuntimeException("This pull request can't merge automatically.")
|
||||||
}
|
}
|
||||||
@@ -769,7 +769,7 @@ object MergeService {
|
|||||||
|
|
||||||
// call pre-commit hooks
|
// call pre-commit hooks
|
||||||
val error = receiveHooks.flatMap { hook =>
|
val error = receiveHooks.flatMap { hook =>
|
||||||
hook.preReceive(userName, repositoryName, receivePack, receiveCommand, committer.getName, true)
|
hook.preReceive(userName, repositoryName, receivePack, receiveCommand, pusher, true)
|
||||||
}.headOption
|
}.headOption
|
||||||
|
|
||||||
error.foreach { error =>
|
error.foreach { error =>
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
package gitbucket.core.service
|
package gitbucket.core.service
|
||||||
|
|
||||||
|
import gitbucket.core.api.ApiMilestone
|
||||||
import gitbucket.core.model.Milestone
|
import gitbucket.core.model.Milestone
|
||||||
import gitbucket.core.model.Profile._
|
import gitbucket.core.model.Profile._
|
||||||
import gitbucket.core.model.Profile.profile.blockingApi._
|
import gitbucket.core.model.Profile.profile.blockingApi._
|
||||||
import gitbucket.core.model.Profile.dateColumnType
|
import gitbucket.core.model.Profile.dateColumnType
|
||||||
|
import gitbucket.core.service.RepositoryService.RepositoryInfo
|
||||||
|
|
||||||
trait MilestonesService {
|
trait MilestonesService {
|
||||||
|
|
||||||
@@ -73,4 +75,17 @@ trait MilestonesService {
|
|||||||
.sortBy(t => (t.dueDate.asc, t.closedDate.desc, t.milestoneId.desc))
|
.sortBy(t => (t.dueDate.asc, t.closedDate.desc, t.milestoneId.desc))
|
||||||
.list
|
.list
|
||||||
|
|
||||||
|
def getApiMilestone(repository: RepositoryInfo, milestoneId: Int)(implicit s: Session): Option[ApiMilestone] = {
|
||||||
|
getMilestonesWithIssueCount(repository.owner, repository.name)
|
||||||
|
.find(p => p._1.milestoneId == milestoneId)
|
||||||
|
.map(
|
||||||
|
milestoneWithIssue =>
|
||||||
|
ApiMilestone(
|
||||||
|
repository.repository,
|
||||||
|
milestoneWithIssue._1,
|
||||||
|
milestoneWithIssue._2,
|
||||||
|
milestoneWithIssue._3
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package gitbucket.core.service
|
|||||||
import gitbucket.core.model.Priority
|
import gitbucket.core.model.Priority
|
||||||
import gitbucket.core.model.Profile._
|
import gitbucket.core.model.Profile._
|
||||||
import gitbucket.core.model.Profile.profile.blockingApi._
|
import gitbucket.core.model.Profile.profile.blockingApi._
|
||||||
import gitbucket.core.util.StringUtil
|
|
||||||
|
|
||||||
trait PrioritiesService {
|
trait PrioritiesService {
|
||||||
|
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ object ProtectedBranchService {
|
|||||||
pusher: String,
|
pusher: String,
|
||||||
mergePullRequest: Boolean
|
mergePullRequest: Boolean
|
||||||
)(implicit session: Session): Option[String] = {
|
)(implicit session: Session): Option[String] = {
|
||||||
if (mergePullRequest == true) {
|
if (mergePullRequest) {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
checkBranchProtection(owner, repository, receivePack, command, pusher)
|
checkBranchProtection(owner, repository, receivePack, command, pusher)
|
||||||
@@ -153,9 +153,9 @@ object ProtectedBranchService {
|
|||||||
Some("Cannot force-push to a protected branch")
|
Some("Cannot force-push to a protected branch")
|
||||||
case ReceiveCommand.Type.UPDATE | ReceiveCommand.Type.UPDATE_NONFASTFORWARD if needStatusCheck(pusher) =>
|
case ReceiveCommand.Type.UPDATE | ReceiveCommand.Type.UPDATE_NONFASTFORWARD if needStatusCheck(pusher) =>
|
||||||
unSuccessedContexts(command.getNewId.name) match {
|
unSuccessedContexts(command.getNewId.name) match {
|
||||||
case s if s.size == 1 => Some(s"""Required status check "${s.toSeq(0)}" is expected""")
|
case s if s.sizeIs == 1 => Some(s"""Required status check "${s.toSeq(0)}" is expected""")
|
||||||
case s if s.size >= 1 => Some(s"${s.size} of ${contexts.size} required status checks are expected")
|
case s if s.sizeIs >= 1 => Some(s"${s.size} of ${contexts.size} required status checks are expected")
|
||||||
case _ => None
|
case _ => None
|
||||||
}
|
}
|
||||||
case ReceiveCommand.Type.DELETE =>
|
case ReceiveCommand.Type.DELETE =>
|
||||||
Some("Cannot delete a protected branch")
|
Some("Cannot delete a protected branch")
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
package gitbucket.core.service
|
package gitbucket.core.service
|
||||||
|
|
||||||
|
import com.github.difflib.DiffUtils
|
||||||
|
import com.github.difflib.patch.DeltaType
|
||||||
import gitbucket.core.model.{CommitComments => _, Session => _, _}
|
import gitbucket.core.model.{CommitComments => _, Session => _, _}
|
||||||
import gitbucket.core.model.Profile._
|
import gitbucket.core.model.Profile._
|
||||||
import gitbucket.core.model.Profile.profile.blockingApi._
|
import gitbucket.core.model.Profile.profile.blockingApi._
|
||||||
import difflib.{Delta, DiffUtils}
|
|
||||||
import gitbucket.core.service.RepositoryService.RepositoryInfo
|
import gitbucket.core.service.RepositoryService.RepositoryInfo
|
||||||
import gitbucket.core.api.JsonFormat
|
import gitbucket.core.api.JsonFormat
|
||||||
import gitbucket.core.controller.Context
|
import gitbucket.core.controller.Context
|
||||||
@@ -243,7 +244,7 @@ trait PullRequestService {
|
|||||||
owner: String,
|
owner: String,
|
||||||
repository: String,
|
repository: String,
|
||||||
branch: String,
|
branch: String,
|
||||||
loginAccount: Account,
|
pusherAccount: Account,
|
||||||
action: String,
|
action: String,
|
||||||
settings: SystemSettings
|
settings: SystemSettings
|
||||||
)(
|
)(
|
||||||
@@ -295,7 +296,7 @@ trait PullRequestService {
|
|||||||
action,
|
action,
|
||||||
getRepository(owner, repository).get,
|
getRepository(owner, repository).get,
|
||||||
pullreq.requestBranch,
|
pullreq.requestBranch,
|
||||||
loginAccount,
|
pusherAccount,
|
||||||
settings
|
settings
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -441,16 +442,17 @@ trait PullRequestService {
|
|||||||
case Left(oldLine) => updateCommitCommentPosition(commentId, newCommitId, Some(oldLine), None)
|
case Left(oldLine) => updateCommitCommentPosition(commentId, newCommitId, Some(oldLine), None)
|
||||||
case Right(newLine) =>
|
case Right(newLine) =>
|
||||||
var counter = newLine
|
var counter = newLine
|
||||||
patch.getDeltas.asScala.filter(_.getOriginal.getPosition < newLine).foreach { delta =>
|
patch.getDeltas.asScala.filter(_.getSource.getPosition < newLine).foreach { delta =>
|
||||||
delta.getType match {
|
delta.getType match {
|
||||||
case Delta.TYPE.CHANGE =>
|
case DeltaType.CHANGE =>
|
||||||
if (delta.getOriginal.getPosition <= newLine - 1 && newLine <= delta.getOriginal.getPosition + delta.getRevised.getLines.size) {
|
if (delta.getSource.getPosition <= newLine - 1 && newLine <= delta.getSource.getPosition + delta.getTarget.getLines.size) {
|
||||||
counter = -1
|
counter = -1
|
||||||
} else {
|
} else {
|
||||||
counter = counter + (delta.getRevised.getLines.size - delta.getOriginal.getLines.size)
|
counter = counter + (delta.getTarget.getLines.size - delta.getSource.getLines.size)
|
||||||
}
|
}
|
||||||
case Delta.TYPE.INSERT => counter = counter + delta.getRevised.getLines.size
|
case DeltaType.INSERT => counter = counter + delta.getTarget.getLines.size
|
||||||
case Delta.TYPE.DELETE => counter = counter - delta.getOriginal.getLines.size
|
case DeltaType.DELETE => counter = counter - delta.getSource.getLines.size
|
||||||
|
case DeltaType.EQUAL => // Do nothing
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (counter >= 0) {
|
if (counter >= 0) {
|
||||||
@@ -507,10 +509,11 @@ trait PullRequestService {
|
|||||||
def getPullRequestComments(userName: String, repositoryName: String, issueId: Int, commits: Seq[CommitInfo])(
|
def getPullRequestComments(userName: String, repositoryName: String, issueId: Int, commits: Seq[CommitInfo])(
|
||||||
implicit s: Session
|
implicit s: Session
|
||||||
): Seq[Comment] = {
|
): Seq[Comment] = {
|
||||||
(commits
|
(commits.flatMap(commit => getCommitComments(userName, repositoryName, commit.id, true)) ++ getComments(
|
||||||
.map(commit => getCommitComments(userName, repositoryName, commit.id, true))
|
userName,
|
||||||
.flatten ++ getComments(userName, repositoryName, issueId))
|
repositoryName,
|
||||||
.groupBy {
|
issueId
|
||||||
|
)).groupBy {
|
||||||
case x: IssueComment => (Some(x.commentId), None, None, None)
|
case x: IssueComment => (Some(x.commentId), None, None, None)
|
||||||
case x: CommitComment if x.fileName.isEmpty => (Some(x.commentId), None, None, None)
|
case x: CommitComment if x.fileName.isEmpty => (Some(x.commentId), None, None, None)
|
||||||
case x: CommitComment => (None, x.fileName, x.originalOldLine, x.originalNewLine)
|
case x: CommitComment => (None, x.fileName, x.originalOldLine, x.originalNewLine)
|
||||||
@@ -576,7 +579,7 @@ trait PullRequestService {
|
|||||||
case (oldGit, newGit) =>
|
case (oldGit, newGit) =>
|
||||||
if (originRepository.branchList.contains(originId)) {
|
if (originRepository.branchList.contains(originId)) {
|
||||||
val forkedId2 =
|
val forkedId2 =
|
||||||
forkedRepository.tags.collectFirst { case x if x.name == forkedId => x.id }.getOrElse(forkedId)
|
forkedRepository.tags.collectFirst { case x if x.name == forkedId => x.commitId }.getOrElse(forkedId)
|
||||||
|
|
||||||
val originId2 = JGitUtil.getForkedCommitId(
|
val originId2 = JGitUtil.getForkedCommitId(
|
||||||
oldGit,
|
oldGit,
|
||||||
@@ -593,9 +596,9 @@ trait PullRequestService {
|
|||||||
|
|
||||||
} else {
|
} else {
|
||||||
val originId2 =
|
val originId2 =
|
||||||
originRepository.tags.collectFirst { case x if x.name == originId => x.id }.getOrElse(originId)
|
originRepository.tags.collectFirst { case x if x.name == originId => x.commitId }.getOrElse(originId)
|
||||||
val forkedId2 =
|
val forkedId2 =
|
||||||
forkedRepository.tags.collectFirst { case x if x.name == forkedId => x.id }.getOrElse(forkedId)
|
forkedRepository.tags.collectFirst { case x if x.name == forkedId => x.commitId }.getOrElse(forkedId)
|
||||||
|
|
||||||
(Option(oldGit.getRepository.resolve(originId2)), Option(newGit.getRepository.resolve(forkedId2)))
|
(Option(oldGit.getRepository.resolve(originId2)), Option(newGit.getRepository.resolve(forkedId2)))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,23 +23,29 @@ trait RepositoryCommitFileService {
|
|||||||
with PullRequestService
|
with PullRequestService
|
||||||
with WebHookPullRequestService
|
with WebHookPullRequestService
|
||||||
with RepositoryService =>
|
with RepositoryService =>
|
||||||
import RepositoryCommitFileService._
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create multiple files by callback function.
|
||||||
|
* Returns commitId.
|
||||||
|
*/
|
||||||
def commitFiles(
|
def commitFiles(
|
||||||
repository: RepositoryService.RepositoryInfo,
|
repository: RepositoryService.RepositoryInfo,
|
||||||
files: Seq[CommitFile],
|
|
||||||
branch: String,
|
branch: String,
|
||||||
path: String,
|
|
||||||
message: String,
|
message: String,
|
||||||
loginAccount: Account,
|
loginAccount: Account,
|
||||||
settings: SystemSettings
|
settings: SystemSettings
|
||||||
)(
|
)(
|
||||||
f: (Git, ObjectId, DirCacheBuilder, ObjectInserter) => Unit
|
f: (Git, ObjectId, DirCacheBuilder, ObjectInserter) => Unit
|
||||||
)(implicit s: Session, c: JsonFormat.Context) = {
|
)(implicit s: Session, c: JsonFormat.Context): ObjectId = {
|
||||||
// prepend path to the filename
|
_createFiles(repository, branch, message, loginAccount, loginAccount.fullName, loginAccount.mailAddress, settings)(
|
||||||
_commitFile(repository, branch, message, loginAccount, loginAccount.fullName, loginAccount.mailAddress, settings)(f)
|
f
|
||||||
|
)._1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a file from string content.
|
||||||
|
* Returns commitId + blobId.
|
||||||
|
*/
|
||||||
def commitFile(
|
def commitFile(
|
||||||
repository: RepositoryService.RepositoryInfo,
|
repository: RepositoryService.RepositoryInfo,
|
||||||
branch: String,
|
branch: String,
|
||||||
@@ -52,7 +58,7 @@ trait RepositoryCommitFileService {
|
|||||||
commit: String,
|
commit: String,
|
||||||
loginAccount: Account,
|
loginAccount: Account,
|
||||||
settings: SystemSettings
|
settings: SystemSettings
|
||||||
)(implicit s: Session, c: JsonFormat.Context): ObjectId = {
|
)(implicit s: Session, c: JsonFormat.Context): (ObjectId, Option[ObjectId]) = {
|
||||||
commitFile(
|
commitFile(
|
||||||
repository,
|
repository,
|
||||||
branch,
|
branch,
|
||||||
@@ -69,6 +75,10 @@ trait RepositoryCommitFileService {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a file from byte array content.
|
||||||
|
* Returns commitId + blobId.
|
||||||
|
*/
|
||||||
def commitFile(
|
def commitFile(
|
||||||
repository: RepositoryService.RepositoryInfo,
|
repository: RepositoryService.RepositoryInfo,
|
||||||
branch: String,
|
branch: String,
|
||||||
@@ -78,11 +88,11 @@ trait RepositoryCommitFileService {
|
|||||||
content: Array[Byte],
|
content: Array[Byte],
|
||||||
message: String,
|
message: String,
|
||||||
commit: String,
|
commit: String,
|
||||||
loginAccount: Account,
|
pusherAccount: Account,
|
||||||
fullName: String,
|
committerName: String,
|
||||||
mailAddress: String,
|
committerMailAddress: String,
|
||||||
settings: SystemSettings
|
settings: SystemSettings
|
||||||
)(implicit s: Session, c: JsonFormat.Context): ObjectId = {
|
)(implicit s: Session, c: JsonFormat.Context): (ObjectId, Option[ObjectId]) = {
|
||||||
|
|
||||||
val newPath = newFileName.map { newFileName =>
|
val newPath = newFileName.map { newFileName =>
|
||||||
if (path.length == 0) newFileName else s"${path}/${newFileName}"
|
if (path.length == 0) newFileName else s"${path}/${newFileName}"
|
||||||
@@ -91,7 +101,7 @@ trait RepositoryCommitFileService {
|
|||||||
if (path.length == 0) oldFileName else s"${path}/${oldFileName}"
|
if (path.length == 0) oldFileName else s"${path}/${oldFileName}"
|
||||||
}
|
}
|
||||||
|
|
||||||
_commitFile(repository, branch, message, loginAccount, fullName, mailAddress, settings) {
|
_createFiles(repository, branch, message, pusherAccount, committerName, committerMailAddress, settings) {
|
||||||
case (git, headTip, builder, inserter) =>
|
case (git, headTip, builder, inserter) =>
|
||||||
if (headTip.getName == commit) {
|
if (headTip.getName == commit) {
|
||||||
val permission = JGitUtil
|
val permission = JGitUtil
|
||||||
@@ -105,28 +115,33 @@ trait RepositoryCommitFileService {
|
|||||||
}
|
}
|
||||||
.flatten
|
.flatten
|
||||||
.headOption
|
.headOption
|
||||||
|
.map { bits =>
|
||||||
newPath.foreach { newPath =>
|
|
||||||
builder.add(JGitUtil.createDirCacheEntry(newPath, permission.map { bits =>
|
|
||||||
FileMode.fromBits(bits)
|
FileMode.fromBits(bits)
|
||||||
} getOrElse FileMode.REGULAR_FILE, inserter.insert(Constants.OBJ_BLOB, content)))
|
}
|
||||||
|
.getOrElse(FileMode.REGULAR_FILE)
|
||||||
|
|
||||||
|
val objectId = newPath.map { newPath =>
|
||||||
|
val objectId = inserter.insert(Constants.OBJ_BLOB, content)
|
||||||
|
builder.add(JGitUtil.createDirCacheEntry(newPath, permission, objectId))
|
||||||
|
objectId
|
||||||
}
|
}
|
||||||
builder.finish()
|
builder.finish()
|
||||||
}
|
objectId
|
||||||
|
} else None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private def _commitFile(
|
private def _createFiles[R](
|
||||||
repository: RepositoryService.RepositoryInfo,
|
repository: RepositoryService.RepositoryInfo,
|
||||||
branch: String,
|
branch: String,
|
||||||
message: String,
|
message: String,
|
||||||
loginAccount: Account,
|
pusherAccount: Account,
|
||||||
committerName: String,
|
committerName: String,
|
||||||
committerMailAddress: String,
|
committerMailAddress: String,
|
||||||
settings: SystemSettings
|
settings: SystemSettings
|
||||||
)(
|
)(
|
||||||
f: (Git, ObjectId, DirCacheBuilder, ObjectInserter) => Unit
|
f: (Git, ObjectId, DirCacheBuilder, ObjectInserter) => R
|
||||||
)(implicit s: Session, c: JsonFormat.Context): ObjectId = {
|
)(implicit s: Session, c: JsonFormat.Context): (ObjectId, R) = {
|
||||||
|
|
||||||
LockUtil.lock(s"${repository.owner}/${repository.name}") {
|
LockUtil.lock(s"${repository.owner}/${repository.name}") {
|
||||||
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
|
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
|
||||||
@@ -135,7 +150,7 @@ trait RepositoryCommitFileService {
|
|||||||
val headName = s"refs/heads/${branch}"
|
val headName = s"refs/heads/${branch}"
|
||||||
val headTip = git.getRepository.resolve(headName)
|
val headTip = git.getRepository.resolve(headName)
|
||||||
|
|
||||||
f(git, headTip, builder, inserter)
|
val result = f(git, headTip, builder, inserter)
|
||||||
|
|
||||||
val commitId = JGitUtil.createNewCommit(
|
val commitId = JGitUtil.createNewCommit(
|
||||||
git,
|
git,
|
||||||
@@ -156,7 +171,7 @@ trait RepositoryCommitFileService {
|
|||||||
|
|
||||||
// call pre-commit hook
|
// call pre-commit hook
|
||||||
val error = PluginRegistry().getReceiveHooks.flatMap { hook =>
|
val error = PluginRegistry().getReceiveHooks.flatMap { hook =>
|
||||||
hook.preReceive(repository.owner, repository.name, receivePack, receiveCommand, committerName, false)
|
hook.preReceive(repository.owner, repository.name, receivePack, receiveCommand, pusherAccount.userName, false)
|
||||||
}.headOption
|
}.headOption
|
||||||
|
|
||||||
error match {
|
error match {
|
||||||
@@ -177,12 +192,12 @@ trait RepositoryCommitFileService {
|
|||||||
refUpdate.update()
|
refUpdate.update()
|
||||||
|
|
||||||
// update pull request
|
// update pull request
|
||||||
updatePullRequests(repository.owner, repository.name, branch, loginAccount, "synchronize", settings)
|
updatePullRequests(repository.owner, repository.name, branch, pusherAccount, "synchronize", settings)
|
||||||
|
|
||||||
// record activity
|
// record activity
|
||||||
updateLastActivityDate(repository.owner, repository.name)
|
updateLastActivityDate(repository.owner, repository.name)
|
||||||
val commitInfo = new CommitInfo(JGitUtil.getRevCommitFromId(git, commitId))
|
val commitInfo = new CommitInfo(JGitUtil.getRevCommitFromId(git, commitId))
|
||||||
val pushInfo = PushInfo(repository.owner, repository.name, loginAccount.userName, branch, List(commitInfo))
|
val pushInfo = PushInfo(repository.owner, repository.name, pusherAccount.userName, branch, List(commitInfo))
|
||||||
recordActivity(pushInfo)
|
recordActivity(pushInfo)
|
||||||
|
|
||||||
// create issue comment by commit message
|
// create issue comment by commit message
|
||||||
@@ -192,17 +207,17 @@ trait RepositoryCommitFileService {
|
|||||||
if (branch == repository.repository.defaultBranch) {
|
if (branch == repository.repository.defaultBranch) {
|
||||||
closeIssuesFromMessage(message, committerName, repository.owner, repository.name).foreach { issueId =>
|
closeIssuesFromMessage(message, committerName, repository.owner, repository.name).foreach { issueId =>
|
||||||
getIssue(repository.owner, repository.name, issueId.toString).foreach { issue =>
|
getIssue(repository.owner, repository.name, issueId.toString).foreach { issue =>
|
||||||
callIssuesWebHook("closed", repository, issue, loginAccount, settings)
|
callIssuesWebHook("closed", repository, issue, pusherAccount, settings)
|
||||||
val closeIssueInfo = CloseIssueInfo(
|
val closeIssueInfo = CloseIssueInfo(
|
||||||
repository.owner,
|
repository.owner,
|
||||||
repository.name,
|
repository.name,
|
||||||
loginAccount.userName,
|
pusherAccount.userName,
|
||||||
issue.issueId,
|
issue.issueId,
|
||||||
issue.title
|
issue.title
|
||||||
)
|
)
|
||||||
recordActivity(closeIssueInfo)
|
recordActivity(closeIssueInfo)
|
||||||
PluginRegistry().getIssueHooks
|
PluginRegistry().getIssueHooks
|
||||||
.foreach(_.closedByCommitComment(issue, repository, message, loginAccount))
|
.foreach(_.closedByCommitComment(issue, repository, message, pusherAccount))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -217,7 +232,7 @@ trait RepositoryCommitFileService {
|
|||||||
getAccountByUserName(repository.owner).map { ownerAccount =>
|
getAccountByUserName(repository.owner).map { ownerAccount =>
|
||||||
WebHookPushPayload(
|
WebHookPushPayload(
|
||||||
git,
|
git,
|
||||||
loginAccount,
|
pusherAccount,
|
||||||
headName,
|
headName,
|
||||||
repository,
|
repository,
|
||||||
List(commit),
|
List(commit),
|
||||||
@@ -228,7 +243,7 @@ trait RepositoryCommitFileService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
commitId
|
(commitId, result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ object RepositoryCreationService {
|
|||||||
private val Creating = new ConcurrentHashMap[String, Option[String]]()
|
private val Creating = new ConcurrentHashMap[String, Option[String]]()
|
||||||
|
|
||||||
def isCreating(owner: String, repository: String): Boolean = {
|
def isCreating(owner: String, repository: String): Boolean = {
|
||||||
Option(Creating.get(s"${owner}/${repository}")).map(_.isEmpty).getOrElse(false)
|
Option(Creating.get(s"${owner}/${repository}")).exists(_.isEmpty)
|
||||||
}
|
}
|
||||||
|
|
||||||
def startCreation(owner: String, repository: String): Unit = {
|
def startCreation(owner: String, repository: String): Unit = {
|
||||||
@@ -40,7 +40,7 @@ object RepositoryCreationService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
def getCreationError(owner: String, repository: String): Option[String] = {
|
def getCreationError(owner: String, repository: String): Option[String] = {
|
||||||
Option(Creating.remove(s"${owner}/${repository}")).getOrElse(None)
|
Option(Creating.remove(s"${owner}/${repository}")).flatten
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -53,6 +53,11 @@ trait RepositoryCreationService {
|
|||||||
with ActivityService
|
with ActivityService
|
||||||
with PrioritiesService =>
|
with PrioritiesService =>
|
||||||
|
|
||||||
|
def canCreateRepository(repositoryOwner: String, loginAccount: Account)(implicit session: Session): Boolean = {
|
||||||
|
repositoryOwner == loginAccount.userName || getGroupsByUserName(loginAccount.userName)
|
||||||
|
.contains(repositoryOwner) || loginAccount.isAdmin
|
||||||
|
}
|
||||||
|
|
||||||
def createRepository(
|
def createRepository(
|
||||||
loginAccount: Account,
|
loginAccount: Account,
|
||||||
owner: String,
|
owner: String,
|
||||||
@@ -76,7 +81,7 @@ trait RepositoryCreationService {
|
|||||||
RepositoryCreationService.startCreation(owner, name)
|
RepositoryCreationService.startCreation(owner, name)
|
||||||
try {
|
try {
|
||||||
Database() withTransaction { implicit session =>
|
Database() withTransaction { implicit session =>
|
||||||
val ownerAccount = getAccountByUserName(owner).get
|
//val ownerAccount = getAccountByUserName(owner).get
|
||||||
val loginUserName = loginAccount.userName
|
val loginUserName = loginAccount.userName
|
||||||
|
|
||||||
val copyRepositoryDir = if (initOption == "COPY") {
|
val copyRepositoryDir = if (initOption == "COPY") {
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package gitbucket.core.service
|
|||||||
|
|
||||||
import gitbucket.core.controller.Context
|
import gitbucket.core.controller.Context
|
||||||
import gitbucket.core.util._
|
import gitbucket.core.util._
|
||||||
import gitbucket.core.util.SyntaxSugars._
|
|
||||||
import gitbucket.core.model.{CommitComments => _, Session => _, _}
|
import gitbucket.core.model.{CommitComments => _, Session => _, _}
|
||||||
import gitbucket.core.model.Profile._
|
import gitbucket.core.model.Profile._
|
||||||
import gitbucket.core.model.Profile.profile.blockingApi._
|
import gitbucket.core.model.Profile.profile.blockingApi._
|
||||||
@@ -12,7 +11,8 @@ import gitbucket.core.util.Directory.{getRepositoryDir, getRepositoryFilesDir, g
|
|||||||
import gitbucket.core.util.JGitUtil.FileInfo
|
import gitbucket.core.util.JGitUtil.FileInfo
|
||||||
import org.apache.commons.io.FileUtils
|
import org.apache.commons.io.FileUtils
|
||||||
import org.eclipse.jgit.api.Git
|
import org.eclipse.jgit.api.Git
|
||||||
import org.eclipse.jgit.lib.{Repository => _, _}
|
import org.eclipse.jgit.lib.{Repository => _}
|
||||||
|
|
||||||
import scala.util.Using
|
import scala.util.Using
|
||||||
|
|
||||||
trait RepositoryService {
|
trait RepositoryService {
|
||||||
@@ -61,7 +61,8 @@ trait RepositoryService {
|
|||||||
externalWikiUrl = None,
|
externalWikiUrl = None,
|
||||||
allowFork = true,
|
allowFork = true,
|
||||||
mergeOptions = "merge-commit,squash,rebase",
|
mergeOptions = "merge-commit,squash,rebase",
|
||||||
defaultMergeOption = "merge-commit"
|
defaultMergeOption = "merge-commit",
|
||||||
|
safeMode = true
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -202,22 +203,19 @@ trait RepositoryService {
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Move git repository
|
// Move git repository
|
||||||
defining(getRepositoryDir(oldUserName, oldRepositoryName)) { dir =>
|
val repoDir = getRepositoryDir(oldUserName, oldRepositoryName)
|
||||||
if (dir.isDirectory) {
|
if (repoDir.isDirectory) {
|
||||||
FileUtils.moveDirectory(dir, getRepositoryDir(newUserName, newRepositoryName))
|
FileUtils.moveDirectory(repoDir, getRepositoryDir(newUserName, newRepositoryName))
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// Move wiki repository
|
// Move wiki repository
|
||||||
defining(getWikiRepositoryDir(oldUserName, oldRepositoryName)) { dir =>
|
val wikiDir = getWikiRepositoryDir(oldUserName, oldRepositoryName)
|
||||||
if (dir.isDirectory) {
|
if (wikiDir.isDirectory) {
|
||||||
FileUtils.moveDirectory(dir, getWikiRepositoryDir(newUserName, newRepositoryName))
|
FileUtils.moveDirectory(wikiDir, getWikiRepositoryDir(newUserName, newRepositoryName))
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// Move files directory
|
// Move files directory
|
||||||
defining(getRepositoryFilesDir(oldUserName, oldRepositoryName)) { dir =>
|
val filesDir = getRepositoryFilesDir(oldUserName, oldRepositoryName)
|
||||||
if (dir.isDirectory) {
|
if (filesDir.isDirectory) {
|
||||||
FileUtils.moveDirectory(dir, getRepositoryFilesDir(newUserName, newRepositoryName))
|
FileUtils.moveDirectory(filesDir, getRepositoryFilesDir(newUserName, newRepositoryName))
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// Delete parent directory
|
// Delete parent directory
|
||||||
FileUtil.deleteDirectoryIfEmpty(getRepositoryFilesDir(oldUserName, oldRepositoryName))
|
FileUtil.deleteDirectoryIfEmpty(getRepositoryFilesDir(oldUserName, oldRepositoryName))
|
||||||
@@ -237,10 +235,11 @@ trait RepositoryService {
|
|||||||
LockUtil.lock(s"${repository.userName}/${repository.repositoryName}") {
|
LockUtil.lock(s"${repository.userName}/${repository.repositoryName}") {
|
||||||
deleteRepositoryOnModel(repository.userName, repository.repositoryName)
|
deleteRepositoryOnModel(repository.userName, repository.repositoryName)
|
||||||
|
|
||||||
FileUtils.deleteDirectory(getRepositoryDir(repository.userName, repository.repositoryName))
|
FileUtil.deleteRecursively(getRepositoryDir(repository.userName, repository.repositoryName))
|
||||||
FileUtils.deleteDirectory(getWikiRepositoryDir(repository.userName, repository.repositoryName))
|
|
||||||
FileUtils.deleteDirectory(getTemporaryDir(repository.userName, repository.repositoryName))
|
FileUtil.deleteRecursively(getWikiRepositoryDir(repository.userName, repository.repositoryName))
|
||||||
FileUtils.deleteDirectory(getRepositoryFilesDir(repository.userName, repository.repositoryName))
|
FileUtil.deleteRecursively(getTemporaryDir(repository.userName, repository.repositoryName))
|
||||||
|
FileUtil.deleteRecursively(getRepositoryFilesDir(repository.userName, repository.repositoryName))
|
||||||
|
|
||||||
// Call hooks
|
// Call hooks
|
||||||
PluginRegistry().getRepositoryHooks.foreach(_.deleted(repository.userName, repository.repositoryName))
|
PluginRegistry().getRepositoryHooks.foreach(_.deleted(repository.userName, repository.repositoryName))
|
||||||
@@ -463,7 +462,7 @@ trait RepositoryService {
|
|||||||
.filter { case (t1, t2) => t2.removed === false.bind }
|
.filter { case (t1, t2) => t2.removed === false.bind }
|
||||||
.map { case (t1, t2) => t1 }
|
.map { case (t1, t2) => t1 }
|
||||||
// for Normal Users
|
// for Normal Users
|
||||||
case Some(x) if (!x.isAdmin || limit) =>
|
case Some(x) =>
|
||||||
Repositories
|
Repositories
|
||||||
.join(Accounts)
|
.join(Accounts)
|
||||||
.on(_.userName === _.userName)
|
.on(_.userName === _.userName)
|
||||||
@@ -553,7 +552,8 @@ trait RepositoryService {
|
|||||||
externalWikiUrl: Option[String],
|
externalWikiUrl: Option[String],
|
||||||
allowFork: Boolean,
|
allowFork: Boolean,
|
||||||
mergeOptions: Seq[String],
|
mergeOptions: Seq[String],
|
||||||
defaultMergeOption: String
|
defaultMergeOption: String,
|
||||||
|
safeMode: Boolean
|
||||||
)(implicit s: Session): Unit = {
|
)(implicit s: Session): Unit = {
|
||||||
|
|
||||||
Repositories
|
Repositories
|
||||||
@@ -569,6 +569,7 @@ trait RepositoryService {
|
|||||||
r.allowFork,
|
r.allowFork,
|
||||||
r.mergeOptions,
|
r.mergeOptions,
|
||||||
r.defaultMergeOption,
|
r.defaultMergeOption,
|
||||||
|
r.safeMode,
|
||||||
r.updatedDate
|
r.updatedDate
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -582,6 +583,7 @@ trait RepositoryService {
|
|||||||
allowFork,
|
allowFork,
|
||||||
mergeOptions.mkString(","),
|
mergeOptions.mkString(","),
|
||||||
defaultMergeOption,
|
defaultMergeOption,
|
||||||
|
safeMode,
|
||||||
currentDate
|
currentDate
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -765,7 +767,8 @@ trait RepositoryService {
|
|||||||
JGitUtil.getContentFromId(git, file.id, true).collect {
|
JGitUtil.getContentFromId(git, file.id, true).collect {
|
||||||
case bytes if FileUtil.isText(bytes) => StringUtil.convertFromByteArray(bytes)
|
case bytes if FileUtil.isText(bytes) => StringUtil.convertFromByteArray(bytes)
|
||||||
}
|
}
|
||||||
} getOrElse None
|
}
|
||||||
|
.flatten
|
||||||
} getOrElse ""
|
} getOrElse ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -819,11 +822,13 @@ object RepositoryService {
|
|||||||
def sshUrl(implicit context: Context): Option[String] = RepositoryService.sshUrl(owner, name)
|
def sshUrl(implicit context: Context): Option[String] = RepositoryService.sshUrl(owner, name)
|
||||||
|
|
||||||
def splitPath(path: String): (String, String) = {
|
def splitPath(path: String): (String, String) = {
|
||||||
val id = branchList.collectFirst {
|
val names = (branchList ++ tags.map(_.name)).sortBy(_.length).reverse
|
||||||
case branch if (path == branch || path.startsWith(branch + "/")) => branch
|
|
||||||
} orElse tags.collectFirst {
|
val id = names.collectFirst {
|
||||||
case tag if (path == tag.name || path.startsWith(tag.name + "/")) => tag.name
|
case name if (path == name || path.startsWith(name + "/")) => name
|
||||||
} getOrElse path.split("/")(0)
|
} getOrElse {
|
||||||
|
path.split("/")(0)
|
||||||
|
}
|
||||||
|
|
||||||
(id, path.substring(id.length).stripPrefix("/"))
|
(id, path.substring(id.length).stripPrefix("/"))
|
||||||
}
|
}
|
||||||
@@ -831,12 +836,10 @@ object RepositoryService {
|
|||||||
|
|
||||||
def httpUrl(owner: String, name: String)(implicit context: Context): String =
|
def httpUrl(owner: String, name: String)(implicit context: Context): String =
|
||||||
s"${context.baseUrl}/git/${owner}/${name}.git"
|
s"${context.baseUrl}/git/${owner}/${name}.git"
|
||||||
|
|
||||||
def sshUrl(owner: String, name: String)(implicit context: Context): Option[String] =
|
def sshUrl(owner: String, name: String)(implicit context: Context): Option[String] =
|
||||||
if (context.settings.ssh.enabled) {
|
context.settings.sshUrl(owner, name)
|
||||||
context.settings.sshAddress.map { x =>
|
|
||||||
s"ssh://${x.genericUser}@${x.host}:${x.port}/${owner}/${name}.git"
|
|
||||||
}
|
|
||||||
} else None
|
|
||||||
def openRepoUrl(openUrl: String)(implicit context: Context): String =
|
def openRepoUrl(openUrl: String)(implicit context: Context): String =
|
||||||
s"github-${context.platform}://openRepo/${openUrl}"
|
s"github-${context.platform}://openRepo/${openUrl}"
|
||||||
|
|
||||||
|
|||||||
@@ -4,10 +4,10 @@ import javax.servlet.http.HttpServletRequest
|
|||||||
import com.nimbusds.jose.JWSAlgorithm
|
import com.nimbusds.jose.JWSAlgorithm
|
||||||
import com.nimbusds.oauth2.sdk.auth.Secret
|
import com.nimbusds.oauth2.sdk.auth.Secret
|
||||||
import com.nimbusds.oauth2.sdk.id.{ClientID, Issuer}
|
import com.nimbusds.oauth2.sdk.id.{ClientID, Issuer}
|
||||||
import gitbucket.core.service.SystemSettingsService._
|
import gitbucket.core.service.SystemSettingsService.{getOptionValue, _}
|
||||||
import gitbucket.core.util.ConfigUtil._
|
import gitbucket.core.util.ConfigUtil._
|
||||||
import gitbucket.core.util.Directory._
|
import gitbucket.core.util.Directory._
|
||||||
import gitbucket.core.util.SyntaxSugars._
|
|
||||||
import scala.util.Using
|
import scala.util.Using
|
||||||
|
|
||||||
trait SystemSettingsService {
|
trait SystemSettingsService {
|
||||||
@@ -15,177 +15,195 @@ trait SystemSettingsService {
|
|||||||
def baseUrl(implicit request: HttpServletRequest): String = loadSystemSettings().baseUrl(request)
|
def baseUrl(implicit request: HttpServletRequest): String = loadSystemSettings().baseUrl(request)
|
||||||
|
|
||||||
def saveSystemSettings(settings: SystemSettings): Unit = {
|
def saveSystemSettings(settings: SystemSettings): Unit = {
|
||||||
defining(new java.util.Properties()) { props =>
|
val props = new java.util.Properties()
|
||||||
settings.baseUrl.foreach(x => props.setProperty(BaseURL, x.replaceFirst("/\\Z", "")))
|
settings.baseUrl.foreach(x => props.setProperty(BaseURL, x.replaceFirst("/\\Z", "")))
|
||||||
settings.information.foreach(x => props.setProperty(Information, x))
|
settings.information.foreach(x => props.setProperty(Information, x))
|
||||||
props.setProperty(AllowAccountRegistration, settings.allowAccountRegistration.toString)
|
props.setProperty(AllowAccountRegistration, settings.allowAccountRegistration.toString)
|
||||||
props.setProperty(AllowAnonymousAccess, settings.allowAnonymousAccess.toString)
|
props.setProperty(AllowAnonymousAccess, settings.allowAnonymousAccess.toString)
|
||||||
props.setProperty(IsCreateRepoOptionPublic, settings.isCreateRepoOptionPublic.toString)
|
props.setProperty(IsCreateRepoOptionPublic, settings.isCreateRepoOptionPublic.toString)
|
||||||
props.setProperty(RepositoryOperationCreate, settings.repositoryOperation.create.toString)
|
props.setProperty(RepositoryOperationCreate, settings.repositoryOperation.create.toString)
|
||||||
props.setProperty(RepositoryOperationDelete, settings.repositoryOperation.delete.toString)
|
props.setProperty(RepositoryOperationDelete, settings.repositoryOperation.delete.toString)
|
||||||
props.setProperty(RepositoryOperationRename, settings.repositoryOperation.rename.toString)
|
props.setProperty(RepositoryOperationRename, settings.repositoryOperation.rename.toString)
|
||||||
props.setProperty(RepositoryOperationTransfer, settings.repositoryOperation.transfer.toString)
|
props.setProperty(RepositoryOperationTransfer, settings.repositoryOperation.transfer.toString)
|
||||||
props.setProperty(RepositoryOperationFork, settings.repositoryOperation.fork.toString)
|
props.setProperty(RepositoryOperationFork, settings.repositoryOperation.fork.toString)
|
||||||
props.setProperty(Gravatar, settings.gravatar.toString)
|
props.setProperty(Gravatar, settings.gravatar.toString)
|
||||||
props.setProperty(Notification, settings.notification.toString)
|
props.setProperty(Notification, settings.notification.toString)
|
||||||
props.setProperty(LimitVisibleRepositories, settings.limitVisibleRepositories.toString)
|
props.setProperty(LimitVisibleRepositories, settings.limitVisibleRepositories.toString)
|
||||||
props.setProperty(SshEnabled, settings.ssh.enabled.toString)
|
props.setProperty(SshEnabled, settings.ssh.enabled.toString)
|
||||||
settings.ssh.sshHost.foreach(x => props.setProperty(SshHost, x.trim))
|
settings.ssh.bindAddress.foreach { bindAddress =>
|
||||||
settings.ssh.sshPort.foreach(x => props.setProperty(SshPort, x.toString))
|
props.setProperty(SshBindAddressHost, bindAddress.host.trim())
|
||||||
props.setProperty(UseSMTP, settings.useSMTP.toString)
|
props.setProperty(SshBindAddressPort, bindAddress.port.toString)
|
||||||
if (settings.useSMTP) {
|
}
|
||||||
settings.smtp.foreach { smtp =>
|
settings.ssh.publicAddress.foreach { publicAddress =>
|
||||||
props.setProperty(SmtpHost, smtp.host)
|
props.setProperty(SshPublicAddressHost, publicAddress.host.trim())
|
||||||
smtp.port.foreach(x => props.setProperty(SmtpPort, x.toString))
|
props.setProperty(SshPublicAddressPort, publicAddress.port.toString)
|
||||||
smtp.user.foreach(props.setProperty(SmtpUser, _))
|
}
|
||||||
smtp.password.foreach(props.setProperty(SmtpPassword, _))
|
props.setProperty(UseSMTP, settings.useSMTP.toString)
|
||||||
smtp.ssl.foreach(x => props.setProperty(SmtpSsl, x.toString))
|
if (settings.useSMTP) {
|
||||||
smtp.starttls.foreach(x => props.setProperty(SmtpStarttls, x.toString))
|
settings.smtp.foreach { smtp =>
|
||||||
smtp.fromAddress.foreach(props.setProperty(SmtpFromAddress, _))
|
props.setProperty(SmtpHost, smtp.host)
|
||||||
smtp.fromName.foreach(props.setProperty(SmtpFromName, _))
|
smtp.port.foreach(x => props.setProperty(SmtpPort, x.toString))
|
||||||
|
smtp.user.foreach(props.setProperty(SmtpUser, _))
|
||||||
|
smtp.password.foreach(props.setProperty(SmtpPassword, _))
|
||||||
|
smtp.ssl.foreach(x => props.setProperty(SmtpSsl, x.toString))
|
||||||
|
smtp.starttls.foreach(x => props.setProperty(SmtpStarttls, x.toString))
|
||||||
|
smtp.fromAddress.foreach(props.setProperty(SmtpFromAddress, _))
|
||||||
|
smtp.fromName.foreach(props.setProperty(SmtpFromName, _))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
props.setProperty(LdapAuthentication, settings.ldapAuthentication.toString)
|
||||||
|
if (settings.ldapAuthentication) {
|
||||||
|
settings.ldap.foreach { ldap =>
|
||||||
|
props.setProperty(LdapHost, ldap.host)
|
||||||
|
ldap.port.foreach(x => props.setProperty(LdapPort, x.toString))
|
||||||
|
ldap.bindDN.foreach(x => props.setProperty(LdapBindDN, x))
|
||||||
|
ldap.bindPassword.foreach(x => props.setProperty(LdapBindPassword, x))
|
||||||
|
props.setProperty(LdapBaseDN, ldap.baseDN)
|
||||||
|
props.setProperty(LdapUserNameAttribute, ldap.userNameAttribute)
|
||||||
|
ldap.additionalFilterCondition.foreach(x => props.setProperty(LdapAdditionalFilterCondition, x))
|
||||||
|
ldap.fullNameAttribute.foreach(x => props.setProperty(LdapFullNameAttribute, x))
|
||||||
|
ldap.mailAttribute.foreach(x => props.setProperty(LdapMailAddressAttribute, x))
|
||||||
|
ldap.tls.foreach(x => props.setProperty(LdapTls, x.toString))
|
||||||
|
ldap.ssl.foreach(x => props.setProperty(LdapSsl, x.toString))
|
||||||
|
ldap.keystore.foreach(x => props.setProperty(LdapKeystore, x))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
props.setProperty(OidcAuthentication, settings.oidcAuthentication.toString)
|
||||||
|
if (settings.oidcAuthentication) {
|
||||||
|
settings.oidc.foreach { oidc =>
|
||||||
|
props.setProperty(OidcIssuer, oidc.issuer.getValue)
|
||||||
|
props.setProperty(OidcClientId, oidc.clientID.getValue)
|
||||||
|
props.setProperty(OidcClientSecret, oidc.clientSecret.getValue)
|
||||||
|
oidc.jwsAlgorithm.foreach { x =>
|
||||||
|
props.setProperty(OidcJwsAlgorithm, x.getName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
props.setProperty(LdapAuthentication, settings.ldapAuthentication.toString)
|
}
|
||||||
if (settings.ldapAuthentication) {
|
props.setProperty(SkinName, settings.skinName)
|
||||||
settings.ldap.foreach { ldap =>
|
settings.userDefinedCss.foreach(x => props.setProperty(UserDefinedCss, x))
|
||||||
props.setProperty(LdapHost, ldap.host)
|
props.setProperty(ShowMailAddress, settings.showMailAddress.toString)
|
||||||
ldap.port.foreach(x => props.setProperty(LdapPort, x.toString))
|
props.setProperty(WebHookBlockPrivateAddress, settings.webHook.blockPrivateAddress.toString)
|
||||||
ldap.bindDN.foreach(x => props.setProperty(LdapBindDN, x))
|
props.setProperty(WebHookWhitelist, settings.webHook.whitelist.mkString("\n"))
|
||||||
ldap.bindPassword.foreach(x => props.setProperty(LdapBindPassword, x))
|
props.setProperty(UploadMaxFileSize, settings.upload.maxFileSize.toString)
|
||||||
props.setProperty(LdapBaseDN, ldap.baseDN)
|
props.setProperty(UploadTimeout, settings.upload.timeout.toString)
|
||||||
props.setProperty(LdapUserNameAttribute, ldap.userNameAttribute)
|
props.setProperty(UploadLargeMaxFileSize, settings.upload.largeMaxFileSize.toString)
|
||||||
ldap.additionalFilterCondition.foreach(x => props.setProperty(LdapAdditionalFilterCondition, x))
|
props.setProperty(UploadLargeTimeout, settings.upload.largeTimeout.toString)
|
||||||
ldap.fullNameAttribute.foreach(x => props.setProperty(LdapFullNameAttribute, x))
|
props.setProperty(RepositoryViewerMaxFiles, settings.repositoryViewer.maxFiles.toString)
|
||||||
ldap.mailAttribute.foreach(x => props.setProperty(LdapMailAddressAttribute, x))
|
|
||||||
ldap.tls.foreach(x => props.setProperty(LdapTls, x.toString))
|
|
||||||
ldap.ssl.foreach(x => props.setProperty(LdapSsl, x.toString))
|
|
||||||
ldap.keystore.foreach(x => props.setProperty(LdapKeystore, x))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
props.setProperty(OidcAuthentication, settings.oidcAuthentication.toString)
|
|
||||||
if (settings.oidcAuthentication) {
|
|
||||||
settings.oidc.foreach { oidc =>
|
|
||||||
props.setProperty(OidcIssuer, oidc.issuer.getValue)
|
|
||||||
props.setProperty(OidcClientId, oidc.clientID.getValue)
|
|
||||||
props.setProperty(OidcClientSecret, oidc.clientSecret.getValue)
|
|
||||||
oidc.jwsAlgorithm.foreach { x =>
|
|
||||||
props.setProperty(OidcJwsAlgorithm, x.getName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
props.setProperty(SkinName, settings.skinName.toString)
|
|
||||||
settings.userDefinedCss.foreach(x => props.setProperty(UserDefinedCss, x))
|
|
||||||
props.setProperty(ShowMailAddress, settings.showMailAddress.toString)
|
|
||||||
props.setProperty(WebHookBlockPrivateAddress, settings.webHook.blockPrivateAddress.toString)
|
|
||||||
props.setProperty(WebHookWhitelist, settings.webHook.whitelist.mkString("\n"))
|
|
||||||
props.setProperty(UploadMaxFileSize, settings.upload.maxFileSize.toString)
|
|
||||||
props.setProperty(UploadTimeout, settings.upload.timeout.toString)
|
|
||||||
props.setProperty(UploadLargeMaxFileSize, settings.upload.largeMaxFileSize.toString)
|
|
||||||
props.setProperty(UploadLargeTimeout, settings.upload.largeTimeout.toString)
|
|
||||||
props.setProperty(RepositoryViewerMaxFiles, settings.repositoryViewer.maxFiles.toString)
|
|
||||||
|
|
||||||
Using.resource(new java.io.FileOutputStream(GitBucketConf)) { out =>
|
Using.resource(new java.io.FileOutputStream(GitBucketConf)) { out =>
|
||||||
props.store(out, null)
|
props.store(out, null)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def loadSystemSettings(): SystemSettings = {
|
def loadSystemSettings(): SystemSettings = {
|
||||||
defining(new java.util.Properties()) { props =>
|
val props = new java.util.Properties()
|
||||||
if (GitBucketConf.exists) {
|
if (GitBucketConf.exists) {
|
||||||
Using.resource(new java.io.FileInputStream(GitBucketConf)) { in =>
|
Using.resource(new java.io.FileInputStream(GitBucketConf)) { in =>
|
||||||
props.load(in)
|
props.load(in)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
SystemSettings(
|
|
||||||
getOptionValue[String](props, BaseURL, None).map(x => x.replaceFirst("/\\Z", "")),
|
|
||||||
getOptionValue(props, Information, None),
|
|
||||||
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),
|
|
||||||
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
|
|
||||||
if (getValue(props, UseSMTP, getValue(props, Notification, false))) {
|
|
||||||
Some(
|
|
||||||
Smtp(
|
|
||||||
getValue(props, SmtpHost, ""),
|
|
||||||
getOptionValue(props, SmtpPort, Some(DefaultSmtpPort)),
|
|
||||||
getOptionValue(props, SmtpUser, None),
|
|
||||||
getOptionValue(props, SmtpPassword, None),
|
|
||||||
getOptionValue[Boolean](props, SmtpSsl, None),
|
|
||||||
getOptionValue[Boolean](props, SmtpStarttls, None),
|
|
||||||
getOptionValue(props, SmtpFromAddress, None),
|
|
||||||
getOptionValue(props, SmtpFromName, None)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
} else None,
|
|
||||||
getValue(props, LdapAuthentication, false),
|
|
||||||
if (getValue(props, LdapAuthentication, false)) {
|
|
||||||
Some(
|
|
||||||
Ldap(
|
|
||||||
getValue(props, LdapHost, ""),
|
|
||||||
getOptionValue(props, LdapPort, Some(DefaultLdapPort)),
|
|
||||||
getOptionValue(props, LdapBindDN, None),
|
|
||||||
getOptionValue(props, LdapBindPassword, None),
|
|
||||||
getValue(props, LdapBaseDN, ""),
|
|
||||||
getValue(props, LdapUserNameAttribute, ""),
|
|
||||||
getOptionValue(props, LdapAdditionalFilterCondition, None),
|
|
||||||
getOptionValue(props, LdapFullNameAttribute, None),
|
|
||||||
getOptionValue(props, LdapMailAddressAttribute, None),
|
|
||||||
getOptionValue[Boolean](props, LdapTls, None),
|
|
||||||
getOptionValue[Boolean](props, LdapSsl, None),
|
|
||||||
getOptionValue(props, LdapKeystore, None)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
} else None,
|
|
||||||
getValue(props, OidcAuthentication, false),
|
|
||||||
if (getValue(props, OidcAuthentication, false)) {
|
|
||||||
Some(
|
|
||||||
OIDC(
|
|
||||||
getValue(props, OidcIssuer, ""),
|
|
||||||
getValue(props, OidcClientId, ""),
|
|
||||||
getValue(props, OidcClientSecret, ""),
|
|
||||||
getOptionValue(props, OidcJwsAlgorithm, None)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
},
|
|
||||||
getValue(props, SkinName, "skin-blue"),
|
|
||||||
getOptionValue(props, UserDefinedCss, None),
|
|
||||||
getValue(props, ShowMailAddress, false),
|
|
||||||
WebHook(getValue(props, WebHookBlockPrivateAddress, false), getSeqValue(props, WebHookWhitelist, "")),
|
|
||||||
Upload(
|
|
||||||
getValue(props, UploadMaxFileSize, 3 * 1024 * 1024),
|
|
||||||
getValue(props, UploadTimeout, 3 * 10000),
|
|
||||||
getValue(props, UploadLargeMaxFileSize, 3 * 1024 * 1024),
|
|
||||||
getValue(props, UploadLargeTimeout, 3 * 10000)
|
|
||||||
),
|
|
||||||
RepositoryViewerSettings(
|
|
||||||
getValue(props, RepositoryViewerMaxFiles, 0)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
loadSystemSettings(props)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def loadSystemSettings(props: java.util.Properties): SystemSettings = {
|
||||||
|
SystemSettings(
|
||||||
|
getOptionValue[String](props, BaseURL, None).map(x => x.replaceFirst("/\\Z", "")),
|
||||||
|
getOptionValue(props, Information, None),
|
||||||
|
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),
|
||||||
|
getValue(props, LimitVisibleRepositories, false),
|
||||||
|
Ssh(
|
||||||
|
enabled = getValue(props, SshEnabled, false),
|
||||||
|
bindAddress = {
|
||||||
|
// try the new-style configuration first
|
||||||
|
getOptionValue[String](props, SshBindAddressHost, None)
|
||||||
|
.map(h => SshAddress(h, getValue(props, SshBindAddressPort, DefaultSshPort), GenericSshUser))
|
||||||
|
.orElse(
|
||||||
|
// otherwise try to get old-style configuration
|
||||||
|
getOptionValue[String](props, SshHost, None)
|
||||||
|
.map(_.trim)
|
||||||
|
.map(h => SshAddress(h, getValue(props, SshPort, DefaultSshPort), GenericSshUser))
|
||||||
|
)
|
||||||
|
},
|
||||||
|
publicAddress = getOptionValue[String](props, SshPublicAddressHost, None)
|
||||||
|
.map(h => SshAddress(h, getValue(props, SshPublicAddressPort, PublicSshPort), GenericSshUser))
|
||||||
|
),
|
||||||
|
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(
|
||||||
|
getValue(props, SmtpHost, ""),
|
||||||
|
getOptionValue(props, SmtpPort, Some(DefaultSmtpPort)),
|
||||||
|
getOptionValue(props, SmtpUser, None),
|
||||||
|
getOptionValue(props, SmtpPassword, None),
|
||||||
|
getOptionValue[Boolean](props, SmtpSsl, None),
|
||||||
|
getOptionValue[Boolean](props, SmtpStarttls, None),
|
||||||
|
getOptionValue(props, SmtpFromAddress, None),
|
||||||
|
getOptionValue(props, SmtpFromName, None)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
} else None,
|
||||||
|
getValue(props, LdapAuthentication, false),
|
||||||
|
if (getValue(props, LdapAuthentication, false)) {
|
||||||
|
Some(
|
||||||
|
Ldap(
|
||||||
|
getValue(props, LdapHost, ""),
|
||||||
|
getOptionValue(props, LdapPort, Some(DefaultLdapPort)),
|
||||||
|
getOptionValue(props, LdapBindDN, None),
|
||||||
|
getOptionValue(props, LdapBindPassword, None),
|
||||||
|
getValue(props, LdapBaseDN, ""),
|
||||||
|
getValue(props, LdapUserNameAttribute, ""),
|
||||||
|
getOptionValue(props, LdapAdditionalFilterCondition, None),
|
||||||
|
getOptionValue(props, LdapFullNameAttribute, None),
|
||||||
|
getOptionValue(props, LdapMailAddressAttribute, None),
|
||||||
|
getOptionValue[Boolean](props, LdapTls, None),
|
||||||
|
getOptionValue[Boolean](props, LdapSsl, None),
|
||||||
|
getOptionValue(props, LdapKeystore, None)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
} else None,
|
||||||
|
getValue(props, OidcAuthentication, false),
|
||||||
|
if (getValue(props, OidcAuthentication, false)) {
|
||||||
|
Some(
|
||||||
|
OIDC(
|
||||||
|
getValue(props, OidcIssuer, ""),
|
||||||
|
getValue(props, OidcClientId, ""),
|
||||||
|
getValue(props, OidcClientSecret, ""),
|
||||||
|
getOptionValue(props, OidcJwsAlgorithm, None)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
},
|
||||||
|
getValue(props, SkinName, "skin-blue"),
|
||||||
|
getOptionValue(props, UserDefinedCss, None),
|
||||||
|
getValue(props, ShowMailAddress, false),
|
||||||
|
WebHook(getValue(props, WebHookBlockPrivateAddress, false), getSeqValue(props, WebHookWhitelist, "")),
|
||||||
|
Upload(
|
||||||
|
getValue(props, UploadMaxFileSize, 3 * 1024 * 1024),
|
||||||
|
getValue(props, UploadTimeout, 3 * 10000),
|
||||||
|
getValue(props, UploadLargeMaxFileSize, 3 * 1024 * 1024),
|
||||||
|
getValue(props, UploadLargeTimeout, 3 * 10000)
|
||||||
|
),
|
||||||
|
RepositoryViewerSettings(
|
||||||
|
getValue(props, RepositoryViewerMaxFiles, 0)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
object SystemSettingsService {
|
object SystemSettingsService {
|
||||||
@@ -217,7 +235,6 @@ object SystemSettingsService {
|
|||||||
upload: Upload,
|
upload: Upload,
|
||||||
repositoryViewer: RepositoryViewerSettings
|
repositoryViewer: RepositoryViewerSettings
|
||||||
) {
|
) {
|
||||||
|
|
||||||
def baseUrl(request: HttpServletRequest): String =
|
def baseUrl(request: HttpServletRequest): String =
|
||||||
baseUrl.getOrElse(parseBaseUrl(request)).stripSuffix("/")
|
baseUrl.getOrElse(parseBaseUrl(request)).stripSuffix("/")
|
||||||
|
|
||||||
@@ -234,11 +251,17 @@ object SystemSettingsService {
|
|||||||
.fold(base)(_ + base.dropWhile(_ != ':'))
|
.fold(base)(_ + base.dropWhile(_ != ':'))
|
||||||
}
|
}
|
||||||
|
|
||||||
def sshAddress: Option[SshAddress] =
|
def sshBindAddress: Option[SshAddress] =
|
||||||
ssh.sshHost.collect {
|
ssh.bindAddress
|
||||||
case host if ssh.enabled =>
|
|
||||||
SshAddress(host, ssh.sshPort.getOrElse(DefaultSshPort), "git")
|
def sshPublicAddress: Option[SshAddress] =
|
||||||
}
|
ssh.publicAddress.orElse(ssh.bindAddress)
|
||||||
|
|
||||||
|
def sshUrl: Option[String] =
|
||||||
|
ssh.getUrl
|
||||||
|
|
||||||
|
def sshUrl(owner: String, name: String): Option[String] =
|
||||||
|
ssh.getUrl(owner: String, name: String)
|
||||||
}
|
}
|
||||||
|
|
||||||
case class RepositoryOperation(
|
case class RepositoryOperation(
|
||||||
@@ -251,9 +274,35 @@ object SystemSettingsService {
|
|||||||
|
|
||||||
case class Ssh(
|
case class Ssh(
|
||||||
enabled: Boolean,
|
enabled: Boolean,
|
||||||
sshHost: Option[String],
|
bindAddress: Option[SshAddress],
|
||||||
sshPort: Option[Int]
|
publicAddress: Option[SshAddress]
|
||||||
)
|
) {
|
||||||
|
|
||||||
|
def getUrl: Option[String] =
|
||||||
|
if (enabled) {
|
||||||
|
publicAddress.map(_.getUrl).orElse(bindAddress.map(_.getUrl))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
def getUrl(owner: String, name: String): Option[String] =
|
||||||
|
if (enabled) {
|
||||||
|
publicAddress
|
||||||
|
.map(_.getUrl(owner, name))
|
||||||
|
.orElse(bindAddress.map(_.getUrl(owner, name)))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object Ssh {
|
||||||
|
def apply(
|
||||||
|
enabled: Boolean,
|
||||||
|
bindAddress: Option[SshAddress],
|
||||||
|
publicAddress: Option[SshAddress]
|
||||||
|
): Ssh =
|
||||||
|
new Ssh(enabled, bindAddress, publicAddress.orElse(bindAddress))
|
||||||
|
}
|
||||||
|
|
||||||
case class Ldap(
|
case class Ldap(
|
||||||
host: String,
|
host: String,
|
||||||
@@ -299,7 +348,25 @@ object SystemSettingsService {
|
|||||||
password: Option[String]
|
password: Option[String]
|
||||||
)
|
)
|
||||||
|
|
||||||
case class SshAddress(host: String, port: Int, genericUser: String)
|
case class SshAddress(host: String, port: Int, genericUser: String) {
|
||||||
|
|
||||||
|
def isDefaultPort: Boolean =
|
||||||
|
port == PublicSshPort
|
||||||
|
|
||||||
|
def getUrl: String =
|
||||||
|
if (isDefaultPort) {
|
||||||
|
s"${genericUser}@${host}"
|
||||||
|
} else {
|
||||||
|
s"${genericUser}@${host}:${port}"
|
||||||
|
}
|
||||||
|
|
||||||
|
def getUrl(owner: String, name: String): String =
|
||||||
|
if (isDefaultPort) {
|
||||||
|
s"${genericUser}@${host}:${owner}/${name}.git"
|
||||||
|
} else {
|
||||||
|
s"ssh://${genericUser}@${host}:${port}/${owner}/${name}.git"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
case class WebHook(blockPrivateAddress: Boolean, whitelist: Seq[String])
|
case class WebHook(blockPrivateAddress: Boolean, whitelist: Seq[String])
|
||||||
|
|
||||||
@@ -307,6 +374,8 @@ object SystemSettingsService {
|
|||||||
|
|
||||||
case class RepositoryViewerSettings(maxFiles: Int)
|
case class RepositoryViewerSettings(maxFiles: Int)
|
||||||
|
|
||||||
|
val GenericSshUser = "git"
|
||||||
|
val PublicSshPort = 22
|
||||||
val DefaultSshPort = 29418
|
val DefaultSshPort = 29418
|
||||||
val DefaultSmtpPort = 25
|
val DefaultSmtpPort = 25
|
||||||
val DefaultLdapPort = 389
|
val DefaultLdapPort = 389
|
||||||
@@ -328,6 +397,10 @@ object SystemSettingsService {
|
|||||||
private val SshEnabled = "ssh"
|
private val SshEnabled = "ssh"
|
||||||
private val SshHost = "ssh.host"
|
private val SshHost = "ssh.host"
|
||||||
private val SshPort = "ssh.port"
|
private val SshPort = "ssh.port"
|
||||||
|
private val SshBindAddressHost = "ssh.bindAddress.host"
|
||||||
|
private val SshBindAddressPort = "ssh.bindAddress.port"
|
||||||
|
private val SshPublicAddressHost = "ssh.publicAddress.host"
|
||||||
|
private val SshPublicAddressPort = "ssh.publicAddress.port"
|
||||||
private val UseSMTP = "useSMTP"
|
private val UseSMTP = "useSMTP"
|
||||||
private val SmtpHost = "smtp.host"
|
private val SmtpHost = "smtp.host"
|
||||||
private val SmtpPort = "smtp.port"
|
private val SmtpPort = "smtp.port"
|
||||||
@@ -368,12 +441,11 @@ object SystemSettingsService {
|
|||||||
|
|
||||||
private def getValue[A: ClassTag](props: java.util.Properties, key: String, default: A): A = {
|
private def getValue[A: ClassTag](props: java.util.Properties, key: String, default: A): A = {
|
||||||
getConfigValue(key).getOrElse {
|
getConfigValue(key).getOrElse {
|
||||||
defining(props.getProperty(key)) { value =>
|
val value = props.getProperty(key)
|
||||||
if (value == null || value.isEmpty) {
|
if (value == null || value.isEmpty) {
|
||||||
default
|
default
|
||||||
} else {
|
} else {
|
||||||
convertType(value).asInstanceOf[A]
|
convertType(value).asInstanceOf[A]
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -390,12 +462,11 @@ object SystemSettingsService {
|
|||||||
|
|
||||||
private def getOptionValue[A: ClassTag](props: java.util.Properties, key: String, default: Option[A]): Option[A] = {
|
private def getOptionValue[A: ClassTag](props: java.util.Properties, key: String, default: Option[A]): Option[A] = {
|
||||||
getConfigValue(key).orElse {
|
getConfigValue(key).orElse {
|
||||||
defining(props.getProperty(key)) { value =>
|
val value = props.getProperty(key)
|
||||||
if (value == null || value.isEmpty) {
|
if (value == null || value.isEmpty) {
|
||||||
default
|
default
|
||||||
} else {
|
} else {
|
||||||
Some(convertType(value)).asInstanceOf[Option[A]]
|
Some(convertType(value)).asInstanceOf[Option[A]]
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package gitbucket.core.service
|
|||||||
import fr.brouillard.oss.security.xhub.XHub
|
import fr.brouillard.oss.security.xhub.XHub
|
||||||
import fr.brouillard.oss.security.xhub.XHub.{XHubConverter, XHubDigest}
|
import fr.brouillard.oss.security.xhub.XHub.{XHubConverter, XHubDigest}
|
||||||
import gitbucket.core.api._
|
import gitbucket.core.api._
|
||||||
import gitbucket.core.controller.Context
|
|
||||||
import gitbucket.core.model.{
|
import gitbucket.core.model.{
|
||||||
Account,
|
Account,
|
||||||
AccountWebHook,
|
AccountWebHook,
|
||||||
@@ -21,7 +20,6 @@ import gitbucket.core.model.Profile._
|
|||||||
import gitbucket.core.model.Profile.profile.blockingApi._
|
import gitbucket.core.model.Profile.profile.blockingApi._
|
||||||
import org.apache.http.client.utils.URLEncodedUtils
|
import org.apache.http.client.utils.URLEncodedUtils
|
||||||
import gitbucket.core.util.JGitUtil.CommitInfo
|
import gitbucket.core.util.JGitUtil.CommitInfo
|
||||||
import gitbucket.core.util.Implicits._
|
|
||||||
import gitbucket.core.util.{HttpClientUtil, RepositoryName, StringUtil}
|
import gitbucket.core.util.{HttpClientUtil, RepositoryName, StringUtil}
|
||||||
import gitbucket.core.service.RepositoryService.RepositoryInfo
|
import gitbucket.core.service.RepositoryService.RepositoryInfo
|
||||||
import org.apache.http.NameValuePair
|
import org.apache.http.NameValuePair
|
||||||
@@ -37,6 +35,7 @@ import org.apache.http.HttpRequest
|
|||||||
import org.apache.http.HttpResponse
|
import org.apache.http.HttpResponse
|
||||||
import gitbucket.core.model.WebHookContentType
|
import gitbucket.core.model.WebHookContentType
|
||||||
import gitbucket.core.service.SystemSettingsService.SystemSettings
|
import gitbucket.core.service.SystemSettingsService.SystemSettings
|
||||||
|
import gitbucket.core.view.helpers.getApiMilestone
|
||||||
import org.apache.http.client.entity.EntityBuilder
|
import org.apache.http.client.entity.EntityBuilder
|
||||||
import org.apache.http.entity.ContentType
|
import org.apache.http.entity.ContentType
|
||||||
|
|
||||||
@@ -128,7 +127,7 @@ trait WebHookService {
|
|||||||
ctype = ctype,
|
ctype = ctype,
|
||||||
token = token
|
token = token
|
||||||
)
|
)
|
||||||
events.map { event: WebHook.Event =>
|
events.map { (event: WebHook.Event) =>
|
||||||
RepositoryWebHookEvents insert RepositoryWebHookEvent(owner, repository, url, event)
|
RepositoryWebHookEvents insert RepositoryWebHookEvent(owner, repository, url, event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -146,7 +145,7 @@ trait WebHookService {
|
|||||||
.map(w => (w.ctype, w.token))
|
.map(w => (w.ctype, w.token))
|
||||||
.update((ctype, token))
|
.update((ctype, token))
|
||||||
RepositoryWebHookEvents.filter(_.byRepositoryWebHook(owner, repository, url)).delete
|
RepositoryWebHookEvents.filter(_.byRepositoryWebHook(owner, repository, url)).delete
|
||||||
events.map { event: WebHook.Event =>
|
events.map { (event: WebHook.Event) =>
|
||||||
RepositoryWebHookEvents insert RepositoryWebHookEvent(owner, repository, url, event)
|
RepositoryWebHookEvents insert RepositoryWebHookEvent(owner, repository, url, event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -165,7 +164,7 @@ trait WebHookService {
|
|||||||
.map(w => (w.url, w.ctype, w.token))
|
.map(w => (w.url, w.ctype, w.token))
|
||||||
.update((url, ctype, token))
|
.update((url, ctype, token))
|
||||||
RepositoryWebHookEvents.filter(_.byRepositoryWebHook(owner, repository, url)).delete
|
RepositoryWebHookEvents.filter(_.byRepositoryWebHook(owner, repository, url)).delete
|
||||||
events.map { event: WebHook.Event =>
|
events.map { (event: WebHook.Event) =>
|
||||||
RepositoryWebHookEvents insert RepositoryWebHookEvent(owner, repository, url, event)
|
RepositoryWebHookEvents insert RepositoryWebHookEvent(owner, repository, url, event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -230,7 +229,7 @@ trait WebHookService {
|
|||||||
token: Option[String]
|
token: Option[String]
|
||||||
)(implicit s: Session): Unit = {
|
)(implicit s: Session): Unit = {
|
||||||
AccountWebHooks insert AccountWebHook(owner, url, ctype, token)
|
AccountWebHooks insert AccountWebHook(owner, url, ctype, token)
|
||||||
events.map { event: WebHook.Event =>
|
events.map { (event: WebHook.Event) =>
|
||||||
AccountWebHookEvents insert AccountWebHookEvent(owner, url, event)
|
AccountWebHookEvents insert AccountWebHookEvent(owner, url, event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -244,7 +243,7 @@ trait WebHookService {
|
|||||||
)(implicit s: Session): Unit = {
|
)(implicit s: Session): Unit = {
|
||||||
AccountWebHooks.filter(_.byPrimaryKey(owner, url)).map(w => (w.ctype, w.token)).update((ctype, token))
|
AccountWebHooks.filter(_.byPrimaryKey(owner, url)).map(w => (w.ctype, w.token)).update((ctype, token))
|
||||||
AccountWebHookEvents.filter(_.byAccountWebHook(owner, url)).delete
|
AccountWebHookEvents.filter(_.byAccountWebHook(owner, url)).delete
|
||||||
events.map { event: WebHook.Event =>
|
events.map { (event: WebHook.Event) =>
|
||||||
AccountWebHookEvents insert AccountWebHookEvent(owner, url, event)
|
AccountWebHookEvents insert AccountWebHookEvent(owner, url, event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -396,7 +395,8 @@ trait WebHookPullRequestService extends WebHookService {
|
|||||||
ApiUser(issueUser),
|
ApiUser(issueUser),
|
||||||
issue.assignedUserName.flatMap(users.get(_)).map(ApiUser(_)),
|
issue.assignedUserName.flatMap(users.get(_)).map(ApiUser(_)),
|
||||||
getIssueLabels(repository.owner, repository.name, issue.issueId)
|
getIssueLabels(repository.owner, repository.name, issue.issueId)
|
||||||
.map(ApiLabel(_, RepositoryName(repository)))
|
.map(ApiLabel(_, RepositoryName(repository))),
|
||||||
|
getApiMilestone(repository, issue.milestoneId getOrElse (0))
|
||||||
),
|
),
|
||||||
sender = ApiUser(sender)
|
sender = ApiUser(sender)
|
||||||
)
|
)
|
||||||
@@ -578,6 +578,7 @@ trait WebHookIssueCommentService extends WebHookPullRequestService {
|
|||||||
commenter <- users.get(issueComment.commentedUserName)
|
commenter <- users.get(issueComment.commentedUserName)
|
||||||
assignedUser = issue.assignedUserName.flatMap(users.get(_))
|
assignedUser = issue.assignedUserName.flatMap(users.get(_))
|
||||||
labels = getIssueLabels(repository.owner, repository.name, issue.issueId)
|
labels = getIssueLabels(repository.owner, repository.name, issue.issueId)
|
||||||
|
milestone = getApiMilestone(repository, issue.milestoneId getOrElse (0))
|
||||||
} yield {
|
} yield {
|
||||||
WebHookIssueCommentPayload(
|
WebHookIssueCommentPayload(
|
||||||
issue = issue,
|
issue = issue,
|
||||||
@@ -588,7 +589,8 @@ trait WebHookIssueCommentService extends WebHookPullRequestService {
|
|||||||
repositoryUser = repoOwner,
|
repositoryUser = repoOwner,
|
||||||
assignedUser = assignedUser,
|
assignedUser = assignedUser,
|
||||||
sender = sender,
|
sender = sender,
|
||||||
labels = labels
|
labels = labels,
|
||||||
|
milestone = milestone
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -762,7 +764,8 @@ object WebHookService {
|
|||||||
repositoryUser: Account,
|
repositoryUser: Account,
|
||||||
assignedUser: Option[Account],
|
assignedUser: Option[Account],
|
||||||
sender: Account,
|
sender: Account,
|
||||||
labels: List[Label]
|
labels: List[Label],
|
||||||
|
milestone: Option[ApiMilestone]
|
||||||
): WebHookIssueCommentPayload =
|
): WebHookIssueCommentPayload =
|
||||||
WebHookIssueCommentPayload(
|
WebHookIssueCommentPayload(
|
||||||
action = "created",
|
action = "created",
|
||||||
@@ -772,7 +775,8 @@ object WebHookService {
|
|||||||
RepositoryName(repository),
|
RepositoryName(repository),
|
||||||
ApiUser(issueUser),
|
ApiUser(issueUser),
|
||||||
assignedUser.map(ApiUser(_)),
|
assignedUser.map(ApiUser(_)),
|
||||||
labels.map(ApiLabel(_, RepositoryName(repository)))
|
labels.map(ApiLabel(_, RepositoryName(repository))),
|
||||||
|
milestone
|
||||||
),
|
),
|
||||||
comment =
|
comment =
|
||||||
ApiComment(comment, RepositoryName(repository), issue.issueId, ApiUser(commentUser), issue.isPullRequest),
|
ApiComment(comment, RepositoryName(repository), issue.issueId, ApiUser(commentUser), issue.isPullRequest),
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import gitbucket.core.controller.Context
|
|||||||
import gitbucket.core.model.Account
|
import gitbucket.core.model.Account
|
||||||
import gitbucket.core.service.RepositoryService.RepositoryInfo
|
import gitbucket.core.service.RepositoryService.RepositoryInfo
|
||||||
import gitbucket.core.util._
|
import gitbucket.core.util._
|
||||||
import gitbucket.core.util.SyntaxSugars._
|
|
||||||
import org.eclipse.jgit.api.Git
|
import org.eclipse.jgit.api.Git
|
||||||
import org.eclipse.jgit.treewalk.CanonicalTreeParser
|
import org.eclipse.jgit.treewalk.CanonicalTreeParser
|
||||||
import org.eclipse.jgit.lib._
|
import org.eclipse.jgit.lib._
|
||||||
@@ -54,20 +53,19 @@ trait WikiService {
|
|||||||
|
|
||||||
def createWikiRepository(loginAccount: Account, owner: String, repository: String): Unit =
|
def createWikiRepository(loginAccount: Account, owner: String, repository: String): Unit =
|
||||||
LockUtil.lock(s"${owner}/${repository}/wiki") {
|
LockUtil.lock(s"${owner}/${repository}/wiki") {
|
||||||
defining(Directory.getWikiRepositoryDir(owner, repository)) { dir =>
|
val dir = Directory.getWikiRepositoryDir(owner, repository)
|
||||||
if (!dir.exists) {
|
if (!dir.exists) {
|
||||||
JGitUtil.initRepository(dir)
|
JGitUtil.initRepository(dir)
|
||||||
saveWikiPage(
|
saveWikiPage(
|
||||||
owner,
|
owner,
|
||||||
repository,
|
repository,
|
||||||
"Home",
|
"Home",
|
||||||
"Home",
|
"Home",
|
||||||
s"Welcome to the ${repository} wiki!!",
|
s"Welcome to the ${repository} wiki!!",
|
||||||
loginAccount,
|
loginAccount,
|
||||||
"Initial Commit",
|
"Initial Commit",
|
||||||
None
|
None
|
||||||
)
|
)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,13 +75,15 @@ trait WikiService {
|
|||||||
def getWikiPage(owner: String, repository: String, pageName: String): Option[WikiPageInfo] = {
|
def getWikiPage(owner: String, repository: String, pageName: String): Option[WikiPageInfo] = {
|
||||||
Using.resource(Git.open(Directory.getWikiRepositoryDir(owner, repository))) { git =>
|
Using.resource(Git.open(Directory.getWikiRepositoryDir(owner, repository))) { git =>
|
||||||
if (!JGitUtil.isEmpty(git)) {
|
if (!JGitUtil.isEmpty(git)) {
|
||||||
JGitUtil.getFileList(git, "master", ".").find(_.name == pageName + ".md").map { file =>
|
val fileName = pageName + ".md"
|
||||||
|
JGitUtil.getLatestCommitFromPath(git, fileName, "master").map { latestCommit =>
|
||||||
|
val content = JGitUtil.getContentFromPath(git, latestCommit.getTree, fileName, true)
|
||||||
WikiPageInfo(
|
WikiPageInfo(
|
||||||
file.name,
|
fileName,
|
||||||
StringUtil.convertFromByteArray(git.getRepository.open(file.id).getBytes),
|
StringUtil.convertFromByteArray(content.getOrElse(Array.empty)),
|
||||||
file.author,
|
latestCommit.getAuthorIdent.getName,
|
||||||
file.time,
|
latestCommit.getAuthorIdent.getWhen,
|
||||||
file.commitId
|
latestCommit.getName
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else None
|
} else None
|
||||||
@@ -147,7 +147,7 @@ trait WikiService {
|
|||||||
if (!p.getErrors.isEmpty) {
|
if (!p.getErrors.isEmpty) {
|
||||||
throw new PatchFormatException(p.getErrors())
|
throw new PatchFormatException(p.getErrors())
|
||||||
}
|
}
|
||||||
val revertInfo = (p.getFiles.asScala.map { fh =>
|
val revertInfo = p.getFiles.asScala.flatMap { fh =>
|
||||||
fh.getChangeType match {
|
fh.getChangeType match {
|
||||||
case DiffEntry.ChangeType.MODIFY => {
|
case DiffEntry.ChangeType.MODIFY => {
|
||||||
val source =
|
val source =
|
||||||
@@ -176,7 +176,7 @@ trait WikiService {
|
|||||||
}
|
}
|
||||||
case _ => Nil
|
case _ => Nil
|
||||||
}
|
}
|
||||||
}).flatten
|
}
|
||||||
|
|
||||||
if (revertInfo.nonEmpty) {
|
if (revertInfo.nonEmpty) {
|
||||||
val builder = DirCache.newInCore.builder()
|
val builder = DirCache.newInCore.builder()
|
||||||
@@ -257,8 +257,7 @@ trait WikiService {
|
|||||||
created = false
|
created = false
|
||||||
updated = JGitUtil
|
updated = JGitUtil
|
||||||
.getContentFromId(git, tree.getEntryObjectId, true)
|
.getContentFromId(git, tree.getEntryObjectId, true)
|
||||||
.map(new String(_, "UTF-8") != content)
|
.exists(new String(_, "UTF-8") != content)
|
||||||
.getOrElse(false)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,8 +19,9 @@ class ApiAuthenticationFilter extends Filter with AccessTokenService with Accoun
|
|||||||
override def destroy(): Unit = {}
|
override def destroy(): Unit = {}
|
||||||
|
|
||||||
override def doFilter(req: ServletRequest, res: ServletResponse, chain: FilterChain): Unit = {
|
override def doFilter(req: ServletRequest, res: ServletResponse, chain: FilterChain): Unit = {
|
||||||
implicit val request = req.asInstanceOf[HttpServletRequest]
|
implicit val request: HttpServletRequest = req.asInstanceOf[HttpServletRequest]
|
||||||
implicit val session = req.getAttribute(Keys.Request.DBSession).asInstanceOf[slick.jdbc.JdbcBackend#Session]
|
implicit val session: slick.jdbc.JdbcBackend#Session =
|
||||||
|
req.getAttribute(Keys.Request.DBSession).asInstanceOf[slick.jdbc.JdbcBackend#Session]
|
||||||
val response = res.asInstanceOf[HttpServletResponse]
|
val response = res.asInstanceOf[HttpServletResponse]
|
||||||
Option(request.getHeader("Authorization"))
|
Option(request.getHeader("Authorization"))
|
||||||
.map {
|
.map {
|
||||||
|
|||||||
@@ -139,7 +139,7 @@ class GitAuthenticationFilter extends Filter with RepositoryService with Account
|
|||||||
case _ =>
|
case _ =>
|
||||||
() =>
|
() =>
|
||||||
{
|
{
|
||||||
logger.debug(s"Not enough path arguments: ${request.paths}")
|
logger.debug(s"Not enough path arguments: ${request.paths.mkString(", ")}")
|
||||||
response.sendError(HttpServletResponse.SC_NOT_FOUND)
|
response.sendError(HttpServletResponse.SC_NOT_FOUND)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import javax.servlet.http.{HttpServlet, HttpServletRequest, HttpServletResponse}
|
|||||||
|
|
||||||
import gitbucket.core.util.{FileUtil, StringUtil}
|
import gitbucket.core.util.{FileUtil, StringUtil}
|
||||||
import org.apache.commons.io.{FileUtils, IOUtils}
|
import org.apache.commons.io.{FileUtils, IOUtils}
|
||||||
|
import org.json4s.Formats
|
||||||
import org.json4s.jackson.Serialization._
|
import org.json4s.jackson.Serialization._
|
||||||
import org.apache.http.HttpStatus
|
import org.apache.http.HttpStatus
|
||||||
|
|
||||||
@@ -17,7 +18,7 @@ import scala.util.Using
|
|||||||
*/
|
*/
|
||||||
class GitLfsTransferServlet extends HttpServlet {
|
class GitLfsTransferServlet extends HttpServlet {
|
||||||
|
|
||||||
private implicit val jsonFormats = gitbucket.core.api.JsonFormat.jsonFormats
|
private implicit val jsonFormats: Formats = gitbucket.core.api.JsonFormat.jsonFormats
|
||||||
private val LongObjectIdLength = 32
|
private val LongObjectIdLength = 32
|
||||||
private val LongObjectIdStringLength = LongObjectIdLength * 2
|
private val LongObjectIdStringLength = LongObjectIdLength * 2
|
||||||
|
|
||||||
|
|||||||
@@ -3,15 +3,14 @@ package gitbucket.core.servlet
|
|||||||
import java.io.File
|
import java.io.File
|
||||||
import java.util
|
import java.util
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
|
|
||||||
import scala.util.Using
|
import scala.util.Using
|
||||||
import gitbucket.core.api
|
import gitbucket.core.api
|
||||||
|
import gitbucket.core.api.JsonFormat.Context
|
||||||
import gitbucket.core.model.WebHook
|
import gitbucket.core.model.WebHook
|
||||||
import gitbucket.core.plugin.{GitRepositoryRouting, PluginRegistry}
|
import gitbucket.core.plugin.{GitRepositoryRouting, PluginRegistry}
|
||||||
import gitbucket.core.service.IssuesService.IssueSearchCondition
|
import gitbucket.core.service.IssuesService.IssueSearchCondition
|
||||||
import gitbucket.core.service.WebHookService._
|
import gitbucket.core.service.WebHookService._
|
||||||
import gitbucket.core.service._
|
import gitbucket.core.service._
|
||||||
import gitbucket.core.util.SyntaxSugars._
|
|
||||||
import gitbucket.core.util.Implicits._
|
import gitbucket.core.util.Implicits._
|
||||||
import gitbucket.core.util._
|
import gitbucket.core.util._
|
||||||
import gitbucket.core.model.Profile.profile.blockingApi._
|
import gitbucket.core.model.Profile.profile.blockingApi._
|
||||||
@@ -42,6 +41,7 @@ import javax.servlet.ServletConfig
|
|||||||
import javax.servlet.http.{HttpServletRequest, HttpServletResponse}
|
import javax.servlet.http.{HttpServletRequest, HttpServletResponse}
|
||||||
import org.eclipse.jgit.diff.DiffEntry.ChangeType
|
import org.eclipse.jgit.diff.DiffEntry.ChangeType
|
||||||
import org.eclipse.jgit.internal.storage.file.FileRepository
|
import org.eclipse.jgit.internal.storage.file.FileRepository
|
||||||
|
import org.json4s.Formats
|
||||||
import org.json4s.jackson.Serialization._
|
import org.json4s.jackson.Serialization._
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -53,7 +53,7 @@ import org.json4s.jackson.Serialization._
|
|||||||
class GitRepositoryServlet extends GitServlet with SystemSettingsService {
|
class GitRepositoryServlet extends GitServlet with SystemSettingsService {
|
||||||
|
|
||||||
private val logger = LoggerFactory.getLogger(classOf[GitRepositoryServlet])
|
private val logger = LoggerFactory.getLogger(classOf[GitRepositoryServlet])
|
||||||
private implicit val jsonFormats = gitbucket.core.api.JsonFormat.jsonFormats
|
private implicit val jsonFormats: Formats = gitbucket.core.api.JsonFormat.jsonFormats
|
||||||
|
|
||||||
override def init(config: ServletConfig): Unit = {
|
override def init(config: ServletConfig): Unit = {
|
||||||
setReceivePackFactory(new GitBucketReceivePackFactory())
|
setReceivePackFactory(new GitBucketReceivePackFactory())
|
||||||
@@ -200,33 +200,26 @@ class GitBucketReceivePackFactory extends ReceivePackFactory[HttpServletRequest]
|
|||||||
logger.debug("requestURI: " + request.getRequestURI)
|
logger.debug("requestURI: " + request.getRequestURI)
|
||||||
logger.debug("pusher:" + pusher)
|
logger.debug("pusher:" + pusher)
|
||||||
|
|
||||||
defining(request.paths) { paths =>
|
val paths = request.paths
|
||||||
val owner = paths(1)
|
val owner = paths(1)
|
||||||
val repository = paths(2).stripSuffix(".git")
|
val repository = paths(2).stripSuffix(".git")
|
||||||
|
|
||||||
logger.debug("repository:" + owner + "/" + repository)
|
logger.debug("repository:" + owner + "/" + repository)
|
||||||
|
|
||||||
val settings = loadSystemSettings()
|
val settings = loadSystemSettings()
|
||||||
val baseUrl = settings.baseUrl(request)
|
val baseUrl = settings.baseUrl(request)
|
||||||
val sshUrl = settings.sshAddress.map { x =>
|
val sshUrl = settings.sshUrl(owner, repository)
|
||||||
s"${x.genericUser}@${x.host}:${x.port}"
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!repository.endsWith(".wiki")) {
|
if (!repository.endsWith(".wiki")) {
|
||||||
defining(request) { implicit r =>
|
val hook = new CommitLogHook(owner, repository, pusher, baseUrl, sshUrl)
|
||||||
val hook = new CommitLogHook(owner, repository, pusher, baseUrl, sshUrl)
|
receivePack.setPreReceiveHook(hook)
|
||||||
receivePack.setPreReceiveHook(hook)
|
receivePack.setPostReceiveHook(hook)
|
||||||
receivePack.setPostReceiveHook(hook)
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (repository.endsWith(".wiki")) {
|
if (repository.endsWith(".wiki")) {
|
||||||
defining(request) { implicit r =>
|
receivePack.setPostReceiveHook(
|
||||||
receivePack.setPostReceiveHook(
|
new WikiCommitHook(owner, repository.stripSuffix(".wiki"), pusher, baseUrl, sshUrl)
|
||||||
new WikiCommitHook(owner, repository.stripSuffix(".wiki"), pusher, baseUrl, sshUrl)
|
)
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -293,7 +286,7 @@ class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl:
|
|||||||
val pushedIds = scala.collection.mutable.Set[String]()
|
val pushedIds = scala.collection.mutable.Set[String]()
|
||||||
commands.asScala.foreach { command =>
|
commands.asScala.foreach { command =>
|
||||||
logger.debug(s"commandType: ${command.getType}, refName: ${command.getRefName}")
|
logger.debug(s"commandType: ${command.getType}, refName: ${command.getRefName}")
|
||||||
implicit val apiContext = api.JsonFormat.Context(baseUrl, sshUrl)
|
implicit val apiContext: Context = api.JsonFormat.Context(baseUrl, sshUrl)
|
||||||
val refName = command.getRefName.split("/")
|
val refName = command.getRefName.split("/")
|
||||||
val branchName = refName.drop(2).mkString("/")
|
val branchName = refName.drop(2).mkString("/")
|
||||||
val commits = if (refName(1) == "tags") {
|
val commits = if (refName(1) == "tags") {
|
||||||
@@ -351,9 +344,9 @@ class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl:
|
|||||||
// set PR as merged
|
// set PR as merged
|
||||||
val pulls = getPullRequestsByBranch(owner, repository, branchName, Some(false))
|
val pulls = getPullRequestsByBranch(owner, repository, branchName, Some(false))
|
||||||
pulls.foreach { pull =>
|
pulls.foreach { pull =>
|
||||||
if (commits.find { c =>
|
if (commits.exists { c =>
|
||||||
c.id == pull.commitIdTo
|
c.id == pull.commitIdTo
|
||||||
}.isDefined) {
|
}) {
|
||||||
markMergeAndClosePullRequest(pusher, owner, repository, pull)
|
markMergeAndClosePullRequest(pusher, owner, repository, pull)
|
||||||
getAccountByUserName(pusher).foreach { pusherAccount =>
|
getAccountByUserName(pusher).foreach { pusherAccount =>
|
||||||
callPullRequestWebHook("closed", repositoryInfo, pull.issueId, pusherAccount, settings)
|
callPullRequestWebHook("closed", repositoryInfo, pull.issueId, pusherAccount, settings)
|
||||||
@@ -469,7 +462,7 @@ class WikiCommitHook(owner: String, repository: String, pusher: String, baseUrl:
|
|||||||
Database() withTransaction { implicit session =>
|
Database() withTransaction { implicit session =>
|
||||||
try {
|
try {
|
||||||
commands.asScala.headOption.foreach { command =>
|
commands.asScala.headOption.foreach { command =>
|
||||||
implicit val apiContext = api.JsonFormat.Context(baseUrl, sshUrl)
|
implicit val apiContext: Context = api.JsonFormat.Context(baseUrl, sshUrl)
|
||||||
val refName = command.getRefName.split("/")
|
val refName = command.getRefName.split("/")
|
||||||
val commitIds = if (refName(1) == "tags") {
|
val commitIds = if (refName(1) == "tags") {
|
||||||
None
|
None
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package gitbucket.core.servlet
|
|||||||
|
|
||||||
import java.io.{File, FileOutputStream}
|
import java.io.{File, FileOutputStream}
|
||||||
|
|
||||||
import com.typesafe.config.ConfigFactory
|
|
||||||
import gitbucket.core.GitBucketCoreModule
|
import gitbucket.core.GitBucketCoreModule
|
||||||
import gitbucket.core.plugin.PluginRegistry
|
import gitbucket.core.plugin.PluginRegistry
|
||||||
import gitbucket.core.service.SystemSettingsService
|
import gitbucket.core.service.SystemSettingsService
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package gitbucket.core.servlet
|
package gitbucket.core.servlet
|
||||||
|
|
||||||
import javax.servlet._
|
import javax.servlet._
|
||||||
import javax.servlet.http.HttpServletRequest
|
|
||||||
|
|
||||||
import gitbucket.core.controller.ControllerBase
|
import gitbucket.core.controller.ControllerBase
|
||||||
import gitbucket.core.plugin.PluginRegistry
|
import gitbucket.core.plugin.PluginRegistry
|
||||||
|
|||||||
@@ -4,27 +4,32 @@ import gitbucket.core.model.Profile.profile.blockingApi._
|
|||||||
import gitbucket.core.plugin.{GitRepositoryRouting, PluginRegistry}
|
import gitbucket.core.plugin.{GitRepositoryRouting, PluginRegistry}
|
||||||
import gitbucket.core.service.{AccountService, DeployKeyService, RepositoryService, SystemSettingsService}
|
import gitbucket.core.service.{AccountService, DeployKeyService, RepositoryService, SystemSettingsService}
|
||||||
import gitbucket.core.servlet.{CommitLogHook, Database}
|
import gitbucket.core.servlet.{CommitLogHook, Database}
|
||||||
import gitbucket.core.util.{SyntaxSugars, Directory}
|
import gitbucket.core.util.Directory
|
||||||
import org.apache.sshd.server.{Environment, ExitCallback, SessionAware}
|
import org.apache.sshd.server.{Environment, ExitCallback}
|
||||||
import org.apache.sshd.server.command.{Command, CommandFactory}
|
import org.apache.sshd.server.command.{Command, CommandFactory}
|
||||||
import org.apache.sshd.server.session.ServerSession
|
import org.apache.sshd.server.session.{ServerSession, ServerSessionAware}
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
import java.io.{File, InputStream, OutputStream}
|
|
||||||
|
|
||||||
|
import java.io.{File, InputStream, OutputStream}
|
||||||
import org.eclipse.jgit.api.Git
|
import org.eclipse.jgit.api.Git
|
||||||
import Directory._
|
import Directory._
|
||||||
|
import gitbucket.core.service.SystemSettingsService.SshAddress
|
||||||
import gitbucket.core.ssh.PublicKeyAuthenticator.AuthType
|
import gitbucket.core.ssh.PublicKeyAuthenticator.AuthType
|
||||||
|
import org.apache.sshd.server.channel.ChannelSession
|
||||||
import org.eclipse.jgit.transport.{ReceivePack, UploadPack}
|
import org.eclipse.jgit.transport.{ReceivePack, UploadPack}
|
||||||
import org.apache.sshd.server.shell.UnknownCommand
|
import org.apache.sshd.server.shell.UnknownCommand
|
||||||
import org.eclipse.jgit.errors.RepositoryNotFoundException
|
import org.eclipse.jgit.errors.RepositoryNotFoundException
|
||||||
|
|
||||||
import scala.util.Using
|
import scala.util.Using
|
||||||
|
|
||||||
object GitCommand {
|
object GitCommand {
|
||||||
val DefaultCommandRegex = """\Agit-(upload|receive)-pack '/([a-zA-Z0-9\-_.]+)/([a-zA-Z0-9\-\+_.]+).git'\Z""".r
|
val DefaultCommandRegex = """\Agit-(upload|receive)-pack '/([a-zA-Z0-9\-_.]+)/([a-zA-Z0-9\-\+_.]+).git'\Z""".r
|
||||||
val SimpleCommandRegex = """\Agit-(upload|receive)-pack '/(.+\.git)'\Z""".r
|
val SimpleCommandRegex = """\Agit-(upload|receive)-pack '/(.+\.git)'\Z""".r
|
||||||
|
val DefaultCommandRegexPort22 = """\Agit-(upload|receive)-pack '/?([a-zA-Z0-9\-_.]+)/([a-zA-Z0-9\-\+_.]+).git'\Z""".r
|
||||||
|
val SimpleCommandRegexPort22 = """\Agit-(upload|receive)-pack '/?(.+\.git)'\Z""".r
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract class GitCommand extends Command with SessionAware {
|
abstract class GitCommand extends Command with ServerSessionAware {
|
||||||
|
|
||||||
private val logger = LoggerFactory.getLogger(classOf[GitCommand])
|
private val logger = LoggerFactory.getLogger(classOf[GitCommand])
|
||||||
|
|
||||||
@@ -57,12 +62,12 @@ abstract class GitCommand extends Command with SessionAware {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final override def start(env: Environment): Unit = {
|
final override def start(channel: ChannelSession, env: Environment): Unit = {
|
||||||
val thread = new Thread(newTask())
|
val thread = new Thread(newTask())
|
||||||
thread.start()
|
thread.start()
|
||||||
}
|
}
|
||||||
|
|
||||||
override def destroy(): Unit = {}
|
override def destroy(channel: ChannelSession): Unit = {}
|
||||||
|
|
||||||
override def setExitCallback(callback: ExitCallback): Unit = {
|
override def setExitCallback(callback: ExitCallback): Unit = {
|
||||||
this.callback = callback
|
this.callback = callback
|
||||||
@@ -144,11 +149,9 @@ class DefaultGitUploadPack(owner: String, repoName: String)
|
|||||||
|
|
||||||
override protected def runTask(authType: AuthType): Unit = {
|
override protected def runTask(authType: AuthType): Unit = {
|
||||||
val execute = Database() withSession { implicit session =>
|
val execute = Database() withSession { implicit session =>
|
||||||
getRepository(owner, repoName.replaceFirst("\\.wiki\\Z", ""))
|
getRepository(owner, repoName.replaceFirst("\\.wiki\\Z", "")).exists { repositoryInfo =>
|
||||||
.map { repositoryInfo =>
|
!repositoryInfo.repository.isPrivate || isReadableUser(authType, repositoryInfo)
|
||||||
!repositoryInfo.repository.isPrivate || isReadableUser(authType, repositoryInfo)
|
}
|
||||||
}
|
|
||||||
.getOrElse(false)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (execute) {
|
if (execute) {
|
||||||
@@ -161,7 +164,7 @@ class DefaultGitUploadPack(owner: String, repoName: String)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class DefaultGitReceivePack(owner: String, repoName: String, baseUrl: String, sshUrl: Option[String])
|
class DefaultGitReceivePack(owner: String, repoName: String, baseUrl: String, sshAddress: SshAddress)
|
||||||
extends DefaultGitCommand(owner, repoName)
|
extends DefaultGitCommand(owner, repoName)
|
||||||
with RepositoryService
|
with RepositoryService
|
||||||
with AccountService
|
with AccountService
|
||||||
@@ -169,11 +172,9 @@ class DefaultGitReceivePack(owner: String, repoName: String, baseUrl: String, ss
|
|||||||
|
|
||||||
override protected def runTask(authType: AuthType): Unit = {
|
override protected def runTask(authType: AuthType): Unit = {
|
||||||
val execute = Database() withSession { implicit session =>
|
val execute = Database() withSession { implicit session =>
|
||||||
getRepository(owner, repoName.replaceFirst("\\.wiki\\Z", ""))
|
getRepository(owner, repoName.replaceFirst("\\.wiki\\Z", "")).exists { repositoryInfo =>
|
||||||
.map { repositoryInfo =>
|
isWritableUser(authType, repositoryInfo)
|
||||||
isWritableUser(authType, repositoryInfo)
|
}
|
||||||
}
|
|
||||||
.getOrElse(false)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (execute) {
|
if (execute) {
|
||||||
@@ -181,7 +182,8 @@ class DefaultGitReceivePack(owner: String, repoName: String, baseUrl: String, ss
|
|||||||
val repository = git.getRepository
|
val repository = git.getRepository
|
||||||
val receive = new ReceivePack(repository)
|
val receive = new ReceivePack(repository)
|
||||||
if (!repoName.endsWith(".wiki")) {
|
if (!repoName.endsWith(".wiki")) {
|
||||||
val hook = new CommitLogHook(owner, repoName, userName(authType), baseUrl, sshUrl)
|
val hook =
|
||||||
|
new CommitLogHook(owner, repoName, userName(authType), baseUrl, Some(sshAddress.getUrl(owner, repoName)))
|
||||||
receive.setPreReceiveHook(hook)
|
receive.setPreReceiveHook(hook)
|
||||||
receive.setPostReceiveHook(hook)
|
receive.setPostReceiveHook(hook)
|
||||||
}
|
}
|
||||||
@@ -231,10 +233,10 @@ class PluginGitReceivePack(repoName: String, routing: GitRepositoryRouting)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class GitCommandFactory(baseUrl: String, sshUrl: Option[String]) extends CommandFactory {
|
class GitCommandFactory(baseUrl: String, sshAddress: SshAddress) extends CommandFactory {
|
||||||
private val logger = LoggerFactory.getLogger(classOf[GitCommandFactory])
|
private val logger = LoggerFactory.getLogger(classOf[GitCommandFactory])
|
||||||
|
|
||||||
override def createCommand(command: String): Command = {
|
override def createCommand(channel: ChannelSession, command: String): Command = {
|
||||||
import GitCommand._
|
import GitCommand._
|
||||||
logger.debug(s"command: $command")
|
logger.debug(s"command: $command")
|
||||||
|
|
||||||
@@ -242,19 +244,24 @@ class GitCommandFactory(baseUrl: String, sshUrl: Option[String]) extends Command
|
|||||||
case f if f.isDefinedAt(command) => f(command)
|
case f if f.isDefinedAt(command) => f(command)
|
||||||
}
|
}
|
||||||
|
|
||||||
pluginCommand match {
|
pluginCommand.map(_.apply(channel)).getOrElse {
|
||||||
case Some(x) => x
|
val (simpleRegex, defaultRegex) =
|
||||||
case None =>
|
if (sshAddress.isDefaultPort) {
|
||||||
command match {
|
(SimpleCommandRegexPort22, DefaultCommandRegexPort22)
|
||||||
case SimpleCommandRegex("upload", repoName) if (pluginRepository(repoName)) =>
|
} else {
|
||||||
new PluginGitUploadPack(repoName, routing(repoName))
|
(SimpleCommandRegex, DefaultCommandRegex)
|
||||||
case SimpleCommandRegex("receive", repoName) if (pluginRepository(repoName)) =>
|
|
||||||
new PluginGitReceivePack(repoName, routing(repoName))
|
|
||||||
case DefaultCommandRegex("upload", owner, repoName) => new DefaultGitUploadPack(owner, repoName)
|
|
||||||
case DefaultCommandRegex("receive", owner, repoName) =>
|
|
||||||
new DefaultGitReceivePack(owner, repoName, baseUrl, sshUrl)
|
|
||||||
case _ => new UnknownCommand(command)
|
|
||||||
}
|
}
|
||||||
|
command match {
|
||||||
|
case simpleRegex("upload", repoName) if pluginRepository(repoName) =>
|
||||||
|
new PluginGitUploadPack(repoName, routing(repoName))
|
||||||
|
case simpleRegex("receive", repoName) if pluginRepository(repoName) =>
|
||||||
|
new PluginGitReceivePack(repoName, routing(repoName))
|
||||||
|
case defaultRegex("upload", owner, repoName) =>
|
||||||
|
new DefaultGitUploadPack(owner, repoName)
|
||||||
|
case defaultRegex("receive", owner, repoName) =>
|
||||||
|
new DefaultGitReceivePack(owner, repoName, baseUrl, sshAddress)
|
||||||
|
case _ => new UnknownCommand(command)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,20 +1,23 @@
|
|||||||
package gitbucket.core.ssh
|
package gitbucket.core.ssh
|
||||||
|
|
||||||
import gitbucket.core.service.SystemSettingsService.SshAddress
|
import gitbucket.core.service.SystemSettingsService.SshAddress
|
||||||
import org.apache.sshd.common.Factory
|
import org.apache.sshd.server.channel.ChannelSession
|
||||||
import org.apache.sshd.server.{Environment, ExitCallback}
|
import org.apache.sshd.server.{Environment, ExitCallback}
|
||||||
import org.apache.sshd.server.command.Command
|
import org.apache.sshd.server.command.Command
|
||||||
import java.io.{OutputStream, InputStream}
|
import org.apache.sshd.server.shell.ShellFactory
|
||||||
|
|
||||||
|
import java.io.{InputStream, OutputStream}
|
||||||
import org.eclipse.jgit.lib.Constants
|
import org.eclipse.jgit.lib.Constants
|
||||||
|
|
||||||
class NoShell(sshAddress: SshAddress) extends Factory[Command] {
|
class NoShell(sshAddress: SshAddress) extends ShellFactory {
|
||||||
override def create(): Command = new Command() {
|
override def createShell(channel: ChannelSession): Command = new Command() {
|
||||||
private var in: InputStream = null
|
private var in: InputStream = null
|
||||||
private var out: OutputStream = null
|
private var out: OutputStream = null
|
||||||
private var err: OutputStream = null
|
private var err: OutputStream = null
|
||||||
private var callback: ExitCallback = null
|
private var callback: ExitCallback = null
|
||||||
|
|
||||||
override def start(env: Environment): Unit = {
|
override def start(channel: ChannelSession, env: Environment): Unit = {
|
||||||
|
val placeholderAddress = sshAddress.getUrl("OWNER", "REPOSITORY_NAME")
|
||||||
val message =
|
val message =
|
||||||
"""
|
"""
|
||||||
| Welcome to
|
| Welcome to
|
||||||
@@ -30,8 +33,8 @@ class NoShell(sshAddress: SshAddress) extends Factory[Command] {
|
|||||||
|
|
|
|
||||||
| Please use:
|
| Please use:
|
||||||
|
|
|
|
||||||
| git clone ssh://%s@%s:%d/OWNER/REPOSITORY_NAME.git
|
| git clone %s
|
||||||
""".stripMargin.format(sshAddress.genericUser, sshAddress.host, sshAddress.port).replace("\n", "\r\n") + "\r\n"
|
""".stripMargin.format(placeholderAddress).replace("\n", "\r\n") + "\r\n"
|
||||||
err.write(Constants.encode(message))
|
err.write(Constants.encode(message))
|
||||||
err.flush()
|
err.flush()
|
||||||
in.close()
|
in.close()
|
||||||
@@ -40,7 +43,7 @@ class NoShell(sshAddress: SshAddress) extends Factory[Command] {
|
|||||||
callback.onExit(127)
|
callback.onExit(127)
|
||||||
}
|
}
|
||||||
|
|
||||||
override def destroy(): Unit = {}
|
override def destroy(channel: ChannelSession): Unit = {}
|
||||||
|
|
||||||
override def setInputStream(in: InputStream): Unit = {
|
override def setInputStream(in: InputStream): Unit = {
|
||||||
this.in = in
|
this.in = in
|
||||||
|
|||||||
@@ -8,13 +8,13 @@ import gitbucket.core.model.Profile.profile.blockingApi._
|
|||||||
import gitbucket.core.ssh.PublicKeyAuthenticator.AuthType
|
import gitbucket.core.ssh.PublicKeyAuthenticator.AuthType
|
||||||
import org.apache.sshd.server.auth.pubkey.PublickeyAuthenticator
|
import org.apache.sshd.server.auth.pubkey.PublickeyAuthenticator
|
||||||
import org.apache.sshd.server.session.ServerSession
|
import org.apache.sshd.server.session.ServerSession
|
||||||
import org.apache.sshd.common.AttributeStore
|
import org.apache.sshd.common.AttributeRepository
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
|
|
||||||
object PublicKeyAuthenticator {
|
object PublicKeyAuthenticator {
|
||||||
|
|
||||||
// put in the ServerSession here to be read by GitCommand later
|
// put in the ServerSession here to be read by GitCommand later
|
||||||
private val authTypeSessionKey = new AttributeStore.AttributeKey[AuthType]
|
private val authTypeSessionKey = new AttributeRepository.AttributeKey[AuthType]
|
||||||
|
|
||||||
def putAuthType(serverSession: ServerSession, authType: AuthType): Unit =
|
def putAuthType(serverSession: ServerSession, authType: AuthType): Unit =
|
||||||
serverSession.setAttribute(authTypeSessionKey, authType)
|
serverSession.setAttribute(authTypeSessionKey, authType)
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
package gitbucket.core.ssh
|
package gitbucket.core.ssh
|
||||||
|
|
||||||
import java.io.File
|
import java.util.concurrent.atomic.AtomicReference
|
||||||
import java.util.concurrent.atomic.AtomicBoolean
|
|
||||||
import javax.servlet.{ServletContextEvent, ServletContextListener}
|
import javax.servlet.{ServletContextEvent, ServletContextListener}
|
||||||
|
|
||||||
import gitbucket.core.service.SystemSettingsService
|
import gitbucket.core.service.SystemSettingsService
|
||||||
import gitbucket.core.service.SystemSettingsService.SshAddress
|
import gitbucket.core.service.SystemSettingsService.SshAddress
|
||||||
import gitbucket.core.util.Directory
|
import gitbucket.core.util.Directory
|
||||||
@@ -12,40 +10,48 @@ import org.slf4j.LoggerFactory
|
|||||||
|
|
||||||
object SshServer {
|
object SshServer {
|
||||||
private val logger = LoggerFactory.getLogger(SshServer.getClass)
|
private val logger = LoggerFactory.getLogger(SshServer.getClass)
|
||||||
private val server = org.apache.sshd.server.SshServer.setUpDefaultServer()
|
private val server = new AtomicReference[org.apache.sshd.server.SshServer](null)
|
||||||
private val active = new AtomicBoolean(false)
|
|
||||||
|
|
||||||
private def configure(sshAddress: SshAddress, baseUrl: String) = {
|
private def configure(
|
||||||
server.setPort(sshAddress.port)
|
bindAddress: SshAddress,
|
||||||
|
publicAddress: SshAddress,
|
||||||
|
baseUrl: String
|
||||||
|
): org.apache.sshd.server.SshServer = {
|
||||||
|
val server = org.apache.sshd.server.SshServer.setUpDefaultServer()
|
||||||
|
server.setPort(bindAddress.port)
|
||||||
val provider = new SimpleGeneratorHostKeyProvider(
|
val provider = new SimpleGeneratorHostKeyProvider(
|
||||||
java.nio.file.Paths.get(s"${Directory.GitBucketHome}/gitbucket.ser")
|
java.nio.file.Paths.get(s"${Directory.GitBucketHome}/gitbucket.ser")
|
||||||
)
|
)
|
||||||
provider.setAlgorithm("RSA")
|
provider.setAlgorithm("RSA")
|
||||||
provider.setOverwriteAllowed(false)
|
provider.setOverwriteAllowed(false)
|
||||||
server.setKeyPairProvider(provider)
|
server.setKeyPairProvider(provider)
|
||||||
server.setPublickeyAuthenticator(new PublicKeyAuthenticator(sshAddress.genericUser))
|
server.setPublickeyAuthenticator(new PublicKeyAuthenticator(bindAddress.genericUser))
|
||||||
server.setCommandFactory(
|
server.setCommandFactory(
|
||||||
new GitCommandFactory(baseUrl, Some(s"${sshAddress.genericUser}@${sshAddress.host}:${sshAddress.port}"))
|
new GitCommandFactory(baseUrl, publicAddress)
|
||||||
)
|
)
|
||||||
server.setShellFactory(new NoShell(sshAddress))
|
server.setShellFactory(new NoShell(publicAddress))
|
||||||
|
server
|
||||||
}
|
}
|
||||||
|
|
||||||
def start(sshAddress: SshAddress, baseUrl: String) = {
|
def start(bindAddress: SshAddress, publicAddress: SshAddress, baseUrl: String): Unit = {
|
||||||
if (active.compareAndSet(false, true)) {
|
this.server.synchronized {
|
||||||
configure(sshAddress, baseUrl)
|
val server = configure(bindAddress, publicAddress, baseUrl)
|
||||||
server.start()
|
if (this.server.compareAndSet(null, server)) {
|
||||||
logger.info(s"Start SSH Server Listen on ${server.getPort}")
|
server.start()
|
||||||
|
logger.info(s"Start SSH Server Listen on ${server.getPort}")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def stop() = {
|
def stop(): Unit = {
|
||||||
if (active.compareAndSet(true, false)) {
|
this.server.synchronized {
|
||||||
server.stop(true)
|
val server = this.server.getAndSet(null)
|
||||||
logger.info("SSH Server is stopped.")
|
if (server != null) {
|
||||||
|
server.stop()
|
||||||
|
logger.info("SSH Server is stopped.")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def isActive = active.get
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -60,13 +66,14 @@ class SshServerListener extends ServletContextListener with SystemSettingsServic
|
|||||||
|
|
||||||
override def contextInitialized(sce: ServletContextEvent): Unit = {
|
override def contextInitialized(sce: ServletContextEvent): Unit = {
|
||||||
val settings = loadSystemSettings()
|
val settings = loadSystemSettings()
|
||||||
if (settings.sshAddress.isDefined && settings.baseUrl.isEmpty) {
|
if (settings.sshBindAddress.isDefined && settings.baseUrl.isEmpty) {
|
||||||
logger.error("Could not start SshServer because the baseUrl is not configured.")
|
logger.error("Could not start SshServer because the baseUrl is not configured.")
|
||||||
}
|
}
|
||||||
for {
|
for {
|
||||||
sshAddress <- settings.sshAddress
|
bindAddress <- settings.sshBindAddress
|
||||||
|
publicAddress <- settings.sshPublicAddress
|
||||||
baseUrl <- settings.baseUrl
|
baseUrl <- settings.baseUrl
|
||||||
} SshServer.start(sshAddress, baseUrl)
|
} SshServer.start(bindAddress, publicAddress, baseUrl)
|
||||||
}
|
}
|
||||||
|
|
||||||
override def contextDestroyed(sce: ServletContextEvent): Unit = {
|
override def contextDestroyed(sce: ServletContextEvent): Unit = {
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import gitbucket.core.service.{AccountService, RepositoryService}
|
|||||||
import gitbucket.core.model.Role
|
import gitbucket.core.model.Role
|
||||||
import RepositoryService.RepositoryInfo
|
import RepositoryService.RepositoryInfo
|
||||||
import Implicits._
|
import Implicits._
|
||||||
import SyntaxSugars._
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Allows only oneself and administrators.
|
* Allows only oneself and administrators.
|
||||||
@@ -39,7 +38,7 @@ trait OwnerAuthenticator { self: ControllerBase with RepositoryService with Acco
|
|||||||
case Some(x) if (repository.owner == x.userName) => action(repository)
|
case Some(x) if (repository.owner == x.userName) => action(repository)
|
||||||
// TODO Repository management is allowed for only group managers?
|
// TODO Repository management is allowed for only group managers?
|
||||||
case Some(x) if (getGroupMembers(repository.owner).exists { m =>
|
case Some(x) if (getGroupMembers(repository.owner).exists { m =>
|
||||||
m.userName == x.userName && m.isManager == true
|
m.userName == x.userName && m.isManager
|
||||||
}) =>
|
}) =>
|
||||||
action(repository)
|
action(repository)
|
||||||
case Some(x) if (getCollaboratorUserNames(userName, repoName, Seq(Role.ADMIN)).contains(x.userName)) =>
|
case Some(x) if (getCollaboratorUserNames(userName, repoName, Seq(Role.ADMIN)).contains(x.userName)) =>
|
||||||
@@ -113,13 +112,10 @@ trait ReadableUsersAuthenticator { self: ControllerBase with RepositoryService w
|
|||||||
val userName = params("owner")
|
val userName = params("owner")
|
||||||
val repoName = params("repository")
|
val repoName = params("repository")
|
||||||
getRepository(userName, repoName).map { repository =>
|
getRepository(userName, repoName).map { repository =>
|
||||||
context.loginAccount match {
|
if (isReadable(repository.repository, context.loginAccount) || !repository.repository.isPrivate) {
|
||||||
case Some(x) if (x.isAdmin) => action(repository)
|
action(repository)
|
||||||
case Some(x) if (!repository.repository.isPrivate) => action(repository)
|
} else {
|
||||||
case Some(x) if (userName == x.userName) => action(repository)
|
Unauthorized()
|
||||||
case Some(x) if (getGroupMembers(repository.owner).exists(_.userName == x.userName)) => action(repository)
|
|
||||||
case Some(x) if (getCollaboratorUserNames(userName, repoName).contains(x.userName)) => action(repository)
|
|
||||||
case _ => Unauthorized()
|
|
||||||
}
|
}
|
||||||
} getOrElse NotFound()
|
} getOrElse NotFound()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
package gitbucket.core.util
|
package gitbucket.core.util
|
||||||
|
|
||||||
import gitbucket.core.util.SyntaxSugars.defining
|
|
||||||
|
|
||||||
import scala.reflect.ClassTag
|
import scala.reflect.ClassTag
|
||||||
|
|
||||||
object ConfigUtil {
|
object ConfigUtil {
|
||||||
@@ -30,12 +28,12 @@ object ConfigUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def convertType[A: ClassTag](value: String): A =
|
def convertType[A: ClassTag](value: String): A = {
|
||||||
defining(implicitly[ClassTag[A]].runtimeClass) { c =>
|
val c = implicitly[ClassTag[A]].runtimeClass
|
||||||
if (c == classOf[Boolean]) value.toBoolean
|
if (c == classOf[Boolean]) value.toBoolean
|
||||||
else if (c == classOf[Long]) value.toLong
|
else if (c == classOf[Long]) value.toLong
|
||||||
else if (c == classOf[Int]) value.toInt
|
else if (c == classOf[Int]) value.toInt
|
||||||
else value
|
else value
|
||||||
}.asInstanceOf[A]
|
}.asInstanceOf[A]
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ object EditorConfigUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private def getRevTree: RevTree = {
|
private def getRevTree: RevTree = {
|
||||||
Using.resource(repo.newObjectReader()) { reader: ObjectReader =>
|
Using.resource(repo.newObjectReader()) { (reader: ObjectReader) =>
|
||||||
val revWalk = new RevWalk(reader)
|
val revWalk = new RevWalk(reader)
|
||||||
val id = repo.resolve(revStr)
|
val id = repo.resolve(revStr)
|
||||||
val commit = revWalk.parseCommit(id)
|
val commit = revWalk.parseCommit(id)
|
||||||
@@ -37,7 +37,7 @@ object EditorConfigUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override def exists(): Boolean = {
|
override def exists(): Boolean = {
|
||||||
Using.resource(repo.newObjectReader()) { reader: ObjectReader =>
|
Using.resource(repo.newObjectReader()) { (reader: ObjectReader) =>
|
||||||
try {
|
try {
|
||||||
val treeWalk = Option(TreeWalk.forPath(reader, removeInitialSlash(path), getRevTree))
|
val treeWalk = Option(TreeWalk.forPath(reader, removeInitialSlash(path), getRevTree))
|
||||||
treeWalk.isDefined
|
treeWalk.isDefined
|
||||||
@@ -52,7 +52,7 @@ object EditorConfigUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override def getParent: ResourcePath = {
|
override def getParent: ResourcePath = {
|
||||||
Option(path.getParentPath).map { new JGitResourcePath(repo, revStr, _) }.getOrElse(null)
|
Option(path.getParentPath).map { new JGitResourcePath(repo, revStr, _) }.orNull
|
||||||
}
|
}
|
||||||
|
|
||||||
override def openRandomReader(): Resource.RandomReader = {
|
override def openRandomReader(): Resource.RandomReader = {
|
||||||
@@ -60,7 +60,7 @@ object EditorConfigUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override def openReader(): Reader = {
|
override def openReader(): Reader = {
|
||||||
Using.resource(repo.newObjectReader) { reader: ObjectReader =>
|
Using.resource(repo.newObjectReader) { (reader: ObjectReader) =>
|
||||||
val treeWalk = TreeWalk.forPath(reader, removeInitialSlash(path), getRevTree)
|
val treeWalk = TreeWalk.forPath(reader, removeInitialSlash(path), getRevTree)
|
||||||
new InputStreamReader(reader.open(treeWalk.getObjectId(0)).openStream, StandardCharsets.UTF_8)
|
new InputStreamReader(reader.open(treeWalk.getObjectId(0)).openStream, StandardCharsets.UTF_8)
|
||||||
}
|
}
|
||||||
@@ -70,7 +70,7 @@ object EditorConfigUtil {
|
|||||||
private class JGitResourcePath(repo: Repository, revStr: String, path: Ec4jPath) extends ResourcePath {
|
private class JGitResourcePath(repo: Repository, revStr: String, path: Ec4jPath) extends ResourcePath {
|
||||||
|
|
||||||
override def getParent: ResourcePath = {
|
override def getParent: ResourcePath = {
|
||||||
Option(path.getParentPath).map { new JGitResourcePath(repo, revStr, _) }.getOrElse(null)
|
Option(path.getParentPath).map { new JGitResourcePath(repo, revStr, _) }.orNull
|
||||||
}
|
}
|
||||||
|
|
||||||
override def getPath: Ec4jPath = {
|
override def getPath: Ec4jPath = {
|
||||||
|
|||||||
@@ -3,36 +3,39 @@ package gitbucket.core.util
|
|||||||
import org.apache.commons.io.FileUtils
|
import org.apache.commons.io.FileUtils
|
||||||
import org.apache.tika.Tika
|
import org.apache.tika.Tika
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import SyntaxSugars._
|
|
||||||
import scala.util.Random
|
import scala.util.Random
|
||||||
|
|
||||||
object FileUtil {
|
object FileUtil {
|
||||||
|
|
||||||
def getMimeType(name: String): String =
|
def getMimeType(name: String): String = {
|
||||||
defining(new Tika()) { tika =>
|
val tika = new Tika()
|
||||||
tika.detect(name) match {
|
tika.detect(name) match {
|
||||||
case null => "application/octet-stream"
|
case null => "application/octet-stream"
|
||||||
case mimeType => mimeType
|
case mimeType => mimeType
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
def getMimeType(name: String, bytes: Array[Byte]): String = {
|
def getMimeType(name: String, bytes: Array[Byte]): String = {
|
||||||
defining(getMimeType(name)) { mimeType =>
|
val mimeType = getMimeType(name)
|
||||||
if (mimeType == "application/octet-stream" && isText(bytes)) {
|
if (mimeType == "application/octet-stream" && isText(bytes)) {
|
||||||
"text/plain"
|
"text/plain"
|
||||||
} else {
|
} else {
|
||||||
mimeType
|
mimeType
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def getSafeMimeType(name: String): String = {
|
def getSafeMimeType(name: String, safeMode: Boolean = true): String = {
|
||||||
getMimeType(name)
|
val mimeType = getMimeType(name)
|
||||||
.replace("text/html", "text/plain")
|
.replace("text/html", "text/plain")
|
||||||
.replace("image/svg+xml", "text/plain; charset=UTF-8")
|
|
||||||
|
if (safeMode) {
|
||||||
|
mimeType.replace("image/svg+xml", "text/plain; charset=UTF-8")
|
||||||
|
} else {
|
||||||
|
mimeType
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def isImage(name: String): Boolean = getSafeMimeType(name).startsWith("image/")
|
def isImage(name: String, safeMode: Boolean = true): Boolean = getSafeMimeType(name, safeMode).startsWith("image/")
|
||||||
|
|
||||||
def isLarge(size: Long): Boolean = (size > 1024 * 1000)
|
def isLarge(size: Long): Boolean = (size > 1024 * 1000)
|
||||||
|
|
||||||
@@ -92,4 +95,14 @@ object FileUtil {
|
|||||||
name
|
name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete given folder recursively.
|
||||||
|
*/
|
||||||
|
def deleteRecursively(f: File): Boolean = {
|
||||||
|
if (f.isDirectory) f.listFiles match {
|
||||||
|
case files: Array[File] => files.foreach(deleteRecursively)
|
||||||
|
case null =>
|
||||||
|
}
|
||||||
|
f.delete()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
package gitbucket.core.util
|
package gitbucket.core.util
|
||||||
|
|
||||||
import java.net.{InetAddress, URL}
|
import java.net.InetAddress
|
||||||
|
|
||||||
import gitbucket.core.service.SystemSettingsService
|
import gitbucket.core.service.SystemSettingsService
|
||||||
import org.apache.commons.net.util.SubnetUtils
|
import org.apache.commons.net.util.SubnetUtils
|
||||||
|
|||||||
@@ -22,9 +22,7 @@ object Implicits {
|
|||||||
implicit def request2Session(implicit request: HttpServletRequest): JdbcBackend#Session = Database.getSession(request)
|
implicit def request2Session(implicit request: HttpServletRequest): JdbcBackend#Session = Database.getSession(request)
|
||||||
|
|
||||||
implicit def context2ApiJsonFormatContext(implicit context: Context): JsonFormat.Context =
|
implicit def context2ApiJsonFormatContext(implicit context: Context): JsonFormat.Context =
|
||||||
JsonFormat.Context(context.baseUrl, context.settings.sshAddress.map { x =>
|
JsonFormat.Context(context.baseUrl, context.settings.sshUrl)
|
||||||
s"${x.genericUser}@${x.host}:${x.port}"
|
|
||||||
})
|
|
||||||
|
|
||||||
implicit class RichSeq[A](private val seq: Seq[A]) extends AnyVal {
|
implicit class RichSeq[A](private val seq: Seq[A]) extends AnyVal {
|
||||||
|
|
||||||
|
|||||||
@@ -83,7 +83,9 @@ object JDBCUtil {
|
|||||||
}
|
}
|
||||||
if (c == ';' && !stringLiteral) {
|
if (c == ';' && !stringLiteral) {
|
||||||
val sql = new String(out.toByteArray, "UTF-8")
|
val sql = new String(out.toByteArray, "UTF-8")
|
||||||
conn.update(sql.trim)
|
if (sql != null && !sql.isEmpty()) {
|
||||||
|
conn.update(sql.trim)
|
||||||
|
}
|
||||||
out = new ByteArrayOutputStream()
|
out = new ByteArrayOutputStream()
|
||||||
} else {
|
} else {
|
||||||
out.write(c)
|
out.write(c)
|
||||||
@@ -225,7 +227,7 @@ object JDBCUtil {
|
|||||||
if (noPreds.isEmpty) {
|
if (noPreds.isEmpty) {
|
||||||
if (hasPreds.isEmpty) done else sys.error(hasPreds.toString)
|
if (hasPreds.isEmpty) done else sys.error(hasPreds.toString)
|
||||||
} else {
|
} else {
|
||||||
val found = noPreds.map { _._1 }
|
val found = noPreds.keys
|
||||||
tsort(hasPreds.map { case (k, v) => (k, v -- found) }, done ++ found)
|
tsort(hasPreds.map { case (k, v) => (k, v -- found) }, done ++ found)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import gitbucket.core.service.RepositoryService
|
|||||||
import org.eclipse.jgit.api.Git
|
import org.eclipse.jgit.api.Git
|
||||||
import Directory._
|
import Directory._
|
||||||
import StringUtil._
|
import StringUtil._
|
||||||
import SyntaxSugars._
|
|
||||||
|
|
||||||
import scala.annotation.tailrec
|
import scala.annotation.tailrec
|
||||||
import scala.jdk.CollectionConverters._
|
import scala.jdk.CollectionConverters._
|
||||||
@@ -38,9 +37,8 @@ object JGitUtil {
|
|||||||
|
|
||||||
private val logger = LoggerFactory.getLogger(JGitUtil.getClass)
|
private val logger = LoggerFactory.getLogger(JGitUtil.getClass)
|
||||||
|
|
||||||
implicit val objectDatabaseReleasable = new Releasable[ObjectDatabase] {
|
implicit val objectDatabaseReleasable: Releasable[ObjectDatabase] =
|
||||||
override def release(resource: ObjectDatabase): Unit = resource.close()
|
_.close()
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The repository data.
|
* The repository data.
|
||||||
@@ -183,7 +181,8 @@ object JGitUtil {
|
|||||||
|
|
||||||
val summary = getSummaryMessage(fullMessage, shortMessage)
|
val summary = getSummaryMessage(fullMessage, shortMessage)
|
||||||
|
|
||||||
val description = defining(fullMessage.trim.indexOf('\n')) { i =>
|
val description = {
|
||||||
|
val i = fullMessage.trim.indexOf('\n')
|
||||||
if (i >= 0) {
|
if (i >= 0) {
|
||||||
Some(fullMessage.trim.substring(i).trim)
|
Some(fullMessage.trim.substring(i).trim)
|
||||||
} else None
|
} else None
|
||||||
@@ -229,10 +228,11 @@ object JGitUtil {
|
|||||||
*
|
*
|
||||||
* @param name the tag name
|
* @param name the tag name
|
||||||
* @param time the tagged date
|
* @param time the tagged date
|
||||||
* @param id the commit id
|
* @param commitId the commit id
|
||||||
* @param message the message of the tagged commit
|
* @param message the message of the tagged commit
|
||||||
|
* @param objectId the tag object id
|
||||||
*/
|
*/
|
||||||
case class TagInfo(name: String, time: Date, id: String, message: String)
|
case class TagInfo(name: String, time: Date, commitId: String, message: String, objectId: String)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The submodule data
|
* The submodule data
|
||||||
@@ -348,7 +348,8 @@ object JGitUtil {
|
|||||||
ref.getName.stripPrefix("refs/tags/"),
|
ref.getName.stripPrefix("refs/tags/"),
|
||||||
revCommit.getCommitterIdent.getWhen,
|
revCommit.getCommitterIdent.getWhen,
|
||||||
revCommit.getName,
|
revCommit.getName,
|
||||||
revCommit.getShortMessage
|
revCommit.getShortMessage,
|
||||||
|
ref.getObjectId.getName
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
} catch {
|
} catch {
|
||||||
@@ -383,7 +384,7 @@ object JGitUtil {
|
|||||||
path: String = ".",
|
path: String = ".",
|
||||||
baseUrl: Option[String] = None,
|
baseUrl: Option[String] = None,
|
||||||
commitCount: Int = 0,
|
commitCount: Int = 0,
|
||||||
maxFiles: Int = 100
|
maxFiles: Int = 5
|
||||||
): List[FileInfo] = {
|
): List[FileInfo] = {
|
||||||
Using.resource(new RevWalk(git.getRepository)) { revWalk =>
|
Using.resource(new RevWalk(git.getRepository)) { revWalk =>
|
||||||
val objectId = git.getRepository.resolve(revision)
|
val objectId = git.getRepository.resolve(revision)
|
||||||
@@ -514,11 +515,9 @@ object JGitUtil {
|
|||||||
* Returns the first line of the commit message.
|
* Returns the first line of the commit message.
|
||||||
*/
|
*/
|
||||||
private def getSummaryMessage(fullMessage: String, shortMessage: String): String = {
|
private def getSummaryMessage(fullMessage: String, shortMessage: String): String = {
|
||||||
defining(fullMessage.trim.indexOf('\n')) { i =>
|
val i = fullMessage.trim.indexOf('\n')
|
||||||
defining(if (i >= 0) fullMessage.trim.substring(0, i).trim else fullMessage) { firstLine =>
|
val firstLine = if (i >= 0) fullMessage.trim.substring(0, i).trim else fullMessage
|
||||||
if (firstLine.length > shortMessage.length) shortMessage else firstLine
|
if (firstLine.length > shortMessage.length) shortMessage else firstLine
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -592,16 +591,15 @@ object JGitUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Using.resource(new RevWalk(git.getRepository)) { revWalk =>
|
Using.resource(new RevWalk(git.getRepository)) { revWalk =>
|
||||||
defining(git.getRepository.resolve(revision)) { objectId =>
|
val objectId = git.getRepository.resolve(revision)
|
||||||
if (objectId == null) {
|
if (objectId == null) {
|
||||||
Left(s"${revision} can't be resolved.")
|
Left(s"${revision} can't be resolved.")
|
||||||
} else {
|
} else {
|
||||||
revWalk.markStart(revWalk.parseCommit(objectId))
|
revWalk.markStart(revWalk.parseCommit(objectId))
|
||||||
if (path.nonEmpty) {
|
if (path.nonEmpty) {
|
||||||
revWalk.setTreeFilter(AndTreeFilter.create(PathFilter.create(path), TreeFilter.ANY_DIFF))
|
revWalk.setTreeFilter(AndTreeFilter.create(PathFilter.create(path), TreeFilter.ANY_DIFF))
|
||||||
}
|
|
||||||
Right(getCommitLog(revWalk.iterator, 0, Nil))
|
|
||||||
}
|
}
|
||||||
|
Right(getCommitLog(revWalk.iterator, 0, Nil))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -662,9 +660,13 @@ object JGitUtil {
|
|||||||
*/
|
*/
|
||||||
def getLatestCommitFromPaths(git: Git, paths: List[String], revision: String): Map[String, RevCommit] = {
|
def getLatestCommitFromPaths(git: Git, paths: List[String], revision: String): Map[String, RevCommit] = {
|
||||||
val start = getRevCommitFromId(git, git.getRepository.resolve(revision))
|
val start = getRevCommitFromId(git, git.getRepository.resolve(revision))
|
||||||
paths.map { path =>
|
paths.flatMap { path =>
|
||||||
val commit = git.log.add(start).addPath(path).setMaxCount(1).call.iterator.next
|
val commit = git.log.add(start).addPath(path).setMaxCount(1).call.iterator.next
|
||||||
(path, commit)
|
if (commit == null) {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some((path, commit))
|
||||||
|
}
|
||||||
}.toMap
|
}.toMap
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -675,11 +677,10 @@ object JGitUtil {
|
|||||||
df.setDiffComparator(RawTextComparator.DEFAULT)
|
df.setDiffComparator(RawTextComparator.DEFAULT)
|
||||||
df.setDetectRenames(true)
|
df.setDetectRenames(true)
|
||||||
getDiffEntries(git, from, to)
|
getDiffEntries(git, from, to)
|
||||||
.map { entry =>
|
.foreach { entry =>
|
||||||
df.format(entry)
|
df.format(entry)
|
||||||
new String(out.toByteArray, "UTF-8")
|
|
||||||
}
|
}
|
||||||
.mkString("\n")
|
new String(out.toByteArray, "UTF-8")
|
||||||
}
|
}
|
||||||
|
|
||||||
private def getDiffEntries(git: Git, from: Option[String], to: String): Seq[DiffEntry] = {
|
private def getDiffEntries(git: Git, from: Option[String], to: String): Seq[DiffEntry] = {
|
||||||
@@ -804,22 +805,21 @@ object JGitUtil {
|
|||||||
*/
|
*/
|
||||||
def getBranchesOfCommit(git: Git, commitId: String): List[String] =
|
def getBranchesOfCommit(git: Git, commitId: String): List[String] =
|
||||||
Using.resource(new RevWalk(git.getRepository)) { revWalk =>
|
Using.resource(new RevWalk(git.getRepository)) { revWalk =>
|
||||||
defining(revWalk.parseCommit(git.getRepository.resolve(commitId + "^0"))) { commit =>
|
val commit = revWalk.parseCommit(git.getRepository.resolve(commitId + "^0"))
|
||||||
git.getRepository.getRefDatabase
|
git.getRepository.getRefDatabase
|
||||||
.getRefsByPrefix(Constants.R_HEADS)
|
.getRefsByPrefix(Constants.R_HEADS)
|
||||||
.asScala
|
.asScala
|
||||||
.filter { e =>
|
.filter { e =>
|
||||||
(revWalk.isMergedInto(
|
(revWalk.isMergedInto(
|
||||||
commit,
|
commit,
|
||||||
revWalk.parseCommit(e.getObjectId)
|
revWalk.parseCommit(e.getObjectId)
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
.map { e =>
|
.map { e =>
|
||||||
e.getName.substring(Constants.R_HEADS.length)
|
e.getName.substring(Constants.R_HEADS.length)
|
||||||
}
|
}
|
||||||
.toList
|
.toList
|
||||||
.sorted
|
.sorted
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -847,23 +847,22 @@ object JGitUtil {
|
|||||||
*/
|
*/
|
||||||
def getTagsOfCommit(git: Git, commitId: String): List[String] =
|
def getTagsOfCommit(git: Git, commitId: String): List[String] =
|
||||||
Using.resource(new RevWalk(git.getRepository)) { revWalk =>
|
Using.resource(new RevWalk(git.getRepository)) { revWalk =>
|
||||||
defining(revWalk.parseCommit(git.getRepository.resolve(commitId + "^0"))) { commit =>
|
val commit = revWalk.parseCommit(git.getRepository.resolve(commitId + "^0"))
|
||||||
git.getRepository.getRefDatabase
|
git.getRepository.getRefDatabase
|
||||||
.getRefsByPrefix(Constants.R_TAGS)
|
.getRefsByPrefix(Constants.R_TAGS)
|
||||||
.asScala
|
.asScala
|
||||||
.filter { e =>
|
.filter { e =>
|
||||||
(revWalk.isMergedInto(
|
(revWalk.isMergedInto(
|
||||||
commit,
|
commit,
|
||||||
revWalk.parseCommit(e.getObjectId)
|
revWalk.parseCommit(e.getObjectId)
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
.map { e =>
|
.map { e =>
|
||||||
e.getName.substring(Constants.R_TAGS.length)
|
e.getName.substring(Constants.R_TAGS.length)
|
||||||
}
|
}
|
||||||
.toList
|
.toList
|
||||||
.sorted
|
.sorted
|
||||||
.reverse
|
.reverse
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def initRepository(dir: java.io.File): Unit =
|
def initRepository(dir: java.io.File): Unit =
|
||||||
@@ -879,11 +878,11 @@ object JGitUtil {
|
|||||||
|
|
||||||
def isEmpty(git: Git): Boolean = git.getRepository.resolve(Constants.HEAD) == null
|
def isEmpty(git: Git): Boolean = git.getRepository.resolve(Constants.HEAD) == null
|
||||||
|
|
||||||
private def setReceivePack(repository: org.eclipse.jgit.lib.Repository): Unit =
|
private def setReceivePack(repository: org.eclipse.jgit.lib.Repository): Unit = {
|
||||||
defining(repository.getConfig) { config =>
|
val config = repository.getConfig
|
||||||
config.setBoolean("http", null, "receivepack", true)
|
config.setBoolean("http", null, "receivepack", true)
|
||||||
config.save
|
config.save
|
||||||
}
|
}
|
||||||
|
|
||||||
def getDefaultBranch(
|
def getDefaultBranch(
|
||||||
git: Git,
|
git: Git,
|
||||||
@@ -912,10 +911,10 @@ object JGitUtil {
|
|||||||
}
|
}
|
||||||
Right("Tag added.")
|
Right("Tag added.")
|
||||||
} catch {
|
} catch {
|
||||||
case e: ConcurrentRefUpdateException => Left("Sorry, some error occurs.")
|
case _: ConcurrentRefUpdateException => Left("Sorry, some error occurs.")
|
||||||
case e: InvalidTagNameException => Left("Sorry, that name is invalid.")
|
case _: InvalidTagNameException => Left("Sorry, that name is invalid.")
|
||||||
case e: NoHeadException => Left("Sorry, this repo doesn't have HEAD reference")
|
case _: NoHeadException => Left("Sorry, this repo doesn't have HEAD reference")
|
||||||
case e: GitAPIException => Left("Sorry, some Git operation error occurs.")
|
case _: GitAPIException => Left("Sorry, some Git operation error occurs.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -924,7 +923,7 @@ object JGitUtil {
|
|||||||
git.branchCreate().setStartPoint(fromBranch).setName(newBranch).call()
|
git.branchCreate().setStartPoint(fromBranch).setName(newBranch).call()
|
||||||
Right("Branch created.")
|
Right("Branch created.")
|
||||||
} catch {
|
} catch {
|
||||||
case e: RefAlreadyExistsException => Left("Sorry, that branch already exists.")
|
case _: RefAlreadyExistsException => Left("Sorry, that branch already exists.")
|
||||||
// JGitInternalException occurs when new branch name is 'a' and the branch whose name is 'a/*' exists.
|
// JGitInternalException occurs when new branch name is 'a' and the branch whose name is 'a/*' exists.
|
||||||
case _: InvalidRefNameException | _: JGitInternalException => Left("Sorry, that name is invalid.")
|
case _: InvalidRefNameException | _: JGitInternalException => Left("Sorry, that name is invalid.")
|
||||||
}
|
}
|
||||||
@@ -1052,13 +1051,13 @@ object JGitUtil {
|
|||||||
!loader.isLarge && new String(loader.getBytes(), "UTF-8").startsWith("version https://git-lfs.github.com/spec/v1")
|
!loader.isLarge && new String(loader.getBytes(), "UTF-8").startsWith("version https://git-lfs.github.com/spec/v1")
|
||||||
}
|
}
|
||||||
|
|
||||||
def getContentInfo(git: Git, path: String, objectId: ObjectId): ContentInfo = {
|
def getContentInfo(git: Git, path: String, objectId: ObjectId, safeMode: Boolean): ContentInfo = {
|
||||||
// Viewer
|
// Viewer
|
||||||
Using.resource(git.getRepository.getObjectDatabase) { db =>
|
Using.resource(git.getRepository.getObjectDatabase) { db =>
|
||||||
val loader = db.open(objectId)
|
val loader = db.open(objectId)
|
||||||
val isLfs = isLfsPointer(loader)
|
val isLfs = isLfsPointer(loader)
|
||||||
val large = FileUtil.isLarge(loader.getSize)
|
val large = FileUtil.isLarge(loader.getSize)
|
||||||
val viewer = if (FileUtil.isImage(path)) "image" else if (large) "large" else "other"
|
val viewer = if (FileUtil.isImage(path, safeMode)) "image" else if (large) "large" else "other"
|
||||||
val bytes = if (viewer == "other") JGitUtil.getContentFromId(git, objectId, false) else None
|
val bytes = if (viewer == "other") JGitUtil.getContentFromId(git, objectId, false) else None
|
||||||
val size = Some(getContentSize(loader))
|
val size = Some(getContentSize(loader))
|
||||||
|
|
||||||
@@ -1101,7 +1100,7 @@ object JGitUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
case e: MissingObjectException => None
|
case _: MissingObjectException => None
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1118,7 +1117,7 @@ object JGitUtil {
|
|||||||
Some(f(db.open(id)))
|
Some(f(db.open(id)))
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
case e: MissingObjectException => None
|
case _: MissingObjectException => None
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1162,12 +1161,12 @@ object JGitUtil {
|
|||||||
requestUserName: String,
|
requestUserName: String,
|
||||||
requestRepositoryName: String,
|
requestRepositoryName: String,
|
||||||
requestBranch: String
|
requestBranch: String
|
||||||
): String =
|
): String = {
|
||||||
defining(getAllCommitIds(oldGit)) { existIds =>
|
val existIds = getAllCommitIds(oldGit)
|
||||||
getCommitLogs(newGit, requestBranch, true) { commit =>
|
getCommitLogs(newGit, requestBranch, true) { commit =>
|
||||||
existIds.contains(commit.name) && getBranchesOfCommit(oldGit, commit.getName).contains(branch)
|
existIds.contains(commit.name) && getBranchesOfCommit(oldGit, commit.getName).contains(branch)
|
||||||
}.head.id
|
}.head.id
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch pull request contents into refs/pull/${issueId}/head and return (commitIdTo, commitIdFrom)
|
* Fetch pull request contents into refs/pull/${issueId}/head and return (commitIdTo, commitIdFrom)
|
||||||
@@ -1264,7 +1263,7 @@ object JGitUtil {
|
|||||||
val blame = blamer.call()
|
val blame = blamer.call()
|
||||||
var blameMap = Map[String, JGitUtil.BlameInfo]()
|
var blameMap = Map[String, JGitUtil.BlameInfo]()
|
||||||
var idLine = List[(String, Int)]()
|
var idLine = List[(String, Int)]()
|
||||||
0.to(blame.getResultContents().size() - 1).map { i =>
|
0.until(blame.getResultContents().size()).foreach { i =>
|
||||||
val c = blame.getSourceCommit(i)
|
val c = blame.getSourceCommit(i)
|
||||||
if (!blameMap.contains(c.name)) {
|
if (!blameMap.contains(c.name)) {
|
||||||
blameMap += c.name -> JGitUtil.BlameInfo(
|
blameMap += c.name -> JGitUtil.BlameInfo(
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package gitbucket.core.util
|
package gitbucket.core.util
|
||||||
|
|
||||||
import gitbucket.core.model.Account
|
import gitbucket.core.model.Account
|
||||||
import SyntaxSugars._
|
|
||||||
import gitbucket.core.service.SystemSettingsService
|
import gitbucket.core.service.SystemSettingsService
|
||||||
import gitbucket.core.service.SystemSettingsService.Ldap
|
import gitbucket.core.service.SystemSettingsService.Ldap
|
||||||
import com.novell.ldap._
|
import com.novell.ldap._
|
||||||
@@ -246,20 +245,18 @@ object LDAPUtil {
|
|||||||
userNameAttribute: String,
|
userNameAttribute: String,
|
||||||
userName: String,
|
userName: String,
|
||||||
mailAttribute: String
|
mailAttribute: String
|
||||||
): Option[String] =
|
): Option[String] = {
|
||||||
defining(
|
val results = conn.search(
|
||||||
conn.search(
|
userDN,
|
||||||
userDN,
|
LDAPConnection.SCOPE_BASE,
|
||||||
LDAPConnection.SCOPE_BASE,
|
userNameAttribute + "=" + userName,
|
||||||
userNameAttribute + "=" + userName,
|
Array[String](mailAttribute),
|
||||||
Array[String](mailAttribute),
|
false
|
||||||
false
|
)
|
||||||
)
|
if (results.hasMore) {
|
||||||
) { results =>
|
Option(results.next.getAttribute(mailAttribute)).map(_.getStringValue)
|
||||||
if (results.hasMore) {
|
} else None
|
||||||
Option(results.next.getAttribute(mailAttribute)).map(_.getStringValue)
|
}
|
||||||
} else None
|
|
||||||
}
|
|
||||||
|
|
||||||
private def findFullName(
|
private def findFullName(
|
||||||
conn: LDAPConnection,
|
conn: LDAPConnection,
|
||||||
@@ -267,20 +264,18 @@ object LDAPUtil {
|
|||||||
userNameAttribute: String,
|
userNameAttribute: String,
|
||||||
userName: String,
|
userName: String,
|
||||||
nameAttribute: String
|
nameAttribute: String
|
||||||
): Option[String] =
|
): Option[String] = {
|
||||||
defining(
|
val results = conn.search(
|
||||||
conn.search(
|
userDN,
|
||||||
userDN,
|
LDAPConnection.SCOPE_BASE,
|
||||||
LDAPConnection.SCOPE_BASE,
|
userNameAttribute + "=" + userName,
|
||||||
userNameAttribute + "=" + userName,
|
Array[String](nameAttribute),
|
||||||
Array[String](nameAttribute),
|
false
|
||||||
false
|
)
|
||||||
)
|
if (results.hasMore) {
|
||||||
) { results =>
|
Option(results.next.getAttribute(nameAttribute)).map(_.getStringValue)
|
||||||
if (results.hasMore) {
|
} else None
|
||||||
Option(results.next.getAttribute(nameAttribute)).map(_.getStringValue)
|
}
|
||||||
} else None
|
|
||||||
}
|
|
||||||
|
|
||||||
case class LDAPUserInfo(userName: String, fullName: String, mailAddress: String)
|
case class LDAPUserInfo(userName: String, fullName: String, mailAddress: String)
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package gitbucket.core.util
|
|||||||
|
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
import java.util.concurrent.locks.{ReentrantLock, Lock}
|
import java.util.concurrent.locks.{ReentrantLock, Lock}
|
||||||
import SyntaxSugars._
|
|
||||||
|
|
||||||
object LockUtil {
|
object LockUtil {
|
||||||
|
|
||||||
@@ -24,7 +23,8 @@ object LockUtil {
|
|||||||
/**
|
/**
|
||||||
* Synchronizes a given function which modifies the working copy of the wiki repository.
|
* Synchronizes a given function which modifies the working copy of the wiki repository.
|
||||||
*/
|
*/
|
||||||
def lock[T](key: String)(f: => T): T = defining(getLockObject(key)) { lock =>
|
def lock[T](key: String)(f: => T): T = {
|
||||||
|
val lock = getLockObject(key)
|
||||||
try {
|
try {
|
||||||
lock.lock()
|
lock.lock()
|
||||||
f
|
f
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ class Mailer(settings: SystemSettings) {
|
|||||||
htmlMsg: Option[String] = None,
|
htmlMsg: Option[String] = None,
|
||||||
loginAccount: Option[Account] = None
|
loginAccount: Option[Account] = None
|
||||||
): Option[HtmlEmail] = {
|
): Option[HtmlEmail] = {
|
||||||
if (settings.notification == true) {
|
if (settings.notification) {
|
||||||
settings.smtp.map { smtp =>
|
settings.smtp.map { smtp =>
|
||||||
val email = new HtmlEmail
|
val email = new HtmlEmail
|
||||||
email.setHostName(smtp.host)
|
email.setHostName(smtp.host)
|
||||||
@@ -51,7 +51,7 @@ class Mailer(settings: SystemSettings) {
|
|||||||
}
|
}
|
||||||
smtp.ssl.foreach { ssl =>
|
smtp.ssl.foreach { ssl =>
|
||||||
email.setSSLOnConnect(ssl)
|
email.setSSLOnConnect(ssl)
|
||||||
if (ssl == true) {
|
if (ssl) {
|
||||||
email.setSslSmtpPort(smtp.port.get.toString)
|
email.setSslSmtpPort(smtp.port.get.toString)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import java.security.SecureRandom
|
|||||||
import java.util.{Base64, UUID}
|
import java.util.{Base64, UUID}
|
||||||
|
|
||||||
import org.mozilla.universalchardet.UniversalDetector
|
import org.mozilla.universalchardet.UniversalDetector
|
||||||
import SyntaxSugars._
|
|
||||||
import javax.crypto.SecretKeyFactory
|
import javax.crypto.SecretKeyFactory
|
||||||
import javax.crypto.spec.PBEKeySpec
|
import javax.crypto.spec.PBEKeySpec
|
||||||
import org.apache.commons.io.input.BOMInputStream
|
import org.apache.commons.io.input.BOMInputStream
|
||||||
@@ -45,11 +44,11 @@ object StringUtil {
|
|||||||
s"""$$pbkdf2-sha256$$${iter}$$${base64Encode(salt)}$$${base64Encode(s.getEncoded)}"""
|
s"""$$pbkdf2-sha256$$${iter}$$${base64Encode(salt)}$$${base64Encode(s.getEncoded)}"""
|
||||||
}
|
}
|
||||||
|
|
||||||
def sha1(value: String): String =
|
def sha1(value: String): String = {
|
||||||
defining(java.security.MessageDigest.getInstance("SHA-1")) { md =>
|
val md = java.security.MessageDigest.getInstance("SHA-1")
|
||||||
md.update(value.getBytes)
|
md.update(value.getBytes)
|
||||||
md.digest.map(b => "%02x".format(b)).mkString
|
md.digest.map(b => "%02x".format(b)).mkString
|
||||||
}
|
}
|
||||||
|
|
||||||
def md5(value: String): String = {
|
def md5(value: String): String = {
|
||||||
val md = java.security.MessageDigest.getInstance("MD5")
|
val md = java.security.MessageDigest.getInstance("MD5")
|
||||||
@@ -89,15 +88,15 @@ object StringUtil {
|
|||||||
def convertFromByteArray(content: Array[Byte]): String =
|
def convertFromByteArray(content: Array[Byte]): String =
|
||||||
IOUtils.toString(new BOMInputStream(new java.io.ByteArrayInputStream(content)), detectEncoding(content))
|
IOUtils.toString(new BOMInputStream(new java.io.ByteArrayInputStream(content)), detectEncoding(content))
|
||||||
|
|
||||||
def detectEncoding(content: Array[Byte]): String =
|
def detectEncoding(content: Array[Byte]): String = {
|
||||||
defining(new UniversalDetector(null)) { detector =>
|
val detector = new UniversalDetector(null)
|
||||||
detector.handleData(content, 0, content.length)
|
detector.handleData(content, 0, content.length)
|
||||||
detector.dataEnd()
|
detector.dataEnd()
|
||||||
detector.getDetectedCharset match {
|
detector.getDetectedCharset match {
|
||||||
case null => "UTF-8"
|
case null => "UTF-8"
|
||||||
case e => e
|
case e => e
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts line separator in the given content.
|
* Converts line separator in the given content.
|
||||||
@@ -144,6 +143,19 @@ object StringUtil {
|
|||||||
.toSeq
|
.toSeq
|
||||||
.distinct
|
.distinct
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract issue id like ```owner/repository#issueId``` from the given message.
|
||||||
|
*
|
||||||
|
*@param message the message which may contains issue id
|
||||||
|
* @return the iterator of issue id
|
||||||
|
*/
|
||||||
|
def extractGlobalIssueId(message: String): List[(Option[String], Option[String], Option[String])] =
|
||||||
|
"\\s?([\\w-\\.]+)?\\/?([\\w\\-\\.]+)?#(\\d+)\\s?".r
|
||||||
|
.findAllIn(message)
|
||||||
|
.matchData
|
||||||
|
.map(i => (Option(i.group(1)), Option(i.group(2)), Option(i.group(3))))
|
||||||
|
.toList
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extract close issue id like ```close #issueId ``` from the given message.
|
* Extract close issue id like ```close #issueId ``` from the given message.
|
||||||
*
|
*
|
||||||
@@ -172,8 +184,7 @@ object StringUtil {
|
|||||||
def removeUserName(baseUrl: String): String = baseUrl.replaceFirst("(https?://).+@", "$1")
|
def removeUserName(baseUrl: String): String = baseUrl.replaceFirst("(https?://).+@", "$1")
|
||||||
|
|
||||||
gitRepositoryUrl match {
|
gitRepositoryUrl match {
|
||||||
case GitBucketUrlPattern(base, user, repository)
|
case GitBucketUrlPattern(base, user, repository) if baseUrl.exists(removeUserName(base).startsWith) =>
|
||||||
if baseUrl.map(removeUserName(base).startsWith).getOrElse(false) =>
|
|
||||||
s"${removeUserName(base)}/$user/$repository"
|
s"${removeUserName(base)}/$user/$repository"
|
||||||
case GitHubUrlPattern(_, user, repository) => s"https://github.com/$user/$repository"
|
case GitHubUrlPattern(_, user, repository) => s"https://github.com/$user/$repository"
|
||||||
case BitBucketUrlPattern(_, user, repository) => s"https://bitbucket.org/$user/$repository"
|
case BitBucketUrlPattern(_, user, repository) => s"https://bitbucket.org/$user/$repository"
|
||||||
|
|||||||
@@ -1,56 +1,14 @@
|
|||||||
package gitbucket.core.util
|
package gitbucket.core.util
|
||||||
|
|
||||||
import org.eclipse.jgit.api.Git
|
|
||||||
import scala.util.control.Exception._
|
|
||||||
import scala.language.reflectiveCalls
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides control facilities.
|
* Provides control facilities.
|
||||||
*/
|
*/
|
||||||
object SyntaxSugars {
|
object SyntaxSugars {
|
||||||
|
|
||||||
|
@deprecated("Use scala.util.Try instead", "4.36.0")
|
||||||
def defining[A, B](value: A)(f: A => B): B = f(value)
|
def defining[A, B](value: A)(f: A => B): B = f(value)
|
||||||
|
|
||||||
@deprecated("Use scala.util.Using.resource instead", "4.32.0")
|
@deprecated("Use scala.util.Try instead", "4.36.0")
|
||||||
def using[A <: { def close(): Unit }, B](resource: A)(f: A => B): B =
|
|
||||||
try f(resource)
|
|
||||||
finally {
|
|
||||||
if (resource != null) {
|
|
||||||
ignoring(classOf[Throwable]) {
|
|
||||||
resource.close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@deprecated("Use scala.util.Using.resources instead", "4.32.0")
|
|
||||||
def using[A <: { def close(): Unit }, B <: { def close(): Unit }, C](resource1: A, resource2: B)(f: (A, B) => C): C =
|
|
||||||
try f(resource1, resource2)
|
|
||||||
finally {
|
|
||||||
if (resource1 != null) {
|
|
||||||
ignoring(classOf[Throwable]) {
|
|
||||||
resource1.close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (resource2 != null) {
|
|
||||||
ignoring(classOf[Throwable]) {
|
|
||||||
resource2.close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@deprecated("Use scala.util.Using.resource instead", "4.32.0")
|
|
||||||
def using[T](git: Git)(f: Git => T): T =
|
|
||||||
try f(git)
|
|
||||||
finally git.getRepository.close()
|
|
||||||
|
|
||||||
@deprecated("Use scala.util.Using.resources instead", "4.32.0")
|
|
||||||
def using[T](git1: Git, git2: Git)(f: (Git, Git) => T): T =
|
|
||||||
try f(git1, git2)
|
|
||||||
finally {
|
|
||||||
git1.getRepository.close()
|
|
||||||
git2.getRepository.close()
|
|
||||||
}
|
|
||||||
|
|
||||||
def ignore[T](f: => Unit): Unit =
|
def ignore[T](f: => Unit): Unit =
|
||||||
try {
|
try {
|
||||||
f
|
f
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user