mirror of
https://github.com/gitbucket/gitbucket.git
synced 2026-05-08 13:16:22 +02:00
Compare commits
384 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
90b9c17d6e | ||
|
|
f9e4500bbb | ||
|
|
df60395ea2 | ||
|
|
3111ab3234 | ||
|
|
74a9aaa31d | ||
|
|
175232cac1 | ||
|
|
cce9c62591 | ||
|
|
35d670843f | ||
|
|
40555c8464 | ||
|
|
28dadef118 | ||
|
|
b697fdf189 | ||
|
|
f72617769b | ||
|
|
b355599a96 | ||
|
|
af6e99d832 | ||
|
|
312927efda | ||
|
|
366074ec55 | ||
|
|
7d679781a8 | ||
|
|
8f2e0f8505 | ||
|
|
73ccfd0788 | ||
|
|
c25145b793 | ||
|
|
1b7fbcb59d | ||
|
|
03760f126b | ||
|
|
3826b690cf | ||
|
|
3f3b111afc | ||
|
|
379c86ba9d | ||
|
|
6dce8672ab | ||
|
|
85d8432755 | ||
|
|
edbec530f3 | ||
|
|
952bfcc37b | ||
|
|
0f48c3c926 | ||
|
|
ed0a6ef4ff | ||
|
|
0bdc765198 | ||
|
|
9a613bb678 | ||
|
|
06ea83579d | ||
|
|
a84e501099 | ||
|
|
17b3587263 | ||
|
|
6cce445dcb | ||
|
|
22c09c2cb1 | ||
|
|
241d46847b | ||
|
|
6b145ea760 | ||
|
|
5fc3dd74a8 | ||
|
|
6ee03843c5 | ||
|
|
516f67f812 | ||
|
|
2b3b26d542 | ||
|
|
858de58cd0 | ||
|
|
3248bef98d | ||
|
|
0c9863f956 | ||
|
|
afc5c2d3d0 | ||
|
|
544a291dac | ||
|
|
9e8d5b89c7 | ||
|
|
86f069c980 | ||
|
|
69bd70b381 | ||
|
|
f1d0b0c080 | ||
|
|
45ae0cfd62 | ||
|
|
d2233e78e4 | ||
|
|
8ab6b386a3 | ||
|
|
2dd2c4f568 | ||
|
|
36f7011ebf | ||
|
|
fbcf962630 | ||
|
|
3b9b261878 | ||
|
|
b334809e3e | ||
|
|
9a1324a870 | ||
|
|
b7c39e90d2 | ||
|
|
63b4f25687 | ||
|
|
b582af4469 | ||
|
|
ecd3f5b4eb | ||
|
|
7be433d331 | ||
|
|
154be0f425 | ||
|
|
9ddd2065b7 | ||
|
|
6917cbf224 | ||
|
|
637b033782 | ||
|
|
7aee451a55 | ||
|
|
e1e369d653 | ||
|
|
c2ad66438c | ||
|
|
c88e5adac2 | ||
|
|
703fb4a650 | ||
|
|
465282580f | ||
|
|
e041f8ffa0 | ||
|
|
094386bb65 | ||
|
|
b9e830f06e | ||
|
|
bdbfbc62a9 | ||
|
|
5787a02daa | ||
|
|
0d6e5af8d7 | ||
|
|
88a8973685 | ||
|
|
e640cec323 | ||
|
|
5a4226db1d | ||
|
|
fb48c2a874 | ||
|
|
28da703052 | ||
|
|
2568171083 | ||
|
|
2dff7f1b9e | ||
|
|
8a79de8b50 | ||
|
|
1b763c753c | ||
|
|
3bb1b1269c | ||
|
|
dbbaf3cc9c | ||
|
|
4465a62b5f | ||
|
|
fb48e75ecf | ||
|
|
a79142074f | ||
|
|
9f58c6dce7 | ||
|
|
6fe903afab | ||
|
|
0d94865633 | ||
|
|
2fbdead64b | ||
|
|
72a354931e | ||
|
|
cb8affcd0d | ||
|
|
a5a997eb40 | ||
|
|
b4220aab68 | ||
|
|
c8a4798f86 | ||
|
|
6b8e9e8892 | ||
|
|
9ab9363d0b | ||
|
|
1edb18a147 | ||
|
|
d8b4bf3033 | ||
|
|
6597c4490b | ||
|
|
daaf1696ad | ||
|
|
53a1ca7874 | ||
|
|
f045fa4c6a | ||
|
|
53a7c5adf8 | ||
|
|
b142eca9a5 | ||
|
|
ba753a373b | ||
|
|
95ceca75a4 | ||
|
|
f16cc117a9 | ||
|
|
af66f8f746 | ||
|
|
d9cc57e8e0 | ||
|
|
72b6dad3a2 | ||
|
|
18f396b4a2 | ||
|
|
9f3fde8de2 | ||
|
|
dfd6f80b63 | ||
|
|
119d91210c | ||
|
|
75ef30ee03 | ||
|
|
d1cf9dd600 | ||
|
|
9c9fea908c | ||
|
|
1145c4d0f6 | ||
|
|
d847fc6e0f | ||
|
|
bb9585f7a6 | ||
|
|
e91411fa45 | ||
|
|
adbc065a6f | ||
|
|
862283b729 | ||
|
|
217df7012c | ||
|
|
e672d41e77 | ||
|
|
d975700bd4 | ||
|
|
f65e41561a | ||
|
|
dab4f33ed9 | ||
|
|
5ff45ef5ae | ||
|
|
af7c622647 | ||
|
|
f7027e57df | ||
|
|
6a4719469d | ||
|
|
6ebc865ba5 | ||
|
|
0c1e8b932b | ||
|
|
fda67a32e2 | ||
|
|
14d7e9ee90 | ||
|
|
b7b7322cce | ||
|
|
5eb44398d0 | ||
|
|
be7bb255c3 | ||
|
|
cb9522d416 | ||
|
|
d80afb473b | ||
|
|
607d85c661 | ||
|
|
a5fab3bc96 | ||
|
|
eb403ada58 | ||
|
|
911c102f39 | ||
|
|
bf23e854f8 | ||
|
|
52427c0a1e | ||
|
|
d8e5ac585c | ||
|
|
2fbeef73b0 | ||
|
|
15e39572dd | ||
|
|
9eac4f42c5 | ||
|
|
01d18bb5c3 | ||
|
|
00258e9125 | ||
|
|
046b337337 | ||
|
|
46cc7b6fd3 | ||
|
|
59344b4f05 | ||
|
|
c4d8af02b2 | ||
|
|
a10bc3687a | ||
|
|
b0d21dee42 | ||
|
|
13ea0e7507 | ||
|
|
d66fdaede5 | ||
|
|
d6217d89eb | ||
|
|
c99ff1cf0f | ||
|
|
001b9ae2ae | ||
|
|
f63493f1c0 | ||
|
|
c9095722f8 | ||
|
|
b9d2efa582 | ||
|
|
9c2e09020a | ||
|
|
1ffcf8c1e9 | ||
|
|
0124091840 | ||
|
|
2a28a7b35b | ||
|
|
2a68ffc8dc | ||
|
|
6b47c49cdd | ||
|
|
b634967776 | ||
|
|
172bed760d | ||
|
|
1b7eb69083 | ||
|
|
185c01db99 | ||
|
|
ab548d8c25 | ||
|
|
983975620b | ||
|
|
b512b08256 | ||
|
|
a54fb4960f | ||
|
|
93de53d717 | ||
|
|
f262c0a9eb | ||
|
|
e5d15569df | ||
|
|
369b08eae3 | ||
|
|
8e6fcb022b | ||
|
|
456b1f6571 | ||
|
|
6e459ad225 | ||
|
|
cece4c1c7d | ||
|
|
42e7a9fa9f | ||
|
|
f9510aba8e | ||
|
|
8a7d719025 | ||
|
|
1293a21450 | ||
|
|
65dd597ab7 | ||
|
|
e145b5151e | ||
|
|
b9684c277b | ||
|
|
4accb77533 | ||
|
|
9eef961025 | ||
|
|
546b40cdd1 | ||
|
|
274a08c14c | ||
|
|
eed4b51189 | ||
|
|
5c2f84367b | ||
|
|
d5b625e43f | ||
|
|
27f9e3dec9 | ||
|
|
00ef4db9a7 | ||
|
|
bf4f814389 | ||
|
|
23e45afd7f | ||
|
|
bfb02eef62 | ||
|
|
c129aae73a | ||
|
|
a955856cef | ||
|
|
a43a3fa55c | ||
|
|
a6254ab955 | ||
|
|
b505c3dc12 | ||
|
|
9d69b9e980 | ||
|
|
44b2320644 | ||
|
|
8fb9643ea5 | ||
|
|
10ea988298 | ||
|
|
7896945519 | ||
|
|
210342d2bc | ||
|
|
fdacea858b | ||
|
|
6825028d37 | ||
|
|
2089882d41 | ||
|
|
8e1d938155 | ||
|
|
39eb4cef04 | ||
|
|
d4e3adafa6 | ||
|
|
a7b8326499 | ||
|
|
249f8738d3 | ||
|
|
fefe6ef74f | ||
|
|
9ca6cd1d90 | ||
|
|
bff7b7c460 | ||
|
|
cfff79758b | ||
|
|
ded8ceb2c6 | ||
|
|
c502ebfc16 | ||
|
|
d3268265cf | ||
|
|
5eaf59eebb | ||
|
|
d6d47aa977 | ||
|
|
dc052e05ce | ||
|
|
afb145ca7c | ||
|
|
a8beed33e0 | ||
|
|
488599dba5 | ||
|
|
ef0d08a917 | ||
|
|
9fa0d03c99 | ||
|
|
15b6c5952b | ||
|
|
cb75e9d312 | ||
|
|
d542ee3528 | ||
|
|
608a59cbfd | ||
|
|
768e3a5c91 | ||
|
|
f1fc794c0c | ||
|
|
4cf924bee0 | ||
|
|
8164efc720 | ||
|
|
580a627d9d | ||
|
|
1701916209 | ||
|
|
811b2bff70 | ||
|
|
9de9dd8940 | ||
|
|
572f83327f | ||
|
|
2afb37823b | ||
|
|
edc9720a88 | ||
|
|
defc0fa041 | ||
|
|
c14b3c5576 | ||
|
|
10fc04cbc9 | ||
|
|
67563a8805 | ||
|
|
743bdab79b | ||
|
|
a5a2e4732d | ||
|
|
3a0cd5df62 | ||
|
|
ad9a0afc9b | ||
|
|
d2fc7a0642 | ||
|
|
b1d4a18c9b | ||
|
|
fdfd8ec9b2 | ||
|
|
396156c4e3 | ||
|
|
8168580d60 | ||
|
|
2abdd233e7 | ||
|
|
ef95ce99d2 | ||
|
|
3a6b2f6f4e | ||
|
|
92304ac8c6 | ||
|
|
a2242a3cb7 | ||
|
|
64e8167fcb | ||
|
|
06b93293a6 | ||
|
|
3dcc0aee3c | ||
|
|
c009a39dd7 | ||
|
|
b27e8ebb7e | ||
|
|
a690d43491 | ||
|
|
86bfb68a0c | ||
|
|
9469801d3d | ||
|
|
757a8399c1 | ||
|
|
ed00e613c3 | ||
|
|
6e341116c1 | ||
|
|
9ccc2d0f97 | ||
|
|
72742306a0 | ||
|
|
47c6d61661 | ||
|
|
cfc444bcd2 | ||
|
|
a1f65258ea | ||
|
|
5e3b97cf85 | ||
|
|
209e1967d6 | ||
|
|
4437a4e7a6 | ||
|
|
e4d92bb494 | ||
|
|
d942c8f48a | ||
|
|
3a798e152f | ||
|
|
91b862970f | ||
|
|
2d199e22e7 | ||
|
|
757da408d1 | ||
|
|
309d6e8b57 | ||
|
|
ec0395a7f3 | ||
|
|
7cb4958ca0 | ||
|
|
2d1c917a2c | ||
|
|
27dd5120dd | ||
|
|
9e0f8992c5 | ||
|
|
5f87a28da6 | ||
|
|
25d5d5eee5 | ||
|
|
ec9472b7cc | ||
|
|
b967fb93a7 | ||
|
|
9d1bf3035b | ||
|
|
d6cedc2171 | ||
|
|
8d8c78966a | ||
|
|
cbe1c1c9f6 | ||
|
|
bf946960a7 | ||
|
|
9677735542 | ||
|
|
e06c8fe5da | ||
|
|
d4ee6c9063 | ||
|
|
1d0f079bd2 | ||
|
|
7cc6972d83 | ||
|
|
177da92b39 | ||
|
|
e1f433573b | ||
|
|
cd958d7681 | ||
|
|
9c8e77f472 | ||
|
|
bf7d3a8f0b | ||
|
|
934e17666d | ||
|
|
b20c4b3d7d | ||
|
|
7c4e8b54d0 | ||
|
|
1e3794674b | ||
|
|
5d5706451d | ||
|
|
76e8d01846 | ||
|
|
75e2883f61 | ||
|
|
7e7cc1d4c7 | ||
|
|
d7623e8b7a | ||
|
|
7dd1470cd4 | ||
|
|
9fb8d6fc66 | ||
|
|
4f1869817c | ||
|
|
6be269c388 | ||
|
|
af857853cb | ||
|
|
1a664c2b6a | ||
|
|
1feb33fc19 | ||
|
|
4fc1d9c3b6 | ||
|
|
7eb77908d4 | ||
|
|
54f9d01c42 | ||
|
|
08154af7b1 | ||
|
|
e7ab376a34 | ||
|
|
a680398c54 | ||
|
|
6acf1e68ca | ||
|
|
fc35e7b83d | ||
|
|
1ae6fc7e9e | ||
|
|
884f63e6f4 | ||
|
|
4d3e34af8b | ||
|
|
54426bb456 | ||
|
|
c86b72bf6b | ||
|
|
e6a5911a39 | ||
|
|
6ea77fa73e | ||
|
|
32d0b94a7a | ||
|
|
4f770adb35 | ||
|
|
361f92a08d | ||
|
|
c2068f58e7 | ||
|
|
67dd604469 | ||
|
|
d1a2c23cf3 | ||
|
|
262500508c | ||
|
|
f1360f44c6 | ||
|
|
1c818f890a | ||
|
|
16f146b660 | ||
|
|
6166eaf0c7 | ||
|
|
ad4312cedb | ||
|
|
15083a5aac | ||
|
|
9ae420c553 | ||
|
|
4115ea4ab6 | ||
|
|
024ac313ae |
@@ -1,2 +1,8 @@
|
||||
# update scalafmt
|
||||
3d5ca44d66c77a46770a65a895c9737c542690f6
|
||||
|
||||
# Scala Steward: Reformat with scalafmt 3.8.2
|
||||
f1360f44c61f8e12666965c10e79f11cd75d6d30
|
||||
|
||||
# Scala Steward: Reformat with scalafmt 3.9.7
|
||||
a54fb4960ff0762738f4895cdc29bf2715a57f87
|
||||
|
||||
1
.github/CODEOWNERS
vendored
1
.github/CODEOWNERS
vendored
@@ -3,3 +3,4 @@
|
||||
build.sbt @xuwei-k
|
||||
project/* @xuwei-k
|
||||
.github/workflows/* @xuwei-k
|
||||
.scalafmt.conf @xuwei-k
|
||||
|
||||
22
.github/ISSUE_TEMPLATE.md
vendored
22
.github/ISSUE_TEMPLATE.md
vendored
@@ -1,22 +0,0 @@
|
||||
### Before submitting an issue to GitBucket I have first:
|
||||
|
||||
- [ ] read the [contribution guidelines](https://github.com/gitbucket/gitbucket/blob/master/.github/CONTRIBUTING.md)
|
||||
- [ ] searched for similar already existing issue
|
||||
- [ ] read the documentation and [wiki](https://github.com/gitbucket/gitbucket/wiki)
|
||||
|
||||
<!--
|
||||
|
||||
*(if you have performed all the above, remove the paragraph and continue describing the issue with template below)*
|
||||
|
||||
-->
|
||||
|
||||
## Issue
|
||||
**Impacted version**: xxxx
|
||||
|
||||
**Deployment mode**: *explain here how you use GitBucket : standalone app, under webcontainer (which one), with an http frontend (nginx, httpd, ...)*
|
||||
|
||||
**Problem description**:
|
||||
- *be as explicit as you can*
|
||||
- *describe the problem and its symptoms*
|
||||
- *explain how to reproduce*
|
||||
- *attach whatever information that can help understanding the context (screen capture, log files)*
|
||||
1
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
1
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@@ -0,0 +1 @@
|
||||
blank_issues_enabled: false
|
||||
41
.github/ISSUE_TEMPLATE/issue_template.yml
vendored
Normal file
41
.github/ISSUE_TEMPLATE/issue_template.yml
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
name: Report issue
|
||||
description: Report a problem or feature request with GitBucket
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
### Before submitting an issue to GitBucket, please ensure you have:
|
||||
- Read the [contribution guidelines](https://github.com/gitbucket/gitbucket/blob/master/.github/CONTRIBUTING.md)
|
||||
- Searched for similar existing issues
|
||||
- Read the documentation and [wiki](https://github.com/gitbucket/gitbucket/wiki)
|
||||
- You can use [Gitter chat room](https://gitter.im/gitbucket/gitbucket) instead of GitHub Issues for casual discussion or inquiry
|
||||
|
||||
- type: checkboxes
|
||||
id: prerequisites
|
||||
attributes:
|
||||
label: Prerequisites
|
||||
options:
|
||||
- label: I have read the contribution guidelines
|
||||
- label: I have searched for similar issues
|
||||
- label: I have read the documentation and wiki
|
||||
|
||||
- type: input
|
||||
id: impacted_version
|
||||
attributes:
|
||||
label: Impacted version
|
||||
description: Which version of GitBucket is affected?
|
||||
placeholder: e.g. 4.37.0
|
||||
|
||||
- type: input
|
||||
id: deployment_mode
|
||||
attributes:
|
||||
label: Deployment mode
|
||||
description: How do you use GitBucket? (standalone app, under webcontainer, with an HTTP frontend, etc.)
|
||||
placeholder: e.g. Standalone app, Tomcat, nginx
|
||||
|
||||
- type: textarea
|
||||
id: problem_description
|
||||
attributes:
|
||||
label: Problem description
|
||||
description: Be as explicit as you can. Describe the problem, its symptoms, how to reproduce, and attach any relevant information (screenshots, logs, etc.)
|
||||
placeholder: Describe the problem and how to reproduce it
|
||||
13
.github/workflows/build.yml
vendored
13
.github/workflows/build.yml
vendored
@@ -7,12 +7,13 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
java: [11, 21]
|
||||
java: [17, 25]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- name: Cache
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v5
|
||||
env:
|
||||
cache-name: cache-sbt-libs
|
||||
with:
|
||||
@@ -22,10 +23,12 @@ jobs:
|
||||
~/.cache/coursier/v1
|
||||
key: build-${{ env.cache-name }}-${{ hashFiles('build.sbt') }}
|
||||
- name: Set up JDK
|
||||
uses: actions/setup-java@v4
|
||||
uses: actions/setup-java@v5
|
||||
with:
|
||||
java-version: ${{ matrix.java }}
|
||||
distribution: adopt
|
||||
- name: Setup sbt launcher
|
||||
uses: sbt/setup-sbt@v1
|
||||
- name: Run tests
|
||||
run: sbt scalafmtSbtCheck scalafmtCheckAll test
|
||||
- name: Scala 3
|
||||
@@ -33,7 +36,7 @@ jobs:
|
||||
- name: Build executable
|
||||
run: sbt executable
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: gitbucket-java${{ matrix.java }}-${{ github.sha }}
|
||||
path: ./target/executable/gitbucket.*
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
version = "3.8.1"
|
||||
version = "3.10.7"
|
||||
project.git = true
|
||||
|
||||
maxColumn = 120
|
||||
|
||||
59
CHANGELOG.md
59
CHANGELOG.md
@@ -1,12 +1,65 @@
|
||||
# Changelog
|
||||
All changes to the project will be documented in this file.
|
||||
|
||||
## 4.46.0 - 7 Mar 2026
|
||||
- Add support for reverting pull request
|
||||
- Add markdown toolbar
|
||||
- Enable text completion in Ace editor
|
||||
- Apply Ace editor for Wiki editing
|
||||
- Webhook security: SHA-256 support
|
||||
- Preserve UTF-8 BOM when editing files in browser
|
||||
|
||||
## 4.45.0 - 10 Jan 2026
|
||||
- Add new option to show full username on UI
|
||||
- Support render plugin in issues, pull requests, wiki and commit comments
|
||||
- Support link to other pages from Wiki page using Wiki link syntax
|
||||
|
||||
## 4.44.0 - 23 Sep 2025
|
||||
- Enhanced branch protection which supports the following settings:
|
||||
- Prevent pushes from non-allowed users
|
||||
- Whether to apply restrictions to administrator users as well
|
||||
- Improve logging for initialization errors
|
||||
|
||||
## 4.43.0 - 29 Jun 2025
|
||||
- Upgrade H2 database from 1.x to 2.x
|
||||
|
||||
Note that upgrading from h2 1.x to 2.x requires data file migration: https://www.h2database.com/html/migration-to-v2.html
|
||||
|
||||
It can't be done automatically using GitBucket's auto migration mechanism because it relies on database itself. So, users who use h2 will have to dump and recreate their database manually with the following steps:
|
||||
```bash
|
||||
# Export database using the current version of H2
|
||||
$ curl -O https://repo1.maven.org/maven2/com/h2database/h2/1.4.199/h2-1.4.199.jar
|
||||
$ java -cp h2-1.4.199.jar org.h2.tools.Script -url "jdbc:h2:~/.gitbucket/data" -user sa -password sa -script dump.sql
|
||||
|
||||
# Recreate database using the new version of H2
|
||||
$ curl -O https://repo1.maven.org/maven2/com/h2database/h2/2.3.232/h2-2.3.232.jar
|
||||
$ java -cp h2-2.3.232.jar org.h2.tools.RunScript -url "jdbc:h2:~/.gitbucket/data" -user sa -password sa -script dump.sql
|
||||
```
|
||||
|
||||
In addition, if `~/.gitbucket/database.conf` has the following configuration, remove `;MVCC=true` from `url`.
|
||||
```
|
||||
db {
|
||||
url = "jdbc:h2:${DatabaseHome};MVCC=true" // => "jdbc:h2:${DatabaseHome}"
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
## 4.42.1 - 20 Jan 2025
|
||||
- Fix LDAP issue with SSL
|
||||
|
||||
## 4.42.0 - 30 Dec 2024
|
||||
- Increase max branch name length 100 -> 255
|
||||
- Fix some GitHub incompatible Web APIs
|
||||
- Apply user-defined CSS after all plugins
|
||||
- Improve performance of listing commit logs
|
||||
- Drop Java 11 support. Java 17 is now required
|
||||
|
||||
## 4.41.0 - 18 May 2024
|
||||
- Simplify pull request UI
|
||||
- Keyword search for issues and pull requests
|
||||
- New settings for max files and lines limit in showing diff
|
||||
- Adjust the default branch automatically when cloning external repository
|
||||
- Fix layout of branch selector
|
||||
- Integrate keyword search with filtering in the search box
|
||||
- Make max files and lines limit in showing diff configurable
|
||||
- Adjust the default branch automatically when cloning external repo
|
||||
- Performance improvement for listing branches
|
||||
- Upgrade internal libraries
|
||||
|
||||
|
||||
48
README.md
48
README.md
@@ -1,4 +1,4 @@
|
||||
GitBucket [](https://gitter.im/gitbucket/gitbucket) [](https://github.com/gitbucket/gitbucket/actions?query=workflow%3Abuild+branch%3Amaster) [](https://index.scala-lang.org/gitbucket/gitbucket/gitbucket) [](https://github.com/gitbucket/gitbucket/blob/master/LICENSE)
|
||||
GitBucket [](https://gitter.im/gitbucket/gitbucket) [](https://github.com/gitbucket/gitbucket/actions/workflows/build.yml) [](https://index.scala-lang.org/gitbucket/gitbucket/gitbucket) [](https://github.com/gitbucket/gitbucket/blob/master/LICENSE)
|
||||
=========
|
||||
|
||||
GitBucket is a Git web platform powered by Scala offering:
|
||||
@@ -24,7 +24,7 @@ The current version of GitBucket provides many features such as:
|
||||
|
||||
Installation
|
||||
--------
|
||||
GitBucket requires **Java 11**. You have to install it, if it is not already installed.
|
||||
GitBucket requires **Java 17**. You have to install it, if it is not already installed.
|
||||
|
||||
1. Download the latest **gitbucket.war** from [the releases page](https://github.com/gitbucket/gitbucket/releases) and run it by `java -jar gitbucket.war`.
|
||||
2. Go to `http://[hostname]:8080/` and log in with ID: **root** / Pass: **root**.
|
||||
@@ -44,7 +44,7 @@ GitBucket has a plug-in system that allows extra functionality. Officially the f
|
||||
- [gitbucket-pages-plugin](https://github.com/gitbucket/gitbucket-pages-plugin)
|
||||
- [gitbucket-notifications-plugin](https://github.com/gitbucket/gitbucket-notifications-plugin)
|
||||
|
||||
You can find more plugins made by the community at [GitBucket community plugins](https://gitbucket-plugins.github.io/).
|
||||
You can find more plugins made by the community at [GitBucket community plugins](https://github.com/gitbucket/gitbucket/wiki/Community-Plugins).
|
||||
|
||||
Building and Development
|
||||
-----------
|
||||
@@ -56,18 +56,38 @@ Support
|
||||
--------
|
||||
|
||||
- If you have any questions about GitBucket, see [Wiki](https://github.com/gitbucket/gitbucket/wiki) and check issues whether there is a same question or request in the past.
|
||||
- If you can't find same question and report, send it to our [Gitter room](https://gitter.im/gitbucket/gitbucket) before raising an issue.
|
||||
- If you can't find same question and report, send it to our [Gitter chat room](https://gitter.im/gitbucket/gitbucket) before raising an issue.
|
||||
- The highest priority of GitBucket is the ease of installation and API compatibility with GitHub, so your feature request might be rejected if they go against those principles.
|
||||
|
||||
What's New in 4.41.x
|
||||
What's New in 4.45.x
|
||||
-------------
|
||||
## 4.41.0 - 18 May 2024
|
||||
- Simplify pull request UI
|
||||
- Fix layout of branch selector
|
||||
- Integrate keyword search with filtering in the search box
|
||||
- Make max files and lines limit in showing diff configurable
|
||||
- Adjust the default branch automatically when cloning external repo
|
||||
- Performance improvement for listing branches
|
||||
- Upgrade internal libraries
|
||||
## 4.46.0 - 7 Mar 2026
|
||||
- Add support for reverting pull request
|
||||
- Add markdown toolbar
|
||||
- Enable text completion in Ace editor
|
||||
- Apply Ace editor for Wiki editing
|
||||
- Webhook security: SHA-256 support
|
||||
- Preserve UTF-8 BOM when editing files in browser
|
||||
|
||||
See the [change log](CHANGELOG.md) for all of the updates.
|
||||
Note that you have to migrate h2 database file if you will upgrade GitBucket from 4.42 or before to 4.43 or later and you are using the default h2 database because h2 1.x and h2.x don't have compatibility: https://www.h2database.com/html/migration-to-v2.html
|
||||
|
||||
It can't be done automatically using GitBucket's auto migration mechanism because it relies on database itself. So, users who use h2 will have to dump and recreate their database manually with the following steps:
|
||||
```bash
|
||||
# Export database using the current version of H2
|
||||
$ curl -O https://repo1.maven.org/maven2/com/h2database/h2/1.4.199/h2-1.4.199.jar
|
||||
$ java -cp h2-1.4.199.jar org.h2.tools.Script -url "jdbc:h2:~/.gitbucket/data" -user sa -password sa -script dump.sql
|
||||
|
||||
# Recreate database using the new version of H2
|
||||
$ curl -O https://repo1.maven.org/maven2/com/h2database/h2/2.3.232/h2-2.3.232.jar
|
||||
$ java -cp h2-2.3.232.jar org.h2.tools.RunScript -url "jdbc:h2:~/.gitbucket/data" -user sa -password sa -script dump.sql
|
||||
```
|
||||
|
||||
In addition, if `~/.gitbucket/database.conf` has the following configuration, remove `;MVCC=true` from `url`.
|
||||
```
|
||||
db {
|
||||
url = "jdbc:h2:${DatabaseHome};MVCC=true" // => "jdbc:h2:${DatabaseHome}"
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
See the [change log](CHANGELOG.md) for all the past updates.
|
||||
|
||||
107
build.sbt
107
build.sbt
@@ -1,23 +1,22 @@
|
||||
import sbtlicensereport.license.{DepModuleInfo, LicenseInfo}
|
||||
import com.jsuereth.sbtpgp.PgpKeys._
|
||||
|
||||
val Organization = "io.github.gitbucket"
|
||||
val Name = "gitbucket"
|
||||
val GitBucketVersion = "4.41.0"
|
||||
val ScalatraVersion = "3.0.0"
|
||||
val JettyVersion = "10.0.21"
|
||||
val JgitVersion = "6.9.0.202403050737-r"
|
||||
val GitBucketVersion = "4.46.0"
|
||||
val ScalatraVersion = "3.1.2"
|
||||
val JettyVersion = "10.0.26"
|
||||
val JgitVersion = "6.10.1.202505221210-r"
|
||||
|
||||
lazy val root = (project in file("."))
|
||||
.enablePlugins(SbtTwirl, ScalatraPlugin)
|
||||
.enablePlugins(SbtTwirl, ContainerPlugin)
|
||||
|
||||
sourcesInBase := false
|
||||
organization := Organization
|
||||
name := Name
|
||||
version := GitBucketVersion
|
||||
scalaVersion := "2.13.14"
|
||||
scalaVersion := "2.13.18"
|
||||
|
||||
crossScalaVersions += "3.4.2"
|
||||
crossScalaVersions += "3.8.2"
|
||||
|
||||
// scalafmtOnCompile := true
|
||||
|
||||
@@ -29,42 +28,47 @@ libraryDependencies ++= Seq(
|
||||
"org.scalatra" %% "scalatra-javax" % ScalatraVersion,
|
||||
"org.scalatra" %% "scalatra-json-javax" % ScalatraVersion,
|
||||
"org.scalatra" %% "scalatra-forms-javax" % ScalatraVersion,
|
||||
"org.json4s" %% "json4s-jackson" % "4.1.0-M5",
|
||||
"commons-io" % "commons-io" % "2.16.1",
|
||||
"io.github.json4s" %% "json4s-jackson" % "4.1.0",
|
||||
"commons-io" % "commons-io" % "2.21.0",
|
||||
"io.github.gitbucket" % "solidbase" % "1.1.0",
|
||||
"io.github.gitbucket" % "markedj" % "1.0.20",
|
||||
"org.apache.commons" % "commons-compress" % "1.26.1",
|
||||
"org.tukaani" % "xz" % "1.12",
|
||||
"org.apache.commons" % "commons-compress" % "1.28.0",
|
||||
"org.apache.commons" % "commons-email" % "1.6.0",
|
||||
"commons-net" % "commons-net" % "3.10.0",
|
||||
"commons-net" % "commons-net" % "3.12.0",
|
||||
"org.apache.httpcomponents" % "httpclient" % "4.5.14",
|
||||
"org.apache.sshd" % "apache-sshd" % "2.12.1" exclude ("org.slf4j", "slf4j-jdk14") exclude ("org.apache.sshd", "sshd-mina") exclude ("org.apache.sshd", "sshd-netty"),
|
||||
"org.apache.tika" % "tika-core" % "2.9.2",
|
||||
"com.github.takezoe" %% "blocking-slick" % "0.0.14",
|
||||
"com.novell.ldap" % "jldap" % "2009-10-07",
|
||||
"com.h2database" % "h2" % "1.4.199",
|
||||
"org.mariadb.jdbc" % "mariadb-java-client" % "2.7.12",
|
||||
"org.postgresql" % "postgresql" % "42.7.3",
|
||||
"ch.qos.logback" % "logback-classic" % "1.5.6",
|
||||
"com.zaxxer" % "HikariCP" % "5.1.0" exclude ("org.slf4j", "slf4j-api"),
|
||||
"com.typesafe" % "config" % "1.4.3",
|
||||
"fr.brouillard.oss.security.xhub" % "xhub4j-core" % "1.1.0",
|
||||
"io.github.java-diff-utils" % "java-diff-utils" % "4.12",
|
||||
"org.cache2k" % "cache2k-all" % "1.6.0.Final",
|
||||
"net.coobird" % "thumbnailator" % "0.4.20",
|
||||
"com.github.zafarkhaja" % "java-semver" % "0.10.2",
|
||||
"com.nimbusds" % "oauth2-oidc-sdk" % "11.12",
|
||||
"org.eclipse.jetty" % "jetty-webapp" % JettyVersion % "provided",
|
||||
"javax.servlet" % "javax.servlet-api" % "3.1.0" % "provided",
|
||||
"junit" % "junit" % "4.13.2" % "test",
|
||||
"org.scalatra" %% "scalatra-scalatest-javax" % ScalatraVersion % "test",
|
||||
"org.mockito" % "mockito-core" % "5.12.0" % "test",
|
||||
"com.dimafeng" %% "testcontainers-scala" % "0.41.3" % "test",
|
||||
"org.testcontainers" % "mysql" % "1.19.8" % "test",
|
||||
"org.testcontainers" % "postgresql" % "1.19.8" % "test",
|
||||
"net.i2p.crypto" % "eddsa" % "0.3.0",
|
||||
"is.tagomor.woothee" % "woothee-java" % "1.11.0",
|
||||
"org.ec4j.core" % "ec4j-core" % "0.3.0",
|
||||
"org.kohsuke" % "github-api" % "1.321" % "test"
|
||||
"org.apache.sshd" % "apache-sshd" % "2.17.1" exclude ("org.slf4j", "slf4j-jdk14") exclude (
|
||||
"org.apache.sshd",
|
||||
"sshd-mina"
|
||||
) exclude ("org.apache.sshd", "sshd-netty")
|
||||
exclude ("org.apache.sshd", "sshd-spring-sftp"),
|
||||
"org.apache.tika" % "tika-core" % "3.2.3",
|
||||
"com.github.takezoe" %% "blocking-slick" % "0.0.14",
|
||||
"com.novell.ldap" % "jldap" % "2009-10-07",
|
||||
"com.h2database" % "h2" % "2.4.240",
|
||||
"org.mariadb.jdbc" % "mariadb-java-client" % "2.7.13",
|
||||
"org.postgresql" % "postgresql" % "42.7.10",
|
||||
"ch.qos.logback" % "logback-classic" % "1.5.32",
|
||||
"com.zaxxer" % "HikariCP" % "7.0.2" exclude ("org.slf4j", "slf4j-api"),
|
||||
"com.typesafe" % "config" % "1.4.6",
|
||||
"fr.brouillard.oss.security.xhub" % "xhub4j-core" % "1.1.0",
|
||||
"io.github.java-diff-utils" % "java-diff-utils" % "4.16",
|
||||
"org.cache2k" % "cache2k-api" % "2.6.1.Final",
|
||||
"org.cache2k" % "cache2k-core" % "2.6.1.Final",
|
||||
"net.coobird" % "thumbnailator" % "0.4.21",
|
||||
"com.github.zafarkhaja" % "java-semver" % "0.10.2",
|
||||
"com.nimbusds" % "oauth2-oidc-sdk" % "11.33",
|
||||
"org.eclipse.jetty" % "jetty-webapp" % JettyVersion % "provided",
|
||||
"javax.servlet" % "javax.servlet-api" % "3.1.0" % "provided",
|
||||
"junit" % "junit" % "4.13.2" % "test",
|
||||
"org.scalatra" %% "scalatra-scalatest-javax" % ScalatraVersion % "test",
|
||||
"org.mockito" % "mockito-core" % "5.22.0" % "test",
|
||||
"org.testcontainers" % "testcontainers-mysql" % "2.0.3" % "test",
|
||||
"org.testcontainers" % "testcontainers-postgresql" % "2.0.3" % "test",
|
||||
"net.i2p.crypto" % "eddsa" % "0.3.0",
|
||||
"is.tagomor.woothee" % "woothee-java" % "1.11.0",
|
||||
"org.ec4j.core" % "ec4j-core" % "1.2.0",
|
||||
"org.kohsuke" % "github-api" % "1.330" % "test"
|
||||
)
|
||||
|
||||
// Compiler settings
|
||||
@@ -79,13 +83,13 @@ scalacOptions := Seq(
|
||||
scalacOptions ++= {
|
||||
scalaBinaryVersion.value match {
|
||||
case "2.13" =>
|
||||
Seq("-Xsource:3")
|
||||
Seq("-Xsource:3-cross")
|
||||
case _ =>
|
||||
Nil
|
||||
}
|
||||
}
|
||||
compile / javacOptions ++= Seq("-target", "11", "-source", "11")
|
||||
Jetty / javaOptions += "-Dlogback.configurationFile=/logback-dev.xml"
|
||||
Container / javaOptions += "-Dlogback.configurationFile=/logback-dev.xml"
|
||||
|
||||
// Test settings
|
||||
//testOptions in Test += Tests.Argument("-l", "ExternalDBTest")
|
||||
@@ -152,8 +156,11 @@ executableKey := {
|
||||
// include jetty classes
|
||||
val jettyJars = Keys.update.value select configurationFilter(name = ExecutableConfig.name)
|
||||
jettyJars foreach { jar =>
|
||||
IO unzip (jar, temp, (name: String) =>
|
||||
(name startsWith "javax/") || (name startsWith "org/") || (name startsWith "META-INF/services/"))
|
||||
IO unzip (
|
||||
jar,
|
||||
temp,
|
||||
(name: String) => (name startsWith "javax/") || (name startsWith "org/") || (name startsWith "META-INF/services/")
|
||||
)
|
||||
}
|
||||
|
||||
// include original war file
|
||||
@@ -185,7 +192,7 @@ executableKey := {
|
||||
|
||||
// zip it up
|
||||
IO delete (temp / "META-INF" / "MANIFEST.MF")
|
||||
val contentMappings = (temp.allPaths --- PathFinder(temp)).get pair { file =>
|
||||
val contentMappings = (temp.allPaths --- PathFinder(temp)).get() pair { file =>
|
||||
IO.relativizeFile(temp, file)
|
||||
}
|
||||
val manifest = new JarManifest
|
||||
@@ -209,9 +216,9 @@ executableKey := {
|
||||
outputFile
|
||||
}
|
||||
publishTo := {
|
||||
val nexus = "https://oss.sonatype.org/"
|
||||
if (version.value.trim.endsWith("SNAPSHOT")) Some("snapshots" at nexus + "content/repositories/snapshots")
|
||||
else Some("releases" at nexus + "service/local/staging/deploy/maven2")
|
||||
val centralSnapshots = "https://central.sonatype.com/repository/maven-snapshots/"
|
||||
if (isSnapshot.value) Some("central-snapshots" at centralSnapshots)
|
||||
else localStaging.value
|
||||
}
|
||||
publishMavenStyle := true
|
||||
pomIncludeRepository := { _ =>
|
||||
@@ -277,10 +284,12 @@ Test / testOptions ++= {
|
||||
}
|
||||
}
|
||||
|
||||
Jetty / javaOptions ++= Seq(
|
||||
Container / javaOptions ++= Seq(
|
||||
"-Dlogback.configurationFile=/logback-dev.xml",
|
||||
"-Xdebug",
|
||||
"-Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=8000",
|
||||
"-Dorg.eclipse.jetty.annotations.AnnotationParser.LEVEL=OFF",
|
||||
// "-Ddev-features=keep-session"
|
||||
)
|
||||
Container / containerLibs := Seq(("org.eclipse.jetty" % "jetty-runner" % JettyVersion).intransitive())
|
||||
Container / containerMain := "org.eclipse.jetty.runner.Runner"
|
||||
|
||||
@@ -13,7 +13,7 @@ Run for Development
|
||||
If you want to test GitBucket, type the following command in the root directory of the source tree.
|
||||
|
||||
```shell
|
||||
$ sbt ~jetty:start
|
||||
$ sbt ~container:start
|
||||
```
|
||||
|
||||
Then access `http://localhost:8080/` in your browser. The default administrator account is `root` and password is `root`.
|
||||
@@ -24,11 +24,11 @@ You can modify the logging configuration by editing `src/main/resources/logback-
|
||||
Note that HttpSession is cleared when auto-reloading happened.
|
||||
This is a bit annoying when developing features that requires sign-in.
|
||||
You can keep HttpSession even if GitBucket is restarted by enabling this configuration in `build.sbt`:
|
||||
https://github.com/gitbucket/gitbucket/blob/d5c083b70f7f3748d080166252e9a3dcaf579648/build.sbt#L292
|
||||
https://github.com/gitbucket/gitbucket/blob/3dcc0aee3c4413b05be7c03476626cb202674afc/build.sbt#L292
|
||||
|
||||
Or by launching GitBucket with the following command:
|
||||
```shell
|
||||
sbt '; set Jetty/javaOptions += "-Ddev-features=keep-session" ; ~jetty:start'
|
||||
sbt '; set Container/javaOptions += "-Ddev-features=keep-session" ; ~container:start'
|
||||
```
|
||||
|
||||
Note that this feature serializes HttpSession on the local disk and assigns all requests to the same session
|
||||
|
||||
@@ -12,7 +12,7 @@ javaOptions in Jetty ++= Seq(
|
||||
Run GitBucket:
|
||||
|
||||
```shell
|
||||
$ sbt ~jetty:start
|
||||
$ sbt ~container:start
|
||||
```
|
||||
|
||||
In IntelliJ, create remote debug configuration as follows. Make sure port number is same as above configuration.
|
||||
|
||||
@@ -38,15 +38,26 @@ Generate release files
|
||||
|
||||
For plug-in development, we have to publish the GitBucket jar file to the Maven central repository before release GitBucket itself.
|
||||
|
||||
First, hit following command to publish artifacts to the sonatype OSS repository:
|
||||
First, stage artifacts on your machine:
|
||||
|
||||
```bash
|
||||
$ sbt publishSigned
|
||||
```
|
||||
|
||||
Then logged-in to https://oss.sonatype.org/, close and release the repository.
|
||||
Next, upload artifacts to Sonatype's Central Portal with the following command:
|
||||
|
||||
You need to wait up to a day until [gitbucket-notification-plugin](https://plugins.gitbucket-community.org/) which is default bundled plugin is built for new version of GitBucket.
|
||||
```bash
|
||||
$ sbt sonaUpload
|
||||
```
|
||||
|
||||
Then logged-in to https://central.sonatype.com/ and publish the deployment.
|
||||
|
||||
You need to wait up to a day until default bundled plugins:
|
||||
|
||||
- https://github.com/gitbucket/gitbucket-notifications-plugin
|
||||
- https://github.com/gitbucket/gitbucket-gist-plugin
|
||||
- https://github.com/gitbucket/gitbucket-pages-plugin
|
||||
- https://github.com/gitbucket/gitbucket-emoji-plugin
|
||||
|
||||
### Make release war file
|
||||
|
||||
@@ -55,5 +66,4 @@ Run `sbt executable`. The release war file and fingerprint are generated into `t
|
||||
```bash
|
||||
$ sbt executable
|
||||
```
|
||||
|
||||
Create new release from the corresponded tag on GitHub, then upload generated jar file and fingerprints to the release.
|
||||
|
||||
@@ -1 +1 @@
|
||||
sbt.version=1.10.0
|
||||
sbt.version=1.12.5
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
scalacOptions ++= Seq("-unchecked", "-deprecation", "-feature")
|
||||
|
||||
addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.5.2")
|
||||
addSbtPlugin("org.playframework.twirl" % "sbt-twirl" % "2.0.5")
|
||||
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "2.2.0")
|
||||
addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.5.6")
|
||||
addSbtPlugin("org.playframework.twirl" % "sbt-twirl" % "2.0.9")
|
||||
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "2.3.1")
|
||||
addSbtPlugin("org.scalatra.sbt" % "sbt-scalatra" % "1.0.4")
|
||||
addSbtPlugin("com.github.sbt" % "sbt-pgp" % "2.2.1")
|
||||
addSbtPlugin("com.github.sbt" % "sbt-license-report" % "1.6.1")
|
||||
addSbtPlugin("org.scoverage" % "sbt-scoverage" % "2.0.12")
|
||||
addSbtPlugin("com.github.sbt" % "sbt-pgp" % "2.3.1")
|
||||
addSbtPlugin("com.github.sbt" % "sbt-license-report" % "1.9.0")
|
||||
addSbtPlugin("org.scoverage" % "sbt-scoverage" % "2.4.4")
|
||||
|
||||
addDependencyTreePlugin
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
notifications:1.11.0
|
||||
gist:4.23.0
|
||||
gist:4.24.0
|
||||
emoji:4.6.0
|
||||
pages:1.10.0
|
||||
|
||||
@@ -237,7 +237,7 @@
|
||||
<addForeignKeyConstraint constraintName="IDX_ISSUE_ID_FK1" baseTableName="ISSUE_ID" baseColumnNames="USER_NAME, REPOSITORY_NAME" referencedTableName="REPOSITORY" referencedColumnNames="USER_NAME, REPOSITORY_NAME"/>
|
||||
|
||||
<!--================================================================================================-->
|
||||
<!-- ISSUE_ID -->
|
||||
<!-- ISSUE_LABEL -->
|
||||
<!--================================================================================================-->
|
||||
<createTable tableName="ISSUE_LABEL">
|
||||
<column name="USER_NAME" type="varchar(100)" nullable="false"/>
|
||||
|
||||
20
src/main/resources/update/gitbucket-core_4.42.xml
Normal file
20
src/main/resources/update/gitbucket-core_4.42.xml
Normal file
@@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<changeSet>
|
||||
<dropForeignKeyConstraint constraintName="IDX_PROTECTED_BRANCH_REQUIRE_CONTEXT_FK0" baseTableName="PROTECTED_BRANCH_REQUIRE_CONTEXT"/>
|
||||
<dropPrimaryKey constraintName="IDX_PROTECTED_BRANCH_REQUIRE_CONTEXT_PK" tableName="PROTECTED_BRANCH_REQUIRE_CONTEXT"/>
|
||||
|
||||
<dropForeignKeyConstraint constraintName="IDX_PROTECTED_BRANCH_FK0" baseTableName="PROTECTED_BRANCH"/>
|
||||
<dropPrimaryKey constraintName="IDX_PROTECTED_BRANCH_PK" tableName="PROTECTED_BRANCH"/>
|
||||
|
||||
<modifyDataType columnName="DEFAULT_BRANCH" newDataType="varchar(255)" tableName="REPOSITORY"/>
|
||||
<modifyDataType columnName="BRANCH" newDataType="varchar(255)" tableName="PULL_REQUEST"/>
|
||||
<modifyDataType columnName="REQUEST_BRANCH" newDataType="varchar(255)" tableName="PULL_REQUEST"/>
|
||||
<modifyDataType columnName="BRANCH" newDataType="varchar(255)" tableName="PROTECTED_BRANCH"/>
|
||||
<modifyDataType columnName="BRANCH" newDataType="varchar(255)" tableName="PROTECTED_BRANCH_REQUIRE_CONTEXT"/>
|
||||
|
||||
<addPrimaryKey constraintName="IDX_PROTECTED_BRANCH_PK" tableName="PROTECTED_BRANCH" columnNames="USER_NAME, REPOSITORY_NAME, BRANCH"/>
|
||||
<addForeignKeyConstraint constraintName="IDX_PROTECTED_BRANCH_FK0" baseTableName="PROTECTED_BRANCH" baseColumnNames="USER_NAME, REPOSITORY_NAME" referencedTableName="REPOSITORY" referencedColumnNames="USER_NAME, REPOSITORY_NAME" onDelete="CASCADE" onUpdate="CASCADE"/>
|
||||
|
||||
<addPrimaryKey constraintName="IDX_PROTECTED_BRANCH_REQUIRE_CONTEXT_PK" tableName="PROTECTED_BRANCH_REQUIRE_CONTEXT" columnNames="USER_NAME, REPOSITORY_NAME, BRANCH, CONTEXT"/>
|
||||
<addForeignKeyConstraint constraintName="IDX_PROTECTED_BRANCH_REQUIRE_CONTEXT_FK0" baseTableName="PROTECTED_BRANCH_REQUIRE_CONTEXT" baseColumnNames="USER_NAME, REPOSITORY_NAME, BRANCH" referencedTableName="PROTECTED_BRANCH" referencedColumnNames="USER_NAME, REPOSITORY_NAME, BRANCH" onDelete="CASCADE" onUpdate="CASCADE"/>
|
||||
</changeSet>
|
||||
32
src/main/resources/update/gitbucket-core_4.44.xml
Normal file
32
src/main/resources/update/gitbucket-core_4.44.xml
Normal file
@@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<changeSet>
|
||||
<!--================================================================================================-->
|
||||
<!-- PROTECTED_BRANCH -->
|
||||
<!--================================================================================================-->
|
||||
<addColumn tableName="PROTECTED_BRANCH">
|
||||
<column name="REQUIRED_STATUS_CHECK" type="boolean" nullable="false" defaultValue="false"/>
|
||||
<column name="RESTRICTIONS" type="boolean" nullable="false" defaultValue="false"/>
|
||||
</addColumn>
|
||||
|
||||
<sql>
|
||||
UPDATE PROTECTED_BRANCH SET REQUIRED_STATUS_CHECK = TRUE
|
||||
WHERE EXISTS (SELECT * FROM PROTECTED_BRANCH_REQUIRE_CONTEXT
|
||||
WHERE PROTECTED_BRANCH.USER_NAME = PROTECTED_BRANCH_REQUIRE_CONTEXT.USER_NAME
|
||||
AND PROTECTED_BRANCH.REPOSITORY_NAME = PROTECTED_BRANCH_REQUIRE_CONTEXT.REPOSITORY_NAME
|
||||
AND PROTECTED_BRANCH.BRANCH = PROTECTED_BRANCH_REQUIRE_CONTEXT.BRANCH)
|
||||
</sql>
|
||||
|
||||
<!--================================================================================================-->
|
||||
<!-- PROTECTED_BRANCH_RESTRICTIONS_USER -->
|
||||
<!--================================================================================================-->
|
||||
<createTable tableName="PROTECTED_BRANCH_RESTRICTION">
|
||||
<column name="USER_NAME" type="varchar(100)" nullable="false"/>
|
||||
<column name="REPOSITORY_NAME" type="varchar(100)" nullable="false"/>
|
||||
<column name="BRANCH" type="varchar(100)" nullable="false"/>
|
||||
<column name="ALLOWED_USER" type="varchar(255)" nullable="false"/>
|
||||
</createTable>
|
||||
|
||||
<addPrimaryKey constraintName="IDX_PROTECTED_BRANCH_RESTRICTION_PK" tableName="PROTECTED_BRANCH_RESTRICTION" columnNames="USER_NAME, REPOSITORY_NAME, BRANCH, ALLOWED_USER"/>
|
||||
<addForeignKeyConstraint constraintName="IDX_PROTECTED_BRANCH_RESTRICTION_FK0" baseTableName="PROTECTED_BRANCH_RESTRICTION" baseColumnNames="USER_NAME, REPOSITORY_NAME, BRANCH" referencedTableName="PROTECTED_BRANCH" referencedColumnNames="USER_NAME, REPOSITORY_NAME, BRANCH" onDelete="CASCADE" onUpdate="CASCADE"/>
|
||||
<addForeignKeyConstraint constraintName="IDX_PROTECTED_BRANCH_RESTRICTION_FK1" baseTableName="PROTECTED_BRANCH_RESTRICTION" baseColumnNames="ALLOWED_USER" referencedTableName="ACCOUNT" referencedColumnNames="USER_NAME"/>
|
||||
</changeSet>
|
||||
10
src/main/resources/update/gitbucket-core_4.46.xml
Normal file
10
src/main/resources/update/gitbucket-core_4.46.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<changeSet>
|
||||
<!--================================================================================================-->
|
||||
<!-- PULL_REQUEST -->
|
||||
<!--================================================================================================-->
|
||||
<addColumn tableName="PULL_REQUEST">
|
||||
<column name="MERGED_COMMIT_IDS" type="text" nullable="true"/>
|
||||
</addColumn>
|
||||
|
||||
</changeSet>
|
||||
@@ -4,7 +4,6 @@ import java.io.FileOutputStream
|
||||
import java.nio.charset.StandardCharsets
|
||||
import java.sql.Connection
|
||||
import java.util.UUID
|
||||
|
||||
import gitbucket.core.model.Activity
|
||||
import gitbucket.core.util.Directory.ActivityLog
|
||||
import gitbucket.core.util.JDBCUtil
|
||||
@@ -15,6 +14,7 @@ import org.json4s.{Formats, NoTypeHints}
|
||||
import org.json4s.jackson.Serialization
|
||||
import org.json4s.jackson.Serialization.write
|
||||
|
||||
import java.util.logging.Level
|
||||
import scala.util.Using
|
||||
|
||||
object GitBucketCoreModule
|
||||
@@ -117,5 +117,13 @@ object GitBucketCoreModule
|
||||
new Version("4.38.4"),
|
||||
new Version("4.39.0", new LiquibaseMigration("update/gitbucket-core_4.39.xml")),
|
||||
new Version("4.40.0"),
|
||||
new Version("4.41.0")
|
||||
)
|
||||
new Version("4.41.0"),
|
||||
new Version("4.42.0", new LiquibaseMigration("update/gitbucket-core_4.42.xml")),
|
||||
new Version("4.42.1"),
|
||||
new Version("4.43.0"),
|
||||
new Version("4.44.0", new LiquibaseMigration("update/gitbucket-core_4.44.xml")),
|
||||
new Version("4.45.0"),
|
||||
new Version("4.46.0", new LiquibaseMigration("update/gitbucket-core_4.46.xml"))
|
||||
) {
|
||||
java.util.logging.Logger.getLogger("liquibase").setLevel(Level.SEVERE)
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import gitbucket.core.util.RepositoryName
|
||||
* https://developer.github.com/v3/repos/#get-branch
|
||||
* https://developer.github.com/v3/repos/#enabling-and-disabling-branch-protection
|
||||
*/
|
||||
case class ApiBranch(name: String, commit: ApiBranchCommit, protection: ApiBranchProtection)(
|
||||
case class ApiBranch(name: String, commit: ApiBranchCommit, protection: ApiBranchProtectionResponse)(
|
||||
repositoryName: RepositoryName
|
||||
) extends FieldSerializable {
|
||||
val _links =
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
package gitbucket.core.api
|
||||
|
||||
/** https://developer.github.com/v3/repos/#enabling-and-disabling-branch-protection */
|
||||
case class ApiBranchProtectionRequest(
|
||||
enabled: Boolean,
|
||||
required_status_checks: Option[ApiBranchProtectionRequest.Status],
|
||||
restrictions: Option[ApiBranchProtectionRequest.Restrictions],
|
||||
enforce_admins: Option[Boolean]
|
||||
)
|
||||
|
||||
object ApiBranchProtectionRequest {
|
||||
|
||||
/** form for enabling-and-disabling-branch-protection */
|
||||
case class EnablingAndDisabling(protection: ApiBranchProtectionRequest)
|
||||
|
||||
case class Status(
|
||||
contexts: Seq[String]
|
||||
)
|
||||
|
||||
case class Restrictions(users: Seq[String])
|
||||
}
|
||||
@@ -4,55 +4,68 @@ import gitbucket.core.service.ProtectedBranchService
|
||||
import org.json4s._
|
||||
|
||||
/** https://developer.github.com/v3/repos/#enabling-and-disabling-branch-protection */
|
||||
case class ApiBranchProtection(
|
||||
case class ApiBranchProtectionResponse(
|
||||
url: Option[ApiPath], // for output
|
||||
enabled: Boolean,
|
||||
required_status_checks: Option[ApiBranchProtection.Status]
|
||||
required_status_checks: Option[ApiBranchProtectionResponse.Status],
|
||||
restrictions: Option[ApiBranchProtectionResponse.Restrictions],
|
||||
enforce_admins: Option[ApiBranchProtectionResponse.EnforceAdmins]
|
||||
) {
|
||||
def status: ApiBranchProtection.Status = required_status_checks.getOrElse(ApiBranchProtection.statusNone)
|
||||
def status: ApiBranchProtectionResponse.Status =
|
||||
required_status_checks.getOrElse(ApiBranchProtectionResponse.statusNone)
|
||||
}
|
||||
|
||||
object ApiBranchProtection {
|
||||
object ApiBranchProtectionResponse {
|
||||
|
||||
/** form for enabling-and-disabling-branch-protection */
|
||||
case class EnablingAndDisabling(protection: ApiBranchProtection)
|
||||
case class EnforceAdmins(enabled: Boolean)
|
||||
|
||||
def apply(info: ProtectedBranchService.ProtectedBranchInfo): ApiBranchProtection =
|
||||
ApiBranchProtection(
|
||||
// /** form for enabling-and-disabling-branch-protection */
|
||||
// case class EnablingAndDisabling(protection: ApiBranchProtectionResponse)
|
||||
|
||||
def apply(info: ProtectedBranchService.ProtectedBranchInfo): ApiBranchProtectionResponse =
|
||||
ApiBranchProtectionResponse(
|
||||
url = Some(
|
||||
ApiPath(
|
||||
s"/api/v3/repos/${info.owner}/${info.repository}/branches/${info.branch}/protection"
|
||||
)
|
||||
),
|
||||
enabled = info.enabled,
|
||||
required_status_checks = Some(
|
||||
required_status_checks = info.contexts.map { contexts =>
|
||||
Status(
|
||||
Some(
|
||||
ApiPath(
|
||||
s"/api/v3/repos/${info.owner}/${info.repository}/branches/${info.branch}/protection/required_status_checks"
|
||||
)
|
||||
),
|
||||
EnforcementLevel(info.enabled && info.contexts.nonEmpty, info.includeAdministrators),
|
||||
info.contexts,
|
||||
EnforcementLevel(info.enabled && info.contexts.nonEmpty, info.enforceAdmins),
|
||||
contexts,
|
||||
Some(
|
||||
ApiPath(
|
||||
s"/api/v3/repos/${info.owner}/${info.repository}/branches/${info.branch}/protection/required_status_checks/contexts"
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
},
|
||||
restrictions = info.restrictionsUsers.map { restrictionsUsers =>
|
||||
Restrictions(restrictionsUsers)
|
||||
},
|
||||
enforce_admins = if (info.enabled) Some(EnforceAdmins(info.enforceAdmins)) else None
|
||||
)
|
||||
val statusNone = Status(None, Off, Seq.empty, None)
|
||||
|
||||
val statusNone: Status = Status(None, Off, Seq.empty, None)
|
||||
|
||||
case class Status(
|
||||
url: Option[ApiPath], // for output
|
||||
enforcement_level: EnforcementLevel,
|
||||
contexts: Seq[String],
|
||||
contexts_url: Option[ApiPath] // for output
|
||||
)
|
||||
|
||||
sealed class EnforcementLevel(val name: String)
|
||||
case object Off extends EnforcementLevel("off")
|
||||
case object NonAdmins extends EnforcementLevel("non_admins")
|
||||
case object Everyone extends EnforcementLevel("everyone")
|
||||
|
||||
object EnforcementLevel {
|
||||
def apply(enabled: Boolean, includeAdministrators: Boolean): EnforcementLevel =
|
||||
if (enabled) {
|
||||
@@ -66,6 +79,8 @@ object ApiBranchProtection {
|
||||
}
|
||||
}
|
||||
|
||||
case class Restrictions(users: Seq[String])
|
||||
|
||||
implicit val enforcementLevelSerializer: CustomSerializer[EnforcementLevel] =
|
||||
new CustomSerializer[EnforcementLevel](format =>
|
||||
(
|
||||
@@ -21,7 +21,7 @@ case class ApiRepository(
|
||||
val url = ApiPath(s"/api/v3/repos/${full_name}")
|
||||
val clone_url = ApiPath(s"/git/${full_name}.git")
|
||||
val html_url = ApiPath(s"/${full_name}")
|
||||
val ssh_url = Some(SshPath(""))
|
||||
val ssh_url = Some(SshPath(s"/${full_name}.git"))
|
||||
}
|
||||
|
||||
object ApiRepository {
|
||||
|
||||
29
src/main/scala/gitbucket/core/api/ApiTag.scala
Normal file
29
src/main/scala/gitbucket/core/api/ApiTag.scala
Normal file
@@ -0,0 +1,29 @@
|
||||
package gitbucket.core.api
|
||||
|
||||
import gitbucket.core.util.RepositoryName
|
||||
|
||||
case class ApiTagCommit(
|
||||
sha: String,
|
||||
url: ApiPath
|
||||
)
|
||||
|
||||
case class ApiTag(
|
||||
name: String,
|
||||
commit: ApiTagCommit,
|
||||
zipball_url: ApiPath,
|
||||
tarball_url: ApiPath
|
||||
)
|
||||
|
||||
object ApiTag {
|
||||
def apply(
|
||||
tagName: String,
|
||||
repositoryName: RepositoryName,
|
||||
commitId: String
|
||||
): ApiTag =
|
||||
ApiTag(
|
||||
name = tagName,
|
||||
commit = ApiTagCommit(sha = commitId, url = ApiPath(s"/${repositoryName.fullName}/commits/${commitId}")),
|
||||
zipball_url = ApiPath(s"/${repositoryName.fullName}/archive/${tagName}.zip"),
|
||||
tarball_url = ApiPath(s"/${repositoryName.fullName}/archive/${tagName}.tar.gz")
|
||||
)
|
||||
}
|
||||
@@ -44,7 +44,7 @@ object JsonFormat {
|
||||
FieldSerializer[ApiCommits.File]() +
|
||||
FieldSerializer[ApiRelease]() +
|
||||
FieldSerializer[ApiReleaseAsset]() +
|
||||
ApiBranchProtection.enforcementLevelSerializer
|
||||
ApiBranchProtectionResponse.enforcementLevelSerializer
|
||||
|
||||
def apiPathSerializer(c: Context) =
|
||||
new CustomSerializer[ApiPath](_ =>
|
||||
|
||||
@@ -38,23 +38,11 @@ class AccountController
|
||||
with RequestCache
|
||||
|
||||
trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
self: AccountService
|
||||
with RepositoryService
|
||||
with ActivityService
|
||||
with WikiService
|
||||
with LabelsService
|
||||
with SshKeyService
|
||||
with GpgKeyService
|
||||
with OneselfAuthenticator
|
||||
with UsersAuthenticator
|
||||
with GroupManagerAuthenticator
|
||||
with ReadableUsersAuthenticator
|
||||
with AccessTokenService
|
||||
with WebHookService
|
||||
with PrioritiesService
|
||||
with RepositoryCreationService =>
|
||||
self: AccountService & RepositoryService & ActivityService & WikiService & LabelsService & SshKeyService &
|
||||
GpgKeyService & OneselfAuthenticator & UsersAuthenticator & GroupManagerAuthenticator & ReadableUsersAuthenticator &
|
||||
AccessTokenService & WebHookService & PrioritiesService & RepositoryCreationService =>
|
||||
|
||||
case class AccountNewForm(
|
||||
private case class AccountNewForm(
|
||||
userName: String,
|
||||
password: String,
|
||||
fullName: String,
|
||||
@@ -65,7 +53,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
fileId: Option[String]
|
||||
)
|
||||
|
||||
case class AccountEditForm(
|
||||
private case class AccountEditForm(
|
||||
password: Option[String],
|
||||
fullName: String,
|
||||
mailAddress: String,
|
||||
@@ -76,15 +64,15 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
clearImage: Boolean
|
||||
)
|
||||
|
||||
case class SshKeyForm(title: String, publicKey: String)
|
||||
private case class SshKeyForm(title: String, publicKey: String)
|
||||
|
||||
case class GpgKeyForm(title: String, publicKey: String)
|
||||
private case class GpgKeyForm(title: String, publicKey: String)
|
||||
|
||||
case class PersonalTokenForm(note: String)
|
||||
private case class PersonalTokenForm(note: String)
|
||||
|
||||
case class SyntaxHighlighterThemeForm(theme: String)
|
||||
private case class SyntaxHighlighterThemeForm(theme: String)
|
||||
|
||||
val newForm = mapping(
|
||||
private val newForm = mapping(
|
||||
"userName" -> trim(label("User name", text(required, maxlength(100), identifier, uniqueUserName, reservedNames))),
|
||||
"password" -> trim(label("Password", text(required, maxlength(40)))),
|
||||
"fullName" -> trim(label("Full Name", text(required, maxlength(100)))),
|
||||
@@ -97,7 +85,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
"fileId" -> trim(label("File ID", optional(text())))
|
||||
)(AccountNewForm.apply)
|
||||
|
||||
val editForm = mapping(
|
||||
private val editForm = mapping(
|
||||
"password" -> trim(label("Password", optional(text(maxlength(40))))),
|
||||
"fullName" -> trim(label("Full Name", text(required, maxlength(100)))),
|
||||
"mailAddress" -> trim(label("Mail Address", text(required, maxlength(100), uniqueMailAddress("userName")))),
|
||||
@@ -110,41 +98,41 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
"clearImage" -> trim(label("Clear image", boolean()))
|
||||
)(AccountEditForm.apply)
|
||||
|
||||
val sshKeyForm = mapping(
|
||||
private val sshKeyForm = mapping(
|
||||
"title" -> trim(label("Title", text(required, maxlength(100)))),
|
||||
"publicKey" -> trim2(label("Key", text(required, validPublicKey)))
|
||||
)(SshKeyForm.apply)
|
||||
|
||||
val gpgKeyForm = mapping(
|
||||
private val gpgKeyForm = mapping(
|
||||
"title" -> trim(label("Title", text(required, maxlength(100)))),
|
||||
"publicKey" -> label("Key", text(required, validGpgPublicKey))
|
||||
)(GpgKeyForm.apply)
|
||||
|
||||
val personalTokenForm = mapping(
|
||||
private val personalTokenForm = mapping(
|
||||
"note" -> trim(label("Token", text(required, maxlength(100))))
|
||||
)(PersonalTokenForm.apply)
|
||||
|
||||
val syntaxHighlighterThemeForm = mapping(
|
||||
private val syntaxHighlighterThemeForm = mapping(
|
||||
"highlighterTheme" -> trim(label("Theme", text(required)))
|
||||
)(SyntaxHighlighterThemeForm.apply)
|
||||
|
||||
val resetPasswordEmailForm = mapping(
|
||||
private val resetPasswordEmailForm = mapping(
|
||||
"mailAddress" -> trim(label("Email", text(required)))
|
||||
)(ResetPasswordEmailForm.apply)
|
||||
|
||||
val resetPasswordForm = mapping(
|
||||
private val resetPasswordForm = mapping(
|
||||
"token" -> trim(label("Token", text(required))),
|
||||
"password" -> trim(label("Password", text(required, maxlength(40))))
|
||||
)(ResetPasswordForm.apply)
|
||||
|
||||
case class NewGroupForm(
|
||||
private case class NewGroupForm(
|
||||
groupName: String,
|
||||
description: Option[String],
|
||||
url: Option[String],
|
||||
fileId: Option[String],
|
||||
members: String
|
||||
)
|
||||
case class EditGroupForm(
|
||||
private case class EditGroupForm(
|
||||
groupName: String,
|
||||
description: Option[String],
|
||||
url: Option[String],
|
||||
@@ -152,15 +140,15 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
members: String,
|
||||
clearImage: Boolean
|
||||
)
|
||||
case class ResetPasswordEmailForm(
|
||||
private case class ResetPasswordEmailForm(
|
||||
mailAddress: String
|
||||
)
|
||||
case class ResetPasswordForm(
|
||||
private case class ResetPasswordForm(
|
||||
token: String,
|
||||
password: String
|
||||
)
|
||||
|
||||
val newGroupForm = mapping(
|
||||
private val newGroupForm = mapping(
|
||||
"groupName" -> trim(label("Group name", text(required, maxlength(100), identifier, uniqueUserName, reservedNames))),
|
||||
"description" -> trim(label("Group description", optional(text()))),
|
||||
"url" -> trim(label("URL", optional(text(maxlength(200))))),
|
||||
@@ -168,7 +156,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
"members" -> trim(label("Members", text(required, members)))
|
||||
)(NewGroupForm.apply)
|
||||
|
||||
val editGroupForm = mapping(
|
||||
private val editGroupForm = mapping(
|
||||
"groupName" -> trim(label("Group name", text(required, maxlength(100), identifier))),
|
||||
"description" -> trim(label("Group description", optional(text()))),
|
||||
"url" -> trim(label("URL", optional(text(maxlength(200))))),
|
||||
@@ -177,7 +165,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
"clearImage" -> trim(label("Clear image", boolean()))
|
||||
)(EditGroupForm.apply)
|
||||
|
||||
case class RepositoryCreationForm(
|
||||
private case class RepositoryCreationForm(
|
||||
owner: String,
|
||||
name: String,
|
||||
description: Option[String],
|
||||
@@ -186,7 +174,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
sourceUrl: Option[String]
|
||||
)
|
||||
|
||||
val newRepositoryForm = mapping(
|
||||
private val newRepositoryForm = mapping(
|
||||
"owner" -> trim(label("Owner", text(required, maxlength(100), identifier, existsAccount))),
|
||||
"name" -> trim(label("Repository name", text(required, maxlength(100), repository, uniqueRepository))),
|
||||
"description" -> trim(label("Description", optional(text()))),
|
||||
@@ -195,21 +183,21 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
"sourceUrl" -> trim(label("Source URL", optionalRequired(_.value("initOption") == "COPY", text())))
|
||||
)(RepositoryCreationForm.apply)
|
||||
|
||||
case class AccountForm(accountName: String)
|
||||
private case class AccountForm(accountName: String)
|
||||
|
||||
val accountForm = mapping(
|
||||
private val accountForm = mapping(
|
||||
"account" -> trim(label("Group/User name", text(required, validAccountName)))
|
||||
)(AccountForm.apply)
|
||||
|
||||
// for account web hook url addition.
|
||||
case class AccountWebHookForm(
|
||||
private case class AccountWebHookForm(
|
||||
url: String,
|
||||
events: Set[WebHook.Event],
|
||||
ctype: WebHookContentType,
|
||||
token: Option[String]
|
||||
)
|
||||
|
||||
def accountWebHookForm(update: Boolean) =
|
||||
private def accountWebHookForm(update: Boolean) =
|
||||
mapping(
|
||||
"url" -> trim(label("url", text(required, accountWebHook(update)))),
|
||||
"events" -> accountWebhookEvents,
|
||||
@@ -265,7 +253,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
)
|
||||
|
||||
// Members
|
||||
case "members" if (account.isGroupAccount) => {
|
||||
case "members" if account.isGroupAccount =>
|
||||
val members = getGroupMembers(account.userName)
|
||||
gitbucket.core.account.html.members(
|
||||
account,
|
||||
@@ -273,10 +261,9 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
extraMailAddresses,
|
||||
isGroupManager(context.loginAccount, members)
|
||||
)
|
||||
}
|
||||
|
||||
// Repositories
|
||||
case _ => {
|
||||
case _ =>
|
||||
val members = getGroupMembers(account.userName)
|
||||
gitbucket.core.account.html.repositories(
|
||||
account,
|
||||
@@ -285,7 +272,6 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
extraMailAddresses,
|
||||
isGroupManager(context.loginAccount, members)
|
||||
)
|
||||
}
|
||||
}
|
||||
} getOrElse NotFound()
|
||||
}
|
||||
@@ -293,7 +279,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
get("/:userName.atom") {
|
||||
val userName = params("userName")
|
||||
contentType = "application/atom+xml; type=feed"
|
||||
helper.xml.feed(getActivitiesByUser(userName, true))
|
||||
helper.xml.feed(getActivitiesByUser(userName, publicOnly = true))
|
||||
}
|
||||
|
||||
get("/:userName.keys") {
|
||||
@@ -352,7 +338,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
updateImage(userName, form.fileId, form.clearImage)
|
||||
updateAccountExtraMailAddresses(userName, form.extraMailAddresses.filter(_ != ""))
|
||||
flash.update("info", "Account information has been updated.")
|
||||
redirect(s"/${userName}/_edit")
|
||||
redirect(s"/$userName/_edit")
|
||||
|
||||
} getOrElse NotFound()
|
||||
})
|
||||
@@ -360,10 +346,10 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
get("/:userName/_delete")(oneselfOnly {
|
||||
val userName = params("userName")
|
||||
|
||||
getAccountByUserName(userName, true).map { account =>
|
||||
getAccountByUserName(userName, includeRemoved = true).map { account =>
|
||||
if (isLastAdministrator(account)) {
|
||||
flash.update("error", "Account can't be removed because this is last one administrator.")
|
||||
redirect(s"/${userName}/_edit")
|
||||
redirect(s"/$userName/_edit")
|
||||
} else {
|
||||
// // Remove repositories
|
||||
// getRepositoryNamesOfUser(userName).foreach { repositoryName =>
|
||||
@@ -373,7 +359,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
// FileUtils.deleteDirectory(getTemporaryDir(userName, repositoryName))
|
||||
// }
|
||||
suspendAccount(account)
|
||||
session.invalidate
|
||||
session.invalidate()
|
||||
redirect("/")
|
||||
}
|
||||
} getOrElse NotFound()
|
||||
@@ -389,14 +375,14 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
post("/:userName/_ssh", sshKeyForm)(oneselfOnly { form =>
|
||||
val userName = params("userName")
|
||||
addPublicKey(userName, form.title, form.publicKey)
|
||||
redirect(s"/${userName}/_ssh")
|
||||
redirect(s"/$userName/_ssh")
|
||||
})
|
||||
|
||||
get("/:userName/_ssh/delete/:id")(oneselfOnly {
|
||||
val userName = params("userName")
|
||||
val sshKeyId = params("id").toInt
|
||||
deletePublicKey(userName, sshKeyId)
|
||||
redirect(s"/${userName}/_ssh")
|
||||
redirect(s"/$userName/_ssh")
|
||||
})
|
||||
|
||||
get("/:userName/_gpg")(oneselfOnly {
|
||||
@@ -410,14 +396,14 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
post("/:userName/_gpg", gpgKeyForm)(oneselfOnly { form =>
|
||||
val userName = params("userName")
|
||||
addGpgPublicKey(userName, form.title, form.publicKey)
|
||||
redirect(s"/${userName}/_gpg")
|
||||
redirect(s"/$userName/_gpg")
|
||||
})
|
||||
|
||||
get("/:userName/_gpg/delete/:id")(oneselfOnly {
|
||||
val userName = params("userName")
|
||||
val keyId = params("id").toInt
|
||||
deleteGpgPublicKey(userName, keyId)
|
||||
redirect(s"/${userName}/_gpg")
|
||||
redirect(s"/$userName/_gpg")
|
||||
})
|
||||
|
||||
get("/:userName/_application")(oneselfOnly {
|
||||
@@ -425,13 +411,12 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
getAccountByUserName(userName).map { x =>
|
||||
var tokens = getAccessTokens(x.userName)
|
||||
val generatedToken = flash.get("generatedToken") match {
|
||||
case Some((tokenId: Int, token: String)) => {
|
||||
case Some((tokenId: Int, token: String)) =>
|
||||
val gt = tokens.find(_.accessTokenId == tokenId)
|
||||
gt.map { t =>
|
||||
tokens = tokens.filterNot(_ == t)
|
||||
(t, token)
|
||||
}
|
||||
}
|
||||
case _ => None
|
||||
}
|
||||
html.application(x, tokens, generatedToken)
|
||||
@@ -440,18 +425,18 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
|
||||
post("/:userName/_personalToken", personalTokenForm)(oneselfOnly { form =>
|
||||
val userName = params("userName")
|
||||
getAccountByUserName(userName).foreach { x =>
|
||||
getAccountByUserName(userName).foreach { _ =>
|
||||
val (tokenId, token) = generateAccessToken(userName, form.note)
|
||||
flash.update("generatedToken", (tokenId, token))
|
||||
}
|
||||
redirect(s"/${userName}/_application")
|
||||
redirect(s"/$userName/_application")
|
||||
})
|
||||
|
||||
get("/:userName/_personalToken/delete/:id")(oneselfOnly {
|
||||
val userName = params("userName")
|
||||
val tokenId = params("id").toInt
|
||||
deleteAccessToken(userName, tokenId)
|
||||
redirect(s"/${userName}/_application")
|
||||
redirect(s"/$userName/_application")
|
||||
})
|
||||
|
||||
/**
|
||||
@@ -474,7 +459,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
post("/:userName/_preferences/highlighter", syntaxHighlighterThemeForm)(oneselfOnly { form =>
|
||||
val userName = params("userName")
|
||||
addOrUpdateAccountPreference(userName, form.theme)
|
||||
redirect(s"/${userName}/_preferences")
|
||||
redirect(s"/$userName/_preferences")
|
||||
})
|
||||
|
||||
get("/:userName/_hooks")(managersOnly {
|
||||
@@ -491,7 +476,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
val userName = params("userName")
|
||||
getAccountByUserName(userName).map { account =>
|
||||
val webhook = AccountWebHook(userName, "", WebHookContentType.FORM, None)
|
||||
html.edithook(webhook, Set(WebHook.Push), account, true)
|
||||
html.edithook(webhook, Set(WebHook.Push), account, create = true)
|
||||
} getOrElse NotFound()
|
||||
})
|
||||
|
||||
@@ -502,7 +487,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
val userName = params("userName")
|
||||
addAccountWebHook(userName, form.url, form.events, form.ctype, form.token)
|
||||
flash.update("info", s"Webhook ${form.url} created")
|
||||
redirect(s"/${userName}/_hooks")
|
||||
redirect(s"/$userName/_hooks")
|
||||
})
|
||||
|
||||
/**
|
||||
@@ -512,7 +497,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
val userName = params("userName")
|
||||
deleteAccountWebHook(userName, params("url"))
|
||||
flash.update("info", s"Webhook ${params("url")} deleted")
|
||||
redirect(s"/${userName}/_hooks")
|
||||
redirect(s"/$userName/_hooks")
|
||||
})
|
||||
|
||||
/**
|
||||
@@ -522,7 +507,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
val userName = params("userName")
|
||||
getAccountByUserName(userName).flatMap { account =>
|
||||
getAccountWebHook(userName, params("url")).map { case (webhook, events) =>
|
||||
html.edithook(webhook, events, account, false)
|
||||
html.edithook(webhook, events, account, create = false)
|
||||
}
|
||||
} getOrElse NotFound()
|
||||
})
|
||||
@@ -534,7 +519,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
val userName = params("userName")
|
||||
updateAccountWebHook(userName, form.url, form.events, form.ctype, form.token)
|
||||
flash.update("info", s"webhook ${form.url} updated")
|
||||
redirect(s"/${userName}/_hooks")
|
||||
redirect(s"/$userName/_hooks")
|
||||
})
|
||||
|
||||
/**
|
||||
@@ -542,8 +527,8 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
*/
|
||||
ajaxPost("/:userName/_hooks/test")(managersOnly {
|
||||
// TODO Is it possible to merge with [[RepositorySettingsController.ajaxPost]]?
|
||||
import scala.concurrent.duration._
|
||||
import scala.concurrent._
|
||||
import scala.concurrent.duration.*
|
||||
import scala.concurrent.*
|
||||
import scala.util.control.NonFatal
|
||||
import org.apache.http.util.EntityUtils
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
@@ -567,10 +552,10 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
callWebHook(WebHook.Push, List(dummyWebHookInfo), dummyPayload, context.settings).head
|
||||
|
||||
val toErrorMap: PartialFunction[Throwable, Map[String, String]] = {
|
||||
case e: java.net.UnknownHostException => Map("error" -> ("Unknown host " + e.getMessage))
|
||||
case e: java.lang.IllegalArgumentException => Map("error" -> ("invalid url"))
|
||||
case e: org.apache.http.client.ClientProtocolException => Map("error" -> ("invalid url"))
|
||||
case NonFatal(e) => Map("error" -> (s"${e.getClass} ${e.getMessage}"))
|
||||
case e: java.net.UnknownHostException => Map("error" -> s"Unknown host ${e.getMessage}")
|
||||
case _: java.lang.IllegalArgumentException => Map("error" -> "invalid url")
|
||||
case _: org.apache.http.client.ClientProtocolException => Map("error" -> "invalid url")
|
||||
case NonFatal(e) => Map("error" -> s"${e.getClass} ${e.getMessage}")
|
||||
}
|
||||
|
||||
contentType = formats("json")
|
||||
@@ -592,9 +577,9 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
resFuture
|
||||
.map(res =>
|
||||
Map(
|
||||
"status" -> res.getStatusLine(),
|
||||
"body" -> EntityUtils.toString(res.getEntity()),
|
||||
"headers" -> _headers(res.getAllHeaders())
|
||||
"status" -> res.getStatusLine,
|
||||
"body" -> EntityUtils.toString(res.getEntity),
|
||||
"headers" -> _headers(res.getAllHeaders)
|
||||
)
|
||||
)
|
||||
.recover(toErrorMap),
|
||||
@@ -621,11 +606,11 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
pbkdf2_sha256(form.password),
|
||||
form.fullName,
|
||||
form.mailAddress,
|
||||
false,
|
||||
isAdmin = false,
|
||||
form.description,
|
||||
form.url
|
||||
)
|
||||
updateImage(form.userName, form.fileId, false)
|
||||
updateImage(form.userName, form.fileId, clearImage = false)
|
||||
updateAccountExtraMailAddresses(form.userName, form.extraMailAddresses.filter(_ != ""))
|
||||
redirect("/signin")
|
||||
} else NotFound()
|
||||
@@ -650,7 +635,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
|You requested to reset the password for your GitBucket account.
|
||||
|If you are not sure about the request, you can ignore this email.
|
||||
|Otherwise, click the following link to set the new password:
|
||||
|${context.baseUrl}/reset/form/${token}
|
||||
|${context.baseUrl}/reset/form/$token
|
||||
|""".stripMargin
|
||||
)
|
||||
}
|
||||
@@ -690,7 +675,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
|
||||
get("/groups/new")(usersOnly {
|
||||
context.withLoginAccount { loginAccount =>
|
||||
html.creategroup(List(GroupMember("", loginAccount.userName, true)))
|
||||
html.creategroup(List(GroupMember("", loginAccount.userName, isManager = true)))
|
||||
}
|
||||
})
|
||||
|
||||
@@ -707,13 +692,13 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
}
|
||||
.toList
|
||||
)
|
||||
updateImage(form.groupName, form.fileId, false)
|
||||
updateImage(form.groupName, form.fileId, clearImage = false)
|
||||
redirect(s"/${form.groupName}")
|
||||
})
|
||||
|
||||
get("/:groupName/_editgroup")(managersOnly {
|
||||
val groupName = params("groupName")
|
||||
getAccountByUserName(groupName, true).map { account =>
|
||||
getAccountByUserName(groupName, includeRemoved = true).map { account =>
|
||||
html.editgroup(account, getGroupMembers(groupName), flash.get("info"))
|
||||
} getOrElse NotFound()
|
||||
})
|
||||
@@ -723,8 +708,8 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
// Remove from GROUP_MEMBER
|
||||
updateGroupMembers(groupName, Nil)
|
||||
// Disable group
|
||||
getAccountByUserName(groupName, false).foreach { account =>
|
||||
updateGroup(groupName, account.description, account.url, true)
|
||||
getAccountByUserName(groupName, includeRemoved = false).foreach { account =>
|
||||
updateGroup(groupName, account.description, account.url, removed = true)
|
||||
}
|
||||
// // Remove repositories
|
||||
// getRepositoryNamesOfUser(groupName).foreach { repositoryName =>
|
||||
@@ -747,8 +732,8 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
}
|
||||
.toList
|
||||
|
||||
getAccountByUserName(groupName, true).map { account =>
|
||||
updateGroup(groupName, form.description, form.url, false)
|
||||
getAccountByUserName(groupName, includeRemoved = true).map { _ =>
|
||||
updateGroup(groupName, form.description, form.url, removed = false)
|
||||
|
||||
// Update GROUP_MEMBER
|
||||
updateGroupMembers(form.groupName, members)
|
||||
@@ -763,7 +748,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
updateImage(form.groupName, form.fileId, form.clearImage)
|
||||
|
||||
flash.update("info", "Account information has been updated.")
|
||||
redirect(s"/${groupName}/_editgroup")
|
||||
redirect(s"/$groupName/_editgroup")
|
||||
|
||||
} getOrElse NotFound()
|
||||
})
|
||||
@@ -831,7 +816,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
repository,
|
||||
(groups zip managerPermissions).sortBy(_._1)
|
||||
)
|
||||
case _ => redirect(s"/${loginUserName}")
|
||||
case _ => redirect(s"/$loginUserName")
|
||||
}
|
||||
} else BadRequest()
|
||||
}
|
||||
@@ -847,7 +832,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
|
||||
if (getRepository(accountName, repository.name).isDefined) {
|
||||
// redirect to the repository if repository already exists
|
||||
redirect(s"/${accountName}/${repository.name}")
|
||||
redirect(s"/$accountName/${repository.name}")
|
||||
} else if (!canCreateRepository(accountName, loginAccount)) {
|
||||
// Permission error
|
||||
Forbidden()
|
||||
@@ -855,7 +840,7 @@ trait AccountControllerBase extends AccountManagementControllerBase {
|
||||
// fork repository asynchronously
|
||||
forkRepository(accountName, repository, loginUserName)
|
||||
// redirect to the repository
|
||||
redirect(s"/${accountName}/${repository.name}")
|
||||
redirect(s"/$accountName/${repository.name}")
|
||||
}
|
||||
} else Forbidden()
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
package gitbucket.core.controller
|
||||
|
||||
import gitbucket.core.api._
|
||||
import gitbucket.core.controller.api._
|
||||
import gitbucket.core.service._
|
||||
import gitbucket.core.util.Implicits._
|
||||
import gitbucket.core.util._
|
||||
import gitbucket.core.api.*
|
||||
import gitbucket.core.controller.api.*
|
||||
import gitbucket.core.service.*
|
||||
import gitbucket.core.util.Implicits.*
|
||||
import gitbucket.core.util.*
|
||||
import gitbucket.core.plugin.PluginRegistry
|
||||
|
||||
class ApiController
|
||||
|
||||
@@ -4,15 +4,15 @@ import java.io.{File, FileInputStream, FileOutputStream}
|
||||
import gitbucket.core.api.{ApiError, JsonFormat}
|
||||
import gitbucket.core.model.Account
|
||||
import gitbucket.core.service.{AccountService, RepositoryService, SystemSettingsService}
|
||||
import gitbucket.core.util.SyntaxSugars._
|
||||
import gitbucket.core.util.Directory._
|
||||
import gitbucket.core.util.Implicits._
|
||||
import gitbucket.core.util._
|
||||
import org.json4s._
|
||||
import org.scalatra._
|
||||
import org.scalatra.i18n._
|
||||
import org.scalatra.json._
|
||||
import org.scalatra.forms._
|
||||
import gitbucket.core.util.SyntaxSugars.*
|
||||
import gitbucket.core.util.Directory.*
|
||||
import gitbucket.core.util.Implicits.*
|
||||
import gitbucket.core.util.*
|
||||
import org.json4s.*
|
||||
import org.scalatra.{MultiParams, *}
|
||||
import org.scalatra.i18n.*
|
||||
import org.scalatra.json.*
|
||||
import org.scalatra.forms.*
|
||||
|
||||
import javax.servlet.http.{HttpServletRequest, HttpServletResponse}
|
||||
import javax.servlet.{FilterChain, ServletRequest, ServletResponse}
|
||||
@@ -24,7 +24,7 @@ import net.coobird.thumbnailator.Thumbnails
|
||||
import org.eclipse.jgit.api.Git
|
||||
import org.eclipse.jgit.lib.ObjectId
|
||||
import org.eclipse.jgit.revwalk.RevCommit
|
||||
import org.eclipse.jgit.treewalk._
|
||||
import org.eclipse.jgit.treewalk.*
|
||||
import org.apache.commons.io.IOUtils
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.json4s.Formats
|
||||
@@ -48,11 +48,21 @@ abstract class ControllerBase
|
||||
|
||||
implicit val jsonFormats: Formats = gitbucket.core.api.JsonFormat.jsonFormats
|
||||
|
||||
private case class HttpException(status: Int) extends RuntimeException
|
||||
|
||||
before("/api/v3/*") {
|
||||
contentType = formats("json")
|
||||
request.setAttribute(Keys.Request.APIv3, true)
|
||||
}
|
||||
|
||||
override def multiParams(implicit request: HttpServletRequest): MultiParams = {
|
||||
try {
|
||||
super.multiParams
|
||||
} catch {
|
||||
case _: Exception => throw HttpException(400)
|
||||
}
|
||||
}
|
||||
|
||||
override def requestPath(uri: String, idx: Int): String = {
|
||||
val path = super.requestPath(uri, idx)
|
||||
if (path != "/" && path.endsWith("/")) {
|
||||
@@ -86,11 +96,10 @@ abstract class ControllerBase
|
||||
*/
|
||||
implicit def context: Context = {
|
||||
contextCache.get match {
|
||||
case null => {
|
||||
case null =>
|
||||
val context = Context(loadSystemSettings(), LoginAccount, request)
|
||||
contextCache.set(context)
|
||||
context
|
||||
}
|
||||
case context => context
|
||||
}
|
||||
}
|
||||
@@ -130,7 +139,7 @@ abstract class ControllerBase
|
||||
action(form)
|
||||
}
|
||||
|
||||
protected def NotFound() =
|
||||
protected def NotFound(): ActionResult =
|
||||
if (request.hasAttribute(Keys.Request.Ajax)) {
|
||||
org.scalatra.NotFound()
|
||||
} else if (request.hasAttribute(Keys.Request.APIv3)) {
|
||||
@@ -150,7 +159,7 @@ abstract class ControllerBase
|
||||
}
|
||||
}
|
||||
|
||||
protected def Unauthorized()(implicit context: Context) =
|
||||
protected def Unauthorized()(implicit context: Context): ActionResult =
|
||||
if (request.hasAttribute(Keys.Request.Ajax)) {
|
||||
org.scalatra.Unauthorized()
|
||||
} else if (request.hasAttribute(Keys.Request.APIv3)) {
|
||||
@@ -178,7 +187,9 @@ abstract class ControllerBase
|
||||
}
|
||||
|
||||
error {
|
||||
case e => {
|
||||
case e: HttpException =>
|
||||
ActionResult(e.status, (), Map.empty)
|
||||
case e =>
|
||||
logger.error(s"Catch unhandled error in request: ${request}", e)
|
||||
if (request.hasAttribute(Keys.Request.Ajax)) {
|
||||
org.scalatra.InternalServerError()
|
||||
@@ -188,7 +199,6 @@ abstract class ControllerBase
|
||||
} else {
|
||||
org.scalatra.InternalServerError(gitbucket.core.html.error("Internal Server Error", Some(e)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override def url(
|
||||
@@ -200,7 +210,7 @@ abstract class ControllerBase
|
||||
withSessionId: Boolean = true
|
||||
)(implicit request: HttpServletRequest, response: HttpServletResponse): String =
|
||||
if (path.startsWith("http")) path
|
||||
else baseUrl + super.url(path, params, false, false, false)
|
||||
else baseUrl + super.url(path, params, includeContextPath = false, includeServletPath = false, absolutize = false)
|
||||
|
||||
/**
|
||||
* Extends scalatra-form's trim rule to eliminate CR and LF.
|
||||
@@ -244,9 +254,9 @@ abstract class ControllerBase
|
||||
protected def getPathObjectId(git: Git, path: String, revCommit: RevCommit): Option[ObjectId] = {
|
||||
@scala.annotation.tailrec
|
||||
def _getPathObjectId(path: String, walk: TreeWalk): Option[ObjectId] = walk.next match {
|
||||
case true if (walk.getPathString == path) => Some(walk.getObjectId(0))
|
||||
case true => _getPathObjectId(path, walk)
|
||||
case false => None
|
||||
case true if walk.getPathString == path => Some(walk.getObjectId(0))
|
||||
case true => _getPathObjectId(path, walk)
|
||||
case false => None
|
||||
}
|
||||
|
||||
Using.resource(new TreeWalk(git.getRepository)) { treeWalk =>
|
||||
@@ -338,18 +348,18 @@ case class Context(
|
||||
loginAccount: Option[Account],
|
||||
request: HttpServletRequest
|
||||
) {
|
||||
val path = settings.baseUrl.getOrElse(request.getContextPath)
|
||||
val currentPath = request.getRequestURI.substring(request.getContextPath.length)
|
||||
val baseUrl = settings.baseUrl(request)
|
||||
val host = new java.net.URL(baseUrl).getHost
|
||||
val platform = request.getHeader("User-Agent") match {
|
||||
val path: String = settings.baseUrl.getOrElse(request.getContextPath)
|
||||
val currentPath: String = request.getRequestURI.substring(request.getContextPath.length)
|
||||
val baseUrl: String = settings.baseUrl(request)
|
||||
val host: String = new java.net.URI(baseUrl).toURL.getHost
|
||||
val platform: String = request.getHeader("User-Agent") match {
|
||||
case null => null
|
||||
case agent if agent.contains("Mac") => "mac"
|
||||
case agent if agent.contains("Linux") => "linux"
|
||||
case agent if agent.contains("Win") => "windows"
|
||||
case _ => null
|
||||
}
|
||||
val sidebarCollapse = request.getSession.getAttribute("sidebar-collapse") != null
|
||||
val sidebarCollapse: Boolean = request.getSession.getAttribute("sidebar-collapse") != null
|
||||
|
||||
def withLoginAccount(f: Account => Any): Any = {
|
||||
loginAccount match {
|
||||
@@ -430,9 +440,9 @@ trait AccountManagementControllerBase extends ControllerBase {
|
||||
) {
|
||||
Some("These mail addresses are duplicated.")
|
||||
} else {
|
||||
getAccountByMailAddress(value, true)
|
||||
getAccountByMailAddress(value, includeRemoved = true)
|
||||
.collect {
|
||||
case x if paramName.isEmpty || Some(x.userName) != params.optionValue(paramName) =>
|
||||
case x if paramName.isEmpty || !params.optionValue(paramName).contains(x.userName) =>
|
||||
"Mail address is already registered."
|
||||
}
|
||||
}
|
||||
@@ -448,22 +458,22 @@ trait AccountManagementControllerBase extends ControllerBase {
|
||||
): Option[String] = {
|
||||
val extraMailAddresses = params.view.filterKeys(k => k.startsWith("extraMailAddresses"))
|
||||
if (
|
||||
Some(value) == params.optionValue("mailAddress") || extraMailAddresses.count { case (k, v) =>
|
||||
params.optionValue("mailAddress").contains(value) || extraMailAddresses.count { case (k, v) =>
|
||||
v.contains(value)
|
||||
} > 1
|
||||
) {
|
||||
Some("These mail addresses are duplicated.")
|
||||
} else {
|
||||
getAccountByMailAddress(value, true)
|
||||
getAccountByMailAddress(value, includeRemoved = true)
|
||||
.collect {
|
||||
case x if paramName.isEmpty || Some(x.userName) != params.optionValue(paramName) =>
|
||||
case x if paramName.isEmpty || !params.optionValue(paramName).contains(x.userName) =>
|
||||
"Mail address is already registered."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val allReservedNames = Set(
|
||||
private val allReservedNames = Set(
|
||||
"git",
|
||||
"admin",
|
||||
"upload",
|
||||
@@ -482,10 +492,9 @@ trait AccountManagementControllerBase extends ControllerBase {
|
||||
protected def reservedNames: Constraint = new Constraint() {
|
||||
override def validate(name: String, value: String, messages: Messages): Option[String] =
|
||||
if (allReservedNames.contains(value.toLowerCase)) {
|
||||
Some(s"${value} is reserved")
|
||||
Some(s"$value is reserved")
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,11 +2,11 @@ package gitbucket.core.controller
|
||||
|
||||
import gitbucket.core.dashboard.html
|
||||
import gitbucket.core.model.Account
|
||||
import gitbucket.core.service._
|
||||
import gitbucket.core.util.{Keys, UsersAuthenticator}
|
||||
import gitbucket.core.util.Implicits._
|
||||
import gitbucket.core.service.IssuesService._
|
||||
import gitbucket.core.service.ActivityService._
|
||||
import gitbucket.core.service.*
|
||||
import gitbucket.core.util.UsersAuthenticator
|
||||
import gitbucket.core.util.Implicits.*
|
||||
import gitbucket.core.service.IssuesService.*
|
||||
import gitbucket.core.service.ActivityService.*
|
||||
|
||||
class DashboardController
|
||||
extends DashboardControllerBase
|
||||
@@ -28,12 +28,8 @@ class DashboardController
|
||||
with RequestCache
|
||||
|
||||
trait DashboardControllerBase extends ControllerBase {
|
||||
self: IssuesService
|
||||
with PullRequestService
|
||||
with RepositoryService
|
||||
with AccountService
|
||||
with CommitStatusService
|
||||
with UsersAuthenticator =>
|
||||
self: IssuesService & PullRequestService & RepositoryService & AccountService & CommitStatusService &
|
||||
UsersAuthenticator =>
|
||||
|
||||
get("/dashboard/repos")(usersOnly {
|
||||
context.withLoginAccount { loginAccount =>
|
||||
@@ -95,7 +91,7 @@ trait DashboardControllerBase extends ControllerBase {
|
||||
}
|
||||
})
|
||||
|
||||
private def getOrCreateCondition(key: String, filter: String, userName: String) = {
|
||||
private def getOrCreateCondition(filter: String, userName: String) = {
|
||||
val condition = IssueSearchCondition(request)
|
||||
|
||||
filter match {
|
||||
@@ -106,11 +102,11 @@ trait DashboardControllerBase extends ControllerBase {
|
||||
}
|
||||
|
||||
private def searchIssues(loginAccount: Account, filter: String) = {
|
||||
import IssuesService._
|
||||
import IssuesService.*
|
||||
|
||||
val userName = loginAccount.userName
|
||||
val condition = getOrCreateCondition(Keys.Session.DashboardIssues, filter, userName)
|
||||
val userRepos = getUserRepositories(userName, true).map(repo => repo.owner -> repo.name)
|
||||
val condition = getOrCreateCondition(filter, userName)
|
||||
val userRepos = getUserRepositories(userName, withoutPhysicalInfo = true).map(repo => repo.owner -> repo.name)
|
||||
val page = IssueSearchCondition.page(request)
|
||||
val issues = searchIssue(condition, IssueSearchOption.Issues, (page - 1) * IssueLimit, IssueLimit, userRepos*)
|
||||
|
||||
@@ -137,11 +133,11 @@ trait DashboardControllerBase extends ControllerBase {
|
||||
}
|
||||
|
||||
private def searchPullRequests(loginAccount: Account, filter: String) = {
|
||||
import IssuesService._
|
||||
import PullRequestService._
|
||||
import IssuesService.*
|
||||
import PullRequestService.*
|
||||
|
||||
val userName = loginAccount.userName
|
||||
val condition = getOrCreateCondition(Keys.Session.DashboardPulls, filter, userName)
|
||||
val condition = getOrCreateCondition(filter, userName)
|
||||
val allRepos = getAllRepositories(userName)
|
||||
val page = IssueSearchCondition.page(request)
|
||||
val issues = searchIssue(
|
||||
|
||||
@@ -84,7 +84,7 @@ class FileUploadController
|
||||
execute(
|
||||
{ (file, fileId) =>
|
||||
val fileName = file.getName
|
||||
LockUtil.lock(s"${owner}/${repository}/wiki") {
|
||||
LockUtil.lock(s"$owner/$repository/wiki") {
|
||||
Using.resource(Git.open(Directory.getWikiRepositoryDir(owner, repository))) { git =>
|
||||
val builder = DirCache.newInCore.builder()
|
||||
val inserter = git.getRepository.newObjectInserter()
|
||||
@@ -108,7 +108,7 @@ class FileUploadController
|
||||
)
|
||||
builder.finish()
|
||||
|
||||
val newHeadId = JGitUtil.createNewCommit(
|
||||
JGitUtil.createNewCommit(
|
||||
git,
|
||||
inserter,
|
||||
headId,
|
||||
@@ -116,7 +116,7 @@ class FileUploadController
|
||||
Constants.HEAD,
|
||||
loginAccount.fullName,
|
||||
loginAccount.mailAddress,
|
||||
s"Uploaded ${fileName}"
|
||||
s"Uploaded $fileName"
|
||||
)
|
||||
|
||||
fileName
|
||||
@@ -151,7 +151,7 @@ class FileUploadController
|
||||
}
|
||||
|
||||
post("/import") {
|
||||
import JDBCUtil._
|
||||
import JDBCUtil.*
|
||||
setMultipartConfig()
|
||||
session.get(Keys.Session.LoginAccount).collect {
|
||||
case loginAccount: Account if loginAccount.isAdmin =>
|
||||
@@ -168,13 +168,13 @@ class FileUploadController
|
||||
private def setMultipartConfig(): Unit = {
|
||||
val settings = loadSystemSettings()
|
||||
val config = MultipartConfig(maxFileSize = Some(settings.upload.maxFileSize))
|
||||
config.apply(request.getServletContext())
|
||||
config.apply(request.getServletContext)
|
||||
}
|
||||
|
||||
private def setMultipartConfigForLargeFile(): Unit = {
|
||||
val settings = loadSystemSettings()
|
||||
val config = MultipartConfig(maxFileSize = Some(settings.upload.largeMaxFileSize))
|
||||
config.apply(request.getServletContext())
|
||||
config.apply(request.getServletContext)
|
||||
}
|
||||
|
||||
private def onlyWikiEditable(owner: String, repository: String, loginAccount: Account)(action: => Any): Any = {
|
||||
@@ -191,7 +191,7 @@ class FileUploadController
|
||||
}
|
||||
}
|
||||
|
||||
private def execute(f: (FileItem, String) => Unit, mimeTypeChecker: (String) => Boolean) =
|
||||
private def execute(f: (FileItem, String) => Unit, mimeTypeChecker: String => Boolean) =
|
||||
fileParams.get("file") match {
|
||||
case Some(file) if mimeTypeChecker(file.name) =>
|
||||
val fileId = FileUtil.generateFileId
|
||||
|
||||
@@ -7,14 +7,14 @@ import com.nimbusds.oauth2.sdk.id.State
|
||||
import com.nimbusds.openid.connect.sdk.Nonce
|
||||
import gitbucket.core.helper.xml
|
||||
import gitbucket.core.model.Account
|
||||
import gitbucket.core.service._
|
||||
import gitbucket.core.util.Implicits._
|
||||
import gitbucket.core.util._
|
||||
import gitbucket.core.view.helpers._
|
||||
import gitbucket.core.service.*
|
||||
import gitbucket.core.util.Implicits.*
|
||||
import gitbucket.core.util.*
|
||||
import gitbucket.core.view.helpers.*
|
||||
import org.scalatra.Ok
|
||||
import org.scalatra.forms._
|
||||
|
||||
import gitbucket.core.service.ActivityService._
|
||||
import org.scalatra.forms.*
|
||||
import gitbucket.core.service.ActivityService.*
|
||||
import gitbucket.core.view.helpers
|
||||
|
||||
class IndexController
|
||||
extends IndexControllerBase
|
||||
@@ -34,19 +34,12 @@ class IndexController
|
||||
with RequestCache
|
||||
|
||||
trait IndexControllerBase extends ControllerBase {
|
||||
self: RepositoryService
|
||||
with ActivityService
|
||||
with AccountService
|
||||
with RepositorySearchService
|
||||
with UsersAuthenticator
|
||||
with ReferrerAuthenticator
|
||||
with AccessTokenService
|
||||
with AccountFederationService
|
||||
with OpenIDConnectService =>
|
||||
self: RepositoryService & ActivityService & AccountService & RepositorySearchService & UsersAuthenticator &
|
||||
ReferrerAuthenticator & AccessTokenService & AccountFederationService & OpenIDConnectService =>
|
||||
|
||||
case class SignInForm(userName: String, password: String, hash: Option[String])
|
||||
private case class SignInForm(userName: String, password: String, hash: Option[String])
|
||||
|
||||
val signinForm = mapping(
|
||||
private val signinForm = mapping(
|
||||
"userName" -> trim(label("Username", text(required))),
|
||||
"password" -> trim(label("Password", text(required))),
|
||||
"hash" -> trim(optional(text()))
|
||||
@@ -60,13 +53,13 @@ trait IndexControllerBase extends ControllerBase {
|
||||
//
|
||||
// case class SearchForm(query: String, owner: String, repository: String)
|
||||
|
||||
case class OidcAuthContext(state: State, nonce: Nonce, redirectBackURI: String)
|
||||
case class OidcSessionContext(token: JWT)
|
||||
private case class OidcAuthContext(state: State, nonce: Nonce, redirectBackURI: String)
|
||||
private case class OidcSessionContext(token: JWT)
|
||||
|
||||
get("/") {
|
||||
context.loginAccount
|
||||
.map { account =>
|
||||
val visibleOwnerSet: Set[String] = Set(account.userName) ++ getGroupsByUserName(account.userName)
|
||||
// val visibleOwnerSet: Set[String] = Set(account.userName) ++ getGroupsByUserName(account.userName)
|
||||
if (!isNewsFeedEnabled) {
|
||||
redirect("/dashboard/repos")
|
||||
} else {
|
||||
@@ -99,10 +92,18 @@ trait IndexControllerBase extends ControllerBase {
|
||||
}
|
||||
}
|
||||
|
||||
get("/_is_renderable") {
|
||||
helpers.isRenderable(params("filename"))
|
||||
}
|
||||
|
||||
get("/signin") {
|
||||
val redirect = params.get("redirect")
|
||||
if (redirect.isDefined && redirect.get.startsWith("/")) {
|
||||
flash.update(Keys.Flash.Redirect, redirect.get)
|
||||
if (context.loginAccount.nonEmpty) {
|
||||
redirect("/")
|
||||
}
|
||||
params.get("redirect").foreach { redirect =>
|
||||
if (redirect.startsWith("/")) {
|
||||
flash.update(Keys.Flash.Redirect, redirect)
|
||||
}
|
||||
}
|
||||
gitbucket.core.html.signin(flash.get("userName"), flash.get("password"), flash.get("error"))
|
||||
}
|
||||
@@ -199,6 +200,16 @@ trait IndexControllerBase extends ControllerBase {
|
||||
Ok()
|
||||
}
|
||||
|
||||
get("/user.css") {
|
||||
context.settings.userDefinedCss match {
|
||||
case Some(css) =>
|
||||
contentType = "text/css"
|
||||
css
|
||||
case None =>
|
||||
NotFound()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set account information into HttpSession and redirect.
|
||||
*/
|
||||
@@ -229,7 +240,7 @@ trait IndexControllerBase extends ControllerBase {
|
||||
val group = params("group").toBoolean
|
||||
org.json4s.jackson.Serialization.write(
|
||||
Map(
|
||||
"options" -> (
|
||||
"options" ->
|
||||
getAllUsers(includeRemoved = false)
|
||||
.withFilter { t =>
|
||||
(user, group) match {
|
||||
@@ -240,26 +251,45 @@ trait IndexControllerBase extends ControllerBase {
|
||||
}
|
||||
}
|
||||
.map { t =>
|
||||
Map(
|
||||
"label" -> s"${avatar(t.userName, 16)}<b>@${StringUtil.escapeHtml(
|
||||
StringUtil.cutTail(t.userName, 25, "...")
|
||||
)}</b> ${StringUtil
|
||||
.escapeHtml(StringUtil.cutTail(t.fullName, 25, "..."))}",
|
||||
"value" -> t.userName
|
||||
)
|
||||
if (t.isGroupAccount) {
|
||||
Map(
|
||||
"label" -> s"${avatar(t.userName, 16)} <b>@${StringUtil.escapeHtml(
|
||||
StringUtil.cutTail(t.userName, 25, "...")
|
||||
)}</b>",
|
||||
"value" -> t.userName
|
||||
)
|
||||
} else {
|
||||
Map(
|
||||
"label" -> s"${avatar(t.userName, 16)} <b>@${StringUtil.escapeHtml(
|
||||
StringUtil.cutTail(t.userName, 25, "...")
|
||||
)}</b> (${StringUtil
|
||||
.escapeHtml(StringUtil.cutTail(t.fullName, 25, "..."))})",
|
||||
"value" -> t.userName
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
)
|
||||
)
|
||||
})
|
||||
|
||||
/**
|
||||
* JSON API for checking user or group existence.
|
||||
*
|
||||
* Returns a single string which is any of "group", "user" or "".
|
||||
* Additionally, check whether the user is writable to the repository
|
||||
* if "owner" and "repository" are given,
|
||||
*/
|
||||
post("/_user/existence")(usersOnly {
|
||||
getAccountByUserNameIgnoreCase(params("userName")).map { account =>
|
||||
if (account.isGroupAccount) "group" else "user"
|
||||
if (!account.isGroupAccount && params.get("repository").isDefined && params.get("owner").isDefined) {
|
||||
getRepository(params("owner"), params("repository"))
|
||||
.collect {
|
||||
case repository if isWritable(repository.repository, Some(account)) => "user"
|
||||
}
|
||||
.getOrElse("")
|
||||
} else {
|
||||
if (account.isGroupAccount) "group" else "user"
|
||||
}
|
||||
} getOrElse ""
|
||||
})
|
||||
|
||||
|
||||
@@ -2,13 +2,12 @@ package gitbucket.core.controller
|
||||
|
||||
import gitbucket.core.issues.html
|
||||
import gitbucket.core.model.{Account, CustomFieldBehavior}
|
||||
import gitbucket.core.service.IssuesService._
|
||||
import gitbucket.core.service._
|
||||
import gitbucket.core.util.Implicits._
|
||||
import gitbucket.core.util._
|
||||
import gitbucket.core.view
|
||||
import gitbucket.core.view.Markdown
|
||||
import org.scalatra.forms._
|
||||
import gitbucket.core.service.IssuesService.*
|
||||
import gitbucket.core.service.*
|
||||
import gitbucket.core.util.Implicits.*
|
||||
import gitbucket.core.util.*
|
||||
import gitbucket.core.view.helpers
|
||||
import org.scalatra.forms.*
|
||||
import org.scalatra.{BadRequest, Ok}
|
||||
|
||||
class IssuesController
|
||||
@@ -34,23 +33,12 @@ class IssuesController
|
||||
with RequestCache
|
||||
|
||||
trait IssuesControllerBase extends ControllerBase {
|
||||
self: IssuesService
|
||||
with RepositoryService
|
||||
with AccountService
|
||||
with LabelsService
|
||||
with MilestonesService
|
||||
with ActivityService
|
||||
with HandleCommentService
|
||||
with IssueCreationService
|
||||
with CustomFieldsService
|
||||
with ReadableUsersAuthenticator
|
||||
with ReferrerAuthenticator
|
||||
with WritableUsersAuthenticator
|
||||
with PullRequestService
|
||||
with WebHookIssueCommentService
|
||||
with PrioritiesService =>
|
||||
self: IssuesService & RepositoryService & AccountService & LabelsService & MilestonesService & ActivityService &
|
||||
HandleCommentService & IssueCreationService & CustomFieldsService & ReadableUsersAuthenticator &
|
||||
ReferrerAuthenticator & WritableUsersAuthenticator & PullRequestService & WebHookIssueCommentService &
|
||||
PrioritiesService =>
|
||||
|
||||
case class IssueCreateForm(
|
||||
private case class IssueCreateForm(
|
||||
title: String,
|
||||
content: Option[String],
|
||||
assigneeUserNames: Option[String],
|
||||
@@ -58,10 +46,10 @@ trait IssuesControllerBase extends ControllerBase {
|
||||
priorityId: Option[Int],
|
||||
labelNames: Option[String]
|
||||
)
|
||||
case class CommentForm(issueId: Int, content: String)
|
||||
case class IssueStateForm(issueId: Int, content: Option[String])
|
||||
private case class CommentForm(issueId: Int, content: String)
|
||||
private case class IssueStateForm(issueId: Int, content: Option[String])
|
||||
|
||||
val issueCreateForm = mapping(
|
||||
private val issueCreateForm = mapping(
|
||||
"title" -> trim(label("Title", text(required))),
|
||||
"content" -> trim(optional(text())),
|
||||
"assigneeUserNames" -> trim(optional(text())),
|
||||
@@ -70,19 +58,19 @@ trait IssuesControllerBase extends ControllerBase {
|
||||
"labelNames" -> trim(optional(text()))
|
||||
)(IssueCreateForm.apply)
|
||||
|
||||
val issueTitleEditForm = mapping(
|
||||
private val issueTitleEditForm = mapping(
|
||||
"title" -> trim(label("Title", text(required)))
|
||||
)(x => x)
|
||||
val issueEditForm = mapping(
|
||||
private val issueEditForm = mapping(
|
||||
"content" -> trim(optional(text()))
|
||||
)(x => x)
|
||||
|
||||
val commentForm = mapping(
|
||||
private val commentForm = mapping(
|
||||
"issueId" -> label("Issue Id", number()),
|
||||
"content" -> trim(label("Comment", text(required)))
|
||||
)(CommentForm.apply)
|
||||
|
||||
val issueStateForm = mapping(
|
||||
private val issueStateForm = mapping(
|
||||
"issueId" -> label("Issue Id", number()),
|
||||
"content" -> trim(optional(text()))
|
||||
)(IssueStateForm.apply)
|
||||
@@ -109,7 +97,7 @@ trait IssuesControllerBase extends ControllerBase {
|
||||
val issueId = params("id")
|
||||
getIssue(repository.owner, repository.name, issueId) map { issue =>
|
||||
if (issue.isPullRequest) {
|
||||
redirect(s"/${repository.owner}/${repository.name}/pull/${issueId}")
|
||||
redirect(s"/${repository.owner}/${repository.name}/pull/$issueId")
|
||||
} else {
|
||||
html.issue(
|
||||
issue,
|
||||
@@ -230,7 +218,7 @@ trait IssuesControllerBase extends ControllerBase {
|
||||
.filter(_ => isEditableContent(issue.userName, issue.repositoryName, issue.openedUserName, loginAccount))
|
||||
handleComment(issue, Some(form.content), repository, actionOpt) map { case (issue, id) =>
|
||||
redirect(
|
||||
s"/${repository.owner}/${repository.name}/${if (issue.isPullRequest) "pull" else "issues"}/${form.issueId}#comment-${id}"
|
||||
s"/${repository.owner}/${repository.name}/${if (issue.isPullRequest) "pull" else "issues"}/${form.issueId}#comment-$id"
|
||||
)
|
||||
}
|
||||
} getOrElse NotFound()
|
||||
@@ -246,7 +234,7 @@ trait IssuesControllerBase extends ControllerBase {
|
||||
.filter(_ => isEditableContent(issue.userName, issue.repositoryName, issue.openedUserName, loginAccount))
|
||||
handleComment(issue, form.content, repository, actionOpt) map { case (issue, id) =>
|
||||
redirect(
|
||||
s"/${repository.owner}/${repository.name}/${if (issue.isPullRequest) "pull" else "issues"}/${form.issueId}#comment-${id}"
|
||||
s"/${repository.owner}/${repository.name}/${if (issue.isPullRequest) "pull" else "issues"}/${form.issueId}#comment-$id"
|
||||
)
|
||||
}
|
||||
} getOrElse NotFound()
|
||||
@@ -282,20 +270,24 @@ trait IssuesControllerBase extends ControllerBase {
|
||||
case t if t == "html" => html.editissue(x.content, x.issueId, repository)
|
||||
} getOrElse {
|
||||
contentType = formats("json")
|
||||
val content = helpers
|
||||
.renderMarkup(
|
||||
filePath = List("temporary.md"),
|
||||
fileContent = x.content getOrElse "No description given.",
|
||||
branch = repository.repository.defaultBranch,
|
||||
repository = repository,
|
||||
enableWikiLink = false,
|
||||
enableRefsLink = true,
|
||||
enableAnchor = true,
|
||||
enableLineBreaks = true,
|
||||
enableTaskList = true,
|
||||
hasWritePermission = true
|
||||
)
|
||||
.toString()
|
||||
org.json4s.jackson.Serialization.write(
|
||||
Map(
|
||||
"title" -> x.title,
|
||||
"content" -> Markdown.toHtml(
|
||||
markdown = x.content getOrElse "No description given.",
|
||||
repository = repository,
|
||||
branch = repository.repository.defaultBranch,
|
||||
enableWikiLink = false,
|
||||
enableRefsLink = true,
|
||||
enableAnchor = true,
|
||||
enableLineBreaks = true,
|
||||
enableTaskList = true,
|
||||
hasWritePermission = true
|
||||
)
|
||||
"content" -> content
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -312,19 +304,23 @@ trait IssuesControllerBase extends ControllerBase {
|
||||
case t if t == "html" => html.editcomment(x.content, x.commentId, repository)
|
||||
} getOrElse {
|
||||
contentType = formats("json")
|
||||
val content = helpers
|
||||
.renderMarkup(
|
||||
filePath = List("temporary.md"),
|
||||
fileContent = x.content,
|
||||
branch = repository.repository.defaultBranch,
|
||||
repository = repository,
|
||||
enableWikiLink = false,
|
||||
enableRefsLink = true,
|
||||
enableAnchor = true,
|
||||
enableLineBreaks = true,
|
||||
enableTaskList = true,
|
||||
hasWritePermission = true
|
||||
)
|
||||
.toString()
|
||||
org.json4s.jackson.Serialization.write(
|
||||
Map(
|
||||
"content" -> view.Markdown.toHtml(
|
||||
markdown = x.content,
|
||||
repository = repository,
|
||||
branch = repository.repository.defaultBranch,
|
||||
enableWikiLink = false,
|
||||
enableRefsLink = true,
|
||||
enableAnchor = true,
|
||||
enableLineBreaks = true,
|
||||
enableTaskList = true,
|
||||
hasWritePermission = true
|
||||
)
|
||||
"content" -> content
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -341,30 +337,36 @@ trait IssuesControllerBase extends ControllerBase {
|
||||
|
||||
ajaxPost("/:owner/:repository/issues/:id/label/new")(writableUsersOnly { repository =>
|
||||
val issueId = params("id").toInt
|
||||
registerIssueLabel(repository.owner, repository.name, issueId, params("labelId").toInt, true)
|
||||
registerIssueLabel(repository.owner, repository.name, issueId, params("labelId").toInt, insertComment = true)
|
||||
html.labellist(getIssueLabels(repository.owner, repository.name, issueId))
|
||||
})
|
||||
|
||||
ajaxPost("/:owner/:repository/issues/:id/label/delete")(writableUsersOnly { repository =>
|
||||
val issueId = params("id").toInt
|
||||
deleteIssueLabel(repository.owner, repository.name, issueId, params("labelId").toInt, true)
|
||||
deleteIssueLabel(repository.owner, repository.name, issueId, params("labelId").toInt, insertComment = true)
|
||||
html.labellist(getIssueLabels(repository.owner, repository.name, issueId))
|
||||
})
|
||||
|
||||
ajaxPost("/:owner/:repository/issues/:id/assignee/new")(writableUsersOnly { repository =>
|
||||
val issueId = params("id").toInt
|
||||
registerIssueAssignee(repository.owner, repository.name, issueId, params("assigneeUserName"), true)
|
||||
registerIssueAssignee(repository.owner, repository.name, issueId, params("assigneeUserName"), insertComment = true)
|
||||
Ok()
|
||||
})
|
||||
|
||||
ajaxPost("/:owner/:repository/issues/:id/assignee/delete")(writableUsersOnly { repository =>
|
||||
val issueId = params("id").toInt
|
||||
deleteIssueAssignee(repository.owner, repository.name, issueId, params("assigneeUserName"), true)
|
||||
deleteIssueAssignee(repository.owner, repository.name, issueId, params("assigneeUserName"), insertComment = true)
|
||||
Ok()
|
||||
})
|
||||
|
||||
ajaxPost("/:owner/:repository/issues/:id/milestone")(writableUsersOnly { repository =>
|
||||
updateMilestoneId(repository.owner, repository.name, params("id").toInt, milestoneId("milestoneId"), true)
|
||||
updateMilestoneId(
|
||||
repository.owner,
|
||||
repository.name,
|
||||
params("id").toInt,
|
||||
milestoneId("milestoneId"),
|
||||
insertComment = true
|
||||
)
|
||||
milestoneId("milestoneId").map { milestoneId =>
|
||||
getMilestonesWithIssueCount(repository.owner, repository.name)
|
||||
.find(_._1.milestoneId == milestoneId)
|
||||
@@ -376,7 +378,7 @@ trait IssuesControllerBase extends ControllerBase {
|
||||
|
||||
ajaxPost("/:owner/:repository/issues/:id/priority")(writableUsersOnly { repository =>
|
||||
val priority = priorityId("priorityId")
|
||||
updatePriorityId(repository.owner, repository.name, params("id").toInt, priority, true)
|
||||
updatePriorityId(repository.owner, repository.name, params("id").toInt, priority, insertComment = true)
|
||||
Ok("updated")
|
||||
})
|
||||
|
||||
@@ -438,7 +440,7 @@ trait IssuesControllerBase extends ControllerBase {
|
||||
params("value").toIntOpt.map { labelId =>
|
||||
executeBatch(repository) { issueId =>
|
||||
getIssueLabel(repository.owner, repository.name, issueId, labelId) getOrElse {
|
||||
registerIssueLabel(repository.owner, repository.name, issueId, labelId, true)
|
||||
registerIssueLabel(repository.owner, repository.name, issueId, labelId, insertComment = true)
|
||||
if (params("uri").nonEmpty) {
|
||||
redirect(params("uri"))
|
||||
}
|
||||
@@ -453,9 +455,9 @@ trait IssuesControllerBase extends ControllerBase {
|
||||
// updateAssignedUserName(repository.owner, repository.name, _, value, true)
|
||||
value match {
|
||||
case Some(assignedUserName) =>
|
||||
registerIssueAssignee(repository.owner, repository.name, _, assignedUserName, true)
|
||||
registerIssueAssignee(repository.owner, repository.name, _, assignedUserName, insertComment = true)
|
||||
case None =>
|
||||
deleteAllIssueAssignees(repository.owner, repository.name, _, true)
|
||||
deleteAllIssueAssignees(repository.owner, repository.name, _, insertComment = true)
|
||||
}
|
||||
}
|
||||
if (params("uri").nonEmpty) {
|
||||
@@ -466,20 +468,20 @@ trait IssuesControllerBase extends ControllerBase {
|
||||
post("/:owner/:repository/issues/batchedit/milestone")(writableUsersOnly { repository =>
|
||||
val value = milestoneId("value")
|
||||
executeBatch(repository) {
|
||||
updateMilestoneId(repository.owner, repository.name, _, value, true)
|
||||
updateMilestoneId(repository.owner, repository.name, _, value, insertComment = true)
|
||||
}
|
||||
})
|
||||
|
||||
post("/:owner/:repository/issues/batchedit/priority")(writableUsersOnly { repository =>
|
||||
val value = priorityId("value")
|
||||
executeBatch(repository) {
|
||||
updatePriorityId(repository.owner, repository.name, _, value, true)
|
||||
updatePriorityId(repository.owner, repository.name, _, value, insertComment = true)
|
||||
}
|
||||
})
|
||||
|
||||
get("/:owner/:repository/_attached/:file")(referrersOnly { repository =>
|
||||
(Directory.getAttachedDir(repository.owner, repository.name) match {
|
||||
case dir if (dir.exists && dir.isDirectory) =>
|
||||
case dir if dir.exists && dir.isDirectory =>
|
||||
dir.listFiles.find(_.getName.startsWith(params("file") + ".")).map { file =>
|
||||
response.setHeader("Content-Disposition", f"""inline; filename=${file.getName}""")
|
||||
RawData(FileUtil.getSafeMimeType(file.getName), file)
|
||||
@@ -495,7 +497,7 @@ trait IssuesControllerBase extends ControllerBase {
|
||||
contentType = formats("json")
|
||||
org.json4s.jackson.Serialization.write(
|
||||
Map(
|
||||
"options" -> (
|
||||
"options" ->
|
||||
getOpenIssues(repository.owner, repository.name)
|
||||
.map { t =>
|
||||
Map(
|
||||
@@ -506,16 +508,15 @@ trait IssuesControllerBase extends ControllerBase {
|
||||
"value" -> t.issueId.toString
|
||||
)
|
||||
}
|
||||
)
|
||||
)
|
||||
)
|
||||
})
|
||||
|
||||
val assignedUserName = (key: String) => params.get(key) filter (_.trim != "")
|
||||
val milestoneId: String => Option[Int] = (key: String) => params.get(key).flatMap(_.toIntOpt)
|
||||
val priorityId: String => Option[Int] = (key: String) => params.get(key).flatMap(_.toIntOpt)
|
||||
private val assignedUserName = (key: String) => params.get(key) filter (_.trim != "")
|
||||
private val milestoneId: String => Option[Int] = (key: String) => params.get(key).flatMap(_.toIntOpt)
|
||||
private val priorityId: String => Option[Int] = (key: String) => params.get(key).flatMap(_.toIntOpt)
|
||||
|
||||
private def executeBatch(repository: RepositoryService.RepositoryInfo)(execute: Int => Unit) = {
|
||||
private def executeBatch(repository: RepositoryService.RepositoryInfo)(execute: Int => Unit): Unit = {
|
||||
params("checked").split(',') map (_.toInt) foreach execute
|
||||
params("from") match {
|
||||
case "issues" => redirect(s"/${repository.owner}/${repository.name}/issues")
|
||||
|
||||
@@ -10,9 +10,9 @@ import gitbucket.core.service.{
|
||||
PrioritiesService
|
||||
}
|
||||
import gitbucket.core.util.{ReferrerAuthenticator, WritableUsersAuthenticator}
|
||||
import gitbucket.core.util.Implicits._
|
||||
import gitbucket.core.util.SyntaxSugars._
|
||||
import org.scalatra.forms._
|
||||
import gitbucket.core.util.Implicits.*
|
||||
import gitbucket.core.util.SyntaxSugars.*
|
||||
import org.scalatra.forms.*
|
||||
import org.scalatra.i18n.Messages
|
||||
import org.scalatra.Ok
|
||||
|
||||
@@ -28,15 +28,11 @@ class LabelsController
|
||||
with WritableUsersAuthenticator
|
||||
|
||||
trait LabelsControllerBase extends ControllerBase {
|
||||
self: LabelsService
|
||||
with IssuesService
|
||||
with RepositoryService
|
||||
with ReferrerAuthenticator
|
||||
with WritableUsersAuthenticator =>
|
||||
self: LabelsService & IssuesService & RepositoryService & ReferrerAuthenticator & WritableUsersAuthenticator =>
|
||||
|
||||
case class LabelForm(labelName: String, color: String)
|
||||
private case class LabelForm(labelName: String, color: String)
|
||||
|
||||
val labelForm = mapping(
|
||||
private val labelForm = mapping(
|
||||
"labelName" -> trim(label("Label name", text(required, labelName, uniqueLabelName, maxlength(100)))),
|
||||
"labelColor" -> trim(label("Color", text(required, color)))
|
||||
)(LabelForm.apply)
|
||||
@@ -93,9 +89,9 @@ trait LabelsControllerBase extends ControllerBase {
|
||||
private def labelName: Constraint = new Constraint() {
|
||||
override def validate(name: String, value: String, messages: Messages): Option[String] =
|
||||
if (value.contains(',')) {
|
||||
Some(s"${name} contains invalid character.")
|
||||
Some(s"$name contains invalid character.")
|
||||
} else if (value.startsWith("_") || value.startsWith("-")) {
|
||||
Some(s"${name} starts with invalid character.")
|
||||
Some(s"$name starts with invalid character.")
|
||||
} else {
|
||||
None
|
||||
}
|
||||
|
||||
@@ -9,11 +9,11 @@ import gitbucket.core.service.{
|
||||
MilestonesService,
|
||||
RepositoryService
|
||||
}
|
||||
import gitbucket.core.util.Implicits._
|
||||
import gitbucket.core.util.Implicits.*
|
||||
import gitbucket.core.util.{ReferrerAuthenticator, WritableUsersAuthenticator}
|
||||
import gitbucket.core.util.SyntaxSugars._
|
||||
import gitbucket.core.util.SyntaxSugars.*
|
||||
import gitbucket.core.view.helpers.{getAssignableUserNames, getLabels, getPriorities, searchIssue}
|
||||
import org.scalatra.forms._
|
||||
import org.scalatra.forms.*
|
||||
import org.scalatra.i18n.Messages
|
||||
|
||||
class MilestonesController
|
||||
@@ -26,15 +26,12 @@ class MilestonesController
|
||||
with WritableUsersAuthenticator
|
||||
|
||||
trait MilestonesControllerBase extends ControllerBase {
|
||||
self: MilestonesService
|
||||
with RepositoryService
|
||||
with CommitStatusService
|
||||
with ReferrerAuthenticator
|
||||
with WritableUsersAuthenticator =>
|
||||
self: MilestonesService & RepositoryService & CommitStatusService & ReferrerAuthenticator &
|
||||
WritableUsersAuthenticator =>
|
||||
|
||||
case class MilestoneForm(title: String, description: Option[String], dueDate: Option[java.util.Date])
|
||||
private case class MilestoneForm(title: String, description: Option[String], dueDate: Option[java.util.Date])
|
||||
|
||||
val milestoneForm = mapping(
|
||||
private val milestoneForm = mapping(
|
||||
"title" -> trim(label("Title", text(required, maxlength(100), uniqueMilestone))),
|
||||
"description" -> trim(label("Description", optional(text()))),
|
||||
"dueDate" -> trim(label("Due Date", optional(date())))
|
||||
|
||||
@@ -33,7 +33,7 @@ trait PreProcessControllerBase extends ControllerBase {
|
||||
if (
|
||||
!context.currentPath.startsWith("/assets") && !context.currentPath.startsWith("/signin") &&
|
||||
!context.currentPath.startsWith("/register") && !context.currentPath.endsWith("/info/refs") &&
|
||||
!context.currentPath.startsWith("/plugin-assets") &&
|
||||
!context.currentPath.startsWith("/plugin-assets") && !context.currentPath.equals("/user.css") &&
|
||||
!PluginRegistry().getAnonymousAccessiblePaths().exists { path =>
|
||||
context.currentPath.startsWith(path)
|
||||
}
|
||||
|
||||
@@ -28,15 +28,11 @@ class PrioritiesController
|
||||
with WritableUsersAuthenticator
|
||||
|
||||
trait PrioritiesControllerBase extends ControllerBase {
|
||||
self: PrioritiesService
|
||||
with IssuesService
|
||||
with RepositoryService
|
||||
with ReferrerAuthenticator
|
||||
with WritableUsersAuthenticator =>
|
||||
self: PrioritiesService & IssuesService & RepositoryService & ReferrerAuthenticator & WritableUsersAuthenticator =>
|
||||
|
||||
case class PriorityForm(priorityName: String, description: Option[String], color: String)
|
||||
private case class PriorityForm(priorityName: String, description: Option[String], color: String)
|
||||
|
||||
val priorityForm = mapping(
|
||||
private val priorityForm = mapping(
|
||||
"priorityName" -> trim(label("Priority name", text(required, priorityName, uniquePriorityName, maxlength(100)))),
|
||||
"description" -> trim(label("Description", optional(text(maxlength(255))))),
|
||||
"priorityColor" -> trim(label("Color", text(required, color)))
|
||||
@@ -90,7 +86,7 @@ trait PrioritiesControllerBase extends ControllerBase {
|
||||
)
|
||||
})
|
||||
|
||||
ajaxPost("/:owner/:repository/issues/priorities/reorder")(writableUsersOnly { (repository) =>
|
||||
ajaxPost("/:owner/:repository/issues/priorities/reorder")(writableUsersOnly { repository =>
|
||||
reorderPriorities(
|
||||
repository.owner,
|
||||
repository.name,
|
||||
@@ -104,7 +100,7 @@ trait PrioritiesControllerBase extends ControllerBase {
|
||||
Ok()
|
||||
})
|
||||
|
||||
ajaxPost("/:owner/:repository/issues/priorities/default")(writableUsersOnly { (repository) =>
|
||||
ajaxPost("/:owner/:repository/issues/priorities/default")(writableUsersOnly { repository =>
|
||||
setDefaultPriority(repository.owner, repository.name, priorityId("priorityId"))
|
||||
Ok()
|
||||
})
|
||||
@@ -122,9 +118,9 @@ trait PrioritiesControllerBase extends ControllerBase {
|
||||
private def priorityName: Constraint = new Constraint() {
|
||||
override def validate(name: String, value: String, messages: Messages): Option[String] =
|
||||
if (value.contains(',')) {
|
||||
Some(s"${name} contains invalid character.")
|
||||
Some(s"$name contains invalid character.")
|
||||
} else if (value.startsWith("_") || value.startsWith("-")) {
|
||||
Some(s"${name} starts with invalid character.")
|
||||
Some(s"$name starts with invalid character.")
|
||||
} else {
|
||||
None
|
||||
}
|
||||
|
||||
@@ -4,14 +4,14 @@ import gitbucket.core.model.activity.DeleteBranchInfo
|
||||
import gitbucket.core.pulls.html
|
||||
import gitbucket.core.service.CommitStatusService
|
||||
import gitbucket.core.service.MergeService
|
||||
import gitbucket.core.service.IssuesService._
|
||||
import gitbucket.core.service.PullRequestService._
|
||||
import gitbucket.core.service.IssuesService.*
|
||||
import gitbucket.core.service.PullRequestService.*
|
||||
import gitbucket.core.service.RepositoryService.RepositoryInfo
|
||||
import gitbucket.core.service._
|
||||
import gitbucket.core.util.Directory._
|
||||
import gitbucket.core.util.Implicits._
|
||||
import gitbucket.core.util._
|
||||
import org.scalatra.forms._
|
||||
import gitbucket.core.service.*
|
||||
import gitbucket.core.util.Directory.*
|
||||
import gitbucket.core.util.Implicits.*
|
||||
import gitbucket.core.util.*
|
||||
import org.scalatra.forms.*
|
||||
import org.eclipse.jgit.api.Git
|
||||
import org.scalatra.BadRequest
|
||||
|
||||
@@ -40,32 +40,19 @@ class PullRequestsController
|
||||
with RequestCache
|
||||
|
||||
trait PullRequestsControllerBase extends ControllerBase {
|
||||
self: RepositoryService
|
||||
with AccountService
|
||||
with IssuesService
|
||||
with MilestonesService
|
||||
with LabelsService
|
||||
with CustomFieldsService
|
||||
with CommitsService
|
||||
with ActivityService
|
||||
with PullRequestService
|
||||
with WebHookPullRequestService
|
||||
with ReadableUsersAuthenticator
|
||||
with ReferrerAuthenticator
|
||||
with WritableUsersAuthenticator
|
||||
with CommitStatusService
|
||||
with MergeService
|
||||
with ProtectedBranchService
|
||||
with PrioritiesService =>
|
||||
self: RepositoryService & AccountService & IssuesService & MilestonesService & LabelsService & CustomFieldsService &
|
||||
CommitsService & ActivityService & PullRequestService & WebHookPullRequestService & ReadableUsersAuthenticator &
|
||||
ReferrerAuthenticator & WritableUsersAuthenticator & CommitStatusService & MergeService & ProtectedBranchService &
|
||||
PrioritiesService =>
|
||||
|
||||
val pullRequestForm = mapping(
|
||||
private val pullRequestForm = mapping(
|
||||
"title" -> trim(label("Title", text(required))),
|
||||
"content" -> trim(label("Content", optional(text()))),
|
||||
"targetUserName" -> trim(text(required, maxlength(100))),
|
||||
"targetBranch" -> trim(text(required, maxlength(100))),
|
||||
"targetBranch" -> trim(text(required, maxlength(255))),
|
||||
"requestUserName" -> trim(text(required, maxlength(100))),
|
||||
"requestRepositoryName" -> trim(text(required, maxlength(100))),
|
||||
"requestBranch" -> trim(text(required, maxlength(100))),
|
||||
"requestBranch" -> trim(text(required, maxlength(255))),
|
||||
"commitIdFrom" -> trim(text(required, maxlength(40))),
|
||||
"commitIdTo" -> trim(text(required, maxlength(40))),
|
||||
"isDraft" -> trim(boolean(required)),
|
||||
@@ -75,13 +62,13 @@ trait PullRequestsControllerBase extends ControllerBase {
|
||||
"labelNames" -> trim(optional(text()))
|
||||
)(PullRequestForm.apply)
|
||||
|
||||
val mergeForm = mapping(
|
||||
private val mergeForm = mapping(
|
||||
"message" -> trim(label("Message", text(required))),
|
||||
"strategy" -> trim(label("Strategy", text(required))),
|
||||
"isDraft" -> trim(boolean(required))
|
||||
)(MergeForm.apply)
|
||||
|
||||
case class PullRequestForm(
|
||||
private case class PullRequestForm(
|
||||
title: String,
|
||||
content: Option[String],
|
||||
targetUserName: String,
|
||||
@@ -98,7 +85,7 @@ trait PullRequestsControllerBase extends ControllerBase {
|
||||
labelNames: Option[String]
|
||||
)
|
||||
|
||||
case class MergeForm(message: String, strategy: String, isDraft: Boolean)
|
||||
private case class MergeForm(message: String, strategy: String, isDraft: Boolean)
|
||||
|
||||
get("/:owner/:repository/pulls")(referrersOnly { repository =>
|
||||
val q = request.getParameter("q")
|
||||
@@ -260,48 +247,50 @@ trait PullRequestsControllerBase extends ControllerBase {
|
||||
})
|
||||
|
||||
get("/:owner/:repository/pull/:id/delete_branch")(readableUsersOnly { baseRepository =>
|
||||
(for {
|
||||
issueId <- params("id").toIntOpt
|
||||
loginAccount <- context.loginAccount
|
||||
(issue, pullreq) <- getPullRequest(baseRepository.owner, baseRepository.name, issueId)
|
||||
owner = pullreq.requestUserName
|
||||
name = pullreq.requestRepositoryName
|
||||
if hasDeveloperRole(owner, name, context.loginAccount)
|
||||
} yield {
|
||||
val repository = getRepository(owner, name).get
|
||||
val branchProtection = getProtectedBranchInfo(owner, name, pullreq.requestBranch)
|
||||
if (branchProtection.enabled) {
|
||||
flash.update("error", s"branch ${pullreq.requestBranch} is protected.")
|
||||
} else {
|
||||
if (repository.repository.defaultBranch != pullreq.requestBranch) {
|
||||
val userName = context.loginAccount.get.userName
|
||||
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
|
||||
git.branchDelete().setForce(true).setBranchNames(pullreq.requestBranch).call()
|
||||
val deleteBranchInfo = DeleteBranchInfo(repository.owner, repository.name, userName, pullreq.requestBranch)
|
||||
recordActivity(deleteBranchInfo)
|
||||
}
|
||||
createComment(
|
||||
baseRepository.owner,
|
||||
baseRepository.name,
|
||||
userName,
|
||||
issueId,
|
||||
pullreq.requestBranch,
|
||||
"delete_branch"
|
||||
)
|
||||
context.withLoginAccount { _ =>
|
||||
(for {
|
||||
issueId <- params("id").toIntOpt
|
||||
case (issue, pullreq) <- getPullRequest(baseRepository.owner, baseRepository.name, issueId)
|
||||
owner = pullreq.requestUserName
|
||||
name = pullreq.requestRepositoryName
|
||||
if hasDeveloperRole(owner, name, context.loginAccount)
|
||||
} yield {
|
||||
val repository = getRepository(owner, name).get
|
||||
val branchProtection = getProtectedBranchInfo(owner, name, pullreq.requestBranch)
|
||||
if (branchProtection.enabled) {
|
||||
flash.update("error", s"branch ${pullreq.requestBranch} is protected.")
|
||||
} else {
|
||||
flash.update("error", s"""Can't delete the default branch "${pullreq.requestBranch}".""")
|
||||
if (repository.repository.defaultBranch != pullreq.requestBranch) {
|
||||
val userName = context.loginAccount.get.userName
|
||||
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
|
||||
git.branchDelete().setForce(true).setBranchNames(pullreq.requestBranch).call()
|
||||
val deleteBranchInfo =
|
||||
DeleteBranchInfo(repository.owner, repository.name, userName, pullreq.requestBranch)
|
||||
recordActivity(deleteBranchInfo)
|
||||
}
|
||||
createComment(
|
||||
baseRepository.owner,
|
||||
baseRepository.name,
|
||||
userName,
|
||||
issueId,
|
||||
pullreq.requestBranch,
|
||||
"delete_branch"
|
||||
)
|
||||
} else {
|
||||
flash.update("error", s"""Can't delete the default branch "${pullreq.requestBranch}".""")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
redirect(s"/${baseRepository.owner}/${baseRepository.name}/pull/${issueId}")
|
||||
}) getOrElse NotFound()
|
||||
redirect(s"/${baseRepository.owner}/${baseRepository.name}/pull/${issueId}")
|
||||
}) getOrElse NotFound()
|
||||
}
|
||||
})
|
||||
|
||||
post("/:owner/:repository/pull/:id/update_branch")(readableUsersOnly { baseRepository =>
|
||||
(for {
|
||||
issueId <- params("id").toIntOpt
|
||||
loginAccount <- context.loginAccount
|
||||
(issue, pullreq) <- getPullRequest(baseRepository.owner, baseRepository.name, issueId)
|
||||
case (issue, pullreq) <- getPullRequest(baseRepository.owner, baseRepository.name, issueId)
|
||||
repository <- getRepository(pullreq.requestUserName, pullreq.requestRepositoryName)
|
||||
remoteRepository <- getRepository(pullreq.userName, pullreq.repositoryName)
|
||||
owner = pullreq.requestUserName
|
||||
@@ -312,7 +301,7 @@ trait PullRequestsControllerBase extends ControllerBase {
|
||||
if (branchProtection.needStatusCheck(loginAccount.userName)) {
|
||||
flash.update("error", s"branch ${pullreq.requestBranch} is protected need status check.")
|
||||
} else {
|
||||
LockUtil.lock(s"${owner}/${name}") {
|
||||
LockUtil.lock(s"$owner/$name") {
|
||||
val alias =
|
||||
if (
|
||||
pullreq.repositoryName == pullreq.requestRepositoryName && pullreq.userName == pullreq.requestUserName
|
||||
@@ -321,27 +310,27 @@ trait PullRequestsControllerBase extends ControllerBase {
|
||||
} else {
|
||||
s"${pullreq.userName}:${pullreq.branch}"
|
||||
}
|
||||
val existIds = Using
|
||||
.resource(Git.open(Directory.getRepositoryDir(owner, name))) { git =>
|
||||
JGitUtil.getAllCommitIds(git)
|
||||
}
|
||||
.toSet
|
||||
// val existIds = Using
|
||||
// .resource(Git.open(Directory.getRepositoryDir(owner, name))) { git =>
|
||||
// JGitUtil.getAllCommitIds(git)
|
||||
// }
|
||||
// .toSet
|
||||
pullRemote(
|
||||
repository,
|
||||
pullreq.requestBranch,
|
||||
remoteRepository,
|
||||
pullreq.branch,
|
||||
loginAccount,
|
||||
s"Merge branch '${alias}' into ${pullreq.requestBranch}",
|
||||
s"Merge branch '$alias' into ${pullreq.requestBranch}",
|
||||
Some(pullreq),
|
||||
context.settings
|
||||
) match {
|
||||
case None => // conflict
|
||||
flash.update("error", s"Can't automatic merging branch '${alias}' into ${pullreq.requestBranch}.")
|
||||
flash.update("error", s"Can't automatic merging branch '$alias' into ${pullreq.requestBranch}.")
|
||||
case Some(oldId) =>
|
||||
// update pull request
|
||||
updatePullRequests(owner, name, pullreq.requestBranch, loginAccount, "synchronize", context.settings)
|
||||
flash.update("info", s"Merge branch '${alias}' into ${pullreq.requestBranch}")
|
||||
flash.update("info", s"Merge branch '$alias' into ${pullreq.requestBranch}")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -353,7 +342,7 @@ trait PullRequestsControllerBase extends ControllerBase {
|
||||
post("/:owner/:repository/pull/:id/update_draft")(readableUsersOnly { baseRepository =>
|
||||
(for {
|
||||
issueId <- params("id").toIntOpt
|
||||
(_, pullreq) <- getPullRequest(baseRepository.owner, baseRepository.name, issueId)
|
||||
case (_, pullreq) <- getPullRequest(baseRepository.owner, baseRepository.name, issueId)
|
||||
owner = pullreq.requestUserName
|
||||
name = pullreq.requestRepositoryName
|
||||
if hasDeveloperRole(owner, name, context.loginAccount)
|
||||
@@ -374,8 +363,11 @@ trait PullRequestsControllerBase extends ControllerBase {
|
||||
form.isDraft,
|
||||
context.settings
|
||||
) match {
|
||||
case Right(objectId) => redirect(s"/${repository.owner}/${repository.name}/pull/${issueId}")
|
||||
case Left(message) => Some(BadRequest(message))
|
||||
case Right(result) =>
|
||||
updateMergedCommitIds(repository.owner, repository.name, issueId, result.mergedCommitId)
|
||||
redirect(s"/${repository.owner}/${repository.name}/pull/$issueId")
|
||||
case Left(message) =>
|
||||
Some(BadRequest(message))
|
||||
}
|
||||
} getOrElse NotFound()
|
||||
}
|
||||
@@ -396,7 +388,7 @@ trait PullRequestsControllerBase extends ControllerBase {
|
||||
.getOrElse(JGitUtil.getDefaultBranch(oldGit, originRepository).get._2)
|
||||
|
||||
redirect(
|
||||
s"/${forkedRepository.owner}/${forkedRepository.name}/compare/${originUserName}:${oldBranch}...${newBranch}"
|
||||
s"/${forkedRepository.owner}/${forkedRepository.name}/compare/$originUserName:$oldBranch...$newBranch"
|
||||
)
|
||||
}
|
||||
} getOrElse NotFound()
|
||||
@@ -404,7 +396,7 @@ trait PullRequestsControllerBase extends ControllerBase {
|
||||
Using.resource(Git.open(getRepositoryDir(forkedRepository.owner, forkedRepository.name))) { git =>
|
||||
JGitUtil.getDefaultBranch(git, forkedRepository).map { case (_, defaultBranch) =>
|
||||
redirect(
|
||||
s"/${forkedRepository.owner}/${forkedRepository.name}/compare/${defaultBranch}...${headBranch.getOrElse(defaultBranch)}"
|
||||
s"/${forkedRepository.owner}/${forkedRepository.name}/compare/$defaultBranch...${headBranch.getOrElse(defaultBranch)}"
|
||||
)
|
||||
} getOrElse {
|
||||
redirect(s"/${forkedRepository.owner}/${forkedRepository.name}")
|
||||
@@ -426,7 +418,7 @@ trait PullRequestsControllerBase extends ControllerBase {
|
||||
getForkedRepositories(forkedRepository.owner, forkedRepository.name)
|
||||
.find(_.userName == originOwner)
|
||||
.map(_.repositoryName)
|
||||
} else if (Some(originOwner) == forkedRepository.repository.originUserName) {
|
||||
} else if (forkedRepository.repository.originUserName.contains(originOwner)) {
|
||||
// Original repository
|
||||
forkedRepository.repository.originRepositoryName
|
||||
} else {
|
||||
@@ -487,7 +479,9 @@ trait PullRequestsControllerBase extends ControllerBase {
|
||||
(repository.userName, repository.repositoryName, repository.defaultBranch)
|
||||
},
|
||||
commits.flatten
|
||||
.flatMap(commit => getCommitComments(forkedRepository.owner, forkedRepository.name, commit.id, false))
|
||||
.flatMap(commit =>
|
||||
getCommitComments(forkedRepository.owner, forkedRepository.name, commit.id, includePullRequest = false)
|
||||
)
|
||||
.toList,
|
||||
originId,
|
||||
forkedId,
|
||||
@@ -508,8 +502,8 @@ trait PullRequestsControllerBase extends ControllerBase {
|
||||
case (oldId, newId) =>
|
||||
redirect(
|
||||
s"/${forkedRepository.owner}/${forkedRepository.name}/compare/" +
|
||||
s"${originOwner}:${oldId.map(_ => originId).getOrElse(originRepository.repository.defaultBranch)}..." +
|
||||
s"${forkedOwner}:${newId.map(_ => forkedId).getOrElse(forkedRepository.repository.defaultBranch)}"
|
||||
s"$originOwner:${oldId.map(_ => originId).getOrElse(originRepository.repository.defaultBranch)}..." +
|
||||
s"$forkedOwner:${newId.map(_ => forkedId).getOrElse(forkedRepository.repository.defaultBranch)}"
|
||||
)
|
||||
|
||||
}
|
||||
@@ -569,7 +563,7 @@ trait PullRequestsControllerBase extends ControllerBase {
|
||||
val (originOwner, tmpOriginBranch) = parseCompareIdentifier(origin, forkedRepository.owner)
|
||||
val (forkedOwner, tmpForkedBranch) = parseCompareIdentifier(forked, forkedRepository.owner)
|
||||
|
||||
(for (
|
||||
(for {
|
||||
originRepositoryName <-
|
||||
if (originOwner == forkedOwner) {
|
||||
Some(forkedRepository.name)
|
||||
@@ -579,9 +573,9 @@ trait PullRequestsControllerBase extends ControllerBase {
|
||||
.find(_.userName == originOwner)
|
||||
.map(_.repositoryName)
|
||||
}
|
||||
};
|
||||
}
|
||||
originRepository <- getRepository(originOwner, originRepositoryName)
|
||||
) yield {
|
||||
} yield {
|
||||
Using.resources(
|
||||
Git.open(getRepositoryDir(originRepository.owner, originRepository.name)),
|
||||
Git.open(getRepositoryDir(forkedRepository.owner, forkedRepository.name))
|
||||
@@ -650,7 +644,7 @@ trait PullRequestsControllerBase extends ControllerBase {
|
||||
}
|
||||
}
|
||||
|
||||
redirect(s"/${repository.owner}/${repository.name}/pull/${issueId}")
|
||||
redirect(s"/${repository.owner}/${repository.name}/pull/$issueId")
|
||||
}
|
||||
})
|
||||
|
||||
@@ -733,15 +727,84 @@ trait PullRequestsControllerBase extends ControllerBase {
|
||||
)
|
||||
}
|
||||
|
||||
post("/:owner/:repository/pull/:id/revert")(writableUsersOnly { repository =>
|
||||
context.withLoginAccount { loginAccount =>
|
||||
(for {
|
||||
issueId <- params.get("id").map(_.toInt)
|
||||
(issue, pullreq) <- getPullRequest(repository.owner, repository.name, issueId) if issue.closed
|
||||
} yield {
|
||||
val baseBranch = pullreq.branch
|
||||
val revertBranch = s"revert-pr-$issueId-${System.currentTimeMillis()}"
|
||||
|
||||
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
|
||||
try {
|
||||
// Create a new branch from base
|
||||
JGitUtil.createBranch(git, baseBranch, revertBranch)
|
||||
|
||||
val revertCommitId = pullreq.mergedCommitIds match {
|
||||
case Some(mergedCommitIds) =>
|
||||
createRevertCommit(
|
||||
git,
|
||||
revertBranch,
|
||||
mergedCommitIds.split(",").toSeq,
|
||||
loginAccount.fullName,
|
||||
loginAccount.mailAddress,
|
||||
s"Revert #$issueId"
|
||||
)
|
||||
case None =>
|
||||
Left("No merged commit IDs found for this pull request")
|
||||
}
|
||||
|
||||
revertCommitId match {
|
||||
case Right(revertCommitObjectId) =>
|
||||
val newIssueId = insertIssue(
|
||||
owner = repository.owner,
|
||||
repository = repository.name,
|
||||
loginUser = loginAccount.userName,
|
||||
title = s"Revert #${issueId}",
|
||||
content = Some(s"Revert #${issueId}"),
|
||||
milestoneId = None,
|
||||
priorityId = None,
|
||||
isPullRequest = true
|
||||
)
|
||||
createPullRequest(
|
||||
originRepository = repository,
|
||||
issueId = newIssueId,
|
||||
originBranch = baseBranch,
|
||||
requestUserName = repository.owner,
|
||||
requestRepositoryName = repository.name,
|
||||
requestBranch = revertBranch,
|
||||
commitIdFrom = git.getRepository.resolve(s"refs/heads/$baseBranch").getName,
|
||||
commitIdTo = revertCommitObjectId.name(),
|
||||
isDraft = false,
|
||||
loginAccount = loginAccount,
|
||||
settings = context.settings
|
||||
)
|
||||
redirect(s"/${repository.owner}/${repository.name}/pull/$newIssueId")
|
||||
|
||||
case Left(errorMessage) =>
|
||||
// Clean up the branch we created
|
||||
git.branchDelete().setForce(true).setBranchNames(revertBranch).call()
|
||||
BadRequest(s"Failed to create revert commit: $errorMessage")
|
||||
}
|
||||
} catch {
|
||||
case ex: Exception =>
|
||||
BadRequest(s"Revert failed: ${ex.getMessage}")
|
||||
}
|
||||
}
|
||||
}) getOrElse NotFound()
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* Tests whether an logged-in user can manage pull requests.
|
||||
* Tests whether the logged-in user can manage pull requests.
|
||||
*/
|
||||
private def isManageable(repository: RepositoryInfo)(implicit context: Context): Boolean = {
|
||||
hasDeveloperRole(repository.owner, repository.name, context.loginAccount)
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests whether an logged-in user can post pull requests.
|
||||
* Tests whether the logged-in user can post pull requests.
|
||||
*/
|
||||
private def isEditable(repository: RepositoryInfo)(implicit context: Context): Boolean = {
|
||||
repository.repository.options.issuesOption match {
|
||||
@@ -751,5 +814,4 @@ trait PullRequestsControllerBase extends ControllerBase {
|
||||
case "DISABLE" => false
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -11,10 +11,10 @@ import gitbucket.core.service.{
|
||||
RepositoryService,
|
||||
RequestCache
|
||||
}
|
||||
import gitbucket.core.util._
|
||||
import gitbucket.core.util.Directory._
|
||||
import gitbucket.core.util.Implicits._
|
||||
import org.scalatra.forms._
|
||||
import gitbucket.core.util.*
|
||||
import gitbucket.core.util.Directory.*
|
||||
import gitbucket.core.util.Implicits.*
|
||||
import org.scalatra.forms.*
|
||||
import gitbucket.core.releases.html
|
||||
import org.apache.commons.io.FileUtils
|
||||
import org.eclipse.jgit.api.Git
|
||||
@@ -33,20 +33,15 @@ class ReleaseController
|
||||
with RequestCache
|
||||
|
||||
trait ReleaseControllerBase extends ControllerBase {
|
||||
self: RepositoryService
|
||||
with AccountService
|
||||
with ReleaseService
|
||||
with ReadableUsersAuthenticator
|
||||
with ReferrerAuthenticator
|
||||
with WritableUsersAuthenticator
|
||||
with ActivityService =>
|
||||
self: RepositoryService & AccountService & ReleaseService & ReadableUsersAuthenticator & ReferrerAuthenticator &
|
||||
WritableUsersAuthenticator & ActivityService =>
|
||||
|
||||
case class ReleaseForm(
|
||||
private case class ReleaseForm(
|
||||
name: String,
|
||||
content: Option[String]
|
||||
)
|
||||
|
||||
val releaseForm = mapping(
|
||||
private val releaseForm = mapping(
|
||||
"name" -> trim(text(required)),
|
||||
"content" -> trim(optional(text()))
|
||||
)(ReleaseForm.apply)
|
||||
@@ -130,16 +125,15 @@ trait ReleaseControllerBase extends ControllerBase {
|
||||
val releaseInfo = ReleaseInfo(repository.owner, repository.name, loginAccount.userName, form.name, tagName)
|
||||
recordActivity(releaseInfo)
|
||||
|
||||
redirect(s"/${repository.owner}/${repository.name}/releases/${tagName}")
|
||||
redirect(s"/${repository.owner}/${repository.name}/releases/$tagName")
|
||||
}
|
||||
})
|
||||
|
||||
get("/:owner/:repository/changelog/*...*")(writableUsersOnly { repository =>
|
||||
val Seq(previousTag, currentTag) = multiParams("splat")
|
||||
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 commits = JGitUtil.getCommitLog(git, previousTagId, currentTag).reverse
|
||||
val Seq(previousTag, currentTag) = multiParams("splat")
|
||||
|
||||
val commits = JGitUtil.getCommitLog(git, previousTag, currentTag).reverse
|
||||
commits
|
||||
.map { commit =>
|
||||
s"- ${commit.shortMessage} ${commit.id}"
|
||||
@@ -205,7 +199,7 @@ trait ReleaseControllerBase extends ControllerBase {
|
||||
}
|
||||
}
|
||||
|
||||
redirect(s"/${release.userName}/${release.repositoryName}/releases/${tagName}")
|
||||
redirect(s"/${release.userName}/${release.repositoryName}/releases/$tagName")
|
||||
}
|
||||
.getOrElse(NotFound())
|
||||
}
|
||||
@@ -223,7 +217,7 @@ trait ReleaseControllerBase extends ControllerBase {
|
||||
})
|
||||
|
||||
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 tagsToDisplay = repository.tags.reverse.slice(offset, offset + limit)
|
||||
|
||||
@@ -4,16 +4,16 @@ import java.time.{LocalDateTime, ZoneOffset}
|
||||
import java.util.Date
|
||||
import gitbucket.core.settings.html
|
||||
import gitbucket.core.model.{RepositoryWebHook, WebHook}
|
||||
import gitbucket.core.service._
|
||||
import gitbucket.core.service.WebHookService._
|
||||
import gitbucket.core.util._
|
||||
import gitbucket.core.util.JGitUtil._
|
||||
import gitbucket.core.util.SyntaxSugars._
|
||||
import gitbucket.core.util.Implicits._
|
||||
import gitbucket.core.util.Directory._
|
||||
import gitbucket.core.service.*
|
||||
import gitbucket.core.service.WebHookService.*
|
||||
import gitbucket.core.util.*
|
||||
import gitbucket.core.util.JGitUtil.*
|
||||
import gitbucket.core.util.SyntaxSugars.*
|
||||
import gitbucket.core.util.Implicits.*
|
||||
import gitbucket.core.util.Directory.*
|
||||
import gitbucket.core.model.WebHookContentType
|
||||
import gitbucket.core.model.activity.RenameRepositoryInfo
|
||||
import org.scalatra.forms._
|
||||
import org.scalatra.forms.*
|
||||
import org.scalatra.i18n.Messages
|
||||
import org.eclipse.jgit.api.Git
|
||||
import org.eclipse.jgit.lib.Constants
|
||||
@@ -37,19 +37,11 @@ class RepositorySettingsController
|
||||
with RequestCache
|
||||
|
||||
trait RepositorySettingsControllerBase extends ControllerBase {
|
||||
self: RepositoryService
|
||||
with AccountService
|
||||
with WebHookService
|
||||
with ProtectedBranchService
|
||||
with CommitStatusService
|
||||
with DeployKeyService
|
||||
with CustomFieldsService
|
||||
with ActivityService
|
||||
with OwnerAuthenticator
|
||||
with UsersAuthenticator =>
|
||||
self: RepositoryService & AccountService & WebHookService & ProtectedBranchService & CommitStatusService &
|
||||
DeployKeyService & CustomFieldsService & ActivityService & OwnerAuthenticator & UsersAuthenticator =>
|
||||
|
||||
// for repository options
|
||||
case class OptionsForm(
|
||||
private case class OptionsForm(
|
||||
description: Option[String],
|
||||
isPrivate: Boolean,
|
||||
issuesOption: String,
|
||||
@@ -62,7 +54,7 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
||||
safeMode: Boolean
|
||||
)
|
||||
|
||||
val optionsForm = mapping(
|
||||
private val optionsForm = mapping(
|
||||
"description" -> trim(label("Description", optional(text()))),
|
||||
"isPrivate" -> trim(label("Repository Type", boolean())),
|
||||
"issuesOption" -> trim(label("Issues Option", text(required, featureOption))),
|
||||
@@ -80,25 +72,30 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
||||
}
|
||||
|
||||
// for default branch
|
||||
case class DefaultBranchForm(defaultBranch: String)
|
||||
private case class DefaultBranchForm(defaultBranch: String)
|
||||
|
||||
val defaultBranchForm = mapping(
|
||||
private val defaultBranchForm = mapping(
|
||||
"defaultBranch" -> trim(label("Default Branch", text(required, maxlength(100))))
|
||||
)(DefaultBranchForm.apply)
|
||||
|
||||
// for deploy key
|
||||
case class DeployKeyForm(title: String, publicKey: String, allowWrite: Boolean)
|
||||
private case class DeployKeyForm(title: String, publicKey: String, allowWrite: Boolean)
|
||||
|
||||
val deployKeyForm = mapping(
|
||||
private val deployKeyForm = mapping(
|
||||
"title" -> trim(label("Title", text(required, maxlength(100)))),
|
||||
"publicKey" -> trim2(label("Key", text(required))), // TODO duplication check in the repository?
|
||||
"allowWrite" -> trim(label("Key", boolean()))
|
||||
)(DeployKeyForm.apply)
|
||||
|
||||
// for web hook url addition
|
||||
case class WebHookForm(url: String, events: Set[WebHook.Event], ctype: WebHookContentType, token: Option[String])
|
||||
private case class WebHookForm(
|
||||
url: String,
|
||||
events: Set[WebHook.Event],
|
||||
ctype: WebHookContentType,
|
||||
token: Option[String]
|
||||
)
|
||||
|
||||
def webHookForm(update: Boolean) =
|
||||
private def webHookForm(update: Boolean) =
|
||||
mapping(
|
||||
"url" -> trim(label("url", text(required, webHook(update)))),
|
||||
"events" -> webhookEvents,
|
||||
@@ -107,23 +104,23 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
||||
)((url, events, ctype, token) => WebHookForm(url, events, WebHookContentType.valueOf(ctype), token))
|
||||
|
||||
// for rename repository
|
||||
case class RenameRepositoryForm(repositoryName: String)
|
||||
private case class RenameRepositoryForm(repositoryName: String)
|
||||
|
||||
val renameForm = mapping(
|
||||
private val renameForm = mapping(
|
||||
"repositoryName" -> trim(
|
||||
label("New repository name", text(required, maxlength(100), repository, renameRepositoryName))
|
||||
)
|
||||
)(RenameRepositoryForm.apply)
|
||||
|
||||
// for transfer ownership
|
||||
case class TransferOwnerShipForm(newOwner: String)
|
||||
private case class TransferOwnerShipForm(newOwner: String)
|
||||
|
||||
val transferForm = mapping(
|
||||
private val transferForm = mapping(
|
||||
"newOwner" -> trim(label("New owner", text(required, transferUser)))
|
||||
)(TransferOwnerShipForm.apply)
|
||||
|
||||
// for custom field
|
||||
case class CustomFieldForm(
|
||||
private case class CustomFieldForm(
|
||||
fieldName: String,
|
||||
fieldType: String,
|
||||
constraints: Option[String],
|
||||
@@ -131,7 +128,7 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
||||
enableForPullRequests: Boolean
|
||||
)
|
||||
|
||||
val customFieldForm = mapping(
|
||||
private val customFieldForm = mapping(
|
||||
"fieldName" -> trim(label("Field name", text(required, maxlength(100)))),
|
||||
"fieldType" -> trim(label("Field type", text(required))),
|
||||
"constraints" -> trim(label("Constraints", optional(text()))),
|
||||
@@ -186,7 +183,7 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
||||
/** Update default branch */
|
||||
post("/:owner/:repository/settings/update_default_branch", defaultBranchForm)(ownerOnly { (form, repository) =>
|
||||
if (!repository.branchList.contains(form.defaultBranch)) {
|
||||
redirect(s"/${repository.owner}/${repository.name}/settings/options")
|
||||
redirect(s"/${repository.owner}/${repository.name}/settings/branches")
|
||||
} else {
|
||||
saveRepositoryDefaultBranch(repository.owner, repository.name, form.defaultBranch)
|
||||
// Change repository HEAD
|
||||
@@ -200,13 +197,13 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
||||
|
||||
/** Branch protection for branch */
|
||||
get("/:owner/:repository/settings/branches/*")(ownerOnly { repository =>
|
||||
import gitbucket.core.api._
|
||||
import gitbucket.core.api.*
|
||||
val branch = params("splat")
|
||||
|
||||
if (!repository.branchList.contains(branch)) {
|
||||
redirect(s"/${repository.owner}/${repository.name}/settings/branches")
|
||||
} else {
|
||||
val protection = ApiBranchProtection(getProtectedBranchInfo(repository.owner, repository.name, branch))
|
||||
val protection = ApiBranchProtectionResponse(getProtectedBranchInfo(repository.owner, repository.name, branch))
|
||||
val lastWeeks = getRecentStatusContexts(
|
||||
repository.owner,
|
||||
repository.name,
|
||||
@@ -256,7 +253,7 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
||||
ctype = WebHookContentType.FORM,
|
||||
token = None
|
||||
)
|
||||
html.edithook(webhook, Set(WebHook.Push), repository, true)
|
||||
html.edithook(webhook, Set(WebHook.Push), repository, create = true)
|
||||
})
|
||||
|
||||
/**
|
||||
@@ -287,9 +284,9 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
||||
}
|
||||
|
||||
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
|
||||
import scala.concurrent.duration._
|
||||
import scala.concurrent._
|
||||
import scala.jdk.CollectionConverters._
|
||||
import scala.concurrent.duration.*
|
||||
import scala.concurrent.*
|
||||
import scala.jdk.CollectionConverters.*
|
||||
import scala.util.control.NonFatal
|
||||
import org.apache.http.util.EntityUtils
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
@@ -335,10 +332,10 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
||||
callWebHook(WebHook.Push, List(dummyWebHookInfo), dummyPayload, context.settings).head
|
||||
|
||||
val toErrorMap: PartialFunction[Throwable, Map[String, String]] = {
|
||||
case e: java.net.UnknownHostException => Map("error" -> ("Unknown host " + e.getMessage))
|
||||
case e: java.lang.IllegalArgumentException => Map("error" -> ("invalid url"))
|
||||
case e: org.apache.http.client.ClientProtocolException => Map("error" -> ("invalid url"))
|
||||
case NonFatal(e) => Map("error" -> (s"${e.getClass} ${e.getMessage}"))
|
||||
case e: java.net.UnknownHostException => Map("error" -> s"Unknown host ${e.getMessage}")
|
||||
case _: java.lang.IllegalArgumentException => Map("error" -> "invalid url")
|
||||
case _: org.apache.http.client.ClientProtocolException => Map("error" -> "invalid url")
|
||||
case NonFatal(e) => Map("error" -> s"${e.getClass} ${e.getMessage}")
|
||||
}
|
||||
|
||||
contentType = formats("json")
|
||||
@@ -361,8 +358,8 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
||||
.map(res =>
|
||||
Map(
|
||||
"status" -> res.getStatusLine.getStatusCode,
|
||||
"body" -> EntityUtils.toString(res.getEntity()),
|
||||
"headers" -> _headers(res.getAllHeaders())
|
||||
"body" -> EntityUtils.toString(res.getEntity),
|
||||
"headers" -> _headers(res.getAllHeaders)
|
||||
)
|
||||
)
|
||||
.recover(toErrorMap),
|
||||
@@ -378,7 +375,7 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
||||
*/
|
||||
get("/:owner/:repository/settings/hooks/edit")(ownerOnly { repository =>
|
||||
getWebHook(repository.owner, repository.name, params("url")).map { case (webhook, events) =>
|
||||
html.edithook(webhook, events, repository, false)
|
||||
html.edithook(webhook, events, repository, create = false)
|
||||
} getOrElse NotFound()
|
||||
})
|
||||
|
||||
@@ -631,7 +628,7 @@ trait RepositorySettingsControllerBase extends ControllerBase {
|
||||
new Constraint() {
|
||||
override def validate(name: String, value: String, messages: Messages): Option[String] =
|
||||
getAccountByUserName(value) match {
|
||||
case None => Some("User does not exist.")
|
||||
case None => Some("User does not exist.")
|
||||
case Some(x) =>
|
||||
if (x.userName == params("owner")) {
|
||||
Some("This is current repository owner.")
|
||||
|
||||
@@ -6,12 +6,12 @@ import javax.servlet.http.{HttpServletRequest, HttpServletResponse}
|
||||
import gitbucket.core.repo.html
|
||||
import gitbucket.core.helper
|
||||
import gitbucket.core.model.activity.DeleteBranchInfo
|
||||
import gitbucket.core.service._
|
||||
import gitbucket.core.service.*
|
||||
import gitbucket.core.service.RepositoryCommitFileService.CommitFile
|
||||
import gitbucket.core.util._
|
||||
import gitbucket.core.util.StringUtil._
|
||||
import gitbucket.core.util.Implicits._
|
||||
import gitbucket.core.util.Directory._
|
||||
import gitbucket.core.util.*
|
||||
import gitbucket.core.util.StringUtil.*
|
||||
import gitbucket.core.util.Implicits.*
|
||||
import gitbucket.core.util.Directory.*
|
||||
import gitbucket.core.model.{Account, WebHook}
|
||||
import gitbucket.core.service.RepositoryService.RepositoryInfo
|
||||
import gitbucket.core.service.WebHookService.{WebHookCreatePayload, WebHookPushPayload}
|
||||
@@ -23,19 +23,18 @@ import org.apache.commons.compress.archivers.zip.{ZipArchiveEntry, ZipArchiveOut
|
||||
import org.apache.commons.compress.compressors.bzip2.BZip2CompressorOutputStream
|
||||
import org.apache.commons.compress.compressors.gzip.GzipCompressorOutputStream
|
||||
import org.apache.commons.compress.compressors.xz.XZCompressorOutputStream
|
||||
import org.apache.commons.compress.utils.IOUtils
|
||||
import org.apache.commons.io.FileUtils
|
||||
import org.scalatra.forms._
|
||||
import org.apache.commons.io.{FileUtils, IOUtils}
|
||||
import org.scalatra.forms.*
|
||||
import org.eclipse.jgit.api.{ArchiveCommand, Git}
|
||||
import org.eclipse.jgit.archive.{TgzFormat, ZipFormat}
|
||||
import org.eclipse.jgit.errors.MissingObjectException
|
||||
import org.eclipse.jgit.lib._
|
||||
import org.eclipse.jgit.lib.*
|
||||
import org.eclipse.jgit.treewalk.{TreeWalk, WorkingTreeOptions}
|
||||
import org.eclipse.jgit.treewalk.TreeWalk.OperationType
|
||||
import org.eclipse.jgit.treewalk.filter.PathFilter
|
||||
import org.eclipse.jgit.util.io.EolStreamTypeUtil
|
||||
import org.json4s.jackson.Serialization
|
||||
import org.scalatra._
|
||||
import org.scalatra.*
|
||||
import org.scalatra.i18n.Messages
|
||||
|
||||
class RepositoryViewerController
|
||||
@@ -65,26 +64,15 @@ class RepositoryViewerController
|
||||
* The repository viewer.
|
||||
*/
|
||||
trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
self: RepositoryService
|
||||
with RepositoryCommitFileService
|
||||
with AccountService
|
||||
with ActivityService
|
||||
with IssuesService
|
||||
with WebHookService
|
||||
with CommitsService
|
||||
with ReadableUsersAuthenticator
|
||||
with ReferrerAuthenticator
|
||||
with WritableUsersAuthenticator
|
||||
with PullRequestService
|
||||
with CommitStatusService
|
||||
with WebHookPullRequestService
|
||||
with WebHookPullRequestReviewCommentService
|
||||
with ProtectedBranchService =>
|
||||
self: RepositoryService & RepositoryCommitFileService & AccountService & ActivityService & IssuesService &
|
||||
WebHookService & CommitsService & ReadableUsersAuthenticator & ReferrerAuthenticator & WritableUsersAuthenticator &
|
||||
PullRequestService & CommitStatusService & WebHookPullRequestService & WebHookPullRequestReviewCommentService &
|
||||
ProtectedBranchService =>
|
||||
|
||||
ArchiveCommand.registerFormat("zip", new ZipFormat)
|
||||
ArchiveCommand.registerFormat("tar.gz", new TgzFormat)
|
||||
|
||||
case class UploadForm(
|
||||
private case class UploadForm(
|
||||
branch: String,
|
||||
path: String,
|
||||
uploadFiles: String,
|
||||
@@ -93,20 +81,21 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
newBranch: Boolean
|
||||
)
|
||||
|
||||
case class EditorForm(
|
||||
private case class EditorForm(
|
||||
branch: String,
|
||||
path: String,
|
||||
content: String,
|
||||
message: Option[String],
|
||||
charset: String,
|
||||
lineSeparator: String,
|
||||
hasBom: Boolean,
|
||||
newFileName: String,
|
||||
oldFileName: Option[String],
|
||||
commit: String,
|
||||
newBranch: Boolean
|
||||
)
|
||||
|
||||
case class DeleteForm(
|
||||
private case class DeleteForm(
|
||||
branch: String,
|
||||
path: String,
|
||||
message: Option[String],
|
||||
@@ -115,7 +104,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
newBranch: Boolean
|
||||
)
|
||||
|
||||
case class CommentForm(
|
||||
private case class CommentForm(
|
||||
fileName: Option[String],
|
||||
oldLineNumber: Option[Int],
|
||||
newLineNumber: Option[Int],
|
||||
@@ -124,13 +113,13 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
diff: Option[String]
|
||||
)
|
||||
|
||||
case class TagForm(
|
||||
private case class TagForm(
|
||||
commitId: String,
|
||||
tagName: String,
|
||||
message: Option[String]
|
||||
)
|
||||
|
||||
val uploadForm = mapping(
|
||||
private val uploadForm = mapping(
|
||||
"branch" -> trim(label("Branch", text(required))),
|
||||
"path" -> trim(label("Path", text())),
|
||||
"uploadFiles" -> trim(label("Upload files", text(required))),
|
||||
@@ -139,20 +128,21 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
"newBranch" -> trim(label("New Branch", boolean()))
|
||||
)(UploadForm.apply)
|
||||
|
||||
val editorForm = mapping(
|
||||
private val editorForm = mapping(
|
||||
"branch" -> trim(label("Branch", text(required))),
|
||||
"path" -> trim(label("Path", text())),
|
||||
"content" -> trim(label("Content", text(required))),
|
||||
"message" -> trim(label("Message", optional(text()))),
|
||||
"charset" -> trim(label("Charset", text(required))),
|
||||
"lineSeparator" -> trim(label("Line Separator", text(required))),
|
||||
"hasBom" -> trim(label("Has BOM", boolean())),
|
||||
"newFileName" -> trim(label("Filename", text(required))),
|
||||
"oldFileName" -> trim(label("Old filename", optional(text()))),
|
||||
"commit" -> trim(label("Commit", text(required, conflict))),
|
||||
"newBranch" -> trim(label("New Branch", boolean()))
|
||||
)(EditorForm.apply)
|
||||
|
||||
val deleteForm = mapping(
|
||||
private val deleteForm = mapping(
|
||||
"branch" -> trim(label("Branch", text(required))),
|
||||
"path" -> trim(label("Path", text())),
|
||||
"message" -> trim(label("Message", optional(text()))),
|
||||
@@ -161,7 +151,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
"newBranch" -> trim(label("New Branch", boolean()))
|
||||
)(DeleteForm.apply)
|
||||
|
||||
val commentForm = mapping(
|
||||
private val commentForm = mapping(
|
||||
"fileName" -> trim(label("Filename", optional(text()))),
|
||||
"oldLineNumber" -> trim(label("Old line number", optional(number()))),
|
||||
"newLineNumber" -> trim(label("New line number", optional(number()))),
|
||||
@@ -170,7 +160,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
"diff" -> optional(text())
|
||||
)(CommentForm.apply)
|
||||
|
||||
val tagForm = mapping(
|
||||
private val tagForm = mapping(
|
||||
"commitId" -> trim(label("Commit id", text(required))),
|
||||
"tagName" -> trim(label("Tag name", text(required))),
|
||||
"message" -> trim(label("Message", optional(text())))
|
||||
@@ -181,31 +171,19 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
*/
|
||||
post("/:owner/:repository/_preview")(referrersOnly { repository =>
|
||||
contentType = "text/html"
|
||||
val filename = params.get("filename")
|
||||
filename match {
|
||||
case Some(f) =>
|
||||
helpers.renderMarkup(
|
||||
filePath = List(f),
|
||||
fileContent = params("content"),
|
||||
branch = repository.repository.defaultBranch,
|
||||
repository = repository,
|
||||
enableWikiLink = params("enableWikiLink").toBoolean,
|
||||
enableRefsLink = params("enableRefsLink").toBoolean,
|
||||
enableAnchor = false
|
||||
)
|
||||
case None =>
|
||||
helpers.markdown(
|
||||
markdown = params("content"),
|
||||
repository = repository,
|
||||
branch = repository.repository.defaultBranch,
|
||||
enableWikiLink = params("enableWikiLink").toBoolean,
|
||||
enableRefsLink = params("enableRefsLink").toBoolean,
|
||||
enableLineBreaks = params("enableLineBreaks").toBoolean,
|
||||
enableTaskList = params("enableTaskList").toBoolean,
|
||||
enableAnchor = false,
|
||||
hasWritePermission = hasDeveloperRole(repository.owner, repository.name, context.loginAccount)
|
||||
)
|
||||
}
|
||||
val filename = params.get("filename").getOrElse("temporary.md")
|
||||
helpers.renderMarkup(
|
||||
filePath = filename.split("/").toList,
|
||||
fileContent = params("content"),
|
||||
branch = repository.repository.defaultBranch,
|
||||
repository = repository,
|
||||
enableWikiLink = params("enableWikiLink").toBoolean,
|
||||
enableRefsLink = params("enableRefsLink").toBoolean,
|
||||
enableLineBreaks = params("enableLineBreaks").toBoolean,
|
||||
enableTaskList = params("enableTaskList").toBoolean,
|
||||
enableAnchor = false,
|
||||
hasWritePermission = hasDeveloperRole(repository.owner, repository.name, context.loginAccount)
|
||||
)
|
||||
})
|
||||
|
||||
/**
|
||||
@@ -296,7 +274,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
html.editor(
|
||||
branch = branch,
|
||||
repository = repository,
|
||||
pathList = if (path.length == 0) Nil else path.split("/").toList,
|
||||
pathList = if (path.isEmpty) Nil else path.split("/").toList,
|
||||
fileName = None,
|
||||
content = JGitUtil.ContentInfo("text", None, None, Some("UTF-8")),
|
||||
protectedBranch = protectedBranch,
|
||||
@@ -316,7 +294,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
html.upload(
|
||||
branch,
|
||||
repository,
|
||||
if (path.length == 0) Nil else path.split("/").toList,
|
||||
if (path.isEmpty) Nil else path.split("/").toList,
|
||||
protectedBranch,
|
||||
revCommit.name
|
||||
)
|
||||
@@ -364,7 +342,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
.toSeq
|
||||
|
||||
val newFiles = files.map { file =>
|
||||
file.copy(name = if (form.path.length == 0) file.name else s"${form.path}/${file.name}")
|
||||
file.copy(name = if (form.path.isEmpty) file.name else s"${form.path}/${file.name}")
|
||||
}
|
||||
|
||||
if (form.newBranch) {
|
||||
@@ -381,13 +359,13 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
form.message,
|
||||
loginAccount
|
||||
)
|
||||
redirect(s"/${repository.owner}/${repository.name}/pull/${issueId}")
|
||||
redirect(s"/${repository.owner}/${repository.name}/pull/$issueId")
|
||||
case Left(error) => Forbidden(gitbucket.core.html.error(error))
|
||||
}
|
||||
} else {
|
||||
_commit(form.branch, newFiles, loginAccount) match {
|
||||
case Right(_) =>
|
||||
if (form.path.length == 0) {
|
||||
if (form.path.isEmpty) {
|
||||
redirect(s"/${repository.owner}/${repository.name}/tree/${encodeRefName(form.branch)}")
|
||||
} else {
|
||||
redirect(
|
||||
@@ -463,7 +441,8 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
message = form.message.getOrElse(s"Create ${form.newFileName}"),
|
||||
commit = form.commit,
|
||||
loginAccount = loginAccount,
|
||||
settings = context.settings
|
||||
settings = context.settings,
|
||||
hasBom = form.hasBom
|
||||
).map(_._1)
|
||||
}
|
||||
|
||||
@@ -482,19 +461,19 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
form.message,
|
||||
loginAccount
|
||||
)
|
||||
redirect(s"/${repository.owner}/${repository.name}/pull/${issueId}")
|
||||
redirect(s"/${repository.owner}/${repository.name}/pull/$issueId")
|
||||
case Left(error) => Forbidden(gitbucket.core.html.error(error))
|
||||
}
|
||||
} else {
|
||||
_commit(form.branch, loginAccount) match {
|
||||
case Right(_) =>
|
||||
if (form.path.length == 0) {
|
||||
if (form.path.isEmpty) {
|
||||
redirect(
|
||||
s"/${repository.owner}/${repository.name}/blob/${encodeRefName(form.branch)}/${urlEncode(form.newFileName)}"
|
||||
s"/${repository.owner}/${repository.name}/blob/${encodeRefName(form.branch)}/${encodeRefName(form.newFileName)}"
|
||||
)
|
||||
} else {
|
||||
redirect(
|
||||
s"/${repository.owner}/${repository.name}/blob/${encodeRefName(form.branch)}/${encodeRefName(form.path)}/${urlEncode(form.newFileName)}"
|
||||
s"/${repository.owner}/${repository.name}/blob/${encodeRefName(form.branch)}/${encodeRefName(form.path)}/${encodeRefName(form.newFileName)}"
|
||||
)
|
||||
}
|
||||
case Left(error) => Forbidden(gitbucket.core.html.error(error))
|
||||
@@ -520,7 +499,8 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
},
|
||||
commit = form.commit,
|
||||
loginAccount = loginAccount,
|
||||
settings = context.settings
|
||||
settings = context.settings,
|
||||
hasBom = form.hasBom
|
||||
).map(_._1)
|
||||
}
|
||||
|
||||
@@ -539,19 +519,19 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
form.message,
|
||||
loginAccount
|
||||
)
|
||||
redirect(s"/${repository.owner}/${repository.name}/pull/${issueId}")
|
||||
redirect(s"/${repository.owner}/${repository.name}/pull/$issueId")
|
||||
case Left(error) => Forbidden(gitbucket.core.html.error(error))
|
||||
}
|
||||
} else {
|
||||
_commit(form.branch, loginAccount) match {
|
||||
case Right(_) =>
|
||||
if (form.path.length == 0) {
|
||||
if (form.path.isEmpty) {
|
||||
redirect(
|
||||
s"/${repository.owner}/${repository.name}/blob/${encodeRefName(form.branch)}/${urlEncode(form.newFileName)}"
|
||||
s"/${repository.owner}/${repository.name}/blob/${encodeRefName(form.branch)}/${encodeRefName(form.newFileName)}"
|
||||
)
|
||||
} else {
|
||||
redirect(
|
||||
s"/${repository.owner}/${repository.name}/blob/${encodeRefName(form.branch)}/${encodeRefName(form.path)}/${urlEncode(form.newFileName)}"
|
||||
s"/${repository.owner}/${repository.name}/blob/${encodeRefName(form.branch)}/${encodeRefName(form.path)}/${encodeRefName(form.newFileName)}"
|
||||
)
|
||||
}
|
||||
case Left(error) => Forbidden(gitbucket.core.html.error(error))
|
||||
@@ -592,7 +572,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
form.message,
|
||||
loginAccount
|
||||
)
|
||||
redirect(s"/${repository.owner}/${repository.name}/pull/${issueId}")
|
||||
redirect(s"/${repository.owner}/${repository.name}/pull/$issueId")
|
||||
case Left(error) => Forbidden(gitbucket.core.html.error(error))
|
||||
}
|
||||
} else {
|
||||
@@ -640,7 +620,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
sender,
|
||||
repository,
|
||||
owner,
|
||||
ref = newBranchName,
|
||||
ref = s"refs/heads/$newBranchName",
|
||||
refType = "branch"
|
||||
)
|
||||
}
|
||||
@@ -697,7 +677,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
/**
|
||||
* Displays the file content of the specified branch or commit.
|
||||
*/
|
||||
val blobRoute = get("/:owner/:repository/blob/*")(referrersOnly { repository =>
|
||||
private val blobRoute = get("/:owner/:repository/blob/*")(referrersOnly { repository =>
|
||||
val (id, path) = repository.splitPath(multiParams("splat").head)
|
||||
val raw = params.get("raw").getOrElse("false").toBoolean
|
||||
val highlighterTheme = getSyntaxHighlighterTheme()
|
||||
@@ -803,7 +783,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
JGitUtil.getBranchesOfCommit(git, revCommit.getName),
|
||||
JGitUtil.getTagsOfCommit(git, revCommit.getName),
|
||||
getCommitStatusWithSummary(repository.owner, repository.name, revCommit.getName),
|
||||
getCommitComments(repository.owner, repository.name, id, true),
|
||||
getCommitComments(repository.owner, repository.name, id, includePullRequest = true),
|
||||
repository,
|
||||
diffs,
|
||||
oldCommitId,
|
||||
@@ -813,7 +793,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
)
|
||||
}
|
||||
} catch {
|
||||
case e: MissingObjectException => NotFound()
|
||||
case _: MissingObjectException => NotFound()
|
||||
}
|
||||
})
|
||||
|
||||
@@ -825,7 +805,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
diff
|
||||
}
|
||||
} catch {
|
||||
case e: MissingObjectException => NotFound()
|
||||
case _: MissingObjectException => NotFound()
|
||||
}
|
||||
})
|
||||
|
||||
@@ -838,7 +818,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
diff
|
||||
}
|
||||
} catch {
|
||||
case e: MissingObjectException => NotFound()
|
||||
case _: MissingObjectException => NotFound()
|
||||
}
|
||||
})
|
||||
|
||||
@@ -857,7 +837,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
form.issueId
|
||||
)
|
||||
|
||||
redirect(s"/${repository.owner}/${repository.name}/commit/${id}")
|
||||
redirect(s"/${repository.owner}/${repository.name}/commit/$id")
|
||||
}
|
||||
})
|
||||
|
||||
@@ -908,19 +888,23 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
case t if t == "html" => html.editcomment(x.content, x.commentId, repository)
|
||||
} getOrElse {
|
||||
contentType = formats("json")
|
||||
val content = helpers
|
||||
.renderMarkup(
|
||||
filePath = List("temporary.md"),
|
||||
fileContent = x.content,
|
||||
branch = repository.repository.defaultBranch,
|
||||
repository = repository,
|
||||
enableWikiLink = false,
|
||||
enableRefsLink = true,
|
||||
enableAnchor = true,
|
||||
enableLineBreaks = true,
|
||||
enableTaskList = true,
|
||||
hasWritePermission = true
|
||||
)
|
||||
.toString()
|
||||
org.json4s.jackson.Serialization.write(
|
||||
Map(
|
||||
"content" -> view.Markdown.toHtml(
|
||||
markdown = x.content,
|
||||
repository = repository,
|
||||
branch = repository.repository.defaultBranch,
|
||||
enableWikiLink = false,
|
||||
enableRefsLink = true,
|
||||
enableAnchor = true,
|
||||
enableLineBreaks = true,
|
||||
enableTaskList = true,
|
||||
hasWritePermission = true
|
||||
)
|
||||
"content" -> content
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -1017,7 +1001,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
case Right(message) =>
|
||||
flash.update("info", message)
|
||||
val settings = loadSystemSettings()
|
||||
val newCommitId = git.getRepository.resolve(s"refs/heads/${newBranchName}")
|
||||
val newCommitId = git.getRepository.resolve(s"refs/heads/$newBranchName")
|
||||
val oldCommitId = ObjectId.fromString("0" * 40)
|
||||
// call push webhook
|
||||
callWebHookOf(repository.owner, repository.name, WebHook.Push, settings) {
|
||||
@@ -1028,7 +1012,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
WebHookPushPayload(
|
||||
git,
|
||||
pusherAccount,
|
||||
newBranchName,
|
||||
s"refs/heads/$newBranchName",
|
||||
repository,
|
||||
List(),
|
||||
ownerAccount,
|
||||
@@ -1047,7 +1031,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
sender,
|
||||
repository,
|
||||
owner,
|
||||
ref = newBranchName,
|
||||
ref = s"refs/heads/$newBranchName",
|
||||
refType = "branch"
|
||||
)
|
||||
}
|
||||
@@ -1057,7 +1041,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
)
|
||||
case Left(message) =>
|
||||
flash.update("error", message)
|
||||
redirect(s"/${repository.owner}/${repository.name}/tree/${fromBranchName}")
|
||||
redirect(s"/${repository.owner}/${repository.name}/tree/$fromBranchName")
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -1087,14 +1071,9 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
redirect(s"${repository.owner}/${repository.name}/releases")
|
||||
})
|
||||
|
||||
get("/:owner/:repository/archive/:name")(referrersOnly { repository =>
|
||||
val name = params("name")
|
||||
archiveRepository(name, repository, "")
|
||||
})
|
||||
|
||||
get("/:owner/:repository/archive/*/:name")(referrersOnly { repository =>
|
||||
val name = params("name")
|
||||
val path = multiParams("splat").head
|
||||
get("/:owner/:repository/archive/*")(referrersOnly { repository =>
|
||||
val name = multiParams("splat").mkString("/")
|
||||
val path = params.get("path").getOrElse("")
|
||||
archiveRepository(name, repository, path)
|
||||
})
|
||||
|
||||
@@ -1143,9 +1122,9 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
}
|
||||
})
|
||||
|
||||
case class UploadFiles(branch: String, path: String, fileIds: Map[String, String], message: String) {
|
||||
lazy val isValid: Boolean = fileIds.nonEmpty
|
||||
}
|
||||
// case class UploadFiles(branch: String, path: String, fileIds: Map[String, String], message: String) {
|
||||
// lazy val isValid: Boolean = fileIds.nonEmpty
|
||||
// }
|
||||
|
||||
/**
|
||||
* Provides HTML of the file list.
|
||||
@@ -1185,7 +1164,11 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
val path = (file.name :: parentPath.reverse).reverse
|
||||
path -> StringUtil.convertFromByteArray(
|
||||
JGitUtil
|
||||
.getContentFromId(Git.open(getRepositoryDir(repository.owner, repository.name)), file.id, true)
|
||||
.getContentFromId(
|
||||
Git.open(getRepositoryDir(repository.owner, repository.name)),
|
||||
file.id,
|
||||
fetchLargeFile = true
|
||||
)
|
||||
.get
|
||||
)
|
||||
}
|
||||
@@ -1226,7 +1209,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
val oid = git.getRepository.resolve(revision)
|
||||
val commit = JGitUtil.getRevCommitFromId(git, oid)
|
||||
val date = commit.getCommitterIdent.getWhen
|
||||
val sha1 = oid.getName()
|
||||
val sha1 = oid.getName
|
||||
val repositorySuffix = (if (sha1.startsWith(revision)) sha1 else revision).replace('/', '-')
|
||||
val pathSuffix = if (path.isEmpty) "" else s"-${path.replace('/', '-')}"
|
||||
val baseName = repository.name + "-" + repositorySuffix + pathSuffix
|
||||
@@ -1234,7 +1217,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
Using.resource(new TreeWalk(git.getRepository)) { treeWalk =>
|
||||
treeWalk.addTree(commit.getTree)
|
||||
treeWalk.setRecursive(true)
|
||||
if (!path.isEmpty) {
|
||||
if (path.nonEmpty) {
|
||||
treeWalk.setFilter(PathFilter.create(path))
|
||||
}
|
||||
if (treeWalk != null) {
|
||||
@@ -1278,7 +1261,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
}
|
||||
|
||||
val suffix =
|
||||
path.split("/").lastOption.collect { case x if x.length > 0 => "-" + x.replace('/', '_') }.getOrElse("")
|
||||
path.split("/").lastOption.collect { case x if x.nonEmpty => "-" + x.replace('/', '_') }.getOrElse("")
|
||||
val zipRe = """(.+)\.zip$""".r
|
||||
val tarRe = """(.+)\.tar\.(gz|bz2|xz)$""".r
|
||||
|
||||
@@ -1286,7 +1269,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
case zipRe(revision) =>
|
||||
response.setHeader(
|
||||
"Content-Disposition",
|
||||
s"attachment; filename=${repository.name}-${revision}${suffix}.zip"
|
||||
s"attachment; filename=${repository.name}-$revision$suffix.zip"
|
||||
)
|
||||
contentType = "application/octet-stream"
|
||||
response.setBufferSize(1024 * 1024)
|
||||
@@ -1303,7 +1286,7 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
case tarRe(revision, compressor) =>
|
||||
response.setHeader(
|
||||
"Content-Disposition",
|
||||
s"attachment; filename=${repository.name}-${revision}${suffix}.tar.${compressor}"
|
||||
s"attachment; filename=${repository.name}-$revision$suffix.tar.$compressor"
|
||||
)
|
||||
contentType = "application/octet-stream"
|
||||
response.setBufferSize(1024 * 1024)
|
||||
@@ -1341,9 +1324,9 @@ trait RepositoryViewerControllerBase extends ControllerBase {
|
||||
val repository = params("repository")
|
||||
val branch = params("branch")
|
||||
|
||||
LockUtil.lock(s"${owner}/${repository}") {
|
||||
LockUtil.lock(s"$owner/$repository") {
|
||||
Using.resource(Git.open(getRepositoryDir(owner, repository))) { git =>
|
||||
val headName = s"refs/heads/${branch}"
|
||||
val headName = s"refs/heads/$branch"
|
||||
val headTip = git.getRepository.resolve(headName)
|
||||
if (headTip.getName != value) {
|
||||
Some("Someone pushed new commits before you. Please reload this page and re-apply your changes.")
|
||||
|
||||
@@ -3,17 +3,17 @@ package gitbucket.core.controller
|
||||
import java.io.FileInputStream
|
||||
import gitbucket.core.admin.html
|
||||
import gitbucket.core.plugin.PluginRegistry
|
||||
import gitbucket.core.service.SystemSettingsService._
|
||||
import gitbucket.core.service.SystemSettingsService.*
|
||||
import gitbucket.core.service.{AccountService, RepositoryService}
|
||||
import gitbucket.core.ssh.SshServer
|
||||
import gitbucket.core.util.Implicits._
|
||||
import gitbucket.core.util.StringUtil._
|
||||
import gitbucket.core.util.Implicits.*
|
||||
import gitbucket.core.util.StringUtil.*
|
||||
import gitbucket.core.util.{AdminAuthenticator, Mailer}
|
||||
import org.apache.commons.io.IOUtils
|
||||
import org.apache.commons.mail.EmailException
|
||||
import org.json4s.jackson.Serialization
|
||||
import org.scalatra._
|
||||
import org.scalatra.forms._
|
||||
import org.scalatra.*
|
||||
import org.scalatra.forms.*
|
||||
import org.scalatra.i18n.Messages
|
||||
|
||||
import scala.collection.mutable.ListBuffer
|
||||
@@ -29,7 +29,7 @@ case class Table(name: String, columns: Seq[Column])
|
||||
case class Column(name: String, primaryKey: Boolean)
|
||||
|
||||
trait SystemSettingsControllerBase extends AccountManagementControllerBase {
|
||||
self: AccountService with RepositoryService with AdminAuthenticator =>
|
||||
self: AccountService & RepositoryService & AdminAuthenticator =>
|
||||
|
||||
private val form = mapping(
|
||||
"baseUrl" -> trim(label("Base URL", optional(text()))),
|
||||
@@ -125,7 +125,8 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
|
||||
"maxDiffFiles" -> trim(label("Max diff files", number(required))),
|
||||
"maxDiffLines" -> trim(label("Max diff lines", number(required)))
|
||||
)(RepositoryViewerSettings.apply),
|
||||
"defaultBranch" -> trim(label("Default branch", text(required)))
|
||||
"defaultBranch" -> trim(label("Default branch", text(required))),
|
||||
"showFullName" -> trim(label("Show full name", boolean()))
|
||||
)(SystemSettings.apply).verifying { settings =>
|
||||
Vector(
|
||||
if (settings.ssh.enabled && settings.baseUrl.isEmpty) {
|
||||
@@ -151,11 +152,11 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
|
||||
"testAddress" -> trim(label("", text(required)))
|
||||
)(SendMailForm.apply)
|
||||
|
||||
case class SendMailForm(smtp: Smtp, testAddress: String)
|
||||
private case class SendMailForm(smtp: Smtp, testAddress: String)
|
||||
|
||||
case class DataExportForm(tableNames: List[String])
|
||||
// case class DataExportForm(tableNames: List[String])
|
||||
|
||||
case class NewUserForm(
|
||||
private case class NewUserForm(
|
||||
userName: String,
|
||||
password: String,
|
||||
fullName: String,
|
||||
@@ -167,7 +168,7 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
|
||||
fileId: Option[String]
|
||||
)
|
||||
|
||||
case class EditUserForm(
|
||||
private case class EditUserForm(
|
||||
userName: String,
|
||||
password: Option[String],
|
||||
fullName: String,
|
||||
@@ -181,7 +182,7 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
|
||||
isRemoved: Boolean
|
||||
)
|
||||
|
||||
case class NewGroupForm(
|
||||
private case class NewGroupForm(
|
||||
groupName: String,
|
||||
description: Option[String],
|
||||
url: Option[String],
|
||||
@@ -189,7 +190,7 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
|
||||
members: String
|
||||
)
|
||||
|
||||
case class EditGroupForm(
|
||||
private case class EditGroupForm(
|
||||
groupName: String,
|
||||
description: Option[String],
|
||||
url: Option[String],
|
||||
@@ -199,7 +200,7 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
|
||||
isRemoved: Boolean
|
||||
)
|
||||
|
||||
val newUserForm = mapping(
|
||||
private val newUserForm = mapping(
|
||||
"userName" -> trim(label("Username", text(required, maxlength(100), identifier, uniqueUserName, reservedNames))),
|
||||
"password" -> trim(label("Password", text(required, maxlength(40)))),
|
||||
"fullName" -> trim(label("Full Name", text(required, maxlength(100)))),
|
||||
@@ -213,7 +214,7 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
|
||||
"fileId" -> trim(label("File ID", optional(text())))
|
||||
)(NewUserForm.apply)
|
||||
|
||||
val editUserForm = mapping(
|
||||
private val editUserForm = mapping(
|
||||
"userName" -> trim(label("Username", text(required, maxlength(100), identifier))),
|
||||
"password" -> trim(label("Password", optional(text(maxlength(40))))),
|
||||
"fullName" -> trim(label("Full Name", text(required, maxlength(100)))),
|
||||
@@ -229,7 +230,7 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
|
||||
"removed" -> trim(label("Disable", boolean(disableByNotYourself("userName"))))
|
||||
)(EditUserForm.apply)
|
||||
|
||||
val newGroupForm = mapping(
|
||||
private val newGroupForm = mapping(
|
||||
"groupName" -> trim(label("Group name", text(required, maxlength(100), identifier, uniqueUserName, reservedNames))),
|
||||
"description" -> trim(label("Group description", optional(text()))),
|
||||
"url" -> trim(label("URL", optional(text(maxlength(200))))),
|
||||
@@ -237,7 +238,7 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
|
||||
"members" -> trim(label("Members", text(required, members)))
|
||||
)(NewGroupForm.apply)
|
||||
|
||||
val editGroupForm = mapping(
|
||||
private val editGroupForm = mapping(
|
||||
"groupName" -> trim(label("Group name", text(required, maxlength(100), identifier))),
|
||||
"description" -> trim(label("Group description", optional(text()))),
|
||||
"url" -> trim(label("URL", optional(text(maxlength(200))))),
|
||||
@@ -363,7 +364,7 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
|
||||
})
|
||||
|
||||
post("/admin/plugins/_reload")(adminOnly {
|
||||
PluginRegistry.reload(request.getServletContext(), loadSystemSettings(), request2Session(request).conn)
|
||||
PluginRegistry.reload(request.getServletContext, loadSystemSettings(), request2Session(request).conn)
|
||||
flash.update("info", "All plugins were reloaded.")
|
||||
redirect("/admin/plugins")
|
||||
})
|
||||
@@ -385,7 +386,7 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
|
||||
val includeGroups = params.get("includeGroups").exists(_.toBoolean)
|
||||
val users = getAllUsers(includeRemoved, includeGroups)
|
||||
val members = users.collect {
|
||||
case account if (account.isGroupAccount) =>
|
||||
case account if account.isGroupAccount =>
|
||||
account.userName -> getGroupMembers(account.userName).map(_.userName)
|
||||
}.toMap
|
||||
|
||||
@@ -406,7 +407,7 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
|
||||
form.description,
|
||||
form.url
|
||||
)
|
||||
updateImage(form.userName, form.fileId, false)
|
||||
updateImage(form.userName, form.fileId, clearImage = false)
|
||||
updateAccountExtraMailAddresses(form.userName, form.extraMailAddresses.filter(_ != ""))
|
||||
redirect("/admin/users")
|
||||
})
|
||||
@@ -414,12 +415,12 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
|
||||
get("/admin/users/:userName/_edituser")(adminOnly {
|
||||
val userName = params("userName")
|
||||
val extraMails = getAccountExtraMailAddresses(userName)
|
||||
html.user(getAccountByUserName(userName, true), extraMails, flash.get("error"))
|
||||
html.user(getAccountByUserName(userName, includeRemoved = true), extraMails, flash.get("error"))
|
||||
})
|
||||
|
||||
post("/admin/users/:name/_edituser", editUserForm)(adminOnly { form =>
|
||||
val userName = params("userName")
|
||||
getAccountByUserName(userName, true).map { account =>
|
||||
getAccountByUserName(userName, includeRemoved = true).map { account =>
|
||||
if (account.isAdmin && (form.isRemoved || !form.isAdmin) && isLastAdministrator(account)) {
|
||||
flash.update("error", "Account can't be turned off because this is last one administrator.")
|
||||
redirect(s"/admin/users/${userName}/_edituser")
|
||||
@@ -476,13 +477,13 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
|
||||
}
|
||||
.toList
|
||||
)
|
||||
updateImage(form.groupName, form.fileId, false)
|
||||
updateImage(form.groupName, form.fileId, clearImage = false)
|
||||
redirect("/admin/users")
|
||||
})
|
||||
|
||||
get("/admin/users/:groupName/_editgroup")(adminOnly {
|
||||
val groupName = params("groupName")
|
||||
html.usergroup(getAccountByUserName(groupName, true), getGroupMembers(groupName))
|
||||
html.usergroup(getAccountByUserName(groupName, includeRemoved = true), getGroupMembers(groupName))
|
||||
})
|
||||
|
||||
post("/admin/users/:groupName/_editgroup", editGroupForm)(adminOnly { form =>
|
||||
@@ -496,7 +497,7 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
|
||||
}
|
||||
.toList
|
||||
|
||||
getAccountByUserName(groupName, true).map { account =>
|
||||
getAccountByUserName(groupName, includeRemoved = true).map { account =>
|
||||
updateGroup(groupName, form.description, form.url, form.isRemoved)
|
||||
|
||||
if (form.isRemoved) {
|
||||
@@ -528,13 +529,13 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
|
||||
})
|
||||
|
||||
get("/admin/data")(adminOnly {
|
||||
import gitbucket.core.util.JDBCUtil._
|
||||
import gitbucket.core.util.JDBCUtil.*
|
||||
val session = request2Session(request)
|
||||
html.data(session.conn.allTableNames())
|
||||
})
|
||||
|
||||
post("/admin/export")(adminOnly {
|
||||
import gitbucket.core.util.JDBCUtil._
|
||||
import gitbucket.core.util.JDBCUtil.*
|
||||
val file = request2Session(request).conn.exportAsSQL(request.getParameterValues("tableNames").toSeq)
|
||||
|
||||
contentType = "application/octet-stream"
|
||||
@@ -577,7 +578,7 @@ trait SystemSettingsControllerBase extends AccountManagementControllerBase {
|
||||
for {
|
||||
userName <- params.get(paramName)
|
||||
loginAccount <- context.loginAccount
|
||||
if userName == loginAccount.userName && params.get("removed") == Some("true")
|
||||
if userName == loginAccount.userName && params.get("removed").contains("true")
|
||||
} yield "You can't disable your account yourself"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
package gitbucket.core.controller
|
||||
|
||||
import org.json4s.{JField, JObject, JString}
|
||||
import org.scalatra._
|
||||
import org.scalatra.json._
|
||||
import org.scalatra.forms._
|
||||
import org.scalatra.*
|
||||
import org.scalatra.json.*
|
||||
import org.scalatra.forms.*
|
||||
import org.scalatra.i18n.I18nSupport
|
||||
import org.scalatra.servlet.ServletBase
|
||||
|
||||
/**
|
||||
* Extends scalatra-forms to support the client-side validation and Ajax requests as well.
|
||||
*/
|
||||
trait ValidationSupport extends FormSupport { self: ServletBase with JacksonJsonSupport with I18nSupport =>
|
||||
trait ValidationSupport extends FormSupport { self: ServletBase & JacksonJsonSupport & I18nSupport =>
|
||||
|
||||
def get[T](path: String, form: ValueType[T])(action: T => Any): Route = {
|
||||
registerValidate(path, form)
|
||||
|
||||
@@ -5,13 +5,13 @@ import gitbucket.core.model.activity.{CreateWikiPageInfo, DeleteWikiInfo, EditWi
|
||||
import gitbucket.core.service.RepositoryService.RepositoryInfo
|
||||
import gitbucket.core.service.WebHookService.WebHookGollumPayload
|
||||
import gitbucket.core.wiki.html
|
||||
import gitbucket.core.service._
|
||||
import gitbucket.core.util._
|
||||
import gitbucket.core.util.StringUtil._
|
||||
import gitbucket.core.util.SyntaxSugars._
|
||||
import gitbucket.core.util.Implicits._
|
||||
import gitbucket.core.util.Directory._
|
||||
import org.scalatra.forms._
|
||||
import gitbucket.core.service.*
|
||||
import gitbucket.core.util.*
|
||||
import gitbucket.core.util.StringUtil.*
|
||||
import gitbucket.core.util.SyntaxSugars.*
|
||||
import gitbucket.core.util.Implicits.*
|
||||
import gitbucket.core.util.Directory.*
|
||||
import org.scalatra.forms.*
|
||||
import org.eclipse.jgit.api.Git
|
||||
import org.scalatra.i18n.Messages
|
||||
|
||||
@@ -29,15 +29,10 @@ class WikiController
|
||||
with RequestCache
|
||||
|
||||
trait WikiControllerBase extends ControllerBase {
|
||||
self: WikiService
|
||||
with RepositoryService
|
||||
with AccountService
|
||||
with ActivityService
|
||||
with WebHookService
|
||||
with ReadableUsersAuthenticator
|
||||
with ReferrerAuthenticator =>
|
||||
self: WikiService & RepositoryService & AccountService & ActivityService & WebHookService &
|
||||
ReadableUsersAuthenticator & ReferrerAuthenticator =>
|
||||
|
||||
case class WikiPageEditForm(
|
||||
private case class WikiPageEditForm(
|
||||
pageName: String,
|
||||
content: String,
|
||||
message: Option[String],
|
||||
@@ -45,7 +40,7 @@ trait WikiControllerBase extends ControllerBase {
|
||||
id: String
|
||||
)
|
||||
|
||||
val newForm = mapping(
|
||||
private val newForm = mapping(
|
||||
"pageName" -> trim(label("Page name", text(required, maxlength(40), pageName, unique))),
|
||||
"content" -> trim(label("Content", text(required, conflictForNew))),
|
||||
"message" -> trim(label("Message", optional(text()))),
|
||||
@@ -53,7 +48,7 @@ trait WikiControllerBase extends ControllerBase {
|
||||
"id" -> trim(label("Latest commit id", text()))
|
||||
)(WikiPageEditForm.apply)
|
||||
|
||||
val editForm = mapping(
|
||||
private val editForm = mapping(
|
||||
"pageName" -> trim(label("Page name", text(required, maxlength(40), pageName))),
|
||||
"content" -> trim(label("Content", text(required, conflictForEdit))),
|
||||
"message" -> trim(label("Message", optional(text()))),
|
||||
@@ -72,7 +67,8 @@ trait WikiControllerBase extends ControllerBase {
|
||||
repository,
|
||||
isEditable(repository),
|
||||
getWikiPage(repository.owner, repository.name, "_Sidebar", branch),
|
||||
getWikiPage(repository.owner, repository.name, "_Footer", branch)
|
||||
getWikiPage(repository.owner, repository.name, "_Footer", branch),
|
||||
branch
|
||||
)
|
||||
} getOrElse redirect(s"/${repository.owner}/${repository.name}/wiki/Home/_edit")
|
||||
})
|
||||
@@ -89,7 +85,8 @@ trait WikiControllerBase extends ControllerBase {
|
||||
repository,
|
||||
isEditable(repository),
|
||||
getWikiPage(repository.owner, repository.name, "_Sidebar", branch),
|
||||
getWikiPage(repository.owner, repository.name, "_Footer", branch)
|
||||
getWikiPage(repository.owner, repository.name, "_Footer", branch),
|
||||
branch
|
||||
)
|
||||
} getOrElse redirect(s"/${repository.owner}/${repository.name}/wiki/${StringUtil.urlEncode(pageName)}/_edit")
|
||||
})
|
||||
@@ -106,12 +103,6 @@ trait WikiControllerBase extends ControllerBase {
|
||||
}
|
||||
})
|
||||
|
||||
private def getWikiBranch(owner: String, repository: String): String = {
|
||||
Using.resource(Git.open(Directory.getWikiRepositoryDir(owner, repository))) { git =>
|
||||
git.getRepository.getBranch
|
||||
}
|
||||
}
|
||||
|
||||
get("/:owner/:repository/wiki/:page/_compare/:commitId")(referrersOnly { repository =>
|
||||
val pageName = StringUtil.urlDecode(params("page"))
|
||||
val Array(from, to) = params("commitId").split("\\.\\.\\.")
|
||||
@@ -175,7 +166,7 @@ trait WikiControllerBase extends ControllerBase {
|
||||
} else {
|
||||
flash.update("info", "This patch was not able to be reversed.")
|
||||
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()
|
||||
@@ -192,7 +183,7 @@ trait WikiControllerBase extends ControllerBase {
|
||||
redirect(s"/${repository.owner}/${repository.name}/wiki")
|
||||
} else {
|
||||
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()
|
||||
}
|
||||
@@ -288,7 +279,7 @@ trait WikiControllerBase extends ControllerBase {
|
||||
pageName,
|
||||
loginAccount.fullName,
|
||||
loginAccount.mailAddress,
|
||||
s"Destroyed ${pageName}"
|
||||
s"Destroyed $pageName"
|
||||
)
|
||||
val deleteWikiInfo = DeleteWikiInfo(
|
||||
repository.owner,
|
||||
@@ -311,7 +302,8 @@ trait WikiControllerBase extends ControllerBase {
|
||||
|
||||
get("/:owner/:repository/wiki/_history")(referrersOnly { repository =>
|
||||
Using.resource(Git.open(getWikiRepositoryDir(repository.owner, repository.name))) { git =>
|
||||
JGitUtil.getCommitLog(git, "master") match {
|
||||
val branch = getWikiBranch(repository.owner, repository.name)
|
||||
JGitUtil.getCommitLog(git, branch) match {
|
||||
case Right((logs, hasNext)) => html.history(None, logs, repository, isEditable(repository))
|
||||
case Left(_) => NotFound()
|
||||
}
|
||||
@@ -321,7 +313,8 @@ trait WikiControllerBase extends ControllerBase {
|
||||
get("/:owner/:repository/wiki/_blob/*")(referrersOnly { repository =>
|
||||
val path = multiParams("splat").head
|
||||
Using.resource(Git.open(getWikiRepositoryDir(repository.owner, repository.name))) { git =>
|
||||
val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve("master"))
|
||||
val branch = getWikiBranch(repository.owner, repository.name)
|
||||
val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(branch))
|
||||
|
||||
getPathObjectId(git, path, revCommit).map { objectId =>
|
||||
responseRawFile(git, objectId, path, repository)
|
||||
@@ -349,9 +342,9 @@ trait WikiControllerBase extends ControllerBase {
|
||||
private def pageName: Constraint = new Constraint() {
|
||||
override def validate(name: String, value: String, messages: Messages): Option[String] =
|
||||
if (value.exists("\\/:*?\"<>|".contains(_))) {
|
||||
Some(s"${name} contains invalid character.")
|
||||
Some(s"$name contains invalid character.")
|
||||
} else if (notReservedPageName(value) && (value.startsWith("_") || value.startsWith("-"))) {
|
||||
Some(s"${name} starts with invalid character.")
|
||||
Some(s"$name starts with invalid character.")
|
||||
} else {
|
||||
None
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ import scala.jdk.CollectionConverters._
|
||||
import scala.util.Using
|
||||
|
||||
trait ApiGitReferenceControllerBase extends ControllerBase {
|
||||
self: ReferrerAuthenticator with WritableUsersAuthenticator =>
|
||||
self: ReferrerAuthenticator & WritableUsersAuthenticator =>
|
||||
|
||||
private val logger = LoggerFactory.getLogger(classOf[ApiGitReferenceControllerBase])
|
||||
|
||||
@@ -138,7 +138,7 @@ trait ApiGitReferenceControllerBase extends ControllerBase {
|
||||
|
||||
val name = RepositoryName(repository)
|
||||
val result = JsonFormat(revstr match {
|
||||
case "tags" => repository.tags.map(ApiRef.fromTag(name, _))
|
||||
case "tags" => repository.tags.map(ApiRef.fromTag(name, _))
|
||||
case x if x.startsWith("tags/") =>
|
||||
val tagName = x.substring("tags/".length)
|
||||
repository.tags.find(_.name == tagName) match {
|
||||
|
||||
@@ -7,13 +7,8 @@ import gitbucket.core.util.{ReadableUsersAuthenticator, ReferrerAuthenticator, R
|
||||
import org.scalatra.ActionResult
|
||||
|
||||
trait ApiIssueCommentControllerBase extends ControllerBase {
|
||||
self: AccountService
|
||||
with IssuesService
|
||||
with RepositoryService
|
||||
with HandleCommentService
|
||||
with MilestonesService
|
||||
with ReadableUsersAuthenticator
|
||||
with ReferrerAuthenticator =>
|
||||
self: AccountService & IssuesService & RepositoryService & HandleCommentService & MilestonesService &
|
||||
ReadableUsersAuthenticator & ReferrerAuthenticator =>
|
||||
/*
|
||||
* i. List issue comments for a repository
|
||||
* https://docs.github.com/en/rest/reference/issues#list-issue-comments-for-a-repository
|
||||
|
||||
@@ -9,12 +9,8 @@ import gitbucket.core.util.{ReadableUsersAuthenticator, ReferrerAuthenticator, R
|
||||
import gitbucket.core.util.Implicits._
|
||||
|
||||
trait ApiIssueControllerBase extends ControllerBase {
|
||||
self: AccountService
|
||||
with IssuesService
|
||||
with IssueCreationService
|
||||
with MilestonesService
|
||||
with ReadableUsersAuthenticator
|
||||
with ReferrerAuthenticator =>
|
||||
self: AccountService & IssuesService & IssueCreationService & MilestonesService & ReadableUsersAuthenticator &
|
||||
ReferrerAuthenticator =>
|
||||
/*
|
||||
* i. List issues
|
||||
* https://developer.github.com/v3/issues/#list-issues
|
||||
|
||||
@@ -7,11 +7,7 @@ import gitbucket.core.util._
|
||||
import org.scalatra.{Created, NoContent, UnprocessableEntity}
|
||||
|
||||
trait ApiIssueLabelControllerBase extends ControllerBase {
|
||||
self: AccountService
|
||||
with IssuesService
|
||||
with LabelsService
|
||||
with ReferrerAuthenticator
|
||||
with WritableUsersAuthenticator =>
|
||||
self: AccountService & IssuesService & LabelsService & ReferrerAuthenticator & WritableUsersAuthenticator =>
|
||||
|
||||
/*
|
||||
* i. List all labels for this repository
|
||||
|
||||
@@ -7,7 +7,7 @@ import gitbucket.core.util.Implicits._
|
||||
import org.scalatra.NoContent
|
||||
|
||||
trait ApiIssueMilestoneControllerBase extends ControllerBase {
|
||||
self: MilestonesService with WritableUsersAuthenticator with ReferrerAuthenticator =>
|
||||
self: MilestonesService & WritableUsersAuthenticator & ReferrerAuthenticator =>
|
||||
|
||||
/*
|
||||
* i. List milestones
|
||||
@@ -29,7 +29,7 @@ trait ApiIssueMilestoneControllerBase extends ControllerBase {
|
||||
)
|
||||
}).reverse
|
||||
state match {
|
||||
case "all" => JsonFormat(apiMilestones)
|
||||
case "all" => JsonFormat(apiMilestones)
|
||||
case "open" | "closed" =>
|
||||
JsonFormat(
|
||||
apiMilestones.filter(p => p.state == state)
|
||||
|
||||
@@ -6,7 +6,7 @@ import gitbucket.core.util.Implicits._
|
||||
import gitbucket.core.util.{AdminAuthenticator, UsersAuthenticator}
|
||||
|
||||
trait ApiOrganizationControllerBase extends ControllerBase {
|
||||
self: RepositoryService with AccountService with AdminAuthenticator with UsersAuthenticator =>
|
||||
self: RepositoryService & AccountService & AdminAuthenticator & UsersAuthenticator =>
|
||||
|
||||
/*
|
||||
* i. List your organizations
|
||||
|
||||
@@ -16,14 +16,8 @@ import scala.util.Using
|
||||
import scala.jdk.CollectionConverters._
|
||||
|
||||
trait ApiPullRequestControllerBase extends ControllerBase {
|
||||
self: AccountService
|
||||
with IssuesService
|
||||
with PullRequestService
|
||||
with RepositoryService
|
||||
with MergeService
|
||||
with ReferrerAuthenticator
|
||||
with ReadableUsersAuthenticator
|
||||
with WritableUsersAuthenticator =>
|
||||
self: AccountService & IssuesService & PullRequestService & RepositoryService & MergeService & ReferrerAuthenticator &
|
||||
ReadableUsersAuthenticator & WritableUsersAuthenticator =>
|
||||
|
||||
/*
|
||||
* i. Link Relations
|
||||
|
||||
@@ -11,7 +11,7 @@ import org.apache.commons.io.FileUtils
|
||||
import org.scalatra.NoContent
|
||||
|
||||
trait ApiReleaseControllerBase extends ControllerBase {
|
||||
self: AccountService with ReleaseService with ReferrerAuthenticator with WritableUsersAuthenticator =>
|
||||
self: AccountService & ReleaseService & ReferrerAuthenticator & WritableUsersAuthenticator =>
|
||||
|
||||
/**
|
||||
* i. List releases for a repository
|
||||
|
||||
@@ -12,15 +12,8 @@ import org.scalatra.NoContent
|
||||
import scala.util.Using
|
||||
|
||||
trait ApiRepositoryBranchControllerBase extends ControllerBase {
|
||||
self: RepositoryService
|
||||
with AccountService
|
||||
with OwnerAuthenticator
|
||||
with UsersAuthenticator
|
||||
with GroupManagerAuthenticator
|
||||
with ProtectedBranchService
|
||||
with ReferrerAuthenticator
|
||||
with ReadableUsersAuthenticator
|
||||
with WritableUsersAuthenticator =>
|
||||
self: RepositoryService & AccountService & OwnerAuthenticator & UsersAuthenticator & GroupManagerAuthenticator &
|
||||
ProtectedBranchService & ReferrerAuthenticator & ReadableUsersAuthenticator & WritableUsersAuthenticator =>
|
||||
|
||||
/**
|
||||
* i. List branches
|
||||
@@ -30,10 +23,7 @@ trait ApiRepositoryBranchControllerBase extends ControllerBase {
|
||||
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
|
||||
JsonFormat(
|
||||
JGitUtil
|
||||
.getBranchesNoMergeInfo(
|
||||
git = git,
|
||||
defaultBranch = repository.repository.defaultBranch
|
||||
)
|
||||
.getBranchesNoMergeInfo(git)
|
||||
.map { br =>
|
||||
ApiBranchForList(br.name, ApiBranchCommit(br.commitId))
|
||||
}
|
||||
@@ -49,14 +39,13 @@ trait ApiRepositoryBranchControllerBase extends ControllerBase {
|
||||
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
|
||||
(for {
|
||||
branch <- params.get("splat") if repository.branchList.contains(branch)
|
||||
br <- getBranchesNoMergeInfo(
|
||||
git,
|
||||
repository.repository.defaultBranch
|
||||
).find(_.name == branch)
|
||||
br <- getBranchesNoMergeInfo(git).find(_.name == branch)
|
||||
} yield {
|
||||
val protection = getProtectedBranchInfo(repository.owner, repository.name, branch)
|
||||
JsonFormat(
|
||||
ApiBranch(branch, ApiBranchCommit(br.commitId), ApiBranchProtection(protection))(RepositoryName(repository))
|
||||
ApiBranch(branch, ApiBranchCommit(br.commitId), ApiBranchProtectionResponse(protection))(
|
||||
RepositoryName(repository)
|
||||
)
|
||||
)
|
||||
}) getOrElse NotFound()
|
||||
}
|
||||
@@ -71,7 +60,7 @@ trait ApiRepositoryBranchControllerBase extends ControllerBase {
|
||||
if (repository.branchList.contains(branch)) {
|
||||
val protection = getProtectedBranchInfo(repository.owner, repository.name, branch)
|
||||
JsonFormat(
|
||||
ApiBranchProtection(protection)
|
||||
ApiBranchProtectionResponse(protection)
|
||||
)
|
||||
} else { NotFound() }
|
||||
})
|
||||
@@ -151,7 +140,7 @@ trait ApiRepositoryBranchControllerBase extends ControllerBase {
|
||||
if (repository.branchList.contains(branch)) {
|
||||
val protection = getProtectedBranchInfo(repository.owner, repository.name, branch)
|
||||
JsonFormat(
|
||||
ApiBranchProtection(protection).required_status_checks
|
||||
ApiBranchProtectionResponse(protection).required_status_checks
|
||||
)
|
||||
} else { NotFound() }
|
||||
})
|
||||
@@ -275,24 +264,25 @@ trait ApiRepositoryBranchControllerBase extends ControllerBase {
|
||||
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
|
||||
(for {
|
||||
branch <- params.get("splat") if repository.branchList.contains(branch)
|
||||
protection <- extractFromJsonBody[ApiBranchProtection.EnablingAndDisabling].map(_.protection)
|
||||
br <- getBranchesNoMergeInfo(
|
||||
git,
|
||||
repository.repository.defaultBranch
|
||||
).find(_.name == branch)
|
||||
protection <- extractFromJsonBody[ApiBranchProtectionRequest.EnablingAndDisabling].map(_.protection)
|
||||
br <- getBranchesNoMergeInfo(git).find(_.name == branch)
|
||||
} yield {
|
||||
if (protection.enabled) {
|
||||
enableBranchProtection(
|
||||
repository.owner,
|
||||
repository.name,
|
||||
branch,
|
||||
protection.status.enforcement_level == ApiBranchProtection.Everyone,
|
||||
protection.status.contexts
|
||||
protection.enforce_admins.getOrElse(false),
|
||||
protection.required_status_checks.isDefined,
|
||||
protection.required_status_checks.map(_.contexts).getOrElse(Nil),
|
||||
protection.restrictions.isDefined,
|
||||
protection.restrictions.map(_.users).getOrElse(Nil)
|
||||
)
|
||||
} else {
|
||||
disableBranchProtection(repository.owner, repository.name, branch)
|
||||
}
|
||||
JsonFormat(ApiBranch(branch, ApiBranchCommit(br.commitId), protection)(RepositoryName(repository)))
|
||||
val response = ApiBranchProtectionResponse(getProtectedBranchInfo(repository.owner, repository.name, branch))
|
||||
JsonFormat(ApiBranch(branch, ApiBranchCommit(br.commitId), response)(RepositoryName(repository)))
|
||||
}) getOrElse NotFound()
|
||||
}
|
||||
})
|
||||
|
||||
@@ -7,7 +7,7 @@ import gitbucket.core.util.{OwnerAuthenticator, ReferrerAuthenticator}
|
||||
import org.scalatra.NoContent
|
||||
|
||||
trait ApiRepositoryCollaboratorControllerBase extends ControllerBase {
|
||||
self: RepositoryService with AccountService with ReferrerAuthenticator with OwnerAuthenticator =>
|
||||
self: RepositoryService & AccountService & ReferrerAuthenticator & OwnerAuthenticator =>
|
||||
|
||||
/*
|
||||
* i. List repository collaborators
|
||||
|
||||
@@ -21,7 +21,7 @@ import scala.math.min
|
||||
import scala.util.Using
|
||||
|
||||
trait ApiRepositoryCommitControllerBase extends ControllerBase {
|
||||
self: AccountService with CommitsService with ProtectedBranchService with ReferrerAuthenticator =>
|
||||
self: AccountService & CommitsService & ProtectedBranchService & ReferrerAuthenticator =>
|
||||
/*
|
||||
* i. List commits on a repository
|
||||
* https://developer.github.com/v3/repos/commits/#list-commits-on-a-repository
|
||||
@@ -159,7 +159,7 @@ trait ApiRepositoryCommitControllerBase extends ControllerBase {
|
||||
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
|
||||
val apiBranchForCommits = for {
|
||||
branch <- getBranchesOfCommit(git, sha)
|
||||
br <- getBranchesNoMergeInfo(git, branch).find(_.name == branch)
|
||||
br <- getBranchesNoMergeInfo(git).find(_.name == branch)
|
||||
} yield {
|
||||
val protection = getProtectedBranchInfo(repository.owner, repository.name, branch)
|
||||
ApiBranchForHeadCommit(branch, ApiBranchCommit(br.commitId), protection.enabled)
|
||||
|
||||
@@ -12,7 +12,7 @@ import org.eclipse.jgit.api.Git
|
||||
import scala.util.Using
|
||||
|
||||
trait ApiRepositoryContentsControllerBase extends ControllerBase {
|
||||
self: ReferrerAuthenticator with WritableUsersAuthenticator with RepositoryCommitFileService =>
|
||||
self: ReferrerAuthenticator & WritableUsersAuthenticator & RepositoryCommitFileService =>
|
||||
|
||||
/**
|
||||
* i. Get a repository README
|
||||
@@ -90,7 +90,17 @@ trait ApiRepositoryContentsControllerBase extends ControllerBase {
|
||||
path,
|
||||
"\" id=\"file\">",
|
||||
"<article>",
|
||||
renderMarkup(path.split("/").toList, new String(c), refStr, repository, false, false, true).body,
|
||||
renderMarkup(
|
||||
filePath = path.split("/").toList,
|
||||
fileContent = new String(c),
|
||||
branch = refStr,
|
||||
repository = repository,
|
||||
enableWikiLink = false,
|
||||
enableRefsLink = false,
|
||||
enableAnchor = false,
|
||||
enableLineBreaks = true,
|
||||
enableTaskList = true
|
||||
).body,
|
||||
"</article>",
|
||||
"</div>"
|
||||
).mkString
|
||||
@@ -142,10 +152,11 @@ trait ApiRepositoryContentsControllerBase extends ControllerBase {
|
||||
val revCommit = JGitUtil.getRevCommitFromId(git, git.getRepository.resolve(branch))
|
||||
revCommit.name
|
||||
}
|
||||
val paths = multiParams("splat").head.split("/")
|
||||
val fullPath = multiParams("splat").head
|
||||
val paths = fullPath.split("/")
|
||||
val path = paths.take(paths.size - 1).toList.mkString("/")
|
||||
Using.resource(Git.open(getRepositoryDir(params("owner"), params("repository")))) { git =>
|
||||
val fileInfo = getFileInfo(git, commit, path, false)
|
||||
val fileInfo = getFileInfo(git, commit, fullPath, ignoreCase = false)
|
||||
|
||||
fileInfo match {
|
||||
case Some(f) if !data.sha.contains(f.id.getName) =>
|
||||
|
||||
@@ -15,16 +15,9 @@ import scala.concurrent.duration.Duration
|
||||
import scala.util.Using
|
||||
|
||||
trait ApiRepositoryControllerBase extends ControllerBase {
|
||||
self: RepositoryService
|
||||
with ApiGitReferenceControllerBase
|
||||
with RepositoryCreationService
|
||||
with AccountService
|
||||
with OwnerAuthenticator
|
||||
with UsersAuthenticator
|
||||
with GroupManagerAuthenticator
|
||||
with ReferrerAuthenticator
|
||||
with ReadableUsersAuthenticator
|
||||
with WritableUsersAuthenticator =>
|
||||
self: RepositoryService & ApiGitReferenceControllerBase & RepositoryCreationService & AccountService &
|
||||
OwnerAuthenticator & UsersAuthenticator & GroupManagerAuthenticator & ReferrerAuthenticator &
|
||||
ReadableUsersAuthenticator & WritableUsersAuthenticator =>
|
||||
|
||||
/**
|
||||
* i. List your repositories
|
||||
@@ -189,7 +182,7 @@ trait ApiRepositoryControllerBase extends ControllerBase {
|
||||
get("/api/v3/repos/:owner/:repository/tags")(referrersOnly { repository =>
|
||||
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
|
||||
JsonFormat(
|
||||
self.getRef("tags", repository)
|
||||
repository.tags.map(tagInfo => ApiTag(tagInfo.name, RepositoryName(repository), tagInfo.commitId))
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -7,7 +7,7 @@ import gitbucket.core.util.Implicits._
|
||||
import gitbucket.core.util.{JGitUtil, ReferrerAuthenticator, WritableUsersAuthenticator}
|
||||
|
||||
trait ApiRepositoryStatusControllerBase extends ControllerBase {
|
||||
self: AccountService with CommitStatusService with ReferrerAuthenticator with WritableUsersAuthenticator =>
|
||||
self: AccountService & CommitStatusService & ReferrerAuthenticator & WritableUsersAuthenticator =>
|
||||
|
||||
/*
|
||||
* i. Create a status
|
||||
|
||||
@@ -8,7 +8,7 @@ import gitbucket.core.util.Implicits._
|
||||
import org.scalatra.NoContent
|
||||
|
||||
trait ApiRepositoryWebhookControllerBase extends ControllerBase {
|
||||
self: RepositoryService with WebHookService with ReferrerAuthenticator with WritableUsersAuthenticator =>
|
||||
self: RepositoryService & WebHookService & ReferrerAuthenticator & WritableUsersAuthenticator =>
|
||||
|
||||
/*
|
||||
* i. List repository webhooks
|
||||
|
||||
@@ -8,7 +8,7 @@ import gitbucket.core.util.StringUtil._
|
||||
import org.scalatra.NoContent
|
||||
|
||||
trait ApiUserControllerBase extends ControllerBase {
|
||||
self: RepositoryService with AccountService with AdminAuthenticator with UsersAuthenticator =>
|
||||
self: RepositoryService & AccountService & AdminAuthenticator & UsersAuthenticator =>
|
||||
|
||||
/**
|
||||
* i. Get a single user
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
package gitbucket.core.model
|
||||
|
||||
trait ProtectedBranchComponent extends TemplateComponent { self: Profile =>
|
||||
import profile.api._
|
||||
import self._
|
||||
import profile.api.*
|
||||
|
||||
lazy val ProtectedBranches = TableQuery[ProtectedBranches]
|
||||
class ProtectedBranches(tag: Tag) extends Table[ProtectedBranch](tag, "PROTECTED_BRANCH") with BranchTemplate {
|
||||
val statusCheckAdmin = column[Boolean]("STATUS_CHECK_ADMIN")
|
||||
def * = (userName, repositoryName, branch, statusCheckAdmin).mapTo[ProtectedBranch]
|
||||
def byPrimaryKey(userName: String, repositoryName: String, branch: String) =
|
||||
val statusCheckAdmin = column[Boolean]("STATUS_CHECK_ADMIN") // enforceAdmins
|
||||
val requiredStatusCheck = column[Boolean]("REQUIRED_STATUS_CHECK")
|
||||
val restrictions = column[Boolean]("RESTRICTIONS")
|
||||
def * =
|
||||
(userName, repositoryName, branch, statusCheckAdmin, requiredStatusCheck, restrictions).mapTo[ProtectedBranch]
|
||||
def byPrimaryKey(userName: String, repositoryName: String, branch: String): Rep[Boolean] =
|
||||
byBranch(userName, repositoryName, branch)
|
||||
def byPrimaryKey(userName: Rep[String], repositoryName: Rep[String], branch: Rep[String]) =
|
||||
def byPrimaryKey(userName: Rep[String], repositoryName: Rep[String], branch: Rep[String]): Rep[Boolean] =
|
||||
byBranch(userName, repositoryName, branch)
|
||||
}
|
||||
|
||||
@@ -22,8 +24,27 @@ trait ProtectedBranchComponent extends TemplateComponent { self: Profile =>
|
||||
def * =
|
||||
(userName, repositoryName, branch, context).mapTo[ProtectedBranchContext]
|
||||
}
|
||||
|
||||
lazy val ProtectedBranchRestrictions = TableQuery[ProtectedBranchRestrictions]
|
||||
class ProtectedBranchRestrictions(tag: Tag)
|
||||
extends Table[ProtectedBranchRestriction](tag, "PROTECTED_BRANCH_RESTRICTION")
|
||||
with BranchTemplate {
|
||||
val allowedUser = column[String]("ALLOWED_USER")
|
||||
def * = (userName, repositoryName, branch, allowedUser).mapTo[ProtectedBranchRestriction]
|
||||
def byPrimaryKey(userName: String, repositoryName: String, branch: String, allowedUser: String): Rep[Boolean] =
|
||||
this.userName === userName.bind && this.repositoryName === repositoryName.bind && this.branch === branch.bind && this.allowedUser === allowedUser.bind
|
||||
}
|
||||
}
|
||||
|
||||
case class ProtectedBranch(userName: String, repositoryName: String, branch: String, statusCheckAdmin: Boolean)
|
||||
case class ProtectedBranch(
|
||||
userName: String,
|
||||
repositoryName: String,
|
||||
branch: String,
|
||||
enforceAdmins: Boolean,
|
||||
requiredStatusCheck: Boolean,
|
||||
restrictions: Boolean
|
||||
)
|
||||
|
||||
case class ProtectedBranchContext(userName: String, repositoryName: String, branch: String, context: String)
|
||||
|
||||
case class ProtectedBranchRestriction(userName: String, repositoryName: String, branch: String, allowedUser: String)
|
||||
|
||||
@@ -13,6 +13,7 @@ trait PullRequestComponent extends TemplateComponent { self: Profile =>
|
||||
val commitIdFrom = column[String]("COMMIT_ID_FROM")
|
||||
val commitIdTo = column[String]("COMMIT_ID_TO")
|
||||
val isDraft = column[Boolean]("IS_DRAFT")
|
||||
val mergedCommitIds = column[String]("MERGED_COMMIT_IDS")
|
||||
def * =
|
||||
(
|
||||
userName,
|
||||
@@ -24,12 +25,13 @@ trait PullRequestComponent extends TemplateComponent { self: Profile =>
|
||||
requestBranch,
|
||||
commitIdFrom,
|
||||
commitIdTo,
|
||||
isDraft
|
||||
isDraft,
|
||||
mergedCommitIds.?
|
||||
).mapTo[PullRequest]
|
||||
|
||||
def byPrimaryKey(userName: String, repositoryName: String, issueId: Int) =
|
||||
def byPrimaryKey(userName: String, repositoryName: String, issueId: Int): Rep[Boolean] =
|
||||
byIssue(userName, repositoryName, issueId)
|
||||
def byPrimaryKey(userName: Rep[String], repositoryName: Rep[String], issueId: Rep[Int]) =
|
||||
def byPrimaryKey(userName: Rep[String], repositoryName: Rep[String], issueId: Rep[Int]): Rep[Boolean] =
|
||||
byIssue(userName, repositoryName, issueId)
|
||||
}
|
||||
}
|
||||
@@ -44,5 +46,6 @@ case class PullRequest(
|
||||
requestBranch: String,
|
||||
commitIdFrom: String,
|
||||
commitIdTo: String,
|
||||
isDraft: Boolean
|
||||
isDraft: Boolean,
|
||||
mergedCommitIds: Option[String]
|
||||
)
|
||||
|
||||
@@ -67,7 +67,7 @@ trait RepositoryComponent extends TemplateComponent { self: Profile =>
|
||||
repository._10,
|
||||
repository._11,
|
||||
repository._12,
|
||||
RepositoryOptions.tupled.apply(options)
|
||||
RepositoryOptions.apply.tupled.apply(options)
|
||||
)
|
||||
},
|
||||
{ (r: Repository) =>
|
||||
|
||||
@@ -15,7 +15,7 @@ import gitbucket.core.service.RepositoryService.RepositoryInfo
|
||||
import gitbucket.core.service.SystemSettingsService
|
||||
import gitbucket.core.service.SystemSettingsService.SystemSettings
|
||||
import gitbucket.core.util.{ConfigUtil, DatabaseConfig}
|
||||
import gitbucket.core.util.Directory._
|
||||
import gitbucket.core.util.Directory.*
|
||||
import io.github.gitbucket.solidbase.Solidbase
|
||||
import io.github.gitbucket.solidbase.manager.JDBCVersionManager
|
||||
import io.github.gitbucket.solidbase.model.Module
|
||||
@@ -25,7 +25,7 @@ import org.apache.sshd.server.command.Command
|
||||
import org.slf4j.LoggerFactory
|
||||
import play.twirl.api.Html
|
||||
|
||||
import scala.jdk.CollectionConverters._
|
||||
import scala.jdk.CollectionConverters.*
|
||||
|
||||
class PluginRegistry {
|
||||
|
||||
@@ -233,29 +233,29 @@ object PluginRegistry {
|
||||
override def accept(dir: File, name: String): Boolean = name.endsWith(".jar")
|
||||
})
|
||||
.toSeq
|
||||
.sortBy(x => Version.valueOf(getPluginVersion(x.getName)))
|
||||
.sortBy(x => Version.parse(getPluginVersion(x.getName)))
|
||||
.reverse
|
||||
}
|
||||
|
||||
lazy val extraPluginDir: Option[String] = ConfigUtil.getConfigValue[String]("gitbucket.pluginDir")
|
||||
private lazy val extraPluginDir: Option[String] = ConfigUtil.getConfigValue[String]("gitbucket.pluginDir")
|
||||
|
||||
def getGitBucketVersion(pluginJarFileName: String): Option[String] = {
|
||||
val regex = ".+-gitbucket\\_(\\d+\\.\\d+\\.\\d+(-SNAPSHOT)?)-.+".r
|
||||
private def getGitBucketVersion(pluginJarFileName: String): Option[String] = {
|
||||
val regex = ".+-gitbucket_(\\d+\\.\\d+\\.\\d+(-SNAPSHOT)?)-.+".r
|
||||
pluginJarFileName match {
|
||||
case regex(all, _) => Some(all)
|
||||
case _ => None
|
||||
}
|
||||
}
|
||||
|
||||
def getPluginVersion(pluginJarFileName: String): String = {
|
||||
private def getPluginVersion(pluginJarFileName: String): String = {
|
||||
val regex = ".+-((\\d+)\\.(\\d+)(\\.(\\d+))?(-SNAPSHOT)?)\\.jar$".r
|
||||
pluginJarFileName match {
|
||||
case regex(all, major, minor, _, patch, modifier) => {
|
||||
if (patch != null) all
|
||||
else {
|
||||
case regex(all, major, minor, _, patch, modifier) =>
|
||||
if (patch != null) {
|
||||
all
|
||||
} else {
|
||||
s"${major}.${minor}.0" + (if (modifier == null) "" else modifier)
|
||||
}
|
||||
}
|
||||
case _ => "0.0.0"
|
||||
}
|
||||
}
|
||||
@@ -295,11 +295,10 @@ object PluginRegistry {
|
||||
|
||||
// Check duplication
|
||||
instance.getPlugins().find(_.pluginId == pluginId) match {
|
||||
case Some(x) => {
|
||||
case Some(x) =>
|
||||
logger.warn(s"Plugin ${pluginId} is duplicated. ${x.pluginJar.getName} is available.")
|
||||
classLoader.close()
|
||||
}
|
||||
case None => {
|
||||
case None =>
|
||||
// Migration
|
||||
val solidbase = new Solidbase()
|
||||
solidbase
|
||||
@@ -334,7 +333,6 @@ object PluginRegistry {
|
||||
classLoader = classLoader
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
case e: Throwable =>
|
||||
@@ -369,9 +367,8 @@ object PluginRegistry {
|
||||
extraWatcher = null
|
||||
}
|
||||
} catch {
|
||||
case e: Exception => {
|
||||
case e: Exception =>
|
||||
logger.error(s"Error during plugin shutdown: ${plugin.pluginJar.getName}", e)
|
||||
}
|
||||
} finally {
|
||||
plugin.classLoader.close()
|
||||
}
|
||||
@@ -437,7 +434,7 @@ class PluginWatchThread(context: ServletContext, dir: String) extends Thread wit
|
||||
logger.info("Start PluginWatchThread: " + path)
|
||||
|
||||
try {
|
||||
while (watchKey.isValid()) {
|
||||
while (watchKey.isValid) {
|
||||
val detectedWatchKey = watcher.take()
|
||||
val events = detectedWatchKey.pollEvents.asScala.filter { e =>
|
||||
e.context.toString != ".installed" && !e.context.toString.endsWith(".bak")
|
||||
|
||||
@@ -20,7 +20,7 @@ trait Renderer {
|
||||
|
||||
object MarkdownRenderer extends Renderer {
|
||||
override def render(request: RenderRequest): Html = {
|
||||
import request._
|
||||
import request.*
|
||||
Html(
|
||||
Markdown.toHtml(
|
||||
markdown = fileContent,
|
||||
@@ -29,9 +29,9 @@ object MarkdownRenderer extends Renderer {
|
||||
enableWikiLink = enableWikiLink,
|
||||
enableRefsLink = enableRefsLink,
|
||||
enableAnchor = enableAnchor,
|
||||
enableLineBreaks = false,
|
||||
enableTaskList = true,
|
||||
hasWritePermission = false
|
||||
enableLineBreaks = enableLineBreaks,
|
||||
enableTaskList = enableTaskList,
|
||||
hasWritePermission = hasWritePermission
|
||||
)(context)
|
||||
)
|
||||
}
|
||||
@@ -51,5 +51,8 @@ case class RenderRequest(
|
||||
enableWikiLink: Boolean,
|
||||
enableRefsLink: Boolean,
|
||||
enableAnchor: Boolean,
|
||||
enableLineBreaks: Boolean,
|
||||
enableTaskList: Boolean,
|
||||
hasWritePermission: Boolean,
|
||||
context: Context
|
||||
)
|
||||
|
||||
@@ -59,7 +59,7 @@ trait AccountFederationService {
|
||||
.orElse(extractSafeStringForUserName(mailAddress)) match {
|
||||
case Some(safeUserName) =>
|
||||
getAccountByUserName(safeUserName, includeRemoved = true) match {
|
||||
case None => Some(safeUserName)
|
||||
case None => Some(safeUserName)
|
||||
case Some(_) =>
|
||||
logger.info(
|
||||
s"User ($safeUserName) already exists. preferredUserName=$preferredUserName, mailAddress=$mailAddress"
|
||||
|
||||
@@ -46,7 +46,7 @@ trait AccountService {
|
||||
case account if !account.isGroupAccount =>
|
||||
account.password match {
|
||||
case pbkdf2re(iter, salt, hash) if (pbkdf2_sha256(iter.toInt, salt, password) == hash) => Some(account)
|
||||
case p if p == sha1(password) =>
|
||||
case p if p == sha1(password) =>
|
||||
updateAccount(account.copy(password = pbkdf2_sha256(password)))
|
||||
Some(account)
|
||||
case _ => None
|
||||
|
||||
@@ -16,7 +16,7 @@ import gitbucket.core.util.{FileUtil, StringUtil}
|
||||
import org.apache.commons.io.FileUtils
|
||||
|
||||
trait CommitsService {
|
||||
self: ActivityService with PullRequestService with WebHookPullRequestReviewCommentService =>
|
||||
self: ActivityService & PullRequestService & WebHookPullRequestReviewCommentService =>
|
||||
|
||||
def getCommitComments(owner: String, repository: String, commitId: String, includePullRequest: Boolean)(implicit
|
||||
s: Session
|
||||
|
||||
@@ -16,12 +16,8 @@ import gitbucket.core.service.RepositoryService.RepositoryInfo
|
||||
import gitbucket.core.util.Implicits._
|
||||
|
||||
trait HandleCommentService {
|
||||
self: RepositoryService
|
||||
with IssuesService
|
||||
with ActivityService
|
||||
with WebHookService
|
||||
with WebHookIssueCommentService
|
||||
with WebHookPullRequestService =>
|
||||
self: RepositoryService & IssuesService & ActivityService & WebHookService & WebHookIssueCommentService &
|
||||
WebHookPullRequestService =>
|
||||
|
||||
/**
|
||||
* @see [[https://github.com/gitbucket/gitbucket/wiki/CommentAction]]
|
||||
@@ -66,7 +62,7 @@ trait HandleCommentService {
|
||||
.getOrElse(None -> None)
|
||||
|
||||
val commentId = (content, action) match {
|
||||
case (None, None) => None
|
||||
case (None, None) => None
|
||||
case (None, Some(action)) =>
|
||||
Some(createComment(owner, name, userName, issue.issueId, action.capitalize, action))
|
||||
case (Some(content), _) =>
|
||||
|
||||
@@ -10,7 +10,7 @@ import gitbucket.core.util.Implicits._
|
||||
|
||||
trait IssueCreationService {
|
||||
|
||||
self: RepositoryService with WebHookIssueCommentService with LabelsService with IssuesService with ActivityService =>
|
||||
self: RepositoryService & WebHookIssueCommentService & LabelsService & IssuesService & ActivityService =>
|
||||
|
||||
def createIssue(
|
||||
repository: RepositoryInfo,
|
||||
|
||||
@@ -26,7 +26,7 @@ import gitbucket.core.plugin.PluginRegistry
|
||||
import scala.jdk.CollectionConverters._
|
||||
|
||||
trait IssuesService {
|
||||
self: AccountService with RepositoryService with LabelsService with PrioritiesService with MilestonesService =>
|
||||
self: AccountService & RepositoryService & LabelsService & PrioritiesService & MilestonesService =>
|
||||
import IssuesService._
|
||||
|
||||
def getIssue(owner: String, repository: String, issueId: String)(implicit s: Session) =
|
||||
|
||||
@@ -7,7 +7,7 @@ import gitbucket.core.plugin.{PluginRegistry, ReceiveHook}
|
||||
import gitbucket.core.service.RepositoryService.RepositoryInfo
|
||||
import gitbucket.core.util.Directory._
|
||||
import gitbucket.core.util.{JGitUtil, LockUtil}
|
||||
import gitbucket.core.model.Profile.profile.blockingApi._
|
||||
import gitbucket.core.model.Profile.profile.blockingApi.*
|
||||
import gitbucket.core.model.activity.{CloseIssueInfo, MergeInfo, PushInfo}
|
||||
import gitbucket.core.service.SystemSettingsService.SystemSettings
|
||||
import gitbucket.core.service.WebHookService.WebHookPushPayload
|
||||
@@ -19,19 +19,14 @@ import org.eclipse.jgit.errors.NoMergeBaseException
|
||||
import org.eclipse.jgit.lib.{CommitBuilder, ObjectId, PersonIdent, Repository}
|
||||
import org.eclipse.jgit.revwalk.{RevCommit, RevWalk}
|
||||
|
||||
import scala.jdk.CollectionConverters._
|
||||
import scala.jdk.CollectionConverters.*
|
||||
import scala.util.Using
|
||||
|
||||
trait MergeService {
|
||||
self: AccountService
|
||||
with ActivityService
|
||||
with IssuesService
|
||||
with RepositoryService
|
||||
with PullRequestService
|
||||
with WebHookPullRequestService
|
||||
with WebHookService =>
|
||||
self: AccountService & ActivityService & IssuesService & RepositoryService & PullRequestService &
|
||||
WebHookPullRequestService & WebHookService =>
|
||||
|
||||
import MergeService._
|
||||
import MergeService.*
|
||||
|
||||
/**
|
||||
* Checks whether conflict will be caused in merging within pull request.
|
||||
@@ -66,15 +61,16 @@ trait MergeService {
|
||||
repository: RepositoryInfo,
|
||||
branch: String,
|
||||
issueId: Int,
|
||||
commits: Seq[RevCommit],
|
||||
message: String,
|
||||
loginAccount: Account,
|
||||
settings: SystemSettings
|
||||
)(implicit s: Session, c: JsonFormat.Context): ObjectId = {
|
||||
)(implicit s: Session, c: JsonFormat.Context): MergeResult = {
|
||||
val beforeCommitId = git.getRepository.resolve(s"refs/heads/${branch}")
|
||||
val afterCommitId = new MergeCacheInfo(git, repository.owner, repository.name, branch, issueId, getReceiveHooks())
|
||||
.merge(message, new PersonIdent(loginAccount.fullName, loginAccount.mailAddress), loginAccount.userName)
|
||||
callWebHook(git, repository, branch, beforeCommitId, afterCommitId, loginAccount, settings)
|
||||
afterCommitId
|
||||
val mergeResult = new MergeCacheInfo(git, repository.owner, repository.name, branch, issueId, getReceiveHooks())
|
||||
.merge(message, new PersonIdent(loginAccount.fullName, loginAccount.mailAddress), loginAccount.userName, commits)
|
||||
callWebHook(git, repository, branch, beforeCommitId, mergeResult.newCommitId, loginAccount, settings)
|
||||
mergeResult
|
||||
}
|
||||
|
||||
/** rebase to the head of the pull request branch */
|
||||
@@ -86,13 +82,13 @@ trait MergeService {
|
||||
commits: Seq[RevCommit],
|
||||
loginAccount: Account,
|
||||
settings: SystemSettings
|
||||
)(implicit s: Session, c: JsonFormat.Context): ObjectId = {
|
||||
)(implicit s: Session, c: JsonFormat.Context): MergeResult = {
|
||||
val beforeCommitId = git.getRepository.resolve(s"refs/heads/${branch}")
|
||||
val afterCommitId =
|
||||
val mergeResult =
|
||||
new MergeCacheInfo(git, repository.owner, repository.name, branch, issueId, getReceiveHooks())
|
||||
.rebase(new PersonIdent(loginAccount.fullName, loginAccount.mailAddress), loginAccount.userName, commits)
|
||||
callWebHook(git, repository, branch, beforeCommitId, afterCommitId, loginAccount, settings)
|
||||
afterCommitId
|
||||
callWebHook(git, repository, branch, beforeCommitId, mergeResult.newCommitId, loginAccount, settings)
|
||||
mergeResult
|
||||
}
|
||||
|
||||
/** squash commits in the pull request and append it */
|
||||
@@ -104,13 +100,13 @@ trait MergeService {
|
||||
message: String,
|
||||
loginAccount: Account,
|
||||
settings: SystemSettings
|
||||
)(implicit s: Session, c: JsonFormat.Context): ObjectId = {
|
||||
)(implicit s: Session, c: JsonFormat.Context): MergeResult = {
|
||||
val beforeCommitId = git.getRepository.resolve(s"refs/heads/${branch}")
|
||||
val afterCommitId =
|
||||
val mergeResult =
|
||||
new MergeCacheInfo(git, repository.owner, repository.name, branch, issueId, getReceiveHooks())
|
||||
.squash(message, new PersonIdent(loginAccount.fullName, loginAccount.mailAddress), loginAccount.userName)
|
||||
callWebHook(git, repository, branch, beforeCommitId, afterCommitId, loginAccount, settings)
|
||||
afterCommitId
|
||||
callWebHook(git, repository, branch, beforeCommitId, mergeResult.newCommitId, loginAccount, settings)
|
||||
mergeResult
|
||||
}
|
||||
|
||||
private def callWebHook(
|
||||
@@ -342,7 +338,7 @@ trait MergeService {
|
||||
strategy: String,
|
||||
isDraft: Boolean,
|
||||
settings: SystemSettings
|
||||
)(implicit s: Session, c: JsonFormat.Context, context: Context): Either[String, ObjectId] = {
|
||||
)(implicit s: Session, c: JsonFormat.Context, context: Context): Either[String, MergeResult] = {
|
||||
if (!isDraft) {
|
||||
if (repository.repository.options.mergeOptions.split(",").contains(strategy)) {
|
||||
LockUtil.lock(s"${repository.owner}/${repository.name}") {
|
||||
@@ -498,7 +494,7 @@ trait MergeService {
|
||||
commits: Seq[Seq[CommitInfo]],
|
||||
receiveHooks: Seq[ReceiveHook],
|
||||
settings: SystemSettings
|
||||
)(implicit s: Session, c: JsonFormat.Context): Option[ObjectId] = {
|
||||
)(implicit s: Session, c: JsonFormat.Context): Option[MergeResult] = {
|
||||
val revCommits = Using
|
||||
.resource(new RevWalk(git.getRepository)) { revWalk =>
|
||||
commits.flatten.map { commit =>
|
||||
@@ -515,6 +511,7 @@ trait MergeService {
|
||||
repository,
|
||||
pullRequest.branch,
|
||||
issue.issueId,
|
||||
revCommits,
|
||||
s"Merge pull request #${issue.issueId} from ${pullRequest.requestUserName}/${pullRequest.requestBranch}\n\n" + message,
|
||||
loginAccount,
|
||||
settings
|
||||
@@ -605,13 +602,13 @@ object MergeService {
|
||||
private val mergedBranchName = s"refs/pull/${issueId}/merge"
|
||||
private val conflictedBranchName = s"refs/pull/${issueId}/conflict"
|
||||
|
||||
lazy val mergeBaseTip = git.getRepository.resolve(s"refs/heads/${branch}")
|
||||
lazy val mergeTip = git.getRepository.resolve(s"refs/pull/${issueId}/head")
|
||||
lazy val mergeBaseTip: ObjectId = git.getRepository.resolve(s"refs/heads/${branch}")
|
||||
lazy val mergeTip: ObjectId = git.getRepository.resolve(s"refs/pull/${issueId}/head")
|
||||
|
||||
def checkConflictCache(): Option[Option[String]] = {
|
||||
Option(git.getRepository.resolve(mergedBranchName))
|
||||
.flatMap { merged =>
|
||||
if (parseCommit(merged).getParents().toSet == Set(mergeBaseTip, mergeTip)) {
|
||||
if (parseCommit(merged).getParents.toSet == Set(mergeBaseTip, mergeTip)) {
|
||||
// merged branch exists
|
||||
Some(None)
|
||||
} else {
|
||||
@@ -620,7 +617,7 @@ object MergeService {
|
||||
}
|
||||
.orElse(Option(git.getRepository.resolve(conflictedBranchName)).flatMap { conflicted =>
|
||||
val commit = parseCommit(conflicted)
|
||||
if (commit.getParents().toSet == Set(mergeBaseTip, mergeTip)) {
|
||||
if (commit.getParents.toSet == Set(mergeBaseTip, mergeTip)) {
|
||||
// conflict branch exists
|
||||
Some(Some(commit.getFullMessage))
|
||||
} else {
|
||||
@@ -656,14 +653,16 @@ object MergeService {
|
||||
None
|
||||
} else {
|
||||
val message = createConflictMessage(mergeTip, mergeBaseTip, merger)
|
||||
_updateBranch(mergeTipCommit.getTree().getId(), message, conflictedBranchName)
|
||||
_updateBranch(mergeTipCommit.getTree.getId, message, conflictedBranchName)
|
||||
git.branchDelete().setForce(true).setBranchNames(mergedBranchName).call()
|
||||
Some(message)
|
||||
}
|
||||
}
|
||||
|
||||
// update branch from cache
|
||||
def merge(message: String, committer: PersonIdent, pusher: String)(implicit s: Session): ObjectId = {
|
||||
def merge(message: String, committer: PersonIdent, pusher: String, commits: Seq[RevCommit])(implicit
|
||||
s: Session
|
||||
): MergeResult = {
|
||||
if (checkConflict().isDefined) {
|
||||
throw new RuntimeException("This pull request can't merge automatically.")
|
||||
}
|
||||
@@ -671,7 +670,7 @@ object MergeService {
|
||||
throw new RuntimeException(s"Not found branch ${mergedBranchName}")
|
||||
})
|
||||
// creates merge commit
|
||||
val mergeCommitId = createMergeCommit(mergeResultCommit.getTree().getId(), committer, message)
|
||||
val mergeCommitId = createMergeCommit(mergeResultCommit.getTree.getId, committer, message)
|
||||
|
||||
val refName = s"refs/heads/${branch}"
|
||||
val currentObjectId = git.getRepository.resolve(refName)
|
||||
@@ -695,10 +694,10 @@ object MergeService {
|
||||
hook.postReceive(userName, repositoryName, receivePack, receiveCommand, committer.getName, true)
|
||||
}
|
||||
|
||||
objectId
|
||||
MergeResult(objectId, commits.map(_.name()))
|
||||
}
|
||||
|
||||
def rebase(committer: PersonIdent, pusher: String, commits: Seq[RevCommit])(implicit s: Session): ObjectId = {
|
||||
def rebase(committer: PersonIdent, pusher: String, commits: Seq[RevCommit])(implicit s: Session): MergeResult = {
|
||||
if (checkConflict().isDefined) {
|
||||
throw new RuntimeException("This pull request can't merge automatically.")
|
||||
}
|
||||
@@ -718,11 +717,13 @@ object MergeService {
|
||||
|
||||
val mergeBaseTipCommit = Using.resource(new RevWalk(git.getRepository))(_.parseCommit(mergeBaseTip))
|
||||
var previousId = mergeBaseTipCommit.getId
|
||||
val mergedCommitIds = Seq.newBuilder[String]
|
||||
|
||||
Using.resource(git.getRepository.newObjectInserter) { inserter =>
|
||||
commits.foreach { commit =>
|
||||
val nextCommit = _cloneCommit(commit, previousId, mergeBaseTipCommit.getId)
|
||||
previousId = inserter.insert(nextCommit)
|
||||
mergedCommitIds += previousId.name()
|
||||
}
|
||||
inserter.flush()
|
||||
}
|
||||
@@ -750,10 +751,10 @@ object MergeService {
|
||||
hook.postReceive(userName, repositoryName, receivePack, receiveCommand, committer.getName, true)
|
||||
}
|
||||
|
||||
objectId
|
||||
MergeResult(objectId, mergedCommitIds.result())
|
||||
}
|
||||
|
||||
def squash(message: String, committer: PersonIdent, pusher: String)(implicit s: Session): ObjectId = {
|
||||
def squash(message: String, committer: PersonIdent, pusher: String)(implicit s: Session): MergeResult = {
|
||||
if (checkConflict().isDefined) {
|
||||
throw new RuntimeException("This pull request can't merge automatically.")
|
||||
}
|
||||
@@ -809,7 +810,7 @@ object MergeService {
|
||||
hook.postReceive(userName, repositoryName, receivePack, receiveCommand, committer.getName, true)
|
||||
}
|
||||
|
||||
objectId
|
||||
MergeResult(objectId, Seq(newCommitId.name()))
|
||||
}
|
||||
|
||||
// return treeId
|
||||
@@ -828,4 +829,5 @@ object MergeService {
|
||||
mergeResults.asScala.map { case (key, _) => "- `" + key + "`\n" }.mkString
|
||||
}
|
||||
|
||||
case class MergeResult(newCommitId: ObjectId, mergedCommitId: Seq[String])
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
package gitbucket.core.service
|
||||
|
||||
import gitbucket.core.model.{Session => _, _}
|
||||
import gitbucket.core.plugin.ReceiveHook
|
||||
import gitbucket.core.model.Profile._
|
||||
import gitbucket.core.model.Profile.profile.blockingApi._
|
||||
import gitbucket.core.model.Profile.*
|
||||
import gitbucket.core.model.Profile.profile.blockingApi.*
|
||||
import gitbucket.core.model.{CommitState, ProtectedBranch, ProtectedBranchContext, ProtectedBranchRestriction, Role}
|
||||
import gitbucket.core.util.SyntaxSugars.*
|
||||
import org.eclipse.jgit.transport.{ReceiveCommand, ReceivePack}
|
||||
|
||||
trait ProtectedBranchService {
|
||||
@@ -13,17 +14,27 @@ trait ProtectedBranchService {
|
||||
): Option[ProtectedBranchInfo] =
|
||||
ProtectedBranches
|
||||
.joinLeft(ProtectedBranchContexts)
|
||||
.on { case (pb, c) => pb.byBranch(c.userName, c.repositoryName, c.branch) }
|
||||
.map { case (pb, c) => pb -> c.map(_.context) }
|
||||
.on { case pb ~ c => pb.byBranch(c.userName, c.repositoryName, c.branch) }
|
||||
.joinLeft(ProtectedBranchRestrictions)
|
||||
.on { case pb ~ c ~ r => pb.byBranch(r.userName, r.repositoryName, r.branch) }
|
||||
.map { case pb ~ c ~ r => pb -> (c.map(_.context), r.map(_.allowedUser)) }
|
||||
.filter(_._1.byPrimaryKey(owner, repository, branch))
|
||||
.list
|
||||
.groupBy(_._1)
|
||||
.headOption
|
||||
.map { p =>
|
||||
p._1 -> p._2.flatMap(_._2)
|
||||
.map { (p: (ProtectedBranch, List[(ProtectedBranch, (Option[String], Option[String]))])) =>
|
||||
p._1 -> (p._2.flatMap(_._2._1), p._2.flatMap(_._2._2))
|
||||
}
|
||||
.map { case (t1, contexts) =>
|
||||
new ProtectedBranchInfo(t1.userName, t1.repositoryName, t1.branch, true, contexts, t1.statusCheckAdmin)
|
||||
.map { case (t1, (contexts, users)) =>
|
||||
new ProtectedBranchInfo(
|
||||
t1.userName,
|
||||
t1.repositoryName,
|
||||
t1.branch,
|
||||
true,
|
||||
if (t1.requiredStatusCheck) Some(contexts) else None,
|
||||
t1.enforceAdmins,
|
||||
if (t1.restrictions) Some(users) else None
|
||||
)
|
||||
}
|
||||
|
||||
def getProtectedBranchInfo(owner: String, repository: String, branch: String)(implicit
|
||||
@@ -40,19 +51,32 @@ trait ProtectedBranchService {
|
||||
owner: String,
|
||||
repository: String,
|
||||
branch: String,
|
||||
includeAdministrators: Boolean,
|
||||
contexts: Seq[String]
|
||||
enforceAdmins: Boolean,
|
||||
requiredStatusCheck: Boolean,
|
||||
contexts: Seq[String],
|
||||
restrictions: Boolean,
|
||||
restrictionsUsers: Seq[String]
|
||||
)(implicit session: Session): Unit = {
|
||||
disableBranchProtection(owner, repository, branch)
|
||||
ProtectedBranches.insert(new ProtectedBranch(owner, repository, branch, includeAdministrators && contexts.nonEmpty))
|
||||
contexts.map { context =>
|
||||
ProtectedBranchContexts.insert(new ProtectedBranchContext(owner, repository, branch, context))
|
||||
ProtectedBranches.insert(
|
||||
ProtectedBranch(owner, repository, branch, enforceAdmins, requiredStatusCheck, restrictions)
|
||||
)
|
||||
|
||||
if (restrictions) {
|
||||
restrictionsUsers.foreach { user =>
|
||||
ProtectedBranchRestrictions.insert(ProtectedBranchRestriction(owner, repository, branch, user))
|
||||
}
|
||||
}
|
||||
|
||||
if (requiredStatusCheck) {
|
||||
contexts.foreach { context =>
|
||||
ProtectedBranchContexts.insert(ProtectedBranchContext(owner, repository, branch, context))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def disableBranchProtection(owner: String, repository: String, branch: String)(implicit session: Session): Unit =
|
||||
ProtectedBranches.filter(_.byPrimaryKey(owner, repository, branch)).delete
|
||||
|
||||
}
|
||||
|
||||
object ProtectedBranchService {
|
||||
@@ -101,6 +125,7 @@ object ProtectedBranchService {
|
||||
)
|
||||
}
|
||||
} else {
|
||||
println("-> else")
|
||||
None
|
||||
}
|
||||
}
|
||||
@@ -117,12 +142,16 @@ object ProtectedBranchService {
|
||||
* When enabled, commits must first be pushed to another branch,
|
||||
* then merged or pushed directly to test after status checks have passed.
|
||||
*/
|
||||
contexts: Seq[String],
|
||||
contexts: Option[Seq[String]],
|
||||
/**
|
||||
* Include administrators
|
||||
* Enforce required status checks for repository administrators.
|
||||
*/
|
||||
includeAdministrators: Boolean
|
||||
enforceAdmins: Boolean,
|
||||
/**
|
||||
* Users who can push to the branch.
|
||||
*/
|
||||
restrictionsUsers: Option[Seq[String]]
|
||||
) extends AccountService
|
||||
with RepositoryService
|
||||
with CommitStatusService {
|
||||
@@ -148,42 +177,66 @@ object ProtectedBranchService {
|
||||
session: Session
|
||||
): Option[String] = {
|
||||
if (enabled) {
|
||||
command.getType() match {
|
||||
command.getType match {
|
||||
case ReceiveCommand.Type.UPDATE_NONFASTFORWARD if isAllowNonFastForwards =>
|
||||
Some("Cannot force-push to a protected branch")
|
||||
case ReceiveCommand.Type.UPDATE | ReceiveCommand.Type.UPDATE_NONFASTFORWARD if !isPushAllowed(pusher) =>
|
||||
Some("You do not have permission to push to this branch")
|
||||
case ReceiveCommand.Type.UPDATE | ReceiveCommand.Type.UPDATE_NONFASTFORWARD if needStatusCheck(pusher) =>
|
||||
unSuccessedContexts(command.getNewId.name) match {
|
||||
case s if s.sizeIs == 1 => Some(s"""Required status check "${s.toSeq(0)}" is expected""")
|
||||
case s if s.sizeIs >= 1 => Some(s"${s.size} of ${contexts.size} required status checks are expected")
|
||||
case _ => None
|
||||
case s if s.sizeIs == 1 => Some(s"""Required status check "${s.head}" is expected""")
|
||||
case s if s.sizeIs >= 1 =>
|
||||
Some(s"${s.size} of ${contexts.map(_.size).getOrElse(0)} required status checks are expected")
|
||||
case _ => None
|
||||
}
|
||||
case ReceiveCommand.Type.DELETE =>
|
||||
Some("Cannot delete a protected branch")
|
||||
Some("You do not have permission to push to this branch")
|
||||
case _ => None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
def unSuccessedContexts(sha1: String)(implicit session: Session): Set[String] =
|
||||
if (contexts.isEmpty) {
|
||||
Set.empty
|
||||
} else {
|
||||
contexts.toSet -- getCommitStatuses(owner, repository, sha1)
|
||||
.filter(_.state == CommitState.SUCCESS)
|
||||
.map(_.context)
|
||||
.toSet
|
||||
|
||||
def unSuccessedContexts(sha1: String)(implicit session: Session): Set[String] = {
|
||||
contexts match {
|
||||
case None => Set.empty
|
||||
case Some(x) if x.isEmpty => Set.empty
|
||||
case Some(x) =>
|
||||
x.toSet -- getCommitStatuses(owner, repository, sha1)
|
||||
.filter(_.state == CommitState.SUCCESS)
|
||||
.map(_.context)
|
||||
.toSet
|
||||
}
|
||||
}
|
||||
|
||||
def needStatusCheck(pusher: String)(implicit session: Session): Boolean = pusher match {
|
||||
case _ if !enabled => false
|
||||
case _ if contexts.isEmpty => false
|
||||
case _ if includeAdministrators => true
|
||||
case p if isAdministrator(p) => false
|
||||
case _ => true
|
||||
case _ if !enabled => false
|
||||
case _ if contexts.isEmpty => false
|
||||
case _ if enforceAdmins => true
|
||||
case p if isAdministrator(p) => false
|
||||
case _ => true
|
||||
}
|
||||
|
||||
def isPushAllowed(pusher: String)(implicit session: Session): Boolean = pusher match {
|
||||
case _ if !enabled || restrictionsUsers.isEmpty => true
|
||||
case _ if restrictionsUsers.get.contains(pusher) => true
|
||||
case p if isAdministrator(p) && enforceAdmins => false
|
||||
case _ => false
|
||||
}
|
||||
}
|
||||
|
||||
object ProtectedBranchInfo {
|
||||
def disabled(owner: String, repository: String, branch: String): ProtectedBranchInfo =
|
||||
ProtectedBranchInfo(owner, repository, branch, false, Nil, false)
|
||||
def disabled(owner: String, repository: String, branch: String): ProtectedBranchInfo = {
|
||||
ProtectedBranchInfo(
|
||||
owner,
|
||||
repository,
|
||||
branch,
|
||||
enabled = false,
|
||||
contexts = None,
|
||||
enforceAdmins = false,
|
||||
restrictionsUsers = None
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,19 +19,17 @@ import gitbucket.core.util.StringUtil.*
|
||||
import gitbucket.core.view
|
||||
import gitbucket.core.view.helpers
|
||||
import org.eclipse.jgit.api.Git
|
||||
import org.eclipse.jgit.lib.ObjectId
|
||||
import org.eclipse.jgit.dircache.{DirCache, DirCacheEntry}
|
||||
import org.eclipse.jgit.lib.{CommitBuilder, FileMode, ObjectId, ObjectInserter, PersonIdent, Repository}
|
||||
import org.eclipse.jgit.revwalk.{RevTree, RevWalk}
|
||||
import org.eclipse.jgit.treewalk.{EmptyTreeIterator, TreeWalk}
|
||||
|
||||
import scala.jdk.CollectionConverters.*
|
||||
import scala.util.Using
|
||||
|
||||
trait PullRequestService {
|
||||
self: IssuesService
|
||||
with CommitsService
|
||||
with WebHookService
|
||||
with WebHookPullRequestService
|
||||
with RepositoryService
|
||||
with MergeService
|
||||
with ActivityService =>
|
||||
self: IssuesService & CommitsService & WebHookService & WebHookPullRequestService & RepositoryService & MergeService &
|
||||
ActivityService =>
|
||||
import PullRequestService.*
|
||||
|
||||
def getPullRequest(owner: String, repository: String, issueId: Int)(implicit
|
||||
@@ -68,6 +66,15 @@ trait PullRequestService {
|
||||
.update((baseBranch, commitIdTo))
|
||||
}
|
||||
|
||||
def updateMergedCommitIds(owner: String, repository: String, issueId: Int, mergedCommitIds: Seq[String])(implicit
|
||||
s: Session
|
||||
): Unit = {
|
||||
PullRequests
|
||||
.filter(_.byPrimaryKey(owner, repository, issueId))
|
||||
.map(pr => pr.mergedCommitIds)
|
||||
.update(mergedCommitIds.mkString(","))
|
||||
}
|
||||
|
||||
def getPullRequestCountGroupByUser(closed: Boolean, owner: Option[String], repository: Option[String])(implicit
|
||||
s: Session
|
||||
): List[PullRequestCount] =
|
||||
@@ -131,7 +138,8 @@ trait PullRequestService {
|
||||
requestBranch,
|
||||
commitIdFrom,
|
||||
commitIdTo,
|
||||
isDraft
|
||||
isDraft,
|
||||
None
|
||||
)
|
||||
|
||||
// fetch requested branch
|
||||
@@ -318,7 +326,7 @@ trait PullRequestService {
|
||||
base.foreach { _base =>
|
||||
if (pr.branch != _base) {
|
||||
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
|
||||
getBranchesNoMergeInfo(git, repository.repository.defaultBranch)
|
||||
getBranchesNoMergeInfo(git)
|
||||
.find(_.name == _base)
|
||||
.foreach(br => updateBaseBranch(repository.owner, repository.name, issueId, br.name, br.commitId))
|
||||
}
|
||||
@@ -413,11 +421,10 @@ trait PullRequestService {
|
||||
.find(x => x.oldPath == file)
|
||||
.map { diff =>
|
||||
(diff.oldContent, diff.newContent) match {
|
||||
case (Some(oldContent), Some(newContent)) => {
|
||||
case (Some(oldContent), Some(newContent)) =>
|
||||
val oldLines = convertLineSeparator(oldContent, "LF").split("\n")
|
||||
val newLines = convertLineSeparator(newContent, "LF").split("\n")
|
||||
file -> Option(DiffUtils.diff(oldLines.toList.asJava, newLines.toList.asJava))
|
||||
}
|
||||
case _ =>
|
||||
file -> None
|
||||
}
|
||||
@@ -432,7 +439,7 @@ trait PullRequestService {
|
||||
case Some(patch) =>
|
||||
file -> comments.foreach { case (commentId, lineNumber) =>
|
||||
lineNumber match {
|
||||
case Left(oldLine) => updateCommitCommentPosition(commentId, newCommitId, Some(oldLine), None)
|
||||
case Left(oldLine) => updateCommitCommentPosition(commentId, newCommitId, Some(oldLine), None)
|
||||
case Right(newLine) =>
|
||||
var counter = newLine
|
||||
patch.getDeltas.asScala.filter(_.getSource.getPosition < newLine).foreach { delta =>
|
||||
@@ -529,7 +536,6 @@ trait PullRequestService {
|
||||
helpers.date(commit1.commitTime) == view.helpers.date(commit2.commitTime)
|
||||
}
|
||||
|
||||
// TODO Isolate to an another method?
|
||||
val diffs = JGitUtil.getDiffs(
|
||||
git = newGit,
|
||||
from = Some(oldId.getName),
|
||||
@@ -639,6 +645,157 @@ trait PullRequestService {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a revert commit directly on the bare repository without cloning.
|
||||
* This works by creating a reverse diff of the merged commits and applying it to the base branch.
|
||||
*/
|
||||
def createRevertCommit(
|
||||
git: Git,
|
||||
targetBranch: String,
|
||||
mergedCommitIds: Seq[String],
|
||||
committerName: String,
|
||||
committerEmail: String,
|
||||
commitMessage: String
|
||||
): Either[String, ObjectId] = {
|
||||
try {
|
||||
val repository = git.getRepository
|
||||
val inserter = repository.newObjectInserter()
|
||||
|
||||
Using.resource(new RevWalk(repository)) { revWalk =>
|
||||
// Get the target branch head
|
||||
val targetHeadId = repository.resolve(s"refs/heads/$targetBranch")
|
||||
if (targetHeadId == null) {
|
||||
return Left(s"Branch $targetBranch not found")
|
||||
}
|
||||
val targetHead = revWalk.parseCommit(targetHeadId)
|
||||
|
||||
// Parse the commits to revert (in reverse order for proper reverting)
|
||||
val commitsToRevert = mergedCommitIds.reverse.map { commitId =>
|
||||
val objectId = repository.resolve(commitId)
|
||||
if (objectId == null) {
|
||||
throw new IllegalArgumentException(s"Commit $commitId not found")
|
||||
}
|
||||
revWalk.parseCommit(objectId)
|
||||
}
|
||||
|
||||
// Start with the current tree of the target branch
|
||||
var currentTreeId = targetHead.getTree.getId
|
||||
|
||||
// Apply reverse changes for each commit
|
||||
for (commit <- commitsToRevert) {
|
||||
val parentCommit = if (commit.getParentCount > 0) {
|
||||
revWalk.parseCommit(commit.getParent(0))
|
||||
} else {
|
||||
// This is an initial commit, revert by creating empty tree
|
||||
null
|
||||
}
|
||||
|
||||
// Create new tree by applying reverse diff
|
||||
currentTreeId = createTreeWithReverseDiff(
|
||||
repository,
|
||||
inserter,
|
||||
currentTreeId,
|
||||
if (parentCommit != null) parentCommit.getTree else null,
|
||||
commit.getTree
|
||||
)
|
||||
}
|
||||
|
||||
// Create the revert commit
|
||||
val commitBuilder = new CommitBuilder()
|
||||
commitBuilder.setTreeId(currentTreeId)
|
||||
commitBuilder.setParentId(targetHeadId)
|
||||
commitBuilder.setAuthor(new PersonIdent(committerName, committerEmail))
|
||||
commitBuilder.setCommitter(new PersonIdent(committerName, committerEmail))
|
||||
commitBuilder.setMessage(commitMessage)
|
||||
|
||||
val revertCommitId = inserter.insert(commitBuilder)
|
||||
inserter.flush()
|
||||
|
||||
// Update the branch to point to the new commit
|
||||
val refUpdate = repository.updateRef(s"refs/heads/$targetBranch")
|
||||
refUpdate.setNewObjectId(revertCommitId)
|
||||
refUpdate.update()
|
||||
|
||||
Right(revertCommitId)
|
||||
}
|
||||
} catch {
|
||||
case ex: Exception =>
|
||||
Left(ex.getMessage)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new tree by applying the reverse of changes between fromTree and toTree to baseTree.
|
||||
*/
|
||||
private def createTreeWithReverseDiff(
|
||||
repository: Repository,
|
||||
inserter: ObjectInserter,
|
||||
baseTreeId: ObjectId,
|
||||
fromTree: RevTree,
|
||||
toTree: RevTree
|
||||
): ObjectId = {
|
||||
val dirCache = DirCache.newInCore()
|
||||
val builder = dirCache.builder()
|
||||
|
||||
val entries = scala.collection.mutable.Map[String, DirCacheEntry]()
|
||||
|
||||
// Start with all files from the base tree
|
||||
if (baseTreeId != null) {
|
||||
Using.resource(new TreeWalk(repository)) { walk =>
|
||||
walk.addTree(baseTreeId)
|
||||
walk.setRecursive(true)
|
||||
|
||||
while (walk.next()) {
|
||||
val entry = new DirCacheEntry(walk.getPathString)
|
||||
entry.setFileMode(walk.getFileMode(0))
|
||||
entry.setObjectId(walk.getObjectId(0))
|
||||
entries(walk.getPathString) = entry
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Apply reverse changes: if a file was added in the original change, remove it
|
||||
// if a file was deleted, restore it; if modified, restore original content
|
||||
Using.resource(new TreeWalk(repository)) { walk =>
|
||||
if (fromTree != null) walk.addTree(fromTree) else walk.addTree(new EmptyTreeIterator())
|
||||
walk.addTree(toTree)
|
||||
walk.setRecursive(true)
|
||||
|
||||
while (walk.next()) {
|
||||
val path = walk.getPathString
|
||||
val fromMode = if (walk.getTreeCount > 1) walk.getFileMode(0) else FileMode.MISSING
|
||||
val toMode = walk.getFileMode(walk.getTreeCount - 1)
|
||||
|
||||
if (fromMode == FileMode.MISSING && toMode != FileMode.MISSING) {
|
||||
// File was added in the original change, so remove it in the revert
|
||||
entries.remove(path)
|
||||
} else if (fromMode != FileMode.MISSING && toMode == FileMode.MISSING) {
|
||||
// File was deleted in the original change, so restore it in the revert
|
||||
val entry = new DirCacheEntry(path)
|
||||
entry.setFileMode(fromMode)
|
||||
entry.setObjectId(walk.getObjectId(0))
|
||||
entries(path) = entry
|
||||
} else if (fromMode != FileMode.MISSING && toMode != FileMode.MISSING) {
|
||||
val fromObjectId = walk.getObjectId(0)
|
||||
val toObjectId = walk.getObjectId(walk.getTreeCount - 1)
|
||||
|
||||
if (!fromObjectId.equals(toObjectId)) {
|
||||
// File was modified in the original change, restore original content
|
||||
val entry = new DirCacheEntry(path)
|
||||
entry.setFileMode(fromMode)
|
||||
entry.setObjectId(fromObjectId)
|
||||
entries(path) = entry
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Build the final tree
|
||||
entries.values.toSeq.sortBy(_.getPathString).foreach(builder.add)
|
||||
builder.finish()
|
||||
dirCache.writeTree(inserter)
|
||||
}
|
||||
}
|
||||
|
||||
object PullRequestService {
|
||||
@@ -658,18 +815,18 @@ object PullRequestService {
|
||||
commitIdTo: String
|
||||
) {
|
||||
|
||||
val hasConflict = conflictMessage.isDefined
|
||||
val hasConflict: Boolean = conflictMessage.isDefined
|
||||
val statuses: List[CommitStatus] =
|
||||
commitStatuses ++ (branchProtection.contexts.toSet -- commitStatuses.map(_.context).toSet)
|
||||
commitStatuses ++ (branchProtection.contexts.getOrElse(Nil).toSet -- commitStatuses.map(_.context).toSet)
|
||||
.map(CommitStatus.pending(branchProtection.owner, branchProtection.repository, _))
|
||||
val hasRequiredStatusProblem = needStatusCheck && branchProtection.contexts.exists(context =>
|
||||
statuses.find(_.context == context).map(_.state) != Some(CommitState.SUCCESS)
|
||||
)
|
||||
val hasProblem = hasRequiredStatusProblem || hasConflict || (statuses.nonEmpty && CommitState.combine(
|
||||
val hasRequiredStatusProblem: Boolean = needStatusCheck && branchProtection.contexts
|
||||
.getOrElse(Nil)
|
||||
.exists(context => !statuses.find(_.context == context).map(_.state).contains(CommitState.SUCCESS))
|
||||
val hasProblem: Boolean = hasRequiredStatusProblem || hasConflict || (statuses.nonEmpty && CommitState.combine(
|
||||
statuses.map(_.state).toSet
|
||||
) != CommitState.SUCCESS)
|
||||
val canUpdate = branchIsOutOfDate && !hasConflict
|
||||
val canMerge = hasMergePermission && !hasConflict && !hasRequiredStatusProblem
|
||||
val canUpdate: Boolean = branchIsOutOfDate && !hasConflict
|
||||
val canMerge: Boolean = hasMergePermission && !hasConflict && !hasRequiredStatusProblem
|
||||
lazy val commitStateSummary: (CommitState, String) = {
|
||||
val stateMap = statuses.groupBy(_.state)
|
||||
val state = CommitState.combine(stateMap.keySet)
|
||||
@@ -677,8 +834,8 @@ object PullRequestService {
|
||||
state -> summary
|
||||
}
|
||||
lazy val statusesAndRequired: List[(CommitStatus, Boolean)] = statuses.map { s =>
|
||||
s -> branchProtection.contexts.contains(s.context)
|
||||
s -> branchProtection.contexts.getOrElse(Nil).contains(s.context)
|
||||
}
|
||||
lazy val isAllSuccess = commitStateSummary._1 == CommitState.SUCCESS
|
||||
lazy val isAllSuccess: Boolean = commitStateSummary._1 == CommitState.SUCCESS
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import gitbucket.core.model.Profile.dateColumnType
|
||||
import gitbucket.core.util.JGitUtil
|
||||
|
||||
trait ReleaseService {
|
||||
self: AccountService with RepositoryService =>
|
||||
self: AccountService & RepositoryService =>
|
||||
|
||||
def createReleaseAsset(
|
||||
owner: String,
|
||||
|
||||
@@ -1,28 +1,24 @@
|
||||
package gitbucket.core.service
|
||||
import gitbucket.core.api.JsonFormat
|
||||
import gitbucket.core.model.{Account, WebHook}
|
||||
import gitbucket.core.model.Profile.profile.blockingApi._
|
||||
import gitbucket.core.model.Profile.profile.blockingApi.*
|
||||
import gitbucket.core.model.activity.{CloseIssueInfo, PushInfo}
|
||||
import gitbucket.core.plugin.PluginRegistry
|
||||
import gitbucket.core.service.SystemSettingsService.SystemSettings
|
||||
import gitbucket.core.service.WebHookService.WebHookPushPayload
|
||||
import gitbucket.core.util.Directory.getRepositoryDir
|
||||
import gitbucket.core.util.JGitUtil.CommitInfo
|
||||
import gitbucket.core.util.{JGitUtil, LockUtil}
|
||||
import gitbucket.core.util.{JGitUtil, LockUtil, StringUtil}
|
||||
import org.eclipse.jgit.api.Git
|
||||
import org.eclipse.jgit.dircache.{DirCache, DirCacheBuilder}
|
||||
import org.eclipse.jgit.lib._
|
||||
import org.eclipse.jgit.lib.*
|
||||
import org.eclipse.jgit.transport.{ReceiveCommand, ReceivePack}
|
||||
|
||||
import scala.util.Using
|
||||
|
||||
trait RepositoryCommitFileService {
|
||||
self: AccountService
|
||||
with ActivityService
|
||||
with IssuesService
|
||||
with PullRequestService
|
||||
with WebHookPullRequestService
|
||||
with RepositoryService =>
|
||||
self: AccountService & ActivityService & IssuesService & PullRequestService & WebHookPullRequestService &
|
||||
RepositoryService & ProtectedBranchService =>
|
||||
|
||||
/**
|
||||
* Create multiple files by callback function.
|
||||
@@ -57,16 +53,22 @@ trait RepositoryCommitFileService {
|
||||
message: String,
|
||||
commit: String,
|
||||
loginAccount: Account,
|
||||
settings: SystemSettings
|
||||
settings: SystemSettings,
|
||||
hasBom: Boolean = false
|
||||
)(implicit s: Session, c: JsonFormat.Context): Either[String, (ObjectId, Option[ObjectId])] = {
|
||||
val contentBytes = if (content.nonEmpty) {
|
||||
val bytes = content.getBytes(charset)
|
||||
if (hasBom) StringUtil.Utf8Bom ++ bytes else bytes
|
||||
} else {
|
||||
Array.emptyByteArray
|
||||
}
|
||||
commitFile(
|
||||
repository,
|
||||
branch,
|
||||
path,
|
||||
newFileName,
|
||||
oldFileName,
|
||||
if (content.nonEmpty) { content.getBytes(charset) }
|
||||
else { Array.emptyByteArray },
|
||||
contentBytes,
|
||||
message,
|
||||
commit,
|
||||
loginAccount,
|
||||
@@ -96,10 +98,10 @@ trait RepositoryCommitFileService {
|
||||
)(implicit s: Session, c: JsonFormat.Context): Either[String, (ObjectId, Option[ObjectId])] = {
|
||||
|
||||
val newPath = newFileName.map { newFileName =>
|
||||
if (path.length == 0) newFileName else s"${path}/${newFileName}"
|
||||
if (path.isEmpty) newFileName else s"${path}/${newFileName}"
|
||||
}
|
||||
val oldPath = oldFileName.map { oldFileName =>
|
||||
if (path.length == 0) oldFileName else s"${path}/${oldFileName}"
|
||||
if (path.isEmpty) oldFileName else s"${path}/${oldFileName}"
|
||||
}
|
||||
|
||||
_createFiles(repository, branch, message, pusherAccount, committerName, committerMailAddress, settings) {
|
||||
@@ -143,7 +145,6 @@ trait RepositoryCommitFileService {
|
||||
)(
|
||||
f: (Git, ObjectId, DirCacheBuilder, ObjectInserter) => R
|
||||
)(implicit s: Session, c: JsonFormat.Context): Either[String, (ObjectId, R)] = {
|
||||
|
||||
LockUtil.lock(s"${repository.owner}/${repository.name}") {
|
||||
Using.resource(Git.open(getRepositoryDir(repository.owner, repository.name))) { git =>
|
||||
val builder = DirCache.newInCore.builder()
|
||||
@@ -172,7 +173,14 @@ trait RepositoryCommitFileService {
|
||||
|
||||
// call pre-commit hook
|
||||
val error = PluginRegistry().getReceiveHooks.flatMap { hook =>
|
||||
hook.preReceive(repository.owner, repository.name, receivePack, receiveCommand, pusherAccount.userName, false)
|
||||
hook.preReceive(
|
||||
repository.owner,
|
||||
repository.name,
|
||||
receivePack,
|
||||
receiveCommand,
|
||||
pusherAccount.userName,
|
||||
mergePullRequest = false
|
||||
)
|
||||
}.headOption
|
||||
|
||||
error match {
|
||||
@@ -198,7 +206,8 @@ trait RepositoryCommitFileService {
|
||||
// record activity
|
||||
updateLastActivityDate(repository.owner, repository.name)
|
||||
val commitInfo = new CommitInfo(JGitUtil.getRevCommitFromId(git, commitId))
|
||||
val pushInfo = PushInfo(repository.owner, repository.name, pusherAccount.userName, branch, List(commitInfo))
|
||||
val pushInfo =
|
||||
PushInfo(repository.owner, repository.name, pusherAccount.userName, branch, List(commitInfo))
|
||||
recordActivity(pushInfo)
|
||||
|
||||
// create issue comment by commit message
|
||||
@@ -225,7 +234,14 @@ trait RepositoryCommitFileService {
|
||||
|
||||
// call post-commit hook
|
||||
PluginRegistry().getReceiveHooks.foreach { hook =>
|
||||
hook.postReceive(repository.owner, repository.name, receivePack, receiveCommand, committerName, false)
|
||||
hook.postReceive(
|
||||
repository.owner,
|
||||
repository.name,
|
||||
receivePack,
|
||||
receiveCommand,
|
||||
committerName,
|
||||
mergePullRequest = false
|
||||
)
|
||||
}
|
||||
|
||||
val commit = new JGitUtil.CommitInfo(JGitUtil.getRevCommitFromId(git, commitId))
|
||||
|
||||
@@ -46,12 +46,7 @@ object RepositoryCreationService {
|
||||
}
|
||||
|
||||
trait RepositoryCreationService {
|
||||
self: AccountService
|
||||
with RepositoryService
|
||||
with LabelsService
|
||||
with WikiService
|
||||
with ActivityService
|
||||
with PrioritiesService =>
|
||||
self: AccountService & RepositoryService & LabelsService & WikiService & ActivityService & PrioritiesService =>
|
||||
|
||||
def canCreateRepository(repositoryOwner: String, loginAccount: Account)(implicit session: Session): Boolean = {
|
||||
repositoryOwner == loginAccount.userName || getGroupsByUserName(loginAccount.userName)
|
||||
|
||||
@@ -654,11 +654,11 @@ trait RepositoryService {
|
||||
|
||||
def hasOwnerRole(owner: String, repository: String, loginAccount: Option[Account])(implicit s: Session): Boolean = {
|
||||
loginAccount match {
|
||||
case Some(a) if (a.isAdmin) => true
|
||||
case Some(a) if (a.userName == owner) => true
|
||||
case Some(a) if (getGroupMembers(owner).exists(_.userName == a.userName)) => true
|
||||
case Some(a) if (getCollaboratorUserNames(owner, repository, Seq(Role.ADMIN)).contains(a.userName)) => true
|
||||
case _ => false
|
||||
case Some(a) if a.isAdmin => true
|
||||
case Some(a) if a.userName == owner => true
|
||||
case Some(a) if getGroupMembers(owner).exists(_.userName == a.userName) => true
|
||||
case Some(a) if getCollaboratorUserNames(owner, repository, Seq(Role.ADMIN)).contains(a.userName) => true
|
||||
case _ => false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -666,11 +666,11 @@ trait RepositoryService {
|
||||
s: Session
|
||||
): Boolean = {
|
||||
loginAccount match {
|
||||
case Some(a) if (a.isAdmin) => true
|
||||
case Some(a) if (a.userName == owner) => true
|
||||
case Some(a) if (getGroupMembers(owner).exists(_.userName == a.userName)) => true
|
||||
case Some(a) if a.isAdmin => true
|
||||
case Some(a) if a.userName == owner => true
|
||||
case Some(a) if getGroupMembers(owner).exists(_.userName == a.userName) => true
|
||||
case Some(a)
|
||||
if (getCollaboratorUserNames(owner, repository, Seq(Role.ADMIN, Role.DEVELOPER)).contains(a.userName)) =>
|
||||
if getCollaboratorUserNames(owner, repository, Seq(Role.ADMIN, Role.DEVELOPER)).contains(a.userName) =>
|
||||
true
|
||||
case _ => false
|
||||
}
|
||||
@@ -678,12 +678,12 @@ trait RepositoryService {
|
||||
|
||||
def hasGuestRole(owner: String, repository: String, loginAccount: Option[Account])(implicit s: Session): Boolean = {
|
||||
loginAccount match {
|
||||
case Some(a) if (a.isAdmin) => true
|
||||
case Some(a) if (a.userName == owner) => true
|
||||
case Some(a) if (getGroupMembers(owner).exists(_.userName == a.userName)) => true
|
||||
case Some(a) if a.isAdmin => true
|
||||
case Some(a) if a.userName == owner => true
|
||||
case Some(a) if getGroupMembers(owner).exists(_.userName == a.userName) => true
|
||||
case Some(a)
|
||||
if (getCollaboratorUserNames(owner, repository, Seq(Role.ADMIN, Role.DEVELOPER, Role.GUEST))
|
||||
.contains(a.userName)) =>
|
||||
if getCollaboratorUserNames(owner, repository, Seq(Role.ADMIN, Role.DEVELOPER, Role.GUEST))
|
||||
.contains(a.userName) =>
|
||||
true
|
||||
case _ => false
|
||||
}
|
||||
@@ -694,17 +694,29 @@ trait RepositoryService {
|
||||
true
|
||||
} else {
|
||||
loginAccount match {
|
||||
case Some(x) if (x.isAdmin) => true
|
||||
case Some(x) if (repository.userName == x.userName) => true
|
||||
case Some(x) if (getGroupMembers(repository.userName).exists(_.userName == x.userName)) => true
|
||||
case Some(x)
|
||||
if (getCollaboratorUserNames(repository.userName, repository.repositoryName).contains(x.userName)) =>
|
||||
case Some(x) if x.isAdmin => true
|
||||
case Some(x) if repository.userName == x.userName => true
|
||||
case Some(x) if getGroupMembers(repository.userName).exists(_.userName == x.userName) => true
|
||||
case Some(x) if getCollaboratorUserNames(repository.userName, repository.repositoryName).contains(x.userName) =>
|
||||
true
|
||||
case _ => false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def isWritable(repository: Repository, loginAccount: Option[Account])(implicit s: Session): Boolean = {
|
||||
loginAccount match {
|
||||
case Some(x) if x.isAdmin => true
|
||||
case Some(x) if repository.userName == x.userName => true
|
||||
case Some(x) if getGroupMembers(repository.userName).exists(_.userName == x.userName) => true
|
||||
case Some(x)
|
||||
if getCollaboratorUserNames(repository.userName, repository.repositoryName, Seq(Role.ADMIN, Role.DEVELOPER))
|
||||
.contains(x.userName) =>
|
||||
true
|
||||
case _ => false
|
||||
}
|
||||
}
|
||||
|
||||
private def getForkedCount(userName: String, repositoryName: String)(implicit s: Session): Int =
|
||||
Query(Repositories.filter { t =>
|
||||
(t.originUserName === userName.bind) && (t.originRepositoryName === repositoryName.bind)
|
||||
|
||||
@@ -93,6 +93,7 @@ trait SystemSettingsService {
|
||||
props.setProperty(RepositoryViewerMaxDiffFiles, settings.repositoryViewer.maxDiffFiles.toString)
|
||||
props.setProperty(RepositoryViewerMaxDiffLines, settings.repositoryViewer.maxDiffLines.toString)
|
||||
props.setProperty(DefaultBranch, settings.defaultBranch)
|
||||
props.setProperty(ShowFullName, settings.showFullName.toString)
|
||||
|
||||
Using.resource(new java.io.FileOutputStream(GitBucketConf)) { out =>
|
||||
props.store(out, null)
|
||||
@@ -211,7 +212,8 @@ trait SystemSettingsService {
|
||||
getValue(props, RepositoryViewerMaxDiffFiles, 100),
|
||||
getValue(props, RepositoryViewerMaxDiffLines, 1000)
|
||||
),
|
||||
getValue(props, DefaultBranch, "main")
|
||||
getValue(props, DefaultBranch, "main"),
|
||||
getValue(props, ShowFullName, false)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -238,7 +240,8 @@ object SystemSettingsService {
|
||||
webHook: WebHook,
|
||||
upload: Upload,
|
||||
repositoryViewer: RepositoryViewerSettings,
|
||||
defaultBranch: String
|
||||
defaultBranch: String,
|
||||
showFullName: Boolean
|
||||
) {
|
||||
def baseUrl(request: HttpServletRequest): String =
|
||||
baseUrl.getOrElse(parseBaseUrl(request)).stripSuffix("/")
|
||||
@@ -373,7 +376,7 @@ object SystemSettingsService {
|
||||
if (isDefaultPort) {
|
||||
s"${genericUser}@${host}"
|
||||
} else {
|
||||
s"${genericUser}@${host}:${port}"
|
||||
s"ssh://${genericUser}@${host}:${port}"
|
||||
}
|
||||
|
||||
def getUrl(owner: String, name: String): String =
|
||||
@@ -457,6 +460,7 @@ object SystemSettingsService {
|
||||
private val RepositoryViewerMaxDiffFiles = "repository_viewer_max_diff_files"
|
||||
private val RepositoryViewerMaxDiffLines = "repository_viewer_max_diff_lines"
|
||||
private val DefaultBranch = "default_branch"
|
||||
private val ShowFullName = "show_full_name"
|
||||
|
||||
private def getValue[A: ClassTag](props: java.util.Properties, key: String, default: A): A = {
|
||||
getConfigValue(key).getOrElse {
|
||||
|
||||
@@ -265,7 +265,7 @@ trait WebHookService {
|
||||
}
|
||||
|
||||
private def validateTargetAddress(settings: SystemSettings, url: String): Boolean = {
|
||||
val host = new java.net.URL(url).getHost
|
||||
val host = new java.net.URI(url).toURL.getHost
|
||||
|
||||
!settings.webHook.blockPrivateAddress ||
|
||||
!HttpClientUtil.isPrivateAddress(host) ||
|
||||
@@ -302,42 +302,45 @@ trait WebHookService {
|
||||
httpPost.addHeader("Content-Type", webHook.ctype.ctype)
|
||||
httpPost.addHeader("X-Github-Event", event.name)
|
||||
httpPost.addHeader("X-Github-Delivery", java.util.UUID.randomUUID().toString)
|
||||
|
||||
def addXHubSignature(content: Array[Byte]): Unit = {
|
||||
webHook.token
|
||||
.filter(_.trim.nonEmpty)
|
||||
.foreach { token =>
|
||||
// https://developer.github.com/webhooks/securing/#validating-payloads-from-github
|
||||
// SHA1 is required for backward compatibility, but SHA256 is recommended.
|
||||
httpPost.addHeader(
|
||||
"X-Hub-Signature",
|
||||
XHub.generateHeaderXHubToken(
|
||||
XHubConverter.HEXA_LOWERCASE,
|
||||
XHubDigest.SHA1,
|
||||
token,
|
||||
content
|
||||
)
|
||||
)
|
||||
httpPost.addHeader(
|
||||
"X-Hub-Signature-256",
|
||||
XHub.generateHeaderXHubToken(
|
||||
XHubConverter.HEXA_LOWERCASE,
|
||||
XHubDigest.SHA256,
|
||||
token,
|
||||
content
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
webHook.ctype match {
|
||||
case WebHookContentType.FORM => {
|
||||
val params: java.util.List[NameValuePair] = new java.util.ArrayList()
|
||||
params.add(new BasicNameValuePair("payload", json))
|
||||
def postContent = new UrlEncodedFormEntity(params, "UTF-8")
|
||||
httpPost.setEntity(postContent)
|
||||
if (webHook.token.exists(_.trim.nonEmpty)) {
|
||||
// TODO find a better way and see how to extract content from postContent
|
||||
val contentAsBytes = URLEncodedUtils.format(params, "UTF-8").getBytes("UTF-8")
|
||||
httpPost.addHeader(
|
||||
"X-Hub-Signature",
|
||||
XHub.generateHeaderXHubToken(
|
||||
XHubConverter.HEXA_LOWERCASE,
|
||||
XHubDigest.SHA1,
|
||||
webHook.token.get,
|
||||
contentAsBytes
|
||||
)
|
||||
)
|
||||
}
|
||||
addXHubSignature(URLEncodedUtils.format(params, "UTF-8").getBytes("UTF-8"))
|
||||
}
|
||||
case WebHookContentType.JSON => {
|
||||
httpPost.setEntity(
|
||||
EntityBuilder.create().setContentType(ContentType.APPLICATION_JSON).setText(json).build()
|
||||
)
|
||||
if (webHook.token.exists(_.trim.nonEmpty)) {
|
||||
httpPost.addHeader(
|
||||
"X-Hub-Signature",
|
||||
XHub.generateHeaderXHubToken(
|
||||
XHubConverter.HEXA_LOWERCASE,
|
||||
XHubDigest.SHA1,
|
||||
webHook.token.orNull,
|
||||
json.getBytes("UTF-8")
|
||||
)
|
||||
)
|
||||
}
|
||||
addXHubSignature(json.getBytes("UTF-8"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -367,7 +370,7 @@ trait WebHookService {
|
||||
}
|
||||
|
||||
trait WebHookPullRequestService extends WebHookService {
|
||||
self: AccountService with RepositoryService with PullRequestService with IssuesService =>
|
||||
self: AccountService & RepositoryService & PullRequestService & IssuesService =>
|
||||
|
||||
import WebHookService._
|
||||
// https://developer.github.com/v3/activity/events/types/#issuesevent
|
||||
@@ -513,7 +516,7 @@ trait WebHookPullRequestService extends WebHookService {
|
||||
}
|
||||
|
||||
trait WebHookPullRequestReviewCommentService extends WebHookService {
|
||||
self: AccountService with RepositoryService with PullRequestService with IssuesService with CommitsService =>
|
||||
self: AccountService & RepositoryService & PullRequestService & IssuesService & CommitsService =>
|
||||
def callPullRequestReviewCommentWebHook(
|
||||
action: String,
|
||||
comment: CommitComment,
|
||||
@@ -561,7 +564,7 @@ trait WebHookPullRequestReviewCommentService extends WebHookService {
|
||||
}
|
||||
|
||||
trait WebHookIssueCommentService extends WebHookPullRequestService {
|
||||
self: AccountService with RepositoryService with PullRequestService with IssuesService =>
|
||||
self: AccountService & RepositoryService & PullRequestService & IssuesService =>
|
||||
|
||||
import WebHookService._
|
||||
def callIssueCommentWebHook(
|
||||
|
||||
@@ -341,4 +341,18 @@ trait WikiService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the branch name from the HEAD of the Wiki repository.
|
||||
*
|
||||
* from gitbucket.core.controller.WikiController
|
||||
*
|
||||
* @param owner Wiki owner
|
||||
* @param repository Wiki repository
|
||||
* @return Branch name
|
||||
*/
|
||||
def getWikiBranch(owner: String, repository: String): String = {
|
||||
Using.resource(Git.open(Directory.getWikiRepositoryDir(owner, repository))) { git =>
|
||||
git.getRepository.getBranch
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,8 +25,8 @@ class ApiAuthenticationFilter extends Filter with AccessTokenService with Accoun
|
||||
val response = res.asInstanceOf[HttpServletResponse]
|
||||
Option(request.getHeader("Authorization"))
|
||||
.map {
|
||||
case auth if auth.toLowerCase().startsWith("token ") =>
|
||||
AccessTokenService.getAccountByAccessToken(auth.substring(6).trim).toRight(())
|
||||
case auth if auth.toLowerCase().startsWith("token ") || auth.toLowerCase().startsWith("bearer ") =>
|
||||
AccessTokenService.getAccountByAccessToken(auth.substring(auth.indexOf(" ") + 1).trim).toRight(())
|
||||
case auth if auth.startsWith("Basic ") => doBasicAuth(auth, loadSystemSettings(), request).toRight(())
|
||||
case _ => Left(())
|
||||
}
|
||||
@@ -42,7 +42,7 @@ class ApiAuthenticationFilter extends Filter with AccessTokenService with Accoun
|
||||
updateLastLoginDate(account.userName)
|
||||
}
|
||||
chain.doFilter(req, res)
|
||||
case None => chain.doFilter(req, res)
|
||||
case None => chain.doFilter(req, res)
|
||||
case Some(Left(_)) => {
|
||||
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED)
|
||||
response.setContentType("application/json; charset=utf-8")
|
||||
|
||||
@@ -9,11 +9,11 @@ import gitbucket.core.api.JsonFormat.Context
|
||||
import gitbucket.core.model.WebHook
|
||||
import gitbucket.core.plugin.{GitRepositoryRouting, PluginRegistry}
|
||||
import gitbucket.core.service.IssuesService.IssueSearchCondition
|
||||
import gitbucket.core.service.WebHookService._
|
||||
import gitbucket.core.service._
|
||||
import gitbucket.core.util.Implicits._
|
||||
import gitbucket.core.util._
|
||||
import gitbucket.core.model.Profile.profile.blockingApi._
|
||||
import gitbucket.core.service.WebHookService.*
|
||||
import gitbucket.core.service.*
|
||||
import gitbucket.core.util.Implicits.*
|
||||
import gitbucket.core.util.*
|
||||
import gitbucket.core.model.Profile.profile.blockingApi.*
|
||||
import gitbucket.core.model.activity.{
|
||||
BaseActivityInfo,
|
||||
CloseIssueInfo,
|
||||
@@ -33,9 +33,9 @@ import gitbucket.core.servlet.Database
|
||||
|
||||
import org.eclipse.jgit.api.Git
|
||||
import org.eclipse.jgit.http.server.GitServlet
|
||||
import org.eclipse.jgit.lib._
|
||||
import org.eclipse.jgit.transport._
|
||||
import org.eclipse.jgit.transport.resolver._
|
||||
import org.eclipse.jgit.lib.*
|
||||
import org.eclipse.jgit.transport.*
|
||||
import org.eclipse.jgit.transport.resolver.*
|
||||
import org.slf4j.LoggerFactory
|
||||
import javax.servlet.ServletConfig
|
||||
import javax.servlet.http.{HttpServletRequest, HttpServletResponse}
|
||||
@@ -43,7 +43,7 @@ import org.eclipse.jgit.diff.DiffEntry.ChangeType
|
||||
import org.eclipse.jgit.internal.storage.file.FileRepository
|
||||
import org.json4s.Formats
|
||||
import org.json4s.convertToJsonInput
|
||||
import org.json4s.jackson.Serialization._
|
||||
import org.json4s.jackson.Serialization.*
|
||||
|
||||
/**
|
||||
* Provides Git repository via HTTP.
|
||||
@@ -117,7 +117,7 @@ class GitRepositoryServlet extends GitServlet with SystemSettingsService {
|
||||
GitLfs.BatchResponseObject(
|
||||
requestObject.oid,
|
||||
requestObject.size,
|
||||
true,
|
||||
authenticated = true,
|
||||
GitLfs.Actions(
|
||||
upload = Some(
|
||||
GitLfs.Action(
|
||||
@@ -138,7 +138,7 @@ class GitRepositoryServlet extends GitServlet with SystemSettingsService {
|
||||
GitLfs.BatchResponseObject(
|
||||
requestObject.oid,
|
||||
requestObject.size,
|
||||
true,
|
||||
authenticated = true,
|
||||
GitLfs.Actions(
|
||||
download = Some(
|
||||
GitLfs.Action(
|
||||
@@ -204,7 +204,7 @@ class GitBucketReceivePackFactory extends ReceivePackFactory[HttpServletRequest]
|
||||
|
||||
val settings = loadSystemSettings()
|
||||
val baseUrl = settings.baseUrl(request)
|
||||
val sshUrl = settings.sshUrl(owner, repository)
|
||||
val sshUrl = settings.sshUrl
|
||||
|
||||
if (!repository.endsWith(".wiki")) {
|
||||
val hook = new CommitLogHook(owner, repository, pusher, baseUrl, sshUrl)
|
||||
@@ -223,7 +223,7 @@ class GitBucketReceivePackFactory extends ReceivePackFactory[HttpServletRequest]
|
||||
}
|
||||
}
|
||||
|
||||
import scala.jdk.CollectionConverters._
|
||||
import scala.jdk.CollectionConverters.*
|
||||
|
||||
class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl: String, sshUrl: Option[String])
|
||||
extends PostReceiveHook
|
||||
@@ -242,6 +242,7 @@ class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl:
|
||||
with WebHookPullRequestReviewCommentService
|
||||
with CommitsService
|
||||
with SystemSettingsService
|
||||
with ProtectedBranchService
|
||||
with RequestCache {
|
||||
|
||||
private val logger = LoggerFactory.getLogger(classOf[CommitLogHook])
|
||||
@@ -253,7 +254,7 @@ class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl:
|
||||
commands.asScala.foreach { command =>
|
||||
// call pre-commit hook
|
||||
PluginRegistry().getReceiveHooks
|
||||
.flatMap(_.preReceive(owner, repository, receivePack, command, pusher, false))
|
||||
.flatMap(_.preReceive(owner, repository, receivePack, command, pusher, mergePullRequest = false))
|
||||
.headOption
|
||||
.foreach { error =>
|
||||
command.setResult(ReceiveCommand.Result.REJECTED_OTHER_REASON, error)
|
||||
@@ -296,7 +297,7 @@ class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl:
|
||||
} else {
|
||||
command.getType match {
|
||||
case ReceiveCommand.Type.DELETE => Nil
|
||||
case _ => JGitUtil.getCommitLog(git, command.getOldId.name, command.getNewId.name)
|
||||
case _ => JGitUtil.getCommitLog(git, command.getOldId, command.getNewId)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -428,8 +429,8 @@ class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl:
|
||||
repositoryInfo,
|
||||
newCommits,
|
||||
ownerAccount,
|
||||
newId = command.getNewId(),
|
||||
oldId = command.getOldId()
|
||||
newId = command.getNewId,
|
||||
oldId = command.getOldId
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -453,7 +454,7 @@ class CommitLogHook(owner: String, repository: String, pusher: String, baseUrl:
|
||||
|
||||
// call post-commit hook
|
||||
PluginRegistry().getReceiveHooks
|
||||
.foreach(_.postReceive(owner, repository, receivePack, command, pusher, false))
|
||||
.foreach(_.postReceive(owner, repository, receivePack, command, pusher, mergePullRequest = false))
|
||||
}
|
||||
}
|
||||
// update repository last modified time.
|
||||
@@ -492,7 +493,7 @@ class WikiCommitHook(owner: String, repository: String, pusher: String, baseUrl:
|
||||
} else {
|
||||
command.getType match {
|
||||
case ReceiveCommand.Type.DELETE => None
|
||||
case _ => Some((command.getOldId.getName, command.getNewId.name))
|
||||
case _ => Some((command.getOldId, command.getNewId))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -543,7 +544,7 @@ class WikiCommitHook(owner: String, repository: String, pusher: String, baseUrl:
|
||||
case ChangeType.ADD | ChangeType.RENAME => "created"
|
||||
case ChangeType.MODIFY => "edited"
|
||||
case ChangeType.DELETE => "deleted"
|
||||
case other =>
|
||||
case other =>
|
||||
logger.error(s"Unsupported Wiki action: $other")
|
||||
"unsupported action"
|
||||
}
|
||||
@@ -560,7 +561,7 @@ class WikiCommitHook(owner: String, repository: String, pusher: String, baseUrl:
|
||||
case "created" => Some(CreateWikiPageInfo(owner, repo, commit.committerName, pageName))
|
||||
case "edited" => Some(EditWikiPageInfo(owner, repo, commit.committerName, pageName, commit.id))
|
||||
case "deleted" => Some(DeleteWikiInfo(owner, repo, commit.committerName, pageName))
|
||||
case other =>
|
||||
case other =>
|
||||
logger.info(s"Attempted to build wiki record for unsupported action: $other")
|
||||
None
|
||||
}
|
||||
|
||||
@@ -6,9 +6,9 @@ import gitbucket.core.GitBucketCoreModule
|
||||
import gitbucket.core.plugin.PluginRegistry
|
||||
import gitbucket.core.service.SystemSettingsService
|
||||
import gitbucket.core.util.DatabaseConfig
|
||||
import gitbucket.core.util.Directory._
|
||||
import gitbucket.core.util.JDBCUtil._
|
||||
import gitbucket.core.model.Profile.profile.blockingApi._
|
||||
import gitbucket.core.util.Directory.*
|
||||
import gitbucket.core.util.JDBCUtil.*
|
||||
import gitbucket.core.model.Profile.profile.blockingApi.*
|
||||
// Imported names have higher precedence than names, defined in other files.
|
||||
// If Database is not bound by explicit import, then "Database" refers to the Database introduced by the wildcard import above.
|
||||
import gitbucket.core.servlet.Database
|
||||
@@ -20,7 +20,7 @@ import javax.servlet.{ServletContextEvent, ServletContextListener}
|
||||
import org.apache.commons.io.{FileUtils, IOUtils}
|
||||
import org.slf4j.LoggerFactory
|
||||
|
||||
import scala.jdk.CollectionConverters._
|
||||
import scala.jdk.CollectionConverters.*
|
||||
import scala.util.Using
|
||||
|
||||
/**
|
||||
@@ -50,51 +50,57 @@ class InitializeListener extends ServletContextListener with SystemSettingsServi
|
||||
// )
|
||||
|
||||
override def contextInitialized(event: ServletContextEvent): Unit = {
|
||||
val dataDir = event.getServletContext.getInitParameter("gitbucket.home")
|
||||
if (dataDir != null) {
|
||||
System.setProperty("gitbucket.home", dataDir)
|
||||
}
|
||||
org.h2.Driver.load()
|
||||
try {
|
||||
val dataDir = event.getServletContext.getInitParameter("gitbucket.home")
|
||||
if (dataDir != null) {
|
||||
System.setProperty("gitbucket.home", dataDir)
|
||||
}
|
||||
org.h2.Driver.load()
|
||||
|
||||
Database() withTransaction { session =>
|
||||
val conn = session.conn
|
||||
val manager = new JDBCVersionManager(conn)
|
||||
Database() withTransaction { session =>
|
||||
val conn = session.conn
|
||||
val manager = new JDBCVersionManager(conn)
|
||||
|
||||
// Check version
|
||||
checkVersion(manager, conn)
|
||||
// Check version
|
||||
checkVersion(manager, conn)
|
||||
|
||||
// Run normal migration
|
||||
logger.info("Start schema update")
|
||||
new Solidbase()
|
||||
.migrate(conn, Thread.currentThread.getContextClassLoader, DatabaseConfig.liquiDriver, GitBucketCoreModule)
|
||||
// Run normal migration
|
||||
logger.info("Start schema update")
|
||||
new Solidbase()
|
||||
.migrate(conn, Thread.currentThread.getContextClassLoader, DatabaseConfig.liquiDriver, GitBucketCoreModule)
|
||||
|
||||
// Rescue code for users who updated from 3.14 to 4.0.0
|
||||
// https://github.com/gitbucket/gitbucket/issues/1227
|
||||
val currentVersion = manager.getCurrentVersion(GitBucketCoreModule.getModuleId)
|
||||
val databaseVersion = if (currentVersion == "4.0") {
|
||||
manager.updateVersion(GitBucketCoreModule.getModuleId, "4.0.0")
|
||||
"4.0.0"
|
||||
} else currentVersion
|
||||
// Rescue code for users who updated from 3.14 to 4.0.0
|
||||
// https://github.com/gitbucket/gitbucket/issues/1227
|
||||
val currentVersion = manager.getCurrentVersion(GitBucketCoreModule.getModuleId)
|
||||
val databaseVersion = if (currentVersion == "4.0") {
|
||||
manager.updateVersion(GitBucketCoreModule.getModuleId, "4.0.0")
|
||||
"4.0.0"
|
||||
} else currentVersion
|
||||
|
||||
val gitbucketVersion = GitBucketCoreModule.getVersions.asScala.last.getVersion
|
||||
if (databaseVersion != gitbucketVersion) {
|
||||
throw new IllegalStateException(
|
||||
s"Initialization failed. GitBucket version is ${gitbucketVersion}, but database version is ${databaseVersion}."
|
||||
)
|
||||
val gitbucketVersion = GitBucketCoreModule.getVersions.asScala.last.getVersion
|
||||
if (databaseVersion != gitbucketVersion) {
|
||||
throw new IllegalStateException(
|
||||
s"Initialization failed. GitBucket version is ${gitbucketVersion}, but database version is ${databaseVersion}."
|
||||
)
|
||||
}
|
||||
|
||||
// Install bundled plugins
|
||||
extractBundledPlugins()
|
||||
|
||||
// Load plugins
|
||||
logger.info("Initialize plugins")
|
||||
PluginRegistry.initialize(event.getServletContext, loadSystemSettings(), conn)
|
||||
}
|
||||
|
||||
// Install bundled plugins
|
||||
extractBundledPlugins()
|
||||
|
||||
// Load plugins
|
||||
logger.info("Initialize plugins")
|
||||
PluginRegistry.initialize(event.getServletContext, loadSystemSettings(), conn)
|
||||
// // Start Quartz scheduler
|
||||
// val scheduler = QuartzSchedulerExtension(system)
|
||||
//
|
||||
// scheduler.schedule("Daily", system.actorOf(Props[DeleteOldActivityActor]), "DeleteOldActivity")
|
||||
} catch {
|
||||
case e: Exception =>
|
||||
logger.error(e.getMessage, e)
|
||||
throw e
|
||||
}
|
||||
|
||||
// // Start Quartz scheduler
|
||||
// val scheduler = QuartzSchedulerExtension(system)
|
||||
//
|
||||
// scheduler.schedule("Daily", system.actorOf(Props[DeleteOldActivityActor]), "DeleteOldActivity")
|
||||
}
|
||||
|
||||
private def checkVersion(manager: JDBCVersionManager, conn: java.sql.Connection): Unit = {
|
||||
@@ -141,7 +147,7 @@ class InitializeListener extends ServletContextListener with SystemSettingsServi
|
||||
Using.resource(cl.getResourceAsStream("bundle-plugins.txt")) { pluginsFile =>
|
||||
if (pluginsFile != null) {
|
||||
val plugins = IOUtils.readLines(pluginsFile, "UTF-8")
|
||||
val gitbucketVersion = GitBucketCoreModule.getVersions.asScala.last.getVersion
|
||||
// val gitbucketVersion = GitBucketCoreModule.getVersions.asScala.last.getVersion
|
||||
|
||||
plugins.asScala.foreach { plugin =>
|
||||
plugin.trim.split(":") match {
|
||||
|
||||
@@ -92,7 +92,7 @@ abstract class GitCommand extends Command with ServerSessionAware {
|
||||
}
|
||||
|
||||
abstract class DefaultGitCommand(val owner: String, val repoName: String) extends GitCommand {
|
||||
self: RepositoryService with AccountService with DeployKeyService =>
|
||||
self: RepositoryService & AccountService & DeployKeyService =>
|
||||
|
||||
protected def userName(authType: AuthType): String = {
|
||||
authType match {
|
||||
@@ -183,7 +183,7 @@ class DefaultGitReceivePack(owner: String, repoName: String, baseUrl: String, ss
|
||||
val receive = new ReceivePack(repository)
|
||||
if (!repoName.endsWith(".wiki")) {
|
||||
val hook =
|
||||
new CommitLogHook(owner, repoName, userName(authType), baseUrl, Some(sshAddress.getUrl(owner, repoName)))
|
||||
new CommitLogHook(owner, repoName, userName(authType), baseUrl, Some(sshAddress.getUrl))
|
||||
receive.setPreReceiveHook(hook)
|
||||
receive.setPostReceiveHook(hook)
|
||||
}
|
||||
|
||||
@@ -15,9 +15,9 @@ trait OneselfAuthenticator { self: ControllerBase =>
|
||||
|
||||
private def authenticate(action: => Any) = {
|
||||
context.loginAccount match {
|
||||
case Some(x) if (x.isAdmin) => action
|
||||
case Some(x) if (request.paths(0) == x.userName) => action
|
||||
case _ => Unauthorized()
|
||||
case Some(x) if x.isAdmin => action
|
||||
case Some(x) if request.paths(0) == x.userName => action
|
||||
case _ => Unauthorized()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -25,8 +25,8 @@ trait OneselfAuthenticator { self: ControllerBase =>
|
||||
/**
|
||||
* Allows only the repository owner and administrators.
|
||||
*/
|
||||
trait OwnerAuthenticator { self: ControllerBase with RepositoryService with AccountService =>
|
||||
protected def ownerOnly(action: (RepositoryInfo) => Any) = { authenticate(action) }
|
||||
trait OwnerAuthenticator { self: ControllerBase & RepositoryService & AccountService =>
|
||||
protected def ownerOnly(action: RepositoryInfo => Any) = { authenticate(action) }
|
||||
protected def ownerOnly[T](action: (T, RepositoryInfo) => Any) = (form: T) => { authenticate(action(form, _)) }
|
||||
|
||||
private def authenticate(action: (RepositoryInfo) => Any) = {
|
||||
@@ -34,14 +34,14 @@ trait OwnerAuthenticator { self: ControllerBase with RepositoryService with Acco
|
||||
val repoName = params("repository")
|
||||
getRepository(userName, repoName).map { repository =>
|
||||
context.loginAccount match {
|
||||
case Some(x) if (x.isAdmin) => action(repository)
|
||||
case Some(x) if (repository.owner == x.userName) => action(repository)
|
||||
case Some(x) if x.isAdmin => action(repository)
|
||||
case Some(x) if repository.owner == x.userName => action(repository)
|
||||
// TODO Repository management is allowed for only group managers?
|
||||
case Some(x) if (getGroupMembers(repository.owner).exists { m =>
|
||||
case Some(x) if getGroupMembers(repository.owner).exists { m =>
|
||||
m.userName == x.userName && m.isManager
|
||||
}) =>
|
||||
} =>
|
||||
action(repository)
|
||||
case Some(x) if (getCollaboratorUserNames(userName, repoName, Seq(Role.ADMIN)).contains(x.userName)) =>
|
||||
case Some(x) if getCollaboratorUserNames(userName, repoName, Seq(Role.ADMIN)).contains(x.userName) =>
|
||||
action(repository)
|
||||
case _ => Unauthorized()
|
||||
}
|
||||
@@ -82,11 +82,11 @@ trait AdminAuthenticator { self: ControllerBase =>
|
||||
/**
|
||||
* Allows only guests and signed in users who can access the repository.
|
||||
*/
|
||||
trait ReferrerAuthenticator { self: ControllerBase with RepositoryService with AccountService =>
|
||||
protected def referrersOnly(action: (RepositoryInfo) => Any) = { authenticate(action) }
|
||||
trait ReferrerAuthenticator { self: ControllerBase & RepositoryService & AccountService =>
|
||||
protected def referrersOnly(action: RepositoryInfo => Any) = { authenticate(action) }
|
||||
protected def referrersOnly[T](action: (T, RepositoryInfo) => Any) = (form: T) => { authenticate(action(form, _)) }
|
||||
|
||||
private def authenticate(action: (RepositoryInfo) => Any) = {
|
||||
private def authenticate(action: RepositoryInfo => Any) = {
|
||||
val userName = params("owner")
|
||||
val repoName = params("repository")
|
||||
getRepository(userName, repoName).map { repository =>
|
||||
@@ -102,13 +102,13 @@ trait ReferrerAuthenticator { self: ControllerBase with RepositoryService with A
|
||||
/**
|
||||
* Allows only signed in users who have read permission for the repository.
|
||||
*/
|
||||
trait ReadableUsersAuthenticator { self: ControllerBase with RepositoryService with AccountService =>
|
||||
protected def readableUsersOnly(action: (RepositoryInfo) => Any) = { authenticate(action) }
|
||||
trait ReadableUsersAuthenticator { self: ControllerBase & RepositoryService & AccountService =>
|
||||
protected def readableUsersOnly(action: RepositoryInfo => Any) = { authenticate(action) }
|
||||
protected def readableUsersOnly[T](action: (T, RepositoryInfo) => Any) = (form: T) => {
|
||||
authenticate(action(form, _))
|
||||
}
|
||||
|
||||
private def authenticate(action: (RepositoryInfo) => Any) = {
|
||||
private def authenticate(action: RepositoryInfo => Any) = {
|
||||
val userName = params("owner")
|
||||
val repoName = params("repository")
|
||||
getRepository(userName, repoName).map { repository =>
|
||||
@@ -124,25 +124,20 @@ trait ReadableUsersAuthenticator { self: ControllerBase with RepositoryService w
|
||||
/**
|
||||
* Allows only signed in users who have write permission for the repository.
|
||||
*/
|
||||
trait WritableUsersAuthenticator { self: ControllerBase with RepositoryService with AccountService =>
|
||||
protected def writableUsersOnly(action: (RepositoryInfo) => Any) = { authenticate(action) }
|
||||
trait WritableUsersAuthenticator { self: ControllerBase & RepositoryService & AccountService =>
|
||||
protected def writableUsersOnly(action: RepositoryInfo => Any) = { authenticate(action) }
|
||||
protected def writableUsersOnly[T](action: (T, RepositoryInfo) => Any) = (form: T) => {
|
||||
authenticate(action(form, _))
|
||||
}
|
||||
|
||||
private def authenticate(action: (RepositoryInfo) => Any) = {
|
||||
private def authenticate(action: RepositoryInfo => Any) = {
|
||||
val userName = params("owner")
|
||||
val repoName = params("repository")
|
||||
getRepository(userName, repoName).map { repository =>
|
||||
context.loginAccount match {
|
||||
case Some(x) if (x.isAdmin) => action(repository)
|
||||
case Some(x) if (userName == x.userName) => action(repository)
|
||||
case Some(x) if (getGroupMembers(repository.owner).exists(_.userName == x.userName)) => action(repository)
|
||||
case Some(x)
|
||||
if (getCollaboratorUserNames(userName, repoName, Seq(Role.ADMIN, Role.DEVELOPER))
|
||||
.contains(x.userName)) =>
|
||||
action(repository)
|
||||
case _ => Unauthorized()
|
||||
if (isWritable(repository.repository, context.loginAccount)) {
|
||||
action(repository)
|
||||
} else {
|
||||
Unauthorized()
|
||||
}
|
||||
} getOrElse NotFound()
|
||||
}
|
||||
@@ -151,7 +146,7 @@ trait WritableUsersAuthenticator { self: ControllerBase with RepositoryService w
|
||||
/**
|
||||
* Allows only the group managers.
|
||||
*/
|
||||
trait GroupManagerAuthenticator { self: ControllerBase with AccountService =>
|
||||
trait GroupManagerAuthenticator { self: ControllerBase & AccountService =>
|
||||
protected def managersOnly(action: => Any) = { authenticate(action) }
|
||||
protected def managersOnly[T](action: T => Any) = (form: T) => { authenticate(action(form)) }
|
||||
|
||||
@@ -159,9 +154,9 @@ trait GroupManagerAuthenticator { self: ControllerBase with AccountService =>
|
||||
context.loginAccount match {
|
||||
case Some(x) if x.isAdmin => action
|
||||
case Some(x) if x.userName == request.paths(0) => action
|
||||
case Some(x) if (getGroupMembers(request.paths(0)).exists { member =>
|
||||
case Some(x) if getGroupMembers(request.paths(0)).exists { member =>
|
||||
member.userName == x.userName && member.isManager
|
||||
}) =>
|
||||
} =>
|
||||
action
|
||||
case _ => Unauthorized()
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ object DatabaseConfig {
|
||||
FileUtils.write(
|
||||
file,
|
||||
"""db {
|
||||
| url = "jdbc:h2:${DatabaseHome};MVCC=true"
|
||||
| url = "jdbc:h2:${DatabaseHome}"
|
||||
| user = "sa"
|
||||
| password = "sa"
|
||||
|# connectionTimeout = 30000
|
||||
|
||||
@@ -10,7 +10,7 @@ object Directory {
|
||||
val GitBucketHome = (System.getProperty("gitbucket.home") match {
|
||||
// -Dgitbucket.home=<path>
|
||||
case path if (path != null) => new File(path)
|
||||
case _ =>
|
||||
case _ =>
|
||||
scala.util.Properties.envOrNone("GITBUCKET_HOME") match {
|
||||
// environment variable GITBUCKET_HOME
|
||||
case Some(env) => new File(env)
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
package gitbucket.core.util
|
||||
|
||||
import java.io._
|
||||
import java.io.*
|
||||
import gitbucket.core.service.RepositoryService
|
||||
import org.eclipse.jgit.api.Git
|
||||
import Directory._
|
||||
import StringUtil._
|
||||
import Directory.*
|
||||
import StringUtil.*
|
||||
|
||||
import scala.annotation.tailrec
|
||||
import scala.jdk.CollectionConverters._
|
||||
import scala.jdk.CollectionConverters.*
|
||||
import scala.util.Using
|
||||
import org.eclipse.jgit.lib._
|
||||
import org.eclipse.jgit.revwalk._
|
||||
import org.eclipse.jgit.revwalk.filter._
|
||||
import org.eclipse.jgit.treewalk._
|
||||
import org.eclipse.jgit.treewalk.filter._
|
||||
import org.eclipse.jgit.lib.*
|
||||
import org.eclipse.jgit.revwalk.*
|
||||
import org.eclipse.jgit.revwalk.filter.*
|
||||
import org.eclipse.jgit.treewalk.*
|
||||
import org.eclipse.jgit.treewalk.filter.*
|
||||
import org.eclipse.jgit.diff.DiffEntry.ChangeType
|
||||
import org.eclipse.jgit.errors.{ConfigInvalidException, IncorrectObjectTypeException, MissingObjectException}
|
||||
import org.eclipse.jgit.transport.RefSpec
|
||||
@@ -21,7 +21,7 @@ import org.eclipse.jgit.transport.RefSpec
|
||||
import java.util.Date
|
||||
import java.util.concurrent.TimeUnit
|
||||
import org.cache2k.{Cache, Cache2kBuilder}
|
||||
import org.eclipse.jgit.api.errors._
|
||||
import org.eclipse.jgit.api.errors.*
|
||||
import org.eclipse.jgit.diff.{DiffEntry, DiffFormatter, RawTextComparator}
|
||||
import org.eclipse.jgit.dircache.DirCacheEntry
|
||||
import org.eclipse.jgit.util.io.DisabledOutputStream
|
||||
@@ -39,7 +39,7 @@ object JGitUtil {
|
||||
private implicit val objectDatabaseReleasable: Releasable[ObjectDatabase] =
|
||||
_.close()
|
||||
|
||||
private def isCacheEnabled(): Boolean =
|
||||
private def isCacheEnabled: Boolean =
|
||||
!ConfigUtil.getConfigValue[Boolean]("gitbucket.disableCache").getOrElse(false)
|
||||
|
||||
/**
|
||||
@@ -90,12 +90,12 @@ object JGitUtil {
|
||||
|
||||
/**
|
||||
* The verified gpg sign data.
|
||||
* @param signedUser
|
||||
* @param signedKeyId
|
||||
*/
|
||||
case class GpgVerifyInfo(signedUser: String, signedKeyId: String)
|
||||
|
||||
private def getSignTarget(rev: RevCommit): Array[Byte] = {
|
||||
logger.debug(s"getSignTarget(${rev})")
|
||||
|
||||
val ascii = "ASCII"
|
||||
val os = new ByteArrayOutputStream()
|
||||
val w = new OutputStreamWriter(os, rev.getEncoding)
|
||||
@@ -127,7 +127,7 @@ object JGitUtil {
|
||||
|
||||
os.write('\n')
|
||||
|
||||
if (!rev.getFullMessage.isEmpty) {
|
||||
if (rev.getFullMessage.nonEmpty) {
|
||||
w.write(rev.getFullMessage)
|
||||
w.flush()
|
||||
}
|
||||
@@ -168,7 +168,7 @@ object JGitUtil {
|
||||
rev.getName,
|
||||
rev.getShortMessage,
|
||||
rev.getFullMessage,
|
||||
rev.getParents().map(_.name).toList,
|
||||
rev.getParents.map(_.name).toList,
|
||||
rev.getAuthorIdent.getWhen,
|
||||
rev.getAuthorIdent.getName,
|
||||
rev.getAuthorIdent.getEmailAddress,
|
||||
@@ -181,9 +181,9 @@ object JGitUtil {
|
||||
None
|
||||
)
|
||||
|
||||
val summary = getSummaryMessage(fullMessage, shortMessage)
|
||||
val summary: String = getSummaryMessage(fullMessage, shortMessage)
|
||||
|
||||
val description = {
|
||||
val description: Option[String] = {
|
||||
val i = fullMessage.trim.indexOf('\n')
|
||||
if (i >= 0) {
|
||||
Some(fullMessage.trim.substring(i).trim)
|
||||
@@ -216,8 +216,15 @@ object JGitUtil {
|
||||
* @param size total size of object in bytes
|
||||
* @param content the string content
|
||||
* @param charset the character encoding
|
||||
* @param hasBom true if the content has UTF-8 BOM
|
||||
*/
|
||||
case class ContentInfo(viewType: String, size: Option[Long], content: Option[String], charset: Option[String]) {
|
||||
case class ContentInfo(
|
||||
viewType: String,
|
||||
size: Option[Long],
|
||||
content: Option[String],
|
||||
charset: Option[String],
|
||||
hasBom: Boolean = false
|
||||
) {
|
||||
|
||||
/**
|
||||
* the line separator of this content ("LF" or "CRLF")
|
||||
@@ -285,16 +292,18 @@ object JGitUtil {
|
||||
* @return the RevCommit for the specified commit or tag
|
||||
*/
|
||||
def getRevCommitFromId(git: Git, objectId: ObjectId): RevCommit = {
|
||||
logger.debug(s"getRevCommitFromId(${git}, ${objectId})")
|
||||
|
||||
val revWalk = new RevWalk(git.getRepository)
|
||||
val revCommit = revWalk.parseAny(objectId) match {
|
||||
case r: RevTag => revWalk.parseCommit(r.getObject)
|
||||
case _ => revWalk.parseCommit(objectId)
|
||||
}
|
||||
revWalk.dispose
|
||||
revWalk.dispose()
|
||||
revCommit
|
||||
}
|
||||
|
||||
private val cache: Cache[String, Int] = if (isCacheEnabled()) {
|
||||
private val cache: Cache[String, Int] = if (isCacheEnabled) {
|
||||
Cache2kBuilder
|
||||
.of(classOf[String], classOf[Int])
|
||||
.name("commit-count")
|
||||
@@ -303,7 +312,7 @@ object JGitUtil {
|
||||
.build()
|
||||
} else null
|
||||
|
||||
private val objectCommitCache: Cache[ObjectId, RevCommit] = if (isCacheEnabled()) {
|
||||
private val objectCommitCache: Cache[ObjectId, RevCommit] = if (isCacheEnabled) {
|
||||
Cache2kBuilder
|
||||
.of(classOf[ObjectId], classOf[RevCommit])
|
||||
.name("object-commit")
|
||||
@@ -312,7 +321,9 @@ object JGitUtil {
|
||||
} else null
|
||||
|
||||
def removeCache(git: Git): Unit = {
|
||||
if (isCacheEnabled()) {
|
||||
logger.debug(s"removeCache(${git})")
|
||||
|
||||
if (isCacheEnabled) {
|
||||
val dir = git.getRepository.getDirectory
|
||||
val keyPrefix = dir.getAbsolutePath + "@"
|
||||
|
||||
@@ -329,9 +340,11 @@ object JGitUtil {
|
||||
* If the specified branch has over 10000 commits, this method returns 100001.
|
||||
*/
|
||||
def getCommitCount(git: Git, branch: String, max: Int = 10001): Int = {
|
||||
logger.debug(s"getCommitCount(${git}, ${branch}, ${max})")
|
||||
|
||||
val dir = git.getRepository.getDirectory
|
||||
|
||||
if (isCacheEnabled()) {
|
||||
if (isCacheEnabled) {
|
||||
val key = dir.getAbsolutePath + "@" + branch
|
||||
val entry = cache.getEntry(key)
|
||||
|
||||
@@ -354,6 +367,8 @@ object JGitUtil {
|
||||
* Returns the repository information. It contains branch names and tag names.
|
||||
*/
|
||||
def getRepositoryInfo(owner: String, repository: String): RepositoryInfo = {
|
||||
logger.debug(s"getRepositoryInfo(${owner}, ${repository})")
|
||||
|
||||
Using.resource(Git.open(getRepositoryDir(owner, repository))) { git =>
|
||||
try {
|
||||
RepositoryInfo(
|
||||
@@ -387,7 +402,7 @@ object JGitUtil {
|
||||
)
|
||||
} catch {
|
||||
// not initialized
|
||||
case e: NoHeadException => RepositoryInfo(owner, repository, Nil, Nil)
|
||||
case _: NoHeadException => RepositoryInfo(owner, repository, Nil, Nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -411,6 +426,10 @@ object JGitUtil {
|
||||
commitCount: Int = 0,
|
||||
maxFiles: Int = 5
|
||||
): List[FileInfo] = {
|
||||
logger.debug(
|
||||
s"getFileList(${git}, ${revision}, ${path}, ${baseUrl}, ${commitCount}, ${maxFiles})"
|
||||
)
|
||||
|
||||
Using.resource(new RevWalk(git.getRepository)) { revWalk =>
|
||||
val objectId = git.getRepository.resolve(revision)
|
||||
if (objectId == null) return Nil
|
||||
@@ -424,7 +443,7 @@ object JGitUtil {
|
||||
} else {
|
||||
val treeWalk = TreeWalk.forPath(git.getRepository, path, rev.getTree)
|
||||
if (treeWalk != null) {
|
||||
treeWalk.enterSubtree
|
||||
treeWalk.enterSubtree()
|
||||
Using.resource(treeWalk)(f)
|
||||
}
|
||||
}
|
||||
@@ -435,7 +454,7 @@ object JGitUtil {
|
||||
): (ObjectId, FileMode, String, String, Option[String], Option[RevCommit]) =
|
||||
tuple match {
|
||||
case (oid, FileMode.TREE, name, path, _, commit) =>
|
||||
(Using.resource(new TreeWalk(git.getRepository)) { walk =>
|
||||
Using.resource(new TreeWalk(git.getRepository)) { walk =>
|
||||
walk.addTree(oid)
|
||||
// single tree child, or None
|
||||
if (walk.next() && walk.getFileMode(0) == FileMode.TREE) {
|
||||
@@ -452,7 +471,7 @@ object JGitUtil {
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}) match {
|
||||
} match {
|
||||
case Some(child) => simplifyPath(child)
|
||||
case _ => tuple
|
||||
}
|
||||
@@ -468,7 +487,7 @@ object JGitUtil {
|
||||
(id, mode, name, path, opt, None)
|
||||
} else if (commitCount < 10000) {
|
||||
(id, mode, name, path, opt, Some(getCommit(path)))
|
||||
} else if (isCacheEnabled()) {
|
||||
} else if (isCacheEnabled) {
|
||||
// Use in-memory cache if the commit count is too big.
|
||||
val cached = objectCommitCache.getEntry(id)
|
||||
if (cached == null) {
|
||||
@@ -486,6 +505,8 @@ object JGitUtil {
|
||||
}
|
||||
|
||||
def getCommit(path: String): RevCommit = {
|
||||
logger.debug(s"getCommit(${path})")
|
||||
|
||||
git
|
||||
.log()
|
||||
.addPath(path)
|
||||
@@ -502,9 +523,13 @@ object JGitUtil {
|
||||
val linkUrl = if (treeWalk.getFileMode(0) == FileMode.GITLINK) {
|
||||
getSubmodules(git, revCommit.getTree, baseUrl).find(_.path == treeWalk.getPathString).map(_.viewerUrl)
|
||||
} else None
|
||||
fileList +:= (treeWalk.getObjectId(0), treeWalk.getFileMode(
|
||||
0
|
||||
), treeWalk.getNameString, treeWalk.getPathString, linkUrl)
|
||||
fileList +:= (
|
||||
treeWalk.getObjectId(0),
|
||||
treeWalk.getFileMode(0),
|
||||
treeWalk.getNameString,
|
||||
treeWalk.getPathString,
|
||||
linkUrl
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -541,6 +566,8 @@ object JGitUtil {
|
||||
* Returns the first line of the commit message.
|
||||
*/
|
||||
private def getSummaryMessage(fullMessage: String, shortMessage: String): String = {
|
||||
logger.debug(s"getSummaryMessage(${fullMessage}, ${shortMessage})")
|
||||
|
||||
val i = fullMessage.trim.indexOf('\n')
|
||||
val firstLine = if (i >= 0) fullMessage.trim.substring(0, i).trim else fullMessage
|
||||
if (firstLine.length > shortMessage.length) shortMessage else firstLine
|
||||
@@ -550,11 +577,16 @@ object JGitUtil {
|
||||
* get all file list by revision. only file.
|
||||
*/
|
||||
def getTreeId(git: Git, revision: String): Option[String] = {
|
||||
logger.debug(s"getTreeId(${git}, ${revision})")
|
||||
|
||||
Using.resource(new RevWalk(git.getRepository)) { revWalk =>
|
||||
val objectId = git.getRepository.resolve(revision)
|
||||
if (objectId == null) return None
|
||||
val revCommit = revWalk.parseCommit(objectId)
|
||||
Some(revCommit.getTree.name)
|
||||
if (objectId == null) {
|
||||
None
|
||||
} else {
|
||||
val revCommit = revWalk.parseCommit(objectId)
|
||||
Some(revCommit.getTree.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -562,20 +594,20 @@ object JGitUtil {
|
||||
* get all file list by tree object id.
|
||||
*/
|
||||
def getAllFileListByTreeId(git: Git, treeId: String): List[String] = {
|
||||
Using.resource(new RevWalk(git.getRepository)) { revWalk =>
|
||||
val objectId = git.getRepository.resolve(treeId + "^{tree}")
|
||||
if (objectId == null) return Nil
|
||||
Using.resource(new TreeWalk(git.getRepository)) { treeWalk =>
|
||||
treeWalk.addTree(objectId)
|
||||
treeWalk.setRecursive(true)
|
||||
var ret: List[String] = Nil
|
||||
if (treeWalk != null) {
|
||||
while (treeWalk.next()) {
|
||||
ret +:= treeWalk.getPathString
|
||||
}
|
||||
logger.debug(s"getAllFileListByTreeId(${git}, ${treeId})")
|
||||
|
||||
val objectId = git.getRepository.resolve(treeId + "^{tree}")
|
||||
if (objectId == null) return Nil
|
||||
Using.resource(new TreeWalk(git.getRepository)) { treeWalk =>
|
||||
treeWalk.addTree(objectId)
|
||||
treeWalk.setRecursive(true)
|
||||
var ret: List[String] = Nil
|
||||
if (treeWalk != null) {
|
||||
while (treeWalk.next()) {
|
||||
ret +:= treeWalk.getPathString
|
||||
}
|
||||
ret.reverse
|
||||
}
|
||||
ret.reverse
|
||||
}
|
||||
}
|
||||
|
||||
@@ -596,6 +628,8 @@ object JGitUtil {
|
||||
limit: Int = 0,
|
||||
path: String = ""
|
||||
): Either[String, (List[CommitInfo], Boolean)] = {
|
||||
logger.debug(s"getCommitLog(${git}, ${revision}, ${page}, ${limit}, ${path})")
|
||||
|
||||
val fixedPage = if (page <= 0) 1 else page
|
||||
|
||||
@scala.annotation.tailrec
|
||||
@@ -604,22 +638,21 @@ object JGitUtil {
|
||||
count: Int,
|
||||
logs: List[CommitInfo]
|
||||
): (List[CommitInfo], Boolean) =
|
||||
i.hasNext match {
|
||||
case true if (limit <= 0 || logs.size < limit) => {
|
||||
val commit = i.next
|
||||
getCommitLog(
|
||||
i,
|
||||
count + 1,
|
||||
if (limit <= 0 || (fixedPage - 1) * limit <= count) logs :+ new CommitInfo(commit) else logs
|
||||
)
|
||||
}
|
||||
case _ => (logs, i.hasNext)
|
||||
if (i.hasNext && limit <= 0 || logs.size < limit) {
|
||||
val commit = i.next
|
||||
getCommitLog(
|
||||
i,
|
||||
count + 1,
|
||||
if (limit <= 0 || (fixedPage - 1) * limit <= count) logs :+ new CommitInfo(commit) else logs
|
||||
)
|
||||
} else {
|
||||
(logs, i.hasNext)
|
||||
}
|
||||
|
||||
Using.resource(new RevWalk(git.getRepository)) { revWalk =>
|
||||
val objectId = git.getRepository.resolve(revision)
|
||||
if (objectId == null) {
|
||||
Left(s"${revision} can't be resolved.")
|
||||
Left(s"$revision can't be resolved.")
|
||||
} else {
|
||||
revWalk.markStart(revWalk.parseCommit(objectId))
|
||||
if (path.nonEmpty) {
|
||||
@@ -633,18 +666,19 @@ object JGitUtil {
|
||||
def getCommitLogs(git: Git, begin: String, includesLastCommit: Boolean = false)(
|
||||
endCondition: RevCommit => Boolean
|
||||
): List[CommitInfo] = {
|
||||
logger.debug(s"getCommitLogs(${git}, ${begin}, ${includesLastCommit})")
|
||||
|
||||
@scala.annotation.tailrec
|
||||
def getCommitLog(i: java.util.Iterator[RevCommit], logs: List[CommitInfo]): List[CommitInfo] =
|
||||
i.hasNext match {
|
||||
case true => {
|
||||
val revCommit = i.next
|
||||
if (endCondition(revCommit)) {
|
||||
if (includesLastCommit) logs :+ new CommitInfo(revCommit) else logs
|
||||
} else {
|
||||
getCommitLog(i, logs :+ new CommitInfo(revCommit))
|
||||
}
|
||||
if (i.hasNext) {
|
||||
val revCommit = i.next
|
||||
if (endCondition(revCommit)) {
|
||||
if (includesLastCommit) logs :+ new CommitInfo(revCommit) else logs
|
||||
} else {
|
||||
getCommitLog(i, logs :+ new CommitInfo(revCommit))
|
||||
}
|
||||
case false => logs
|
||||
} else {
|
||||
logs
|
||||
}
|
||||
|
||||
Using.resource(new RevWalk(git.getRepository)) { revWalk =>
|
||||
@@ -653,17 +687,64 @@ object JGitUtil {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the commit list between two revisions.
|
||||
* `to` and `from` must be valid revision strings.
|
||||
*
|
||||
* @see [[org.eclipse.jgit.lib.Repository#resolve]]
|
||||
* @param git the Git object
|
||||
* @param from Must refer to a valid commit object.
|
||||
* @param to Must refer to a valid commit object.
|
||||
* @return The commits before 'to', that are not already present in the tree of 'from'.
|
||||
*/
|
||||
def getCommitLog(git: Git, from: String, to: String): List[CommitInfo] = {
|
||||
logger.debug(s"getCommitLog(${git}, ${from}, ${to})")
|
||||
|
||||
def resolveString(name: String): ObjectId = {
|
||||
val objectId = git.getRepository.resolve(name)
|
||||
git.getRepository.open(objectId).getType match {
|
||||
case Constants.OBJ_COMMIT => objectId
|
||||
case Constants.OBJ_TAG =>
|
||||
val ref = git.getRepository.getRefDatabase.findRef(name)
|
||||
git.getRepository.getRefDatabase.peel(ref).getPeeledObjectId
|
||||
case _ => ObjectId.zeroId()
|
||||
}
|
||||
}
|
||||
|
||||
getCommitLog(git, resolveString(from), resolveString(to))
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the commit list between two revisions.
|
||||
*
|
||||
* @param git the Git object
|
||||
* @param from the from revision
|
||||
* @param to the to revision
|
||||
* @return the commit list
|
||||
* @param from Must refer to a valid commit object.
|
||||
* @param to Must refer to a valid commit object.
|
||||
* @return The commits before 'to', that are not already present in the tree of 'from'.
|
||||
*/
|
||||
// TODO swap parameters 'from' and 'to'!?
|
||||
def getCommitLog(git: Git, from: String, to: String): List[CommitInfo] =
|
||||
getCommitLogs(git, to)(_.getName == from)
|
||||
def getCommitLog(git: Git, from: ObjectId, to: ObjectId): List[CommitInfo] =
|
||||
Option(from)
|
||||
.filter(f => f != ObjectId.zeroId)
|
||||
// find the common ancestor of the two commits
|
||||
.flatMap(f =>
|
||||
git
|
||||
.log()
|
||||
.add(f)
|
||||
.add(to)
|
||||
.setRevFilter(RevFilter.MERGE_BASE)
|
||||
.call()
|
||||
.asScala
|
||||
.headOption
|
||||
)
|
||||
.fold(
|
||||
git.log() // no stop condition when merge base with 'from' is not found
|
||||
)(f => git.log().not(f)) // we have a stop condition (start commit)
|
||||
.add(to)
|
||||
.call()
|
||||
.asScala
|
||||
.map(new CommitInfo(_))
|
||||
.toList
|
||||
.reverse
|
||||
|
||||
/**
|
||||
* Returns the latest RevCommit of the specified path.
|
||||
@@ -685,6 +766,8 @@ object JGitUtil {
|
||||
* @return the list of latest commit
|
||||
*/
|
||||
def getLatestCommitFromPaths(git: Git, paths: List[String], revision: String): Map[String, RevCommit] = {
|
||||
logger.debug(s"getLatestCommitFromPaths(${git}, ${paths}, ${revision})")
|
||||
|
||||
val start = getRevCommitFromId(git, git.getRepository.resolve(revision))
|
||||
paths.flatMap { path =>
|
||||
val commit = git.log.add(start).addPath(path).setMaxCount(1).call.iterator.next
|
||||
@@ -697,6 +780,8 @@ object JGitUtil {
|
||||
}
|
||||
|
||||
def getPatch(git: Git, from: Option[String], to: String): String = {
|
||||
logger.debug(s"getPatch(${git}, ${from}, ${to})")
|
||||
|
||||
val out = new ByteArrayOutputStream()
|
||||
val df = new DiffFormatter(out)
|
||||
df.setRepository(git.getRepository)
|
||||
@@ -710,6 +795,8 @@ object JGitUtil {
|
||||
}
|
||||
|
||||
private def getDiffEntries(git: Git, from: Option[String], to: String): Seq[DiffEntry] = {
|
||||
logger.debug(s"getDiffEntries(${git}, ${from}, ${to})")
|
||||
|
||||
Using.resource(new RevWalk(git.getRepository)) { revWalk =>
|
||||
val df = new DiffFormatter(DisabledOutputStream.INSTANCE)
|
||||
df.setRepository(git.getRepository)
|
||||
@@ -733,6 +820,8 @@ object JGitUtil {
|
||||
}
|
||||
|
||||
def getParentCommitId(git: Git, id: String): Option[String] = {
|
||||
logger.debug(s"getParentCommitId(${git}, ${id})")
|
||||
|
||||
Using.resource(new RevWalk(git.getRepository)) { revWalk =>
|
||||
val commit = revWalk.parseCommit(git.getRepository.resolve(id))
|
||||
commit.getParentCount match {
|
||||
@@ -743,6 +832,8 @@ object JGitUtil {
|
||||
}
|
||||
|
||||
def getDiff(git: Git, from: Option[String], to: String, path: String): Option[DiffInfo] = {
|
||||
logger.debug(s"getDiff(${git}, ${from}, ${to}, ${path})")
|
||||
|
||||
getDiffEntries(git, from, to).find(_.getNewPath == path).map { diff =>
|
||||
val oldIsImage = FileUtil.isImage(diff.getOldPath)
|
||||
val newIsImage = FileUtil.isImage(diff.getNewPath)
|
||||
@@ -774,6 +865,10 @@ object JGitUtil {
|
||||
maxFiles: Int = 100,
|
||||
maxLines: Int = 1000
|
||||
): List[DiffInfo] = {
|
||||
logger.debug(
|
||||
s"getDiffs(${git}, ${from.getOrElse("")}, ${to}, ${fetchContent}, ${makePatch}, ${maxFiles}, ${maxLines})"
|
||||
)
|
||||
|
||||
val diffs = getDiffEntries(git, from, to)
|
||||
diffs.map { diff =>
|
||||
if (maxFiles > 0 && diffs.size > maxFiles) { // Don't show diff if there are more than maxFiles
|
||||
@@ -820,13 +915,17 @@ object JGitUtil {
|
||||
}
|
||||
|
||||
private def getTextContent(git: Git, objectId: ObjectId): Option[String] = {
|
||||
logger.debug(s"getTextContent(${git}, ${objectId})")
|
||||
|
||||
JGitUtil
|
||||
.getContentFromId(git, objectId, false)
|
||||
.getContentFromId(git, objectId, fetchLargeFile = false)
|
||||
.filter(FileUtil.isText)
|
||||
.map(convertFromByteArray)
|
||||
}
|
||||
|
||||
private def makePatchFromDiffEntry(git: Git, diff: DiffEntry): String = {
|
||||
logger.debug(s"makePatchFromDiffEntry(${git}, ${diff})")
|
||||
|
||||
val out = new ByteArrayOutputStream()
|
||||
Using.resource(new DiffFormatter(out)) { formatter =>
|
||||
formatter.setRepository(git.getRepository)
|
||||
@@ -846,10 +945,7 @@ object JGitUtil {
|
||||
.getRefsByPrefix(Constants.R_HEADS)
|
||||
.asScala
|
||||
.filter { e =>
|
||||
(revWalk.isMergedInto(
|
||||
commit,
|
||||
revWalk.parseCommit(e.getObjectId)
|
||||
))
|
||||
revWalk.isMergedInto(commit, revWalk.parseCommit(e.getObjectId))
|
||||
}
|
||||
.map { e =>
|
||||
e.getName.substring(Constants.R_HEADS.length)
|
||||
@@ -862,6 +958,8 @@ object JGitUtil {
|
||||
* Returns the list of tags which pointed on the specified commit.
|
||||
*/
|
||||
def getTagsOnCommit(git: Git, commitId: String): List[String] = {
|
||||
logger.debug(s"getTagsOnCommit(${git}, ${commitId})")
|
||||
|
||||
git.getRepository.getAllRefsByPeeledObjectId.asScala
|
||||
.get(git.getRepository.resolve(commitId + "^0"))
|
||||
.map {
|
||||
@@ -888,10 +986,7 @@ object JGitUtil {
|
||||
.getRefsByPrefix(Constants.R_TAGS)
|
||||
.asScala
|
||||
.filter { e =>
|
||||
(revWalk.isMergedInto(
|
||||
commit,
|
||||
revWalk.parseCommit(e.getObjectId)
|
||||
))
|
||||
revWalk.isMergedInto(commit, revWalk.parseCommit(e.getObjectId))
|
||||
}
|
||||
.map { e =>
|
||||
e.getName.substring(Constants.R_TAGS.length)
|
||||
@@ -916,9 +1011,11 @@ object JGitUtil {
|
||||
def isEmpty(git: Git): Boolean = git.getRepository.resolve(Constants.HEAD) == null
|
||||
|
||||
private def setReceivePack(repository: org.eclipse.jgit.lib.Repository): Unit = {
|
||||
logger.debug(s"setReceivePack(${repository})")
|
||||
|
||||
val config = repository.getConfig
|
||||
config.setBoolean("http", null, "receivepack", true)
|
||||
config.save
|
||||
config.save()
|
||||
}
|
||||
|
||||
def getDefaultBranch(
|
||||
@@ -926,6 +1023,8 @@ object JGitUtil {
|
||||
repository: RepositoryService.RepositoryInfo,
|
||||
revstr: String = ""
|
||||
): Option[(ObjectId, String)] = {
|
||||
logger.debug(s"getDefaultBranch(${git}, ${repository})")
|
||||
|
||||
Seq(
|
||||
Some(if (revstr.isEmpty) repository.repository.defaultBranch else revstr),
|
||||
repository.branchList.headOption
|
||||
@@ -935,7 +1034,9 @@ object JGitUtil {
|
||||
}.find(_._1 != null)
|
||||
}
|
||||
|
||||
def createTag(git: Git, name: String, message: Option[String], commitId: String) = {
|
||||
def createTag(git: Git, name: String, message: Option[String], commitId: String): Either[String, String] = {
|
||||
logger.debug(s"createTag(${git}, ${message}, ${commitId})")
|
||||
|
||||
try {
|
||||
val objectId: ObjectId = git.getRepository.resolve(commitId)
|
||||
Using.resource(new RevWalk(git.getRepository)) { walk =>
|
||||
@@ -954,7 +1055,9 @@ object JGitUtil {
|
||||
}
|
||||
}
|
||||
|
||||
def createBranch(git: Git, fromBranch: String, newBranch: String) = {
|
||||
def createBranch(git: Git, fromBranch: String, newBranch: String): Either[String, String] = {
|
||||
logger.debug(s"createBranch(${git}, ${fromBranch}, ${newBranch})")
|
||||
|
||||
try {
|
||||
git.branchCreate().setStartPoint(fromBranch).setName(newBranch).call()
|
||||
Right("Branch created.")
|
||||
@@ -966,6 +1069,8 @@ object JGitUtil {
|
||||
}
|
||||
|
||||
def createDirCacheEntry(path: String, mode: FileMode, objectId: ObjectId): DirCacheEntry = {
|
||||
logger.debug(s"createDirCacheEntry(${path}, ${mode}, ${objectId})")
|
||||
|
||||
val entry = new DirCacheEntry(path)
|
||||
entry.setFileMode(mode)
|
||||
entry.setObjectId(objectId)
|
||||
@@ -982,6 +1087,10 @@ object JGitUtil {
|
||||
mailAddress: String,
|
||||
message: String
|
||||
): ObjectId = {
|
||||
logger.debug(
|
||||
s"createNewCommit(${git}, ${inserter}, ${headId}, ${treeId}, ${ref}, ${fullName}, ${mailAddress}, ${message})"
|
||||
)
|
||||
|
||||
val newCommit = new CommitBuilder()
|
||||
newCommit.setCommitter(new PersonIdent(fullName, mailAddress))
|
||||
newCommit.setAuthor(new PersonIdent(fullName, mailAddress))
|
||||
@@ -1008,20 +1117,21 @@ object JGitUtil {
|
||||
* Read submodule information from .gitmodules
|
||||
*/
|
||||
def getSubmodules(git: Git, tree: RevTree, baseUrl: Option[String]): List[SubmoduleInfo] = {
|
||||
logger.debug(s"getSubmodules(${git}, ${tree}, ${baseUrl}")
|
||||
|
||||
val repository = git.getRepository
|
||||
getContentFromPath(git, tree, ".gitmodules", true).map { bytes =>
|
||||
getContentFromPath(git, tree, ".gitmodules", fetchLargeFile = true).map { bytes =>
|
||||
(try {
|
||||
val config = new BlobBasedConfig(repository.getConfig(), bytes)
|
||||
val config = new BlobBasedConfig(repository.getConfig, bytes)
|
||||
config.getSubsections("submodule").asScala.map { module =>
|
||||
val path = config.getString("submodule", module, "path")
|
||||
val url = config.getString("submodule", module, "url")
|
||||
SubmoduleInfo(module, path, url, StringUtil.getRepositoryViewerUrl(url, baseUrl))
|
||||
}
|
||||
} catch {
|
||||
case e: ConfigInvalidException => {
|
||||
logger.error("Failed to load .gitmodules file for " + repository.getDirectory(), e)
|
||||
case e: ConfigInvalidException =>
|
||||
logger.error("Failed to load .gitmodules file for " + repository.getDirectory, e)
|
||||
Nil
|
||||
}
|
||||
}).toList
|
||||
} getOrElse Nil
|
||||
}
|
||||
@@ -1036,12 +1146,14 @@ object JGitUtil {
|
||||
* @return the byte array of content or None if object does not exist
|
||||
*/
|
||||
def getContentFromPath(git: Git, revTree: RevTree, path: String, fetchLargeFile: Boolean): Option[Array[Byte]] = {
|
||||
logger.debug(s"getContentFromPath(${git}, ${revTree}, ${path}, ${fetchLargeFile})")
|
||||
|
||||
@scala.annotation.tailrec
|
||||
def getPathObjectId(path: String, walk: TreeWalk): Option[ObjectId] =
|
||||
walk.next match {
|
||||
case true if (walk.getPathString == path) => Some(walk.getObjectId(0))
|
||||
case true => getPathObjectId(path, walk)
|
||||
case false => None
|
||||
case true if walk.getPathString == path => Some(walk.getObjectId(0))
|
||||
case true => getPathObjectId(path, walk)
|
||||
case false => None
|
||||
}
|
||||
|
||||
Using.resource(new TreeWalk(git.getRepository)) { treeWalk =>
|
||||
@@ -1069,6 +1181,8 @@ object JGitUtil {
|
||||
}
|
||||
|
||||
def getContentSize(loader: ObjectLoader): Long = {
|
||||
logger.debug(s"getContentSize(${loader})")
|
||||
|
||||
if (loader.isLarge) {
|
||||
loader.getSize
|
||||
} else {
|
||||
@@ -1084,17 +1198,21 @@ object JGitUtil {
|
||||
}
|
||||
|
||||
def isLfsPointer(loader: ObjectLoader): Boolean = {
|
||||
logger.debug(s"isLfsPointer(${loader})")
|
||||
|
||||
!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, safeMode: Boolean): ContentInfo = {
|
||||
logger.debug(s"getContentInfo(${git}, ${path}, ${objectId}, ${safeMode})")
|
||||
|
||||
// Viewer
|
||||
Using.resource(git.getRepository.getObjectDatabase) { db =>
|
||||
val loader = db.open(objectId)
|
||||
val isLfs = isLfsPointer(loader)
|
||||
val large = FileUtil.isLarge(loader.getSize)
|
||||
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, fetchLargeFile = false) else None
|
||||
val size = Some(getContentSize(loader))
|
||||
|
||||
if (viewer == "other") {
|
||||
@@ -1104,7 +1222,8 @@ object JGitUtil {
|
||||
"text",
|
||||
size,
|
||||
Some(StringUtil.convertFromByteArray(bytes.get)),
|
||||
Some(StringUtil.detectEncoding(bytes.get))
|
||||
Some(StringUtil.detectEncoding(bytes.get)),
|
||||
StringUtil.hasUtf8Bom(bytes.get)
|
||||
)
|
||||
} else {
|
||||
// binary
|
||||
@@ -1129,7 +1248,7 @@ object JGitUtil {
|
||||
try {
|
||||
Using.resource(git.getRepository.getObjectDatabase) { db =>
|
||||
val loader = db.open(id)
|
||||
if (loader.isLarge || (fetchLargeFile == false && FileUtil.isLarge(loader.getSize))) {
|
||||
if (loader.isLarge || (!fetchLargeFile && FileUtil.isLarge(loader.getSize))) {
|
||||
None
|
||||
} else {
|
||||
Some(loader.getBytes)
|
||||
@@ -1172,6 +1291,8 @@ object JGitUtil {
|
||||
}
|
||||
|
||||
def processTree[T](git: Git, id: ObjectId)(f: (String, CanonicalTreeParser) => T): Seq[T] = {
|
||||
logger.debug(s"processTree(${git}, ${id})")
|
||||
|
||||
Using.resource(new RevWalk(git.getRepository)) { revWalk =>
|
||||
Using.resource(new TreeWalk(git.getRepository)) { treeWalk =>
|
||||
val index = treeWalk.addTree(revWalk.parseTree(id))
|
||||
@@ -1198,8 +1319,12 @@ object JGitUtil {
|
||||
requestRepositoryName: String,
|
||||
requestBranch: String
|
||||
): String = {
|
||||
logger.debug(
|
||||
s"getForkedCommitId(${oldGit}, ${newGit}, ${userName}, ${repositoryName}, ${branch}, ${requestUserName}, ${requestRepositoryName}, ${requestBranch})"
|
||||
)
|
||||
|
||||
val existIds = getAllCommitIds(oldGit)
|
||||
getCommitLogs(newGit, requestBranch, true) { commit =>
|
||||
getCommitLogs(newGit, requestBranch, includesLastCommit = true) { commit =>
|
||||
existIds.contains(commit.name) && getBranchesOfCommit(oldGit, commit.getName).contains(branch)
|
||||
}.head.id
|
||||
}
|
||||
@@ -1223,10 +1348,10 @@ object JGitUtil {
|
||||
) { (oldGit, newGit) =>
|
||||
oldGit.fetch
|
||||
.setRemote(Directory.getRepositoryDir(requestUserName, requestRepositoryName).toURI.toString)
|
||||
.setRefSpecs(new RefSpec(s"refs/heads/${requestBranch}:refs/pull/${issueId}/head").setForceUpdate(true))
|
||||
.setRefSpecs(new RefSpec(s"refs/heads/$requestBranch:refs/pull/$issueId/head").setForceUpdate(true))
|
||||
.call
|
||||
|
||||
val commitIdTo = oldGit.getRepository.resolve(s"refs/pull/${issueId}/head").getName
|
||||
val commitIdTo = oldGit.getRepository.resolve(s"refs/pull/$issueId/head").getName
|
||||
val commitIdFrom = getForkedCommitId(
|
||||
oldGit,
|
||||
newGit,
|
||||
@@ -1249,12 +1374,15 @@ object JGitUtil {
|
||||
* @return the last modified commit of specified path
|
||||
*/
|
||||
def getLastModifiedCommit(git: Git, startCommit: RevCommit, path: String): RevCommit = {
|
||||
logger.debug(s"getLastModifiedCommit(${git}, ${startCommit}, ${path})")
|
||||
|
||||
git.log.add(startCommit).addPath(path).setMaxCount(1).call.iterator.next
|
||||
}
|
||||
|
||||
def getBranchesNoMergeInfo(git: Git, defaultBranch: String): Seq[BranchInfoSimple] = {
|
||||
def getBranchesNoMergeInfo(git: Git): Seq[BranchInfoSimple] = {
|
||||
logger.debug(s"getBranchesNoMergeInfo(${git})")
|
||||
|
||||
val repo = git.getRepository
|
||||
val defaultObject = repo.resolve(defaultBranch)
|
||||
|
||||
git.branchList.call.asScala.map { ref =>
|
||||
val walk = new RevWalk(repo)
|
||||
@@ -1272,6 +1400,8 @@ object JGitUtil {
|
||||
}
|
||||
|
||||
def getBranches(git: Git, defaultBranch: String, origin: Boolean): Seq[BranchInfo] = {
|
||||
logger.debug(s"getBranches(${git}, ${defaultBranch}, ${origin})")
|
||||
|
||||
val repo = git.getRepository
|
||||
val defaultObject = repo.resolve(defaultBranch)
|
||||
|
||||
@@ -1310,6 +1440,8 @@ object JGitUtil {
|
||||
}
|
||||
|
||||
def getBlame(git: Git, id: String, path: String): Iterable[BlameInfo] = {
|
||||
logger.debug(s"getBlame(${git}, ${id}, ${path})")
|
||||
|
||||
Option(git.getRepository.resolve(id))
|
||||
.map { commitId =>
|
||||
val blamer = new org.eclipse.jgit.api.BlameCommand(git.getRepository)
|
||||
@@ -1318,7 +1450,7 @@ object JGitUtil {
|
||||
val blame = blamer.call()
|
||||
var blameMap = Map[String, JGitUtil.BlameInfo]()
|
||||
var idLine = List[(String, Int)]()
|
||||
0.until(blame.getResultContents().size()).foreach { i =>
|
||||
0.until(blame.getResultContents.size()).foreach { i =>
|
||||
val c = blame.getSourceCommit(i)
|
||||
if (!blameMap.contains(c.name)) {
|
||||
blameMap += c.name -> JGitUtil.BlameInfo(
|
||||
@@ -1354,14 +1486,18 @@ object JGitUtil {
|
||||
* @return sha1
|
||||
*/
|
||||
def getShaByRef(owner: String, name: String, revstr: String): Option[String] = {
|
||||
logger.debug(s"getShaByRef(${owner}, ${name}, ${revstr})")
|
||||
|
||||
Using.resource(Git.open(getRepositoryDir(owner, name))) { git =>
|
||||
Option(git.getRepository.resolve(revstr)).map(ObjectId.toString(_))
|
||||
Option(git.getRepository.resolve(revstr)).map(ObjectId.toString)
|
||||
}
|
||||
}
|
||||
|
||||
private def openFile[T](git: Git, repository: RepositoryService.RepositoryInfo, treeWalk: TreeWalk)(
|
||||
f: InputStream => T
|
||||
): T = {
|
||||
logger.debug(s"openFile(${git}, ${repository}, ${treeWalk})")
|
||||
|
||||
val attrs = treeWalk.getAttributes
|
||||
val loader = git.getRepository.open(treeWalk.getObjectId(0))
|
||||
if (attrs.containsKey("filter") && attrs.get("filter").getValue == "lfs") {
|
||||
@@ -1385,12 +1521,16 @@ object JGitUtil {
|
||||
def openFile[T](git: Git, repository: RepositoryService.RepositoryInfo, tree: RevTree, path: String)(
|
||||
f: InputStream => T
|
||||
): T = {
|
||||
logger.debug(s"openFile(${git}, ${repository}, ${tree}, ${path})")
|
||||
|
||||
Using.resource(TreeWalk.forPath(git.getRepository, path, tree)) { treeWalk =>
|
||||
openFile(git, repository, treeWalk)(f)
|
||||
}
|
||||
}
|
||||
|
||||
private def getLfsAttributes(loader: ObjectLoader): Map[String, String] = {
|
||||
logger.debug(s"getLfsAttributes(${loader})")
|
||||
|
||||
val bytes = loader.getCachedBytes
|
||||
val text = new String(bytes, "UTF-8")
|
||||
|
||||
|
||||
@@ -3,9 +3,7 @@ package gitbucket.core.util
|
||||
import gitbucket.core.model.Account
|
||||
import gitbucket.core.service.SystemSettingsService
|
||||
import gitbucket.core.service.SystemSettingsService.Ldap
|
||||
import com.novell.ldap._
|
||||
import java.security.{Provider, Security}
|
||||
import java.util.concurrent.atomic.AtomicReference
|
||||
import com.novell.ldap.*
|
||||
|
||||
import org.slf4j.LoggerFactory
|
||||
|
||||
@@ -19,8 +17,7 @@ object LDAPUtil {
|
||||
private val LDAP_VERSION: Int = LDAPConnection.LDAP_V3
|
||||
private val LDAP_DUMMY_MAL = "@ldap-devnull"
|
||||
|
||||
private val logger = LoggerFactory.getLogger(getClass().getName())
|
||||
private val provider = new AtomicReference[Provider](null)
|
||||
private val logger = LoggerFactory.getLogger(getClass.getName)
|
||||
|
||||
/**
|
||||
* Returns true if mail address ends with "@ldap-devnull"
|
||||
@@ -119,34 +116,10 @@ object LDAPUtil {
|
||||
private def getUserNameFromMailAddress(userName: String): String = {
|
||||
(userName.indexOf('@') match {
|
||||
case i if i >= 0 => userName.substring(0, i)
|
||||
case i => userName
|
||||
case _ => userName
|
||||
}).replaceAll("[^a-zA-Z0-9\\-_.]", "").replaceAll("^[_\\-]", "")
|
||||
}
|
||||
|
||||
private def getSslProvider(): Provider = {
|
||||
import scala.language.existentials
|
||||
|
||||
val cachedInstance = provider.get()
|
||||
if (cachedInstance == null) {
|
||||
val cls =
|
||||
try {
|
||||
Class.forName("com.sun.net.ssl.internal.ssl.Provider")
|
||||
} catch {
|
||||
case e: ClassNotFoundException =>
|
||||
Class.forName("com.ibm.jsse.IBMJSSEProvider")
|
||||
case e: Throwable => throw e
|
||||
}
|
||||
val newInstance = cls
|
||||
.getDeclaredConstructor()
|
||||
.newInstance()
|
||||
.asInstanceOf[Provider]
|
||||
provider.compareAndSet(null, newInstance)
|
||||
newInstance
|
||||
} else {
|
||||
cachedInstance
|
||||
}
|
||||
}
|
||||
|
||||
private def bind[A](
|
||||
host: String,
|
||||
port: Int,
|
||||
@@ -158,9 +131,6 @@ object LDAPUtil {
|
||||
error: String
|
||||
)(f: LDAPConnection => Either[String, A]): Either[String, A] = {
|
||||
if (tls || ssl) {
|
||||
// Dynamically set Sun as the security provider
|
||||
Security.addProvider(getSslProvider())
|
||||
|
||||
if (keystore.compareTo("") != 0) {
|
||||
// Dynamically set the property that JSSE uses to identify
|
||||
// the keystore that holds trusted root certificates
|
||||
@@ -191,7 +161,7 @@ object LDAPUtil {
|
||||
f(conn)
|
||||
|
||||
} catch {
|
||||
case e: Exception => {
|
||||
case e: Exception =>
|
||||
// Provide more information if something goes wrong
|
||||
logger.info("" + e)
|
||||
|
||||
@@ -200,7 +170,6 @@ object LDAPUtil {
|
||||
}
|
||||
// Returns an error message
|
||||
Left(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -222,7 +191,7 @@ object LDAPUtil {
|
||||
entries :+ (try {
|
||||
Option(results.next)
|
||||
} catch {
|
||||
case ex: LDAPReferralException => None // NOTE(tanacasino): Referral follow is off. so ignores it.(for AD)
|
||||
case _: LDAPReferralException => None // NOTE(tanacasino): Referral follow is off. so ignores it.(for AD)
|
||||
})
|
||||
)
|
||||
} else {
|
||||
|
||||
@@ -91,7 +91,10 @@ object StringUtil {
|
||||
* And if given bytes contains UTF-8 BOM, it's removed from returned string.
|
||||
*/
|
||||
def convertFromByteArray(content: Array[Byte]): String =
|
||||
IOUtils.toString(new BOMInputStream(new java.io.ByteArrayInputStream(content)), detectEncoding(content))
|
||||
IOUtils.toString(
|
||||
BOMInputStream.builder().setInputStream(new java.io.ByteArrayInputStream(content)).setInclude(true).get(),
|
||||
detectEncoding(content)
|
||||
)
|
||||
|
||||
def detectEncoding(content: Array[Byte]): String = {
|
||||
val detector = new UniversalDetector(null)
|
||||
@@ -103,6 +106,19 @@ object StringUtil {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Detects if the given byte array starts with UTF-8 BOM (Byte Order Mark).
|
||||
* UTF-8 BOM is the byte sequence: 0xEF 0xBB 0xBF
|
||||
*/
|
||||
def hasUtf8Bom(content: Array[Byte]): Boolean =
|
||||
content.length >= 3 &&
|
||||
(content(0) & 0xff) == 0xef &&
|
||||
(content(1) & 0xff) == 0xbb &&
|
||||
(content(2) & 0xff) == 0xbf
|
||||
|
||||
/** UTF-8 BOM byte sequence */
|
||||
val Utf8Bom: Array[Byte] = Array(0xef.toByte, 0xbb.toByte, 0xbf.toByte)
|
||||
|
||||
/**
|
||||
* Converts line separator in the given content.
|
||||
*
|
||||
|
||||
@@ -47,18 +47,28 @@ trait AvatarImageProvider { self: RequestCache =>
|
||||
}
|
||||
}
|
||||
|
||||
val displayName = if (!context.settings.showFullName) {
|
||||
s"@$userName"
|
||||
} else {
|
||||
if (mailAddress.isEmpty) {
|
||||
getAccountByUserNameFromCache(userName).map(_.fullName).getOrElse(s"@$userName")
|
||||
} else {
|
||||
getAccountByMailAddressFromCache(mailAddress).map(_.fullName).getOrElse(s"@$userName")
|
||||
}
|
||||
}
|
||||
|
||||
if (tooltip) {
|
||||
Html(
|
||||
s"""<img src="${src}" class="${if (size > 20) { "avatar" }
|
||||
else { "avatar-mini" }}" style="width: ${size}px; height: ${size}px;"
|
||||
| alt="@${StringUtil.escapeHtml(userName)}"
|
||||
| data-toggle="tooltip" title="${StringUtil.escapeHtml(userName)}" />""".stripMargin
|
||||
| alt="${StringUtil.escapeHtml(displayName)}"
|
||||
| data-toggle="tooltip" title="${StringUtil.escapeHtml(displayName)}" />""".stripMargin
|
||||
)
|
||||
} else {
|
||||
Html(
|
||||
s"""<img src="${src}" class="${if (size > 20) { "avatar" }
|
||||
else { "avatar-mini" }}" style="width: ${size}px; height: ${size}px;"
|
||||
| alt="@${StringUtil.escapeHtml(userName)}" />""".stripMargin
|
||||
| alt="${StringUtil.escapeHtml(displayName)}" />""".stripMargin
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import gitbucket.core.service.{RepositoryService, RequestCache}
|
||||
import gitbucket.core.util.StringUtil
|
||||
import io.github.gitbucket.markedj._
|
||||
import io.github.gitbucket.markedj.Utils._
|
||||
import gitbucket.core.service.WikiService
|
||||
|
||||
object Markdown {
|
||||
|
||||
@@ -75,7 +76,8 @@ object Markdown {
|
||||
)(implicit val context: Context)
|
||||
extends Renderer(options)
|
||||
with LinkConverter
|
||||
with RequestCache {
|
||||
with RequestCache
|
||||
with WikiService {
|
||||
|
||||
override def heading(text: String, level: Int, raw: String): String = {
|
||||
val id = generateAnchorName(text)
|
||||
@@ -192,7 +194,16 @@ object Markdown {
|
||||
.stripSuffix(".git") + "/blob/" + branch + "/" + urlWithRawParam
|
||||
}
|
||||
} else {
|
||||
repository.httpUrl.replaceFirst("/git/", "/").stripSuffix(".git") + "/wiki/_blob/" + url
|
||||
// URL is being modified to link to the image file on the repository, but users may want to link to the page if the page name is a link.
|
||||
// If the wiki page cannot be retrieved from the url, the blob address is returned; otherwise, the page address is returned.
|
||||
val pathElems = context.currentPath.split("/")
|
||||
val owner = pathElems(1)
|
||||
val repos = pathElems(2)
|
||||
if (getWikiPage(owner, repos, url, getWikiBranch(owner, repos)) == None) {
|
||||
repository.httpUrl.replaceFirst("/git/", "/").stripSuffix(".git") + "/wiki/_blob/" + url
|
||||
} else {
|
||||
repository.httpUrl.replaceFirst("/git/", "/").stripSuffix(".git") + "/wiki/" + url
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,8 +53,8 @@ object helpers extends AvatarImageProvider with LinkConverter with RequestCache
|
||||
def datetimeAgoRecentOnly(date: Date): String = {
|
||||
val duration = new Date().getTime - date.getTime
|
||||
timeUnits.find(tuple => duration / tuple._1 > 0) match {
|
||||
case Some((_, "month")) => s"on ${new SimpleDateFormat("d MMM", Locale.ENGLISH).format(date)}"
|
||||
case Some((_, "year")) => s"on ${new SimpleDateFormat("d MMM yyyy", Locale.ENGLISH).format(date)}"
|
||||
case Some((_, "month")) => s"on ${new SimpleDateFormat("d MMM", Locale.ENGLISH).format(date)}"
|
||||
case Some((_, "year")) => s"on ${new SimpleDateFormat("d MMM yyyy", Locale.ENGLISH).format(date)}"
|
||||
case Some((unitValue, unitString)) =>
|
||||
val value = duration / unitValue
|
||||
s"${value} ${unitString}${if (value > 1) "s" else ""} ago"
|
||||
@@ -101,6 +101,7 @@ object helpers extends AvatarImageProvider with LinkConverter with RequestCache
|
||||
/**
|
||||
* Converts Markdown of Wiki pages to HTML.
|
||||
*/
|
||||
@deprecated("This doesn't apply render plugins. Should use renderMarkup() instead.", "4.45.0")
|
||||
def markdown(
|
||||
markdown: String,
|
||||
repository: RepositoryService.RepositoryInfo,
|
||||
@@ -139,14 +140,29 @@ object helpers extends AvatarImageProvider with LinkConverter with RequestCache
|
||||
repository: RepositoryService.RepositoryInfo,
|
||||
enableWikiLink: Boolean,
|
||||
enableRefsLink: Boolean,
|
||||
enableAnchor: Boolean
|
||||
enableAnchor: Boolean,
|
||||
enableLineBreaks: Boolean,
|
||||
enableTaskList: Boolean,
|
||||
hasWritePermission: Boolean = false
|
||||
)(implicit context: Context): Html = {
|
||||
|
||||
val fileName = filePath.last.toLowerCase
|
||||
val extension = FileUtil.getExtension(fileName)
|
||||
val renderer = PluginRegistry().getRenderer(extension)
|
||||
renderer.render(
|
||||
RenderRequest(filePath, fileContent, branch, repository, enableWikiLink, enableRefsLink, enableAnchor, context)
|
||||
RenderRequest(
|
||||
filePath,
|
||||
fileContent,
|
||||
branch,
|
||||
repository,
|
||||
enableWikiLink,
|
||||
enableRefsLink,
|
||||
enableAnchor,
|
||||
enableLineBreaks,
|
||||
enableTaskList,
|
||||
hasWritePermission,
|
||||
context
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -316,12 +332,30 @@ object helpers extends AvatarImageProvider with LinkConverter with RequestCache
|
||||
*/
|
||||
def assets(path: String)(implicit context: Context): String = s"${context.path}/assets${path}?${hashQuery}"
|
||||
|
||||
def displayUserName(userName: String, mailAddress: String = "")(implicit context: Context): String = {
|
||||
val displayName = if (!context.settings.showFullName) {
|
||||
userName
|
||||
} else {
|
||||
if (mailAddress.isEmpty) {
|
||||
getAccountByUserNameFromCache(userName).map(_.fullName).getOrElse(userName)
|
||||
} else {
|
||||
getAccountByMailAddressFromCache(mailAddress).map(_.fullName).getOrElse(userName)
|
||||
}
|
||||
}
|
||||
if (userName == displayName) {
|
||||
userName
|
||||
} else {
|
||||
s"$userName ($displayName)"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Generates the text link to the account page.
|
||||
* If user does not exist or disabled, this method returns user name as text without link.
|
||||
*/
|
||||
def user(userName: String, mailAddress: String = "", styleClass: String = "")(implicit context: Context): Html =
|
||||
userWithContent(userName, mailAddress, styleClass)(Html(StringUtil.escapeHtml(userName)))
|
||||
userWithContent(userName, mailAddress, styleClass)(Html(StringUtil.escapeHtml(displayUserName(userName, mailAddress))))
|
||||
|
||||
/**
|
||||
* Generates the avatar link to the account page.
|
||||
@@ -511,6 +545,18 @@ object helpers extends AvatarImageProvider with LinkConverter with RequestCache
|
||||
Html(sb.toString())
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility method to enable checkboxes
|
||||
*/
|
||||
def enableCheckbox(html: Html, enable: Boolean): Html = {
|
||||
if (enable) {
|
||||
val re = "(<input\\s+[^<>]*type=\"checkbox\"\\s+[^<>]*)\\s+disabled[^<>]*>".r
|
||||
Html(re.replaceAllIn(html.toString(), "$1>"))
|
||||
} else {
|
||||
html
|
||||
}
|
||||
}
|
||||
|
||||
case class CommentDiffLine(newLine: Option[String], oldLine: Option[String], `type`: String, text: String)
|
||||
|
||||
def appendQueryString(baseUrl: String, queryString: String): String = {
|
||||
|
||||
@@ -56,16 +56,16 @@ $(function(){
|
||||
|
||||
$('#addMember').click(function(){
|
||||
$('#error-members').text('');
|
||||
var userName = $('#memberName').val();
|
||||
const userName = $('#memberName').val();
|
||||
|
||||
// check empty
|
||||
if($.trim(userName) == ''){
|
||||
if($.trim(userName) === ''){
|
||||
return false;
|
||||
}
|
||||
|
||||
// check duplication
|
||||
var exists = $('#member-list li').filter(function(){
|
||||
return $(this).data('name') == userName;
|
||||
const exists = $('#member-list li').filter(function(){
|
||||
return $(this).data('name') === userName;
|
||||
}).length > 0;
|
||||
if(exists){
|
||||
$('#error-members').text('User has been already added.');
|
||||
@@ -75,7 +75,7 @@ $(function(){
|
||||
// check existence
|
||||
$.post('@context.path/_user/existence', { 'userName': userName },
|
||||
function(data, status){
|
||||
if(data == 'user'){
|
||||
if(data === 'user'){
|
||||
addMemberHTML(userName, false);
|
||||
$('#memberName').val('');
|
||||
} else {
|
||||
@@ -90,7 +90,7 @@ $(function(){
|
||||
|
||||
// Don't submit form by ENTER key
|
||||
$('#memberName').keypress(function(e){
|
||||
return !(e.keyCode == 13);
|
||||
return !(e.keyCode === 13);
|
||||
});
|
||||
|
||||
$('#delete').click(function(){
|
||||
@@ -102,11 +102,11 @@ $(function(){
|
||||
}
|
||||
|
||||
function addMemberHTML(userName, isManager){
|
||||
var memberButton = $('<label class="btn btn-default btn-mini"><input type="radio" value="false" name="' + userName + '">Member</label>');
|
||||
const memberButton = $('<label class="btn btn-default btn-mini"><input type="radio" value="false" name="' + userName + '">Member</label>');
|
||||
if(!isManager){
|
||||
memberButton.addClass('active');
|
||||
}
|
||||
var managerButton = $('<label class="btn btn-default btn-mini"><input type="radio" value="true" name="' + userName + '">Manager</label>');
|
||||
const managerButton = $('<label class="btn btn-default btn-mini"><input type="radio" value="true" name="' + userName + '">Manager</label>');
|
||||
if(isManager){
|
||||
managerButton.addClass('active');
|
||||
}
|
||||
@@ -123,8 +123,8 @@ $(function(){
|
||||
}
|
||||
|
||||
function updateMembers(){
|
||||
var members = $('#member-list li').map(function(i, e){
|
||||
var userName = $(e).data('name');
|
||||
const members = $('#member-list li').map(function(i, e){
|
||||
const userName = $(e).data('name');
|
||||
return userName + ':' + $(e).find('label.active input[type=radio]').attr('value');
|
||||
}).get().join(',');
|
||||
$('#members').val(members);
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
</li>
|
||||
@gitbucket.core.plugin.PluginRegistry().getSystemSettingMenus.map { menu =>
|
||||
@menu(context).map { link =>
|
||||
<li@if(active==link.id){ class="active"}>
|
||||
<li class="menu-item-hover @if(active==link.id){active}">
|
||||
<a href="@context.path/@link.path">
|
||||
<i class="menu-icon octicon octicon-@link.icon.getOrElse("plug")"></i>
|
||||
<span>@link.label</span>
|
||||
|
||||
@@ -253,6 +253,21 @@
|
||||
<input type="radio" name="showMailAddress" value="false"@if(!context.settings.showMailAddress){ checked}>
|
||||
<span class="strong">Hide</span> <span class="normal">- Hide mail address in user's profile page.</span>
|
||||
</label>
|
||||
</fieldset>
|
||||
<!--====================================================================-->
|
||||
<!-- Show full name -->
|
||||
<!--====================================================================-->
|
||||
<hr>
|
||||
<label class="strong">Username display on UI</label>
|
||||
<fieldset>
|
||||
<label class="radio">
|
||||
<input type="radio" name="showFullName" value="false"@if(!context.settings.showFullName){ checked}>
|
||||
<span class="strong">User name</span> <span class="normal">- Username is primarily displayed on UI.</span>
|
||||
</label>
|
||||
<label class="radio">
|
||||
<input type="radio" name="showFullName" value="true"@if(context.settings.showFullName){ checked}>
|
||||
<span class="strong">Full name</span> <span class="normal">- Fullname is primarily displayed instead of username on UI.</span>
|
||||
</label>
|
||||
</fieldset>
|
||||
<!--====================================================================-->
|
||||
<!-- File upload -->
|
||||
|
||||
@@ -25,20 +25,23 @@
|
||||
<a href="@context.path/admin/users/@account.userName/_edituser">Edit</a>
|
||||
}
|
||||
</div>
|
||||
<div class="strong">
|
||||
@helpers.avatarLink(account.userName, 20)
|
||||
@if(account.isRemoved){
|
||||
@account.userName
|
||||
} else {
|
||||
<a href="@helpers.url(account.userName)">@account.userName</a>
|
||||
}
|
||||
@if(account.isGroupAccount){
|
||||
(Group)
|
||||
} else {
|
||||
@if(account.isAdmin){
|
||||
(Administrator)
|
||||
<div>
|
||||
<span class="strong">
|
||||
@helpers.avatarLink(account.userName, 20)
|
||||
@if(account.isRemoved){
|
||||
@account.userName
|
||||
} else {
|
||||
(Normal)
|
||||
<a href="@helpers.url(account.userName)">@account.userName</a>
|
||||
}
|
||||
</span>
|
||||
@if(account.isGroupAccount){
|
||||
- Group
|
||||
} else {
|
||||
(@account.fullName)
|
||||
@if(account.isAdmin){
|
||||
- Administrator
|
||||
} else {
|
||||
- Normal
|
||||
}
|
||||
}
|
||||
@if(account.isGroupAccount){
|
||||
@@ -50,10 +53,10 @@
|
||||
<div>
|
||||
<hr>
|
||||
@if(!account.isGroupAccount){
|
||||
<i class="octicon octicon-mail"></i> @account.mailAddress
|
||||
<i class="octicon octicon-mail"></i>@account.mailAddress
|
||||
}
|
||||
@account.url.map { url =>
|
||||
<i class="octicon octicon-home"></i> @url
|
||||
<i class="octicon octicon-home"></i>@url
|
||||
}
|
||||
</div>
|
||||
<div>
|
||||
|
||||
93
src/main/twirl/gitbucket/core/helper/acenavbar.scala.html
Normal file
93
src/main/twirl/gitbucket/core/helper/acenavbar.scala.html
Normal file
@@ -0,0 +1,93 @@
|
||||
@()(implicit context: gitbucket.core.controller.Context)
|
||||
<nav class="navbar navbar-default" style="margin-bottom: 0px; min-height: auto; border-radius: 0px;">
|
||||
<div class="collapse navbar-collapse">
|
||||
<ul class="nav navbar-nav">
|
||||
<li>
|
||||
<div class="btn-group" data-toggle="buttons-radio">
|
||||
<input type="button" id="btn-code" class="btn btn-default btn-sm active navbar-btn" value="Code">
|
||||
<input type="button" id="btn-preview" class="btn btn-default btn-sm navbar-btn" value="Preview">
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
<ul class="nav navbar-nav" style="margin-left: 8px;">
|
||||
<li>
|
||||
<div class="btn-group border" id="markdown-toolbar" style="display: none;">
|
||||
<button class="btn btn-default navbar-btn btn-sm" id="markdown-bold" title="Bold"><i class="fa fa-bold"></i></button>
|
||||
<button class="btn btn-default navbar-btn btn-sm" id="markdown-italic" title="Italic"><i class="fa fa-italic"></i></button>
|
||||
<button class="btn btn-default navbar-btn btn-sm" id="markdown-code" title="Code"><i class="fa fa-code"></i></button>
|
||||
<button class="btn btn-default navbar-btn btn-sm" id="markdown-link" title="Link"><i class="fa fa-link"></i></button>
|
||||
|
||||
<button class="btn btn-default navbar-btn btn-sm" id="markdown-heading" title="Heading" style="margin-left: 4px;"><i class="fa fa-header"></i></button>
|
||||
<button class="btn btn-default navbar-btn btn-sm" id="markdown-quote" title="Quote"><i class="fa fa-quote-right"></i></button>
|
||||
<button class="btn btn-default navbar-btn btn-sm" id="markdown-list-ul" title="Unordered List"><i class="fa fa-list-ul"></i></button>
|
||||
<button class="btn btn-default navbar-btn btn-sm" id="markdown-list-ol" title="Ordered List"><i class="fa fa-list-ol"></i></button>
|
||||
<button class="btn btn-default navbar-btn btn-sm" id="markdown-task" title="Task List"><i class="fa fa-check-square-o"></i></button>
|
||||
<button class="btn btn-default navbar-btn btn-sm" id="markdown-code-block" title="Code Block"><i class="fa fa-file-code-o"></i></button>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
<ul class="nav navbar-nav navbar-right">
|
||||
<li>
|
||||
<select id="aceKeyboardSelect" class="form-control navbar-form input-sm" aria-label="Keyboard" style="margin-right: 4px;">
|
||||
<optgroup label="Keyboard">
|
||||
<option value="">Default</option>
|
||||
<option value="ace/keyboard/emacs">Emacs</option>
|
||||
<option value="ace/keyboard/sublime">Sublime</option>
|
||||
<option value="ace/keyboard/vim">Vim</option>
|
||||
<option value="ace/keyboard/vscode">VSCode</option>
|
||||
</optgroup>
|
||||
</select>
|
||||
</li>
|
||||
<li>
|
||||
<select id="theme" class="form-control navbar-form input-sm" aria-label="Theme" style="margin-right: 4px;">
|
||||
<optgroup label="Editor Theme">
|
||||
<option value="ambiance">Ambiance</option>
|
||||
<option value="chaos">Chaos</option>
|
||||
<option value="chrome">Chrome</option>
|
||||
<option value="clouds">Clouds</option>
|
||||
<option value="clouds_midnight">Clouds Midnight</option>
|
||||
<option value="cobalt">Cobalt</option>
|
||||
<option value="crimson_editor">Crimson</option>
|
||||
<option value="dawn">Dawn</option>
|
||||
<option value="dracula">Dracula</option>
|
||||
<option value="dreamweaver">Dreamweaver</option>
|
||||
<option value="eclipse">Eclipse</option>
|
||||
<option value="github">GitHub</option>
|
||||
<option value="gob">Gob</option>
|
||||
<option value="gruvbox">Gruvbox</option>
|
||||
<option value="idle_fingers">Idle Fingers</option>
|
||||
<option value="iplastic">Iplastic</option>
|
||||
<option value="katzenmilch">Katzenmilch</option>
|
||||
<option value="kr_theme">Kr</option>
|
||||
<option value="kuroir">Kuroir</option>
|
||||
<option value="merbivore">Merbivore</option>
|
||||
<option value="mono_industrial">Mono Industrial</option>
|
||||
<option selected value="monokai">Monokai</option>
|
||||
<option value="nord_dark">Nord Dark</option>
|
||||
<option value="pastel_on_dark">Pastel on Dark</option>
|
||||
<option value="solarized_dark">Solarized Dark</option>
|
||||
<option value="solarized_light">Solarized Light</option>
|
||||
<option value="sqlserver">Sqlserver</option>
|
||||
<option value="terminal">Terminal</option>
|
||||
<option value="textmate">Textmate</option>
|
||||
<option value="tomorrow">Tomorrow</option>
|
||||
<option value="tomorrow_night">Tomorrow Night</option>
|
||||
<option value="tomorrow_night_bright">Tomorrow Night Bright</option>
|
||||
<option value="tomorrow_night_eighties">Tomorrow Night Eighties</option>
|
||||
<option value="twilight">Twilight</option>
|
||||
<option value="vibrant_ink">Vibrant Ink</option>
|
||||
<option value="xcode">Xcode</option>
|
||||
</optgroup>
|
||||
</select>
|
||||
</li>
|
||||
<li>
|
||||
<select id="wrap" class="form-control navbar-form input-sm" aria-label="Wrap" style="margin-right: 15px;">
|
||||
<optgroup label="Line Wrap Mode">
|
||||
<option value="false">No wrap</option>
|
||||
<option value="true">Soft wrap</option>
|
||||
</optgroup>
|
||||
</select>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user